@aiden-ade/sandbox-agent 0.1.37 → 0.1.38
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 +846 -193
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3404,7 +3404,7 @@ var require_websocket = __commonJS({
|
|
|
3404
3404
|
var http2 = require("http");
|
|
3405
3405
|
var net = require("net");
|
|
3406
3406
|
var tls = require("tls");
|
|
3407
|
-
var { randomBytes: randomBytes5, createHash:
|
|
3407
|
+
var { randomBytes: randomBytes5, createHash: createHash7 } = require("crypto");
|
|
3408
3408
|
var { Duplex, Readable } = require("stream");
|
|
3409
3409
|
var { URL: URL2 } = require("url");
|
|
3410
3410
|
var PerMessageDeflate = require_permessage_deflate();
|
|
@@ -4061,7 +4061,7 @@ var require_websocket = __commonJS({
|
|
|
4061
4061
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
4062
4062
|
return;
|
|
4063
4063
|
}
|
|
4064
|
-
const digest =
|
|
4064
|
+
const digest = createHash7("sha1").update(key + GUID).digest("base64");
|
|
4065
4065
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
4066
4066
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
4067
4067
|
return;
|
|
@@ -4428,7 +4428,7 @@ var require_websocket_server = __commonJS({
|
|
|
4428
4428
|
var EventEmitter = require("events");
|
|
4429
4429
|
var http2 = require("http");
|
|
4430
4430
|
var { Duplex } = require("stream");
|
|
4431
|
-
var { createHash:
|
|
4431
|
+
var { createHash: createHash7 } = require("crypto");
|
|
4432
4432
|
var extension = require_extension();
|
|
4433
4433
|
var PerMessageDeflate = require_permessage_deflate();
|
|
4434
4434
|
var subprotocol = require_subprotocol();
|
|
@@ -4725,7 +4725,7 @@ var require_websocket_server = __commonJS({
|
|
|
4725
4725
|
);
|
|
4726
4726
|
}
|
|
4727
4727
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
4728
|
-
const digest =
|
|
4728
|
+
const digest = createHash7("sha1").update(key + GUID).digest("base64");
|
|
4729
4729
|
const headers = [
|
|
4730
4730
|
"HTTP/1.1 101 Switching Protocols",
|
|
4731
4731
|
"Upgrade: websocket",
|
|
@@ -4867,19 +4867,18 @@ var import_node_os7 = require("os");
|
|
|
4867
4867
|
var import_node_path10 = require("path");
|
|
4868
4868
|
|
|
4869
4869
|
// ../shared/dist/constants/agent.js
|
|
4870
|
-
var INTERACTIVE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
4870
|
+
var INTERACTIVE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
4871
|
+
"askuserquestion",
|
|
4872
|
+
"askquestion",
|
|
4873
|
+
"exitplanmode",
|
|
4874
|
+
"enterplanmode"
|
|
4875
|
+
]);
|
|
4876
|
+
var HARNESS_ASK_TOOL_NAMES = /* @__PURE__ */ new Set(["askuserquestion", "askquestion"]);
|
|
4871
4877
|
function normalizeToolName(name) {
|
|
4872
4878
|
const lower = name.toLowerCase();
|
|
4873
4879
|
const mcp = lower.match(/^mcp__[a-z0-9-]+__(.+)$/);
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
for (const vendor of ["alan", "alan"]) {
|
|
4877
|
-
if (bare.startsWith(vendor) && bare.length > vendor.length) {
|
|
4878
|
-
bare = bare.slice(vendor.length);
|
|
4879
|
-
break;
|
|
4880
|
-
}
|
|
4881
|
-
}
|
|
4882
|
-
return bare;
|
|
4880
|
+
const bare = mcp ? mcp[1] : lower;
|
|
4881
|
+
return bare.replace(/[_\s-]/g, "");
|
|
4883
4882
|
}
|
|
4884
4883
|
function toolNameFromInput(input) {
|
|
4885
4884
|
if (!input || typeof input !== "object")
|
|
@@ -4899,16 +4898,13 @@ function inputLooksLikeAskUserQuestion(input) {
|
|
|
4899
4898
|
return Array.isArray(args.questions) && args.questions.length > 0;
|
|
4900
4899
|
}
|
|
4901
4900
|
function isAskUserQuestionTool(name, input) {
|
|
4902
|
-
if (normalizeToolName(name)
|
|
4901
|
+
if (HARNESS_ASK_TOOL_NAMES.has(normalizeToolName(name)))
|
|
4903
4902
|
return true;
|
|
4904
4903
|
const nested = toolNameFromInput(input);
|
|
4905
|
-
if (nested && nested !== name && normalizeToolName(nested)
|
|
4904
|
+
if (nested && nested !== name && HARNESS_ASK_TOOL_NAMES.has(normalizeToolName(nested))) {
|
|
4906
4905
|
return true;
|
|
4907
4906
|
}
|
|
4908
|
-
|
|
4909
|
-
return true;
|
|
4910
|
-
}
|
|
4911
|
-
return false;
|
|
4907
|
+
return inputLooksLikeAskUserQuestion(input);
|
|
4912
4908
|
}
|
|
4913
4909
|
function isInteractiveToolName(name, input) {
|
|
4914
4910
|
const normalized = normalizeToolName(name);
|
|
@@ -9574,6 +9570,11 @@ var RUNNER_STATUS_PROTOCOL_VERSION = 2;
|
|
|
9574
9570
|
var RUNNER_STATUS_MIN_SUPPORTED_PROTOCOL_VERSION = Math.max(1, RUNNER_STATUS_PROTOCOL_VERSION - 1);
|
|
9575
9571
|
var RUNNER_STATUS_MAX_SUPPORTED_PROTOCOL_VERSION = RUNNER_STATUS_PROTOCOL_VERSION;
|
|
9576
9572
|
var RUNNER_RECOVERY_GRACE_MS = 2 * 60 * 1e3;
|
|
9573
|
+
var SYSTEM_ABORT_RUN_ERRORS = {
|
|
9574
|
+
watchdog_timeout: "Agent run was stopped by the server after a period of no stream activity",
|
|
9575
|
+
provider_exited: "Agent process exited unexpectedly",
|
|
9576
|
+
runner_stalled: "Agent run stalled with no recent activity and was recovered"
|
|
9577
|
+
};
|
|
9577
9578
|
function resolveAgentConfigFileName(profile) {
|
|
9578
9579
|
switch (profile) {
|
|
9579
9580
|
case "production":
|
|
@@ -14510,33 +14511,6 @@ Deliverables persist as Alan artifacts and documents \u2014 not as chat. Plans,
|
|
|
14510
14511
|
For non-trivial tasks, log high-signal events as they happen: decisions, changed assumptions, validation results, known-good IDs, blockers, risks, and direction changes. Keep one current task-state summary updated with current state, known-good IDs, open risks, and next action. Do not log routine commands or file reads. Before stopping, ensure the timeline and current state are enough for a human to understand progress and for another agent to resume. If a prior decision is stale, write a newer event that supersedes it.
|
|
14511
14512
|
|
|
14512
14513
|
The specific work you do \u2014 implementation, planning, review, coordination, verification \u2014 is determined by your agent role, described next.`;
|
|
14513
|
-
var ALAN_CURSOR_ASK_USER_OVERLAY = `### Cursor runtime note
|
|
14514
|
-
Cursor native \`AskQuestion\` does not block stdin for Alan yet. When you need clickable question cards on Cursor, use \`mcp__alan__ask_user_question\` (server tool: \`ask_user_question\`) instead of native AskQuestion.`;
|
|
14515
|
-
var ALAN_ASK_USER_PROMPT = `## Asking the user
|
|
14516
|
-
|
|
14517
|
-
When you need a decision or information only the user can provide, do not guess \u2014 ask with an interactive ask tool so Alan can render clickable question cards.
|
|
14518
|
-
|
|
14519
|
-
### Tool preference (required)
|
|
14520
|
-
1. Prefer the **native** ask tool when it appears in your tool list (\`AskUserQuestion\`, or the backend's equivalent built-in ask tool).
|
|
14521
|
-
2. Only if native ask is missing, call \`mcp__alan__ask_user_question\` (server tool name: \`ask_user_question\`).
|
|
14522
|
-
|
|
14523
|
-
Required arguments (both native and MCP forms):
|
|
14524
|
-
- \`questions\`: non-empty array. Each item needs a clear \`question\` string.
|
|
14525
|
-
- Prefer 2\u20135 selectable \`options\` per question (\`label\` required; optional \`value\` / \`description\`). Free-text-only questions are allowed when options do not fit.
|
|
14526
|
-
- Do NOT include an "Other" option \u2014 the UI always adds a freeform Other\u2026 field.
|
|
14527
|
-
- Set \`multiSelect: true\` on a question when the user may pick more than one option; omit or set false for single-select (default).
|
|
14528
|
-
- Optional \`header\` per question and optional \`title\` for the card header.
|
|
14529
|
-
|
|
14530
|
-
If neither native ask nor the MCP ask tool is available, ask ONE short plain-text question \u2014 do not invent cards or pretend the UI exists.
|
|
14531
|
-
|
|
14532
|
-
### Never do this
|
|
14533
|
-
- Do NOT paste multiple-choice lists, A/B/C tables, or "reply with 1A / 1B" in chat.
|
|
14534
|
-
- Do NOT say "answer in the UI", "see the cards", "questions are above", or similar unless you just successfully called an ask tool with real \`questions\` in this turn.
|
|
14535
|
-
- Do NOT call the tool with empty \`questions\`, placeholder text, or missing options when choices exist.
|
|
14536
|
-
- Do NOT invent defaults when the user skips. If the next user message says they skipped, continue without those answers (or re-ask only if truly required).
|
|
14537
|
-
|
|
14538
|
-
### After the tool call
|
|
14539
|
-
The ask tool returns an empty/ack result and does NOT wait. Immediately end your turn. At most one short line like "Please pick an option above." \u2014 no restating the full questions in markdown. The user's answer (or skip) arrives as the next user message; continue from there.`;
|
|
14540
14514
|
function buildAlanTeamScopePrompt(teamId) {
|
|
14541
14515
|
return [
|
|
14542
14516
|
`You are operating within team \`${teamId}\`.`,
|
|
@@ -15228,20 +15202,23 @@ var import_path3 = require("path");
|
|
|
15228
15202
|
var import_readline = require("readline");
|
|
15229
15203
|
var import_child_process2 = require("child_process");
|
|
15230
15204
|
var import_crypto2 = require("crypto");
|
|
15231
|
-
var import_child_process3 = require("child_process");
|
|
15232
|
-
var import_util10 = require("util");
|
|
15233
15205
|
var import_fs4 = require("fs");
|
|
15234
15206
|
var import_os4 = require("os");
|
|
15235
15207
|
var import_path4 = require("path");
|
|
15208
|
+
var import_child_process3 = require("child_process");
|
|
15209
|
+
var import_util10 = require("util");
|
|
15236
15210
|
var import_fs5 = require("fs");
|
|
15237
15211
|
var import_os5 = require("os");
|
|
15238
15212
|
var import_path5 = require("path");
|
|
15239
15213
|
var import_fs6 = require("fs");
|
|
15240
15214
|
var import_os6 = require("os");
|
|
15241
15215
|
var import_path6 = require("path");
|
|
15242
|
-
var import_crypto3 = require("crypto");
|
|
15243
15216
|
var import_fs7 = require("fs");
|
|
15217
|
+
var import_os7 = require("os");
|
|
15244
15218
|
var import_path7 = require("path");
|
|
15219
|
+
var import_crypto3 = require("crypto");
|
|
15220
|
+
var import_fs8 = require("fs");
|
|
15221
|
+
var import_path8 = require("path");
|
|
15245
15222
|
var MANAGED_ALAN_MCP_SERVER_NAMES = [
|
|
15246
15223
|
"alan",
|
|
15247
15224
|
"alan-prod",
|
|
@@ -15736,6 +15713,10 @@ function getClaudePermissionMode(config) {
|
|
|
15736
15713
|
function getClaudeDisallowedTools(config) {
|
|
15737
15714
|
return isReadOnlyAgent(config.agentId) ? ["Edit", "Write", "MultiEdit", "NotebookEdit"] : [];
|
|
15738
15715
|
}
|
|
15716
|
+
function getCodexPermissionArgs(config) {
|
|
15717
|
+
const sandbox = shouldUseReadOnlyRuntimePermissions(config) ? "read-only" : "danger-full-access";
|
|
15718
|
+
return ["--ask-for-approval", "never", "--sandbox", sandbox];
|
|
15719
|
+
}
|
|
15739
15720
|
function isLikelyProviderAuthError(stderr) {
|
|
15740
15721
|
const lower = stderr.toLowerCase();
|
|
15741
15722
|
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");
|
|
@@ -15832,6 +15813,16 @@ function spawnCli(command, args, context) {
|
|
|
15832
15813
|
detached: process.platform !== "win32"
|
|
15833
15814
|
});
|
|
15834
15815
|
}
|
|
15816
|
+
function emitSessionNotice(presenter, kind, message) {
|
|
15817
|
+
if (presenter.onNotice) {
|
|
15818
|
+
void presenter.onNotice(kind, message);
|
|
15819
|
+
return;
|
|
15820
|
+
}
|
|
15821
|
+
presenter.onLog(`[session_notice:${kind}] ${message}`);
|
|
15822
|
+
}
|
|
15823
|
+
function logResumeObservability(event, fields) {
|
|
15824
|
+
console.info(`[agent-resume] ${event}`, { event, ...fields });
|
|
15825
|
+
}
|
|
15835
15826
|
function structuredEventKey(event) {
|
|
15836
15827
|
const type = typeof event.type === "string" && event.type ? event.type : "";
|
|
15837
15828
|
const role = typeof event.role === "string" && event.role ? event.role : "";
|
|
@@ -15901,7 +15892,12 @@ function createGenericCliBackend(options) {
|
|
|
15901
15892
|
const args = options.buildArgs?.(context, prompt) ?? options.args.map((arg) => arg === "{{prompt}}" ? prompt : arg);
|
|
15902
15893
|
const child = spawnCli(options.command, args, context);
|
|
15903
15894
|
state.process = child;
|
|
15895
|
+
state.lastRawOutputAtMs = Date.now();
|
|
15904
15896
|
context.onProcessSpawned?.(child);
|
|
15897
|
+
context.registerLivenessProbe?.(() => ({
|
|
15898
|
+
providerAlive: child.exitCode === null,
|
|
15899
|
+
lastRawOutputAgoMs: typeof state.lastRawOutputAtMs === "number" ? Date.now() - state.lastRawOutputAtMs : null
|
|
15900
|
+
}));
|
|
15905
15901
|
const safeArgs = args.map(
|
|
15906
15902
|
(a, i) => i > 0 && args[i - 1] === "--system-prompt" ? `"<system-prompt ${a.length} chars>"` : a
|
|
15907
15903
|
);
|
|
@@ -15950,6 +15946,7 @@ function createGenericCliBackend(options) {
|
|
|
15950
15946
|
}
|
|
15951
15947
|
stdoutRl.on("line", (line) => {
|
|
15952
15948
|
sawStdoutLine = true;
|
|
15949
|
+
state.lastRawOutputAtMs = Date.now();
|
|
15953
15950
|
presenter.recordRawTranscript?.("stdout", line);
|
|
15954
15951
|
if (options.parseStructuredLine) {
|
|
15955
15952
|
options.parseStructuredLine(line, context, state);
|
|
@@ -15967,6 +15964,7 @@ function createGenericCliBackend(options) {
|
|
|
15967
15964
|
}
|
|
15968
15965
|
});
|
|
15969
15966
|
stderrRl.on("line", (line) => {
|
|
15967
|
+
state.lastRawOutputAtMs = Date.now();
|
|
15970
15968
|
presenter.recordRawTranscript?.("stderr", line);
|
|
15971
15969
|
if (options.parseStderrLine) {
|
|
15972
15970
|
options.parseStderrLine(line, context, state);
|
|
@@ -16013,7 +16011,7 @@ function createGenericCliBackend(options) {
|
|
|
16013
16011
|
if (presenter.pendingAskUserToolIds?.size) {
|
|
16014
16012
|
idleTimeoutReason = "pending_user_answer";
|
|
16015
16013
|
resolve22("idle_timeout");
|
|
16016
|
-
} else if (state.activeBackgroundTaskIds?.size) {
|
|
16014
|
+
} else if (state.resultDeferredOnBackgroundWork && state.activeBackgroundTaskIds?.size) {
|
|
16017
16015
|
idleTimeoutReason = "background_task";
|
|
16018
16016
|
resolve22("idle_timeout");
|
|
16019
16017
|
} else {
|
|
@@ -16076,10 +16074,26 @@ function createGenericCliBackend(options) {
|
|
|
16076
16074
|
logUnhandledStructuredEventSummary(options.kind, state);
|
|
16077
16075
|
}
|
|
16078
16076
|
const requestedResumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
|
|
16077
|
+
let cursorResumeSilentlyFailed = false;
|
|
16079
16078
|
if (requestedResumeId && state.runtimeSessionId && state.runtimeSessionId !== requestedResumeId) {
|
|
16080
16079
|
console.warn(
|
|
16081
16080
|
`[${options.kind}] CLI reported session id ${state.runtimeSessionId} after resume was requested with ${requestedResumeId} \u2014 expected for claude session forks, a failed-resume signal for cursor-agent`
|
|
16082
16081
|
);
|
|
16082
|
+
if (options.kind === "cursor_agent_cli") {
|
|
16083
|
+
cursorResumeSilentlyFailed = true;
|
|
16084
|
+
logResumeObservability("agent_resume_failed", {
|
|
16085
|
+
backendKind: options.kind,
|
|
16086
|
+
reason: "cli_forked_fresh_session",
|
|
16087
|
+
requestedResumeId,
|
|
16088
|
+
runtimeSessionId: state.runtimeSessionId,
|
|
16089
|
+
hadFallbackContext: Boolean(context.config.resumeFallbackContext?.trim())
|
|
16090
|
+
});
|
|
16091
|
+
emitSessionNotice(
|
|
16092
|
+
context.presenter,
|
|
16093
|
+
"session_resume_failed",
|
|
16094
|
+
"The previous session could not be resumed, so this reply was generated without earlier conversation context. Retrying with the recent history re-included."
|
|
16095
|
+
);
|
|
16096
|
+
}
|
|
16083
16097
|
}
|
|
16084
16098
|
if (context.abortController.signal.aborted) {
|
|
16085
16099
|
return {
|
|
@@ -16173,6 +16187,22 @@ function createGenericCliBackend(options) {
|
|
|
16173
16187
|
const stderrText = stderrLines.join("\n").trim();
|
|
16174
16188
|
const hasStructuredError = exitCode === 0 && !!state.error?.trim();
|
|
16175
16189
|
const failed = exitCode !== 0 || hasStructuredError;
|
|
16190
|
+
if (cursorResumeSilentlyFailed && !failed) {
|
|
16191
|
+
return {
|
|
16192
|
+
success: false,
|
|
16193
|
+
summary: state.summary.trim() || "Session resume failed",
|
|
16194
|
+
filesModified: [],
|
|
16195
|
+
planFilesCreated: [],
|
|
16196
|
+
iterations: Math.max(state.iterations, 1),
|
|
16197
|
+
error: SESSION_RESUME_FAILED_MESSAGE,
|
|
16198
|
+
errorKind: "resume_failed",
|
|
16199
|
+
providerSessionId: state.runtimeSessionId,
|
|
16200
|
+
runtimeSessionId: state.runtimeSessionId,
|
|
16201
|
+
backendKind: options.kind,
|
|
16202
|
+
supportTier: options.supportTier,
|
|
16203
|
+
usage: state.usage
|
|
16204
|
+
};
|
|
16205
|
+
}
|
|
16176
16206
|
const summary = state.summary.trim() || state.error?.trim() || (failed ? "Task failed" : "Task completed");
|
|
16177
16207
|
return {
|
|
16178
16208
|
success: !failed,
|
|
@@ -16622,6 +16652,21 @@ function createAntigravityCliBackend(command = "agy", defaultArgs = []) {
|
|
|
16622
16652
|
}
|
|
16623
16653
|
};
|
|
16624
16654
|
}
|
|
16655
|
+
function resolveClaudeHome(env, variant = "claude") {
|
|
16656
|
+
if (variant === "supatest") {
|
|
16657
|
+
return (0, import_path4.join)((0, import_os4.homedir)(), ".supatest", "claude-internal");
|
|
16658
|
+
}
|
|
16659
|
+
return env.CLAUDE_CONFIG_DIR || process.env.CLAUDE_CONFIG_DIR || (0, import_path4.join)((0, import_os4.homedir)(), ".claude");
|
|
16660
|
+
}
|
|
16661
|
+
function encodeClaudeProjectDir(cwd) {
|
|
16662
|
+
return cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
16663
|
+
}
|
|
16664
|
+
function claudeSessionLogExists(input) {
|
|
16665
|
+
if (!input.sessionId || !input.cwd) return false;
|
|
16666
|
+
const projectDir = (0, import_path4.join)(input.home, "projects", encodeClaudeProjectDir(input.cwd));
|
|
16667
|
+
if (!(0, import_fs4.existsSync)(projectDir)) return false;
|
|
16668
|
+
return (0, import_fs4.existsSync)((0, import_path4.join)(projectDir, `${input.sessionId}.jsonl`));
|
|
16669
|
+
}
|
|
16625
16670
|
var execFileAsync = (0, import_util10.promisify)(import_child_process3.execFile);
|
|
16626
16671
|
var flagSupportCache = /* @__PURE__ */ new Map();
|
|
16627
16672
|
function helpAdvertisesFlag(helpText, flag) {
|
|
@@ -16672,15 +16717,38 @@ function registerBackgroundTaskIds(state, keys) {
|
|
|
16672
16717
|
state.activeBackgroundTaskIds.add(key);
|
|
16673
16718
|
}
|
|
16674
16719
|
}
|
|
16675
|
-
function
|
|
16720
|
+
function registerBackgroundLauncher(state, toolUseId) {
|
|
16721
|
+
registerBackgroundTaskIds(state, [toolUseId]);
|
|
16722
|
+
if (!state.pendingLauncherToolIds) state.pendingLauncherToolIds = [];
|
|
16723
|
+
if (!state.pendingLauncherToolIds.includes(toolUseId)) {
|
|
16724
|
+
state.pendingLauncherToolIds.push(toolUseId);
|
|
16725
|
+
}
|
|
16726
|
+
}
|
|
16727
|
+
function drainNextUnlinkedBackgroundLauncher(state) {
|
|
16728
|
+
const launcherId = state.pendingLauncherToolIds?.shift();
|
|
16729
|
+
if (state.pendingLauncherToolIds?.length === 0) state.pendingLauncherToolIds = void 0;
|
|
16730
|
+
if (launcherId) clearBackgroundTaskIds(state, [launcherId]);
|
|
16731
|
+
return launcherId;
|
|
16732
|
+
}
|
|
16733
|
+
function registerBackgroundTaskRecord(state, record, launcherToolUseId) {
|
|
16676
16734
|
const taskId = backgroundTaskId(record);
|
|
16677
|
-
let toolUseId = backgroundToolUseId(record);
|
|
16678
|
-
if (!toolUseId && taskId
|
|
16679
|
-
const
|
|
16680
|
-
if (
|
|
16735
|
+
let toolUseId = backgroundToolUseId(record) ?? launcherToolUseId ?? void 0;
|
|
16736
|
+
if (!toolUseId && taskId) {
|
|
16737
|
+
const nextLauncher = state.pendingLauncherToolIds?.[0];
|
|
16738
|
+
if (nextLauncher) {
|
|
16739
|
+
toolUseId = nextLauncher;
|
|
16740
|
+
} else if (state.activeBackgroundTaskIds?.size === 1) {
|
|
16741
|
+
const [candidate] = state.activeBackgroundTaskIds;
|
|
16742
|
+
if (candidate && candidate !== taskId) toolUseId = candidate;
|
|
16743
|
+
}
|
|
16681
16744
|
}
|
|
16682
16745
|
registerBackgroundTaskIds(state, backgroundTaskKeys(record));
|
|
16683
16746
|
if (!taskId || !toolUseId) return;
|
|
16747
|
+
if (state.pendingLauncherToolIds) {
|
|
16748
|
+
const idx = state.pendingLauncherToolIds.indexOf(toolUseId);
|
|
16749
|
+
if (idx >= 0) state.pendingLauncherToolIds.splice(idx, 1);
|
|
16750
|
+
if (state.pendingLauncherToolIds.length === 0) state.pendingLauncherToolIds = void 0;
|
|
16751
|
+
}
|
|
16684
16752
|
if (!state.backgroundToolIdByTaskId) state.backgroundToolIdByTaskId = /* @__PURE__ */ new Map();
|
|
16685
16753
|
if (!state.backgroundTaskIdsByToolId) state.backgroundTaskIdsByToolId = /* @__PURE__ */ new Map();
|
|
16686
16754
|
state.backgroundToolIdByTaskId.set(taskId, toolUseId);
|
|
@@ -16711,6 +16779,10 @@ function clearBackgroundTaskIds(state, keys) {
|
|
|
16711
16779
|
state.backgroundTaskIdsByToolId?.delete(key);
|
|
16712
16780
|
}
|
|
16713
16781
|
}
|
|
16782
|
+
if (state.pendingLauncherToolIds) {
|
|
16783
|
+
state.pendingLauncherToolIds = state.pendingLauncherToolIds.filter((id) => activeIds.has(id));
|
|
16784
|
+
if (state.pendingLauncherToolIds.length === 0) state.pendingLauncherToolIds = void 0;
|
|
16785
|
+
}
|
|
16714
16786
|
if (activeIds.size === 0) state.activeBackgroundTaskIds = void 0;
|
|
16715
16787
|
}
|
|
16716
16788
|
function resolveBackgroundTaskToolUseId(state, record) {
|
|
@@ -16733,6 +16805,7 @@ function deferOrFinalizeStructuredResult(kind, context, state) {
|
|
|
16733
16805
|
console.info(
|
|
16734
16806
|
`[${kind}] Keeping stdin open \u2014 ${pendingAsks} interactive question(s) awaiting user response`
|
|
16735
16807
|
);
|
|
16808
|
+
state.resultDeferredOnPendingAnswer = true;
|
|
16736
16809
|
return "deferred_pending_answer";
|
|
16737
16810
|
}
|
|
16738
16811
|
const activeBackgroundTasks = state.activeBackgroundTaskIds?.size ?? 0;
|
|
@@ -16747,9 +16820,10 @@ function deferOrFinalizeStructuredResult(kind, context, state) {
|
|
|
16747
16820
|
return "finalized";
|
|
16748
16821
|
}
|
|
16749
16822
|
function maybeFinalizeDeferredResult(kind, context, state) {
|
|
16750
|
-
if (!state.resultDeferredOnBackgroundWork) return;
|
|
16823
|
+
if (!state.resultDeferredOnBackgroundWork && !state.resultDeferredOnPendingAnswer) return;
|
|
16751
16824
|
if (context.presenter.pendingAskUserToolIds?.size || state.activeBackgroundTaskIds?.size) return;
|
|
16752
16825
|
state.resultDeferredOnBackgroundWork = false;
|
|
16826
|
+
state.resultDeferredOnPendingAnswer = false;
|
|
16753
16827
|
finalizeStructuredResult(kind, state);
|
|
16754
16828
|
}
|
|
16755
16829
|
var DEBUG_ASK = process.env.ALAN_DEBUG_ASK === "1" || process.env.ALAN_DEBUG === "1";
|
|
@@ -16819,21 +16893,44 @@ function handleClaudeStructuredEvent(parsed, context, state) {
|
|
|
16819
16893
|
return true;
|
|
16820
16894
|
}
|
|
16821
16895
|
if (parsed.subtype === "task_started") {
|
|
16822
|
-
registerBackgroundTaskRecord(state, parsed);
|
|
16896
|
+
registerBackgroundTaskRecord(state, parsed, parentToolUseId);
|
|
16897
|
+
return true;
|
|
16898
|
+
}
|
|
16899
|
+
if (parsed.subtype === "compact_boundary") {
|
|
16900
|
+
const compactMetadata = typeof parsed.compact_metadata === "object" && parsed.compact_metadata !== null ? parsed.compact_metadata : {};
|
|
16901
|
+
const trigger = typeof compactMetadata.trigger === "string" ? compactMetadata.trigger : typeof parsed.trigger === "string" ? parsed.trigger : "auto";
|
|
16902
|
+
const preTokens = typeof compactMetadata.pre_tokens === "number" ? compactMetadata.pre_tokens : typeof parsed.pre_tokens === "number" ? parsed.pre_tokens : void 0;
|
|
16903
|
+
const tokensPart = typeof preTokens === "number" && preTokens > 0 ? ` (${Math.round(preTokens / 1e3)}k tokens summarized)` : "";
|
|
16904
|
+
emitSessionNotice(
|
|
16905
|
+
presenter,
|
|
16906
|
+
"context_compacted",
|
|
16907
|
+
trigger === "manual" ? `Conversation context was compacted${tokensPart} \u2014 earlier detail was replaced with a summary.` : `Conversation context was automatically compacted${tokensPart} to stay within the model's limit \u2014 earlier detail was replaced with a summary.`
|
|
16908
|
+
);
|
|
16823
16909
|
return true;
|
|
16824
16910
|
}
|
|
16825
16911
|
if (parsed.subtype === "task_notification") {
|
|
16826
16912
|
if (isTerminalTaskNotification(parsed)) {
|
|
16827
16913
|
const toolUseId = resolveBackgroundTaskToolUseId(state, parsed);
|
|
16828
16914
|
const wasActive = !!toolUseId && state.activeBackgroundTaskIds?.has(toolUseId) ? true : backgroundTaskKeys(parsed).some((key) => state.activeBackgroundTaskIds?.has(key));
|
|
16915
|
+
const isFailed = isFailedTaskNotification(parsed) ? true : void 0;
|
|
16829
16916
|
if (toolUseId && wasActive) {
|
|
16830
16917
|
void presenter.onToolResult?.(
|
|
16831
16918
|
toolUseId,
|
|
16832
16919
|
formatClaudeTaskNotificationResult(parsed),
|
|
16833
|
-
|
|
16920
|
+
isFailed
|
|
16834
16921
|
);
|
|
16922
|
+
clearBackgroundTaskIds(state, backgroundTaskKeys(parsed));
|
|
16923
|
+
} else {
|
|
16924
|
+
clearBackgroundTaskIds(state, backgroundTaskKeys(parsed));
|
|
16925
|
+
const drainedLauncher = drainNextUnlinkedBackgroundLauncher(state);
|
|
16926
|
+
if (drainedLauncher) {
|
|
16927
|
+
void presenter.onToolResult?.(
|
|
16928
|
+
drainedLauncher,
|
|
16929
|
+
formatClaudeTaskNotificationResult(parsed),
|
|
16930
|
+
isFailed
|
|
16931
|
+
);
|
|
16932
|
+
}
|
|
16835
16933
|
}
|
|
16836
|
-
clearBackgroundTaskIds(state, backgroundTaskKeys(parsed));
|
|
16837
16934
|
maybeFinalizeDeferredResult("claude_cli", context, state);
|
|
16838
16935
|
}
|
|
16839
16936
|
return true;
|
|
@@ -16841,9 +16938,10 @@ function handleClaudeStructuredEvent(parsed, context, state) {
|
|
|
16841
16938
|
return false;
|
|
16842
16939
|
}
|
|
16843
16940
|
case "assistant": {
|
|
16844
|
-
if (
|
|
16941
|
+
if (state.awaitingAskDenialFallback) {
|
|
16942
|
+
state.awaitingAskDenialFallback = false;
|
|
16845
16943
|
console.info(
|
|
16846
|
-
|
|
16944
|
+
"[claude_cli] Suppressing auto-denial fallback assistant turn after native AskUserQuestion"
|
|
16847
16945
|
);
|
|
16848
16946
|
return true;
|
|
16849
16947
|
}
|
|
@@ -16872,7 +16970,7 @@ function handleClaudeStructuredEvent(parsed, context, state) {
|
|
|
16872
16970
|
parentToolUseId
|
|
16873
16971
|
);
|
|
16874
16972
|
if (CLAUDE_SUBAGENT_TOOL_NAMES.has(toolBlock.name.toLowerCase())) {
|
|
16875
|
-
|
|
16973
|
+
registerBackgroundLauncher(state, toolBlock.id);
|
|
16876
16974
|
}
|
|
16877
16975
|
const normalizedName = normalizeToolName(toolBlock.name);
|
|
16878
16976
|
if (isInteractiveToolName(toolBlock.name) || INTERACTIVE_TOOL_NAMES.has(normalizedName)) {
|
|
@@ -16883,6 +16981,10 @@ function handleClaudeStructuredEvent(parsed, context, state) {
|
|
|
16883
16981
|
if (isAskUserQuestionTool(toolBlock.name)) {
|
|
16884
16982
|
if (!presenter.pendingAskUserToolIds) presenter.pendingAskUserToolIds = /* @__PURE__ */ new Set();
|
|
16885
16983
|
presenter.pendingAskUserToolIds.add(toolBlock.id);
|
|
16984
|
+
if (!toolBlock.name.toLowerCase().startsWith("mcp__")) {
|
|
16985
|
+
if (!state.nativeAskToolIds) state.nativeAskToolIds = /* @__PURE__ */ new Set();
|
|
16986
|
+
state.nativeAskToolIds.add(toolBlock.id);
|
|
16987
|
+
}
|
|
16886
16988
|
debugAskLog(
|
|
16887
16989
|
`[claude_cli] AskUserQuestion tool_use detected \u2014 id=${toolBlock.id}, name=${toolBlock.name}, pendingSize=${presenter.pendingAskUserToolIds.size}, presenterHasProp=${Object.hasOwn(presenter, "pendingAskUserToolIds")}`
|
|
16888
16990
|
);
|
|
@@ -16907,6 +17009,22 @@ function handleClaudeStructuredEvent(parsed, context, state) {
|
|
|
16907
17009
|
for (const block of content) {
|
|
16908
17010
|
if (typeof block === "object" && block !== null && "type" in block && block.type === "tool_result" && "tool_use_id" in block && typeof block.tool_use_id === "string") {
|
|
16909
17011
|
if (state.suppressedToolResultIds?.has(block.tool_use_id)) {
|
|
17012
|
+
const isSuppressedError = "is_error" in block && block.is_error === true;
|
|
17013
|
+
const isNativeAsk = state.nativeAskToolIds?.has(block.tool_use_id) === true;
|
|
17014
|
+
if (isSuppressedError && !isNativeAsk && presenter.pendingAskUserToolIds?.has(block.tool_use_id)) {
|
|
17015
|
+
presenter.pendingAskUserToolIds.delete(block.tool_use_id);
|
|
17016
|
+
state.suppressedToolResultIds.delete(block.tool_use_id);
|
|
17017
|
+
const resultText2 = extractToolResultContent(block.content);
|
|
17018
|
+
console.info(
|
|
17019
|
+
`[claude_cli] Ask tool ${block.tool_use_id} returned an error \u2014 clearing pending ask so the run can finish`
|
|
17020
|
+
);
|
|
17021
|
+
void presenter.onToolResult?.(block.tool_use_id, resultText2, true, parentToolUseId);
|
|
17022
|
+
maybeFinalizeDeferredResult("claude_cli", context, state);
|
|
17023
|
+
continue;
|
|
17024
|
+
}
|
|
17025
|
+
if (isNativeAsk) {
|
|
17026
|
+
state.awaitingAskDenialFallback = true;
|
|
17027
|
+
}
|
|
16910
17028
|
console.info(
|
|
16911
17029
|
`[claude_cli] Suppressed auto-generated tool_result for ${block.tool_use_id}`
|
|
16912
17030
|
);
|
|
@@ -17014,7 +17132,31 @@ function createClaudeCliBackend(command = "claude", defaultArgs = []) {
|
|
|
17014
17132
|
if (disallowedTools.length > 0) {
|
|
17015
17133
|
args.push("--disallowedTools", disallowedTools.join(","));
|
|
17016
17134
|
}
|
|
17017
|
-
const
|
|
17135
|
+
const requestedResumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
|
|
17136
|
+
let resumeId = requestedResumeId;
|
|
17137
|
+
let resumeContextPrefix;
|
|
17138
|
+
if (requestedResumeId && !claudeSessionLogExists({
|
|
17139
|
+
sessionId: requestedResumeId,
|
|
17140
|
+
cwd: context.cwd,
|
|
17141
|
+
home: resolveClaudeHome(context.env)
|
|
17142
|
+
})) {
|
|
17143
|
+
resumeId = void 0;
|
|
17144
|
+
resumeContextPrefix = context.config.resumeFallbackContext?.trim() || void 0;
|
|
17145
|
+
console.warn(
|
|
17146
|
+
`[claude_cli] No session log for ${requestedResumeId} under cwd ${context.cwd}; starting a fresh session instead of resuming`
|
|
17147
|
+
);
|
|
17148
|
+
logResumeObservability("agent_resume_fallback_used", {
|
|
17149
|
+
backendKind: "claude_cli",
|
|
17150
|
+
reason: "session_log_missing_for_cwd",
|
|
17151
|
+
requestedResumeId,
|
|
17152
|
+
hadFallbackContext: Boolean(resumeContextPrefix)
|
|
17153
|
+
});
|
|
17154
|
+
emitSessionNotice(
|
|
17155
|
+
context.presenter,
|
|
17156
|
+
"session_resume_failed",
|
|
17157
|
+
resumeContextPrefix ? "The previous Claude session was created in a different working directory and could not be resumed here, so a fresh session was started with the recent conversation history re-included." : "The previous Claude session was created in a different working directory and could not be resumed here, so a fresh session was started. Earlier conversation context may be missing from this reply."
|
|
17158
|
+
);
|
|
17159
|
+
}
|
|
17018
17160
|
if (!resumeId && context.config.systemPrompt?.trim()) {
|
|
17019
17161
|
args.push("--system-prompt", context.config.systemPrompt.trim());
|
|
17020
17162
|
} else if (resumeId && context.config.systemPromptAppend?.trim()) {
|
|
@@ -17034,6 +17176,10 @@ function createClaudeCliBackend(command = "claude", defaultArgs = []) {
|
|
|
17034
17176
|
if (resumeId) {
|
|
17035
17177
|
args.push("--resume", resumeId);
|
|
17036
17178
|
console.info("[claude_cli] Resuming session", { resumeId });
|
|
17179
|
+
logResumeObservability("agent_resume_requested", {
|
|
17180
|
+
backendKind: "claude_cli",
|
|
17181
|
+
requestedResumeId: resumeId
|
|
17182
|
+
});
|
|
17037
17183
|
}
|
|
17038
17184
|
args.push("--chrome");
|
|
17039
17185
|
console.info("[claude_cli] Chrome flag added \u2014 final args:", args.join(" "));
|
|
@@ -17054,7 +17200,10 @@ function createClaudeCliBackend(command = "claude", defaultArgs = []) {
|
|
|
17054
17200
|
});
|
|
17055
17201
|
}
|
|
17056
17202
|
}
|
|
17057
|
-
const
|
|
17203
|
+
const basePromptText = resumeContextPrefix ? `${resumeContextPrefix}
|
|
17204
|
+
|
|
17205
|
+
${ctx.promptText}` : ctx.promptText;
|
|
17206
|
+
const promptText = ctx.config.mode === "plan" ? buildPlanModePrefix(buildPromptWithSystem(ctx.config, basePromptText)) : buildPromptWithSystem(ctx.config, basePromptText);
|
|
17058
17207
|
if (promptText.trim()) {
|
|
17059
17208
|
contentBlocks.push({ type: "text", text: promptText });
|
|
17060
17209
|
}
|
|
@@ -17105,27 +17254,30 @@ function normalizeCodexMcpToolResult(output) {
|
|
|
17105
17254
|
}
|
|
17106
17255
|
return raw;
|
|
17107
17256
|
}
|
|
17257
|
+
function resolveCodexHome(env) {
|
|
17258
|
+
return env.CODEX_HOME || process.env.CODEX_HOME || (0, import_path5.join)((0, import_os5.homedir)(), ".codex");
|
|
17259
|
+
}
|
|
17108
17260
|
function findCodexSessionLog(runtimeSessionId, codexHome) {
|
|
17109
|
-
if (!runtimeSessionId || !(0,
|
|
17110
|
-
const root = (0,
|
|
17111
|
-
if (!(0,
|
|
17261
|
+
if (!runtimeSessionId || !(0, import_fs5.existsSync)(codexHome)) return null;
|
|
17262
|
+
const root = (0, import_path5.join)(codexHome, "sessions");
|
|
17263
|
+
if (!(0, import_fs5.existsSync)(root)) return null;
|
|
17112
17264
|
const matches = [];
|
|
17113
17265
|
const stack = [root];
|
|
17114
17266
|
while (stack.length > 0) {
|
|
17115
17267
|
const dir = stack.pop();
|
|
17116
17268
|
let entries;
|
|
17117
17269
|
try {
|
|
17118
|
-
entries = (0,
|
|
17270
|
+
entries = (0, import_fs5.readdirSync)(dir, { withFileTypes: true });
|
|
17119
17271
|
} catch {
|
|
17120
17272
|
continue;
|
|
17121
17273
|
}
|
|
17122
17274
|
for (const entry of entries) {
|
|
17123
|
-
const path = (0,
|
|
17275
|
+
const path = (0, import_path5.join)(dir, entry.name);
|
|
17124
17276
|
if (entry.isDirectory()) {
|
|
17125
17277
|
stack.push(path);
|
|
17126
17278
|
} else if (entry.isFile() && entry.name.includes(runtimeSessionId) && entry.name.endsWith(".jsonl")) {
|
|
17127
17279
|
try {
|
|
17128
|
-
matches.push({ path, mtimeMs: (0,
|
|
17280
|
+
matches.push({ path, mtimeMs: (0, import_fs5.statSync)(path).mtimeMs });
|
|
17129
17281
|
} catch {
|
|
17130
17282
|
matches.push({ path, mtimeMs: 0 });
|
|
17131
17283
|
}
|
|
@@ -17158,12 +17310,14 @@ function latestUserMessageLineIndex(lines) {
|
|
|
17158
17310
|
async function replayCodexMcpToolEventsFromSessionLog(context, state) {
|
|
17159
17311
|
const runtimeSessionId = state.runtimeSessionId;
|
|
17160
17312
|
if (!runtimeSessionId) return;
|
|
17161
|
-
|
|
17313
|
+
if (!state.iterations || state.iterations <= 0) return;
|
|
17314
|
+
const codexHome = resolveCodexHome(context.env);
|
|
17162
17315
|
const logPath = findCodexSessionLog(runtimeSessionId, codexHome);
|
|
17163
17316
|
if (!logPath) return;
|
|
17164
|
-
const allLines = (0,
|
|
17317
|
+
const allLines = (0, import_fs5.readFileSync)(logPath, "utf8").split(/\r?\n/).filter(Boolean);
|
|
17165
17318
|
const latestUserLineIndex = latestUserMessageLineIndex(allLines);
|
|
17166
17319
|
const lines = latestUserLineIndex >= 0 ? allLines.slice(latestUserLineIndex + 1) : allLines;
|
|
17320
|
+
const streamedToolIds = state.codexStreamedToolIds ?? /* @__PURE__ */ new Set();
|
|
17167
17321
|
const emittedToolIds = /* @__PURE__ */ new Set();
|
|
17168
17322
|
for (const line of lines) {
|
|
17169
17323
|
const entry = parseJsonObject(line);
|
|
@@ -17174,6 +17328,7 @@ async function replayCodexMcpToolEventsFromSessionLog(context, state) {
|
|
|
17174
17328
|
const name = typeof payload.name === "string" ? payload.name : "";
|
|
17175
17329
|
const namespace = typeof payload.namespace === "string" ? payload.namespace : "";
|
|
17176
17330
|
if (!callId || !name || !namespace.startsWith("mcp__")) continue;
|
|
17331
|
+
if (streamedToolIds.has(callId)) continue;
|
|
17177
17332
|
const toolName = `${namespace.replace(/_+$/, "")}__${name}`;
|
|
17178
17333
|
context.presenter.onToolUse(toolName, parseMaybeJson(payload.arguments), callId);
|
|
17179
17334
|
emittedToolIds.add(callId);
|
|
@@ -17182,6 +17337,7 @@ async function replayCodexMcpToolEventsFromSessionLog(context, state) {
|
|
|
17182
17337
|
if (payload.type === "tool_search_call") {
|
|
17183
17338
|
const callId = typeof payload.call_id === "string" ? payload.call_id : "";
|
|
17184
17339
|
if (!callId) continue;
|
|
17340
|
+
if (streamedToolIds.has(callId)) continue;
|
|
17185
17341
|
context.presenter.onToolUse("tool_search", payload.arguments ?? {}, callId);
|
|
17186
17342
|
emittedToolIds.add(callId);
|
|
17187
17343
|
continue;
|
|
@@ -17208,6 +17364,63 @@ async function replayCodexMcpToolEventsFromSessionLog(context, state) {
|
|
|
17208
17364
|
}
|
|
17209
17365
|
}
|
|
17210
17366
|
}
|
|
17367
|
+
var CODEX_EXIT_GRACE_MS = 3e4;
|
|
17368
|
+
function armCodexExitGraceKill(state) {
|
|
17369
|
+
if (state.exitGraceKillTimer) return;
|
|
17370
|
+
const child = state.process;
|
|
17371
|
+
if (!child) return;
|
|
17372
|
+
const timer = setTimeout(() => {
|
|
17373
|
+
if (child.exitCode !== null || child.killed) return;
|
|
17374
|
+
console.warn(
|
|
17375
|
+
"[codex_app_server] Process did not exit after turn completion; killing process group"
|
|
17376
|
+
);
|
|
17377
|
+
const pid = child.pid;
|
|
17378
|
+
if (pid) {
|
|
17379
|
+
try {
|
|
17380
|
+
process.kill(-pid, "SIGTERM");
|
|
17381
|
+
return;
|
|
17382
|
+
} catch {
|
|
17383
|
+
}
|
|
17384
|
+
}
|
|
17385
|
+
try {
|
|
17386
|
+
child.kill("SIGTERM");
|
|
17387
|
+
} catch {
|
|
17388
|
+
}
|
|
17389
|
+
}, CODEX_EXIT_GRACE_MS);
|
|
17390
|
+
timer.unref?.();
|
|
17391
|
+
state.exitGraceKillTimer = timer;
|
|
17392
|
+
}
|
|
17393
|
+
function disarmCodexExitGraceKill(state) {
|
|
17394
|
+
if (!state.exitGraceKillTimer) return;
|
|
17395
|
+
clearTimeout(state.exitGraceKillTimer);
|
|
17396
|
+
state.exitGraceKillTimer = void 0;
|
|
17397
|
+
}
|
|
17398
|
+
function trackCodexStreamedToolId(state, toolId) {
|
|
17399
|
+
if (!state.codexStreamedToolIds) state.codexStreamedToolIds = /* @__PURE__ */ new Set();
|
|
17400
|
+
if (state.codexStreamedToolIds.has(toolId)) return false;
|
|
17401
|
+
state.codexStreamedToolIds.add(toolId);
|
|
17402
|
+
return true;
|
|
17403
|
+
}
|
|
17404
|
+
function buildCodexMcpToolName(item) {
|
|
17405
|
+
const server = typeof item.server === "string" ? item.server.replace(/_+$/, "") : "";
|
|
17406
|
+
const tool = typeof item.tool === "string" ? item.tool : typeof item.tool_name === "string" ? item.tool_name : "";
|
|
17407
|
+
if (server && tool) return `mcp__${server}__${tool}`;
|
|
17408
|
+
return tool || "MCP Tool";
|
|
17409
|
+
}
|
|
17410
|
+
function emitCodexFileChanges(context, state, itemId, changes) {
|
|
17411
|
+
changes.forEach((change, index) => {
|
|
17412
|
+
if (!change || typeof change !== "object") return;
|
|
17413
|
+
const record = change;
|
|
17414
|
+
const filePath = typeof record.path === "string" ? record.path : "";
|
|
17415
|
+
if (!filePath) return;
|
|
17416
|
+
const kind = typeof record.kind === "string" ? record.kind : "update";
|
|
17417
|
+
const toolName = kind === "add" ? "write_file" : "edit_file";
|
|
17418
|
+
const toolId = changes.length > 1 ? `${itemId}:${index}` : itemId;
|
|
17419
|
+
trackCodexStreamedToolId(state, toolId);
|
|
17420
|
+
void context.presenter.onToolUse(toolName, { file_path: filePath, kind }, toolId);
|
|
17421
|
+
void context.presenter.onToolResult?.(toolId, `${kind} ${filePath}`);
|
|
17422
|
+
});
|
|
17423
|
+
}
|
|
17211
17424
|
function handleCodexStructuredEvent(parsed, context, state) {
|
|
17212
17425
|
const presenter = context.presenter;
|
|
17213
17426
|
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
@@ -17235,6 +17448,7 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17235
17448
|
case "thread.started":
|
|
17236
17449
|
return true;
|
|
17237
17450
|
case "turn.started":
|
|
17451
|
+
disarmCodexExitGraceKill(state);
|
|
17238
17452
|
state.iterations += 1;
|
|
17239
17453
|
return true;
|
|
17240
17454
|
case "session_configured":
|
|
@@ -17243,36 +17457,112 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17243
17457
|
}
|
|
17244
17458
|
return true;
|
|
17245
17459
|
case "task_started":
|
|
17460
|
+
disarmCodexExitGraceKill(state);
|
|
17246
17461
|
state.iterations += 1;
|
|
17247
17462
|
return true;
|
|
17248
17463
|
case "item.started": {
|
|
17249
17464
|
const item = typeof parsed.item === "object" && parsed.item !== null ? parsed.item : null;
|
|
17250
|
-
if (!item || item.type !== "
|
|
17251
|
-
|
|
17252
|
-
|
|
17253
|
-
|
|
17465
|
+
if (!item || typeof item.type !== "string" || typeof item.id !== "string") return true;
|
|
17466
|
+
switch (item.type) {
|
|
17467
|
+
case "command_execution": {
|
|
17468
|
+
if (trackCodexStreamedToolId(state, item.id)) {
|
|
17469
|
+
const command = typeof item.command === "string" ? item.command : "";
|
|
17470
|
+
void presenter.onToolUse("Bash", { command, cwd: context.cwd }, item.id);
|
|
17471
|
+
}
|
|
17472
|
+
return true;
|
|
17473
|
+
}
|
|
17474
|
+
case "mcp_tool_call": {
|
|
17475
|
+
if (trackCodexStreamedToolId(state, item.id)) {
|
|
17476
|
+
const input = typeof item.arguments === "object" && item.arguments !== null ? item.arguments : {};
|
|
17477
|
+
void presenter.onToolUse(buildCodexMcpToolName(item), input, item.id);
|
|
17478
|
+
}
|
|
17479
|
+
return true;
|
|
17480
|
+
}
|
|
17481
|
+
case "web_search": {
|
|
17482
|
+
if (trackCodexStreamedToolId(state, item.id)) {
|
|
17483
|
+
const query = typeof item.query === "string" ? item.query : "";
|
|
17484
|
+
void presenter.onToolUse("web_search", { query }, item.id);
|
|
17485
|
+
}
|
|
17486
|
+
return true;
|
|
17487
|
+
}
|
|
17488
|
+
// Text-bearing and patch/todo items carry no useful live-start payload; they
|
|
17489
|
+
// are surfaced on item.completed. Recognized (do not count as unhandled).
|
|
17490
|
+
case "reasoning":
|
|
17491
|
+
case "agent_message":
|
|
17492
|
+
case "file_change":
|
|
17493
|
+
case "todo_list":
|
|
17494
|
+
return true;
|
|
17495
|
+
default:
|
|
17496
|
+
return false;
|
|
17497
|
+
}
|
|
17254
17498
|
}
|
|
17255
17499
|
case "item.completed": {
|
|
17256
17500
|
const item = typeof parsed.item === "object" && parsed.item !== null ? parsed.item : null;
|
|
17257
17501
|
if (!item || typeof item.type !== "string") return true;
|
|
17258
|
-
|
|
17259
|
-
|
|
17260
|
-
|
|
17261
|
-
|
|
17262
|
-
|
|
17263
|
-
|
|
17502
|
+
switch (item.type) {
|
|
17503
|
+
case "reasoning": {
|
|
17504
|
+
if (typeof item.text === "string") void presenter.onThinking(item.text);
|
|
17505
|
+
return true;
|
|
17506
|
+
}
|
|
17507
|
+
case "agent_message": {
|
|
17508
|
+
if (typeof item.text === "string") {
|
|
17509
|
+
state.summary += `${item.text}
|
|
17264
17510
|
`;
|
|
17265
|
-
|
|
17266
|
-
|
|
17267
|
-
|
|
17268
|
-
|
|
17269
|
-
|
|
17270
|
-
|
|
17271
|
-
|
|
17272
|
-
|
|
17273
|
-
|
|
17511
|
+
void presenter.onAssistantText(item.text);
|
|
17512
|
+
}
|
|
17513
|
+
return true;
|
|
17514
|
+
}
|
|
17515
|
+
case "command_execution": {
|
|
17516
|
+
if (typeof item.id !== "string") return true;
|
|
17517
|
+
if (trackCodexStreamedToolId(state, item.id)) {
|
|
17518
|
+
const command = typeof item.command === "string" ? item.command : "";
|
|
17519
|
+
void presenter.onToolUse("Bash", { command, cwd: context.cwd }, item.id);
|
|
17520
|
+
}
|
|
17521
|
+
const output = typeof item.aggregated_output === "string" ? item.aggregated_output : "";
|
|
17522
|
+
const itemExitCode = typeof item.exit_code === "number" ? item.exit_code : null;
|
|
17523
|
+
const resultText = output.trim().length > 0 ? output : itemExitCode === null ? "" : `Exit code: ${itemExitCode}`;
|
|
17524
|
+
void presenter.onToolResult?.(
|
|
17525
|
+
item.id,
|
|
17526
|
+
resultText,
|
|
17527
|
+
itemExitCode != null && itemExitCode !== 0
|
|
17528
|
+
);
|
|
17529
|
+
return true;
|
|
17530
|
+
}
|
|
17531
|
+
case "mcp_tool_call": {
|
|
17532
|
+
if (typeof item.id !== "string") return true;
|
|
17533
|
+
if (trackCodexStreamedToolId(state, item.id)) {
|
|
17534
|
+
const input = typeof item.arguments === "object" && item.arguments !== null ? item.arguments : {};
|
|
17535
|
+
void presenter.onToolUse(buildCodexMcpToolName(item), input, item.id);
|
|
17536
|
+
}
|
|
17537
|
+
const isError = item.status === "failed" || item.error != null;
|
|
17538
|
+
const payload = item.result ?? item.output ?? item.error ?? {};
|
|
17539
|
+
const resultText = typeof payload === "string" ? payload : JSON.stringify(payload ?? {}, null, 2);
|
|
17540
|
+
void presenter.onToolResult?.(item.id, resultText, isError);
|
|
17541
|
+
return true;
|
|
17542
|
+
}
|
|
17543
|
+
case "file_change": {
|
|
17544
|
+
if (typeof item.id !== "string") return true;
|
|
17545
|
+
const changes = Array.isArray(item.changes) ? item.changes : [];
|
|
17546
|
+
emitCodexFileChanges(context, state, item.id, changes);
|
|
17547
|
+
return true;
|
|
17548
|
+
}
|
|
17549
|
+
case "web_search": {
|
|
17550
|
+
if (typeof item.id !== "string") return true;
|
|
17551
|
+
const query = typeof item.query === "string" ? item.query : "";
|
|
17552
|
+
if (trackCodexStreamedToolId(state, item.id)) {
|
|
17553
|
+
void presenter.onToolUse("web_search", { query }, item.id);
|
|
17554
|
+
}
|
|
17555
|
+
void presenter.onToolResult?.(item.id, query ? `Searched: ${query}` : "");
|
|
17556
|
+
return true;
|
|
17557
|
+
}
|
|
17558
|
+
case "todo_list": {
|
|
17559
|
+
const todos = Array.isArray(item.items) ? item.items : [];
|
|
17560
|
+
void presenter.onTodoWrite?.(todos);
|
|
17561
|
+
return true;
|
|
17562
|
+
}
|
|
17563
|
+
default:
|
|
17564
|
+
return false;
|
|
17274
17565
|
}
|
|
17275
|
-
return true;
|
|
17276
17566
|
}
|
|
17277
17567
|
case "agent_message_delta":
|
|
17278
17568
|
case "agent_message_content_delta": {
|
|
@@ -17294,6 +17584,7 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17294
17584
|
const toolId = typeof parsed.call_id === "string" ? parsed.call_id : `exec-${Date.now()}`;
|
|
17295
17585
|
const command = Array.isArray(parsed.command) ? parsed.command.join(" ") : "";
|
|
17296
17586
|
const cwd = typeof parsed.cwd === "string" ? parsed.cwd : context.cwd;
|
|
17587
|
+
trackCodexStreamedToolId(state, toolId);
|
|
17297
17588
|
void presenter.onToolUse("Bash", { command, cwd }, toolId);
|
|
17298
17589
|
return true;
|
|
17299
17590
|
}
|
|
@@ -17307,6 +17598,7 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17307
17598
|
const toolId = typeof parsed.call_id === "string" ? parsed.call_id : `mcp-${Date.now()}`;
|
|
17308
17599
|
const invocation = typeof parsed.invocation === "object" && parsed.invocation !== null ? parsed.invocation : {};
|
|
17309
17600
|
const tool = typeof invocation.tool_name === "string" ? invocation.tool_name : typeof invocation.tool === "string" ? invocation.tool : "MCP Tool";
|
|
17601
|
+
trackCodexStreamedToolId(state, toolId);
|
|
17310
17602
|
void presenter.onToolUse(tool, invocation, toolId);
|
|
17311
17603
|
return true;
|
|
17312
17604
|
}
|
|
@@ -17344,6 +17636,8 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17344
17636
|
case "task_complete": {
|
|
17345
17637
|
const lastMessage = typeof parsed.last_agent_message === "string" ? parsed.last_agent_message : "";
|
|
17346
17638
|
if (lastMessage.length > 0) state.summary = lastMessage;
|
|
17639
|
+
state.error = void 0;
|
|
17640
|
+
armCodexExitGraceKill(state);
|
|
17347
17641
|
return true;
|
|
17348
17642
|
}
|
|
17349
17643
|
case "turn.completed": {
|
|
@@ -17365,6 +17659,8 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17365
17659
|
cacheReadTokens: state.usage.cacheReadTokens,
|
|
17366
17660
|
cacheCreationTokens: state.usage.cacheCreationTokens
|
|
17367
17661
|
});
|
|
17662
|
+
state.error = void 0;
|
|
17663
|
+
armCodexExitGraceKill(state);
|
|
17368
17664
|
return true;
|
|
17369
17665
|
}
|
|
17370
17666
|
case "error": {
|
|
@@ -17409,6 +17705,33 @@ function createCodexRuntimeBackend(command = "codex", defaultArgs = []) {
|
|
|
17409
17705
|
context.config.images,
|
|
17410
17706
|
context.cwd
|
|
17411
17707
|
);
|
|
17708
|
+
const requestedResumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
|
|
17709
|
+
let resumableSessionId = requestedResumeId;
|
|
17710
|
+
let resumeContextPrefix;
|
|
17711
|
+
if (requestedResumeId && !findCodexSessionLog(requestedResumeId, resolveCodexHome(context.env))) {
|
|
17712
|
+
resumableSessionId = void 0;
|
|
17713
|
+
resumeContextPrefix = context.config.resumeFallbackContext?.trim() || void 0;
|
|
17714
|
+
console.warn(
|
|
17715
|
+
`[codex_app_server] No rollout log found for session ${requestedResumeId}; starting a fresh session instead of resuming`
|
|
17716
|
+
);
|
|
17717
|
+
logResumeObservability("agent_resume_fallback_used", {
|
|
17718
|
+
backendKind: "codex_app_server",
|
|
17719
|
+
reason: "rollout_log_missing",
|
|
17720
|
+
requestedResumeId,
|
|
17721
|
+
hadFallbackContext: Boolean(resumeContextPrefix)
|
|
17722
|
+
});
|
|
17723
|
+
emitSessionNotice(
|
|
17724
|
+
context.presenter,
|
|
17725
|
+
"session_resume_failed",
|
|
17726
|
+
resumeContextPrefix ? "The previous Codex session log was not found on this machine, so a fresh session was started with the recent conversation history re-included." : "The previous Codex session log was not found on this machine, so a fresh session was started. Earlier conversation context may be missing from this reply."
|
|
17727
|
+
);
|
|
17728
|
+
}
|
|
17729
|
+
if (resumableSessionId) {
|
|
17730
|
+
logResumeObservability("agent_resume_requested", {
|
|
17731
|
+
backendKind: "codex_app_server",
|
|
17732
|
+
requestedResumeId: resumableSessionId
|
|
17733
|
+
});
|
|
17734
|
+
}
|
|
17412
17735
|
try {
|
|
17413
17736
|
return await createGenericCliBackend({
|
|
17414
17737
|
kind: "codex_app_server",
|
|
@@ -17417,15 +17740,10 @@ function createCodexRuntimeBackend(command = "codex", defaultArgs = []) {
|
|
|
17417
17740
|
args: [],
|
|
17418
17741
|
startupTimeoutMs: CODEX_STARTUP_TIMEOUT_MS,
|
|
17419
17742
|
buildArgs: (ctx) => {
|
|
17420
|
-
const resumeId =
|
|
17743
|
+
const resumeId = resumableSessionId;
|
|
17421
17744
|
const modelArgs = ctx.config.selectedModel?.trim() ? ["--model", ctx.config.selectedModel.trim()] : [];
|
|
17422
17745
|
const effortArgs = buildCodexEffortArgs(ctx.config.selectedEffortLevel);
|
|
17423
|
-
const permissionArgs =
|
|
17424
|
-
"--ask-for-approval",
|
|
17425
|
-
"never",
|
|
17426
|
-
"--sandbox",
|
|
17427
|
-
"danger-full-access"
|
|
17428
|
-
];
|
|
17746
|
+
const permissionArgs = getCodexPermissionArgs(ctx.config);
|
|
17429
17747
|
const imageArgs = imagePaths.flatMap((p) => ["--image", p]);
|
|
17430
17748
|
const baseArgs = resumeId ? [
|
|
17431
17749
|
...permissionArgs,
|
|
@@ -17452,7 +17770,10 @@ function createCodexRuntimeBackend(command = "codex", defaultArgs = []) {
|
|
|
17452
17770
|
},
|
|
17453
17771
|
promptViaStdin: true,
|
|
17454
17772
|
augmentPrompt: (ctx) => {
|
|
17455
|
-
const
|
|
17773
|
+
const promptWithContext = resumeContextPrefix ? `${resumeContextPrefix}
|
|
17774
|
+
|
|
17775
|
+
${ctx.promptText}` : ctx.promptText;
|
|
17776
|
+
const basePrompt = buildPromptWithSystem(ctx.config, promptWithContext);
|
|
17456
17777
|
return ctx.config.mode === "plan" ? buildPlanModePrefix(basePrompt) : basePrompt;
|
|
17457
17778
|
},
|
|
17458
17779
|
parseStructuredLine: parseCodexStructuredLine,
|
|
@@ -17779,7 +18100,10 @@ function buildCursorAgentModelArg(modelId, options) {
|
|
|
17779
18100
|
return `${parsed.baseId}[${overrides.join(",")}]`;
|
|
17780
18101
|
}
|
|
17781
18102
|
function isCursorAgentCliModelId(model) {
|
|
17782
|
-
return model === "auto" || /^composer-/.test(model) || /^gpt
|
|
18103
|
+
return model === "auto" || /^composer-/.test(model) || /^gpt-/.test(model) || /^claude-/.test(model) || /^gemini-/.test(model) || /^grok-/.test(model) || /^kimi-/.test(model);
|
|
18104
|
+
}
|
|
18105
|
+
function isDispatchableCursorModel(model) {
|
|
18106
|
+
return isCursorAgentCliModelId(model) || Boolean(findModelDef("cursor_agent_cli", model));
|
|
17783
18107
|
}
|
|
17784
18108
|
function shouldForceCursorAgent(config) {
|
|
17785
18109
|
if (shouldUseReadOnlyRuntimePermissions(config)) return false;
|
|
@@ -17797,6 +18121,21 @@ function createCursorAgentCliBackend(command = "cursor-agent", defaultArgs = [])
|
|
|
17797
18121
|
kind: "cursor_agent_cli",
|
|
17798
18122
|
supportTier: "structured",
|
|
17799
18123
|
async run(context) {
|
|
18124
|
+
const selectedModel = context.config.selectedModel?.trim();
|
|
18125
|
+
if (selectedModel && !isDispatchableCursorModel(selectedModel)) {
|
|
18126
|
+
const message = `Model "${selectedModel}" is not supported by cursor-agent. Pick a cursor-agent model (or "auto") and send your message again.`;
|
|
18127
|
+
return {
|
|
18128
|
+
success: false,
|
|
18129
|
+
summary: message,
|
|
18130
|
+
filesModified: [],
|
|
18131
|
+
planFilesCreated: [],
|
|
18132
|
+
iterations: 0,
|
|
18133
|
+
error: message,
|
|
18134
|
+
errorKind: "model_mismatch",
|
|
18135
|
+
backendKind: "cursor_agent_cli",
|
|
18136
|
+
supportTier: "structured"
|
|
18137
|
+
};
|
|
18138
|
+
}
|
|
17800
18139
|
const { files: imageFiles, cleanup } = writeImagesToTempFiles(
|
|
17801
18140
|
context.config.images,
|
|
17802
18141
|
context.cwd
|
|
@@ -17826,10 +18165,18 @@ function createCursorAgentCliBackend(command = "cursor-agent", defaultArgs = [])
|
|
|
17826
18165
|
args.push("--mode", "plan");
|
|
17827
18166
|
} else if (ctx.config.mode === "ask") {
|
|
17828
18167
|
args.push("--mode", "ask");
|
|
18168
|
+
} else if (shouldUseReadOnlyRuntimePermissions(ctx.config)) {
|
|
18169
|
+
args.push("--mode", "plan");
|
|
18170
|
+
}
|
|
18171
|
+
if (resumeId) {
|
|
18172
|
+
args.push("--resume", resumeId);
|
|
18173
|
+
logResumeObservability("agent_resume_requested", {
|
|
18174
|
+
backendKind: "cursor_agent_cli",
|
|
18175
|
+
requestedResumeId: resumeId
|
|
18176
|
+
});
|
|
17829
18177
|
}
|
|
17830
|
-
if (resumeId) args.push("--resume", resumeId);
|
|
17831
18178
|
const model = ctx.config.selectedModel?.trim();
|
|
17832
|
-
if (model &&
|
|
18179
|
+
if (model && isDispatchableCursorModel(model)) {
|
|
17833
18180
|
const modelDef = findModelDef("cursor_agent_cli", model);
|
|
17834
18181
|
args.push(
|
|
17835
18182
|
"--model",
|
|
@@ -18292,9 +18639,9 @@ function buildGrokAgentArgs(config, defaultArgs = []) {
|
|
|
18292
18639
|
return args;
|
|
18293
18640
|
}
|
|
18294
18641
|
var DEFAULT_SKILL_ROOTS = [
|
|
18295
|
-
(0,
|
|
18296
|
-
(0,
|
|
18297
|
-
(0,
|
|
18642
|
+
(0, import_path6.join)((0, import_os6.homedir)(), ".agents", "skills"),
|
|
18643
|
+
(0, import_path6.join)((0, import_os6.homedir)(), ".claude", "skills"),
|
|
18644
|
+
(0, import_path6.join)((0, import_os6.homedir)(), ".config", "opencode", "skills")
|
|
18298
18645
|
];
|
|
18299
18646
|
function parseFrontmatter(content) {
|
|
18300
18647
|
if (!content.startsWith("---\n")) return {};
|
|
@@ -18310,12 +18657,12 @@ function parseFrontmatter(content) {
|
|
|
18310
18657
|
return result;
|
|
18311
18658
|
}
|
|
18312
18659
|
function listSkillFiles(root) {
|
|
18313
|
-
if (!(0,
|
|
18660
|
+
if (!(0, import_fs6.existsSync)(root)) return [];
|
|
18314
18661
|
const files = [];
|
|
18315
|
-
for (const entry of (0,
|
|
18662
|
+
for (const entry of (0, import_fs6.readdirSync)(root, { withFileTypes: true })) {
|
|
18316
18663
|
if (!entry.isDirectory()) continue;
|
|
18317
|
-
const skillPath = (0,
|
|
18318
|
-
if ((0,
|
|
18664
|
+
const skillPath = (0, import_path6.join)(root, entry.name, "SKILL.md");
|
|
18665
|
+
if ((0, import_fs6.existsSync)(skillPath)) files.push(skillPath);
|
|
18319
18666
|
}
|
|
18320
18667
|
return files;
|
|
18321
18668
|
}
|
|
@@ -18324,7 +18671,7 @@ function loadInstalledSkills(roots = DEFAULT_SKILL_ROOTS) {
|
|
|
18324
18671
|
for (const root of roots) {
|
|
18325
18672
|
for (const skillPath of listSkillFiles(root)) {
|
|
18326
18673
|
try {
|
|
18327
|
-
const frontmatter = parseFrontmatter((0,
|
|
18674
|
+
const frontmatter = parseFrontmatter((0, import_fs6.readFileSync)(skillPath, "utf8"));
|
|
18328
18675
|
const name = frontmatter.name;
|
|
18329
18676
|
const description = frontmatter.description;
|
|
18330
18677
|
if (!name || !description || skills.has(name)) continue;
|
|
@@ -18757,7 +19104,31 @@ function createSupatestCliBackend(command = "supatest", defaultArgs = []) {
|
|
|
18757
19104
|
"--permission-mode",
|
|
18758
19105
|
getClaudePermissionMode(context.config)
|
|
18759
19106
|
];
|
|
18760
|
-
const
|
|
19107
|
+
const requestedResumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
|
|
19108
|
+
let resumeId = requestedResumeId;
|
|
19109
|
+
let resumeContextPrefix;
|
|
19110
|
+
if (requestedResumeId && !claudeSessionLogExists({
|
|
19111
|
+
sessionId: requestedResumeId,
|
|
19112
|
+
cwd: context.cwd,
|
|
19113
|
+
home: resolveClaudeHome(context.env, "supatest")
|
|
19114
|
+
})) {
|
|
19115
|
+
resumeId = void 0;
|
|
19116
|
+
resumeContextPrefix = context.config.resumeFallbackContext?.trim() || void 0;
|
|
19117
|
+
console.warn(
|
|
19118
|
+
`[supatest_cli] No session log for ${requestedResumeId} under cwd ${context.cwd}; starting a fresh session instead of resuming`
|
|
19119
|
+
);
|
|
19120
|
+
logResumeObservability("agent_resume_fallback_used", {
|
|
19121
|
+
backendKind: "supatest_cli",
|
|
19122
|
+
reason: "session_log_missing_for_cwd",
|
|
19123
|
+
requestedResumeId,
|
|
19124
|
+
hadFallbackContext: Boolean(resumeContextPrefix)
|
|
19125
|
+
});
|
|
19126
|
+
emitSessionNotice(
|
|
19127
|
+
context.presenter,
|
|
19128
|
+
"session_resume_failed",
|
|
19129
|
+
resumeContextPrefix ? "The previous session was created in a different working directory and could not be resumed here, so a fresh session was started with the recent conversation history re-included." : "The previous session was created in a different working directory and could not be resumed here, so a fresh session was started. Earlier conversation context may be missing from this reply."
|
|
19130
|
+
);
|
|
19131
|
+
}
|
|
18761
19132
|
if (!resumeId) {
|
|
18762
19133
|
if (context.config.systemPrompt?.trim()) {
|
|
18763
19134
|
args.push("--system-prompt", context.config.systemPrompt.trim());
|
|
@@ -18773,6 +19144,10 @@ function createSupatestCliBackend(command = "supatest", defaultArgs = []) {
|
|
|
18773
19144
|
if (resumeId) {
|
|
18774
19145
|
args.push("--resume", resumeId);
|
|
18775
19146
|
console.info("[supatest_cli] Resuming session", { resumeId });
|
|
19147
|
+
logResumeObservability("agent_resume_requested", {
|
|
19148
|
+
backendKind: "supatest_cli",
|
|
19149
|
+
requestedResumeId: resumeId
|
|
19150
|
+
});
|
|
18776
19151
|
}
|
|
18777
19152
|
args.push(...defaultArgs);
|
|
18778
19153
|
return createGenericCliBackend({
|
|
@@ -18791,7 +19166,10 @@ function createSupatestCliBackend(command = "supatest", defaultArgs = []) {
|
|
|
18791
19166
|
});
|
|
18792
19167
|
}
|
|
18793
19168
|
}
|
|
18794
|
-
const
|
|
19169
|
+
const basePromptText = resumeContextPrefix ? `${resumeContextPrefix}
|
|
19170
|
+
|
|
19171
|
+
${ctx.promptText}` : ctx.promptText;
|
|
19172
|
+
const promptText = ctx.config.mode === "plan" ? buildPlanModePrefix(buildPromptWithSystem(ctx.config, basePromptText)) : buildPromptWithSystem(ctx.config, basePromptText);
|
|
18795
19173
|
if (promptText.trim()) {
|
|
18796
19174
|
contentBlocks.push({ type: "text", text: promptText });
|
|
18797
19175
|
}
|
|
@@ -18821,40 +19199,40 @@ function parseSkillInvocation(text) {
|
|
|
18821
19199
|
function candidateRoots(cwd) {
|
|
18822
19200
|
const roots = [];
|
|
18823
19201
|
if (cwd) {
|
|
18824
|
-
roots.push((0,
|
|
18825
|
-
roots.push((0,
|
|
18826
|
-
roots.push((0,
|
|
18827
|
-
if ((0,
|
|
19202
|
+
roots.push((0, import_path7.join)(cwd, ".agents", "skills"));
|
|
19203
|
+
roots.push((0, import_path7.join)(cwd, ".claude", "skills"));
|
|
19204
|
+
roots.push((0, import_path7.join)(cwd, ".codex", "skills"));
|
|
19205
|
+
if ((0, import_fs7.existsSync)(cwd)) {
|
|
18828
19206
|
let entries = [];
|
|
18829
19207
|
try {
|
|
18830
|
-
entries = (0,
|
|
19208
|
+
entries = (0, import_fs7.readdirSync)(cwd, { withFileTypes: true });
|
|
18831
19209
|
} catch {
|
|
18832
19210
|
}
|
|
18833
19211
|
for (const entry of entries) {
|
|
18834
19212
|
if (!entry.isDirectory()) continue;
|
|
18835
19213
|
if (entry.name.startsWith(".")) continue;
|
|
18836
|
-
const child = (0,
|
|
18837
|
-
roots.push((0,
|
|
18838
|
-
roots.push((0,
|
|
18839
|
-
roots.push((0,
|
|
19214
|
+
const child = (0, import_path7.join)(cwd, entry.name);
|
|
19215
|
+
roots.push((0, import_path7.join)(child, ".agents", "skills"));
|
|
19216
|
+
roots.push((0, import_path7.join)(child, ".claude", "skills"));
|
|
19217
|
+
roots.push((0, import_path7.join)(child, ".codex", "skills"));
|
|
18840
19218
|
}
|
|
18841
19219
|
}
|
|
18842
19220
|
}
|
|
18843
|
-
const home = (0,
|
|
18844
|
-
roots.push((0,
|
|
18845
|
-
roots.push((0,
|
|
18846
|
-
roots.push((0,
|
|
19221
|
+
const home = (0, import_os7.homedir)();
|
|
19222
|
+
roots.push((0, import_path7.join)(home, ".agents", "skills"));
|
|
19223
|
+
roots.push((0, import_path7.join)(home, ".claude", "skills"));
|
|
19224
|
+
roots.push((0, import_path7.join)(home, ".codex", "skills"));
|
|
18847
19225
|
return Array.from(new Set(roots));
|
|
18848
19226
|
}
|
|
18849
19227
|
function resolveSkillInvocation(text, cwd) {
|
|
18850
19228
|
const parsed = parseSkillInvocation(text);
|
|
18851
19229
|
if (!parsed) return null;
|
|
18852
19230
|
for (const root of candidateRoots(cwd)) {
|
|
18853
|
-
const skillPath = (0,
|
|
18854
|
-
if (!(0,
|
|
19231
|
+
const skillPath = (0, import_path7.join)(root, parsed.command, "SKILL.md");
|
|
19232
|
+
if (!(0, import_fs7.existsSync)(skillPath)) continue;
|
|
18855
19233
|
let content;
|
|
18856
19234
|
try {
|
|
18857
|
-
content = (0,
|
|
19235
|
+
content = (0, import_fs7.readFileSync)(skillPath, "utf-8");
|
|
18858
19236
|
} catch {
|
|
18859
19237
|
continue;
|
|
18860
19238
|
}
|
|
@@ -19035,11 +19413,19 @@ var BaseMachineAgent = class _BaseMachineAgent {
|
|
|
19035
19413
|
async runBackendWithActivityHeartbeat(backend, context) {
|
|
19036
19414
|
const intervalMs = this.getActivityHeartbeatIntervalMs();
|
|
19037
19415
|
let heartbeat = null;
|
|
19416
|
+
let livenessProbe = null;
|
|
19417
|
+
context.registerLivenessProbe = (probe) => {
|
|
19418
|
+
livenessProbe = probe;
|
|
19419
|
+
};
|
|
19420
|
+
const emitHeartbeat = () => {
|
|
19421
|
+
const evidence = livenessProbe?.();
|
|
19422
|
+
void this.presenter.onActivity?.("background_work", evidence);
|
|
19423
|
+
};
|
|
19038
19424
|
if (this.presenter.onActivity && intervalMs > 0) {
|
|
19039
|
-
|
|
19425
|
+
emitHeartbeat();
|
|
19040
19426
|
heartbeat = setInterval(() => {
|
|
19041
19427
|
if (!context.abortController.signal.aborted) {
|
|
19042
|
-
|
|
19428
|
+
emitHeartbeat();
|
|
19043
19429
|
}
|
|
19044
19430
|
}, intervalMs);
|
|
19045
19431
|
}
|
|
@@ -19088,10 +19474,6 @@ var BaseMachineAgent = class _BaseMachineAgent {
|
|
|
19088
19474
|
if (agentPrompt) systemPromptParts.push(agentPrompt);
|
|
19089
19475
|
const modeHint = getAlanModePrompt(config.mode);
|
|
19090
19476
|
if (modeHint) systemPromptParts.push(modeHint);
|
|
19091
|
-
systemPromptParts.push(ALAN_ASK_USER_PROMPT);
|
|
19092
|
-
if (backendKind === "cursor_agent_cli") {
|
|
19093
|
-
systemPromptParts.push(ALAN_CURSOR_ASK_USER_OVERLAY);
|
|
19094
|
-
}
|
|
19095
19477
|
const operatorSystemPrompt = config.systemPrompt?.trim();
|
|
19096
19478
|
if (operatorSystemPrompt) systemPromptParts.push(operatorSystemPrompt);
|
|
19097
19479
|
const workingDir = config.worktreePath || safeProjectPath;
|
|
@@ -19164,6 +19546,7 @@ Prefer relative paths and keep your work scoped to this project.`
|
|
|
19164
19546
|
};
|
|
19165
19547
|
if (lastResult.success || !lastResult.error) break;
|
|
19166
19548
|
if (this.abortController?.signal.aborted) break;
|
|
19549
|
+
if (lastResult.errorKind === "model_mismatch") break;
|
|
19167
19550
|
const hadResumeId = runtimeConfig.runtimeSessionId || runtimeConfig.providerSessionId;
|
|
19168
19551
|
if (hadResumeId && !isTransientError(lastResult.error)) {
|
|
19169
19552
|
const overrideResult = await this.onSessionResumeFailure(runtimeConfig, lastResult);
|
|
@@ -19175,10 +19558,25 @@ Prefer relative paths and keep your work scoped to this project.`
|
|
|
19175
19558
|
"[base-machine-agent] Resume failed, retrying without session ID:",
|
|
19176
19559
|
lastResult.error
|
|
19177
19560
|
);
|
|
19561
|
+
const fallbackContext = runtimeConfig.resumeFallbackContext?.trim();
|
|
19562
|
+
logResumeObservability("agent_resume_fallback_used", {
|
|
19563
|
+
backendKind: backend.kind,
|
|
19564
|
+
reason: "retry_fresh_after_resume_failure",
|
|
19565
|
+
errorKind: lastResult.errorKind,
|
|
19566
|
+
hadFallbackContext: Boolean(fallbackContext)
|
|
19567
|
+
});
|
|
19568
|
+
emitSessionNotice(
|
|
19569
|
+
this.presenter,
|
|
19570
|
+
"session_resume_failed",
|
|
19571
|
+
fallbackContext ? "Could not resume the previous session; started a fresh session with the recent conversation history re-included." : "Could not resume the previous session; retrying with a fresh session. Earlier conversation context may be missing from this reply."
|
|
19572
|
+
);
|
|
19178
19573
|
runtimeConfig = {
|
|
19179
19574
|
...runtimeConfig,
|
|
19180
19575
|
runtimeSessionId: void 0,
|
|
19181
|
-
providerSessionId: void 0
|
|
19576
|
+
providerSessionId: void 0,
|
|
19577
|
+
...fallbackContext ? { task: `${fallbackContext}
|
|
19578
|
+
|
|
19579
|
+
${runtimeConfig.task}` } : {}
|
|
19182
19580
|
};
|
|
19183
19581
|
const retryResult = await this.runBackendWithActivityHeartbeat(backend, {
|
|
19184
19582
|
presenter: this.presenter,
|
|
@@ -19186,7 +19584,7 @@ Prefer relative paths and keep your work scoped to this project.`
|
|
|
19186
19584
|
abortController: this.abortController,
|
|
19187
19585
|
cwd,
|
|
19188
19586
|
env,
|
|
19189
|
-
promptText,
|
|
19587
|
+
promptText: fallbackContext ? this.buildPromptText(runtimeConfig) : promptText,
|
|
19190
19588
|
onProcessSpawned: this.getOnProcessSpawned()
|
|
19191
19589
|
});
|
|
19192
19590
|
lastResult = {
|
|
@@ -19382,14 +19780,14 @@ function normalizePath(candidate, cwd) {
|
|
|
19382
19780
|
} catch {
|
|
19383
19781
|
}
|
|
19384
19782
|
if (path.startsWith("~/")) return null;
|
|
19385
|
-
if ((0,
|
|
19386
|
-
return cwd ? (0,
|
|
19783
|
+
if ((0, import_path8.isAbsolute)(path)) return path;
|
|
19784
|
+
return cwd ? (0, import_path8.join)(cwd, path) : null;
|
|
19387
19785
|
}
|
|
19388
19786
|
function resolveToolCwd(cwd, toolInput) {
|
|
19389
19787
|
if (!isRecord(toolInput)) return cwd;
|
|
19390
19788
|
const raw = typeof toolInput.cwd === "string" ? toolInput.cwd : typeof toolInput.workdir === "string" ? toolInput.workdir : typeof toolInput.workingDirectory === "string" ? toolInput.workingDirectory : null;
|
|
19391
19789
|
if (!raw) return cwd;
|
|
19392
|
-
return (0,
|
|
19790
|
+
return (0, import_path8.isAbsolute)(raw) || !cwd ? raw : (0, import_path8.join)(cwd, raw);
|
|
19393
19791
|
}
|
|
19394
19792
|
function extractBrowserMediaPathHints(content, cwd, toolName, options = {}, kind = "video") {
|
|
19395
19793
|
if (!isAgentBrowserMediaInvocation(toolName, content, options.toolInput, kind)) return [];
|
|
@@ -19432,19 +19830,19 @@ function extractGeneratedImagesFromToolResult(toolId, content, cwd, toolName, op
|
|
|
19432
19830
|
const effectiveCwd = resolveToolCwd(cwd, options.toolInput);
|
|
19433
19831
|
for (const candidate of extractPathCandidates(content, options)) {
|
|
19434
19832
|
const path = normalizePath(candidate, effectiveCwd);
|
|
19435
|
-
if (!path || seen.has(path) || !(0,
|
|
19833
|
+
if (!path || seen.has(path) || !(0, import_fs8.existsSync)(path)) continue;
|
|
19436
19834
|
seen.add(path);
|
|
19437
|
-
const ext = (0,
|
|
19835
|
+
const ext = (0, import_path8.extname)(path).toLowerCase();
|
|
19438
19836
|
const mimeType = IMAGE_MIME_BY_EXT[ext];
|
|
19439
19837
|
if (!mimeType) continue;
|
|
19440
|
-
const stat = (0,
|
|
19838
|
+
const stat = (0, import_fs8.statSync)(path);
|
|
19441
19839
|
if (!stat.isFile() || stat.size <= 0 || stat.size > MAX_GENERATED_IMAGE_BYTES) continue;
|
|
19442
|
-
const buffer = (0,
|
|
19840
|
+
const buffer = (0, import_fs8.readFileSync)(path);
|
|
19443
19841
|
const { width, height } = readImageDimensions2(buffer, mimeType);
|
|
19444
19842
|
images.push({
|
|
19445
19843
|
type: "generated_image",
|
|
19446
19844
|
id: (0, import_crypto3.randomUUID)(),
|
|
19447
|
-
filename: (0,
|
|
19845
|
+
filename: (0, import_path8.basename)(path),
|
|
19448
19846
|
mimeType,
|
|
19449
19847
|
size: buffer.length,
|
|
19450
19848
|
width,
|
|
@@ -19471,20 +19869,20 @@ function extractSessionMediaFromToolResult(toolId, content, cwd, toolName, optio
|
|
|
19471
19869
|
const mimeByExt = kind === "video" ? VIDEO_MIME_BY_EXT : IMAGE_MIME_BY_EXT;
|
|
19472
19870
|
for (const candidate of extractPathCandidates(content, options, extensionsPattern)) {
|
|
19473
19871
|
const path = normalizePath(candidate, effectiveCwd);
|
|
19474
|
-
if (!path || seen.has(path) || !(0,
|
|
19872
|
+
if (!path || seen.has(path) || !(0, import_fs8.existsSync)(path)) continue;
|
|
19475
19873
|
seen.add(path);
|
|
19476
|
-
const ext = (0,
|
|
19874
|
+
const ext = (0, import_path8.extname)(path).toLowerCase();
|
|
19477
19875
|
const mimeType = mimeByExt[ext];
|
|
19478
19876
|
if (!mimeType) continue;
|
|
19479
|
-
const stat = (0,
|
|
19877
|
+
const stat = (0, import_fs8.statSync)(path);
|
|
19480
19878
|
if (!stat.isFile() || stat.size <= 0 || stat.size > maxBytes) continue;
|
|
19481
|
-
const buffer = (0,
|
|
19879
|
+
const buffer = (0, import_fs8.readFileSync)(path);
|
|
19482
19880
|
const dimensions = kind === "image" ? readImageDimensions2(buffer, mimeType) : null;
|
|
19483
19881
|
media.push({
|
|
19484
19882
|
type: "session_media",
|
|
19485
19883
|
kind,
|
|
19486
19884
|
id: (0, import_crypto3.randomUUID)(),
|
|
19487
|
-
filename: (0,
|
|
19885
|
+
filename: (0, import_path8.basename)(path),
|
|
19488
19886
|
mimeType,
|
|
19489
19887
|
size: buffer.length,
|
|
19490
19888
|
width: dimensions?.width,
|
|
@@ -19648,6 +20046,10 @@ var CoreAgent = class _CoreAgent extends BaseMachineAgent {
|
|
|
19648
20046
|
if (!this.childProcess) return false;
|
|
19649
20047
|
const stdin = this.childProcess.stdin;
|
|
19650
20048
|
if (!stdin || stdin.destroyed) return false;
|
|
20049
|
+
if (this.presenter.hasKnownToolUse && !this.presenter.hasKnownToolUse(toolId)) {
|
|
20050
|
+
console.warn(`[CoreAgent] Ignoring tool_response for unknown tool ${toolId}`);
|
|
20051
|
+
return false;
|
|
20052
|
+
}
|
|
19651
20053
|
try {
|
|
19652
20054
|
let message;
|
|
19653
20055
|
if (this.presenter.pendingAskUserToolIds?.has(toolId)) {
|
|
@@ -19902,6 +20304,9 @@ function decodeKey(encodedKey) {
|
|
|
19902
20304
|
function isTerminal(event) {
|
|
19903
20305
|
return TERMINAL_EVENT_TYPES.has(event.type);
|
|
19904
20306
|
}
|
|
20307
|
+
function isBackgroundHeartbeat(event) {
|
|
20308
|
+
return event.type === "activity" && event.status === "background_work";
|
|
20309
|
+
}
|
|
19905
20310
|
function truncateUtf8(value2, maxBytes) {
|
|
19906
20311
|
const encoded = Buffer.from(value2, "utf8");
|
|
19907
20312
|
if (encoded.length <= maxBytes) return value2;
|
|
@@ -20050,6 +20455,23 @@ var EncryptedEventOutbox = class {
|
|
|
20050
20455
|
isDroppable(entry) {
|
|
20051
20456
|
return entry.eventId !== this.inFlightEventId && entry.kind !== "condensation_marker" && !isTerminal(entry.event);
|
|
20052
20457
|
}
|
|
20458
|
+
/**
|
|
20459
|
+
* Pick the next droppable entry to evict. Heartbeats are always evicted
|
|
20460
|
+
* first (oldest heartbeat first) since they carry no transcript content;
|
|
20461
|
+
* only once none remain do we fall back to the oldest droppable entry of
|
|
20462
|
+
* any kind. Without this, oldest-first eviction treats a background_work
|
|
20463
|
+
* heartbeat exactly like real transcript content, so a long offline stretch
|
|
20464
|
+
* (heartbeats every 10s, never removed by an ack since nothing is
|
|
20465
|
+
* connected) evicts genuine early events — including `start` — ahead of
|
|
20466
|
+
* the heartbeats that caused the overflow.
|
|
20467
|
+
*/
|
|
20468
|
+
pickDropCandidate(matches) {
|
|
20469
|
+
const heartbeat = this.entries.find(
|
|
20470
|
+
(entry) => matches(entry) && this.isDroppable(entry) && isBackgroundHeartbeat(entry.event)
|
|
20471
|
+
);
|
|
20472
|
+
if (heartbeat) return heartbeat;
|
|
20473
|
+
return this.entries.find((entry) => matches(entry) && this.isDroppable(entry));
|
|
20474
|
+
}
|
|
20053
20475
|
ensureCondensationMarker(runId, conversationId) {
|
|
20054
20476
|
if (this.entries.some((entry) => entry.runId === runId && entry.kind === "condensation_marker")) {
|
|
20055
20477
|
return;
|
|
@@ -20077,7 +20499,7 @@ var EncryptedEventOutbox = class {
|
|
|
20077
20499
|
const runIds = preferredRunId ? [preferredRunId] : [...new Set(this.entries.map((entry) => entry.runId))];
|
|
20078
20500
|
for (const runId of runIds) {
|
|
20079
20501
|
while (this.entries.filter((entry) => entry.runId === runId && this.isDroppable(entry)).length > this.maxIntermediateEventsPerRun) {
|
|
20080
|
-
const drop = this.
|
|
20502
|
+
const drop = this.pickDropCandidate((entry) => entry.runId === runId);
|
|
20081
20503
|
if (!drop) break;
|
|
20082
20504
|
this.entries = this.entries.filter((entry) => entry.eventId !== drop.eventId);
|
|
20083
20505
|
lossCountByRunId.set(runId, (lossCountByRunId.get(runId) ?? 0) + 1);
|
|
@@ -20085,7 +20507,7 @@ var EncryptedEventOutbox = class {
|
|
|
20085
20507
|
}
|
|
20086
20508
|
}
|
|
20087
20509
|
while (encryptedFileSize(this.entries) > this.maxEncryptedBytes) {
|
|
20088
|
-
const drop = this.
|
|
20510
|
+
const drop = this.pickDropCandidate(() => true);
|
|
20089
20511
|
if (drop) {
|
|
20090
20512
|
this.entries = this.entries.filter((entry) => entry.eventId !== drop.eventId);
|
|
20091
20513
|
lossCountByRunId.set(drop.runId, (lossCountByRunId.get(drop.runId) ?? 0) + 1);
|
|
@@ -20104,7 +20526,7 @@ var EncryptedEventOutbox = class {
|
|
|
20104
20526
|
this.ensureCondensationMarker(runId, lossConversationByRunId.get(runId));
|
|
20105
20527
|
}
|
|
20106
20528
|
while (encryptedFileSize(this.entries) > this.maxEncryptedBytes) {
|
|
20107
|
-
const drop = this.
|
|
20529
|
+
const drop = this.pickDropCandidate(() => true);
|
|
20108
20530
|
if (!drop) break;
|
|
20109
20531
|
this.entries = this.entries.filter((entry) => entry.eventId !== drop.eventId);
|
|
20110
20532
|
lossCountByRunId.set(drop.runId, (lossCountByRunId.get(drop.runId) ?? 0) + 1);
|
|
@@ -21029,6 +21451,10 @@ function assertValidExistingToml(path, content) {
|
|
|
21029
21451
|
`Existing config is malformed; no changes were applied. Review the backup at ${backupPath}`
|
|
21030
21452
|
);
|
|
21031
21453
|
}
|
|
21454
|
+
var CODEX_ALAN_TOML_SECTION = "mcp_servers.alan";
|
|
21455
|
+
function isCodexAlanTomlSection(section) {
|
|
21456
|
+
return section === CODEX_ALAN_TOML_SECTION || section.startsWith(`${CODEX_ALAN_TOML_SECTION}.`);
|
|
21457
|
+
}
|
|
21032
21458
|
function mergeCodexAlanSection(path, existingContent, desiredContent) {
|
|
21033
21459
|
if (existingContent === null || existingContent.trim() === "") return desiredContent;
|
|
21034
21460
|
assertValidExistingToml(path, existingContent);
|
|
@@ -21038,7 +21464,7 @@ function mergeCodexAlanSection(path, existingContent, desiredContent) {
|
|
|
21038
21464
|
for (const line of lines) {
|
|
21039
21465
|
const section = line.trim().match(/^\[([^\]]+)]$/)?.[1];
|
|
21040
21466
|
if (section) {
|
|
21041
|
-
insideAlanSection = section
|
|
21467
|
+
insideAlanSection = isCodexAlanTomlSection(section);
|
|
21042
21468
|
}
|
|
21043
21469
|
if (!insideAlanSection) retained.push(line);
|
|
21044
21470
|
}
|
|
@@ -21824,7 +22250,7 @@ var RunStartGate = class {
|
|
|
21824
22250
|
};
|
|
21825
22251
|
|
|
21826
22252
|
// src/version.ts
|
|
21827
|
-
var AGENT_VERSION = "0.1.
|
|
22253
|
+
var AGENT_VERSION = "0.1.38";
|
|
21828
22254
|
|
|
21829
22255
|
// src/workspace-relocation.ts
|
|
21830
22256
|
var import_node_child_process3 = require("child_process");
|
|
@@ -23180,14 +23606,29 @@ var USER_INTERRUPTED_RESULT = {
|
|
|
23180
23606
|
filesModified: [],
|
|
23181
23607
|
planFilesCreated: [],
|
|
23182
23608
|
iterations: 0,
|
|
23183
|
-
error: "Interrupted by user"
|
|
23609
|
+
error: "Interrupted by user",
|
|
23610
|
+
errorKind: "user"
|
|
23184
23611
|
};
|
|
23612
|
+
function buildAbortResult(reason) {
|
|
23613
|
+
if (reason === "user") return USER_INTERRUPTED_RESULT;
|
|
23614
|
+
const message = SYSTEM_ABORT_RUN_ERRORS[reason];
|
|
23615
|
+
return {
|
|
23616
|
+
success: false,
|
|
23617
|
+
summary: message,
|
|
23618
|
+
filesModified: [],
|
|
23619
|
+
planFilesCreated: [],
|
|
23620
|
+
iterations: 0,
|
|
23621
|
+
error: message,
|
|
23622
|
+
errorKind: reason
|
|
23623
|
+
};
|
|
23624
|
+
}
|
|
23185
23625
|
function abortActiveAgent(entry, options) {
|
|
23186
23626
|
const awaitingAsk = (entry.presenter.pendingAskUserToolIds?.size ?? 0) > 0;
|
|
23187
23627
|
if (awaitingAsk && !options?.userInitiated) {
|
|
23188
23628
|
return;
|
|
23189
23629
|
}
|
|
23190
|
-
|
|
23630
|
+
const reason = options?.reason ?? (options?.userInitiated ? "user" : "runner_stalled");
|
|
23631
|
+
entry.presenter.onComplete(buildAbortResult(reason));
|
|
23191
23632
|
entry.agent.kill();
|
|
23192
23633
|
}
|
|
23193
23634
|
function isProviderProcessAlive(pid) {
|
|
@@ -23296,7 +23737,11 @@ function flushPendingDeliveryLostEvents(input) {
|
|
|
23296
23737
|
if (input.pending.length === 0) return;
|
|
23297
23738
|
for (const item of input.pending) {
|
|
23298
23739
|
if (input.eventOutbox.hasTerminalEvent(item.runId)) continue;
|
|
23299
|
-
|
|
23740
|
+
const activeEntry = input.activeAgents.get(item.runId);
|
|
23741
|
+
if (activeEntry) {
|
|
23742
|
+
if (activeEntry.stage === "delivery_lost") activeEntry.stage = "running";
|
|
23743
|
+
continue;
|
|
23744
|
+
}
|
|
23300
23745
|
input.eventOutbox.enqueue(item.conversationId, item.runId, {
|
|
23301
23746
|
type: "session_error",
|
|
23302
23747
|
error: DELIVERY_LOST_ERROR
|
|
@@ -23320,7 +23765,7 @@ function reconcileActiveRuns(input) {
|
|
|
23320
23765
|
input.pushLog?.(`reconciled dead provider pid run=${runId} (awaiting ask, no interrupt)`);
|
|
23321
23766
|
continue;
|
|
23322
23767
|
}
|
|
23323
|
-
abortActiveAgent(entry);
|
|
23768
|
+
abortActiveAgent(entry, { reason: staleByDeadPid ? "provider_exited" : "runner_stalled" });
|
|
23324
23769
|
input.activeAgents.delete(runId);
|
|
23325
23770
|
if (staleByDeadPid) {
|
|
23326
23771
|
input.pushLog?.(`reconciled dead provider pid run=${runId}`);
|
|
@@ -23384,6 +23829,7 @@ var RuntimePresenter = class {
|
|
|
23384
23829
|
emitEvent(event) {
|
|
23385
23830
|
if (this.terminalFenced) return;
|
|
23386
23831
|
this.touchRunnerActivity?.();
|
|
23832
|
+
if (isBackgroundHeartbeat(event) && !this.socket.connected) return;
|
|
23387
23833
|
this.eventOutbox.enqueue(this.conversationId, this.runId, event);
|
|
23388
23834
|
this.eventOutbox.flush(this.socket);
|
|
23389
23835
|
}
|
|
@@ -23411,6 +23857,9 @@ var RuntimePresenter = class {
|
|
|
23411
23857
|
onLog(message) {
|
|
23412
23858
|
console.error(message);
|
|
23413
23859
|
}
|
|
23860
|
+
onNotice(kind, message) {
|
|
23861
|
+
this.emitEvent({ type: "session_notice", kind, message, ts: Date.now() });
|
|
23862
|
+
}
|
|
23414
23863
|
onAssistantText(text) {
|
|
23415
23864
|
this.emitEvent({ type: "text", text, ts: Date.now() });
|
|
23416
23865
|
}
|
|
@@ -23510,8 +23959,8 @@ var RuntimePresenter = class {
|
|
|
23510
23959
|
onCheckpoint(id) {
|
|
23511
23960
|
this.emitEvent({ type: "checkpoint", id });
|
|
23512
23961
|
}
|
|
23513
|
-
onActivity(status) {
|
|
23514
|
-
this.emitEvent({ type: "activity", status, ts: Date.now() });
|
|
23962
|
+
onActivity(status, evidence) {
|
|
23963
|
+
this.emitEvent({ type: "activity", status, ts: Date.now(), ...evidence ? { evidence } : {} });
|
|
23515
23964
|
}
|
|
23516
23965
|
onOpenBrowserTab(browserId, url2) {
|
|
23517
23966
|
this.emitEvent({ type: "open_browser_tab", browserId, ...url2 ? { url: url2 } : {} });
|
|
@@ -24297,16 +24746,22 @@ async function startDaemon(args) {
|
|
|
24297
24746
|
flushWorkspaceRelocations();
|
|
24298
24747
|
}
|
|
24299
24748
|
}
|
|
24300
|
-
if (workspaceReadiness.ready && expectedRepoUrls.length > 0 && !isAlanDefaultWorkspace(cwd)
|
|
24301
|
-
|
|
24302
|
-
|
|
24303
|
-
|
|
24304
|
-
|
|
24305
|
-
|
|
24306
|
-
|
|
24307
|
-
|
|
24308
|
-
|
|
24309
|
-
|
|
24749
|
+
if (workspaceReadiness.ready && expectedRepoUrls.length > 0 && !isAlanDefaultWorkspace(cwd)) {
|
|
24750
|
+
const worktreePaths = payload.worktreeInfo?.allWorktrees?.map((worktree) => worktree.worktreePath).filter((worktreePath) => Boolean(worktreePath));
|
|
24751
|
+
const repositoryMismatch = worktreePaths && worktreePaths.length > 0 ? worktreePaths.some(
|
|
24752
|
+
(worktreePath) => !workspaceMatchesExpectedRepositories({
|
|
24753
|
+
workspacePath: worktreePath,
|
|
24754
|
+
expectedRepoUrls
|
|
24755
|
+
})
|
|
24756
|
+
) : !workspaceMatchesExpectedRepositories({ workspacePath: cwd, expectedRepoUrls });
|
|
24757
|
+
if (repositoryMismatch) {
|
|
24758
|
+
socket.emit("agent.rejected", {
|
|
24759
|
+
runId: payload.runId,
|
|
24760
|
+
message: "This folder belongs to a different repository. Choose the correct project folder and try again.",
|
|
24761
|
+
code: "workspace_repository_mismatch"
|
|
24762
|
+
});
|
|
24763
|
+
return;
|
|
24764
|
+
}
|
|
24310
24765
|
}
|
|
24311
24766
|
if (workspaceRequired) workspaceAccessTracker.track(cwd, workspaceReadiness);
|
|
24312
24767
|
const runnerSummary = summarizeRunnerStatus({
|
|
@@ -24604,6 +25059,7 @@ async function startDaemon(args) {
|
|
|
24604
25059
|
selectedContextWindow: payload.selectedContextWindow ?? null,
|
|
24605
25060
|
selectedEffortLevel: payload.selectedEffortLevel ?? null,
|
|
24606
25061
|
providerSessionId: payload.providerSessionId,
|
|
25062
|
+
resumeFallbackContext: payload.resumeFallbackContext,
|
|
24607
25063
|
taskMeta: payload.taskMeta,
|
|
24608
25064
|
prMeta: payload.prMeta,
|
|
24609
25065
|
conversationId: payload.conversationId,
|
|
@@ -24640,11 +25096,13 @@ async function startDaemon(args) {
|
|
|
24640
25096
|
if (!payload?.runId) return;
|
|
24641
25097
|
const entry = activeAgents.get(payload.runId);
|
|
24642
25098
|
if (!entry) return;
|
|
24643
|
-
|
|
25099
|
+
const reason = payload.reason === "watchdog_timeout" ? "watchdog_timeout" : "user";
|
|
25100
|
+
abortActiveAgent(entry, { userInitiated: true, reason });
|
|
24644
25101
|
activeAgents.delete(payload.runId);
|
|
24645
|
-
pushLog(`aborted run=${payload.runId}`);
|
|
25102
|
+
pushLog(`aborted run=${payload.runId} reason=${reason}`);
|
|
24646
25103
|
console.info("[alan-agent] Agent run aborted", {
|
|
24647
25104
|
runId: payload.runId,
|
|
25105
|
+
reason,
|
|
24648
25106
|
agentVersion: AGENT_VERSION
|
|
24649
25107
|
});
|
|
24650
25108
|
});
|
|
@@ -24652,7 +25110,9 @@ async function startDaemon(args) {
|
|
|
24652
25110
|
"agent.tool_response",
|
|
24653
25111
|
(payload) => {
|
|
24654
25112
|
if (!payload?.toolId || typeof payload.response !== "string") return;
|
|
24655
|
-
const entries = payload.runId ? [[payload.runId, activeAgents.get(payload.runId)]] : [...activeAgents.entries()]
|
|
25113
|
+
const entries = payload.runId ? [[payload.runId, activeAgents.get(payload.runId)]] : payload.conversationId ? [...activeAgents.entries()].filter(
|
|
25114
|
+
([, entry]) => entry.conversationId === payload.conversationId
|
|
25115
|
+
) : [...activeAgents.entries()];
|
|
24656
25116
|
const delivered = entries.some(([runId, entry]) => {
|
|
24657
25117
|
if (!entry || !verifyActiveWorkspaceLease(runId, entry)) return false;
|
|
24658
25118
|
return entry.agent.sendToolResponse(payload.toolId, payload.response) === true;
|
|
@@ -24660,8 +25120,8 @@ async function startDaemon(args) {
|
|
|
24660
25120
|
if (!delivered) {
|
|
24661
25121
|
pushLog(`tool_response missed tool=${payload.toolId}`);
|
|
24662
25122
|
const fallbackRun = [...activeAgents.entries()][0];
|
|
24663
|
-
const resolvedRunId = payload.runId ?? fallbackRun?.[0];
|
|
24664
|
-
const conversationId = (payload.runId ? activeAgents.get(payload.runId)?.conversationId : void 0) ?? fallbackRun?.[1].conversationId;
|
|
25123
|
+
const resolvedRunId = payload.runId ?? entries[0]?.[0] ?? fallbackRun?.[0];
|
|
25124
|
+
const conversationId = payload.conversationId ?? (payload.runId ? activeAgents.get(payload.runId)?.conversationId : void 0) ?? fallbackRun?.[1].conversationId;
|
|
24665
25125
|
if (conversationId && resolvedRunId) {
|
|
24666
25126
|
eventOutbox.enqueue(conversationId, resolvedRunId, {
|
|
24667
25127
|
type: "error",
|
|
@@ -25444,12 +25904,18 @@ var WebPresenter = class {
|
|
|
25444
25904
|
onLog(message) {
|
|
25445
25905
|
console.error(message);
|
|
25446
25906
|
}
|
|
25907
|
+
onNotice(kind, message) {
|
|
25908
|
+
this.wsClient.emitEvent({ type: "session_notice", kind, message, ts: Date.now() });
|
|
25909
|
+
}
|
|
25447
25910
|
onAssistantText(text) {
|
|
25448
25911
|
this.wsClient.emitEvent({ type: "text", text, ts: Date.now() });
|
|
25449
25912
|
}
|
|
25450
25913
|
onThinking(text) {
|
|
25451
25914
|
this.wsClient.emitEvent({ type: "thinking", text, ts: Date.now() });
|
|
25452
25915
|
}
|
|
25916
|
+
hasKnownToolUse(toolId) {
|
|
25917
|
+
return this.toolNamesById.has(toolId);
|
|
25918
|
+
}
|
|
25453
25919
|
onToolUse(tool, input, toolId, parentToolUseId) {
|
|
25454
25920
|
this.toolNamesById.set(toolId, tool);
|
|
25455
25921
|
this.toolInputsById.set(toolId, input);
|
|
@@ -25541,8 +26007,13 @@ var WebPresenter = class {
|
|
|
25541
26007
|
onCheckpoint(id) {
|
|
25542
26008
|
this.wsClient.emitEvent({ type: "checkpoint", id });
|
|
25543
26009
|
}
|
|
25544
|
-
onActivity(status) {
|
|
25545
|
-
this.wsClient.emitEvent({
|
|
26010
|
+
onActivity(status, evidence) {
|
|
26011
|
+
this.wsClient.emitEvent({
|
|
26012
|
+
type: "activity",
|
|
26013
|
+
status,
|
|
26014
|
+
ts: Date.now(),
|
|
26015
|
+
...evidence ? { evidence } : {}
|
|
26016
|
+
});
|
|
25546
26017
|
}
|
|
25547
26018
|
onOpenBrowserTab(browserId, url2) {
|
|
25548
26019
|
this.wsClient.emitEvent({ type: "open_browser_tab", browserId, ...url2 ? { url: url2 } : {} });
|
|
@@ -25583,7 +26054,7 @@ var WebPresenter = class {
|
|
|
25583
26054
|
};
|
|
25584
26055
|
|
|
25585
26056
|
// src/ws-client.ts
|
|
25586
|
-
var
|
|
26057
|
+
var import_node_crypto5 = require("crypto");
|
|
25587
26058
|
|
|
25588
26059
|
// ../shared/dist/agent-liveness.js
|
|
25589
26060
|
var AGENT_LIVENESS_PROTOCOL_VERSION = 1;
|
|
@@ -25610,7 +26081,117 @@ var agentProbeAckSchema = external_exports.object({
|
|
|
25610
26081
|
protocolVersion: external_exports.number().int().positive().optional()
|
|
25611
26082
|
});
|
|
25612
26083
|
|
|
26084
|
+
// src/sandbox-outbox.ts
|
|
26085
|
+
var import_node_crypto4 = require("crypto");
|
|
26086
|
+
var import_node_fs13 = require("fs");
|
|
26087
|
+
var SANDBOX_EVENT_OUTBOX_ENV = "ALAN_EVENT_OUTBOX_ENABLED";
|
|
26088
|
+
function isSandboxEventOutboxEnabled(env = process.env) {
|
|
26089
|
+
return env[SANDBOX_EVENT_OUTBOX_ENV] === "true";
|
|
26090
|
+
}
|
|
26091
|
+
function deriveSandboxOutboxKey(sessionToken) {
|
|
26092
|
+
if (!sessionToken) {
|
|
26093
|
+
throw new Error("sandbox event outbox requires ALAN_SESSION_TOKEN for key derivation");
|
|
26094
|
+
}
|
|
26095
|
+
return (0, import_node_crypto4.createHash)("sha256").update(sessionToken, "utf8").digest("base64url");
|
|
26096
|
+
}
|
|
26097
|
+
function sandboxOutboxSpoolPath(conversationId) {
|
|
26098
|
+
return `/tmp/alan-agent-outbox-${conversationId}.enc`;
|
|
26099
|
+
}
|
|
26100
|
+
function createSandboxEventOutbox(input) {
|
|
26101
|
+
const path = input.spoolPath ?? sandboxOutboxSpoolPath(input.conversationId);
|
|
26102
|
+
const key = deriveSandboxOutboxKey(input.sessionToken);
|
|
26103
|
+
try {
|
|
26104
|
+
return new EncryptedEventOutbox(path, key, input.pushLog, input.options);
|
|
26105
|
+
} catch (error) {
|
|
26106
|
+
input.pushLog?.(
|
|
26107
|
+
`sandbox_outbox_spool_reset ${error instanceof Error ? error.message : String(error)}`
|
|
26108
|
+
);
|
|
26109
|
+
(0, import_node_fs13.rmSync)(path, { force: true });
|
|
26110
|
+
return new EncryptedEventOutbox(path, key, input.pushLog, input.options);
|
|
26111
|
+
}
|
|
26112
|
+
}
|
|
26113
|
+
var SandboxOutboxSocket = class {
|
|
26114
|
+
constructor(socket) {
|
|
26115
|
+
this.socket = socket;
|
|
26116
|
+
}
|
|
26117
|
+
seqByEventId = /* @__PURE__ */ new Map();
|
|
26118
|
+
seqCounter = 0;
|
|
26119
|
+
get connected() {
|
|
26120
|
+
return this.socket.connected;
|
|
26121
|
+
}
|
|
26122
|
+
emit(_event, payload, acknowledge) {
|
|
26123
|
+
let seq = this.seqByEventId.get(payload.eventId);
|
|
26124
|
+
if (seq === void 0) {
|
|
26125
|
+
this.seqCounter += 1;
|
|
26126
|
+
seq = this.seqCounter;
|
|
26127
|
+
this.seqByEventId.set(payload.eventId, seq);
|
|
26128
|
+
}
|
|
26129
|
+
const wirePayload = { ...payload.event, eventId: payload.eventId, seq };
|
|
26130
|
+
this.socket.emit("agent_event", wirePayload, (result) => {
|
|
26131
|
+
if (result?.ok) this.seqByEventId.delete(payload.eventId);
|
|
26132
|
+
acknowledge({ ok: Boolean(result?.ok), retryable: result?.retryable });
|
|
26133
|
+
});
|
|
26134
|
+
}
|
|
26135
|
+
};
|
|
26136
|
+
var SandboxEventDispatcher = class {
|
|
26137
|
+
conversationId;
|
|
26138
|
+
outbox;
|
|
26139
|
+
outboxSocket;
|
|
26140
|
+
currentRunId;
|
|
26141
|
+
constructor(input) {
|
|
26142
|
+
this.conversationId = input.conversationId;
|
|
26143
|
+
if (!input.enabled) return;
|
|
26144
|
+
try {
|
|
26145
|
+
this.outbox = createSandboxEventOutbox({
|
|
26146
|
+
conversationId: input.conversationId,
|
|
26147
|
+
sessionToken: input.sessionToken,
|
|
26148
|
+
pushLog: input.pushLog,
|
|
26149
|
+
options: input.options,
|
|
26150
|
+
spoolPath: input.spoolPath
|
|
26151
|
+
});
|
|
26152
|
+
this.outboxSocket = new SandboxOutboxSocket(input.socket);
|
|
26153
|
+
} catch (error) {
|
|
26154
|
+
input.onInitError?.(error);
|
|
26155
|
+
this.outbox = void 0;
|
|
26156
|
+
this.outboxSocket = void 0;
|
|
26157
|
+
}
|
|
26158
|
+
}
|
|
26159
|
+
/** True when events are being durably queued rather than fired-and-forgotten. */
|
|
26160
|
+
get active() {
|
|
26161
|
+
return this.outbox !== void 0 && this.outboxSocket !== void 0;
|
|
26162
|
+
}
|
|
26163
|
+
/** Set the run id used to bucket per-run caps / terminal condensation. */
|
|
26164
|
+
setRunId(runId) {
|
|
26165
|
+
this.currentRunId = runId;
|
|
26166
|
+
}
|
|
26167
|
+
/**
|
|
26168
|
+
* Durably deliver one event (enqueue-before-send, remove-on-ack). When the
|
|
26169
|
+
* outbox is inactive, defers to `fireAndForget` so behaviour is unchanged.
|
|
26170
|
+
*/
|
|
26171
|
+
emit(event, fireAndForget) {
|
|
26172
|
+
if (!this.outbox || !this.outboxSocket) {
|
|
26173
|
+
fireAndForget(event);
|
|
26174
|
+
return;
|
|
26175
|
+
}
|
|
26176
|
+
this.outbox.enqueue(this.conversationId, this.currentRunId ?? this.conversationId, event);
|
|
26177
|
+
this.outbox.flush(this.outboxSocket);
|
|
26178
|
+
}
|
|
26179
|
+
/** Replay un-acked events in order — called on (re)connect before new events. */
|
|
26180
|
+
flush() {
|
|
26181
|
+
if (this.outbox && this.outboxSocket) this.outbox.flush(this.outboxSocket);
|
|
26182
|
+
}
|
|
26183
|
+
/** Reset in-flight state on disconnect so the next flush replays from the head. */
|
|
26184
|
+
disconnect() {
|
|
26185
|
+
this.outbox?.disconnect();
|
|
26186
|
+
}
|
|
26187
|
+
/** Diagnostics: number of events still awaiting a server ack. */
|
|
26188
|
+
pendingCount() {
|
|
26189
|
+
return this.outbox?.pendingCount() ?? 0;
|
|
26190
|
+
}
|
|
26191
|
+
};
|
|
26192
|
+
|
|
25613
26193
|
// src/ws-client.ts
|
|
26194
|
+
var ACCEPTED_MESSAGE_ID_CACHE_SIZE = 200;
|
|
25614
26195
|
var WSClient = class {
|
|
25615
26196
|
constructor(wsUrl, sessionId, taskId, token, callbacks, agentVersion, lifecycle) {
|
|
25616
26197
|
this.lifecycle = lifecycle;
|
|
@@ -25629,16 +26210,63 @@ var WSClient = class {
|
|
|
25629
26210
|
upgrade: false,
|
|
25630
26211
|
extraHeaders: { "ngrok-skip-browser-warning": "1" }
|
|
25631
26212
|
});
|
|
26213
|
+
const outboxEnabled = isSandboxEventOutboxEnabled();
|
|
26214
|
+
this.eventDispatcher = new SandboxEventDispatcher({
|
|
26215
|
+
conversationId: sessionId,
|
|
26216
|
+
sessionToken: token,
|
|
26217
|
+
enabled: outboxEnabled,
|
|
26218
|
+
socket: this.socket,
|
|
26219
|
+
pushLog: (message) => this.lifecycle?.info("sandbox_agent_event_outbox", { detail: message }),
|
|
26220
|
+
onInitError: (error) => this.lifecycle?.warn("sandbox_agent_event_outbox_init_failed", {
|
|
26221
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
26222
|
+
})
|
|
26223
|
+
});
|
|
26224
|
+
if (this.eventDispatcher.active) {
|
|
26225
|
+
this.lifecycle?.info("sandbox_agent_event_outbox_enabled", { conversationId: sessionId });
|
|
26226
|
+
}
|
|
25632
26227
|
this.setupListeners(callbacks);
|
|
25633
26228
|
}
|
|
25634
26229
|
socket;
|
|
25635
26230
|
/** Stable id for this agent process; fences stale heartbeats server-side. */
|
|
25636
|
-
agentSessionId = (0,
|
|
26231
|
+
agentSessionId = (0, import_node_crypto5.randomUUID)();
|
|
25637
26232
|
/** Monotonic heartbeat sequence — advances on every hello + heartbeat. */
|
|
25638
26233
|
heartbeatSeq = 0;
|
|
25639
26234
|
heartbeatTimer = null;
|
|
25640
26235
|
agentVersion;
|
|
25641
26236
|
getRunState;
|
|
26237
|
+
/**
|
|
26238
|
+
* Durable at-least-once delivery for stream events. Active only when
|
|
26239
|
+
* `ALAN_EVENT_OUTBOX_ENABLED === "true"`; otherwise events are fired-and-
|
|
26240
|
+
* forgotten exactly as before (see {@link SandboxEventDispatcher}).
|
|
26241
|
+
*/
|
|
26242
|
+
eventDispatcher;
|
|
26243
|
+
/**
|
|
26244
|
+
* Bounded record of `messageId`s whose handoff to `onUserMessage` already
|
|
26245
|
+
* completed successfully. A redelivered `user_message` for one of these ids
|
|
26246
|
+
* (e.g. the original `ok:true` ack was lost in transit and the server's
|
|
26247
|
+
* ≤5-attempt redelivery re-armed the run) is re-acked `{ ok: true }`
|
|
26248
|
+
* without invoking the handler again, so the message is never processed
|
|
26249
|
+
* twice. A `Set`'s insertion order doubles as recency, so eviction is just
|
|
26250
|
+
* "drop the oldest key" — a minimal LRU without extra bookkeeping.
|
|
26251
|
+
*/
|
|
26252
|
+
acceptedMessageIds = /* @__PURE__ */ new Set();
|
|
26253
|
+
/** Record a successfully-handed-off messageId, evicting the oldest entry past the cap. */
|
|
26254
|
+
rememberAcceptedMessageId(messageId) {
|
|
26255
|
+
this.acceptedMessageIds.delete(messageId);
|
|
26256
|
+
this.acceptedMessageIds.add(messageId);
|
|
26257
|
+
if (this.acceptedMessageIds.size > ACCEPTED_MESSAGE_ID_CACHE_SIZE) {
|
|
26258
|
+
const oldest = this.acceptedMessageIds.keys().next().value;
|
|
26259
|
+
if (oldest !== void 0) this.acceptedMessageIds.delete(oldest);
|
|
26260
|
+
}
|
|
26261
|
+
}
|
|
26262
|
+
/**
|
|
26263
|
+
* Record the run id for events emitted from here on, so the durable outbox can
|
|
26264
|
+
* bucket per-run caps and terminal-event condensation correctly. No-op when the
|
|
26265
|
+
* outbox is disabled.
|
|
26266
|
+
*/
|
|
26267
|
+
setCurrentRunId(runId) {
|
|
26268
|
+
this.eventDispatcher.setRunId(runId);
|
|
26269
|
+
}
|
|
25642
26270
|
currentRunState() {
|
|
25643
26271
|
try {
|
|
25644
26272
|
return this.getRunState?.() ?? { idle: true, activeRunIds: [] };
|
|
@@ -25675,7 +26303,9 @@ var WSClient = class {
|
|
|
25675
26303
|
}
|
|
25676
26304
|
}
|
|
25677
26305
|
emitEvent(event) {
|
|
25678
|
-
this.
|
|
26306
|
+
this.eventDispatcher.emit(event, (payload) => {
|
|
26307
|
+
this.socket.emit("agent_event", payload);
|
|
26308
|
+
});
|
|
25679
26309
|
}
|
|
25680
26310
|
waitForConnection(timeoutMs = 15e3) {
|
|
25681
26311
|
return new Promise((resolve6, reject) => {
|
|
@@ -25712,11 +26342,13 @@ var WSClient = class {
|
|
|
25712
26342
|
this.socket.on("connect", () => {
|
|
25713
26343
|
this.lifecycle?.info("sandbox_agent_ws_socket_connected", { socketId: this.socket.id });
|
|
25714
26344
|
this.startHeartbeat();
|
|
26345
|
+
this.eventDispatcher.flush();
|
|
25715
26346
|
callbacks.onConnect?.();
|
|
25716
26347
|
});
|
|
25717
26348
|
this.socket.on("disconnect", (reason) => {
|
|
25718
26349
|
this.lifecycle?.info("sandbox_agent_ws_socket_disconnected", { reason });
|
|
25719
26350
|
this.stopHeartbeat();
|
|
26351
|
+
this.eventDispatcher.disconnect();
|
|
25720
26352
|
callbacks.onDisconnect?.(reason);
|
|
25721
26353
|
});
|
|
25722
26354
|
this.socket.on("agent.probe", (_data, ack) => {
|
|
@@ -25734,8 +26366,11 @@ var WSClient = class {
|
|
|
25734
26366
|
this.socket.on(
|
|
25735
26367
|
"user_message",
|
|
25736
26368
|
(data, ack) => {
|
|
25737
|
-
|
|
25738
|
-
|
|
26369
|
+
if (data.messageId && this.acceptedMessageIds.has(data.messageId)) {
|
|
26370
|
+
ack?.({ ok: true });
|
|
26371
|
+
return;
|
|
26372
|
+
}
|
|
26373
|
+
const payload = {
|
|
25739
26374
|
text: data.text,
|
|
25740
26375
|
images: data.images,
|
|
25741
26376
|
files: data.files,
|
|
@@ -25759,7 +26394,24 @@ var WSClient = class {
|
|
|
25759
26394
|
conversationId: data.conversationId,
|
|
25760
26395
|
teamId: data.teamId,
|
|
25761
26396
|
currentUser: data.currentUser
|
|
25762
|
-
}
|
|
26397
|
+
};
|
|
26398
|
+
const acceptDelivery = () => {
|
|
26399
|
+
if (data.messageId) this.rememberAcceptedMessageId(data.messageId);
|
|
26400
|
+
ack?.({ ok: true });
|
|
26401
|
+
};
|
|
26402
|
+
const rejectDelivery = (error) => {
|
|
26403
|
+
ack?.({ ok: false, message: error instanceof Error ? error.message : String(error) });
|
|
26404
|
+
};
|
|
26405
|
+
try {
|
|
26406
|
+
const result = callbacks.onUserMessage(payload);
|
|
26407
|
+
if (result instanceof Promise) {
|
|
26408
|
+
result.then(acceptDelivery, rejectDelivery);
|
|
26409
|
+
} else {
|
|
26410
|
+
acceptDelivery();
|
|
26411
|
+
}
|
|
26412
|
+
} catch (error) {
|
|
26413
|
+
rejectDelivery(error);
|
|
26414
|
+
}
|
|
25763
26415
|
}
|
|
25764
26416
|
);
|
|
25765
26417
|
this.socket.on("stop", () => {
|
|
@@ -25928,6 +26580,7 @@ async function runSandbox(config) {
|
|
|
25928
26580
|
backendKind: activeBackendKind
|
|
25929
26581
|
});
|
|
25930
26582
|
currentAgent = agent;
|
|
26583
|
+
wsClient.setCurrentRunId(currentRunId ?? config.runId);
|
|
25931
26584
|
const correlation = correlationLogFields2({
|
|
25932
26585
|
taskId: currentTaskId,
|
|
25933
26586
|
conversationId: currentConversationId,
|
|
@@ -26145,7 +26798,7 @@ async function runSandbox(config) {
|
|
|
26145
26798
|
|
|
26146
26799
|
// src/service-manager.ts
|
|
26147
26800
|
var import_node_child_process6 = require("child_process");
|
|
26148
|
-
var
|
|
26801
|
+
var import_node_fs14 = require("fs");
|
|
26149
26802
|
var import_node_os8 = require("os");
|
|
26150
26803
|
var import_node_path11 = require("path");
|
|
26151
26804
|
var SERVICE_LABEL = "ai.tryalan.agent";
|
|
@@ -26344,14 +26997,14 @@ function currentServicePlan(args) {
|
|
|
26344
26997
|
});
|
|
26345
26998
|
}
|
|
26346
26999
|
function writeManifest(path, contents) {
|
|
26347
|
-
(0,
|
|
27000
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path11.dirname)(path), { recursive: true, mode: 448 });
|
|
26348
27001
|
const pendingPath = `${path}.pending-${process.pid}`;
|
|
26349
27002
|
try {
|
|
26350
|
-
(0,
|
|
26351
|
-
(0,
|
|
26352
|
-
(0,
|
|
27003
|
+
(0, import_node_fs14.writeFileSync)(pendingPath, contents, { mode: 384 });
|
|
27004
|
+
(0, import_node_fs14.chmodSync)(pendingPath, 384);
|
|
27005
|
+
(0, import_node_fs14.renameSync)(pendingPath, path);
|
|
26353
27006
|
} finally {
|
|
26354
|
-
(0,
|
|
27007
|
+
(0, import_node_fs14.rmSync)(pendingPath, { force: true });
|
|
26355
27008
|
}
|
|
26356
27009
|
}
|
|
26357
27010
|
function runServiceCommand(command) {
|
|
@@ -26374,7 +27027,7 @@ function installDaemonService(args = []) {
|
|
|
26374
27027
|
const plan = currentServicePlan(args);
|
|
26375
27028
|
if (plan.manifestPath && plan.manifest) {
|
|
26376
27029
|
if ((0, import_node_os8.platform)() === "darwin") {
|
|
26377
|
-
(0,
|
|
27030
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path11.join)((0, import_node_os8.homedir)(), ".alan", "agent", "logs"), { recursive: true, mode: 448 });
|
|
26378
27031
|
}
|
|
26379
27032
|
writeManifest(plan.manifestPath, plan.manifest);
|
|
26380
27033
|
}
|
|
@@ -26384,7 +27037,7 @@ function installDaemonService(args = []) {
|
|
|
26384
27037
|
function uninstallDaemonService(args = []) {
|
|
26385
27038
|
const plan = currentServicePlan(args);
|
|
26386
27039
|
for (const command of plan.uninstallCommands) runServiceCommand(command);
|
|
26387
|
-
if (plan.manifestPath) (0,
|
|
27040
|
+
if (plan.manifestPath) (0, import_node_fs14.rmSync)(plan.manifestPath, { force: true });
|
|
26388
27041
|
console.info("[alan-agent] Per-user daemon service removed");
|
|
26389
27042
|
}
|
|
26390
27043
|
function printDaemonServiceStatus(args = []) {
|