@aiden-ade/sandbox-agent 0.1.2 → 0.1.5
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 +1096 -402
- package/package.json +6 -5
package/dist/index.cjs
CHANGED
|
@@ -36,7 +36,7 @@ var require_XMLHttpRequest = __commonJS({
|
|
|
36
36
|
"use strict";
|
|
37
37
|
var fs = require("fs");
|
|
38
38
|
var Url = require("url");
|
|
39
|
-
var
|
|
39
|
+
var spawn2 = require("child_process").spawn;
|
|
40
40
|
module2.exports = XMLHttpRequest3;
|
|
41
41
|
XMLHttpRequest3.XMLHttpRequest = XMLHttpRequest3;
|
|
42
42
|
function XMLHttpRequest3(opts) {
|
|
@@ -332,7 +332,7 @@ var require_XMLHttpRequest = __commonJS({
|
|
|
332
332
|
var syncFile = ".node-xmlhttprequest-sync-" + process.pid;
|
|
333
333
|
fs.writeFileSync(syncFile, "", "utf8");
|
|
334
334
|
var execString = "var http = require('http'), https = require('https'), fs = require('fs');var doRequest = http" + (ssl ? "s" : "") + ".request;var options = " + JSON.stringify(options) + ";var responseText = '';var responseData = Buffer.alloc(0);var req = doRequest(options, function(response) {response.on('data', function(chunk) { var data = Buffer.from(chunk); responseText += data.toString('utf8'); responseData = Buffer.concat([responseData, data]);});response.on('end', function() {fs.writeFileSync('" + contentFile + "', JSON.stringify({err: null, data: {statusCode: response.statusCode, headers: response.headers, text: responseText, data: responseData.toString('base64')}}), 'utf8');fs.unlinkSync('" + syncFile + "');});response.on('error', function(error) {fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');fs.unlinkSync('" + syncFile + "');});}).on('error', function(error) {fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');fs.unlinkSync('" + syncFile + "');});" + (data ? "req.write('" + JSON.stringify(data).slice(1, -1).replace(/'/g, "\\'") + "');" : "") + "req.end();";
|
|
335
|
-
var syncProc =
|
|
335
|
+
var syncProc = spawn2(process.argv[0], ["-e", execString]);
|
|
336
336
|
var statusText;
|
|
337
337
|
while (fs.existsSync(syncFile)) {
|
|
338
338
|
}
|
|
@@ -921,21 +921,19 @@ var require_supports_color = __commonJS({
|
|
|
921
921
|
var tty = require("tty");
|
|
922
922
|
var hasFlag = require_has_flag();
|
|
923
923
|
var { env } = process;
|
|
924
|
-
var
|
|
924
|
+
var forceColor;
|
|
925
925
|
if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
|
|
926
|
-
|
|
926
|
+
forceColor = 0;
|
|
927
927
|
} else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
|
|
928
|
-
|
|
928
|
+
forceColor = 1;
|
|
929
929
|
}
|
|
930
|
-
|
|
931
|
-
if (
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
}
|
|
938
|
-
return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
|
|
930
|
+
if ("FORCE_COLOR" in env) {
|
|
931
|
+
if (env.FORCE_COLOR === "true") {
|
|
932
|
+
forceColor = 1;
|
|
933
|
+
} else if (env.FORCE_COLOR === "false") {
|
|
934
|
+
forceColor = 0;
|
|
935
|
+
} else {
|
|
936
|
+
forceColor = env.FORCE_COLOR.length === 0 ? 1 : Math.min(parseInt(env.FORCE_COLOR, 10), 3);
|
|
939
937
|
}
|
|
940
938
|
}
|
|
941
939
|
function translateLevel(level) {
|
|
@@ -949,22 +947,15 @@ var require_supports_color = __commonJS({
|
|
|
949
947
|
has16m: level >= 3
|
|
950
948
|
};
|
|
951
949
|
}
|
|
952
|
-
function supportsColor(haveStream,
|
|
953
|
-
const noFlagForceColor = envForceColor();
|
|
954
|
-
if (noFlagForceColor !== void 0) {
|
|
955
|
-
flagForceColor = noFlagForceColor;
|
|
956
|
-
}
|
|
957
|
-
const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
|
|
950
|
+
function supportsColor(haveStream, streamIsTTY) {
|
|
958
951
|
if (forceColor === 0) {
|
|
959
952
|
return 0;
|
|
960
953
|
}
|
|
961
|
-
if (
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
return 2;
|
|
967
|
-
}
|
|
954
|
+
if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
|
|
955
|
+
return 3;
|
|
956
|
+
}
|
|
957
|
+
if (hasFlag("color=256")) {
|
|
958
|
+
return 2;
|
|
968
959
|
}
|
|
969
960
|
if (haveStream && !streamIsTTY && forceColor === void 0) {
|
|
970
961
|
return 0;
|
|
@@ -981,7 +972,7 @@ var require_supports_color = __commonJS({
|
|
|
981
972
|
return 1;
|
|
982
973
|
}
|
|
983
974
|
if ("CI" in env) {
|
|
984
|
-
if (["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "GITHUB_ACTIONS", "BUILDKITE"
|
|
975
|
+
if (["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "GITHUB_ACTIONS", "BUILDKITE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
|
|
985
976
|
return 1;
|
|
986
977
|
}
|
|
987
978
|
return min;
|
|
@@ -993,7 +984,7 @@ var require_supports_color = __commonJS({
|
|
|
993
984
|
return 3;
|
|
994
985
|
}
|
|
995
986
|
if ("TERM_PROGRAM" in env) {
|
|
996
|
-
const version =
|
|
987
|
+
const version = parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
|
|
997
988
|
switch (env.TERM_PROGRAM) {
|
|
998
989
|
case "iTerm.app":
|
|
999
990
|
return version >= 3 ? 3 : 2;
|
|
@@ -1012,17 +1003,14 @@ var require_supports_color = __commonJS({
|
|
|
1012
1003
|
}
|
|
1013
1004
|
return min;
|
|
1014
1005
|
}
|
|
1015
|
-
function getSupportLevel(stream
|
|
1016
|
-
const level = supportsColor(stream,
|
|
1017
|
-
streamIsTTY: stream && stream.isTTY,
|
|
1018
|
-
...options
|
|
1019
|
-
});
|
|
1006
|
+
function getSupportLevel(stream) {
|
|
1007
|
+
const level = supportsColor(stream, stream && stream.isTTY);
|
|
1020
1008
|
return translateLevel(level);
|
|
1021
1009
|
}
|
|
1022
1010
|
module2.exports = {
|
|
1023
1011
|
supportsColor: getSupportLevel,
|
|
1024
|
-
stdout:
|
|
1025
|
-
stderr:
|
|
1012
|
+
stdout: translateLevel(supportsColor(true, tty.isatty(1))),
|
|
1013
|
+
stderr: translateLevel(supportsColor(true, tty.isatty(2)))
|
|
1026
1014
|
};
|
|
1027
1015
|
}
|
|
1028
1016
|
});
|
|
@@ -4822,14 +4810,76 @@ var require_websocket_server = __commonJS({
|
|
|
4822
4810
|
}
|
|
4823
4811
|
});
|
|
4824
4812
|
|
|
4825
|
-
//
|
|
4826
|
-
var
|
|
4827
|
-
var
|
|
4828
|
-
var
|
|
4813
|
+
// ../agent-core/dist/index.js
|
|
4814
|
+
var import_child_process = require("child_process");
|
|
4815
|
+
var import_fs = require("fs");
|
|
4816
|
+
var import_os = require("os");
|
|
4817
|
+
var import_path = require("path");
|
|
4818
|
+
var import_readline = require("readline");
|
|
4819
|
+
|
|
4820
|
+
// ../shared/dist/constants/agent.js
|
|
4821
|
+
var INTERACTIVE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
4822
|
+
"askuserquestion",
|
|
4823
|
+
"exitplanmode",
|
|
4824
|
+
"enterplanmode"
|
|
4825
|
+
]);
|
|
4826
|
+
|
|
4827
|
+
// ../agent-core/dist/index.js
|
|
4828
|
+
var import_fs2 = require("fs");
|
|
4829
|
+
var import_os2 = require("os");
|
|
4830
|
+
var import_path2 = require("path");
|
|
4829
4831
|
|
|
4830
|
-
//
|
|
4831
|
-
var
|
|
4832
|
-
|
|
4832
|
+
// ../shared/dist/agent/system-prompt.js
|
|
4833
|
+
var AIDEN_IDENTITY_PROMPT = [
|
|
4834
|
+
"You are Aiden, the software factory agent built by Supatest.",
|
|
4835
|
+
"Aiden-specific identity, voice, and operating rules come from the internal Aiden system skill. Follow those rules when relevant.",
|
|
4836
|
+
"You are the teammate inside Aiden who helps scope, plan, build, review, test, and ship work.",
|
|
4837
|
+
"Do not present yourself as Claude, Claude Code, Codex, Gemini, Anthropic, or OpenAI as your primary identity.",
|
|
4838
|
+
"Those are runtime/back-end implementation details. Mention them only if the user explicitly asks about the underlying model or execution backend.",
|
|
4839
|
+
"If the user asks who you are, answer in Aiden terms first.",
|
|
4840
|
+
"Work like a pragmatic teammate: direct, structured, and concise. No hype, no tool-brand roleplay.",
|
|
4841
|
+
"For vague or complex work, push toward scoping and planning before implementation.",
|
|
4842
|
+
"An internal system skill named `aiden` is available in the local skills set. Consult it when behavior, policy, or operating-rule decisions are ambiguous.",
|
|
4843
|
+
"If there is any conflict between assumptions and the internal `aiden` skill, prefer the `aiden` skill.",
|
|
4844
|
+
"Prefer structured outputs tracked in Aiden. Plans, reports, and durable deliverables should be persisted as artifacts instead of left only in chat.",
|
|
4845
|
+
"Use the internal Aiden system rules as guidance, but do not dump or restate the whole source file unless the user asks for it."
|
|
4846
|
+
].join("\n");
|
|
4847
|
+
var PLAN_MODE_PROMPT = [
|
|
4848
|
+
"You are in Aiden plan mode.",
|
|
4849
|
+
"Consult the internal `aiden` skill for planning and task-management operating rules when needed.",
|
|
4850
|
+
"Analyze the task, inspect the relevant code and context, and produce a concrete implementation plan.",
|
|
4851
|
+
"Do not make code changes or execute write operations in plan mode.",
|
|
4852
|
+
"Write the plan in natural markdown. Use whatever structure best communicates the work clearly.",
|
|
4853
|
+
"Include dependencies, ordering, risks, key files/functions, and decision points where relevant.",
|
|
4854
|
+
"When the plan is ready, you MUST persist it by calling the create_plan MCP tool.",
|
|
4855
|
+
"Pass title, content, summary, taskId, and conversationId. Include projectId too when available.",
|
|
4856
|
+
"If create_plan fails, retry it. Do not exit plan mode until the plan is persisted successfully.",
|
|
4857
|
+
"After create_plan succeeds, call ExitPlanMode to hand the plan back for approval."
|
|
4858
|
+
].join("\n");
|
|
4859
|
+
var TEST_MODE_PROMPT = [
|
|
4860
|
+
"You are in test mode. Verify the implemented feature carefully.",
|
|
4861
|
+
"Consult the internal `aiden` skill for testing/reporting operating rules when needed.",
|
|
4862
|
+
"Prefer browser-based validation when available.",
|
|
4863
|
+
"Persist the outcome as a structured test report artifact with create_test_report instead of leaving it only in chat."
|
|
4864
|
+
].join("\n");
|
|
4865
|
+
var ASK_MODE_PROMPT = "You are in ask mode. Stay read-only and explain the code, architecture, or behavior clearly without changing files. Consult the internal `aiden` skill for behavior-policy questions when needed.";
|
|
4866
|
+
var REVIEW_MODE_PROMPT = "You are in review mode. Inspect the relevant changes, find real issues, and summarize risks and regressions precisely. Consult the internal `aiden` skill for review-quality expectations when needed.";
|
|
4867
|
+
function getAidenModePrompt(mode2) {
|
|
4868
|
+
switch (mode2) {
|
|
4869
|
+
case "plan":
|
|
4870
|
+
return PLAN_MODE_PROMPT;
|
|
4871
|
+
case "test":
|
|
4872
|
+
return TEST_MODE_PROMPT;
|
|
4873
|
+
case "ask":
|
|
4874
|
+
return ASK_MODE_PROMPT;
|
|
4875
|
+
case "review":
|
|
4876
|
+
return REVIEW_MODE_PROMPT;
|
|
4877
|
+
default:
|
|
4878
|
+
return void 0;
|
|
4879
|
+
}
|
|
4880
|
+
}
|
|
4881
|
+
|
|
4882
|
+
// ../agent-core/dist/index.js
|
|
4833
4883
|
function formatCliSpawnError(command, error) {
|
|
4834
4884
|
if (error.code === "ENOENT") {
|
|
4835
4885
|
return new Error(
|
|
@@ -4838,6 +4888,41 @@ function formatCliSpawnError(command, error) {
|
|
|
4838
4888
|
}
|
|
4839
4889
|
return error;
|
|
4840
4890
|
}
|
|
4891
|
+
function isLikelyProviderAuthError(stderr) {
|
|
4892
|
+
const lower = stderr.toLowerCase();
|
|
4893
|
+
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");
|
|
4894
|
+
}
|
|
4895
|
+
function classifyCliError(stderr, exitCode) {
|
|
4896
|
+
const lower = stderr.toLowerCase();
|
|
4897
|
+
if (lower.includes("rate_limit") || lower.includes("rate limit") || lower.includes("429"))
|
|
4898
|
+
return "Rate limited by API. Try again shortly.";
|
|
4899
|
+
if (isLikelyProviderAuthError(stderr))
|
|
4900
|
+
return "API key invalid or expired. Check your provider settings.";
|
|
4901
|
+
if (lower.includes("not logged in") || lower.includes("login required") || lower.includes("please sign in") || lower.includes("please log in") || lower.includes("sign in to") || lower.includes("log in to"))
|
|
4902
|
+
return "Not logged in. Please authenticate with this provider first.";
|
|
4903
|
+
if (lower.includes("model not supported") || lower.includes("unsupported model") || lower.includes("model not found") || lower.includes("model not available") || lower.includes("model is not available") || lower.includes("invalid model") || lower.includes("unknown model") || lower.includes("no such model"))
|
|
4904
|
+
return "Model not supported by this provider. Check your provider's model list.";
|
|
4905
|
+
if (lower.includes("subscription") || lower.includes("paid plan") || lower.includes("plan required") || lower.includes("upgrade your plan") || lower.includes("only supports free") || lower.includes("not available on your") || lower.includes("requires a paid"))
|
|
4906
|
+
return "This model requires a paid subscription. Switch to a supported model or upgrade your plan.";
|
|
4907
|
+
if (lower.includes("quota exceeded") || lower.includes("quota limit") || lower.includes("daily limit") || lower.includes("monthly limit") || lower.includes("usage limit"))
|
|
4908
|
+
return "API quota exceeded. Wait for it to reset or check your billing.";
|
|
4909
|
+
if (lower.includes("overloaded") || lower.includes("503") || lower.includes("service unavailable"))
|
|
4910
|
+
return "API is overloaded. Will retry automatically.";
|
|
4911
|
+
if (lower.includes("context_length") || lower.includes("too long") || lower.includes("max tokens") || lower.includes("context window"))
|
|
4912
|
+
return "Context window exceeded. Try a shorter prompt or start a new session.";
|
|
4913
|
+
if (lower.includes("econnrefused") || lower.includes("network") || lower.includes("enotfound") || lower.includes("timeout") || lower.includes("etimedout"))
|
|
4914
|
+
return "Network connection failed. Check your internet connection.";
|
|
4915
|
+
if (lower.includes("permission denied") || lower.includes("eacces"))
|
|
4916
|
+
return "Permission denied. Check file or command permissions.";
|
|
4917
|
+
if (lower.includes("out of memory") || lower.includes("enomem"))
|
|
4918
|
+
return "Out of memory. Close other applications and try again.";
|
|
4919
|
+
const raw = stderr.trim().slice(0, 200);
|
|
4920
|
+
return raw || `Process exited with code ${exitCode}`;
|
|
4921
|
+
}
|
|
4922
|
+
function isTransientError(errorMessage) {
|
|
4923
|
+
const lower = errorMessage.toLowerCase();
|
|
4924
|
+
return lower.includes("rate limit") || lower.includes("overloaded") || lower.includes("503") || lower.includes("429") || lower.includes("service unavailable") || lower.includes("etimedout") || lower.includes("econnreset") || lower.includes("econnrefused") || lower.includes("epipe");
|
|
4925
|
+
}
|
|
4841
4926
|
function buildPromptWithSystem(config, promptText) {
|
|
4842
4927
|
const parts2 = [
|
|
4843
4928
|
config.systemPrompt?.trim(),
|
|
@@ -4856,13 +4941,16 @@ function buildPlanModePrefix(promptText) {
|
|
|
4856
4941
|
].join("\n");
|
|
4857
4942
|
}
|
|
4858
4943
|
function spawnCli(command, args, context) {
|
|
4859
|
-
const
|
|
4860
|
-
return (0,
|
|
4944
|
+
const isWindows = process.platform === "win32";
|
|
4945
|
+
return (0, import_child_process.spawn)(command, args, {
|
|
4861
4946
|
cwd: context.cwd,
|
|
4862
4947
|
env: context.env,
|
|
4863
4948
|
stdio: ["pipe", "pipe", "pipe"],
|
|
4864
|
-
shell:
|
|
4865
|
-
|
|
4949
|
+
shell: isWindows,
|
|
4950
|
+
// Detached on Unix creates a new process group so we can kill the entire tree
|
|
4951
|
+
// (CLI tools spawn child processes for tool execution that would otherwise be orphaned).
|
|
4952
|
+
// On Windows, detached opens a new console — shell mode handles grouping instead.
|
|
4953
|
+
detached: !isWindows
|
|
4866
4954
|
});
|
|
4867
4955
|
}
|
|
4868
4956
|
function createGenericCliBackend(options) {
|
|
@@ -4874,30 +4962,39 @@ function createGenericCliBackend(options) {
|
|
|
4874
4962
|
const state = {
|
|
4875
4963
|
process: null,
|
|
4876
4964
|
iterations: 0,
|
|
4877
|
-
summary: ""
|
|
4965
|
+
summary: "",
|
|
4966
|
+
allowContinuation: options.allowContinuation ?? false
|
|
4878
4967
|
};
|
|
4879
4968
|
const prompt = options.augmentPrompt?.(context) ?? (context.config.mode === "plan" ? buildPlanModePrefix(buildPromptWithSystem(context.config, context.promptText)) : buildPromptWithSystem(context.config, context.promptText));
|
|
4880
4969
|
const args = options.buildArgs?.(context, prompt) ?? options.args.map((arg) => arg === "{{prompt}}" ? prompt : arg);
|
|
4881
4970
|
const child = spawnCli(options.command, args, context);
|
|
4882
4971
|
state.process = child;
|
|
4972
|
+
context.onProcessSpawned?.(child);
|
|
4883
4973
|
const safeArgs = args.map(
|
|
4884
|
-
(a, i) =>
|
|
4885
|
-
// Redact API keys but show everything else for debugging
|
|
4886
|
-
i > 0 && args[i - 1] === "--system-prompt" ? `"<system-prompt ${a.length} chars>"` : a
|
|
4887
|
-
)
|
|
4974
|
+
(a, i) => i > 0 && args[i - 1] === "--system-prompt" ? `"<system-prompt ${a.length} chars>"` : a
|
|
4888
4975
|
);
|
|
4889
4976
|
console.info(`[${options.kind}] Spawning: ${options.command} ${safeArgs.join(" ")}`);
|
|
4890
4977
|
presenter.recordRawTranscript?.("system", `${options.command} ${args.join(" ")}`, {
|
|
4891
4978
|
backendKind: options.kind
|
|
4892
4979
|
});
|
|
4893
|
-
|
|
4980
|
+
child.stdin.on("error", (err) => {
|
|
4981
|
+
if (err.code !== "EPIPE") {
|
|
4982
|
+
console.error(`[${options.kind}] stdin error:`, err);
|
|
4983
|
+
}
|
|
4984
|
+
});
|
|
4985
|
+
if (options.stdinWriter) {
|
|
4986
|
+
options.stdinWriter(child.stdin, context);
|
|
4987
|
+
} else if (options.promptViaStdin) {
|
|
4894
4988
|
child.stdin.write(prompt);
|
|
4895
4989
|
child.stdin.end();
|
|
4896
4990
|
} else {
|
|
4897
4991
|
child.stdin.end();
|
|
4898
4992
|
}
|
|
4899
|
-
|
|
4900
|
-
|
|
4993
|
+
if (options.keepStdinOpen) {
|
|
4994
|
+
state.stdin = child.stdin;
|
|
4995
|
+
}
|
|
4996
|
+
const stdoutRl = (0, import_readline.createInterface)({ input: child.stdout });
|
|
4997
|
+
const stderrRl = (0, import_readline.createInterface)({ input: child.stderr });
|
|
4901
4998
|
const stderrLines = [];
|
|
4902
4999
|
stdoutRl.on("line", (line) => {
|
|
4903
5000
|
presenter.recordRawTranscript?.("stdout", line);
|
|
@@ -4919,12 +5016,109 @@ function createGenericCliBackend(options) {
|
|
|
4919
5016
|
void presenter.onLog(`[${options.kind}] ${line}`);
|
|
4920
5017
|
}
|
|
4921
5018
|
});
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
5019
|
+
let exitCode;
|
|
5020
|
+
try {
|
|
5021
|
+
const exitPromise = new Promise((resolve2, reject) => {
|
|
5022
|
+
child.on("error", (error) => {
|
|
5023
|
+
reject(formatCliSpawnError(options.command, error));
|
|
5024
|
+
});
|
|
5025
|
+
child.on("exit", (code) => resolve2(code ?? 0));
|
|
5026
|
+
const onAbort = () => {
|
|
5027
|
+
const pid = child.pid;
|
|
5028
|
+
if (!pid) return;
|
|
5029
|
+
try {
|
|
5030
|
+
process.kill(-pid, "SIGTERM");
|
|
5031
|
+
} catch {
|
|
5032
|
+
try {
|
|
5033
|
+
child.kill("SIGTERM");
|
|
5034
|
+
} catch {
|
|
5035
|
+
}
|
|
5036
|
+
}
|
|
5037
|
+
};
|
|
5038
|
+
context.abortController.signal.addEventListener("abort", onAbort, { once: true });
|
|
4925
5039
|
});
|
|
4926
|
-
|
|
4927
|
-
|
|
5040
|
+
if (options.keepStdinOpen) {
|
|
5041
|
+
if (options.allowContinuation) {
|
|
5042
|
+
const IDLE_MS = options.continuationIdleMs ?? 10 * 60 * 1e3;
|
|
5043
|
+
let idleTimer = null;
|
|
5044
|
+
let heartbeatInterval = null;
|
|
5045
|
+
const resetIdleTimer = () => {
|
|
5046
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
5047
|
+
idleTimer = setTimeout(() => {
|
|
5048
|
+
if (state.stdin && !state.stdin.destroyed) {
|
|
5049
|
+
state.stdin.end();
|
|
5050
|
+
console.info(`[${options.kind}] Continuation idle timeout (${IDLE_MS}ms) \u2014 closing stdin`);
|
|
5051
|
+
}
|
|
5052
|
+
}, IDLE_MS);
|
|
5053
|
+
};
|
|
5054
|
+
state.onNewOutput = resetIdleTimer;
|
|
5055
|
+
state.stopHeartbeat = () => {
|
|
5056
|
+
if (heartbeatInterval) {
|
|
5057
|
+
clearInterval(heartbeatInterval);
|
|
5058
|
+
heartbeatInterval = null;
|
|
5059
|
+
}
|
|
5060
|
+
};
|
|
5061
|
+
state.startHeartbeat = () => {
|
|
5062
|
+
state.stopHeartbeat?.();
|
|
5063
|
+
void presenter.onActivity?.("background_work");
|
|
5064
|
+
heartbeatInterval = setInterval(() => {
|
|
5065
|
+
void presenter.onActivity?.("background_work");
|
|
5066
|
+
}, 3e4);
|
|
5067
|
+
};
|
|
5068
|
+
resetIdleTimer();
|
|
5069
|
+
exitCode = await exitPromise;
|
|
5070
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
5071
|
+
state.stopHeartbeat?.();
|
|
5072
|
+
} else {
|
|
5073
|
+
const resultPromise = new Promise((resolve2) => {
|
|
5074
|
+
state.onResultReceived = () => resolve2("result_received");
|
|
5075
|
+
if (state.resultReceived) resolve2("result_received");
|
|
5076
|
+
});
|
|
5077
|
+
const raceResult = await Promise.race([exitPromise, resultPromise]);
|
|
5078
|
+
if (raceResult === "result_received") {
|
|
5079
|
+
const GRACE_MS = 5e3;
|
|
5080
|
+
const gracedExit = await Promise.race([
|
|
5081
|
+
exitPromise,
|
|
5082
|
+
new Promise((resolve2) => setTimeout(() => resolve2(0), GRACE_MS))
|
|
5083
|
+
]);
|
|
5084
|
+
exitCode = typeof gracedExit === "number" ? gracedExit : 0;
|
|
5085
|
+
if (child.exitCode === null && !child.killed) {
|
|
5086
|
+
console.warn(`[${options.kind}] Process still alive ${GRACE_MS}ms after result event, killing`);
|
|
5087
|
+
try {
|
|
5088
|
+
if (process.platform === "win32") {
|
|
5089
|
+
child.kill("SIGKILL");
|
|
5090
|
+
} else if (child.pid) {
|
|
5091
|
+
process.kill(-child.pid, "SIGKILL");
|
|
5092
|
+
}
|
|
5093
|
+
} catch {
|
|
5094
|
+
}
|
|
5095
|
+
}
|
|
5096
|
+
} else {
|
|
5097
|
+
exitCode = raceResult;
|
|
5098
|
+
}
|
|
5099
|
+
}
|
|
5100
|
+
} else {
|
|
5101
|
+
exitCode = await exitPromise;
|
|
5102
|
+
}
|
|
5103
|
+
} finally {
|
|
5104
|
+
stdoutRl.close();
|
|
5105
|
+
stderrRl.close();
|
|
5106
|
+
}
|
|
5107
|
+
if (context.abortController.signal.aborted) {
|
|
5108
|
+
return {
|
|
5109
|
+
success: false,
|
|
5110
|
+
summary: "Interrupted by user",
|
|
5111
|
+
filesModified: [],
|
|
5112
|
+
planFilesCreated: [],
|
|
5113
|
+
iterations: Math.max(state.iterations, 1),
|
|
5114
|
+
error: "Interrupted by user",
|
|
5115
|
+
providerSessionId: state.runtimeSessionId,
|
|
5116
|
+
runtimeSessionId: state.runtimeSessionId,
|
|
5117
|
+
backendKind: options.kind,
|
|
5118
|
+
supportTier: options.supportTier,
|
|
5119
|
+
usage: state.usage
|
|
5120
|
+
};
|
|
5121
|
+
}
|
|
4928
5122
|
if (exitCode !== 0) {
|
|
4929
5123
|
console.error(`[${options.kind}] Process exited with code ${exitCode}`, {
|
|
4930
5124
|
stderrLines: stderrLines.slice(-10),
|
|
@@ -4937,14 +5131,17 @@ function createGenericCliBackend(options) {
|
|
|
4937
5131
|
if (hasRenderableTurn) {
|
|
4938
5132
|
await presenter.onTurnComplete([]);
|
|
4939
5133
|
}
|
|
4940
|
-
const
|
|
5134
|
+
const stderrText = stderrLines.join("\n").trim();
|
|
5135
|
+
const hasStructuredError = exitCode === 0 && !!state.error?.trim();
|
|
5136
|
+
const failed = exitCode !== 0 || hasStructuredError;
|
|
5137
|
+
const summary = state.summary.trim() || state.error?.trim() || (failed ? "Task failed" : "Task completed");
|
|
4941
5138
|
return {
|
|
4942
|
-
success:
|
|
5139
|
+
success: !failed,
|
|
4943
5140
|
summary,
|
|
4944
5141
|
filesModified: [],
|
|
4945
5142
|
planFilesCreated: [],
|
|
4946
5143
|
iterations: Math.max(state.iterations, 1),
|
|
4947
|
-
error:
|
|
5144
|
+
error: failed ? classifyCliError(state.error?.trim() || stderrText || "", exitCode) : void 0,
|
|
4948
5145
|
providerSessionId: state.runtimeSessionId,
|
|
4949
5146
|
runtimeSessionId: state.runtimeSessionId,
|
|
4950
5147
|
backendKind: options.kind,
|
|
@@ -4955,12 +5152,8 @@ function createGenericCliBackend(options) {
|
|
|
4955
5152
|
};
|
|
4956
5153
|
}
|
|
4957
5154
|
function extractToolResultContent(content) {
|
|
4958
|
-
if (typeof content === "string")
|
|
4959
|
-
|
|
4960
|
-
}
|
|
4961
|
-
if (!Array.isArray(content)) {
|
|
4962
|
-
return "";
|
|
4963
|
-
}
|
|
5155
|
+
if (typeof content === "string") return content;
|
|
5156
|
+
if (!Array.isArray(content)) return "";
|
|
4964
5157
|
return content.map((block) => {
|
|
4965
5158
|
if (typeof block === "object" && block !== null && "type" in block && block.type === "text" && "text" in block && typeof block.text === "string") {
|
|
4966
5159
|
return block.text;
|
|
@@ -4974,10 +5167,7 @@ function parseClaudeStructuredLine(line, context, state) {
|
|
|
4974
5167
|
try {
|
|
4975
5168
|
parsed = JSON.parse(line);
|
|
4976
5169
|
} catch {
|
|
4977
|
-
|
|
4978
|
-
`;
|
|
4979
|
-
void presenter.onAssistantText(`${line}
|
|
4980
|
-
`);
|
|
5170
|
+
void presenter.onLog(`[claude_cli] ${line}`);
|
|
4981
5171
|
return;
|
|
4982
5172
|
}
|
|
4983
5173
|
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
@@ -4994,13 +5184,22 @@ function parseClaudeStructuredLine(line, context, state) {
|
|
|
4994
5184
|
break;
|
|
4995
5185
|
}
|
|
4996
5186
|
case "assistant": {
|
|
5187
|
+
if (presenter.pendingAskUserToolIds?.size) {
|
|
5188
|
+
console.info(`[claude_cli] Suppressing fallback assistant turn \u2014 ${presenter.pendingAskUserToolIds.size} AskUserQuestion(s) still pending`);
|
|
5189
|
+
break;
|
|
5190
|
+
}
|
|
5191
|
+
if (state.resultReceived && state.allowContinuation) {
|
|
5192
|
+
console.info("[claude_cli] Continuation turn detected \u2014 restarting presenter");
|
|
5193
|
+
state.resultReceived = false;
|
|
5194
|
+
state.stopHeartbeat?.();
|
|
5195
|
+
void presenter.onStart(context.config);
|
|
5196
|
+
}
|
|
5197
|
+
state.onNewOutput?.();
|
|
4997
5198
|
state.iterations += 1;
|
|
4998
5199
|
const message = typeof parsed.message === "object" && parsed.message !== null ? parsed.message : null;
|
|
4999
5200
|
const content = Array.isArray(message?.content) ? message.content : [];
|
|
5000
5201
|
for (const block of content) {
|
|
5001
|
-
if (typeof block !== "object" || block === null || !("type" in block))
|
|
5002
|
-
continue;
|
|
5003
|
-
}
|
|
5202
|
+
if (typeof block !== "object" || block === null || !("type" in block)) continue;
|
|
5004
5203
|
if (block.type === "text" && typeof block.text === "string") {
|
|
5005
5204
|
const text = block.text;
|
|
5006
5205
|
state.summary += `${text}
|
|
@@ -5015,6 +5214,25 @@ function parseClaudeStructuredLine(line, context, state) {
|
|
|
5015
5214
|
if (block.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") {
|
|
5016
5215
|
const toolBlock = block;
|
|
5017
5216
|
void presenter.onToolUse(toolBlock.name, toolBlock.input ?? {}, toolBlock.id);
|
|
5217
|
+
const lowerName = toolBlock.name.toLowerCase();
|
|
5218
|
+
if (INTERACTIVE_TOOL_NAMES.has(lowerName)) {
|
|
5219
|
+
if (!state.suppressedToolResultIds) state.suppressedToolResultIds = /* @__PURE__ */ new Set();
|
|
5220
|
+
state.suppressedToolResultIds.add(toolBlock.id);
|
|
5221
|
+
}
|
|
5222
|
+
const toolInput = toolBlock.input ?? {};
|
|
5223
|
+
if (lowerName === "askuserquestion") {
|
|
5224
|
+
if (!presenter.pendingAskUserToolIds) presenter.pendingAskUserToolIds = /* @__PURE__ */ new Set();
|
|
5225
|
+
presenter.pendingAskUserToolIds.add(toolBlock.id);
|
|
5226
|
+
console.info(`[claude_cli] AskUserQuestion tool_use detected \u2014 id=${toolBlock.id}, pendingSize=${presenter.pendingAskUserToolIds.size}, presenterHasProp=${Object.prototype.hasOwnProperty.call(presenter, "pendingAskUserToolIds")}`);
|
|
5227
|
+
const questions = Array.isArray(toolInput.questions) ? toolInput.questions : [];
|
|
5228
|
+
void presenter.onAskUser?.(toolBlock.id, questions);
|
|
5229
|
+
console.info(`[claude_cli] After onAskUser \u2014 pendingSize=${presenter.pendingAskUserToolIds?.size}`);
|
|
5230
|
+
} else if (lowerName === "exitplanmode") {
|
|
5231
|
+
void presenter.onExitPlanMode?.(toolBlock.id, toolInput);
|
|
5232
|
+
} else if (lowerName === "todowrite") {
|
|
5233
|
+
const todos = Array.isArray(toolInput.todos) ? toolInput.todos : [];
|
|
5234
|
+
void presenter.onTodoWrite?.(todos);
|
|
5235
|
+
}
|
|
5018
5236
|
}
|
|
5019
5237
|
}
|
|
5020
5238
|
break;
|
|
@@ -5024,19 +5242,26 @@ function parseClaudeStructuredLine(line, context, state) {
|
|
|
5024
5242
|
const content = Array.isArray(message?.content) ? message.content : [];
|
|
5025
5243
|
for (const block of content) {
|
|
5026
5244
|
if (typeof block === "object" && block !== null && "type" in block && block.type === "tool_result" && "tool_use_id" in block && typeof block.tool_use_id === "string") {
|
|
5245
|
+
if (state.suppressedToolResultIds?.has(block.tool_use_id)) {
|
|
5246
|
+
console.info(`[claude_cli] Suppressed auto-generated tool_result for ${block.tool_use_id}`);
|
|
5247
|
+
continue;
|
|
5248
|
+
}
|
|
5249
|
+
const isError = "is_error" in block && block.is_error === true ? true : void 0;
|
|
5027
5250
|
void presenter.onToolResult?.(
|
|
5028
5251
|
block.tool_use_id,
|
|
5029
|
-
extractToolResultContent(block.content)
|
|
5252
|
+
extractToolResultContent(block.content),
|
|
5253
|
+
isError
|
|
5030
5254
|
);
|
|
5031
5255
|
}
|
|
5032
5256
|
}
|
|
5033
5257
|
break;
|
|
5034
5258
|
}
|
|
5035
5259
|
case "result": {
|
|
5036
|
-
const
|
|
5037
|
-
const
|
|
5038
|
-
const
|
|
5039
|
-
const
|
|
5260
|
+
const usage = parsed.usage;
|
|
5261
|
+
const inputTokens = typeof usage?.input_tokens === "number" ? usage.input_tokens : 0;
|
|
5262
|
+
const outputTokens = typeof usage?.output_tokens === "number" ? usage.output_tokens : 0;
|
|
5263
|
+
const cacheReadTokens = typeof usage?.cache_read_input_tokens === "number" ? usage.cache_read_input_tokens : 0;
|
|
5264
|
+
const cacheCreationTokens = typeof usage?.cache_creation_input_tokens === "number" ? usage.cache_creation_input_tokens : 0;
|
|
5040
5265
|
state.usage = {
|
|
5041
5266
|
model: typeof parsed.model === "string" ? parsed.model : typeof context.config.selectedModel === "string" ? context.config.selectedModel : "claude",
|
|
5042
5267
|
numTurns: typeof parsed.num_turns === "number" ? parsed.num_turns : Math.max(state.iterations, 1),
|
|
@@ -5065,17 +5290,46 @@ function parseClaudeStructuredLine(line, context, state) {
|
|
|
5065
5290
|
const errObj = typeof parsed.error === "object" && parsed.error !== null ? parsed.error : null;
|
|
5066
5291
|
const errMsg = typeof parsed.error === "string" && parsed.error.trim() || errObj && typeof errObj.message === "string" && errObj.message.trim() || typeof parsed.message === "string" && parsed.message.trim() || typeof parsed.result === "string" && parsed.result.trim() || subtype;
|
|
5067
5292
|
state.error = errMsg;
|
|
5068
|
-
|
|
5293
|
+
console.error("[claude_cli] result subtype=", subtype, "error fields:", JSON.stringify({
|
|
5069
5294
|
error: parsed.error,
|
|
5070
5295
|
message: parsed.message,
|
|
5071
5296
|
result: typeof parsed.result === "string" ? parsed.result.slice(0, 200) : parsed.result,
|
|
5072
5297
|
session_id: parsed.session_id,
|
|
5073
5298
|
model: parsed.model,
|
|
5074
|
-
num_turns: parsed.num_turns
|
|
5075
|
-
|
|
5299
|
+
num_turns: parsed.num_turns
|
|
5300
|
+
}));
|
|
5301
|
+
}
|
|
5302
|
+
console.info(`[claude_cli] result event \u2014 pendingAskUserToolIds size=${presenter.pendingAskUserToolIds?.size ?? "undefined"}, hasOwnProp=${Object.prototype.hasOwnProperty.call(presenter, "pendingAskUserToolIds")}`);
|
|
5303
|
+
if (presenter.pendingAskUserToolIds?.size) {
|
|
5304
|
+
console.info(`[claude_cli] Keeping stdin open \u2014 ${presenter.pendingAskUserToolIds.size} AskUserQuestion(s) awaiting user response`);
|
|
5305
|
+
state.onNewOutput?.();
|
|
5306
|
+
break;
|
|
5307
|
+
}
|
|
5308
|
+
state.resultReceived = true;
|
|
5309
|
+
if (state.allowContinuation) {
|
|
5310
|
+
const continuationResult = {
|
|
5311
|
+
success: !state.error,
|
|
5312
|
+
summary: state.summary || state.error || "",
|
|
5313
|
+
filesModified: [],
|
|
5314
|
+
planFilesCreated: [],
|
|
5315
|
+
iterations: Math.max(state.iterations, 1),
|
|
5316
|
+
error: state.error ? classifyCliError(state.error, 0) : void 0,
|
|
5317
|
+
providerSessionId: state.runtimeSessionId,
|
|
5318
|
+
runtimeSessionId: state.runtimeSessionId,
|
|
5319
|
+
backendKind: context.config.backendKind,
|
|
5320
|
+
supportTier: context.config.supportTier,
|
|
5321
|
+
usage: state.usage
|
|
5076
5322
|
};
|
|
5077
|
-
|
|
5078
|
-
|
|
5323
|
+
void presenter.onComplete(continuationResult);
|
|
5324
|
+
state.onNewOutput?.();
|
|
5325
|
+
state.startHeartbeat?.();
|
|
5326
|
+
console.info("[claude_cli] Result received in continuation mode \u2014 keeping stdin open for background work");
|
|
5327
|
+
} else {
|
|
5328
|
+
state.onResultReceived?.();
|
|
5329
|
+
if (state.stdin && !state.stdin.destroyed) {
|
|
5330
|
+
state.stdin.end();
|
|
5331
|
+
console.info("[claude_cli] Closed stdin after result event to signal CLI exit");
|
|
5332
|
+
}
|
|
5079
5333
|
}
|
|
5080
5334
|
break;
|
|
5081
5335
|
}
|
|
@@ -5089,10 +5343,7 @@ function parseCodexStructuredLine(line, context, state) {
|
|
|
5089
5343
|
try {
|
|
5090
5344
|
parsed = JSON.parse(line);
|
|
5091
5345
|
} catch {
|
|
5092
|
-
|
|
5093
|
-
`;
|
|
5094
|
-
void presenter.onAssistantText(`${line}
|
|
5095
|
-
`);
|
|
5346
|
+
void presenter.onLog(`[codex] ${line}`);
|
|
5096
5347
|
return;
|
|
5097
5348
|
}
|
|
5098
5349
|
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
@@ -5101,13 +5352,9 @@ function parseCodexStructuredLine(line, context, state) {
|
|
|
5101
5352
|
state.runtimeSessionId = parsed.thread_id;
|
|
5102
5353
|
}
|
|
5103
5354
|
const extractCodexErrorMessage = (value2) => {
|
|
5104
|
-
if (typeof value2 !== "string")
|
|
5105
|
-
return "";
|
|
5106
|
-
}
|
|
5355
|
+
if (typeof value2 !== "string") return "";
|
|
5107
5356
|
const trimmed = value2.trim();
|
|
5108
|
-
if (!trimmed)
|
|
5109
|
-
return "";
|
|
5110
|
-
}
|
|
5357
|
+
if (!trimmed) return "";
|
|
5111
5358
|
try {
|
|
5112
5359
|
const parsedValue = JSON.parse(trimmed);
|
|
5113
5360
|
if (typeof parsedValue.detail === "string" && parsedValue.detail.trim().length > 0) {
|
|
@@ -5136,18 +5383,14 @@ function parseCodexStructuredLine(line, context, state) {
|
|
|
5136
5383
|
break;
|
|
5137
5384
|
case "item.started": {
|
|
5138
5385
|
const item = typeof parsed.item === "object" && parsed.item !== null ? parsed.item : null;
|
|
5139
|
-
if (!item || item.type !== "command_execution" || typeof item.id !== "string")
|
|
5140
|
-
break;
|
|
5141
|
-
}
|
|
5386
|
+
if (!item || item.type !== "command_execution" || typeof item.id !== "string") break;
|
|
5142
5387
|
const command = typeof item.command === "string" ? item.command : "";
|
|
5143
5388
|
void presenter.onToolUse("Bash", { command, cwd: context.cwd }, item.id);
|
|
5144
5389
|
break;
|
|
5145
5390
|
}
|
|
5146
5391
|
case "item.completed": {
|
|
5147
5392
|
const item = typeof parsed.item === "object" && parsed.item !== null ? parsed.item : null;
|
|
5148
|
-
if (!item || typeof item.type !== "string")
|
|
5149
|
-
break;
|
|
5150
|
-
}
|
|
5393
|
+
if (!item || typeof item.type !== "string") break;
|
|
5151
5394
|
if (item.type === "reasoning" && typeof item.text === "string") {
|
|
5152
5395
|
void presenter.onThinking(item.text);
|
|
5153
5396
|
break;
|
|
@@ -5160,8 +5403,8 @@ function parseCodexStructuredLine(line, context, state) {
|
|
|
5160
5403
|
}
|
|
5161
5404
|
if (item.type === "command_execution" && typeof item.id === "string") {
|
|
5162
5405
|
const output = typeof item.aggregated_output === "string" ? item.aggregated_output : "";
|
|
5163
|
-
const
|
|
5164
|
-
const resultText = output.trim().length > 0 ? output :
|
|
5406
|
+
const itemExitCode = typeof item.exit_code === "number" ? item.exit_code : null;
|
|
5407
|
+
const resultText = output.trim().length > 0 ? output : itemExitCode === null ? "" : `Exit code: ${itemExitCode}`;
|
|
5165
5408
|
void presenter.onToolResult?.(item.id, resultText);
|
|
5166
5409
|
break;
|
|
5167
5410
|
}
|
|
@@ -5205,20 +5448,17 @@ function parseCodexStructuredLine(line, context, state) {
|
|
|
5205
5448
|
}
|
|
5206
5449
|
case "mcp_tool_call_end": {
|
|
5207
5450
|
const toolId = typeof parsed.call_id === "string" ? parsed.call_id : `mcp-${Date.now()}`;
|
|
5208
|
-
|
|
5209
|
-
void presenter.onToolResult?.(toolId, JSON.stringify(result ?? {}, null, 2));
|
|
5451
|
+
void presenter.onToolResult?.(toolId, JSON.stringify(parsed.result ?? {}, null, 2));
|
|
5210
5452
|
break;
|
|
5211
5453
|
}
|
|
5212
5454
|
case "token_count": {
|
|
5213
5455
|
const info = typeof parsed.info === "object" && parsed.info !== null ? parsed.info : {};
|
|
5214
|
-
const inputTokens = typeof info.input_tokens === "number" ? info.input_tokens : 0;
|
|
5215
|
-
const outputTokens = typeof info.output_tokens === "number" ? info.output_tokens : 0;
|
|
5216
5456
|
state.usage = {
|
|
5217
5457
|
model: typeof info.model === "string" ? info.model : "codex",
|
|
5218
5458
|
numTurns: Math.max(state.iterations, 1),
|
|
5219
5459
|
durationMs: 0,
|
|
5220
|
-
inputTokens,
|
|
5221
|
-
outputTokens,
|
|
5460
|
+
inputTokens: typeof info.input_tokens === "number" ? info.input_tokens : 0,
|
|
5461
|
+
outputTokens: typeof info.output_tokens === "number" ? info.output_tokens : 0,
|
|
5222
5462
|
cacheReadTokens: typeof info.cached_input_tokens === "number" ? info.cached_input_tokens : 0,
|
|
5223
5463
|
cacheCreationTokens: 0,
|
|
5224
5464
|
costUsd: typeof info.total_cost_usd === "number" ? info.total_cost_usd : 0
|
|
@@ -5234,23 +5474,18 @@ function parseCodexStructuredLine(line, context, state) {
|
|
|
5234
5474
|
}
|
|
5235
5475
|
case "task_complete": {
|
|
5236
5476
|
const lastMessage = typeof parsed.last_agent_message === "string" ? parsed.last_agent_message : "";
|
|
5237
|
-
if (lastMessage.length > 0)
|
|
5238
|
-
state.summary = lastMessage;
|
|
5239
|
-
}
|
|
5477
|
+
if (lastMessage.length > 0) state.summary = lastMessage;
|
|
5240
5478
|
break;
|
|
5241
5479
|
}
|
|
5242
5480
|
case "turn.completed": {
|
|
5243
5481
|
const usage = typeof parsed.usage === "object" && parsed.usage !== null ? parsed.usage : {};
|
|
5244
|
-
const inputTokens = typeof usage.input_tokens === "number" ? usage.input_tokens : 0;
|
|
5245
|
-
const outputTokens = typeof usage.output_tokens === "number" ? usage.output_tokens : 0;
|
|
5246
|
-
const cacheReadTokens = typeof usage.cached_input_tokens === "number" ? usage.cached_input_tokens : 0;
|
|
5247
5482
|
state.usage = {
|
|
5248
5483
|
model: typeof context.config.selectedModel === "string" && context.config.selectedModel.trim() ? context.config.selectedModel : "codex",
|
|
5249
5484
|
numTurns: Math.max(state.iterations, 1),
|
|
5250
5485
|
durationMs: 0,
|
|
5251
|
-
inputTokens,
|
|
5252
|
-
outputTokens,
|
|
5253
|
-
cacheReadTokens,
|
|
5486
|
+
inputTokens: typeof usage.input_tokens === "number" ? usage.input_tokens : 0,
|
|
5487
|
+
outputTokens: typeof usage.output_tokens === "number" ? usage.output_tokens : 0,
|
|
5488
|
+
cacheReadTokens: typeof usage.cached_input_tokens === "number" ? usage.cached_input_tokens : 0,
|
|
5254
5489
|
cacheCreationTokens: 0,
|
|
5255
5490
|
costUsd: 0
|
|
5256
5491
|
};
|
|
@@ -5266,6 +5501,7 @@ function parseCodexStructuredLine(line, context, state) {
|
|
|
5266
5501
|
case "error": {
|
|
5267
5502
|
const message = extractCodexErrorMessage(parsed.message) || extractCodexErrorMessage(parsed.error) || typeof parsed.detail === "string" && parsed.detail.trim() || "";
|
|
5268
5503
|
if (message) {
|
|
5504
|
+
void presenter.onError(message);
|
|
5269
5505
|
state.error = message;
|
|
5270
5506
|
}
|
|
5271
5507
|
break;
|
|
@@ -5274,6 +5510,7 @@ function parseCodexStructuredLine(line, context, state) {
|
|
|
5274
5510
|
const error = typeof parsed.error === "object" && parsed.error !== null ? parsed.error : {};
|
|
5275
5511
|
const message = extractCodexErrorMessage(error.message) || extractCodexErrorMessage(error.detail) || typeof parsed.message === "string" && parsed.message.trim() || "";
|
|
5276
5512
|
if (message) {
|
|
5513
|
+
void presenter.onError(message);
|
|
5277
5514
|
state.error = message;
|
|
5278
5515
|
}
|
|
5279
5516
|
break;
|
|
@@ -5282,20 +5519,130 @@ function parseCodexStructuredLine(line, context, state) {
|
|
|
5282
5519
|
break;
|
|
5283
5520
|
}
|
|
5284
5521
|
}
|
|
5522
|
+
function extractCursorToolEntry(toolCall) {
|
|
5523
|
+
const [rawName, rawPayload] = Object.entries(toolCall)[0] ?? [];
|
|
5524
|
+
if (!rawName || typeof rawPayload !== "object" || rawPayload === null) return null;
|
|
5525
|
+
return { rawName, payload: rawPayload };
|
|
5526
|
+
}
|
|
5527
|
+
function formatCursorToolName(rawName) {
|
|
5528
|
+
if (rawName === "terminalToolCall") return "Bash";
|
|
5529
|
+
const trimmed = rawName.replace(/ToolCall$/, "");
|
|
5530
|
+
if (!trimmed) return "Cursor Tool";
|
|
5531
|
+
return trimmed.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/^[a-z]/, (char) => char.toUpperCase());
|
|
5532
|
+
}
|
|
5533
|
+
function buildCursorToolResultText(result) {
|
|
5534
|
+
if (typeof result === "string") return result;
|
|
5535
|
+
if (!result || typeof result !== "object") return JSON.stringify(result ?? {});
|
|
5536
|
+
const resultRecord = result;
|
|
5537
|
+
const success = typeof resultRecord.success === "object" && resultRecord.success !== null ? resultRecord.success : null;
|
|
5538
|
+
if (success) {
|
|
5539
|
+
if (typeof success.content === "string" && success.content.trim().length > 0) return success.content;
|
|
5540
|
+
if (typeof success.output === "string" && success.output.trim().length > 0) return success.output;
|
|
5541
|
+
const stdout = typeof success.stdout === "string" ? success.stdout.trim() : "";
|
|
5542
|
+
const stderr = typeof success.stderr === "string" ? success.stderr.trim() : "";
|
|
5543
|
+
if (stdout || stderr) return [stdout, stderr].filter(Boolean).join("\n\n");
|
|
5544
|
+
}
|
|
5545
|
+
const error = typeof resultRecord.error === "object" && resultRecord.error !== null ? resultRecord.error : null;
|
|
5546
|
+
if (error) {
|
|
5547
|
+
if (typeof error.message === "string" && error.message.trim().length > 0) return error.message;
|
|
5548
|
+
if (typeof error.content === "string" && error.content.trim().length > 0) return error.content;
|
|
5549
|
+
}
|
|
5550
|
+
return JSON.stringify(result, null, 2);
|
|
5551
|
+
}
|
|
5552
|
+
function parseCursorStructuredLine(line, context, state) {
|
|
5553
|
+
const presenter = context.presenter;
|
|
5554
|
+
let parsed = null;
|
|
5555
|
+
try {
|
|
5556
|
+
parsed = JSON.parse(line);
|
|
5557
|
+
} catch {
|
|
5558
|
+
if (line.trim().length > 0) void presenter.onLog(`[cursor_agent_cli] ${line}`);
|
|
5559
|
+
return;
|
|
5560
|
+
}
|
|
5561
|
+
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
5562
|
+
if (!type) return;
|
|
5563
|
+
if (typeof parsed.session_id === "string") state.runtimeSessionId = parsed.session_id;
|
|
5564
|
+
if (typeof parsed.model === "string") state.activeModel = parsed.model;
|
|
5565
|
+
switch (type) {
|
|
5566
|
+
case "system":
|
|
5567
|
+
break;
|
|
5568
|
+
case "assistant": {
|
|
5569
|
+
const message = typeof parsed.message === "object" && parsed.message !== null ? parsed.message : null;
|
|
5570
|
+
const content = Array.isArray(message?.content) ? message.content : [];
|
|
5571
|
+
for (const block of content) {
|
|
5572
|
+
if (!block || typeof block !== "object") continue;
|
|
5573
|
+
const part = block;
|
|
5574
|
+
const blockType = typeof part.type === "string" ? part.type : "";
|
|
5575
|
+
const text = typeof part.text === "string" ? part.text : "";
|
|
5576
|
+
if (!text) continue;
|
|
5577
|
+
state.iterations = Math.max(state.iterations, 1);
|
|
5578
|
+
if (blockType === "thinking" || blockType === "reasoning") {
|
|
5579
|
+
void presenter.onThinking(text);
|
|
5580
|
+
continue;
|
|
5581
|
+
}
|
|
5582
|
+
state.summary += text;
|
|
5583
|
+
void presenter.onAssistantText(text);
|
|
5584
|
+
}
|
|
5585
|
+
break;
|
|
5586
|
+
}
|
|
5587
|
+
case "tool_call": {
|
|
5588
|
+
const subtype = typeof parsed.subtype === "string" ? parsed.subtype : "";
|
|
5589
|
+
const toolId = typeof parsed.call_id === "string" ? parsed.call_id : `cursor-tool-${Date.now()}`;
|
|
5590
|
+
const toolCall = typeof parsed.tool_call === "object" && parsed.tool_call !== null ? parsed.tool_call : null;
|
|
5591
|
+
const entry = toolCall ? extractCursorToolEntry(toolCall) : null;
|
|
5592
|
+
if (!entry) break;
|
|
5593
|
+
state.iterations = Math.max(state.iterations, 1);
|
|
5594
|
+
const toolName = formatCursorToolName(entry.rawName);
|
|
5595
|
+
const toolArgs = typeof entry.payload.args === "object" && entry.payload.args !== null ? entry.payload.args : entry.payload;
|
|
5596
|
+
if (subtype === "started") {
|
|
5597
|
+
void presenter.onToolUse(toolName, toolArgs, toolId);
|
|
5598
|
+
break;
|
|
5599
|
+
}
|
|
5600
|
+
if (subtype === "completed") {
|
|
5601
|
+
void presenter.onToolResult?.(toolId, buildCursorToolResultText(entry.payload.result));
|
|
5602
|
+
}
|
|
5603
|
+
break;
|
|
5604
|
+
}
|
|
5605
|
+
case "result": {
|
|
5606
|
+
const subtype = typeof parsed.subtype === "string" ? parsed.subtype : "";
|
|
5607
|
+
const durationMs = typeof parsed.duration_ms === "number" ? parsed.duration_ms : 0;
|
|
5608
|
+
const resultText = typeof parsed.result === "string" ? parsed.result : "";
|
|
5609
|
+
state.iterations = Math.max(state.iterations, 1);
|
|
5610
|
+
state.usage = {
|
|
5611
|
+
model: state.activeModel || (typeof context.config.selectedModel === "string" && context.config.selectedModel.trim() ? context.config.selectedModel : "cursor-agent"),
|
|
5612
|
+
numTurns: Math.max(state.iterations, 1),
|
|
5613
|
+
durationMs,
|
|
5614
|
+
inputTokens: 0,
|
|
5615
|
+
outputTokens: 0,
|
|
5616
|
+
cacheReadTokens: 0,
|
|
5617
|
+
cacheCreationTokens: 0,
|
|
5618
|
+
costUsd: 0
|
|
5619
|
+
};
|
|
5620
|
+
void presenter.onUsageUpdate?.({
|
|
5621
|
+
model: state.usage.model,
|
|
5622
|
+
inputTokens: 0,
|
|
5623
|
+
outputTokens: 0,
|
|
5624
|
+
cacheReadTokens: 0,
|
|
5625
|
+
cacheCreationTokens: 0
|
|
5626
|
+
});
|
|
5627
|
+
if (resultText.trim().length > 0) state.summary = resultText;
|
|
5628
|
+
if ((parsed.is_error === true || subtype === "error") && !state.error) {
|
|
5629
|
+
state.error = resultText.trim() || "Cursor Agent run failed";
|
|
5630
|
+
void presenter.onError(state.error);
|
|
5631
|
+
}
|
|
5632
|
+
break;
|
|
5633
|
+
}
|
|
5634
|
+
default:
|
|
5635
|
+
break;
|
|
5636
|
+
}
|
|
5637
|
+
}
|
|
5285
5638
|
function buildGeminiToolResultText(parsed) {
|
|
5286
5639
|
const output = typeof parsed.output === "string" ? parsed.output : "";
|
|
5287
|
-
if (output.trim().length > 0)
|
|
5288
|
-
return output;
|
|
5289
|
-
}
|
|
5640
|
+
if (output.trim().length > 0) return output;
|
|
5290
5641
|
const error = typeof parsed.error === "object" && parsed.error !== null ? parsed.error : null;
|
|
5291
5642
|
const message = typeof error?.message === "string" ? error.message.trim() : "";
|
|
5292
5643
|
const errorType = typeof error?.type === "string" ? error.type.trim() : "";
|
|
5293
|
-
if (message && errorType) {
|
|
5294
|
-
|
|
5295
|
-
}
|
|
5296
|
-
if (message) {
|
|
5297
|
-
return message;
|
|
5298
|
-
}
|
|
5644
|
+
if (message && errorType) return `${errorType}: ${message}`;
|
|
5645
|
+
if (message) return message;
|
|
5299
5646
|
const status = typeof parsed.status === "string" ? parsed.status.trim() : "";
|
|
5300
5647
|
return status || "Tool completed";
|
|
5301
5648
|
}
|
|
@@ -5305,22 +5652,13 @@ function parseGeminiStructuredLine(line, context, state) {
|
|
|
5305
5652
|
try {
|
|
5306
5653
|
parsed = JSON.parse(line);
|
|
5307
5654
|
} catch {
|
|
5308
|
-
|
|
5309
|
-
`;
|
|
5310
|
-
void presenter.onAssistantText(`${line}
|
|
5311
|
-
`);
|
|
5655
|
+
void presenter.onLog(`[gemini_cli] ${line}`);
|
|
5312
5656
|
return;
|
|
5313
5657
|
}
|
|
5314
5658
|
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
5315
|
-
if (!type)
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
if (typeof parsed.session_id === "string") {
|
|
5319
|
-
state.runtimeSessionId = parsed.session_id;
|
|
5320
|
-
}
|
|
5321
|
-
if (typeof parsed.model === "string") {
|
|
5322
|
-
state.activeModel = parsed.model;
|
|
5323
|
-
}
|
|
5659
|
+
if (!type) return;
|
|
5660
|
+
if (typeof parsed.session_id === "string") state.runtimeSessionId = parsed.session_id;
|
|
5661
|
+
if (typeof parsed.model === "string") state.activeModel = parsed.model;
|
|
5324
5662
|
switch (type) {
|
|
5325
5663
|
case "init": {
|
|
5326
5664
|
const model2 = state.activeModel || context.config.selectedModel || "gemini";
|
|
@@ -5328,14 +5666,9 @@ function parseGeminiStructuredLine(line, context, state) {
|
|
|
5328
5666
|
break;
|
|
5329
5667
|
}
|
|
5330
5668
|
case "message": {
|
|
5331
|
-
|
|
5332
|
-
if (role !== "assistant") {
|
|
5333
|
-
break;
|
|
5334
|
-
}
|
|
5669
|
+
if (typeof parsed.role !== "string" || parsed.role !== "assistant") break;
|
|
5335
5670
|
const content = typeof parsed.content === "string" ? parsed.content : "";
|
|
5336
|
-
if (!content)
|
|
5337
|
-
break;
|
|
5338
|
-
}
|
|
5671
|
+
if (!content) break;
|
|
5339
5672
|
state.iterations = Math.max(state.iterations, 1);
|
|
5340
5673
|
state.summary += content;
|
|
5341
5674
|
void presenter.onAssistantText(content);
|
|
@@ -5359,9 +5692,11 @@ function parseGeminiStructuredLine(line, context, state) {
|
|
|
5359
5692
|
const message = typeof parsed.message === "string" && parsed.message.trim() || errObj && typeof errObj.message === "string" && errObj.message.trim() || typeof parsed.error === "string" && parsed.error.trim() || typeof parsed.error_message === "string" && parsed.error_message.trim() || "";
|
|
5360
5693
|
const severity = typeof parsed.severity === "string" ? parsed.severity : "error";
|
|
5361
5694
|
if (message) {
|
|
5362
|
-
void presenter.onLog(`[gemini_cli/${severity}] ${message}`);
|
|
5363
5695
|
if (severity === "error") {
|
|
5696
|
+
void presenter.onError(message);
|
|
5364
5697
|
state.error = message;
|
|
5698
|
+
} else {
|
|
5699
|
+
void presenter.onLog(`[gemini_cli/${severity}] ${message}`);
|
|
5365
5700
|
}
|
|
5366
5701
|
}
|
|
5367
5702
|
break;
|
|
@@ -5390,11 +5725,10 @@ function parseGeminiStructuredLine(line, context, state) {
|
|
|
5390
5725
|
const errObj = typeof parsed.error === "object" && parsed.error !== null ? parsed.error : null;
|
|
5391
5726
|
const errMsg = typeof parsed.error === "string" && parsed.error.trim() || errObj && typeof errObj.message === "string" && errObj.message.trim() || typeof parsed.error_message === "string" && parsed.error_message.trim() || typeof parsed.message === "string" && parsed.message.trim() || typeof parsed.result === "string" && parsed.result.trim() || parsed.status;
|
|
5392
5727
|
state.error = errMsg;
|
|
5393
|
-
console.error("[gemini_cli] result status=", parsed.status, "error
|
|
5728
|
+
console.error("[gemini_cli] result status=", parsed.status, "error:", JSON.stringify({
|
|
5394
5729
|
error: parsed.error,
|
|
5395
5730
|
error_message: parsed.error_message,
|
|
5396
|
-
message: parsed.message
|
|
5397
|
-
result: typeof parsed.result === "string" ? parsed.result.slice(0, 200) : parsed.result
|
|
5731
|
+
message: parsed.message
|
|
5398
5732
|
}));
|
|
5399
5733
|
}
|
|
5400
5734
|
break;
|
|
@@ -5403,8 +5737,8 @@ function parseGeminiStructuredLine(line, context, state) {
|
|
|
5403
5737
|
break;
|
|
5404
5738
|
}
|
|
5405
5739
|
}
|
|
5406
|
-
function getClaudePermissionMode(
|
|
5407
|
-
switch (
|
|
5740
|
+
function getClaudePermissionMode(mode2) {
|
|
5741
|
+
switch (mode2) {
|
|
5408
5742
|
case "plan":
|
|
5409
5743
|
case "ask":
|
|
5410
5744
|
case "review":
|
|
@@ -5413,40 +5747,108 @@ function getClaudePermissionMode(mode) {
|
|
|
5413
5747
|
return "bypassPermissions";
|
|
5414
5748
|
}
|
|
5415
5749
|
}
|
|
5750
|
+
function isCodexReadOnlyMode(mode2) {
|
|
5751
|
+
return mode2 === "plan" || mode2 === "ask" || mode2 === "review";
|
|
5752
|
+
}
|
|
5753
|
+
function shouldForceCursorAgent(mode2) {
|
|
5754
|
+
switch (mode2) {
|
|
5755
|
+
case "plan":
|
|
5756
|
+
case "ask":
|
|
5757
|
+
case "review":
|
|
5758
|
+
return false;
|
|
5759
|
+
default:
|
|
5760
|
+
return true;
|
|
5761
|
+
}
|
|
5762
|
+
}
|
|
5763
|
+
function writeImagesToTempFiles(images) {
|
|
5764
|
+
if (!images?.length) return { paths: [], cleanup: () => {
|
|
5765
|
+
} };
|
|
5766
|
+
const paths = [];
|
|
5767
|
+
for (const img of images) {
|
|
5768
|
+
const ext = img.mimeType.split("/")[1]?.replace("jpeg", "jpg") ?? "png";
|
|
5769
|
+
const filePath = (0, import_path.join)((0, import_os.tmpdir)(), `aiden-img-${img.id}.${ext}`);
|
|
5770
|
+
(0, import_fs.writeFileSync)(filePath, Buffer.from(img.data, "base64"));
|
|
5771
|
+
paths.push(filePath);
|
|
5772
|
+
}
|
|
5773
|
+
return {
|
|
5774
|
+
paths,
|
|
5775
|
+
cleanup: () => {
|
|
5776
|
+
for (const p of paths) {
|
|
5777
|
+
try {
|
|
5778
|
+
(0, import_fs.rmSync)(p, { force: true });
|
|
5779
|
+
} catch {
|
|
5780
|
+
}
|
|
5781
|
+
}
|
|
5782
|
+
}
|
|
5783
|
+
};
|
|
5784
|
+
}
|
|
5416
5785
|
function createClaudeCliBackend(command = "claude", defaultArgs = []) {
|
|
5417
5786
|
return {
|
|
5418
5787
|
kind: "claude_cli",
|
|
5419
5788
|
supportTier: "structured",
|
|
5420
5789
|
async run(context) {
|
|
5421
5790
|
const args = [
|
|
5422
|
-
"--print",
|
|
5423
5791
|
"--verbose",
|
|
5424
5792
|
"--output-format",
|
|
5425
5793
|
"stream-json",
|
|
5794
|
+
"--input-format",
|
|
5795
|
+
"stream-json",
|
|
5426
5796
|
"--permission-mode",
|
|
5427
5797
|
getClaudePermissionMode(context.config.mode)
|
|
5428
5798
|
];
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5799
|
+
const resumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
|
|
5800
|
+
if (!resumeId) {
|
|
5801
|
+
if (context.config.systemPrompt?.trim()) {
|
|
5802
|
+
args.push("--system-prompt", context.config.systemPrompt.trim());
|
|
5803
|
+
}
|
|
5804
|
+
if (context.config.systemPromptAppend?.trim()) {
|
|
5805
|
+
args.push("--append-system-prompt", context.config.systemPromptAppend.trim());
|
|
5806
|
+
}
|
|
5434
5807
|
}
|
|
5435
5808
|
if (context.config.selectedModel?.trim()) {
|
|
5436
5809
|
args.push("--model", context.config.selectedModel.trim());
|
|
5437
5810
|
}
|
|
5438
|
-
|
|
5811
|
+
if (context.config.mcpConfigPath?.trim()) {
|
|
5812
|
+
args.push("--mcp-config", context.config.mcpConfigPath.trim());
|
|
5813
|
+
}
|
|
5439
5814
|
if (resumeId) {
|
|
5440
5815
|
args.push("--resume", resumeId);
|
|
5441
5816
|
console.info("[claude_cli] Resuming session", { resumeId });
|
|
5442
5817
|
}
|
|
5443
|
-
args.push(...defaultArgs
|
|
5818
|
+
args.push(...defaultArgs);
|
|
5444
5819
|
return createGenericCliBackend({
|
|
5445
5820
|
kind: "claude_cli",
|
|
5446
5821
|
supportTier: "structured",
|
|
5447
5822
|
command,
|
|
5448
5823
|
args,
|
|
5449
|
-
parseStructuredLine: parseClaudeStructuredLine
|
|
5824
|
+
parseStructuredLine: parseClaudeStructuredLine,
|
|
5825
|
+
stdinWriter: (stdin, ctx) => {
|
|
5826
|
+
const contentBlocks = [];
|
|
5827
|
+
if (ctx.config.images?.length) {
|
|
5828
|
+
for (const img of ctx.config.images) {
|
|
5829
|
+
contentBlocks.push({
|
|
5830
|
+
type: "image",
|
|
5831
|
+
source: { type: "base64", media_type: img.mimeType, data: img.data }
|
|
5832
|
+
});
|
|
5833
|
+
}
|
|
5834
|
+
}
|
|
5835
|
+
const promptText = ctx.config.mode === "plan" ? buildPlanModePrefix(buildPromptWithSystem(ctx.config, ctx.promptText)) : buildPromptWithSystem(ctx.config, ctx.promptText);
|
|
5836
|
+
if (promptText.trim()) {
|
|
5837
|
+
contentBlocks.push({ type: "text", text: promptText });
|
|
5838
|
+
}
|
|
5839
|
+
console.info(`[claude_cli] stdinWriter: ${contentBlocks.length} content blocks (${ctx.config.images?.length ?? 0} images)`);
|
|
5840
|
+
const message = JSON.stringify({
|
|
5841
|
+
type: "user",
|
|
5842
|
+
message: { role: "user", content: contentBlocks },
|
|
5843
|
+
session_id: "default",
|
|
5844
|
+
parent_tool_use_id: null
|
|
5845
|
+
});
|
|
5846
|
+
stdin.write(message + "\n");
|
|
5847
|
+
},
|
|
5848
|
+
keepStdinOpen: true,
|
|
5849
|
+
allowContinuation: true,
|
|
5850
|
+
continuationIdleMs: 10 * 60 * 1e3
|
|
5851
|
+
// 10 minutes
|
|
5450
5852
|
}).run(context);
|
|
5451
5853
|
}
|
|
5452
5854
|
};
|
|
@@ -5462,45 +5864,168 @@ function createGeminiCliBackend(command = "gemini", defaultArgs = []) {
|
|
|
5462
5864
|
const resumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
|
|
5463
5865
|
const approvalMode = context.config.mode === "plan" || context.config.mode === "ask" || context.config.mode === "review" ? "plan" : "yolo";
|
|
5464
5866
|
args.push("--approval-mode", approvalMode);
|
|
5465
|
-
if (resumeId)
|
|
5466
|
-
|
|
5467
|
-
}
|
|
5468
|
-
if (context.config.selectedModel?.trim()) {
|
|
5469
|
-
args.push("--model", context.config.selectedModel.trim());
|
|
5470
|
-
}
|
|
5867
|
+
if (resumeId) args.push("--resume", resumeId);
|
|
5868
|
+
if (context.config.selectedModel?.trim()) args.push("--model", context.config.selectedModel.trim());
|
|
5471
5869
|
return [...args, ...defaultArgs];
|
|
5472
5870
|
},
|
|
5473
5871
|
promptViaStdin: true,
|
|
5474
5872
|
augmentPrompt: (context) => {
|
|
5475
5873
|
const basePrompt = buildPromptWithSystem(context.config, context.promptText);
|
|
5476
|
-
|
|
5477
|
-
return buildPlanModePrefix(basePrompt);
|
|
5478
|
-
}
|
|
5479
|
-
return basePrompt;
|
|
5874
|
+
return context.config.mode === "plan" ? buildPlanModePrefix(basePrompt) : basePrompt;
|
|
5480
5875
|
},
|
|
5481
5876
|
parseStructuredLine: parseGeminiStructuredLine
|
|
5482
5877
|
});
|
|
5483
5878
|
}
|
|
5484
|
-
function
|
|
5879
|
+
function isCursorAgentCliModelId(model2) {
|
|
5880
|
+
return model2 === "auto" || /^composer-/.test(model2) || /^gpt-\d/.test(model2) || /^claude-\d/.test(model2) || /^gemini-\d/.test(model2) || /^grok-\d/.test(model2) || /^kimi-/.test(model2);
|
|
5881
|
+
}
|
|
5882
|
+
function createCursorAgentCliBackend(command = "cursor-agent", defaultArgs = []) {
|
|
5883
|
+
return createGenericCliBackend({
|
|
5884
|
+
kind: "cursor_agent_cli",
|
|
5885
|
+
supportTier: "structured",
|
|
5886
|
+
command,
|
|
5887
|
+
args: [],
|
|
5888
|
+
buildArgs: (context, prompt) => {
|
|
5889
|
+
const args = ["--print", "--output-format", "stream-json"];
|
|
5890
|
+
const resumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
|
|
5891
|
+
if (shouldForceCursorAgent(context.config.mode)) args.push("--force");
|
|
5892
|
+
if (resumeId) args.push("--resume", resumeId);
|
|
5893
|
+
const model2 = context.config.selectedModel?.trim();
|
|
5894
|
+
if (model2 && isCursorAgentCliModelId(model2)) args.push("--model", model2);
|
|
5895
|
+
return [...args, ...defaultArgs, prompt];
|
|
5896
|
+
},
|
|
5897
|
+
augmentPrompt: (context) => {
|
|
5898
|
+
const base = buildPromptWithSystem(context.config, context.promptText);
|
|
5899
|
+
return context.config.mode === "plan" ? buildPlanModePrefix(base) : base;
|
|
5900
|
+
},
|
|
5901
|
+
parseStructuredLine: parseCursorStructuredLine
|
|
5902
|
+
});
|
|
5903
|
+
}
|
|
5904
|
+
function droidExecPermissionArgs(mode2) {
|
|
5905
|
+
if (mode2 === "plan" || mode2 === "ask" || mode2 === "review") return [];
|
|
5906
|
+
return ["--auto", "high"];
|
|
5907
|
+
}
|
|
5908
|
+
function parseDroidStructuredLine(line, context, state) {
|
|
5485
5909
|
const presenter = context.presenter;
|
|
5486
5910
|
let parsed = null;
|
|
5487
5911
|
try {
|
|
5488
5912
|
parsed = JSON.parse(line);
|
|
5489
5913
|
} catch {
|
|
5490
|
-
if (line.trim().length > 0) {
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5914
|
+
if (line.trim().length > 0) void presenter.onLog(`[droid_cli] ${line}`);
|
|
5915
|
+
return;
|
|
5916
|
+
}
|
|
5917
|
+
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
5918
|
+
if (!type) return;
|
|
5919
|
+
if (typeof parsed.session_id === "string") state.runtimeSessionId = parsed.session_id;
|
|
5920
|
+
if (typeof parsed.model === "string") state.activeModel = parsed.model;
|
|
5921
|
+
switch (type) {
|
|
5922
|
+
case "system": {
|
|
5923
|
+
const subtype = typeof parsed.subtype === "string" ? parsed.subtype : "";
|
|
5924
|
+
if (subtype === "init") {
|
|
5925
|
+
if (typeof parsed.session_id === "string") state.runtimeSessionId = parsed.session_id;
|
|
5926
|
+
if (typeof parsed.model === "string") state.activeModel = parsed.model;
|
|
5927
|
+
}
|
|
5928
|
+
break;
|
|
5495
5929
|
}
|
|
5930
|
+
case "message": {
|
|
5931
|
+
const role = typeof parsed.role === "string" ? parsed.role : "";
|
|
5932
|
+
if (role !== "assistant") break;
|
|
5933
|
+
const text = typeof parsed.text === "string" ? parsed.text : "";
|
|
5934
|
+
if (!text) break;
|
|
5935
|
+
state.iterations = Math.max(state.iterations, 1);
|
|
5936
|
+
state.summary += text;
|
|
5937
|
+
void presenter.onAssistantText(text);
|
|
5938
|
+
break;
|
|
5939
|
+
}
|
|
5940
|
+
case "tool_call": {
|
|
5941
|
+
const toolId = typeof parsed.id === "string" ? parsed.id : `droid-tool-${Date.now()}`;
|
|
5942
|
+
const toolName = typeof parsed.toolName === "string" ? parsed.toolName : "Tool";
|
|
5943
|
+
const parameters = typeof parsed.parameters === "object" && parsed.parameters !== null ? parsed.parameters : {};
|
|
5944
|
+
state.iterations = Math.max(state.iterations, 1);
|
|
5945
|
+
void presenter.onToolUse(toolName, parameters, toolId);
|
|
5946
|
+
break;
|
|
5947
|
+
}
|
|
5948
|
+
case "tool_result": {
|
|
5949
|
+
const toolId = typeof parsed.id === "string" ? parsed.id : `droid-tool-${Date.now()}`;
|
|
5950
|
+
const value2 = parsed.value;
|
|
5951
|
+
const content = typeof value2 === "string" ? value2 : value2 !== void 0 ? JSON.stringify(value2) : "";
|
|
5952
|
+
const isError = parsed.isError === true;
|
|
5953
|
+
void presenter.onToolResult?.(toolId, content, isError);
|
|
5954
|
+
break;
|
|
5955
|
+
}
|
|
5956
|
+
case "completion": {
|
|
5957
|
+
const finalText = typeof parsed.finalText === "string" ? parsed.finalText : "";
|
|
5958
|
+
const numTurns = typeof parsed.numTurns === "number" ? parsed.numTurns : 1;
|
|
5959
|
+
const durationMs = typeof parsed.durationMs === "number" ? parsed.durationMs : 0;
|
|
5960
|
+
state.iterations = Math.max(state.iterations, numTurns, finalText ? 1 : 0);
|
|
5961
|
+
if (finalText.trim() && !state.summary.trim()) {
|
|
5962
|
+
state.summary = finalText;
|
|
5963
|
+
void presenter.onAssistantText(finalText);
|
|
5964
|
+
}
|
|
5965
|
+
state.usage = {
|
|
5966
|
+
model: state.activeModel || (typeof context.config.selectedModel === "string" && context.config.selectedModel.trim() ? context.config.selectedModel : "droid"),
|
|
5967
|
+
numTurns: Math.max(numTurns, 1),
|
|
5968
|
+
durationMs,
|
|
5969
|
+
inputTokens: 0,
|
|
5970
|
+
outputTokens: 0,
|
|
5971
|
+
cacheReadTokens: 0,
|
|
5972
|
+
cacheCreationTokens: 0,
|
|
5973
|
+
costUsd: 0
|
|
5974
|
+
};
|
|
5975
|
+
void presenter.onUsageUpdate?.({
|
|
5976
|
+
model: state.usage.model,
|
|
5977
|
+
inputTokens: 0,
|
|
5978
|
+
outputTokens: 0,
|
|
5979
|
+
cacheReadTokens: 0,
|
|
5980
|
+
cacheCreationTokens: 0
|
|
5981
|
+
});
|
|
5982
|
+
break;
|
|
5983
|
+
}
|
|
5984
|
+
default:
|
|
5985
|
+
break;
|
|
5986
|
+
}
|
|
5987
|
+
}
|
|
5988
|
+
function createDroidCliBackend(command = "droid", defaultArgs = []) {
|
|
5989
|
+
return createGenericCliBackend({
|
|
5990
|
+
kind: "droid_cli",
|
|
5991
|
+
supportTier: "structured",
|
|
5992
|
+
command,
|
|
5993
|
+
args: [],
|
|
5994
|
+
buildArgs: (context, prompt) => {
|
|
5995
|
+
const args = [
|
|
5996
|
+
"exec",
|
|
5997
|
+
"--output-format",
|
|
5998
|
+
"stream-json",
|
|
5999
|
+
...droidExecPermissionArgs(context.config.mode),
|
|
6000
|
+
...defaultArgs
|
|
6001
|
+
];
|
|
6002
|
+
const resumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
|
|
6003
|
+
if (resumeId) args.push("--session-id", resumeId);
|
|
6004
|
+
const model2 = context.config.selectedModel?.trim();
|
|
6005
|
+
if (model2) args.push("-m", model2);
|
|
6006
|
+
args.push(prompt);
|
|
6007
|
+
return args;
|
|
6008
|
+
},
|
|
6009
|
+
augmentPrompt: (context) => {
|
|
6010
|
+
const base = buildPromptWithSystem(context.config, context.promptText);
|
|
6011
|
+
return context.config.mode === "plan" ? buildPlanModePrefix(base) : base;
|
|
6012
|
+
},
|
|
6013
|
+
parseStructuredLine: parseDroidStructuredLine
|
|
6014
|
+
});
|
|
6015
|
+
}
|
|
6016
|
+
function parseOpencodeStructuredLine(line, context, state) {
|
|
6017
|
+
const presenter = context.presenter;
|
|
6018
|
+
let parsed = null;
|
|
6019
|
+
try {
|
|
6020
|
+
parsed = JSON.parse(line);
|
|
6021
|
+
} catch {
|
|
6022
|
+
if (line.trim().length > 0) void presenter.onLog(`[opencode] ${line}`);
|
|
5496
6023
|
return;
|
|
5497
6024
|
}
|
|
5498
6025
|
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
5499
6026
|
if (!type) return;
|
|
5500
6027
|
const sessionID = typeof parsed.sessionID === "string" ? parsed.sessionID : void 0;
|
|
5501
|
-
if (sessionID)
|
|
5502
|
-
state.runtimeSessionId = sessionID;
|
|
5503
|
-
}
|
|
6028
|
+
if (sessionID) state.runtimeSessionId = sessionID;
|
|
5504
6029
|
const part = typeof parsed.part === "object" && parsed.part !== null ? parsed.part : null;
|
|
5505
6030
|
switch (type) {
|
|
5506
6031
|
case "step_start": {
|
|
@@ -5534,20 +6059,49 @@ function parseOpencodeStructuredLine(line, context, state) {
|
|
|
5534
6059
|
if (part && typeof part.tokens === "object" && part.tokens !== null) {
|
|
5535
6060
|
const tokens = part.tokens;
|
|
5536
6061
|
state.usage = {
|
|
6062
|
+
model: typeof context.config.selectedModel === "string" && context.config.selectedModel.trim() || "opencode",
|
|
6063
|
+
numTurns: Math.max(state.iterations, 1),
|
|
6064
|
+
durationMs: 0,
|
|
5537
6065
|
inputTokens: tokens.input ?? 0,
|
|
5538
|
-
outputTokens: tokens.output ?? 0
|
|
6066
|
+
outputTokens: tokens.output ?? 0,
|
|
6067
|
+
cacheReadTokens: tokens.cache_read ?? 0,
|
|
6068
|
+
cacheCreationTokens: tokens.cache_creation ?? 0,
|
|
6069
|
+
costUsd: 0
|
|
5539
6070
|
};
|
|
6071
|
+
void presenter.onUsageUpdate?.({
|
|
6072
|
+
model: state.usage.model,
|
|
6073
|
+
inputTokens: state.usage.inputTokens,
|
|
6074
|
+
outputTokens: state.usage.outputTokens,
|
|
6075
|
+
cacheReadTokens: state.usage.cacheReadTokens,
|
|
6076
|
+
cacheCreationTokens: state.usage.cacheCreationTokens
|
|
6077
|
+
});
|
|
5540
6078
|
}
|
|
5541
6079
|
break;
|
|
5542
6080
|
}
|
|
5543
6081
|
case "session.error":
|
|
5544
6082
|
case "error": {
|
|
5545
6083
|
const errObj = typeof parsed.error === "object" && parsed.error !== null ? parsed.error : null;
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
6084
|
+
if (!state.error) {
|
|
6085
|
+
console.error("[opencode] error event (raw):", JSON.stringify(parsed).slice(0, 500));
|
|
6086
|
+
}
|
|
6087
|
+
const rawMessage = typeof parsed.message === "string" && parsed.message.trim() || part && typeof part.message === "string" && part.message.trim() || typeof parsed.error === "string" && parsed.error.trim() || errObj && typeof errObj.message === "string" && errObj.message.trim() || part && typeof part.error === "string" && part.error.trim() || // Also try data.message — some opencode versions nest the message here
|
|
6088
|
+
(typeof parsed.data?.message === "string" ? parsed.data.message.trim() : "") || "";
|
|
6089
|
+
const message = rawMessage || "OpenCode encountered an error. Check your provider and model settings.";
|
|
6090
|
+
if (!state.error) {
|
|
6091
|
+
void presenter.onError(message);
|
|
5549
6092
|
state.error = message;
|
|
5550
6093
|
}
|
|
6094
|
+
const pid = state.process?.pid;
|
|
6095
|
+
if (pid) {
|
|
6096
|
+
try {
|
|
6097
|
+
process.kill(-pid, "SIGTERM");
|
|
6098
|
+
} catch {
|
|
6099
|
+
try {
|
|
6100
|
+
state.process?.kill("SIGTERM");
|
|
6101
|
+
} catch {
|
|
6102
|
+
}
|
|
6103
|
+
}
|
|
6104
|
+
}
|
|
5551
6105
|
break;
|
|
5552
6106
|
}
|
|
5553
6107
|
default:
|
|
@@ -5555,25 +6109,40 @@ function parseOpencodeStructuredLine(line, context, state) {
|
|
|
5555
6109
|
}
|
|
5556
6110
|
}
|
|
5557
6111
|
function createOpencodeCliBackend(command = "opencode", defaultArgs = []) {
|
|
5558
|
-
return
|
|
6112
|
+
return {
|
|
5559
6113
|
kind: "opencode_cli",
|
|
5560
6114
|
supportTier: "structured",
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
6115
|
+
async run(context) {
|
|
6116
|
+
const { paths: imagePaths, cleanup } = writeImagesToTempFiles(context.config.images);
|
|
6117
|
+
try {
|
|
6118
|
+
return await createGenericCliBackend({
|
|
6119
|
+
kind: "opencode_cli",
|
|
6120
|
+
supportTier: "structured",
|
|
6121
|
+
command,
|
|
6122
|
+
args: [],
|
|
6123
|
+
buildArgs: (ctx, prompt) => {
|
|
6124
|
+
const args = ["run", "--format", "json"];
|
|
6125
|
+
const resumeId = ctx.config.runtimeSessionId?.trim() || ctx.config.providerSessionId?.trim();
|
|
6126
|
+
if (resumeId) args.push("--session", resumeId);
|
|
6127
|
+
const model2 = ctx.config.selectedModel?.trim();
|
|
6128
|
+
if (model2 && model2.includes("/")) args.push("--model", model2);
|
|
6129
|
+
const fileArgs = imagePaths.flatMap((p) => ["--file", p]);
|
|
6130
|
+
if (fileArgs.length > 0) {
|
|
6131
|
+
return [...args, ...fileArgs, ...defaultArgs, "--", prompt];
|
|
6132
|
+
}
|
|
6133
|
+
return [...args, ...defaultArgs, prompt];
|
|
6134
|
+
},
|
|
6135
|
+
augmentPrompt: (ctx) => {
|
|
6136
|
+
const base = buildPromptWithSystem(ctx.config, ctx.promptText);
|
|
6137
|
+
return ctx.config.mode === "plan" ? buildPlanModePrefix(base) : base;
|
|
6138
|
+
},
|
|
6139
|
+
parseStructuredLine: parseOpencodeStructuredLine
|
|
6140
|
+
}).run(context);
|
|
6141
|
+
} finally {
|
|
6142
|
+
cleanup();
|
|
6143
|
+
}
|
|
6144
|
+
}
|
|
6145
|
+
};
|
|
5577
6146
|
}
|
|
5578
6147
|
function createGenericCliPassthroughBackend(command, defaultArgs = []) {
|
|
5579
6148
|
return createGenericCliBackend({
|
|
@@ -5585,95 +6154,121 @@ function createGenericCliPassthroughBackend(command, defaultArgs = []) {
|
|
|
5585
6154
|
});
|
|
5586
6155
|
}
|
|
5587
6156
|
function createCodexRuntimeBackend(command = "codex", defaultArgs = []) {
|
|
5588
|
-
return
|
|
6157
|
+
return {
|
|
5589
6158
|
kind: "codex_app_server",
|
|
5590
6159
|
supportTier: "structured",
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
6160
|
+
async run(context) {
|
|
6161
|
+
const { paths: imagePaths, cleanup } = writeImagesToTempFiles(context.config.images);
|
|
6162
|
+
try {
|
|
6163
|
+
return await createGenericCliBackend({
|
|
6164
|
+
kind: "codex_app_server",
|
|
6165
|
+
supportTier: "structured",
|
|
6166
|
+
command,
|
|
6167
|
+
args: [],
|
|
6168
|
+
buildArgs: (ctx) => {
|
|
6169
|
+
const resumeId = ctx.config.runtimeSessionId?.trim() || ctx.config.providerSessionId?.trim();
|
|
6170
|
+
const modelArgs = ctx.config.selectedModel?.trim() ? ["--model", ctx.config.selectedModel.trim()] : [];
|
|
6171
|
+
const permissionArgs = isCodexReadOnlyMode(ctx.config.mode) ? ["--ask-for-approval", "never", "--sandbox", "read-only"] : ["--dangerously-bypass-approvals-and-sandbox"];
|
|
6172
|
+
const imageArgs = imagePaths.flatMap((p) => ["--image", p]);
|
|
6173
|
+
const baseArgs = resumeId ? [...permissionArgs, "exec", "resume", "--json", "--skip-git-repo-check", ...modelArgs, ...imageArgs, resumeId, "-"] : [...permissionArgs, "exec", "--json", "--skip-git-repo-check", ...modelArgs, ...imageArgs, "-"];
|
|
6174
|
+
return [...baseArgs, ...defaultArgs];
|
|
6175
|
+
},
|
|
6176
|
+
promptViaStdin: true,
|
|
6177
|
+
augmentPrompt: (ctx) => {
|
|
6178
|
+
const basePrompt = buildPromptWithSystem(ctx.config, ctx.promptText);
|
|
6179
|
+
return ctx.config.mode === "plan" ? buildPlanModePrefix(basePrompt) : basePrompt;
|
|
6180
|
+
},
|
|
6181
|
+
parseStructuredLine: parseCodexStructuredLine
|
|
6182
|
+
}).run(context);
|
|
6183
|
+
} finally {
|
|
6184
|
+
cleanup();
|
|
5603
6185
|
}
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
parseStructuredLine: parseCodexStructuredLine
|
|
5607
|
-
});
|
|
6186
|
+
}
|
|
6187
|
+
};
|
|
5608
6188
|
}
|
|
5609
|
-
|
|
5610
|
-
// src/core-agent.ts
|
|
5611
|
-
var MODE_CONFIG = {
|
|
5612
|
-
agent: {},
|
|
5613
|
-
plan: {
|
|
5614
|
-
systemPromptHint: "You are in plan mode. Focus on analysis, sequencing, and implementation planning rather than making edits."
|
|
5615
|
-
},
|
|
5616
|
-
debug: {
|
|
5617
|
-
systemPromptHint: "You are in debug mode. Prioritize reproducing the issue, isolating the cause, and validating the fix."
|
|
5618
|
-
},
|
|
5619
|
-
ask: {
|
|
5620
|
-
systemPromptHint: "You are in ask mode. Stay read-only and explain the code or architecture clearly without changing files."
|
|
5621
|
-
},
|
|
5622
|
-
review: {
|
|
5623
|
-
systemPromptHint: "You are in review mode. Inspect the changes, identify real issues, and summarize risks and regressions."
|
|
5624
|
-
}
|
|
5625
|
-
};
|
|
5626
6189
|
var DEFAULT_MODEL = "claude-sonnet-4-6";
|
|
6190
|
+
var DROID_DEFAULT_MODEL = "claude-opus-4-7";
|
|
5627
6191
|
function canonicalizePath(inputPath) {
|
|
5628
6192
|
try {
|
|
5629
|
-
const resolved = (0,
|
|
5630
|
-
return (0,
|
|
5631
|
-
} catch
|
|
5632
|
-
console.warn("CoreAgent", "Failed to canonicalize path", { inputPath, error });
|
|
6193
|
+
const resolved = (0, import_path2.resolve)(inputPath);
|
|
6194
|
+
return (0, import_fs2.existsSync)(resolved) ? (0, import_fs2.realpathSync)(resolved) : resolved;
|
|
6195
|
+
} catch {
|
|
5633
6196
|
return null;
|
|
5634
6197
|
}
|
|
5635
6198
|
}
|
|
5636
6199
|
function buildPromptWithFiles(task2, files) {
|
|
5637
|
-
if (!files || files.length === 0)
|
|
5638
|
-
return task2;
|
|
5639
|
-
}
|
|
6200
|
+
if (!files || files.length === 0) return task2;
|
|
5640
6201
|
const fileList = files.map((file) => `- ${file.filename} (${file.label}): ${file.tempPath}`).join("\n");
|
|
5641
6202
|
return `${task2}
|
|
5642
6203
|
|
|
5643
6204
|
The user has attached the following files. You can read them using your available tools:
|
|
5644
6205
|
${fileList}`;
|
|
5645
6206
|
}
|
|
5646
|
-
var
|
|
6207
|
+
var BaseMachineAgent = class _BaseMachineAgent {
|
|
6208
|
+
static MAX_RETRIES = 2;
|
|
6209
|
+
static RETRY_BASE_DELAY_MS = 3e3;
|
|
5647
6210
|
presenter;
|
|
5648
|
-
abortController = null;
|
|
5649
6211
|
runtime;
|
|
5650
|
-
|
|
5651
|
-
pendingPlanApprovalResolvers = /* @__PURE__ */ new Map();
|
|
6212
|
+
abortController = null;
|
|
5652
6213
|
constructor(presenter, runtime) {
|
|
5653
6214
|
this.presenter = presenter;
|
|
5654
6215
|
this.runtime = runtime;
|
|
5655
6216
|
}
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
6217
|
+
// ── Overridable hooks (defaults match sandbox behaviour) ───────────────
|
|
6218
|
+
/**
|
|
6219
|
+
* Extra system-prompt sections appended after identity + mode hint.
|
|
6220
|
+
* Sandbox: artifact IDs from env vars.
|
|
6221
|
+
* Desktop: taskMeta block + artifact IDs from AgentConfig fields.
|
|
6222
|
+
*/
|
|
6223
|
+
buildExtraSystemPromptParts(_config) {
|
|
6224
|
+
return [];
|
|
5662
6225
|
}
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
6226
|
+
/**
|
|
6227
|
+
* Return a callback to be notified when the CLI child process spawns.
|
|
6228
|
+
* Desktop returns a closure that registers the process for kill tracking.
|
|
6229
|
+
* Sandbox returns undefined (no process management needed).
|
|
6230
|
+
*/
|
|
6231
|
+
getOnProcessSpawned() {
|
|
6232
|
+
return void 0;
|
|
5669
6233
|
}
|
|
6234
|
+
/**
|
|
6235
|
+
* Whether to skip calling presenter.onComplete() after runCliBackend.
|
|
6236
|
+
* Desktop returns true when the agent was externally killed to avoid
|
|
6237
|
+
* a second onComplete firing on top of the "Interrupted by user" event.
|
|
6238
|
+
*/
|
|
6239
|
+
shouldSkipOnComplete() {
|
|
6240
|
+
return false;
|
|
6241
|
+
}
|
|
6242
|
+
/**
|
|
6243
|
+
* Called when a session-resume attempt fails with a non-transient error.
|
|
6244
|
+
*
|
|
6245
|
+
* - Return null → retry immediately without the session ID (sandbox default).
|
|
6246
|
+
* - Return AgentResult → stop and surface this result, no retry (desktop behaviour).
|
|
6247
|
+
*/
|
|
6248
|
+
async onSessionResumeFailure(_runtimeConfig, _lastResult) {
|
|
6249
|
+
this.presenter.onLog("Session resume failed, starting fresh session...");
|
|
6250
|
+
return null;
|
|
6251
|
+
}
|
|
6252
|
+
/**
|
|
6253
|
+
* Called before sleeping between retry attempts.
|
|
6254
|
+
* Desktop uses this to wait for the previous child process to exit.
|
|
6255
|
+
*/
|
|
6256
|
+
async beforeRetry() {
|
|
6257
|
+
}
|
|
6258
|
+
/**
|
|
6259
|
+
* Build the prompt text sent to the CLI backend.
|
|
6260
|
+
* Default appends file attachment paths (sandbox uses this).
|
|
6261
|
+
* Desktop overrides to return config.task unchanged.
|
|
6262
|
+
*/
|
|
6263
|
+
buildPromptText(config) {
|
|
6264
|
+
return buildPromptWithFiles(config.task, config.files);
|
|
6265
|
+
}
|
|
6266
|
+
// ── Shared helpers ─────────────────────────────────────────────────────
|
|
5670
6267
|
abort() {
|
|
5671
6268
|
this.abortController?.abort();
|
|
5672
6269
|
}
|
|
5673
6270
|
isAbortError(error) {
|
|
5674
|
-
if (error instanceof Error && error.name === "AbortError")
|
|
5675
|
-
return true;
|
|
5676
|
-
}
|
|
6271
|
+
if (error instanceof Error && error.name === "AbortError") return true;
|
|
5677
6272
|
const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
5678
6273
|
return message.includes("aborted") || message.includes("interrupted") || message.includes("cancelled") || message.includes("canceled");
|
|
5679
6274
|
}
|
|
@@ -5684,6 +6279,8 @@ var CoreAgent = class {
|
|
|
5684
6279
|
switch (backendKind2) {
|
|
5685
6280
|
case "claude_cli":
|
|
5686
6281
|
case "codex_app_server":
|
|
6282
|
+
case "cursor_agent_cli":
|
|
6283
|
+
case "droid_cli":
|
|
5687
6284
|
case "gemini_cli":
|
|
5688
6285
|
case "opencode_cli":
|
|
5689
6286
|
return "structured";
|
|
@@ -5692,77 +6289,112 @@ var CoreAgent = class {
|
|
|
5692
6289
|
return "text";
|
|
5693
6290
|
}
|
|
5694
6291
|
}
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
const env = {};
|
|
5698
|
-
for (const [key, value2] of Object.entries(loginEnv)) {
|
|
5699
|
-
if (value2 !== void 0) {
|
|
5700
|
-
env[key] = value2;
|
|
5701
|
-
}
|
|
5702
|
-
}
|
|
5703
|
-
env.PATH = `/home/user/.local/node/bin:${env.PATH ?? ""}`;
|
|
5704
|
-
return env;
|
|
5705
|
-
}
|
|
5706
|
-
async buildRuntimeConfig(config, safeProjectPath) {
|
|
6292
|
+
// ── Core lifecycle ─────────────────────────────────────────────────────
|
|
6293
|
+
buildRuntimeConfig(config, safeProjectPath) {
|
|
5707
6294
|
const backendKind2 = this.resolveBackendKind(config);
|
|
5708
6295
|
const supportTier = config.supportTier || this.getDefaultSupportTier(backendKind2);
|
|
5709
|
-
const systemPromptParts = [config.systemPrompt?.trim()].filter(
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
6296
|
+
const systemPromptParts = [AIDEN_IDENTITY_PROMPT, config.systemPrompt?.trim()].filter(
|
|
6297
|
+
Boolean
|
|
6298
|
+
);
|
|
6299
|
+
const modeHint = getAidenModePrompt(config.mode);
|
|
6300
|
+
if (modeHint) systemPromptParts.push(modeHint);
|
|
6301
|
+
const workingDir = config.worktreePath || safeProjectPath;
|
|
6302
|
+
if (workingDir) {
|
|
5715
6303
|
systemPromptParts.push(
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
"Prefer relative paths and keep your work scoped to this project."
|
|
5719
|
-
].join("\n")
|
|
5720
|
-
);
|
|
5721
|
-
}
|
|
5722
|
-
if (config.images?.length) {
|
|
5723
|
-
this.presenter.onLog(
|
|
5724
|
-
`Image attachments are not yet passed directly to the CLI agent. Attached image count: ${config.images.length}.`
|
|
6304
|
+
`You are working inside: ${workingDir}
|
|
6305
|
+
Prefer relative paths and keep your work scoped to this project.`
|
|
5725
6306
|
);
|
|
5726
6307
|
}
|
|
6308
|
+
const extra = this.buildExtraSystemPromptParts(config);
|
|
6309
|
+
systemPromptParts.push(...extra);
|
|
5727
6310
|
return {
|
|
5728
6311
|
...config,
|
|
5729
6312
|
backendKind: backendKind2,
|
|
5730
6313
|
supportTier,
|
|
5731
6314
|
runtimeCommand: config.runtimeCommand || this.runtime.runtimeCommand,
|
|
5732
6315
|
runtimeArgs: config.runtimeArgs || this.runtime.runtimeArgs,
|
|
5733
|
-
selectedModel: config.selectedModel || DEFAULT_MODEL,
|
|
6316
|
+
selectedModel: config.selectedModel || (backendKind2 === "droid_cli" ? DROID_DEFAULT_MODEL : DEFAULT_MODEL),
|
|
5734
6317
|
systemPrompt: systemPromptParts.join("\n\n") || void 0
|
|
5735
6318
|
};
|
|
5736
6319
|
}
|
|
5737
6320
|
async runCliBackend(config, safeProjectPath) {
|
|
5738
6321
|
const backendKind2 = this.resolveBackendKind(config);
|
|
5739
|
-
const defaultCwd = (0,
|
|
5740
|
-
if (!(0,
|
|
5741
|
-
(0,
|
|
6322
|
+
const defaultCwd = (0, import_path2.join)((0, import_os2.homedir)(), "Documents", "aiden");
|
|
6323
|
+
if (!(0, import_fs2.existsSync)(defaultCwd)) {
|
|
6324
|
+
(0, import_fs2.mkdirSync)(defaultCwd, { recursive: true });
|
|
5742
6325
|
}
|
|
5743
|
-
|
|
6326
|
+
let runtimeConfig = this.buildRuntimeConfig(config, safeProjectPath);
|
|
5744
6327
|
const cwd = runtimeConfig.worktreePath || safeProjectPath || runtimeConfig.cwd || defaultCwd;
|
|
5745
|
-
const promptText = buildPromptWithFiles(runtimeConfig.task, runtimeConfig.files);
|
|
5746
6328
|
const env = this.buildCliEnvironment();
|
|
6329
|
+
const promptText = this.buildPromptText(runtimeConfig);
|
|
5747
6330
|
const runtimeCommand = runtimeConfig.runtimeCommand || this.runtime.runtimeCommand;
|
|
5748
6331
|
const runtimeArgs = runtimeConfig.runtimeArgs || this.runtime.runtimeArgs || [];
|
|
5749
|
-
const backend = backendKind2 === "claude_cli" ? createClaudeCliBackend(runtimeCommand || "claude", runtimeArgs) : backendKind2 === "codex_app_server" ? createCodexRuntimeBackend(runtimeCommand || "codex", runtimeArgs) : backendKind2 === "gemini_cli" ? createGeminiCliBackend(runtimeCommand || "gemini", runtimeArgs) : backendKind2 === "opencode_cli" ? createOpencodeCliBackend(runtimeCommand || "opencode", runtimeArgs) : createGenericCliPassthroughBackend(runtimeCommand || "generic-cli", runtimeArgs);
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
6332
|
+
const backend = backendKind2 === "claude_cli" ? createClaudeCliBackend(runtimeCommand || "claude", runtimeArgs) : backendKind2 === "codex_app_server" ? createCodexRuntimeBackend(runtimeCommand || "codex", runtimeArgs) : backendKind2 === "cursor_agent_cli" ? createCursorAgentCliBackend(runtimeCommand || "cursor-agent", runtimeArgs) : backendKind2 === "gemini_cli" ? createGeminiCliBackend(runtimeCommand || "gemini", runtimeArgs) : backendKind2 === "droid_cli" ? createDroidCliBackend(runtimeCommand || "droid", runtimeArgs) : backendKind2 === "opencode_cli" ? createOpencodeCliBackend(runtimeCommand || "opencode", runtimeArgs) : createGenericCliPassthroughBackend(runtimeCommand || "generic-cli", runtimeArgs);
|
|
6333
|
+
let lastResult = null;
|
|
6334
|
+
for (let attempt = 0; attempt <= _BaseMachineAgent.MAX_RETRIES; attempt++) {
|
|
6335
|
+
if (this.abortController?.signal.aborted) break;
|
|
6336
|
+
const result = await backend.run({
|
|
6337
|
+
presenter: this.presenter,
|
|
6338
|
+
config: runtimeConfig,
|
|
6339
|
+
abortController: this.abortController,
|
|
6340
|
+
cwd,
|
|
6341
|
+
env,
|
|
6342
|
+
promptText,
|
|
6343
|
+
onProcessSpawned: this.getOnProcessSpawned()
|
|
6344
|
+
});
|
|
6345
|
+
lastResult = {
|
|
6346
|
+
...result,
|
|
6347
|
+
backendKind: backend.kind,
|
|
6348
|
+
supportTier: backend.supportTier,
|
|
6349
|
+
runtimeSessionId: result.runtimeSessionId || result.providerSessionId,
|
|
6350
|
+
providerSessionId: result.providerSessionId || result.runtimeSessionId
|
|
6351
|
+
};
|
|
6352
|
+
if (lastResult.success || !lastResult.error) break;
|
|
6353
|
+
const hadResumeId = runtimeConfig.runtimeSessionId || runtimeConfig.providerSessionId;
|
|
6354
|
+
if (hadResumeId && !isTransientError(lastResult.error)) {
|
|
6355
|
+
const overrideResult = await this.onSessionResumeFailure(runtimeConfig, lastResult);
|
|
6356
|
+
if (overrideResult !== null) {
|
|
6357
|
+
lastResult = overrideResult;
|
|
6358
|
+
break;
|
|
6359
|
+
}
|
|
6360
|
+
console.warn(
|
|
6361
|
+
"[base-machine-agent] Resume failed, retrying without session ID:",
|
|
6362
|
+
lastResult.error
|
|
6363
|
+
);
|
|
6364
|
+
runtimeConfig = { ...runtimeConfig, runtimeSessionId: void 0, providerSessionId: void 0 };
|
|
6365
|
+
const retryResult = await backend.run({
|
|
6366
|
+
presenter: this.presenter,
|
|
6367
|
+
config: runtimeConfig,
|
|
6368
|
+
abortController: this.abortController,
|
|
6369
|
+
cwd,
|
|
6370
|
+
env,
|
|
6371
|
+
promptText,
|
|
6372
|
+
onProcessSpawned: this.getOnProcessSpawned()
|
|
6373
|
+
});
|
|
6374
|
+
lastResult = {
|
|
6375
|
+
...retryResult,
|
|
6376
|
+
backendKind: backend.kind,
|
|
6377
|
+
supportTier: backend.supportTier,
|
|
6378
|
+
runtimeSessionId: retryResult.runtimeSessionId || retryResult.providerSessionId,
|
|
6379
|
+
providerSessionId: retryResult.providerSessionId || retryResult.runtimeSessionId
|
|
6380
|
+
};
|
|
6381
|
+
break;
|
|
6382
|
+
}
|
|
6383
|
+
if (!isTransientError(lastResult.error)) break;
|
|
6384
|
+
if (attempt < _BaseMachineAgent.MAX_RETRIES) {
|
|
6385
|
+
await this.beforeRetry();
|
|
6386
|
+
const delayMs = _BaseMachineAgent.RETRY_BASE_DELAY_MS * Math.pow(2, attempt);
|
|
6387
|
+
console.warn(
|
|
6388
|
+
`[base-machine-agent] Transient error (attempt ${attempt + 1}/${_BaseMachineAgent.MAX_RETRIES + 1}), retrying in ${delayMs}ms: ${lastResult.error}`
|
|
6389
|
+
);
|
|
6390
|
+
void this.presenter.onLog(`Transient error, retrying in ${Math.round(delayMs / 1e3)}s...`);
|
|
6391
|
+
await new Promise((resolve2) => setTimeout(resolve2, delayMs));
|
|
6392
|
+
}
|
|
6393
|
+
}
|
|
6394
|
+
const normalizedResult = lastResult;
|
|
6395
|
+
if (!this.shouldSkipOnComplete()) {
|
|
6396
|
+
this.presenter.onComplete(normalizedResult);
|
|
6397
|
+
}
|
|
5766
6398
|
return normalizedResult;
|
|
5767
6399
|
}
|
|
5768
6400
|
async run(config) {
|
|
@@ -5780,11 +6412,9 @@ var CoreAgent = class {
|
|
|
5780
6412
|
let safeProjectPath = initialConfig.projectPath;
|
|
5781
6413
|
if (initialConfig.projectPath) {
|
|
5782
6414
|
const canonicalized = canonicalizePath(initialConfig.projectPath);
|
|
5783
|
-
if (canonicalized)
|
|
5784
|
-
safeProjectPath = canonicalized;
|
|
5785
|
-
}
|
|
6415
|
+
if (canonicalized) safeProjectPath = canonicalized;
|
|
5786
6416
|
}
|
|
5787
|
-
if (safeProjectPath && !(0,
|
|
6417
|
+
if (safeProjectPath && !(0, import_fs2.existsSync)(safeProjectPath)) {
|
|
5788
6418
|
const message = `Project path no longer exists: "${safeProjectPath}". Please update the project's local path in project settings before running the agent.`;
|
|
5789
6419
|
this.presenter.onError(message, true);
|
|
5790
6420
|
const result = {
|
|
@@ -5806,7 +6436,7 @@ var CoreAgent = class {
|
|
|
5806
6436
|
const interrupted = this.isAbortError(error);
|
|
5807
6437
|
const message = interrupted ? "Interrupted by user" : error instanceof Error ? error.message : String(error);
|
|
5808
6438
|
if (!interrupted) {
|
|
5809
|
-
console.error("
|
|
6439
|
+
console.error("[base-machine-agent] CLI runtime failed", error);
|
|
5810
6440
|
this.presenter.onError(message, true);
|
|
5811
6441
|
}
|
|
5812
6442
|
const result = {
|
|
@@ -5825,6 +6455,79 @@ var CoreAgent = class {
|
|
|
5825
6455
|
}
|
|
5826
6456
|
};
|
|
5827
6457
|
|
|
6458
|
+
// src/core-agent.ts
|
|
6459
|
+
var CoreAgent = class extends BaseMachineAgent {
|
|
6460
|
+
childProcess = null;
|
|
6461
|
+
constructor(presenter, runtime) {
|
|
6462
|
+
super(presenter, runtime);
|
|
6463
|
+
}
|
|
6464
|
+
// ── Interactive tool response (WS relay → stdin) ───────────────────────
|
|
6465
|
+
/**
|
|
6466
|
+
* Send a tool_result to the CLI's stdin (stream-json format).
|
|
6467
|
+
* Used for interactive tools like AskUserQuestion.
|
|
6468
|
+
*/
|
|
6469
|
+
sendToolResponse(toolId, response) {
|
|
6470
|
+
if (!this.childProcess) return false;
|
|
6471
|
+
const stdin = this.childProcess.stdin;
|
|
6472
|
+
if (!stdin || stdin.destroyed) return false;
|
|
6473
|
+
try {
|
|
6474
|
+
const message = JSON.stringify({
|
|
6475
|
+
type: "user",
|
|
6476
|
+
message: {
|
|
6477
|
+
role: "user",
|
|
6478
|
+
content: [
|
|
6479
|
+
{
|
|
6480
|
+
type: "tool_result",
|
|
6481
|
+
tool_use_id: toolId,
|
|
6482
|
+
content: response
|
|
6483
|
+
}
|
|
6484
|
+
]
|
|
6485
|
+
},
|
|
6486
|
+
session_id: "default",
|
|
6487
|
+
parent_tool_use_id: null
|
|
6488
|
+
});
|
|
6489
|
+
stdin.write(message + "\n");
|
|
6490
|
+
console.info(`[CoreAgent] Sent tool_response for tool ${toolId}`);
|
|
6491
|
+
return true;
|
|
6492
|
+
} catch (err) {
|
|
6493
|
+
console.error("[CoreAgent] Failed to write tool_response to stdin", err);
|
|
6494
|
+
return false;
|
|
6495
|
+
}
|
|
6496
|
+
}
|
|
6497
|
+
// ── BaseMachineAgent hooks ─────────────────────────────────────────────
|
|
6498
|
+
getOnProcessSpawned() {
|
|
6499
|
+
return (child) => {
|
|
6500
|
+
this.childProcess = child;
|
|
6501
|
+
child.on("exit", () => {
|
|
6502
|
+
this.childProcess = null;
|
|
6503
|
+
});
|
|
6504
|
+
};
|
|
6505
|
+
}
|
|
6506
|
+
buildCliEnvironment() {
|
|
6507
|
+
const loginEnv = process.env;
|
|
6508
|
+
const env = {};
|
|
6509
|
+
for (const [key, value2] of Object.entries(loginEnv)) {
|
|
6510
|
+
if (value2 !== void 0) env[key] = value2;
|
|
6511
|
+
}
|
|
6512
|
+
env.PATH = `/home/user/.local/node/bin:${env.PATH ?? ""}`;
|
|
6513
|
+
return env;
|
|
6514
|
+
}
|
|
6515
|
+
buildExtraSystemPromptParts(_config) {
|
|
6516
|
+
const artifactIds = [];
|
|
6517
|
+
const envTaskId = process.env.AIDEN_TASK_ID;
|
|
6518
|
+
const envSessionId = process.env.AIDEN_SESSION_ID;
|
|
6519
|
+
if (envTaskId) artifactIds.push(`- taskId: "${envTaskId}"`);
|
|
6520
|
+
if (envSessionId) artifactIds.push(`- conversationId: "${envSessionId}"`);
|
|
6521
|
+
if (artifactIds.length === 0) return [];
|
|
6522
|
+
return [
|
|
6523
|
+
[
|
|
6524
|
+
"When creating artifacts using MCP tools (create_plan, create_document_artifact, create_test_report), always pass these IDs:",
|
|
6525
|
+
...artifactIds
|
|
6526
|
+
].join("\n")
|
|
6527
|
+
];
|
|
6528
|
+
}
|
|
6529
|
+
};
|
|
6530
|
+
|
|
5828
6531
|
// src/web-presenter.ts
|
|
5829
6532
|
var WebPresenter = class {
|
|
5830
6533
|
wsClient;
|
|
@@ -5852,8 +6555,14 @@ var WebPresenter = class {
|
|
|
5852
6555
|
onToolUse(tool, input, toolId) {
|
|
5853
6556
|
this.wsClient.emitEvent({ type: "tool_use", id: toolId, name: tool, input, ts: Date.now() });
|
|
5854
6557
|
}
|
|
5855
|
-
onToolResult(toolId, content) {
|
|
5856
|
-
this.wsClient.emitEvent({
|
|
6558
|
+
onToolResult(toolId, content, isError) {
|
|
6559
|
+
this.wsClient.emitEvent({
|
|
6560
|
+
type: "tool_result",
|
|
6561
|
+
id: toolId,
|
|
6562
|
+
content,
|
|
6563
|
+
ts: Date.now(),
|
|
6564
|
+
...isError ? { is_error: true } : {}
|
|
6565
|
+
});
|
|
5857
6566
|
}
|
|
5858
6567
|
onTurnComplete(content) {
|
|
5859
6568
|
this.wsClient.emitEvent({ type: "turn_complete", content, ts: Date.now() });
|
|
@@ -5861,6 +6570,26 @@ var WebPresenter = class {
|
|
|
5861
6570
|
onUsageUpdate(usage) {
|
|
5862
6571
|
this.wsClient.emitEvent({ type: "usage", usage });
|
|
5863
6572
|
}
|
|
6573
|
+
onAskUser(id, questions) {
|
|
6574
|
+
this.wsClient.emitEvent({ type: "ask_user", id, questions });
|
|
6575
|
+
}
|
|
6576
|
+
onTodoWrite(todos) {
|
|
6577
|
+
this.wsClient.emitEvent({ type: "todo_write", todos });
|
|
6578
|
+
}
|
|
6579
|
+
onExitPlanMode(id, input, planFilePath) {
|
|
6580
|
+
this.wsClient.emitEvent({ type: "exit_plan_mode", id, input, ...planFilePath ? { planFilePath } : {} });
|
|
6581
|
+
}
|
|
6582
|
+
onCheckpoint(id) {
|
|
6583
|
+
this.wsClient.emitEvent({ type: "checkpoint", id });
|
|
6584
|
+
}
|
|
6585
|
+
onActivity(status) {
|
|
6586
|
+
this.wsClient.emitEvent({ type: "activity", status, ts: Date.now() });
|
|
6587
|
+
}
|
|
6588
|
+
onOpenBrowserTab(browserId, url2) {
|
|
6589
|
+
this.wsClient.emitEvent({ type: "open_browser_tab", browserId, ...url2 ? { url: url2 } : {} });
|
|
6590
|
+
}
|
|
6591
|
+
recordRawTranscript() {
|
|
6592
|
+
}
|
|
5864
6593
|
onError(message, _isAgentError) {
|
|
5865
6594
|
this.wsClient.emitEvent({ type: "error", message });
|
|
5866
6595
|
if (!this.suppressSessionLifecycle) {
|
|
@@ -9462,10 +10191,6 @@ var WSClient = class {
|
|
|
9462
10191
|
emitEvent(event) {
|
|
9463
10192
|
this.socket.emit("agent_event", event);
|
|
9464
10193
|
}
|
|
9465
|
-
/** Signal the WS relay that this agent is about to update (not crashing). */
|
|
9466
|
-
emitUpdating() {
|
|
9467
|
-
this.socket.emit("agent_updating");
|
|
9468
|
-
}
|
|
9469
10194
|
waitForConnection(timeoutMs = 15e3) {
|
|
9470
10195
|
return new Promise((resolve2, reject) => {
|
|
9471
10196
|
if (this.socket.connected) {
|
|
@@ -9501,15 +10226,24 @@ var WSClient = class {
|
|
|
9501
10226
|
this.socket.on("connect_error", (error) => {
|
|
9502
10227
|
console.warn(`[WSClient] Connection error: ${error.message}`);
|
|
9503
10228
|
});
|
|
9504
|
-
this.socket.on("user_message", (data) => {
|
|
9505
|
-
|
|
10229
|
+
this.socket.on("user_message", (data, ack) => {
|
|
10230
|
+
ack?.({ ok: true });
|
|
10231
|
+
callbacks.onUserMessage({
|
|
10232
|
+
text: data.text,
|
|
10233
|
+
images: data.images,
|
|
10234
|
+
providerSessionId: data.providerSessionId,
|
|
10235
|
+
backendKind: data.backendKind,
|
|
10236
|
+
selectedModel: data.selectedModel,
|
|
10237
|
+
mode: data.mode
|
|
10238
|
+
});
|
|
9506
10239
|
});
|
|
9507
10240
|
this.socket.on("stop", () => {
|
|
9508
10241
|
callbacks.onStop();
|
|
9509
10242
|
});
|
|
9510
|
-
this.socket.on("
|
|
9511
|
-
|
|
9512
|
-
|
|
10243
|
+
this.socket.on("tool_response", (data) => {
|
|
10244
|
+
if (data?.toolId && typeof data.response === "string") {
|
|
10245
|
+
callbacks.onToolResponse?.(data.toolId, data.response);
|
|
10246
|
+
}
|
|
9513
10247
|
});
|
|
9514
10248
|
this.socket.on("error", (data) => {
|
|
9515
10249
|
console.error(`[WSClient] Server error: ${data.message} (${data.code})`);
|
|
@@ -9518,52 +10252,15 @@ var WSClient = class {
|
|
|
9518
10252
|
};
|
|
9519
10253
|
|
|
9520
10254
|
// src/version.ts
|
|
9521
|
-
var AGENT_VERSION = "0.1.
|
|
9522
|
-
|
|
9523
|
-
// src/updater.ts
|
|
9524
|
-
var import_node_child_process2 = require("child_process");
|
|
9525
|
-
var DEFAULT_PATH = "/home/user/.local/node/bin:/home/user/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
9526
|
-
async function installUpdate(payload) {
|
|
9527
|
-
const version = payload.version ?? "latest";
|
|
9528
|
-
try {
|
|
9529
|
-
console.info(`[updater] Installing @aiden-ade/sandbox-agent@${version} via npm`);
|
|
9530
|
-
(0, import_node_child_process2.execSync)(`npm install -g @aiden-ade/sandbox-agent@${version}`, {
|
|
9531
|
-
timeout: 6e4,
|
|
9532
|
-
env: { ...process.env, PATH: DEFAULT_PATH },
|
|
9533
|
-
stdio: "pipe"
|
|
9534
|
-
});
|
|
9535
|
-
console.info("[updater] npm install complete");
|
|
9536
|
-
return true;
|
|
9537
|
-
} catch (error) {
|
|
9538
|
-
console.error("[updater] npm install failed:", error.message);
|
|
9539
|
-
return false;
|
|
9540
|
-
}
|
|
9541
|
-
}
|
|
9542
|
-
function respawnAgent(lastProviderSessionId) {
|
|
9543
|
-
const env = { ...process.env };
|
|
9544
|
-
if (lastProviderSessionId) {
|
|
9545
|
-
env.AIDEN_PROVIDER_SESSION_ID = lastProviderSessionId;
|
|
9546
|
-
}
|
|
9547
|
-
console.info("[updater] Spawning new agent process");
|
|
9548
|
-
const child = (0, import_node_child_process2.spawn)("aiden-agent", [], {
|
|
9549
|
-
detached: true,
|
|
9550
|
-
stdio: "ignore",
|
|
9551
|
-
env: { ...env, PATH: DEFAULT_PATH }
|
|
9552
|
-
});
|
|
9553
|
-
child.unref();
|
|
9554
|
-
setTimeout(() => {
|
|
9555
|
-
console.info("[updater] Old agent exiting after respawn");
|
|
9556
|
-
process.exit(0);
|
|
9557
|
-
}, 500);
|
|
9558
|
-
}
|
|
10255
|
+
var AGENT_VERSION = "0.1.5";
|
|
9559
10256
|
|
|
9560
10257
|
// src/sandbox.ts
|
|
9561
10258
|
async function runSandbox(config) {
|
|
9562
10259
|
let currentTask = config.task;
|
|
10260
|
+
let currentImages = [];
|
|
9563
10261
|
let stopped = false;
|
|
9564
10262
|
let pendingMessageResolve = null;
|
|
9565
10263
|
let currentAgent = null;
|
|
9566
|
-
let pendingUpdate = null;
|
|
9567
10264
|
console.info("[sandbox] Connecting to WS server", { wsUrl: config.wsUrl, sessionId: config.sessionId });
|
|
9568
10265
|
const wsClient = new WSClient(
|
|
9569
10266
|
config.wsUrl,
|
|
@@ -9584,13 +10281,14 @@ async function runSandbox(config) {
|
|
|
9584
10281
|
console.info("[sandbox] Stop signal received");
|
|
9585
10282
|
if (currentAgent) currentAgent.abort();
|
|
9586
10283
|
},
|
|
9587
|
-
|
|
9588
|
-
|
|
9589
|
-
|
|
9590
|
-
|
|
9591
|
-
|
|
9592
|
-
|
|
9593
|
-
|
|
10284
|
+
onToolResponse: (toolId, response) => {
|
|
10285
|
+
if (currentAgent) {
|
|
10286
|
+
const sent = currentAgent.sendToolResponse(toolId, response);
|
|
10287
|
+
if (!sent) {
|
|
10288
|
+
console.warn("[sandbox] Failed to deliver tool_response to agent stdin", { toolId });
|
|
10289
|
+
}
|
|
10290
|
+
} else {
|
|
10291
|
+
console.warn("[sandbox] No active agent for tool_response", { toolId });
|
|
9594
10292
|
}
|
|
9595
10293
|
},
|
|
9596
10294
|
onConnect: () => console.info("[sandbox] WS connected"),
|
|
@@ -9609,11 +10307,8 @@ async function runSandbox(config) {
|
|
|
9609
10307
|
let lastProviderSessionId = config.providerSessionId;
|
|
9610
10308
|
let activeBackendKind = config.backendKind || "claude_cli";
|
|
9611
10309
|
let activeModel = config.model;
|
|
10310
|
+
let activeMode = config.mode || "agent";
|
|
9612
10311
|
while (!stopped) {
|
|
9613
|
-
if (pendingUpdate) {
|
|
9614
|
-
await performUpdate(wsClient, pendingUpdate, lastProviderSessionId);
|
|
9615
|
-
pendingUpdate = null;
|
|
9616
|
-
}
|
|
9617
10312
|
if (currentTask?.trim()) {
|
|
9618
10313
|
const agent = new CoreAgent(presenter, {
|
|
9619
10314
|
backendKind: activeBackendKind
|
|
@@ -9627,8 +10322,17 @@ async function runSandbox(config) {
|
|
|
9627
10322
|
projectPath: config.projectPath,
|
|
9628
10323
|
cwd: config.projectPath,
|
|
9629
10324
|
backendKind: activeBackendKind,
|
|
10325
|
+
mode: activeMode,
|
|
9630
10326
|
selectedModel: activeModel,
|
|
9631
|
-
providerSessionId: lastProviderSessionId ?? void 0
|
|
10327
|
+
providerSessionId: lastProviderSessionId ?? void 0,
|
|
10328
|
+
images: currentImages.length > 0 ? currentImages.map((img) => ({
|
|
10329
|
+
id: img.id,
|
|
10330
|
+
data: img.data,
|
|
10331
|
+
mimeType: img.mimeType,
|
|
10332
|
+
filename: img.filename,
|
|
10333
|
+
width: 0,
|
|
10334
|
+
height: 0
|
|
10335
|
+
})) : void 0
|
|
9632
10336
|
});
|
|
9633
10337
|
} catch (error) {
|
|
9634
10338
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -9640,22 +10344,15 @@ async function runSandbox(config) {
|
|
|
9640
10344
|
lastProviderSessionId = result.providerSessionId;
|
|
9641
10345
|
}
|
|
9642
10346
|
if (stopped) break;
|
|
9643
|
-
if (pendingUpdate) {
|
|
9644
|
-
await performUpdate(wsClient, pendingUpdate, lastProviderSessionId);
|
|
9645
|
-
pendingUpdate = null;
|
|
9646
|
-
}
|
|
9647
10347
|
}
|
|
9648
10348
|
const nextPayload = await new Promise((resolve2) => {
|
|
9649
10349
|
pendingMessageResolve = resolve2;
|
|
9650
10350
|
});
|
|
9651
10351
|
if (nextPayload === null || stopped) {
|
|
9652
|
-
if (pendingUpdate) {
|
|
9653
|
-
await performUpdate(wsClient, pendingUpdate, lastProviderSessionId);
|
|
9654
|
-
pendingUpdate = null;
|
|
9655
|
-
}
|
|
9656
10352
|
break;
|
|
9657
10353
|
}
|
|
9658
10354
|
currentTask = nextPayload.text;
|
|
10355
|
+
currentImages = nextPayload.images ?? [];
|
|
9659
10356
|
if (nextPayload.providerSessionId) {
|
|
9660
10357
|
lastProviderSessionId = nextPayload.providerSessionId;
|
|
9661
10358
|
}
|
|
@@ -9665,25 +10362,20 @@ async function runSandbox(config) {
|
|
|
9665
10362
|
if (nextPayload.selectedModel) {
|
|
9666
10363
|
activeModel = nextPayload.selectedModel;
|
|
9667
10364
|
}
|
|
10365
|
+
if (nextPayload.mode) {
|
|
10366
|
+
activeMode = nextPayload.mode;
|
|
10367
|
+
}
|
|
9668
10368
|
}
|
|
9669
10369
|
presenter.setSuppressSessionLifecycle(false);
|
|
9670
10370
|
wsClient.close();
|
|
9671
10371
|
}
|
|
9672
|
-
async function performUpdate(wsClient, payload, lastProviderSessionId) {
|
|
9673
|
-
console.info("[sandbox] Performing self-update", payload);
|
|
9674
|
-
wsClient.emitUpdating();
|
|
9675
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
9676
|
-
wsClient.close();
|
|
9677
|
-
const success = await installUpdate(payload);
|
|
9678
|
-
if (success) {
|
|
9679
|
-
respawnAgent(lastProviderSessionId);
|
|
9680
|
-
await new Promise((r) => setTimeout(r, 2e3));
|
|
9681
|
-
} else {
|
|
9682
|
-
console.error("[sandbox] Update failed \u2014 continuing with current version");
|
|
9683
|
-
}
|
|
9684
|
-
}
|
|
9685
10372
|
|
|
9686
10373
|
// src/index.ts
|
|
10374
|
+
if (process.argv.includes("--version")) {
|
|
10375
|
+
process.stdout.write(`${AGENT_VERSION}
|
|
10376
|
+
`);
|
|
10377
|
+
process.exit(0);
|
|
10378
|
+
}
|
|
9687
10379
|
var wsUrl = process.env.AIDEN_WS_URL;
|
|
9688
10380
|
var sessionId = process.env.AIDEN_SESSION_ID;
|
|
9689
10381
|
var taskId = process.env.AIDEN_TASK_ID;
|
|
@@ -9691,13 +10383,14 @@ var sessionToken = process.env.AIDEN_SESSION_TOKEN;
|
|
|
9691
10383
|
var task = process.env.AIDEN_TASK;
|
|
9692
10384
|
var projectPath = process.env.AIDEN_PROJECT_PATH || "/home/user/workspace";
|
|
9693
10385
|
var backendKind = process.env.AIDEN_BACKEND_KIND || "claude_cli";
|
|
10386
|
+
var mode = process.env.AIDEN_MODE || "agent";
|
|
9694
10387
|
var model = process.env.AIDEN_MODEL;
|
|
9695
10388
|
var providerSessionId = process.env.AIDEN_PROVIDER_SESSION_ID || void 0;
|
|
9696
10389
|
if (!wsUrl || !sessionId || !taskId || !sessionToken || task == null) {
|
|
9697
10390
|
console.error("[aiden-agent] Missing required env vars: AIDEN_WS_URL, AIDEN_SESSION_ID, AIDEN_TASK_ID, AIDEN_SESSION_TOKEN, AIDEN_TASK");
|
|
9698
10391
|
process.exit(1);
|
|
9699
10392
|
}
|
|
9700
|
-
console.info("[aiden-agent] Starting", { sessionId, taskId, projectPath, backendKind, model, version: AGENT_VERSION });
|
|
10393
|
+
console.info("[aiden-agent] Starting", { sessionId, taskId, projectPath, backendKind, mode, model, version: AGENT_VERSION });
|
|
9701
10394
|
runSandbox({
|
|
9702
10395
|
wsUrl,
|
|
9703
10396
|
sessionId,
|
|
@@ -9706,6 +10399,7 @@ runSandbox({
|
|
|
9706
10399
|
task,
|
|
9707
10400
|
projectPath,
|
|
9708
10401
|
backendKind,
|
|
10402
|
+
mode,
|
|
9709
10403
|
model,
|
|
9710
10404
|
providerSessionId,
|
|
9711
10405
|
maxIterations: 50
|