@livx.cc/agentx 0.99.27 → 0.99.30

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
@@ -9,6 +9,271 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
+ // src/logging.ts
13
+ import { log } from "libx.js/src/modules/log";
14
+ var forComponent;
15
+ var init_logging = __esm({
16
+ "src/logging.ts"() {
17
+ "use strict";
18
+ forComponent = (name) => log.forComponent(name);
19
+ }
20
+ });
21
+
22
+ // src/llm.ts
23
+ import { readFileSync, statSync as statSync2, realpathSync } from "fs";
24
+ import { resolve, sep } from "path";
25
+ import { randomBytes } from "crypto";
26
+ function contentText(content) {
27
+ if (content == null) return "";
28
+ if (typeof content === "string") return content;
29
+ return content.map((p) => p.type === "text" ? p.text ?? "" : "[image]").join(p_sep(content));
30
+ }
31
+ function imagePart(url) {
32
+ return { type: "image_url", image_url: { url } };
33
+ }
34
+ function messageHasInlineImage(content) {
35
+ if (content == null || typeof content === "string") return false;
36
+ return content.some((p) => p.type === "image_url" && (p.image_url?.url ?? "").startsWith("data:"));
37
+ }
38
+ function contentBytes(content) {
39
+ if (content == null) return 0;
40
+ if (typeof content === "string") return content.length;
41
+ let n = 0;
42
+ for (const p of content) {
43
+ if (p.type === "text") {
44
+ n += (p.text ?? "").length;
45
+ continue;
46
+ }
47
+ const url = p.image_url?.url ?? "";
48
+ const ref = parseImageRef(url);
49
+ n += (ref ? ref.bytes : url.length) || 7;
50
+ }
51
+ return n;
52
+ }
53
+ function elideInlineImages(content, stub = IMAGE_ELIDE_STUB) {
54
+ return elideImageParts(content, (url) => url.startsWith("data:"), stub);
55
+ }
56
+ function elideImages(content, stub = IMAGE_ELIDE_STUB) {
57
+ return elideImageParts(content, (url) => url.startsWith("data:") || isImageRefUrl(url), stub);
58
+ }
59
+ function elideImageParts(content, hit, stub) {
60
+ if (typeof content === "string") return content;
61
+ let changed = false;
62
+ const out = [];
63
+ for (const p of content) {
64
+ if (p.type !== "image_url" || !hit(p.image_url?.url ?? "")) {
65
+ out.push(p);
66
+ continue;
67
+ }
68
+ changed = true;
69
+ out.push({ type: "text", text: stub });
70
+ }
71
+ return changed ? out : content;
72
+ }
73
+ function mintImageRef(path, mime) {
74
+ const token = randomBytes(16).toString("hex");
75
+ let bytes = 0;
76
+ try {
77
+ bytes = Math.ceil(statSync2(path).size * 4 / 3);
78
+ } catch (e) {
79
+ log2.warn(`image ref stat failed, weighing as 0: ${path}`, e);
80
+ }
81
+ refRegistry.set(token, { mime, path, bytes });
82
+ while (refRegistry.size > REF_REGISTRY_MAX) refRegistry.delete(refRegistry.keys().next().value);
83
+ return `${IMAGE_REF_SCHEME}${mime};${token};${path}`;
84
+ }
85
+ function imageRefPart(path, mime) {
86
+ return { type: "image_url", image_url: { url: mintImageRef(path, mime) } };
87
+ }
88
+ function parseImageRef(url) {
89
+ const parts = refUrlParts(url);
90
+ const ref = parts && refRegistry.get(parts.token);
91
+ if (!ref) {
92
+ log2.debug("image ref not in registry \u2014 treating as non-image text");
93
+ return null;
94
+ }
95
+ return ref;
96
+ }
97
+ function refUrlParts(url) {
98
+ if (!isImageRefUrl(url)) return null;
99
+ const rest = url.slice(IMAGE_REF_SCHEME.length);
100
+ const a = rest.indexOf(";");
101
+ if (a < 0) return null;
102
+ const b = rest.indexOf(";", a + 1);
103
+ return { mime: rest.slice(0, a), token: b < 0 ? rest.slice(a + 1) : rest.slice(a + 1, b), path: b < 0 ? "" : rest.slice(b + 1) };
104
+ }
105
+ function remintImageRefs(messages, roots) {
106
+ const allowed = [];
107
+ for (const r of roots) {
108
+ try {
109
+ allowed.push(realpathSync(resolve(r)));
110
+ } catch (e) {
111
+ log2.debug(`image-ref root unavailable: ${r}`, e);
112
+ }
113
+ }
114
+ if (allowed.length === 0) return messages;
115
+ const inside = (p) => allowed.some((root) => p === root || p.startsWith(root + sep));
116
+ let changed = false;
117
+ const out = messages.map((m) => {
118
+ if (m.role !== "user" && m.role !== "assistant" || typeof m.content === "string" || m.content == null) return m;
119
+ let touched = false;
120
+ const parts = m.content.map((p) => {
121
+ if (p.type !== "image_url") return p;
122
+ const f = refUrlParts(p.image_url?.url);
123
+ if (!f || refRegistry.has(f.token)) return p;
124
+ let real;
125
+ try {
126
+ real = realpathSync(f.path);
127
+ } catch (e) {
128
+ log2.warn(`resumed image ref unreadable, will stub: ${f.path}`, e);
129
+ return p;
130
+ }
131
+ if (!inside(real)) {
132
+ log2.warn(`resumed image ref outside the allowed roots, will stub: ${f.path} -> ${real}`);
133
+ return p;
134
+ }
135
+ touched = true;
136
+ return { type: "image_url", image_url: { url: mintImageRef(real, f.mime) } };
137
+ });
138
+ if (!touched) return m;
139
+ changed = true;
140
+ return { ...m, content: parts };
141
+ });
142
+ return changed ? out : messages;
143
+ }
144
+ function messageHasImageRef(content) {
145
+ if (content == null || typeof content === "string") return false;
146
+ return content.some((p) => p.type === "image_url" && isImageRefUrl(p.image_url?.url));
147
+ }
148
+ function messageHasImage(content) {
149
+ return messageHasInlineImage(content) || messageHasImageRef(content);
150
+ }
151
+ function imageRefResult(path, mime) {
152
+ return JSON.stringify({ imageRef: mintImageRef(path, mime), image: path });
153
+ }
154
+ function toolImageRef(content) {
155
+ if (typeof content !== "string" || !content.startsWith('{"imageRef":')) return null;
156
+ try {
157
+ return parseImageRef(JSON.parse(content).imageRef);
158
+ } catch (e) {
159
+ log2.debug("bad imageRef tool result", e);
160
+ return null;
161
+ }
162
+ }
163
+ function elideStaleImages(messages, keepRecent = 1) {
164
+ const idx = messages.map((m, i) => messageHasImage(m.content) ? i : -1).filter((i) => i >= 0);
165
+ const drop = new Set(idx.slice(0, Math.max(0, idx.length - keepRecent)));
166
+ if (drop.size === 0) return messages;
167
+ let changed = false;
168
+ const out = messages.map((m, i) => {
169
+ if (!drop.has(i)) return m;
170
+ const c = elideInlineImages(m.content);
171
+ if (c === m.content) return m;
172
+ changed = true;
173
+ return { ...m, content: c };
174
+ });
175
+ return changed ? out : messages;
176
+ }
177
+ function imageWindows(messages, keepRecent) {
178
+ const attach = [];
179
+ const tool = [];
180
+ messages.forEach((m, i) => {
181
+ if (toolImageRef(m.content)) tool.push(i);
182
+ else if (messageHasImage(m.content)) attach.push(i);
183
+ });
184
+ const k = Math.max(0, keepRecent);
185
+ return /* @__PURE__ */ new Set([...attach.slice(Math.max(0, attach.length - k)), ...tool.slice(Math.max(0, tool.length - k))]);
186
+ }
187
+ function expandImagesForSend(messages, keepRecent = 1, stub = IMAGE_ELIDE_STUB) {
188
+ if (!messages.some(bearsImage)) return messages;
189
+ const keep = imageWindows(messages, keepRecent);
190
+ let changed = false;
191
+ const out = messages.map((m, i) => {
192
+ const content = keep.has(i) ? expandContent(m.content, stub) : stripContent(m.content, stub);
193
+ if (content === m.content) return m;
194
+ changed = true;
195
+ return { ...m, content };
196
+ });
197
+ return changed ? out : messages;
198
+ }
199
+ function sendBytes(messages, keepRecent = 1, stub = IMAGE_ELIDE_STUB) {
200
+ let n = 0;
201
+ for (const b of sendBytesPer(messages, keepRecent, stub)) n += b;
202
+ return n;
203
+ }
204
+ function sendBytesPer(messages, keepRecent = 1, stub = IMAGE_ELIDE_STUB) {
205
+ const keep = messages.some(bearsImage) ? imageWindows(messages, keepRecent) : null;
206
+ return messages.map((m, i) => wireBytes(m.content, keep == null || keep.has(i), stub) + (m.tool_calls ? JSON.stringify(m.tool_calls).length : 0));
207
+ }
208
+ function wireBytes(content, kept, stub) {
209
+ const tool = toolImageRef(content);
210
+ if (tool) return kept ? tool.bytes : stub.length;
211
+ if (content == null || typeof content === "string") return contentBytes(content);
212
+ if (kept) return contentBytes(content);
213
+ let n = 0;
214
+ for (const p of content) {
215
+ if (p.type === "text") {
216
+ n += (p.text ?? "").length;
217
+ continue;
218
+ }
219
+ const url = p.image_url?.url ?? "";
220
+ n += url.startsWith("data:") || isImageRefUrl(url) ? stub.length : url.length || 7;
221
+ }
222
+ return n;
223
+ }
224
+ function readRefDataUrl(ref) {
225
+ try {
226
+ return `data:${ref.mime};base64,${readFileSync(ref.path).toString("base64")}`;
227
+ } catch (e) {
228
+ log2.warn(`image ref unreadable, sending stub instead: ${ref.path}`, e);
229
+ return null;
230
+ }
231
+ }
232
+ function expandContent(content, stub) {
233
+ const tool = toolImageRef(content);
234
+ if (tool) {
235
+ const url = readRefDataUrl(tool);
236
+ return url ? JSON.stringify({ dataUrl: url, image: tool.path }) : stub;
237
+ }
238
+ if (typeof content === "string") return content;
239
+ let changed = false;
240
+ const out = content.map((p) => {
241
+ if (p.type !== "image_url" || !isImageRefUrl(p.image_url?.url)) return p;
242
+ changed = true;
243
+ const ref = parseImageRef(p.image_url?.url);
244
+ const url = ref && readRefDataUrl(ref);
245
+ return url ? imagePart(url) : { type: "text", text: stub };
246
+ });
247
+ return changed ? out : content;
248
+ }
249
+ function stripContent(content, stub) {
250
+ if (toolImageRef(content)) return stub;
251
+ if (typeof content === "string") return content;
252
+ let changed = false;
253
+ const out = content.map((p) => {
254
+ const url = p.type === "image_url" ? p.image_url?.url ?? "" : "";
255
+ if (!url.startsWith("data:") && !isImageRefUrl(url)) return p;
256
+ changed = true;
257
+ return { type: "text", text: stub };
258
+ });
259
+ return changed ? out : content;
260
+ }
261
+ var log2, p_sep, IMAGE_ELIDE_STUB, IMAGE_REF_SCHEME, REF_REGISTRY_MAX, refRegistry, isImageRefUrl, bearsImage;
262
+ var init_llm = __esm({
263
+ "src/llm.ts"() {
264
+ "use strict";
265
+ init_logging();
266
+ log2 = forComponent("llm");
267
+ p_sep = (parts) => parts.length > 1 ? "\n" : "";
268
+ IMAGE_ELIDE_STUB = "[image elided \u2014 re-attach @path or /paste to view again]";
269
+ IMAGE_REF_SCHEME = "agentx-image:";
270
+ REF_REGISTRY_MAX = 256;
271
+ refRegistry = /* @__PURE__ */ new Map();
272
+ isImageRefUrl = (url) => !!url && url.startsWith(IMAGE_REF_SCHEME);
273
+ bearsImage = (m) => messageHasImage(m.content) || toolImageRef(m.content) != null;
274
+ }
275
+ });
276
+
12
277
  // src/redact.ts
13
278
  function redactSecrets(text) {
14
279
  if (!text) return text;
@@ -475,16 +740,6 @@ var init_todo = __esm({
475
740
  }
476
741
  });
477
742
 
478
- // src/logging.ts
479
- import { log } from "libx.js/src/modules/log";
480
- var forComponent;
481
- var init_logging = __esm({
482
- "src/logging.ts"() {
483
- "use strict";
484
- forComponent = (name) => log.forComponent(name);
485
- }
486
- });
487
-
488
743
  // src/tools.web.ts
489
744
  function htmlToText(html) {
490
745
  let s = html.replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<title[\s\S]*?<\/title>/gi, " ").replace(/<noscript[\s\S]*?<\/noscript>/gi, " ").replace(/<textarea[\s\S]*?<\/textarea>/gi, " ").replace(/<!--[\s\S]*?-->/g, " ").replace(/<\/(p|div|li|h[1-6]|tr|section|article|header|footer|nav)>/gi, "\n").replace(/<br\s*\/?>/gi, "\n").replace(/<[^>]+>/g, " ");
@@ -606,7 +861,7 @@ function makeWebFetchTool(options = {}) {
606
861
 
607
862
  ${capped}`;
608
863
  } catch (e) {
609
- log2.debug(`WebFetch ${u} failed`, e);
864
+ log3.debug(`WebFetch ${u} failed`, e);
610
865
  return `Error fetching ${u}: ${e?.name === "AbortError" ? `timed out after ${timeoutMs}ms` : e?.message ?? e}`;
611
866
  } finally {
612
867
  clearTimeout(timer);
@@ -730,7 +985,7 @@ function makeWebSearchTool(options = {}) {
730
985
  if (!res.ok) return `Error: search returned ${res.status} ${res.statusText}`;
731
986
  return formatHits(parseDdgHtml(await res.text(), maxResults));
732
987
  } catch (e) {
733
- log2.debug("WebSearch failed", e);
988
+ log3.debug("WebSearch failed", e);
734
989
  return `Error searching: ${e?.name === "AbortError" ? `timed out after ${timeoutMs}ms` : e?.message ?? e}`;
735
990
  } finally {
736
991
  clearTimeout(timer);
@@ -741,12 +996,12 @@ function makeWebSearchTool(options = {}) {
741
996
  function makeWebSearchAnthropicTool(opts = {}) {
742
997
  return makeWebSearchTool({ provider: "anthropic", name: "WebSearchAnthropic", description: ANTHROPIC_SEARCH_DESC, anthropicApiKey: opts.anthropicApiKey, model: opts.model });
743
998
  }
744
- var log2, _dnsLookup, webFetchTool, webSearchTool, ANTHROPIC_SEARCH_DESC, webSearchAnthropicTool;
999
+ var log3, _dnsLookup, webFetchTool, webSearchTool, ANTHROPIC_SEARCH_DESC, webSearchAnthropicTool;
745
1000
  var init_tools_web = __esm({
746
1001
  "src/tools.web.ts"() {
747
1002
  "use strict";
748
1003
  init_logging();
749
- log2 = forComponent("web");
1004
+ log3 = forComponent("web");
750
1005
  webFetchTool = makeWebFetchTool();
751
1006
  webSearchTool = makeWebSearchTool();
752
1007
  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).";
@@ -961,6 +1216,36 @@ ${out}`.trim() : out || "(command succeeded, no output)";
961
1216
  );
962
1217
  return `Started background job ${id} \u2014 poll with JobOutput({id:"${id}"}) / JobStatus, stop with JobKill.`;
963
1218
  }
1219
+ function realPathOf(fs, path) {
1220
+ if (typeof fs.realPath !== "function") return null;
1221
+ try {
1222
+ return fs.realPath(path);
1223
+ } catch (e) {
1224
+ log4.debug(`realPath unavailable for ${path} \u2014 inlining image bytes instead`, e);
1225
+ return null;
1226
+ }
1227
+ }
1228
+ function isBinary(raw) {
1229
+ if (raw === "") return false;
1230
+ const head = raw.length > 8192 ? raw.slice(0, 8192) : raw;
1231
+ let repl2 = 0;
1232
+ for (let i = 0; i < head.length; i++) {
1233
+ const c = head.charCodeAt(i);
1234
+ if (c === 0) return true;
1235
+ if (c === 65533) repl2++;
1236
+ }
1237
+ return repl2 / head.length > 0.1;
1238
+ }
1239
+ async function byteSize(fs, path, raw) {
1240
+ const bfs = fs;
1241
+ if (typeof bfs.readFileBytes === "function") {
1242
+ try {
1243
+ return (await bfs.readFileBytes(path)).length;
1244
+ } catch {
1245
+ }
1246
+ }
1247
+ return Buffer.byteLength(raw, "utf8");
1248
+ }
964
1249
  function exitSessionTool(onExit) {
965
1250
  return {
966
1251
  name: "ExitSession",
@@ -987,15 +1272,18 @@ function toolsByName(names) {
987
1272
  return t;
988
1273
  });
989
1274
  }
990
- var numberLines, bashTool, IMG_MIME, readTool, editTool;
1275
+ var log4, numberLines, bashTool, IMG_MIME, readTool, editTool;
991
1276
  var init_tools = __esm({
992
1277
  "src/tools.ts"() {
993
1278
  "use strict";
1279
+ init_llm();
1280
+ init_logging();
994
1281
  init_tools_structured();
995
1282
  init_todo();
996
1283
  init_tools_web();
997
1284
  init_OverlayFilesystem();
998
1285
  init_redact();
1286
+ log4 = forComponent("tools");
999
1287
  numberLines = (content, offset = 0, limit) => {
1000
1288
  const lines = content.split("\n");
1001
1289
  const start = Math.max(0, offset);
@@ -1050,12 +1338,22 @@ var init_tools = __esm({
1050
1338
  if (typeof fs.readFileBytes !== "function") {
1051
1339
  return `[${path} is an image, but this filesystem can't read binary \u2014 attach it as @${path} instead, or run on disk.]`;
1052
1340
  }
1341
+ const real = realPathOf(fs, path);
1342
+ if (real) {
1343
+ if (!await ctx.fs.exists(path)) return `Error: File not found: ${path}`;
1344
+ return imageRefResult(real, IMG_MIME[ext]);
1345
+ }
1053
1346
  const bytes = await fs.readFileBytes(path);
1054
1347
  const b64 = Buffer.from(bytes).toString("base64");
1055
1348
  return JSON.stringify({ dataUrl: `data:${IMG_MIME[ext]};base64,${b64}`, image: path });
1056
1349
  }
1057
1350
  const raw = await ctx.fs.readFile(path);
1351
+ if (isBinary(raw)) {
1352
+ const bytes = await byteSize(ctx.fs, path, raw);
1353
+ return `[binary file: ${path} \u2014 ${bytes} bytes, not shown as text. Read renders png/jpg/jpeg/gif/webp images directly; otherwise attach it as @${path}.]`;
1354
+ }
1058
1355
  ctx.readState.set(ctx.fs.resolvePath(path), raw);
1356
+ if (raw === "") return `[empty file: ${path} \u2014 0 bytes]`;
1059
1357
  const content = CONFIG_FILE_RE.test(ctx.fs.resolvePath(path)) ? redactSecrets(raw) : raw;
1060
1358
  const total = content === "" ? 0 : content.split("\n").length;
1061
1359
  const start = Math.max(0, offset ?? 0);
@@ -1123,7 +1421,7 @@ __export(worktree_exports, {
1123
1421
  worktreeBranchName: () => worktreeBranchName
1124
1422
  });
1125
1423
  import { spawnSync } from "child_process";
1126
- import { symlinkSync, mkdirSync, statSync as statSync2, readFileSync, rmSync } from "fs";
1424
+ import { symlinkSync, mkdirSync, statSync as statSync3, readFileSync as readFileSync2, rmSync } from "fs";
1127
1425
  import { join as join2 } from "path";
1128
1426
  function validateWorktreeSlug(slug2) {
1129
1427
  if (!slug2) throw new Error("worktree slug must not be empty");
@@ -1159,16 +1457,16 @@ function findGitRoot(from) {
1159
1457
  }
1160
1458
  function readWorktreeHead(worktreePath) {
1161
1459
  try {
1162
- const gitFile = readFileSync(join2(worktreePath, ".git"), "utf-8").trim();
1460
+ const gitFile = readFileSync2(join2(worktreePath, ".git"), "utf-8").trim();
1163
1461
  const m = gitFile.match(/^gitdir:\s*(.+)$/);
1164
1462
  if (!m) return null;
1165
1463
  const headPath = join2(m[1], "HEAD");
1166
- const head = readFileSync(headPath, "utf-8").trim();
1464
+ const head = readFileSync2(headPath, "utf-8").trim();
1167
1465
  const refMatch = head.match(/^ref:\s*(.+)$/);
1168
1466
  if (!refMatch) return head.length === 40 ? head : null;
1169
1467
  const refPath = join2(m[1], "..", refMatch[1]);
1170
1468
  try {
1171
- return readFileSync(refPath, "utf-8").trim();
1469
+ return readFileSync2(refPath, "utf-8").trim();
1172
1470
  } catch {
1173
1471
  }
1174
1472
  const r = git(["rev-parse", "HEAD"], worktreePath);
@@ -1204,7 +1502,7 @@ function symlinkDirs(repoRoot, worktreePath, dirs) {
1204
1502
  const src = join2(repoRoot, dir);
1205
1503
  const dst = join2(worktreePath, dir);
1206
1504
  try {
1207
- statSync2(src);
1505
+ statSync3(src);
1208
1506
  } catch {
1209
1507
  continue;
1210
1508
  }
@@ -1220,7 +1518,7 @@ function cleanupWorktree(repoRoot, slug2, opts) {
1220
1518
  const worktreePath = worktreePathFor(repoRoot, slug2);
1221
1519
  const branch = worktreeBranchName(slug2);
1222
1520
  try {
1223
- statSync2(worktreePath);
1521
+ statSync3(worktreePath);
1224
1522
  } catch {
1225
1523
  return { removed: false, reason: "not found" };
1226
1524
  }
@@ -1355,6 +1653,11 @@ var init_NodeDiskFilesystem = __esm({
1355
1653
  throw new Error(`File not found: ${path}`);
1356
1654
  }
1357
1655
  }
1656
+ /** The REAL on-disk path behind a VFS path — an optional capability (not on IFilesystem) the Read
1657
+ * tool duck-types so it can persist an image REFERENCE instead of megabytes of base64. */
1658
+ realPath(path) {
1659
+ return this.real(this.resolvePath(path));
1660
+ }
1358
1661
  /** Read raw bytes (for binary files like images). Not on the base IFilesystem (which is utf8-only) — an
1359
1662
  * optional capability the Read tool duck-types to return image blocks. Same symlink/jail guards as readFile. */
1360
1663
  async readFileBytes(path) {
@@ -1537,6 +1840,12 @@ var init_JailedFilesystem = __esm({
1537
1840
  if (typeof inner.readFileBytes !== "function") throw new Error("binary read not supported by this filesystem");
1538
1841
  return inner.readFileBytes(this.guardRead("readFileBytes", path));
1539
1842
  }
1843
+ /** Forward the optional real-path capability (image REFS), jail-guarded like readFile. */
1844
+ realPath(path) {
1845
+ const inner = this.inner;
1846
+ if (typeof inner.realPath !== "function") throw new Error("realPath not supported by this filesystem");
1847
+ return inner.realPath(this.guardRead("realPath", path));
1848
+ }
1540
1849
  async writeFile(path, content) {
1541
1850
  return this.inner.writeFile(this.guardWrite("writeFile", path), content);
1542
1851
  }
@@ -1777,13 +2086,13 @@ function makeRealShellTool(options) {
1777
2086
  }
1778
2087
  };
1779
2088
  try {
1780
- return await new Promise((resolve4) => {
2089
+ return await new Promise((resolve5) => {
1781
2090
  let out = "";
1782
2091
  let settled = false;
1783
2092
  const finish = (s) => {
1784
2093
  if (settled) return;
1785
2094
  settled = true;
1786
- resolve4(s);
2095
+ resolve5(s);
1787
2096
  };
1788
2097
  let proc;
1789
2098
  try {
@@ -1806,7 +2115,7 @@ function makeRealShellTool(options) {
1806
2115
  proc.stderr?.on("data", collect);
1807
2116
  proc.on("error", (err2) => {
1808
2117
  if (err2?.name === "AbortError" || ctl.signal.aborted) return finish(reasonFor(timedOut, timeoutMs, clean(out)));
1809
- log14.debug("shell spawn error", err2);
2118
+ log16.debug("shell spawn error", err2);
1810
2119
  finish(`[exit 1] ${err2?.message ?? err2}${out ? "\n" + clean(out) : ""}`);
1811
2120
  });
1812
2121
  proc.on("close", (code) => {
@@ -1868,7 +2177,7 @@ ${clean(out) || "(no output yet)"}`;
1868
2177
  }
1869
2178
  ];
1870
2179
  }
1871
- var log14, clean, DETACHED, SECRET_ENV_RE, _spawn, ShellJobRegistry, NO_JOB2;
2180
+ var log16, clean, DETACHED, SECRET_ENV_RE, _spawn, ShellJobRegistry, NO_JOB2;
1872
2181
  var init_tools_shell = __esm({
1873
2182
  "src/tools.shell.ts"() {
1874
2183
  "use strict";
@@ -1876,7 +2185,7 @@ var init_tools_shell = __esm({
1876
2185
  init_redact();
1877
2186
  init_logging();
1878
2187
  init_shell_sandbox();
1879
- log14 = forComponent("shell");
2188
+ log16 = forComponent("shell");
1880
2189
  clean = (s) => truncateOutput(redactSecrets(s.replace(/\n+$/, "")));
1881
2190
  DETACHED = { stdio: ["ignore", "pipe", "pipe"], detached: true };
1882
2191
  SECRET_ENV_RE = /(_API_KEY|_TOKEN|_SECRET|_PASSWORD|_PRIVATE_KEY|^AWS_|^GITHUB_TOKEN$|^OPENAI_|^ANTHROPIC_|^GOOGLE_|^GEMINI_|^GROQ_|^NPM_TOKEN$)/i;
@@ -1958,8 +2267,8 @@ var init_tools_shell = __esm({
1958
2267
 
1959
2268
  // cli/cli.ts
1960
2269
  import { createInterface } from "readline/promises";
1961
- import { existsSync as existsSync9, readFileSync as readFileSync8, appendFileSync, mkdirSync as mkdirSync12, writeFileSync as writeFileSync9, readdirSync as readdirSync4, statSync as statSync4, unlinkSync as unlinkSync5 } from "fs";
1962
- import { homedir as homedir10, tmpdir as tmpdir3 } from "os";
2270
+ import { existsSync as existsSync9, readFileSync as readFileSync9, appendFileSync, mkdirSync as mkdirSync12, writeFileSync as writeFileSync9, readdirSync as readdirSync4, statSync as statSync5, unlinkSync as unlinkSync5 } from "fs";
2271
+ import { homedir as homedir10 } from "os";
1963
2272
  import { spawnSync as spawnSync6 } from "child_process";
1964
2273
 
1965
2274
  // cli/clipboard.ts
@@ -2027,21 +2336,11 @@ function copyTextToClipboard(text, platform2 = process.platform) {
2027
2336
  }
2028
2337
 
2029
2338
  // cli/cli.ts
2030
- import { join as join14, resolve as resolve3, basename as basename2, extname, dirname as dirname5 } from "path";
2339
+ import { join as join14, resolve as resolve4, basename as basename2, extname, dirname as dirname5 } from "path";
2031
2340
  import { AIClient, listModels, listProviders, getProviderFromModel, getModelInfo, resolveModel, isModelSupported, disposeCursorSessions, disposeClaudeCodeSessions } from "ai.libx.js";
2032
2341
 
2033
- // src/llm.ts
2034
- function contentText(content) {
2035
- if (content == null) return "";
2036
- if (typeof content === "string") return content;
2037
- return content.map((p) => p.type === "text" ? p.text ?? "" : "[image]").join(p_sep(content));
2038
- }
2039
- var p_sep = (parts) => parts.length > 1 ? "\n" : "";
2040
- function imagePart(url) {
2041
- return { type: "image_url", image_url: { url } };
2042
- }
2043
-
2044
2342
  // src/Agent.ts
2343
+ init_llm();
2045
2344
  init_tools();
2046
2345
 
2047
2346
  // src/tools.jobs.ts
@@ -2680,7 +2979,7 @@ init_OverlayFilesystem();
2680
2979
  init_worktree();
2681
2980
  init_NodeDiskFilesystem();
2682
2981
  init_logging();
2683
- var log3 = forComponent("subagent");
2982
+ var log5 = forComponent("subagent");
2684
2983
  async function boundedPool(items, limit, fn) {
2685
2984
  const out = new Array(items.length);
2686
2985
  let next = 0;
@@ -2722,7 +3021,7 @@ function traceFileFor(dir, label, depth) {
2722
3021
  const id = `${Date.now().toString(36)}-${(traceSeq++).toString(36)}`;
2723
3022
  return join4(dir, `${id}-d${depth}-${sanitizeSlug(label) || "sub"}.json`);
2724
3023
  } catch (e) {
2725
- log3.warn(`subagent trace dir unavailable (${dir}) \u2014 running without a trace: ${e instanceof Error ? e.message : String(e)}`);
3024
+ log5.warn(`subagent trace dir unavailable (${dir}) \u2014 running without a trace: ${e instanceof Error ? e.message : String(e)}`);
2726
3025
  return void 0;
2727
3026
  }
2728
3027
  }
@@ -2730,7 +3029,7 @@ function flushTrace(file, meta, messages, status) {
2730
3029
  try {
2731
3030
  writeFileSync2(file, JSON.stringify({ ...meta, status, updated: Date.now(), messages }, null, 2));
2732
3031
  } catch (e) {
2733
- log3.warn(`subagent trace flush failed (${file}): ${e instanceof Error ? e.message : String(e)}`);
3032
+ log5.warn(`subagent trace flush failed (${file}): ${e instanceof Error ? e.message : String(e)}`);
2734
3033
  }
2735
3034
  }
2736
3035
  function installTrace(child, file, meta) {
@@ -3191,7 +3490,11 @@ function reasoningToChatFragment(model, effort) {
3191
3490
  }
3192
3491
 
3193
3492
  // src/Agent.ts
3194
- var log4 = forComponent("Agent");
3493
+ var log6 = forComponent("Agent");
3494
+ function envInt(key, fallback) {
3495
+ const n = typeof process !== "undefined" ? Number(process.env?.[key]) : NaN;
3496
+ return Number.isFinite(n) && n >= 0 ? n : fallback;
3497
+ }
3195
3498
  function isBrowserTool(name) {
3196
3499
  return name.startsWith("mcp__") && /browser/i.test(name.slice(5).split("__")[0]);
3197
3500
  }
@@ -3217,8 +3520,9 @@ var AgentOptions = class {
3217
3520
  /** Hard ceiling on accumulated tokens (prompt+completion) across the run; cache READS count at 0.1×
3218
3521
  * (their real price) so a fat cached context doesn't trip the guard on healthy turns. 0 = unbounded. */
3219
3522
  maxTokens = 2e5;
3220
- /** Wall-clock ceiling in ms for the whole run. 0 = unbounded. */
3221
- timeoutMs = 12e4;
3523
+ /** Idle ceiling in ms: max time since the last productive step before the run is killed (a long/active
3524
+ * step resets it — see runLoop's `lastProgressAt`). Not a whole-run wall clock. 0 = unbounded. */
3525
+ timeoutMs = 3e5;
3222
3526
  /** Idle-stall watchdog for a SINGLE streamed model request: abort it if no chunk (text OR reasoning)
3223
3527
  * arrives for this long. The between-steps `timeoutMs` can't preempt an in-flight `chat()`, so a
3224
3528
  * provider that goes silent mid-stream would otherwise park the loop forever. 0 = off. */
@@ -3229,15 +3533,33 @@ var AgentOptions = class {
3229
3533
  maxToolCalls = 100;
3230
3534
  /** External cancellation — abort the loop between steps (e.g. a UI "Cancel" button). */
3231
3535
  signal;
3536
+ /** Retry budget for an EMPTY/silent-turn (a delegated-runtime wedge: cursor's warm helper spins then
3537
+ * returns a content-less end, surfaced as `code:'empty'`/`code:'cursor_silent'`). Tuned WIDER than the
3538
+ * network-drop budget (fixed 2) because clearing a wedge needs several reap+respawn cycles — each retry
3539
+ * hits a FRESH cursor session (the adapter evicts the wedged warm helper on the silent-end). Still bounded:
3540
+ * after exhaustion the turn surfaces `finishReason:'empty'` (non-zero exit), never infinite, never silent.
3541
+ * Env: AGENTX_EMPTY_RETRIES. */
3542
+ emptyRetries = envInt("AGENTX_EMPTY_RETRIES", 4);
3543
+ /** Base backoff (ms) between empty-turn retries; grows linearly per attempt (longer than the network
3544
+ * class's fixed 1s so a wedged helper has time to be reaped/respawned). Env: AGENTX_EMPTY_RETRY_BASE_MS. */
3545
+ emptyRetryBaseMs = envInt("AGENTX_EMPTY_RETRY_BASE_MS", 1500);
3232
3546
  /** 0 = never trim. Otherwise cap the messages sent per turn (system + most recent). */
3233
3547
  maxContextMessages = 0;
3234
3548
  /** Note-taking: keep the most-recent N tool-result outputs verbatim; collapse OLDER ones to a one-line
3235
3549
  * stub in the sent context (the model already consumed them — it can re-Read/re-run). The stored
3236
3550
  * transcript is never mutated. 0 = keep all verbatim. */
3237
3551
  keepToolOutputs = 0;
3552
+ /** How many of the most-recent image-bearing messages get their image REF expanded to real base64 in
3553
+ * the outgoing request. Older ones (and legacy inline base64) go on the wire as the elision stub. */
3554
+ keepRecentImages = 1;
3238
3555
  /** Token-aware backstop (~4 chars/token estimate). After note-taking, drop oldest messages from the
3239
3556
  * sent context until the estimate is under this ceiling (pairing-safe). 0 = off. */
3240
3557
  maxContextTokens = 0;
3558
+ /** The MODEL's context window (tokens) — what the context % is measured against and what `/compact`
3559
+ * targets 50% of. Filled by the CLI from model metadata; 0 = fall back to 200k. Deliberately NOT
3560
+ * `maxTokens`: that is the cumulative SPEND kill-switch, and a spend budget must never be able to
3561
+ * drive destructive compaction (`--max-tokens 50000` would otherwise shred the transcript). */
3562
+ contextWindow = 0;
3241
3563
  /** Pagination ceiling for a SINGLE tool result (bytes). A result over this is cropped to page 1 with
3242
3564
  * a marker telling the model it was cropped (refine the query, or page further). Guards against one
3243
3565
  * Grep/Read/MCP call blowing the whole context window. 0 = off. Default 60k (~15k tokens). */
@@ -3293,8 +3615,10 @@ var AgentOptions = class {
3293
3615
  /** Stream tokens from the model. Takes effect only with a `host.notify`; off => current (non-stream) behavior. */
3294
3616
  stream = false;
3295
3617
  /** Closure reflex for delegated runtimes (cursor/claude-code): when such a turn ends on a tool call with
3296
- * no assistant text after it (the runtime went silent post-work), nudge ONE more step for a brief summary
3297
- * of what changed + what's pending — so the turn delivers a wrap-up instead of trailing off mid-narration.
3618
+ * no assistant text after it (the runtime went silent post-work), nudge ONE more step to ANSWER the user
3619
+ * from the tool results already in the transcript — so the turn delivers a reply instead of trailing off
3620
+ * mid-narration. Deliberately not a "summarize what you did" nudge: that produced process reports and
3621
+ * permission-asking instead of answers (see the nudge text in runLoop).
3298
3622
  * Fires at most once per run and only for delegated runtimes (native turns never emit the trigger). */
3299
3623
  closeDelegatedTurns = true;
3300
3624
  /** Fold the dropped middle of an over-long transcript into a synthetic summary (edge-safe, no LLM). Off => drop-oldest. */
@@ -3481,7 +3805,7 @@ var Agent = class _Agent {
3481
3805
  const disk = new NodeDiskFilesystem2(process.cwd());
3482
3806
  await disk.init();
3483
3807
  this.options.fs = new JailedFilesystem2(disk);
3484
- log4.info(`no fs provided \u2014 defaulting to jailed real disk at ${process.cwd()}`);
3808
+ log6.info(`no fs provided \u2014 defaulting to jailed real disk at ${process.cwd()}`);
3485
3809
  }
3486
3810
  this.buildCtx();
3487
3811
  }
@@ -3599,19 +3923,42 @@ var Agent = class _Agent {
3599
3923
  return this.runLoop();
3600
3924
  }
3601
3925
  /**
3602
- * Fold the conversation in place (manual `/compact`): keep the system message + the
3603
- * most-recent window, summarizing the dropped middle (deterministic, no LLM call).
3604
- * No-op when the transcript already fits. Returns the number of messages removed.
3605
- * `focus` (e.g. from `/compact keep the API details`) preserves matching lines from the
3606
- * dropped span verbatim in the summary, instead of losing them to the generic recap.
3926
+ * Fold the conversation in place (manual `/compact`) until it fits a TOKEN budget — not a message
3927
+ * count. Message-count folding alone frees almost nothing when a few recent tool results are huge,
3928
+ * so we escalate: fold the middle into a synthetic recap → elide oversized tool-result bodies in the
3929
+ * retained window → halve the window → drop-oldest backstop. Deterministic, no LLM call.
3930
+ * `focus` (e.g. `/compact keep the API details`) preserves matching lines from the dropped span verbatim.
3931
+ * Returns before/after token estimates so the caller can report honestly (0 freed => say so).
3607
3932
  */
3608
3933
  compactNow(maxMessages = 12, focus) {
3609
- const max = Math.max(2, maxMessages);
3610
- if (this.transcript.length <= max) return 0;
3934
+ const cap = _Agent.contextCap(this.options);
3935
+ const target = Math.round(cap * _Agent.COMPACT_TARGET);
3936
+ const kr = this.options.keepRecentImages ?? 1;
3937
+ const tokensBefore = estimateTokens(this.transcript, kr);
3938
+ const msgsBefore = this.transcript.length;
3939
+ let max = Math.max(2, maxMessages);
3940
+ if (this.transcript.length <= max && tokensBefore <= target)
3941
+ return { tokensBefore, tokensAfter: tokensBefore, target, cap, removed: 0 };
3611
3942
  void this.activeHooks?.onPreCompact?.(this.transcript);
3612
- const before = this.transcript.length;
3613
- this.transcript = compact(this.transcript, max, focus);
3614
- return before - this.transcript.length;
3943
+ let out = this.transcript;
3944
+ if (out.length > max) out = compact(out, max, focus);
3945
+ if (estimateTokens(out, kr) > target) out = elideOversizedImages(out, target, true, kr);
3946
+ if (estimateTokens(out, kr) > target) out = capOversizedBodies(out, target, kr);
3947
+ if (estimateTokens(out, kr) > target) out = elideOversizedImages(out, target, false, kr);
3948
+ while (estimateTokens(out, kr) > target && max > 2 && out.length > 2) {
3949
+ max = Math.max(2, Math.floor(max / 2));
3950
+ out = capOversizedBodies(compact(out, max, focus), target, kr);
3951
+ }
3952
+ if (estimateTokens(out, kr) > target) out = fitTokenBudget(out, target, kr);
3953
+ out = dropOrphanToolResults(out);
3954
+ this.transcript = out;
3955
+ this.releaseStaleImages();
3956
+ return { tokensBefore, tokensAfter: estimateTokens(this.transcript, kr), target, cap, removed: msgsBefore - this.transcript.length };
3957
+ }
3958
+ /** Drop inline base64 images from older messages so Bun/JSC can reclaim the heap (see contentBytes). */
3959
+ releaseStaleImages(keepRecent = 1) {
3960
+ const next = elideStaleImages(this.transcript, keepRecent);
3961
+ if (next !== this.transcript) this.transcript = next;
3615
3962
  }
3616
3963
  async runLoop() {
3617
3964
  const o = this.options;
@@ -3627,15 +3974,16 @@ var Agent = class _Agent {
3627
3974
  let lastFp = "";
3628
3975
  let repeats = 0;
3629
3976
  let closureNudged = false;
3977
+ let lastProgressAt = start;
3630
3978
  const kill = (finishReason) => {
3631
- log4.warn(`kill-switch: ${finishReason} (steps=${steps}, tokens=${usage.totalTokens}, budgetTokens=${Math.round(usage.totalTokens - 0.9 * usage.cacheReadTokens)}, ms=${Date.now() - start - this.parkedMs}${this.parkedMs ? ` +${this.parkedMs} parked` : ""})`);
3979
+ log6.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` : ""})`);
3632
3980
  this.ctx.jobs?.killAll();
3633
3981
  return { text: lastAssistantText(this.transcript), steps, finishReason, messages: this.transcript, usage, usageEstimated };
3634
3982
  };
3635
3983
  while (true) {
3636
3984
  if (o.signal?.aborted) return kill("aborted");
3637
3985
  if (steps >= o.maxSteps) return kill("max_steps");
3638
- if (o.timeoutMs && Date.now() - start - this.parkedMs >= o.timeoutMs) return kill("timeout");
3986
+ if (o.timeoutMs && Date.now() - lastProgressAt - this.parkedMs >= o.timeoutMs) return kill("timeout");
3639
3987
  const budgetTokens = usage.totalTokens - 0.9 * usage.cacheReadTokens;
3640
3988
  if (o.maxTokens && budgetTokens >= o.maxTokens) return kill("budget");
3641
3989
  steps++;
@@ -3675,27 +4023,38 @@ var Agent = class _Agent {
3675
4023
  const r = await o.ai.chat({ model: o.model, messages: sent, tools: sentTools, stream: false, signal: o.signal, ...o.toolChoice ? { toolChoice: o.toolChoice } : {}, ...reasonOpts });
3676
4024
  res = r;
3677
4025
  }
4026
+ if (steps === 1 && this.isEmptyStop(res) && !this.pendingInjections.length) {
4027
+ const e = new Error("model returned an empty turn (finishReason=stop, no text, no tools)");
4028
+ e.code = "empty";
4029
+ throw e;
4030
+ }
3678
4031
  break;
3679
4032
  } catch (err2) {
3680
4033
  const sc = err2?.statusCode;
3681
4034
  const serverSide = sc >= 500 || sc === 408 || /Service Unavailable|overloaded|Internal server error|Bad gateway|Gateway time/i.test(String(err2?.message ?? ""));
3682
4035
  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));
3683
4036
  const stalled = err2?.code === "stall";
3684
- const transient = !o.signal?.aborted && !isAbortError(err2) && attempt < 2 && (network || serverSide || stalled);
4037
+ const empty = err2?.code === "empty" || err2?.code === "cursor_silent";
4038
+ const maxAttempts = empty ? Math.max(o.emptyRetries, 0) : 2;
4039
+ const transient = !o.signal?.aborted && !isAbortError(err2) && attempt < maxAttempts && (network || serverSide || stalled || empty);
3685
4040
  if (!transient) throw err2;
3686
- const waitMs = 1e3 * (attempt + 1);
3687
- log4.warn(`${stalled ? "model stall" : "network drop"} mid-step (${err2?.message ?? err2}) \u2014 retrying in ${waitMs}ms`);
3688
- o.host?.notify?.({ kind: "retry", message: `${stalled ? "model stalled" : "connection dropped"} \u2014 retrying step (#${attempt + 1})` });
4041
+ const waitMs = (empty ? o.emptyRetryBaseMs : 1e3) * (attempt + 1);
4042
+ log6.warn(`${empty ? "empty model turn" : stalled ? "model stall" : "network drop"} mid-step (${err2?.message ?? err2}) \u2014 retrying in ${waitMs}ms`);
4043
+ o.host?.notify?.({ kind: "retry", message: `${empty ? "model returned nothing" : stalled ? "model stalled" : "connection dropped"} \u2014 retrying step (#${attempt + 1})` });
3689
4044
  await new Promise((r) => setTimeout(r, waitMs));
3690
4045
  }
3691
4046
  }
3692
4047
  } catch (err2) {
3693
4048
  if (err2?.code === "budget") return kill("budget");
3694
4049
  if (err2?.code === "stall") {
3695
- log4.warn(`model stalled out after retries: ${err2?.message ?? err2}`);
4050
+ log6.warn(`model stalled out after retries: ${err2?.message ?? err2}`);
3696
4051
  return kill("timeout");
3697
4052
  }
3698
4053
  if (o.signal?.aborted || isAbortError(err2)) return kill("aborted");
4054
+ if (err2?.code === "empty" || err2?.code === "cursor_silent") {
4055
+ log6.warn(`model returned empty/silent turns after retries \u2014 surfacing as failed`);
4056
+ return { text: "", steps, finishReason: "empty", messages: this.transcript, usage, usageEstimated, error: err2 };
4057
+ }
3699
4058
  const body = err2?.body ?? err2?.response?.data ?? err2?.error;
3700
4059
  let bodyStr;
3701
4060
  try {
@@ -3705,7 +4064,7 @@ var Agent = class _Agent {
3705
4064
  }
3706
4065
  if (bodyStr && err2 instanceof Error && !err2.message.includes(bodyStr)) err2.detail = bodyStr;
3707
4066
  const errLog = err2 instanceof Error ? { message: err2.message, ...err2.detail ? { detail: err2.detail } : {}, stack: err2.stack } : err2;
3708
- log4.error(`chat() failed: ${err2?.message ?? err2}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, errLog);
4067
+ log6.error(`chat() failed: ${err2?.message ?? err2}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, errLog);
3709
4068
  return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err2 };
3710
4069
  }
3711
4070
  if (o.signal?.aborted) return kill("aborted");
@@ -3749,6 +4108,7 @@ var Agent = class _Agent {
3749
4108
  }
3750
4109
  const toolCalls = res.toolCalls ?? [];
3751
4110
  const emptyTurn = toolCalls.length === 0 && delegatedTools.length === 0 && contentText(res.content ?? "").trim() === "";
4111
+ if (!emptyTurn) lastProgressAt = Date.now();
3752
4112
  if (!emptyTurn) {
3753
4113
  this.transcript.push({
3754
4114
  role: "assistant",
@@ -3760,12 +4120,16 @@ var Agent = class _Agent {
3760
4120
  if (this.drainInjections()) continue;
3761
4121
  if (o.closeDelegatedTurns && res.endedOnToolNoText && !closureNudged && steps < o.maxSteps) {
3762
4122
  closureNudged = true;
3763
- log4.verbose("delegated turn went silent after a tool \u2014 nudging a closing summary");
3764
- this.transcript.push({ role: "user", content: "Summarize what you did this turn and what (if anything) still needs doing. Be concise." });
4123
+ log6.verbose("delegated turn went silent after a tool \u2014 nudging a closing answer");
4124
+ this.transcript.push({
4125
+ role: "user",
4126
+ 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."
4127
+ });
3765
4128
  continue;
3766
4129
  }
3767
- log4.verbose(`completed in ${steps} step(s)`);
4130
+ log6.verbose(`completed in ${steps} step(s)`);
3768
4131
  await this.ctx.jobs?.drain();
4132
+ this.releaseStaleImages();
3769
4133
  await this.activeHooks?.onStop?.(res.content ?? "");
3770
4134
  return { text: res.content ?? "", steps, finishReason: "stop", messages: this.transcript, usage, usageEstimated };
3771
4135
  }
@@ -3791,7 +4155,7 @@ var Agent = class _Agent {
3791
4155
  if (this.ctx.needsInput) {
3792
4156
  const question = this.ctx.needsInput;
3793
4157
  this.ctx.needsInput = void 0;
3794
- log4.warn(`needs_input: ${question.question} (steps=${steps})`);
4158
+ log6.warn(`needs_input: ${question.question} (steps=${steps})`);
3795
4159
  await this.ctx.jobs?.drain();
3796
4160
  const text = [
3797
4161
  `OPEN QUESTION (run paused \u2014 no human was reachable): ${question.question}`,
@@ -3848,6 +4212,12 @@ var Agent = class _Agent {
3848
4212
  }
3849
4213
  };
3850
4214
  }
4215
+ /** A clean-stop turn that carries no assistant text AND no tool calls (native or delegated) — a
4216
+ * non-result the model produced by going silent. Callers retry it rather than report "done". */
4217
+ isEmptyStop(res) {
4218
+ const stop = res.finishReason === "stop" || res.finishReason == null;
4219
+ return stop && (res.toolCalls?.length ?? 0) === 0 && (res.delegatedTools?.length ?? 0) === 0 && contentText(res.content ?? "").trim() === "";
4220
+ }
3851
4221
  async consumeStream(stream, poke) {
3852
4222
  let content = "";
3853
4223
  let finishReason;
@@ -3906,13 +4276,13 @@ var Agent = class _Agent {
3906
4276
  const decision = await this.park(Promise.resolve(hooks?.preToolUse?.(call, meta)));
3907
4277
  if (decision?.block) {
3908
4278
  const blocked = `Blocked by hook: ${decision.reason ?? "no reason given"}`;
3909
- log4.debug(`${tc.function.name} -> ${blocked}`);
4279
+ log6.debug(`${tc.function.name} -> ${blocked}`);
3910
4280
  await hooks?.postToolUse?.(call, blocked, meta);
3911
4281
  return blocked;
3912
4282
  }
3913
4283
  this.options.host?.notify?.({ kind: "tool_use", id: tc.id ?? "", name: tc.function.name, input: args });
3914
4284
  if (earlyError) {
3915
- log4.debug(`${tc.function.name} -> ${earlyError}`);
4285
+ log6.debug(`${tc.function.name} -> ${earlyError}`);
3916
4286
  await hooks?.postToolUse?.(call, earlyError, meta);
3917
4287
  this.options.host?.notify?.({ kind: "tool_result", id: tc.id ?? "", output: earlyError, isError: true });
3918
4288
  return earlyError;
@@ -3921,12 +4291,12 @@ var Agent = class _Agent {
3921
4291
  let images;
3922
4292
  let threw = false;
3923
4293
  try {
3924
- log4.debug(`${tc.function.name}(${tc.function.arguments})`);
4294
+ log6.debug(`${tc.function.name}(${tc.function.arguments})`);
3925
4295
  this.ctx.emit = hooks?.onToolOutput ? (chunk) => {
3926
4296
  try {
3927
4297
  hooks.onToolOutput(call, chunk, meta);
3928
4298
  } catch (e) {
3929
- log4.debug(`onToolOutput hook error: ${e}`);
4299
+ log6.debug(`onToolOutput hook error: ${e}`);
3930
4300
  }
3931
4301
  } : void 0;
3932
4302
  const raw = await tool.run(args, this.ctx);
@@ -3938,7 +4308,7 @@ var Agent = class _Agent {
3938
4308
  }
3939
4309
  } catch (e) {
3940
4310
  const msg = e instanceof Error ? e.message : String(e);
3941
- log4.debug(`${tc.function.name} -> error: ${msg}`);
4311
+ log6.debug(`${tc.function.name} -> error: ${msg}`);
3942
4312
  result = `Error: ${msg}`;
3943
4313
  threw = true;
3944
4314
  } finally {
@@ -3964,6 +4334,16 @@ var Agent = class _Agent {
3964
4334
  }
3965
4335
  return images?.length ? { text: result, images } : result;
3966
4336
  }
4337
+ /** `/compact` aims to land the transcript at this fraction of the context window. */
4338
+ static COMPACT_TARGET = 0.5;
4339
+ /** THE context-window cap resolution — `/compact`'s budget and the CLI's context footer/warnings both
4340
+ * route here, so the percentage the user reads and the one compaction targets can never drift. */
4341
+ static contextCap(o) {
4342
+ if (o.maxContextTokens) return o.maxContextTokens;
4343
+ const w = o.contextWindow || 0;
4344
+ if (w > ANTHROPIC_WINDOW_WITHOUT_1M_BETA && isAnthropicModel(o.model)) return ANTHROPIC_WINDOW_WITHOUT_1M_BETA;
4345
+ return w || 2e5;
4346
+ }
3967
4347
  static WRITE_CLASS = ["Write", "Edit", "MultiEdit", "ApplyEdits"];
3968
4348
  /** Append an autoTest failure section to a write-class tool result, if configured. */
3969
4349
  async maybeAutoTest(toolName, result) {
@@ -3997,26 +4377,27 @@ ${out}`;
3997
4377
  const browserKeep = this.browserActive ? o.browserTrim?.keepOutputs : void 0;
3998
4378
  const keep = browserKeep != null ? o.keepToolOutputs ? Math.min(o.keepToolOutputs, browserKeep) : browserKeep : o.keepToolOutputs;
3999
4379
  if (keep) out = stubOldToolResults(out ?? m, keep);
4380
+ out = expandImagesForSend(dropOrphanToolResults(out ?? m), o.keepRecentImages ?? 1);
4000
4381
  if (o.maxContextTokens) {
4001
- const pre = (out ?? m).length;
4002
- out = fitTokenBudget(out ?? m, o.maxContextTokens);
4382
+ const pre = out.length;
4383
+ out = fitTokenBudget(out, o.maxContextTokens, o.keepRecentImages ?? 1);
4003
4384
  const dropped = pre - out.length;
4004
4385
  if (dropped > 0 && dropped !== this.lastTrimNotified) {
4005
4386
  this.lastTrimNotified = dropped;
4006
4387
  o.host?.notify?.({ kind: "compaction", message: `context full \u2014 auto-trimmed ${dropped} oldest message(s) to fit ~${Math.round(o.maxContextTokens / 1e3)}k tokens` });
4007
4388
  }
4008
4389
  }
4009
- return dropOrphanToolResults(out ?? m);
4390
+ return dropOrphanToolResults(out);
4010
4391
  }
4011
4392
  };
4012
4393
  function dropOldest(m, max) {
4013
4394
  const head = m[0]?.role === "system" ? [m[0]] : [];
4014
4395
  return [...head, ...m.slice(-(max - head.length))];
4015
4396
  }
4016
- function estimateTokens(m) {
4017
- let chars = 0;
4018
- for (const x of m) chars += contentText(x.content).length + (x.tool_calls ? JSON.stringify(x.tool_calls).length : 0);
4019
- return Math.ceil(chars / 4);
4397
+ var ANTHROPIC_WINDOW_WITHOUT_1M_BETA = 2e5;
4398
+ var isAnthropicModel = (m) => !!m && (m.startsWith("anthropic/") || /(^|\/)claude-/.test(m));
4399
+ function estimateTokens(m, keepRecentImages = 1) {
4400
+ return Math.ceil(sendBytes(m, keepRecentImages) / 4);
4020
4401
  }
4021
4402
  function cropResult(result, cap) {
4022
4403
  const head = result.slice(0, cap);
@@ -4050,6 +4431,60 @@ function stubOldToolResults(messages, keep) {
4050
4431
  return { ...x, content: `[${x.name ?? "tool"}${where ? ` ${where}` : ""} output elided \u2014 ${lines} lines; re-run the tool to view]` };
4051
4432
  });
4052
4433
  }
4434
+ function elideOversizedImages(messages, target, protectNewest = false, keepRecentImages = 1) {
4435
+ let out = messages;
4436
+ const withImages = out.map((_, i) => i).filter((i) => messageHasImage(out[i].content));
4437
+ const newest = protectNewest ? withImages[withImages.length - 1] : -1;
4438
+ const weight = sendBytesPer(out, keepRecentImages);
4439
+ const heaviestFirst = withImages.filter((i) => i !== newest).sort((a, b) => weight[b] - weight[a]);
4440
+ for (const i of heaviestFirst) {
4441
+ if (estimateTokens(out, keepRecentImages) <= target) break;
4442
+ const c = elideImages(out[i].content);
4443
+ if (c === out[i].content) continue;
4444
+ out = out.map((m, j) => j === i ? { ...m, content: c } : m);
4445
+ }
4446
+ return out;
4447
+ }
4448
+ function capOversizedBodies(messages, target, keepRecentImages = 1) {
4449
+ let out = messages;
4450
+ for (const cap of [8e3, 4e3, 2e3, 800, 300, 120]) {
4451
+ if (estimateTokens(out, keepRecentImages) <= target) break;
4452
+ out = out.map((x) => x.role === "system" ? x : elideBody(x, cap));
4453
+ }
4454
+ return out;
4455
+ }
4456
+ function elideBody(x, cap) {
4457
+ const note = (bytes, imgs2) => `
4458
+
4459
+ [${bytes} bytes elided by /compact${x.role === "tool" ? " \u2014 re-run the tool to view" : ""}${imgs2 ? `; ${imgs2} image part(s) KEPT in full \u2014 they still cost context` : ""}]`;
4460
+ if (!Array.isArray(x.content)) {
4461
+ const text = contentText(x.content);
4462
+ if (text.length <= cap) return x;
4463
+ return { ...x, content: `${text.slice(0, cap)}${note(text.length - cap, 0)}` };
4464
+ }
4465
+ const imgs = x.content.filter((p) => p.type !== "text").length;
4466
+ let left = cap;
4467
+ let elided = 0;
4468
+ const parts = [];
4469
+ for (const p of x.content) {
4470
+ if (p.type !== "text") {
4471
+ parts.push(p);
4472
+ continue;
4473
+ }
4474
+ const t = p.text ?? "";
4475
+ if (t.length <= left) {
4476
+ left -= t.length;
4477
+ if (t) parts.push(p);
4478
+ continue;
4479
+ }
4480
+ const keep = left;
4481
+ left = 0;
4482
+ elided += t.length - keep;
4483
+ if (keep > 0) parts.push({ ...p, text: t.slice(0, keep) });
4484
+ }
4485
+ if (!elided) return parts.length === x.content.length || !parts.length ? x : { ...x, content: parts };
4486
+ return { ...x, content: [...parts, { type: "text", text: note(elided, imgs).trimStart() }] };
4487
+ }
4053
4488
  var callIdSet = (msgs) => {
4054
4489
  const ids = /* @__PURE__ */ new Set();
4055
4490
  for (const m of msgs) if (m.role === "assistant") for (const tc of m.tool_calls ?? []) ids.add(tc.id);
@@ -4060,8 +4495,8 @@ function dropOrphanToolResults(messages) {
4060
4495
  const ok = (m) => m.role !== "tool" || ids.has(m.tool_call_id ?? "");
4061
4496
  return messages.every(ok) ? messages : messages.filter(ok);
4062
4497
  }
4063
- function fitTokenBudget(messages, maxTokens) {
4064
- const per = messages.map((x) => estimateTokens([x]));
4498
+ function fitTokenBudget(messages, maxTokens, keepRecentImages = 1) {
4499
+ const per = sendBytesPer(messages, keepRecentImages).map((b) => Math.ceil(b / 4));
4065
4500
  let total = per.reduce((a, b) => a + b, 0);
4066
4501
  if (total <= maxTokens) return messages;
4067
4502
  const head = messages[0]?.role === "system" ? [messages[0]] : [];
@@ -4070,7 +4505,7 @@ function fitTokenBudget(messages, maxTokens) {
4070
4505
  const ids = callIdSet(messages.slice(from));
4071
4506
  while (from < messages.length && messages[from].role === "tool" && !ids.has(messages[from].tool_call_id ?? "")) total -= per[from++];
4072
4507
  if (total > maxTokens)
4073
- log4.warn(`context ~${total} tok still over maxContextTokens=${maxTokens} after trimming (system head can't be dropped)`);
4508
+ log6.warn(`context ~${total} tok still over maxContextTokens=${maxTokens} after trimming (system head can't be dropped)`);
4074
4509
  return [...head, ...messages.slice(from)];
4075
4510
  }
4076
4511
  function compact(m, max, focus) {
@@ -4082,11 +4517,24 @@ function compact(m, max, focus) {
4082
4517
  if (dropped.length === 0) return [...head, ...tail];
4083
4518
  return [...head, { role: "system", content: summarize(dropped, focus) }, ...tail];
4084
4519
  }
4520
+ var SUMMARY_HEAD = "[summary of ";
4521
+ var MAX_CARRIED_COUNT = 1e4;
4522
+ var isSyntheticSummary = (m) => m.role === "system" && typeof m.content === "string" && m.content.startsWith(SUMMARY_HEAD);
4085
4523
  function summarize(messages, focus) {
4086
4524
  const tools = /* @__PURE__ */ new Set();
4087
4525
  const files = /* @__PURE__ */ new Set();
4526
+ const carried = [];
4527
+ let carriedCount = 0;
4528
+ let priors = 0;
4088
4529
  let lastAssistant = "";
4089
4530
  for (const msg of messages) {
4531
+ if (isSyntheticSummary(msg)) {
4532
+ priors++;
4533
+ const [head, ...rest] = String(msg.content).split("\n");
4534
+ carriedCount += Math.min(Math.max(Number(/of (\d+)/.exec(head)?.[1]) || 0, 0), MAX_CARRIED_COUNT);
4535
+ for (const l of rest) if (l.trim() && !carried.includes(l) && carried.length < 20) carried.push(l);
4536
+ continue;
4537
+ }
4090
4538
  for (const tc of msg.tool_calls ?? []) {
4091
4539
  tools.add(tc.function.name);
4092
4540
  try {
@@ -4097,14 +4545,16 @@ function summarize(messages, focus) {
4097
4545
  }
4098
4546
  if (msg.role === "assistant" && msg.content) lastAssistant = contentText(msg.content);
4099
4547
  }
4100
- const lines = [`[summary of ${messages.length} earlier message(s)]`];
4548
+ const lines = [`${SUMMARY_HEAD}${messages.length - priors + carriedCount} earlier message(s)]`];
4101
4549
  if (tools.size) lines.push(`tools used: ${[...tools].join(", ")}`);
4102
4550
  if (files.size) lines.push(`files touched: ${[...files].join(", ")}`);
4103
4551
  if (lastAssistant) lines.push(`last assistant: ${lastAssistant}`);
4552
+ if (carried.length) lines.push("earlier recap:", ...carried.map((l) => ` ${l}`));
4104
4553
  if (focus?.trim()) {
4105
4554
  const words = focus.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
4106
4555
  const kept = [];
4107
- outer: for (const msg of messages) {
4556
+ const ordered = [...messages.filter((m) => !isSyntheticSummary(m)), ...messages.filter(isSyntheticSummary)];
4557
+ outer: for (const msg of ordered) {
4108
4558
  if (msg.role === "tool") continue;
4109
4559
  for (const line of contentText(msg.content).split("\n")) {
4110
4560
  const l = line.toLowerCase();
@@ -4627,7 +5077,7 @@ init_tools_web();
4627
5077
  init_tools();
4628
5078
  init_tools_structured();
4629
5079
  init_logging();
4630
- var log5 = forComponent("scratch");
5080
+ var log7 = forComponent("scratch");
4631
5081
  var SCRATCH_DIR = "/scratch";
4632
5082
  function shortArgs(args) {
4633
5083
  try {
@@ -4667,7 +5117,7 @@ var Scratch = class {
4667
5117
  await (this.dirReady ??= mkdirp(this.fs, dir));
4668
5118
  await this.fs.writeFile(path, header + raw);
4669
5119
  } catch (e) {
4670
- log5.debug("scratch write failed; returning raw", e);
5120
+ log7.debug("scratch write failed; returning raw", e);
4671
5121
  return raw;
4672
5122
  }
4673
5123
  const preview = raw.slice(0, previewChars).replace(/\s+/g, " ").trim();
@@ -4697,7 +5147,7 @@ To pull a specific detail, Grep/Read ${path}, or call Ask({ question: "\u2026",
4697
5147
  await (this.dirReady ??= mkdirp(this.fs, dir));
4698
5148
  await this.fs.writeFile(path, header + full);
4699
5149
  } catch (e) {
4700
- log5.debug("scratch spill failed; cropping lossy", e);
5150
+ log7.debug("scratch spill failed; cropping lossy", e);
4701
5151
  return full.slice(0, pageBytes) + `
4702
5152
 
4703
5153
  [output cropped to ${pageBytes} of ${full.length} bytes; full output unavailable (scratch write failed) \u2014 refine your query]`;
@@ -4743,7 +5193,7 @@ Question: ${q2}`);
4743
5193
  const answer = (res.text ?? "").trim();
4744
5194
  return answer || "(no answer found in scratch)";
4745
5195
  } catch (e) {
4746
- log5.debug("Ask peek failed", e);
5196
+ log7.debug("Ask peek failed", e);
4747
5197
  return `Error querying scratch: ${e?.message ?? e}`;
4748
5198
  }
4749
5199
  }
@@ -4752,7 +5202,7 @@ Question: ${q2}`);
4752
5202
 
4753
5203
  // src/lessons.ts
4754
5204
  init_logging();
4755
- var log6 = forComponent("Lessons");
5205
+ var log8 = forComponent("Lessons");
4756
5206
  var LessonOptionsDefaults = class {
4757
5207
  minRepeats = 2;
4758
5208
  };
@@ -4777,15 +5227,16 @@ function lessonCapture(options) {
4777
5227
  counts.set(lesson.slug, n);
4778
5228
  if (n < o.minRepeats) return;
4779
5229
  written.add(lesson.slug);
4780
- await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) => log6.warn(`could not persist ${lesson.slug}: ${e?.message ?? e}`));
4781
- log6.debug(`captured lesson ${lesson.slug} (recurred ${n}\xD7)`);
5230
+ await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) => log8.warn(`could not persist ${lesson.slug}: ${e?.message ?? e}`));
5231
+ log8.debug(`captured lesson ${lesson.slug} (recurred ${n}\xD7)`);
4782
5232
  }
4783
5233
  };
4784
5234
  }
4785
5235
 
4786
5236
  // src/reflect.ts
5237
+ init_llm();
4787
5238
  init_logging();
4788
- var log7 = forComponent("Reflect");
5239
+ var log9 = forComponent("Reflect");
4789
5240
  async function reflectOnRun(o) {
4790
5241
  const digest = digestRun(o.result.messages, o.maxDigestChars ?? 6e3);
4791
5242
  if (!digest.trim()) return null;
@@ -4801,7 +5252,7 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
4801
5252
  const r = await o.ai.chat({ model: o.model, messages: [{ role: "user", content: prompt }], stream: false });
4802
5253
  text = r?.content ?? "";
4803
5254
  } catch (e) {
4804
- log7.warn(`reflection call failed: ${e?.message ?? e}`);
5255
+ log9.warn(`reflection call failed: ${e?.message ?? e}`);
4805
5256
  return null;
4806
5257
  }
4807
5258
  const m = text.match(/LESSON:\s*(.+)/i);
@@ -4811,10 +5262,10 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
4811
5262
  try {
4812
5263
  await writeFact(o.fs, o.dir, slug2, lesson);
4813
5264
  } catch (e) {
4814
- log7.warn(`could not persist lesson: ${e?.message ?? e}`);
5265
+ log9.warn(`could not persist lesson: ${e?.message ?? e}`);
4815
5266
  return null;
4816
5267
  }
4817
- log7.debug(`reflection persisted ${slug2}`);
5268
+ log9.debug(`reflection persisted ${slug2}`);
4818
5269
  return slug2;
4819
5270
  }
4820
5271
  function digestRun(messages, maxChars) {
@@ -4830,6 +5281,7 @@ function digestRun(messages, maxChars) {
4830
5281
 
4831
5282
  // src/duplex.ts
4832
5283
  import { MemFilesystem as MemFilesystem2 } from "@livx.cc/wcli/core";
5284
+ init_llm();
4833
5285
  init_logging();
4834
5286
 
4835
5287
  // node_modules/@bod.ee/voice/src/core/logging.ts
@@ -4862,7 +5314,7 @@ function forComponent2(name) {
4862
5314
  }
4863
5315
 
4864
5316
  // node_modules/@bod.ee/voice/src/core/emotion.ts
4865
- var log8 = forComponent2("Emotion");
5317
+ var log10 = forComponent2("Emotion");
4866
5318
  var EMOTIONS = [
4867
5319
  // primary (best results)
4868
5320
  "neutral",
@@ -4994,7 +5446,7 @@ function renderEmotions(text, opts = { show: true }) {
4994
5446
  speech += cartesiaTag(tag);
4995
5447
  if (opts.show) display += m[0];
4996
5448
  } else {
4997
- log8.debug(`dropping unknown emotion tag ${m[0]}`);
5449
+ log10.debug(`dropping unknown emotion tag ${m[0]}`);
4998
5450
  }
4999
5451
  last = m.index + m[0].length;
5000
5452
  }
@@ -5036,7 +5488,7 @@ var EmotionStream = class {
5036
5488
  if (tag) {
5037
5489
  this.pending = tag;
5038
5490
  if (this.show) display += m[0];
5039
- } else log8.debug(`dropping unknown emotion tag ${m[0]}`);
5491
+ } else log10.debug(`dropping unknown emotion tag ${m[0]}`);
5040
5492
  last = m.index + m[0].length;
5041
5493
  }
5042
5494
  this.emit(body.slice(last), (s, d, p) => {
@@ -5059,7 +5511,7 @@ var EmotionStream = class {
5059
5511
  };
5060
5512
 
5061
5513
  // node_modules/@bod.ee/voice/src/core/engine.ts
5062
- var log9 = forComponent2("VoiceEngine");
5514
+ var log11 = forComponent2("VoiceEngine");
5063
5515
  var realClock = {
5064
5516
  now: () => performance.now(),
5065
5517
  setTimeout: (fn, ms) => setTimeout(fn, ms),
@@ -5334,7 +5786,7 @@ var VoiceEngine = class _VoiceEngine {
5334
5786
  await Promise.all([this.tts.connect(), this.stt.start()]);
5335
5787
  this.tts.warmup?.();
5336
5788
  this.setState("listening");
5337
- log9.debug(`voice I/O up (${this.stt.usingAec ? "AEC" : "heuristic echo"} capture)`);
5789
+ log11.debug(`voice I/O up (${this.stt.usingAec ? "AEC" : "heuristic echo"} capture)`);
5338
5790
  }
5339
5791
  get usingAec() {
5340
5792
  return this.stt.usingAec;
@@ -5356,7 +5808,7 @@ var VoiceEngine = class _VoiceEngine {
5356
5808
  this.options.onDiag({ t: this.clock.now(), kind, ...fields });
5357
5809
  } catch (e) {
5358
5810
  this.diagOn = false;
5359
- log9.debug(`onDiag threw \u2014 diagnostics disabled: ${e instanceof Error ? e.message : e}`);
5811
+ log11.debug(`onDiag threw \u2014 diagnostics disabled: ${e instanceof Error ? e.message : e}`);
5360
5812
  }
5361
5813
  }
5362
5814
  idleWaiters = [];
@@ -5465,7 +5917,7 @@ var VoiceEngine = class _VoiceEngine {
5465
5917
  if (prose.trim()) this.repliedSinceDispatch = true;
5466
5918
  for (const w of this.words(this.reply)) this.echoWords.add(w);
5467
5919
  this.tts.speak(forSpeech(speech), true);
5468
- if (!this.spokeDeltas && this.turnStartAt) log9.debug(`ttft: ${Math.round(this.clock.now() - this.turnStartAt)}ms`);
5920
+ if (!this.spokeDeltas && this.turnStartAt) log11.debug(`ttft: ${Math.round(this.clock.now() - this.turnStartAt)}ms`);
5469
5921
  this.spokeDeltas = true;
5470
5922
  this.setState("speaking");
5471
5923
  if (this.options.revealMode !== "off" && display) {
@@ -5542,7 +5994,7 @@ var VoiceEngine = class _VoiceEngine {
5542
5994
  this.drainTimer = null;
5543
5995
  this.speaking = false;
5544
5996
  this.stopWordReveal(true);
5545
- if (this.turnStartAt) log9.debug(`turn: ${Math.round(this.clock.now() - this.turnStartAt)}ms (incl. playback)`);
5997
+ if (this.turnStartAt) log11.debug(`turn: ${Math.round(this.clock.now() - this.turnStartAt)}ms (incl. playback)`);
5546
5998
  this.echoUntil = this.clock.now() + 2500;
5547
5999
  if (!this.usingAec) this.stt.reset();
5548
6000
  this.setState("listening");
@@ -5630,7 +6082,7 @@ var VoiceEngine = class _VoiceEngine {
5630
6082
  this.clearAckTimer("interrupt");
5631
6083
  this.adaptiveAckFired = false;
5632
6084
  this.ackProbe = null;
5633
- if (this.uttQueue.length) log9.info(`barge-in dropped ${this.uttQueue.length} queued worker utterance(s)`);
6085
+ if (this.uttQueue.length) log11.info(`barge-in dropped ${this.uttQueue.length} queued worker utterance(s)`);
5634
6086
  const droppedQueued = this.uttQueue.length;
5635
6087
  this.uttQueue = [];
5636
6088
  if (!this.speaking && !this.drainTimer) return;
@@ -5776,7 +6228,7 @@ var VoiceEngine = class _VoiceEngine {
5776
6228
  if (flat && this.lastDispatchFlat && this.clock.now() - this.lastDispatchAt < _VoiceEngine.DUP_FINAL_MS && (this.lastDispatchFlat.startsWith(flat) || flat.startsWith(this.lastDispatchFlat))) return;
5777
6229
  this.specSpent = true;
5778
6230
  this.specText = this.specPartial;
5779
- log9.debug(`speculate: "${this.specText.slice(0, 60)}"`);
6231
+ log11.debug(`speculate: "${this.specText.slice(0, 60)}"`);
5780
6232
  this.diag("speculate", { text: this.specText });
5781
6233
  this.options.onSpeculate(this.specText);
5782
6234
  }, this.options.speculativeMs);
@@ -5845,7 +6297,7 @@ var VoiceEngine = class _VoiceEngine {
5845
6297
  };
5846
6298
  this.tts.newContext();
5847
6299
  this.tts.speak(phrase, false);
5848
- log9.debug(`backchannel: "${phrase}"`);
6300
+ log11.debug(`backchannel: "${phrase}"`);
5849
6301
  this.diag("backchannel", { phrase });
5850
6302
  this.options.onBackchannel(phrase);
5851
6303
  }
@@ -5878,7 +6330,7 @@ var VoiceEngine = class _VoiceEngine {
5878
6330
  });
5879
6331
  if (idx < 0) return text;
5880
6332
  toks.splice(idx, 1);
5881
- log9.verbose(`stripped backchannel echo "${this.lastBcPhrase}" from dispatching final`);
6333
+ log11.verbose(`stripped backchannel echo "${this.lastBcPhrase}" from dispatching final`);
5882
6334
  return toks.join(" ");
5883
6335
  }
5884
6336
  /** Merge a resumed utterance into the pending one, deduping any word-overlap. Soniox re-finalizes
@@ -5969,7 +6421,7 @@ var VoiceEngine = class _VoiceEngine {
5969
6421
  this.pendingUtt = this.mergeUtterance(this.pendingUtt, text);
5970
6422
  if (this.pendingTimer) this.clock.clearTimeout(this.pendingTimer);
5971
6423
  if (this.options.incompleteMergeMs && this.looksIncomplete(this.pendingUtt)) {
5972
- log9.verbose(`hold: incomplete utterance "${this.pendingUtt.slice(-40)}"`);
6424
+ log11.verbose(`hold: incomplete utterance "${this.pendingUtt.slice(-40)}"`);
5973
6425
  this.diag("hold", { reason: "incomplete", tail: this.pendingUtt.slice(-40) });
5974
6426
  this.options.onHold();
5975
6427
  if (this.options.holdFiller && !this.speaking) this.speakFiller(this.options.holdFiller);
@@ -6004,13 +6456,13 @@ var VoiceEngine = class _VoiceEngine {
6004
6456
  if (text) {
6005
6457
  const flat = text.toLowerCase().replace(/[^a-z0-9]/g, "");
6006
6458
  if (flat && flat === this.lastDispatchFlat && !this.repliedSinceDispatch && this.clock.now() - this.lastDispatchAt < _VoiceEngine.DUP_FINAL_MS) {
6007
- log9.verbose(`dropped duplicate final "${text.slice(0, 40)}" (identical to the just-dispatched utterance, no reply between)`);
6459
+ log11.verbose(`dropped duplicate final "${text.slice(0, 40)}" (identical to the just-dispatched utterance, no reply between)`);
6008
6460
  this.diag("utterance_drop", { reason: "dup_final", text: text.slice(0, 60) });
6009
6461
  return;
6010
6462
  }
6011
6463
  const refinal = this.refinalizes(text);
6012
6464
  if (refinal && this.repliedSinceDispatch) {
6013
- log9.verbose(`dropped re-finalization "${text.slice(0, 60)}" (superset of the just-dispatched utterance, reply already flowing)`);
6465
+ log11.verbose(`dropped re-finalization "${text.slice(0, 60)}" (superset of the just-dispatched utterance, reply already flowing)`);
6014
6466
  this.diag("utterance_drop", { reason: "refinalized", text: text.slice(0, 60) });
6015
6467
  this.lastDispatchFlat = flat;
6016
6468
  this.lastDispatchWords = text.trim().split(/\s+/);
@@ -6170,7 +6622,7 @@ var SpokenSplitter = class {
6170
6622
  };
6171
6623
 
6172
6624
  // node_modules/@bod.ee/voice/src/adapters/soniox.ts
6173
- var log10 = forComponent2("SonioxSTT");
6625
+ var log12 = forComponent2("SonioxSTT");
6174
6626
  var now = () => performance.now();
6175
6627
  var SonioxSTTOptions = class {
6176
6628
  auth = "";
@@ -6211,7 +6663,7 @@ var SonioxSTT = class {
6211
6663
  this.onDiag({ t: now(), kind, ...fields });
6212
6664
  } catch (e) {
6213
6665
  this.diagOn = false;
6214
- log10.debug(`onDiag threw \u2014 STT diagnostics disabled: ${e instanceof Error ? e.message : e}`);
6666
+ log12.debug(`onDiag threw \u2014 STT diagnostics disabled: ${e instanceof Error ? e.message : e}`);
6215
6667
  }
6216
6668
  }
6217
6669
  lastChunkAt = 0;
@@ -6253,11 +6705,11 @@ var SonioxSTT = class {
6253
6705
  this.ws.onmessage = (ev) => this.handle(JSON.parse(String(ev.data)));
6254
6706
  this.ws.onclose = (ev) => {
6255
6707
  if (this.stopped) return;
6256
- log10.warn(`soniox ws closed (${ev.code} ${ev.reason || ""}) \u2014 reconnecting`);
6708
+ log12.warn(`soniox ws closed (${ev.code} ${ev.reason || ""}) \u2014 reconnecting`);
6257
6709
  this.diag("stt_ws_closed", { code: ev.code, reason: String(ev.reason || ""), reconnecting: true });
6258
6710
  this.reset();
6259
6711
  this.connectWs().catch((e) => {
6260
- log10.error(`soniox reconnect failed: ${e.message}`);
6712
+ log12.error(`soniox reconnect failed: ${e.message}`);
6261
6713
  this.diag("stt_reconnect_failed", { message: e.message });
6262
6714
  });
6263
6715
  };
@@ -6269,7 +6721,7 @@ var SonioxSTT = class {
6269
6721
  this.endpointTimer = setInterval(() => {
6270
6722
  const combined = (this.finalText + this.partialText).trim();
6271
6723
  if (!combined || now() - this.lastChangeAt < this.options.silenceEndpointMs) return;
6272
- if (this.firstTokenAt) log10.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192silence-endpoint, "${combined.slice(0, 60)}"`);
6724
+ if (this.firstTokenAt) log12.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192silence-endpoint, "${combined.slice(0, 60)}"`);
6273
6725
  this.reset();
6274
6726
  this.onUtterance(combined, now());
6275
6727
  }, 120);
@@ -6281,7 +6733,7 @@ var SonioxSTT = class {
6281
6733
  if (this.stopped) return;
6282
6734
  const ref = this.lastChunkAt || this.startedChunksAt;
6283
6735
  if (now() - ref > noAudioMs) {
6284
- log10.error(`stt: no mic audio for >${Math.round(noAudioMs / 1e3)}s \u2014 capture device stopped delivering`);
6736
+ log12.error(`stt: no mic audio for >${Math.round(noAudioMs / 1e3)}s \u2014 capture device stopped delivering`);
6285
6737
  this.diag("stt_watchdog_fatal", { noAudioMs });
6286
6738
  this.onFatal("microphone stopped delivering audio (try a different input device, e.g. AirPods, or check System Settings \u2192 Sound \u2192 Input)");
6287
6739
  this.stop();
@@ -6304,7 +6756,7 @@ var SonioxSTT = class {
6304
6756
  handle(m) {
6305
6757
  if (m.error_message) {
6306
6758
  this.diag("stt_error", { message: String(m.error_message), code: m.error_code });
6307
- return log10.error(`soniox: ${m.error_message}`);
6759
+ return log12.error(`soniox: ${m.error_message}`);
6308
6760
  }
6309
6761
  let endpoint = false;
6310
6762
  for (const t of m.tokens ?? []) {
@@ -6321,7 +6773,7 @@ var SonioxSTT = class {
6321
6773
  this.onPartial(combined);
6322
6774
  if (endpoint && this.finalText.trim()) {
6323
6775
  const utterance = this.finalText.trim();
6324
- if (this.firstTokenAt) log10.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192endpoint, "${utterance.slice(0, 60)}"`);
6776
+ if (this.firstTokenAt) log12.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192endpoint, "${utterance.slice(0, 60)}"`);
6325
6777
  this.reset();
6326
6778
  this.onUtterance(utterance, now());
6327
6779
  }
@@ -6343,7 +6795,7 @@ var SonioxSTT = class {
6343
6795
  };
6344
6796
 
6345
6797
  // node_modules/@bod.ee/voice/src/adapters/cartesia.ts
6346
- var log11 = forComponent2("CartesiaTTS");
6798
+ var log13 = forComponent2("CartesiaTTS");
6347
6799
  var now2 = () => performance.now();
6348
6800
  var CartesiaTTSOptions = class {
6349
6801
  auth = "";
@@ -6379,7 +6831,7 @@ var CartesiaTTS = class _CartesiaTTS {
6379
6831
  this.onDiag({ t: now2(), kind, ...fields });
6380
6832
  } catch (e) {
6381
6833
  this.diagOn = false;
6382
- log11.debug(`onDiag threw \u2014 TTS diagnostics disabled: ${e instanceof Error ? e.message : e}`);
6834
+ log13.debug(`onDiag threw \u2014 TTS diagnostics disabled: ${e instanceof Error ? e.message : e}`);
6383
6835
  }
6384
6836
  }
6385
6837
  firstAudioAt = 0;
@@ -6414,11 +6866,11 @@ var CartesiaTTS = class _CartesiaTTS {
6414
6866
  this.ws.onerror = (e) => rej(new Error(`cartesia ws: ${e.message || "connect failed"}`));
6415
6867
  });
6416
6868
  this.ws.onclose = (ev) => {
6417
- log11.warn(`cartesia ws closed (${ev.code} ${ev.reason || ""})`);
6869
+ log13.warn(`cartesia ws closed (${ev.code} ${ev.reason || ""})`);
6418
6870
  this.diag("tts_ws_closed", { code: ev.code, reason: String(ev.reason || ""), reconnecting: !this.closed });
6419
6871
  if (!this.closed) {
6420
6872
  this.connecting = this.doConnect().catch((e) => {
6421
- log11.error(`cartesia reconnect failed: ${e.message}`);
6873
+ log13.error(`cartesia reconnect failed: ${e.message}`);
6422
6874
  this.diag("tts_reconnect_failed", { message: e.message });
6423
6875
  });
6424
6876
  }
@@ -6446,12 +6898,12 @@ var CartesiaTTS = class _CartesiaTTS {
6446
6898
  this.down = true;
6447
6899
  this.downAt = now2();
6448
6900
  this.consecutiveOk = 0;
6449
- log11.warn(`TTS circuit breaker open \u2014 ${this.consecutiveErrors} consecutive errors, switching to text-only`);
6901
+ log13.warn(`TTS circuit breaker open \u2014 ${this.consecutiveErrors} consecutive errors, switching to text-only`);
6450
6902
  this.diag("tts_breaker_open", { errors: this.consecutiveErrors });
6451
6903
  this.onDone();
6452
6904
  this.startProbe();
6453
6905
  } else if (!this.down) {
6454
- (/No valid transcripts/i.test(m.message || "") ? log11.debug : log11.warn)(`cartesia: ${JSON.stringify(m)}`);
6906
+ (/No valid transcripts/i.test(m.message || "") ? log13.debug : log13.warn)(`cartesia: ${JSON.stringify(m)}`);
6455
6907
  }
6456
6908
  }
6457
6909
  };
@@ -6466,7 +6918,7 @@ var CartesiaTTS = class _CartesiaTTS {
6466
6918
  this.stopProbe();
6467
6919
  const downMs = this.downAt ? now2() - this.downAt : 0;
6468
6920
  this.diag("tts_breaker_close", { downMs: Math.round(downMs) });
6469
- (downMs < 2e3 ? log11.debug : log11.info)(`TTS recovered${downMs ? ` (down ${downMs}ms)` : ""}`);
6921
+ (downMs < 2e3 ? log13.debug : log13.info)(`TTS recovered${downMs ? ` (down ${downMs}ms)` : ""}`);
6470
6922
  }
6471
6923
  /** Ensure the WS is open before sending — reconnects if idle-closed. */
6472
6924
  async ensureConnected() {
@@ -6557,7 +7009,7 @@ configureLogging((name) => {
6557
7009
  });
6558
7010
 
6559
7011
  // src/duplex.ts
6560
- var log12 = forComponent("DuplexAgent");
7012
+ var log14 = forComponent("DuplexAgent");
6561
7013
  function describeCall(call) {
6562
7014
  const v = call.args && Object.values(call.args).find((x) => typeof x === "string" && x.trim());
6563
7015
  const hint = v ? ` (${String(v).replace(/\s+/g, " ").trim().slice(0, 48)})` : "";
@@ -6768,7 +7220,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
6768
7220
  const m = this.reflexBuf.match(RESERVED_EVENT_MARKER) ?? this.reflexBuf.match(RESERVED_EVENT_OPENER);
6769
7221
  if (m) {
6770
7222
  this.fabricationCut = true;
6771
- log12.warn(`reflex fabricated a [task \u2026] event in its spoken stream \u2014 cutting it (kept ${m.index} chars)`);
7223
+ log14.warn(`reflex fabricated a [task \u2026] event in its spoken stream \u2014 cutting it (kept ${m.index} chars)`);
6772
7224
  const safe = this.reflexBuf.slice(this.reflexForwarded, m.index);
6773
7225
  if (!safe) return;
6774
7226
  if (safe.trim()) this.spokeThisTurn = true;
@@ -6861,7 +7313,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
6861
7313
  return {
6862
7314
  preToolUse: (call) => {
6863
7315
  if (this.spec?.state === "pending" && !_DuplexAgent.SPEC_SAFE_TOOLS.has(call.name)) {
6864
- log12.verbose(`speculation aborted: reflex called ${call.name} on unconfirmed input`);
7316
+ log14.verbose(`speculation aborted: reflex called ${call.name} on unconfirmed input`);
6865
7317
  this.abortSpeculation();
6866
7318
  return { block: true, reason: "Speculative turn aborted." };
6867
7319
  }
@@ -6899,7 +7351,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
6899
7351
  try {
6900
7352
  await this.voice.send(fallback ? "[reminder] You said nothing to the user this turn. Tell them, in ONE short spoken sentence, what just happened \u2014 no tools." : dispatched ? "[reminder] You dispatched a task but said nothing to the user. Say ONE short spoken acknowledgement now \u2014 no tools." : "[reminder] You said nothing to the user this turn. Give your ONE short spoken reply now \u2014 no tools.");
6901
7353
  } catch (e) {
6902
- log12.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
7354
+ log14.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
6903
7355
  } finally {
6904
7356
  this.nudging = false;
6905
7357
  }
@@ -6992,7 +7444,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
6992
7444
  try {
6993
7445
  res = await this.voice.send(spec.text);
6994
7446
  } catch (e) {
6995
- log12.warn(`speculative turn failed: ${e instanceof Error ? e.message : e}`);
7447
+ log14.warn(`speculative turn failed: ${e instanceof Error ? e.message : e}`);
6996
7448
  } finally {
6997
7449
  this.voice.options.signal = prevSignal;
6998
7450
  }
@@ -7006,7 +7458,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
7006
7458
  if (d === "abort") {
7007
7459
  if (this.voice.transcript.length > base) this.voice.transcript.length = base;
7008
7460
  this.speculativeAbortedCalls++;
7009
- log12.verbose(`speculation aborted (${this.speculativeAbortedCalls} total): "${spec.text.slice(0, 50)}"`);
7461
+ log14.verbose(`speculation aborted (${this.speculativeAbortedCalls} total): "${spec.text.slice(0, 50)}"`);
7010
7462
  if (this.spec === spec) this.spec = void 0;
7011
7463
  return res ?? empty;
7012
7464
  }
@@ -7271,7 +7723,7 @@ Another agent just implemented the above. Independently check the CURRENT state
7271
7723
  this.notify("task_verify", `task ${id}: verifying`, { id });
7272
7724
  const cres = await new Agent(checkerOpts).run(checkBrief);
7273
7725
  if (cres.finishReason !== "stop") {
7274
- log12.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
7726
+ log14.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
7275
7727
  this.notify("task_verify", `task ${id}: verify inconclusive (${cres.finishReason})`, { id, finishReason: cres.finishReason });
7276
7728
  }
7277
7729
  const sum = (a = 0, b = 0) => a + b;
@@ -7337,14 +7789,14 @@ Another agent just implemented the above. Independently check the CURRENT state
7337
7789
  * '[task <id> asks] …' and resolves with the user's answer via AnswerTask — or '' on timeout/
7338
7790
  * task settle (callers map '' to deny / best-judgment). Workers never block forever. */
7339
7791
  parkQuestion(askId, question) {
7340
- return new Promise((resolve4) => {
7792
+ return new Promise((resolve5) => {
7341
7793
  let settled = false;
7342
7794
  const finish = (answer) => {
7343
7795
  if (settled) return;
7344
7796
  settled = true;
7345
7797
  clearTimeout(timer);
7346
7798
  this.pendingAsks.delete(askId);
7347
- resolve4(answer);
7799
+ resolve5(answer);
7348
7800
  };
7349
7801
  const timer = setTimeout(() => {
7350
7802
  this.notify("task_ask_timeout", `task ${askId}: question timed out \u2014 proceeding without an answer`);
@@ -7406,8 +7858,8 @@ Another agent just implemented the above. Independently check the CURRENT state
7406
7858
  }
7407
7859
  rec.status = "done";
7408
7860
  rec.result = res.text;
7409
- const incomplete = res.finishReason !== "stop";
7410
- log12.verbose(`task ${id} done (${res.steps} steps${incomplete ? `, INCOMPLETE: ${res.finishReason}` : ""})`);
7861
+ const incomplete = res.finishReason !== "stop" && res.finishReason !== "empty";
7862
+ log14.verbose(`task ${id} done (${res.steps} steps${incomplete ? `, INCOMPLETE: ${res.finishReason}` : ""})`);
7411
7863
  this.notify("task_done", `task ${id} (${rec.label}) completed`, {
7412
7864
  id,
7413
7865
  text: res.text,
@@ -7449,14 +7901,14 @@ Another agent just implemented the above. Independently check the CURRENT state
7449
7901
  foldIfSuperseded(rec, note) {
7450
7902
  if (!(rec.deliveryParked && this.suppressParkedResume)) return false;
7451
7903
  if (note.trim()) this.voice.transcript.push({ role: "assistant", content: note });
7452
- log12.verbose(`task ${rec.id} settled but superseded (${rec.status}) \u2014 folded into transcript, not voiced`);
7904
+ log14.verbose(`task ${rec.id} settled but superseded (${rec.status}) \u2014 folded into transcript, not voiced`);
7453
7905
  return true;
7454
7906
  }
7455
7907
  failTask(rec, msg) {
7456
7908
  this.dropAsk(rec.id);
7457
7909
  rec.status = "error";
7458
7910
  rec.result = msg;
7459
- log12.warn(`task ${rec.id} failed: ${msg}`);
7911
+ log14.warn(`task ${rec.id} failed: ${msg}`);
7460
7912
  this.notify("task_error", `task ${rec.id} (${rec.label}) failed: ${msg}`);
7461
7913
  if (this.foldIfSuperseded(rec, `The "${rec.label}" task failed: ${msg}`)) return;
7462
7914
  this.queueRevoice(this.integrationPrompt(rec, "error", msg, "error"), true);
@@ -7761,11 +8213,11 @@ function buildMcpCatalog(servers) {
7761
8213
  }
7762
8214
  return { specs, routes };
7763
8215
  }
7764
- function searchOverCatalog(servers, specs, routes, resolve4, options) {
8216
+ function searchOverCatalog(servers, specs, routes, resolve5, options) {
7765
8217
  const tools = specs.length ? makeMcpToolSearch(specs, (name, args) => {
7766
8218
  const r = routes.get(name);
7767
8219
  if (!r) throw new Error(`unknown MCP tool '${name}' \u2014 use ToolSearch to find valid names`);
7768
- return resolve4(r.server, r.rawName, args ?? {});
8220
+ return resolve5(r.server, r.rawName, args ?? {});
7769
8221
  }, options) : [];
7770
8222
  return { tools, serverNames: servers, toolCount: specs.length };
7771
8223
  }
@@ -7777,6 +8229,7 @@ function makeMcpToolSearchFromMounted(mounted, options) {
7777
8229
 
7778
8230
  // src/index.ts
7779
8231
  init_logging();
8232
+ init_llm();
7780
8233
  init_worktree();
7781
8234
  import { MemFilesystem as MemFilesystem3, IndexedDbFilesystem, CommandExecutor as CommandExecutor2, registerHeadlessCommands as registerHeadlessCommands2 } from "@livx.cc/wcli/core";
7782
8235
 
@@ -7784,7 +8237,7 @@ import { MemFilesystem as MemFilesystem3, IndexedDbFilesystem, CommandExecutor a
7784
8237
  init_logging();
7785
8238
  import { spawn } from "child_process";
7786
8239
  import { createHash } from "crypto";
7787
- var log13 = forComponent("mcp");
8240
+ var log15 = forComponent("mcp");
7788
8241
  var PROTOCOL_VERSION = "2025-06-18";
7789
8242
  var DEFAULT_TIMEOUT_MS = 3e4;
7790
8243
  var StdioTransport = class {
@@ -7803,7 +8256,7 @@ var StdioTransport = class {
7803
8256
  proc.stdout.setEncoding("utf8");
7804
8257
  proc.stdout.on("data", (chunk) => this.onData(chunk));
7805
8258
  proc.stderr.setEncoding("utf8");
7806
- proc.stderr.on("data", (chunk) => log13.debug(`[${command}] stderr:`, chunk.trimEnd()));
8259
+ proc.stderr.on("data", (chunk) => log15.debug(`[${command}] stderr:`, chunk.trimEnd()));
7807
8260
  proc.on("exit", (code) => this.failAll(new Error(`MCP server "${command}" exited (code ${code})`)));
7808
8261
  proc.on("error", (e) => this.failAll(e instanceof Error ? e : new Error(String(e))));
7809
8262
  }
@@ -7817,7 +8270,7 @@ var StdioTransport = class {
7817
8270
  try {
7818
8271
  this.dispatch(JSON.parse(line));
7819
8272
  } catch (e) {
7820
- log13.debug("dropping non-JSON line from MCP server:", line, e);
8273
+ log15.debug("dropping non-JSON line from MCP server:", line, e);
7821
8274
  }
7822
8275
  }
7823
8276
  }
@@ -7843,12 +8296,12 @@ var StdioTransport = class {
7843
8296
  request(method, params) {
7844
8297
  const id = this.nextId++;
7845
8298
  const timeoutMs = this.spec.timeoutMs ?? DEFAULT_TIMEOUT_MS;
7846
- return new Promise((resolve4, reject) => {
8299
+ return new Promise((resolve5, reject) => {
7847
8300
  const timer = setTimeout(() => {
7848
8301
  this.pending.delete(id);
7849
8302
  reject(new Error(`MCP request "${method}" timed out after ${timeoutMs}ms`));
7850
8303
  }, timeoutMs);
7851
- this.pending.set(id, { resolve: resolve4, reject, timer });
8304
+ this.pending.set(id, { resolve: resolve5, reject, timer });
7852
8305
  try {
7853
8306
  this.write({ jsonrpc: "2.0", id, method, params });
7854
8307
  } catch (e) {
@@ -7866,7 +8319,7 @@ var StdioTransport = class {
7866
8319
  try {
7867
8320
  this.proc?.stdin?.end();
7868
8321
  } catch (e) {
7869
- log13.debug("stdin end failed", e);
8322
+ log15.debug("stdin end failed", e);
7870
8323
  }
7871
8324
  this.proc?.kill();
7872
8325
  }
@@ -7935,7 +8388,7 @@ function parseSseResponse(body) {
7935
8388
  const obj = JSON.parse(trimmed.slice(5).trim());
7936
8389
  if (obj && (obj.result !== void 0 || obj.error !== void 0)) return obj;
7937
8390
  } catch (e) {
7938
- log13.debug("skipping unparseable SSE data line", e);
8391
+ log15.debug("skipping unparseable SSE data line", e);
7939
8392
  }
7940
8393
  }
7941
8394
  return {};
@@ -7981,12 +8434,12 @@ function buildTransport(cfg) {
7981
8434
  }
7982
8435
  function withTimeout(p, ms, label) {
7983
8436
  if (!ms || ms <= 0) return p;
7984
- return new Promise((resolve4, reject) => {
8437
+ return new Promise((resolve5, reject) => {
7985
8438
  const timer = setTimeout(() => reject(new Error(`MCP "${label}" mount exceeded ${ms}ms`)), ms);
7986
8439
  timer.unref?.();
7987
8440
  p.then((v) => {
7988
8441
  clearTimeout(timer);
7989
- resolve4(v);
8442
+ resolve5(v);
7990
8443
  }, (e) => {
7991
8444
  clearTimeout(timer);
7992
8445
  reject(e);
@@ -8003,7 +8456,7 @@ async function mountWithDeadline(name, cfg, mountTimeoutMs) {
8003
8456
  return { name, client, tools, specs, serverInfo: init?.serverInfo, config: cfg };
8004
8457
  })(), mountTimeoutMs, name);
8005
8458
  } catch (e) {
8006
- await client.close().catch((err2) => log13.debug(`close after failed mount of "${name}": ${err2}`));
8459
+ await client.close().catch((err2) => log15.debug(`close after failed mount of "${name}": ${err2}`));
8007
8460
  throw e;
8008
8461
  }
8009
8462
  }
@@ -8014,15 +8467,15 @@ function validEntries(servers) {
8014
8467
  return Object.entries(servers).filter(([name, cfg]) => {
8015
8468
  if (!cfg || cfg.disabled) return false;
8016
8469
  if (!cfg.command && !cfg.url) {
8017
- log13.warn(`MCP server "${name}" needs a command (stdio) or url (http) \u2014 skipping`);
8470
+ log15.warn(`MCP server "${name}" needs a command (stdio) or url (http) \u2014 skipping`);
8018
8471
  return false;
8019
8472
  }
8020
8473
  return true;
8021
8474
  });
8022
8475
  }
8023
8476
  function logMountFailure(name, e) {
8024
- if (e instanceof McpAuthError) log13.warn(`MCP "${name}" needs-auth: HTTP ${e.status} \u2014 set bearerToken or headers in its config; skipping`);
8025
- else log13.error(`MCP server "${name}" failed to mount: ${e?.message ?? e}`);
8477
+ if (e instanceof McpAuthError) log15.warn(`MCP "${name}" needs-auth: HTTP ${e.status} \u2014 set bearerToken or headers in its config; skipping`);
8478
+ else log15.error(`MCP server "${name}" failed to mount: ${e?.message ?? e}`);
8026
8479
  }
8027
8480
  async function mountMcpServers(servers = {}, opts = {}) {
8028
8481
  const entries = validEntries(servers);
@@ -8032,7 +8485,7 @@ async function mountMcpServers(servers = {}, opts = {}) {
8032
8485
  const name = entries[i][0];
8033
8486
  if (r.status === "fulfilled") {
8034
8487
  out.push(r.value);
8035
- log13.debug(`MCP "${name}" mounted \u2014 ${r.value.tools.length} tool(s)${r.value.serverInfo?.name ? ` from ${r.value.serverInfo.name}` : ""}`);
8488
+ log15.debug(`MCP "${name}" mounted \u2014 ${r.value.tools.length} tool(s)${r.value.serverInfo?.name ? ` from ${r.value.serverInfo.name}` : ""}`);
8036
8489
  } else logMountFailure(name, r.reason);
8037
8490
  });
8038
8491
  return out;
@@ -8072,7 +8525,7 @@ var McpPool = class {
8072
8525
  const prev = this.warm.get(key);
8073
8526
  if (prev) {
8074
8527
  clearTimeout(prev.timer);
8075
- if (prev.client !== client) void prev.client.close().catch((err2) => log13.debug(`warm-pool replace close failed: ${err2}`));
8528
+ if (prev.client !== client) void prev.client.close().catch((err2) => log15.debug(`warm-pool replace close failed: ${err2}`));
8076
8529
  }
8077
8530
  const e = { client, timer: void 0 };
8078
8531
  this.warm.set(key, e);
@@ -8089,7 +8542,7 @@ var McpPool = class {
8089
8542
  const e = this.warm.get(key);
8090
8543
  if (!e) return;
8091
8544
  this.warm.delete(key);
8092
- await e.client.close().catch((err2) => log13.debug(`warm-pool evict close failed: ${err2}`));
8545
+ await e.client.close().catch((err2) => log15.debug(`warm-pool evict close failed: ${err2}`));
8093
8546
  }
8094
8547
  async closeAll() {
8095
8548
  for (const e of this.warm.values()) {
@@ -8105,8 +8558,8 @@ var defaultPool = new McpPool();
8105
8558
 
8106
8559
  // cli/mcpOAuth.ts
8107
8560
  import { createServer } from "http";
8108
- import { randomBytes, createHash as createHash2 } from "crypto";
8109
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync } from "fs";
8561
+ import { randomBytes as randomBytes2, createHash as createHash2 } from "crypto";
8562
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync } from "fs";
8110
8563
  import { dirname as dirname2 } from "path";
8111
8564
  var McpOAuthOptions = class {
8112
8565
  /** Where to persist tokens. Mode 0600. */
@@ -8134,7 +8587,7 @@ var McpOAuth = class {
8134
8587
  // -- store I/O ----------------------------------------------------------------
8135
8588
  load() {
8136
8589
  try {
8137
- return existsSync(this.options.storePath) ? JSON.parse(readFileSync2(this.options.storePath, "utf8")) : {};
8590
+ return existsSync(this.options.storePath) ? JSON.parse(readFileSync3(this.options.storePath, "utf8")) : {};
8138
8591
  } catch {
8139
8592
  return {};
8140
8593
  }
@@ -8148,9 +8601,9 @@ var McpOAuth = class {
8148
8601
  async register(serverUrl) {
8149
8602
  const meta = await this.discover(serverUrl);
8150
8603
  const clientId = this.options.clientId ?? await this.registerClient(meta, serverUrl);
8151
- const verifier = b64url(randomBytes(32));
8604
+ const verifier = b64url(randomBytes2(32));
8152
8605
  const challenge = b64url(createHash2("sha256").update(verifier).digest());
8153
- const state = b64url(randomBytes(16));
8606
+ const state = b64url(randomBytes2(16));
8154
8607
  const { code, redirectUri } = await this.captureCode(meta.authorization_endpoint, clientId, challenge, state);
8155
8608
  const tok = await this.exchange(meta.token_endpoint, {
8156
8609
  grant_type: "authorization_code",
@@ -8232,7 +8685,7 @@ var McpOAuth = class {
8232
8685
  }
8233
8686
  /** Start a one-route loopback server, open the browser, and resolve with the returned auth code. */
8234
8687
  captureCode(authEndpoint, clientId, challenge, state) {
8235
- return new Promise((resolve4, reject) => {
8688
+ return new Promise((resolve5, reject) => {
8236
8689
  const server = createServer((req, res) => {
8237
8690
  const url = new URL(req.url ?? "/", "http://127.0.0.1");
8238
8691
  if (url.pathname !== "/callback") {
@@ -8246,7 +8699,7 @@ var McpOAuth = class {
8246
8699
  clearTimeout(timer);
8247
8700
  if (gotState !== state) return reject(new Error("OAuth state mismatch (possible CSRF) \u2014 aborting"));
8248
8701
  if (!code) return reject(new Error(`authorization failed: ${url.searchParams.get("error") ?? "no code"}`));
8249
- resolve4({ code, redirectUri });
8702
+ resolve5({ code, redirectUri });
8250
8703
  });
8251
8704
  let redirectUri = "";
8252
8705
  const timer = setTimeout(() => {
@@ -8299,7 +8752,7 @@ function defaultOpenBrowser(url) {
8299
8752
  // cli/core.ts
8300
8753
  import { randomUUID } from "crypto";
8301
8754
  import { execFile as execFile2 } from "child_process";
8302
- import { resolve, basename, join as join5 } from "path";
8755
+ import { resolve as resolve2, basename, join as join5 } from "path";
8303
8756
  import { existsSync as existsSync3, mkdirSync as mkdirSync4 } from "fs";
8304
8757
  import { platform, arch, release, userInfo, homedir as homedir2, tmpdir } from "os";
8305
8758
  init_tools_shell();
@@ -8307,7 +8760,7 @@ init_tools_shell();
8307
8760
  // src/tools.notify.ts
8308
8761
  init_logging();
8309
8762
  import { execFile } from "child_process";
8310
- var log15 = forComponent("notify");
8763
+ var log17 = forComponent("notify");
8311
8764
  function makeNotifyTool(opts = {}) {
8312
8765
  const platform2 = opts.platform ?? process.platform;
8313
8766
  const run = opts.exec ?? execFile;
@@ -8328,12 +8781,12 @@ function makeNotifyTool(opts = {}) {
8328
8781
  if (!msg) return "Error: empty message";
8329
8782
  const argv = platform2 === "darwin" ? ["osascript", ["-e", `display notification ${JSON.stringify(msg)} with title ${JSON.stringify(head)}`]] : platform2 === "linux" ? ["notify-send", [head, msg]] : null;
8330
8783
  if (!argv) return `Notifications unavailable on ${platform2}.`;
8331
- return new Promise((resolve4) => {
8784
+ return new Promise((resolve5) => {
8332
8785
  run(argv[0], argv[1], { timeout: 5e3 }, (e) => {
8333
8786
  if (e) {
8334
- log15.debug("notification failed", e);
8335
- resolve4(`Notification failed: ${e.message}`);
8336
- } else resolve4("Notification shown.");
8787
+ log17.debug("notification failed", e);
8788
+ resolve5(`Notification failed: ${e.message}`);
8789
+ } else resolve5("Notification shown.");
8337
8790
  });
8338
8791
  });
8339
8792
  }
@@ -8345,9 +8798,9 @@ import { BodDB as BodDB2 } from "@bod.ee/db";
8345
8798
 
8346
8799
  // cli/util.ts
8347
8800
  init_logging();
8348
- import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
8801
+ import { existsSync as existsSync2, readFileSync as readFileSync4 } from "fs";
8349
8802
  import { homedir } from "os";
8350
- var log16 = forComponent("cli-util");
8803
+ var log18 = forComponent("cli-util");
8351
8804
  function dotDirs(base, sub, opts = {}) {
8352
8805
  const home = opts.home ?? homedir();
8353
8806
  const dirs = [`${base}/.agent/${sub}`, `${base}/.claude/${sub}`, `${home}/.agent/${sub}`, `${home}/.claude/${sub}`];
@@ -8364,7 +8817,7 @@ function parseJson(text, fallback, what = "json") {
8364
8817
  try {
8365
8818
  return JSON.parse(text);
8366
8819
  } catch (e) {
8367
- log16.debug(`parseJson(${what}) failed: ${e.message}`);
8820
+ log18.debug(`parseJson(${what}) failed: ${e.message}`);
8368
8821
  return fallback;
8369
8822
  }
8370
8823
  }
@@ -8372,9 +8825,9 @@ function readJsonFile(path, fallback) {
8372
8825
  if (!existsSync2(path)) return fallback;
8373
8826
  let text;
8374
8827
  try {
8375
- text = readFileSync3(path, "utf8");
8828
+ text = readFileSync4(path, "utf8");
8376
8829
  } catch (e) {
8377
- log16.debug(`readJsonFile(${path}) unreadable: ${e.message}`);
8830
+ log18.debug(`readJsonFile(${path}) unreadable: ${e.message}`);
8378
8831
  return fallback;
8379
8832
  }
8380
8833
  return parseJson(text, fallback, path);
@@ -8466,7 +8919,7 @@ async function buildAgent(o) {
8466
8919
  throw new Error(
8467
8920
  "buildAgent: explicit `sandbox` required \u2014 false = disk (the REAL filesystem, full reach like Claude Code; secrets still hidden) or true = sandbox (an in-memory copy of cwd; the real disk is never modified). This forces a conscious sandboxing decision."
8468
8921
  );
8469
- const cwd = resolve(o.cwd ?? process.cwd());
8922
+ const cwd = resolve2(o.cwd ?? process.cwd());
8470
8923
  const disk = new NodeDiskFilesystem("/", { denySymlinks: false });
8471
8924
  await disk.init();
8472
8925
  const jailedDisk = new JailedFilesystem(disk);
@@ -8487,7 +8940,7 @@ async function buildAgent(o) {
8487
8940
  mem.setCwd(cwd);
8488
8941
  fs = mem;
8489
8942
  } else if (o.boddb) {
8490
- const root = resolve(o.boddb);
8943
+ const root = resolve2(o.boddb);
8491
8944
  mkdirSync4(root, { recursive: true });
8492
8945
  const db = new BodDB2({ path: join5(root, "meta.db"), sweepInterval: 0, vfs: { storageRoot: join5(root, "files") } });
8493
8946
  const bod = new BodDbFilesystem(db);
@@ -8503,7 +8956,7 @@ async function buildAgent(o) {
8503
8956
  const mounts = [];
8504
8957
  const notes = [];
8505
8958
  for (const d of o.addDirs) {
8506
- const abs = resolve(d);
8959
+ const abs = resolve2(d);
8507
8960
  let name = basename(abs) || "dir", prefix = `/${name}`, n = 2;
8508
8961
  while (used.has(prefix)) prefix = `/${name}-${n++}`;
8509
8962
  used.add(prefix);
@@ -8638,6 +9091,7 @@ The filesystem root '/' is the real machine root \u2014 you have full filesystem
8638
9091
  // Context-v2: by default, note-take over a long session (stub tool outputs older than the last 8).
8639
9092
  keepToolOutputs: o.keepToolOutputs ?? 8,
8640
9093
  ...o.maxContextTokens != null ? { maxContextTokens: o.maxContextTokens } : {},
9094
+ ...o.contextWindow ? { contextWindow: o.contextWindow } : {},
8641
9095
  ...o.browserTrim ? { browserTrim: o.browserTrim } : {}
8642
9096
  });
8643
9097
  }
@@ -8658,11 +9112,11 @@ var trunc = (s, n) => (s == null ? "" : String(s).length > n ? String(s).slice(0
8658
9112
  // cli/voice.ts
8659
9113
  init_logging();
8660
9114
  import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
8661
- import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync4, statSync as statSync3 } from "fs";
9115
+ import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
8662
9116
  import { homedir as homedir3 } from "os";
8663
9117
  import { dirname as dirname3, join as join6 } from "path";
8664
9118
  import { fileURLToPath } from "url";
8665
- var log17 = forComponent("VoiceIO");
9119
+ var log19 = forComponent("VoiceIO");
8666
9120
  var now3 = () => performance.now();
8667
9121
  var Player = class {
8668
9122
  proc = null;
@@ -8676,7 +9130,7 @@ var Player = class {
8676
9130
  ["-loglevel", "quiet", "-nodisp", "-fflags", "nobuffer", "-flags", "low_delay", "-probesize", "32", "-f", "s16le", "-ar", String(TTS_SAMPLE_RATE), "-ch_layout", "mono", "-i", "-"],
8677
9131
  { stdio: ["pipe", "ignore", "ignore"] }
8678
9132
  );
8679
- this.proc.on("error", (e) => log17.warn(`ffplay error: ${e.message}`));
9133
+ this.proc.on("error", (e) => log19.warn(`ffplay error: ${e.message}`));
8680
9134
  this.proc.stdin.on("error", () => {
8681
9135
  });
8682
9136
  this.bytesWritten = 0;
@@ -8714,7 +9168,7 @@ function detectFfmpegMic() {
8714
9168
  const devices = [...audio.matchAll(/\[(\d+)\] (.+)/g)].map(([, idx, name]) => ({ idx, name: name.trim() }));
8715
9169
  const mic = devices.find((d) => /microphone|built-in/i.test(d.name) && !/teams|blackhole|loopback/i.test(d.name)) ?? devices[0];
8716
9170
  if (!mic) throw new Error("no audio input device found");
8717
- log17.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
9171
+ log19.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
8718
9172
  return `:${mic.idx}`;
8719
9173
  }
8720
9174
  function detectedInputDevice() {
@@ -8747,18 +9201,18 @@ function resolveAecBinary() {
8747
9201
  if (!existsSync4(src)) return null;
8748
9202
  const cacheDir = join6(homedir3(), ".agent", "cache");
8749
9203
  const bin = join6(cacheDir, "mic-aec");
8750
- if (existsSync4(bin) && statSync3(bin).mtimeMs >= statSync3(src).mtimeMs) return bin;
9204
+ if (existsSync4(bin) && statSync4(bin).mtimeMs >= statSync4(src).mtimeMs) return bin;
8751
9205
  if (spawnSync2("which", ["swiftc"]).status !== 0) return null;
8752
9206
  mkdirSync5(cacheDir, { recursive: true });
8753
- log17.info("compiling AEC mic helper (first run)\u2026");
9207
+ log19.info("compiling AEC mic helper (first run)\u2026");
8754
9208
  const build = spawnSync2("swiftc", ["-O", "-o", bin, src, "-Xlinker", "-sectcreate", "-Xlinker", "__TEXT", "-Xlinker", "__info_plist", "-Xlinker", plist], { encoding: "utf8" });
8755
9209
  if (build.status !== 0) {
8756
- log17.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
9210
+ log19.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
8757
9211
  return null;
8758
9212
  }
8759
9213
  const sign = spawnSync2("codesign", ["-fs", "-", bin], { encoding: "utf8" });
8760
9214
  if (sign.status !== 0) {
8761
- log17.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
9215
+ log19.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
8762
9216
  return null;
8763
9217
  }
8764
9218
  return bin;
@@ -8800,16 +9254,16 @@ var NodeMicSource = class {
8800
9254
  this.proc = spawn2(this.bin, [], { stdio: ["ignore", "pipe", "ignore"] });
8801
9255
  } else {
8802
9256
  if (spawnSync2("which", ["ffmpeg"]).status !== 0) throw new Error("voice I/O unavailable: no AEC helper and no ffmpeg on PATH");
8803
- log17.info("mic: raw capture (no AEC) \u2014 echo handled heuristically; headphones recommended");
9257
+ log19.info("mic: raw capture (no AEC) \u2014 echo handled heuristically; headphones recommended");
8804
9258
  this.proc = spawn2(
8805
9259
  "ffmpeg",
8806
9260
  ["-loglevel", "error", "-f", "avfoundation", "-i", detectFfmpegMic(), "-ar", String(STT_SAMPLE_RATE), "-ac", "1", "-f", "s16le", "-"],
8807
9261
  { stdio: ["ignore", "pipe", "pipe"] }
8808
9262
  );
8809
- this.proc.stderr.on("data", (d) => log17.warn(`ffmpeg: ${String(d).trim()}`));
9263
+ this.proc.stderr.on("data", (d) => log19.warn(`ffmpeg: ${String(d).trim()}`));
8810
9264
  }
8811
9265
  this.proc.on("exit", (c) => {
8812
- if (c && !this.stopped) log17.error(`mic capture exited (${c}) \u2014 check mic permission / MIC_DEVICE / MIC_AEC=0`);
9266
+ if (c && !this.stopped) log19.error(`mic capture exited (${c}) \u2014 check mic permission / MIC_DEVICE / MIC_AEC=0`);
8813
9267
  });
8814
9268
  this.proc.stdout.on("data", (chunk) => onChunk(chunk));
8815
9269
  }
@@ -8875,7 +9329,7 @@ var AecDuplexAudio = class {
8875
9329
  this.proc.stdin.on("error", () => {
8876
9330
  });
8877
9331
  this.proc.on("exit", (c) => {
8878
- if (c && !this.stopped) log17.error(`aec duplex audio exited (${c}) \u2014 check mic permission / MIC_AEC=0`);
9332
+ if (c && !this.stopped) log19.error(`aec duplex audio exited (${c}) \u2014 check mic permission / MIC_AEC=0`);
8879
9333
  });
8880
9334
  this.proc.stdout.on("data", (chunk) => {
8881
9335
  this.gotChunk = true;
@@ -8891,7 +9345,7 @@ var AecDuplexAudio = class {
8891
9345
  openMicSettings();
8892
9346
  this.onFatal?.("microphone permission denied \u2014 enable it in System Settings \u2192 Privacy & Security \u2192 Microphone for your terminal, then restart it");
8893
9347
  }
8894
- } else log17.debug(`mic-aec: ${s}`);
9348
+ } else log19.debug(`mic-aec: ${s}`);
8895
9349
  }
8896
9350
  });
8897
9351
  if (!this.noVpio && !this.triedFallback) {
@@ -8900,7 +9354,7 @@ var AecDuplexAudio = class {
8900
9354
  this.triedFallback = true;
8901
9355
  this.noVpio = true;
8902
9356
  this._aec = false;
8903
- log17.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)");
9357
+ log19.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)");
8904
9358
  this.onDegrade?.();
8905
9359
  this.killProc();
8906
9360
  this.spawnHelper();
@@ -9077,7 +9531,7 @@ function fakeVoiceParts(uttFile) {
9077
9531
  timer = setInterval(() => {
9078
9532
  let buf;
9079
9533
  try {
9080
- buf = readFileSync4(uttFile, "utf8");
9534
+ buf = readFileSync5(uttFile, "utf8");
9081
9535
  } catch {
9082
9536
  return;
9083
9537
  }
@@ -9120,7 +9574,7 @@ function fakeVoiceParts(uttFile) {
9120
9574
  init_logging();
9121
9575
  import { appendFile, mkdirSync as mkdirSync6 } from "fs";
9122
9576
  import { dirname as dirname4 } from "path";
9123
- var log18 = forComponent("VoiceDiag");
9577
+ var log20 = forComponent("VoiceDiag");
9124
9578
  var JsonlDiagSink = class {
9125
9579
  constructor(path) {
9126
9580
  this.path = path;
@@ -9161,7 +9615,7 @@ var JsonlDiagSink = class {
9161
9615
  disable(e) {
9162
9616
  this.disabled = true;
9163
9617
  this.queue.length = 0;
9164
- log18.debug(`voice diag sink disabled (${this.path}): ${e instanceof Error ? e.message : e}`);
9618
+ log20.debug(`voice diag sink disabled (${this.path}): ${e instanceof Error ? e.message : e}`);
9165
9619
  }
9166
9620
  /** True once a failure permanently disabled the sink (tests/inspection). */
9167
9621
  get isDisabled() {
@@ -9175,7 +9629,7 @@ var JsonlDiagSink = class {
9175
9629
 
9176
9630
  // cli/config.ts
9177
9631
  import { homedir as homedir4 } from "os";
9178
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
9632
+ import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
9179
9633
  import { join as join7 } from "path";
9180
9634
  import { pathToFileURL } from "url";
9181
9635
  var FILES = ["config.ts", "config.js", "config.mjs", "config.json"];
@@ -9197,7 +9651,7 @@ function loadSettings(dir) {
9197
9651
  const p = join7(dir, ".agent", "settings.json");
9198
9652
  if (!existsSync5(p)) return {};
9199
9653
  try {
9200
- const raw = JSON.parse(readFileSync5(p, "utf8"));
9654
+ const raw = JSON.parse(readFileSync6(p, "utf8"));
9201
9655
  const cfg = {};
9202
9656
  if (raw.mcpServers && typeof raw.mcpServers === "object") cfg.mcpServers = raw.mcpServers;
9203
9657
  if (raw.permissions && typeof raw.permissions === "object") cfg.permissions = raw.permissions;
@@ -9231,7 +9685,7 @@ async function loadConfig(cwd) {
9231
9685
 
9232
9686
  // cli/hooks-config.ts
9233
9687
  import { spawnSync as spawnSync3 } from "child_process";
9234
- var log19 = forComponent("hooks");
9688
+ var log21 = forComponent("hooks");
9235
9689
  var escapeRegex = (s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
9236
9690
  function ruleMatches(rule, toolName) {
9237
9691
  if (!rule.tool || rule.tool === "*") return true;
@@ -9248,7 +9702,7 @@ function runCmd(rule, env) {
9248
9702
  });
9249
9703
  return { code: r.status ?? 1, out: ((r.stdout ?? "") + (r.stderr ?? "")).trim() };
9250
9704
  } catch (e) {
9251
- log19.debug(`hook command failed: ${rule.command}`, e);
9705
+ log21.debug(`hook command failed: ${rule.command}`, e);
9252
9706
  return { code: 1, out: String(e?.message ?? e) };
9253
9707
  }
9254
9708
  }
@@ -9352,10 +9806,10 @@ function formatDiff(ops, opts = {}) {
9352
9806
  }
9353
9807
 
9354
9808
  // cli/session.ts
9355
- import { existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync4, readdirSync, renameSync, symlinkSync as symlinkSync2, unlinkSync, readlinkSync } from "fs";
9809
+ import { existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync4, readdirSync, renameSync, symlinkSync as symlinkSync2, unlinkSync, readlinkSync } from "fs";
9356
9810
  import { homedir as homedir5 } from "os";
9357
9811
  import { join as join8 } from "path";
9358
- var log20 = forComponent("session");
9812
+ var log22 = forComponent("session");
9359
9813
  var globalDir = () => join8(homedir5(), ".agent", "sessions");
9360
9814
  var SessionStore = class {
9361
9815
  dir;
@@ -9400,15 +9854,15 @@ var SessionStore = class {
9400
9854
  }
9401
9855
  load(id) {
9402
9856
  if (!this.safeId(id)) {
9403
- log20.debug(`rejecting unsafe session id: ${id}`);
9857
+ log22.debug(`rejecting unsafe session id: ${id}`);
9404
9858
  return void 0;
9405
9859
  }
9406
9860
  const path = join8(this.dir, `${id}.json`);
9407
9861
  if (!existsSync6(path)) return void 0;
9408
9862
  try {
9409
- return JSON.parse(readFileSync6(path, "utf8"));
9863
+ return JSON.parse(readFileSync7(path, "utf8"));
9410
9864
  } catch (e) {
9411
- log20.debug(`unreadable session ${id} \u2014 ignoring`, e);
9865
+ log22.debug(`unreadable session ${id} \u2014 ignoring`, e);
9412
9866
  return void 0;
9413
9867
  }
9414
9868
  }
@@ -9419,9 +9873,9 @@ var SessionStore = class {
9419
9873
  for (const f of readdirSync(this.dir)) {
9420
9874
  if (!f.endsWith(".json")) continue;
9421
9875
  try {
9422
- metas.push(JSON.parse(readFileSync6(join8(this.dir, f), "utf8")).meta);
9876
+ metas.push(JSON.parse(readFileSync7(join8(this.dir, f), "utf8")).meta);
9423
9877
  } catch (e) {
9424
- log20.debug(`skipping unreadable session file ${f}`, e);
9878
+ log22.debug(`skipping unreadable session file ${f}`, e);
9425
9879
  }
9426
9880
  }
9427
9881
  return metas.sort((a, b) => b.updated - a.updated);
@@ -9442,7 +9896,7 @@ function globalSessionLoad(idOrPrefix) {
9442
9896
  if (existsSync6(exact)) {
9443
9897
  try {
9444
9898
  const target = readlinkSync(exact);
9445
- return JSON.parse(readFileSync6(target, "utf8"));
9899
+ return JSON.parse(readFileSync7(target, "utf8"));
9446
9900
  } catch {
9447
9901
  return void 0;
9448
9902
  }
@@ -9453,7 +9907,7 @@ function globalSessionLoad(idOrPrefix) {
9453
9907
  const base = f.slice(0, -5);
9454
9908
  if (base.includes(idOrPrefix) || base.endsWith(idOrPrefix)) {
9455
9909
  const target = readlinkSync(join8(gd, f));
9456
- return JSON.parse(readFileSync6(target, "utf8"));
9910
+ return JSON.parse(readFileSync7(target, "utf8"));
9457
9911
  }
9458
9912
  }
9459
9913
  } catch {
@@ -9475,7 +9929,7 @@ function globalSessionList() {
9475
9929
  }
9476
9930
  continue;
9477
9931
  }
9478
- metas.push(JSON.parse(readFileSync6(target, "utf8")).meta);
9932
+ metas.push(JSON.parse(readFileSync7(target, "utf8")).meta);
9479
9933
  } catch {
9480
9934
  }
9481
9935
  }
@@ -9589,13 +10043,13 @@ import { execFile as execFile3 } from "child_process";
9589
10043
  import { promisify } from "util";
9590
10044
  import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync8 } from "fs";
9591
10045
  import { homedir as homedir6 } from "os";
9592
- import { join as join9, resolve as resolve2, sep as sep2, parse as parsePath } from "path";
9593
- var log21 = forComponent("checkpoints");
10046
+ import { join as join9, resolve as resolve3, sep as sep3, parse as parsePath } from "path";
10047
+ var log23 = forComponent("checkpoints");
9594
10048
  var exec = promisify(execFile3);
9595
10049
  var DEFAULT_EXCLUDE = [".agent/", ".git/", "node_modules/", "dist/", "build/", ".next/", "target/", ".venv/", "__pycache__/", "*.log"];
9596
10050
  function isBroadRoot(p) {
9597
- const abs = resolve2(p);
9598
- return abs === resolve2(homedir6()) || abs === parsePath(abs).root;
10051
+ const abs = resolve3(p);
10052
+ return abs === resolve3(homedir6()) || abs === parsePath(abs).root;
9599
10053
  }
9600
10054
  var ShadowRepo = class {
9601
10055
  // undefined = unprobed; false = git/this root unusable
@@ -9636,7 +10090,7 @@ var ShadowRepo = class {
9636
10090
  writeFileSync5(join9(this.gitDir, "info", "exclude"), this.exclude.join("\n") + "\n");
9637
10091
  this.ready = true;
9638
10092
  } catch (e) {
9639
- log21.debug(`git checkpoints unavailable for ${this.workTree}`, e);
10093
+ log23.debug(`git checkpoints unavailable for ${this.workTree}`, e);
9640
10094
  this.ready = false;
9641
10095
  }
9642
10096
  return this.ready;
@@ -9647,7 +10101,7 @@ var ShadowRepo = class {
9647
10101
  }
9648
10102
  async commit(label, forced = []) {
9649
10103
  await this.run("add", "-A");
9650
- for (const p of forced) await this.run("add", "-f", "--", p).catch((e) => log21.debug(`force-add failed: ${p}`, e));
10104
+ for (const p of forced) await this.run("add", "-f", "--", p).catch((e) => log23.debug(`force-add failed: ${p}`, e));
9651
10105
  await this.run("commit", "--allow-empty", "-q", "-m", label);
9652
10106
  }
9653
10107
  /** Inject the CURRENT (pre-edit) content of `paths` into the turn-open restore point by amending it.
@@ -9655,8 +10109,8 @@ var ShadowRepo = class {
9655
10109
  * turn-boundary `add -A` would never have captured it. Amend (vs a new commit) keeps one restore
9656
10110
  * point per turn, so the REPL's turn↔frame mapping stays intact. */
9657
10111
  async amendForced(paths) {
9658
- for (const p of paths) await this.run("add", "-f", "--", p).catch((e) => log21.debug(`force-capture failed: ${p}`, e));
9659
- await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) => log21.debug("amend failed", e));
10112
+ for (const p of paths) await this.run("add", "-f", "--", p).catch((e) => log23.debug(`force-capture failed: ${p}`, e));
10113
+ await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) => log23.debug("amend failed", e));
9660
10114
  }
9661
10115
  /** Commits on `ref`, oldest-first (canonical index space). */
9662
10116
  async log(ref) {
@@ -9716,7 +10170,7 @@ var ShadowRepo = class {
9716
10170
  await this.run("gc", "--auto", "-q").catch(() => {
9717
10171
  });
9718
10172
  } catch (e) {
9719
- log21.debug("checkpoint prune failed", e);
10173
+ log23.debug("checkpoint prune failed", e);
9720
10174
  }
9721
10175
  }
9722
10176
  };
@@ -9742,12 +10196,12 @@ var GitCheckpoints = class {
9742
10196
  }
9743
10197
  /** cwd + any add-dir mounts that live OUTSIDE cwd (in-cwd mounts are already covered by the cwd root). */
9744
10198
  roots() {
9745
- const cwd = resolve2(this.options.workTree);
10199
+ const cwd = resolve3(this.options.workTree);
9746
10200
  const out = [{ workTree: cwd, gitDir: this.options.gitDir }];
9747
10201
  for (const d of this.options.addDirs ?? []) {
9748
- const abs = resolve2(d);
9749
- if (abs === cwd || abs.startsWith(cwd + sep2)) continue;
9750
- if (cwd.startsWith(abs + sep2)) continue;
10202
+ const abs = resolve3(d);
10203
+ if (abs === cwd || abs.startsWith(cwd + sep3)) continue;
10204
+ if (cwd.startsWith(abs + sep3)) continue;
9751
10205
  out.push({ workTree: abs, gitDir: join9(abs, ".agent", "checkpoints.git") });
9752
10206
  }
9753
10207
  return out;
@@ -9784,7 +10238,7 @@ var GitCheckpoints = class {
9784
10238
  use(sessionId) {
9785
10239
  if (sessionId === this.session) return;
9786
10240
  this.session = sessionId;
9787
- if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) => log21.debug("re-point failed", e));
10241
+ if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) => log23.debug("re-point failed", e));
9788
10242
  }
9789
10243
  async begin(label) {
9790
10244
  if (!await this.start()) return;
@@ -9804,7 +10258,7 @@ var GitCheckpoints = class {
9804
10258
  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
9805
10259
  `);
9806
10260
  } else {
9807
- log21.debug("checkpoint commit failed", e);
10261
+ log23.debug("checkpoint commit failed", e);
9808
10262
  survivors.push(this.repos[i]);
9809
10263
  survivorCaches.push(this.caches[i] ?? []);
9810
10264
  }
@@ -9819,8 +10273,8 @@ var GitCheckpoints = class {
9819
10273
  repoIndexFor(abs) {
9820
10274
  let best = -1, bestLen = -1;
9821
10275
  this.repos.forEach((r, i) => {
9822
- const wt = r.root.endsWith(sep2) ? r.root.slice(0, -1) : r.root;
9823
- if ((abs === wt || abs.startsWith(wt + sep2)) && wt.length > bestLen) {
10276
+ const wt = r.root.endsWith(sep3) ? r.root.slice(0, -1) : r.root;
10277
+ if ((abs === wt || abs.startsWith(wt + sep3)) && wt.length > bestLen) {
9824
10278
  best = i;
9825
10279
  bestLen = wt.length;
9826
10280
  }
@@ -9835,11 +10289,11 @@ var GitCheckpoints = class {
9835
10289
  preToolUse: async (call) => {
9836
10290
  if (!this.snapshotted || !this.repos.length) return;
9837
10291
  for (const rel of mutatedPaths(call)) {
9838
- const abs = resolve2(this.options.workTree, rel);
10292
+ const abs = resolve3(this.options.workTree, rel);
9839
10293
  if (this.forced.has(abs)) continue;
9840
10294
  this.forced.add(abs);
9841
10295
  const i = this.repoIndexFor(abs);
9842
- if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) => log21.debug("amendForced failed", e));
10296
+ if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) => log23.debug("amendForced failed", e));
9843
10297
  }
9844
10298
  }
9845
10299
  };
@@ -10039,7 +10493,7 @@ function completePath(listDir, ref) {
10039
10493
  // cli/lineEditor.ts
10040
10494
  import { emitKeypressEvents } from "readline";
10041
10495
  import { spawnSync as spawnSync4 } from "child_process";
10042
- import { writeFileSync as writeFileSync7, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
10496
+ import { writeFileSync as writeFileSync7, readFileSync as readFileSync8, unlinkSync as unlinkSync2 } from "fs";
10043
10497
  import { tmpdir as tmpdir2 } from "os";
10044
10498
  import { join as join11 } from "path";
10045
10499
 
@@ -10635,7 +11089,8 @@ var EditorState = class _EditorState {
10635
11089
  const lines = text.split("\n").length;
10636
11090
  const id = ++this.pasteSeq;
10637
11091
  const measure = multiline ? `+${lines} line${lines === 1 ? "" : "s"}` : `+${text.length} chars`;
10638
- const placeholder = `[Pasted text #${id} ${measure}]`;
11092
+ const preview = pastePreview(text);
11093
+ const placeholder = `[Pasted #${id}${preview ? ` "${preview}"` : ""} ${measure}]`;
10639
11094
  this.pastes.set(placeholder, text);
10640
11095
  this.insert(placeholder);
10641
11096
  } else {
@@ -10914,6 +11369,12 @@ var EditorState = class _EditorState {
10914
11369
  }
10915
11370
  };
10916
11371
  var REWIND = "\0REWIND";
11372
+ var PASTE_PREVIEW_MAX = 40;
11373
+ function pastePreview(text) {
11374
+ const line = text.split("\n").find((l) => l.trim()) ?? "";
11375
+ const clean2 = line.replace(/[[\]]/g, "").replace(/\s+/g, " ").trim();
11376
+ return clean2.length > PASTE_PREVIEW_MAX ? clean2.slice(0, PASTE_PREVIEW_MAX) + "\u2026" : clean2;
11377
+ }
10917
11378
  function applyVimNormal(s, key, str) {
10918
11379
  const k = key?.name;
10919
11380
  if (key?.ctrl || key?.meta) return "pass";
@@ -11228,7 +11689,7 @@ function createLineEditor(out, input = keyInput) {
11228
11689
  out.write("\x1B[?2004l");
11229
11690
  const r = spawnSync4(cmd, [...cargs, file], { stdio: "inherit" });
11230
11691
  if (r.status === 0) {
11231
- const text = readFileSync7(file, "utf8").replace(/\n$/, "");
11692
+ const text = readFileSync8(file, "utf8").replace(/\n$/, "");
11232
11693
  s.reset();
11233
11694
  if (text) s.insert(text);
11234
11695
  }
@@ -11264,10 +11725,10 @@ function createLineEditor(out, input = keyInput) {
11264
11725
  render(s, promptOf(), maxVisible, opts.status);
11265
11726
  };
11266
11727
  process.on("SIGWINCH", onResize);
11267
- return new Promise((resolve4) => {
11728
+ return new Promise((resolve5) => {
11268
11729
  activeAbort = () => {
11269
11730
  finish();
11270
- resolve4(null);
11731
+ resolve5(null);
11271
11732
  };
11272
11733
  const redraw = () => render(s, promptOf(), maxVisible, opts.status);
11273
11734
  let lastStatus = opts.status?.() ?? "";
@@ -11347,20 +11808,20 @@ function createLineEditor(out, input = keyInput) {
11347
11808
  if (action === "submit") {
11348
11809
  finish();
11349
11810
  opts.onSubmit?.();
11350
- return resolve4(s.expand());
11811
+ return resolve5(s.expand());
11351
11812
  }
11352
11813
  if (action === "eof") {
11353
11814
  finish();
11354
- return resolve4(null);
11815
+ return resolve5(null);
11355
11816
  }
11356
11817
  if (action === "rewind") {
11357
11818
  finish();
11358
- return resolve4(REWIND);
11819
+ return resolve5(REWIND);
11359
11820
  }
11360
11821
  if (action === "cancel") {
11361
11822
  if (s.buf.length === 0) {
11362
11823
  finish();
11363
- return resolve4(null);
11824
+ return resolve5(null);
11364
11825
  }
11365
11826
  s.reset();
11366
11827
  s.refresh();
@@ -11473,7 +11934,7 @@ function selectMenu(out, opts) {
11473
11934
  out.write(dim2(` ${filterHint}\u2191/\u2193 select \xB7 ${back}\u23CE choose \xB7 esc cancel${more}`));
11474
11935
  prevLines++;
11475
11936
  };
11476
- return new Promise((resolve4) => {
11937
+ return new Promise((resolve5) => {
11477
11938
  const wasRaw = !!keyInput.isRaw;
11478
11939
  menusOpen++;
11479
11940
  emitKeypressEvents(keyInput);
@@ -11491,7 +11952,7 @@ function selectMenu(out, opts) {
11491
11952
  } catch {
11492
11953
  }
11493
11954
  }
11494
- resolve4(val);
11955
+ resolve5(val);
11495
11956
  };
11496
11957
  const drillIn = () => {
11497
11958
  const it = items[sel];
@@ -11612,19 +12073,19 @@ function windowAround(count, sel, max) {
11612
12073
  var plainBuf = "";
11613
12074
  var plainEnded = false;
11614
12075
  function readPlainLine() {
11615
- return new Promise((resolve4) => {
12076
+ return new Promise((resolve5) => {
11616
12077
  const emit = () => {
11617
12078
  const nl = plainBuf.indexOf("\n");
11618
12079
  if (nl >= 0) {
11619
12080
  const line = plainBuf.slice(0, nl);
11620
12081
  plainBuf = plainBuf.slice(nl + 1);
11621
- resolve4(line);
12082
+ resolve5(line);
11622
12083
  return true;
11623
12084
  }
11624
12085
  if (plainEnded) {
11625
12086
  const rest = plainBuf;
11626
12087
  plainBuf = "";
11627
- resolve4(rest.length ? rest : null);
12088
+ resolve5(rest.length ? rest : null);
11628
12089
  return true;
11629
12090
  }
11630
12091
  return false;
@@ -11655,7 +12116,7 @@ import { spawnSync as spawnSync5 } from "child_process";
11655
12116
  import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3, chmodSync, existsSync as existsSync7 } from "fs";
11656
12117
  import { homedir as homedir8 } from "os";
11657
12118
  import { join as join12 } from "path";
11658
- var log22 = forComponent("os-sched");
12119
+ var log24 = forComponent("os-sched");
11659
12120
  var OsScheduler = class {
11660
12121
  options;
11661
12122
  constructor(options) {
@@ -11717,7 +12178,7 @@ var OsScheduler = class {
11717
12178
  }
11718
12179
  }
11719
12180
  } catch (e) {
11720
- log22.debug(`cancel ${id}`, e);
12181
+ log24.debug(`cancel ${id}`, e);
11721
12182
  }
11722
12183
  for (const f of [`${id}.json`, `${id}.sh`]) {
11723
12184
  try {
@@ -11834,7 +12295,7 @@ import { spawn as spawn3 } from "child_process";
11834
12295
  import { existsSync as existsSync8, mkdirSync as mkdirSync11, unlinkSync as unlinkSync4, readdirSync as readdirSync3 } from "fs";
11835
12296
  import { homedir as homedir9 } from "os";
11836
12297
  import { join as join13 } from "path";
11837
- var log23 = forComponent("remote-trigger");
12298
+ var log25 = forComponent("remote-trigger");
11838
12299
  var TRIGGER_DIR = () => join13(homedir9(), ".agent", "triggers");
11839
12300
  var sockPath = (sessionId, dir = TRIGGER_DIR()) => join13(dir, `${sessionId}.sock`);
11840
12301
  var TriggerServer = class {
@@ -11872,13 +12333,13 @@ var TriggerServer = class {
11872
12333
  conn.end(JSON.stringify({ ok: false, error: String(e) }) + "\n");
11873
12334
  }
11874
12335
  });
11875
- conn.on("error", (e) => log23.debug("trigger conn error", e));
12336
+ conn.on("error", (e) => log25.debug("trigger conn error", e));
11876
12337
  });
11877
- this.server.on("error", (e) => log23.debug("trigger server error", e));
12338
+ this.server.on("error", (e) => log25.debug("trigger server error", e));
11878
12339
  this.server.listen(p);
11879
12340
  this.path = p;
11880
12341
  } catch (e) {
11881
- log23.debug("trigger server unavailable", e);
12342
+ log25.debug("trigger server unavailable", e);
11882
12343
  }
11883
12344
  }
11884
12345
  /** Re-bind on /resume (the session id changed). */
@@ -12000,10 +12461,10 @@ var strike = C("9");
12000
12461
  var link = (text, url) => useColor ? `\x1B]8;;${url}\x1B\\${cyan(text)}\x1B]8;;\x1B\\` : `${text} (${url})`;
12001
12462
  var mdPalette = { bold, dim, cyan, italic, strike, green, link };
12002
12463
  var err = (s) => process.stderr.write(s);
12003
- var log24 = forComponent("cli");
12464
+ var log26 = forComponent("cli");
12004
12465
  var VERSION = (() => {
12005
12466
  try {
12006
- return JSON.parse(readFileSync8(new URL("../package.json", import.meta.url), "utf8")).version ?? "?";
12467
+ return JSON.parse(readFileSync9(new URL("../package.json", import.meta.url), "utf8")).version ?? "?";
12007
12468
  } catch {
12008
12469
  return "?";
12009
12470
  }
@@ -12255,7 +12716,7 @@ REPL multi-line: Option/Alt+Enter inserts a newline, or end a line with \\ to co
12255
12716
  REPL shortcuts: Shift+Tab cycles permission posture (ask \u2192 accept-edits \u2192 plan) \xB7 Alt+T toggles reasoning \xB7 Alt+P switches model \xB7 Ctrl+O toggles verbose tool output \xB7 Ctrl+X Ctrl+E edits the buffer in $EDITOR \xB7 \u2192 or Tab accepts the dim history ghost-suggestion \xB7 Alt+S/Ctrl+S stash/unstash.
12256
12717
  REPL steer/stash: type while a turn is running \u2192 Enter STEERS it (the text reaches the model at its next step, alongside the next tool result). Alt+S (or Ctrl+S) with text stashes it for AFTER the turn instead; on an empty prompt pops the next stashed entry for editing.
12257
12718
  REPL editing (emacs/readline): Ctrl-A/E line start/end \xB7 Ctrl-B/F char \xB7 Alt-B/F or Alt/Ctrl-\u2190/\u2192 word \xB7 Ctrl-W kill word \xB7 Ctrl-U/K kill to start/end \xB7 Ctrl-Y yank \xB7 Ctrl-_ undo \xB7 Alt-D kill word fwd \xB7 Ctrl-L clear screen. Set editorMode:'vim' (or /config) for modal vim editing.
12258
- REPL paste: large/multi-line pastes collapse to a [Pasted text +N lines] preview (expands on send); a pasted image/file path attaches as [Image]/[File]; Ctrl-V or /paste grabs a clipboard image (macOS).`;
12719
+ REPL paste: large/multi-line pastes collapse to a [Pasted #N "\u2026" +N lines] preview (expands on send); a pasted image/file path attaches as [Image]/[File]; Ctrl-V or /paste grabs a clipboard image (macOS).`;
12259
12720
  function newestModel() {
12260
12721
  return listModels().slice().sort((a, b) => (getModelInfo(b)?.releaseDate ?? "").localeCompare(getModelInfo(a)?.releaseDate ?? ""))[0] ?? "";
12261
12722
  }
@@ -12290,7 +12751,7 @@ function resolveModelOrNewest(model) {
12290
12751
  var ENV_KEY_ALIASES = { google: ["GEMINI_API_KEY"] };
12291
12752
  function loadEnvFile(file) {
12292
12753
  if (!existsSync9(file)) return;
12293
- for (const line of readFileSync8(file, "utf8").split("\n")) {
12754
+ for (const line of readFileSync9(file, "utf8").split("\n")) {
12294
12755
  const m = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
12295
12756
  if (!m || m[1] in process.env) continue;
12296
12757
  let val = m[2].trim();
@@ -12745,7 +13206,7 @@ function costOf(pricing, promptTokens = 0, completionTokens = 0, cacheCreationTo
12745
13206
  function turnCost(model, usage) {
12746
13207
  return costOf(getModelInfo(model)?.pricing, usage?.promptTokens ?? 0, usage?.completionTokens ?? 0, usage?.cacheCreationTokens ?? 0, usage?.cacheReadTokens ?? 0, model);
12747
13208
  }
12748
- async function evaluateGoal(ai, condition, transcript, log25) {
13209
+ async function evaluateGoal(ai, condition, transcript, log27) {
12749
13210
  const recent = transcript.filter((m) => m.role === "assistant").slice(-8).map((m) => {
12750
13211
  const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join(" ");
12751
13212
  return text.slice(0, 600);
@@ -12765,7 +13226,7 @@ ${recent}` }
12765
13226
  const match = r.content.match(/\{[\s\S]*\}/);
12766
13227
  if (match) return JSON.parse(match[0]);
12767
13228
  } catch (e) {
12768
- log25(dim(` (goal evaluator error: ${e?.message ?? e})
13229
+ log27(dim(` (goal evaluator error: ${e?.message ?? e})
12769
13230
  `));
12770
13231
  }
12771
13232
  return { met: false, reason: "evaluation unclear" };
@@ -12776,10 +13237,8 @@ function errInfo(e) {
12776
13237
  function fmtUsd(n) {
12777
13238
  return n >= 1 ? `$${n.toFixed(2)}` : `$${n.toFixed(4)}`;
12778
13239
  }
12779
- function estimateTranscriptTokens(messages) {
12780
- let chars = 0;
12781
- for (const m of messages) chars += (m.content?.length ?? 0) + (m.tool_calls ? JSON.stringify(m.tool_calls).length : 0);
12782
- return Math.ceil(chars / 4);
13240
+ function estimateTranscriptTokens(messages, keepRecentImages = 1) {
13241
+ return estimateTokens(messages, keepRecentImages);
12783
13242
  }
12784
13243
  function formatStatus(s) {
12785
13244
  const L = (k, v) => ` ${k.padEnd(12)} ${v}
@@ -12891,9 +13350,12 @@ function optsFor(args, ai, cfg = {}, extraTools = []) {
12891
13350
  const rules = [...flagRules, ...configRules, ...askRules];
12892
13351
  if (configRules.length) err(dim(` \u2295 ${configRules.length} permission rule(s) from config
12893
13352
  `));
13353
+ const model = resolveModelOrNewest(args.model ?? cfg.model);
12894
13354
  return {
12895
13355
  ai,
12896
- model: resolveModelOrNewest(args.model ?? cfg.model),
13356
+ model,
13357
+ // The real context window drives the context % and `/compact`'s budget (never `maxTokens` — that's spend).
13358
+ contextWindow: getModelInfo(model)?.contextWindow ?? 0,
12897
13359
  cwd: args.cwd,
12898
13360
  tools: cfg.tools,
12899
13361
  extraTools,
@@ -12909,7 +13371,7 @@ function optsFor(args, ai, cfg = {}, extraTools = []) {
12909
13371
  stream: args.stream,
12910
13372
  host: makeHost(args.outputFormat, { stream: true }),
12911
13373
  planMode: args.plan || cfg.planMode || false,
12912
- permissions: rules.length ? new PermissionPolicy({ rules, default: "allow", host: makeHost(), ask: makeAskResolver(resolve3(args.cwd ?? process.cwd())) }) : void 0,
13374
+ permissions: rules.length ? new PermissionPolicy({ rules, default: "allow", host: makeHost(), ask: makeAskResolver(resolve4(args.cwd ?? process.cwd())) }) : void 0,
12913
13375
  subagents: args.subagents || cfg.subagents || false,
12914
13376
  maxSteps: args.maxSteps ?? cfg.maxSteps,
12915
13377
  reasoning: args.reasoning ?? cfg.reasoning,
@@ -12985,7 +13447,7 @@ function mcpAgentTools(mounted, opts) {
12985
13447
  return tools;
12986
13448
  }
12987
13449
  async function closeMcp(mounted) {
12988
- await Promise.all(mounted.map((m) => m.client.close().catch((e) => log24.debug("mcp close failed", e))));
13450
+ await Promise.all(mounted.map((m) => m.client.close().catch((e) => log26.debug("mcp close failed", e))));
12989
13451
  }
12990
13452
  var IMG_EXT = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
12991
13453
  function mentionRefs(line) {
@@ -12998,11 +13460,13 @@ function readImageParts(cwd, line) {
12998
13460
  for (const ref of refs) {
12999
13461
  const mime = IMG_EXT[extname(ref).toLowerCase()];
13000
13462
  if (!mime) continue;
13001
- const abs = ref.startsWith("~/") ? untilde(ref) : resolve3(cwd, ref);
13463
+ const abs = ref.startsWith("~/") ? untilde(ref) : resolve4(cwd, ref);
13002
13464
  try {
13003
- parts.push(imagePart(`data:${mime};base64,${readFileSync8(abs).toString("base64")}`));
13465
+ statSync5(abs);
13004
13466
  } catch {
13467
+ continue;
13005
13468
  }
13469
+ parts.push(imageRefPart(abs, mime));
13006
13470
  }
13007
13471
  return parts;
13008
13472
  }
@@ -13012,9 +13476,9 @@ function pastePathClassifier(cwd) {
13012
13476
  if (!t || t.includes("\n")) return null;
13013
13477
  t = t.replace(/\\ /g, " ").replace(/^['"]|['"]$/g, "");
13014
13478
  if (!/^(\/|~\/|\.\/|\.\.\/)/.test(t)) return null;
13015
- const abs = t.startsWith("~/") ? untilde(t) : resolve3(cwd, t);
13479
+ const abs = t.startsWith("~/") ? untilde(t) : resolve4(cwd, t);
13016
13480
  try {
13017
- if (!statSync4(abs).isFile()) return null;
13481
+ if (!statSync5(abs).isFile()) return null;
13018
13482
  } catch {
13019
13483
  return null;
13020
13484
  }
@@ -13035,7 +13499,7 @@ async function expandMentions(fs, line) {
13035
13499
  if (loaded.includes(ref) || missing.includes(ref)) continue;
13036
13500
  if (ref.includes(":") && mcpMentionResolver) {
13037
13501
  const body = await mcpMentionResolver(ref).catch((e) => {
13038
- log24.debug("mcp mention resolve failed", e);
13502
+ log26.debug("mcp mention resolve failed", e);
13039
13503
  return null;
13040
13504
  });
13041
13505
  if (body != null) {
@@ -13121,7 +13585,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
13121
13585
  try {
13122
13586
  store.save(session);
13123
13587
  } catch (ex) {
13124
- log24.debug("mid-turn session flush failed", ex);
13588
+ log26.debug("mid-turn session flush failed", ex);
13125
13589
  }
13126
13590
  }
13127
13591
  return origNotify(e);
@@ -13152,9 +13616,8 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
13152
13616
  const cost = turnCost(agent.options.model, res.usage);
13153
13617
  const m = res.usageEstimated ? "~" : "";
13154
13618
  const tok = res.usage?.totalTokens ? `${m}${(res.usage.totalTokens / 1e3).toFixed(1)}k tok \xB7 ${cost > 0 ? m + fmtUsd(cost) + " \xB7 " : ""}` : "";
13155
- const lastUser = res.messages.map((m2) => m2.role).lastIndexOf("user");
13156
13619
  agent.options.host?.takeToolCount?.();
13157
- const tools = res.messages.slice(lastUser).filter((m2) => m2.role === "tool").length;
13620
+ const tools = res.messages.slice(msgsBefore).filter((m2) => m2.role === "tool").length;
13158
13621
  const ok = res.finishReason === "stop";
13159
13622
  const shortId = session.meta.id.replace(/^\d{8}-/, "");
13160
13623
  const didWork = agent.transcript.slice(msgsBefore).some((m2) => m2.role === "assistant" || m2.role === "tool");
@@ -13190,10 +13653,12 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
13190
13653
  }
13191
13654
  return { ok, res };
13192
13655
  }
13656
+ var remintSessionImages = (data, store, cwd) => remintImageRefs(data.messages, [join14(store.dir, `${data.meta.id}.images`), cwd]);
13193
13657
  function startSession(args, store, agent, cwd) {
13194
13658
  if (args.resume || args.cont) {
13195
13659
  const data = args.resume ? store.load(args.resume) ?? globalSessionLoad(args.resume) : store.latestData();
13196
13660
  if (data) {
13661
+ data.messages = elideStaleImages(remintSessionImages(data, store, cwd), 1);
13197
13662
  agent.transcript = data.messages;
13198
13663
  if (args.fork) {
13199
13664
  const now5 = Date.now();
@@ -13249,7 +13714,7 @@ function initInstructions(cwd) {
13249
13714
  function persistSetting(cwd, key, value) {
13250
13715
  const path = join14(cwd, ".agent", "settings.json");
13251
13716
  try {
13252
- const obj = existsSync9(path) ? JSON.parse(readFileSync8(path, "utf8")) : {};
13717
+ const obj = existsSync9(path) ? JSON.parse(readFileSync9(path, "utf8")) : {};
13253
13718
  if (obj[key] === value) return;
13254
13719
  obj[key] = value;
13255
13720
  mkdirSync12(dirname5(path), { recursive: true });
@@ -13269,14 +13734,14 @@ var isCancelTeardown = (e) => {
13269
13734
  function installCancelGuards(mounted) {
13270
13735
  process.on("unhandledRejection", (e) => {
13271
13736
  if (isCancelTeardown(e)) {
13272
- log24.debug("suppressed unhandledRejection (cursor stream cancel)", e);
13737
+ log26.debug("suppressed unhandledRejection (cursor stream cancel)", e);
13273
13738
  return;
13274
13739
  }
13275
- log24.error("unhandledRejection", e);
13740
+ log26.error("unhandledRejection", e);
13276
13741
  });
13277
13742
  process.on("uncaughtException", (e) => {
13278
13743
  if (isCancelTeardown(e)) {
13279
- log24.debug("suppressed uncaughtException (cursor stream cancel)", e);
13744
+ log26.debug("suppressed uncaughtException (cursor stream cancel)", e);
13280
13745
  return;
13281
13746
  }
13282
13747
  console.error(e);
@@ -13308,7 +13773,7 @@ async function repl(args, ai, cfg, cwd) {
13308
13773
  },
13309
13774
  tickMs: 15e3
13310
13775
  });
13311
- const agentxInvocation = () => process.argv[1] ? `${process.execPath} ${resolve3(process.argv[1])}` : "agentx";
13776
+ const agentxInvocation = () => process.argv[1] ? `${process.execPath} ${resolve4(process.argv[1])}` : "agentx";
13312
13777
  const osSched = new OsScheduler({ agentx: agentxInvocation() });
13313
13778
  const osBackend = osSched.available() ? {
13314
13779
  get sessionId() {
@@ -13547,7 +14012,7 @@ async function repl(args, ai, cfg, cwd) {
13547
14012
  quickLook: {
13548
14013
  branch: () => {
13549
14014
  try {
13550
- const head = readFileSync8(join14(cwd, ".git", "HEAD"), "utf8").trim();
14015
+ const head = readFileSync9(join14(cwd, ".git", "HEAD"), "utf8").trim();
13551
14016
  return head.startsWith("ref: refs/heads/") ? `branch: ${head.slice("ref: refs/heads/".length)}` : `detached HEAD at ${head.slice(0, 12)}`;
13552
14017
  } catch {
13553
14018
  return "not a git repository";
@@ -13613,6 +14078,7 @@ async function repl(args, ai, cfg, cwd) {
13613
14078
  };
13614
14079
  const setModel = (m) => {
13615
14080
  work.model = m;
14081
+ work.contextWindow = getModelInfo(m)?.contextWindow ?? 0;
13616
14082
  work.providerOptions = providerOptionsFor(m, cwd, cfg.mcpServers);
13617
14083
  if (dx) dx.options.actModel = m;
13618
14084
  persistModel(cwd, m);
@@ -13676,10 +14142,11 @@ async function repl(args, ai, cfg, cwd) {
13676
14142
  const pendingImages = [];
13677
14143
  const bangContext = [];
13678
14144
  const grabClipboardAttachment = () => {
13679
- const dir = join14(tmpdir3(), "agentx-pasted");
14145
+ const dir = join14(store.dir, `${session.meta.id}.images`);
13680
14146
  try {
13681
14147
  mkdirSync12(dir, { recursive: true });
13682
- } catch {
14148
+ } catch (e) {
14149
+ log26.debug("paste image dir create failed", e);
13683
14150
  }
13684
14151
  const img = grabClipboardImage(dir, String(Date.now()));
13685
14152
  return img ? { display: "Image", ref: "@" + img.path, path: img.path } : null;
@@ -13786,13 +14253,13 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
13786
14253
  </system-reminder>`;
13787
14254
  };
13788
14255
  const histPath = join14(cwd, ".agent", "history");
13789
- const history = existsSync9(histPath) ? readFileSync8(histPath, "utf8").split("\n").filter(Boolean).reverse().slice(0, 500) : [];
14256
+ const history = existsSync9(histPath) ? readFileSync9(histPath, "utf8").split("\n").filter(Boolean).reverse().slice(0, 500) : [];
13790
14257
  const remember = (line) => {
13791
14258
  try {
13792
14259
  mkdirSync12(join14(cwd, ".agent"), { recursive: true });
13793
14260
  appendFileSync(histPath, line + "\n");
13794
14261
  } catch (e) {
13795
- log24.debug("history write failed", e);
14262
+ log26.debug("history write failed", e);
13796
14263
  }
13797
14264
  };
13798
14265
  const ago = (t) => {
@@ -13800,6 +14267,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
13800
14267
  return s < 60 ? "just now" : s < 3600 ? `${Math.floor(s / 60)}m ago` : s < 86400 ? `${Math.floor(s / 3600)}h ago` : `${Math.floor(s / 86400)}d ago`;
13801
14268
  };
13802
14269
  const resumeInto = (data) => {
14270
+ data.messages = elideStaleImages(remintSessionImages(data, store, cwd), 1);
13803
14271
  face.transcript = data.messages;
13804
14272
  session = data;
13805
14273
  checkpoints.use?.(data.meta.id);
@@ -13863,7 +14331,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
13863
14331
  try {
13864
14332
  store.save(session);
13865
14333
  } catch (e) {
13866
- log24.debug("session save after rewind failed", e);
14334
+ log26.debug("session save after rewind failed", e);
13867
14335
  }
13868
14336
  err(green(" \u27F2 jumped back") + dim(` \u2014 ${face.transcript.length} message(s) kept; edit + resend
13869
14337
  `));
@@ -13897,7 +14365,7 @@ ${task}`;
13897
14365
  bangContext.length = 0;
13898
14366
  }
13899
14367
  const delta = await refreshCatalogs().catch((e) => {
13900
- log24.debug("catalog refresh failed", e);
14368
+ log26.debug("catalog refresh failed", e);
13901
14369
  return "";
13902
14370
  });
13903
14371
  if (delta) {
@@ -14075,8 +14543,8 @@ ${task}`;
14075
14543
  context: {
14076
14544
  desc: "context-window usage (messages + estimated tokens)",
14077
14545
  run: () => {
14078
- const est = estimateTranscriptTokens(face.transcript);
14079
- const cap = face.options.maxTokens || 2e5;
14546
+ const est = estimateTranscriptTokens(face.transcript, face.options.keepRecentImages);
14547
+ const cap = Agent.contextCap(face.options);
14080
14548
  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)
14081
14549
  `));
14082
14550
  }
@@ -14161,7 +14629,7 @@ ${task}`;
14161
14629
  desc: "rescan skills/commands dirs and rebuild the system prompt (one cache miss) \u2014 picks up entries created mid-session",
14162
14630
  run: async () => {
14163
14631
  await refreshCatalogs().catch((e) => {
14164
- log24.debug("catalog refresh failed", e);
14632
+ log26.debug("catalog refresh failed", e);
14165
14633
  });
14166
14634
  face.reprepare();
14167
14635
  err(green(` \u2713 reloaded \u2014 ${skills.length} skill(s), ${cmds.length} command(s); system prompt rebuilds on next message
@@ -14454,13 +14922,21 @@ ${task}`;
14454
14922
  desc: "summarize older context to free up the window \u2014 /compact [what to preserve]",
14455
14923
  run: (a) => {
14456
14924
  const focus = a.join(" ").trim() || void 0;
14457
- const n = face.compactNow(12, focus);
14925
+ const r = face.compactNow(12, focus);
14458
14926
  session.messages = face.transcript;
14459
14927
  try {
14460
14928
  store.save(session);
14461
14929
  } catch {
14462
14930
  }
14463
- err(dim(` compacted \u2014 folded ${n} message(s)${focus && n ? ` (preserving: ${focus})` : ""}
14931
+ const pct = (t) => `${Math.round(t / r.cap * 100)}%`;
14932
+ const k = (t) => `${(t / 1e3).toFixed(1)}k`;
14933
+ const freed = r.tokensBefore - r.tokensAfter;
14934
+ if (freed <= 0) {
14935
+ err(dim(` nothing to compact \u2014 ~${k(r.tokensAfter)} tok (${pct(r.tokensAfter)} of window) is already at the floor
14936
+ `));
14937
+ return;
14938
+ }
14939
+ err(dim(` compacted \u2014 ~${k(r.tokensBefore)} \u2192 ~${k(r.tokensAfter)} tok (${pct(r.tokensBefore)} \u2192 ${pct(r.tokensAfter)} of window), freed ~${k(freed)}; folded ${r.removed} message(s)${focus ? ` (preserving: ${focus})` : ""}
14464
14940
  `));
14465
14941
  }
14466
14942
  },
@@ -14655,7 +15131,7 @@ ${task}`;
14655
15131
  try {
14656
15132
  for (const def of (await loadAgents(fs2, d)).agents) if (!seen.has(def.name)) seen.set(def.name, { def, from: d });
14657
15133
  } catch (e) {
14658
- log24.debug(`loadAgents(${d}) failed`, e);
15134
+ log26.debug(`loadAgents(${d}) failed`, e);
14659
15135
  }
14660
15136
  }
14661
15137
  if (!seen.size) {
@@ -14743,7 +15219,7 @@ ${task}`;
14743
15219
  }
14744
15220
  if (idx >= 0) {
14745
15221
  const old = mounted.splice(idx, 1)[0];
14746
- await old.client.close().catch((e) => log24.debug("mcp close failed", e));
15222
+ await old.client.close().catch((e) => log26.debug("mcp close failed", e));
14747
15223
  }
14748
15224
  try {
14749
15225
  const m = await mountMcpServer(name, conf);
@@ -14771,7 +15247,7 @@ ${task}`;
14771
15247
  }
14772
15248
  const m = mounted.splice(idx, 1)[0];
14773
15249
  remountMcpTools();
14774
- await m.client.close().catch((e) => log24.debug("mcp close failed", e));
15250
+ await m.client.close().catch((e) => log26.debug("mcp close failed", e));
14775
15251
  err(dim(` removed "${name}"
14776
15252
  `));
14777
15253
  return;
@@ -14887,7 +15363,7 @@ ${task}`;
14887
15363
  }
14888
15364
  const md = exportMarkdown(session.meta, shown);
14889
15365
  const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" : join14(".agent", "exports", `${session.meta.id}.md`);
14890
- const path = resolve3(cwd, name);
15366
+ const path = resolve4(cwd, name);
14891
15367
  try {
14892
15368
  mkdirSync12(dirname5(path), { recursive: true });
14893
15369
  writeFileSync9(path, md);
@@ -14974,7 +15450,7 @@ ${task}`;
14974
15450
  try {
14975
15451
  return readdirSync4(join14(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
14976
15452
  } catch (e) {
14977
- log24.debug("completion readdir failed", absDir, e);
15453
+ log26.debug("completion readdir failed", absDir, e);
14978
15454
  return null;
14979
15455
  }
14980
15456
  };
@@ -15110,7 +15586,7 @@ ${out}
15110
15586
  return;
15111
15587
  }
15112
15588
  await refreshCatalogs().catch((e) => {
15113
- log24.debug("catalog refresh failed", e);
15589
+ log26.debug("catalog refresh failed", e);
15114
15590
  });
15115
15591
  const sk = skills.find((x) => x.name === name);
15116
15592
  if (sk) {
@@ -15326,8 +15802,8 @@ ${out}
15326
15802
  err("\n");
15327
15803
  const initial = prefill ?? (carry || void 0);
15328
15804
  prefill = void 0;
15329
- const ctxTok = estimateTranscriptTokens(face.transcript);
15330
- const ctxCap = face.options.maxTokens || 2e5;
15805
+ const ctxTok = estimateTranscriptTokens(face.transcript, face.options.keepRecentImages);
15806
+ const ctxCap = Agent.contextCap(face.options);
15331
15807
  {
15332
15808
  const pct = Math.round(ctxTok / ctxCap * 100);
15333
15809
  const step = pct >= 90 ? 90 : pct >= 80 ? 80 : 0;
@@ -15541,7 +16017,7 @@ async function main() {
15541
16017
  let worktreeCleanup;
15542
16018
  if (args.worktree) {
15543
16019
  const { enterWorktree: enterWorktree2, exitWorktree: exitWorktree2 } = await Promise.resolve().then(() => (init_worktree(), worktree_exports));
15544
- const session = enterWorktree2(args.worktree, resolve3(args.cwd ?? process.cwd()));
16020
+ const session = enterWorktree2(args.worktree, resolve4(args.cwd ?? process.cwd()));
15545
16021
  err(dim(` \u2295 worktree: ${session.worktreePath} (branch ${session.branch})
15546
16022
  `));
15547
16023
  worktreeCleanup = () => {
@@ -15551,7 +16027,7 @@ async function main() {
15551
16027
  `));
15552
16028
  };
15553
16029
  }
15554
- const cwd = resolve3(args.cwd ?? process.cwd());
16030
+ const cwd = resolve4(args.cwd ?? process.cwd());
15555
16031
  const cfg = await loadConfig(cwd);
15556
16032
  cfg.permissions = mergePerms(loadClaudeSettings(cwd), mergePerms(loadPersistedRules(cwd), cfg.permissions));
15557
16033
  if (canPrompt && !args.yes && !isTrusted(cwd)) {