@livx.cc/agentx 0.99.71 → 0.99.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{Agent-CuXPAfSY.d.ts → Agent-CSFGRC-I.d.ts} +9 -2
- package/dist/cli.d.ts +3 -6
- package/dist/cli.js +730 -432
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +11 -6
- package/dist/index.js +477 -198
- package/dist/index.js.map +1 -1
- package/dist/{mcp-DGd2If0Z.d.ts → mcp-8_lBApgs.d.ts} +1 -1
- package/dist/mcp.client.d.ts +2 -2
- package/dist/{tools-DV4SkkQs.d.ts → tools-wVQBhYwm.d.ts} +3 -3
- package/dist/tools.shell.d.ts +1 -1
- package/dist/tools.shell.js +26 -16
- package/dist/tools.shell.js.map +1 -1
- package/package.json +5 -2
package/dist/cli.js
CHANGED
|
@@ -19,8 +19,192 @@ var init_logging = __esm({
|
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
+
// src/imageWire.ts
|
|
23
|
+
import { readFileSync, writeFileSync as writeFileSync2, statSync as statSync2, mkdtempSync, rmSync } from "fs";
|
|
24
|
+
import { spawnSync } from "child_process";
|
|
25
|
+
import { createHash } from "crypto";
|
|
26
|
+
import { tmpdir } from "os";
|
|
27
|
+
import { join as join2 } from "path";
|
|
28
|
+
import { PNG } from "pngjs";
|
|
29
|
+
import jpeg from "jpeg-js";
|
|
30
|
+
function imageSize(b) {
|
|
31
|
+
const u16 = (i) => b[i] << 8 | b[i + 1];
|
|
32
|
+
const le16 = (i) => b[i] | b[i + 1] << 8;
|
|
33
|
+
const u32 = (i) => (b[i] << 24 >>> 0) + (b[i + 1] << 16) + (b[i + 2] << 8) + b[i + 3];
|
|
34
|
+
if (b[0] === 137 && b[1] === 80 && b.length >= 24) return { w: u32(16), h: u32(20) };
|
|
35
|
+
if (b[0] === 71 && b[1] === 73 && b.length >= 10) return { w: le16(6), h: le16(8) };
|
|
36
|
+
if (b[0] === 255 && b[1] === 216) {
|
|
37
|
+
for (let i = 2; i + 9 < b.length; ) {
|
|
38
|
+
if (b[i] !== 255) return null;
|
|
39
|
+
const m = b[i + 1];
|
|
40
|
+
if (m >= 192 && m <= 207 && m !== 196 && m !== 200 && m !== 204) return { w: u16(i + 7), h: u16(i + 5) };
|
|
41
|
+
i += 2 + u16(i + 2);
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
if (b.length >= 30 && String.fromCharCode(...b.subarray(8, 12)) === "WEBP") {
|
|
46
|
+
const kind = String.fromCharCode(...b.subarray(12, 16));
|
|
47
|
+
if (kind === "VP8X") return { w: 1 + (b[24] | b[25] << 8 | b[26] << 16), h: 1 + (b[27] | b[28] << 8 | b[29] << 16) };
|
|
48
|
+
if (kind === "VP8 ") return { w: le16(26) & 16383, h: le16(28) & 16383 };
|
|
49
|
+
if (kind === "VP8L") {
|
|
50
|
+
const v = b[21] | b[22] << 8 | b[23] << 16 | b[24] << 24;
|
|
51
|
+
return { w: (v & 16383) + 1, h: (v >> 14 & 16383) + 1 };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
function imageNote(what, bytes, dims, why) {
|
|
57
|
+
return `[image ${what}${dims ? `, ${dims.w}x${dims.h}` : ""}, ${fmtBytes(bytes)} \u2014 not sent: ${why}]`;
|
|
58
|
+
}
|
|
59
|
+
function downscaleJs(buf, maxSide) {
|
|
60
|
+
let src;
|
|
61
|
+
try {
|
|
62
|
+
if (buf[0] === 137 && buf[1] === 80) src = PNG.sync.read(buf);
|
|
63
|
+
else if (buf[0] === 255 && buf[1] === 216) src = jpeg.decode(buf, { useTArray: true, maxMemoryUsageInMB: 1024 });
|
|
64
|
+
else return null;
|
|
65
|
+
} catch (e) {
|
|
66
|
+
log2.warn("pure-JS image decode failed", e);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const { width: sw, height: sh, data: s } = src;
|
|
70
|
+
const k = Math.min(1, maxSide / Math.max(sw, sh));
|
|
71
|
+
const w = Math.max(1, Math.round(sw * k)), h = Math.max(1, Math.round(sh * k));
|
|
72
|
+
const d = new Uint8Array(w * h * 4);
|
|
73
|
+
for (let y = 0; y < h; y++) {
|
|
74
|
+
const y0 = Math.floor(y * sh / h), y1 = Math.max(y0 + 1, Math.floor((y + 1) * sh / h));
|
|
75
|
+
for (let x = 0; x < w; x++) {
|
|
76
|
+
const x0 = Math.floor(x * sw / w), x1 = Math.max(x0 + 1, Math.floor((x + 1) * sw / w));
|
|
77
|
+
let r = 0, g = 0, b = 0, a = 0;
|
|
78
|
+
for (let yy = y0; yy < y1; yy++) for (let xx = x0; xx < x1; xx++) {
|
|
79
|
+
const i = (yy * sw + xx) * 4, al = s[i + 3];
|
|
80
|
+
r += s[i] * al;
|
|
81
|
+
g += s[i + 1] * al;
|
|
82
|
+
b += s[i + 2] * al;
|
|
83
|
+
a += al;
|
|
84
|
+
}
|
|
85
|
+
const o = (y * w + x) * 4, n = (y1 - y0) * (x1 - x0), bg = 255 * (n - a / 255);
|
|
86
|
+
d[o] = (r / 255 + bg) / n;
|
|
87
|
+
d[o + 1] = (g / 255 + bg) / n;
|
|
88
|
+
d[o + 2] = (b / 255 + bg) / n;
|
|
89
|
+
d[o + 3] = 255;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
let out = Buffer.alloc(0);
|
|
93
|
+
for (const q2 of [80, 60, 40]) {
|
|
94
|
+
out = Buffer.from(jpeg.encode({ width: w, height: h, data: d }, q2).data);
|
|
95
|
+
if (out.length <= IMAGE_MAX_BYTES) break;
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
function downscale(path, maxSide) {
|
|
100
|
+
const dir = mkdtempSync(join2(tmpdir(), "agentx-img-"));
|
|
101
|
+
const out = join2(dir, "x.jpg");
|
|
102
|
+
try {
|
|
103
|
+
const tries = [
|
|
104
|
+
["sips", ["-Z", String(maxSide), "-s", "format", "jpeg", "-s", "formatOptions", "80", path, "--out", out]],
|
|
105
|
+
["magick", [path, "-resize", `${maxSide}x${maxSide}>`, "-quality", "80", out]],
|
|
106
|
+
["convert", [path, "-resize", `${maxSide}x${maxSide}>`, "-quality", "80", out]]
|
|
107
|
+
];
|
|
108
|
+
for (const [cmd, args] of tries) {
|
|
109
|
+
const r = spawnSync(cmd, args, { stdio: "ignore", timeout: 15e3 });
|
|
110
|
+
if (r.status !== 0) continue;
|
|
111
|
+
try {
|
|
112
|
+
const o = readFileSync(out);
|
|
113
|
+
if (o.length <= IMAGE_MAX_BYTES) return o;
|
|
114
|
+
break;
|
|
115
|
+
} catch {
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const js = downscaleJs(readFileSync(path), maxSide);
|
|
119
|
+
if (!js) log2.warn(`could not downscale ${path} (tried sips/magick/convert/pure JS)`);
|
|
120
|
+
return js;
|
|
121
|
+
} finally {
|
|
122
|
+
rmSync(dir, { recursive: true, force: true });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function remember(id, w) {
|
|
126
|
+
cache.delete(id);
|
|
127
|
+
cache.set(id, w);
|
|
128
|
+
if (cache.size > CACHE_MAX) cache.delete(cache.keys().next().value);
|
|
129
|
+
return w;
|
|
130
|
+
}
|
|
131
|
+
function wireOf(key, buf, mime, label, file) {
|
|
132
|
+
const dims = imageSize(buf);
|
|
133
|
+
const fits = buf.length <= IMAGE_MAX_BYTES && (!dims || Math.max(dims.w, dims.h) <= IMAGE_MAX_SIDE);
|
|
134
|
+
let small = null;
|
|
135
|
+
if (!fits) {
|
|
136
|
+
if (file) small = downscale(file, IMAGE_MAX_SIDE);
|
|
137
|
+
else {
|
|
138
|
+
const dir = mkdtempSync(join2(tmpdir(), "agentx-img-in-"));
|
|
139
|
+
try {
|
|
140
|
+
const f = join2(dir, `in.${mime.split("/")[1] || "img"}`);
|
|
141
|
+
writeFileSync2(f, buf);
|
|
142
|
+
small = downscale(f, IMAGE_MAX_SIDE);
|
|
143
|
+
} finally {
|
|
144
|
+
rmSync(dir, { recursive: true, force: true });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const url = fits ? `data:${mime};base64,${buf.toString("base64")}` : small && small.length <= IMAGE_MAX_BYTES ? `data:image/jpeg;base64,${small.toString("base64")}` : null;
|
|
149
|
+
return { key, url, note: imageNote(label, buf.length, dims, url ? "this model can't see images" : "too large and it couldn't be downscaled") };
|
|
150
|
+
}
|
|
151
|
+
function imageForWire(path, mime, vision) {
|
|
152
|
+
let st;
|
|
153
|
+
try {
|
|
154
|
+
st = statSync2(path);
|
|
155
|
+
} catch (e) {
|
|
156
|
+
log2.warn(`image unreadable: ${path}`, e);
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
const key = `${st.mtimeMs}:${st.size}`;
|
|
160
|
+
let hit = cache.get(path);
|
|
161
|
+
if (!hit || hit.key !== key) hit = remember(path, wireOf(key, readFileSync(path), mime, path, path));
|
|
162
|
+
return vision && hit.url ? { url: hit.url } : { note: hit.note };
|
|
163
|
+
}
|
|
164
|
+
function inlineImageForWire(url, vision) {
|
|
165
|
+
const comma = url.indexOf(",");
|
|
166
|
+
const mime = url.slice(5, url.indexOf(";"));
|
|
167
|
+
if (vision && url.length < 3e5) {
|
|
168
|
+
const b64 = url.slice(comma + 1);
|
|
169
|
+
const dims = imageSize(Buffer.from(b64.slice(0, 4096), "base64")) ?? imageSize(Buffer.from(b64, "base64"));
|
|
170
|
+
if (!dims || Math.max(dims.w, dims.h) <= IMAGE_MAX_SIDE) return { url };
|
|
171
|
+
}
|
|
172
|
+
const id = `inline:${createHash("sha1").update(url).digest("base64")}`;
|
|
173
|
+
let hit = cache.get(id);
|
|
174
|
+
if (!hit) {
|
|
175
|
+
const buf = Buffer.from(url.slice(comma + 1), "base64");
|
|
176
|
+
hit = remember(id, wireOf(id, buf, mime, `(${mime})`));
|
|
177
|
+
}
|
|
178
|
+
return vision && hit.url ? { url: hit.url } : { note: hit.note };
|
|
179
|
+
}
|
|
180
|
+
function imageWireBytes(path, mime, vision) {
|
|
181
|
+
const r = imageForWire(path, mime, vision);
|
|
182
|
+
return !r ? 0 : "url" in r ? r.url.length : r.note.length;
|
|
183
|
+
}
|
|
184
|
+
function scrubBase64Text(s) {
|
|
185
|
+
if (s.length < 2e3) return s;
|
|
186
|
+
const out = s.replace(DATA_URI, (m) => imageNote(m.slice(5, m.indexOf(";")), Math.floor((m.length - m.indexOf(",") - 1) * 3 / 4), null, "base64 is never sent as text")).replace(B64_RUN, (m) => !/[+/]/.test(m) || !/\d/.test(m) ? m : `[base64 data, ${fmtBytes(Math.floor(m.length * 3 / 4))} \u2014 not sent: base64 is never sent as text]`);
|
|
187
|
+
if (out !== s) log2.warn(`scrubbed base64 from request text (${s.length} \u2192 ${out.length} chars)`);
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
var log2, IMAGE_MAX_SIDE, IMAGE_MAX_BYTES, fmtBytes, cache, CACHE_MAX, DATA_URI, B64_RUN;
|
|
191
|
+
var init_imageWire = __esm({
|
|
192
|
+
"src/imageWire.ts"() {
|
|
193
|
+
"use strict";
|
|
194
|
+
init_logging();
|
|
195
|
+
log2 = forComponent("imageWire");
|
|
196
|
+
IMAGE_MAX_SIDE = 1568;
|
|
197
|
+
IMAGE_MAX_BYTES = 1e6;
|
|
198
|
+
fmtBytes = (n) => n >= 1e6 ? `${(n / 1e6).toFixed(1)} MB` : n >= 1e3 ? `${Math.round(n / 1e3)} KB` : `${n} B`;
|
|
199
|
+
cache = /* @__PURE__ */ new Map();
|
|
200
|
+
CACHE_MAX = 8;
|
|
201
|
+
DATA_URI = /data:image\/[\w.+-]+;base64,[A-Za-z0-9+/=]+/g;
|
|
202
|
+
B64_RUN = /[A-Za-z0-9+/=]{2000,}/g;
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
22
206
|
// src/llm.ts
|
|
23
|
-
import {
|
|
207
|
+
import { statSync as statSync3, realpathSync } from "fs";
|
|
24
208
|
import { resolve, sep } from "path";
|
|
25
209
|
import { randomBytes } from "crypto";
|
|
26
210
|
function passesBackReasoning(model) {
|
|
@@ -77,9 +261,9 @@ function mintImageRef(path, mime) {
|
|
|
77
261
|
const token = randomBytes(16).toString("hex");
|
|
78
262
|
let bytes = 0;
|
|
79
263
|
try {
|
|
80
|
-
bytes = Math.ceil(
|
|
264
|
+
bytes = Math.ceil(statSync3(path).size * 4 / 3);
|
|
81
265
|
} catch (e) {
|
|
82
|
-
|
|
266
|
+
log3.warn(`image ref stat failed, weighing as 0: ${path}`, e);
|
|
83
267
|
}
|
|
84
268
|
refRegistry.set(token, { mime, path, bytes });
|
|
85
269
|
while (refRegistry.size > REF_REGISTRY_MAX) refRegistry.delete(refRegistry.keys().next().value);
|
|
@@ -92,7 +276,7 @@ function parseImageRef(url) {
|
|
|
92
276
|
const parts = refUrlParts(url);
|
|
93
277
|
const ref = parts && refRegistry.get(parts.token);
|
|
94
278
|
if (!ref) {
|
|
95
|
-
|
|
279
|
+
log3.debug("image ref not in registry \u2014 treating as non-image text");
|
|
96
280
|
return null;
|
|
97
281
|
}
|
|
98
282
|
return ref;
|
|
@@ -111,7 +295,7 @@ function remintImageRefs(messages, roots) {
|
|
|
111
295
|
try {
|
|
112
296
|
allowed.push(realpathSync(resolve(r)));
|
|
113
297
|
} catch (e) {
|
|
114
|
-
|
|
298
|
+
log3.debug(`image-ref root unavailable: ${r}`, e);
|
|
115
299
|
}
|
|
116
300
|
}
|
|
117
301
|
if (allowed.length === 0) return messages;
|
|
@@ -128,11 +312,11 @@ function remintImageRefs(messages, roots) {
|
|
|
128
312
|
try {
|
|
129
313
|
real = realpathSync(f.path);
|
|
130
314
|
} catch (e) {
|
|
131
|
-
|
|
315
|
+
log3.warn(`resumed image ref unreadable, will stub: ${f.path}`, e);
|
|
132
316
|
return p;
|
|
133
317
|
}
|
|
134
318
|
if (!inside(real)) {
|
|
135
|
-
|
|
319
|
+
log3.warn(`resumed image ref outside the allowed roots, will stub: ${f.path} -> ${real}`);
|
|
136
320
|
return p;
|
|
137
321
|
}
|
|
138
322
|
touched = true;
|
|
@@ -159,7 +343,7 @@ function toolImageRef(content) {
|
|
|
159
343
|
try {
|
|
160
344
|
return parseImageRef(JSON.parse(content).imageRef);
|
|
161
345
|
} catch (e) {
|
|
162
|
-
|
|
346
|
+
log3.debug("bad imageRef tool result", e);
|
|
163
347
|
return null;
|
|
164
348
|
}
|
|
165
349
|
}
|
|
@@ -187,32 +371,41 @@ function imageWindows(messages, keepRecent) {
|
|
|
187
371
|
const k = Math.max(0, keepRecent);
|
|
188
372
|
return /* @__PURE__ */ new Set([...attach.slice(Math.max(0, attach.length - k)), ...tool.slice(Math.max(0, tool.length - k))]);
|
|
189
373
|
}
|
|
190
|
-
function expandImagesForSend(messages, keepRecent = 1, stub = IMAGE_ELIDE_STUB) {
|
|
374
|
+
function expandImagesForSend(messages, keepRecent = 1, stub = IMAGE_ELIDE_STUB, vision = true) {
|
|
191
375
|
if (!messages.some(bearsImage)) return messages;
|
|
192
376
|
const keep = imageWindows(messages, keepRecent);
|
|
193
377
|
let changed = false;
|
|
194
378
|
const out = messages.map((m, i) => {
|
|
195
|
-
const content = keep.has(i) ? expandContent(m.content,
|
|
379
|
+
const content = keep.has(i) ? expandContent(m.content, vision) : stripContent(m.content, stub);
|
|
196
380
|
if (content === m.content) return m;
|
|
197
381
|
changed = true;
|
|
198
382
|
return { ...m, content };
|
|
199
383
|
});
|
|
200
384
|
return changed ? out : messages;
|
|
201
385
|
}
|
|
202
|
-
function sendBytes(messages, keepRecent = 1, stub = IMAGE_ELIDE_STUB) {
|
|
386
|
+
function sendBytes(messages, keepRecent = 1, vision = true, stub = IMAGE_ELIDE_STUB) {
|
|
203
387
|
let n = 0;
|
|
204
|
-
for (const b of sendBytesPer(messages, keepRecent, stub)) n += b;
|
|
388
|
+
for (const b of sendBytesPer(messages, keepRecent, vision, stub)) n += b;
|
|
205
389
|
return n;
|
|
206
390
|
}
|
|
207
|
-
function sendBytesPer(messages, keepRecent = 1, stub = IMAGE_ELIDE_STUB) {
|
|
391
|
+
function sendBytesPer(messages, keepRecent = 1, vision = true, stub = IMAGE_ELIDE_STUB) {
|
|
208
392
|
const keep = messages.some(bearsImage) ? imageWindows(messages, keepRecent) : null;
|
|
209
|
-
return messages.map((m, i) => wireBytes(m.content, keep == null || keep.has(i), stub) + (m.tool_calls ? JSON.stringify(m.tool_calls).length : 0) + (m.reasoning_content?.length ?? 0));
|
|
393
|
+
return messages.map((m, i) => wireBytes(m.content, keep == null || keep.has(i), vision, stub) + (m.tool_calls ? JSON.stringify(m.tool_calls).length : 0) + (m.reasoning_content?.length ?? 0));
|
|
210
394
|
}
|
|
211
|
-
function wireBytes(content, kept, stub) {
|
|
395
|
+
function wireBytes(content, kept, vision, stub) {
|
|
212
396
|
const tool = toolImageRef(content);
|
|
213
|
-
if (tool) return kept ? tool.
|
|
397
|
+
if (tool) return kept ? imageWireBytes(tool.path, tool.mime, vision) || stub.length : stub.length;
|
|
214
398
|
if (content == null || typeof content === "string") return contentBytes(content);
|
|
215
|
-
if (kept)
|
|
399
|
+
if (kept) {
|
|
400
|
+
let n2 = 0;
|
|
401
|
+
for (const p of content) {
|
|
402
|
+
const url = p.type === "image_url" ? p.image_url?.url ?? "" : "";
|
|
403
|
+
const ref = url ? parseImageRef(url) : null;
|
|
404
|
+
const inline = url.startsWith("data:") ? inlineImageForWire(url, vision) : null;
|
|
405
|
+
n2 += ref ? imageWireBytes(ref.path, ref.mime, vision) : inline ? ("url" in inline ? inline.url : inline.note).length : contentBytes([p]);
|
|
406
|
+
}
|
|
407
|
+
return n2;
|
|
408
|
+
}
|
|
216
409
|
let n = 0;
|
|
217
410
|
for (const p of content) {
|
|
218
411
|
if (p.type === "text") {
|
|
@@ -224,31 +417,70 @@ function wireBytes(content, kept, stub) {
|
|
|
224
417
|
}
|
|
225
418
|
return n;
|
|
226
419
|
}
|
|
227
|
-
function
|
|
228
|
-
try {
|
|
229
|
-
return `data:${ref.mime};base64,${readFileSync(ref.path).toString("base64")}`;
|
|
230
|
-
} catch (e) {
|
|
231
|
-
log2.warn(`image ref unreadable, sending stub instead: ${ref.path}`, e);
|
|
232
|
-
return null;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
function expandContent(content, stub) {
|
|
420
|
+
function expandContent(content, vision) {
|
|
236
421
|
const tool = toolImageRef(content);
|
|
237
422
|
if (tool) {
|
|
238
|
-
const
|
|
239
|
-
|
|
423
|
+
const w = imageForWire(tool.path, tool.mime, vision);
|
|
424
|
+
if (!w) return IMAGE_ELIDE_STUB;
|
|
425
|
+
return "url" in w ? [{ type: "text", text: `image: ${tool.path}` }, imagePart(w.url)] : w.note;
|
|
240
426
|
}
|
|
241
427
|
if (typeof content === "string") return content;
|
|
242
428
|
let changed = false;
|
|
243
429
|
const out = content.map((p) => {
|
|
244
|
-
if (p.type !== "image_url"
|
|
430
|
+
if (p.type !== "image_url") return p;
|
|
431
|
+
const url = p.image_url?.url ?? "";
|
|
432
|
+
if (url.startsWith("data:")) {
|
|
433
|
+
const w2 = inlineImageForWire(url, vision);
|
|
434
|
+
if ("url" in w2 && w2.url === url) return p;
|
|
435
|
+
changed = true;
|
|
436
|
+
return "url" in w2 ? imagePart(w2.url) : { type: "text", text: w2.note };
|
|
437
|
+
}
|
|
438
|
+
if (!isImageRefUrl(url)) return p;
|
|
245
439
|
changed = true;
|
|
246
|
-
const ref = parseImageRef(
|
|
247
|
-
|
|
248
|
-
|
|
440
|
+
const ref = parseImageRef(url);
|
|
441
|
+
if (!ref) return { type: "text", text: IMAGE_ELIDE_STUB };
|
|
442
|
+
const w = imageForWire(ref.path, ref.mime, vision);
|
|
443
|
+
if (!w) return { type: "text", text: IMAGE_ELIDE_STUB };
|
|
444
|
+
return "url" in w ? imagePart(w.url) : { type: "text", text: w.note };
|
|
249
445
|
});
|
|
250
446
|
return changed ? out : content;
|
|
251
447
|
}
|
|
448
|
+
function hoistToolImages(messages) {
|
|
449
|
+
if (!messages.some((m) => m.role === "tool" && Array.isArray(m.content) && m.content.some((p) => p.type === "image_url"))) return messages;
|
|
450
|
+
const out = [];
|
|
451
|
+
let pending = [];
|
|
452
|
+
const flush = () => {
|
|
453
|
+
if (pending.length) out.push({ role: "user", content: [{ type: "text", text: "Image output from the tool call above:" }, ...pending] });
|
|
454
|
+
pending = [];
|
|
455
|
+
};
|
|
456
|
+
for (const m of messages) {
|
|
457
|
+
if (m.role !== "tool") flush();
|
|
458
|
+
if (m.role === "tool" && Array.isArray(m.content) && m.content.some((p) => p.type === "image_url")) {
|
|
459
|
+
const imgs = m.content.filter((p) => p.type === "image_url");
|
|
460
|
+
pending.push(...imgs);
|
|
461
|
+
const text = m.content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
|
|
462
|
+
out.push({ ...m, content: text || `[${imgs.length} image(s) returned; shown below]` });
|
|
463
|
+
} else out.push(m);
|
|
464
|
+
}
|
|
465
|
+
flush();
|
|
466
|
+
return out;
|
|
467
|
+
}
|
|
468
|
+
function scrubBase64(messages) {
|
|
469
|
+
let changed = false;
|
|
470
|
+
const out = messages.map((m) => {
|
|
471
|
+
const c = m.content;
|
|
472
|
+
let next = c;
|
|
473
|
+
if (typeof c === "string") next = scrubBase64Text(c);
|
|
474
|
+
else if (Array.isArray(c)) {
|
|
475
|
+
const parts = c.map((p) => p.type === "text" && p.text ? { ...p, text: scrubBase64Text(p.text) } : p);
|
|
476
|
+
if (parts.some((p, i) => p.text !== c[i].text)) next = parts;
|
|
477
|
+
}
|
|
478
|
+
if (next === c) return m;
|
|
479
|
+
changed = true;
|
|
480
|
+
return { ...m, content: next };
|
|
481
|
+
});
|
|
482
|
+
return changed ? out : messages;
|
|
483
|
+
}
|
|
252
484
|
function stripContent(content, stub) {
|
|
253
485
|
if (toolImageRef(content)) return stub;
|
|
254
486
|
if (typeof content === "string") return content;
|
|
@@ -271,12 +503,13 @@ function endsOnAnnouncedAction(text) {
|
|
|
271
503
|
if (PROGRESSIVE.test(last)) return true;
|
|
272
504
|
return clauses(last).some((c) => !NEXT_STEP.test(c) && !SIGN_OFF.test(c) && COMMITMENT.test(c));
|
|
273
505
|
}
|
|
274
|
-
var
|
|
506
|
+
var log3, p_sep, IMAGE_ELIDE_STUB, IMAGE_REF_SCHEME, REF_REGISTRY_MAX, refRegistry, isImageRefUrl, bearsImage, COMMITMENT, PROGRESSIVE, NEXT_STEP, SIGN_OFF;
|
|
275
507
|
var init_llm = __esm({
|
|
276
508
|
"src/llm.ts"() {
|
|
277
509
|
"use strict";
|
|
278
510
|
init_logging();
|
|
279
|
-
|
|
511
|
+
init_imageWire();
|
|
512
|
+
log3 = forComponent("llm");
|
|
280
513
|
p_sep = (parts) => parts.length > 1 ? "\n" : "";
|
|
281
514
|
IMAGE_ELIDE_STUB = "[image elided \u2014 re-attach @path or /paste to view again]";
|
|
282
515
|
IMAGE_REF_SCHEME = "agentx-image:";
|
|
@@ -963,7 +1196,7 @@ function makeWebFetchTool(options = {}) {
|
|
|
963
1196
|
|
|
964
1197
|
${capped}`;
|
|
965
1198
|
} catch (e) {
|
|
966
|
-
|
|
1199
|
+
log4.debug(`WebFetch ${u} failed`, e);
|
|
967
1200
|
return `Error fetching ${u}: ${e?.name === "AbortError" ? `timed out after ${timeoutMs}ms` : e?.message ?? e}`;
|
|
968
1201
|
} finally {
|
|
969
1202
|
clearTimeout(timer);
|
|
@@ -1087,7 +1320,7 @@ function makeWebSearchTool(options = {}) {
|
|
|
1087
1320
|
if (!res.ok) return `Error: search returned ${res.status} ${res.statusText}`;
|
|
1088
1321
|
return formatHits(parseDdgHtml(await res.text(), maxResults));
|
|
1089
1322
|
} catch (e) {
|
|
1090
|
-
|
|
1323
|
+
log4.debug("WebSearch failed", e);
|
|
1091
1324
|
return `Error searching: ${e?.name === "AbortError" ? `timed out after ${timeoutMs}ms` : e?.message ?? e}`;
|
|
1092
1325
|
} finally {
|
|
1093
1326
|
clearTimeout(timer);
|
|
@@ -1098,12 +1331,12 @@ function makeWebSearchTool(options = {}) {
|
|
|
1098
1331
|
function makeWebSearchAnthropicTool(opts = {}) {
|
|
1099
1332
|
return makeWebSearchTool({ provider: "anthropic", name: "WebSearchAnthropic", description: ANTHROPIC_SEARCH_DESC, anthropicApiKey: opts.anthropicApiKey, model: opts.model });
|
|
1100
1333
|
}
|
|
1101
|
-
var
|
|
1334
|
+
var log4, _dnsLookup, webFetchTool, webSearchTool, ANTHROPIC_SEARCH_DESC, webSearchAnthropicTool;
|
|
1102
1335
|
var init_tools_web = __esm({
|
|
1103
1336
|
"src/tools.web.ts"() {
|
|
1104
1337
|
"use strict";
|
|
1105
1338
|
init_logging();
|
|
1106
|
-
|
|
1339
|
+
log4 = forComponent("web");
|
|
1107
1340
|
webFetchTool = makeWebFetchTool();
|
|
1108
1341
|
webSearchTool = makeWebSearchTool();
|
|
1109
1342
|
ANTHROPIC_SEARCH_DESC = "High-quality web search via Anthropic's native search index. Returns concise, sourced findings \u2014 one claim per line, each with its source URL. Prefer this over WebSearch when accuracy and citations matter; it is slower (~3\u20138s) and bills the Anthropic API account per call (one cheap-model turn + search fee).";
|
|
@@ -1323,7 +1556,7 @@ function realPathOf(fs, path) {
|
|
|
1323
1556
|
try {
|
|
1324
1557
|
return fs.realPath(path);
|
|
1325
1558
|
} catch (e) {
|
|
1326
|
-
|
|
1559
|
+
log5.debug(`realPath unavailable for ${path} \u2014 inlining image bytes instead`, e);
|
|
1327
1560
|
return null;
|
|
1328
1561
|
}
|
|
1329
1562
|
}
|
|
@@ -1375,7 +1608,7 @@ function toolsByName(names, available) {
|
|
|
1375
1608
|
return t;
|
|
1376
1609
|
});
|
|
1377
1610
|
}
|
|
1378
|
-
var
|
|
1611
|
+
var log5, numberLines, bashTool, IMG_MIME, readTool, editTool;
|
|
1379
1612
|
var init_tools = __esm({
|
|
1380
1613
|
"src/tools.ts"() {
|
|
1381
1614
|
"use strict";
|
|
@@ -1386,7 +1619,7 @@ var init_tools = __esm({
|
|
|
1386
1619
|
init_tools_web();
|
|
1387
1620
|
init_OverlayFilesystem();
|
|
1388
1621
|
init_redact();
|
|
1389
|
-
|
|
1622
|
+
log5 = forComponent("tools");
|
|
1390
1623
|
numberLines = (content, offset = 0, limit) => {
|
|
1391
1624
|
const lines = content.split("\n");
|
|
1392
1625
|
const start = Math.max(0, offset);
|
|
@@ -1568,12 +1801,12 @@ ${tail || "(no output)"}`;
|
|
|
1568
1801
|
}
|
|
1569
1802
|
];
|
|
1570
1803
|
}
|
|
1571
|
-
var
|
|
1804
|
+
var log6, JobRegistryOptions, secs, JobRegistry, NO_JOB, describe;
|
|
1572
1805
|
var init_tools_jobs = __esm({
|
|
1573
1806
|
"src/tools.jobs.ts"() {
|
|
1574
1807
|
"use strict";
|
|
1575
1808
|
init_logging();
|
|
1576
|
-
|
|
1809
|
+
log6 = forComponent("jobs");
|
|
1577
1810
|
JobRegistryOptions = class {
|
|
1578
1811
|
/** Tail buffer cap per job (chars). */
|
|
1579
1812
|
maxBuffer = 256 * 1024;
|
|
@@ -1752,7 +1985,7 @@ var init_tools_jobs = __esm({
|
|
|
1752
1985
|
try {
|
|
1753
1986
|
onExit({ id: job.id, kind: job.kind, label: job.label, status, reason: job.reason, tail: job.buf.slice(-4e3), result });
|
|
1754
1987
|
} catch (e) {
|
|
1755
|
-
|
|
1988
|
+
log6.warn(`job onExit handler threw for ${job.id}: ${e}`);
|
|
1756
1989
|
}
|
|
1757
1990
|
}
|
|
1758
1991
|
}
|
|
@@ -1806,7 +2039,7 @@ var init_tools_jobs = __esm({
|
|
|
1806
2039
|
try {
|
|
1807
2040
|
j.onKill?.(reason);
|
|
1808
2041
|
} catch (e) {
|
|
1809
|
-
|
|
2042
|
+
log6.warn(`onKill for ${id} threw: ${e}`);
|
|
1810
2043
|
}
|
|
1811
2044
|
}
|
|
1812
2045
|
return true;
|
|
@@ -1853,7 +2086,7 @@ var init_tools_jobs = __esm({
|
|
|
1853
2086
|
try {
|
|
1854
2087
|
cpu = await j.cpuTime();
|
|
1855
2088
|
} catch (e) {
|
|
1856
|
-
|
|
2089
|
+
log6.debug(`cpu probe for ${j.id} failed: ${e}`);
|
|
1857
2090
|
}
|
|
1858
2091
|
if (cpu != null && j.lastCpu != null && cpu > j.lastCpu) j.lastActivityAt = this.options.now();
|
|
1859
2092
|
const prev = j.lastCpu;
|
|
@@ -1861,7 +2094,7 @@ var init_tools_jobs = __esm({
|
|
|
1861
2094
|
const verdict = this.verdict(j, now4);
|
|
1862
2095
|
if (!verdict?.idle || j.status !== "running") continue;
|
|
1863
2096
|
if (cpu == null) {
|
|
1864
|
-
|
|
2097
|
+
log6.debug(`not reaping ${j.id}: ${verdict.reason} but CPU unknown`);
|
|
1865
2098
|
continue;
|
|
1866
2099
|
}
|
|
1867
2100
|
if (prev == null) continue;
|
|
@@ -1875,7 +2108,7 @@ var init_tools_jobs = __esm({
|
|
|
1875
2108
|
return null;
|
|
1876
2109
|
}
|
|
1877
2110
|
stuck(j, reason, now4) {
|
|
1878
|
-
|
|
2111
|
+
log6.warn(`reaping ${j.id} (${j.kind} "${j.label}"): ${reason} \u2014 age ${secs(now4 - j.startedAt)}, last activity ${secs(now4 - j.lastActivityAt)} ago, idleMs=${j.idleMs}, hardCapMs=${j.hardCapMs}, ${j.bytes} byte(s) output`);
|
|
1879
2112
|
if (j.onKill) j.notify = true;
|
|
1880
2113
|
this.kill(j.id, reason);
|
|
1881
2114
|
}
|
|
@@ -1889,7 +2122,7 @@ var init_tools_jobs = __esm({
|
|
|
1889
2122
|
this.timer = void 0;
|
|
1890
2123
|
return;
|
|
1891
2124
|
}
|
|
1892
|
-
void this.sweep().catch((e) =>
|
|
2125
|
+
void this.sweep().catch((e) => log6.warn(`job sweep failed: ${e}`));
|
|
1893
2126
|
}, this.options.sweepMs);
|
|
1894
2127
|
this.timer.unref?.();
|
|
1895
2128
|
}
|
|
@@ -1916,9 +2149,9 @@ __export(worktree_exports, {
|
|
|
1916
2149
|
validateWorktreeSlug: () => validateWorktreeSlug,
|
|
1917
2150
|
worktreeBranchName: () => worktreeBranchName
|
|
1918
2151
|
});
|
|
1919
|
-
import { spawnSync } from "child_process";
|
|
1920
|
-
import { symlinkSync, mkdirSync, statSync as
|
|
1921
|
-
import { join as
|
|
2152
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
2153
|
+
import { symlinkSync, mkdirSync, statSync as statSync4, readFileSync as readFileSync3, rmSync as rmSync2 } from "fs";
|
|
2154
|
+
import { join as join3 } from "path";
|
|
1922
2155
|
function validateWorktreeSlug(slug2) {
|
|
1923
2156
|
if (!slug2) throw new Error("worktree slug must not be empty");
|
|
1924
2157
|
if (slug2.length > MAX_SLUG_LENGTH) throw new Error(`worktree slug must be \u2264${MAX_SLUG_LENGTH} chars (got ${slug2.length})`);
|
|
@@ -1934,13 +2167,13 @@ function worktreeBranchName(slug2) {
|
|
|
1934
2167
|
return `worktree-${flattenSlug(slug2)}`;
|
|
1935
2168
|
}
|
|
1936
2169
|
function worktreesDir(repoRoot) {
|
|
1937
|
-
return
|
|
2170
|
+
return join3(repoRoot, ".claude", "worktrees");
|
|
1938
2171
|
}
|
|
1939
2172
|
function worktreePathFor(repoRoot, slug2) {
|
|
1940
|
-
return
|
|
2173
|
+
return join3(worktreesDir(repoRoot), flattenSlug(slug2));
|
|
1941
2174
|
}
|
|
1942
2175
|
function git(args, cwd) {
|
|
1943
|
-
const r =
|
|
2176
|
+
const r = spawnSync2("git", args, {
|
|
1944
2177
|
cwd,
|
|
1945
2178
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1946
2179
|
env: { ...process.env, ...GIT_NO_PROMPT_ENV }
|
|
@@ -1953,16 +2186,16 @@ function findGitRoot(from) {
|
|
|
1953
2186
|
}
|
|
1954
2187
|
function readWorktreeHead(worktreePath) {
|
|
1955
2188
|
try {
|
|
1956
|
-
const gitFile =
|
|
2189
|
+
const gitFile = readFileSync3(join3(worktreePath, ".git"), "utf-8").trim();
|
|
1957
2190
|
const m = gitFile.match(/^gitdir:\s*(.+)$/);
|
|
1958
2191
|
if (!m) return null;
|
|
1959
|
-
const headPath =
|
|
1960
|
-
const head =
|
|
2192
|
+
const headPath = join3(m[1], "HEAD");
|
|
2193
|
+
const head = readFileSync3(headPath, "utf-8").trim();
|
|
1961
2194
|
const refMatch = head.match(/^ref:\s*(.+)$/);
|
|
1962
2195
|
if (!refMatch) return head.length === 40 ? head : null;
|
|
1963
|
-
const refPath =
|
|
2196
|
+
const refPath = join3(m[1], "..", refMatch[1]);
|
|
1964
2197
|
try {
|
|
1965
|
-
return
|
|
2198
|
+
return readFileSync3(refPath, "utf-8").trim();
|
|
1966
2199
|
} catch {
|
|
1967
2200
|
}
|
|
1968
2201
|
const r = git(["rev-parse", "HEAD"], worktreePath);
|
|
@@ -1995,10 +2228,10 @@ function getOrCreateWorktree(repoRoot, slug2) {
|
|
|
1995
2228
|
}
|
|
1996
2229
|
function symlinkDirs(repoRoot, worktreePath, dirs) {
|
|
1997
2230
|
for (const dir of dirs) {
|
|
1998
|
-
const src =
|
|
1999
|
-
const dst =
|
|
2231
|
+
const src = join3(repoRoot, dir);
|
|
2232
|
+
const dst = join3(worktreePath, dir);
|
|
2000
2233
|
try {
|
|
2001
|
-
|
|
2234
|
+
statSync4(src);
|
|
2002
2235
|
} catch {
|
|
2003
2236
|
continue;
|
|
2004
2237
|
}
|
|
@@ -2014,7 +2247,7 @@ function cleanupWorktree(repoRoot, slug2, opts) {
|
|
|
2014
2247
|
const worktreePath = worktreePathFor(repoRoot, slug2);
|
|
2015
2248
|
const branch = worktreeBranchName(slug2);
|
|
2016
2249
|
try {
|
|
2017
|
-
|
|
2250
|
+
statSync4(worktreePath);
|
|
2018
2251
|
} catch {
|
|
2019
2252
|
return { removed: false, reason: "not found" };
|
|
2020
2253
|
}
|
|
@@ -2024,7 +2257,7 @@ function cleanupWorktree(repoRoot, slug2, opts) {
|
|
|
2024
2257
|
}
|
|
2025
2258
|
for (const dir of ["node_modules", ".bun"]) {
|
|
2026
2259
|
try {
|
|
2027
|
-
|
|
2260
|
+
rmSync2(join3(worktreePath, dir));
|
|
2028
2261
|
} catch {
|
|
2029
2262
|
}
|
|
2030
2263
|
}
|
|
@@ -2561,7 +2794,7 @@ async function psGroupCpuSeconds(pgid) {
|
|
|
2561
2794
|
const out = await new Promise((res, rej) => execFile4("ps", ["-A", "-o", "pid=,ppid=,pgid=,time="], { timeout: 5e3, env: { ...process.env, LC_ALL: "C" } }, (e, so) => e ? rej(e) : res(String(so))));
|
|
2562
2795
|
return sumTreeCpu(out, pgid);
|
|
2563
2796
|
} catch (e) {
|
|
2564
|
-
|
|
2797
|
+
log24.debug(`ps cpu probe failed for group ${pgid}: ${e}`);
|
|
2565
2798
|
return void 0;
|
|
2566
2799
|
}
|
|
2567
2800
|
}
|
|
@@ -2709,7 +2942,7 @@ function makeRealShellTool(options) {
|
|
|
2709
2942
|
proc.stderr?.on("data", collect);
|
|
2710
2943
|
proc.on("error", (err2) => {
|
|
2711
2944
|
if (err2?.name === "AbortError" || ctl.signal.aborted) return finish(reasonFor(timedOut, timeoutMs, clean2(out)));
|
|
2712
|
-
|
|
2945
|
+
log24.debug("shell spawn error", err2);
|
|
2713
2946
|
finish(`[exit 1] ${err2?.message ?? err2}${out ? "\n" + clean2(out) : ""}`);
|
|
2714
2947
|
});
|
|
2715
2948
|
proc.on("close", (code) => {
|
|
@@ -2782,7 +3015,7 @@ ${clean2(out) || "(no output yet)"}`;
|
|
|
2782
3015
|
}
|
|
2783
3016
|
];
|
|
2784
3017
|
}
|
|
2785
|
-
var
|
|
3018
|
+
var log24, clean2, DETACHED, SECRET_ENV_RE, _spawn, ShellJobRegistry, shellStatus, NO_JOB2;
|
|
2786
3019
|
var init_tools_shell = __esm({
|
|
2787
3020
|
"src/tools.shell.ts"() {
|
|
2788
3021
|
"use strict";
|
|
@@ -2791,7 +3024,7 @@ var init_tools_shell = __esm({
|
|
|
2791
3024
|
init_logging();
|
|
2792
3025
|
init_shell_sandbox();
|
|
2793
3026
|
init_tools_jobs();
|
|
2794
|
-
|
|
3027
|
+
log24 = forComponent("shell");
|
|
2795
3028
|
clean2 = (s) => truncateOutput(redactSecrets(s.replace(/\n+$/, "")));
|
|
2796
3029
|
DETACHED = { stdio: ["ignore", "pipe", "pipe"], detached: true };
|
|
2797
3030
|
SECRET_ENV_RE = /(_API_KEY|_TOKEN|_SECRET|_PASSWORD|_PRIVATE_KEY|^AWS_|^GITHUB_TOKEN$|^OPENAI_|^ANTHROPIC_|^GOOGLE_|^GEMINI_|^GROQ_|^NPM_TOKEN$)/i;
|
|
@@ -2939,9 +3172,9 @@ var init_tools_shell = __esm({
|
|
|
2939
3172
|
|
|
2940
3173
|
// cli/cli.ts
|
|
2941
3174
|
import { createInterface } from "readline/promises";
|
|
2942
|
-
import { existsSync as existsSync9, readFileSync as
|
|
3175
|
+
import { existsSync as existsSync9, readFileSync as readFileSync10, appendFileSync, mkdirSync as mkdirSync12, writeFileSync as writeFileSync10, readdirSync as readdirSync4, statSync as statSync7, unlinkSync as unlinkSync5 } from "fs";
|
|
2943
3176
|
import { homedir as homedir10 } from "os";
|
|
2944
|
-
import { spawnSync as
|
|
3177
|
+
import { spawnSync as spawnSync7 } from "child_process";
|
|
2945
3178
|
|
|
2946
3179
|
// cli/clipboard.ts
|
|
2947
3180
|
import { execFileSync } from "child_process";
|
|
@@ -3008,7 +3241,7 @@ function copyTextToClipboard(text, platform2 = process.platform) {
|
|
|
3008
3241
|
}
|
|
3009
3242
|
|
|
3010
3243
|
// cli/cli.ts
|
|
3011
|
-
import { join as
|
|
3244
|
+
import { join as join15, resolve as resolve4, basename as basename2, extname, dirname as dirname5 } from "path";
|
|
3012
3245
|
import { AIClient, DecideClient, listModels, listProviders, getProviderFromModel, getModelInfo, resolveModel, isModelSupported, disposeCursorSessions, disposeClaudeCodeSessions } from "ai.libx.js";
|
|
3013
3246
|
|
|
3014
3247
|
// src/ccBridge.ts
|
|
@@ -3589,8 +3822,8 @@ ${sections.join("\n\n---\n\n")}`;
|
|
|
3589
3822
|
|
|
3590
3823
|
// src/subagent.ts
|
|
3591
3824
|
init_tools();
|
|
3592
|
-
import { mkdirSync as mkdirSync2, writeFileSync as
|
|
3593
|
-
import { join as
|
|
3825
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
3826
|
+
import { join as join5 } from "path";
|
|
3594
3827
|
|
|
3595
3828
|
// src/models.ts
|
|
3596
3829
|
var MODEL_ALIASES = {
|
|
@@ -3767,7 +4000,7 @@ init_OverlayFilesystem();
|
|
|
3767
4000
|
init_worktree();
|
|
3768
4001
|
init_NodeDiskFilesystem();
|
|
3769
4002
|
init_logging();
|
|
3770
|
-
var
|
|
4003
|
+
var log7 = forComponent("subagent");
|
|
3771
4004
|
async function boundedPool(items, limit, fn) {
|
|
3772
4005
|
const out = new Array(items.length);
|
|
3773
4006
|
let next = 0;
|
|
@@ -3839,17 +4072,17 @@ function traceFileFor(dir, label, depth) {
|
|
|
3839
4072
|
try {
|
|
3840
4073
|
mkdirSync2(dir, { recursive: true });
|
|
3841
4074
|
const id = `${Date.now().toString(36)}-${(traceSeq++).toString(36)}`;
|
|
3842
|
-
return
|
|
4075
|
+
return join5(dir, `${id}-d${depth}-${sanitizeSlug(label) || "sub"}.json`);
|
|
3843
4076
|
} catch (e) {
|
|
3844
|
-
|
|
4077
|
+
log7.warn(`subagent trace dir unavailable (${dir}) \u2014 running without a trace: ${e instanceof Error ? e.message : String(e)}`);
|
|
3845
4078
|
return void 0;
|
|
3846
4079
|
}
|
|
3847
4080
|
}
|
|
3848
4081
|
function flushTrace(file, meta, messages, status) {
|
|
3849
4082
|
try {
|
|
3850
|
-
|
|
4083
|
+
writeFileSync3(file, JSON.stringify({ ...meta, status, updated: Date.now(), messages }, null, 2));
|
|
3851
4084
|
} catch (e) {
|
|
3852
|
-
|
|
4085
|
+
log7.warn(`subagent trace flush failed (${file}): ${e instanceof Error ? e.message : String(e)}`);
|
|
3853
4086
|
}
|
|
3854
4087
|
}
|
|
3855
4088
|
function installTrace(child, file, meta) {
|
|
@@ -3890,7 +4123,7 @@ async function runChildTraced(args) {
|
|
|
3890
4123
|
if (file) flushTrace(file, meta, child.transcript, wc.timedOut() ? "deadline" : res.finishReason);
|
|
3891
4124
|
let result = res.finishReason === "stop" ? res.text || `(child '${label}' finished with no summary; finishReason=${res.finishReason})` : abortHint(label, res, file);
|
|
3892
4125
|
if (wc.timedOut()) {
|
|
3893
|
-
|
|
4126
|
+
log7.warn(`sub-task '${label}' hit its ${deadlineMs}ms wall-clock deadline (steps=${res.steps})`);
|
|
3894
4127
|
result += deadlineNote(deadlineMs);
|
|
3895
4128
|
}
|
|
3896
4129
|
await opts.hooks?.onSubagentStop?.(result, { label, agentType });
|
|
@@ -4219,7 +4452,7 @@ function reasoningToChatFragment(model, effort) {
|
|
|
4219
4452
|
}
|
|
4220
4453
|
|
|
4221
4454
|
// src/Agent.ts
|
|
4222
|
-
var
|
|
4455
|
+
var log8 = forComponent("Agent");
|
|
4223
4456
|
function envInt(key, fallback) {
|
|
4224
4457
|
const n = typeof process !== "undefined" ? Number(process.env?.[key]) : NaN;
|
|
4225
4458
|
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
@@ -4300,6 +4533,9 @@ var AgentOptions = class {
|
|
|
4300
4533
|
* `maxTokens`: that is the cumulative SPEND kill-switch, and a spend budget must never be able to
|
|
4301
4534
|
* drive destructive compaction (`--max-tokens 50000` would otherwise shred the transcript). */
|
|
4302
4535
|
contextWindow = 0;
|
|
4536
|
+
/** Does a model take image input? true/false from the host's model catalog; undefined = unknown (images are tried,
|
|
4537
|
+
* and turned off for the run when the adapter refuses them). Unset = always try. */
|
|
4538
|
+
imageInput;
|
|
4303
4539
|
/** Pagination ceiling for a SINGLE tool result (bytes). A result over this is cropped to page 1 with
|
|
4304
4540
|
* a marker telling the model it was cropped (refine the query, or page further). Guards against one
|
|
4305
4541
|
* Grep/Read/MCP call blowing the whole context window. 0 = off. Default 60k (~15k tokens). */
|
|
@@ -4599,7 +4835,7 @@ var Agent = class _Agent {
|
|
|
4599
4835
|
const disk = new NodeDiskFilesystem2(process.cwd());
|
|
4600
4836
|
await disk.init();
|
|
4601
4837
|
this.options.fs = new JailedFilesystem2(disk);
|
|
4602
|
-
|
|
4838
|
+
log8.info(`no fs provided \u2014 defaulting to jailed real disk at ${process.cwd()}`);
|
|
4603
4839
|
}
|
|
4604
4840
|
this.buildCtx();
|
|
4605
4841
|
}
|
|
@@ -4744,7 +4980,8 @@ var Agent = class _Agent {
|
|
|
4744
4980
|
const cap = _Agent.contextCap(this.options);
|
|
4745
4981
|
const target = Math.round(cap * _Agent.COMPACT_TARGET);
|
|
4746
4982
|
const kr = this.options.keepRecentImages ?? 1;
|
|
4747
|
-
const
|
|
4983
|
+
const vis2 = this.sendsImages();
|
|
4984
|
+
const tokensBefore = estimateTokens(this.transcript, kr, vis2);
|
|
4748
4985
|
const msgsBefore = this.transcript.length;
|
|
4749
4986
|
let max = Math.max(2, maxMessages);
|
|
4750
4987
|
if (this.transcript.length <= max && tokensBefore <= target)
|
|
@@ -4752,18 +4989,18 @@ var Agent = class _Agent {
|
|
|
4752
4989
|
void this.activeHooks?.onPreCompact?.(this.transcript);
|
|
4753
4990
|
let out = this.transcript;
|
|
4754
4991
|
if (out.length > max) out = compact(out, max, focus);
|
|
4755
|
-
if (estimateTokens(out, kr) > target) out = elideOversizedImages(out, target, true, kr);
|
|
4756
|
-
if (estimateTokens(out, kr) > target) out = capOversizedBodies(out, target, kr);
|
|
4757
|
-
if (estimateTokens(out, kr) > target) out = elideOversizedImages(out, target, false, kr);
|
|
4758
|
-
while (estimateTokens(out, kr) > target && max > 2 && out.length > 2) {
|
|
4992
|
+
if (estimateTokens(out, kr, vis2) > target) out = elideOversizedImages(out, target, true, kr);
|
|
4993
|
+
if (estimateTokens(out, kr, vis2) > target) out = capOversizedBodies(out, target, kr);
|
|
4994
|
+
if (estimateTokens(out, kr, vis2) > target) out = elideOversizedImages(out, target, false, kr);
|
|
4995
|
+
while (estimateTokens(out, kr, vis2) > target && max > 2 && out.length > 2) {
|
|
4759
4996
|
max = Math.max(2, Math.floor(max / 2));
|
|
4760
4997
|
out = capOversizedBodies(compact(out, max, focus), target, kr);
|
|
4761
4998
|
}
|
|
4762
|
-
if (estimateTokens(out, kr) > target) out = fitTokenBudget(out, target, kr);
|
|
4999
|
+
if (estimateTokens(out, kr, vis2) > target) out = fitTokenBudget(out, target, kr);
|
|
4763
5000
|
out = dropOrphanToolResults(out);
|
|
4764
5001
|
this.transcript = out;
|
|
4765
5002
|
this.releaseStaleImages();
|
|
4766
|
-
return { tokensBefore, tokensAfter: estimateTokens(this.transcript, kr), target, cap, removed: msgsBefore - this.transcript.length };
|
|
5003
|
+
return { tokensBefore, tokensAfter: estimateTokens(this.transcript, kr, vis2), target, cap, removed: msgsBefore - this.transcript.length };
|
|
4767
5004
|
}
|
|
4768
5005
|
/** Drop inline base64 images from older messages so Bun/JSC can reclaim the heap (see contentBytes). */
|
|
4769
5006
|
releaseStaleImages(keepRecent = 1) {
|
|
@@ -4787,7 +5024,7 @@ var Agent = class _Agent {
|
|
|
4787
5024
|
let closureNudged = false;
|
|
4788
5025
|
let lastProgressAt = start;
|
|
4789
5026
|
const kill = (finishReason) => {
|
|
4790
|
-
|
|
5027
|
+
log8.warn(`kill-switch: ${finishReason} (steps=${steps}, tokens=${usage.totalTokens}, budgetTokens=${Math.round(usage.totalTokens - 0.9 * usage.cacheReadTokens)}, ms=${Date.now() - start - this.parkedMs}${finishReason === "timeout" ? `, idle=${Date.now() - lastProgressAt - this.parkedMs}ms` : ""}${this.parkedMs ? ` +${this.parkedMs} parked` : ""})`);
|
|
4791
5028
|
this.jobs.killAll(`run ended: ${finishReason}`, true);
|
|
4792
5029
|
const claims = this.survivingClaims();
|
|
4793
5030
|
return { text: lastAssistantText(this.transcript), steps, finishReason, messages: this.transcript, usage, usageEstimated, ...claims.length ? { fabricatedToolClaims: claims } : {} };
|
|
@@ -4802,7 +5039,7 @@ var Agent = class _Agent {
|
|
|
4802
5039
|
steps++;
|
|
4803
5040
|
this.options.host?.notify?.({ kind: "turn_start", message: `step ${steps}` });
|
|
4804
5041
|
let res;
|
|
4805
|
-
|
|
5042
|
+
let sent = this.trimContext();
|
|
4806
5043
|
const frag = reasoningToChatFragment(o.model, o.reasoning);
|
|
4807
5044
|
const sentTools = o.model.startsWith("claude-code/") ? [] : wireTools;
|
|
4808
5045
|
const isCursorWithTools = o.model.startsWith("cursor/") && wireTools.length > 0;
|
|
@@ -4813,9 +5050,9 @@ var Agent = class _Agent {
|
|
|
4813
5050
|
this.nativeHoldMs = o.nativeHoldMs;
|
|
4814
5051
|
if (isCursorWithTools) {
|
|
4815
5052
|
if (typeof hostToolTimeoutMs === "number" && effectiveToolTimeoutMs !== hostToolTimeoutMs) {
|
|
4816
|
-
|
|
5053
|
+
log8.warn(`providerOptions.toolTimeoutMs=${hostToolTimeoutMs}ms is below what the registered tools need \u2014 RAISED to ${effectiveToolTimeoutMs}ms. ${toolTimeoutViolations(this.activeTools, hostToolTimeoutMs).length} tool(s) could outlive it, and the transport would have destroyed their results. To lower it, lower the tools' own maxDurationMs (e.g. the Shell tool's maxTimeoutMs) instead.`);
|
|
4817
5054
|
}
|
|
4818
|
-
for (const v of toolTimeoutViolations(this.activeTools, effectiveToolTimeoutMs))
|
|
5055
|
+
for (const v of toolTimeoutViolations(this.activeTools, effectiveToolTimeoutMs)) log8.warn(v);
|
|
4819
5056
|
}
|
|
4820
5057
|
const hostExec = async (name, args) => {
|
|
4821
5058
|
const tc = { id: `host-${Date.now()}`, type: "function", function: { name, arguments: JSON.stringify(args) } };
|
|
@@ -4874,12 +5111,20 @@ var Agent = class _Agent {
|
|
|
4874
5111
|
res = r;
|
|
4875
5112
|
}
|
|
4876
5113
|
if (steps === 1 && this.isEmptyStop(res) && !this.pendingInjections.length) {
|
|
4877
|
-
const e = new Error(
|
|
5114
|
+
const e = new Error(`model returned an empty turn (finishReason=${res.finishReason ?? "n/a"}, no text, no tools)`);
|
|
4878
5115
|
e.code = "empty";
|
|
5116
|
+
if (res.finishReason) e.providerFinishReason = res.finishReason;
|
|
4879
5117
|
throw e;
|
|
4880
5118
|
}
|
|
4881
5119
|
break;
|
|
4882
5120
|
} catch (err2) {
|
|
5121
|
+
if (!this.imagesOff && /does not support .*input/i.test(String(err2?.message ?? "")) && sent.some((m) => Array.isArray(m.content) && m.role === "tool")) {
|
|
5122
|
+
this.imagesOff = true;
|
|
5123
|
+
log8.warn(`model refused image input (${err2?.message}) \u2014 resending with image Reads as text stubs`);
|
|
5124
|
+
sent = this.trimContext();
|
|
5125
|
+
attempt--;
|
|
5126
|
+
continue;
|
|
5127
|
+
}
|
|
4883
5128
|
const sc = err2?.statusCode;
|
|
4884
5129
|
const serverSide = sc >= 500 || sc === 408 || /Service Unavailable|overloaded|Internal server error|Bad gateway|Gateway time/i.test(String(err2?.message ?? ""));
|
|
4885
5130
|
const network = !sc && /ECONNRESET|ECONNREFUSED|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|EPIPE|socket hang up|fetch failed|network|terminated|UND_ERR/i.test(String(err2?.message ?? err2?.code ?? err2));
|
|
@@ -4887,11 +5132,11 @@ var Agent = class _Agent {
|
|
|
4887
5132
|
const empty = err2?.code === "empty" || err2?.code === "cursor_silent";
|
|
4888
5133
|
const maxAttempts = empty ? Math.max(o.emptyRetries, 0) : 2;
|
|
4889
5134
|
const replaysTool = stalled && this.toolRanThisAttempt;
|
|
4890
|
-
if (replaysTool)
|
|
5135
|
+
if (replaysTool) log8.warn("model stall after tool(s) already ran this step \u2014 not retrying (a retry would replay them)");
|
|
4891
5136
|
const transient = !o.signal?.aborted && !isAbortError(err2) && !replaysTool && attempt < maxAttempts && (network || serverSide || stalled || empty);
|
|
4892
5137
|
if (!transient) throw err2;
|
|
4893
5138
|
const waitMs = (empty ? o.emptyRetryBaseMs : 1e3) * (attempt + 1);
|
|
4894
|
-
|
|
5139
|
+
log8.warn(`${empty ? "empty model turn" : stalled ? "model stall" : "network drop"} mid-step (${err2?.message ?? err2}) \u2014 retrying in ${waitMs}ms`);
|
|
4895
5140
|
o.host?.notify?.({ kind: "retry", message: `${empty ? "model returned nothing" : stalled ? "model stalled" : "connection dropped"} \u2014 retrying step (#${attempt + 1})` });
|
|
4896
5141
|
await new Promise((r) => setTimeout(r, waitMs));
|
|
4897
5142
|
}
|
|
@@ -4899,13 +5144,14 @@ var Agent = class _Agent {
|
|
|
4899
5144
|
} catch (err2) {
|
|
4900
5145
|
if (err2?.code === "budget") return kill("budget");
|
|
4901
5146
|
if (err2?.code === "stall") {
|
|
4902
|
-
|
|
4903
|
-
return { ...kill("timeout"), error: err2 };
|
|
5147
|
+
log8.warn(`model stalled out after retries: ${err2?.message ?? err2}`);
|
|
5148
|
+
return { ...kill("timeout"), providerFinishReason: "none (stalled)", error: err2 };
|
|
4904
5149
|
}
|
|
4905
5150
|
if (o.signal?.aborted || isAbortError(err2)) return kill("aborted");
|
|
4906
5151
|
if (err2?.code === "empty" || err2?.code === "cursor_silent") {
|
|
4907
|
-
|
|
4908
|
-
|
|
5152
|
+
log8.warn(`model returned empty/silent turns after retries \u2014 surfacing as failed`);
|
|
5153
|
+
const pfr = err2?.providerFinishReason;
|
|
5154
|
+
return { text: "", steps, finishReason: "empty", ...pfr ? { providerFinishReason: pfr } : {}, messages: this.transcript, usage, usageEstimated, error: err2 };
|
|
4909
5155
|
}
|
|
4910
5156
|
const body = err2?.body ?? err2?.response?.data ?? err2?.error;
|
|
4911
5157
|
let bodyStr;
|
|
@@ -4916,13 +5162,13 @@ var Agent = class _Agent {
|
|
|
4916
5162
|
}
|
|
4917
5163
|
if (bodyStr && err2 instanceof Error && !err2.message.includes(bodyStr)) err2.detail = bodyStr;
|
|
4918
5164
|
const errLog = err2 instanceof Error ? { message: err2.message, ...err2.detail ? { detail: err2.detail } : {}, stack: err2.stack } : err2;
|
|
4919
|
-
|
|
5165
|
+
log8.error(`chat() failed: ${err2?.message ?? err2}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, errLog);
|
|
4920
5166
|
return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err2 };
|
|
4921
5167
|
}
|
|
4922
5168
|
if (o.signal?.aborted) return kill("aborted");
|
|
4923
5169
|
if (res.finishReason === "error" && !res.toolCalls?.length) {
|
|
4924
5170
|
const err2 = new Error(contentText(res.content ?? "").trim() || "provider error");
|
|
4925
|
-
|
|
5171
|
+
log8.error(`chat() failed: ${err2.message}`);
|
|
4926
5172
|
return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err2 };
|
|
4927
5173
|
}
|
|
4928
5174
|
if (!res.usage) {
|
|
@@ -4980,21 +5226,21 @@ var Agent = class _Agent {
|
|
|
4980
5226
|
if (emptyTurn && o.failEmptyTurns) {
|
|
4981
5227
|
if (!emptyNudged && canNudge()) {
|
|
4982
5228
|
emptyNudged = true;
|
|
4983
|
-
|
|
5229
|
+
log8.warn(`empty model turn after tool work (step ${steps}, provider finishReason=${res.finishReason ?? "n/a"}) \u2014 nudging once`);
|
|
4984
5230
|
this.transcript.push({ role: "user", content: "Continue \u2014 you returned nothing. Answer the user now using the tool results above, or keep working if you are not done." });
|
|
4985
5231
|
continue;
|
|
4986
5232
|
}
|
|
4987
|
-
|
|
5233
|
+
log8.warn(`model returned empty turns after tool work \u2014 surfacing as failed`);
|
|
4988
5234
|
const e = new Error("empty_response: model returned an empty turn (no text, no tools) after a re-prompt");
|
|
4989
5235
|
e.code = "empty";
|
|
4990
5236
|
return { text: "", steps, finishReason: "empty", ...res.finishReason ? { providerFinishReason: res.finishReason } : {}, messages: this.transcript, usage, usageEstimated, error: e };
|
|
4991
5237
|
}
|
|
4992
5238
|
const fabricated = o.catchFabricatedToolUse && !closureNudged ? this.fabricatedToolNames(contentText(res.content ?? "")) : [];
|
|
4993
5239
|
for (const n of fabricated) this.fabricatedClaims.add(n);
|
|
4994
|
-
if (fabricated.length && !canNudge())
|
|
5240
|
+
if (fabricated.length && !canNudge()) log8.warn(`fabricated tool use: text names ${fabricated.join(", ")} but no step/budget left to demand the call \u2014 finishing with the existing answer`);
|
|
4995
5241
|
if (fabricated.length && canNudge()) {
|
|
4996
5242
|
closureNudged = true;
|
|
4997
|
-
|
|
5243
|
+
log8.warn(`fabricated tool use: text names ${fabricated.join(", ")} but the run invoked ${this.invokedTools.size ? [...this.invokedTools].join(", ") : "nothing"} \u2014 demanding the real call`);
|
|
4998
5244
|
this.transcript.push({
|
|
4999
5245
|
role: "user",
|
|
5000
5246
|
content: `You referred to ${fabricated.map((n) => `\`${n}\``).join(", ")} in your reply, but you never actually called ${fabricated.length > 1 ? "those tools" : "that tool"} \u2014 no tool call was made, so any result you reported for ${fabricated.length > 1 ? "them" : "it"} was invented. Either call ${fabricated.length > 1 ? "them" : "it"} for real now, or answer without ${fabricated.length > 1 ? "them" : "it"} and do NOT claim any outcome \u2014 including a failure or a timeout \u2014 for a call you did not make.`
|
|
@@ -5003,14 +5249,14 @@ var Agent = class _Agent {
|
|
|
5003
5249
|
}
|
|
5004
5250
|
if (o.closeDelegatedTurns && res.endedWithoutClosing && !closureNudged && canNudge()) {
|
|
5005
5251
|
closureNudged = true;
|
|
5006
|
-
|
|
5252
|
+
log8.verbose("delegated turn went silent after a tool \u2014 nudging a closing answer");
|
|
5007
5253
|
this.transcript.push({
|
|
5008
5254
|
role: "user",
|
|
5009
5255
|
content: "You ran tools but did not reply. Answer the user now using the tool results above; if a tool failed, say so plainly. Do NOT re-run those tools, do NOT narrate what you did, and do NOT ask for permission to continue."
|
|
5010
5256
|
});
|
|
5011
5257
|
continue;
|
|
5012
5258
|
}
|
|
5013
|
-
|
|
5259
|
+
log8.verbose(`completed in ${steps} step(s)`);
|
|
5014
5260
|
const drained = await this.jobs.drain(o.signal);
|
|
5015
5261
|
if (o.signal?.aborted) return kill("aborted");
|
|
5016
5262
|
if (drained.length) lastProgressAt = Date.now();
|
|
@@ -5044,7 +5290,7 @@ var Agent = class _Agent {
|
|
|
5044
5290
|
if (this.ctx.needsInput) {
|
|
5045
5291
|
const question = this.ctx.needsInput;
|
|
5046
5292
|
this.ctx.needsInput = void 0;
|
|
5047
|
-
|
|
5293
|
+
log8.warn(`needs_input: ${question.question} (steps=${steps})`);
|
|
5048
5294
|
await this.jobs.drain();
|
|
5049
5295
|
const text = [
|
|
5050
5296
|
`OPEN QUESTION (run paused \u2014 no human was reachable): ${question.question}`,
|
|
@@ -5086,12 +5332,12 @@ var Agent = class _Agent {
|
|
|
5086
5332
|
if (this.toolHoldActive()) {
|
|
5087
5333
|
const now4 = Date.now();
|
|
5088
5334
|
const who = this.jobs.holding().map((j) => `${j.kind} "${j.label}" (${Math.round((now4 - j.startedAt) / 1e3)}s, quiet ${Math.round((now4 - j.lastActivityAt) / 1e3)}s)`).join(", ");
|
|
5089
|
-
|
|
5335
|
+
log8.warn(`stall watchdog: no stream output for ${stallMs}ms \u2014 held by ${who}`);
|
|
5090
5336
|
this.options.host?.notify?.({ kind: "tool_hold", message: `waiting on ${who}` });
|
|
5091
5337
|
return poke();
|
|
5092
5338
|
}
|
|
5093
5339
|
const inFlight = this.jobs.list().filter((j) => j.status === "running");
|
|
5094
|
-
|
|
5340
|
+
log8.warn(`stall watchdog: no stream output for ${stallMs}ms and no tool holding the stream \u2014 aborting the request` + (inFlight.length ? ` (${inFlight.length} detached job(s) running, not awaited by the stream: ${inFlight.map((j) => `${j.id}/${j.kind}`).join(", ")})` : ""));
|
|
5095
5341
|
fired = true;
|
|
5096
5342
|
ac.abort(stallError());
|
|
5097
5343
|
}, stallMs);
|
|
@@ -5261,13 +5507,13 @@ var Agent = class _Agent {
|
|
|
5261
5507
|
const decision = await this.park(Promise.resolve(hooks?.preToolUse?.(call, meta)));
|
|
5262
5508
|
if (decision?.block) {
|
|
5263
5509
|
const blocked = `Blocked by hook: ${decision.reason ?? "no reason given"}`;
|
|
5264
|
-
|
|
5510
|
+
log8.debug(`${tc.function.name} -> ${blocked}`);
|
|
5265
5511
|
await hooks?.postToolUse?.(call, blocked, meta);
|
|
5266
5512
|
return blocked;
|
|
5267
5513
|
}
|
|
5268
5514
|
this.options.host?.notify?.({ kind: "tool_use", id: tc.id ?? "", name: tc.function.name, input: args });
|
|
5269
5515
|
if (earlyError) {
|
|
5270
|
-
|
|
5516
|
+
log8.debug(`${tc.function.name} -> ${earlyError}`);
|
|
5271
5517
|
await hooks?.postToolUse?.(call, earlyError, meta);
|
|
5272
5518
|
this.options.host?.notify?.({ kind: "tool_result", id: tc.id ?? "", output: earlyError, isError: true });
|
|
5273
5519
|
return earlyError;
|
|
@@ -5276,12 +5522,12 @@ var Agent = class _Agent {
|
|
|
5276
5522
|
let images;
|
|
5277
5523
|
let threw = false;
|
|
5278
5524
|
try {
|
|
5279
|
-
|
|
5525
|
+
log8.debug(`${tc.function.name}(${tc.function.arguments})`);
|
|
5280
5526
|
this.ctx.emit = hooks?.onToolOutput ? (chunk) => {
|
|
5281
5527
|
try {
|
|
5282
5528
|
hooks.onToolOutput(call, chunk, meta);
|
|
5283
5529
|
} catch (e) {
|
|
5284
|
-
|
|
5530
|
+
log8.debug(`onToolOutput hook error: ${e}`);
|
|
5285
5531
|
}
|
|
5286
5532
|
} : void 0;
|
|
5287
5533
|
const raw = await tool.run(args, this.ctx);
|
|
@@ -5293,7 +5539,7 @@ var Agent = class _Agent {
|
|
|
5293
5539
|
}
|
|
5294
5540
|
} catch (e) {
|
|
5295
5541
|
const msg = e instanceof Error ? e.message : String(e);
|
|
5296
|
-
|
|
5542
|
+
log8.debug(`${tc.function.name} -> error: ${msg}`);
|
|
5297
5543
|
result = `Error: ${msg}`;
|
|
5298
5544
|
threw = true;
|
|
5299
5545
|
} finally {
|
|
@@ -5353,6 +5599,13 @@ ${out}`;
|
|
|
5353
5599
|
* 3. token-aware backstop (`maxContextTokens`) — drop oldest messages until under an estimated token ceiling.
|
|
5354
5600
|
* With every knob off, returns the transcript unchanged (same reference).
|
|
5355
5601
|
*/
|
|
5602
|
+
/** Set when the model refused image parts this run: tool image Reads go as text stubs from then on. */
|
|
5603
|
+
imagesOff = false;
|
|
5604
|
+
/** Send tool image Reads as images? Known models by their catalog flag; unknown ones try (and latch off on refusal). */
|
|
5605
|
+
sendsImages() {
|
|
5606
|
+
if (this.imagesOff) return false;
|
|
5607
|
+
return this.options.imageInput?.(this.options.model) ?? true;
|
|
5608
|
+
}
|
|
5356
5609
|
trimContext() {
|
|
5357
5610
|
const o = this.options;
|
|
5358
5611
|
const m = this.transcript;
|
|
@@ -5362,17 +5615,19 @@ ${out}`;
|
|
|
5362
5615
|
const browserKeep = this.browserActive ? o.browserTrim?.keepOutputs : void 0;
|
|
5363
5616
|
const keep = browserKeep != null ? o.keepToolOutputs ? Math.min(o.keepToolOutputs, browserKeep) : browserKeep : o.keepToolOutputs;
|
|
5364
5617
|
if (keep) out = stubOldToolResults(out ?? m, keep);
|
|
5365
|
-
out = expandImagesForSend(dropOrphanToolResults(out ?? m), o.keepRecentImages ?? 1);
|
|
5618
|
+
out = expandImagesForSend(dropOrphanToolResults(out ?? m), o.keepRecentImages ?? 1, void 0, this.sendsImages());
|
|
5366
5619
|
if (o.maxContextTokens) {
|
|
5367
5620
|
const pre = out.length;
|
|
5368
|
-
out = fitTokenBudget(out, o.maxContextTokens, o.keepRecentImages ?? 1);
|
|
5621
|
+
out = fitTokenBudget(out, o.maxContextTokens, o.keepRecentImages ?? 1, this.sendsImages());
|
|
5369
5622
|
const dropped = pre - out.length;
|
|
5370
5623
|
if (dropped > 0 && dropped !== this.lastTrimNotified) {
|
|
5371
5624
|
this.lastTrimNotified = dropped;
|
|
5372
5625
|
o.host?.notify?.({ kind: "compaction", message: `context full \u2014 auto-trimmed ${dropped} oldest message(s) to fit ~${Math.round(o.maxContextTokens / 1e3)}k tokens` });
|
|
5373
5626
|
}
|
|
5374
5627
|
}
|
|
5375
|
-
|
|
5628
|
+
out = dropOrphanToolResults(out);
|
|
5629
|
+
if (/^google\//.test(o.model ?? "")) out = hoistToolImages(out);
|
|
5630
|
+
return scrubBase64(out);
|
|
5376
5631
|
}
|
|
5377
5632
|
};
|
|
5378
5633
|
function dropOldest(m, max) {
|
|
@@ -5381,8 +5636,8 @@ function dropOldest(m, max) {
|
|
|
5381
5636
|
}
|
|
5382
5637
|
var ANTHROPIC_WINDOW_WITHOUT_1M_BETA = 2e5;
|
|
5383
5638
|
var isAnthropicModel = (m) => !!m && (m.startsWith("anthropic/") || /(^|\/)claude-/.test(m));
|
|
5384
|
-
function estimateTokens(m, keepRecentImages = 1) {
|
|
5385
|
-
return Math.ceil(sendBytes(m, keepRecentImages) / 4);
|
|
5639
|
+
function estimateTokens(m, keepRecentImages = 1, vision = true) {
|
|
5640
|
+
return Math.ceil(sendBytes(m, keepRecentImages, vision) / 4);
|
|
5386
5641
|
}
|
|
5387
5642
|
var TRANSPORT_TOOL_TIMEOUT_FLOOR_MS = 18e5;
|
|
5388
5643
|
var isMcpTool = (t) => t.name.startsWith("mcp__") || t.name === "McpCall";
|
|
@@ -5551,8 +5806,8 @@ function dropOrphanToolResults(messages) {
|
|
|
5551
5806
|
const ok = (m) => m.role !== "tool" || ids.has(m.tool_call_id ?? "");
|
|
5552
5807
|
return messages.every(ok) ? messages : messages.filter(ok);
|
|
5553
5808
|
}
|
|
5554
|
-
function fitTokenBudget(messages, maxTokens, keepRecentImages = 1) {
|
|
5555
|
-
const per = sendBytesPer(messages, keepRecentImages).map((b) => Math.ceil(b / 4));
|
|
5809
|
+
function fitTokenBudget(messages, maxTokens, keepRecentImages = 1, vision = true) {
|
|
5810
|
+
const per = sendBytesPer(messages, keepRecentImages, vision).map((b) => Math.ceil(b / 4));
|
|
5556
5811
|
let total = per.reduce((a, b) => a + b, 0);
|
|
5557
5812
|
if (total <= maxTokens) return messages;
|
|
5558
5813
|
const head = messages[0]?.role === "system" ? [messages[0]] : [];
|
|
@@ -5561,7 +5816,7 @@ function fitTokenBudget(messages, maxTokens, keepRecentImages = 1) {
|
|
|
5561
5816
|
const ids = callIdSet(messages.slice(from));
|
|
5562
5817
|
while (from < messages.length && messages[from].role === "tool" && !ids.has(messages[from].tool_call_id ?? "")) total -= per[from++];
|
|
5563
5818
|
if (total > maxTokens)
|
|
5564
|
-
|
|
5819
|
+
log8.warn(`context ~${total} tok still over maxContextTokens=${maxTokens} after trimming (system head can't be dropped)`);
|
|
5565
5820
|
return [...head, ...messages.slice(from)];
|
|
5566
5821
|
}
|
|
5567
5822
|
function compact(m, max, focus) {
|
|
@@ -6139,7 +6394,7 @@ init_tools_web();
|
|
|
6139
6394
|
init_tools();
|
|
6140
6395
|
init_tools_structured();
|
|
6141
6396
|
init_logging();
|
|
6142
|
-
var
|
|
6397
|
+
var log9 = forComponent("scratch");
|
|
6143
6398
|
var SCRATCH_DIR = "/scratch";
|
|
6144
6399
|
function shortArgs(args) {
|
|
6145
6400
|
try {
|
|
@@ -6179,7 +6434,7 @@ var Scratch = class {
|
|
|
6179
6434
|
await (this.dirReady ??= mkdirp(this.fs, dir));
|
|
6180
6435
|
await this.fs.writeFile(path, header + raw);
|
|
6181
6436
|
} catch (e) {
|
|
6182
|
-
|
|
6437
|
+
log9.debug("scratch write failed; returning raw", e);
|
|
6183
6438
|
return raw;
|
|
6184
6439
|
}
|
|
6185
6440
|
const preview = raw.slice(0, previewChars).replace(/\s+/g, " ").trim();
|
|
@@ -6209,7 +6464,7 @@ To pull a specific detail, Grep/Read ${path}, or call Ask({ question: "\u2026",
|
|
|
6209
6464
|
await (this.dirReady ??= mkdirp(this.fs, dir));
|
|
6210
6465
|
await this.fs.writeFile(path, header + full);
|
|
6211
6466
|
} catch (e) {
|
|
6212
|
-
|
|
6467
|
+
log9.debug("scratch spill failed; cropping lossy", e);
|
|
6213
6468
|
return full.slice(0, pageBytes) + `
|
|
6214
6469
|
|
|
6215
6470
|
[output cropped to ${pageBytes} of ${full.length} bytes; full output unavailable (scratch write failed) \u2014 refine your query]`;
|
|
@@ -6255,7 +6510,7 @@ Question: ${q2}`);
|
|
|
6255
6510
|
const answer = (res.text ?? "").trim();
|
|
6256
6511
|
return answer || "(no answer found in scratch)";
|
|
6257
6512
|
} catch (e) {
|
|
6258
|
-
|
|
6513
|
+
log9.debug("Ask peek failed", e);
|
|
6259
6514
|
return `Error querying scratch: ${e?.message ?? e}`;
|
|
6260
6515
|
}
|
|
6261
6516
|
}
|
|
@@ -6264,7 +6519,7 @@ Question: ${q2}`);
|
|
|
6264
6519
|
|
|
6265
6520
|
// src/lessons.ts
|
|
6266
6521
|
init_logging();
|
|
6267
|
-
var
|
|
6522
|
+
var log10 = forComponent("Lessons");
|
|
6268
6523
|
var LessonOptionsDefaults = class {
|
|
6269
6524
|
minRepeats = 2;
|
|
6270
6525
|
};
|
|
@@ -6289,8 +6544,8 @@ function lessonCapture(options) {
|
|
|
6289
6544
|
counts.set(lesson.slug, n);
|
|
6290
6545
|
if (n < o.minRepeats) return;
|
|
6291
6546
|
written.add(lesson.slug);
|
|
6292
|
-
await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) =>
|
|
6293
|
-
|
|
6547
|
+
await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) => log10.warn(`could not persist ${lesson.slug}: ${e?.message ?? e}`));
|
|
6548
|
+
log10.debug(`captured lesson ${lesson.slug} (recurred ${n}\xD7)`);
|
|
6294
6549
|
}
|
|
6295
6550
|
};
|
|
6296
6551
|
}
|
|
@@ -6298,7 +6553,7 @@ function lessonCapture(options) {
|
|
|
6298
6553
|
// src/reflect.ts
|
|
6299
6554
|
init_llm();
|
|
6300
6555
|
init_logging();
|
|
6301
|
-
var
|
|
6556
|
+
var log11 = forComponent("Reflect");
|
|
6302
6557
|
async function reflectOnRun(o) {
|
|
6303
6558
|
const digest = digestRun(o.result.messages, o.maxDigestChars ?? 6e3);
|
|
6304
6559
|
if (!digest.trim()) return null;
|
|
@@ -6314,7 +6569,7 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
|
|
|
6314
6569
|
const r = await o.ai.chat({ model: o.model, messages: [{ role: "user", content: prompt }], stream: false });
|
|
6315
6570
|
text = r?.content ?? "";
|
|
6316
6571
|
} catch (e) {
|
|
6317
|
-
|
|
6572
|
+
log11.warn(`reflection call failed: ${e?.message ?? e}`);
|
|
6318
6573
|
return null;
|
|
6319
6574
|
}
|
|
6320
6575
|
const m = text.match(/LESSON:\s*(.+)/i);
|
|
@@ -6324,10 +6579,10 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
|
|
|
6324
6579
|
try {
|
|
6325
6580
|
await writeFact(o.fs, o.dir, slug2, lesson);
|
|
6326
6581
|
} catch (e) {
|
|
6327
|
-
|
|
6582
|
+
log11.warn(`could not persist lesson: ${e?.message ?? e}`);
|
|
6328
6583
|
return null;
|
|
6329
6584
|
}
|
|
6330
|
-
|
|
6585
|
+
log11.debug(`reflection persisted ${slug2}`);
|
|
6331
6586
|
return slug2;
|
|
6332
6587
|
}
|
|
6333
6588
|
function digestRun(messages, maxChars) {
|
|
@@ -6376,7 +6631,7 @@ function forComponent2(name) {
|
|
|
6376
6631
|
}
|
|
6377
6632
|
|
|
6378
6633
|
// node_modules/@bod.ee/voice/src/core/emotion.ts
|
|
6379
|
-
var
|
|
6634
|
+
var log12 = forComponent2("Emotion");
|
|
6380
6635
|
var EMOTIONS = [
|
|
6381
6636
|
// primary (best results)
|
|
6382
6637
|
"neutral",
|
|
@@ -6508,7 +6763,7 @@ function renderEmotions(text, opts = { show: true }) {
|
|
|
6508
6763
|
speech += cartesiaTag(tag);
|
|
6509
6764
|
if (opts.show) display += m[0];
|
|
6510
6765
|
} else {
|
|
6511
|
-
|
|
6766
|
+
log12.debug(`dropping unknown emotion tag ${m[0]}`);
|
|
6512
6767
|
}
|
|
6513
6768
|
last = m.index + m[0].length;
|
|
6514
6769
|
}
|
|
@@ -6550,7 +6805,7 @@ var EmotionStream = class {
|
|
|
6550
6805
|
if (tag) {
|
|
6551
6806
|
this.pending = tag;
|
|
6552
6807
|
if (this.show) display += m[0];
|
|
6553
|
-
} else
|
|
6808
|
+
} else log12.debug(`dropping unknown emotion tag ${m[0]}`);
|
|
6554
6809
|
last = m.index + m[0].length;
|
|
6555
6810
|
}
|
|
6556
6811
|
this.emit(body.slice(last), (s, d, p) => {
|
|
@@ -6697,7 +6952,7 @@ function computeHoldMs(ctx) {
|
|
|
6697
6952
|
}
|
|
6698
6953
|
|
|
6699
6954
|
// node_modules/@bod.ee/voice/src/core/engine.ts
|
|
6700
|
-
var
|
|
6955
|
+
var log13 = forComponent2("VoiceEngine");
|
|
6701
6956
|
var realClock = {
|
|
6702
6957
|
now: () => performance.now(),
|
|
6703
6958
|
setTimeout: (fn, ms) => setTimeout(fn, ms),
|
|
@@ -7085,8 +7340,8 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7085
7340
|
this.tts.warmup?.();
|
|
7086
7341
|
this.setState("listening");
|
|
7087
7342
|
if (this.options.bargeInMode === "aec" && !this.stt.usingAec)
|
|
7088
|
-
|
|
7089
|
-
|
|
7343
|
+
log13.warn("bargeInMode=aec but mic does not report echoCancellation applied \u2014 STT may transcribe the assistant");
|
|
7344
|
+
log13.debug(`voice I/O up (${this.usingAec ? "AEC" : "heuristic echo"} capture, mode=${this.options.bargeInMode}, mic reports aec=${this.stt.usingAec})`);
|
|
7090
7345
|
}
|
|
7091
7346
|
/** The RESOLVED barge-in tier (the single seam every tier branch reads). `bargeInMode` overrides
|
|
7092
7347
|
* the mic's self-report — see the option doc for why the report is not trustworthy on mobile. */
|
|
@@ -7111,7 +7366,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7111
7366
|
this.options.onDiag({ t: this.clock.now(), kind, ...fields });
|
|
7112
7367
|
} catch (e) {
|
|
7113
7368
|
this.diagOn = false;
|
|
7114
|
-
|
|
7369
|
+
log13.debug(`onDiag threw \u2014 diagnostics disabled: ${e instanceof Error ? e.message : e}`);
|
|
7115
7370
|
}
|
|
7116
7371
|
}
|
|
7117
7372
|
idleWaiters = [];
|
|
@@ -7250,7 +7505,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7250
7505
|
}
|
|
7251
7506
|
for (const w of this.words(this.reply)) this.echoWords.add(w);
|
|
7252
7507
|
this.tts.speak(forSpeech(speech), true);
|
|
7253
|
-
if (!this.spokeDeltas && this.turnStartAt)
|
|
7508
|
+
if (!this.spokeDeltas && this.turnStartAt) log13.debug(`ttft: ${Math.round(this.clock.now() - this.turnStartAt)}ms`);
|
|
7254
7509
|
this.spokeDeltas = true;
|
|
7255
7510
|
this.setState("speaking");
|
|
7256
7511
|
if (this.options.revealMode !== "off" && display) {
|
|
@@ -7363,13 +7618,13 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7363
7618
|
this.drainTimer = null;
|
|
7364
7619
|
this.speaking = false;
|
|
7365
7620
|
this.stopWordReveal(true);
|
|
7366
|
-
if (this.turnStartAt)
|
|
7621
|
+
if (this.turnStartAt) log13.debug(`turn: ${Math.round(this.clock.now() - this.turnStartAt)}ms (incl. playback)`);
|
|
7367
7622
|
this.echoUntil = this.clock.now() + 2500;
|
|
7368
7623
|
if (!this.usingAec) this.stt.reset();
|
|
7369
7624
|
try {
|
|
7370
7625
|
this.options.onSpeechEnd();
|
|
7371
7626
|
} catch (e) {
|
|
7372
|
-
|
|
7627
|
+
log13.warn(`onSpeechEnd threw: ${e instanceof Error ? e.message : e}`);
|
|
7373
7628
|
}
|
|
7374
7629
|
this.setState("listening");
|
|
7375
7630
|
if (this.uttQueue.length) this.pumpQueue();
|
|
@@ -7443,7 +7698,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7443
7698
|
if (!host) return pick;
|
|
7444
7699
|
const ctx = { text: this.lastDispatchText, question: this.lastDispatchWasQuestion, barged: this.bargedBeforeDispatch };
|
|
7445
7700
|
const fallback = (e) => {
|
|
7446
|
-
|
|
7701
|
+
log13.warn(`pickAck failed \u2014 using the default pools: ${e instanceof Error ? e.message : e}`);
|
|
7447
7702
|
pick.picked = false;
|
|
7448
7703
|
return void 0;
|
|
7449
7704
|
};
|
|
@@ -7533,7 +7788,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7533
7788
|
this.ackProbe = null;
|
|
7534
7789
|
const queued = this.uttQueue.splice(0);
|
|
7535
7790
|
const droppedQueued = queued.length;
|
|
7536
|
-
if (droppedQueued)
|
|
7791
|
+
if (droppedQueued) log13.info(`barge-in cut ${droppedQueued} queued worker utterance(s) \u2014 handed to the host`);
|
|
7537
7792
|
this.overlapKeptUntil = 0;
|
|
7538
7793
|
if (!this.speaking && !this.drainTimer) {
|
|
7539
7794
|
if (droppedQueued) this.lastInterrupted = { full: "", heard: "", queued, queuedTurn: false };
|
|
@@ -7762,7 +8017,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7762
8017
|
if (flat && this.lastDispatchFlat && this.clock.now() - this.lastDispatchAt < _VoiceEngine.DUP_FINAL_MS && (this.lastDispatchFlat.startsWith(flat) || flat.startsWith(this.lastDispatchFlat))) return;
|
|
7763
8018
|
this.specSpent = true;
|
|
7764
8019
|
this.specText = this.specPartial;
|
|
7765
|
-
|
|
8020
|
+
log13.debug(`speculate: "${this.specText.slice(0, 60)}"`);
|
|
7766
8021
|
this.diag("speculate", { text: this.specText });
|
|
7767
8022
|
this.options.onSpeculate(this.specText);
|
|
7768
8023
|
}, this.options.speculativeMs);
|
|
@@ -7831,7 +8086,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7831
8086
|
};
|
|
7832
8087
|
this.tts.newContext();
|
|
7833
8088
|
this.tts.speak(phrase, false);
|
|
7834
|
-
|
|
8089
|
+
log13.debug(`backchannel: "${phrase}"`);
|
|
7835
8090
|
this.diag("backchannel", { phrase });
|
|
7836
8091
|
this.options.onBackchannel(phrase);
|
|
7837
8092
|
}
|
|
@@ -7864,7 +8119,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7864
8119
|
});
|
|
7865
8120
|
if (idx < 0) return text;
|
|
7866
8121
|
toks.splice(idx, 1);
|
|
7867
|
-
|
|
8122
|
+
log13.verbose(`stripped backchannel echo "${this.lastBcPhrase}" from dispatching final`);
|
|
7868
8123
|
return toks.join(" ");
|
|
7869
8124
|
}
|
|
7870
8125
|
/** Merge a resumed utterance into the pending one, deduping any word-overlap. Soniox re-finalizes
|
|
@@ -8050,7 +8305,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
8050
8305
|
}
|
|
8051
8306
|
const bargeHold = !this.looksIncomplete(this.pendingUtt) && this.unfinishedBargeLine(this.pendingUtt);
|
|
8052
8307
|
if (this.options.incompleteMergeMs && (bargeHold || this.looksIncomplete(this.pendingUtt))) {
|
|
8053
|
-
|
|
8308
|
+
log13.verbose(`hold: incomplete utterance "${this.pendingUtt.slice(-40)}"`);
|
|
8054
8309
|
this.diag("hold", { reason: bargeHold ? "barge_unfinished" : "incomplete", tail: this.pendingUtt.slice(-40) });
|
|
8055
8310
|
this.options.onHold();
|
|
8056
8311
|
if (this.options.holdFiller && !this.speaking) this.speakFiller(this.options.holdFiller);
|
|
@@ -8089,13 +8344,13 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
8089
8344
|
const answered = this.answeredSinceDispatch();
|
|
8090
8345
|
const part = flat !== this.lastDispatchFlat && !answered && this.echoesDispatch(text);
|
|
8091
8346
|
if (part || flat && flat === this.lastDispatchFlat && !answered && this.clock.now() - this.lastDispatchAt < _VoiceEngine.DUP_FINAL_MS) {
|
|
8092
|
-
|
|
8347
|
+
log13.verbose(`dropped duplicate final "${text.slice(0, 40)}" (identical to the just-dispatched utterance, no reply between)`);
|
|
8093
8348
|
this.diag("utterance_drop", { reason: "dup_final", text: text.slice(0, 60) });
|
|
8094
8349
|
return;
|
|
8095
8350
|
}
|
|
8096
8351
|
const refinal = this.refinalizes(text);
|
|
8097
8352
|
if (refinal && this.repliedSinceDispatch) {
|
|
8098
|
-
|
|
8353
|
+
log13.verbose(`dropped re-finalization "${text.slice(0, 60)}" (superset of the just-dispatched utterance, reply already flowing)`);
|
|
8099
8354
|
this.diag("utterance_drop", { reason: "refinalized", text: text.slice(0, 60) });
|
|
8100
8355
|
this.lastDispatchFlat = flat;
|
|
8101
8356
|
this.lastDispatchWords = text.trim().split(/\s+/);
|
|
@@ -8374,7 +8629,7 @@ var SpokenSplitter = class {
|
|
|
8374
8629
|
};
|
|
8375
8630
|
|
|
8376
8631
|
// node_modules/@bod.ee/voice/src/adapters/soniox.ts
|
|
8377
|
-
var
|
|
8632
|
+
var log14 = forComponent2("SonioxSTT");
|
|
8378
8633
|
var now = () => performance.now();
|
|
8379
8634
|
var SonioxSTTOptions = class {
|
|
8380
8635
|
auth = "";
|
|
@@ -8419,7 +8674,7 @@ var SonioxSTT = class {
|
|
|
8419
8674
|
this.onDiag({ t: now(), kind, ...fields });
|
|
8420
8675
|
} catch (e) {
|
|
8421
8676
|
this.diagOn = false;
|
|
8422
|
-
|
|
8677
|
+
log14.debug(`onDiag threw \u2014 STT diagnostics disabled: ${e instanceof Error ? e.message : e}`);
|
|
8423
8678
|
}
|
|
8424
8679
|
}
|
|
8425
8680
|
lastChunkAt = 0;
|
|
@@ -8461,11 +8716,11 @@ var SonioxSTT = class {
|
|
|
8461
8716
|
this.ws.onmessage = (ev) => this.handle(JSON.parse(String(ev.data)));
|
|
8462
8717
|
this.ws.onclose = (ev) => {
|
|
8463
8718
|
if (this.stopped) return;
|
|
8464
|
-
|
|
8719
|
+
log14.warn(`soniox ws closed (${ev.code} ${ev.reason || ""}) \u2014 reconnecting`);
|
|
8465
8720
|
this.diag("stt_ws_closed", { code: ev.code, reason: String(ev.reason || ""), reconnecting: true });
|
|
8466
8721
|
this.reset();
|
|
8467
8722
|
this.connectWs().catch((e) => {
|
|
8468
|
-
|
|
8723
|
+
log14.error(`soniox reconnect failed: ${e.message}`);
|
|
8469
8724
|
this.diag("stt_reconnect_failed", { message: e.message });
|
|
8470
8725
|
});
|
|
8471
8726
|
};
|
|
@@ -8477,7 +8732,7 @@ var SonioxSTT = class {
|
|
|
8477
8732
|
this.endpointTimer = setInterval(() => {
|
|
8478
8733
|
const combined = (this.finalText + this.partialText).trim();
|
|
8479
8734
|
if (!combined || now() - this.lastChangeAt < this.options.silenceEndpointMs) return;
|
|
8480
|
-
if (this.firstTokenAt)
|
|
8735
|
+
if (this.firstTokenAt) log14.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192silence-endpoint, "${combined.slice(0, 60)}"`);
|
|
8481
8736
|
this.reset();
|
|
8482
8737
|
this.onUtterance(combined, now());
|
|
8483
8738
|
}, 120);
|
|
@@ -8489,7 +8744,7 @@ var SonioxSTT = class {
|
|
|
8489
8744
|
if (this.stopped) return;
|
|
8490
8745
|
const ref = this.lastChunkAt || this.startedChunksAt;
|
|
8491
8746
|
if (now() - ref > noAudioMs) {
|
|
8492
|
-
|
|
8747
|
+
log14.error(`stt: no mic audio for >${Math.round(noAudioMs / 1e3)}s \u2014 capture device stopped delivering`);
|
|
8493
8748
|
this.diag("stt_watchdog_fatal", { noAudioMs });
|
|
8494
8749
|
this.onFatal("microphone stopped delivering audio (try a different input device, e.g. AirPods, or check System Settings \u2192 Sound \u2192 Input)");
|
|
8495
8750
|
this.stop();
|
|
@@ -8512,7 +8767,7 @@ var SonioxSTT = class {
|
|
|
8512
8767
|
handle(m) {
|
|
8513
8768
|
if (m.error_message) {
|
|
8514
8769
|
this.diag("stt_error", { message: String(m.error_message), code: m.error_code });
|
|
8515
|
-
return
|
|
8770
|
+
return log14.error(`soniox: ${m.error_message}`);
|
|
8516
8771
|
}
|
|
8517
8772
|
let endpoint = false;
|
|
8518
8773
|
for (const t of m.tokens ?? []) {
|
|
@@ -8529,7 +8784,7 @@ var SonioxSTT = class {
|
|
|
8529
8784
|
this.onPartial(combined);
|
|
8530
8785
|
if (endpoint && this.finalText.trim()) {
|
|
8531
8786
|
const utterance = this.finalText.trim();
|
|
8532
|
-
if (this.firstTokenAt)
|
|
8787
|
+
if (this.firstTokenAt) log14.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192endpoint, "${utterance.slice(0, 60)}"`);
|
|
8533
8788
|
this.reset();
|
|
8534
8789
|
this.onUtterance(utterance, now());
|
|
8535
8790
|
}
|
|
@@ -8551,7 +8806,7 @@ var SonioxSTT = class {
|
|
|
8551
8806
|
};
|
|
8552
8807
|
|
|
8553
8808
|
// node_modules/@bod.ee/voice/src/adapters/cartesia.ts
|
|
8554
|
-
var
|
|
8809
|
+
var log15 = forComponent2("CartesiaTTS");
|
|
8555
8810
|
var now2 = () => performance.now();
|
|
8556
8811
|
var CartesiaTTSOptions = class {
|
|
8557
8812
|
auth = "";
|
|
@@ -8587,7 +8842,7 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
8587
8842
|
this.onDiag({ t: now2(), kind, ...fields });
|
|
8588
8843
|
} catch (e) {
|
|
8589
8844
|
this.diagOn = false;
|
|
8590
|
-
|
|
8845
|
+
log15.debug(`onDiag threw \u2014 TTS diagnostics disabled: ${e instanceof Error ? e.message : e}`);
|
|
8591
8846
|
}
|
|
8592
8847
|
}
|
|
8593
8848
|
firstAudioAt = 0;
|
|
@@ -8622,11 +8877,11 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
8622
8877
|
this.ws.onerror = (e) => rej(new Error(`cartesia ws: ${e.message || "connect failed"}`));
|
|
8623
8878
|
});
|
|
8624
8879
|
this.ws.onclose = (ev) => {
|
|
8625
|
-
|
|
8880
|
+
log15.warn(`cartesia ws closed (${ev.code} ${ev.reason || ""})`);
|
|
8626
8881
|
this.diag("tts_ws_closed", { code: ev.code, reason: String(ev.reason || ""), reconnecting: !this.closed });
|
|
8627
8882
|
if (!this.closed) {
|
|
8628
8883
|
this.connecting = this.doConnect().catch((e) => {
|
|
8629
|
-
|
|
8884
|
+
log15.error(`cartesia reconnect failed: ${e.message}`);
|
|
8630
8885
|
this.diag("tts_reconnect_failed", { message: e.message });
|
|
8631
8886
|
});
|
|
8632
8887
|
}
|
|
@@ -8654,12 +8909,12 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
8654
8909
|
this.down = true;
|
|
8655
8910
|
this.downAt = now2();
|
|
8656
8911
|
this.consecutiveOk = 0;
|
|
8657
|
-
|
|
8912
|
+
log15.warn(`TTS circuit breaker open \u2014 ${this.consecutiveErrors} consecutive errors, switching to text-only`);
|
|
8658
8913
|
this.diag("tts_breaker_open", { errors: this.consecutiveErrors });
|
|
8659
8914
|
this.onDone();
|
|
8660
8915
|
this.startProbe();
|
|
8661
8916
|
} else if (!this.down) {
|
|
8662
|
-
(/No valid transcripts/i.test(m.message || "") ?
|
|
8917
|
+
(/No valid transcripts/i.test(m.message || "") ? log15.debug : log15.warn)(`cartesia: ${JSON.stringify(m)}`);
|
|
8663
8918
|
}
|
|
8664
8919
|
}
|
|
8665
8920
|
};
|
|
@@ -8674,7 +8929,7 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
8674
8929
|
this.stopProbe();
|
|
8675
8930
|
const downMs = this.downAt ? now2() - this.downAt : 0;
|
|
8676
8931
|
this.diag("tts_breaker_close", { downMs: Math.round(downMs) });
|
|
8677
|
-
(downMs < 2e3 ?
|
|
8932
|
+
(downMs < 2e3 ? log15.debug : log15.info)(`TTS recovered${downMs ? ` (down ${downMs}ms)` : ""}`);
|
|
8678
8933
|
}
|
|
8679
8934
|
/** Ensure the WS is open before sending — reconnects if idle-closed. */
|
|
8680
8935
|
async ensureConnected() {
|
|
@@ -8757,13 +9012,13 @@ function base64ToBytes(b64) {
|
|
|
8757
9012
|
}
|
|
8758
9013
|
|
|
8759
9014
|
// node_modules/@bod.ee/voice/src/adapters/fish.ts
|
|
8760
|
-
var
|
|
9015
|
+
var log16 = forComponent2("FishTTS");
|
|
8761
9016
|
|
|
8762
9017
|
// node_modules/@bod.ee/voice/src/adapters/grok.ts
|
|
8763
|
-
var
|
|
9018
|
+
var log17 = forComponent2("GrokTTS");
|
|
8764
9019
|
|
|
8765
9020
|
// node_modules/@bod.ee/voice/src/adapters/grok-stt.ts
|
|
8766
|
-
var
|
|
9021
|
+
var log18 = forComponent2("GrokSTT");
|
|
8767
9022
|
|
|
8768
9023
|
// src/voice/engine.ts
|
|
8769
9024
|
init_logging();
|
|
@@ -8775,7 +9030,7 @@ configureLogging((name) => {
|
|
|
8775
9030
|
|
|
8776
9031
|
// src/voice/instinct.ts
|
|
8777
9032
|
init_logging();
|
|
8778
|
-
var
|
|
9033
|
+
var log19 = forComponent("Instinct");
|
|
8779
9034
|
var InstinctOptions = class {
|
|
8780
9035
|
decider;
|
|
8781
9036
|
/** Partial unchanged this long (a micro-pause) → consult. */
|
|
@@ -9012,13 +9267,13 @@ var Instinct = class {
|
|
|
9012
9267
|
* unfinished partial is no turn to react to — the interjection itself is the reply. */
|
|
9013
9268
|
pickAck(ctx) {
|
|
9014
9269
|
if (this.interjecting) {
|
|
9015
|
-
|
|
9270
|
+
log19.debug(`ack: cut-in turn \u2192 silent (engine context "${ctx.text.slice(0, 40)}" is the previous final)`);
|
|
9016
9271
|
return Promise.resolve(null);
|
|
9017
9272
|
}
|
|
9018
9273
|
const text = ctx.text.trim();
|
|
9019
9274
|
const p = (async () => {
|
|
9020
9275
|
const intentP = ctx.barged ? this.bargeIntent(text).catch((e) => {
|
|
9021
|
-
|
|
9276
|
+
log19.warn(`barge intent for the ack failed \u2014 situational ack: ${e.message}`);
|
|
9022
9277
|
return null;
|
|
9023
9278
|
}) : null;
|
|
9024
9279
|
const d = await this.ask(
|
|
@@ -9040,7 +9295,7 @@ var Instinct = class {
|
|
|
9040
9295
|
settle = r;
|
|
9041
9296
|
});
|
|
9042
9297
|
const timer = setTimeout(() => {
|
|
9043
|
-
|
|
9298
|
+
log19.debug(`ack: no engine outcome in ${this.o.ackSettleMs}ms \u2014 not claimed`);
|
|
9044
9299
|
settle(null);
|
|
9045
9300
|
}, this.o.ackSettleMs);
|
|
9046
9301
|
void spoken.then(() => clearTimeout(timer));
|
|
@@ -9078,7 +9333,7 @@ var Instinct = class {
|
|
|
9078
9333
|
});
|
|
9079
9334
|
return { kind, heard, choice: answers.act.choice, probabilities: answers.act.probabilities, ms: Math.round(this.o.now() - t0) };
|
|
9080
9335
|
} catch (e) {
|
|
9081
|
-
|
|
9336
|
+
log19.warn(`${kind} decision failed \u2014 falling back to engine defaults: ${e.message}`);
|
|
9082
9337
|
if (rethrow) throw e;
|
|
9083
9338
|
return null;
|
|
9084
9339
|
}
|
|
@@ -9087,7 +9342,7 @@ var Instinct = class {
|
|
|
9087
9342
|
|
|
9088
9343
|
// src/voice/unsaid.ts
|
|
9089
9344
|
init_logging();
|
|
9090
|
-
var
|
|
9345
|
+
var log20 = forComponent("Unsaid");
|
|
9091
9346
|
var STOP2 = new Set("that this with have from they them their there then than what when were will would your about into just also been some only over more most very which while after before because these those does here".split(" "));
|
|
9092
9347
|
var contentStems = (s) => (s.toLowerCase().match(/[a-z0-9']+/g) ?? []).filter((w) => w.length >= 4 && !STOP2.has(w)).map((w) => w.slice(0, 4));
|
|
9093
9348
|
var SAID_OVERLAP = 0.6;
|
|
@@ -9127,7 +9382,7 @@ async function unsaidClauses(pieces, spoken, decider, timeoutMs = 5e3) {
|
|
|
9127
9382
|
return p < CONVEYED_MIN;
|
|
9128
9383
|
}).map((x) => x.c);
|
|
9129
9384
|
} catch (e) {
|
|
9130
|
-
|
|
9385
|
+
log20.warn(`unsaid judge failed \u2014 only near-zero-overlap clauses count as unsaid: ${e.message}`);
|
|
9131
9386
|
return fallback();
|
|
9132
9387
|
} finally {
|
|
9133
9388
|
clearTimeout(timer);
|
|
@@ -9167,15 +9422,16 @@ var STRICT = [
|
|
|
9167
9422
|
];
|
|
9168
9423
|
var SENTENCE_END = new RegExp(`[.!?\u2026]+["')\\]]*(?:\\s|(?=\\p{Lu}|\\[))`, "gu");
|
|
9169
9424
|
var SENTENCE_SPLIT = new RegExp(`(?<=[.!?\u2026]+["')\\]]*)(?:\\s+|(?=\\p{Lu}|\\[))`, "u");
|
|
9425
|
+
var BRACKET_NOTE = /^\s*\[\s*(?:[^\]\s]+\s+){3,}[^\]]*(?:\]\s*)?$/;
|
|
9170
9426
|
var clean = (s) => s.replace(/\[[a-z_ ]+\]\s*/gi, "").trim();
|
|
9171
9427
|
function isLeakSuspect(sentence) {
|
|
9172
9428
|
const s = clean(sentence);
|
|
9173
|
-
return SUSPECT.some((re) => re.test(s));
|
|
9429
|
+
return BRACKET_NOTE.test(sentence) || SUSPECT.some((re) => re.test(s));
|
|
9174
9430
|
}
|
|
9175
9431
|
function isStrictLeak(sentence) {
|
|
9176
9432
|
return sentence.split(SENTENCE_SPLIT).some((x) => {
|
|
9177
9433
|
const s = clean(x);
|
|
9178
|
-
return STRICT.some((re) => re.test(s));
|
|
9434
|
+
return BRACKET_NOTE.test(x) || STRICT.some((re) => re.test(s));
|
|
9179
9435
|
});
|
|
9180
9436
|
}
|
|
9181
9437
|
var JUDGE_INSTRUCTIONS = `This sentence was produced by a voice assistant's reply stream and is about to be read aloud to the user. Is it the assistant's private scratchpad reasoning that leaked out \u2014 talking ABOUT the conversation instead of TO the listener? Leaked reasoning: refers to the listener in the third person ("the user wants\u2026", "user said\u2026"), plans its own reply ("we need to respond\u2026", "we should output\u2026", "I should answer briefly"), cites its own guidelines/instructions, or narrates its internal tool calls and task events (QuickLook, Act, Think, Hold, TaskStatus, "t2 completed") as steps it is taking. Speech: anything said TO the listener \u2014 answers, questions, offers, news, advice, explaining what it can do when asked, even if it mentions a user, an assistant, a policy, instructions, replying, or a tool.`;
|
|
@@ -9197,7 +9453,7 @@ async function judgeLeak(decider, sentence, userSaid) {
|
|
|
9197
9453
|
}
|
|
9198
9454
|
|
|
9199
9455
|
// src/duplex.ts
|
|
9200
|
-
var
|
|
9456
|
+
var log21 = forComponent("DuplexAgent");
|
|
9201
9457
|
function describeCall(call) {
|
|
9202
9458
|
const v = call.args && Object.values(call.args).find((x) => typeof x === "string" && x.trim());
|
|
9203
9459
|
const hint = v ? ` (${String(v).replace(/\s+/g, " ").trim().slice(0, 48)})` : "";
|
|
@@ -9457,7 +9713,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9457
9713
|
const m = this.reflexBuf.match(RESERVED_EVENT_MARKER) ?? this.reflexBuf.match(RESERVED_EVENT_OPENER);
|
|
9458
9714
|
if (m) {
|
|
9459
9715
|
this.fabricationCut = true;
|
|
9460
|
-
|
|
9716
|
+
log21.warn(`reflex fabricated a [task \u2026] event in its spoken stream \u2014 cutting it (kept ${m.index} chars)`);
|
|
9461
9717
|
this.reflexBuf = this.reflexBuf.slice(0, Math.max(m.index, this.reflexForwarded));
|
|
9462
9718
|
this.pumpReflex(true);
|
|
9463
9719
|
return;
|
|
@@ -9549,7 +9805,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9549
9805
|
const at = from + ok;
|
|
9550
9806
|
this.leakHit = true;
|
|
9551
9807
|
this.leakCuts.push(leak.trim().slice(0, 40));
|
|
9552
|
-
|
|
9808
|
+
log21.warn(`reflex spoke its own analysis ("${leak.trim().slice(0, 40)}\u2026") \u2014 cut before TTS`);
|
|
9553
9809
|
this.notify("diag", "analysis_leak", { text: leak.trim().slice(0, 80), at: this.turnSpoken.trim() ? "mid_reply" : "before_speech" });
|
|
9554
9810
|
const next = this.stepMarks.find((k) => k > at);
|
|
9555
9811
|
if (next === void 0) {
|
|
@@ -9589,7 +9845,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9589
9845
|
this.notify("diag", "leak_judge", { text: s.slice(0, 80), p: Math.round(p * 100) / 100, ms: Math.round(performance.now() - t0) });
|
|
9590
9846
|
return p >= LEAK_JUDGE_THRESHOLD;
|
|
9591
9847
|
}, (e) => {
|
|
9592
|
-
|
|
9848
|
+
log21.warn(`leak judge failed \u2014 speaking the sentence: ${e.message}`);
|
|
9593
9849
|
return false;
|
|
9594
9850
|
}).then((leak) => {
|
|
9595
9851
|
clearTimeout(timer);
|
|
@@ -9623,7 +9879,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9623
9879
|
let i = t.length - 1;
|
|
9624
9880
|
while (i >= this.turnBase && !(t[i].role === "assistant" && typeof t[i].content === "string" && t[i].content.includes(key))) i--;
|
|
9625
9881
|
if (i < this.turnBase) {
|
|
9626
|
-
|
|
9882
|
+
log21.warn("analysis leak cut, but its text was not found in the transcript to scrub");
|
|
9627
9883
|
continue;
|
|
9628
9884
|
}
|
|
9629
9885
|
const m = t[i];
|
|
@@ -9703,7 +9959,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9703
9959
|
return {
|
|
9704
9960
|
preToolUse: (call) => {
|
|
9705
9961
|
if (this.spec?.state === "pending" && !_DuplexAgent.SPEC_SAFE_TOOLS.has(call.name)) {
|
|
9706
|
-
|
|
9962
|
+
log21.verbose(`speculation aborted: reflex called ${call.name} on unconfirmed input`);
|
|
9707
9963
|
this.abortSpeculation();
|
|
9708
9964
|
return { block: true, reason: "Speculative turn aborted." };
|
|
9709
9965
|
}
|
|
@@ -9748,7 +10004,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9748
10004
|
try {
|
|
9749
10005
|
await this.voice.send(reminder);
|
|
9750
10006
|
} catch (e) {
|
|
9751
|
-
|
|
10007
|
+
log21.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
|
|
9752
10008
|
} finally {
|
|
9753
10009
|
this.nudging = false;
|
|
9754
10010
|
const i = this.voice.transcript.findIndex((m, k) => k >= base && m.role === "user" && m.content === reminder);
|
|
@@ -9778,12 +10034,17 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9778
10034
|
* `opts.id` names the turn; `opts.replaces` names an earlier one this content REPLACES — the voice engine's
|
|
9779
10035
|
* fuller re-finalization of an utterance whose reply had not started ("…previous" → "…previous
|
|
9780
10036
|
* conversation."). The replaced turn is skipped if it has not run yet, else aborted and rolled back out of
|
|
9781
|
-
* the transcript (kept only if it already dispatched work) — one reply, to the full text.
|
|
10037
|
+
* the transcript (kept only if it already dispatched work) — one reply, to the full text. `opts.followUp` = the new words
|
|
10038
|
+
* beyond the replaced turn (a string), or beyond each earlier turn of a re-final chain ({turnId: words}): when the nearest
|
|
10039
|
+
* turn still in the transcript is KEPT, its words run instead of the merged text (which would repeat its request — a
|
|
10040
|
+
* second Act). The host gets `turn_replace` {id, replaces, rolledBack, followed?}: only a rollback drops the replaced
|
|
10041
|
+
* line; `followed` = the kept turn whose follow-up ran (absent: the merged text ran). */
|
|
9782
10042
|
send(content, opts) {
|
|
9783
10043
|
if (opts?.replaces) this.replaceTurn(opts.replaces);
|
|
9784
10044
|
const cur = this.voice.options.signal;
|
|
9785
10045
|
const ext = cur && cur === this.turnSignal?.own ? this.turnSignal.ext : cur;
|
|
9786
10046
|
const rec = opts?.id ? this.trackTurn(opts.id) : void 0;
|
|
10047
|
+
if (rec && opts?.replaces && typeof content === "string") rec.replaces = { id: opts.id, of: opts.replaces, content, followUp: typeof opts.followUp === "string" ? { [opts.replaces]: opts.followUp } : opts.followUp ?? {} };
|
|
9787
10048
|
if (rec?.superseded) {
|
|
9788
10049
|
this.notify("diag", "turn_replaced", { id: opts.id, ran: false });
|
|
9789
10050
|
return Promise.resolve(_DuplexAgent.replacedResult(this.voice.transcript));
|
|
@@ -9795,7 +10056,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9795
10056
|
const accepted = this.offerOpen && this.parkedRedeliver.length > 0 && this.parkedRedeliver.every((p) => p.task === this.offer.task) && (isOfferAcceptance(content) || isTrivialBarge(content));
|
|
9796
10057
|
const judge = accepted ? void 0 : this.options.bargeIntent;
|
|
9797
10058
|
return (accepted ? Promise.resolve("resume") : judge ? judge(content).catch((e) => {
|
|
9798
|
-
|
|
10059
|
+
log21.warn(`bargeIntent judge failed \u2014 using the allowlist: ${e.message}`);
|
|
9799
10060
|
return fallback();
|
|
9800
10061
|
}) : Promise.resolve(fallback())).then((intent) => {
|
|
9801
10062
|
this.notify("diag", "barge_intent", { intent, text: content.slice(0, 80) });
|
|
@@ -9841,6 +10102,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9841
10102
|
if (rec.done) {
|
|
9842
10103
|
const drop = rec.dropped;
|
|
9843
10104
|
rec.dropped = void 0;
|
|
10105
|
+
rec.kept = !drop?.length;
|
|
9844
10106
|
if (drop?.length) void this.enqueue(async () => {
|
|
9845
10107
|
const t = this.voice.transcript;
|
|
9846
10108
|
for (const m of drop) {
|
|
@@ -9862,12 +10124,13 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9862
10124
|
* carries the user's full words) — unless it already dispatched work or SPOKE: the task is real, and the user
|
|
9863
10125
|
* heard those words, so both stay visible to the replacing turn. An aborted stream never reaches the transcript,
|
|
9864
10126
|
* so what was spoken is recorded as the replaced turn's reply. */
|
|
9865
|
-
dropReplacedTurn() {
|
|
10127
|
+
dropReplacedTurn(rec) {
|
|
9866
10128
|
this.fabricationCut = true;
|
|
9867
10129
|
this.reflexBuf = this.reflexBuf.slice(0, this.reflexForwarded);
|
|
9868
10130
|
this.dropJudge();
|
|
9869
10131
|
const spoke = this.turnSpoken.trim();
|
|
9870
10132
|
const rolledBack = !this.turnDispatched && !spoke;
|
|
10133
|
+
rec.kept = !rolledBack;
|
|
9871
10134
|
if (rolledBack) this.voice.transcript.length = this.turnBase;
|
|
9872
10135
|
else if (spoke) {
|
|
9873
10136
|
const t = this.voice.transcript;
|
|
@@ -9911,6 +10174,22 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9911
10174
|
this.parkedRedeliver = [];
|
|
9912
10175
|
for (const t of this.tasks.values()) if (t.deliveryParked === "barge") t.deliveryParked = "moved_on";
|
|
9913
10176
|
}
|
|
10177
|
+
if (rec?.replaces) {
|
|
10178
|
+
const { id, of, content: said, followUp } = rec.replaces;
|
|
10179
|
+
let base;
|
|
10180
|
+
for (let t = of, n = 0; t && n < 32; n++) {
|
|
10181
|
+
const r = this.userTurns.get(t);
|
|
10182
|
+
if (r?.kept) {
|
|
10183
|
+
base = t;
|
|
10184
|
+
break;
|
|
10185
|
+
}
|
|
10186
|
+
t = r?.replaces?.of;
|
|
10187
|
+
}
|
|
10188
|
+
const fu = base !== void 0 ? followUp[base] : void 0;
|
|
10189
|
+
if (fu !== void 0 && typeof content === "string" && content.startsWith(said)) content = fu + content.slice(said.length);
|
|
10190
|
+
else base = void 0;
|
|
10191
|
+
this.notify("turn_replace", of, { id, replaces: of, rolledBack: !this.userTurns.get(of)?.kept, ...base !== void 0 ? { followed: base } : {} });
|
|
10192
|
+
}
|
|
9914
10193
|
if (rec?.superseded) {
|
|
9915
10194
|
this.notify("diag", "turn_replaced", { ran: false });
|
|
9916
10195
|
return _DuplexAgent.replacedResult(this.voice.transcript);
|
|
@@ -9930,7 +10209,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9930
10209
|
}
|
|
9931
10210
|
if (rec) rec.done = true;
|
|
9932
10211
|
}
|
|
9933
|
-
if (rec?.superseded) return this.dropReplacedTurn();
|
|
10212
|
+
if (rec?.superseded) return this.dropReplacedTurn(rec);
|
|
9934
10213
|
if (rec && res.finishReason === "aborted" && !this.turnDispatched && !this.turnSpoken.trim()) rec.dropped = this.voice.transcript.slice(this.turnBase);
|
|
9935
10214
|
await this.endTurn(res);
|
|
9936
10215
|
return res;
|
|
@@ -9973,7 +10252,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9973
10252
|
try {
|
|
9974
10253
|
res = await this.voice.send(spec.text);
|
|
9975
10254
|
} catch (e) {
|
|
9976
|
-
|
|
10255
|
+
log21.warn(`speculative turn failed: ${e instanceof Error ? e.message : e}`);
|
|
9977
10256
|
} finally {
|
|
9978
10257
|
this.voice.options.signal = prevSignal;
|
|
9979
10258
|
}
|
|
@@ -9987,7 +10266,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9987
10266
|
if (d === "abort") {
|
|
9988
10267
|
if (this.voice.transcript.length > base) this.voice.transcript.length = base;
|
|
9989
10268
|
this.speculativeAbortedCalls++;
|
|
9990
|
-
|
|
10269
|
+
log21.verbose(`speculation aborted (${this.speculativeAbortedCalls} total): "${spec.text.slice(0, 50)}"`);
|
|
9991
10270
|
if (this.spec === spec) this.spec = void 0;
|
|
9992
10271
|
return res ?? empty;
|
|
9993
10272
|
}
|
|
@@ -10000,7 +10279,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
10000
10279
|
}
|
|
10001
10280
|
this.spec = void 0;
|
|
10002
10281
|
if (spec.rec) spec.rec.done = true;
|
|
10003
|
-
if (spec.rec?.superseded) return this.dropReplacedTurn();
|
|
10282
|
+
if (spec.rec?.superseded) return this.dropReplacedTurn(spec.rec);
|
|
10004
10283
|
await this.endTurn(res);
|
|
10005
10284
|
return res ?? empty;
|
|
10006
10285
|
});
|
|
@@ -10337,7 +10616,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
10337
10616
|
this.notify("task_verify", `task ${id}: verifying`, { id });
|
|
10338
10617
|
const cres = await new Agent(checkerOpts).run(checkBrief);
|
|
10339
10618
|
if (cres.finishReason !== "stop") {
|
|
10340
|
-
|
|
10619
|
+
log21.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
|
|
10341
10620
|
this.notify("task_verify", `task ${id}: verify inconclusive (${cres.finishReason})`, { id, finishReason: cres.finishReason });
|
|
10342
10621
|
}
|
|
10343
10622
|
const sum = (a = 0, b = 0) => a + b;
|
|
@@ -10473,7 +10752,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
10473
10752
|
rec.status = "done";
|
|
10474
10753
|
rec.result = res.text;
|
|
10475
10754
|
const incomplete = res.finishReason !== "stop" && res.finishReason !== "empty";
|
|
10476
|
-
|
|
10755
|
+
log21.verbose(`task ${id} done (${res.steps} steps${incomplete ? `, INCOMPLETE: ${res.finishReason}` : ""})`);
|
|
10477
10756
|
this.notify("task_done", `task ${id} (${rec.label}) completed`, {
|
|
10478
10757
|
id,
|
|
10479
10758
|
text: res.text,
|
|
@@ -10507,14 +10786,14 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
10507
10786
|
foldIfSuperseded(rec, note) {
|
|
10508
10787
|
if (rec.deliveryParked !== "moved_on") return false;
|
|
10509
10788
|
if (note.trim()) this.voice.transcript.push({ role: "assistant", content: note });
|
|
10510
|
-
|
|
10789
|
+
log21.verbose(`task ${rec.id} settled but superseded (${rec.status}) \u2014 folded into transcript, not voiced`);
|
|
10511
10790
|
return true;
|
|
10512
10791
|
}
|
|
10513
10792
|
failTask(rec, msg) {
|
|
10514
10793
|
this.dropAsk(rec.id);
|
|
10515
10794
|
rec.status = "error";
|
|
10516
10795
|
rec.result = msg;
|
|
10517
|
-
|
|
10796
|
+
log21.warn(`task ${rec.id} failed: ${msg}`);
|
|
10518
10797
|
this.notify("task_error", `task ${rec.id} (${rec.label}) failed: ${msg}`, { id: rec.id });
|
|
10519
10798
|
if (this.foldIfSuperseded(rec, `The "${rec.label}" task failed: ${msg}`)) return;
|
|
10520
10799
|
this.dropParked(rec);
|
|
@@ -10753,7 +11032,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
10753
11032
|
// src/modelRouter.ts
|
|
10754
11033
|
init_llm();
|
|
10755
11034
|
init_logging();
|
|
10756
|
-
var
|
|
11035
|
+
var log22 = forComponent("ModelRouter");
|
|
10757
11036
|
var ModelRouterOptions = class {
|
|
10758
11037
|
classifier = jevClassifier();
|
|
10759
11038
|
/** Capability tiers → model id (any provider). basic/standard run the main session; premium is only ever an offload. */
|
|
@@ -10837,10 +11116,10 @@ var ModelRouter = class {
|
|
|
10837
11116
|
if (tier === "premium") d.offload = this.state.offload && (c.sameThread ?? 0) >= o.sameThreadAt ? "continue" : "fresh";
|
|
10838
11117
|
} catch (e) {
|
|
10839
11118
|
const msg = e instanceof Error ? e.message : String(e);
|
|
10840
|
-
|
|
11119
|
+
log22.warn(`classifier failed, falling back to standard: ${msg}`);
|
|
10841
11120
|
d = { tier: "standard", model: o.models.standard, probs: {}, fallback: msg, classifyMs: Math.round(performance.now() - t0) };
|
|
10842
11121
|
}
|
|
10843
|
-
|
|
11122
|
+
log22.info(`route ${d.tier}${d.offload ? `(${d.offload})` : ""} \xB7 argmax=${d.choice ?? "-"} \xB7 p=${JSON.stringify(d.probs)}${d.sameThread != null ? ` \xB7 same=${d.sameThread.toFixed(2)}` : ""} \xB7 ${d.classifyMs}ms`);
|
|
10844
11123
|
return d;
|
|
10845
11124
|
}
|
|
10846
11125
|
/** Run one user turn on `agent` with routing. Returns a RunResult carrying `costUsd` + `routing`. */
|
|
@@ -10892,7 +11171,7 @@ ${text}`;
|
|
|
10892
11171
|
const answer = res.text || `(premium sub-agent finished with no reply; finishReason=${res.finishReason})`;
|
|
10893
11172
|
agent.transcript.push({ role: "user", content }, { role: "assistant", content: answer });
|
|
10894
11173
|
this.state.offload = { messages: child.transcript, mainLen: agent.transcript.length, question: text };
|
|
10895
|
-
|
|
11174
|
+
log22.info(`offload ${cont ? "continue" : "fresh"} done \xB7 ${res.finishReason} \xB7 ${res.steps} steps`);
|
|
10896
11175
|
const costUsd = o.costOf(main2, briefUsage) + o.costOf(o.models.premium, res.usage);
|
|
10897
11176
|
return { ...res, text: answer, messages: agent.transcript, usage: addUsage(briefUsage, res.usage), costUsd, routing: d };
|
|
10898
11177
|
}
|
|
@@ -10908,7 +11187,7 @@ The request:
|
|
|
10908
11187
|
${text}`);
|
|
10909
11188
|
return { text: r.text.trim() || text, usage: r.usage };
|
|
10910
11189
|
} catch (e) {
|
|
10911
|
-
|
|
11190
|
+
log22.warn(`brief failed, sending the raw request: ${e instanceof Error ? e.message : String(e)}`);
|
|
10912
11191
|
return { text };
|
|
10913
11192
|
} finally {
|
|
10914
11193
|
agent.transcript = snap;
|
|
@@ -10940,8 +11219,8 @@ async function loadJev(opts) {
|
|
|
10940
11219
|
const env = { ...process.env };
|
|
10941
11220
|
const file = opts.envFile ?? process.env.AGENTX_JEV_ENV;
|
|
10942
11221
|
if (file) {
|
|
10943
|
-
const { readFileSync:
|
|
10944
|
-
for (const l of
|
|
11222
|
+
const { readFileSync: readFileSync11 } = await import("fs");
|
|
11223
|
+
for (const l of readFileSync11(file, "utf8").split("\n")) {
|
|
10945
11224
|
const m = l.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*?)\s*$/);
|
|
10946
11225
|
if (m && !env[m[1]]) env[m[1]] = m[2].replace(/^['"]|['"]$/g, "");
|
|
10947
11226
|
}
|
|
@@ -10953,7 +11232,7 @@ async function jevAvailable(opts = {}) {
|
|
|
10953
11232
|
const { env } = await loadJev(opts);
|
|
10954
11233
|
return JEV_KEYS.some((k) => env[k]?.trim());
|
|
10955
11234
|
} catch (e) {
|
|
10956
|
-
|
|
11235
|
+
log22.debug(`jev unavailable: ${e instanceof Error ? e.message : String(e)}`);
|
|
10957
11236
|
return false;
|
|
10958
11237
|
}
|
|
10959
11238
|
}
|
|
@@ -11180,8 +11459,8 @@ import { MemFilesystem as MemFilesystem3, IndexedDbFilesystem, CommandExecutor a
|
|
|
11180
11459
|
// src/mcp.client.ts
|
|
11181
11460
|
init_logging();
|
|
11182
11461
|
import { spawn } from "child_process";
|
|
11183
|
-
import { createHash } from "crypto";
|
|
11184
|
-
var
|
|
11462
|
+
import { createHash as createHash2 } from "crypto";
|
|
11463
|
+
var log23 = forComponent("mcp");
|
|
11185
11464
|
var PROTOCOL_VERSION = "2025-06-18";
|
|
11186
11465
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
11187
11466
|
var DEFAULT_CALL_TIMEOUT_MS = 30 * 6e4;
|
|
@@ -11211,7 +11490,7 @@ var StdioTransport = class {
|
|
|
11211
11490
|
proc.stdout.setEncoding("utf8");
|
|
11212
11491
|
proc.stdout.on("data", (chunk) => this.onData(chunk));
|
|
11213
11492
|
proc.stderr.setEncoding("utf8");
|
|
11214
|
-
proc.stderr.on("data", (chunk) =>
|
|
11493
|
+
proc.stderr.on("data", (chunk) => log23.debug(`[${command}] stderr:`, chunk.trimEnd()));
|
|
11215
11494
|
proc.on("exit", (code) => this.failAll(new Error(`MCP server "${command}" exited (code ${code})`)));
|
|
11216
11495
|
proc.on("error", (e) => this.failAll(e instanceof Error ? e : new Error(String(e))));
|
|
11217
11496
|
}
|
|
@@ -11225,7 +11504,7 @@ var StdioTransport = class {
|
|
|
11225
11504
|
try {
|
|
11226
11505
|
this.dispatch(JSON.parse(line));
|
|
11227
11506
|
} catch (e) {
|
|
11228
|
-
|
|
11507
|
+
log23.debug("dropping non-JSON line from MCP server:", line, e);
|
|
11229
11508
|
}
|
|
11230
11509
|
}
|
|
11231
11510
|
}
|
|
@@ -11235,7 +11514,7 @@ var StdioTransport = class {
|
|
|
11235
11514
|
try {
|
|
11236
11515
|
p2?.onProgress?.(msg.params ?? {});
|
|
11237
11516
|
} catch (e) {
|
|
11238
|
-
|
|
11517
|
+
log23.debug("onProgress handler threw", e);
|
|
11239
11518
|
}
|
|
11240
11519
|
return;
|
|
11241
11520
|
}
|
|
@@ -11273,7 +11552,7 @@ var StdioTransport = class {
|
|
|
11273
11552
|
const onAbort = () => {
|
|
11274
11553
|
if (!this.pending.delete(id)) return;
|
|
11275
11554
|
clearTimeout(timer);
|
|
11276
|
-
this.notify("notifications/cancelled", { requestId: id, reason: String(signal?.reason?.message ?? "cancelled") }).catch((e) =>
|
|
11555
|
+
this.notify("notifications/cancelled", { requestId: id, reason: String(signal?.reason?.message ?? "cancelled") }).catch((e) => log23.debug("cancel notify failed", e));
|
|
11277
11556
|
reject(abortError(signal));
|
|
11278
11557
|
};
|
|
11279
11558
|
const done = () => signal?.removeEventListener("abort", onAbort);
|
|
@@ -11297,7 +11576,7 @@ var StdioTransport = class {
|
|
|
11297
11576
|
try {
|
|
11298
11577
|
this.proc?.stdin?.end();
|
|
11299
11578
|
} catch (e) {
|
|
11300
|
-
|
|
11579
|
+
log23.debug("stdin end failed", e);
|
|
11301
11580
|
}
|
|
11302
11581
|
this.proc?.kill();
|
|
11303
11582
|
}
|
|
@@ -11332,7 +11611,7 @@ var HttpTransport = class {
|
|
|
11332
11611
|
return await this.post({ jsonrpc: "2.0", id, method, params: opts.onProgress ? withProgressToken(params, id) : params }, false, signal, opts.onProgress);
|
|
11333
11612
|
} catch (e) {
|
|
11334
11613
|
if (opts.signal?.aborted) {
|
|
11335
|
-
void this.notify("notifications/cancelled", { requestId: id, reason: String(opts.signal.reason?.message ?? "cancelled") }).catch((err2) =>
|
|
11614
|
+
void this.notify("notifications/cancelled", { requestId: id, reason: String(opts.signal.reason?.message ?? "cancelled") }).catch((err2) => log23.debug("cancel notify failed", err2));
|
|
11336
11615
|
throw abortError(opts.signal);
|
|
11337
11616
|
}
|
|
11338
11617
|
throw e;
|
|
@@ -11375,7 +11654,7 @@ function sseMessage(line) {
|
|
|
11375
11654
|
try {
|
|
11376
11655
|
return JSON.parse(trimmed.slice(5).trim());
|
|
11377
11656
|
} catch (e) {
|
|
11378
|
-
|
|
11657
|
+
log23.debug("skipping unparseable SSE data line", e);
|
|
11379
11658
|
return void 0;
|
|
11380
11659
|
}
|
|
11381
11660
|
}
|
|
@@ -11385,7 +11664,7 @@ async function readSseResponse(resp, id, onProgress) {
|
|
|
11385
11664
|
try {
|
|
11386
11665
|
onProgress?.(obj.params ?? {});
|
|
11387
11666
|
} catch (e) {
|
|
11388
|
-
|
|
11667
|
+
log23.debug("onProgress handler threw", e);
|
|
11389
11668
|
}
|
|
11390
11669
|
return void 0;
|
|
11391
11670
|
}
|
|
@@ -11484,7 +11763,7 @@ async function mountWithDeadline(name, cfg, mountTimeoutMs) {
|
|
|
11484
11763
|
return { name, client, tools, specs, serverInfo: init?.serverInfo, config: cfg };
|
|
11485
11764
|
})(), mountTimeoutMs, name);
|
|
11486
11765
|
} catch (e) {
|
|
11487
|
-
await client.close().catch((err2) =>
|
|
11766
|
+
await client.close().catch((err2) => log23.debug(`close after failed mount of "${name}": ${err2}`));
|
|
11488
11767
|
throw e;
|
|
11489
11768
|
}
|
|
11490
11769
|
}
|
|
@@ -11495,15 +11774,15 @@ function validEntries(servers) {
|
|
|
11495
11774
|
return Object.entries(servers).filter(([name, cfg]) => {
|
|
11496
11775
|
if (!cfg || cfg.disabled) return false;
|
|
11497
11776
|
if (!cfg.command && !cfg.url) {
|
|
11498
|
-
|
|
11777
|
+
log23.warn(`MCP server "${name}" needs a command (stdio) or url (http) \u2014 skipping`);
|
|
11499
11778
|
return false;
|
|
11500
11779
|
}
|
|
11501
11780
|
return true;
|
|
11502
11781
|
});
|
|
11503
11782
|
}
|
|
11504
11783
|
function logMountFailure(name, e) {
|
|
11505
|
-
if (e instanceof McpAuthError)
|
|
11506
|
-
else
|
|
11784
|
+
if (e instanceof McpAuthError) log23.warn(`MCP "${name}" needs-auth: HTTP ${e.status} \u2014 set bearerToken or headers in its config; skipping`);
|
|
11785
|
+
else log23.error(`MCP server "${name}" failed to mount: ${e?.message ?? e}`);
|
|
11507
11786
|
}
|
|
11508
11787
|
async function mountMcpServers(servers = {}, opts = {}) {
|
|
11509
11788
|
const entries = validEntries(servers);
|
|
@@ -11513,7 +11792,7 @@ async function mountMcpServers(servers = {}, opts = {}) {
|
|
|
11513
11792
|
const name = entries[i][0];
|
|
11514
11793
|
if (r.status === "fulfilled") {
|
|
11515
11794
|
out.push(r.value);
|
|
11516
|
-
|
|
11795
|
+
log23.debug(`MCP "${name}" mounted \u2014 ${r.value.tools.length} tool(s)${r.value.serverInfo?.name ? ` from ${r.value.serverInfo.name}` : ""}`);
|
|
11517
11796
|
} else logMountFailure(name, r.reason);
|
|
11518
11797
|
});
|
|
11519
11798
|
return out;
|
|
@@ -11553,7 +11832,7 @@ var McpPool = class {
|
|
|
11553
11832
|
const prev = this.warm.get(key);
|
|
11554
11833
|
if (prev) {
|
|
11555
11834
|
clearTimeout(prev.timer);
|
|
11556
|
-
if (prev.client !== client) void prev.client.close().catch((err2) =>
|
|
11835
|
+
if (prev.client !== client) void prev.client.close().catch((err2) => log23.debug(`warm-pool replace close failed: ${err2}`));
|
|
11557
11836
|
}
|
|
11558
11837
|
const e = { client, timer: void 0 };
|
|
11559
11838
|
this.warm.set(key, e);
|
|
@@ -11570,7 +11849,7 @@ var McpPool = class {
|
|
|
11570
11849
|
const e = this.warm.get(key);
|
|
11571
11850
|
if (!e) return;
|
|
11572
11851
|
this.warm.delete(key);
|
|
11573
|
-
await e.client.close().catch((err2) =>
|
|
11852
|
+
await e.client.close().catch((err2) => log23.debug(`warm-pool evict close failed: ${err2}`));
|
|
11574
11853
|
}
|
|
11575
11854
|
async closeAll() {
|
|
11576
11855
|
for (const e of this.warm.values()) {
|
|
@@ -11586,8 +11865,8 @@ var defaultPool = new McpPool();
|
|
|
11586
11865
|
|
|
11587
11866
|
// cli/mcpOAuth.ts
|
|
11588
11867
|
import { createServer } from "http";
|
|
11589
|
-
import { randomBytes as randomBytes2, createHash as
|
|
11590
|
-
import { readFileSync as
|
|
11868
|
+
import { randomBytes as randomBytes2, createHash as createHash3 } from "crypto";
|
|
11869
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync } from "fs";
|
|
11591
11870
|
import { dirname as dirname2 } from "path";
|
|
11592
11871
|
var McpOAuthOptions = class {
|
|
11593
11872
|
/** Where to persist tokens. Mode 0600. */
|
|
@@ -11615,14 +11894,14 @@ var McpOAuth = class {
|
|
|
11615
11894
|
// -- store I/O ----------------------------------------------------------------
|
|
11616
11895
|
load() {
|
|
11617
11896
|
try {
|
|
11618
|
-
return existsSync(this.options.storePath) ? JSON.parse(
|
|
11897
|
+
return existsSync(this.options.storePath) ? JSON.parse(readFileSync4(this.options.storePath, "utf8")) : {};
|
|
11619
11898
|
} catch {
|
|
11620
11899
|
return {};
|
|
11621
11900
|
}
|
|
11622
11901
|
}
|
|
11623
11902
|
save(store) {
|
|
11624
11903
|
mkdirSync3(dirname2(this.options.storePath), { recursive: true });
|
|
11625
|
-
|
|
11904
|
+
writeFileSync4(this.options.storePath, JSON.stringify(store, null, 2), { mode: 384 });
|
|
11626
11905
|
}
|
|
11627
11906
|
// -- ATTENDED: one-time authorization ----------------------------------------
|
|
11628
11907
|
/** Run the authorization-code + PKCE flow and persist the resulting tokens for `serverUrl`. */
|
|
@@ -11630,7 +11909,7 @@ var McpOAuth = class {
|
|
|
11630
11909
|
const meta = await this.discover(serverUrl);
|
|
11631
11910
|
const clientId = this.options.clientId ?? await this.registerClient(meta, serverUrl);
|
|
11632
11911
|
const verifier = b64url(randomBytes2(32));
|
|
11633
|
-
const challenge = b64url(
|
|
11912
|
+
const challenge = b64url(createHash3("sha256").update(verifier).digest());
|
|
11634
11913
|
const state = b64url(randomBytes2(16));
|
|
11635
11914
|
const { code, redirectUri } = await this.captureCode(meta.authorization_endpoint, clientId, challenge, state);
|
|
11636
11915
|
const tok = await this.exchange(meta.token_endpoint, {
|
|
@@ -11780,15 +12059,15 @@ function defaultOpenBrowser(url) {
|
|
|
11780
12059
|
// cli/core.ts
|
|
11781
12060
|
import { randomUUID } from "crypto";
|
|
11782
12061
|
import { execFile as execFile2 } from "child_process";
|
|
11783
|
-
import { resolve as resolve2, basename, join as
|
|
12062
|
+
import { resolve as resolve2, basename, join as join6 } from "path";
|
|
11784
12063
|
import { existsSync as existsSync3, mkdirSync as mkdirSync4 } from "fs";
|
|
11785
|
-
import { platform, arch, release, userInfo, homedir as homedir2, tmpdir } from "os";
|
|
12064
|
+
import { platform, arch, release, userInfo, homedir as homedir2, tmpdir as tmpdir2 } from "os";
|
|
11786
12065
|
init_tools_shell();
|
|
11787
12066
|
|
|
11788
12067
|
// src/tools.notify.ts
|
|
11789
12068
|
init_logging();
|
|
11790
12069
|
import { execFile } from "child_process";
|
|
11791
|
-
var
|
|
12070
|
+
var log25 = forComponent("notify");
|
|
11792
12071
|
function makeNotifyTool(opts = {}) {
|
|
11793
12072
|
const platform2 = opts.platform ?? process.platform;
|
|
11794
12073
|
const run = opts.exec ?? execFile;
|
|
@@ -11812,7 +12091,7 @@ function makeNotifyTool(opts = {}) {
|
|
|
11812
12091
|
return new Promise((resolve5) => {
|
|
11813
12092
|
run(argv[0], argv[1], { timeout: 5e3 }, (e) => {
|
|
11814
12093
|
if (e) {
|
|
11815
|
-
|
|
12094
|
+
log25.debug("notification failed", e);
|
|
11816
12095
|
resolve5(`Notification failed: ${e.message}`);
|
|
11817
12096
|
} else resolve5("Notification shown.");
|
|
11818
12097
|
});
|
|
@@ -11826,9 +12105,9 @@ import { BodDB as BodDB2 } from "@bod.ee/db";
|
|
|
11826
12105
|
|
|
11827
12106
|
// cli/util.ts
|
|
11828
12107
|
init_logging();
|
|
11829
|
-
import { existsSync as existsSync2, readFileSync as
|
|
12108
|
+
import { existsSync as existsSync2, readFileSync as readFileSync5 } from "fs";
|
|
11830
12109
|
import { homedir } from "os";
|
|
11831
|
-
var
|
|
12110
|
+
var log26 = forComponent("cli-util");
|
|
11832
12111
|
function dotDirs(base, sub, opts = {}) {
|
|
11833
12112
|
const home = opts.home ?? homedir();
|
|
11834
12113
|
const dirs = [`${base}/.agent/${sub}`, `${base}/.claude/${sub}`, `${home}/.agent/${sub}`, `${home}/.claude/${sub}`];
|
|
@@ -11845,7 +12124,7 @@ function parseJson(text, fallback, what = "json") {
|
|
|
11845
12124
|
try {
|
|
11846
12125
|
return JSON.parse(text);
|
|
11847
12126
|
} catch (e) {
|
|
11848
|
-
|
|
12127
|
+
log26.debug(`parseJson(${what}) failed: ${e.message}`);
|
|
11849
12128
|
return fallback;
|
|
11850
12129
|
}
|
|
11851
12130
|
}
|
|
@@ -11853,9 +12132,9 @@ function readJsonFile(path, fallback) {
|
|
|
11853
12132
|
if (!existsSync2(path)) return fallback;
|
|
11854
12133
|
let text;
|
|
11855
12134
|
try {
|
|
11856
|
-
text =
|
|
12135
|
+
text = readFileSync5(path, "utf8");
|
|
11857
12136
|
} catch (e) {
|
|
11858
|
-
|
|
12137
|
+
log26.debug(`readJsonFile(${path}) unreadable: ${e.message}`);
|
|
11859
12138
|
return fallback;
|
|
11860
12139
|
}
|
|
11861
12140
|
return parseJson(text, fallback, path);
|
|
@@ -11970,7 +12249,7 @@ async function buildAgent(o) {
|
|
|
11970
12249
|
} else if (o.boddb) {
|
|
11971
12250
|
const root = resolve2(o.boddb);
|
|
11972
12251
|
mkdirSync4(root, { recursive: true });
|
|
11973
|
-
const db = new BodDB2({ path:
|
|
12252
|
+
const db = new BodDB2({ path: join6(root, "meta.db"), sweepInterval: 0, vfs: { storageRoot: join6(root, "files") } });
|
|
11974
12253
|
const bod = new BodDbFilesystem(db);
|
|
11975
12254
|
const firstRun = (await bod.readDir("/").catch(() => [])).length === 0;
|
|
11976
12255
|
await mkdirp(bod, cwd);
|
|
@@ -12037,7 +12316,7 @@ Reference files in them by their mount path (the left side).`;
|
|
|
12037
12316
|
});
|
|
12038
12317
|
realShell = [makeRealShellTool({ cwd, registry: jobs, osSandbox: o.osSandbox, jobsSurviveRun: o.singleRun !== true }), ...makeShellJobTools(jobs)];
|
|
12039
12318
|
}
|
|
12040
|
-
const scratchDir = o.scratch ? o.scratchDir ?? (virtual ? `${cwd}/.agent/scratch` : `${
|
|
12319
|
+
const scratchDir = o.scratch ? o.scratchDir ?? (virtual ? `${cwd}/.agent/scratch` : `${tmpdir2()}/agentx-scratch-${process.pid}`) : void 0;
|
|
12041
12320
|
const scratch = scratchDir ? new Scratch(fs, { dir: scratchDir }) : void 0;
|
|
12042
12321
|
return agentRef.agent = new Agent({
|
|
12043
12322
|
ai: o.ai,
|
|
@@ -12130,6 +12409,7 @@ The filesystem root '/' is the real machine root \u2014 you have full filesystem
|
|
|
12130
12409
|
keepToolOutputs: o.keepToolOutputs ?? 8,
|
|
12131
12410
|
...o.maxContextTokens != null ? { maxContextTokens: o.maxContextTokens } : {},
|
|
12132
12411
|
...o.contextWindow ? { contextWindow: o.contextWindow } : {},
|
|
12412
|
+
...o.imageInput ? { imageInput: o.imageInput } : {},
|
|
12133
12413
|
...o.browserTrim ? { browserTrim: o.browserTrim } : {}
|
|
12134
12414
|
});
|
|
12135
12415
|
}
|
|
@@ -12149,12 +12429,12 @@ var trunc = (s, n) => (s == null ? "" : String(s).length > n ? String(s).slice(0
|
|
|
12149
12429
|
|
|
12150
12430
|
// cli/voice.ts
|
|
12151
12431
|
init_logging();
|
|
12152
|
-
import { spawn as spawn2, spawnSync as
|
|
12153
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as
|
|
12432
|
+
import { spawn as spawn2, spawnSync as spawnSync3 } from "child_process";
|
|
12433
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync6, statSync as statSync5 } from "fs";
|
|
12154
12434
|
import { homedir as homedir3 } from "os";
|
|
12155
|
-
import { dirname as dirname3, join as
|
|
12435
|
+
import { dirname as dirname3, join as join7 } from "path";
|
|
12156
12436
|
import { fileURLToPath } from "url";
|
|
12157
|
-
var
|
|
12437
|
+
var log27 = forComponent("VoiceIO");
|
|
12158
12438
|
var now3 = () => performance.now();
|
|
12159
12439
|
var Player = class {
|
|
12160
12440
|
proc = null;
|
|
@@ -12168,7 +12448,7 @@ var Player = class {
|
|
|
12168
12448
|
["-loglevel", "quiet", "-nodisp", "-fflags", "nobuffer", "-flags", "low_delay", "-probesize", "32", "-f", "s16le", "-ar", String(TTS_SAMPLE_RATE), "-ch_layout", "mono", "-i", "-"],
|
|
12169
12449
|
{ stdio: ["pipe", "ignore", "ignore"] }
|
|
12170
12450
|
);
|
|
12171
|
-
this.proc.on("error", (e) =>
|
|
12451
|
+
this.proc.on("error", (e) => log27.warn(`ffplay error: ${e.message}`));
|
|
12172
12452
|
this.proc.stdin.on("error", () => {
|
|
12173
12453
|
});
|
|
12174
12454
|
this.bytesWritten = 0;
|
|
@@ -12195,10 +12475,10 @@ var Player = class {
|
|
|
12195
12475
|
this.proc = null;
|
|
12196
12476
|
}
|
|
12197
12477
|
};
|
|
12198
|
-
var nativeDir = () =>
|
|
12478
|
+
var nativeDir = () => join7(dirname3(fileURLToPath(import.meta.url)), "native");
|
|
12199
12479
|
function detectFfmpegMic() {
|
|
12200
12480
|
if (process.env.MIC_DEVICE) return process.env.MIC_DEVICE;
|
|
12201
|
-
const r =
|
|
12481
|
+
const r = spawnSync3("ffmpeg", ["-f", "avfoundation", "-list_devices", "true", "-i", ""], { encoding: "utf8" });
|
|
12202
12482
|
if (r.error?.code === "ENOENT") throw new Error("ffmpeg not installed \u2014 run `brew install ffmpeg` to enable voice");
|
|
12203
12483
|
if (r.error) throw new Error(`ffmpeg failed to run (${r.error.message}) \u2014 likely a wrong-arch binary; run \`brew reinstall ffmpeg\``);
|
|
12204
12484
|
const out = r.stderr ?? "";
|
|
@@ -12206,16 +12486,16 @@ function detectFfmpegMic() {
|
|
|
12206
12486
|
const devices = [...audio.matchAll(/\[(\d+)\] (.+)/g)].map(([, idx, name]) => ({ idx, name: name.trim() }));
|
|
12207
12487
|
const mic = devices.find((d) => /microphone|built-in/i.test(d.name) && !/teams|blackhole|loopback/i.test(d.name)) ?? devices[0];
|
|
12208
12488
|
if (!mic) throw new Error("no audio input device found");
|
|
12209
|
-
|
|
12489
|
+
log27.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
|
|
12210
12490
|
return `:${mic.idx}`;
|
|
12211
12491
|
}
|
|
12212
12492
|
function detectedInputDevice() {
|
|
12213
12493
|
if (process.platform !== "darwin") return null;
|
|
12214
12494
|
let name = "";
|
|
12215
|
-
if (
|
|
12216
|
-
name = (
|
|
12495
|
+
if (spawnSync3("which", ["SwitchAudioSource"]).status === 0) {
|
|
12496
|
+
name = (spawnSync3("SwitchAudioSource", ["-c", "-t", "input"], { encoding: "utf8" }).stdout || "").trim();
|
|
12217
12497
|
} else {
|
|
12218
|
-
const out =
|
|
12498
|
+
const out = spawnSync3("system_profiler", ["SPAudioDataType"], { encoding: "utf8" }).stdout || "";
|
|
12219
12499
|
let last = "";
|
|
12220
12500
|
for (const ln of out.split("\n")) {
|
|
12221
12501
|
const hdr = ln.match(/^\s{6,}(\S.*?):\s*$/);
|
|
@@ -12234,23 +12514,23 @@ function detectedInputDevice() {
|
|
|
12234
12514
|
}
|
|
12235
12515
|
function resolveAecBinary() {
|
|
12236
12516
|
if (process.env.MIC_AEC === "0" || process.platform !== "darwin") return null;
|
|
12237
|
-
const src =
|
|
12238
|
-
const plist =
|
|
12517
|
+
const src = join7(nativeDir(), "mic-aec.swift");
|
|
12518
|
+
const plist = join7(nativeDir(), "Info.plist");
|
|
12239
12519
|
if (!existsSync4(src)) return null;
|
|
12240
|
-
const cacheDir =
|
|
12241
|
-
const bin =
|
|
12242
|
-
if (existsSync4(bin) &&
|
|
12243
|
-
if (
|
|
12520
|
+
const cacheDir = join7(homedir3(), ".agent", "cache");
|
|
12521
|
+
const bin = join7(cacheDir, "mic-aec");
|
|
12522
|
+
if (existsSync4(bin) && statSync5(bin).mtimeMs >= statSync5(src).mtimeMs) return bin;
|
|
12523
|
+
if (spawnSync3("which", ["swiftc"]).status !== 0) return null;
|
|
12244
12524
|
mkdirSync5(cacheDir, { recursive: true });
|
|
12245
|
-
|
|
12246
|
-
const build =
|
|
12525
|
+
log27.info("compiling AEC mic helper (first run)\u2026");
|
|
12526
|
+
const build = spawnSync3("swiftc", ["-O", "-o", bin, src, "-Xlinker", "-sectcreate", "-Xlinker", "__TEXT", "-Xlinker", "__info_plist", "-Xlinker", plist], { encoding: "utf8" });
|
|
12247
12527
|
if (build.status !== 0) {
|
|
12248
|
-
|
|
12528
|
+
log27.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
|
|
12249
12529
|
return null;
|
|
12250
12530
|
}
|
|
12251
|
-
const sign =
|
|
12531
|
+
const sign = spawnSync3("codesign", ["-fs", "-", bin], { encoding: "utf8" });
|
|
12252
12532
|
if (sign.status !== 0) {
|
|
12253
|
-
|
|
12533
|
+
log27.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
|
|
12254
12534
|
return null;
|
|
12255
12535
|
}
|
|
12256
12536
|
return bin;
|
|
@@ -12258,14 +12538,14 @@ function resolveAecBinary() {
|
|
|
12258
12538
|
function openMicSettings() {
|
|
12259
12539
|
if (process.platform !== "darwin") return;
|
|
12260
12540
|
try {
|
|
12261
|
-
|
|
12541
|
+
spawnSync3("open", ["x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone"]);
|
|
12262
12542
|
} catch {
|
|
12263
12543
|
}
|
|
12264
12544
|
}
|
|
12265
12545
|
function micPermissionStatus() {
|
|
12266
12546
|
const bin = resolveAecBinary();
|
|
12267
12547
|
if (!bin) return null;
|
|
12268
|
-
const r =
|
|
12548
|
+
const r = spawnSync3(bin, ["--check-mic"], { encoding: "utf8" });
|
|
12269
12549
|
const out = (r.stdout ?? "").trim();
|
|
12270
12550
|
if (out === "authorized") return "authorized";
|
|
12271
12551
|
if (out === "denied" || out === "restricted") return "denied";
|
|
@@ -12275,7 +12555,7 @@ function micPermissionStatus() {
|
|
|
12275
12555
|
function aecUnavailableHint() {
|
|
12276
12556
|
if (process.env.MIC_AEC === "0" || process.platform !== "darwin") return null;
|
|
12277
12557
|
if (resolveAecBinary()) return null;
|
|
12278
|
-
if (
|
|
12558
|
+
if (spawnSync3("which", ["swiftc"]).status !== 0) return "install Xcode CLI tools (`xcode-select --install`) for echo cancellation";
|
|
12279
12559
|
return null;
|
|
12280
12560
|
}
|
|
12281
12561
|
var NodeMicSource = class {
|
|
@@ -12291,17 +12571,17 @@ var NodeMicSource = class {
|
|
|
12291
12571
|
if (this.bin) {
|
|
12292
12572
|
this.proc = spawn2(this.bin, [], { stdio: ["ignore", "pipe", "ignore"] });
|
|
12293
12573
|
} else {
|
|
12294
|
-
if (
|
|
12295
|
-
|
|
12574
|
+
if (spawnSync3("which", ["ffmpeg"]).status !== 0) throw new Error("voice I/O unavailable: no AEC helper and no ffmpeg on PATH");
|
|
12575
|
+
log27.info("mic: raw capture (no AEC) \u2014 echo handled heuristically; headphones recommended");
|
|
12296
12576
|
this.proc = spawn2(
|
|
12297
12577
|
"ffmpeg",
|
|
12298
12578
|
["-loglevel", "error", "-f", "avfoundation", "-i", detectFfmpegMic(), "-ar", String(STT_SAMPLE_RATE), "-ac", "1", "-f", "s16le", "-"],
|
|
12299
12579
|
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
12300
12580
|
);
|
|
12301
|
-
this.proc.stderr.on("data", (d) =>
|
|
12581
|
+
this.proc.stderr.on("data", (d) => log27.warn(`ffmpeg: ${String(d).trim()}`));
|
|
12302
12582
|
}
|
|
12303
12583
|
this.proc.on("exit", (c) => {
|
|
12304
|
-
if (c && !this.stopped)
|
|
12584
|
+
if (c && !this.stopped) log27.error(`mic capture exited (${c}) \u2014 check mic permission / MIC_DEVICE / MIC_AEC=0`);
|
|
12305
12585
|
});
|
|
12306
12586
|
this.proc.stdout.on("data", (chunk) => onChunk(chunk));
|
|
12307
12587
|
}
|
|
@@ -12367,7 +12647,7 @@ var AecDuplexAudio = class {
|
|
|
12367
12647
|
this.proc.stdin.on("error", () => {
|
|
12368
12648
|
});
|
|
12369
12649
|
this.proc.on("exit", (c) => {
|
|
12370
|
-
if (c && !this.stopped)
|
|
12650
|
+
if (c && !this.stopped) log27.error(`aec duplex audio exited (${c}) \u2014 check mic permission / MIC_AEC=0`);
|
|
12371
12651
|
});
|
|
12372
12652
|
this.proc.stdout.on("data", (chunk) => {
|
|
12373
12653
|
this.gotChunk = true;
|
|
@@ -12383,7 +12663,7 @@ var AecDuplexAudio = class {
|
|
|
12383
12663
|
openMicSettings();
|
|
12384
12664
|
this.onFatal?.("microphone permission denied \u2014 enable it in System Settings \u2192 Privacy & Security \u2192 Microphone for your terminal, then restart it");
|
|
12385
12665
|
}
|
|
12386
|
-
} else
|
|
12666
|
+
} else log27.debug(`mic-aec: ${s}`);
|
|
12387
12667
|
}
|
|
12388
12668
|
});
|
|
12389
12669
|
if (!this.noVpio && !this.triedFallback) {
|
|
@@ -12392,7 +12672,7 @@ var AecDuplexAudio = class {
|
|
|
12392
12672
|
this.triedFallback = true;
|
|
12393
12673
|
this.noVpio = true;
|
|
12394
12674
|
this._aec = false;
|
|
12395
|
-
|
|
12675
|
+
log27.warn("mic-aec: VPIO delivered no audio in 2.5s \u2014 falling back to non-VPIO capture (no AEC \u2192 half-duplex, no barge-in)");
|
|
12396
12676
|
this.onDegrade?.();
|
|
12397
12677
|
this.killProc();
|
|
12398
12678
|
this.spawnHelper();
|
|
@@ -12563,7 +12843,7 @@ function bindAckInstinct(v, instinct) {
|
|
|
12563
12843
|
if (ev.kind === "ack_fired") instinct.ackOutcome(String(ev.phrase));
|
|
12564
12844
|
else if (ev.kind === "ack_skipped" || ev.kind === "ack_cancelled") instinct.ackOutcome(null);
|
|
12565
12845
|
} catch (e) {
|
|
12566
|
-
|
|
12846
|
+
log27.warn(`ack outcome tap failed: ${e instanceof Error ? e.message : e}`);
|
|
12567
12847
|
}
|
|
12568
12848
|
onDiag(ev);
|
|
12569
12849
|
};
|
|
@@ -12641,7 +12921,7 @@ function fakeVoiceParts(uttFile) {
|
|
|
12641
12921
|
timer = setInterval(() => {
|
|
12642
12922
|
let buf;
|
|
12643
12923
|
try {
|
|
12644
|
-
buf =
|
|
12924
|
+
buf = readFileSync6(uttFile, "utf8");
|
|
12645
12925
|
} catch {
|
|
12646
12926
|
return;
|
|
12647
12927
|
}
|
|
@@ -12684,7 +12964,7 @@ function fakeVoiceParts(uttFile) {
|
|
|
12684
12964
|
init_logging();
|
|
12685
12965
|
import { appendFile, mkdirSync as mkdirSync6 } from "fs";
|
|
12686
12966
|
import { dirname as dirname4 } from "path";
|
|
12687
|
-
var
|
|
12967
|
+
var log28 = forComponent("VoiceDiag");
|
|
12688
12968
|
var JsonlDiagSink = class {
|
|
12689
12969
|
constructor(path) {
|
|
12690
12970
|
this.path = path;
|
|
@@ -12725,7 +13005,7 @@ var JsonlDiagSink = class {
|
|
|
12725
13005
|
disable(e) {
|
|
12726
13006
|
this.disabled = true;
|
|
12727
13007
|
this.queue.length = 0;
|
|
12728
|
-
|
|
13008
|
+
log28.debug(`voice diag sink disabled (${this.path}): ${e instanceof Error ? e.message : e}`);
|
|
12729
13009
|
}
|
|
12730
13010
|
/** True once a failure permanently disabled the sink (tests/inspection). */
|
|
12731
13011
|
get isDisabled() {
|
|
@@ -12739,13 +13019,13 @@ var JsonlDiagSink = class {
|
|
|
12739
13019
|
|
|
12740
13020
|
// cli/config.ts
|
|
12741
13021
|
import { homedir as homedir4 } from "os";
|
|
12742
|
-
import { existsSync as existsSync5, readFileSync as
|
|
12743
|
-
import { join as
|
|
13022
|
+
import { existsSync as existsSync5, readFileSync as readFileSync7 } from "fs";
|
|
13023
|
+
import { join as join8 } from "path";
|
|
12744
13024
|
import { pathToFileURL } from "url";
|
|
12745
13025
|
var FILES = ["config.ts", "config.js", "config.mjs", "config.json"];
|
|
12746
13026
|
async function loadFrom(dir) {
|
|
12747
13027
|
for (const f of FILES) {
|
|
12748
|
-
const p =
|
|
13028
|
+
const p = join8(dir, ".agent", f);
|
|
12749
13029
|
if (!existsSync5(p)) continue;
|
|
12750
13030
|
try {
|
|
12751
13031
|
const mod = await import(pathToFileURL(p).href, f.endsWith(".json") ? { with: { type: "json" } } : void 0);
|
|
@@ -12758,10 +13038,10 @@ async function loadFrom(dir) {
|
|
|
12758
13038
|
return {};
|
|
12759
13039
|
}
|
|
12760
13040
|
function loadSettings(dir) {
|
|
12761
|
-
const p =
|
|
13041
|
+
const p = join8(dir, ".agent", "settings.json");
|
|
12762
13042
|
if (!existsSync5(p)) return {};
|
|
12763
13043
|
try {
|
|
12764
|
-
const raw = JSON.parse(
|
|
13044
|
+
const raw = JSON.parse(readFileSync7(p, "utf8"));
|
|
12765
13045
|
const cfg = {};
|
|
12766
13046
|
if (raw.mcpServers && typeof raw.mcpServers === "object") cfg.mcpServers = raw.mcpServers;
|
|
12767
13047
|
if (raw.permissions && typeof raw.permissions === "object") cfg.permissions = raw.permissions;
|
|
@@ -12794,8 +13074,8 @@ async function loadConfig(cwd) {
|
|
|
12794
13074
|
}
|
|
12795
13075
|
|
|
12796
13076
|
// cli/hooks-config.ts
|
|
12797
|
-
import { spawnSync as
|
|
12798
|
-
var
|
|
13077
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
13078
|
+
var log29 = forComponent("hooks");
|
|
12799
13079
|
var escapeRegex = (s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
12800
13080
|
function ruleMatches(rule, toolName) {
|
|
12801
13081
|
if (!rule.tool || rule.tool === "*") return true;
|
|
@@ -12804,7 +13084,7 @@ function ruleMatches(rule, toolName) {
|
|
|
12804
13084
|
}
|
|
12805
13085
|
function runCmd(rule, env) {
|
|
12806
13086
|
try {
|
|
12807
|
-
const r =
|
|
13087
|
+
const r = spawnSync4(rule.command, {
|
|
12808
13088
|
shell: true,
|
|
12809
13089
|
encoding: "utf8",
|
|
12810
13090
|
timeout: rule.timeoutMs ?? 1e4,
|
|
@@ -12812,7 +13092,7 @@ function runCmd(rule, env) {
|
|
|
12812
13092
|
});
|
|
12813
13093
|
return { code: r.status ?? 1, out: ((r.stdout ?? "") + (r.stderr ?? "")).trim() };
|
|
12814
13094
|
} catch (e) {
|
|
12815
|
-
|
|
13095
|
+
log29.debug(`hook command failed: ${rule.command}`, e);
|
|
12816
13096
|
return { code: 1, out: String(e?.message ?? e) };
|
|
12817
13097
|
}
|
|
12818
13098
|
}
|
|
@@ -12916,15 +13196,15 @@ function formatDiff(ops, opts = {}) {
|
|
|
12916
13196
|
}
|
|
12917
13197
|
|
|
12918
13198
|
// cli/session.ts
|
|
12919
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as
|
|
13199
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync5, readdirSync, renameSync, symlinkSync as symlinkSync2, unlinkSync, readlinkSync, statSync as statSync6 } from "fs";
|
|
12920
13200
|
import { homedir as homedir5 } from "os";
|
|
12921
|
-
import { join as
|
|
12922
|
-
var
|
|
12923
|
-
var globalDir = () =>
|
|
13201
|
+
import { join as join9 } from "path";
|
|
13202
|
+
var log30 = forComponent("session");
|
|
13203
|
+
var globalDir = () => join9(homedir5(), ".agent", "sessions");
|
|
12924
13204
|
var SessionStore = class {
|
|
12925
13205
|
dir;
|
|
12926
13206
|
constructor(cwd) {
|
|
12927
|
-
this.dir =
|
|
13207
|
+
this.dir = join9(cwd, ".agent", "sessions");
|
|
12928
13208
|
}
|
|
12929
13209
|
/** Sortable, human-readable id: `YYYYMMDD-HHMMSS-<folder>`. */
|
|
12930
13210
|
newId(now4 = Date.now(), cwd) {
|
|
@@ -12932,10 +13212,10 @@ var SessionStore = class {
|
|
|
12932
13212
|
const p = (n, w = 2) => String(n).padStart(w, "0");
|
|
12933
13213
|
const slug2 = (cwd ?? process.cwd()).split("/").pop()?.replace(/[^A-Za-z0-9_-]/g, "") || "session";
|
|
12934
13214
|
let id = `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}-${slug2}`;
|
|
12935
|
-
if (existsSync6(this.dir) && existsSync6(
|
|
13215
|
+
if (existsSync6(this.dir) && existsSync6(join9(this.dir, `${id}.json`))) {
|
|
12936
13216
|
for (let i = 2; i <= 99; i++) {
|
|
12937
13217
|
const c = `${id}-${i}`;
|
|
12938
|
-
if (!existsSync6(
|
|
13218
|
+
if (!existsSync6(join9(this.dir, `${c}.json`))) {
|
|
12939
13219
|
id = c;
|
|
12940
13220
|
break;
|
|
12941
13221
|
}
|
|
@@ -12950,29 +13230,29 @@ var SessionStore = class {
|
|
|
12950
13230
|
save(data) {
|
|
12951
13231
|
if (!this.safeId(data.meta.id)) throw new Error(`unsafe session id: ${data.meta.id}`);
|
|
12952
13232
|
if (!existsSync6(this.dir)) mkdirSync7(this.dir, { recursive: true });
|
|
12953
|
-
const path =
|
|
13233
|
+
const path = join9(this.dir, `${data.meta.id}.json`);
|
|
12954
13234
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
12955
|
-
|
|
13235
|
+
writeFileSync5(tmp, JSON.stringify(data));
|
|
12956
13236
|
renameSync(tmp, path);
|
|
12957
13237
|
try {
|
|
12958
13238
|
const gd = globalDir();
|
|
12959
13239
|
if (!existsSync6(gd)) mkdirSync7(gd, { recursive: true });
|
|
12960
|
-
const link2 =
|
|
13240
|
+
const link2 = join9(gd, `${data.meta.id}.json`);
|
|
12961
13241
|
if (!existsSync6(link2)) symlinkSync2(path, link2);
|
|
12962
13242
|
} catch {
|
|
12963
13243
|
}
|
|
12964
13244
|
}
|
|
12965
13245
|
load(id) {
|
|
12966
13246
|
if (!this.safeId(id)) {
|
|
12967
|
-
|
|
13247
|
+
log30.debug(`rejecting unsafe session id: ${id}`);
|
|
12968
13248
|
return void 0;
|
|
12969
13249
|
}
|
|
12970
|
-
const path =
|
|
13250
|
+
const path = join9(this.dir, `${id}.json`);
|
|
12971
13251
|
if (!existsSync6(path)) return void 0;
|
|
12972
13252
|
try {
|
|
12973
|
-
return JSON.parse(
|
|
13253
|
+
return JSON.parse(readFileSync8(path, "utf8"));
|
|
12974
13254
|
} catch (e) {
|
|
12975
|
-
|
|
13255
|
+
log30.debug(`unreadable session ${id} \u2014 ignoring`, e);
|
|
12976
13256
|
return void 0;
|
|
12977
13257
|
}
|
|
12978
13258
|
}
|
|
@@ -12983,9 +13263,9 @@ var SessionStore = class {
|
|
|
12983
13263
|
for (const f of readdirSync(this.dir)) {
|
|
12984
13264
|
if (!f.endsWith(".json")) continue;
|
|
12985
13265
|
try {
|
|
12986
|
-
metas.push(JSON.parse(
|
|
13266
|
+
metas.push(JSON.parse(readFileSync8(join9(this.dir, f), "utf8")).meta);
|
|
12987
13267
|
} catch (e) {
|
|
12988
|
-
|
|
13268
|
+
log30.debug(`skipping unreadable session file ${f}`, e);
|
|
12989
13269
|
}
|
|
12990
13270
|
}
|
|
12991
13271
|
return metas.sort((a, b) => b.updated - a.updated);
|
|
@@ -12998,7 +13278,7 @@ var SessionStore = class {
|
|
|
12998
13278
|
for (const f of readdirSync(this.dir)) {
|
|
12999
13279
|
if (!f.endsWith(".json")) continue;
|
|
13000
13280
|
try {
|
|
13001
|
-
const st =
|
|
13281
|
+
const st = statSync6(join9(this.dir, f));
|
|
13002
13282
|
out.push({ id: f.slice(0, -5), mtime: st.mtimeMs, size: st.size });
|
|
13003
13283
|
} catch {
|
|
13004
13284
|
}
|
|
@@ -13017,11 +13297,11 @@ var SessionStore = class {
|
|
|
13017
13297
|
function globalSessionLoad(idOrPrefix) {
|
|
13018
13298
|
const gd = globalDir();
|
|
13019
13299
|
if (!existsSync6(gd)) return void 0;
|
|
13020
|
-
const exact =
|
|
13300
|
+
const exact = join9(gd, `${idOrPrefix}.json`);
|
|
13021
13301
|
if (existsSync6(exact)) {
|
|
13022
13302
|
try {
|
|
13023
13303
|
const target = readlinkSync(exact);
|
|
13024
|
-
return JSON.parse(
|
|
13304
|
+
return JSON.parse(readFileSync8(target, "utf8"));
|
|
13025
13305
|
} catch {
|
|
13026
13306
|
return void 0;
|
|
13027
13307
|
}
|
|
@@ -13031,8 +13311,8 @@ function globalSessionLoad(idOrPrefix) {
|
|
|
13031
13311
|
if (!f.endsWith(".json")) continue;
|
|
13032
13312
|
const base = f.slice(0, -5);
|
|
13033
13313
|
if (base.includes(idOrPrefix) || base.endsWith(idOrPrefix)) {
|
|
13034
|
-
const target = readlinkSync(
|
|
13035
|
-
return JSON.parse(
|
|
13314
|
+
const target = readlinkSync(join9(gd, f));
|
|
13315
|
+
return JSON.parse(readFileSync8(target, "utf8"));
|
|
13036
13316
|
}
|
|
13037
13317
|
}
|
|
13038
13318
|
} catch {
|
|
@@ -13046,15 +13326,15 @@ function globalSessionList() {
|
|
|
13046
13326
|
for (const f of readdirSync(gd)) {
|
|
13047
13327
|
if (!f.endsWith(".json")) continue;
|
|
13048
13328
|
try {
|
|
13049
|
-
const target = readlinkSync(
|
|
13329
|
+
const target = readlinkSync(join9(gd, f));
|
|
13050
13330
|
if (!existsSync6(target)) {
|
|
13051
13331
|
try {
|
|
13052
|
-
unlinkSync(
|
|
13332
|
+
unlinkSync(join9(gd, f));
|
|
13053
13333
|
} catch {
|
|
13054
13334
|
}
|
|
13055
13335
|
continue;
|
|
13056
13336
|
}
|
|
13057
|
-
metas.push(JSON.parse(
|
|
13337
|
+
metas.push(JSON.parse(readFileSync8(target, "utf8")).meta);
|
|
13058
13338
|
} catch {
|
|
13059
13339
|
}
|
|
13060
13340
|
}
|
|
@@ -13221,10 +13501,10 @@ ${formatDiff(ops)}`);
|
|
|
13221
13501
|
// cli/gitCheckpoints.ts
|
|
13222
13502
|
import { execFile as execFile3 } from "child_process";
|
|
13223
13503
|
import { promisify } from "util";
|
|
13224
|
-
import { writeFileSync as
|
|
13504
|
+
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync8 } from "fs";
|
|
13225
13505
|
import { homedir as homedir6 } from "os";
|
|
13226
|
-
import { join as
|
|
13227
|
-
var
|
|
13506
|
+
import { join as join10, resolve as resolve3, sep as sep3, parse as parsePath } from "path";
|
|
13507
|
+
var log31 = forComponent("checkpoints");
|
|
13228
13508
|
var exec = promisify(execFile3);
|
|
13229
13509
|
var DEFAULT_EXCLUDE = [".agent/", ".git/", "node_modules/", "dist/", "build/", ".next/", "target/", ".venv/", "__pycache__/", "*.log"];
|
|
13230
13510
|
function isBroadRoot(p) {
|
|
@@ -13266,11 +13546,11 @@ var ShadowRepo = class {
|
|
|
13266
13546
|
mkdirSync8(this.gitDir, { recursive: true });
|
|
13267
13547
|
const valid = await this.run("rev-parse", "--git-dir").then(() => true, () => false);
|
|
13268
13548
|
if (!valid) await this.run("init", "-q");
|
|
13269
|
-
mkdirSync8(
|
|
13270
|
-
|
|
13549
|
+
mkdirSync8(join10(this.gitDir, "info"), { recursive: true });
|
|
13550
|
+
writeFileSync6(join10(this.gitDir, "info", "exclude"), this.exclude.join("\n") + "\n");
|
|
13271
13551
|
this.ready = true;
|
|
13272
13552
|
} catch (e) {
|
|
13273
|
-
|
|
13553
|
+
log31.debug(`git checkpoints unavailable for ${this.workTree}`, e);
|
|
13274
13554
|
this.ready = false;
|
|
13275
13555
|
}
|
|
13276
13556
|
return this.ready;
|
|
@@ -13281,7 +13561,7 @@ var ShadowRepo = class {
|
|
|
13281
13561
|
}
|
|
13282
13562
|
async commit(label, forced = []) {
|
|
13283
13563
|
await this.run("add", "-A");
|
|
13284
|
-
for (const p of forced) await this.run("add", "-f", "--", p).catch((e) =>
|
|
13564
|
+
for (const p of forced) await this.run("add", "-f", "--", p).catch((e) => log31.debug(`force-add failed: ${p}`, e));
|
|
13285
13565
|
await this.run("commit", "--allow-empty", "-q", "-m", label);
|
|
13286
13566
|
}
|
|
13287
13567
|
/** Inject the CURRENT (pre-edit) content of `paths` into the turn-open restore point by amending it.
|
|
@@ -13289,8 +13569,8 @@ var ShadowRepo = class {
|
|
|
13289
13569
|
* turn-boundary `add -A` would never have captured it. Amend (vs a new commit) keeps one restore
|
|
13290
13570
|
* point per turn, so the REPL's turn↔frame mapping stays intact. */
|
|
13291
13571
|
async amendForced(paths) {
|
|
13292
|
-
for (const p of paths) await this.run("add", "-f", "--", p).catch((e) =>
|
|
13293
|
-
await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) =>
|
|
13572
|
+
for (const p of paths) await this.run("add", "-f", "--", p).catch((e) => log31.debug(`force-capture failed: ${p}`, e));
|
|
13573
|
+
await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) => log31.debug("amend failed", e));
|
|
13294
13574
|
}
|
|
13295
13575
|
/** Commits on `ref`, oldest-first (canonical index space). */
|
|
13296
13576
|
async log(ref) {
|
|
@@ -13350,7 +13630,7 @@ var ShadowRepo = class {
|
|
|
13350
13630
|
await this.run("gc", "--auto", "-q").catch(() => {
|
|
13351
13631
|
});
|
|
13352
13632
|
} catch (e) {
|
|
13353
|
-
|
|
13633
|
+
log31.debug("checkpoint prune failed", e);
|
|
13354
13634
|
}
|
|
13355
13635
|
}
|
|
13356
13636
|
};
|
|
@@ -13382,7 +13662,7 @@ var GitCheckpoints = class {
|
|
|
13382
13662
|
const abs = resolve3(d);
|
|
13383
13663
|
if (abs === cwd || abs.startsWith(cwd + sep3)) continue;
|
|
13384
13664
|
if (cwd.startsWith(abs + sep3)) continue;
|
|
13385
|
-
out.push({ workTree: abs, gitDir:
|
|
13665
|
+
out.push({ workTree: abs, gitDir: join10(abs, ".agent", "checkpoints.git") });
|
|
13386
13666
|
}
|
|
13387
13667
|
return out;
|
|
13388
13668
|
}
|
|
@@ -13418,7 +13698,7 @@ var GitCheckpoints = class {
|
|
|
13418
13698
|
use(sessionId) {
|
|
13419
13699
|
if (sessionId === this.session) return;
|
|
13420
13700
|
this.session = sessionId;
|
|
13421
|
-
if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) =>
|
|
13701
|
+
if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) => log31.debug("re-point failed", e));
|
|
13422
13702
|
}
|
|
13423
13703
|
async begin(label) {
|
|
13424
13704
|
if (!await this.start()) return;
|
|
@@ -13438,7 +13718,7 @@ var GitCheckpoints = class {
|
|
|
13438
13718
|
process.stderr.write(`\x1B[2m checkpoints: '${this.repos[i].root}' too large to snapshot in ${Math.round(this.options.opTimeoutMs / 1e3)}s \u2014 file rewind disabled here\x1B[0m
|
|
13439
13719
|
`);
|
|
13440
13720
|
} else {
|
|
13441
|
-
|
|
13721
|
+
log31.debug("checkpoint commit failed", e);
|
|
13442
13722
|
survivors.push(this.repos[i]);
|
|
13443
13723
|
survivorCaches.push(this.caches[i] ?? []);
|
|
13444
13724
|
}
|
|
@@ -13473,7 +13753,7 @@ var GitCheckpoints = class {
|
|
|
13473
13753
|
if (this.forced.has(abs)) continue;
|
|
13474
13754
|
this.forced.add(abs);
|
|
13475
13755
|
const i = this.repoIndexFor(abs);
|
|
13476
|
-
if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) =>
|
|
13756
|
+
if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) => log31.debug("amendForced failed", e));
|
|
13477
13757
|
}
|
|
13478
13758
|
}
|
|
13479
13759
|
};
|
|
@@ -13525,7 +13805,7 @@ var GitCheckpointsOptions = class {
|
|
|
13525
13805
|
/** Real working tree to snapshot (the launch cwd). */
|
|
13526
13806
|
workTree = process.cwd();
|
|
13527
13807
|
/** Isolated git dir for the cwd shadow repo (kept out of the user's real .git). */
|
|
13528
|
-
gitDir =
|
|
13808
|
+
gitDir = join10(process.cwd(), ".agent", "checkpoints.git");
|
|
13529
13809
|
/** Extra mounted dirs (`--add-dir`); those outside cwd each get their own shadow repo. */
|
|
13530
13810
|
addDirs = [];
|
|
13531
13811
|
/** Conversation id → per-session restore-point ref. */
|
|
@@ -13541,9 +13821,9 @@ var GitCheckpointsOptions = class {
|
|
|
13541
13821
|
};
|
|
13542
13822
|
|
|
13543
13823
|
// cli/permissions.ts
|
|
13544
|
-
import { writeFileSync as
|
|
13824
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync9 } from "fs";
|
|
13545
13825
|
import { homedir as homedir7 } from "os";
|
|
13546
|
-
import { join as
|
|
13826
|
+
import { join as join11 } from "path";
|
|
13547
13827
|
var RULE_RE = /^(\w[\w*-]*)(?:\((.+)\))?$/;
|
|
13548
13828
|
function parseOne(raw, decision) {
|
|
13549
13829
|
const m = RULE_RE.exec(raw.trim());
|
|
@@ -13565,13 +13845,13 @@ function parsePermRules(perms, explicit = false) {
|
|
|
13565
13845
|
function describeRule(r) {
|
|
13566
13846
|
return `${r.decision.padEnd(5)} ${r.tool ?? "*"}${r.pathGlob ? `(${r.pathGlob})` : ""}`;
|
|
13567
13847
|
}
|
|
13568
|
-
var PERM_FILE = (cwd) =>
|
|
13848
|
+
var PERM_FILE = (cwd) => join11(cwd, ".agent", "permissions.json");
|
|
13569
13849
|
function loadPersistedRules(cwd) {
|
|
13570
13850
|
const j = readJsonFile(PERM_FILE(cwd), null);
|
|
13571
13851
|
return j ? { allow: j.allow ?? [], ask: j.ask ?? [], deny: j.deny ?? [] } : {};
|
|
13572
13852
|
}
|
|
13573
13853
|
function loadClaudeSettings(cwd, home = homedir7()) {
|
|
13574
|
-
const files = [
|
|
13854
|
+
const files = [join11(home, ".claude", "settings.json"), join11(cwd, ".claude", "settings.json"), join11(cwd, ".claude", "settings.local.json")];
|
|
13575
13855
|
let out = {};
|
|
13576
13856
|
for (const p of files) {
|
|
13577
13857
|
const perms = readJsonFile(p, null)?.permissions;
|
|
@@ -13584,8 +13864,8 @@ function persistRule(cwd, decision, ruleStr) {
|
|
|
13584
13864
|
const list = cur[decision] ??= [];
|
|
13585
13865
|
if (!list.includes(ruleStr)) list.push(ruleStr);
|
|
13586
13866
|
try {
|
|
13587
|
-
mkdirSync9(
|
|
13588
|
-
|
|
13867
|
+
mkdirSync9(join11(cwd, ".agent"), { recursive: true });
|
|
13868
|
+
writeFileSync7(PERM_FILE(cwd), JSON.stringify(cur, null, 2) + "\n");
|
|
13589
13869
|
} catch {
|
|
13590
13870
|
}
|
|
13591
13871
|
}
|
|
@@ -13598,7 +13878,7 @@ function mergePerms(a, b) {
|
|
|
13598
13878
|
}
|
|
13599
13879
|
return Object.keys(out).length ? out : void 0;
|
|
13600
13880
|
}
|
|
13601
|
-
var TRUST_FILE =
|
|
13881
|
+
var TRUST_FILE = join11(homedir7(), ".agent", "trusted.json");
|
|
13602
13882
|
function isTrusted(cwd, file = TRUST_FILE) {
|
|
13603
13883
|
const list = readJsonFile(file, []);
|
|
13604
13884
|
return Array.isArray(list) && list.includes(cwd);
|
|
@@ -13608,8 +13888,8 @@ function trustDir(cwd, file = TRUST_FILE) {
|
|
|
13608
13888
|
if (!Array.isArray(list)) list = [];
|
|
13609
13889
|
if (!list.includes(cwd)) list.push(cwd);
|
|
13610
13890
|
try {
|
|
13611
|
-
mkdirSync9(
|
|
13612
|
-
|
|
13891
|
+
mkdirSync9(join11(file, ".."), { recursive: true });
|
|
13892
|
+
writeFileSync7(file, JSON.stringify(list, null, 2) + "\n");
|
|
13613
13893
|
} catch {
|
|
13614
13894
|
}
|
|
13615
13895
|
}
|
|
@@ -13672,10 +13952,10 @@ function completePath(listDir, ref) {
|
|
|
13672
13952
|
|
|
13673
13953
|
// cli/lineEditor.ts
|
|
13674
13954
|
import { emitKeypressEvents } from "readline";
|
|
13675
|
-
import { spawnSync as
|
|
13676
|
-
import { writeFileSync as
|
|
13677
|
-
import { tmpdir as
|
|
13678
|
-
import { join as
|
|
13955
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
13956
|
+
import { writeFileSync as writeFileSync8, readFileSync as readFileSync9, unlinkSync as unlinkSync2 } from "fs";
|
|
13957
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
13958
|
+
import { join as join12 } from "path";
|
|
13679
13959
|
|
|
13680
13960
|
// cli/bidi.ts
|
|
13681
13961
|
var RTL_RE = /[\u0590-\u05ff\u0600-\u06ff\u0750-\u077f\u08a0-\u08ff\ufb1d-\ufdff\ufe70-\ufeff]/;
|
|
@@ -14862,14 +15142,14 @@ function createLineEditor(out, input = keyInput) {
|
|
|
14862
15142
|
function externalEdit(s) {
|
|
14863
15143
|
const spec = process.env.VISUAL || process.env.EDITOR || "vi";
|
|
14864
15144
|
const [cmd, ...cargs] = spec.split(" ").filter(Boolean);
|
|
14865
|
-
const file =
|
|
15145
|
+
const file = join12(tmpdir3(), `agentx-edit-${process.pid}-${Date.now()}.md`);
|
|
14866
15146
|
try {
|
|
14867
|
-
|
|
15147
|
+
writeFileSync8(file, s.buf);
|
|
14868
15148
|
process.stdin.setRawMode(false);
|
|
14869
15149
|
out.write("\x1B[?2004l");
|
|
14870
|
-
const r =
|
|
15150
|
+
const r = spawnSync5(cmd, [...cargs, file], { stdio: "inherit" });
|
|
14871
15151
|
if (r.status === 0) {
|
|
14872
|
-
const text =
|
|
15152
|
+
const text = readFileSync9(file, "utf8").replace(/\n$/, "");
|
|
14873
15153
|
s.reset();
|
|
14874
15154
|
if (text) s.insert(text);
|
|
14875
15155
|
}
|
|
@@ -15322,24 +15602,24 @@ function readPlainLine() {
|
|
|
15322
15602
|
}
|
|
15323
15603
|
|
|
15324
15604
|
// cli/osScheduler.ts
|
|
15325
|
-
import { spawnSync as
|
|
15326
|
-
import { writeFileSync as
|
|
15605
|
+
import { spawnSync as spawnSync6 } from "child_process";
|
|
15606
|
+
import { writeFileSync as writeFileSync9, mkdirSync as mkdirSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3, chmodSync, existsSync as existsSync7 } from "fs";
|
|
15327
15607
|
import { homedir as homedir8 } from "os";
|
|
15328
|
-
import { join as
|
|
15329
|
-
var
|
|
15608
|
+
import { join as join13 } from "path";
|
|
15609
|
+
var log32 = forComponent("os-sched");
|
|
15330
15610
|
var OsScheduler = class {
|
|
15331
15611
|
options;
|
|
15332
15612
|
constructor(options) {
|
|
15333
15613
|
this.options = { ...new OsSchedulerOptions(), ...options };
|
|
15334
15614
|
}
|
|
15335
15615
|
get dir() {
|
|
15336
|
-
return
|
|
15616
|
+
return join13(this.options.home, ".agent", "sched");
|
|
15337
15617
|
}
|
|
15338
15618
|
label(id) {
|
|
15339
15619
|
return `cc.livx.agentx.sched-${id}`;
|
|
15340
15620
|
}
|
|
15341
15621
|
plistPath(id) {
|
|
15342
|
-
return
|
|
15622
|
+
return join13(this.options.home, "Library", "LaunchAgents", `${this.label(id)}.plist`);
|
|
15343
15623
|
}
|
|
15344
15624
|
run(cmd, args, input) {
|
|
15345
15625
|
return this.options.exec(cmd, args, input);
|
|
@@ -15355,11 +15635,11 @@ var OsScheduler = class {
|
|
|
15355
15635
|
const script = this.writeScript(spec, oneOff);
|
|
15356
15636
|
const mechanism = this.options.platform === "darwin" ? this.scheduleDarwin(spec, script) : this.scheduleLinux(spec, script, oneOff);
|
|
15357
15637
|
const meta = { ...spec, created: Date.now(), mechanism };
|
|
15358
|
-
|
|
15638
|
+
writeFileSync9(join13(this.dir, `${spec.id}.json`), JSON.stringify(meta, null, 2));
|
|
15359
15639
|
return mechanism;
|
|
15360
15640
|
}
|
|
15361
15641
|
cancel(id) {
|
|
15362
|
-
const meta = readJsonFile(
|
|
15642
|
+
const meta = readJsonFile(join13(this.dir, `${id}.json`), null);
|
|
15363
15643
|
if (!meta) return false;
|
|
15364
15644
|
try {
|
|
15365
15645
|
if (this.options.platform === "darwin") {
|
|
@@ -15388,11 +15668,11 @@ var OsScheduler = class {
|
|
|
15388
15668
|
}
|
|
15389
15669
|
}
|
|
15390
15670
|
} catch (e) {
|
|
15391
|
-
|
|
15671
|
+
log32.debug(`cancel ${id}`, e);
|
|
15392
15672
|
}
|
|
15393
15673
|
for (const f of [`${id}.json`, `${id}.sh`]) {
|
|
15394
15674
|
try {
|
|
15395
|
-
unlinkSync3(
|
|
15675
|
+
unlinkSync3(join13(this.dir, f));
|
|
15396
15676
|
} catch {
|
|
15397
15677
|
}
|
|
15398
15678
|
}
|
|
@@ -15400,19 +15680,19 @@ var OsScheduler = class {
|
|
|
15400
15680
|
}
|
|
15401
15681
|
list() {
|
|
15402
15682
|
if (!existsSync7(this.dir)) return [];
|
|
15403
|
-
return readdirSync2(this.dir).filter((f) => f.endsWith(".json")).map((f) => readJsonFile(
|
|
15683
|
+
return readdirSync2(this.dir).filter((f) => f.endsWith(".json")).map((f) => readJsonFile(join13(this.dir, f), null)).filter(Boolean);
|
|
15404
15684
|
}
|
|
15405
15685
|
/** The per-job runner script: cd to the project, headless-resume the session, log, notify. */
|
|
15406
15686
|
writeScript(spec, oneOff) {
|
|
15407
|
-
const p =
|
|
15687
|
+
const p = join13(this.dir, `${spec.id}.sh`);
|
|
15408
15688
|
const q2 = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
|
|
15409
|
-
const cleanup = oneOff ? this.options.platform === "darwin" ? `launchctl remove ${this.label(spec.id)} 2>/dev/null; rm -f ${q2(this.plistPath(spec.id))} ${q2(
|
|
15410
|
-
` : `rm -f ${q2(
|
|
15689
|
+
const cleanup = oneOff ? this.options.platform === "darwin" ? `launchctl remove ${this.label(spec.id)} 2>/dev/null; rm -f ${q2(this.plistPath(spec.id))} ${q2(join13(this.dir, `${spec.id}.json`))} ${q2(p)}
|
|
15690
|
+
` : `rm -f ${q2(join13(this.dir, `${spec.id}.json`))} ${q2(p)}
|
|
15411
15691
|
` : "";
|
|
15412
|
-
|
|
15692
|
+
writeFileSync9(p, `#!/bin/sh
|
|
15413
15693
|
# agentx scheduled job ${spec.id}${spec.label ? ` \u2014 ${spec.label}` : ""}
|
|
15414
15694
|
cd ${q2(spec.cwd)} || exit 1
|
|
15415
|
-
${this.options.agentx} -p ${q2(spec.prompt)} --resume ${q2(spec.sessionId)} --yes >> ${q2(
|
|
15695
|
+
${this.options.agentx} -p ${q2(spec.prompt)} --resume ${q2(spec.sessionId)} --yes >> ${q2(join13(this.dir, `${spec.id}.log`))} 2>&1
|
|
15416
15696
|
${cleanup}`);
|
|
15417
15697
|
chmodSync(p, 493);
|
|
15418
15698
|
return p;
|
|
@@ -15449,8 +15729,8 @@ ${trigger}
|
|
|
15449
15729
|
<key>RunAtLoad</key><false/>
|
|
15450
15730
|
</dict></plist>
|
|
15451
15731
|
`;
|
|
15452
|
-
mkdirSync10(
|
|
15453
|
-
|
|
15732
|
+
mkdirSync10(join13(this.options.home, "Library", "LaunchAgents"), { recursive: true });
|
|
15733
|
+
writeFileSync9(this.plistPath(spec.id), plist);
|
|
15454
15734
|
this.run("launchctl", ["load", this.plistPath(spec.id)]);
|
|
15455
15735
|
return `launchd:${this.label(spec.id)}`;
|
|
15456
15736
|
}
|
|
@@ -15487,7 +15767,7 @@ var OsSchedulerOptions = class {
|
|
|
15487
15767
|
/** Injectable executor for tests. Returns stdout+stderr merged — `at` reports "job N" on STDERR,
|
|
15488
15768
|
* and the job id is what atrm needs for cancel. Throws on non-zero exit. */
|
|
15489
15769
|
exec = (cmd, args, input) => {
|
|
15490
|
-
const r =
|
|
15770
|
+
const r = spawnSync6(cmd, args, { input, encoding: "utf8" });
|
|
15491
15771
|
if (r.error) throw r.error;
|
|
15492
15772
|
if (r.status !== 0) throw new Error(`${cmd} exited ${r.status}: ${r.stderr || r.stdout}`);
|
|
15493
15773
|
return `${r.stdout ?? ""}${r.stderr ?? ""}`;
|
|
@@ -15504,10 +15784,10 @@ import { createServer as createServer2, createConnection } from "net";
|
|
|
15504
15784
|
import { spawn as spawn3 } from "child_process";
|
|
15505
15785
|
import { existsSync as existsSync8, mkdirSync as mkdirSync11, unlinkSync as unlinkSync4, readdirSync as readdirSync3 } from "fs";
|
|
15506
15786
|
import { homedir as homedir9 } from "os";
|
|
15507
|
-
import { join as
|
|
15508
|
-
var
|
|
15509
|
-
var TRIGGER_DIR = () =>
|
|
15510
|
-
var sockPath = (sessionId, dir = TRIGGER_DIR()) =>
|
|
15787
|
+
import { join as join14 } from "path";
|
|
15788
|
+
var log33 = forComponent("remote-trigger");
|
|
15789
|
+
var TRIGGER_DIR = () => join14(homedir9(), ".agent", "triggers");
|
|
15790
|
+
var sockPath = (sessionId, dir = TRIGGER_DIR()) => join14(dir, `${sessionId}.sock`);
|
|
15511
15791
|
var TriggerServer = class {
|
|
15512
15792
|
constructor(onTrigger) {
|
|
15513
15793
|
this.onTrigger = onTrigger;
|
|
@@ -15543,13 +15823,13 @@ var TriggerServer = class {
|
|
|
15543
15823
|
conn.end(JSON.stringify({ ok: false, error: String(e) }) + "\n");
|
|
15544
15824
|
}
|
|
15545
15825
|
});
|
|
15546
|
-
conn.on("error", (e) =>
|
|
15826
|
+
conn.on("error", (e) => log33.debug("trigger conn error", e));
|
|
15547
15827
|
});
|
|
15548
|
-
this.server.on("error", (e) =>
|
|
15828
|
+
this.server.on("error", (e) => log33.debug("trigger server error", e));
|
|
15549
15829
|
this.server.listen(p);
|
|
15550
15830
|
this.path = p;
|
|
15551
15831
|
} catch (e) {
|
|
15552
|
-
|
|
15832
|
+
log33.debug("trigger server unavailable", e);
|
|
15553
15833
|
}
|
|
15554
15834
|
}
|
|
15555
15835
|
/** Re-bind on /resume (the session id changed). */
|
|
@@ -15671,10 +15951,10 @@ var strike = C("9");
|
|
|
15671
15951
|
var link = (text, url) => useColor ? `\x1B]8;;${url}\x1B\\${cyan(text)}\x1B]8;;\x1B\\` : `${text} (${url})`;
|
|
15672
15952
|
var mdPalette = { bold, dim, cyan, italic, strike, green, link };
|
|
15673
15953
|
var err = (s) => process.stderr.write(s);
|
|
15674
|
-
var
|
|
15954
|
+
var log34 = forComponent("cli");
|
|
15675
15955
|
var VERSION = (() => {
|
|
15676
15956
|
try {
|
|
15677
|
-
return JSON.parse(
|
|
15957
|
+
return JSON.parse(readFileSync10(new URL("../package.json", import.meta.url), "utf8")).version ?? "?";
|
|
15678
15958
|
} catch {
|
|
15679
15959
|
return "?";
|
|
15680
15960
|
}
|
|
@@ -15693,7 +15973,7 @@ async function checkForUpdate(current) {
|
|
|
15693
15973
|
return `Update available: ${current} \u2192 ${latest}. Run \`bun add -g @livx.cc/agentx\` to update.`;
|
|
15694
15974
|
}
|
|
15695
15975
|
function installUpdate() {
|
|
15696
|
-
const r =
|
|
15976
|
+
const r = spawnSync7("bun", ["add", "-g", "@livx.cc/agentx"], { stdio: "inherit" });
|
|
15697
15977
|
return (r.status ?? 1) === 0;
|
|
15698
15978
|
}
|
|
15699
15979
|
var typeaheadOnLine = false;
|
|
@@ -15981,7 +16261,7 @@ function resolveModelOrNewest(model) {
|
|
|
15981
16261
|
var ENV_KEY_ALIASES = { google: ["GEMINI_API_KEY"] };
|
|
15982
16262
|
function loadEnvFile(file) {
|
|
15983
16263
|
if (!existsSync9(file)) return;
|
|
15984
|
-
for (const line of
|
|
16264
|
+
for (const line of readFileSync10(file, "utf8").split("\n")) {
|
|
15985
16265
|
const m = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
|
|
15986
16266
|
if (!m || m[1] in process.env) continue;
|
|
15987
16267
|
let val = m[2].trim();
|
|
@@ -15992,10 +16272,10 @@ function loadEnvFile(file) {
|
|
|
15992
16272
|
}
|
|
15993
16273
|
function loadInstallEnv() {
|
|
15994
16274
|
let dir = dirname5(import.meta.path);
|
|
15995
|
-
for (let i = 0; i < 5 && !existsSync9(
|
|
16275
|
+
for (let i = 0; i < 5 && !existsSync9(join15(dir, "package.json")); i++) dir = dirname5(dir);
|
|
15996
16276
|
for (const name of [".env", ".env.local"]) {
|
|
15997
|
-
loadEnvFile(
|
|
15998
|
-
loadEnvFile(
|
|
16277
|
+
loadEnvFile(join15(dir, name));
|
|
16278
|
+
loadEnvFile(join15(homedir10(), ".agent", name));
|
|
15999
16279
|
}
|
|
16000
16280
|
}
|
|
16001
16281
|
async function loadBodifySecrets() {
|
|
@@ -16046,6 +16326,7 @@ function makeHost(format = "text", opts) {
|
|
|
16046
16326
|
};
|
|
16047
16327
|
const activitySeen = /* @__PURE__ */ new Set();
|
|
16048
16328
|
const turnActivityIds = /* @__PURE__ */ new Set();
|
|
16329
|
+
const toolNames = /* @__PURE__ */ new Map();
|
|
16049
16330
|
let anonActivityCount = 0;
|
|
16050
16331
|
return {
|
|
16051
16332
|
flushText,
|
|
@@ -16129,7 +16410,17 @@ function makeHost(format = "text", opts) {
|
|
|
16129
16410
|
spinner.start();
|
|
16130
16411
|
return;
|
|
16131
16412
|
}
|
|
16413
|
+
if (streamJson && (e.kind === "tool_use" || e.kind === "tool_result")) {
|
|
16414
|
+
const t = e;
|
|
16415
|
+
if (t.kind === "tool_use") toolNames.set(t.id, t.name ?? "");
|
|
16416
|
+
const name = toolNames.get(t.id);
|
|
16417
|
+
if (t.kind === "tool_result") toolNames.delete(t.id);
|
|
16418
|
+
const line = t.kind === "tool_use" ? { type: "tool_activity", id: t.id, name: t.name, input: t.input, status: "running" } : { type: "tool_activity", id: t.id, ...name ? { name } : {}, output: typeof t.output === "string" ? t.output.slice(0, 4e3) : t.output, status: t.isError ? "error" : "completed" };
|
|
16419
|
+
process.stdout.write(JSON.stringify(line) + "\n");
|
|
16420
|
+
return;
|
|
16421
|
+
}
|
|
16132
16422
|
if (!("message" in e)) return;
|
|
16423
|
+
if (streamJson && e.kind === "retry") process.stdout.write(JSON.stringify({ type: "notice", kind: "retry", text: e.message }) + "\n");
|
|
16133
16424
|
flushLookupGroup();
|
|
16134
16425
|
closeReasonLine();
|
|
16135
16426
|
flushText();
|
|
@@ -16456,7 +16747,7 @@ function turnCost(model, usage) {
|
|
|
16456
16747
|
const priced = model.startsWith("claude-code/") ? `anthropic/${resolveModelAlias(model.slice("claude-code/".length))}` : model;
|
|
16457
16748
|
return costOf(getModelInfo(priced)?.pricing, usage?.promptTokens ?? 0, usage?.completionTokens ?? 0, usage?.cacheCreationTokens ?? 0, usage?.cacheReadTokens ?? 0, priced);
|
|
16458
16749
|
}
|
|
16459
|
-
async function evaluateGoal(ai, condition, transcript,
|
|
16750
|
+
async function evaluateGoal(ai, condition, transcript, log35) {
|
|
16460
16751
|
const recent = transcript.filter((m) => m.role === "assistant").slice(-8).map((m) => {
|
|
16461
16752
|
const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join(" ");
|
|
16462
16753
|
return text.slice(0, 600);
|
|
@@ -16476,7 +16767,7 @@ ${recent}` }
|
|
|
16476
16767
|
const match = r.content.match(/\{[\s\S]*\}/);
|
|
16477
16768
|
if (match) return JSON.parse(match[0]);
|
|
16478
16769
|
} catch (e) {
|
|
16479
|
-
|
|
16770
|
+
log35(dim(` (goal evaluator error: ${e?.message ?? e})
|
|
16480
16771
|
`));
|
|
16481
16772
|
}
|
|
16482
16773
|
return { met: false, reason: "evaluation unclear" };
|
|
@@ -16487,8 +16778,12 @@ function errInfo(e) {
|
|
|
16487
16778
|
function fmtUsd(n) {
|
|
16488
16779
|
return n >= 1 ? `$${n.toFixed(2)}` : `$${n.toFixed(4)}`;
|
|
16489
16780
|
}
|
|
16490
|
-
|
|
16491
|
-
|
|
16781
|
+
var imageInputOf = (m) => {
|
|
16782
|
+
const i = getModelInfo(m);
|
|
16783
|
+
return i ? !!i.imageInput || !!i.vision : void 0;
|
|
16784
|
+
};
|
|
16785
|
+
function estimateTranscriptTokens(messages, keepRecentImages = 1, vision = true) {
|
|
16786
|
+
return estimateTokens(messages, keepRecentImages, vision);
|
|
16492
16787
|
}
|
|
16493
16788
|
function formatStatus(s) {
|
|
16494
16789
|
const L = (k, v) => ` ${k.padEnd(12)} ${v}
|
|
@@ -16606,6 +16901,7 @@ function optsFor(args, ai, cfg = {}, extraTools = []) {
|
|
|
16606
16901
|
model,
|
|
16607
16902
|
// The real context window drives the context % and `/compact`'s budget (never `maxTokens` — that's spend).
|
|
16608
16903
|
contextWindow: getModelInfo(model)?.contextWindow ?? 0,
|
|
16904
|
+
imageInput: imageInputOf,
|
|
16609
16905
|
cwd: args.cwd,
|
|
16610
16906
|
tools: cfg.tools,
|
|
16611
16907
|
extraTools,
|
|
@@ -16697,13 +16993,13 @@ function mcpAgentTools(mounted, opts) {
|
|
|
16697
16993
|
return tools;
|
|
16698
16994
|
}
|
|
16699
16995
|
async function closeMcp(mounted) {
|
|
16700
|
-
await Promise.all(mounted.map((m) => m.client.close().catch((e) =>
|
|
16996
|
+
await Promise.all(mounted.map((m) => m.client.close().catch((e) => log34.debug("mcp close failed", e))));
|
|
16701
16997
|
}
|
|
16702
16998
|
var IMG_EXT = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
|
|
16703
16999
|
function mentionRefs(line) {
|
|
16704
17000
|
return [...line.matchAll(/(?:^|\s)@(?:"([^"]+)"|(\S+))/g)].map((m) => m[1] ?? m[2].replace(/[?!.,;:)\]}'">]+$/, "")).filter(Boolean);
|
|
16705
17001
|
}
|
|
16706
|
-
var untilde = (p) => p.startsWith("~/") ?
|
|
17002
|
+
var untilde = (p) => p.startsWith("~/") ? join15(homedir10(), p.slice(2)) : p;
|
|
16707
17003
|
function readImageParts(cwd, line) {
|
|
16708
17004
|
const refs = mentionRefs(line);
|
|
16709
17005
|
const parts = [];
|
|
@@ -16712,7 +17008,7 @@ function readImageParts(cwd, line) {
|
|
|
16712
17008
|
if (!mime) continue;
|
|
16713
17009
|
const abs = ref.startsWith("~/") ? untilde(ref) : resolve4(cwd, ref);
|
|
16714
17010
|
try {
|
|
16715
|
-
|
|
17011
|
+
statSync7(abs);
|
|
16716
17012
|
} catch {
|
|
16717
17013
|
continue;
|
|
16718
17014
|
}
|
|
@@ -16728,7 +17024,7 @@ function pastePathClassifier(cwd) {
|
|
|
16728
17024
|
if (!/^(\/|~\/|\.\/|\.\.\/)/.test(t)) return null;
|
|
16729
17025
|
const abs = t.startsWith("~/") ? untilde(t) : resolve4(cwd, t);
|
|
16730
17026
|
try {
|
|
16731
|
-
if (!
|
|
17027
|
+
if (!statSync7(abs).isFile()) return null;
|
|
16732
17028
|
} catch {
|
|
16733
17029
|
return null;
|
|
16734
17030
|
}
|
|
@@ -16749,7 +17045,7 @@ async function expandMentions(fs, line) {
|
|
|
16749
17045
|
if (loaded.includes(ref) || missing.includes(ref)) continue;
|
|
16750
17046
|
if (ref.includes(":") && mcpMentionResolver) {
|
|
16751
17047
|
const body = await mcpMentionResolver(ref).catch((e) => {
|
|
16752
|
-
|
|
17048
|
+
log34.debug("mcp mention resolve failed", e);
|
|
16753
17049
|
return null;
|
|
16754
17050
|
});
|
|
16755
17051
|
if (body != null) {
|
|
@@ -16789,7 +17085,8 @@ function jsonResult(res, session) {
|
|
|
16789
17085
|
sessionId: session.meta.id,
|
|
16790
17086
|
// The parked question, machine-readable — the caller answers it and resumes `sessionId`.
|
|
16791
17087
|
...res.finishReason === "needs_input" && res.question ? { question: res.question } : {},
|
|
16792
|
-
|
|
17088
|
+
// 'timeout' too: a provider stall ("model stalled: no stream output for 60s") vs the between-steps idle ceiling (no error).
|
|
17089
|
+
...(res.finishReason === "error" || res.finishReason === "empty" || res.finishReason === "timeout") && res.error ? { error: res.error?.message ?? String(res.error) } : {}
|
|
16793
17090
|
};
|
|
16794
17091
|
}
|
|
16795
17092
|
async function readMultiline(readLine) {
|
|
@@ -16838,7 +17135,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
16838
17135
|
try {
|
|
16839
17136
|
store.save(session);
|
|
16840
17137
|
} catch (ex) {
|
|
16841
|
-
|
|
17138
|
+
log34.debug("mid-turn session flush failed", ex);
|
|
16842
17139
|
}
|
|
16843
17140
|
}
|
|
16844
17141
|
return origNotify(e);
|
|
@@ -16906,7 +17203,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
16906
17203
|
}
|
|
16907
17204
|
return { ok, res };
|
|
16908
17205
|
}
|
|
16909
|
-
var remintSessionImages = (data, store, cwd) => remintImageRefs(data.messages, [
|
|
17206
|
+
var remintSessionImages = (data, store, cwd) => remintImageRefs(data.messages, [join15(store.dir, `${data.meta.id}.images`), cwd]);
|
|
16910
17207
|
function startSession(args, store, agent, cwd) {
|
|
16911
17208
|
if (args.resume || args.cont) {
|
|
16912
17209
|
const data = args.resume ? store.load(args.resume) ?? globalSessionLoad(args.resume) : store.latestData();
|
|
@@ -16953,25 +17250,25 @@ var AGENTS_MD_TEMPLATE = `# ${"${name}"}
|
|
|
16953
17250
|
`;
|
|
16954
17251
|
function initInstructions(cwd) {
|
|
16955
17252
|
for (const f of ["AGENTS.md", "CLAUDE.md"]) {
|
|
16956
|
-
if (existsSync9(
|
|
17253
|
+
if (existsSync9(join15(cwd, f))) {
|
|
16957
17254
|
err(yellow(` ${f} already exists \u2014 leaving it as-is
|
|
16958
17255
|
`));
|
|
16959
17256
|
return;
|
|
16960
17257
|
}
|
|
16961
17258
|
}
|
|
16962
|
-
const path =
|
|
16963
|
-
|
|
17259
|
+
const path = join15(cwd, "AGENTS.md");
|
|
17260
|
+
writeFileSync10(path, AGENTS_MD_TEMPLATE.replace("${name}", basename2(cwd)));
|
|
16964
17261
|
err(green(` created ${path}
|
|
16965
17262
|
`) + dim(" edit it, then it auto-loads into every run.\n"));
|
|
16966
17263
|
}
|
|
16967
17264
|
function persistSetting(cwd, key, value) {
|
|
16968
|
-
const path =
|
|
17265
|
+
const path = join15(cwd, ".agent", "settings.json");
|
|
16969
17266
|
try {
|
|
16970
|
-
const obj = existsSync9(path) ? JSON.parse(
|
|
17267
|
+
const obj = existsSync9(path) ? JSON.parse(readFileSync10(path, "utf8")) : {};
|
|
16971
17268
|
if (obj[key] === value) return;
|
|
16972
17269
|
obj[key] = value;
|
|
16973
17270
|
mkdirSync12(dirname5(path), { recursive: true });
|
|
16974
|
-
|
|
17271
|
+
writeFileSync10(path, JSON.stringify(obj, null, 2) + "\n");
|
|
16975
17272
|
} catch (e) {
|
|
16976
17273
|
err(yellow(` \u26A0 couldn't persist ${key} to ${path} \u2014 ${e?.message ?? e}
|
|
16977
17274
|
`));
|
|
@@ -16987,14 +17284,14 @@ var isCancelTeardown = (e) => {
|
|
|
16987
17284
|
function installCancelGuards(mounted) {
|
|
16988
17285
|
process.on("unhandledRejection", (e) => {
|
|
16989
17286
|
if (isCancelTeardown(e)) {
|
|
16990
|
-
|
|
17287
|
+
log34.debug("suppressed unhandledRejection (cursor stream cancel)", e);
|
|
16991
17288
|
return;
|
|
16992
17289
|
}
|
|
16993
|
-
|
|
17290
|
+
log34.error("unhandledRejection", e);
|
|
16994
17291
|
});
|
|
16995
17292
|
process.on("uncaughtException", (e) => {
|
|
16996
17293
|
if (isCancelTeardown(e)) {
|
|
16997
|
-
|
|
17294
|
+
log34.debug("suppressed uncaughtException (cursor stream cancel)", e);
|
|
16998
17295
|
return;
|
|
16999
17296
|
}
|
|
17000
17297
|
console.error(e);
|
|
@@ -17003,7 +17300,7 @@ function installCancelGuards(mounted) {
|
|
|
17003
17300
|
});
|
|
17004
17301
|
}
|
|
17005
17302
|
async function repl(args, ai, cfg, cwd) {
|
|
17006
|
-
const oauth = new McpOAuth({ storePath:
|
|
17303
|
+
const oauth = new McpOAuth({ storePath: join15(cwd, ".agent", "mcp-auth.json") });
|
|
17007
17304
|
const mounted = await mountMcp(cfg, oauth);
|
|
17008
17305
|
let mcpToolNames = [];
|
|
17009
17306
|
const initialMcpTools = mcpAgentTools(mounted);
|
|
@@ -17279,7 +17576,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
17279
17576
|
quickLook: {
|
|
17280
17577
|
branch: () => {
|
|
17281
17578
|
try {
|
|
17282
|
-
const head =
|
|
17579
|
+
const head = readFileSync10(join15(cwd, ".git", "HEAD"), "utf8").trim();
|
|
17283
17580
|
return head.startsWith("ref: refs/heads/") ? `branch: ${head.slice("ref: refs/heads/".length)}` : `detached HEAD at ${head.slice(0, 12)}`;
|
|
17284
17581
|
} catch {
|
|
17285
17582
|
return "not a git repository";
|
|
@@ -17413,11 +17710,11 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
17413
17710
|
const pendingImages = [];
|
|
17414
17711
|
const bangContext = [];
|
|
17415
17712
|
const grabClipboardAttachment = () => {
|
|
17416
|
-
const dir =
|
|
17713
|
+
const dir = join15(store.dir, `${session.meta.id}.images`);
|
|
17417
17714
|
try {
|
|
17418
17715
|
mkdirSync12(dir, { recursive: true });
|
|
17419
17716
|
} catch (e) {
|
|
17420
|
-
|
|
17717
|
+
log34.debug("paste image dir create failed", e);
|
|
17421
17718
|
}
|
|
17422
17719
|
const img = grabClipboardImage(dir, String(Date.now()));
|
|
17423
17720
|
return img ? { display: "Image", ref: "@" + img.path, path: img.path } : null;
|
|
@@ -17471,7 +17768,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
17471
17768
|
err(dim(` \u23F0 ${scheduler.size} scheduled job(s) re-armed
|
|
17472
17769
|
`));
|
|
17473
17770
|
}
|
|
17474
|
-
const checkpoints = args.vfs || args.boddb ? new CheckpointStack(agent.options.fs) : new GitCheckpoints({ workTree: cwd, gitDir:
|
|
17771
|
+
const checkpoints = args.vfs || args.boddb ? new CheckpointStack(agent.options.fs) : new GitCheckpoints({ workTree: cwd, gitDir: join15(cwd, ".agent", "checkpoints.git"), addDirs: args.addDirs, sessionId: session.meta.id });
|
|
17475
17772
|
const cpHooks = checkpoints.hooks?.();
|
|
17476
17773
|
if (cpHooks) {
|
|
17477
17774
|
agent.options.hooks = composeHooks(agent.options.hooks, cpHooks);
|
|
@@ -17523,14 +17820,14 @@ ${lines.join("\n")}
|
|
|
17523
17820
|
Added entries are loadable now via the Skill/SlashCommand tools; removed ones are gone even if still listed in the system prompt.
|
|
17524
17821
|
</system-reminder>`;
|
|
17525
17822
|
};
|
|
17526
|
-
const histPath =
|
|
17527
|
-
const history = existsSync9(histPath) ?
|
|
17528
|
-
const
|
|
17823
|
+
const histPath = join15(cwd, ".agent", "history");
|
|
17824
|
+
const history = existsSync9(histPath) ? readFileSync10(histPath, "utf8").split("\n").filter(Boolean).reverse().slice(0, 500) : [];
|
|
17825
|
+
const remember2 = (line) => {
|
|
17529
17826
|
try {
|
|
17530
|
-
mkdirSync12(
|
|
17827
|
+
mkdirSync12(join15(cwd, ".agent"), { recursive: true });
|
|
17531
17828
|
appendFileSync(histPath, line + "\n");
|
|
17532
17829
|
} catch (e) {
|
|
17533
|
-
|
|
17830
|
+
log34.debug("history write failed", e);
|
|
17534
17831
|
}
|
|
17535
17832
|
};
|
|
17536
17833
|
const ago = (t) => {
|
|
@@ -17602,7 +17899,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
|
|
|
17602
17899
|
try {
|
|
17603
17900
|
store.save(session);
|
|
17604
17901
|
} catch (e) {
|
|
17605
|
-
|
|
17902
|
+
log34.debug("session save after rewind failed", e);
|
|
17606
17903
|
}
|
|
17607
17904
|
err(green(" \u27F2 jumped back") + dim(` \u2014 ${face.transcript.length} message(s) kept; edit + resend
|
|
17608
17905
|
`));
|
|
@@ -17637,7 +17934,7 @@ ${task}`;
|
|
|
17637
17934
|
bangContext.length = 0;
|
|
17638
17935
|
}
|
|
17639
17936
|
const delta = await refreshCatalogs().catch((e) => {
|
|
17640
|
-
|
|
17937
|
+
log34.debug("catalog refresh failed", e);
|
|
17641
17938
|
return "";
|
|
17642
17939
|
});
|
|
17643
17940
|
if (delta) {
|
|
@@ -17815,7 +18112,7 @@ ${task}`;
|
|
|
17815
18112
|
context: {
|
|
17816
18113
|
desc: "context-window usage (messages + estimated tokens)",
|
|
17817
18114
|
run: () => {
|
|
17818
|
-
const est = estimateTranscriptTokens(face.transcript, face.options.keepRecentImages);
|
|
18115
|
+
const est = estimateTranscriptTokens(face.transcript, face.options.keepRecentImages, face.options.imageInput?.(face.options.model) !== false);
|
|
17819
18116
|
const cap = Agent.contextCap(face.options);
|
|
17820
18117
|
err(dim(` ${face.transcript.length} message(s) \xB7 ~${(est / 1e3).toFixed(1)}k tokens (~${Math.round(est / cap * 100)}% of ${Math.round(cap / 1e3)}k budget)
|
|
17821
18118
|
`));
|
|
@@ -17830,8 +18127,8 @@ ${task}`;
|
|
|
17830
18127
|
const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
|
|
17831
18128
|
if (wasRaw) process.stdin.setRawMode(false);
|
|
17832
18129
|
try {
|
|
17833
|
-
const { spawnSync:
|
|
17834
|
-
const r =
|
|
18130
|
+
const { spawnSync: spawnSync8 } = await import("child_process");
|
|
18131
|
+
const r = spawnSync8("less", ["-R"], { input: text, stdio: ["pipe", "inherit", "inherit"] });
|
|
17835
18132
|
if (r.error) err(text);
|
|
17836
18133
|
} finally {
|
|
17837
18134
|
if (wasRaw) process.stdin.setRawMode(true);
|
|
@@ -17851,8 +18148,8 @@ ${task}`;
|
|
|
17851
18148
|
keys.length ? ok(`provider keys: ${keys.join(", ")}`) : bad("no provider keys set (ANTHROPIC_API_KEY / OPENAI_API_KEY / GOOGLE_API_KEY / GROQ_API_KEY)");
|
|
17852
18149
|
process.env.BODIFY_API_KEY && process.env.BODIFY_APP_ID ? ok(`bodify secrets: ${process.env.BODIFY_APP_ID}`) : warn("bodify secrets: not configured (set BODIFY_API_KEY + BODIFY_APP_ID)");
|
|
17853
18150
|
{
|
|
17854
|
-
const { spawnSync:
|
|
17855
|
-
const has = (cmd) =>
|
|
18151
|
+
const { spawnSync: spawnSync8 } = await import("child_process");
|
|
18152
|
+
const has = (cmd) => spawnSync8("which", [cmd]).status === 0;
|
|
17856
18153
|
if (!VoiceIO.available()) warn("voice: keys missing (SONIOX_API_KEY / CARTESIA_API_KEY / CARTESIA_VOICE_ID) \u2014 voice disabled");
|
|
17857
18154
|
else {
|
|
17858
18155
|
ok("voice: keys present");
|
|
@@ -17873,7 +18170,7 @@ ${task}`;
|
|
|
17873
18170
|
try {
|
|
17874
18171
|
const probe = `${cwd}/.agent/sessions/.doctor-probe`;
|
|
17875
18172
|
mkdirSync12(`${cwd}/.agent/sessions`, { recursive: true });
|
|
17876
|
-
|
|
18173
|
+
writeFileSync10(probe, "ok");
|
|
17877
18174
|
unlinkSync5(probe);
|
|
17878
18175
|
ok(`session store writable (${cwd}/.agent/sessions)`);
|
|
17879
18176
|
} catch (e) {
|
|
@@ -17901,7 +18198,7 @@ ${task}`;
|
|
|
17901
18198
|
desc: "rescan skills/commands dirs and rebuild the system prompt (one cache miss) \u2014 picks up entries created mid-session",
|
|
17902
18199
|
run: async () => {
|
|
17903
18200
|
await refreshCatalogs().catch((e) => {
|
|
17904
|
-
|
|
18201
|
+
log34.debug("catalog refresh failed", e);
|
|
17905
18202
|
});
|
|
17906
18203
|
face.reprepare();
|
|
17907
18204
|
err(green(` \u2713 reloaded \u2014 ${skills.length} skill(s), ${cmds.length} command(s); system prompt rebuilds on next message
|
|
@@ -18283,8 +18580,8 @@ ${task}`;
|
|
|
18283
18580
|
const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
|
|
18284
18581
|
if (wasRaw) process.stdin.setRawMode(false);
|
|
18285
18582
|
try {
|
|
18286
|
-
const { spawnSync:
|
|
18287
|
-
|
|
18583
|
+
const { spawnSync: spawnSync8 } = await import("child_process");
|
|
18584
|
+
spawnSync8(ed, [idx], { stdio: "inherit" });
|
|
18288
18585
|
} finally {
|
|
18289
18586
|
if (wasRaw) process.stdin.setRawMode(true);
|
|
18290
18587
|
}
|
|
@@ -18403,7 +18700,7 @@ ${task}`;
|
|
|
18403
18700
|
try {
|
|
18404
18701
|
for (const def of (await loadAgents(fs2, d)).agents) if (!seen.has(def.name)) seen.set(def.name, { def, from: d });
|
|
18405
18702
|
} catch (e) {
|
|
18406
|
-
|
|
18703
|
+
log34.debug(`loadAgents(${d}) failed`, e);
|
|
18407
18704
|
}
|
|
18408
18705
|
}
|
|
18409
18706
|
if (!seen.size) {
|
|
@@ -18491,7 +18788,7 @@ ${task}`;
|
|
|
18491
18788
|
}
|
|
18492
18789
|
if (idx >= 0) {
|
|
18493
18790
|
const old = mounted.splice(idx, 1)[0];
|
|
18494
|
-
await old.client.close().catch((e) =>
|
|
18791
|
+
await old.client.close().catch((e) => log34.debug("mcp close failed", e));
|
|
18495
18792
|
}
|
|
18496
18793
|
try {
|
|
18497
18794
|
const m = await mountMcpServer(name, conf);
|
|
@@ -18519,7 +18816,7 @@ ${task}`;
|
|
|
18519
18816
|
}
|
|
18520
18817
|
const m = mounted.splice(idx, 1)[0];
|
|
18521
18818
|
remountMcpTools();
|
|
18522
|
-
await m.client.close().catch((e) =>
|
|
18819
|
+
await m.client.close().catch((e) => log34.debug("mcp close failed", e));
|
|
18523
18820
|
err(dim(` removed "${name}"
|
|
18524
18821
|
`));
|
|
18525
18822
|
return;
|
|
@@ -18634,11 +18931,11 @@ ${task}`;
|
|
|
18634
18931
|
return;
|
|
18635
18932
|
}
|
|
18636
18933
|
const md = exportMarkdown(session.meta, shown);
|
|
18637
|
-
const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" :
|
|
18934
|
+
const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" : join15(".agent", "exports", `${session.meta.id}.md`);
|
|
18638
18935
|
const path = resolve4(cwd, name);
|
|
18639
18936
|
try {
|
|
18640
18937
|
mkdirSync12(dirname5(path), { recursive: true });
|
|
18641
|
-
|
|
18938
|
+
writeFileSync10(path, md);
|
|
18642
18939
|
err(green(` \u2713 exported \u2192 ${path}
|
|
18643
18940
|
`) + dim(` ${shown.length} message(s) \xB7 ${md.length} chars
|
|
18644
18941
|
`));
|
|
@@ -18720,9 +19017,9 @@ ${task}`;
|
|
|
18720
19017
|
});
|
|
18721
19018
|
const listDir = (absDir) => {
|
|
18722
19019
|
try {
|
|
18723
|
-
return readdirSync4(
|
|
19020
|
+
return readdirSync4(join15(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
|
|
18724
19021
|
} catch (e) {
|
|
18725
|
-
|
|
19022
|
+
log34.debug("completion readdir failed", absDir, e);
|
|
18726
19023
|
return null;
|
|
18727
19024
|
}
|
|
18728
19025
|
};
|
|
@@ -18825,7 +19122,7 @@ ${task}`;
|
|
|
18825
19122
|
if (!opts?.id) takeCut(voiceIO, false);
|
|
18826
19123
|
if (/^[!#/]/.test(line)) dispatchPending = false;
|
|
18827
19124
|
history.unshift(line.replace(/\n+/g, " \u23CE "));
|
|
18828
|
-
|
|
19125
|
+
remember2(line.replace(/\n+/g, " \u23CE "));
|
|
18829
19126
|
if (line.startsWith("!")) {
|
|
18830
19127
|
const cmd = line.slice(1).trim();
|
|
18831
19128
|
if (cmd) {
|
|
@@ -18859,7 +19156,7 @@ ${out}
|
|
|
18859
19156
|
return;
|
|
18860
19157
|
}
|
|
18861
19158
|
await refreshCatalogs().catch((e) => {
|
|
18862
|
-
|
|
19159
|
+
log34.debug("catalog refresh failed", e);
|
|
18863
19160
|
});
|
|
18864
19161
|
const sk = skills.find((x) => x.name === name);
|
|
18865
19162
|
if (sk) {
|
|
@@ -18905,7 +19202,7 @@ ${out}
|
|
|
18905
19202
|
return false;
|
|
18906
19203
|
}
|
|
18907
19204
|
const fakeVoice = process.env.AGENTX_VOICE_FAKE ? fakeVoiceParts(process.env.AGENTX_VOICE_FAKE) : null;
|
|
18908
|
-
voiceDiag = new JsonlDiagSink(
|
|
19205
|
+
voiceDiag = new JsonlDiagSink(join15(store.dir, `${session.meta.id}.voice.jsonl`));
|
|
18909
19206
|
voiceIO = new VoiceIO({
|
|
18910
19207
|
...fakeVoice ?? {},
|
|
18911
19208
|
onDiag: (ev) => voiceDiag?.push(ev),
|
|
@@ -18970,7 +19267,7 @@ ${out}
|
|
|
18970
19267
|
if (delivery || cut) dx?.parkCutDelivery(delivery, !!cut, heard);
|
|
18971
19268
|
const cutReply = cut && cut.full.length - cut.heard.length > 40 ? cut : null;
|
|
18972
19269
|
const noteP = replaces ? replaces.note : !cutReply ? Promise.resolve("") : (instinct ? instinct.bargeIntent(text).catch((e) => {
|
|
18973
|
-
|
|
19270
|
+
log34.warn(`barge intent failed \u2014 model decides: ${e.message}`);
|
|
18974
19271
|
return void 0;
|
|
18975
19272
|
}) : Promise.resolve(void 0)).then(async (intent) => interruptionNote(cutReply, intent) + ackedNote(await instinct?.ackFor(text)));
|
|
18976
19273
|
const cutIn = replaces ? replaces.cutIn : interjectNote;
|
|
@@ -19128,7 +19425,7 @@ ${out}
|
|
|
19128
19425
|
err("\n");
|
|
19129
19426
|
const initial = prefill ?? (carry || void 0);
|
|
19130
19427
|
prefill = void 0;
|
|
19131
|
-
const ctxTok = estimateTranscriptTokens(face.transcript, face.options.keepRecentImages);
|
|
19428
|
+
const ctxTok = estimateTranscriptTokens(face.transcript, face.options.keepRecentImages, face.options.imageInput?.(face.options.model) !== false);
|
|
19132
19429
|
const ctxCap = Agent.contextCap(face.options);
|
|
19133
19430
|
{
|
|
19134
19431
|
const pct = Math.round(ctxTok / ctxCap * 100);
|
|
@@ -19308,7 +19605,7 @@ ${out}
|
|
|
19308
19605
|
}
|
|
19309
19606
|
async function duplexServe(args, ai, cfg, cwd) {
|
|
19310
19607
|
const out = (o) => process.stdout.write(JSON.stringify(o) + "\n");
|
|
19311
|
-
const mounted = await mountMcp(cfg, new McpOAuth({ storePath:
|
|
19608
|
+
const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join15(cwd, ".agent", "mcp-auth.json") }));
|
|
19312
19609
|
installCancelGuards(mounted);
|
|
19313
19610
|
const agent = await makeAgent(args, ai, cfg, mcpAgentTools(mounted, { quiet: true }), mounted.map((m) => m.name));
|
|
19314
19611
|
const { host: _h, stream: _s, signal: _g, providerOptions: _po, ...wo } = agent.options;
|
|
@@ -19319,6 +19616,7 @@ async function duplexServe(args, ai, cfg, cwd) {
|
|
|
19319
19616
|
else if (k === "thinking_delta") out({ type: "thinking", text: e.message });
|
|
19320
19617
|
else if (k === "speak_utterance") out({ type: "speak", text: e.message });
|
|
19321
19618
|
else if (k === "hold_filler") out({ type: "filler", text: e.message });
|
|
19619
|
+
else if (k === "turn_replace") out({ type: "replaced", ...e.data });
|
|
19322
19620
|
else if (k === "revoice_done") {
|
|
19323
19621
|
out({ type: "revoice_done" });
|
|
19324
19622
|
persist();
|
|
@@ -19351,7 +19649,7 @@ async function duplexServe(args, ai, cfg, cwd) {
|
|
|
19351
19649
|
try {
|
|
19352
19650
|
store.save(session);
|
|
19353
19651
|
} catch (e) {
|
|
19354
|
-
|
|
19652
|
+
log34.warn("duplex session save failed", e);
|
|
19355
19653
|
}
|
|
19356
19654
|
};
|
|
19357
19655
|
out({ type: "ready", sessionId: session.meta.id, reflexModel: dx.options.reflexModel, actModel: dx.options.actModel });
|
|
@@ -19374,8 +19672,8 @@ async function duplexServe(args, ai, cfg, cwd) {
|
|
|
19374
19672
|
if (delivery || reply) dx.parkCutDelivery(delivery, !!reply, heard);
|
|
19375
19673
|
const note = reply && reply.full.length - reply.heard.length > 40 ? interruptionNote(reply) : "";
|
|
19376
19674
|
const id = m.id ? String(m.id) : void 0;
|
|
19377
|
-
void runTurn(dx.voice, store, session, m.text + note, void 0, cwd, (c) => dx.send(c, { id, replaces: m.replaces ? String(m.replaces) : void 0 })).then(({ res }) => out({ type: "turn_done", id, finishReason: res?.finishReason ?? "error" })).catch((e) => {
|
|
19378
|
-
|
|
19675
|
+
void runTurn(dx.voice, store, session, m.text + note, void 0, cwd, (c) => dx.send(c, { id, replaces: m.replaces ? String(m.replaces) : void 0, followUp: typeof m.followUp === "string" || m.followUp && typeof m.followUp === "object" ? m.followUp : void 0 })).then(({ res }) => out({ type: "turn_done", id, finishReason: res?.finishReason ?? "error" })).catch((e) => {
|
|
19676
|
+
log34.error("duplex turn failed", e);
|
|
19379
19677
|
out({ type: "turn_done", id, finishReason: "error", error: String(e?.message ?? e) });
|
|
19380
19678
|
});
|
|
19381
19679
|
}
|
|
@@ -19489,7 +19787,7 @@ async function main() {
|
|
|
19489
19787
|
}
|
|
19490
19788
|
});
|
|
19491
19789
|
if (args.task) {
|
|
19492
|
-
const mounted = await mountMcp(cfg, new McpOAuth({ storePath:
|
|
19790
|
+
const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join15(cwd, ".agent", "mcp-auth.json") }));
|
|
19493
19791
|
const agent = await makeAgent(args, ai, cfg, mcpAgentTools(mounted), mounted.map((m) => m.name));
|
|
19494
19792
|
const store = new SessionStore(cwd);
|
|
19495
19793
|
const session = startSession(args, store, agent, cwd);
|