@aiden-ade/sandbox-agent 0.1.41 → 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 +891 -183
- 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
|
|
@@ -13514,6 +13514,16 @@ function resolveBackendRuntimeCommand(backendKind, env = getDaemonCliEnvironment
|
|
|
13514
13514
|
cacheResolvedCli(command, resolved);
|
|
13515
13515
|
return resolved ?? void 0;
|
|
13516
13516
|
}
|
|
13517
|
+
function revalidateBackendRuntimeCommand(backendKind, env = getDaemonCliEnvironment()) {
|
|
13518
|
+
if (!backendKind) return void 0;
|
|
13519
|
+
const command = BACKEND_CLI_COMMANDS[backendKind];
|
|
13520
|
+
if (!command) return void 0;
|
|
13521
|
+
resolvedCliCache.delete(command);
|
|
13522
|
+
const spec = Object.values(PROVIDER_CLI_COMMANDS).find((entry) => entry.command === command);
|
|
13523
|
+
const envOverride = spec ? spec.envVars.map((name) => env[name]?.trim()).find(Boolean) : void 0;
|
|
13524
|
+
if (envOverride) resolvedCliCache.delete(envOverride);
|
|
13525
|
+
return resolveBackendRuntimeCommand(backendKind, env);
|
|
13526
|
+
}
|
|
13517
13527
|
|
|
13518
13528
|
// src/computer-readiness.ts
|
|
13519
13529
|
var import_node_fs5 = require("fs");
|
|
@@ -14545,15 +14555,6 @@ If the index returns no useful path, say so and run one targeted local search in
|
|
|
14545
14555
|
function buildAlanTeamCodeContextOverlay(teamId) {
|
|
14546
14556
|
return [buildAlanTeamScopePrompt(teamId), ALAN_CODE_CONTEXT_PROMPT].join("\n\n");
|
|
14547
14557
|
}
|
|
14548
|
-
function buildCodeDiscoveryEnforcementTail() {
|
|
14549
|
-
return [
|
|
14550
|
-
"## MANDATORY \u2014 index once, then use local source",
|
|
14551
|
-
"1. For codebase discovery, make one `mcp__alan__search_code_context` call (hybrid, topK 5). Omit teamId \u2014 Alan session headers resolve it.",
|
|
14552
|
-
"2. Once useful paths are returned, stop querying the index and read those local files. The current worktree is the source of truth.",
|
|
14553
|
-
"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.",
|
|
14554
|
-
"Do not repeat or rephrase index searches once paths are known, and do not claim current behavior from index excerpts alone."
|
|
14555
|
-
].join("\n");
|
|
14556
|
-
}
|
|
14557
14558
|
var PLAN_MODE_PROMPT = [
|
|
14558
14559
|
"You are in Alan plan mode.",
|
|
14559
14560
|
"Analyze the task, inspect the relevant code and context, and produce a concrete implementation plan.",
|
|
@@ -15282,11 +15283,12 @@ var import_os3 = require("os");
|
|
|
15282
15283
|
var import_path3 = require("path");
|
|
15283
15284
|
var import_readline = require("readline");
|
|
15284
15285
|
var import_child_process2 = require("child_process");
|
|
15286
|
+
var import_child_process3 = require("child_process");
|
|
15285
15287
|
var import_crypto2 = require("crypto");
|
|
15286
15288
|
var import_fs4 = require("fs");
|
|
15287
15289
|
var import_os4 = require("os");
|
|
15288
15290
|
var import_path4 = require("path");
|
|
15289
|
-
var
|
|
15291
|
+
var import_child_process4 = require("child_process");
|
|
15290
15292
|
var import_util10 = require("util");
|
|
15291
15293
|
var import_fs5 = require("fs");
|
|
15292
15294
|
var import_os5 = require("os");
|
|
@@ -15432,6 +15434,11 @@ function buildSessionHeaders(input) {
|
|
|
15432
15434
|
if (input.allowedTools?.length) {
|
|
15433
15435
|
headers["x-allowed-tools"] = input.allowedTools.join(",");
|
|
15434
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
|
+
}
|
|
15435
15442
|
return headers;
|
|
15436
15443
|
}
|
|
15437
15444
|
function getTomlSectionName(line) {
|
|
@@ -15642,6 +15649,23 @@ function syncAlanMcpSessionHeaders(input) {
|
|
|
15642
15649
|
}
|
|
15643
15650
|
return written;
|
|
15644
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
|
+
}
|
|
15645
15669
|
function buildPromptWithSystem(config, promptText) {
|
|
15646
15670
|
const parts2 = [
|
|
15647
15671
|
config.systemPrompt?.trim(),
|
|
@@ -15650,6 +15674,13 @@ function buildPromptWithSystem(config, promptText) {
|
|
|
15650
15674
|
].filter((value2) => Boolean(value2 && value2.length > 0));
|
|
15651
15675
|
return parts2.join("\n\n");
|
|
15652
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
|
+
}
|
|
15653
15684
|
function buildPlanModePrefix(promptText) {
|
|
15654
15685
|
return [
|
|
15655
15686
|
"You are in plan-only mode.",
|
|
@@ -15816,7 +15847,7 @@ var ERROR_SPECS = {
|
|
|
15816
15847
|
recoveryClass: "needs_env"
|
|
15817
15848
|
},
|
|
15818
15849
|
auth_expired: {
|
|
15819
|
-
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.",
|
|
15820
15851
|
recoveryClass: "user_fixable"
|
|
15821
15852
|
},
|
|
15822
15853
|
subscription_required: {
|
|
@@ -15876,6 +15907,7 @@ function specForErrorKind(errorKind) {
|
|
|
15876
15907
|
const spec = ERROR_SPECS[errorKind];
|
|
15877
15908
|
return { message: spec.message, errorKind, recoveryClass: spec.recoveryClass };
|
|
15878
15909
|
}
|
|
15910
|
+
var RESUME_SESSION_UNAVAILABLE_MESSAGE = ERROR_SPECS.resume_failed.message;
|
|
15879
15911
|
function isLikelyProviderAuthError(stderr) {
|
|
15880
15912
|
const lower = stderr.toLowerCase();
|
|
15881
15913
|
return lower.includes("api key") || lower.includes("api_key") || lower.includes("invalid api key") || lower.includes("api key invalid") || lower.includes("unauthorized") || lower.includes("authentication failed") || lower.includes("authentication error") || lower.includes("provider settings") || lower.includes("invalid token") || lower.includes("token expired");
|
|
@@ -15905,7 +15937,7 @@ function normalizeCodexCliErrorMessage(message) {
|
|
|
15905
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.";
|
|
15906
15938
|
}
|
|
15907
15939
|
if (lower.includes("access token could not be refreshed") || lower.includes("refresh_token_invalidated") || lower.includes("refresh token has been invalidated")) {
|
|
15908
|
-
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.";
|
|
15909
15941
|
}
|
|
15910
15942
|
return message;
|
|
15911
15943
|
}
|
|
@@ -16031,6 +16063,54 @@ function spawnCli(command, args, context) {
|
|
|
16031
16063
|
detached: process.platform !== "win32"
|
|
16032
16064
|
});
|
|
16033
16065
|
}
|
|
16066
|
+
function isAlreadyDead(error) {
|
|
16067
|
+
return error?.code === "ESRCH";
|
|
16068
|
+
}
|
|
16069
|
+
function killProcessTree(pid, options = {}) {
|
|
16070
|
+
const { signal = "SIGTERM", child, onError } = options;
|
|
16071
|
+
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) {
|
|
16072
|
+
if (child) {
|
|
16073
|
+
try {
|
|
16074
|
+
child.kill(signal);
|
|
16075
|
+
} catch (err) {
|
|
16076
|
+
if (!isAlreadyDead(err)) onError?.("fallback", err);
|
|
16077
|
+
}
|
|
16078
|
+
}
|
|
16079
|
+
return;
|
|
16080
|
+
}
|
|
16081
|
+
if (process.platform === "win32") {
|
|
16082
|
+
try {
|
|
16083
|
+
const killer = (0, import_child_process3.spawn)("taskkill", ["/pid", String(pid), "/t", "/f"], {
|
|
16084
|
+
stdio: "ignore",
|
|
16085
|
+
windowsHide: true
|
|
16086
|
+
});
|
|
16087
|
+
killer.once("error", (err) => {
|
|
16088
|
+
if (child) {
|
|
16089
|
+
try {
|
|
16090
|
+
child.kill();
|
|
16091
|
+
} catch {
|
|
16092
|
+
}
|
|
16093
|
+
}
|
|
16094
|
+
onError?.("taskkill", err);
|
|
16095
|
+
});
|
|
16096
|
+
} catch (err) {
|
|
16097
|
+
onError?.("taskkill", err);
|
|
16098
|
+
}
|
|
16099
|
+
return;
|
|
16100
|
+
}
|
|
16101
|
+
try {
|
|
16102
|
+
process.kill(-pid, signal);
|
|
16103
|
+
} catch (groupErr) {
|
|
16104
|
+
if (!isAlreadyDead(groupErr)) onError?.("group", groupErr);
|
|
16105
|
+
if (child) {
|
|
16106
|
+
try {
|
|
16107
|
+
child.kill(signal);
|
|
16108
|
+
} catch (fallbackErr) {
|
|
16109
|
+
if (!isAlreadyDead(fallbackErr)) onError?.("fallback", fallbackErr);
|
|
16110
|
+
}
|
|
16111
|
+
}
|
|
16112
|
+
}
|
|
16113
|
+
}
|
|
16034
16114
|
function emitSessionNotice(presenter, kind, message) {
|
|
16035
16115
|
if (presenter.onNotice) {
|
|
16036
16116
|
void presenter.onNotice(kind, message);
|
|
@@ -16095,6 +16175,12 @@ function formatCliSpawnError(command, error) {
|
|
|
16095
16175
|
}
|
|
16096
16176
|
return error;
|
|
16097
16177
|
}
|
|
16178
|
+
function waitForSpawnOutcome(child) {
|
|
16179
|
+
return new Promise((resolve22) => {
|
|
16180
|
+
child.once("spawn", () => resolve22(null));
|
|
16181
|
+
child.once("error", (error) => resolve22(error));
|
|
16182
|
+
});
|
|
16183
|
+
}
|
|
16098
16184
|
function createGenericCliBackend(options) {
|
|
16099
16185
|
return {
|
|
16100
16186
|
kind: options.kind,
|
|
@@ -16108,7 +16194,23 @@ function createGenericCliBackend(options) {
|
|
|
16108
16194
|
};
|
|
16109
16195
|
const prompt = options.augmentPrompt?.(context) ?? (context.config.mode === "plan" ? buildPlanModePrefix(buildPromptWithSystem(context.config, context.promptText)) : buildPromptWithSystem(context.config, context.promptText));
|
|
16110
16196
|
const args = options.buildArgs?.(context, prompt) ?? options.args.map((arg) => arg === "{{prompt}}" ? prompt : arg);
|
|
16111
|
-
|
|
16197
|
+
let command = options.command;
|
|
16198
|
+
let child = spawnCli(command, args, context);
|
|
16199
|
+
if (context.revalidateCommand) {
|
|
16200
|
+
const spawnError = await waitForSpawnOutcome(child);
|
|
16201
|
+
if (spawnError) {
|
|
16202
|
+
const fresh = spawnError.code === "ENOENT" ? context.revalidateCommand()?.trim() || null : null;
|
|
16203
|
+
if (fresh && fresh !== command) {
|
|
16204
|
+
console.warn(
|
|
16205
|
+
`[${options.kind}] Spawn failed ENOENT for "${command}" \u2014 re-resolved to "${fresh}", retrying once`
|
|
16206
|
+
);
|
|
16207
|
+
command = fresh;
|
|
16208
|
+
child = spawnCli(command, args, context);
|
|
16209
|
+
} else {
|
|
16210
|
+
throw formatCliSpawnError(command, spawnError);
|
|
16211
|
+
}
|
|
16212
|
+
}
|
|
16213
|
+
}
|
|
16112
16214
|
state.process = child;
|
|
16113
16215
|
state.lastRawOutputAtMs = Date.now();
|
|
16114
16216
|
context.onProcessSpawned?.(child);
|
|
@@ -16119,8 +16221,8 @@ function createGenericCliBackend(options) {
|
|
|
16119
16221
|
const safeArgs = args.map(
|
|
16120
16222
|
(a, i) => i > 0 && args[i - 1] === "--system-prompt" ? `"<system-prompt ${a.length} chars>"` : a
|
|
16121
16223
|
);
|
|
16122
|
-
console.info(`[${options.kind}] Spawning: ${
|
|
16123
|
-
presenter.recordRawTranscript?.("system", `${
|
|
16224
|
+
console.info(`[${options.kind}] Spawning: ${command} ${safeArgs.join(" ")}`);
|
|
16225
|
+
presenter.recordRawTranscript?.("system", `${command} ${args.join(" ")}`, {
|
|
16124
16226
|
backendKind: options.kind
|
|
16125
16227
|
});
|
|
16126
16228
|
child.stdin.on("error", (err) => {
|
|
@@ -16152,14 +16254,7 @@ function createGenericCliBackend(options) {
|
|
|
16152
16254
|
console.warn(
|
|
16153
16255
|
`[${options.kind}] No stdout within ${options.startupTimeoutMs}ms of spawn \u2014 killing hung process`
|
|
16154
16256
|
);
|
|
16155
|
-
|
|
16156
|
-
if (process.platform === "win32") {
|
|
16157
|
-
child.kill("SIGKILL");
|
|
16158
|
-
} else if (child.pid) {
|
|
16159
|
-
process.kill(-child.pid, "SIGKILL");
|
|
16160
|
-
}
|
|
16161
|
-
} catch {
|
|
16162
|
-
}
|
|
16257
|
+
killProcessTree(child.pid, { signal: "SIGKILL", child });
|
|
16163
16258
|
}, options.startupTimeoutMs);
|
|
16164
16259
|
}
|
|
16165
16260
|
stdoutRl.on("line", (line) => {
|
|
@@ -16202,16 +16297,7 @@ function createGenericCliBackend(options) {
|
|
|
16202
16297
|
});
|
|
16203
16298
|
child.on("exit", (code) => resolve22(code ?? 0));
|
|
16204
16299
|
const onAbort = () => {
|
|
16205
|
-
|
|
16206
|
-
if (!pid) return;
|
|
16207
|
-
try {
|
|
16208
|
-
process.kill(-pid, "SIGTERM");
|
|
16209
|
-
} catch {
|
|
16210
|
-
try {
|
|
16211
|
-
child.kill("SIGTERM");
|
|
16212
|
-
} catch {
|
|
16213
|
-
}
|
|
16214
|
-
}
|
|
16300
|
+
killProcessTree(child.pid, { signal: "SIGTERM", child });
|
|
16215
16301
|
};
|
|
16216
16302
|
context.abortController.signal.addEventListener("abort", onAbort, { once: true });
|
|
16217
16303
|
});
|
|
@@ -16232,6 +16318,9 @@ function createGenericCliBackend(options) {
|
|
|
16232
16318
|
} else if (state.resultDeferredOnBackgroundWork && state.activeBackgroundTaskIds?.size) {
|
|
16233
16319
|
idleTimeoutReason = "background_task";
|
|
16234
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");
|
|
16235
16324
|
} else {
|
|
16236
16325
|
armIdleTimer();
|
|
16237
16326
|
}
|
|
@@ -16244,17 +16333,11 @@ function createGenericCliBackend(options) {
|
|
|
16244
16333
|
if (idleTimer) clearTimeout(idleTimer);
|
|
16245
16334
|
if (raceResult === "idle_timeout") {
|
|
16246
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";
|
|
16247
16337
|
console.warn(
|
|
16248
|
-
`[${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`
|
|
16249
16339
|
);
|
|
16250
|
-
|
|
16251
|
-
if (process.platform === "win32") {
|
|
16252
|
-
child.kill("SIGKILL");
|
|
16253
|
-
} else if (child.pid) {
|
|
16254
|
-
process.kill(-child.pid, "SIGKILL");
|
|
16255
|
-
}
|
|
16256
|
-
} catch {
|
|
16257
|
-
}
|
|
16340
|
+
killProcessTree(child.pid, { signal: "SIGKILL", child });
|
|
16258
16341
|
exitCode = await Promise.race([
|
|
16259
16342
|
exitPromise,
|
|
16260
16343
|
new Promise((resolve22) => setTimeout(() => resolve22(1), 5e3))
|
|
@@ -16270,14 +16353,7 @@ function createGenericCliBackend(options) {
|
|
|
16270
16353
|
console.warn(
|
|
16271
16354
|
`[${options.kind}] Process still alive ${GRACE_MS}ms after result event, killing`
|
|
16272
16355
|
);
|
|
16273
|
-
|
|
16274
|
-
if (process.platform === "win32") {
|
|
16275
|
-
child.kill("SIGKILL");
|
|
16276
|
-
} else if (child.pid) {
|
|
16277
|
-
process.kill(-child.pid, "SIGKILL");
|
|
16278
|
-
}
|
|
16279
|
-
} catch {
|
|
16280
|
-
}
|
|
16356
|
+
killProcessTree(child.pid, { signal: "SIGKILL", child });
|
|
16281
16357
|
}
|
|
16282
16358
|
} else {
|
|
16283
16359
|
exitCode = raceResult;
|
|
@@ -16332,6 +16408,25 @@ function createGenericCliBackend(options) {
|
|
|
16332
16408
|
console.error(`[${options.kind}] Process force-killed by startup watchdog`, {
|
|
16333
16409
|
stderrLines: stderrLines.slice(-10)
|
|
16334
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
|
+
}
|
|
16335
16430
|
return {
|
|
16336
16431
|
success: false,
|
|
16337
16432
|
summary: "Agent produced no output",
|
|
@@ -16381,7 +16476,7 @@ function createGenericCliBackend(options) {
|
|
|
16381
16476
|
filesModified: [],
|
|
16382
16477
|
planFilesCreated: [],
|
|
16383
16478
|
iterations: Math.max(state.iterations, 1),
|
|
16384
|
-
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.",
|
|
16385
16480
|
providerSessionId: state.runtimeSessionId,
|
|
16386
16481
|
runtimeSessionId: state.runtimeSessionId,
|
|
16387
16482
|
backendKind: options.kind,
|
|
@@ -16440,6 +16535,9 @@ function createGenericCliBackend(options) {
|
|
|
16440
16535
|
classifiedErrorKind = detailed.errorKind;
|
|
16441
16536
|
classifiedRecoveryClass = detailed.recoveryClass;
|
|
16442
16537
|
}
|
|
16538
|
+
if (classifiedErrorKind === "resume_failed" && !requestedResumeId) {
|
|
16539
|
+
classifiedErrorKind = void 0;
|
|
16540
|
+
}
|
|
16443
16541
|
}
|
|
16444
16542
|
return {
|
|
16445
16543
|
success: !failed,
|
|
@@ -16448,7 +16546,11 @@ function createGenericCliBackend(options) {
|
|
|
16448
16546
|
planFilesCreated: [],
|
|
16449
16547
|
iterations: Math.max(state.iterations, 1),
|
|
16450
16548
|
error: classifiedError,
|
|
16451
|
-
|
|
16549
|
+
// Surface every recognized, machine-actionable kind (model_mismatch,
|
|
16550
|
+
// resume_failed, auth_*, …). The catch-all unknown_cli_error carries no routing
|
|
16551
|
+
// signal — nothing downstream keys on it — so leave errorKind absent for it,
|
|
16552
|
+
// keeping an unrelated failure untagged even when a resume id was set.
|
|
16553
|
+
...classifiedErrorKind && classifiedErrorKind !== "unknown_cli_error" ? { errorKind: classifiedErrorKind } : {},
|
|
16452
16554
|
...classifiedRecoveryClass ? { recoveryClass: classifiedRecoveryClass } : {},
|
|
16453
16555
|
providerSessionId: state.runtimeSessionId,
|
|
16454
16556
|
runtimeSessionId: state.runtimeSessionId,
|
|
@@ -16906,7 +17008,7 @@ function claudeSessionLogExists(input) {
|
|
|
16906
17008
|
if (!(0, import_fs4.existsSync)(projectDir)) return false;
|
|
16907
17009
|
return (0, import_fs4.existsSync)((0, import_path4.join)(projectDir, `${input.sessionId}.jsonl`));
|
|
16908
17010
|
}
|
|
16909
|
-
var execFileAsync = (0, import_util10.promisify)(
|
|
17011
|
+
var execFileAsync = (0, import_util10.promisify)(import_child_process4.execFile);
|
|
16910
17012
|
var flagSupportCache = /* @__PURE__ */ new Map();
|
|
16911
17013
|
function helpAdvertisesFlag(helpText, flag) {
|
|
16912
17014
|
return helpText.includes(flag);
|
|
@@ -17082,6 +17184,16 @@ var TERMINAL_TASK_NOTIFICATION_STATUSES = /* @__PURE__ */ new Set([
|
|
|
17082
17184
|
"cancelled",
|
|
17083
17185
|
"canceled"
|
|
17084
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
|
+
]);
|
|
17085
17197
|
function extractToolResultContent(content) {
|
|
17086
17198
|
if (typeof content === "string") return content;
|
|
17087
17199
|
if (!Array.isArray(content)) return "";
|
|
@@ -17099,6 +17211,7 @@ function stringField2(record, key) {
|
|
|
17099
17211
|
function isTerminalTaskNotification(record) {
|
|
17100
17212
|
const status = stringField2(record, "status")?.toLowerCase();
|
|
17101
17213
|
if (status && TERMINAL_TASK_NOTIFICATION_STATUSES.has(status)) return true;
|
|
17214
|
+
if (status && NON_TERMINAL_TASK_NOTIFICATION_STATUSES.has(status)) return false;
|
|
17102
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";
|
|
17103
17216
|
}
|
|
17104
17217
|
function isFailedTaskNotification(record) {
|
|
@@ -17190,20 +17303,25 @@ function handleClaudeStructuredEvent(parsed, context, state) {
|
|
|
17190
17303
|
state.iterations += 1;
|
|
17191
17304
|
const message = typeof parsed.message === "object" && parsed.message !== null ? parsed.message : null;
|
|
17192
17305
|
const content = Array.isArray(message?.content) ? message.content : [];
|
|
17306
|
+
let previousBlockWasText = false;
|
|
17193
17307
|
for (const block of content) {
|
|
17194
17308
|
if (typeof block !== "object" || block === null || !("type" in block)) continue;
|
|
17195
17309
|
if (block.type === "text" && typeof block.text === "string") {
|
|
17196
17310
|
const text = block.text;
|
|
17197
17311
|
state.summary += `${text}
|
|
17198
17312
|
`;
|
|
17199
|
-
void presenter.onAssistantText(
|
|
17313
|
+
void presenter.onAssistantText(previousBlockWasText ? `
|
|
17314
|
+
${text}` : text);
|
|
17315
|
+
previousBlockWasText = true;
|
|
17200
17316
|
continue;
|
|
17201
17317
|
}
|
|
17202
17318
|
if (block.type === "thinking" && typeof block.thinking === "string") {
|
|
17319
|
+
previousBlockWasText = false;
|
|
17203
17320
|
void presenter.onThinking(block.thinking);
|
|
17204
17321
|
continue;
|
|
17205
17322
|
}
|
|
17206
17323
|
if (block.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") {
|
|
17324
|
+
previousBlockWasText = false;
|
|
17207
17325
|
const toolBlock = block;
|
|
17208
17326
|
void presenter.onToolUse(
|
|
17209
17327
|
toolBlock.name,
|
|
@@ -17319,7 +17437,7 @@ function handleClaudeStructuredEvent(parsed, context, state) {
|
|
|
17319
17437
|
state.iterations = parsed.num_turns;
|
|
17320
17438
|
}
|
|
17321
17439
|
const subtype = typeof parsed.subtype === "string" ? parsed.subtype : "";
|
|
17322
|
-
if ((
|
|
17440
|
+
if (subtype.startsWith("error") && !state.error) {
|
|
17323
17441
|
const errObj = typeof parsed.error === "object" && parsed.error !== null ? parsed.error : null;
|
|
17324
17442
|
const resumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
|
|
17325
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;
|
|
@@ -17470,7 +17588,7 @@ function createClaudeCliBackend(command = "claude", defaultArgs = []) {
|
|
|
17470
17588
|
const basePromptText = resumeContextPrefix ? `${resumeContextPrefix}
|
|
17471
17589
|
|
|
17472
17590
|
${ctx.promptText}` : ctx.promptText;
|
|
17473
|
-
const promptText = ctx.config.mode === "plan" ? buildPlanModePrefix(
|
|
17591
|
+
const promptText = ctx.config.mode === "plan" ? buildPlanModePrefix(basePromptText) : basePromptText;
|
|
17474
17592
|
if (promptText.trim()) {
|
|
17475
17593
|
contentBlocks.push({ type: "text", text: promptText });
|
|
17476
17594
|
}
|
|
@@ -17561,6 +17679,25 @@ function buildGeneratedImageFromCodexPayload(payload) {
|
|
|
17561
17679
|
idFields: []
|
|
17562
17680
|
});
|
|
17563
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
|
+
}
|
|
17564
17701
|
function latestUserMessageLineIndex(lines) {
|
|
17565
17702
|
let latestIndex = -1;
|
|
17566
17703
|
for (let index = 0; index < lines.length; index += 1) {
|
|
@@ -17568,7 +17705,7 @@ function latestUserMessageLineIndex(lines) {
|
|
|
17568
17705
|
if (!line) continue;
|
|
17569
17706
|
const entry = parseJsonObject(line);
|
|
17570
17707
|
const payload = entry && typeof entry.payload === "object" && entry.payload !== null ? entry.payload : null;
|
|
17571
|
-
if (payload?.type === "message" && payload.role === "user") {
|
|
17708
|
+
if (payload?.type === "message" && payload.role === "user" && !isSyntheticUserRolloutMessage(payload)) {
|
|
17572
17709
|
latestIndex = index;
|
|
17573
17710
|
}
|
|
17574
17711
|
}
|
|
@@ -17632,7 +17769,9 @@ async function replayCodexMcpToolEventsFromSessionLog(context, state) {
|
|
|
17632
17769
|
}
|
|
17633
17770
|
}
|
|
17634
17771
|
var CODEX_EXIT_GRACE_MS = 3e4;
|
|
17635
|
-
|
|
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) {
|
|
17636
17775
|
if (state.exitGraceKillTimer) return;
|
|
17637
17776
|
const child = state.process;
|
|
17638
17777
|
if (!child) return;
|
|
@@ -17641,19 +17780,16 @@ function armCodexExitGraceKill(state) {
|
|
|
17641
17780
|
console.warn(
|
|
17642
17781
|
"[codex_app_server] Process did not exit after turn completion; killing process group"
|
|
17643
17782
|
);
|
|
17644
|
-
|
|
17645
|
-
|
|
17646
|
-
|
|
17647
|
-
|
|
17648
|
-
|
|
17649
|
-
|
|
17650
|
-
}
|
|
17651
|
-
}
|
|
17652
|
-
|
|
17653
|
-
|
|
17654
|
-
} catch {
|
|
17655
|
-
}
|
|
17656
|
-
}, CODEX_EXIT_GRACE_MS);
|
|
17783
|
+
killProcessTree(child.pid, { signal: "SIGTERM", child });
|
|
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);
|
|
17657
17793
|
timer.unref?.();
|
|
17658
17794
|
state.exitGraceKillTimer = timer;
|
|
17659
17795
|
}
|
|
@@ -17662,6 +17798,167 @@ function disarmCodexExitGraceKill(state) {
|
|
|
17662
17798
|
clearTimeout(state.exitGraceKillTimer);
|
|
17663
17799
|
state.exitGraceKillTimer = void 0;
|
|
17664
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
|
+
}
|
|
17665
17962
|
function trackCodexStreamedToolId(state, toolId) {
|
|
17666
17963
|
if (!state.codexStreamedToolIds) state.codexStreamedToolIds = /* @__PURE__ */ new Set();
|
|
17667
17964
|
if (state.codexStreamedToolIds.has(toolId)) return false;
|
|
@@ -17692,6 +17989,7 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17692
17989
|
const presenter = context.presenter;
|
|
17693
17990
|
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
17694
17991
|
if (!type) return false;
|
|
17992
|
+
touchCodexBackgroundGrace(state);
|
|
17695
17993
|
if (typeof parsed.thread_id === "string") {
|
|
17696
17994
|
state.runtimeSessionId = parsed.thread_id;
|
|
17697
17995
|
}
|
|
@@ -17716,7 +18014,12 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17716
18014
|
return true;
|
|
17717
18015
|
case "turn.started":
|
|
17718
18016
|
disarmCodexExitGraceKill(state);
|
|
17719
|
-
state.
|
|
18017
|
+
state.codexAwaitingWorkersAfterTurnEnd = false;
|
|
18018
|
+
state.resultDeferredOnBackgroundWork = false;
|
|
18019
|
+
if (state.codexIterationEventFamily !== "legacy") {
|
|
18020
|
+
state.codexIterationEventFamily = "modern";
|
|
18021
|
+
state.iterations += 1;
|
|
18022
|
+
}
|
|
17720
18023
|
return true;
|
|
17721
18024
|
case "session_configured":
|
|
17722
18025
|
if (typeof parsed.session_id === "string") {
|
|
@@ -17725,7 +18028,12 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17725
18028
|
return true;
|
|
17726
18029
|
case "task_started":
|
|
17727
18030
|
disarmCodexExitGraceKill(state);
|
|
17728
|
-
state.
|
|
18031
|
+
state.codexAwaitingWorkersAfterTurnEnd = false;
|
|
18032
|
+
state.resultDeferredOnBackgroundWork = false;
|
|
18033
|
+
if (state.codexIterationEventFamily !== "modern") {
|
|
18034
|
+
state.codexIterationEventFamily = "legacy";
|
|
18035
|
+
state.iterations += 1;
|
|
18036
|
+
}
|
|
17729
18037
|
return true;
|
|
17730
18038
|
case "item.started": {
|
|
17731
18039
|
const item = typeof parsed.item === "object" && parsed.item !== null ? parsed.item : null;
|
|
@@ -17752,6 +18060,8 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17752
18060
|
}
|
|
17753
18061
|
return true;
|
|
17754
18062
|
}
|
|
18063
|
+
case "collab_tool_call":
|
|
18064
|
+
return handleCodexCollabToolCallItem(context, state, item, "started");
|
|
17755
18065
|
// Text-bearing and patch/todo items carry no useful live-start payload; they
|
|
17756
18066
|
// are surfaced on item.completed. Recognized (do not count as unhandled).
|
|
17757
18067
|
case "reasoning":
|
|
@@ -17763,6 +18073,21 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17763
18073
|
return false;
|
|
17764
18074
|
}
|
|
17765
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
|
+
}
|
|
17766
18091
|
case "item.completed": {
|
|
17767
18092
|
const item = typeof parsed.item === "object" && parsed.item !== null ? parsed.item : null;
|
|
17768
18093
|
if (!item || typeof item.type !== "string") return true;
|
|
@@ -17827,6 +18152,8 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17827
18152
|
void presenter.onTodoWrite?.(todos);
|
|
17828
18153
|
return true;
|
|
17829
18154
|
}
|
|
18155
|
+
case "collab_tool_call":
|
|
18156
|
+
return handleCodexCollabToolCallItem(context, state, item, "completed");
|
|
17830
18157
|
default:
|
|
17831
18158
|
return false;
|
|
17832
18159
|
}
|
|
@@ -17848,7 +18175,9 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17848
18175
|
return true;
|
|
17849
18176
|
}
|
|
17850
18177
|
case "exec_command_begin": {
|
|
17851
|
-
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;
|
|
17852
18181
|
const command = Array.isArray(parsed.command) ? parsed.command.join(" ") : "";
|
|
17853
18182
|
const cwd = typeof parsed.cwd === "string" ? parsed.cwd : context.cwd;
|
|
17854
18183
|
trackCodexStreamedToolId(state, toolId);
|
|
@@ -17856,7 +18185,10 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17856
18185
|
return true;
|
|
17857
18186
|
}
|
|
17858
18187
|
case "exec_command_end": {
|
|
17859
|
-
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;
|
|
17860
18192
|
const output = typeof parsed.formatted_output === "string" ? parsed.formatted_output : typeof parsed.aggregated_output === "string" ? parsed.aggregated_output : "";
|
|
17861
18193
|
void presenter.onToolResult?.(toolId, output);
|
|
17862
18194
|
return true;
|
|
@@ -17864,9 +18196,8 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17864
18196
|
case "mcp_tool_call_begin": {
|
|
17865
18197
|
const toolId = typeof parsed.call_id === "string" ? parsed.call_id : `mcp-${Date.now()}`;
|
|
17866
18198
|
const invocation = typeof parsed.invocation === "object" && parsed.invocation !== null ? parsed.invocation : {};
|
|
17867
|
-
const tool = typeof invocation.tool_name === "string" ? invocation.tool_name : typeof invocation.tool === "string" ? invocation.tool : "MCP Tool";
|
|
17868
18199
|
trackCodexStreamedToolId(state, toolId);
|
|
17869
|
-
void presenter.onToolUse(
|
|
18200
|
+
void presenter.onToolUse(buildCodexMcpToolName(invocation), invocation, toolId);
|
|
17870
18201
|
return true;
|
|
17871
18202
|
}
|
|
17872
18203
|
case "mcp_tool_call_end": {
|
|
@@ -17904,7 +18235,7 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17904
18235
|
const lastMessage = typeof parsed.last_agent_message === "string" ? parsed.last_agent_message : "";
|
|
17905
18236
|
if (lastMessage.length > 0) state.summary = lastMessage;
|
|
17906
18237
|
state.error = void 0;
|
|
17907
|
-
|
|
18238
|
+
completeCodexTurnRespectingWorkers(state);
|
|
17908
18239
|
return true;
|
|
17909
18240
|
}
|
|
17910
18241
|
case "turn.completed": {
|
|
@@ -17927,7 +18258,108 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17927
18258
|
cacheCreationTokens: state.usage.cacheCreationTokens
|
|
17928
18259
|
});
|
|
17929
18260
|
state.error = void 0;
|
|
17930
|
-
|
|
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" });
|
|
17931
18363
|
return true;
|
|
17932
18364
|
}
|
|
17933
18365
|
case "error": {
|
|
@@ -18046,10 +18478,11 @@ function createCodexRuntimeBackend(command = "codex", defaultArgs = []) {
|
|
|
18046
18478
|
},
|
|
18047
18479
|
promptViaStdin: true,
|
|
18048
18480
|
augmentPrompt: (ctx) => {
|
|
18481
|
+
const isResume = Boolean(resumableSessionId);
|
|
18049
18482
|
const promptWithContext = resumeContextPrefix ? `${resumeContextPrefix}
|
|
18050
18483
|
|
|
18051
18484
|
${ctx.promptText}` : ctx.promptText;
|
|
18052
|
-
const basePrompt =
|
|
18485
|
+
const basePrompt = buildResumeAwarePrompt(ctx.config, promptWithContext, { isResume });
|
|
18053
18486
|
return ctx.config.mode === "plan" ? buildPlanModePrefix(basePrompt) : basePrompt;
|
|
18054
18487
|
},
|
|
18055
18488
|
parseStructuredLine: parseCodexStructuredLine,
|
|
@@ -18255,10 +18688,21 @@ function handleCursorStructuredEvent(parsed, context, state) {
|
|
|
18255
18688
|
}
|
|
18256
18689
|
case "tool_call": {
|
|
18257
18690
|
const subtype = typeof parsed.subtype === "string" ? parsed.subtype : "";
|
|
18258
|
-
const
|
|
18691
|
+
const hasCallId = typeof parsed.call_id === "string" && parsed.call_id.length > 0;
|
|
18259
18692
|
const toolCall = typeof parsed.tool_call === "object" && parsed.tool_call !== null ? parsed.tool_call : null;
|
|
18260
18693
|
const entry = toolCall ? extractCursorToolEntry(toolCall) : null;
|
|
18261
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;
|
|
18262
18706
|
state.iterations = Math.max(state.iterations, 1);
|
|
18263
18707
|
const toolName = formatCursorToolName(entry.rawName, entry.payload);
|
|
18264
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;
|
|
@@ -18267,6 +18711,7 @@ function handleCursorStructuredEvent(parsed, context, state) {
|
|
|
18267
18711
|
return true;
|
|
18268
18712
|
}
|
|
18269
18713
|
if (subtype === "completed") {
|
|
18714
|
+
if (!hasCallId) state.lastAnonymousCursorToolId = void 0;
|
|
18270
18715
|
void presenter.onToolResult?.(toolId, buildCursorToolResultText(entry.payload.result));
|
|
18271
18716
|
}
|
|
18272
18717
|
return true;
|
|
@@ -18345,7 +18790,10 @@ function buildCursorAgentModelArg(modelId, options) {
|
|
|
18345
18790
|
const wireEffort = resolveWireEffort({
|
|
18346
18791
|
harness: "cursor_agent_cli",
|
|
18347
18792
|
modelId: baseId,
|
|
18348
|
-
|
|
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,
|
|
18349
18797
|
effortLevels
|
|
18350
18798
|
});
|
|
18351
18799
|
let id = baseId;
|
|
@@ -18359,7 +18807,8 @@ function buildCursorAgentModelArg(modelId, options) {
|
|
|
18359
18807
|
}
|
|
18360
18808
|
}
|
|
18361
18809
|
const contextWindow = asContextWindow(options?.selectedContextWindow);
|
|
18362
|
-
|
|
18810
|
+
const modelSupports1m = (options?.contextWindows ?? []).includes("1m");
|
|
18811
|
+
if (contextWindow === "1m" && id === baseId && modelSupports1m) {
|
|
18363
18812
|
return `${baseId}[context=1m]`;
|
|
18364
18813
|
}
|
|
18365
18814
|
if (parsed.fast) return `${id}-fast`;
|
|
@@ -18449,7 +18898,8 @@ function createCursorAgentCliBackend(command = "cursor-agent", defaultArgs = [])
|
|
|
18449
18898
|
buildCursorAgentModelArg(model, {
|
|
18450
18899
|
selectedEffortLevel: ctx.config.selectedEffortLevel,
|
|
18451
18900
|
selectedContextWindow: ctx.config.selectedContextWindow,
|
|
18452
|
-
effortLevels: modelDef?.capabilities?.effortLevels
|
|
18901
|
+
effortLevels: modelDef?.capabilities?.effortLevels,
|
|
18902
|
+
contextWindows: modelDef?.capabilities?.contextWindows
|
|
18453
18903
|
})
|
|
18454
18904
|
);
|
|
18455
18905
|
}
|
|
@@ -18467,11 +18917,20 @@ function createCursorAgentCliBackend(command = "cursor-agent", defaultArgs = [])
|
|
|
18467
18917
|
// with zero output; without this watchdog only the 30-min server reaper
|
|
18468
18918
|
// would end the run.
|
|
18469
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,
|
|
18470
18927
|
augmentPrompt: (ctx) => {
|
|
18471
|
-
const
|
|
18472
|
-
|
|
18473
|
-
|
|
18474
|
-
|
|
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);
|
|
18475
18934
|
},
|
|
18476
18935
|
parseStructuredLine: parseCursorStructuredLine
|
|
18477
18936
|
}).run(context);
|
|
@@ -19231,17 +19690,7 @@ function handleOpencodeStructuredEvent(parsed, context, state) {
|
|
|
19231
19690
|
void presenter.onError(message);
|
|
19232
19691
|
state.error = message;
|
|
19233
19692
|
}
|
|
19234
|
-
|
|
19235
|
-
if (pid) {
|
|
19236
|
-
try {
|
|
19237
|
-
process.kill(-pid, "SIGTERM");
|
|
19238
|
-
} catch {
|
|
19239
|
-
try {
|
|
19240
|
-
state.process?.kill("SIGTERM");
|
|
19241
|
-
} catch {
|
|
19242
|
-
}
|
|
19243
|
-
}
|
|
19244
|
-
}
|
|
19693
|
+
killProcessTree(state.process?.pid, { signal: "SIGTERM", child: state.process });
|
|
19245
19694
|
return true;
|
|
19246
19695
|
}
|
|
19247
19696
|
default:
|
|
@@ -19435,7 +19884,7 @@ function createSupatestCliBackend(command = "supatest", defaultArgs = []) {
|
|
|
19435
19884
|
const basePromptText = resumeContextPrefix ? `${resumeContextPrefix}
|
|
19436
19885
|
|
|
19437
19886
|
${ctx.promptText}` : ctx.promptText;
|
|
19438
|
-
const promptText = ctx.config.mode === "plan" ? buildPlanModePrefix(
|
|
19887
|
+
const promptText = ctx.config.mode === "plan" ? buildPlanModePrefix(basePromptText) : basePromptText;
|
|
19439
19888
|
if (promptText.trim()) {
|
|
19440
19889
|
contentBlocks.push({ type: "text", text: promptText });
|
|
19441
19890
|
}
|
|
@@ -19620,6 +20069,8 @@ var BaseMachineAgent = class _BaseMachineAgent {
|
|
|
19620
20069
|
presenter;
|
|
19621
20070
|
runtime;
|
|
19622
20071
|
abortController = null;
|
|
20072
|
+
/** Log the "synced MCP session headers" line once per run(), not per respawn. */
|
|
20073
|
+
mcpHeadersLogged = false;
|
|
19623
20074
|
constructor(presenter, runtime) {
|
|
19624
20075
|
this.presenter = presenter;
|
|
19625
20076
|
this.runtime = runtime;
|
|
@@ -19676,7 +20127,52 @@ var BaseMachineAgent = class _BaseMachineAgent {
|
|
|
19676
20127
|
getActivityHeartbeatIntervalMs() {
|
|
19677
20128
|
return AGENT_ACTIVITY_HEARTBEAT_MS;
|
|
19678
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
|
+
}
|
|
19679
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;
|
|
19680
20176
|
const intervalMs = this.getActivityHeartbeatIntervalMs();
|
|
19681
20177
|
let heartbeat = null;
|
|
19682
20178
|
let livenessProbe = null;
|
|
@@ -19695,9 +20191,17 @@ var BaseMachineAgent = class _BaseMachineAgent {
|
|
|
19695
20191
|
}
|
|
19696
20192
|
}, intervalMs);
|
|
19697
20193
|
}
|
|
20194
|
+
const spawnAwareContext = {
|
|
20195
|
+
...context,
|
|
20196
|
+
onProcessSpawned: (child) => {
|
|
20197
|
+
releaseMcpGateOnce();
|
|
20198
|
+
callerOnProcessSpawned?.(child);
|
|
20199
|
+
}
|
|
20200
|
+
};
|
|
19698
20201
|
try {
|
|
19699
|
-
return await backend.run(
|
|
20202
|
+
return await backend.run(spawnAwareContext);
|
|
19700
20203
|
} finally {
|
|
20204
|
+
releaseMcpGateOnce();
|
|
19701
20205
|
if (heartbeat) clearInterval(heartbeat);
|
|
19702
20206
|
}
|
|
19703
20207
|
}
|
|
@@ -19791,6 +20295,8 @@ Prefer relative paths and keep your work scoped to this project.`
|
|
|
19791
20295
|
runtimeCommand || "generic-cli",
|
|
19792
20296
|
runtimeArgs
|
|
19793
20297
|
);
|
|
20298
|
+
const revalidateRuntimeCommand = this.revalidateRuntimeCommand;
|
|
20299
|
+
const revalidateCommand = revalidateRuntimeCommand ? () => revalidateRuntimeCommand.call(this) : void 0;
|
|
19794
20300
|
let lastResult = null;
|
|
19795
20301
|
for (let attempt = 0; attempt <= _BaseMachineAgent.MAX_RETRIES; attempt++) {
|
|
19796
20302
|
if (this.abortController?.signal.aborted) break;
|
|
@@ -19801,7 +20307,8 @@ Prefer relative paths and keep your work scoped to this project.`
|
|
|
19801
20307
|
cwd,
|
|
19802
20308
|
env,
|
|
19803
20309
|
promptText,
|
|
19804
|
-
onProcessSpawned: this.getOnProcessSpawned()
|
|
20310
|
+
onProcessSpawned: this.getOnProcessSpawned(),
|
|
20311
|
+
revalidateCommand
|
|
19805
20312
|
});
|
|
19806
20313
|
lastResult = {
|
|
19807
20314
|
...result,
|
|
@@ -19851,7 +20358,8 @@ ${runtimeConfig.task}` } : {}
|
|
|
19851
20358
|
cwd,
|
|
19852
20359
|
env,
|
|
19853
20360
|
promptText: fallbackContext ? this.buildPromptText(runtimeConfig) : promptText,
|
|
19854
|
-
onProcessSpawned: this.getOnProcessSpawned()
|
|
20361
|
+
onProcessSpawned: this.getOnProcessSpawned(),
|
|
20362
|
+
revalidateCommand
|
|
19855
20363
|
});
|
|
19856
20364
|
lastResult = {
|
|
19857
20365
|
...retryResult,
|
|
@@ -19927,19 +20435,7 @@ ${runtimeConfig.task}` } : {}
|
|
|
19927
20435
|
this.presenter.onComplete(result);
|
|
19928
20436
|
return result;
|
|
19929
20437
|
}
|
|
19930
|
-
|
|
19931
|
-
try {
|
|
19932
|
-
const writtenClis = syncAlanMcpSessionHeaders({
|
|
19933
|
-
conversationId: initialConfig.conversationId,
|
|
19934
|
-
teamId: initialConfig.teamId
|
|
19935
|
-
});
|
|
19936
|
-
if (writtenClis.length > 0) {
|
|
19937
|
-
void this.presenter.onLog(`Synced Alan MCP session headers to ${writtenClis.join(", ")}`);
|
|
19938
|
-
}
|
|
19939
|
-
} catch (error) {
|
|
19940
|
-
console.warn("[base-machine-agent] Failed to sync MCP session headers", error);
|
|
19941
|
-
}
|
|
19942
|
-
}
|
|
20438
|
+
this.mcpHeadersLogged = false;
|
|
19943
20439
|
try {
|
|
19944
20440
|
return await this.runCliBackend(initialConfig, safeProjectPath);
|
|
19945
20441
|
} catch (error) {
|
|
@@ -20271,38 +20767,32 @@ var CoreAgent = class _CoreAgent extends BaseMachineAgent {
|
|
|
20271
20767
|
const pid = child.pid;
|
|
20272
20768
|
if (!pid) return false;
|
|
20273
20769
|
console.info("[CoreAgent] Killing agent process", { pid });
|
|
20274
|
-
|
|
20275
|
-
|
|
20276
|
-
|
|
20277
|
-
}
|
|
20278
|
-
|
|
20279
|
-
}
|
|
20280
|
-
} catch (err) {
|
|
20281
|
-
if (err.code !== "ESRCH") {
|
|
20282
|
-
console.warn("[CoreAgent] SIGTERM failed", { pid, err });
|
|
20283
|
-
}
|
|
20284
|
-
try {
|
|
20285
|
-
child.kill("SIGTERM");
|
|
20286
|
-
} catch {
|
|
20287
|
-
}
|
|
20288
|
-
}
|
|
20770
|
+
killProcessTree(pid, {
|
|
20771
|
+
signal: "SIGTERM",
|
|
20772
|
+
child,
|
|
20773
|
+
onError: (stage, err) => console.warn(`[CoreAgent] SIGTERM failed (${stage})`, { pid, err })
|
|
20774
|
+
});
|
|
20289
20775
|
setTimeout(() => {
|
|
20290
20776
|
if (child.killed || child.exitCode !== null) return;
|
|
20291
20777
|
console.warn("[CoreAgent] Escalating to SIGKILL", { pid });
|
|
20292
|
-
|
|
20293
|
-
|
|
20294
|
-
|
|
20295
|
-
}
|
|
20296
|
-
|
|
20297
|
-
}
|
|
20298
|
-
} catch (err) {
|
|
20299
|
-
if (err.code !== "ESRCH") {
|
|
20300
|
-
console.warn("[CoreAgent] SIGKILL failed", { pid, err });
|
|
20301
|
-
}
|
|
20302
|
-
}
|
|
20778
|
+
killProcessTree(pid, {
|
|
20779
|
+
signal: "SIGKILL",
|
|
20780
|
+
child,
|
|
20781
|
+
onError: (stage, err) => console.warn(`[CoreAgent] SIGKILL failed (${stage})`, { pid, err })
|
|
20782
|
+
});
|
|
20303
20783
|
}, _CoreAgent.SIGKILL_DELAY_MS);
|
|
20304
20784
|
return true;
|
|
20305
20785
|
}
|
|
20786
|
+
/**
|
|
20787
|
+
* Re-resolve this agent's CLI executable from scratch when a spawn fails
|
|
20788
|
+
* ENOENT. The daemon caches resolved CLI paths, so a provider binary that was
|
|
20789
|
+
* moved or reinstalled mid-session would otherwise fail every turn with
|
|
20790
|
+
* "CLI command not found" until the daemon restarts. Invalidating the cache
|
|
20791
|
+
* and re-resolving once lets the current run recover against the new location.
|
|
20792
|
+
*/
|
|
20793
|
+
revalidateRuntimeCommand() {
|
|
20794
|
+
return revalidateBackendRuntimeCommand(this.runtime.backendKind) ?? null;
|
|
20795
|
+
}
|
|
20306
20796
|
// ── Interactive tool response (WS relay → stdin) ───────────────────────
|
|
20307
20797
|
/**
|
|
20308
20798
|
* Send a tool_result to the CLI's stdin (stream-json format).
|
|
@@ -20633,7 +21123,7 @@ function encryptedFileSize(entries) {
|
|
|
20633
21123
|
);
|
|
20634
21124
|
return emptyEnvelopeBytes + base64UrlLength(plaintextBytes);
|
|
20635
21125
|
}
|
|
20636
|
-
var EncryptedEventOutbox = class {
|
|
21126
|
+
var EncryptedEventOutbox = class _EncryptedEventOutbox {
|
|
20637
21127
|
constructor(path, encodedKey, pushLog = () => {
|
|
20638
21128
|
}, options = {}) {
|
|
20639
21129
|
this.path = path;
|
|
@@ -20648,6 +21138,11 @@ var EncryptedEventOutbox = class {
|
|
|
20648
21138
|
throw new Error("event outbox maxIntermediateEventsPerRun must be a non-negative integer");
|
|
20649
21139
|
}
|
|
20650
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
|
+
}
|
|
20651
21146
|
this.enforceCaps();
|
|
20652
21147
|
this.persist();
|
|
20653
21148
|
}
|
|
@@ -20655,6 +21150,19 @@ var EncryptedEventOutbox = class {
|
|
|
20655
21150
|
inFlightEventId = null;
|
|
20656
21151
|
ackTimer = null;
|
|
20657
21152
|
retryTimer = null;
|
|
21153
|
+
/** Latch so a persistent disk failure logs once per streak, not per event. */
|
|
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";
|
|
20658
21166
|
key;
|
|
20659
21167
|
maxEncryptedBytes;
|
|
20660
21168
|
maxIntermediateEventsPerRun;
|
|
@@ -20857,31 +21365,53 @@ var EncryptedEventOutbox = class {
|
|
|
20857
21365
|
throw new Error(`unable to decrypt durable event outbox: ${detail}`);
|
|
20858
21366
|
}
|
|
20859
21367
|
}
|
|
20860
|
-
|
|
21368
|
+
/** Serialize the current entries to their plaintext + content signature (once). */
|
|
21369
|
+
serializeForPersist() {
|
|
20861
21370
|
if (this.entries.length === 0) {
|
|
20862
|
-
|
|
20863
|
-
return;
|
|
21371
|
+
return { plaintext: "", signature: _EncryptedEventOutbox.EMPTY_SIGNATURE };
|
|
20864
21372
|
}
|
|
20865
|
-
|
|
20866
|
-
|
|
20867
|
-
|
|
20868
|
-
|
|
20869
|
-
|
|
20870
|
-
|
|
20871
|
-
]);
|
|
20872
|
-
const envelope = {
|
|
20873
|
-
version: OUTBOX_VERSION,
|
|
20874
|
-
iv: iv.toString("base64url"),
|
|
20875
|
-
authTag: cipher.getAuthTag().toString("base64url"),
|
|
20876
|
-
ciphertext: ciphertext.toString("base64url")
|
|
20877
|
-
};
|
|
20878
|
-
const pendingPath = `${this.path}.pending-${process.pid}-${(0, import_node_crypto.randomBytes)(6).toString("hex")}`;
|
|
21373
|
+
const plaintext = JSON.stringify({ entries: this.entries });
|
|
21374
|
+
return { plaintext, signature: (0, import_node_crypto.createHash)("sha256").update(plaintext).digest("base64url") };
|
|
21375
|
+
}
|
|
21376
|
+
persist() {
|
|
21377
|
+
const { plaintext, signature } = this.serializeForPersist();
|
|
21378
|
+
if (signature === this.lastPersistedSignature) return;
|
|
20879
21379
|
try {
|
|
20880
|
-
(
|
|
20881
|
-
|
|
20882
|
-
|
|
20883
|
-
|
|
20884
|
-
|
|
21380
|
+
if (this.entries.length === 0) {
|
|
21381
|
+
(0, import_node_fs6.rmSync)(this.path, { force: true });
|
|
21382
|
+
this.lastPersistedSignature = signature;
|
|
21383
|
+
this.persistFailureLogged = false;
|
|
21384
|
+
return;
|
|
21385
|
+
}
|
|
21386
|
+
(0, import_node_fs6.mkdirSync)((0, import_node_path5.dirname)(this.path), { recursive: true, mode: 448 });
|
|
21387
|
+
const iv = (0, import_node_crypto.randomBytes)(12);
|
|
21388
|
+
const cipher = (0, import_node_crypto.createCipheriv)("aes-256-gcm", this.key, iv);
|
|
21389
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
21390
|
+
const envelope = {
|
|
21391
|
+
version: OUTBOX_VERSION,
|
|
21392
|
+
iv: iv.toString("base64url"),
|
|
21393
|
+
authTag: cipher.getAuthTag().toString("base64url"),
|
|
21394
|
+
ciphertext: ciphertext.toString("base64url")
|
|
21395
|
+
};
|
|
21396
|
+
const pendingPath = `${this.path}.pending-${process.pid}-${(0, import_node_crypto.randomBytes)(6).toString("hex")}`;
|
|
21397
|
+
try {
|
|
21398
|
+
(0, import_node_fs6.writeFileSync)(pendingPath, JSON.stringify(envelope), { mode: 384 });
|
|
21399
|
+
(0, import_node_fs6.chmodSync)(pendingPath, 384);
|
|
21400
|
+
(0, import_node_fs6.renameSync)(pendingPath, this.path);
|
|
21401
|
+
} finally {
|
|
21402
|
+
(0, import_node_fs6.rmSync)(pendingPath, { force: true });
|
|
21403
|
+
}
|
|
21404
|
+
this.lastPersistedSignature = signature;
|
|
21405
|
+
this.persistFailureLogged = false;
|
|
21406
|
+
} catch (error) {
|
|
21407
|
+
this.lastPersistedSignature = null;
|
|
21408
|
+
if (!this.persistFailureLogged) {
|
|
21409
|
+
this.persistFailureLogged = true;
|
|
21410
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
21411
|
+
this.pushLog(
|
|
21412
|
+
`event_outbox_persist_failed error=${detail} pending_entries=${this.entries.length}`
|
|
21413
|
+
);
|
|
21414
|
+
}
|
|
20885
21415
|
}
|
|
20886
21416
|
}
|
|
20887
21417
|
};
|
|
@@ -21587,8 +22117,8 @@ var CONFIG_LOCK_STALE_MS = 3e4;
|
|
|
21587
22117
|
var CONFIG_LOCK_WAIT_MS = 2e3;
|
|
21588
22118
|
var CONFIG_LOCK_POLL_MS = 20;
|
|
21589
22119
|
function compactProviderVersion(output) {
|
|
21590
|
-
const
|
|
21591
|
-
return
|
|
22120
|
+
const firstLine2 = output.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
22121
|
+
return firstLine2?.slice(0, 120);
|
|
21592
22122
|
}
|
|
21593
22123
|
var inspectAlanMcpThroughProviderCli = (backendKind, expectedUrl, home) => {
|
|
21594
22124
|
const plan = getMcpCliInspectionPlan(backendKind);
|
|
@@ -21628,7 +22158,30 @@ var inspectAlanMcpThroughProviderCli = (backendKind, expectedUrl, home) => {
|
|
|
21628
22158
|
};
|
|
21629
22159
|
}
|
|
21630
22160
|
const providerVersion = compactProviderVersion(versionProbe.stdout);
|
|
21631
|
-
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
|
+
}
|
|
21632
22185
|
const classification = classifyMcpCliInspection(
|
|
21633
22186
|
plan,
|
|
21634
22187
|
{
|
|
@@ -22527,7 +23080,7 @@ var RunStartGate = class {
|
|
|
22527
23080
|
};
|
|
22528
23081
|
|
|
22529
23082
|
// src/version.ts
|
|
22530
|
-
var AGENT_VERSION = "0.1.
|
|
23083
|
+
var AGENT_VERSION = "0.1.43";
|
|
22531
23084
|
|
|
22532
23085
|
// src/workspace-relocation.ts
|
|
22533
23086
|
var import_node_child_process3 = require("child_process");
|
|
@@ -22603,11 +23156,19 @@ var import_node_child_process4 = require("child_process");
|
|
|
22603
23156
|
var import_node_fs11 = require("fs");
|
|
22604
23157
|
var import_node_path9 = require("path");
|
|
22605
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
|
+
);
|
|
22606
23167
|
function gitValue(cwd, args) {
|
|
22607
23168
|
const result = (0, import_node_child_process4.spawnSync)("git", ["-C", cwd, ...args], {
|
|
22608
23169
|
encoding: "utf8",
|
|
22609
23170
|
env: getDaemonCliEnvironment(),
|
|
22610
|
-
timeout:
|
|
23171
|
+
timeout: WORKSPACE_LEASE_GIT_TIMEOUT_MS
|
|
22611
23172
|
});
|
|
22612
23173
|
if (result.status !== 0) return null;
|
|
22613
23174
|
return result.stdout.trim() || null;
|
|
@@ -22643,6 +23204,13 @@ var DEFAULT_PROBE2 = {
|
|
|
22643
23204
|
} catch {
|
|
22644
23205
|
return null;
|
|
22645
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
|
+
}
|
|
22646
23214
|
}
|
|
22647
23215
|
};
|
|
22648
23216
|
function unavailableFailure(target) {
|
|
@@ -22690,6 +23258,57 @@ var WorkspaceRunLease = class _WorkspaceRunLease {
|
|
|
22690
23258
|
failure: null
|
|
22691
23259
|
};
|
|
22692
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
|
+
}
|
|
22693
23312
|
verify() {
|
|
22694
23313
|
for (const captured of this.captured) {
|
|
22695
23314
|
const current = this.probe.snapshot(captured.target.workspacePath);
|
|
@@ -22719,6 +23338,40 @@ var WorkspaceRunLease = class _WorkspaceRunLease {
|
|
|
22719
23338
|
return null;
|
|
22720
23339
|
}
|
|
22721
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
|
+
}
|
|
22722
23375
|
|
|
22723
23376
|
// src/daemon.ts
|
|
22724
23377
|
var STAGING_API_URL = process.env.ALAN_STAGING_API_URL ?? "https://staging-api.tryalan.ai";
|
|
@@ -24037,9 +24690,12 @@ function reconcileActiveRuns(input) {
|
|
|
24037
24690
|
if (!staleByGrace && !staleByDeadPid) continue;
|
|
24038
24691
|
if (awaitingAsk && staleByGrace && !staleByDeadPid) continue;
|
|
24039
24692
|
if (staleByDeadPid && awaitingAsk) {
|
|
24693
|
+
entry.presenter.onComplete(buildAbortResult("provider_exited"));
|
|
24040
24694
|
entry.agent.kill();
|
|
24041
24695
|
input.activeAgents.delete(runId);
|
|
24042
|
-
input.pushLog?.(
|
|
24696
|
+
input.pushLog?.(
|
|
24697
|
+
`reconciled dead provider pid run=${runId} (awaiting ask \u2014 finalized for resume)`
|
|
24698
|
+
);
|
|
24043
24699
|
continue;
|
|
24044
24700
|
}
|
|
24045
24701
|
abortActiveAgent(entry, { reason: staleByDeadPid ? "provider_exited" : "runner_stalled" });
|
|
@@ -24780,6 +25436,10 @@ async function startDaemon(args) {
|
|
|
24780
25436
|
const versionRefresh = setInterval(() => {
|
|
24781
25437
|
void refreshProviderVersions();
|
|
24782
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;
|
|
24783
25443
|
const stopRunForWorkspaceLease = (runId, entry, failure) => {
|
|
24784
25444
|
entry.stage = "failed";
|
|
24785
25445
|
if (!entry.presenter.failWorkspaceLease(failure)) return;
|
|
@@ -24787,15 +25447,39 @@ async function startDaemon(args) {
|
|
|
24787
25447
|
activeAgents.delete(runId);
|
|
24788
25448
|
pushLog(`workspace-lease-lost run=${runId} code=${failure.code}`);
|
|
24789
25449
|
};
|
|
24790
|
-
const
|
|
24791
|
-
|
|
24792
|
-
|
|
24793
|
-
|
|
24794
|
-
|
|
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;
|
|
24795
25478
|
};
|
|
24796
25479
|
const workspaceLeaseMonitor = setInterval(() => {
|
|
25480
|
+
const nowMs = Date.now();
|
|
24797
25481
|
for (const [runId, entry] of activeAgents) {
|
|
24798
|
-
verifyActiveWorkspaceLease(runId, entry);
|
|
25482
|
+
verifyActiveWorkspaceLease(runId, entry, nowMs);
|
|
24799
25483
|
}
|
|
24800
25484
|
}, WORKSPACE_LEASE_CHECK_INTERVAL_MS);
|
|
24801
25485
|
workspaceLeaseMonitor.unref?.();
|
|
@@ -25272,7 +25956,7 @@ async function startDaemon(args) {
|
|
|
25272
25956
|
},
|
|
25273
25957
|
() => {
|
|
25274
25958
|
const entry = activeAgents.get(payload.runId);
|
|
25275
|
-
return entry ? verifyActiveWorkspaceLease(payload.runId, entry) : false;
|
|
25959
|
+
return entry ? verifyActiveWorkspaceLease(payload.runId, entry, Date.now()) : false;
|
|
25276
25960
|
}
|
|
25277
25961
|
);
|
|
25278
25962
|
let agent;
|
|
@@ -25340,6 +26024,7 @@ async function startDaemon(args) {
|
|
|
25340
26024
|
taskMeta: payload.taskMeta,
|
|
25341
26025
|
prMeta: payload.prMeta,
|
|
25342
26026
|
conversationId: payload.conversationId,
|
|
26027
|
+
alanMcp: payload.alanMcp,
|
|
25343
26028
|
taskId: payload.taskId ?? payload.taskMeta?.id,
|
|
25344
26029
|
teamId: payload.teamId,
|
|
25345
26030
|
currentUser: payload.currentUser,
|
|
@@ -25391,7 +26076,7 @@ async function startDaemon(args) {
|
|
|
25391
26076
|
([, entry]) => entry.conversationId === payload.conversationId
|
|
25392
26077
|
) : [...activeAgents.entries()];
|
|
25393
26078
|
const delivered = entries.some(([runId, entry]) => {
|
|
25394
|
-
if (!entry || !verifyActiveWorkspaceLease(runId, entry)) return false;
|
|
26079
|
+
if (!entry || !verifyActiveWorkspaceLease(runId, entry, Date.now())) return false;
|
|
25395
26080
|
return entry.agent.sendToolResponse(payload.toolId, payload.response) === true;
|
|
25396
26081
|
});
|
|
25397
26082
|
if (!delivered) {
|
|
@@ -26158,6 +26843,18 @@ function omitUndefined(record) {
|
|
|
26158
26843
|
return Object.fromEntries(Object.entries(record).filter(([, value2]) => value2 !== void 0));
|
|
26159
26844
|
}
|
|
26160
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
|
+
|
|
26161
26858
|
// src/web-presenter.ts
|
|
26162
26859
|
var WebPresenter = class {
|
|
26163
26860
|
constructor(wsClient, _conversationId, cwd) {
|
|
@@ -26450,6 +27147,7 @@ var SandboxEventDispatcher = class {
|
|
|
26450
27147
|
fireAndForget(event);
|
|
26451
27148
|
return;
|
|
26452
27149
|
}
|
|
27150
|
+
if (isBackgroundHeartbeat(event) && !this.outboxSocket.connected) return;
|
|
26453
27151
|
this.outbox.enqueue(this.conversationId, this.currentRunId ?? this.conversationId, event);
|
|
26454
27152
|
this.outbox.flush(this.outboxSocket);
|
|
26455
27153
|
}
|
|
@@ -26569,7 +27267,7 @@ var WSClient = class {
|
|
|
26569
27267
|
if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
|
|
26570
27268
|
this.heartbeatTimer = setInterval(() => {
|
|
26571
27269
|
if (!this.socket.connected) return;
|
|
26572
|
-
this.socket.emit("agent.heartbeat", this.buildHeartbeat());
|
|
27270
|
+
this.socket.volatile.emit("agent.heartbeat", this.buildHeartbeat());
|
|
26573
27271
|
}, AGENT_HEARTBEAT_INTERVAL_MS);
|
|
26574
27272
|
this.heartbeatTimer.unref?.();
|
|
26575
27273
|
}
|
|
@@ -26652,6 +27350,7 @@ var WSClient = class {
|
|
|
26652
27350
|
images: data.images,
|
|
26653
27351
|
files: data.files,
|
|
26654
27352
|
providerSessionId: data.providerSessionId,
|
|
27353
|
+
resumeFallbackContext: data.resumeFallbackContext,
|
|
26655
27354
|
backendKind: data.backendKind,
|
|
26656
27355
|
agentId: data.agentId,
|
|
26657
27356
|
agentPrompt: data.agentPrompt,
|
|
@@ -26843,6 +27542,7 @@ async function runSandbox(config) {
|
|
|
26843
27542
|
const presenter = new WebPresenter(wsClient, config.sessionId, config.projectPath);
|
|
26844
27543
|
presenter.setSuppressSessionLifecycle(true);
|
|
26845
27544
|
let lastProviderSessionId = config.providerSessionId;
|
|
27545
|
+
let resumeFallbackContext = config.resumeFallbackContext;
|
|
26846
27546
|
let activeBackendKind = config.backendKind || "claude_cli";
|
|
26847
27547
|
let activeAgentId = config.agentId;
|
|
26848
27548
|
let activeAgentPrompt = config.agentPrompt;
|
|
@@ -26926,10 +27626,12 @@ async function runSandbox(config) {
|
|
|
26926
27626
|
selectedContextWindow: activeContextWindow ?? null,
|
|
26927
27627
|
selectedEffortLevel: activeEffortLevel ?? null,
|
|
26928
27628
|
providerSessionId: lastProviderSessionId ?? void 0,
|
|
27629
|
+
resumeFallbackContext,
|
|
26929
27630
|
taskMeta: currentTaskMeta,
|
|
26930
27631
|
prMeta: currentPrMeta,
|
|
26931
27632
|
taskId: currentTaskId,
|
|
26932
27633
|
conversationId: currentConversationId,
|
|
27634
|
+
alanMcp: currentAlanMcp,
|
|
26933
27635
|
teamId: currentTeamId,
|
|
26934
27636
|
workflowId,
|
|
26935
27637
|
workflowExecutionId,
|
|
@@ -26977,6 +27679,7 @@ async function runSandbox(config) {
|
|
|
26977
27679
|
}
|
|
26978
27680
|
}
|
|
26979
27681
|
currentAgent = null;
|
|
27682
|
+
resumeFallbackContext = void 0;
|
|
26980
27683
|
lifecycle.info("sandbox_agent_run_complete", {
|
|
26981
27684
|
success: result.success,
|
|
26982
27685
|
iterations: result.iterations,
|
|
@@ -27003,6 +27706,7 @@ async function runSandbox(config) {
|
|
|
27003
27706
|
currentFiles = nextPayload.files ?? [];
|
|
27004
27707
|
if (nextPayload.providerSessionId) {
|
|
27005
27708
|
lastProviderSessionId = nextPayload.providerSessionId;
|
|
27709
|
+
resumeFallbackContext = nextPayload.resumeFallbackContext;
|
|
27006
27710
|
}
|
|
27007
27711
|
if (nextPayload.backendKind) {
|
|
27008
27712
|
activeBackendKind = nextPayload.backendKind;
|
|
@@ -27341,6 +28045,9 @@ async function runSessionFromEnv() {
|
|
|
27341
28045
|
const contextWindow = process.env.ALAN_CONTEXT_WINDOW || void 0;
|
|
27342
28046
|
const effortLevel = process.env.ALAN_EFFORT_LEVEL || void 0;
|
|
27343
28047
|
const providerSessionId = process.env.ALAN_PROVIDER_SESSION_ID || void 0;
|
|
28048
|
+
const resumeFallbackContext = decodeResumeFallbackContext(
|
|
28049
|
+
process.env[RESUME_FALLBACK_CONTEXT_ENV]
|
|
28050
|
+
);
|
|
27344
28051
|
const workflowId = process.env.ALAN_WORKFLOW_ID || void 0;
|
|
27345
28052
|
const workflowExecutionId = process.env.ALAN_WORKFLOW_EXECUTION_ID || void 0;
|
|
27346
28053
|
const sandboxId = process.env.ALAN_SANDBOX_ID || void 0;
|
|
@@ -27382,6 +28089,7 @@ async function runSessionFromEnv() {
|
|
|
27382
28089
|
contextWindow,
|
|
27383
28090
|
effortLevel,
|
|
27384
28091
|
providerSessionId,
|
|
28092
|
+
resumeFallbackContext,
|
|
27385
28093
|
workflowId,
|
|
27386
28094
|
workflowExecutionId,
|
|
27387
28095
|
sandboxId,
|