agent.libx.js 0.94.23 → 0.94.25
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 +3 -3
- package/dist/{Agent-DmsB5hzp.d.ts → Agent-BA-rueWn.d.ts} +7 -2
- package/dist/cli.d.ts +7 -3
- package/dist/cli.js +678 -207
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +36 -6
- package/dist/index.js +117 -36
- package/dist/index.js.map +1 -1
- package/dist/{mcp-D00OuccC.d.ts → mcp-CnzmQ8JE.d.ts} +1 -1
- package/dist/mcp.client.d.ts +2 -2
- package/dist/{tools-9AUK6SG2.d.ts → tools-DtpN8Agv.d.ts} +2 -0
- package/dist/tools.shell.d.ts +7 -1
- package/dist/tools.shell.js +20 -6
- package/dist/tools.shell.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { a as AgentOptions, H as Hooks, h as RunResult, A as Agent } from './Agent-
|
|
2
|
-
export { C as ChatFragment, D as DEFAULT_MUTATING, b as Decision, P as PermissionOptions, c as PermissionPolicy, d as PermissionRule, e as PreToolUseDecision, R as ReasoningEffort, f as RecordingHooks, g as RecordingLifecycle, T as ToolUse, i as ToolUseMeta, j as composeHooks, p as planMode, r as reasoningToChatFragment } from './Agent-
|
|
1
|
+
import { a as AgentOptions, H as Hooks, h as RunResult, A as Agent } from './Agent-BA-rueWn.js';
|
|
2
|
+
export { C as ChatFragment, D as DEFAULT_MUTATING, b as Decision, P as PermissionOptions, c as PermissionPolicy, d as PermissionRule, e as PreToolUseDecision, R as ReasoningEffort, f as RecordingHooks, g as RecordingLifecycle, T as ToolUse, i as ToolUseMeta, j as composeHooks, p as planMode, r as reasoningToChatFragment } from './Agent-BA-rueWn.js';
|
|
3
3
|
import { IFilesystem, FileMetadata } from '@livx.cc/wcli/core';
|
|
4
4
|
export { CommandExecutor, FileMetadata, IFilesystem, IndexedDbFilesystem, MemFilesystem, registerHeadlessCommands } from '@livx.cc/wcli/core';
|
|
5
5
|
import { BodDB } from '@bod.ee/db';
|
|
6
|
-
import { A as AgentTool, C as ChatLike, a as ChatOptions, b as ChatResponse, h as ToolCall, H as HostBridge, U as UserQuestion, e as MessageContent } from './tools-
|
|
7
|
-
export { c as ContentPart, d as HostEvent, M as Message, R as Role, S as SandboxJobRegistry, f as StreamChunk, T as TodoItem, g as Tool, i as ToolContext, j as bashTool, k as contentText, l as defaultTools, m as editTool, n as exitSessionTool, o as imagePart, p as makeContext, q as makeJobTools, r as readTool, t as toWireTools, s as todoWriteTool, u as toolRegistry, v as toolsByName } from './tools-
|
|
8
|
-
export { M as McpCall, a as McpImage, b as McpRoute, c as McpRouteResolver, d as McpToolResult, e as McpToolSearchOptions, f as McpToolSpec, g as MountedMcpLike, h as buildMcpCatalog, m as makeLazyMcpToolSearch, i as makeMcpToolSearch, j as makeMcpToolSearchFromMounted, k as mcpToolToAgentTool, l as mcpToolsToAgentTools } from './mcp-
|
|
6
|
+
import { A as AgentTool, C as ChatLike, a as ChatOptions, b as ChatResponse, h as ToolCall, H as HostBridge, U as UserQuestion, e as MessageContent } from './tools-DtpN8Agv.js';
|
|
7
|
+
export { c as ContentPart, d as HostEvent, M as Message, R as Role, S as SandboxJobRegistry, f as StreamChunk, T as TodoItem, g as Tool, i as ToolContext, j as bashTool, k as contentText, l as defaultTools, m as editTool, n as exitSessionTool, o as imagePart, p as makeContext, q as makeJobTools, r as readTool, t as toWireTools, s as todoWriteTool, u as toolRegistry, v as toolsByName } from './tools-DtpN8Agv.js';
|
|
8
|
+
export { M as McpCall, a as McpImage, b as McpRoute, c as McpRouteResolver, d as McpToolResult, e as McpToolSearchOptions, f as McpToolSpec, g as MountedMcpLike, h as buildMcpCatalog, m as makeLazyMcpToolSearch, i as makeMcpToolSearch, j as makeMcpToolSearchFromMounted, k as mcpToolToAgentTool, l as mcpToolsToAgentTools } from './mcp-CnzmQ8JE.js';
|
|
9
9
|
import * as libx_js_src_modules_log from 'libx.js/src/modules/log';
|
|
10
10
|
export { log } from 'libx.js/src/modules/log';
|
|
11
11
|
|
|
@@ -25,6 +25,8 @@ declare class NodeDiskFilesystem implements IFilesystem {
|
|
|
25
25
|
/** Ensure the root dir exists. */
|
|
26
26
|
init(): Promise<void>;
|
|
27
27
|
private real;
|
|
28
|
+
private verified;
|
|
29
|
+
private static VERIFY_TTL_MS;
|
|
28
30
|
/** Throw if any existing component of `real` is a symlink (escape vector). */
|
|
29
31
|
private assertNoSymlink;
|
|
30
32
|
resolvePath(path: string, cwd?: string): string;
|
|
@@ -384,7 +386,32 @@ declare class Scheduler {
|
|
|
384
386
|
destroy(): void;
|
|
385
387
|
}
|
|
386
388
|
|
|
387
|
-
|
|
389
|
+
/** Narrow seam to an OS-level scheduler backend (jobs that survive quit/reboot). Node hosts wire
|
|
390
|
+
* the CLI's `OsScheduler` (launchd/cron/at); absent => everything stays in-process. Edge-safe:
|
|
391
|
+
* this module only sees the interface. */
|
|
392
|
+
interface OsBackend {
|
|
393
|
+
sessionId: string;
|
|
394
|
+
cwd: string;
|
|
395
|
+
/** 'os' when the trigger should outlive the session (per hint/heuristic). */
|
|
396
|
+
route(trigger: Trigger, backendHint?: string): 'session' | 'os';
|
|
397
|
+
/** Register with the OS. Returns a mechanism description (e.g. 'launchd:…'). Throws on failure. */
|
|
398
|
+
schedule(spec: {
|
|
399
|
+
id: string;
|
|
400
|
+
prompt: string;
|
|
401
|
+
sessionId: string;
|
|
402
|
+
cwd: string;
|
|
403
|
+
trigger: Trigger;
|
|
404
|
+
label?: string;
|
|
405
|
+
}): string;
|
|
406
|
+
cancel(id: string): boolean;
|
|
407
|
+
list(): Array<{
|
|
408
|
+
id: string;
|
|
409
|
+
label?: string;
|
|
410
|
+
mechanism: string;
|
|
411
|
+
trigger: Trigger;
|
|
412
|
+
}>;
|
|
413
|
+
}
|
|
414
|
+
declare function makeScheduleTools(scheduler: Scheduler, os?: OsBackend): AgentTool[];
|
|
388
415
|
|
|
389
416
|
/** Sandbox mode: an in-memory VFS — the real disk is never read or written. Edge/browser/test/dry-run. */
|
|
390
417
|
declare function sandboxAgentOptions(opts?: Partial<AgentOptions>): Partial<AgentOptions>;
|
|
@@ -829,6 +856,9 @@ declare class DuplexAgentOptions {
|
|
|
829
856
|
askRelay: boolean;
|
|
830
857
|
/** Parked questions auto-resolve empty after this long (callers map '' to deny/best-judgment). */
|
|
831
858
|
askTimeoutMs: number;
|
|
859
|
+
/** Max retained task records: oldest SETTLED tasks (and their activity tails) are evicted past this,
|
|
860
|
+
* bounding memory over a long-lived session. Running tasks are never evicted. */
|
|
861
|
+
maxTaskRecords: number;
|
|
832
862
|
/** Host overrides for QuickLook lookups (keyed by `what`). The engine's defaults go through the
|
|
833
863
|
* (possibly jailed) fs — e.g. `.git/**` is deny-listed, so the CLI supplies 'branch' itself. */
|
|
834
864
|
quickLook?: Record<string, (path?: string) => string | Promise<string>>;
|
package/dist/index.js
CHANGED
|
@@ -253,12 +253,14 @@ var init_tools_structured = __esm({
|
|
|
253
253
|
const ctxN = Math.max(0, Number(context ?? 0));
|
|
254
254
|
const out = [];
|
|
255
255
|
const matched = [];
|
|
256
|
+
let skipped = 0;
|
|
256
257
|
for (const path of files) {
|
|
257
258
|
ckAbort(ctx.signal);
|
|
258
259
|
let content;
|
|
259
260
|
try {
|
|
260
261
|
content = await ctx.fs.readFile(path);
|
|
261
262
|
} catch {
|
|
263
|
+
skipped++;
|
|
262
264
|
continue;
|
|
263
265
|
}
|
|
264
266
|
const lines = content.split("\n");
|
|
@@ -273,8 +275,10 @@ var init_tools_structured = __esm({
|
|
|
273
275
|
}
|
|
274
276
|
if (fileHit) matched.push(path);
|
|
275
277
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
+
const note = skipped ? `
|
|
279
|
+
[skipped ${skipped} unreadable file${skipped === 1 ? "" : "s"}]` : "";
|
|
280
|
+
if (filesOnly) return (matched.length ? matched.join("\n") : "(no matches)") + note;
|
|
281
|
+
return (out.length ? out.join("\n") : "(no matches)") + note;
|
|
278
282
|
}
|
|
279
283
|
};
|
|
280
284
|
SIG_RE = /^\s*(export\b|(?:export\s+)?(?:async\s+)?function\s+\*?\w|(?:export\s+)?(?:abstract\s+)?class\s+\w|(?:export\s+)?interface\s+\w|(?:export\s+)?type\s+\w|(?:export\s+)?enum\s+\w)/;
|
|
@@ -963,7 +967,7 @@ var init_tools = __esm({
|
|
|
963
967
|
IMG_MIME = { png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif", webp: "image/webp" };
|
|
964
968
|
readTool = {
|
|
965
969
|
name: "Read",
|
|
966
|
-
description: "Read a file. Text files return 1-indexed numbered lines (with optional `offset`/`limit` and a re-Read pointer for partial reads). Image files (png/jpg/jpeg/gif/webp) return the picture itself so you can SEE it. Always Read a file before Editing it.",
|
|
970
|
+
description: "Read a file. Text files return 1-indexed numbered lines (with optional `offset`/`limit` and a re-Read pointer for partial reads). Image files (png/jpg/jpeg/gif/webp) return the picture itself so you can SEE it. PDFs return their extracted text. Always Read a file before Editing it.",
|
|
967
971
|
parameters: {
|
|
968
972
|
type: "object",
|
|
969
973
|
required: ["path"],
|
|
@@ -975,6 +979,12 @@ var init_tools = __esm({
|
|
|
975
979
|
},
|
|
976
980
|
async run({ path, offset, limit }, ctx) {
|
|
977
981
|
const ext = String(path).toLowerCase().split(".").pop() ?? "";
|
|
982
|
+
if (ext === "pdf") {
|
|
983
|
+
if (!ctx.pdfText) return `[${path} is a PDF \u2014 text extraction isn't available in this environment (install poppler's pdftotext and run on disk).]`;
|
|
984
|
+
if (!await ctx.fs.exists(path)) return `Error: File not found: ${path}`;
|
|
985
|
+
const text = (await ctx.pdfText(ctx.fs.resolvePath(path))).trim();
|
|
986
|
+
return text ? numberLines(text, Math.max(0, offset ?? 0), limit) : `[${path}: no extractable text (scanned/image-only PDF?)]`;
|
|
987
|
+
}
|
|
978
988
|
if (IMG_MIME[ext]) {
|
|
979
989
|
const fs = ctx.fs;
|
|
980
990
|
if (typeof fs.readFileBytes !== "function") {
|
|
@@ -1052,7 +1062,7 @@ var NodeDiskFilesystem;
|
|
|
1052
1062
|
var init_NodeDiskFilesystem = __esm({
|
|
1053
1063
|
"src/NodeDiskFilesystem.ts"() {
|
|
1054
1064
|
"use strict";
|
|
1055
|
-
NodeDiskFilesystem = class {
|
|
1065
|
+
NodeDiskFilesystem = class _NodeDiskFilesystem {
|
|
1056
1066
|
constructor(baseDir, opts = {}) {
|
|
1057
1067
|
this.baseDir = baseDir;
|
|
1058
1068
|
this.opts = { denySymlinks: true, ...opts };
|
|
@@ -1067,14 +1077,24 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
1067
1077
|
real(vpath) {
|
|
1068
1078
|
return np.join(this.baseDir, "." + vpath);
|
|
1069
1079
|
}
|
|
1080
|
+
// Verified non-symlink DIRECTORY components, with a short TTL: tree walks (Glob/Grep) hit the same
|
|
1081
|
+
// parents thousands of times; re-lstat'ing each per op is the dominant syscall cost. The 1s window
|
|
1082
|
+
// is an accepted race only against an out-of-band symlink swap mid-walk (this FS can't create
|
|
1083
|
+
// symlinks itself); leaf components are never cached.
|
|
1084
|
+
verified = /* @__PURE__ */ new Map();
|
|
1085
|
+
static VERIFY_TTL_MS = 1e3;
|
|
1070
1086
|
/** Throw if any existing component of `real` is a symlink (escape vector). */
|
|
1071
1087
|
async assertNoSymlink(real) {
|
|
1072
1088
|
if (!this.opts.denySymlinks) return;
|
|
1073
1089
|
const rel = np.relative(this.baseDir, real);
|
|
1074
1090
|
if (rel === "" || rel.startsWith("..")) return;
|
|
1091
|
+
const parts = rel.split(np.sep);
|
|
1075
1092
|
let cur = this.baseDir;
|
|
1076
|
-
|
|
1077
|
-
|
|
1093
|
+
const now4 = Date.now();
|
|
1094
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1095
|
+
cur = np.join(cur, parts[i]);
|
|
1096
|
+
const isLeaf = i === parts.length - 1;
|
|
1097
|
+
if (!isLeaf && (this.verified.get(cur) ?? 0) > now4) continue;
|
|
1078
1098
|
let st;
|
|
1079
1099
|
try {
|
|
1080
1100
|
st = await fsp.lstat(cur);
|
|
@@ -1082,6 +1102,10 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
1082
1102
|
return;
|
|
1083
1103
|
}
|
|
1084
1104
|
if (st.isSymbolicLink()) throw new Error("File not found: symlink not permitted");
|
|
1105
|
+
if (!isLeaf) {
|
|
1106
|
+
if (this.verified.size > 1e4) this.verified.clear();
|
|
1107
|
+
this.verified.set(cur, now4 + _NodeDiskFilesystem.VERIFY_TTL_MS);
|
|
1108
|
+
}
|
|
1085
1109
|
}
|
|
1086
1110
|
}
|
|
1087
1111
|
resolvePath(path, cwd) {
|
|
@@ -1446,6 +1470,15 @@ __export(tools_shell_exports, {
|
|
|
1446
1470
|
makeRealShellTool: () => makeRealShellTool,
|
|
1447
1471
|
makeShellJobTools: () => makeShellJobTools
|
|
1448
1472
|
});
|
|
1473
|
+
function killGroup(proc, signal) {
|
|
1474
|
+
if (!proc?.pid) return false;
|
|
1475
|
+
try {
|
|
1476
|
+
process.kill(-proc.pid, signal);
|
|
1477
|
+
return true;
|
|
1478
|
+
} catch {
|
|
1479
|
+
return false;
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1449
1482
|
async function spawnArgvFor(command, cwd, osSandbox) {
|
|
1450
1483
|
if (!osSandbox) return { bin: "/bin/sh", args: ["-c", command] };
|
|
1451
1484
|
const opts = osSandbox === true ? {} : osSandbox;
|
|
@@ -1468,7 +1501,7 @@ function makeRealShellTool(options) {
|
|
|
1468
1501
|
const timeoutMs = options.timeoutMs ?? 12e4;
|
|
1469
1502
|
return {
|
|
1470
1503
|
name: "Shell",
|
|
1471
|
-
description: "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]`. Set `background:true` for long-running processes (servers, watchers) \u2014 returns a job id immediately; poll with ShellOutput, stop with ShellKill.",
|
|
1504
|
+
description: "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. Set `background:true` for long-running processes (servers, watchers) \u2014 returns a job id immediately; poll with ShellOutput, stop with ShellKill.",
|
|
1472
1505
|
parameters: {
|
|
1473
1506
|
type: "object",
|
|
1474
1507
|
required: ["command"],
|
|
@@ -1528,10 +1561,12 @@ function makeRealShellTool(options) {
|
|
|
1528
1561
|
};
|
|
1529
1562
|
let proc;
|
|
1530
1563
|
try {
|
|
1531
|
-
proc = spawn(argv.bin, argv.args, { cwd: options.cwd, env: childEnv(options), signal: ctl.signal });
|
|
1564
|
+
proc = spawn(argv.bin, argv.args, { cwd: options.cwd, env: childEnv(options), signal: ctl.signal, ...DETACHED });
|
|
1532
1565
|
} catch (e) {
|
|
1533
1566
|
return finish(`[exit 1] failed to spawn shell: ${e?.message ?? e}`);
|
|
1534
1567
|
}
|
|
1568
|
+
if (ctl.signal.aborted) killGroup(proc, "SIGKILL");
|
|
1569
|
+
else ctl.signal.addEventListener("abort", () => killGroup(proc, "SIGKILL"), { once: true });
|
|
1535
1570
|
const collect = (chunk) => {
|
|
1536
1571
|
const s = typeof chunk === "string" ? chunk : chunk?.toString?.("utf8") ?? "";
|
|
1537
1572
|
out += s;
|
|
@@ -1607,7 +1642,7 @@ ${clean(out) || "(no output yet)"}`;
|
|
|
1607
1642
|
}
|
|
1608
1643
|
];
|
|
1609
1644
|
}
|
|
1610
|
-
var log4, clean, SECRET_ENV_RE, _spawn, ShellJobRegistry, NO_JOB2;
|
|
1645
|
+
var log4, clean, DETACHED, SECRET_ENV_RE, _spawn, ShellJobRegistry, NO_JOB2;
|
|
1611
1646
|
var init_tools_shell = __esm({
|
|
1612
1647
|
"src/tools.shell.ts"() {
|
|
1613
1648
|
"use strict";
|
|
@@ -1617,6 +1652,7 @@ var init_tools_shell = __esm({
|
|
|
1617
1652
|
init_shell_sandbox();
|
|
1618
1653
|
log4 = forComponent("shell");
|
|
1619
1654
|
clean = (s) => truncateOutput(redactSecrets(s.replace(/\n+$/, "")));
|
|
1655
|
+
DETACHED = { stdio: ["ignore", "pipe", "pipe"], detached: true };
|
|
1620
1656
|
SECRET_ENV_RE = /(_API_KEY|_TOKEN|_SECRET|_PASSWORD|_PRIVATE_KEY|^AWS_|^GITHUB_TOKEN$|^OPENAI_|^ANTHROPIC_|^GOOGLE_|^GEMINI_|^GROQ_|^NPM_TOKEN$)/i;
|
|
1621
1657
|
ShellJobRegistry = class {
|
|
1622
1658
|
constructor(cfg) {
|
|
@@ -1637,7 +1673,7 @@ var init_tools_shell = __esm({
|
|
|
1637
1673
|
try {
|
|
1638
1674
|
const spawn = this.cfg.spawn ?? await nodeSpawn();
|
|
1639
1675
|
const argv = this.cfg.osSandbox ? await spawnArgvFor(command, this.cfg.cwd, this.cfg.osSandbox) : { bin: "/bin/sh", args: ["-c", command] };
|
|
1640
|
-
const proc = spawn(argv.bin, argv.args, { cwd: this.cfg.cwd, env: childEnv(this.cfg) });
|
|
1676
|
+
const proc = spawn(argv.bin, argv.args, { cwd: this.cfg.cwd, env: childEnv(this.cfg), ...DETACHED });
|
|
1641
1677
|
job.proc = proc;
|
|
1642
1678
|
proc.stdout?.on("data", append);
|
|
1643
1679
|
proc.stderr?.on("data", append);
|
|
@@ -1676,9 +1712,11 @@ var init_tools_shell = __esm({
|
|
|
1676
1712
|
const j = this.jobs.get(id);
|
|
1677
1713
|
if (!j) return false;
|
|
1678
1714
|
if (j.status === "running") {
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1715
|
+
if (!killGroup(j.proc, "SIGTERM")) {
|
|
1716
|
+
try {
|
|
1717
|
+
j.proc?.kill("SIGTERM");
|
|
1718
|
+
} catch {
|
|
1719
|
+
}
|
|
1682
1720
|
}
|
|
1683
1721
|
j.status = "killed";
|
|
1684
1722
|
}
|
|
@@ -2835,6 +2873,8 @@ var AgentOptions = class {
|
|
|
2835
2873
|
permissions;
|
|
2836
2874
|
/** Opt-in syntax guardrail: refuse to persist a syntactically-broken code-file write/edit. Default off. */
|
|
2837
2875
|
lintOnWrite;
|
|
2876
|
+
/** Optional PDF text extraction for Read on .pdf files (node hosts wire pdftotext); absent => Read explains. */
|
|
2877
|
+
pdfText;
|
|
2838
2878
|
/** Opt-in: after a write-class tool runs, run `command` over the VFS and append any failure to the tool result.
|
|
2839
2879
|
* `tools` defaults to ['Write','Edit','MultiEdit','ApplyEdits']. */
|
|
2840
2880
|
autoTest;
|
|
@@ -2885,8 +2925,12 @@ var Agent = class _Agent {
|
|
|
2885
2925
|
reprepare() {
|
|
2886
2926
|
this.prepared = false;
|
|
2887
2927
|
}
|
|
2888
|
-
/**
|
|
2928
|
+
/** Tools injected via addTools(); kept separate from options.tools so prepare() rebuilds don't drop them. */
|
|
2929
|
+
injectedTools = [];
|
|
2930
|
+
/** Inject tools into a running agent (e.g. dynamically mounted MCP servers). Takes effect on the next turn
|
|
2931
|
+
* and survives prepare() rebuilds (reprepare(), new conversations). */
|
|
2889
2932
|
addTools(tools) {
|
|
2933
|
+
this.injectedTools.push(...tools);
|
|
2890
2934
|
this.activeTools.push(...tools);
|
|
2891
2935
|
}
|
|
2892
2936
|
/** Remove tools by name from a running agent. Returns the count removed. */
|
|
@@ -2894,6 +2938,7 @@ var Agent = class _Agent {
|
|
|
2894
2938
|
const s = names instanceof Set ? names : new Set(names);
|
|
2895
2939
|
const before = this.activeTools.length;
|
|
2896
2940
|
this.activeTools = this.activeTools.filter((t) => !s.has(t.name));
|
|
2941
|
+
this.injectedTools = this.injectedTools.filter((t) => !s.has(t.name));
|
|
2897
2942
|
return before - this.activeTools.length;
|
|
2898
2943
|
}
|
|
2899
2944
|
constructor(options) {
|
|
@@ -2905,6 +2950,7 @@ var Agent = class _Agent {
|
|
|
2905
2950
|
this.ctx = makeContext(this.options.fs, this.options.host);
|
|
2906
2951
|
this.ctx.signal = this.options.signal;
|
|
2907
2952
|
if (this.options.lintOnWrite) this.ctx.lint = checkSyntax;
|
|
2953
|
+
if (this.options.pdfText) this.ctx.pdfText = this.options.pdfText;
|
|
2908
2954
|
this.ctx.ai = this.options.ai;
|
|
2909
2955
|
this.ctx.model = this.options.model;
|
|
2910
2956
|
this.ctx.parkHuman = (p) => this.park(p);
|
|
@@ -2977,7 +3023,7 @@ var Agent = class _Agent {
|
|
|
2977
3023
|
const plan = o.planMode ? planMode({ host: o.host }) : void 0;
|
|
2978
3024
|
if (plan) tools = [...tools, plan.tool];
|
|
2979
3025
|
this.activeHooks = composeHooks(o.hooks, plan?.hooks, o.permissions?.hooks());
|
|
2980
|
-
this.activeTools = tools;
|
|
3026
|
+
this.activeTools = [...tools, ...this.injectedTools];
|
|
2981
3027
|
this.systemPromptCache = systemPrompt;
|
|
2982
3028
|
this.prepared = true;
|
|
2983
3029
|
return systemPrompt;
|
|
@@ -3112,7 +3158,15 @@ var Agent = class _Agent {
|
|
|
3112
3158
|
} catch (err) {
|
|
3113
3159
|
if (err?.code === "budget") return kill("budget");
|
|
3114
3160
|
if (o.signal?.aborted || isAbortError(err)) return kill("aborted");
|
|
3115
|
-
|
|
3161
|
+
const body = err?.body ?? err?.response?.data ?? err?.error;
|
|
3162
|
+
let bodyStr;
|
|
3163
|
+
try {
|
|
3164
|
+
bodyStr = body && typeof body !== "string" ? JSON.stringify(body).slice(0, 2e3) : body;
|
|
3165
|
+
} catch {
|
|
3166
|
+
bodyStr = void 0;
|
|
3167
|
+
}
|
|
3168
|
+
if (bodyStr && err instanceof Error && !err.message.includes(bodyStr)) err.detail = bodyStr;
|
|
3169
|
+
log3.error(`chat() failed: ${err?.message ?? err}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, err);
|
|
3116
3170
|
return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err };
|
|
3117
3171
|
}
|
|
3118
3172
|
if (o.signal?.aborted) return kill("aborted");
|
|
@@ -3343,20 +3397,28 @@ function stubOldToolResults(messages, keep) {
|
|
|
3343
3397
|
return { ...x, content: `[${x.name ?? "tool"}${where ? ` ${where}` : ""} output elided \u2014 ${lines} lines; re-run the tool to view]` };
|
|
3344
3398
|
});
|
|
3345
3399
|
}
|
|
3346
|
-
var
|
|
3400
|
+
var callIdSet = (msgs) => {
|
|
3401
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3402
|
+
for (const m of msgs) if (m.role === "assistant") for (const tc of m.tool_calls ?? []) ids.add(tc.id);
|
|
3403
|
+
return ids;
|
|
3404
|
+
};
|
|
3347
3405
|
function dropOrphanToolResults(messages) {
|
|
3348
|
-
const
|
|
3406
|
+
const ids = callIdSet(messages);
|
|
3407
|
+
const ok = (m) => m.role !== "tool" || ids.has(m.tool_call_id ?? "");
|
|
3349
3408
|
return messages.every(ok) ? messages : messages.filter(ok);
|
|
3350
3409
|
}
|
|
3351
3410
|
function fitTokenBudget(messages, maxTokens) {
|
|
3352
|
-
|
|
3411
|
+
const per = messages.map((x) => estimateTokens([x]));
|
|
3412
|
+
let total = per.reduce((a, b) => a + b, 0);
|
|
3413
|
+
if (total <= maxTokens) return messages;
|
|
3353
3414
|
const head = messages[0]?.role === "system" ? [messages[0]] : [];
|
|
3354
|
-
let
|
|
3355
|
-
while (
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3415
|
+
let from = head.length;
|
|
3416
|
+
while (from < messages.length && total > maxTokens) total -= per[from++];
|
|
3417
|
+
const ids = callIdSet(messages.slice(from));
|
|
3418
|
+
while (from < messages.length && messages[from].role === "tool" && !ids.has(messages[from].tool_call_id ?? "")) total -= per[from++];
|
|
3419
|
+
if (total > maxTokens)
|
|
3420
|
+
log3.warn(`context ~${total} tok still over maxContextTokens=${maxTokens} after trimming (system head can't be dropped)`);
|
|
3421
|
+
return [...head, ...messages.slice(from)];
|
|
3360
3422
|
}
|
|
3361
3423
|
function compact(m, max, focus) {
|
|
3362
3424
|
const hasSystem = m[0]?.role === "system";
|
|
@@ -3899,11 +3961,11 @@ var Scheduler = class {
|
|
|
3899
3961
|
this.jobs.clear();
|
|
3900
3962
|
}
|
|
3901
3963
|
};
|
|
3902
|
-
function makeScheduleTools(scheduler) {
|
|
3964
|
+
function makeScheduleTools(scheduler, os) {
|
|
3903
3965
|
return [
|
|
3904
3966
|
{
|
|
3905
3967
|
name: "ScheduleTask",
|
|
3906
|
-
description: 'Schedule a prompt to fire automatically
|
|
3968
|
+
description: 'Schedule a prompt to fire automatically.\nModes:\n \u2022 One-off: {at: <epoch_ms>} \u2014 fires once at that time, then done.\n \u2022 Interval: {everyMs: <ms>} \u2014 fires repeatedly (\u22651s).\n \u2022 Cron: {cron: "min hr dom mon dow"} \u2014 standard 5-field cron.\nBackend: "session" fires while this CLI session is alive (default for recurring + near one-offs); "os" registers with the OS scheduler so the job survives quitting \u2014 it headless-resumes this session when it fires (auto-chosen for one-offs \u226530min out when available). Pass backend:"os" explicitly for recurring jobs that must outlive the session.',
|
|
3907
3969
|
parameters: {
|
|
3908
3970
|
type: "object",
|
|
3909
3971
|
required: ["prompt", "trigger"],
|
|
@@ -3918,15 +3980,22 @@ function makeScheduleTools(scheduler) {
|
|
|
3918
3980
|
cron: { type: "string" }
|
|
3919
3981
|
}
|
|
3920
3982
|
},
|
|
3921
|
-
label: { type: "string", description: "Short label for display (optional)." }
|
|
3983
|
+
label: { type: "string", description: "Short label for display (optional)." },
|
|
3984
|
+
backend: { type: "string", enum: ["auto", "session", "os"], description: "Where the job lives (default auto)." }
|
|
3922
3985
|
}
|
|
3923
3986
|
},
|
|
3924
|
-
async run({ prompt, trigger, label }) {
|
|
3987
|
+
async run({ prompt, trigger, label, backend }) {
|
|
3925
3988
|
try {
|
|
3989
|
+
if (os && os.route(trigger, backend) === "os") {
|
|
3990
|
+
const id2 = `os-${Date.now().toString(36)}`;
|
|
3991
|
+
const mechanism = os.schedule({ id: id2, prompt, sessionId: os.sessionId, cwd: os.cwd, trigger, label });
|
|
3992
|
+
return `Scheduled ${id2}${label ? ` (${label})` : ""} on the OS scheduler (${mechanism}) \u2014 survives quitting; fires \`agentx --resume ${os.sessionId}\` headless.`;
|
|
3993
|
+
}
|
|
3994
|
+
if (backend === "os") return "Error: no OS scheduler available on this platform \u2014 job not created (use the default in-session backend).";
|
|
3926
3995
|
const id = scheduler.add({ prompt, trigger, label });
|
|
3927
3996
|
const job = scheduler.get(id);
|
|
3928
3997
|
const next = scheduler.nextFire(job);
|
|
3929
|
-
return `Scheduled ${id}${label ? ` (${label})` : ""}. Next fire: ${next ? new Date(next).toLocaleString() : "now"}
|
|
3998
|
+
return `Scheduled ${id}${label ? ` (${label})` : ""}. Next fire: ${next ? new Date(next).toLocaleString() : "now"}. (In-session: does not survive quitting.)`;
|
|
3930
3999
|
} catch (e) {
|
|
3931
4000
|
return `Error: ${e?.message ?? e}`;
|
|
3932
4001
|
}
|
|
@@ -3934,24 +4003,28 @@ function makeScheduleTools(scheduler) {
|
|
|
3934
4003
|
},
|
|
3935
4004
|
{
|
|
3936
4005
|
name: "ScheduleList",
|
|
3937
|
-
description: "List all scheduled jobs and their next fire time.",
|
|
4006
|
+
description: "List all scheduled jobs (in-session + OS-backed) and their next fire time.",
|
|
3938
4007
|
parameters: { type: "object", properties: {} },
|
|
3939
4008
|
async run() {
|
|
4009
|
+
const osJobs = os?.list() ?? [];
|
|
4010
|
+
const osLines = osJobs.map((j) => `${j.id} os ${j.mechanism}${j.label ? " " + j.label : ""}`);
|
|
3940
4011
|
const jobs = scheduler.list();
|
|
3941
|
-
if (!jobs.length) return "(no scheduled jobs)";
|
|
3942
|
-
return jobs.map((j) => {
|
|
4012
|
+
if (!jobs.length && !osLines.length) return "(no scheduled jobs)";
|
|
4013
|
+
return [...osLines, ...jobs.map((j) => {
|
|
3943
4014
|
const next = scheduler.nextFire(j);
|
|
3944
4015
|
const trig = "at" in j.trigger ? `once @ ${new Date(j.trigger.at).toLocaleString()}` : "everyMs" in j.trigger ? `every ${(j.trigger.everyMs / 1e3).toFixed(0)}s` : `cron: ${j.trigger.cron}`;
|
|
3945
4016
|
return `${j.id} ${j.status} ${trig} runs:${j.runs} next:${next ? new Date(next).toLocaleTimeString() : "\u2014"}${j.label ? " " + j.label : ""}`;
|
|
3946
|
-
}).join("\n");
|
|
4017
|
+
})].join("\n");
|
|
3947
4018
|
}
|
|
3948
4019
|
},
|
|
3949
4020
|
{
|
|
3950
4021
|
name: "ScheduleCancel",
|
|
3951
|
-
description: "Cancel a scheduled job by id.",
|
|
4022
|
+
description: "Cancel a scheduled job by id (in-session or OS-backed).",
|
|
3952
4023
|
parameters: { type: "object", required: ["id"], properties: { id: { type: "string" } } },
|
|
3953
4024
|
async run({ id }) {
|
|
3954
|
-
|
|
4025
|
+
const key = String(id);
|
|
4026
|
+
if (key.startsWith("os-")) return os?.cancel(key) ? `Cancelled ${key} (OS job removed).` : `Error: no OS job '${key}'.`;
|
|
4027
|
+
return scheduler.cancel(key) ? `Cancelled ${key}.` : `Error: no scheduled job '${key}'. Use ScheduleList to see jobs.`;
|
|
3955
4028
|
}
|
|
3956
4029
|
},
|
|
3957
4030
|
{
|
|
@@ -4262,6 +4335,9 @@ var DuplexAgentOptions = class {
|
|
|
4262
4335
|
askRelay = false;
|
|
4263
4336
|
/** Parked questions auto-resolve empty after this long (callers map '' to deny/best-judgment). */
|
|
4264
4337
|
askTimeoutMs = 12e4;
|
|
4338
|
+
/** Max retained task records: oldest SETTLED tasks (and their activity tails) are evicted past this,
|
|
4339
|
+
* bounding memory over a long-lived session. Running tasks are never evicted. */
|
|
4340
|
+
maxTaskRecords = 50;
|
|
4265
4341
|
/** Host overrides for QuickLook lookups (keyed by `what`). The engine's defaults go through the
|
|
4266
4342
|
* (possibly jailed) fs — e.g. `.git/**` is deny-listed, so the CLI supplies 'branch' itself. */
|
|
4267
4343
|
quickLook;
|
|
@@ -4552,6 +4628,11 @@ ${recent}` : brief) + verify;
|
|
|
4552
4628
|
};
|
|
4553
4629
|
const promise = new Agent(agentOpts).run(briefText).then((res) => this.maybeVerify(id, briefText, res, tier, agentOpts)).then((res) => this.onWorkerSettled(id, res)).catch((err) => this.onWorkerFailed(id, err));
|
|
4554
4630
|
this.tasks.set(id, { id, label, status: "running", controller, promise, tail });
|
|
4631
|
+
if (this.tasks.size > this.options.maxTaskRecords)
|
|
4632
|
+
for (const [tid, rec] of this.tasks) {
|
|
4633
|
+
if (this.tasks.size <= this.options.maxTaskRecords) break;
|
|
4634
|
+
if (rec.status !== "running") this.tasks.delete(tid);
|
|
4635
|
+
}
|
|
4555
4636
|
}
|
|
4556
4637
|
/** Fresh-context check of a successful Act task: a NEW agent (same model/fs/tools, but NO shared
|
|
4557
4638
|
* conversation context) re-reads the file state against the brief and fixes any gap. The fix lands
|