agentbox-sdk 0.1.508 → 0.1.512
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/README.md +74 -2
- package/dist/agents/index.d.ts +2 -2
- package/dist/agents/index.js +1 -1
- package/dist/{chunk-BO4J4KSM.js → chunk-4DADE7TF.js} +754 -106
- package/dist/events/index.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{types-BQ-_AUfY.d.ts → types-VT1LMbLW.d.ts} +44 -4
- package/package.json +1 -1
|
@@ -1825,8 +1825,203 @@ function extractOpenCodeCostData(events) {
|
|
|
1825
1825
|
}) : null;
|
|
1826
1826
|
}
|
|
1827
1827
|
|
|
1828
|
+
// src/agents/background-tasks.ts
|
|
1829
|
+
var DEFAULT_BACKGROUND_TASK_TIMEOUT_MS = 30 * 6e4;
|
|
1830
|
+
var BACKGROUND_TASK_GRACE_MS = 15e3;
|
|
1831
|
+
var CLI_BACKGROUND_WAIT_CEILING_ENV = "CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS";
|
|
1832
|
+
var CLI_BACKGROUND_WAIT_CEILING_MS = 3e4;
|
|
1833
|
+
function resolveBackgroundTaskTimeoutMs(value) {
|
|
1834
|
+
if (value === void 0) return DEFAULT_BACKGROUND_TASK_TIMEOUT_MS;
|
|
1835
|
+
if (Number.isNaN(value) || value < 0) {
|
|
1836
|
+
throw new Error(
|
|
1837
|
+
"backgroundTaskTimeoutMs must be a non-negative number (Infinity waits forever)."
|
|
1838
|
+
);
|
|
1839
|
+
}
|
|
1840
|
+
return value;
|
|
1841
|
+
}
|
|
1842
|
+
function applyCliBackgroundWaitCeiling(env) {
|
|
1843
|
+
env[CLI_BACKGROUND_WAIT_CEILING_ENV] ??= String(
|
|
1844
|
+
CLI_BACKGROUND_WAIT_CEILING_MS
|
|
1845
|
+
);
|
|
1846
|
+
}
|
|
1847
|
+
function asRecord2(value) {
|
|
1848
|
+
return value !== null && typeof value === "object" ? value : void 0;
|
|
1849
|
+
}
|
|
1850
|
+
function asArray(value) {
|
|
1851
|
+
return Array.isArray(value) ? value : [];
|
|
1852
|
+
}
|
|
1853
|
+
var SCHEDULE_TOOLS = /* @__PURE__ */ new Set(["CronCreate", "ScheduleWakeup"]);
|
|
1854
|
+
var DONE_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "killed"]);
|
|
1855
|
+
var BackgroundTaskTracker = class {
|
|
1856
|
+
tasks = /* @__PURE__ */ new Map();
|
|
1857
|
+
wakeups = /* @__PURE__ */ new Map();
|
|
1858
|
+
// tool_use seen, tool_result not yet: a failed schedule adds nothing.
|
|
1859
|
+
pendingWakeups = /* @__PURE__ */ new Map();
|
|
1860
|
+
afterResult = false;
|
|
1861
|
+
seenBackgroundWork = false;
|
|
1862
|
+
liveTasks() {
|
|
1863
|
+
return [...this.tasks.values(), ...this.wakeups.values()];
|
|
1864
|
+
}
|
|
1865
|
+
/**
|
|
1866
|
+
* True once any background task or scheduled wakeup was live in this run.
|
|
1867
|
+
* The CLI queues a wake-up for every task that finishes and delivers it as
|
|
1868
|
+
* a new turn once the model is idle — including tasks that finished
|
|
1869
|
+
* mid-turn, whose queued turn starts right after that turn's `result`
|
|
1870
|
+
* with nothing live and nothing observable in between. After background
|
|
1871
|
+
* work has been seen, a `result` is therefore never the end of the run on
|
|
1872
|
+
* its own; only the grace passing without a new turn is.
|
|
1873
|
+
*/
|
|
1874
|
+
hasSeenBackgroundWork() {
|
|
1875
|
+
return this.seenBackgroundWork;
|
|
1876
|
+
}
|
|
1877
|
+
/** Feed one SDKMessage. Returns true when it started a follow-up turn. */
|
|
1878
|
+
ingest(message) {
|
|
1879
|
+
const m = asRecord2(message);
|
|
1880
|
+
if (!m) return false;
|
|
1881
|
+
if (m.type === "result") {
|
|
1882
|
+
this.afterResult = true;
|
|
1883
|
+
return false;
|
|
1884
|
+
}
|
|
1885
|
+
if (m.type === "system") return this.ingestSystem(m);
|
|
1886
|
+
if (m.type === "command_lifecycle") {
|
|
1887
|
+
if (m.state !== "started") return false;
|
|
1888
|
+
this.wakeups.clear();
|
|
1889
|
+
return this.startTurn();
|
|
1890
|
+
}
|
|
1891
|
+
if (m.parent_tool_use_id) return false;
|
|
1892
|
+
if (m.type === "assistant") {
|
|
1893
|
+
const started = this.startTurn();
|
|
1894
|
+
this.ingestToolUses(m);
|
|
1895
|
+
return started;
|
|
1896
|
+
}
|
|
1897
|
+
if (m.type === "user") {
|
|
1898
|
+
this.ingestToolResults(m);
|
|
1899
|
+
return false;
|
|
1900
|
+
}
|
|
1901
|
+
if (m.type === "stream_event")
|
|
1902
|
+
return asRecord2(m.event)?.type === "message_start" && this.startTurn();
|
|
1903
|
+
return false;
|
|
1904
|
+
}
|
|
1905
|
+
startTurn() {
|
|
1906
|
+
if (!this.afterResult) return false;
|
|
1907
|
+
this.afterResult = false;
|
|
1908
|
+
return true;
|
|
1909
|
+
}
|
|
1910
|
+
ingestSystem(m) {
|
|
1911
|
+
const id = String(m.task_id ?? "");
|
|
1912
|
+
switch (m.subtype) {
|
|
1913
|
+
case "background_tasks_changed":
|
|
1914
|
+
this.tasks.clear();
|
|
1915
|
+
for (const entry of asArray(m.tasks)) {
|
|
1916
|
+
const task = asRecord2(entry);
|
|
1917
|
+
if (task) this.addTask(task);
|
|
1918
|
+
}
|
|
1919
|
+
return false;
|
|
1920
|
+
case "task_started":
|
|
1921
|
+
if (m.is_backgrounded === true && !m.owned_by_subagent) this.addTask(m);
|
|
1922
|
+
return false;
|
|
1923
|
+
case "task_notification":
|
|
1924
|
+
this.tasks.delete(id);
|
|
1925
|
+
return false;
|
|
1926
|
+
case "task_updated":
|
|
1927
|
+
if (DONE_STATUSES.has(String(asRecord2(m.patch)?.status)))
|
|
1928
|
+
this.tasks.delete(id);
|
|
1929
|
+
return false;
|
|
1930
|
+
case "init":
|
|
1931
|
+
return this.startTurn();
|
|
1932
|
+
default:
|
|
1933
|
+
return false;
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
addTask(task) {
|
|
1937
|
+
const id = String(task.task_id ?? "");
|
|
1938
|
+
if (!id) return;
|
|
1939
|
+
this.seenBackgroundWork = true;
|
|
1940
|
+
this.tasks.set(id, {
|
|
1941
|
+
id,
|
|
1942
|
+
type: String(task.task_type ?? "task"),
|
|
1943
|
+
description: String(task.description ?? "")
|
|
1944
|
+
});
|
|
1945
|
+
}
|
|
1946
|
+
ingestToolUses(m) {
|
|
1947
|
+
for (const entry of asArray(asRecord2(m.message)?.content)) {
|
|
1948
|
+
const block = asRecord2(entry);
|
|
1949
|
+
if (block?.type !== "tool_use") continue;
|
|
1950
|
+
const name = String(block.name ?? "");
|
|
1951
|
+
const input = asRecord2(block.input) ?? {};
|
|
1952
|
+
if (name === "CronDelete" || name === "ScheduleWakeup" && input.stop === true) {
|
|
1953
|
+
this.wakeups.clear();
|
|
1954
|
+
continue;
|
|
1955
|
+
}
|
|
1956
|
+
if (!SCHEDULE_TOOLS.has(name)) continue;
|
|
1957
|
+
const id = String(block.id ?? "");
|
|
1958
|
+
if (!id) continue;
|
|
1959
|
+
this.pendingWakeups.set(id, {
|
|
1960
|
+
id,
|
|
1961
|
+
type: "scheduled_wakeup",
|
|
1962
|
+
description: String(input.prompt ?? input.cron ?? name)
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
ingestToolResults(m) {
|
|
1967
|
+
for (const entry of asArray(asRecord2(m.message)?.content)) {
|
|
1968
|
+
const block = asRecord2(entry);
|
|
1969
|
+
if (block?.type !== "tool_result") continue;
|
|
1970
|
+
const id = String(block.tool_use_id ?? "");
|
|
1971
|
+
const pending = this.pendingWakeups.get(id);
|
|
1972
|
+
if (!pending) continue;
|
|
1973
|
+
this.pendingWakeups.delete(id);
|
|
1974
|
+
if (block.is_error) continue;
|
|
1975
|
+
this.seenBackgroundWork = true;
|
|
1976
|
+
this.wakeups.set(id, pending);
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
};
|
|
1980
|
+
var MAX_TIMER_MS = 2 ** 31 - 1;
|
|
1981
|
+
var STOP_TASKS_TIMEOUT_MS = 5e3;
|
|
1982
|
+
function withTimeout(promise, ms) {
|
|
1983
|
+
let timer;
|
|
1984
|
+
const timeout = new Promise((resolve) => {
|
|
1985
|
+
timer = setTimeout(() => resolve(void 0), ms);
|
|
1986
|
+
});
|
|
1987
|
+
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
1988
|
+
}
|
|
1989
|
+
var BackgroundWait = class {
|
|
1990
|
+
constructor(graceMs, ceilingMs) {
|
|
1991
|
+
this.graceMs = graceMs;
|
|
1992
|
+
this.expired = new Promise((resolve) => {
|
|
1993
|
+
this.expire = resolve;
|
|
1994
|
+
});
|
|
1995
|
+
if (Number.isFinite(ceilingMs)) {
|
|
1996
|
+
this.ceiling = setTimeout(() => this.expire("ceiling"), Math.min(ceilingMs, MAX_TIMER_MS));
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
graceMs;
|
|
2000
|
+
expired;
|
|
2001
|
+
expire;
|
|
2002
|
+
grace;
|
|
2003
|
+
ceiling;
|
|
2004
|
+
startedAt = Date.now();
|
|
2005
|
+
/** Arm the grace timer while nothing is live; disarm it once a task appears. */
|
|
2006
|
+
setIdle(idle) {
|
|
2007
|
+
if (!idle) {
|
|
2008
|
+
clearTimeout(this.grace);
|
|
2009
|
+
this.grace = void 0;
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
2012
|
+
this.grace ??= setTimeout(() => this.expire("grace"), this.graceMs);
|
|
2013
|
+
}
|
|
2014
|
+
elapsedMs() {
|
|
2015
|
+
return Date.now() - this.startedAt;
|
|
2016
|
+
}
|
|
2017
|
+
clear() {
|
|
2018
|
+
clearTimeout(this.grace);
|
|
2019
|
+
clearTimeout(this.ceiling);
|
|
2020
|
+
}
|
|
2021
|
+
};
|
|
2022
|
+
|
|
1828
2023
|
// src/agents/providers/claude-code.ts
|
|
1829
|
-
var DAEMON_PROTOCOL_VERSION = "
|
|
2024
|
+
var DAEMON_PROTOCOL_VERSION = "5";
|
|
1830
2025
|
var DAEMON_PORT = 43180;
|
|
1831
2026
|
var DAEMON_PATH = "/tmp/agentbox/claude-code/daemon.mjs";
|
|
1832
2027
|
var DAEMON_LOG_PATH = "/tmp/agentbox/claude-code/daemon.log";
|
|
@@ -1856,12 +2051,16 @@ function buildClaudeQueryOptions(params) {
|
|
|
1856
2051
|
extraArgs["append-system-prompt"] = run.systemPrompt;
|
|
1857
2052
|
}
|
|
1858
2053
|
const includeHookEvents = provider?.includeHookEvents ?? false;
|
|
2054
|
+
if (run.reasoning === "max" || run.reasoning === "ultra") {
|
|
2055
|
+
throw new Error(`Reasoning effort "${run.reasoning}" is only supported by Codex.`);
|
|
2056
|
+
}
|
|
1859
2057
|
const effort = provider?.ultracode ? "xhigh" : run.reasoning;
|
|
1860
2058
|
return {
|
|
1861
2059
|
cwd: params.cwd ?? params.request.options.cwd,
|
|
1862
2060
|
env: params.env,
|
|
1863
2061
|
pathToClaudeCodeExecutable: provider?.binary ?? "claude",
|
|
1864
2062
|
...params.settingsPath ? { settings: params.settingsPath } : {},
|
|
2063
|
+
...params.request.options.configuration === "native" && provider?.fastMode !== void 0 ? { settings: { fastMode: provider.fastMode } } : {},
|
|
1865
2064
|
...params.request.options.configuration === "native" ? {
|
|
1866
2065
|
settingSources: ["user", "project", "local"],
|
|
1867
2066
|
systemPrompt: { type: "preset", preset: "claude_code" }
|
|
@@ -2091,6 +2290,29 @@ async function handleStart(req, res, runId) {
|
|
|
2091
2290
|
opts.pathToClaudeCodeExecutable,
|
|
2092
2291
|
);
|
|
2093
2292
|
|
|
2293
|
+
let queryHandle;
|
|
2294
|
+
let clientGone = false;
|
|
2295
|
+
// This run's own teardown. The liveRuns entry is only removed when it is
|
|
2296
|
+
// still ours: hosts reuse a runId across retry attempts, and a successor
|
|
2297
|
+
// registered while our CLI winds down must not be evicted by our exit.
|
|
2298
|
+
const releaseRun = () => {
|
|
2299
|
+
clearInterval(heartbeat);
|
|
2300
|
+
clearPermissions();
|
|
2301
|
+
if (liveRuns.get(runId)?.query === queryHandle) liveRuns.delete(runId);
|
|
2302
|
+
promptStream.end();
|
|
2303
|
+
};
|
|
2304
|
+
// Host gone (settled, cancelled or crashed) \u2192 end the prompt so the CLI
|
|
2305
|
+
// winds down instead of living on with its background work. Detected on
|
|
2306
|
+
// the response: \`req\` emits "close" as soon as its body is consumed (Node
|
|
2307
|
+
// >= 16), long before any disconnect, while the response only closes
|
|
2308
|
+
// early when the socket dies before the stream finished.
|
|
2309
|
+
res.on("close", () => {
|
|
2310
|
+
if (res.writableFinished) return;
|
|
2311
|
+
clientGone = true;
|
|
2312
|
+
releaseRun();
|
|
2313
|
+
queryHandle?.interrupt().catch(() => {});
|
|
2314
|
+
});
|
|
2315
|
+
|
|
2094
2316
|
// Resume-if-exists gate. \`claude --resume <id>\` errors hard with "No
|
|
2095
2317
|
// conversation found with session ID" when the local session jsonl is
|
|
2096
2318
|
// missing \u2014 most often because a prior post-task snapshot failed and the
|
|
@@ -2121,7 +2343,8 @@ async function handleStart(req, res, runId) {
|
|
|
2121
2343
|
}
|
|
2122
2344
|
}
|
|
2123
2345
|
|
|
2124
|
-
|
|
2346
|
+
// Nobody left to stream to: do not start a CLI for it.
|
|
2347
|
+
if (clientGone) { res.end(); return; }
|
|
2125
2348
|
try {
|
|
2126
2349
|
queryHandle = query({
|
|
2127
2350
|
prompt: promptStream,
|
|
@@ -2144,28 +2367,19 @@ async function handleStart(req, res, runId) {
|
|
|
2144
2367
|
|
|
2145
2368
|
liveRuns.set(runId, { query: queryHandle, prompt: promptStream, permissions });
|
|
2146
2369
|
|
|
2147
|
-
//
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
liveRuns.delete(runId);
|
|
2153
|
-
promptStream.end();
|
|
2154
|
-
queryHandle.interrupt().catch(() => {});
|
|
2155
|
-
});
|
|
2156
|
-
|
|
2370
|
+
// Forward every SDKMessage, not just up to the first result: in
|
|
2371
|
+
// streaming-input mode the CLI keeps running after a turn ends and
|
|
2372
|
+
// re-prompts the model when background work finishes. The host decides
|
|
2373
|
+
// when the run is over and disconnects (res "close" above), which ends
|
|
2374
|
+
// the prompt and lets the CLI wind down.
|
|
2157
2375
|
try {
|
|
2158
2376
|
for await (const message of queryHandle) {
|
|
2159
2377
|
res.write(JSON.stringify(message) + "\\n");
|
|
2160
|
-
if (message.type === "result") break;
|
|
2161
2378
|
}
|
|
2162
2379
|
} catch (e) {
|
|
2163
2380
|
res.write(JSON.stringify({ _error: String(e?.message ?? e) }) + "\\n");
|
|
2164
2381
|
} finally {
|
|
2165
|
-
|
|
2166
|
-
clearPermissions();
|
|
2167
|
-
liveRuns.delete(runId);
|
|
2168
|
-
promptStream.end();
|
|
2382
|
+
releaseRun();
|
|
2169
2383
|
res.end();
|
|
2170
2384
|
}
|
|
2171
2385
|
}
|
|
@@ -2494,7 +2708,11 @@ var ClaudeCodeAgentAdapter = class {
|
|
|
2494
2708
|
const workflowSettings = buildClaudeWorkflowSettings(
|
|
2495
2709
|
options.provider?.ultracode
|
|
2496
2710
|
);
|
|
2497
|
-
const claudeSettings = {
|
|
2711
|
+
const claudeSettings = {
|
|
2712
|
+
...hookSettings,
|
|
2713
|
+
...workflowSettings,
|
|
2714
|
+
...options.provider?.fastMode !== void 0 ? { fastMode: options.provider.fastMode } : {}
|
|
2715
|
+
};
|
|
2498
2716
|
const mcpConfigJson = buildClaudeMcpConfig(options.mcps) ?? JSON.stringify({ mcpServers: {} }, null, 2);
|
|
2499
2717
|
const artifacts = [
|
|
2500
2718
|
...!sandbox ? [{ path: path8.join(target.layout.claudeDir, ".claude-plugin", "plugin.json"), content: JSON.stringify({ name: "agentbox", version: "1.0.0" }) }] : [],
|
|
@@ -2555,6 +2773,7 @@ var ClaudeCodeAgentAdapter = class {
|
|
|
2555
2773
|
// user inside our images.
|
|
2556
2774
|
IS_SANDBOX: "1"
|
|
2557
2775
|
};
|
|
2776
|
+
applyCliBackgroundWaitCeiling(env);
|
|
2558
2777
|
const customHeaders = request.options.customHeaders;
|
|
2559
2778
|
if (customHeaders && Object.keys(customHeaders).length > 0) {
|
|
2560
2779
|
const serialized = Object.entries(customHeaders).map(([name, value]) => `${name}: ${value}`).join("\n");
|
|
@@ -2604,17 +2823,23 @@ ${serialized}` : serialized;
|
|
|
2604
2823
|
}
|
|
2605
2824
|
};
|
|
2606
2825
|
const fetchAbort = new AbortController();
|
|
2826
|
+
const runUrl = `${baseUrl}/runs/${encodeURIComponent(request.runId)}`;
|
|
2607
2827
|
const cleanup = async () => {
|
|
2608
2828
|
try {
|
|
2609
|
-
await fetch(
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2829
|
+
await fetch(`${runUrl}/abort`, { method: "POST", headers: authHeaders });
|
|
2830
|
+
} catch {
|
|
2831
|
+
}
|
|
2832
|
+
try {
|
|
2833
|
+
await fetch(runUrl, { method: "DELETE", headers: authHeaders, signal: AbortSignal.timeout(3e3) });
|
|
2613
2834
|
} catch {
|
|
2614
2835
|
}
|
|
2615
2836
|
fetchAbort.abort();
|
|
2616
2837
|
};
|
|
2617
|
-
|
|
2838
|
+
let cancelled = false;
|
|
2839
|
+
sink.setAbort(async () => {
|
|
2840
|
+
cancelled = true;
|
|
2841
|
+
await cleanup();
|
|
2842
|
+
});
|
|
2618
2843
|
sink.onMessage(async (content) => {
|
|
2619
2844
|
const parts = await validateProviderUserInput(request.provider, content);
|
|
2620
2845
|
const mapped = mapToClaudeUserContent(parts);
|
|
@@ -2680,7 +2905,7 @@ ${serialized}` : serialized;
|
|
|
2680
2905
|
if (!reply.ok) throw new Error(`Claude permission response failed: ${reply.status}`);
|
|
2681
2906
|
}
|
|
2682
2907
|
};
|
|
2683
|
-
await consumeClaudeMessages(request, sink, permissionMessages(), executeStartedAt, cleanup);
|
|
2908
|
+
await consumeClaudeMessages(request, sink, permissionMessages(), executeStartedAt, cleanup, () => cancelled);
|
|
2684
2909
|
return async () => void 0;
|
|
2685
2910
|
}
|
|
2686
2911
|
/**
|
|
@@ -2741,24 +2966,66 @@ ${serialized}` : serialized;
|
|
|
2741
2966
|
}
|
|
2742
2967
|
}
|
|
2743
2968
|
};
|
|
2744
|
-
async function consumeClaudeMessages(request, sink, messages, executeStartedAt, cleanup, wasCancelled = () => false) {
|
|
2969
|
+
async function consumeClaudeMessages(request, sink, messages, executeStartedAt, cleanup, wasCancelled = () => false, wait = {}) {
|
|
2745
2970
|
let accumulatedText = "";
|
|
2746
2971
|
let streamedThinkingChars = 0;
|
|
2747
|
-
let pendingMessages = 1;
|
|
2748
2972
|
let sawResult = false;
|
|
2749
2973
|
let firstStreamEventLogged = false;
|
|
2750
2974
|
let firstTextDeltaLogged = false;
|
|
2751
2975
|
let lastTerminalReason;
|
|
2752
2976
|
let lastIsError = false;
|
|
2753
2977
|
const rawPayloads = [];
|
|
2978
|
+
const tracker = new BackgroundTaskTracker();
|
|
2979
|
+
const timeoutMs = resolveBackgroundTaskTimeoutMs(request.options.backgroundTaskTimeoutMs);
|
|
2980
|
+
const graceMs = wait.graceMs ?? BACKGROUND_TASK_GRACE_MS;
|
|
2981
|
+
let pendingWait;
|
|
2982
|
+
let waitedMs = 0;
|
|
2983
|
+
let expiry;
|
|
2984
|
+
let lastTasksKey = JSON.stringify({ tasks: [], waiting: false });
|
|
2985
|
+
const emitTasks = (tasks, waiting) => {
|
|
2986
|
+
const key = JSON.stringify({ tasks, waiting });
|
|
2987
|
+
if (key === lastTasksKey) return;
|
|
2988
|
+
lastTasksKey = key;
|
|
2989
|
+
sink.emitEvent(createNormalizedEvent("background.tasks", { provider: request.provider, runId: request.runId }, { tasks, waiting }));
|
|
2990
|
+
};
|
|
2991
|
+
const isAborted = () => wasCancelled() || lastTerminalReason === "aborted_streaming" || lastTerminalReason === "aborted_tools";
|
|
2992
|
+
const endWait = () => {
|
|
2993
|
+
if (!pendingWait) return;
|
|
2994
|
+
waitedMs += pendingWait.elapsedMs();
|
|
2995
|
+
pendingWait.clear();
|
|
2996
|
+
pendingWait = void 0;
|
|
2997
|
+
};
|
|
2998
|
+
const settleOnFailure = (error) => {
|
|
2999
|
+
if (!pendingWait || !sawResult || lastIsError) return false;
|
|
3000
|
+
debugClaude("\u2605 transport failed during background wait; settling on the last result: %o", error);
|
|
3001
|
+
expiry = "transport";
|
|
3002
|
+
return true;
|
|
3003
|
+
};
|
|
3004
|
+
const iterator = messages[Symbol.asyncIterator]();
|
|
2754
3005
|
try {
|
|
2755
|
-
for
|
|
3006
|
+
for (let next = iterator.next(); ; next = iterator.next()) {
|
|
3007
|
+
let step;
|
|
3008
|
+
try {
|
|
3009
|
+
step = pendingWait ? await Promise.race([
|
|
3010
|
+
next.then((result) => ({ result })),
|
|
3011
|
+
pendingWait.expired.then((reason) => ({ reason }))
|
|
3012
|
+
]) : { result: await next };
|
|
3013
|
+
} catch (error) {
|
|
3014
|
+
if (!settleOnFailure(error)) throw error;
|
|
3015
|
+
break;
|
|
3016
|
+
}
|
|
3017
|
+
if ("reason" in step) {
|
|
3018
|
+
expiry = step.reason;
|
|
3019
|
+
break;
|
|
3020
|
+
}
|
|
3021
|
+
if (step.result.done) break;
|
|
3022
|
+
const item = step.result.value;
|
|
2756
3023
|
if (item && typeof item === "object") {
|
|
2757
3024
|
const ctrl = item;
|
|
2758
3025
|
if ("_error" in ctrl) {
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
3026
|
+
const error = new Error(String(ctrl._error ?? "daemon error"));
|
|
3027
|
+
if (!settleOnFailure(error)) throw error;
|
|
3028
|
+
break;
|
|
2762
3029
|
}
|
|
2763
3030
|
if ("_notice" in ctrl) {
|
|
2764
3031
|
debugClaude("daemon notice: %o", ctrl);
|
|
@@ -2775,6 +3042,12 @@ async function consumeClaudeMessages(request, sink, messages, executeStartedAt,
|
|
|
2775
3042
|
const message = item;
|
|
2776
3043
|
rawPayloads.push(message);
|
|
2777
3044
|
sink.emitRaw(toRawEvent(request.runId, message, message.type));
|
|
3045
|
+
if (tracker.ingest(message) && pendingWait) {
|
|
3046
|
+
debugClaude("\u2605 follow-up turn started; background wait over (%dms since execute start)", Date.now() - executeStartedAt);
|
|
3047
|
+
endWait();
|
|
3048
|
+
}
|
|
3049
|
+
emitTasks(tracker.liveTasks(), pendingWait !== void 0);
|
|
3050
|
+
pendingWait?.setIdle(tracker.liveTasks().length === 0);
|
|
2778
3051
|
if (message.type === "system") {
|
|
2779
3052
|
const sub = message.subtype;
|
|
2780
3053
|
if (sub === "init") {
|
|
@@ -2891,15 +3164,33 @@ async function consumeClaudeMessages(request, sink, messages, executeStartedAt,
|
|
|
2891
3164
|
if (resultText && resultText !== accumulatedText) {
|
|
2892
3165
|
accumulatedText = resultText;
|
|
2893
3166
|
}
|
|
2894
|
-
|
|
2895
|
-
if (
|
|
3167
|
+
const live = tracker.liveTasks();
|
|
3168
|
+
if (timeoutMs === 0 || !tracker.hasSeenBackgroundWork() || isAborted()) break;
|
|
3169
|
+
debugClaude("\u2605 turn ended with %d background task(s); waiting", live.length);
|
|
3170
|
+
endWait();
|
|
3171
|
+
pendingWait = new BackgroundWait(graceMs, Math.max(0, timeoutMs - waitedMs));
|
|
3172
|
+
pendingWait.setIdle(live.length === 0);
|
|
3173
|
+
emitTasks(live, true);
|
|
2896
3174
|
continue;
|
|
2897
3175
|
}
|
|
2898
3176
|
}
|
|
3177
|
+
if (expiry === "ceiling") {
|
|
3178
|
+
const ids = tracker.liveTasks().filter((task) => task.type !== "scheduled_wakeup").map((task) => task.id);
|
|
3179
|
+
debugClaude("\u2605 background wait ceiling (%dms) hit; stopping %d task(s)", timeoutMs, ids.length);
|
|
3180
|
+
if (wait.stopTasks) {
|
|
3181
|
+
await withTimeout(wait.stopTasks(ids), wait.stopTimeoutMs ?? STOP_TASKS_TIMEOUT_MS).catch(() => void 0);
|
|
3182
|
+
}
|
|
3183
|
+
} else if (expiry === "grace") {
|
|
3184
|
+
debugClaude("\u2605 background set emptied with no follow-up turn; settling");
|
|
3185
|
+
}
|
|
3186
|
+
if (pendingWait) {
|
|
3187
|
+
endWait();
|
|
3188
|
+
emitTasks([], false);
|
|
3189
|
+
}
|
|
2899
3190
|
await cleanup();
|
|
2900
3191
|
if (!sawResult && !wasCancelled()) throw new Error("Claude Code closed before reporting a result");
|
|
2901
3192
|
const finalText = accumulatedText;
|
|
2902
|
-
const isCancelled =
|
|
3193
|
+
const isCancelled = isAborted();
|
|
2903
3194
|
const isError = !isCancelled && lastIsError;
|
|
2904
3195
|
if (isCancelled) {
|
|
2905
3196
|
debugClaude(
|
|
@@ -2941,10 +3232,11 @@ async function consumeClaudeMessages(request, sink, messages, executeStartedAt,
|
|
|
2941
3232
|
});
|
|
2942
3233
|
}
|
|
2943
3234
|
} finally {
|
|
3235
|
+
pendingWait?.clear();
|
|
2944
3236
|
await cleanup();
|
|
2945
3237
|
}
|
|
2946
3238
|
}
|
|
2947
|
-
async function executeNativeClaude(request, sink) {
|
|
3239
|
+
async function executeNativeClaude(request, sink, wait = {}) {
|
|
2948
3240
|
const { query } = await import("@anthropic-ai/claude-agent-sdk");
|
|
2949
3241
|
const claudeDir = claudeConfigDir(request.options);
|
|
2950
3242
|
const input = await validateProviderUserInput(request.provider, request.run.input);
|
|
@@ -2969,6 +3261,7 @@ async function executeNativeClaude(request, sink) {
|
|
|
2969
3261
|
const messageId = randomUUID();
|
|
2970
3262
|
prompt.push({ type: "user", uuid: messageId, message: { role: "user", content: mapToClaudeUserContent(input) }, parent_tool_use_id: null });
|
|
2971
3263
|
const hostEnv = Object.fromEntries(Object.entries({ ...process.env, ...request.options.env }).filter((entry) => entry[1] !== void 0));
|
|
3264
|
+
applyCliBackgroundWaitCeiling(hostEnv);
|
|
2972
3265
|
if (request.options.customHeaders) {
|
|
2973
3266
|
const headers = Object.entries(request.options.customHeaders).map(([name, value]) => `${name}: ${value}`).join("\n");
|
|
2974
3267
|
hostEnv.ANTHROPIC_CUSTOM_HEADERS = [hostEnv.ANTHROPIC_CUSTOM_HEADERS, headers].filter(Boolean).join("\n");
|
|
@@ -3041,10 +3334,17 @@ async function executeNativeClaude(request, sink) {
|
|
|
3041
3334
|
}
|
|
3042
3335
|
}
|
|
3043
3336
|
} });
|
|
3044
|
-
|
|
3337
|
+
const live = handle;
|
|
3338
|
+
sink.setRaw({ query: live, claudeDir, runId: request.runId });
|
|
3045
3339
|
sink.emitEvent(createNormalizedEvent("run.started", { provider: request.provider, runId: request.runId }));
|
|
3046
3340
|
sink.emitEvent(createNormalizedEvent("message.started", { provider: request.provider, runId: request.runId }, { messageId }));
|
|
3047
|
-
await consumeClaudeMessages(request, sink,
|
|
3341
|
+
await consumeClaudeMessages(request, sink, live, Date.now(), stop, () => cancelled, {
|
|
3342
|
+
...wait,
|
|
3343
|
+
// Native owns the CLI: ask it to stop leftover tasks before closing it.
|
|
3344
|
+
stopTasks: async (ids) => {
|
|
3345
|
+
await Promise.all(ids.map((id) => live.stopTask(id).catch(() => void 0)));
|
|
3346
|
+
}
|
|
3347
|
+
});
|
|
3048
3348
|
} catch (error) {
|
|
3049
3349
|
await stop();
|
|
3050
3350
|
if (cancelled) sink.cancel();
|
|
@@ -3350,7 +3650,8 @@ function buildThreadParams(cwd, options, request) {
|
|
|
3350
3650
|
return {
|
|
3351
3651
|
cwd,
|
|
3352
3652
|
model: request.run.model ?? null,
|
|
3353
|
-
...options.
|
|
3653
|
+
...request.options.provider?.serviceTier !== void 0 ? { serviceTier: request.options.provider.serviceTier } : {},
|
|
3654
|
+
...options.provider?.approvalPolicy ? { approvalPolicy: options.provider.approvalPolicy } : options.configuration === "native" && !options.fullAccess ? {} : { approvalPolicy: !options.fullAccess && isInteractiveApproval(options) ? "untrusted" : "never" },
|
|
3354
3655
|
sandbox: buildCodexSandboxMode(options),
|
|
3355
3656
|
serviceName: "agentbox",
|
|
3356
3657
|
// Persist the rollout on disk so follow-up runs can call `thread/resume`.
|
|
@@ -3365,7 +3666,8 @@ function buildResumeParams(cwd, options, request) {
|
|
|
3365
3666
|
threadId: request.run.resumeSessionId,
|
|
3366
3667
|
cwd,
|
|
3367
3668
|
model: request.run.model ?? null,
|
|
3368
|
-
...options.
|
|
3669
|
+
...request.options.provider?.serviceTier !== void 0 ? { serviceTier: request.options.provider.serviceTier } : {},
|
|
3670
|
+
...options.provider?.approvalPolicy ? { approvalPolicy: options.provider.approvalPolicy } : options.configuration === "native" && !options.fullAccess ? {} : { approvalPolicy: !options.fullAccess && isInteractiveApproval(options) ? "untrusted" : "never" },
|
|
3369
3671
|
sandbox: buildCodexSandboxMode(options),
|
|
3370
3672
|
...request.run.systemPrompt ? { developerInstructions: request.run.systemPrompt } : options.configuration === "native" ? {} : { developerInstructions: null },
|
|
3371
3673
|
// We only need the thread id back; we never read `thread.turns`.
|
|
@@ -3381,7 +3683,8 @@ function buildForkParams(cwd, options, request) {
|
|
|
3381
3683
|
lastTurnId: request.run.forkAtMessageId ?? null,
|
|
3382
3684
|
cwd,
|
|
3383
3685
|
model: request.run.model ?? null,
|
|
3384
|
-
...options.
|
|
3686
|
+
...request.options.provider?.serviceTier !== void 0 ? { serviceTier: request.options.provider.serviceTier } : {},
|
|
3687
|
+
...options.provider?.approvalPolicy ? { approvalPolicy: options.provider.approvalPolicy } : options.configuration === "native" && !options.fullAccess ? {} : { approvalPolicy: !options.fullAccess && isInteractiveApproval(options) ? "untrusted" : "never" },
|
|
3385
3688
|
sandbox: buildCodexSandboxMode(options),
|
|
3386
3689
|
...request.run.systemPrompt ? { developerInstructions: request.run.systemPrompt } : options.configuration === "native" ? {} : { developerInstructions: null },
|
|
3387
3690
|
excludeTurns: true
|
|
@@ -3415,11 +3718,12 @@ function buildCodexTurnStartParams(params) {
|
|
|
3415
3718
|
return {
|
|
3416
3719
|
threadId,
|
|
3417
3720
|
input: inputItems,
|
|
3418
|
-
...request.options.configuration === "native" && !request.options.fullAccess ? {} : {
|
|
3721
|
+
...request.options.provider?.approvalPolicy ? { approvalPolicy: request.options.provider.approvalPolicy } : request.options.configuration === "native" && !request.options.fullAccess ? {} : {
|
|
3419
3722
|
approvalPolicy: !request.options.fullAccess && isInteractiveApproval(request.options) ? "untrusted" : "never"
|
|
3420
3723
|
},
|
|
3421
3724
|
...sandboxPolicy ? { sandboxPolicy } : {},
|
|
3422
3725
|
model: request.run.model ?? null,
|
|
3726
|
+
...request.options.provider?.serviceTier !== void 0 ? { serviceTier: request.options.provider.serviceTier } : {},
|
|
3423
3727
|
effort: request.run.reasoning ?? null,
|
|
3424
3728
|
...request.run.mode ? { collaborationMode: {
|
|
3425
3729
|
mode: request.run.mode,
|
|
@@ -3428,6 +3732,23 @@ function buildCodexTurnStartParams(params) {
|
|
|
3428
3732
|
outputSchema: null
|
|
3429
3733
|
};
|
|
3430
3734
|
}
|
|
3735
|
+
var CODEX_CANCEL_TURN_TEXT = "Run cancelled by the host.";
|
|
3736
|
+
async function terminateBackgroundTerminals(client, threadId) {
|
|
3737
|
+
await withTimeout((async () => {
|
|
3738
|
+
try {
|
|
3739
|
+
const listed = await client.request(
|
|
3740
|
+
"thread/backgroundTerminals/list",
|
|
3741
|
+
{ threadId }
|
|
3742
|
+
);
|
|
3743
|
+
for (const terminal of listed?.data ?? []) {
|
|
3744
|
+
if (typeof terminal?.processId !== "string") continue;
|
|
3745
|
+
await client.request("thread/backgroundTerminals/terminate", { threadId, processId: terminal.processId });
|
|
3746
|
+
}
|
|
3747
|
+
} catch (error) {
|
|
3748
|
+
debugCodex("background terminal termination stopped early: %o", error);
|
|
3749
|
+
}
|
|
3750
|
+
})(), STOP_TASKS_TIMEOUT_MS);
|
|
3751
|
+
}
|
|
3431
3752
|
function toRawEvent2(runId, payload, type) {
|
|
3432
3753
|
return {
|
|
3433
3754
|
provider: AgentProvider.Codex,
|
|
@@ -3584,6 +3905,42 @@ function createCodexPermissionEvent(request, notification, fileChanges) {
|
|
|
3584
3905
|
}
|
|
3585
3906
|
return null;
|
|
3586
3907
|
}
|
|
3908
|
+
var CODEX_ELICITATION_METHOD = "mcpServer/elicitation/request";
|
|
3909
|
+
function createCodexElicitationPermissionEvent(request, notification) {
|
|
3910
|
+
if (notification.method !== CODEX_ELICITATION_METHOD || notification.id === void 0) {
|
|
3911
|
+
return null;
|
|
3912
|
+
}
|
|
3913
|
+
const params = notification.params ?? {};
|
|
3914
|
+
const meta = params._meta ?? {};
|
|
3915
|
+
if (meta.codex_approval_kind !== "mcp_tool_call") {
|
|
3916
|
+
return null;
|
|
3917
|
+
}
|
|
3918
|
+
const raw = toRawEvent2(request.runId, notification, notification.method);
|
|
3919
|
+
const toolName = typeof meta.tool_name === "string" ? meta.tool_name : void 0;
|
|
3920
|
+
const server = typeof params.serverName === "string" ? params.serverName : void 0;
|
|
3921
|
+
const persist = Array.isArray(meta.persist) ? meta.persist : [];
|
|
3922
|
+
return createNormalizedEvent(
|
|
3923
|
+
"permission.requested",
|
|
3924
|
+
{ provider: request.provider, runId: request.runId, raw },
|
|
3925
|
+
{
|
|
3926
|
+
requestId: String(notification.id),
|
|
3927
|
+
kind: "tool",
|
|
3928
|
+
toolName: toolName ?? server,
|
|
3929
|
+
title: "Approve tool call",
|
|
3930
|
+
message: typeof params.message === "string" && params.message.trim() ? params.message : `Codex wants to call ${toolName ?? "an MCP tool"}${server ? ` on ${server}` : ""}.`,
|
|
3931
|
+
input: { server, tool: toolName, arguments: meta.tool_params, ...params },
|
|
3932
|
+
canRemember: persist.includes("session")
|
|
3933
|
+
}
|
|
3934
|
+
);
|
|
3935
|
+
}
|
|
3936
|
+
function toCodexElicitationResult(notification, response) {
|
|
3937
|
+
if (response.decision === "deny") {
|
|
3938
|
+
return { action: "decline", content: null };
|
|
3939
|
+
}
|
|
3940
|
+
const meta = notification.params?._meta ?? {};
|
|
3941
|
+
const persist = Array.isArray(meta.persist) ? meta.persist : [];
|
|
3942
|
+
return response.remember && persist.includes("session") ? { action: "accept", content: null, _meta: { persist: "session" } } : { action: "accept", content: null };
|
|
3943
|
+
}
|
|
3587
3944
|
function toCodexApprovalDecision(notification, response) {
|
|
3588
3945
|
const params = notification.params ?? {};
|
|
3589
3946
|
const availableDecisions = Array.isArray(params.availableDecisions) ? params.availableDecisions : [];
|
|
@@ -4149,7 +4506,6 @@ var CodexAgentAdapter = class {
|
|
|
4149
4506
|
const interactiveApproval = isInteractiveApproval(request.options);
|
|
4150
4507
|
let rootThreadId;
|
|
4151
4508
|
let turnId;
|
|
4152
|
-
let pendingTurns = 1;
|
|
4153
4509
|
let abortInvoked = false;
|
|
4154
4510
|
sink.setAbort(async () => {
|
|
4155
4511
|
abortInvoked = true;
|
|
@@ -4201,10 +4557,63 @@ var CodexAgentAdapter = class {
|
|
|
4201
4557
|
const pendingFileChanges = /* @__PURE__ */ new Map();
|
|
4202
4558
|
const fileItemKey = (params, itemId) => typeof params?.threadId === "string" && typeof params.turnId === "string" && typeof itemId === "string" ? `${params.threadId}:${params.turnId}:${itemId}` : void 0;
|
|
4203
4559
|
let streamedText = "";
|
|
4560
|
+
const timeoutMs = resolveBackgroundTaskTimeoutMs(request.options.backgroundTaskTimeoutMs);
|
|
4561
|
+
const isRootThread = (params) => !params?.threadId || params.threadId === rootThreadId;
|
|
4562
|
+
let goalStatus = request.run.goal ? "active" : void 0;
|
|
4563
|
+
let pendingWait;
|
|
4564
|
+
let waitedMs = 0;
|
|
4565
|
+
let turnMessageText = "";
|
|
4566
|
+
let lastTurn;
|
|
4567
|
+
let goalWaiting = false;
|
|
4568
|
+
const emitGoalWaiting = (waiting) => {
|
|
4569
|
+
if (waiting === goalWaiting) return;
|
|
4570
|
+
goalWaiting = waiting;
|
|
4571
|
+
sink.emitEvent(createNormalizedEvent("background.tasks", { provider: request.provider, runId: request.runId }, { tasks: [], waiting }));
|
|
4572
|
+
};
|
|
4573
|
+
const endWait = () => {
|
|
4574
|
+
if (!pendingWait) return;
|
|
4575
|
+
waitedMs += pendingWait.elapsedMs();
|
|
4576
|
+
pendingWait.clear();
|
|
4577
|
+
pendingWait = void 0;
|
|
4578
|
+
emitGoalWaiting(false);
|
|
4579
|
+
};
|
|
4204
4580
|
const completion = new Promise((resolve, reject) => {
|
|
4581
|
+
const settle = () => {
|
|
4582
|
+
endWait();
|
|
4583
|
+
sink.emitEvent(createNormalizedEvent("run.completed", { provider: request.provider, runId: request.runId }, { text: lastTurn?.messageText || void 0 }));
|
|
4584
|
+
resolve({ text: lastTurn?.text ?? streamedText, turnId, threadId: rootThreadId, interrupted: false });
|
|
4585
|
+
};
|
|
4586
|
+
const settleOnFailure = (error) => {
|
|
4587
|
+
if (!pendingWait || !lastTurn || abortInvoked) return false;
|
|
4588
|
+
debugCodex("\u2605 transport failed during goal wait; settling on the last turn: %o", error);
|
|
4589
|
+
settle();
|
|
4590
|
+
return true;
|
|
4591
|
+
};
|
|
4205
4592
|
void (async () => {
|
|
4206
4593
|
let firstClientMessageLogged = false;
|
|
4207
|
-
|
|
4594
|
+
const iterator = client.messages()[Symbol.asyncIterator]();
|
|
4595
|
+
for (let next = iterator.next(); ; next = iterator.next()) {
|
|
4596
|
+
let step;
|
|
4597
|
+
try {
|
|
4598
|
+
step = pendingWait ? await Promise.race([
|
|
4599
|
+
next.then((result) => ({ result })),
|
|
4600
|
+
pendingWait.expired.then((reason) => ({ reason }))
|
|
4601
|
+
]) : { result: await next };
|
|
4602
|
+
} catch (error) {
|
|
4603
|
+
if (settleOnFailure(error)) return;
|
|
4604
|
+
throw error;
|
|
4605
|
+
}
|
|
4606
|
+
if ("reason" in step) {
|
|
4607
|
+
if (!abortInvoked) {
|
|
4608
|
+
debugCodex("\u2605 native goal wait over (%s)", step.reason);
|
|
4609
|
+
settle();
|
|
4610
|
+
return;
|
|
4611
|
+
}
|
|
4612
|
+
endWait();
|
|
4613
|
+
step = { result: await next };
|
|
4614
|
+
}
|
|
4615
|
+
if (step.result.done) break;
|
|
4616
|
+
const message = step.result.value;
|
|
4208
4617
|
if (!firstClientMessageLogged) {
|
|
4209
4618
|
firstClientMessageLogged = true;
|
|
4210
4619
|
debugCodex(
|
|
@@ -4260,43 +4669,92 @@ var CodexAgentAdapter = class {
|
|
|
4260
4669
|
if (approvalKey) pendingFileChanges.delete(approvalKey);
|
|
4261
4670
|
continue;
|
|
4262
4671
|
}
|
|
4672
|
+
const elicitation = createCodexElicitationPermissionEvent(request, message);
|
|
4673
|
+
if (elicitation && message.id !== void 0) {
|
|
4674
|
+
const response = interactiveApproval ? await sink.requestPermission(elicitation) : { requestId: elicitation.requestId, decision: "allow" };
|
|
4675
|
+
await client.respond(message.id, toCodexElicitationResult(message, response));
|
|
4676
|
+
continue;
|
|
4677
|
+
}
|
|
4678
|
+
if (message.id !== void 0) {
|
|
4679
|
+
debugCodex("unsupported server request %s; declining", message.method);
|
|
4680
|
+
await (message.method === CODEX_ELICITATION_METHOD ? client.respond(message.id, { action: "cancel", content: null }) : client.respondError(message.id, {
|
|
4681
|
+
code: -32601,
|
|
4682
|
+
message: `Unsupported request ${message.method}`
|
|
4683
|
+
}));
|
|
4684
|
+
continue;
|
|
4685
|
+
}
|
|
4263
4686
|
if (message.method === "item/completed") {
|
|
4264
4687
|
const item2 = message.params?.item;
|
|
4265
4688
|
if (item2?.type === "plan" && typeof item2.text === "string") {
|
|
4266
4689
|
sink.emitEvent(createNormalizedEvent("plan.completed", { provider: request.provider, runId: request.runId }, { text: item2.text }));
|
|
4267
4690
|
}
|
|
4268
4691
|
}
|
|
4692
|
+
if (rootThreadId && message.params && message.params.threadId === rootThreadId) {
|
|
4693
|
+
if (message.method === "thread/goal/updated") {
|
|
4694
|
+
const goal = message.params.goal;
|
|
4695
|
+
if (!message.params.turnId || message.params.turnId === turnId) {
|
|
4696
|
+
goalStatus = typeof goal?.status === "string" ? goal.status : void 0;
|
|
4697
|
+
if (goalStatus !== "active" && pendingWait && !abortInvoked) {
|
|
4698
|
+
settle();
|
|
4699
|
+
return;
|
|
4700
|
+
}
|
|
4701
|
+
}
|
|
4702
|
+
} else if (message.method === "thread/goal/cleared") {
|
|
4703
|
+
goalStatus = void 0;
|
|
4704
|
+
if (pendingWait && !abortInvoked) {
|
|
4705
|
+
settle();
|
|
4706
|
+
return;
|
|
4707
|
+
}
|
|
4708
|
+
}
|
|
4709
|
+
}
|
|
4710
|
+
const turn = message.params?.turn;
|
|
4711
|
+
const rootTurnCompleted = message.method === "turn/completed" && isRootThread(message.params);
|
|
4712
|
+
const waitAfterTurn = rootTurnCompleted && timeoutMs !== 0 && !abortInvoked && turn?.status === "completed" && goalStatus === "active";
|
|
4269
4713
|
for (const event of toNormalizedCodexEvents(request.runId, message)) {
|
|
4714
|
+
if (event.type === "run.completed" && (!rootTurnCompleted || waitAfterTurn || turn?.status === "interrupted")) continue;
|
|
4270
4715
|
sink.emitEvent(event);
|
|
4271
4716
|
if (event.type === "text.delta") {
|
|
4272
4717
|
streamedText += event.delta;
|
|
4718
|
+
} else if (event.type === "message.completed" && event.text) {
|
|
4719
|
+
turnMessageText = event.text;
|
|
4273
4720
|
}
|
|
4274
4721
|
}
|
|
4275
4722
|
if (message.method === "thread/started" && !rootThreadId) {
|
|
4276
4723
|
rootThreadId = message.params?.thread?.id ?? rootThreadId;
|
|
4277
4724
|
}
|
|
4278
4725
|
if (message.method === "turn/started") {
|
|
4279
|
-
|
|
4726
|
+
if (isRootThread(message.params)) {
|
|
4727
|
+
turnId = turn?.id ?? turnId;
|
|
4728
|
+
streamedText = "";
|
|
4729
|
+
turnMessageText = "";
|
|
4730
|
+
if (pendingWait) debugCodex("\u2605 follow-up turn started; native goal wait over");
|
|
4731
|
+
endWait();
|
|
4732
|
+
}
|
|
4280
4733
|
}
|
|
4281
|
-
if (
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
const interrupted = turn?.status === "interrupted";
|
|
4734
|
+
if (rootTurnCompleted) {
|
|
4735
|
+
const previousText = lastTurn?.text;
|
|
4736
|
+
lastTurn = { text: streamedText, messageText: turnMessageText };
|
|
4737
|
+
if (!waitAfterTurn) {
|
|
4286
4738
|
resolve({
|
|
4287
|
-
text: streamedText,
|
|
4739
|
+
text: streamedText || previousText,
|
|
4288
4740
|
turnId,
|
|
4289
4741
|
threadId: rootThreadId,
|
|
4290
|
-
interrupted
|
|
4742
|
+
interrupted: turn?.status === "interrupted"
|
|
4291
4743
|
});
|
|
4292
4744
|
return;
|
|
4293
4745
|
}
|
|
4746
|
+
debugCodex("\u2605 turn ended with an active goal; waiting for native continuation");
|
|
4747
|
+
endWait();
|
|
4748
|
+
pendingWait = new BackgroundWait(BACKGROUND_TASK_GRACE_MS, Math.max(0, timeoutMs - waitedMs));
|
|
4749
|
+
pendingWait.setIdle(true);
|
|
4750
|
+
emitGoalWaiting(true);
|
|
4294
4751
|
}
|
|
4295
4752
|
if (message.method === "error" && !shouldIgnoreCodexError(message)) {
|
|
4296
4753
|
reject(message);
|
|
4297
4754
|
return;
|
|
4298
4755
|
}
|
|
4299
4756
|
}
|
|
4757
|
+
if (settleOnFailure(new Error("Codex transport closed."))) return;
|
|
4300
4758
|
reject(new Error("Codex transport closed before run completed."));
|
|
4301
4759
|
})().catch(reject);
|
|
4302
4760
|
});
|
|
@@ -4367,6 +4825,7 @@ var CodexAgentAdapter = class {
|
|
|
4367
4825
|
} catch (err) {
|
|
4368
4826
|
completionError = err;
|
|
4369
4827
|
}
|
|
4828
|
+
endWait();
|
|
4370
4829
|
if (completionError !== void 0) {
|
|
4371
4830
|
if (abortInvoked) {
|
|
4372
4831
|
debugCodex(
|
|
@@ -4374,7 +4833,7 @@ var CodexAgentAdapter = class {
|
|
|
4374
4833
|
Date.now() - executeStartedAt
|
|
4375
4834
|
);
|
|
4376
4835
|
sink.cancel({
|
|
4377
|
-
text: streamedText || void 0,
|
|
4836
|
+
text: streamedText || lastTurn?.text || void 0,
|
|
4378
4837
|
costData: extractCodexCostData(rawPayloads)
|
|
4379
4838
|
});
|
|
4380
4839
|
} else {
|
|
@@ -4399,6 +4858,7 @@ var CodexAgentAdapter = class {
|
|
|
4399
4858
|
}
|
|
4400
4859
|
}
|
|
4401
4860
|
} finally {
|
|
4861
|
+
pendingWait?.clear();
|
|
4402
4862
|
await runtime.cleanup().catch(() => void 0);
|
|
4403
4863
|
}
|
|
4404
4864
|
return async () => void 0;
|
|
@@ -4411,36 +4871,51 @@ var CodexAgentAdapter = class {
|
|
|
4411
4871
|
* driven by the normalized `message.started` event whose
|
|
4412
4872
|
* `messageId` IS the codex turnId).
|
|
4413
4873
|
*
|
|
4414
|
-
*
|
|
4874
|
+
* When that interrupt is rejected — the thread is between native goal
|
|
4875
|
+
* turns, or the turn id is stale or missing — a turn is started only to be
|
|
4876
|
+
* interrupted, so the originating run still observes an interrupted turn
|
|
4877
|
+
* and cancels; the model's leftover processes are then terminated.
|
|
4878
|
+
* Without `sessionId` the call is a no-op.
|
|
4415
4879
|
*/
|
|
4416
4880
|
async attachAbort(request) {
|
|
4417
4881
|
const threadId = request.sessionId;
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
debugCodex(
|
|
4421
|
-
"attachAbort runId=%s skipped: threadId=%s turnId=%s",
|
|
4422
|
-
request.runId,
|
|
4423
|
-
threadId,
|
|
4424
|
-
turnId
|
|
4425
|
-
);
|
|
4882
|
+
if (!threadId) {
|
|
4883
|
+
debugCodex("attachAbort runId=%s skipped: no threadId", request.runId);
|
|
4426
4884
|
return;
|
|
4427
4885
|
}
|
|
4428
4886
|
await withCodexAppServer(request, async (client) => {
|
|
4429
|
-
|
|
4430
|
-
|
|
4887
|
+
const bounded = (what, promise) => Promise.race([
|
|
4888
|
+
promise,
|
|
4431
4889
|
new Promise(
|
|
4432
|
-
(_, reject) => setTimeout(
|
|
4433
|
-
() => reject(new Error("codex turn/interrupt timed out")),
|
|
4434
|
-
3e3
|
|
4435
|
-
)
|
|
4890
|
+
(_, reject) => setTimeout(() => reject(new Error(`codex ${what} timed out`)), 3e3)
|
|
4436
4891
|
)
|
|
4437
|
-
])
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4892
|
+
]);
|
|
4893
|
+
const interrupt = (turnId) => bounded("turn/interrupt", client.request("turn/interrupt", { threadId, turnId }));
|
|
4894
|
+
if (request.turnId) {
|
|
4895
|
+
try {
|
|
4896
|
+
await interrupt(request.turnId);
|
|
4897
|
+
return;
|
|
4898
|
+
} catch (error) {
|
|
4899
|
+
debugCodex("attachAbort runId=%s turn/interrupt failed: %o", request.runId, error);
|
|
4900
|
+
}
|
|
4901
|
+
}
|
|
4902
|
+
try {
|
|
4903
|
+
const response = await bounded(
|
|
4904
|
+
"turn/start",
|
|
4905
|
+
client.request("turn/start", {
|
|
4906
|
+
threadId,
|
|
4907
|
+
input: [{ type: "text", text: CODEX_CANCEL_TURN_TEXT, text_elements: [] }],
|
|
4908
|
+
approvalPolicy: "never",
|
|
4909
|
+
model: null,
|
|
4910
|
+
effort: null,
|
|
4911
|
+
outputSchema: null
|
|
4912
|
+
})
|
|
4442
4913
|
);
|
|
4443
|
-
|
|
4914
|
+
if (typeof response?.turn?.id === "string") await interrupt(response.turn.id);
|
|
4915
|
+
} catch (error) {
|
|
4916
|
+
debugCodex("attachAbort runId=%s cancel turn failed: %o", request.runId, error);
|
|
4917
|
+
}
|
|
4918
|
+
await terminateBackgroundTerminals(client, threadId);
|
|
4444
4919
|
});
|
|
4445
4920
|
}
|
|
4446
4921
|
/**
|
|
@@ -4569,6 +5044,9 @@ function toRawEvent3(runId, payload, type) {
|
|
|
4569
5044
|
payload
|
|
4570
5045
|
};
|
|
4571
5046
|
}
|
|
5047
|
+
function injectedTaskResultChild(text2) {
|
|
5048
|
+
return /<task id="?([^"\s>]+)"? state="?(?:completed|error)"?>/.exec(text2)?.[1];
|
|
5049
|
+
}
|
|
4572
5050
|
function toOpenCodeModel(model) {
|
|
4573
5051
|
if (!model) {
|
|
4574
5052
|
return void 0;
|
|
@@ -5094,35 +5572,149 @@ var OpenCodeAgentAdapter = class {
|
|
|
5094
5572
|
resolveSessionTerminal = resolve;
|
|
5095
5573
|
});
|
|
5096
5574
|
let lastSseActivityAt = Date.now();
|
|
5575
|
+
const postAbort = async (id) => {
|
|
5576
|
+
try {
|
|
5577
|
+
await Promise.race([
|
|
5578
|
+
fetchJson(`${runtime.baseUrl}/session/${id}/abort`, {
|
|
5579
|
+
method: "POST",
|
|
5580
|
+
headers: {
|
|
5581
|
+
"content-type": "application/json",
|
|
5582
|
+
...runtime.previewHeaders
|
|
5583
|
+
}
|
|
5584
|
+
}),
|
|
5585
|
+
new Promise(
|
|
5586
|
+
(_, reject) => setTimeout(
|
|
5587
|
+
() => reject(new Error("opencode POST /session/abort timed out")),
|
|
5588
|
+
3e3
|
|
5589
|
+
)
|
|
5590
|
+
)
|
|
5591
|
+
]);
|
|
5592
|
+
} catch {
|
|
5593
|
+
}
|
|
5594
|
+
};
|
|
5097
5595
|
let userAbortRequested = false;
|
|
5098
5596
|
sink.setAbort(async () => {
|
|
5099
5597
|
userAbortRequested = true;
|
|
5100
5598
|
const sessionIdAtAbort = capturedSessionId;
|
|
5101
|
-
if (sessionIdAtAbort)
|
|
5102
|
-
try {
|
|
5103
|
-
await Promise.race([
|
|
5104
|
-
fetchJson(
|
|
5105
|
-
`${runtime.baseUrl}/session/${sessionIdAtAbort}/abort`,
|
|
5106
|
-
{
|
|
5107
|
-
method: "POST",
|
|
5108
|
-
headers: {
|
|
5109
|
-
"content-type": "application/json",
|
|
5110
|
-
...runtime.previewHeaders
|
|
5111
|
-
}
|
|
5112
|
-
}
|
|
5113
|
-
),
|
|
5114
|
-
new Promise(
|
|
5115
|
-
(_, reject) => setTimeout(
|
|
5116
|
-
() => reject(new Error("opencode POST /session/abort timed out")),
|
|
5117
|
-
3e3
|
|
5118
|
-
)
|
|
5119
|
-
)
|
|
5120
|
-
]);
|
|
5121
|
-
} catch {
|
|
5122
|
-
}
|
|
5123
|
-
}
|
|
5599
|
+
if (sessionIdAtAbort) await postAbort(sessionIdAtAbort);
|
|
5124
5600
|
resolveSessionTerminal();
|
|
5125
5601
|
});
|
|
5602
|
+
const backgroundTimeoutMs = resolveBackgroundTaskTimeoutMs(
|
|
5603
|
+
request.options.backgroundTaskTimeoutMs
|
|
5604
|
+
);
|
|
5605
|
+
const trackChildren = backgroundTimeoutMs !== 0;
|
|
5606
|
+
const children = /* @__PURE__ */ new Map();
|
|
5607
|
+
const liveChildren = () => [...children.values()].filter((child) => child.live).map((child) => ({
|
|
5608
|
+
id: child.id,
|
|
5609
|
+
type: "subagent",
|
|
5610
|
+
description: child.title
|
|
5611
|
+
}));
|
|
5612
|
+
const shouldWait = () => [...children.values()].some((child) => child.background) && liveChildren().length > 0;
|
|
5613
|
+
let pendingWait;
|
|
5614
|
+
let waitedMs = 0;
|
|
5615
|
+
let expiry;
|
|
5616
|
+
let sawParentIdle = false;
|
|
5617
|
+
let lastTasksKey = JSON.stringify({ tasks: [], waiting: false });
|
|
5618
|
+
const emitTasks = (tasks, waiting) => {
|
|
5619
|
+
const key = JSON.stringify({ tasks, waiting });
|
|
5620
|
+
if (key === lastTasksKey) return;
|
|
5621
|
+
lastTasksKey = key;
|
|
5622
|
+
sink.emitEvent(
|
|
5623
|
+
createNormalizedEvent(
|
|
5624
|
+
"background.tasks",
|
|
5625
|
+
{ provider: request.provider, runId: request.runId },
|
|
5626
|
+
{ tasks, waiting }
|
|
5627
|
+
)
|
|
5628
|
+
);
|
|
5629
|
+
};
|
|
5630
|
+
const endWait = () => {
|
|
5631
|
+
if (!pendingWait) return;
|
|
5632
|
+
waitedMs += pendingWait.elapsedMs();
|
|
5633
|
+
pendingWait.clear();
|
|
5634
|
+
pendingWait = void 0;
|
|
5635
|
+
};
|
|
5636
|
+
const onChildrenChanged = () => {
|
|
5637
|
+
if (!pendingWait) return;
|
|
5638
|
+
const live = liveChildren();
|
|
5639
|
+
emitTasks(live, true);
|
|
5640
|
+
pendingWait.setIdle(live.length === 0);
|
|
5641
|
+
};
|
|
5642
|
+
const registerChild = (id, title, background = false) => {
|
|
5643
|
+
const known = children.get(id);
|
|
5644
|
+
if (known) {
|
|
5645
|
+
known.background ||= background;
|
|
5646
|
+
if (title && title !== known.title) {
|
|
5647
|
+
known.title = title;
|
|
5648
|
+
onChildrenChanged();
|
|
5649
|
+
}
|
|
5650
|
+
return;
|
|
5651
|
+
}
|
|
5652
|
+
children.set(id, { id, title: title ?? "", live: true, background });
|
|
5653
|
+
onChildrenChanged();
|
|
5654
|
+
};
|
|
5655
|
+
const setChildLive = (id, live) => {
|
|
5656
|
+
const child = children.get(id);
|
|
5657
|
+
if (!child || child.live === live) return;
|
|
5658
|
+
child.live = live;
|
|
5659
|
+
onChildrenChanged();
|
|
5660
|
+
};
|
|
5661
|
+
const reconcileChildren = async () => {
|
|
5662
|
+
const live = [...children.values()].filter((child) => child.live);
|
|
5663
|
+
if (live.length === 0) return;
|
|
5664
|
+
const statuses = await withTimeout(
|
|
5665
|
+
fetchJson(
|
|
5666
|
+
`${runtime.baseUrl}/session/status`,
|
|
5667
|
+
{ headers: runtime.previewHeaders }
|
|
5668
|
+
).catch(() => void 0),
|
|
5669
|
+
3e3
|
|
5670
|
+
);
|
|
5671
|
+
if (!statuses || typeof statuses !== "object") return;
|
|
5672
|
+
for (const child of live) {
|
|
5673
|
+
const status = statuses[child.id];
|
|
5674
|
+
if (!status || status.type === "idle") setChildLive(child.id, false);
|
|
5675
|
+
}
|
|
5676
|
+
};
|
|
5677
|
+
const onParentIdle = async () => {
|
|
5678
|
+
sawParentIdle = true;
|
|
5679
|
+
if (pendingWait || sessionIdleFromSse) return;
|
|
5680
|
+
const tracking = trackChildren && !userAbortRequested;
|
|
5681
|
+
if (tracking && shouldWait()) await reconcileChildren();
|
|
5682
|
+
if (!tracking || !shouldWait()) {
|
|
5683
|
+
sessionIdleFromSse = true;
|
|
5684
|
+
resolveSessionTerminal();
|
|
5685
|
+
return;
|
|
5686
|
+
}
|
|
5687
|
+
const live = liveChildren();
|
|
5688
|
+
debugOpencode(
|
|
5689
|
+
"\u2605 parent idle with %d live subagent(s); waiting",
|
|
5690
|
+
live.length
|
|
5691
|
+
);
|
|
5692
|
+
const wait = new BackgroundWait(
|
|
5693
|
+
BACKGROUND_TASK_GRACE_MS,
|
|
5694
|
+
Math.max(0, backgroundTimeoutMs - waitedMs)
|
|
5695
|
+
);
|
|
5696
|
+
pendingWait = wait;
|
|
5697
|
+
void wait.expired.then((reason) => {
|
|
5698
|
+
if (pendingWait !== wait) return;
|
|
5699
|
+
expiry = reason;
|
|
5700
|
+
resolveSessionTerminal();
|
|
5701
|
+
});
|
|
5702
|
+
emitTasks(live, true);
|
|
5703
|
+
};
|
|
5704
|
+
const onParentBusy = () => {
|
|
5705
|
+
if (!pendingWait) return;
|
|
5706
|
+
debugOpencode(
|
|
5707
|
+
"\u2605 parent resumed after %dms of background wait",
|
|
5708
|
+
pendingWait.elapsedMs()
|
|
5709
|
+
);
|
|
5710
|
+
endWait();
|
|
5711
|
+
emitTasks(liveChildren(), false);
|
|
5712
|
+
};
|
|
5713
|
+
const onInjectedResult = (childId) => {
|
|
5714
|
+
if (!children.has(childId)) return;
|
|
5715
|
+
setChildLive(childId, false);
|
|
5716
|
+
onParentBusy();
|
|
5717
|
+
};
|
|
5126
5718
|
try {
|
|
5127
5719
|
const interactiveApproval = !request.options.fullAccess && isInteractiveApproval(request.options);
|
|
5128
5720
|
let forkedSession = null;
|
|
@@ -5225,6 +5817,12 @@ var OpenCodeAgentAdapter = class {
|
|
|
5225
5817
|
const info = properties?.info;
|
|
5226
5818
|
if (info && typeof info.id === "string" && typeof info.parentID === "string" && runSessionIds.has(info.parentID)) {
|
|
5227
5819
|
runSessionIds.add(info.id);
|
|
5820
|
+
if (trackChildren && info.parentID === sessionId) {
|
|
5821
|
+
registerChild(
|
|
5822
|
+
info.id,
|
|
5823
|
+
typeof info.title === "string" ? info.title : void 0
|
|
5824
|
+
);
|
|
5825
|
+
}
|
|
5228
5826
|
}
|
|
5229
5827
|
}
|
|
5230
5828
|
if (eventType === "message.updated") {
|
|
@@ -5333,28 +5931,35 @@ var OpenCodeAgentAdapter = class {
|
|
|
5333
5931
|
const errMsg = typeof errData?.data?.message === "string" ? errData.data.message : typeof errData?.message === "string" ? errData.message : "OpenCode session error";
|
|
5334
5932
|
sessionErrorFromSse = new Error(errMsg);
|
|
5335
5933
|
}
|
|
5934
|
+
resolveSessionTerminal();
|
|
5336
5935
|
} else {
|
|
5337
|
-
|
|
5936
|
+
await onParentIdle();
|
|
5338
5937
|
}
|
|
5339
5938
|
debugOpencode(
|
|
5340
5939
|
"\u2605 %s for session=%s",
|
|
5341
5940
|
payloadRecord.type,
|
|
5342
5941
|
sessionId
|
|
5343
5942
|
);
|
|
5344
|
-
|
|
5943
|
+
} else if (trackChildren && payloadRecord.type === "session.idle") {
|
|
5944
|
+
setChildLive(eventSessionId, false);
|
|
5345
5945
|
}
|
|
5346
5946
|
}
|
|
5347
5947
|
if (payloadRecord?.type === "session.status") {
|
|
5348
5948
|
const properties = payloadRecord.properties;
|
|
5349
5949
|
const status = properties?.status;
|
|
5350
5950
|
const eventSessionId = typeof properties?.sessionID === "string" ? properties.sessionID : void 0;
|
|
5351
|
-
if (
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5951
|
+
if (!eventSessionId || eventSessionId === sessionId) {
|
|
5952
|
+
if (status?.type === "idle") {
|
|
5953
|
+
debugOpencode(
|
|
5954
|
+
"\u2605 session.status{idle} for session=%s",
|
|
5955
|
+
sessionId
|
|
5956
|
+
);
|
|
5957
|
+
await onParentIdle();
|
|
5958
|
+
} else if (status?.type === "busy" || status?.type === "retry") {
|
|
5959
|
+
onParentBusy();
|
|
5960
|
+
}
|
|
5961
|
+
} else if (trackChildren) {
|
|
5962
|
+
setChildLive(eventSessionId, status?.type !== "idle");
|
|
5358
5963
|
}
|
|
5359
5964
|
}
|
|
5360
5965
|
if (payloadRecord?.type === "message.part.updated") {
|
|
@@ -5363,6 +5968,22 @@ var OpenCodeAgentAdapter = class {
|
|
|
5363
5968
|
if (part && typeof part.id === "string" && typeof part.type === "string") {
|
|
5364
5969
|
partTypeById.set(part.id, part.type);
|
|
5365
5970
|
}
|
|
5971
|
+
if (trackChildren && part?.type === "tool" && part.tool === "task" && part.sessionID === sessionId) {
|
|
5972
|
+
const state = part.state;
|
|
5973
|
+
const metadata = state?.metadata;
|
|
5974
|
+
const childId = metadata?.sessionId ?? metadata?.jobId;
|
|
5975
|
+
if (metadata?.background === true && typeof childId === "string") {
|
|
5976
|
+
registerChild(
|
|
5977
|
+
childId,
|
|
5978
|
+
typeof state?.title === "string" ? state.title : void 0,
|
|
5979
|
+
true
|
|
5980
|
+
);
|
|
5981
|
+
}
|
|
5982
|
+
}
|
|
5983
|
+
if (trackChildren && part?.type === "text" && part.synthetic === true && part.sessionID === sessionId && typeof part.text === "string") {
|
|
5984
|
+
const childId = injectedTaskResultChild(part.text);
|
|
5985
|
+
if (childId) onInjectedResult(childId);
|
|
5986
|
+
}
|
|
5366
5987
|
}
|
|
5367
5988
|
if (payloadRecord?.type === "message.part.delta") {
|
|
5368
5989
|
const properties = payloadRecord.properties;
|
|
@@ -5373,6 +5994,9 @@ var OpenCodeAgentAdapter = class {
|
|
|
5373
5994
|
if (isForeignSession) {
|
|
5374
5995
|
continue;
|
|
5375
5996
|
}
|
|
5997
|
+
if (eventMessageId !== void 0 && announcedUserMessageIds.has(eventMessageId)) {
|
|
5998
|
+
continue;
|
|
5999
|
+
}
|
|
5376
6000
|
const delta = typeof properties?.delta === "string" ? properties.delta : "";
|
|
5377
6001
|
const field = typeof properties?.field === "string" ? properties.field : void 0;
|
|
5378
6002
|
const partType = eventPartId ? partTypeById.get(eventPartId) : void 0;
|
|
@@ -5523,13 +6147,22 @@ var OpenCodeAgentAdapter = class {
|
|
|
5523
6147
|
const SSE_POLL_INTERVAL_MS = 5e3;
|
|
5524
6148
|
lastSseActivityAt = Date.now();
|
|
5525
6149
|
let sseSilent = false;
|
|
5526
|
-
while (!sessionIdleFromSse && !sessionErrorFromSse && !sessionAbortedFromSse && !userAbortRequested && !dispatchError) {
|
|
6150
|
+
while (!sessionIdleFromSse && !sessionErrorFromSse && !sessionAbortedFromSse && !userAbortRequested && !dispatchError && !expiry) {
|
|
5527
6151
|
const silence = Date.now() - lastSseActivityAt;
|
|
5528
6152
|
if (silence > SSE_SILENCE_THRESHOLD_MS) {
|
|
6153
|
+
if (pendingWait && sawParentIdle) {
|
|
6154
|
+
debugOpencode(
|
|
6155
|
+
"SSE went silent (%dms) during background wait; settling",
|
|
6156
|
+
silence
|
|
6157
|
+
);
|
|
6158
|
+
expiry = "transport";
|
|
6159
|
+
break;
|
|
6160
|
+
}
|
|
5529
6161
|
sseSilent = true;
|
|
5530
6162
|
debugOpencode("SSE went silent (%dms) \u2014 giving up", silence);
|
|
5531
6163
|
break;
|
|
5532
6164
|
}
|
|
6165
|
+
if (pendingWait && liveChildren().length > 0) await reconcileChildren();
|
|
5533
6166
|
await Promise.race([
|
|
5534
6167
|
sessionTerminal,
|
|
5535
6168
|
new Promise(
|
|
@@ -5539,6 +6172,16 @@ var OpenCodeAgentAdapter = class {
|
|
|
5539
6172
|
}
|
|
5540
6173
|
sseAbort.abort();
|
|
5541
6174
|
await sseTask;
|
|
6175
|
+
if (expiry === "ceiling" || (sessionErrorFromSse || dispatchError) && liveChildren().length > 0) {
|
|
6176
|
+
debugOpencode(
|
|
6177
|
+
"\u2605 run over (%s) with %d subagent(s) live; aborting them",
|
|
6178
|
+
expiry ?? "failure",
|
|
6179
|
+
liveChildren().length
|
|
6180
|
+
);
|
|
6181
|
+
await postAbort(sessionId);
|
|
6182
|
+
}
|
|
6183
|
+
endWait();
|
|
6184
|
+
emitTasks([], false);
|
|
5542
6185
|
if (userAbortRequested || sessionAbortedFromSse) {
|
|
5543
6186
|
debugOpencode(
|
|
5544
6187
|
"\u2605 run.cancelled (%dms since execute start)",
|
|
@@ -5552,7 +6195,7 @@ var OpenCodeAgentAdapter = class {
|
|
|
5552
6195
|
sink.fail(sessionErrorFromSse);
|
|
5553
6196
|
} else if (dispatchError) {
|
|
5554
6197
|
sink.fail(dispatchError);
|
|
5555
|
-
} else if (sessionIdleFromSse) {
|
|
6198
|
+
} else if (sessionIdleFromSse || expiry) {
|
|
5556
6199
|
debugOpencode(
|
|
5557
6200
|
"\u2605 run.completed (%dms since execute start) chars=%d",
|
|
5558
6201
|
Date.now() - executeStartedAt,
|
|
@@ -5597,6 +6240,7 @@ var OpenCodeAgentAdapter = class {
|
|
|
5597
6240
|
sink.fail(new Error("opencode run ended without a terminal signal"));
|
|
5598
6241
|
}
|
|
5599
6242
|
} finally {
|
|
6243
|
+
endWait();
|
|
5600
6244
|
sseAbort.abort();
|
|
5601
6245
|
if (sseTask) {
|
|
5602
6246
|
await sseTask.catch(() => void 0);
|
|
@@ -5744,6 +6388,7 @@ function prepareAgentOptions(_provider, options) {
|
|
|
5744
6388
|
if (!path11.isAbsolute(options.stateDirectory)) throw new Error("stateDirectory must be an absolute path.");
|
|
5745
6389
|
}
|
|
5746
6390
|
if (options.sandbox && options.processGroup !== void 0) throw new Error("processGroup is only supported for host execution.");
|
|
6391
|
+
resolveBackgroundTaskTimeoutMs(options.backgroundTaskTimeoutMs);
|
|
5747
6392
|
if (options.configuration === "native") {
|
|
5748
6393
|
if (options.sandbox) throw new Error("Native configuration is only supported for host execution.");
|
|
5749
6394
|
if (options.mcps?.length || options.skills?.length || options.subAgents?.length || options.commands?.length || options.enableRtk) {
|
|
@@ -6137,6 +6782,9 @@ var Agent = class {
|
|
|
6137
6782
|
this.setupPromise = void 0;
|
|
6138
6783
|
}
|
|
6139
6784
|
stream(runConfig) {
|
|
6785
|
+
if (this.provider !== AgentProvider.Codex && (runConfig.reasoning === "max" || runConfig.reasoning === "ultra")) {
|
|
6786
|
+
throw new Error(`Reasoning effort "${runConfig.reasoning}" is only supported by Codex.`);
|
|
6787
|
+
}
|
|
6140
6788
|
if (runConfig.resumeSessionId && runConfig.forkSessionId) {
|
|
6141
6789
|
throw new Error(
|
|
6142
6790
|
"AgentRunConfig.resumeSessionId and forkSessionId are mutually exclusive."
|