@livx.cc/agentx 0.99.40 → 0.99.42
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-D4-CLnxC.d.ts → Agent-DydgLoT-.d.ts} +5 -1
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +156 -17
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +17 -5
- package/dist/index.js +177 -56
- package/dist/index.js.map +1 -1
- package/dist/{mcp-aONOXRNk.d.ts → mcp-D5eN0JyQ.d.ts} +1 -1
- package/dist/mcp.client.d.ts +2 -2
- package/dist/models.d.ts +15 -1
- package/dist/models.js +7 -0
- package/dist/models.js.map +1 -1
- package/dist/{tools-BWzPQVy0.d.ts → tools-S-ArIR3o.d.ts} +14 -2
- package/dist/tools.shell.d.ts +14 -2
- package/dist/tools.shell.js +80 -2
- package/dist/tools.shell.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,6 +86,7 @@ Beyond file tools, the runtime ships the higher-altitude pieces too — each an
|
|
|
86
86
|
|
|
87
87
|
- **Skills + memory** — VFS-backed (`skillsDir`/`memoryDir`); persistence is just the backend choice.
|
|
88
88
|
- **Subagents** (`subagents`; **typed agents** via `agentsDir` — `<dir>/<name>.md` defines a persona + model + scoped tools, selected with the `Task` `agentType`), **hooks** (`hooks`: preToolUse/postToolUse/onStop — block or audit any tool call), **slash-commands** (`commandsDir`), **TodoWrite**, **MCP** (`mcpToolsToAgentTools`).
|
|
89
|
+
- **A delegated child is a full agent**: it inherits the parent's **tools** (incl. host-injected/MCP ones), **system prompt**, and **providerOptions** (only when it stays on the same provider — cursor's `cwd`/`cursorSession` never leak onto an anthropic child; override with `providerOptionsFor(model)`). `Task({ model })` (and a `TaskBatch` task's `model`) runs one sub-task on another model — alias or full id, precedence **explicit `model` > `agentType` def's `model` > parent's**. A def's frontmatter `tools:` allowlist narrows the inherited set and may name a host-injected tool by name; its body still overrides the persona. The host's `preToolUse` gate is inherited too, so delegation is not a way around it (the observer/lifecycle hooks stay parent-only). Inherited tools that escape the VFS are re-bound per child (`AgentTool.withCwd`): a `isolation:"worktree"` child's real `Shell` runs in the WORKTREE, keeping any host rename (`Shell` -> `Bash`) (background shell jobs are off there, and the parent-bound `ShellOutput`/`ShellStatus`/`ShellKill` are dropped rather than pointed at the parent's jobs); an overlay child's shell writes straight to the working tree — an overlay isolates the VFS, not a process.
|
|
89
90
|
- **Streaming** (`stream: true` → `text_delta` via `HostBridge.notify`) and **context compaction** (`compaction: { maxMessages }` → edge-safe summarize-and-boundary). Defaults preserve the original non-stream, drop-oldest behavior.
|
|
90
91
|
- **Multi-turn + project context** — `Agent.send()` continues a conversation across turns (vs `run()`, which starts fresh); **project instructions** (`instructionFiles`: `AGENTS.md`/`CLAUDE.md` at the FS root) inject into the system prompt.
|
|
91
92
|
- **DuplexAgent** (`src/duplex.ts`) — voice-optimized three-tier engine (reflex/act/think): a fast reflex agent streams instant replies and self-selects escalation — `Act` for standard tool work (Sonnet-class), `Think` for deep reasoning (Opus-class, configurable, default on). Results are pushed back and re-voiced by the reflex (turn mutex, coalesced completions, `TaskStatus`/`CancelTask`). See [`mind/10`](./mind/10-duplex.md).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IFilesystem } from '@livx.cc/wcli/core';
|
|
2
|
-
import { M as Message, H as HostBridge, A as AgentTool, C as ChatLike, g as MessageContent, U as UserQuestion } from './tools-
|
|
2
|
+
import { M as Message, H as HostBridge, A as AgentTool, C as ChatLike, g as MessageContent, U as UserQuestion } from './tools-S-ArIR3o.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Hooks — deterministic interception points around tool execution, run by the
|
|
@@ -375,6 +375,10 @@ declare class AgentOptions {
|
|
|
375
375
|
};
|
|
376
376
|
/** Provider-specific options forwarded to ai.chat() (e.g. cursor mcpServers, cwd). */
|
|
377
377
|
providerOptions?: Record<string, unknown>;
|
|
378
|
+
/** Derive `providerOptions` for a DELEGATED child whose model may differ from this agent's
|
|
379
|
+
* (`Task({ model })` / an agentType def). Default: inherit `providerOptions` only when the child's
|
|
380
|
+
* model is on the SAME provider — cursor's `cwd`/`cursorSession` on an anthropic child is a 400. */
|
|
381
|
+
providerOptionsFor?: (model: string) => Record<string, unknown> | undefined;
|
|
378
382
|
/** Prompt caching (providers that support it, e.g. Anthropic): cache tools/system/conversation
|
|
379
383
|
* prefix across the loop's steps — reads cost 0.1x, writes 1.25x. A multi-step agent loop
|
|
380
384
|
* re-sends its whole prefix every step, so this is a large net cost cut. Default on. */
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { H as Hooks, i as RunResult, R as ReasoningEffort, A as Agent } from './Agent-
|
|
2
|
+
import { H as Hooks, i as RunResult, R as ReasoningEffort, A as Agent } from './Agent-DydgLoT-.js';
|
|
3
3
|
import { IFilesystem } from '@livx.cc/wcli/core';
|
|
4
|
-
import { M as Message, U as UserQuestion, H as HostBridge, c as ContentPart, g as MessageContent } from './tools-
|
|
4
|
+
import { M as Message, U as UserQuestion, H as HostBridge, c as ContentPart, g as MessageContent } from './tools-S-ArIR3o.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* On-disk session store for the CLI: each conversation is one JSON file at
|
package/dist/cli.js
CHANGED
|
@@ -1361,11 +1361,12 @@ function toolRegistry() {
|
|
|
1361
1361
|
const all = [bashTool, readTool, editTool, grepTool, globTool, writeTool, multiEditTool, applyEditsTool, repoMapTool, reviewTool(), todoWriteTool, webFetchTool, webSearchTool, webSearchAnthropicTool];
|
|
1362
1362
|
return Object.fromEntries(all.map((t) => [t.name, t]));
|
|
1363
1363
|
}
|
|
1364
|
-
function toolsByName(names) {
|
|
1364
|
+
function toolsByName(names, available) {
|
|
1365
1365
|
const reg = toolRegistry();
|
|
1366
|
+
const extra = Object.fromEntries((available ?? []).map((t) => [t.name, t]));
|
|
1366
1367
|
return names.map((n) => {
|
|
1367
|
-
const t = reg[n];
|
|
1368
|
-
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(", ")}`);
|
|
1369
1370
|
return t;
|
|
1370
1371
|
});
|
|
1371
1372
|
}
|
|
@@ -2159,8 +2160,26 @@ function makeRealShellTool(options) {
|
|
|
2159
2160
|
};
|
|
2160
2161
|
return {
|
|
2161
2162
|
name: "Shell",
|
|
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
|
+
},
|
|
2162
2181
|
get description() {
|
|
2163
|
-
return
|
|
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. " + (options.registry?.adopt ? `A command still running after ${defaultTimeoutMs}ms is NOT killed \u2014 it is handed to a background job and you get its id, so you can keep checking on it (\`[still running]\`). Pass \`timeoutMs\` (max ${maxTimeoutMs}) to wait longer in the foreground, and always bound network commands yourself (e.g. \`curl -m 10\`). ` : `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();
|
|
2164
2183
|
},
|
|
2165
2184
|
// Declared so transports above the agent loop can size their own deadline with headroom (see AgentTool.maxDurationMs).
|
|
2166
2185
|
maxDurationMs: maxTimeoutMs,
|
|
@@ -2199,9 +2218,12 @@ function makeRealShellTool(options) {
|
|
|
2199
2218
|
else ctx.signal.addEventListener("abort", onAbort, { once: true });
|
|
2200
2219
|
}
|
|
2201
2220
|
let timedOut = false;
|
|
2202
|
-
|
|
2221
|
+
let onDeadline = () => {
|
|
2203
2222
|
timedOut = true;
|
|
2204
2223
|
ctl.abort();
|
|
2224
|
+
};
|
|
2225
|
+
const timer = setTimeout(() => {
|
|
2226
|
+
onDeadline();
|
|
2205
2227
|
}, timeoutMs);
|
|
2206
2228
|
let pend = "";
|
|
2207
2229
|
let flushTimer = null;
|
|
@@ -2232,6 +2254,18 @@ function makeRealShellTool(options) {
|
|
|
2232
2254
|
}
|
|
2233
2255
|
if (ctl.signal.aborted) killGroup(proc, "SIGKILL");
|
|
2234
2256
|
else ctl.signal.addEventListener("abort", () => killGroup(proc, "SIGKILL"), { once: true });
|
|
2257
|
+
const registry = options.registry;
|
|
2258
|
+
if (registry?.adopt) {
|
|
2259
|
+
onDeadline = () => {
|
|
2260
|
+
timedOut = true;
|
|
2261
|
+
if (settled) return;
|
|
2262
|
+
flushEmit(ctx);
|
|
2263
|
+
proc.stdout?.off?.("data", collect);
|
|
2264
|
+
proc.stderr?.off?.("data", collect);
|
|
2265
|
+
const id = registry.adopt(cmd, proc, out);
|
|
2266
|
+
finish(handoffFor(timeoutMs, id, clean(out), !!registry.notifiesOnExit));
|
|
2267
|
+
};
|
|
2268
|
+
}
|
|
2235
2269
|
const collect = (chunk) => {
|
|
2236
2270
|
const s = typeof chunk === "string" ? chunk : chunk?.toString?.("utf8") ?? "";
|
|
2237
2271
|
out += s;
|
|
@@ -2264,6 +2298,12 @@ function makeRealShellTool(options) {
|
|
|
2264
2298
|
}
|
|
2265
2299
|
};
|
|
2266
2300
|
}
|
|
2301
|
+
function handoffFor(timeoutMs, id, body, notifies) {
|
|
2302
|
+
const head = `[still running] exceeded ${timeoutMs}ms, so it was handed to background job ${id} \u2014 NOT killed, it is still going. Check on it with ShellOutput({id:"${id}"}) / ShellStatus({id:"${id}"}), stop it with ShellKill({id:"${id}"}). ` + (notifies ? "Its completion will be reported to you, so you may continue with other work meanwhile." : "Nothing will tell you when it finishes \u2014 poll it yourself before you rely on its result.");
|
|
2303
|
+
return body ? `${head}
|
|
2304
|
+
Output so far:
|
|
2305
|
+
${body}` : head;
|
|
2306
|
+
}
|
|
2267
2307
|
function reasonFor(timedOut, timeoutMs, body) {
|
|
2268
2308
|
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)";
|
|
2269
2309
|
return body ? `${head}
|
|
@@ -2272,8 +2312,10 @@ ${body}` : head;
|
|
|
2272
2312
|
}
|
|
2273
2313
|
function makeShellJobTools(registry) {
|
|
2274
2314
|
const idParam = { type: "object", properties: { id: { type: "string", description: "the job id from Shell({background:true})" } } };
|
|
2315
|
+
const dropOnRebind = { withCwd: () => void 0 };
|
|
2275
2316
|
return [
|
|
2276
2317
|
{
|
|
2318
|
+
...dropOnRebind,
|
|
2277
2319
|
name: "ShellOutput",
|
|
2278
2320
|
description: "Read the accumulated output (tail) of a background Shell job by id.",
|
|
2279
2321
|
parameters: { type: "object", required: ["id"], properties: { id: { type: "string" } } },
|
|
@@ -2286,6 +2328,7 @@ ${clean(out) || "(no output yet)"}`;
|
|
|
2286
2328
|
}
|
|
2287
2329
|
},
|
|
2288
2330
|
{
|
|
2331
|
+
...dropOnRebind,
|
|
2289
2332
|
name: "ShellStatus",
|
|
2290
2333
|
description: "Status of a background Shell job (running/exited/killed + exit code). Omit `id` to list all jobs.",
|
|
2291
2334
|
parameters: idParam,
|
|
@@ -2299,6 +2342,7 @@ ${clean(out) || "(no output yet)"}`;
|
|
|
2299
2342
|
}
|
|
2300
2343
|
},
|
|
2301
2344
|
{
|
|
2345
|
+
...dropOnRebind,
|
|
2302
2346
|
name: "ShellKill",
|
|
2303
2347
|
description: "Stop a running background Shell job by id (SIGTERM).",
|
|
2304
2348
|
parameters: { type: "object", required: ["id"], properties: { id: { type: "string" } } },
|
|
@@ -2399,6 +2443,41 @@ var init_tools_shell = __esm({
|
|
|
2399
2443
|
list() {
|
|
2400
2444
|
return [...this.jobs].map(([id, j]) => ({ id, command: j.command, status: j.status }));
|
|
2401
2445
|
}
|
|
2446
|
+
/**
|
|
2447
|
+
* Take over an ALREADY-RUNNING child as a background job. A foreground command that outruns its
|
|
2448
|
+
* timeout is not necessarily a hung command — killing it throws away work that was nearly done and,
|
|
2449
|
+
* worse, a launcher that spawned its own detached worker leaves that worker running with nothing
|
|
2450
|
+
* tracking it. Adopting hands the model a handle instead: the command keeps going, its completion
|
|
2451
|
+
* is reported like any other job, and `seed` carries the output produced before the handoff.
|
|
2452
|
+
*/
|
|
2453
|
+
adopt(command, proc, seed = "") {
|
|
2454
|
+
const id = `job-${++this.seq}`;
|
|
2455
|
+
const max = this.cfg.maxBuffer ?? 256 * 1024;
|
|
2456
|
+
const job = { command, buf: seed.slice(-max), status: "running", proc };
|
|
2457
|
+
const append = (chunk) => {
|
|
2458
|
+
const s = typeof chunk === "string" ? chunk : chunk?.toString?.("utf8") ?? "";
|
|
2459
|
+
job.buf = (job.buf + s).slice(-max);
|
|
2460
|
+
};
|
|
2461
|
+
proc.stdout?.on("data", append);
|
|
2462
|
+
proc.stderr?.on("data", append);
|
|
2463
|
+
proc.on("error", (err2) => {
|
|
2464
|
+
if (job.status === "running") {
|
|
2465
|
+
job.status = "error";
|
|
2466
|
+
append(`
|
|
2467
|
+
[error] ${err2?.message ?? err2}`);
|
|
2468
|
+
this.notifyExit(id, job);
|
|
2469
|
+
}
|
|
2470
|
+
});
|
|
2471
|
+
proc.on("close", (code) => {
|
|
2472
|
+
if (job.status === "running") {
|
|
2473
|
+
job.status = "exited";
|
|
2474
|
+
job.exitCode = code ?? void 0;
|
|
2475
|
+
this.notifyExit(id, job);
|
|
2476
|
+
}
|
|
2477
|
+
});
|
|
2478
|
+
this.jobs.set(id, job);
|
|
2479
|
+
return id;
|
|
2480
|
+
}
|
|
2402
2481
|
kill(id) {
|
|
2403
2482
|
const j = this.jobs.get(id);
|
|
2404
2483
|
if (!j) return false;
|
|
@@ -3131,6 +3210,32 @@ ${sections.join("\n\n---\n\n")}`;
|
|
|
3131
3210
|
init_tools();
|
|
3132
3211
|
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
3133
3212
|
import { join as join4 } from "path";
|
|
3213
|
+
|
|
3214
|
+
// src/models.ts
|
|
3215
|
+
var MODEL_ALIASES = {
|
|
3216
|
+
fable: "claude-fable-5",
|
|
3217
|
+
"fable-5": "claude-fable-5",
|
|
3218
|
+
opus: "claude-opus-4-8",
|
|
3219
|
+
"opus-4-8": "claude-opus-4-8",
|
|
3220
|
+
"opus-4-7": "claude-opus-4-7",
|
|
3221
|
+
"opus-4-6": "claude-opus-4-6",
|
|
3222
|
+
sonnet: "claude-sonnet-4-6",
|
|
3223
|
+
haiku: "claude-haiku-4-5-20251001"
|
|
3224
|
+
};
|
|
3225
|
+
function resolveModelAlias(input) {
|
|
3226
|
+
const slash = input.indexOf("/");
|
|
3227
|
+
const prefix = slash === -1 ? "" : input.slice(0, slash + 1);
|
|
3228
|
+
const rest = slash === -1 ? input : input.slice(slash + 1);
|
|
3229
|
+
return prefix + (MODEL_ALIASES[rest.toLowerCase()] ?? rest);
|
|
3230
|
+
}
|
|
3231
|
+
function modelProvider(model) {
|
|
3232
|
+
const i = model.indexOf("/");
|
|
3233
|
+
if (i !== -1) return model.slice(0, i);
|
|
3234
|
+
if (/^claude-/i.test(model)) return "anthropic";
|
|
3235
|
+
return `?${model}`;
|
|
3236
|
+
}
|
|
3237
|
+
|
|
3238
|
+
// src/subagent.ts
|
|
3134
3239
|
init_OverlayFilesystem();
|
|
3135
3240
|
init_worktree();
|
|
3136
3241
|
init_NodeDiskFilesystem();
|
|
@@ -3189,6 +3294,14 @@ function createWorktreeFs(cwd, label, index) {
|
|
|
3189
3294
|
function releaseWorktree(handle) {
|
|
3190
3295
|
return cleanupWorktree(handle.repoRoot, handle.slug, { force: true });
|
|
3191
3296
|
}
|
|
3297
|
+
function rebindTools(tools, root) {
|
|
3298
|
+
if (!tools || !root) return tools;
|
|
3299
|
+
return tools.flatMap((t) => {
|
|
3300
|
+
if (!t.withCwd) return [t];
|
|
3301
|
+
const rebound = t.withCwd(root);
|
|
3302
|
+
return rebound ? [rebound] : [];
|
|
3303
|
+
});
|
|
3304
|
+
}
|
|
3192
3305
|
function worktreePromptPrefix(branch) {
|
|
3193
3306
|
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).
|
|
3194
3307
|
|
|
@@ -3256,22 +3369,30 @@ async function runChildTraced(args) {
|
|
|
3256
3369
|
await opts.hooks?.onSubagentStop?.(result, { label, agentType });
|
|
3257
3370
|
return { res, result };
|
|
3258
3371
|
}
|
|
3259
|
-
function childOptionsFor(opts, fs, depth, maxDepth, agentType) {
|
|
3372
|
+
function childOptionsFor(opts, fs, depth, maxDepth, agentType, model, root) {
|
|
3260
3373
|
let def;
|
|
3261
3374
|
if (agentType) {
|
|
3262
3375
|
def = (opts.agents ?? []).find((a) => a.name === agentType);
|
|
3263
3376
|
if (!def) return `no subagent type '${agentType}'. Available: ${(opts.agents ?? []).map((a) => a.name).join(", ") || "(none defined)"}`;
|
|
3264
3377
|
}
|
|
3265
|
-
const
|
|
3378
|
+
const childModel = resolveModelAlias(model || def?.model || opts.model);
|
|
3379
|
+
const childOpts = { ai: opts.ai, fs, model: childModel, subagents: true, depth: depth + 1, maxDepth, traceDir: opts.traceDir };
|
|
3266
3380
|
if (opts.maxSteps != null) childOpts.maxSteps = opts.maxSteps;
|
|
3267
|
-
|
|
3381
|
+
const childPrompt = def?.systemPrompt ?? opts.systemPrompt;
|
|
3382
|
+
if (childPrompt) childOpts.systemPrompt = childPrompt;
|
|
3383
|
+
const inherited = rebindTools(opts.tools, root);
|
|
3268
3384
|
if (def?.tools?.length) {
|
|
3269
3385
|
try {
|
|
3270
|
-
childOpts.tools = toolsByName(def.tools);
|
|
3386
|
+
childOpts.tools = toolsByName(def.tools, inherited);
|
|
3271
3387
|
} catch (e) {
|
|
3272
3388
|
return `subagent '${agentType}' declares an unknown tool \u2014 ${e.message}`;
|
|
3273
3389
|
}
|
|
3390
|
+
} else if (inherited?.length) {
|
|
3391
|
+
childOpts.tools = inherited;
|
|
3274
3392
|
}
|
|
3393
|
+
const po = opts.providerOptionsFor?.(childModel);
|
|
3394
|
+
if (po) childOpts.providerOptions = po;
|
|
3395
|
+
if (opts.hooks?.preToolUse) childOpts.hooks = { preToolUse: opts.hooks.preToolUse.bind(opts.hooks) };
|
|
3275
3396
|
return childOpts;
|
|
3276
3397
|
}
|
|
3277
3398
|
function makeTaskTool(opts) {
|
|
@@ -3279,7 +3400,7 @@ function makeTaskTool(opts) {
|
|
|
3279
3400
|
const maxDepth = opts.maxDepth ?? 2;
|
|
3280
3401
|
return {
|
|
3281
3402
|
name: "Task",
|
|
3282
|
-
description: 'Delegate a self-contained sub-task to a child agent over the same filesystem. It runs autonomously with its own step budget and a wall-clock deadline, and returns a concise summary \u2014 use to isolate context-heavy work (broad search, a scoped refactor). Provide a short `description` and a full `prompt`. If it is interrupted or fails, the result is a STATUS HINT with a pointer to its full transcript (Read it to recover what it did, then decide whether to continue or restart) \u2014 do not assume an incomplete sub-task succeeded. Set `background: true` to run it detached (returns a job id to poll with JobOutput while you keep working); its file edits are overlay-isolated and commit when it finishes
|
|
3403
|
+
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.',
|
|
3283
3404
|
parameters: {
|
|
3284
3405
|
type: "object",
|
|
3285
3406
|
required: ["description", "prompt"],
|
|
@@ -3287,6 +3408,7 @@ function makeTaskTool(opts) {
|
|
|
3287
3408
|
description: { type: "string", description: "a short (3-5 word) label for the sub-task" },
|
|
3288
3409
|
prompt: { type: "string", description: "the full instructions the child agent should carry out" },
|
|
3289
3410
|
agentType: { type: "string", description: "optional named subagent type (its persona, model, and scoped tools) \u2014 see the catalog" },
|
|
3411
|
+
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.' },
|
|
3290
3412
|
background: { type: "boolean", description: "run detached (overlay-isolated, commits on finish); returns a job id to poll with JobOutput instead of blocking" },
|
|
3291
3413
|
isolation: { type: "string", enum: ["overlay", "worktree"], description: 'isolation mode: "overlay" (default, VFS layer) or "worktree" (real git worktree for shell-heavy work)' }
|
|
3292
3414
|
}
|
|
@@ -3297,7 +3419,7 @@ function makeTaskTool(opts) {
|
|
|
3297
3419
|
maxDurationMs: opts.maxDurationMs ?? SUBAGENT_MAX_DURATION_MS,
|
|
3298
3420
|
maxDurationEnforced: true,
|
|
3299
3421
|
// the wall clock below ABORTS the child at this bound — it cannot be overrun, so the transport needs slack, not a doubling
|
|
3300
|
-
async run({ description, prompt, agentType, background, isolation }, ctx) {
|
|
3422
|
+
async run({ description, prompt, agentType, model, background, isolation }, ctx) {
|
|
3301
3423
|
if (depth >= maxDepth) {
|
|
3302
3424
|
return `Error: Task depth limit reached (maxDepth ${maxDepth}). Cannot spawn another child agent \u2014 do this work directly instead.`;
|
|
3303
3425
|
}
|
|
@@ -3320,7 +3442,7 @@ function makeTaskTool(opts) {
|
|
|
3320
3442
|
fs2 = overlay;
|
|
3321
3443
|
commit = () => overlay.commit();
|
|
3322
3444
|
}
|
|
3323
|
-
const childOpts2 = childOptionsFor(opts, fs2, depth, maxDepth, agentType);
|
|
3445
|
+
const childOpts2 = childOptionsFor(opts, fs2, depth, maxDepth, agentType, model, wtHandle2?.worktreePath);
|
|
3324
3446
|
if (typeof childOpts2 === "string") throw new Error(childOpts2);
|
|
3325
3447
|
const childPrompt = wtHandle2 ? worktreePromptPrefix(wtHandle2.branch) + String(prompt ?? "") : String(prompt ?? "");
|
|
3326
3448
|
const { res, result } = await runChildTraced({ opts, childOpts: childOpts2, prompt: childPrompt, label, agentType, childDepth: depth + 1, signal });
|
|
@@ -3350,7 +3472,7 @@ function makeTaskTool(opts) {
|
|
|
3350
3472
|
} else {
|
|
3351
3473
|
fs = opts.fs;
|
|
3352
3474
|
}
|
|
3353
|
-
const childOpts = childOptionsFor(opts, fs, depth, maxDepth, agentType);
|
|
3475
|
+
const childOpts = childOptionsFor(opts, fs, depth, maxDepth, agentType, model, wtHandle?.worktreePath);
|
|
3354
3476
|
if (typeof childOpts === "string") {
|
|
3355
3477
|
wtHandle && releaseWorktree(wtHandle);
|
|
3356
3478
|
return `Error: ${childOpts}`;
|
|
@@ -3379,7 +3501,7 @@ function makeTaskBatchTool(opts) {
|
|
|
3379
3501
|
const maxParallel = opts.maxParallel ?? 4;
|
|
3380
3502
|
return {
|
|
3381
3503
|
name: "TaskBatch",
|
|
3382
|
-
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? }]
|
|
3504
|
+
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.',
|
|
3383
3505
|
parameters: {
|
|
3384
3506
|
type: "object",
|
|
3385
3507
|
required: ["tasks"],
|
|
@@ -3393,7 +3515,8 @@ function makeTaskBatchTool(opts) {
|
|
|
3393
3515
|
properties: {
|
|
3394
3516
|
description: { type: "string", description: "a short (3-5 word) label" },
|
|
3395
3517
|
prompt: { type: "string", description: "the full instructions for this child" },
|
|
3396
|
-
agentType: { type: "string", description: "optional named subagent type" }
|
|
3518
|
+
agentType: { type: "string", description: "optional named subagent type" },
|
|
3519
|
+
model: { type: "string", description: "optional model for this child (alias or full id); overrides the agentType default" }
|
|
3397
3520
|
}
|
|
3398
3521
|
}
|
|
3399
3522
|
},
|
|
@@ -3426,7 +3549,7 @@ function makeTaskBatchTool(opts) {
|
|
|
3426
3549
|
overlay = new OverlayFilesystem(opts.fs);
|
|
3427
3550
|
fs = overlay;
|
|
3428
3551
|
}
|
|
3429
|
-
const childOpts = childOptionsFor(opts, fs, depth, maxDepth, t?.agentType);
|
|
3552
|
+
const childOpts = childOptionsFor(opts, fs, depth, maxDepth, t?.agentType, t?.model, wtHandle?.worktreePath);
|
|
3430
3553
|
if (typeof childOpts === "string") return { label, error: childOpts, ok: false };
|
|
3431
3554
|
try {
|
|
3432
3555
|
const childPrompt = wtHandle ? worktreePromptPrefix(wtHandle.branch) + String(t?.prompt ?? "") : String(t?.prompt ?? "");
|
|
@@ -3853,6 +3976,10 @@ var AgentOptions = class {
|
|
|
3853
3976
|
autoTest;
|
|
3854
3977
|
/** Provider-specific options forwarded to ai.chat() (e.g. cursor mcpServers, cwd). */
|
|
3855
3978
|
providerOptions;
|
|
3979
|
+
/** Derive `providerOptions` for a DELEGATED child whose model may differ from this agent's
|
|
3980
|
+
* (`Task({ model })` / an agentType def). Default: inherit `providerOptions` only when the child's
|
|
3981
|
+
* model is on the SAME provider — cursor's `cwd`/`cursorSession` on an anthropic child is a 400. */
|
|
3982
|
+
providerOptionsFor;
|
|
3856
3983
|
/** Prompt caching (providers that support it, e.g. Anthropic): cache tools/system/conversation
|
|
3857
3984
|
* prefix across the loop's steps — reads cost 0.1x, writes 1.25x. A multi-step agent loop
|
|
3858
3985
|
* re-sends its whole prefix every step, so this is a large net cost cut. Default on. */
|
|
@@ -4076,7 +4203,19 @@ var Agent = class _Agent {
|
|
|
4076
4203
|
agents = loaded.agents;
|
|
4077
4204
|
if (loaded.catalog) systemPrompt += "\n\n" + loaded.catalog;
|
|
4078
4205
|
}
|
|
4079
|
-
const taskOpts = {
|
|
4206
|
+
const taskOpts = {
|
|
4207
|
+
ai: o.ai,
|
|
4208
|
+
model: o.model,
|
|
4209
|
+
fs,
|
|
4210
|
+
depth: o.depth,
|
|
4211
|
+
maxDepth: o.maxDepth,
|
|
4212
|
+
agents,
|
|
4213
|
+
hooks: o.hooks,
|
|
4214
|
+
traceDir: o.traceDir,
|
|
4215
|
+
tools: [...o.tools, ...this.injectedTools],
|
|
4216
|
+
systemPrompt: o.systemPrompt,
|
|
4217
|
+
providerOptionsFor: o.providerOptionsFor ?? ((m) => modelProvider(m) === modelProvider(o.model) ? o.providerOptions : void 0)
|
|
4218
|
+
};
|
|
4080
4219
|
tools = [...tools, makeTaskTool(taskOpts), makeTaskBatchTool(taskOpts)];
|
|
4081
4220
|
}
|
|
4082
4221
|
if (o.checkpoints) tools = [...tools, ...checkpointTools()];
|