@aiden-ade/sandbox-agent 0.1.42 → 0.1.43
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/dist/index.cjs +732 -74
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -13315,13 +13315,13 @@ function resolveViaWhere(command, env) {
|
|
|
13315
13315
|
}
|
|
13316
13316
|
return null;
|
|
13317
13317
|
}
|
|
13318
|
-
function runCliVersionProbe(executable, env, args = ["--version"]) {
|
|
13318
|
+
function runCliVersionProbe(executable, env, args = ["--version"], timeoutMs = 3e3) {
|
|
13319
13319
|
const isWin = (0, import_node_os3.platform)() === "win32";
|
|
13320
13320
|
if (isWin && /\.(cmd|bat)$/i.test(executable)) {
|
|
13321
13321
|
const comSpec = env.ComSpec ?? process.env.ComSpec ?? "cmd.exe";
|
|
13322
13322
|
return (0, import_node_child_process2.spawnSync)(comSpec, ["/d", "/s", "/c", executable, ...args], {
|
|
13323
13323
|
encoding: "utf8",
|
|
13324
|
-
timeout:
|
|
13324
|
+
timeout: timeoutMs,
|
|
13325
13325
|
env,
|
|
13326
13326
|
windowsHide: true
|
|
13327
13327
|
});
|
|
@@ -13329,7 +13329,7 @@ function runCliVersionProbe(executable, env, args = ["--version"]) {
|
|
|
13329
13329
|
const useShell = isWin && !/[\\/]/.test(executable);
|
|
13330
13330
|
return (0, import_node_child_process2.spawnSync)(executable, args, {
|
|
13331
13331
|
encoding: "utf8",
|
|
13332
|
-
timeout:
|
|
13332
|
+
timeout: timeoutMs,
|
|
13333
13333
|
env,
|
|
13334
13334
|
shell: useShell,
|
|
13335
13335
|
windowsHide: isWin ? true : void 0
|
|
@@ -14555,15 +14555,6 @@ If the index returns no useful path, say so and run one targeted local search in
|
|
|
14555
14555
|
function buildAlanTeamCodeContextOverlay(teamId) {
|
|
14556
14556
|
return [buildAlanTeamScopePrompt(teamId), ALAN_CODE_CONTEXT_PROMPT].join("\n\n");
|
|
14557
14557
|
}
|
|
14558
|
-
function buildCodeDiscoveryEnforcementTail() {
|
|
14559
|
-
return [
|
|
14560
|
-
"## MANDATORY \u2014 index once, then use local source",
|
|
14561
|
-
"1. For codebase discovery, make one `mcp__alan__search_code_context` call (hybrid, topK 5). Omit teamId \u2014 Alan session headers resolve it.",
|
|
14562
|
-
"2. Once useful paths are returned, stop querying the index and read those local files. The current worktree is the source of truth.",
|
|
14563
|
-
"3. Use narrow local `Grep`/graph queries only after the orientation pass. If the index has no useful path, run one targeted local search in the likely package.",
|
|
14564
|
-
"Do not repeat or rephrase index searches once paths are known, and do not claim current behavior from index excerpts alone."
|
|
14565
|
-
].join("\n");
|
|
14566
|
-
}
|
|
14567
14558
|
var PLAN_MODE_PROMPT = [
|
|
14568
14559
|
"You are in Alan plan mode.",
|
|
14569
14560
|
"Analyze the task, inspect the relevant code and context, and produce a concrete implementation plan.",
|
|
@@ -15443,6 +15434,11 @@ function buildSessionHeaders(input) {
|
|
|
15443
15434
|
if (input.allowedTools?.length) {
|
|
15444
15435
|
headers["x-allowed-tools"] = input.allowedTools.join(",");
|
|
15445
15436
|
}
|
|
15437
|
+
if (input.runHeaders) {
|
|
15438
|
+
for (const [key, value2] of Object.entries(input.runHeaders)) {
|
|
15439
|
+
if (typeof value2 === "string" && value2.length > 0) headers[key] = value2;
|
|
15440
|
+
}
|
|
15441
|
+
}
|
|
15446
15442
|
return headers;
|
|
15447
15443
|
}
|
|
15448
15444
|
function getTomlSectionName(line) {
|
|
@@ -15653,6 +15649,23 @@ function syncAlanMcpSessionHeaders(input) {
|
|
|
15653
15649
|
}
|
|
15654
15650
|
return written;
|
|
15655
15651
|
}
|
|
15652
|
+
var mcpConfigWriteGateTail = Promise.resolve();
|
|
15653
|
+
function acquireMcpConfigWriteGate() {
|
|
15654
|
+
const previous = mcpConfigWriteGateTail;
|
|
15655
|
+
let releaseCurrent;
|
|
15656
|
+
const current = new Promise((resolve22) => {
|
|
15657
|
+
releaseCurrent = resolve22;
|
|
15658
|
+
});
|
|
15659
|
+
mcpConfigWriteGateTail = previous.then(() => current);
|
|
15660
|
+
return previous.then(() => {
|
|
15661
|
+
let released = false;
|
|
15662
|
+
return () => {
|
|
15663
|
+
if (released) return;
|
|
15664
|
+
released = true;
|
|
15665
|
+
releaseCurrent();
|
|
15666
|
+
};
|
|
15667
|
+
});
|
|
15668
|
+
}
|
|
15656
15669
|
function buildPromptWithSystem(config, promptText) {
|
|
15657
15670
|
const parts2 = [
|
|
15658
15671
|
config.systemPrompt?.trim(),
|
|
@@ -15661,6 +15674,13 @@ function buildPromptWithSystem(config, promptText) {
|
|
|
15661
15674
|
].filter((value2) => Boolean(value2 && value2.length > 0));
|
|
15662
15675
|
return parts2.join("\n\n");
|
|
15663
15676
|
}
|
|
15677
|
+
function buildResumeAwarePrompt(config, promptText, options) {
|
|
15678
|
+
const systemParts = options.isResume ? [config.systemPromptAppend?.trim()] : [config.systemPrompt?.trim()];
|
|
15679
|
+
const parts2 = [...systemParts, promptText.trim()].filter(
|
|
15680
|
+
(value2) => Boolean(value2 && value2.length > 0)
|
|
15681
|
+
);
|
|
15682
|
+
return parts2.join("\n\n");
|
|
15683
|
+
}
|
|
15664
15684
|
function buildPlanModePrefix(promptText) {
|
|
15665
15685
|
return [
|
|
15666
15686
|
"You are in plan-only mode.",
|
|
@@ -15827,7 +15847,7 @@ var ERROR_SPECS = {
|
|
|
15827
15847
|
recoveryClass: "needs_env"
|
|
15828
15848
|
},
|
|
15829
15849
|
auth_expired: {
|
|
15830
|
-
message: "The provider connection expired.
|
|
15850
|
+
message: "The provider connection expired. Re-authenticate the agent CLI on this runtime (or reconnect it in your provider/Cloud settings), then retry.",
|
|
15831
15851
|
recoveryClass: "user_fixable"
|
|
15832
15852
|
},
|
|
15833
15853
|
subscription_required: {
|
|
@@ -15917,7 +15937,7 @@ function normalizeCodexCliErrorMessage(message) {
|
|
|
15917
15937
|
return "Codex connection to OpenAI timed out while waiting for the turn to continue. Retry the message; if it repeats, check network/API latency or reduce slow MCP/tool calls in the turn.";
|
|
15918
15938
|
}
|
|
15919
15939
|
if (lower.includes("access token could not be refreshed") || lower.includes("refresh_token_invalidated") || lower.includes("refresh token has been invalidated")) {
|
|
15920
|
-
return "Codex CLI connection expired.
|
|
15940
|
+
return "Codex CLI connection expired. Re-authenticate the Codex CLI on this computer (run `codex login`) or reconnect it in Cloud settings, then retry.";
|
|
15921
15941
|
}
|
|
15922
15942
|
return message;
|
|
15923
15943
|
}
|
|
@@ -16298,6 +16318,9 @@ function createGenericCliBackend(options) {
|
|
|
16298
16318
|
} else if (state.resultDeferredOnBackgroundWork && state.activeBackgroundTaskIds?.size) {
|
|
16299
16319
|
idleTimeoutReason = "background_task";
|
|
16300
16320
|
resolve22("idle_timeout");
|
|
16321
|
+
} else if (options.postStartupSilenceTimeoutMs && sawStdoutLine && typeof state.lastRawOutputAtMs === "number" && Date.now() - state.lastRawOutputAtMs >= options.postStartupSilenceTimeoutMs) {
|
|
16322
|
+
idleTimeoutReason = "post_startup_silence";
|
|
16323
|
+
resolve22("idle_timeout");
|
|
16301
16324
|
} else {
|
|
16302
16325
|
armIdleTimer();
|
|
16303
16326
|
}
|
|
@@ -16310,8 +16333,9 @@ function createGenericCliBackend(options) {
|
|
|
16310
16333
|
if (idleTimer) clearTimeout(idleTimer);
|
|
16311
16334
|
if (raceResult === "idle_timeout") {
|
|
16312
16335
|
idleTimedOut = true;
|
|
16336
|
+
const idleWaitLabel = idleTimeoutReason === "background_task" ? "a background task terminal event" : idleTimeoutReason === "post_startup_silence" ? "any further output (silent hang)" : "a pending user answer";
|
|
16313
16337
|
console.warn(
|
|
16314
|
-
`[${options.kind}] Idle timeout (${IDLE_MS}ms) waiting for ${
|
|
16338
|
+
`[${options.kind}] Idle timeout (${IDLE_MS}ms) waiting for ${idleWaitLabel} \u2014 force-killing orphaned process`
|
|
16315
16339
|
);
|
|
16316
16340
|
killProcessTree(child.pid, { signal: "SIGKILL", child });
|
|
16317
16341
|
exitCode = await Promise.race([
|
|
@@ -16384,6 +16408,25 @@ function createGenericCliBackend(options) {
|
|
|
16384
16408
|
console.error(`[${options.kind}] Process force-killed by startup watchdog`, {
|
|
16385
16409
|
stderrLines: stderrLines.slice(-10)
|
|
16386
16410
|
});
|
|
16411
|
+
const startupStderr = stderrLines.join("\n").trim();
|
|
16412
|
+
if (startupStderr) {
|
|
16413
|
+
const classified = classifyCliErrorDetailed(startupStderr, exitCode);
|
|
16414
|
+
return {
|
|
16415
|
+
success: false,
|
|
16416
|
+
summary: state.summary.trim() || classified.message,
|
|
16417
|
+
filesModified: [],
|
|
16418
|
+
planFilesCreated: [],
|
|
16419
|
+
iterations: Math.max(state.iterations, 1),
|
|
16420
|
+
error: classified.message,
|
|
16421
|
+
...classified.errorKind !== "unknown_cli_error" ? { errorKind: classified.errorKind } : {},
|
|
16422
|
+
recoveryClass: classified.recoveryClass,
|
|
16423
|
+
providerSessionId: state.runtimeSessionId,
|
|
16424
|
+
runtimeSessionId: state.runtimeSessionId,
|
|
16425
|
+
backendKind: options.kind,
|
|
16426
|
+
supportTier: options.supportTier,
|
|
16427
|
+
usage: state.usage
|
|
16428
|
+
};
|
|
16429
|
+
}
|
|
16387
16430
|
return {
|
|
16388
16431
|
success: false,
|
|
16389
16432
|
summary: "Agent produced no output",
|
|
@@ -16433,7 +16476,7 @@ function createGenericCliBackend(options) {
|
|
|
16433
16476
|
filesModified: [],
|
|
16434
16477
|
planFilesCreated: [],
|
|
16435
16478
|
iterations: Math.max(state.iterations, 1),
|
|
16436
|
-
error: idleTimeoutReason === "background_task" ? "Agent timed out waiting for a background task to finish \u2014 no terminal event arrived, so the run was stopped. Send your message again to continue." : "Agent timed out waiting for your answer to its question \u2014 no response arrived, so the run was stopped. Send your reply as a new message to continue.",
|
|
16479
|
+
error: idleTimeoutReason === "background_task" ? "Agent timed out waiting for a background task to finish \u2014 no terminal event arrived, so the run was stopped. Send your message again to continue." : idleTimeoutReason === "post_startup_silence" ? "The agent went silent and stopped responding, so the run was stopped. Send your message again to retry." : "Agent timed out waiting for your answer to its question \u2014 no response arrived, so the run was stopped. Send your reply as a new message to continue.",
|
|
16437
16480
|
providerSessionId: state.runtimeSessionId,
|
|
16438
16481
|
runtimeSessionId: state.runtimeSessionId,
|
|
16439
16482
|
backendKind: options.kind,
|
|
@@ -17141,6 +17184,16 @@ var TERMINAL_TASK_NOTIFICATION_STATUSES = /* @__PURE__ */ new Set([
|
|
|
17141
17184
|
"cancelled",
|
|
17142
17185
|
"canceled"
|
|
17143
17186
|
]);
|
|
17187
|
+
var NON_TERMINAL_TASK_NOTIFICATION_STATUSES = /* @__PURE__ */ new Set([
|
|
17188
|
+
"running",
|
|
17189
|
+
"in_progress",
|
|
17190
|
+
"inprogress",
|
|
17191
|
+
"started",
|
|
17192
|
+
"pending",
|
|
17193
|
+
"active",
|
|
17194
|
+
"working",
|
|
17195
|
+
"queued"
|
|
17196
|
+
]);
|
|
17144
17197
|
function extractToolResultContent(content) {
|
|
17145
17198
|
if (typeof content === "string") return content;
|
|
17146
17199
|
if (!Array.isArray(content)) return "";
|
|
@@ -17158,6 +17211,7 @@ function stringField2(record, key) {
|
|
|
17158
17211
|
function isTerminalTaskNotification(record) {
|
|
17159
17212
|
const status = stringField2(record, "status")?.toLowerCase();
|
|
17160
17213
|
if (status && TERMINAL_TASK_NOTIFICATION_STATUSES.has(status)) return true;
|
|
17214
|
+
if (status && NON_TERMINAL_TASK_NOTIFICATION_STATUSES.has(status)) return false;
|
|
17161
17215
|
return typeof record.duration_ms === "number" || typeof record.durationMs === "number" || typeof record.total_tokens === "number" || typeof record.totalTokens === "number" || typeof record.tool_uses === "number" || typeof record.toolUses === "number";
|
|
17162
17216
|
}
|
|
17163
17217
|
function isFailedTaskNotification(record) {
|
|
@@ -17249,20 +17303,25 @@ function handleClaudeStructuredEvent(parsed, context, state) {
|
|
|
17249
17303
|
state.iterations += 1;
|
|
17250
17304
|
const message = typeof parsed.message === "object" && parsed.message !== null ? parsed.message : null;
|
|
17251
17305
|
const content = Array.isArray(message?.content) ? message.content : [];
|
|
17306
|
+
let previousBlockWasText = false;
|
|
17252
17307
|
for (const block of content) {
|
|
17253
17308
|
if (typeof block !== "object" || block === null || !("type" in block)) continue;
|
|
17254
17309
|
if (block.type === "text" && typeof block.text === "string") {
|
|
17255
17310
|
const text = block.text;
|
|
17256
17311
|
state.summary += `${text}
|
|
17257
17312
|
`;
|
|
17258
|
-
void presenter.onAssistantText(
|
|
17313
|
+
void presenter.onAssistantText(previousBlockWasText ? `
|
|
17314
|
+
${text}` : text);
|
|
17315
|
+
previousBlockWasText = true;
|
|
17259
17316
|
continue;
|
|
17260
17317
|
}
|
|
17261
17318
|
if (block.type === "thinking" && typeof block.thinking === "string") {
|
|
17319
|
+
previousBlockWasText = false;
|
|
17262
17320
|
void presenter.onThinking(block.thinking);
|
|
17263
17321
|
continue;
|
|
17264
17322
|
}
|
|
17265
17323
|
if (block.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") {
|
|
17324
|
+
previousBlockWasText = false;
|
|
17266
17325
|
const toolBlock = block;
|
|
17267
17326
|
void presenter.onToolUse(
|
|
17268
17327
|
toolBlock.name,
|
|
@@ -17378,7 +17437,7 @@ function handleClaudeStructuredEvent(parsed, context, state) {
|
|
|
17378
17437
|
state.iterations = parsed.num_turns;
|
|
17379
17438
|
}
|
|
17380
17439
|
const subtype = typeof parsed.subtype === "string" ? parsed.subtype : "";
|
|
17381
|
-
if ((
|
|
17440
|
+
if (subtype.startsWith("error") && !state.error) {
|
|
17382
17441
|
const errObj = typeof parsed.error === "object" && parsed.error !== null ? parsed.error : null;
|
|
17383
17442
|
const resumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
|
|
17384
17443
|
const errMsg = subtype === "error_during_execution" && resumeId ? SESSION_RESUME_FAILED_MESSAGE : typeof parsed.error === "string" && parsed.error.trim() || errObj && typeof errObj.message === "string" && errObj.message.trim() || typeof parsed.message === "string" && parsed.message.trim() || typeof parsed.result === "string" && parsed.result.trim() || subtype;
|
|
@@ -17529,7 +17588,7 @@ function createClaudeCliBackend(command = "claude", defaultArgs = []) {
|
|
|
17529
17588
|
const basePromptText = resumeContextPrefix ? `${resumeContextPrefix}
|
|
17530
17589
|
|
|
17531
17590
|
${ctx.promptText}` : ctx.promptText;
|
|
17532
|
-
const promptText = ctx.config.mode === "plan" ? buildPlanModePrefix(
|
|
17591
|
+
const promptText = ctx.config.mode === "plan" ? buildPlanModePrefix(basePromptText) : basePromptText;
|
|
17533
17592
|
if (promptText.trim()) {
|
|
17534
17593
|
contentBlocks.push({ type: "text", text: promptText });
|
|
17535
17594
|
}
|
|
@@ -17620,6 +17679,25 @@ function buildGeneratedImageFromCodexPayload(payload) {
|
|
|
17620
17679
|
idFields: []
|
|
17621
17680
|
});
|
|
17622
17681
|
}
|
|
17682
|
+
function extractRolloutMessageText(payload) {
|
|
17683
|
+
const content = payload.content;
|
|
17684
|
+
if (typeof content === "string") return content;
|
|
17685
|
+
if (Array.isArray(content)) {
|
|
17686
|
+
for (const block of content) {
|
|
17687
|
+
if (block && typeof block === "object" && typeof block.text === "string") {
|
|
17688
|
+
return block.text;
|
|
17689
|
+
}
|
|
17690
|
+
}
|
|
17691
|
+
}
|
|
17692
|
+
return "";
|
|
17693
|
+
}
|
|
17694
|
+
function isSyntheticUserRolloutMessage(payload) {
|
|
17695
|
+
const text = extractRolloutMessageText(payload).trimStart();
|
|
17696
|
+
if (!text) return false;
|
|
17697
|
+
const lower = text.toLowerCase();
|
|
17698
|
+
return lower.startsWith("<environment_context") || lower.startsWith("<user_instructions") || // Auto-compaction bridge summaries are tagged in the rollout content.
|
|
17699
|
+
lower.startsWith("<compact") || lower.startsWith("[compact");
|
|
17700
|
+
}
|
|
17623
17701
|
function latestUserMessageLineIndex(lines) {
|
|
17624
17702
|
let latestIndex = -1;
|
|
17625
17703
|
for (let index = 0; index < lines.length; index += 1) {
|
|
@@ -17627,7 +17705,7 @@ function latestUserMessageLineIndex(lines) {
|
|
|
17627
17705
|
if (!line) continue;
|
|
17628
17706
|
const entry = parseJsonObject(line);
|
|
17629
17707
|
const payload = entry && typeof entry.payload === "object" && entry.payload !== null ? entry.payload : null;
|
|
17630
|
-
if (payload?.type === "message" && payload.role === "user") {
|
|
17708
|
+
if (payload?.type === "message" && payload.role === "user" && !isSyntheticUserRolloutMessage(payload)) {
|
|
17631
17709
|
latestIndex = index;
|
|
17632
17710
|
}
|
|
17633
17711
|
}
|
|
@@ -17691,7 +17769,9 @@ async function replayCodexMcpToolEventsFromSessionLog(context, state) {
|
|
|
17691
17769
|
}
|
|
17692
17770
|
}
|
|
17693
17771
|
var CODEX_EXIT_GRACE_MS = 3e4;
|
|
17694
|
-
|
|
17772
|
+
var CODEX_BACKGROUND_EXIT_GRACE_MS = 10 * 6e4;
|
|
17773
|
+
var CODEX_EXIT_GRACE_SIGKILL_MS = 3e3;
|
|
17774
|
+
function armCodexExitGraceKill(state, graceMs = CODEX_EXIT_GRACE_MS) {
|
|
17695
17775
|
if (state.exitGraceKillTimer) return;
|
|
17696
17776
|
const child = state.process;
|
|
17697
17777
|
if (!child) return;
|
|
@@ -17701,7 +17781,15 @@ function armCodexExitGraceKill(state) {
|
|
|
17701
17781
|
"[codex_app_server] Process did not exit after turn completion; killing process group"
|
|
17702
17782
|
);
|
|
17703
17783
|
killProcessTree(child.pid, { signal: "SIGTERM", child });
|
|
17704
|
-
|
|
17784
|
+
const killTimer = setTimeout(() => {
|
|
17785
|
+
if (child.exitCode !== null) return;
|
|
17786
|
+
console.warn(
|
|
17787
|
+
"[codex_app_server] Process still alive after grace SIGTERM; escalating to SIGKILL"
|
|
17788
|
+
);
|
|
17789
|
+
killProcessTree(child.pid, { signal: "SIGKILL", child });
|
|
17790
|
+
}, CODEX_EXIT_GRACE_SIGKILL_MS);
|
|
17791
|
+
killTimer.unref?.();
|
|
17792
|
+
}, graceMs);
|
|
17705
17793
|
timer.unref?.();
|
|
17706
17794
|
state.exitGraceKillTimer = timer;
|
|
17707
17795
|
}
|
|
@@ -17710,6 +17798,167 @@ function disarmCodexExitGraceKill(state) {
|
|
|
17710
17798
|
clearTimeout(state.exitGraceKillTimer);
|
|
17711
17799
|
state.exitGraceKillTimer = void 0;
|
|
17712
17800
|
}
|
|
17801
|
+
function completeCodexTurnRespectingWorkers(state) {
|
|
17802
|
+
disarmCodexExitGraceKill(state);
|
|
17803
|
+
const activeWorkers = state.activeBackgroundTaskIds?.size ?? 0;
|
|
17804
|
+
if (activeWorkers > 0) {
|
|
17805
|
+
state.resultDeferredOnBackgroundWork = true;
|
|
17806
|
+
state.codexAwaitingWorkersAfterTurnEnd = true;
|
|
17807
|
+
console.info(
|
|
17808
|
+
`[codex_app_server] Turn completed with ${activeWorkers} background worker id(s) still active \u2014 deferring exit grace`
|
|
17809
|
+
);
|
|
17810
|
+
armCodexExitGraceKill(state, CODEX_BACKGROUND_EXIT_GRACE_MS);
|
|
17811
|
+
return;
|
|
17812
|
+
}
|
|
17813
|
+
armCodexExitGraceKill(state);
|
|
17814
|
+
}
|
|
17815
|
+
function touchCodexBackgroundGrace(state) {
|
|
17816
|
+
if (!state.codexAwaitingWorkersAfterTurnEnd) return;
|
|
17817
|
+
disarmCodexExitGraceKill(state);
|
|
17818
|
+
armCodexExitGraceKill(state, CODEX_BACKGROUND_EXIT_GRACE_MS);
|
|
17819
|
+
}
|
|
17820
|
+
function maybeReleaseCodexDeferredTurn(state) {
|
|
17821
|
+
if (!state.codexAwaitingWorkersAfterTurnEnd) return;
|
|
17822
|
+
if (state.activeBackgroundTaskIds?.size) return;
|
|
17823
|
+
state.codexAwaitingWorkersAfterTurnEnd = false;
|
|
17824
|
+
state.resultDeferredOnBackgroundWork = false;
|
|
17825
|
+
disarmCodexExitGraceKill(state);
|
|
17826
|
+
armCodexExitGraceKill(state);
|
|
17827
|
+
console.info(
|
|
17828
|
+
"[codex_app_server] All background workers finished after turn completion \u2014 arming normal exit grace"
|
|
17829
|
+
);
|
|
17830
|
+
}
|
|
17831
|
+
var TERMINAL_COLLAB_AGENT_STATUSES = /* @__PURE__ */ new Set([
|
|
17832
|
+
"completed",
|
|
17833
|
+
"errored",
|
|
17834
|
+
"shutdown",
|
|
17835
|
+
"interrupted",
|
|
17836
|
+
"not_found"
|
|
17837
|
+
]);
|
|
17838
|
+
var FAILED_COLLAB_AGENT_STATUSES = /* @__PURE__ */ new Set(["errored", "interrupted", "not_found"]);
|
|
17839
|
+
var COLLAB_COORDINATION_TOOL_NAMES = {
|
|
17840
|
+
send_input: "collab_send_input",
|
|
17841
|
+
wait: "collab_wait",
|
|
17842
|
+
close_agent: "collab_close_agent"
|
|
17843
|
+
};
|
|
17844
|
+
function firstLine(text, maxLength = 140) {
|
|
17845
|
+
const line = text.split("\n").find((candidate) => candidate.trim().length > 0)?.trim() ?? "";
|
|
17846
|
+
return line.length > maxLength ? `${line.slice(0, maxLength - 1)}\u2026` : line;
|
|
17847
|
+
}
|
|
17848
|
+
function finishCodexWorker(context, state, options) {
|
|
17849
|
+
const launcherId = options.launcherId ?? (options.threadId ? state.backgroundToolIdByTaskId?.get(options.threadId) : void 0) ?? (options.agentPath ? state.codexWorkerToolIdByPath?.get(options.agentPath) : void 0);
|
|
17850
|
+
if (!launcherId) {
|
|
17851
|
+
if (options.threadId) clearBackgroundTaskIds(state, [options.threadId]);
|
|
17852
|
+
maybeReleaseCodexDeferredTurn(state);
|
|
17853
|
+
return;
|
|
17854
|
+
}
|
|
17855
|
+
const launcherStillActive = state.activeBackgroundTaskIds?.has(launcherId) === true || (state.backgroundTaskIdsByToolId?.get(launcherId)?.size ?? 0) > 0;
|
|
17856
|
+
if (!launcherStillActive) {
|
|
17857
|
+
if (options.threadId) clearBackgroundTaskIds(state, [options.threadId]);
|
|
17858
|
+
maybeReleaseCodexDeferredTurn(state);
|
|
17859
|
+
return;
|
|
17860
|
+
}
|
|
17861
|
+
clearBackgroundTaskIds(state, [options.threadId ?? launcherId]);
|
|
17862
|
+
const remainingSiblings = state.backgroundTaskIdsByToolId?.get(launcherId)?.size ?? 0;
|
|
17863
|
+
if (remainingSiblings === 0) {
|
|
17864
|
+
clearBackgroundTaskIds(state, [launcherId]);
|
|
17865
|
+
const isError = FAILED_COLLAB_AGENT_STATUSES.has(options.status);
|
|
17866
|
+
const resultText = options.message?.trim() || `Background worker ${options.status}.`;
|
|
17867
|
+
void context.presenter.onToolResult?.(launcherId, resultText, isError);
|
|
17868
|
+
}
|
|
17869
|
+
maybeReleaseCodexDeferredTurn(state);
|
|
17870
|
+
}
|
|
17871
|
+
function applyCodexCollabAgentStates(context, state, agentsStates) {
|
|
17872
|
+
for (const [threadId, raw] of Object.entries(agentsStates)) {
|
|
17873
|
+
if (!raw || typeof raw !== "object") continue;
|
|
17874
|
+
const entry = raw;
|
|
17875
|
+
const status = typeof entry.status === "string" ? entry.status : "";
|
|
17876
|
+
if (!TERMINAL_COLLAB_AGENT_STATUSES.has(status)) continue;
|
|
17877
|
+
finishCodexWorker(context, state, {
|
|
17878
|
+
threadId,
|
|
17879
|
+
status,
|
|
17880
|
+
message: typeof entry.message === "string" ? entry.message : void 0
|
|
17881
|
+
});
|
|
17882
|
+
}
|
|
17883
|
+
}
|
|
17884
|
+
function summarizeCodexAgentStates(agentsStates) {
|
|
17885
|
+
return Object.entries(agentsStates).map(([threadId, raw]) => {
|
|
17886
|
+
const entry = raw && typeof raw === "object" ? raw : {};
|
|
17887
|
+
const status = typeof entry.status === "string" ? entry.status : "unknown";
|
|
17888
|
+
const message = typeof entry.message === "string" && entry.message.trim() ? ` \u2014 ${firstLine(entry.message)}` : "";
|
|
17889
|
+
return `${threadId}: ${status}${message}`;
|
|
17890
|
+
}).join("\n");
|
|
17891
|
+
}
|
|
17892
|
+
function handleCodexCollabToolCallItem(context, state, item, phase) {
|
|
17893
|
+
const itemId = typeof item.id === "string" ? item.id : "";
|
|
17894
|
+
const tool = typeof item.tool === "string" ? item.tool : "";
|
|
17895
|
+
if (!itemId || !tool) return true;
|
|
17896
|
+
const senderThreadId = typeof item.sender_thread_id === "string" ? item.sender_thread_id : "";
|
|
17897
|
+
const receiverThreadIds = Array.isArray(item.receiver_thread_ids) ? item.receiver_thread_ids.filter(
|
|
17898
|
+
(value2) => typeof value2 === "string" && value2.length > 0
|
|
17899
|
+
) : [];
|
|
17900
|
+
const prompt = typeof item.prompt === "string" ? item.prompt : "";
|
|
17901
|
+
const agentsStates = typeof item.agents_states === "object" && item.agents_states !== null ? item.agents_states : {};
|
|
17902
|
+
const itemStatus = typeof item.status === "string" ? item.status : "";
|
|
17903
|
+
if (tool === "spawn_agent") {
|
|
17904
|
+
if (trackCodexStreamedToolId(state, itemId)) {
|
|
17905
|
+
const parentToolUseId = senderThreadId ? state.backgroundToolIdByTaskId?.get(senderThreadId) ?? null : null;
|
|
17906
|
+
void context.presenter.onToolUse(
|
|
17907
|
+
"Agent",
|
|
17908
|
+
{
|
|
17909
|
+
subagent_type: "codex-worker",
|
|
17910
|
+
description: firstLine(prompt) || "Codex background worker",
|
|
17911
|
+
...prompt ? { prompt } : {},
|
|
17912
|
+
...receiverThreadIds.length > 0 ? { agent_thread_ids: receiverThreadIds } : {}
|
|
17913
|
+
},
|
|
17914
|
+
itemId,
|
|
17915
|
+
parentToolUseId
|
|
17916
|
+
);
|
|
17917
|
+
registerBackgroundLauncher(state, itemId);
|
|
17918
|
+
}
|
|
17919
|
+
for (const threadId of receiverThreadIds) {
|
|
17920
|
+
if (state.backgroundToolIdByTaskId?.get(threadId)) continue;
|
|
17921
|
+
registerBackgroundTaskRecord(state, { task_id: threadId, tool_use_id: itemId });
|
|
17922
|
+
}
|
|
17923
|
+
applyCodexCollabAgentStates(context, state, agentsStates);
|
|
17924
|
+
if (phase === "completed" && itemStatus === "failed") {
|
|
17925
|
+
finishCodexWorker(context, state, {
|
|
17926
|
+
launcherId: itemId,
|
|
17927
|
+
status: "errored",
|
|
17928
|
+
message: "Failed to spawn background worker."
|
|
17929
|
+
});
|
|
17930
|
+
}
|
|
17931
|
+
return true;
|
|
17932
|
+
}
|
|
17933
|
+
const coordinationToolName = COLLAB_COORDINATION_TOOL_NAMES[tool];
|
|
17934
|
+
if (!coordinationToolName) return false;
|
|
17935
|
+
if (trackCodexStreamedToolId(state, itemId)) {
|
|
17936
|
+
void context.presenter.onToolUse(
|
|
17937
|
+
coordinationToolName,
|
|
17938
|
+
{
|
|
17939
|
+
...receiverThreadIds.length > 0 ? { receiver_thread_ids: receiverThreadIds } : {},
|
|
17940
|
+
...prompt ? { prompt } : {}
|
|
17941
|
+
},
|
|
17942
|
+
itemId
|
|
17943
|
+
);
|
|
17944
|
+
}
|
|
17945
|
+
if (phase === "completed") {
|
|
17946
|
+
const isError = itemStatus === "failed";
|
|
17947
|
+
const summary = summarizeCodexAgentStates(agentsStates);
|
|
17948
|
+
void context.presenter.onToolResult?.(
|
|
17949
|
+
itemId,
|
|
17950
|
+
summary || (isError ? "Failed." : "Done."),
|
|
17951
|
+
isError
|
|
17952
|
+
);
|
|
17953
|
+
}
|
|
17954
|
+
applyCodexCollabAgentStates(context, state, agentsStates);
|
|
17955
|
+
if (tool === "close_agent" && phase === "completed" && itemStatus !== "failed") {
|
|
17956
|
+
for (const threadId of receiverThreadIds) {
|
|
17957
|
+
finishCodexWorker(context, state, { threadId, status: "shutdown" });
|
|
17958
|
+
}
|
|
17959
|
+
}
|
|
17960
|
+
return true;
|
|
17961
|
+
}
|
|
17713
17962
|
function trackCodexStreamedToolId(state, toolId) {
|
|
17714
17963
|
if (!state.codexStreamedToolIds) state.codexStreamedToolIds = /* @__PURE__ */ new Set();
|
|
17715
17964
|
if (state.codexStreamedToolIds.has(toolId)) return false;
|
|
@@ -17740,6 +17989,7 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17740
17989
|
const presenter = context.presenter;
|
|
17741
17990
|
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
17742
17991
|
if (!type) return false;
|
|
17992
|
+
touchCodexBackgroundGrace(state);
|
|
17743
17993
|
if (typeof parsed.thread_id === "string") {
|
|
17744
17994
|
state.runtimeSessionId = parsed.thread_id;
|
|
17745
17995
|
}
|
|
@@ -17764,7 +18014,12 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17764
18014
|
return true;
|
|
17765
18015
|
case "turn.started":
|
|
17766
18016
|
disarmCodexExitGraceKill(state);
|
|
17767
|
-
state.
|
|
18017
|
+
state.codexAwaitingWorkersAfterTurnEnd = false;
|
|
18018
|
+
state.resultDeferredOnBackgroundWork = false;
|
|
18019
|
+
if (state.codexIterationEventFamily !== "legacy") {
|
|
18020
|
+
state.codexIterationEventFamily = "modern";
|
|
18021
|
+
state.iterations += 1;
|
|
18022
|
+
}
|
|
17768
18023
|
return true;
|
|
17769
18024
|
case "session_configured":
|
|
17770
18025
|
if (typeof parsed.session_id === "string") {
|
|
@@ -17773,7 +18028,12 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17773
18028
|
return true;
|
|
17774
18029
|
case "task_started":
|
|
17775
18030
|
disarmCodexExitGraceKill(state);
|
|
17776
|
-
state.
|
|
18031
|
+
state.codexAwaitingWorkersAfterTurnEnd = false;
|
|
18032
|
+
state.resultDeferredOnBackgroundWork = false;
|
|
18033
|
+
if (state.codexIterationEventFamily !== "modern") {
|
|
18034
|
+
state.codexIterationEventFamily = "legacy";
|
|
18035
|
+
state.iterations += 1;
|
|
18036
|
+
}
|
|
17777
18037
|
return true;
|
|
17778
18038
|
case "item.started": {
|
|
17779
18039
|
const item = typeof parsed.item === "object" && parsed.item !== null ? parsed.item : null;
|
|
@@ -17800,6 +18060,8 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17800
18060
|
}
|
|
17801
18061
|
return true;
|
|
17802
18062
|
}
|
|
18063
|
+
case "collab_tool_call":
|
|
18064
|
+
return handleCodexCollabToolCallItem(context, state, item, "started");
|
|
17803
18065
|
// Text-bearing and patch/todo items carry no useful live-start payload; they
|
|
17804
18066
|
// are surfaced on item.completed. Recognized (do not count as unhandled).
|
|
17805
18067
|
case "reasoning":
|
|
@@ -17811,6 +18073,21 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17811
18073
|
return false;
|
|
17812
18074
|
}
|
|
17813
18075
|
}
|
|
18076
|
+
case "item.updated": {
|
|
18077
|
+
const item = typeof parsed.item === "object" && parsed.item !== null ? parsed.item : null;
|
|
18078
|
+
if (!item || typeof item.type !== "string") return true;
|
|
18079
|
+
switch (item.type) {
|
|
18080
|
+
case "collab_tool_call":
|
|
18081
|
+
return handleCodexCollabToolCallItem(context, state, item, "updated");
|
|
18082
|
+
case "todo_list": {
|
|
18083
|
+
const todos = Array.isArray(item.items) ? item.items : [];
|
|
18084
|
+
void presenter.onTodoWrite?.(todos);
|
|
18085
|
+
return true;
|
|
18086
|
+
}
|
|
18087
|
+
default:
|
|
18088
|
+
return false;
|
|
18089
|
+
}
|
|
18090
|
+
}
|
|
17814
18091
|
case "item.completed": {
|
|
17815
18092
|
const item = typeof parsed.item === "object" && parsed.item !== null ? parsed.item : null;
|
|
17816
18093
|
if (!item || typeof item.type !== "string") return true;
|
|
@@ -17875,6 +18152,8 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17875
18152
|
void presenter.onTodoWrite?.(todos);
|
|
17876
18153
|
return true;
|
|
17877
18154
|
}
|
|
18155
|
+
case "collab_tool_call":
|
|
18156
|
+
return handleCodexCollabToolCallItem(context, state, item, "completed");
|
|
17878
18157
|
default:
|
|
17879
18158
|
return false;
|
|
17880
18159
|
}
|
|
@@ -17896,7 +18175,9 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17896
18175
|
return true;
|
|
17897
18176
|
}
|
|
17898
18177
|
case "exec_command_begin": {
|
|
17899
|
-
const
|
|
18178
|
+
const hasCallId = typeof parsed.call_id === "string" && parsed.call_id.length > 0;
|
|
18179
|
+
const toolId = hasCallId ? parsed.call_id : `exec-${Date.now()}`;
|
|
18180
|
+
if (!hasCallId) state.lastAnonymousExecId = toolId;
|
|
17900
18181
|
const command = Array.isArray(parsed.command) ? parsed.command.join(" ") : "";
|
|
17901
18182
|
const cwd = typeof parsed.cwd === "string" ? parsed.cwd : context.cwd;
|
|
17902
18183
|
trackCodexStreamedToolId(state, toolId);
|
|
@@ -17904,7 +18185,10 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17904
18185
|
return true;
|
|
17905
18186
|
}
|
|
17906
18187
|
case "exec_command_end": {
|
|
17907
|
-
const
|
|
18188
|
+
const hasCallId = typeof parsed.call_id === "string" && parsed.call_id.length > 0;
|
|
18189
|
+
const toolId = hasCallId ? parsed.call_id : state.lastAnonymousExecId;
|
|
18190
|
+
if (!hasCallId) state.lastAnonymousExecId = void 0;
|
|
18191
|
+
if (!toolId) return true;
|
|
17908
18192
|
const output = typeof parsed.formatted_output === "string" ? parsed.formatted_output : typeof parsed.aggregated_output === "string" ? parsed.aggregated_output : "";
|
|
17909
18193
|
void presenter.onToolResult?.(toolId, output);
|
|
17910
18194
|
return true;
|
|
@@ -17912,9 +18196,8 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17912
18196
|
case "mcp_tool_call_begin": {
|
|
17913
18197
|
const toolId = typeof parsed.call_id === "string" ? parsed.call_id : `mcp-${Date.now()}`;
|
|
17914
18198
|
const invocation = typeof parsed.invocation === "object" && parsed.invocation !== null ? parsed.invocation : {};
|
|
17915
|
-
const tool = typeof invocation.tool_name === "string" ? invocation.tool_name : typeof invocation.tool === "string" ? invocation.tool : "MCP Tool";
|
|
17916
18199
|
trackCodexStreamedToolId(state, toolId);
|
|
17917
|
-
void presenter.onToolUse(
|
|
18200
|
+
void presenter.onToolUse(buildCodexMcpToolName(invocation), invocation, toolId);
|
|
17918
18201
|
return true;
|
|
17919
18202
|
}
|
|
17920
18203
|
case "mcp_tool_call_end": {
|
|
@@ -17952,7 +18235,7 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17952
18235
|
const lastMessage = typeof parsed.last_agent_message === "string" ? parsed.last_agent_message : "";
|
|
17953
18236
|
if (lastMessage.length > 0) state.summary = lastMessage;
|
|
17954
18237
|
state.error = void 0;
|
|
17955
|
-
|
|
18238
|
+
completeCodexTurnRespectingWorkers(state);
|
|
17956
18239
|
return true;
|
|
17957
18240
|
}
|
|
17958
18241
|
case "turn.completed": {
|
|
@@ -17975,7 +18258,108 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17975
18258
|
cacheCreationTokens: state.usage.cacheCreationTokens
|
|
17976
18259
|
});
|
|
17977
18260
|
state.error = void 0;
|
|
17978
|
-
|
|
18261
|
+
completeCodexTurnRespectingWorkers(state);
|
|
18262
|
+
return true;
|
|
18263
|
+
}
|
|
18264
|
+
case "sub_agent_activity": {
|
|
18265
|
+
const kind = typeof parsed.kind === "string" ? parsed.kind : "";
|
|
18266
|
+
const agentThreadId = typeof parsed.agent_thread_id === "string" ? parsed.agent_thread_id : "";
|
|
18267
|
+
const agentPath = typeof parsed.agent_path === "string" ? parsed.agent_path : "";
|
|
18268
|
+
const eventId = typeof parsed.event_id === "string" ? parsed.event_id : "";
|
|
18269
|
+
const isRootPath = !agentPath || agentPath === "/root" || agentPath === "/";
|
|
18270
|
+
switch (kind) {
|
|
18271
|
+
case "started": {
|
|
18272
|
+
if (isRootPath) return true;
|
|
18273
|
+
if (agentThreadId && state.backgroundToolIdByTaskId?.has(agentThreadId)) return true;
|
|
18274
|
+
const toolId = eventId || agentThreadId;
|
|
18275
|
+
if (!toolId) return true;
|
|
18276
|
+
if (trackCodexStreamedToolId(state, toolId)) {
|
|
18277
|
+
const workerName = agentPath.slice(agentPath.lastIndexOf("/") + 1) || "codex-worker";
|
|
18278
|
+
const parentPath = agentPath.slice(0, agentPath.lastIndexOf("/"));
|
|
18279
|
+
const parentToolUseId = parentPath && parentPath !== "/root" ? state.codexWorkerToolIdByPath?.get(parentPath) ?? null : null;
|
|
18280
|
+
void presenter.onToolUse(
|
|
18281
|
+
"Agent",
|
|
18282
|
+
{
|
|
18283
|
+
subagent_type: workerName,
|
|
18284
|
+
description: agentPath,
|
|
18285
|
+
...agentThreadId ? { agent_thread_id: agentThreadId } : {}
|
|
18286
|
+
},
|
|
18287
|
+
toolId,
|
|
18288
|
+
parentToolUseId
|
|
18289
|
+
);
|
|
18290
|
+
registerBackgroundLauncher(state, toolId);
|
|
18291
|
+
if (agentThreadId) {
|
|
18292
|
+
registerBackgroundTaskRecord(state, { task_id: agentThreadId, tool_use_id: toolId });
|
|
18293
|
+
}
|
|
18294
|
+
if (!state.codexWorkerToolIdByPath) state.codexWorkerToolIdByPath = /* @__PURE__ */ new Map();
|
|
18295
|
+
state.codexWorkerToolIdByPath.set(agentPath, toolId);
|
|
18296
|
+
}
|
|
18297
|
+
return true;
|
|
18298
|
+
}
|
|
18299
|
+
case "interacted":
|
|
18300
|
+
return true;
|
|
18301
|
+
case "interrupted": {
|
|
18302
|
+
if (isRootPath) return true;
|
|
18303
|
+
finishCodexWorker(context, state, {
|
|
18304
|
+
threadId: agentThreadId || void 0,
|
|
18305
|
+
agentPath: agentPath || void 0,
|
|
18306
|
+
status: "interrupted",
|
|
18307
|
+
message: "Background worker interrupted."
|
|
18308
|
+
});
|
|
18309
|
+
return true;
|
|
18310
|
+
}
|
|
18311
|
+
default:
|
|
18312
|
+
return false;
|
|
18313
|
+
}
|
|
18314
|
+
}
|
|
18315
|
+
// App-server EventMsg collab lifecycle notices. Begin/interaction/resume events
|
|
18316
|
+
// carry no state Alan tracks beyond liveness (touched above); spawn_end and the
|
|
18317
|
+
// waiting/close terminals map into the shared worker registry below.
|
|
18318
|
+
case "collab_agent_spawn_begin":
|
|
18319
|
+
case "collab_agent_interaction_begin":
|
|
18320
|
+
case "collab_agent_interaction_end":
|
|
18321
|
+
case "collab_waiting_begin":
|
|
18322
|
+
case "collab_resume_begin":
|
|
18323
|
+
case "collab_resume_end":
|
|
18324
|
+
case "collab_close_begin":
|
|
18325
|
+
return true;
|
|
18326
|
+
case "collab_agent_spawn_end": {
|
|
18327
|
+
const newThreadId = typeof parsed.new_thread_id === "string" ? parsed.new_thread_id : "";
|
|
18328
|
+
if (!newThreadId || state.backgroundToolIdByTaskId?.has(newThreadId)) return true;
|
|
18329
|
+
const callId = typeof parsed.call_id === "string" && parsed.call_id ? parsed.call_id : typeof parsed.event_id === "string" ? parsed.event_id : "";
|
|
18330
|
+
const toolId = callId || `collab-spawn-${newThreadId}`;
|
|
18331
|
+
if (trackCodexStreamedToolId(state, toolId)) {
|
|
18332
|
+
const nickname = typeof parsed.new_agent_nickname === "string" ? parsed.new_agent_nickname : "";
|
|
18333
|
+
const role = typeof parsed.new_agent_role === "string" ? parsed.new_agent_role : "";
|
|
18334
|
+
void presenter.onToolUse(
|
|
18335
|
+
"Agent",
|
|
18336
|
+
{
|
|
18337
|
+
subagent_type: nickname || role || "codex-worker",
|
|
18338
|
+
description: [nickname, role].filter(Boolean).join(" \u2014 ") || "Codex background worker",
|
|
18339
|
+
agent_thread_id: newThreadId
|
|
18340
|
+
},
|
|
18341
|
+
toolId
|
|
18342
|
+
);
|
|
18343
|
+
registerBackgroundLauncher(state, toolId);
|
|
18344
|
+
}
|
|
18345
|
+
registerBackgroundTaskRecord(state, { task_id: newThreadId, tool_use_id: toolId });
|
|
18346
|
+
return true;
|
|
18347
|
+
}
|
|
18348
|
+
case "collab_waiting_end": {
|
|
18349
|
+
const entries = Array.isArray(parsed.agent_statuses) ? parsed.agent_statuses : [];
|
|
18350
|
+
for (const raw of entries) {
|
|
18351
|
+
if (!raw || typeof raw !== "object") continue;
|
|
18352
|
+
const entry = raw;
|
|
18353
|
+
const threadId = typeof entry.agent_thread_id === "string" ? entry.agent_thread_id : typeof entry.thread_id === "string" ? entry.thread_id : "";
|
|
18354
|
+
const status = typeof entry.status === "string" ? entry.status.toLowerCase() : "";
|
|
18355
|
+
if (!threadId || !TERMINAL_COLLAB_AGENT_STATUSES.has(status)) continue;
|
|
18356
|
+
finishCodexWorker(context, state, { threadId, status });
|
|
18357
|
+
}
|
|
18358
|
+
return true;
|
|
18359
|
+
}
|
|
18360
|
+
case "collab_close_end": {
|
|
18361
|
+
const threadId = typeof parsed.receiver_thread_id === "string" ? parsed.receiver_thread_id : "";
|
|
18362
|
+
if (threadId) finishCodexWorker(context, state, { threadId, status: "shutdown" });
|
|
17979
18363
|
return true;
|
|
17980
18364
|
}
|
|
17981
18365
|
case "error": {
|
|
@@ -18094,10 +18478,11 @@ function createCodexRuntimeBackend(command = "codex", defaultArgs = []) {
|
|
|
18094
18478
|
},
|
|
18095
18479
|
promptViaStdin: true,
|
|
18096
18480
|
augmentPrompt: (ctx) => {
|
|
18481
|
+
const isResume = Boolean(resumableSessionId);
|
|
18097
18482
|
const promptWithContext = resumeContextPrefix ? `${resumeContextPrefix}
|
|
18098
18483
|
|
|
18099
18484
|
${ctx.promptText}` : ctx.promptText;
|
|
18100
|
-
const basePrompt =
|
|
18485
|
+
const basePrompt = buildResumeAwarePrompt(ctx.config, promptWithContext, { isResume });
|
|
18101
18486
|
return ctx.config.mode === "plan" ? buildPlanModePrefix(basePrompt) : basePrompt;
|
|
18102
18487
|
},
|
|
18103
18488
|
parseStructuredLine: parseCodexStructuredLine,
|
|
@@ -18303,10 +18688,21 @@ function handleCursorStructuredEvent(parsed, context, state) {
|
|
|
18303
18688
|
}
|
|
18304
18689
|
case "tool_call": {
|
|
18305
18690
|
const subtype = typeof parsed.subtype === "string" ? parsed.subtype : "";
|
|
18306
|
-
const
|
|
18691
|
+
const hasCallId = typeof parsed.call_id === "string" && parsed.call_id.length > 0;
|
|
18307
18692
|
const toolCall = typeof parsed.tool_call === "object" && parsed.tool_call !== null ? parsed.tool_call : null;
|
|
18308
18693
|
const entry = toolCall ? extractCursorToolEntry(toolCall) : null;
|
|
18309
18694
|
if (!entry) return true;
|
|
18695
|
+
let toolId;
|
|
18696
|
+
if (hasCallId) {
|
|
18697
|
+
toolId = parsed.call_id;
|
|
18698
|
+
} else if (subtype === "started") {
|
|
18699
|
+
state.cursorAnonymousToolCounter = (state.cursorAnonymousToolCounter ?? 0) + 1;
|
|
18700
|
+
toolId = `cursor-tool-${state.cursorAnonymousToolCounter}`;
|
|
18701
|
+
state.lastAnonymousCursorToolId = toolId;
|
|
18702
|
+
} else {
|
|
18703
|
+
toolId = state.lastAnonymousCursorToolId;
|
|
18704
|
+
}
|
|
18705
|
+
if (!toolId) return true;
|
|
18310
18706
|
state.iterations = Math.max(state.iterations, 1);
|
|
18311
18707
|
const toolName = formatCursorToolName(entry.rawName, entry.payload);
|
|
18312
18708
|
const toolArgs = typeof entry.payload.args === "object" && entry.payload.args !== null ? entry.payload.args : typeof entry.payload.arguments === "object" && entry.payload.arguments !== null ? entry.payload.arguments : entry.payload;
|
|
@@ -18315,6 +18711,7 @@ function handleCursorStructuredEvent(parsed, context, state) {
|
|
|
18315
18711
|
return true;
|
|
18316
18712
|
}
|
|
18317
18713
|
if (subtype === "completed") {
|
|
18714
|
+
if (!hasCallId) state.lastAnonymousCursorToolId = void 0;
|
|
18318
18715
|
void presenter.onToolResult?.(toolId, buildCursorToolResultText(entry.payload.result));
|
|
18319
18716
|
}
|
|
18320
18717
|
return true;
|
|
@@ -18393,7 +18790,10 @@ function buildCursorAgentModelArg(modelId, options) {
|
|
|
18393
18790
|
const wireEffort = resolveWireEffort({
|
|
18394
18791
|
harness: "cursor_agent_cli",
|
|
18395
18792
|
modelId: baseId,
|
|
18396
|
-
|
|
18793
|
+
// Fall back to an effort baked into the model id (e.g. `gpt-5.5-high` from a
|
|
18794
|
+
// session import or raw discovered id) when no explicit selection overrides it,
|
|
18795
|
+
// so the SKU is preserved instead of silently downgraded to default effort.
|
|
18796
|
+
selectedEffortLevel: options?.selectedEffortLevel ?? parsed.effort,
|
|
18397
18797
|
effortLevels
|
|
18398
18798
|
});
|
|
18399
18799
|
let id = baseId;
|
|
@@ -18407,7 +18807,8 @@ function buildCursorAgentModelArg(modelId, options) {
|
|
|
18407
18807
|
}
|
|
18408
18808
|
}
|
|
18409
18809
|
const contextWindow = asContextWindow(options?.selectedContextWindow);
|
|
18410
|
-
|
|
18810
|
+
const modelSupports1m = (options?.contextWindows ?? []).includes("1m");
|
|
18811
|
+
if (contextWindow === "1m" && id === baseId && modelSupports1m) {
|
|
18411
18812
|
return `${baseId}[context=1m]`;
|
|
18412
18813
|
}
|
|
18413
18814
|
if (parsed.fast) return `${id}-fast`;
|
|
@@ -18497,7 +18898,8 @@ function createCursorAgentCliBackend(command = "cursor-agent", defaultArgs = [])
|
|
|
18497
18898
|
buildCursorAgentModelArg(model, {
|
|
18498
18899
|
selectedEffortLevel: ctx.config.selectedEffortLevel,
|
|
18499
18900
|
selectedContextWindow: ctx.config.selectedContextWindow,
|
|
18500
|
-
effortLevels: modelDef?.capabilities?.effortLevels
|
|
18901
|
+
effortLevels: modelDef?.capabilities?.effortLevels,
|
|
18902
|
+
contextWindows: modelDef?.capabilities?.contextWindows
|
|
18501
18903
|
})
|
|
18502
18904
|
);
|
|
18503
18905
|
}
|
|
@@ -18515,11 +18917,20 @@ function createCursorAgentCliBackend(command = "cursor-agent", defaultArgs = [])
|
|
|
18515
18917
|
// with zero output; without this watchdog only the 30-min server reaper
|
|
18516
18918
|
// would end the run.
|
|
18517
18919
|
startupTimeoutMs: 18e4,
|
|
18920
|
+
// Post-startup hang bound: cursor's asks use the non-blocking MCP model and it
|
|
18921
|
+
// never populates the idle timer's ask/background fire conditions, so a cursor
|
|
18922
|
+
// process that produced output then went silent (no result event) is otherwise
|
|
18923
|
+
// bounded only by the ~30-min external reaper. Poll every 5 min; kill after 15
|
|
18924
|
+
// min of continuous silence.
|
|
18925
|
+
resultIdleTimeoutMs: 5 * 6e4,
|
|
18926
|
+
postStartupSilenceTimeoutMs: 15 * 6e4,
|
|
18518
18927
|
augmentPrompt: (ctx) => {
|
|
18519
|
-
const
|
|
18520
|
-
|
|
18521
|
-
|
|
18522
|
-
|
|
18928
|
+
const isResume = Boolean(
|
|
18929
|
+
ctx.config.runtimeSessionId?.trim() || ctx.config.providerSessionId?.trim()
|
|
18930
|
+
);
|
|
18931
|
+
const base = buildResumeAwarePrompt(ctx.config, ctx.promptText, { isResume });
|
|
18932
|
+
const prompt = ctx.config.mode === "plan" ? buildPlanModePrefix(base) : base;
|
|
18933
|
+
return appendImagePathReferences(prompt, imageFiles);
|
|
18523
18934
|
},
|
|
18524
18935
|
parseStructuredLine: parseCursorStructuredLine
|
|
18525
18936
|
}).run(context);
|
|
@@ -19473,7 +19884,7 @@ function createSupatestCliBackend(command = "supatest", defaultArgs = []) {
|
|
|
19473
19884
|
const basePromptText = resumeContextPrefix ? `${resumeContextPrefix}
|
|
19474
19885
|
|
|
19475
19886
|
${ctx.promptText}` : ctx.promptText;
|
|
19476
|
-
const promptText = ctx.config.mode === "plan" ? buildPlanModePrefix(
|
|
19887
|
+
const promptText = ctx.config.mode === "plan" ? buildPlanModePrefix(basePromptText) : basePromptText;
|
|
19477
19888
|
if (promptText.trim()) {
|
|
19478
19889
|
contentBlocks.push({ type: "text", text: promptText });
|
|
19479
19890
|
}
|
|
@@ -19658,6 +20069,8 @@ var BaseMachineAgent = class _BaseMachineAgent {
|
|
|
19658
20069
|
presenter;
|
|
19659
20070
|
runtime;
|
|
19660
20071
|
abortController = null;
|
|
20072
|
+
/** Log the "synced MCP session headers" line once per run(), not per respawn. */
|
|
20073
|
+
mcpHeadersLogged = false;
|
|
19661
20074
|
constructor(presenter, runtime) {
|
|
19662
20075
|
this.presenter = presenter;
|
|
19663
20076
|
this.runtime = runtime;
|
|
@@ -19714,7 +20127,52 @@ var BaseMachineAgent = class _BaseMachineAgent {
|
|
|
19714
20127
|
getActivityHeartbeatIntervalMs() {
|
|
19715
20128
|
return AGENT_ACTIVITY_HEARTBEAT_MS;
|
|
19716
20129
|
}
|
|
20130
|
+
/**
|
|
20131
|
+
* Home directory whose CLI config files receive the Alan MCP session headers.
|
|
20132
|
+
* Defaults to the process home (production behaviour); tests override to a
|
|
20133
|
+
* temp dir so runs never touch the developer's real CLI configs.
|
|
20134
|
+
*/
|
|
20135
|
+
getMcpConfigHomeDir() {
|
|
20136
|
+
return void 0;
|
|
20137
|
+
}
|
|
20138
|
+
/**
|
|
20139
|
+
* Re-assert this run's Alan MCP identity in the shared CLI config under the
|
|
20140
|
+
* serialization gate, holding the gate until the provider spawns. Returns the
|
|
20141
|
+
* gate release. Concurrent runs on one machine share ~/.codex/config.toml,
|
|
20142
|
+
* ~/.cursor/mcp.json, and ~/.claude.json; the CLI reads its config once at
|
|
20143
|
+
* spawn, so without serialization a concurrent run could rewrite the shared
|
|
20144
|
+
* file between this write and this spawn and steal this run's session identity.
|
|
20145
|
+
*/
|
|
20146
|
+
async beginMcpConfigCriticalSection(config) {
|
|
20147
|
+
const conversationId = config.conversationId;
|
|
20148
|
+
if (!conversationId) return () => {
|
|
20149
|
+
};
|
|
20150
|
+
const releaseGate = await acquireMcpConfigWriteGate();
|
|
20151
|
+
try {
|
|
20152
|
+
const writtenClis = syncAlanMcpSessionHeaders({
|
|
20153
|
+
conversationId,
|
|
20154
|
+
teamId: config.teamId,
|
|
20155
|
+
runHeaders: config.alanMcp?.headers,
|
|
20156
|
+
homeDir: this.getMcpConfigHomeDir()
|
|
20157
|
+
});
|
|
20158
|
+
if (writtenClis.length > 0 && !this.mcpHeadersLogged) {
|
|
20159
|
+
this.mcpHeadersLogged = true;
|
|
20160
|
+
void this.presenter.onLog(`Synced Alan MCP session headers to ${writtenClis.join(", ")}`);
|
|
20161
|
+
}
|
|
20162
|
+
} catch (error) {
|
|
20163
|
+
console.warn("[base-machine-agent] Failed to sync MCP session headers", error);
|
|
20164
|
+
}
|
|
20165
|
+
return releaseGate;
|
|
20166
|
+
}
|
|
19717
20167
|
async runBackendWithActivityHeartbeat(backend, context) {
|
|
20168
|
+
const releaseMcpGate = await this.beginMcpConfigCriticalSection(context.config);
|
|
20169
|
+
let mcpGateReleased = false;
|
|
20170
|
+
const releaseMcpGateOnce = () => {
|
|
20171
|
+
if (mcpGateReleased) return;
|
|
20172
|
+
mcpGateReleased = true;
|
|
20173
|
+
releaseMcpGate();
|
|
20174
|
+
};
|
|
20175
|
+
const callerOnProcessSpawned = context.onProcessSpawned;
|
|
19718
20176
|
const intervalMs = this.getActivityHeartbeatIntervalMs();
|
|
19719
20177
|
let heartbeat = null;
|
|
19720
20178
|
let livenessProbe = null;
|
|
@@ -19733,9 +20191,17 @@ var BaseMachineAgent = class _BaseMachineAgent {
|
|
|
19733
20191
|
}
|
|
19734
20192
|
}, intervalMs);
|
|
19735
20193
|
}
|
|
20194
|
+
const spawnAwareContext = {
|
|
20195
|
+
...context,
|
|
20196
|
+
onProcessSpawned: (child) => {
|
|
20197
|
+
releaseMcpGateOnce();
|
|
20198
|
+
callerOnProcessSpawned?.(child);
|
|
20199
|
+
}
|
|
20200
|
+
};
|
|
19736
20201
|
try {
|
|
19737
|
-
return await backend.run(
|
|
20202
|
+
return await backend.run(spawnAwareContext);
|
|
19738
20203
|
} finally {
|
|
20204
|
+
releaseMcpGateOnce();
|
|
19739
20205
|
if (heartbeat) clearInterval(heartbeat);
|
|
19740
20206
|
}
|
|
19741
20207
|
}
|
|
@@ -19969,19 +20435,7 @@ ${runtimeConfig.task}` } : {}
|
|
|
19969
20435
|
this.presenter.onComplete(result);
|
|
19970
20436
|
return result;
|
|
19971
20437
|
}
|
|
19972
|
-
|
|
19973
|
-
try {
|
|
19974
|
-
const writtenClis = syncAlanMcpSessionHeaders({
|
|
19975
|
-
conversationId: initialConfig.conversationId,
|
|
19976
|
-
teamId: initialConfig.teamId
|
|
19977
|
-
});
|
|
19978
|
-
if (writtenClis.length > 0) {
|
|
19979
|
-
void this.presenter.onLog(`Synced Alan MCP session headers to ${writtenClis.join(", ")}`);
|
|
19980
|
-
}
|
|
19981
|
-
} catch (error) {
|
|
19982
|
-
console.warn("[base-machine-agent] Failed to sync MCP session headers", error);
|
|
19983
|
-
}
|
|
19984
|
-
}
|
|
20438
|
+
this.mcpHeadersLogged = false;
|
|
19985
20439
|
try {
|
|
19986
20440
|
return await this.runCliBackend(initialConfig, safeProjectPath);
|
|
19987
20441
|
} catch (error) {
|
|
@@ -20669,7 +21123,7 @@ function encryptedFileSize(entries) {
|
|
|
20669
21123
|
);
|
|
20670
21124
|
return emptyEnvelopeBytes + base64UrlLength(plaintextBytes);
|
|
20671
21125
|
}
|
|
20672
|
-
var EncryptedEventOutbox = class {
|
|
21126
|
+
var EncryptedEventOutbox = class _EncryptedEventOutbox {
|
|
20673
21127
|
constructor(path, encodedKey, pushLog = () => {
|
|
20674
21128
|
}, options = {}) {
|
|
20675
21129
|
this.path = path;
|
|
@@ -20684,6 +21138,11 @@ var EncryptedEventOutbox = class {
|
|
|
20684
21138
|
throw new Error("event outbox maxIntermediateEventsPerRun must be a non-negative integer");
|
|
20685
21139
|
}
|
|
20686
21140
|
this.entries = this.read();
|
|
21141
|
+
if (!(0, import_node_fs6.existsSync)(this.path)) {
|
|
21142
|
+
this.lastPersistedSignature = _EncryptedEventOutbox.EMPTY_SIGNATURE;
|
|
21143
|
+
} else if (this.entries.length > 0) {
|
|
21144
|
+
this.lastPersistedSignature = this.serializeForPersist().signature;
|
|
21145
|
+
}
|
|
20687
21146
|
this.enforceCaps();
|
|
20688
21147
|
this.persist();
|
|
20689
21148
|
}
|
|
@@ -20693,6 +21152,17 @@ var EncryptedEventOutbox = class {
|
|
|
20693
21152
|
retryTimer = null;
|
|
20694
21153
|
/** Latch so a persistent disk failure logs once per streak, not per event. */
|
|
20695
21154
|
persistFailureLogged = false;
|
|
21155
|
+
/**
|
|
21156
|
+
* Signature of the entries last durably written, so a `persist()` whose entries
|
|
21157
|
+
* are byte-identical to what is already on disk skips the AES-GCM re-encrypt +
|
|
21158
|
+
* temp-file write + rename entirely. Under link flapping the spool is persisted
|
|
21159
|
+
* on every enqueue/ack; this collapses redundant writes (e.g. a reconnect that
|
|
21160
|
+
* replays from head without mutating the queue) to O(delta), not O(spool). Set
|
|
21161
|
+
* to null on any write failure so the next persist retries rather than skips.
|
|
21162
|
+
*/
|
|
21163
|
+
lastPersistedSignature = null;
|
|
21164
|
+
/** Sentinel signature for the empty (no-entries) on-disk state. */
|
|
21165
|
+
static EMPTY_SIGNATURE = "empty";
|
|
20696
21166
|
key;
|
|
20697
21167
|
maxEncryptedBytes;
|
|
20698
21168
|
maxIntermediateEventsPerRun;
|
|
@@ -20895,20 +21365,28 @@ var EncryptedEventOutbox = class {
|
|
|
20895
21365
|
throw new Error(`unable to decrypt durable event outbox: ${detail}`);
|
|
20896
21366
|
}
|
|
20897
21367
|
}
|
|
21368
|
+
/** Serialize the current entries to their plaintext + content signature (once). */
|
|
21369
|
+
serializeForPersist() {
|
|
21370
|
+
if (this.entries.length === 0) {
|
|
21371
|
+
return { plaintext: "", signature: _EncryptedEventOutbox.EMPTY_SIGNATURE };
|
|
21372
|
+
}
|
|
21373
|
+
const plaintext = JSON.stringify({ entries: this.entries });
|
|
21374
|
+
return { plaintext, signature: (0, import_node_crypto.createHash)("sha256").update(plaintext).digest("base64url") };
|
|
21375
|
+
}
|
|
20898
21376
|
persist() {
|
|
21377
|
+
const { plaintext, signature } = this.serializeForPersist();
|
|
21378
|
+
if (signature === this.lastPersistedSignature) return;
|
|
20899
21379
|
try {
|
|
20900
21380
|
if (this.entries.length === 0) {
|
|
20901
21381
|
(0, import_node_fs6.rmSync)(this.path, { force: true });
|
|
21382
|
+
this.lastPersistedSignature = signature;
|
|
20902
21383
|
this.persistFailureLogged = false;
|
|
20903
21384
|
return;
|
|
20904
21385
|
}
|
|
20905
21386
|
(0, import_node_fs6.mkdirSync)((0, import_node_path5.dirname)(this.path), { recursive: true, mode: 448 });
|
|
20906
21387
|
const iv = (0, import_node_crypto.randomBytes)(12);
|
|
20907
21388
|
const cipher = (0, import_node_crypto.createCipheriv)("aes-256-gcm", this.key, iv);
|
|
20908
|
-
const ciphertext = Buffer.concat([
|
|
20909
|
-
cipher.update(JSON.stringify({ entries: this.entries }), "utf8"),
|
|
20910
|
-
cipher.final()
|
|
20911
|
-
]);
|
|
21389
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
20912
21390
|
const envelope = {
|
|
20913
21391
|
version: OUTBOX_VERSION,
|
|
20914
21392
|
iv: iv.toString("base64url"),
|
|
@@ -20923,8 +21401,10 @@ var EncryptedEventOutbox = class {
|
|
|
20923
21401
|
} finally {
|
|
20924
21402
|
(0, import_node_fs6.rmSync)(pendingPath, { force: true });
|
|
20925
21403
|
}
|
|
21404
|
+
this.lastPersistedSignature = signature;
|
|
20926
21405
|
this.persistFailureLogged = false;
|
|
20927
21406
|
} catch (error) {
|
|
21407
|
+
this.lastPersistedSignature = null;
|
|
20928
21408
|
if (!this.persistFailureLogged) {
|
|
20929
21409
|
this.persistFailureLogged = true;
|
|
20930
21410
|
const detail = error instanceof Error ? error.message : String(error);
|
|
@@ -21637,8 +22117,8 @@ var CONFIG_LOCK_STALE_MS = 3e4;
|
|
|
21637
22117
|
var CONFIG_LOCK_WAIT_MS = 2e3;
|
|
21638
22118
|
var CONFIG_LOCK_POLL_MS = 20;
|
|
21639
22119
|
function compactProviderVersion(output) {
|
|
21640
|
-
const
|
|
21641
|
-
return
|
|
22120
|
+
const firstLine2 = output.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
22121
|
+
return firstLine2?.slice(0, 120);
|
|
21642
22122
|
}
|
|
21643
22123
|
var inspectAlanMcpThroughProviderCli = (backendKind, expectedUrl, home) => {
|
|
21644
22124
|
const plan = getMcpCliInspectionPlan(backendKind);
|
|
@@ -21678,7 +22158,30 @@ var inspectAlanMcpThroughProviderCli = (backendKind, expectedUrl, home) => {
|
|
|
21678
22158
|
};
|
|
21679
22159
|
}
|
|
21680
22160
|
const providerVersion = compactProviderVersion(versionProbe.stdout);
|
|
21681
|
-
const
|
|
22161
|
+
const inspectTimeoutMs = Number(process.env.ALAN_MCP_INSPECT_TIMEOUT_MS) || 2e4;
|
|
22162
|
+
const probe = runCliVersionProbe(executable, env, plan.args, inspectTimeoutMs);
|
|
22163
|
+
const timedOut = probe.status === null && (probe.signal === "SIGTERM" || probe.error?.code === "ETIMEDOUT");
|
|
22164
|
+
if (timedOut) {
|
|
22165
|
+
const fileCheck = verifyAlanMcpRegistered(backendKind, home, () => ({
|
|
22166
|
+
ok: true,
|
|
22167
|
+
verification: { method: "file" }
|
|
22168
|
+
}));
|
|
22169
|
+
console.warn("[alan-agent] MCP CLI inspection timed out; falling back to file check", {
|
|
22170
|
+
backendKind,
|
|
22171
|
+
command,
|
|
22172
|
+
inspectTimeoutMs,
|
|
22173
|
+
fileOk: fileCheck.ok
|
|
22174
|
+
});
|
|
22175
|
+
return {
|
|
22176
|
+
ok: fileCheck.ok,
|
|
22177
|
+
issue: fileCheck.ok ? void 0 : {
|
|
22178
|
+
code: "provider_mcp_repair_required",
|
|
22179
|
+
command,
|
|
22180
|
+
message: `Alan tools could not be verified for ${plan.providerLabel}. Repair Alan tools, then retry.`
|
|
22181
|
+
},
|
|
22182
|
+
verification: { method: "file", command, providerVersion }
|
|
22183
|
+
};
|
|
22184
|
+
}
|
|
21682
22185
|
const classification = classifyMcpCliInspection(
|
|
21683
22186
|
plan,
|
|
21684
22187
|
{
|
|
@@ -22577,7 +23080,7 @@ var RunStartGate = class {
|
|
|
22577
23080
|
};
|
|
22578
23081
|
|
|
22579
23082
|
// src/version.ts
|
|
22580
|
-
var AGENT_VERSION = "0.1.
|
|
23083
|
+
var AGENT_VERSION = "0.1.43";
|
|
22581
23084
|
|
|
22582
23085
|
// src/workspace-relocation.ts
|
|
22583
23086
|
var import_node_child_process3 = require("child_process");
|
|
@@ -22653,11 +23156,19 @@ var import_node_child_process4 = require("child_process");
|
|
|
22653
23156
|
var import_node_fs11 = require("fs");
|
|
22654
23157
|
var import_node_path9 = require("path");
|
|
22655
23158
|
var WORKSPACE_LEASE_CHECK_INTERVAL_MS = 1e3;
|
|
23159
|
+
function parsePositiveMs(value2, fallback) {
|
|
23160
|
+
const parsed = value2 ? Number(value2) : Number.NaN;
|
|
23161
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
23162
|
+
}
|
|
23163
|
+
var WORKSPACE_LEASE_GIT_TIMEOUT_MS = parsePositiveMs(
|
|
23164
|
+
process.env.ALAN_WORKSPACE_LEASE_GIT_TIMEOUT_MS,
|
|
23165
|
+
2e3
|
|
23166
|
+
);
|
|
22656
23167
|
function gitValue(cwd, args) {
|
|
22657
23168
|
const result = (0, import_node_child_process4.spawnSync)("git", ["-C", cwd, ...args], {
|
|
22658
23169
|
encoding: "utf8",
|
|
22659
23170
|
env: getDaemonCliEnvironment(),
|
|
22660
|
-
timeout:
|
|
23171
|
+
timeout: WORKSPACE_LEASE_GIT_TIMEOUT_MS
|
|
22661
23172
|
});
|
|
22662
23173
|
if (result.status !== 0) return null;
|
|
22663
23174
|
return result.stdout.trim() || null;
|
|
@@ -22693,6 +23204,13 @@ var DEFAULT_PROBE2 = {
|
|
|
22693
23204
|
} catch {
|
|
22694
23205
|
return null;
|
|
22695
23206
|
}
|
|
23207
|
+
},
|
|
23208
|
+
pathExists: (workspacePath) => {
|
|
23209
|
+
try {
|
|
23210
|
+
return (0, import_node_fs11.existsSync)((0, import_node_path9.resolve)(workspacePath));
|
|
23211
|
+
} catch {
|
|
23212
|
+
return true;
|
|
23213
|
+
}
|
|
22696
23214
|
}
|
|
22697
23215
|
};
|
|
22698
23216
|
function unavailableFailure(target) {
|
|
@@ -22740,6 +23258,57 @@ var WorkspaceRunLease = class _WorkspaceRunLease {
|
|
|
22740
23258
|
failure: null
|
|
22741
23259
|
};
|
|
22742
23260
|
}
|
|
23261
|
+
/**
|
|
23262
|
+
* Classify the lease state for the run monitor.
|
|
23263
|
+
*
|
|
23264
|
+
* - `transient: true` — a null snapshot while the workspace path still exists:
|
|
23265
|
+
* the git probe timed out or failed transiently (OS throttling / IO
|
|
23266
|
+
* contention), NOT a vanished checkout. The monitor should retry (require N
|
|
23267
|
+
* consecutive transient failures) before declaring the workspace lost.
|
|
23268
|
+
* - `transient: false` — a definitive change: the path is genuinely missing
|
|
23269
|
+
* (ENOENT), or the workspace/repository/worktree/branch identity changed.
|
|
23270
|
+
* These are real and fail fast.
|
|
23271
|
+
*/
|
|
23272
|
+
verifyDetailed() {
|
|
23273
|
+
for (const captured of this.captured) {
|
|
23274
|
+
const current = this.probe.snapshot(captured.target.workspacePath);
|
|
23275
|
+
if (!current) {
|
|
23276
|
+
const pathGone = this.probe.pathExists?.(captured.target.workspacePath) === false;
|
|
23277
|
+
return { failure: unavailableFailure(captured.target), transient: !pathGone };
|
|
23278
|
+
}
|
|
23279
|
+
if (current.workspaceIdentity !== captured.identity.workspaceIdentity) {
|
|
23280
|
+
return {
|
|
23281
|
+
failure: {
|
|
23282
|
+
code: "workspace_lease_path_changed",
|
|
23283
|
+
message: "The selected workspace folder changed while the run was active. The run stopped before continuing; choose the correct folder and start a new run.",
|
|
23284
|
+
workspacePath: captured.target.workspacePath,
|
|
23285
|
+
expectedBranch: captured.identity.branch,
|
|
23286
|
+
actualBranch: current.branch
|
|
23287
|
+
},
|
|
23288
|
+
transient: false
|
|
23289
|
+
};
|
|
23290
|
+
}
|
|
23291
|
+
if (current.repositoryIdentity !== captured.identity.repositoryIdentity || current.worktreeIdentity !== captured.identity.worktreeIdentity) {
|
|
23292
|
+
return {
|
|
23293
|
+
failure: {
|
|
23294
|
+
code: "workspace_lease_repository_changed",
|
|
23295
|
+
message: "The workspace now points to a different repository or worktree. The run stopped before continuing; restore the original checkout and start a new run.",
|
|
23296
|
+
workspacePath: captured.target.workspacePath,
|
|
23297
|
+
expectedBranch: captured.identity.branch,
|
|
23298
|
+
actualBranch: current.branch
|
|
23299
|
+
},
|
|
23300
|
+
transient: false
|
|
23301
|
+
};
|
|
23302
|
+
}
|
|
23303
|
+
if (current.branch !== captured.identity.branch) {
|
|
23304
|
+
return {
|
|
23305
|
+
failure: branchFailure(captured.target, captured.identity.branch, current.branch),
|
|
23306
|
+
transient: false
|
|
23307
|
+
};
|
|
23308
|
+
}
|
|
23309
|
+
}
|
|
23310
|
+
return { failure: null, transient: false };
|
|
23311
|
+
}
|
|
22743
23312
|
verify() {
|
|
22744
23313
|
for (const captured of this.captured) {
|
|
22745
23314
|
const current = this.probe.snapshot(captured.target.workspacePath);
|
|
@@ -22769,6 +23338,40 @@ var WorkspaceRunLease = class _WorkspaceRunLease {
|
|
|
22769
23338
|
return null;
|
|
22770
23339
|
}
|
|
22771
23340
|
};
|
|
23341
|
+
var FRESH_LEASE_MONITOR_STATE = {
|
|
23342
|
+
transientStreak: 0,
|
|
23343
|
+
nextCheckAtMs: 0
|
|
23344
|
+
};
|
|
23345
|
+
function isLeaseCheckDue(state, nowMs) {
|
|
23346
|
+
return nowMs >= state.nextCheckAtMs;
|
|
23347
|
+
}
|
|
23348
|
+
function applyLeaseCheckResult(input) {
|
|
23349
|
+
const { detailed, nowMs, policy } = input;
|
|
23350
|
+
if (!detailed.failure) {
|
|
23351
|
+
return { action: "ok", state: { ...FRESH_LEASE_MONITOR_STATE } };
|
|
23352
|
+
}
|
|
23353
|
+
if (!detailed.transient) {
|
|
23354
|
+
return { action: "fail", state: input.state, failure: detailed.failure };
|
|
23355
|
+
}
|
|
23356
|
+
const transientStreak = input.state.transientStreak + 1;
|
|
23357
|
+
if (transientStreak >= policy.maxTransientFailures) {
|
|
23358
|
+
return {
|
|
23359
|
+
action: "fail",
|
|
23360
|
+
state: { transientStreak, nextCheckAtMs: input.state.nextCheckAtMs },
|
|
23361
|
+
failure: detailed.failure
|
|
23362
|
+
};
|
|
23363
|
+
}
|
|
23364
|
+
const backoffMs = Math.min(
|
|
23365
|
+
policy.backoffBaseMs * 2 ** (transientStreak - 1),
|
|
23366
|
+
policy.backoffMaxMs
|
|
23367
|
+
);
|
|
23368
|
+
return {
|
|
23369
|
+
action: "transient",
|
|
23370
|
+
state: { transientStreak, nextCheckAtMs: nowMs + backoffMs },
|
|
23371
|
+
failure: detailed.failure,
|
|
23372
|
+
backoffMs
|
|
23373
|
+
};
|
|
23374
|
+
}
|
|
22772
23375
|
|
|
22773
23376
|
// src/daemon.ts
|
|
22774
23377
|
var STAGING_API_URL = process.env.ALAN_STAGING_API_URL ?? "https://staging-api.tryalan.ai";
|
|
@@ -24087,9 +24690,12 @@ function reconcileActiveRuns(input) {
|
|
|
24087
24690
|
if (!staleByGrace && !staleByDeadPid) continue;
|
|
24088
24691
|
if (awaitingAsk && staleByGrace && !staleByDeadPid) continue;
|
|
24089
24692
|
if (staleByDeadPid && awaitingAsk) {
|
|
24693
|
+
entry.presenter.onComplete(buildAbortResult("provider_exited"));
|
|
24090
24694
|
entry.agent.kill();
|
|
24091
24695
|
input.activeAgents.delete(runId);
|
|
24092
|
-
input.pushLog?.(
|
|
24696
|
+
input.pushLog?.(
|
|
24697
|
+
`reconciled dead provider pid run=${runId} (awaiting ask \u2014 finalized for resume)`
|
|
24698
|
+
);
|
|
24093
24699
|
continue;
|
|
24094
24700
|
}
|
|
24095
24701
|
abortActiveAgent(entry, { reason: staleByDeadPid ? "provider_exited" : "runner_stalled" });
|
|
@@ -24830,6 +25436,10 @@ async function startDaemon(args) {
|
|
|
24830
25436
|
const versionRefresh = setInterval(() => {
|
|
24831
25437
|
void refreshProviderVersions();
|
|
24832
25438
|
}, VERSION_REFRESH_INTERVAL_MS);
|
|
25439
|
+
const parsedMaxTransient = Number(process.env.ALAN_WORKSPACE_LEASE_MAX_TRANSIENT_FAILURES);
|
|
25440
|
+
const WORKSPACE_LEASE_MAX_TRANSIENT_FAILURES = Number.isFinite(parsedMaxTransient) && parsedMaxTransient >= 1 ? Math.floor(parsedMaxTransient) : 3;
|
|
25441
|
+
const WORKSPACE_LEASE_FAILURE_BACKOFF_BASE_MS = 1e3;
|
|
25442
|
+
const WORKSPACE_LEASE_FAILURE_BACKOFF_MAX_MS = 15e3;
|
|
24833
25443
|
const stopRunForWorkspaceLease = (runId, entry, failure) => {
|
|
24834
25444
|
entry.stage = "failed";
|
|
24835
25445
|
if (!entry.presenter.failWorkspaceLease(failure)) return;
|
|
@@ -24837,15 +25447,39 @@ async function startDaemon(args) {
|
|
|
24837
25447
|
activeAgents.delete(runId);
|
|
24838
25448
|
pushLog(`workspace-lease-lost run=${runId} code=${failure.code}`);
|
|
24839
25449
|
};
|
|
24840
|
-
const
|
|
24841
|
-
|
|
24842
|
-
|
|
24843
|
-
|
|
24844
|
-
|
|
25450
|
+
const leaseCheckPolicy = {
|
|
25451
|
+
maxTransientFailures: WORKSPACE_LEASE_MAX_TRANSIENT_FAILURES,
|
|
25452
|
+
backoffBaseMs: WORKSPACE_LEASE_FAILURE_BACKOFF_BASE_MS,
|
|
25453
|
+
backoffMaxMs: WORKSPACE_LEASE_FAILURE_BACKOFF_MAX_MS
|
|
25454
|
+
};
|
|
25455
|
+
const verifyActiveWorkspaceLease = (runId, entry, nowMs) => {
|
|
25456
|
+
const lease = entry.workspaceLease;
|
|
25457
|
+
if (!lease) return true;
|
|
25458
|
+
const state = entry.leaseCheck ?? { ...FRESH_LEASE_MONITOR_STATE };
|
|
25459
|
+
entry.leaseCheck = state;
|
|
25460
|
+
if (!isLeaseCheckDue(state, nowMs)) return true;
|
|
25461
|
+
const decision = applyLeaseCheckResult({
|
|
25462
|
+
state,
|
|
25463
|
+
detailed: lease.verifyDetailed(),
|
|
25464
|
+
nowMs,
|
|
25465
|
+
policy: leaseCheckPolicy
|
|
25466
|
+
});
|
|
25467
|
+
entry.leaseCheck = decision.state;
|
|
25468
|
+
if (decision.action === "fail" && decision.failure) {
|
|
25469
|
+
stopRunForWorkspaceLease(runId, entry, decision.failure);
|
|
25470
|
+
return false;
|
|
25471
|
+
}
|
|
25472
|
+
if (decision.action === "transient" && decision.failure) {
|
|
25473
|
+
pushLog(
|
|
25474
|
+
`workspace-lease-transient run=${runId} streak=${decision.state.transientStreak}/${WORKSPACE_LEASE_MAX_TRANSIENT_FAILURES} code=${decision.failure.code} backoff_ms=${decision.backoffMs ?? 0}`
|
|
25475
|
+
);
|
|
25476
|
+
}
|
|
25477
|
+
return true;
|
|
24845
25478
|
};
|
|
24846
25479
|
const workspaceLeaseMonitor = setInterval(() => {
|
|
25480
|
+
const nowMs = Date.now();
|
|
24847
25481
|
for (const [runId, entry] of activeAgents) {
|
|
24848
|
-
verifyActiveWorkspaceLease(runId, entry);
|
|
25482
|
+
verifyActiveWorkspaceLease(runId, entry, nowMs);
|
|
24849
25483
|
}
|
|
24850
25484
|
}, WORKSPACE_LEASE_CHECK_INTERVAL_MS);
|
|
24851
25485
|
workspaceLeaseMonitor.unref?.();
|
|
@@ -25322,7 +25956,7 @@ async function startDaemon(args) {
|
|
|
25322
25956
|
},
|
|
25323
25957
|
() => {
|
|
25324
25958
|
const entry = activeAgents.get(payload.runId);
|
|
25325
|
-
return entry ? verifyActiveWorkspaceLease(payload.runId, entry) : false;
|
|
25959
|
+
return entry ? verifyActiveWorkspaceLease(payload.runId, entry, Date.now()) : false;
|
|
25326
25960
|
}
|
|
25327
25961
|
);
|
|
25328
25962
|
let agent;
|
|
@@ -25390,6 +26024,7 @@ async function startDaemon(args) {
|
|
|
25390
26024
|
taskMeta: payload.taskMeta,
|
|
25391
26025
|
prMeta: payload.prMeta,
|
|
25392
26026
|
conversationId: payload.conversationId,
|
|
26027
|
+
alanMcp: payload.alanMcp,
|
|
25393
26028
|
taskId: payload.taskId ?? payload.taskMeta?.id,
|
|
25394
26029
|
teamId: payload.teamId,
|
|
25395
26030
|
currentUser: payload.currentUser,
|
|
@@ -25441,7 +26076,7 @@ async function startDaemon(args) {
|
|
|
25441
26076
|
([, entry]) => entry.conversationId === payload.conversationId
|
|
25442
26077
|
) : [...activeAgents.entries()];
|
|
25443
26078
|
const delivered = entries.some(([runId, entry]) => {
|
|
25444
|
-
if (!entry || !verifyActiveWorkspaceLease(runId, entry)) return false;
|
|
26079
|
+
if (!entry || !verifyActiveWorkspaceLease(runId, entry, Date.now())) return false;
|
|
25445
26080
|
return entry.agent.sendToolResponse(payload.toolId, payload.response) === true;
|
|
25446
26081
|
});
|
|
25447
26082
|
if (!delivered) {
|
|
@@ -26208,6 +26843,18 @@ function omitUndefined(record) {
|
|
|
26208
26843
|
return Object.fromEntries(Object.entries(record).filter(([, value2]) => value2 !== void 0));
|
|
26209
26844
|
}
|
|
26210
26845
|
|
|
26846
|
+
// src/resume-fallback-env.ts
|
|
26847
|
+
var RESUME_FALLBACK_CONTEXT_ENV = "ALAN_RESUME_FALLBACK_CONTEXT_B64";
|
|
26848
|
+
function decodeResumeFallbackContext(encoded) {
|
|
26849
|
+
if (!encoded) return void 0;
|
|
26850
|
+
try {
|
|
26851
|
+
const decoded = Buffer.from(encoded, "base64").toString("utf8").trim();
|
|
26852
|
+
return decoded.length > 0 ? decoded : void 0;
|
|
26853
|
+
} catch {
|
|
26854
|
+
return void 0;
|
|
26855
|
+
}
|
|
26856
|
+
}
|
|
26857
|
+
|
|
26211
26858
|
// src/web-presenter.ts
|
|
26212
26859
|
var WebPresenter = class {
|
|
26213
26860
|
constructor(wsClient, _conversationId, cwd) {
|
|
@@ -26500,6 +27147,7 @@ var SandboxEventDispatcher = class {
|
|
|
26500
27147
|
fireAndForget(event);
|
|
26501
27148
|
return;
|
|
26502
27149
|
}
|
|
27150
|
+
if (isBackgroundHeartbeat(event) && !this.outboxSocket.connected) return;
|
|
26503
27151
|
this.outbox.enqueue(this.conversationId, this.currentRunId ?? this.conversationId, event);
|
|
26504
27152
|
this.outbox.flush(this.outboxSocket);
|
|
26505
27153
|
}
|
|
@@ -26619,7 +27267,7 @@ var WSClient = class {
|
|
|
26619
27267
|
if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
|
|
26620
27268
|
this.heartbeatTimer = setInterval(() => {
|
|
26621
27269
|
if (!this.socket.connected) return;
|
|
26622
|
-
this.socket.emit("agent.heartbeat", this.buildHeartbeat());
|
|
27270
|
+
this.socket.volatile.emit("agent.heartbeat", this.buildHeartbeat());
|
|
26623
27271
|
}, AGENT_HEARTBEAT_INTERVAL_MS);
|
|
26624
27272
|
this.heartbeatTimer.unref?.();
|
|
26625
27273
|
}
|
|
@@ -26702,6 +27350,7 @@ var WSClient = class {
|
|
|
26702
27350
|
images: data.images,
|
|
26703
27351
|
files: data.files,
|
|
26704
27352
|
providerSessionId: data.providerSessionId,
|
|
27353
|
+
resumeFallbackContext: data.resumeFallbackContext,
|
|
26705
27354
|
backendKind: data.backendKind,
|
|
26706
27355
|
agentId: data.agentId,
|
|
26707
27356
|
agentPrompt: data.agentPrompt,
|
|
@@ -26893,6 +27542,7 @@ async function runSandbox(config) {
|
|
|
26893
27542
|
const presenter = new WebPresenter(wsClient, config.sessionId, config.projectPath);
|
|
26894
27543
|
presenter.setSuppressSessionLifecycle(true);
|
|
26895
27544
|
let lastProviderSessionId = config.providerSessionId;
|
|
27545
|
+
let resumeFallbackContext = config.resumeFallbackContext;
|
|
26896
27546
|
let activeBackendKind = config.backendKind || "claude_cli";
|
|
26897
27547
|
let activeAgentId = config.agentId;
|
|
26898
27548
|
let activeAgentPrompt = config.agentPrompt;
|
|
@@ -26976,10 +27626,12 @@ async function runSandbox(config) {
|
|
|
26976
27626
|
selectedContextWindow: activeContextWindow ?? null,
|
|
26977
27627
|
selectedEffortLevel: activeEffortLevel ?? null,
|
|
26978
27628
|
providerSessionId: lastProviderSessionId ?? void 0,
|
|
27629
|
+
resumeFallbackContext,
|
|
26979
27630
|
taskMeta: currentTaskMeta,
|
|
26980
27631
|
prMeta: currentPrMeta,
|
|
26981
27632
|
taskId: currentTaskId,
|
|
26982
27633
|
conversationId: currentConversationId,
|
|
27634
|
+
alanMcp: currentAlanMcp,
|
|
26983
27635
|
teamId: currentTeamId,
|
|
26984
27636
|
workflowId,
|
|
26985
27637
|
workflowExecutionId,
|
|
@@ -27027,6 +27679,7 @@ async function runSandbox(config) {
|
|
|
27027
27679
|
}
|
|
27028
27680
|
}
|
|
27029
27681
|
currentAgent = null;
|
|
27682
|
+
resumeFallbackContext = void 0;
|
|
27030
27683
|
lifecycle.info("sandbox_agent_run_complete", {
|
|
27031
27684
|
success: result.success,
|
|
27032
27685
|
iterations: result.iterations,
|
|
@@ -27053,6 +27706,7 @@ async function runSandbox(config) {
|
|
|
27053
27706
|
currentFiles = nextPayload.files ?? [];
|
|
27054
27707
|
if (nextPayload.providerSessionId) {
|
|
27055
27708
|
lastProviderSessionId = nextPayload.providerSessionId;
|
|
27709
|
+
resumeFallbackContext = nextPayload.resumeFallbackContext;
|
|
27056
27710
|
}
|
|
27057
27711
|
if (nextPayload.backendKind) {
|
|
27058
27712
|
activeBackendKind = nextPayload.backendKind;
|
|
@@ -27391,6 +28045,9 @@ async function runSessionFromEnv() {
|
|
|
27391
28045
|
const contextWindow = process.env.ALAN_CONTEXT_WINDOW || void 0;
|
|
27392
28046
|
const effortLevel = process.env.ALAN_EFFORT_LEVEL || void 0;
|
|
27393
28047
|
const providerSessionId = process.env.ALAN_PROVIDER_SESSION_ID || void 0;
|
|
28048
|
+
const resumeFallbackContext = decodeResumeFallbackContext(
|
|
28049
|
+
process.env[RESUME_FALLBACK_CONTEXT_ENV]
|
|
28050
|
+
);
|
|
27394
28051
|
const workflowId = process.env.ALAN_WORKFLOW_ID || void 0;
|
|
27395
28052
|
const workflowExecutionId = process.env.ALAN_WORKFLOW_EXECUTION_ID || void 0;
|
|
27396
28053
|
const sandboxId = process.env.ALAN_SANDBOX_ID || void 0;
|
|
@@ -27432,6 +28089,7 @@ async function runSessionFromEnv() {
|
|
|
27432
28089
|
contextWindow,
|
|
27433
28090
|
effortLevel,
|
|
27434
28091
|
providerSessionId,
|
|
28092
|
+
resumeFallbackContext,
|
|
27435
28093
|
workflowId,
|
|
27436
28094
|
workflowExecutionId,
|
|
27437
28095
|
sandboxId,
|