@livx.cc/agentx 0.99.72 → 0.99.74

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/index.js CHANGED
@@ -18,8 +18,54 @@ var init_logging = __esm({
18
18
  }
19
19
  });
20
20
 
21
+ // src/imageWire.ts
22
+ import { readFileSync, statSync } from "fs";
23
+ import { imageWireOf, inlineImageForWire, scrubBase64Text, imageSize, imageNote, IMAGE_MAX_SIDE, IMAGE_MAX_BYTES } from "ai.libx.js";
24
+ function remember(id, w) {
25
+ cache.delete(id);
26
+ cache.set(id, w);
27
+ if (cache.size > CACHE_MAX) cache.delete(cache.keys().next().value);
28
+ return w;
29
+ }
30
+ function imageForWire(path, mime, vision) {
31
+ let st;
32
+ try {
33
+ st = statSync(path);
34
+ } catch (e) {
35
+ log2.warn(`image unreadable: ${path}`, e);
36
+ return null;
37
+ }
38
+ const key = `${st.mtimeMs}:${st.size}`;
39
+ let hit = cache.get(path);
40
+ if (!hit || hit.key !== key) hit = remember(path, { key, ...imageWireOf(readFileSync(path), mime, path, path) });
41
+ return vision && hit.url ? { url: hit.url } : { note: hit.note };
42
+ }
43
+ function imageTokens(dims) {
44
+ return dims ? Math.min(IMAGE_TOKENS_MAX, Math.max(1, Math.ceil(dims.w * dims.h / 750))) : IMAGE_TOKENS_MAX;
45
+ }
46
+ function wiredImageWeight(r) {
47
+ if ("note" in r) return r.note.length;
48
+ const b64 = r.url.slice(r.url.indexOf(",") + 1);
49
+ return 4 * imageTokens(imageSize(Buffer.from(b64.slice(0, 4096), "base64")));
50
+ }
51
+ function imageWireWeight(path, mime, vision) {
52
+ const r = imageForWire(path, mime, vision);
53
+ return r ? wiredImageWeight(r) : 0;
54
+ }
55
+ var log2, cache, CACHE_MAX, IMAGE_TOKENS_MAX;
56
+ var init_imageWire = __esm({
57
+ "src/imageWire.ts"() {
58
+ "use strict";
59
+ init_logging();
60
+ log2 = forComponent("imageWire");
61
+ cache = /* @__PURE__ */ new Map();
62
+ CACHE_MAX = 8;
63
+ IMAGE_TOKENS_MAX = 1600;
64
+ }
65
+ });
66
+
21
67
  // src/llm.ts
22
- import { readFileSync, statSync, realpathSync } from "fs";
68
+ import { statSync as statSync2, realpathSync } from "fs";
23
69
  import { resolve, sep } from "path";
24
70
  import { randomBytes } from "crypto";
25
71
  function passesBackReasoning(model) {
@@ -76,9 +122,9 @@ function mintImageRef(path, mime) {
76
122
  const token = randomBytes(16).toString("hex");
77
123
  let bytes = 0;
78
124
  try {
79
- bytes = Math.ceil(statSync(path).size * 4 / 3);
125
+ bytes = Math.ceil(statSync2(path).size * 4 / 3);
80
126
  } catch (e) {
81
- log2.warn(`image ref stat failed, weighing as 0: ${path}`, e);
127
+ log3.warn(`image ref stat failed, weighing as 0: ${path}`, e);
82
128
  }
83
129
  refRegistry.set(token, { mime, path, bytes });
84
130
  while (refRegistry.size > REF_REGISTRY_MAX) refRegistry.delete(refRegistry.keys().next().value);
@@ -94,7 +140,7 @@ function parseImageRef(url) {
94
140
  const parts = refUrlParts(url);
95
141
  const ref = parts && refRegistry.get(parts.token);
96
142
  if (!ref) {
97
- log2.debug("image ref not in registry \u2014 treating as non-image text");
143
+ log3.debug("image ref not in registry \u2014 treating as non-image text");
98
144
  return null;
99
145
  }
100
146
  return ref;
@@ -113,7 +159,7 @@ function remintImageRefs(messages, roots) {
113
159
  try {
114
160
  allowed.push(realpathSync(resolve(r)));
115
161
  } catch (e) {
116
- log2.debug(`image-ref root unavailable: ${r}`, e);
162
+ log3.debug(`image-ref root unavailable: ${r}`, e);
117
163
  }
118
164
  }
119
165
  if (allowed.length === 0) return messages;
@@ -130,11 +176,11 @@ function remintImageRefs(messages, roots) {
130
176
  try {
131
177
  real = realpathSync(f.path);
132
178
  } catch (e) {
133
- log2.warn(`resumed image ref unreadable, will stub: ${f.path}`, e);
179
+ log3.warn(`resumed image ref unreadable, will stub: ${f.path}`, e);
134
180
  return p;
135
181
  }
136
182
  if (!inside(real)) {
137
- log2.warn(`resumed image ref outside the allowed roots, will stub: ${f.path} -> ${real}`);
183
+ log3.warn(`resumed image ref outside the allowed roots, will stub: ${f.path} -> ${real}`);
138
184
  return p;
139
185
  }
140
186
  touched = true;
@@ -161,7 +207,7 @@ function toolImageRef(content) {
161
207
  try {
162
208
  return parseImageRef(JSON.parse(content).imageRef);
163
209
  } catch (e) {
164
- log2.debug("bad imageRef tool result", e);
210
+ log3.debug("bad imageRef tool result", e);
165
211
  return null;
166
212
  }
167
213
  }
@@ -189,32 +235,41 @@ function imageWindows(messages, keepRecent) {
189
235
  const k = Math.max(0, keepRecent);
190
236
  return /* @__PURE__ */ new Set([...attach.slice(Math.max(0, attach.length - k)), ...tool.slice(Math.max(0, tool.length - k))]);
191
237
  }
192
- function expandImagesForSend(messages, keepRecent = 1, stub = IMAGE_ELIDE_STUB) {
238
+ function expandImagesForSend(messages, keepRecent = 1, stub = IMAGE_ELIDE_STUB, vision = true) {
193
239
  if (!messages.some(bearsImage)) return messages;
194
240
  const keep = imageWindows(messages, keepRecent);
195
241
  let changed = false;
196
242
  const out = messages.map((m, i) => {
197
- const content = keep.has(i) ? expandContent(m.content, stub) : stripContent(m.content, stub);
243
+ const content = keep.has(i) ? expandContent(m.content, vision) : stripContent(m.content, stub);
198
244
  if (content === m.content) return m;
199
245
  changed = true;
200
246
  return { ...m, content };
201
247
  });
202
248
  return changed ? out : messages;
203
249
  }
204
- function sendBytes(messages, keepRecent = 1, stub = IMAGE_ELIDE_STUB) {
250
+ function sendBytes(messages, keepRecent = 1, vision = true, stub = IMAGE_ELIDE_STUB) {
205
251
  let n = 0;
206
- for (const b of sendBytesPer(messages, keepRecent, stub)) n += b;
252
+ for (const b of sendBytesPer(messages, keepRecent, vision, stub)) n += b;
207
253
  return n;
208
254
  }
209
- function sendBytesPer(messages, keepRecent = 1, stub = IMAGE_ELIDE_STUB) {
255
+ function sendBytesPer(messages, keepRecent = 1, vision = true, stub = IMAGE_ELIDE_STUB) {
210
256
  const keep = messages.some(bearsImage) ? imageWindows(messages, keepRecent) : null;
211
- return messages.map((m, i) => wireBytes(m.content, keep == null || keep.has(i), stub) + (m.tool_calls ? JSON.stringify(m.tool_calls).length : 0) + (m.reasoning_content?.length ?? 0));
257
+ return messages.map((m, i) => wireBytes(m.content, keep == null || keep.has(i), vision, stub) + (m.tool_calls ? JSON.stringify(m.tool_calls).length : 0) + (m.reasoning_content?.length ?? 0));
212
258
  }
213
- function wireBytes(content, kept, stub) {
259
+ function wireBytes(content, kept, vision, stub) {
214
260
  const tool = toolImageRef(content);
215
- if (tool) return kept ? tool.bytes : stub.length;
261
+ if (tool) return kept ? imageWireWeight(tool.path, tool.mime, vision) || stub.length : stub.length;
216
262
  if (content == null || typeof content === "string") return contentBytes(content);
217
- if (kept) return contentBytes(content);
263
+ if (kept) {
264
+ let n2 = 0;
265
+ for (const p of content) {
266
+ const url = p.type === "image_url" ? p.image_url?.url ?? "" : "";
267
+ const ref = url ? parseImageRef(url) : null;
268
+ const inline = url.startsWith("data:") ? inlineImageForWire(url, vision) : null;
269
+ n2 += ref ? imageWireWeight(ref.path, ref.mime, vision) : inline ? wiredImageWeight(inline) : contentBytes([p]);
270
+ }
271
+ return n2;
272
+ }
218
273
  let n = 0;
219
274
  for (const p of content) {
220
275
  if (p.type === "text") {
@@ -226,31 +281,70 @@ function wireBytes(content, kept, stub) {
226
281
  }
227
282
  return n;
228
283
  }
229
- function readRefDataUrl(ref) {
230
- try {
231
- return `data:${ref.mime};base64,${readFileSync(ref.path).toString("base64")}`;
232
- } catch (e) {
233
- log2.warn(`image ref unreadable, sending stub instead: ${ref.path}`, e);
234
- return null;
235
- }
236
- }
237
- function expandContent(content, stub) {
284
+ function expandContent(content, vision) {
238
285
  const tool = toolImageRef(content);
239
286
  if (tool) {
240
- const url = readRefDataUrl(tool);
241
- return url ? JSON.stringify({ dataUrl: url, image: tool.path }) : stub;
287
+ const w = imageForWire(tool.path, tool.mime, vision);
288
+ if (!w) return IMAGE_ELIDE_STUB;
289
+ return "url" in w ? [{ type: "text", text: `image: ${tool.path}` }, imagePart(w.url)] : w.note;
242
290
  }
243
291
  if (typeof content === "string") return content;
244
292
  let changed = false;
245
293
  const out = content.map((p) => {
246
- if (p.type !== "image_url" || !isImageRefUrl(p.image_url?.url)) return p;
294
+ if (p.type !== "image_url") return p;
295
+ const url = p.image_url?.url ?? "";
296
+ if (url.startsWith("data:")) {
297
+ const w2 = inlineImageForWire(url, vision);
298
+ if ("url" in w2 && w2.url === url) return p;
299
+ changed = true;
300
+ return "url" in w2 ? imagePart(w2.url) : { type: "text", text: w2.note };
301
+ }
302
+ if (!isImageRefUrl(url)) return p;
247
303
  changed = true;
248
- const ref = parseImageRef(p.image_url?.url);
249
- const url = ref && readRefDataUrl(ref);
250
- return url ? imagePart(url) : { type: "text", text: stub };
304
+ const ref = parseImageRef(url);
305
+ if (!ref) return { type: "text", text: IMAGE_ELIDE_STUB };
306
+ const w = imageForWire(ref.path, ref.mime, vision);
307
+ if (!w) return { type: "text", text: IMAGE_ELIDE_STUB };
308
+ return "url" in w ? imagePart(w.url) : { type: "text", text: w.note };
251
309
  });
252
310
  return changed ? out : content;
253
311
  }
312
+ function hoistToolImages(messages) {
313
+ if (!messages.some((m) => m.role === "tool" && Array.isArray(m.content) && m.content.some((p) => p.type === "image_url"))) return messages;
314
+ const out = [];
315
+ let pending = [];
316
+ const flush = () => {
317
+ if (pending.length) out.push({ role: "user", content: [{ type: "text", text: "Image output from the tool call above:" }, ...pending] });
318
+ pending = [];
319
+ };
320
+ for (const m of messages) {
321
+ if (m.role !== "tool") flush();
322
+ if (m.role === "tool" && Array.isArray(m.content) && m.content.some((p) => p.type === "image_url")) {
323
+ const imgs = m.content.filter((p) => p.type === "image_url");
324
+ pending.push(...imgs);
325
+ const text = m.content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
326
+ out.push({ ...m, content: text || `[${imgs.length} image(s) returned; shown below]` });
327
+ } else out.push(m);
328
+ }
329
+ flush();
330
+ return out;
331
+ }
332
+ function scrubBase64(messages) {
333
+ let changed = false;
334
+ const out = messages.map((m) => {
335
+ const c = m.content;
336
+ let next = c;
337
+ if (typeof c === "string") next = scrubBase64Text(c);
338
+ else if (Array.isArray(c)) {
339
+ const parts = c.map((p) => p.type === "text" && p.text ? { ...p, text: scrubBase64Text(p.text) } : p);
340
+ if (parts.some((p, i) => p.text !== c[i].text)) next = parts;
341
+ }
342
+ if (next === c) return m;
343
+ changed = true;
344
+ return { ...m, content: next };
345
+ });
346
+ return changed ? out : messages;
347
+ }
254
348
  function stripContent(content, stub) {
255
349
  if (toolImageRef(content)) return stub;
256
350
  if (typeof content === "string") return content;
@@ -273,12 +367,13 @@ function endsOnAnnouncedAction(text) {
273
367
  if (PROGRESSIVE.test(last)) return true;
274
368
  return clauses(last).some((c) => !NEXT_STEP.test(c) && !SIGN_OFF.test(c) && COMMITMENT.test(c));
275
369
  }
276
- var log2, p_sep, IMAGE_ELIDE_STUB, IMAGE_REF_SCHEME, REF_REGISTRY_MAX, refRegistry, isImageRefUrl, bearsImage, COMMITMENT, PROGRESSIVE, NEXT_STEP, SIGN_OFF;
370
+ var log3, p_sep, IMAGE_ELIDE_STUB, IMAGE_REF_SCHEME, REF_REGISTRY_MAX, refRegistry, isImageRefUrl, bearsImage, COMMITMENT, PROGRESSIVE, NEXT_STEP, SIGN_OFF;
277
371
  var init_llm = __esm({
278
372
  "src/llm.ts"() {
279
373
  "use strict";
280
374
  init_logging();
281
- log2 = forComponent("llm");
375
+ init_imageWire();
376
+ log3 = forComponent("llm");
282
377
  p_sep = (parts) => parts.length > 1 ? "\n" : "";
283
378
  IMAGE_ELIDE_STUB = "[image elided \u2014 re-attach @path or /paste to view again]";
284
379
  IMAGE_REF_SCHEME = "agentx-image:";
@@ -965,7 +1060,7 @@ function makeWebFetchTool(options = {}) {
965
1060
 
966
1061
  ${capped}`;
967
1062
  } catch (e) {
968
- log3.debug(`WebFetch ${u} failed`, e);
1063
+ log4.debug(`WebFetch ${u} failed`, e);
969
1064
  return `Error fetching ${u}: ${e?.name === "AbortError" ? `timed out after ${timeoutMs}ms` : e?.message ?? e}`;
970
1065
  } finally {
971
1066
  clearTimeout(timer);
@@ -1089,7 +1184,7 @@ function makeWebSearchTool(options = {}) {
1089
1184
  if (!res.ok) return `Error: search returned ${res.status} ${res.statusText}`;
1090
1185
  return formatHits(parseDdgHtml(await res.text(), maxResults));
1091
1186
  } catch (e) {
1092
- log3.debug("WebSearch failed", e);
1187
+ log4.debug("WebSearch failed", e);
1093
1188
  return `Error searching: ${e?.name === "AbortError" ? `timed out after ${timeoutMs}ms` : e?.message ?? e}`;
1094
1189
  } finally {
1095
1190
  clearTimeout(timer);
@@ -1100,12 +1195,12 @@ function makeWebSearchTool(options = {}) {
1100
1195
  function makeWebSearchAnthropicTool(opts = {}) {
1101
1196
  return makeWebSearchTool({ provider: "anthropic", name: "WebSearchAnthropic", description: ANTHROPIC_SEARCH_DESC, anthropicApiKey: opts.anthropicApiKey, model: opts.model });
1102
1197
  }
1103
- var log3, _dnsLookup, webFetchTool, webSearchTool, ANTHROPIC_SEARCH_DESC, webSearchAnthropicTool;
1198
+ var log4, _dnsLookup, webFetchTool, webSearchTool, ANTHROPIC_SEARCH_DESC, webSearchAnthropicTool;
1104
1199
  var init_tools_web = __esm({
1105
1200
  "src/tools.web.ts"() {
1106
1201
  "use strict";
1107
1202
  init_logging();
1108
- log3 = forComponent("web");
1203
+ log4 = forComponent("web");
1109
1204
  webFetchTool = makeWebFetchTool();
1110
1205
  webSearchTool = makeWebSearchTool();
1111
1206
  ANTHROPIC_SEARCH_DESC = "High-quality web search via Anthropic's native search index. Returns concise, sourced findings \u2014 one claim per line, each with its source URL. Prefer this over WebSearch when accuracy and citations matter; it is slower (~3\u20138s) and bills the Anthropic API account per call (one cheap-model turn + search fee).";
@@ -1325,7 +1420,7 @@ function realPathOf(fs, path) {
1325
1420
  try {
1326
1421
  return fs.realPath(path);
1327
1422
  } catch (e) {
1328
- log4.debug(`realPath unavailable for ${path} \u2014 inlining image bytes instead`, e);
1423
+ log5.debug(`realPath unavailable for ${path} \u2014 inlining image bytes instead`, e);
1329
1424
  return null;
1330
1425
  }
1331
1426
  }
@@ -1377,7 +1472,7 @@ function toolsByName(names, available) {
1377
1472
  return t;
1378
1473
  });
1379
1474
  }
1380
- var log4, numberLines, bashTool, IMG_MIME, readTool, editTool;
1475
+ var log5, numberLines, bashTool, IMG_MIME, readTool, editTool;
1381
1476
  var init_tools = __esm({
1382
1477
  "src/tools.ts"() {
1383
1478
  "use strict";
@@ -1388,7 +1483,7 @@ var init_tools = __esm({
1388
1483
  init_tools_web();
1389
1484
  init_OverlayFilesystem();
1390
1485
  init_redact();
1391
- log4 = forComponent("tools");
1486
+ log5 = forComponent("tools");
1392
1487
  numberLines = (content, offset = 0, limit) => {
1393
1488
  const lines = content.split("\n");
1394
1489
  const start = Math.max(0, offset);
@@ -1570,12 +1665,12 @@ ${tail || "(no output)"}`;
1570
1665
  }
1571
1666
  ];
1572
1667
  }
1573
- var log5, JobRegistryOptions, secs, JobRegistry, SandboxJobRegistry, NO_JOB, describe;
1668
+ var log6, JobRegistryOptions, secs, JobRegistry, SandboxJobRegistry, NO_JOB, describe;
1574
1669
  var init_tools_jobs = __esm({
1575
1670
  "src/tools.jobs.ts"() {
1576
1671
  "use strict";
1577
1672
  init_logging();
1578
- log5 = forComponent("jobs");
1673
+ log6 = forComponent("jobs");
1579
1674
  JobRegistryOptions = class {
1580
1675
  /** Tail buffer cap per job (chars). */
1581
1676
  maxBuffer = 256 * 1024;
@@ -1754,7 +1849,7 @@ var init_tools_jobs = __esm({
1754
1849
  try {
1755
1850
  onExit({ id: job.id, kind: job.kind, label: job.label, status, reason: job.reason, tail: job.buf.slice(-4e3), result });
1756
1851
  } catch (e) {
1757
- log5.warn(`job onExit handler threw for ${job.id}: ${e}`);
1852
+ log6.warn(`job onExit handler threw for ${job.id}: ${e}`);
1758
1853
  }
1759
1854
  }
1760
1855
  }
@@ -1808,7 +1903,7 @@ var init_tools_jobs = __esm({
1808
1903
  try {
1809
1904
  j.onKill?.(reason);
1810
1905
  } catch (e) {
1811
- log5.warn(`onKill for ${id} threw: ${e}`);
1906
+ log6.warn(`onKill for ${id} threw: ${e}`);
1812
1907
  }
1813
1908
  }
1814
1909
  return true;
@@ -1855,7 +1950,7 @@ var init_tools_jobs = __esm({
1855
1950
  try {
1856
1951
  cpu = await j.cpuTime();
1857
1952
  } catch (e) {
1858
- log5.debug(`cpu probe for ${j.id} failed: ${e}`);
1953
+ log6.debug(`cpu probe for ${j.id} failed: ${e}`);
1859
1954
  }
1860
1955
  if (cpu != null && j.lastCpu != null && cpu > j.lastCpu) j.lastActivityAt = this.options.now();
1861
1956
  const prev = j.lastCpu;
@@ -1863,7 +1958,7 @@ var init_tools_jobs = __esm({
1863
1958
  const verdict = this.verdict(j, now3);
1864
1959
  if (!verdict?.idle || j.status !== "running") continue;
1865
1960
  if (cpu == null) {
1866
- log5.debug(`not reaping ${j.id}: ${verdict.reason} but CPU unknown`);
1961
+ log6.debug(`not reaping ${j.id}: ${verdict.reason} but CPU unknown`);
1867
1962
  continue;
1868
1963
  }
1869
1964
  if (prev == null) continue;
@@ -1877,7 +1972,7 @@ var init_tools_jobs = __esm({
1877
1972
  return null;
1878
1973
  }
1879
1974
  stuck(j, reason, now3) {
1880
- log5.warn(`reaping ${j.id} (${j.kind} "${j.label}"): ${reason} \u2014 age ${secs(now3 - j.startedAt)}, last activity ${secs(now3 - j.lastActivityAt)} ago, idleMs=${j.idleMs}, hardCapMs=${j.hardCapMs}, ${j.bytes} byte(s) output`);
1975
+ log6.warn(`reaping ${j.id} (${j.kind} "${j.label}"): ${reason} \u2014 age ${secs(now3 - j.startedAt)}, last activity ${secs(now3 - j.lastActivityAt)} ago, idleMs=${j.idleMs}, hardCapMs=${j.hardCapMs}, ${j.bytes} byte(s) output`);
1881
1976
  if (j.onKill) j.notify = true;
1882
1977
  this.kill(j.id, reason);
1883
1978
  }
@@ -1891,7 +1986,7 @@ var init_tools_jobs = __esm({
1891
1986
  this.timer = void 0;
1892
1987
  return;
1893
1988
  }
1894
- void this.sweep().catch((e) => log5.warn(`job sweep failed: ${e}`));
1989
+ void this.sweep().catch((e) => log6.warn(`job sweep failed: ${e}`));
1895
1990
  }, this.options.sweepMs);
1896
1991
  this.timer.unref?.();
1897
1992
  }
@@ -2402,7 +2497,7 @@ async function psGroupCpuSeconds(pgid) {
2402
2497
  const out = await new Promise((res, rej) => execFile("ps", ["-A", "-o", "pid=,ppid=,pgid=,time="], { timeout: 5e3, env: { ...process.env, LC_ALL: "C" } }, (e, so) => e ? rej(e) : res(String(so))));
2403
2498
  return sumTreeCpu(out, pgid);
2404
2499
  } catch (e) {
2405
- log8.debug(`ps cpu probe failed for group ${pgid}: ${e}`);
2500
+ log9.debug(`ps cpu probe failed for group ${pgid}: ${e}`);
2406
2501
  return void 0;
2407
2502
  }
2408
2503
  }
@@ -2550,7 +2645,7 @@ function makeRealShellTool(options) {
2550
2645
  proc.stderr?.on("data", collect);
2551
2646
  proc.on("error", (err) => {
2552
2647
  if (err?.name === "AbortError" || ctl.signal.aborted) return finish(reasonFor(timedOut, timeoutMs, clean(out)));
2553
- log8.debug("shell spawn error", err);
2648
+ log9.debug("shell spawn error", err);
2554
2649
  finish(`[exit 1] ${err?.message ?? err}${out ? "\n" + clean(out) : ""}`);
2555
2650
  });
2556
2651
  proc.on("close", (code) => {
@@ -2623,7 +2718,7 @@ ${clean(out) || "(no output yet)"}`;
2623
2718
  }
2624
2719
  ];
2625
2720
  }
2626
- var log8, clean, DETACHED, SECRET_ENV_RE, _spawn, ShellJobRegistry, shellStatus, NO_JOB2;
2721
+ var log9, clean, DETACHED, SECRET_ENV_RE, _spawn, ShellJobRegistry, shellStatus, NO_JOB2;
2627
2722
  var init_tools_shell = __esm({
2628
2723
  "src/tools.shell.ts"() {
2629
2724
  "use strict";
@@ -2632,7 +2727,7 @@ var init_tools_shell = __esm({
2632
2727
  init_logging();
2633
2728
  init_shell_sandbox();
2634
2729
  init_tools_jobs();
2635
- log8 = forComponent("shell");
2730
+ log9 = forComponent("shell");
2636
2731
  clean = (s) => truncateOutput(redactSecrets(s.replace(/\n+$/, "")));
2637
2732
  DETACHED = { stdio: ["ignore", "pipe", "pipe"], detached: true };
2638
2733
  SECRET_ENV_RE = /(_API_KEY|_TOKEN|_SECRET|_PASSWORD|_PRIVATE_KEY|^AWS_|^GITHUB_TOKEN$|^OPENAI_|^ANTHROPIC_|^GOOGLE_|^GEMINI_|^GROQ_|^NPM_TOKEN$)/i;
@@ -3608,7 +3703,7 @@ init_OverlayFilesystem();
3608
3703
 
3609
3704
  // src/worktree.ts
3610
3705
  import { spawnSync } from "child_process";
3611
- import { symlinkSync, mkdirSync, statSync as statSync2, readFileSync as readFileSync2, rmSync } from "fs";
3706
+ import { symlinkSync, mkdirSync, statSync as statSync3, readFileSync as readFileSync3, rmSync } from "fs";
3612
3707
  import { join } from "path";
3613
3708
  var VALID_SEGMENT = /^[a-zA-Z0-9._-]+$/;
3614
3709
  var MAX_SLUG_LENGTH = 64;
@@ -3647,16 +3742,16 @@ function findGitRoot(from) {
3647
3742
  }
3648
3743
  function readWorktreeHead(worktreePath) {
3649
3744
  try {
3650
- const gitFile = readFileSync2(join(worktreePath, ".git"), "utf-8").trim();
3745
+ const gitFile = readFileSync3(join(worktreePath, ".git"), "utf-8").trim();
3651
3746
  const m = gitFile.match(/^gitdir:\s*(.+)$/);
3652
3747
  if (!m) return null;
3653
3748
  const headPath = join(m[1], "HEAD");
3654
- const head = readFileSync2(headPath, "utf-8").trim();
3749
+ const head = readFileSync3(headPath, "utf-8").trim();
3655
3750
  const refMatch = head.match(/^ref:\s*(.+)$/);
3656
3751
  if (!refMatch) return head.length === 40 ? head : null;
3657
3752
  const refPath = join(m[1], "..", refMatch[1]);
3658
3753
  try {
3659
- return readFileSync2(refPath, "utf-8").trim();
3754
+ return readFileSync3(refPath, "utf-8").trim();
3660
3755
  } catch {
3661
3756
  }
3662
3757
  const r = git(["rev-parse", "HEAD"], worktreePath);
@@ -3692,7 +3787,7 @@ function symlinkDirs(repoRoot, worktreePath, dirs) {
3692
3787
  const src = join(repoRoot, dir);
3693
3788
  const dst = join(worktreePath, dir);
3694
3789
  try {
3695
- statSync2(src);
3790
+ statSync3(src);
3696
3791
  } catch {
3697
3792
  continue;
3698
3793
  }
@@ -3708,7 +3803,7 @@ function cleanupWorktree(repoRoot, slug2, opts) {
3708
3803
  const worktreePath = worktreePathFor(repoRoot, slug2);
3709
3804
  const branch = worktreeBranchName(slug2);
3710
3805
  try {
3711
- statSync2(worktreePath);
3806
+ statSync3(worktreePath);
3712
3807
  } catch {
3713
3808
  return { removed: false, reason: "not found" };
3714
3809
  }
@@ -3758,7 +3853,7 @@ function exitWorktree() {
3758
3853
  // src/subagent.ts
3759
3854
  init_NodeDiskFilesystem();
3760
3855
  init_logging();
3761
- var log6 = forComponent("subagent");
3856
+ var log7 = forComponent("subagent");
3762
3857
  async function boundedPool(items, limit, fn) {
3763
3858
  const out = new Array(items.length);
3764
3859
  let next = 0;
@@ -3832,7 +3927,7 @@ function traceFileFor(dir, label, depth) {
3832
3927
  const id = `${Date.now().toString(36)}-${(traceSeq++).toString(36)}`;
3833
3928
  return join3(dir, `${id}-d${depth}-${sanitizeSlug(label) || "sub"}.json`);
3834
3929
  } catch (e) {
3835
- log6.warn(`subagent trace dir unavailable (${dir}) \u2014 running without a trace: ${e instanceof Error ? e.message : String(e)}`);
3930
+ log7.warn(`subagent trace dir unavailable (${dir}) \u2014 running without a trace: ${e instanceof Error ? e.message : String(e)}`);
3836
3931
  return void 0;
3837
3932
  }
3838
3933
  }
@@ -3840,7 +3935,7 @@ function flushTrace(file, meta, messages, status) {
3840
3935
  try {
3841
3936
  writeFileSync(file, JSON.stringify({ ...meta, status, updated: Date.now(), messages }, null, 2));
3842
3937
  } catch (e) {
3843
- log6.warn(`subagent trace flush failed (${file}): ${e instanceof Error ? e.message : String(e)}`);
3938
+ log7.warn(`subagent trace flush failed (${file}): ${e instanceof Error ? e.message : String(e)}`);
3844
3939
  }
3845
3940
  }
3846
3941
  function installTrace(child, file, meta) {
@@ -3881,7 +3976,7 @@ async function runChildTraced(args) {
3881
3976
  if (file) flushTrace(file, meta, child.transcript, wc.timedOut() ? "deadline" : res.finishReason);
3882
3977
  let result = res.finishReason === "stop" ? res.text || `(child '${label}' finished with no summary; finishReason=${res.finishReason})` : abortHint(label, res, file);
3883
3978
  if (wc.timedOut()) {
3884
- log6.warn(`sub-task '${label}' hit its ${deadlineMs}ms wall-clock deadline (steps=${res.steps})`);
3979
+ log7.warn(`sub-task '${label}' hit its ${deadlineMs}ms wall-clock deadline (steps=${res.steps})`);
3885
3980
  result += deadlineNote(deadlineMs);
3886
3981
  }
3887
3982
  await opts.hooks?.onSubagentStop?.(result, { label, agentType });
@@ -4210,7 +4305,7 @@ function reasoningToChatFragment(model, effort) {
4210
4305
  }
4211
4306
 
4212
4307
  // src/Agent.ts
4213
- var log7 = forComponent("Agent");
4308
+ var log8 = forComponent("Agent");
4214
4309
  function envInt(key, fallback) {
4215
4310
  const n = typeof process !== "undefined" ? Number(process.env?.[key]) : NaN;
4216
4311
  return Number.isFinite(n) && n >= 0 ? n : fallback;
@@ -4291,6 +4386,9 @@ var AgentOptions = class {
4291
4386
  * `maxTokens`: that is the cumulative SPEND kill-switch, and a spend budget must never be able to
4292
4387
  * drive destructive compaction (`--max-tokens 50000` would otherwise shred the transcript). */
4293
4388
  contextWindow = 0;
4389
+ /** Does a model take image input? true/false from the host's model catalog; undefined = unknown (images are tried,
4390
+ * and turned off for the run when the adapter refuses them). Unset = always try. */
4391
+ imageInput;
4294
4392
  /** Pagination ceiling for a SINGLE tool result (bytes). A result over this is cropped to page 1 with
4295
4393
  * a marker telling the model it was cropped (refine the query, or page further). Guards against one
4296
4394
  * Grep/Read/MCP call blowing the whole context window. 0 = off. Default 60k (~15k tokens). */
@@ -4590,7 +4688,7 @@ var Agent = class _Agent {
4590
4688
  const disk = new NodeDiskFilesystem2(process.cwd());
4591
4689
  await disk.init();
4592
4690
  this.options.fs = new JailedFilesystem2(disk);
4593
- log7.info(`no fs provided \u2014 defaulting to jailed real disk at ${process.cwd()}`);
4691
+ log8.info(`no fs provided \u2014 defaulting to jailed real disk at ${process.cwd()}`);
4594
4692
  }
4595
4693
  this.buildCtx();
4596
4694
  }
@@ -4735,7 +4833,8 @@ var Agent = class _Agent {
4735
4833
  const cap = _Agent.contextCap(this.options);
4736
4834
  const target = Math.round(cap * _Agent.COMPACT_TARGET);
4737
4835
  const kr = this.options.keepRecentImages ?? 1;
4738
- const tokensBefore = estimateTokens(this.transcript, kr);
4836
+ const vis = this.sendsImages();
4837
+ const tokensBefore = estimateTokens(this.transcript, kr, vis);
4739
4838
  const msgsBefore = this.transcript.length;
4740
4839
  let max = Math.max(2, maxMessages);
4741
4840
  if (this.transcript.length <= max && tokensBefore <= target)
@@ -4743,18 +4842,18 @@ var Agent = class _Agent {
4743
4842
  void this.activeHooks?.onPreCompact?.(this.transcript);
4744
4843
  let out = this.transcript;
4745
4844
  if (out.length > max) out = compact(out, max, focus);
4746
- if (estimateTokens(out, kr) > target) out = elideOversizedImages(out, target, true, kr);
4747
- if (estimateTokens(out, kr) > target) out = capOversizedBodies(out, target, kr);
4748
- if (estimateTokens(out, kr) > target) out = elideOversizedImages(out, target, false, kr);
4749
- while (estimateTokens(out, kr) > target && max > 2 && out.length > 2) {
4845
+ if (estimateTokens(out, kr, vis) > target) out = elideOversizedImages(out, target, true, kr);
4846
+ if (estimateTokens(out, kr, vis) > target) out = capOversizedBodies(out, target, kr);
4847
+ if (estimateTokens(out, kr, vis) > target) out = elideOversizedImages(out, target, false, kr);
4848
+ while (estimateTokens(out, kr, vis) > target && max > 2 && out.length > 2) {
4750
4849
  max = Math.max(2, Math.floor(max / 2));
4751
4850
  out = capOversizedBodies(compact(out, max, focus), target, kr);
4752
4851
  }
4753
- if (estimateTokens(out, kr) > target) out = fitTokenBudget(out, target, kr);
4852
+ if (estimateTokens(out, kr, vis) > target) out = fitTokenBudget(out, target, kr);
4754
4853
  out = dropOrphanToolResults(out);
4755
4854
  this.transcript = out;
4756
4855
  this.releaseStaleImages();
4757
- return { tokensBefore, tokensAfter: estimateTokens(this.transcript, kr), target, cap, removed: msgsBefore - this.transcript.length };
4856
+ return { tokensBefore, tokensAfter: estimateTokens(this.transcript, kr, vis), target, cap, removed: msgsBefore - this.transcript.length };
4758
4857
  }
4759
4858
  /** Drop inline base64 images from older messages so Bun/JSC can reclaim the heap (see contentBytes). */
4760
4859
  releaseStaleImages(keepRecent = 1) {
@@ -4778,7 +4877,7 @@ var Agent = class _Agent {
4778
4877
  let closureNudged = false;
4779
4878
  let lastProgressAt = start;
4780
4879
  const kill = (finishReason) => {
4781
- 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` : ""})`);
4880
+ log8.warn(`kill-switch: ${finishReason} (steps=${steps}, tokens=${usage.totalTokens}, budgetTokens=${Math.round(usage.totalTokens - 0.9 * usage.cacheReadTokens)}, ms=${Date.now() - start - this.parkedMs}${finishReason === "timeout" ? `, idle=${Date.now() - lastProgressAt - this.parkedMs}ms` : ""}${this.parkedMs ? ` +${this.parkedMs} parked` : ""})`);
4782
4881
  this.jobs.killAll(`run ended: ${finishReason}`, true);
4783
4882
  const claims = this.survivingClaims();
4784
4883
  return { text: lastAssistantText(this.transcript), steps, finishReason, messages: this.transcript, usage, usageEstimated, ...claims.length ? { fabricatedToolClaims: claims } : {} };
@@ -4793,7 +4892,7 @@ var Agent = class _Agent {
4793
4892
  steps++;
4794
4893
  this.options.host?.notify?.({ kind: "turn_start", message: `step ${steps}` });
4795
4894
  let res;
4796
- const sent = this.trimContext();
4895
+ let sent = this.trimContext();
4797
4896
  const frag = reasoningToChatFragment(o.model, o.reasoning);
4798
4897
  const sentTools = o.model.startsWith("claude-code/") ? [] : wireTools;
4799
4898
  const isCursorWithTools = o.model.startsWith("cursor/") && wireTools.length > 0;
@@ -4804,9 +4903,9 @@ var Agent = class _Agent {
4804
4903
  this.nativeHoldMs = o.nativeHoldMs;
4805
4904
  if (isCursorWithTools) {
4806
4905
  if (typeof hostToolTimeoutMs === "number" && effectiveToolTimeoutMs !== hostToolTimeoutMs) {
4807
- 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.`);
4906
+ log8.warn(`providerOptions.toolTimeoutMs=${hostToolTimeoutMs}ms is below what the registered tools need \u2014 RAISED to ${effectiveToolTimeoutMs}ms. ${toolTimeoutViolations(this.activeTools, hostToolTimeoutMs).length} tool(s) could outlive it, and the transport would have destroyed their results. To lower it, lower the tools' own maxDurationMs (e.g. the Shell tool's maxTimeoutMs) instead.`);
4808
4907
  }
4809
- for (const v of toolTimeoutViolations(this.activeTools, effectiveToolTimeoutMs)) log7.warn(v);
4908
+ for (const v of toolTimeoutViolations(this.activeTools, effectiveToolTimeoutMs)) log8.warn(v);
4810
4909
  }
4811
4910
  const hostExec = async (name, args) => {
4812
4911
  const tc = { id: `host-${Date.now()}`, type: "function", function: { name, arguments: JSON.stringify(args) } };
@@ -4865,12 +4964,20 @@ var Agent = class _Agent {
4865
4964
  res = r;
4866
4965
  }
4867
4966
  if (steps === 1 && this.isEmptyStop(res) && !this.pendingInjections.length) {
4868
- const e = new Error("model returned an empty turn (finishReason=stop, no text, no tools)");
4967
+ const e = new Error(`model returned an empty turn (finishReason=${res.finishReason ?? "n/a"}, no text, no tools)`);
4869
4968
  e.code = "empty";
4969
+ if (res.finishReason) e.providerFinishReason = res.finishReason;
4870
4970
  throw e;
4871
4971
  }
4872
4972
  break;
4873
4973
  } catch (err) {
4974
+ if (!this.imagesOff && /does not support .*input/i.test(String(err?.message ?? "")) && sent.some((m) => Array.isArray(m.content) && m.role === "tool")) {
4975
+ this.imagesOff = true;
4976
+ log8.warn(`model refused image input (${err?.message}) \u2014 resending with image Reads as text stubs`);
4977
+ sent = this.trimContext();
4978
+ attempt--;
4979
+ continue;
4980
+ }
4874
4981
  const sc = err?.statusCode;
4875
4982
  const serverSide = sc >= 500 || sc === 408 || /Service Unavailable|overloaded|Internal server error|Bad gateway|Gateway time/i.test(String(err?.message ?? ""));
4876
4983
  const network = !sc && /ECONNRESET|ECONNREFUSED|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|EPIPE|socket hang up|fetch failed|network|terminated|UND_ERR/i.test(String(err?.message ?? err?.code ?? err));
@@ -4878,11 +4985,11 @@ var Agent = class _Agent {
4878
4985
  const empty = err?.code === "empty" || err?.code === "cursor_silent";
4879
4986
  const maxAttempts = empty ? Math.max(o.emptyRetries, 0) : 2;
4880
4987
  const replaysTool = stalled && this.toolRanThisAttempt;
4881
- if (replaysTool) log7.warn("model stall after tool(s) already ran this step \u2014 not retrying (a retry would replay them)");
4988
+ if (replaysTool) log8.warn("model stall after tool(s) already ran this step \u2014 not retrying (a retry would replay them)");
4882
4989
  const transient = !o.signal?.aborted && !isAbortError(err) && !replaysTool && attempt < maxAttempts && (network || serverSide || stalled || empty);
4883
4990
  if (!transient) throw err;
4884
4991
  const waitMs = (empty ? o.emptyRetryBaseMs : 1e3) * (attempt + 1);
4885
- log7.warn(`${empty ? "empty model turn" : stalled ? "model stall" : "network drop"} mid-step (${err?.message ?? err}) \u2014 retrying in ${waitMs}ms`);
4992
+ log8.warn(`${empty ? "empty model turn" : stalled ? "model stall" : "network drop"} mid-step (${err?.message ?? err}) \u2014 retrying in ${waitMs}ms`);
4886
4993
  o.host?.notify?.({ kind: "retry", message: `${empty ? "model returned nothing" : stalled ? "model stalled" : "connection dropped"} \u2014 retrying step (#${attempt + 1})` });
4887
4994
  await new Promise((r) => setTimeout(r, waitMs));
4888
4995
  }
@@ -4890,13 +4997,14 @@ var Agent = class _Agent {
4890
4997
  } catch (err) {
4891
4998
  if (err?.code === "budget") return kill("budget");
4892
4999
  if (err?.code === "stall") {
4893
- log7.warn(`model stalled out after retries: ${err?.message ?? err}`);
4894
- return { ...kill("timeout"), error: err };
5000
+ log8.warn(`model stalled out after retries: ${err?.message ?? err}`);
5001
+ return { ...kill("timeout"), providerFinishReason: "none (stalled)", error: err };
4895
5002
  }
4896
5003
  if (o.signal?.aborted || isAbortError(err)) return kill("aborted");
4897
5004
  if (err?.code === "empty" || err?.code === "cursor_silent") {
4898
- log7.warn(`model returned empty/silent turns after retries \u2014 surfacing as failed`);
4899
- return { text: "", steps, finishReason: "empty", messages: this.transcript, usage, usageEstimated, error: err };
5005
+ log8.warn(`model returned empty/silent turns after retries \u2014 surfacing as failed`);
5006
+ const pfr = err?.providerFinishReason;
5007
+ return { text: "", steps, finishReason: "empty", ...pfr ? { providerFinishReason: pfr } : {}, messages: this.transcript, usage, usageEstimated, error: err };
4900
5008
  }
4901
5009
  const body = err?.body ?? err?.response?.data ?? err?.error;
4902
5010
  let bodyStr;
@@ -4907,13 +5015,13 @@ var Agent = class _Agent {
4907
5015
  }
4908
5016
  if (bodyStr && err instanceof Error && !err.message.includes(bodyStr)) err.detail = bodyStr;
4909
5017
  const errLog = err instanceof Error ? { message: err.message, ...err.detail ? { detail: err.detail } : {}, stack: err.stack } : err;
4910
- log7.error(`chat() failed: ${err?.message ?? err}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, errLog);
5018
+ log8.error(`chat() failed: ${err?.message ?? err}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, errLog);
4911
5019
  return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err };
4912
5020
  }
4913
5021
  if (o.signal?.aborted) return kill("aborted");
4914
5022
  if (res.finishReason === "error" && !res.toolCalls?.length) {
4915
5023
  const err = new Error(contentText(res.content ?? "").trim() || "provider error");
4916
- log7.error(`chat() failed: ${err.message}`);
5024
+ log8.error(`chat() failed: ${err.message}`);
4917
5025
  return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err };
4918
5026
  }
4919
5027
  if (!res.usage) {
@@ -4971,21 +5079,21 @@ var Agent = class _Agent {
4971
5079
  if (emptyTurn && o.failEmptyTurns) {
4972
5080
  if (!emptyNudged && canNudge()) {
4973
5081
  emptyNudged = true;
4974
- log7.warn(`empty model turn after tool work (step ${steps}, provider finishReason=${res.finishReason ?? "n/a"}) \u2014 nudging once`);
5082
+ log8.warn(`empty model turn after tool work (step ${steps}, provider finishReason=${res.finishReason ?? "n/a"}) \u2014 nudging once`);
4975
5083
  this.transcript.push({ role: "user", content: "Continue \u2014 you returned nothing. Answer the user now using the tool results above, or keep working if you are not done." });
4976
5084
  continue;
4977
5085
  }
4978
- log7.warn(`model returned empty turns after tool work \u2014 surfacing as failed`);
5086
+ log8.warn(`model returned empty turns after tool work \u2014 surfacing as failed`);
4979
5087
  const e = new Error("empty_response: model returned an empty turn (no text, no tools) after a re-prompt");
4980
5088
  e.code = "empty";
4981
5089
  return { text: "", steps, finishReason: "empty", ...res.finishReason ? { providerFinishReason: res.finishReason } : {}, messages: this.transcript, usage, usageEstimated, error: e };
4982
5090
  }
4983
5091
  const fabricated = o.catchFabricatedToolUse && !closureNudged ? this.fabricatedToolNames(contentText(res.content ?? "")) : [];
4984
5092
  for (const n of fabricated) this.fabricatedClaims.add(n);
4985
- 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`);
5093
+ if (fabricated.length && !canNudge()) log8.warn(`fabricated tool use: text names ${fabricated.join(", ")} but no step/budget left to demand the call \u2014 finishing with the existing answer`);
4986
5094
  if (fabricated.length && canNudge()) {
4987
5095
  closureNudged = true;
4988
- 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`);
5096
+ log8.warn(`fabricated tool use: text names ${fabricated.join(", ")} but the run invoked ${this.invokedTools.size ? [...this.invokedTools].join(", ") : "nothing"} \u2014 demanding the real call`);
4989
5097
  this.transcript.push({
4990
5098
  role: "user",
4991
5099
  content: `You referred to ${fabricated.map((n) => `\`${n}\``).join(", ")} in your reply, but you never actually called ${fabricated.length > 1 ? "those tools" : "that tool"} \u2014 no tool call was made, so any result you reported for ${fabricated.length > 1 ? "them" : "it"} was invented. Either call ${fabricated.length > 1 ? "them" : "it"} for real now, or answer without ${fabricated.length > 1 ? "them" : "it"} and do NOT claim any outcome \u2014 including a failure or a timeout \u2014 for a call you did not make.`
@@ -4994,14 +5102,14 @@ var Agent = class _Agent {
4994
5102
  }
4995
5103
  if (o.closeDelegatedTurns && res.endedWithoutClosing && !closureNudged && canNudge()) {
4996
5104
  closureNudged = true;
4997
- log7.verbose("delegated turn went silent after a tool \u2014 nudging a closing answer");
5105
+ log8.verbose("delegated turn went silent after a tool \u2014 nudging a closing answer");
4998
5106
  this.transcript.push({
4999
5107
  role: "user",
5000
5108
  content: "You ran tools but did not reply. Answer the user now using the tool results above; if a tool failed, say so plainly. Do NOT re-run those tools, do NOT narrate what you did, and do NOT ask for permission to continue."
5001
5109
  });
5002
5110
  continue;
5003
5111
  }
5004
- log7.verbose(`completed in ${steps} step(s)`);
5112
+ log8.verbose(`completed in ${steps} step(s)`);
5005
5113
  const drained = await this.jobs.drain(o.signal);
5006
5114
  if (o.signal?.aborted) return kill("aborted");
5007
5115
  if (drained.length) lastProgressAt = Date.now();
@@ -5035,7 +5143,7 @@ var Agent = class _Agent {
5035
5143
  if (this.ctx.needsInput) {
5036
5144
  const question = this.ctx.needsInput;
5037
5145
  this.ctx.needsInput = void 0;
5038
- log7.warn(`needs_input: ${question.question} (steps=${steps})`);
5146
+ log8.warn(`needs_input: ${question.question} (steps=${steps})`);
5039
5147
  await this.jobs.drain();
5040
5148
  const text = [
5041
5149
  `OPEN QUESTION (run paused \u2014 no human was reachable): ${question.question}`,
@@ -5077,12 +5185,12 @@ var Agent = class _Agent {
5077
5185
  if (this.toolHoldActive()) {
5078
5186
  const now3 = Date.now();
5079
5187
  const who = this.jobs.holding().map((j) => `${j.kind} "${j.label}" (${Math.round((now3 - j.startedAt) / 1e3)}s, quiet ${Math.round((now3 - j.lastActivityAt) / 1e3)}s)`).join(", ");
5080
- log7.warn(`stall watchdog: no stream output for ${stallMs}ms \u2014 held by ${who}`);
5188
+ log8.warn(`stall watchdog: no stream output for ${stallMs}ms \u2014 held by ${who}`);
5081
5189
  this.options.host?.notify?.({ kind: "tool_hold", message: `waiting on ${who}` });
5082
5190
  return poke();
5083
5191
  }
5084
5192
  const inFlight = this.jobs.list().filter((j) => j.status === "running");
5085
- 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(", ")})` : ""));
5193
+ log8.warn(`stall watchdog: no stream output for ${stallMs}ms and no tool holding the stream \u2014 aborting the request` + (inFlight.length ? ` (${inFlight.length} detached job(s) running, not awaited by the stream: ${inFlight.map((j) => `${j.id}/${j.kind}`).join(", ")})` : ""));
5086
5194
  fired = true;
5087
5195
  ac.abort(stallError());
5088
5196
  }, stallMs);
@@ -5252,13 +5360,13 @@ var Agent = class _Agent {
5252
5360
  const decision = await this.park(Promise.resolve(hooks?.preToolUse?.(call, meta)));
5253
5361
  if (decision?.block) {
5254
5362
  const blocked = `Blocked by hook: ${decision.reason ?? "no reason given"}`;
5255
- log7.debug(`${tc.function.name} -> ${blocked}`);
5363
+ log8.debug(`${tc.function.name} -> ${blocked}`);
5256
5364
  await hooks?.postToolUse?.(call, blocked, meta);
5257
5365
  return blocked;
5258
5366
  }
5259
5367
  this.options.host?.notify?.({ kind: "tool_use", id: tc.id ?? "", name: tc.function.name, input: args });
5260
5368
  if (earlyError) {
5261
- log7.debug(`${tc.function.name} -> ${earlyError}`);
5369
+ log8.debug(`${tc.function.name} -> ${earlyError}`);
5262
5370
  await hooks?.postToolUse?.(call, earlyError, meta);
5263
5371
  this.options.host?.notify?.({ kind: "tool_result", id: tc.id ?? "", output: earlyError, isError: true });
5264
5372
  return earlyError;
@@ -5267,12 +5375,12 @@ var Agent = class _Agent {
5267
5375
  let images;
5268
5376
  let threw = false;
5269
5377
  try {
5270
- log7.debug(`${tc.function.name}(${tc.function.arguments})`);
5378
+ log8.debug(`${tc.function.name}(${tc.function.arguments})`);
5271
5379
  this.ctx.emit = hooks?.onToolOutput ? (chunk) => {
5272
5380
  try {
5273
5381
  hooks.onToolOutput(call, chunk, meta);
5274
5382
  } catch (e) {
5275
- log7.debug(`onToolOutput hook error: ${e}`);
5383
+ log8.debug(`onToolOutput hook error: ${e}`);
5276
5384
  }
5277
5385
  } : void 0;
5278
5386
  const raw = await tool.run(args, this.ctx);
@@ -5284,7 +5392,7 @@ var Agent = class _Agent {
5284
5392
  }
5285
5393
  } catch (e) {
5286
5394
  const msg = e instanceof Error ? e.message : String(e);
5287
- log7.debug(`${tc.function.name} -> error: ${msg}`);
5395
+ log8.debug(`${tc.function.name} -> error: ${msg}`);
5288
5396
  result = `Error: ${msg}`;
5289
5397
  threw = true;
5290
5398
  } finally {
@@ -5344,6 +5452,13 @@ ${out}`;
5344
5452
  * 3. token-aware backstop (`maxContextTokens`) — drop oldest messages until under an estimated token ceiling.
5345
5453
  * With every knob off, returns the transcript unchanged (same reference).
5346
5454
  */
5455
+ /** Set when the model refused image parts this run: tool image Reads go as text stubs from then on. */
5456
+ imagesOff = false;
5457
+ /** Send tool image Reads as images? Known models by their catalog flag; unknown ones try (and latch off on refusal). */
5458
+ sendsImages() {
5459
+ if (this.imagesOff) return false;
5460
+ return this.options.imageInput?.(this.options.model) ?? true;
5461
+ }
5347
5462
  trimContext() {
5348
5463
  const o = this.options;
5349
5464
  const m = this.transcript;
@@ -5353,17 +5468,19 @@ ${out}`;
5353
5468
  const browserKeep = this.browserActive ? o.browserTrim?.keepOutputs : void 0;
5354
5469
  const keep = browserKeep != null ? o.keepToolOutputs ? Math.min(o.keepToolOutputs, browserKeep) : browserKeep : o.keepToolOutputs;
5355
5470
  if (keep) out = stubOldToolResults(out ?? m, keep);
5356
- out = expandImagesForSend(dropOrphanToolResults(out ?? m), o.keepRecentImages ?? 1);
5471
+ out = expandImagesForSend(dropOrphanToolResults(out ?? m), o.keepRecentImages ?? 1, void 0, this.sendsImages());
5357
5472
  if (o.maxContextTokens) {
5358
5473
  const pre = out.length;
5359
- out = fitTokenBudget(out, o.maxContextTokens, o.keepRecentImages ?? 1);
5474
+ out = fitTokenBudget(out, o.maxContextTokens, o.keepRecentImages ?? 1, this.sendsImages());
5360
5475
  const dropped = pre - out.length;
5361
5476
  if (dropped > 0 && dropped !== this.lastTrimNotified) {
5362
5477
  this.lastTrimNotified = dropped;
5363
5478
  o.host?.notify?.({ kind: "compaction", message: `context full \u2014 auto-trimmed ${dropped} oldest message(s) to fit ~${Math.round(o.maxContextTokens / 1e3)}k tokens` });
5364
5479
  }
5365
5480
  }
5366
- return dropOrphanToolResults(out);
5481
+ out = dropOrphanToolResults(out);
5482
+ if (/^google\//.test(o.model ?? "")) out = hoistToolImages(out);
5483
+ return scrubBase64(out);
5367
5484
  }
5368
5485
  };
5369
5486
  function dropOldest(m, max) {
@@ -5372,8 +5489,8 @@ function dropOldest(m, max) {
5372
5489
  }
5373
5490
  var ANTHROPIC_WINDOW_WITHOUT_1M_BETA = 2e5;
5374
5491
  var isAnthropicModel = (m) => !!m && (m.startsWith("anthropic/") || /(^|\/)claude-/.test(m));
5375
- function estimateTokens(m, keepRecentImages = 1) {
5376
- return Math.ceil(sendBytes(m, keepRecentImages) / 4);
5492
+ function estimateTokens(m, keepRecentImages = 1, vision = true) {
5493
+ return Math.ceil(sendBytes(m, keepRecentImages, vision) / 4);
5377
5494
  }
5378
5495
  var TRANSPORT_TOOL_TIMEOUT_FLOOR_MS = 18e5;
5379
5496
  var isMcpTool = (t) => t.name.startsWith("mcp__") || t.name === "McpCall";
@@ -5542,8 +5659,8 @@ function dropOrphanToolResults(messages) {
5542
5659
  const ok = (m) => m.role !== "tool" || ids.has(m.tool_call_id ?? "");
5543
5660
  return messages.every(ok) ? messages : messages.filter(ok);
5544
5661
  }
5545
- function fitTokenBudget(messages, maxTokens, keepRecentImages = 1) {
5546
- const per = sendBytesPer(messages, keepRecentImages).map((b) => Math.ceil(b / 4));
5662
+ function fitTokenBudget(messages, maxTokens, keepRecentImages = 1, vision = true) {
5663
+ const per = sendBytesPer(messages, keepRecentImages, vision).map((b) => Math.ceil(b / 4));
5547
5664
  let total = per.reduce((a, b) => a + b, 0);
5548
5665
  if (total <= maxTokens) return messages;
5549
5666
  const head = messages[0]?.role === "system" ? [messages[0]] : [];
@@ -5552,7 +5669,7 @@ function fitTokenBudget(messages, maxTokens, keepRecentImages = 1) {
5552
5669
  const ids = callIdSet(messages.slice(from));
5553
5670
  while (from < messages.length && messages[from].role === "tool" && !ids.has(messages[from].tool_call_id ?? "")) total -= per[from++];
5554
5671
  if (total > maxTokens)
5555
- log7.warn(`context ~${total} tok still over maxContextTokens=${maxTokens} after trimming (system head can't be dropped)`);
5672
+ log8.warn(`context ~${total} tok still over maxContextTokens=${maxTokens} after trimming (system head can't be dropped)`);
5556
5673
  return [...head, ...messages.slice(from)];
5557
5674
  }
5558
5675
  function compact(m, max, focus) {
@@ -6235,7 +6352,7 @@ init_tools_web();
6235
6352
  init_tools();
6236
6353
  init_tools_structured();
6237
6354
  init_logging();
6238
- var log9 = forComponent("scratch");
6355
+ var log10 = forComponent("scratch");
6239
6356
  var SCRATCH_DIR = "/scratch";
6240
6357
  function shortArgs(args) {
6241
6358
  try {
@@ -6275,7 +6392,7 @@ var Scratch = class {
6275
6392
  await (this.dirReady ??= mkdirp(this.fs, dir));
6276
6393
  await this.fs.writeFile(path, header + raw);
6277
6394
  } catch (e) {
6278
- log9.debug("scratch write failed; returning raw", e);
6395
+ log10.debug("scratch write failed; returning raw", e);
6279
6396
  return raw;
6280
6397
  }
6281
6398
  const preview = raw.slice(0, previewChars).replace(/\s+/g, " ").trim();
@@ -6305,7 +6422,7 @@ To pull a specific detail, Grep/Read ${path}, or call Ask({ question: "\u2026",
6305
6422
  await (this.dirReady ??= mkdirp(this.fs, dir));
6306
6423
  await this.fs.writeFile(path, header + full);
6307
6424
  } catch (e) {
6308
- log9.debug("scratch spill failed; cropping lossy", e);
6425
+ log10.debug("scratch spill failed; cropping lossy", e);
6309
6426
  return full.slice(0, pageBytes) + `
6310
6427
 
6311
6428
  [output cropped to ${pageBytes} of ${full.length} bytes; full output unavailable (scratch write failed) \u2014 refine your query]`;
@@ -6351,7 +6468,7 @@ Question: ${q}`);
6351
6468
  const answer = (res.text ?? "").trim();
6352
6469
  return answer || "(no answer found in scratch)";
6353
6470
  } catch (e) {
6354
- log9.debug("Ask peek failed", e);
6471
+ log10.debug("Ask peek failed", e);
6355
6472
  return `Error querying scratch: ${e?.message ?? e}`;
6356
6473
  }
6357
6474
  }
@@ -6360,7 +6477,7 @@ Question: ${q}`);
6360
6477
 
6361
6478
  // src/lessons.ts
6362
6479
  init_logging();
6363
- var log10 = forComponent("Lessons");
6480
+ var log11 = forComponent("Lessons");
6364
6481
  var LessonOptionsDefaults = class {
6365
6482
  minRepeats = 2;
6366
6483
  };
@@ -6385,8 +6502,8 @@ function lessonCapture(options) {
6385
6502
  counts.set(lesson.slug, n);
6386
6503
  if (n < o.minRepeats) return;
6387
6504
  written.add(lesson.slug);
6388
- await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) => log10.warn(`could not persist ${lesson.slug}: ${e?.message ?? e}`));
6389
- log10.debug(`captured lesson ${lesson.slug} (recurred ${n}\xD7)`);
6505
+ await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) => log11.warn(`could not persist ${lesson.slug}: ${e?.message ?? e}`));
6506
+ log11.debug(`captured lesson ${lesson.slug} (recurred ${n}\xD7)`);
6390
6507
  }
6391
6508
  };
6392
6509
  }
@@ -6394,7 +6511,7 @@ function lessonCapture(options) {
6394
6511
  // src/reflect.ts
6395
6512
  init_llm();
6396
6513
  init_logging();
6397
- var log11 = forComponent("Reflect");
6514
+ var log12 = forComponent("Reflect");
6398
6515
  async function reflectOnRun(o) {
6399
6516
  const digest = digestRun(o.result.messages, o.maxDigestChars ?? 6e3);
6400
6517
  if (!digest.trim()) return null;
@@ -6410,7 +6527,7 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
6410
6527
  const r = await o.ai.chat({ model: o.model, messages: [{ role: "user", content: prompt }], stream: false });
6411
6528
  text = r?.content ?? "";
6412
6529
  } catch (e) {
6413
- log11.warn(`reflection call failed: ${e?.message ?? e}`);
6530
+ log12.warn(`reflection call failed: ${e?.message ?? e}`);
6414
6531
  return null;
6415
6532
  }
6416
6533
  const m = text.match(/LESSON:\s*(.+)/i);
@@ -6420,10 +6537,10 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
6420
6537
  try {
6421
6538
  await writeFact(o.fs, o.dir, slug2, lesson);
6422
6539
  } catch (e) {
6423
- log11.warn(`could not persist lesson: ${e?.message ?? e}`);
6540
+ log12.warn(`could not persist lesson: ${e?.message ?? e}`);
6424
6541
  return null;
6425
6542
  }
6426
- log11.debug(`reflection persisted ${slug2}`);
6543
+ log12.debug(`reflection persisted ${slug2}`);
6427
6544
  return slug2;
6428
6545
  }
6429
6546
  function digestRun(messages, maxChars) {
@@ -6472,7 +6589,7 @@ function forComponent2(name) {
6472
6589
  }
6473
6590
 
6474
6591
  // node_modules/@bod.ee/voice/src/core/emotion.ts
6475
- var log12 = forComponent2("Emotion");
6592
+ var log13 = forComponent2("Emotion");
6476
6593
  var EMOTIONS = [
6477
6594
  // primary (best results)
6478
6595
  "neutral",
@@ -6626,7 +6743,7 @@ var EmotionStream = class {
6626
6743
  if (tag) {
6627
6744
  this.pending = tag;
6628
6745
  if (this.show) display += m[0];
6629
- } else log12.debug(`dropping unknown emotion tag ${m[0]}`);
6746
+ } else log13.debug(`dropping unknown emotion tag ${m[0]}`);
6630
6747
  last = m.index + m[0].length;
6631
6748
  }
6632
6749
  this.emit(body.slice(last), (s, d, p) => {
@@ -6773,7 +6890,7 @@ function computeHoldMs(ctx) {
6773
6890
  }
6774
6891
 
6775
6892
  // node_modules/@bod.ee/voice/src/core/engine.ts
6776
- var log13 = forComponent2("VoiceEngine");
6893
+ var log14 = forComponent2("VoiceEngine");
6777
6894
  var realClock = {
6778
6895
  now: () => performance.now(),
6779
6896
  setTimeout: (fn, ms) => setTimeout(fn, ms),
@@ -7161,8 +7278,8 @@ var VoiceEngine = class _VoiceEngine {
7161
7278
  this.tts.warmup?.();
7162
7279
  this.setState("listening");
7163
7280
  if (this.options.bargeInMode === "aec" && !this.stt.usingAec)
7164
- log13.warn("bargeInMode=aec but mic does not report echoCancellation applied \u2014 STT may transcribe the assistant");
7165
- log13.debug(`voice I/O up (${this.usingAec ? "AEC" : "heuristic echo"} capture, mode=${this.options.bargeInMode}, mic reports aec=${this.stt.usingAec})`);
7281
+ log14.warn("bargeInMode=aec but mic does not report echoCancellation applied \u2014 STT may transcribe the assistant");
7282
+ log14.debug(`voice I/O up (${this.usingAec ? "AEC" : "heuristic echo"} capture, mode=${this.options.bargeInMode}, mic reports aec=${this.stt.usingAec})`);
7166
7283
  }
7167
7284
  /** The RESOLVED barge-in tier (the single seam every tier branch reads). `bargeInMode` overrides
7168
7285
  * the mic's self-report — see the option doc for why the report is not trustworthy on mobile. */
@@ -7187,7 +7304,7 @@ var VoiceEngine = class _VoiceEngine {
7187
7304
  this.options.onDiag({ t: this.clock.now(), kind, ...fields });
7188
7305
  } catch (e) {
7189
7306
  this.diagOn = false;
7190
- log13.debug(`onDiag threw \u2014 diagnostics disabled: ${e instanceof Error ? e.message : e}`);
7307
+ log14.debug(`onDiag threw \u2014 diagnostics disabled: ${e instanceof Error ? e.message : e}`);
7191
7308
  }
7192
7309
  }
7193
7310
  idleWaiters = [];
@@ -7326,7 +7443,7 @@ var VoiceEngine = class _VoiceEngine {
7326
7443
  }
7327
7444
  for (const w of this.words(this.reply)) this.echoWords.add(w);
7328
7445
  this.tts.speak(forSpeech(speech), true);
7329
- if (!this.spokeDeltas && this.turnStartAt) log13.debug(`ttft: ${Math.round(this.clock.now() - this.turnStartAt)}ms`);
7446
+ if (!this.spokeDeltas && this.turnStartAt) log14.debug(`ttft: ${Math.round(this.clock.now() - this.turnStartAt)}ms`);
7330
7447
  this.spokeDeltas = true;
7331
7448
  this.setState("speaking");
7332
7449
  if (this.options.revealMode !== "off" && display) {
@@ -7439,13 +7556,13 @@ var VoiceEngine = class _VoiceEngine {
7439
7556
  this.drainTimer = null;
7440
7557
  this.speaking = false;
7441
7558
  this.stopWordReveal(true);
7442
- if (this.turnStartAt) log13.debug(`turn: ${Math.round(this.clock.now() - this.turnStartAt)}ms (incl. playback)`);
7559
+ if (this.turnStartAt) log14.debug(`turn: ${Math.round(this.clock.now() - this.turnStartAt)}ms (incl. playback)`);
7443
7560
  this.echoUntil = this.clock.now() + 2500;
7444
7561
  if (!this.usingAec) this.stt.reset();
7445
7562
  try {
7446
7563
  this.options.onSpeechEnd();
7447
7564
  } catch (e) {
7448
- log13.warn(`onSpeechEnd threw: ${e instanceof Error ? e.message : e}`);
7565
+ log14.warn(`onSpeechEnd threw: ${e instanceof Error ? e.message : e}`);
7449
7566
  }
7450
7567
  this.setState("listening");
7451
7568
  if (this.uttQueue.length) this.pumpQueue();
@@ -7519,7 +7636,7 @@ var VoiceEngine = class _VoiceEngine {
7519
7636
  if (!host) return pick;
7520
7637
  const ctx = { text: this.lastDispatchText, question: this.lastDispatchWasQuestion, barged: this.bargedBeforeDispatch };
7521
7638
  const fallback = (e) => {
7522
- log13.warn(`pickAck failed \u2014 using the default pools: ${e instanceof Error ? e.message : e}`);
7639
+ log14.warn(`pickAck failed \u2014 using the default pools: ${e instanceof Error ? e.message : e}`);
7523
7640
  pick.picked = false;
7524
7641
  return void 0;
7525
7642
  };
@@ -7609,7 +7726,7 @@ var VoiceEngine = class _VoiceEngine {
7609
7726
  this.ackProbe = null;
7610
7727
  const queued = this.uttQueue.splice(0);
7611
7728
  const droppedQueued = queued.length;
7612
- if (droppedQueued) log13.info(`barge-in cut ${droppedQueued} queued worker utterance(s) \u2014 handed to the host`);
7729
+ if (droppedQueued) log14.info(`barge-in cut ${droppedQueued} queued worker utterance(s) \u2014 handed to the host`);
7613
7730
  this.overlapKeptUntil = 0;
7614
7731
  if (!this.speaking && !this.drainTimer) {
7615
7732
  if (droppedQueued) this.lastInterrupted = { full: "", heard: "", queued, queuedTurn: false };
@@ -7838,7 +7955,7 @@ var VoiceEngine = class _VoiceEngine {
7838
7955
  if (flat && this.lastDispatchFlat && this.clock.now() - this.lastDispatchAt < _VoiceEngine.DUP_FINAL_MS && (this.lastDispatchFlat.startsWith(flat) || flat.startsWith(this.lastDispatchFlat))) return;
7839
7956
  this.specSpent = true;
7840
7957
  this.specText = this.specPartial;
7841
- log13.debug(`speculate: "${this.specText.slice(0, 60)}"`);
7958
+ log14.debug(`speculate: "${this.specText.slice(0, 60)}"`);
7842
7959
  this.diag("speculate", { text: this.specText });
7843
7960
  this.options.onSpeculate(this.specText);
7844
7961
  }, this.options.speculativeMs);
@@ -7907,7 +8024,7 @@ var VoiceEngine = class _VoiceEngine {
7907
8024
  };
7908
8025
  this.tts.newContext();
7909
8026
  this.tts.speak(phrase, false);
7910
- log13.debug(`backchannel: "${phrase}"`);
8027
+ log14.debug(`backchannel: "${phrase}"`);
7911
8028
  this.diag("backchannel", { phrase });
7912
8029
  this.options.onBackchannel(phrase);
7913
8030
  }
@@ -7940,7 +8057,7 @@ var VoiceEngine = class _VoiceEngine {
7940
8057
  });
7941
8058
  if (idx < 0) return text;
7942
8059
  toks.splice(idx, 1);
7943
- log13.verbose(`stripped backchannel echo "${this.lastBcPhrase}" from dispatching final`);
8060
+ log14.verbose(`stripped backchannel echo "${this.lastBcPhrase}" from dispatching final`);
7944
8061
  return toks.join(" ");
7945
8062
  }
7946
8063
  /** Merge a resumed utterance into the pending one, deduping any word-overlap. Soniox re-finalizes
@@ -8126,7 +8243,7 @@ var VoiceEngine = class _VoiceEngine {
8126
8243
  }
8127
8244
  const bargeHold = !this.looksIncomplete(this.pendingUtt) && this.unfinishedBargeLine(this.pendingUtt);
8128
8245
  if (this.options.incompleteMergeMs && (bargeHold || this.looksIncomplete(this.pendingUtt))) {
8129
- log13.verbose(`hold: incomplete utterance "${this.pendingUtt.slice(-40)}"`);
8246
+ log14.verbose(`hold: incomplete utterance "${this.pendingUtt.slice(-40)}"`);
8130
8247
  this.diag("hold", { reason: bargeHold ? "barge_unfinished" : "incomplete", tail: this.pendingUtt.slice(-40) });
8131
8248
  this.options.onHold();
8132
8249
  if (this.options.holdFiller && !this.speaking) this.speakFiller(this.options.holdFiller);
@@ -8165,13 +8282,13 @@ var VoiceEngine = class _VoiceEngine {
8165
8282
  const answered = this.answeredSinceDispatch();
8166
8283
  const part = flat !== this.lastDispatchFlat && !answered && this.echoesDispatch(text);
8167
8284
  if (part || flat && flat === this.lastDispatchFlat && !answered && this.clock.now() - this.lastDispatchAt < _VoiceEngine.DUP_FINAL_MS) {
8168
- log13.verbose(`dropped duplicate final "${text.slice(0, 40)}" (identical to the just-dispatched utterance, no reply between)`);
8285
+ log14.verbose(`dropped duplicate final "${text.slice(0, 40)}" (identical to the just-dispatched utterance, no reply between)`);
8169
8286
  this.diag("utterance_drop", { reason: "dup_final", text: text.slice(0, 60) });
8170
8287
  return;
8171
8288
  }
8172
8289
  const refinal = this.refinalizes(text);
8173
8290
  if (refinal && this.repliedSinceDispatch) {
8174
- log13.verbose(`dropped re-finalization "${text.slice(0, 60)}" (superset of the just-dispatched utterance, reply already flowing)`);
8291
+ log14.verbose(`dropped re-finalization "${text.slice(0, 60)}" (superset of the just-dispatched utterance, reply already flowing)`);
8175
8292
  this.diag("utterance_drop", { reason: "refinalized", text: text.slice(0, 60) });
8176
8293
  this.lastDispatchFlat = flat;
8177
8294
  this.lastDispatchWords = text.trim().split(/\s+/);
@@ -8450,7 +8567,7 @@ var SpokenSplitter = class {
8450
8567
  };
8451
8568
 
8452
8569
  // node_modules/@bod.ee/voice/src/adapters/soniox.ts
8453
- var log14 = forComponent2("SonioxSTT");
8570
+ var log15 = forComponent2("SonioxSTT");
8454
8571
  var now = () => performance.now();
8455
8572
  var SonioxSTTOptions = class {
8456
8573
  auth = "";
@@ -8495,7 +8612,7 @@ var SonioxSTT = class {
8495
8612
  this.onDiag({ t: now(), kind, ...fields });
8496
8613
  } catch (e) {
8497
8614
  this.diagOn = false;
8498
- log14.debug(`onDiag threw \u2014 STT diagnostics disabled: ${e instanceof Error ? e.message : e}`);
8615
+ log15.debug(`onDiag threw \u2014 STT diagnostics disabled: ${e instanceof Error ? e.message : e}`);
8499
8616
  }
8500
8617
  }
8501
8618
  lastChunkAt = 0;
@@ -8537,11 +8654,11 @@ var SonioxSTT = class {
8537
8654
  this.ws.onmessage = (ev) => this.handle(JSON.parse(String(ev.data)));
8538
8655
  this.ws.onclose = (ev) => {
8539
8656
  if (this.stopped) return;
8540
- log14.warn(`soniox ws closed (${ev.code} ${ev.reason || ""}) \u2014 reconnecting`);
8657
+ log15.warn(`soniox ws closed (${ev.code} ${ev.reason || ""}) \u2014 reconnecting`);
8541
8658
  this.diag("stt_ws_closed", { code: ev.code, reason: String(ev.reason || ""), reconnecting: true });
8542
8659
  this.reset();
8543
8660
  this.connectWs().catch((e) => {
8544
- log14.error(`soniox reconnect failed: ${e.message}`);
8661
+ log15.error(`soniox reconnect failed: ${e.message}`);
8545
8662
  this.diag("stt_reconnect_failed", { message: e.message });
8546
8663
  });
8547
8664
  };
@@ -8553,7 +8670,7 @@ var SonioxSTT = class {
8553
8670
  this.endpointTimer = setInterval(() => {
8554
8671
  const combined = (this.finalText + this.partialText).trim();
8555
8672
  if (!combined || now() - this.lastChangeAt < this.options.silenceEndpointMs) return;
8556
- if (this.firstTokenAt) log14.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192silence-endpoint, "${combined.slice(0, 60)}"`);
8673
+ if (this.firstTokenAt) log15.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192silence-endpoint, "${combined.slice(0, 60)}"`);
8557
8674
  this.reset();
8558
8675
  this.onUtterance(combined, now());
8559
8676
  }, 120);
@@ -8565,7 +8682,7 @@ var SonioxSTT = class {
8565
8682
  if (this.stopped) return;
8566
8683
  const ref = this.lastChunkAt || this.startedChunksAt;
8567
8684
  if (now() - ref > noAudioMs) {
8568
- log14.error(`stt: no mic audio for >${Math.round(noAudioMs / 1e3)}s \u2014 capture device stopped delivering`);
8685
+ log15.error(`stt: no mic audio for >${Math.round(noAudioMs / 1e3)}s \u2014 capture device stopped delivering`);
8569
8686
  this.diag("stt_watchdog_fatal", { noAudioMs });
8570
8687
  this.onFatal("microphone stopped delivering audio (try a different input device, e.g. AirPods, or check System Settings \u2192 Sound \u2192 Input)");
8571
8688
  this.stop();
@@ -8588,7 +8705,7 @@ var SonioxSTT = class {
8588
8705
  handle(m) {
8589
8706
  if (m.error_message) {
8590
8707
  this.diag("stt_error", { message: String(m.error_message), code: m.error_code });
8591
- return log14.error(`soniox: ${m.error_message}`);
8708
+ return log15.error(`soniox: ${m.error_message}`);
8592
8709
  }
8593
8710
  let endpoint = false;
8594
8711
  for (const t of m.tokens ?? []) {
@@ -8605,7 +8722,7 @@ var SonioxSTT = class {
8605
8722
  this.onPartial(combined);
8606
8723
  if (endpoint && this.finalText.trim()) {
8607
8724
  const utterance = this.finalText.trim();
8608
- if (this.firstTokenAt) log14.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192endpoint, "${utterance.slice(0, 60)}"`);
8725
+ if (this.firstTokenAt) log15.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192endpoint, "${utterance.slice(0, 60)}"`);
8609
8726
  this.reset();
8610
8727
  this.onUtterance(utterance, now());
8611
8728
  }
@@ -8627,7 +8744,7 @@ var SonioxSTT = class {
8627
8744
  };
8628
8745
 
8629
8746
  // node_modules/@bod.ee/voice/src/adapters/cartesia.ts
8630
- var log15 = forComponent2("CartesiaTTS");
8747
+ var log16 = forComponent2("CartesiaTTS");
8631
8748
  var now2 = () => performance.now();
8632
8749
  var CartesiaTTSOptions = class {
8633
8750
  auth = "";
@@ -8663,7 +8780,7 @@ var CartesiaTTS = class _CartesiaTTS {
8663
8780
  this.onDiag({ t: now2(), kind, ...fields });
8664
8781
  } catch (e) {
8665
8782
  this.diagOn = false;
8666
- log15.debug(`onDiag threw \u2014 TTS diagnostics disabled: ${e instanceof Error ? e.message : e}`);
8783
+ log16.debug(`onDiag threw \u2014 TTS diagnostics disabled: ${e instanceof Error ? e.message : e}`);
8667
8784
  }
8668
8785
  }
8669
8786
  firstAudioAt = 0;
@@ -8698,11 +8815,11 @@ var CartesiaTTS = class _CartesiaTTS {
8698
8815
  this.ws.onerror = (e) => rej(new Error(`cartesia ws: ${e.message || "connect failed"}`));
8699
8816
  });
8700
8817
  this.ws.onclose = (ev) => {
8701
- log15.warn(`cartesia ws closed (${ev.code} ${ev.reason || ""})`);
8818
+ log16.warn(`cartesia ws closed (${ev.code} ${ev.reason || ""})`);
8702
8819
  this.diag("tts_ws_closed", { code: ev.code, reason: String(ev.reason || ""), reconnecting: !this.closed });
8703
8820
  if (!this.closed) {
8704
8821
  this.connecting = this.doConnect().catch((e) => {
8705
- log15.error(`cartesia reconnect failed: ${e.message}`);
8822
+ log16.error(`cartesia reconnect failed: ${e.message}`);
8706
8823
  this.diag("tts_reconnect_failed", { message: e.message });
8707
8824
  });
8708
8825
  }
@@ -8730,12 +8847,12 @@ var CartesiaTTS = class _CartesiaTTS {
8730
8847
  this.down = true;
8731
8848
  this.downAt = now2();
8732
8849
  this.consecutiveOk = 0;
8733
- log15.warn(`TTS circuit breaker open \u2014 ${this.consecutiveErrors} consecutive errors, switching to text-only`);
8850
+ log16.warn(`TTS circuit breaker open \u2014 ${this.consecutiveErrors} consecutive errors, switching to text-only`);
8734
8851
  this.diag("tts_breaker_open", { errors: this.consecutiveErrors });
8735
8852
  this.onDone();
8736
8853
  this.startProbe();
8737
8854
  } else if (!this.down) {
8738
- (/No valid transcripts/i.test(m.message || "") ? log15.debug : log15.warn)(`cartesia: ${JSON.stringify(m)}`);
8855
+ (/No valid transcripts/i.test(m.message || "") ? log16.debug : log16.warn)(`cartesia: ${JSON.stringify(m)}`);
8739
8856
  }
8740
8857
  }
8741
8858
  };
@@ -8750,7 +8867,7 @@ var CartesiaTTS = class _CartesiaTTS {
8750
8867
  this.stopProbe();
8751
8868
  const downMs = this.downAt ? now2() - this.downAt : 0;
8752
8869
  this.diag("tts_breaker_close", { downMs: Math.round(downMs) });
8753
- (downMs < 2e3 ? log15.debug : log15.info)(`TTS recovered${downMs ? ` (down ${downMs}ms)` : ""}`);
8870
+ (downMs < 2e3 ? log16.debug : log16.info)(`TTS recovered${downMs ? ` (down ${downMs}ms)` : ""}`);
8754
8871
  }
8755
8872
  /** Ensure the WS is open before sending — reconnects if idle-closed. */
8756
8873
  async ensureConnected() {
@@ -8833,13 +8950,13 @@ function base64ToBytes(b64) {
8833
8950
  }
8834
8951
 
8835
8952
  // node_modules/@bod.ee/voice/src/adapters/fish.ts
8836
- var log16 = forComponent2("FishTTS");
8953
+ var log17 = forComponent2("FishTTS");
8837
8954
 
8838
8955
  // node_modules/@bod.ee/voice/src/adapters/grok.ts
8839
- var log17 = forComponent2("GrokTTS");
8956
+ var log18 = forComponent2("GrokTTS");
8840
8957
 
8841
8958
  // node_modules/@bod.ee/voice/src/adapters/grok-stt.ts
8842
- var log18 = forComponent2("GrokSTT");
8959
+ var log19 = forComponent2("GrokSTT");
8843
8960
 
8844
8961
  // src/voice/engine.ts
8845
8962
  init_logging();
@@ -8851,7 +8968,7 @@ configureLogging((name) => {
8851
8968
 
8852
8969
  // src/voice/instinct.ts
8853
8970
  init_logging();
8854
- var log19 = forComponent("Instinct");
8971
+ var log20 = forComponent("Instinct");
8855
8972
  var ACK_SITUATIONS = {
8856
8973
  hard_question: `a question that takes real thought, judgment or looking something up ("what's the biggest takeaway?", "how should I structure this?")`,
8857
8974
  simple_question: 'a quick question with an instant, obvious answer ("how are you?", "what time is it?", "can you hear me?")',
@@ -8890,7 +9007,7 @@ function interruptionNote(cut, intent) {
8890
9007
 
8891
9008
  // src/voice/unsaid.ts
8892
9009
  init_logging();
8893
- var log20 = forComponent("Unsaid");
9010
+ var log21 = forComponent("Unsaid");
8894
9011
  var STOP2 = new Set("that this with have from they them their there then than what when were will would your about into just also been some only over more most very which while after before because these those does here".split(" "));
8895
9012
  var contentStems = (s) => (s.toLowerCase().match(/[a-z0-9']+/g) ?? []).filter((w) => w.length >= 4 && !STOP2.has(w)).map((w) => w.slice(0, 4));
8896
9013
  var SAID_OVERLAP = 0.6;
@@ -8930,7 +9047,7 @@ async function unsaidClauses(pieces, spoken, decider, timeoutMs = 5e3) {
8930
9047
  return p < CONVEYED_MIN;
8931
9048
  }).map((x) => x.c);
8932
9049
  } catch (e) {
8933
- log20.warn(`unsaid judge failed \u2014 only near-zero-overlap clauses count as unsaid: ${e.message}`);
9050
+ log21.warn(`unsaid judge failed \u2014 only near-zero-overlap clauses count as unsaid: ${e.message}`);
8934
9051
  return fallback();
8935
9052
  } finally {
8936
9053
  clearTimeout(timer);
@@ -9001,7 +9118,7 @@ async function judgeLeak(decider, sentence, userSaid) {
9001
9118
  }
9002
9119
 
9003
9120
  // src/duplex.ts
9004
- var log21 = forComponent("DuplexAgent");
9121
+ var log22 = forComponent("DuplexAgent");
9005
9122
  function describeCall(call) {
9006
9123
  const v = call.args && Object.values(call.args).find((x) => typeof x === "string" && x.trim());
9007
9124
  const hint = v ? ` (${String(v).replace(/\s+/g, " ").trim().slice(0, 48)})` : "";
@@ -9261,7 +9378,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9261
9378
  const m = this.reflexBuf.match(RESERVED_EVENT_MARKER) ?? this.reflexBuf.match(RESERVED_EVENT_OPENER);
9262
9379
  if (m) {
9263
9380
  this.fabricationCut = true;
9264
- log21.warn(`reflex fabricated a [task \u2026] event in its spoken stream \u2014 cutting it (kept ${m.index} chars)`);
9381
+ log22.warn(`reflex fabricated a [task \u2026] event in its spoken stream \u2014 cutting it (kept ${m.index} chars)`);
9265
9382
  this.reflexBuf = this.reflexBuf.slice(0, Math.max(m.index, this.reflexForwarded));
9266
9383
  this.pumpReflex(true);
9267
9384
  return;
@@ -9353,7 +9470,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9353
9470
  const at = from + ok;
9354
9471
  this.leakHit = true;
9355
9472
  this.leakCuts.push(leak.trim().slice(0, 40));
9356
- log21.warn(`reflex spoke its own analysis ("${leak.trim().slice(0, 40)}\u2026") \u2014 cut before TTS`);
9473
+ log22.warn(`reflex spoke its own analysis ("${leak.trim().slice(0, 40)}\u2026") \u2014 cut before TTS`);
9357
9474
  this.notify("diag", "analysis_leak", { text: leak.trim().slice(0, 80), at: this.turnSpoken.trim() ? "mid_reply" : "before_speech" });
9358
9475
  const next = this.stepMarks.find((k) => k > at);
9359
9476
  if (next === void 0) {
@@ -9393,7 +9510,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9393
9510
  this.notify("diag", "leak_judge", { text: s.slice(0, 80), p: Math.round(p * 100) / 100, ms: Math.round(performance.now() - t0) });
9394
9511
  return p >= LEAK_JUDGE_THRESHOLD;
9395
9512
  }, (e) => {
9396
- log21.warn(`leak judge failed \u2014 speaking the sentence: ${e.message}`);
9513
+ log22.warn(`leak judge failed \u2014 speaking the sentence: ${e.message}`);
9397
9514
  return false;
9398
9515
  }).then((leak) => {
9399
9516
  clearTimeout(timer);
@@ -9427,7 +9544,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9427
9544
  let i = t.length - 1;
9428
9545
  while (i >= this.turnBase && !(t[i].role === "assistant" && typeof t[i].content === "string" && t[i].content.includes(key))) i--;
9429
9546
  if (i < this.turnBase) {
9430
- log21.warn("analysis leak cut, but its text was not found in the transcript to scrub");
9547
+ log22.warn("analysis leak cut, but its text was not found in the transcript to scrub");
9431
9548
  continue;
9432
9549
  }
9433
9550
  const m = t[i];
@@ -9507,7 +9624,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9507
9624
  return {
9508
9625
  preToolUse: (call) => {
9509
9626
  if (this.spec?.state === "pending" && !_DuplexAgent.SPEC_SAFE_TOOLS.has(call.name)) {
9510
- log21.verbose(`speculation aborted: reflex called ${call.name} on unconfirmed input`);
9627
+ log22.verbose(`speculation aborted: reflex called ${call.name} on unconfirmed input`);
9511
9628
  this.abortSpeculation();
9512
9629
  return { block: true, reason: "Speculative turn aborted." };
9513
9630
  }
@@ -9552,7 +9669,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9552
9669
  try {
9553
9670
  await this.voice.send(reminder);
9554
9671
  } catch (e) {
9555
- log21.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
9672
+ log22.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
9556
9673
  } finally {
9557
9674
  this.nudging = false;
9558
9675
  const i = this.voice.transcript.findIndex((m, k) => k >= base && m.role === "user" && m.content === reminder);
@@ -9604,7 +9721,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9604
9721
  const accepted = this.offerOpen && this.parkedRedeliver.length > 0 && this.parkedRedeliver.every((p) => p.task === this.offer.task) && (isOfferAcceptance(content) || isTrivialBarge(content));
9605
9722
  const judge = accepted ? void 0 : this.options.bargeIntent;
9606
9723
  return (accepted ? Promise.resolve("resume") : judge ? judge(content).catch((e) => {
9607
- log21.warn(`bargeIntent judge failed \u2014 using the allowlist: ${e.message}`);
9724
+ log22.warn(`bargeIntent judge failed \u2014 using the allowlist: ${e.message}`);
9608
9725
  return fallback();
9609
9726
  }) : Promise.resolve(fallback())).then((intent) => {
9610
9727
  this.notify("diag", "barge_intent", { intent, text: content.slice(0, 80) });
@@ -9800,7 +9917,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9800
9917
  try {
9801
9918
  res = await this.voice.send(spec.text);
9802
9919
  } catch (e) {
9803
- log21.warn(`speculative turn failed: ${e instanceof Error ? e.message : e}`);
9920
+ log22.warn(`speculative turn failed: ${e instanceof Error ? e.message : e}`);
9804
9921
  } finally {
9805
9922
  this.voice.options.signal = prevSignal;
9806
9923
  }
@@ -9814,7 +9931,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9814
9931
  if (d === "abort") {
9815
9932
  if (this.voice.transcript.length > base) this.voice.transcript.length = base;
9816
9933
  this.speculativeAbortedCalls++;
9817
- log21.verbose(`speculation aborted (${this.speculativeAbortedCalls} total): "${spec.text.slice(0, 50)}"`);
9934
+ log22.verbose(`speculation aborted (${this.speculativeAbortedCalls} total): "${spec.text.slice(0, 50)}"`);
9818
9935
  if (this.spec === spec) this.spec = void 0;
9819
9936
  return res ?? empty;
9820
9937
  }
@@ -10164,7 +10281,7 @@ Another agent just implemented the above. Independently check the CURRENT state
10164
10281
  this.notify("task_verify", `task ${id}: verifying`, { id });
10165
10282
  const cres = await new Agent(checkerOpts).run(checkBrief);
10166
10283
  if (cres.finishReason !== "stop") {
10167
- log21.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
10284
+ log22.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
10168
10285
  this.notify("task_verify", `task ${id}: verify inconclusive (${cres.finishReason})`, { id, finishReason: cres.finishReason });
10169
10286
  }
10170
10287
  const sum = (a = 0, b = 0) => a + b;
@@ -10300,7 +10417,7 @@ Another agent just implemented the above. Independently check the CURRENT state
10300
10417
  rec.status = "done";
10301
10418
  rec.result = res.text;
10302
10419
  const incomplete = res.finishReason !== "stop" && res.finishReason !== "empty";
10303
- log21.verbose(`task ${id} done (${res.steps} steps${incomplete ? `, INCOMPLETE: ${res.finishReason}` : ""})`);
10420
+ log22.verbose(`task ${id} done (${res.steps} steps${incomplete ? `, INCOMPLETE: ${res.finishReason}` : ""})`);
10304
10421
  this.notify("task_done", `task ${id} (${rec.label}) completed`, {
10305
10422
  id,
10306
10423
  text: res.text,
@@ -10334,14 +10451,14 @@ Another agent just implemented the above. Independently check the CURRENT state
10334
10451
  foldIfSuperseded(rec, note) {
10335
10452
  if (rec.deliveryParked !== "moved_on") return false;
10336
10453
  if (note.trim()) this.voice.transcript.push({ role: "assistant", content: note });
10337
- log21.verbose(`task ${rec.id} settled but superseded (${rec.status}) \u2014 folded into transcript, not voiced`);
10454
+ log22.verbose(`task ${rec.id} settled but superseded (${rec.status}) \u2014 folded into transcript, not voiced`);
10338
10455
  return true;
10339
10456
  }
10340
10457
  failTask(rec, msg) {
10341
10458
  this.dropAsk(rec.id);
10342
10459
  rec.status = "error";
10343
10460
  rec.result = msg;
10344
- log21.warn(`task ${rec.id} failed: ${msg}`);
10461
+ log22.warn(`task ${rec.id} failed: ${msg}`);
10345
10462
  this.notify("task_error", `task ${rec.id} (${rec.label}) failed: ${msg}`, { id: rec.id });
10346
10463
  if (this.foldIfSuperseded(rec, `The "${rec.label}" task failed: ${msg}`)) return;
10347
10464
  this.dropParked(rec);
@@ -10580,7 +10697,7 @@ Another agent just implemented the above. Independently check the CURRENT state
10580
10697
  // src/modelRouter.ts
10581
10698
  init_llm();
10582
10699
  init_logging();
10583
- var log22 = forComponent("ModelRouter");
10700
+ var log23 = forComponent("ModelRouter");
10584
10701
  var ModelRouterOptions = class {
10585
10702
  classifier = jevClassifier();
10586
10703
  /** Capability tiers → model id (any provider). basic/standard run the main session; premium is only ever an offload. */
@@ -10664,10 +10781,10 @@ var ModelRouter = class {
10664
10781
  if (tier === "premium") d.offload = this.state.offload && (c.sameThread ?? 0) >= o.sameThreadAt ? "continue" : "fresh";
10665
10782
  } catch (e) {
10666
10783
  const msg = e instanceof Error ? e.message : String(e);
10667
- log22.warn(`classifier failed, falling back to standard: ${msg}`);
10784
+ log23.warn(`classifier failed, falling back to standard: ${msg}`);
10668
10785
  d = { tier: "standard", model: o.models.standard, probs: {}, fallback: msg, classifyMs: Math.round(performance.now() - t0) };
10669
10786
  }
10670
- 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`);
10787
+ log23.info(`route ${d.tier}${d.offload ? `(${d.offload})` : ""} \xB7 argmax=${d.choice ?? "-"} \xB7 p=${JSON.stringify(d.probs)}${d.sameThread != null ? ` \xB7 same=${d.sameThread.toFixed(2)}` : ""} \xB7 ${d.classifyMs}ms`);
10671
10788
  return d;
10672
10789
  }
10673
10790
  /** Run one user turn on `agent` with routing. Returns a RunResult carrying `costUsd` + `routing`. */
@@ -10719,7 +10836,7 @@ ${text}`;
10719
10836
  const answer = res.text || `(premium sub-agent finished with no reply; finishReason=${res.finishReason})`;
10720
10837
  agent.transcript.push({ role: "user", content }, { role: "assistant", content: answer });
10721
10838
  this.state.offload = { messages: child.transcript, mainLen: agent.transcript.length, question: text };
10722
- log22.info(`offload ${cont ? "continue" : "fresh"} done \xB7 ${res.finishReason} \xB7 ${res.steps} steps`);
10839
+ log23.info(`offload ${cont ? "continue" : "fresh"} done \xB7 ${res.finishReason} \xB7 ${res.steps} steps`);
10723
10840
  const costUsd = o.costOf(main, briefUsage) + o.costOf(o.models.premium, res.usage);
10724
10841
  return { ...res, text: answer, messages: agent.transcript, usage: addUsage(briefUsage, res.usage), costUsd, routing: d };
10725
10842
  }
@@ -10735,7 +10852,7 @@ The request:
10735
10852
  ${text}`);
10736
10853
  return { text: r.text.trim() || text, usage: r.usage };
10737
10854
  } catch (e) {
10738
- log22.warn(`brief failed, sending the raw request: ${e instanceof Error ? e.message : String(e)}`);
10855
+ log23.warn(`brief failed, sending the raw request: ${e instanceof Error ? e.message : String(e)}`);
10739
10856
  return { text };
10740
10857
  } finally {
10741
10858
  agent.transcript = snap;
@@ -10767,8 +10884,8 @@ async function loadJev(opts) {
10767
10884
  const env = { ...process.env };
10768
10885
  const file = opts.envFile ?? process.env.AGENTX_JEV_ENV;
10769
10886
  if (file) {
10770
- const { readFileSync: readFileSync3 } = await import("fs");
10771
- for (const l of readFileSync3(file, "utf8").split("\n")) {
10887
+ const { readFileSync: readFileSync4 } = await import("fs");
10888
+ for (const l of readFileSync4(file, "utf8").split("\n")) {
10772
10889
  const m = l.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*?)\s*$/);
10773
10890
  if (m && !env[m[1]]) env[m[1]] = m[2].replace(/^['"]|['"]$/g, "");
10774
10891
  }
@@ -10780,7 +10897,7 @@ async function jevAvailable(opts = {}) {
10780
10897
  const { env } = await loadJev(opts);
10781
10898
  return JEV_KEYS.some((k) => env[k]?.trim());
10782
10899
  } catch (e) {
10783
- log22.debug(`jev unavailable: ${e instanceof Error ? e.message : String(e)}`);
10900
+ log23.debug(`jev unavailable: ${e instanceof Error ? e.message : String(e)}`);
10784
10901
  return false;
10785
10902
  }
10786
10903
  }