reasonix 0.50.0 → 0.51.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 (147) hide show
  1. package/dashboard/dist/app.css +1 -1
  2. package/dashboard/dist/app.js +24 -22
  3. package/dashboard/dist/app.js.map +1 -1
  4. package/dist/cli/{acp-6B25WIFF.js → acp-XEUHGG7X.js} +34 -31
  5. package/dist/cli/acp-XEUHGG7X.js.map +1 -0
  6. package/dist/cli/chat-NJ2Q5KHG.js +50 -0
  7. package/dist/cli/{chunk-OPGWCKKU.js → chunk-2HVTBFCI.js} +3 -3
  8. package/dist/cli/{chunk-AJIZ5KFK.js → chunk-2WUEAI2I.js} +3 -3
  9. package/dist/cli/{chunk-I4Q3QT4W.js → chunk-36BM7INR.js} +2 -2
  10. package/dist/cli/{chunk-3RNFYDDM.js → chunk-3BTK5BHI.js} +11 -7
  11. package/dist/cli/chunk-3BTK5BHI.js.map +1 -0
  12. package/dist/cli/{chunk-GMSAB2TC.js → chunk-3YRTIWFX.js} +2 -2
  13. package/dist/cli/{chunk-NLRC3DWQ.js → chunk-544J4PXD.js} +5 -5
  14. package/dist/cli/{chunk-7WITYWKN.js → chunk-5AIDYVH2.js} +2 -2
  15. package/dist/cli/{chunk-ALCOQP6R.js → chunk-5BBC6YMV.js} +5 -5
  16. package/dist/cli/{chunk-S4XVGLRW.js → chunk-6UNHNVJR.js} +72 -5
  17. package/dist/cli/chunk-6UNHNVJR.js.map +1 -0
  18. package/dist/cli/{chunk-IK6WWRIX.js → chunk-6XWXIVQ3.js} +38 -22
  19. package/dist/cli/chunk-6XWXIVQ3.js.map +1 -0
  20. package/dist/cli/{chunk-AAHB2PFX.js → chunk-7YB26OQO.js} +4 -4
  21. package/dist/cli/chunk-7YB26OQO.js.map +1 -0
  22. package/dist/cli/{chunk-MXWPAPZW.js → chunk-A5PBEIJ7.js} +53 -10
  23. package/dist/cli/chunk-A5PBEIJ7.js.map +1 -0
  24. package/dist/cli/{chunk-FQSQFCBI.js → chunk-BA5R6BAE.js} +2 -2
  25. package/dist/cli/{chunk-XWPZHWC2.js → chunk-BM6BBFAV.js} +2 -2
  26. package/dist/cli/{chunk-CAGKEGNE.js → chunk-BOWSNGQC.js} +52 -140
  27. package/dist/cli/chunk-BOWSNGQC.js.map +1 -0
  28. package/dist/cli/{chunk-EZ57UEZQ.js → chunk-C2MRSJTV.js} +2 -2
  29. package/dist/cli/{chunk-PYIZZAVQ.js → chunk-DVD67FXQ.js} +1716 -4
  30. package/dist/cli/chunk-DVD67FXQ.js.map +1 -0
  31. package/dist/cli/{chunk-ZAXMJANP.js → chunk-EAMXOWUW.js} +3 -3
  32. package/dist/cli/{chunk-TX652NBA.js → chunk-EWVFGYT6.js} +2 -2
  33. package/dist/cli/{chunk-IBRTU5WO.js → chunk-FP7IOWBQ.js} +18 -1182
  34. package/dist/cli/chunk-FP7IOWBQ.js.map +1 -0
  35. package/dist/cli/{chunk-I6FBSTTR.js → chunk-HGK57NBN.js} +9 -353
  36. package/dist/cli/chunk-HGK57NBN.js.map +1 -0
  37. package/dist/cli/chunk-JHWQDJZA.js +80 -0
  38. package/dist/cli/chunk-JHWQDJZA.js.map +1 -0
  39. package/dist/cli/{chunk-X2BQZQEE.js → chunk-K3QJ3GKI.js} +3 -3
  40. package/dist/cli/{chunk-GPUH2BNM.js → chunk-K4YQFULP.js} +612 -254
  41. package/dist/cli/chunk-K4YQFULP.js.map +1 -0
  42. package/dist/cli/chunk-L3VPEESB.js +31 -0
  43. package/dist/cli/chunk-L3VPEESB.js.map +1 -0
  44. package/dist/cli/{chunk-ENFBF6HI.js → chunk-N4SEBLU4.js} +383 -5
  45. package/dist/cli/chunk-N4SEBLU4.js.map +1 -0
  46. package/dist/cli/chunk-NRROJXXT.js +879 -0
  47. package/dist/cli/chunk-NRROJXXT.js.map +1 -0
  48. package/dist/cli/{chunk-3KRRTLC5.js → chunk-R6KIHEF3.js} +1619 -1036
  49. package/dist/cli/chunk-R6KIHEF3.js.map +1 -0
  50. package/dist/cli/{chunk-VVMY4M7J.js → chunk-SBHF5NWD.js} +27 -4
  51. package/dist/cli/chunk-SBHF5NWD.js.map +1 -0
  52. package/dist/cli/{chunk-OWA42BKS.js → chunk-SXSAWOB7.js} +14 -14
  53. package/dist/cli/{chunk-6IUMTRFP.js → chunk-UMZ6KHTS.js} +2 -2
  54. package/dist/cli/{chunk-7X4JJOO7.js → chunk-UO6E7FN3.js} +69 -5
  55. package/dist/cli/{chunk-7X4JJOO7.js.map → chunk-UO6E7FN3.js.map} +1 -1
  56. package/dist/cli/{chunk-3ZZXQ3CZ.js → chunk-UPW544V3.js} +2 -2
  57. package/dist/cli/{chunk-XJZWMU5P.js → chunk-WPOKBW5E.js} +2 -2
  58. package/dist/cli/{chunk-WSBFVOCO.js → chunk-Z3MKG7MQ.js} +2 -2
  59. package/dist/cli/{code-TBK2TASK.js → code-BMXLBC7D.js} +37 -36
  60. package/dist/cli/{code-TBK2TASK.js.map → code-BMXLBC7D.js.map} +1 -1
  61. package/dist/cli/{commands-NXTKSQTN.js → commands-E4RZXMF6.js} +5 -5
  62. package/dist/cli/{commit-IR5SPP7A.js → commit-KSRQ64IL.js} +3 -3
  63. package/dist/cli/{config-XK5WQGTS.js → config-QNDONOTU.js} +4 -2
  64. package/dist/cli/{desktop-5NTQBADL.js → desktop-H3ZHIMDA.js} +83 -37
  65. package/dist/cli/desktop-H3ZHIMDA.js.map +1 -0
  66. package/dist/cli/{diff-JNYX5BSZ.js → diff-I4PYI43W.js} +9 -9
  67. package/dist/cli/{doctor-IKYLUFXX.js → doctor-Y2E4MY2F.js} +12 -12
  68. package/dist/cli/{events-HSC57ONU.js → events-47HOT7ZA.js} +5 -5
  69. package/dist/cli/find-in-code-YLEIK5FK.js +145 -0
  70. package/dist/cli/find-in-code-YLEIK5FK.js.map +1 -0
  71. package/dist/cli/index.js +95 -44
  72. package/dist/cli/index.js.map +1 -1
  73. package/dist/cli/{mcp-BDJJWOCD.js → mcp-76DK63ZB.js} +3 -3
  74. package/dist/cli/{mcp-browse-NJRZDI6V.js → mcp-browse-SDNUGO74.js} +3 -3
  75. package/dist/cli/{mcp-inspect-Y62NWZQL.js → mcp-inspect-BL5DEO5M.js} +3 -3
  76. package/dist/cli/{prompt-UTOIFUQC.js → prompt-JLATI3P7.js} +5 -5
  77. package/dist/cli/{prune-sessions-UCUD4XAP.js → prune-sessions-WHZDFUKD.js} +4 -4
  78. package/dist/cli/{replay-VVIN64MN.js → replay-MHXS7C7Z.js} +10 -10
  79. package/dist/cli/{run-76OBDZFB.js → run-SXNCPRJE.js} +22 -22
  80. package/dist/cli/{server-SZZDKTH2.js → server-GEHOE6CO.js} +61 -35
  81. package/dist/cli/server-GEHOE6CO.js.map +1 -0
  82. package/dist/cli/{sessions-FZTGRCM5.js → sessions-EPBFYISL.js} +18 -18
  83. package/dist/cli/{setup-4UNENGOE.js → setup-IW2XR5XI.js} +8 -7
  84. package/dist/cli/setup-IW2XR5XI.js.map +1 -0
  85. package/dist/cli/{stats-F4NDOD7D.js → stats-4WB4XHBP.js} +6 -6
  86. package/dist/cli/symbols-UQ274IOB.js +167 -0
  87. package/dist/cli/symbols-UQ274IOB.js.map +1 -0
  88. package/dist/cli/version-4SP3DLLH.js +33 -0
  89. package/dist/index.d.ts +25 -6
  90. package/dist/index.js +2700 -578
  91. package/dist/index.js.map +1 -1
  92. package/package.json +6 -3
  93. package/scripts/postinstall.mjs +10 -0
  94. package/dist/cli/acp-6B25WIFF.js.map +0 -1
  95. package/dist/cli/chat-7WASPB4O.js +0 -50
  96. package/dist/cli/chunk-3KRRTLC5.js.map +0 -1
  97. package/dist/cli/chunk-3RNFYDDM.js.map +0 -1
  98. package/dist/cli/chunk-AAHB2PFX.js.map +0 -1
  99. package/dist/cli/chunk-CAGKEGNE.js.map +0 -1
  100. package/dist/cli/chunk-ENFBF6HI.js.map +0 -1
  101. package/dist/cli/chunk-GPUH2BNM.js.map +0 -1
  102. package/dist/cli/chunk-I6FBSTTR.js.map +0 -1
  103. package/dist/cli/chunk-IBRTU5WO.js.map +0 -1
  104. package/dist/cli/chunk-IK6WWRIX.js.map +0 -1
  105. package/dist/cli/chunk-MXWPAPZW.js.map +0 -1
  106. package/dist/cli/chunk-PYIZZAVQ.js.map +0 -1
  107. package/dist/cli/chunk-S4XVGLRW.js.map +0 -1
  108. package/dist/cli/chunk-VVMY4M7J.js.map +0 -1
  109. package/dist/cli/desktop-5NTQBADL.js.map +0 -1
  110. package/dist/cli/server-SZZDKTH2.js.map +0 -1
  111. package/dist/cli/setup-4UNENGOE.js.map +0 -1
  112. package/dist/cli/version-LUVTWHLL.js +0 -33
  113. /package/dist/cli/{chat-7WASPB4O.js.map → chat-NJ2Q5KHG.js.map} +0 -0
  114. /package/dist/cli/{chunk-OPGWCKKU.js.map → chunk-2HVTBFCI.js.map} +0 -0
  115. /package/dist/cli/{chunk-AJIZ5KFK.js.map → chunk-2WUEAI2I.js.map} +0 -0
  116. /package/dist/cli/{chunk-I4Q3QT4W.js.map → chunk-36BM7INR.js.map} +0 -0
  117. /package/dist/cli/{chunk-GMSAB2TC.js.map → chunk-3YRTIWFX.js.map} +0 -0
  118. /package/dist/cli/{chunk-NLRC3DWQ.js.map → chunk-544J4PXD.js.map} +0 -0
  119. /package/dist/cli/{chunk-7WITYWKN.js.map → chunk-5AIDYVH2.js.map} +0 -0
  120. /package/dist/cli/{chunk-ALCOQP6R.js.map → chunk-5BBC6YMV.js.map} +0 -0
  121. /package/dist/cli/{chunk-FQSQFCBI.js.map → chunk-BA5R6BAE.js.map} +0 -0
  122. /package/dist/cli/{chunk-XWPZHWC2.js.map → chunk-BM6BBFAV.js.map} +0 -0
  123. /package/dist/cli/{chunk-EZ57UEZQ.js.map → chunk-C2MRSJTV.js.map} +0 -0
  124. /package/dist/cli/{chunk-ZAXMJANP.js.map → chunk-EAMXOWUW.js.map} +0 -0
  125. /package/dist/cli/{chunk-TX652NBA.js.map → chunk-EWVFGYT6.js.map} +0 -0
  126. /package/dist/cli/{chunk-X2BQZQEE.js.map → chunk-K3QJ3GKI.js.map} +0 -0
  127. /package/dist/cli/{chunk-OWA42BKS.js.map → chunk-SXSAWOB7.js.map} +0 -0
  128. /package/dist/cli/{chunk-6IUMTRFP.js.map → chunk-UMZ6KHTS.js.map} +0 -0
  129. /package/dist/cli/{chunk-3ZZXQ3CZ.js.map → chunk-UPW544V3.js.map} +0 -0
  130. /package/dist/cli/{chunk-XJZWMU5P.js.map → chunk-WPOKBW5E.js.map} +0 -0
  131. /package/dist/cli/{chunk-WSBFVOCO.js.map → chunk-Z3MKG7MQ.js.map} +0 -0
  132. /package/dist/cli/{commands-NXTKSQTN.js.map → commands-E4RZXMF6.js.map} +0 -0
  133. /package/dist/cli/{commit-IR5SPP7A.js.map → commit-KSRQ64IL.js.map} +0 -0
  134. /package/dist/cli/{config-XK5WQGTS.js.map → config-QNDONOTU.js.map} +0 -0
  135. /package/dist/cli/{diff-JNYX5BSZ.js.map → diff-I4PYI43W.js.map} +0 -0
  136. /package/dist/cli/{doctor-IKYLUFXX.js.map → doctor-Y2E4MY2F.js.map} +0 -0
  137. /package/dist/cli/{events-HSC57ONU.js.map → events-47HOT7ZA.js.map} +0 -0
  138. /package/dist/cli/{mcp-BDJJWOCD.js.map → mcp-76DK63ZB.js.map} +0 -0
  139. /package/dist/cli/{mcp-browse-NJRZDI6V.js.map → mcp-browse-SDNUGO74.js.map} +0 -0
  140. /package/dist/cli/{mcp-inspect-Y62NWZQL.js.map → mcp-inspect-BL5DEO5M.js.map} +0 -0
  141. /package/dist/cli/{prompt-UTOIFUQC.js.map → prompt-JLATI3P7.js.map} +0 -0
  142. /package/dist/cli/{prune-sessions-UCUD4XAP.js.map → prune-sessions-WHZDFUKD.js.map} +0 -0
  143. /package/dist/cli/{replay-VVIN64MN.js.map → replay-MHXS7C7Z.js.map} +0 -0
  144. /package/dist/cli/{run-76OBDZFB.js.map → run-SXNCPRJE.js.map} +0 -0
  145. /package/dist/cli/{sessions-FZTGRCM5.js.map → sessions-EPBFYISL.js.map} +0 -0
  146. /package/dist/cli/{stats-F4NDOD7D.js.map → stats-4WB4XHBP.js.map} +0 -0
  147. /package/dist/cli/{version-LUVTWHLL.js.map → version-4SP3DLLH.js.map} +0 -0
@@ -7,32 +7,39 @@ import {
7
7
  decodeFileBuffer,
8
8
  encodeFile,
9
9
  pauseGate
10
- } from "./chunk-6IUMTRFP.js";
10
+ } from "./chunk-UMZ6KHTS.js";
11
11
  import {
12
- MemoryStore,
13
- sanitizeMemoryName
14
- } from "./chunk-I6FBSTTR.js";
12
+ DEEPSEEK_CONTEXT_TOKENS,
13
+ DEFAULT_CONTEXT_TOKENS,
14
+ SessionStats
15
+ } from "./chunk-BA5R6BAE.js";
15
16
  import {
17
+ MemoryStore,
16
18
  NEGATIVE_CLAIM_RULE,
17
19
  PROJECT_MEMORY_FILES,
18
20
  PROJECT_MEMORY_MAX_CHARS,
19
21
  TUI_FORMATTING_RULES,
20
- memoryEnabled
21
- } from "./chunk-ENFBF6HI.js";
22
+ memoryEnabled,
23
+ sanitizeMemoryName
24
+ } from "./chunk-N4SEBLU4.js";
22
25
  import {
23
26
  countTokens,
24
27
  countTokensBounded,
25
28
  estimateRequestTokens
26
- } from "./chunk-CAGKEGNE.js";
29
+ } from "./chunk-BOWSNGQC.js";
30
+ import {
31
+ Usage
32
+ } from "./chunk-C2MRSJTV.js";
27
33
  import {
28
34
  formatHookOutcomeMessage,
29
35
  runHooks
30
- } from "./chunk-AJIZ5KFK.js";
36
+ } from "./chunk-2WUEAI2I.js";
31
37
  import {
38
+ TtlLruCache,
32
39
  ignoredByLayers,
33
40
  loadGitignoreAt,
34
41
  loadGitignoreAtSync
35
- } from "./chunk-S4XVGLRW.js";
42
+ } from "./chunk-6UNHNVJR.js";
36
43
  import {
37
44
  appendSessionMessage,
38
45
  archiveSession,
@@ -41,18 +48,10 @@ import {
41
48
  patchSessionMeta,
42
49
  rewriteSession,
43
50
  timestampSuffix
44
- } from "./chunk-IK6WWRIX.js";
45
- import {
46
- Usage
47
- } from "./chunk-EZ57UEZQ.js";
48
- import {
49
- DEEPSEEK_CONTEXT_TOKENS,
50
- DEFAULT_CONTEXT_TOKENS,
51
- SessionStats
52
- } from "./chunk-FQSQFCBI.js";
51
+ } from "./chunk-6XWXIVQ3.js";
53
52
  import {
54
53
  t
55
- } from "./chunk-PYIZZAVQ.js";
54
+ } from "./chunk-DVD67FXQ.js";
56
55
  import {
57
56
  DEFAULT_INDEX_EXCLUDES,
58
57
  ToolRateLimiter,
@@ -67,7 +66,7 @@ import {
67
66
  require_picomatch,
68
67
  webSearchEndpoint,
69
68
  webSearchEngine
70
- } from "./chunk-MXWPAPZW.js";
69
+ } from "./chunk-A5PBEIJ7.js";
71
70
  import {
72
71
  __commonJS,
73
72
  __esm,
@@ -2604,10 +2603,10 @@ var require_helpers = __commonJS({
2604
2603
  return !arr.includes(node, i + 1);
2605
2604
  });
2606
2605
  nodes.sort(function(a, b) {
2607
- var relative7 = compareDocumentPosition(a, b);
2608
- if (relative7 & DocumentPosition.PRECEDING) {
2606
+ var relative8 = compareDocumentPosition(a, b);
2607
+ if (relative8 & DocumentPosition.PRECEDING) {
2609
2608
  return -1;
2610
- } else if (relative7 & DocumentPosition.FOLLOWING) {
2609
+ } else if (relative8 & DocumentPosition.FOLLOWING) {
2611
2610
  return 1;
2612
2611
  }
2613
2612
  return 0;
@@ -6057,12 +6056,12 @@ async function waitForReady(ready, timeoutMs, serverName, signal) {
6057
6056
  let timer;
6058
6057
  let onAbort;
6059
6058
  try {
6060
- await new Promise((resolve6, reject) => {
6059
+ await new Promise((resolve7, reject) => {
6061
6060
  ready.then(
6062
6061
  () => {
6063
6062
  if (settled) return;
6064
6063
  settled = true;
6065
- resolve6();
6064
+ resolve7();
6066
6065
  },
6067
6066
  (err) => {
6068
6067
  if (settled) return;
@@ -6168,25 +6167,30 @@ function validateResultShape(result) {
6168
6167
  }
6169
6168
  }
6170
6169
  }
6171
- function truncateForModel(s, maxChars) {
6170
+ function truncateForModel(s, maxChars, extraNote) {
6172
6171
  if (s.length <= maxChars) return s;
6173
6172
  const tailBudget = Math.min(1024, Math.floor(maxChars * 0.1));
6174
6173
  const headBudget = Math.max(0, maxChars - tailBudget);
6175
6174
  const head = s.slice(0, headBudget);
6176
6175
  const tail = s.slice(-tailBudget);
6177
6176
  const dropped = s.length - head.length - tail.length;
6177
+ const note = extraNote ? ` \u2014 ${extraNote}` : "";
6178
6178
  return `${head}
6179
6179
 
6180
- [\u2026truncated ${dropped} chars \u2014 raise BridgeOptions.maxResultChars, or call the tool with a narrower scope (filter, head, pagination)\u2026]
6180
+ [\u2026truncated ${dropped} chars \u2014 raise BridgeOptions.maxResultChars, or call the tool with a narrower scope (filter, head, pagination)${note}\u2026]
6181
6181
 
6182
6182
  ${tail}`;
6183
6183
  }
6184
- function truncateForModelByTokens(s, maxTokens) {
6184
+ function truncateForModelByTokens(s, maxTokens, extraNote) {
6185
6185
  if (maxTokens <= 0) return "";
6186
6186
  if (s.length <= maxTokens) return s;
6187
6187
  if (s.length <= maxTokens * 4) {
6188
- const tokens = countTokens(s);
6189
- if (tokens <= maxTokens) return s;
6188
+ const est = countTokensBounded(s);
6189
+ if (Math.ceil(est * 1.15) <= maxTokens) return s;
6190
+ if (est <= maxTokens) {
6191
+ const tokens = countTokens(s);
6192
+ if (tokens <= maxTokens) return s;
6193
+ }
6190
6194
  }
6191
6195
  const markerOverhead = 48;
6192
6196
  const contentBudget = Math.max(0, maxTokens - markerOverhead);
@@ -6202,9 +6206,10 @@ function truncateForModelByTokens(s, maxTokens) {
6202
6206
  const ratio = sampleChars > 0 ? sampleTokens / sampleChars : 0.3;
6203
6207
  const estTotalTokens = Math.ceil(s.length * ratio);
6204
6208
  const droppedTokens = Math.max(0, estTotalTokens - sampleTokens);
6209
+ const note = extraNote ? ` \u2014 ${extraNote}` : "";
6205
6210
  return `${head}
6206
6211
 
6207
- [\u2026truncated ~${droppedTokens} tokens (${droppedChars} chars) \u2014 raise BridgeOptions.maxResultTokens, or call the tool with a narrower scope (filter, head, pagination)\u2026]
6212
+ [\u2026truncated ~${droppedTokens} tokens (${droppedChars} chars) \u2014 raise BridgeOptions.maxResultTokens, or call the tool with a narrower scope (filter, head, pagination)${note}\u2026]
6208
6213
 
6209
6214
  ${tail}`;
6210
6215
  }
@@ -6310,6 +6315,81 @@ function setByPath(target, path, value) {
6310
6315
  cur[path[path.length - 1]] = value;
6311
6316
  }
6312
6317
 
6318
+ // src/tools/truncated-result-saver.ts
6319
+ import { randomUUID } from "crypto";
6320
+ import {
6321
+ chmodSync,
6322
+ existsSync,
6323
+ mkdirSync,
6324
+ readdirSync,
6325
+ rmSync,
6326
+ statSync,
6327
+ writeFileSync
6328
+ } from "fs";
6329
+ import { homedir } from "os";
6330
+ import { join, relative, resolve } from "path";
6331
+ var TRUNCATED_DIR = "truncated-results";
6332
+ var DEFAULT_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
6333
+ function sanitizeToolName(name) {
6334
+ return name.replace(/[^\w\-]/g, "_").slice(0, 48) || "unknown";
6335
+ }
6336
+ function storageDir(rootDir) {
6337
+ const base = rootDir ? join(resolve(rootDir), ".reasonix") : join(homedir(), ".reasonix");
6338
+ return join(base, TRUNCATED_DIR);
6339
+ }
6340
+ function resultFilename(toolName) {
6341
+ const ts = Date.now().toString();
6342
+ const suffix = randomUUID().slice(0, 8);
6343
+ const safeName = sanitizeToolName(toolName);
6344
+ return `${ts}-${suffix}-${safeName}.txt`;
6345
+ }
6346
+ function saveTruncatedResult(content, toolName, rootDir) {
6347
+ cleanupOldResults(rootDir);
6348
+ const dir = storageDir(rootDir);
6349
+ if (!existsSync(dir)) {
6350
+ mkdirSync(dir, { recursive: true });
6351
+ }
6352
+ const filename = resultFilename(toolName);
6353
+ const absPath = join(dir, filename);
6354
+ writeFileSync(absPath, content, "utf-8");
6355
+ try {
6356
+ chmodSync(absPath, 384);
6357
+ } catch {
6358
+ }
6359
+ if (rootDir) {
6360
+ const absRoot = resolve(rootDir);
6361
+ return relative(absRoot, absPath).replaceAll("\\", "/");
6362
+ }
6363
+ return absPath.replaceAll("\\", "/");
6364
+ }
6365
+ function cleanupOldResults(rootDir, maxAgeMs = DEFAULT_MAX_AGE_MS) {
6366
+ const dir = storageDir(rootDir);
6367
+ if (!existsSync(dir)) return;
6368
+ const cutoff = Date.now() - maxAgeMs;
6369
+ let entries;
6370
+ try {
6371
+ entries = readdirSync(dir);
6372
+ } catch {
6373
+ return;
6374
+ }
6375
+ for (const entry of entries) {
6376
+ if (!entry.endsWith(".txt")) continue;
6377
+ const abs = join(dir, entry);
6378
+ try {
6379
+ const st = statSync(abs);
6380
+ if (st.isFile() && st.mtimeMs < cutoff) {
6381
+ rmSync(abs);
6382
+ }
6383
+ } catch {
6384
+ }
6385
+ }
6386
+ }
6387
+ function shouldSkipSave(toolName, skipTruncationSave) {
6388
+ if (skipTruncationSave) return true;
6389
+ const alwaysSkip = /* @__PURE__ */ new Set(["get_env", "everything_get-env"]);
6390
+ return alwaysSkip.has(toolName);
6391
+ }
6392
+
6313
6393
  // src/tools.ts
6314
6394
  var ToolRegistry = class {
6315
6395
  _tools = /* @__PURE__ */ new Map();
@@ -6487,7 +6567,20 @@ var ToolRegistry = class {
6487
6567
  if (opts.maxResultChars !== void 0) {
6488
6568
  clipped = truncateForModel(clipped, opts.maxResultChars);
6489
6569
  }
6490
- finalResult = clipped;
6570
+ if (clipped !== str && !shouldSkipSave(name, tool?.skipTruncationSave)) {
6571
+ const relPath = saveTruncatedResult(str, name, opts.rootDir ?? process.cwd());
6572
+ const note = `Full result saved at: ${relPath}`;
6573
+ let annotated = str;
6574
+ if (opts.maxResultTokens !== void 0) {
6575
+ annotated = truncateForModelByTokens(annotated, opts.maxResultTokens, note);
6576
+ }
6577
+ if (opts.maxResultChars !== void 0) {
6578
+ annotated = truncateForModel(annotated, opts.maxResultChars, note);
6579
+ }
6580
+ finalResult = annotated;
6581
+ } else {
6582
+ finalResult = clipped;
6583
+ }
6491
6584
  } catch (err) {
6492
6585
  const e = err;
6493
6586
  if (typeof e.toToolResult === "function") {
@@ -7054,8 +7147,88 @@ var InflightSet = class {
7054
7147
  }
7055
7148
  };
7056
7149
 
7150
+ // src/loop/dispatch.ts
7151
+ function readParallelMax() {
7152
+ const raw = Number.parseInt(process.env.REASONIX_PARALLEL_MAX ?? "", 10);
7153
+ return Number.isFinite(raw) && raw >= 1 ? Math.min(raw, 16) : 3;
7154
+ }
7155
+ function readDispatchSerial() {
7156
+ return (process.env.REASONIX_TOOL_DISPATCH ?? "auto").toLowerCase() === "serial";
7157
+ }
7158
+ async function* dispatchToolCallsChunked(repairedCalls, ctx) {
7159
+ const dispatchSerial = readDispatchSerial();
7160
+ const parallelMax = readParallelMax();
7161
+ let callIdx = 0;
7162
+ while (callIdx < repairedCalls.length) {
7163
+ const chunk = [];
7164
+ if (!dispatchSerial) {
7165
+ while (callIdx < repairedCalls.length && chunk.length < parallelMax && ctx.isParallelSafe(repairedCalls[callIdx]?.function?.name ?? "")) {
7166
+ chunk.push(repairedCalls[callIdx++]);
7167
+ }
7168
+ }
7169
+ if (chunk.length === 0) {
7170
+ chunk.push(repairedCalls[callIdx++]);
7171
+ }
7172
+ for (const call of chunk) {
7173
+ const callId = ctx.inflightIdFor(call);
7174
+ ctx.inflightAdd(callId);
7175
+ yield {
7176
+ turn: ctx.turn,
7177
+ role: "tool_start",
7178
+ content: "",
7179
+ toolName: call.function?.name ?? "",
7180
+ toolArgs: call.function?.arguments ?? "{}",
7181
+ callId
7182
+ };
7183
+ }
7184
+ const settled = await Promise.allSettled(chunk.map((c) => ctx.runOne(c, ctx.signal)));
7185
+ for (let k = 0; k < chunk.length; k++) {
7186
+ const call = chunk[k];
7187
+ const name = call.function?.name ?? "";
7188
+ const args = call.function?.arguments ?? "{}";
7189
+ const s = settled[k];
7190
+ let result;
7191
+ let preWarnings = [];
7192
+ let postWarnings = [];
7193
+ if (s.status === "fulfilled") {
7194
+ preWarnings = s.value.preWarnings;
7195
+ postWarnings = s.value.postWarnings;
7196
+ result = s.value.result;
7197
+ } else {
7198
+ const err = s.reason instanceof Error ? s.reason : new Error(String(s.reason));
7199
+ result = JSON.stringify({ error: `${err.name}: ${err.message}` });
7200
+ }
7201
+ for (const w of preWarnings) yield w;
7202
+ for (const w of postWarnings) yield w;
7203
+ const rateLimited = parseRateLimitedToolResult(result);
7204
+ if (rateLimited && !ctx.rateLimitState.shown) {
7205
+ ctx.rateLimitState.shown = true;
7206
+ yield {
7207
+ turn: ctx.turn,
7208
+ role: "warning",
7209
+ content: rateLimited.message
7210
+ };
7211
+ }
7212
+ ctx.appendAndPersist({
7213
+ role: "tool",
7214
+ tool_call_id: call.id ?? "",
7215
+ name,
7216
+ content: result
7217
+ });
7218
+ yield {
7219
+ turn: ctx.turn,
7220
+ role: "tool",
7221
+ content: result,
7222
+ toolName: name,
7223
+ toolArgs: args,
7224
+ callId: ctx.inflightIdFor(call)
7225
+ };
7226
+ }
7227
+ }
7228
+ }
7229
+
7057
7230
  // src/loop/errors.ts
7058
- function formatLoopError(err, probe) {
7231
+ function formatLoopError(err, probe, opts) {
7059
7232
  const msg = err.message ?? "";
7060
7233
  if (msg.includes("maximum context length")) {
7061
7234
  const reqMatch = msg.match(/requested\s+(\d+)\s+tokens/);
@@ -7072,7 +7245,7 @@ function formatLoopError(err, probe) {
7072
7245
  if (status === "422") return t("errors.badparam422", { inner });
7073
7246
  if (status === "400") return t("errors.badrequest400", { inner });
7074
7247
  if (status === "429") return t("errors.concurrency429", { inner });
7075
- if (is5xxStatus(status)) return formatDeepSeek5xx(status, probe);
7248
+ if (is5xxStatus(status)) return format5xx(status, probe, opts?.upstreamHost);
7076
7249
  return msg;
7077
7250
  }
7078
7251
  function is5xxError(err) {
@@ -7084,15 +7257,40 @@ async function probeDeepSeekReachable(client, timeoutMs = 1500) {
7084
7257
  const balance = await client.getBalance({ signal: AbortSignal.timeout(timeoutMs) });
7085
7258
  return { reachable: balance !== null };
7086
7259
  }
7260
+ function isDeepSeekHost(baseUrl) {
7261
+ if (!baseUrl) return false;
7262
+ try {
7263
+ const host = new URL(baseUrl).hostname.toLowerCase();
7264
+ return host === "api.deepseek.com";
7265
+ } catch {
7266
+ return false;
7267
+ }
7268
+ }
7087
7269
  function is5xxStatus(status) {
7088
7270
  return status === "500" || status === "502" || status === "503" || status === "504";
7089
7271
  }
7272
+ function format5xx(status, probe, upstreamHost) {
7273
+ if (upstreamHost !== void 0 && !isDeepSeekHost(upstreamHost)) {
7274
+ return formatUpstream5xx(status, upstreamHost);
7275
+ }
7276
+ return formatDeepSeek5xx(status, probe);
7277
+ }
7090
7278
  function formatDeepSeek5xx(status, probe) {
7091
7279
  const head = t("errors.deepseek5xxHead", { status });
7092
7280
  const probeNote = probe === void 0 ? "" : probe.reachable ? t("errors.deepseek5xxReachable") : t("errors.deepseek5xxUnreachable");
7093
7281
  const action = probe?.reachable === false ? t("errors.deepseek5xxActionNetwork") : t("errors.deepseek5xxActionRetry");
7094
7282
  return `${head}${probeNote}${action}`;
7095
7283
  }
7284
+ function formatUpstream5xx(status, baseUrl) {
7285
+ let host = baseUrl;
7286
+ try {
7287
+ host = new URL(baseUrl).host || baseUrl;
7288
+ } catch {
7289
+ }
7290
+ const head = t("errors.upstream5xxHead", { status, host });
7291
+ const action = t("errors.upstream5xxActionRetry");
7292
+ return `${head}${action}`;
7293
+ }
7096
7294
  function reasonPrefixFor(reason) {
7097
7295
  if (reason === "aborted") return t("errors.reasonAborted");
7098
7296
  if (reason === "context-guard") return t("errors.reasonContextGuard");
@@ -7205,6 +7403,56 @@ function shrinkOversizedToolResultsByTokens(messages, maxTokens) {
7205
7403
  });
7206
7404
  return { messages: out, healedCount, tokensSaved, charsSaved };
7207
7405
  }
7406
+ function shrinkOversizedToolCallArgsByTokens(messages, maxTokens) {
7407
+ let healedCount = 0;
7408
+ let tokensSaved = 0;
7409
+ let charsSaved = 0;
7410
+ const out = messages.map((msg) => {
7411
+ if (msg.role !== "assistant" || !Array.isArray(msg.tool_calls)) return msg;
7412
+ let changed = false;
7413
+ const newCalls = msg.tool_calls.map((call) => {
7414
+ const args = call.function?.arguments;
7415
+ if (typeof args !== "string" || args.length <= maxTokens) return call;
7416
+ const beforeTokens = countTokensBounded(args);
7417
+ if (beforeTokens <= maxTokens) return call;
7418
+ const shrunk = shrinkJsonLongStrings(args);
7419
+ const afterTokens = countTokens(shrunk);
7420
+ if (afterTokens >= beforeTokens) return call;
7421
+ changed = true;
7422
+ healedCount += 1;
7423
+ tokensSaved += beforeTokens - afterTokens;
7424
+ charsSaved += args.length - shrunk.length;
7425
+ return { ...call, function: { ...call.function, arguments: shrunk } };
7426
+ });
7427
+ if (!changed) return msg;
7428
+ return { ...msg, tool_calls: newCalls };
7429
+ });
7430
+ return { messages: out, healedCount, tokensSaved, charsSaved };
7431
+ }
7432
+ function shrinkJsonLongStrings(jsonStr) {
7433
+ let parsed;
7434
+ try {
7435
+ parsed = JSON.parse(jsonStr);
7436
+ } catch {
7437
+ const head = jsonStr.slice(0, 200);
7438
+ return `${head}\u2026[shrunk: ${jsonStr.length} chars, unparsed]`;
7439
+ }
7440
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
7441
+ return jsonStr;
7442
+ }
7443
+ const LONG_THRESHOLD = 300;
7444
+ const input = parsed;
7445
+ const output = {};
7446
+ for (const [k, v] of Object.entries(input)) {
7447
+ if (typeof v === "string" && v.length > LONG_THRESHOLD) {
7448
+ const newlines = v.match(/\n/g)?.length ?? 0;
7449
+ output[k] = `[\u2026shrunk: ${v.length} chars, ${newlines} lines \u2014 tool already responded, see result]`;
7450
+ } else {
7451
+ output[k] = v;
7452
+ }
7453
+ }
7454
+ return JSON.stringify(output);
7455
+ }
7208
7456
 
7209
7457
  // src/loop/healing.ts
7210
7458
  var _stampSeq = 0;
@@ -7275,12 +7523,13 @@ function stampMissingReasoningForThinkingMode(messages, model) {
7275
7523
  function healLoadedMessagesByTokens(messages, maxTokens) {
7276
7524
  const shrunk = shrinkOversizedToolResultsByTokens(messages, maxTokens);
7277
7525
  const paired = fixToolCallPairing(shrunk.messages);
7278
- const healedCount = shrunk.healedCount + paired.droppedAssistantCalls + paired.droppedStrayTools;
7526
+ const argsShrunk = shrinkOversizedToolCallArgsByTokens(paired.messages, maxTokens);
7527
+ const healedCount = shrunk.healedCount + argsShrunk.healedCount + paired.droppedAssistantCalls + paired.droppedStrayTools;
7279
7528
  return {
7280
- messages: paired.messages,
7529
+ messages: argsShrunk.messages,
7281
7530
  healedCount,
7282
- tokensSaved: shrunk.tokensSaved,
7283
- charsSaved: shrunk.charsSaved
7531
+ tokensSaved: shrunk.tokensSaved + argsShrunk.tokensSaved,
7532
+ charsSaved: shrunk.charsSaved + argsShrunk.charsSaved
7284
7533
  };
7285
7534
  }
7286
7535
 
@@ -7299,6 +7548,112 @@ function* hookWarnings(outcomes, turn) {
7299
7548
  }
7300
7549
  }
7301
7550
 
7551
+ // src/loop/reasoning-retention.ts
7552
+ function hasToolCalls(msg) {
7553
+ return Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0;
7554
+ }
7555
+ function stripDroppableReasoningContent(messages) {
7556
+ let lastUser = -1;
7557
+ for (let i = messages.length - 1; i >= 0; i--) {
7558
+ if (messages[i].role === "user") {
7559
+ lastUser = i;
7560
+ break;
7561
+ }
7562
+ }
7563
+ if (lastUser < 0) {
7564
+ return { messages, prunedCount: 0, charsDropped: 0 };
7565
+ }
7566
+ let next = null;
7567
+ let prunedCount = 0;
7568
+ let charsDropped = 0;
7569
+ for (let i = 0; i < messages.length; i++) {
7570
+ const msg = messages[i];
7571
+ if (msg.role !== "assistant" || i > lastUser || hasToolCalls(msg) || !Object.hasOwn(msg, "reasoning_content")) {
7572
+ continue;
7573
+ }
7574
+ if (next === null) next = messages.slice();
7575
+ const { reasoning_content: dropped, ...replacement } = msg;
7576
+ if (typeof dropped === "string") charsDropped += dropped.length;
7577
+ next[i] = replacement;
7578
+ prunedCount += 1;
7579
+ }
7580
+ return {
7581
+ messages: next ?? messages,
7582
+ prunedCount,
7583
+ charsDropped
7584
+ };
7585
+ }
7586
+
7587
+ // src/loop/streaming.ts
7588
+ async function* streamModelResponse(opts) {
7589
+ const { client, model, messages, toolSpecs, signal, reasoningEffort, turn } = opts;
7590
+ let assistantContent = "";
7591
+ let reasoningContent = "";
7592
+ let usage = null;
7593
+ const callBuf = /* @__PURE__ */ new Map();
7594
+ const readyIndices = /* @__PURE__ */ new Set();
7595
+ for await (const chunk of client.stream({
7596
+ model,
7597
+ messages,
7598
+ tools: toolSpecs.length ? toolSpecs : void 0,
7599
+ signal,
7600
+ thinking: thinkingModeForModel(model),
7601
+ reasoningEffort
7602
+ })) {
7603
+ if (chunk.reasoningDelta) {
7604
+ reasoningContent += chunk.reasoningDelta;
7605
+ yield {
7606
+ turn,
7607
+ role: "assistant_delta",
7608
+ content: "",
7609
+ reasoningDelta: chunk.reasoningDelta
7610
+ };
7611
+ }
7612
+ if (chunk.contentDelta) {
7613
+ assistantContent += chunk.contentDelta;
7614
+ yield {
7615
+ turn,
7616
+ role: "assistant_delta",
7617
+ content: chunk.contentDelta
7618
+ };
7619
+ }
7620
+ if (chunk.toolCallDelta) {
7621
+ const d = chunk.toolCallDelta;
7622
+ const cur = callBuf.get(d.index) ?? {
7623
+ id: d.id,
7624
+ type: "function",
7625
+ function: { name: "", arguments: "" }
7626
+ };
7627
+ if (d.id) cur.id = d.id;
7628
+ if (d.name) cur.function.name = (cur.function.name ?? "") + d.name;
7629
+ if (d.argumentsDelta)
7630
+ cur.function.arguments = (cur.function.arguments ?? "") + d.argumentsDelta;
7631
+ callBuf.set(d.index, cur);
7632
+ if (!readyIndices.has(d.index) && cur.function.name && looksLikeCompleteJson(cur.function.arguments ?? "")) {
7633
+ readyIndices.add(d.index);
7634
+ }
7635
+ if (cur.function.name) {
7636
+ yield {
7637
+ turn,
7638
+ role: "tool_call_delta",
7639
+ content: "",
7640
+ toolName: cur.function.name,
7641
+ toolCallArgsChars: (cur.function.arguments ?? "").length,
7642
+ toolCallIndex: d.index,
7643
+ toolCallReadyCount: readyIndices.size
7644
+ };
7645
+ }
7646
+ }
7647
+ if (chunk.usage) usage = chunk.usage;
7648
+ }
7649
+ return {
7650
+ assistantContent,
7651
+ reasoningContent,
7652
+ toolCalls: [...callBuf.values()],
7653
+ usage
7654
+ };
7655
+ }
7656
+
7302
7657
  // src/repair/scavenge.ts
7303
7658
  var MAX_SCAVENGE_INPUT = 100 * 1024;
7304
7659
  function scavengeToolCalls(reasoningContent, opts) {
@@ -7656,6 +8011,10 @@ var MID_TURN_STEER_WRAPPER = "[Mid-turn steer queued by the user. Do not treat t
7656
8011
  function formatSteerUserMessage(content) {
7657
8012
  return [MID_TURN_STEER_WRAPPER, content].join("\n");
7658
8013
  }
8014
+ function shrinkMessageForRetention(message) {
8015
+ if (message.role !== "assistant" || !Array.isArray(message.tool_calls)) return message;
8016
+ return shrinkOversizedToolCallArgsByTokens([message], DEFAULT_MAX_RESULT_TOKENS).messages[0] ?? message;
8017
+ }
7659
8018
  var CacheFirstLoop = class {
7660
8019
  client;
7661
8020
  prefix;
@@ -7749,7 +8108,8 @@ var CacheFirstLoop = class {
7749
8108
  const prior = loadSessionMessages(this.sessionName);
7750
8109
  const shrunk = healLoadedMessagesByTokens(prior, DEFAULT_MAX_RESULT_TOKENS);
7751
8110
  const stamped = stampMissingReasoningForThinkingMode(shrunk.messages, this.model);
7752
- const messages = stamped.messages;
8111
+ const pruned = stripDroppableReasoningContent(stamped.messages);
8112
+ const messages = pruned.messages;
7753
8113
  const healedCount = shrunk.healedCount + stamped.stampedCount;
7754
8114
  const tokensSaved = shrunk.tokensSaved;
7755
8115
  for (const msg of messages) this.log.append(msg);
@@ -7766,15 +8126,17 @@ var CacheFirstLoop = class {
7766
8126
  lastPromptTokens: meta.lastPromptTokens
7767
8127
  });
7768
8128
  }
7769
- if (healedCount > 0) {
8129
+ if (healedCount > 0 || pruned.prunedCount > 0) {
7770
8130
  try {
7771
8131
  rewriteSession(this.sessionName, messages);
7772
8132
  } catch {
7773
8133
  }
7774
- process.stderr.write(
7775
- `\u25B8 session "${this.sessionName}": healed ${healedCount} entr${healedCount === 1 ? "y" : "ies"}${tokensSaved > 0 ? ` (shrunk ${tokensSaved.toLocaleString()} tokens of oversized tool output)` : " (dropped dangling tool_calls tail)"}. Rewrote session file.
8134
+ if (healedCount > 0) {
8135
+ process.stderr.write(
8136
+ `\u25B8 session "${this.sessionName}": healed ${healedCount} entr${healedCount === 1 ? "y" : "ies"}${tokensSaved > 0 ? ` (shrunk ${tokensSaved.toLocaleString()} tokens of oversized tool output/arguments)` : " (dropped dangling tool_calls tail)"}. Rewrote session file.
7776
8137
  `
7777
- );
8138
+ );
8139
+ }
7778
8140
  }
7779
8141
  } else {
7780
8142
  this.resumedMessageCount = 0;
@@ -7801,21 +8163,23 @@ var CacheFirstLoop = class {
7801
8163
  return this.context.getLogTokens();
7802
8164
  }
7803
8165
  appendAndPersist(message) {
7804
- this.log.append(message);
8166
+ const retained = shrinkMessageForRetention(message);
8167
+ this.log.append(retained);
7805
8168
  if (this.sessionName) {
7806
8169
  try {
7807
- appendSessionMessage(this.sessionName, message);
8170
+ appendSessionMessage(this.sessionName, retained);
7808
8171
  } catch {
7809
8172
  }
7810
8173
  }
7811
8174
  }
7812
8175
  /** Swap the just-appended assistant entry — used by self-correction to restore the original tool_calls without dropping reasoning_content. */
7813
8176
  replaceTailAssistantMessage(message) {
8177
+ const retained = shrinkMessageForRetention(message);
7814
8178
  const entries = this.log.entries;
7815
8179
  const tail = entries[entries.length - 1];
7816
8180
  if (!tail || tail.role !== "assistant") return;
7817
8181
  const kept = entries.slice(0, -1);
7818
- kept.push(message);
8182
+ kept.push(retained);
7819
8183
  this.log.compactInPlace(kept);
7820
8184
  if (this.sessionName) {
7821
8185
  try {
@@ -7841,6 +8205,8 @@ var CacheFirstLoop = class {
7841
8205
  this.stats.reset();
7842
8206
  this._turn = 0;
7843
8207
  this._budgetWarned = false;
8208
+ this._steerQueue.length = 0;
8209
+ this._steerConsumed = false;
7844
8210
  let systemRebuilt = false;
7845
8211
  if (this._rebuildSystem) {
7846
8212
  try {
@@ -7864,6 +8230,8 @@ var CacheFirstLoop = class {
7864
8230
  this.log.compactInPlace([]);
7865
8231
  this.scratch.reset();
7866
8232
  this._inflight.clear();
8233
+ this._steerQueue.length = 0;
8234
+ this._steerConsumed = false;
7867
8235
  this.sessionName = opts.sessionName;
7868
8236
  if (this._rebuildSystem) {
7869
8237
  try {
@@ -7941,7 +8309,8 @@ ${reason}`
7941
8309
  signal,
7942
8310
  maxResultTokens: DEFAULT_MAX_RESULT_TOKENS,
7943
8311
  confirmationGate: this.confirmationGate,
7944
- readTracker: this.readTracker
8312
+ readTracker: this.readTracker,
8313
+ rootDir: this.hookCwd
7945
8314
  });
7946
8315
  const postReport = await runHooks({
7947
8316
  hooks: this.hooks,
@@ -7976,15 +8345,22 @@ ${reason}`
7976
8345
  healActiveLogBeforeSend() {
7977
8346
  const current = this.log.toMessages();
7978
8347
  const healed = healLoadedMessages(current, DEFAULT_MAX_RESULT_CHARS);
7979
- if (healed.healedCount === 0) return current;
7980
- this.log.compactInPlace(healed.messages);
8348
+ const argsShrunk = shrinkOversizedToolCallArgsByTokens(
8349
+ healed.messages,
8350
+ DEFAULT_MAX_RESULT_TOKENS
8351
+ );
8352
+ const pruned = stripDroppableReasoningContent(argsShrunk.messages);
8353
+ if (healed.healedCount === 0 && argsShrunk.healedCount === 0 && pruned.prunedCount === 0) {
8354
+ return current;
8355
+ }
8356
+ this.log.compactInPlace(pruned.messages);
7981
8357
  if (this.sessionName) {
7982
8358
  try {
7983
- rewriteSession(this.sessionName, healed.messages);
8359
+ rewriteSession(this.sessionName, pruned.messages);
7984
8360
  } catch {
7985
8361
  }
7986
8362
  }
7987
- return healed.messages;
8363
+ return pruned.messages;
7988
8364
  }
7989
8365
  abort(opts = {}) {
7990
8366
  if (opts.discardCurrentTurn) this._discardAbortRequested = true;
@@ -8094,7 +8470,7 @@ ${reason}`
8094
8470
  const turnStartLogIndex = this.log.length;
8095
8471
  this.appendAndPersist({ role: "user", content: userInput });
8096
8472
  const toolSpecs = this.prefix.tools();
8097
- let rateLimitWarningShown = false;
8473
+ const rateLimitState = { shown: false };
8098
8474
  {
8099
8475
  const turnStart = this.context.estimateTurnStart(
8100
8476
  this.buildMessages(),
@@ -8107,7 +8483,9 @@ ${reason}`
8107
8483
  role: "status",
8108
8484
  content: t("loop.turnStartFoldStatus")
8109
8485
  };
8110
- const result = await this.context.fold(this.model, { requireTailBoundary: true });
8486
+ const result = await this.context.fold(this.model, {
8487
+ requireTailBoundary: true
8488
+ });
8111
8489
  if (result.folded) {
8112
8490
  this._foldedThisTurn = true;
8113
8491
  yield {
@@ -8175,64 +8553,19 @@ ${reason}`
8175
8553
  let usage = null;
8176
8554
  try {
8177
8555
  if (this.stream) {
8178
- const callBuf = /* @__PURE__ */ new Map();
8179
- const readyIndices = /* @__PURE__ */ new Set();
8180
- const callModel = this.model;
8181
- for await (const chunk of this.client.stream({
8182
- model: callModel,
8556
+ const result = yield* streamModelResponse({
8557
+ client: this.client,
8558
+ model: this.model,
8183
8559
  messages,
8184
- tools: toolSpecs.length ? toolSpecs : void 0,
8560
+ toolSpecs,
8185
8561
  signal,
8186
- thinking: thinkingModeForModel(callModel),
8187
- reasoningEffort: this.reasoningEffort
8188
- })) {
8189
- if (chunk.reasoningDelta) {
8190
- reasoningContent += chunk.reasoningDelta;
8191
- yield {
8192
- turn: this._turn,
8193
- role: "assistant_delta",
8194
- content: "",
8195
- reasoningDelta: chunk.reasoningDelta
8196
- };
8197
- }
8198
- if (chunk.contentDelta) {
8199
- assistantContent += chunk.contentDelta;
8200
- yield {
8201
- turn: this._turn,
8202
- role: "assistant_delta",
8203
- content: chunk.contentDelta
8204
- };
8205
- }
8206
- if (chunk.toolCallDelta) {
8207
- const d = chunk.toolCallDelta;
8208
- const cur = callBuf.get(d.index) ?? {
8209
- id: d.id,
8210
- type: "function",
8211
- function: { name: "", arguments: "" }
8212
- };
8213
- if (d.id) cur.id = d.id;
8214
- if (d.name) cur.function.name = (cur.function.name ?? "") + d.name;
8215
- if (d.argumentsDelta)
8216
- cur.function.arguments = (cur.function.arguments ?? "") + d.argumentsDelta;
8217
- callBuf.set(d.index, cur);
8218
- if (!readyIndices.has(d.index) && cur.function.name && looksLikeCompleteJson(cur.function.arguments ?? "")) {
8219
- readyIndices.add(d.index);
8220
- }
8221
- if (cur.function.name) {
8222
- yield {
8223
- turn: this._turn,
8224
- role: "tool_call_delta",
8225
- content: "",
8226
- toolName: cur.function.name,
8227
- toolCallArgsChars: (cur.function.arguments ?? "").length,
8228
- toolCallIndex: d.index,
8229
- toolCallReadyCount: readyIndices.size
8230
- };
8231
- }
8232
- }
8233
- if (chunk.usage) usage = chunk.usage;
8234
- }
8235
- toolCalls = [...callBuf.values()];
8562
+ reasoningEffort: this.reasoningEffort,
8563
+ turn: this._turn
8564
+ });
8565
+ assistantContent = result.assistantContent;
8566
+ reasoningContent = result.reasoningContent;
8567
+ toolCalls = result.toolCalls;
8568
+ usage = result.usage;
8236
8569
  } else {
8237
8570
  const callModel = this.model;
8238
8571
  const resp = await this.client.chat({
@@ -8259,12 +8592,14 @@ ${reason}`
8259
8592
  this._steerQueue.length = 0;
8260
8593
  return;
8261
8594
  }
8262
- const probe = is5xxError(err) ? await probeDeepSeekReachable(this.client) : void 0;
8595
+ const upstreamHost = this.client.baseUrl;
8596
+ const dsHost = isDeepSeekHost(upstreamHost);
8597
+ const probe = is5xxError(err) && dsHost ? await probeDeepSeekReachable(this.client) : void 0;
8263
8598
  yield {
8264
8599
  turn: this._turn,
8265
8600
  role: "error",
8266
8601
  content: "",
8267
- error: formatLoopError(err, probe)
8602
+ error: formatLoopError(err, probe, { upstreamHost })
8268
8603
  };
8269
8604
  this._steerQueue.length = 0;
8270
8605
  return;
@@ -8390,76 +8725,16 @@ ${reason}`
8390
8725
  this._steerQueue.length = 0;
8391
8726
  return;
8392
8727
  }
8393
- const dispatchSerial = (process.env.REASONIX_TOOL_DISPATCH ?? "auto").toLowerCase() === "serial";
8394
- const parallelMaxParsed = Number.parseInt(process.env.REASONIX_PARALLEL_MAX ?? "", 10);
8395
- const parallelMax = Number.isFinite(parallelMaxParsed) && parallelMaxParsed >= 1 ? Math.min(parallelMaxParsed, 16) : 3;
8396
- let callIdx = 0;
8397
- while (callIdx < repairedCalls.length) {
8398
- const chunk = [];
8399
- if (!dispatchSerial) {
8400
- while (callIdx < repairedCalls.length && chunk.length < parallelMax && this.tools.isParallelSafe(repairedCalls[callIdx]?.function?.name ?? "")) {
8401
- chunk.push(repairedCalls[callIdx++]);
8402
- }
8403
- }
8404
- if (chunk.length === 0) {
8405
- chunk.push(repairedCalls[callIdx++]);
8406
- }
8407
- for (const call of chunk) {
8408
- const callId = this.inflightIdFor(call);
8409
- this._inflight.add(callId);
8410
- yield {
8411
- turn: this._turn,
8412
- role: "tool_start",
8413
- content: "",
8414
- toolName: call.function?.name ?? "",
8415
- toolArgs: call.function?.arguments ?? "{}",
8416
- callId
8417
- };
8418
- }
8419
- const settled = await Promise.allSettled(chunk.map((c) => this.runOneToolCall(c, signal)));
8420
- for (let k = 0; k < chunk.length; k++) {
8421
- const call = chunk[k];
8422
- const name = call.function?.name ?? "";
8423
- const args = call.function?.arguments ?? "{}";
8424
- const s = settled[k];
8425
- let result;
8426
- let preWarnings = [];
8427
- let postWarnings = [];
8428
- if (s.status === "fulfilled") {
8429
- preWarnings = s.value.preWarnings;
8430
- postWarnings = s.value.postWarnings;
8431
- result = s.value.result;
8432
- } else {
8433
- const err = s.reason instanceof Error ? s.reason : new Error(String(s.reason));
8434
- result = JSON.stringify({ error: `${err.name}: ${err.message}` });
8435
- }
8436
- for (const w of preWarnings) yield w;
8437
- for (const w of postWarnings) yield w;
8438
- const rateLimited = parseRateLimitedToolResult(result);
8439
- if (rateLimited && !rateLimitWarningShown) {
8440
- rateLimitWarningShown = true;
8441
- yield {
8442
- turn: this._turn,
8443
- role: "warning",
8444
- content: rateLimited.message
8445
- };
8446
- }
8447
- this.appendAndPersist({
8448
- role: "tool",
8449
- tool_call_id: call.id ?? "",
8450
- name,
8451
- content: result
8452
- });
8453
- yield {
8454
- turn: this._turn,
8455
- role: "tool",
8456
- content: result,
8457
- toolName: name,
8458
- toolArgs: args,
8459
- callId: this.inflightIdFor(call)
8460
- };
8461
- }
8462
- }
8728
+ yield* dispatchToolCallsChunked(repairedCalls, {
8729
+ turn: this._turn,
8730
+ signal,
8731
+ isParallelSafe: (name) => this.tools.isParallelSafe(name),
8732
+ inflightIdFor: (call) => this.inflightIdFor(call),
8733
+ inflightAdd: (id) => this._inflight.add(id),
8734
+ runOne: (call, sig) => this.runOneToolCall(call, sig),
8735
+ appendAndPersist: (m) => this.appendAndPersist(m),
8736
+ rateLimitState
8737
+ });
8463
8738
  }
8464
8739
  }
8465
8740
  summaryContext() {
@@ -8489,9 +8764,9 @@ function parsePositiveIntEnv(raw) {
8489
8764
  }
8490
8765
 
8491
8766
  // src/at-mentions.ts
8492
- import { existsSync, readFileSync, readdirSync, statSync } from "fs";
8767
+ import { existsSync as existsSync2, readFileSync, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
8493
8768
  import { readdir, stat } from "fs/promises";
8494
- import { isAbsolute, join, relative, resolve as resolve2 } from "path";
8769
+ import { isAbsolute, join as join2, relative as relative2, resolve as resolve3 } from "path";
8495
8770
 
8496
8771
  // src/at-mentions-url.ts
8497
8772
  var AT_URL_PATTERN = /(?<=^|\s)@(https?:\/\/\S+)/g;
@@ -8622,7 +8897,7 @@ function listFilesSync(root, opts = {}) {
8622
8897
  function listFilesWithStatsSync(root, opts = {}) {
8623
8898
  const maxResults = Math.max(1, opts.maxResults ?? 2e3);
8624
8899
  const ignoreDirs = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
8625
- const rootAbs = resolve2(root);
8900
+ const rootAbs = resolve3(root);
8626
8901
  const respectGi = opts.respectGitignore !== false;
8627
8902
  const out = [];
8628
8903
  const walk2 = (dirAbs, dirRel, layers) => {
@@ -8634,7 +8909,7 @@ function listFilesWithStatsSync(root, opts = {}) {
8634
8909
  }
8635
8910
  let entries;
8636
8911
  try {
8637
- entries = readdirSync(dirAbs, { withFileTypes: true });
8912
+ entries = readdirSync2(dirAbs, { withFileTypes: true });
8638
8913
  } catch {
8639
8914
  return;
8640
8915
  }
@@ -8642,7 +8917,7 @@ function listFilesWithStatsSync(root, opts = {}) {
8642
8917
  for (const ent of entries) {
8643
8918
  if (out.length >= maxResults) return;
8644
8919
  const relPath = dirRel ? `${dirRel}/${ent.name}` : ent.name;
8645
- const absPath = join(dirAbs, ent.name);
8920
+ const absPath = join2(dirAbs, ent.name);
8646
8921
  if (ent.isDirectory()) {
8647
8922
  if (ent.name.startsWith(".") || ignoreDirs.has(ent.name)) continue;
8648
8923
  if (ignoredByLayers(effectiveLayers, absPath, true)) continue;
@@ -8651,14 +8926,14 @@ function listFilesWithStatsSync(root, opts = {}) {
8651
8926
  if (ignoredByLayers(effectiveLayers, absPath, false)) continue;
8652
8927
  let mtimeMs = 0;
8653
8928
  try {
8654
- mtimeMs = statSync(absPath).mtimeMs;
8929
+ mtimeMs = statSync2(absPath).mtimeMs;
8655
8930
  } catch {
8656
8931
  }
8657
8932
  out.push({ path: relPath, mtimeMs });
8658
8933
  } else if (ent.isSymbolicLink()) {
8659
8934
  let target = null;
8660
8935
  try {
8661
- target = statSync(absPath);
8936
+ target = statSync2(absPath);
8662
8937
  } catch {
8663
8938
  continue;
8664
8939
  }
@@ -8686,7 +8961,7 @@ async function listFilesWithStatsAsync(root, opts = {}) {
8686
8961
  async function walkFilesStream(root, opts) {
8687
8962
  const ignoreDirs = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
8688
8963
  const respectGi = opts.respectGitignore !== false;
8689
- const rootAbs = resolve2(root);
8964
+ const rootAbs = resolve3(root);
8690
8965
  const progressGap = Math.max(0, opts.progressIntervalMs ?? 100);
8691
8966
  let scanned = 0;
8692
8967
  let halted = false;
@@ -8722,7 +8997,7 @@ async function walkFilesStream(root, opts) {
8722
8997
  const fileEnts = [];
8723
8998
  for (const ent of entries) {
8724
8999
  if (halted || opts.signal?.aborted) break;
8725
- const absPath = join(dirAbs, ent.name);
9000
+ const absPath = join2(dirAbs, ent.name);
8726
9001
  if (ent.isDirectory()) {
8727
9002
  if (ent.name.startsWith(".") || ignoreDirs.has(ent.name)) continue;
8728
9003
  if (ignoredByLayers(effectiveLayers, absPath, true)) continue;
@@ -8745,10 +9020,10 @@ async function walkFilesStream(root, opts) {
8745
9020
  return { scanned, cancelled: !!opts.signal?.aborted };
8746
9021
  }
8747
9022
  async function flushFiles(ents, dirAbs, dirRel, layers, emit) {
8748
- const accepted = ents.filter((e) => !ignoredByLayers(layers, join(dirAbs, e.name), false));
9023
+ const accepted = ents.filter((e) => !ignoredByLayers(layers, join2(dirAbs, e.name), false));
8749
9024
  const stats = await Promise.all(
8750
9025
  accepted.map(
8751
- (e) => stat(join(dirAbs, e.name)).then((s) => ({ mtimeMs: s.mtimeMs, isFile: s.isFile() })).catch(() => null)
9026
+ (e) => stat(join2(dirAbs, e.name)).then((s) => ({ mtimeMs: s.mtimeMs, isFile: s.isFile() })).catch(() => null)
8752
9027
  )
8753
9028
  );
8754
9029
  for (let i = 0; i < accepted.length; i++) {
@@ -8761,13 +9036,17 @@ async function flushFiles(ents, dirAbs, dirRel, layers, emit) {
8761
9036
  });
8762
9037
  }
8763
9038
  }
9039
+ var listDirectoryCache = new TtlLruCache(64, 5e3);
8764
9040
  async function listDirectory(root, relDir, opts = {}) {
8765
9041
  const ignoreDirs = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
8766
9042
  const respectGi = opts.respectGitignore !== false;
8767
- const rootAbs = resolve2(root);
8768
- const dirAbs = resolve2(rootAbs, relDir);
8769
- const rel = relative(rootAbs, dirAbs);
9043
+ const rootAbs = resolve3(root);
9044
+ const dirAbs = resolve3(rootAbs, relDir);
9045
+ const rel = relative2(rootAbs, dirAbs);
8770
9046
  if (rel.startsWith("..") || isAbsolute(rel)) return [];
9047
+ const cacheKey = `${dirAbs}\0${respectGi ? "g" : ""}\0${[...ignoreDirs].sort().join(",")}`;
9048
+ const cached = listDirectoryCache.get(cacheKey);
9049
+ if (cached) return cached;
8771
9050
  const layers = [];
8772
9051
  if (respectGi) {
8773
9052
  const segs = rel ? rel.split(/[\\/]/) : [];
@@ -8775,7 +9054,7 @@ async function listDirectory(root, relDir, opts = {}) {
8775
9054
  const ig = await loadGitignoreAt(cursor);
8776
9055
  if (ig) layers.push({ dirAbs: cursor, ig });
8777
9056
  for (const seg of segs) {
8778
- cursor = join(cursor, seg);
9057
+ cursor = join2(cursor, seg);
8779
9058
  const igSeg = await loadGitignoreAt(cursor);
8780
9059
  if (igSeg) layers.push({ dirAbs: cursor, ig: igSeg });
8781
9060
  }
@@ -8790,7 +9069,7 @@ async function listDirectory(root, relDir, opts = {}) {
8790
9069
  const dirs = [];
8791
9070
  const files = [];
8792
9071
  for (const ent of raw) {
8793
- const absPath = join(dirAbs, ent.name);
9072
+ const absPath = join2(dirAbs, ent.name);
8794
9073
  if (ent.isDirectory()) {
8795
9074
  if (ent.name.startsWith(".") || ignoreDirs.has(ent.name)) continue;
8796
9075
  if (ignoredByLayers(layers, absPath, true)) continue;
@@ -8807,7 +9086,7 @@ async function listDirectory(root, relDir, opts = {}) {
8807
9086
  }
8808
9087
  const stats = await Promise.all(
8809
9088
  files.map(
8810
- (e) => stat(join(dirAbs, e.name)).then((s) => ({ mtimeMs: s.mtimeMs, isFile: s.isFile() })).catch(() => null)
9089
+ (e) => stat(join2(dirAbs, e.name)).then((s) => ({ mtimeMs: s.mtimeMs, isFile: s.isFile() })).catch(() => null)
8811
9090
  )
8812
9091
  );
8813
9092
  const fileEntries = [];
@@ -8824,7 +9103,9 @@ async function listDirectory(root, relDir, opts = {}) {
8824
9103
  }
8825
9104
  dirs.sort((a, b) => a.name.localeCompare(b.name));
8826
9105
  fileEntries.sort((a, b) => a.name.localeCompare(b.name));
8827
- return [...dirs, ...fileEntries];
9106
+ const result = [...dirs, ...fileEntries];
9107
+ listDirectoryCache.set(cacheKey, result);
9108
+ return result;
8828
9109
  }
8829
9110
  function parseAtQuery(query) {
8830
9111
  const normalized = query.replace(/\\/g, "/");
@@ -8930,7 +9211,7 @@ function expandAtMentions(text, rootDir, opts = {}) {
8930
9211
  const maxBytes = opts.maxBytes ?? DEFAULT_AT_MENTION_MAX_BYTES;
8931
9212
  const maxDirEntries = Math.max(1, opts.maxDirEntries ?? DEFAULT_AT_DIR_MAX_ENTRIES);
8932
9213
  const fs4 = opts.fs ?? defaultFs;
8933
- const root = resolve2(rootDir);
9214
+ const root = resolve3(rootDir);
8934
9215
  const seen = /* @__PURE__ */ new Map();
8935
9216
  const expansions = [];
8936
9217
  const dirListings = /* @__PURE__ */ new Map();
@@ -8977,8 +9258,8 @@ function resolveMention(rawPath, root, maxBytes, maxDirEntries, fs4, dirListings
8977
9258
  if (isAbsolute(rawPath)) {
8978
9259
  return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "escape" };
8979
9260
  }
8980
- const resolved = resolve2(root, rawPath);
8981
- const rel = relative(root, resolved);
9261
+ const resolved = resolve3(root, rawPath);
9262
+ const rel = relative2(root, resolved);
8982
9263
  if (rel.startsWith("..") || isAbsolute(rel)) {
8983
9264
  return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "escape" };
8984
9265
  }
@@ -9007,7 +9288,7 @@ function resolveMention(rawPath, root, maxBytes, maxDirEntries, fs4, dirListings
9007
9288
  return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "not-file" };
9008
9289
  }
9009
9290
  function readSafe(root, rawPath, fs4) {
9010
- const resolved = resolve2(root, rawPath);
9291
+ const resolved = resolve3(root, rawPath);
9011
9292
  try {
9012
9293
  return fs4.read(resolved);
9013
9294
  } catch {
@@ -9015,23 +9296,23 @@ function readSafe(root, rawPath, fs4) {
9015
9296
  }
9016
9297
  }
9017
9298
  var defaultFs = {
9018
- exists: (p) => existsSync(p),
9299
+ exists: (p) => existsSync2(p),
9019
9300
  isFile: (p) => {
9020
9301
  try {
9021
- return statSync(p).isFile();
9302
+ return statSync2(p).isFile();
9022
9303
  } catch {
9023
9304
  return false;
9024
9305
  }
9025
9306
  },
9026
9307
  isDir: (p) => {
9027
9308
  try {
9028
- return statSync(p).isDirectory();
9309
+ return statSync2(p).isDirectory();
9029
9310
  } catch {
9030
9311
  return false;
9031
9312
  }
9032
9313
  },
9033
9314
  listDir: (dirAbs, root, max) => {
9034
- const dirRel = relative(root, dirAbs).split(/[\\/]/).join("/");
9315
+ const dirRel = relative2(root, dirAbs).split(/[\\/]/).join("/");
9035
9316
  const walkCap = Math.max(max * 4, 5e3);
9036
9317
  const all = listFilesSync(root, { maxResults: walkCap });
9037
9318
  const prefix = dirRel ? `${dirRel}/` : "";
@@ -9043,7 +9324,7 @@ var defaultFs = {
9043
9324
  },
9044
9325
  size: (p) => {
9045
9326
  try {
9046
- return statSync(p).size;
9327
+ return statSync2(p).size;
9047
9328
  } catch {
9048
9329
  return 0;
9049
9330
  }
@@ -9976,21 +10257,21 @@ import { promises as fs3 } from "fs";
9976
10257
  import * as pathMod5 from "path";
9977
10258
 
9978
10259
  // src/memory/subdir.ts
9979
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
9980
- import { dirname, join as join2, relative as relative2, resolve as resolve3 } from "path";
10260
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
10261
+ import { dirname, join as join3, relative as relative3, resolve as resolve4 } from "path";
9981
10262
  function findDirMemory(absDir, rootDir) {
9982
- const root = resolve3(rootDir);
9983
- const target = resolve3(absDir);
9984
- const rel = relative2(root, target);
10263
+ const root = resolve4(rootDir);
10264
+ const target = resolve4(absDir);
10265
+ const rel = relative3(root, target);
9985
10266
  if (rel.startsWith("..")) return [];
9986
10267
  const found = [];
9987
10268
  let cur = target;
9988
10269
  while (cur !== root) {
9989
- const r = relative2(root, cur);
10270
+ const r = relative3(root, cur);
9990
10271
  if (!r || r.startsWith("..")) break;
9991
10272
  for (const name of PROJECT_MEMORY_FILES) {
9992
- const path = join2(cur, name);
9993
- if (existsSync2(path)) {
10273
+ const path = join3(cur, name);
10274
+ if (existsSync3(path)) {
9994
10275
  found.push(path);
9995
10276
  break;
9996
10277
  }
@@ -10002,7 +10283,7 @@ function findDirMemory(absDir, rootDir) {
10002
10283
  return found;
10003
10284
  }
10004
10285
  function findSubdirMemoryAncestors(absPath, rootDir) {
10005
- return findDirMemory(dirname(resolve3(absPath)), rootDir);
10286
+ return findDirMemory(dirname(resolve4(absPath)), rootDir);
10006
10287
  }
10007
10288
  function readSubdirMemoryContent(path) {
10008
10289
  let raw;
@@ -10304,7 +10585,7 @@ var RegexRunner = class {
10304
10585
  this.defaultTimeoutMs = opts.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
10305
10586
  }
10306
10587
  testLines(text, source, flags, opts = {}) {
10307
- return new Promise((resolve6, reject) => {
10588
+ return new Promise((resolve7, reject) => {
10308
10589
  if (opts.signal?.aborted) {
10309
10590
  reject(new Error("regex evaluation aborted"));
10310
10591
  return;
@@ -10317,7 +10598,7 @@ var RegexRunner = class {
10317
10598
  this.killWorker();
10318
10599
  reject(new Error(`regex evaluation exceeded ${timeoutMs}ms`));
10319
10600
  }, timeoutMs);
10320
- const entry = { resolve: resolve6, reject, timer };
10601
+ const entry = { resolve: resolve7, reject, timer };
10321
10602
  if (opts.signal) {
10322
10603
  entry.signal = opts.signal;
10323
10604
  entry.onAbort = () => {
@@ -10448,6 +10729,7 @@ async function searchFiles(ctx, startAbs, args) {
10448
10729
  var MAX_HITS_PER_FILE = 30;
10449
10730
  var SUMMARY_MODE_TRIGGER_RATIO = 0.8;
10450
10731
  var WALK_DEADLINE_MS = 12e4;
10732
+ var REGEX_METACHARS = /[\\.+*?()[\]{}|^$]/;
10451
10733
  async function searchContent(ctx, startAbs, args) {
10452
10734
  throwIfAborted(args.signal);
10453
10735
  const caseSensitive = args.case_sensitive === true;
@@ -10455,12 +10737,15 @@ async function searchContent(ctx, startAbs, args) {
10455
10737
  const ctxLines = Math.max(0, Math.min(20, Math.floor(args.context ?? 0)));
10456
10738
  const summaryOnly = args.summary_only === true;
10457
10739
  const reFlags = caseSensitive ? "" : "i";
10740
+ const hasMeta = REGEX_METACHARS.test(args.pattern);
10458
10741
  let reSource = null;
10459
- try {
10460
- new RegExp(args.pattern, reFlags);
10461
- reSource = args.pattern;
10462
- } catch {
10463
- reSource = null;
10742
+ if (hasMeta) {
10743
+ try {
10744
+ new RegExp(args.pattern, reFlags);
10745
+ reSource = args.pattern;
10746
+ } catch {
10747
+ reSource = null;
10748
+ }
10464
10749
  }
10465
10750
  const needle = caseSensitive ? args.pattern : args.pattern.toLowerCase();
10466
10751
  const matches = [];
@@ -10549,9 +10834,10 @@ async function searchContent(ctx, startAbs, args) {
10549
10834
  if (firstNul !== -1 && firstNul < 8 * 1024) continue;
10550
10835
  const text = raw.toString("utf8");
10551
10836
  const rel = displayRel2(ctx.rootDir, full);
10552
- const lines = text.split(/\r?\n/);
10553
10837
  let hits;
10838
+ let lines;
10554
10839
  if (reSource !== null) {
10840
+ lines = text.split(/\r?\n/);
10555
10841
  try {
10556
10842
  hits = await getRegexRunner().testLines(text, reSource, reFlags, {
10557
10843
  signal: args.signal
@@ -10563,9 +10849,14 @@ async function searchContent(ctx, startAbs, args) {
10563
10849
  continue;
10564
10850
  }
10565
10851
  } else {
10852
+ const haystack = caseSensitive ? text : text.toLowerCase();
10853
+ if (haystack.indexOf(needle) === -1) {
10854
+ scanned++;
10855
+ continue;
10856
+ }
10857
+ lines = text.split(/\r?\n/);
10566
10858
  hits = [];
10567
10859
  for (let li = 0; li < lines.length; li++) {
10568
- throwIfAborted(args.signal);
10569
10860
  const lineForCheck = caseSensitive ? lines[li] : lines[li].toLowerCase();
10570
10861
  if (lineForCheck.includes(needle)) hits.push(li);
10571
10862
  }
@@ -10773,6 +11064,7 @@ ${body}`;
10773
11064
  registry.register({
10774
11065
  name: "read_file",
10775
11066
  parallelSafe: true,
11067
+ skipTruncationSave: true,
10776
11068
  description: `Read a file under the sandbox root. Default returns FULL CONTENT for files \u2264 ${Math.round(DEFAULT_OUTLINE_THRESHOLD_BYTES / 1024)} KiB. Optional scoping: head/tail (N lines), range "A-B" (1-indexed inclusive). Larger files auto-switch to outline mode (metadata + head + symbol outline for TS/JS/Python/Go/Rust/Markdown/Protobuf/text) \u2014 drill in with range or search_content. Files over ${Math.round(HARD_MAX_FILE_BYTES / (1024 * 1024))} MiB and binaries are refused \u2014 use get_file_info for stat.`,
10777
11069
  readOnly: true,
10778
11070
  stormExempt: true,
@@ -10870,6 +11162,7 @@ ${slice.join("\n")}`);
10870
11162
  registry.register({
10871
11163
  name: "list_directory",
10872
11164
  parallelSafe: true,
11165
+ skipTruncationSave: true,
10873
11166
  description: "List entries in a directory under the sandbox root. Returns one line per entry, marking directories with a trailing slash. Not recursive \u2014 use directory_tree for that.",
10874
11167
  readOnly: true,
10875
11168
  stormExempt: true,
@@ -10892,6 +11185,7 @@ ${slice.join("\n")}`);
10892
11185
  registry.register({
10893
11186
  name: "directory_tree",
10894
11187
  parallelSafe: true,
11188
+ skipTruncationSave: true,
10895
11189
  description: `Recursively list entries with indented tree structure (dirs marked '/'). Budget-aware: maxDepth defaults to 2, large subtrees (>50 children) auto-collapse to "[N hidden \u2014 list_directory to inspect]", and ${[...SKIP_DIR_NAMES].sort().join(" / ")} are skipped unless include_deps:true. For single-level use list_directory; for path lookups use search_files; for code lookups use search_content.`,
10896
11190
  readOnly: true,
10897
11191
  parameters: {
@@ -10967,6 +11261,7 @@ ${slice.join("\n")}`);
10967
11261
  registry.register({
10968
11262
  name: "search_files",
10969
11263
  parallelSafe: true,
11264
+ skipTruncationSave: true,
10970
11265
  description: "Find files whose NAME matches a substring or regex. Case-insensitive. Walks the directory recursively under the sandbox root. Returns one path per line. Skips dependency / VCS / build directories (node_modules, .git, dist, build, .next, target, .venv) by default.",
10971
11266
  readOnly: true,
10972
11267
  parameters: {
@@ -10993,6 +11288,7 @@ ${slice.join("\n")}`);
10993
11288
  registry.register({
10994
11289
  name: "search_content",
10995
11290
  parallelSafe: true,
11291
+ skipTruncationSave: true,
10996
11292
  description: "Recursively grep file CONTENTS for a substring or regex \u2014 'where is X called', 'what files contain Y'. Returns one match per line as `path:line: text`. Per-file hit cap 30; when the byte budget is mostly spent, remaining files switch to a `rel: N matches` histogram. Pass `summary_only:true` for just the histogram. Skips dependency / VCS / build dirs and binary files. For file NAMES use search_files.",
10997
11293
  readOnly: true,
10998
11294
  parameters: {
@@ -11044,6 +11340,7 @@ ${slice.join("\n")}`);
11044
11340
  registry.register({
11045
11341
  name: "glob",
11046
11342
  parallelSafe: true,
11343
+ skipTruncationSave: true,
11047
11344
  description: "List files matching a glob pattern, sorted by mtime (most-recently-modified first) by default. Use this for 'what changed lately', 'find all *.test.ts', 'all configs under src/'. Glob syntax matches the cross-tool standard: `*` (any chars in one segment), `**` (any segments), `?` (one char), `{a,b}` (alternation). Pattern matches against the path RELATIVE to the search root (e.g. 'src/**/*.ts' from project root). Skips node_modules / .git / dist / build / etc by default. Default limit 200; raise via `limit` (max 1000). Different from `search_files` (substring on basename) and `search_content` (matches inside file contents).",
11048
11345
  readOnly: true,
11049
11346
  parameters: {
@@ -11082,6 +11379,7 @@ ${slice.join("\n")}`);
11082
11379
  registry.register({
11083
11380
  name: "get_file_info",
11084
11381
  parallelSafe: true,
11382
+ skipTruncationSave: true,
11085
11383
  description: "Stat a path under the sandbox root. Returns type (file|directory|symlink), size in bytes, mtime in ISO-8601.",
11086
11384
  readOnly: true,
11087
11385
  parameters: {
@@ -12017,19 +12315,22 @@ function forkRegistryWithAllowList(parent, allow, alsoExclude) {
12017
12315
 
12018
12316
  // src/code/edit-blocks.ts
12019
12317
  import {
12318
+ chmodSync as chmodSync2,
12020
12319
  closeSync,
12021
- existsSync as existsSync3,
12320
+ existsSync as existsSync4,
12022
12321
  fstatSync,
12023
- ftruncateSync,
12024
- mkdirSync,
12322
+ fsyncSync,
12323
+ mkdirSync as mkdirSync2,
12025
12324
  openSync,
12026
12325
  readFileSync as readFileSync3,
12027
12326
  readSync,
12327
+ realpathSync,
12328
+ renameSync,
12028
12329
  unlinkSync,
12029
- writeFileSync,
12330
+ writeFileSync as writeFileSync2,
12030
12331
  writeSync
12031
12332
  } from "fs";
12032
- import { dirname as dirname3, isAbsolute as isAbsolute3, relative as relative6, resolve as resolve5 } from "path";
12333
+ import { dirname as dirname3, isAbsolute as isAbsolute3, relative as relative7, resolve as resolve6 } from "path";
12033
12334
  var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
12034
12335
  function parseEditBlocks(text) {
12035
12336
  const out = [];
@@ -12047,15 +12348,15 @@ function parseEditBlocks(text) {
12047
12348
  return out;
12048
12349
  }
12049
12350
  function resolveEditPath(rootDir, rawPath) {
12050
- const absRoot = resolve5(rootDir);
12351
+ const absRoot = resolve6(rootDir);
12051
12352
  if (/^[A-Za-z]:[\\/]/.test(rawPath) || looksLikeAbsoluteSystemPath2(rawPath)) {
12052
- return resolve5(rawPath);
12353
+ return resolve6(rawPath);
12053
12354
  }
12054
12355
  let rooted = rawPath;
12055
12356
  while (rooted.startsWith("/") || rooted.startsWith("\\")) {
12056
12357
  rooted = rooted.slice(1);
12057
12358
  }
12058
- return resolve5(absRoot, rooted || ".");
12359
+ return resolve6(absRoot, rooted || ".");
12059
12360
  }
12060
12361
  function looksLikeAbsoluteSystemPath2(rawPath) {
12061
12362
  return /^\/(?:home|Users|etc|var|opt|tmp|usr|mnt|Library|Volumes|proc|sys|dev|run|srv|media|Applications|System|root|boot|private)(?:[/\\]|$)/.test(
@@ -12063,11 +12364,59 @@ function looksLikeAbsoluteSystemPath2(rawPath) {
12063
12364
  );
12064
12365
  }
12065
12366
  function pathIsUnder2(child, parent) {
12066
- const rel = relative6(parent, child);
12367
+ const rel = relative7(parent, child);
12067
12368
  return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
12068
12369
  }
12370
+ function writeAllSync(fd, buf) {
12371
+ let written = 0;
12372
+ while (written < buf.length) {
12373
+ const n = writeSync(fd, buf, written, buf.length - written, written);
12374
+ if (n <= 0) throw new Error("write returned 0 bytes before completing");
12375
+ written += n;
12376
+ }
12377
+ }
12378
+ function fsyncDirectoryBestEffort(path) {
12379
+ let fd;
12380
+ try {
12381
+ fd = openSync(path, "r");
12382
+ fsyncSync(fd);
12383
+ } catch {
12384
+ } finally {
12385
+ if (fd !== void 0) closeSync(fd);
12386
+ }
12387
+ }
12388
+ function atomicReplaceFileSync(path, buf, mode) {
12389
+ const tmp = `${path}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
12390
+ const permissions = mode & 4095;
12391
+ let fd;
12392
+ try {
12393
+ fd = openSync(tmp, "wx", permissions);
12394
+ writeAllSync(fd, buf);
12395
+ try {
12396
+ chmodSync2(tmp, permissions);
12397
+ } catch {
12398
+ }
12399
+ fsyncSync(fd);
12400
+ closeSync(fd);
12401
+ fd = void 0;
12402
+ renameSync(tmp, path);
12403
+ fsyncDirectoryBestEffort(dirname3(path));
12404
+ } catch (err) {
12405
+ if (fd !== void 0) {
12406
+ try {
12407
+ closeSync(fd);
12408
+ } catch {
12409
+ }
12410
+ }
12411
+ try {
12412
+ unlinkSync(tmp);
12413
+ } catch {
12414
+ }
12415
+ throw err;
12416
+ }
12417
+ }
12069
12418
  function applyEditBlock(block, rootDir) {
12070
- const absRoot = resolve5(rootDir);
12419
+ const absRoot = resolve6(rootDir);
12071
12420
  const absTarget = resolveEditPath(rootDir, block.path);
12072
12421
  if (!pathIsUnder2(absTarget, absRoot)) {
12073
12422
  return {
@@ -12079,7 +12428,7 @@ function applyEditBlock(block, rootDir) {
12079
12428
  const searchEmpty = block.search.length === 0;
12080
12429
  if (searchEmpty) {
12081
12430
  try {
12082
- mkdirSync(dirname3(absTarget), { recursive: true });
12431
+ mkdirSync2(dirname3(absTarget), { recursive: true });
12083
12432
  const fd = openSync(absTarget, "wx");
12084
12433
  try {
12085
12434
  writeSync(fd, block.replace);
@@ -12100,9 +12449,22 @@ function applyEditBlock(block, rootDir) {
12100
12449
  }
12101
12450
  }
12102
12451
  try {
12452
+ let writeTarget;
12453
+ try {
12454
+ writeTarget = realpathSync(absTarget);
12455
+ } catch (err) {
12456
+ if (err.code === "ENOENT") {
12457
+ return {
12458
+ path: block.path,
12459
+ status: "file-missing",
12460
+ message: "file does not exist; to create it, use an empty SEARCH block"
12461
+ };
12462
+ }
12463
+ throw err;
12464
+ }
12103
12465
  let fd;
12104
12466
  try {
12105
- fd = openSync(absTarget, "r+");
12467
+ fd = openSync(writeTarget, "r+");
12106
12468
  } catch (err) {
12107
12469
  if (err.code === "ENOENT") {
12108
12470
  return {
@@ -12143,17 +12505,12 @@ function applyEditBlock(block, rootDir) {
12143
12505
  };
12144
12506
  }
12145
12507
  const replaced = `${content.slice(0, idx)}${adaptedReplace}${content.slice(idx + adaptedSearch.length)}`;
12146
- const outBuf = encodeFile(replaced, encoding);
12147
- ftruncateSync(fd, outBuf.length);
12148
- let written = 0;
12149
- while (written < outBuf.length) {
12150
- const n = writeSync(fd, outBuf, written, outBuf.length - written, written);
12151
- if (n <= 0) break;
12152
- written += n;
12153
- }
12508
+ closeSync(fd);
12509
+ fd = void 0;
12510
+ atomicReplaceFileSync(writeTarget, encodeFile(replaced, encoding), stat2.mode);
12154
12511
  return { path: block.path, status: "applied" };
12155
12512
  } finally {
12156
- closeSync(fd);
12513
+ if (fd !== void 0) closeSync(fd);
12157
12514
  }
12158
12515
  } catch (err) {
12159
12516
  return { path: block.path, status: "error", message: err.message };
@@ -12165,7 +12522,7 @@ function applyEditBlocks(blocks, rootDir) {
12165
12522
  function toWholeFileEditBlock(path, content, rootDir) {
12166
12523
  const abs = resolveEditPath(rootDir, path);
12167
12524
  let search = "";
12168
- if (existsSync3(abs)) {
12525
+ if (existsSync4(abs)) {
12169
12526
  try {
12170
12527
  search = decodeFileBuffer(readFileSync3(abs)).text;
12171
12528
  } catch {
@@ -12175,7 +12532,7 @@ function toWholeFileEditBlock(path, content, rootDir) {
12175
12532
  return { path, search, replace: content, offset: 0 };
12176
12533
  }
12177
12534
  function snapshotBeforeEdits(blocks, rootDir) {
12178
- const absRoot = resolve5(rootDir);
12535
+ const absRoot = resolve6(rootDir);
12179
12536
  const seen = /* @__PURE__ */ new Set();
12180
12537
  const snapshots = [];
12181
12538
  for (const b of blocks) {
@@ -12183,7 +12540,7 @@ function snapshotBeforeEdits(blocks, rootDir) {
12183
12540
  if (!pathIsUnder2(abs, absRoot)) continue;
12184
12541
  if (seen.has(abs)) continue;
12185
12542
  seen.add(abs);
12186
- if (!existsSync3(abs)) {
12543
+ if (!existsSync4(abs)) {
12187
12544
  snapshots.push({ path: b.path, prevContent: null });
12188
12545
  continue;
12189
12546
  }
@@ -12197,7 +12554,7 @@ function snapshotBeforeEdits(blocks, rootDir) {
12197
12554
  return snapshots;
12198
12555
  }
12199
12556
  function restoreSnapshots(snapshots, rootDir) {
12200
- const absRoot = resolve5(rootDir);
12557
+ const absRoot = resolve6(rootDir);
12201
12558
  return snapshots.map((snap) => {
12202
12559
  const abs = resolveEditPath(rootDir, snap.path);
12203
12560
  if (!pathIsUnder2(abs, absRoot)) {
@@ -12209,14 +12566,14 @@ function restoreSnapshots(snapshots, rootDir) {
12209
12566
  }
12210
12567
  try {
12211
12568
  if (snap.prevContent === null) {
12212
- if (existsSync3(abs)) unlinkSync(abs);
12569
+ if (existsSync4(abs)) unlinkSync(abs);
12213
12570
  return {
12214
12571
  path: snap.path,
12215
12572
  status: "applied",
12216
12573
  message: "removed (the edit had created it)"
12217
12574
  };
12218
12575
  }
12219
- writeFileSync(abs, encodeFile(snap.prevContent, snap.prevEncoding ?? "utf8"));
12576
+ writeFileSync2(abs, encodeFile(snap.prevContent, snap.prevEncoding ?? "utf8"));
12220
12577
  return {
12221
12578
  path: snap.path,
12222
12579
  status: "applied",
@@ -12235,6 +12592,7 @@ export {
12235
12592
  ToolRegistry,
12236
12593
  registerSingleMcpTool,
12237
12594
  bridgeMcpTools,
12595
+ isDeepSeekHost,
12238
12596
  ImmutablePrefix,
12239
12597
  CacheFirstLoop,
12240
12598
  expandAtUrls,
@@ -12268,4 +12626,4 @@ export {
12268
12626
  he/he.js:
12269
12627
  (*! https://mths.be/he v1.2.0 by @mathias | MIT license *)
12270
12628
  */
12271
- //# sourceMappingURL=chunk-GPUH2BNM.js.map
12629
+ //# sourceMappingURL=chunk-K4YQFULP.js.map