@aiden-ade/sandbox-agent 0.1.43 → 0.1.45

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 (86) hide show
  1. package/dist/__tests__/cli-executable.test.d.ts +2 -0
  2. package/dist/__tests__/cli-executable.test.d.ts.map +1 -0
  3. package/dist/__tests__/cli-executable.test.js +83 -0
  4. package/dist/__tests__/cli-executable.test.js.map +1 -0
  5. package/dist/__tests__/cli-help.test.d.ts +2 -0
  6. package/dist/__tests__/cli-help.test.d.ts.map +1 -0
  7. package/dist/__tests__/cli-help.test.js +33 -0
  8. package/dist/__tests__/cli-help.test.js.map +1 -0
  9. package/dist/__tests__/core-agent-workflow-context.test.d.ts +2 -0
  10. package/dist/__tests__/core-agent-workflow-context.test.d.ts.map +1 -0
  11. package/dist/__tests__/core-agent-workflow-context.test.js +50 -0
  12. package/dist/__tests__/core-agent-workflow-context.test.js.map +1 -0
  13. package/dist/__tests__/daemon-socket.integration.test.d.ts +2 -0
  14. package/dist/__tests__/daemon-socket.integration.test.d.ts.map +1 -0
  15. package/dist/__tests__/daemon-socket.integration.test.js +156 -0
  16. package/dist/__tests__/daemon-socket.integration.test.js.map +1 -0
  17. package/dist/__tests__/daemon.test.d.ts +2 -0
  18. package/dist/__tests__/daemon.test.d.ts.map +1 -0
  19. package/dist/__tests__/daemon.test.js +1014 -0
  20. package/dist/__tests__/daemon.test.js.map +1 -0
  21. package/dist/__tests__/generated-images.test.d.ts +2 -0
  22. package/dist/__tests__/generated-images.test.d.ts.map +1 -0
  23. package/dist/__tests__/generated-images.test.js +50 -0
  24. package/dist/__tests__/generated-images.test.js.map +1 -0
  25. package/dist/__tests__/sandbox-workflow-context.test.d.ts +2 -0
  26. package/dist/__tests__/sandbox-workflow-context.test.d.ts.map +1 -0
  27. package/dist/__tests__/sandbox-workflow-context.test.js +66 -0
  28. package/dist/__tests__/sandbox-workflow-context.test.js.map +1 -0
  29. package/dist/aiden-system-prompt.d.ts +6 -0
  30. package/dist/aiden-system-prompt.d.ts.map +1 -0
  31. package/dist/aiden-system-prompt.js +6 -0
  32. package/dist/aiden-system-prompt.js.map +1 -0
  33. package/dist/cli-executable.d.ts +58 -0
  34. package/dist/cli-executable.d.ts.map +1 -0
  35. package/dist/cli-executable.js +301 -0
  36. package/dist/cli-executable.js.map +1 -0
  37. package/dist/cli-help.d.ts +7 -0
  38. package/dist/cli-help.d.ts.map +1 -0
  39. package/dist/cli-help.js +45 -0
  40. package/dist/cli-help.js.map +1 -0
  41. package/dist/core-agent.d.ts +37 -0
  42. package/dist/core-agent.d.ts.map +1 -0
  43. package/dist/core-agent.js +337 -0
  44. package/dist/core-agent.js.map +1 -0
  45. package/dist/daemon.d.ts +46 -0
  46. package/dist/daemon.d.ts.map +1 -0
  47. package/dist/daemon.js +876 -0
  48. package/dist/daemon.js.map +1 -0
  49. package/dist/generated-images.d.ts +4 -0
  50. package/dist/generated-images.d.ts.map +1 -0
  51. package/dist/generated-images.js +142 -0
  52. package/dist/generated-images.js.map +1 -0
  53. package/dist/index.cjs +737 -130
  54. package/dist/index.d.ts +6 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +118 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/runtime-backends.d.ts +2 -0
  59. package/dist/runtime-backends.d.ts.map +1 -0
  60. package/dist/runtime-backends.js +2 -0
  61. package/dist/runtime-backends.js.map +1 -0
  62. package/dist/sandbox.d.ts +31 -0
  63. package/dist/sandbox.d.ts.map +1 -0
  64. package/dist/sandbox.js +215 -0
  65. package/dist/sandbox.js.map +1 -0
  66. package/dist/types.d.ts +6 -0
  67. package/dist/types.d.ts.map +1 -0
  68. package/dist/types.js +6 -0
  69. package/dist/types.js.map +1 -0
  70. package/dist/updater.d.ts +15 -0
  71. package/dist/updater.d.ts.map +1 -0
  72. package/dist/updater.js +50 -0
  73. package/dist/updater.js.map +1 -0
  74. package/dist/version.d.ts +2 -0
  75. package/dist/version.d.ts.map +1 -0
  76. package/dist/version.js +2 -0
  77. package/dist/version.js.map +1 -0
  78. package/dist/web-presenter.d.ts +36 -0
  79. package/dist/web-presenter.d.ts.map +1 -0
  80. package/dist/web-presenter.js +125 -0
  81. package/dist/web-presenter.js.map +1 -0
  82. package/dist/ws-client.d.ts +114 -0
  83. package/dist/ws-client.d.ts.map +1 -0
  84. package/dist/ws-client.js +97 -0
  85. package/dist/ws-client.js.map +1 -0
  86. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -14479,13 +14479,7 @@ function alanMcpConfigFilesForBackend(backendKind, mcpServers, home) {
14479
14479
  var SESSION_RESUME_FAILED_MESSAGE = "Session resume failed. The previous session may have expired or is no longer available. Please start a new session.";
14480
14480
 
14481
14481
  // ../shared/dist/agent/system-prompt.js
14482
- var ALAN_IDENTITY_PROMPT = `You are Alan, the software factory agent built by Supatest in Bangalore. Alan ships software end to end \u2014 plan, write, review, test, deploy.
14483
-
14484
- Do not present yourself as a runtime model, CLI, or backend vendor as your primary identity \u2014 those are runtime details. Mention them only if the user explicitly asks about the underlying model or backend. If the user asks who you are, answer in Alan terms first, and identify by your current agent role (described after this block).
14485
-
14486
- ## Voice
14487
-
14488
- Direct. Structured. Concise. No hype, no "Great question!", no tool-brand roleplay. Speak like a teammate, not a data dump. Precise and decisive when working a task; listen, think, then act.
14482
+ var ALAN_IDENTITY_PROMPT = `You are Alan, the software factory agent. Alan ships software end to end \u2014 plan, write, review, test, deploy.
14489
14483
 
14490
14484
  ## How Alan is structured
14491
14485
 
@@ -14503,10 +14497,6 @@ Team
14503
14497
  - **Document vs Artifact**: Document = standalone page in a team. Artifact = output from work on a task + conversation, tied to that context. Belongs to a task \u2192 artifact. Doesn't \u2192 document.
14504
14498
  - **IDs are not interchangeable**: teamId, taskId, conversationId identify different things. Substituting one for another orphans records. Use the IDs the system provides; never invent or guess.
14505
14499
 
14506
- ## Structure over chat
14507
-
14508
- Deliverables persist as Alan artifacts and documents \u2014 not as chat. Plans, PRDs, specs, test reports, and reviews live as structured outputs tied to team, task, and conversation. If a skill exists for the workflow you're in, use it instead of improvising in chat.
14509
-
14510
14500
  ## Universal teammate principles
14511
14501
 
14512
14502
  - Listen first. Don't jump to solutions. One question at a time, not a list.
@@ -14545,7 +14535,6 @@ Examples: "where is X configured", "which file handles Y", "how does Z work", "f
14545
14535
  - Do NOT start with \`grep\`, \`rg\`, \`find\`, or broad Bash filesystem search for codebase discovery or implementation-answer questions when \`search_code_context\` is available.
14546
14536
  - Do NOT repeat or rephrase index searches after the first result identifies useful paths. Continue locally instead.
14547
14537
  - Do NOT claim current behavior from an index excerpt without reading the cited local file.
14548
- - Do NOT use graphify or local graph tools for the initial semantic "where is this implemented" pass.
14549
14538
 
14550
14539
  ### Local follow-ups
14551
14540
  - Import/caller structure: use local \`code_graph_query\` or narrow symbol search after reading the cited files.
@@ -15296,6 +15285,8 @@ var import_path5 = require("path");
15296
15285
  var import_fs6 = require("fs");
15297
15286
  var import_os6 = require("os");
15298
15287
  var import_path6 = require("path");
15288
+ var import_readline2 = require("readline");
15289
+ var import_url2 = require("url");
15299
15290
  var import_fs7 = require("fs");
15300
15291
  var import_os7 = require("os");
15301
15292
  var import_path7 = require("path");
@@ -15333,7 +15324,7 @@ function writeFileSafe(path, content) {
15333
15324
  (0, import_fs2.rmSync)(pendingPath, { force: true });
15334
15325
  }
15335
15326
  }
15336
- function acquireConfigLock(path) {
15327
+ async function acquireConfigLock(path) {
15337
15328
  (0, import_fs2.mkdirSync)((0, import_path2.dirname)(path), { recursive: true });
15338
15329
  const lockPath = `${path}.alan-lock`;
15339
15330
  const startedAt = Date.now();
@@ -15355,7 +15346,7 @@ function acquireConfigLock(path) {
15355
15346
  if (Date.now() - startedAt >= 2e3) {
15356
15347
  throw new Error(`Alan could not safely update ${path}`);
15357
15348
  }
15358
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 20);
15349
+ await new Promise((resolve22) => setTimeout(resolve22, 20));
15359
15350
  }
15360
15351
  }
15361
15352
  }
@@ -15619,13 +15610,13 @@ function buildJsonTargets(home) {
15619
15610
  }
15620
15611
  ];
15621
15612
  }
15622
- function syncAlanMcpSessionHeaders(input) {
15613
+ async function syncAlanMcpSessionHeaders(input) {
15623
15614
  const home = input.homeDir ?? (0, import_os2.homedir)();
15624
15615
  const sessionHeaders = buildSessionHeaders(input);
15625
15616
  const written = [];
15626
15617
  for (const target of buildCodexTomlTargets(home)) {
15627
15618
  if (!(0, import_fs2.existsSync)(target.guardDir)) continue;
15628
- const releaseLock = acquireConfigLock(target.configPath);
15619
+ const releaseLock = await acquireConfigLock(target.configPath);
15629
15620
  try {
15630
15621
  if (patchCodexTomlMcpTarget(target, sessionHeaders)) {
15631
15622
  written.push(target.cli);
@@ -15637,7 +15628,7 @@ function syncAlanMcpSessionHeaders(input) {
15637
15628
  }
15638
15629
  for (const target of buildJsonTargets(home)) {
15639
15630
  if (!(0, import_fs2.existsSync)(target.guardDir)) continue;
15640
- const releaseLock = acquireConfigLock(target.configPath);
15631
+ const releaseLock = await acquireConfigLock(target.configPath);
15641
15632
  try {
15642
15633
  if (patchJsonMcpTarget(target, sessionHeaders)) {
15643
15634
  written.push(target.cli);
@@ -19157,7 +19148,7 @@ function createGrokAcpDriver(imageFiles) {
19157
19148
  }
19158
19149
  newSession(context);
19159
19150
  }
19160
- function sendPrompt(context, state) {
19151
+ function sendPrompt2(context, state) {
19161
19152
  const text = buildPromptWithImagePathReferences(context, imageFiles);
19162
19153
  isPromptActive = true;
19163
19154
  promptId = request("session/prompt", {
@@ -19200,7 +19191,7 @@ function createGrokAcpDriver(imageFiles) {
19200
19191
  const sessionId = asString(result?.sessionId);
19201
19192
  if (sessionId) state.runtimeSessionId = sessionId;
19202
19193
  state.iterations = Math.max(state.iterations, 1);
19203
- sendPrompt(context, state);
19194
+ sendPrompt2(context, state);
19204
19195
  return;
19205
19196
  }
19206
19197
  if (message.id === promptId) {
@@ -19546,23 +19537,59 @@ function createKimiCliBackend(command = "kimi-cli", defaultArgs = []) {
19546
19537
  }
19547
19538
  };
19548
19539
  }
19540
+ var AUTONOMOUS_OPENCODE_CONFIG_CONTENT = JSON.stringify({
19541
+ $schema: "https://opencode.ai/config.json",
19542
+ permission: "allow"
19543
+ });
19544
+ function buildAutonomousOpenCodeConfigContent(existing) {
19545
+ if (!existing?.trim()) return AUTONOMOUS_OPENCODE_CONFIG_CONTENT;
19546
+ try {
19547
+ const parsed = JSON.parse(existing);
19548
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
19549
+ return JSON.stringify({
19550
+ ...parsed,
19551
+ permission: "allow"
19552
+ });
19553
+ }
19554
+ } catch {
19555
+ }
19556
+ return AUTONOMOUS_OPENCODE_CONFIG_CONTENT;
19557
+ }
19558
+ function withAutonomousOpenCodePermissions(context) {
19559
+ if (shouldUseReadOnlyRuntimePermissions(context.config)) return context;
19560
+ return {
19561
+ ...context,
19562
+ env: {
19563
+ ...context.env,
19564
+ OPENCODE_CONFIG_CONTENT: buildAutonomousOpenCodeConfigContent(
19565
+ context.env.OPENCODE_CONFIG_CONTENT
19566
+ )
19567
+ }
19568
+ };
19569
+ }
19549
19570
  function getRecord(value2) {
19550
19571
  return typeof value2 === "object" && value2 !== null ? value2 : null;
19551
19572
  }
19552
19573
  function getString(value2) {
19553
19574
  return typeof value2 === "string" ? value2.trim() : "";
19554
19575
  }
19555
- function getOpenCodeToolError(parsed, part) {
19556
- const state = getRecord(part?.state) ?? getRecord(parsed.state);
19557
- const error = getRecord(part?.error) ?? getRecord(parsed.error) ?? getRecord(state?.error);
19558
- return getString(part?.error) || getString(state?.error) || getString(error?.message) || getString(parsed.error);
19576
+ function getOpenCodeToolError(part) {
19577
+ const state = getRecord(part.state);
19578
+ const error = getRecord(part.error) ?? getRecord(state?.error);
19579
+ return getString(part.error) || getString(state?.error) || getString(error?.message);
19559
19580
  }
19560
- function isOpenCodeToolError(parsed, part) {
19561
- const state = getRecord(part?.state) ?? getRecord(parsed.state);
19562
- return part?.is_error === true || part?.isError === true || state?.status === "error" || Boolean(getOpenCodeToolError(parsed, part));
19581
+ function isOpenCodeToolError(part) {
19582
+ const state = getRecord(part.state);
19583
+ return part.is_error === true || part.isError === true || state?.status === "error" || Boolean(getOpenCodeToolError(part));
19563
19584
  }
19564
- function getOpenCodePartId(parsed, part) {
19565
- return getString(part?.id) || getString(parsed.id) || getString(parsed.partID);
19585
+ function getOpenCodePartId(part) {
19586
+ return getString(part.id) || getString(part.partID);
19587
+ }
19588
+ function getOpenCodeToolId(part) {
19589
+ return getString(part.callID) || getString(part.id) || `opencode-tool-${Date.now()}`;
19590
+ }
19591
+ function getOpenCodeToolName(part) {
19592
+ return getString(part.tool) || (typeof part.name === "string" ? part.name.trim() : "") || "Tool";
19566
19593
  }
19567
19594
  function resolveOpenCodePartSnapshotDelta(text, partId, snapshots) {
19568
19595
  if (!partId) return text;
@@ -19579,93 +19606,92 @@ function resolveOpenCodeReasoningDelta(text, partId, state) {
19579
19606
  state.opencodeReasoningByPartId ??= /* @__PURE__ */ new Map();
19580
19607
  return resolveOpenCodePartSnapshotDelta(text, partId, state.opencodeReasoningByPartId);
19581
19608
  }
19582
- function handleOpencodeStructuredEvent(parsed, context, state) {
19609
+ var TASK_TOOL_NAME = "task";
19610
+ var TASK_RESULT_TAG_PATTERN = /<task_result>\n?([\s\S]*?)\n?<\/task_result>/;
19611
+ var TASK_ERROR_TAG_PATTERN = /<task_error>\n?([\s\S]*?)\n?<\/task_error>/;
19612
+ function unwrapOpenCodeTaskOutput(output) {
19613
+ const result = output.match(TASK_RESULT_TAG_PATTERN);
19614
+ if (result?.[1] !== void 0) return result[1].trim();
19615
+ const error = output.match(TASK_ERROR_TAG_PATTERN);
19616
+ if (error?.[1] !== void 0) return error[1].trim();
19617
+ return output;
19618
+ }
19619
+ function emitOpenCodeToolPart(presenter, state, part, opts) {
19620
+ const stateRecord = getRecord(part.state);
19621
+ const status = getString(stateRecord?.status);
19622
+ if (status === "pending") return;
19623
+ const toolName = getOpenCodeToolName(part);
19624
+ const toolId = getOpenCodeToolId(part);
19625
+ const isTask = toolName.toLowerCase() === TASK_TOOL_NAME;
19626
+ const parentToolUseId = opts.parentToolUseId ?? void 0;
19627
+ state.opencodeToolUseIds ??= /* @__PURE__ */ new Set();
19628
+ if (!state.opencodeToolUseIds.has(toolId)) {
19629
+ state.opencodeToolUseIds.add(toolId);
19630
+ void presenter.onToolUse(toolName, stateRecord?.input ?? part, toolId, parentToolUseId);
19631
+ if (isTask) registerBackgroundLauncher(state, toolId);
19632
+ }
19633
+ if (status !== "completed" && status !== "error") return;
19634
+ const toolError = isOpenCodeToolError(part);
19635
+ const rawOutput = getString(stateRecord?.output) || getString(stateRecord?.error) || JSON.stringify(stateRecord ?? part);
19636
+ const output = isTask ? unwrapOpenCodeTaskOutput(rawOutput) : rawOutput;
19637
+ void presenter.onToolResult?.(toolId, output, toolError, parentToolUseId);
19638
+ if (isTask) clearBackgroundTaskIds(state, [toolId]);
19639
+ if (toolError && !state.error) {
19640
+ const message = getOpenCodeToolError(part) || "OpenCode tool call failed.";
19641
+ state.error = message;
19642
+ void presenter.onError(message);
19643
+ }
19644
+ }
19645
+ function mapOpencodePart(part, context, state, opts = {}) {
19583
19646
  const presenter = context.presenter;
19584
- const type = typeof parsed.type === "string" ? parsed.type : "";
19585
- if (!type) return false;
19586
- const sessionID = typeof parsed.sessionID === "string" ? parsed.sessionID : void 0;
19587
- if (sessionID) state.runtimeSessionId = sessionID;
19588
- const part = typeof parsed.part === "object" && parsed.part !== null ? parsed.part : null;
19647
+ const type = getString(part.type);
19589
19648
  switch (type) {
19590
- case "step_start": {
19649
+ case "step-start": {
19591
19650
  state.iterations += 1;
19592
19651
  void presenter.onLog(`[opencode] Step ${state.iterations} started`);
19593
19652
  return true;
19594
19653
  }
19595
19654
  case "text": {
19596
- const text = part && typeof part.text === "string" ? part.text : "";
19655
+ const text = typeof part.text === "string" ? part.text : "";
19597
19656
  if (text) {
19598
- const delta = resolveOpenCodeTextDelta(text, getOpenCodePartId(parsed, part), state);
19599
- if (!delta) return true;
19600
- state.summary += delta;
19601
- void presenter.onAssistantText(delta);
19657
+ const delta = resolveOpenCodeTextDelta(text, getOpenCodePartId(part), state);
19658
+ if (delta) {
19659
+ state.summary += delta;
19660
+ void presenter.onAssistantText(delta);
19661
+ }
19602
19662
  }
19603
19663
  return true;
19604
19664
  }
19665
+ // Real Part schema only ever uses "reasoning"; "thinking" is a defensive alias in
19666
+ // case a CLI dialect ever names the part itself that way.
19605
19667
  case "reasoning":
19606
19668
  case "thinking": {
19607
- const text = part && typeof part.text === "string" ? part.text : "";
19669
+ const text = typeof part.text === "string" ? part.text : "";
19608
19670
  if (!text) return true;
19609
- const delta = resolveOpenCodeReasoningDelta(text, getOpenCodePartId(parsed, part), state);
19671
+ const delta = resolveOpenCodeReasoningDelta(text, getOpenCodePartId(part), state);
19610
19672
  if (!delta) return true;
19611
19673
  state.iterations = Math.max(state.iterations, 1);
19612
19674
  void presenter.onThinking(delta);
19613
19675
  return true;
19614
19676
  }
19615
- case "tool_use":
19616
- case "tool.execute": {
19617
- const toolName = getString(parsed.tool) || (part && typeof part.name === "string" ? part.name : "") || getString(parsed.name) || (part && typeof part.tool === "string" ? part.tool : "") || "Tool";
19618
- const toolId = part && typeof part.id === "string" ? part.id : `opencode-tool-${Date.now()}`;
19619
- void presenter.onToolUse(toolName, part ?? {}, toolId);
19620
- return true;
19621
- }
19622
19677
  case "tool": {
19623
- const stateRecord = getRecord(parsed.state);
19624
- const toolName = getString(parsed.tool) || (part && typeof part.name === "string" ? part.name : "") || getString(parsed.name) || (part && typeof part.tool === "string" ? part.tool : "") || "Tool";
19625
- const toolId = getString(parsed.id) || getString(parsed.callID) || `opencode-tool-${Date.now()}`;
19626
- state.opencodeToolUseIds ??= /* @__PURE__ */ new Set();
19627
- if (!state.opencodeToolUseIds.has(toolId)) {
19628
- state.opencodeToolUseIds.add(toolId);
19629
- void presenter.onToolUse(toolName, stateRecord?.input ?? part ?? parsed, toolId);
19630
- }
19631
- const status = getString(stateRecord?.status);
19632
- if (status === "completed" || status === "error") {
19633
- const toolError = isOpenCodeToolError(parsed, part);
19634
- const output = getString(stateRecord?.output) || getString(stateRecord?.error) || JSON.stringify(stateRecord ?? part ?? parsed);
19635
- void presenter.onToolResult?.(toolId, output, toolError);
19636
- if (toolError && !state.error) {
19637
- const message = getOpenCodeToolError(parsed, part) || "OpenCode tool call failed.";
19638
- state.error = message;
19639
- void presenter.onError(message);
19640
- }
19641
- }
19678
+ emitOpenCodeToolPart(presenter, state, part, opts);
19642
19679
  return true;
19643
19680
  }
19644
- case "tool_result":
19645
- case "tool.result": {
19646
- const toolId = part && typeof part.id === "string" ? part.id : `opencode-tool-${Date.now()}`;
19647
- const output = part && typeof part.output === "string" ? part.output : JSON.stringify(part);
19648
- const toolError = isOpenCodeToolError(parsed, part);
19649
- void presenter.onToolResult?.(toolId, output, toolError);
19650
- if (toolError && !state.error) {
19651
- const message = getOpenCodeToolError(parsed, part) || "OpenCode tool call failed.";
19652
- state.error = message;
19653
- void presenter.onError(message);
19654
- }
19655
- return true;
19656
- }
19657
- case "step_finish": {
19658
- if (part && typeof part.tokens === "object" && part.tokens !== null) {
19659
- const tokens = part.tokens;
19681
+ case "step-finish": {
19682
+ const tokens = getRecord(part.tokens);
19683
+ if (tokens) {
19684
+ const cache2 = getRecord(tokens.cache);
19685
+ const cost = typeof part.cost === "number" ? part.cost : 0;
19660
19686
  state.usage = {
19661
19687
  model: typeof context.config.selectedModel === "string" && context.config.selectedModel.trim() || "opencode",
19662
19688
  numTurns: Math.max(state.iterations, 1),
19663
19689
  durationMs: 0,
19664
- inputTokens: tokens.input ?? 0,
19665
- outputTokens: tokens.output ?? 0,
19666
- cacheReadTokens: tokens.cache_read ?? 0,
19667
- cacheCreationTokens: tokens.cache_creation ?? 0,
19668
- costUsd: 0
19690
+ inputTokens: Number(tokens.input) || 0,
19691
+ outputTokens: Number(tokens.output) || 0,
19692
+ cacheReadTokens: Number(cache2?.read) || 0,
19693
+ cacheCreationTokens: Number(cache2?.write) || 0,
19694
+ costUsd: cost
19669
19695
  };
19670
19696
  void presenter.onUsageUpdate?.({
19671
19697
  model: state.usage.model,
@@ -19677,6 +19703,57 @@ function handleOpencodeStructuredEvent(parsed, context, state) {
19677
19703
  }
19678
19704
  return true;
19679
19705
  }
19706
+ default:
19707
+ return false;
19708
+ }
19709
+ }
19710
+ function normalizeOpenCodePart(parsed, part, partType) {
19711
+ return { ...parsed, ...part ?? {}, type: partType };
19712
+ }
19713
+ function handleOpencodeStructuredEvent(parsed, context, state) {
19714
+ const presenter = context.presenter;
19715
+ const type = typeof parsed.type === "string" ? parsed.type : "";
19716
+ if (!type) return false;
19717
+ const sessionID = typeof parsed.sessionID === "string" ? parsed.sessionID : void 0;
19718
+ if (sessionID) state.runtimeSessionId = sessionID;
19719
+ const part = typeof parsed.part === "object" && parsed.part !== null ? parsed.part : null;
19720
+ switch (type) {
19721
+ case "step_start":
19722
+ return mapOpencodePart(normalizeOpenCodePart(parsed, part, "step-start"), context, state);
19723
+ case "text":
19724
+ return mapOpencodePart(normalizeOpenCodePart(parsed, part, "text"), context, state);
19725
+ case "reasoning":
19726
+ case "thinking":
19727
+ return mapOpencodePart(normalizeOpenCodePart(parsed, part, "reasoning"), context, state);
19728
+ // Real event: `opencode run --format json` emits exactly this shape — a single
19729
+ // terminal event per tool call, carrying part.state.{input,output|error}. See
19730
+ // notes/opencode-cli-reverse-engineering.md for the live-captured schema.
19731
+ case "tool_use":
19732
+ case "tool.execute":
19733
+ case "tool":
19734
+ return mapOpencodePart(normalizeOpenCodePart(parsed, part, "tool"), context, state);
19735
+ case "step_finish":
19736
+ return mapOpencodePart(normalizeOpenCodePart(parsed, part, "step-finish"), context, state);
19737
+ // Defensive fallback only: no observed OpenCode version emits a separate
19738
+ // tool_result/tool.result event in `--format json` mode (tool state always
19739
+ // arrives via `tool_use` above). Kept in case a future/older CLI build splits
19740
+ // the terminal tool event into a distinct result message. Deliberately NOT routed
19741
+ // through mapOpencodePart — this shape has no part.state wrapper at all (output
19742
+ // sits directly on the part), so it isn't a real Part-schema shape.
19743
+ case "tool_result":
19744
+ case "tool.result": {
19745
+ const toolId = getString(part?.callID) || getString(parsed.callID) || getString(part?.id) || getString(parsed.id) || `opencode-tool-${Date.now()}`;
19746
+ const output = part && typeof part.output === "string" ? part.output : JSON.stringify(part);
19747
+ const stateRecord = getRecord(part?.state) ?? getRecord(parsed.state);
19748
+ const toolError = part?.is_error === true || part?.isError === true || stateRecord?.status === "error" || Boolean(getString(part?.error) || getString(stateRecord?.error));
19749
+ void presenter.onToolResult?.(toolId, output, toolError);
19750
+ if (toolError && !state.error) {
19751
+ const message = getString(part?.error) || getString(stateRecord?.error) || "OpenCode tool call failed.";
19752
+ state.error = message;
19753
+ void presenter.onError(message);
19754
+ }
19755
+ return true;
19756
+ }
19680
19757
  case "session.error":
19681
19758
  case "error": {
19682
19759
  const errObj = typeof parsed.error === "object" && parsed.error !== null ? parsed.error : null;
@@ -19701,35 +19778,10 @@ var parseOpencodeStructuredLine = createStructuredLineParser(
19701
19778
  "opencode_cli",
19702
19779
  handleOpencodeStructuredEvent
19703
19780
  );
19704
- var AUTONOMOUS_OPENCODE_CONFIG_CONTENT = JSON.stringify({
19705
- $schema: "https://opencode.ai/config.json",
19706
- permission: "allow"
19707
- });
19708
- function buildAutonomousOpenCodeConfigContent(existing) {
19709
- if (!existing?.trim()) return AUTONOMOUS_OPENCODE_CONFIG_CONTENT;
19710
- try {
19711
- const parsed = JSON.parse(existing);
19712
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
19713
- return JSON.stringify({
19714
- ...parsed,
19715
- permission: "allow"
19716
- });
19717
- }
19718
- } catch {
19719
- }
19720
- return AUTONOMOUS_OPENCODE_CONFIG_CONTENT;
19721
- }
19722
- function withAutonomousOpenCodePermissions(context) {
19723
- if (shouldUseReadOnlyRuntimePermissions(context.config)) return context;
19724
- return {
19725
- ...context,
19726
- env: {
19727
- ...context.env,
19728
- OPENCODE_CONFIG_CONTENT: buildAutonomousOpenCodeConfigContent(
19729
- context.env.OPENCODE_CONFIG_CONTENT
19730
- )
19731
- }
19732
- };
19781
+ function buildOpencodeEffortArgs(selectedEffortLevel) {
19782
+ const trimmed = selectedEffortLevel?.trim().toLowerCase();
19783
+ if (!trimmed || !isEffortLevel(trimmed)) return [];
19784
+ return ["--variant", trimmed];
19733
19785
  }
19734
19786
  function createOpencodeCliBackend(command = "opencode", defaultArgs = []) {
19735
19787
  return {
@@ -19752,7 +19804,7 @@ function createOpencodeCliBackend(command = "opencode", defaultArgs = []) {
19752
19804
  command,
19753
19805
  args: [],
19754
19806
  buildArgs: (ctx, prompt) => {
19755
- const args = ["run", "--format", "json"];
19807
+ const args = ["run", "--format", "json", "--thinking"];
19756
19808
  if (!shouldUseReadOnlyRuntimePermissions(ctx.config)) {
19757
19809
  args.push("--dangerously-skip-permissions");
19758
19810
  }
@@ -19760,6 +19812,7 @@ function createOpencodeCliBackend(command = "opencode", defaultArgs = []) {
19760
19812
  if (resumeId) args.push("--session", resumeId);
19761
19813
  const model = ctx.config.selectedModel?.trim();
19762
19814
  if (model?.includes("/")) args.push("--model", model);
19815
+ args.push(...buildOpencodeEffortArgs(ctx.config.selectedEffortLevel));
19763
19816
  const attachmentPaths = [...imagePaths, ...filePaths];
19764
19817
  const fileArgs = attachmentPaths.flatMap((p) => ["--file", p]);
19765
19818
  if (fileArgs.length > 0) {
@@ -19780,6 +19833,395 @@ function createOpencodeCliBackend(command = "opencode", defaultArgs = []) {
19780
19833
  }
19781
19834
  };
19782
19835
  }
19836
+ function parseSseEventBlock(block) {
19837
+ const dataLines = block.split("\n").map((line) => line.endsWith("\r") ? line.slice(0, -1) : line).filter((line) => line.startsWith("data:")).map((line) => line.slice("data:".length).replace(/^ /, ""));
19838
+ if (dataLines.length === 0) return null;
19839
+ try {
19840
+ const parsed = JSON.parse(dataLines.join("\n"));
19841
+ if (parsed && typeof parsed === "object" && typeof parsed.type === "string") {
19842
+ const record = parsed;
19843
+ return {
19844
+ id: typeof record.id === "string" ? record.id : void 0,
19845
+ type: record.type,
19846
+ properties: typeof record.properties === "object" && record.properties !== null ? record.properties : {}
19847
+ };
19848
+ }
19849
+ } catch {
19850
+ }
19851
+ return null;
19852
+ }
19853
+ async function connectOpenCodeSse(url2, signal) {
19854
+ const response = await fetch(url2, {
19855
+ signal,
19856
+ headers: { Accept: "text/event-stream" }
19857
+ });
19858
+ if (!response.ok || !response.body) {
19859
+ throw new Error(`OpenCode SSE connection failed: HTTP ${response.status}`);
19860
+ }
19861
+ const reader = response.body.getReader();
19862
+ const decoder = new TextDecoder();
19863
+ let closed = false;
19864
+ async function* generate() {
19865
+ let buffer = "";
19866
+ try {
19867
+ while (!closed) {
19868
+ const { value: value2, done } = await reader.read();
19869
+ if (done) return;
19870
+ buffer += decoder.decode(value2, { stream: true });
19871
+ let separatorIndex = buffer.indexOf("\n\n");
19872
+ while (separatorIndex !== -1) {
19873
+ const block = buffer.slice(0, separatorIndex);
19874
+ buffer = buffer.slice(separatorIndex + 2);
19875
+ const event = parseSseEventBlock(block);
19876
+ if (event) yield event;
19877
+ separatorIndex = buffer.indexOf("\n\n");
19878
+ }
19879
+ }
19880
+ } finally {
19881
+ try {
19882
+ reader.releaseLock();
19883
+ } catch {
19884
+ }
19885
+ }
19886
+ }
19887
+ return {
19888
+ events: generate(),
19889
+ close: () => {
19890
+ if (closed) return;
19891
+ closed = true;
19892
+ reader.cancel().catch(() => {
19893
+ });
19894
+ }
19895
+ };
19896
+ }
19897
+ var SERVER_LISTENING_PATTERN = /listening on (https?:\/\/\S+)/i;
19898
+ var STARTUP_TIMEOUT_MS = 2e4;
19899
+ var HEALTH_POLL_INTERVAL_MS = 200;
19900
+ var HEALTH_POLL_MAX_ATTEMPTS = 50;
19901
+ function startOpenCodeServer(command, context) {
19902
+ const child = spawnCli(command, ["serve", "--port", "0", "--hostname", "127.0.0.1"], context);
19903
+ const ready = new Promise((resolve22, reject) => {
19904
+ let settled = false;
19905
+ const timeout = setTimeout(() => {
19906
+ if (settled) return;
19907
+ settled = true;
19908
+ reject(
19909
+ new Error(`opencode serve did not report a listening URL within ${STARTUP_TIMEOUT_MS}ms`)
19910
+ );
19911
+ }, STARTUP_TIMEOUT_MS);
19912
+ const stdoutRl = (0, import_readline2.createInterface)({ input: child.stdout });
19913
+ const stderrRl = (0, import_readline2.createInterface)({ input: child.stderr });
19914
+ const stderrLines = [];
19915
+ const checkLine = (line) => {
19916
+ if (settled) return;
19917
+ const match = line.match(SERVER_LISTENING_PATTERN);
19918
+ if (!match) return;
19919
+ settled = true;
19920
+ clearTimeout(timeout);
19921
+ resolve22({ baseUrl: match[1].replace(/\/$/, "") });
19922
+ };
19923
+ stdoutRl.on("line", checkLine);
19924
+ stderrRl.on("line", (line) => {
19925
+ stderrLines.push(line);
19926
+ checkLine(line);
19927
+ });
19928
+ child.once("error", (error) => {
19929
+ if (settled) return;
19930
+ settled = true;
19931
+ clearTimeout(timeout);
19932
+ reject(error);
19933
+ });
19934
+ child.once("exit", (code) => {
19935
+ if (settled) return;
19936
+ settled = true;
19937
+ clearTimeout(timeout);
19938
+ reject(
19939
+ new Error(
19940
+ `opencode serve exited (code ${code}) before reporting a listening URL. stderr: ${stderrLines.slice(-5).join("\n")}`
19941
+ )
19942
+ );
19943
+ });
19944
+ });
19945
+ return { child, ready };
19946
+ }
19947
+ async function waitForHealthy(baseUrl, signal) {
19948
+ for (let attempt = 0; attempt < HEALTH_POLL_MAX_ATTEMPTS; attempt++) {
19949
+ if (signal.aborted)
19950
+ throw new Error("Aborted while waiting for opencode server to become healthy");
19951
+ try {
19952
+ const response = await fetch(`${baseUrl}/global/health`, { signal });
19953
+ if (response.ok) {
19954
+ const body = await response.json().catch(() => null);
19955
+ if (body?.healthy) return;
19956
+ }
19957
+ } catch {
19958
+ }
19959
+ await new Promise((resolve22) => setTimeout(resolve22, HEALTH_POLL_INTERVAL_MS));
19960
+ }
19961
+ throw new Error(`opencode server at ${baseUrl} did not become healthy in time`);
19962
+ }
19963
+ var NON_INTERACTIVE_DENY_RULES = [
19964
+ { permission: "question", pattern: "*", action: "deny" },
19965
+ { permission: "plan_enter", pattern: "*", action: "deny" },
19966
+ { permission: "plan_exit", pattern: "*", action: "deny" }
19967
+ ];
19968
+ async function resolveSession(baseUrl, context, signal) {
19969
+ const resumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
19970
+ if (resumeId) {
19971
+ const existing = await fetch(`${baseUrl}/session/${encodeURIComponent(resumeId)}`, {
19972
+ signal
19973
+ }).catch(() => null);
19974
+ if (existing?.ok) return { id: resumeId };
19975
+ }
19976
+ const title = context.promptText.slice(0, 50) + (context.promptText.length > 50 ? "..." : "");
19977
+ const response = await fetch(`${baseUrl}/session`, {
19978
+ method: "POST",
19979
+ signal,
19980
+ headers: { "Content-Type": "application/json" },
19981
+ body: JSON.stringify({ title, permission: NON_INTERACTIVE_DENY_RULES })
19982
+ });
19983
+ if (!response.ok) {
19984
+ throw new Error(`Failed to create OpenCode session: HTTP ${response.status}`);
19985
+ }
19986
+ const info = await response.json();
19987
+ if (!info.id) throw new Error("OpenCode session create response had no id");
19988
+ return { id: info.id };
19989
+ }
19990
+ function buildPromptParts(promptText, imageFiles, attachmentFiles) {
19991
+ const attachments = [...imageFiles, ...attachmentFiles].map(
19992
+ (file) => ({
19993
+ type: "file",
19994
+ mime: file.mimeType,
19995
+ filename: file.filename,
19996
+ url: (0, import_url2.pathToFileURL)(file.path).href
19997
+ })
19998
+ );
19999
+ return [...attachments, { type: "text", text: promptText }];
20000
+ }
20001
+ async function sendPrompt(baseUrl, sessionId, context, parts2, signal) {
20002
+ const model = context.config.selectedModel?.trim();
20003
+ const modelBody = model && model.includes("/") ? (() => {
20004
+ const slashIndex = model.indexOf("/");
20005
+ return { providerID: model.slice(0, slashIndex), modelID: model.slice(slashIndex + 1) };
20006
+ })() : void 0;
20007
+ const variant = context.config.selectedEffortLevel?.trim().toLowerCase();
20008
+ const response = await fetch(`${baseUrl}/session/${encodeURIComponent(sessionId)}/message`, {
20009
+ method: "POST",
20010
+ signal,
20011
+ headers: { "Content-Type": "application/json" },
20012
+ body: JSON.stringify({
20013
+ parts: parts2,
20014
+ ...modelBody ? { model: modelBody } : {},
20015
+ ...variant && isEffortLevel(variant) ? { variant } : {}
20016
+ })
20017
+ });
20018
+ if (!response.ok) {
20019
+ const text = await response.text().catch(() => "");
20020
+ throw new Error(`OpenCode prompt request failed: HTTP ${response.status} ${text}`.trim());
20021
+ }
20022
+ }
20023
+ async function replyToPermission(baseUrl, requestId, reply, signal) {
20024
+ await fetch(`${baseUrl}/permission/${encodeURIComponent(requestId)}/reply`, {
20025
+ method: "POST",
20026
+ signal,
20027
+ headers: { "Content-Type": "application/json" },
20028
+ body: JSON.stringify({ reply })
20029
+ }).catch((error) => {
20030
+ console.warn("[opencode_serve] Failed to reply to permission request", requestId, error);
20031
+ });
20032
+ }
20033
+ function buildAgentResult(state, sessionId, aborted2) {
20034
+ if (aborted2) {
20035
+ return {
20036
+ success: false,
20037
+ summary: "Interrupted by user",
20038
+ filesModified: [],
20039
+ planFilesCreated: [],
20040
+ iterations: Math.max(state.iterations, 1),
20041
+ error: "Interrupted by user",
20042
+ providerSessionId: sessionId,
20043
+ runtimeSessionId: sessionId,
20044
+ backendKind: "opencode_serve",
20045
+ supportTier: "structured",
20046
+ usage: state.usage
20047
+ };
20048
+ }
20049
+ const failed = Boolean(state.error?.trim());
20050
+ const summary = state.summary.trim() || state.error?.trim() || (failed ? "Task failed" : "Task completed");
20051
+ return {
20052
+ success: !failed,
20053
+ summary,
20054
+ filesModified: [],
20055
+ planFilesCreated: [],
20056
+ iterations: Math.max(state.iterations, 1),
20057
+ ...state.error?.trim() ? { error: state.error.trim() } : {},
20058
+ providerSessionId: sessionId,
20059
+ runtimeSessionId: sessionId,
20060
+ backendKind: "opencode_serve",
20061
+ supportTier: "structured",
20062
+ usage: state.usage
20063
+ };
20064
+ }
20065
+ function createOpencodeServeBackend(command = "opencode") {
20066
+ return {
20067
+ kind: "opencode_serve",
20068
+ supportTier: "structured",
20069
+ async run(context) {
20070
+ const { files: imageFiles, cleanup: cleanupImages } = writeImagesToTempFiles(
20071
+ context.config.images,
20072
+ context.cwd
20073
+ );
20074
+ const { files: attachmentFiles, cleanup: cleanupFiles } = writeFilesToTempFiles(
20075
+ context.config.files,
20076
+ context.cwd
20077
+ );
20078
+ const runContext = withAutonomousOpenCodePermissions(context);
20079
+ const readOnly = shouldUseReadOnlyRuntimePermissions(context.config);
20080
+ const { child, ready } = startOpenCodeServer(command, runContext);
20081
+ const state = { process: child, iterations: 0, summary: "" };
20082
+ context.onProcessSpawned?.(child);
20083
+ context.registerLivenessProbe?.(() => ({
20084
+ providerAlive: child.exitCode === null,
20085
+ lastRawOutputAgoMs: typeof state.lastRawOutputAtMs === "number" ? Date.now() - state.lastRawOutputAtMs : null
20086
+ }));
20087
+ let sseClose = null;
20088
+ let sessionId = "";
20089
+ let aborted2 = false;
20090
+ const onAbort = () => {
20091
+ aborted2 = true;
20092
+ sseClose?.();
20093
+ killProcessTree(child.pid, { signal: "SIGTERM", child });
20094
+ };
20095
+ context.abortController.signal.addEventListener("abort", onAbort, { once: true });
20096
+ try {
20097
+ const server = await ready;
20098
+ state.lastRawOutputAtMs = Date.now();
20099
+ await waitForHealthy(server.baseUrl, context.abortController.signal);
20100
+ const session = await resolveSession(
20101
+ server.baseUrl,
20102
+ runContext,
20103
+ context.abortController.signal
20104
+ );
20105
+ sessionId = session.id;
20106
+ state.runtimeSessionId = sessionId;
20107
+ const prompt = context.config.mode === "plan" ? buildPlanModePrefix(buildPromptWithSystem(context.config, context.promptText)) : buildPromptWithSystem(context.config, context.promptText);
20108
+ const parts2 = buildPromptParts(prompt, imageFiles, attachmentFiles);
20109
+ const connection = await connectOpenCodeSse(
20110
+ `${server.baseUrl}/event`,
20111
+ context.abortController.signal
20112
+ );
20113
+ sseClose = connection.close;
20114
+ const promptError = await sendPrompt(
20115
+ server.baseUrl,
20116
+ sessionId,
20117
+ runContext,
20118
+ parts2,
20119
+ context.abortController.signal
20120
+ ).then(() => null).catch((error) => ({
20121
+ message: error instanceof Error ? error.message : String(error)
20122
+ }));
20123
+ if (context.abortController.signal.aborted) {
20124
+ } else if (promptError) {
20125
+ state.error = promptError.message;
20126
+ void context.presenter.onError(promptError.message);
20127
+ } else {
20128
+ for await (const event of connection.events) {
20129
+ if (aborted2) break;
20130
+ const done = handleOpenCodeServeEvent(
20131
+ event,
20132
+ sessionId,
20133
+ runContext,
20134
+ state,
20135
+ readOnly,
20136
+ server.baseUrl
20137
+ );
20138
+ if (done) break;
20139
+ }
20140
+ }
20141
+ return buildAgentResult(state, sessionId, aborted2);
20142
+ } catch (error) {
20143
+ const message = error instanceof Error ? error.message : String(error);
20144
+ if (!aborted2 && !context.abortController.signal.aborted) {
20145
+ console.error("[opencode_serve] Run failed:", message);
20146
+ state.error = message;
20147
+ void context.presenter.onError(message);
20148
+ }
20149
+ return buildAgentResult(
20150
+ state,
20151
+ sessionId,
20152
+ aborted2 || context.abortController.signal.aborted
20153
+ );
20154
+ } finally {
20155
+ context.abortController.signal.removeEventListener("abort", onAbort);
20156
+ sseClose?.();
20157
+ killProcessTree(child.pid, { signal: "SIGTERM", child });
20158
+ cleanupImages();
20159
+ cleanupFiles();
20160
+ }
20161
+ }
20162
+ };
20163
+ }
20164
+ function handleOpenCodeServeEvent(event, sessionId, context, state, readOnly, baseUrl) {
20165
+ const properties = event.properties;
20166
+ const eventSessionId = getString(properties.sessionID);
20167
+ if (eventSessionId && eventSessionId !== sessionId) return false;
20168
+ switch (event.type) {
20169
+ // Live-verified: the SSE bus emits a message.updated (and matching
20170
+ // message.part.updated) for the USER's own message too, not just the
20171
+ // assistant's — track user message ids so their echoed text is never
20172
+ // mistaken for assistant output below.
20173
+ case "message.updated": {
20174
+ const info = getRecord(properties.info);
20175
+ if (getString(info?.role) === "user") {
20176
+ const messageId = getString(info?.id);
20177
+ if (messageId) {
20178
+ state.opencodeUserMessageIds ??= /* @__PURE__ */ new Set();
20179
+ state.opencodeUserMessageIds.add(messageId);
20180
+ }
20181
+ }
20182
+ return false;
20183
+ }
20184
+ case "message.part.updated": {
20185
+ const part = getRecord(properties.part);
20186
+ if (!part) return false;
20187
+ const messageId = getString(part.messageID);
20188
+ if (messageId && state.opencodeUserMessageIds?.has(messageId)) return false;
20189
+ mapOpencodePart(part, context, state);
20190
+ return false;
20191
+ }
20192
+ // Live token-by-token streaming is explicitly out of scope — only the
20193
+ // start/complete snapshots from message.part.updated are consumed.
20194
+ case "message.part.delta":
20195
+ return false;
20196
+ case "permission.asked": {
20197
+ const requestId = getString(properties.id);
20198
+ if (requestId) {
20199
+ void replyToPermission(
20200
+ baseUrl,
20201
+ requestId,
20202
+ readOnly ? "reject" : "once",
20203
+ context.abortController.signal
20204
+ );
20205
+ }
20206
+ return false;
20207
+ }
20208
+ case "session.error": {
20209
+ const errorRecord = getRecord(properties.error);
20210
+ const message = getString(errorRecord?.message) || getString(properties.error) || "OpenCode session error.";
20211
+ if (!state.error) {
20212
+ state.error = message;
20213
+ void context.presenter.onError(message);
20214
+ }
20215
+ return false;
20216
+ }
20217
+ case "session.status": {
20218
+ const status = getRecord(properties.status);
20219
+ return getString(status?.type) === "idle";
20220
+ }
20221
+ default:
20222
+ return false;
20223
+ }
20224
+ }
19783
20225
  function createGenericCliPassthroughBackend(command, defaultArgs = []) {
19784
20226
  return {
19785
20227
  kind: "generic_cli",
@@ -20008,6 +20450,7 @@ function defaultSelectedModelForBackend(backendKind) {
20008
20450
  case "grok_cli":
20009
20451
  return firstCatalogModelId(backendKind) ?? "grok-4.5";
20010
20452
  case "opencode_cli":
20453
+ case "opencode_serve":
20011
20454
  return void 0;
20012
20455
  case "antigravity_cli":
20013
20456
  return void 0;
@@ -20149,7 +20592,7 @@ var BaseMachineAgent = class _BaseMachineAgent {
20149
20592
  };
20150
20593
  const releaseGate = await acquireMcpConfigWriteGate();
20151
20594
  try {
20152
- const writtenClis = syncAlanMcpSessionHeaders({
20595
+ const writtenClis = await syncAlanMcpSessionHeaders({
20153
20596
  conversationId,
20154
20597
  teamId: config.teamId,
20155
20598
  runHeaders: config.alanMcp?.headers,
@@ -20224,6 +20667,7 @@ var BaseMachineAgent = class _BaseMachineAgent {
20224
20667
  case "cursor_agent_cli":
20225
20668
  case "droid_cli":
20226
20669
  case "opencode_cli":
20670
+ case "opencode_serve":
20227
20671
  case "copilot_cli":
20228
20672
  case "supatest_cli":
20229
20673
  case "kimi_cli":
@@ -20291,7 +20735,7 @@ Prefer relative paths and keep your work scoped to this project.`
20291
20735
  const promptText = this.buildPromptText(runtimeConfig);
20292
20736
  const runtimeCommand = runtimeConfig.runtimeCommand || this.runtime.runtimeCommand;
20293
20737
  const runtimeArgs = runtimeConfig.runtimeArgs || this.runtime.runtimeArgs || [];
20294
- const backend = backendKind === "claude_cli" ? createClaudeCliBackend(runtimeCommand || "claude", runtimeArgs) : backendKind === "codex_app_server" ? createCodexRuntimeBackend(runtimeCommand || "codex", runtimeArgs) : backendKind === "copilot_cli" ? createCopilotCliBackend(runtimeCommand || "copilot", runtimeArgs) : backendKind === "cursor_agent_cli" ? createCursorAgentCliBackend(runtimeCommand || "cursor-agent", runtimeArgs) : backendKind === "antigravity_cli" ? createAntigravityCliBackend(runtimeCommand || "agy", runtimeArgs) : backendKind === "droid_cli" ? createDroidCliBackend(runtimeCommand || "droid", runtimeArgs) : backendKind === "opencode_cli" ? createOpencodeCliBackend(runtimeCommand || "opencode", runtimeArgs) : backendKind === "kimi_cli" ? createKimiCliBackend(runtimeCommand || "kimi-cli", runtimeArgs) : backendKind === "grok_cli" ? createGrokCliBackend(runtimeCommand || "grok", runtimeArgs) : backendKind === "supatest_cli" ? createSupatestCliBackend(runtimeCommand || "supatest", runtimeArgs) : createGenericCliPassthroughBackend(
20738
+ const backend = backendKind === "claude_cli" ? createClaudeCliBackend(runtimeCommand || "claude", runtimeArgs) : backendKind === "codex_app_server" ? createCodexRuntimeBackend(runtimeCommand || "codex", runtimeArgs) : backendKind === "copilot_cli" ? createCopilotCliBackend(runtimeCommand || "copilot", runtimeArgs) : backendKind === "cursor_agent_cli" ? createCursorAgentCliBackend(runtimeCommand || "cursor-agent", runtimeArgs) : backendKind === "antigravity_cli" ? createAntigravityCliBackend(runtimeCommand || "agy", runtimeArgs) : backendKind === "droid_cli" ? createDroidCliBackend(runtimeCommand || "droid", runtimeArgs) : backendKind === "opencode_cli" ? createOpencodeCliBackend(runtimeCommand || "opencode", runtimeArgs) : backendKind === "opencode_serve" ? createOpencodeServeBackend(runtimeCommand || "opencode") : backendKind === "kimi_cli" ? createKimiCliBackend(runtimeCommand || "kimi-cli", runtimeArgs) : backendKind === "grok_cli" ? createGrokCliBackend(runtimeCommand || "grok", runtimeArgs) : backendKind === "supatest_cli" ? createSupatestCliBackend(runtimeCommand || "supatest", runtimeArgs) : createGenericCliPassthroughBackend(
20295
20739
  runtimeCommand || "generic-cli",
20296
20740
  runtimeArgs
20297
20741
  );
@@ -21004,13 +21448,20 @@ ${meta.description}`);
21004
21448
  }
21005
21449
  if (conversationId) artifactIds.push(`- conversationId: "${conversationId}"`);
21006
21450
  if (artifactIds.length > 0) {
21451
+ const artifactTools = ["create_plan", "create_document_artifact", "create_test_report"];
21452
+ if (taskId || isWorkflowSession) artifactTools.push("upsert_prototype_version");
21007
21453
  parts2.push(
21008
21454
  [
21009
- "When creating artifacts using MCP tools (create_plan, create_document_artifact, create_test_report, upsert_prototype_version), always pass these IDs:",
21455
+ `When creating artifacts using MCP tools (${artifactTools.join(", ")}), always pass these IDs:`,
21010
21456
  ...artifactIds
21011
21457
  ].join("\n")
21012
21458
  );
21013
21459
  }
21460
+ if (teamId && conversationId && !taskId && !isWorkflowSession) {
21461
+ parts2.push(
21462
+ "This is a standalone chat. For create_plan, create_document_artifact, and create_test_report, omit taskId and workflowId; conversationId is the artifact owner. Do not use create_document unless the user explicitly asks for a standalone team Document."
21463
+ );
21464
+ }
21014
21465
  if (workflowId && conversationId && teamId) {
21015
21466
  parts2.push(
21016
21467
  [
@@ -22274,9 +22725,7 @@ var CODEX_ALAN_TOML_SECTION = "mcp_servers.alan";
22274
22725
  function isCodexAlanTomlSection(section) {
22275
22726
  return section === CODEX_ALAN_TOML_SECTION || section.startsWith(`${CODEX_ALAN_TOML_SECTION}.`);
22276
22727
  }
22277
- function mergeCodexAlanSection(path, existingContent, desiredContent) {
22278
- if (existingContent === null || existingContent.trim() === "") return desiredContent;
22279
- assertValidExistingToml(path, existingContent);
22728
+ function stripCodexAlanSection(existingContent) {
22280
22729
  const lines = existingContent.split("\n");
22281
22730
  const retained = [];
22282
22731
  let insideAlanSection = false;
@@ -22287,7 +22736,12 @@ function mergeCodexAlanSection(path, existingContent, desiredContent) {
22287
22736
  }
22288
22737
  if (!insideAlanSection) retained.push(line);
22289
22738
  }
22290
- const prefix = retained.join("\n").trimEnd();
22739
+ return retained.join("\n").trimEnd();
22740
+ }
22741
+ function mergeCodexAlanSection(path, existingContent, desiredContent) {
22742
+ if (existingContent === null || existingContent.trim() === "") return desiredContent;
22743
+ assertValidExistingToml(path, existingContent);
22744
+ const prefix = stripCodexAlanSection(existingContent);
22291
22745
  const merged = `${prefix}${prefix ? "\n\n" : ""}${desiredContent.trim()}
22292
22746
  `;
22293
22747
  try {
@@ -22373,6 +22827,45 @@ function verifyAlanMcpRegistered(backendKind, home = (0, import_node_os6.homedir
22373
22827
  verification: inspection.verification
22374
22828
  };
22375
22829
  }
22830
+ function removeAlanFromJsonConfig(path, existingContent) {
22831
+ const config = parseJsonObject2(path, existingContent);
22832
+ for (const containerKey of ["mcpServers", "mcp"]) {
22833
+ const container = config[containerKey];
22834
+ if (container && typeof container === "object" && !Array.isArray(container)) {
22835
+ delete container.alan;
22836
+ }
22837
+ }
22838
+ return JSON.stringify(config);
22839
+ }
22840
+ function unregisterAlanMcp(backendKind, home = (0, import_node_os6.homedir)()) {
22841
+ const removedPaths = [];
22842
+ const errors = [];
22843
+ const files = alanMcpConfigFilesForBackend(backendKind, {}, home);
22844
+ for (const file of files) {
22845
+ if (!(0, import_node_fs7.existsSync)(file.path)) continue;
22846
+ const releaseLock = waitForConfigLock(`${file.path}.alan-lock`);
22847
+ try {
22848
+ const current = (0, import_node_fs7.readFileSync)(file.path, "utf8");
22849
+ let next;
22850
+ if (file.path.endsWith(".toml")) {
22851
+ const stripped = stripCodexAlanSection(current);
22852
+ next = stripped ? `${stripped}
22853
+ ` : "";
22854
+ } else {
22855
+ next = removeAlanFromJsonConfig(file.path, current);
22856
+ }
22857
+ if (next !== current) {
22858
+ atomicWriteManagedConfig(file.path, next);
22859
+ removedPaths.push(file.path);
22860
+ }
22861
+ } catch (err) {
22862
+ errors.push(err instanceof Error ? err.message : String(err));
22863
+ } finally {
22864
+ releaseLock();
22865
+ }
22866
+ }
22867
+ return { removedPaths, errors };
22868
+ }
22376
22869
  function isHttpUrl(value2) {
22377
22870
  if (typeof value2 !== "string") return false;
22378
22871
  try {
@@ -22719,6 +23212,88 @@ function extractCodexDiscoveredModelIdsFromDebugJson(value2) {
22719
23212
  }
22720
23213
  return [...new Set(ids)];
22721
23214
  }
23215
+ var OPENCODE_MODEL_ID_LINE_PATTERN = /^[a-z0-9][\w.-]*\/[\w.-]+$/i;
23216
+ var OPENCODE_KNOWN_VARIANT_KEYS = /* @__PURE__ */ new Set([
23217
+ "minimal",
23218
+ "none",
23219
+ "low",
23220
+ "medium",
23221
+ "high",
23222
+ "xhigh",
23223
+ "extra-high",
23224
+ "max",
23225
+ "ultra"
23226
+ ]);
23227
+ function parseOpenCodeVerboseModelOutput(text) {
23228
+ const entries = [];
23229
+ const lines = text.split(/\r?\n/);
23230
+ let i = 0;
23231
+ while (i < lines.length) {
23232
+ const line = (lines[i] ?? "").trim();
23233
+ i += 1;
23234
+ if (!OPENCODE_MODEL_ID_LINE_PATTERN.test(line)) continue;
23235
+ let jsonStart = i;
23236
+ while (jsonStart < lines.length && (lines[jsonStart] ?? "").trim() === "") jsonStart += 1;
23237
+ if (jsonStart >= lines.length || !(lines[jsonStart] ?? "").trim().startsWith("{")) continue;
23238
+ let depth = 0;
23239
+ let inString = false;
23240
+ let escaped = false;
23241
+ let endLineIdx = -1;
23242
+ let buffer = "";
23243
+ for (let j = jsonStart; j < lines.length; j += 1) {
23244
+ const raw = lines[j] ?? "";
23245
+ buffer += (buffer ? "\n" : "") + raw;
23246
+ for (const ch of raw) {
23247
+ if (escaped) {
23248
+ escaped = false;
23249
+ continue;
23250
+ }
23251
+ if (ch === "\\" && inString) {
23252
+ escaped = true;
23253
+ continue;
23254
+ }
23255
+ if (ch === '"') {
23256
+ inString = !inString;
23257
+ continue;
23258
+ }
23259
+ if (inString) continue;
23260
+ if (ch === "{") depth += 1;
23261
+ else if (ch === "}") depth -= 1;
23262
+ }
23263
+ if (depth === 0) {
23264
+ endLineIdx = j;
23265
+ break;
23266
+ }
23267
+ }
23268
+ if (endLineIdx === -1) break;
23269
+ try {
23270
+ const obj = JSON.parse(buffer);
23271
+ entries.push({
23272
+ id: line,
23273
+ reasoning: obj.capabilities?.reasoning === true,
23274
+ variants: obj.variants ? Object.keys(obj.variants) : []
23275
+ });
23276
+ } catch {
23277
+ }
23278
+ i = endLineIdx + 1;
23279
+ }
23280
+ return entries;
23281
+ }
23282
+ function extractOpenCodeDiscoveredModelIdsFromVerboseOutput(text) {
23283
+ const entries = parseOpenCodeVerboseModelOutput(text);
23284
+ const ids = [];
23285
+ for (const entry of entries) {
23286
+ const knownVariants = entry.reasoning ? entry.variants.filter((variant) => OPENCODE_KNOWN_VARIANT_KEYS.has(variant)) : [];
23287
+ if (knownVariants.length === 0) {
23288
+ ids.push(entry.id);
23289
+ continue;
23290
+ }
23291
+ for (const variant of knownVariants) {
23292
+ ids.push(`${entry.id}-${variant}`);
23293
+ }
23294
+ }
23295
+ return [...new Set(ids)];
23296
+ }
22722
23297
  function extractModelIdsFromText(value2) {
22723
23298
  const ansiPattern = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
22724
23299
  const ignoredLinePattern = /^(available models|models|usage|error|flag provided|try |agy\b|warning:)/i;
@@ -22829,12 +23404,24 @@ async function probeJsonOrTextModels(executable, env, argSets, options) {
22829
23404
  return [];
22830
23405
  }
22831
23406
  async function discoverOpenCodeModels(executable, env) {
22832
- const probed = await probeJsonOrTextModels(executable, env, [
23407
+ const verbose = await runCliProbeAsync(
23408
+ executable,
23409
+ withModelProbeEnv(env),
23410
+ ["models", "--verbose"],
23411
+ MODEL_DISCOVERY_TIMEOUT_MS
23412
+ );
23413
+ if (verbose && !verbose.error && verbose.status === 0 && verbose.stdout.trim()) {
23414
+ const withEffort = extractOpenCodeDiscoveredModelIdsFromVerboseOutput(verbose.stdout);
23415
+ if (withEffort.length > 0) return withEffort;
23416
+ }
23417
+ const plain = await probeJsonOrTextModels(executable, env, [["models"]]);
23418
+ if (plain.length > 0) return plain;
23419
+ const legacy = await probeJsonOrTextModels(executable, env, [
22833
23420
  ["models", "--json"],
22834
23421
  ["models", "list", "--json"],
22835
23422
  ["model", "list", "--json"]
22836
23423
  ]);
22837
- return probed.length > 0 ? probed : OPENCODE_ZEN_MODEL_IDS;
23424
+ return legacy.length > 0 ? legacy : OPENCODE_ZEN_MODEL_IDS;
22838
23425
  }
22839
23426
  async function discoverAntigravityModels(executable, env) {
22840
23427
  return probeJsonOrTextModels(executable, env, [["models"]], {
@@ -23080,7 +23667,7 @@ var RunStartGate = class {
23080
23667
  };
23081
23668
 
23082
23669
  // src/version.ts
23083
- var AGENT_VERSION = "0.1.43";
23670
+ var AGENT_VERSION = "0.1.45";
23084
23671
 
23085
23672
  // src/workspace-relocation.ts
23086
23673
  var import_node_child_process3 = require("child_process");
@@ -25224,6 +25811,21 @@ async function startDaemon(args) {
25224
25811
  recentLogs.push(`${(/* @__PURE__ */ new Date()).toISOString()} ${message}`);
25225
25812
  if (recentLogs.length > 200) recentLogs.splice(0, recentLogs.length - 200);
25226
25813
  };
25814
+ const alanMcpRegisteredRuns = /* @__PURE__ */ new Set();
25815
+ const alanMcpRegisteredBackends = /* @__PURE__ */ new Set();
25816
+ const finalizeAlanMcpForRun = (runId) => {
25817
+ if (!alanMcpRegisteredRuns.delete(runId)) return;
25818
+ if (alanMcpRegisteredRuns.size > 0) return;
25819
+ for (const backend of alanMcpRegisteredBackends) {
25820
+ try {
25821
+ const { removedPaths } = unregisterAlanMcp(backend);
25822
+ if (removedPaths.length > 0) pushLog(`mcp-unregistered backend=${backend} run=${runId}`);
25823
+ } catch (err) {
25824
+ pushLog(`mcp-unregister-failed backend=${backend} ${err.message}`);
25825
+ }
25826
+ }
25827
+ alanMcpRegisteredBackends.clear();
25828
+ };
25227
25829
  const workspaceAccessTracker = new WorkspaceAccessTracker();
25228
25830
  const recheckWorkspaceIssue = () => {
25229
25831
  const previousPath = workspaceAccessTracker.recoveryPath();
@@ -26006,6 +26608,10 @@ async function startDaemon(args) {
26006
26608
  workspaceLease: workspaceLeaseCapture.lease
26007
26609
  });
26008
26610
  pendingRunStarts.delete(payload.runId);
26611
+ if (payload.alanMcp && backendKind) {
26612
+ alanMcpRegisteredRuns.add(payload.runId);
26613
+ alanMcpRegisteredBackends.add(backendKind);
26614
+ }
26009
26615
  void agent.run({
26010
26616
  task: payload.content,
26011
26617
  maxIterations: payload.maxIterations ?? 50,
@@ -26051,6 +26657,7 @@ async function startDaemon(args) {
26051
26657
  } catch {
26052
26658
  pushLog(`run-journal-remove-failed run=${payload.runId}`);
26053
26659
  }
26660
+ finalizeAlanMcpForRun(payload.runId);
26054
26661
  releaseActiveRun();
26055
26662
  });
26056
26663
  });
@@ -28037,7 +28644,7 @@ async function runSessionFromEnv() {
28037
28644
  const task = process.env.ALAN_TASK;
28038
28645
  const teamId = process.env.ALAN_TEAM_ID || void 0;
28039
28646
  const projectPath = process.env.ALAN_PROJECT_PATH || "/home/user/workspace";
28040
- const backendKind = process.env.ALAN_BACKEND_KIND || "claude_cli";
28647
+ const backendKind = process.env.ALAN_BACKEND_KIND || "codex_app_server";
28041
28648
  const agentId = process.env.ALAN_AGENT_ID || void 0;
28042
28649
  const agentPrompt = process.env.ALAN_AGENT_PROMPT || void 0;
28043
28650
  const mode = process.env.ALAN_MODE || "agent";