agent.libx.js 0.94.22 → 0.94.24
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 +820 -269
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +36 -6
- package/dist/index.js +96 -29
- 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 +1 -1
- 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;
|
|
@@ -1689,8 +1713,8 @@ var init_tools_shell = __esm({
|
|
|
1689
1713
|
|
|
1690
1714
|
// cli/cli.ts
|
|
1691
1715
|
import { createInterface } from "readline/promises";
|
|
1692
|
-
import { existsSync as
|
|
1693
|
-
import { homedir as
|
|
1716
|
+
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";
|
|
1717
|
+
import { homedir as homedir8, tmpdir as tmpdir3 } from "os";
|
|
1694
1718
|
|
|
1695
1719
|
// cli/clipboard.ts
|
|
1696
1720
|
import { execFileSync } from "child_process";
|
|
@@ -1744,9 +1768,20 @@ close access f`;
|
|
|
1744
1768
|
}
|
|
1745
1769
|
return null;
|
|
1746
1770
|
}
|
|
1771
|
+
function copyTextToClipboard(text, platform2 = process.platform) {
|
|
1772
|
+
const candidates = platform2 === "darwin" ? [["pbcopy", []]] : platform2 === "linux" ? [["wl-copy", []], ["xclip", ["-selection", "clipboard"]]] : [];
|
|
1773
|
+
for (const [cmd, args] of candidates) {
|
|
1774
|
+
try {
|
|
1775
|
+
execFileSync(cmd, args, { input: text, stdio: ["pipe", "ignore", "ignore"] });
|
|
1776
|
+
return true;
|
|
1777
|
+
} catch {
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
return false;
|
|
1781
|
+
}
|
|
1747
1782
|
|
|
1748
1783
|
// cli/cli.ts
|
|
1749
|
-
import { join as
|
|
1784
|
+
import { join as join11, resolve as resolve3, basename as basename2, extname, dirname as dirname4 } from "path";
|
|
1750
1785
|
import { AIClient, listModels, listProviders, getProviderFromModel, getModelInfo, resolveModel, isModelSupported, disposeCursorSessions } from "ai.libx.js";
|
|
1751
1786
|
|
|
1752
1787
|
// src/llm.ts
|
|
@@ -2839,6 +2874,8 @@ var AgentOptions = class {
|
|
|
2839
2874
|
permissions;
|
|
2840
2875
|
/** Opt-in syntax guardrail: refuse to persist a syntactically-broken code-file write/edit. Default off. */
|
|
2841
2876
|
lintOnWrite;
|
|
2877
|
+
/** Optional PDF text extraction for Read on .pdf files (node hosts wire pdftotext); absent => Read explains. */
|
|
2878
|
+
pdfText;
|
|
2842
2879
|
/** Opt-in: after a write-class tool runs, run `command` over the VFS and append any failure to the tool result.
|
|
2843
2880
|
* `tools` defaults to ['Write','Edit','MultiEdit','ApplyEdits']. */
|
|
2844
2881
|
autoTest;
|
|
@@ -2889,8 +2926,12 @@ var Agent = class _Agent {
|
|
|
2889
2926
|
reprepare() {
|
|
2890
2927
|
this.prepared = false;
|
|
2891
2928
|
}
|
|
2892
|
-
/**
|
|
2929
|
+
/** Tools injected via addTools(); kept separate from options.tools so prepare() rebuilds don't drop them. */
|
|
2930
|
+
injectedTools = [];
|
|
2931
|
+
/** Inject tools into a running agent (e.g. dynamically mounted MCP servers). Takes effect on the next turn
|
|
2932
|
+
* and survives prepare() rebuilds (reprepare(), new conversations). */
|
|
2893
2933
|
addTools(tools) {
|
|
2934
|
+
this.injectedTools.push(...tools);
|
|
2894
2935
|
this.activeTools.push(...tools);
|
|
2895
2936
|
}
|
|
2896
2937
|
/** Remove tools by name from a running agent. Returns the count removed. */
|
|
@@ -2898,6 +2939,7 @@ var Agent = class _Agent {
|
|
|
2898
2939
|
const s = names instanceof Set ? names : new Set(names);
|
|
2899
2940
|
const before = this.activeTools.length;
|
|
2900
2941
|
this.activeTools = this.activeTools.filter((t) => !s.has(t.name));
|
|
2942
|
+
this.injectedTools = this.injectedTools.filter((t) => !s.has(t.name));
|
|
2901
2943
|
return before - this.activeTools.length;
|
|
2902
2944
|
}
|
|
2903
2945
|
constructor(options) {
|
|
@@ -2909,6 +2951,7 @@ var Agent = class _Agent {
|
|
|
2909
2951
|
this.ctx = makeContext(this.options.fs, this.options.host);
|
|
2910
2952
|
this.ctx.signal = this.options.signal;
|
|
2911
2953
|
if (this.options.lintOnWrite) this.ctx.lint = checkSyntax;
|
|
2954
|
+
if (this.options.pdfText) this.ctx.pdfText = this.options.pdfText;
|
|
2912
2955
|
this.ctx.ai = this.options.ai;
|
|
2913
2956
|
this.ctx.model = this.options.model;
|
|
2914
2957
|
this.ctx.parkHuman = (p) => this.park(p);
|
|
@@ -2981,7 +3024,7 @@ var Agent = class _Agent {
|
|
|
2981
3024
|
const plan = o.planMode ? planMode({ host: o.host }) : void 0;
|
|
2982
3025
|
if (plan) tools = [...tools, plan.tool];
|
|
2983
3026
|
this.activeHooks = composeHooks(o.hooks, plan?.hooks, o.permissions?.hooks());
|
|
2984
|
-
this.activeTools = tools;
|
|
3027
|
+
this.activeTools = [...tools, ...this.injectedTools];
|
|
2985
3028
|
this.systemPromptCache = systemPrompt;
|
|
2986
3029
|
this.prepared = true;
|
|
2987
3030
|
return systemPrompt;
|
|
@@ -3116,7 +3159,15 @@ var Agent = class _Agent {
|
|
|
3116
3159
|
} catch (err2) {
|
|
3117
3160
|
if (err2?.code === "budget") return kill("budget");
|
|
3118
3161
|
if (o.signal?.aborted || isAbortError(err2)) return kill("aborted");
|
|
3119
|
-
|
|
3162
|
+
const body = err2?.body ?? err2?.response?.data ?? err2?.error;
|
|
3163
|
+
let bodyStr;
|
|
3164
|
+
try {
|
|
3165
|
+
bodyStr = body && typeof body !== "string" ? JSON.stringify(body).slice(0, 2e3) : body;
|
|
3166
|
+
} catch {
|
|
3167
|
+
bodyStr = void 0;
|
|
3168
|
+
}
|
|
3169
|
+
if (bodyStr && err2 instanceof Error && !err2.message.includes(bodyStr)) err2.detail = bodyStr;
|
|
3170
|
+
log3.error(`chat() failed: ${err2?.message ?? err2}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, err2);
|
|
3120
3171
|
return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err2 };
|
|
3121
3172
|
}
|
|
3122
3173
|
if (o.signal?.aborted) return kill("aborted");
|
|
@@ -3347,20 +3398,28 @@ function stubOldToolResults(messages, keep) {
|
|
|
3347
3398
|
return { ...x, content: `[${x.name ?? "tool"}${where ? ` ${where}` : ""} output elided \u2014 ${lines} lines; re-run the tool to view]` };
|
|
3348
3399
|
});
|
|
3349
3400
|
}
|
|
3350
|
-
var
|
|
3401
|
+
var callIdSet = (msgs) => {
|
|
3402
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3403
|
+
for (const m of msgs) if (m.role === "assistant") for (const tc of m.tool_calls ?? []) ids.add(tc.id);
|
|
3404
|
+
return ids;
|
|
3405
|
+
};
|
|
3351
3406
|
function dropOrphanToolResults(messages) {
|
|
3352
|
-
const
|
|
3407
|
+
const ids = callIdSet(messages);
|
|
3408
|
+
const ok = (m) => m.role !== "tool" || ids.has(m.tool_call_id ?? "");
|
|
3353
3409
|
return messages.every(ok) ? messages : messages.filter(ok);
|
|
3354
3410
|
}
|
|
3355
3411
|
function fitTokenBudget(messages, maxTokens) {
|
|
3356
|
-
|
|
3412
|
+
const per = messages.map((x) => estimateTokens([x]));
|
|
3413
|
+
let total = per.reduce((a, b) => a + b, 0);
|
|
3414
|
+
if (total <= maxTokens) return messages;
|
|
3357
3415
|
const head = messages[0]?.role === "system" ? [messages[0]] : [];
|
|
3358
|
-
let
|
|
3359
|
-
while (
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3416
|
+
let from = head.length;
|
|
3417
|
+
while (from < messages.length && total > maxTokens) total -= per[from++];
|
|
3418
|
+
const ids = callIdSet(messages.slice(from));
|
|
3419
|
+
while (from < messages.length && messages[from].role === "tool" && !ids.has(messages[from].tool_call_id ?? "")) total -= per[from++];
|
|
3420
|
+
if (total > maxTokens)
|
|
3421
|
+
log3.warn(`context ~${total} tok still over maxContextTokens=${maxTokens} after trimming (system head can't be dropped)`);
|
|
3422
|
+
return [...head, ...messages.slice(from)];
|
|
3364
3423
|
}
|
|
3365
3424
|
function compact(m, max, focus) {
|
|
3366
3425
|
const hasSystem = m[0]?.role === "system";
|
|
@@ -3812,11 +3871,11 @@ var Scheduler = class {
|
|
|
3812
3871
|
this.jobs.clear();
|
|
3813
3872
|
}
|
|
3814
3873
|
};
|
|
3815
|
-
function makeScheduleTools(scheduler) {
|
|
3874
|
+
function makeScheduleTools(scheduler, os) {
|
|
3816
3875
|
return [
|
|
3817
3876
|
{
|
|
3818
3877
|
name: "ScheduleTask",
|
|
3819
|
-
description: 'Schedule a prompt to fire automatically
|
|
3878
|
+
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
3879
|
parameters: {
|
|
3821
3880
|
type: "object",
|
|
3822
3881
|
required: ["prompt", "trigger"],
|
|
@@ -3831,15 +3890,22 @@ function makeScheduleTools(scheduler) {
|
|
|
3831
3890
|
cron: { type: "string" }
|
|
3832
3891
|
}
|
|
3833
3892
|
},
|
|
3834
|
-
label: { type: "string", description: "Short label for display (optional)." }
|
|
3893
|
+
label: { type: "string", description: "Short label for display (optional)." },
|
|
3894
|
+
backend: { type: "string", enum: ["auto", "session", "os"], description: "Where the job lives (default auto)." }
|
|
3835
3895
|
}
|
|
3836
3896
|
},
|
|
3837
|
-
async run({ prompt, trigger, label }) {
|
|
3897
|
+
async run({ prompt, trigger, label, backend }) {
|
|
3838
3898
|
try {
|
|
3899
|
+
if (os && os.route(trigger, backend) === "os") {
|
|
3900
|
+
const id2 = `os-${Date.now().toString(36)}`;
|
|
3901
|
+
const mechanism = os.schedule({ id: id2, prompt, sessionId: os.sessionId, cwd: os.cwd, trigger, label });
|
|
3902
|
+
return `Scheduled ${id2}${label ? ` (${label})` : ""} on the OS scheduler (${mechanism}) \u2014 survives quitting; fires \`agentx --resume ${os.sessionId}\` headless.`;
|
|
3903
|
+
}
|
|
3904
|
+
if (backend === "os") return "Error: no OS scheduler available on this platform \u2014 job not created (use the default in-session backend).";
|
|
3839
3905
|
const id = scheduler.add({ prompt, trigger, label });
|
|
3840
3906
|
const job = scheduler.get(id);
|
|
3841
3907
|
const next = scheduler.nextFire(job);
|
|
3842
|
-
return `Scheduled ${id}${label ? ` (${label})` : ""}. Next fire: ${next ? new Date(next).toLocaleString() : "now"}
|
|
3908
|
+
return `Scheduled ${id}${label ? ` (${label})` : ""}. Next fire: ${next ? new Date(next).toLocaleString() : "now"}. (In-session: does not survive quitting.)`;
|
|
3843
3909
|
} catch (e) {
|
|
3844
3910
|
return `Error: ${e?.message ?? e}`;
|
|
3845
3911
|
}
|
|
@@ -3847,24 +3913,28 @@ function makeScheduleTools(scheduler) {
|
|
|
3847
3913
|
},
|
|
3848
3914
|
{
|
|
3849
3915
|
name: "ScheduleList",
|
|
3850
|
-
description: "List all scheduled jobs and their next fire time.",
|
|
3916
|
+
description: "List all scheduled jobs (in-session + OS-backed) and their next fire time.",
|
|
3851
3917
|
parameters: { type: "object", properties: {} },
|
|
3852
3918
|
async run() {
|
|
3919
|
+
const osJobs = os?.list() ?? [];
|
|
3920
|
+
const osLines = osJobs.map((j) => `${j.id} os ${j.mechanism}${j.label ? " " + j.label : ""}`);
|
|
3853
3921
|
const jobs = scheduler.list();
|
|
3854
|
-
if (!jobs.length) return "(no scheduled jobs)";
|
|
3855
|
-
return jobs.map((j) => {
|
|
3922
|
+
if (!jobs.length && !osLines.length) return "(no scheduled jobs)";
|
|
3923
|
+
return [...osLines, ...jobs.map((j) => {
|
|
3856
3924
|
const next = scheduler.nextFire(j);
|
|
3857
3925
|
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
3926
|
return `${j.id} ${j.status} ${trig} runs:${j.runs} next:${next ? new Date(next).toLocaleTimeString() : "\u2014"}${j.label ? " " + j.label : ""}`;
|
|
3859
|
-
}).join("\n");
|
|
3927
|
+
})].join("\n");
|
|
3860
3928
|
}
|
|
3861
3929
|
},
|
|
3862
3930
|
{
|
|
3863
3931
|
name: "ScheduleCancel",
|
|
3864
|
-
description: "Cancel a scheduled job by id.",
|
|
3932
|
+
description: "Cancel a scheduled job by id (in-session or OS-backed).",
|
|
3865
3933
|
parameters: { type: "object", required: ["id"], properties: { id: { type: "string" } } },
|
|
3866
3934
|
async run({ id }) {
|
|
3867
|
-
|
|
3935
|
+
const key = String(id);
|
|
3936
|
+
if (key.startsWith("os-")) return os?.cancel(key) ? `Cancelled ${key} (OS job removed).` : `Error: no OS job '${key}'.`;
|
|
3937
|
+
return scheduler.cancel(key) ? `Cancelled ${key}.` : `Error: no scheduled job '${key}'. Use ScheduleList to see jobs.`;
|
|
3868
3938
|
}
|
|
3869
3939
|
},
|
|
3870
3940
|
{
|
|
@@ -4162,6 +4232,9 @@ var DuplexAgentOptions = class {
|
|
|
4162
4232
|
askRelay = false;
|
|
4163
4233
|
/** Parked questions auto-resolve empty after this long (callers map '' to deny/best-judgment). */
|
|
4164
4234
|
askTimeoutMs = 12e4;
|
|
4235
|
+
/** Max retained task records: oldest SETTLED tasks (and their activity tails) are evicted past this,
|
|
4236
|
+
* bounding memory over a long-lived session. Running tasks are never evicted. */
|
|
4237
|
+
maxTaskRecords = 50;
|
|
4165
4238
|
/** Host overrides for QuickLook lookups (keyed by `what`). The engine's defaults go through the
|
|
4166
4239
|
* (possibly jailed) fs — e.g. `.git/**` is deny-listed, so the CLI supplies 'branch' itself. */
|
|
4167
4240
|
quickLook;
|
|
@@ -4452,6 +4525,11 @@ ${recent}` : brief) + verify;
|
|
|
4452
4525
|
};
|
|
4453
4526
|
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
4527
|
this.tasks.set(id, { id, label, status: "running", controller, promise, tail });
|
|
4528
|
+
if (this.tasks.size > this.options.maxTaskRecords)
|
|
4529
|
+
for (const [tid, rec] of this.tasks) {
|
|
4530
|
+
if (this.tasks.size <= this.options.maxTaskRecords) break;
|
|
4531
|
+
if (rec.status !== "running") this.tasks.delete(tid);
|
|
4532
|
+
}
|
|
4455
4533
|
}
|
|
4456
4534
|
/** Fresh-context check of a successful Act task: a NEW agent (same model/fs/tools, but NO shared
|
|
4457
4535
|
* conversation context) re-reads the file state against the brief and fixes any gap. The fix lands
|
|
@@ -6052,15 +6130,101 @@ function defaultOpenBrowser(url) {
|
|
|
6052
6130
|
|
|
6053
6131
|
// cli/core.ts
|
|
6054
6132
|
import { randomUUID } from "crypto";
|
|
6133
|
+
import { execFile as execFile2 } from "child_process";
|
|
6055
6134
|
import { resolve, basename, join as join3 } from "path";
|
|
6056
|
-
import { existsSync as
|
|
6057
|
-
import { platform, arch, release, userInfo, homedir, tmpdir } from "os";
|
|
6135
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
6136
|
+
import { platform, arch, release, userInfo, homedir as homedir2, tmpdir } from "os";
|
|
6058
6137
|
init_tools_shell();
|
|
6138
|
+
|
|
6139
|
+
// src/tools.notify.ts
|
|
6140
|
+
init_logging();
|
|
6141
|
+
import { execFile } from "child_process";
|
|
6142
|
+
var log13 = forComponent("notify");
|
|
6143
|
+
function makeNotifyTool(opts = {}) {
|
|
6144
|
+
const platform2 = opts.platform ?? process.platform;
|
|
6145
|
+
const run = opts.exec ?? execFile;
|
|
6146
|
+
return {
|
|
6147
|
+
name: "PushNotification",
|
|
6148
|
+
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.",
|
|
6149
|
+
parameters: {
|
|
6150
|
+
type: "object",
|
|
6151
|
+
required: ["message"],
|
|
6152
|
+
properties: {
|
|
6153
|
+
message: { type: "string", description: "notification body" },
|
|
6154
|
+
title: { type: "string", description: 'notification title (default "agentx")' }
|
|
6155
|
+
}
|
|
6156
|
+
},
|
|
6157
|
+
async run({ message, title }) {
|
|
6158
|
+
const msg = String(message ?? "").slice(0, 256);
|
|
6159
|
+
const head = String(title ?? "agentx").slice(0, 64);
|
|
6160
|
+
if (!msg) return "Error: empty message";
|
|
6161
|
+
const argv = platform2 === "darwin" ? ["osascript", ["-e", `display notification ${JSON.stringify(msg)} with title ${JSON.stringify(head)}`]] : platform2 === "linux" ? ["notify-send", [head, msg]] : null;
|
|
6162
|
+
if (!argv) return `Notifications unavailable on ${platform2}.`;
|
|
6163
|
+
return new Promise((resolve4) => {
|
|
6164
|
+
run(argv[0], argv[1], { timeout: 5e3 }, (e) => {
|
|
6165
|
+
if (e) {
|
|
6166
|
+
log13.debug("notification failed", e);
|
|
6167
|
+
resolve4(`Notification failed: ${e.message}`);
|
|
6168
|
+
} else resolve4("Notification shown.");
|
|
6169
|
+
});
|
|
6170
|
+
});
|
|
6171
|
+
}
|
|
6172
|
+
};
|
|
6173
|
+
}
|
|
6174
|
+
|
|
6175
|
+
// cli/core.ts
|
|
6059
6176
|
import { BodDB as BodDB2 } from "@bod.ee/db";
|
|
6177
|
+
|
|
6178
|
+
// cli/util.ts
|
|
6179
|
+
init_logging();
|
|
6180
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
6181
|
+
import { homedir } from "os";
|
|
6182
|
+
var log14 = forComponent("cli-util");
|
|
6183
|
+
function dotDirs(base, sub, opts = {}) {
|
|
6184
|
+
const home = opts.home ?? homedir();
|
|
6185
|
+
const dirs = [`${base}/.agent/${sub}`, `${base}/.claude/${sub}`, `${home}/.agent/${sub}`, `${home}/.claude/${sub}`];
|
|
6186
|
+
return opts.existing ? dirs.filter((d) => existsSync2(d)) : dirs;
|
|
6187
|
+
}
|
|
6188
|
+
function truncate(s, n, suffix = "\u2026") {
|
|
6189
|
+
const t = s ?? "";
|
|
6190
|
+
return t.length > n ? t.slice(0, n) + suffix : t;
|
|
6191
|
+
}
|
|
6192
|
+
function sanitizeLabel(s, max = 60) {
|
|
6193
|
+
return truncate(s.replace(/\s+/g, " ").trim(), max, "");
|
|
6194
|
+
}
|
|
6195
|
+
function parseJson(text, fallback, what = "json") {
|
|
6196
|
+
try {
|
|
6197
|
+
return JSON.parse(text);
|
|
6198
|
+
} catch (e) {
|
|
6199
|
+
log14.debug(`parseJson(${what}) failed: ${e.message}`);
|
|
6200
|
+
return fallback;
|
|
6201
|
+
}
|
|
6202
|
+
}
|
|
6203
|
+
function readJsonFile(path, fallback) {
|
|
6204
|
+
if (!existsSync2(path)) return fallback;
|
|
6205
|
+
let text;
|
|
6206
|
+
try {
|
|
6207
|
+
text = readFileSync2(path, "utf8");
|
|
6208
|
+
} catch (e) {
|
|
6209
|
+
log14.debug(`readJsonFile(${path}) unreadable: ${e.message}`);
|
|
6210
|
+
return fallback;
|
|
6211
|
+
}
|
|
6212
|
+
return parseJson(text, fallback, path);
|
|
6213
|
+
}
|
|
6214
|
+
|
|
6215
|
+
// cli/core.ts
|
|
6060
6216
|
var DEFAULT_TOOLS = ["bash", "Read", "Edit", "Write", "Grep", "Glob", "MultiEdit", "ApplyEdits", "RepoMap", "TodoWrite"];
|
|
6061
6217
|
function autoWebTools() {
|
|
6062
6218
|
return ["WebFetch", "WebSearch"];
|
|
6063
6219
|
}
|
|
6220
|
+
function pdfTextViaPoppler(path) {
|
|
6221
|
+
return new Promise((res, rej) => {
|
|
6222
|
+
execFile2("pdftotext", [path, "-"], { maxBuffer: 32 * 1024 * 1024, timeout: 3e4 }, (e, stdout) => {
|
|
6223
|
+
if (e) rej(new Error(/ENOENT/.test(String(e.code ?? e.message)) ? "pdftotext not installed (brew/apt install poppler)" : e.message));
|
|
6224
|
+
else res(stdout);
|
|
6225
|
+
});
|
|
6226
|
+
});
|
|
6227
|
+
}
|
|
6064
6228
|
var SANDBOX_SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "coverage", ".cache"]);
|
|
6065
6229
|
async function hydrate(from, to, dir = "/") {
|
|
6066
6230
|
let n = 0;
|
|
@@ -6155,33 +6319,27 @@ async function buildAgent(o) {
|
|
|
6155
6319
|
${notes.join("\n")}
|
|
6156
6320
|
Reference files in them by their mount path (the left side).`;
|
|
6157
6321
|
}
|
|
6158
|
-
const dot = (sub) =>
|
|
6322
|
+
const dot = (sub) => existsSync3(`${cwd}/.agent/${sub}`) ? `${cwd}/.agent/${sub}` : void 0;
|
|
6159
6323
|
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);
|
|
6324
|
+
const dirs = dotDirs(cwd, sub, { existing: true });
|
|
6167
6325
|
return dirs.length ? dirs : void 0;
|
|
6168
6326
|
};
|
|
6169
6327
|
const memoryDir = (() => {
|
|
6170
|
-
const home =
|
|
6328
|
+
const home = homedir2();
|
|
6171
6329
|
const projectDir = `${cwd}/.agent/memory`;
|
|
6172
6330
|
const readDirs = [
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6331
|
+
existsSync3(projectDir) ? projectDir : void 0,
|
|
6332
|
+
existsSync3(`${cwd}/.claude/memory`) ? `${cwd}/.claude/memory` : void 0,
|
|
6333
|
+
existsSync3(`${home}/.agent/memory`) ? `${home}/.agent/memory` : void 0,
|
|
6334
|
+
existsSync3(`${home}/.claude/memory`) ? `${home}/.claude/memory` : void 0
|
|
6177
6335
|
].filter(Boolean);
|
|
6178
6336
|
return readDirs[0] === projectDir ? readDirs : [projectDir, ...readDirs];
|
|
6179
6337
|
})();
|
|
6180
6338
|
const memoryUserDir = (() => {
|
|
6181
|
-
const home =
|
|
6339
|
+
const home = homedir2();
|
|
6182
6340
|
return [
|
|
6183
|
-
|
|
6184
|
-
|
|
6341
|
+
existsSync3(`${home}/.agent/memory`) ? `${home}/.agent/memory` : void 0,
|
|
6342
|
+
existsSync3(`${home}/.claude/memory`) ? `${home}/.claude/memory` : void 0
|
|
6185
6343
|
].find(Boolean) ?? `${home}/.agent/memory`;
|
|
6186
6344
|
})();
|
|
6187
6345
|
const memoryWriteDir = memoryDir[0];
|
|
@@ -6198,6 +6356,8 @@ Reference files in them by their mount path (the left side).`;
|
|
|
6198
6356
|
ai: o.ai,
|
|
6199
6357
|
fs,
|
|
6200
6358
|
model: o.model ?? "anthropic/claude-sonnet-4-6",
|
|
6359
|
+
// PDF reads (disk mode only — VFS paths aren't real files): poppler's pdftotext when installed.
|
|
6360
|
+
...!virtual ? { pdfText: pdfTextViaPoppler } : {},
|
|
6201
6361
|
// Anchor cursor to the launch dir (its adapter defaults to TMPDIR otherwise) and forward the
|
|
6202
6362
|
// host's MCP servers so the delegated cursor agent runs in the same environment. Gated to cursor:
|
|
6203
6363
|
// openai/google adapters Object.assign providerOptions into the request body, so a blanket cwd
|
|
@@ -6240,6 +6400,7 @@ The filesystem root '/' is the real machine root \u2014 you have full filesystem
|
|
|
6240
6400
|
const base = toolsByName([...o.tools ?? DEFAULT_TOOLS, ...autoWebTools()]);
|
|
6241
6401
|
const tail = [...o.extraTools ?? []];
|
|
6242
6402
|
if (scratch) tail.push(makeAskTool({ fs, ai: o.ai, model: o.scratchAskModel ?? o.model ?? "anthropic/claude-sonnet-4-6", dir: scratchDir }));
|
|
6403
|
+
tail.push(makeNotifyTool());
|
|
6243
6404
|
if (!realShell.length) return [...base, ...tail];
|
|
6244
6405
|
const filtered = base.filter((t) => t.name !== "bash");
|
|
6245
6406
|
return [...filtered, ...realShell, ...tail];
|
|
@@ -6291,11 +6452,11 @@ var trunc = (s, n) => (s == null ? "" : String(s).length > n ? String(s).slice(0
|
|
|
6291
6452
|
// cli/voice.ts
|
|
6292
6453
|
init_logging();
|
|
6293
6454
|
import { spawn as spawn2, spawnSync } from "child_process";
|
|
6294
|
-
import { existsSync as
|
|
6295
|
-
import { homedir as
|
|
6455
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, statSync as statSync2 } from "fs";
|
|
6456
|
+
import { homedir as homedir3 } from "os";
|
|
6296
6457
|
import { dirname as dirname3, join as join4 } from "path";
|
|
6297
6458
|
import { fileURLToPath } from "url";
|
|
6298
|
-
var
|
|
6459
|
+
var log15 = forComponent("VoiceIO");
|
|
6299
6460
|
var now4 = () => performance.now();
|
|
6300
6461
|
var Player = class {
|
|
6301
6462
|
proc = null;
|
|
@@ -6309,7 +6470,7 @@ var Player = class {
|
|
|
6309
6470
|
["-loglevel", "quiet", "-nodisp", "-fflags", "nobuffer", "-flags", "low_delay", "-probesize", "32", "-f", "s16le", "-ar", String(TTS_SAMPLE_RATE), "-ch_layout", "mono", "-i", "-"],
|
|
6310
6471
|
{ stdio: ["pipe", "ignore", "ignore"] }
|
|
6311
6472
|
);
|
|
6312
|
-
this.proc.on("error", (e) =>
|
|
6473
|
+
this.proc.on("error", (e) => log15.warn(`ffplay error: ${e.message}`));
|
|
6313
6474
|
this.proc.stdin.on("error", () => {
|
|
6314
6475
|
});
|
|
6315
6476
|
this.bytesWritten = 0;
|
|
@@ -6344,28 +6505,28 @@ function detectFfmpegMic() {
|
|
|
6344
6505
|
const devices = [...audio.matchAll(/\[(\d+)\] (.+)/g)].map(([, idx, name]) => ({ idx, name: name.trim() }));
|
|
6345
6506
|
const mic = devices.find((d) => /microphone|built-in/i.test(d.name) && !/teams|blackhole|loopback/i.test(d.name)) ?? devices[0];
|
|
6346
6507
|
if (!mic) throw new Error("no audio input device found");
|
|
6347
|
-
|
|
6508
|
+
log15.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
|
|
6348
6509
|
return `:${mic.idx}`;
|
|
6349
6510
|
}
|
|
6350
6511
|
function resolveAecBinary() {
|
|
6351
6512
|
if (process.env.MIC_AEC === "0" || process.platform !== "darwin") return null;
|
|
6352
6513
|
const src = join4(nativeDir(), "mic-aec.swift");
|
|
6353
6514
|
const plist = join4(nativeDir(), "Info.plist");
|
|
6354
|
-
if (!
|
|
6355
|
-
const cacheDir = join4(
|
|
6515
|
+
if (!existsSync4(src)) return null;
|
|
6516
|
+
const cacheDir = join4(homedir3(), ".agent", "cache");
|
|
6356
6517
|
const bin = join4(cacheDir, "mic-aec");
|
|
6357
|
-
if (
|
|
6518
|
+
if (existsSync4(bin) && statSync2(bin).mtimeMs >= statSync2(src).mtimeMs) return bin;
|
|
6358
6519
|
if (spawnSync("which", ["swiftc"]).status !== 0) return null;
|
|
6359
6520
|
mkdirSync3(cacheDir, { recursive: true });
|
|
6360
|
-
|
|
6521
|
+
log15.info("compiling AEC mic helper (first run)\u2026");
|
|
6361
6522
|
const build = spawnSync("swiftc", ["-O", "-o", bin, src, "-Xlinker", "-sectcreate", "-Xlinker", "__TEXT", "-Xlinker", "__info_plist", "-Xlinker", plist], { encoding: "utf8" });
|
|
6362
6523
|
if (build.status !== 0) {
|
|
6363
|
-
|
|
6524
|
+
log15.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
|
|
6364
6525
|
return null;
|
|
6365
6526
|
}
|
|
6366
6527
|
const sign = spawnSync("codesign", ["-fs", "-", bin], { encoding: "utf8" });
|
|
6367
6528
|
if (sign.status !== 0) {
|
|
6368
|
-
|
|
6529
|
+
log15.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
|
|
6369
6530
|
return null;
|
|
6370
6531
|
}
|
|
6371
6532
|
return bin;
|
|
@@ -6384,16 +6545,16 @@ var NodeMicSource = class {
|
|
|
6384
6545
|
this.proc = spawn2(this.bin, [], { stdio: ["ignore", "pipe", "ignore"] });
|
|
6385
6546
|
} else {
|
|
6386
6547
|
if (spawnSync("which", ["ffmpeg"]).status !== 0) throw new Error("voice I/O unavailable: no AEC helper and no ffmpeg on PATH");
|
|
6387
|
-
|
|
6548
|
+
log15.info("mic: raw capture (no AEC) \u2014 echo handled heuristically; headphones recommended");
|
|
6388
6549
|
this.proc = spawn2(
|
|
6389
6550
|
"ffmpeg",
|
|
6390
6551
|
["-loglevel", "error", "-f", "avfoundation", "-i", detectFfmpegMic(), "-ar", String(STT_SAMPLE_RATE), "-ac", "1", "-f", "s16le", "-"],
|
|
6391
6552
|
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
6392
6553
|
);
|
|
6393
|
-
this.proc.stderr.on("data", (d) =>
|
|
6554
|
+
this.proc.stderr.on("data", (d) => log15.warn(`ffmpeg: ${String(d).trim()}`));
|
|
6394
6555
|
}
|
|
6395
6556
|
this.proc.on("exit", (c) => {
|
|
6396
|
-
if (c && !this.stopped)
|
|
6557
|
+
if (c && !this.stopped) log15.error(`mic capture exited (${c}) \u2014 check mic permission / MIC_DEVICE / MIC_AEC=0`);
|
|
6397
6558
|
});
|
|
6398
6559
|
this.proc.stdout.on("data", (chunk) => onChunk(chunk));
|
|
6399
6560
|
}
|
|
@@ -6427,11 +6588,11 @@ var AecDuplexAudio = class {
|
|
|
6427
6588
|
this.proc.stdin.on("error", () => {
|
|
6428
6589
|
});
|
|
6429
6590
|
this.proc.on("exit", (c) => {
|
|
6430
|
-
if (c && !this.stopped)
|
|
6591
|
+
if (c && !this.stopped) log15.error(`aec duplex audio exited (${c}) \u2014 check mic permission / MIC_AEC=0`);
|
|
6431
6592
|
});
|
|
6432
6593
|
this.proc.stdout.on("data", (chunk) => onChunk(chunk));
|
|
6433
6594
|
this.proc.stderr.on("data", (d) => {
|
|
6434
|
-
for (const ln of String(d).split("\n")) if (ln.trim())
|
|
6595
|
+
for (const ln of String(d).split("\n")) if (ln.trim()) log15.debug(`mic-aec: ${ln.trim()}`);
|
|
6435
6596
|
});
|
|
6436
6597
|
}
|
|
6437
6598
|
stop() {
|
|
@@ -6547,15 +6708,15 @@ var VoiceIO = class extends VoiceEngine {
|
|
|
6547
6708
|
};
|
|
6548
6709
|
|
|
6549
6710
|
// cli/config.ts
|
|
6550
|
-
import { homedir as
|
|
6551
|
-
import { existsSync as
|
|
6711
|
+
import { homedir as homedir4 } from "os";
|
|
6712
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
|
|
6552
6713
|
import { join as join5 } from "path";
|
|
6553
6714
|
import { pathToFileURL } from "url";
|
|
6554
6715
|
var FILES = ["config.ts", "config.js", "config.mjs", "config.json"];
|
|
6555
6716
|
async function loadFrom(dir) {
|
|
6556
6717
|
for (const f of FILES) {
|
|
6557
6718
|
const p = join5(dir, ".agent", f);
|
|
6558
|
-
if (!
|
|
6719
|
+
if (!existsSync5(p)) continue;
|
|
6559
6720
|
try {
|
|
6560
6721
|
const mod = await import(pathToFileURL(p).href, f.endsWith(".json") ? { with: { type: "json" } } : void 0);
|
|
6561
6722
|
return mod.default ?? mod.config ?? mod;
|
|
@@ -6568,9 +6729,9 @@ async function loadFrom(dir) {
|
|
|
6568
6729
|
}
|
|
6569
6730
|
function loadSettings(dir) {
|
|
6570
6731
|
const p = join5(dir, ".agent", "settings.json");
|
|
6571
|
-
if (!
|
|
6732
|
+
if (!existsSync5(p)) return {};
|
|
6572
6733
|
try {
|
|
6573
|
-
const raw = JSON.parse(
|
|
6734
|
+
const raw = JSON.parse(readFileSync3(p, "utf8"));
|
|
6574
6735
|
const cfg = {};
|
|
6575
6736
|
if (raw.mcpServers && typeof raw.mcpServers === "object") cfg.mcpServers = raw.mcpServers;
|
|
6576
6737
|
if (raw.permissions && typeof raw.permissions === "object") cfg.permissions = raw.permissions;
|
|
@@ -6591,8 +6752,8 @@ function loadSettings(dir) {
|
|
|
6591
6752
|
}
|
|
6592
6753
|
}
|
|
6593
6754
|
async function loadConfig(cwd) {
|
|
6594
|
-
const userSettings = loadSettings(
|
|
6595
|
-
const user = await loadFrom(
|
|
6755
|
+
const userSettings = loadSettings(homedir4());
|
|
6756
|
+
const user = await loadFrom(homedir4());
|
|
6596
6757
|
const projectSettings = loadSettings(cwd);
|
|
6597
6758
|
const project = await loadFrom(cwd);
|
|
6598
6759
|
const merged = { ...userSettings, ...user, ...projectSettings, ...project };
|
|
@@ -6604,7 +6765,7 @@ async function loadConfig(cwd) {
|
|
|
6604
6765
|
|
|
6605
6766
|
// cli/hooks-config.ts
|
|
6606
6767
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
6607
|
-
var
|
|
6768
|
+
var log16 = forComponent("hooks");
|
|
6608
6769
|
var escapeRegex = (s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
6609
6770
|
function ruleMatches(rule, toolName) {
|
|
6610
6771
|
if (!rule.tool || rule.tool === "*") return true;
|
|
@@ -6621,7 +6782,7 @@ function runCmd(rule, env) {
|
|
|
6621
6782
|
});
|
|
6622
6783
|
return { code: r.status ?? 1, out: ((r.stdout ?? "") + (r.stderr ?? "")).trim() };
|
|
6623
6784
|
} catch (e) {
|
|
6624
|
-
|
|
6785
|
+
log16.debug(`hook command failed: ${rule.command}`, e);
|
|
6625
6786
|
return { code: 1, out: String(e?.message ?? e) };
|
|
6626
6787
|
}
|
|
6627
6788
|
}
|
|
@@ -6725,11 +6886,11 @@ function formatDiff(ops, opts = {}) {
|
|
|
6725
6886
|
}
|
|
6726
6887
|
|
|
6727
6888
|
// cli/session.ts
|
|
6728
|
-
import { existsSync as
|
|
6729
|
-
import { homedir as
|
|
6889
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3, readdirSync, renameSync, symlinkSync, unlinkSync, readlinkSync } from "fs";
|
|
6890
|
+
import { homedir as homedir5 } from "os";
|
|
6730
6891
|
import { join as join6 } from "path";
|
|
6731
|
-
var
|
|
6732
|
-
var globalDir = () => join6(
|
|
6892
|
+
var log17 = forComponent("session");
|
|
6893
|
+
var globalDir = () => join6(homedir5(), ".agent", "sessions");
|
|
6733
6894
|
var SessionStore = class {
|
|
6734
6895
|
dir;
|
|
6735
6896
|
constructor(cwd) {
|
|
@@ -6741,10 +6902,10 @@ var SessionStore = class {
|
|
|
6741
6902
|
const p = (n, w = 2) => String(n).padStart(w, "0");
|
|
6742
6903
|
const slug2 = (cwd ?? process.cwd()).split("/").pop()?.replace(/[^A-Za-z0-9_-]/g, "") || "session";
|
|
6743
6904
|
let id = `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}-${slug2}`;
|
|
6744
|
-
if (
|
|
6905
|
+
if (existsSync6(this.dir) && existsSync6(join6(this.dir, `${id}.json`))) {
|
|
6745
6906
|
for (let i = 2; i <= 99; i++) {
|
|
6746
6907
|
const c = `${id}-${i}`;
|
|
6747
|
-
if (!
|
|
6908
|
+
if (!existsSync6(join6(this.dir, `${c}.json`))) {
|
|
6748
6909
|
id = c;
|
|
6749
6910
|
break;
|
|
6750
6911
|
}
|
|
@@ -6758,43 +6919,43 @@ var SessionStore = class {
|
|
|
6758
6919
|
}
|
|
6759
6920
|
save(data) {
|
|
6760
6921
|
if (!this.safeId(data.meta.id)) throw new Error(`unsafe session id: ${data.meta.id}`);
|
|
6761
|
-
if (!
|
|
6922
|
+
if (!existsSync6(this.dir)) mkdirSync4(this.dir, { recursive: true });
|
|
6762
6923
|
const path = join6(this.dir, `${data.meta.id}.json`);
|
|
6763
6924
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
6764
6925
|
writeFileSync3(tmp, JSON.stringify(data));
|
|
6765
6926
|
renameSync(tmp, path);
|
|
6766
6927
|
try {
|
|
6767
6928
|
const gd = globalDir();
|
|
6768
|
-
if (!
|
|
6929
|
+
if (!existsSync6(gd)) mkdirSync4(gd, { recursive: true });
|
|
6769
6930
|
const link2 = join6(gd, `${data.meta.id}.json`);
|
|
6770
|
-
if (!
|
|
6931
|
+
if (!existsSync6(link2)) symlinkSync(path, link2);
|
|
6771
6932
|
} catch {
|
|
6772
6933
|
}
|
|
6773
6934
|
}
|
|
6774
6935
|
load(id) {
|
|
6775
6936
|
if (!this.safeId(id)) {
|
|
6776
|
-
|
|
6937
|
+
log17.debug(`rejecting unsafe session id: ${id}`);
|
|
6777
6938
|
return void 0;
|
|
6778
6939
|
}
|
|
6779
6940
|
const path = join6(this.dir, `${id}.json`);
|
|
6780
|
-
if (!
|
|
6941
|
+
if (!existsSync6(path)) return void 0;
|
|
6781
6942
|
try {
|
|
6782
|
-
return JSON.parse(
|
|
6943
|
+
return JSON.parse(readFileSync4(path, "utf8"));
|
|
6783
6944
|
} catch (e) {
|
|
6784
|
-
|
|
6945
|
+
log17.debug(`unreadable session ${id} \u2014 ignoring`, e);
|
|
6785
6946
|
return void 0;
|
|
6786
6947
|
}
|
|
6787
6948
|
}
|
|
6788
6949
|
/** All sessions' metadata, most-recently-updated first. */
|
|
6789
6950
|
list() {
|
|
6790
|
-
if (!
|
|
6951
|
+
if (!existsSync6(this.dir)) return [];
|
|
6791
6952
|
const metas = [];
|
|
6792
6953
|
for (const f of readdirSync(this.dir)) {
|
|
6793
6954
|
if (!f.endsWith(".json")) continue;
|
|
6794
6955
|
try {
|
|
6795
|
-
metas.push(JSON.parse(
|
|
6956
|
+
metas.push(JSON.parse(readFileSync4(join6(this.dir, f), "utf8")).meta);
|
|
6796
6957
|
} catch (e) {
|
|
6797
|
-
|
|
6958
|
+
log17.debug(`skipping unreadable session file ${f}`, e);
|
|
6798
6959
|
}
|
|
6799
6960
|
}
|
|
6800
6961
|
return metas.sort((a, b) => b.updated - a.updated);
|
|
@@ -6810,12 +6971,12 @@ var SessionStore = class {
|
|
|
6810
6971
|
};
|
|
6811
6972
|
function globalSessionLoad(idOrPrefix) {
|
|
6812
6973
|
const gd = globalDir();
|
|
6813
|
-
if (!
|
|
6974
|
+
if (!existsSync6(gd)) return void 0;
|
|
6814
6975
|
const exact = join6(gd, `${idOrPrefix}.json`);
|
|
6815
|
-
if (
|
|
6976
|
+
if (existsSync6(exact)) {
|
|
6816
6977
|
try {
|
|
6817
6978
|
const target = readlinkSync(exact);
|
|
6818
|
-
return JSON.parse(
|
|
6979
|
+
return JSON.parse(readFileSync4(target, "utf8"));
|
|
6819
6980
|
} catch {
|
|
6820
6981
|
return void 0;
|
|
6821
6982
|
}
|
|
@@ -6826,7 +6987,7 @@ function globalSessionLoad(idOrPrefix) {
|
|
|
6826
6987
|
const base = f.slice(0, -5);
|
|
6827
6988
|
if (base.includes(idOrPrefix) || base.endsWith(idOrPrefix)) {
|
|
6828
6989
|
const target = readlinkSync(join6(gd, f));
|
|
6829
|
-
return JSON.parse(
|
|
6990
|
+
return JSON.parse(readFileSync4(target, "utf8"));
|
|
6830
6991
|
}
|
|
6831
6992
|
}
|
|
6832
6993
|
} catch {
|
|
@@ -6835,20 +6996,20 @@ function globalSessionLoad(idOrPrefix) {
|
|
|
6835
6996
|
}
|
|
6836
6997
|
function globalSessionList() {
|
|
6837
6998
|
const gd = globalDir();
|
|
6838
|
-
if (!
|
|
6999
|
+
if (!existsSync6(gd)) return [];
|
|
6839
7000
|
const metas = [];
|
|
6840
7001
|
for (const f of readdirSync(gd)) {
|
|
6841
7002
|
if (!f.endsWith(".json")) continue;
|
|
6842
7003
|
try {
|
|
6843
7004
|
const target = readlinkSync(join6(gd, f));
|
|
6844
|
-
if (!
|
|
7005
|
+
if (!existsSync6(target)) {
|
|
6845
7006
|
try {
|
|
6846
7007
|
unlinkSync(join6(gd, f));
|
|
6847
7008
|
} catch {
|
|
6848
7009
|
}
|
|
6849
7010
|
continue;
|
|
6850
7011
|
}
|
|
6851
|
-
metas.push(JSON.parse(
|
|
7012
|
+
metas.push(JSON.parse(readFileSync4(target, "utf8")).meta);
|
|
6852
7013
|
} catch {
|
|
6853
7014
|
}
|
|
6854
7015
|
}
|
|
@@ -6873,7 +7034,7 @@ var CheckpointStack = class {
|
|
|
6873
7034
|
current;
|
|
6874
7035
|
/** Open a new turn frame (call right before sending a user turn). */
|
|
6875
7036
|
begin(label) {
|
|
6876
|
-
this.current = { label: label
|
|
7037
|
+
this.current = { label: sanitizeLabel(label) || "(turn)", at: Date.now(), saved: /* @__PURE__ */ new Map() };
|
|
6877
7038
|
this.frames.push(this.current);
|
|
6878
7039
|
if (this.frames.length > this.max) this.frames.shift();
|
|
6879
7040
|
}
|
|
@@ -6901,6 +7062,24 @@ var CheckpointStack = class {
|
|
|
6901
7062
|
list() {
|
|
6902
7063
|
return this.frames.map((f, i) => ({ index: i, label: f.label, at: f.at, files: f.saved.size })).reverse();
|
|
6903
7064
|
}
|
|
7065
|
+
/** Unified-style diff of all session edits: each file's OLDEST saved content vs its current content. */
|
|
7066
|
+
async diff() {
|
|
7067
|
+
const base = /* @__PURE__ */ new Map();
|
|
7068
|
+
for (const f of this.frames) for (const [path, prior] of f.saved) if (!base.has(path)) base.set(path, prior);
|
|
7069
|
+
const parts = [];
|
|
7070
|
+
for (const [path, prior] of base) {
|
|
7071
|
+
let now5 = null;
|
|
7072
|
+
try {
|
|
7073
|
+
now5 = await this.fs.readFile(path);
|
|
7074
|
+
} catch {
|
|
7075
|
+
}
|
|
7076
|
+
if ((prior ?? "") === (now5 ?? "")) continue;
|
|
7077
|
+
const ops = diffLines(prior ?? "", now5 ?? "");
|
|
7078
|
+
parts.push(`--- ${path}${prior == null ? " (new)" : now5 == null ? " (deleted)" : ""}
|
|
7079
|
+
${formatDiff(ops)}`);
|
|
7080
|
+
}
|
|
7081
|
+
return parts.join("\n");
|
|
7082
|
+
}
|
|
6904
7083
|
/**
|
|
6905
7084
|
* Restore the working tree to BEFORE frame `index` — undo that frame and every later one.
|
|
6906
7085
|
* Frames are replayed newest→oldest so the OLDEST saved content for a path wins (its true
|
|
@@ -6930,12 +7109,12 @@ var CheckpointStack = class {
|
|
|
6930
7109
|
};
|
|
6931
7110
|
|
|
6932
7111
|
// cli/gitCheckpoints.ts
|
|
6933
|
-
import { execFile } from "child_process";
|
|
7112
|
+
import { execFile as execFile3 } from "child_process";
|
|
6934
7113
|
import { promisify } from "util";
|
|
6935
|
-
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as
|
|
7114
|
+
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync7 } from "fs";
|
|
6936
7115
|
import { join as join7, resolve as resolve2, sep as sep2 } from "path";
|
|
6937
|
-
var
|
|
6938
|
-
var exec = promisify(
|
|
7116
|
+
var log18 = forComponent("checkpoints");
|
|
7117
|
+
var exec = promisify(execFile3);
|
|
6939
7118
|
var DEFAULT_EXCLUDE = [".agent/", ".git/", "node_modules/", "dist/", "build/", ".next/", "target/", ".venv/", "__pycache__/", "*.log"];
|
|
6940
7119
|
var ShadowRepo = class {
|
|
6941
7120
|
// undefined = unprobed; false = git/this root unusable
|
|
@@ -6961,14 +7140,14 @@ var ShadowRepo = class {
|
|
|
6961
7140
|
if (this.ready !== void 0) return this.ready;
|
|
6962
7141
|
try {
|
|
6963
7142
|
await exec(this.git, ["--version"]);
|
|
6964
|
-
if (!
|
|
7143
|
+
if (!existsSync7(this.gitDir)) {
|
|
6965
7144
|
mkdirSync5(this.gitDir, { recursive: true });
|
|
6966
7145
|
await this.run("init", "-q");
|
|
6967
7146
|
}
|
|
6968
7147
|
writeFileSync4(join7(this.gitDir, "info", "exclude"), this.exclude.join("\n") + "\n");
|
|
6969
7148
|
this.ready = true;
|
|
6970
7149
|
} catch (e) {
|
|
6971
|
-
|
|
7150
|
+
log18.debug(`git checkpoints unavailable for ${this.workTree}`, e);
|
|
6972
7151
|
this.ready = false;
|
|
6973
7152
|
}
|
|
6974
7153
|
return this.ready;
|
|
@@ -7001,6 +7180,14 @@ var ShadowRepo = class {
|
|
|
7001
7180
|
return 0;
|
|
7002
7181
|
}
|
|
7003
7182
|
}
|
|
7183
|
+
/** Unified diff between `sha` and the current work-tree (for `/diff`). */
|
|
7184
|
+
async diffSince(sha) {
|
|
7185
|
+
try {
|
|
7186
|
+
return await this.run("diff", sha);
|
|
7187
|
+
} catch {
|
|
7188
|
+
return "";
|
|
7189
|
+
}
|
|
7190
|
+
}
|
|
7004
7191
|
/** Restore the tree to `sha`; returns counts of reverted tracked files + removed untracked-new. */
|
|
7005
7192
|
async resetTo(sha) {
|
|
7006
7193
|
let restored = 0, deleted = 0;
|
|
@@ -7031,7 +7218,7 @@ var ShadowRepo = class {
|
|
|
7031
7218
|
await this.run("gc", "--auto", "-q").catch(() => {
|
|
7032
7219
|
});
|
|
7033
7220
|
} catch (e) {
|
|
7034
|
-
|
|
7221
|
+
log18.debug("checkpoint prune failed", e);
|
|
7035
7222
|
}
|
|
7036
7223
|
}
|
|
7037
7224
|
};
|
|
@@ -7088,18 +7275,18 @@ var GitCheckpoints = class {
|
|
|
7088
7275
|
use(sessionId) {
|
|
7089
7276
|
if (sessionId === this.session) return;
|
|
7090
7277
|
this.session = sessionId;
|
|
7091
|
-
if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) =>
|
|
7278
|
+
if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) => log18.debug("re-point failed", e));
|
|
7092
7279
|
}
|
|
7093
7280
|
async begin(label) {
|
|
7094
7281
|
if (!await this.start()) return;
|
|
7095
|
-
const msg = label
|
|
7282
|
+
const msg = sanitizeLabel(label, 72) || "(turn)";
|
|
7096
7283
|
let slow;
|
|
7097
7284
|
if (!this.snapshotted) slow = setTimeout(() => process.stderr.write("\x1B[2m checkpointing initial workspace snapshot\u2026\x1B[0m\n"), 1500);
|
|
7098
7285
|
for (const r of this.repos) {
|
|
7099
7286
|
try {
|
|
7100
7287
|
await r.commit(msg);
|
|
7101
7288
|
} catch (e) {
|
|
7102
|
-
|
|
7289
|
+
log18.debug("checkpoint commit failed", e);
|
|
7103
7290
|
}
|
|
7104
7291
|
}
|
|
7105
7292
|
if (slow) clearTimeout(slow);
|
|
@@ -7123,6 +7310,16 @@ var GitCheckpoints = class {
|
|
|
7123
7310
|
get size() {
|
|
7124
7311
|
return this.caches[0]?.length ?? 0;
|
|
7125
7312
|
}
|
|
7313
|
+
/** Unified diff of everything this session changed: oldest session checkpoint → current tree, across all roots. */
|
|
7314
|
+
async diff() {
|
|
7315
|
+
if (!this.caches[0]?.length) await this.refresh();
|
|
7316
|
+
const parts = [];
|
|
7317
|
+
for (let i = 0; i < this.repos.length; i++) {
|
|
7318
|
+
const base = this.caches[i]?.[0];
|
|
7319
|
+
if (base) parts.push(await this.repos[i].diffSince(base.sha));
|
|
7320
|
+
}
|
|
7321
|
+
return parts.filter(Boolean).join("\n");
|
|
7322
|
+
}
|
|
7126
7323
|
async rewindTo(index) {
|
|
7127
7324
|
if (!this.caches[0]?.length) await this.refresh();
|
|
7128
7325
|
if (index < 0 || index >= (this.caches[0]?.length ?? 0)) throw new Error("no such checkpoint");
|
|
@@ -7156,8 +7353,8 @@ var GitCheckpointsOptions = class {
|
|
|
7156
7353
|
};
|
|
7157
7354
|
|
|
7158
7355
|
// cli/permissions.ts
|
|
7159
|
-
import {
|
|
7160
|
-
import { homedir as
|
|
7356
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync6 } from "fs";
|
|
7357
|
+
import { homedir as homedir6 } from "os";
|
|
7161
7358
|
import { join as join8 } from "path";
|
|
7162
7359
|
var RULE_RE = /^(\w+)(?:\((.+)\))?$/;
|
|
7163
7360
|
function parseOne(raw, decision) {
|
|
@@ -7182,25 +7379,15 @@ function describeRule(r) {
|
|
|
7182
7379
|
}
|
|
7183
7380
|
var PERM_FILE = (cwd) => join8(cwd, ".agent", "permissions.json");
|
|
7184
7381
|
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
|
-
}
|
|
7382
|
+
const j = readJsonFile(PERM_FILE(cwd), null);
|
|
7383
|
+
return j ? { allow: j.allow ?? [], ask: j.ask ?? [], deny: j.deny ?? [] } : {};
|
|
7193
7384
|
}
|
|
7194
|
-
function loadClaudeSettings(cwd, home =
|
|
7385
|
+
function loadClaudeSettings(cwd, home = homedir6()) {
|
|
7195
7386
|
const files = [join8(home, ".claude", "settings.json"), join8(cwd, ".claude", "settings.json"), join8(cwd, ".claude", "settings.local.json")];
|
|
7196
7387
|
let out = {};
|
|
7197
7388
|
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
|
-
}
|
|
7389
|
+
const perms = readJsonFile(p, null)?.permissions;
|
|
7390
|
+
if (perms) out = mergePerms(out, { allow: perms.allow, ask: perms.ask, deny: perms.deny }) ?? out;
|
|
7204
7391
|
}
|
|
7205
7392
|
return out;
|
|
7206
7393
|
}
|
|
@@ -7223,20 +7410,14 @@ function mergePerms(a, b) {
|
|
|
7223
7410
|
}
|
|
7224
7411
|
return Object.keys(out).length ? out : void 0;
|
|
7225
7412
|
}
|
|
7226
|
-
var TRUST_FILE = join8(
|
|
7413
|
+
var TRUST_FILE = join8(homedir6(), ".agent", "trusted.json");
|
|
7227
7414
|
function isTrusted(cwd, file = TRUST_FILE) {
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
} catch {
|
|
7231
|
-
return false;
|
|
7232
|
-
}
|
|
7415
|
+
const list = readJsonFile(file, []);
|
|
7416
|
+
return Array.isArray(list) && list.includes(cwd);
|
|
7233
7417
|
}
|
|
7234
7418
|
function trustDir(cwd, file = TRUST_FILE) {
|
|
7235
|
-
let list = [];
|
|
7236
|
-
|
|
7237
|
-
if (existsSync7(file)) list = JSON.parse(readFileSync4(file, "utf8"));
|
|
7238
|
-
} catch {
|
|
7239
|
-
}
|
|
7419
|
+
let list = readJsonFile(file, []);
|
|
7420
|
+
if (!Array.isArray(list)) list = [];
|
|
7240
7421
|
if (!list.includes(cwd)) list.push(cwd);
|
|
7241
7422
|
try {
|
|
7242
7423
|
mkdirSync6(join8(file, ".."), { recursive: true });
|
|
@@ -7291,14 +7472,18 @@ function completePath(listDir, ref) {
|
|
|
7291
7472
|
for (const e of entries) {
|
|
7292
7473
|
if (!fuzzy(base, e.name)) continue;
|
|
7293
7474
|
if (e.name.startsWith(".") && !base.startsWith(".")) continue;
|
|
7294
|
-
const rel = dir ? `${dir}/${e.name}` : e.name;
|
|
7295
|
-
matched.push(
|
|
7475
|
+
const rel = (dir ? `${dir}/${e.name}` : e.name) + (e.dir ? "/" : "");
|
|
7476
|
+
matched.push(/\s/.test(rel) ? `@"${rel}"` : "@" + rel);
|
|
7296
7477
|
}
|
|
7297
7478
|
return rank(matched, "@" + (dir ? dir + "/" : "") + base);
|
|
7298
7479
|
}
|
|
7299
7480
|
|
|
7300
7481
|
// cli/lineEditor.ts
|
|
7301
7482
|
import { emitKeypressEvents } from "readline";
|
|
7483
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
7484
|
+
import { writeFileSync as writeFileSync6, readFileSync as readFileSync5, unlinkSync as unlinkSync2 } from "fs";
|
|
7485
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
7486
|
+
import { join as join9 } from "path";
|
|
7302
7487
|
|
|
7303
7488
|
// cli/bidi.ts
|
|
7304
7489
|
var RTL_RE = /[\u0590-\u05ff\u0600-\u06ff\u0750-\u077f\u08a0-\u08ff\ufb1d-\ufdff\ufe70-\ufeff]/;
|
|
@@ -7429,8 +7614,61 @@ function applyBidi(buf, cursor) {
|
|
|
7429
7614
|
return { text: out.join("\n"), cursor: visCursor };
|
|
7430
7615
|
}
|
|
7431
7616
|
|
|
7617
|
+
// cli/stdinSource.ts
|
|
7618
|
+
import { PassThrough } from "stream";
|
|
7619
|
+
var cancelPump;
|
|
7620
|
+
var keyInput = (() => {
|
|
7621
|
+
const bun = globalThis.Bun;
|
|
7622
|
+
if (!bun || !process.stdin.isTTY) return process.stdin;
|
|
7623
|
+
const pt = new PassThrough();
|
|
7624
|
+
pt.isTTY = true;
|
|
7625
|
+
pt.setRawMode = (mode) => {
|
|
7626
|
+
process.stdin.setRawMode(mode);
|
|
7627
|
+
return pt;
|
|
7628
|
+
};
|
|
7629
|
+
Object.defineProperty(pt, "isRaw", { get: () => process.stdin.isRaw });
|
|
7630
|
+
let pump = () => {
|
|
7631
|
+
pump = void 0;
|
|
7632
|
+
const reader = bun.stdin.stream().getReader();
|
|
7633
|
+
let stopped = false;
|
|
7634
|
+
cancelPump = () => {
|
|
7635
|
+
stopped = true;
|
|
7636
|
+
void reader.cancel().catch(() => {
|
|
7637
|
+
});
|
|
7638
|
+
};
|
|
7639
|
+
void (async () => {
|
|
7640
|
+
try {
|
|
7641
|
+
while (!stopped) {
|
|
7642
|
+
const { value, done } = await reader.read();
|
|
7643
|
+
if (done) break;
|
|
7644
|
+
if (value?.length) pt.write(Buffer.from(value));
|
|
7645
|
+
}
|
|
7646
|
+
} catch {
|
|
7647
|
+
}
|
|
7648
|
+
pt.end();
|
|
7649
|
+
})();
|
|
7650
|
+
};
|
|
7651
|
+
const ptResume = pt.resume.bind(pt);
|
|
7652
|
+
pt.resume = () => {
|
|
7653
|
+
pump?.();
|
|
7654
|
+
return ptResume();
|
|
7655
|
+
};
|
|
7656
|
+
return pt;
|
|
7657
|
+
})();
|
|
7658
|
+
function releaseKeyInput() {
|
|
7659
|
+
cancelPump?.();
|
|
7660
|
+
}
|
|
7661
|
+
|
|
7432
7662
|
// cli/lineEditor.ts
|
|
7433
|
-
var
|
|
7663
|
+
var cpWidth = (cp) => {
|
|
7664
|
+
if (cp === 8205 || cp === 65039 || cp >= 768 && cp <= 879) return 0;
|
|
7665
|
+
return cp >= 4352 && (cp <= 4447 || cp === 9001 || cp === 9002 || cp >= 11904 && cp <= 42191 && cp !== 12351 || cp >= 44032 && cp <= 55203 || cp >= 63744 && cp <= 64255 || cp >= 65072 && cp <= 65103 || cp >= 65280 && cp <= 65376 || cp >= 65504 && cp <= 65510 || cp >= 126976 && cp <= 129791 || cp >= 131072 && cp <= 262141) ? 2 : 1;
|
|
7666
|
+
};
|
|
7667
|
+
var visibleWidth = (s) => {
|
|
7668
|
+
let w = 0;
|
|
7669
|
+
for (const ch of s.replace(/\x1b\[[0-9;]*m/g, "")) w += cpWidth(ch.codePointAt(0));
|
|
7670
|
+
return w;
|
|
7671
|
+
};
|
|
7434
7672
|
var isPrintable = (str) => !!str && !/[\x00-\x1f\x7f]/.test(str);
|
|
7435
7673
|
var EditorState = class _EditorState {
|
|
7436
7674
|
// pending operator awaiting a motion (dd/dw/cc)
|
|
@@ -7564,6 +7802,17 @@ var EditorState = class _EditorState {
|
|
|
7564
7802
|
this.histIdx = -1;
|
|
7565
7803
|
this.refresh();
|
|
7566
7804
|
}
|
|
7805
|
+
// Code-point boundaries: cursor/delete ops must never split a surrogate pair (emoji = 2 code units;
|
|
7806
|
+
// a half-pair in the buffer reaches the model as mojibake). Steps are by code POINT (lean — ZWJ
|
|
7807
|
+
// families delete component-wise, which even many terminals do).
|
|
7808
|
+
prevCp(i) {
|
|
7809
|
+
if (i >= 2 && /[\uDC00-\uDFFF]/.test(this.buf[i - 1]) && /[\uD800-\uDBFF]/.test(this.buf[i - 2])) return i - 2;
|
|
7810
|
+
return Math.max(0, i - 1);
|
|
7811
|
+
}
|
|
7812
|
+
nextCp(i) {
|
|
7813
|
+
if (i <= this.buf.length - 2 && /[\uD800-\uDBFF]/.test(this.buf[i]) && /[\uDC00-\uDFFF]/.test(this.buf[i + 1])) return i + 2;
|
|
7814
|
+
return Math.min(this.buf.length, i + 1);
|
|
7815
|
+
}
|
|
7567
7816
|
insert(s) {
|
|
7568
7817
|
this.snapshot();
|
|
7569
7818
|
this.buf = this.buf.slice(0, this.cursor) + s + this.buf.slice(this.cursor);
|
|
@@ -7574,22 +7823,23 @@ var EditorState = class _EditorState {
|
|
|
7574
7823
|
backspace() {
|
|
7575
7824
|
if (this.cursor === 0) return;
|
|
7576
7825
|
this.snapshot();
|
|
7577
|
-
|
|
7578
|
-
this.cursor
|
|
7826
|
+
const start = this.prevCp(this.cursor);
|
|
7827
|
+
this.buf = this.buf.slice(0, start) + this.buf.slice(this.cursor);
|
|
7828
|
+
this.cursor = start;
|
|
7579
7829
|
this.histIdx = -1;
|
|
7580
7830
|
this.refresh();
|
|
7581
7831
|
}
|
|
7582
7832
|
del() {
|
|
7583
7833
|
if (this.cursor >= this.buf.length) return;
|
|
7584
7834
|
this.snapshot();
|
|
7585
|
-
this.buf = this.buf.slice(0, this.cursor) + this.buf.slice(this.cursor
|
|
7835
|
+
this.buf = this.buf.slice(0, this.cursor) + this.buf.slice(this.nextCp(this.cursor));
|
|
7586
7836
|
this.refresh();
|
|
7587
7837
|
}
|
|
7588
7838
|
left() {
|
|
7589
|
-
if (this.cursor > 0) this.cursor
|
|
7839
|
+
if (this.cursor > 0) this.cursor = this.prevCp(this.cursor);
|
|
7590
7840
|
}
|
|
7591
7841
|
right() {
|
|
7592
|
-
if (this.cursor < this.buf.length) this.cursor
|
|
7842
|
+
if (this.cursor < this.buf.length) this.cursor = this.nextCp(this.cursor);
|
|
7593
7843
|
}
|
|
7594
7844
|
home() {
|
|
7595
7845
|
this.cursor = 0;
|
|
@@ -8073,6 +8323,30 @@ function createLineEditor(out) {
|
|
|
8073
8323
|
if (cursorCol > 0) out.write(`\x1B[${cursorCol}C`);
|
|
8074
8324
|
curRow = cursorRow;
|
|
8075
8325
|
}
|
|
8326
|
+
function externalEdit(s) {
|
|
8327
|
+
const spec = process.env.VISUAL || process.env.EDITOR || "vi";
|
|
8328
|
+
const [cmd, ...cargs] = spec.split(" ").filter(Boolean);
|
|
8329
|
+
const file = join9(tmpdir2(), `agentx-edit-${process.pid}-${Date.now()}.md`);
|
|
8330
|
+
try {
|
|
8331
|
+
writeFileSync6(file, s.buf);
|
|
8332
|
+
process.stdin.setRawMode(false);
|
|
8333
|
+
out.write("\x1B[?2004l");
|
|
8334
|
+
const r = spawnSync3(cmd, [...cargs, file], { stdio: "inherit" });
|
|
8335
|
+
if (r.status === 0) {
|
|
8336
|
+
const text = readFileSync5(file, "utf8").replace(/\n$/, "");
|
|
8337
|
+
s.reset();
|
|
8338
|
+
if (text) s.insert(text);
|
|
8339
|
+
}
|
|
8340
|
+
} catch {
|
|
8341
|
+
} finally {
|
|
8342
|
+
try {
|
|
8343
|
+
unlinkSync2(file);
|
|
8344
|
+
} catch {
|
|
8345
|
+
}
|
|
8346
|
+
process.stdin.setRawMode(true);
|
|
8347
|
+
out.write("\x1B[?2004h");
|
|
8348
|
+
}
|
|
8349
|
+
}
|
|
8076
8350
|
async function readLine(opts) {
|
|
8077
8351
|
const maxVisible = opts.maxVisible ?? 8;
|
|
8078
8352
|
if (!isTTY) return readPlainLine();
|
|
@@ -8080,9 +8354,9 @@ function createLineEditor(out) {
|
|
|
8080
8354
|
curRow = 0;
|
|
8081
8355
|
if (opts.initial) s.insert(opts.initial);
|
|
8082
8356
|
s.refresh();
|
|
8083
|
-
emitKeypressEvents(
|
|
8084
|
-
|
|
8085
|
-
|
|
8357
|
+
emitKeypressEvents(keyInput);
|
|
8358
|
+
keyInput.setRawMode(true);
|
|
8359
|
+
keyInput.resume();
|
|
8086
8360
|
out.write("\x1B[?2004h");
|
|
8087
8361
|
const promptOf = () => typeof opts.prompt === "function" ? opts.prompt() : opts.prompt;
|
|
8088
8362
|
render(s, promptOf(), maxVisible, opts.status);
|
|
@@ -8109,6 +8383,7 @@ function createLineEditor(out) {
|
|
|
8109
8383
|
lastStatus = cur;
|
|
8110
8384
|
redraw();
|
|
8111
8385
|
}, opts.statusTickMs) : void 0;
|
|
8386
|
+
let chordCtrlX = false;
|
|
8112
8387
|
const onKey = (str, key) => {
|
|
8113
8388
|
if (key?.ctrl && key.name === "l") {
|
|
8114
8389
|
out.write("\x1B[2J\x1B[3J\x1B[H");
|
|
@@ -8116,6 +8391,19 @@ function createLineEditor(out) {
|
|
|
8116
8391
|
redraw();
|
|
8117
8392
|
return;
|
|
8118
8393
|
}
|
|
8394
|
+
if (key?.ctrl && key.name === "x" && !s.pasting) {
|
|
8395
|
+
chordCtrlX = true;
|
|
8396
|
+
return;
|
|
8397
|
+
}
|
|
8398
|
+
if (chordCtrlX) {
|
|
8399
|
+
chordCtrlX = false;
|
|
8400
|
+
if (key?.ctrl && key.name === "e") {
|
|
8401
|
+
externalEdit(s);
|
|
8402
|
+
curRow = 0;
|
|
8403
|
+
redraw();
|
|
8404
|
+
return;
|
|
8405
|
+
}
|
|
8406
|
+
}
|
|
8119
8407
|
if (key?.name === "tab" && key.shift && opts.onCyclePosture) {
|
|
8120
8408
|
opts.onCyclePosture();
|
|
8121
8409
|
redraw();
|
|
@@ -8152,9 +8440,9 @@ function createLineEditor(out) {
|
|
|
8152
8440
|
return;
|
|
8153
8441
|
}
|
|
8154
8442
|
if (key?.meta && key.name === "p" && opts.onPickModel) {
|
|
8155
|
-
|
|
8443
|
+
keyInput.off("keypress", onKey);
|
|
8156
8444
|
void opts.onPickModel().finally(() => {
|
|
8157
|
-
|
|
8445
|
+
keyInput.on("keypress", onKey);
|
|
8158
8446
|
curRow = 0;
|
|
8159
8447
|
redraw();
|
|
8160
8448
|
});
|
|
@@ -8163,6 +8451,7 @@ function createLineEditor(out) {
|
|
|
8163
8451
|
const action = applyKey(s, key ?? {}, str);
|
|
8164
8452
|
if (action === "submit") {
|
|
8165
8453
|
finish();
|
|
8454
|
+
opts.onSubmit?.();
|
|
8166
8455
|
return resolve4(s.expand());
|
|
8167
8456
|
}
|
|
8168
8457
|
if (action === "eof") {
|
|
@@ -8190,15 +8479,14 @@ function createLineEditor(out) {
|
|
|
8190
8479
|
activeRedraw = void 0;
|
|
8191
8480
|
activeAbort = void 0;
|
|
8192
8481
|
if (ticker) clearInterval(ticker);
|
|
8193
|
-
|
|
8482
|
+
keyInput.off("keypress", onKey);
|
|
8194
8483
|
process.removeListener("SIGWINCH", onResize);
|
|
8195
|
-
out.write("\x1B[?2004l");
|
|
8196
8484
|
s.closeMenu();
|
|
8197
8485
|
s.end();
|
|
8198
8486
|
render(s, promptOf(), maxVisible);
|
|
8199
8487
|
out.write("\r\n");
|
|
8200
8488
|
};
|
|
8201
|
-
|
|
8489
|
+
keyInput.on("keypress", onKey);
|
|
8202
8490
|
});
|
|
8203
8491
|
}
|
|
8204
8492
|
return {
|
|
@@ -8220,6 +8508,8 @@ function createLineEditor(out) {
|
|
|
8220
8508
|
abort: () => activeAbort?.()
|
|
8221
8509
|
};
|
|
8222
8510
|
}
|
|
8511
|
+
var menusOpen = 0;
|
|
8512
|
+
var isMenuActive = () => menusOpen > 0;
|
|
8223
8513
|
function selectMenu(out, opts) {
|
|
8224
8514
|
if (!out.isTTY || !process.stdin.isTTY || !opts.items.length) return Promise.resolve(null);
|
|
8225
8515
|
const maxVisible = opts.maxVisible ?? 10;
|
|
@@ -8278,18 +8568,20 @@ function selectMenu(out, opts) {
|
|
|
8278
8568
|
prevLines++;
|
|
8279
8569
|
};
|
|
8280
8570
|
return new Promise((resolve4) => {
|
|
8281
|
-
const wasRaw = !!
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8571
|
+
const wasRaw = !!keyInput.isRaw;
|
|
8572
|
+
menusOpen++;
|
|
8573
|
+
emitKeypressEvents(keyInput);
|
|
8574
|
+
keyInput.setRawMode(true);
|
|
8575
|
+
keyInput.resume();
|
|
8285
8576
|
draw();
|
|
8286
8577
|
const finish = (val) => {
|
|
8287
|
-
|
|
8578
|
+
menusOpen--;
|
|
8579
|
+
keyInput.off("keypress", onKey);
|
|
8288
8580
|
if (prevLines > 1) out.write(`\x1B[${prevLines - 1}A`);
|
|
8289
8581
|
out.write("\r\x1B[J");
|
|
8290
8582
|
if (!wasRaw) {
|
|
8291
8583
|
try {
|
|
8292
|
-
|
|
8584
|
+
keyInput.setRawMode(false);
|
|
8293
8585
|
} catch {
|
|
8294
8586
|
}
|
|
8295
8587
|
}
|
|
@@ -8352,7 +8644,7 @@ function selectMenu(out, opts) {
|
|
|
8352
8644
|
draw();
|
|
8353
8645
|
}
|
|
8354
8646
|
};
|
|
8355
|
-
|
|
8647
|
+
keyInput.on("keypress", onKey);
|
|
8356
8648
|
});
|
|
8357
8649
|
}
|
|
8358
8650
|
var INPUT_MODES = [
|
|
@@ -8372,26 +8664,35 @@ function wrapLayout(promptW, buf, cursor, cols) {
|
|
|
8372
8664
|
rows.push(line);
|
|
8373
8665
|
line = "";
|
|
8374
8666
|
};
|
|
8375
|
-
for (let i = 0; i <= buf.length;
|
|
8667
|
+
for (let i = 0; i <= buf.length; ) {
|
|
8668
|
+
const cp = i < buf.length ? buf.codePointAt(i) : -1;
|
|
8669
|
+
const ch = cp >= 0 ? String.fromCodePoint(cp) : "";
|
|
8670
|
+
const w = ch && ch !== "\n" ? cpWidth(cp) : 0;
|
|
8671
|
+
if (w === 2 && col + w > width && col > 0) {
|
|
8672
|
+
pushRow();
|
|
8673
|
+
r++;
|
|
8674
|
+
col = 0;
|
|
8675
|
+
}
|
|
8376
8676
|
if (i === cursor) {
|
|
8377
8677
|
cursorRow = r;
|
|
8378
8678
|
cursorCol = col;
|
|
8379
8679
|
}
|
|
8380
8680
|
if (i === buf.length) break;
|
|
8381
|
-
const ch = buf[i];
|
|
8382
8681
|
if (ch === "\n") {
|
|
8383
8682
|
pushRow();
|
|
8384
8683
|
r++;
|
|
8385
8684
|
col = 0;
|
|
8685
|
+
i++;
|
|
8386
8686
|
continue;
|
|
8387
8687
|
}
|
|
8388
8688
|
line += ch;
|
|
8389
|
-
col
|
|
8689
|
+
col += w;
|
|
8390
8690
|
if (col >= width) {
|
|
8391
8691
|
pushRow();
|
|
8392
8692
|
r++;
|
|
8393
8693
|
col = 0;
|
|
8394
8694
|
}
|
|
8695
|
+
i += ch.length;
|
|
8395
8696
|
}
|
|
8396
8697
|
pushRow();
|
|
8397
8698
|
return { rows, cursorRow, cursorCol };
|
|
@@ -8608,6 +8909,184 @@ var MarkdownStream = class {
|
|
|
8608
8909
|
}
|
|
8609
8910
|
};
|
|
8610
8911
|
|
|
8912
|
+
// cli/osScheduler.ts
|
|
8913
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
8914
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, readdirSync as readdirSync2, unlinkSync as unlinkSync3, chmodSync, existsSync as existsSync8 } from "fs";
|
|
8915
|
+
import { homedir as homedir7 } from "os";
|
|
8916
|
+
import { join as join10 } from "path";
|
|
8917
|
+
var log19 = forComponent("os-sched");
|
|
8918
|
+
var OsScheduler = class {
|
|
8919
|
+
options;
|
|
8920
|
+
constructor(options) {
|
|
8921
|
+
this.options = { ...new OsSchedulerOptions(), ...options };
|
|
8922
|
+
}
|
|
8923
|
+
get dir() {
|
|
8924
|
+
return join10(this.options.home, ".agent", "sched");
|
|
8925
|
+
}
|
|
8926
|
+
label(id) {
|
|
8927
|
+
return `cc.livx.agentx.sched-${id}`;
|
|
8928
|
+
}
|
|
8929
|
+
plistPath(id) {
|
|
8930
|
+
return join10(this.options.home, "Library", "LaunchAgents", `${this.label(id)}.plist`);
|
|
8931
|
+
}
|
|
8932
|
+
run(cmd, args, input) {
|
|
8933
|
+
return this.options.exec(cmd, args, input);
|
|
8934
|
+
}
|
|
8935
|
+
available() {
|
|
8936
|
+
return this.options.platform === "darwin" || this.options.platform === "linux";
|
|
8937
|
+
}
|
|
8938
|
+
/** Register the job with the OS. Returns a human description of the mechanism used. Throws on failure. */
|
|
8939
|
+
schedule(spec) {
|
|
8940
|
+
if (!this.available()) throw new Error(`no OS scheduler on ${this.options.platform}`);
|
|
8941
|
+
mkdirSync7(this.dir, { recursive: true });
|
|
8942
|
+
const oneOff = "at" in spec.trigger;
|
|
8943
|
+
const script = this.writeScript(spec, oneOff);
|
|
8944
|
+
const mechanism = this.options.platform === "darwin" ? this.scheduleDarwin(spec, script) : this.scheduleLinux(spec, script, oneOff);
|
|
8945
|
+
const meta = { ...spec, created: Date.now(), mechanism };
|
|
8946
|
+
writeFileSync7(join10(this.dir, `${spec.id}.json`), JSON.stringify(meta, null, 2));
|
|
8947
|
+
return mechanism;
|
|
8948
|
+
}
|
|
8949
|
+
cancel(id) {
|
|
8950
|
+
const meta = readJsonFile(join10(this.dir, `${id}.json`), null);
|
|
8951
|
+
if (!meta) return false;
|
|
8952
|
+
try {
|
|
8953
|
+
if (this.options.platform === "darwin") {
|
|
8954
|
+
try {
|
|
8955
|
+
this.run("launchctl", ["remove", this.label(id)]);
|
|
8956
|
+
} catch {
|
|
8957
|
+
}
|
|
8958
|
+
try {
|
|
8959
|
+
unlinkSync3(this.plistPath(id));
|
|
8960
|
+
} catch {
|
|
8961
|
+
}
|
|
8962
|
+
} else if (meta.mechanism.startsWith("crontab")) {
|
|
8963
|
+
const cur = (() => {
|
|
8964
|
+
try {
|
|
8965
|
+
return this.run("crontab", ["-l"]);
|
|
8966
|
+
} catch {
|
|
8967
|
+
return "";
|
|
8968
|
+
}
|
|
8969
|
+
})();
|
|
8970
|
+
const next = cur.split("\n").filter((l) => !l.includes(`# agentx-sched-${id}`)).join("\n");
|
|
8971
|
+
this.run("crontab", ["-"], next.trim() ? next.trimEnd() + "\n" : "");
|
|
8972
|
+
} else if (meta.mechanism.startsWith("at:")) {
|
|
8973
|
+
try {
|
|
8974
|
+
this.run("atrm", [meta.mechanism.slice(3)]);
|
|
8975
|
+
} catch {
|
|
8976
|
+
}
|
|
8977
|
+
}
|
|
8978
|
+
} catch (e) {
|
|
8979
|
+
log19.debug(`cancel ${id}`, e);
|
|
8980
|
+
}
|
|
8981
|
+
for (const f of [`${id}.json`, `${id}.sh`]) {
|
|
8982
|
+
try {
|
|
8983
|
+
unlinkSync3(join10(this.dir, f));
|
|
8984
|
+
} catch {
|
|
8985
|
+
}
|
|
8986
|
+
}
|
|
8987
|
+
return true;
|
|
8988
|
+
}
|
|
8989
|
+
list() {
|
|
8990
|
+
if (!existsSync8(this.dir)) return [];
|
|
8991
|
+
return readdirSync2(this.dir).filter((f) => f.endsWith(".json")).map((f) => readJsonFile(join10(this.dir, f), null)).filter(Boolean);
|
|
8992
|
+
}
|
|
8993
|
+
/** The per-job runner script: cd to the project, headless-resume the session, log, notify. */
|
|
8994
|
+
writeScript(spec, oneOff) {
|
|
8995
|
+
const p = join10(this.dir, `${spec.id}.sh`);
|
|
8996
|
+
const q2 = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
|
|
8997
|
+
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)}
|
|
8998
|
+
` : `rm -f ${q2(join10(this.dir, `${spec.id}.json`))} ${q2(p)}
|
|
8999
|
+
` : "";
|
|
9000
|
+
writeFileSync7(p, `#!/bin/sh
|
|
9001
|
+
# agentx scheduled job ${spec.id}${spec.label ? ` \u2014 ${spec.label}` : ""}
|
|
9002
|
+
cd ${q2(spec.cwd)} || exit 1
|
|
9003
|
+
${this.options.agentx} -p ${q2(spec.prompt)} --resume ${q2(spec.sessionId)} --yes >> ${q2(join10(this.dir, `${spec.id}.log`))} 2>&1
|
|
9004
|
+
${cleanup}`);
|
|
9005
|
+
chmodSync(p, 493);
|
|
9006
|
+
return p;
|
|
9007
|
+
}
|
|
9008
|
+
scheduleDarwin(spec, script) {
|
|
9009
|
+
const t = spec.trigger;
|
|
9010
|
+
let trigger;
|
|
9011
|
+
if ("everyMs" in t) {
|
|
9012
|
+
trigger = `<key>StartInterval</key><integer>${Math.max(60, Math.round(t.everyMs / 1e3))}</integer>`;
|
|
9013
|
+
} else if ("at" in t) {
|
|
9014
|
+
const d = new Date(t.at);
|
|
9015
|
+
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>`;
|
|
9016
|
+
} else {
|
|
9017
|
+
const f = parseCron(t.cron);
|
|
9018
|
+
const dict = [];
|
|
9019
|
+
const put = (key, vals, full) => {
|
|
9020
|
+
if (vals.length === full) return;
|
|
9021
|
+
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`);
|
|
9022
|
+
dict.push(`<key>${key}</key><integer>${vals[0]}</integer>`);
|
|
9023
|
+
};
|
|
9024
|
+
put("Minute", f.minute, 60);
|
|
9025
|
+
put("Hour", f.hour, 24);
|
|
9026
|
+
put("Day", f.dom, 31);
|
|
9027
|
+
put("Month", f.month, 12);
|
|
9028
|
+
put("Weekday", f.dow, 7);
|
|
9029
|
+
trigger = `<key>StartCalendarInterval</key><dict>${dict.join("")}</dict>`;
|
|
9030
|
+
}
|
|
9031
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
9032
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
9033
|
+
<plist version="1.0"><dict>
|
|
9034
|
+
<key>Label</key><string>${this.label(spec.id)}</string>
|
|
9035
|
+
<key>ProgramArguments</key><array><string>/bin/sh</string><string>${script}</string></array>
|
|
9036
|
+
${trigger}
|
|
9037
|
+
<key>RunAtLoad</key><false/>
|
|
9038
|
+
</dict></plist>
|
|
9039
|
+
`;
|
|
9040
|
+
mkdirSync7(join10(this.options.home, "Library", "LaunchAgents"), { recursive: true });
|
|
9041
|
+
writeFileSync7(this.plistPath(spec.id), plist);
|
|
9042
|
+
this.run("launchctl", ["load", this.plistPath(spec.id)]);
|
|
9043
|
+
return `launchd:${this.label(spec.id)}`;
|
|
9044
|
+
}
|
|
9045
|
+
scheduleLinux(spec, script, oneOff) {
|
|
9046
|
+
const t = spec.trigger;
|
|
9047
|
+
if (oneOff) {
|
|
9048
|
+
const d = new Date("at" in t ? t.at : Date.now());
|
|
9049
|
+
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")}`;
|
|
9050
|
+
const out = this.run("at", [stamp], `/bin/sh ${script}
|
|
9051
|
+
`);
|
|
9052
|
+
const jobId = /job (\d+)/.exec(out)?.[1] ?? "";
|
|
9053
|
+
return `at:${jobId}`;
|
|
9054
|
+
}
|
|
9055
|
+
const expr = "cron" in t ? t.cron : `*/${Math.max(1, Math.round(t.everyMs / 6e4))} * * * *`;
|
|
9056
|
+
parseCron(expr);
|
|
9057
|
+
const cur = (() => {
|
|
9058
|
+
try {
|
|
9059
|
+
return this.run("crontab", ["-l"]);
|
|
9060
|
+
} catch {
|
|
9061
|
+
return "";
|
|
9062
|
+
}
|
|
9063
|
+
})();
|
|
9064
|
+
this.run("crontab", ["-"], `${cur.trimEnd()}
|
|
9065
|
+
${expr} /bin/sh ${script} # agentx-sched-${spec.id}
|
|
9066
|
+
`.replace(/^\n/, ""));
|
|
9067
|
+
return `crontab:${expr}`;
|
|
9068
|
+
}
|
|
9069
|
+
};
|
|
9070
|
+
var OsSchedulerOptions = class {
|
|
9071
|
+
platform = process.platform;
|
|
9072
|
+
home = homedir7();
|
|
9073
|
+
/** How the fired job invokes the CLI — a RAW shell snippet (may be `bun /path/cli.ts`). Default: `agentx` on PATH. */
|
|
9074
|
+
agentx = "agentx";
|
|
9075
|
+
/** Injectable executor for tests. Returns stdout+stderr merged — `at` reports "job N" on STDERR,
|
|
9076
|
+
* and the job id is what atrm needs for cancel. Throws on non-zero exit. */
|
|
9077
|
+
exec = (cmd, args, input) => {
|
|
9078
|
+
const r = spawnSync4(cmd, args, { input, encoding: "utf8" });
|
|
9079
|
+
if (r.error) throw r.error;
|
|
9080
|
+
if (r.status !== 0) throw new Error(`${cmd} exited ${r.status}: ${r.stderr || r.stdout}`);
|
|
9081
|
+
return `${r.stdout ?? ""}${r.stderr ?? ""}`;
|
|
9082
|
+
};
|
|
9083
|
+
};
|
|
9084
|
+
function routeTrigger(trigger, backendHint, now5 = Date.now()) {
|
|
9085
|
+
if (backendHint === "os") return "os";
|
|
9086
|
+
if (backendHint === "session") return "session";
|
|
9087
|
+
return "at" in trigger && trigger.at - now5 >= 30 * 6e4 ? "os" : "session";
|
|
9088
|
+
}
|
|
9089
|
+
|
|
8611
9090
|
// cli/cli.ts
|
|
8612
9091
|
var forceColor = process.env.FORCE_COLOR != null && process.env.FORCE_COLOR !== "" && process.env.FORCE_COLOR !== "0";
|
|
8613
9092
|
var useColor = forceColor || !process.env.NO_COLOR && !!process.stdout.isTTY && !!process.stderr.isTTY;
|
|
@@ -8623,10 +9102,10 @@ var italic = C("3");
|
|
|
8623
9102
|
var strike = C("9");
|
|
8624
9103
|
var link = (text, url) => useColor ? `\x1B]8;;${url}\x1B\\${cyan(text)}\x1B]8;;\x1B\\` : `${text} (${url})`;
|
|
8625
9104
|
var err = (s) => process.stderr.write(s);
|
|
8626
|
-
var
|
|
9105
|
+
var log20 = forComponent("cli");
|
|
8627
9106
|
var VERSION = (() => {
|
|
8628
9107
|
try {
|
|
8629
|
-
return JSON.parse(
|
|
9108
|
+
return JSON.parse(readFileSync6(new URL("../package.json", import.meta.url), "utf8")).version ?? "?";
|
|
8630
9109
|
} catch {
|
|
8631
9110
|
return "?";
|
|
8632
9111
|
}
|
|
@@ -8639,12 +9118,19 @@ var spinner = /* @__PURE__ */ (() => {
|
|
|
8639
9118
|
return {
|
|
8640
9119
|
/** Anchor for the elapsed counter; runTurn sets it once per turn so tool-call stop/start cycles don't reset it. */
|
|
8641
9120
|
turnStart: 0,
|
|
9121
|
+
/** Mid-turn type-ahead, rendered as the frame's tail — the spinner repaints its row every 90ms,
|
|
9122
|
+
* so the typed text must live INSIDE the frame or it gets erased on the next tick. */
|
|
9123
|
+
tail: void 0,
|
|
9124
|
+
get active() {
|
|
9125
|
+
return !!timer;
|
|
9126
|
+
},
|
|
8642
9127
|
start(label = "thinking\u2026") {
|
|
8643
9128
|
if (!tty || timer) return;
|
|
8644
9129
|
t0 = this.turnStart || Date.now();
|
|
8645
9130
|
timer = setInterval(() => {
|
|
8646
9131
|
const secs = Math.round((Date.now() - t0) / 1e3);
|
|
8647
|
-
|
|
9132
|
+
const tail = this.tail?.();
|
|
9133
|
+
err("\r\x1B[2K" + dim(` ${frames[i = (i + 1) % frames.length]} ${label} ${secs ? `${secs}s \xB7 ` : ""}esc to interrupt`) + (tail ? " " + tail : ""));
|
|
8648
9134
|
}, 90);
|
|
8649
9135
|
},
|
|
8650
9136
|
stop() {
|
|
@@ -8662,7 +9148,9 @@ var setTermTitle = (t) => {
|
|
|
8662
9148
|
var activeTurn = null;
|
|
8663
9149
|
var exitRequested = false;
|
|
8664
9150
|
var inputStash = [];
|
|
8665
|
-
var
|
|
9151
|
+
var stashEd = null;
|
|
9152
|
+
var stashText = () => stashEd?.buf ?? "";
|
|
9153
|
+
var dispatchPending = false;
|
|
8666
9154
|
var latestTodos = [];
|
|
8667
9155
|
function numFlag(raw, flag) {
|
|
8668
9156
|
const n = Number(raw);
|
|
@@ -8818,7 +9306,7 @@ REPL shortcuts: !<cmd> runs a shell command inline \xB7 #<note> saves a memory \
|
|
|
8818
9306
|
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)
|
|
8819
9307
|
REPL completion: type / (commands+skills) or @ (files) for a LIVE menu \u2014 \u2191/\u2193 select, \u23CE/Tab accept, Esc dismiss.
|
|
8820
9308
|
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.
|
|
8821
|
-
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.
|
|
9309
|
+
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.
|
|
8822
9310
|
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.
|
|
8823
9311
|
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.
|
|
8824
9312
|
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).`;
|
|
@@ -8838,11 +9326,11 @@ function resolveModelOrNewest(model) {
|
|
|
8838
9326
|
var ENV_KEY_ALIASES = { google: ["GEMINI_API_KEY"] };
|
|
8839
9327
|
function loadInstallEnv() {
|
|
8840
9328
|
let dir = dirname4(import.meta.path);
|
|
8841
|
-
for (let i = 0; i < 5 && !
|
|
9329
|
+
for (let i = 0; i < 5 && !existsSync9(join11(dir, "package.json")); i++) dir = dirname4(dir);
|
|
8842
9330
|
for (const name of [".env", ".env.local"]) {
|
|
8843
|
-
const file =
|
|
8844
|
-
if (!
|
|
8845
|
-
for (const line of
|
|
9331
|
+
const file = join11(dir, name);
|
|
9332
|
+
if (!existsSync9(file)) continue;
|
|
9333
|
+
for (const line of readFileSync6(file, "utf8").split("\n")) {
|
|
8846
9334
|
const m = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
|
|
8847
9335
|
if (!m || m[1] in process.env) continue;
|
|
8848
9336
|
let val = m[2].trim();
|
|
@@ -8919,7 +9407,7 @@ function makeHost(format = "text", opts) {
|
|
|
8919
9407
|
async confirm(prompt) {
|
|
8920
9408
|
const v = await selectMenu(process.stderr, { title: `? ${prompt}`, items: [{ label: "Yes", value: "y" }, { label: "No", value: "n" }], current: "n" });
|
|
8921
9409
|
if (v !== null) return v === "y";
|
|
8922
|
-
const io = createInterface({ input:
|
|
9410
|
+
const io = createInterface({ input: keyInput, output: process.stderr });
|
|
8923
9411
|
try {
|
|
8924
9412
|
return /^y(es)?$/i.test((await io.question(yellow(` ? ${prompt} [y/N] `))).trim());
|
|
8925
9413
|
} finally {
|
|
@@ -8930,7 +9418,7 @@ function makeHost(format = "text", opts) {
|
|
|
8930
9418
|
const title = `? ${q2.header ? "[" + q2.header + "] " : ""}${q2.question}`;
|
|
8931
9419
|
const v = await selectMenu(process.stderr, { title, items: q2.options.map((o) => ({ label: o.label, value: o.label, desc: o.description })) });
|
|
8932
9420
|
if (v !== null) return v;
|
|
8933
|
-
const io = createInterface({ input:
|
|
9421
|
+
const io = createInterface({ input: keyInput, output: process.stderr });
|
|
8934
9422
|
try {
|
|
8935
9423
|
const lines = [yellow(" " + title)];
|
|
8936
9424
|
q2.options.forEach((o, i) => lines.push(` ${i + 1}) ${o.label}${o.description ? dim(" \u2014 " + o.description) : ""}`));
|
|
@@ -9150,7 +9638,7 @@ function costOf(pricing, promptTokens = 0, completionTokens = 0, cacheCreationTo
|
|
|
9150
9638
|
function turnCost(model, usage) {
|
|
9151
9639
|
return costOf(getModelInfo(model)?.pricing, usage?.promptTokens ?? 0, usage?.completionTokens ?? 0, usage?.cacheCreationTokens ?? 0, usage?.cacheReadTokens ?? 0, model);
|
|
9152
9640
|
}
|
|
9153
|
-
async function evaluateGoal(ai, condition, transcript,
|
|
9641
|
+
async function evaluateGoal(ai, condition, transcript, log21) {
|
|
9154
9642
|
const recent = transcript.filter((m) => m.role === "assistant").slice(-8).map((m) => {
|
|
9155
9643
|
const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join(" ");
|
|
9156
9644
|
return text.slice(0, 600);
|
|
@@ -9170,7 +9658,7 @@ ${recent}` }
|
|
|
9170
9658
|
const match = r.content.match(/\{[\s\S]*\}/);
|
|
9171
9659
|
if (match) return JSON.parse(match[0]);
|
|
9172
9660
|
} catch (e) {
|
|
9173
|
-
|
|
9661
|
+
log21(dim(` (goal evaluator error: ${e?.message ?? e})
|
|
9174
9662
|
`));
|
|
9175
9663
|
}
|
|
9176
9664
|
return { met: false, reason: "evaluation unclear" };
|
|
@@ -9247,7 +9735,7 @@ function makeAskResolver(cwd) {
|
|
|
9247
9735
|
current: "allow"
|
|
9248
9736
|
});
|
|
9249
9737
|
if (v === null) {
|
|
9250
|
-
const io = createInterface({ input:
|
|
9738
|
+
const io = createInterface({ input: keyInput, output: process.stderr });
|
|
9251
9739
|
try {
|
|
9252
9740
|
return { decision: /^y(es)?$/i.test((await io.question(yellow(` ? Allow ${call.name}${tgt}? [y/N] `))).trim()) ? "allow" : "deny" };
|
|
9253
9741
|
} finally {
|
|
@@ -9357,19 +9845,22 @@ async function mountMcp(cfg, oauth) {
|
|
|
9357
9845
|
return mounted;
|
|
9358
9846
|
}
|
|
9359
9847
|
async function closeMcp(mounted) {
|
|
9360
|
-
await Promise.all(mounted.map((m) => m.client.close().catch((e) =>
|
|
9848
|
+
await Promise.all(mounted.map((m) => m.client.close().catch((e) => log20.debug("mcp close failed", e))));
|
|
9361
9849
|
}
|
|
9362
9850
|
var IMG_EXT = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
|
|
9363
|
-
|
|
9851
|
+
function mentionRefs(line) {
|
|
9852
|
+
return [...line.matchAll(/(?:^|\s)@(?:"([^"]+)"|(\S+))/g)].map((m) => m[1] ?? m[2].replace(/[?!.,;:)\]}'">]+$/, "")).filter(Boolean);
|
|
9853
|
+
}
|
|
9854
|
+
var untilde = (p) => p.startsWith("~/") ? join11(homedir8(), p.slice(2)) : p;
|
|
9364
9855
|
function readImageParts(cwd, line) {
|
|
9365
|
-
const refs =
|
|
9856
|
+
const refs = mentionRefs(line);
|
|
9366
9857
|
const parts = [];
|
|
9367
9858
|
for (const ref of refs) {
|
|
9368
9859
|
const mime = IMG_EXT[extname(ref).toLowerCase()];
|
|
9369
9860
|
if (!mime) continue;
|
|
9370
9861
|
const abs = ref.startsWith("~/") ? untilde(ref) : resolve3(cwd, ref);
|
|
9371
9862
|
try {
|
|
9372
|
-
parts.push(imagePart(`data:${mime};base64,${
|
|
9863
|
+
parts.push(imagePart(`data:${mime};base64,${readFileSync6(abs).toString("base64")}`));
|
|
9373
9864
|
} catch {
|
|
9374
9865
|
}
|
|
9375
9866
|
}
|
|
@@ -9380,7 +9871,6 @@ function pastePathClassifier(cwd) {
|
|
|
9380
9871
|
let t = text.trim();
|
|
9381
9872
|
if (!t || t.includes("\n")) return null;
|
|
9382
9873
|
t = t.replace(/\\ /g, " ").replace(/^['"]|['"]$/g, "");
|
|
9383
|
-
if (/\s/.test(t)) return null;
|
|
9384
9874
|
if (!/^(\/|~\/|\.\/|\.\.\/)/.test(t)) return null;
|
|
9385
9875
|
const abs = t.startsWith("~/") ? untilde(t) : resolve3(cwd, t);
|
|
9386
9876
|
try {
|
|
@@ -9389,7 +9879,7 @@ function pastePathClassifier(cwd) {
|
|
|
9389
9879
|
return null;
|
|
9390
9880
|
}
|
|
9391
9881
|
const isImg = !!IMG_EXT[extname(abs).toLowerCase()];
|
|
9392
|
-
return { display: isImg ? "Image" : `File ${basename2(abs)}`, ref: "@" + abs };
|
|
9882
|
+
return { display: isImg ? "Image" : `File ${basename2(abs)}`, ref: /\s/.test(abs) ? `@"${abs}"` : "@" + abs };
|
|
9393
9883
|
};
|
|
9394
9884
|
}
|
|
9395
9885
|
var mcpMentionResolver;
|
|
@@ -9397,7 +9887,7 @@ function setMcpMentionResolver(fn) {
|
|
|
9397
9887
|
mcpMentionResolver = fn;
|
|
9398
9888
|
}
|
|
9399
9889
|
async function expandMentions(fs, line) {
|
|
9400
|
-
const refs =
|
|
9890
|
+
const refs = mentionRefs(line);
|
|
9401
9891
|
if (!refs.length) return { text: line, loaded: [], missing: [] };
|
|
9402
9892
|
const loaded = [], missing = [], blocks = [];
|
|
9403
9893
|
for (const ref of refs) {
|
|
@@ -9405,7 +9895,7 @@ async function expandMentions(fs, line) {
|
|
|
9405
9895
|
if (loaded.includes(ref) || missing.includes(ref)) continue;
|
|
9406
9896
|
if (ref.includes(":") && mcpMentionResolver) {
|
|
9407
9897
|
const body = await mcpMentionResolver(ref).catch((e) => {
|
|
9408
|
-
|
|
9898
|
+
log20.debug("mcp mention resolve failed", e);
|
|
9409
9899
|
return null;
|
|
9410
9900
|
});
|
|
9411
9901
|
if (body != null) {
|
|
@@ -9583,25 +10073,25 @@ var AGENTS_MD_TEMPLATE = `# ${"${name}"}
|
|
|
9583
10073
|
`;
|
|
9584
10074
|
function initInstructions(cwd) {
|
|
9585
10075
|
for (const f of ["AGENTS.md", "CLAUDE.md"]) {
|
|
9586
|
-
if (
|
|
10076
|
+
if (existsSync9(join11(cwd, f))) {
|
|
9587
10077
|
err(yellow(` ${f} already exists \u2014 leaving it as-is
|
|
9588
10078
|
`));
|
|
9589
10079
|
return;
|
|
9590
10080
|
}
|
|
9591
10081
|
}
|
|
9592
|
-
const path =
|
|
9593
|
-
|
|
10082
|
+
const path = join11(cwd, "AGENTS.md");
|
|
10083
|
+
writeFileSync8(path, AGENTS_MD_TEMPLATE.replace("${name}", basename2(cwd)));
|
|
9594
10084
|
err(green(` created ${path}
|
|
9595
10085
|
`) + dim(" edit it, then it auto-loads into every run.\n"));
|
|
9596
10086
|
}
|
|
9597
10087
|
function persistSetting(cwd, key, value) {
|
|
9598
|
-
const path =
|
|
10088
|
+
const path = join11(cwd, ".agent", "settings.json");
|
|
9599
10089
|
try {
|
|
9600
|
-
const obj =
|
|
10090
|
+
const obj = existsSync9(path) ? JSON.parse(readFileSync6(path, "utf8")) : {};
|
|
9601
10091
|
if (obj[key] === value) return;
|
|
9602
10092
|
obj[key] = value;
|
|
9603
|
-
|
|
9604
|
-
|
|
10093
|
+
mkdirSync8(dirname4(path), { recursive: true });
|
|
10094
|
+
writeFileSync8(path, JSON.stringify(obj, null, 2) + "\n");
|
|
9605
10095
|
} catch (e) {
|
|
9606
10096
|
err(yellow(` \u26A0 couldn't persist ${key} to ${path} \u2014 ${e?.message ?? e}
|
|
9607
10097
|
`));
|
|
@@ -9617,14 +10107,14 @@ var isCancelTeardown = (e) => {
|
|
|
9617
10107
|
function installCancelGuards(mounted) {
|
|
9618
10108
|
process.on("unhandledRejection", (e) => {
|
|
9619
10109
|
if (isCancelTeardown(e)) {
|
|
9620
|
-
|
|
10110
|
+
log20.debug("suppressed unhandledRejection (cursor stream cancel)", e);
|
|
9621
10111
|
return;
|
|
9622
10112
|
}
|
|
9623
|
-
|
|
10113
|
+
log20.error("unhandledRejection", e);
|
|
9624
10114
|
});
|
|
9625
10115
|
process.on("uncaughtException", (e) => {
|
|
9626
10116
|
if (isCancelTeardown(e)) {
|
|
9627
|
-
|
|
10117
|
+
log20.debug("suppressed uncaughtException (cursor stream cancel)", e);
|
|
9628
10118
|
return;
|
|
9629
10119
|
}
|
|
9630
10120
|
console.error(e);
|
|
@@ -9633,7 +10123,7 @@ function installCancelGuards(mounted) {
|
|
|
9633
10123
|
});
|
|
9634
10124
|
}
|
|
9635
10125
|
async function repl(args, ai, cfg, cwd) {
|
|
9636
|
-
const oauth = new McpOAuth({ storePath:
|
|
10126
|
+
const oauth = new McpOAuth({ storePath: join11(cwd, ".agent", "mcp-auth.json") });
|
|
9637
10127
|
const mounted = await mountMcp(cfg, oauth);
|
|
9638
10128
|
const agent = await makeAgent(args, ai, cfg, mounted.flatMap((m) => m.tools));
|
|
9639
10129
|
if (args.voice && !args.duplex) agent.options.tools = [...agent.options.tools ?? [], exitSessionTool(() => {
|
|
@@ -9653,7 +10143,19 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
9653
10143
|
},
|
|
9654
10144
|
tickMs: 15e3
|
|
9655
10145
|
});
|
|
9656
|
-
|
|
10146
|
+
const agentxInvocation = () => process.argv[1] ? `${process.execPath} ${resolve3(process.argv[1])}` : "agentx";
|
|
10147
|
+
const osSched = new OsScheduler({ agentx: agentxInvocation() });
|
|
10148
|
+
const osBackend = osSched.available() ? {
|
|
10149
|
+
get sessionId() {
|
|
10150
|
+
return session.meta.id;
|
|
10151
|
+
},
|
|
10152
|
+
cwd,
|
|
10153
|
+
route: (t, hint) => routeTrigger(t, hint),
|
|
10154
|
+
schedule: (spec) => osSched.schedule(spec),
|
|
10155
|
+
cancel: (id) => osSched.cancel(id),
|
|
10156
|
+
list: () => osSched.list()
|
|
10157
|
+
} : void 0;
|
|
10158
|
+
agent.options.tools = [...agent.options.tools ?? [], ...makeScheduleTools(scheduler, osBackend)];
|
|
9657
10159
|
const duplex = args.duplex;
|
|
9658
10160
|
let dx;
|
|
9659
10161
|
let voiceIO;
|
|
@@ -9732,7 +10234,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
9732
10234
|
editorRef?.suspend();
|
|
9733
10235
|
voiceEcho(e.message);
|
|
9734
10236
|
return;
|
|
9735
|
-
} else if (e.kind === "text_delta" &&
|
|
10237
|
+
} else if (e.kind === "text_delta" && stashText()) {
|
|
9736
10238
|
process.stdout.write("\r\x1B[K");
|
|
9737
10239
|
base.notify(e);
|
|
9738
10240
|
repaintStash();
|
|
@@ -9815,7 +10317,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
9815
10317
|
quickLook: {
|
|
9816
10318
|
branch: () => {
|
|
9817
10319
|
try {
|
|
9818
|
-
const head =
|
|
10320
|
+
const head = readFileSync6(join11(cwd, ".git", "HEAD"), "utf8").trim();
|
|
9819
10321
|
return head.startsWith("ref: refs/heads/") ? `branch: ${head.slice("ref: refs/heads/".length)}` : `detached HEAD at ${head.slice(0, 12)}`;
|
|
9820
10322
|
} catch {
|
|
9821
10323
|
return "not a git repository";
|
|
@@ -9937,9 +10439,9 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
9937
10439
|
};
|
|
9938
10440
|
const pendingImages = [];
|
|
9939
10441
|
const grabClipboardAttachment = () => {
|
|
9940
|
-
const dir =
|
|
10442
|
+
const dir = join11(tmpdir3(), "agentx-pasted");
|
|
9941
10443
|
try {
|
|
9942
|
-
|
|
10444
|
+
mkdirSync8(dir, { recursive: true });
|
|
9943
10445
|
} catch {
|
|
9944
10446
|
}
|
|
9945
10447
|
const img = grabClipboardImage(dir, String(Date.now()));
|
|
@@ -9989,7 +10491,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
9989
10491
|
err(dim(` \u23F0 ${scheduler.size} scheduled job(s) re-armed
|
|
9990
10492
|
`));
|
|
9991
10493
|
}
|
|
9992
|
-
const checkpoints = args.vfs || args.boddb ? new CheckpointStack(agent.options.fs) : new GitCheckpoints({ workTree: cwd, gitDir:
|
|
10494
|
+
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 });
|
|
9993
10495
|
const cpHooks = checkpoints.hooks?.();
|
|
9994
10496
|
if (cpHooks) work.hooks = composeHooks(work.hooks, cpHooks);
|
|
9995
10497
|
duplexPersist = () => {
|
|
@@ -10016,7 +10518,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
10016
10518
|
const fs = agent.options.fs;
|
|
10017
10519
|
const fsBase = fs.getCwd() === "/" ? "" : fs.getCwd();
|
|
10018
10520
|
const adot = (sub) => `${fsBase}/.agent/${sub}`;
|
|
10019
|
-
const adots = (sub) =>
|
|
10521
|
+
const adots = (sub) => dotDirs(fsBase, sub);
|
|
10020
10522
|
const cmds = (await loadCommands(fs, adots("commands"))).commands;
|
|
10021
10523
|
const skills = (await loadSkills(fs, adots("skills"))).skills;
|
|
10022
10524
|
const refreshCatalogs = async () => {
|
|
@@ -10038,14 +10540,14 @@ ${lines.join("\n")}
|
|
|
10038
10540
|
Added entries are loadable now via the Skill/SlashCommand tools; removed ones are gone even if still listed in the system prompt.
|
|
10039
10541
|
</system-reminder>`;
|
|
10040
10542
|
};
|
|
10041
|
-
const histPath =
|
|
10042
|
-
const history =
|
|
10543
|
+
const histPath = join11(cwd, ".agent", "history");
|
|
10544
|
+
const history = existsSync9(histPath) ? readFileSync6(histPath, "utf8").split("\n").filter(Boolean).reverse().slice(0, 500) : [];
|
|
10043
10545
|
const remember = (line) => {
|
|
10044
10546
|
try {
|
|
10045
|
-
|
|
10547
|
+
mkdirSync8(join11(cwd, ".agent"), { recursive: true });
|
|
10046
10548
|
appendFileSync(histPath, line + "\n");
|
|
10047
10549
|
} catch (e) {
|
|
10048
|
-
|
|
10550
|
+
log20.debug("history write failed", e);
|
|
10049
10551
|
}
|
|
10050
10552
|
};
|
|
10051
10553
|
const ago = (t) => {
|
|
@@ -10115,7 +10617,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
|
|
|
10115
10617
|
try {
|
|
10116
10618
|
store.save(session);
|
|
10117
10619
|
} catch (e) {
|
|
10118
|
-
|
|
10620
|
+
log20.debug("session save after rewind failed", e);
|
|
10119
10621
|
}
|
|
10120
10622
|
err(green(" \u27F2 jumped back") + dim(` \u2014 ${face.transcript.length} message(s) kept; edit + resend
|
|
10121
10623
|
`));
|
|
@@ -10143,7 +10645,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
|
|
|
10143
10645
|
const announcedTasks = /* @__PURE__ */ new Set();
|
|
10144
10646
|
const turn = async (task) => {
|
|
10145
10647
|
const delta = await refreshCatalogs().catch((e) => {
|
|
10146
|
-
|
|
10648
|
+
log20.debug("catalog refresh failed", e);
|
|
10147
10649
|
return "";
|
|
10148
10650
|
});
|
|
10149
10651
|
if (delta) {
|
|
@@ -10338,8 +10840,8 @@ ${extra}` : body);
|
|
|
10338
10840
|
const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
|
|
10339
10841
|
if (wasRaw) process.stdin.setRawMode(false);
|
|
10340
10842
|
try {
|
|
10341
|
-
const { spawnSync:
|
|
10342
|
-
const r =
|
|
10843
|
+
const { spawnSync: spawnSync5 } = await import("child_process");
|
|
10844
|
+
const r = spawnSync5("less", ["-R"], { input: text, stdio: ["pipe", "inherit", "inherit"] });
|
|
10343
10845
|
if (r.error) err(text);
|
|
10344
10846
|
} finally {
|
|
10345
10847
|
if (wasRaw) process.stdin.setRawMode(true);
|
|
@@ -10359,13 +10861,13 @@ ${extra}` : body);
|
|
|
10359
10861
|
keys.length ? ok(`provider keys: ${keys.join(", ")}`) : bad("no provider keys set (ANTHROPIC_API_KEY / OPENAI_API_KEY / GOOGLE_API_KEY / GROQ_API_KEY)");
|
|
10360
10862
|
const info = getModelInfo(work.model);
|
|
10361
10863
|
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)`);
|
|
10362
|
-
const cfgFiles = ["ts", "js", "json"].flatMap((e) => [`${cwd}/.agent/config.${e}`, `${
|
|
10864
|
+
const cfgFiles = ["ts", "js", "json"].flatMap((e) => [`${cwd}/.agent/config.${e}`, `${homedir8()}/.agent/config.${e}`]).filter((p) => existsSync9(p));
|
|
10363
10865
|
cfgFiles.length ? ok(`config: ${cfgFiles.join(", ")}`) : warn("no .agent/config.* found (project or ~) \u2014 running on defaults");
|
|
10364
10866
|
try {
|
|
10365
10867
|
const probe = `${cwd}/.agent/sessions/.doctor-probe`;
|
|
10366
|
-
|
|
10367
|
-
|
|
10368
|
-
|
|
10868
|
+
mkdirSync8(`${cwd}/.agent/sessions`, { recursive: true });
|
|
10869
|
+
writeFileSync8(probe, "ok");
|
|
10870
|
+
unlinkSync4(probe);
|
|
10369
10871
|
ok(`session store writable (${cwd}/.agent/sessions)`);
|
|
10370
10872
|
} catch (e) {
|
|
10371
10873
|
bad(`session store not writable: ${e?.message ?? e}`);
|
|
@@ -10392,7 +10894,7 @@ ${extra}` : body);
|
|
|
10392
10894
|
desc: "rescan skills/commands dirs and rebuild the system prompt (one cache miss) \u2014 picks up entries created mid-session",
|
|
10393
10895
|
run: async () => {
|
|
10394
10896
|
await refreshCatalogs().catch((e) => {
|
|
10395
|
-
|
|
10897
|
+
log20.debug("catalog refresh failed", e);
|
|
10396
10898
|
});
|
|
10397
10899
|
face.reprepare();
|
|
10398
10900
|
err(green(` \u2713 reloaded \u2014 ${skills.length} skill(s), ${cmds.length} command(s); system prompt rebuilds on next message
|
|
@@ -10658,6 +11160,47 @@ ${extra}` : body);
|
|
|
10658
11160
|
`));
|
|
10659
11161
|
}
|
|
10660
11162
|
},
|
|
11163
|
+
copy: {
|
|
11164
|
+
desc: "copy the last reply to the clipboard \u2014 /copy code = last code block only",
|
|
11165
|
+
run: (a) => {
|
|
11166
|
+
const last = [...face.transcript].reverse().find((m) => m.role === "assistant" && contentText(m.content).trim());
|
|
11167
|
+
if (!last) {
|
|
11168
|
+
err(dim(" (nothing to copy yet)\n"));
|
|
11169
|
+
return;
|
|
11170
|
+
}
|
|
11171
|
+
let text = contentText(last.content).trim();
|
|
11172
|
+
if (a[0] === "code") {
|
|
11173
|
+
const fences = [...text.matchAll(/```[^\n]*\n([\s\S]*?)```/g)];
|
|
11174
|
+
if (!fences.length) {
|
|
11175
|
+
err(dim(" (no code block in the last reply)\n"));
|
|
11176
|
+
return;
|
|
11177
|
+
}
|
|
11178
|
+
text = fences[fences.length - 1][1].trimEnd();
|
|
11179
|
+
}
|
|
11180
|
+
err(dim(copyTextToClipboard(text) ? ` \u2713 copied ${text.length} chars
|
|
11181
|
+
` : " no clipboard tool found (pbcopy/wl-copy/xclip)\n"));
|
|
11182
|
+
}
|
|
11183
|
+
},
|
|
11184
|
+
diff: {
|
|
11185
|
+
desc: "show all file changes this session (oldest checkpoint \u2192 now)",
|
|
11186
|
+
run: async () => {
|
|
11187
|
+
if (!checkpoints.diff) {
|
|
11188
|
+
err(dim(" (diff not supported by this checkpoint backend)\n"));
|
|
11189
|
+
return;
|
|
11190
|
+
}
|
|
11191
|
+
await checkpoints.refresh?.();
|
|
11192
|
+
if (!checkpoints.size) {
|
|
11193
|
+
err(dim(" (no checkpoints yet \u2014 make a turn first)\n"));
|
|
11194
|
+
return;
|
|
11195
|
+
}
|
|
11196
|
+
const d = (await checkpoints.diff()).trim();
|
|
11197
|
+
if (!d) {
|
|
11198
|
+
err(dim(" (no file changes this session)\n"));
|
|
11199
|
+
return;
|
|
11200
|
+
}
|
|
11201
|
+
err(d.split("\n").map((l) => l.startsWith("+") && !l.startsWith("+++") ? green(l) : l.startsWith("-") && !l.startsWith("---") ? red(l) : dim(l)).join("\n") + "\n");
|
|
11202
|
+
}
|
|
11203
|
+
},
|
|
10661
11204
|
memory: {
|
|
10662
11205
|
desc: "open the memory index in $EDITOR (.agent/memory/MEMORY.md)",
|
|
10663
11206
|
run: async () => {
|
|
@@ -10688,8 +11231,8 @@ ${extra}` : body);
|
|
|
10688
11231
|
const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
|
|
10689
11232
|
if (wasRaw) process.stdin.setRawMode(false);
|
|
10690
11233
|
try {
|
|
10691
|
-
const { spawnSync:
|
|
10692
|
-
|
|
11234
|
+
const { spawnSync: spawnSync5 } = await import("child_process");
|
|
11235
|
+
spawnSync5(ed, [idx], { stdio: "inherit" });
|
|
10693
11236
|
} finally {
|
|
10694
11237
|
if (wasRaw) process.stdin.setRawMode(true);
|
|
10695
11238
|
}
|
|
@@ -10764,7 +11307,7 @@ ${extra}` : body);
|
|
|
10764
11307
|
if (a[0] === "new") {
|
|
10765
11308
|
let name = a[1];
|
|
10766
11309
|
if (!name) {
|
|
10767
|
-
const io = createInterface({ input:
|
|
11310
|
+
const io = createInterface({ input: keyInput, output: process.stderr });
|
|
10768
11311
|
try {
|
|
10769
11312
|
name = (await io.question(yellow(" agent name: "))).trim();
|
|
10770
11313
|
} finally {
|
|
@@ -10806,7 +11349,7 @@ ${extra}` : body);
|
|
|
10806
11349
|
try {
|
|
10807
11350
|
for (const def of (await loadAgents(fs2, d)).agents) if (!seen.has(def.name)) seen.set(def.name, { def, from: d });
|
|
10808
11351
|
} catch (e) {
|
|
10809
|
-
|
|
11352
|
+
log20.debug(`loadAgents(${d}) failed`, e);
|
|
10810
11353
|
}
|
|
10811
11354
|
}
|
|
10812
11355
|
if (!seen.size) {
|
|
@@ -10898,7 +11441,7 @@ ${extra}` : body);
|
|
|
10898
11441
|
if (idx >= 0) {
|
|
10899
11442
|
const old = mounted.splice(idx, 1)[0];
|
|
10900
11443
|
removeWorkTools(old.tools.map((t) => t.name));
|
|
10901
|
-
await old.client.close().catch((e) =>
|
|
11444
|
+
await old.client.close().catch((e) => log20.debug("mcp close failed", e));
|
|
10902
11445
|
}
|
|
10903
11446
|
try {
|
|
10904
11447
|
const m = await mountMcpServer(name, conf);
|
|
@@ -10926,7 +11469,7 @@ ${extra}` : body);
|
|
|
10926
11469
|
}
|
|
10927
11470
|
const m = mounted.splice(idx, 1)[0];
|
|
10928
11471
|
removeWorkTools(m.tools.map((t) => t.name));
|
|
10929
|
-
await m.client.close().catch((e) =>
|
|
11472
|
+
await m.client.close().catch((e) => log20.debug("mcp close failed", e));
|
|
10930
11473
|
err(dim(` removed "${name}"
|
|
10931
11474
|
`));
|
|
10932
11475
|
return;
|
|
@@ -11004,7 +11547,7 @@ ${extra}` : body);
|
|
|
11004
11547
|
}
|
|
11005
11548
|
a = [picked];
|
|
11006
11549
|
if (picked === "add") {
|
|
11007
|
-
const io = createInterface({ input:
|
|
11550
|
+
const io = createInterface({ input: keyInput, output: process.stderr });
|
|
11008
11551
|
try {
|
|
11009
11552
|
const name = (await io.question(yellow(" name: "))).trim();
|
|
11010
11553
|
const target = (await io.question(yellow(" command or url: "))).trim();
|
|
@@ -11058,11 +11601,11 @@ ${extra}` : body);
|
|
|
11058
11601
|
return;
|
|
11059
11602
|
}
|
|
11060
11603
|
const md = exportMarkdown(session.meta, shown);
|
|
11061
|
-
const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" :
|
|
11604
|
+
const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" : join11(".agent", "exports", `${session.meta.id}.md`);
|
|
11062
11605
|
const path = resolve3(cwd, name);
|
|
11063
11606
|
try {
|
|
11064
|
-
|
|
11065
|
-
|
|
11607
|
+
mkdirSync8(dirname4(path), { recursive: true });
|
|
11608
|
+
writeFileSync8(path, md);
|
|
11066
11609
|
err(green(` \u2713 exported \u2192 ${path}
|
|
11067
11610
|
`) + dim(` ${shown.length} message(s) \xB7 ${md.length} chars
|
|
11068
11611
|
`));
|
|
@@ -11125,9 +11668,9 @@ ${extra}` : body);
|
|
|
11125
11668
|
`));
|
|
11126
11669
|
const listDir = (absDir) => {
|
|
11127
11670
|
try {
|
|
11128
|
-
return
|
|
11671
|
+
return readdirSync3(join11(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
|
|
11129
11672
|
} catch (e) {
|
|
11130
|
-
|
|
11673
|
+
log20.debug("completion readdir failed", absDir, e);
|
|
11131
11674
|
return null;
|
|
11132
11675
|
}
|
|
11133
11676
|
};
|
|
@@ -11141,30 +11684,36 @@ ${extra}` : body);
|
|
|
11141
11684
|
editorRef = editor;
|
|
11142
11685
|
let aborting = false;
|
|
11143
11686
|
let pendingRewind = false;
|
|
11687
|
+
const classifyPaste = pastePathClassifier(cwd);
|
|
11144
11688
|
if (process.stdin.isTTY) {
|
|
11145
|
-
const
|
|
11146
|
-
|
|
11689
|
+
const sEd = new EditorState(() => ({ hits: [], token: "" }), [], classifyPaste, grabClipboardAttachment);
|
|
11690
|
+
stashEd = sEd;
|
|
11691
|
+
const inverse = (x) => `\x1B[7m${x}\x1B[0m`;
|
|
11692
|
+
const stashView = () => {
|
|
11693
|
+
const v = sEd.buf.replace(/\n/g, "\u23CE");
|
|
11694
|
+
const c = Math.min(sEd.cursor, v.length);
|
|
11695
|
+
const at = c < v.length ? String.fromCodePoint(v.codePointAt(c)) : " ";
|
|
11147
11696
|
const q2 = inputStash.length ? dim(` [${inputStash.length} queued]`) : "";
|
|
11148
|
-
|
|
11697
|
+
return `${dim("\u203A ")}${v.slice(0, c)}${inverse(at)}${v.slice(c + at.length)}${q2}`;
|
|
11698
|
+
};
|
|
11699
|
+
spinner.tail = () => sEd.buf ? stashView() : "";
|
|
11700
|
+
const renderStashBuf = () => {
|
|
11701
|
+
if (spinner.active) return;
|
|
11702
|
+
err(`\r\x1B[K${sEd.buf ? " " + stashView() : ""}`);
|
|
11149
11703
|
};
|
|
11150
11704
|
repaintStash = renderStashBuf;
|
|
11151
|
-
|
|
11152
|
-
if (
|
|
11705
|
+
keyInput.on("keypress", (_s, key) => {
|
|
11706
|
+
if (isMenuActive()) return;
|
|
11707
|
+
if (!activeTurn && !dispatchPending) return;
|
|
11153
11708
|
if (key?.ctrl && key?.name === "o") {
|
|
11154
11709
|
toggleVerbose();
|
|
11155
11710
|
return;
|
|
11156
11711
|
}
|
|
11157
11712
|
const k = key?.name;
|
|
11158
|
-
|
|
11159
|
-
if (cancel) {
|
|
11160
|
-
if (stashBuf) {
|
|
11161
|
-
stashBuf = "";
|
|
11162
|
-
err("\r\x1B[K");
|
|
11163
|
-
return;
|
|
11164
|
-
}
|
|
11713
|
+
if (!sEd.pasting && (k === "escape" || key?.ctrl && k === "c")) {
|
|
11165
11714
|
if (!aborting) {
|
|
11166
11715
|
aborting = true;
|
|
11167
|
-
activeTurn
|
|
11716
|
+
activeTurn?.abort();
|
|
11168
11717
|
voiceIO?.interrupt();
|
|
11169
11718
|
err(yellow("\n \u238B cancelling\u2026") + dim(" (Ctrl-C again to force-quit)\n"));
|
|
11170
11719
|
setTimeout(() => {
|
|
@@ -11179,35 +11728,29 @@ ${extra}` : body);
|
|
|
11179
11728
|
}
|
|
11180
11729
|
return;
|
|
11181
11730
|
}
|
|
11182
|
-
|
|
11183
|
-
|
|
11184
|
-
|
|
11185
|
-
|
|
11731
|
+
const action = applyKey(sEd, key ?? {}, _s);
|
|
11732
|
+
if (action === "submit") {
|
|
11733
|
+
const text = sEd.expand().trim();
|
|
11734
|
+
sEd.reset();
|
|
11735
|
+
if (text) {
|
|
11736
|
+
inputStash.push(text);
|
|
11737
|
+
const view = text.replace(/\n+/g, " \u23CE ");
|
|
11738
|
+
err(`\r\x1B[K${green(" \u2713 stashed")} ${dim(`#${inputStash.length}: ${view.slice(0, 50)}${view.length > 50 ? "\u2026" : ""}`)}
|
|
11186
11739
|
`);
|
|
11187
11740
|
}
|
|
11188
|
-
stashBuf = "";
|
|
11189
|
-
return;
|
|
11190
|
-
}
|
|
11191
|
-
if (k === "backspace") {
|
|
11192
|
-
if (stashBuf.length) {
|
|
11193
|
-
stashBuf = stashBuf.slice(0, -1);
|
|
11194
|
-
if (stashBuf) renderStashBuf();
|
|
11195
|
-
else err("\r\x1B[K");
|
|
11196
|
-
}
|
|
11197
|
-
return;
|
|
11198
|
-
}
|
|
11199
|
-
if (!key?.ctrl && !key?.meta && isPrintable(_s)) {
|
|
11200
|
-
stashBuf += _s;
|
|
11201
|
-
renderStashBuf();
|
|
11202
11741
|
return;
|
|
11203
11742
|
}
|
|
11743
|
+
if (sEd.pasting) return;
|
|
11744
|
+
if (action === "eof" || action === "rewind") return;
|
|
11745
|
+
renderStashBuf();
|
|
11204
11746
|
});
|
|
11205
11747
|
}
|
|
11206
11748
|
const promptStr = bold(cyan("agentx \u203A "));
|
|
11207
11749
|
const contPrompt = dim(" \u2026 \u203A ");
|
|
11208
|
-
const classifyPaste = pastePathClassifier(cwd);
|
|
11209
11750
|
const releaseStdin = () => {
|
|
11751
|
+
releaseKeyInput();
|
|
11210
11752
|
if (process.stdin.isTTY) {
|
|
11753
|
+
err("\x1B[?2004l");
|
|
11211
11754
|
try {
|
|
11212
11755
|
process.stdin.setRawMode(false);
|
|
11213
11756
|
} catch {
|
|
@@ -11218,6 +11761,7 @@ ${extra}` : body);
|
|
|
11218
11761
|
let prefill;
|
|
11219
11762
|
let tick = 0;
|
|
11220
11763
|
const dispatchLine = async (line) => {
|
|
11764
|
+
if (/^[!#/]/.test(line)) dispatchPending = false;
|
|
11221
11765
|
history.unshift(line.replace(/\n+/g, " \u23CE "));
|
|
11222
11766
|
remember(line.replace(/\n+/g, " \u23CE "));
|
|
11223
11767
|
if (line.startsWith("!")) {
|
|
@@ -11372,8 +11916,8 @@ ${extra}` : body);
|
|
|
11372
11916
|
if (t !== void 0) prefill = t;
|
|
11373
11917
|
}
|
|
11374
11918
|
aborting = false;
|
|
11375
|
-
const carry =
|
|
11376
|
-
|
|
11919
|
+
const carry = stashText() ? stashEd.expand() : "";
|
|
11920
|
+
stashEd?.reset();
|
|
11377
11921
|
err("\n");
|
|
11378
11922
|
const initial = prefill ?? (carry || void 0);
|
|
11379
11923
|
prefill = void 0;
|
|
@@ -11428,6 +11972,10 @@ ${extra}` : body);
|
|
|
11428
11972
|
history,
|
|
11429
11973
|
classifyPaste,
|
|
11430
11974
|
onEmptyPaste: grabClipboardAttachment,
|
|
11975
|
+
onSubmit: () => {
|
|
11976
|
+
dispatchPending = true;
|
|
11977
|
+
},
|
|
11978
|
+
// claim keys buffered behind the Enter (turn isn't active yet)
|
|
11431
11979
|
initial: cont ? void 0 : initial,
|
|
11432
11980
|
status: computeFooter,
|
|
11433
11981
|
vimMode: cfg.editorMode === "vim",
|
|
@@ -11478,7 +12026,8 @@ ${extra}` : body);
|
|
|
11478
12026
|
let quit = await dispatchLine(line) === "quit";
|
|
11479
12027
|
while (!quit && inputStash.length) {
|
|
11480
12028
|
const next = inputStash.shift();
|
|
11481
|
-
|
|
12029
|
+
const nview = next.replace(/\n+/g, " \u23CE ");
|
|
12030
|
+
err(dim(` \u23CE stashed \u203A ${nview.slice(0, 60)}${nview.length > 60 ? "\u2026" : ""}
|
|
11482
12031
|
`));
|
|
11483
12032
|
quit = await dispatchLine(next) === "quit";
|
|
11484
12033
|
}
|
|
@@ -11488,6 +12037,7 @@ ${extra}` : body);
|
|
|
11488
12037
|
`));
|
|
11489
12038
|
await turn(prompt);
|
|
11490
12039
|
}
|
|
12040
|
+
dispatchPending = false;
|
|
11491
12041
|
session.meta.scheduledJobs = scheduler.snapshot();
|
|
11492
12042
|
try {
|
|
11493
12043
|
store.save(session);
|
|
@@ -11524,11 +12074,11 @@ ${extra}` : body);
|
|
|
11524
12074
|
`));
|
|
11525
12075
|
onCtrlC();
|
|
11526
12076
|
};
|
|
11527
|
-
|
|
12077
|
+
keyInput.on("data", onByte);
|
|
11528
12078
|
await Promise.race([dx.idle(), new Promise((res) => {
|
|
11529
12079
|
onCtrlC = res;
|
|
11530
12080
|
})]);
|
|
11531
|
-
|
|
12081
|
+
keyInput.off("data", onByte);
|
|
11532
12082
|
if (forced) {
|
|
11533
12083
|
voiceIO?.stop();
|
|
11534
12084
|
releaseStdin();
|
|
@@ -11610,7 +12160,7 @@ async function main() {
|
|
|
11610
12160
|
}
|
|
11611
12161
|
});
|
|
11612
12162
|
if (args.task) {
|
|
11613
|
-
const mounted = await mountMcp(cfg, new McpOAuth({ storePath:
|
|
12163
|
+
const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join11(cwd, ".agent", "mcp-auth.json") }));
|
|
11614
12164
|
const agent = await makeAgent(args, ai, cfg, mounted.flatMap((m) => m.tools));
|
|
11615
12165
|
const store = new SessionStore(cwd);
|
|
11616
12166
|
const session = startSession(args, store, agent, cwd);
|
|
@@ -11649,6 +12199,7 @@ export {
|
|
|
11649
12199
|
formatTranscriptFull,
|
|
11650
12200
|
jsonResult,
|
|
11651
12201
|
mcpMentionResolver,
|
|
12202
|
+
mentionRefs,
|
|
11652
12203
|
parseArgs,
|
|
11653
12204
|
pastePathClassifier,
|
|
11654
12205
|
readImageParts,
|