@livx.cc/agentx 0.99.39 → 0.99.41
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 +1 -0
- package/dist/{Agent-DJwg1g8V.d.ts → Agent-DydgLoT-.d.ts} +5 -1
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +328 -43
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +45 -5
- package/dist/index.js +344 -82
- package/dist/index.js.map +1 -1
- package/dist/{mcp-Bd19NiCL.d.ts → mcp-D5eN0JyQ.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/models.d.ts +15 -1
- package/dist/models.js +7 -0
- package/dist/models.js.map +1 -1
- package/dist/{tools-Byyh_bae.d.ts → tools-S-ArIR3o.d.ts} +29 -2
- package/dist/tools.shell.d.ts +41 -3
- package/dist/tools.shell.js +68 -2
- package/dist/tools.shell.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -258,13 +258,17 @@ function stripContent(content, stub) {
|
|
|
258
258
|
});
|
|
259
259
|
return changed ? out : content;
|
|
260
260
|
}
|
|
261
|
+
function clauses(sentence) {
|
|
262
|
+
return sentence.split(/[,;:]\s*|\s+(?:so|but|and|then|because|however|therefore|thus|although|though)\s+/i).map((c) => c.trim()).filter(Boolean);
|
|
263
|
+
}
|
|
261
264
|
function endsOnAnnouncedAction(text) {
|
|
262
265
|
const sentences = text.split(/(?<=[.!?])\s+|\n+/).map((s) => s.trim()).filter(Boolean);
|
|
263
266
|
const last = sentences[sentences.length - 1];
|
|
264
267
|
if (!last || NEXT_STEP.test(last) || SIGN_OFF.test(last)) return false;
|
|
265
|
-
|
|
268
|
+
if (PROGRESSIVE.test(last)) return true;
|
|
269
|
+
return clauses(last).some((c) => !NEXT_STEP.test(c) && !SIGN_OFF.test(c) && COMMITMENT.test(c));
|
|
266
270
|
}
|
|
267
|
-
var log2, p_sep, IMAGE_ELIDE_STUB, IMAGE_REF_SCHEME, REF_REGISTRY_MAX, refRegistry, isImageRefUrl, bearsImage,
|
|
271
|
+
var log2, p_sep, IMAGE_ELIDE_STUB, IMAGE_REF_SCHEME, REF_REGISTRY_MAX, refRegistry, isImageRefUrl, bearsImage, COMMITMENT, PROGRESSIVE, NEXT_STEP, SIGN_OFF;
|
|
268
272
|
var init_llm = __esm({
|
|
269
273
|
"src/llm.ts"() {
|
|
270
274
|
"use strict";
|
|
@@ -277,7 +281,8 @@ var init_llm = __esm({
|
|
|
277
281
|
refRegistry = /* @__PURE__ */ new Map();
|
|
278
282
|
isImageRefUrl = (url) => !!url && url.startsWith(IMAGE_REF_SCHEME);
|
|
279
283
|
bearsImage = (m) => messageHasImage(m.content) || toolImageRef(m.content) != null;
|
|
280
|
-
|
|
284
|
+
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;
|
|
285
|
+
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;
|
|
281
286
|
NEXT_STEP = /^next (?:step|run|up)\b/i;
|
|
282
287
|
SIGN_OFF = /^let me know\b/i;
|
|
283
288
|
}
|
|
@@ -902,6 +907,8 @@ function makeWebFetchTool(options = {}) {
|
|
|
902
907
|
return {
|
|
903
908
|
name: "WebFetch",
|
|
904
909
|
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.",
|
|
910
|
+
// Declared so a transport above the loop sizes its deadline around this one (see AgentTool.maxDurationMs).
|
|
911
|
+
maxDurationMs: timeoutMs,
|
|
905
912
|
parameters: { type: "object", required: ["url"], properties: { url: { type: "string", description: "absolute http(s) URL" } } },
|
|
906
913
|
async run({ url }) {
|
|
907
914
|
const doFetch = options.fetch ?? globalThis.fetch;
|
|
@@ -1354,11 +1361,12 @@ function toolRegistry() {
|
|
|
1354
1361
|
const all = [bashTool, readTool, editTool, grepTool, globTool, writeTool, multiEditTool, applyEditsTool, repoMapTool, reviewTool(), todoWriteTool, webFetchTool, webSearchTool, webSearchAnthropicTool];
|
|
1355
1362
|
return Object.fromEntries(all.map((t) => [t.name, t]));
|
|
1356
1363
|
}
|
|
1357
|
-
function toolsByName(names) {
|
|
1364
|
+
function toolsByName(names, available) {
|
|
1358
1365
|
const reg = toolRegistry();
|
|
1366
|
+
const extra = Object.fromEntries((available ?? []).map((t) => [t.name, t]));
|
|
1359
1367
|
return names.map((n) => {
|
|
1360
|
-
const t = reg[n];
|
|
1361
|
-
if (!t) throw new Error(`unknown tool '${n}'. Known: ${Object.keys(reg).join(", ")}`);
|
|
1368
|
+
const t = extra[n] ?? reg[n];
|
|
1369
|
+
if (!t) throw new Error(`unknown tool '${n}'. Known: ${[.../* @__PURE__ */ new Set([...Object.keys(extra), ...Object.keys(reg)])].join(", ")}`);
|
|
1362
1370
|
return t;
|
|
1363
1371
|
});
|
|
1364
1372
|
}
|
|
@@ -2105,6 +2113,7 @@ var init_shell_sandbox = __esm({
|
|
|
2105
2113
|
var tools_shell_exports = {};
|
|
2106
2114
|
__export(tools_shell_exports, {
|
|
2107
2115
|
ShellJobRegistry: () => ShellJobRegistry,
|
|
2116
|
+
formatJobExit: () => formatJobExit,
|
|
2108
2117
|
makeRealShellTool: () => makeRealShellTool,
|
|
2109
2118
|
makeShellJobTools: () => makeShellJobTools
|
|
2110
2119
|
});
|
|
@@ -2135,12 +2144,45 @@ async function nodeSpawn() {
|
|
|
2135
2144
|
if (!_spawn) _spawn = (await import("child_process")).spawn;
|
|
2136
2145
|
return _spawn;
|
|
2137
2146
|
}
|
|
2147
|
+
function formatJobExit(n) {
|
|
2148
|
+
return `[background job ${n.id} ${n.status}${n.exitCode != null ? ` exit ${n.exitCode}` : ""}] \`${n.command}\`
|
|
2149
|
+
` + (n.tail ? `${n.tail}
|
|
2150
|
+
` : "(no output)\n") + `Read the full output with ShellOutput({id:"${n.id}"}).`;
|
|
2151
|
+
}
|
|
2138
2152
|
function makeRealShellTool(options) {
|
|
2139
2153
|
const defaultTimeoutMs = options.timeoutMs ?? 12e4;
|
|
2140
2154
|
const maxTimeoutMs = Math.max(options.maxTimeoutMs ?? 6e5, defaultTimeoutMs);
|
|
2155
|
+
const backgroundDoc = () => {
|
|
2156
|
+
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. ";
|
|
2157
|
+
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.";
|
|
2158
|
+
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.";
|
|
2159
|
+
return `${base}${notify} ${survival}`;
|
|
2160
|
+
};
|
|
2141
2161
|
return {
|
|
2142
2162
|
name: "Shell",
|
|
2143
|
-
|
|
2163
|
+
// Rebind for an isolated child agent (git worktree): same policy/env/timeouts, new cwd. The
|
|
2164
|
+
// `registry` is deliberately dropped — it is bound to the PARENT's cwd (and, in hosts like
|
|
2165
|
+
// shraga-ee, to the parent's session), so a background job started from the child would run
|
|
2166
|
+
// outside the child's isolation and report into the parent. Background stays a parent capability
|
|
2167
|
+
// (`makeShellJobTools`' companions drop out of an isolated child entirely — see their `withCwd`).
|
|
2168
|
+
// Only `run` is rebuilt: everything else is carried over from THIS instance, because a host may
|
|
2169
|
+
// have mutated the tool AFTER construction (shraga-ee renames `Shell` -> `Bash` and its system
|
|
2170
|
+
// prompt says `Bash` everywhere). Rebuilding from `options` alone silently dropped that rename,
|
|
2171
|
+
// so a worktree child advertised `Shell` while being told to call `Bash` — an unknown-tool error
|
|
2172
|
+
// on its first call, and a hard failure for an agentType def whose allowlist names `Bash`.
|
|
2173
|
+
// `description` is the ONE thing taken from the rebound tool instead: it is derived from the
|
|
2174
|
+
// options and states whether `background:true` works — the rebind drops the registry, so carrying
|
|
2175
|
+
// the parent's text over would advertise a background capability the child does not have. (The
|
|
2176
|
+
// spread also collapses the getter below to a plain value, which is why this override is explicit.)
|
|
2177
|
+
withCwd(cwd) {
|
|
2178
|
+
const rebound = makeRealShellTool({ ...options, cwd, registry: void 0 });
|
|
2179
|
+
return { ...this, description: rebound.description, run: rebound.run, withCwd: rebound.withCwd };
|
|
2180
|
+
},
|
|
2181
|
+
get description() {
|
|
2182
|
+
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();
|
|
2183
|
+
},
|
|
2184
|
+
// Declared so transports above the agent loop can size their own deadline with headroom (see AgentTool.maxDurationMs).
|
|
2185
|
+
maxDurationMs: maxTimeoutMs,
|
|
2144
2186
|
parameters: {
|
|
2145
2187
|
type: "object",
|
|
2146
2188
|
required: ["command"],
|
|
@@ -2242,14 +2284,17 @@ function makeRealShellTool(options) {
|
|
|
2242
2284
|
};
|
|
2243
2285
|
}
|
|
2244
2286
|
function reasonFor(timedOut, timeoutMs, body) {
|
|
2245
|
-
const head = timedOut ? `[exit 124] timed out after ${timeoutMs}ms (killed)
|
|
2287
|
+
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)";
|
|
2246
2288
|
return body ? `${head}
|
|
2289
|
+
Partial output before the kill:
|
|
2247
2290
|
${body}` : head;
|
|
2248
2291
|
}
|
|
2249
2292
|
function makeShellJobTools(registry) {
|
|
2250
2293
|
const idParam = { type: "object", properties: { id: { type: "string", description: "the job id from Shell({background:true})" } } };
|
|
2294
|
+
const dropOnRebind = { withCwd: () => void 0 };
|
|
2251
2295
|
return [
|
|
2252
2296
|
{
|
|
2297
|
+
...dropOnRebind,
|
|
2253
2298
|
name: "ShellOutput",
|
|
2254
2299
|
description: "Read the accumulated output (tail) of a background Shell job by id.",
|
|
2255
2300
|
parameters: { type: "object", required: ["id"], properties: { id: { type: "string" } } },
|
|
@@ -2262,6 +2307,7 @@ ${clean(out) || "(no output yet)"}`;
|
|
|
2262
2307
|
}
|
|
2263
2308
|
},
|
|
2264
2309
|
{
|
|
2310
|
+
...dropOnRebind,
|
|
2265
2311
|
name: "ShellStatus",
|
|
2266
2312
|
description: "Status of a background Shell job (running/exited/killed + exit code). Omit `id` to list all jobs.",
|
|
2267
2313
|
parameters: idParam,
|
|
@@ -2275,6 +2321,7 @@ ${clean(out) || "(no output yet)"}`;
|
|
|
2275
2321
|
}
|
|
2276
2322
|
},
|
|
2277
2323
|
{
|
|
2324
|
+
...dropOnRebind,
|
|
2278
2325
|
name: "ShellKill",
|
|
2279
2326
|
description: "Stop a running background Shell job by id (SIGTERM).",
|
|
2280
2327
|
parameters: { type: "object", required: ["id"], properties: { id: { type: "string" } } },
|
|
@@ -2324,12 +2371,14 @@ var init_tools_shell = __esm({
|
|
|
2324
2371
|
job.status = "error";
|
|
2325
2372
|
append(`
|
|
2326
2373
|
[error] ${err2?.message ?? err2}`);
|
|
2374
|
+
this.notifyExit(id, job);
|
|
2327
2375
|
}
|
|
2328
2376
|
});
|
|
2329
2377
|
proc.on("close", (code) => {
|
|
2330
2378
|
if (job.status === "running") {
|
|
2331
2379
|
job.status = "exited";
|
|
2332
2380
|
job.exitCode = code ?? void 0;
|
|
2381
|
+
this.notifyExit(id, job);
|
|
2333
2382
|
}
|
|
2334
2383
|
});
|
|
2335
2384
|
} catch (e) {
|
|
@@ -2339,6 +2388,29 @@ var init_tools_shell = __esm({
|
|
|
2339
2388
|
this.jobs.set(id, job);
|
|
2340
2389
|
return id;
|
|
2341
2390
|
}
|
|
2391
|
+
/** Fire `onExit` at most once per job, with the tail so the model can act without a second round-trip. */
|
|
2392
|
+
notified = /* @__PURE__ */ new Set();
|
|
2393
|
+
notifyExit(id, job) {
|
|
2394
|
+
if (this.notified.has(id) || !this.cfg.onExit) return;
|
|
2395
|
+
this.notified.add(id);
|
|
2396
|
+
try {
|
|
2397
|
+
this.cfg.onExit({ id, command: job.command, status: job.status, exitCode: job.exitCode, tail: clean(job.buf).slice(-4e3) });
|
|
2398
|
+
} catch {
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
/**
|
|
2402
|
+
* Wire (or rewire) the completion callback AFTER construction. A host that only gets its agent handle
|
|
2403
|
+
* once the Agent is constructed (the library's own `fullAgentOptions` preset, any embedder) could not
|
|
2404
|
+
* pass `onExit` up front, so background completions were silently REPL-only. Set it here instead.
|
|
2405
|
+
*/
|
|
2406
|
+
setOnExit(fn) {
|
|
2407
|
+
this.cfg.onExit = fn;
|
|
2408
|
+
}
|
|
2409
|
+
/** Whether a finished job will actually be reported to the model. The Shell tool's description reads
|
|
2410
|
+
* this so it can't promise a completion notice on a host that discards it. */
|
|
2411
|
+
get notifiesOnExit() {
|
|
2412
|
+
return !!this.cfg.onExit;
|
|
2413
|
+
}
|
|
2342
2414
|
/** Current tail output for a job (null = no such job). */
|
|
2343
2415
|
output(id) {
|
|
2344
2416
|
return this.jobs.get(id)?.buf ?? (this.jobs.has(id) ? "" : null);
|
|
@@ -3082,6 +3154,32 @@ ${sections.join("\n\n---\n\n")}`;
|
|
|
3082
3154
|
init_tools();
|
|
3083
3155
|
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
3084
3156
|
import { join as join4 } from "path";
|
|
3157
|
+
|
|
3158
|
+
// src/models.ts
|
|
3159
|
+
var MODEL_ALIASES = {
|
|
3160
|
+
fable: "claude-fable-5",
|
|
3161
|
+
"fable-5": "claude-fable-5",
|
|
3162
|
+
opus: "claude-opus-4-8",
|
|
3163
|
+
"opus-4-8": "claude-opus-4-8",
|
|
3164
|
+
"opus-4-7": "claude-opus-4-7",
|
|
3165
|
+
"opus-4-6": "claude-opus-4-6",
|
|
3166
|
+
sonnet: "claude-sonnet-4-6",
|
|
3167
|
+
haiku: "claude-haiku-4-5-20251001"
|
|
3168
|
+
};
|
|
3169
|
+
function resolveModelAlias(input) {
|
|
3170
|
+
const slash = input.indexOf("/");
|
|
3171
|
+
const prefix = slash === -1 ? "" : input.slice(0, slash + 1);
|
|
3172
|
+
const rest = slash === -1 ? input : input.slice(slash + 1);
|
|
3173
|
+
return prefix + (MODEL_ALIASES[rest.toLowerCase()] ?? rest);
|
|
3174
|
+
}
|
|
3175
|
+
function modelProvider(model) {
|
|
3176
|
+
const i = model.indexOf("/");
|
|
3177
|
+
if (i !== -1) return model.slice(0, i);
|
|
3178
|
+
if (/^claude-/i.test(model)) return "anthropic";
|
|
3179
|
+
return `?${model}`;
|
|
3180
|
+
}
|
|
3181
|
+
|
|
3182
|
+
// src/subagent.ts
|
|
3085
3183
|
init_OverlayFilesystem();
|
|
3086
3184
|
init_worktree();
|
|
3087
3185
|
init_NodeDiskFilesystem();
|
|
@@ -3099,6 +3197,30 @@ async function boundedPool(items, limit, fn) {
|
|
|
3099
3197
|
await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, worker));
|
|
3100
3198
|
return out;
|
|
3101
3199
|
}
|
|
3200
|
+
var SUBAGENT_MAX_DURATION_MS = 18e5;
|
|
3201
|
+
function wallClock(parent, ms) {
|
|
3202
|
+
const ac = new AbortController();
|
|
3203
|
+
let fired = false;
|
|
3204
|
+
const onParent = () => ac.abort();
|
|
3205
|
+
const timer = setTimeout(() => {
|
|
3206
|
+
fired = true;
|
|
3207
|
+
ac.abort();
|
|
3208
|
+
}, ms);
|
|
3209
|
+
if (parent) {
|
|
3210
|
+
if (parent.aborted) ac.abort();
|
|
3211
|
+
else parent.addEventListener("abort", onParent, { once: true });
|
|
3212
|
+
}
|
|
3213
|
+
return {
|
|
3214
|
+
signal: ac.signal,
|
|
3215
|
+
timedOut: () => fired,
|
|
3216
|
+
dispose: () => {
|
|
3217
|
+
clearTimeout(timer);
|
|
3218
|
+
parent?.removeEventListener("abort", onParent);
|
|
3219
|
+
}
|
|
3220
|
+
};
|
|
3221
|
+
}
|
|
3222
|
+
var deadlineNote = (ms) => `
|
|
3223
|
+
[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.]`;
|
|
3102
3224
|
function sanitizeSlug(raw) {
|
|
3103
3225
|
return raw.replace(/[^a-zA-Z0-9._-]/g, "-").replace(/-+/g, "-").slice(0, 40);
|
|
3104
3226
|
}
|
|
@@ -3116,6 +3238,14 @@ function createWorktreeFs(cwd, label, index) {
|
|
|
3116
3238
|
function releaseWorktree(handle) {
|
|
3117
3239
|
return cleanupWorktree(handle.repoRoot, handle.slug, { force: true });
|
|
3118
3240
|
}
|
|
3241
|
+
function rebindTools(tools, root) {
|
|
3242
|
+
if (!tools || !root) return tools;
|
|
3243
|
+
return tools.flatMap((t) => {
|
|
3244
|
+
if (!t.withCwd) return [t];
|
|
3245
|
+
const rebound = t.withCwd(root);
|
|
3246
|
+
return rebound ? [rebound] : [];
|
|
3247
|
+
});
|
|
3248
|
+
}
|
|
3119
3249
|
function worktreePromptPrefix(branch) {
|
|
3120
3250
|
return `You are running in an isolated git worktree on branch "${branch}". When you finish making changes, commit them with a clear message before replying with your summary. Your worktree will be removed after you finish \u2014 only committed work survives (on the branch).
|
|
3121
3251
|
|
|
@@ -3161,33 +3291,52 @@ function abortHint(label, res, file) {
|
|
|
3161
3291
|
}
|
|
3162
3292
|
async function runChildTraced(args) {
|
|
3163
3293
|
const { opts, label, agentType, prompt } = args;
|
|
3164
|
-
|
|
3294
|
+
const deadlineMs = opts.maxDurationMs ?? SUBAGENT_MAX_DURATION_MS;
|
|
3295
|
+
const wc = wallClock(args.signal, deadlineMs);
|
|
3296
|
+
args.childOpts.signal = wc.signal;
|
|
3165
3297
|
const file = opts.traceDir ? traceFileFor(opts.traceDir, label, args.childDepth) : void 0;
|
|
3166
3298
|
const meta = { label, agentType, prompt };
|
|
3167
3299
|
const child = new Agent(args.childOpts);
|
|
3168
3300
|
if (file) installTrace(child, file, meta);
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3301
|
+
let res;
|
|
3302
|
+
try {
|
|
3303
|
+
res = await child.run(prompt);
|
|
3304
|
+
} finally {
|
|
3305
|
+
wc.dispose();
|
|
3306
|
+
}
|
|
3307
|
+
if (file) flushTrace(file, meta, child.transcript, wc.timedOut() ? "deadline" : res.finishReason);
|
|
3308
|
+
let result = res.finishReason === "stop" ? res.text || `(child '${label}' finished with no summary; finishReason=${res.finishReason})` : abortHint(label, res, file);
|
|
3309
|
+
if (wc.timedOut()) {
|
|
3310
|
+
log5.warn(`sub-task '${label}' hit its ${deadlineMs}ms wall-clock deadline (steps=${res.steps})`);
|
|
3311
|
+
result += deadlineNote(deadlineMs);
|
|
3312
|
+
}
|
|
3172
3313
|
await opts.hooks?.onSubagentStop?.(result, { label, agentType });
|
|
3173
3314
|
return { res, result };
|
|
3174
3315
|
}
|
|
3175
|
-
function childOptionsFor(opts, fs, depth, maxDepth, agentType) {
|
|
3316
|
+
function childOptionsFor(opts, fs, depth, maxDepth, agentType, model, root) {
|
|
3176
3317
|
let def;
|
|
3177
3318
|
if (agentType) {
|
|
3178
3319
|
def = (opts.agents ?? []).find((a) => a.name === agentType);
|
|
3179
3320
|
if (!def) return `no subagent type '${agentType}'. Available: ${(opts.agents ?? []).map((a) => a.name).join(", ") || "(none defined)"}`;
|
|
3180
3321
|
}
|
|
3181
|
-
const
|
|
3322
|
+
const childModel = resolveModelAlias(model || def?.model || opts.model);
|
|
3323
|
+
const childOpts = { ai: opts.ai, fs, model: childModel, subagents: true, depth: depth + 1, maxDepth, traceDir: opts.traceDir };
|
|
3182
3324
|
if (opts.maxSteps != null) childOpts.maxSteps = opts.maxSteps;
|
|
3183
|
-
|
|
3325
|
+
const childPrompt = def?.systemPrompt ?? opts.systemPrompt;
|
|
3326
|
+
if (childPrompt) childOpts.systemPrompt = childPrompt;
|
|
3327
|
+
const inherited = rebindTools(opts.tools, root);
|
|
3184
3328
|
if (def?.tools?.length) {
|
|
3185
3329
|
try {
|
|
3186
|
-
childOpts.tools = toolsByName(def.tools);
|
|
3330
|
+
childOpts.tools = toolsByName(def.tools, inherited);
|
|
3187
3331
|
} catch (e) {
|
|
3188
3332
|
return `subagent '${agentType}' declares an unknown tool \u2014 ${e.message}`;
|
|
3189
3333
|
}
|
|
3334
|
+
} else if (inherited?.length) {
|
|
3335
|
+
childOpts.tools = inherited;
|
|
3190
3336
|
}
|
|
3337
|
+
const po = opts.providerOptionsFor?.(childModel);
|
|
3338
|
+
if (po) childOpts.providerOptions = po;
|
|
3339
|
+
if (opts.hooks?.preToolUse) childOpts.hooks = { preToolUse: opts.hooks.preToolUse.bind(opts.hooks) };
|
|
3191
3340
|
return childOpts;
|
|
3192
3341
|
}
|
|
3193
3342
|
function makeTaskTool(opts) {
|
|
@@ -3195,7 +3344,7 @@ function makeTaskTool(opts) {
|
|
|
3195
3344
|
const maxDepth = opts.maxDepth ?? 2;
|
|
3196
3345
|
return {
|
|
3197
3346
|
name: "Task",
|
|
3198
|
-
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
|
|
3347
|
+
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 VFS file edits are overlay-isolated and commit when it finishes \u2014 but a child that uses the real `Shell` writes straight to the working tree (an overlay cannot isolate a real process), so pair shell-heavy work with `isolation: "worktree"`. 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 its shell is re-bound to the worktree \u2014 keeping any name your host gave it \u2014 and background shell jobs, including inspecting or killing MINE, are unavailable there) \u2014 slower (~200ms setup), auto-cleaned if no changes. The child inherits YOUR tools and system prompt by default (and is subject to the same tool-use gate); pass `model` to run this one sub-task on a different model (e.g. a cheap one for bulk search, a stronger one for hard reasoning) \u2014 it overrides the agentType default.',
|
|
3199
3348
|
parameters: {
|
|
3200
3349
|
type: "object",
|
|
3201
3350
|
required: ["description", "prompt"],
|
|
@@ -3203,11 +3352,18 @@ function makeTaskTool(opts) {
|
|
|
3203
3352
|
description: { type: "string", description: "a short (3-5 word) label for the sub-task" },
|
|
3204
3353
|
prompt: { type: "string", description: "the full instructions the child agent should carry out" },
|
|
3205
3354
|
agentType: { type: "string", description: "optional named subagent type (its persona, model, and scoped tools) \u2014 see the catalog" },
|
|
3355
|
+
model: { type: "string", description: 'optional model for THIS sub-task (alias like "haiku"/"opus"/"sonnet", or a full id). Overrides the agentType default; otherwise the child runs on your model.' },
|
|
3206
3356
|
background: { type: "boolean", description: "run detached (overlay-isolated, commits on finish); returns a job id to poll with JobOutput instead of blocking" },
|
|
3207
3357
|
isolation: { type: "string", enum: ["overlay", "worktree"], description: 'isolation mode: "overlay" (default, VFS layer) or "worktree" (real git worktree for shell-heavy work)' }
|
|
3208
3358
|
}
|
|
3209
3359
|
},
|
|
3210
|
-
|
|
3360
|
+
// Declared because it is ENFORCED (see runChildTraced) — a transport above the loop sizes its own
|
|
3361
|
+
// deadline around this one (AgentTool.maxDurationMs). Declaring an unenforced number would be a lie;
|
|
3362
|
+
// declaring nothing left `Task` invisible to toolTimeoutViolations, which is the same hole.
|
|
3363
|
+
maxDurationMs: opts.maxDurationMs ?? SUBAGENT_MAX_DURATION_MS,
|
|
3364
|
+
maxDurationEnforced: true,
|
|
3365
|
+
// the wall clock below ABORTS the child at this bound — it cannot be overrun, so the transport needs slack, not a doubling
|
|
3366
|
+
async run({ description, prompt, agentType, model, background, isolation }, ctx) {
|
|
3211
3367
|
if (depth >= maxDepth) {
|
|
3212
3368
|
return `Error: Task depth limit reached (maxDepth ${maxDepth}). Cannot spawn another child agent \u2014 do this work directly instead.`;
|
|
3213
3369
|
}
|
|
@@ -3230,7 +3386,7 @@ function makeTaskTool(opts) {
|
|
|
3230
3386
|
fs2 = overlay;
|
|
3231
3387
|
commit = () => overlay.commit();
|
|
3232
3388
|
}
|
|
3233
|
-
const childOpts2 = childOptionsFor(opts, fs2, depth, maxDepth, agentType);
|
|
3389
|
+
const childOpts2 = childOptionsFor(opts, fs2, depth, maxDepth, agentType, model, wtHandle2?.worktreePath);
|
|
3234
3390
|
if (typeof childOpts2 === "string") throw new Error(childOpts2);
|
|
3235
3391
|
const childPrompt = wtHandle2 ? worktreePromptPrefix(wtHandle2.branch) + String(prompt ?? "") : String(prompt ?? "");
|
|
3236
3392
|
const { res, result } = await runChildTraced({ opts, childOpts: childOpts2, prompt: childPrompt, label, agentType, childDepth: depth + 1, signal });
|
|
@@ -3260,7 +3416,7 @@ function makeTaskTool(opts) {
|
|
|
3260
3416
|
} else {
|
|
3261
3417
|
fs = opts.fs;
|
|
3262
3418
|
}
|
|
3263
|
-
const childOpts = childOptionsFor(opts, fs, depth, maxDepth, agentType);
|
|
3419
|
+
const childOpts = childOptionsFor(opts, fs, depth, maxDepth, agentType, model, wtHandle?.worktreePath);
|
|
3264
3420
|
if (typeof childOpts === "string") {
|
|
3265
3421
|
wtHandle && releaseWorktree(wtHandle);
|
|
3266
3422
|
return `Error: ${childOpts}`;
|
|
@@ -3289,7 +3445,7 @@ function makeTaskBatchTool(opts) {
|
|
|
3289
3445
|
const maxParallel = opts.maxParallel ?? 4;
|
|
3290
3446
|
return {
|
|
3291
3447
|
name: "TaskBatch",
|
|
3292
|
-
description: 'Delegate SEVERAL independent sub-tasks to child agents that run concurrently; returns all their summaries. Each child is write-isolated (its file edits are merged back in array order). Use for parallel fan-out (review/search/scoped refactors across files). Provide `tasks: [{ description, prompt, agentType? }]
|
|
3448
|
+
description: 'Delegate SEVERAL independent sub-tasks to child agents that run concurrently; returns all their summaries. Each child is write-isolated at the VFS level (its file edits are merged back in array order); real `Shell` commands are NOT \u2014 use `isolation: "worktree"` for those. Use for parallel fan-out (review/search/scoped refactors across files). Provide `tasks: [{ description, prompt, agentType?, model? }]` \u2014 each child inherits your tools and system prompt, and `model` runs that one child on a different model. Set `isolation: "worktree"` to run each child in its own git worktree (needed for real shell commands) \u2014 slower, auto-cleaned if no changes.',
|
|
3293
3449
|
parameters: {
|
|
3294
3450
|
type: "object",
|
|
3295
3451
|
required: ["tasks"],
|
|
@@ -3303,18 +3459,26 @@ function makeTaskBatchTool(opts) {
|
|
|
3303
3459
|
properties: {
|
|
3304
3460
|
description: { type: "string", description: "a short (3-5 word) label" },
|
|
3305
3461
|
prompt: { type: "string", description: "the full instructions for this child" },
|
|
3306
|
-
agentType: { type: "string", description: "optional named subagent type" }
|
|
3462
|
+
agentType: { type: "string", description: "optional named subagent type" },
|
|
3463
|
+
model: { type: "string", description: "optional model for this child (alias or full id); overrides the agentType default" }
|
|
3307
3464
|
}
|
|
3308
3465
|
}
|
|
3309
3466
|
},
|
|
3310
3467
|
isolation: { type: "string", enum: ["overlay", "worktree"], description: 'isolation mode for ALL children: "overlay" (default) or "worktree" (real git worktree each)' }
|
|
3311
3468
|
}
|
|
3312
3469
|
},
|
|
3470
|
+
// Same enforced bound as `Task`, applied to the WHOLE batch (below), not per child — otherwise the
|
|
3471
|
+
// honest ceiling would be ceil(n/maxParallel) x the per-child deadline, i.e. unbounded in `tasks`.
|
|
3472
|
+
maxDurationMs: opts.maxDurationMs ?? SUBAGENT_MAX_DURATION_MS,
|
|
3473
|
+
maxDurationEnforced: true,
|
|
3474
|
+
// the wall clock below ABORTS the child at this bound — it cannot be overrun, so the transport needs slack, not a doubling
|
|
3313
3475
|
async run({ tasks, isolation }, ctx) {
|
|
3314
3476
|
if (depth >= maxDepth) return `Error: Task depth limit reached (maxDepth ${maxDepth}). Cannot spawn child agents \u2014 do this work directly instead.`;
|
|
3315
3477
|
const list = Array.isArray(tasks) ? tasks : [];
|
|
3316
3478
|
if (!list.length) return "Error: TaskBatch needs a non-empty `tasks` array.";
|
|
3317
3479
|
const useWorktree = isolation === "worktree";
|
|
3480
|
+
const batchDeadlineMs = opts.maxDurationMs ?? SUBAGENT_MAX_DURATION_MS;
|
|
3481
|
+
const batchClock = wallClock(ctx.signal, batchDeadlineMs);
|
|
3318
3482
|
const results = await boundedPool(list, maxParallel, async (t, i) => {
|
|
3319
3483
|
const label = String(t?.description ?? t?.agentType ?? `task ${i + 1}`);
|
|
3320
3484
|
let fs;
|
|
@@ -3329,16 +3493,16 @@ function makeTaskBatchTool(opts) {
|
|
|
3329
3493
|
overlay = new OverlayFilesystem(opts.fs);
|
|
3330
3494
|
fs = overlay;
|
|
3331
3495
|
}
|
|
3332
|
-
const childOpts = childOptionsFor(opts, fs, depth, maxDepth, t?.agentType);
|
|
3496
|
+
const childOpts = childOptionsFor(opts, fs, depth, maxDepth, t?.agentType, t?.model, wtHandle?.worktreePath);
|
|
3333
3497
|
if (typeof childOpts === "string") return { label, error: childOpts, ok: false };
|
|
3334
3498
|
try {
|
|
3335
3499
|
const childPrompt = wtHandle ? worktreePromptPrefix(wtHandle.branch) + String(t?.prompt ?? "") : String(t?.prompt ?? "");
|
|
3336
|
-
const { res, result } = await runChildTraced({ opts, childOpts, prompt: childPrompt, label, agentType: t?.agentType, childDepth: depth + 1, signal:
|
|
3500
|
+
const { res, result } = await runChildTraced({ opts, childOpts, prompt: childPrompt, label, agentType: t?.agentType, childDepth: depth + 1, signal: batchClock.signal });
|
|
3337
3501
|
return { label, text: result, overlay, wtHandle, ok: res.finishReason !== "error" && res.finishReason !== "aborted" };
|
|
3338
3502
|
} catch (e) {
|
|
3339
3503
|
return { label, error: e instanceof Error ? e.message : String(e), wtHandle, ok: false };
|
|
3340
3504
|
}
|
|
3341
|
-
});
|
|
3505
|
+
}).finally(() => batchClock.dispose());
|
|
3342
3506
|
const lines = [];
|
|
3343
3507
|
for (const r of results) {
|
|
3344
3508
|
if (r.ok && r.overlay) await r.overlay.commit().catch(() => {
|
|
@@ -3352,7 +3516,7 @@ function makeTaskBatchTool(opts) {
|
|
|
3352
3516
|
lines.push(`### ${lines.length + 1}. ${r.label}
|
|
3353
3517
|
${r.error ? `ERROR: ${r.error}` : r.text || "(no summary)"}${extra}`);
|
|
3354
3518
|
}
|
|
3355
|
-
return lines.join("\n\n");
|
|
3519
|
+
return lines.join("\n\n") + (batchClock.timedOut() ? deadlineNote(batchDeadlineMs) : "");
|
|
3356
3520
|
}
|
|
3357
3521
|
};
|
|
3358
3522
|
}
|
|
@@ -3756,6 +3920,10 @@ var AgentOptions = class {
|
|
|
3756
3920
|
autoTest;
|
|
3757
3921
|
/** Provider-specific options forwarded to ai.chat() (e.g. cursor mcpServers, cwd). */
|
|
3758
3922
|
providerOptions;
|
|
3923
|
+
/** Derive `providerOptions` for a DELEGATED child whose model may differ from this agent's
|
|
3924
|
+
* (`Task({ model })` / an agentType def). Default: inherit `providerOptions` only when the child's
|
|
3925
|
+
* model is on the SAME provider — cursor's `cwd`/`cursorSession` on an anthropic child is a 400. */
|
|
3926
|
+
providerOptionsFor;
|
|
3759
3927
|
/** Prompt caching (providers that support it, e.g. Anthropic): cache tools/system/conversation
|
|
3760
3928
|
* prefix across the loop's steps — reads cost 0.1x, writes 1.25x. A multi-step agent loop
|
|
3761
3929
|
* re-sends its whole prefix every step, so this is a large net cost cut. Default on. */
|
|
@@ -3979,7 +4147,19 @@ var Agent = class _Agent {
|
|
|
3979
4147
|
agents = loaded.agents;
|
|
3980
4148
|
if (loaded.catalog) systemPrompt += "\n\n" + loaded.catalog;
|
|
3981
4149
|
}
|
|
3982
|
-
const taskOpts = {
|
|
4150
|
+
const taskOpts = {
|
|
4151
|
+
ai: o.ai,
|
|
4152
|
+
model: o.model,
|
|
4153
|
+
fs,
|
|
4154
|
+
depth: o.depth,
|
|
4155
|
+
maxDepth: o.maxDepth,
|
|
4156
|
+
agents,
|
|
4157
|
+
hooks: o.hooks,
|
|
4158
|
+
traceDir: o.traceDir,
|
|
4159
|
+
tools: [...o.tools, ...this.injectedTools],
|
|
4160
|
+
systemPrompt: o.systemPrompt,
|
|
4161
|
+
providerOptionsFor: o.providerOptionsFor ?? ((m) => modelProvider(m) === modelProvider(o.model) ? o.providerOptions : void 0)
|
|
4162
|
+
};
|
|
3983
4163
|
tools = [...tools, makeTaskTool(taskOpts), makeTaskBatchTool(taskOpts)];
|
|
3984
4164
|
}
|
|
3985
4165
|
if (o.checkpoints) tools = [...tools, ...checkpointTools()];
|
|
@@ -4116,7 +4296,17 @@ var Agent = class _Agent {
|
|
|
4116
4296
|
const frag = reasoningToChatFragment(o.model, o.reasoning);
|
|
4117
4297
|
const sentTools = o.model.startsWith("claude-code/") ? [] : wireTools;
|
|
4118
4298
|
const isCursorWithTools = o.model.startsWith("cursor/") && wireTools.length > 0;
|
|
4299
|
+
const hostToolTimeoutMs = o.providerOptions?.toolTimeoutMs;
|
|
4300
|
+
const derivedToolTimeoutMs = Math.max(TRANSPORT_TOOL_TIMEOUT_FLOOR_MS, ...this.activeTools.map(transportHeadroomMs));
|
|
4301
|
+
const effectiveToolTimeoutMs = typeof hostToolTimeoutMs === "number" ? Math.max(hostToolTimeoutMs, derivedToolTimeoutMs) : derivedToolTimeoutMs;
|
|
4302
|
+
if (isCursorWithTools) {
|
|
4303
|
+
if (typeof hostToolTimeoutMs === "number" && effectiveToolTimeoutMs !== hostToolTimeoutMs) {
|
|
4304
|
+
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.`);
|
|
4305
|
+
}
|
|
4306
|
+
for (const v of toolTimeoutViolations(this.activeTools, effectiveToolTimeoutMs)) log6.warn(v);
|
|
4307
|
+
}
|
|
4119
4308
|
const cursorPo = isCursorWithTools ? {
|
|
4309
|
+
toolTimeoutMs: effectiveToolTimeoutMs,
|
|
4120
4310
|
toolExecutor: async (name, args) => {
|
|
4121
4311
|
const tc = { id: `cursor-${Date.now()}`, type: "function", function: { name, arguments: JSON.stringify(args) } };
|
|
4122
4312
|
return await this.dispatch(tc);
|
|
@@ -4218,14 +4408,12 @@ var Agent = class _Agent {
|
|
|
4218
4408
|
})() } }))
|
|
4219
4409
|
});
|
|
4220
4410
|
for (const a of delegatedTools) {
|
|
4221
|
-
const
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
})();
|
|
4228
|
-
this.transcript.push({ role: "tool", tool_call_id: a.id, content: cap > 0 && out.length > cap ? cropResult(out, cap) : out });
|
|
4411
|
+
const name = a.name || "tool";
|
|
4412
|
+
const r = delegatedResult(a.output);
|
|
4413
|
+
const text = r.images.length ? r.text : nonEmptyResult(isVacuous(a.output) ? "" : r.text, name, deliveryOf(a.output, a.status));
|
|
4414
|
+
const body = cap > 0 && text.length > cap ? cropResult(text, cap) : text;
|
|
4415
|
+
const content = r.images.length ? [{ type: "text", text: body }, ...r.images.map((i) => imagePart(`data:${i.mimeType};base64,${i.data}`))] : body;
|
|
4416
|
+
this.transcript.push({ role: "tool", tool_call_id: a.id, content });
|
|
4229
4417
|
}
|
|
4230
4418
|
}
|
|
4231
4419
|
const toolCalls = res.toolCalls ?? [];
|
|
@@ -4278,9 +4466,10 @@ var Agent = class _Agent {
|
|
|
4278
4466
|
const raw = await this.dispatch(tc);
|
|
4279
4467
|
let content;
|
|
4280
4468
|
if (typeof raw === "string") {
|
|
4281
|
-
content = raw;
|
|
4469
|
+
content = nonEmptyResult(raw, tc.function.name);
|
|
4282
4470
|
} else {
|
|
4283
|
-
const
|
|
4471
|
+
const hasImages = !!raw.images?.length;
|
|
4472
|
+
const parts = [{ type: "text", text: hasImages ? raw.text : nonEmptyResult(raw.text ?? "", tc.function.name) }];
|
|
4284
4473
|
for (const img of raw.images ?? []) parts.push(imagePart(`data:${img.mimeType};base64,${img.data}`));
|
|
4285
4474
|
content = parts;
|
|
4286
4475
|
}
|
|
@@ -4589,6 +4778,76 @@ var isAnthropicModel = (m) => !!m && (m.startsWith("anthropic/") || /(^|\/)claud
|
|
|
4589
4778
|
function estimateTokens(m, keepRecentImages = 1) {
|
|
4590
4779
|
return Math.ceil(sendBytes(m, keepRecentImages) / 4);
|
|
4591
4780
|
}
|
|
4781
|
+
var TRANSPORT_TOOL_TIMEOUT_FLOOR_MS = 18e5;
|
|
4782
|
+
function transportHeadroomMs(t) {
|
|
4783
|
+
const d = Number(t.maxDurationMs) || 0;
|
|
4784
|
+
if (!d) return 0;
|
|
4785
|
+
return t.maxDurationEnforced ? d + 6e4 : d * 2 + 6e4;
|
|
4786
|
+
}
|
|
4787
|
+
function toolTimeoutViolations(tools, transportMs) {
|
|
4788
|
+
const out = [];
|
|
4789
|
+
for (const t of tools) {
|
|
4790
|
+
const d = Number(t.maxDurationMs) || 0;
|
|
4791
|
+
if (d && d >= transportMs) {
|
|
4792
|
+
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.`);
|
|
4793
|
+
}
|
|
4794
|
+
}
|
|
4795
|
+
return out;
|
|
4796
|
+
}
|
|
4797
|
+
function nonEmptyResult(out, name = "tool", delivery = "ran") {
|
|
4798
|
+
if (out.trim()) return out;
|
|
4799
|
+
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.`;
|
|
4800
|
+
if (delivery === "missing") {
|
|
4801
|
+
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.`;
|
|
4802
|
+
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.`;
|
|
4803
|
+
}
|
|
4804
|
+
return `[${name}] ran and returned no output. The call completed \u2014 this is an empty answer, not a failure.`;
|
|
4805
|
+
}
|
|
4806
|
+
function isVacuous(v, seen = /* @__PURE__ */ new Set(), depth = 0) {
|
|
4807
|
+
if (v == null) return true;
|
|
4808
|
+
if (typeof v === "string") return v.trim() === "";
|
|
4809
|
+
if (typeof v !== "object") return false;
|
|
4810
|
+
if (seen.has(v) || depth >= 64) return false;
|
|
4811
|
+
seen.add(v);
|
|
4812
|
+
try {
|
|
4813
|
+
if (Array.isArray(v)) return v.every((x) => isVacuous(x, seen, depth + 1));
|
|
4814
|
+
const proto = Object.getPrototypeOf(v);
|
|
4815
|
+
if (proto !== Object.prototype && proto !== null) return false;
|
|
4816
|
+
const vals = Object.values(v);
|
|
4817
|
+
return vals.length === 0 || vals.every((x) => isVacuous(x, seen, depth + 1));
|
|
4818
|
+
} finally {
|
|
4819
|
+
seen.delete(v);
|
|
4820
|
+
}
|
|
4821
|
+
}
|
|
4822
|
+
function delegatedResult(output) {
|
|
4823
|
+
if (typeof output === "string") return { text: output, images: [] };
|
|
4824
|
+
if (output == null) return { text: "", images: [] };
|
|
4825
|
+
const isImg = (i) => i && typeof i.data === "string" && typeof i.mimeType === "string";
|
|
4826
|
+
const o = output;
|
|
4827
|
+
if (Array.isArray(o.images)) {
|
|
4828
|
+
const images = o.images.filter(isImg);
|
|
4829
|
+
if (images.length) return { text: typeof o.text === "string" ? o.text : "", images };
|
|
4830
|
+
}
|
|
4831
|
+
const blocks = Array.isArray(output) ? output : Array.isArray(o.content) ? o.content : void 0;
|
|
4832
|
+
if (blocks) {
|
|
4833
|
+
const images = blocks.filter((b) => b?.type === "image" && isImg(b)).map((b) => ({ mimeType: b.mimeType, data: b.data }));
|
|
4834
|
+
if (images.length) {
|
|
4835
|
+
const text = blocks.filter((b) => b?.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n");
|
|
4836
|
+
return { text, images };
|
|
4837
|
+
}
|
|
4838
|
+
}
|
|
4839
|
+
try {
|
|
4840
|
+
return { text: JSON.stringify(output), images: [] };
|
|
4841
|
+
} catch {
|
|
4842
|
+
return { text: String(output), images: [] };
|
|
4843
|
+
}
|
|
4844
|
+
}
|
|
4845
|
+
function deliveryOf(output, status) {
|
|
4846
|
+
if (status === "error") return "error";
|
|
4847
|
+
if (output == null) return "missing";
|
|
4848
|
+
if (status != null && status !== "completed") return "missing";
|
|
4849
|
+
return "ran";
|
|
4850
|
+
}
|
|
4592
4851
|
function cropResult(result, cap) {
|
|
4593
4852
|
const head = result.slice(0, cap);
|
|
4594
4853
|
const nl = head.lastIndexOf("\n");
|
|
@@ -8334,13 +8593,29 @@ function toResult(result) {
|
|
|
8334
8593
|
}
|
|
8335
8594
|
return { text: JSON.stringify(result) };
|
|
8336
8595
|
}
|
|
8337
|
-
|
|
8596
|
+
var MCP_CALL_TIMEOUT_MS = 3e5;
|
|
8597
|
+
var TIMED_OUT = /* @__PURE__ */ Symbol("mcp-timeout");
|
|
8598
|
+
async function withDeadline(p, ms) {
|
|
8599
|
+
let t;
|
|
8600
|
+
try {
|
|
8601
|
+
return await Promise.race([p, new Promise((res) => {
|
|
8602
|
+
t = setTimeout(() => res(TIMED_OUT), ms);
|
|
8603
|
+
})]);
|
|
8604
|
+
} finally {
|
|
8605
|
+
clearTimeout(t);
|
|
8606
|
+
}
|
|
8607
|
+
}
|
|
8608
|
+
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.`;
|
|
8609
|
+
function mcpToolToAgentTool(spec, callTool, prefix = "mcp__", timeoutMs = MCP_CALL_TIMEOUT_MS) {
|
|
8338
8610
|
return {
|
|
8339
8611
|
name: `${prefix}${spec.name}`,
|
|
8340
8612
|
description: spec.description ?? `MCP tool ${spec.name}`,
|
|
8341
8613
|
parameters: spec.inputSchema ?? { type: "object", properties: {} },
|
|
8614
|
+
maxDurationMs: timeoutMs,
|
|
8342
8615
|
async run(args, _ctx) {
|
|
8343
|
-
const
|
|
8616
|
+
const raw = await withDeadline(callTool(spec.name, args ?? {}), timeoutMs);
|
|
8617
|
+
if (raw === TIMED_OUT) return mcpTimedOut(spec.name, timeoutMs);
|
|
8618
|
+
const r = toResult(raw);
|
|
8344
8619
|
return r.images?.length ? r : r.text;
|
|
8345
8620
|
}
|
|
8346
8621
|
};
|
|
@@ -8355,6 +8630,7 @@ function describeSpec(s) {
|
|
|
8355
8630
|
}
|
|
8356
8631
|
function makeMcpToolSearch(specs, callTool, options = {}) {
|
|
8357
8632
|
const maxResults = options.maxResults ?? 10;
|
|
8633
|
+
const callTimeoutMs = options.timeoutMs ?? MCP_CALL_TIMEOUT_MS;
|
|
8358
8634
|
const byName = new Map(specs.map((s) => [s.name, s]));
|
|
8359
8635
|
const catalogLine = `${specs.length} MCP tool(s) available \u2014 search by keyword, then call by exact name.`;
|
|
8360
8636
|
const searchTool = {
|
|
@@ -8380,10 +8656,13 @@ function makeMcpToolSearch(specs, callTool, options = {}) {
|
|
|
8380
8656
|
args: { type: "object", description: "arguments object for the tool (per its schema)" }
|
|
8381
8657
|
}
|
|
8382
8658
|
},
|
|
8659
|
+
maxDurationMs: callTimeoutMs,
|
|
8383
8660
|
async run({ name, args }) {
|
|
8384
8661
|
const n = String(name ?? "");
|
|
8385
8662
|
if (!byName.has(n)) return `Error: unknown MCP tool '${n}'. Use ToolSearch to find valid names.`;
|
|
8386
|
-
const
|
|
8663
|
+
const raw = await withDeadline(callTool(n, args ?? {}), callTimeoutMs);
|
|
8664
|
+
if (raw === TIMED_OUT) return mcpTimedOut(n, callTimeoutMs);
|
|
8665
|
+
const r = toResult(raw);
|
|
8387
8666
|
return r.images?.length ? r : r.text;
|
|
8388
8667
|
}
|
|
8389
8668
|
};
|
|
@@ -9186,14 +9465,20 @@ Reference files in them by their mount path (the left side).`;
|
|
|
9186
9465
|
const memoryWriteDir = memoryDir[0];
|
|
9187
9466
|
const hooks = o.learnFromMistakes && memoryWriteDir ? composeHooks(o.hooks, lessonCapture({ fs, dir: memoryWriteDir, minRepeats: 2 })) : o.hooks;
|
|
9188
9467
|
let realShell = [];
|
|
9468
|
+
const agentRef = {};
|
|
9189
9469
|
const useRealShell = o.realShell ?? !virtual;
|
|
9190
9470
|
if (useRealShell && !virtual && !isClaudeCode) {
|
|
9191
|
-
const jobs = new ShellJobRegistry({
|
|
9192
|
-
|
|
9471
|
+
const jobs = new ShellJobRegistry({
|
|
9472
|
+
cwd,
|
|
9473
|
+
killOnExit: true,
|
|
9474
|
+
osSandbox: o.osSandbox,
|
|
9475
|
+
onExit: (n) => agentRef.agent?.inject(formatJobExit(n))
|
|
9476
|
+
});
|
|
9477
|
+
realShell = [makeRealShellTool({ cwd, registry: jobs, osSandbox: o.osSandbox, jobsSurviveRun: o.singleRun !== true }), ...makeShellJobTools(jobs)];
|
|
9193
9478
|
}
|
|
9194
9479
|
const scratchDir = o.scratch ? o.scratchDir ?? (virtual ? `${cwd}/.agent/scratch` : `${tmpdir()}/agentx-scratch-${process.pid}`) : void 0;
|
|
9195
9480
|
const scratch = scratchDir ? new Scratch(fs, { dir: scratchDir }) : void 0;
|
|
9196
|
-
return new Agent({
|
|
9481
|
+
return agentRef.agent = new Agent({
|
|
9197
9482
|
ai: o.ai,
|
|
9198
9483
|
fs,
|
|
9199
9484
|
model: o.model ?? "anthropic/claude-sonnet-4-6",
|