@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/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;
|
|
@@ -1356,11 +1363,12 @@ function toolRegistry() {
|
|
|
1356
1363
|
const all = [bashTool, readTool, editTool, grepTool, globTool, writeTool, multiEditTool, applyEditsTool, repoMapTool, reviewTool(), todoWriteTool, webFetchTool, webSearchTool, webSearchAnthropicTool];
|
|
1357
1364
|
return Object.fromEntries(all.map((t) => [t.name, t]));
|
|
1358
1365
|
}
|
|
1359
|
-
function toolsByName(names) {
|
|
1366
|
+
function toolsByName(names, available) {
|
|
1360
1367
|
const reg = toolRegistry();
|
|
1368
|
+
const extra = Object.fromEntries((available ?? []).map((t) => [t.name, t]));
|
|
1361
1369
|
return names.map((n) => {
|
|
1362
|
-
const t = reg[n];
|
|
1363
|
-
if (!t) throw new Error(`unknown tool '${n}'. Known: ${Object.keys(reg).join(", ")}`);
|
|
1370
|
+
const t = extra[n] ?? reg[n];
|
|
1371
|
+
if (!t) throw new Error(`unknown tool '${n}'. Known: ${[.../* @__PURE__ */ new Set([...Object.keys(extra), ...Object.keys(reg)])].join(", ")}`);
|
|
1364
1372
|
return t;
|
|
1365
1373
|
});
|
|
1366
1374
|
}
|
|
@@ -1941,6 +1949,7 @@ var init_shell_sandbox = __esm({
|
|
|
1941
1949
|
var tools_shell_exports = {};
|
|
1942
1950
|
__export(tools_shell_exports, {
|
|
1943
1951
|
ShellJobRegistry: () => ShellJobRegistry,
|
|
1952
|
+
formatJobExit: () => formatJobExit,
|
|
1944
1953
|
makeRealShellTool: () => makeRealShellTool,
|
|
1945
1954
|
makeShellJobTools: () => makeShellJobTools
|
|
1946
1955
|
});
|
|
@@ -1971,12 +1980,45 @@ async function nodeSpawn() {
|
|
|
1971
1980
|
if (!_spawn) _spawn = (await import("child_process")).spawn;
|
|
1972
1981
|
return _spawn;
|
|
1973
1982
|
}
|
|
1983
|
+
function formatJobExit(n) {
|
|
1984
|
+
return `[background job ${n.id} ${n.status}${n.exitCode != null ? ` exit ${n.exitCode}` : ""}] \`${n.command}\`
|
|
1985
|
+
` + (n.tail ? `${n.tail}
|
|
1986
|
+
` : "(no output)\n") + `Read the full output with ShellOutput({id:"${n.id}"}).`;
|
|
1987
|
+
}
|
|
1974
1988
|
function makeRealShellTool(options) {
|
|
1975
1989
|
const defaultTimeoutMs = options.timeoutMs ?? 12e4;
|
|
1976
1990
|
const maxTimeoutMs = Math.max(options.maxTimeoutMs ?? 6e5, defaultTimeoutMs);
|
|
1991
|
+
const backgroundDoc = () => {
|
|
1992
|
+
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. ";
|
|
1993
|
+
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.";
|
|
1994
|
+
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.";
|
|
1995
|
+
return `${base}${notify} ${survival}`;
|
|
1996
|
+
};
|
|
1977
1997
|
return {
|
|
1978
1998
|
name: "Shell",
|
|
1979
|
-
|
|
1999
|
+
// Rebind for an isolated child agent (git worktree): same policy/env/timeouts, new cwd. The
|
|
2000
|
+
// `registry` is deliberately dropped — it is bound to the PARENT's cwd (and, in hosts like
|
|
2001
|
+
// shraga-ee, to the parent's session), so a background job started from the child would run
|
|
2002
|
+
// outside the child's isolation and report into the parent. Background stays a parent capability
|
|
2003
|
+
// (`makeShellJobTools`' companions drop out of an isolated child entirely — see their `withCwd`).
|
|
2004
|
+
// Only `run` is rebuilt: everything else is carried over from THIS instance, because a host may
|
|
2005
|
+
// have mutated the tool AFTER construction (shraga-ee renames `Shell` -> `Bash` and its system
|
|
2006
|
+
// prompt says `Bash` everywhere). Rebuilding from `options` alone silently dropped that rename,
|
|
2007
|
+
// so a worktree child advertised `Shell` while being told to call `Bash` — an unknown-tool error
|
|
2008
|
+
// on its first call, and a hard failure for an agentType def whose allowlist names `Bash`.
|
|
2009
|
+
// `description` is the ONE thing taken from the rebound tool instead: it is derived from the
|
|
2010
|
+
// options and states whether `background:true` works — the rebind drops the registry, so carrying
|
|
2011
|
+
// the parent's text over would advertise a background capability the child does not have. (The
|
|
2012
|
+
// spread also collapses the getter below to a plain value, which is why this override is explicit.)
|
|
2013
|
+
withCwd(cwd) {
|
|
2014
|
+
const rebound = makeRealShellTool({ ...options, cwd, registry: void 0 });
|
|
2015
|
+
return { ...this, description: rebound.description, run: rebound.run, withCwd: rebound.withCwd };
|
|
2016
|
+
},
|
|
2017
|
+
get description() {
|
|
2018
|
+
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();
|
|
2019
|
+
},
|
|
2020
|
+
// Declared so transports above the agent loop can size their own deadline with headroom (see AgentTool.maxDurationMs).
|
|
2021
|
+
maxDurationMs: maxTimeoutMs,
|
|
1980
2022
|
parameters: {
|
|
1981
2023
|
type: "object",
|
|
1982
2024
|
required: ["command"],
|
|
@@ -2078,14 +2120,17 @@ function makeRealShellTool(options) {
|
|
|
2078
2120
|
};
|
|
2079
2121
|
}
|
|
2080
2122
|
function reasonFor(timedOut, timeoutMs, body) {
|
|
2081
|
-
const head = timedOut ? `[exit 124] timed out after ${timeoutMs}ms (killed)
|
|
2123
|
+
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
2124
|
return body ? `${head}
|
|
2125
|
+
Partial output before the kill:
|
|
2083
2126
|
${body}` : head;
|
|
2084
2127
|
}
|
|
2085
2128
|
function makeShellJobTools(registry) {
|
|
2086
2129
|
const idParam = { type: "object", properties: { id: { type: "string", description: "the job id from Shell({background:true})" } } };
|
|
2130
|
+
const dropOnRebind = { withCwd: () => void 0 };
|
|
2087
2131
|
return [
|
|
2088
2132
|
{
|
|
2133
|
+
...dropOnRebind,
|
|
2089
2134
|
name: "ShellOutput",
|
|
2090
2135
|
description: "Read the accumulated output (tail) of a background Shell job by id.",
|
|
2091
2136
|
parameters: { type: "object", required: ["id"], properties: { id: { type: "string" } } },
|
|
@@ -2098,6 +2143,7 @@ ${clean(out) || "(no output yet)"}`;
|
|
|
2098
2143
|
}
|
|
2099
2144
|
},
|
|
2100
2145
|
{
|
|
2146
|
+
...dropOnRebind,
|
|
2101
2147
|
name: "ShellStatus",
|
|
2102
2148
|
description: "Status of a background Shell job (running/exited/killed + exit code). Omit `id` to list all jobs.",
|
|
2103
2149
|
parameters: idParam,
|
|
@@ -2111,6 +2157,7 @@ ${clean(out) || "(no output yet)"}`;
|
|
|
2111
2157
|
}
|
|
2112
2158
|
},
|
|
2113
2159
|
{
|
|
2160
|
+
...dropOnRebind,
|
|
2114
2161
|
name: "ShellKill",
|
|
2115
2162
|
description: "Stop a running background Shell job by id (SIGTERM).",
|
|
2116
2163
|
parameters: { type: "object", required: ["id"], properties: { id: { type: "string" } } },
|
|
@@ -2160,12 +2207,14 @@ var init_tools_shell = __esm({
|
|
|
2160
2207
|
job.status = "error";
|
|
2161
2208
|
append(`
|
|
2162
2209
|
[error] ${err?.message ?? err}`);
|
|
2210
|
+
this.notifyExit(id, job);
|
|
2163
2211
|
}
|
|
2164
2212
|
});
|
|
2165
2213
|
proc.on("close", (code) => {
|
|
2166
2214
|
if (job.status === "running") {
|
|
2167
2215
|
job.status = "exited";
|
|
2168
2216
|
job.exitCode = code ?? void 0;
|
|
2217
|
+
this.notifyExit(id, job);
|
|
2169
2218
|
}
|
|
2170
2219
|
});
|
|
2171
2220
|
} catch (e) {
|
|
@@ -2175,6 +2224,29 @@ var init_tools_shell = __esm({
|
|
|
2175
2224
|
this.jobs.set(id, job);
|
|
2176
2225
|
return id;
|
|
2177
2226
|
}
|
|
2227
|
+
/** Fire `onExit` at most once per job, with the tail so the model can act without a second round-trip. */
|
|
2228
|
+
notified = /* @__PURE__ */ new Set();
|
|
2229
|
+
notifyExit(id, job) {
|
|
2230
|
+
if (this.notified.has(id) || !this.cfg.onExit) return;
|
|
2231
|
+
this.notified.add(id);
|
|
2232
|
+
try {
|
|
2233
|
+
this.cfg.onExit({ id, command: job.command, status: job.status, exitCode: job.exitCode, tail: clean(job.buf).slice(-4e3) });
|
|
2234
|
+
} catch {
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
/**
|
|
2238
|
+
* Wire (or rewire) the completion callback AFTER construction. A host that only gets its agent handle
|
|
2239
|
+
* once the Agent is constructed (the library's own `fullAgentOptions` preset, any embedder) could not
|
|
2240
|
+
* pass `onExit` up front, so background completions were silently REPL-only. Set it here instead.
|
|
2241
|
+
*/
|
|
2242
|
+
setOnExit(fn) {
|
|
2243
|
+
this.cfg.onExit = fn;
|
|
2244
|
+
}
|
|
2245
|
+
/** Whether a finished job will actually be reported to the model. The Shell tool's description reads
|
|
2246
|
+
* this so it can't promise a completion notice on a host that discards it. */
|
|
2247
|
+
get notifiesOnExit() {
|
|
2248
|
+
return !!this.cfg.onExit;
|
|
2249
|
+
}
|
|
2178
2250
|
/** Current tail output for a job (null = no such job). */
|
|
2179
2251
|
output(id) {
|
|
2180
2252
|
return this.jobs.get(id)?.buf ?? (this.jobs.has(id) ? "" : null);
|
|
@@ -2897,6 +2969,53 @@ ${sections.join("\n\n---\n\n")}`;
|
|
|
2897
2969
|
init_tools();
|
|
2898
2970
|
import { mkdirSync as mkdirSync2, writeFileSync } from "fs";
|
|
2899
2971
|
import { join as join3 } from "path";
|
|
2972
|
+
|
|
2973
|
+
// src/models.ts
|
|
2974
|
+
var MODEL_ALIASES = {
|
|
2975
|
+
fable: "claude-fable-5",
|
|
2976
|
+
"fable-5": "claude-fable-5",
|
|
2977
|
+
opus: "claude-opus-4-8",
|
|
2978
|
+
"opus-4-8": "claude-opus-4-8",
|
|
2979
|
+
"opus-4-7": "claude-opus-4-7",
|
|
2980
|
+
"opus-4-6": "claude-opus-4-6",
|
|
2981
|
+
sonnet: "claude-sonnet-4-6",
|
|
2982
|
+
haiku: "claude-haiku-4-5-20251001"
|
|
2983
|
+
};
|
|
2984
|
+
function resolveModelAlias(input) {
|
|
2985
|
+
const slash = input.indexOf("/");
|
|
2986
|
+
const prefix = slash === -1 ? "" : input.slice(0, slash + 1);
|
|
2987
|
+
const rest = slash === -1 ? input : input.slice(slash + 1);
|
|
2988
|
+
return prefix + (MODEL_ALIASES[rest.toLowerCase()] ?? rest);
|
|
2989
|
+
}
|
|
2990
|
+
function modelProvider(model) {
|
|
2991
|
+
const i = model.indexOf("/");
|
|
2992
|
+
if (i !== -1) return model.slice(0, i);
|
|
2993
|
+
if (/^claude-/i.test(model)) return "anthropic";
|
|
2994
|
+
return `?${model}`;
|
|
2995
|
+
}
|
|
2996
|
+
function modelShortLabel(model) {
|
|
2997
|
+
if (!model) return "default";
|
|
2998
|
+
const id = model.split("/").pop();
|
|
2999
|
+
return id.replace(/^claude-/, "").replace(/-\d.*$/, "");
|
|
3000
|
+
}
|
|
3001
|
+
function resolveModelSwitch(opts) {
|
|
3002
|
+
const { requested, current, prior, allowed } = opts;
|
|
3003
|
+
if (!requested) return { model: current, denied: false };
|
|
3004
|
+
if (allowed && !allowed.some((a) => modelShortLabel(a) === modelShortLabel(requested))) {
|
|
3005
|
+
return { model: current, denied: true };
|
|
3006
|
+
}
|
|
3007
|
+
const fmt = opts.format ?? ((from, to) => `_[switching from ${from} to ${to}]_
|
|
3008
|
+
|
|
3009
|
+
`);
|
|
3010
|
+
const changed = prior !== void 0 && modelShortLabel(prior) !== modelShortLabel(requested);
|
|
3011
|
+
return {
|
|
3012
|
+
model: requested,
|
|
3013
|
+
denied: false,
|
|
3014
|
+
notice: changed ? fmt(modelShortLabel(prior), modelShortLabel(requested)) : void 0
|
|
3015
|
+
};
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
// src/subagent.ts
|
|
2900
3019
|
init_OverlayFilesystem();
|
|
2901
3020
|
|
|
2902
3021
|
// src/worktree.ts
|
|
@@ -3064,6 +3183,30 @@ async function boundedPool(items, limit, fn) {
|
|
|
3064
3183
|
await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, worker));
|
|
3065
3184
|
return out;
|
|
3066
3185
|
}
|
|
3186
|
+
var SUBAGENT_MAX_DURATION_MS = 18e5;
|
|
3187
|
+
function wallClock(parent, ms) {
|
|
3188
|
+
const ac = new AbortController();
|
|
3189
|
+
let fired = false;
|
|
3190
|
+
const onParent = () => ac.abort();
|
|
3191
|
+
const timer = setTimeout(() => {
|
|
3192
|
+
fired = true;
|
|
3193
|
+
ac.abort();
|
|
3194
|
+
}, ms);
|
|
3195
|
+
if (parent) {
|
|
3196
|
+
if (parent.aborted) ac.abort();
|
|
3197
|
+
else parent.addEventListener("abort", onParent, { once: true });
|
|
3198
|
+
}
|
|
3199
|
+
return {
|
|
3200
|
+
signal: ac.signal,
|
|
3201
|
+
timedOut: () => fired,
|
|
3202
|
+
dispose: () => {
|
|
3203
|
+
clearTimeout(timer);
|
|
3204
|
+
parent?.removeEventListener("abort", onParent);
|
|
3205
|
+
}
|
|
3206
|
+
};
|
|
3207
|
+
}
|
|
3208
|
+
var deadlineNote = (ms) => `
|
|
3209
|
+
[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
3210
|
function sanitizeSlug(raw) {
|
|
3068
3211
|
return raw.replace(/[^a-zA-Z0-9._-]/g, "-").replace(/-+/g, "-").slice(0, 40);
|
|
3069
3212
|
}
|
|
@@ -3081,6 +3224,14 @@ function createWorktreeFs(cwd, label, index) {
|
|
|
3081
3224
|
function releaseWorktree(handle) {
|
|
3082
3225
|
return cleanupWorktree(handle.repoRoot, handle.slug, { force: true });
|
|
3083
3226
|
}
|
|
3227
|
+
function rebindTools(tools, root) {
|
|
3228
|
+
if (!tools || !root) return tools;
|
|
3229
|
+
return tools.flatMap((t) => {
|
|
3230
|
+
if (!t.withCwd) return [t];
|
|
3231
|
+
const rebound = t.withCwd(root);
|
|
3232
|
+
return rebound ? [rebound] : [];
|
|
3233
|
+
});
|
|
3234
|
+
}
|
|
3084
3235
|
function worktreePromptPrefix(branch) {
|
|
3085
3236
|
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).
|
|
3086
3237
|
|
|
@@ -3126,33 +3277,52 @@ function abortHint(label, res, file) {
|
|
|
3126
3277
|
}
|
|
3127
3278
|
async function runChildTraced(args) {
|
|
3128
3279
|
const { opts, label, agentType, prompt } = args;
|
|
3129
|
-
|
|
3280
|
+
const deadlineMs = opts.maxDurationMs ?? SUBAGENT_MAX_DURATION_MS;
|
|
3281
|
+
const wc = wallClock(args.signal, deadlineMs);
|
|
3282
|
+
args.childOpts.signal = wc.signal;
|
|
3130
3283
|
const file = opts.traceDir ? traceFileFor(opts.traceDir, label, args.childDepth) : void 0;
|
|
3131
3284
|
const meta = { label, agentType, prompt };
|
|
3132
3285
|
const child = new Agent(args.childOpts);
|
|
3133
3286
|
if (file) installTrace(child, file, meta);
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3287
|
+
let res;
|
|
3288
|
+
try {
|
|
3289
|
+
res = await child.run(prompt);
|
|
3290
|
+
} finally {
|
|
3291
|
+
wc.dispose();
|
|
3292
|
+
}
|
|
3293
|
+
if (file) flushTrace(file, meta, child.transcript, wc.timedOut() ? "deadline" : res.finishReason);
|
|
3294
|
+
let result = res.finishReason === "stop" ? res.text || `(child '${label}' finished with no summary; finishReason=${res.finishReason})` : abortHint(label, res, file);
|
|
3295
|
+
if (wc.timedOut()) {
|
|
3296
|
+
log5.warn(`sub-task '${label}' hit its ${deadlineMs}ms wall-clock deadline (steps=${res.steps})`);
|
|
3297
|
+
result += deadlineNote(deadlineMs);
|
|
3298
|
+
}
|
|
3137
3299
|
await opts.hooks?.onSubagentStop?.(result, { label, agentType });
|
|
3138
3300
|
return { res, result };
|
|
3139
3301
|
}
|
|
3140
|
-
function childOptionsFor(opts, fs, depth, maxDepth, agentType) {
|
|
3302
|
+
function childOptionsFor(opts, fs, depth, maxDepth, agentType, model, root) {
|
|
3141
3303
|
let def;
|
|
3142
3304
|
if (agentType) {
|
|
3143
3305
|
def = (opts.agents ?? []).find((a) => a.name === agentType);
|
|
3144
3306
|
if (!def) return `no subagent type '${agentType}'. Available: ${(opts.agents ?? []).map((a) => a.name).join(", ") || "(none defined)"}`;
|
|
3145
3307
|
}
|
|
3146
|
-
const
|
|
3308
|
+
const childModel = resolveModelAlias(model || def?.model || opts.model);
|
|
3309
|
+
const childOpts = { ai: opts.ai, fs, model: childModel, subagents: true, depth: depth + 1, maxDepth, traceDir: opts.traceDir };
|
|
3147
3310
|
if (opts.maxSteps != null) childOpts.maxSteps = opts.maxSteps;
|
|
3148
|
-
|
|
3311
|
+
const childPrompt = def?.systemPrompt ?? opts.systemPrompt;
|
|
3312
|
+
if (childPrompt) childOpts.systemPrompt = childPrompt;
|
|
3313
|
+
const inherited = rebindTools(opts.tools, root);
|
|
3149
3314
|
if (def?.tools?.length) {
|
|
3150
3315
|
try {
|
|
3151
|
-
childOpts.tools = toolsByName(def.tools);
|
|
3316
|
+
childOpts.tools = toolsByName(def.tools, inherited);
|
|
3152
3317
|
} catch (e) {
|
|
3153
3318
|
return `subagent '${agentType}' declares an unknown tool \u2014 ${e.message}`;
|
|
3154
3319
|
}
|
|
3320
|
+
} else if (inherited?.length) {
|
|
3321
|
+
childOpts.tools = inherited;
|
|
3155
3322
|
}
|
|
3323
|
+
const po = opts.providerOptionsFor?.(childModel);
|
|
3324
|
+
if (po) childOpts.providerOptions = po;
|
|
3325
|
+
if (opts.hooks?.preToolUse) childOpts.hooks = { preToolUse: opts.hooks.preToolUse.bind(opts.hooks) };
|
|
3156
3326
|
return childOpts;
|
|
3157
3327
|
}
|
|
3158
3328
|
function makeTaskTool(opts) {
|
|
@@ -3160,7 +3330,7 @@ function makeTaskTool(opts) {
|
|
|
3160
3330
|
const maxDepth = opts.maxDepth ?? 2;
|
|
3161
3331
|
return {
|
|
3162
3332
|
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
|
|
3333
|
+
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.',
|
|
3164
3334
|
parameters: {
|
|
3165
3335
|
type: "object",
|
|
3166
3336
|
required: ["description", "prompt"],
|
|
@@ -3168,11 +3338,18 @@ function makeTaskTool(opts) {
|
|
|
3168
3338
|
description: { type: "string", description: "a short (3-5 word) label for the sub-task" },
|
|
3169
3339
|
prompt: { type: "string", description: "the full instructions the child agent should carry out" },
|
|
3170
3340
|
agentType: { type: "string", description: "optional named subagent type (its persona, model, and scoped tools) \u2014 see the catalog" },
|
|
3341
|
+
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.' },
|
|
3171
3342
|
background: { type: "boolean", description: "run detached (overlay-isolated, commits on finish); returns a job id to poll with JobOutput instead of blocking" },
|
|
3172
3343
|
isolation: { type: "string", enum: ["overlay", "worktree"], description: 'isolation mode: "overlay" (default, VFS layer) or "worktree" (real git worktree for shell-heavy work)' }
|
|
3173
3344
|
}
|
|
3174
3345
|
},
|
|
3175
|
-
|
|
3346
|
+
// Declared because it is ENFORCED (see runChildTraced) — a transport above the loop sizes its own
|
|
3347
|
+
// deadline around this one (AgentTool.maxDurationMs). Declaring an unenforced number would be a lie;
|
|
3348
|
+
// declaring nothing left `Task` invisible to toolTimeoutViolations, which is the same hole.
|
|
3349
|
+
maxDurationMs: opts.maxDurationMs ?? SUBAGENT_MAX_DURATION_MS,
|
|
3350
|
+
maxDurationEnforced: true,
|
|
3351
|
+
// the wall clock below ABORTS the child at this bound — it cannot be overrun, so the transport needs slack, not a doubling
|
|
3352
|
+
async run({ description, prompt, agentType, model, background, isolation }, ctx) {
|
|
3176
3353
|
if (depth >= maxDepth) {
|
|
3177
3354
|
return `Error: Task depth limit reached (maxDepth ${maxDepth}). Cannot spawn another child agent \u2014 do this work directly instead.`;
|
|
3178
3355
|
}
|
|
@@ -3195,7 +3372,7 @@ function makeTaskTool(opts) {
|
|
|
3195
3372
|
fs2 = overlay;
|
|
3196
3373
|
commit = () => overlay.commit();
|
|
3197
3374
|
}
|
|
3198
|
-
const childOpts2 = childOptionsFor(opts, fs2, depth, maxDepth, agentType);
|
|
3375
|
+
const childOpts2 = childOptionsFor(opts, fs2, depth, maxDepth, agentType, model, wtHandle2?.worktreePath);
|
|
3199
3376
|
if (typeof childOpts2 === "string") throw new Error(childOpts2);
|
|
3200
3377
|
const childPrompt = wtHandle2 ? worktreePromptPrefix(wtHandle2.branch) + String(prompt ?? "") : String(prompt ?? "");
|
|
3201
3378
|
const { res, result } = await runChildTraced({ opts, childOpts: childOpts2, prompt: childPrompt, label, agentType, childDepth: depth + 1, signal });
|
|
@@ -3225,7 +3402,7 @@ function makeTaskTool(opts) {
|
|
|
3225
3402
|
} else {
|
|
3226
3403
|
fs = opts.fs;
|
|
3227
3404
|
}
|
|
3228
|
-
const childOpts = childOptionsFor(opts, fs, depth, maxDepth, agentType);
|
|
3405
|
+
const childOpts = childOptionsFor(opts, fs, depth, maxDepth, agentType, model, wtHandle?.worktreePath);
|
|
3229
3406
|
if (typeof childOpts === "string") {
|
|
3230
3407
|
wtHandle && releaseWorktree(wtHandle);
|
|
3231
3408
|
return `Error: ${childOpts}`;
|
|
@@ -3254,7 +3431,7 @@ function makeTaskBatchTool(opts) {
|
|
|
3254
3431
|
const maxParallel = opts.maxParallel ?? 4;
|
|
3255
3432
|
return {
|
|
3256
3433
|
name: "TaskBatch",
|
|
3257
|
-
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? }]
|
|
3434
|
+
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.',
|
|
3258
3435
|
parameters: {
|
|
3259
3436
|
type: "object",
|
|
3260
3437
|
required: ["tasks"],
|
|
@@ -3268,18 +3445,26 @@ function makeTaskBatchTool(opts) {
|
|
|
3268
3445
|
properties: {
|
|
3269
3446
|
description: { type: "string", description: "a short (3-5 word) label" },
|
|
3270
3447
|
prompt: { type: "string", description: "the full instructions for this child" },
|
|
3271
|
-
agentType: { type: "string", description: "optional named subagent type" }
|
|
3448
|
+
agentType: { type: "string", description: "optional named subagent type" },
|
|
3449
|
+
model: { type: "string", description: "optional model for this child (alias or full id); overrides the agentType default" }
|
|
3272
3450
|
}
|
|
3273
3451
|
}
|
|
3274
3452
|
},
|
|
3275
3453
|
isolation: { type: "string", enum: ["overlay", "worktree"], description: 'isolation mode for ALL children: "overlay" (default) or "worktree" (real git worktree each)' }
|
|
3276
3454
|
}
|
|
3277
3455
|
},
|
|
3456
|
+
// Same enforced bound as `Task`, applied to the WHOLE batch (below), not per child — otherwise the
|
|
3457
|
+
// honest ceiling would be ceil(n/maxParallel) x the per-child deadline, i.e. unbounded in `tasks`.
|
|
3458
|
+
maxDurationMs: opts.maxDurationMs ?? SUBAGENT_MAX_DURATION_MS,
|
|
3459
|
+
maxDurationEnforced: true,
|
|
3460
|
+
// the wall clock below ABORTS the child at this bound — it cannot be overrun, so the transport needs slack, not a doubling
|
|
3278
3461
|
async run({ tasks, isolation }, ctx) {
|
|
3279
3462
|
if (depth >= maxDepth) return `Error: Task depth limit reached (maxDepth ${maxDepth}). Cannot spawn child agents \u2014 do this work directly instead.`;
|
|
3280
3463
|
const list = Array.isArray(tasks) ? tasks : [];
|
|
3281
3464
|
if (!list.length) return "Error: TaskBatch needs a non-empty `tasks` array.";
|
|
3282
3465
|
const useWorktree = isolation === "worktree";
|
|
3466
|
+
const batchDeadlineMs = opts.maxDurationMs ?? SUBAGENT_MAX_DURATION_MS;
|
|
3467
|
+
const batchClock = wallClock(ctx.signal, batchDeadlineMs);
|
|
3283
3468
|
const results = await boundedPool(list, maxParallel, async (t, i) => {
|
|
3284
3469
|
const label = String(t?.description ?? t?.agentType ?? `task ${i + 1}`);
|
|
3285
3470
|
let fs;
|
|
@@ -3294,16 +3479,16 @@ function makeTaskBatchTool(opts) {
|
|
|
3294
3479
|
overlay = new OverlayFilesystem(opts.fs);
|
|
3295
3480
|
fs = overlay;
|
|
3296
3481
|
}
|
|
3297
|
-
const childOpts = childOptionsFor(opts, fs, depth, maxDepth, t?.agentType);
|
|
3482
|
+
const childOpts = childOptionsFor(opts, fs, depth, maxDepth, t?.agentType, t?.model, wtHandle?.worktreePath);
|
|
3298
3483
|
if (typeof childOpts === "string") return { label, error: childOpts, ok: false };
|
|
3299
3484
|
try {
|
|
3300
3485
|
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:
|
|
3486
|
+
const { res, result } = await runChildTraced({ opts, childOpts, prompt: childPrompt, label, agentType: t?.agentType, childDepth: depth + 1, signal: batchClock.signal });
|
|
3302
3487
|
return { label, text: result, overlay, wtHandle, ok: res.finishReason !== "error" && res.finishReason !== "aborted" };
|
|
3303
3488
|
} catch (e) {
|
|
3304
3489
|
return { label, error: e instanceof Error ? e.message : String(e), wtHandle, ok: false };
|
|
3305
3490
|
}
|
|
3306
|
-
});
|
|
3491
|
+
}).finally(() => batchClock.dispose());
|
|
3307
3492
|
const lines = [];
|
|
3308
3493
|
for (const r of results) {
|
|
3309
3494
|
if (r.ok && r.overlay) await r.overlay.commit().catch(() => {
|
|
@@ -3317,7 +3502,7 @@ function makeTaskBatchTool(opts) {
|
|
|
3317
3502
|
lines.push(`### ${lines.length + 1}. ${r.label}
|
|
3318
3503
|
${r.error ? `ERROR: ${r.error}` : r.text || "(no summary)"}${extra}`);
|
|
3319
3504
|
}
|
|
3320
|
-
return lines.join("\n\n");
|
|
3505
|
+
return lines.join("\n\n") + (batchClock.timedOut() ? deadlineNote(batchDeadlineMs) : "");
|
|
3321
3506
|
}
|
|
3322
3507
|
};
|
|
3323
3508
|
}
|
|
@@ -3721,6 +3906,10 @@ var AgentOptions = class {
|
|
|
3721
3906
|
autoTest;
|
|
3722
3907
|
/** Provider-specific options forwarded to ai.chat() (e.g. cursor mcpServers, cwd). */
|
|
3723
3908
|
providerOptions;
|
|
3909
|
+
/** Derive `providerOptions` for a DELEGATED child whose model may differ from this agent's
|
|
3910
|
+
* (`Task({ model })` / an agentType def). Default: inherit `providerOptions` only when the child's
|
|
3911
|
+
* model is on the SAME provider — cursor's `cwd`/`cursorSession` on an anthropic child is a 400. */
|
|
3912
|
+
providerOptionsFor;
|
|
3724
3913
|
/** Prompt caching (providers that support it, e.g. Anthropic): cache tools/system/conversation
|
|
3725
3914
|
* prefix across the loop's steps — reads cost 0.1x, writes 1.25x. A multi-step agent loop
|
|
3726
3915
|
* re-sends its whole prefix every step, so this is a large net cost cut. Default on. */
|
|
@@ -3944,7 +4133,19 @@ var Agent = class _Agent {
|
|
|
3944
4133
|
agents = loaded.agents;
|
|
3945
4134
|
if (loaded.catalog) systemPrompt += "\n\n" + loaded.catalog;
|
|
3946
4135
|
}
|
|
3947
|
-
const taskOpts = {
|
|
4136
|
+
const taskOpts = {
|
|
4137
|
+
ai: o.ai,
|
|
4138
|
+
model: o.model,
|
|
4139
|
+
fs,
|
|
4140
|
+
depth: o.depth,
|
|
4141
|
+
maxDepth: o.maxDepth,
|
|
4142
|
+
agents,
|
|
4143
|
+
hooks: o.hooks,
|
|
4144
|
+
traceDir: o.traceDir,
|
|
4145
|
+
tools: [...o.tools, ...this.injectedTools],
|
|
4146
|
+
systemPrompt: o.systemPrompt,
|
|
4147
|
+
providerOptionsFor: o.providerOptionsFor ?? ((m) => modelProvider(m) === modelProvider(o.model) ? o.providerOptions : void 0)
|
|
4148
|
+
};
|
|
3948
4149
|
tools = [...tools, makeTaskTool(taskOpts), makeTaskBatchTool(taskOpts)];
|
|
3949
4150
|
}
|
|
3950
4151
|
if (o.checkpoints) tools = [...tools, ...checkpointTools()];
|
|
@@ -4081,7 +4282,17 @@ var Agent = class _Agent {
|
|
|
4081
4282
|
const frag = reasoningToChatFragment(o.model, o.reasoning);
|
|
4082
4283
|
const sentTools = o.model.startsWith("claude-code/") ? [] : wireTools;
|
|
4083
4284
|
const isCursorWithTools = o.model.startsWith("cursor/") && wireTools.length > 0;
|
|
4285
|
+
const hostToolTimeoutMs = o.providerOptions?.toolTimeoutMs;
|
|
4286
|
+
const derivedToolTimeoutMs = Math.max(TRANSPORT_TOOL_TIMEOUT_FLOOR_MS, ...this.activeTools.map(transportHeadroomMs));
|
|
4287
|
+
const effectiveToolTimeoutMs = typeof hostToolTimeoutMs === "number" ? Math.max(hostToolTimeoutMs, derivedToolTimeoutMs) : derivedToolTimeoutMs;
|
|
4288
|
+
if (isCursorWithTools) {
|
|
4289
|
+
if (typeof hostToolTimeoutMs === "number" && effectiveToolTimeoutMs !== hostToolTimeoutMs) {
|
|
4290
|
+
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.`);
|
|
4291
|
+
}
|
|
4292
|
+
for (const v of toolTimeoutViolations(this.activeTools, effectiveToolTimeoutMs)) log6.warn(v);
|
|
4293
|
+
}
|
|
4084
4294
|
const cursorPo = isCursorWithTools ? {
|
|
4295
|
+
toolTimeoutMs: effectiveToolTimeoutMs,
|
|
4085
4296
|
toolExecutor: async (name, args) => {
|
|
4086
4297
|
const tc = { id: `cursor-${Date.now()}`, type: "function", function: { name, arguments: JSON.stringify(args) } };
|
|
4087
4298
|
return await this.dispatch(tc);
|
|
@@ -4183,14 +4394,12 @@ var Agent = class _Agent {
|
|
|
4183
4394
|
})() } }))
|
|
4184
4395
|
});
|
|
4185
4396
|
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 });
|
|
4397
|
+
const name = a.name || "tool";
|
|
4398
|
+
const r = delegatedResult(a.output);
|
|
4399
|
+
const text = r.images.length ? r.text : nonEmptyResult(isVacuous(a.output) ? "" : r.text, name, deliveryOf(a.output, a.status));
|
|
4400
|
+
const body = cap > 0 && text.length > cap ? cropResult(text, cap) : text;
|
|
4401
|
+
const content = r.images.length ? [{ type: "text", text: body }, ...r.images.map((i) => imagePart(`data:${i.mimeType};base64,${i.data}`))] : body;
|
|
4402
|
+
this.transcript.push({ role: "tool", tool_call_id: a.id, content });
|
|
4194
4403
|
}
|
|
4195
4404
|
}
|
|
4196
4405
|
const toolCalls = res.toolCalls ?? [];
|
|
@@ -4243,9 +4452,10 @@ var Agent = class _Agent {
|
|
|
4243
4452
|
const raw = await this.dispatch(tc);
|
|
4244
4453
|
let content;
|
|
4245
4454
|
if (typeof raw === "string") {
|
|
4246
|
-
content = raw;
|
|
4455
|
+
content = nonEmptyResult(raw, tc.function.name);
|
|
4247
4456
|
} else {
|
|
4248
|
-
const
|
|
4457
|
+
const hasImages = !!raw.images?.length;
|
|
4458
|
+
const parts = [{ type: "text", text: hasImages ? raw.text : nonEmptyResult(raw.text ?? "", tc.function.name) }];
|
|
4249
4459
|
for (const img of raw.images ?? []) parts.push(imagePart(`data:${img.mimeType};base64,${img.data}`));
|
|
4250
4460
|
content = parts;
|
|
4251
4461
|
}
|
|
@@ -4554,6 +4764,76 @@ var isAnthropicModel = (m) => !!m && (m.startsWith("anthropic/") || /(^|\/)claud
|
|
|
4554
4764
|
function estimateTokens(m, keepRecentImages = 1) {
|
|
4555
4765
|
return Math.ceil(sendBytes(m, keepRecentImages) / 4);
|
|
4556
4766
|
}
|
|
4767
|
+
var TRANSPORT_TOOL_TIMEOUT_FLOOR_MS = 18e5;
|
|
4768
|
+
function transportHeadroomMs(t) {
|
|
4769
|
+
const d = Number(t.maxDurationMs) || 0;
|
|
4770
|
+
if (!d) return 0;
|
|
4771
|
+
return t.maxDurationEnforced ? d + 6e4 : d * 2 + 6e4;
|
|
4772
|
+
}
|
|
4773
|
+
function toolTimeoutViolations(tools, transportMs) {
|
|
4774
|
+
const out = [];
|
|
4775
|
+
for (const t of tools) {
|
|
4776
|
+
const d = Number(t.maxDurationMs) || 0;
|
|
4777
|
+
if (d && d >= transportMs) {
|
|
4778
|
+
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.`);
|
|
4779
|
+
}
|
|
4780
|
+
}
|
|
4781
|
+
return out;
|
|
4782
|
+
}
|
|
4783
|
+
function nonEmptyResult(out, name = "tool", delivery = "ran") {
|
|
4784
|
+
if (out.trim()) return out;
|
|
4785
|
+
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.`;
|
|
4786
|
+
if (delivery === "missing") {
|
|
4787
|
+
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.`;
|
|
4788
|
+
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.`;
|
|
4789
|
+
}
|
|
4790
|
+
return `[${name}] ran and returned no output. The call completed \u2014 this is an empty answer, not a failure.`;
|
|
4791
|
+
}
|
|
4792
|
+
function isVacuous(v, seen = /* @__PURE__ */ new Set(), depth = 0) {
|
|
4793
|
+
if (v == null) return true;
|
|
4794
|
+
if (typeof v === "string") return v.trim() === "";
|
|
4795
|
+
if (typeof v !== "object") return false;
|
|
4796
|
+
if (seen.has(v) || depth >= 64) return false;
|
|
4797
|
+
seen.add(v);
|
|
4798
|
+
try {
|
|
4799
|
+
if (Array.isArray(v)) return v.every((x) => isVacuous(x, seen, depth + 1));
|
|
4800
|
+
const proto = Object.getPrototypeOf(v);
|
|
4801
|
+
if (proto !== Object.prototype && proto !== null) return false;
|
|
4802
|
+
const vals = Object.values(v);
|
|
4803
|
+
return vals.length === 0 || vals.every((x) => isVacuous(x, seen, depth + 1));
|
|
4804
|
+
} finally {
|
|
4805
|
+
seen.delete(v);
|
|
4806
|
+
}
|
|
4807
|
+
}
|
|
4808
|
+
function delegatedResult(output) {
|
|
4809
|
+
if (typeof output === "string") return { text: output, images: [] };
|
|
4810
|
+
if (output == null) return { text: "", images: [] };
|
|
4811
|
+
const isImg = (i) => i && typeof i.data === "string" && typeof i.mimeType === "string";
|
|
4812
|
+
const o = output;
|
|
4813
|
+
if (Array.isArray(o.images)) {
|
|
4814
|
+
const images = o.images.filter(isImg);
|
|
4815
|
+
if (images.length) return { text: typeof o.text === "string" ? o.text : "", images };
|
|
4816
|
+
}
|
|
4817
|
+
const blocks = Array.isArray(output) ? output : Array.isArray(o.content) ? o.content : void 0;
|
|
4818
|
+
if (blocks) {
|
|
4819
|
+
const images = blocks.filter((b) => b?.type === "image" && isImg(b)).map((b) => ({ mimeType: b.mimeType, data: b.data }));
|
|
4820
|
+
if (images.length) {
|
|
4821
|
+
const text = blocks.filter((b) => b?.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n");
|
|
4822
|
+
return { text, images };
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
4825
|
+
try {
|
|
4826
|
+
return { text: JSON.stringify(output), images: [] };
|
|
4827
|
+
} catch {
|
|
4828
|
+
return { text: String(output), images: [] };
|
|
4829
|
+
}
|
|
4830
|
+
}
|
|
4831
|
+
function deliveryOf(output, status) {
|
|
4832
|
+
if (status === "error") return "error";
|
|
4833
|
+
if (output == null) return "missing";
|
|
4834
|
+
if (status != null && status !== "completed") return "missing";
|
|
4835
|
+
return "ran";
|
|
4836
|
+
}
|
|
4557
4837
|
function cropResult(result, cap) {
|
|
4558
4838
|
const head = result.slice(0, cap);
|
|
4559
4839
|
const nl = head.lastIndexOf("\n");
|
|
@@ -4730,45 +5010,6 @@ function lastAssistantText(messages) {
|
|
|
4730
5010
|
return "";
|
|
4731
5011
|
}
|
|
4732
5012
|
|
|
4733
|
-
// src/models.ts
|
|
4734
|
-
var MODEL_ALIASES = {
|
|
4735
|
-
fable: "claude-fable-5",
|
|
4736
|
-
"fable-5": "claude-fable-5",
|
|
4737
|
-
opus: "claude-opus-4-8",
|
|
4738
|
-
"opus-4-8": "claude-opus-4-8",
|
|
4739
|
-
"opus-4-7": "claude-opus-4-7",
|
|
4740
|
-
"opus-4-6": "claude-opus-4-6",
|
|
4741
|
-
sonnet: "claude-sonnet-4-6",
|
|
4742
|
-
haiku: "claude-haiku-4-5-20251001"
|
|
4743
|
-
};
|
|
4744
|
-
function resolveModelAlias(input) {
|
|
4745
|
-
const slash = input.indexOf("/");
|
|
4746
|
-
const prefix = slash === -1 ? "" : input.slice(0, slash + 1);
|
|
4747
|
-
const rest = slash === -1 ? input : input.slice(slash + 1);
|
|
4748
|
-
return prefix + (MODEL_ALIASES[rest.toLowerCase()] ?? rest);
|
|
4749
|
-
}
|
|
4750
|
-
function modelShortLabel(model) {
|
|
4751
|
-
if (!model) return "default";
|
|
4752
|
-
const id = model.split("/").pop();
|
|
4753
|
-
return id.replace(/^claude-/, "").replace(/-\d.*$/, "");
|
|
4754
|
-
}
|
|
4755
|
-
function resolveModelSwitch(opts) {
|
|
4756
|
-
const { requested, current, prior, allowed } = opts;
|
|
4757
|
-
if (!requested) return { model: current, denied: false };
|
|
4758
|
-
if (allowed && !allowed.some((a) => modelShortLabel(a) === modelShortLabel(requested))) {
|
|
4759
|
-
return { model: current, denied: true };
|
|
4760
|
-
}
|
|
4761
|
-
const fmt = opts.format ?? ((from, to) => `_[switching from ${from} to ${to}]_
|
|
4762
|
-
|
|
4763
|
-
`);
|
|
4764
|
-
const changed = prior !== void 0 && modelShortLabel(prior) !== modelShortLabel(requested);
|
|
4765
|
-
return {
|
|
4766
|
-
model: requested,
|
|
4767
|
-
denied: false,
|
|
4768
|
-
notice: changed ? fmt(modelShortLabel(prior), modelShortLabel(requested)) : void 0
|
|
4769
|
-
};
|
|
4770
|
-
}
|
|
4771
|
-
|
|
4772
5013
|
// src/index.ts
|
|
4773
5014
|
init_NodeDiskFilesystem();
|
|
4774
5015
|
|
|
@@ -5359,9 +5600,10 @@ function diskAgentOptions(opts = {}) {
|
|
|
5359
5600
|
return { ...opts };
|
|
5360
5601
|
}
|
|
5361
5602
|
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 });
|
|
5603
|
+
const { makeRealShellTool: makeRealShellTool2, makeShellJobTools: makeShellJobTools2, ShellJobRegistry: ShellJobRegistry2, formatJobExit: formatJobExit2 } = await Promise.resolve().then(() => (init_tools_shell(), tools_shell_exports));
|
|
5604
|
+
const { cwd = process.cwd(), tools, onJobExit, jobs, ...rest } = opts;
|
|
5605
|
+
const registry = jobs ?? new ShellJobRegistry2({ cwd, killOnExit: true });
|
|
5606
|
+
if (onJobExit) registry.setOnExit((n) => onJobExit(formatJobExit2(n)));
|
|
5365
5607
|
const shell = [makeRealShellTool2({ cwd, registry }), ...makeShellJobTools2(registry)];
|
|
5366
5608
|
return { ...rest, tools: [...tools ?? defaultTools(), ...shell] };
|
|
5367
5609
|
}
|
|
@@ -8422,13 +8664,29 @@ function toResult(result) {
|
|
|
8422
8664
|
}
|
|
8423
8665
|
return { text: JSON.stringify(result) };
|
|
8424
8666
|
}
|
|
8425
|
-
|
|
8667
|
+
var MCP_CALL_TIMEOUT_MS = 3e5;
|
|
8668
|
+
var TIMED_OUT = /* @__PURE__ */ Symbol("mcp-timeout");
|
|
8669
|
+
async function withDeadline(p, ms) {
|
|
8670
|
+
let t;
|
|
8671
|
+
try {
|
|
8672
|
+
return await Promise.race([p, new Promise((res) => {
|
|
8673
|
+
t = setTimeout(() => res(TIMED_OUT), ms);
|
|
8674
|
+
})]);
|
|
8675
|
+
} finally {
|
|
8676
|
+
clearTimeout(t);
|
|
8677
|
+
}
|
|
8678
|
+
}
|
|
8679
|
+
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.`;
|
|
8680
|
+
function mcpToolToAgentTool(spec, callTool, prefix = "mcp__", timeoutMs = MCP_CALL_TIMEOUT_MS) {
|
|
8426
8681
|
return {
|
|
8427
8682
|
name: `${prefix}${spec.name}`,
|
|
8428
8683
|
description: spec.description ?? `MCP tool ${spec.name}`,
|
|
8429
8684
|
parameters: spec.inputSchema ?? { type: "object", properties: {} },
|
|
8685
|
+
maxDurationMs: timeoutMs,
|
|
8430
8686
|
async run(args, _ctx) {
|
|
8431
|
-
const
|
|
8687
|
+
const raw = await withDeadline(callTool(spec.name, args ?? {}), timeoutMs);
|
|
8688
|
+
if (raw === TIMED_OUT) return mcpTimedOut(spec.name, timeoutMs);
|
|
8689
|
+
const r = toResult(raw);
|
|
8432
8690
|
return r.images?.length ? r : r.text;
|
|
8433
8691
|
}
|
|
8434
8692
|
};
|
|
@@ -8443,6 +8701,7 @@ function describeSpec(s) {
|
|
|
8443
8701
|
}
|
|
8444
8702
|
function makeMcpToolSearch(specs, callTool, options = {}) {
|
|
8445
8703
|
const maxResults = options.maxResults ?? 10;
|
|
8704
|
+
const callTimeoutMs = options.timeoutMs ?? MCP_CALL_TIMEOUT_MS;
|
|
8446
8705
|
const byName = new Map(specs.map((s) => [s.name, s]));
|
|
8447
8706
|
const catalogLine = `${specs.length} MCP tool(s) available \u2014 search by keyword, then call by exact name.`;
|
|
8448
8707
|
const searchTool = {
|
|
@@ -8468,10 +8727,13 @@ function makeMcpToolSearch(specs, callTool, options = {}) {
|
|
|
8468
8727
|
args: { type: "object", description: "arguments object for the tool (per its schema)" }
|
|
8469
8728
|
}
|
|
8470
8729
|
},
|
|
8730
|
+
maxDurationMs: callTimeoutMs,
|
|
8471
8731
|
async run({ name, args }) {
|
|
8472
8732
|
const n = String(name ?? "");
|
|
8473
8733
|
if (!byName.has(n)) return `Error: unknown MCP tool '${n}'. Use ToolSearch to find valid names.`;
|
|
8474
|
-
const
|
|
8734
|
+
const raw = await withDeadline(callTool(n, args ?? {}), callTimeoutMs);
|
|
8735
|
+
if (raw === TIMED_OUT) return mcpTimedOut(n, callTimeoutMs);
|
|
8736
|
+
const r = toResult(raw);
|
|
8475
8737
|
return r.images?.length ? r : r.text;
|
|
8476
8738
|
}
|
|
8477
8739
|
};
|