agent-relay-runner 0.38.0 → 0.39.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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-relay-runner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"description": "Unified provider lifecycle runner for Agent Relay",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"directory": "runner"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"agent-relay-sdk": "0.2.
|
|
23
|
+
"agent-relay-sdk": "0.2.24"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/bun": "latest",
|
package/src/adapters/claude.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { homedir, tmpdir } from "node:os";
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
|
-
import type
|
|
5
|
+
import { extractClaudeModelUnavailableMessage, type Message } from "agent-relay-sdk";
|
|
6
6
|
import { shellEscape as shellQuote } from "agent-relay-sdk/shell-utils";
|
|
7
7
|
import { tmuxCommand, tmuxHasSession } from "agent-relay-sdk/tmux-utils";
|
|
8
8
|
import { sanitizeFsName } from "agent-relay-sdk/fs-name";
|
|
@@ -21,6 +21,7 @@ export class ClaudeAdapter implements ProviderAdapter {
|
|
|
21
21
|
private statusCb: (status: ProviderStatusUpdate) => void = () => {};
|
|
22
22
|
private tmuxWatcher?: Timer;
|
|
23
23
|
private turnWatcher?: Timer;
|
|
24
|
+
private modelUnavailableReported = false;
|
|
24
25
|
|
|
25
26
|
onStatusChange(cb: (status: ProviderStatusUpdate) => void): void {
|
|
26
27
|
this.statusCb = cb;
|
|
@@ -285,6 +286,7 @@ export class ClaudeAdapter implements ProviderAdapter {
|
|
|
285
286
|
|
|
286
287
|
private async spawnHeadless(config: RunnerSpawnConfig, spawnArgs: SpawnArgs): Promise<ManagedProcess> {
|
|
287
288
|
const { sessionName, socketName, args: tmuxArgs } = this.buildTmuxArgs(config, spawnArgs);
|
|
289
|
+
this.modelUnavailableReported = false;
|
|
288
290
|
|
|
289
291
|
Bun.spawnSync(tmuxCommand(socketName, "kill-session", "-t", sessionName), {
|
|
290
292
|
stdin: "ignore", stdout: "ignore", stderr: "ignore",
|
|
@@ -332,6 +334,19 @@ export class ClaudeAdapter implements ProviderAdapter {
|
|
|
332
334
|
clearInterval(this.tmuxWatcher!);
|
|
333
335
|
this.tmuxWatcher = undefined;
|
|
334
336
|
this.statusCb("offline");
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (this.modelUnavailableReported) return;
|
|
340
|
+
let pane = "";
|
|
341
|
+
try {
|
|
342
|
+
pane = captureTmuxPane(sessionName, socketName);
|
|
343
|
+
} catch {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
const status = claudeModelUnavailableStatus(pane, sessionName);
|
|
347
|
+
if (status) {
|
|
348
|
+
this.modelUnavailableReported = true;
|
|
349
|
+
this.statusCb(status);
|
|
335
350
|
}
|
|
336
351
|
}, 2000);
|
|
337
352
|
}
|
|
@@ -505,6 +520,43 @@ export function claudePaneIsBusy(text: string): boolean {
|
|
|
505
520
|
return CLAUDE_BUSY_SPINNER_RE.test(text) || text.includes("esc to interrupt");
|
|
506
521
|
}
|
|
507
522
|
|
|
523
|
+
export function claudeModelUnavailableStatus(text: string, sessionName?: string): ProviderStatusUpdate | null {
|
|
524
|
+
const message = extractClaudeModelUnavailableMessage(text);
|
|
525
|
+
if (!message) return null;
|
|
526
|
+
return {
|
|
527
|
+
status: "error",
|
|
528
|
+
clear: ["provider-turn", "subagent"],
|
|
529
|
+
providerState: {
|
|
530
|
+
state: "failed",
|
|
531
|
+
reason: "model-unavailable",
|
|
532
|
+
message,
|
|
533
|
+
source: "claude-pane",
|
|
534
|
+
terminal: true,
|
|
535
|
+
...(sessionName ? { sessionName } : {}),
|
|
536
|
+
recommendedAction: "Choose a different Claude model before restarting this agent.",
|
|
537
|
+
},
|
|
538
|
+
metadata: {
|
|
539
|
+
terminalFailureReason: "model-unavailable",
|
|
540
|
+
terminalFailureMessage: message,
|
|
541
|
+
},
|
|
542
|
+
timeline: {
|
|
543
|
+
status: "provider.restart_decision",
|
|
544
|
+
id: `provider-model-unavailable-${Date.now()}`,
|
|
545
|
+
timestamp: Date.now(),
|
|
546
|
+
title: "Provider restart skipped",
|
|
547
|
+
body: message,
|
|
548
|
+
icon: "ti-player-stop",
|
|
549
|
+
metadata: {
|
|
550
|
+
eventType: "provider.restart_decision",
|
|
551
|
+
decision: "stop-surface",
|
|
552
|
+
reason: "model-unavailable",
|
|
553
|
+
modelUnavailable: true,
|
|
554
|
+
modelUnavailableMessage: message,
|
|
555
|
+
},
|
|
556
|
+
},
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
|
|
508
560
|
async function waitForClaudeInputReady(sessionName: string, timeoutMs = CLAUDE_TMUX_READY_TIMEOUT_MS, socketName?: string): Promise<void> {
|
|
509
561
|
const deadline = Date.now() + timeoutMs;
|
|
510
562
|
while (Date.now() < deadline) {
|
package/src/runner.ts
CHANGED
|
@@ -269,6 +269,7 @@ export class AgentRunner {
|
|
|
269
269
|
// Tracks whether the provider is in a legitimate blocked/approval state, so the
|
|
270
270
|
// busy reconciler doesn't mistake a permission prompt for a stuck-busy turn.
|
|
271
271
|
private providerBlocked = false;
|
|
272
|
+
private terminalFailure?: { reason: string; message: string; providerState?: Record<string, unknown> };
|
|
272
273
|
// Reasoning tailer (item 5): streams the in-flight turn's reasoning/tool steps
|
|
273
274
|
// from the Claude transcript into chat as discreet session events.
|
|
274
275
|
private reasoningTail?: { timer: ReturnType<typeof setInterval>; seen: Set<string> };
|
|
@@ -1092,6 +1093,25 @@ export class AgentRunner {
|
|
|
1092
1093
|
} else if (status === "idle") {
|
|
1093
1094
|
this.providerBlocked = false;
|
|
1094
1095
|
}
|
|
1096
|
+
if (typeof update !== "string" && status === "error") {
|
|
1097
|
+
const terminalReason = typeof update.metadata?.terminalFailureReason === "string"
|
|
1098
|
+
? update.metadata.terminalFailureReason
|
|
1099
|
+
: typeof update.providerState?.reason === "string"
|
|
1100
|
+
? update.providerState.reason
|
|
1101
|
+
: "provider-error";
|
|
1102
|
+
const terminalMessage = typeof update.metadata?.terminalFailureMessage === "string"
|
|
1103
|
+
? update.metadata.terminalFailureMessage
|
|
1104
|
+
: typeof update.providerState?.message === "string"
|
|
1105
|
+
? update.providerState.message
|
|
1106
|
+
: "Provider reported an unrecoverable error.";
|
|
1107
|
+
this.terminalFailure = {
|
|
1108
|
+
reason: terminalReason,
|
|
1109
|
+
message: terminalMessage,
|
|
1110
|
+
...(update.providerState ? { providerState: update.providerState } : {}),
|
|
1111
|
+
};
|
|
1112
|
+
} else if (status === "idle" || status === "busy") {
|
|
1113
|
+
this.terminalFailure = undefined;
|
|
1114
|
+
}
|
|
1095
1115
|
if (status === "busy" && reason === "provider-turn") {
|
|
1096
1116
|
if (!this.currentTurnId) {
|
|
1097
1117
|
this.currentTurnId = typeof update !== "string" && update.id ? update.id : crypto.randomUUID();
|
|
@@ -1683,7 +1703,8 @@ export class AgentRunner {
|
|
|
1683
1703
|
const agentStatus = runnerAgentStatus(status);
|
|
1684
1704
|
const activeWork = this.claims.activeWork();
|
|
1685
1705
|
const activeSubagents = activeWork.filter((item) => item.kind === "subagent");
|
|
1686
|
-
const
|
|
1706
|
+
const terminalFailure = this.terminalFailure;
|
|
1707
|
+
const providerState = terminalFailure?.providerState ?? providerStateFromActiveWork(activeWork);
|
|
1687
1708
|
this.bus.setSemanticStatus(status === "offline" || status === "error" ? "idle" : status);
|
|
1688
1709
|
const timelineEvent = this.pendingTimelineEvent;
|
|
1689
1710
|
this.pendingTimelineEvent = undefined;
|
|
@@ -1704,6 +1725,11 @@ export class AgentRunner {
|
|
|
1704
1725
|
lifecycleAction: this.lifecycleAction ?? null,
|
|
1705
1726
|
profile: this.options.profile ?? null,
|
|
1706
1727
|
...(status === "error" ? { terminalStatus: "error" } : {}),
|
|
1728
|
+
...(terminalFailure ? {
|
|
1729
|
+
lastError: terminalFailure.message,
|
|
1730
|
+
terminalFailureReason: terminalFailure.reason,
|
|
1731
|
+
terminalFailureMessage: terminalFailure.message,
|
|
1732
|
+
} : {}),
|
|
1707
1733
|
busyReasons: this.claims.reasons(),
|
|
1708
1734
|
activeWork,
|
|
1709
1735
|
activeSubagents,
|