Skip to content

Commit fec73d4

Browse files
IMNMVclaude
andcommitted
Shared-connection personas, coordination visibility, pilot-2 fixes
ClaudeR 0.12.0 / clauder-mcp 0.14.0. Field bugs from a three-persona game-agent session plus the queued pilot-2 items. Identity tug-of-war fix: personas sharing one MCP connection were renaming each other via set_agent_name (one shared global). New as_agent parameter on send_message, check_messages, and wait_for_message acts as that identity for one call, with a separate read cursor per name. set_agent_name now warns it renames the whole connection; the agent intro states where the current id came from (env, argument, random, or rename) and steers shared connections to as_agent; the coordination protocol documents both cases. Coordination visibility: messaging bypasses R by design, so the console and Shiny panel showed nothing while agents talked. The addin now tails the session event log every 2s: new events echo to the console (respecting print-to-console), append to the session log as comments, and the Agents panel shows a coordination roster with last-seen ages alongside execution agents. Pilot-2 items: check_cross_references handles S-prefixed supplement numbering and exempts extractor-marker ids from orphan reporting when caption declarations exist; Step 3b documents unname() for htest components; Pass 4c requires author-plus-year citation matching. CI: S-prefix crossref regression test. R CMD check: Status OK. pytest 30/30, handshake 40 tools. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 063338c commit fec73d4

11 files changed

Lines changed: 254 additions & 76 deletions

File tree

.github/scripts/checks.R

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,27 @@ r <- tryCatch({
437437
}, error = function(e) conditionMessage(e))
438438
if (isTRUE(r)) pass("cross-restart history: past logs parsed, agent filter works") else fail("past history:", r)
439439

440+
# --- 13b. crossref: S-prefixed supplement numbering, marker orphan exemption ---
441+
r <- tryCatch({
442+
doc <- tempfile(fileext = ".txt")
443+
writeLines(c(
444+
"Supplemental materials. Table S1 reports response times.",
445+
"[Table 1, header] a | b",
446+
"[Table 1, row 2] 1 | 2",
447+
"Table S1. RT descriptives.",
448+
"[Table 2, header] c | d",
449+
"[Table 2, row 2] 3 | 4",
450+
"Table S2. Exploratory correlations."
451+
), doc)
452+
invisible(capture.output(env$check_cross_references(doc)))
453+
reg <- get("crossref_registry", envir = .GlobalEnv)
454+
no_dangling_s1 <- !any(reg$issue == "dangling" & reg$id == "S1")
455+
no_marker_orphans <- !any(reg$issue == "never_referenced" & reg$id %in% c("1", "2"))
456+
s2_orphan <- any(reg$issue == "never_referenced" & reg$id == "S2")
457+
no_dangling_s1 && no_marker_orphans && s2_orphan
458+
}, error = function(e) conditionMessage(e))
459+
if (isTRUE(r)) pass("crossref: S-prefix resolves, marker orphans exempt, S2 orphan real") else fail("crossref S-prefix:", r)
460+
440461
# --- 14. DOI extraction: parenthesized DOIs, trailing junk, prose parens ---
441462
r <- tryCatch({
442463
d <- env$extract_dois(paste(

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: ClaudeR
22
Title: R Integration for Claude AI
3-
Version: 0.11.1
3+
Version: 0.12.0
44
Authors@R: person("Nykko", "Vitali", email = "nykvt@icloud.com", role = c("aut", "cre"))
55
Description: Connects RStudio with Claude AI to enable interactive coding sessions.
66
License: MIT + file LICENSE

R/coordination.R

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,3 +417,35 @@ consensus_banner_needed <- function(session = NULL) {
417417
.claude_consensus_cache$needed <- needed
418418
needed
419419
}
420+
421+
# --- Human observability -----------------------------------------------
422+
# Coordination deliberately bypasses R so a busy session cannot block
423+
# messaging. The cost is that the human sees nothing in the console or the
424+
# addin. These helpers let the addin's refresh loop surface the traffic.
425+
426+
# One-line rendering of a coordination event for console and log display.
427+
format_coord_event <- function(e) {
428+
body_txt <- tryCatch({
429+
if (is.list(e$body) && !is.null(e$body$text)) as.character(e$body$text)
430+
else as.character(jsonlite::toJSON(e$body, auto_unbox = TRUE))
431+
}, error = function(err) "")
432+
if (nchar(body_txt) > 160) body_txt <- paste0(substr(body_txt, 1, 157), "...")
433+
sprintf("[%s] %s -> %s (%s): %s",
434+
substr(e$ts, 12, 19), e$from, e$to, e$type, body_txt)
435+
}
436+
437+
# Compact presence summary from the event log: who has written, how long ago.
438+
coord_roster_text <- function(session = NULL, stale_after = 900) {
439+
evs <- tryCatch(coord_events(session), error = function(err) list())
440+
if (length(evs) == 0) return(NULL)
441+
last <- list()
442+
for (e in evs) if (!is.null(e$from)) last[[e$from]] <- e$ts
443+
now <- Sys.time()
444+
parts <- vapply(names(last), function(nm) {
445+
ts <- suppressWarnings(as.POSIXct(last[[nm]], format = "%Y-%m-%dT%H:%M:%OS"))
446+
if (is.na(ts)) return(sprintf("%s (?)", nm))
447+
ago <- round(as.numeric(difftime(now, ts, units = "secs")))
448+
sprintf("%s (%ss ago%s)", nm, ago, if (ago > stale_after) ", STALE" else "")
449+
}, character(1))
450+
paste(parts, collapse = ", ")
451+
}

R/refcheck.R

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,27 @@ expand_ref_numbers <- function(tail_text) {
3939
# tables/figures/theorems, single letters for appendices. Mixing them lets
4040
# "Table 1 and Figure 2" wrongly swallow the F of "Figure".
4141
scan_ref_class <- function(lines, class_name, declare_patterns, mention_labels,
42-
id_pattern = "\\d+(?:\\.\\d+)*") {
43-
declared <- character(0)
44-
decl_lines <- integer(0)
45-
for (pat in declare_patterns) {
46-
mm <- regexec(pat, lines, perl = TRUE)
47-
for (i in seq_along(mm)) {
48-
if (mm[[i]][1] == -1) next
49-
grp <- regmatches(lines[i], mm[i])[[1]]
50-
if (length(grp) >= 2 && nzchar(grp[2])) {
51-
declared <- c(declared, grp[2])
52-
decl_lines <- c(decl_lines, i)
42+
id_pattern = "\\d+(?:\\.\\d+)*",
43+
marker_patterns = character(0)) {
44+
scan_decl <- function(pats) {
45+
ids <- character(0); at <- integer(0)
46+
for (pat in pats) {
47+
mm <- regexec(pat, lines, perl = TRUE)
48+
for (i in seq_along(mm)) {
49+
if (mm[[i]][1] == -1) next
50+
grp <- regmatches(lines[i], mm[i])[[1]]
51+
if (length(grp) >= 2 && nzchar(grp[2])) {
52+
ids <- c(ids, grp[2]); at <- c(at, i)
53+
}
5354
}
5455
}
56+
list(ids = unique(ids), lines = unique(at))
5557
}
56-
declared <- unique(declared)
57-
decl_lines <- unique(decl_lines)
58+
cap <- scan_decl(declare_patterns)
59+
mark <- scan_decl(marker_patterns)
60+
declared <- unique(c(cap$ids, mark$ids))
61+
declared_caption <- cap$ids
62+
decl_lines <- unique(c(cap$lines, mark$lines))
5863

5964
labels_alt <- paste(mention_labels, collapse = "|")
6065
mention_pat <- paste0("(?i)\\b(", labels_alt, ")\\.?\\s+(", id_pattern,
@@ -75,7 +80,8 @@ scan_ref_class <- function(lines, class_name, declare_patterns, mention_labels,
7580
}
7681
}
7782
}
78-
list(class = class_name, declared = declared, mentions = mentions)
83+
list(class = class_name, declared = declared,
84+
declared_caption = declared_caption, mentions = mentions)
7985
}
8086

8187
#' Check a manuscript's internal cross-references
@@ -101,11 +107,14 @@ check_cross_references <- function(document) {
101107

102108
classes <- list(
103109
scan_ref_class(lines, "Table",
104-
c("^\\[Table (\\d+),", "^Table (\\d+)[.:]"),
105-
c("Table", "Tables", "Tab")),
110+
c("^Table (S?\\d+)[.:]"),
111+
c("Table", "Tables", "Tab"),
112+
id_pattern = "S?\\d+(?:\\.\\d+)*",
113+
marker_patterns = c("^\\[Table (\\d+),")),
106114
scan_ref_class(lines, "Figure",
107-
c("^Figure (\\d+)[.:]", "^Fig\\.? (\\d+)[.:]"),
108-
c("Figure", "Figures", "Fig", "Figs")),
115+
c("^Figure (S?\\d+)[.:]", "^Fig\\.? (S?\\d+)[.:]"),
116+
c("Figure", "Figures", "Fig", "Figs"),
117+
id_pattern = "S?\\d+(?:\\.\\d+)*"),
109118
scan_ref_class(lines, "Equation",
110119
c("^Equation (\\d+)[.:]", "^\\((\\d+)\\)\\s*$"),
111120
c("Equation", "Equations", "Eq", "Eqs")),
@@ -138,8 +147,13 @@ check_cross_references <- function(document) {
138147
}
139148

140149
dangling <- cl$mentions[!(cl$mentions$id %in% cl$declared), , drop = FALSE]
150+
# Orphan reporting uses the author-facing numbering. When caption-style
151+
# declarations exist (e.g. "Table S1."), the extractor's own [Table k]
152+
# markers carry a parallel numbering that the prose never cites, and
153+
# flagging those as never-referenced is noise, not signal.
154+
orphan_base <- if (length(cl$declared_caption) > 0) cl$declared_caption else cl$declared
141155
orphans <- if (cl$class %in% c("Table", "Figure")) {
142-
setdiff(cl$declared, unique(cl$mentions$id))
156+
setdiff(orphan_base, unique(cl$mentions$id))
143157
} else character(0)
144158

145159
report_parts <- c(report_parts, sprintf(

R/ui.R

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -852,11 +852,23 @@ claudeAddin <- function() {
852852
n_agents <- length(agent_ids)
853853
n_exec <- length(entries)
854854

855-
if (n_agents == 0) {
856-
"No agents connected yet"
855+
exec_txt <- if (n_agents == 0) {
856+
"No code executed by agents yet"
857857
} else {
858-
agents_str <- paste(agent_ids, collapse = ", ")
859-
sprintf("Connected: %s\nExecutions: %d", agents_str, n_exec)
858+
sprintf("Executed code: %s\nExecutions: %d",
859+
paste(agent_ids, collapse = ", "), n_exec)
860+
}
861+
862+
# Coordination happens on disk without touching R, so agents that only
863+
# message each other never appear in the execution history. Surface
864+
# them from the event log so the human can see who is actually around.
865+
roster <- if (isTRUE(.claude_server_env$running)) {
866+
coord_roster_text(.claude_server_env$session_name)
867+
} else NULL
868+
if (!is.null(roster)) {
869+
paste0(exec_txt, "\nCoordinating: ", roster)
870+
} else {
871+
exec_txt
860872
}
861873
})
862874

@@ -998,6 +1010,7 @@ claudeAddin <- function() {
9981010
session_name <- trimws(input$session_name)
9991011
if (session_name == "") session_name <- paste0("session_", input$port)
10001012
.claude_server_env$session_name <- session_name
1013+
.claude_server_env$coord_seen <- NULL # re-baseline coordination echo
10011014
write_discovery_file(session_name, input$port, .claude_server_env$token)
10021015

10031016
# Create log file with session name in the filename
@@ -1152,6 +1165,40 @@ claudeAddin <- function() {
11521165
invalidateLater(2000)
11531166
})
11541167

1168+
# Echo coordination traffic to the console and session log. Coordination
1169+
# bypasses the R server by design (a busy session cannot block it), so
1170+
# without this the human sees none of it.
1171+
observe({
1172+
invalidateLater(2000)
1173+
if (!isTRUE(.claude_server_env$running)) return(invisible(NULL))
1174+
evs <- tryCatch(coord_events(.claude_server_env$session_name),
1175+
error = function(e) list())
1176+
if (length(evs) == 0) return(invisible(NULL))
1177+
max_id <- max(vapply(evs, function(e) e$id, integer(1)))
1178+
seen <- .claude_server_env$coord_seen
1179+
if (is.null(seen)) {
1180+
# First look at this log: do not replay history into the console
1181+
.claude_server_env$coord_seen <- max_id
1182+
return(invisible(NULL))
1183+
}
1184+
new_evs <- Filter(function(e) e$id > seen, evs)
1185+
if (length(new_evs) == 0) return(invisible(NULL))
1186+
s <- .claude_server_env$settings
1187+
for (e in new_evs) {
1188+
line <- tryCatch(format_coord_event(e), error = function(err) NULL)
1189+
if (is.null(line)) next
1190+
if (isTRUE(s$print_to_console)) {
1191+
cat("### coordination ###", line, "\n")
1192+
}
1193+
if (isTRUE(s$log_to_file) && !is.null(s$log_file_path) &&
1194+
nzchar(s$log_file_path)) {
1195+
try(cat(sprintf("# [coordination] %s\n", line),
1196+
file = s$log_file_path, append = TRUE), silent = TRUE)
1197+
}
1198+
}
1199+
.claude_server_env$coord_seen <- max_id
1200+
})
1201+
11551202
# Close handler -- just close the UI, keep the server running
11561203
observeEvent(input$done, {
11571204
invisible(stopApp())

clauder-mcp/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "clauder-mcp"
3-
version = "0.13.0"
3+
version = "0.14.0"
44
description = "MCP server connecting AI assistants to RStudio for interactive R coding and data analysis"
55
readme = "README.md"
66
requires-python = ">=3.10"

clauder-mcp/server.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
"url": "https://github.com/IMNMV/ClaudeR",
88
"source": "github"
99
},
10-
"version": "0.13.0",
10+
"version": "0.14.0",
1111
"packages": [
1212
{
1313
"registryType": "pypi",
1414
"identifier": "clauder-mcp",
15-
"version": "0.13.0",
15+
"version": "0.14.0",
1616
"transport": {
1717
"type": "stdio"
1818
}

0 commit comments

Comments
 (0)