agentbox-sdk 0.1.508 → 0.1.511
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 +71 -1
- package/dist/agents/index.d.ts +2 -2
- package/dist/agents/index.js +1 -1
- package/dist/{chunk-BO4J4KSM.js → chunk-G3VEVR6Y.js} +800 -107
- 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-B4yy80AJ.d.ts} +32 -3
- 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";
|
|
@@ -2091,6 +2286,29 @@ async function handleStart(req, res, runId) {
|
|
|
2091
2286
|
opts.pathToClaudeCodeExecutable,
|
|
2092
2287
|
);
|
|
2093
2288
|
|
|
2289
|
+
let queryHandle;
|
|
2290
|
+
let clientGone = false;
|
|
2291
|
+
// This run's own teardown. The liveRuns entry is only removed when it is
|
|
2292
|
+
// still ours: hosts reuse a runId across retry attempts, and a successor
|
|
2293
|
+
// registered while our CLI winds down must not be evicted by our exit.
|
|
2294
|
+
const releaseRun = () => {
|
|
2295
|
+
clearInterval(heartbeat);
|
|
2296
|
+
clearPermissions();
|
|
2297
|
+
if (liveRuns.get(runId)?.query === queryHandle) liveRuns.delete(runId);
|
|
2298
|
+
promptStream.end();
|
|
2299
|
+
};
|
|
2300
|
+
// Host gone (settled, cancelled or crashed) \u2192 end the prompt so the CLI
|
|
2301
|
+
// winds down instead of living on with its background work. Detected on
|
|
2302
|
+
// the response: \`req\` emits "close" as soon as its body is consumed (Node
|
|
2303
|
+
// >= 16), long before any disconnect, while the response only closes
|
|
2304
|
+
// early when the socket dies before the stream finished.
|
|
2305
|
+
res.on("close", () => {
|
|
2306
|
+
if (res.writableFinished) return;
|
|
2307
|
+
clientGone = true;
|
|
2308
|
+
releaseRun();
|
|
2309
|
+
queryHandle?.interrupt().catch(() => {});
|
|
2310
|
+
});
|
|
2311
|
+
|
|
2094
2312
|
// Resume-if-exists gate. \`claude --resume <id>\` errors hard with "No
|
|
2095
2313
|
// conversation found with session ID" when the local session jsonl is
|
|
2096
2314
|
// missing \u2014 most often because a prior post-task snapshot failed and the
|
|
@@ -2121,7 +2339,8 @@ async function handleStart(req, res, runId) {
|
|
|
2121
2339
|
}
|
|
2122
2340
|
}
|
|
2123
2341
|
|
|
2124
|
-
|
|
2342
|
+
// Nobody left to stream to: do not start a CLI for it.
|
|
2343
|
+
if (clientGone) { res.end(); return; }
|
|
2125
2344
|
try {
|
|
2126
2345
|
queryHandle = query({
|
|
2127
2346
|
prompt: promptStream,
|
|
@@ -2144,28 +2363,19 @@ async function handleStart(req, res, runId) {
|
|
|
2144
2363
|
|
|
2145
2364
|
liveRuns.set(runId, { query: queryHandle, prompt: promptStream, permissions });
|
|
2146
2365
|
|
|
2147
|
-
//
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
liveRuns.delete(runId);
|
|
2153
|
-
promptStream.end();
|
|
2154
|
-
queryHandle.interrupt().catch(() => {});
|
|
2155
|
-
});
|
|
2156
|
-
|
|
2366
|
+
// Forward every SDKMessage, not just up to the first result: in
|
|
2367
|
+
// streaming-input mode the CLI keeps running after a turn ends and
|
|
2368
|
+
// re-prompts the model when background work finishes. The host decides
|
|
2369
|
+
// when the run is over and disconnects (res "close" above), which ends
|
|
2370
|
+
// the prompt and lets the CLI wind down.
|
|
2157
2371
|
try {
|
|
2158
2372
|
for await (const message of queryHandle) {
|
|
2159
2373
|
res.write(JSON.stringify(message) + "\\n");
|
|
2160
|
-
if (message.type === "result") break;
|
|
2161
2374
|
}
|
|
2162
2375
|
} catch (e) {
|
|
2163
2376
|
res.write(JSON.stringify({ _error: String(e?.message ?? e) }) + "\\n");
|
|
2164
2377
|
} finally {
|
|
2165
|
-
|
|
2166
|
-
clearPermissions();
|
|
2167
|
-
liveRuns.delete(runId);
|
|
2168
|
-
promptStream.end();
|
|
2378
|
+
releaseRun();
|
|
2169
2379
|
res.end();
|
|
2170
2380
|
}
|
|
2171
2381
|
}
|
|
@@ -2555,6 +2765,7 @@ var ClaudeCodeAgentAdapter = class {
|
|
|
2555
2765
|
// user inside our images.
|
|
2556
2766
|
IS_SANDBOX: "1"
|
|
2557
2767
|
};
|
|
2768
|
+
applyCliBackgroundWaitCeiling(env);
|
|
2558
2769
|
const customHeaders = request.options.customHeaders;
|
|
2559
2770
|
if (customHeaders && Object.keys(customHeaders).length > 0) {
|
|
2560
2771
|
const serialized = Object.entries(customHeaders).map(([name, value]) => `${name}: ${value}`).join("\n");
|
|
@@ -2604,17 +2815,23 @@ ${serialized}` : serialized;
|
|
|
2604
2815
|
}
|
|
2605
2816
|
};
|
|
2606
2817
|
const fetchAbort = new AbortController();
|
|
2818
|
+
const runUrl = `${baseUrl}/runs/${encodeURIComponent(request.runId)}`;
|
|
2607
2819
|
const cleanup = async () => {
|
|
2608
2820
|
try {
|
|
2609
|
-
await fetch(
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2821
|
+
await fetch(`${runUrl}/abort`, { method: "POST", headers: authHeaders });
|
|
2822
|
+
} catch {
|
|
2823
|
+
}
|
|
2824
|
+
try {
|
|
2825
|
+
await fetch(runUrl, { method: "DELETE", headers: authHeaders, signal: AbortSignal.timeout(3e3) });
|
|
2613
2826
|
} catch {
|
|
2614
2827
|
}
|
|
2615
2828
|
fetchAbort.abort();
|
|
2616
2829
|
};
|
|
2617
|
-
|
|
2830
|
+
let cancelled = false;
|
|
2831
|
+
sink.setAbort(async () => {
|
|
2832
|
+
cancelled = true;
|
|
2833
|
+
await cleanup();
|
|
2834
|
+
});
|
|
2618
2835
|
sink.onMessage(async (content) => {
|
|
2619
2836
|
const parts = await validateProviderUserInput(request.provider, content);
|
|
2620
2837
|
const mapped = mapToClaudeUserContent(parts);
|
|
@@ -2680,7 +2897,7 @@ ${serialized}` : serialized;
|
|
|
2680
2897
|
if (!reply.ok) throw new Error(`Claude permission response failed: ${reply.status}`);
|
|
2681
2898
|
}
|
|
2682
2899
|
};
|
|
2683
|
-
await consumeClaudeMessages(request, sink, permissionMessages(), executeStartedAt, cleanup);
|
|
2900
|
+
await consumeClaudeMessages(request, sink, permissionMessages(), executeStartedAt, cleanup, () => cancelled);
|
|
2684
2901
|
return async () => void 0;
|
|
2685
2902
|
}
|
|
2686
2903
|
/**
|
|
@@ -2741,24 +2958,66 @@ ${serialized}` : serialized;
|
|
|
2741
2958
|
}
|
|
2742
2959
|
}
|
|
2743
2960
|
};
|
|
2744
|
-
async function consumeClaudeMessages(request, sink, messages, executeStartedAt, cleanup, wasCancelled = () => false) {
|
|
2961
|
+
async function consumeClaudeMessages(request, sink, messages, executeStartedAt, cleanup, wasCancelled = () => false, wait = {}) {
|
|
2745
2962
|
let accumulatedText = "";
|
|
2746
2963
|
let streamedThinkingChars = 0;
|
|
2747
|
-
let pendingMessages = 1;
|
|
2748
2964
|
let sawResult = false;
|
|
2749
2965
|
let firstStreamEventLogged = false;
|
|
2750
2966
|
let firstTextDeltaLogged = false;
|
|
2751
2967
|
let lastTerminalReason;
|
|
2752
2968
|
let lastIsError = false;
|
|
2753
2969
|
const rawPayloads = [];
|
|
2970
|
+
const tracker = new BackgroundTaskTracker();
|
|
2971
|
+
const timeoutMs = resolveBackgroundTaskTimeoutMs(request.options.backgroundTaskTimeoutMs);
|
|
2972
|
+
const graceMs = wait.graceMs ?? BACKGROUND_TASK_GRACE_MS;
|
|
2973
|
+
let pendingWait;
|
|
2974
|
+
let waitedMs = 0;
|
|
2975
|
+
let expiry;
|
|
2976
|
+
let lastTasksKey = JSON.stringify({ tasks: [], waiting: false });
|
|
2977
|
+
const emitTasks = (tasks, waiting) => {
|
|
2978
|
+
const key = JSON.stringify({ tasks, waiting });
|
|
2979
|
+
if (key === lastTasksKey) return;
|
|
2980
|
+
lastTasksKey = key;
|
|
2981
|
+
sink.emitEvent(createNormalizedEvent("background.tasks", { provider: request.provider, runId: request.runId }, { tasks, waiting }));
|
|
2982
|
+
};
|
|
2983
|
+
const isAborted = () => wasCancelled() || lastTerminalReason === "aborted_streaming" || lastTerminalReason === "aborted_tools";
|
|
2984
|
+
const endWait = () => {
|
|
2985
|
+
if (!pendingWait) return;
|
|
2986
|
+
waitedMs += pendingWait.elapsedMs();
|
|
2987
|
+
pendingWait.clear();
|
|
2988
|
+
pendingWait = void 0;
|
|
2989
|
+
};
|
|
2990
|
+
const settleOnFailure = (error) => {
|
|
2991
|
+
if (!pendingWait || !sawResult || lastIsError) return false;
|
|
2992
|
+
debugClaude("\u2605 transport failed during background wait; settling on the last result: %o", error);
|
|
2993
|
+
expiry = "transport";
|
|
2994
|
+
return true;
|
|
2995
|
+
};
|
|
2996
|
+
const iterator = messages[Symbol.asyncIterator]();
|
|
2754
2997
|
try {
|
|
2755
|
-
for
|
|
2998
|
+
for (let next = iterator.next(); ; next = iterator.next()) {
|
|
2999
|
+
let step;
|
|
3000
|
+
try {
|
|
3001
|
+
step = pendingWait ? await Promise.race([
|
|
3002
|
+
next.then((result) => ({ result })),
|
|
3003
|
+
pendingWait.expired.then((reason) => ({ reason }))
|
|
3004
|
+
]) : { result: await next };
|
|
3005
|
+
} catch (error) {
|
|
3006
|
+
if (!settleOnFailure(error)) throw error;
|
|
3007
|
+
break;
|
|
3008
|
+
}
|
|
3009
|
+
if ("reason" in step) {
|
|
3010
|
+
expiry = step.reason;
|
|
3011
|
+
break;
|
|
3012
|
+
}
|
|
3013
|
+
if (step.result.done) break;
|
|
3014
|
+
const item = step.result.value;
|
|
2756
3015
|
if (item && typeof item === "object") {
|
|
2757
3016
|
const ctrl = item;
|
|
2758
3017
|
if ("_error" in ctrl) {
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
3018
|
+
const error = new Error(String(ctrl._error ?? "daemon error"));
|
|
3019
|
+
if (!settleOnFailure(error)) throw error;
|
|
3020
|
+
break;
|
|
2762
3021
|
}
|
|
2763
3022
|
if ("_notice" in ctrl) {
|
|
2764
3023
|
debugClaude("daemon notice: %o", ctrl);
|
|
@@ -2775,6 +3034,12 @@ async function consumeClaudeMessages(request, sink, messages, executeStartedAt,
|
|
|
2775
3034
|
const message = item;
|
|
2776
3035
|
rawPayloads.push(message);
|
|
2777
3036
|
sink.emitRaw(toRawEvent(request.runId, message, message.type));
|
|
3037
|
+
if (tracker.ingest(message) && pendingWait) {
|
|
3038
|
+
debugClaude("\u2605 follow-up turn started; background wait over (%dms since execute start)", Date.now() - executeStartedAt);
|
|
3039
|
+
endWait();
|
|
3040
|
+
}
|
|
3041
|
+
emitTasks(tracker.liveTasks(), pendingWait !== void 0);
|
|
3042
|
+
pendingWait?.setIdle(tracker.liveTasks().length === 0);
|
|
2778
3043
|
if (message.type === "system") {
|
|
2779
3044
|
const sub = message.subtype;
|
|
2780
3045
|
if (sub === "init") {
|
|
@@ -2891,15 +3156,33 @@ async function consumeClaudeMessages(request, sink, messages, executeStartedAt,
|
|
|
2891
3156
|
if (resultText && resultText !== accumulatedText) {
|
|
2892
3157
|
accumulatedText = resultText;
|
|
2893
3158
|
}
|
|
2894
|
-
|
|
2895
|
-
if (
|
|
3159
|
+
const live = tracker.liveTasks();
|
|
3160
|
+
if (timeoutMs === 0 || !tracker.hasSeenBackgroundWork() || isAborted()) break;
|
|
3161
|
+
debugClaude("\u2605 turn ended with %d background task(s); waiting", live.length);
|
|
3162
|
+
endWait();
|
|
3163
|
+
pendingWait = new BackgroundWait(graceMs, Math.max(0, timeoutMs - waitedMs));
|
|
3164
|
+
pendingWait.setIdle(live.length === 0);
|
|
3165
|
+
emitTasks(live, true);
|
|
2896
3166
|
continue;
|
|
2897
3167
|
}
|
|
2898
3168
|
}
|
|
3169
|
+
if (expiry === "ceiling") {
|
|
3170
|
+
const ids = tracker.liveTasks().filter((task) => task.type !== "scheduled_wakeup").map((task) => task.id);
|
|
3171
|
+
debugClaude("\u2605 background wait ceiling (%dms) hit; stopping %d task(s)", timeoutMs, ids.length);
|
|
3172
|
+
if (wait.stopTasks) {
|
|
3173
|
+
await withTimeout(wait.stopTasks(ids), wait.stopTimeoutMs ?? STOP_TASKS_TIMEOUT_MS).catch(() => void 0);
|
|
3174
|
+
}
|
|
3175
|
+
} else if (expiry === "grace") {
|
|
3176
|
+
debugClaude("\u2605 background set emptied with no follow-up turn; settling");
|
|
3177
|
+
}
|
|
3178
|
+
if (pendingWait) {
|
|
3179
|
+
endWait();
|
|
3180
|
+
emitTasks([], false);
|
|
3181
|
+
}
|
|
2899
3182
|
await cleanup();
|
|
2900
3183
|
if (!sawResult && !wasCancelled()) throw new Error("Claude Code closed before reporting a result");
|
|
2901
3184
|
const finalText = accumulatedText;
|
|
2902
|
-
const isCancelled =
|
|
3185
|
+
const isCancelled = isAborted();
|
|
2903
3186
|
const isError = !isCancelled && lastIsError;
|
|
2904
3187
|
if (isCancelled) {
|
|
2905
3188
|
debugClaude(
|
|
@@ -2941,10 +3224,11 @@ async function consumeClaudeMessages(request, sink, messages, executeStartedAt,
|
|
|
2941
3224
|
});
|
|
2942
3225
|
}
|
|
2943
3226
|
} finally {
|
|
3227
|
+
pendingWait?.clear();
|
|
2944
3228
|
await cleanup();
|
|
2945
3229
|
}
|
|
2946
3230
|
}
|
|
2947
|
-
async function executeNativeClaude(request, sink) {
|
|
3231
|
+
async function executeNativeClaude(request, sink, wait = {}) {
|
|
2948
3232
|
const { query } = await import("@anthropic-ai/claude-agent-sdk");
|
|
2949
3233
|
const claudeDir = claudeConfigDir(request.options);
|
|
2950
3234
|
const input = await validateProviderUserInput(request.provider, request.run.input);
|
|
@@ -2969,6 +3253,7 @@ async function executeNativeClaude(request, sink) {
|
|
|
2969
3253
|
const messageId = randomUUID();
|
|
2970
3254
|
prompt.push({ type: "user", uuid: messageId, message: { role: "user", content: mapToClaudeUserContent(input) }, parent_tool_use_id: null });
|
|
2971
3255
|
const hostEnv = Object.fromEntries(Object.entries({ ...process.env, ...request.options.env }).filter((entry) => entry[1] !== void 0));
|
|
3256
|
+
applyCliBackgroundWaitCeiling(hostEnv);
|
|
2972
3257
|
if (request.options.customHeaders) {
|
|
2973
3258
|
const headers = Object.entries(request.options.customHeaders).map(([name, value]) => `${name}: ${value}`).join("\n");
|
|
2974
3259
|
hostEnv.ANTHROPIC_CUSTOM_HEADERS = [hostEnv.ANTHROPIC_CUSTOM_HEADERS, headers].filter(Boolean).join("\n");
|
|
@@ -3041,10 +3326,17 @@ async function executeNativeClaude(request, sink) {
|
|
|
3041
3326
|
}
|
|
3042
3327
|
}
|
|
3043
3328
|
} });
|
|
3044
|
-
|
|
3329
|
+
const live = handle;
|
|
3330
|
+
sink.setRaw({ query: live, claudeDir, runId: request.runId });
|
|
3045
3331
|
sink.emitEvent(createNormalizedEvent("run.started", { provider: request.provider, runId: request.runId }));
|
|
3046
3332
|
sink.emitEvent(createNormalizedEvent("message.started", { provider: request.provider, runId: request.runId }, { messageId }));
|
|
3047
|
-
await consumeClaudeMessages(request, sink,
|
|
3333
|
+
await consumeClaudeMessages(request, sink, live, Date.now(), stop, () => cancelled, {
|
|
3334
|
+
...wait,
|
|
3335
|
+
// Native owns the CLI: ask it to stop leftover tasks before closing it.
|
|
3336
|
+
stopTasks: async (ids) => {
|
|
3337
|
+
await Promise.all(ids.map((id) => live.stopTask(id).catch(() => void 0)));
|
|
3338
|
+
}
|
|
3339
|
+
});
|
|
3048
3340
|
} catch (error) {
|
|
3049
3341
|
await stop();
|
|
3050
3342
|
if (cancelled) sink.cancel();
|
|
@@ -3350,7 +3642,7 @@ function buildThreadParams(cwd, options, request) {
|
|
|
3350
3642
|
return {
|
|
3351
3643
|
cwd,
|
|
3352
3644
|
model: request.run.model ?? null,
|
|
3353
|
-
...options.configuration === "native" && !options.fullAccess ? {} : { approvalPolicy: !options.fullAccess && isInteractiveApproval(options) ? "untrusted" : "never" },
|
|
3645
|
+
...options.provider?.approvalPolicy ? { approvalPolicy: options.provider.approvalPolicy } : options.configuration === "native" && !options.fullAccess ? {} : { approvalPolicy: !options.fullAccess && isInteractiveApproval(options) ? "untrusted" : "never" },
|
|
3354
3646
|
sandbox: buildCodexSandboxMode(options),
|
|
3355
3647
|
serviceName: "agentbox",
|
|
3356
3648
|
// Persist the rollout on disk so follow-up runs can call `thread/resume`.
|
|
@@ -3365,7 +3657,7 @@ function buildResumeParams(cwd, options, request) {
|
|
|
3365
3657
|
threadId: request.run.resumeSessionId,
|
|
3366
3658
|
cwd,
|
|
3367
3659
|
model: request.run.model ?? null,
|
|
3368
|
-
...options.configuration === "native" && !options.fullAccess ? {} : { approvalPolicy: !options.fullAccess && isInteractiveApproval(options) ? "untrusted" : "never" },
|
|
3660
|
+
...options.provider?.approvalPolicy ? { approvalPolicy: options.provider.approvalPolicy } : options.configuration === "native" && !options.fullAccess ? {} : { approvalPolicy: !options.fullAccess && isInteractiveApproval(options) ? "untrusted" : "never" },
|
|
3369
3661
|
sandbox: buildCodexSandboxMode(options),
|
|
3370
3662
|
...request.run.systemPrompt ? { developerInstructions: request.run.systemPrompt } : options.configuration === "native" ? {} : { developerInstructions: null },
|
|
3371
3663
|
// We only need the thread id back; we never read `thread.turns`.
|
|
@@ -3381,7 +3673,7 @@ function buildForkParams(cwd, options, request) {
|
|
|
3381
3673
|
lastTurnId: request.run.forkAtMessageId ?? null,
|
|
3382
3674
|
cwd,
|
|
3383
3675
|
model: request.run.model ?? null,
|
|
3384
|
-
...options.configuration === "native" && !options.fullAccess ? {} : { approvalPolicy: !options.fullAccess && isInteractiveApproval(options) ? "untrusted" : "never" },
|
|
3676
|
+
...options.provider?.approvalPolicy ? { approvalPolicy: options.provider.approvalPolicy } : options.configuration === "native" && !options.fullAccess ? {} : { approvalPolicy: !options.fullAccess && isInteractiveApproval(options) ? "untrusted" : "never" },
|
|
3385
3677
|
sandbox: buildCodexSandboxMode(options),
|
|
3386
3678
|
...request.run.systemPrompt ? { developerInstructions: request.run.systemPrompt } : options.configuration === "native" ? {} : { developerInstructions: null },
|
|
3387
3679
|
excludeTurns: true
|
|
@@ -3415,7 +3707,7 @@ function buildCodexTurnStartParams(params) {
|
|
|
3415
3707
|
return {
|
|
3416
3708
|
threadId,
|
|
3417
3709
|
input: inputItems,
|
|
3418
|
-
...request.options.configuration === "native" && !request.options.fullAccess ? {} : {
|
|
3710
|
+
...request.options.provider?.approvalPolicy ? { approvalPolicy: request.options.provider.approvalPolicy } : request.options.configuration === "native" && !request.options.fullAccess ? {} : {
|
|
3419
3711
|
approvalPolicy: !request.options.fullAccess && isInteractiveApproval(request.options) ? "untrusted" : "never"
|
|
3420
3712
|
},
|
|
3421
3713
|
...sandboxPolicy ? { sandboxPolicy } : {},
|
|
@@ -3428,6 +3720,40 @@ function buildCodexTurnStartParams(params) {
|
|
|
3428
3720
|
outputSchema: null
|
|
3429
3721
|
};
|
|
3430
3722
|
}
|
|
3723
|
+
var BACKGROUND_OUTPUT_TAIL_CHARS = 4e3;
|
|
3724
|
+
function buildCodexBackgroundFollowUp(items) {
|
|
3725
|
+
const reports = items.map((item) => {
|
|
3726
|
+
const tail = (item.aggregatedOutput ?? "").slice(-BACKGROUND_OUTPUT_TAIL_CHARS).trimEnd();
|
|
3727
|
+
const duration = item.durationMs === void 0 ? "an unknown time" : `${Math.round(item.durationMs / 1e3)}s`;
|
|
3728
|
+
return `\`${item.command}\` exited with code ${item.exitCode ?? "unknown"} after ${duration}.
|
|
3729
|
+
Output (last ${BACKGROUND_OUTPUT_TAIL_CHARS} chars):
|
|
3730
|
+
\`\`\`
|
|
3731
|
+
${tail || "(no output)"}
|
|
3732
|
+
\`\`\`
|
|
3733
|
+
`;
|
|
3734
|
+
});
|
|
3735
|
+
return `Background command finished while you were idle.
|
|
3736
|
+
|
|
3737
|
+
${reports.join("")}
|
|
3738
|
+
Continue from here: verify the outcome and finish the task. Do not restart the command.`;
|
|
3739
|
+
}
|
|
3740
|
+
var CODEX_CANCEL_TURN_TEXT = "Run cancelled by the host.";
|
|
3741
|
+
async function terminateBackgroundTerminals(client, threadId) {
|
|
3742
|
+
await withTimeout((async () => {
|
|
3743
|
+
try {
|
|
3744
|
+
const listed = await client.request(
|
|
3745
|
+
"thread/backgroundTerminals/list",
|
|
3746
|
+
{ threadId }
|
|
3747
|
+
);
|
|
3748
|
+
for (const terminal of listed?.data ?? []) {
|
|
3749
|
+
if (typeof terminal?.processId !== "string") continue;
|
|
3750
|
+
await client.request("thread/backgroundTerminals/terminate", { threadId, processId: terminal.processId });
|
|
3751
|
+
}
|
|
3752
|
+
} catch (error) {
|
|
3753
|
+
debugCodex("background terminal termination stopped early: %o", error);
|
|
3754
|
+
}
|
|
3755
|
+
})(), STOP_TASKS_TIMEOUT_MS);
|
|
3756
|
+
}
|
|
3431
3757
|
function toRawEvent2(runId, payload, type) {
|
|
3432
3758
|
return {
|
|
3433
3759
|
provider: AgentProvider.Codex,
|
|
@@ -3584,6 +3910,42 @@ function createCodexPermissionEvent(request, notification, fileChanges) {
|
|
|
3584
3910
|
}
|
|
3585
3911
|
return null;
|
|
3586
3912
|
}
|
|
3913
|
+
var CODEX_ELICITATION_METHOD = "mcpServer/elicitation/request";
|
|
3914
|
+
function createCodexElicitationPermissionEvent(request, notification) {
|
|
3915
|
+
if (notification.method !== CODEX_ELICITATION_METHOD || notification.id === void 0) {
|
|
3916
|
+
return null;
|
|
3917
|
+
}
|
|
3918
|
+
const params = notification.params ?? {};
|
|
3919
|
+
const meta = params._meta ?? {};
|
|
3920
|
+
if (meta.codex_approval_kind !== "mcp_tool_call") {
|
|
3921
|
+
return null;
|
|
3922
|
+
}
|
|
3923
|
+
const raw = toRawEvent2(request.runId, notification, notification.method);
|
|
3924
|
+
const toolName = typeof meta.tool_name === "string" ? meta.tool_name : void 0;
|
|
3925
|
+
const server = typeof params.serverName === "string" ? params.serverName : void 0;
|
|
3926
|
+
const persist = Array.isArray(meta.persist) ? meta.persist : [];
|
|
3927
|
+
return createNormalizedEvent(
|
|
3928
|
+
"permission.requested",
|
|
3929
|
+
{ provider: request.provider, runId: request.runId, raw },
|
|
3930
|
+
{
|
|
3931
|
+
requestId: String(notification.id),
|
|
3932
|
+
kind: "tool",
|
|
3933
|
+
toolName: toolName ?? server,
|
|
3934
|
+
title: "Approve tool call",
|
|
3935
|
+
message: typeof params.message === "string" && params.message.trim() ? params.message : `Codex wants to call ${toolName ?? "an MCP tool"}${server ? ` on ${server}` : ""}.`,
|
|
3936
|
+
input: { server, tool: toolName, arguments: meta.tool_params, ...params },
|
|
3937
|
+
canRemember: persist.includes("session")
|
|
3938
|
+
}
|
|
3939
|
+
);
|
|
3940
|
+
}
|
|
3941
|
+
function toCodexElicitationResult(notification, response) {
|
|
3942
|
+
if (response.decision === "deny") {
|
|
3943
|
+
return { action: "decline", content: null };
|
|
3944
|
+
}
|
|
3945
|
+
const meta = notification.params?._meta ?? {};
|
|
3946
|
+
const persist = Array.isArray(meta.persist) ? meta.persist : [];
|
|
3947
|
+
return response.remember && persist.includes("session") ? { action: "accept", content: null, _meta: { persist: "session" } } : { action: "accept", content: null };
|
|
3948
|
+
}
|
|
3587
3949
|
function toCodexApprovalDecision(notification, response) {
|
|
3588
3950
|
const params = notification.params ?? {};
|
|
3589
3951
|
const availableDecisions = Array.isArray(params.availableDecisions) ? params.availableDecisions : [];
|
|
@@ -4201,10 +4563,92 @@ var CodexAgentAdapter = class {
|
|
|
4201
4563
|
const pendingFileChanges = /* @__PURE__ */ new Map();
|
|
4202
4564
|
const fileItemKey = (params, itemId) => typeof params?.threadId === "string" && typeof params.turnId === "string" && typeof itemId === "string" ? `${params.threadId}:${params.turnId}:${itemId}` : void 0;
|
|
4203
4565
|
let streamedText = "";
|
|
4566
|
+
const timeoutMs = resolveBackgroundTaskTimeoutMs(request.options.backgroundTaskTimeoutMs);
|
|
4567
|
+
const isRootThread = (params) => !params?.threadId || params.threadId === rootThreadId;
|
|
4568
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
4569
|
+
const finished = [];
|
|
4570
|
+
let pendingWait;
|
|
4571
|
+
let waitedMs = 0;
|
|
4572
|
+
let followUpSent = false;
|
|
4573
|
+
let turnMessageText = "";
|
|
4574
|
+
let lastTurn;
|
|
4575
|
+
let lastTasksKey = JSON.stringify({ tasks: [], waiting: false });
|
|
4576
|
+
const liveTasks = () => [...inFlight.values()].filter((item) => item.outlived).map((item) => ({ id: item.id, type: "command", description: item.command }));
|
|
4577
|
+
const emitTasks = (tasks, waiting) => {
|
|
4578
|
+
const key = JSON.stringify({ tasks, waiting });
|
|
4579
|
+
if (key === lastTasksKey) return;
|
|
4580
|
+
lastTasksKey = key;
|
|
4581
|
+
sink.emitEvent(createNormalizedEvent("background.tasks", { provider: request.provider, runId: request.runId }, { tasks, waiting }));
|
|
4582
|
+
};
|
|
4583
|
+
const endWait = () => {
|
|
4584
|
+
if (!pendingWait) return;
|
|
4585
|
+
waitedMs += pendingWait.elapsedMs();
|
|
4586
|
+
pendingWait.clear();
|
|
4587
|
+
pendingWait = void 0;
|
|
4588
|
+
followUpSent = false;
|
|
4589
|
+
};
|
|
4590
|
+
const sendBackgroundFollowUp = async () => {
|
|
4591
|
+
if (!rootThreadId || !pendingWait || followUpSent || finished.length === 0 || abortInvoked) return;
|
|
4592
|
+
if (timeoutMs - waitedMs - pendingWait.elapsedMs() <= 0) return;
|
|
4593
|
+
followUpSent = true;
|
|
4594
|
+
const text2 = buildCodexBackgroundFollowUp(finished.splice(0));
|
|
4595
|
+
try {
|
|
4596
|
+
const response = await client.request(
|
|
4597
|
+
"turn/start",
|
|
4598
|
+
buildCodexTurnStartParams({
|
|
4599
|
+
threadId: rootThreadId,
|
|
4600
|
+
inputItems: [{ type: "text", text: text2, text_elements: [] }],
|
|
4601
|
+
request
|
|
4602
|
+
})
|
|
4603
|
+
);
|
|
4604
|
+
endWait();
|
|
4605
|
+
sink.emitEvent(createNormalizedEvent("message.injected", { provider: request.provider, runId: request.runId }, {
|
|
4606
|
+
content: text2,
|
|
4607
|
+
...typeof response?.turn?.id === "string" ? { messageId: response.turn.id } : {}
|
|
4608
|
+
}));
|
|
4609
|
+
} catch (error) {
|
|
4610
|
+
debugCodex("background follow-up turn/start failed: %o", error);
|
|
4611
|
+
}
|
|
4612
|
+
};
|
|
4204
4613
|
const completion = new Promise((resolve, reject) => {
|
|
4614
|
+
const settle = () => {
|
|
4615
|
+
endWait();
|
|
4616
|
+
emitTasks([], false);
|
|
4617
|
+
sink.emitEvent(createNormalizedEvent("run.completed", { provider: request.provider, runId: request.runId }, { text: lastTurn?.messageText || void 0 }));
|
|
4618
|
+
resolve({ text: lastTurn?.text ?? streamedText, turnId, threadId: rootThreadId, interrupted: false });
|
|
4619
|
+
};
|
|
4620
|
+
const settleOnFailure = (error) => {
|
|
4621
|
+
if (!pendingWait || !lastTurn || abortInvoked) return false;
|
|
4622
|
+
debugCodex("\u2605 transport failed during background wait; settling on the last turn: %o", error);
|
|
4623
|
+
settle();
|
|
4624
|
+
return true;
|
|
4625
|
+
};
|
|
4205
4626
|
void (async () => {
|
|
4206
4627
|
let firstClientMessageLogged = false;
|
|
4207
|
-
|
|
4628
|
+
const iterator = client.messages()[Symbol.asyncIterator]();
|
|
4629
|
+
for (let next = iterator.next(); ; next = iterator.next()) {
|
|
4630
|
+
let step;
|
|
4631
|
+
try {
|
|
4632
|
+
step = pendingWait ? await Promise.race([
|
|
4633
|
+
next.then((result) => ({ result })),
|
|
4634
|
+
pendingWait.expired.then((reason) => ({ reason }))
|
|
4635
|
+
]) : { result: await next };
|
|
4636
|
+
} catch (error) {
|
|
4637
|
+
if (settleOnFailure(error)) return;
|
|
4638
|
+
throw error;
|
|
4639
|
+
}
|
|
4640
|
+
if ("reason" in step) {
|
|
4641
|
+
if (!abortInvoked) {
|
|
4642
|
+
debugCodex("\u2605 background wait over (%s) with %d command(s) in flight", step.reason, inFlight.size);
|
|
4643
|
+
if (step.reason === "ceiling" && rootThreadId) await terminateBackgroundTerminals(client, rootThreadId);
|
|
4644
|
+
settle();
|
|
4645
|
+
return;
|
|
4646
|
+
}
|
|
4647
|
+
endWait();
|
|
4648
|
+
step = { result: await next };
|
|
4649
|
+
}
|
|
4650
|
+
if (step.result.done) break;
|
|
4651
|
+
const message = step.result.value;
|
|
4208
4652
|
if (!firstClientMessageLogged) {
|
|
4209
4653
|
firstClientMessageLogged = true;
|
|
4210
4654
|
debugCodex(
|
|
@@ -4260,36 +4704,96 @@ var CodexAgentAdapter = class {
|
|
|
4260
4704
|
if (approvalKey) pendingFileChanges.delete(approvalKey);
|
|
4261
4705
|
continue;
|
|
4262
4706
|
}
|
|
4707
|
+
const elicitation = createCodexElicitationPermissionEvent(request, message);
|
|
4708
|
+
if (elicitation && message.id !== void 0) {
|
|
4709
|
+
const response = interactiveApproval ? await sink.requestPermission(elicitation) : { requestId: elicitation.requestId, decision: "allow" };
|
|
4710
|
+
await client.respond(message.id, toCodexElicitationResult(message, response));
|
|
4711
|
+
continue;
|
|
4712
|
+
}
|
|
4713
|
+
if (message.id !== void 0) {
|
|
4714
|
+
debugCodex("unsupported server request %s; declining", message.method);
|
|
4715
|
+
await (message.method === CODEX_ELICITATION_METHOD ? client.respond(message.id, { action: "cancel", content: null }) : client.respondError(message.id, {
|
|
4716
|
+
code: -32601,
|
|
4717
|
+
message: `Unsupported request ${message.method}`
|
|
4718
|
+
}));
|
|
4719
|
+
continue;
|
|
4720
|
+
}
|
|
4263
4721
|
if (message.method === "item/completed") {
|
|
4264
4722
|
const item2 = message.params?.item;
|
|
4265
4723
|
if (item2?.type === "plan" && typeof item2.text === "string") {
|
|
4266
4724
|
sink.emitEvent(createNormalizedEvent("plan.completed", { provider: request.provider, runId: request.runId }, { text: item2.text }));
|
|
4267
4725
|
}
|
|
4268
4726
|
}
|
|
4727
|
+
const turn = message.params?.turn;
|
|
4728
|
+
const rootTurnCompleted = message.method === "turn/completed" && isRootThread(message.params);
|
|
4729
|
+
const waitAfterTurn = rootTurnCompleted && pendingTurns <= 1 && timeoutMs !== 0 && !abortInvoked && turn?.status === "completed" && (inFlight.size > 0 || finished.length > 0);
|
|
4269
4730
|
for (const event of toNormalizedCodexEvents(request.runId, message)) {
|
|
4731
|
+
if (event.type === "run.completed" && (waitAfterTurn || turn?.status === "interrupted")) continue;
|
|
4270
4732
|
sink.emitEvent(event);
|
|
4271
4733
|
if (event.type === "text.delta") {
|
|
4272
4734
|
streamedText += event.delta;
|
|
4735
|
+
} else if (event.type === "message.completed" && event.text) {
|
|
4736
|
+
turnMessageText = event.text;
|
|
4273
4737
|
}
|
|
4274
4738
|
}
|
|
4275
4739
|
if (message.method === "thread/started" && !rootThreadId) {
|
|
4276
4740
|
rootThreadId = message.params?.thread?.id ?? rootThreadId;
|
|
4277
4741
|
}
|
|
4278
4742
|
if (message.method === "turn/started") {
|
|
4279
|
-
turnId =
|
|
4743
|
+
turnId = turn?.id ?? turnId;
|
|
4744
|
+
if (isRootThread(message.params)) {
|
|
4745
|
+
streamedText = "";
|
|
4746
|
+
turnMessageText = "";
|
|
4747
|
+
if (pendingWait) debugCodex("\u2605 follow-up turn started; background wait over");
|
|
4748
|
+
endWait();
|
|
4749
|
+
emitTasks(liveTasks(), false);
|
|
4750
|
+
}
|
|
4280
4751
|
}
|
|
4281
|
-
if (
|
|
4752
|
+
if (item?.type === "commandExecution" && typeof item.id === "string" && isRootThread(message.params)) {
|
|
4753
|
+
if (message.method === "item/started") {
|
|
4754
|
+
inFlight.set(item.id, {
|
|
4755
|
+
id: item.id,
|
|
4756
|
+
command: String(item.command ?? ""),
|
|
4757
|
+
processId: typeof item.processId === "string" ? item.processId : void 0,
|
|
4758
|
+
outlived: false
|
|
4759
|
+
});
|
|
4760
|
+
} else if (message.method === "item/completed") {
|
|
4761
|
+
const tracked = inFlight.get(item.id);
|
|
4762
|
+
inFlight.delete(item.id);
|
|
4763
|
+
if (tracked?.outlived && pendingWait) {
|
|
4764
|
+
finished.push({
|
|
4765
|
+
...tracked,
|
|
4766
|
+
aggregatedOutput: typeof item.aggregatedOutput === "string" ? item.aggregatedOutput : void 0,
|
|
4767
|
+
exitCode: typeof item.exitCode === "number" ? item.exitCode : void 0,
|
|
4768
|
+
durationMs: typeof item.durationMs === "number" ? item.durationMs : void 0
|
|
4769
|
+
});
|
|
4770
|
+
}
|
|
4771
|
+
emitTasks(liveTasks(), pendingWait !== void 0);
|
|
4772
|
+
pendingWait?.setIdle(inFlight.size === 0);
|
|
4773
|
+
if (pendingWait && inFlight.size === 0) await sendBackgroundFollowUp();
|
|
4774
|
+
}
|
|
4775
|
+
}
|
|
4776
|
+
if (rootTurnCompleted) {
|
|
4282
4777
|
pendingTurns--;
|
|
4283
4778
|
if (pendingTurns <= 0) {
|
|
4284
|
-
const
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4779
|
+
const previousText = lastTurn?.text;
|
|
4780
|
+
lastTurn = { text: streamedText, messageText: turnMessageText };
|
|
4781
|
+
if (!waitAfterTurn) {
|
|
4782
|
+
resolve({
|
|
4783
|
+
text: streamedText || previousText,
|
|
4784
|
+
turnId,
|
|
4785
|
+
threadId: rootThreadId,
|
|
4786
|
+
interrupted: turn?.status === "interrupted"
|
|
4787
|
+
});
|
|
4788
|
+
return;
|
|
4789
|
+
}
|
|
4790
|
+
for (const tracked of inFlight.values()) tracked.outlived = true;
|
|
4791
|
+
debugCodex("\u2605 turn ended with %d command(s) in flight; waiting", inFlight.size);
|
|
4792
|
+
endWait();
|
|
4793
|
+
pendingWait = new BackgroundWait(BACKGROUND_TASK_GRACE_MS, Math.max(0, timeoutMs - waitedMs));
|
|
4794
|
+
pendingWait.setIdle(inFlight.size === 0);
|
|
4795
|
+
emitTasks(liveTasks(), true);
|
|
4796
|
+
if (inFlight.size === 0) await sendBackgroundFollowUp();
|
|
4293
4797
|
}
|
|
4294
4798
|
}
|
|
4295
4799
|
if (message.method === "error" && !shouldIgnoreCodexError(message)) {
|
|
@@ -4297,6 +4801,7 @@ var CodexAgentAdapter = class {
|
|
|
4297
4801
|
return;
|
|
4298
4802
|
}
|
|
4299
4803
|
}
|
|
4804
|
+
if (settleOnFailure(new Error("Codex transport closed."))) return;
|
|
4300
4805
|
reject(new Error("Codex transport closed before run completed."));
|
|
4301
4806
|
})().catch(reject);
|
|
4302
4807
|
});
|
|
@@ -4367,6 +4872,8 @@ var CodexAgentAdapter = class {
|
|
|
4367
4872
|
} catch (err) {
|
|
4368
4873
|
completionError = err;
|
|
4369
4874
|
}
|
|
4875
|
+
endWait();
|
|
4876
|
+
emitTasks([], false);
|
|
4370
4877
|
if (completionError !== void 0) {
|
|
4371
4878
|
if (abortInvoked) {
|
|
4372
4879
|
debugCodex(
|
|
@@ -4374,7 +4881,7 @@ var CodexAgentAdapter = class {
|
|
|
4374
4881
|
Date.now() - executeStartedAt
|
|
4375
4882
|
);
|
|
4376
4883
|
sink.cancel({
|
|
4377
|
-
text: streamedText || void 0,
|
|
4884
|
+
text: streamedText || lastTurn?.text || void 0,
|
|
4378
4885
|
costData: extractCodexCostData(rawPayloads)
|
|
4379
4886
|
});
|
|
4380
4887
|
} else {
|
|
@@ -4399,6 +4906,7 @@ var CodexAgentAdapter = class {
|
|
|
4399
4906
|
}
|
|
4400
4907
|
}
|
|
4401
4908
|
} finally {
|
|
4909
|
+
pendingWait?.clear();
|
|
4402
4910
|
await runtime.cleanup().catch(() => void 0);
|
|
4403
4911
|
}
|
|
4404
4912
|
return async () => void 0;
|
|
@@ -4411,36 +4919,51 @@ var CodexAgentAdapter = class {
|
|
|
4411
4919
|
* driven by the normalized `message.started` event whose
|
|
4412
4920
|
* `messageId` IS the codex turnId).
|
|
4413
4921
|
*
|
|
4414
|
-
*
|
|
4922
|
+
* When that interrupt is rejected — the thread is idle in a background
|
|
4923
|
+
* wait, or the turn id is stale or missing — a turn is started only to be
|
|
4924
|
+
* interrupted, so the originating run still observes an interrupted turn
|
|
4925
|
+
* and cancels; the model's leftover processes are then terminated.
|
|
4926
|
+
* Without `sessionId` the call is a no-op.
|
|
4415
4927
|
*/
|
|
4416
4928
|
async attachAbort(request) {
|
|
4417
4929
|
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
|
-
);
|
|
4930
|
+
if (!threadId) {
|
|
4931
|
+
debugCodex("attachAbort runId=%s skipped: no threadId", request.runId);
|
|
4426
4932
|
return;
|
|
4427
4933
|
}
|
|
4428
4934
|
await withCodexAppServer(request, async (client) => {
|
|
4429
|
-
|
|
4430
|
-
|
|
4935
|
+
const bounded = (what, promise) => Promise.race([
|
|
4936
|
+
promise,
|
|
4431
4937
|
new Promise(
|
|
4432
|
-
(_, reject) => setTimeout(
|
|
4433
|
-
() => reject(new Error("codex turn/interrupt timed out")),
|
|
4434
|
-
3e3
|
|
4435
|
-
)
|
|
4938
|
+
(_, reject) => setTimeout(() => reject(new Error(`codex ${what} timed out`)), 3e3)
|
|
4436
4939
|
)
|
|
4437
|
-
])
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4940
|
+
]);
|
|
4941
|
+
const interrupt = (turnId) => bounded("turn/interrupt", client.request("turn/interrupt", { threadId, turnId }));
|
|
4942
|
+
if (request.turnId) {
|
|
4943
|
+
try {
|
|
4944
|
+
await interrupt(request.turnId);
|
|
4945
|
+
return;
|
|
4946
|
+
} catch (error) {
|
|
4947
|
+
debugCodex("attachAbort runId=%s turn/interrupt failed: %o", request.runId, error);
|
|
4948
|
+
}
|
|
4949
|
+
}
|
|
4950
|
+
try {
|
|
4951
|
+
const response = await bounded(
|
|
4952
|
+
"turn/start",
|
|
4953
|
+
client.request("turn/start", {
|
|
4954
|
+
threadId,
|
|
4955
|
+
input: [{ type: "text", text: CODEX_CANCEL_TURN_TEXT, text_elements: [] }],
|
|
4956
|
+
approvalPolicy: "never",
|
|
4957
|
+
model: null,
|
|
4958
|
+
effort: null,
|
|
4959
|
+
outputSchema: null
|
|
4960
|
+
})
|
|
4442
4961
|
);
|
|
4443
|
-
|
|
4962
|
+
if (typeof response?.turn?.id === "string") await interrupt(response.turn.id);
|
|
4963
|
+
} catch (error) {
|
|
4964
|
+
debugCodex("attachAbort runId=%s cancel turn failed: %o", request.runId, error);
|
|
4965
|
+
}
|
|
4966
|
+
await terminateBackgroundTerminals(client, threadId);
|
|
4444
4967
|
});
|
|
4445
4968
|
}
|
|
4446
4969
|
/**
|
|
@@ -4569,6 +5092,9 @@ function toRawEvent3(runId, payload, type) {
|
|
|
4569
5092
|
payload
|
|
4570
5093
|
};
|
|
4571
5094
|
}
|
|
5095
|
+
function injectedTaskResultChild(text2) {
|
|
5096
|
+
return /<task id="?([^"\s>]+)"? state="?(?:completed|error)"?>/.exec(text2)?.[1];
|
|
5097
|
+
}
|
|
4572
5098
|
function toOpenCodeModel(model) {
|
|
4573
5099
|
if (!model) {
|
|
4574
5100
|
return void 0;
|
|
@@ -5094,35 +5620,149 @@ var OpenCodeAgentAdapter = class {
|
|
|
5094
5620
|
resolveSessionTerminal = resolve;
|
|
5095
5621
|
});
|
|
5096
5622
|
let lastSseActivityAt = Date.now();
|
|
5623
|
+
const postAbort = async (id) => {
|
|
5624
|
+
try {
|
|
5625
|
+
await Promise.race([
|
|
5626
|
+
fetchJson(`${runtime.baseUrl}/session/${id}/abort`, {
|
|
5627
|
+
method: "POST",
|
|
5628
|
+
headers: {
|
|
5629
|
+
"content-type": "application/json",
|
|
5630
|
+
...runtime.previewHeaders
|
|
5631
|
+
}
|
|
5632
|
+
}),
|
|
5633
|
+
new Promise(
|
|
5634
|
+
(_, reject) => setTimeout(
|
|
5635
|
+
() => reject(new Error("opencode POST /session/abort timed out")),
|
|
5636
|
+
3e3
|
|
5637
|
+
)
|
|
5638
|
+
)
|
|
5639
|
+
]);
|
|
5640
|
+
} catch {
|
|
5641
|
+
}
|
|
5642
|
+
};
|
|
5097
5643
|
let userAbortRequested = false;
|
|
5098
5644
|
sink.setAbort(async () => {
|
|
5099
5645
|
userAbortRequested = true;
|
|
5100
5646
|
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
|
-
}
|
|
5647
|
+
if (sessionIdAtAbort) await postAbort(sessionIdAtAbort);
|
|
5124
5648
|
resolveSessionTerminal();
|
|
5125
5649
|
});
|
|
5650
|
+
const backgroundTimeoutMs = resolveBackgroundTaskTimeoutMs(
|
|
5651
|
+
request.options.backgroundTaskTimeoutMs
|
|
5652
|
+
);
|
|
5653
|
+
const trackChildren = backgroundTimeoutMs !== 0;
|
|
5654
|
+
const children = /* @__PURE__ */ new Map();
|
|
5655
|
+
const liveChildren = () => [...children.values()].filter((child) => child.live).map((child) => ({
|
|
5656
|
+
id: child.id,
|
|
5657
|
+
type: "subagent",
|
|
5658
|
+
description: child.title
|
|
5659
|
+
}));
|
|
5660
|
+
const shouldWait = () => [...children.values()].some((child) => child.background) && liveChildren().length > 0;
|
|
5661
|
+
let pendingWait;
|
|
5662
|
+
let waitedMs = 0;
|
|
5663
|
+
let expiry;
|
|
5664
|
+
let sawParentIdle = false;
|
|
5665
|
+
let lastTasksKey = JSON.stringify({ tasks: [], waiting: false });
|
|
5666
|
+
const emitTasks = (tasks, waiting) => {
|
|
5667
|
+
const key = JSON.stringify({ tasks, waiting });
|
|
5668
|
+
if (key === lastTasksKey) return;
|
|
5669
|
+
lastTasksKey = key;
|
|
5670
|
+
sink.emitEvent(
|
|
5671
|
+
createNormalizedEvent(
|
|
5672
|
+
"background.tasks",
|
|
5673
|
+
{ provider: request.provider, runId: request.runId },
|
|
5674
|
+
{ tasks, waiting }
|
|
5675
|
+
)
|
|
5676
|
+
);
|
|
5677
|
+
};
|
|
5678
|
+
const endWait = () => {
|
|
5679
|
+
if (!pendingWait) return;
|
|
5680
|
+
waitedMs += pendingWait.elapsedMs();
|
|
5681
|
+
pendingWait.clear();
|
|
5682
|
+
pendingWait = void 0;
|
|
5683
|
+
};
|
|
5684
|
+
const onChildrenChanged = () => {
|
|
5685
|
+
if (!pendingWait) return;
|
|
5686
|
+
const live = liveChildren();
|
|
5687
|
+
emitTasks(live, true);
|
|
5688
|
+
pendingWait.setIdle(live.length === 0);
|
|
5689
|
+
};
|
|
5690
|
+
const registerChild = (id, title, background = false) => {
|
|
5691
|
+
const known = children.get(id);
|
|
5692
|
+
if (known) {
|
|
5693
|
+
known.background ||= background;
|
|
5694
|
+
if (title && title !== known.title) {
|
|
5695
|
+
known.title = title;
|
|
5696
|
+
onChildrenChanged();
|
|
5697
|
+
}
|
|
5698
|
+
return;
|
|
5699
|
+
}
|
|
5700
|
+
children.set(id, { id, title: title ?? "", live: true, background });
|
|
5701
|
+
onChildrenChanged();
|
|
5702
|
+
};
|
|
5703
|
+
const setChildLive = (id, live) => {
|
|
5704
|
+
const child = children.get(id);
|
|
5705
|
+
if (!child || child.live === live) return;
|
|
5706
|
+
child.live = live;
|
|
5707
|
+
onChildrenChanged();
|
|
5708
|
+
};
|
|
5709
|
+
const reconcileChildren = async () => {
|
|
5710
|
+
const live = [...children.values()].filter((child) => child.live);
|
|
5711
|
+
if (live.length === 0) return;
|
|
5712
|
+
const statuses = await withTimeout(
|
|
5713
|
+
fetchJson(
|
|
5714
|
+
`${runtime.baseUrl}/session/status`,
|
|
5715
|
+
{ headers: runtime.previewHeaders }
|
|
5716
|
+
).catch(() => void 0),
|
|
5717
|
+
3e3
|
|
5718
|
+
);
|
|
5719
|
+
if (!statuses || typeof statuses !== "object") return;
|
|
5720
|
+
for (const child of live) {
|
|
5721
|
+
const status = statuses[child.id];
|
|
5722
|
+
if (!status || status.type === "idle") setChildLive(child.id, false);
|
|
5723
|
+
}
|
|
5724
|
+
};
|
|
5725
|
+
const onParentIdle = async () => {
|
|
5726
|
+
sawParentIdle = true;
|
|
5727
|
+
if (pendingWait || sessionIdleFromSse) return;
|
|
5728
|
+
const tracking = trackChildren && !userAbortRequested;
|
|
5729
|
+
if (tracking && shouldWait()) await reconcileChildren();
|
|
5730
|
+
if (!tracking || !shouldWait()) {
|
|
5731
|
+
sessionIdleFromSse = true;
|
|
5732
|
+
resolveSessionTerminal();
|
|
5733
|
+
return;
|
|
5734
|
+
}
|
|
5735
|
+
const live = liveChildren();
|
|
5736
|
+
debugOpencode(
|
|
5737
|
+
"\u2605 parent idle with %d live subagent(s); waiting",
|
|
5738
|
+
live.length
|
|
5739
|
+
);
|
|
5740
|
+
const wait = new BackgroundWait(
|
|
5741
|
+
BACKGROUND_TASK_GRACE_MS,
|
|
5742
|
+
Math.max(0, backgroundTimeoutMs - waitedMs)
|
|
5743
|
+
);
|
|
5744
|
+
pendingWait = wait;
|
|
5745
|
+
void wait.expired.then((reason) => {
|
|
5746
|
+
if (pendingWait !== wait) return;
|
|
5747
|
+
expiry = reason;
|
|
5748
|
+
resolveSessionTerminal();
|
|
5749
|
+
});
|
|
5750
|
+
emitTasks(live, true);
|
|
5751
|
+
};
|
|
5752
|
+
const onParentBusy = () => {
|
|
5753
|
+
if (!pendingWait) return;
|
|
5754
|
+
debugOpencode(
|
|
5755
|
+
"\u2605 parent resumed after %dms of background wait",
|
|
5756
|
+
pendingWait.elapsedMs()
|
|
5757
|
+
);
|
|
5758
|
+
endWait();
|
|
5759
|
+
emitTasks(liveChildren(), false);
|
|
5760
|
+
};
|
|
5761
|
+
const onInjectedResult = (childId) => {
|
|
5762
|
+
if (!children.has(childId)) return;
|
|
5763
|
+
setChildLive(childId, false);
|
|
5764
|
+
onParentBusy();
|
|
5765
|
+
};
|
|
5126
5766
|
try {
|
|
5127
5767
|
const interactiveApproval = !request.options.fullAccess && isInteractiveApproval(request.options);
|
|
5128
5768
|
let forkedSession = null;
|
|
@@ -5225,6 +5865,12 @@ var OpenCodeAgentAdapter = class {
|
|
|
5225
5865
|
const info = properties?.info;
|
|
5226
5866
|
if (info && typeof info.id === "string" && typeof info.parentID === "string" && runSessionIds.has(info.parentID)) {
|
|
5227
5867
|
runSessionIds.add(info.id);
|
|
5868
|
+
if (trackChildren && info.parentID === sessionId) {
|
|
5869
|
+
registerChild(
|
|
5870
|
+
info.id,
|
|
5871
|
+
typeof info.title === "string" ? info.title : void 0
|
|
5872
|
+
);
|
|
5873
|
+
}
|
|
5228
5874
|
}
|
|
5229
5875
|
}
|
|
5230
5876
|
if (eventType === "message.updated") {
|
|
@@ -5333,28 +5979,35 @@ var OpenCodeAgentAdapter = class {
|
|
|
5333
5979
|
const errMsg = typeof errData?.data?.message === "string" ? errData.data.message : typeof errData?.message === "string" ? errData.message : "OpenCode session error";
|
|
5334
5980
|
sessionErrorFromSse = new Error(errMsg);
|
|
5335
5981
|
}
|
|
5982
|
+
resolveSessionTerminal();
|
|
5336
5983
|
} else {
|
|
5337
|
-
|
|
5984
|
+
await onParentIdle();
|
|
5338
5985
|
}
|
|
5339
5986
|
debugOpencode(
|
|
5340
5987
|
"\u2605 %s for session=%s",
|
|
5341
5988
|
payloadRecord.type,
|
|
5342
5989
|
sessionId
|
|
5343
5990
|
);
|
|
5344
|
-
|
|
5991
|
+
} else if (trackChildren && payloadRecord.type === "session.idle") {
|
|
5992
|
+
setChildLive(eventSessionId, false);
|
|
5345
5993
|
}
|
|
5346
5994
|
}
|
|
5347
5995
|
if (payloadRecord?.type === "session.status") {
|
|
5348
5996
|
const properties = payloadRecord.properties;
|
|
5349
5997
|
const status = properties?.status;
|
|
5350
5998
|
const eventSessionId = typeof properties?.sessionID === "string" ? properties.sessionID : void 0;
|
|
5351
|
-
if (
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5999
|
+
if (!eventSessionId || eventSessionId === sessionId) {
|
|
6000
|
+
if (status?.type === "idle") {
|
|
6001
|
+
debugOpencode(
|
|
6002
|
+
"\u2605 session.status{idle} for session=%s",
|
|
6003
|
+
sessionId
|
|
6004
|
+
);
|
|
6005
|
+
await onParentIdle();
|
|
6006
|
+
} else if (status?.type === "busy" || status?.type === "retry") {
|
|
6007
|
+
onParentBusy();
|
|
6008
|
+
}
|
|
6009
|
+
} else if (trackChildren) {
|
|
6010
|
+
setChildLive(eventSessionId, status?.type !== "idle");
|
|
5358
6011
|
}
|
|
5359
6012
|
}
|
|
5360
6013
|
if (payloadRecord?.type === "message.part.updated") {
|
|
@@ -5363,6 +6016,22 @@ var OpenCodeAgentAdapter = class {
|
|
|
5363
6016
|
if (part && typeof part.id === "string" && typeof part.type === "string") {
|
|
5364
6017
|
partTypeById.set(part.id, part.type);
|
|
5365
6018
|
}
|
|
6019
|
+
if (trackChildren && part?.type === "tool" && part.tool === "task" && part.sessionID === sessionId) {
|
|
6020
|
+
const state = part.state;
|
|
6021
|
+
const metadata = state?.metadata;
|
|
6022
|
+
const childId = metadata?.sessionId ?? metadata?.jobId;
|
|
6023
|
+
if (metadata?.background === true && typeof childId === "string") {
|
|
6024
|
+
registerChild(
|
|
6025
|
+
childId,
|
|
6026
|
+
typeof state?.title === "string" ? state.title : void 0,
|
|
6027
|
+
true
|
|
6028
|
+
);
|
|
6029
|
+
}
|
|
6030
|
+
}
|
|
6031
|
+
if (trackChildren && part?.type === "text" && part.synthetic === true && part.sessionID === sessionId && typeof part.text === "string") {
|
|
6032
|
+
const childId = injectedTaskResultChild(part.text);
|
|
6033
|
+
if (childId) onInjectedResult(childId);
|
|
6034
|
+
}
|
|
5366
6035
|
}
|
|
5367
6036
|
if (payloadRecord?.type === "message.part.delta") {
|
|
5368
6037
|
const properties = payloadRecord.properties;
|
|
@@ -5373,6 +6042,9 @@ var OpenCodeAgentAdapter = class {
|
|
|
5373
6042
|
if (isForeignSession) {
|
|
5374
6043
|
continue;
|
|
5375
6044
|
}
|
|
6045
|
+
if (eventMessageId !== void 0 && announcedUserMessageIds.has(eventMessageId)) {
|
|
6046
|
+
continue;
|
|
6047
|
+
}
|
|
5376
6048
|
const delta = typeof properties?.delta === "string" ? properties.delta : "";
|
|
5377
6049
|
const field = typeof properties?.field === "string" ? properties.field : void 0;
|
|
5378
6050
|
const partType = eventPartId ? partTypeById.get(eventPartId) : void 0;
|
|
@@ -5523,13 +6195,22 @@ var OpenCodeAgentAdapter = class {
|
|
|
5523
6195
|
const SSE_POLL_INTERVAL_MS = 5e3;
|
|
5524
6196
|
lastSseActivityAt = Date.now();
|
|
5525
6197
|
let sseSilent = false;
|
|
5526
|
-
while (!sessionIdleFromSse && !sessionErrorFromSse && !sessionAbortedFromSse && !userAbortRequested && !dispatchError) {
|
|
6198
|
+
while (!sessionIdleFromSse && !sessionErrorFromSse && !sessionAbortedFromSse && !userAbortRequested && !dispatchError && !expiry) {
|
|
5527
6199
|
const silence = Date.now() - lastSseActivityAt;
|
|
5528
6200
|
if (silence > SSE_SILENCE_THRESHOLD_MS) {
|
|
6201
|
+
if (pendingWait && sawParentIdle) {
|
|
6202
|
+
debugOpencode(
|
|
6203
|
+
"SSE went silent (%dms) during background wait; settling",
|
|
6204
|
+
silence
|
|
6205
|
+
);
|
|
6206
|
+
expiry = "transport";
|
|
6207
|
+
break;
|
|
6208
|
+
}
|
|
5529
6209
|
sseSilent = true;
|
|
5530
6210
|
debugOpencode("SSE went silent (%dms) \u2014 giving up", silence);
|
|
5531
6211
|
break;
|
|
5532
6212
|
}
|
|
6213
|
+
if (pendingWait && liveChildren().length > 0) await reconcileChildren();
|
|
5533
6214
|
await Promise.race([
|
|
5534
6215
|
sessionTerminal,
|
|
5535
6216
|
new Promise(
|
|
@@ -5539,6 +6220,16 @@ var OpenCodeAgentAdapter = class {
|
|
|
5539
6220
|
}
|
|
5540
6221
|
sseAbort.abort();
|
|
5541
6222
|
await sseTask;
|
|
6223
|
+
if (expiry === "ceiling" || (sessionErrorFromSse || dispatchError) && liveChildren().length > 0) {
|
|
6224
|
+
debugOpencode(
|
|
6225
|
+
"\u2605 run over (%s) with %d subagent(s) live; aborting them",
|
|
6226
|
+
expiry ?? "failure",
|
|
6227
|
+
liveChildren().length
|
|
6228
|
+
);
|
|
6229
|
+
await postAbort(sessionId);
|
|
6230
|
+
}
|
|
6231
|
+
endWait();
|
|
6232
|
+
emitTasks([], false);
|
|
5542
6233
|
if (userAbortRequested || sessionAbortedFromSse) {
|
|
5543
6234
|
debugOpencode(
|
|
5544
6235
|
"\u2605 run.cancelled (%dms since execute start)",
|
|
@@ -5552,7 +6243,7 @@ var OpenCodeAgentAdapter = class {
|
|
|
5552
6243
|
sink.fail(sessionErrorFromSse);
|
|
5553
6244
|
} else if (dispatchError) {
|
|
5554
6245
|
sink.fail(dispatchError);
|
|
5555
|
-
} else if (sessionIdleFromSse) {
|
|
6246
|
+
} else if (sessionIdleFromSse || expiry) {
|
|
5556
6247
|
debugOpencode(
|
|
5557
6248
|
"\u2605 run.completed (%dms since execute start) chars=%d",
|
|
5558
6249
|
Date.now() - executeStartedAt,
|
|
@@ -5597,6 +6288,7 @@ var OpenCodeAgentAdapter = class {
|
|
|
5597
6288
|
sink.fail(new Error("opencode run ended without a terminal signal"));
|
|
5598
6289
|
}
|
|
5599
6290
|
} finally {
|
|
6291
|
+
endWait();
|
|
5600
6292
|
sseAbort.abort();
|
|
5601
6293
|
if (sseTask) {
|
|
5602
6294
|
await sseTask.catch(() => void 0);
|
|
@@ -5744,6 +6436,7 @@ function prepareAgentOptions(_provider, options) {
|
|
|
5744
6436
|
if (!path11.isAbsolute(options.stateDirectory)) throw new Error("stateDirectory must be an absolute path.");
|
|
5745
6437
|
}
|
|
5746
6438
|
if (options.sandbox && options.processGroup !== void 0) throw new Error("processGroup is only supported for host execution.");
|
|
6439
|
+
resolveBackgroundTaskTimeoutMs(options.backgroundTaskTimeoutMs);
|
|
5747
6440
|
if (options.configuration === "native") {
|
|
5748
6441
|
if (options.sandbox) throw new Error("Native configuration is only supported for host execution.");
|
|
5749
6442
|
if (options.mcps?.length || options.skills?.length || options.subAgents?.length || options.commands?.length || options.enableRtk) {
|