agent-relay-runner 0.64.0 → 0.65.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 +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/src/config.ts +10 -0
- package/src/index.ts +7 -1
- package/src/runner-core.ts +3 -2
- package/src/runner-helpers.ts +30 -0
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -89,6 +89,16 @@ export function runtimeTokenExpiresAtFromEnv(): string | undefined {
|
|
|
89
89
|
return process.env.AGENT_RELAY_TOKEN_EXPIRES_AT;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
// Deadline for a freshly spawned runner's INITIAL relay registration. Reconnects after the
|
|
93
|
+
// first registration retry forever (a long-lived agent rides out a relay blip), but the
|
|
94
|
+
// initial handshake is bounded: a runner that can never register (a token the relay rejects,
|
|
95
|
+
// an unreachable relay) must EXIT instead of hanging `active` forever — a hung runner is
|
|
96
|
+
// invisible to the orchestrator's exit detection, so the spawn-parent is never notified.
|
|
97
|
+
export function registrationTimeoutMsFromEnv(): number {
|
|
98
|
+
const parsed = Number(process.env.AGENT_RELAY_REGISTRATION_TIMEOUT_MS);
|
|
99
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 60_000;
|
|
100
|
+
}
|
|
101
|
+
|
|
92
102
|
export function agentProfileNameFromEnv(): string | undefined {
|
|
93
103
|
return process.env.AGENT_RELAY_AGENT_PROFILE;
|
|
94
104
|
}
|
package/src/index.ts
CHANGED
|
@@ -344,5 +344,11 @@ export function detectApprovalModeFromArgs(provider: string, defaultArgs: string
|
|
|
344
344
|
}
|
|
345
345
|
|
|
346
346
|
if (import.meta.main) {
|
|
347
|
-
|
|
347
|
+
main().catch((error) => {
|
|
348
|
+
// A fatal startup failure (e.g. the runner could not register with the relay within the
|
|
349
|
+
// deadline) must exit non-zero, not hang or swallow — the orchestrator's exit detection
|
|
350
|
+
// then reports it and the spawn-parent is notified instead of waiting on a silent zombie.
|
|
351
|
+
console.error(`[agent-relay] runner fatal: ${errMessage(error)}`);
|
|
352
|
+
process.exit(1);
|
|
353
|
+
});
|
|
348
354
|
}
|
package/src/runner-core.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { ensureSessionScratch, reapSessionScratch, sweepStaleSessions, type Sess
|
|
|
22
22
|
import { deliverBufferedMcpCall as deliverBufferedMcpOutboxCall, deliverRunnerOutboxEvent } from "./mcp-outbox";
|
|
23
23
|
import { RunnerInsights } from "./runner-insights";
|
|
24
24
|
import { BusyReconciler } from "./busy-reconciler";
|
|
25
|
-
import { capsFromEnv, contextStateDirFromEnv, logLevelFromEnv, mcpProxyEnabledFromEnv, orchestratorBaseDirFromEnv, orchestratorUrlFromEnv, runnerInfoFileFromEnv, runnerLogFileFromEnv, runnerOutboxDirWithInfoFallback, sessionDebugEnabled, tagsFromEnv, workspaceModeFromEnv } from "./config";
|
|
25
|
+
import { capsFromEnv, contextStateDirFromEnv, logLevelFromEnv, mcpProxyEnabledFromEnv, orchestratorBaseDirFromEnv, orchestratorUrlFromEnv, registrationTimeoutMsFromEnv, runnerInfoFileFromEnv, runnerLogFileFromEnv, runnerOutboxDirWithInfoFallback, sessionDebugEnabled, tagsFromEnv, workspaceModeFromEnv } from "./config";
|
|
26
26
|
import {
|
|
27
27
|
appliedAgentProfileMetadata,
|
|
28
28
|
commandTimeoutMs,
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
latestClaudeResumeIdFromLogFile,
|
|
35
35
|
lifecycleCapabilities,
|
|
36
36
|
providerStateFromActiveWork,
|
|
37
|
+
registerWithinDeadline,
|
|
37
38
|
relayBusUrl,
|
|
38
39
|
runnerAgentStatus,
|
|
39
40
|
runnerBusErrorAction,
|
|
@@ -447,7 +448,7 @@ export class AgentRunner {
|
|
|
447
448
|
void this.handleCommand(type, params, commandId, command);
|
|
448
449
|
});
|
|
449
450
|
this.bus.on("error", (code, message) => this.handleBusError(String(code), String(message)));
|
|
450
|
-
await this.bus.
|
|
451
|
+
await registerWithinDeadline(this.bus, registrationTimeoutMsFromEnv(), this.options.relayUrl, (reason) => logger.fatal("register", reason));
|
|
451
452
|
this.obligationCache.start();
|
|
452
453
|
this.outbox.start();
|
|
453
454
|
this.sessionOutbox.start();
|
package/src/runner-helpers.ts
CHANGED
|
@@ -56,6 +56,36 @@ export function stripRunnerClaimedGuidance(body: string): string {
|
|
|
56
56
|
return body.replace(RUNNER_CLAIMED_GUIDANCE_RE, "");
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
// Bound a runner's INITIAL relay registration. `connect` resolves on the first `registered`
|
|
60
|
+
// frame and otherwise loops forever, so a token the relay rejects (401 at the WS upgrade)
|
|
61
|
+
// hangs here silently. Race it against `timeoutMs`; on timeout, surface a fatal reason, tear
|
|
62
|
+
// down the bus (stop the retry loop) and throw so the process exits and the orchestrator
|
|
63
|
+
// observes it instead of a silent zombie. Reconnects after first registration stay infinite.
|
|
64
|
+
export async function registerWithinDeadline(
|
|
65
|
+
bus: { connect: () => Promise<void>; close: () => Promise<void> },
|
|
66
|
+
timeoutMs: number,
|
|
67
|
+
relayUrl: string,
|
|
68
|
+
onFatal: (reason: string) => void,
|
|
69
|
+
): Promise<void> {
|
|
70
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
71
|
+
const deadline = new Promise<never>((_, reject) => {
|
|
72
|
+
timer = setTimeout(() => reject(new Error(
|
|
73
|
+
`runner failed to register with the relay within ${timeoutMs}ms — aborting so the orchestrator `
|
|
74
|
+
+ `observes the exit instead of a silent hang (relay ${relayUrl}; check the relay is reachable `
|
|
75
|
+
+ `and the runner token is valid).`,
|
|
76
|
+
)), timeoutMs);
|
|
77
|
+
});
|
|
78
|
+
try {
|
|
79
|
+
await Promise.race([bus.connect(), deadline]);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
onFatal(errMessage(error));
|
|
82
|
+
await bus.close().catch(() => {});
|
|
83
|
+
throw error;
|
|
84
|
+
} finally {
|
|
85
|
+
if (timer) clearTimeout(timer);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
59
89
|
export function csvTags(raw: string | undefined): string[] {
|
|
60
90
|
return (raw || "").split(",").map((tag) => tag.trim()).filter(Boolean);
|
|
61
91
|
}
|