@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.
- package/dist/index.cjs +730 -112
- 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
|
|
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
|
-
|
|
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(
|
|
19556
|
-
const state = getRecord(part
|
|
19557
|
-
const error = getRecord(part
|
|
19558
|
-
return getString(part
|
|
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(
|
|
19561
|
-
const state = getRecord(part
|
|
19562
|
-
return 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(
|
|
19565
|
-
return getString(part
|
|
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
|
-
|
|
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 =
|
|
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 "
|
|
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 =
|
|
19666
|
+
const text = typeof part.text === "string" ? part.text : "";
|
|
19597
19667
|
if (text) {
|
|
19598
|
-
const delta = resolveOpenCodeTextDelta(text, getOpenCodePartId(
|
|
19599
|
-
if (
|
|
19600
|
-
|
|
19601
|
-
|
|
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 =
|
|
19680
|
+
const text = typeof part.text === "string" ? part.text : "";
|
|
19608
19681
|
if (!text) return true;
|
|
19609
|
-
const delta = resolveOpenCodeReasoningDelta(text, getOpenCodePartId(
|
|
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
|
-
|
|
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 "
|
|
19658
|
-
|
|
19659
|
-
|
|
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
|
|
19665
|
-
outputTokens: tokens.output
|
|
19666
|
-
cacheReadTokens:
|
|
19667
|
-
cacheCreationTokens:
|
|
19668
|
-
costUsd:
|
|
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
|
-
|
|
19705
|
-
|
|
19706
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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 || "
|
|
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.
|
|
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/
|
|
26
|
-
"@alan-ai/
|
|
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": {
|