agent-relay-runner 0.127.10 → 0.128.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/monitors/relay-monitor.provisioned.mjs +2 -2
- package/src/adapters/claude-status-detectors.ts +15 -2
- package/src/adapters/claude.ts +7 -8
- package/src/profile-home.ts +1 -1
- package/src/runner-core.ts +6 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-relay-runner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.128.0",
|
|
4
4
|
"description": "Unified provider lifecycle runner for Agent Relay",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"agent-relay-providers": "0.104.4",
|
|
26
|
-
"agent-relay-sdk": "0.2.
|
|
26
|
+
"agent-relay-sdk": "0.2.125",
|
|
27
27
|
"callmux": "0.23.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
@@ -47,8 +47,8 @@ var RUNNER_PROMPT_TEMPLATE_DEFAULTS = [
|
|
|
47
47
|
category: "runner",
|
|
48
48
|
title: "Codex Relay Context",
|
|
49
49
|
description: "Relay context note injected into Codex sessions.",
|
|
50
|
-
template: "[agent-relay] You are connected to Agent Relay, a real-time message bus between agents and users. When you receive a relay message: read it, do what it asks, and reply through the relay when a text response is needed. Use agent-relay /react <messageId> <emoji> for lightweight acknowledgement or approval. If Relay MCP tools are available, prefer relay_reply, relay_get_message, relay_get_thread, relay_send_message, relay_upload_artifact, relay_attach_artifact, relay_agent_status, relay_find_agents, relay_compact_and_resume, relay_recall, relay_spawn_agent, and relay_shutdown_agent. You never need to know or pass your own agent id \u2014 relay fills it from your token; use relay_whoami only
|
|
51
|
-
defaultTemplate: "[agent-relay] You are connected to Agent Relay, a real-time message bus between agents and users. When you receive a relay message: read it, do what it asks, and reply through the relay when a text response is needed. Use agent-relay /react <messageId> <emoji> for lightweight acknowledgement or approval. If Relay MCP tools are available, prefer relay_reply, relay_get_message, relay_get_thread, relay_send_message, relay_upload_artifact, relay_attach_artifact, relay_agent_status, relay_find_agents, relay_compact_and_resume, relay_recall, relay_spawn_agent, and relay_shutdown_agent. You never need to know or pass your own agent id \u2014 relay fills it from your token; use relay_whoami only
|
|
50
|
+
template: "[agent-relay] You are connected to Agent Relay, a real-time message bus between agents and users. When you receive a relay message: read it, do what it asks, and reply through the relay when a text response is needed. Use agent-relay /react <messageId> <emoji> for lightweight acknowledgement or approval. If Relay MCP tools are available, prefer relay_reply, relay_get_message, relay_get_thread, relay_send_message, relay_upload_artifact, relay_attach_artifact, relay_agent_status, relay_find_agents, relay_compact_and_resume, relay_recall, relay_spawn_agent, and relay_shutdown_agent. You never need to know or pass your own agent id \u2014 relay fills it from your token. Your identity is injected at startup as the `## Agent Relay Identity` block; use relay_whoami only for the full verbose identity dump. relay_compact_and_resume is for clean-seam self-resume after a context advisory: pass workingState and optional ruledOut; Relay owns the objective envelope. relay_recall searches your own archived pre-compaction segments by keyword when a discarded detail is needed. relay_spawn_targets / relay_spawn_agent / relay_shutdown_agent only appear if your profile grants spawning (a live-children quota); when present, call relay_spawn_targets FIRST for the live host/provider/model matrix + your quota, then stand up long-living child agents and shut down your own \u2014 find them later with relay_find_agents spawnedBy:me. For replies, prefer relay_reply or relay_send_message; CLI fallback: agent-relay /reply <messageId> \"<your reply>\". If a delivered message says it was truncated, fetch the full body with: agent-relay get-message <messageId>. For command details, run: agent-relay /guide",
|
|
51
|
+
defaultTemplate: "[agent-relay] You are connected to Agent Relay, a real-time message bus between agents and users. When you receive a relay message: read it, do what it asks, and reply through the relay when a text response is needed. Use agent-relay /react <messageId> <emoji> for lightweight acknowledgement or approval. If Relay MCP tools are available, prefer relay_reply, relay_get_message, relay_get_thread, relay_send_message, relay_upload_artifact, relay_attach_artifact, relay_agent_status, relay_find_agents, relay_compact_and_resume, relay_recall, relay_spawn_agent, and relay_shutdown_agent. You never need to know or pass your own agent id \u2014 relay fills it from your token. Your identity is injected at startup as the `## Agent Relay Identity` block; use relay_whoami only for the full verbose identity dump. relay_compact_and_resume is for clean-seam self-resume after a context advisory: pass workingState and optional ruledOut; Relay owns the objective envelope. relay_recall searches your own archived pre-compaction segments by keyword when a discarded detail is needed. relay_spawn_targets / relay_spawn_agent / relay_shutdown_agent only appear if your profile grants spawning (a live-children quota); when present, call relay_spawn_targets FIRST for the live host/provider/model matrix + your quota, then stand up long-living child agents and shut down your own \u2014 find them later with relay_find_agents spawnedBy:me. For replies, prefer relay_reply or relay_send_message; CLI fallback: agent-relay /reply <messageId> \"<your reply>\". If a delivered message says it was truncated, fetch the full body with: agent-relay get-message <messageId>. For command details, run: agent-relay /guide",
|
|
52
52
|
variables: []
|
|
53
53
|
},
|
|
54
54
|
{
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { extractClaudeModelUnavailableMessage } from "agent-relay-sdk";
|
|
2
2
|
import type { ProviderStatusUpdate } from "../adapter";
|
|
3
|
+
import type { ClaudeProbeActivity } from "./claude-session-probe";
|
|
3
4
|
import { buildRateLimitProviderState, parseClaudeRateLimitPane } from "../rate-limit";
|
|
4
5
|
|
|
5
6
|
// LEGACY PANE-SCRAPE FALLBACK (#766/#768) — busy/idle is now driven primarily by
|
|
@@ -19,8 +20,8 @@ import { buildRateLimitProviderState, parseClaudeRateLimitPane } from "../rate-l
|
|
|
19
20
|
// (the cross-version anchor; the "esc to interrupt" hint was already dropped in 2.1.x).
|
|
20
21
|
// FALSE POSITIVES: agent output that literally QUOTES any of these strings (e.g. a
|
|
21
22
|
// transcript discussing "esc to interrupt", or this very comment shown in a pane)
|
|
22
|
-
// reads as ready/busy.
|
|
23
|
-
//
|
|
23
|
+
// reads as ready/busy. The activity reconciler gates on the LAST N lines only (the
|
|
24
|
+
// live footer); direct raw predicates are still used by readiness/wait gates.
|
|
24
25
|
// History: 18067b5 (busy counter), and the readiness footer-vs-banner fix below.
|
|
25
26
|
export function claudePaneLooksReady(text: string): boolean {
|
|
26
27
|
// Claude's startup banner ("Claude Code" / "Welcome back") scrolls off the pane once the
|
|
@@ -43,6 +44,7 @@ export function claudePaneLooksReady(text: string): boolean {
|
|
|
43
44
|
// (ctrl+o to expand)" truncation marker, the idle input box, or the persistent
|
|
44
45
|
// "/btw … without interrupting Claude's current work" queue hint.
|
|
45
46
|
const CLAUDE_BUSY_SPINNER_RE = /…\s*\((?:\d+h\s+)?(?:\d+m\s+)?\d+s\b/;
|
|
47
|
+
const CLAUDE_ACTIVITY_FOOTER_LINES = 10;
|
|
46
48
|
|
|
47
49
|
export function claudePaneIsBusy(text: string): boolean {
|
|
48
50
|
// Claude Code <2.1.x rendered "(esc to interrupt)" in the spinner footer; 2.1.x
|
|
@@ -52,6 +54,17 @@ export function claudePaneIsBusy(text: string): boolean {
|
|
|
52
54
|
return CLAUDE_BUSY_SPINNER_RE.test(text) || text.includes("esc to interrupt");
|
|
53
55
|
}
|
|
54
56
|
|
|
57
|
+
function claudeActivityFooter(text: string): string {
|
|
58
|
+
return text.split("\n").slice(-CLAUDE_ACTIVITY_FOOTER_LINES).join("\n");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function claudeActivityFromProbeAndPane(probe: ClaudeProbeActivity | undefined, pane: string): ClaudeProbeActivity {
|
|
62
|
+
const footer = claudeActivityFooter(pane);
|
|
63
|
+
if (probe === "busy" || claudePaneIsBusy(footer)) return "busy";
|
|
64
|
+
if (claudePaneLooksReady(footer)) return "idle";
|
|
65
|
+
return probe === "idle" ? "unknown" : probe ?? "unknown";
|
|
66
|
+
}
|
|
67
|
+
|
|
55
68
|
// #286: detect the subscription usage-limit modal in the pane and turn it into the
|
|
56
69
|
// provider-neutral rate-limit hold (idle + blocked) that the relay's resume sweep lifts
|
|
57
70
|
// at reset. Mirrors claudeModelUnavailableStatus, but non-terminal (idle, not error).
|
package/src/adapters/claude.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { assembleLaunch, materializeLaunchAssembly, sessionStatusLineSettingsArg
|
|
|
12
12
|
import { claudeProviderMessageText } from "./claude-delivery";
|
|
13
13
|
import { claudeProbeActivity, readManagedClaudeStatus, resolveClaudePid, type ClaudeProbeActivity } from "./claude-session-probe";
|
|
14
14
|
import { evaluateClaudePromptGatePane, initialClaudePromptGatePaneState, type ClaudePromptGatePaneState } from "./claude-prompt-gates";
|
|
15
|
-
import { claudePaneLooksReady, claudePaneIsBusy, claudeRateLimitStatus, claudeConnectionRetryStatus, claudeModelUnavailableStatus } from "./claude-status-detectors";
|
|
15
|
+
import { claudeActivityFromProbeAndPane, claudePaneLooksReady, claudePaneIsBusy, claudeRateLimitStatus, claudeConnectionRetryStatus, claudeModelUnavailableStatus } from "./claude-status-detectors";
|
|
16
16
|
import { ClaudeSessionCapture, collectClaudeTranscriptArchiveSegment, collectClaudeTranscriptSessionEvents } from "./claude-session-capture";
|
|
17
17
|
import { CLAUDE_TMUX_READY_TIMEOUT_MS, captureTmuxPane, submitTextToTmux, tmuxEnvKeys, tmuxSessionName, tmuxSocketName, waitForClaudeInputReady, writeLauncherScript } from "./claude-tmux";
|
|
18
18
|
import { claudePermissionPromptHandler } from "./claude-permission";
|
|
@@ -138,12 +138,11 @@ export class ClaudeAdapter implements ProviderAdapter {
|
|
|
138
138
|
tmuxSocket: socket,
|
|
139
139
|
configHomeEnv: (process.meta?.env as Record<string, string | undefined> | undefined)?.CLAUDE_CONFIG_DIR,
|
|
140
140
|
});
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return "unknown";
|
|
141
|
+
const probe = managed?.reading.available ? claudeProbeActivity(managed.reading.status) : undefined;
|
|
142
|
+
if (probe === "busy") return "busy";
|
|
143
|
+
let pane: string | undefined;
|
|
144
|
+
try { pane = captureTmuxPane(session, socket); } catch { return probe ?? "unknown"; }
|
|
145
|
+
return claudeActivityFromProbeAndPane(probe, pane);
|
|
147
146
|
}
|
|
148
147
|
|
|
149
148
|
// #769 P1-A: an independent pane-busy read, consumed ONLY as a veto on the busy
|
|
@@ -532,7 +531,7 @@ export { tmuxHasSession };
|
|
|
532
531
|
|
|
533
532
|
// Re-export pane-status detectors (moved to ./claude-status-detectors) so existing
|
|
534
533
|
// import paths (including claude.test.ts) continue to resolve via this module.
|
|
535
|
-
export { claudePaneLooksReady, claudePaneIsBusy, claudeRateLimitStatus, claudeConnectionRetryStatus, claudeModelUnavailableStatus } from "./claude-status-detectors";
|
|
534
|
+
export { claudeActivityFromProbeAndPane, claudePaneLooksReady, claudePaneIsBusy, claudeRateLimitStatus, claudeConnectionRetryStatus, claudeModelUnavailableStatus } from "./claude-status-detectors";
|
|
536
535
|
|
|
537
536
|
// Shared shell-quoting; re-exported so `./claude` consumers + tests resolve it.
|
|
538
537
|
export { shellQuote };
|
package/src/profile-home.ts
CHANGED
|
@@ -140,7 +140,7 @@ function seedClaudeConfigIfMissing(claudeHome: string, config: RunnerSpawnConfig
|
|
|
140
140
|
|
|
141
141
|
function readHostClaudeConfig(): Record<string, unknown> | undefined {
|
|
142
142
|
try {
|
|
143
|
-
const hostPath = join(
|
|
143
|
+
const hostPath = join(providerSourceHome("claude"), ".claude.json");
|
|
144
144
|
if (!existsSync(hostPath)) return undefined;
|
|
145
145
|
return JSON.parse(readFileSync(hostPath, "utf8")) as Record<string, unknown>;
|
|
146
146
|
} catch {
|
package/src/runner-core.ts
CHANGED
|
@@ -192,6 +192,7 @@ export class AgentRunner {
|
|
|
192
192
|
private currentTokenProfileId?: string;
|
|
193
193
|
private currentTokenExpiresAt?: number;
|
|
194
194
|
private control?: ControlServer;
|
|
195
|
+
private registeredAt?: number;
|
|
195
196
|
// Stage 2 (#215): the local MCP endpoint the agent connects to, fronting the relay so the
|
|
196
197
|
// Runner owns reconnect/backoff + a durable buffer. Disabled via AGENT_RELAY_MCP_PROXY=0
|
|
197
198
|
// (then the agent connects to the relay directly, Stage-1 behaviour). The proxy secret is the
|
|
@@ -470,7 +471,10 @@ export class AgentRunner {
|
|
|
470
471
|
void this.handleCommand(type, params, commandId, command);
|
|
471
472
|
});
|
|
472
473
|
this.bus.on("error", (code, message) => this.handleBusError(String(code), String(message)));
|
|
473
|
-
await registerWithinDeadline(this.bus, registrationTimeoutMsFromEnv(), this.options.relayUrl, (reason) => logger.fatal("register", reason));
|
|
474
|
+
await registerWithinDeadline(this.bus, registrationTimeoutMsFromEnv(), this.options.relayUrl, (reason) => logger.fatal("register", reason));
|
|
475
|
+
this.registeredAt = Date.now();
|
|
476
|
+
this.writeRunnerInfoFile();
|
|
477
|
+
await initializeRunnerTemplates(this.http);
|
|
474
478
|
this.obligationCache.start();
|
|
475
479
|
this.outbox.start();
|
|
476
480
|
this.sessionOutbox.start();
|
|
@@ -649,6 +653,7 @@ export class AgentRunner {
|
|
|
649
653
|
...(providerTerminalSession(this.process) ? { tmuxSession: providerTerminalSession(this.process) } : {}),
|
|
650
654
|
...(providerTerminalSocket(this.process) ? { tmuxSocket: providerTerminalSocket(this.process) } : {}),
|
|
651
655
|
startedAt: this.options.startedAt,
|
|
656
|
+
...(this.registeredAt ? { registeredAt: this.registeredAt } : {}),
|
|
652
657
|
}, null, 2) + "\n", { mode: 0o600 });
|
|
653
658
|
} catch (error) {
|
|
654
659
|
logger.error("runner", `failed to write runner info file: ${error}`);
|