agent-relay-runner 0.36.2 → 0.38.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.38.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.23"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/bun": "latest",
|
package/src/adapter.ts
CHANGED
|
@@ -157,6 +157,13 @@ export interface ProviderAdapter {
|
|
|
157
157
|
// `options.readyTimeoutMs` lets the runner widen the provider-ready wait for the
|
|
158
158
|
// first (cold-start) delivery vs. a fast re-attempt after a ready signal (#329).
|
|
159
159
|
deliverInitialPrompt?(process: ManagedProcess, prompt: string, options?: { readyTimeoutMs?: number }): Promise<void>;
|
|
160
|
+
// When true, the adapter seeds the spawn-time initial prompt as a launch argument
|
|
161
|
+
// (Claude's positional `claude "<prompt>"`), so it's already delivered the instant the
|
|
162
|
+
// session starts. The runner must then NOT also deliver it post-launch via
|
|
163
|
+
// deliverInitialPrompt — that would double-deliver and re-introduce the send-keys
|
|
164
|
+
// onboarding race (#352). deliverInitialPrompt stays available for mid-session injection
|
|
165
|
+
// (dashboard chat box) and ongoing message delivery, which have no launch-arg equivalent.
|
|
166
|
+
seedsInitialPromptAtLaunch?: boolean;
|
|
160
167
|
deliver(process: ManagedProcess, messages: Message[]): Promise<void>;
|
|
161
168
|
onStatusChange(cb: (status: ProviderStatusUpdate) => void): void;
|
|
162
169
|
// Subscribe to session-mirror events from providers that emit them directly
|
package/src/adapters/claude.ts
CHANGED
|
@@ -15,6 +15,9 @@ import { claudeProviderMessageText } from "./claude-delivery";
|
|
|
15
15
|
|
|
16
16
|
export class ClaudeAdapter implements ProviderAdapter {
|
|
17
17
|
readonly provider = "claude";
|
|
18
|
+
// #352: initial prompt is seeded as Claude's positional launch arg (buildSpawnArgs) — reliable,
|
|
19
|
+
// no send-keys/onboarding race; tells the runner to skip the redundant post-launch delivery.
|
|
20
|
+
readonly seedsInitialPromptAtLaunch = true;
|
|
18
21
|
private statusCb: (status: ProviderStatusUpdate) => void = () => {};
|
|
19
22
|
private tmuxWatcher?: Timer;
|
|
20
23
|
private turnWatcher?: Timer;
|
|
@@ -226,7 +229,11 @@ export class ClaudeAdapter implements ProviderAdapter {
|
|
|
226
229
|
...providerArgs,
|
|
227
230
|
];
|
|
228
231
|
if (config.model) args.push("--model", config.model);
|
|
229
|
-
|
|
232
|
+
// #352: seed the prompt as Claude's positional arg for ALL launches (headless included) —
|
|
233
|
+
// `claude "<prompt>"` seeds the first turn via Claude's own input handling, immune to the
|
|
234
|
+
// send-keys/onboarding race that silently dropped headless spawns. Long/special-char briefs
|
|
235
|
+
// are shell-safe (shellEscape + launcher-script externalization in buildTmuxArgs).
|
|
236
|
+
if (config.prompt) args.push(String(config.prompt));
|
|
230
237
|
return {
|
|
231
238
|
command,
|
|
232
239
|
args,
|
package/src/runner.ts
CHANGED
|
@@ -250,6 +250,7 @@ export class AgentRunner {
|
|
|
250
250
|
// Session-mirror: a synthesized id grouping a turn's reasoning/tool steps and
|
|
251
251
|
// its final response. Set when a provider-turn starts, cleared when it ends.
|
|
252
252
|
private currentTurnId?: string;
|
|
253
|
+
private currentTurnStartedAt?: number;
|
|
253
254
|
// Prompt-echo dedup: a short, time-bounded queue of prompts the runner itself
|
|
254
255
|
// injected (chat box or initial prompt) that are still awaiting their matching
|
|
255
256
|
// UserPromptSubmit echo. A single slot dropped earlier entries when several prompts
|
|
@@ -600,7 +601,13 @@ export class AgentRunner {
|
|
|
600
601
|
|
|
601
602
|
private async deliverInitialPrompt(): Promise<void> {
|
|
602
603
|
const prompt = this.options.prompt?.trim();
|
|
603
|
-
if (prompt)
|
|
604
|
+
if (!prompt) return;
|
|
605
|
+
// #352: adapters that seed the prompt as a launch arg (Claude's positional prompt) already
|
|
606
|
+
// delivered it at session start. Returning BEFORE attemptInitialPromptDelivery avoids a
|
|
607
|
+
// double-delivery AND leaves pendingInitialPrompt unset, so the #329 ready-signal retry never
|
|
608
|
+
// arms — one prompt, one turn. Mid-session injectPrompt + message delivery are unaffected.
|
|
609
|
+
if (this.options.adapter.seedsInitialPromptAtLaunch) return;
|
|
610
|
+
await this.attemptInitialPromptDelivery(prompt, INITIAL_PROMPT_FIRST_READY_TIMEOUT_MS);
|
|
604
611
|
}
|
|
605
612
|
|
|
606
613
|
// Deliver the spawn-time first prompt, surviving a cold-start TUI that isn't input-ready yet
|
|
@@ -1088,12 +1095,14 @@ export class AgentRunner {
|
|
|
1088
1095
|
if (status === "busy" && reason === "provider-turn") {
|
|
1089
1096
|
if (!this.currentTurnId) {
|
|
1090
1097
|
this.currentTurnId = typeof update !== "string" && update.id ? update.id : crypto.randomUUID();
|
|
1098
|
+
this.currentTurnStartedAt = Date.now();
|
|
1091
1099
|
this.sessionLog(`turn started (turn ${this.currentTurnId})`);
|
|
1092
1100
|
}
|
|
1093
1101
|
this.armBusyReconciler();
|
|
1094
1102
|
} else if (status === "idle" && reason === "provider-turn") {
|
|
1095
1103
|
if (this.currentTurnId) this.sessionLog(`turn ended via provider idle (turn ${this.currentTurnId})`);
|
|
1096
1104
|
this.currentTurnId = undefined;
|
|
1105
|
+
this.currentTurnStartedAt = undefined;
|
|
1097
1106
|
this.disarmBusyReconciler();
|
|
1098
1107
|
this.stopReasoningTail();
|
|
1099
1108
|
}
|
|
@@ -1318,7 +1327,10 @@ export class AgentRunner {
|
|
|
1318
1327
|
// deliveries) so those aren't double-posted.
|
|
1319
1328
|
private async handleUserPrompt(input: { prompt: string; transcriptPath?: string }): Promise<void> {
|
|
1320
1329
|
if (input.transcriptPath) this.lastTranscriptPath = input.transcriptPath;
|
|
1321
|
-
if (!this.currentTurnId)
|
|
1330
|
+
if (!this.currentTurnId) {
|
|
1331
|
+
this.currentTurnId = crypto.randomUUID();
|
|
1332
|
+
this.currentTurnStartedAt = Date.now();
|
|
1333
|
+
}
|
|
1322
1334
|
const text = input.prompt.trim();
|
|
1323
1335
|
if (text && !this.isRunnerInjectedPrompt(text)) {
|
|
1324
1336
|
this.sessionLog(`prompt echoed from terminal (${text.length} chars)`);
|
|
@@ -1476,7 +1488,7 @@ export class AgentRunner {
|
|
|
1476
1488
|
if (pendingPrompt) {
|
|
1477
1489
|
replyToMessageId = pendingPrompt;
|
|
1478
1490
|
this.pendingPromptMessageId = undefined;
|
|
1479
|
-
} else if (this.obligationCache.get().some((o) => o.from === "user")) {
|
|
1491
|
+
} else if (this.obligationCache.get().some((o) => o.from === "user" && this.obligationPredatesCurrentTurn(o))) {
|
|
1480
1492
|
// The agent will answer the relay obligation itself — don't double-post (#196).
|
|
1481
1493
|
return;
|
|
1482
1494
|
}
|
|
@@ -1528,6 +1540,10 @@ export class AgentRunner {
|
|
|
1528
1540
|
return false;
|
|
1529
1541
|
}
|
|
1530
1542
|
|
|
1543
|
+
private obligationPredatesCurrentTurn(obligation: { createdAt: number }): boolean {
|
|
1544
|
+
return this.currentTurnStartedAt === undefined || obligation.createdAt <= this.currentTurnStartedAt;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1531
1547
|
// --- Busy-state reconciler (item 2) -------------------------------------------------
|
|
1532
1548
|
// A safety net for turns that end out of band (interrupted from the web terminal,
|
|
1533
1549
|
// a hook that never fired) where the runner would otherwise stay stuck "busy".
|
|
@@ -1662,6 +1678,7 @@ export class AgentRunner {
|
|
|
1662
1678
|
}
|
|
1663
1679
|
|
|
1664
1680
|
private publishStatus(): void {
|
|
1681
|
+
this.claims.expire();
|
|
1665
1682
|
const status = this.claims.currentStatus();
|
|
1666
1683
|
const agentStatus = runnerAgentStatus(status);
|
|
1667
1684
|
const activeWork = this.claims.activeWork();
|