@aiden-ade/sandbox-agent 0.1.43 → 0.1.44

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 +730 -112
  2. package/package.json +3 -3
package/dist/index.cjs CHANGED
@@ -15296,6 +15296,8 @@ var import_path5 = require("path");
15296
15296
  var import_fs6 = require("fs");
15297
15297
  var import_os6 = require("os");
15298
15298
  var import_path6 = require("path");
15299
+ var import_readline2 = require("readline");
15300
+ var import_url2 = require("url");
15299
15301
  var import_fs7 = require("fs");
15300
15302
  var import_os7 = require("os");
15301
15303
  var import_path7 = require("path");
@@ -19157,7 +19159,7 @@ function createGrokAcpDriver(imageFiles) {
19157
19159
  }
19158
19160
  newSession(context);
19159
19161
  }
19160
- function sendPrompt(context, state) {
19162
+ function sendPrompt2(context, state) {
19161
19163
  const text = buildPromptWithImagePathReferences(context, imageFiles);
19162
19164
  isPromptActive = true;
19163
19165
  promptId = request("session/prompt", {
@@ -19200,7 +19202,7 @@ function createGrokAcpDriver(imageFiles) {
19200
19202
  const sessionId = asString(result?.sessionId);
19201
19203
  if (sessionId) state.runtimeSessionId = sessionId;
19202
19204
  state.iterations = Math.max(state.iterations, 1);
19203
- sendPrompt(context, state);
19205
+ sendPrompt2(context, state);
19204
19206
  return;
19205
19207
  }
19206
19208
  if (message.id === promptId) {
@@ -19546,23 +19548,59 @@ function createKimiCliBackend(command = "kimi-cli", defaultArgs = []) {
19546
19548
  }
19547
19549
  };
19548
19550
  }
19551
+ var AUTONOMOUS_OPENCODE_CONFIG_CONTENT = JSON.stringify({
19552
+ $schema: "https://opencode.ai/config.json",
19553
+ permission: "allow"
19554
+ });
19555
+ function buildAutonomousOpenCodeConfigContent(existing) {
19556
+ if (!existing?.trim()) return AUTONOMOUS_OPENCODE_CONFIG_CONTENT;
19557
+ try {
19558
+ const parsed = JSON.parse(existing);
19559
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
19560
+ return JSON.stringify({
19561
+ ...parsed,
19562
+ permission: "allow"
19563
+ });
19564
+ }
19565
+ } catch {
19566
+ }
19567
+ return AUTONOMOUS_OPENCODE_CONFIG_CONTENT;
19568
+ }
19569
+ function withAutonomousOpenCodePermissions(context) {
19570
+ if (shouldUseReadOnlyRuntimePermissions(context.config)) return context;
19571
+ return {
19572
+ ...context,
19573
+ env: {
19574
+ ...context.env,
19575
+ OPENCODE_CONFIG_CONTENT: buildAutonomousOpenCodeConfigContent(
19576
+ context.env.OPENCODE_CONFIG_CONTENT
19577
+ )
19578
+ }
19579
+ };
19580
+ }
19549
19581
  function getRecord(value2) {
19550
19582
  return typeof value2 === "object" && value2 !== null ? value2 : null;
19551
19583
  }
19552
19584
  function getString(value2) {
19553
19585
  return typeof value2 === "string" ? value2.trim() : "";
19554
19586
  }
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);
19587
+ function getOpenCodeToolError(part) {
19588
+ const state = getRecord(part.state);
19589
+ const error = getRecord(part.error) ?? getRecord(state?.error);
19590
+ return getString(part.error) || getString(state?.error) || getString(error?.message);
19559
19591
  }
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));
19592
+ function isOpenCodeToolError(part) {
19593
+ const state = getRecord(part.state);
19594
+ return part.is_error === true || part.isError === true || state?.status === "error" || Boolean(getOpenCodeToolError(part));
19563
19595
  }
19564
- function getOpenCodePartId(parsed, part) {
19565
- return getString(part?.id) || getString(parsed.id) || getString(parsed.partID);
19596
+ function getOpenCodePartId(part) {
19597
+ return getString(part.id) || getString(part.partID);
19598
+ }
19599
+ function getOpenCodeToolId(part) {
19600
+ return getString(part.callID) || getString(part.id) || `opencode-tool-${Date.now()}`;
19601
+ }
19602
+ function getOpenCodeToolName(part) {
19603
+ return getString(part.tool) || (typeof part.name === "string" ? part.name.trim() : "") || "Tool";
19566
19604
  }
19567
19605
  function resolveOpenCodePartSnapshotDelta(text, partId, snapshots) {
19568
19606
  if (!partId) return text;
@@ -19579,93 +19617,92 @@ function resolveOpenCodeReasoningDelta(text, partId, state) {
19579
19617
  state.opencodeReasoningByPartId ??= /* @__PURE__ */ new Map();
19580
19618
  return resolveOpenCodePartSnapshotDelta(text, partId, state.opencodeReasoningByPartId);
19581
19619
  }
19582
- function handleOpencodeStructuredEvent(parsed, context, state) {
19620
+ var TASK_TOOL_NAME = "task";
19621
+ var TASK_RESULT_TAG_PATTERN = /<task_result>\n?([\s\S]*?)\n?<\/task_result>/;
19622
+ var TASK_ERROR_TAG_PATTERN = /<task_error>\n?([\s\S]*?)\n?<\/task_error>/;
19623
+ function unwrapOpenCodeTaskOutput(output) {
19624
+ const result = output.match(TASK_RESULT_TAG_PATTERN);
19625
+ if (result?.[1] !== void 0) return result[1].trim();
19626
+ const error = output.match(TASK_ERROR_TAG_PATTERN);
19627
+ if (error?.[1] !== void 0) return error[1].trim();
19628
+ return output;
19629
+ }
19630
+ function emitOpenCodeToolPart(presenter, state, part, opts) {
19631
+ const stateRecord = getRecord(part.state);
19632
+ const status = getString(stateRecord?.status);
19633
+ if (status === "pending") return;
19634
+ const toolName = getOpenCodeToolName(part);
19635
+ const toolId = getOpenCodeToolId(part);
19636
+ const isTask = toolName.toLowerCase() === TASK_TOOL_NAME;
19637
+ const parentToolUseId = opts.parentToolUseId ?? void 0;
19638
+ state.opencodeToolUseIds ??= /* @__PURE__ */ new Set();
19639
+ if (!state.opencodeToolUseIds.has(toolId)) {
19640
+ state.opencodeToolUseIds.add(toolId);
19641
+ void presenter.onToolUse(toolName, stateRecord?.input ?? part, toolId, parentToolUseId);
19642
+ if (isTask) registerBackgroundLauncher(state, toolId);
19643
+ }
19644
+ if (status !== "completed" && status !== "error") return;
19645
+ const toolError = isOpenCodeToolError(part);
19646
+ const rawOutput = getString(stateRecord?.output) || getString(stateRecord?.error) || JSON.stringify(stateRecord ?? part);
19647
+ const output = isTask ? unwrapOpenCodeTaskOutput(rawOutput) : rawOutput;
19648
+ void presenter.onToolResult?.(toolId, output, toolError, parentToolUseId);
19649
+ if (isTask) clearBackgroundTaskIds(state, [toolId]);
19650
+ if (toolError && !state.error) {
19651
+ const message = getOpenCodeToolError(part) || "OpenCode tool call failed.";
19652
+ state.error = message;
19653
+ void presenter.onError(message);
19654
+ }
19655
+ }
19656
+ function mapOpencodePart(part, context, state, opts = {}) {
19583
19657
  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;
19658
+ const type = getString(part.type);
19589
19659
  switch (type) {
19590
- case "step_start": {
19660
+ case "step-start": {
19591
19661
  state.iterations += 1;
19592
19662
  void presenter.onLog(`[opencode] Step ${state.iterations} started`);
19593
19663
  return true;
19594
19664
  }
19595
19665
  case "text": {
19596
- const text = part && typeof part.text === "string" ? part.text : "";
19666
+ const text = typeof part.text === "string" ? part.text : "";
19597
19667
  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);
19668
+ const delta = resolveOpenCodeTextDelta(text, getOpenCodePartId(part), state);
19669
+ if (delta) {
19670
+ state.summary += delta;
19671
+ void presenter.onAssistantText(delta);
19672
+ }
19602
19673
  }
19603
19674
  return true;
19604
19675
  }
19676
+ // Real Part schema only ever uses "reasoning"; "thinking" is a defensive alias in
19677
+ // case a CLI dialect ever names the part itself that way.
19605
19678
  case "reasoning":
19606
19679
  case "thinking": {
19607
- const text = part && typeof part.text === "string" ? part.text : "";
19680
+ const text = typeof part.text === "string" ? part.text : "";
19608
19681
  if (!text) return true;
19609
- const delta = resolveOpenCodeReasoningDelta(text, getOpenCodePartId(parsed, part), state);
19682
+ const delta = resolveOpenCodeReasoningDelta(text, getOpenCodePartId(part), state);
19610
19683
  if (!delta) return true;
19611
19684
  state.iterations = Math.max(state.iterations, 1);
19612
19685
  void presenter.onThinking(delta);
19613
19686
  return true;
19614
19687
  }
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
19688
  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
- }
19642
- return true;
19643
- }
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
- }
19689
+ emitOpenCodeToolPart(presenter, state, part, opts);
19655
19690
  return true;
19656
19691
  }
19657
- case "step_finish": {
19658
- if (part && typeof part.tokens === "object" && part.tokens !== null) {
19659
- const tokens = part.tokens;
19692
+ case "step-finish": {
19693
+ const tokens = getRecord(part.tokens);
19694
+ if (tokens) {
19695
+ const cache2 = getRecord(tokens.cache);
19696
+ const cost = typeof part.cost === "number" ? part.cost : 0;
19660
19697
  state.usage = {
19661
19698
  model: typeof context.config.selectedModel === "string" && context.config.selectedModel.trim() || "opencode",
19662
19699
  numTurns: Math.max(state.iterations, 1),
19663
19700
  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
19701
+ inputTokens: Number(tokens.input) || 0,
19702
+ outputTokens: Number(tokens.output) || 0,
19703
+ cacheReadTokens: Number(cache2?.read) || 0,
19704
+ cacheCreationTokens: Number(cache2?.write) || 0,
19705
+ costUsd: cost
19669
19706
  };
19670
19707
  void presenter.onUsageUpdate?.({
19671
19708
  model: state.usage.model,
@@ -19677,6 +19714,57 @@ function handleOpencodeStructuredEvent(parsed, context, state) {
19677
19714
  }
19678
19715
  return true;
19679
19716
  }
19717
+ default:
19718
+ return false;
19719
+ }
19720
+ }
19721
+ function normalizeOpenCodePart(parsed, part, partType) {
19722
+ return { ...parsed, ...part ?? {}, type: partType };
19723
+ }
19724
+ function handleOpencodeStructuredEvent(parsed, context, state) {
19725
+ const presenter = context.presenter;
19726
+ const type = typeof parsed.type === "string" ? parsed.type : "";
19727
+ if (!type) return false;
19728
+ const sessionID = typeof parsed.sessionID === "string" ? parsed.sessionID : void 0;
19729
+ if (sessionID) state.runtimeSessionId = sessionID;
19730
+ const part = typeof parsed.part === "object" && parsed.part !== null ? parsed.part : null;
19731
+ switch (type) {
19732
+ case "step_start":
19733
+ return mapOpencodePart(normalizeOpenCodePart(parsed, part, "step-start"), context, state);
19734
+ case "text":
19735
+ return mapOpencodePart(normalizeOpenCodePart(parsed, part, "text"), context, state);
19736
+ case "reasoning":
19737
+ case "thinking":
19738
+ return mapOpencodePart(normalizeOpenCodePart(parsed, part, "reasoning"), context, state);
19739
+ // Real event: `opencode run --format json` emits exactly this shape — a single
19740
+ // terminal event per tool call, carrying part.state.{input,output|error}. See
19741
+ // notes/opencode-cli-reverse-engineering.md for the live-captured schema.
19742
+ case "tool_use":
19743
+ case "tool.execute":
19744
+ case "tool":
19745
+ return mapOpencodePart(normalizeOpenCodePart(parsed, part, "tool"), context, state);
19746
+ case "step_finish":
19747
+ return mapOpencodePart(normalizeOpenCodePart(parsed, part, "step-finish"), context, state);
19748
+ // Defensive fallback only: no observed OpenCode version emits a separate
19749
+ // tool_result/tool.result event in `--format json` mode (tool state always
19750
+ // arrives via `tool_use` above). Kept in case a future/older CLI build splits
19751
+ // the terminal tool event into a distinct result message. Deliberately NOT routed
19752
+ // through mapOpencodePart — this shape has no part.state wrapper at all (output
19753
+ // sits directly on the part), so it isn't a real Part-schema shape.
19754
+ case "tool_result":
19755
+ case "tool.result": {
19756
+ const toolId = getString(part?.callID) || getString(parsed.callID) || getString(part?.id) || getString(parsed.id) || `opencode-tool-${Date.now()}`;
19757
+ const output = part && typeof part.output === "string" ? part.output : JSON.stringify(part);
19758
+ const stateRecord = getRecord(part?.state) ?? getRecord(parsed.state);
19759
+ const toolError = part?.is_error === true || part?.isError === true || stateRecord?.status === "error" || Boolean(getString(part?.error) || getString(stateRecord?.error));
19760
+ void presenter.onToolResult?.(toolId, output, toolError);
19761
+ if (toolError && !state.error) {
19762
+ const message = getString(part?.error) || getString(stateRecord?.error) || "OpenCode tool call failed.";
19763
+ state.error = message;
19764
+ void presenter.onError(message);
19765
+ }
19766
+ return true;
19767
+ }
19680
19768
  case "session.error":
19681
19769
  case "error": {
19682
19770
  const errObj = typeof parsed.error === "object" && parsed.error !== null ? parsed.error : null;
@@ -19701,35 +19789,10 @@ var parseOpencodeStructuredLine = createStructuredLineParser(
19701
19789
  "opencode_cli",
19702
19790
  handleOpencodeStructuredEvent
19703
19791
  );
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
- };
19792
+ function buildOpencodeEffortArgs(selectedEffortLevel) {
19793
+ const trimmed = selectedEffortLevel?.trim().toLowerCase();
19794
+ if (!trimmed || !isEffortLevel(trimmed)) return [];
19795
+ return ["--variant", trimmed];
19733
19796
  }
19734
19797
  function createOpencodeCliBackend(command = "opencode", defaultArgs = []) {
19735
19798
  return {
@@ -19752,7 +19815,7 @@ function createOpencodeCliBackend(command = "opencode", defaultArgs = []) {
19752
19815
  command,
19753
19816
  args: [],
19754
19817
  buildArgs: (ctx, prompt) => {
19755
- const args = ["run", "--format", "json"];
19818
+ const args = ["run", "--format", "json", "--thinking"];
19756
19819
  if (!shouldUseReadOnlyRuntimePermissions(ctx.config)) {
19757
19820
  args.push("--dangerously-skip-permissions");
19758
19821
  }
@@ -19760,6 +19823,7 @@ function createOpencodeCliBackend(command = "opencode", defaultArgs = []) {
19760
19823
  if (resumeId) args.push("--session", resumeId);
19761
19824
  const model = ctx.config.selectedModel?.trim();
19762
19825
  if (model?.includes("/")) args.push("--model", model);
19826
+ args.push(...buildOpencodeEffortArgs(ctx.config.selectedEffortLevel));
19763
19827
  const attachmentPaths = [...imagePaths, ...filePaths];
19764
19828
  const fileArgs = attachmentPaths.flatMap((p) => ["--file", p]);
19765
19829
  if (fileArgs.length > 0) {
@@ -19780,6 +19844,395 @@ function createOpencodeCliBackend(command = "opencode", defaultArgs = []) {
19780
19844
  }
19781
19845
  };
19782
19846
  }
19847
+ function parseSseEventBlock(block) {
19848
+ 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(/^ /, ""));
19849
+ if (dataLines.length === 0) return null;
19850
+ try {
19851
+ const parsed = JSON.parse(dataLines.join("\n"));
19852
+ if (parsed && typeof parsed === "object" && typeof parsed.type === "string") {
19853
+ const record = parsed;
19854
+ return {
19855
+ id: typeof record.id === "string" ? record.id : void 0,
19856
+ type: record.type,
19857
+ properties: typeof record.properties === "object" && record.properties !== null ? record.properties : {}
19858
+ };
19859
+ }
19860
+ } catch {
19861
+ }
19862
+ return null;
19863
+ }
19864
+ async function connectOpenCodeSse(url2, signal) {
19865
+ const response = await fetch(url2, {
19866
+ signal,
19867
+ headers: { Accept: "text/event-stream" }
19868
+ });
19869
+ if (!response.ok || !response.body) {
19870
+ throw new Error(`OpenCode SSE connection failed: HTTP ${response.status}`);
19871
+ }
19872
+ const reader = response.body.getReader();
19873
+ const decoder = new TextDecoder();
19874
+ let closed = false;
19875
+ async function* generate() {
19876
+ let buffer = "";
19877
+ try {
19878
+ while (!closed) {
19879
+ const { value: value2, done } = await reader.read();
19880
+ if (done) return;
19881
+ buffer += decoder.decode(value2, { stream: true });
19882
+ let separatorIndex = buffer.indexOf("\n\n");
19883
+ while (separatorIndex !== -1) {
19884
+ const block = buffer.slice(0, separatorIndex);
19885
+ buffer = buffer.slice(separatorIndex + 2);
19886
+ const event = parseSseEventBlock(block);
19887
+ if (event) yield event;
19888
+ separatorIndex = buffer.indexOf("\n\n");
19889
+ }
19890
+ }
19891
+ } finally {
19892
+ try {
19893
+ reader.releaseLock();
19894
+ } catch {
19895
+ }
19896
+ }
19897
+ }
19898
+ return {
19899
+ events: generate(),
19900
+ close: () => {
19901
+ if (closed) return;
19902
+ closed = true;
19903
+ reader.cancel().catch(() => {
19904
+ });
19905
+ }
19906
+ };
19907
+ }
19908
+ var SERVER_LISTENING_PATTERN = /listening on (https?:\/\/\S+)/i;
19909
+ var STARTUP_TIMEOUT_MS = 2e4;
19910
+ var HEALTH_POLL_INTERVAL_MS = 200;
19911
+ var HEALTH_POLL_MAX_ATTEMPTS = 50;
19912
+ function startOpenCodeServer(command, context) {
19913
+ const child = spawnCli(command, ["serve", "--port", "0", "--hostname", "127.0.0.1"], context);
19914
+ const ready = new Promise((resolve22, reject) => {
19915
+ let settled = false;
19916
+ const timeout = setTimeout(() => {
19917
+ if (settled) return;
19918
+ settled = true;
19919
+ reject(
19920
+ new Error(`opencode serve did not report a listening URL within ${STARTUP_TIMEOUT_MS}ms`)
19921
+ );
19922
+ }, STARTUP_TIMEOUT_MS);
19923
+ const stdoutRl = (0, import_readline2.createInterface)({ input: child.stdout });
19924
+ const stderrRl = (0, import_readline2.createInterface)({ input: child.stderr });
19925
+ const stderrLines = [];
19926
+ const checkLine = (line) => {
19927
+ if (settled) return;
19928
+ const match = line.match(SERVER_LISTENING_PATTERN);
19929
+ if (!match) return;
19930
+ settled = true;
19931
+ clearTimeout(timeout);
19932
+ resolve22({ baseUrl: match[1].replace(/\/$/, "") });
19933
+ };
19934
+ stdoutRl.on("line", checkLine);
19935
+ stderrRl.on("line", (line) => {
19936
+ stderrLines.push(line);
19937
+ checkLine(line);
19938
+ });
19939
+ child.once("error", (error) => {
19940
+ if (settled) return;
19941
+ settled = true;
19942
+ clearTimeout(timeout);
19943
+ reject(error);
19944
+ });
19945
+ child.once("exit", (code) => {
19946
+ if (settled) return;
19947
+ settled = true;
19948
+ clearTimeout(timeout);
19949
+ reject(
19950
+ new Error(
19951
+ `opencode serve exited (code ${code}) before reporting a listening URL. stderr: ${stderrLines.slice(-5).join("\n")}`
19952
+ )
19953
+ );
19954
+ });
19955
+ });
19956
+ return { child, ready };
19957
+ }
19958
+ async function waitForHealthy(baseUrl, signal) {
19959
+ for (let attempt = 0; attempt < HEALTH_POLL_MAX_ATTEMPTS; attempt++) {
19960
+ if (signal.aborted)
19961
+ throw new Error("Aborted while waiting for opencode server to become healthy");
19962
+ try {
19963
+ const response = await fetch(`${baseUrl}/global/health`, { signal });
19964
+ if (response.ok) {
19965
+ const body = await response.json().catch(() => null);
19966
+ if (body?.healthy) return;
19967
+ }
19968
+ } catch {
19969
+ }
19970
+ await new Promise((resolve22) => setTimeout(resolve22, HEALTH_POLL_INTERVAL_MS));
19971
+ }
19972
+ throw new Error(`opencode server at ${baseUrl} did not become healthy in time`);
19973
+ }
19974
+ var NON_INTERACTIVE_DENY_RULES = [
19975
+ { permission: "question", pattern: "*", action: "deny" },
19976
+ { permission: "plan_enter", pattern: "*", action: "deny" },
19977
+ { permission: "plan_exit", pattern: "*", action: "deny" }
19978
+ ];
19979
+ async function resolveSession(baseUrl, context, signal) {
19980
+ const resumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
19981
+ if (resumeId) {
19982
+ const existing = await fetch(`${baseUrl}/session/${encodeURIComponent(resumeId)}`, {
19983
+ signal
19984
+ }).catch(() => null);
19985
+ if (existing?.ok) return { id: resumeId };
19986
+ }
19987
+ const title = context.promptText.slice(0, 50) + (context.promptText.length > 50 ? "..." : "");
19988
+ const response = await fetch(`${baseUrl}/session`, {
19989
+ method: "POST",
19990
+ signal,
19991
+ headers: { "Content-Type": "application/json" },
19992
+ body: JSON.stringify({ title, permission: NON_INTERACTIVE_DENY_RULES })
19993
+ });
19994
+ if (!response.ok) {
19995
+ throw new Error(`Failed to create OpenCode session: HTTP ${response.status}`);
19996
+ }
19997
+ const info = await response.json();
19998
+ if (!info.id) throw new Error("OpenCode session create response had no id");
19999
+ return { id: info.id };
20000
+ }
20001
+ function buildPromptParts(promptText, imageFiles, attachmentFiles) {
20002
+ const attachments = [...imageFiles, ...attachmentFiles].map(
20003
+ (file) => ({
20004
+ type: "file",
20005
+ mime: file.mimeType,
20006
+ filename: file.filename,
20007
+ url: (0, import_url2.pathToFileURL)(file.path).href
20008
+ })
20009
+ );
20010
+ return [...attachments, { type: "text", text: promptText }];
20011
+ }
20012
+ async function sendPrompt(baseUrl, sessionId, context, parts2, signal) {
20013
+ const model = context.config.selectedModel?.trim();
20014
+ const modelBody = model && model.includes("/") ? (() => {
20015
+ const slashIndex = model.indexOf("/");
20016
+ return { providerID: model.slice(0, slashIndex), modelID: model.slice(slashIndex + 1) };
20017
+ })() : void 0;
20018
+ const variant = context.config.selectedEffortLevel?.trim().toLowerCase();
20019
+ const response = await fetch(`${baseUrl}/session/${encodeURIComponent(sessionId)}/message`, {
20020
+ method: "POST",
20021
+ signal,
20022
+ headers: { "Content-Type": "application/json" },
20023
+ body: JSON.stringify({
20024
+ parts: parts2,
20025
+ ...modelBody ? { model: modelBody } : {},
20026
+ ...variant && isEffortLevel(variant) ? { variant } : {}
20027
+ })
20028
+ });
20029
+ if (!response.ok) {
20030
+ const text = await response.text().catch(() => "");
20031
+ throw new Error(`OpenCode prompt request failed: HTTP ${response.status} ${text}`.trim());
20032
+ }
20033
+ }
20034
+ async function replyToPermission(baseUrl, requestId, reply, signal) {
20035
+ await fetch(`${baseUrl}/permission/${encodeURIComponent(requestId)}/reply`, {
20036
+ method: "POST",
20037
+ signal,
20038
+ headers: { "Content-Type": "application/json" },
20039
+ body: JSON.stringify({ reply })
20040
+ }).catch((error) => {
20041
+ console.warn("[opencode_serve] Failed to reply to permission request", requestId, error);
20042
+ });
20043
+ }
20044
+ function buildAgentResult(state, sessionId, aborted2) {
20045
+ if (aborted2) {
20046
+ return {
20047
+ success: false,
20048
+ summary: "Interrupted by user",
20049
+ filesModified: [],
20050
+ planFilesCreated: [],
20051
+ iterations: Math.max(state.iterations, 1),
20052
+ error: "Interrupted by user",
20053
+ providerSessionId: sessionId,
20054
+ runtimeSessionId: sessionId,
20055
+ backendKind: "opencode_serve",
20056
+ supportTier: "structured",
20057
+ usage: state.usage
20058
+ };
20059
+ }
20060
+ const failed = Boolean(state.error?.trim());
20061
+ const summary = state.summary.trim() || state.error?.trim() || (failed ? "Task failed" : "Task completed");
20062
+ return {
20063
+ success: !failed,
20064
+ summary,
20065
+ filesModified: [],
20066
+ planFilesCreated: [],
20067
+ iterations: Math.max(state.iterations, 1),
20068
+ ...state.error?.trim() ? { error: state.error.trim() } : {},
20069
+ providerSessionId: sessionId,
20070
+ runtimeSessionId: sessionId,
20071
+ backendKind: "opencode_serve",
20072
+ supportTier: "structured",
20073
+ usage: state.usage
20074
+ };
20075
+ }
20076
+ function createOpencodeServeBackend(command = "opencode") {
20077
+ return {
20078
+ kind: "opencode_serve",
20079
+ supportTier: "structured",
20080
+ async run(context) {
20081
+ const { files: imageFiles, cleanup: cleanupImages } = writeImagesToTempFiles(
20082
+ context.config.images,
20083
+ context.cwd
20084
+ );
20085
+ const { files: attachmentFiles, cleanup: cleanupFiles } = writeFilesToTempFiles(
20086
+ context.config.files,
20087
+ context.cwd
20088
+ );
20089
+ const runContext = withAutonomousOpenCodePermissions(context);
20090
+ const readOnly = shouldUseReadOnlyRuntimePermissions(context.config);
20091
+ const { child, ready } = startOpenCodeServer(command, runContext);
20092
+ const state = { process: child, iterations: 0, summary: "" };
20093
+ context.onProcessSpawned?.(child);
20094
+ context.registerLivenessProbe?.(() => ({
20095
+ providerAlive: child.exitCode === null,
20096
+ lastRawOutputAgoMs: typeof state.lastRawOutputAtMs === "number" ? Date.now() - state.lastRawOutputAtMs : null
20097
+ }));
20098
+ let sseClose = null;
20099
+ let sessionId = "";
20100
+ let aborted2 = false;
20101
+ const onAbort = () => {
20102
+ aborted2 = true;
20103
+ sseClose?.();
20104
+ killProcessTree(child.pid, { signal: "SIGTERM", child });
20105
+ };
20106
+ context.abortController.signal.addEventListener("abort", onAbort, { once: true });
20107
+ try {
20108
+ const server = await ready;
20109
+ state.lastRawOutputAtMs = Date.now();
20110
+ await waitForHealthy(server.baseUrl, context.abortController.signal);
20111
+ const session = await resolveSession(
20112
+ server.baseUrl,
20113
+ runContext,
20114
+ context.abortController.signal
20115
+ );
20116
+ sessionId = session.id;
20117
+ state.runtimeSessionId = sessionId;
20118
+ const prompt = context.config.mode === "plan" ? buildPlanModePrefix(buildPromptWithSystem(context.config, context.promptText)) : buildPromptWithSystem(context.config, context.promptText);
20119
+ const parts2 = buildPromptParts(prompt, imageFiles, attachmentFiles);
20120
+ const connection = await connectOpenCodeSse(
20121
+ `${server.baseUrl}/event`,
20122
+ context.abortController.signal
20123
+ );
20124
+ sseClose = connection.close;
20125
+ const promptError = await sendPrompt(
20126
+ server.baseUrl,
20127
+ sessionId,
20128
+ runContext,
20129
+ parts2,
20130
+ context.abortController.signal
20131
+ ).then(() => null).catch((error) => ({
20132
+ message: error instanceof Error ? error.message : String(error)
20133
+ }));
20134
+ if (context.abortController.signal.aborted) {
20135
+ } else if (promptError) {
20136
+ state.error = promptError.message;
20137
+ void context.presenter.onError(promptError.message);
20138
+ } else {
20139
+ for await (const event of connection.events) {
20140
+ if (aborted2) break;
20141
+ const done = handleOpenCodeServeEvent(
20142
+ event,
20143
+ sessionId,
20144
+ runContext,
20145
+ state,
20146
+ readOnly,
20147
+ server.baseUrl
20148
+ );
20149
+ if (done) break;
20150
+ }
20151
+ }
20152
+ return buildAgentResult(state, sessionId, aborted2);
20153
+ } catch (error) {
20154
+ const message = error instanceof Error ? error.message : String(error);
20155
+ if (!aborted2 && !context.abortController.signal.aborted) {
20156
+ console.error("[opencode_serve] Run failed:", message);
20157
+ state.error = message;
20158
+ void context.presenter.onError(message);
20159
+ }
20160
+ return buildAgentResult(
20161
+ state,
20162
+ sessionId,
20163
+ aborted2 || context.abortController.signal.aborted
20164
+ );
20165
+ } finally {
20166
+ context.abortController.signal.removeEventListener("abort", onAbort);
20167
+ sseClose?.();
20168
+ killProcessTree(child.pid, { signal: "SIGTERM", child });
20169
+ cleanupImages();
20170
+ cleanupFiles();
20171
+ }
20172
+ }
20173
+ };
20174
+ }
20175
+ function handleOpenCodeServeEvent(event, sessionId, context, state, readOnly, baseUrl) {
20176
+ const properties = event.properties;
20177
+ const eventSessionId = getString(properties.sessionID);
20178
+ if (eventSessionId && eventSessionId !== sessionId) return false;
20179
+ switch (event.type) {
20180
+ // Live-verified: the SSE bus emits a message.updated (and matching
20181
+ // message.part.updated) for the USER's own message too, not just the
20182
+ // assistant's — track user message ids so their echoed text is never
20183
+ // mistaken for assistant output below.
20184
+ case "message.updated": {
20185
+ const info = getRecord(properties.info);
20186
+ if (getString(info?.role) === "user") {
20187
+ const messageId = getString(info?.id);
20188
+ if (messageId) {
20189
+ state.opencodeUserMessageIds ??= /* @__PURE__ */ new Set();
20190
+ state.opencodeUserMessageIds.add(messageId);
20191
+ }
20192
+ }
20193
+ return false;
20194
+ }
20195
+ case "message.part.updated": {
20196
+ const part = getRecord(properties.part);
20197
+ if (!part) return false;
20198
+ const messageId = getString(part.messageID);
20199
+ if (messageId && state.opencodeUserMessageIds?.has(messageId)) return false;
20200
+ mapOpencodePart(part, context, state);
20201
+ return false;
20202
+ }
20203
+ // Live token-by-token streaming is explicitly out of scope — only the
20204
+ // start/complete snapshots from message.part.updated are consumed.
20205
+ case "message.part.delta":
20206
+ return false;
20207
+ case "permission.asked": {
20208
+ const requestId = getString(properties.id);
20209
+ if (requestId) {
20210
+ void replyToPermission(
20211
+ baseUrl,
20212
+ requestId,
20213
+ readOnly ? "reject" : "once",
20214
+ context.abortController.signal
20215
+ );
20216
+ }
20217
+ return false;
20218
+ }
20219
+ case "session.error": {
20220
+ const errorRecord = getRecord(properties.error);
20221
+ const message = getString(errorRecord?.message) || getString(properties.error) || "OpenCode session error.";
20222
+ if (!state.error) {
20223
+ state.error = message;
20224
+ void context.presenter.onError(message);
20225
+ }
20226
+ return false;
20227
+ }
20228
+ case "session.status": {
20229
+ const status = getRecord(properties.status);
20230
+ return getString(status?.type) === "idle";
20231
+ }
20232
+ default:
20233
+ return false;
20234
+ }
20235
+ }
19783
20236
  function createGenericCliPassthroughBackend(command, defaultArgs = []) {
19784
20237
  return {
19785
20238
  kind: "generic_cli",
@@ -20008,6 +20461,7 @@ function defaultSelectedModelForBackend(backendKind) {
20008
20461
  case "grok_cli":
20009
20462
  return firstCatalogModelId(backendKind) ?? "grok-4.5";
20010
20463
  case "opencode_cli":
20464
+ case "opencode_serve":
20011
20465
  return void 0;
20012
20466
  case "antigravity_cli":
20013
20467
  return void 0;
@@ -20224,6 +20678,7 @@ var BaseMachineAgent = class _BaseMachineAgent {
20224
20678
  case "cursor_agent_cli":
20225
20679
  case "droid_cli":
20226
20680
  case "opencode_cli":
20681
+ case "opencode_serve":
20227
20682
  case "copilot_cli":
20228
20683
  case "supatest_cli":
20229
20684
  case "kimi_cli":
@@ -20291,7 +20746,7 @@ Prefer relative paths and keep your work scoped to this project.`
20291
20746
  const promptText = this.buildPromptText(runtimeConfig);
20292
20747
  const runtimeCommand = runtimeConfig.runtimeCommand || this.runtime.runtimeCommand;
20293
20748
  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(
20749
+ 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
20750
  runtimeCommand || "generic-cli",
20296
20751
  runtimeArgs
20297
20752
  );
@@ -21004,13 +21459,20 @@ ${meta.description}`);
21004
21459
  }
21005
21460
  if (conversationId) artifactIds.push(`- conversationId: "${conversationId}"`);
21006
21461
  if (artifactIds.length > 0) {
21462
+ const artifactTools = ["create_plan", "create_document_artifact", "create_test_report"];
21463
+ if (taskId || isWorkflowSession) artifactTools.push("upsert_prototype_version");
21007
21464
  parts2.push(
21008
21465
  [
21009
- "When creating artifacts using MCP tools (create_plan, create_document_artifact, create_test_report, upsert_prototype_version), always pass these IDs:",
21466
+ `When creating artifacts using MCP tools (${artifactTools.join(", ")}), always pass these IDs:`,
21010
21467
  ...artifactIds
21011
21468
  ].join("\n")
21012
21469
  );
21013
21470
  }
21471
+ if (teamId && conversationId && !taskId && !isWorkflowSession) {
21472
+ parts2.push(
21473
+ "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."
21474
+ );
21475
+ }
21014
21476
  if (workflowId && conversationId && teamId) {
21015
21477
  parts2.push(
21016
21478
  [
@@ -22274,9 +22736,7 @@ var CODEX_ALAN_TOML_SECTION = "mcp_servers.alan";
22274
22736
  function isCodexAlanTomlSection(section) {
22275
22737
  return section === CODEX_ALAN_TOML_SECTION || section.startsWith(`${CODEX_ALAN_TOML_SECTION}.`);
22276
22738
  }
22277
- function mergeCodexAlanSection(path, existingContent, desiredContent) {
22278
- if (existingContent === null || existingContent.trim() === "") return desiredContent;
22279
- assertValidExistingToml(path, existingContent);
22739
+ function stripCodexAlanSection(existingContent) {
22280
22740
  const lines = existingContent.split("\n");
22281
22741
  const retained = [];
22282
22742
  let insideAlanSection = false;
@@ -22287,7 +22747,12 @@ function mergeCodexAlanSection(path, existingContent, desiredContent) {
22287
22747
  }
22288
22748
  if (!insideAlanSection) retained.push(line);
22289
22749
  }
22290
- const prefix = retained.join("\n").trimEnd();
22750
+ return retained.join("\n").trimEnd();
22751
+ }
22752
+ function mergeCodexAlanSection(path, existingContent, desiredContent) {
22753
+ if (existingContent === null || existingContent.trim() === "") return desiredContent;
22754
+ assertValidExistingToml(path, existingContent);
22755
+ const prefix = stripCodexAlanSection(existingContent);
22291
22756
  const merged = `${prefix}${prefix ? "\n\n" : ""}${desiredContent.trim()}
22292
22757
  `;
22293
22758
  try {
@@ -22373,6 +22838,45 @@ function verifyAlanMcpRegistered(backendKind, home = (0, import_node_os6.homedir
22373
22838
  verification: inspection.verification
22374
22839
  };
22375
22840
  }
22841
+ function removeAlanFromJsonConfig(path, existingContent) {
22842
+ const config = parseJsonObject2(path, existingContent);
22843
+ for (const containerKey of ["mcpServers", "mcp"]) {
22844
+ const container = config[containerKey];
22845
+ if (container && typeof container === "object" && !Array.isArray(container)) {
22846
+ delete container.alan;
22847
+ }
22848
+ }
22849
+ return JSON.stringify(config);
22850
+ }
22851
+ function unregisterAlanMcp(backendKind, home = (0, import_node_os6.homedir)()) {
22852
+ const removedPaths = [];
22853
+ const errors = [];
22854
+ const files = alanMcpConfigFilesForBackend(backendKind, {}, home);
22855
+ for (const file of files) {
22856
+ if (!(0, import_node_fs7.existsSync)(file.path)) continue;
22857
+ const releaseLock = waitForConfigLock(`${file.path}.alan-lock`);
22858
+ try {
22859
+ const current = (0, import_node_fs7.readFileSync)(file.path, "utf8");
22860
+ let next;
22861
+ if (file.path.endsWith(".toml")) {
22862
+ const stripped = stripCodexAlanSection(current);
22863
+ next = stripped ? `${stripped}
22864
+ ` : "";
22865
+ } else {
22866
+ next = removeAlanFromJsonConfig(file.path, current);
22867
+ }
22868
+ if (next !== current) {
22869
+ atomicWriteManagedConfig(file.path, next);
22870
+ removedPaths.push(file.path);
22871
+ }
22872
+ } catch (err) {
22873
+ errors.push(err instanceof Error ? err.message : String(err));
22874
+ } finally {
22875
+ releaseLock();
22876
+ }
22877
+ }
22878
+ return { removedPaths, errors };
22879
+ }
22376
22880
  function isHttpUrl(value2) {
22377
22881
  if (typeof value2 !== "string") return false;
22378
22882
  try {
@@ -22719,6 +23223,88 @@ function extractCodexDiscoveredModelIdsFromDebugJson(value2) {
22719
23223
  }
22720
23224
  return [...new Set(ids)];
22721
23225
  }
23226
+ var OPENCODE_MODEL_ID_LINE_PATTERN = /^[a-z0-9][\w.-]*\/[\w.-]+$/i;
23227
+ var OPENCODE_KNOWN_VARIANT_KEYS = /* @__PURE__ */ new Set([
23228
+ "minimal",
23229
+ "none",
23230
+ "low",
23231
+ "medium",
23232
+ "high",
23233
+ "xhigh",
23234
+ "extra-high",
23235
+ "max",
23236
+ "ultra"
23237
+ ]);
23238
+ function parseOpenCodeVerboseModelOutput(text) {
23239
+ const entries = [];
23240
+ const lines = text.split(/\r?\n/);
23241
+ let i = 0;
23242
+ while (i < lines.length) {
23243
+ const line = (lines[i] ?? "").trim();
23244
+ i += 1;
23245
+ if (!OPENCODE_MODEL_ID_LINE_PATTERN.test(line)) continue;
23246
+ let jsonStart = i;
23247
+ while (jsonStart < lines.length && (lines[jsonStart] ?? "").trim() === "") jsonStart += 1;
23248
+ if (jsonStart >= lines.length || !(lines[jsonStart] ?? "").trim().startsWith("{")) continue;
23249
+ let depth = 0;
23250
+ let inString = false;
23251
+ let escaped = false;
23252
+ let endLineIdx = -1;
23253
+ let buffer = "";
23254
+ for (let j = jsonStart; j < lines.length; j += 1) {
23255
+ const raw = lines[j] ?? "";
23256
+ buffer += (buffer ? "\n" : "") + raw;
23257
+ for (const ch of raw) {
23258
+ if (escaped) {
23259
+ escaped = false;
23260
+ continue;
23261
+ }
23262
+ if (ch === "\\" && inString) {
23263
+ escaped = true;
23264
+ continue;
23265
+ }
23266
+ if (ch === '"') {
23267
+ inString = !inString;
23268
+ continue;
23269
+ }
23270
+ if (inString) continue;
23271
+ if (ch === "{") depth += 1;
23272
+ else if (ch === "}") depth -= 1;
23273
+ }
23274
+ if (depth === 0) {
23275
+ endLineIdx = j;
23276
+ break;
23277
+ }
23278
+ }
23279
+ if (endLineIdx === -1) break;
23280
+ try {
23281
+ const obj = JSON.parse(buffer);
23282
+ entries.push({
23283
+ id: line,
23284
+ reasoning: obj.capabilities?.reasoning === true,
23285
+ variants: obj.variants ? Object.keys(obj.variants) : []
23286
+ });
23287
+ } catch {
23288
+ }
23289
+ i = endLineIdx + 1;
23290
+ }
23291
+ return entries;
23292
+ }
23293
+ function extractOpenCodeDiscoveredModelIdsFromVerboseOutput(text) {
23294
+ const entries = parseOpenCodeVerboseModelOutput(text);
23295
+ const ids = [];
23296
+ for (const entry of entries) {
23297
+ const knownVariants = entry.reasoning ? entry.variants.filter((variant) => OPENCODE_KNOWN_VARIANT_KEYS.has(variant)) : [];
23298
+ if (knownVariants.length === 0) {
23299
+ ids.push(entry.id);
23300
+ continue;
23301
+ }
23302
+ for (const variant of knownVariants) {
23303
+ ids.push(`${entry.id}-${variant}`);
23304
+ }
23305
+ }
23306
+ return [...new Set(ids)];
23307
+ }
22722
23308
  function extractModelIdsFromText(value2) {
22723
23309
  const ansiPattern = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
22724
23310
  const ignoredLinePattern = /^(available models|models|usage|error|flag provided|try |agy\b|warning:)/i;
@@ -22829,12 +23415,24 @@ async function probeJsonOrTextModels(executable, env, argSets, options) {
22829
23415
  return [];
22830
23416
  }
22831
23417
  async function discoverOpenCodeModels(executable, env) {
22832
- const probed = await probeJsonOrTextModels(executable, env, [
23418
+ const verbose = await runCliProbeAsync(
23419
+ executable,
23420
+ withModelProbeEnv(env),
23421
+ ["models", "--verbose"],
23422
+ MODEL_DISCOVERY_TIMEOUT_MS
23423
+ );
23424
+ if (verbose && !verbose.error && verbose.status === 0 && verbose.stdout.trim()) {
23425
+ const withEffort = extractOpenCodeDiscoveredModelIdsFromVerboseOutput(verbose.stdout);
23426
+ if (withEffort.length > 0) return withEffort;
23427
+ }
23428
+ const plain = await probeJsonOrTextModels(executable, env, [["models"]]);
23429
+ if (plain.length > 0) return plain;
23430
+ const legacy = await probeJsonOrTextModels(executable, env, [
22833
23431
  ["models", "--json"],
22834
23432
  ["models", "list", "--json"],
22835
23433
  ["model", "list", "--json"]
22836
23434
  ]);
22837
- return probed.length > 0 ? probed : OPENCODE_ZEN_MODEL_IDS;
23435
+ return legacy.length > 0 ? legacy : OPENCODE_ZEN_MODEL_IDS;
22838
23436
  }
22839
23437
  async function discoverAntigravityModels(executable, env) {
22840
23438
  return probeJsonOrTextModels(executable, env, [["models"]], {
@@ -23080,7 +23678,7 @@ var RunStartGate = class {
23080
23678
  };
23081
23679
 
23082
23680
  // src/version.ts
23083
- var AGENT_VERSION = "0.1.43";
23681
+ var AGENT_VERSION = "0.1.44";
23084
23682
 
23085
23683
  // src/workspace-relocation.ts
23086
23684
  var import_node_child_process3 = require("child_process");
@@ -25224,6 +25822,21 @@ async function startDaemon(args) {
25224
25822
  recentLogs.push(`${(/* @__PURE__ */ new Date()).toISOString()} ${message}`);
25225
25823
  if (recentLogs.length > 200) recentLogs.splice(0, recentLogs.length - 200);
25226
25824
  };
25825
+ const alanMcpRegisteredRuns = /* @__PURE__ */ new Set();
25826
+ const alanMcpRegisteredBackends = /* @__PURE__ */ new Set();
25827
+ const finalizeAlanMcpForRun = (runId) => {
25828
+ if (!alanMcpRegisteredRuns.delete(runId)) return;
25829
+ if (alanMcpRegisteredRuns.size > 0) return;
25830
+ for (const backend of alanMcpRegisteredBackends) {
25831
+ try {
25832
+ const { removedPaths } = unregisterAlanMcp(backend);
25833
+ if (removedPaths.length > 0) pushLog(`mcp-unregistered backend=${backend} run=${runId}`);
25834
+ } catch (err) {
25835
+ pushLog(`mcp-unregister-failed backend=${backend} ${err.message}`);
25836
+ }
25837
+ }
25838
+ alanMcpRegisteredBackends.clear();
25839
+ };
25227
25840
  const workspaceAccessTracker = new WorkspaceAccessTracker();
25228
25841
  const recheckWorkspaceIssue = () => {
25229
25842
  const previousPath = workspaceAccessTracker.recoveryPath();
@@ -26006,6 +26619,10 @@ async function startDaemon(args) {
26006
26619
  workspaceLease: workspaceLeaseCapture.lease
26007
26620
  });
26008
26621
  pendingRunStarts.delete(payload.runId);
26622
+ if (payload.alanMcp && backendKind) {
26623
+ alanMcpRegisteredRuns.add(payload.runId);
26624
+ alanMcpRegisteredBackends.add(backendKind);
26625
+ }
26009
26626
  void agent.run({
26010
26627
  task: payload.content,
26011
26628
  maxIterations: payload.maxIterations ?? 50,
@@ -26051,6 +26668,7 @@ async function startDaemon(args) {
26051
26668
  } catch {
26052
26669
  pushLog(`run-journal-remove-failed run=${payload.runId}`);
26053
26670
  }
26671
+ finalizeAlanMcpForRun(payload.runId);
26054
26672
  releaseActiveRun();
26055
26673
  });
26056
26674
  });
@@ -28037,7 +28655,7 @@ async function runSessionFromEnv() {
28037
28655
  const task = process.env.ALAN_TASK;
28038
28656
  const teamId = process.env.ALAN_TEAM_ID || void 0;
28039
28657
  const projectPath = process.env.ALAN_PROJECT_PATH || "/home/user/workspace";
28040
- const backendKind = process.env.ALAN_BACKEND_KIND || "claude_cli";
28658
+ const backendKind = process.env.ALAN_BACKEND_KIND || "codex_app_server";
28041
28659
  const agentId = process.env.ALAN_AGENT_ID || void 0;
28042
28660
  const agentPrompt = process.env.ALAN_AGENT_PROMPT || void 0;
28043
28661
  const mode = process.env.ALAN_MODE || "agent";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiden-ade/sandbox-agent",
3
- "version": "0.1.43",
3
+ "version": "0.1.44",
4
4
  "type": "module",
5
5
  "description": "Alan agent runtime — cloud sandbox and local daemon (alan-agent CLI)",
6
6
  "bin": {
@@ -22,8 +22,8 @@
22
22
  "tsup": "^8.5.1",
23
23
  "tsx": "^4.19.0",
24
24
  "typescript": "~5.9.3",
25
- "@alan-ai/agent-core": "0.1.0",
26
- "@alan-ai/shared": "0.1.0"
25
+ "@alan-ai/shared": "0.1.0",
26
+ "@alan-ai/agent-core": "0.1.0"
27
27
  },
28
28
  "deprecated": "Use @alan-ai-hq/agent-manager instead.",
29
29
  "scripts": {