reasonix 0.34.1 → 0.36.0

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.
Files changed (91) hide show
  1. package/dashboard/app.css +16 -14
  2. package/dashboard/dist/app.js +77 -18
  3. package/dashboard/dist/app.js.map +1 -1
  4. package/dist/cli/{chat-TD6GR3QK.js → chat-RGMYAOY2.js} +18 -17
  5. package/dist/cli/{chunk-SA4UGZPG.js → chunk-2MCYGFLK.js} +15 -10
  6. package/dist/cli/chunk-2MCYGFLK.js.map +1 -0
  7. package/dist/cli/{chunk-SW3CCXEV.js → chunk-4Q3GRJIU.js} +2 -2
  8. package/dist/cli/{chunk-5JXXEPDM.js → chunk-BHLHOS5Y.js} +8 -2
  9. package/dist/cli/chunk-BHLHOS5Y.js.map +1 -0
  10. package/dist/cli/{chunk-F3ILWP2L.js → chunk-BJ376EN3.js} +9 -8
  11. package/dist/cli/chunk-BJ376EN3.js.map +1 -0
  12. package/dist/cli/{chunk-2AWTGJ2C.js → chunk-CRPQUBP6.js} +26 -9
  13. package/dist/cli/{chunk-2AWTGJ2C.js.map → chunk-CRPQUBP6.js.map} +1 -1
  14. package/dist/cli/{chunk-EINEIIIW.js → chunk-EN4LAZW5.js} +563 -250
  15. package/dist/cli/chunk-EN4LAZW5.js.map +1 -0
  16. package/dist/cli/{chunk-OERAGRJX.js → chunk-IPCPEZWQ.js} +2 -2
  17. package/dist/cli/{chunk-U3V2ZQ5J.js → chunk-KJQIA4US.js} +6 -2
  18. package/dist/cli/chunk-KJQIA4US.js.map +1 -0
  19. package/dist/cli/{chunk-Q36KBLSU.js → chunk-MLXUGPJE.js} +292 -35
  20. package/dist/cli/chunk-MLXUGPJE.js.map +1 -0
  21. package/dist/cli/{chunk-SX6L4HZZ.js → chunk-QPNZWUZF.js} +53 -6
  22. package/dist/cli/chunk-QPNZWUZF.js.map +1 -0
  23. package/dist/cli/{chunk-LNTORE5K.js → chunk-QRUQ2BFT.js} +159 -40
  24. package/dist/cli/chunk-QRUQ2BFT.js.map +1 -0
  25. package/dist/cli/{chunk-KZHMKOJH.js → chunk-T52GAWPP.js} +25 -3
  26. package/dist/cli/chunk-T52GAWPP.js.map +1 -0
  27. package/dist/cli/{chunk-I6YIAK6C.js → chunk-UNMYFZPZ.js} +2 -2
  28. package/dist/cli/{update-4TJWRUIN.js → chunk-WJ3YX4PZ.js} +51 -12
  29. package/dist/cli/chunk-WJ3YX4PZ.js.map +1 -0
  30. package/dist/cli/{chunk-RXGEGA7K.js → chunk-XQIFIB3U.js} +18 -7
  31. package/dist/cli/{chunk-RXGEGA7K.js.map → chunk-XQIFIB3U.js.map} +1 -1
  32. package/dist/cli/{chunk-2EBODRRO.js → chunk-ZJR4QLXB.js} +5 -1
  33. package/dist/cli/{chunk-2EBODRRO.js.map → chunk-ZJR4QLXB.js.map} +1 -1
  34. package/dist/cli/{chunk-6TMHAK5D.js → chunk-ZU45XW3P.js} +2 -2
  35. package/dist/cli/code-KJB3WDU6.js +435 -0
  36. package/dist/cli/code-KJB3WDU6.js.map +1 -0
  37. package/dist/cli/{commands-MEZPSEHV.js → commands-FE2UDFBC.js} +3 -3
  38. package/dist/cli/{commit-CE4EFTUQ.js → commit-3IAGB22T.js} +5 -4
  39. package/dist/cli/commit-3IAGB22T.js.map +1 -0
  40. package/dist/cli/{doctor-YASM64X6.js → doctor-DKD34EFD.js} +7 -7
  41. package/dist/cli/index.js +35 -33
  42. package/dist/cli/index.js.map +1 -1
  43. package/dist/cli/{mcp-LDFK5QJI.js → mcp-2RDEQST6.js} +2 -2
  44. package/dist/cli/{mcp-browse-FYHEITCM.js → mcp-browse-VM5GLRBQ.js} +2 -2
  45. package/dist/cli/{mcp-inspect-T2HBR22P.js → mcp-inspect-CWSVCZUQ.js} +3 -3
  46. package/dist/cli/{prompt-V47QKSAR.js → prompt-YEKXMNNV.js} +3 -3
  47. package/dist/cli/{replay-JEDLU7F2.js → replay-D7RT2DR7.js} +3 -3
  48. package/dist/cli/replay-D7RT2DR7.js.map +1 -0
  49. package/dist/cli/{run-NHD2RSTD.js → run-FK5UBIIM.js} +13 -12
  50. package/dist/cli/run-FK5UBIIM.js.map +1 -0
  51. package/dist/cli/{server-MC4A4WAJ.js → server-W4XJK4GX.js} +17 -17
  52. package/dist/cli/{server-MC4A4WAJ.js.map → server-W4XJK4GX.js.map} +1 -1
  53. package/dist/cli/{sessions-ZHWJEW4L.js → sessions-YZXWMIWW.js} +10 -10
  54. package/dist/cli/{setup-DK43MT47.js → setup-IIAJXHP4.js} +196 -130
  55. package/dist/cli/setup-IIAJXHP4.js.map +1 -0
  56. package/dist/cli/update-GUCWB4UN.js +13 -0
  57. package/dist/cli/update-GUCWB4UN.js.map +1 -0
  58. package/dist/cli/{version-O362UKPM.js → version-DWD6RLIU.js} +12 -12
  59. package/dist/index.d.ts +19 -2
  60. package/dist/index.js +543 -78
  61. package/dist/index.js.map +1 -1
  62. package/package.json +1 -1
  63. package/dist/cli/chunk-5JXXEPDM.js.map +0 -1
  64. package/dist/cli/chunk-EINEIIIW.js.map +0 -1
  65. package/dist/cli/chunk-F3ILWP2L.js.map +0 -1
  66. package/dist/cli/chunk-KZHMKOJH.js.map +0 -1
  67. package/dist/cli/chunk-LNTORE5K.js.map +0 -1
  68. package/dist/cli/chunk-Q36KBLSU.js.map +0 -1
  69. package/dist/cli/chunk-SA4UGZPG.js.map +0 -1
  70. package/dist/cli/chunk-SX6L4HZZ.js.map +0 -1
  71. package/dist/cli/chunk-U3V2ZQ5J.js.map +0 -1
  72. package/dist/cli/code-TGUOQBRJ.js +0 -153
  73. package/dist/cli/code-TGUOQBRJ.js.map +0 -1
  74. package/dist/cli/commit-CE4EFTUQ.js.map +0 -1
  75. package/dist/cli/replay-JEDLU7F2.js.map +0 -1
  76. package/dist/cli/run-NHD2RSTD.js.map +0 -1
  77. package/dist/cli/setup-DK43MT47.js.map +0 -1
  78. package/dist/cli/update-4TJWRUIN.js.map +0 -1
  79. /package/dist/cli/{chat-TD6GR3QK.js.map → chat-RGMYAOY2.js.map} +0 -0
  80. /package/dist/cli/{chunk-SW3CCXEV.js.map → chunk-4Q3GRJIU.js.map} +0 -0
  81. /package/dist/cli/{chunk-OERAGRJX.js.map → chunk-IPCPEZWQ.js.map} +0 -0
  82. /package/dist/cli/{chunk-I6YIAK6C.js.map → chunk-UNMYFZPZ.js.map} +0 -0
  83. /package/dist/cli/{chunk-6TMHAK5D.js.map → chunk-ZU45XW3P.js.map} +0 -0
  84. /package/dist/cli/{commands-MEZPSEHV.js.map → commands-FE2UDFBC.js.map} +0 -0
  85. /package/dist/cli/{doctor-YASM64X6.js.map → doctor-DKD34EFD.js.map} +0 -0
  86. /package/dist/cli/{mcp-LDFK5QJI.js.map → mcp-2RDEQST6.js.map} +0 -0
  87. /package/dist/cli/{mcp-browse-FYHEITCM.js.map → mcp-browse-VM5GLRBQ.js.map} +0 -0
  88. /package/dist/cli/{mcp-inspect-T2HBR22P.js.map → mcp-inspect-CWSVCZUQ.js.map} +0 -0
  89. /package/dist/cli/{prompt-V47QKSAR.js.map → prompt-YEKXMNNV.js.map} +0 -0
  90. /package/dist/cli/{sessions-ZHWJEW4L.js.map → sessions-YZXWMIWW.js.map} +0 -0
  91. /package/dist/cli/{version-O362UKPM.js.map → version-DWD6RLIU.js.map} +0 -0
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  MemoryStore,
4
4
  sanitizeMemoryName
5
- } from "./chunk-6TMHAK5D.js";
5
+ } from "./chunk-ZU45XW3P.js";
6
6
  import {
7
7
  countTokens,
8
8
  estimateConversationTokens,
@@ -13,16 +13,16 @@ import {
13
13
  } from "./chunk-KMWKGPFZ.js";
14
14
  import {
15
15
  pauseGate
16
- } from "./chunk-SX6L4HZZ.js";
16
+ } from "./chunk-QPNZWUZF.js";
17
17
  import {
18
18
  ESCALATION_CONTRACT,
19
19
  NEGATIVE_CLAIM_RULE,
20
20
  TUI_FORMATTING_RULES
21
- } from "./chunk-U3V2ZQ5J.js";
21
+ } from "./chunk-KJQIA4US.js";
22
22
  import {
23
23
  formatHookOutcomeMessage,
24
24
  runHooks
25
- } from "./chunk-OERAGRJX.js";
25
+ } from "./chunk-IPCPEZWQ.js";
26
26
  import {
27
27
  ignoredByLayers,
28
28
  loadGitignoreAt,
@@ -36,12 +36,12 @@ import {
36
36
  } from "./chunk-DFP4YSVM.js";
37
37
  import {
38
38
  t
39
- } from "./chunk-Q36KBLSU.js";
39
+ } from "./chunk-MLXUGPJE.js";
40
40
  import {
41
41
  DEFAULT_INDEX_EXCLUDES,
42
42
  webSearchEndpoint,
43
43
  webSearchEngine
44
- } from "./chunk-5JXXEPDM.js";
44
+ } from "./chunk-BHLHOS5Y.js";
45
45
  import {
46
46
  DEEPSEEK_CONTEXT_TOKENS,
47
47
  DEFAULT_CONTEXT_TOKENS,
@@ -286,6 +286,7 @@ var ToolRegistry = class {
286
286
  _planMode = false;
287
287
  _interceptor = null;
288
288
  _auditListener = null;
289
+ _resultAugmenter = null;
289
290
  constructor(opts = {}) {
290
291
  this._autoFlatten = opts.autoFlatten !== false;
291
292
  }
@@ -304,6 +305,10 @@ var ToolRegistry = class {
304
305
  setAuditListener(fn) {
305
306
  this._auditListener = fn;
306
307
  }
308
+ /** Final-stage post-processor; replaces previous augmenter when called twice. Pass null to clear. */
309
+ setResultAugmenter(fn) {
310
+ this._resultAugmenter = fn;
311
+ }
307
312
  register(def) {
308
313
  if (!def.name) throw new Error("tool requires a name");
309
314
  const internal = { ...def };
@@ -379,6 +384,7 @@ var ToolRegistry = class {
379
384
  });
380
385
  }
381
386
  }
387
+ let finalResult;
382
388
  try {
383
389
  try {
384
390
  this._auditListener?.({ name, args });
@@ -396,19 +402,26 @@ var ToolRegistry = class {
396
402
  if (opts.maxResultChars !== void 0) {
397
403
  clipped = truncateForModel(clipped, opts.maxResultChars);
398
404
  }
399
- return clipped;
405
+ finalResult = clipped;
400
406
  } catch (err) {
401
407
  const e = err;
402
408
  if (typeof e.toToolResult === "function") {
403
409
  try {
404
- return JSON.stringify(e.toToolResult());
410
+ finalResult = JSON.stringify(e.toToolResult());
405
411
  } catch {
412
+ finalResult = JSON.stringify({ error: `${e.name}: ${e.message}` });
406
413
  }
414
+ } else {
415
+ finalResult = JSON.stringify({ error: `${e.name}: ${e.message}` });
407
416
  }
408
- return JSON.stringify({
409
- error: `${e.name}: ${e.message}`
410
- });
411
417
  }
418
+ if (this._resultAugmenter) {
419
+ try {
420
+ return this._resultAugmenter(name, args, finalResult);
421
+ } catch {
422
+ }
423
+ }
424
+ return finalResult;
412
425
  }
413
426
  };
414
427
  function isReadOnlyCall(tool, args) {
@@ -2374,11 +2387,14 @@ async function searchFiles(ctx, startAbs, args) {
2374
2387
  await walk2(startAbs);
2375
2388
  return matches.length === 0 ? "(no matches)" : matches.join("\n");
2376
2389
  }
2390
+ var MAX_HITS_PER_FILE = 30;
2391
+ var SUMMARY_MODE_TRIGGER_RATIO = 0.8;
2377
2392
  async function searchContent(ctx, startAbs, args) {
2378
2393
  throwIfAborted(args.signal);
2379
2394
  const caseSensitive = args.case_sensitive === true;
2380
2395
  const includeDeps = args.include_deps === true;
2381
2396
  const ctxLines = Math.max(0, Math.min(20, Math.floor(args.context ?? 0)));
2397
+ const summaryOnly = args.summary_only === true;
2382
2398
  let re = null;
2383
2399
  try {
2384
2400
  re = new RegExp(args.pattern, caseSensitive ? "" : "i");
@@ -2390,6 +2406,9 @@ async function searchContent(ctx, startAbs, args) {
2390
2406
  let totalBytes = 0;
2391
2407
  let scanned = 0;
2392
2408
  let truncated = false;
2409
+ let summaryMode = summaryOnly;
2410
+ let summaryNoticeEmitted = false;
2411
+ const fileHitCounts = /* @__PURE__ */ new Map();
2393
2412
  const pushLine = (out) => {
2394
2413
  if (totalBytes + out.length + 1 > ctx.maxListBytes) {
2395
2414
  matches.push(`[\u2026 truncated at ${ctx.maxListBytes} bytes \u2014 refine pattern or path \u2026]`);
@@ -2400,6 +2419,18 @@ async function searchContent(ctx, startAbs, args) {
2400
2419
  totalBytes += out.length + 1;
2401
2420
  return true;
2402
2421
  };
2422
+ const maybeEnterSummaryMode = () => {
2423
+ if (summaryMode) return;
2424
+ if (totalBytes <= SUMMARY_MODE_TRIGGER_RATIO * ctx.maxListBytes) return;
2425
+ summaryMode = true;
2426
+ if (!summaryNoticeEmitted) {
2427
+ const pct = Math.round(totalBytes / ctx.maxListBytes * 100);
2428
+ pushLine(
2429
+ `[switching to summary mode \u2014 byte budget at ${pct}%; remaining files will report match counts only]`
2430
+ );
2431
+ summaryNoticeEmitted = true;
2432
+ }
2433
+ };
2403
2434
  const walk2 = async (dir) => {
2404
2435
  if (truncated) return;
2405
2436
  throwIfAborted(args.signal);
@@ -2458,33 +2489,48 @@ async function searchContent(ctx, startAbs, args) {
2458
2489
  }
2459
2490
  scanned++;
2460
2491
  if (hits.length === 0) continue;
2492
+ fileHitCounts.set(rel, hits.length);
2493
+ if (summaryMode) {
2494
+ if (!pushLine(`${rel}: ${hits.length} match${hits.length === 1 ? "" : "es"}`)) return;
2495
+ continue;
2496
+ }
2497
+ const printable = Math.min(hits.length, MAX_HITS_PER_FILE);
2498
+ const omittedFromFile = hits.length - printable;
2499
+ const printableHits = hits.slice(0, printable);
2461
2500
  if (ctxLines === 0) {
2462
- for (const li of hits) {
2501
+ for (const li of printableHits) {
2463
2502
  if (truncated) return;
2464
2503
  const line = lines[li];
2465
2504
  const display = line.length > 200 ? `${line.slice(0, 200)}\u2026` : line;
2466
2505
  if (!pushLine(`${rel}:${li + 1}: ${display}`)) return;
2467
2506
  }
2468
- continue;
2469
- }
2470
- const hitSet = new Set(hits);
2471
- let prevWindowEnd = -2;
2472
- for (const li of hits) {
2473
- if (truncated) return;
2474
- const winStart = Math.max(0, li - ctxLines);
2475
- const winEnd = Math.min(lines.length - 1, li + ctxLines);
2476
- if (winStart > prevWindowEnd + 1 && prevWindowEnd >= 0) {
2477
- if (!pushLine("--")) return;
2478
- }
2479
- const realStart = winStart > prevWindowEnd + 1 ? winStart : prevWindowEnd + 1;
2480
- for (let i = realStart; i <= winEnd; i++) {
2481
- const line = lines[i];
2482
- const display = line.length > 200 ? `${line.slice(0, 200)}\u2026` : line;
2483
- const sep2 = hitSet.has(i) ? ":" : "-";
2484
- if (!pushLine(`${rel}:${i + 1}${sep2} ${display}`)) return;
2507
+ } else {
2508
+ const hitSet = new Set(printableHits);
2509
+ let prevWindowEnd = -2;
2510
+ for (const li of printableHits) {
2511
+ if (truncated) return;
2512
+ const winStart = Math.max(0, li - ctxLines);
2513
+ const winEnd = Math.min(lines.length - 1, li + ctxLines);
2514
+ if (winStart > prevWindowEnd + 1 && prevWindowEnd >= 0) {
2515
+ if (!pushLine("--")) return;
2516
+ }
2517
+ const realStart = winStart > prevWindowEnd + 1 ? winStart : prevWindowEnd + 1;
2518
+ for (let i = realStart; i <= winEnd; i++) {
2519
+ const line = lines[i];
2520
+ const display = line.length > 200 ? `${line.slice(0, 200)}\u2026` : line;
2521
+ const sep2 = hitSet.has(i) ? ":" : "-";
2522
+ if (!pushLine(`${rel}:${i + 1}${sep2} ${display}`)) return;
2523
+ }
2524
+ prevWindowEnd = winEnd;
2485
2525
  }
2486
- prevWindowEnd = winEnd;
2487
2526
  }
2527
+ if (omittedFromFile > 0) {
2528
+ if (!pushLine(
2529
+ `[${rel}: ${omittedFromFile} more match${omittedFromFile === 1 ? "" : "es"} in this file \u2014 re-grep with a tighter pattern or use read_file to see them]`
2530
+ ))
2531
+ return;
2532
+ }
2533
+ maybeEnterSummaryMode();
2488
2534
  }
2489
2535
  };
2490
2536
  await walk2(startAbs);
@@ -2500,6 +2546,43 @@ var DEFAULT_MAX_LIST_BYTES = 256 * 1024;
2500
2546
  var DEFAULT_AUTO_PREVIEW_LINES = 200;
2501
2547
  var AUTO_PREVIEW_HEAD_LINES = 80;
2502
2548
  var AUTO_PREVIEW_TAIL_LINES = 40;
2549
+ var OUTLINE_MAX_ENTRIES = 30;
2550
+ var OUTLINE_TAIL_KEEP = 5;
2551
+ var TS_EXPORT_RE = /^export\s+(?:default\s+)?(?:async\s+)?(function|class|const|let|var|interface|type|enum)\s+\*?\s*(\w+)/;
2552
+ function extractTsExportOutline(lines) {
2553
+ const out = [];
2554
+ for (let i = 0; i < lines.length; i++) {
2555
+ const line = lines[i];
2556
+ if (!line.startsWith("export ")) continue;
2557
+ const m = TS_EXPORT_RE.exec(line);
2558
+ if (!m) continue;
2559
+ out.push({ line: i + 1, kind: m[1], name: m[2] });
2560
+ }
2561
+ return out;
2562
+ }
2563
+ function formatOutline(entries) {
2564
+ const total = entries.length;
2565
+ if (total === 0) return "";
2566
+ const lastEntry = entries[total - 1];
2567
+ const width = String(lastEntry.line).length;
2568
+ const fmt = (e) => ` L${String(e.line).padStart(width, " ")} export ${e.kind} ${e.name}`;
2569
+ const header = `[outline: ${total} top-level export${total === 1 ? "" : "s"}]`;
2570
+ if (total <= OUTLINE_MAX_ENTRIES) {
2571
+ return [header, ...entries.map(fmt)].join("\n");
2572
+ }
2573
+ const headCount = OUTLINE_MAX_ENTRIES - OUTLINE_TAIL_KEEP;
2574
+ const headEntries = entries.slice(0, headCount);
2575
+ const tailEntries = entries.slice(-OUTLINE_TAIL_KEEP);
2576
+ const omitted = total - OUTLINE_MAX_ENTRIES;
2577
+ const gapStart = headEntries[headEntries.length - 1].line;
2578
+ const gapEnd = tailEntries[0].line;
2579
+ return [
2580
+ header,
2581
+ ...headEntries.map(fmt),
2582
+ ` [\u2026 ${omitted} more export${omitted === 1 ? "" : "s"} between L${gapStart} and L${gapEnd} \u2026]`,
2583
+ ...tailEntries.map(fmt)
2584
+ ].join("\n");
2585
+ }
2503
2586
  var SKIP_DIR_NAMES = new Set(DEFAULT_INDEX_EXCLUDES.dirs);
2504
2587
  var BINARY_EXTENSIONS = new Set(DEFAULT_INDEX_EXCLUDES.exts);
2505
2588
  function displayRel4(rootDir, full) {
@@ -2552,7 +2635,7 @@ function registerFilesystemTools(registry, opts) {
2552
2635
  - head: N \u2192 first N lines (imports, public API, small configs)
2553
2636
  - tail: N \u2192 last N lines (recently-added code, log tails)
2554
2637
  - range: "A-B" \u2192 inclusive line range A..B, 1-indexed (e.g. "120-180" around an edit site)
2555
- When none of these is given AND the file is longer than ${DEFAULT_AUTO_PREVIEW_LINES} lines, the tool auto-returns a head+tail preview with an "N lines omitted" marker rather than dumping everything. If you need the middle, re-call with a range. Prefer search_content to locate a symbol first, then read_file with a range around the hit \u2014 one scoped read beats three full-file reads.`,
2638
+ When none of these is given AND the file is longer than ${DEFAULT_AUTO_PREVIEW_LINES} lines, the tool auto-returns a head+tail preview with an "N lines omitted" marker, plus a top-level export outline (function / class / const / interface / type / enum names with line numbers, capped at ${OUTLINE_MAX_ENTRIES}) so you can pick a smart range without a follow-up grep. If you need the middle, re-call with a range. Prefer search_content to locate a symbol first only when the outline doesn't have what you want \u2014 one scoped read beats three full-file reads.`,
2556
2639
  readOnly: true,
2557
2640
  stormExempt: true,
2558
2641
  parameters: {
@@ -2620,14 +2703,19 @@ ${slice.join("\n")}`;
2620
2703
  const head = lines.slice(0, AUTO_PREVIEW_HEAD_LINES).join("\n");
2621
2704
  const tail = lines.slice(totalLines - AUTO_PREVIEW_TAIL_LINES).join("\n");
2622
2705
  const omitted = totalLines - AUTO_PREVIEW_HEAD_LINES - AUTO_PREVIEW_TAIL_LINES;
2623
- return [
2706
+ const outline = formatOutline(extractTsExportOutline(lines));
2707
+ const parts = [
2624
2708
  `[auto-preview: head ${AUTO_PREVIEW_HEAD_LINES} + tail ${AUTO_PREVIEW_TAIL_LINES} of ${totalLines} lines]`,
2625
- head,
2709
+ head
2710
+ ];
2711
+ if (outline) parts.push("", outline);
2712
+ parts.push(
2626
2713
  `
2627
2714
  [\u2026 ${omitted} lines omitted \u2014 call read_file again with range:"A-B" (1-indexed) or head / tail to get the middle]
2628
2715
  `,
2629
2716
  tail
2630
- ].join("\n");
2717
+ );
2718
+ return parts.join("\n");
2631
2719
  }
2632
2720
  });
2633
2721
  registry.register({
@@ -2760,7 +2848,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
2760
2848
  registry.register({
2761
2849
  name: "search_content",
2762
2850
  parallelSafe: true,
2763
- description: "Recursively grep file CONTENTS for a substring or regex. This is the right tool for 'find all places that call X', 'where is Y referenced', 'what files contain Z'. Different from search_files (which matches FILE NAMES). Returns one match per line in 'path:line: text' format. Skips dependency / VCS / build directories (node_modules, .git, dist, build, .next, target, .venv) and binary files by default.",
2851
+ description: "Recursively grep file CONTENTS for a substring or regex. This is the right tool for 'find all places that call X', 'where is Y referenced', 'what files contain Z'. Different from search_files (which matches FILE NAMES). Returns one match per line in 'path:line: text' format. Per-file hits are capped at 30 (a footer reports any extras); when the byte budget is mostly spent the remaining files switch to a 'rel: N matches' histogram so distribution stays visible instead of one popular file drowning the rest. Pass `summary_only:true` to skip line content entirely and get just the histogram. Skips dependency / VCS / build directories (node_modules, .git, dist, build, .next, target, .venv) and binary files by default.",
2764
2852
  readOnly: true,
2765
2853
  parameters: {
2766
2854
  type: "object",
@@ -2788,6 +2876,10 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
2788
2876
  context: {
2789
2877
  type: "integer",
2790
2878
  description: "Lines of context to show around each match (both before and after). Default 0 (just the matching line). Capped at 20. Output uses ripgrep style: `:` after the line number on the matching line, `-` on context lines, `--` separating non-adjacent windows."
2879
+ },
2880
+ summary_only: {
2881
+ type: "boolean",
2882
+ description: "When true, skip line content and return one 'rel: N matches' line per matching file. Use for 'where does this exist at all' questions before drilling in with a targeted read_file."
2791
2883
  }
2792
2884
  },
2793
2885
  required: ["pattern"]
@@ -3254,9 +3346,14 @@ function registerSubmitPlan(registry, opts) {
3254
3346
  kind: "plan_proposed",
3255
3347
  payload: { plan, steps, summary }
3256
3348
  });
3257
- if (verdict.type === "approve") return "plan approved";
3258
- if (verdict.type === "refine") throw new Error("user requested refinement");
3259
- throw new Error("plan cancelled");
3349
+ const fb = verdict.feedback?.trim();
3350
+ if (verdict.type === "approve") {
3351
+ return fb ? `plan approved. user's additional instructions: ${fb}` : "plan approved";
3352
+ }
3353
+ if (verdict.type === "refine") {
3354
+ throw new Error(fb ? `user requested refinement: ${fb}` : "user requested refinement");
3355
+ }
3356
+ throw new Error(fb ? `plan cancelled: ${fb}` : "plan cancelled");
3260
3357
  }
3261
3358
  });
3262
3359
  }
@@ -4447,6 +4544,10 @@ ${ESCALATION_CONTRACT}
4447
4544
  ${TUI_FORMATTING_RULES}`;
4448
4545
  var DEFAULT_MAX_RESULT_CHARS2 = 8e3;
4449
4546
  var DEFAULT_MAX_ITERS = 16;
4547
+ var BUDGET_WARN_THRESHOLD = 3;
4548
+ function budgetParagraph(maxToolIters) {
4549
+ return `Tool budget: you have ${maxToolIters} tool call${maxToolIters === 1 ? "" : "s"} for this task. The cap is enforced from outside \u2014 the call after #${maxToolIters} is refused. Pace yourself: if you can't fully resolve the task within the budget, stop early and return what you have plus what's missing, rather than burning the budget on one branch.`;
4550
+ }
4450
4551
  var DEFAULT_SUBAGENT_MODEL = "deepseek-v4-flash";
4451
4552
  var DEFAULT_SUBAGENT_EFFORT = "high";
4452
4553
  var SUBAGENT_TOOL_NAME = "spawn_subagent";
@@ -4505,8 +4606,26 @@ async function spawnSubagent(opts) {
4505
4606
  new Set(opts.allowedTools),
4506
4607
  NEVER_INHERITED_TOOLS
4507
4608
  ) : forkRegistryExcluding(opts.parentRegistry, NEVER_INHERITED_TOOLS);
4609
+ let dispatchCount = 0;
4610
+ childTools.setResultAugmenter((_name, _args, result) => {
4611
+ dispatchCount++;
4612
+ const remaining = maxToolIters - dispatchCount;
4613
+ if (remaining <= 0) {
4614
+ return `${result}
4615
+
4616
+ [budget: 0 of ${maxToolIters} tool calls left \u2014 finalize NOW; the next tool call will be refused]`;
4617
+ }
4618
+ if (remaining <= BUDGET_WARN_THRESHOLD) {
4619
+ return `${result}
4620
+
4621
+ [budget: ${remaining} of ${maxToolIters} tool call${remaining === 1 ? "" : "s"} left \u2014 wrap up soon]`;
4622
+ }
4623
+ return result;
4624
+ });
4508
4625
  const childPrefix = new ImmutablePrefix({
4509
- system: opts.system,
4626
+ system: `${opts.system}
4627
+
4628
+ ${budgetParagraph(maxToolIters)}`,
4510
4629
  toolSpecs: childTools.specs()
4511
4630
  });
4512
4631
  const childLoop = new CacheFirstLoop({
@@ -4890,4 +5009,4 @@ export {
4890
5009
  snapshotBeforeEdits,
4891
5010
  restoreSnapshots
4892
5011
  };
4893
- //# sourceMappingURL=chunk-LNTORE5K.js.map
5012
+ //# sourceMappingURL=chunk-QRUQ2BFT.js.map