@aiden-ade/sandbox-agent 0.1.37 → 0.1.39
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 +1066 -227
- 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)
|
|
4906
|
-
return true;
|
|
4907
|
-
}
|
|
4908
|
-
if (normalizeToolName(name) === "mcp" && inputLooksLikeAskUserQuestion(input)) {
|
|
4904
|
+
if (nested && nested !== name && HARNESS_ASK_TOOL_NAMES.has(normalizeToolName(nested))) {
|
|
4909
4905
|
return true;
|
|
4910
4906
|
}
|
|
4911
|
-
return
|
|
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,10 +15713,111 @@ 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
|
+
}
|
|
15720
|
+
var ERROR_SPECS = {
|
|
15721
|
+
model_mismatch: {
|
|
15722
|
+
message: "The selected model isn't available, or you don't have access to it. Pick a different model, then send your message again.",
|
|
15723
|
+
recoveryClass: "user_fixable"
|
|
15724
|
+
},
|
|
15725
|
+
auth_invalid: {
|
|
15726
|
+
message: "API key invalid or expired. Check your provider settings, then retry.",
|
|
15727
|
+
recoveryClass: "user_fixable"
|
|
15728
|
+
},
|
|
15729
|
+
not_logged_in: {
|
|
15730
|
+
message: "Not logged in to this provider. Sign in from provider settings, then retry.",
|
|
15731
|
+
recoveryClass: "user_fixable"
|
|
15732
|
+
},
|
|
15733
|
+
not_authenticated: {
|
|
15734
|
+
message: "The agent CLI isn't signed in on this runtime. Authenticate it there, then retry.",
|
|
15735
|
+
recoveryClass: "needs_env"
|
|
15736
|
+
},
|
|
15737
|
+
auth_expired: {
|
|
15738
|
+
message: "The provider connection expired. Reconnect it in Cloud settings, then restart or recreate the machine.",
|
|
15739
|
+
recoveryClass: "user_fixable"
|
|
15740
|
+
},
|
|
15741
|
+
subscription_required: {
|
|
15742
|
+
message: "This model requires a paid subscription. Switch to a supported model or upgrade your plan.",
|
|
15743
|
+
recoveryClass: "user_fixable"
|
|
15744
|
+
},
|
|
15745
|
+
quota_exceeded: {
|
|
15746
|
+
message: "API quota exceeded. Wait for it to reset or check your billing, then retry.",
|
|
15747
|
+
recoveryClass: "user_fixable"
|
|
15748
|
+
},
|
|
15749
|
+
usage_limit: {
|
|
15750
|
+
message: "Provider usage limit reached. Switch model or wait for the limit to reset, then retry.",
|
|
15751
|
+
recoveryClass: "user_fixable"
|
|
15752
|
+
},
|
|
15753
|
+
context_length: {
|
|
15754
|
+
message: "Context window exceeded \u2014 the conversation is too long. Start a new session or send a shorter message.",
|
|
15755
|
+
recoveryClass: "user_fixable"
|
|
15756
|
+
},
|
|
15757
|
+
rate_limited: {
|
|
15758
|
+
message: "Rate limited by the provider. Retrying automatically shortly.",
|
|
15759
|
+
recoveryClass: "auto_recovering"
|
|
15760
|
+
},
|
|
15761
|
+
overloaded: {
|
|
15762
|
+
message: "The provider is overloaded. Retrying automatically.",
|
|
15763
|
+
recoveryClass: "auto_recovering"
|
|
15764
|
+
},
|
|
15765
|
+
network: {
|
|
15766
|
+
message: "Network connection failed. Check your internet connection \u2014 retrying automatically.",
|
|
15767
|
+
recoveryClass: "auto_recovering"
|
|
15768
|
+
},
|
|
15769
|
+
provider_timeout: {
|
|
15770
|
+
message: "The provider timed out while continuing the turn. Send your message again; if it repeats, check network/API latency or reduce slow tool calls.",
|
|
15771
|
+
recoveryClass: "retry"
|
|
15772
|
+
},
|
|
15773
|
+
resume_failed: {
|
|
15774
|
+
message: "The previous session could not be resumed \u2014 its stored session was missing or expired. The next message starts a fresh session without earlier context.",
|
|
15775
|
+
recoveryClass: "retry"
|
|
15776
|
+
},
|
|
15777
|
+
permission_denied: {
|
|
15778
|
+
message: "Permission denied. Check file or command permissions on the runtime.",
|
|
15779
|
+
recoveryClass: "needs_env"
|
|
15780
|
+
},
|
|
15781
|
+
out_of_memory: {
|
|
15782
|
+
message: "Out of memory on the runtime. Close other work or use a larger machine, then retry.",
|
|
15783
|
+
recoveryClass: "needs_env"
|
|
15784
|
+
},
|
|
15785
|
+
provider_error: {
|
|
15786
|
+
message: "The provider returned an error. Send your message again to retry.",
|
|
15787
|
+
recoveryClass: "retry"
|
|
15788
|
+
},
|
|
15789
|
+
unknown_cli_error: {
|
|
15790
|
+
message: "The agent stopped unexpectedly. Send your message again to retry.",
|
|
15791
|
+
recoveryClass: "retry"
|
|
15792
|
+
}
|
|
15793
|
+
};
|
|
15794
|
+
function specForErrorKind(errorKind) {
|
|
15795
|
+
const spec = ERROR_SPECS[errorKind];
|
|
15796
|
+
return { message: spec.message, errorKind, recoveryClass: spec.recoveryClass };
|
|
15797
|
+
}
|
|
15739
15798
|
function isLikelyProviderAuthError(stderr) {
|
|
15740
15799
|
const lower = stderr.toLowerCase();
|
|
15741
15800
|
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");
|
|
15742
15801
|
}
|
|
15802
|
+
function mapProviderApiError(errorCode, apiStatus) {
|
|
15803
|
+
const code = errorCode?.trim().toLowerCase();
|
|
15804
|
+
if (code) {
|
|
15805
|
+
if (code.includes("model_not_found") || code.includes("model_not_available")) {
|
|
15806
|
+
return "model_mismatch";
|
|
15807
|
+
}
|
|
15808
|
+
if (code.includes("authentication") || code.includes("permission_error")) return "auth_invalid";
|
|
15809
|
+
if (code.includes("rate_limit")) return "rate_limited";
|
|
15810
|
+
if (code.includes("overloaded")) return "overloaded";
|
|
15811
|
+
if (code.includes("insufficient_quota") || code.includes("billing")) return "quota_exceeded";
|
|
15812
|
+
}
|
|
15813
|
+
if (typeof apiStatus === "number") {
|
|
15814
|
+
if (apiStatus === 404) return "model_mismatch";
|
|
15815
|
+
if (apiStatus === 401 || apiStatus === 403) return "auth_invalid";
|
|
15816
|
+
if (apiStatus === 429) return "rate_limited";
|
|
15817
|
+
if (apiStatus >= 500) return "overloaded";
|
|
15818
|
+
}
|
|
15819
|
+
return null;
|
|
15820
|
+
}
|
|
15743
15821
|
function normalizeCodexCliErrorMessage(message) {
|
|
15744
15822
|
const lower = message.toLowerCase();
|
|
15745
15823
|
if (lower.includes("reconnecting") && lower.includes("request timed out")) {
|
|
@@ -15750,40 +15828,73 @@ function normalizeCodexCliErrorMessage(message) {
|
|
|
15750
15828
|
}
|
|
15751
15829
|
return message;
|
|
15752
15830
|
}
|
|
15753
|
-
function
|
|
15831
|
+
function classifyCliErrorDetailed(stderr, _exitCode, opts) {
|
|
15754
15832
|
const normalizedCodexError = normalizeCodexCliErrorMessage(stderr);
|
|
15755
|
-
if (normalizedCodexError !== stderr)
|
|
15756
|
-
|
|
15757
|
-
|
|
15758
|
-
|
|
15759
|
-
|
|
15760
|
-
|
|
15761
|
-
|
|
15762
|
-
|
|
15763
|
-
|
|
15764
|
-
|
|
15765
|
-
if (
|
|
15766
|
-
return "
|
|
15767
|
-
if (
|
|
15768
|
-
|
|
15769
|
-
|
|
15770
|
-
|
|
15771
|
-
if (
|
|
15772
|
-
|
|
15773
|
-
|
|
15774
|
-
|
|
15775
|
-
|
|
15776
|
-
|
|
15777
|
-
|
|
15778
|
-
|
|
15779
|
-
|
|
15780
|
-
return "
|
|
15781
|
-
if (
|
|
15782
|
-
|
|
15783
|
-
|
|
15784
|
-
|
|
15833
|
+
if (normalizedCodexError !== stderr) {
|
|
15834
|
+
const kind = /timed out/i.test(normalizedCodexError) ? "provider_timeout" : "auth_expired";
|
|
15835
|
+
return {
|
|
15836
|
+
message: normalizedCodexError,
|
|
15837
|
+
errorKind: kind,
|
|
15838
|
+
recoveryClass: ERROR_SPECS[kind].recoveryClass
|
|
15839
|
+
};
|
|
15840
|
+
}
|
|
15841
|
+
const haystack = [stderr, ...opts?.extraSignals ?? []].join("\n").toLowerCase();
|
|
15842
|
+
const has = (...needles) => needles.some((n) => haystack.includes(n));
|
|
15843
|
+
if (has("authentication required", "not authenticated"))
|
|
15844
|
+
return specForErrorKind("not_authenticated");
|
|
15845
|
+
if (has("out of usage", "increase your limit")) return specForErrorKind("usage_limit");
|
|
15846
|
+
if (has("no chat found", "chat not found", "session not found", "could not resume"))
|
|
15847
|
+
return specForErrorKind("resume_failed");
|
|
15848
|
+
if (has("rate_limit", "rate limit", "429")) return specForErrorKind("rate_limited");
|
|
15849
|
+
if (isLikelyProviderAuthError(haystack)) return specForErrorKind("auth_invalid");
|
|
15850
|
+
if (has(
|
|
15851
|
+
"not logged in",
|
|
15852
|
+
"login required",
|
|
15853
|
+
"please sign in",
|
|
15854
|
+
"please log in",
|
|
15855
|
+
"sign in to",
|
|
15856
|
+
"log in to"
|
|
15857
|
+
))
|
|
15858
|
+
return specForErrorKind("not_logged_in");
|
|
15859
|
+
if (has(
|
|
15860
|
+
"model not supported",
|
|
15861
|
+
"unsupported model",
|
|
15862
|
+
"model not found",
|
|
15863
|
+
"model not available",
|
|
15864
|
+
"model is not available",
|
|
15865
|
+
"invalid model",
|
|
15866
|
+
"unknown model",
|
|
15867
|
+
"no such model",
|
|
15868
|
+
"llm not set",
|
|
15869
|
+
// Claude Code phrasings (safety net; the structured decoder catches these first).
|
|
15870
|
+
"issue with the selected model",
|
|
15871
|
+
"may not exist or you may not have access"
|
|
15872
|
+
))
|
|
15873
|
+
return specForErrorKind("model_mismatch");
|
|
15874
|
+
if (has(
|
|
15875
|
+
"subscription",
|
|
15876
|
+
"paid plan",
|
|
15877
|
+
"plan required",
|
|
15878
|
+
"upgrade your plan",
|
|
15879
|
+
"only supports free",
|
|
15880
|
+
"not available on your",
|
|
15881
|
+
"requires a paid"
|
|
15882
|
+
))
|
|
15883
|
+
return specForErrorKind("subscription_required");
|
|
15884
|
+
if (has("quota exceeded", "quota limit", "daily limit", "monthly limit", "usage limit"))
|
|
15885
|
+
return specForErrorKind("quota_exceeded");
|
|
15886
|
+
if (has("overloaded", "503", "service unavailable")) return specForErrorKind("overloaded");
|
|
15887
|
+
if (has("context_length", "too long", "max tokens", "context window"))
|
|
15888
|
+
return specForErrorKind("context_length");
|
|
15889
|
+
if (has("econnrefused", "network", "enotfound", "timeout", "etimedout"))
|
|
15890
|
+
return specForErrorKind("network");
|
|
15891
|
+
if (has("permission denied", "eacces")) return specForErrorKind("permission_denied");
|
|
15892
|
+
if (has("out of memory", "enomem")) return specForErrorKind("out_of_memory");
|
|
15785
15893
|
const raw = stderr.trim().slice(0, 200);
|
|
15786
|
-
|
|
15894
|
+
if (raw) {
|
|
15895
|
+
return { message: raw, errorKind: "unknown_cli_error", recoveryClass: "unknown" };
|
|
15896
|
+
}
|
|
15897
|
+
return specForErrorKind("unknown_cli_error");
|
|
15787
15898
|
}
|
|
15788
15899
|
function isTransientError(errorMessage) {
|
|
15789
15900
|
const lower = errorMessage.toLowerCase();
|
|
@@ -15832,6 +15943,16 @@ function spawnCli(command, args, context) {
|
|
|
15832
15943
|
detached: process.platform !== "win32"
|
|
15833
15944
|
});
|
|
15834
15945
|
}
|
|
15946
|
+
function emitSessionNotice(presenter, kind, message) {
|
|
15947
|
+
if (presenter.onNotice) {
|
|
15948
|
+
void presenter.onNotice(kind, message);
|
|
15949
|
+
return;
|
|
15950
|
+
}
|
|
15951
|
+
presenter.onLog(`[session_notice:${kind}] ${message}`);
|
|
15952
|
+
}
|
|
15953
|
+
function logResumeObservability(event, fields) {
|
|
15954
|
+
console.info(`[agent-resume] ${event}`, { event, ...fields });
|
|
15955
|
+
}
|
|
15835
15956
|
function structuredEventKey(event) {
|
|
15836
15957
|
const type = typeof event.type === "string" && event.type ? event.type : "";
|
|
15837
15958
|
const role = typeof event.role === "string" && event.role ? event.role : "";
|
|
@@ -15901,7 +16022,12 @@ function createGenericCliBackend(options) {
|
|
|
15901
16022
|
const args = options.buildArgs?.(context, prompt) ?? options.args.map((arg) => arg === "{{prompt}}" ? prompt : arg);
|
|
15902
16023
|
const child = spawnCli(options.command, args, context);
|
|
15903
16024
|
state.process = child;
|
|
16025
|
+
state.lastRawOutputAtMs = Date.now();
|
|
15904
16026
|
context.onProcessSpawned?.(child);
|
|
16027
|
+
context.registerLivenessProbe?.(() => ({
|
|
16028
|
+
providerAlive: child.exitCode === null,
|
|
16029
|
+
lastRawOutputAgoMs: typeof state.lastRawOutputAtMs === "number" ? Date.now() - state.lastRawOutputAtMs : null
|
|
16030
|
+
}));
|
|
15905
16031
|
const safeArgs = args.map(
|
|
15906
16032
|
(a, i) => i > 0 && args[i - 1] === "--system-prompt" ? `"<system-prompt ${a.length} chars>"` : a
|
|
15907
16033
|
);
|
|
@@ -15950,6 +16076,7 @@ function createGenericCliBackend(options) {
|
|
|
15950
16076
|
}
|
|
15951
16077
|
stdoutRl.on("line", (line) => {
|
|
15952
16078
|
sawStdoutLine = true;
|
|
16079
|
+
state.lastRawOutputAtMs = Date.now();
|
|
15953
16080
|
presenter.recordRawTranscript?.("stdout", line);
|
|
15954
16081
|
if (options.parseStructuredLine) {
|
|
15955
16082
|
options.parseStructuredLine(line, context, state);
|
|
@@ -15967,6 +16094,7 @@ function createGenericCliBackend(options) {
|
|
|
15967
16094
|
}
|
|
15968
16095
|
});
|
|
15969
16096
|
stderrRl.on("line", (line) => {
|
|
16097
|
+
state.lastRawOutputAtMs = Date.now();
|
|
15970
16098
|
presenter.recordRawTranscript?.("stderr", line);
|
|
15971
16099
|
if (options.parseStderrLine) {
|
|
15972
16100
|
options.parseStderrLine(line, context, state);
|
|
@@ -16013,7 +16141,7 @@ function createGenericCliBackend(options) {
|
|
|
16013
16141
|
if (presenter.pendingAskUserToolIds?.size) {
|
|
16014
16142
|
idleTimeoutReason = "pending_user_answer";
|
|
16015
16143
|
resolve22("idle_timeout");
|
|
16016
|
-
} else if (state.activeBackgroundTaskIds?.size) {
|
|
16144
|
+
} else if (state.resultDeferredOnBackgroundWork && state.activeBackgroundTaskIds?.size) {
|
|
16017
16145
|
idleTimeoutReason = "background_task";
|
|
16018
16146
|
resolve22("idle_timeout");
|
|
16019
16147
|
} else {
|
|
@@ -16076,10 +16204,26 @@ function createGenericCliBackend(options) {
|
|
|
16076
16204
|
logUnhandledStructuredEventSummary(options.kind, state);
|
|
16077
16205
|
}
|
|
16078
16206
|
const requestedResumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
|
|
16207
|
+
let cursorResumeSilentlyFailed = false;
|
|
16079
16208
|
if (requestedResumeId && state.runtimeSessionId && state.runtimeSessionId !== requestedResumeId) {
|
|
16080
16209
|
console.warn(
|
|
16081
16210
|
`[${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
16211
|
);
|
|
16212
|
+
if (options.kind === "cursor_agent_cli") {
|
|
16213
|
+
cursorResumeSilentlyFailed = true;
|
|
16214
|
+
logResumeObservability("agent_resume_failed", {
|
|
16215
|
+
backendKind: options.kind,
|
|
16216
|
+
reason: "cli_forked_fresh_session",
|
|
16217
|
+
requestedResumeId,
|
|
16218
|
+
runtimeSessionId: state.runtimeSessionId,
|
|
16219
|
+
hadFallbackContext: Boolean(context.config.resumeFallbackContext?.trim())
|
|
16220
|
+
});
|
|
16221
|
+
emitSessionNotice(
|
|
16222
|
+
context.presenter,
|
|
16223
|
+
"session_resume_failed",
|
|
16224
|
+
"The previous session could not be resumed, so this reply was generated without earlier conversation context. Retrying with the recent history re-included."
|
|
16225
|
+
);
|
|
16226
|
+
}
|
|
16083
16227
|
}
|
|
16084
16228
|
if (context.abortController.signal.aborted) {
|
|
16085
16229
|
return {
|
|
@@ -16173,14 +16317,51 @@ function createGenericCliBackend(options) {
|
|
|
16173
16317
|
const stderrText = stderrLines.join("\n").trim();
|
|
16174
16318
|
const hasStructuredError = exitCode === 0 && !!state.error?.trim();
|
|
16175
16319
|
const failed = exitCode !== 0 || hasStructuredError;
|
|
16320
|
+
if (cursorResumeSilentlyFailed && !failed) {
|
|
16321
|
+
return {
|
|
16322
|
+
success: false,
|
|
16323
|
+
summary: state.summary.trim() || "Session resume failed",
|
|
16324
|
+
filesModified: [],
|
|
16325
|
+
planFilesCreated: [],
|
|
16326
|
+
iterations: Math.max(state.iterations, 1),
|
|
16327
|
+
error: SESSION_RESUME_FAILED_MESSAGE,
|
|
16328
|
+
errorKind: "resume_failed",
|
|
16329
|
+
providerSessionId: state.runtimeSessionId,
|
|
16330
|
+
runtimeSessionId: state.runtimeSessionId,
|
|
16331
|
+
backendKind: options.kind,
|
|
16332
|
+
supportTier: options.supportTier,
|
|
16333
|
+
usage: state.usage
|
|
16334
|
+
};
|
|
16335
|
+
}
|
|
16176
16336
|
const summary = state.summary.trim() || state.error?.trim() || (failed ? "Task failed" : "Task completed");
|
|
16337
|
+
let classifiedError;
|
|
16338
|
+
let classifiedErrorKind;
|
|
16339
|
+
let classifiedRecoveryClass;
|
|
16340
|
+
if (failed) {
|
|
16341
|
+
if (state.errorKind && state.error?.trim()) {
|
|
16342
|
+
classifiedError = state.error.trim();
|
|
16343
|
+
classifiedErrorKind = state.errorKind;
|
|
16344
|
+
classifiedRecoveryClass = state.recoveryClass;
|
|
16345
|
+
} else {
|
|
16346
|
+
const detailed = classifyCliErrorDetailed(
|
|
16347
|
+
state.error?.trim() || stderrText || "",
|
|
16348
|
+
exitCode,
|
|
16349
|
+
{ extraSignals: state.summary ? [state.summary] : void 0 }
|
|
16350
|
+
);
|
|
16351
|
+
classifiedError = detailed.message;
|
|
16352
|
+
classifiedErrorKind = detailed.errorKind;
|
|
16353
|
+
classifiedRecoveryClass = detailed.recoveryClass;
|
|
16354
|
+
}
|
|
16355
|
+
}
|
|
16177
16356
|
return {
|
|
16178
16357
|
success: !failed,
|
|
16179
16358
|
summary,
|
|
16180
16359
|
filesModified: [],
|
|
16181
16360
|
planFilesCreated: [],
|
|
16182
16361
|
iterations: Math.max(state.iterations, 1),
|
|
16183
|
-
error:
|
|
16362
|
+
error: classifiedError,
|
|
16363
|
+
...classifiedErrorKind ? { errorKind: classifiedErrorKind } : {},
|
|
16364
|
+
...classifiedRecoveryClass ? { recoveryClass: classifiedRecoveryClass } : {},
|
|
16184
16365
|
providerSessionId: state.runtimeSessionId,
|
|
16185
16366
|
runtimeSessionId: state.runtimeSessionId,
|
|
16186
16367
|
backendKind: options.kind,
|
|
@@ -16622,6 +16803,21 @@ function createAntigravityCliBackend(command = "agy", defaultArgs = []) {
|
|
|
16622
16803
|
}
|
|
16623
16804
|
};
|
|
16624
16805
|
}
|
|
16806
|
+
function resolveClaudeHome(env, variant = "claude") {
|
|
16807
|
+
if (variant === "supatest") {
|
|
16808
|
+
return (0, import_path4.join)((0, import_os4.homedir)(), ".supatest", "claude-internal");
|
|
16809
|
+
}
|
|
16810
|
+
return env.CLAUDE_CONFIG_DIR || process.env.CLAUDE_CONFIG_DIR || (0, import_path4.join)((0, import_os4.homedir)(), ".claude");
|
|
16811
|
+
}
|
|
16812
|
+
function encodeClaudeProjectDir(cwd) {
|
|
16813
|
+
return cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
16814
|
+
}
|
|
16815
|
+
function claudeSessionLogExists(input) {
|
|
16816
|
+
if (!input.sessionId || !input.cwd) return false;
|
|
16817
|
+
const projectDir = (0, import_path4.join)(input.home, "projects", encodeClaudeProjectDir(input.cwd));
|
|
16818
|
+
if (!(0, import_fs4.existsSync)(projectDir)) return false;
|
|
16819
|
+
return (0, import_fs4.existsSync)((0, import_path4.join)(projectDir, `${input.sessionId}.jsonl`));
|
|
16820
|
+
}
|
|
16625
16821
|
var execFileAsync = (0, import_util10.promisify)(import_child_process3.execFile);
|
|
16626
16822
|
var flagSupportCache = /* @__PURE__ */ new Map();
|
|
16627
16823
|
function helpAdvertisesFlag(helpText, flag) {
|
|
@@ -16672,15 +16868,38 @@ function registerBackgroundTaskIds(state, keys) {
|
|
|
16672
16868
|
state.activeBackgroundTaskIds.add(key);
|
|
16673
16869
|
}
|
|
16674
16870
|
}
|
|
16675
|
-
function
|
|
16871
|
+
function registerBackgroundLauncher(state, toolUseId) {
|
|
16872
|
+
registerBackgroundTaskIds(state, [toolUseId]);
|
|
16873
|
+
if (!state.pendingLauncherToolIds) state.pendingLauncherToolIds = [];
|
|
16874
|
+
if (!state.pendingLauncherToolIds.includes(toolUseId)) {
|
|
16875
|
+
state.pendingLauncherToolIds.push(toolUseId);
|
|
16876
|
+
}
|
|
16877
|
+
}
|
|
16878
|
+
function drainNextUnlinkedBackgroundLauncher(state) {
|
|
16879
|
+
const launcherId = state.pendingLauncherToolIds?.shift();
|
|
16880
|
+
if (state.pendingLauncherToolIds?.length === 0) state.pendingLauncherToolIds = void 0;
|
|
16881
|
+
if (launcherId) clearBackgroundTaskIds(state, [launcherId]);
|
|
16882
|
+
return launcherId;
|
|
16883
|
+
}
|
|
16884
|
+
function registerBackgroundTaskRecord(state, record, launcherToolUseId) {
|
|
16676
16885
|
const taskId = backgroundTaskId(record);
|
|
16677
|
-
let toolUseId = backgroundToolUseId(record);
|
|
16678
|
-
if (!toolUseId && taskId
|
|
16679
|
-
const
|
|
16680
|
-
if (
|
|
16886
|
+
let toolUseId = backgroundToolUseId(record) ?? launcherToolUseId ?? void 0;
|
|
16887
|
+
if (!toolUseId && taskId) {
|
|
16888
|
+
const nextLauncher = state.pendingLauncherToolIds?.[0];
|
|
16889
|
+
if (nextLauncher) {
|
|
16890
|
+
toolUseId = nextLauncher;
|
|
16891
|
+
} else if (state.activeBackgroundTaskIds?.size === 1) {
|
|
16892
|
+
const [candidate] = state.activeBackgroundTaskIds;
|
|
16893
|
+
if (candidate && candidate !== taskId) toolUseId = candidate;
|
|
16894
|
+
}
|
|
16681
16895
|
}
|
|
16682
16896
|
registerBackgroundTaskIds(state, backgroundTaskKeys(record));
|
|
16683
16897
|
if (!taskId || !toolUseId) return;
|
|
16898
|
+
if (state.pendingLauncherToolIds) {
|
|
16899
|
+
const idx = state.pendingLauncherToolIds.indexOf(toolUseId);
|
|
16900
|
+
if (idx >= 0) state.pendingLauncherToolIds.splice(idx, 1);
|
|
16901
|
+
if (state.pendingLauncherToolIds.length === 0) state.pendingLauncherToolIds = void 0;
|
|
16902
|
+
}
|
|
16684
16903
|
if (!state.backgroundToolIdByTaskId) state.backgroundToolIdByTaskId = /* @__PURE__ */ new Map();
|
|
16685
16904
|
if (!state.backgroundTaskIdsByToolId) state.backgroundTaskIdsByToolId = /* @__PURE__ */ new Map();
|
|
16686
16905
|
state.backgroundToolIdByTaskId.set(taskId, toolUseId);
|
|
@@ -16711,6 +16930,10 @@ function clearBackgroundTaskIds(state, keys) {
|
|
|
16711
16930
|
state.backgroundTaskIdsByToolId?.delete(key);
|
|
16712
16931
|
}
|
|
16713
16932
|
}
|
|
16933
|
+
if (state.pendingLauncherToolIds) {
|
|
16934
|
+
state.pendingLauncherToolIds = state.pendingLauncherToolIds.filter((id) => activeIds.has(id));
|
|
16935
|
+
if (state.pendingLauncherToolIds.length === 0) state.pendingLauncherToolIds = void 0;
|
|
16936
|
+
}
|
|
16714
16937
|
if (activeIds.size === 0) state.activeBackgroundTaskIds = void 0;
|
|
16715
16938
|
}
|
|
16716
16939
|
function resolveBackgroundTaskToolUseId(state, record) {
|
|
@@ -16733,6 +16956,7 @@ function deferOrFinalizeStructuredResult(kind, context, state) {
|
|
|
16733
16956
|
console.info(
|
|
16734
16957
|
`[${kind}] Keeping stdin open \u2014 ${pendingAsks} interactive question(s) awaiting user response`
|
|
16735
16958
|
);
|
|
16959
|
+
state.resultDeferredOnPendingAnswer = true;
|
|
16736
16960
|
return "deferred_pending_answer";
|
|
16737
16961
|
}
|
|
16738
16962
|
const activeBackgroundTasks = state.activeBackgroundTaskIds?.size ?? 0;
|
|
@@ -16747,9 +16971,10 @@ function deferOrFinalizeStructuredResult(kind, context, state) {
|
|
|
16747
16971
|
return "finalized";
|
|
16748
16972
|
}
|
|
16749
16973
|
function maybeFinalizeDeferredResult(kind, context, state) {
|
|
16750
|
-
if (!state.resultDeferredOnBackgroundWork) return;
|
|
16974
|
+
if (!state.resultDeferredOnBackgroundWork && !state.resultDeferredOnPendingAnswer) return;
|
|
16751
16975
|
if (context.presenter.pendingAskUserToolIds?.size || state.activeBackgroundTaskIds?.size) return;
|
|
16752
16976
|
state.resultDeferredOnBackgroundWork = false;
|
|
16977
|
+
state.resultDeferredOnPendingAnswer = false;
|
|
16753
16978
|
finalizeStructuredResult(kind, state);
|
|
16754
16979
|
}
|
|
16755
16980
|
var DEBUG_ASK = process.env.ALAN_DEBUG_ASK === "1" || process.env.ALAN_DEBUG === "1";
|
|
@@ -16810,6 +17035,9 @@ function handleClaudeStructuredEvent(parsed, context, state) {
|
|
|
16810
17035
|
if (typeof parsed.session_id === "string") {
|
|
16811
17036
|
state.runtimeSessionId = parsed.session_id;
|
|
16812
17037
|
}
|
|
17038
|
+
if (typeof parsed.error === "string" && parsed.error.trim()) {
|
|
17039
|
+
state.providerErrorCode = parsed.error.trim();
|
|
17040
|
+
}
|
|
16813
17041
|
const parentToolUseId = typeof parsed.parent_tool_use_id === "string" ? parsed.parent_tool_use_id : null;
|
|
16814
17042
|
switch (type) {
|
|
16815
17043
|
case "system": {
|
|
@@ -16819,21 +17047,44 @@ function handleClaudeStructuredEvent(parsed, context, state) {
|
|
|
16819
17047
|
return true;
|
|
16820
17048
|
}
|
|
16821
17049
|
if (parsed.subtype === "task_started") {
|
|
16822
|
-
registerBackgroundTaskRecord(state, parsed);
|
|
17050
|
+
registerBackgroundTaskRecord(state, parsed, parentToolUseId);
|
|
17051
|
+
return true;
|
|
17052
|
+
}
|
|
17053
|
+
if (parsed.subtype === "compact_boundary") {
|
|
17054
|
+
const compactMetadata = typeof parsed.compact_metadata === "object" && parsed.compact_metadata !== null ? parsed.compact_metadata : {};
|
|
17055
|
+
const trigger = typeof compactMetadata.trigger === "string" ? compactMetadata.trigger : typeof parsed.trigger === "string" ? parsed.trigger : "auto";
|
|
17056
|
+
const preTokens = typeof compactMetadata.pre_tokens === "number" ? compactMetadata.pre_tokens : typeof parsed.pre_tokens === "number" ? parsed.pre_tokens : void 0;
|
|
17057
|
+
const tokensPart = typeof preTokens === "number" && preTokens > 0 ? ` (${Math.round(preTokens / 1e3)}k tokens summarized)` : "";
|
|
17058
|
+
emitSessionNotice(
|
|
17059
|
+
presenter,
|
|
17060
|
+
"context_compacted",
|
|
17061
|
+
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.`
|
|
17062
|
+
);
|
|
16823
17063
|
return true;
|
|
16824
17064
|
}
|
|
16825
17065
|
if (parsed.subtype === "task_notification") {
|
|
16826
17066
|
if (isTerminalTaskNotification(parsed)) {
|
|
16827
17067
|
const toolUseId = resolveBackgroundTaskToolUseId(state, parsed);
|
|
16828
17068
|
const wasActive = !!toolUseId && state.activeBackgroundTaskIds?.has(toolUseId) ? true : backgroundTaskKeys(parsed).some((key) => state.activeBackgroundTaskIds?.has(key));
|
|
17069
|
+
const isFailed = isFailedTaskNotification(parsed) ? true : void 0;
|
|
16829
17070
|
if (toolUseId && wasActive) {
|
|
16830
17071
|
void presenter.onToolResult?.(
|
|
16831
17072
|
toolUseId,
|
|
16832
17073
|
formatClaudeTaskNotificationResult(parsed),
|
|
16833
|
-
|
|
17074
|
+
isFailed
|
|
16834
17075
|
);
|
|
17076
|
+
clearBackgroundTaskIds(state, backgroundTaskKeys(parsed));
|
|
17077
|
+
} else {
|
|
17078
|
+
clearBackgroundTaskIds(state, backgroundTaskKeys(parsed));
|
|
17079
|
+
const drainedLauncher = drainNextUnlinkedBackgroundLauncher(state);
|
|
17080
|
+
if (drainedLauncher) {
|
|
17081
|
+
void presenter.onToolResult?.(
|
|
17082
|
+
drainedLauncher,
|
|
17083
|
+
formatClaudeTaskNotificationResult(parsed),
|
|
17084
|
+
isFailed
|
|
17085
|
+
);
|
|
17086
|
+
}
|
|
16835
17087
|
}
|
|
16836
|
-
clearBackgroundTaskIds(state, backgroundTaskKeys(parsed));
|
|
16837
17088
|
maybeFinalizeDeferredResult("claude_cli", context, state);
|
|
16838
17089
|
}
|
|
16839
17090
|
return true;
|
|
@@ -16841,9 +17092,10 @@ function handleClaudeStructuredEvent(parsed, context, state) {
|
|
|
16841
17092
|
return false;
|
|
16842
17093
|
}
|
|
16843
17094
|
case "assistant": {
|
|
16844
|
-
if (
|
|
17095
|
+
if (state.awaitingAskDenialFallback) {
|
|
17096
|
+
state.awaitingAskDenialFallback = false;
|
|
16845
17097
|
console.info(
|
|
16846
|
-
|
|
17098
|
+
"[claude_cli] Suppressing auto-denial fallback assistant turn after native AskUserQuestion"
|
|
16847
17099
|
);
|
|
16848
17100
|
return true;
|
|
16849
17101
|
}
|
|
@@ -16872,7 +17124,7 @@ function handleClaudeStructuredEvent(parsed, context, state) {
|
|
|
16872
17124
|
parentToolUseId
|
|
16873
17125
|
);
|
|
16874
17126
|
if (CLAUDE_SUBAGENT_TOOL_NAMES.has(toolBlock.name.toLowerCase())) {
|
|
16875
|
-
|
|
17127
|
+
registerBackgroundLauncher(state, toolBlock.id);
|
|
16876
17128
|
}
|
|
16877
17129
|
const normalizedName = normalizeToolName(toolBlock.name);
|
|
16878
17130
|
if (isInteractiveToolName(toolBlock.name) || INTERACTIVE_TOOL_NAMES.has(normalizedName)) {
|
|
@@ -16883,6 +17135,10 @@ function handleClaudeStructuredEvent(parsed, context, state) {
|
|
|
16883
17135
|
if (isAskUserQuestionTool(toolBlock.name)) {
|
|
16884
17136
|
if (!presenter.pendingAskUserToolIds) presenter.pendingAskUserToolIds = /* @__PURE__ */ new Set();
|
|
16885
17137
|
presenter.pendingAskUserToolIds.add(toolBlock.id);
|
|
17138
|
+
if (!toolBlock.name.toLowerCase().startsWith("mcp__")) {
|
|
17139
|
+
if (!state.nativeAskToolIds) state.nativeAskToolIds = /* @__PURE__ */ new Set();
|
|
17140
|
+
state.nativeAskToolIds.add(toolBlock.id);
|
|
17141
|
+
}
|
|
16886
17142
|
debugAskLog(
|
|
16887
17143
|
`[claude_cli] AskUserQuestion tool_use detected \u2014 id=${toolBlock.id}, name=${toolBlock.name}, pendingSize=${presenter.pendingAskUserToolIds.size}, presenterHasProp=${Object.hasOwn(presenter, "pendingAskUserToolIds")}`
|
|
16888
17144
|
);
|
|
@@ -16907,6 +17163,22 @@ function handleClaudeStructuredEvent(parsed, context, state) {
|
|
|
16907
17163
|
for (const block of content) {
|
|
16908
17164
|
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
17165
|
if (state.suppressedToolResultIds?.has(block.tool_use_id)) {
|
|
17166
|
+
const isSuppressedError = "is_error" in block && block.is_error === true;
|
|
17167
|
+
const isNativeAsk = state.nativeAskToolIds?.has(block.tool_use_id) === true;
|
|
17168
|
+
if (isSuppressedError && !isNativeAsk && presenter.pendingAskUserToolIds?.has(block.tool_use_id)) {
|
|
17169
|
+
presenter.pendingAskUserToolIds.delete(block.tool_use_id);
|
|
17170
|
+
state.suppressedToolResultIds.delete(block.tool_use_id);
|
|
17171
|
+
const resultText2 = extractToolResultContent(block.content);
|
|
17172
|
+
console.info(
|
|
17173
|
+
`[claude_cli] Ask tool ${block.tool_use_id} returned an error \u2014 clearing pending ask so the run can finish`
|
|
17174
|
+
);
|
|
17175
|
+
void presenter.onToolResult?.(block.tool_use_id, resultText2, true, parentToolUseId);
|
|
17176
|
+
maybeFinalizeDeferredResult("claude_cli", context, state);
|
|
17177
|
+
continue;
|
|
17178
|
+
}
|
|
17179
|
+
if (isNativeAsk) {
|
|
17180
|
+
state.awaitingAskDenialFallback = true;
|
|
17181
|
+
}
|
|
16910
17182
|
console.info(
|
|
16911
17183
|
`[claude_cli] Suppressed auto-generated tool_result for ${block.tool_use_id}`
|
|
16912
17184
|
);
|
|
@@ -16978,6 +17250,27 @@ function handleClaudeStructuredEvent(parsed, context, state) {
|
|
|
16978
17250
|
})
|
|
16979
17251
|
);
|
|
16980
17252
|
}
|
|
17253
|
+
const isApiError = parsed.is_error === true || parsed.terminal_reason === "api_error" || typeof parsed.api_error_status === "number";
|
|
17254
|
+
if (isApiError && !state.error) {
|
|
17255
|
+
const apiStatus = typeof parsed.api_error_status === "number" ? parsed.api_error_status : void 0;
|
|
17256
|
+
state.apiErrorStatus = apiStatus;
|
|
17257
|
+
const mappedKind = mapProviderApiError(state.providerErrorCode, apiStatus);
|
|
17258
|
+
const spec = mappedKind ? specForErrorKind(mappedKind) : specForErrorKind("provider_error");
|
|
17259
|
+
state.error = mappedKind || typeof parsed.result !== "string" || !parsed.result.trim() ? spec.message : parsed.result.trim();
|
|
17260
|
+
state.errorKind = spec.errorKind;
|
|
17261
|
+
state.recoveryClass = spec.recoveryClass;
|
|
17262
|
+
console.error(
|
|
17263
|
+
"[claude_cli] result flagged API error:",
|
|
17264
|
+
JSON.stringify({
|
|
17265
|
+
is_error: parsed.is_error,
|
|
17266
|
+
terminal_reason: parsed.terminal_reason,
|
|
17267
|
+
api_error_status: parsed.api_error_status,
|
|
17268
|
+
providerErrorCode: state.providerErrorCode,
|
|
17269
|
+
mappedKind,
|
|
17270
|
+
result: typeof parsed.result === "string" ? parsed.result.slice(0, 200) : void 0
|
|
17271
|
+
})
|
|
17272
|
+
);
|
|
17273
|
+
}
|
|
16981
17274
|
debugAskLog(
|
|
16982
17275
|
`[claude_cli] result event \u2014 pendingAskUserToolIds size=${presenter.pendingAskUserToolIds?.size ?? "undefined"}, hasOwnProp=${Object.hasOwn(presenter, "pendingAskUserToolIds")}`
|
|
16983
17276
|
);
|
|
@@ -17014,7 +17307,31 @@ function createClaudeCliBackend(command = "claude", defaultArgs = []) {
|
|
|
17014
17307
|
if (disallowedTools.length > 0) {
|
|
17015
17308
|
args.push("--disallowedTools", disallowedTools.join(","));
|
|
17016
17309
|
}
|
|
17017
|
-
const
|
|
17310
|
+
const requestedResumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
|
|
17311
|
+
let resumeId = requestedResumeId;
|
|
17312
|
+
let resumeContextPrefix;
|
|
17313
|
+
if (requestedResumeId && !claudeSessionLogExists({
|
|
17314
|
+
sessionId: requestedResumeId,
|
|
17315
|
+
cwd: context.cwd,
|
|
17316
|
+
home: resolveClaudeHome(context.env)
|
|
17317
|
+
})) {
|
|
17318
|
+
resumeId = void 0;
|
|
17319
|
+
resumeContextPrefix = context.config.resumeFallbackContext?.trim() || void 0;
|
|
17320
|
+
console.warn(
|
|
17321
|
+
`[claude_cli] No session log for ${requestedResumeId} under cwd ${context.cwd}; starting a fresh session instead of resuming`
|
|
17322
|
+
);
|
|
17323
|
+
logResumeObservability("agent_resume_fallback_used", {
|
|
17324
|
+
backendKind: "claude_cli",
|
|
17325
|
+
reason: "session_log_missing_for_cwd",
|
|
17326
|
+
requestedResumeId,
|
|
17327
|
+
hadFallbackContext: Boolean(resumeContextPrefix)
|
|
17328
|
+
});
|
|
17329
|
+
emitSessionNotice(
|
|
17330
|
+
context.presenter,
|
|
17331
|
+
"session_resume_failed",
|
|
17332
|
+
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."
|
|
17333
|
+
);
|
|
17334
|
+
}
|
|
17018
17335
|
if (!resumeId && context.config.systemPrompt?.trim()) {
|
|
17019
17336
|
args.push("--system-prompt", context.config.systemPrompt.trim());
|
|
17020
17337
|
} else if (resumeId && context.config.systemPromptAppend?.trim()) {
|
|
@@ -17034,6 +17351,10 @@ function createClaudeCliBackend(command = "claude", defaultArgs = []) {
|
|
|
17034
17351
|
if (resumeId) {
|
|
17035
17352
|
args.push("--resume", resumeId);
|
|
17036
17353
|
console.info("[claude_cli] Resuming session", { resumeId });
|
|
17354
|
+
logResumeObservability("agent_resume_requested", {
|
|
17355
|
+
backendKind: "claude_cli",
|
|
17356
|
+
requestedResumeId: resumeId
|
|
17357
|
+
});
|
|
17037
17358
|
}
|
|
17038
17359
|
args.push("--chrome");
|
|
17039
17360
|
console.info("[claude_cli] Chrome flag added \u2014 final args:", args.join(" "));
|
|
@@ -17054,7 +17375,10 @@ function createClaudeCliBackend(command = "claude", defaultArgs = []) {
|
|
|
17054
17375
|
});
|
|
17055
17376
|
}
|
|
17056
17377
|
}
|
|
17057
|
-
const
|
|
17378
|
+
const basePromptText = resumeContextPrefix ? `${resumeContextPrefix}
|
|
17379
|
+
|
|
17380
|
+
${ctx.promptText}` : ctx.promptText;
|
|
17381
|
+
const promptText = ctx.config.mode === "plan" ? buildPlanModePrefix(buildPromptWithSystem(ctx.config, basePromptText)) : buildPromptWithSystem(ctx.config, basePromptText);
|
|
17058
17382
|
if (promptText.trim()) {
|
|
17059
17383
|
contentBlocks.push({ type: "text", text: promptText });
|
|
17060
17384
|
}
|
|
@@ -17105,27 +17429,30 @@ function normalizeCodexMcpToolResult(output) {
|
|
|
17105
17429
|
}
|
|
17106
17430
|
return raw;
|
|
17107
17431
|
}
|
|
17432
|
+
function resolveCodexHome(env) {
|
|
17433
|
+
return env.CODEX_HOME || process.env.CODEX_HOME || (0, import_path5.join)((0, import_os5.homedir)(), ".codex");
|
|
17434
|
+
}
|
|
17108
17435
|
function findCodexSessionLog(runtimeSessionId, codexHome) {
|
|
17109
|
-
if (!runtimeSessionId || !(0,
|
|
17110
|
-
const root = (0,
|
|
17111
|
-
if (!(0,
|
|
17436
|
+
if (!runtimeSessionId || !(0, import_fs5.existsSync)(codexHome)) return null;
|
|
17437
|
+
const root = (0, import_path5.join)(codexHome, "sessions");
|
|
17438
|
+
if (!(0, import_fs5.existsSync)(root)) return null;
|
|
17112
17439
|
const matches = [];
|
|
17113
17440
|
const stack = [root];
|
|
17114
17441
|
while (stack.length > 0) {
|
|
17115
17442
|
const dir = stack.pop();
|
|
17116
17443
|
let entries;
|
|
17117
17444
|
try {
|
|
17118
|
-
entries = (0,
|
|
17445
|
+
entries = (0, import_fs5.readdirSync)(dir, { withFileTypes: true });
|
|
17119
17446
|
} catch {
|
|
17120
17447
|
continue;
|
|
17121
17448
|
}
|
|
17122
17449
|
for (const entry of entries) {
|
|
17123
|
-
const path = (0,
|
|
17450
|
+
const path = (0, import_path5.join)(dir, entry.name);
|
|
17124
17451
|
if (entry.isDirectory()) {
|
|
17125
17452
|
stack.push(path);
|
|
17126
17453
|
} else if (entry.isFile() && entry.name.includes(runtimeSessionId) && entry.name.endsWith(".jsonl")) {
|
|
17127
17454
|
try {
|
|
17128
|
-
matches.push({ path, mtimeMs: (0,
|
|
17455
|
+
matches.push({ path, mtimeMs: (0, import_fs5.statSync)(path).mtimeMs });
|
|
17129
17456
|
} catch {
|
|
17130
17457
|
matches.push({ path, mtimeMs: 0 });
|
|
17131
17458
|
}
|
|
@@ -17158,12 +17485,14 @@ function latestUserMessageLineIndex(lines) {
|
|
|
17158
17485
|
async function replayCodexMcpToolEventsFromSessionLog(context, state) {
|
|
17159
17486
|
const runtimeSessionId = state.runtimeSessionId;
|
|
17160
17487
|
if (!runtimeSessionId) return;
|
|
17161
|
-
|
|
17488
|
+
if (!state.iterations || state.iterations <= 0) return;
|
|
17489
|
+
const codexHome = resolveCodexHome(context.env);
|
|
17162
17490
|
const logPath = findCodexSessionLog(runtimeSessionId, codexHome);
|
|
17163
17491
|
if (!logPath) return;
|
|
17164
|
-
const allLines = (0,
|
|
17492
|
+
const allLines = (0, import_fs5.readFileSync)(logPath, "utf8").split(/\r?\n/).filter(Boolean);
|
|
17165
17493
|
const latestUserLineIndex = latestUserMessageLineIndex(allLines);
|
|
17166
17494
|
const lines = latestUserLineIndex >= 0 ? allLines.slice(latestUserLineIndex + 1) : allLines;
|
|
17495
|
+
const streamedToolIds = state.codexStreamedToolIds ?? /* @__PURE__ */ new Set();
|
|
17167
17496
|
const emittedToolIds = /* @__PURE__ */ new Set();
|
|
17168
17497
|
for (const line of lines) {
|
|
17169
17498
|
const entry = parseJsonObject(line);
|
|
@@ -17174,6 +17503,7 @@ async function replayCodexMcpToolEventsFromSessionLog(context, state) {
|
|
|
17174
17503
|
const name = typeof payload.name === "string" ? payload.name : "";
|
|
17175
17504
|
const namespace = typeof payload.namespace === "string" ? payload.namespace : "";
|
|
17176
17505
|
if (!callId || !name || !namespace.startsWith("mcp__")) continue;
|
|
17506
|
+
if (streamedToolIds.has(callId)) continue;
|
|
17177
17507
|
const toolName = `${namespace.replace(/_+$/, "")}__${name}`;
|
|
17178
17508
|
context.presenter.onToolUse(toolName, parseMaybeJson(payload.arguments), callId);
|
|
17179
17509
|
emittedToolIds.add(callId);
|
|
@@ -17182,6 +17512,7 @@ async function replayCodexMcpToolEventsFromSessionLog(context, state) {
|
|
|
17182
17512
|
if (payload.type === "tool_search_call") {
|
|
17183
17513
|
const callId = typeof payload.call_id === "string" ? payload.call_id : "";
|
|
17184
17514
|
if (!callId) continue;
|
|
17515
|
+
if (streamedToolIds.has(callId)) continue;
|
|
17185
17516
|
context.presenter.onToolUse("tool_search", payload.arguments ?? {}, callId);
|
|
17186
17517
|
emittedToolIds.add(callId);
|
|
17187
17518
|
continue;
|
|
@@ -17208,6 +17539,63 @@ async function replayCodexMcpToolEventsFromSessionLog(context, state) {
|
|
|
17208
17539
|
}
|
|
17209
17540
|
}
|
|
17210
17541
|
}
|
|
17542
|
+
var CODEX_EXIT_GRACE_MS = 3e4;
|
|
17543
|
+
function armCodexExitGraceKill(state) {
|
|
17544
|
+
if (state.exitGraceKillTimer) return;
|
|
17545
|
+
const child = state.process;
|
|
17546
|
+
if (!child) return;
|
|
17547
|
+
const timer = setTimeout(() => {
|
|
17548
|
+
if (child.exitCode !== null || child.killed) return;
|
|
17549
|
+
console.warn(
|
|
17550
|
+
"[codex_app_server] Process did not exit after turn completion; killing process group"
|
|
17551
|
+
);
|
|
17552
|
+
const pid = child.pid;
|
|
17553
|
+
if (pid) {
|
|
17554
|
+
try {
|
|
17555
|
+
process.kill(-pid, "SIGTERM");
|
|
17556
|
+
return;
|
|
17557
|
+
} catch {
|
|
17558
|
+
}
|
|
17559
|
+
}
|
|
17560
|
+
try {
|
|
17561
|
+
child.kill("SIGTERM");
|
|
17562
|
+
} catch {
|
|
17563
|
+
}
|
|
17564
|
+
}, CODEX_EXIT_GRACE_MS);
|
|
17565
|
+
timer.unref?.();
|
|
17566
|
+
state.exitGraceKillTimer = timer;
|
|
17567
|
+
}
|
|
17568
|
+
function disarmCodexExitGraceKill(state) {
|
|
17569
|
+
if (!state.exitGraceKillTimer) return;
|
|
17570
|
+
clearTimeout(state.exitGraceKillTimer);
|
|
17571
|
+
state.exitGraceKillTimer = void 0;
|
|
17572
|
+
}
|
|
17573
|
+
function trackCodexStreamedToolId(state, toolId) {
|
|
17574
|
+
if (!state.codexStreamedToolIds) state.codexStreamedToolIds = /* @__PURE__ */ new Set();
|
|
17575
|
+
if (state.codexStreamedToolIds.has(toolId)) return false;
|
|
17576
|
+
state.codexStreamedToolIds.add(toolId);
|
|
17577
|
+
return true;
|
|
17578
|
+
}
|
|
17579
|
+
function buildCodexMcpToolName(item) {
|
|
17580
|
+
const server = typeof item.server === "string" ? item.server.replace(/_+$/, "") : "";
|
|
17581
|
+
const tool = typeof item.tool === "string" ? item.tool : typeof item.tool_name === "string" ? item.tool_name : "";
|
|
17582
|
+
if (server && tool) return `mcp__${server}__${tool}`;
|
|
17583
|
+
return tool || "MCP Tool";
|
|
17584
|
+
}
|
|
17585
|
+
function emitCodexFileChanges(context, state, itemId, changes) {
|
|
17586
|
+
changes.forEach((change, index) => {
|
|
17587
|
+
if (!change || typeof change !== "object") return;
|
|
17588
|
+
const record = change;
|
|
17589
|
+
const filePath = typeof record.path === "string" ? record.path : "";
|
|
17590
|
+
if (!filePath) return;
|
|
17591
|
+
const kind = typeof record.kind === "string" ? record.kind : "update";
|
|
17592
|
+
const toolName = kind === "add" ? "write_file" : "edit_file";
|
|
17593
|
+
const toolId = changes.length > 1 ? `${itemId}:${index}` : itemId;
|
|
17594
|
+
trackCodexStreamedToolId(state, toolId);
|
|
17595
|
+
void context.presenter.onToolUse(toolName, { file_path: filePath, kind }, toolId);
|
|
17596
|
+
void context.presenter.onToolResult?.(toolId, `${kind} ${filePath}`);
|
|
17597
|
+
});
|
|
17598
|
+
}
|
|
17211
17599
|
function handleCodexStructuredEvent(parsed, context, state) {
|
|
17212
17600
|
const presenter = context.presenter;
|
|
17213
17601
|
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
@@ -17235,6 +17623,7 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17235
17623
|
case "thread.started":
|
|
17236
17624
|
return true;
|
|
17237
17625
|
case "turn.started":
|
|
17626
|
+
disarmCodexExitGraceKill(state);
|
|
17238
17627
|
state.iterations += 1;
|
|
17239
17628
|
return true;
|
|
17240
17629
|
case "session_configured":
|
|
@@ -17243,36 +17632,112 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17243
17632
|
}
|
|
17244
17633
|
return true;
|
|
17245
17634
|
case "task_started":
|
|
17635
|
+
disarmCodexExitGraceKill(state);
|
|
17246
17636
|
state.iterations += 1;
|
|
17247
17637
|
return true;
|
|
17248
17638
|
case "item.started": {
|
|
17249
17639
|
const item = typeof parsed.item === "object" && parsed.item !== null ? parsed.item : null;
|
|
17250
|
-
if (!item || item.type !== "
|
|
17251
|
-
|
|
17252
|
-
|
|
17253
|
-
|
|
17640
|
+
if (!item || typeof item.type !== "string" || typeof item.id !== "string") return true;
|
|
17641
|
+
switch (item.type) {
|
|
17642
|
+
case "command_execution": {
|
|
17643
|
+
if (trackCodexStreamedToolId(state, item.id)) {
|
|
17644
|
+
const command = typeof item.command === "string" ? item.command : "";
|
|
17645
|
+
void presenter.onToolUse("Bash", { command, cwd: context.cwd }, item.id);
|
|
17646
|
+
}
|
|
17647
|
+
return true;
|
|
17648
|
+
}
|
|
17649
|
+
case "mcp_tool_call": {
|
|
17650
|
+
if (trackCodexStreamedToolId(state, item.id)) {
|
|
17651
|
+
const input = typeof item.arguments === "object" && item.arguments !== null ? item.arguments : {};
|
|
17652
|
+
void presenter.onToolUse(buildCodexMcpToolName(item), input, item.id);
|
|
17653
|
+
}
|
|
17654
|
+
return true;
|
|
17655
|
+
}
|
|
17656
|
+
case "web_search": {
|
|
17657
|
+
if (trackCodexStreamedToolId(state, item.id)) {
|
|
17658
|
+
const query = typeof item.query === "string" ? item.query : "";
|
|
17659
|
+
void presenter.onToolUse("web_search", { query }, item.id);
|
|
17660
|
+
}
|
|
17661
|
+
return true;
|
|
17662
|
+
}
|
|
17663
|
+
// Text-bearing and patch/todo items carry no useful live-start payload; they
|
|
17664
|
+
// are surfaced on item.completed. Recognized (do not count as unhandled).
|
|
17665
|
+
case "reasoning":
|
|
17666
|
+
case "agent_message":
|
|
17667
|
+
case "file_change":
|
|
17668
|
+
case "todo_list":
|
|
17669
|
+
return true;
|
|
17670
|
+
default:
|
|
17671
|
+
return false;
|
|
17672
|
+
}
|
|
17254
17673
|
}
|
|
17255
17674
|
case "item.completed": {
|
|
17256
17675
|
const item = typeof parsed.item === "object" && parsed.item !== null ? parsed.item : null;
|
|
17257
17676
|
if (!item || typeof item.type !== "string") return true;
|
|
17258
|
-
|
|
17259
|
-
|
|
17260
|
-
|
|
17261
|
-
|
|
17262
|
-
|
|
17263
|
-
|
|
17677
|
+
switch (item.type) {
|
|
17678
|
+
case "reasoning": {
|
|
17679
|
+
if (typeof item.text === "string") void presenter.onThinking(item.text);
|
|
17680
|
+
return true;
|
|
17681
|
+
}
|
|
17682
|
+
case "agent_message": {
|
|
17683
|
+
if (typeof item.text === "string") {
|
|
17684
|
+
state.summary += `${item.text}
|
|
17264
17685
|
`;
|
|
17265
|
-
|
|
17266
|
-
|
|
17267
|
-
|
|
17268
|
-
|
|
17269
|
-
|
|
17270
|
-
|
|
17271
|
-
|
|
17272
|
-
|
|
17273
|
-
|
|
17686
|
+
void presenter.onAssistantText(item.text);
|
|
17687
|
+
}
|
|
17688
|
+
return true;
|
|
17689
|
+
}
|
|
17690
|
+
case "command_execution": {
|
|
17691
|
+
if (typeof item.id !== "string") return true;
|
|
17692
|
+
if (trackCodexStreamedToolId(state, item.id)) {
|
|
17693
|
+
const command = typeof item.command === "string" ? item.command : "";
|
|
17694
|
+
void presenter.onToolUse("Bash", { command, cwd: context.cwd }, item.id);
|
|
17695
|
+
}
|
|
17696
|
+
const output = typeof item.aggregated_output === "string" ? item.aggregated_output : "";
|
|
17697
|
+
const itemExitCode = typeof item.exit_code === "number" ? item.exit_code : null;
|
|
17698
|
+
const resultText = output.trim().length > 0 ? output : itemExitCode === null ? "" : `Exit code: ${itemExitCode}`;
|
|
17699
|
+
void presenter.onToolResult?.(
|
|
17700
|
+
item.id,
|
|
17701
|
+
resultText,
|
|
17702
|
+
itemExitCode != null && itemExitCode !== 0
|
|
17703
|
+
);
|
|
17704
|
+
return true;
|
|
17705
|
+
}
|
|
17706
|
+
case "mcp_tool_call": {
|
|
17707
|
+
if (typeof item.id !== "string") return true;
|
|
17708
|
+
if (trackCodexStreamedToolId(state, item.id)) {
|
|
17709
|
+
const input = typeof item.arguments === "object" && item.arguments !== null ? item.arguments : {};
|
|
17710
|
+
void presenter.onToolUse(buildCodexMcpToolName(item), input, item.id);
|
|
17711
|
+
}
|
|
17712
|
+
const isError = item.status === "failed" || item.error != null;
|
|
17713
|
+
const payload = item.result ?? item.output ?? item.error ?? {};
|
|
17714
|
+
const resultText = typeof payload === "string" ? payload : JSON.stringify(payload ?? {}, null, 2);
|
|
17715
|
+
void presenter.onToolResult?.(item.id, resultText, isError);
|
|
17716
|
+
return true;
|
|
17717
|
+
}
|
|
17718
|
+
case "file_change": {
|
|
17719
|
+
if (typeof item.id !== "string") return true;
|
|
17720
|
+
const changes = Array.isArray(item.changes) ? item.changes : [];
|
|
17721
|
+
emitCodexFileChanges(context, state, item.id, changes);
|
|
17722
|
+
return true;
|
|
17723
|
+
}
|
|
17724
|
+
case "web_search": {
|
|
17725
|
+
if (typeof item.id !== "string") return true;
|
|
17726
|
+
const query = typeof item.query === "string" ? item.query : "";
|
|
17727
|
+
if (trackCodexStreamedToolId(state, item.id)) {
|
|
17728
|
+
void presenter.onToolUse("web_search", { query }, item.id);
|
|
17729
|
+
}
|
|
17730
|
+
void presenter.onToolResult?.(item.id, query ? `Searched: ${query}` : "");
|
|
17731
|
+
return true;
|
|
17732
|
+
}
|
|
17733
|
+
case "todo_list": {
|
|
17734
|
+
const todos = Array.isArray(item.items) ? item.items : [];
|
|
17735
|
+
void presenter.onTodoWrite?.(todos);
|
|
17736
|
+
return true;
|
|
17737
|
+
}
|
|
17738
|
+
default:
|
|
17739
|
+
return false;
|
|
17274
17740
|
}
|
|
17275
|
-
return true;
|
|
17276
17741
|
}
|
|
17277
17742
|
case "agent_message_delta":
|
|
17278
17743
|
case "agent_message_content_delta": {
|
|
@@ -17294,6 +17759,7 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17294
17759
|
const toolId = typeof parsed.call_id === "string" ? parsed.call_id : `exec-${Date.now()}`;
|
|
17295
17760
|
const command = Array.isArray(parsed.command) ? parsed.command.join(" ") : "";
|
|
17296
17761
|
const cwd = typeof parsed.cwd === "string" ? parsed.cwd : context.cwd;
|
|
17762
|
+
trackCodexStreamedToolId(state, toolId);
|
|
17297
17763
|
void presenter.onToolUse("Bash", { command, cwd }, toolId);
|
|
17298
17764
|
return true;
|
|
17299
17765
|
}
|
|
@@ -17307,6 +17773,7 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17307
17773
|
const toolId = typeof parsed.call_id === "string" ? parsed.call_id : `mcp-${Date.now()}`;
|
|
17308
17774
|
const invocation = typeof parsed.invocation === "object" && parsed.invocation !== null ? parsed.invocation : {};
|
|
17309
17775
|
const tool = typeof invocation.tool_name === "string" ? invocation.tool_name : typeof invocation.tool === "string" ? invocation.tool : "MCP Tool";
|
|
17776
|
+
trackCodexStreamedToolId(state, toolId);
|
|
17310
17777
|
void presenter.onToolUse(tool, invocation, toolId);
|
|
17311
17778
|
return true;
|
|
17312
17779
|
}
|
|
@@ -17344,6 +17811,8 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17344
17811
|
case "task_complete": {
|
|
17345
17812
|
const lastMessage = typeof parsed.last_agent_message === "string" ? parsed.last_agent_message : "";
|
|
17346
17813
|
if (lastMessage.length > 0) state.summary = lastMessage;
|
|
17814
|
+
state.error = void 0;
|
|
17815
|
+
armCodexExitGraceKill(state);
|
|
17347
17816
|
return true;
|
|
17348
17817
|
}
|
|
17349
17818
|
case "turn.completed": {
|
|
@@ -17365,6 +17834,8 @@ function handleCodexStructuredEvent(parsed, context, state) {
|
|
|
17365
17834
|
cacheReadTokens: state.usage.cacheReadTokens,
|
|
17366
17835
|
cacheCreationTokens: state.usage.cacheCreationTokens
|
|
17367
17836
|
});
|
|
17837
|
+
state.error = void 0;
|
|
17838
|
+
armCodexExitGraceKill(state);
|
|
17368
17839
|
return true;
|
|
17369
17840
|
}
|
|
17370
17841
|
case "error": {
|
|
@@ -17409,6 +17880,33 @@ function createCodexRuntimeBackend(command = "codex", defaultArgs = []) {
|
|
|
17409
17880
|
context.config.images,
|
|
17410
17881
|
context.cwd
|
|
17411
17882
|
);
|
|
17883
|
+
const requestedResumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
|
|
17884
|
+
let resumableSessionId = requestedResumeId;
|
|
17885
|
+
let resumeContextPrefix;
|
|
17886
|
+
if (requestedResumeId && !findCodexSessionLog(requestedResumeId, resolveCodexHome(context.env))) {
|
|
17887
|
+
resumableSessionId = void 0;
|
|
17888
|
+
resumeContextPrefix = context.config.resumeFallbackContext?.trim() || void 0;
|
|
17889
|
+
console.warn(
|
|
17890
|
+
`[codex_app_server] No rollout log found for session ${requestedResumeId}; starting a fresh session instead of resuming`
|
|
17891
|
+
);
|
|
17892
|
+
logResumeObservability("agent_resume_fallback_used", {
|
|
17893
|
+
backendKind: "codex_app_server",
|
|
17894
|
+
reason: "rollout_log_missing",
|
|
17895
|
+
requestedResumeId,
|
|
17896
|
+
hadFallbackContext: Boolean(resumeContextPrefix)
|
|
17897
|
+
});
|
|
17898
|
+
emitSessionNotice(
|
|
17899
|
+
context.presenter,
|
|
17900
|
+
"session_resume_failed",
|
|
17901
|
+
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."
|
|
17902
|
+
);
|
|
17903
|
+
}
|
|
17904
|
+
if (resumableSessionId) {
|
|
17905
|
+
logResumeObservability("agent_resume_requested", {
|
|
17906
|
+
backendKind: "codex_app_server",
|
|
17907
|
+
requestedResumeId: resumableSessionId
|
|
17908
|
+
});
|
|
17909
|
+
}
|
|
17412
17910
|
try {
|
|
17413
17911
|
return await createGenericCliBackend({
|
|
17414
17912
|
kind: "codex_app_server",
|
|
@@ -17417,15 +17915,10 @@ function createCodexRuntimeBackend(command = "codex", defaultArgs = []) {
|
|
|
17417
17915
|
args: [],
|
|
17418
17916
|
startupTimeoutMs: CODEX_STARTUP_TIMEOUT_MS,
|
|
17419
17917
|
buildArgs: (ctx) => {
|
|
17420
|
-
const resumeId =
|
|
17918
|
+
const resumeId = resumableSessionId;
|
|
17421
17919
|
const modelArgs = ctx.config.selectedModel?.trim() ? ["--model", ctx.config.selectedModel.trim()] : [];
|
|
17422
17920
|
const effortArgs = buildCodexEffortArgs(ctx.config.selectedEffortLevel);
|
|
17423
|
-
const permissionArgs =
|
|
17424
|
-
"--ask-for-approval",
|
|
17425
|
-
"never",
|
|
17426
|
-
"--sandbox",
|
|
17427
|
-
"danger-full-access"
|
|
17428
|
-
];
|
|
17921
|
+
const permissionArgs = getCodexPermissionArgs(ctx.config);
|
|
17429
17922
|
const imageArgs = imagePaths.flatMap((p) => ["--image", p]);
|
|
17430
17923
|
const baseArgs = resumeId ? [
|
|
17431
17924
|
...permissionArgs,
|
|
@@ -17452,7 +17945,10 @@ function createCodexRuntimeBackend(command = "codex", defaultArgs = []) {
|
|
|
17452
17945
|
},
|
|
17453
17946
|
promptViaStdin: true,
|
|
17454
17947
|
augmentPrompt: (ctx) => {
|
|
17455
|
-
const
|
|
17948
|
+
const promptWithContext = resumeContextPrefix ? `${resumeContextPrefix}
|
|
17949
|
+
|
|
17950
|
+
${ctx.promptText}` : ctx.promptText;
|
|
17951
|
+
const basePrompt = buildPromptWithSystem(ctx.config, promptWithContext);
|
|
17456
17952
|
return ctx.config.mode === "plan" ? buildPlanModePrefix(basePrompt) : basePrompt;
|
|
17457
17953
|
},
|
|
17458
17954
|
parseStructuredLine: parseCodexStructuredLine,
|
|
@@ -17779,7 +18275,10 @@ function buildCursorAgentModelArg(modelId, options) {
|
|
|
17779
18275
|
return `${parsed.baseId}[${overrides.join(",")}]`;
|
|
17780
18276
|
}
|
|
17781
18277
|
function isCursorAgentCliModelId(model) {
|
|
17782
|
-
return model === "auto" || /^composer-/.test(model) || /^gpt
|
|
18278
|
+
return model === "auto" || /^composer-/.test(model) || /^gpt-/.test(model) || /^claude-/.test(model) || /^gemini-/.test(model) || /^grok-/.test(model) || /^kimi-/.test(model);
|
|
18279
|
+
}
|
|
18280
|
+
function isDispatchableCursorModel(model) {
|
|
18281
|
+
return isCursorAgentCliModelId(model) || Boolean(findModelDef("cursor_agent_cli", model));
|
|
17783
18282
|
}
|
|
17784
18283
|
function shouldForceCursorAgent(config) {
|
|
17785
18284
|
if (shouldUseReadOnlyRuntimePermissions(config)) return false;
|
|
@@ -17797,6 +18296,21 @@ function createCursorAgentCliBackend(command = "cursor-agent", defaultArgs = [])
|
|
|
17797
18296
|
kind: "cursor_agent_cli",
|
|
17798
18297
|
supportTier: "structured",
|
|
17799
18298
|
async run(context) {
|
|
18299
|
+
const selectedModel = context.config.selectedModel?.trim();
|
|
18300
|
+
if (selectedModel && !isDispatchableCursorModel(selectedModel)) {
|
|
18301
|
+
const message = `Model "${selectedModel}" is not supported by cursor-agent. Pick a cursor-agent model (or "auto") and send your message again.`;
|
|
18302
|
+
return {
|
|
18303
|
+
success: false,
|
|
18304
|
+
summary: message,
|
|
18305
|
+
filesModified: [],
|
|
18306
|
+
planFilesCreated: [],
|
|
18307
|
+
iterations: 0,
|
|
18308
|
+
error: message,
|
|
18309
|
+
errorKind: "model_mismatch",
|
|
18310
|
+
backendKind: "cursor_agent_cli",
|
|
18311
|
+
supportTier: "structured"
|
|
18312
|
+
};
|
|
18313
|
+
}
|
|
17800
18314
|
const { files: imageFiles, cleanup } = writeImagesToTempFiles(
|
|
17801
18315
|
context.config.images,
|
|
17802
18316
|
context.cwd
|
|
@@ -17826,10 +18340,18 @@ function createCursorAgentCliBackend(command = "cursor-agent", defaultArgs = [])
|
|
|
17826
18340
|
args.push("--mode", "plan");
|
|
17827
18341
|
} else if (ctx.config.mode === "ask") {
|
|
17828
18342
|
args.push("--mode", "ask");
|
|
18343
|
+
} else if (shouldUseReadOnlyRuntimePermissions(ctx.config)) {
|
|
18344
|
+
args.push("--mode", "plan");
|
|
18345
|
+
}
|
|
18346
|
+
if (resumeId) {
|
|
18347
|
+
args.push("--resume", resumeId);
|
|
18348
|
+
logResumeObservability("agent_resume_requested", {
|
|
18349
|
+
backendKind: "cursor_agent_cli",
|
|
18350
|
+
requestedResumeId: resumeId
|
|
18351
|
+
});
|
|
17829
18352
|
}
|
|
17830
|
-
if (resumeId) args.push("--resume", resumeId);
|
|
17831
18353
|
const model = ctx.config.selectedModel?.trim();
|
|
17832
|
-
if (model &&
|
|
18354
|
+
if (model && isDispatchableCursorModel(model)) {
|
|
17833
18355
|
const modelDef = findModelDef("cursor_agent_cli", model);
|
|
17834
18356
|
args.push(
|
|
17835
18357
|
"--model",
|
|
@@ -18292,9 +18814,9 @@ function buildGrokAgentArgs(config, defaultArgs = []) {
|
|
|
18292
18814
|
return args;
|
|
18293
18815
|
}
|
|
18294
18816
|
var DEFAULT_SKILL_ROOTS = [
|
|
18295
|
-
(0,
|
|
18296
|
-
(0,
|
|
18297
|
-
(0,
|
|
18817
|
+
(0, import_path6.join)((0, import_os6.homedir)(), ".agents", "skills"),
|
|
18818
|
+
(0, import_path6.join)((0, import_os6.homedir)(), ".claude", "skills"),
|
|
18819
|
+
(0, import_path6.join)((0, import_os6.homedir)(), ".config", "opencode", "skills")
|
|
18298
18820
|
];
|
|
18299
18821
|
function parseFrontmatter(content) {
|
|
18300
18822
|
if (!content.startsWith("---\n")) return {};
|
|
@@ -18310,12 +18832,12 @@ function parseFrontmatter(content) {
|
|
|
18310
18832
|
return result;
|
|
18311
18833
|
}
|
|
18312
18834
|
function listSkillFiles(root) {
|
|
18313
|
-
if (!(0,
|
|
18835
|
+
if (!(0, import_fs6.existsSync)(root)) return [];
|
|
18314
18836
|
const files = [];
|
|
18315
|
-
for (const entry of (0,
|
|
18837
|
+
for (const entry of (0, import_fs6.readdirSync)(root, { withFileTypes: true })) {
|
|
18316
18838
|
if (!entry.isDirectory()) continue;
|
|
18317
|
-
const skillPath = (0,
|
|
18318
|
-
if ((0,
|
|
18839
|
+
const skillPath = (0, import_path6.join)(root, entry.name, "SKILL.md");
|
|
18840
|
+
if ((0, import_fs6.existsSync)(skillPath)) files.push(skillPath);
|
|
18319
18841
|
}
|
|
18320
18842
|
return files;
|
|
18321
18843
|
}
|
|
@@ -18324,7 +18846,7 @@ function loadInstalledSkills(roots = DEFAULT_SKILL_ROOTS) {
|
|
|
18324
18846
|
for (const root of roots) {
|
|
18325
18847
|
for (const skillPath of listSkillFiles(root)) {
|
|
18326
18848
|
try {
|
|
18327
|
-
const frontmatter = parseFrontmatter((0,
|
|
18849
|
+
const frontmatter = parseFrontmatter((0, import_fs6.readFileSync)(skillPath, "utf8"));
|
|
18328
18850
|
const name = frontmatter.name;
|
|
18329
18851
|
const description = frontmatter.description;
|
|
18330
18852
|
if (!name || !description || skills.has(name)) continue;
|
|
@@ -18757,7 +19279,31 @@ function createSupatestCliBackend(command = "supatest", defaultArgs = []) {
|
|
|
18757
19279
|
"--permission-mode",
|
|
18758
19280
|
getClaudePermissionMode(context.config)
|
|
18759
19281
|
];
|
|
18760
|
-
const
|
|
19282
|
+
const requestedResumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
|
|
19283
|
+
let resumeId = requestedResumeId;
|
|
19284
|
+
let resumeContextPrefix;
|
|
19285
|
+
if (requestedResumeId && !claudeSessionLogExists({
|
|
19286
|
+
sessionId: requestedResumeId,
|
|
19287
|
+
cwd: context.cwd,
|
|
19288
|
+
home: resolveClaudeHome(context.env, "supatest")
|
|
19289
|
+
})) {
|
|
19290
|
+
resumeId = void 0;
|
|
19291
|
+
resumeContextPrefix = context.config.resumeFallbackContext?.trim() || void 0;
|
|
19292
|
+
console.warn(
|
|
19293
|
+
`[supatest_cli] No session log for ${requestedResumeId} under cwd ${context.cwd}; starting a fresh session instead of resuming`
|
|
19294
|
+
);
|
|
19295
|
+
logResumeObservability("agent_resume_fallback_used", {
|
|
19296
|
+
backendKind: "supatest_cli",
|
|
19297
|
+
reason: "session_log_missing_for_cwd",
|
|
19298
|
+
requestedResumeId,
|
|
19299
|
+
hadFallbackContext: Boolean(resumeContextPrefix)
|
|
19300
|
+
});
|
|
19301
|
+
emitSessionNotice(
|
|
19302
|
+
context.presenter,
|
|
19303
|
+
"session_resume_failed",
|
|
19304
|
+
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."
|
|
19305
|
+
);
|
|
19306
|
+
}
|
|
18761
19307
|
if (!resumeId) {
|
|
18762
19308
|
if (context.config.systemPrompt?.trim()) {
|
|
18763
19309
|
args.push("--system-prompt", context.config.systemPrompt.trim());
|
|
@@ -18773,6 +19319,10 @@ function createSupatestCliBackend(command = "supatest", defaultArgs = []) {
|
|
|
18773
19319
|
if (resumeId) {
|
|
18774
19320
|
args.push("--resume", resumeId);
|
|
18775
19321
|
console.info("[supatest_cli] Resuming session", { resumeId });
|
|
19322
|
+
logResumeObservability("agent_resume_requested", {
|
|
19323
|
+
backendKind: "supatest_cli",
|
|
19324
|
+
requestedResumeId: resumeId
|
|
19325
|
+
});
|
|
18776
19326
|
}
|
|
18777
19327
|
args.push(...defaultArgs);
|
|
18778
19328
|
return createGenericCliBackend({
|
|
@@ -18791,7 +19341,10 @@ function createSupatestCliBackend(command = "supatest", defaultArgs = []) {
|
|
|
18791
19341
|
});
|
|
18792
19342
|
}
|
|
18793
19343
|
}
|
|
18794
|
-
const
|
|
19344
|
+
const basePromptText = resumeContextPrefix ? `${resumeContextPrefix}
|
|
19345
|
+
|
|
19346
|
+
${ctx.promptText}` : ctx.promptText;
|
|
19347
|
+
const promptText = ctx.config.mode === "plan" ? buildPlanModePrefix(buildPromptWithSystem(ctx.config, basePromptText)) : buildPromptWithSystem(ctx.config, basePromptText);
|
|
18795
19348
|
if (promptText.trim()) {
|
|
18796
19349
|
contentBlocks.push({ type: "text", text: promptText });
|
|
18797
19350
|
}
|
|
@@ -18821,40 +19374,40 @@ function parseSkillInvocation(text) {
|
|
|
18821
19374
|
function candidateRoots(cwd) {
|
|
18822
19375
|
const roots = [];
|
|
18823
19376
|
if (cwd) {
|
|
18824
|
-
roots.push((0,
|
|
18825
|
-
roots.push((0,
|
|
18826
|
-
roots.push((0,
|
|
18827
|
-
if ((0,
|
|
19377
|
+
roots.push((0, import_path7.join)(cwd, ".agents", "skills"));
|
|
19378
|
+
roots.push((0, import_path7.join)(cwd, ".claude", "skills"));
|
|
19379
|
+
roots.push((0, import_path7.join)(cwd, ".codex", "skills"));
|
|
19380
|
+
if ((0, import_fs7.existsSync)(cwd)) {
|
|
18828
19381
|
let entries = [];
|
|
18829
19382
|
try {
|
|
18830
|
-
entries = (0,
|
|
19383
|
+
entries = (0, import_fs7.readdirSync)(cwd, { withFileTypes: true });
|
|
18831
19384
|
} catch {
|
|
18832
19385
|
}
|
|
18833
19386
|
for (const entry of entries) {
|
|
18834
19387
|
if (!entry.isDirectory()) continue;
|
|
18835
19388
|
if (entry.name.startsWith(".")) continue;
|
|
18836
|
-
const child = (0,
|
|
18837
|
-
roots.push((0,
|
|
18838
|
-
roots.push((0,
|
|
18839
|
-
roots.push((0,
|
|
19389
|
+
const child = (0, import_path7.join)(cwd, entry.name);
|
|
19390
|
+
roots.push((0, import_path7.join)(child, ".agents", "skills"));
|
|
19391
|
+
roots.push((0, import_path7.join)(child, ".claude", "skills"));
|
|
19392
|
+
roots.push((0, import_path7.join)(child, ".codex", "skills"));
|
|
18840
19393
|
}
|
|
18841
19394
|
}
|
|
18842
19395
|
}
|
|
18843
|
-
const home = (0,
|
|
18844
|
-
roots.push((0,
|
|
18845
|
-
roots.push((0,
|
|
18846
|
-
roots.push((0,
|
|
19396
|
+
const home = (0, import_os7.homedir)();
|
|
19397
|
+
roots.push((0, import_path7.join)(home, ".agents", "skills"));
|
|
19398
|
+
roots.push((0, import_path7.join)(home, ".claude", "skills"));
|
|
19399
|
+
roots.push((0, import_path7.join)(home, ".codex", "skills"));
|
|
18847
19400
|
return Array.from(new Set(roots));
|
|
18848
19401
|
}
|
|
18849
19402
|
function resolveSkillInvocation(text, cwd) {
|
|
18850
19403
|
const parsed = parseSkillInvocation(text);
|
|
18851
19404
|
if (!parsed) return null;
|
|
18852
19405
|
for (const root of candidateRoots(cwd)) {
|
|
18853
|
-
const skillPath = (0,
|
|
18854
|
-
if (!(0,
|
|
19406
|
+
const skillPath = (0, import_path7.join)(root, parsed.command, "SKILL.md");
|
|
19407
|
+
if (!(0, import_fs7.existsSync)(skillPath)) continue;
|
|
18855
19408
|
let content;
|
|
18856
19409
|
try {
|
|
18857
|
-
content = (0,
|
|
19410
|
+
content = (0, import_fs7.readFileSync)(skillPath, "utf-8");
|
|
18858
19411
|
} catch {
|
|
18859
19412
|
continue;
|
|
18860
19413
|
}
|
|
@@ -19035,11 +19588,19 @@ var BaseMachineAgent = class _BaseMachineAgent {
|
|
|
19035
19588
|
async runBackendWithActivityHeartbeat(backend, context) {
|
|
19036
19589
|
const intervalMs = this.getActivityHeartbeatIntervalMs();
|
|
19037
19590
|
let heartbeat = null;
|
|
19591
|
+
let livenessProbe = null;
|
|
19592
|
+
context.registerLivenessProbe = (probe) => {
|
|
19593
|
+
livenessProbe = probe;
|
|
19594
|
+
};
|
|
19595
|
+
const emitHeartbeat = () => {
|
|
19596
|
+
const evidence = livenessProbe?.();
|
|
19597
|
+
void this.presenter.onActivity?.("background_work", evidence);
|
|
19598
|
+
};
|
|
19038
19599
|
if (this.presenter.onActivity && intervalMs > 0) {
|
|
19039
|
-
|
|
19600
|
+
emitHeartbeat();
|
|
19040
19601
|
heartbeat = setInterval(() => {
|
|
19041
19602
|
if (!context.abortController.signal.aborted) {
|
|
19042
|
-
|
|
19603
|
+
emitHeartbeat();
|
|
19043
19604
|
}
|
|
19044
19605
|
}, intervalMs);
|
|
19045
19606
|
}
|
|
@@ -19088,10 +19649,6 @@ var BaseMachineAgent = class _BaseMachineAgent {
|
|
|
19088
19649
|
if (agentPrompt) systemPromptParts.push(agentPrompt);
|
|
19089
19650
|
const modeHint = getAlanModePrompt(config.mode);
|
|
19090
19651
|
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
19652
|
const operatorSystemPrompt = config.systemPrompt?.trim();
|
|
19096
19653
|
if (operatorSystemPrompt) systemPromptParts.push(operatorSystemPrompt);
|
|
19097
19654
|
const workingDir = config.worktreePath || safeProjectPath;
|
|
@@ -19164,6 +19721,7 @@ Prefer relative paths and keep your work scoped to this project.`
|
|
|
19164
19721
|
};
|
|
19165
19722
|
if (lastResult.success || !lastResult.error) break;
|
|
19166
19723
|
if (this.abortController?.signal.aborted) break;
|
|
19724
|
+
if (lastResult.errorKind === "model_mismatch") break;
|
|
19167
19725
|
const hadResumeId = runtimeConfig.runtimeSessionId || runtimeConfig.providerSessionId;
|
|
19168
19726
|
if (hadResumeId && !isTransientError(lastResult.error)) {
|
|
19169
19727
|
const overrideResult = await this.onSessionResumeFailure(runtimeConfig, lastResult);
|
|
@@ -19175,10 +19733,25 @@ Prefer relative paths and keep your work scoped to this project.`
|
|
|
19175
19733
|
"[base-machine-agent] Resume failed, retrying without session ID:",
|
|
19176
19734
|
lastResult.error
|
|
19177
19735
|
);
|
|
19736
|
+
const fallbackContext = runtimeConfig.resumeFallbackContext?.trim();
|
|
19737
|
+
logResumeObservability("agent_resume_fallback_used", {
|
|
19738
|
+
backendKind: backend.kind,
|
|
19739
|
+
reason: "retry_fresh_after_resume_failure",
|
|
19740
|
+
errorKind: lastResult.errorKind,
|
|
19741
|
+
hadFallbackContext: Boolean(fallbackContext)
|
|
19742
|
+
});
|
|
19743
|
+
emitSessionNotice(
|
|
19744
|
+
this.presenter,
|
|
19745
|
+
"session_resume_failed",
|
|
19746
|
+
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."
|
|
19747
|
+
);
|
|
19178
19748
|
runtimeConfig = {
|
|
19179
19749
|
...runtimeConfig,
|
|
19180
19750
|
runtimeSessionId: void 0,
|
|
19181
|
-
providerSessionId: void 0
|
|
19751
|
+
providerSessionId: void 0,
|
|
19752
|
+
...fallbackContext ? { task: `${fallbackContext}
|
|
19753
|
+
|
|
19754
|
+
${runtimeConfig.task}` } : {}
|
|
19182
19755
|
};
|
|
19183
19756
|
const retryResult = await this.runBackendWithActivityHeartbeat(backend, {
|
|
19184
19757
|
presenter: this.presenter,
|
|
@@ -19186,7 +19759,7 @@ Prefer relative paths and keep your work scoped to this project.`
|
|
|
19186
19759
|
abortController: this.abortController,
|
|
19187
19760
|
cwd,
|
|
19188
19761
|
env,
|
|
19189
|
-
promptText,
|
|
19762
|
+
promptText: fallbackContext ? this.buildPromptText(runtimeConfig) : promptText,
|
|
19190
19763
|
onProcessSpawned: this.getOnProcessSpawned()
|
|
19191
19764
|
});
|
|
19192
19765
|
lastResult = {
|
|
@@ -19382,14 +19955,14 @@ function normalizePath(candidate, cwd) {
|
|
|
19382
19955
|
} catch {
|
|
19383
19956
|
}
|
|
19384
19957
|
if (path.startsWith("~/")) return null;
|
|
19385
|
-
if ((0,
|
|
19386
|
-
return cwd ? (0,
|
|
19958
|
+
if ((0, import_path8.isAbsolute)(path)) return path;
|
|
19959
|
+
return cwd ? (0, import_path8.join)(cwd, path) : null;
|
|
19387
19960
|
}
|
|
19388
19961
|
function resolveToolCwd(cwd, toolInput) {
|
|
19389
19962
|
if (!isRecord(toolInput)) return cwd;
|
|
19390
19963
|
const raw = typeof toolInput.cwd === "string" ? toolInput.cwd : typeof toolInput.workdir === "string" ? toolInput.workdir : typeof toolInput.workingDirectory === "string" ? toolInput.workingDirectory : null;
|
|
19391
19964
|
if (!raw) return cwd;
|
|
19392
|
-
return (0,
|
|
19965
|
+
return (0, import_path8.isAbsolute)(raw) || !cwd ? raw : (0, import_path8.join)(cwd, raw);
|
|
19393
19966
|
}
|
|
19394
19967
|
function extractBrowserMediaPathHints(content, cwd, toolName, options = {}, kind = "video") {
|
|
19395
19968
|
if (!isAgentBrowserMediaInvocation(toolName, content, options.toolInput, kind)) return [];
|
|
@@ -19432,19 +20005,19 @@ function extractGeneratedImagesFromToolResult(toolId, content, cwd, toolName, op
|
|
|
19432
20005
|
const effectiveCwd = resolveToolCwd(cwd, options.toolInput);
|
|
19433
20006
|
for (const candidate of extractPathCandidates(content, options)) {
|
|
19434
20007
|
const path = normalizePath(candidate, effectiveCwd);
|
|
19435
|
-
if (!path || seen.has(path) || !(0,
|
|
20008
|
+
if (!path || seen.has(path) || !(0, import_fs8.existsSync)(path)) continue;
|
|
19436
20009
|
seen.add(path);
|
|
19437
|
-
const ext = (0,
|
|
20010
|
+
const ext = (0, import_path8.extname)(path).toLowerCase();
|
|
19438
20011
|
const mimeType = IMAGE_MIME_BY_EXT[ext];
|
|
19439
20012
|
if (!mimeType) continue;
|
|
19440
|
-
const stat = (0,
|
|
20013
|
+
const stat = (0, import_fs8.statSync)(path);
|
|
19441
20014
|
if (!stat.isFile() || stat.size <= 0 || stat.size > MAX_GENERATED_IMAGE_BYTES) continue;
|
|
19442
|
-
const buffer = (0,
|
|
20015
|
+
const buffer = (0, import_fs8.readFileSync)(path);
|
|
19443
20016
|
const { width, height } = readImageDimensions2(buffer, mimeType);
|
|
19444
20017
|
images.push({
|
|
19445
20018
|
type: "generated_image",
|
|
19446
20019
|
id: (0, import_crypto3.randomUUID)(),
|
|
19447
|
-
filename: (0,
|
|
20020
|
+
filename: (0, import_path8.basename)(path),
|
|
19448
20021
|
mimeType,
|
|
19449
20022
|
size: buffer.length,
|
|
19450
20023
|
width,
|
|
@@ -19471,20 +20044,20 @@ function extractSessionMediaFromToolResult(toolId, content, cwd, toolName, optio
|
|
|
19471
20044
|
const mimeByExt = kind === "video" ? VIDEO_MIME_BY_EXT : IMAGE_MIME_BY_EXT;
|
|
19472
20045
|
for (const candidate of extractPathCandidates(content, options, extensionsPattern)) {
|
|
19473
20046
|
const path = normalizePath(candidate, effectiveCwd);
|
|
19474
|
-
if (!path || seen.has(path) || !(0,
|
|
20047
|
+
if (!path || seen.has(path) || !(0, import_fs8.existsSync)(path)) continue;
|
|
19475
20048
|
seen.add(path);
|
|
19476
|
-
const ext = (0,
|
|
20049
|
+
const ext = (0, import_path8.extname)(path).toLowerCase();
|
|
19477
20050
|
const mimeType = mimeByExt[ext];
|
|
19478
20051
|
if (!mimeType) continue;
|
|
19479
|
-
const stat = (0,
|
|
20052
|
+
const stat = (0, import_fs8.statSync)(path);
|
|
19480
20053
|
if (!stat.isFile() || stat.size <= 0 || stat.size > maxBytes) continue;
|
|
19481
|
-
const buffer = (0,
|
|
20054
|
+
const buffer = (0, import_fs8.readFileSync)(path);
|
|
19482
20055
|
const dimensions = kind === "image" ? readImageDimensions2(buffer, mimeType) : null;
|
|
19483
20056
|
media.push({
|
|
19484
20057
|
type: "session_media",
|
|
19485
20058
|
kind,
|
|
19486
20059
|
id: (0, import_crypto3.randomUUID)(),
|
|
19487
|
-
filename: (0,
|
|
20060
|
+
filename: (0, import_path8.basename)(path),
|
|
19488
20061
|
mimeType,
|
|
19489
20062
|
size: buffer.length,
|
|
19490
20063
|
width: dimensions?.width,
|
|
@@ -19648,6 +20221,10 @@ var CoreAgent = class _CoreAgent extends BaseMachineAgent {
|
|
|
19648
20221
|
if (!this.childProcess) return false;
|
|
19649
20222
|
const stdin = this.childProcess.stdin;
|
|
19650
20223
|
if (!stdin || stdin.destroyed) return false;
|
|
20224
|
+
if (this.presenter.hasKnownToolUse && !this.presenter.hasKnownToolUse(toolId)) {
|
|
20225
|
+
console.warn(`[CoreAgent] Ignoring tool_response for unknown tool ${toolId}`);
|
|
20226
|
+
return false;
|
|
20227
|
+
}
|
|
19651
20228
|
try {
|
|
19652
20229
|
let message;
|
|
19653
20230
|
if (this.presenter.pendingAskUserToolIds?.has(toolId)) {
|
|
@@ -19902,6 +20479,9 @@ function decodeKey(encodedKey) {
|
|
|
19902
20479
|
function isTerminal(event) {
|
|
19903
20480
|
return TERMINAL_EVENT_TYPES.has(event.type);
|
|
19904
20481
|
}
|
|
20482
|
+
function isBackgroundHeartbeat(event) {
|
|
20483
|
+
return event.type === "activity" && event.status === "background_work";
|
|
20484
|
+
}
|
|
19905
20485
|
function truncateUtf8(value2, maxBytes) {
|
|
19906
20486
|
const encoded = Buffer.from(value2, "utf8");
|
|
19907
20487
|
if (encoded.length <= maxBytes) return value2;
|
|
@@ -20050,6 +20630,23 @@ var EncryptedEventOutbox = class {
|
|
|
20050
20630
|
isDroppable(entry) {
|
|
20051
20631
|
return entry.eventId !== this.inFlightEventId && entry.kind !== "condensation_marker" && !isTerminal(entry.event);
|
|
20052
20632
|
}
|
|
20633
|
+
/**
|
|
20634
|
+
* Pick the next droppable entry to evict. Heartbeats are always evicted
|
|
20635
|
+
* first (oldest heartbeat first) since they carry no transcript content;
|
|
20636
|
+
* only once none remain do we fall back to the oldest droppable entry of
|
|
20637
|
+
* any kind. Without this, oldest-first eviction treats a background_work
|
|
20638
|
+
* heartbeat exactly like real transcript content, so a long offline stretch
|
|
20639
|
+
* (heartbeats every 10s, never removed by an ack since nothing is
|
|
20640
|
+
* connected) evicts genuine early events — including `start` — ahead of
|
|
20641
|
+
* the heartbeats that caused the overflow.
|
|
20642
|
+
*/
|
|
20643
|
+
pickDropCandidate(matches) {
|
|
20644
|
+
const heartbeat = this.entries.find(
|
|
20645
|
+
(entry) => matches(entry) && this.isDroppable(entry) && isBackgroundHeartbeat(entry.event)
|
|
20646
|
+
);
|
|
20647
|
+
if (heartbeat) return heartbeat;
|
|
20648
|
+
return this.entries.find((entry) => matches(entry) && this.isDroppable(entry));
|
|
20649
|
+
}
|
|
20053
20650
|
ensureCondensationMarker(runId, conversationId) {
|
|
20054
20651
|
if (this.entries.some((entry) => entry.runId === runId && entry.kind === "condensation_marker")) {
|
|
20055
20652
|
return;
|
|
@@ -20077,7 +20674,7 @@ var EncryptedEventOutbox = class {
|
|
|
20077
20674
|
const runIds = preferredRunId ? [preferredRunId] : [...new Set(this.entries.map((entry) => entry.runId))];
|
|
20078
20675
|
for (const runId of runIds) {
|
|
20079
20676
|
while (this.entries.filter((entry) => entry.runId === runId && this.isDroppable(entry)).length > this.maxIntermediateEventsPerRun) {
|
|
20080
|
-
const drop = this.
|
|
20677
|
+
const drop = this.pickDropCandidate((entry) => entry.runId === runId);
|
|
20081
20678
|
if (!drop) break;
|
|
20082
20679
|
this.entries = this.entries.filter((entry) => entry.eventId !== drop.eventId);
|
|
20083
20680
|
lossCountByRunId.set(runId, (lossCountByRunId.get(runId) ?? 0) + 1);
|
|
@@ -20085,7 +20682,7 @@ var EncryptedEventOutbox = class {
|
|
|
20085
20682
|
}
|
|
20086
20683
|
}
|
|
20087
20684
|
while (encryptedFileSize(this.entries) > this.maxEncryptedBytes) {
|
|
20088
|
-
const drop = this.
|
|
20685
|
+
const drop = this.pickDropCandidate(() => true);
|
|
20089
20686
|
if (drop) {
|
|
20090
20687
|
this.entries = this.entries.filter((entry) => entry.eventId !== drop.eventId);
|
|
20091
20688
|
lossCountByRunId.set(drop.runId, (lossCountByRunId.get(drop.runId) ?? 0) + 1);
|
|
@@ -20104,7 +20701,7 @@ var EncryptedEventOutbox = class {
|
|
|
20104
20701
|
this.ensureCondensationMarker(runId, lossConversationByRunId.get(runId));
|
|
20105
20702
|
}
|
|
20106
20703
|
while (encryptedFileSize(this.entries) > this.maxEncryptedBytes) {
|
|
20107
|
-
const drop = this.
|
|
20704
|
+
const drop = this.pickDropCandidate(() => true);
|
|
20108
20705
|
if (!drop) break;
|
|
20109
20706
|
this.entries = this.entries.filter((entry) => entry.eventId !== drop.eventId);
|
|
20110
20707
|
lossCountByRunId.set(drop.runId, (lossCountByRunId.get(drop.runId) ?? 0) + 1);
|
|
@@ -21029,6 +21626,10 @@ function assertValidExistingToml(path, content) {
|
|
|
21029
21626
|
`Existing config is malformed; no changes were applied. Review the backup at ${backupPath}`
|
|
21030
21627
|
);
|
|
21031
21628
|
}
|
|
21629
|
+
var CODEX_ALAN_TOML_SECTION = "mcp_servers.alan";
|
|
21630
|
+
function isCodexAlanTomlSection(section) {
|
|
21631
|
+
return section === CODEX_ALAN_TOML_SECTION || section.startsWith(`${CODEX_ALAN_TOML_SECTION}.`);
|
|
21632
|
+
}
|
|
21032
21633
|
function mergeCodexAlanSection(path, existingContent, desiredContent) {
|
|
21033
21634
|
if (existingContent === null || existingContent.trim() === "") return desiredContent;
|
|
21034
21635
|
assertValidExistingToml(path, existingContent);
|
|
@@ -21038,7 +21639,7 @@ function mergeCodexAlanSection(path, existingContent, desiredContent) {
|
|
|
21038
21639
|
for (const line of lines) {
|
|
21039
21640
|
const section = line.trim().match(/^\[([^\]]+)]$/)?.[1];
|
|
21040
21641
|
if (section) {
|
|
21041
|
-
insideAlanSection = section
|
|
21642
|
+
insideAlanSection = isCodexAlanTomlSection(section);
|
|
21042
21643
|
}
|
|
21043
21644
|
if (!insideAlanSection) retained.push(line);
|
|
21044
21645
|
}
|
|
@@ -21403,6 +22004,10 @@ var ANTIGRAVITY_MODEL_DISCOVERY_TIMEOUT_MS = 12e3;
|
|
|
21403
22004
|
var OPENCODE_ZEN_MODEL_IDS = OPENCODE_ZEN_MODELS.map((model) => model.id);
|
|
21404
22005
|
var CLAUDE_MODEL_ID_PATTERN = /claude-(?:sonnet|opus|haiku|fable)-[a-z0-9][a-z0-9-]*/g;
|
|
21405
22006
|
var CLAUDE_MODEL_ALIASES = ["sonnet", "opus", "haiku", "fable"];
|
|
22007
|
+
var CLAUDE_MODEL_QUALIFIER_SEGMENTS = /* @__PURE__ */ new Set(["fast", "thinking", "latest", "preview"]);
|
|
22008
|
+
var PROTECTED_CATALOG_MODEL_IDS = new Set(
|
|
22009
|
+
AVAILABLE_MODELS.map((model) => model.id).filter((id) => id.length > 0)
|
|
22010
|
+
);
|
|
21406
22011
|
var CATALOG_MODEL_IDS_BY_PROVIDER = Object.fromEntries(
|
|
21407
22012
|
Object.entries(PROVIDER_MODELS).map(([provider, models]) => [
|
|
21408
22013
|
provider,
|
|
@@ -21493,15 +22098,22 @@ function extractModelIdsFromLabeledList(value2) {
|
|
|
21493
22098
|
}
|
|
21494
22099
|
function isValidClaudeModelId(id) {
|
|
21495
22100
|
if (CLAUDE_MODEL_ALIASES.includes(id)) return true;
|
|
21496
|
-
|
|
22101
|
+
const match = id.match(/^claude-(sonnet|opus|haiku|fable)-([a-z0-9][a-z0-9-]*)$/);
|
|
22102
|
+
if (!match) return false;
|
|
21497
22103
|
if (id.includes(".")) return false;
|
|
21498
22104
|
if (/-\d{8}$/.test(id)) return false;
|
|
21499
22105
|
if (/-v\d+$/.test(id)) return false;
|
|
22106
|
+
for (const segment of match[2].split("-")) {
|
|
22107
|
+
if (/^\d+$/.test(segment)) continue;
|
|
22108
|
+
if (CLAUDE_MODEL_QUALIFIER_SEGMENTS.has(segment)) continue;
|
|
22109
|
+
return false;
|
|
22110
|
+
}
|
|
21500
22111
|
return true;
|
|
21501
22112
|
}
|
|
21502
22113
|
function filterClaudePrefixModelIds(ids) {
|
|
21503
22114
|
const set = new Set(ids);
|
|
21504
22115
|
return ids.filter((id) => {
|
|
22116
|
+
if (PROTECTED_CATALOG_MODEL_IDS.has(id)) return true;
|
|
21505
22117
|
for (const other of set) {
|
|
21506
22118
|
if (other !== id && other.startsWith(`${id}-`)) return false;
|
|
21507
22119
|
}
|
|
@@ -21824,7 +22436,7 @@ var RunStartGate = class {
|
|
|
21824
22436
|
};
|
|
21825
22437
|
|
|
21826
22438
|
// src/version.ts
|
|
21827
|
-
var AGENT_VERSION = "0.1.
|
|
22439
|
+
var AGENT_VERSION = "0.1.39";
|
|
21828
22440
|
|
|
21829
22441
|
// src/workspace-relocation.ts
|
|
21830
22442
|
var import_node_child_process3 = require("child_process");
|
|
@@ -23180,14 +23792,29 @@ var USER_INTERRUPTED_RESULT = {
|
|
|
23180
23792
|
filesModified: [],
|
|
23181
23793
|
planFilesCreated: [],
|
|
23182
23794
|
iterations: 0,
|
|
23183
|
-
error: "Interrupted by user"
|
|
23795
|
+
error: "Interrupted by user",
|
|
23796
|
+
errorKind: "user"
|
|
23184
23797
|
};
|
|
23798
|
+
function buildAbortResult(reason) {
|
|
23799
|
+
if (reason === "user") return USER_INTERRUPTED_RESULT;
|
|
23800
|
+
const message = SYSTEM_ABORT_RUN_ERRORS[reason];
|
|
23801
|
+
return {
|
|
23802
|
+
success: false,
|
|
23803
|
+
summary: message,
|
|
23804
|
+
filesModified: [],
|
|
23805
|
+
planFilesCreated: [],
|
|
23806
|
+
iterations: 0,
|
|
23807
|
+
error: message,
|
|
23808
|
+
errorKind: reason
|
|
23809
|
+
};
|
|
23810
|
+
}
|
|
23185
23811
|
function abortActiveAgent(entry, options) {
|
|
23186
23812
|
const awaitingAsk = (entry.presenter.pendingAskUserToolIds?.size ?? 0) > 0;
|
|
23187
23813
|
if (awaitingAsk && !options?.userInitiated) {
|
|
23188
23814
|
return;
|
|
23189
23815
|
}
|
|
23190
|
-
|
|
23816
|
+
const reason = options?.reason ?? (options?.userInitiated ? "user" : "runner_stalled");
|
|
23817
|
+
entry.presenter.onComplete(buildAbortResult(reason));
|
|
23191
23818
|
entry.agent.kill();
|
|
23192
23819
|
}
|
|
23193
23820
|
function isProviderProcessAlive(pid) {
|
|
@@ -23296,7 +23923,11 @@ function flushPendingDeliveryLostEvents(input) {
|
|
|
23296
23923
|
if (input.pending.length === 0) return;
|
|
23297
23924
|
for (const item of input.pending) {
|
|
23298
23925
|
if (input.eventOutbox.hasTerminalEvent(item.runId)) continue;
|
|
23299
|
-
|
|
23926
|
+
const activeEntry = input.activeAgents.get(item.runId);
|
|
23927
|
+
if (activeEntry) {
|
|
23928
|
+
if (activeEntry.stage === "delivery_lost") activeEntry.stage = "running";
|
|
23929
|
+
continue;
|
|
23930
|
+
}
|
|
23300
23931
|
input.eventOutbox.enqueue(item.conversationId, item.runId, {
|
|
23301
23932
|
type: "session_error",
|
|
23302
23933
|
error: DELIVERY_LOST_ERROR
|
|
@@ -23320,7 +23951,7 @@ function reconcileActiveRuns(input) {
|
|
|
23320
23951
|
input.pushLog?.(`reconciled dead provider pid run=${runId} (awaiting ask, no interrupt)`);
|
|
23321
23952
|
continue;
|
|
23322
23953
|
}
|
|
23323
|
-
abortActiveAgent(entry);
|
|
23954
|
+
abortActiveAgent(entry, { reason: staleByDeadPid ? "provider_exited" : "runner_stalled" });
|
|
23324
23955
|
input.activeAgents.delete(runId);
|
|
23325
23956
|
if (staleByDeadPid) {
|
|
23326
23957
|
input.pushLog?.(`reconciled dead provider pid run=${runId}`);
|
|
@@ -23384,6 +24015,7 @@ var RuntimePresenter = class {
|
|
|
23384
24015
|
emitEvent(event) {
|
|
23385
24016
|
if (this.terminalFenced) return;
|
|
23386
24017
|
this.touchRunnerActivity?.();
|
|
24018
|
+
if (isBackgroundHeartbeat(event) && !this.socket.connected) return;
|
|
23387
24019
|
this.eventOutbox.enqueue(this.conversationId, this.runId, event);
|
|
23388
24020
|
this.eventOutbox.flush(this.socket);
|
|
23389
24021
|
}
|
|
@@ -23411,6 +24043,9 @@ var RuntimePresenter = class {
|
|
|
23411
24043
|
onLog(message) {
|
|
23412
24044
|
console.error(message);
|
|
23413
24045
|
}
|
|
24046
|
+
onNotice(kind, message) {
|
|
24047
|
+
this.emitEvent({ type: "session_notice", kind, message, ts: Date.now() });
|
|
24048
|
+
}
|
|
23414
24049
|
onAssistantText(text) {
|
|
23415
24050
|
this.emitEvent({ type: "text", text, ts: Date.now() });
|
|
23416
24051
|
}
|
|
@@ -23510,8 +24145,8 @@ var RuntimePresenter = class {
|
|
|
23510
24145
|
onCheckpoint(id) {
|
|
23511
24146
|
this.emitEvent({ type: "checkpoint", id });
|
|
23512
24147
|
}
|
|
23513
|
-
onActivity(status) {
|
|
23514
|
-
this.emitEvent({ type: "activity", status, ts: Date.now() });
|
|
24148
|
+
onActivity(status, evidence) {
|
|
24149
|
+
this.emitEvent({ type: "activity", status, ts: Date.now(), ...evidence ? { evidence } : {} });
|
|
23515
24150
|
}
|
|
23516
24151
|
onOpenBrowserTab(browserId, url2) {
|
|
23517
24152
|
this.emitEvent({ type: "open_browser_tab", browserId, ...url2 ? { url: url2 } : {} });
|
|
@@ -24297,16 +24932,22 @@ async function startDaemon(args) {
|
|
|
24297
24932
|
flushWorkspaceRelocations();
|
|
24298
24933
|
}
|
|
24299
24934
|
}
|
|
24300
|
-
if (workspaceReadiness.ready && expectedRepoUrls.length > 0 && !isAlanDefaultWorkspace(cwd)
|
|
24301
|
-
|
|
24302
|
-
|
|
24303
|
-
|
|
24304
|
-
|
|
24305
|
-
|
|
24306
|
-
|
|
24307
|
-
|
|
24308
|
-
|
|
24309
|
-
|
|
24935
|
+
if (workspaceReadiness.ready && expectedRepoUrls.length > 0 && !isAlanDefaultWorkspace(cwd)) {
|
|
24936
|
+
const worktreePaths = payload.worktreeInfo?.allWorktrees?.map((worktree) => worktree.worktreePath).filter((worktreePath) => Boolean(worktreePath));
|
|
24937
|
+
const repositoryMismatch = worktreePaths && worktreePaths.length > 0 ? worktreePaths.some(
|
|
24938
|
+
(worktreePath) => !workspaceMatchesExpectedRepositories({
|
|
24939
|
+
workspacePath: worktreePath,
|
|
24940
|
+
expectedRepoUrls
|
|
24941
|
+
})
|
|
24942
|
+
) : !workspaceMatchesExpectedRepositories({ workspacePath: cwd, expectedRepoUrls });
|
|
24943
|
+
if (repositoryMismatch) {
|
|
24944
|
+
socket.emit("agent.rejected", {
|
|
24945
|
+
runId: payload.runId,
|
|
24946
|
+
message: "This folder belongs to a different repository. Choose the correct project folder and try again.",
|
|
24947
|
+
code: "workspace_repository_mismatch"
|
|
24948
|
+
});
|
|
24949
|
+
return;
|
|
24950
|
+
}
|
|
24310
24951
|
}
|
|
24311
24952
|
if (workspaceRequired) workspaceAccessTracker.track(cwd, workspaceReadiness);
|
|
24312
24953
|
const runnerSummary = summarizeRunnerStatus({
|
|
@@ -24604,6 +25245,7 @@ async function startDaemon(args) {
|
|
|
24604
25245
|
selectedContextWindow: payload.selectedContextWindow ?? null,
|
|
24605
25246
|
selectedEffortLevel: payload.selectedEffortLevel ?? null,
|
|
24606
25247
|
providerSessionId: payload.providerSessionId,
|
|
25248
|
+
resumeFallbackContext: payload.resumeFallbackContext,
|
|
24607
25249
|
taskMeta: payload.taskMeta,
|
|
24608
25250
|
prMeta: payload.prMeta,
|
|
24609
25251
|
conversationId: payload.conversationId,
|
|
@@ -24640,11 +25282,13 @@ async function startDaemon(args) {
|
|
|
24640
25282
|
if (!payload?.runId) return;
|
|
24641
25283
|
const entry = activeAgents.get(payload.runId);
|
|
24642
25284
|
if (!entry) return;
|
|
24643
|
-
|
|
25285
|
+
const reason = payload.reason === "watchdog_timeout" ? "watchdog_timeout" : "user";
|
|
25286
|
+
abortActiveAgent(entry, { userInitiated: true, reason });
|
|
24644
25287
|
activeAgents.delete(payload.runId);
|
|
24645
|
-
pushLog(`aborted run=${payload.runId}`);
|
|
25288
|
+
pushLog(`aborted run=${payload.runId} reason=${reason}`);
|
|
24646
25289
|
console.info("[alan-agent] Agent run aborted", {
|
|
24647
25290
|
runId: payload.runId,
|
|
25291
|
+
reason,
|
|
24648
25292
|
agentVersion: AGENT_VERSION
|
|
24649
25293
|
});
|
|
24650
25294
|
});
|
|
@@ -24652,7 +25296,9 @@ async function startDaemon(args) {
|
|
|
24652
25296
|
"agent.tool_response",
|
|
24653
25297
|
(payload) => {
|
|
24654
25298
|
if (!payload?.toolId || typeof payload.response !== "string") return;
|
|
24655
|
-
const entries = payload.runId ? [[payload.runId, activeAgents.get(payload.runId)]] : [...activeAgents.entries()]
|
|
25299
|
+
const entries = payload.runId ? [[payload.runId, activeAgents.get(payload.runId)]] : payload.conversationId ? [...activeAgents.entries()].filter(
|
|
25300
|
+
([, entry]) => entry.conversationId === payload.conversationId
|
|
25301
|
+
) : [...activeAgents.entries()];
|
|
24656
25302
|
const delivered = entries.some(([runId, entry]) => {
|
|
24657
25303
|
if (!entry || !verifyActiveWorkspaceLease(runId, entry)) return false;
|
|
24658
25304
|
return entry.agent.sendToolResponse(payload.toolId, payload.response) === true;
|
|
@@ -24660,8 +25306,8 @@ async function startDaemon(args) {
|
|
|
24660
25306
|
if (!delivered) {
|
|
24661
25307
|
pushLog(`tool_response missed tool=${payload.toolId}`);
|
|
24662
25308
|
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;
|
|
25309
|
+
const resolvedRunId = payload.runId ?? entries[0]?.[0] ?? fallbackRun?.[0];
|
|
25310
|
+
const conversationId = payload.conversationId ?? (payload.runId ? activeAgents.get(payload.runId)?.conversationId : void 0) ?? fallbackRun?.[1].conversationId;
|
|
24665
25311
|
if (conversationId && resolvedRunId) {
|
|
24666
25312
|
eventOutbox.enqueue(conversationId, resolvedRunId, {
|
|
24667
25313
|
type: "error",
|
|
@@ -25444,12 +26090,18 @@ var WebPresenter = class {
|
|
|
25444
26090
|
onLog(message) {
|
|
25445
26091
|
console.error(message);
|
|
25446
26092
|
}
|
|
26093
|
+
onNotice(kind, message) {
|
|
26094
|
+
this.wsClient.emitEvent({ type: "session_notice", kind, message, ts: Date.now() });
|
|
26095
|
+
}
|
|
25447
26096
|
onAssistantText(text) {
|
|
25448
26097
|
this.wsClient.emitEvent({ type: "text", text, ts: Date.now() });
|
|
25449
26098
|
}
|
|
25450
26099
|
onThinking(text) {
|
|
25451
26100
|
this.wsClient.emitEvent({ type: "thinking", text, ts: Date.now() });
|
|
25452
26101
|
}
|
|
26102
|
+
hasKnownToolUse(toolId) {
|
|
26103
|
+
return this.toolNamesById.has(toolId);
|
|
26104
|
+
}
|
|
25453
26105
|
onToolUse(tool, input, toolId, parentToolUseId) {
|
|
25454
26106
|
this.toolNamesById.set(toolId, tool);
|
|
25455
26107
|
this.toolInputsById.set(toolId, input);
|
|
@@ -25541,8 +26193,13 @@ var WebPresenter = class {
|
|
|
25541
26193
|
onCheckpoint(id) {
|
|
25542
26194
|
this.wsClient.emitEvent({ type: "checkpoint", id });
|
|
25543
26195
|
}
|
|
25544
|
-
onActivity(status) {
|
|
25545
|
-
this.wsClient.emitEvent({
|
|
26196
|
+
onActivity(status, evidence) {
|
|
26197
|
+
this.wsClient.emitEvent({
|
|
26198
|
+
type: "activity",
|
|
26199
|
+
status,
|
|
26200
|
+
ts: Date.now(),
|
|
26201
|
+
...evidence ? { evidence } : {}
|
|
26202
|
+
});
|
|
25546
26203
|
}
|
|
25547
26204
|
onOpenBrowserTab(browserId, url2) {
|
|
25548
26205
|
this.wsClient.emitEvent({ type: "open_browser_tab", browserId, ...url2 ? { url: url2 } : {} });
|
|
@@ -25583,7 +26240,7 @@ var WebPresenter = class {
|
|
|
25583
26240
|
};
|
|
25584
26241
|
|
|
25585
26242
|
// src/ws-client.ts
|
|
25586
|
-
var
|
|
26243
|
+
var import_node_crypto5 = require("crypto");
|
|
25587
26244
|
|
|
25588
26245
|
// ../shared/dist/agent-liveness.js
|
|
25589
26246
|
var AGENT_LIVENESS_PROTOCOL_VERSION = 1;
|
|
@@ -25610,7 +26267,117 @@ var agentProbeAckSchema = external_exports.object({
|
|
|
25610
26267
|
protocolVersion: external_exports.number().int().positive().optional()
|
|
25611
26268
|
});
|
|
25612
26269
|
|
|
26270
|
+
// src/sandbox-outbox.ts
|
|
26271
|
+
var import_node_crypto4 = require("crypto");
|
|
26272
|
+
var import_node_fs13 = require("fs");
|
|
26273
|
+
var SANDBOX_EVENT_OUTBOX_ENV = "ALAN_EVENT_OUTBOX_ENABLED";
|
|
26274
|
+
function isSandboxEventOutboxEnabled(env = process.env) {
|
|
26275
|
+
return env[SANDBOX_EVENT_OUTBOX_ENV] === "true";
|
|
26276
|
+
}
|
|
26277
|
+
function deriveSandboxOutboxKey(sessionToken) {
|
|
26278
|
+
if (!sessionToken) {
|
|
26279
|
+
throw new Error("sandbox event outbox requires ALAN_SESSION_TOKEN for key derivation");
|
|
26280
|
+
}
|
|
26281
|
+
return (0, import_node_crypto4.createHash)("sha256").update(sessionToken, "utf8").digest("base64url");
|
|
26282
|
+
}
|
|
26283
|
+
function sandboxOutboxSpoolPath(conversationId) {
|
|
26284
|
+
return `/tmp/alan-agent-outbox-${conversationId}.enc`;
|
|
26285
|
+
}
|
|
26286
|
+
function createSandboxEventOutbox(input) {
|
|
26287
|
+
const path = input.spoolPath ?? sandboxOutboxSpoolPath(input.conversationId);
|
|
26288
|
+
const key = deriveSandboxOutboxKey(input.sessionToken);
|
|
26289
|
+
try {
|
|
26290
|
+
return new EncryptedEventOutbox(path, key, input.pushLog, input.options);
|
|
26291
|
+
} catch (error) {
|
|
26292
|
+
input.pushLog?.(
|
|
26293
|
+
`sandbox_outbox_spool_reset ${error instanceof Error ? error.message : String(error)}`
|
|
26294
|
+
);
|
|
26295
|
+
(0, import_node_fs13.rmSync)(path, { force: true });
|
|
26296
|
+
return new EncryptedEventOutbox(path, key, input.pushLog, input.options);
|
|
26297
|
+
}
|
|
26298
|
+
}
|
|
26299
|
+
var SandboxOutboxSocket = class {
|
|
26300
|
+
constructor(socket) {
|
|
26301
|
+
this.socket = socket;
|
|
26302
|
+
}
|
|
26303
|
+
seqByEventId = /* @__PURE__ */ new Map();
|
|
26304
|
+
seqCounter = 0;
|
|
26305
|
+
get connected() {
|
|
26306
|
+
return this.socket.connected;
|
|
26307
|
+
}
|
|
26308
|
+
emit(_event, payload, acknowledge) {
|
|
26309
|
+
let seq = this.seqByEventId.get(payload.eventId);
|
|
26310
|
+
if (seq === void 0) {
|
|
26311
|
+
this.seqCounter += 1;
|
|
26312
|
+
seq = this.seqCounter;
|
|
26313
|
+
this.seqByEventId.set(payload.eventId, seq);
|
|
26314
|
+
}
|
|
26315
|
+
const wirePayload = { ...payload.event, eventId: payload.eventId, seq };
|
|
26316
|
+
this.socket.emit("agent_event", wirePayload, (result) => {
|
|
26317
|
+
if (result?.ok) this.seqByEventId.delete(payload.eventId);
|
|
26318
|
+
acknowledge({ ok: Boolean(result?.ok), retryable: result?.retryable });
|
|
26319
|
+
});
|
|
26320
|
+
}
|
|
26321
|
+
};
|
|
26322
|
+
var SandboxEventDispatcher = class {
|
|
26323
|
+
conversationId;
|
|
26324
|
+
outbox;
|
|
26325
|
+
outboxSocket;
|
|
26326
|
+
currentRunId;
|
|
26327
|
+
constructor(input) {
|
|
26328
|
+
this.conversationId = input.conversationId;
|
|
26329
|
+
if (!input.enabled) return;
|
|
26330
|
+
try {
|
|
26331
|
+
this.outbox = createSandboxEventOutbox({
|
|
26332
|
+
conversationId: input.conversationId,
|
|
26333
|
+
sessionToken: input.sessionToken,
|
|
26334
|
+
pushLog: input.pushLog,
|
|
26335
|
+
options: input.options,
|
|
26336
|
+
spoolPath: input.spoolPath
|
|
26337
|
+
});
|
|
26338
|
+
this.outboxSocket = new SandboxOutboxSocket(input.socket);
|
|
26339
|
+
} catch (error) {
|
|
26340
|
+
input.onInitError?.(error);
|
|
26341
|
+
this.outbox = void 0;
|
|
26342
|
+
this.outboxSocket = void 0;
|
|
26343
|
+
}
|
|
26344
|
+
}
|
|
26345
|
+
/** True when events are being durably queued rather than fired-and-forgotten. */
|
|
26346
|
+
get active() {
|
|
26347
|
+
return this.outbox !== void 0 && this.outboxSocket !== void 0;
|
|
26348
|
+
}
|
|
26349
|
+
/** Set the run id used to bucket per-run caps / terminal condensation. */
|
|
26350
|
+
setRunId(runId) {
|
|
26351
|
+
this.currentRunId = runId;
|
|
26352
|
+
}
|
|
26353
|
+
/**
|
|
26354
|
+
* Durably deliver one event (enqueue-before-send, remove-on-ack). When the
|
|
26355
|
+
* outbox is inactive, defers to `fireAndForget` so behaviour is unchanged.
|
|
26356
|
+
*/
|
|
26357
|
+
emit(event, fireAndForget) {
|
|
26358
|
+
if (!this.outbox || !this.outboxSocket) {
|
|
26359
|
+
fireAndForget(event);
|
|
26360
|
+
return;
|
|
26361
|
+
}
|
|
26362
|
+
this.outbox.enqueue(this.conversationId, this.currentRunId ?? this.conversationId, event);
|
|
26363
|
+
this.outbox.flush(this.outboxSocket);
|
|
26364
|
+
}
|
|
26365
|
+
/** Replay un-acked events in order — called on (re)connect before new events. */
|
|
26366
|
+
flush() {
|
|
26367
|
+
if (this.outbox && this.outboxSocket) this.outbox.flush(this.outboxSocket);
|
|
26368
|
+
}
|
|
26369
|
+
/** Reset in-flight state on disconnect so the next flush replays from the head. */
|
|
26370
|
+
disconnect() {
|
|
26371
|
+
this.outbox?.disconnect();
|
|
26372
|
+
}
|
|
26373
|
+
/** Diagnostics: number of events still awaiting a server ack. */
|
|
26374
|
+
pendingCount() {
|
|
26375
|
+
return this.outbox?.pendingCount() ?? 0;
|
|
26376
|
+
}
|
|
26377
|
+
};
|
|
26378
|
+
|
|
25613
26379
|
// src/ws-client.ts
|
|
26380
|
+
var ACCEPTED_MESSAGE_ID_CACHE_SIZE = 200;
|
|
25614
26381
|
var WSClient = class {
|
|
25615
26382
|
constructor(wsUrl, sessionId, taskId, token, callbacks, agentVersion, lifecycle) {
|
|
25616
26383
|
this.lifecycle = lifecycle;
|
|
@@ -25629,16 +26396,63 @@ var WSClient = class {
|
|
|
25629
26396
|
upgrade: false,
|
|
25630
26397
|
extraHeaders: { "ngrok-skip-browser-warning": "1" }
|
|
25631
26398
|
});
|
|
26399
|
+
const outboxEnabled = isSandboxEventOutboxEnabled();
|
|
26400
|
+
this.eventDispatcher = new SandboxEventDispatcher({
|
|
26401
|
+
conversationId: sessionId,
|
|
26402
|
+
sessionToken: token,
|
|
26403
|
+
enabled: outboxEnabled,
|
|
26404
|
+
socket: this.socket,
|
|
26405
|
+
pushLog: (message) => this.lifecycle?.info("sandbox_agent_event_outbox", { detail: message }),
|
|
26406
|
+
onInitError: (error) => this.lifecycle?.warn("sandbox_agent_event_outbox_init_failed", {
|
|
26407
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
26408
|
+
})
|
|
26409
|
+
});
|
|
26410
|
+
if (this.eventDispatcher.active) {
|
|
26411
|
+
this.lifecycle?.info("sandbox_agent_event_outbox_enabled", { conversationId: sessionId });
|
|
26412
|
+
}
|
|
25632
26413
|
this.setupListeners(callbacks);
|
|
25633
26414
|
}
|
|
25634
26415
|
socket;
|
|
25635
26416
|
/** Stable id for this agent process; fences stale heartbeats server-side. */
|
|
25636
|
-
agentSessionId = (0,
|
|
26417
|
+
agentSessionId = (0, import_node_crypto5.randomUUID)();
|
|
25637
26418
|
/** Monotonic heartbeat sequence — advances on every hello + heartbeat. */
|
|
25638
26419
|
heartbeatSeq = 0;
|
|
25639
26420
|
heartbeatTimer = null;
|
|
25640
26421
|
agentVersion;
|
|
25641
26422
|
getRunState;
|
|
26423
|
+
/**
|
|
26424
|
+
* Durable at-least-once delivery for stream events. Active only when
|
|
26425
|
+
* `ALAN_EVENT_OUTBOX_ENABLED === "true"`; otherwise events are fired-and-
|
|
26426
|
+
* forgotten exactly as before (see {@link SandboxEventDispatcher}).
|
|
26427
|
+
*/
|
|
26428
|
+
eventDispatcher;
|
|
26429
|
+
/**
|
|
26430
|
+
* Bounded record of `messageId`s whose handoff to `onUserMessage` already
|
|
26431
|
+
* completed successfully. A redelivered `user_message` for one of these ids
|
|
26432
|
+
* (e.g. the original `ok:true` ack was lost in transit and the server's
|
|
26433
|
+
* ≤5-attempt redelivery re-armed the run) is re-acked `{ ok: true }`
|
|
26434
|
+
* without invoking the handler again, so the message is never processed
|
|
26435
|
+
* twice. A `Set`'s insertion order doubles as recency, so eviction is just
|
|
26436
|
+
* "drop the oldest key" — a minimal LRU without extra bookkeeping.
|
|
26437
|
+
*/
|
|
26438
|
+
acceptedMessageIds = /* @__PURE__ */ new Set();
|
|
26439
|
+
/** Record a successfully-handed-off messageId, evicting the oldest entry past the cap. */
|
|
26440
|
+
rememberAcceptedMessageId(messageId) {
|
|
26441
|
+
this.acceptedMessageIds.delete(messageId);
|
|
26442
|
+
this.acceptedMessageIds.add(messageId);
|
|
26443
|
+
if (this.acceptedMessageIds.size > ACCEPTED_MESSAGE_ID_CACHE_SIZE) {
|
|
26444
|
+
const oldest = this.acceptedMessageIds.keys().next().value;
|
|
26445
|
+
if (oldest !== void 0) this.acceptedMessageIds.delete(oldest);
|
|
26446
|
+
}
|
|
26447
|
+
}
|
|
26448
|
+
/**
|
|
26449
|
+
* Record the run id for events emitted from here on, so the durable outbox can
|
|
26450
|
+
* bucket per-run caps and terminal-event condensation correctly. No-op when the
|
|
26451
|
+
* outbox is disabled.
|
|
26452
|
+
*/
|
|
26453
|
+
setCurrentRunId(runId) {
|
|
26454
|
+
this.eventDispatcher.setRunId(runId);
|
|
26455
|
+
}
|
|
25642
26456
|
currentRunState() {
|
|
25643
26457
|
try {
|
|
25644
26458
|
return this.getRunState?.() ?? { idle: true, activeRunIds: [] };
|
|
@@ -25675,7 +26489,9 @@ var WSClient = class {
|
|
|
25675
26489
|
}
|
|
25676
26490
|
}
|
|
25677
26491
|
emitEvent(event) {
|
|
25678
|
-
this.
|
|
26492
|
+
this.eventDispatcher.emit(event, (payload) => {
|
|
26493
|
+
this.socket.emit("agent_event", payload);
|
|
26494
|
+
});
|
|
25679
26495
|
}
|
|
25680
26496
|
waitForConnection(timeoutMs = 15e3) {
|
|
25681
26497
|
return new Promise((resolve6, reject) => {
|
|
@@ -25712,11 +26528,13 @@ var WSClient = class {
|
|
|
25712
26528
|
this.socket.on("connect", () => {
|
|
25713
26529
|
this.lifecycle?.info("sandbox_agent_ws_socket_connected", { socketId: this.socket.id });
|
|
25714
26530
|
this.startHeartbeat();
|
|
26531
|
+
this.eventDispatcher.flush();
|
|
25715
26532
|
callbacks.onConnect?.();
|
|
25716
26533
|
});
|
|
25717
26534
|
this.socket.on("disconnect", (reason) => {
|
|
25718
26535
|
this.lifecycle?.info("sandbox_agent_ws_socket_disconnected", { reason });
|
|
25719
26536
|
this.stopHeartbeat();
|
|
26537
|
+
this.eventDispatcher.disconnect();
|
|
25720
26538
|
callbacks.onDisconnect?.(reason);
|
|
25721
26539
|
});
|
|
25722
26540
|
this.socket.on("agent.probe", (_data, ack) => {
|
|
@@ -25734,8 +26552,11 @@ var WSClient = class {
|
|
|
25734
26552
|
this.socket.on(
|
|
25735
26553
|
"user_message",
|
|
25736
26554
|
(data, ack) => {
|
|
25737
|
-
|
|
25738
|
-
|
|
26555
|
+
if (data.messageId && this.acceptedMessageIds.has(data.messageId)) {
|
|
26556
|
+
ack?.({ ok: true });
|
|
26557
|
+
return;
|
|
26558
|
+
}
|
|
26559
|
+
const payload = {
|
|
25739
26560
|
text: data.text,
|
|
25740
26561
|
images: data.images,
|
|
25741
26562
|
files: data.files,
|
|
@@ -25759,7 +26580,24 @@ var WSClient = class {
|
|
|
25759
26580
|
conversationId: data.conversationId,
|
|
25760
26581
|
teamId: data.teamId,
|
|
25761
26582
|
currentUser: data.currentUser
|
|
25762
|
-
}
|
|
26583
|
+
};
|
|
26584
|
+
const acceptDelivery = () => {
|
|
26585
|
+
if (data.messageId) this.rememberAcceptedMessageId(data.messageId);
|
|
26586
|
+
ack?.({ ok: true });
|
|
26587
|
+
};
|
|
26588
|
+
const rejectDelivery = (error) => {
|
|
26589
|
+
ack?.({ ok: false, message: error instanceof Error ? error.message : String(error) });
|
|
26590
|
+
};
|
|
26591
|
+
try {
|
|
26592
|
+
const result = callbacks.onUserMessage(payload);
|
|
26593
|
+
if (result instanceof Promise) {
|
|
26594
|
+
result.then(acceptDelivery, rejectDelivery);
|
|
26595
|
+
} else {
|
|
26596
|
+
acceptDelivery();
|
|
26597
|
+
}
|
|
26598
|
+
} catch (error) {
|
|
26599
|
+
rejectDelivery(error);
|
|
26600
|
+
}
|
|
25763
26601
|
}
|
|
25764
26602
|
);
|
|
25765
26603
|
this.socket.on("stop", () => {
|
|
@@ -25928,6 +26766,7 @@ async function runSandbox(config) {
|
|
|
25928
26766
|
backendKind: activeBackendKind
|
|
25929
26767
|
});
|
|
25930
26768
|
currentAgent = agent;
|
|
26769
|
+
wsClient.setCurrentRunId(currentRunId ?? config.runId);
|
|
25931
26770
|
const correlation = correlationLogFields2({
|
|
25932
26771
|
taskId: currentTaskId,
|
|
25933
26772
|
conversationId: currentConversationId,
|
|
@@ -26145,7 +26984,7 @@ async function runSandbox(config) {
|
|
|
26145
26984
|
|
|
26146
26985
|
// src/service-manager.ts
|
|
26147
26986
|
var import_node_child_process6 = require("child_process");
|
|
26148
|
-
var
|
|
26987
|
+
var import_node_fs14 = require("fs");
|
|
26149
26988
|
var import_node_os8 = require("os");
|
|
26150
26989
|
var import_node_path11 = require("path");
|
|
26151
26990
|
var SERVICE_LABEL = "ai.tryalan.agent";
|
|
@@ -26344,14 +27183,14 @@ function currentServicePlan(args) {
|
|
|
26344
27183
|
});
|
|
26345
27184
|
}
|
|
26346
27185
|
function writeManifest(path, contents) {
|
|
26347
|
-
(0,
|
|
27186
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path11.dirname)(path), { recursive: true, mode: 448 });
|
|
26348
27187
|
const pendingPath = `${path}.pending-${process.pid}`;
|
|
26349
27188
|
try {
|
|
26350
|
-
(0,
|
|
26351
|
-
(0,
|
|
26352
|
-
(0,
|
|
27189
|
+
(0, import_node_fs14.writeFileSync)(pendingPath, contents, { mode: 384 });
|
|
27190
|
+
(0, import_node_fs14.chmodSync)(pendingPath, 384);
|
|
27191
|
+
(0, import_node_fs14.renameSync)(pendingPath, path);
|
|
26353
27192
|
} finally {
|
|
26354
|
-
(0,
|
|
27193
|
+
(0, import_node_fs14.rmSync)(pendingPath, { force: true });
|
|
26355
27194
|
}
|
|
26356
27195
|
}
|
|
26357
27196
|
function runServiceCommand(command) {
|
|
@@ -26374,7 +27213,7 @@ function installDaemonService(args = []) {
|
|
|
26374
27213
|
const plan = currentServicePlan(args);
|
|
26375
27214
|
if (plan.manifestPath && plan.manifest) {
|
|
26376
27215
|
if ((0, import_node_os8.platform)() === "darwin") {
|
|
26377
|
-
(0,
|
|
27216
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path11.join)((0, import_node_os8.homedir)(), ".alan", "agent", "logs"), { recursive: true, mode: 448 });
|
|
26378
27217
|
}
|
|
26379
27218
|
writeManifest(plan.manifestPath, plan.manifest);
|
|
26380
27219
|
}
|
|
@@ -26384,7 +27223,7 @@ function installDaemonService(args = []) {
|
|
|
26384
27223
|
function uninstallDaemonService(args = []) {
|
|
26385
27224
|
const plan = currentServicePlan(args);
|
|
26386
27225
|
for (const command of plan.uninstallCommands) runServiceCommand(command);
|
|
26387
|
-
if (plan.manifestPath) (0,
|
|
27226
|
+
if (plan.manifestPath) (0, import_node_fs14.rmSync)(plan.manifestPath, { force: true });
|
|
26388
27227
|
console.info("[alan-agent] Per-user daemon service removed");
|
|
26389
27228
|
}
|
|
26390
27229
|
function printDaemonServiceStatus(args = []) {
|