@aiden-ade/sandbox-agent 0.1.2 → 0.1.4

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.
Files changed (2) hide show
  1. package/dist/index.cjs +1074 -406
  2. package/package.json +5 -3
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 spawn3 = require("child_process").spawn;
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 = spawn3(process.argv[0], ["-e", execString]);
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 flagForceColor;
924
+ var forceColor;
925
925
  if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
926
- flagForceColor = 0;
926
+ forceColor = 0;
927
927
  } else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
928
- flagForceColor = 1;
928
+ forceColor = 1;
929
929
  }
930
- function envForceColor() {
931
- if ("FORCE_COLOR" in env) {
932
- if (env.FORCE_COLOR === "true") {
933
- return 1;
934
- }
935
- if (env.FORCE_COLOR === "false") {
936
- return 0;
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, { streamIsTTY, sniffFlags = true } = {}) {
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 (sniffFlags) {
962
- if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
963
- return 3;
964
- }
965
- if (hasFlag("color=256")) {
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", "DRONE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
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 = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
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, options = {}) {
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: getSupportLevel({ isTTY: tty.isatty(1) }),
1025
- stderr: getSupportLevel({ isTTY: tty.isatty(2) })
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
- // src/core-agent.ts
4826
- var import_node_fs = require("fs");
4827
- var import_node_os = require("os");
4828
- var import_node_path = require("path");
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
- // src/runtime-backends.ts
4831
- var import_node_child_process = require("child_process");
4832
- var import_node_readline = require("readline");
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,32 @@ 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("overloaded") || lower.includes("503") || lower.includes("service unavailable"))
4902
+ return "API is overloaded. Will retry automatically.";
4903
+ if (lower.includes("context_length") || lower.includes("too long") || lower.includes("max tokens") || lower.includes("context window"))
4904
+ return "Context window exceeded. Try a shorter prompt or start a new session.";
4905
+ if (lower.includes("econnrefused") || lower.includes("network") || lower.includes("enotfound") || lower.includes("timeout") || lower.includes("etimedout"))
4906
+ return "Network connection failed. Check your internet connection.";
4907
+ if (lower.includes("permission denied") || lower.includes("eacces"))
4908
+ return "Permission denied. Check file or command permissions.";
4909
+ if (lower.includes("out of memory") || lower.includes("enomem"))
4910
+ return "Out of memory. Close other applications and try again.";
4911
+ return stderr.trim() || `Process exited with code ${exitCode}`;
4912
+ }
4913
+ function isTransientError(errorMessage) {
4914
+ const lower = errorMessage.toLowerCase();
4915
+ 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");
4916
+ }
4841
4917
  function buildPromptWithSystem(config, promptText) {
4842
4918
  const parts2 = [
4843
4919
  config.systemPrompt?.trim(),
@@ -4856,13 +4932,16 @@ function buildPlanModePrefix(promptText) {
4856
4932
  ].join("\n");
4857
4933
  }
4858
4934
  function spawnCli(command, args, context) {
4859
- const spawnWithShell = process.platform === "win32";
4860
- return (0, import_node_child_process.spawn)(command, args, {
4935
+ const isWindows = process.platform === "win32";
4936
+ return (0, import_child_process.spawn)(command, args, {
4861
4937
  cwd: context.cwd,
4862
4938
  env: context.env,
4863
4939
  stdio: ["pipe", "pipe", "pipe"],
4864
- shell: spawnWithShell,
4865
- signal: context.abortController.signal
4940
+ shell: isWindows,
4941
+ // Detached on Unix creates a new process group so we can kill the entire tree
4942
+ // (CLI tools spawn child processes for tool execution that would otherwise be orphaned).
4943
+ // On Windows, detached opens a new console — shell mode handles grouping instead.
4944
+ detached: !isWindows
4866
4945
  });
4867
4946
  }
4868
4947
  function createGenericCliBackend(options) {
@@ -4874,30 +4953,39 @@ function createGenericCliBackend(options) {
4874
4953
  const state = {
4875
4954
  process: null,
4876
4955
  iterations: 0,
4877
- summary: ""
4956
+ summary: "",
4957
+ allowContinuation: options.allowContinuation ?? false
4878
4958
  };
4879
4959
  const prompt = options.augmentPrompt?.(context) ?? (context.config.mode === "plan" ? buildPlanModePrefix(buildPromptWithSystem(context.config, context.promptText)) : buildPromptWithSystem(context.config, context.promptText));
4880
4960
  const args = options.buildArgs?.(context, prompt) ?? options.args.map((arg) => arg === "{{prompt}}" ? prompt : arg);
4881
4961
  const child = spawnCli(options.command, args, context);
4882
4962
  state.process = child;
4963
+ context.onProcessSpawned?.(child);
4883
4964
  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
- )
4965
+ (a, i) => i > 0 && args[i - 1] === "--system-prompt" ? `"<system-prompt ${a.length} chars>"` : a
4888
4966
  );
4889
4967
  console.info(`[${options.kind}] Spawning: ${options.command} ${safeArgs.join(" ")}`);
4890
4968
  presenter.recordRawTranscript?.("system", `${options.command} ${args.join(" ")}`, {
4891
4969
  backendKind: options.kind
4892
4970
  });
4893
- if (options.promptViaStdin) {
4971
+ child.stdin.on("error", (err) => {
4972
+ if (err.code !== "EPIPE") {
4973
+ console.error(`[${options.kind}] stdin error:`, err);
4974
+ }
4975
+ });
4976
+ if (options.stdinWriter) {
4977
+ options.stdinWriter(child.stdin, context);
4978
+ } else if (options.promptViaStdin) {
4894
4979
  child.stdin.write(prompt);
4895
4980
  child.stdin.end();
4896
4981
  } else {
4897
4982
  child.stdin.end();
4898
4983
  }
4899
- const stdoutRl = (0, import_node_readline.createInterface)({ input: child.stdout });
4900
- const stderrRl = (0, import_node_readline.createInterface)({ input: child.stderr });
4984
+ if (options.keepStdinOpen) {
4985
+ state.stdin = child.stdin;
4986
+ }
4987
+ const stdoutRl = (0, import_readline.createInterface)({ input: child.stdout });
4988
+ const stderrRl = (0, import_readline.createInterface)({ input: child.stderr });
4901
4989
  const stderrLines = [];
4902
4990
  stdoutRl.on("line", (line) => {
4903
4991
  presenter.recordRawTranscript?.("stdout", line);
@@ -4919,12 +5007,108 @@ function createGenericCliBackend(options) {
4919
5007
  void presenter.onLog(`[${options.kind}] ${line}`);
4920
5008
  }
4921
5009
  });
4922
- const exitCode = await new Promise((resolve2, reject) => {
4923
- child.on("error", (error) => {
4924
- reject(formatCliSpawnError(options.command, error));
5010
+ let exitCode;
5011
+ try {
5012
+ const exitPromise = new Promise((resolve2, reject) => {
5013
+ child.on("error", (error) => {
5014
+ reject(formatCliSpawnError(options.command, error));
5015
+ });
5016
+ child.on("exit", (code) => resolve2(code ?? 0));
5017
+ const onAbort = () => {
5018
+ const pid = child.pid;
5019
+ if (!pid) return;
5020
+ try {
5021
+ process.kill(-pid, "SIGTERM");
5022
+ } catch {
5023
+ try {
5024
+ child.kill("SIGTERM");
5025
+ } catch {
5026
+ }
5027
+ }
5028
+ };
5029
+ context.abortController.signal.addEventListener("abort", onAbort, { once: true });
4925
5030
  });
4926
- child.on("exit", (code) => resolve2(code ?? 0));
4927
- });
5031
+ if (options.keepStdinOpen) {
5032
+ if (options.allowContinuation) {
5033
+ const IDLE_MS = options.continuationIdleMs ?? 10 * 60 * 1e3;
5034
+ let idleTimer = null;
5035
+ let heartbeatInterval = null;
5036
+ const resetIdleTimer = () => {
5037
+ if (idleTimer) clearTimeout(idleTimer);
5038
+ idleTimer = setTimeout(() => {
5039
+ if (state.stdin && !state.stdin.destroyed) {
5040
+ state.stdin.end();
5041
+ console.info(`[${options.kind}] Continuation idle timeout (${IDLE_MS}ms) \u2014 closing stdin`);
5042
+ }
5043
+ }, IDLE_MS);
5044
+ };
5045
+ state.onNewOutput = resetIdleTimer;
5046
+ state.stopHeartbeat = () => {
5047
+ if (heartbeatInterval) {
5048
+ clearInterval(heartbeatInterval);
5049
+ heartbeatInterval = null;
5050
+ }
5051
+ };
5052
+ state.startHeartbeat = () => {
5053
+ state.stopHeartbeat?.();
5054
+ heartbeatInterval = setInterval(() => {
5055
+ void presenter.onActivity?.("background_work");
5056
+ }, 3e4);
5057
+ };
5058
+ resetIdleTimer();
5059
+ exitCode = await exitPromise;
5060
+ if (idleTimer) clearTimeout(idleTimer);
5061
+ state.stopHeartbeat?.();
5062
+ } else {
5063
+ const resultPromise = new Promise((resolve2) => {
5064
+ state.onResultReceived = () => resolve2("result_received");
5065
+ if (state.resultReceived) resolve2("result_received");
5066
+ });
5067
+ const raceResult = await Promise.race([exitPromise, resultPromise]);
5068
+ if (raceResult === "result_received") {
5069
+ const GRACE_MS = 5e3;
5070
+ const gracedExit = await Promise.race([
5071
+ exitPromise,
5072
+ new Promise((resolve2) => setTimeout(() => resolve2(0), GRACE_MS))
5073
+ ]);
5074
+ exitCode = typeof gracedExit === "number" ? gracedExit : 0;
5075
+ if (child.exitCode === null && !child.killed) {
5076
+ console.warn(`[${options.kind}] Process still alive ${GRACE_MS}ms after result event, killing`);
5077
+ try {
5078
+ if (process.platform === "win32") {
5079
+ child.kill("SIGKILL");
5080
+ } else if (child.pid) {
5081
+ process.kill(-child.pid, "SIGKILL");
5082
+ }
5083
+ } catch {
5084
+ }
5085
+ }
5086
+ } else {
5087
+ exitCode = raceResult;
5088
+ }
5089
+ }
5090
+ } else {
5091
+ exitCode = await exitPromise;
5092
+ }
5093
+ } finally {
5094
+ stdoutRl.close();
5095
+ stderrRl.close();
5096
+ }
5097
+ if (context.abortController.signal.aborted) {
5098
+ return {
5099
+ success: false,
5100
+ summary: "Interrupted by user",
5101
+ filesModified: [],
5102
+ planFilesCreated: [],
5103
+ iterations: Math.max(state.iterations, 1),
5104
+ error: "Interrupted by user",
5105
+ providerSessionId: state.runtimeSessionId,
5106
+ runtimeSessionId: state.runtimeSessionId,
5107
+ backendKind: options.kind,
5108
+ supportTier: options.supportTier,
5109
+ usage: state.usage
5110
+ };
5111
+ }
4928
5112
  if (exitCode !== 0) {
4929
5113
  console.error(`[${options.kind}] Process exited with code ${exitCode}`, {
4930
5114
  stderrLines: stderrLines.slice(-10),
@@ -4937,14 +5121,17 @@ function createGenericCliBackend(options) {
4937
5121
  if (hasRenderableTurn) {
4938
5122
  await presenter.onTurnComplete([]);
4939
5123
  }
4940
- const summary = state.summary.trim() || state.error?.trim() || (exitCode === 0 ? "Task completed" : "Task failed");
5124
+ const stderrText = stderrLines.join("\n").trim();
5125
+ const hasStructuredError = exitCode === 0 && !!state.error?.trim();
5126
+ const failed = exitCode !== 0 || hasStructuredError;
5127
+ const summary = state.summary.trim() || state.error?.trim() || (failed ? "Task failed" : "Task completed");
4941
5128
  return {
4942
- success: exitCode === 0,
5129
+ success: !failed,
4943
5130
  summary,
4944
5131
  filesModified: [],
4945
5132
  planFilesCreated: [],
4946
5133
  iterations: Math.max(state.iterations, 1),
4947
- error: exitCode === 0 ? void 0 : state.error?.trim() || (stderrLines.length > 0 ? stderrLines.join("\n").trim() : void 0) || `${options.command} exited with code ${exitCode}`,
5134
+ error: failed ? classifyCliError(state.error?.trim() || stderrText || "", exitCode) : void 0,
4948
5135
  providerSessionId: state.runtimeSessionId,
4949
5136
  runtimeSessionId: state.runtimeSessionId,
4950
5137
  backendKind: options.kind,
@@ -4955,12 +5142,8 @@ function createGenericCliBackend(options) {
4955
5142
  };
4956
5143
  }
4957
5144
  function extractToolResultContent(content) {
4958
- if (typeof content === "string") {
4959
- return content;
4960
- }
4961
- if (!Array.isArray(content)) {
4962
- return "";
4963
- }
5145
+ if (typeof content === "string") return content;
5146
+ if (!Array.isArray(content)) return "";
4964
5147
  return content.map((block) => {
4965
5148
  if (typeof block === "object" && block !== null && "type" in block && block.type === "text" && "text" in block && typeof block.text === "string") {
4966
5149
  return block.text;
@@ -4974,10 +5157,7 @@ function parseClaudeStructuredLine(line, context, state) {
4974
5157
  try {
4975
5158
  parsed = JSON.parse(line);
4976
5159
  } catch {
4977
- state.summary += `${line}
4978
- `;
4979
- void presenter.onAssistantText(`${line}
4980
- `);
5160
+ void presenter.onLog(`[claude_cli] ${line}`);
4981
5161
  return;
4982
5162
  }
4983
5163
  const type = typeof parsed.type === "string" ? parsed.type : "";
@@ -4994,13 +5174,22 @@ function parseClaudeStructuredLine(line, context, state) {
4994
5174
  break;
4995
5175
  }
4996
5176
  case "assistant": {
5177
+ if (presenter.pendingAskUserToolIds?.size) {
5178
+ console.info(`[claude_cli] Suppressing fallback assistant turn \u2014 ${presenter.pendingAskUserToolIds.size} AskUserQuestion(s) still pending`);
5179
+ break;
5180
+ }
5181
+ if (state.resultReceived && state.allowContinuation) {
5182
+ console.info("[claude_cli] Continuation turn detected \u2014 restarting presenter");
5183
+ state.resultReceived = false;
5184
+ state.stopHeartbeat?.();
5185
+ void presenter.onStart(context.config);
5186
+ }
5187
+ state.onNewOutput?.();
4997
5188
  state.iterations += 1;
4998
5189
  const message = typeof parsed.message === "object" && parsed.message !== null ? parsed.message : null;
4999
5190
  const content = Array.isArray(message?.content) ? message.content : [];
5000
5191
  for (const block of content) {
5001
- if (typeof block !== "object" || block === null || !("type" in block)) {
5002
- continue;
5003
- }
5192
+ if (typeof block !== "object" || block === null || !("type" in block)) continue;
5004
5193
  if (block.type === "text" && typeof block.text === "string") {
5005
5194
  const text = block.text;
5006
5195
  state.summary += `${text}
@@ -5015,6 +5204,25 @@ function parseClaudeStructuredLine(line, context, state) {
5015
5204
  if (block.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") {
5016
5205
  const toolBlock = block;
5017
5206
  void presenter.onToolUse(toolBlock.name, toolBlock.input ?? {}, toolBlock.id);
5207
+ const lowerName = toolBlock.name.toLowerCase();
5208
+ if (INTERACTIVE_TOOL_NAMES.has(lowerName)) {
5209
+ if (!state.suppressedToolResultIds) state.suppressedToolResultIds = /* @__PURE__ */ new Set();
5210
+ state.suppressedToolResultIds.add(toolBlock.id);
5211
+ }
5212
+ const toolInput = toolBlock.input ?? {};
5213
+ if (lowerName === "askuserquestion") {
5214
+ if (!presenter.pendingAskUserToolIds) presenter.pendingAskUserToolIds = /* @__PURE__ */ new Set();
5215
+ presenter.pendingAskUserToolIds.add(toolBlock.id);
5216
+ console.info(`[claude_cli] AskUserQuestion tool_use detected \u2014 id=${toolBlock.id}, pendingSize=${presenter.pendingAskUserToolIds.size}, presenterHasProp=${Object.prototype.hasOwnProperty.call(presenter, "pendingAskUserToolIds")}`);
5217
+ const questions = Array.isArray(toolInput.questions) ? toolInput.questions : [];
5218
+ void presenter.onAskUser?.(toolBlock.id, questions);
5219
+ console.info(`[claude_cli] After onAskUser \u2014 pendingSize=${presenter.pendingAskUserToolIds?.size}`);
5220
+ } else if (lowerName === "exitplanmode") {
5221
+ void presenter.onExitPlanMode?.(toolBlock.id, toolInput);
5222
+ } else if (lowerName === "todowrite") {
5223
+ const todos = Array.isArray(toolInput.todos) ? toolInput.todos : [];
5224
+ void presenter.onTodoWrite?.(todos);
5225
+ }
5018
5226
  }
5019
5227
  }
5020
5228
  break;
@@ -5024,19 +5232,26 @@ function parseClaudeStructuredLine(line, context, state) {
5024
5232
  const content = Array.isArray(message?.content) ? message.content : [];
5025
5233
  for (const block of content) {
5026
5234
  if (typeof block === "object" && block !== null && "type" in block && block.type === "tool_result" && "tool_use_id" in block && typeof block.tool_use_id === "string") {
5235
+ if (state.suppressedToolResultIds?.has(block.tool_use_id)) {
5236
+ console.info(`[claude_cli] Suppressed auto-generated tool_result for ${block.tool_use_id}`);
5237
+ continue;
5238
+ }
5239
+ const isError = "is_error" in block && block.is_error === true ? true : void 0;
5027
5240
  void presenter.onToolResult?.(
5028
5241
  block.tool_use_id,
5029
- extractToolResultContent(block.content)
5242
+ extractToolResultContent(block.content),
5243
+ isError
5030
5244
  );
5031
5245
  }
5032
5246
  }
5033
5247
  break;
5034
5248
  }
5035
5249
  case "result": {
5036
- const inputTokens = typeof parsed.usage === "object" && parsed.usage !== null && typeof parsed.usage.input_tokens === "number" ? parsed.usage.input_tokens : 0;
5037
- const outputTokens = typeof parsed.usage === "object" && parsed.usage !== null && typeof parsed.usage.output_tokens === "number" ? parsed.usage.output_tokens : 0;
5038
- const cacheReadTokens = typeof parsed.usage === "object" && parsed.usage !== null && typeof parsed.usage.cache_read_input_tokens === "number" ? parsed.usage.cache_read_input_tokens : 0;
5039
- const cacheCreationTokens = typeof parsed.usage === "object" && parsed.usage !== null && typeof parsed.usage.cache_creation_input_tokens === "number" ? parsed.usage.cache_creation_input_tokens : 0;
5250
+ const usage = parsed.usage;
5251
+ const inputTokens = typeof usage?.input_tokens === "number" ? usage.input_tokens : 0;
5252
+ const outputTokens = typeof usage?.output_tokens === "number" ? usage.output_tokens : 0;
5253
+ const cacheReadTokens = typeof usage?.cache_read_input_tokens === "number" ? usage.cache_read_input_tokens : 0;
5254
+ const cacheCreationTokens = typeof usage?.cache_creation_input_tokens === "number" ? usage.cache_creation_input_tokens : 0;
5040
5255
  state.usage = {
5041
5256
  model: typeof parsed.model === "string" ? parsed.model : typeof context.config.selectedModel === "string" ? context.config.selectedModel : "claude",
5042
5257
  numTurns: typeof parsed.num_turns === "number" ? parsed.num_turns : Math.max(state.iterations, 1),
@@ -5065,17 +5280,46 @@ function parseClaudeStructuredLine(line, context, state) {
5065
5280
  const errObj = typeof parsed.error === "object" && parsed.error !== null ? parsed.error : null;
5066
5281
  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
5282
  state.error = errMsg;
5068
- const errFields = {
5283
+ console.error("[claude_cli] result subtype=", subtype, "error fields:", JSON.stringify({
5069
5284
  error: parsed.error,
5070
5285
  message: parsed.message,
5071
5286
  result: typeof parsed.result === "string" ? parsed.result.slice(0, 200) : parsed.result,
5072
5287
  session_id: parsed.session_id,
5073
5288
  model: parsed.model,
5074
- num_turns: parsed.num_turns,
5075
- is_error: parsed.is_error
5289
+ num_turns: parsed.num_turns
5290
+ }));
5291
+ }
5292
+ console.info(`[claude_cli] result event \u2014 pendingAskUserToolIds size=${presenter.pendingAskUserToolIds?.size ?? "undefined"}, hasOwnProp=${Object.prototype.hasOwnProperty.call(presenter, "pendingAskUserToolIds")}`);
5293
+ if (presenter.pendingAskUserToolIds?.size) {
5294
+ console.info(`[claude_cli] Keeping stdin open \u2014 ${presenter.pendingAskUserToolIds.size} AskUserQuestion(s) awaiting user response`);
5295
+ state.onNewOutput?.();
5296
+ break;
5297
+ }
5298
+ state.resultReceived = true;
5299
+ if (state.allowContinuation) {
5300
+ const continuationResult = {
5301
+ success: !state.error,
5302
+ summary: state.summary || "",
5303
+ filesModified: [],
5304
+ planFilesCreated: [],
5305
+ iterations: Math.max(state.iterations, 1),
5306
+ error: state.error,
5307
+ providerSessionId: state.runtimeSessionId,
5308
+ runtimeSessionId: state.runtimeSessionId,
5309
+ backendKind: context.config.backendKind,
5310
+ supportTier: context.config.supportTier,
5311
+ usage: state.usage
5076
5312
  };
5077
- console.error("[claude_cli] result subtype=", subtype, "error fields:", JSON.stringify(errFields));
5078
- console.error("[claude_cli] Full result event:", JSON.stringify(parsed).slice(0, 2e3));
5313
+ void presenter.onComplete(continuationResult);
5314
+ state.onNewOutput?.();
5315
+ state.startHeartbeat?.();
5316
+ console.info("[claude_cli] Result received in continuation mode \u2014 keeping stdin open for background work");
5317
+ } else {
5318
+ state.onResultReceived?.();
5319
+ if (state.stdin && !state.stdin.destroyed) {
5320
+ state.stdin.end();
5321
+ console.info("[claude_cli] Closed stdin after result event to signal CLI exit");
5322
+ }
5079
5323
  }
5080
5324
  break;
5081
5325
  }
@@ -5089,10 +5333,7 @@ function parseCodexStructuredLine(line, context, state) {
5089
5333
  try {
5090
5334
  parsed = JSON.parse(line);
5091
5335
  } catch {
5092
- state.summary += `${line}
5093
- `;
5094
- void presenter.onAssistantText(`${line}
5095
- `);
5336
+ void presenter.onLog(`[codex] ${line}`);
5096
5337
  return;
5097
5338
  }
5098
5339
  const type = typeof parsed.type === "string" ? parsed.type : "";
@@ -5101,13 +5342,9 @@ function parseCodexStructuredLine(line, context, state) {
5101
5342
  state.runtimeSessionId = parsed.thread_id;
5102
5343
  }
5103
5344
  const extractCodexErrorMessage = (value2) => {
5104
- if (typeof value2 !== "string") {
5105
- return "";
5106
- }
5345
+ if (typeof value2 !== "string") return "";
5107
5346
  const trimmed = value2.trim();
5108
- if (!trimmed) {
5109
- return "";
5110
- }
5347
+ if (!trimmed) return "";
5111
5348
  try {
5112
5349
  const parsedValue = JSON.parse(trimmed);
5113
5350
  if (typeof parsedValue.detail === "string" && parsedValue.detail.trim().length > 0) {
@@ -5136,18 +5373,14 @@ function parseCodexStructuredLine(line, context, state) {
5136
5373
  break;
5137
5374
  case "item.started": {
5138
5375
  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
- }
5376
+ if (!item || item.type !== "command_execution" || typeof item.id !== "string") break;
5142
5377
  const command = typeof item.command === "string" ? item.command : "";
5143
5378
  void presenter.onToolUse("Bash", { command, cwd: context.cwd }, item.id);
5144
5379
  break;
5145
5380
  }
5146
5381
  case "item.completed": {
5147
5382
  const item = typeof parsed.item === "object" && parsed.item !== null ? parsed.item : null;
5148
- if (!item || typeof item.type !== "string") {
5149
- break;
5150
- }
5383
+ if (!item || typeof item.type !== "string") break;
5151
5384
  if (item.type === "reasoning" && typeof item.text === "string") {
5152
5385
  void presenter.onThinking(item.text);
5153
5386
  break;
@@ -5160,8 +5393,8 @@ function parseCodexStructuredLine(line, context, state) {
5160
5393
  }
5161
5394
  if (item.type === "command_execution" && typeof item.id === "string") {
5162
5395
  const output = typeof item.aggregated_output === "string" ? item.aggregated_output : "";
5163
- const exitCode = typeof item.exit_code === "number" ? item.exit_code : null;
5164
- const resultText = output.trim().length > 0 ? output : exitCode === null ? "" : `Exit code: ${exitCode}`;
5396
+ const itemExitCode = typeof item.exit_code === "number" ? item.exit_code : null;
5397
+ const resultText = output.trim().length > 0 ? output : itemExitCode === null ? "" : `Exit code: ${itemExitCode}`;
5165
5398
  void presenter.onToolResult?.(item.id, resultText);
5166
5399
  break;
5167
5400
  }
@@ -5205,20 +5438,17 @@ function parseCodexStructuredLine(line, context, state) {
5205
5438
  }
5206
5439
  case "mcp_tool_call_end": {
5207
5440
  const toolId = typeof parsed.call_id === "string" ? parsed.call_id : `mcp-${Date.now()}`;
5208
- const result = parsed.result;
5209
- void presenter.onToolResult?.(toolId, JSON.stringify(result ?? {}, null, 2));
5441
+ void presenter.onToolResult?.(toolId, JSON.stringify(parsed.result ?? {}, null, 2));
5210
5442
  break;
5211
5443
  }
5212
5444
  case "token_count": {
5213
5445
  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
5446
  state.usage = {
5217
5447
  model: typeof info.model === "string" ? info.model : "codex",
5218
5448
  numTurns: Math.max(state.iterations, 1),
5219
5449
  durationMs: 0,
5220
- inputTokens,
5221
- outputTokens,
5450
+ inputTokens: typeof info.input_tokens === "number" ? info.input_tokens : 0,
5451
+ outputTokens: typeof info.output_tokens === "number" ? info.output_tokens : 0,
5222
5452
  cacheReadTokens: typeof info.cached_input_tokens === "number" ? info.cached_input_tokens : 0,
5223
5453
  cacheCreationTokens: 0,
5224
5454
  costUsd: typeof info.total_cost_usd === "number" ? info.total_cost_usd : 0
@@ -5234,23 +5464,18 @@ function parseCodexStructuredLine(line, context, state) {
5234
5464
  }
5235
5465
  case "task_complete": {
5236
5466
  const lastMessage = typeof parsed.last_agent_message === "string" ? parsed.last_agent_message : "";
5237
- if (lastMessage.length > 0) {
5238
- state.summary = lastMessage;
5239
- }
5467
+ if (lastMessage.length > 0) state.summary = lastMessage;
5240
5468
  break;
5241
5469
  }
5242
5470
  case "turn.completed": {
5243
5471
  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
5472
  state.usage = {
5248
5473
  model: typeof context.config.selectedModel === "string" && context.config.selectedModel.trim() ? context.config.selectedModel : "codex",
5249
5474
  numTurns: Math.max(state.iterations, 1),
5250
5475
  durationMs: 0,
5251
- inputTokens,
5252
- outputTokens,
5253
- cacheReadTokens,
5476
+ inputTokens: typeof usage.input_tokens === "number" ? usage.input_tokens : 0,
5477
+ outputTokens: typeof usage.output_tokens === "number" ? usage.output_tokens : 0,
5478
+ cacheReadTokens: typeof usage.cached_input_tokens === "number" ? usage.cached_input_tokens : 0,
5254
5479
  cacheCreationTokens: 0,
5255
5480
  costUsd: 0
5256
5481
  };
@@ -5265,16 +5490,127 @@ function parseCodexStructuredLine(line, context, state) {
5265
5490
  }
5266
5491
  case "error": {
5267
5492
  const message = extractCodexErrorMessage(parsed.message) || extractCodexErrorMessage(parsed.error) || typeof parsed.detail === "string" && parsed.detail.trim() || "";
5268
- if (message) {
5269
- state.error = message;
5270
- }
5493
+ if (message) state.error = message;
5271
5494
  break;
5272
5495
  }
5273
5496
  case "turn.failed": {
5274
5497
  const error = typeof parsed.error === "object" && parsed.error !== null ? parsed.error : {};
5275
5498
  const message = extractCodexErrorMessage(error.message) || extractCodexErrorMessage(error.detail) || typeof parsed.message === "string" && parsed.message.trim() || "";
5276
- if (message) {
5277
- state.error = message;
5499
+ if (message) state.error = message;
5500
+ break;
5501
+ }
5502
+ default:
5503
+ break;
5504
+ }
5505
+ }
5506
+ function extractCursorToolEntry(toolCall) {
5507
+ const [rawName, rawPayload] = Object.entries(toolCall)[0] ?? [];
5508
+ if (!rawName || typeof rawPayload !== "object" || rawPayload === null) return null;
5509
+ return { rawName, payload: rawPayload };
5510
+ }
5511
+ function formatCursorToolName(rawName) {
5512
+ if (rawName === "terminalToolCall") return "Bash";
5513
+ const trimmed = rawName.replace(/ToolCall$/, "");
5514
+ if (!trimmed) return "Cursor Tool";
5515
+ return trimmed.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/^[a-z]/, (char) => char.toUpperCase());
5516
+ }
5517
+ function buildCursorToolResultText(result) {
5518
+ if (typeof result === "string") return result;
5519
+ if (!result || typeof result !== "object") return JSON.stringify(result ?? {});
5520
+ const resultRecord = result;
5521
+ const success = typeof resultRecord.success === "object" && resultRecord.success !== null ? resultRecord.success : null;
5522
+ if (success) {
5523
+ if (typeof success.content === "string" && success.content.trim().length > 0) return success.content;
5524
+ if (typeof success.output === "string" && success.output.trim().length > 0) return success.output;
5525
+ const stdout = typeof success.stdout === "string" ? success.stdout.trim() : "";
5526
+ const stderr = typeof success.stderr === "string" ? success.stderr.trim() : "";
5527
+ if (stdout || stderr) return [stdout, stderr].filter(Boolean).join("\n\n");
5528
+ }
5529
+ const error = typeof resultRecord.error === "object" && resultRecord.error !== null ? resultRecord.error : null;
5530
+ if (error) {
5531
+ if (typeof error.message === "string" && error.message.trim().length > 0) return error.message;
5532
+ if (typeof error.content === "string" && error.content.trim().length > 0) return error.content;
5533
+ }
5534
+ return JSON.stringify(result, null, 2);
5535
+ }
5536
+ function parseCursorStructuredLine(line, context, state) {
5537
+ const presenter = context.presenter;
5538
+ let parsed = null;
5539
+ try {
5540
+ parsed = JSON.parse(line);
5541
+ } catch {
5542
+ if (line.trim().length > 0) void presenter.onLog(`[cursor_agent_cli] ${line}`);
5543
+ return;
5544
+ }
5545
+ const type = typeof parsed.type === "string" ? parsed.type : "";
5546
+ if (!type) return;
5547
+ if (typeof parsed.session_id === "string") state.runtimeSessionId = parsed.session_id;
5548
+ if (typeof parsed.model === "string") state.activeModel = parsed.model;
5549
+ switch (type) {
5550
+ case "system":
5551
+ break;
5552
+ case "assistant": {
5553
+ const message = typeof parsed.message === "object" && parsed.message !== null ? parsed.message : null;
5554
+ const content = Array.isArray(message?.content) ? message.content : [];
5555
+ for (const block of content) {
5556
+ if (!block || typeof block !== "object") continue;
5557
+ const part = block;
5558
+ const blockType = typeof part.type === "string" ? part.type : "";
5559
+ const text = typeof part.text === "string" ? part.text : "";
5560
+ if (!text) continue;
5561
+ state.iterations = Math.max(state.iterations, 1);
5562
+ if (blockType === "thinking" || blockType === "reasoning") {
5563
+ void presenter.onThinking(text);
5564
+ continue;
5565
+ }
5566
+ state.summary += text;
5567
+ void presenter.onAssistantText(text);
5568
+ }
5569
+ break;
5570
+ }
5571
+ case "tool_call": {
5572
+ const subtype = typeof parsed.subtype === "string" ? parsed.subtype : "";
5573
+ const toolId = typeof parsed.call_id === "string" ? parsed.call_id : `cursor-tool-${Date.now()}`;
5574
+ const toolCall = typeof parsed.tool_call === "object" && parsed.tool_call !== null ? parsed.tool_call : null;
5575
+ const entry = toolCall ? extractCursorToolEntry(toolCall) : null;
5576
+ if (!entry) break;
5577
+ state.iterations = Math.max(state.iterations, 1);
5578
+ const toolName = formatCursorToolName(entry.rawName);
5579
+ const toolArgs = typeof entry.payload.args === "object" && entry.payload.args !== null ? entry.payload.args : entry.payload;
5580
+ if (subtype === "started") {
5581
+ void presenter.onToolUse(toolName, toolArgs, toolId);
5582
+ break;
5583
+ }
5584
+ if (subtype === "completed") {
5585
+ void presenter.onToolResult?.(toolId, buildCursorToolResultText(entry.payload.result));
5586
+ }
5587
+ break;
5588
+ }
5589
+ case "result": {
5590
+ const subtype = typeof parsed.subtype === "string" ? parsed.subtype : "";
5591
+ const durationMs = typeof parsed.duration_ms === "number" ? parsed.duration_ms : 0;
5592
+ const resultText = typeof parsed.result === "string" ? parsed.result : "";
5593
+ state.iterations = Math.max(state.iterations, 1);
5594
+ state.usage = {
5595
+ model: state.activeModel || (typeof context.config.selectedModel === "string" && context.config.selectedModel.trim() ? context.config.selectedModel : "cursor-agent"),
5596
+ numTurns: Math.max(state.iterations, 1),
5597
+ durationMs,
5598
+ inputTokens: 0,
5599
+ outputTokens: 0,
5600
+ cacheReadTokens: 0,
5601
+ cacheCreationTokens: 0,
5602
+ costUsd: 0
5603
+ };
5604
+ void presenter.onUsageUpdate?.({
5605
+ model: state.usage.model,
5606
+ inputTokens: 0,
5607
+ outputTokens: 0,
5608
+ cacheReadTokens: 0,
5609
+ cacheCreationTokens: 0
5610
+ });
5611
+ if (resultText.trim().length > 0) state.summary = resultText;
5612
+ if ((parsed.is_error === true || subtype === "error") && !state.error) {
5613
+ state.error = resultText.trim() || "Cursor Agent run failed";
5278
5614
  }
5279
5615
  break;
5280
5616
  }
@@ -5284,18 +5620,12 @@ function parseCodexStructuredLine(line, context, state) {
5284
5620
  }
5285
5621
  function buildGeminiToolResultText(parsed) {
5286
5622
  const output = typeof parsed.output === "string" ? parsed.output : "";
5287
- if (output.trim().length > 0) {
5288
- return output;
5289
- }
5623
+ if (output.trim().length > 0) return output;
5290
5624
  const error = typeof parsed.error === "object" && parsed.error !== null ? parsed.error : null;
5291
5625
  const message = typeof error?.message === "string" ? error.message.trim() : "";
5292
5626
  const errorType = typeof error?.type === "string" ? error.type.trim() : "";
5293
- if (message && errorType) {
5294
- return `${errorType}: ${message}`;
5295
- }
5296
- if (message) {
5297
- return message;
5298
- }
5627
+ if (message && errorType) return `${errorType}: ${message}`;
5628
+ if (message) return message;
5299
5629
  const status = typeof parsed.status === "string" ? parsed.status.trim() : "";
5300
5630
  return status || "Tool completed";
5301
5631
  }
@@ -5305,22 +5635,13 @@ function parseGeminiStructuredLine(line, context, state) {
5305
5635
  try {
5306
5636
  parsed = JSON.parse(line);
5307
5637
  } catch {
5308
- state.summary += `${line}
5309
- `;
5310
- void presenter.onAssistantText(`${line}
5311
- `);
5638
+ void presenter.onLog(`[gemini_cli] ${line}`);
5312
5639
  return;
5313
5640
  }
5314
5641
  const type = typeof parsed.type === "string" ? parsed.type : "";
5315
- if (!type) {
5316
- return;
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
- }
5642
+ if (!type) return;
5643
+ if (typeof parsed.session_id === "string") state.runtimeSessionId = parsed.session_id;
5644
+ if (typeof parsed.model === "string") state.activeModel = parsed.model;
5324
5645
  switch (type) {
5325
5646
  case "init": {
5326
5647
  const model2 = state.activeModel || context.config.selectedModel || "gemini";
@@ -5328,14 +5649,9 @@ function parseGeminiStructuredLine(line, context, state) {
5328
5649
  break;
5329
5650
  }
5330
5651
  case "message": {
5331
- const role = typeof parsed.role === "string" ? parsed.role : "";
5332
- if (role !== "assistant") {
5333
- break;
5334
- }
5652
+ if (typeof parsed.role !== "string" || parsed.role !== "assistant") break;
5335
5653
  const content = typeof parsed.content === "string" ? parsed.content : "";
5336
- if (!content) {
5337
- break;
5338
- }
5654
+ if (!content) break;
5339
5655
  state.iterations = Math.max(state.iterations, 1);
5340
5656
  state.summary += content;
5341
5657
  void presenter.onAssistantText(content);
@@ -5360,9 +5676,7 @@ function parseGeminiStructuredLine(line, context, state) {
5360
5676
  const severity = typeof parsed.severity === "string" ? parsed.severity : "error";
5361
5677
  if (message) {
5362
5678
  void presenter.onLog(`[gemini_cli/${severity}] ${message}`);
5363
- if (severity === "error") {
5364
- state.error = message;
5365
- }
5679
+ if (severity === "error") state.error = message;
5366
5680
  }
5367
5681
  break;
5368
5682
  }
@@ -5390,11 +5704,10 @@ function parseGeminiStructuredLine(line, context, state) {
5390
5704
  const errObj = typeof parsed.error === "object" && parsed.error !== null ? parsed.error : null;
5391
5705
  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
5706
  state.error = errMsg;
5393
- console.error("[gemini_cli] result status=", parsed.status, "error fields:", JSON.stringify({
5707
+ console.error("[gemini_cli] result status=", parsed.status, "error:", JSON.stringify({
5394
5708
  error: parsed.error,
5395
5709
  error_message: parsed.error_message,
5396
- message: parsed.message,
5397
- result: typeof parsed.result === "string" ? parsed.result.slice(0, 200) : parsed.result
5710
+ message: parsed.message
5398
5711
  }));
5399
5712
  }
5400
5713
  break;
@@ -5403,8 +5716,8 @@ function parseGeminiStructuredLine(line, context, state) {
5403
5716
  break;
5404
5717
  }
5405
5718
  }
5406
- function getClaudePermissionMode(mode) {
5407
- switch (mode) {
5719
+ function getClaudePermissionMode(mode2) {
5720
+ switch (mode2) {
5408
5721
  case "plan":
5409
5722
  case "ask":
5410
5723
  case "review":
@@ -5413,40 +5726,108 @@ function getClaudePermissionMode(mode) {
5413
5726
  return "bypassPermissions";
5414
5727
  }
5415
5728
  }
5729
+ function isCodexReadOnlyMode(mode2) {
5730
+ return mode2 === "plan" || mode2 === "ask" || mode2 === "review";
5731
+ }
5732
+ function shouldForceCursorAgent(mode2) {
5733
+ switch (mode2) {
5734
+ case "plan":
5735
+ case "ask":
5736
+ case "review":
5737
+ return false;
5738
+ default:
5739
+ return true;
5740
+ }
5741
+ }
5742
+ function writeImagesToTempFiles(images) {
5743
+ if (!images?.length) return { paths: [], cleanup: () => {
5744
+ } };
5745
+ const paths = [];
5746
+ for (const img of images) {
5747
+ const ext = img.mimeType.split("/")[1]?.replace("jpeg", "jpg") ?? "png";
5748
+ const filePath = (0, import_path.join)((0, import_os.tmpdir)(), `aiden-img-${img.id}.${ext}`);
5749
+ (0, import_fs.writeFileSync)(filePath, Buffer.from(img.data, "base64"));
5750
+ paths.push(filePath);
5751
+ }
5752
+ return {
5753
+ paths,
5754
+ cleanup: () => {
5755
+ for (const p of paths) {
5756
+ try {
5757
+ (0, import_fs.rmSync)(p, { force: true });
5758
+ } catch {
5759
+ }
5760
+ }
5761
+ }
5762
+ };
5763
+ }
5416
5764
  function createClaudeCliBackend(command = "claude", defaultArgs = []) {
5417
5765
  return {
5418
5766
  kind: "claude_cli",
5419
5767
  supportTier: "structured",
5420
5768
  async run(context) {
5421
5769
  const args = [
5422
- "--print",
5423
5770
  "--verbose",
5424
5771
  "--output-format",
5425
5772
  "stream-json",
5773
+ "--input-format",
5774
+ "stream-json",
5426
5775
  "--permission-mode",
5427
5776
  getClaudePermissionMode(context.config.mode)
5428
5777
  ];
5429
- if (context.config.systemPrompt?.trim()) {
5430
- args.push("--system-prompt", context.config.systemPrompt.trim());
5431
- }
5432
- if (context.config.systemPromptAppend?.trim()) {
5433
- args.push("--append-system-prompt", context.config.systemPromptAppend.trim());
5778
+ const resumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
5779
+ if (!resumeId) {
5780
+ if (context.config.systemPrompt?.trim()) {
5781
+ args.push("--system-prompt", context.config.systemPrompt.trim());
5782
+ }
5783
+ if (context.config.systemPromptAppend?.trim()) {
5784
+ args.push("--append-system-prompt", context.config.systemPromptAppend.trim());
5785
+ }
5434
5786
  }
5435
5787
  if (context.config.selectedModel?.trim()) {
5436
5788
  args.push("--model", context.config.selectedModel.trim());
5437
5789
  }
5438
- const resumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
5790
+ if (context.config.mcpConfigPath?.trim()) {
5791
+ args.push("--mcp-config", context.config.mcpConfigPath.trim());
5792
+ }
5439
5793
  if (resumeId) {
5440
5794
  args.push("--resume", resumeId);
5441
5795
  console.info("[claude_cli] Resuming session", { resumeId });
5442
5796
  }
5443
- args.push(...defaultArgs, context.promptText);
5797
+ args.push(...defaultArgs);
5444
5798
  return createGenericCliBackend({
5445
5799
  kind: "claude_cli",
5446
5800
  supportTier: "structured",
5447
5801
  command,
5448
5802
  args,
5449
- parseStructuredLine: parseClaudeStructuredLine
5803
+ parseStructuredLine: parseClaudeStructuredLine,
5804
+ stdinWriter: (stdin, ctx) => {
5805
+ const contentBlocks = [];
5806
+ if (ctx.config.images?.length) {
5807
+ for (const img of ctx.config.images) {
5808
+ contentBlocks.push({
5809
+ type: "image",
5810
+ source: { type: "base64", media_type: img.mimeType, data: img.data }
5811
+ });
5812
+ }
5813
+ }
5814
+ const promptText = ctx.config.mode === "plan" ? buildPlanModePrefix(buildPromptWithSystem(ctx.config, ctx.promptText)) : buildPromptWithSystem(ctx.config, ctx.promptText);
5815
+ if (promptText.trim()) {
5816
+ contentBlocks.push({ type: "text", text: promptText });
5817
+ }
5818
+ console.info(`[claude_cli] stdinWriter: ${contentBlocks.length} content blocks (${ctx.config.images?.length ?? 0} images)`);
5819
+ const message = JSON.stringify({
5820
+ type: "user",
5821
+ message: { role: "user", content: contentBlocks },
5822
+ session_id: "default",
5823
+ parent_tool_use_id: null
5824
+ });
5825
+ stdin.write(message + "\n");
5826
+ },
5827
+ keepStdinOpen: true,
5828
+ allowContinuation: true,
5829
+ continuationIdleMs: 10 * 60 * 1e3
5830
+ // 10 minutes
5450
5831
  }).run(context);
5451
5832
  }
5452
5833
  };
@@ -5462,45 +5843,168 @@ function createGeminiCliBackend(command = "gemini", defaultArgs = []) {
5462
5843
  const resumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
5463
5844
  const approvalMode = context.config.mode === "plan" || context.config.mode === "ask" || context.config.mode === "review" ? "plan" : "yolo";
5464
5845
  args.push("--approval-mode", approvalMode);
5465
- if (resumeId) {
5466
- args.push("--resume", resumeId);
5467
- }
5468
- if (context.config.selectedModel?.trim()) {
5469
- args.push("--model", context.config.selectedModel.trim());
5470
- }
5846
+ if (resumeId) args.push("--resume", resumeId);
5847
+ if (context.config.selectedModel?.trim()) args.push("--model", context.config.selectedModel.trim());
5471
5848
  return [...args, ...defaultArgs];
5472
5849
  },
5473
5850
  promptViaStdin: true,
5474
5851
  augmentPrompt: (context) => {
5475
5852
  const basePrompt = buildPromptWithSystem(context.config, context.promptText);
5476
- if (context.config.mode === "plan") {
5477
- return buildPlanModePrefix(basePrompt);
5478
- }
5479
- return basePrompt;
5853
+ return context.config.mode === "plan" ? buildPlanModePrefix(basePrompt) : basePrompt;
5480
5854
  },
5481
5855
  parseStructuredLine: parseGeminiStructuredLine
5482
5856
  });
5483
5857
  }
5484
- function parseOpencodeStructuredLine(line, context, state) {
5858
+ function isCursorAgentCliModelId(model2) {
5859
+ 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);
5860
+ }
5861
+ function createCursorAgentCliBackend(command = "cursor-agent", defaultArgs = []) {
5862
+ return createGenericCliBackend({
5863
+ kind: "cursor_agent_cli",
5864
+ supportTier: "structured",
5865
+ command,
5866
+ args: [],
5867
+ buildArgs: (context, prompt) => {
5868
+ const args = ["--print", "--output-format", "stream-json"];
5869
+ const resumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
5870
+ if (shouldForceCursorAgent(context.config.mode)) args.push("--force");
5871
+ if (resumeId) args.push("--resume", resumeId);
5872
+ const model2 = context.config.selectedModel?.trim();
5873
+ if (model2 && isCursorAgentCliModelId(model2)) args.push("--model", model2);
5874
+ return [...args, ...defaultArgs, prompt];
5875
+ },
5876
+ augmentPrompt: (context) => {
5877
+ const base = buildPromptWithSystem(context.config, context.promptText);
5878
+ return context.config.mode === "plan" ? buildPlanModePrefix(base) : base;
5879
+ },
5880
+ parseStructuredLine: parseCursorStructuredLine
5881
+ });
5882
+ }
5883
+ function droidExecPermissionArgs(mode2) {
5884
+ if (mode2 === "plan" || mode2 === "ask" || mode2 === "review") return [];
5885
+ return ["--auto", "high"];
5886
+ }
5887
+ function parseDroidStructuredLine(line, context, state) {
5485
5888
  const presenter = context.presenter;
5486
5889
  let parsed = null;
5487
5890
  try {
5488
5891
  parsed = JSON.parse(line);
5489
5892
  } catch {
5490
- if (line.trim().length > 0) {
5491
- state.summary += `${line}
5492
- `;
5493
- void presenter.onAssistantText(`${line}
5494
- `);
5893
+ if (line.trim().length > 0) void presenter.onLog(`[droid_cli] ${line}`);
5894
+ return;
5895
+ }
5896
+ const type = typeof parsed.type === "string" ? parsed.type : "";
5897
+ if (!type) return;
5898
+ if (typeof parsed.session_id === "string") state.runtimeSessionId = parsed.session_id;
5899
+ if (typeof parsed.model === "string") state.activeModel = parsed.model;
5900
+ switch (type) {
5901
+ case "system": {
5902
+ const subtype = typeof parsed.subtype === "string" ? parsed.subtype : "";
5903
+ if (subtype === "init") {
5904
+ if (typeof parsed.session_id === "string") state.runtimeSessionId = parsed.session_id;
5905
+ if (typeof parsed.model === "string") state.activeModel = parsed.model;
5906
+ }
5907
+ break;
5495
5908
  }
5909
+ case "message": {
5910
+ const role = typeof parsed.role === "string" ? parsed.role : "";
5911
+ if (role !== "assistant") break;
5912
+ const text = typeof parsed.text === "string" ? parsed.text : "";
5913
+ if (!text) break;
5914
+ state.iterations = Math.max(state.iterations, 1);
5915
+ state.summary += text;
5916
+ void presenter.onAssistantText(text);
5917
+ break;
5918
+ }
5919
+ case "tool_call": {
5920
+ const toolId = typeof parsed.id === "string" ? parsed.id : `droid-tool-${Date.now()}`;
5921
+ const toolName = typeof parsed.toolName === "string" ? parsed.toolName : "Tool";
5922
+ const parameters = typeof parsed.parameters === "object" && parsed.parameters !== null ? parsed.parameters : {};
5923
+ state.iterations = Math.max(state.iterations, 1);
5924
+ void presenter.onToolUse(toolName, parameters, toolId);
5925
+ break;
5926
+ }
5927
+ case "tool_result": {
5928
+ const toolId = typeof parsed.id === "string" ? parsed.id : `droid-tool-${Date.now()}`;
5929
+ const value2 = parsed.value;
5930
+ const content = typeof value2 === "string" ? value2 : value2 !== void 0 ? JSON.stringify(value2) : "";
5931
+ const isError = parsed.isError === true;
5932
+ void presenter.onToolResult?.(toolId, content, isError);
5933
+ break;
5934
+ }
5935
+ case "completion": {
5936
+ const finalText = typeof parsed.finalText === "string" ? parsed.finalText : "";
5937
+ const numTurns = typeof parsed.numTurns === "number" ? parsed.numTurns : 1;
5938
+ const durationMs = typeof parsed.durationMs === "number" ? parsed.durationMs : 0;
5939
+ state.iterations = Math.max(state.iterations, numTurns, finalText ? 1 : 0);
5940
+ if (finalText.trim() && !state.summary.trim()) {
5941
+ state.summary = finalText;
5942
+ void presenter.onAssistantText(finalText);
5943
+ }
5944
+ state.usage = {
5945
+ model: state.activeModel || (typeof context.config.selectedModel === "string" && context.config.selectedModel.trim() ? context.config.selectedModel : "droid"),
5946
+ numTurns: Math.max(numTurns, 1),
5947
+ durationMs,
5948
+ inputTokens: 0,
5949
+ outputTokens: 0,
5950
+ cacheReadTokens: 0,
5951
+ cacheCreationTokens: 0,
5952
+ costUsd: 0
5953
+ };
5954
+ void presenter.onUsageUpdate?.({
5955
+ model: state.usage.model,
5956
+ inputTokens: 0,
5957
+ outputTokens: 0,
5958
+ cacheReadTokens: 0,
5959
+ cacheCreationTokens: 0
5960
+ });
5961
+ break;
5962
+ }
5963
+ default:
5964
+ break;
5965
+ }
5966
+ }
5967
+ function createDroidCliBackend(command = "droid", defaultArgs = []) {
5968
+ return createGenericCliBackend({
5969
+ kind: "droid_cli",
5970
+ supportTier: "structured",
5971
+ command,
5972
+ args: [],
5973
+ buildArgs: (context, prompt) => {
5974
+ const args = [
5975
+ "exec",
5976
+ "--output-format",
5977
+ "stream-json",
5978
+ ...droidExecPermissionArgs(context.config.mode),
5979
+ ...defaultArgs
5980
+ ];
5981
+ const resumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
5982
+ if (resumeId) args.push("--session-id", resumeId);
5983
+ const model2 = context.config.selectedModel?.trim();
5984
+ if (model2) args.push("-m", model2);
5985
+ args.push(prompt);
5986
+ return args;
5987
+ },
5988
+ augmentPrompt: (context) => {
5989
+ const base = buildPromptWithSystem(context.config, context.promptText);
5990
+ return context.config.mode === "plan" ? buildPlanModePrefix(base) : base;
5991
+ },
5992
+ parseStructuredLine: parseDroidStructuredLine
5993
+ });
5994
+ }
5995
+ function parseOpencodeStructuredLine(line, context, state) {
5996
+ const presenter = context.presenter;
5997
+ let parsed = null;
5998
+ try {
5999
+ parsed = JSON.parse(line);
6000
+ } catch {
6001
+ if (line.trim().length > 0) void presenter.onLog(`[opencode] ${line}`);
5496
6002
  return;
5497
6003
  }
5498
6004
  const type = typeof parsed.type === "string" ? parsed.type : "";
5499
6005
  if (!type) return;
5500
6006
  const sessionID = typeof parsed.sessionID === "string" ? parsed.sessionID : void 0;
5501
- if (sessionID) {
5502
- state.runtimeSessionId = sessionID;
5503
- }
6007
+ if (sessionID) state.runtimeSessionId = sessionID;
5504
6008
  const part = typeof parsed.part === "object" && parsed.part !== null ? parsed.part : null;
5505
6009
  switch (type) {
5506
6010
  case "step_start": {
@@ -5534,9 +6038,22 @@ function parseOpencodeStructuredLine(line, context, state) {
5534
6038
  if (part && typeof part.tokens === "object" && part.tokens !== null) {
5535
6039
  const tokens = part.tokens;
5536
6040
  state.usage = {
6041
+ model: typeof context.config.selectedModel === "string" && context.config.selectedModel.trim() || "opencode",
6042
+ numTurns: Math.max(state.iterations, 1),
6043
+ durationMs: 0,
5537
6044
  inputTokens: tokens.input ?? 0,
5538
- outputTokens: tokens.output ?? 0
6045
+ outputTokens: tokens.output ?? 0,
6046
+ cacheReadTokens: tokens.cache_read ?? 0,
6047
+ cacheCreationTokens: tokens.cache_creation ?? 0,
6048
+ costUsd: 0
5539
6049
  };
6050
+ void presenter.onUsageUpdate?.({
6051
+ model: state.usage.model,
6052
+ inputTokens: state.usage.inputTokens,
6053
+ outputTokens: state.usage.outputTokens,
6054
+ cacheReadTokens: state.usage.cacheReadTokens,
6055
+ cacheCreationTokens: state.usage.cacheCreationTokens
6056
+ });
5540
6057
  }
5541
6058
  break;
5542
6059
  }
@@ -5548,6 +6065,17 @@ function parseOpencodeStructuredLine(line, context, state) {
5548
6065
  void presenter.onLog(`[opencode/error] ${message}`);
5549
6066
  state.error = message;
5550
6067
  }
6068
+ const pid = state.process?.pid;
6069
+ if (pid) {
6070
+ try {
6071
+ process.kill(-pid, "SIGTERM");
6072
+ } catch {
6073
+ try {
6074
+ state.process?.kill("SIGTERM");
6075
+ } catch {
6076
+ }
6077
+ }
6078
+ }
5551
6079
  break;
5552
6080
  }
5553
6081
  default:
@@ -5555,25 +6083,40 @@ function parseOpencodeStructuredLine(line, context, state) {
5555
6083
  }
5556
6084
  }
5557
6085
  function createOpencodeCliBackend(command = "opencode", defaultArgs = []) {
5558
- return createGenericCliBackend({
6086
+ return {
5559
6087
  kind: "opencode_cli",
5560
6088
  supportTier: "structured",
5561
- command,
5562
- args: [],
5563
- buildArgs: (context, prompt) => {
5564
- const args = ["run", "--format", "json"];
5565
- const resumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
5566
- if (resumeId) args.push("--session", resumeId);
5567
- const model2 = context.config.selectedModel?.trim();
5568
- if (model2 && model2.includes("/")) args.push("--model", model2);
5569
- return [...args, ...defaultArgs, prompt];
5570
- },
5571
- augmentPrompt: (context) => {
5572
- const base = buildPromptWithSystem(context.config, context.promptText);
5573
- return context.config.mode === "plan" ? buildPlanModePrefix(base) : base;
5574
- },
5575
- parseStructuredLine: parseOpencodeStructuredLine
5576
- });
6089
+ async run(context) {
6090
+ const { paths: imagePaths, cleanup } = writeImagesToTempFiles(context.config.images);
6091
+ try {
6092
+ return await createGenericCliBackend({
6093
+ kind: "opencode_cli",
6094
+ supportTier: "structured",
6095
+ command,
6096
+ args: [],
6097
+ buildArgs: (ctx, prompt) => {
6098
+ const args = ["run", "--format", "json"];
6099
+ const resumeId = ctx.config.runtimeSessionId?.trim() || ctx.config.providerSessionId?.trim();
6100
+ if (resumeId) args.push("--session", resumeId);
6101
+ const model2 = ctx.config.selectedModel?.trim();
6102
+ if (model2 && model2.includes("/")) args.push("--model", model2);
6103
+ const fileArgs = imagePaths.flatMap((p) => ["--file", p]);
6104
+ if (fileArgs.length > 0) {
6105
+ return [...args, ...fileArgs, ...defaultArgs, "--", prompt];
6106
+ }
6107
+ return [...args, ...defaultArgs, prompt];
6108
+ },
6109
+ augmentPrompt: (ctx) => {
6110
+ const base = buildPromptWithSystem(ctx.config, ctx.promptText);
6111
+ return ctx.config.mode === "plan" ? buildPlanModePrefix(base) : base;
6112
+ },
6113
+ parseStructuredLine: parseOpencodeStructuredLine
6114
+ }).run(context);
6115
+ } finally {
6116
+ cleanup();
6117
+ }
6118
+ }
6119
+ };
5577
6120
  }
5578
6121
  function createGenericCliPassthroughBackend(command, defaultArgs = []) {
5579
6122
  return createGenericCliBackend({
@@ -5585,95 +6128,121 @@ function createGenericCliPassthroughBackend(command, defaultArgs = []) {
5585
6128
  });
5586
6129
  }
5587
6130
  function createCodexRuntimeBackend(command = "codex", defaultArgs = []) {
5588
- return createGenericCliBackend({
6131
+ return {
5589
6132
  kind: "codex_app_server",
5590
6133
  supportTier: "structured",
5591
- command,
5592
- args: [],
5593
- buildArgs: (context) => {
5594
- const resumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
5595
- const baseArgs = resumeId ? ["exec", "resume", "--json", "--skip-git-repo-check", resumeId, "-"] : ["exec", "--json", "--skip-git-repo-check", "-"];
5596
- return [...baseArgs, ...defaultArgs];
5597
- },
5598
- promptViaStdin: true,
5599
- augmentPrompt: (context) => {
5600
- const basePrompt = buildPromptWithSystem(context.config, context.promptText);
5601
- if (context.config.mode === "plan") {
5602
- return buildPlanModePrefix(basePrompt);
6134
+ async run(context) {
6135
+ const { paths: imagePaths, cleanup } = writeImagesToTempFiles(context.config.images);
6136
+ try {
6137
+ return await createGenericCliBackend({
6138
+ kind: "codex_app_server",
6139
+ supportTier: "structured",
6140
+ command,
6141
+ args: [],
6142
+ buildArgs: (ctx) => {
6143
+ const resumeId = ctx.config.runtimeSessionId?.trim() || ctx.config.providerSessionId?.trim();
6144
+ const modelArgs = ctx.config.selectedModel?.trim() ? ["--model", ctx.config.selectedModel.trim()] : [];
6145
+ const permissionArgs = isCodexReadOnlyMode(ctx.config.mode) ? ["--ask-for-approval", "never", "--sandbox", "read-only"] : ["--dangerously-bypass-approvals-and-sandbox"];
6146
+ const imageArgs = imagePaths.flatMap((p) => ["--image", p]);
6147
+ const baseArgs = resumeId ? [...permissionArgs, "exec", "resume", "--json", "--skip-git-repo-check", ...modelArgs, ...imageArgs, resumeId, "-"] : [...permissionArgs, "exec", "--json", "--skip-git-repo-check", ...modelArgs, ...imageArgs, "-"];
6148
+ return [...baseArgs, ...defaultArgs];
6149
+ },
6150
+ promptViaStdin: true,
6151
+ augmentPrompt: (ctx) => {
6152
+ const basePrompt = buildPromptWithSystem(ctx.config, ctx.promptText);
6153
+ return ctx.config.mode === "plan" ? buildPlanModePrefix(basePrompt) : basePrompt;
6154
+ },
6155
+ parseStructuredLine: parseCodexStructuredLine
6156
+ }).run(context);
6157
+ } finally {
6158
+ cleanup();
5603
6159
  }
5604
- return basePrompt;
5605
- },
5606
- parseStructuredLine: parseCodexStructuredLine
5607
- });
6160
+ }
6161
+ };
5608
6162
  }
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
6163
  var DEFAULT_MODEL = "claude-sonnet-4-6";
6164
+ var DROID_DEFAULT_MODEL = "claude-opus-4-7";
5627
6165
  function canonicalizePath(inputPath) {
5628
6166
  try {
5629
- const resolved = (0, import_node_path.resolve)(inputPath);
5630
- return (0, import_node_fs.existsSync)(resolved) ? (0, import_node_fs.realpathSync)(resolved) : resolved;
5631
- } catch (error) {
5632
- console.warn("CoreAgent", "Failed to canonicalize path", { inputPath, error });
6167
+ const resolved = (0, import_path2.resolve)(inputPath);
6168
+ return (0, import_fs2.existsSync)(resolved) ? (0, import_fs2.realpathSync)(resolved) : resolved;
6169
+ } catch {
5633
6170
  return null;
5634
6171
  }
5635
6172
  }
5636
6173
  function buildPromptWithFiles(task2, files) {
5637
- if (!files || files.length === 0) {
5638
- return task2;
5639
- }
6174
+ if (!files || files.length === 0) return task2;
5640
6175
  const fileList = files.map((file) => `- ${file.filename} (${file.label}): ${file.tempPath}`).join("\n");
5641
6176
  return `${task2}
5642
6177
 
5643
6178
  The user has attached the following files. You can read them using your available tools:
5644
6179
  ${fileList}`;
5645
6180
  }
5646
- var CoreAgent = class {
6181
+ var BaseMachineAgent = class _BaseMachineAgent {
6182
+ static MAX_RETRIES = 2;
6183
+ static RETRY_BASE_DELAY_MS = 3e3;
5647
6184
  presenter;
5648
- abortController = null;
5649
6185
  runtime;
5650
- pendingQuestionResolvers = /* @__PURE__ */ new Map();
5651
- pendingPlanApprovalResolvers = /* @__PURE__ */ new Map();
6186
+ abortController = null;
5652
6187
  constructor(presenter, runtime) {
5653
6188
  this.presenter = presenter;
5654
6189
  this.runtime = runtime;
5655
6190
  }
5656
- submitAnswer(questionId, answers) {
5657
- const resolver = this.pendingQuestionResolvers.get(questionId);
5658
- if (!resolver) return false;
5659
- resolver(answers);
5660
- this.pendingQuestionResolvers.delete(questionId);
5661
- return true;
6191
+ // ── Overridable hooks (defaults match sandbox behaviour) ───────────────
6192
+ /**
6193
+ * Extra system-prompt sections appended after identity + mode hint.
6194
+ * Sandbox: artifact IDs from env vars.
6195
+ * Desktop: taskMeta block + artifact IDs from AgentConfig fields.
6196
+ */
6197
+ buildExtraSystemPromptParts(_config) {
6198
+ return [];
5662
6199
  }
5663
- submitPlanApproval(approvalId, approved) {
5664
- const resolver = this.pendingPlanApprovalResolvers.get(approvalId);
5665
- if (!resolver) return false;
5666
- resolver(approved);
5667
- this.pendingPlanApprovalResolvers.delete(approvalId);
5668
- return true;
6200
+ /**
6201
+ * Return a callback to be notified when the CLI child process spawns.
6202
+ * Desktop returns a closure that registers the process for kill tracking.
6203
+ * Sandbox returns undefined (no process management needed).
6204
+ */
6205
+ getOnProcessSpawned() {
6206
+ return void 0;
5669
6207
  }
6208
+ /**
6209
+ * Whether to skip calling presenter.onComplete() after runCliBackend.
6210
+ * Desktop returns true when the agent was externally killed to avoid
6211
+ * a second onComplete firing on top of the "Interrupted by user" event.
6212
+ */
6213
+ shouldSkipOnComplete() {
6214
+ return false;
6215
+ }
6216
+ /**
6217
+ * Called when a session-resume attempt fails with a non-transient error.
6218
+ *
6219
+ * - Return null → retry immediately without the session ID (sandbox default).
6220
+ * - Return AgentResult → stop and surface this result, no retry (desktop behaviour).
6221
+ */
6222
+ async onSessionResumeFailure(_runtimeConfig, _lastResult) {
6223
+ this.presenter.onLog("Session resume failed, starting fresh session...");
6224
+ return null;
6225
+ }
6226
+ /**
6227
+ * Called before sleeping between retry attempts.
6228
+ * Desktop uses this to wait for the previous child process to exit.
6229
+ */
6230
+ async beforeRetry() {
6231
+ }
6232
+ /**
6233
+ * Build the prompt text sent to the CLI backend.
6234
+ * Default appends file attachment paths (sandbox uses this).
6235
+ * Desktop overrides to return config.task unchanged.
6236
+ */
6237
+ buildPromptText(config) {
6238
+ return buildPromptWithFiles(config.task, config.files);
6239
+ }
6240
+ // ── Shared helpers ─────────────────────────────────────────────────────
5670
6241
  abort() {
5671
6242
  this.abortController?.abort();
5672
6243
  }
5673
6244
  isAbortError(error) {
5674
- if (error instanceof Error && error.name === "AbortError") {
5675
- return true;
5676
- }
6245
+ if (error instanceof Error && error.name === "AbortError") return true;
5677
6246
  const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
5678
6247
  return message.includes("aborted") || message.includes("interrupted") || message.includes("cancelled") || message.includes("canceled");
5679
6248
  }
@@ -5684,6 +6253,8 @@ var CoreAgent = class {
5684
6253
  switch (backendKind2) {
5685
6254
  case "claude_cli":
5686
6255
  case "codex_app_server":
6256
+ case "cursor_agent_cli":
6257
+ case "droid_cli":
5687
6258
  case "gemini_cli":
5688
6259
  case "opencode_cli":
5689
6260
  return "structured";
@@ -5692,77 +6263,112 @@ var CoreAgent = class {
5692
6263
  return "text";
5693
6264
  }
5694
6265
  }
5695
- buildCliEnvironment() {
5696
- const loginEnv = process.env;
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) {
6266
+ // ── Core lifecycle ─────────────────────────────────────────────────────
6267
+ buildRuntimeConfig(config, safeProjectPath) {
5707
6268
  const backendKind2 = this.resolveBackendKind(config);
5708
6269
  const supportTier = config.supportTier || this.getDefaultSupportTier(backendKind2);
5709
- const systemPromptParts = [config.systemPrompt?.trim()].filter(Boolean);
5710
- const modeHint = MODE_CONFIG[config.mode || "agent"]?.systemPromptHint;
5711
- if (modeHint) {
5712
- systemPromptParts.push(modeHint);
5713
- }
5714
- if (safeProjectPath) {
6270
+ const systemPromptParts = [AIDEN_IDENTITY_PROMPT, config.systemPrompt?.trim()].filter(
6271
+ Boolean
6272
+ );
6273
+ const modeHint = getAidenModePrompt(config.mode);
6274
+ if (modeHint) systemPromptParts.push(modeHint);
6275
+ const workingDir = config.worktreePath || safeProjectPath;
6276
+ if (workingDir) {
5715
6277
  systemPromptParts.push(
5716
- [
5717
- `You are working inside: ${safeProjectPath}`,
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}.`
6278
+ `You are working inside: ${workingDir}
6279
+ Prefer relative paths and keep your work scoped to this project.`
5725
6280
  );
5726
6281
  }
6282
+ const extra = this.buildExtraSystemPromptParts(config);
6283
+ systemPromptParts.push(...extra);
5727
6284
  return {
5728
6285
  ...config,
5729
6286
  backendKind: backendKind2,
5730
6287
  supportTier,
5731
6288
  runtimeCommand: config.runtimeCommand || this.runtime.runtimeCommand,
5732
6289
  runtimeArgs: config.runtimeArgs || this.runtime.runtimeArgs,
5733
- selectedModel: config.selectedModel || DEFAULT_MODEL,
6290
+ selectedModel: config.selectedModel || (backendKind2 === "droid_cli" ? DROID_DEFAULT_MODEL : DEFAULT_MODEL),
5734
6291
  systemPrompt: systemPromptParts.join("\n\n") || void 0
5735
6292
  };
5736
6293
  }
5737
6294
  async runCliBackend(config, safeProjectPath) {
5738
6295
  const backendKind2 = this.resolveBackendKind(config);
5739
- const defaultCwd = (0, import_node_path.join)((0, import_node_os.homedir)(), "Documents", "aiden");
5740
- if (!(0, import_node_fs.existsSync)(defaultCwd)) {
5741
- (0, import_node_fs.mkdirSync)(defaultCwd, { recursive: true });
6296
+ const defaultCwd = (0, import_path2.join)((0, import_os2.homedir)(), "Documents", "aiden");
6297
+ if (!(0, import_fs2.existsSync)(defaultCwd)) {
6298
+ (0, import_fs2.mkdirSync)(defaultCwd, { recursive: true });
5742
6299
  }
5743
- const runtimeConfig = await this.buildRuntimeConfig(config, safeProjectPath);
6300
+ let runtimeConfig = this.buildRuntimeConfig(config, safeProjectPath);
5744
6301
  const cwd = runtimeConfig.worktreePath || safeProjectPath || runtimeConfig.cwd || defaultCwd;
5745
- const promptText = buildPromptWithFiles(runtimeConfig.task, runtimeConfig.files);
5746
6302
  const env = this.buildCliEnvironment();
6303
+ const promptText = this.buildPromptText(runtimeConfig);
5747
6304
  const runtimeCommand = runtimeConfig.runtimeCommand || this.runtime.runtimeCommand;
5748
6305
  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
- const result = await backend.run({
5751
- presenter: this.presenter,
5752
- config: runtimeConfig,
5753
- abortController: this.abortController,
5754
- cwd,
5755
- env,
5756
- promptText
5757
- });
5758
- const normalizedResult = {
5759
- ...result,
5760
- backendKind: backend.kind,
5761
- supportTier: backend.supportTier,
5762
- runtimeSessionId: result.runtimeSessionId || result.providerSessionId,
5763
- providerSessionId: result.providerSessionId || result.runtimeSessionId
5764
- };
5765
- this.presenter.onComplete(normalizedResult);
6306
+ 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);
6307
+ let lastResult = null;
6308
+ for (let attempt = 0; attempt <= _BaseMachineAgent.MAX_RETRIES; attempt++) {
6309
+ if (this.abortController?.signal.aborted) break;
6310
+ const result = await backend.run({
6311
+ presenter: this.presenter,
6312
+ config: runtimeConfig,
6313
+ abortController: this.abortController,
6314
+ cwd,
6315
+ env,
6316
+ promptText,
6317
+ onProcessSpawned: this.getOnProcessSpawned()
6318
+ });
6319
+ lastResult = {
6320
+ ...result,
6321
+ backendKind: backend.kind,
6322
+ supportTier: backend.supportTier,
6323
+ runtimeSessionId: result.runtimeSessionId || result.providerSessionId,
6324
+ providerSessionId: result.providerSessionId || result.runtimeSessionId
6325
+ };
6326
+ if (lastResult.success || !lastResult.error) break;
6327
+ const hadResumeId = runtimeConfig.runtimeSessionId || runtimeConfig.providerSessionId;
6328
+ if (hadResumeId && !isTransientError(lastResult.error)) {
6329
+ const overrideResult = await this.onSessionResumeFailure(runtimeConfig, lastResult);
6330
+ if (overrideResult !== null) {
6331
+ lastResult = overrideResult;
6332
+ break;
6333
+ }
6334
+ console.warn(
6335
+ "[base-machine-agent] Resume failed, retrying without session ID:",
6336
+ lastResult.error
6337
+ );
6338
+ runtimeConfig = { ...runtimeConfig, runtimeSessionId: void 0, providerSessionId: void 0 };
6339
+ const retryResult = await backend.run({
6340
+ presenter: this.presenter,
6341
+ config: runtimeConfig,
6342
+ abortController: this.abortController,
6343
+ cwd,
6344
+ env,
6345
+ promptText,
6346
+ onProcessSpawned: this.getOnProcessSpawned()
6347
+ });
6348
+ lastResult = {
6349
+ ...retryResult,
6350
+ backendKind: backend.kind,
6351
+ supportTier: backend.supportTier,
6352
+ runtimeSessionId: retryResult.runtimeSessionId || retryResult.providerSessionId,
6353
+ providerSessionId: retryResult.providerSessionId || retryResult.runtimeSessionId
6354
+ };
6355
+ break;
6356
+ }
6357
+ if (!isTransientError(lastResult.error)) break;
6358
+ if (attempt < _BaseMachineAgent.MAX_RETRIES) {
6359
+ await this.beforeRetry();
6360
+ const delayMs = _BaseMachineAgent.RETRY_BASE_DELAY_MS * Math.pow(2, attempt);
6361
+ console.warn(
6362
+ `[base-machine-agent] Transient error (attempt ${attempt + 1}/${_BaseMachineAgent.MAX_RETRIES + 1}), retrying in ${delayMs}ms: ${lastResult.error}`
6363
+ );
6364
+ void this.presenter.onLog(`Transient error, retrying in ${Math.round(delayMs / 1e3)}s...`);
6365
+ await new Promise((resolve2) => setTimeout(resolve2, delayMs));
6366
+ }
6367
+ }
6368
+ const normalizedResult = lastResult;
6369
+ if (!this.shouldSkipOnComplete()) {
6370
+ this.presenter.onComplete(normalizedResult);
6371
+ }
5766
6372
  return normalizedResult;
5767
6373
  }
5768
6374
  async run(config) {
@@ -5780,11 +6386,9 @@ var CoreAgent = class {
5780
6386
  let safeProjectPath = initialConfig.projectPath;
5781
6387
  if (initialConfig.projectPath) {
5782
6388
  const canonicalized = canonicalizePath(initialConfig.projectPath);
5783
- if (canonicalized) {
5784
- safeProjectPath = canonicalized;
5785
- }
6389
+ if (canonicalized) safeProjectPath = canonicalized;
5786
6390
  }
5787
- if (safeProjectPath && !(0, import_node_fs.existsSync)(safeProjectPath)) {
6391
+ if (safeProjectPath && !(0, import_fs2.existsSync)(safeProjectPath)) {
5788
6392
  const message = `Project path no longer exists: "${safeProjectPath}". Please update the project's local path in project settings before running the agent.`;
5789
6393
  this.presenter.onError(message, true);
5790
6394
  const result = {
@@ -5806,7 +6410,7 @@ var CoreAgent = class {
5806
6410
  const interrupted = this.isAbortError(error);
5807
6411
  const message = interrupted ? "Interrupted by user" : error instanceof Error ? error.message : String(error);
5808
6412
  if (!interrupted) {
5809
- console.error("CoreAgent", "CLI runtime failed", error);
6413
+ console.error("[base-machine-agent] CLI runtime failed", error);
5810
6414
  this.presenter.onError(message, true);
5811
6415
  }
5812
6416
  const result = {
@@ -5825,6 +6429,79 @@ var CoreAgent = class {
5825
6429
  }
5826
6430
  };
5827
6431
 
6432
+ // src/core-agent.ts
6433
+ var CoreAgent = class extends BaseMachineAgent {
6434
+ childProcess = null;
6435
+ constructor(presenter, runtime) {
6436
+ super(presenter, runtime);
6437
+ }
6438
+ // ── Interactive tool response (WS relay → stdin) ───────────────────────
6439
+ /**
6440
+ * Send a tool_result to the CLI's stdin (stream-json format).
6441
+ * Used for interactive tools like AskUserQuestion.
6442
+ */
6443
+ sendToolResponse(toolId, response) {
6444
+ if (!this.childProcess) return false;
6445
+ const stdin = this.childProcess.stdin;
6446
+ if (!stdin || stdin.destroyed) return false;
6447
+ try {
6448
+ const message = JSON.stringify({
6449
+ type: "user",
6450
+ message: {
6451
+ role: "user",
6452
+ content: [
6453
+ {
6454
+ type: "tool_result",
6455
+ tool_use_id: toolId,
6456
+ content: response
6457
+ }
6458
+ ]
6459
+ },
6460
+ session_id: "default",
6461
+ parent_tool_use_id: null
6462
+ });
6463
+ stdin.write(message + "\n");
6464
+ console.info(`[CoreAgent] Sent tool_response for tool ${toolId}`);
6465
+ return true;
6466
+ } catch (err) {
6467
+ console.error("[CoreAgent] Failed to write tool_response to stdin", err);
6468
+ return false;
6469
+ }
6470
+ }
6471
+ // ── BaseMachineAgent hooks ─────────────────────────────────────────────
6472
+ getOnProcessSpawned() {
6473
+ return (child) => {
6474
+ this.childProcess = child;
6475
+ child.on("exit", () => {
6476
+ this.childProcess = null;
6477
+ });
6478
+ };
6479
+ }
6480
+ buildCliEnvironment() {
6481
+ const loginEnv = process.env;
6482
+ const env = {};
6483
+ for (const [key, value2] of Object.entries(loginEnv)) {
6484
+ if (value2 !== void 0) env[key] = value2;
6485
+ }
6486
+ env.PATH = `/home/user/.local/node/bin:${env.PATH ?? ""}`;
6487
+ return env;
6488
+ }
6489
+ buildExtraSystemPromptParts(_config) {
6490
+ const artifactIds = [];
6491
+ const envTaskId = process.env.AIDEN_TASK_ID;
6492
+ const envSessionId = process.env.AIDEN_SESSION_ID;
6493
+ if (envTaskId) artifactIds.push(`- taskId: "${envTaskId}"`);
6494
+ if (envSessionId) artifactIds.push(`- conversationId: "${envSessionId}"`);
6495
+ if (artifactIds.length === 0) return [];
6496
+ return [
6497
+ [
6498
+ "When creating artifacts using MCP tools (create_plan, create_document_artifact, create_test_report), always pass these IDs:",
6499
+ ...artifactIds
6500
+ ].join("\n")
6501
+ ];
6502
+ }
6503
+ };
6504
+
5828
6505
  // src/web-presenter.ts
5829
6506
  var WebPresenter = class {
5830
6507
  wsClient;
@@ -5852,8 +6529,14 @@ var WebPresenter = class {
5852
6529
  onToolUse(tool, input, toolId) {
5853
6530
  this.wsClient.emitEvent({ type: "tool_use", id: toolId, name: tool, input, ts: Date.now() });
5854
6531
  }
5855
- onToolResult(toolId, content) {
5856
- this.wsClient.emitEvent({ type: "tool_result", id: toolId, content, ts: Date.now() });
6532
+ onToolResult(toolId, content, isError) {
6533
+ this.wsClient.emitEvent({
6534
+ type: "tool_result",
6535
+ id: toolId,
6536
+ content,
6537
+ ts: Date.now(),
6538
+ ...isError ? { is_error: true } : {}
6539
+ });
5857
6540
  }
5858
6541
  onTurnComplete(content) {
5859
6542
  this.wsClient.emitEvent({ type: "turn_complete", content, ts: Date.now() });
@@ -5861,6 +6544,26 @@ var WebPresenter = class {
5861
6544
  onUsageUpdate(usage) {
5862
6545
  this.wsClient.emitEvent({ type: "usage", usage });
5863
6546
  }
6547
+ onAskUser(id, questions) {
6548
+ this.wsClient.emitEvent({ type: "ask_user", id, questions });
6549
+ }
6550
+ onTodoWrite(todos) {
6551
+ this.wsClient.emitEvent({ type: "todo_write", todos });
6552
+ }
6553
+ onExitPlanMode(id, input, planFilePath) {
6554
+ this.wsClient.emitEvent({ type: "exit_plan_mode", id, input, ...planFilePath ? { planFilePath } : {} });
6555
+ }
6556
+ onCheckpoint(id) {
6557
+ this.wsClient.emitEvent({ type: "checkpoint", id });
6558
+ }
6559
+ onActivity(status) {
6560
+ this.wsClient.emitEvent({ type: "activity", status, ts: Date.now() });
6561
+ }
6562
+ onOpenBrowserTab(browserId, url2) {
6563
+ this.wsClient.emitEvent({ type: "open_browser_tab", browserId, ...url2 ? { url: url2 } : {} });
6564
+ }
6565
+ recordRawTranscript() {
6566
+ }
5864
6567
  onError(message, _isAgentError) {
5865
6568
  this.wsClient.emitEvent({ type: "error", message });
5866
6569
  if (!this.suppressSessionLifecycle) {
@@ -9462,10 +10165,6 @@ var WSClient = class {
9462
10165
  emitEvent(event) {
9463
10166
  this.socket.emit("agent_event", event);
9464
10167
  }
9465
- /** Signal the WS relay that this agent is about to update (not crashing). */
9466
- emitUpdating() {
9467
- this.socket.emit("agent_updating");
9468
- }
9469
10168
  waitForConnection(timeoutMs = 15e3) {
9470
10169
  return new Promise((resolve2, reject) => {
9471
10170
  if (this.socket.connected) {
@@ -9501,15 +10200,24 @@ var WSClient = class {
9501
10200
  this.socket.on("connect_error", (error) => {
9502
10201
  console.warn(`[WSClient] Connection error: ${error.message}`);
9503
10202
  });
9504
- this.socket.on("user_message", (data) => {
9505
- callbacks.onUserMessage({ text: data.text, providerSessionId: data.providerSessionId, backendKind: data.backendKind, selectedModel: data.selectedModel });
10203
+ this.socket.on("user_message", (data, ack) => {
10204
+ ack?.({ ok: true });
10205
+ callbacks.onUserMessage({
10206
+ text: data.text,
10207
+ images: data.images,
10208
+ providerSessionId: data.providerSessionId,
10209
+ backendKind: data.backendKind,
10210
+ selectedModel: data.selectedModel,
10211
+ mode: data.mode
10212
+ });
9506
10213
  });
9507
10214
  this.socket.on("stop", () => {
9508
10215
  callbacks.onStop();
9509
10216
  });
9510
- this.socket.on("update_agent", (data) => {
9511
- console.info("[WSClient] Received update_agent:", data);
9512
- callbacks.onUpdateAgent?.(data);
10217
+ this.socket.on("tool_response", (data) => {
10218
+ if (data?.toolId && typeof data.response === "string") {
10219
+ callbacks.onToolResponse?.(data.toolId, data.response);
10220
+ }
9513
10221
  });
9514
10222
  this.socket.on("error", (data) => {
9515
10223
  console.error(`[WSClient] Server error: ${data.message} (${data.code})`);
@@ -9518,52 +10226,15 @@ var WSClient = class {
9518
10226
  };
9519
10227
 
9520
10228
  // src/version.ts
9521
- var AGENT_VERSION = "0.1.2";
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
- }
10229
+ var AGENT_VERSION = "0.1.4";
9559
10230
 
9560
10231
  // src/sandbox.ts
9561
10232
  async function runSandbox(config) {
9562
10233
  let currentTask = config.task;
10234
+ let currentImages = [];
9563
10235
  let stopped = false;
9564
10236
  let pendingMessageResolve = null;
9565
10237
  let currentAgent = null;
9566
- let pendingUpdate = null;
9567
10238
  console.info("[sandbox] Connecting to WS server", { wsUrl: config.wsUrl, sessionId: config.sessionId });
9568
10239
  const wsClient = new WSClient(
9569
10240
  config.wsUrl,
@@ -9584,13 +10255,14 @@ async function runSandbox(config) {
9584
10255
  console.info("[sandbox] Stop signal received");
9585
10256
  if (currentAgent) currentAgent.abort();
9586
10257
  },
9587
- onUpdateAgent: (payload) => {
9588
- console.info("[sandbox] Update requested:", payload);
9589
- pendingUpdate = payload;
9590
- if (pendingMessageResolve) {
9591
- const resolve2 = pendingMessageResolve;
9592
- pendingMessageResolve = null;
9593
- resolve2(null);
10258
+ onToolResponse: (toolId, response) => {
10259
+ if (currentAgent) {
10260
+ const sent = currentAgent.sendToolResponse(toolId, response);
10261
+ if (!sent) {
10262
+ console.warn("[sandbox] Failed to deliver tool_response to agent stdin", { toolId });
10263
+ }
10264
+ } else {
10265
+ console.warn("[sandbox] No active agent for tool_response", { toolId });
9594
10266
  }
9595
10267
  },
9596
10268
  onConnect: () => console.info("[sandbox] WS connected"),
@@ -9609,11 +10281,8 @@ async function runSandbox(config) {
9609
10281
  let lastProviderSessionId = config.providerSessionId;
9610
10282
  let activeBackendKind = config.backendKind || "claude_cli";
9611
10283
  let activeModel = config.model;
10284
+ let activeMode = config.mode || "agent";
9612
10285
  while (!stopped) {
9613
- if (pendingUpdate) {
9614
- await performUpdate(wsClient, pendingUpdate, lastProviderSessionId);
9615
- pendingUpdate = null;
9616
- }
9617
10286
  if (currentTask?.trim()) {
9618
10287
  const agent = new CoreAgent(presenter, {
9619
10288
  backendKind: activeBackendKind
@@ -9627,8 +10296,17 @@ async function runSandbox(config) {
9627
10296
  projectPath: config.projectPath,
9628
10297
  cwd: config.projectPath,
9629
10298
  backendKind: activeBackendKind,
10299
+ mode: activeMode,
9630
10300
  selectedModel: activeModel,
9631
- providerSessionId: lastProviderSessionId ?? void 0
10301
+ providerSessionId: lastProviderSessionId ?? void 0,
10302
+ images: currentImages.length > 0 ? currentImages.map((img) => ({
10303
+ id: img.id,
10304
+ data: img.data,
10305
+ mimeType: img.mimeType,
10306
+ filename: img.filename,
10307
+ width: 0,
10308
+ height: 0
10309
+ })) : void 0
9632
10310
  });
9633
10311
  } catch (error) {
9634
10312
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -9640,22 +10318,15 @@ async function runSandbox(config) {
9640
10318
  lastProviderSessionId = result.providerSessionId;
9641
10319
  }
9642
10320
  if (stopped) break;
9643
- if (pendingUpdate) {
9644
- await performUpdate(wsClient, pendingUpdate, lastProviderSessionId);
9645
- pendingUpdate = null;
9646
- }
9647
10321
  }
9648
10322
  const nextPayload = await new Promise((resolve2) => {
9649
10323
  pendingMessageResolve = resolve2;
9650
10324
  });
9651
10325
  if (nextPayload === null || stopped) {
9652
- if (pendingUpdate) {
9653
- await performUpdate(wsClient, pendingUpdate, lastProviderSessionId);
9654
- pendingUpdate = null;
9655
- }
9656
10326
  break;
9657
10327
  }
9658
10328
  currentTask = nextPayload.text;
10329
+ currentImages = nextPayload.images ?? [];
9659
10330
  if (nextPayload.providerSessionId) {
9660
10331
  lastProviderSessionId = nextPayload.providerSessionId;
9661
10332
  }
@@ -9665,25 +10336,20 @@ async function runSandbox(config) {
9665
10336
  if (nextPayload.selectedModel) {
9666
10337
  activeModel = nextPayload.selectedModel;
9667
10338
  }
10339
+ if (nextPayload.mode) {
10340
+ activeMode = nextPayload.mode;
10341
+ }
9668
10342
  }
9669
10343
  presenter.setSuppressSessionLifecycle(false);
9670
10344
  wsClient.close();
9671
10345
  }
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
10346
 
9686
10347
  // src/index.ts
10348
+ if (process.argv.includes("--version")) {
10349
+ process.stdout.write(`${AGENT_VERSION}
10350
+ `);
10351
+ process.exit(0);
10352
+ }
9687
10353
  var wsUrl = process.env.AIDEN_WS_URL;
9688
10354
  var sessionId = process.env.AIDEN_SESSION_ID;
9689
10355
  var taskId = process.env.AIDEN_TASK_ID;
@@ -9691,13 +10357,14 @@ var sessionToken = process.env.AIDEN_SESSION_TOKEN;
9691
10357
  var task = process.env.AIDEN_TASK;
9692
10358
  var projectPath = process.env.AIDEN_PROJECT_PATH || "/home/user/workspace";
9693
10359
  var backendKind = process.env.AIDEN_BACKEND_KIND || "claude_cli";
10360
+ var mode = process.env.AIDEN_MODE || "agent";
9694
10361
  var model = process.env.AIDEN_MODEL;
9695
10362
  var providerSessionId = process.env.AIDEN_PROVIDER_SESSION_ID || void 0;
9696
10363
  if (!wsUrl || !sessionId || !taskId || !sessionToken || task == null) {
9697
10364
  console.error("[aiden-agent] Missing required env vars: AIDEN_WS_URL, AIDEN_SESSION_ID, AIDEN_TASK_ID, AIDEN_SESSION_TOKEN, AIDEN_TASK");
9698
10365
  process.exit(1);
9699
10366
  }
9700
- console.info("[aiden-agent] Starting", { sessionId, taskId, projectPath, backendKind, model, version: AGENT_VERSION });
10367
+ console.info("[aiden-agent] Starting", { sessionId, taskId, projectPath, backendKind, mode, model, version: AGENT_VERSION });
9701
10368
  runSandbox({
9702
10369
  wsUrl,
9703
10370
  sessionId,
@@ -9706,6 +10373,7 @@ runSandbox({
9706
10373
  task,
9707
10374
  projectPath,
9708
10375
  backendKind,
10376
+ mode,
9709
10377
  model,
9710
10378
  providerSessionId,
9711
10379
  maxIterations: 50