agent-relay-runner 0.12.3 → 0.12.4
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
CHANGED
package/src/adapters/claude.ts
CHANGED
|
@@ -355,6 +355,13 @@ export function sessionStatusLineSettingsArgs(...argLists: string[][]): string[]
|
|
|
355
355
|
command: "agent-relay context-probe --wrap",
|
|
356
356
|
refreshInterval: 30,
|
|
357
357
|
},
|
|
358
|
+
// Force readable thinking text for managed sessions so the session-mirror can
|
|
359
|
+
// surface reasoning in the dashboard. With showThinkingSummaries:false the API
|
|
360
|
+
// redacts thinking to a signature-only stub (empty text), leaving the transcript
|
|
361
|
+
// tail nothing to mirror. --settings merges per-key, so this overrides only this
|
|
362
|
+
// key for managed sessions — a host rig default of false still governs the
|
|
363
|
+
// operator's own interactive TUI sessions.
|
|
364
|
+
showThinkingSummaries: true,
|
|
358
365
|
})];
|
|
359
366
|
}
|
|
360
367
|
|
|
@@ -448,12 +455,19 @@ export function claudePaneLooksReady(text: string): boolean {
|
|
|
448
455
|
|| text.includes("Claude Code");
|
|
449
456
|
}
|
|
450
457
|
|
|
458
|
+
// The working-spinner footer carries a live elapsed-time counter while a turn is in
|
|
459
|
+
// flight, e.g. "✶ Perambulating… (2m 17s · ↓ 8.7k tokens)" — gerund, "… (", then
|
|
460
|
+
// "[Nh ][Nm ]Ns". Anchored on the gerund ellipsis so it can't match the "… +N lines
|
|
461
|
+
// (ctrl+o to expand)" truncation marker, the idle input box, or the persistent
|
|
462
|
+
// "/btw … without interrupting Claude's current work" queue hint.
|
|
463
|
+
const CLAUDE_BUSY_SPINNER_RE = /…\s*\((?:\d+h\s+)?(?:\d+m\s+)?\d+s\b/;
|
|
464
|
+
|
|
451
465
|
export function claudePaneIsBusy(text: string): boolean {
|
|
452
|
-
// Claude
|
|
453
|
-
//
|
|
454
|
-
//
|
|
455
|
-
//
|
|
456
|
-
return text.includes("esc to interrupt");
|
|
466
|
+
// Claude Code <2.1.x rendered "(esc to interrupt)" in the spinner footer; 2.1.x
|
|
467
|
+
// dropped that hint but kept the "(<elapsed>" counter, which is the stable busy
|
|
468
|
+
// signal across versions. Match either so the busy probe (and the reconciler
|
|
469
|
+
// backstop that depends on it) keep working as the footer wording changes.
|
|
470
|
+
return CLAUDE_BUSY_SPINNER_RE.test(text) || text.includes("esc to interrupt");
|
|
457
471
|
}
|
|
458
472
|
|
|
459
473
|
async function waitForClaudeInputReady(sessionName: string, timeoutMs = CLAUDE_TMUX_READY_TIMEOUT_MS, socketName?: string): Promise<void> {
|
package/src/adapters/codex.ts
CHANGED
|
@@ -453,6 +453,18 @@ export function codexToolSummary(type: string | undefined, item: Record<string,
|
|
|
453
453
|
if (type === "webSearch") {
|
|
454
454
|
return { label: "Search", body: clip(oneLine(item.query) || "web search") };
|
|
455
455
|
}
|
|
456
|
+
if (type === "plan") {
|
|
457
|
+
return { label: "Plan", body: clip(oneLine(item.text) || "updated plan") };
|
|
458
|
+
}
|
|
459
|
+
if (type === "collabAgentToolCall") {
|
|
460
|
+
const tool = stringValue(item.tool) ?? "collab";
|
|
461
|
+
const prompt = oneLine(item.prompt);
|
|
462
|
+
const targets = Array.isArray(item.receiverThreadIds)
|
|
463
|
+
? item.receiverThreadIds.filter((t): t is string => typeof t === "string").length
|
|
464
|
+
: 0;
|
|
465
|
+
const detail = prompt || (targets ? `${targets} agent${targets === 1 ? "" : "s"}` : tool);
|
|
466
|
+
return { label: `Collab/${tool}`, body: clip(detail) };
|
|
467
|
+
}
|
|
456
468
|
return null;
|
|
457
469
|
}
|
|
458
470
|
|
package/src/runner.ts
CHANGED
|
@@ -63,6 +63,9 @@ const CLAIM_RENEW_INTERVAL_MS = 5 * 60 * 1000;
|
|
|
63
63
|
const HTTP_LIVENESS_INTERVAL_MS = 20_000;
|
|
64
64
|
const HTTP_LIVENESS_LOG_INTERVAL_MS = 5 * 60 * 1000;
|
|
65
65
|
const TOKEN_RENEW_RETRY_MS = 60_000;
|
|
66
|
+
// Debounce reactive token recovery so a burst of 401-ing calls in the same window
|
|
67
|
+
// triggers a single re-mint attempt, not one per failing request.
|
|
68
|
+
const REACTIVE_TOKEN_RECOVERY_DEBOUNCE_MS = 10_000;
|
|
66
69
|
const UNEXPECTED_EXIT_WINDOW_MS = 2 * 60 * 1000;
|
|
67
70
|
const RAPID_EXIT_MS = 30 * 1000;
|
|
68
71
|
const MAX_RAPID_UNEXPECTED_EXITS = 3;
|
|
@@ -128,6 +131,7 @@ export class AgentRunner {
|
|
|
128
131
|
private tokenRenewTimer?: Timer;
|
|
129
132
|
private tokenRenewInFlight = false;
|
|
130
133
|
private tokenRenewLastLog?: { key: string; at: number };
|
|
134
|
+
private reactiveTokenRecoveryAt?: number;
|
|
131
135
|
private processStartedAt = 0;
|
|
132
136
|
private providerSessionId = crypto.randomUUID();
|
|
133
137
|
private lifecycleAction?: "shutting-down" | "killing" | "restarting";
|
|
@@ -972,6 +976,7 @@ export class AgentRunner {
|
|
|
972
976
|
});
|
|
973
977
|
} catch (error) {
|
|
974
978
|
this.logRunnerDiagnostic(`session ${input.session.type} capture failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
979
|
+
if (isHttpAuthError(error)) this.recoverRuntimeTokenAfterAuthFailure("session-capture");
|
|
975
980
|
}
|
|
976
981
|
}
|
|
977
982
|
|
|
@@ -1259,6 +1264,25 @@ export class AgentRunner {
|
|
|
1259
1264
|
this.httpLivenessAuthFailed = true;
|
|
1260
1265
|
if (this.httpLivenessTimer) clearInterval(this.httpLivenessTimer);
|
|
1261
1266
|
this.httpLivenessTimer = undefined;
|
|
1267
|
+
// A 401/403 here is the only timely signal that the token died — stopping the
|
|
1268
|
+
// liveness timer means there is no second chance, so recover from THIS failure.
|
|
1269
|
+
this.recoverRuntimeTokenAfterAuthFailure("http-liveness");
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
// A definitive relay auth failure (401/403) means the runtime token is dead right
|
|
1273
|
+
// now — expired, or (the common case) revoked when the relay marked this agent
|
|
1274
|
+
// stale across its own restart/reconnect. The proactive renew timer is keyed to
|
|
1275
|
+
// TTL and structurally cannot catch a revocation, so the auth failure itself must
|
|
1276
|
+
// drive recovery. renewRuntimeToken() prefers an orchestrator re-mint, which heals
|
|
1277
|
+
// even a revoked token. Debounced so a burst of failing calls re-mints once.
|
|
1278
|
+
private recoverRuntimeTokenAfterAuthFailure(source: string): void {
|
|
1279
|
+
if (this.stopped || this.tokenRenewInFlight) return;
|
|
1280
|
+
if (!this.isRuntimeTokenRenewable() && !this.canRemintViaOrchestrator()) return;
|
|
1281
|
+
const now = Date.now();
|
|
1282
|
+
if (this.reactiveTokenRecoveryAt && now - this.reactiveTokenRecoveryAt < REACTIVE_TOKEN_RECOVERY_DEBOUNCE_MS) return;
|
|
1283
|
+
this.reactiveTokenRecoveryAt = now;
|
|
1284
|
+
this.logRunnerDiagnostic(`[runner] relay auth failure on ${source}; recovering runtime token`);
|
|
1285
|
+
void this.renewRuntimeToken();
|
|
1262
1286
|
}
|
|
1263
1287
|
|
|
1264
1288
|
private logHttpLivenessFailure(error: unknown, authFailed: boolean): void {
|
|
@@ -1432,6 +1456,11 @@ export class AgentRunner {
|
|
|
1432
1456
|
this.http.setToken(token);
|
|
1433
1457
|
this.bus.setToken(token);
|
|
1434
1458
|
this.httpLivenessAuthFailed = false;
|
|
1459
|
+
this.reactiveTokenRecoveryAt = undefined;
|
|
1460
|
+
// An earlier auth failure may have stopped the liveness loop; restart it so the
|
|
1461
|
+
// agent reports live again on the fresh token. startHttpLiveness clears any
|
|
1462
|
+
// existing timer first, so this is safe on the normal (proactive) renew path too.
|
|
1463
|
+
this.startHttpLiveness();
|
|
1435
1464
|
this.pendingTimelineEvent = { status, id: record.jti, timestamp: Date.now() };
|
|
1436
1465
|
this.bus.reconnectTransport(status === "runtime-token-reminted" ? "runtime token re-minted" : "runtime token renewed");
|
|
1437
1466
|
this.publishStatus();
|