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/cli.js
CHANGED
|
@@ -254,12 +254,14 @@ var init_tools_structured = __esm({
|
|
|
254
254
|
const ctxN = Math.max(0, Number(context ?? 0));
|
|
255
255
|
const out = [];
|
|
256
256
|
const matched = [];
|
|
257
|
+
let skipped = 0;
|
|
257
258
|
for (const path of files) {
|
|
258
259
|
ckAbort(ctx.signal);
|
|
259
260
|
let content;
|
|
260
261
|
try {
|
|
261
262
|
content = await ctx.fs.readFile(path);
|
|
262
263
|
} catch {
|
|
264
|
+
skipped++;
|
|
263
265
|
continue;
|
|
264
266
|
}
|
|
265
267
|
const lines = content.split("\n");
|
|
@@ -274,8 +276,10 @@ var init_tools_structured = __esm({
|
|
|
274
276
|
}
|
|
275
277
|
if (fileHit) matched.push(path);
|
|
276
278
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
+
const note = skipped ? `
|
|
280
|
+
[skipped ${skipped} unreadable file${skipped === 1 ? "" : "s"}]` : "";
|
|
281
|
+
if (filesOnly) return (matched.length ? matched.join("\n") : "(no matches)") + note;
|
|
282
|
+
return (out.length ? out.join("\n") : "(no matches)") + note;
|
|
279
283
|
}
|
|
280
284
|
};
|
|
281
285
|
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)/;
|
|
@@ -964,7 +968,7 @@ var init_tools = __esm({
|
|
|
964
968
|
IMG_MIME = { png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif", webp: "image/webp" };
|
|
965
969
|
readTool = {
|
|
966
970
|
name: "Read",
|
|
967
|
-
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.",
|
|
971
|
+
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.",
|
|
968
972
|
parameters: {
|
|
969
973
|
type: "object",
|
|
970
974
|
required: ["path"],
|
|
@@ -976,6 +980,12 @@ var init_tools = __esm({
|
|
|
976
980
|
},
|
|
977
981
|
async run({ path, offset, limit }, ctx) {
|
|
978
982
|
const ext = String(path).toLowerCase().split(".").pop() ?? "";
|
|
983
|
+
if (ext === "pdf") {
|
|
984
|
+
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).]`;
|
|
985
|
+
if (!await ctx.fs.exists(path)) return `Error: File not found: ${path}`;
|
|
986
|
+
const text = (await ctx.pdfText(ctx.fs.resolvePath(path))).trim();
|
|
987
|
+
return text ? numberLines(text, Math.max(0, offset ?? 0), limit) : `[${path}: no extractable text (scanned/image-only PDF?)]`;
|
|
988
|
+
}
|
|
979
989
|
if (IMG_MIME[ext]) {
|
|
980
990
|
const fs = ctx.fs;
|
|
981
991
|
if (typeof fs.readFileBytes !== "function") {
|
|
@@ -1053,7 +1063,7 @@ var NodeDiskFilesystem;
|
|
|
1053
1063
|
var init_NodeDiskFilesystem = __esm({
|
|
1054
1064
|
"src/NodeDiskFilesystem.ts"() {
|
|
1055
1065
|
"use strict";
|
|
1056
|
-
NodeDiskFilesystem = class {
|
|
1066
|
+
NodeDiskFilesystem = class _NodeDiskFilesystem {
|
|
1057
1067
|
constructor(baseDir, opts = {}) {
|
|
1058
1068
|
this.baseDir = baseDir;
|
|
1059
1069
|
this.opts = { denySymlinks: true, ...opts };
|
|
@@ -1068,14 +1078,24 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
1068
1078
|
real(vpath) {
|
|
1069
1079
|
return np.join(this.baseDir, "." + vpath);
|
|
1070
1080
|
}
|
|
1081
|
+
// Verified non-symlink DIRECTORY components, with a short TTL: tree walks (Glob/Grep) hit the same
|
|
1082
|
+
// parents thousands of times; re-lstat'ing each per op is the dominant syscall cost. The 1s window
|
|
1083
|
+
// is an accepted race only against an out-of-band symlink swap mid-walk (this FS can't create
|
|
1084
|
+
// symlinks itself); leaf components are never cached.
|
|
1085
|
+
verified = /* @__PURE__ */ new Map();
|
|
1086
|
+
static VERIFY_TTL_MS = 1e3;
|
|
1071
1087
|
/** Throw if any existing component of `real` is a symlink (escape vector). */
|
|
1072
1088
|
async assertNoSymlink(real) {
|
|
1073
1089
|
if (!this.opts.denySymlinks) return;
|
|
1074
1090
|
const rel = np.relative(this.baseDir, real);
|
|
1075
1091
|
if (rel === "" || rel.startsWith("..")) return;
|
|
1092
|
+
const parts = rel.split(np.sep);
|
|
1076
1093
|
let cur = this.baseDir;
|
|
1077
|
-
|
|
1078
|
-
|
|
1094
|
+
const now5 = Date.now();
|
|
1095
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1096
|
+
cur = np.join(cur, parts[i]);
|
|
1097
|
+
const isLeaf = i === parts.length - 1;
|
|
1098
|
+
if (!isLeaf && (this.verified.get(cur) ?? 0) > now5) continue;
|
|
1079
1099
|
let st;
|
|
1080
1100
|
try {
|
|
1081
1101
|
st = await fsp.lstat(cur);
|
|
@@ -1083,6 +1103,10 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
1083
1103
|
return;
|
|
1084
1104
|
}
|
|
1085
1105
|
if (st.isSymbolicLink()) throw new Error("File not found: symlink not permitted");
|
|
1106
|
+
if (!isLeaf) {
|
|
1107
|
+
if (this.verified.size > 1e4) this.verified.clear();
|
|
1108
|
+
this.verified.set(cur, now5 + _NodeDiskFilesystem.VERIFY_TTL_MS);
|
|
1109
|
+
}
|
|
1086
1110
|
}
|
|
1087
1111
|
}
|
|
1088
1112
|
resolvePath(path, cwd) {
|
|
@@ -1418,10 +1442,10 @@ function sandboxArgv(command, cwd, opts = {}, platform2 = process.platform, tmpD
|
|
|
1418
1442
|
return null;
|
|
1419
1443
|
}
|
|
1420
1444
|
async function findSandboxWrapper(platform2 = process.platform) {
|
|
1421
|
-
const { existsSync:
|
|
1422
|
-
if (platform2 === "darwin") return
|
|
1445
|
+
const { existsSync: existsSync10 } = await import("fs");
|
|
1446
|
+
if (platform2 === "darwin") return existsSync10("/usr/bin/sandbox-exec") ? "/usr/bin/sandbox-exec" : null;
|
|
1423
1447
|
if (platform2 === "linux") {
|
|
1424
|
-
for (const dir of (process.env.PATH ?? "/usr/bin:/bin").split(":")) if (dir &&
|
|
1448
|
+
for (const dir of (process.env.PATH ?? "/usr/bin:/bin").split(":")) if (dir && existsSync10(`${dir}/bwrap`)) return `${dir}/bwrap`;
|
|
1425
1449
|
return null;
|
|
1426
1450
|
}
|
|
1427
1451
|
return null;
|
|
@@ -1441,6 +1465,15 @@ var init_shell_sandbox = __esm({
|
|
|
1441
1465
|
});
|
|
1442
1466
|
|
|
1443
1467
|
// src/tools.shell.ts
|
|
1468
|
+
function killGroup(proc, signal) {
|
|
1469
|
+
if (!proc?.pid) return false;
|
|
1470
|
+
try {
|
|
1471
|
+
process.kill(-proc.pid, signal);
|
|
1472
|
+
return true;
|
|
1473
|
+
} catch {
|
|
1474
|
+
return false;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1444
1477
|
async function spawnArgvFor(command, cwd, osSandbox) {
|
|
1445
1478
|
if (!osSandbox) return { bin: "/bin/sh", args: ["-c", command] };
|
|
1446
1479
|
const opts = osSandbox === true ? {} : osSandbox;
|
|
@@ -1463,7 +1496,7 @@ function makeRealShellTool(options) {
|
|
|
1463
1496
|
const timeoutMs = options.timeoutMs ?? 12e4;
|
|
1464
1497
|
return {
|
|
1465
1498
|
name: "Shell",
|
|
1466
|
-
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.",
|
|
1499
|
+
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.",
|
|
1467
1500
|
parameters: {
|
|
1468
1501
|
type: "object",
|
|
1469
1502
|
required: ["command"],
|
|
@@ -1523,10 +1556,12 @@ function makeRealShellTool(options) {
|
|
|
1523
1556
|
};
|
|
1524
1557
|
let proc;
|
|
1525
1558
|
try {
|
|
1526
|
-
proc = spawn3(argv.bin, argv.args, { cwd: options.cwd, env: childEnv(options), signal: ctl.signal });
|
|
1559
|
+
proc = spawn3(argv.bin, argv.args, { cwd: options.cwd, env: childEnv(options), signal: ctl.signal, ...DETACHED });
|
|
1527
1560
|
} catch (e) {
|
|
1528
1561
|
return finish(`[exit 1] failed to spawn shell: ${e?.message ?? e}`);
|
|
1529
1562
|
}
|
|
1563
|
+
if (ctl.signal.aborted) killGroup(proc, "SIGKILL");
|
|
1564
|
+
else ctl.signal.addEventListener("abort", () => killGroup(proc, "SIGKILL"), { once: true });
|
|
1530
1565
|
const collect = (chunk) => {
|
|
1531
1566
|
const s = typeof chunk === "string" ? chunk : chunk?.toString?.("utf8") ?? "";
|
|
1532
1567
|
out += s;
|
|
@@ -1602,7 +1637,7 @@ ${clean(out) || "(no output yet)"}`;
|
|
|
1602
1637
|
}
|
|
1603
1638
|
];
|
|
1604
1639
|
}
|
|
1605
|
-
var log12, clean, SECRET_ENV_RE, _spawn, ShellJobRegistry, NO_JOB2;
|
|
1640
|
+
var log12, clean, DETACHED, SECRET_ENV_RE, _spawn, ShellJobRegistry, NO_JOB2;
|
|
1606
1641
|
var init_tools_shell = __esm({
|
|
1607
1642
|
"src/tools.shell.ts"() {
|
|
1608
1643
|
"use strict";
|
|
@@ -1612,6 +1647,7 @@ var init_tools_shell = __esm({
|
|
|
1612
1647
|
init_shell_sandbox();
|
|
1613
1648
|
log12 = forComponent("shell");
|
|
1614
1649
|
clean = (s) => truncateOutput(redactSecrets(s.replace(/\n+$/, "")));
|
|
1650
|
+
DETACHED = { stdio: ["ignore", "pipe", "pipe"], detached: true };
|
|
1615
1651
|
SECRET_ENV_RE = /(_API_KEY|_TOKEN|_SECRET|_PASSWORD|_PRIVATE_KEY|^AWS_|^GITHUB_TOKEN$|^OPENAI_|^ANTHROPIC_|^GOOGLE_|^GEMINI_|^GROQ_|^NPM_TOKEN$)/i;
|
|
1616
1652
|
ShellJobRegistry = class {
|
|
1617
1653
|
constructor(cfg) {
|
|
@@ -1632,7 +1668,7 @@ var init_tools_shell = __esm({
|
|
|
1632
1668
|
try {
|
|
1633
1669
|
const spawn3 = this.cfg.spawn ?? await nodeSpawn();
|
|
1634
1670
|
const argv = this.cfg.osSandbox ? await spawnArgvFor(command, this.cfg.cwd, this.cfg.osSandbox) : { bin: "/bin/sh", args: ["-c", command] };
|
|
1635
|
-
const proc = spawn3(argv.bin, argv.args, { cwd: this.cfg.cwd, env: childEnv(this.cfg) });
|
|
1671
|
+
const proc = spawn3(argv.bin, argv.args, { cwd: this.cfg.cwd, env: childEnv(this.cfg), ...DETACHED });
|
|
1636
1672
|
job.proc = proc;
|
|
1637
1673
|
proc.stdout?.on("data", append);
|
|
1638
1674
|
proc.stderr?.on("data", append);
|
|
@@ -1671,9 +1707,11 @@ var init_tools_shell = __esm({
|
|
|
1671
1707
|
const j = this.jobs.get(id);
|
|
1672
1708
|
if (!j) return false;
|
|
1673
1709
|
if (j.status === "running") {
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1710
|
+
if (!killGroup(j.proc, "SIGTERM")) {
|
|
1711
|
+
try {
|
|
1712
|
+
j.proc?.kill("SIGTERM");
|
|
1713
|
+
} catch {
|
|
1714
|
+
}
|
|
1677
1715
|
}
|
|
1678
1716
|
j.status = "killed";
|
|
1679
1717
|
}
|
|
@@ -1689,8 +1727,8 @@ var init_tools_shell = __esm({
|
|
|
1689
1727
|
|
|
1690
1728
|
// cli/cli.ts
|
|
1691
1729
|
import { createInterface } from "readline/promises";
|
|
1692
|
-
import { existsSync as
|
|
1693
|
-
import { homedir as
|
|
1730
|
+
import { existsSync as existsSync9, readFileSync as readFileSync6, appendFileSync, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8, readdirSync as readdirSync3, statSync as statSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
1731
|
+
import { homedir as homedir8, tmpdir as tmpdir3 } from "os";
|
|
1694
1732
|
|
|
1695
1733
|
// cli/clipboard.ts
|
|
1696
1734
|
import { execFileSync } from "child_process";
|
|
@@ -1744,9 +1782,20 @@ close access f`;
|
|
|
1744
1782
|
}
|
|
1745
1783
|
return null;
|
|
1746
1784
|
}
|
|
1785
|
+
function copyTextToClipboard(text, platform2 = process.platform) {
|
|
1786
|
+
const candidates = platform2 === "darwin" ? [["pbcopy", []]] : platform2 === "linux" ? [["wl-copy", []], ["xclip", ["-selection", "clipboard"]]] : [];
|
|
1787
|
+
for (const [cmd, args] of candidates) {
|
|
1788
|
+
try {
|
|
1789
|
+
execFileSync(cmd, args, { input: text, stdio: ["pipe", "ignore", "ignore"] });
|
|
1790
|
+
return true;
|
|
1791
|
+
} catch {
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
return false;
|
|
1795
|
+
}
|
|
1747
1796
|
|
|
1748
1797
|
// cli/cli.ts
|
|
1749
|
-
import { join as
|
|
1798
|
+
import { join as join11, resolve as resolve3, basename as basename2, extname, dirname as dirname4 } from "path";
|
|
1750
1799
|
import { AIClient, listModels, listProviders, getProviderFromModel, getModelInfo, resolveModel, isModelSupported, disposeCursorSessions } from "ai.libx.js";
|
|
1751
1800
|
|
|
1752
1801
|
// src/llm.ts
|
|
@@ -2839,6 +2888,8 @@ var AgentOptions = class {
|
|
|
2839
2888
|
permissions;
|
|
2840
2889
|
/** Opt-in syntax guardrail: refuse to persist a syntactically-broken code-file write/edit. Default off. */
|
|
2841
2890
|
lintOnWrite;
|
|
2891
|
+
/** Optional PDF text extraction for Read on .pdf files (node hosts wire pdftotext); absent => Read explains. */
|
|
2892
|
+
pdfText;
|
|
2842
2893
|
/** Opt-in: after a write-class tool runs, run `command` over the VFS and append any failure to the tool result.
|
|
2843
2894
|
* `tools` defaults to ['Write','Edit','MultiEdit','ApplyEdits']. */
|
|
2844
2895
|
autoTest;
|
|
@@ -2889,8 +2940,12 @@ var Agent = class _Agent {
|
|
|
2889
2940
|
reprepare() {
|
|
2890
2941
|
this.prepared = false;
|
|
2891
2942
|
}
|
|
2892
|
-
/**
|
|
2943
|
+
/** Tools injected via addTools(); kept separate from options.tools so prepare() rebuilds don't drop them. */
|
|
2944
|
+
injectedTools = [];
|
|
2945
|
+
/** Inject tools into a running agent (e.g. dynamically mounted MCP servers). Takes effect on the next turn
|
|
2946
|
+
* and survives prepare() rebuilds (reprepare(), new conversations). */
|
|
2893
2947
|
addTools(tools) {
|
|
2948
|
+
this.injectedTools.push(...tools);
|
|
2894
2949
|
this.activeTools.push(...tools);
|
|
2895
2950
|
}
|
|
2896
2951
|
/** Remove tools by name from a running agent. Returns the count removed. */
|
|
@@ -2898,6 +2953,7 @@ var Agent = class _Agent {
|
|
|
2898
2953
|
const s = names instanceof Set ? names : new Set(names);
|
|
2899
2954
|
const before = this.activeTools.length;
|
|
2900
2955
|
this.activeTools = this.activeTools.filter((t) => !s.has(t.name));
|
|
2956
|
+
this.injectedTools = this.injectedTools.filter((t) => !s.has(t.name));
|
|
2901
2957
|
return before - this.activeTools.length;
|
|
2902
2958
|
}
|
|
2903
2959
|
constructor(options) {
|
|
@@ -2909,6 +2965,7 @@ var Agent = class _Agent {
|
|
|
2909
2965
|
this.ctx = makeContext(this.options.fs, this.options.host);
|
|
2910
2966
|
this.ctx.signal = this.options.signal;
|
|
2911
2967
|
if (this.options.lintOnWrite) this.ctx.lint = checkSyntax;
|
|
2968
|
+
if (this.options.pdfText) this.ctx.pdfText = this.options.pdfText;
|
|
2912
2969
|
this.ctx.ai = this.options.ai;
|
|
2913
2970
|
this.ctx.model = this.options.model;
|
|
2914
2971
|
this.ctx.parkHuman = (p) => this.park(p);
|
|
@@ -2981,7 +3038,7 @@ var Agent = class _Agent {
|
|
|
2981
3038
|
const plan = o.planMode ? planMode({ host: o.host }) : void 0;
|
|
2982
3039
|
if (plan) tools = [...tools, plan.tool];
|
|
2983
3040
|
this.activeHooks = composeHooks(o.hooks, plan?.hooks, o.permissions?.hooks());
|
|
2984
|
-
this.activeTools = tools;
|
|
3041
|
+
this.activeTools = [...tools, ...this.injectedTools];
|
|
2985
3042
|
this.systemPromptCache = systemPrompt;
|
|
2986
3043
|
this.prepared = true;
|
|
2987
3044
|
return systemPrompt;
|
|
@@ -3116,7 +3173,15 @@ var Agent = class _Agent {
|
|
|
3116
3173
|
} catch (err2) {
|
|
3117
3174
|
if (err2?.code === "budget") return kill("budget");
|
|
3118
3175
|
if (o.signal?.aborted || isAbortError(err2)) return kill("aborted");
|
|
3119
|
-
|
|
3176
|
+
const body = err2?.body ?? err2?.response?.data ?? err2?.error;
|
|
3177
|
+
let bodyStr;
|
|
3178
|
+
try {
|
|
3179
|
+
bodyStr = body && typeof body !== "string" ? JSON.stringify(body).slice(0, 2e3) : body;
|
|
3180
|
+
} catch {
|
|
3181
|
+
bodyStr = void 0;
|
|
3182
|
+
}
|
|
3183
|
+
if (bodyStr && err2 instanceof Error && !err2.message.includes(bodyStr)) err2.detail = bodyStr;
|
|
3184
|
+
log3.error(`chat() failed: ${err2?.message ?? err2}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, err2);
|
|
3120
3185
|
return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err2 };
|
|
3121
3186
|
}
|
|
3122
3187
|
if (o.signal?.aborted) return kill("aborted");
|
|
@@ -3347,20 +3412,28 @@ function stubOldToolResults(messages, keep) {
|
|
|
3347
3412
|
return { ...x, content: `[${x.name ?? "tool"}${where ? ` ${where}` : ""} output elided \u2014 ${lines} lines; re-run the tool to view]` };
|
|
3348
3413
|
});
|
|
3349
3414
|
}
|
|
3350
|
-
var
|
|
3415
|
+
var callIdSet = (msgs) => {
|
|
3416
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3417
|
+
for (const m of msgs) if (m.role === "assistant") for (const tc of m.tool_calls ?? []) ids.add(tc.id);
|
|
3418
|
+
return ids;
|
|
3419
|
+
};
|
|
3351
3420
|
function dropOrphanToolResults(messages) {
|
|
3352
|
-
const
|
|
3421
|
+
const ids = callIdSet(messages);
|
|
3422
|
+
const ok = (m) => m.role !== "tool" || ids.has(m.tool_call_id ?? "");
|
|
3353
3423
|
return messages.every(ok) ? messages : messages.filter(ok);
|
|
3354
3424
|
}
|
|
3355
3425
|
function fitTokenBudget(messages, maxTokens) {
|
|
3356
|
-
|
|
3426
|
+
const per = messages.map((x) => estimateTokens([x]));
|
|
3427
|
+
let total = per.reduce((a, b) => a + b, 0);
|
|
3428
|
+
if (total <= maxTokens) return messages;
|
|
3357
3429
|
const head = messages[0]?.role === "system" ? [messages[0]] : [];
|
|
3358
|
-
let
|
|
3359
|
-
while (
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3430
|
+
let from = head.length;
|
|
3431
|
+
while (from < messages.length && total > maxTokens) total -= per[from++];
|
|
3432
|
+
const ids = callIdSet(messages.slice(from));
|
|
3433
|
+
while (from < messages.length && messages[from].role === "tool" && !ids.has(messages[from].tool_call_id ?? "")) total -= per[from++];
|
|
3434
|
+
if (total > maxTokens)
|
|
3435
|
+
log3.warn(`context ~${total} tok still over maxContextTokens=${maxTokens} after trimming (system head can't be dropped)`);
|
|
3436
|
+
return [...head, ...messages.slice(from)];
|
|
3364
3437
|
}
|
|
3365
3438
|
function compact(m, max, focus) {
|
|
3366
3439
|
const hasSystem = m[0]?.role === "system";
|
|
@@ -3812,11 +3885,11 @@ var Scheduler = class {
|
|
|
3812
3885
|
this.jobs.clear();
|
|
3813
3886
|
}
|
|
3814
3887
|
};
|
|
3815
|
-
function makeScheduleTools(scheduler) {
|
|
3888
|
+
function makeScheduleTools(scheduler, os) {
|
|
3816
3889
|
return [
|
|
3817
3890
|
{
|
|
3818
3891
|
name: "ScheduleTask",
|
|
3819
|
-
description: 'Schedule a prompt to fire automatically
|
|
3892
|
+
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.',
|
|
3820
3893
|
parameters: {
|
|
3821
3894
|
type: "object",
|
|
3822
3895
|
required: ["prompt", "trigger"],
|
|
@@ -3831,15 +3904,22 @@ function makeScheduleTools(scheduler) {
|
|
|
3831
3904
|
cron: { type: "string" }
|
|
3832
3905
|
}
|
|
3833
3906
|
},
|
|
3834
|
-
label: { type: "string", description: "Short label for display (optional)." }
|
|
3907
|
+
label: { type: "string", description: "Short label for display (optional)." },
|
|
3908
|
+
backend: { type: "string", enum: ["auto", "session", "os"], description: "Where the job lives (default auto)." }
|
|
3835
3909
|
}
|
|
3836
3910
|
},
|
|
3837
|
-
async run({ prompt, trigger, label }) {
|
|
3911
|
+
async run({ prompt, trigger, label, backend }) {
|
|
3838
3912
|
try {
|
|
3913
|
+
if (os && os.route(trigger, backend) === "os") {
|
|
3914
|
+
const id2 = `os-${Date.now().toString(36)}`;
|
|
3915
|
+
const mechanism = os.schedule({ id: id2, prompt, sessionId: os.sessionId, cwd: os.cwd, trigger, label });
|
|
3916
|
+
return `Scheduled ${id2}${label ? ` (${label})` : ""} on the OS scheduler (${mechanism}) \u2014 survives quitting; fires \`agentx --resume ${os.sessionId}\` headless.`;
|
|
3917
|
+
}
|
|
3918
|
+
if (backend === "os") return "Error: no OS scheduler available on this platform \u2014 job not created (use the default in-session backend).";
|
|
3839
3919
|
const id = scheduler.add({ prompt, trigger, label });
|
|
3840
3920
|
const job = scheduler.get(id);
|
|
3841
3921
|
const next = scheduler.nextFire(job);
|
|
3842
|
-
return `Scheduled ${id}${label ? ` (${label})` : ""}. Next fire: ${next ? new Date(next).toLocaleString() : "now"}
|
|
3922
|
+
return `Scheduled ${id}${label ? ` (${label})` : ""}. Next fire: ${next ? new Date(next).toLocaleString() : "now"}. (In-session: does not survive quitting.)`;
|
|
3843
3923
|
} catch (e) {
|
|
3844
3924
|
return `Error: ${e?.message ?? e}`;
|
|
3845
3925
|
}
|
|
@@ -3847,24 +3927,28 @@ function makeScheduleTools(scheduler) {
|
|
|
3847
3927
|
},
|
|
3848
3928
|
{
|
|
3849
3929
|
name: "ScheduleList",
|
|
3850
|
-
description: "List all scheduled jobs and their next fire time.",
|
|
3930
|
+
description: "List all scheduled jobs (in-session + OS-backed) and their next fire time.",
|
|
3851
3931
|
parameters: { type: "object", properties: {} },
|
|
3852
3932
|
async run() {
|
|
3933
|
+
const osJobs = os?.list() ?? [];
|
|
3934
|
+
const osLines = osJobs.map((j) => `${j.id} os ${j.mechanism}${j.label ? " " + j.label : ""}`);
|
|
3853
3935
|
const jobs = scheduler.list();
|
|
3854
|
-
if (!jobs.length) return "(no scheduled jobs)";
|
|
3855
|
-
return jobs.map((j) => {
|
|
3936
|
+
if (!jobs.length && !osLines.length) return "(no scheduled jobs)";
|
|
3937
|
+
return [...osLines, ...jobs.map((j) => {
|
|
3856
3938
|
const next = scheduler.nextFire(j);
|
|
3857
3939
|
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}`;
|
|
3858
3940
|
return `${j.id} ${j.status} ${trig} runs:${j.runs} next:${next ? new Date(next).toLocaleTimeString() : "\u2014"}${j.label ? " " + j.label : ""}`;
|
|
3859
|
-
}).join("\n");
|
|
3941
|
+
})].join("\n");
|
|
3860
3942
|
}
|
|
3861
3943
|
},
|
|
3862
3944
|
{
|
|
3863
3945
|
name: "ScheduleCancel",
|
|
3864
|
-
description: "Cancel a scheduled job by id.",
|
|
3946
|
+
description: "Cancel a scheduled job by id (in-session or OS-backed).",
|
|
3865
3947
|
parameters: { type: "object", required: ["id"], properties: { id: { type: "string" } } },
|
|
3866
3948
|
async run({ id }) {
|
|
3867
|
-
|
|
3949
|
+
const key = String(id);
|
|
3950
|
+
if (key.startsWith("os-")) return os?.cancel(key) ? `Cancelled ${key} (OS job removed).` : `Error: no OS job '${key}'.`;
|
|
3951
|
+
return scheduler.cancel(key) ? `Cancelled ${key}.` : `Error: no scheduled job '${key}'. Use ScheduleList to see jobs.`;
|
|
3868
3952
|
}
|
|
3869
3953
|
},
|
|
3870
3954
|
{
|
|
@@ -4162,6 +4246,9 @@ var DuplexAgentOptions = class {
|
|
|
4162
4246
|
askRelay = false;
|
|
4163
4247
|
/** Parked questions auto-resolve empty after this long (callers map '' to deny/best-judgment). */
|
|
4164
4248
|
askTimeoutMs = 12e4;
|
|
4249
|
+
/** Max retained task records: oldest SETTLED tasks (and their activity tails) are evicted past this,
|
|
4250
|
+
* bounding memory over a long-lived session. Running tasks are never evicted. */
|
|
4251
|
+
maxTaskRecords = 50;
|
|
4165
4252
|
/** Host overrides for QuickLook lookups (keyed by `what`). The engine's defaults go through the
|
|
4166
4253
|
* (possibly jailed) fs — e.g. `.git/**` is deny-listed, so the CLI supplies 'branch' itself. */
|
|
4167
4254
|
quickLook;
|
|
@@ -4452,6 +4539,11 @@ ${recent}` : brief) + verify;
|
|
|
4452
4539
|
};
|
|
4453
4540
|
const promise = new Agent(agentOpts).run(briefText).then((res) => this.maybeVerify(id, briefText, res, tier, agentOpts)).then((res) => this.onWorkerSettled(id, res)).catch((err2) => this.onWorkerFailed(id, err2));
|
|
4454
4541
|
this.tasks.set(id, { id, label, status: "running", controller, promise, tail });
|
|
4542
|
+
if (this.tasks.size > this.options.maxTaskRecords)
|
|
4543
|
+
for (const [tid, rec] of this.tasks) {
|
|
4544
|
+
if (this.tasks.size <= this.options.maxTaskRecords) break;
|
|
4545
|
+
if (rec.status !== "running") this.tasks.delete(tid);
|
|
4546
|
+
}
|
|
4455
4547
|
}
|
|
4456
4548
|
/** Fresh-context check of a successful Act task: a NEW agent (same model/fs/tools, but NO shared
|
|
4457
4549
|
* conversation context) re-reads the file state against the brief and fixes any gap. The fix lands
|
|
@@ -6052,15 +6144,101 @@ function defaultOpenBrowser(url) {
|
|
|
6052
6144
|
|
|
6053
6145
|
// cli/core.ts
|
|
6054
6146
|
import { randomUUID } from "crypto";
|
|
6147
|
+
import { execFile as execFile2 } from "child_process";
|
|
6055
6148
|
import { resolve, basename, join as join3 } from "path";
|
|
6056
|
-
import { existsSync as
|
|
6057
|
-
import { platform, arch, release, userInfo, homedir, tmpdir } from "os";
|
|
6149
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
6150
|
+
import { platform, arch, release, userInfo, homedir as homedir2, tmpdir } from "os";
|
|
6058
6151
|
init_tools_shell();
|
|
6152
|
+
|
|
6153
|
+
// src/tools.notify.ts
|
|
6154
|
+
init_logging();
|
|
6155
|
+
import { execFile } from "child_process";
|
|
6156
|
+
var log13 = forComponent("notify");
|
|
6157
|
+
function makeNotifyTool(opts = {}) {
|
|
6158
|
+
const platform2 = opts.platform ?? process.platform;
|
|
6159
|
+
const run = opts.exec ?? execFile;
|
|
6160
|
+
return {
|
|
6161
|
+
name: "PushNotification",
|
|
6162
|
+
description: "Show an OS desktop notification to the user (out-of-band alert \u2014 e.g. a long task finished, input is needed). Use sparingly: only for events the user would want to be interrupted for.",
|
|
6163
|
+
parameters: {
|
|
6164
|
+
type: "object",
|
|
6165
|
+
required: ["message"],
|
|
6166
|
+
properties: {
|
|
6167
|
+
message: { type: "string", description: "notification body" },
|
|
6168
|
+
title: { type: "string", description: 'notification title (default "agentx")' }
|
|
6169
|
+
}
|
|
6170
|
+
},
|
|
6171
|
+
async run({ message, title }) {
|
|
6172
|
+
const msg = String(message ?? "").slice(0, 256);
|
|
6173
|
+
const head = String(title ?? "agentx").slice(0, 64);
|
|
6174
|
+
if (!msg) return "Error: empty message";
|
|
6175
|
+
const argv = platform2 === "darwin" ? ["osascript", ["-e", `display notification ${JSON.stringify(msg)} with title ${JSON.stringify(head)}`]] : platform2 === "linux" ? ["notify-send", [head, msg]] : null;
|
|
6176
|
+
if (!argv) return `Notifications unavailable on ${platform2}.`;
|
|
6177
|
+
return new Promise((resolve4) => {
|
|
6178
|
+
run(argv[0], argv[1], { timeout: 5e3 }, (e) => {
|
|
6179
|
+
if (e) {
|
|
6180
|
+
log13.debug("notification failed", e);
|
|
6181
|
+
resolve4(`Notification failed: ${e.message}`);
|
|
6182
|
+
} else resolve4("Notification shown.");
|
|
6183
|
+
});
|
|
6184
|
+
});
|
|
6185
|
+
}
|
|
6186
|
+
};
|
|
6187
|
+
}
|
|
6188
|
+
|
|
6189
|
+
// cli/core.ts
|
|
6059
6190
|
import { BodDB as BodDB2 } from "@bod.ee/db";
|
|
6191
|
+
|
|
6192
|
+
// cli/util.ts
|
|
6193
|
+
init_logging();
|
|
6194
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
6195
|
+
import { homedir } from "os";
|
|
6196
|
+
var log14 = forComponent("cli-util");
|
|
6197
|
+
function dotDirs(base, sub, opts = {}) {
|
|
6198
|
+
const home = opts.home ?? homedir();
|
|
6199
|
+
const dirs = [`${base}/.agent/${sub}`, `${base}/.claude/${sub}`, `${home}/.agent/${sub}`, `${home}/.claude/${sub}`];
|
|
6200
|
+
return opts.existing ? dirs.filter((d) => existsSync2(d)) : dirs;
|
|
6201
|
+
}
|
|
6202
|
+
function truncate(s, n, suffix = "\u2026") {
|
|
6203
|
+
const t = s ?? "";
|
|
6204
|
+
return t.length > n ? t.slice(0, n) + suffix : t;
|
|
6205
|
+
}
|
|
6206
|
+
function sanitizeLabel(s, max = 60) {
|
|
6207
|
+
return truncate(s.replace(/\s+/g, " ").trim(), max, "");
|
|
6208
|
+
}
|
|
6209
|
+
function parseJson(text, fallback, what = "json") {
|
|
6210
|
+
try {
|
|
6211
|
+
return JSON.parse(text);
|
|
6212
|
+
} catch (e) {
|
|
6213
|
+
log14.debug(`parseJson(${what}) failed: ${e.message}`);
|
|
6214
|
+
return fallback;
|
|
6215
|
+
}
|
|
6216
|
+
}
|
|
6217
|
+
function readJsonFile(path, fallback) {
|
|
6218
|
+
if (!existsSync2(path)) return fallback;
|
|
6219
|
+
let text;
|
|
6220
|
+
try {
|
|
6221
|
+
text = readFileSync2(path, "utf8");
|
|
6222
|
+
} catch (e) {
|
|
6223
|
+
log14.debug(`readJsonFile(${path}) unreadable: ${e.message}`);
|
|
6224
|
+
return fallback;
|
|
6225
|
+
}
|
|
6226
|
+
return parseJson(text, fallback, path);
|
|
6227
|
+
}
|
|
6228
|
+
|
|
6229
|
+
// cli/core.ts
|
|
6060
6230
|
var DEFAULT_TOOLS = ["bash", "Read", "Edit", "Write", "Grep", "Glob", "MultiEdit", "ApplyEdits", "RepoMap", "TodoWrite"];
|
|
6061
6231
|
function autoWebTools() {
|
|
6062
6232
|
return ["WebFetch", "WebSearch"];
|
|
6063
6233
|
}
|
|
6234
|
+
function pdfTextViaPoppler(path) {
|
|
6235
|
+
return new Promise((res, rej) => {
|
|
6236
|
+
execFile2("pdftotext", [path, "-"], { maxBuffer: 32 * 1024 * 1024, timeout: 3e4 }, (e, stdout) => {
|
|
6237
|
+
if (e) rej(new Error(/ENOENT/.test(String(e.code ?? e.message)) ? "pdftotext not installed (brew/apt install poppler)" : e.message));
|
|
6238
|
+
else res(stdout);
|
|
6239
|
+
});
|
|
6240
|
+
});
|
|
6241
|
+
}
|
|
6064
6242
|
var SANDBOX_SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "coverage", ".cache"]);
|
|
6065
6243
|
async function hydrate(from, to, dir = "/") {
|
|
6066
6244
|
let n = 0;
|
|
@@ -6155,33 +6333,27 @@ async function buildAgent(o) {
|
|
|
6155
6333
|
${notes.join("\n")}
|
|
6156
6334
|
Reference files in them by their mount path (the left side).`;
|
|
6157
6335
|
}
|
|
6158
|
-
const dot = (sub) =>
|
|
6336
|
+
const dot = (sub) => existsSync3(`${cwd}/.agent/${sub}`) ? `${cwd}/.agent/${sub}` : void 0;
|
|
6159
6337
|
const dots = (sub) => {
|
|
6160
|
-
const
|
|
6161
|
-
const dirs = [
|
|
6162
|
-
dot(sub),
|
|
6163
|
-
existsSync2(`${cwd}/.claude/${sub}`) ? `${cwd}/.claude/${sub}` : void 0,
|
|
6164
|
-
existsSync2(`${home}/.agent/${sub}`) ? `${home}/.agent/${sub}` : void 0,
|
|
6165
|
-
existsSync2(`${home}/.claude/${sub}`) ? `${home}/.claude/${sub}` : void 0
|
|
6166
|
-
].filter(Boolean);
|
|
6338
|
+
const dirs = dotDirs(cwd, sub, { existing: true });
|
|
6167
6339
|
return dirs.length ? dirs : void 0;
|
|
6168
6340
|
};
|
|
6169
6341
|
const memoryDir = (() => {
|
|
6170
|
-
const home =
|
|
6342
|
+
const home = homedir2();
|
|
6171
6343
|
const projectDir = `${cwd}/.agent/memory`;
|
|
6172
6344
|
const readDirs = [
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6345
|
+
existsSync3(projectDir) ? projectDir : void 0,
|
|
6346
|
+
existsSync3(`${cwd}/.claude/memory`) ? `${cwd}/.claude/memory` : void 0,
|
|
6347
|
+
existsSync3(`${home}/.agent/memory`) ? `${home}/.agent/memory` : void 0,
|
|
6348
|
+
existsSync3(`${home}/.claude/memory`) ? `${home}/.claude/memory` : void 0
|
|
6177
6349
|
].filter(Boolean);
|
|
6178
6350
|
return readDirs[0] === projectDir ? readDirs : [projectDir, ...readDirs];
|
|
6179
6351
|
})();
|
|
6180
6352
|
const memoryUserDir = (() => {
|
|
6181
|
-
const home =
|
|
6353
|
+
const home = homedir2();
|
|
6182
6354
|
return [
|
|
6183
|
-
|
|
6184
|
-
|
|
6355
|
+
existsSync3(`${home}/.agent/memory`) ? `${home}/.agent/memory` : void 0,
|
|
6356
|
+
existsSync3(`${home}/.claude/memory`) ? `${home}/.claude/memory` : void 0
|
|
6185
6357
|
].find(Boolean) ?? `${home}/.agent/memory`;
|
|
6186
6358
|
})();
|
|
6187
6359
|
const memoryWriteDir = memoryDir[0];
|
|
@@ -6198,6 +6370,8 @@ Reference files in them by their mount path (the left side).`;
|
|
|
6198
6370
|
ai: o.ai,
|
|
6199
6371
|
fs,
|
|
6200
6372
|
model: o.model ?? "anthropic/claude-sonnet-4-6",
|
|
6373
|
+
// PDF reads (disk mode only — VFS paths aren't real files): poppler's pdftotext when installed.
|
|
6374
|
+
...!virtual ? { pdfText: pdfTextViaPoppler } : {},
|
|
6201
6375
|
// Anchor cursor to the launch dir (its adapter defaults to TMPDIR otherwise) and forward the
|
|
6202
6376
|
// host's MCP servers so the delegated cursor agent runs in the same environment. Gated to cursor:
|
|
6203
6377
|
// openai/google adapters Object.assign providerOptions into the request body, so a blanket cwd
|
|
@@ -6240,6 +6414,7 @@ The filesystem root '/' is the real machine root \u2014 you have full filesystem
|
|
|
6240
6414
|
const base = toolsByName([...o.tools ?? DEFAULT_TOOLS, ...autoWebTools()]);
|
|
6241
6415
|
const tail = [...o.extraTools ?? []];
|
|
6242
6416
|
if (scratch) tail.push(makeAskTool({ fs, ai: o.ai, model: o.scratchAskModel ?? o.model ?? "anthropic/claude-sonnet-4-6", dir: scratchDir }));
|
|
6417
|
+
tail.push(makeNotifyTool());
|
|
6243
6418
|
if (!realShell.length) return [...base, ...tail];
|
|
6244
6419
|
const filtered = base.filter((t) => t.name !== "bash");
|
|
6245
6420
|
return [...filtered, ...realShell, ...tail];
|
|
@@ -6291,11 +6466,11 @@ var trunc = (s, n) => (s == null ? "" : String(s).length > n ? String(s).slice(0
|
|
|
6291
6466
|
// cli/voice.ts
|
|
6292
6467
|
init_logging();
|
|
6293
6468
|
import { spawn as spawn2, spawnSync } from "child_process";
|
|
6294
|
-
import { existsSync as
|
|
6295
|
-
import { homedir as
|
|
6469
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, statSync as statSync2 } from "fs";
|
|
6470
|
+
import { homedir as homedir3 } from "os";
|
|
6296
6471
|
import { dirname as dirname3, join as join4 } from "path";
|
|
6297
6472
|
import { fileURLToPath } from "url";
|
|
6298
|
-
var
|
|
6473
|
+
var log15 = forComponent("VoiceIO");
|
|
6299
6474
|
var now4 = () => performance.now();
|
|
6300
6475
|
var Player = class {
|
|
6301
6476
|
proc = null;
|
|
@@ -6309,7 +6484,7 @@ var Player = class {
|
|
|
6309
6484
|
["-loglevel", "quiet", "-nodisp", "-fflags", "nobuffer", "-flags", "low_delay", "-probesize", "32", "-f", "s16le", "-ar", String(TTS_SAMPLE_RATE), "-ch_layout", "mono", "-i", "-"],
|
|
6310
6485
|
{ stdio: ["pipe", "ignore", "ignore"] }
|
|
6311
6486
|
);
|
|
6312
|
-
this.proc.on("error", (e) =>
|
|
6487
|
+
this.proc.on("error", (e) => log15.warn(`ffplay error: ${e.message}`));
|
|
6313
6488
|
this.proc.stdin.on("error", () => {
|
|
6314
6489
|
});
|
|
6315
6490
|
this.bytesWritten = 0;
|
|
@@ -6344,28 +6519,28 @@ function detectFfmpegMic() {
|
|
|
6344
6519
|
const devices = [...audio.matchAll(/\[(\d+)\] (.+)/g)].map(([, idx, name]) => ({ idx, name: name.trim() }));
|
|
6345
6520
|
const mic = devices.find((d) => /microphone|built-in/i.test(d.name) && !/teams|blackhole|loopback/i.test(d.name)) ?? devices[0];
|
|
6346
6521
|
if (!mic) throw new Error("no audio input device found");
|
|
6347
|
-
|
|
6522
|
+
log15.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
|
|
6348
6523
|
return `:${mic.idx}`;
|
|
6349
6524
|
}
|
|
6350
6525
|
function resolveAecBinary() {
|
|
6351
6526
|
if (process.env.MIC_AEC === "0" || process.platform !== "darwin") return null;
|
|
6352
6527
|
const src = join4(nativeDir(), "mic-aec.swift");
|
|
6353
6528
|
const plist = join4(nativeDir(), "Info.plist");
|
|
6354
|
-
if (!
|
|
6355
|
-
const cacheDir = join4(
|
|
6529
|
+
if (!existsSync4(src)) return null;
|
|
6530
|
+
const cacheDir = join4(homedir3(), ".agent", "cache");
|
|
6356
6531
|
const bin = join4(cacheDir, "mic-aec");
|
|
6357
|
-
if (
|
|
6532
|
+
if (existsSync4(bin) && statSync2(bin).mtimeMs >= statSync2(src).mtimeMs) return bin;
|
|
6358
6533
|
if (spawnSync("which", ["swiftc"]).status !== 0) return null;
|
|
6359
6534
|
mkdirSync3(cacheDir, { recursive: true });
|
|
6360
|
-
|
|
6535
|
+
log15.info("compiling AEC mic helper (first run)\u2026");
|
|
6361
6536
|
const build = spawnSync("swiftc", ["-O", "-o", bin, src, "-Xlinker", "-sectcreate", "-Xlinker", "__TEXT", "-Xlinker", "__info_plist", "-Xlinker", plist], { encoding: "utf8" });
|
|
6362
6537
|
if (build.status !== 0) {
|
|
6363
|
-
|
|
6538
|
+
log15.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
|
|
6364
6539
|
return null;
|
|
6365
6540
|
}
|
|
6366
6541
|
const sign = spawnSync("codesign", ["-fs", "-", bin], { encoding: "utf8" });
|
|
6367
6542
|
if (sign.status !== 0) {
|
|
6368
|
-
|
|
6543
|
+
log15.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
|
|
6369
6544
|
return null;
|
|
6370
6545
|
}
|
|
6371
6546
|
return bin;
|
|
@@ -6384,16 +6559,16 @@ var NodeMicSource = class {
|
|
|
6384
6559
|
this.proc = spawn2(this.bin, [], { stdio: ["ignore", "pipe", "ignore"] });
|
|
6385
6560
|
} else {
|
|
6386
6561
|
if (spawnSync("which", ["ffmpeg"]).status !== 0) throw new Error("voice I/O unavailable: no AEC helper and no ffmpeg on PATH");
|
|
6387
|
-
|
|
6562
|
+
log15.info("mic: raw capture (no AEC) \u2014 echo handled heuristically; headphones recommended");
|
|
6388
6563
|
this.proc = spawn2(
|
|
6389
6564
|
"ffmpeg",
|
|
6390
6565
|
["-loglevel", "error", "-f", "avfoundation", "-i", detectFfmpegMic(), "-ar", String(STT_SAMPLE_RATE), "-ac", "1", "-f", "s16le", "-"],
|
|
6391
6566
|
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
6392
6567
|
);
|
|
6393
|
-
this.proc.stderr.on("data", (d) =>
|
|
6568
|
+
this.proc.stderr.on("data", (d) => log15.warn(`ffmpeg: ${String(d).trim()}`));
|
|
6394
6569
|
}
|
|
6395
6570
|
this.proc.on("exit", (c) => {
|
|
6396
|
-
if (c && !this.stopped)
|
|
6571
|
+
if (c && !this.stopped) log15.error(`mic capture exited (${c}) \u2014 check mic permission / MIC_DEVICE / MIC_AEC=0`);
|
|
6397
6572
|
});
|
|
6398
6573
|
this.proc.stdout.on("data", (chunk) => onChunk(chunk));
|
|
6399
6574
|
}
|
|
@@ -6427,11 +6602,11 @@ var AecDuplexAudio = class {
|
|
|
6427
6602
|
this.proc.stdin.on("error", () => {
|
|
6428
6603
|
});
|
|
6429
6604
|
this.proc.on("exit", (c) => {
|
|
6430
|
-
if (c && !this.stopped)
|
|
6605
|
+
if (c && !this.stopped) log15.error(`aec duplex audio exited (${c}) \u2014 check mic permission / MIC_AEC=0`);
|
|
6431
6606
|
});
|
|
6432
6607
|
this.proc.stdout.on("data", (chunk) => onChunk(chunk));
|
|
6433
6608
|
this.proc.stderr.on("data", (d) => {
|
|
6434
|
-
for (const ln of String(d).split("\n")) if (ln.trim())
|
|
6609
|
+
for (const ln of String(d).split("\n")) if (ln.trim()) log15.debug(`mic-aec: ${ln.trim()}`);
|
|
6435
6610
|
});
|
|
6436
6611
|
}
|
|
6437
6612
|
stop() {
|
|
@@ -6547,15 +6722,15 @@ var VoiceIO = class extends VoiceEngine {
|
|
|
6547
6722
|
};
|
|
6548
6723
|
|
|
6549
6724
|
// cli/config.ts
|
|
6550
|
-
import { homedir as
|
|
6551
|
-
import { existsSync as
|
|
6725
|
+
import { homedir as homedir4 } from "os";
|
|
6726
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
|
|
6552
6727
|
import { join as join5 } from "path";
|
|
6553
6728
|
import { pathToFileURL } from "url";
|
|
6554
6729
|
var FILES = ["config.ts", "config.js", "config.mjs", "config.json"];
|
|
6555
6730
|
async function loadFrom(dir) {
|
|
6556
6731
|
for (const f of FILES) {
|
|
6557
6732
|
const p = join5(dir, ".agent", f);
|
|
6558
|
-
if (!
|
|
6733
|
+
if (!existsSync5(p)) continue;
|
|
6559
6734
|
try {
|
|
6560
6735
|
const mod = await import(pathToFileURL(p).href, f.endsWith(".json") ? { with: { type: "json" } } : void 0);
|
|
6561
6736
|
return mod.default ?? mod.config ?? mod;
|
|
@@ -6568,9 +6743,9 @@ async function loadFrom(dir) {
|
|
|
6568
6743
|
}
|
|
6569
6744
|
function loadSettings(dir) {
|
|
6570
6745
|
const p = join5(dir, ".agent", "settings.json");
|
|
6571
|
-
if (!
|
|
6746
|
+
if (!existsSync5(p)) return {};
|
|
6572
6747
|
try {
|
|
6573
|
-
const raw = JSON.parse(
|
|
6748
|
+
const raw = JSON.parse(readFileSync3(p, "utf8"));
|
|
6574
6749
|
const cfg = {};
|
|
6575
6750
|
if (raw.mcpServers && typeof raw.mcpServers === "object") cfg.mcpServers = raw.mcpServers;
|
|
6576
6751
|
if (raw.permissions && typeof raw.permissions === "object") cfg.permissions = raw.permissions;
|
|
@@ -6591,8 +6766,8 @@ function loadSettings(dir) {
|
|
|
6591
6766
|
}
|
|
6592
6767
|
}
|
|
6593
6768
|
async function loadConfig(cwd) {
|
|
6594
|
-
const userSettings = loadSettings(
|
|
6595
|
-
const user = await loadFrom(
|
|
6769
|
+
const userSettings = loadSettings(homedir4());
|
|
6770
|
+
const user = await loadFrom(homedir4());
|
|
6596
6771
|
const projectSettings = loadSettings(cwd);
|
|
6597
6772
|
const project = await loadFrom(cwd);
|
|
6598
6773
|
const merged = { ...userSettings, ...user, ...projectSettings, ...project };
|
|
@@ -6604,7 +6779,7 @@ async function loadConfig(cwd) {
|
|
|
6604
6779
|
|
|
6605
6780
|
// cli/hooks-config.ts
|
|
6606
6781
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
6607
|
-
var
|
|
6782
|
+
var log16 = forComponent("hooks");
|
|
6608
6783
|
var escapeRegex = (s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
6609
6784
|
function ruleMatches(rule, toolName) {
|
|
6610
6785
|
if (!rule.tool || rule.tool === "*") return true;
|
|
@@ -6621,7 +6796,7 @@ function runCmd(rule, env) {
|
|
|
6621
6796
|
});
|
|
6622
6797
|
return { code: r.status ?? 1, out: ((r.stdout ?? "") + (r.stderr ?? "")).trim() };
|
|
6623
6798
|
} catch (e) {
|
|
6624
|
-
|
|
6799
|
+
log16.debug(`hook command failed: ${rule.command}`, e);
|
|
6625
6800
|
return { code: 1, out: String(e?.message ?? e) };
|
|
6626
6801
|
}
|
|
6627
6802
|
}
|
|
@@ -6725,11 +6900,11 @@ function formatDiff(ops, opts = {}) {
|
|
|
6725
6900
|
}
|
|
6726
6901
|
|
|
6727
6902
|
// cli/session.ts
|
|
6728
|
-
import { existsSync as
|
|
6729
|
-
import { homedir as
|
|
6903
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3, readdirSync, renameSync, symlinkSync, unlinkSync, readlinkSync } from "fs";
|
|
6904
|
+
import { homedir as homedir5 } from "os";
|
|
6730
6905
|
import { join as join6 } from "path";
|
|
6731
|
-
var
|
|
6732
|
-
var globalDir = () => join6(
|
|
6906
|
+
var log17 = forComponent("session");
|
|
6907
|
+
var globalDir = () => join6(homedir5(), ".agent", "sessions");
|
|
6733
6908
|
var SessionStore = class {
|
|
6734
6909
|
dir;
|
|
6735
6910
|
constructor(cwd) {
|
|
@@ -6741,10 +6916,10 @@ var SessionStore = class {
|
|
|
6741
6916
|
const p = (n, w = 2) => String(n).padStart(w, "0");
|
|
6742
6917
|
const slug2 = (cwd ?? process.cwd()).split("/").pop()?.replace(/[^A-Za-z0-9_-]/g, "") || "session";
|
|
6743
6918
|
let id = `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}-${slug2}`;
|
|
6744
|
-
if (
|
|
6919
|
+
if (existsSync6(this.dir) && existsSync6(join6(this.dir, `${id}.json`))) {
|
|
6745
6920
|
for (let i = 2; i <= 99; i++) {
|
|
6746
6921
|
const c = `${id}-${i}`;
|
|
6747
|
-
if (!
|
|
6922
|
+
if (!existsSync6(join6(this.dir, `${c}.json`))) {
|
|
6748
6923
|
id = c;
|
|
6749
6924
|
break;
|
|
6750
6925
|
}
|
|
@@ -6758,43 +6933,43 @@ var SessionStore = class {
|
|
|
6758
6933
|
}
|
|
6759
6934
|
save(data) {
|
|
6760
6935
|
if (!this.safeId(data.meta.id)) throw new Error(`unsafe session id: ${data.meta.id}`);
|
|
6761
|
-
if (!
|
|
6936
|
+
if (!existsSync6(this.dir)) mkdirSync4(this.dir, { recursive: true });
|
|
6762
6937
|
const path = join6(this.dir, `${data.meta.id}.json`);
|
|
6763
6938
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
6764
6939
|
writeFileSync3(tmp, JSON.stringify(data));
|
|
6765
6940
|
renameSync(tmp, path);
|
|
6766
6941
|
try {
|
|
6767
6942
|
const gd = globalDir();
|
|
6768
|
-
if (!
|
|
6943
|
+
if (!existsSync6(gd)) mkdirSync4(gd, { recursive: true });
|
|
6769
6944
|
const link2 = join6(gd, `${data.meta.id}.json`);
|
|
6770
|
-
if (!
|
|
6945
|
+
if (!existsSync6(link2)) symlinkSync(path, link2);
|
|
6771
6946
|
} catch {
|
|
6772
6947
|
}
|
|
6773
6948
|
}
|
|
6774
6949
|
load(id) {
|
|
6775
6950
|
if (!this.safeId(id)) {
|
|
6776
|
-
|
|
6951
|
+
log17.debug(`rejecting unsafe session id: ${id}`);
|
|
6777
6952
|
return void 0;
|
|
6778
6953
|
}
|
|
6779
6954
|
const path = join6(this.dir, `${id}.json`);
|
|
6780
|
-
if (!
|
|
6955
|
+
if (!existsSync6(path)) return void 0;
|
|
6781
6956
|
try {
|
|
6782
|
-
return JSON.parse(
|
|
6957
|
+
return JSON.parse(readFileSync4(path, "utf8"));
|
|
6783
6958
|
} catch (e) {
|
|
6784
|
-
|
|
6959
|
+
log17.debug(`unreadable session ${id} \u2014 ignoring`, e);
|
|
6785
6960
|
return void 0;
|
|
6786
6961
|
}
|
|
6787
6962
|
}
|
|
6788
6963
|
/** All sessions' metadata, most-recently-updated first. */
|
|
6789
6964
|
list() {
|
|
6790
|
-
if (!
|
|
6965
|
+
if (!existsSync6(this.dir)) return [];
|
|
6791
6966
|
const metas = [];
|
|
6792
6967
|
for (const f of readdirSync(this.dir)) {
|
|
6793
6968
|
if (!f.endsWith(".json")) continue;
|
|
6794
6969
|
try {
|
|
6795
|
-
metas.push(JSON.parse(
|
|
6970
|
+
metas.push(JSON.parse(readFileSync4(join6(this.dir, f), "utf8")).meta);
|
|
6796
6971
|
} catch (e) {
|
|
6797
|
-
|
|
6972
|
+
log17.debug(`skipping unreadable session file ${f}`, e);
|
|
6798
6973
|
}
|
|
6799
6974
|
}
|
|
6800
6975
|
return metas.sort((a, b) => b.updated - a.updated);
|
|
@@ -6810,12 +6985,12 @@ var SessionStore = class {
|
|
|
6810
6985
|
};
|
|
6811
6986
|
function globalSessionLoad(idOrPrefix) {
|
|
6812
6987
|
const gd = globalDir();
|
|
6813
|
-
if (!
|
|
6988
|
+
if (!existsSync6(gd)) return void 0;
|
|
6814
6989
|
const exact = join6(gd, `${idOrPrefix}.json`);
|
|
6815
|
-
if (
|
|
6990
|
+
if (existsSync6(exact)) {
|
|
6816
6991
|
try {
|
|
6817
6992
|
const target = readlinkSync(exact);
|
|
6818
|
-
return JSON.parse(
|
|
6993
|
+
return JSON.parse(readFileSync4(target, "utf8"));
|
|
6819
6994
|
} catch {
|
|
6820
6995
|
return void 0;
|
|
6821
6996
|
}
|
|
@@ -6826,7 +7001,7 @@ function globalSessionLoad(idOrPrefix) {
|
|
|
6826
7001
|
const base = f.slice(0, -5);
|
|
6827
7002
|
if (base.includes(idOrPrefix) || base.endsWith(idOrPrefix)) {
|
|
6828
7003
|
const target = readlinkSync(join6(gd, f));
|
|
6829
|
-
return JSON.parse(
|
|
7004
|
+
return JSON.parse(readFileSync4(target, "utf8"));
|
|
6830
7005
|
}
|
|
6831
7006
|
}
|
|
6832
7007
|
} catch {
|
|
@@ -6835,20 +7010,20 @@ function globalSessionLoad(idOrPrefix) {
|
|
|
6835
7010
|
}
|
|
6836
7011
|
function globalSessionList() {
|
|
6837
7012
|
const gd = globalDir();
|
|
6838
|
-
if (!
|
|
7013
|
+
if (!existsSync6(gd)) return [];
|
|
6839
7014
|
const metas = [];
|
|
6840
7015
|
for (const f of readdirSync(gd)) {
|
|
6841
7016
|
if (!f.endsWith(".json")) continue;
|
|
6842
7017
|
try {
|
|
6843
7018
|
const target = readlinkSync(join6(gd, f));
|
|
6844
|
-
if (!
|
|
7019
|
+
if (!existsSync6(target)) {
|
|
6845
7020
|
try {
|
|
6846
7021
|
unlinkSync(join6(gd, f));
|
|
6847
7022
|
} catch {
|
|
6848
7023
|
}
|
|
6849
7024
|
continue;
|
|
6850
7025
|
}
|
|
6851
|
-
metas.push(JSON.parse(
|
|
7026
|
+
metas.push(JSON.parse(readFileSync4(target, "utf8")).meta);
|
|
6852
7027
|
} catch {
|
|
6853
7028
|
}
|
|
6854
7029
|
}
|
|
@@ -6873,7 +7048,7 @@ var CheckpointStack = class {
|
|
|
6873
7048
|
current;
|
|
6874
7049
|
/** Open a new turn frame (call right before sending a user turn). */
|
|
6875
7050
|
begin(label) {
|
|
6876
|
-
this.current = { label: label
|
|
7051
|
+
this.current = { label: sanitizeLabel(label) || "(turn)", at: Date.now(), saved: /* @__PURE__ */ new Map() };
|
|
6877
7052
|
this.frames.push(this.current);
|
|
6878
7053
|
if (this.frames.length > this.max) this.frames.shift();
|
|
6879
7054
|
}
|
|
@@ -6901,6 +7076,24 @@ var CheckpointStack = class {
|
|
|
6901
7076
|
list() {
|
|
6902
7077
|
return this.frames.map((f, i) => ({ index: i, label: f.label, at: f.at, files: f.saved.size })).reverse();
|
|
6903
7078
|
}
|
|
7079
|
+
/** Unified-style diff of all session edits: each file's OLDEST saved content vs its current content. */
|
|
7080
|
+
async diff() {
|
|
7081
|
+
const base = /* @__PURE__ */ new Map();
|
|
7082
|
+
for (const f of this.frames) for (const [path, prior] of f.saved) if (!base.has(path)) base.set(path, prior);
|
|
7083
|
+
const parts = [];
|
|
7084
|
+
for (const [path, prior] of base) {
|
|
7085
|
+
let now5 = null;
|
|
7086
|
+
try {
|
|
7087
|
+
now5 = await this.fs.readFile(path);
|
|
7088
|
+
} catch {
|
|
7089
|
+
}
|
|
7090
|
+
if ((prior ?? "") === (now5 ?? "")) continue;
|
|
7091
|
+
const ops = diffLines(prior ?? "", now5 ?? "");
|
|
7092
|
+
parts.push(`--- ${path}${prior == null ? " (new)" : now5 == null ? " (deleted)" : ""}
|
|
7093
|
+
${formatDiff(ops)}`);
|
|
7094
|
+
}
|
|
7095
|
+
return parts.join("\n");
|
|
7096
|
+
}
|
|
6904
7097
|
/**
|
|
6905
7098
|
* Restore the working tree to BEFORE frame `index` — undo that frame and every later one.
|
|
6906
7099
|
* Frames are replayed newest→oldest so the OLDEST saved content for a path wins (its true
|
|
@@ -6930,12 +7123,12 @@ var CheckpointStack = class {
|
|
|
6930
7123
|
};
|
|
6931
7124
|
|
|
6932
7125
|
// cli/gitCheckpoints.ts
|
|
6933
|
-
import { execFile } from "child_process";
|
|
7126
|
+
import { execFile as execFile3 } from "child_process";
|
|
6934
7127
|
import { promisify } from "util";
|
|
6935
|
-
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as
|
|
7128
|
+
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync7 } from "fs";
|
|
6936
7129
|
import { join as join7, resolve as resolve2, sep as sep2 } from "path";
|
|
6937
|
-
var
|
|
6938
|
-
var exec = promisify(
|
|
7130
|
+
var log18 = forComponent("checkpoints");
|
|
7131
|
+
var exec = promisify(execFile3);
|
|
6939
7132
|
var DEFAULT_EXCLUDE = [".agent/", ".git/", "node_modules/", "dist/", "build/", ".next/", "target/", ".venv/", "__pycache__/", "*.log"];
|
|
6940
7133
|
var ShadowRepo = class {
|
|
6941
7134
|
// undefined = unprobed; false = git/this root unusable
|
|
@@ -6961,14 +7154,14 @@ var ShadowRepo = class {
|
|
|
6961
7154
|
if (this.ready !== void 0) return this.ready;
|
|
6962
7155
|
try {
|
|
6963
7156
|
await exec(this.git, ["--version"]);
|
|
6964
|
-
if (!
|
|
7157
|
+
if (!existsSync7(this.gitDir)) {
|
|
6965
7158
|
mkdirSync5(this.gitDir, { recursive: true });
|
|
6966
7159
|
await this.run("init", "-q");
|
|
6967
7160
|
}
|
|
6968
7161
|
writeFileSync4(join7(this.gitDir, "info", "exclude"), this.exclude.join("\n") + "\n");
|
|
6969
7162
|
this.ready = true;
|
|
6970
7163
|
} catch (e) {
|
|
6971
|
-
|
|
7164
|
+
log18.debug(`git checkpoints unavailable for ${this.workTree}`, e);
|
|
6972
7165
|
this.ready = false;
|
|
6973
7166
|
}
|
|
6974
7167
|
return this.ready;
|
|
@@ -7001,6 +7194,14 @@ var ShadowRepo = class {
|
|
|
7001
7194
|
return 0;
|
|
7002
7195
|
}
|
|
7003
7196
|
}
|
|
7197
|
+
/** Unified diff between `sha` and the current work-tree (for `/diff`). */
|
|
7198
|
+
async diffSince(sha) {
|
|
7199
|
+
try {
|
|
7200
|
+
return await this.run("diff", sha);
|
|
7201
|
+
} catch {
|
|
7202
|
+
return "";
|
|
7203
|
+
}
|
|
7204
|
+
}
|
|
7004
7205
|
/** Restore the tree to `sha`; returns counts of reverted tracked files + removed untracked-new. */
|
|
7005
7206
|
async resetTo(sha) {
|
|
7006
7207
|
let restored = 0, deleted = 0;
|
|
@@ -7031,7 +7232,7 @@ var ShadowRepo = class {
|
|
|
7031
7232
|
await this.run("gc", "--auto", "-q").catch(() => {
|
|
7032
7233
|
});
|
|
7033
7234
|
} catch (e) {
|
|
7034
|
-
|
|
7235
|
+
log18.debug("checkpoint prune failed", e);
|
|
7035
7236
|
}
|
|
7036
7237
|
}
|
|
7037
7238
|
};
|
|
@@ -7088,18 +7289,18 @@ var GitCheckpoints = class {
|
|
|
7088
7289
|
use(sessionId) {
|
|
7089
7290
|
if (sessionId === this.session) return;
|
|
7090
7291
|
this.session = sessionId;
|
|
7091
|
-
if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) =>
|
|
7292
|
+
if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) => log18.debug("re-point failed", e));
|
|
7092
7293
|
}
|
|
7093
7294
|
async begin(label) {
|
|
7094
7295
|
if (!await this.start()) return;
|
|
7095
|
-
const msg = label
|
|
7296
|
+
const msg = sanitizeLabel(label, 72) || "(turn)";
|
|
7096
7297
|
let slow;
|
|
7097
7298
|
if (!this.snapshotted) slow = setTimeout(() => process.stderr.write("\x1B[2m checkpointing initial workspace snapshot\u2026\x1B[0m\n"), 1500);
|
|
7098
7299
|
for (const r of this.repos) {
|
|
7099
7300
|
try {
|
|
7100
7301
|
await r.commit(msg);
|
|
7101
7302
|
} catch (e) {
|
|
7102
|
-
|
|
7303
|
+
log18.debug("checkpoint commit failed", e);
|
|
7103
7304
|
}
|
|
7104
7305
|
}
|
|
7105
7306
|
if (slow) clearTimeout(slow);
|
|
@@ -7123,6 +7324,16 @@ var GitCheckpoints = class {
|
|
|
7123
7324
|
get size() {
|
|
7124
7325
|
return this.caches[0]?.length ?? 0;
|
|
7125
7326
|
}
|
|
7327
|
+
/** Unified diff of everything this session changed: oldest session checkpoint → current tree, across all roots. */
|
|
7328
|
+
async diff() {
|
|
7329
|
+
if (!this.caches[0]?.length) await this.refresh();
|
|
7330
|
+
const parts = [];
|
|
7331
|
+
for (let i = 0; i < this.repos.length; i++) {
|
|
7332
|
+
const base = this.caches[i]?.[0];
|
|
7333
|
+
if (base) parts.push(await this.repos[i].diffSince(base.sha));
|
|
7334
|
+
}
|
|
7335
|
+
return parts.filter(Boolean).join("\n");
|
|
7336
|
+
}
|
|
7126
7337
|
async rewindTo(index) {
|
|
7127
7338
|
if (!this.caches[0]?.length) await this.refresh();
|
|
7128
7339
|
if (index < 0 || index >= (this.caches[0]?.length ?? 0)) throw new Error("no such checkpoint");
|
|
@@ -7156,8 +7367,8 @@ var GitCheckpointsOptions = class {
|
|
|
7156
7367
|
};
|
|
7157
7368
|
|
|
7158
7369
|
// cli/permissions.ts
|
|
7159
|
-
import {
|
|
7160
|
-
import { homedir as
|
|
7370
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync6 } from "fs";
|
|
7371
|
+
import { homedir as homedir6 } from "os";
|
|
7161
7372
|
import { join as join8 } from "path";
|
|
7162
7373
|
var RULE_RE = /^(\w+)(?:\((.+)\))?$/;
|
|
7163
7374
|
function parseOne(raw, decision) {
|
|
@@ -7182,25 +7393,15 @@ function describeRule(r) {
|
|
|
7182
7393
|
}
|
|
7183
7394
|
var PERM_FILE = (cwd) => join8(cwd, ".agent", "permissions.json");
|
|
7184
7395
|
function loadPersistedRules(cwd) {
|
|
7185
|
-
const
|
|
7186
|
-
|
|
7187
|
-
try {
|
|
7188
|
-
const j = JSON.parse(readFileSync4(p, "utf8"));
|
|
7189
|
-
return { allow: j.allow ?? [], ask: j.ask ?? [], deny: j.deny ?? [] };
|
|
7190
|
-
} catch {
|
|
7191
|
-
return {};
|
|
7192
|
-
}
|
|
7396
|
+
const j = readJsonFile(PERM_FILE(cwd), null);
|
|
7397
|
+
return j ? { allow: j.allow ?? [], ask: j.ask ?? [], deny: j.deny ?? [] } : {};
|
|
7193
7398
|
}
|
|
7194
|
-
function loadClaudeSettings(cwd, home =
|
|
7399
|
+
function loadClaudeSettings(cwd, home = homedir6()) {
|
|
7195
7400
|
const files = [join8(home, ".claude", "settings.json"), join8(cwd, ".claude", "settings.json"), join8(cwd, ".claude", "settings.local.json")];
|
|
7196
7401
|
let out = {};
|
|
7197
7402
|
for (const p of files) {
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
const perms = JSON.parse(readFileSync4(p, "utf8"))?.permissions;
|
|
7201
|
-
if (perms) out = mergePerms(out, { allow: perms.allow, ask: perms.ask, deny: perms.deny }) ?? out;
|
|
7202
|
-
} catch {
|
|
7203
|
-
}
|
|
7403
|
+
const perms = readJsonFile(p, null)?.permissions;
|
|
7404
|
+
if (perms) out = mergePerms(out, { allow: perms.allow, ask: perms.ask, deny: perms.deny }) ?? out;
|
|
7204
7405
|
}
|
|
7205
7406
|
return out;
|
|
7206
7407
|
}
|
|
@@ -7223,20 +7424,14 @@ function mergePerms(a, b) {
|
|
|
7223
7424
|
}
|
|
7224
7425
|
return Object.keys(out).length ? out : void 0;
|
|
7225
7426
|
}
|
|
7226
|
-
var TRUST_FILE = join8(
|
|
7427
|
+
var TRUST_FILE = join8(homedir6(), ".agent", "trusted.json");
|
|
7227
7428
|
function isTrusted(cwd, file = TRUST_FILE) {
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
} catch {
|
|
7231
|
-
return false;
|
|
7232
|
-
}
|
|
7429
|
+
const list = readJsonFile(file, []);
|
|
7430
|
+
return Array.isArray(list) && list.includes(cwd);
|
|
7233
7431
|
}
|
|
7234
7432
|
function trustDir(cwd, file = TRUST_FILE) {
|
|
7235
|
-
let list = [];
|
|
7236
|
-
|
|
7237
|
-
if (existsSync7(file)) list = JSON.parse(readFileSync4(file, "utf8"));
|
|
7238
|
-
} catch {
|
|
7239
|
-
}
|
|
7433
|
+
let list = readJsonFile(file, []);
|
|
7434
|
+
if (!Array.isArray(list)) list = [];
|
|
7240
7435
|
if (!list.includes(cwd)) list.push(cwd);
|
|
7241
7436
|
try {
|
|
7242
7437
|
mkdirSync6(join8(file, ".."), { recursive: true });
|
|
@@ -7291,14 +7486,18 @@ function completePath(listDir, ref) {
|
|
|
7291
7486
|
for (const e of entries) {
|
|
7292
7487
|
if (!fuzzy(base, e.name)) continue;
|
|
7293
7488
|
if (e.name.startsWith(".") && !base.startsWith(".")) continue;
|
|
7294
|
-
const rel = dir ? `${dir}/${e.name}` : e.name;
|
|
7295
|
-
matched.push(
|
|
7489
|
+
const rel = (dir ? `${dir}/${e.name}` : e.name) + (e.dir ? "/" : "");
|
|
7490
|
+
matched.push(/\s/.test(rel) ? `@"${rel}"` : "@" + rel);
|
|
7296
7491
|
}
|
|
7297
7492
|
return rank(matched, "@" + (dir ? dir + "/" : "") + base);
|
|
7298
7493
|
}
|
|
7299
7494
|
|
|
7300
7495
|
// cli/lineEditor.ts
|
|
7301
7496
|
import { emitKeypressEvents } from "readline";
|
|
7497
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
7498
|
+
import { writeFileSync as writeFileSync6, readFileSync as readFileSync5, unlinkSync as unlinkSync2 } from "fs";
|
|
7499
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
7500
|
+
import { join as join9 } from "path";
|
|
7302
7501
|
|
|
7303
7502
|
// cli/bidi.ts
|
|
7304
7503
|
var RTL_RE = /[\u0590-\u05ff\u0600-\u06ff\u0750-\u077f\u08a0-\u08ff\ufb1d-\ufdff\ufe70-\ufeff]/;
|
|
@@ -8138,6 +8337,30 @@ function createLineEditor(out) {
|
|
|
8138
8337
|
if (cursorCol > 0) out.write(`\x1B[${cursorCol}C`);
|
|
8139
8338
|
curRow = cursorRow;
|
|
8140
8339
|
}
|
|
8340
|
+
function externalEdit(s) {
|
|
8341
|
+
const spec = process.env.VISUAL || process.env.EDITOR || "vi";
|
|
8342
|
+
const [cmd, ...cargs] = spec.split(" ").filter(Boolean);
|
|
8343
|
+
const file = join9(tmpdir2(), `agentx-edit-${process.pid}-${Date.now()}.md`);
|
|
8344
|
+
try {
|
|
8345
|
+
writeFileSync6(file, s.buf);
|
|
8346
|
+
process.stdin.setRawMode(false);
|
|
8347
|
+
out.write("\x1B[?2004l");
|
|
8348
|
+
const r = spawnSync3(cmd, [...cargs, file], { stdio: "inherit" });
|
|
8349
|
+
if (r.status === 0) {
|
|
8350
|
+
const text = readFileSync5(file, "utf8").replace(/\n$/, "");
|
|
8351
|
+
s.reset();
|
|
8352
|
+
if (text) s.insert(text);
|
|
8353
|
+
}
|
|
8354
|
+
} catch {
|
|
8355
|
+
} finally {
|
|
8356
|
+
try {
|
|
8357
|
+
unlinkSync2(file);
|
|
8358
|
+
} catch {
|
|
8359
|
+
}
|
|
8360
|
+
process.stdin.setRawMode(true);
|
|
8361
|
+
out.write("\x1B[?2004h");
|
|
8362
|
+
}
|
|
8363
|
+
}
|
|
8141
8364
|
async function readLine(opts) {
|
|
8142
8365
|
const maxVisible = opts.maxVisible ?? 8;
|
|
8143
8366
|
if (!isTTY) return readPlainLine();
|
|
@@ -8174,6 +8397,7 @@ function createLineEditor(out) {
|
|
|
8174
8397
|
lastStatus = cur;
|
|
8175
8398
|
redraw();
|
|
8176
8399
|
}, opts.statusTickMs) : void 0;
|
|
8400
|
+
let chordCtrlX = false;
|
|
8177
8401
|
const onKey = (str, key) => {
|
|
8178
8402
|
if (key?.ctrl && key.name === "l") {
|
|
8179
8403
|
out.write("\x1B[2J\x1B[3J\x1B[H");
|
|
@@ -8181,6 +8405,19 @@ function createLineEditor(out) {
|
|
|
8181
8405
|
redraw();
|
|
8182
8406
|
return;
|
|
8183
8407
|
}
|
|
8408
|
+
if (key?.ctrl && key.name === "x" && !s.pasting) {
|
|
8409
|
+
chordCtrlX = true;
|
|
8410
|
+
return;
|
|
8411
|
+
}
|
|
8412
|
+
if (chordCtrlX) {
|
|
8413
|
+
chordCtrlX = false;
|
|
8414
|
+
if (key?.ctrl && key.name === "e") {
|
|
8415
|
+
externalEdit(s);
|
|
8416
|
+
curRow = 0;
|
|
8417
|
+
redraw();
|
|
8418
|
+
return;
|
|
8419
|
+
}
|
|
8420
|
+
}
|
|
8184
8421
|
if (key?.name === "tab" && key.shift && opts.onCyclePosture) {
|
|
8185
8422
|
opts.onCyclePosture();
|
|
8186
8423
|
redraw();
|
|
@@ -8686,6 +8923,184 @@ var MarkdownStream = class {
|
|
|
8686
8923
|
}
|
|
8687
8924
|
};
|
|
8688
8925
|
|
|
8926
|
+
// cli/osScheduler.ts
|
|
8927
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
8928
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, readdirSync as readdirSync2, unlinkSync as unlinkSync3, chmodSync, existsSync as existsSync8 } from "fs";
|
|
8929
|
+
import { homedir as homedir7 } from "os";
|
|
8930
|
+
import { join as join10 } from "path";
|
|
8931
|
+
var log19 = forComponent("os-sched");
|
|
8932
|
+
var OsScheduler = class {
|
|
8933
|
+
options;
|
|
8934
|
+
constructor(options) {
|
|
8935
|
+
this.options = { ...new OsSchedulerOptions(), ...options };
|
|
8936
|
+
}
|
|
8937
|
+
get dir() {
|
|
8938
|
+
return join10(this.options.home, ".agent", "sched");
|
|
8939
|
+
}
|
|
8940
|
+
label(id) {
|
|
8941
|
+
return `cc.livx.agentx.sched-${id}`;
|
|
8942
|
+
}
|
|
8943
|
+
plistPath(id) {
|
|
8944
|
+
return join10(this.options.home, "Library", "LaunchAgents", `${this.label(id)}.plist`);
|
|
8945
|
+
}
|
|
8946
|
+
run(cmd, args, input) {
|
|
8947
|
+
return this.options.exec(cmd, args, input);
|
|
8948
|
+
}
|
|
8949
|
+
available() {
|
|
8950
|
+
return this.options.platform === "darwin" || this.options.platform === "linux";
|
|
8951
|
+
}
|
|
8952
|
+
/** Register the job with the OS. Returns a human description of the mechanism used. Throws on failure. */
|
|
8953
|
+
schedule(spec) {
|
|
8954
|
+
if (!this.available()) throw new Error(`no OS scheduler on ${this.options.platform}`);
|
|
8955
|
+
mkdirSync7(this.dir, { recursive: true });
|
|
8956
|
+
const oneOff = "at" in spec.trigger;
|
|
8957
|
+
const script = this.writeScript(spec, oneOff);
|
|
8958
|
+
const mechanism = this.options.platform === "darwin" ? this.scheduleDarwin(spec, script) : this.scheduleLinux(spec, script, oneOff);
|
|
8959
|
+
const meta = { ...spec, created: Date.now(), mechanism };
|
|
8960
|
+
writeFileSync7(join10(this.dir, `${spec.id}.json`), JSON.stringify(meta, null, 2));
|
|
8961
|
+
return mechanism;
|
|
8962
|
+
}
|
|
8963
|
+
cancel(id) {
|
|
8964
|
+
const meta = readJsonFile(join10(this.dir, `${id}.json`), null);
|
|
8965
|
+
if (!meta) return false;
|
|
8966
|
+
try {
|
|
8967
|
+
if (this.options.platform === "darwin") {
|
|
8968
|
+
try {
|
|
8969
|
+
this.run("launchctl", ["remove", this.label(id)]);
|
|
8970
|
+
} catch {
|
|
8971
|
+
}
|
|
8972
|
+
try {
|
|
8973
|
+
unlinkSync3(this.plistPath(id));
|
|
8974
|
+
} catch {
|
|
8975
|
+
}
|
|
8976
|
+
} else if (meta.mechanism.startsWith("crontab")) {
|
|
8977
|
+
const cur = (() => {
|
|
8978
|
+
try {
|
|
8979
|
+
return this.run("crontab", ["-l"]);
|
|
8980
|
+
} catch {
|
|
8981
|
+
return "";
|
|
8982
|
+
}
|
|
8983
|
+
})();
|
|
8984
|
+
const next = cur.split("\n").filter((l) => !l.includes(`# agentx-sched-${id}`)).join("\n");
|
|
8985
|
+
this.run("crontab", ["-"], next.trim() ? next.trimEnd() + "\n" : "");
|
|
8986
|
+
} else if (meta.mechanism.startsWith("at:")) {
|
|
8987
|
+
try {
|
|
8988
|
+
this.run("atrm", [meta.mechanism.slice(3)]);
|
|
8989
|
+
} catch {
|
|
8990
|
+
}
|
|
8991
|
+
}
|
|
8992
|
+
} catch (e) {
|
|
8993
|
+
log19.debug(`cancel ${id}`, e);
|
|
8994
|
+
}
|
|
8995
|
+
for (const f of [`${id}.json`, `${id}.sh`]) {
|
|
8996
|
+
try {
|
|
8997
|
+
unlinkSync3(join10(this.dir, f));
|
|
8998
|
+
} catch {
|
|
8999
|
+
}
|
|
9000
|
+
}
|
|
9001
|
+
return true;
|
|
9002
|
+
}
|
|
9003
|
+
list() {
|
|
9004
|
+
if (!existsSync8(this.dir)) return [];
|
|
9005
|
+
return readdirSync2(this.dir).filter((f) => f.endsWith(".json")).map((f) => readJsonFile(join10(this.dir, f), null)).filter(Boolean);
|
|
9006
|
+
}
|
|
9007
|
+
/** The per-job runner script: cd to the project, headless-resume the session, log, notify. */
|
|
9008
|
+
writeScript(spec, oneOff) {
|
|
9009
|
+
const p = join10(this.dir, `${spec.id}.sh`);
|
|
9010
|
+
const q2 = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
|
|
9011
|
+
const cleanup = oneOff ? this.options.platform === "darwin" ? `launchctl remove ${this.label(spec.id)} 2>/dev/null; rm -f ${q2(this.plistPath(spec.id))} ${q2(join10(this.dir, `${spec.id}.json`))} ${q2(p)}
|
|
9012
|
+
` : `rm -f ${q2(join10(this.dir, `${spec.id}.json`))} ${q2(p)}
|
|
9013
|
+
` : "";
|
|
9014
|
+
writeFileSync7(p, `#!/bin/sh
|
|
9015
|
+
# agentx scheduled job ${spec.id}${spec.label ? ` \u2014 ${spec.label}` : ""}
|
|
9016
|
+
cd ${q2(spec.cwd)} || exit 1
|
|
9017
|
+
${this.options.agentx} -p ${q2(spec.prompt)} --resume ${q2(spec.sessionId)} --yes >> ${q2(join10(this.dir, `${spec.id}.log`))} 2>&1
|
|
9018
|
+
${cleanup}`);
|
|
9019
|
+
chmodSync(p, 493);
|
|
9020
|
+
return p;
|
|
9021
|
+
}
|
|
9022
|
+
scheduleDarwin(spec, script) {
|
|
9023
|
+
const t = spec.trigger;
|
|
9024
|
+
let trigger;
|
|
9025
|
+
if ("everyMs" in t) {
|
|
9026
|
+
trigger = `<key>StartInterval</key><integer>${Math.max(60, Math.round(t.everyMs / 1e3))}</integer>`;
|
|
9027
|
+
} else if ("at" in t) {
|
|
9028
|
+
const d = new Date(t.at);
|
|
9029
|
+
trigger = `<key>StartCalendarInterval</key><dict><key>Minute</key><integer>${d.getMinutes()}</integer><key>Hour</key><integer>${d.getHours()}</integer><key>Day</key><integer>${d.getDate()}</integer><key>Month</key><integer>${d.getMonth() + 1}</integer></dict>`;
|
|
9030
|
+
} else {
|
|
9031
|
+
const f = parseCron(t.cron);
|
|
9032
|
+
const dict = [];
|
|
9033
|
+
const put = (key, vals, full) => {
|
|
9034
|
+
if (vals.length === full) return;
|
|
9035
|
+
if (vals.length !== 1) throw new Error(`macOS launchd supports only single-value cron fields (got "${t.cron}") \u2014 use {everyMs} or a simpler cron`);
|
|
9036
|
+
dict.push(`<key>${key}</key><integer>${vals[0]}</integer>`);
|
|
9037
|
+
};
|
|
9038
|
+
put("Minute", f.minute, 60);
|
|
9039
|
+
put("Hour", f.hour, 24);
|
|
9040
|
+
put("Day", f.dom, 31);
|
|
9041
|
+
put("Month", f.month, 12);
|
|
9042
|
+
put("Weekday", f.dow, 7);
|
|
9043
|
+
trigger = `<key>StartCalendarInterval</key><dict>${dict.join("")}</dict>`;
|
|
9044
|
+
}
|
|
9045
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
9046
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
9047
|
+
<plist version="1.0"><dict>
|
|
9048
|
+
<key>Label</key><string>${this.label(spec.id)}</string>
|
|
9049
|
+
<key>ProgramArguments</key><array><string>/bin/sh</string><string>${script}</string></array>
|
|
9050
|
+
${trigger}
|
|
9051
|
+
<key>RunAtLoad</key><false/>
|
|
9052
|
+
</dict></plist>
|
|
9053
|
+
`;
|
|
9054
|
+
mkdirSync7(join10(this.options.home, "Library", "LaunchAgents"), { recursive: true });
|
|
9055
|
+
writeFileSync7(this.plistPath(spec.id), plist);
|
|
9056
|
+
this.run("launchctl", ["load", this.plistPath(spec.id)]);
|
|
9057
|
+
return `launchd:${this.label(spec.id)}`;
|
|
9058
|
+
}
|
|
9059
|
+
scheduleLinux(spec, script, oneOff) {
|
|
9060
|
+
const t = spec.trigger;
|
|
9061
|
+
if (oneOff) {
|
|
9062
|
+
const d = new Date("at" in t ? t.at : Date.now());
|
|
9063
|
+
const stamp = `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")} ${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
9064
|
+
const out = this.run("at", [stamp], `/bin/sh ${script}
|
|
9065
|
+
`);
|
|
9066
|
+
const jobId = /job (\d+)/.exec(out)?.[1] ?? "";
|
|
9067
|
+
return `at:${jobId}`;
|
|
9068
|
+
}
|
|
9069
|
+
const expr = "cron" in t ? t.cron : `*/${Math.max(1, Math.round(t.everyMs / 6e4))} * * * *`;
|
|
9070
|
+
parseCron(expr);
|
|
9071
|
+
const cur = (() => {
|
|
9072
|
+
try {
|
|
9073
|
+
return this.run("crontab", ["-l"]);
|
|
9074
|
+
} catch {
|
|
9075
|
+
return "";
|
|
9076
|
+
}
|
|
9077
|
+
})();
|
|
9078
|
+
this.run("crontab", ["-"], `${cur.trimEnd()}
|
|
9079
|
+
${expr} /bin/sh ${script} # agentx-sched-${spec.id}
|
|
9080
|
+
`.replace(/^\n/, ""));
|
|
9081
|
+
return `crontab:${expr}`;
|
|
9082
|
+
}
|
|
9083
|
+
};
|
|
9084
|
+
var OsSchedulerOptions = class {
|
|
9085
|
+
platform = process.platform;
|
|
9086
|
+
home = homedir7();
|
|
9087
|
+
/** How the fired job invokes the CLI — a RAW shell snippet (may be `bun /path/cli.ts`). Default: `agentx` on PATH. */
|
|
9088
|
+
agentx = "agentx";
|
|
9089
|
+
/** Injectable executor for tests. Returns stdout+stderr merged — `at` reports "job N" on STDERR,
|
|
9090
|
+
* and the job id is what atrm needs for cancel. Throws on non-zero exit. */
|
|
9091
|
+
exec = (cmd, args, input) => {
|
|
9092
|
+
const r = spawnSync4(cmd, args, { input, encoding: "utf8" });
|
|
9093
|
+
if (r.error) throw r.error;
|
|
9094
|
+
if (r.status !== 0) throw new Error(`${cmd} exited ${r.status}: ${r.stderr || r.stdout}`);
|
|
9095
|
+
return `${r.stdout ?? ""}${r.stderr ?? ""}`;
|
|
9096
|
+
};
|
|
9097
|
+
};
|
|
9098
|
+
function routeTrigger(trigger, backendHint, now5 = Date.now()) {
|
|
9099
|
+
if (backendHint === "os") return "os";
|
|
9100
|
+
if (backendHint === "session") return "session";
|
|
9101
|
+
return "at" in trigger && trigger.at - now5 >= 30 * 6e4 ? "os" : "session";
|
|
9102
|
+
}
|
|
9103
|
+
|
|
8689
9104
|
// cli/cli.ts
|
|
8690
9105
|
var forceColor = process.env.FORCE_COLOR != null && process.env.FORCE_COLOR !== "" && process.env.FORCE_COLOR !== "0";
|
|
8691
9106
|
var useColor = forceColor || !process.env.NO_COLOR && !!process.stdout.isTTY && !!process.stderr.isTTY;
|
|
@@ -8701,10 +9116,10 @@ var italic = C("3");
|
|
|
8701
9116
|
var strike = C("9");
|
|
8702
9117
|
var link = (text, url) => useColor ? `\x1B]8;;${url}\x1B\\${cyan(text)}\x1B]8;;\x1B\\` : `${text} (${url})`;
|
|
8703
9118
|
var err = (s) => process.stderr.write(s);
|
|
8704
|
-
var
|
|
9119
|
+
var log20 = forComponent("cli");
|
|
8705
9120
|
var VERSION = (() => {
|
|
8706
9121
|
try {
|
|
8707
|
-
return JSON.parse(
|
|
9122
|
+
return JSON.parse(readFileSync6(new URL("../package.json", import.meta.url), "utf8")).version ?? "?";
|
|
8708
9123
|
} catch {
|
|
8709
9124
|
return "?";
|
|
8710
9125
|
}
|
|
@@ -8905,7 +9320,7 @@ REPL shortcuts: !<cmd> runs a shell command inline \xB7 #<note> saves a memory \
|
|
|
8905
9320
|
REPL slash commands: /help /version /tools /permissions /status /cost /context /transcript /doctor /cwd /model /reasoning /config /rename /compact /memory /rewind /undo /clear /sessions /resume /commands /skills /reload /mcp /init /export /paste /goal /exit (duplex: /act /think /tasks /voice /voice-model /think-model)
|
|
8906
9321
|
REPL completion: type / (commands+skills) or @ (files) for a LIVE menu \u2014 \u2191/\u2193 select, \u23CE/Tab accept, Esc dismiss.
|
|
8907
9322
|
REPL multi-line: Option/Alt+Enter inserts a newline, or end a line with \\ to continue. Esc cancels a running turn / clears the input line; double-Esc jumps back to edit a previous message.
|
|
8908
|
-
REPL shortcuts: Shift+Tab cycles permission posture (ask \u2192 accept-edits \u2192 plan) \xB7 Alt+T toggles reasoning \xB7 Alt+P switches model \xB7 Ctrl+O toggles verbose tool output \xB7 \u2192 or Tab accepts the dim history ghost-suggestion \xB7 Alt+S/Ctrl+S stash/unstash.
|
|
9323
|
+
REPL shortcuts: Shift+Tab cycles permission posture (ask \u2192 accept-edits \u2192 plan) \xB7 Alt+T toggles reasoning \xB7 Alt+P switches model \xB7 Ctrl+O toggles verbose tool output \xB7 Ctrl+X Ctrl+E edits the buffer in $EDITOR \xB7 \u2192 or Tab accepts the dim history ghost-suggestion \xB7 Alt+S/Ctrl+S stash/unstash.
|
|
8909
9324
|
REPL stash: type while a turn is running \u2192 Enter queues it (auto-submits when the turn finishes). Alt+S (or Ctrl+S) with text stashes it; on an empty prompt pops the next entry for editing.
|
|
8910
9325
|
REPL editing (emacs/readline): Ctrl-A/E line start/end \xB7 Ctrl-B/F char \xB7 Alt-B/F or Alt/Ctrl-\u2190/\u2192 word \xB7 Ctrl-W kill word \xB7 Ctrl-U/K kill to start/end \xB7 Ctrl-Y yank \xB7 Ctrl-_ undo \xB7 Alt-D kill word fwd \xB7 Ctrl-L clear screen. Set editorMode:'vim' (or /config) for modal vim editing.
|
|
8911
9326
|
REPL paste: large/multi-line pastes collapse to a [Pasted text +N lines] preview (expands on send); a pasted image/file path attaches as [Image]/[File]; Ctrl-V or /paste grabs a clipboard image (macOS).`;
|
|
@@ -8925,11 +9340,11 @@ function resolveModelOrNewest(model) {
|
|
|
8925
9340
|
var ENV_KEY_ALIASES = { google: ["GEMINI_API_KEY"] };
|
|
8926
9341
|
function loadInstallEnv() {
|
|
8927
9342
|
let dir = dirname4(import.meta.path);
|
|
8928
|
-
for (let i = 0; i < 5 && !
|
|
9343
|
+
for (let i = 0; i < 5 && !existsSync9(join11(dir, "package.json")); i++) dir = dirname4(dir);
|
|
8929
9344
|
for (const name of [".env", ".env.local"]) {
|
|
8930
|
-
const file =
|
|
8931
|
-
if (!
|
|
8932
|
-
for (const line of
|
|
9345
|
+
const file = join11(dir, name);
|
|
9346
|
+
if (!existsSync9(file)) continue;
|
|
9347
|
+
for (const line of readFileSync6(file, "utf8").split("\n")) {
|
|
8933
9348
|
const m = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
|
|
8934
9349
|
if (!m || m[1] in process.env) continue;
|
|
8935
9350
|
let val = m[2].trim();
|
|
@@ -9237,7 +9652,7 @@ function costOf(pricing, promptTokens = 0, completionTokens = 0, cacheCreationTo
|
|
|
9237
9652
|
function turnCost(model, usage) {
|
|
9238
9653
|
return costOf(getModelInfo(model)?.pricing, usage?.promptTokens ?? 0, usage?.completionTokens ?? 0, usage?.cacheCreationTokens ?? 0, usage?.cacheReadTokens ?? 0, model);
|
|
9239
9654
|
}
|
|
9240
|
-
async function evaluateGoal(ai, condition, transcript,
|
|
9655
|
+
async function evaluateGoal(ai, condition, transcript, log21) {
|
|
9241
9656
|
const recent = transcript.filter((m) => m.role === "assistant").slice(-8).map((m) => {
|
|
9242
9657
|
const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join(" ");
|
|
9243
9658
|
return text.slice(0, 600);
|
|
@@ -9257,7 +9672,7 @@ ${recent}` }
|
|
|
9257
9672
|
const match = r.content.match(/\{[\s\S]*\}/);
|
|
9258
9673
|
if (match) return JSON.parse(match[0]);
|
|
9259
9674
|
} catch (e) {
|
|
9260
|
-
|
|
9675
|
+
log21(dim(` (goal evaluator error: ${e?.message ?? e})
|
|
9261
9676
|
`));
|
|
9262
9677
|
}
|
|
9263
9678
|
return { met: false, reason: "evaluation unclear" };
|
|
@@ -9444,19 +9859,22 @@ async function mountMcp(cfg, oauth) {
|
|
|
9444
9859
|
return mounted;
|
|
9445
9860
|
}
|
|
9446
9861
|
async function closeMcp(mounted) {
|
|
9447
|
-
await Promise.all(mounted.map((m) => m.client.close().catch((e) =>
|
|
9862
|
+
await Promise.all(mounted.map((m) => m.client.close().catch((e) => log20.debug("mcp close failed", e))));
|
|
9448
9863
|
}
|
|
9449
9864
|
var IMG_EXT = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
|
|
9450
|
-
|
|
9865
|
+
function mentionRefs(line) {
|
|
9866
|
+
return [...line.matchAll(/(?:^|\s)@(?:"([^"]+)"|(\S+))/g)].map((m) => m[1] ?? m[2].replace(/[?!.,;:)\]}'">]+$/, "")).filter(Boolean);
|
|
9867
|
+
}
|
|
9868
|
+
var untilde = (p) => p.startsWith("~/") ? join11(homedir8(), p.slice(2)) : p;
|
|
9451
9869
|
function readImageParts(cwd, line) {
|
|
9452
|
-
const refs =
|
|
9870
|
+
const refs = mentionRefs(line);
|
|
9453
9871
|
const parts = [];
|
|
9454
9872
|
for (const ref of refs) {
|
|
9455
9873
|
const mime = IMG_EXT[extname(ref).toLowerCase()];
|
|
9456
9874
|
if (!mime) continue;
|
|
9457
9875
|
const abs = ref.startsWith("~/") ? untilde(ref) : resolve3(cwd, ref);
|
|
9458
9876
|
try {
|
|
9459
|
-
parts.push(imagePart(`data:${mime};base64,${
|
|
9877
|
+
parts.push(imagePart(`data:${mime};base64,${readFileSync6(abs).toString("base64")}`));
|
|
9460
9878
|
} catch {
|
|
9461
9879
|
}
|
|
9462
9880
|
}
|
|
@@ -9467,7 +9885,6 @@ function pastePathClassifier(cwd) {
|
|
|
9467
9885
|
let t = text.trim();
|
|
9468
9886
|
if (!t || t.includes("\n")) return null;
|
|
9469
9887
|
t = t.replace(/\\ /g, " ").replace(/^['"]|['"]$/g, "");
|
|
9470
|
-
if (/\s/.test(t)) return null;
|
|
9471
9888
|
if (!/^(\/|~\/|\.\/|\.\.\/)/.test(t)) return null;
|
|
9472
9889
|
const abs = t.startsWith("~/") ? untilde(t) : resolve3(cwd, t);
|
|
9473
9890
|
try {
|
|
@@ -9476,7 +9893,7 @@ function pastePathClassifier(cwd) {
|
|
|
9476
9893
|
return null;
|
|
9477
9894
|
}
|
|
9478
9895
|
const isImg = !!IMG_EXT[extname(abs).toLowerCase()];
|
|
9479
|
-
return { display: isImg ? "Image" : `File ${basename2(abs)}`, ref: "@" + abs };
|
|
9896
|
+
return { display: isImg ? "Image" : `File ${basename2(abs)}`, ref: /\s/.test(abs) ? `@"${abs}"` : "@" + abs };
|
|
9480
9897
|
};
|
|
9481
9898
|
}
|
|
9482
9899
|
var mcpMentionResolver;
|
|
@@ -9484,7 +9901,7 @@ function setMcpMentionResolver(fn) {
|
|
|
9484
9901
|
mcpMentionResolver = fn;
|
|
9485
9902
|
}
|
|
9486
9903
|
async function expandMentions(fs, line) {
|
|
9487
|
-
const refs =
|
|
9904
|
+
const refs = mentionRefs(line);
|
|
9488
9905
|
if (!refs.length) return { text: line, loaded: [], missing: [] };
|
|
9489
9906
|
const loaded = [], missing = [], blocks = [];
|
|
9490
9907
|
for (const ref of refs) {
|
|
@@ -9492,7 +9909,7 @@ async function expandMentions(fs, line) {
|
|
|
9492
9909
|
if (loaded.includes(ref) || missing.includes(ref)) continue;
|
|
9493
9910
|
if (ref.includes(":") && mcpMentionResolver) {
|
|
9494
9911
|
const body = await mcpMentionResolver(ref).catch((e) => {
|
|
9495
|
-
|
|
9912
|
+
log20.debug("mcp mention resolve failed", e);
|
|
9496
9913
|
return null;
|
|
9497
9914
|
});
|
|
9498
9915
|
if (body != null) {
|
|
@@ -9670,25 +10087,25 @@ var AGENTS_MD_TEMPLATE = `# ${"${name}"}
|
|
|
9670
10087
|
`;
|
|
9671
10088
|
function initInstructions(cwd) {
|
|
9672
10089
|
for (const f of ["AGENTS.md", "CLAUDE.md"]) {
|
|
9673
|
-
if (
|
|
10090
|
+
if (existsSync9(join11(cwd, f))) {
|
|
9674
10091
|
err(yellow(` ${f} already exists \u2014 leaving it as-is
|
|
9675
10092
|
`));
|
|
9676
10093
|
return;
|
|
9677
10094
|
}
|
|
9678
10095
|
}
|
|
9679
|
-
const path =
|
|
9680
|
-
|
|
10096
|
+
const path = join11(cwd, "AGENTS.md");
|
|
10097
|
+
writeFileSync8(path, AGENTS_MD_TEMPLATE.replace("${name}", basename2(cwd)));
|
|
9681
10098
|
err(green(` created ${path}
|
|
9682
10099
|
`) + dim(" edit it, then it auto-loads into every run.\n"));
|
|
9683
10100
|
}
|
|
9684
10101
|
function persistSetting(cwd, key, value) {
|
|
9685
|
-
const path =
|
|
10102
|
+
const path = join11(cwd, ".agent", "settings.json");
|
|
9686
10103
|
try {
|
|
9687
|
-
const obj =
|
|
10104
|
+
const obj = existsSync9(path) ? JSON.parse(readFileSync6(path, "utf8")) : {};
|
|
9688
10105
|
if (obj[key] === value) return;
|
|
9689
10106
|
obj[key] = value;
|
|
9690
|
-
|
|
9691
|
-
|
|
10107
|
+
mkdirSync8(dirname4(path), { recursive: true });
|
|
10108
|
+
writeFileSync8(path, JSON.stringify(obj, null, 2) + "\n");
|
|
9692
10109
|
} catch (e) {
|
|
9693
10110
|
err(yellow(` \u26A0 couldn't persist ${key} to ${path} \u2014 ${e?.message ?? e}
|
|
9694
10111
|
`));
|
|
@@ -9704,14 +10121,14 @@ var isCancelTeardown = (e) => {
|
|
|
9704
10121
|
function installCancelGuards(mounted) {
|
|
9705
10122
|
process.on("unhandledRejection", (e) => {
|
|
9706
10123
|
if (isCancelTeardown(e)) {
|
|
9707
|
-
|
|
10124
|
+
log20.debug("suppressed unhandledRejection (cursor stream cancel)", e);
|
|
9708
10125
|
return;
|
|
9709
10126
|
}
|
|
9710
|
-
|
|
10127
|
+
log20.error("unhandledRejection", e);
|
|
9711
10128
|
});
|
|
9712
10129
|
process.on("uncaughtException", (e) => {
|
|
9713
10130
|
if (isCancelTeardown(e)) {
|
|
9714
|
-
|
|
10131
|
+
log20.debug("suppressed uncaughtException (cursor stream cancel)", e);
|
|
9715
10132
|
return;
|
|
9716
10133
|
}
|
|
9717
10134
|
console.error(e);
|
|
@@ -9720,7 +10137,7 @@ function installCancelGuards(mounted) {
|
|
|
9720
10137
|
});
|
|
9721
10138
|
}
|
|
9722
10139
|
async function repl(args, ai, cfg, cwd) {
|
|
9723
|
-
const oauth = new McpOAuth({ storePath:
|
|
10140
|
+
const oauth = new McpOAuth({ storePath: join11(cwd, ".agent", "mcp-auth.json") });
|
|
9724
10141
|
const mounted = await mountMcp(cfg, oauth);
|
|
9725
10142
|
const agent = await makeAgent(args, ai, cfg, mounted.flatMap((m) => m.tools));
|
|
9726
10143
|
if (args.voice && !args.duplex) agent.options.tools = [...agent.options.tools ?? [], exitSessionTool(() => {
|
|
@@ -9740,7 +10157,19 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
9740
10157
|
},
|
|
9741
10158
|
tickMs: 15e3
|
|
9742
10159
|
});
|
|
9743
|
-
|
|
10160
|
+
const agentxInvocation = () => process.argv[1] ? `${process.execPath} ${resolve3(process.argv[1])}` : "agentx";
|
|
10161
|
+
const osSched = new OsScheduler({ agentx: agentxInvocation() });
|
|
10162
|
+
const osBackend = osSched.available() ? {
|
|
10163
|
+
get sessionId() {
|
|
10164
|
+
return session.meta.id;
|
|
10165
|
+
},
|
|
10166
|
+
cwd,
|
|
10167
|
+
route: (t, hint) => routeTrigger(t, hint),
|
|
10168
|
+
schedule: (spec) => osSched.schedule(spec),
|
|
10169
|
+
cancel: (id) => osSched.cancel(id),
|
|
10170
|
+
list: () => osSched.list()
|
|
10171
|
+
} : void 0;
|
|
10172
|
+
agent.options.tools = [...agent.options.tools ?? [], ...makeScheduleTools(scheduler, osBackend)];
|
|
9744
10173
|
const duplex = args.duplex;
|
|
9745
10174
|
let dx;
|
|
9746
10175
|
let voiceIO;
|
|
@@ -9902,7 +10331,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
9902
10331
|
quickLook: {
|
|
9903
10332
|
branch: () => {
|
|
9904
10333
|
try {
|
|
9905
|
-
const head =
|
|
10334
|
+
const head = readFileSync6(join11(cwd, ".git", "HEAD"), "utf8").trim();
|
|
9906
10335
|
return head.startsWith("ref: refs/heads/") ? `branch: ${head.slice("ref: refs/heads/".length)}` : `detached HEAD at ${head.slice(0, 12)}`;
|
|
9907
10336
|
} catch {
|
|
9908
10337
|
return "not a git repository";
|
|
@@ -10024,9 +10453,9 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
10024
10453
|
};
|
|
10025
10454
|
const pendingImages = [];
|
|
10026
10455
|
const grabClipboardAttachment = () => {
|
|
10027
|
-
const dir =
|
|
10456
|
+
const dir = join11(tmpdir3(), "agentx-pasted");
|
|
10028
10457
|
try {
|
|
10029
|
-
|
|
10458
|
+
mkdirSync8(dir, { recursive: true });
|
|
10030
10459
|
} catch {
|
|
10031
10460
|
}
|
|
10032
10461
|
const img = grabClipboardImage(dir, String(Date.now()));
|
|
@@ -10076,7 +10505,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
10076
10505
|
err(dim(` \u23F0 ${scheduler.size} scheduled job(s) re-armed
|
|
10077
10506
|
`));
|
|
10078
10507
|
}
|
|
10079
|
-
const checkpoints = args.vfs || args.boddb ? new CheckpointStack(agent.options.fs) : new GitCheckpoints({ workTree: cwd, gitDir:
|
|
10508
|
+
const checkpoints = args.vfs || args.boddb ? new CheckpointStack(agent.options.fs) : new GitCheckpoints({ workTree: cwd, gitDir: join11(cwd, ".agent", "checkpoints.git"), addDirs: args.addDirs, sessionId: session.meta.id });
|
|
10080
10509
|
const cpHooks = checkpoints.hooks?.();
|
|
10081
10510
|
if (cpHooks) work.hooks = composeHooks(work.hooks, cpHooks);
|
|
10082
10511
|
duplexPersist = () => {
|
|
@@ -10103,7 +10532,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
10103
10532
|
const fs = agent.options.fs;
|
|
10104
10533
|
const fsBase = fs.getCwd() === "/" ? "" : fs.getCwd();
|
|
10105
10534
|
const adot = (sub) => `${fsBase}/.agent/${sub}`;
|
|
10106
|
-
const adots = (sub) =>
|
|
10535
|
+
const adots = (sub) => dotDirs(fsBase, sub);
|
|
10107
10536
|
const cmds = (await loadCommands(fs, adots("commands"))).commands;
|
|
10108
10537
|
const skills = (await loadSkills(fs, adots("skills"))).skills;
|
|
10109
10538
|
const refreshCatalogs = async () => {
|
|
@@ -10125,14 +10554,14 @@ ${lines.join("\n")}
|
|
|
10125
10554
|
Added entries are loadable now via the Skill/SlashCommand tools; removed ones are gone even if still listed in the system prompt.
|
|
10126
10555
|
</system-reminder>`;
|
|
10127
10556
|
};
|
|
10128
|
-
const histPath =
|
|
10129
|
-
const history =
|
|
10557
|
+
const histPath = join11(cwd, ".agent", "history");
|
|
10558
|
+
const history = existsSync9(histPath) ? readFileSync6(histPath, "utf8").split("\n").filter(Boolean).reverse().slice(0, 500) : [];
|
|
10130
10559
|
const remember = (line) => {
|
|
10131
10560
|
try {
|
|
10132
|
-
|
|
10561
|
+
mkdirSync8(join11(cwd, ".agent"), { recursive: true });
|
|
10133
10562
|
appendFileSync(histPath, line + "\n");
|
|
10134
10563
|
} catch (e) {
|
|
10135
|
-
|
|
10564
|
+
log20.debug("history write failed", e);
|
|
10136
10565
|
}
|
|
10137
10566
|
};
|
|
10138
10567
|
const ago = (t) => {
|
|
@@ -10202,7 +10631,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
|
|
|
10202
10631
|
try {
|
|
10203
10632
|
store.save(session);
|
|
10204
10633
|
} catch (e) {
|
|
10205
|
-
|
|
10634
|
+
log20.debug("session save after rewind failed", e);
|
|
10206
10635
|
}
|
|
10207
10636
|
err(green(" \u27F2 jumped back") + dim(` \u2014 ${face.transcript.length} message(s) kept; edit + resend
|
|
10208
10637
|
`));
|
|
@@ -10230,7 +10659,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
|
|
|
10230
10659
|
const announcedTasks = /* @__PURE__ */ new Set();
|
|
10231
10660
|
const turn = async (task) => {
|
|
10232
10661
|
const delta = await refreshCatalogs().catch((e) => {
|
|
10233
|
-
|
|
10662
|
+
log20.debug("catalog refresh failed", e);
|
|
10234
10663
|
return "";
|
|
10235
10664
|
});
|
|
10236
10665
|
if (delta) {
|
|
@@ -10425,8 +10854,8 @@ ${extra}` : body);
|
|
|
10425
10854
|
const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
|
|
10426
10855
|
if (wasRaw) process.stdin.setRawMode(false);
|
|
10427
10856
|
try {
|
|
10428
|
-
const { spawnSync:
|
|
10429
|
-
const r =
|
|
10857
|
+
const { spawnSync: spawnSync5 } = await import("child_process");
|
|
10858
|
+
const r = spawnSync5("less", ["-R"], { input: text, stdio: ["pipe", "inherit", "inherit"] });
|
|
10430
10859
|
if (r.error) err(text);
|
|
10431
10860
|
} finally {
|
|
10432
10861
|
if (wasRaw) process.stdin.setRawMode(true);
|
|
@@ -10446,13 +10875,13 @@ ${extra}` : body);
|
|
|
10446
10875
|
keys.length ? ok(`provider keys: ${keys.join(", ")}`) : bad("no provider keys set (ANTHROPIC_API_KEY / OPENAI_API_KEY / GOOGLE_API_KEY / GROQ_API_KEY)");
|
|
10447
10876
|
const info = getModelInfo(work.model);
|
|
10448
10877
|
info?.pricing ? ok(`model ${work.model} \u2014 priced (${info.pricing.inputCostPer1K}/${info.pricing.outputCostPer1K} per 1k in/out)`) : warn(`model ${work.model} \u2014 no pricing in the catalog (costs will show ~$0; verify the id)`);
|
|
10449
|
-
const cfgFiles = ["ts", "js", "json"].flatMap((e) => [`${cwd}/.agent/config.${e}`, `${
|
|
10878
|
+
const cfgFiles = ["ts", "js", "json"].flatMap((e) => [`${cwd}/.agent/config.${e}`, `${homedir8()}/.agent/config.${e}`]).filter((p) => existsSync9(p));
|
|
10450
10879
|
cfgFiles.length ? ok(`config: ${cfgFiles.join(", ")}`) : warn("no .agent/config.* found (project or ~) \u2014 running on defaults");
|
|
10451
10880
|
try {
|
|
10452
10881
|
const probe = `${cwd}/.agent/sessions/.doctor-probe`;
|
|
10453
|
-
|
|
10454
|
-
|
|
10455
|
-
|
|
10882
|
+
mkdirSync8(`${cwd}/.agent/sessions`, { recursive: true });
|
|
10883
|
+
writeFileSync8(probe, "ok");
|
|
10884
|
+
unlinkSync4(probe);
|
|
10456
10885
|
ok(`session store writable (${cwd}/.agent/sessions)`);
|
|
10457
10886
|
} catch (e) {
|
|
10458
10887
|
bad(`session store not writable: ${e?.message ?? e}`);
|
|
@@ -10479,7 +10908,7 @@ ${extra}` : body);
|
|
|
10479
10908
|
desc: "rescan skills/commands dirs and rebuild the system prompt (one cache miss) \u2014 picks up entries created mid-session",
|
|
10480
10909
|
run: async () => {
|
|
10481
10910
|
await refreshCatalogs().catch((e) => {
|
|
10482
|
-
|
|
10911
|
+
log20.debug("catalog refresh failed", e);
|
|
10483
10912
|
});
|
|
10484
10913
|
face.reprepare();
|
|
10485
10914
|
err(green(` \u2713 reloaded \u2014 ${skills.length} skill(s), ${cmds.length} command(s); system prompt rebuilds on next message
|
|
@@ -10745,6 +11174,47 @@ ${extra}` : body);
|
|
|
10745
11174
|
`));
|
|
10746
11175
|
}
|
|
10747
11176
|
},
|
|
11177
|
+
copy: {
|
|
11178
|
+
desc: "copy the last reply to the clipboard \u2014 /copy code = last code block only",
|
|
11179
|
+
run: (a) => {
|
|
11180
|
+
const last = [...face.transcript].reverse().find((m) => m.role === "assistant" && contentText(m.content).trim());
|
|
11181
|
+
if (!last) {
|
|
11182
|
+
err(dim(" (nothing to copy yet)\n"));
|
|
11183
|
+
return;
|
|
11184
|
+
}
|
|
11185
|
+
let text = contentText(last.content).trim();
|
|
11186
|
+
if (a[0] === "code") {
|
|
11187
|
+
const fences = [...text.matchAll(/```[^\n]*\n([\s\S]*?)```/g)];
|
|
11188
|
+
if (!fences.length) {
|
|
11189
|
+
err(dim(" (no code block in the last reply)\n"));
|
|
11190
|
+
return;
|
|
11191
|
+
}
|
|
11192
|
+
text = fences[fences.length - 1][1].trimEnd();
|
|
11193
|
+
}
|
|
11194
|
+
err(dim(copyTextToClipboard(text) ? ` \u2713 copied ${text.length} chars
|
|
11195
|
+
` : " no clipboard tool found (pbcopy/wl-copy/xclip)\n"));
|
|
11196
|
+
}
|
|
11197
|
+
},
|
|
11198
|
+
diff: {
|
|
11199
|
+
desc: "show all file changes this session (oldest checkpoint \u2192 now)",
|
|
11200
|
+
run: async () => {
|
|
11201
|
+
if (!checkpoints.diff) {
|
|
11202
|
+
err(dim(" (diff not supported by this checkpoint backend)\n"));
|
|
11203
|
+
return;
|
|
11204
|
+
}
|
|
11205
|
+
await checkpoints.refresh?.();
|
|
11206
|
+
if (!checkpoints.size) {
|
|
11207
|
+
err(dim(" (no checkpoints yet \u2014 make a turn first)\n"));
|
|
11208
|
+
return;
|
|
11209
|
+
}
|
|
11210
|
+
const d = (await checkpoints.diff()).trim();
|
|
11211
|
+
if (!d) {
|
|
11212
|
+
err(dim(" (no file changes this session)\n"));
|
|
11213
|
+
return;
|
|
11214
|
+
}
|
|
11215
|
+
err(d.split("\n").map((l) => l.startsWith("+") && !l.startsWith("+++") ? green(l) : l.startsWith("-") && !l.startsWith("---") ? red(l) : dim(l)).join("\n") + "\n");
|
|
11216
|
+
}
|
|
11217
|
+
},
|
|
10748
11218
|
memory: {
|
|
10749
11219
|
desc: "open the memory index in $EDITOR (.agent/memory/MEMORY.md)",
|
|
10750
11220
|
run: async () => {
|
|
@@ -10775,8 +11245,8 @@ ${extra}` : body);
|
|
|
10775
11245
|
const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
|
|
10776
11246
|
if (wasRaw) process.stdin.setRawMode(false);
|
|
10777
11247
|
try {
|
|
10778
|
-
const { spawnSync:
|
|
10779
|
-
|
|
11248
|
+
const { spawnSync: spawnSync5 } = await import("child_process");
|
|
11249
|
+
spawnSync5(ed, [idx], { stdio: "inherit" });
|
|
10780
11250
|
} finally {
|
|
10781
11251
|
if (wasRaw) process.stdin.setRawMode(true);
|
|
10782
11252
|
}
|
|
@@ -10893,7 +11363,7 @@ ${extra}` : body);
|
|
|
10893
11363
|
try {
|
|
10894
11364
|
for (const def of (await loadAgents(fs2, d)).agents) if (!seen.has(def.name)) seen.set(def.name, { def, from: d });
|
|
10895
11365
|
} catch (e) {
|
|
10896
|
-
|
|
11366
|
+
log20.debug(`loadAgents(${d}) failed`, e);
|
|
10897
11367
|
}
|
|
10898
11368
|
}
|
|
10899
11369
|
if (!seen.size) {
|
|
@@ -10985,7 +11455,7 @@ ${extra}` : body);
|
|
|
10985
11455
|
if (idx >= 0) {
|
|
10986
11456
|
const old = mounted.splice(idx, 1)[0];
|
|
10987
11457
|
removeWorkTools(old.tools.map((t) => t.name));
|
|
10988
|
-
await old.client.close().catch((e) =>
|
|
11458
|
+
await old.client.close().catch((e) => log20.debug("mcp close failed", e));
|
|
10989
11459
|
}
|
|
10990
11460
|
try {
|
|
10991
11461
|
const m = await mountMcpServer(name, conf);
|
|
@@ -11013,7 +11483,7 @@ ${extra}` : body);
|
|
|
11013
11483
|
}
|
|
11014
11484
|
const m = mounted.splice(idx, 1)[0];
|
|
11015
11485
|
removeWorkTools(m.tools.map((t) => t.name));
|
|
11016
|
-
await m.client.close().catch((e) =>
|
|
11486
|
+
await m.client.close().catch((e) => log20.debug("mcp close failed", e));
|
|
11017
11487
|
err(dim(` removed "${name}"
|
|
11018
11488
|
`));
|
|
11019
11489
|
return;
|
|
@@ -11145,11 +11615,11 @@ ${extra}` : body);
|
|
|
11145
11615
|
return;
|
|
11146
11616
|
}
|
|
11147
11617
|
const md = exportMarkdown(session.meta, shown);
|
|
11148
|
-
const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" :
|
|
11618
|
+
const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" : join11(".agent", "exports", `${session.meta.id}.md`);
|
|
11149
11619
|
const path = resolve3(cwd, name);
|
|
11150
11620
|
try {
|
|
11151
|
-
|
|
11152
|
-
|
|
11621
|
+
mkdirSync8(dirname4(path), { recursive: true });
|
|
11622
|
+
writeFileSync8(path, md);
|
|
11153
11623
|
err(green(` \u2713 exported \u2192 ${path}
|
|
11154
11624
|
`) + dim(` ${shown.length} message(s) \xB7 ${md.length} chars
|
|
11155
11625
|
`));
|
|
@@ -11212,9 +11682,9 @@ ${extra}` : body);
|
|
|
11212
11682
|
`));
|
|
11213
11683
|
const listDir = (absDir) => {
|
|
11214
11684
|
try {
|
|
11215
|
-
return
|
|
11685
|
+
return readdirSync3(join11(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
|
|
11216
11686
|
} catch (e) {
|
|
11217
|
-
|
|
11687
|
+
log20.debug("completion readdir failed", absDir, e);
|
|
11218
11688
|
return null;
|
|
11219
11689
|
}
|
|
11220
11690
|
};
|
|
@@ -11704,7 +12174,7 @@ async function main() {
|
|
|
11704
12174
|
}
|
|
11705
12175
|
});
|
|
11706
12176
|
if (args.task) {
|
|
11707
|
-
const mounted = await mountMcp(cfg, new McpOAuth({ storePath:
|
|
12177
|
+
const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join11(cwd, ".agent", "mcp-auth.json") }));
|
|
11708
12178
|
const agent = await makeAgent(args, ai, cfg, mounted.flatMap((m) => m.tools));
|
|
11709
12179
|
const store = new SessionStore(cwd);
|
|
11710
12180
|
const session = startSession(args, store, agent, cwd);
|
|
@@ -11743,6 +12213,7 @@ export {
|
|
|
11743
12213
|
formatTranscriptFull,
|
|
11744
12214
|
jsonResult,
|
|
11745
12215
|
mcpMentionResolver,
|
|
12216
|
+
mentionRefs,
|
|
11746
12217
|
parseArgs,
|
|
11747
12218
|
pastePathClassifier,
|
|
11748
12219
|
readImageParts,
|