@livx.cc/agentx 0.99.72 → 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 +698 -425
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +447 -192
- 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/index.js
CHANGED
|
@@ -18,8 +18,192 @@ var init_logging = __esm({
|
|
|
18
18
|
}
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
+
// src/imageWire.ts
|
|
22
|
+
import { readFileSync, writeFileSync, statSync, mkdtempSync, rmSync } from "fs";
|
|
23
|
+
import { spawnSync } from "child_process";
|
|
24
|
+
import { createHash } from "crypto";
|
|
25
|
+
import { tmpdir } from "os";
|
|
26
|
+
import { join } from "path";
|
|
27
|
+
import { PNG } from "pngjs";
|
|
28
|
+
import jpeg from "jpeg-js";
|
|
29
|
+
function imageSize(b) {
|
|
30
|
+
const u16 = (i) => b[i] << 8 | b[i + 1];
|
|
31
|
+
const le16 = (i) => b[i] | b[i + 1] << 8;
|
|
32
|
+
const u32 = (i) => (b[i] << 24 >>> 0) + (b[i + 1] << 16) + (b[i + 2] << 8) + b[i + 3];
|
|
33
|
+
if (b[0] === 137 && b[1] === 80 && b.length >= 24) return { w: u32(16), h: u32(20) };
|
|
34
|
+
if (b[0] === 71 && b[1] === 73 && b.length >= 10) return { w: le16(6), h: le16(8) };
|
|
35
|
+
if (b[0] === 255 && b[1] === 216) {
|
|
36
|
+
for (let i = 2; i + 9 < b.length; ) {
|
|
37
|
+
if (b[i] !== 255) return null;
|
|
38
|
+
const m = b[i + 1];
|
|
39
|
+
if (m >= 192 && m <= 207 && m !== 196 && m !== 200 && m !== 204) return { w: u16(i + 7), h: u16(i + 5) };
|
|
40
|
+
i += 2 + u16(i + 2);
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
if (b.length >= 30 && String.fromCharCode(...b.subarray(8, 12)) === "WEBP") {
|
|
45
|
+
const kind = String.fromCharCode(...b.subarray(12, 16));
|
|
46
|
+
if (kind === "VP8X") return { w: 1 + (b[24] | b[25] << 8 | b[26] << 16), h: 1 + (b[27] | b[28] << 8 | b[29] << 16) };
|
|
47
|
+
if (kind === "VP8 ") return { w: le16(26) & 16383, h: le16(28) & 16383 };
|
|
48
|
+
if (kind === "VP8L") {
|
|
49
|
+
const v = b[21] | b[22] << 8 | b[23] << 16 | b[24] << 24;
|
|
50
|
+
return { w: (v & 16383) + 1, h: (v >> 14 & 16383) + 1 };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
function imageNote(what, bytes, dims, why) {
|
|
56
|
+
return `[image ${what}${dims ? `, ${dims.w}x${dims.h}` : ""}, ${fmtBytes(bytes)} \u2014 not sent: ${why}]`;
|
|
57
|
+
}
|
|
58
|
+
function downscaleJs(buf, maxSide) {
|
|
59
|
+
let src;
|
|
60
|
+
try {
|
|
61
|
+
if (buf[0] === 137 && buf[1] === 80) src = PNG.sync.read(buf);
|
|
62
|
+
else if (buf[0] === 255 && buf[1] === 216) src = jpeg.decode(buf, { useTArray: true, maxMemoryUsageInMB: 1024 });
|
|
63
|
+
else return null;
|
|
64
|
+
} catch (e) {
|
|
65
|
+
log2.warn("pure-JS image decode failed", e);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const { width: sw, height: sh, data: s } = src;
|
|
69
|
+
const k = Math.min(1, maxSide / Math.max(sw, sh));
|
|
70
|
+
const w = Math.max(1, Math.round(sw * k)), h = Math.max(1, Math.round(sh * k));
|
|
71
|
+
const d = new Uint8Array(w * h * 4);
|
|
72
|
+
for (let y = 0; y < h; y++) {
|
|
73
|
+
const y0 = Math.floor(y * sh / h), y1 = Math.max(y0 + 1, Math.floor((y + 1) * sh / h));
|
|
74
|
+
for (let x = 0; x < w; x++) {
|
|
75
|
+
const x0 = Math.floor(x * sw / w), x1 = Math.max(x0 + 1, Math.floor((x + 1) * sw / w));
|
|
76
|
+
let r = 0, g = 0, b = 0, a = 0;
|
|
77
|
+
for (let yy = y0; yy < y1; yy++) for (let xx = x0; xx < x1; xx++) {
|
|
78
|
+
const i = (yy * sw + xx) * 4, al = s[i + 3];
|
|
79
|
+
r += s[i] * al;
|
|
80
|
+
g += s[i + 1] * al;
|
|
81
|
+
b += s[i + 2] * al;
|
|
82
|
+
a += al;
|
|
83
|
+
}
|
|
84
|
+
const o = (y * w + x) * 4, n = (y1 - y0) * (x1 - x0), bg = 255 * (n - a / 255);
|
|
85
|
+
d[o] = (r / 255 + bg) / n;
|
|
86
|
+
d[o + 1] = (g / 255 + bg) / n;
|
|
87
|
+
d[o + 2] = (b / 255 + bg) / n;
|
|
88
|
+
d[o + 3] = 255;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
let out = Buffer.alloc(0);
|
|
92
|
+
for (const q of [80, 60, 40]) {
|
|
93
|
+
out = Buffer.from(jpeg.encode({ width: w, height: h, data: d }, q).data);
|
|
94
|
+
if (out.length <= IMAGE_MAX_BYTES) break;
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
function downscale(path, maxSide) {
|
|
99
|
+
const dir = mkdtempSync(join(tmpdir(), "agentx-img-"));
|
|
100
|
+
const out = join(dir, "x.jpg");
|
|
101
|
+
try {
|
|
102
|
+
const tries = [
|
|
103
|
+
["sips", ["-Z", String(maxSide), "-s", "format", "jpeg", "-s", "formatOptions", "80", path, "--out", out]],
|
|
104
|
+
["magick", [path, "-resize", `${maxSide}x${maxSide}>`, "-quality", "80", out]],
|
|
105
|
+
["convert", [path, "-resize", `${maxSide}x${maxSide}>`, "-quality", "80", out]]
|
|
106
|
+
];
|
|
107
|
+
for (const [cmd, args] of tries) {
|
|
108
|
+
const r = spawnSync(cmd, args, { stdio: "ignore", timeout: 15e3 });
|
|
109
|
+
if (r.status !== 0) continue;
|
|
110
|
+
try {
|
|
111
|
+
const o = readFileSync(out);
|
|
112
|
+
if (o.length <= IMAGE_MAX_BYTES) return o;
|
|
113
|
+
break;
|
|
114
|
+
} catch {
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const js = downscaleJs(readFileSync(path), maxSide);
|
|
118
|
+
if (!js) log2.warn(`could not downscale ${path} (tried sips/magick/convert/pure JS)`);
|
|
119
|
+
return js;
|
|
120
|
+
} finally {
|
|
121
|
+
rmSync(dir, { recursive: true, force: true });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function remember(id, w) {
|
|
125
|
+
cache.delete(id);
|
|
126
|
+
cache.set(id, w);
|
|
127
|
+
if (cache.size > CACHE_MAX) cache.delete(cache.keys().next().value);
|
|
128
|
+
return w;
|
|
129
|
+
}
|
|
130
|
+
function wireOf(key, buf, mime, label, file) {
|
|
131
|
+
const dims = imageSize(buf);
|
|
132
|
+
const fits = buf.length <= IMAGE_MAX_BYTES && (!dims || Math.max(dims.w, dims.h) <= IMAGE_MAX_SIDE);
|
|
133
|
+
let small = null;
|
|
134
|
+
if (!fits) {
|
|
135
|
+
if (file) small = downscale(file, IMAGE_MAX_SIDE);
|
|
136
|
+
else {
|
|
137
|
+
const dir = mkdtempSync(join(tmpdir(), "agentx-img-in-"));
|
|
138
|
+
try {
|
|
139
|
+
const f = join(dir, `in.${mime.split("/")[1] || "img"}`);
|
|
140
|
+
writeFileSync(f, buf);
|
|
141
|
+
small = downscale(f, IMAGE_MAX_SIDE);
|
|
142
|
+
} finally {
|
|
143
|
+
rmSync(dir, { recursive: true, force: true });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const url = fits ? `data:${mime};base64,${buf.toString("base64")}` : small && small.length <= IMAGE_MAX_BYTES ? `data:image/jpeg;base64,${small.toString("base64")}` : null;
|
|
148
|
+
return { key, url, note: imageNote(label, buf.length, dims, url ? "this model can't see images" : "too large and it couldn't be downscaled") };
|
|
149
|
+
}
|
|
150
|
+
function imageForWire(path, mime, vision) {
|
|
151
|
+
let st;
|
|
152
|
+
try {
|
|
153
|
+
st = statSync(path);
|
|
154
|
+
} catch (e) {
|
|
155
|
+
log2.warn(`image unreadable: ${path}`, e);
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
const key = `${st.mtimeMs}:${st.size}`;
|
|
159
|
+
let hit = cache.get(path);
|
|
160
|
+
if (!hit || hit.key !== key) hit = remember(path, wireOf(key, readFileSync(path), mime, path, path));
|
|
161
|
+
return vision && hit.url ? { url: hit.url } : { note: hit.note };
|
|
162
|
+
}
|
|
163
|
+
function inlineImageForWire(url, vision) {
|
|
164
|
+
const comma = url.indexOf(",");
|
|
165
|
+
const mime = url.slice(5, url.indexOf(";"));
|
|
166
|
+
if (vision && url.length < 3e5) {
|
|
167
|
+
const b64 = url.slice(comma + 1);
|
|
168
|
+
const dims = imageSize(Buffer.from(b64.slice(0, 4096), "base64")) ?? imageSize(Buffer.from(b64, "base64"));
|
|
169
|
+
if (!dims || Math.max(dims.w, dims.h) <= IMAGE_MAX_SIDE) return { url };
|
|
170
|
+
}
|
|
171
|
+
const id = `inline:${createHash("sha1").update(url).digest("base64")}`;
|
|
172
|
+
let hit = cache.get(id);
|
|
173
|
+
if (!hit) {
|
|
174
|
+
const buf = Buffer.from(url.slice(comma + 1), "base64");
|
|
175
|
+
hit = remember(id, wireOf(id, buf, mime, `(${mime})`));
|
|
176
|
+
}
|
|
177
|
+
return vision && hit.url ? { url: hit.url } : { note: hit.note };
|
|
178
|
+
}
|
|
179
|
+
function imageWireBytes(path, mime, vision) {
|
|
180
|
+
const r = imageForWire(path, mime, vision);
|
|
181
|
+
return !r ? 0 : "url" in r ? r.url.length : r.note.length;
|
|
182
|
+
}
|
|
183
|
+
function scrubBase64Text(s) {
|
|
184
|
+
if (s.length < 2e3) return s;
|
|
185
|
+
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]`);
|
|
186
|
+
if (out !== s) log2.warn(`scrubbed base64 from request text (${s.length} \u2192 ${out.length} chars)`);
|
|
187
|
+
return out;
|
|
188
|
+
}
|
|
189
|
+
var log2, IMAGE_MAX_SIDE, IMAGE_MAX_BYTES, fmtBytes, cache, CACHE_MAX, DATA_URI, B64_RUN;
|
|
190
|
+
var init_imageWire = __esm({
|
|
191
|
+
"src/imageWire.ts"() {
|
|
192
|
+
"use strict";
|
|
193
|
+
init_logging();
|
|
194
|
+
log2 = forComponent("imageWire");
|
|
195
|
+
IMAGE_MAX_SIDE = 1568;
|
|
196
|
+
IMAGE_MAX_BYTES = 1e6;
|
|
197
|
+
fmtBytes = (n) => n >= 1e6 ? `${(n / 1e6).toFixed(1)} MB` : n >= 1e3 ? `${Math.round(n / 1e3)} KB` : `${n} B`;
|
|
198
|
+
cache = /* @__PURE__ */ new Map();
|
|
199
|
+
CACHE_MAX = 8;
|
|
200
|
+
DATA_URI = /data:image\/[\w.+-]+;base64,[A-Za-z0-9+/=]+/g;
|
|
201
|
+
B64_RUN = /[A-Za-z0-9+/=]{2000,}/g;
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
21
205
|
// src/llm.ts
|
|
22
|
-
import {
|
|
206
|
+
import { statSync as statSync2, realpathSync } from "fs";
|
|
23
207
|
import { resolve, sep } from "path";
|
|
24
208
|
import { randomBytes } from "crypto";
|
|
25
209
|
function passesBackReasoning(model) {
|
|
@@ -76,9 +260,9 @@ function mintImageRef(path, mime) {
|
|
|
76
260
|
const token = randomBytes(16).toString("hex");
|
|
77
261
|
let bytes = 0;
|
|
78
262
|
try {
|
|
79
|
-
bytes = Math.ceil(
|
|
263
|
+
bytes = Math.ceil(statSync2(path).size * 4 / 3);
|
|
80
264
|
} catch (e) {
|
|
81
|
-
|
|
265
|
+
log3.warn(`image ref stat failed, weighing as 0: ${path}`, e);
|
|
82
266
|
}
|
|
83
267
|
refRegistry.set(token, { mime, path, bytes });
|
|
84
268
|
while (refRegistry.size > REF_REGISTRY_MAX) refRegistry.delete(refRegistry.keys().next().value);
|
|
@@ -94,7 +278,7 @@ function parseImageRef(url) {
|
|
|
94
278
|
const parts = refUrlParts(url);
|
|
95
279
|
const ref = parts && refRegistry.get(parts.token);
|
|
96
280
|
if (!ref) {
|
|
97
|
-
|
|
281
|
+
log3.debug("image ref not in registry \u2014 treating as non-image text");
|
|
98
282
|
return null;
|
|
99
283
|
}
|
|
100
284
|
return ref;
|
|
@@ -113,7 +297,7 @@ function remintImageRefs(messages, roots) {
|
|
|
113
297
|
try {
|
|
114
298
|
allowed.push(realpathSync(resolve(r)));
|
|
115
299
|
} catch (e) {
|
|
116
|
-
|
|
300
|
+
log3.debug(`image-ref root unavailable: ${r}`, e);
|
|
117
301
|
}
|
|
118
302
|
}
|
|
119
303
|
if (allowed.length === 0) return messages;
|
|
@@ -130,11 +314,11 @@ function remintImageRefs(messages, roots) {
|
|
|
130
314
|
try {
|
|
131
315
|
real = realpathSync(f.path);
|
|
132
316
|
} catch (e) {
|
|
133
|
-
|
|
317
|
+
log3.warn(`resumed image ref unreadable, will stub: ${f.path}`, e);
|
|
134
318
|
return p;
|
|
135
319
|
}
|
|
136
320
|
if (!inside(real)) {
|
|
137
|
-
|
|
321
|
+
log3.warn(`resumed image ref outside the allowed roots, will stub: ${f.path} -> ${real}`);
|
|
138
322
|
return p;
|
|
139
323
|
}
|
|
140
324
|
touched = true;
|
|
@@ -161,7 +345,7 @@ function toolImageRef(content) {
|
|
|
161
345
|
try {
|
|
162
346
|
return parseImageRef(JSON.parse(content).imageRef);
|
|
163
347
|
} catch (e) {
|
|
164
|
-
|
|
348
|
+
log3.debug("bad imageRef tool result", e);
|
|
165
349
|
return null;
|
|
166
350
|
}
|
|
167
351
|
}
|
|
@@ -189,32 +373,41 @@ function imageWindows(messages, keepRecent) {
|
|
|
189
373
|
const k = Math.max(0, keepRecent);
|
|
190
374
|
return /* @__PURE__ */ new Set([...attach.slice(Math.max(0, attach.length - k)), ...tool.slice(Math.max(0, tool.length - k))]);
|
|
191
375
|
}
|
|
192
|
-
function expandImagesForSend(messages, keepRecent = 1, stub = IMAGE_ELIDE_STUB) {
|
|
376
|
+
function expandImagesForSend(messages, keepRecent = 1, stub = IMAGE_ELIDE_STUB, vision = true) {
|
|
193
377
|
if (!messages.some(bearsImage)) return messages;
|
|
194
378
|
const keep = imageWindows(messages, keepRecent);
|
|
195
379
|
let changed = false;
|
|
196
380
|
const out = messages.map((m, i) => {
|
|
197
|
-
const content = keep.has(i) ? expandContent(m.content,
|
|
381
|
+
const content = keep.has(i) ? expandContent(m.content, vision) : stripContent(m.content, stub);
|
|
198
382
|
if (content === m.content) return m;
|
|
199
383
|
changed = true;
|
|
200
384
|
return { ...m, content };
|
|
201
385
|
});
|
|
202
386
|
return changed ? out : messages;
|
|
203
387
|
}
|
|
204
|
-
function sendBytes(messages, keepRecent = 1, stub = IMAGE_ELIDE_STUB) {
|
|
388
|
+
function sendBytes(messages, keepRecent = 1, vision = true, stub = IMAGE_ELIDE_STUB) {
|
|
205
389
|
let n = 0;
|
|
206
|
-
for (const b of sendBytesPer(messages, keepRecent, stub)) n += b;
|
|
390
|
+
for (const b of sendBytesPer(messages, keepRecent, vision, stub)) n += b;
|
|
207
391
|
return n;
|
|
208
392
|
}
|
|
209
|
-
function sendBytesPer(messages, keepRecent = 1, stub = IMAGE_ELIDE_STUB) {
|
|
393
|
+
function sendBytesPer(messages, keepRecent = 1, vision = true, stub = IMAGE_ELIDE_STUB) {
|
|
210
394
|
const keep = messages.some(bearsImage) ? imageWindows(messages, keepRecent) : null;
|
|
211
|
-
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));
|
|
395
|
+
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));
|
|
212
396
|
}
|
|
213
|
-
function wireBytes(content, kept, stub) {
|
|
397
|
+
function wireBytes(content, kept, vision, stub) {
|
|
214
398
|
const tool = toolImageRef(content);
|
|
215
|
-
if (tool) return kept ? tool.
|
|
399
|
+
if (tool) return kept ? imageWireBytes(tool.path, tool.mime, vision) || stub.length : stub.length;
|
|
216
400
|
if (content == null || typeof content === "string") return contentBytes(content);
|
|
217
|
-
if (kept)
|
|
401
|
+
if (kept) {
|
|
402
|
+
let n2 = 0;
|
|
403
|
+
for (const p of content) {
|
|
404
|
+
const url = p.type === "image_url" ? p.image_url?.url ?? "" : "";
|
|
405
|
+
const ref = url ? parseImageRef(url) : null;
|
|
406
|
+
const inline = url.startsWith("data:") ? inlineImageForWire(url, vision) : null;
|
|
407
|
+
n2 += ref ? imageWireBytes(ref.path, ref.mime, vision) : inline ? ("url" in inline ? inline.url : inline.note).length : contentBytes([p]);
|
|
408
|
+
}
|
|
409
|
+
return n2;
|
|
410
|
+
}
|
|
218
411
|
let n = 0;
|
|
219
412
|
for (const p of content) {
|
|
220
413
|
if (p.type === "text") {
|
|
@@ -226,31 +419,70 @@ function wireBytes(content, kept, stub) {
|
|
|
226
419
|
}
|
|
227
420
|
return n;
|
|
228
421
|
}
|
|
229
|
-
function
|
|
230
|
-
try {
|
|
231
|
-
return `data:${ref.mime};base64,${readFileSync(ref.path).toString("base64")}`;
|
|
232
|
-
} catch (e) {
|
|
233
|
-
log2.warn(`image ref unreadable, sending stub instead: ${ref.path}`, e);
|
|
234
|
-
return null;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
function expandContent(content, stub) {
|
|
422
|
+
function expandContent(content, vision) {
|
|
238
423
|
const tool = toolImageRef(content);
|
|
239
424
|
if (tool) {
|
|
240
|
-
const
|
|
241
|
-
|
|
425
|
+
const w = imageForWire(tool.path, tool.mime, vision);
|
|
426
|
+
if (!w) return IMAGE_ELIDE_STUB;
|
|
427
|
+
return "url" in w ? [{ type: "text", text: `image: ${tool.path}` }, imagePart(w.url)] : w.note;
|
|
242
428
|
}
|
|
243
429
|
if (typeof content === "string") return content;
|
|
244
430
|
let changed = false;
|
|
245
431
|
const out = content.map((p) => {
|
|
246
|
-
if (p.type !== "image_url"
|
|
432
|
+
if (p.type !== "image_url") return p;
|
|
433
|
+
const url = p.image_url?.url ?? "";
|
|
434
|
+
if (url.startsWith("data:")) {
|
|
435
|
+
const w2 = inlineImageForWire(url, vision);
|
|
436
|
+
if ("url" in w2 && w2.url === url) return p;
|
|
437
|
+
changed = true;
|
|
438
|
+
return "url" in w2 ? imagePart(w2.url) : { type: "text", text: w2.note };
|
|
439
|
+
}
|
|
440
|
+
if (!isImageRefUrl(url)) return p;
|
|
247
441
|
changed = true;
|
|
248
|
-
const ref = parseImageRef(
|
|
249
|
-
|
|
250
|
-
|
|
442
|
+
const ref = parseImageRef(url);
|
|
443
|
+
if (!ref) return { type: "text", text: IMAGE_ELIDE_STUB };
|
|
444
|
+
const w = imageForWire(ref.path, ref.mime, vision);
|
|
445
|
+
if (!w) return { type: "text", text: IMAGE_ELIDE_STUB };
|
|
446
|
+
return "url" in w ? imagePart(w.url) : { type: "text", text: w.note };
|
|
251
447
|
});
|
|
252
448
|
return changed ? out : content;
|
|
253
449
|
}
|
|
450
|
+
function hoistToolImages(messages) {
|
|
451
|
+
if (!messages.some((m) => m.role === "tool" && Array.isArray(m.content) && m.content.some((p) => p.type === "image_url"))) return messages;
|
|
452
|
+
const out = [];
|
|
453
|
+
let pending = [];
|
|
454
|
+
const flush = () => {
|
|
455
|
+
if (pending.length) out.push({ role: "user", content: [{ type: "text", text: "Image output from the tool call above:" }, ...pending] });
|
|
456
|
+
pending = [];
|
|
457
|
+
};
|
|
458
|
+
for (const m of messages) {
|
|
459
|
+
if (m.role !== "tool") flush();
|
|
460
|
+
if (m.role === "tool" && Array.isArray(m.content) && m.content.some((p) => p.type === "image_url")) {
|
|
461
|
+
const imgs = m.content.filter((p) => p.type === "image_url");
|
|
462
|
+
pending.push(...imgs);
|
|
463
|
+
const text = m.content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
|
|
464
|
+
out.push({ ...m, content: text || `[${imgs.length} image(s) returned; shown below]` });
|
|
465
|
+
} else out.push(m);
|
|
466
|
+
}
|
|
467
|
+
flush();
|
|
468
|
+
return out;
|
|
469
|
+
}
|
|
470
|
+
function scrubBase64(messages) {
|
|
471
|
+
let changed = false;
|
|
472
|
+
const out = messages.map((m) => {
|
|
473
|
+
const c = m.content;
|
|
474
|
+
let next = c;
|
|
475
|
+
if (typeof c === "string") next = scrubBase64Text(c);
|
|
476
|
+
else if (Array.isArray(c)) {
|
|
477
|
+
const parts = c.map((p) => p.type === "text" && p.text ? { ...p, text: scrubBase64Text(p.text) } : p);
|
|
478
|
+
if (parts.some((p, i) => p.text !== c[i].text)) next = parts;
|
|
479
|
+
}
|
|
480
|
+
if (next === c) return m;
|
|
481
|
+
changed = true;
|
|
482
|
+
return { ...m, content: next };
|
|
483
|
+
});
|
|
484
|
+
return changed ? out : messages;
|
|
485
|
+
}
|
|
254
486
|
function stripContent(content, stub) {
|
|
255
487
|
if (toolImageRef(content)) return stub;
|
|
256
488
|
if (typeof content === "string") return content;
|
|
@@ -273,12 +505,13 @@ function endsOnAnnouncedAction(text) {
|
|
|
273
505
|
if (PROGRESSIVE.test(last)) return true;
|
|
274
506
|
return clauses(last).some((c) => !NEXT_STEP.test(c) && !SIGN_OFF.test(c) && COMMITMENT.test(c));
|
|
275
507
|
}
|
|
276
|
-
var
|
|
508
|
+
var log3, p_sep, IMAGE_ELIDE_STUB, IMAGE_REF_SCHEME, REF_REGISTRY_MAX, refRegistry, isImageRefUrl, bearsImage, COMMITMENT, PROGRESSIVE, NEXT_STEP, SIGN_OFF;
|
|
277
509
|
var init_llm = __esm({
|
|
278
510
|
"src/llm.ts"() {
|
|
279
511
|
"use strict";
|
|
280
512
|
init_logging();
|
|
281
|
-
|
|
513
|
+
init_imageWire();
|
|
514
|
+
log3 = forComponent("llm");
|
|
282
515
|
p_sep = (parts) => parts.length > 1 ? "\n" : "";
|
|
283
516
|
IMAGE_ELIDE_STUB = "[image elided \u2014 re-attach @path or /paste to view again]";
|
|
284
517
|
IMAGE_REF_SCHEME = "agentx-image:";
|
|
@@ -965,7 +1198,7 @@ function makeWebFetchTool(options = {}) {
|
|
|
965
1198
|
|
|
966
1199
|
${capped}`;
|
|
967
1200
|
} catch (e) {
|
|
968
|
-
|
|
1201
|
+
log4.debug(`WebFetch ${u} failed`, e);
|
|
969
1202
|
return `Error fetching ${u}: ${e?.name === "AbortError" ? `timed out after ${timeoutMs}ms` : e?.message ?? e}`;
|
|
970
1203
|
} finally {
|
|
971
1204
|
clearTimeout(timer);
|
|
@@ -1089,7 +1322,7 @@ function makeWebSearchTool(options = {}) {
|
|
|
1089
1322
|
if (!res.ok) return `Error: search returned ${res.status} ${res.statusText}`;
|
|
1090
1323
|
return formatHits(parseDdgHtml(await res.text(), maxResults));
|
|
1091
1324
|
} catch (e) {
|
|
1092
|
-
|
|
1325
|
+
log4.debug("WebSearch failed", e);
|
|
1093
1326
|
return `Error searching: ${e?.name === "AbortError" ? `timed out after ${timeoutMs}ms` : e?.message ?? e}`;
|
|
1094
1327
|
} finally {
|
|
1095
1328
|
clearTimeout(timer);
|
|
@@ -1100,12 +1333,12 @@ function makeWebSearchTool(options = {}) {
|
|
|
1100
1333
|
function makeWebSearchAnthropicTool(opts = {}) {
|
|
1101
1334
|
return makeWebSearchTool({ provider: "anthropic", name: "WebSearchAnthropic", description: ANTHROPIC_SEARCH_DESC, anthropicApiKey: opts.anthropicApiKey, model: opts.model });
|
|
1102
1335
|
}
|
|
1103
|
-
var
|
|
1336
|
+
var log4, _dnsLookup, webFetchTool, webSearchTool, ANTHROPIC_SEARCH_DESC, webSearchAnthropicTool;
|
|
1104
1337
|
var init_tools_web = __esm({
|
|
1105
1338
|
"src/tools.web.ts"() {
|
|
1106
1339
|
"use strict";
|
|
1107
1340
|
init_logging();
|
|
1108
|
-
|
|
1341
|
+
log4 = forComponent("web");
|
|
1109
1342
|
webFetchTool = makeWebFetchTool();
|
|
1110
1343
|
webSearchTool = makeWebSearchTool();
|
|
1111
1344
|
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).";
|
|
@@ -1325,7 +1558,7 @@ function realPathOf(fs, path) {
|
|
|
1325
1558
|
try {
|
|
1326
1559
|
return fs.realPath(path);
|
|
1327
1560
|
} catch (e) {
|
|
1328
|
-
|
|
1561
|
+
log5.debug(`realPath unavailable for ${path} \u2014 inlining image bytes instead`, e);
|
|
1329
1562
|
return null;
|
|
1330
1563
|
}
|
|
1331
1564
|
}
|
|
@@ -1377,7 +1610,7 @@ function toolsByName(names, available) {
|
|
|
1377
1610
|
return t;
|
|
1378
1611
|
});
|
|
1379
1612
|
}
|
|
1380
|
-
var
|
|
1613
|
+
var log5, numberLines, bashTool, IMG_MIME, readTool, editTool;
|
|
1381
1614
|
var init_tools = __esm({
|
|
1382
1615
|
"src/tools.ts"() {
|
|
1383
1616
|
"use strict";
|
|
@@ -1388,7 +1621,7 @@ var init_tools = __esm({
|
|
|
1388
1621
|
init_tools_web();
|
|
1389
1622
|
init_OverlayFilesystem();
|
|
1390
1623
|
init_redact();
|
|
1391
|
-
|
|
1624
|
+
log5 = forComponent("tools");
|
|
1392
1625
|
numberLines = (content, offset = 0, limit) => {
|
|
1393
1626
|
const lines = content.split("\n");
|
|
1394
1627
|
const start = Math.max(0, offset);
|
|
@@ -1570,12 +1803,12 @@ ${tail || "(no output)"}`;
|
|
|
1570
1803
|
}
|
|
1571
1804
|
];
|
|
1572
1805
|
}
|
|
1573
|
-
var
|
|
1806
|
+
var log6, JobRegistryOptions, secs, JobRegistry, SandboxJobRegistry, NO_JOB, describe;
|
|
1574
1807
|
var init_tools_jobs = __esm({
|
|
1575
1808
|
"src/tools.jobs.ts"() {
|
|
1576
1809
|
"use strict";
|
|
1577
1810
|
init_logging();
|
|
1578
|
-
|
|
1811
|
+
log6 = forComponent("jobs");
|
|
1579
1812
|
JobRegistryOptions = class {
|
|
1580
1813
|
/** Tail buffer cap per job (chars). */
|
|
1581
1814
|
maxBuffer = 256 * 1024;
|
|
@@ -1754,7 +1987,7 @@ var init_tools_jobs = __esm({
|
|
|
1754
1987
|
try {
|
|
1755
1988
|
onExit({ id: job.id, kind: job.kind, label: job.label, status, reason: job.reason, tail: job.buf.slice(-4e3), result });
|
|
1756
1989
|
} catch (e) {
|
|
1757
|
-
|
|
1990
|
+
log6.warn(`job onExit handler threw for ${job.id}: ${e}`);
|
|
1758
1991
|
}
|
|
1759
1992
|
}
|
|
1760
1993
|
}
|
|
@@ -1808,7 +2041,7 @@ var init_tools_jobs = __esm({
|
|
|
1808
2041
|
try {
|
|
1809
2042
|
j.onKill?.(reason);
|
|
1810
2043
|
} catch (e) {
|
|
1811
|
-
|
|
2044
|
+
log6.warn(`onKill for ${id} threw: ${e}`);
|
|
1812
2045
|
}
|
|
1813
2046
|
}
|
|
1814
2047
|
return true;
|
|
@@ -1855,7 +2088,7 @@ var init_tools_jobs = __esm({
|
|
|
1855
2088
|
try {
|
|
1856
2089
|
cpu = await j.cpuTime();
|
|
1857
2090
|
} catch (e) {
|
|
1858
|
-
|
|
2091
|
+
log6.debug(`cpu probe for ${j.id} failed: ${e}`);
|
|
1859
2092
|
}
|
|
1860
2093
|
if (cpu != null && j.lastCpu != null && cpu > j.lastCpu) j.lastActivityAt = this.options.now();
|
|
1861
2094
|
const prev = j.lastCpu;
|
|
@@ -1863,7 +2096,7 @@ var init_tools_jobs = __esm({
|
|
|
1863
2096
|
const verdict = this.verdict(j, now3);
|
|
1864
2097
|
if (!verdict?.idle || j.status !== "running") continue;
|
|
1865
2098
|
if (cpu == null) {
|
|
1866
|
-
|
|
2099
|
+
log6.debug(`not reaping ${j.id}: ${verdict.reason} but CPU unknown`);
|
|
1867
2100
|
continue;
|
|
1868
2101
|
}
|
|
1869
2102
|
if (prev == null) continue;
|
|
@@ -1877,7 +2110,7 @@ var init_tools_jobs = __esm({
|
|
|
1877
2110
|
return null;
|
|
1878
2111
|
}
|
|
1879
2112
|
stuck(j, reason, now3) {
|
|
1880
|
-
|
|
2113
|
+
log6.warn(`reaping ${j.id} (${j.kind} "${j.label}"): ${reason} \u2014 age ${secs(now3 - j.startedAt)}, last activity ${secs(now3 - j.lastActivityAt)} ago, idleMs=${j.idleMs}, hardCapMs=${j.hardCapMs}, ${j.bytes} byte(s) output`);
|
|
1881
2114
|
if (j.onKill) j.notify = true;
|
|
1882
2115
|
this.kill(j.id, reason);
|
|
1883
2116
|
}
|
|
@@ -1891,7 +2124,7 @@ var init_tools_jobs = __esm({
|
|
|
1891
2124
|
this.timer = void 0;
|
|
1892
2125
|
return;
|
|
1893
2126
|
}
|
|
1894
|
-
void this.sweep().catch((e) =>
|
|
2127
|
+
void this.sweep().catch((e) => log6.warn(`job sweep failed: ${e}`));
|
|
1895
2128
|
}, this.options.sweepMs);
|
|
1896
2129
|
this.timer.unref?.();
|
|
1897
2130
|
}
|
|
@@ -2402,7 +2635,7 @@ async function psGroupCpuSeconds(pgid) {
|
|
|
2402
2635
|
const out = await new Promise((res, rej) => execFile("ps", ["-A", "-o", "pid=,ppid=,pgid=,time="], { timeout: 5e3, env: { ...process.env, LC_ALL: "C" } }, (e, so) => e ? rej(e) : res(String(so))));
|
|
2403
2636
|
return sumTreeCpu(out, pgid);
|
|
2404
2637
|
} catch (e) {
|
|
2405
|
-
|
|
2638
|
+
log9.debug(`ps cpu probe failed for group ${pgid}: ${e}`);
|
|
2406
2639
|
return void 0;
|
|
2407
2640
|
}
|
|
2408
2641
|
}
|
|
@@ -2550,7 +2783,7 @@ function makeRealShellTool(options) {
|
|
|
2550
2783
|
proc.stderr?.on("data", collect);
|
|
2551
2784
|
proc.on("error", (err) => {
|
|
2552
2785
|
if (err?.name === "AbortError" || ctl.signal.aborted) return finish(reasonFor(timedOut, timeoutMs, clean(out)));
|
|
2553
|
-
|
|
2786
|
+
log9.debug("shell spawn error", err);
|
|
2554
2787
|
finish(`[exit 1] ${err?.message ?? err}${out ? "\n" + clean(out) : ""}`);
|
|
2555
2788
|
});
|
|
2556
2789
|
proc.on("close", (code) => {
|
|
@@ -2623,7 +2856,7 @@ ${clean(out) || "(no output yet)"}`;
|
|
|
2623
2856
|
}
|
|
2624
2857
|
];
|
|
2625
2858
|
}
|
|
2626
|
-
var
|
|
2859
|
+
var log9, clean, DETACHED, SECRET_ENV_RE, _spawn, ShellJobRegistry, shellStatus, NO_JOB2;
|
|
2627
2860
|
var init_tools_shell = __esm({
|
|
2628
2861
|
"src/tools.shell.ts"() {
|
|
2629
2862
|
"use strict";
|
|
@@ -2632,7 +2865,7 @@ var init_tools_shell = __esm({
|
|
|
2632
2865
|
init_logging();
|
|
2633
2866
|
init_shell_sandbox();
|
|
2634
2867
|
init_tools_jobs();
|
|
2635
|
-
|
|
2868
|
+
log9 = forComponent("shell");
|
|
2636
2869
|
clean = (s) => truncateOutput(redactSecrets(s.replace(/\n+$/, "")));
|
|
2637
2870
|
DETACHED = { stdio: ["ignore", "pipe", "pipe"], detached: true };
|
|
2638
2871
|
SECRET_ENV_RE = /(_API_KEY|_TOKEN|_SECRET|_PASSWORD|_PRIVATE_KEY|^AWS_|^GITHUB_TOKEN$|^OPENAI_|^ANTHROPIC_|^GOOGLE_|^GEMINI_|^GROQ_|^NPM_TOKEN$)/i;
|
|
@@ -3409,8 +3642,8 @@ ${sections.join("\n\n---\n\n")}`;
|
|
|
3409
3642
|
|
|
3410
3643
|
// src/subagent.ts
|
|
3411
3644
|
init_tools();
|
|
3412
|
-
import { mkdirSync as mkdirSync2, writeFileSync } from "fs";
|
|
3413
|
-
import { join as
|
|
3645
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
3646
|
+
import { join as join4 } from "path";
|
|
3414
3647
|
|
|
3415
3648
|
// src/models.ts
|
|
3416
3649
|
var MODEL_ALIASES = {
|
|
@@ -3607,9 +3840,9 @@ function composeHooks(...list) {
|
|
|
3607
3840
|
init_OverlayFilesystem();
|
|
3608
3841
|
|
|
3609
3842
|
// src/worktree.ts
|
|
3610
|
-
import { spawnSync } from "child_process";
|
|
3611
|
-
import { symlinkSync, mkdirSync, statSync as
|
|
3612
|
-
import { join } from "path";
|
|
3843
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
3844
|
+
import { symlinkSync, mkdirSync, statSync as statSync3, readFileSync as readFileSync3, rmSync as rmSync2 } from "fs";
|
|
3845
|
+
import { join as join2 } from "path";
|
|
3613
3846
|
var VALID_SEGMENT = /^[a-zA-Z0-9._-]+$/;
|
|
3614
3847
|
var MAX_SLUG_LENGTH = 64;
|
|
3615
3848
|
var GIT_NO_PROMPT_ENV = { GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "" };
|
|
@@ -3628,13 +3861,13 @@ function worktreeBranchName(slug2) {
|
|
|
3628
3861
|
return `worktree-${flattenSlug(slug2)}`;
|
|
3629
3862
|
}
|
|
3630
3863
|
function worktreesDir(repoRoot) {
|
|
3631
|
-
return
|
|
3864
|
+
return join2(repoRoot, ".claude", "worktrees");
|
|
3632
3865
|
}
|
|
3633
3866
|
function worktreePathFor(repoRoot, slug2) {
|
|
3634
|
-
return
|
|
3867
|
+
return join2(worktreesDir(repoRoot), flattenSlug(slug2));
|
|
3635
3868
|
}
|
|
3636
3869
|
function git(args, cwd) {
|
|
3637
|
-
const r =
|
|
3870
|
+
const r = spawnSync2("git", args, {
|
|
3638
3871
|
cwd,
|
|
3639
3872
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3640
3873
|
env: { ...process.env, ...GIT_NO_PROMPT_ENV }
|
|
@@ -3647,16 +3880,16 @@ function findGitRoot(from) {
|
|
|
3647
3880
|
}
|
|
3648
3881
|
function readWorktreeHead(worktreePath) {
|
|
3649
3882
|
try {
|
|
3650
|
-
const gitFile =
|
|
3883
|
+
const gitFile = readFileSync3(join2(worktreePath, ".git"), "utf-8").trim();
|
|
3651
3884
|
const m = gitFile.match(/^gitdir:\s*(.+)$/);
|
|
3652
3885
|
if (!m) return null;
|
|
3653
|
-
const headPath =
|
|
3654
|
-
const head =
|
|
3886
|
+
const headPath = join2(m[1], "HEAD");
|
|
3887
|
+
const head = readFileSync3(headPath, "utf-8").trim();
|
|
3655
3888
|
const refMatch = head.match(/^ref:\s*(.+)$/);
|
|
3656
3889
|
if (!refMatch) return head.length === 40 ? head : null;
|
|
3657
|
-
const refPath =
|
|
3890
|
+
const refPath = join2(m[1], "..", refMatch[1]);
|
|
3658
3891
|
try {
|
|
3659
|
-
return
|
|
3892
|
+
return readFileSync3(refPath, "utf-8").trim();
|
|
3660
3893
|
} catch {
|
|
3661
3894
|
}
|
|
3662
3895
|
const r = git(["rev-parse", "HEAD"], worktreePath);
|
|
@@ -3689,10 +3922,10 @@ function getOrCreateWorktree(repoRoot, slug2) {
|
|
|
3689
3922
|
}
|
|
3690
3923
|
function symlinkDirs(repoRoot, worktreePath, dirs) {
|
|
3691
3924
|
for (const dir of dirs) {
|
|
3692
|
-
const src =
|
|
3693
|
-
const dst =
|
|
3925
|
+
const src = join2(repoRoot, dir);
|
|
3926
|
+
const dst = join2(worktreePath, dir);
|
|
3694
3927
|
try {
|
|
3695
|
-
|
|
3928
|
+
statSync3(src);
|
|
3696
3929
|
} catch {
|
|
3697
3930
|
continue;
|
|
3698
3931
|
}
|
|
@@ -3708,7 +3941,7 @@ function cleanupWorktree(repoRoot, slug2, opts) {
|
|
|
3708
3941
|
const worktreePath = worktreePathFor(repoRoot, slug2);
|
|
3709
3942
|
const branch = worktreeBranchName(slug2);
|
|
3710
3943
|
try {
|
|
3711
|
-
|
|
3944
|
+
statSync3(worktreePath);
|
|
3712
3945
|
} catch {
|
|
3713
3946
|
return { removed: false, reason: "not found" };
|
|
3714
3947
|
}
|
|
@@ -3718,7 +3951,7 @@ function cleanupWorktree(repoRoot, slug2, opts) {
|
|
|
3718
3951
|
}
|
|
3719
3952
|
for (const dir of ["node_modules", ".bun"]) {
|
|
3720
3953
|
try {
|
|
3721
|
-
|
|
3954
|
+
rmSync2(join2(worktreePath, dir));
|
|
3722
3955
|
} catch {
|
|
3723
3956
|
}
|
|
3724
3957
|
}
|
|
@@ -3758,7 +3991,7 @@ function exitWorktree() {
|
|
|
3758
3991
|
// src/subagent.ts
|
|
3759
3992
|
init_NodeDiskFilesystem();
|
|
3760
3993
|
init_logging();
|
|
3761
|
-
var
|
|
3994
|
+
var log7 = forComponent("subagent");
|
|
3762
3995
|
async function boundedPool(items, limit, fn) {
|
|
3763
3996
|
const out = new Array(items.length);
|
|
3764
3997
|
let next = 0;
|
|
@@ -3830,17 +4063,17 @@ function traceFileFor(dir, label, depth) {
|
|
|
3830
4063
|
try {
|
|
3831
4064
|
mkdirSync2(dir, { recursive: true });
|
|
3832
4065
|
const id = `${Date.now().toString(36)}-${(traceSeq++).toString(36)}`;
|
|
3833
|
-
return
|
|
4066
|
+
return join4(dir, `${id}-d${depth}-${sanitizeSlug(label) || "sub"}.json`);
|
|
3834
4067
|
} catch (e) {
|
|
3835
|
-
|
|
4068
|
+
log7.warn(`subagent trace dir unavailable (${dir}) \u2014 running without a trace: ${e instanceof Error ? e.message : String(e)}`);
|
|
3836
4069
|
return void 0;
|
|
3837
4070
|
}
|
|
3838
4071
|
}
|
|
3839
4072
|
function flushTrace(file, meta, messages, status) {
|
|
3840
4073
|
try {
|
|
3841
|
-
|
|
4074
|
+
writeFileSync2(file, JSON.stringify({ ...meta, status, updated: Date.now(), messages }, null, 2));
|
|
3842
4075
|
} catch (e) {
|
|
3843
|
-
|
|
4076
|
+
log7.warn(`subagent trace flush failed (${file}): ${e instanceof Error ? e.message : String(e)}`);
|
|
3844
4077
|
}
|
|
3845
4078
|
}
|
|
3846
4079
|
function installTrace(child, file, meta) {
|
|
@@ -3881,7 +4114,7 @@ async function runChildTraced(args) {
|
|
|
3881
4114
|
if (file) flushTrace(file, meta, child.transcript, wc.timedOut() ? "deadline" : res.finishReason);
|
|
3882
4115
|
let result = res.finishReason === "stop" ? res.text || `(child '${label}' finished with no summary; finishReason=${res.finishReason})` : abortHint(label, res, file);
|
|
3883
4116
|
if (wc.timedOut()) {
|
|
3884
|
-
|
|
4117
|
+
log7.warn(`sub-task '${label}' hit its ${deadlineMs}ms wall-clock deadline (steps=${res.steps})`);
|
|
3885
4118
|
result += deadlineNote(deadlineMs);
|
|
3886
4119
|
}
|
|
3887
4120
|
await opts.hooks?.onSubagentStop?.(result, { label, agentType });
|
|
@@ -4210,7 +4443,7 @@ function reasoningToChatFragment(model, effort) {
|
|
|
4210
4443
|
}
|
|
4211
4444
|
|
|
4212
4445
|
// src/Agent.ts
|
|
4213
|
-
var
|
|
4446
|
+
var log8 = forComponent("Agent");
|
|
4214
4447
|
function envInt(key, fallback) {
|
|
4215
4448
|
const n = typeof process !== "undefined" ? Number(process.env?.[key]) : NaN;
|
|
4216
4449
|
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
@@ -4291,6 +4524,9 @@ var AgentOptions = class {
|
|
|
4291
4524
|
* `maxTokens`: that is the cumulative SPEND kill-switch, and a spend budget must never be able to
|
|
4292
4525
|
* drive destructive compaction (`--max-tokens 50000` would otherwise shred the transcript). */
|
|
4293
4526
|
contextWindow = 0;
|
|
4527
|
+
/** Does a model take image input? true/false from the host's model catalog; undefined = unknown (images are tried,
|
|
4528
|
+
* and turned off for the run when the adapter refuses them). Unset = always try. */
|
|
4529
|
+
imageInput;
|
|
4294
4530
|
/** Pagination ceiling for a SINGLE tool result (bytes). A result over this is cropped to page 1 with
|
|
4295
4531
|
* a marker telling the model it was cropped (refine the query, or page further). Guards against one
|
|
4296
4532
|
* Grep/Read/MCP call blowing the whole context window. 0 = off. Default 60k (~15k tokens). */
|
|
@@ -4590,7 +4826,7 @@ var Agent = class _Agent {
|
|
|
4590
4826
|
const disk = new NodeDiskFilesystem2(process.cwd());
|
|
4591
4827
|
await disk.init();
|
|
4592
4828
|
this.options.fs = new JailedFilesystem2(disk);
|
|
4593
|
-
|
|
4829
|
+
log8.info(`no fs provided \u2014 defaulting to jailed real disk at ${process.cwd()}`);
|
|
4594
4830
|
}
|
|
4595
4831
|
this.buildCtx();
|
|
4596
4832
|
}
|
|
@@ -4735,7 +4971,8 @@ var Agent = class _Agent {
|
|
|
4735
4971
|
const cap = _Agent.contextCap(this.options);
|
|
4736
4972
|
const target = Math.round(cap * _Agent.COMPACT_TARGET);
|
|
4737
4973
|
const kr = this.options.keepRecentImages ?? 1;
|
|
4738
|
-
const
|
|
4974
|
+
const vis = this.sendsImages();
|
|
4975
|
+
const tokensBefore = estimateTokens(this.transcript, kr, vis);
|
|
4739
4976
|
const msgsBefore = this.transcript.length;
|
|
4740
4977
|
let max = Math.max(2, maxMessages);
|
|
4741
4978
|
if (this.transcript.length <= max && tokensBefore <= target)
|
|
@@ -4743,18 +4980,18 @@ var Agent = class _Agent {
|
|
|
4743
4980
|
void this.activeHooks?.onPreCompact?.(this.transcript);
|
|
4744
4981
|
let out = this.transcript;
|
|
4745
4982
|
if (out.length > max) out = compact(out, max, focus);
|
|
4746
|
-
if (estimateTokens(out, kr) > target) out = elideOversizedImages(out, target, true, kr);
|
|
4747
|
-
if (estimateTokens(out, kr) > target) out = capOversizedBodies(out, target, kr);
|
|
4748
|
-
if (estimateTokens(out, kr) > target) out = elideOversizedImages(out, target, false, kr);
|
|
4749
|
-
while (estimateTokens(out, kr) > target && max > 2 && out.length > 2) {
|
|
4983
|
+
if (estimateTokens(out, kr, vis) > target) out = elideOversizedImages(out, target, true, kr);
|
|
4984
|
+
if (estimateTokens(out, kr, vis) > target) out = capOversizedBodies(out, target, kr);
|
|
4985
|
+
if (estimateTokens(out, kr, vis) > target) out = elideOversizedImages(out, target, false, kr);
|
|
4986
|
+
while (estimateTokens(out, kr, vis) > target && max > 2 && out.length > 2) {
|
|
4750
4987
|
max = Math.max(2, Math.floor(max / 2));
|
|
4751
4988
|
out = capOversizedBodies(compact(out, max, focus), target, kr);
|
|
4752
4989
|
}
|
|
4753
|
-
if (estimateTokens(out, kr) > target) out = fitTokenBudget(out, target, kr);
|
|
4990
|
+
if (estimateTokens(out, kr, vis) > target) out = fitTokenBudget(out, target, kr);
|
|
4754
4991
|
out = dropOrphanToolResults(out);
|
|
4755
4992
|
this.transcript = out;
|
|
4756
4993
|
this.releaseStaleImages();
|
|
4757
|
-
return { tokensBefore, tokensAfter: estimateTokens(this.transcript, kr), target, cap, removed: msgsBefore - this.transcript.length };
|
|
4994
|
+
return { tokensBefore, tokensAfter: estimateTokens(this.transcript, kr, vis), target, cap, removed: msgsBefore - this.transcript.length };
|
|
4758
4995
|
}
|
|
4759
4996
|
/** Drop inline base64 images from older messages so Bun/JSC can reclaim the heap (see contentBytes). */
|
|
4760
4997
|
releaseStaleImages(keepRecent = 1) {
|
|
@@ -4778,7 +5015,7 @@ var Agent = class _Agent {
|
|
|
4778
5015
|
let closureNudged = false;
|
|
4779
5016
|
let lastProgressAt = start;
|
|
4780
5017
|
const kill = (finishReason) => {
|
|
4781
|
-
|
|
5018
|
+
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` : ""})`);
|
|
4782
5019
|
this.jobs.killAll(`run ended: ${finishReason}`, true);
|
|
4783
5020
|
const claims = this.survivingClaims();
|
|
4784
5021
|
return { text: lastAssistantText(this.transcript), steps, finishReason, messages: this.transcript, usage, usageEstimated, ...claims.length ? { fabricatedToolClaims: claims } : {} };
|
|
@@ -4793,7 +5030,7 @@ var Agent = class _Agent {
|
|
|
4793
5030
|
steps++;
|
|
4794
5031
|
this.options.host?.notify?.({ kind: "turn_start", message: `step ${steps}` });
|
|
4795
5032
|
let res;
|
|
4796
|
-
|
|
5033
|
+
let sent = this.trimContext();
|
|
4797
5034
|
const frag = reasoningToChatFragment(o.model, o.reasoning);
|
|
4798
5035
|
const sentTools = o.model.startsWith("claude-code/") ? [] : wireTools;
|
|
4799
5036
|
const isCursorWithTools = o.model.startsWith("cursor/") && wireTools.length > 0;
|
|
@@ -4804,9 +5041,9 @@ var Agent = class _Agent {
|
|
|
4804
5041
|
this.nativeHoldMs = o.nativeHoldMs;
|
|
4805
5042
|
if (isCursorWithTools) {
|
|
4806
5043
|
if (typeof hostToolTimeoutMs === "number" && effectiveToolTimeoutMs !== hostToolTimeoutMs) {
|
|
4807
|
-
|
|
5044
|
+
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.`);
|
|
4808
5045
|
}
|
|
4809
|
-
for (const v of toolTimeoutViolations(this.activeTools, effectiveToolTimeoutMs))
|
|
5046
|
+
for (const v of toolTimeoutViolations(this.activeTools, effectiveToolTimeoutMs)) log8.warn(v);
|
|
4810
5047
|
}
|
|
4811
5048
|
const hostExec = async (name, args) => {
|
|
4812
5049
|
const tc = { id: `host-${Date.now()}`, type: "function", function: { name, arguments: JSON.stringify(args) } };
|
|
@@ -4865,12 +5102,20 @@ var Agent = class _Agent {
|
|
|
4865
5102
|
res = r;
|
|
4866
5103
|
}
|
|
4867
5104
|
if (steps === 1 && this.isEmptyStop(res) && !this.pendingInjections.length) {
|
|
4868
|
-
const e = new Error(
|
|
5105
|
+
const e = new Error(`model returned an empty turn (finishReason=${res.finishReason ?? "n/a"}, no text, no tools)`);
|
|
4869
5106
|
e.code = "empty";
|
|
5107
|
+
if (res.finishReason) e.providerFinishReason = res.finishReason;
|
|
4870
5108
|
throw e;
|
|
4871
5109
|
}
|
|
4872
5110
|
break;
|
|
4873
5111
|
} catch (err) {
|
|
5112
|
+
if (!this.imagesOff && /does not support .*input/i.test(String(err?.message ?? "")) && sent.some((m) => Array.isArray(m.content) && m.role === "tool")) {
|
|
5113
|
+
this.imagesOff = true;
|
|
5114
|
+
log8.warn(`model refused image input (${err?.message}) \u2014 resending with image Reads as text stubs`);
|
|
5115
|
+
sent = this.trimContext();
|
|
5116
|
+
attempt--;
|
|
5117
|
+
continue;
|
|
5118
|
+
}
|
|
4874
5119
|
const sc = err?.statusCode;
|
|
4875
5120
|
const serverSide = sc >= 500 || sc === 408 || /Service Unavailable|overloaded|Internal server error|Bad gateway|Gateway time/i.test(String(err?.message ?? ""));
|
|
4876
5121
|
const network = !sc && /ECONNRESET|ECONNREFUSED|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|EPIPE|socket hang up|fetch failed|network|terminated|UND_ERR/i.test(String(err?.message ?? err?.code ?? err));
|
|
@@ -4878,11 +5123,11 @@ var Agent = class _Agent {
|
|
|
4878
5123
|
const empty = err?.code === "empty" || err?.code === "cursor_silent";
|
|
4879
5124
|
const maxAttempts = empty ? Math.max(o.emptyRetries, 0) : 2;
|
|
4880
5125
|
const replaysTool = stalled && this.toolRanThisAttempt;
|
|
4881
|
-
if (replaysTool)
|
|
5126
|
+
if (replaysTool) log8.warn("model stall after tool(s) already ran this step \u2014 not retrying (a retry would replay them)");
|
|
4882
5127
|
const transient = !o.signal?.aborted && !isAbortError(err) && !replaysTool && attempt < maxAttempts && (network || serverSide || stalled || empty);
|
|
4883
5128
|
if (!transient) throw err;
|
|
4884
5129
|
const waitMs = (empty ? o.emptyRetryBaseMs : 1e3) * (attempt + 1);
|
|
4885
|
-
|
|
5130
|
+
log8.warn(`${empty ? "empty model turn" : stalled ? "model stall" : "network drop"} mid-step (${err?.message ?? err}) \u2014 retrying in ${waitMs}ms`);
|
|
4886
5131
|
o.host?.notify?.({ kind: "retry", message: `${empty ? "model returned nothing" : stalled ? "model stalled" : "connection dropped"} \u2014 retrying step (#${attempt + 1})` });
|
|
4887
5132
|
await new Promise((r) => setTimeout(r, waitMs));
|
|
4888
5133
|
}
|
|
@@ -4890,13 +5135,14 @@ var Agent = class _Agent {
|
|
|
4890
5135
|
} catch (err) {
|
|
4891
5136
|
if (err?.code === "budget") return kill("budget");
|
|
4892
5137
|
if (err?.code === "stall") {
|
|
4893
|
-
|
|
4894
|
-
return { ...kill("timeout"), error: err };
|
|
5138
|
+
log8.warn(`model stalled out after retries: ${err?.message ?? err}`);
|
|
5139
|
+
return { ...kill("timeout"), providerFinishReason: "none (stalled)", error: err };
|
|
4895
5140
|
}
|
|
4896
5141
|
if (o.signal?.aborted || isAbortError(err)) return kill("aborted");
|
|
4897
5142
|
if (err?.code === "empty" || err?.code === "cursor_silent") {
|
|
4898
|
-
|
|
4899
|
-
|
|
5143
|
+
log8.warn(`model returned empty/silent turns after retries \u2014 surfacing as failed`);
|
|
5144
|
+
const pfr = err?.providerFinishReason;
|
|
5145
|
+
return { text: "", steps, finishReason: "empty", ...pfr ? { providerFinishReason: pfr } : {}, messages: this.transcript, usage, usageEstimated, error: err };
|
|
4900
5146
|
}
|
|
4901
5147
|
const body = err?.body ?? err?.response?.data ?? err?.error;
|
|
4902
5148
|
let bodyStr;
|
|
@@ -4907,13 +5153,13 @@ var Agent = class _Agent {
|
|
|
4907
5153
|
}
|
|
4908
5154
|
if (bodyStr && err instanceof Error && !err.message.includes(bodyStr)) err.detail = bodyStr;
|
|
4909
5155
|
const errLog = err instanceof Error ? { message: err.message, ...err.detail ? { detail: err.detail } : {}, stack: err.stack } : err;
|
|
4910
|
-
|
|
5156
|
+
log8.error(`chat() failed: ${err?.message ?? err}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, errLog);
|
|
4911
5157
|
return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err };
|
|
4912
5158
|
}
|
|
4913
5159
|
if (o.signal?.aborted) return kill("aborted");
|
|
4914
5160
|
if (res.finishReason === "error" && !res.toolCalls?.length) {
|
|
4915
5161
|
const err = new Error(contentText(res.content ?? "").trim() || "provider error");
|
|
4916
|
-
|
|
5162
|
+
log8.error(`chat() failed: ${err.message}`);
|
|
4917
5163
|
return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err };
|
|
4918
5164
|
}
|
|
4919
5165
|
if (!res.usage) {
|
|
@@ -4971,21 +5217,21 @@ var Agent = class _Agent {
|
|
|
4971
5217
|
if (emptyTurn && o.failEmptyTurns) {
|
|
4972
5218
|
if (!emptyNudged && canNudge()) {
|
|
4973
5219
|
emptyNudged = true;
|
|
4974
|
-
|
|
5220
|
+
log8.warn(`empty model turn after tool work (step ${steps}, provider finishReason=${res.finishReason ?? "n/a"}) \u2014 nudging once`);
|
|
4975
5221
|
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." });
|
|
4976
5222
|
continue;
|
|
4977
5223
|
}
|
|
4978
|
-
|
|
5224
|
+
log8.warn(`model returned empty turns after tool work \u2014 surfacing as failed`);
|
|
4979
5225
|
const e = new Error("empty_response: model returned an empty turn (no text, no tools) after a re-prompt");
|
|
4980
5226
|
e.code = "empty";
|
|
4981
5227
|
return { text: "", steps, finishReason: "empty", ...res.finishReason ? { providerFinishReason: res.finishReason } : {}, messages: this.transcript, usage, usageEstimated, error: e };
|
|
4982
5228
|
}
|
|
4983
5229
|
const fabricated = o.catchFabricatedToolUse && !closureNudged ? this.fabricatedToolNames(contentText(res.content ?? "")) : [];
|
|
4984
5230
|
for (const n of fabricated) this.fabricatedClaims.add(n);
|
|
4985
|
-
if (fabricated.length && !canNudge())
|
|
5231
|
+
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`);
|
|
4986
5232
|
if (fabricated.length && canNudge()) {
|
|
4987
5233
|
closureNudged = true;
|
|
4988
|
-
|
|
5234
|
+
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`);
|
|
4989
5235
|
this.transcript.push({
|
|
4990
5236
|
role: "user",
|
|
4991
5237
|
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.`
|
|
@@ -4994,14 +5240,14 @@ var Agent = class _Agent {
|
|
|
4994
5240
|
}
|
|
4995
5241
|
if (o.closeDelegatedTurns && res.endedWithoutClosing && !closureNudged && canNudge()) {
|
|
4996
5242
|
closureNudged = true;
|
|
4997
|
-
|
|
5243
|
+
log8.verbose("delegated turn went silent after a tool \u2014 nudging a closing answer");
|
|
4998
5244
|
this.transcript.push({
|
|
4999
5245
|
role: "user",
|
|
5000
5246
|
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."
|
|
5001
5247
|
});
|
|
5002
5248
|
continue;
|
|
5003
5249
|
}
|
|
5004
|
-
|
|
5250
|
+
log8.verbose(`completed in ${steps} step(s)`);
|
|
5005
5251
|
const drained = await this.jobs.drain(o.signal);
|
|
5006
5252
|
if (o.signal?.aborted) return kill("aborted");
|
|
5007
5253
|
if (drained.length) lastProgressAt = Date.now();
|
|
@@ -5035,7 +5281,7 @@ var Agent = class _Agent {
|
|
|
5035
5281
|
if (this.ctx.needsInput) {
|
|
5036
5282
|
const question = this.ctx.needsInput;
|
|
5037
5283
|
this.ctx.needsInput = void 0;
|
|
5038
|
-
|
|
5284
|
+
log8.warn(`needs_input: ${question.question} (steps=${steps})`);
|
|
5039
5285
|
await this.jobs.drain();
|
|
5040
5286
|
const text = [
|
|
5041
5287
|
`OPEN QUESTION (run paused \u2014 no human was reachable): ${question.question}`,
|
|
@@ -5077,12 +5323,12 @@ var Agent = class _Agent {
|
|
|
5077
5323
|
if (this.toolHoldActive()) {
|
|
5078
5324
|
const now3 = Date.now();
|
|
5079
5325
|
const who = this.jobs.holding().map((j) => `${j.kind} "${j.label}" (${Math.round((now3 - j.startedAt) / 1e3)}s, quiet ${Math.round((now3 - j.lastActivityAt) / 1e3)}s)`).join(", ");
|
|
5080
|
-
|
|
5326
|
+
log8.warn(`stall watchdog: no stream output for ${stallMs}ms \u2014 held by ${who}`);
|
|
5081
5327
|
this.options.host?.notify?.({ kind: "tool_hold", message: `waiting on ${who}` });
|
|
5082
5328
|
return poke();
|
|
5083
5329
|
}
|
|
5084
5330
|
const inFlight = this.jobs.list().filter((j) => j.status === "running");
|
|
5085
|
-
|
|
5331
|
+
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(", ")})` : ""));
|
|
5086
5332
|
fired = true;
|
|
5087
5333
|
ac.abort(stallError());
|
|
5088
5334
|
}, stallMs);
|
|
@@ -5252,13 +5498,13 @@ var Agent = class _Agent {
|
|
|
5252
5498
|
const decision = await this.park(Promise.resolve(hooks?.preToolUse?.(call, meta)));
|
|
5253
5499
|
if (decision?.block) {
|
|
5254
5500
|
const blocked = `Blocked by hook: ${decision.reason ?? "no reason given"}`;
|
|
5255
|
-
|
|
5501
|
+
log8.debug(`${tc.function.name} -> ${blocked}`);
|
|
5256
5502
|
await hooks?.postToolUse?.(call, blocked, meta);
|
|
5257
5503
|
return blocked;
|
|
5258
5504
|
}
|
|
5259
5505
|
this.options.host?.notify?.({ kind: "tool_use", id: tc.id ?? "", name: tc.function.name, input: args });
|
|
5260
5506
|
if (earlyError) {
|
|
5261
|
-
|
|
5507
|
+
log8.debug(`${tc.function.name} -> ${earlyError}`);
|
|
5262
5508
|
await hooks?.postToolUse?.(call, earlyError, meta);
|
|
5263
5509
|
this.options.host?.notify?.({ kind: "tool_result", id: tc.id ?? "", output: earlyError, isError: true });
|
|
5264
5510
|
return earlyError;
|
|
@@ -5267,12 +5513,12 @@ var Agent = class _Agent {
|
|
|
5267
5513
|
let images;
|
|
5268
5514
|
let threw = false;
|
|
5269
5515
|
try {
|
|
5270
|
-
|
|
5516
|
+
log8.debug(`${tc.function.name}(${tc.function.arguments})`);
|
|
5271
5517
|
this.ctx.emit = hooks?.onToolOutput ? (chunk) => {
|
|
5272
5518
|
try {
|
|
5273
5519
|
hooks.onToolOutput(call, chunk, meta);
|
|
5274
5520
|
} catch (e) {
|
|
5275
|
-
|
|
5521
|
+
log8.debug(`onToolOutput hook error: ${e}`);
|
|
5276
5522
|
}
|
|
5277
5523
|
} : void 0;
|
|
5278
5524
|
const raw = await tool.run(args, this.ctx);
|
|
@@ -5284,7 +5530,7 @@ var Agent = class _Agent {
|
|
|
5284
5530
|
}
|
|
5285
5531
|
} catch (e) {
|
|
5286
5532
|
const msg = e instanceof Error ? e.message : String(e);
|
|
5287
|
-
|
|
5533
|
+
log8.debug(`${tc.function.name} -> error: ${msg}`);
|
|
5288
5534
|
result = `Error: ${msg}`;
|
|
5289
5535
|
threw = true;
|
|
5290
5536
|
} finally {
|
|
@@ -5344,6 +5590,13 @@ ${out}`;
|
|
|
5344
5590
|
* 3. token-aware backstop (`maxContextTokens`) — drop oldest messages until under an estimated token ceiling.
|
|
5345
5591
|
* With every knob off, returns the transcript unchanged (same reference).
|
|
5346
5592
|
*/
|
|
5593
|
+
/** Set when the model refused image parts this run: tool image Reads go as text stubs from then on. */
|
|
5594
|
+
imagesOff = false;
|
|
5595
|
+
/** Send tool image Reads as images? Known models by their catalog flag; unknown ones try (and latch off on refusal). */
|
|
5596
|
+
sendsImages() {
|
|
5597
|
+
if (this.imagesOff) return false;
|
|
5598
|
+
return this.options.imageInput?.(this.options.model) ?? true;
|
|
5599
|
+
}
|
|
5347
5600
|
trimContext() {
|
|
5348
5601
|
const o = this.options;
|
|
5349
5602
|
const m = this.transcript;
|
|
@@ -5353,17 +5606,19 @@ ${out}`;
|
|
|
5353
5606
|
const browserKeep = this.browserActive ? o.browserTrim?.keepOutputs : void 0;
|
|
5354
5607
|
const keep = browserKeep != null ? o.keepToolOutputs ? Math.min(o.keepToolOutputs, browserKeep) : browserKeep : o.keepToolOutputs;
|
|
5355
5608
|
if (keep) out = stubOldToolResults(out ?? m, keep);
|
|
5356
|
-
out = expandImagesForSend(dropOrphanToolResults(out ?? m), o.keepRecentImages ?? 1);
|
|
5609
|
+
out = expandImagesForSend(dropOrphanToolResults(out ?? m), o.keepRecentImages ?? 1, void 0, this.sendsImages());
|
|
5357
5610
|
if (o.maxContextTokens) {
|
|
5358
5611
|
const pre = out.length;
|
|
5359
|
-
out = fitTokenBudget(out, o.maxContextTokens, o.keepRecentImages ?? 1);
|
|
5612
|
+
out = fitTokenBudget(out, o.maxContextTokens, o.keepRecentImages ?? 1, this.sendsImages());
|
|
5360
5613
|
const dropped = pre - out.length;
|
|
5361
5614
|
if (dropped > 0 && dropped !== this.lastTrimNotified) {
|
|
5362
5615
|
this.lastTrimNotified = dropped;
|
|
5363
5616
|
o.host?.notify?.({ kind: "compaction", message: `context full \u2014 auto-trimmed ${dropped} oldest message(s) to fit ~${Math.round(o.maxContextTokens / 1e3)}k tokens` });
|
|
5364
5617
|
}
|
|
5365
5618
|
}
|
|
5366
|
-
|
|
5619
|
+
out = dropOrphanToolResults(out);
|
|
5620
|
+
if (/^google\//.test(o.model ?? "")) out = hoistToolImages(out);
|
|
5621
|
+
return scrubBase64(out);
|
|
5367
5622
|
}
|
|
5368
5623
|
};
|
|
5369
5624
|
function dropOldest(m, max) {
|
|
@@ -5372,8 +5627,8 @@ function dropOldest(m, max) {
|
|
|
5372
5627
|
}
|
|
5373
5628
|
var ANTHROPIC_WINDOW_WITHOUT_1M_BETA = 2e5;
|
|
5374
5629
|
var isAnthropicModel = (m) => !!m && (m.startsWith("anthropic/") || /(^|\/)claude-/.test(m));
|
|
5375
|
-
function estimateTokens(m, keepRecentImages = 1) {
|
|
5376
|
-
return Math.ceil(sendBytes(m, keepRecentImages) / 4);
|
|
5630
|
+
function estimateTokens(m, keepRecentImages = 1, vision = true) {
|
|
5631
|
+
return Math.ceil(sendBytes(m, keepRecentImages, vision) / 4);
|
|
5377
5632
|
}
|
|
5378
5633
|
var TRANSPORT_TOOL_TIMEOUT_FLOOR_MS = 18e5;
|
|
5379
5634
|
var isMcpTool = (t) => t.name.startsWith("mcp__") || t.name === "McpCall";
|
|
@@ -5542,8 +5797,8 @@ function dropOrphanToolResults(messages) {
|
|
|
5542
5797
|
const ok = (m) => m.role !== "tool" || ids.has(m.tool_call_id ?? "");
|
|
5543
5798
|
return messages.every(ok) ? messages : messages.filter(ok);
|
|
5544
5799
|
}
|
|
5545
|
-
function fitTokenBudget(messages, maxTokens, keepRecentImages = 1) {
|
|
5546
|
-
const per = sendBytesPer(messages, keepRecentImages).map((b) => Math.ceil(b / 4));
|
|
5800
|
+
function fitTokenBudget(messages, maxTokens, keepRecentImages = 1, vision = true) {
|
|
5801
|
+
const per = sendBytesPer(messages, keepRecentImages, vision).map((b) => Math.ceil(b / 4));
|
|
5547
5802
|
let total = per.reduce((a, b) => a + b, 0);
|
|
5548
5803
|
if (total <= maxTokens) return messages;
|
|
5549
5804
|
const head = messages[0]?.role === "system" ? [messages[0]] : [];
|
|
@@ -5552,7 +5807,7 @@ function fitTokenBudget(messages, maxTokens, keepRecentImages = 1) {
|
|
|
5552
5807
|
const ids = callIdSet(messages.slice(from));
|
|
5553
5808
|
while (from < messages.length && messages[from].role === "tool" && !ids.has(messages[from].tool_call_id ?? "")) total -= per[from++];
|
|
5554
5809
|
if (total > maxTokens)
|
|
5555
|
-
|
|
5810
|
+
log8.warn(`context ~${total} tok still over maxContextTokens=${maxTokens} after trimming (system head can't be dropped)`);
|
|
5556
5811
|
return [...head, ...messages.slice(from)];
|
|
5557
5812
|
}
|
|
5558
5813
|
function compact(m, max, focus) {
|
|
@@ -6235,7 +6490,7 @@ init_tools_web();
|
|
|
6235
6490
|
init_tools();
|
|
6236
6491
|
init_tools_structured();
|
|
6237
6492
|
init_logging();
|
|
6238
|
-
var
|
|
6493
|
+
var log10 = forComponent("scratch");
|
|
6239
6494
|
var SCRATCH_DIR = "/scratch";
|
|
6240
6495
|
function shortArgs(args) {
|
|
6241
6496
|
try {
|
|
@@ -6275,7 +6530,7 @@ var Scratch = class {
|
|
|
6275
6530
|
await (this.dirReady ??= mkdirp(this.fs, dir));
|
|
6276
6531
|
await this.fs.writeFile(path, header + raw);
|
|
6277
6532
|
} catch (e) {
|
|
6278
|
-
|
|
6533
|
+
log10.debug("scratch write failed; returning raw", e);
|
|
6279
6534
|
return raw;
|
|
6280
6535
|
}
|
|
6281
6536
|
const preview = raw.slice(0, previewChars).replace(/\s+/g, " ").trim();
|
|
@@ -6305,7 +6560,7 @@ To pull a specific detail, Grep/Read ${path}, or call Ask({ question: "\u2026",
|
|
|
6305
6560
|
await (this.dirReady ??= mkdirp(this.fs, dir));
|
|
6306
6561
|
await this.fs.writeFile(path, header + full);
|
|
6307
6562
|
} catch (e) {
|
|
6308
|
-
|
|
6563
|
+
log10.debug("scratch spill failed; cropping lossy", e);
|
|
6309
6564
|
return full.slice(0, pageBytes) + `
|
|
6310
6565
|
|
|
6311
6566
|
[output cropped to ${pageBytes} of ${full.length} bytes; full output unavailable (scratch write failed) \u2014 refine your query]`;
|
|
@@ -6351,7 +6606,7 @@ Question: ${q}`);
|
|
|
6351
6606
|
const answer = (res.text ?? "").trim();
|
|
6352
6607
|
return answer || "(no answer found in scratch)";
|
|
6353
6608
|
} catch (e) {
|
|
6354
|
-
|
|
6609
|
+
log10.debug("Ask peek failed", e);
|
|
6355
6610
|
return `Error querying scratch: ${e?.message ?? e}`;
|
|
6356
6611
|
}
|
|
6357
6612
|
}
|
|
@@ -6360,7 +6615,7 @@ Question: ${q}`);
|
|
|
6360
6615
|
|
|
6361
6616
|
// src/lessons.ts
|
|
6362
6617
|
init_logging();
|
|
6363
|
-
var
|
|
6618
|
+
var log11 = forComponent("Lessons");
|
|
6364
6619
|
var LessonOptionsDefaults = class {
|
|
6365
6620
|
minRepeats = 2;
|
|
6366
6621
|
};
|
|
@@ -6385,8 +6640,8 @@ function lessonCapture(options) {
|
|
|
6385
6640
|
counts.set(lesson.slug, n);
|
|
6386
6641
|
if (n < o.minRepeats) return;
|
|
6387
6642
|
written.add(lesson.slug);
|
|
6388
|
-
await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) =>
|
|
6389
|
-
|
|
6643
|
+
await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) => log11.warn(`could not persist ${lesson.slug}: ${e?.message ?? e}`));
|
|
6644
|
+
log11.debug(`captured lesson ${lesson.slug} (recurred ${n}\xD7)`);
|
|
6390
6645
|
}
|
|
6391
6646
|
};
|
|
6392
6647
|
}
|
|
@@ -6394,7 +6649,7 @@ function lessonCapture(options) {
|
|
|
6394
6649
|
// src/reflect.ts
|
|
6395
6650
|
init_llm();
|
|
6396
6651
|
init_logging();
|
|
6397
|
-
var
|
|
6652
|
+
var log12 = forComponent("Reflect");
|
|
6398
6653
|
async function reflectOnRun(o) {
|
|
6399
6654
|
const digest = digestRun(o.result.messages, o.maxDigestChars ?? 6e3);
|
|
6400
6655
|
if (!digest.trim()) return null;
|
|
@@ -6410,7 +6665,7 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
|
|
|
6410
6665
|
const r = await o.ai.chat({ model: o.model, messages: [{ role: "user", content: prompt }], stream: false });
|
|
6411
6666
|
text = r?.content ?? "";
|
|
6412
6667
|
} catch (e) {
|
|
6413
|
-
|
|
6668
|
+
log12.warn(`reflection call failed: ${e?.message ?? e}`);
|
|
6414
6669
|
return null;
|
|
6415
6670
|
}
|
|
6416
6671
|
const m = text.match(/LESSON:\s*(.+)/i);
|
|
@@ -6420,10 +6675,10 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
|
|
|
6420
6675
|
try {
|
|
6421
6676
|
await writeFact(o.fs, o.dir, slug2, lesson);
|
|
6422
6677
|
} catch (e) {
|
|
6423
|
-
|
|
6678
|
+
log12.warn(`could not persist lesson: ${e?.message ?? e}`);
|
|
6424
6679
|
return null;
|
|
6425
6680
|
}
|
|
6426
|
-
|
|
6681
|
+
log12.debug(`reflection persisted ${slug2}`);
|
|
6427
6682
|
return slug2;
|
|
6428
6683
|
}
|
|
6429
6684
|
function digestRun(messages, maxChars) {
|
|
@@ -6472,7 +6727,7 @@ function forComponent2(name) {
|
|
|
6472
6727
|
}
|
|
6473
6728
|
|
|
6474
6729
|
// node_modules/@bod.ee/voice/src/core/emotion.ts
|
|
6475
|
-
var
|
|
6730
|
+
var log13 = forComponent2("Emotion");
|
|
6476
6731
|
var EMOTIONS = [
|
|
6477
6732
|
// primary (best results)
|
|
6478
6733
|
"neutral",
|
|
@@ -6626,7 +6881,7 @@ var EmotionStream = class {
|
|
|
6626
6881
|
if (tag) {
|
|
6627
6882
|
this.pending = tag;
|
|
6628
6883
|
if (this.show) display += m[0];
|
|
6629
|
-
} else
|
|
6884
|
+
} else log13.debug(`dropping unknown emotion tag ${m[0]}`);
|
|
6630
6885
|
last = m.index + m[0].length;
|
|
6631
6886
|
}
|
|
6632
6887
|
this.emit(body.slice(last), (s, d, p) => {
|
|
@@ -6773,7 +7028,7 @@ function computeHoldMs(ctx) {
|
|
|
6773
7028
|
}
|
|
6774
7029
|
|
|
6775
7030
|
// node_modules/@bod.ee/voice/src/core/engine.ts
|
|
6776
|
-
var
|
|
7031
|
+
var log14 = forComponent2("VoiceEngine");
|
|
6777
7032
|
var realClock = {
|
|
6778
7033
|
now: () => performance.now(),
|
|
6779
7034
|
setTimeout: (fn, ms) => setTimeout(fn, ms),
|
|
@@ -7161,8 +7416,8 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7161
7416
|
this.tts.warmup?.();
|
|
7162
7417
|
this.setState("listening");
|
|
7163
7418
|
if (this.options.bargeInMode === "aec" && !this.stt.usingAec)
|
|
7164
|
-
|
|
7165
|
-
|
|
7419
|
+
log14.warn("bargeInMode=aec but mic does not report echoCancellation applied \u2014 STT may transcribe the assistant");
|
|
7420
|
+
log14.debug(`voice I/O up (${this.usingAec ? "AEC" : "heuristic echo"} capture, mode=${this.options.bargeInMode}, mic reports aec=${this.stt.usingAec})`);
|
|
7166
7421
|
}
|
|
7167
7422
|
/** The RESOLVED barge-in tier (the single seam every tier branch reads). `bargeInMode` overrides
|
|
7168
7423
|
* the mic's self-report — see the option doc for why the report is not trustworthy on mobile. */
|
|
@@ -7187,7 +7442,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7187
7442
|
this.options.onDiag({ t: this.clock.now(), kind, ...fields });
|
|
7188
7443
|
} catch (e) {
|
|
7189
7444
|
this.diagOn = false;
|
|
7190
|
-
|
|
7445
|
+
log14.debug(`onDiag threw \u2014 diagnostics disabled: ${e instanceof Error ? e.message : e}`);
|
|
7191
7446
|
}
|
|
7192
7447
|
}
|
|
7193
7448
|
idleWaiters = [];
|
|
@@ -7326,7 +7581,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7326
7581
|
}
|
|
7327
7582
|
for (const w of this.words(this.reply)) this.echoWords.add(w);
|
|
7328
7583
|
this.tts.speak(forSpeech(speech), true);
|
|
7329
|
-
if (!this.spokeDeltas && this.turnStartAt)
|
|
7584
|
+
if (!this.spokeDeltas && this.turnStartAt) log14.debug(`ttft: ${Math.round(this.clock.now() - this.turnStartAt)}ms`);
|
|
7330
7585
|
this.spokeDeltas = true;
|
|
7331
7586
|
this.setState("speaking");
|
|
7332
7587
|
if (this.options.revealMode !== "off" && display) {
|
|
@@ -7439,13 +7694,13 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7439
7694
|
this.drainTimer = null;
|
|
7440
7695
|
this.speaking = false;
|
|
7441
7696
|
this.stopWordReveal(true);
|
|
7442
|
-
if (this.turnStartAt)
|
|
7697
|
+
if (this.turnStartAt) log14.debug(`turn: ${Math.round(this.clock.now() - this.turnStartAt)}ms (incl. playback)`);
|
|
7443
7698
|
this.echoUntil = this.clock.now() + 2500;
|
|
7444
7699
|
if (!this.usingAec) this.stt.reset();
|
|
7445
7700
|
try {
|
|
7446
7701
|
this.options.onSpeechEnd();
|
|
7447
7702
|
} catch (e) {
|
|
7448
|
-
|
|
7703
|
+
log14.warn(`onSpeechEnd threw: ${e instanceof Error ? e.message : e}`);
|
|
7449
7704
|
}
|
|
7450
7705
|
this.setState("listening");
|
|
7451
7706
|
if (this.uttQueue.length) this.pumpQueue();
|
|
@@ -7519,7 +7774,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7519
7774
|
if (!host) return pick;
|
|
7520
7775
|
const ctx = { text: this.lastDispatchText, question: this.lastDispatchWasQuestion, barged: this.bargedBeforeDispatch };
|
|
7521
7776
|
const fallback = (e) => {
|
|
7522
|
-
|
|
7777
|
+
log14.warn(`pickAck failed \u2014 using the default pools: ${e instanceof Error ? e.message : e}`);
|
|
7523
7778
|
pick.picked = false;
|
|
7524
7779
|
return void 0;
|
|
7525
7780
|
};
|
|
@@ -7609,7 +7864,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7609
7864
|
this.ackProbe = null;
|
|
7610
7865
|
const queued = this.uttQueue.splice(0);
|
|
7611
7866
|
const droppedQueued = queued.length;
|
|
7612
|
-
if (droppedQueued)
|
|
7867
|
+
if (droppedQueued) log14.info(`barge-in cut ${droppedQueued} queued worker utterance(s) \u2014 handed to the host`);
|
|
7613
7868
|
this.overlapKeptUntil = 0;
|
|
7614
7869
|
if (!this.speaking && !this.drainTimer) {
|
|
7615
7870
|
if (droppedQueued) this.lastInterrupted = { full: "", heard: "", queued, queuedTurn: false };
|
|
@@ -7838,7 +8093,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7838
8093
|
if (flat && this.lastDispatchFlat && this.clock.now() - this.lastDispatchAt < _VoiceEngine.DUP_FINAL_MS && (this.lastDispatchFlat.startsWith(flat) || flat.startsWith(this.lastDispatchFlat))) return;
|
|
7839
8094
|
this.specSpent = true;
|
|
7840
8095
|
this.specText = this.specPartial;
|
|
7841
|
-
|
|
8096
|
+
log14.debug(`speculate: "${this.specText.slice(0, 60)}"`);
|
|
7842
8097
|
this.diag("speculate", { text: this.specText });
|
|
7843
8098
|
this.options.onSpeculate(this.specText);
|
|
7844
8099
|
}, this.options.speculativeMs);
|
|
@@ -7907,7 +8162,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7907
8162
|
};
|
|
7908
8163
|
this.tts.newContext();
|
|
7909
8164
|
this.tts.speak(phrase, false);
|
|
7910
|
-
|
|
8165
|
+
log14.debug(`backchannel: "${phrase}"`);
|
|
7911
8166
|
this.diag("backchannel", { phrase });
|
|
7912
8167
|
this.options.onBackchannel(phrase);
|
|
7913
8168
|
}
|
|
@@ -7940,7 +8195,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7940
8195
|
});
|
|
7941
8196
|
if (idx < 0) return text;
|
|
7942
8197
|
toks.splice(idx, 1);
|
|
7943
|
-
|
|
8198
|
+
log14.verbose(`stripped backchannel echo "${this.lastBcPhrase}" from dispatching final`);
|
|
7944
8199
|
return toks.join(" ");
|
|
7945
8200
|
}
|
|
7946
8201
|
/** Merge a resumed utterance into the pending one, deduping any word-overlap. Soniox re-finalizes
|
|
@@ -8126,7 +8381,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
8126
8381
|
}
|
|
8127
8382
|
const bargeHold = !this.looksIncomplete(this.pendingUtt) && this.unfinishedBargeLine(this.pendingUtt);
|
|
8128
8383
|
if (this.options.incompleteMergeMs && (bargeHold || this.looksIncomplete(this.pendingUtt))) {
|
|
8129
|
-
|
|
8384
|
+
log14.verbose(`hold: incomplete utterance "${this.pendingUtt.slice(-40)}"`);
|
|
8130
8385
|
this.diag("hold", { reason: bargeHold ? "barge_unfinished" : "incomplete", tail: this.pendingUtt.slice(-40) });
|
|
8131
8386
|
this.options.onHold();
|
|
8132
8387
|
if (this.options.holdFiller && !this.speaking) this.speakFiller(this.options.holdFiller);
|
|
@@ -8165,13 +8420,13 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
8165
8420
|
const answered = this.answeredSinceDispatch();
|
|
8166
8421
|
const part = flat !== this.lastDispatchFlat && !answered && this.echoesDispatch(text);
|
|
8167
8422
|
if (part || flat && flat === this.lastDispatchFlat && !answered && this.clock.now() - this.lastDispatchAt < _VoiceEngine.DUP_FINAL_MS) {
|
|
8168
|
-
|
|
8423
|
+
log14.verbose(`dropped duplicate final "${text.slice(0, 40)}" (identical to the just-dispatched utterance, no reply between)`);
|
|
8169
8424
|
this.diag("utterance_drop", { reason: "dup_final", text: text.slice(0, 60) });
|
|
8170
8425
|
return;
|
|
8171
8426
|
}
|
|
8172
8427
|
const refinal = this.refinalizes(text);
|
|
8173
8428
|
if (refinal && this.repliedSinceDispatch) {
|
|
8174
|
-
|
|
8429
|
+
log14.verbose(`dropped re-finalization "${text.slice(0, 60)}" (superset of the just-dispatched utterance, reply already flowing)`);
|
|
8175
8430
|
this.diag("utterance_drop", { reason: "refinalized", text: text.slice(0, 60) });
|
|
8176
8431
|
this.lastDispatchFlat = flat;
|
|
8177
8432
|
this.lastDispatchWords = text.trim().split(/\s+/);
|
|
@@ -8450,7 +8705,7 @@ var SpokenSplitter = class {
|
|
|
8450
8705
|
};
|
|
8451
8706
|
|
|
8452
8707
|
// node_modules/@bod.ee/voice/src/adapters/soniox.ts
|
|
8453
|
-
var
|
|
8708
|
+
var log15 = forComponent2("SonioxSTT");
|
|
8454
8709
|
var now = () => performance.now();
|
|
8455
8710
|
var SonioxSTTOptions = class {
|
|
8456
8711
|
auth = "";
|
|
@@ -8495,7 +8750,7 @@ var SonioxSTT = class {
|
|
|
8495
8750
|
this.onDiag({ t: now(), kind, ...fields });
|
|
8496
8751
|
} catch (e) {
|
|
8497
8752
|
this.diagOn = false;
|
|
8498
|
-
|
|
8753
|
+
log15.debug(`onDiag threw \u2014 STT diagnostics disabled: ${e instanceof Error ? e.message : e}`);
|
|
8499
8754
|
}
|
|
8500
8755
|
}
|
|
8501
8756
|
lastChunkAt = 0;
|
|
@@ -8537,11 +8792,11 @@ var SonioxSTT = class {
|
|
|
8537
8792
|
this.ws.onmessage = (ev) => this.handle(JSON.parse(String(ev.data)));
|
|
8538
8793
|
this.ws.onclose = (ev) => {
|
|
8539
8794
|
if (this.stopped) return;
|
|
8540
|
-
|
|
8795
|
+
log15.warn(`soniox ws closed (${ev.code} ${ev.reason || ""}) \u2014 reconnecting`);
|
|
8541
8796
|
this.diag("stt_ws_closed", { code: ev.code, reason: String(ev.reason || ""), reconnecting: true });
|
|
8542
8797
|
this.reset();
|
|
8543
8798
|
this.connectWs().catch((e) => {
|
|
8544
|
-
|
|
8799
|
+
log15.error(`soniox reconnect failed: ${e.message}`);
|
|
8545
8800
|
this.diag("stt_reconnect_failed", { message: e.message });
|
|
8546
8801
|
});
|
|
8547
8802
|
};
|
|
@@ -8553,7 +8808,7 @@ var SonioxSTT = class {
|
|
|
8553
8808
|
this.endpointTimer = setInterval(() => {
|
|
8554
8809
|
const combined = (this.finalText + this.partialText).trim();
|
|
8555
8810
|
if (!combined || now() - this.lastChangeAt < this.options.silenceEndpointMs) return;
|
|
8556
|
-
if (this.firstTokenAt)
|
|
8811
|
+
if (this.firstTokenAt) log15.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192silence-endpoint, "${combined.slice(0, 60)}"`);
|
|
8557
8812
|
this.reset();
|
|
8558
8813
|
this.onUtterance(combined, now());
|
|
8559
8814
|
}, 120);
|
|
@@ -8565,7 +8820,7 @@ var SonioxSTT = class {
|
|
|
8565
8820
|
if (this.stopped) return;
|
|
8566
8821
|
const ref = this.lastChunkAt || this.startedChunksAt;
|
|
8567
8822
|
if (now() - ref > noAudioMs) {
|
|
8568
|
-
|
|
8823
|
+
log15.error(`stt: no mic audio for >${Math.round(noAudioMs / 1e3)}s \u2014 capture device stopped delivering`);
|
|
8569
8824
|
this.diag("stt_watchdog_fatal", { noAudioMs });
|
|
8570
8825
|
this.onFatal("microphone stopped delivering audio (try a different input device, e.g. AirPods, or check System Settings \u2192 Sound \u2192 Input)");
|
|
8571
8826
|
this.stop();
|
|
@@ -8588,7 +8843,7 @@ var SonioxSTT = class {
|
|
|
8588
8843
|
handle(m) {
|
|
8589
8844
|
if (m.error_message) {
|
|
8590
8845
|
this.diag("stt_error", { message: String(m.error_message), code: m.error_code });
|
|
8591
|
-
return
|
|
8846
|
+
return log15.error(`soniox: ${m.error_message}`);
|
|
8592
8847
|
}
|
|
8593
8848
|
let endpoint = false;
|
|
8594
8849
|
for (const t of m.tokens ?? []) {
|
|
@@ -8605,7 +8860,7 @@ var SonioxSTT = class {
|
|
|
8605
8860
|
this.onPartial(combined);
|
|
8606
8861
|
if (endpoint && this.finalText.trim()) {
|
|
8607
8862
|
const utterance = this.finalText.trim();
|
|
8608
|
-
if (this.firstTokenAt)
|
|
8863
|
+
if (this.firstTokenAt) log15.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192endpoint, "${utterance.slice(0, 60)}"`);
|
|
8609
8864
|
this.reset();
|
|
8610
8865
|
this.onUtterance(utterance, now());
|
|
8611
8866
|
}
|
|
@@ -8627,7 +8882,7 @@ var SonioxSTT = class {
|
|
|
8627
8882
|
};
|
|
8628
8883
|
|
|
8629
8884
|
// node_modules/@bod.ee/voice/src/adapters/cartesia.ts
|
|
8630
|
-
var
|
|
8885
|
+
var log16 = forComponent2("CartesiaTTS");
|
|
8631
8886
|
var now2 = () => performance.now();
|
|
8632
8887
|
var CartesiaTTSOptions = class {
|
|
8633
8888
|
auth = "";
|
|
@@ -8663,7 +8918,7 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
8663
8918
|
this.onDiag({ t: now2(), kind, ...fields });
|
|
8664
8919
|
} catch (e) {
|
|
8665
8920
|
this.diagOn = false;
|
|
8666
|
-
|
|
8921
|
+
log16.debug(`onDiag threw \u2014 TTS diagnostics disabled: ${e instanceof Error ? e.message : e}`);
|
|
8667
8922
|
}
|
|
8668
8923
|
}
|
|
8669
8924
|
firstAudioAt = 0;
|
|
@@ -8698,11 +8953,11 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
8698
8953
|
this.ws.onerror = (e) => rej(new Error(`cartesia ws: ${e.message || "connect failed"}`));
|
|
8699
8954
|
});
|
|
8700
8955
|
this.ws.onclose = (ev) => {
|
|
8701
|
-
|
|
8956
|
+
log16.warn(`cartesia ws closed (${ev.code} ${ev.reason || ""})`);
|
|
8702
8957
|
this.diag("tts_ws_closed", { code: ev.code, reason: String(ev.reason || ""), reconnecting: !this.closed });
|
|
8703
8958
|
if (!this.closed) {
|
|
8704
8959
|
this.connecting = this.doConnect().catch((e) => {
|
|
8705
|
-
|
|
8960
|
+
log16.error(`cartesia reconnect failed: ${e.message}`);
|
|
8706
8961
|
this.diag("tts_reconnect_failed", { message: e.message });
|
|
8707
8962
|
});
|
|
8708
8963
|
}
|
|
@@ -8730,12 +8985,12 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
8730
8985
|
this.down = true;
|
|
8731
8986
|
this.downAt = now2();
|
|
8732
8987
|
this.consecutiveOk = 0;
|
|
8733
|
-
|
|
8988
|
+
log16.warn(`TTS circuit breaker open \u2014 ${this.consecutiveErrors} consecutive errors, switching to text-only`);
|
|
8734
8989
|
this.diag("tts_breaker_open", { errors: this.consecutiveErrors });
|
|
8735
8990
|
this.onDone();
|
|
8736
8991
|
this.startProbe();
|
|
8737
8992
|
} else if (!this.down) {
|
|
8738
|
-
(/No valid transcripts/i.test(m.message || "") ?
|
|
8993
|
+
(/No valid transcripts/i.test(m.message || "") ? log16.debug : log16.warn)(`cartesia: ${JSON.stringify(m)}`);
|
|
8739
8994
|
}
|
|
8740
8995
|
}
|
|
8741
8996
|
};
|
|
@@ -8750,7 +9005,7 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
8750
9005
|
this.stopProbe();
|
|
8751
9006
|
const downMs = this.downAt ? now2() - this.downAt : 0;
|
|
8752
9007
|
this.diag("tts_breaker_close", { downMs: Math.round(downMs) });
|
|
8753
|
-
(downMs < 2e3 ?
|
|
9008
|
+
(downMs < 2e3 ? log16.debug : log16.info)(`TTS recovered${downMs ? ` (down ${downMs}ms)` : ""}`);
|
|
8754
9009
|
}
|
|
8755
9010
|
/** Ensure the WS is open before sending — reconnects if idle-closed. */
|
|
8756
9011
|
async ensureConnected() {
|
|
@@ -8833,13 +9088,13 @@ function base64ToBytes(b64) {
|
|
|
8833
9088
|
}
|
|
8834
9089
|
|
|
8835
9090
|
// node_modules/@bod.ee/voice/src/adapters/fish.ts
|
|
8836
|
-
var
|
|
9091
|
+
var log17 = forComponent2("FishTTS");
|
|
8837
9092
|
|
|
8838
9093
|
// node_modules/@bod.ee/voice/src/adapters/grok.ts
|
|
8839
|
-
var
|
|
9094
|
+
var log18 = forComponent2("GrokTTS");
|
|
8840
9095
|
|
|
8841
9096
|
// node_modules/@bod.ee/voice/src/adapters/grok-stt.ts
|
|
8842
|
-
var
|
|
9097
|
+
var log19 = forComponent2("GrokSTT");
|
|
8843
9098
|
|
|
8844
9099
|
// src/voice/engine.ts
|
|
8845
9100
|
init_logging();
|
|
@@ -8851,7 +9106,7 @@ configureLogging((name) => {
|
|
|
8851
9106
|
|
|
8852
9107
|
// src/voice/instinct.ts
|
|
8853
9108
|
init_logging();
|
|
8854
|
-
var
|
|
9109
|
+
var log20 = forComponent("Instinct");
|
|
8855
9110
|
var ACK_SITUATIONS = {
|
|
8856
9111
|
hard_question: `a question that takes real thought, judgment or looking something up ("what's the biggest takeaway?", "how should I structure this?")`,
|
|
8857
9112
|
simple_question: 'a quick question with an instant, obvious answer ("how are you?", "what time is it?", "can you hear me?")',
|
|
@@ -8890,7 +9145,7 @@ function interruptionNote(cut, intent) {
|
|
|
8890
9145
|
|
|
8891
9146
|
// src/voice/unsaid.ts
|
|
8892
9147
|
init_logging();
|
|
8893
|
-
var
|
|
9148
|
+
var log21 = forComponent("Unsaid");
|
|
8894
9149
|
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(" "));
|
|
8895
9150
|
var contentStems = (s) => (s.toLowerCase().match(/[a-z0-9']+/g) ?? []).filter((w) => w.length >= 4 && !STOP2.has(w)).map((w) => w.slice(0, 4));
|
|
8896
9151
|
var SAID_OVERLAP = 0.6;
|
|
@@ -8930,7 +9185,7 @@ async function unsaidClauses(pieces, spoken, decider, timeoutMs = 5e3) {
|
|
|
8930
9185
|
return p < CONVEYED_MIN;
|
|
8931
9186
|
}).map((x) => x.c);
|
|
8932
9187
|
} catch (e) {
|
|
8933
|
-
|
|
9188
|
+
log21.warn(`unsaid judge failed \u2014 only near-zero-overlap clauses count as unsaid: ${e.message}`);
|
|
8934
9189
|
return fallback();
|
|
8935
9190
|
} finally {
|
|
8936
9191
|
clearTimeout(timer);
|
|
@@ -9001,7 +9256,7 @@ async function judgeLeak(decider, sentence, userSaid) {
|
|
|
9001
9256
|
}
|
|
9002
9257
|
|
|
9003
9258
|
// src/duplex.ts
|
|
9004
|
-
var
|
|
9259
|
+
var log22 = forComponent("DuplexAgent");
|
|
9005
9260
|
function describeCall(call) {
|
|
9006
9261
|
const v = call.args && Object.values(call.args).find((x) => typeof x === "string" && x.trim());
|
|
9007
9262
|
const hint = v ? ` (${String(v).replace(/\s+/g, " ").trim().slice(0, 48)})` : "";
|
|
@@ -9261,7 +9516,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9261
9516
|
const m = this.reflexBuf.match(RESERVED_EVENT_MARKER) ?? this.reflexBuf.match(RESERVED_EVENT_OPENER);
|
|
9262
9517
|
if (m) {
|
|
9263
9518
|
this.fabricationCut = true;
|
|
9264
|
-
|
|
9519
|
+
log22.warn(`reflex fabricated a [task \u2026] event in its spoken stream \u2014 cutting it (kept ${m.index} chars)`);
|
|
9265
9520
|
this.reflexBuf = this.reflexBuf.slice(0, Math.max(m.index, this.reflexForwarded));
|
|
9266
9521
|
this.pumpReflex(true);
|
|
9267
9522
|
return;
|
|
@@ -9353,7 +9608,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9353
9608
|
const at = from + ok;
|
|
9354
9609
|
this.leakHit = true;
|
|
9355
9610
|
this.leakCuts.push(leak.trim().slice(0, 40));
|
|
9356
|
-
|
|
9611
|
+
log22.warn(`reflex spoke its own analysis ("${leak.trim().slice(0, 40)}\u2026") \u2014 cut before TTS`);
|
|
9357
9612
|
this.notify("diag", "analysis_leak", { text: leak.trim().slice(0, 80), at: this.turnSpoken.trim() ? "mid_reply" : "before_speech" });
|
|
9358
9613
|
const next = this.stepMarks.find((k) => k > at);
|
|
9359
9614
|
if (next === void 0) {
|
|
@@ -9393,7 +9648,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9393
9648
|
this.notify("diag", "leak_judge", { text: s.slice(0, 80), p: Math.round(p * 100) / 100, ms: Math.round(performance.now() - t0) });
|
|
9394
9649
|
return p >= LEAK_JUDGE_THRESHOLD;
|
|
9395
9650
|
}, (e) => {
|
|
9396
|
-
|
|
9651
|
+
log22.warn(`leak judge failed \u2014 speaking the sentence: ${e.message}`);
|
|
9397
9652
|
return false;
|
|
9398
9653
|
}).then((leak) => {
|
|
9399
9654
|
clearTimeout(timer);
|
|
@@ -9427,7 +9682,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9427
9682
|
let i = t.length - 1;
|
|
9428
9683
|
while (i >= this.turnBase && !(t[i].role === "assistant" && typeof t[i].content === "string" && t[i].content.includes(key))) i--;
|
|
9429
9684
|
if (i < this.turnBase) {
|
|
9430
|
-
|
|
9685
|
+
log22.warn("analysis leak cut, but its text was not found in the transcript to scrub");
|
|
9431
9686
|
continue;
|
|
9432
9687
|
}
|
|
9433
9688
|
const m = t[i];
|
|
@@ -9507,7 +9762,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9507
9762
|
return {
|
|
9508
9763
|
preToolUse: (call) => {
|
|
9509
9764
|
if (this.spec?.state === "pending" && !_DuplexAgent.SPEC_SAFE_TOOLS.has(call.name)) {
|
|
9510
|
-
|
|
9765
|
+
log22.verbose(`speculation aborted: reflex called ${call.name} on unconfirmed input`);
|
|
9511
9766
|
this.abortSpeculation();
|
|
9512
9767
|
return { block: true, reason: "Speculative turn aborted." };
|
|
9513
9768
|
}
|
|
@@ -9552,7 +9807,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9552
9807
|
try {
|
|
9553
9808
|
await this.voice.send(reminder);
|
|
9554
9809
|
} catch (e) {
|
|
9555
|
-
|
|
9810
|
+
log22.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
|
|
9556
9811
|
} finally {
|
|
9557
9812
|
this.nudging = false;
|
|
9558
9813
|
const i = this.voice.transcript.findIndex((m, k) => k >= base && m.role === "user" && m.content === reminder);
|
|
@@ -9604,7 +9859,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9604
9859
|
const accepted = this.offerOpen && this.parkedRedeliver.length > 0 && this.parkedRedeliver.every((p) => p.task === this.offer.task) && (isOfferAcceptance(content) || isTrivialBarge(content));
|
|
9605
9860
|
const judge = accepted ? void 0 : this.options.bargeIntent;
|
|
9606
9861
|
return (accepted ? Promise.resolve("resume") : judge ? judge(content).catch((e) => {
|
|
9607
|
-
|
|
9862
|
+
log22.warn(`bargeIntent judge failed \u2014 using the allowlist: ${e.message}`);
|
|
9608
9863
|
return fallback();
|
|
9609
9864
|
}) : Promise.resolve(fallback())).then((intent) => {
|
|
9610
9865
|
this.notify("diag", "barge_intent", { intent, text: content.slice(0, 80) });
|
|
@@ -9800,7 +10055,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9800
10055
|
try {
|
|
9801
10056
|
res = await this.voice.send(spec.text);
|
|
9802
10057
|
} catch (e) {
|
|
9803
|
-
|
|
10058
|
+
log22.warn(`speculative turn failed: ${e instanceof Error ? e.message : e}`);
|
|
9804
10059
|
} finally {
|
|
9805
10060
|
this.voice.options.signal = prevSignal;
|
|
9806
10061
|
}
|
|
@@ -9814,7 +10069,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9814
10069
|
if (d === "abort") {
|
|
9815
10070
|
if (this.voice.transcript.length > base) this.voice.transcript.length = base;
|
|
9816
10071
|
this.speculativeAbortedCalls++;
|
|
9817
|
-
|
|
10072
|
+
log22.verbose(`speculation aborted (${this.speculativeAbortedCalls} total): "${spec.text.slice(0, 50)}"`);
|
|
9818
10073
|
if (this.spec === spec) this.spec = void 0;
|
|
9819
10074
|
return res ?? empty;
|
|
9820
10075
|
}
|
|
@@ -10164,7 +10419,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
10164
10419
|
this.notify("task_verify", `task ${id}: verifying`, { id });
|
|
10165
10420
|
const cres = await new Agent(checkerOpts).run(checkBrief);
|
|
10166
10421
|
if (cres.finishReason !== "stop") {
|
|
10167
|
-
|
|
10422
|
+
log22.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
|
|
10168
10423
|
this.notify("task_verify", `task ${id}: verify inconclusive (${cres.finishReason})`, { id, finishReason: cres.finishReason });
|
|
10169
10424
|
}
|
|
10170
10425
|
const sum = (a = 0, b = 0) => a + b;
|
|
@@ -10300,7 +10555,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
10300
10555
|
rec.status = "done";
|
|
10301
10556
|
rec.result = res.text;
|
|
10302
10557
|
const incomplete = res.finishReason !== "stop" && res.finishReason !== "empty";
|
|
10303
|
-
|
|
10558
|
+
log22.verbose(`task ${id} done (${res.steps} steps${incomplete ? `, INCOMPLETE: ${res.finishReason}` : ""})`);
|
|
10304
10559
|
this.notify("task_done", `task ${id} (${rec.label}) completed`, {
|
|
10305
10560
|
id,
|
|
10306
10561
|
text: res.text,
|
|
@@ -10334,14 +10589,14 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
10334
10589
|
foldIfSuperseded(rec, note) {
|
|
10335
10590
|
if (rec.deliveryParked !== "moved_on") return false;
|
|
10336
10591
|
if (note.trim()) this.voice.transcript.push({ role: "assistant", content: note });
|
|
10337
|
-
|
|
10592
|
+
log22.verbose(`task ${rec.id} settled but superseded (${rec.status}) \u2014 folded into transcript, not voiced`);
|
|
10338
10593
|
return true;
|
|
10339
10594
|
}
|
|
10340
10595
|
failTask(rec, msg) {
|
|
10341
10596
|
this.dropAsk(rec.id);
|
|
10342
10597
|
rec.status = "error";
|
|
10343
10598
|
rec.result = msg;
|
|
10344
|
-
|
|
10599
|
+
log22.warn(`task ${rec.id} failed: ${msg}`);
|
|
10345
10600
|
this.notify("task_error", `task ${rec.id} (${rec.label}) failed: ${msg}`, { id: rec.id });
|
|
10346
10601
|
if (this.foldIfSuperseded(rec, `The "${rec.label}" task failed: ${msg}`)) return;
|
|
10347
10602
|
this.dropParked(rec);
|
|
@@ -10580,7 +10835,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
10580
10835
|
// src/modelRouter.ts
|
|
10581
10836
|
init_llm();
|
|
10582
10837
|
init_logging();
|
|
10583
|
-
var
|
|
10838
|
+
var log23 = forComponent("ModelRouter");
|
|
10584
10839
|
var ModelRouterOptions = class {
|
|
10585
10840
|
classifier = jevClassifier();
|
|
10586
10841
|
/** Capability tiers → model id (any provider). basic/standard run the main session; premium is only ever an offload. */
|
|
@@ -10664,10 +10919,10 @@ var ModelRouter = class {
|
|
|
10664
10919
|
if (tier === "premium") d.offload = this.state.offload && (c.sameThread ?? 0) >= o.sameThreadAt ? "continue" : "fresh";
|
|
10665
10920
|
} catch (e) {
|
|
10666
10921
|
const msg = e instanceof Error ? e.message : String(e);
|
|
10667
|
-
|
|
10922
|
+
log23.warn(`classifier failed, falling back to standard: ${msg}`);
|
|
10668
10923
|
d = { tier: "standard", model: o.models.standard, probs: {}, fallback: msg, classifyMs: Math.round(performance.now() - t0) };
|
|
10669
10924
|
}
|
|
10670
|
-
|
|
10925
|
+
log23.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`);
|
|
10671
10926
|
return d;
|
|
10672
10927
|
}
|
|
10673
10928
|
/** Run one user turn on `agent` with routing. Returns a RunResult carrying `costUsd` + `routing`. */
|
|
@@ -10719,7 +10974,7 @@ ${text}`;
|
|
|
10719
10974
|
const answer = res.text || `(premium sub-agent finished with no reply; finishReason=${res.finishReason})`;
|
|
10720
10975
|
agent.transcript.push({ role: "user", content }, { role: "assistant", content: answer });
|
|
10721
10976
|
this.state.offload = { messages: child.transcript, mainLen: agent.transcript.length, question: text };
|
|
10722
|
-
|
|
10977
|
+
log23.info(`offload ${cont ? "continue" : "fresh"} done \xB7 ${res.finishReason} \xB7 ${res.steps} steps`);
|
|
10723
10978
|
const costUsd = o.costOf(main, briefUsage) + o.costOf(o.models.premium, res.usage);
|
|
10724
10979
|
return { ...res, text: answer, messages: agent.transcript, usage: addUsage(briefUsage, res.usage), costUsd, routing: d };
|
|
10725
10980
|
}
|
|
@@ -10735,7 +10990,7 @@ The request:
|
|
|
10735
10990
|
${text}`);
|
|
10736
10991
|
return { text: r.text.trim() || text, usage: r.usage };
|
|
10737
10992
|
} catch (e) {
|
|
10738
|
-
|
|
10993
|
+
log23.warn(`brief failed, sending the raw request: ${e instanceof Error ? e.message : String(e)}`);
|
|
10739
10994
|
return { text };
|
|
10740
10995
|
} finally {
|
|
10741
10996
|
agent.transcript = snap;
|
|
@@ -10767,8 +11022,8 @@ async function loadJev(opts) {
|
|
|
10767
11022
|
const env = { ...process.env };
|
|
10768
11023
|
const file = opts.envFile ?? process.env.AGENTX_JEV_ENV;
|
|
10769
11024
|
if (file) {
|
|
10770
|
-
const { readFileSync:
|
|
10771
|
-
for (const l of
|
|
11025
|
+
const { readFileSync: readFileSync4 } = await import("fs");
|
|
11026
|
+
for (const l of readFileSync4(file, "utf8").split("\n")) {
|
|
10772
11027
|
const m = l.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*?)\s*$/);
|
|
10773
11028
|
if (m && !env[m[1]]) env[m[1]] = m[2].replace(/^['"]|['"]$/g, "");
|
|
10774
11029
|
}
|
|
@@ -10780,7 +11035,7 @@ async function jevAvailable(opts = {}) {
|
|
|
10780
11035
|
const { env } = await loadJev(opts);
|
|
10781
11036
|
return JEV_KEYS.some((k) => env[k]?.trim());
|
|
10782
11037
|
} catch (e) {
|
|
10783
|
-
|
|
11038
|
+
log23.debug(`jev unavailable: ${e instanceof Error ? e.message : String(e)}`);
|
|
10784
11039
|
return false;
|
|
10785
11040
|
}
|
|
10786
11041
|
}
|