@livx.cc/agentx 0.99.39 → 0.99.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{Agent-DJwg1g8V.d.ts → Agent-D4-CLnxC.d.ts} +1 -1
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +231 -29
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +33 -5
- package/dist/index.js +226 -29
- package/dist/index.js.map +1 -1
- package/dist/{mcp-Bd19NiCL.d.ts → mcp-aONOXRNk.d.ts} +4 -2
- package/dist/mcp.client.d.ts +2 -2
- package/dist/mcp.client.js +23 -3
- package/dist/mcp.client.js.map +1 -1
- package/dist/{tools-Byyh_bae.d.ts → tools-BWzPQVy0.d.ts} +15 -0
- package/dist/tools.shell.d.ts +36 -2
- package/dist/tools.shell.js +46 -2
- package/dist/tools.shell.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { a as AgentOptions, H as Hooks, i as RunResult, A as Agent } from './Agent-
|
|
2
|
-
export { C as ChatFragment, b as CompactResult, D as DEFAULT_MUTATING, c as Decision, P as PermissionOptions, d as PermissionPolicy, e as PermissionRule, f as PreToolUseDecision, R as ReasoningEffort, g as RecordingHooks, h as RecordingLifecycle, T as ToolUse, j as ToolUseMeta, k as composeHooks, l as estimateTokens, p as planMode, r as reasoningToChatFragment } from './Agent-
|
|
1
|
+
import { a as AgentOptions, H as Hooks, i as RunResult, A as Agent } from './Agent-D4-CLnxC.js';
|
|
2
|
+
export { C as ChatFragment, b as CompactResult, D as DEFAULT_MUTATING, c as Decision, P as PermissionOptions, d as PermissionPolicy, e as PermissionRule, f as PreToolUseDecision, R as ReasoningEffort, g as RecordingHooks, h as RecordingLifecycle, T as ToolUse, j as ToolUseMeta, k as composeHooks, l as estimateTokens, p as planMode, r as reasoningToChatFragment } from './Agent-D4-CLnxC.js';
|
|
3
3
|
export { MODEL_ALIASES, ModelSwitch, ResolveModelSwitchOpts, modelShortLabel, resolveModelAlias, resolveModelSwitch } from './models.js';
|
|
4
4
|
import { IFilesystem, FileMetadata } from '@livx.cc/wcli/core';
|
|
5
5
|
export { CommandExecutor, FileMetadata, IFilesystem, IndexedDbFilesystem, MemFilesystem, registerHeadlessCommands } from '@livx.cc/wcli/core';
|
|
6
6
|
import { BodDB } from '@bod.ee/db';
|
|
7
|
-
import { A as AgentTool, C as ChatLike, a as ChatOptions, b as ChatResponse, j as ToolCall, H as HostBridge, U as UserQuestion, g as MessageContent } from './tools-
|
|
8
|
-
export { c as ContentPart, d as HostEvent, I as IMAGE_ELIDE_STUB, e as IMAGE_REF_SCHEME, f as ImageRef, M as Message, R as Role, S as SandboxJobRegistry, h as StreamChunk, T as TodoItem, i as Tool, k as ToolContext, l as bashTool, m as clearImageRefs, n as contentBytes, o as contentText, p as defaultTools, q as editTool, r as elideImages, s as elideInlineImages, t as elideStaleImages, u as exitSessionTool, v as expandImagesForSend, w as imagePart, x as imageRefPart, y as imageRefResult, z as makeContext, B as makeJobTools, D as messageHasImage, E as messageHasImageRef, F as messageHasInlineImage, G as mintImageRef, J as parseImageRef, K as readTool, L as remintImageRefs, N as sendBytes, O as sendBytesPer, P as toWireTools, Q as todoWriteTool, V as toolImageRef, W as toolRegistry, X as toolsByName } from './tools-
|
|
7
|
+
import { A as AgentTool, C as ChatLike, a as ChatOptions, b as ChatResponse, j as ToolCall, H as HostBridge, U as UserQuestion, g as MessageContent } from './tools-BWzPQVy0.js';
|
|
8
|
+
export { c as ContentPart, d as HostEvent, I as IMAGE_ELIDE_STUB, e as IMAGE_REF_SCHEME, f as ImageRef, M as Message, R as Role, S as SandboxJobRegistry, h as StreamChunk, T as TodoItem, i as Tool, k as ToolContext, l as bashTool, m as clearImageRefs, n as contentBytes, o as contentText, p as defaultTools, q as editTool, r as elideImages, s as elideInlineImages, t as elideStaleImages, u as exitSessionTool, v as expandImagesForSend, w as imagePart, x as imageRefPart, y as imageRefResult, z as makeContext, B as makeJobTools, D as messageHasImage, E as messageHasImageRef, F as messageHasInlineImage, G as mintImageRef, J as parseImageRef, K as readTool, L as remintImageRefs, N as sendBytes, O as sendBytesPer, P as toWireTools, Q as todoWriteTool, V as toolImageRef, W as toolRegistry, X as toolsByName } from './tools-BWzPQVy0.js';
|
|
9
|
+
import { ShellJobRegistry } from './tools.shell.js';
|
|
9
10
|
import { SpokenSplitter } from '@bod.ee/voice';
|
|
10
11
|
export { AudioSink, AudioSource, AuthProvider, CartesiaTTS, CartesiaTTSOptions, STT_SAMPLE_RATE, SonioxSTT, SonioxSTTOptions, SttLike, TTS_SAMPLE_RATE, TtsLike, VoiceEngine, VoiceEngineOptions, VoiceState, resolveAuth } from '@bod.ee/voice';
|
|
11
|
-
export { M as McpCall, a as McpImage, b as McpRoute, c as McpRouteResolver, d as McpToolResult, e as McpToolSearchOptions, f as McpToolSpec, g as MountedMcpLike, h as buildMcpCatalog, m as makeLazyMcpToolSearch, i as makeMcpToolSearch, j as makeMcpToolSearchFromMounted, k as mcpToolToAgentTool, l as mcpToolsToAgentTools } from './mcp-
|
|
12
|
+
export { M as McpCall, a as McpImage, b as McpRoute, c as McpRouteResolver, d as McpToolResult, e as McpToolSearchOptions, f as McpToolSpec, g as MountedMcpLike, h as buildMcpCatalog, m as makeLazyMcpToolSearch, i as makeMcpToolSearch, j as makeMcpToolSearchFromMounted, k as mcpToolToAgentTool, l as mcpToolsToAgentTools } from './mcp-aONOXRNk.js';
|
|
12
13
|
import * as libx_js_src_modules_log from 'libx.js/src/modules/log';
|
|
13
14
|
export { log } from 'libx.js/src/modules/log';
|
|
14
15
|
|
|
@@ -438,6 +439,26 @@ declare function diskAgentOptions(opts?: Partial<AgentOptions>): Partial<AgentOp
|
|
|
438
439
|
* edge-safe to import. `cwd` defaults to `process.cwd()`; secret env is scrubbed from spawned children. */
|
|
439
440
|
declare function fullAgentOptions(opts?: Partial<AgentOptions> & {
|
|
440
441
|
cwd?: string;
|
|
442
|
+
/**
|
|
443
|
+
* Push a finished background job back to the MODEL (Claude-Code parity). Without it `background:true`
|
|
444
|
+
* is write-only: the job finishes and nothing ever tells the model — so the Shell tool's description
|
|
445
|
+
* drops its cross-turn promise too, rather than lying to the model about this host.
|
|
446
|
+
*
|
|
447
|
+
* Wire it to the agent you are about to build — the closure runs long after construction:
|
|
448
|
+
* const agent = new Agent(await fullAgentOptions({ ai, onJobExit: (n) => agent.inject(n) }));
|
|
449
|
+
*/
|
|
450
|
+
onJobExit?: (notice: string) => void;
|
|
451
|
+
/**
|
|
452
|
+
* Bring your OWN registry instead of letting the preset build one. This is the handle a preset host
|
|
453
|
+
* otherwise never gets (the preset returns `AgentOptions`, not the registry), which is what left
|
|
454
|
+
* `ShellJobRegistry.setOnExit()` unreachable from this path — a host can now wire (or rewire) the
|
|
455
|
+
* completion callback after the Agent exists:
|
|
456
|
+
* const jobs = new ShellJobRegistry({ cwd, killOnExit: true });
|
|
457
|
+
* const agent = new Agent(await fullAgentOptions({ ai, jobs }));
|
|
458
|
+
* jobs.setOnExit((n) => agent.inject(formatJobExit(n)));
|
|
459
|
+
* `onJobExit` still works and, when both are given, is applied to the registry you passed.
|
|
460
|
+
*/
|
|
461
|
+
jobs?: ShellJobRegistry;
|
|
441
462
|
}): Promise<Partial<AgentOptions>>;
|
|
442
463
|
|
|
443
464
|
/**
|
|
@@ -1258,6 +1279,13 @@ interface TaskToolOptions {
|
|
|
1258
1279
|
maxParallel?: number;
|
|
1259
1280
|
/** Parent hooks — `onSubagentStop` fires with each child's summary when it finishes. */
|
|
1260
1281
|
hooks?: Hooks;
|
|
1282
|
+
/**
|
|
1283
|
+
* Wall-clock ceiling for ONE child run, in ms (default `SUBAGENT_MAX_DURATION_MS`). `maxSteps` alone is
|
|
1284
|
+
* not a bound: 25 steps x a 600s Shell is ~250 minutes, far past any transport deadline wrapping the
|
|
1285
|
+
* parent — and an undeclared tool cannot be named by `toolTimeoutViolations`. This deadline is ENFORCED
|
|
1286
|
+
* (the child's signal is aborted) and therefore honestly declarable as the tool's `maxDurationMs`.
|
|
1287
|
+
*/
|
|
1288
|
+
maxDurationMs?: number;
|
|
1261
1289
|
/** Origin-anchored real-disk dir for child transcript traces (see `AgentOptions.traceDir`). Passed
|
|
1262
1290
|
* down UNCHANGED to nested children so every level writes to the same origin `.agent`. */
|
|
1263
1291
|
traceDir?: string;
|
package/dist/index.js
CHANGED
|
@@ -260,13 +260,17 @@ function stripContent(content, stub) {
|
|
|
260
260
|
});
|
|
261
261
|
return changed ? out : content;
|
|
262
262
|
}
|
|
263
|
+
function clauses(sentence) {
|
|
264
|
+
return sentence.split(/[,;:]\s*|\s+(?:so|but|and|then|because|however|therefore|thus|although|though)\s+/i).map((c) => c.trim()).filter(Boolean);
|
|
265
|
+
}
|
|
263
266
|
function endsOnAnnouncedAction(text) {
|
|
264
267
|
const sentences = text.split(/(?<=[.!?])\s+|\n+/).map((s) => s.trim()).filter(Boolean);
|
|
265
268
|
const last = sentences[sentences.length - 1];
|
|
266
269
|
if (!last || NEXT_STEP.test(last) || SIGN_OFF.test(last)) return false;
|
|
267
|
-
|
|
270
|
+
if (PROGRESSIVE.test(last)) return true;
|
|
271
|
+
return clauses(last).some((c) => !NEXT_STEP.test(c) && !SIGN_OFF.test(c) && COMMITMENT.test(c));
|
|
268
272
|
}
|
|
269
|
-
var log2, p_sep, IMAGE_ELIDE_STUB, IMAGE_REF_SCHEME, REF_REGISTRY_MAX, refRegistry, isImageRefUrl, bearsImage,
|
|
273
|
+
var log2, p_sep, IMAGE_ELIDE_STUB, IMAGE_REF_SCHEME, REF_REGISTRY_MAX, refRegistry, isImageRefUrl, bearsImage, COMMITMENT, PROGRESSIVE, NEXT_STEP, SIGN_OFF;
|
|
270
274
|
var init_llm = __esm({
|
|
271
275
|
"src/llm.ts"() {
|
|
272
276
|
"use strict";
|
|
@@ -279,7 +283,8 @@ var init_llm = __esm({
|
|
|
279
283
|
refRegistry = /* @__PURE__ */ new Map();
|
|
280
284
|
isImageRefUrl = (url) => !!url && url.startsWith(IMAGE_REF_SCHEME);
|
|
281
285
|
bearsImage = (m) => messageHasImage(m.content) || toolImageRef(m.content) != null;
|
|
282
|
-
|
|
286
|
+
COMMITMENT = /^(?:also\s+|now\s+|then\s+|so\s+)*(?:let me\b(?!\s+know\b)|i'?ll\b|i will\b|i'?m going to\b|i\s+(?:still\s+|now\s+|first\s+)*need to\b)/i;
|
|
287
|
+
PROGRESSIVE = /^(?:also\s+|now\s+|then\s+)*(?:re-?)?(?:check|search|scan|read|verify|run|inspect|expand|look|examin|continu|proceed|try|fetch|load|open|test|build|grep|find|review|trac|dig|explor)\w*ing\b/i;
|
|
283
288
|
NEXT_STEP = /^next (?:step|run|up)\b/i;
|
|
284
289
|
SIGN_OFF = /^let me know\b/i;
|
|
285
290
|
}
|
|
@@ -904,6 +909,8 @@ function makeWebFetchTool(options = {}) {
|
|
|
904
909
|
return {
|
|
905
910
|
name: "WebFetch",
|
|
906
911
|
description: "Fetch an http/https URL and return its readable text (HTML is stripped to text). Use to read docs or web pages. Returns the status line then up to ~100k chars of content.",
|
|
912
|
+
// Declared so a transport above the loop sizes its deadline around this one (see AgentTool.maxDurationMs).
|
|
913
|
+
maxDurationMs: timeoutMs,
|
|
907
914
|
parameters: { type: "object", required: ["url"], properties: { url: { type: "string", description: "absolute http(s) URL" } } },
|
|
908
915
|
async run({ url }) {
|
|
909
916
|
const doFetch = options.fetch ?? globalThis.fetch;
|
|
@@ -1941,6 +1948,7 @@ var init_shell_sandbox = __esm({
|
|
|
1941
1948
|
var tools_shell_exports = {};
|
|
1942
1949
|
__export(tools_shell_exports, {
|
|
1943
1950
|
ShellJobRegistry: () => ShellJobRegistry,
|
|
1951
|
+
formatJobExit: () => formatJobExit,
|
|
1944
1952
|
makeRealShellTool: () => makeRealShellTool,
|
|
1945
1953
|
makeShellJobTools: () => makeShellJobTools
|
|
1946
1954
|
});
|
|
@@ -1971,12 +1979,27 @@ async function nodeSpawn() {
|
|
|
1971
1979
|
if (!_spawn) _spawn = (await import("child_process")).spawn;
|
|
1972
1980
|
return _spawn;
|
|
1973
1981
|
}
|
|
1982
|
+
function formatJobExit(n) {
|
|
1983
|
+
return `[background job ${n.id} ${n.status}${n.exitCode != null ? ` exit ${n.exitCode}` : ""}] \`${n.command}\`
|
|
1984
|
+
` + (n.tail ? `${n.tail}
|
|
1985
|
+
` : "(no output)\n") + `Read the full output with ShellOutput({id:"${n.id}"}).`;
|
|
1986
|
+
}
|
|
1974
1987
|
function makeRealShellTool(options) {
|
|
1975
1988
|
const defaultTimeoutMs = options.timeoutMs ?? 12e4;
|
|
1976
1989
|
const maxTimeoutMs = Math.max(options.maxTimeoutMs ?? 6e5, defaultTimeoutMs);
|
|
1990
|
+
const backgroundDoc = () => {
|
|
1991
|
+
const base = "Set `background:true` for long-running processes (servers, watchers) and for anything that already timed out in the foreground \u2014 returns a job id immediately; poll with ShellOutput/ShellStatus, stop with ShellKill. ";
|
|
1992
|
+
const notify = options.registry?.notifiesOnExit ? "Its completion is reported back to you when it finishes." : "NOTE: nothing will tell you when it finishes \u2014 poll ShellOutput/ShellStatus yourself, or you will never see its result.";
|
|
1993
|
+
const survival = options.jobsSurviveRun === false ? "The job does NOT survive the end of this run, so do not stop while you still need its result." : "It keeps running across turns.";
|
|
1994
|
+
return `${base}${notify} ${survival}`;
|
|
1995
|
+
};
|
|
1977
1996
|
return {
|
|
1978
1997
|
name: "Shell",
|
|
1979
|
-
description
|
|
1998
|
+
get description() {
|
|
1999
|
+
return `Run a shell command via /bin/sh in the working directory. Executes any installed binary \u2014 ls, cat, grep, git, bun, node, curl, scripts, etc. Returns combined stdout+stderr; non-zero exits are prefixed \`[exit N]\`. Runs non-interactively with no terminal (stdin is /dev/null): commands that prompt for input fail fast rather than hang \u2014 for privileged actions use a non-interactive flag (e.g. \`sudo -n\`), or ask the user to run the command themselves. Each command is killed after ${defaultTimeoutMs}ms (result \`[exit 124]\` with whatever output it produced) \u2014 pass \`timeoutMs\` (max ${maxTimeoutMs}) for a legitimately slower command, and always bound network commands yourself (e.g. \`curl -m 10\`). ` + backgroundDoc();
|
|
2000
|
+
},
|
|
2001
|
+
// Declared so transports above the agent loop can size their own deadline with headroom (see AgentTool.maxDurationMs).
|
|
2002
|
+
maxDurationMs: maxTimeoutMs,
|
|
1980
2003
|
parameters: {
|
|
1981
2004
|
type: "object",
|
|
1982
2005
|
required: ["command"],
|
|
@@ -2078,8 +2101,9 @@ function makeRealShellTool(options) {
|
|
|
2078
2101
|
};
|
|
2079
2102
|
}
|
|
2080
2103
|
function reasonFor(timedOut, timeoutMs, body) {
|
|
2081
|
-
const head = timedOut ? `[exit 124] timed out after ${timeoutMs}ms (killed)
|
|
2104
|
+
const head = timedOut ? `[exit 124] timed out after ${timeoutMs}ms (killed). Re-running this command unchanged will time out again. Either re-run it with background:true (returns a job id immediately; poll with ShellOutput/ShellStatus, stop with ShellKill) or narrow it so it can finish \u2014 bound it, scope it, or ask for less.` : "[exit 130] cancelled (killed)";
|
|
2082
2105
|
return body ? `${head}
|
|
2106
|
+
Partial output before the kill:
|
|
2083
2107
|
${body}` : head;
|
|
2084
2108
|
}
|
|
2085
2109
|
function makeShellJobTools(registry) {
|
|
@@ -2160,12 +2184,14 @@ var init_tools_shell = __esm({
|
|
|
2160
2184
|
job.status = "error";
|
|
2161
2185
|
append(`
|
|
2162
2186
|
[error] ${err?.message ?? err}`);
|
|
2187
|
+
this.notifyExit(id, job);
|
|
2163
2188
|
}
|
|
2164
2189
|
});
|
|
2165
2190
|
proc.on("close", (code) => {
|
|
2166
2191
|
if (job.status === "running") {
|
|
2167
2192
|
job.status = "exited";
|
|
2168
2193
|
job.exitCode = code ?? void 0;
|
|
2194
|
+
this.notifyExit(id, job);
|
|
2169
2195
|
}
|
|
2170
2196
|
});
|
|
2171
2197
|
} catch (e) {
|
|
@@ -2175,6 +2201,29 @@ var init_tools_shell = __esm({
|
|
|
2175
2201
|
this.jobs.set(id, job);
|
|
2176
2202
|
return id;
|
|
2177
2203
|
}
|
|
2204
|
+
/** Fire `onExit` at most once per job, with the tail so the model can act without a second round-trip. */
|
|
2205
|
+
notified = /* @__PURE__ */ new Set();
|
|
2206
|
+
notifyExit(id, job) {
|
|
2207
|
+
if (this.notified.has(id) || !this.cfg.onExit) return;
|
|
2208
|
+
this.notified.add(id);
|
|
2209
|
+
try {
|
|
2210
|
+
this.cfg.onExit({ id, command: job.command, status: job.status, exitCode: job.exitCode, tail: clean(job.buf).slice(-4e3) });
|
|
2211
|
+
} catch {
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
/**
|
|
2215
|
+
* Wire (or rewire) the completion callback AFTER construction. A host that only gets its agent handle
|
|
2216
|
+
* once the Agent is constructed (the library's own `fullAgentOptions` preset, any embedder) could not
|
|
2217
|
+
* pass `onExit` up front, so background completions were silently REPL-only. Set it here instead.
|
|
2218
|
+
*/
|
|
2219
|
+
setOnExit(fn) {
|
|
2220
|
+
this.cfg.onExit = fn;
|
|
2221
|
+
}
|
|
2222
|
+
/** Whether a finished job will actually be reported to the model. The Shell tool's description reads
|
|
2223
|
+
* this so it can't promise a completion notice on a host that discards it. */
|
|
2224
|
+
get notifiesOnExit() {
|
|
2225
|
+
return !!this.cfg.onExit;
|
|
2226
|
+
}
|
|
2178
2227
|
/** Current tail output for a job (null = no such job). */
|
|
2179
2228
|
output(id) {
|
|
2180
2229
|
return this.jobs.get(id)?.buf ?? (this.jobs.has(id) ? "" : null);
|
|
@@ -3064,6 +3113,30 @@ async function boundedPool(items, limit, fn) {
|
|
|
3064
3113
|
await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, worker));
|
|
3065
3114
|
return out;
|
|
3066
3115
|
}
|
|
3116
|
+
var SUBAGENT_MAX_DURATION_MS = 18e5;
|
|
3117
|
+
function wallClock(parent, ms) {
|
|
3118
|
+
const ac = new AbortController();
|
|
3119
|
+
let fired = false;
|
|
3120
|
+
const onParent = () => ac.abort();
|
|
3121
|
+
const timer = setTimeout(() => {
|
|
3122
|
+
fired = true;
|
|
3123
|
+
ac.abort();
|
|
3124
|
+
}, ms);
|
|
3125
|
+
if (parent) {
|
|
3126
|
+
if (parent.aborted) ac.abort();
|
|
3127
|
+
else parent.addEventListener("abort", onParent, { once: true });
|
|
3128
|
+
}
|
|
3129
|
+
return {
|
|
3130
|
+
signal: ac.signal,
|
|
3131
|
+
timedOut: () => fired,
|
|
3132
|
+
dispose: () => {
|
|
3133
|
+
clearTimeout(timer);
|
|
3134
|
+
parent?.removeEventListener("abort", onParent);
|
|
3135
|
+
}
|
|
3136
|
+
};
|
|
3137
|
+
}
|
|
3138
|
+
var deadlineNote = (ms) => `
|
|
3139
|
+
[It was stopped by the sub-task wall-clock deadline of ${Math.round(ms / 1e3)}s \u2014 not by the user. Re-delegate a NARROWER sub-task (or continue from the trace); the same prompt will hit the same deadline again.]`;
|
|
3067
3140
|
function sanitizeSlug(raw) {
|
|
3068
3141
|
return raw.replace(/[^a-zA-Z0-9._-]/g, "-").replace(/-+/g, "-").slice(0, 40);
|
|
3069
3142
|
}
|
|
@@ -3126,14 +3199,25 @@ function abortHint(label, res, file) {
|
|
|
3126
3199
|
}
|
|
3127
3200
|
async function runChildTraced(args) {
|
|
3128
3201
|
const { opts, label, agentType, prompt } = args;
|
|
3129
|
-
|
|
3202
|
+
const deadlineMs = opts.maxDurationMs ?? SUBAGENT_MAX_DURATION_MS;
|
|
3203
|
+
const wc = wallClock(args.signal, deadlineMs);
|
|
3204
|
+
args.childOpts.signal = wc.signal;
|
|
3130
3205
|
const file = opts.traceDir ? traceFileFor(opts.traceDir, label, args.childDepth) : void 0;
|
|
3131
3206
|
const meta = { label, agentType, prompt };
|
|
3132
3207
|
const child = new Agent(args.childOpts);
|
|
3133
3208
|
if (file) installTrace(child, file, meta);
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3209
|
+
let res;
|
|
3210
|
+
try {
|
|
3211
|
+
res = await child.run(prompt);
|
|
3212
|
+
} finally {
|
|
3213
|
+
wc.dispose();
|
|
3214
|
+
}
|
|
3215
|
+
if (file) flushTrace(file, meta, child.transcript, wc.timedOut() ? "deadline" : res.finishReason);
|
|
3216
|
+
let result = res.finishReason === "stop" ? res.text || `(child '${label}' finished with no summary; finishReason=${res.finishReason})` : abortHint(label, res, file);
|
|
3217
|
+
if (wc.timedOut()) {
|
|
3218
|
+
log5.warn(`sub-task '${label}' hit its ${deadlineMs}ms wall-clock deadline (steps=${res.steps})`);
|
|
3219
|
+
result += deadlineNote(deadlineMs);
|
|
3220
|
+
}
|
|
3137
3221
|
await opts.hooks?.onSubagentStop?.(result, { label, agentType });
|
|
3138
3222
|
return { res, result };
|
|
3139
3223
|
}
|
|
@@ -3160,7 +3244,7 @@ function makeTaskTool(opts) {
|
|
|
3160
3244
|
const maxDepth = opts.maxDepth ?? 2;
|
|
3161
3245
|
return {
|
|
3162
3246
|
name: "Task",
|
|
3163
|
-
description: 'Delegate a self-contained sub-task to a child agent over the same filesystem. It runs autonomously with its own step budget and returns a concise summary \u2014 use to isolate context-heavy work (broad search, a scoped refactor). Provide a short `description` and a full `prompt`. If it is interrupted or fails, the result is a STATUS HINT with a pointer to its full transcript (Read it to recover what it did, then decide whether to continue or restart) \u2014 do not assume an incomplete sub-task succeeded. Set `background: true` to run it detached (returns a job id to poll with JobOutput while you keep working); its file edits are overlay-isolated and commit when it finishes. Set `isolation: "worktree"` to run in a real git worktree (needed when the child uses real shell commands that must see/modify actual files) \u2014 slower (~200ms setup), auto-cleaned if no changes.',
|
|
3247
|
+
description: 'Delegate a self-contained sub-task to a child agent over the same filesystem. It runs autonomously with its own step budget and a wall-clock deadline, and returns a concise summary \u2014 use to isolate context-heavy work (broad search, a scoped refactor). Provide a short `description` and a full `prompt`. If it is interrupted or fails, the result is a STATUS HINT with a pointer to its full transcript (Read it to recover what it did, then decide whether to continue or restart) \u2014 do not assume an incomplete sub-task succeeded. Set `background: true` to run it detached (returns a job id to poll with JobOutput while you keep working); its file edits are overlay-isolated and commit when it finishes. Set `isolation: "worktree"` to run in a real git worktree (needed when the child uses real shell commands that must see/modify actual files) \u2014 slower (~200ms setup), auto-cleaned if no changes.',
|
|
3164
3248
|
parameters: {
|
|
3165
3249
|
type: "object",
|
|
3166
3250
|
required: ["description", "prompt"],
|
|
@@ -3172,6 +3256,12 @@ function makeTaskTool(opts) {
|
|
|
3172
3256
|
isolation: { type: "string", enum: ["overlay", "worktree"], description: 'isolation mode: "overlay" (default, VFS layer) or "worktree" (real git worktree for shell-heavy work)' }
|
|
3173
3257
|
}
|
|
3174
3258
|
},
|
|
3259
|
+
// Declared because it is ENFORCED (see runChildTraced) — a transport above the loop sizes its own
|
|
3260
|
+
// deadline around this one (AgentTool.maxDurationMs). Declaring an unenforced number would be a lie;
|
|
3261
|
+
// declaring nothing left `Task` invisible to toolTimeoutViolations, which is the same hole.
|
|
3262
|
+
maxDurationMs: opts.maxDurationMs ?? SUBAGENT_MAX_DURATION_MS,
|
|
3263
|
+
maxDurationEnforced: true,
|
|
3264
|
+
// the wall clock below ABORTS the child at this bound — it cannot be overrun, so the transport needs slack, not a doubling
|
|
3175
3265
|
async run({ description, prompt, agentType, background, isolation }, ctx) {
|
|
3176
3266
|
if (depth >= maxDepth) {
|
|
3177
3267
|
return `Error: Task depth limit reached (maxDepth ${maxDepth}). Cannot spawn another child agent \u2014 do this work directly instead.`;
|
|
@@ -3275,11 +3365,18 @@ function makeTaskBatchTool(opts) {
|
|
|
3275
3365
|
isolation: { type: "string", enum: ["overlay", "worktree"], description: 'isolation mode for ALL children: "overlay" (default) or "worktree" (real git worktree each)' }
|
|
3276
3366
|
}
|
|
3277
3367
|
},
|
|
3368
|
+
// Same enforced bound as `Task`, applied to the WHOLE batch (below), not per child — otherwise the
|
|
3369
|
+
// honest ceiling would be ceil(n/maxParallel) x the per-child deadline, i.e. unbounded in `tasks`.
|
|
3370
|
+
maxDurationMs: opts.maxDurationMs ?? SUBAGENT_MAX_DURATION_MS,
|
|
3371
|
+
maxDurationEnforced: true,
|
|
3372
|
+
// the wall clock below ABORTS the child at this bound — it cannot be overrun, so the transport needs slack, not a doubling
|
|
3278
3373
|
async run({ tasks, isolation }, ctx) {
|
|
3279
3374
|
if (depth >= maxDepth) return `Error: Task depth limit reached (maxDepth ${maxDepth}). Cannot spawn child agents \u2014 do this work directly instead.`;
|
|
3280
3375
|
const list = Array.isArray(tasks) ? tasks : [];
|
|
3281
3376
|
if (!list.length) return "Error: TaskBatch needs a non-empty `tasks` array.";
|
|
3282
3377
|
const useWorktree = isolation === "worktree";
|
|
3378
|
+
const batchDeadlineMs = opts.maxDurationMs ?? SUBAGENT_MAX_DURATION_MS;
|
|
3379
|
+
const batchClock = wallClock(ctx.signal, batchDeadlineMs);
|
|
3283
3380
|
const results = await boundedPool(list, maxParallel, async (t, i) => {
|
|
3284
3381
|
const label = String(t?.description ?? t?.agentType ?? `task ${i + 1}`);
|
|
3285
3382
|
let fs;
|
|
@@ -3298,12 +3395,12 @@ function makeTaskBatchTool(opts) {
|
|
|
3298
3395
|
if (typeof childOpts === "string") return { label, error: childOpts, ok: false };
|
|
3299
3396
|
try {
|
|
3300
3397
|
const childPrompt = wtHandle ? worktreePromptPrefix(wtHandle.branch) + String(t?.prompt ?? "") : String(t?.prompt ?? "");
|
|
3301
|
-
const { res, result } = await runChildTraced({ opts, childOpts, prompt: childPrompt, label, agentType: t?.agentType, childDepth: depth + 1, signal:
|
|
3398
|
+
const { res, result } = await runChildTraced({ opts, childOpts, prompt: childPrompt, label, agentType: t?.agentType, childDepth: depth + 1, signal: batchClock.signal });
|
|
3302
3399
|
return { label, text: result, overlay, wtHandle, ok: res.finishReason !== "error" && res.finishReason !== "aborted" };
|
|
3303
3400
|
} catch (e) {
|
|
3304
3401
|
return { label, error: e instanceof Error ? e.message : String(e), wtHandle, ok: false };
|
|
3305
3402
|
}
|
|
3306
|
-
});
|
|
3403
|
+
}).finally(() => batchClock.dispose());
|
|
3307
3404
|
const lines = [];
|
|
3308
3405
|
for (const r of results) {
|
|
3309
3406
|
if (r.ok && r.overlay) await r.overlay.commit().catch(() => {
|
|
@@ -3317,7 +3414,7 @@ function makeTaskBatchTool(opts) {
|
|
|
3317
3414
|
lines.push(`### ${lines.length + 1}. ${r.label}
|
|
3318
3415
|
${r.error ? `ERROR: ${r.error}` : r.text || "(no summary)"}${extra}`);
|
|
3319
3416
|
}
|
|
3320
|
-
return lines.join("\n\n");
|
|
3417
|
+
return lines.join("\n\n") + (batchClock.timedOut() ? deadlineNote(batchDeadlineMs) : "");
|
|
3321
3418
|
}
|
|
3322
3419
|
};
|
|
3323
3420
|
}
|
|
@@ -4081,7 +4178,17 @@ var Agent = class _Agent {
|
|
|
4081
4178
|
const frag = reasoningToChatFragment(o.model, o.reasoning);
|
|
4082
4179
|
const sentTools = o.model.startsWith("claude-code/") ? [] : wireTools;
|
|
4083
4180
|
const isCursorWithTools = o.model.startsWith("cursor/") && wireTools.length > 0;
|
|
4181
|
+
const hostToolTimeoutMs = o.providerOptions?.toolTimeoutMs;
|
|
4182
|
+
const derivedToolTimeoutMs = Math.max(TRANSPORT_TOOL_TIMEOUT_FLOOR_MS, ...this.activeTools.map(transportHeadroomMs));
|
|
4183
|
+
const effectiveToolTimeoutMs = typeof hostToolTimeoutMs === "number" ? Math.max(hostToolTimeoutMs, derivedToolTimeoutMs) : derivedToolTimeoutMs;
|
|
4184
|
+
if (isCursorWithTools) {
|
|
4185
|
+
if (typeof hostToolTimeoutMs === "number" && effectiveToolTimeoutMs !== hostToolTimeoutMs) {
|
|
4186
|
+
log6.warn(`providerOptions.toolTimeoutMs=${hostToolTimeoutMs}ms is below what the registered tools need \u2014 RAISED to ${effectiveToolTimeoutMs}ms. ${toolTimeoutViolations(this.activeTools, hostToolTimeoutMs).length} tool(s) could outlive it, and the transport would have destroyed their results. To lower it, lower the tools' own maxDurationMs (e.g. the Shell tool's maxTimeoutMs) instead.`);
|
|
4187
|
+
}
|
|
4188
|
+
for (const v of toolTimeoutViolations(this.activeTools, effectiveToolTimeoutMs)) log6.warn(v);
|
|
4189
|
+
}
|
|
4084
4190
|
const cursorPo = isCursorWithTools ? {
|
|
4191
|
+
toolTimeoutMs: effectiveToolTimeoutMs,
|
|
4085
4192
|
toolExecutor: async (name, args) => {
|
|
4086
4193
|
const tc = { id: `cursor-${Date.now()}`, type: "function", function: { name, arguments: JSON.stringify(args) } };
|
|
4087
4194
|
return await this.dispatch(tc);
|
|
@@ -4183,14 +4290,12 @@ var Agent = class _Agent {
|
|
|
4183
4290
|
})() } }))
|
|
4184
4291
|
});
|
|
4185
4292
|
for (const a of delegatedTools) {
|
|
4186
|
-
const
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
})();
|
|
4193
|
-
this.transcript.push({ role: "tool", tool_call_id: a.id, content: cap > 0 && out.length > cap ? cropResult(out, cap) : out });
|
|
4293
|
+
const name = a.name || "tool";
|
|
4294
|
+
const r = delegatedResult(a.output);
|
|
4295
|
+
const text = r.images.length ? r.text : nonEmptyResult(isVacuous(a.output) ? "" : r.text, name, deliveryOf(a.output, a.status));
|
|
4296
|
+
const body = cap > 0 && text.length > cap ? cropResult(text, cap) : text;
|
|
4297
|
+
const content = r.images.length ? [{ type: "text", text: body }, ...r.images.map((i) => imagePart(`data:${i.mimeType};base64,${i.data}`))] : body;
|
|
4298
|
+
this.transcript.push({ role: "tool", tool_call_id: a.id, content });
|
|
4194
4299
|
}
|
|
4195
4300
|
}
|
|
4196
4301
|
const toolCalls = res.toolCalls ?? [];
|
|
@@ -4243,9 +4348,10 @@ var Agent = class _Agent {
|
|
|
4243
4348
|
const raw = await this.dispatch(tc);
|
|
4244
4349
|
let content;
|
|
4245
4350
|
if (typeof raw === "string") {
|
|
4246
|
-
content = raw;
|
|
4351
|
+
content = nonEmptyResult(raw, tc.function.name);
|
|
4247
4352
|
} else {
|
|
4248
|
-
const
|
|
4353
|
+
const hasImages = !!raw.images?.length;
|
|
4354
|
+
const parts = [{ type: "text", text: hasImages ? raw.text : nonEmptyResult(raw.text ?? "", tc.function.name) }];
|
|
4249
4355
|
for (const img of raw.images ?? []) parts.push(imagePart(`data:${img.mimeType};base64,${img.data}`));
|
|
4250
4356
|
content = parts;
|
|
4251
4357
|
}
|
|
@@ -4554,6 +4660,76 @@ var isAnthropicModel = (m) => !!m && (m.startsWith("anthropic/") || /(^|\/)claud
|
|
|
4554
4660
|
function estimateTokens(m, keepRecentImages = 1) {
|
|
4555
4661
|
return Math.ceil(sendBytes(m, keepRecentImages) / 4);
|
|
4556
4662
|
}
|
|
4663
|
+
var TRANSPORT_TOOL_TIMEOUT_FLOOR_MS = 18e5;
|
|
4664
|
+
function transportHeadroomMs(t) {
|
|
4665
|
+
const d = Number(t.maxDurationMs) || 0;
|
|
4666
|
+
if (!d) return 0;
|
|
4667
|
+
return t.maxDurationEnforced ? d + 6e4 : d * 2 + 6e4;
|
|
4668
|
+
}
|
|
4669
|
+
function toolTimeoutViolations(tools, transportMs) {
|
|
4670
|
+
const out = [];
|
|
4671
|
+
for (const t of tools) {
|
|
4672
|
+
const d = Number(t.maxDurationMs) || 0;
|
|
4673
|
+
if (d && d >= transportMs) {
|
|
4674
|
+
out.push(`tool-timeout layering VIOLATED: '${t.name}' may run up to ${d}ms but the transport deadline is ${transportMs}ms \u2014 the transport wins the race and the tool's real result is destroyed. Raise providerOptions.toolTimeoutMs above ${d}ms.`);
|
|
4675
|
+
}
|
|
4676
|
+
}
|
|
4677
|
+
return out;
|
|
4678
|
+
}
|
|
4679
|
+
function nonEmptyResult(out, name = "tool", delivery = "ran") {
|
|
4680
|
+
if (out.trim()) return out;
|
|
4681
|
+
if (delivery === "error") return `[${name}] failed with no error message \u2014 the tool reported an error but produced no detail. Its effect is UNKNOWN: verify the state before assuming it did or did not happen.`;
|
|
4682
|
+
if (delivery === "missing") {
|
|
4683
|
+
const base = `[${name}] returned no result. This is a transport failure, not an empty answer \u2014 the call's outcome is UNKNOWN, so do not assume it succeeded or failed.`;
|
|
4684
|
+
return name === "Shell" ? `${base} Do NOT re-issue the same long-running command: run it with background:true and poll ShellOutput/ShellStatus, or narrow it so it can finish.` : `${base} Retry it, or verify the state another way before continuing.`;
|
|
4685
|
+
}
|
|
4686
|
+
return `[${name}] ran and returned no output. The call completed \u2014 this is an empty answer, not a failure.`;
|
|
4687
|
+
}
|
|
4688
|
+
function isVacuous(v, seen = /* @__PURE__ */ new Set(), depth = 0) {
|
|
4689
|
+
if (v == null) return true;
|
|
4690
|
+
if (typeof v === "string") return v.trim() === "";
|
|
4691
|
+
if (typeof v !== "object") return false;
|
|
4692
|
+
if (seen.has(v) || depth >= 64) return false;
|
|
4693
|
+
seen.add(v);
|
|
4694
|
+
try {
|
|
4695
|
+
if (Array.isArray(v)) return v.every((x) => isVacuous(x, seen, depth + 1));
|
|
4696
|
+
const proto = Object.getPrototypeOf(v);
|
|
4697
|
+
if (proto !== Object.prototype && proto !== null) return false;
|
|
4698
|
+
const vals = Object.values(v);
|
|
4699
|
+
return vals.length === 0 || vals.every((x) => isVacuous(x, seen, depth + 1));
|
|
4700
|
+
} finally {
|
|
4701
|
+
seen.delete(v);
|
|
4702
|
+
}
|
|
4703
|
+
}
|
|
4704
|
+
function delegatedResult(output) {
|
|
4705
|
+
if (typeof output === "string") return { text: output, images: [] };
|
|
4706
|
+
if (output == null) return { text: "", images: [] };
|
|
4707
|
+
const isImg = (i) => i && typeof i.data === "string" && typeof i.mimeType === "string";
|
|
4708
|
+
const o = output;
|
|
4709
|
+
if (Array.isArray(o.images)) {
|
|
4710
|
+
const images = o.images.filter(isImg);
|
|
4711
|
+
if (images.length) return { text: typeof o.text === "string" ? o.text : "", images };
|
|
4712
|
+
}
|
|
4713
|
+
const blocks = Array.isArray(output) ? output : Array.isArray(o.content) ? o.content : void 0;
|
|
4714
|
+
if (blocks) {
|
|
4715
|
+
const images = blocks.filter((b) => b?.type === "image" && isImg(b)).map((b) => ({ mimeType: b.mimeType, data: b.data }));
|
|
4716
|
+
if (images.length) {
|
|
4717
|
+
const text = blocks.filter((b) => b?.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n");
|
|
4718
|
+
return { text, images };
|
|
4719
|
+
}
|
|
4720
|
+
}
|
|
4721
|
+
try {
|
|
4722
|
+
return { text: JSON.stringify(output), images: [] };
|
|
4723
|
+
} catch {
|
|
4724
|
+
return { text: String(output), images: [] };
|
|
4725
|
+
}
|
|
4726
|
+
}
|
|
4727
|
+
function deliveryOf(output, status) {
|
|
4728
|
+
if (status === "error") return "error";
|
|
4729
|
+
if (output == null) return "missing";
|
|
4730
|
+
if (status != null && status !== "completed") return "missing";
|
|
4731
|
+
return "ran";
|
|
4732
|
+
}
|
|
4557
4733
|
function cropResult(result, cap) {
|
|
4558
4734
|
const head = result.slice(0, cap);
|
|
4559
4735
|
const nl = head.lastIndexOf("\n");
|
|
@@ -5359,9 +5535,10 @@ function diskAgentOptions(opts = {}) {
|
|
|
5359
5535
|
return { ...opts };
|
|
5360
5536
|
}
|
|
5361
5537
|
async function fullAgentOptions(opts = {}) {
|
|
5362
|
-
const { makeRealShellTool: makeRealShellTool2, makeShellJobTools: makeShellJobTools2, ShellJobRegistry: ShellJobRegistry2 } = await Promise.resolve().then(() => (init_tools_shell(), tools_shell_exports));
|
|
5363
|
-
const { cwd = process.cwd(), tools, ...rest } = opts;
|
|
5364
|
-
const registry = new ShellJobRegistry2({ cwd, killOnExit: true });
|
|
5538
|
+
const { makeRealShellTool: makeRealShellTool2, makeShellJobTools: makeShellJobTools2, ShellJobRegistry: ShellJobRegistry2, formatJobExit: formatJobExit2 } = await Promise.resolve().then(() => (init_tools_shell(), tools_shell_exports));
|
|
5539
|
+
const { cwd = process.cwd(), tools, onJobExit, jobs, ...rest } = opts;
|
|
5540
|
+
const registry = jobs ?? new ShellJobRegistry2({ cwd, killOnExit: true });
|
|
5541
|
+
if (onJobExit) registry.setOnExit((n) => onJobExit(formatJobExit2(n)));
|
|
5365
5542
|
const shell = [makeRealShellTool2({ cwd, registry }), ...makeShellJobTools2(registry)];
|
|
5366
5543
|
return { ...rest, tools: [...tools ?? defaultTools(), ...shell] };
|
|
5367
5544
|
}
|
|
@@ -8422,13 +8599,29 @@ function toResult(result) {
|
|
|
8422
8599
|
}
|
|
8423
8600
|
return { text: JSON.stringify(result) };
|
|
8424
8601
|
}
|
|
8425
|
-
|
|
8602
|
+
var MCP_CALL_TIMEOUT_MS = 3e5;
|
|
8603
|
+
var TIMED_OUT = /* @__PURE__ */ Symbol("mcp-timeout");
|
|
8604
|
+
async function withDeadline(p, ms) {
|
|
8605
|
+
let t;
|
|
8606
|
+
try {
|
|
8607
|
+
return await Promise.race([p, new Promise((res) => {
|
|
8608
|
+
t = setTimeout(() => res(TIMED_OUT), ms);
|
|
8609
|
+
})]);
|
|
8610
|
+
} finally {
|
|
8611
|
+
clearTimeout(t);
|
|
8612
|
+
}
|
|
8613
|
+
}
|
|
8614
|
+
var mcpTimedOut = (name, ms = MCP_CALL_TIMEOUT_MS) => `[${name}] no response after ${ms / 1e3}s \u2014 the MCP server never answered. The call's outcome is UNKNOWN: do not assume it ran or that it failed. Verify the state (or pick a bounded/narrower call) before retrying.`;
|
|
8615
|
+
function mcpToolToAgentTool(spec, callTool, prefix = "mcp__", timeoutMs = MCP_CALL_TIMEOUT_MS) {
|
|
8426
8616
|
return {
|
|
8427
8617
|
name: `${prefix}${spec.name}`,
|
|
8428
8618
|
description: spec.description ?? `MCP tool ${spec.name}`,
|
|
8429
8619
|
parameters: spec.inputSchema ?? { type: "object", properties: {} },
|
|
8620
|
+
maxDurationMs: timeoutMs,
|
|
8430
8621
|
async run(args, _ctx) {
|
|
8431
|
-
const
|
|
8622
|
+
const raw = await withDeadline(callTool(spec.name, args ?? {}), timeoutMs);
|
|
8623
|
+
if (raw === TIMED_OUT) return mcpTimedOut(spec.name, timeoutMs);
|
|
8624
|
+
const r = toResult(raw);
|
|
8432
8625
|
return r.images?.length ? r : r.text;
|
|
8433
8626
|
}
|
|
8434
8627
|
};
|
|
@@ -8443,6 +8636,7 @@ function describeSpec(s) {
|
|
|
8443
8636
|
}
|
|
8444
8637
|
function makeMcpToolSearch(specs, callTool, options = {}) {
|
|
8445
8638
|
const maxResults = options.maxResults ?? 10;
|
|
8639
|
+
const callTimeoutMs = options.timeoutMs ?? MCP_CALL_TIMEOUT_MS;
|
|
8446
8640
|
const byName = new Map(specs.map((s) => [s.name, s]));
|
|
8447
8641
|
const catalogLine = `${specs.length} MCP tool(s) available \u2014 search by keyword, then call by exact name.`;
|
|
8448
8642
|
const searchTool = {
|
|
@@ -8468,10 +8662,13 @@ function makeMcpToolSearch(specs, callTool, options = {}) {
|
|
|
8468
8662
|
args: { type: "object", description: "arguments object for the tool (per its schema)" }
|
|
8469
8663
|
}
|
|
8470
8664
|
},
|
|
8665
|
+
maxDurationMs: callTimeoutMs,
|
|
8471
8666
|
async run({ name, args }) {
|
|
8472
8667
|
const n = String(name ?? "");
|
|
8473
8668
|
if (!byName.has(n)) return `Error: unknown MCP tool '${n}'. Use ToolSearch to find valid names.`;
|
|
8474
|
-
const
|
|
8669
|
+
const raw = await withDeadline(callTool(n, args ?? {}), callTimeoutMs);
|
|
8670
|
+
if (raw === TIMED_OUT) return mcpTimedOut(n, callTimeoutMs);
|
|
8671
|
+
const r = toResult(raw);
|
|
8475
8672
|
return r.images?.length ? r : r.text;
|
|
8476
8673
|
}
|
|
8477
8674
|
};
|