@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/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 { readFileSync, statSync as statSync2, realpathSync } from "fs";
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(statSync2(path).size * 4 / 3);
264
+ bytes = Math.ceil(statSync3(path).size * 4 / 3);
81
265
  } catch (e) {
82
- log2.warn(`image ref stat failed, weighing as 0: ${path}`, e);
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
- log2.debug("image ref not in registry \u2014 treating as non-image text");
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
- log2.debug(`image-ref root unavailable: ${r}`, e);
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
- log2.warn(`resumed image ref unreadable, will stub: ${f.path}`, e);
315
+ log3.warn(`resumed image ref unreadable, will stub: ${f.path}`, e);
132
316
  return p;
133
317
  }
134
318
  if (!inside(real)) {
135
- log2.warn(`resumed image ref outside the allowed roots, will stub: ${f.path} -> ${real}`);
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
- log2.debug("bad imageRef tool result", e);
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, stub) : stripContent(m.content, stub);
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.bytes : stub.length;
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) return contentBytes(content);
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 readRefDataUrl(ref) {
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 url = readRefDataUrl(tool);
239
- return url ? JSON.stringify({ dataUrl: url, image: tool.path }) : stub;
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" || !isImageRefUrl(p.image_url?.url)) return p;
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(p.image_url?.url);
247
- const url = ref && readRefDataUrl(ref);
248
- return url ? imagePart(url) : { type: "text", text: stub };
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 log2, p_sep, IMAGE_ELIDE_STUB, IMAGE_REF_SCHEME, REF_REGISTRY_MAX, refRegistry, isImageRefUrl, bearsImage, COMMITMENT, PROGRESSIVE, NEXT_STEP, SIGN_OFF;
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
- log2 = forComponent("llm");
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
- log3.debug(`WebFetch ${u} failed`, e);
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
- log3.debug("WebSearch failed", e);
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 log3, _dnsLookup, webFetchTool, webSearchTool, ANTHROPIC_SEARCH_DESC, webSearchAnthropicTool;
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
- log3 = forComponent("web");
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
- log4.debug(`realPath unavailable for ${path} \u2014 inlining image bytes instead`, e);
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 log4, numberLines, bashTool, IMG_MIME, readTool, editTool;
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
- log4 = forComponent("tools");
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 log5, JobRegistryOptions, secs, JobRegistry, NO_JOB, describe;
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
- log5 = forComponent("jobs");
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
- log5.warn(`job onExit handler threw for ${job.id}: ${e}`);
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
- log5.warn(`onKill for ${id} threw: ${e}`);
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
- log5.debug(`cpu probe for ${j.id} failed: ${e}`);
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
- log5.debug(`not reaping ${j.id}: ${verdict.reason} but CPU unknown`);
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
- log5.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`);
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) => log5.warn(`job sweep failed: ${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 statSync3, readFileSync as readFileSync2, rmSync } from "fs";
1921
- import { join as join2 } from "path";
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 join2(repoRoot, ".claude", "worktrees");
2170
+ return join3(repoRoot, ".claude", "worktrees");
1938
2171
  }
1939
2172
  function worktreePathFor(repoRoot, slug2) {
1940
- return join2(worktreesDir(repoRoot), flattenSlug(slug2));
2173
+ return join3(worktreesDir(repoRoot), flattenSlug(slug2));
1941
2174
  }
1942
2175
  function git(args, cwd) {
1943
- const r = spawnSync("git", args, {
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 = readFileSync2(join2(worktreePath, ".git"), "utf-8").trim();
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 = join2(m[1], "HEAD");
1960
- const head = readFileSync2(headPath, "utf-8").trim();
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 = join2(m[1], "..", refMatch[1]);
2196
+ const refPath = join3(m[1], "..", refMatch[1]);
1964
2197
  try {
1965
- return readFileSync2(refPath, "utf-8").trim();
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 = join2(repoRoot, dir);
1999
- const dst = join2(worktreePath, dir);
2231
+ const src = join3(repoRoot, dir);
2232
+ const dst = join3(worktreePath, dir);
2000
2233
  try {
2001
- statSync3(src);
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
- statSync3(worktreePath);
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
- rmSync(join2(worktreePath, dir));
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
- log23.debug(`ps cpu probe failed for group ${pgid}: ${e}`);
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
- log23.debug("shell spawn error", err2);
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 log23, clean2, DETACHED, SECRET_ENV_RE, _spawn, ShellJobRegistry, shellStatus, NO_JOB2;
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
- log23 = forComponent("shell");
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 readFileSync9, appendFileSync, mkdirSync as mkdirSync12, writeFileSync as writeFileSync9, readdirSync as readdirSync4, statSync as statSync6, unlinkSync as unlinkSync5 } from "fs";
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 spawnSync6 } from "child_process";
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 join14, resolve as resolve4, basename as basename2, extname, dirname as dirname5 } from "path";
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 writeFileSync2 } from "fs";
3593
- import { join as join4 } from "path";
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 log6 = forComponent("subagent");
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 join4(dir, `${id}-d${depth}-${sanitizeSlug(label) || "sub"}.json`);
4075
+ return join5(dir, `${id}-d${depth}-${sanitizeSlug(label) || "sub"}.json`);
3843
4076
  } catch (e) {
3844
- log6.warn(`subagent trace dir unavailable (${dir}) \u2014 running without a trace: ${e instanceof Error ? e.message : String(e)}`);
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
- writeFileSync2(file, JSON.stringify({ ...meta, status, updated: Date.now(), messages }, null, 2));
4083
+ writeFileSync3(file, JSON.stringify({ ...meta, status, updated: Date.now(), messages }, null, 2));
3851
4084
  } catch (e) {
3852
- log6.warn(`subagent trace flush failed (${file}): ${e instanceof Error ? e.message : String(e)}`);
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
- log6.warn(`sub-task '${label}' hit its ${deadlineMs}ms wall-clock deadline (steps=${res.steps})`);
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 log7 = forComponent("Agent");
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
- log7.info(`no fs provided \u2014 defaulting to jailed real disk at ${process.cwd()}`);
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 tokensBefore = estimateTokens(this.transcript, kr);
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
- log7.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` : ""})`);
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
- const sent = this.trimContext();
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
- log7.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.`);
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)) log7.warn(v);
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("model returned an empty turn (finishReason=stop, no text, no tools)");
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) log7.warn("model stall after tool(s) already ran this step \u2014 not retrying (a retry would replay them)");
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
- log7.warn(`${empty ? "empty model turn" : stalled ? "model stall" : "network drop"} mid-step (${err2?.message ?? err2}) \u2014 retrying in ${waitMs}ms`);
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
- log7.warn(`model stalled out after retries: ${err2?.message ?? err2}`);
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
- log7.warn(`model returned empty/silent turns after retries \u2014 surfacing as failed`);
4908
- return { text: "", steps, finishReason: "empty", messages: this.transcript, usage, usageEstimated, error: err2 };
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
- log7.error(`chat() failed: ${err2?.message ?? err2}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, errLog);
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
- log7.error(`chat() failed: ${err2.message}`);
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
- log7.warn(`empty model turn after tool work (step ${steps}, provider finishReason=${res.finishReason ?? "n/a"}) \u2014 nudging once`);
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
- log7.warn(`model returned empty turns after tool work \u2014 surfacing as failed`);
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()) log7.warn(`fabricated tool use: text names ${fabricated.join(", ")} but no step/budget left to demand the call \u2014 finishing with the existing answer`);
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
- log7.warn(`fabricated tool use: text names ${fabricated.join(", ")} but the run invoked ${this.invokedTools.size ? [...this.invokedTools].join(", ") : "nothing"} \u2014 demanding the real call`);
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
- log7.verbose("delegated turn went silent after a tool \u2014 nudging a closing answer");
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
- log7.verbose(`completed in ${steps} step(s)`);
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
- log7.warn(`needs_input: ${question.question} (steps=${steps})`);
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
- log7.warn(`stall watchdog: no stream output for ${stallMs}ms \u2014 held by ${who}`);
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
- log7.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(", ")})` : ""));
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
- log7.debug(`${tc.function.name} -> ${blocked}`);
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
- log7.debug(`${tc.function.name} -> ${earlyError}`);
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
- log7.debug(`${tc.function.name}(${tc.function.arguments})`);
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
- log7.debug(`onToolOutput hook error: ${e}`);
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
- log7.debug(`${tc.function.name} -> error: ${msg}`);
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
- return dropOrphanToolResults(out);
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
- log7.warn(`context ~${total} tok still over maxContextTokens=${maxTokens} after trimming (system head can't be dropped)`);
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 log8 = forComponent("scratch");
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
- log8.debug("scratch write failed; returning raw", e);
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
- log8.debug("scratch spill failed; cropping lossy", e);
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
- log8.debug("Ask peek failed", e);
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 log9 = forComponent("Lessons");
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) => log9.warn(`could not persist ${lesson.slug}: ${e?.message ?? e}`));
6293
- log9.debug(`captured lesson ${lesson.slug} (recurred ${n}\xD7)`);
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 log10 = forComponent("Reflect");
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
- log10.warn(`reflection call failed: ${e?.message ?? e}`);
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
- log10.warn(`could not persist lesson: ${e?.message ?? e}`);
6582
+ log11.warn(`could not persist lesson: ${e?.message ?? e}`);
6328
6583
  return null;
6329
6584
  }
6330
- log10.debug(`reflection persisted ${slug2}`);
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 log11 = forComponent2("Emotion");
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
- log11.debug(`dropping unknown emotion tag ${m[0]}`);
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 log11.debug(`dropping unknown emotion tag ${m[0]}`);
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 log12 = forComponent2("VoiceEngine");
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
- log12.warn("bargeInMode=aec but mic does not report echoCancellation applied \u2014 STT may transcribe the assistant");
7089
- log12.debug(`voice I/O up (${this.usingAec ? "AEC" : "heuristic echo"} capture, mode=${this.options.bargeInMode}, mic reports aec=${this.stt.usingAec})`);
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
- log12.debug(`onDiag threw \u2014 diagnostics disabled: ${e instanceof Error ? e.message : e}`);
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) log12.debug(`ttft: ${Math.round(this.clock.now() - this.turnStartAt)}ms`);
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) log12.debug(`turn: ${Math.round(this.clock.now() - this.turnStartAt)}ms (incl. playback)`);
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
- log12.warn(`onSpeechEnd threw: ${e instanceof Error ? e.message : e}`);
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
- log12.warn(`pickAck failed \u2014 using the default pools: ${e instanceof Error ? e.message : e}`);
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) log12.info(`barge-in cut ${droppedQueued} queued worker utterance(s) \u2014 handed to the host`);
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
- log12.debug(`speculate: "${this.specText.slice(0, 60)}"`);
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
- log12.debug(`backchannel: "${phrase}"`);
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
- log12.verbose(`stripped backchannel echo "${this.lastBcPhrase}" from dispatching final`);
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
- log12.verbose(`hold: incomplete utterance "${this.pendingUtt.slice(-40)}"`);
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
- log12.verbose(`dropped duplicate final "${text.slice(0, 40)}" (identical to the just-dispatched utterance, no reply between)`);
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
- log12.verbose(`dropped re-finalization "${text.slice(0, 60)}" (superset of the just-dispatched utterance, reply already flowing)`);
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 log13 = forComponent2("SonioxSTT");
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
- log13.debug(`onDiag threw \u2014 STT diagnostics disabled: ${e instanceof Error ? e.message : e}`);
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
- log13.warn(`soniox ws closed (${ev.code} ${ev.reason || ""}) \u2014 reconnecting`);
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
- log13.error(`soniox reconnect failed: ${e.message}`);
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) log13.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192silence-endpoint, "${combined.slice(0, 60)}"`);
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
- log13.error(`stt: no mic audio for >${Math.round(noAudioMs / 1e3)}s \u2014 capture device stopped delivering`);
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 log13.error(`soniox: ${m.error_message}`);
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) log13.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192endpoint, "${utterance.slice(0, 60)}"`);
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 log14 = forComponent2("CartesiaTTS");
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
- log14.debug(`onDiag threw \u2014 TTS diagnostics disabled: ${e instanceof Error ? e.message : e}`);
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
- log14.warn(`cartesia ws closed (${ev.code} ${ev.reason || ""})`);
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
- log14.error(`cartesia reconnect failed: ${e.message}`);
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
- log14.warn(`TTS circuit breaker open \u2014 ${this.consecutiveErrors} consecutive errors, switching to text-only`);
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 || "") ? log14.debug : log14.warn)(`cartesia: ${JSON.stringify(m)}`);
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 ? log14.debug : log14.info)(`TTS recovered${downMs ? ` (down ${downMs}ms)` : ""}`);
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 log15 = forComponent2("FishTTS");
9015
+ var log16 = forComponent2("FishTTS");
8761
9016
 
8762
9017
  // node_modules/@bod.ee/voice/src/adapters/grok.ts
8763
- var log16 = forComponent2("GrokTTS");
9018
+ var log17 = forComponent2("GrokTTS");
8764
9019
 
8765
9020
  // node_modules/@bod.ee/voice/src/adapters/grok-stt.ts
8766
- var log17 = forComponent2("GrokSTT");
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 log18 = forComponent("Instinct");
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
- log18.debug(`ack: cut-in turn \u2192 silent (engine context "${ctx.text.slice(0, 40)}" is the previous final)`);
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
- log18.warn(`barge intent for the ack failed \u2014 situational ack: ${e.message}`);
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
- log18.debug(`ack: no engine outcome in ${this.o.ackSettleMs}ms \u2014 not claimed`);
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
- log18.warn(`${kind} decision failed \u2014 falling back to engine defaults: ${e.message}`);
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 log19 = forComponent("Unsaid");
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
- log19.warn(`unsaid judge failed \u2014 only near-zero-overlap clauses count as unsaid: ${e.message}`);
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);
@@ -9198,7 +9453,7 @@ async function judgeLeak(decider, sentence, userSaid) {
9198
9453
  }
9199
9454
 
9200
9455
  // src/duplex.ts
9201
- var log20 = forComponent("DuplexAgent");
9456
+ var log21 = forComponent("DuplexAgent");
9202
9457
  function describeCall(call) {
9203
9458
  const v = call.args && Object.values(call.args).find((x) => typeof x === "string" && x.trim());
9204
9459
  const hint = v ? ` (${String(v).replace(/\s+/g, " ").trim().slice(0, 48)})` : "";
@@ -9458,7 +9713,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9458
9713
  const m = this.reflexBuf.match(RESERVED_EVENT_MARKER) ?? this.reflexBuf.match(RESERVED_EVENT_OPENER);
9459
9714
  if (m) {
9460
9715
  this.fabricationCut = true;
9461
- log20.warn(`reflex fabricated a [task \u2026] event in its spoken stream \u2014 cutting it (kept ${m.index} chars)`);
9716
+ log21.warn(`reflex fabricated a [task \u2026] event in its spoken stream \u2014 cutting it (kept ${m.index} chars)`);
9462
9717
  this.reflexBuf = this.reflexBuf.slice(0, Math.max(m.index, this.reflexForwarded));
9463
9718
  this.pumpReflex(true);
9464
9719
  return;
@@ -9550,7 +9805,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9550
9805
  const at = from + ok;
9551
9806
  this.leakHit = true;
9552
9807
  this.leakCuts.push(leak.trim().slice(0, 40));
9553
- log20.warn(`reflex spoke its own analysis ("${leak.trim().slice(0, 40)}\u2026") \u2014 cut before TTS`);
9808
+ log21.warn(`reflex spoke its own analysis ("${leak.trim().slice(0, 40)}\u2026") \u2014 cut before TTS`);
9554
9809
  this.notify("diag", "analysis_leak", { text: leak.trim().slice(0, 80), at: this.turnSpoken.trim() ? "mid_reply" : "before_speech" });
9555
9810
  const next = this.stepMarks.find((k) => k > at);
9556
9811
  if (next === void 0) {
@@ -9590,7 +9845,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9590
9845
  this.notify("diag", "leak_judge", { text: s.slice(0, 80), p: Math.round(p * 100) / 100, ms: Math.round(performance.now() - t0) });
9591
9846
  return p >= LEAK_JUDGE_THRESHOLD;
9592
9847
  }, (e) => {
9593
- log20.warn(`leak judge failed \u2014 speaking the sentence: ${e.message}`);
9848
+ log21.warn(`leak judge failed \u2014 speaking the sentence: ${e.message}`);
9594
9849
  return false;
9595
9850
  }).then((leak) => {
9596
9851
  clearTimeout(timer);
@@ -9624,7 +9879,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9624
9879
  let i = t.length - 1;
9625
9880
  while (i >= this.turnBase && !(t[i].role === "assistant" && typeof t[i].content === "string" && t[i].content.includes(key))) i--;
9626
9881
  if (i < this.turnBase) {
9627
- log20.warn("analysis leak cut, but its text was not found in the transcript to scrub");
9882
+ log21.warn("analysis leak cut, but its text was not found in the transcript to scrub");
9628
9883
  continue;
9629
9884
  }
9630
9885
  const m = t[i];
@@ -9704,7 +9959,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9704
9959
  return {
9705
9960
  preToolUse: (call) => {
9706
9961
  if (this.spec?.state === "pending" && !_DuplexAgent.SPEC_SAFE_TOOLS.has(call.name)) {
9707
- log20.verbose(`speculation aborted: reflex called ${call.name} on unconfirmed input`);
9962
+ log21.verbose(`speculation aborted: reflex called ${call.name} on unconfirmed input`);
9708
9963
  this.abortSpeculation();
9709
9964
  return { block: true, reason: "Speculative turn aborted." };
9710
9965
  }
@@ -9749,7 +10004,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9749
10004
  try {
9750
10005
  await this.voice.send(reminder);
9751
10006
  } catch (e) {
9752
- log20.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
10007
+ log21.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
9753
10008
  } finally {
9754
10009
  this.nudging = false;
9755
10010
  const i = this.voice.transcript.findIndex((m, k) => k >= base && m.role === "user" && m.content === reminder);
@@ -9801,7 +10056,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9801
10056
  const accepted = this.offerOpen && this.parkedRedeliver.length > 0 && this.parkedRedeliver.every((p) => p.task === this.offer.task) && (isOfferAcceptance(content) || isTrivialBarge(content));
9802
10057
  const judge = accepted ? void 0 : this.options.bargeIntent;
9803
10058
  return (accepted ? Promise.resolve("resume") : judge ? judge(content).catch((e) => {
9804
- log20.warn(`bargeIntent judge failed \u2014 using the allowlist: ${e.message}`);
10059
+ log21.warn(`bargeIntent judge failed \u2014 using the allowlist: ${e.message}`);
9805
10060
  return fallback();
9806
10061
  }) : Promise.resolve(fallback())).then((intent) => {
9807
10062
  this.notify("diag", "barge_intent", { intent, text: content.slice(0, 80) });
@@ -9997,7 +10252,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9997
10252
  try {
9998
10253
  res = await this.voice.send(spec.text);
9999
10254
  } catch (e) {
10000
- log20.warn(`speculative turn failed: ${e instanceof Error ? e.message : e}`);
10255
+ log21.warn(`speculative turn failed: ${e instanceof Error ? e.message : e}`);
10001
10256
  } finally {
10002
10257
  this.voice.options.signal = prevSignal;
10003
10258
  }
@@ -10011,7 +10266,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
10011
10266
  if (d === "abort") {
10012
10267
  if (this.voice.transcript.length > base) this.voice.transcript.length = base;
10013
10268
  this.speculativeAbortedCalls++;
10014
- log20.verbose(`speculation aborted (${this.speculativeAbortedCalls} total): "${spec.text.slice(0, 50)}"`);
10269
+ log21.verbose(`speculation aborted (${this.speculativeAbortedCalls} total): "${spec.text.slice(0, 50)}"`);
10015
10270
  if (this.spec === spec) this.spec = void 0;
10016
10271
  return res ?? empty;
10017
10272
  }
@@ -10361,7 +10616,7 @@ Another agent just implemented the above. Independently check the CURRENT state
10361
10616
  this.notify("task_verify", `task ${id}: verifying`, { id });
10362
10617
  const cres = await new Agent(checkerOpts).run(checkBrief);
10363
10618
  if (cres.finishReason !== "stop") {
10364
- log20.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
10619
+ log21.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
10365
10620
  this.notify("task_verify", `task ${id}: verify inconclusive (${cres.finishReason})`, { id, finishReason: cres.finishReason });
10366
10621
  }
10367
10622
  const sum = (a = 0, b = 0) => a + b;
@@ -10497,7 +10752,7 @@ Another agent just implemented the above. Independently check the CURRENT state
10497
10752
  rec.status = "done";
10498
10753
  rec.result = res.text;
10499
10754
  const incomplete = res.finishReason !== "stop" && res.finishReason !== "empty";
10500
- log20.verbose(`task ${id} done (${res.steps} steps${incomplete ? `, INCOMPLETE: ${res.finishReason}` : ""})`);
10755
+ log21.verbose(`task ${id} done (${res.steps} steps${incomplete ? `, INCOMPLETE: ${res.finishReason}` : ""})`);
10501
10756
  this.notify("task_done", `task ${id} (${rec.label}) completed`, {
10502
10757
  id,
10503
10758
  text: res.text,
@@ -10531,14 +10786,14 @@ Another agent just implemented the above. Independently check the CURRENT state
10531
10786
  foldIfSuperseded(rec, note) {
10532
10787
  if (rec.deliveryParked !== "moved_on") return false;
10533
10788
  if (note.trim()) this.voice.transcript.push({ role: "assistant", content: note });
10534
- log20.verbose(`task ${rec.id} settled but superseded (${rec.status}) \u2014 folded into transcript, not voiced`);
10789
+ log21.verbose(`task ${rec.id} settled but superseded (${rec.status}) \u2014 folded into transcript, not voiced`);
10535
10790
  return true;
10536
10791
  }
10537
10792
  failTask(rec, msg) {
10538
10793
  this.dropAsk(rec.id);
10539
10794
  rec.status = "error";
10540
10795
  rec.result = msg;
10541
- log20.warn(`task ${rec.id} failed: ${msg}`);
10796
+ log21.warn(`task ${rec.id} failed: ${msg}`);
10542
10797
  this.notify("task_error", `task ${rec.id} (${rec.label}) failed: ${msg}`, { id: rec.id });
10543
10798
  if (this.foldIfSuperseded(rec, `The "${rec.label}" task failed: ${msg}`)) return;
10544
10799
  this.dropParked(rec);
@@ -10777,7 +11032,7 @@ Another agent just implemented the above. Independently check the CURRENT state
10777
11032
  // src/modelRouter.ts
10778
11033
  init_llm();
10779
11034
  init_logging();
10780
- var log21 = forComponent("ModelRouter");
11035
+ var log22 = forComponent("ModelRouter");
10781
11036
  var ModelRouterOptions = class {
10782
11037
  classifier = jevClassifier();
10783
11038
  /** Capability tiers → model id (any provider). basic/standard run the main session; premium is only ever an offload. */
@@ -10861,10 +11116,10 @@ var ModelRouter = class {
10861
11116
  if (tier === "premium") d.offload = this.state.offload && (c.sameThread ?? 0) >= o.sameThreadAt ? "continue" : "fresh";
10862
11117
  } catch (e) {
10863
11118
  const msg = e instanceof Error ? e.message : String(e);
10864
- log21.warn(`classifier failed, falling back to standard: ${msg}`);
11119
+ log22.warn(`classifier failed, falling back to standard: ${msg}`);
10865
11120
  d = { tier: "standard", model: o.models.standard, probs: {}, fallback: msg, classifyMs: Math.round(performance.now() - t0) };
10866
11121
  }
10867
- log21.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`);
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`);
10868
11123
  return d;
10869
11124
  }
10870
11125
  /** Run one user turn on `agent` with routing. Returns a RunResult carrying `costUsd` + `routing`. */
@@ -10916,7 +11171,7 @@ ${text}`;
10916
11171
  const answer = res.text || `(premium sub-agent finished with no reply; finishReason=${res.finishReason})`;
10917
11172
  agent.transcript.push({ role: "user", content }, { role: "assistant", content: answer });
10918
11173
  this.state.offload = { messages: child.transcript, mainLen: agent.transcript.length, question: text };
10919
- log21.info(`offload ${cont ? "continue" : "fresh"} done \xB7 ${res.finishReason} \xB7 ${res.steps} steps`);
11174
+ log22.info(`offload ${cont ? "continue" : "fresh"} done \xB7 ${res.finishReason} \xB7 ${res.steps} steps`);
10920
11175
  const costUsd = o.costOf(main2, briefUsage) + o.costOf(o.models.premium, res.usage);
10921
11176
  return { ...res, text: answer, messages: agent.transcript, usage: addUsage(briefUsage, res.usage), costUsd, routing: d };
10922
11177
  }
@@ -10932,7 +11187,7 @@ The request:
10932
11187
  ${text}`);
10933
11188
  return { text: r.text.trim() || text, usage: r.usage };
10934
11189
  } catch (e) {
10935
- log21.warn(`brief failed, sending the raw request: ${e instanceof Error ? e.message : String(e)}`);
11190
+ log22.warn(`brief failed, sending the raw request: ${e instanceof Error ? e.message : String(e)}`);
10936
11191
  return { text };
10937
11192
  } finally {
10938
11193
  agent.transcript = snap;
@@ -10964,8 +11219,8 @@ async function loadJev(opts) {
10964
11219
  const env = { ...process.env };
10965
11220
  const file = opts.envFile ?? process.env.AGENTX_JEV_ENV;
10966
11221
  if (file) {
10967
- const { readFileSync: readFileSync10 } = await import("fs");
10968
- for (const l of readFileSync10(file, "utf8").split("\n")) {
11222
+ const { readFileSync: readFileSync11 } = await import("fs");
11223
+ for (const l of readFileSync11(file, "utf8").split("\n")) {
10969
11224
  const m = l.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*?)\s*$/);
10970
11225
  if (m && !env[m[1]]) env[m[1]] = m[2].replace(/^['"]|['"]$/g, "");
10971
11226
  }
@@ -10977,7 +11232,7 @@ async function jevAvailable(opts = {}) {
10977
11232
  const { env } = await loadJev(opts);
10978
11233
  return JEV_KEYS.some((k) => env[k]?.trim());
10979
11234
  } catch (e) {
10980
- log21.debug(`jev unavailable: ${e instanceof Error ? e.message : String(e)}`);
11235
+ log22.debug(`jev unavailable: ${e instanceof Error ? e.message : String(e)}`);
10981
11236
  return false;
10982
11237
  }
10983
11238
  }
@@ -11204,8 +11459,8 @@ import { MemFilesystem as MemFilesystem3, IndexedDbFilesystem, CommandExecutor a
11204
11459
  // src/mcp.client.ts
11205
11460
  init_logging();
11206
11461
  import { spawn } from "child_process";
11207
- import { createHash } from "crypto";
11208
- var log22 = forComponent("mcp");
11462
+ import { createHash as createHash2 } from "crypto";
11463
+ var log23 = forComponent("mcp");
11209
11464
  var PROTOCOL_VERSION = "2025-06-18";
11210
11465
  var DEFAULT_TIMEOUT_MS = 3e4;
11211
11466
  var DEFAULT_CALL_TIMEOUT_MS = 30 * 6e4;
@@ -11235,7 +11490,7 @@ var StdioTransport = class {
11235
11490
  proc.stdout.setEncoding("utf8");
11236
11491
  proc.stdout.on("data", (chunk) => this.onData(chunk));
11237
11492
  proc.stderr.setEncoding("utf8");
11238
- proc.stderr.on("data", (chunk) => log22.debug(`[${command}] stderr:`, chunk.trimEnd()));
11493
+ proc.stderr.on("data", (chunk) => log23.debug(`[${command}] stderr:`, chunk.trimEnd()));
11239
11494
  proc.on("exit", (code) => this.failAll(new Error(`MCP server "${command}" exited (code ${code})`)));
11240
11495
  proc.on("error", (e) => this.failAll(e instanceof Error ? e : new Error(String(e))));
11241
11496
  }
@@ -11249,7 +11504,7 @@ var StdioTransport = class {
11249
11504
  try {
11250
11505
  this.dispatch(JSON.parse(line));
11251
11506
  } catch (e) {
11252
- log22.debug("dropping non-JSON line from MCP server:", line, e);
11507
+ log23.debug("dropping non-JSON line from MCP server:", line, e);
11253
11508
  }
11254
11509
  }
11255
11510
  }
@@ -11259,7 +11514,7 @@ var StdioTransport = class {
11259
11514
  try {
11260
11515
  p2?.onProgress?.(msg.params ?? {});
11261
11516
  } catch (e) {
11262
- log22.debug("onProgress handler threw", e);
11517
+ log23.debug("onProgress handler threw", e);
11263
11518
  }
11264
11519
  return;
11265
11520
  }
@@ -11297,7 +11552,7 @@ var StdioTransport = class {
11297
11552
  const onAbort = () => {
11298
11553
  if (!this.pending.delete(id)) return;
11299
11554
  clearTimeout(timer);
11300
- this.notify("notifications/cancelled", { requestId: id, reason: String(signal?.reason?.message ?? "cancelled") }).catch((e) => log22.debug("cancel notify failed", e));
11555
+ this.notify("notifications/cancelled", { requestId: id, reason: String(signal?.reason?.message ?? "cancelled") }).catch((e) => log23.debug("cancel notify failed", e));
11301
11556
  reject(abortError(signal));
11302
11557
  };
11303
11558
  const done = () => signal?.removeEventListener("abort", onAbort);
@@ -11321,7 +11576,7 @@ var StdioTransport = class {
11321
11576
  try {
11322
11577
  this.proc?.stdin?.end();
11323
11578
  } catch (e) {
11324
- log22.debug("stdin end failed", e);
11579
+ log23.debug("stdin end failed", e);
11325
11580
  }
11326
11581
  this.proc?.kill();
11327
11582
  }
@@ -11356,7 +11611,7 @@ var HttpTransport = class {
11356
11611
  return await this.post({ jsonrpc: "2.0", id, method, params: opts.onProgress ? withProgressToken(params, id) : params }, false, signal, opts.onProgress);
11357
11612
  } catch (e) {
11358
11613
  if (opts.signal?.aborted) {
11359
- void this.notify("notifications/cancelled", { requestId: id, reason: String(opts.signal.reason?.message ?? "cancelled") }).catch((err2) => log22.debug("cancel notify failed", err2));
11614
+ void this.notify("notifications/cancelled", { requestId: id, reason: String(opts.signal.reason?.message ?? "cancelled") }).catch((err2) => log23.debug("cancel notify failed", err2));
11360
11615
  throw abortError(opts.signal);
11361
11616
  }
11362
11617
  throw e;
@@ -11399,7 +11654,7 @@ function sseMessage(line) {
11399
11654
  try {
11400
11655
  return JSON.parse(trimmed.slice(5).trim());
11401
11656
  } catch (e) {
11402
- log22.debug("skipping unparseable SSE data line", e);
11657
+ log23.debug("skipping unparseable SSE data line", e);
11403
11658
  return void 0;
11404
11659
  }
11405
11660
  }
@@ -11409,7 +11664,7 @@ async function readSseResponse(resp, id, onProgress) {
11409
11664
  try {
11410
11665
  onProgress?.(obj.params ?? {});
11411
11666
  } catch (e) {
11412
- log22.debug("onProgress handler threw", e);
11667
+ log23.debug("onProgress handler threw", e);
11413
11668
  }
11414
11669
  return void 0;
11415
11670
  }
@@ -11508,7 +11763,7 @@ async function mountWithDeadline(name, cfg, mountTimeoutMs) {
11508
11763
  return { name, client, tools, specs, serverInfo: init?.serverInfo, config: cfg };
11509
11764
  })(), mountTimeoutMs, name);
11510
11765
  } catch (e) {
11511
- await client.close().catch((err2) => log22.debug(`close after failed mount of "${name}": ${err2}`));
11766
+ await client.close().catch((err2) => log23.debug(`close after failed mount of "${name}": ${err2}`));
11512
11767
  throw e;
11513
11768
  }
11514
11769
  }
@@ -11519,15 +11774,15 @@ function validEntries(servers) {
11519
11774
  return Object.entries(servers).filter(([name, cfg]) => {
11520
11775
  if (!cfg || cfg.disabled) return false;
11521
11776
  if (!cfg.command && !cfg.url) {
11522
- log22.warn(`MCP server "${name}" needs a command (stdio) or url (http) \u2014 skipping`);
11777
+ log23.warn(`MCP server "${name}" needs a command (stdio) or url (http) \u2014 skipping`);
11523
11778
  return false;
11524
11779
  }
11525
11780
  return true;
11526
11781
  });
11527
11782
  }
11528
11783
  function logMountFailure(name, e) {
11529
- if (e instanceof McpAuthError) log22.warn(`MCP "${name}" needs-auth: HTTP ${e.status} \u2014 set bearerToken or headers in its config; skipping`);
11530
- else log22.error(`MCP server "${name}" failed to mount: ${e?.message ?? e}`);
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}`);
11531
11786
  }
11532
11787
  async function mountMcpServers(servers = {}, opts = {}) {
11533
11788
  const entries = validEntries(servers);
@@ -11537,7 +11792,7 @@ async function mountMcpServers(servers = {}, opts = {}) {
11537
11792
  const name = entries[i][0];
11538
11793
  if (r.status === "fulfilled") {
11539
11794
  out.push(r.value);
11540
- log22.debug(`MCP "${name}" mounted \u2014 ${r.value.tools.length} tool(s)${r.value.serverInfo?.name ? ` from ${r.value.serverInfo.name}` : ""}`);
11795
+ log23.debug(`MCP "${name}" mounted \u2014 ${r.value.tools.length} tool(s)${r.value.serverInfo?.name ? ` from ${r.value.serverInfo.name}` : ""}`);
11541
11796
  } else logMountFailure(name, r.reason);
11542
11797
  });
11543
11798
  return out;
@@ -11577,7 +11832,7 @@ var McpPool = class {
11577
11832
  const prev = this.warm.get(key);
11578
11833
  if (prev) {
11579
11834
  clearTimeout(prev.timer);
11580
- if (prev.client !== client) void prev.client.close().catch((err2) => log22.debug(`warm-pool replace close failed: ${err2}`));
11835
+ if (prev.client !== client) void prev.client.close().catch((err2) => log23.debug(`warm-pool replace close failed: ${err2}`));
11581
11836
  }
11582
11837
  const e = { client, timer: void 0 };
11583
11838
  this.warm.set(key, e);
@@ -11594,7 +11849,7 @@ var McpPool = class {
11594
11849
  const e = this.warm.get(key);
11595
11850
  if (!e) return;
11596
11851
  this.warm.delete(key);
11597
- await e.client.close().catch((err2) => log22.debug(`warm-pool evict close failed: ${err2}`));
11852
+ await e.client.close().catch((err2) => log23.debug(`warm-pool evict close failed: ${err2}`));
11598
11853
  }
11599
11854
  async closeAll() {
11600
11855
  for (const e of this.warm.values()) {
@@ -11610,8 +11865,8 @@ var defaultPool = new McpPool();
11610
11865
 
11611
11866
  // cli/mcpOAuth.ts
11612
11867
  import { createServer } from "http";
11613
- import { randomBytes as randomBytes2, createHash as createHash2 } from "crypto";
11614
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync } from "fs";
11868
+ import { randomBytes as randomBytes2, createHash as createHash3 } from "crypto";
11869
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync } from "fs";
11615
11870
  import { dirname as dirname2 } from "path";
11616
11871
  var McpOAuthOptions = class {
11617
11872
  /** Where to persist tokens. Mode 0600. */
@@ -11639,14 +11894,14 @@ var McpOAuth = class {
11639
11894
  // -- store I/O ----------------------------------------------------------------
11640
11895
  load() {
11641
11896
  try {
11642
- return existsSync(this.options.storePath) ? JSON.parse(readFileSync3(this.options.storePath, "utf8")) : {};
11897
+ return existsSync(this.options.storePath) ? JSON.parse(readFileSync4(this.options.storePath, "utf8")) : {};
11643
11898
  } catch {
11644
11899
  return {};
11645
11900
  }
11646
11901
  }
11647
11902
  save(store) {
11648
11903
  mkdirSync3(dirname2(this.options.storePath), { recursive: true });
11649
- writeFileSync3(this.options.storePath, JSON.stringify(store, null, 2), { mode: 384 });
11904
+ writeFileSync4(this.options.storePath, JSON.stringify(store, null, 2), { mode: 384 });
11650
11905
  }
11651
11906
  // -- ATTENDED: one-time authorization ----------------------------------------
11652
11907
  /** Run the authorization-code + PKCE flow and persist the resulting tokens for `serverUrl`. */
@@ -11654,7 +11909,7 @@ var McpOAuth = class {
11654
11909
  const meta = await this.discover(serverUrl);
11655
11910
  const clientId = this.options.clientId ?? await this.registerClient(meta, serverUrl);
11656
11911
  const verifier = b64url(randomBytes2(32));
11657
- const challenge = b64url(createHash2("sha256").update(verifier).digest());
11912
+ const challenge = b64url(createHash3("sha256").update(verifier).digest());
11658
11913
  const state = b64url(randomBytes2(16));
11659
11914
  const { code, redirectUri } = await this.captureCode(meta.authorization_endpoint, clientId, challenge, state);
11660
11915
  const tok = await this.exchange(meta.token_endpoint, {
@@ -11804,15 +12059,15 @@ function defaultOpenBrowser(url) {
11804
12059
  // cli/core.ts
11805
12060
  import { randomUUID } from "crypto";
11806
12061
  import { execFile as execFile2 } from "child_process";
11807
- import { resolve as resolve2, basename, join as join5 } from "path";
12062
+ import { resolve as resolve2, basename, join as join6 } from "path";
11808
12063
  import { existsSync as existsSync3, mkdirSync as mkdirSync4 } from "fs";
11809
- 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";
11810
12065
  init_tools_shell();
11811
12066
 
11812
12067
  // src/tools.notify.ts
11813
12068
  init_logging();
11814
12069
  import { execFile } from "child_process";
11815
- var log24 = forComponent("notify");
12070
+ var log25 = forComponent("notify");
11816
12071
  function makeNotifyTool(opts = {}) {
11817
12072
  const platform2 = opts.platform ?? process.platform;
11818
12073
  const run = opts.exec ?? execFile;
@@ -11836,7 +12091,7 @@ function makeNotifyTool(opts = {}) {
11836
12091
  return new Promise((resolve5) => {
11837
12092
  run(argv[0], argv[1], { timeout: 5e3 }, (e) => {
11838
12093
  if (e) {
11839
- log24.debug("notification failed", e);
12094
+ log25.debug("notification failed", e);
11840
12095
  resolve5(`Notification failed: ${e.message}`);
11841
12096
  } else resolve5("Notification shown.");
11842
12097
  });
@@ -11850,9 +12105,9 @@ import { BodDB as BodDB2 } from "@bod.ee/db";
11850
12105
 
11851
12106
  // cli/util.ts
11852
12107
  init_logging();
11853
- import { existsSync as existsSync2, readFileSync as readFileSync4 } from "fs";
12108
+ import { existsSync as existsSync2, readFileSync as readFileSync5 } from "fs";
11854
12109
  import { homedir } from "os";
11855
- var log25 = forComponent("cli-util");
12110
+ var log26 = forComponent("cli-util");
11856
12111
  function dotDirs(base, sub, opts = {}) {
11857
12112
  const home = opts.home ?? homedir();
11858
12113
  const dirs = [`${base}/.agent/${sub}`, `${base}/.claude/${sub}`, `${home}/.agent/${sub}`, `${home}/.claude/${sub}`];
@@ -11869,7 +12124,7 @@ function parseJson(text, fallback, what = "json") {
11869
12124
  try {
11870
12125
  return JSON.parse(text);
11871
12126
  } catch (e) {
11872
- log25.debug(`parseJson(${what}) failed: ${e.message}`);
12127
+ log26.debug(`parseJson(${what}) failed: ${e.message}`);
11873
12128
  return fallback;
11874
12129
  }
11875
12130
  }
@@ -11877,9 +12132,9 @@ function readJsonFile(path, fallback) {
11877
12132
  if (!existsSync2(path)) return fallback;
11878
12133
  let text;
11879
12134
  try {
11880
- text = readFileSync4(path, "utf8");
12135
+ text = readFileSync5(path, "utf8");
11881
12136
  } catch (e) {
11882
- log25.debug(`readJsonFile(${path}) unreadable: ${e.message}`);
12137
+ log26.debug(`readJsonFile(${path}) unreadable: ${e.message}`);
11883
12138
  return fallback;
11884
12139
  }
11885
12140
  return parseJson(text, fallback, path);
@@ -11994,7 +12249,7 @@ async function buildAgent(o) {
11994
12249
  } else if (o.boddb) {
11995
12250
  const root = resolve2(o.boddb);
11996
12251
  mkdirSync4(root, { recursive: true });
11997
- const db = new BodDB2({ path: join5(root, "meta.db"), sweepInterval: 0, vfs: { storageRoot: join5(root, "files") } });
12252
+ const db = new BodDB2({ path: join6(root, "meta.db"), sweepInterval: 0, vfs: { storageRoot: join6(root, "files") } });
11998
12253
  const bod = new BodDbFilesystem(db);
11999
12254
  const firstRun = (await bod.readDir("/").catch(() => [])).length === 0;
12000
12255
  await mkdirp(bod, cwd);
@@ -12061,7 +12316,7 @@ Reference files in them by their mount path (the left side).`;
12061
12316
  });
12062
12317
  realShell = [makeRealShellTool({ cwd, registry: jobs, osSandbox: o.osSandbox, jobsSurviveRun: o.singleRun !== true }), ...makeShellJobTools(jobs)];
12063
12318
  }
12064
- const scratchDir = o.scratch ? o.scratchDir ?? (virtual ? `${cwd}/.agent/scratch` : `${tmpdir()}/agentx-scratch-${process.pid}`) : void 0;
12319
+ const scratchDir = o.scratch ? o.scratchDir ?? (virtual ? `${cwd}/.agent/scratch` : `${tmpdir2()}/agentx-scratch-${process.pid}`) : void 0;
12065
12320
  const scratch = scratchDir ? new Scratch(fs, { dir: scratchDir }) : void 0;
12066
12321
  return agentRef.agent = new Agent({
12067
12322
  ai: o.ai,
@@ -12154,6 +12409,7 @@ The filesystem root '/' is the real machine root \u2014 you have full filesystem
12154
12409
  keepToolOutputs: o.keepToolOutputs ?? 8,
12155
12410
  ...o.maxContextTokens != null ? { maxContextTokens: o.maxContextTokens } : {},
12156
12411
  ...o.contextWindow ? { contextWindow: o.contextWindow } : {},
12412
+ ...o.imageInput ? { imageInput: o.imageInput } : {},
12157
12413
  ...o.browserTrim ? { browserTrim: o.browserTrim } : {}
12158
12414
  });
12159
12415
  }
@@ -12173,12 +12429,12 @@ var trunc = (s, n) => (s == null ? "" : String(s).length > n ? String(s).slice(0
12173
12429
 
12174
12430
  // cli/voice.ts
12175
12431
  init_logging();
12176
- import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
12177
- import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
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";
12178
12434
  import { homedir as homedir3 } from "os";
12179
- import { dirname as dirname3, join as join6 } from "path";
12435
+ import { dirname as dirname3, join as join7 } from "path";
12180
12436
  import { fileURLToPath } from "url";
12181
- var log26 = forComponent("VoiceIO");
12437
+ var log27 = forComponent("VoiceIO");
12182
12438
  var now3 = () => performance.now();
12183
12439
  var Player = class {
12184
12440
  proc = null;
@@ -12192,7 +12448,7 @@ var Player = class {
12192
12448
  ["-loglevel", "quiet", "-nodisp", "-fflags", "nobuffer", "-flags", "low_delay", "-probesize", "32", "-f", "s16le", "-ar", String(TTS_SAMPLE_RATE), "-ch_layout", "mono", "-i", "-"],
12193
12449
  { stdio: ["pipe", "ignore", "ignore"] }
12194
12450
  );
12195
- this.proc.on("error", (e) => log26.warn(`ffplay error: ${e.message}`));
12451
+ this.proc.on("error", (e) => log27.warn(`ffplay error: ${e.message}`));
12196
12452
  this.proc.stdin.on("error", () => {
12197
12453
  });
12198
12454
  this.bytesWritten = 0;
@@ -12219,10 +12475,10 @@ var Player = class {
12219
12475
  this.proc = null;
12220
12476
  }
12221
12477
  };
12222
- var nativeDir = () => join6(dirname3(fileURLToPath(import.meta.url)), "native");
12478
+ var nativeDir = () => join7(dirname3(fileURLToPath(import.meta.url)), "native");
12223
12479
  function detectFfmpegMic() {
12224
12480
  if (process.env.MIC_DEVICE) return process.env.MIC_DEVICE;
12225
- const r = spawnSync2("ffmpeg", ["-f", "avfoundation", "-list_devices", "true", "-i", ""], { encoding: "utf8" });
12481
+ const r = spawnSync3("ffmpeg", ["-f", "avfoundation", "-list_devices", "true", "-i", ""], { encoding: "utf8" });
12226
12482
  if (r.error?.code === "ENOENT") throw new Error("ffmpeg not installed \u2014 run `brew install ffmpeg` to enable voice");
12227
12483
  if (r.error) throw new Error(`ffmpeg failed to run (${r.error.message}) \u2014 likely a wrong-arch binary; run \`brew reinstall ffmpeg\``);
12228
12484
  const out = r.stderr ?? "";
@@ -12230,16 +12486,16 @@ function detectFfmpegMic() {
12230
12486
  const devices = [...audio.matchAll(/\[(\d+)\] (.+)/g)].map(([, idx, name]) => ({ idx, name: name.trim() }));
12231
12487
  const mic = devices.find((d) => /microphone|built-in/i.test(d.name) && !/teams|blackhole|loopback/i.test(d.name)) ?? devices[0];
12232
12488
  if (!mic) throw new Error("no audio input device found");
12233
- log26.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
12489
+ log27.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
12234
12490
  return `:${mic.idx}`;
12235
12491
  }
12236
12492
  function detectedInputDevice() {
12237
12493
  if (process.platform !== "darwin") return null;
12238
12494
  let name = "";
12239
- if (spawnSync2("which", ["SwitchAudioSource"]).status === 0) {
12240
- name = (spawnSync2("SwitchAudioSource", ["-c", "-t", "input"], { encoding: "utf8" }).stdout || "").trim();
12495
+ if (spawnSync3("which", ["SwitchAudioSource"]).status === 0) {
12496
+ name = (spawnSync3("SwitchAudioSource", ["-c", "-t", "input"], { encoding: "utf8" }).stdout || "").trim();
12241
12497
  } else {
12242
- const out = spawnSync2("system_profiler", ["SPAudioDataType"], { encoding: "utf8" }).stdout || "";
12498
+ const out = spawnSync3("system_profiler", ["SPAudioDataType"], { encoding: "utf8" }).stdout || "";
12243
12499
  let last = "";
12244
12500
  for (const ln of out.split("\n")) {
12245
12501
  const hdr = ln.match(/^\s{6,}(\S.*?):\s*$/);
@@ -12258,23 +12514,23 @@ function detectedInputDevice() {
12258
12514
  }
12259
12515
  function resolveAecBinary() {
12260
12516
  if (process.env.MIC_AEC === "0" || process.platform !== "darwin") return null;
12261
- const src = join6(nativeDir(), "mic-aec.swift");
12262
- const plist = join6(nativeDir(), "Info.plist");
12517
+ const src = join7(nativeDir(), "mic-aec.swift");
12518
+ const plist = join7(nativeDir(), "Info.plist");
12263
12519
  if (!existsSync4(src)) return null;
12264
- const cacheDir = join6(homedir3(), ".agent", "cache");
12265
- const bin = join6(cacheDir, "mic-aec");
12266
- if (existsSync4(bin) && statSync4(bin).mtimeMs >= statSync4(src).mtimeMs) return bin;
12267
- if (spawnSync2("which", ["swiftc"]).status !== 0) return null;
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;
12268
12524
  mkdirSync5(cacheDir, { recursive: true });
12269
- log26.info("compiling AEC mic helper (first run)\u2026");
12270
- const build = spawnSync2("swiftc", ["-O", "-o", bin, src, "-Xlinker", "-sectcreate", "-Xlinker", "__TEXT", "-Xlinker", "__info_plist", "-Xlinker", plist], { encoding: "utf8" });
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" });
12271
12527
  if (build.status !== 0) {
12272
- log26.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
12528
+ log27.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
12273
12529
  return null;
12274
12530
  }
12275
- const sign = spawnSync2("codesign", ["-fs", "-", bin], { encoding: "utf8" });
12531
+ const sign = spawnSync3("codesign", ["-fs", "-", bin], { encoding: "utf8" });
12276
12532
  if (sign.status !== 0) {
12277
- log26.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
12533
+ log27.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
12278
12534
  return null;
12279
12535
  }
12280
12536
  return bin;
@@ -12282,14 +12538,14 @@ function resolveAecBinary() {
12282
12538
  function openMicSettings() {
12283
12539
  if (process.platform !== "darwin") return;
12284
12540
  try {
12285
- spawnSync2("open", ["x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone"]);
12541
+ spawnSync3("open", ["x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone"]);
12286
12542
  } catch {
12287
12543
  }
12288
12544
  }
12289
12545
  function micPermissionStatus() {
12290
12546
  const bin = resolveAecBinary();
12291
12547
  if (!bin) return null;
12292
- const r = spawnSync2(bin, ["--check-mic"], { encoding: "utf8" });
12548
+ const r = spawnSync3(bin, ["--check-mic"], { encoding: "utf8" });
12293
12549
  const out = (r.stdout ?? "").trim();
12294
12550
  if (out === "authorized") return "authorized";
12295
12551
  if (out === "denied" || out === "restricted") return "denied";
@@ -12299,7 +12555,7 @@ function micPermissionStatus() {
12299
12555
  function aecUnavailableHint() {
12300
12556
  if (process.env.MIC_AEC === "0" || process.platform !== "darwin") return null;
12301
12557
  if (resolveAecBinary()) return null;
12302
- if (spawnSync2("which", ["swiftc"]).status !== 0) return "install Xcode CLI tools (`xcode-select --install`) for echo cancellation";
12558
+ if (spawnSync3("which", ["swiftc"]).status !== 0) return "install Xcode CLI tools (`xcode-select --install`) for echo cancellation";
12303
12559
  return null;
12304
12560
  }
12305
12561
  var NodeMicSource = class {
@@ -12315,17 +12571,17 @@ var NodeMicSource = class {
12315
12571
  if (this.bin) {
12316
12572
  this.proc = spawn2(this.bin, [], { stdio: ["ignore", "pipe", "ignore"] });
12317
12573
  } else {
12318
- if (spawnSync2("which", ["ffmpeg"]).status !== 0) throw new Error("voice I/O unavailable: no AEC helper and no ffmpeg on PATH");
12319
- log26.info("mic: raw capture (no AEC) \u2014 echo handled heuristically; headphones recommended");
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");
12320
12576
  this.proc = spawn2(
12321
12577
  "ffmpeg",
12322
12578
  ["-loglevel", "error", "-f", "avfoundation", "-i", detectFfmpegMic(), "-ar", String(STT_SAMPLE_RATE), "-ac", "1", "-f", "s16le", "-"],
12323
12579
  { stdio: ["ignore", "pipe", "pipe"] }
12324
12580
  );
12325
- this.proc.stderr.on("data", (d) => log26.warn(`ffmpeg: ${String(d).trim()}`));
12581
+ this.proc.stderr.on("data", (d) => log27.warn(`ffmpeg: ${String(d).trim()}`));
12326
12582
  }
12327
12583
  this.proc.on("exit", (c) => {
12328
- if (c && !this.stopped) log26.error(`mic capture exited (${c}) \u2014 check mic permission / MIC_DEVICE / MIC_AEC=0`);
12584
+ if (c && !this.stopped) log27.error(`mic capture exited (${c}) \u2014 check mic permission / MIC_DEVICE / MIC_AEC=0`);
12329
12585
  });
12330
12586
  this.proc.stdout.on("data", (chunk) => onChunk(chunk));
12331
12587
  }
@@ -12391,7 +12647,7 @@ var AecDuplexAudio = class {
12391
12647
  this.proc.stdin.on("error", () => {
12392
12648
  });
12393
12649
  this.proc.on("exit", (c) => {
12394
- if (c && !this.stopped) log26.error(`aec duplex audio exited (${c}) \u2014 check mic permission / MIC_AEC=0`);
12650
+ if (c && !this.stopped) log27.error(`aec duplex audio exited (${c}) \u2014 check mic permission / MIC_AEC=0`);
12395
12651
  });
12396
12652
  this.proc.stdout.on("data", (chunk) => {
12397
12653
  this.gotChunk = true;
@@ -12407,7 +12663,7 @@ var AecDuplexAudio = class {
12407
12663
  openMicSettings();
12408
12664
  this.onFatal?.("microphone permission denied \u2014 enable it in System Settings \u2192 Privacy & Security \u2192 Microphone for your terminal, then restart it");
12409
12665
  }
12410
- } else log26.debug(`mic-aec: ${s}`);
12666
+ } else log27.debug(`mic-aec: ${s}`);
12411
12667
  }
12412
12668
  });
12413
12669
  if (!this.noVpio && !this.triedFallback) {
@@ -12416,7 +12672,7 @@ var AecDuplexAudio = class {
12416
12672
  this.triedFallback = true;
12417
12673
  this.noVpio = true;
12418
12674
  this._aec = false;
12419
- log26.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)");
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)");
12420
12676
  this.onDegrade?.();
12421
12677
  this.killProc();
12422
12678
  this.spawnHelper();
@@ -12587,7 +12843,7 @@ function bindAckInstinct(v, instinct) {
12587
12843
  if (ev.kind === "ack_fired") instinct.ackOutcome(String(ev.phrase));
12588
12844
  else if (ev.kind === "ack_skipped" || ev.kind === "ack_cancelled") instinct.ackOutcome(null);
12589
12845
  } catch (e) {
12590
- log26.warn(`ack outcome tap failed: ${e instanceof Error ? e.message : e}`);
12846
+ log27.warn(`ack outcome tap failed: ${e instanceof Error ? e.message : e}`);
12591
12847
  }
12592
12848
  onDiag(ev);
12593
12849
  };
@@ -12665,7 +12921,7 @@ function fakeVoiceParts(uttFile) {
12665
12921
  timer = setInterval(() => {
12666
12922
  let buf;
12667
12923
  try {
12668
- buf = readFileSync5(uttFile, "utf8");
12924
+ buf = readFileSync6(uttFile, "utf8");
12669
12925
  } catch {
12670
12926
  return;
12671
12927
  }
@@ -12708,7 +12964,7 @@ function fakeVoiceParts(uttFile) {
12708
12964
  init_logging();
12709
12965
  import { appendFile, mkdirSync as mkdirSync6 } from "fs";
12710
12966
  import { dirname as dirname4 } from "path";
12711
- var log27 = forComponent("VoiceDiag");
12967
+ var log28 = forComponent("VoiceDiag");
12712
12968
  var JsonlDiagSink = class {
12713
12969
  constructor(path) {
12714
12970
  this.path = path;
@@ -12749,7 +13005,7 @@ var JsonlDiagSink = class {
12749
13005
  disable(e) {
12750
13006
  this.disabled = true;
12751
13007
  this.queue.length = 0;
12752
- log27.debug(`voice diag sink disabled (${this.path}): ${e instanceof Error ? e.message : e}`);
13008
+ log28.debug(`voice diag sink disabled (${this.path}): ${e instanceof Error ? e.message : e}`);
12753
13009
  }
12754
13010
  /** True once a failure permanently disabled the sink (tests/inspection). */
12755
13011
  get isDisabled() {
@@ -12763,13 +13019,13 @@ var JsonlDiagSink = class {
12763
13019
 
12764
13020
  // cli/config.ts
12765
13021
  import { homedir as homedir4 } from "os";
12766
- import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
12767
- import { join as join7 } from "path";
13022
+ import { existsSync as existsSync5, readFileSync as readFileSync7 } from "fs";
13023
+ import { join as join8 } from "path";
12768
13024
  import { pathToFileURL } from "url";
12769
13025
  var FILES = ["config.ts", "config.js", "config.mjs", "config.json"];
12770
13026
  async function loadFrom(dir) {
12771
13027
  for (const f of FILES) {
12772
- const p = join7(dir, ".agent", f);
13028
+ const p = join8(dir, ".agent", f);
12773
13029
  if (!existsSync5(p)) continue;
12774
13030
  try {
12775
13031
  const mod = await import(pathToFileURL(p).href, f.endsWith(".json") ? { with: { type: "json" } } : void 0);
@@ -12782,10 +13038,10 @@ async function loadFrom(dir) {
12782
13038
  return {};
12783
13039
  }
12784
13040
  function loadSettings(dir) {
12785
- const p = join7(dir, ".agent", "settings.json");
13041
+ const p = join8(dir, ".agent", "settings.json");
12786
13042
  if (!existsSync5(p)) return {};
12787
13043
  try {
12788
- const raw = JSON.parse(readFileSync6(p, "utf8"));
13044
+ const raw = JSON.parse(readFileSync7(p, "utf8"));
12789
13045
  const cfg = {};
12790
13046
  if (raw.mcpServers && typeof raw.mcpServers === "object") cfg.mcpServers = raw.mcpServers;
12791
13047
  if (raw.permissions && typeof raw.permissions === "object") cfg.permissions = raw.permissions;
@@ -12818,8 +13074,8 @@ async function loadConfig(cwd) {
12818
13074
  }
12819
13075
 
12820
13076
  // cli/hooks-config.ts
12821
- import { spawnSync as spawnSync3 } from "child_process";
12822
- var log28 = forComponent("hooks");
13077
+ import { spawnSync as spawnSync4 } from "child_process";
13078
+ var log29 = forComponent("hooks");
12823
13079
  var escapeRegex = (s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
12824
13080
  function ruleMatches(rule, toolName) {
12825
13081
  if (!rule.tool || rule.tool === "*") return true;
@@ -12828,7 +13084,7 @@ function ruleMatches(rule, toolName) {
12828
13084
  }
12829
13085
  function runCmd(rule, env) {
12830
13086
  try {
12831
- const r = spawnSync3(rule.command, {
13087
+ const r = spawnSync4(rule.command, {
12832
13088
  shell: true,
12833
13089
  encoding: "utf8",
12834
13090
  timeout: rule.timeoutMs ?? 1e4,
@@ -12836,7 +13092,7 @@ function runCmd(rule, env) {
12836
13092
  });
12837
13093
  return { code: r.status ?? 1, out: ((r.stdout ?? "") + (r.stderr ?? "")).trim() };
12838
13094
  } catch (e) {
12839
- log28.debug(`hook command failed: ${rule.command}`, e);
13095
+ log29.debug(`hook command failed: ${rule.command}`, e);
12840
13096
  return { code: 1, out: String(e?.message ?? e) };
12841
13097
  }
12842
13098
  }
@@ -12940,15 +13196,15 @@ function formatDiff(ops, opts = {}) {
12940
13196
  }
12941
13197
 
12942
13198
  // cli/session.ts
12943
- import { existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync4, readdirSync, renameSync, symlinkSync as symlinkSync2, unlinkSync, readlinkSync, statSync as statSync5 } from "fs";
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";
12944
13200
  import { homedir as homedir5 } from "os";
12945
- import { join as join8 } from "path";
12946
- var log29 = forComponent("session");
12947
- var globalDir = () => join8(homedir5(), ".agent", "sessions");
13201
+ import { join as join9 } from "path";
13202
+ var log30 = forComponent("session");
13203
+ var globalDir = () => join9(homedir5(), ".agent", "sessions");
12948
13204
  var SessionStore = class {
12949
13205
  dir;
12950
13206
  constructor(cwd) {
12951
- this.dir = join8(cwd, ".agent", "sessions");
13207
+ this.dir = join9(cwd, ".agent", "sessions");
12952
13208
  }
12953
13209
  /** Sortable, human-readable id: `YYYYMMDD-HHMMSS-<folder>`. */
12954
13210
  newId(now4 = Date.now(), cwd) {
@@ -12956,10 +13212,10 @@ var SessionStore = class {
12956
13212
  const p = (n, w = 2) => String(n).padStart(w, "0");
12957
13213
  const slug2 = (cwd ?? process.cwd()).split("/").pop()?.replace(/[^A-Za-z0-9_-]/g, "") || "session";
12958
13214
  let id = `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}-${slug2}`;
12959
- if (existsSync6(this.dir) && existsSync6(join8(this.dir, `${id}.json`))) {
13215
+ if (existsSync6(this.dir) && existsSync6(join9(this.dir, `${id}.json`))) {
12960
13216
  for (let i = 2; i <= 99; i++) {
12961
13217
  const c = `${id}-${i}`;
12962
- if (!existsSync6(join8(this.dir, `${c}.json`))) {
13218
+ if (!existsSync6(join9(this.dir, `${c}.json`))) {
12963
13219
  id = c;
12964
13220
  break;
12965
13221
  }
@@ -12974,29 +13230,29 @@ var SessionStore = class {
12974
13230
  save(data) {
12975
13231
  if (!this.safeId(data.meta.id)) throw new Error(`unsafe session id: ${data.meta.id}`);
12976
13232
  if (!existsSync6(this.dir)) mkdirSync7(this.dir, { recursive: true });
12977
- const path = join8(this.dir, `${data.meta.id}.json`);
13233
+ const path = join9(this.dir, `${data.meta.id}.json`);
12978
13234
  const tmp = `${path}.${process.pid}.tmp`;
12979
- writeFileSync4(tmp, JSON.stringify(data));
13235
+ writeFileSync5(tmp, JSON.stringify(data));
12980
13236
  renameSync(tmp, path);
12981
13237
  try {
12982
13238
  const gd = globalDir();
12983
13239
  if (!existsSync6(gd)) mkdirSync7(gd, { recursive: true });
12984
- const link2 = join8(gd, `${data.meta.id}.json`);
13240
+ const link2 = join9(gd, `${data.meta.id}.json`);
12985
13241
  if (!existsSync6(link2)) symlinkSync2(path, link2);
12986
13242
  } catch {
12987
13243
  }
12988
13244
  }
12989
13245
  load(id) {
12990
13246
  if (!this.safeId(id)) {
12991
- log29.debug(`rejecting unsafe session id: ${id}`);
13247
+ log30.debug(`rejecting unsafe session id: ${id}`);
12992
13248
  return void 0;
12993
13249
  }
12994
- const path = join8(this.dir, `${id}.json`);
13250
+ const path = join9(this.dir, `${id}.json`);
12995
13251
  if (!existsSync6(path)) return void 0;
12996
13252
  try {
12997
- return JSON.parse(readFileSync7(path, "utf8"));
13253
+ return JSON.parse(readFileSync8(path, "utf8"));
12998
13254
  } catch (e) {
12999
- log29.debug(`unreadable session ${id} \u2014 ignoring`, e);
13255
+ log30.debug(`unreadable session ${id} \u2014 ignoring`, e);
13000
13256
  return void 0;
13001
13257
  }
13002
13258
  }
@@ -13007,9 +13263,9 @@ var SessionStore = class {
13007
13263
  for (const f of readdirSync(this.dir)) {
13008
13264
  if (!f.endsWith(".json")) continue;
13009
13265
  try {
13010
- metas.push(JSON.parse(readFileSync7(join8(this.dir, f), "utf8")).meta);
13266
+ metas.push(JSON.parse(readFileSync8(join9(this.dir, f), "utf8")).meta);
13011
13267
  } catch (e) {
13012
- log29.debug(`skipping unreadable session file ${f}`, e);
13268
+ log30.debug(`skipping unreadable session file ${f}`, e);
13013
13269
  }
13014
13270
  }
13015
13271
  return metas.sort((a, b) => b.updated - a.updated);
@@ -13022,7 +13278,7 @@ var SessionStore = class {
13022
13278
  for (const f of readdirSync(this.dir)) {
13023
13279
  if (!f.endsWith(".json")) continue;
13024
13280
  try {
13025
- const st = statSync5(join8(this.dir, f));
13281
+ const st = statSync6(join9(this.dir, f));
13026
13282
  out.push({ id: f.slice(0, -5), mtime: st.mtimeMs, size: st.size });
13027
13283
  } catch {
13028
13284
  }
@@ -13041,11 +13297,11 @@ var SessionStore = class {
13041
13297
  function globalSessionLoad(idOrPrefix) {
13042
13298
  const gd = globalDir();
13043
13299
  if (!existsSync6(gd)) return void 0;
13044
- const exact = join8(gd, `${idOrPrefix}.json`);
13300
+ const exact = join9(gd, `${idOrPrefix}.json`);
13045
13301
  if (existsSync6(exact)) {
13046
13302
  try {
13047
13303
  const target = readlinkSync(exact);
13048
- return JSON.parse(readFileSync7(target, "utf8"));
13304
+ return JSON.parse(readFileSync8(target, "utf8"));
13049
13305
  } catch {
13050
13306
  return void 0;
13051
13307
  }
@@ -13055,8 +13311,8 @@ function globalSessionLoad(idOrPrefix) {
13055
13311
  if (!f.endsWith(".json")) continue;
13056
13312
  const base = f.slice(0, -5);
13057
13313
  if (base.includes(idOrPrefix) || base.endsWith(idOrPrefix)) {
13058
- const target = readlinkSync(join8(gd, f));
13059
- return JSON.parse(readFileSync7(target, "utf8"));
13314
+ const target = readlinkSync(join9(gd, f));
13315
+ return JSON.parse(readFileSync8(target, "utf8"));
13060
13316
  }
13061
13317
  }
13062
13318
  } catch {
@@ -13070,15 +13326,15 @@ function globalSessionList() {
13070
13326
  for (const f of readdirSync(gd)) {
13071
13327
  if (!f.endsWith(".json")) continue;
13072
13328
  try {
13073
- const target = readlinkSync(join8(gd, f));
13329
+ const target = readlinkSync(join9(gd, f));
13074
13330
  if (!existsSync6(target)) {
13075
13331
  try {
13076
- unlinkSync(join8(gd, f));
13332
+ unlinkSync(join9(gd, f));
13077
13333
  } catch {
13078
13334
  }
13079
13335
  continue;
13080
13336
  }
13081
- metas.push(JSON.parse(readFileSync7(target, "utf8")).meta);
13337
+ metas.push(JSON.parse(readFileSync8(target, "utf8")).meta);
13082
13338
  } catch {
13083
13339
  }
13084
13340
  }
@@ -13245,10 +13501,10 @@ ${formatDiff(ops)}`);
13245
13501
  // cli/gitCheckpoints.ts
13246
13502
  import { execFile as execFile3 } from "child_process";
13247
13503
  import { promisify } from "util";
13248
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync8 } from "fs";
13504
+ import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync8 } from "fs";
13249
13505
  import { homedir as homedir6 } from "os";
13250
- import { join as join9, resolve as resolve3, sep as sep3, parse as parsePath } from "path";
13251
- var log30 = forComponent("checkpoints");
13506
+ import { join as join10, resolve as resolve3, sep as sep3, parse as parsePath } from "path";
13507
+ var log31 = forComponent("checkpoints");
13252
13508
  var exec = promisify(execFile3);
13253
13509
  var DEFAULT_EXCLUDE = [".agent/", ".git/", "node_modules/", "dist/", "build/", ".next/", "target/", ".venv/", "__pycache__/", "*.log"];
13254
13510
  function isBroadRoot(p) {
@@ -13290,11 +13546,11 @@ var ShadowRepo = class {
13290
13546
  mkdirSync8(this.gitDir, { recursive: true });
13291
13547
  const valid = await this.run("rev-parse", "--git-dir").then(() => true, () => false);
13292
13548
  if (!valid) await this.run("init", "-q");
13293
- mkdirSync8(join9(this.gitDir, "info"), { recursive: true });
13294
- writeFileSync5(join9(this.gitDir, "info", "exclude"), this.exclude.join("\n") + "\n");
13549
+ mkdirSync8(join10(this.gitDir, "info"), { recursive: true });
13550
+ writeFileSync6(join10(this.gitDir, "info", "exclude"), this.exclude.join("\n") + "\n");
13295
13551
  this.ready = true;
13296
13552
  } catch (e) {
13297
- log30.debug(`git checkpoints unavailable for ${this.workTree}`, e);
13553
+ log31.debug(`git checkpoints unavailable for ${this.workTree}`, e);
13298
13554
  this.ready = false;
13299
13555
  }
13300
13556
  return this.ready;
@@ -13305,7 +13561,7 @@ var ShadowRepo = class {
13305
13561
  }
13306
13562
  async commit(label, forced = []) {
13307
13563
  await this.run("add", "-A");
13308
- for (const p of forced) await this.run("add", "-f", "--", p).catch((e) => log30.debug(`force-add failed: ${p}`, e));
13564
+ for (const p of forced) await this.run("add", "-f", "--", p).catch((e) => log31.debug(`force-add failed: ${p}`, e));
13309
13565
  await this.run("commit", "--allow-empty", "-q", "-m", label);
13310
13566
  }
13311
13567
  /** Inject the CURRENT (pre-edit) content of `paths` into the turn-open restore point by amending it.
@@ -13313,8 +13569,8 @@ var ShadowRepo = class {
13313
13569
  * turn-boundary `add -A` would never have captured it. Amend (vs a new commit) keeps one restore
13314
13570
  * point per turn, so the REPL's turn↔frame mapping stays intact. */
13315
13571
  async amendForced(paths) {
13316
- for (const p of paths) await this.run("add", "-f", "--", p).catch((e) => log30.debug(`force-capture failed: ${p}`, e));
13317
- await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) => log30.debug("amend failed", 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));
13318
13574
  }
13319
13575
  /** Commits on `ref`, oldest-first (canonical index space). */
13320
13576
  async log(ref) {
@@ -13374,7 +13630,7 @@ var ShadowRepo = class {
13374
13630
  await this.run("gc", "--auto", "-q").catch(() => {
13375
13631
  });
13376
13632
  } catch (e) {
13377
- log30.debug("checkpoint prune failed", e);
13633
+ log31.debug("checkpoint prune failed", e);
13378
13634
  }
13379
13635
  }
13380
13636
  };
@@ -13406,7 +13662,7 @@ var GitCheckpoints = class {
13406
13662
  const abs = resolve3(d);
13407
13663
  if (abs === cwd || abs.startsWith(cwd + sep3)) continue;
13408
13664
  if (cwd.startsWith(abs + sep3)) continue;
13409
- out.push({ workTree: abs, gitDir: join9(abs, ".agent", "checkpoints.git") });
13665
+ out.push({ workTree: abs, gitDir: join10(abs, ".agent", "checkpoints.git") });
13410
13666
  }
13411
13667
  return out;
13412
13668
  }
@@ -13442,7 +13698,7 @@ var GitCheckpoints = class {
13442
13698
  use(sessionId) {
13443
13699
  if (sessionId === this.session) return;
13444
13700
  this.session = sessionId;
13445
- if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) => log30.debug("re-point failed", e));
13701
+ if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) => log31.debug("re-point failed", e));
13446
13702
  }
13447
13703
  async begin(label) {
13448
13704
  if (!await this.start()) return;
@@ -13462,7 +13718,7 @@ var GitCheckpoints = class {
13462
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
13463
13719
  `);
13464
13720
  } else {
13465
- log30.debug("checkpoint commit failed", e);
13721
+ log31.debug("checkpoint commit failed", e);
13466
13722
  survivors.push(this.repos[i]);
13467
13723
  survivorCaches.push(this.caches[i] ?? []);
13468
13724
  }
@@ -13497,7 +13753,7 @@ var GitCheckpoints = class {
13497
13753
  if (this.forced.has(abs)) continue;
13498
13754
  this.forced.add(abs);
13499
13755
  const i = this.repoIndexFor(abs);
13500
- if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) => log30.debug("amendForced failed", e));
13756
+ if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) => log31.debug("amendForced failed", e));
13501
13757
  }
13502
13758
  }
13503
13759
  };
@@ -13549,7 +13805,7 @@ var GitCheckpointsOptions = class {
13549
13805
  /** Real working tree to snapshot (the launch cwd). */
13550
13806
  workTree = process.cwd();
13551
13807
  /** Isolated git dir for the cwd shadow repo (kept out of the user's real .git). */
13552
- gitDir = join9(process.cwd(), ".agent", "checkpoints.git");
13808
+ gitDir = join10(process.cwd(), ".agent", "checkpoints.git");
13553
13809
  /** Extra mounted dirs (`--add-dir`); those outside cwd each get their own shadow repo. */
13554
13810
  addDirs = [];
13555
13811
  /** Conversation id → per-session restore-point ref. */
@@ -13565,9 +13821,9 @@ var GitCheckpointsOptions = class {
13565
13821
  };
13566
13822
 
13567
13823
  // cli/permissions.ts
13568
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync9 } from "fs";
13824
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync9 } from "fs";
13569
13825
  import { homedir as homedir7 } from "os";
13570
- import { join as join10 } from "path";
13826
+ import { join as join11 } from "path";
13571
13827
  var RULE_RE = /^(\w[\w*-]*)(?:\((.+)\))?$/;
13572
13828
  function parseOne(raw, decision) {
13573
13829
  const m = RULE_RE.exec(raw.trim());
@@ -13589,13 +13845,13 @@ function parsePermRules(perms, explicit = false) {
13589
13845
  function describeRule(r) {
13590
13846
  return `${r.decision.padEnd(5)} ${r.tool ?? "*"}${r.pathGlob ? `(${r.pathGlob})` : ""}`;
13591
13847
  }
13592
- var PERM_FILE = (cwd) => join10(cwd, ".agent", "permissions.json");
13848
+ var PERM_FILE = (cwd) => join11(cwd, ".agent", "permissions.json");
13593
13849
  function loadPersistedRules(cwd) {
13594
13850
  const j = readJsonFile(PERM_FILE(cwd), null);
13595
13851
  return j ? { allow: j.allow ?? [], ask: j.ask ?? [], deny: j.deny ?? [] } : {};
13596
13852
  }
13597
13853
  function loadClaudeSettings(cwd, home = homedir7()) {
13598
- const files = [join10(home, ".claude", "settings.json"), join10(cwd, ".claude", "settings.json"), join10(cwd, ".claude", "settings.local.json")];
13854
+ const files = [join11(home, ".claude", "settings.json"), join11(cwd, ".claude", "settings.json"), join11(cwd, ".claude", "settings.local.json")];
13599
13855
  let out = {};
13600
13856
  for (const p of files) {
13601
13857
  const perms = readJsonFile(p, null)?.permissions;
@@ -13608,8 +13864,8 @@ function persistRule(cwd, decision, ruleStr) {
13608
13864
  const list = cur[decision] ??= [];
13609
13865
  if (!list.includes(ruleStr)) list.push(ruleStr);
13610
13866
  try {
13611
- mkdirSync9(join10(cwd, ".agent"), { recursive: true });
13612
- writeFileSync6(PERM_FILE(cwd), JSON.stringify(cur, null, 2) + "\n");
13867
+ mkdirSync9(join11(cwd, ".agent"), { recursive: true });
13868
+ writeFileSync7(PERM_FILE(cwd), JSON.stringify(cur, null, 2) + "\n");
13613
13869
  } catch {
13614
13870
  }
13615
13871
  }
@@ -13622,7 +13878,7 @@ function mergePerms(a, b) {
13622
13878
  }
13623
13879
  return Object.keys(out).length ? out : void 0;
13624
13880
  }
13625
- var TRUST_FILE = join10(homedir7(), ".agent", "trusted.json");
13881
+ var TRUST_FILE = join11(homedir7(), ".agent", "trusted.json");
13626
13882
  function isTrusted(cwd, file = TRUST_FILE) {
13627
13883
  const list = readJsonFile(file, []);
13628
13884
  return Array.isArray(list) && list.includes(cwd);
@@ -13632,8 +13888,8 @@ function trustDir(cwd, file = TRUST_FILE) {
13632
13888
  if (!Array.isArray(list)) list = [];
13633
13889
  if (!list.includes(cwd)) list.push(cwd);
13634
13890
  try {
13635
- mkdirSync9(join10(file, ".."), { recursive: true });
13636
- writeFileSync6(file, JSON.stringify(list, null, 2) + "\n");
13891
+ mkdirSync9(join11(file, ".."), { recursive: true });
13892
+ writeFileSync7(file, JSON.stringify(list, null, 2) + "\n");
13637
13893
  } catch {
13638
13894
  }
13639
13895
  }
@@ -13696,10 +13952,10 @@ function completePath(listDir, ref) {
13696
13952
 
13697
13953
  // cli/lineEditor.ts
13698
13954
  import { emitKeypressEvents } from "readline";
13699
- import { spawnSync as spawnSync4 } from "child_process";
13700
- import { writeFileSync as writeFileSync7, readFileSync as readFileSync8, unlinkSync as unlinkSync2 } from "fs";
13701
- import { tmpdir as tmpdir2 } from "os";
13702
- import { join as join11 } from "path";
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";
13703
13959
 
13704
13960
  // cli/bidi.ts
13705
13961
  var RTL_RE = /[\u0590-\u05ff\u0600-\u06ff\u0750-\u077f\u08a0-\u08ff\ufb1d-\ufdff\ufe70-\ufeff]/;
@@ -14886,14 +15142,14 @@ function createLineEditor(out, input = keyInput) {
14886
15142
  function externalEdit(s) {
14887
15143
  const spec = process.env.VISUAL || process.env.EDITOR || "vi";
14888
15144
  const [cmd, ...cargs] = spec.split(" ").filter(Boolean);
14889
- const file = join11(tmpdir2(), `agentx-edit-${process.pid}-${Date.now()}.md`);
15145
+ const file = join12(tmpdir3(), `agentx-edit-${process.pid}-${Date.now()}.md`);
14890
15146
  try {
14891
- writeFileSync7(file, s.buf);
15147
+ writeFileSync8(file, s.buf);
14892
15148
  process.stdin.setRawMode(false);
14893
15149
  out.write("\x1B[?2004l");
14894
- const r = spawnSync4(cmd, [...cargs, file], { stdio: "inherit" });
15150
+ const r = spawnSync5(cmd, [...cargs, file], { stdio: "inherit" });
14895
15151
  if (r.status === 0) {
14896
- const text = readFileSync8(file, "utf8").replace(/\n$/, "");
15152
+ const text = readFileSync9(file, "utf8").replace(/\n$/, "");
14897
15153
  s.reset();
14898
15154
  if (text) s.insert(text);
14899
15155
  }
@@ -15346,24 +15602,24 @@ function readPlainLine() {
15346
15602
  }
15347
15603
 
15348
15604
  // cli/osScheduler.ts
15349
- import { spawnSync as spawnSync5 } from "child_process";
15350
- import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3, chmodSync, existsSync as existsSync7 } from "fs";
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";
15351
15607
  import { homedir as homedir8 } from "os";
15352
- import { join as join12 } from "path";
15353
- var log31 = forComponent("os-sched");
15608
+ import { join as join13 } from "path";
15609
+ var log32 = forComponent("os-sched");
15354
15610
  var OsScheduler = class {
15355
15611
  options;
15356
15612
  constructor(options) {
15357
15613
  this.options = { ...new OsSchedulerOptions(), ...options };
15358
15614
  }
15359
15615
  get dir() {
15360
- return join12(this.options.home, ".agent", "sched");
15616
+ return join13(this.options.home, ".agent", "sched");
15361
15617
  }
15362
15618
  label(id) {
15363
15619
  return `cc.livx.agentx.sched-${id}`;
15364
15620
  }
15365
15621
  plistPath(id) {
15366
- return join12(this.options.home, "Library", "LaunchAgents", `${this.label(id)}.plist`);
15622
+ return join13(this.options.home, "Library", "LaunchAgents", `${this.label(id)}.plist`);
15367
15623
  }
15368
15624
  run(cmd, args, input) {
15369
15625
  return this.options.exec(cmd, args, input);
@@ -15379,11 +15635,11 @@ var OsScheduler = class {
15379
15635
  const script = this.writeScript(spec, oneOff);
15380
15636
  const mechanism = this.options.platform === "darwin" ? this.scheduleDarwin(spec, script) : this.scheduleLinux(spec, script, oneOff);
15381
15637
  const meta = { ...spec, created: Date.now(), mechanism };
15382
- writeFileSync8(join12(this.dir, `${spec.id}.json`), JSON.stringify(meta, null, 2));
15638
+ writeFileSync9(join13(this.dir, `${spec.id}.json`), JSON.stringify(meta, null, 2));
15383
15639
  return mechanism;
15384
15640
  }
15385
15641
  cancel(id) {
15386
- const meta = readJsonFile(join12(this.dir, `${id}.json`), null);
15642
+ const meta = readJsonFile(join13(this.dir, `${id}.json`), null);
15387
15643
  if (!meta) return false;
15388
15644
  try {
15389
15645
  if (this.options.platform === "darwin") {
@@ -15412,11 +15668,11 @@ var OsScheduler = class {
15412
15668
  }
15413
15669
  }
15414
15670
  } catch (e) {
15415
- log31.debug(`cancel ${id}`, e);
15671
+ log32.debug(`cancel ${id}`, e);
15416
15672
  }
15417
15673
  for (const f of [`${id}.json`, `${id}.sh`]) {
15418
15674
  try {
15419
- unlinkSync3(join12(this.dir, f));
15675
+ unlinkSync3(join13(this.dir, f));
15420
15676
  } catch {
15421
15677
  }
15422
15678
  }
@@ -15424,19 +15680,19 @@ var OsScheduler = class {
15424
15680
  }
15425
15681
  list() {
15426
15682
  if (!existsSync7(this.dir)) return [];
15427
- return readdirSync2(this.dir).filter((f) => f.endsWith(".json")).map((f) => readJsonFile(join12(this.dir, f), null)).filter(Boolean);
15683
+ return readdirSync2(this.dir).filter((f) => f.endsWith(".json")).map((f) => readJsonFile(join13(this.dir, f), null)).filter(Boolean);
15428
15684
  }
15429
15685
  /** The per-job runner script: cd to the project, headless-resume the session, log, notify. */
15430
15686
  writeScript(spec, oneOff) {
15431
- const p = join12(this.dir, `${spec.id}.sh`);
15687
+ const p = join13(this.dir, `${spec.id}.sh`);
15432
15688
  const q2 = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
15433
- const cleanup = oneOff ? this.options.platform === "darwin" ? `launchctl remove ${this.label(spec.id)} 2>/dev/null; rm -f ${q2(this.plistPath(spec.id))} ${q2(join12(this.dir, `${spec.id}.json`))} ${q2(p)}
15434
- ` : `rm -f ${q2(join12(this.dir, `${spec.id}.json`))} ${q2(p)}
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)}
15435
15691
  ` : "";
15436
- writeFileSync8(p, `#!/bin/sh
15692
+ writeFileSync9(p, `#!/bin/sh
15437
15693
  # agentx scheduled job ${spec.id}${spec.label ? ` \u2014 ${spec.label}` : ""}
15438
15694
  cd ${q2(spec.cwd)} || exit 1
15439
- ${this.options.agentx} -p ${q2(spec.prompt)} --resume ${q2(spec.sessionId)} --yes >> ${q2(join12(this.dir, `${spec.id}.log`))} 2>&1
15695
+ ${this.options.agentx} -p ${q2(spec.prompt)} --resume ${q2(spec.sessionId)} --yes >> ${q2(join13(this.dir, `${spec.id}.log`))} 2>&1
15440
15696
  ${cleanup}`);
15441
15697
  chmodSync(p, 493);
15442
15698
  return p;
@@ -15473,8 +15729,8 @@ ${trigger}
15473
15729
  <key>RunAtLoad</key><false/>
15474
15730
  </dict></plist>
15475
15731
  `;
15476
- mkdirSync10(join12(this.options.home, "Library", "LaunchAgents"), { recursive: true });
15477
- writeFileSync8(this.plistPath(spec.id), plist);
15732
+ mkdirSync10(join13(this.options.home, "Library", "LaunchAgents"), { recursive: true });
15733
+ writeFileSync9(this.plistPath(spec.id), plist);
15478
15734
  this.run("launchctl", ["load", this.plistPath(spec.id)]);
15479
15735
  return `launchd:${this.label(spec.id)}`;
15480
15736
  }
@@ -15511,7 +15767,7 @@ var OsSchedulerOptions = class {
15511
15767
  /** Injectable executor for tests. Returns stdout+stderr merged — `at` reports "job N" on STDERR,
15512
15768
  * and the job id is what atrm needs for cancel. Throws on non-zero exit. */
15513
15769
  exec = (cmd, args, input) => {
15514
- const r = spawnSync5(cmd, args, { input, encoding: "utf8" });
15770
+ const r = spawnSync6(cmd, args, { input, encoding: "utf8" });
15515
15771
  if (r.error) throw r.error;
15516
15772
  if (r.status !== 0) throw new Error(`${cmd} exited ${r.status}: ${r.stderr || r.stdout}`);
15517
15773
  return `${r.stdout ?? ""}${r.stderr ?? ""}`;
@@ -15528,10 +15784,10 @@ import { createServer as createServer2, createConnection } from "net";
15528
15784
  import { spawn as spawn3 } from "child_process";
15529
15785
  import { existsSync as existsSync8, mkdirSync as mkdirSync11, unlinkSync as unlinkSync4, readdirSync as readdirSync3 } from "fs";
15530
15786
  import { homedir as homedir9 } from "os";
15531
- import { join as join13 } from "path";
15532
- var log32 = forComponent("remote-trigger");
15533
- var TRIGGER_DIR = () => join13(homedir9(), ".agent", "triggers");
15534
- var sockPath = (sessionId, dir = TRIGGER_DIR()) => join13(dir, `${sessionId}.sock`);
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`);
15535
15791
  var TriggerServer = class {
15536
15792
  constructor(onTrigger) {
15537
15793
  this.onTrigger = onTrigger;
@@ -15567,13 +15823,13 @@ var TriggerServer = class {
15567
15823
  conn.end(JSON.stringify({ ok: false, error: String(e) }) + "\n");
15568
15824
  }
15569
15825
  });
15570
- conn.on("error", (e) => log32.debug("trigger conn error", e));
15826
+ conn.on("error", (e) => log33.debug("trigger conn error", e));
15571
15827
  });
15572
- this.server.on("error", (e) => log32.debug("trigger server error", e));
15828
+ this.server.on("error", (e) => log33.debug("trigger server error", e));
15573
15829
  this.server.listen(p);
15574
15830
  this.path = p;
15575
15831
  } catch (e) {
15576
- log32.debug("trigger server unavailable", e);
15832
+ log33.debug("trigger server unavailable", e);
15577
15833
  }
15578
15834
  }
15579
15835
  /** Re-bind on /resume (the session id changed). */
@@ -15695,10 +15951,10 @@ var strike = C("9");
15695
15951
  var link = (text, url) => useColor ? `\x1B]8;;${url}\x1B\\${cyan(text)}\x1B]8;;\x1B\\` : `${text} (${url})`;
15696
15952
  var mdPalette = { bold, dim, cyan, italic, strike, green, link };
15697
15953
  var err = (s) => process.stderr.write(s);
15698
- var log33 = forComponent("cli");
15954
+ var log34 = forComponent("cli");
15699
15955
  var VERSION = (() => {
15700
15956
  try {
15701
- return JSON.parse(readFileSync9(new URL("../package.json", import.meta.url), "utf8")).version ?? "?";
15957
+ return JSON.parse(readFileSync10(new URL("../package.json", import.meta.url), "utf8")).version ?? "?";
15702
15958
  } catch {
15703
15959
  return "?";
15704
15960
  }
@@ -15717,7 +15973,7 @@ async function checkForUpdate(current) {
15717
15973
  return `Update available: ${current} \u2192 ${latest}. Run \`bun add -g @livx.cc/agentx\` to update.`;
15718
15974
  }
15719
15975
  function installUpdate() {
15720
- const r = spawnSync6("bun", ["add", "-g", "@livx.cc/agentx"], { stdio: "inherit" });
15976
+ const r = spawnSync7("bun", ["add", "-g", "@livx.cc/agentx"], { stdio: "inherit" });
15721
15977
  return (r.status ?? 1) === 0;
15722
15978
  }
15723
15979
  var typeaheadOnLine = false;
@@ -16005,7 +16261,7 @@ function resolveModelOrNewest(model) {
16005
16261
  var ENV_KEY_ALIASES = { google: ["GEMINI_API_KEY"] };
16006
16262
  function loadEnvFile(file) {
16007
16263
  if (!existsSync9(file)) return;
16008
- for (const line of readFileSync9(file, "utf8").split("\n")) {
16264
+ for (const line of readFileSync10(file, "utf8").split("\n")) {
16009
16265
  const m = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
16010
16266
  if (!m || m[1] in process.env) continue;
16011
16267
  let val = m[2].trim();
@@ -16016,10 +16272,10 @@ function loadEnvFile(file) {
16016
16272
  }
16017
16273
  function loadInstallEnv() {
16018
16274
  let dir = dirname5(import.meta.path);
16019
- for (let i = 0; i < 5 && !existsSync9(join14(dir, "package.json")); i++) dir = dirname5(dir);
16275
+ for (let i = 0; i < 5 && !existsSync9(join15(dir, "package.json")); i++) dir = dirname5(dir);
16020
16276
  for (const name of [".env", ".env.local"]) {
16021
- loadEnvFile(join14(dir, name));
16022
- loadEnvFile(join14(homedir10(), ".agent", name));
16277
+ loadEnvFile(join15(dir, name));
16278
+ loadEnvFile(join15(homedir10(), ".agent", name));
16023
16279
  }
16024
16280
  }
16025
16281
  async function loadBodifySecrets() {
@@ -16070,6 +16326,7 @@ function makeHost(format = "text", opts) {
16070
16326
  };
16071
16327
  const activitySeen = /* @__PURE__ */ new Set();
16072
16328
  const turnActivityIds = /* @__PURE__ */ new Set();
16329
+ const toolNames = /* @__PURE__ */ new Map();
16073
16330
  let anonActivityCount = 0;
16074
16331
  return {
16075
16332
  flushText,
@@ -16153,7 +16410,17 @@ function makeHost(format = "text", opts) {
16153
16410
  spinner.start();
16154
16411
  return;
16155
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
+ }
16156
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");
16157
16424
  flushLookupGroup();
16158
16425
  closeReasonLine();
16159
16426
  flushText();
@@ -16480,7 +16747,7 @@ function turnCost(model, usage) {
16480
16747
  const priced = model.startsWith("claude-code/") ? `anthropic/${resolveModelAlias(model.slice("claude-code/".length))}` : model;
16481
16748
  return costOf(getModelInfo(priced)?.pricing, usage?.promptTokens ?? 0, usage?.completionTokens ?? 0, usage?.cacheCreationTokens ?? 0, usage?.cacheReadTokens ?? 0, priced);
16482
16749
  }
16483
- async function evaluateGoal(ai, condition, transcript, log34) {
16750
+ async function evaluateGoal(ai, condition, transcript, log35) {
16484
16751
  const recent = transcript.filter((m) => m.role === "assistant").slice(-8).map((m) => {
16485
16752
  const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join(" ");
16486
16753
  return text.slice(0, 600);
@@ -16500,7 +16767,7 @@ ${recent}` }
16500
16767
  const match = r.content.match(/\{[\s\S]*\}/);
16501
16768
  if (match) return JSON.parse(match[0]);
16502
16769
  } catch (e) {
16503
- log34(dim(` (goal evaluator error: ${e?.message ?? e})
16770
+ log35(dim(` (goal evaluator error: ${e?.message ?? e})
16504
16771
  `));
16505
16772
  }
16506
16773
  return { met: false, reason: "evaluation unclear" };
@@ -16511,8 +16778,12 @@ function errInfo(e) {
16511
16778
  function fmtUsd(n) {
16512
16779
  return n >= 1 ? `$${n.toFixed(2)}` : `$${n.toFixed(4)}`;
16513
16780
  }
16514
- function estimateTranscriptTokens(messages, keepRecentImages = 1) {
16515
- return estimateTokens(messages, keepRecentImages);
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);
16516
16787
  }
16517
16788
  function formatStatus(s) {
16518
16789
  const L = (k, v) => ` ${k.padEnd(12)} ${v}
@@ -16630,6 +16901,7 @@ function optsFor(args, ai, cfg = {}, extraTools = []) {
16630
16901
  model,
16631
16902
  // The real context window drives the context % and `/compact`'s budget (never `maxTokens` — that's spend).
16632
16903
  contextWindow: getModelInfo(model)?.contextWindow ?? 0,
16904
+ imageInput: imageInputOf,
16633
16905
  cwd: args.cwd,
16634
16906
  tools: cfg.tools,
16635
16907
  extraTools,
@@ -16721,13 +16993,13 @@ function mcpAgentTools(mounted, opts) {
16721
16993
  return tools;
16722
16994
  }
16723
16995
  async function closeMcp(mounted) {
16724
- await Promise.all(mounted.map((m) => m.client.close().catch((e) => log33.debug("mcp close failed", e))));
16996
+ await Promise.all(mounted.map((m) => m.client.close().catch((e) => log34.debug("mcp close failed", e))));
16725
16997
  }
16726
16998
  var IMG_EXT = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
16727
16999
  function mentionRefs(line) {
16728
17000
  return [...line.matchAll(/(?:^|\s)@(?:"([^"]+)"|(\S+))/g)].map((m) => m[1] ?? m[2].replace(/[?!.,;:)\]}'">]+$/, "")).filter(Boolean);
16729
17001
  }
16730
- var untilde = (p) => p.startsWith("~/") ? join14(homedir10(), p.slice(2)) : p;
17002
+ var untilde = (p) => p.startsWith("~/") ? join15(homedir10(), p.slice(2)) : p;
16731
17003
  function readImageParts(cwd, line) {
16732
17004
  const refs = mentionRefs(line);
16733
17005
  const parts = [];
@@ -16736,7 +17008,7 @@ function readImageParts(cwd, line) {
16736
17008
  if (!mime) continue;
16737
17009
  const abs = ref.startsWith("~/") ? untilde(ref) : resolve4(cwd, ref);
16738
17010
  try {
16739
- statSync6(abs);
17011
+ statSync7(abs);
16740
17012
  } catch {
16741
17013
  continue;
16742
17014
  }
@@ -16752,7 +17024,7 @@ function pastePathClassifier(cwd) {
16752
17024
  if (!/^(\/|~\/|\.\/|\.\.\/)/.test(t)) return null;
16753
17025
  const abs = t.startsWith("~/") ? untilde(t) : resolve4(cwd, t);
16754
17026
  try {
16755
- if (!statSync6(abs).isFile()) return null;
17027
+ if (!statSync7(abs).isFile()) return null;
16756
17028
  } catch {
16757
17029
  return null;
16758
17030
  }
@@ -16773,7 +17045,7 @@ async function expandMentions(fs, line) {
16773
17045
  if (loaded.includes(ref) || missing.includes(ref)) continue;
16774
17046
  if (ref.includes(":") && mcpMentionResolver) {
16775
17047
  const body = await mcpMentionResolver(ref).catch((e) => {
16776
- log33.debug("mcp mention resolve failed", e);
17048
+ log34.debug("mcp mention resolve failed", e);
16777
17049
  return null;
16778
17050
  });
16779
17051
  if (body != null) {
@@ -16813,7 +17085,8 @@ function jsonResult(res, session) {
16813
17085
  sessionId: session.meta.id,
16814
17086
  // The parked question, machine-readable — the caller answers it and resumes `sessionId`.
16815
17087
  ...res.finishReason === "needs_input" && res.question ? { question: res.question } : {},
16816
- ...(res.finishReason === "error" || res.finishReason === "empty") && res.error ? { error: res.error?.message ?? String(res.error) } : {}
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) } : {}
16817
17090
  };
16818
17091
  }
16819
17092
  async function readMultiline(readLine) {
@@ -16862,7 +17135,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
16862
17135
  try {
16863
17136
  store.save(session);
16864
17137
  } catch (ex) {
16865
- log33.debug("mid-turn session flush failed", ex);
17138
+ log34.debug("mid-turn session flush failed", ex);
16866
17139
  }
16867
17140
  }
16868
17141
  return origNotify(e);
@@ -16930,7 +17203,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
16930
17203
  }
16931
17204
  return { ok, res };
16932
17205
  }
16933
- var remintSessionImages = (data, store, cwd) => remintImageRefs(data.messages, [join14(store.dir, `${data.meta.id}.images`), cwd]);
17206
+ var remintSessionImages = (data, store, cwd) => remintImageRefs(data.messages, [join15(store.dir, `${data.meta.id}.images`), cwd]);
16934
17207
  function startSession(args, store, agent, cwd) {
16935
17208
  if (args.resume || args.cont) {
16936
17209
  const data = args.resume ? store.load(args.resume) ?? globalSessionLoad(args.resume) : store.latestData();
@@ -16977,25 +17250,25 @@ var AGENTS_MD_TEMPLATE = `# ${"${name}"}
16977
17250
  `;
16978
17251
  function initInstructions(cwd) {
16979
17252
  for (const f of ["AGENTS.md", "CLAUDE.md"]) {
16980
- if (existsSync9(join14(cwd, f))) {
17253
+ if (existsSync9(join15(cwd, f))) {
16981
17254
  err(yellow(` ${f} already exists \u2014 leaving it as-is
16982
17255
  `));
16983
17256
  return;
16984
17257
  }
16985
17258
  }
16986
- const path = join14(cwd, "AGENTS.md");
16987
- writeFileSync9(path, AGENTS_MD_TEMPLATE.replace("${name}", basename2(cwd)));
17259
+ const path = join15(cwd, "AGENTS.md");
17260
+ writeFileSync10(path, AGENTS_MD_TEMPLATE.replace("${name}", basename2(cwd)));
16988
17261
  err(green(` created ${path}
16989
17262
  `) + dim(" edit it, then it auto-loads into every run.\n"));
16990
17263
  }
16991
17264
  function persistSetting(cwd, key, value) {
16992
- const path = join14(cwd, ".agent", "settings.json");
17265
+ const path = join15(cwd, ".agent", "settings.json");
16993
17266
  try {
16994
- const obj = existsSync9(path) ? JSON.parse(readFileSync9(path, "utf8")) : {};
17267
+ const obj = existsSync9(path) ? JSON.parse(readFileSync10(path, "utf8")) : {};
16995
17268
  if (obj[key] === value) return;
16996
17269
  obj[key] = value;
16997
17270
  mkdirSync12(dirname5(path), { recursive: true });
16998
- writeFileSync9(path, JSON.stringify(obj, null, 2) + "\n");
17271
+ writeFileSync10(path, JSON.stringify(obj, null, 2) + "\n");
16999
17272
  } catch (e) {
17000
17273
  err(yellow(` \u26A0 couldn't persist ${key} to ${path} \u2014 ${e?.message ?? e}
17001
17274
  `));
@@ -17011,14 +17284,14 @@ var isCancelTeardown = (e) => {
17011
17284
  function installCancelGuards(mounted) {
17012
17285
  process.on("unhandledRejection", (e) => {
17013
17286
  if (isCancelTeardown(e)) {
17014
- log33.debug("suppressed unhandledRejection (cursor stream cancel)", e);
17287
+ log34.debug("suppressed unhandledRejection (cursor stream cancel)", e);
17015
17288
  return;
17016
17289
  }
17017
- log33.error("unhandledRejection", e);
17290
+ log34.error("unhandledRejection", e);
17018
17291
  });
17019
17292
  process.on("uncaughtException", (e) => {
17020
17293
  if (isCancelTeardown(e)) {
17021
- log33.debug("suppressed uncaughtException (cursor stream cancel)", e);
17294
+ log34.debug("suppressed uncaughtException (cursor stream cancel)", e);
17022
17295
  return;
17023
17296
  }
17024
17297
  console.error(e);
@@ -17027,7 +17300,7 @@ function installCancelGuards(mounted) {
17027
17300
  });
17028
17301
  }
17029
17302
  async function repl(args, ai, cfg, cwd) {
17030
- const oauth = new McpOAuth({ storePath: join14(cwd, ".agent", "mcp-auth.json") });
17303
+ const oauth = new McpOAuth({ storePath: join15(cwd, ".agent", "mcp-auth.json") });
17031
17304
  const mounted = await mountMcp(cfg, oauth);
17032
17305
  let mcpToolNames = [];
17033
17306
  const initialMcpTools = mcpAgentTools(mounted);
@@ -17303,7 +17576,7 @@ async function repl(args, ai, cfg, cwd) {
17303
17576
  quickLook: {
17304
17577
  branch: () => {
17305
17578
  try {
17306
- const head = readFileSync9(join14(cwd, ".git", "HEAD"), "utf8").trim();
17579
+ const head = readFileSync10(join15(cwd, ".git", "HEAD"), "utf8").trim();
17307
17580
  return head.startsWith("ref: refs/heads/") ? `branch: ${head.slice("ref: refs/heads/".length)}` : `detached HEAD at ${head.slice(0, 12)}`;
17308
17581
  } catch {
17309
17582
  return "not a git repository";
@@ -17437,11 +17710,11 @@ async function repl(args, ai, cfg, cwd) {
17437
17710
  const pendingImages = [];
17438
17711
  const bangContext = [];
17439
17712
  const grabClipboardAttachment = () => {
17440
- const dir = join14(store.dir, `${session.meta.id}.images`);
17713
+ const dir = join15(store.dir, `${session.meta.id}.images`);
17441
17714
  try {
17442
17715
  mkdirSync12(dir, { recursive: true });
17443
17716
  } catch (e) {
17444
- log33.debug("paste image dir create failed", e);
17717
+ log34.debug("paste image dir create failed", e);
17445
17718
  }
17446
17719
  const img = grabClipboardImage(dir, String(Date.now()));
17447
17720
  return img ? { display: "Image", ref: "@" + img.path, path: img.path } : null;
@@ -17495,7 +17768,7 @@ async function repl(args, ai, cfg, cwd) {
17495
17768
  err(dim(` \u23F0 ${scheduler.size} scheduled job(s) re-armed
17496
17769
  `));
17497
17770
  }
17498
- const checkpoints = args.vfs || args.boddb ? new CheckpointStack(agent.options.fs) : new GitCheckpoints({ workTree: cwd, gitDir: join14(cwd, ".agent", "checkpoints.git"), addDirs: args.addDirs, sessionId: session.meta.id });
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 });
17499
17772
  const cpHooks = checkpoints.hooks?.();
17500
17773
  if (cpHooks) {
17501
17774
  agent.options.hooks = composeHooks(agent.options.hooks, cpHooks);
@@ -17547,14 +17820,14 @@ ${lines.join("\n")}
17547
17820
  Added entries are loadable now via the Skill/SlashCommand tools; removed ones are gone even if still listed in the system prompt.
17548
17821
  </system-reminder>`;
17549
17822
  };
17550
- const histPath = join14(cwd, ".agent", "history");
17551
- const history = existsSync9(histPath) ? readFileSync9(histPath, "utf8").split("\n").filter(Boolean).reverse().slice(0, 500) : [];
17552
- const remember = (line) => {
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) => {
17553
17826
  try {
17554
- mkdirSync12(join14(cwd, ".agent"), { recursive: true });
17827
+ mkdirSync12(join15(cwd, ".agent"), { recursive: true });
17555
17828
  appendFileSync(histPath, line + "\n");
17556
17829
  } catch (e) {
17557
- log33.debug("history write failed", e);
17830
+ log34.debug("history write failed", e);
17558
17831
  }
17559
17832
  };
17560
17833
  const ago = (t) => {
@@ -17626,7 +17899,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
17626
17899
  try {
17627
17900
  store.save(session);
17628
17901
  } catch (e) {
17629
- log33.debug("session save after rewind failed", e);
17902
+ log34.debug("session save after rewind failed", e);
17630
17903
  }
17631
17904
  err(green(" \u27F2 jumped back") + dim(` \u2014 ${face.transcript.length} message(s) kept; edit + resend
17632
17905
  `));
@@ -17661,7 +17934,7 @@ ${task}`;
17661
17934
  bangContext.length = 0;
17662
17935
  }
17663
17936
  const delta = await refreshCatalogs().catch((e) => {
17664
- log33.debug("catalog refresh failed", e);
17937
+ log34.debug("catalog refresh failed", e);
17665
17938
  return "";
17666
17939
  });
17667
17940
  if (delta) {
@@ -17839,7 +18112,7 @@ ${task}`;
17839
18112
  context: {
17840
18113
  desc: "context-window usage (messages + estimated tokens)",
17841
18114
  run: () => {
17842
- const est = estimateTranscriptTokens(face.transcript, face.options.keepRecentImages);
18115
+ const est = estimateTranscriptTokens(face.transcript, face.options.keepRecentImages, face.options.imageInput?.(face.options.model) !== false);
17843
18116
  const cap = Agent.contextCap(face.options);
17844
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)
17845
18118
  `));
@@ -17854,8 +18127,8 @@ ${task}`;
17854
18127
  const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
17855
18128
  if (wasRaw) process.stdin.setRawMode(false);
17856
18129
  try {
17857
- const { spawnSync: spawnSync7 } = await import("child_process");
17858
- const r = spawnSync7("less", ["-R"], { input: text, stdio: ["pipe", "inherit", "inherit"] });
18130
+ const { spawnSync: spawnSync8 } = await import("child_process");
18131
+ const r = spawnSync8("less", ["-R"], { input: text, stdio: ["pipe", "inherit", "inherit"] });
17859
18132
  if (r.error) err(text);
17860
18133
  } finally {
17861
18134
  if (wasRaw) process.stdin.setRawMode(true);
@@ -17875,8 +18148,8 @@ ${task}`;
17875
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)");
17876
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)");
17877
18150
  {
17878
- const { spawnSync: spawnSync7 } = await import("child_process");
17879
- const has = (cmd) => spawnSync7("which", [cmd]).status === 0;
18151
+ const { spawnSync: spawnSync8 } = await import("child_process");
18152
+ const has = (cmd) => spawnSync8("which", [cmd]).status === 0;
17880
18153
  if (!VoiceIO.available()) warn("voice: keys missing (SONIOX_API_KEY / CARTESIA_API_KEY / CARTESIA_VOICE_ID) \u2014 voice disabled");
17881
18154
  else {
17882
18155
  ok("voice: keys present");
@@ -17897,7 +18170,7 @@ ${task}`;
17897
18170
  try {
17898
18171
  const probe = `${cwd}/.agent/sessions/.doctor-probe`;
17899
18172
  mkdirSync12(`${cwd}/.agent/sessions`, { recursive: true });
17900
- writeFileSync9(probe, "ok");
18173
+ writeFileSync10(probe, "ok");
17901
18174
  unlinkSync5(probe);
17902
18175
  ok(`session store writable (${cwd}/.agent/sessions)`);
17903
18176
  } catch (e) {
@@ -17925,7 +18198,7 @@ ${task}`;
17925
18198
  desc: "rescan skills/commands dirs and rebuild the system prompt (one cache miss) \u2014 picks up entries created mid-session",
17926
18199
  run: async () => {
17927
18200
  await refreshCatalogs().catch((e) => {
17928
- log33.debug("catalog refresh failed", e);
18201
+ log34.debug("catalog refresh failed", e);
17929
18202
  });
17930
18203
  face.reprepare();
17931
18204
  err(green(` \u2713 reloaded \u2014 ${skills.length} skill(s), ${cmds.length} command(s); system prompt rebuilds on next message
@@ -18307,8 +18580,8 @@ ${task}`;
18307
18580
  const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
18308
18581
  if (wasRaw) process.stdin.setRawMode(false);
18309
18582
  try {
18310
- const { spawnSync: spawnSync7 } = await import("child_process");
18311
- spawnSync7(ed, [idx], { stdio: "inherit" });
18583
+ const { spawnSync: spawnSync8 } = await import("child_process");
18584
+ spawnSync8(ed, [idx], { stdio: "inherit" });
18312
18585
  } finally {
18313
18586
  if (wasRaw) process.stdin.setRawMode(true);
18314
18587
  }
@@ -18427,7 +18700,7 @@ ${task}`;
18427
18700
  try {
18428
18701
  for (const def of (await loadAgents(fs2, d)).agents) if (!seen.has(def.name)) seen.set(def.name, { def, from: d });
18429
18702
  } catch (e) {
18430
- log33.debug(`loadAgents(${d}) failed`, e);
18703
+ log34.debug(`loadAgents(${d}) failed`, e);
18431
18704
  }
18432
18705
  }
18433
18706
  if (!seen.size) {
@@ -18515,7 +18788,7 @@ ${task}`;
18515
18788
  }
18516
18789
  if (idx >= 0) {
18517
18790
  const old = mounted.splice(idx, 1)[0];
18518
- await old.client.close().catch((e) => log33.debug("mcp close failed", e));
18791
+ await old.client.close().catch((e) => log34.debug("mcp close failed", e));
18519
18792
  }
18520
18793
  try {
18521
18794
  const m = await mountMcpServer(name, conf);
@@ -18543,7 +18816,7 @@ ${task}`;
18543
18816
  }
18544
18817
  const m = mounted.splice(idx, 1)[0];
18545
18818
  remountMcpTools();
18546
- await m.client.close().catch((e) => log33.debug("mcp close failed", e));
18819
+ await m.client.close().catch((e) => log34.debug("mcp close failed", e));
18547
18820
  err(dim(` removed "${name}"
18548
18821
  `));
18549
18822
  return;
@@ -18658,11 +18931,11 @@ ${task}`;
18658
18931
  return;
18659
18932
  }
18660
18933
  const md = exportMarkdown(session.meta, shown);
18661
- const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" : join14(".agent", "exports", `${session.meta.id}.md`);
18934
+ const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" : join15(".agent", "exports", `${session.meta.id}.md`);
18662
18935
  const path = resolve4(cwd, name);
18663
18936
  try {
18664
18937
  mkdirSync12(dirname5(path), { recursive: true });
18665
- writeFileSync9(path, md);
18938
+ writeFileSync10(path, md);
18666
18939
  err(green(` \u2713 exported \u2192 ${path}
18667
18940
  `) + dim(` ${shown.length} message(s) \xB7 ${md.length} chars
18668
18941
  `));
@@ -18744,9 +19017,9 @@ ${task}`;
18744
19017
  });
18745
19018
  const listDir = (absDir) => {
18746
19019
  try {
18747
- return readdirSync4(join14(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
19020
+ return readdirSync4(join15(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
18748
19021
  } catch (e) {
18749
- log33.debug("completion readdir failed", absDir, e);
19022
+ log34.debug("completion readdir failed", absDir, e);
18750
19023
  return null;
18751
19024
  }
18752
19025
  };
@@ -18849,7 +19122,7 @@ ${task}`;
18849
19122
  if (!opts?.id) takeCut(voiceIO, false);
18850
19123
  if (/^[!#/]/.test(line)) dispatchPending = false;
18851
19124
  history.unshift(line.replace(/\n+/g, " \u23CE "));
18852
- remember(line.replace(/\n+/g, " \u23CE "));
19125
+ remember2(line.replace(/\n+/g, " \u23CE "));
18853
19126
  if (line.startsWith("!")) {
18854
19127
  const cmd = line.slice(1).trim();
18855
19128
  if (cmd) {
@@ -18883,7 +19156,7 @@ ${out}
18883
19156
  return;
18884
19157
  }
18885
19158
  await refreshCatalogs().catch((e) => {
18886
- log33.debug("catalog refresh failed", e);
19159
+ log34.debug("catalog refresh failed", e);
18887
19160
  });
18888
19161
  const sk = skills.find((x) => x.name === name);
18889
19162
  if (sk) {
@@ -18929,7 +19202,7 @@ ${out}
18929
19202
  return false;
18930
19203
  }
18931
19204
  const fakeVoice = process.env.AGENTX_VOICE_FAKE ? fakeVoiceParts(process.env.AGENTX_VOICE_FAKE) : null;
18932
- voiceDiag = new JsonlDiagSink(join14(store.dir, `${session.meta.id}.voice.jsonl`));
19205
+ voiceDiag = new JsonlDiagSink(join15(store.dir, `${session.meta.id}.voice.jsonl`));
18933
19206
  voiceIO = new VoiceIO({
18934
19207
  ...fakeVoice ?? {},
18935
19208
  onDiag: (ev) => voiceDiag?.push(ev),
@@ -18994,7 +19267,7 @@ ${out}
18994
19267
  if (delivery || cut) dx?.parkCutDelivery(delivery, !!cut, heard);
18995
19268
  const cutReply = cut && cut.full.length - cut.heard.length > 40 ? cut : null;
18996
19269
  const noteP = replaces ? replaces.note : !cutReply ? Promise.resolve("") : (instinct ? instinct.bargeIntent(text).catch((e) => {
18997
- log33.warn(`barge intent failed \u2014 model decides: ${e.message}`);
19270
+ log34.warn(`barge intent failed \u2014 model decides: ${e.message}`);
18998
19271
  return void 0;
18999
19272
  }) : Promise.resolve(void 0)).then(async (intent) => interruptionNote(cutReply, intent) + ackedNote(await instinct?.ackFor(text)));
19000
19273
  const cutIn = replaces ? replaces.cutIn : interjectNote;
@@ -19152,7 +19425,7 @@ ${out}
19152
19425
  err("\n");
19153
19426
  const initial = prefill ?? (carry || void 0);
19154
19427
  prefill = void 0;
19155
- const ctxTok = estimateTranscriptTokens(face.transcript, face.options.keepRecentImages);
19428
+ const ctxTok = estimateTranscriptTokens(face.transcript, face.options.keepRecentImages, face.options.imageInput?.(face.options.model) !== false);
19156
19429
  const ctxCap = Agent.contextCap(face.options);
19157
19430
  {
19158
19431
  const pct = Math.round(ctxTok / ctxCap * 100);
@@ -19332,7 +19605,7 @@ ${out}
19332
19605
  }
19333
19606
  async function duplexServe(args, ai, cfg, cwd) {
19334
19607
  const out = (o) => process.stdout.write(JSON.stringify(o) + "\n");
19335
- const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join14(cwd, ".agent", "mcp-auth.json") }));
19608
+ const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join15(cwd, ".agent", "mcp-auth.json") }));
19336
19609
  installCancelGuards(mounted);
19337
19610
  const agent = await makeAgent(args, ai, cfg, mcpAgentTools(mounted, { quiet: true }), mounted.map((m) => m.name));
19338
19611
  const { host: _h, stream: _s, signal: _g, providerOptions: _po, ...wo } = agent.options;
@@ -19376,7 +19649,7 @@ async function duplexServe(args, ai, cfg, cwd) {
19376
19649
  try {
19377
19650
  store.save(session);
19378
19651
  } catch (e) {
19379
- log33.warn("duplex session save failed", e);
19652
+ log34.warn("duplex session save failed", e);
19380
19653
  }
19381
19654
  };
19382
19655
  out({ type: "ready", sessionId: session.meta.id, reflexModel: dx.options.reflexModel, actModel: dx.options.actModel });
@@ -19400,7 +19673,7 @@ async function duplexServe(args, ai, cfg, cwd) {
19400
19673
  const note = reply && reply.full.length - reply.heard.length > 40 ? interruptionNote(reply) : "";
19401
19674
  const id = m.id ? String(m.id) : void 0;
19402
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) => {
19403
- log33.error("duplex turn failed", e);
19676
+ log34.error("duplex turn failed", e);
19404
19677
  out({ type: "turn_done", id, finishReason: "error", error: String(e?.message ?? e) });
19405
19678
  });
19406
19679
  }
@@ -19514,7 +19787,7 @@ async function main() {
19514
19787
  }
19515
19788
  });
19516
19789
  if (args.task) {
19517
- const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join14(cwd, ".agent", "mcp-auth.json") }));
19790
+ const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join15(cwd, ".agent", "mcp-auth.json") }));
19518
19791
  const agent = await makeAgent(args, ai, cfg, mcpAgentTools(mounted), mounted.map((m) => m.name));
19519
19792
  const store = new SessionStore(cwd);
19520
19793
  const session = startSession(args, store, agent, cwd);