@livx.cc/agentx 0.99.39 → 0.99.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{Agent-DJwg1g8V.d.ts → Agent-D4-CLnxC.d.ts} +1 -1
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +231 -29
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +33 -5
- package/dist/index.js +226 -29
- package/dist/index.js.map +1 -1
- package/dist/{mcp-Bd19NiCL.d.ts → mcp-aONOXRNk.d.ts} +4 -2
- package/dist/mcp.client.d.ts +2 -2
- package/dist/mcp.client.js +23 -3
- package/dist/mcp.client.js.map +1 -1
- package/dist/{tools-Byyh_bae.d.ts → tools-BWzPQVy0.d.ts} +15 -0
- package/dist/tools.shell.d.ts +36 -2
- package/dist/tools.shell.js +46 -2
- package/dist/tools.shell.js.map +1 -1
- package/package.json +2 -2
|
@@ -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-BWzPQVy0.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Hooks — deterministic interception points around tool execution, run by the
|
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-D4-CLnxC.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-BWzPQVy0.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
|
@@ -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;
|
|
@@ -2105,6 +2112,7 @@ var init_shell_sandbox = __esm({
|
|
|
2105
2112
|
var tools_shell_exports = {};
|
|
2106
2113
|
__export(tools_shell_exports, {
|
|
2107
2114
|
ShellJobRegistry: () => ShellJobRegistry,
|
|
2115
|
+
formatJobExit: () => formatJobExit,
|
|
2108
2116
|
makeRealShellTool: () => makeRealShellTool,
|
|
2109
2117
|
makeShellJobTools: () => makeShellJobTools
|
|
2110
2118
|
});
|
|
@@ -2135,12 +2143,27 @@ async function nodeSpawn() {
|
|
|
2135
2143
|
if (!_spawn) _spawn = (await import("child_process")).spawn;
|
|
2136
2144
|
return _spawn;
|
|
2137
2145
|
}
|
|
2146
|
+
function formatJobExit(n) {
|
|
2147
|
+
return `[background job ${n.id} ${n.status}${n.exitCode != null ? ` exit ${n.exitCode}` : ""}] \`${n.command}\`
|
|
2148
|
+
` + (n.tail ? `${n.tail}
|
|
2149
|
+
` : "(no output)\n") + `Read the full output with ShellOutput({id:"${n.id}"}).`;
|
|
2150
|
+
}
|
|
2138
2151
|
function makeRealShellTool(options) {
|
|
2139
2152
|
const defaultTimeoutMs = options.timeoutMs ?? 12e4;
|
|
2140
2153
|
const maxTimeoutMs = Math.max(options.maxTimeoutMs ?? 6e5, defaultTimeoutMs);
|
|
2154
|
+
const backgroundDoc = () => {
|
|
2155
|
+
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. ";
|
|
2156
|
+
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.";
|
|
2157
|
+
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.";
|
|
2158
|
+
return `${base}${notify} ${survival}`;
|
|
2159
|
+
};
|
|
2141
2160
|
return {
|
|
2142
2161
|
name: "Shell",
|
|
2143
|
-
description
|
|
2162
|
+
get description() {
|
|
2163
|
+
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();
|
|
2164
|
+
},
|
|
2165
|
+
// Declared so transports above the agent loop can size their own deadline with headroom (see AgentTool.maxDurationMs).
|
|
2166
|
+
maxDurationMs: maxTimeoutMs,
|
|
2144
2167
|
parameters: {
|
|
2145
2168
|
type: "object",
|
|
2146
2169
|
required: ["command"],
|
|
@@ -2242,8 +2265,9 @@ function makeRealShellTool(options) {
|
|
|
2242
2265
|
};
|
|
2243
2266
|
}
|
|
2244
2267
|
function reasonFor(timedOut, timeoutMs, body) {
|
|
2245
|
-
const head = timedOut ? `[exit 124] timed out after ${timeoutMs}ms (killed)
|
|
2268
|
+
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
2269
|
return body ? `${head}
|
|
2270
|
+
Partial output before the kill:
|
|
2247
2271
|
${body}` : head;
|
|
2248
2272
|
}
|
|
2249
2273
|
function makeShellJobTools(registry) {
|
|
@@ -2324,12 +2348,14 @@ var init_tools_shell = __esm({
|
|
|
2324
2348
|
job.status = "error";
|
|
2325
2349
|
append(`
|
|
2326
2350
|
[error] ${err2?.message ?? err2}`);
|
|
2351
|
+
this.notifyExit(id, job);
|
|
2327
2352
|
}
|
|
2328
2353
|
});
|
|
2329
2354
|
proc.on("close", (code) => {
|
|
2330
2355
|
if (job.status === "running") {
|
|
2331
2356
|
job.status = "exited";
|
|
2332
2357
|
job.exitCode = code ?? void 0;
|
|
2358
|
+
this.notifyExit(id, job);
|
|
2333
2359
|
}
|
|
2334
2360
|
});
|
|
2335
2361
|
} catch (e) {
|
|
@@ -2339,6 +2365,29 @@ var init_tools_shell = __esm({
|
|
|
2339
2365
|
this.jobs.set(id, job);
|
|
2340
2366
|
return id;
|
|
2341
2367
|
}
|
|
2368
|
+
/** Fire `onExit` at most once per job, with the tail so the model can act without a second round-trip. */
|
|
2369
|
+
notified = /* @__PURE__ */ new Set();
|
|
2370
|
+
notifyExit(id, job) {
|
|
2371
|
+
if (this.notified.has(id) || !this.cfg.onExit) return;
|
|
2372
|
+
this.notified.add(id);
|
|
2373
|
+
try {
|
|
2374
|
+
this.cfg.onExit({ id, command: job.command, status: job.status, exitCode: job.exitCode, tail: clean(job.buf).slice(-4e3) });
|
|
2375
|
+
} catch {
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
/**
|
|
2379
|
+
* Wire (or rewire) the completion callback AFTER construction. A host that only gets its agent handle
|
|
2380
|
+
* once the Agent is constructed (the library's own `fullAgentOptions` preset, any embedder) could not
|
|
2381
|
+
* pass `onExit` up front, so background completions were silently REPL-only. Set it here instead.
|
|
2382
|
+
*/
|
|
2383
|
+
setOnExit(fn) {
|
|
2384
|
+
this.cfg.onExit = fn;
|
|
2385
|
+
}
|
|
2386
|
+
/** Whether a finished job will actually be reported to the model. The Shell tool's description reads
|
|
2387
|
+
* this so it can't promise a completion notice on a host that discards it. */
|
|
2388
|
+
get notifiesOnExit() {
|
|
2389
|
+
return !!this.cfg.onExit;
|
|
2390
|
+
}
|
|
2342
2391
|
/** Current tail output for a job (null = no such job). */
|
|
2343
2392
|
output(id) {
|
|
2344
2393
|
return this.jobs.get(id)?.buf ?? (this.jobs.has(id) ? "" : null);
|
|
@@ -3099,6 +3148,30 @@ async function boundedPool(items, limit, fn) {
|
|
|
3099
3148
|
await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, worker));
|
|
3100
3149
|
return out;
|
|
3101
3150
|
}
|
|
3151
|
+
var SUBAGENT_MAX_DURATION_MS = 18e5;
|
|
3152
|
+
function wallClock(parent, ms) {
|
|
3153
|
+
const ac = new AbortController();
|
|
3154
|
+
let fired = false;
|
|
3155
|
+
const onParent = () => ac.abort();
|
|
3156
|
+
const timer = setTimeout(() => {
|
|
3157
|
+
fired = true;
|
|
3158
|
+
ac.abort();
|
|
3159
|
+
}, ms);
|
|
3160
|
+
if (parent) {
|
|
3161
|
+
if (parent.aborted) ac.abort();
|
|
3162
|
+
else parent.addEventListener("abort", onParent, { once: true });
|
|
3163
|
+
}
|
|
3164
|
+
return {
|
|
3165
|
+
signal: ac.signal,
|
|
3166
|
+
timedOut: () => fired,
|
|
3167
|
+
dispose: () => {
|
|
3168
|
+
clearTimeout(timer);
|
|
3169
|
+
parent?.removeEventListener("abort", onParent);
|
|
3170
|
+
}
|
|
3171
|
+
};
|
|
3172
|
+
}
|
|
3173
|
+
var deadlineNote = (ms) => `
|
|
3174
|
+
[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
3175
|
function sanitizeSlug(raw) {
|
|
3103
3176
|
return raw.replace(/[^a-zA-Z0-9._-]/g, "-").replace(/-+/g, "-").slice(0, 40);
|
|
3104
3177
|
}
|
|
@@ -3161,14 +3234,25 @@ function abortHint(label, res, file) {
|
|
|
3161
3234
|
}
|
|
3162
3235
|
async function runChildTraced(args) {
|
|
3163
3236
|
const { opts, label, agentType, prompt } = args;
|
|
3164
|
-
|
|
3237
|
+
const deadlineMs = opts.maxDurationMs ?? SUBAGENT_MAX_DURATION_MS;
|
|
3238
|
+
const wc = wallClock(args.signal, deadlineMs);
|
|
3239
|
+
args.childOpts.signal = wc.signal;
|
|
3165
3240
|
const file = opts.traceDir ? traceFileFor(opts.traceDir, label, args.childDepth) : void 0;
|
|
3166
3241
|
const meta = { label, agentType, prompt };
|
|
3167
3242
|
const child = new Agent(args.childOpts);
|
|
3168
3243
|
if (file) installTrace(child, file, meta);
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3244
|
+
let res;
|
|
3245
|
+
try {
|
|
3246
|
+
res = await child.run(prompt);
|
|
3247
|
+
} finally {
|
|
3248
|
+
wc.dispose();
|
|
3249
|
+
}
|
|
3250
|
+
if (file) flushTrace(file, meta, child.transcript, wc.timedOut() ? "deadline" : res.finishReason);
|
|
3251
|
+
let result = res.finishReason === "stop" ? res.text || `(child '${label}' finished with no summary; finishReason=${res.finishReason})` : abortHint(label, res, file);
|
|
3252
|
+
if (wc.timedOut()) {
|
|
3253
|
+
log5.warn(`sub-task '${label}' hit its ${deadlineMs}ms wall-clock deadline (steps=${res.steps})`);
|
|
3254
|
+
result += deadlineNote(deadlineMs);
|
|
3255
|
+
}
|
|
3172
3256
|
await opts.hooks?.onSubagentStop?.(result, { label, agentType });
|
|
3173
3257
|
return { res, result };
|
|
3174
3258
|
}
|
|
@@ -3195,7 +3279,7 @@ function makeTaskTool(opts) {
|
|
|
3195
3279
|
const maxDepth = opts.maxDepth ?? 2;
|
|
3196
3280
|
return {
|
|
3197
3281
|
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. Set `isolation: "worktree"` to run in a real git worktree (needed when the child uses real shell commands that must see/modify actual files) \u2014 slower (~200ms setup), auto-cleaned if no changes.',
|
|
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. Set `isolation: "worktree"` to run in a real git worktree (needed when the child uses real shell commands that must see/modify actual files) \u2014 slower (~200ms setup), auto-cleaned if no changes.',
|
|
3199
3283
|
parameters: {
|
|
3200
3284
|
type: "object",
|
|
3201
3285
|
required: ["description", "prompt"],
|
|
@@ -3207,6 +3291,12 @@ function makeTaskTool(opts) {
|
|
|
3207
3291
|
isolation: { type: "string", enum: ["overlay", "worktree"], description: 'isolation mode: "overlay" (default, VFS layer) or "worktree" (real git worktree for shell-heavy work)' }
|
|
3208
3292
|
}
|
|
3209
3293
|
},
|
|
3294
|
+
// Declared because it is ENFORCED (see runChildTraced) — a transport above the loop sizes its own
|
|
3295
|
+
// deadline around this one (AgentTool.maxDurationMs). Declaring an unenforced number would be a lie;
|
|
3296
|
+
// declaring nothing left `Task` invisible to toolTimeoutViolations, which is the same hole.
|
|
3297
|
+
maxDurationMs: opts.maxDurationMs ?? SUBAGENT_MAX_DURATION_MS,
|
|
3298
|
+
maxDurationEnforced: true,
|
|
3299
|
+
// the wall clock below ABORTS the child at this bound — it cannot be overrun, so the transport needs slack, not a doubling
|
|
3210
3300
|
async run({ description, prompt, agentType, background, isolation }, ctx) {
|
|
3211
3301
|
if (depth >= maxDepth) {
|
|
3212
3302
|
return `Error: Task depth limit reached (maxDepth ${maxDepth}). Cannot spawn another child agent \u2014 do this work directly instead.`;
|
|
@@ -3310,11 +3400,18 @@ function makeTaskBatchTool(opts) {
|
|
|
3310
3400
|
isolation: { type: "string", enum: ["overlay", "worktree"], description: 'isolation mode for ALL children: "overlay" (default) or "worktree" (real git worktree each)' }
|
|
3311
3401
|
}
|
|
3312
3402
|
},
|
|
3403
|
+
// Same enforced bound as `Task`, applied to the WHOLE batch (below), not per child — otherwise the
|
|
3404
|
+
// honest ceiling would be ceil(n/maxParallel) x the per-child deadline, i.e. unbounded in `tasks`.
|
|
3405
|
+
maxDurationMs: opts.maxDurationMs ?? SUBAGENT_MAX_DURATION_MS,
|
|
3406
|
+
maxDurationEnforced: true,
|
|
3407
|
+
// the wall clock below ABORTS the child at this bound — it cannot be overrun, so the transport needs slack, not a doubling
|
|
3313
3408
|
async run({ tasks, isolation }, ctx) {
|
|
3314
3409
|
if (depth >= maxDepth) return `Error: Task depth limit reached (maxDepth ${maxDepth}). Cannot spawn child agents \u2014 do this work directly instead.`;
|
|
3315
3410
|
const list = Array.isArray(tasks) ? tasks : [];
|
|
3316
3411
|
if (!list.length) return "Error: TaskBatch needs a non-empty `tasks` array.";
|
|
3317
3412
|
const useWorktree = isolation === "worktree";
|
|
3413
|
+
const batchDeadlineMs = opts.maxDurationMs ?? SUBAGENT_MAX_DURATION_MS;
|
|
3414
|
+
const batchClock = wallClock(ctx.signal, batchDeadlineMs);
|
|
3318
3415
|
const results = await boundedPool(list, maxParallel, async (t, i) => {
|
|
3319
3416
|
const label = String(t?.description ?? t?.agentType ?? `task ${i + 1}`);
|
|
3320
3417
|
let fs;
|
|
@@ -3333,12 +3430,12 @@ function makeTaskBatchTool(opts) {
|
|
|
3333
3430
|
if (typeof childOpts === "string") return { label, error: childOpts, ok: false };
|
|
3334
3431
|
try {
|
|
3335
3432
|
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:
|
|
3433
|
+
const { res, result } = await runChildTraced({ opts, childOpts, prompt: childPrompt, label, agentType: t?.agentType, childDepth: depth + 1, signal: batchClock.signal });
|
|
3337
3434
|
return { label, text: result, overlay, wtHandle, ok: res.finishReason !== "error" && res.finishReason !== "aborted" };
|
|
3338
3435
|
} catch (e) {
|
|
3339
3436
|
return { label, error: e instanceof Error ? e.message : String(e), wtHandle, ok: false };
|
|
3340
3437
|
}
|
|
3341
|
-
});
|
|
3438
|
+
}).finally(() => batchClock.dispose());
|
|
3342
3439
|
const lines = [];
|
|
3343
3440
|
for (const r of results) {
|
|
3344
3441
|
if (r.ok && r.overlay) await r.overlay.commit().catch(() => {
|
|
@@ -3352,7 +3449,7 @@ function makeTaskBatchTool(opts) {
|
|
|
3352
3449
|
lines.push(`### ${lines.length + 1}. ${r.label}
|
|
3353
3450
|
${r.error ? `ERROR: ${r.error}` : r.text || "(no summary)"}${extra}`);
|
|
3354
3451
|
}
|
|
3355
|
-
return lines.join("\n\n");
|
|
3452
|
+
return lines.join("\n\n") + (batchClock.timedOut() ? deadlineNote(batchDeadlineMs) : "");
|
|
3356
3453
|
}
|
|
3357
3454
|
};
|
|
3358
3455
|
}
|
|
@@ -4116,7 +4213,17 @@ var Agent = class _Agent {
|
|
|
4116
4213
|
const frag = reasoningToChatFragment(o.model, o.reasoning);
|
|
4117
4214
|
const sentTools = o.model.startsWith("claude-code/") ? [] : wireTools;
|
|
4118
4215
|
const isCursorWithTools = o.model.startsWith("cursor/") && wireTools.length > 0;
|
|
4216
|
+
const hostToolTimeoutMs = o.providerOptions?.toolTimeoutMs;
|
|
4217
|
+
const derivedToolTimeoutMs = Math.max(TRANSPORT_TOOL_TIMEOUT_FLOOR_MS, ...this.activeTools.map(transportHeadroomMs));
|
|
4218
|
+
const effectiveToolTimeoutMs = typeof hostToolTimeoutMs === "number" ? Math.max(hostToolTimeoutMs, derivedToolTimeoutMs) : derivedToolTimeoutMs;
|
|
4219
|
+
if (isCursorWithTools) {
|
|
4220
|
+
if (typeof hostToolTimeoutMs === "number" && effectiveToolTimeoutMs !== hostToolTimeoutMs) {
|
|
4221
|
+
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.`);
|
|
4222
|
+
}
|
|
4223
|
+
for (const v of toolTimeoutViolations(this.activeTools, effectiveToolTimeoutMs)) log6.warn(v);
|
|
4224
|
+
}
|
|
4119
4225
|
const cursorPo = isCursorWithTools ? {
|
|
4226
|
+
toolTimeoutMs: effectiveToolTimeoutMs,
|
|
4120
4227
|
toolExecutor: async (name, args) => {
|
|
4121
4228
|
const tc = { id: `cursor-${Date.now()}`, type: "function", function: { name, arguments: JSON.stringify(args) } };
|
|
4122
4229
|
return await this.dispatch(tc);
|
|
@@ -4218,14 +4325,12 @@ var Agent = class _Agent {
|
|
|
4218
4325
|
})() } }))
|
|
4219
4326
|
});
|
|
4220
4327
|
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 });
|
|
4328
|
+
const name = a.name || "tool";
|
|
4329
|
+
const r = delegatedResult(a.output);
|
|
4330
|
+
const text = r.images.length ? r.text : nonEmptyResult(isVacuous(a.output) ? "" : r.text, name, deliveryOf(a.output, a.status));
|
|
4331
|
+
const body = cap > 0 && text.length > cap ? cropResult(text, cap) : text;
|
|
4332
|
+
const content = r.images.length ? [{ type: "text", text: body }, ...r.images.map((i) => imagePart(`data:${i.mimeType};base64,${i.data}`))] : body;
|
|
4333
|
+
this.transcript.push({ role: "tool", tool_call_id: a.id, content });
|
|
4229
4334
|
}
|
|
4230
4335
|
}
|
|
4231
4336
|
const toolCalls = res.toolCalls ?? [];
|
|
@@ -4278,9 +4383,10 @@ var Agent = class _Agent {
|
|
|
4278
4383
|
const raw = await this.dispatch(tc);
|
|
4279
4384
|
let content;
|
|
4280
4385
|
if (typeof raw === "string") {
|
|
4281
|
-
content = raw;
|
|
4386
|
+
content = nonEmptyResult(raw, tc.function.name);
|
|
4282
4387
|
} else {
|
|
4283
|
-
const
|
|
4388
|
+
const hasImages = !!raw.images?.length;
|
|
4389
|
+
const parts = [{ type: "text", text: hasImages ? raw.text : nonEmptyResult(raw.text ?? "", tc.function.name) }];
|
|
4284
4390
|
for (const img of raw.images ?? []) parts.push(imagePart(`data:${img.mimeType};base64,${img.data}`));
|
|
4285
4391
|
content = parts;
|
|
4286
4392
|
}
|
|
@@ -4589,6 +4695,76 @@ var isAnthropicModel = (m) => !!m && (m.startsWith("anthropic/") || /(^|\/)claud
|
|
|
4589
4695
|
function estimateTokens(m, keepRecentImages = 1) {
|
|
4590
4696
|
return Math.ceil(sendBytes(m, keepRecentImages) / 4);
|
|
4591
4697
|
}
|
|
4698
|
+
var TRANSPORT_TOOL_TIMEOUT_FLOOR_MS = 18e5;
|
|
4699
|
+
function transportHeadroomMs(t) {
|
|
4700
|
+
const d = Number(t.maxDurationMs) || 0;
|
|
4701
|
+
if (!d) return 0;
|
|
4702
|
+
return t.maxDurationEnforced ? d + 6e4 : d * 2 + 6e4;
|
|
4703
|
+
}
|
|
4704
|
+
function toolTimeoutViolations(tools, transportMs) {
|
|
4705
|
+
const out = [];
|
|
4706
|
+
for (const t of tools) {
|
|
4707
|
+
const d = Number(t.maxDurationMs) || 0;
|
|
4708
|
+
if (d && d >= transportMs) {
|
|
4709
|
+
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.`);
|
|
4710
|
+
}
|
|
4711
|
+
}
|
|
4712
|
+
return out;
|
|
4713
|
+
}
|
|
4714
|
+
function nonEmptyResult(out, name = "tool", delivery = "ran") {
|
|
4715
|
+
if (out.trim()) return out;
|
|
4716
|
+
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.`;
|
|
4717
|
+
if (delivery === "missing") {
|
|
4718
|
+
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.`;
|
|
4719
|
+
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.`;
|
|
4720
|
+
}
|
|
4721
|
+
return `[${name}] ran and returned no output. The call completed \u2014 this is an empty answer, not a failure.`;
|
|
4722
|
+
}
|
|
4723
|
+
function isVacuous(v, seen = /* @__PURE__ */ new Set(), depth = 0) {
|
|
4724
|
+
if (v == null) return true;
|
|
4725
|
+
if (typeof v === "string") return v.trim() === "";
|
|
4726
|
+
if (typeof v !== "object") return false;
|
|
4727
|
+
if (seen.has(v) || depth >= 64) return false;
|
|
4728
|
+
seen.add(v);
|
|
4729
|
+
try {
|
|
4730
|
+
if (Array.isArray(v)) return v.every((x) => isVacuous(x, seen, depth + 1));
|
|
4731
|
+
const proto = Object.getPrototypeOf(v);
|
|
4732
|
+
if (proto !== Object.prototype && proto !== null) return false;
|
|
4733
|
+
const vals = Object.values(v);
|
|
4734
|
+
return vals.length === 0 || vals.every((x) => isVacuous(x, seen, depth + 1));
|
|
4735
|
+
} finally {
|
|
4736
|
+
seen.delete(v);
|
|
4737
|
+
}
|
|
4738
|
+
}
|
|
4739
|
+
function delegatedResult(output) {
|
|
4740
|
+
if (typeof output === "string") return { text: output, images: [] };
|
|
4741
|
+
if (output == null) return { text: "", images: [] };
|
|
4742
|
+
const isImg = (i) => i && typeof i.data === "string" && typeof i.mimeType === "string";
|
|
4743
|
+
const o = output;
|
|
4744
|
+
if (Array.isArray(o.images)) {
|
|
4745
|
+
const images = o.images.filter(isImg);
|
|
4746
|
+
if (images.length) return { text: typeof o.text === "string" ? o.text : "", images };
|
|
4747
|
+
}
|
|
4748
|
+
const blocks = Array.isArray(output) ? output : Array.isArray(o.content) ? o.content : void 0;
|
|
4749
|
+
if (blocks) {
|
|
4750
|
+
const images = blocks.filter((b) => b?.type === "image" && isImg(b)).map((b) => ({ mimeType: b.mimeType, data: b.data }));
|
|
4751
|
+
if (images.length) {
|
|
4752
|
+
const text = blocks.filter((b) => b?.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n");
|
|
4753
|
+
return { text, images };
|
|
4754
|
+
}
|
|
4755
|
+
}
|
|
4756
|
+
try {
|
|
4757
|
+
return { text: JSON.stringify(output), images: [] };
|
|
4758
|
+
} catch {
|
|
4759
|
+
return { text: String(output), images: [] };
|
|
4760
|
+
}
|
|
4761
|
+
}
|
|
4762
|
+
function deliveryOf(output, status) {
|
|
4763
|
+
if (status === "error") return "error";
|
|
4764
|
+
if (output == null) return "missing";
|
|
4765
|
+
if (status != null && status !== "completed") return "missing";
|
|
4766
|
+
return "ran";
|
|
4767
|
+
}
|
|
4592
4768
|
function cropResult(result, cap) {
|
|
4593
4769
|
const head = result.slice(0, cap);
|
|
4594
4770
|
const nl = head.lastIndexOf("\n");
|
|
@@ -8334,13 +8510,29 @@ function toResult(result) {
|
|
|
8334
8510
|
}
|
|
8335
8511
|
return { text: JSON.stringify(result) };
|
|
8336
8512
|
}
|
|
8337
|
-
|
|
8513
|
+
var MCP_CALL_TIMEOUT_MS = 3e5;
|
|
8514
|
+
var TIMED_OUT = /* @__PURE__ */ Symbol("mcp-timeout");
|
|
8515
|
+
async function withDeadline(p, ms) {
|
|
8516
|
+
let t;
|
|
8517
|
+
try {
|
|
8518
|
+
return await Promise.race([p, new Promise((res) => {
|
|
8519
|
+
t = setTimeout(() => res(TIMED_OUT), ms);
|
|
8520
|
+
})]);
|
|
8521
|
+
} finally {
|
|
8522
|
+
clearTimeout(t);
|
|
8523
|
+
}
|
|
8524
|
+
}
|
|
8525
|
+
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.`;
|
|
8526
|
+
function mcpToolToAgentTool(spec, callTool, prefix = "mcp__", timeoutMs = MCP_CALL_TIMEOUT_MS) {
|
|
8338
8527
|
return {
|
|
8339
8528
|
name: `${prefix}${spec.name}`,
|
|
8340
8529
|
description: spec.description ?? `MCP tool ${spec.name}`,
|
|
8341
8530
|
parameters: spec.inputSchema ?? { type: "object", properties: {} },
|
|
8531
|
+
maxDurationMs: timeoutMs,
|
|
8342
8532
|
async run(args, _ctx) {
|
|
8343
|
-
const
|
|
8533
|
+
const raw = await withDeadline(callTool(spec.name, args ?? {}), timeoutMs);
|
|
8534
|
+
if (raw === TIMED_OUT) return mcpTimedOut(spec.name, timeoutMs);
|
|
8535
|
+
const r = toResult(raw);
|
|
8344
8536
|
return r.images?.length ? r : r.text;
|
|
8345
8537
|
}
|
|
8346
8538
|
};
|
|
@@ -8355,6 +8547,7 @@ function describeSpec(s) {
|
|
|
8355
8547
|
}
|
|
8356
8548
|
function makeMcpToolSearch(specs, callTool, options = {}) {
|
|
8357
8549
|
const maxResults = options.maxResults ?? 10;
|
|
8550
|
+
const callTimeoutMs = options.timeoutMs ?? MCP_CALL_TIMEOUT_MS;
|
|
8358
8551
|
const byName = new Map(specs.map((s) => [s.name, s]));
|
|
8359
8552
|
const catalogLine = `${specs.length} MCP tool(s) available \u2014 search by keyword, then call by exact name.`;
|
|
8360
8553
|
const searchTool = {
|
|
@@ -8380,10 +8573,13 @@ function makeMcpToolSearch(specs, callTool, options = {}) {
|
|
|
8380
8573
|
args: { type: "object", description: "arguments object for the tool (per its schema)" }
|
|
8381
8574
|
}
|
|
8382
8575
|
},
|
|
8576
|
+
maxDurationMs: callTimeoutMs,
|
|
8383
8577
|
async run({ name, args }) {
|
|
8384
8578
|
const n = String(name ?? "");
|
|
8385
8579
|
if (!byName.has(n)) return `Error: unknown MCP tool '${n}'. Use ToolSearch to find valid names.`;
|
|
8386
|
-
const
|
|
8580
|
+
const raw = await withDeadline(callTool(n, args ?? {}), callTimeoutMs);
|
|
8581
|
+
if (raw === TIMED_OUT) return mcpTimedOut(n, callTimeoutMs);
|
|
8582
|
+
const r = toResult(raw);
|
|
8387
8583
|
return r.images?.length ? r : r.text;
|
|
8388
8584
|
}
|
|
8389
8585
|
};
|
|
@@ -9186,14 +9382,20 @@ Reference files in them by their mount path (the left side).`;
|
|
|
9186
9382
|
const memoryWriteDir = memoryDir[0];
|
|
9187
9383
|
const hooks = o.learnFromMistakes && memoryWriteDir ? composeHooks(o.hooks, lessonCapture({ fs, dir: memoryWriteDir, minRepeats: 2 })) : o.hooks;
|
|
9188
9384
|
let realShell = [];
|
|
9385
|
+
const agentRef = {};
|
|
9189
9386
|
const useRealShell = o.realShell ?? !virtual;
|
|
9190
9387
|
if (useRealShell && !virtual && !isClaudeCode) {
|
|
9191
|
-
const jobs = new ShellJobRegistry({
|
|
9192
|
-
|
|
9388
|
+
const jobs = new ShellJobRegistry({
|
|
9389
|
+
cwd,
|
|
9390
|
+
killOnExit: true,
|
|
9391
|
+
osSandbox: o.osSandbox,
|
|
9392
|
+
onExit: (n) => agentRef.agent?.inject(formatJobExit(n))
|
|
9393
|
+
});
|
|
9394
|
+
realShell = [makeRealShellTool({ cwd, registry: jobs, osSandbox: o.osSandbox, jobsSurviveRun: o.singleRun !== true }), ...makeShellJobTools(jobs)];
|
|
9193
9395
|
}
|
|
9194
9396
|
const scratchDir = o.scratch ? o.scratchDir ?? (virtual ? `${cwd}/.agent/scratch` : `${tmpdir()}/agentx-scratch-${process.pid}`) : void 0;
|
|
9195
9397
|
const scratch = scratchDir ? new Scratch(fs, { dir: scratchDir }) : void 0;
|
|
9196
|
-
return new Agent({
|
|
9398
|
+
return agentRef.agent = new Agent({
|
|
9197
9399
|
ai: o.ai,
|
|
9198
9400
|
fs,
|
|
9199
9401
|
model: o.model ?? "anthropic/claude-sonnet-4-6",
|