reasonix 0.41.0 → 0.43.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 (186) hide show
  1. package/README.md +26 -3
  2. package/dashboard/dist/app.js +98 -3
  3. package/dashboard/dist/app.js.map +1 -1
  4. package/dist/cli/{acp-64VQZLDJ.js → acp-DAGPCVFZ.js} +33 -28
  5. package/dist/cli/acp-DAGPCVFZ.js.map +1 -0
  6. package/dist/cli/chat-7ES4IBNH.js +50 -0
  7. package/dist/cli/{chunk-E46ECXJD.js → chunk-2425HK6U.js} +2 -1
  8. package/dist/cli/{chunk-E46ECXJD.js.map → chunk-2425HK6U.js.map} +1 -1
  9. package/dist/cli/chunk-25T6CVUP.js +172 -0
  10. package/dist/cli/chunk-25T6CVUP.js.map +1 -0
  11. package/dist/cli/{chunk-CD4SCQL4.js → chunk-2K65GZBT.js} +11 -2
  12. package/dist/cli/chunk-2K65GZBT.js.map +1 -0
  13. package/dist/cli/{chunk-H4OLWRSX.js → chunk-2KDUS647.js} +5 -4
  14. package/dist/cli/chunk-2KDUS647.js.map +1 -0
  15. package/dist/cli/chunk-2R4QCDOZ.js +11392 -0
  16. package/dist/cli/chunk-2R4QCDOZ.js.map +1 -0
  17. package/dist/cli/{chunk-3Q3C4W66.js → chunk-2UQP6H6T.js} +2 -1
  18. package/dist/cli/{chunk-3Q3C4W66.js.map → chunk-2UQP6H6T.js.map} +1 -1
  19. package/dist/cli/chunk-2Z35JOA4.js +96 -0
  20. package/dist/cli/chunk-2Z35JOA4.js.map +1 -0
  21. package/dist/cli/chunk-32TIKD5U.js +54 -0
  22. package/dist/cli/{chunk-L7W3HJZQ.js.map → chunk-32TIKD5U.js.map} +1 -1
  23. package/dist/cli/{chunk-A3LL4XDV.js → chunk-3BXRZFWS.js} +59 -3
  24. package/dist/cli/chunk-3BXRZFWS.js.map +1 -0
  25. package/dist/cli/{chunk-SXLJBFIV.js → chunk-3Z6IBU3D.js} +11 -7
  26. package/dist/cli/chunk-3Z6IBU3D.js.map +1 -0
  27. package/dist/cli/{chunk-A7VHMMDE.js → chunk-45U62RI3.js} +12 -5
  28. package/dist/cli/chunk-45U62RI3.js.map +1 -0
  29. package/dist/cli/{chunk-7VFNPMKG.js → chunk-4QUNBQQ2.js} +3 -2
  30. package/dist/cli/{chunk-7VFNPMKG.js.map → chunk-4QUNBQQ2.js.map} +1 -1
  31. package/dist/cli/{chunk-ARF3N2SY.js → chunk-5JJRUIPA.js} +4 -3
  32. package/dist/cli/{chunk-ARF3N2SY.js.map → chunk-5JJRUIPA.js.map} +1 -1
  33. package/dist/cli/{chunk-CFY2XLY6.js → chunk-6AK4EY3D.js} +7 -5
  34. package/dist/cli/{chunk-CFY2XLY6.js.map → chunk-6AK4EY3D.js.map} +1 -1
  35. package/dist/cli/chunk-6G3CUUFG.js +34320 -0
  36. package/dist/cli/chunk-6G3CUUFG.js.map +1 -0
  37. package/dist/cli/{chunk-YJFKFTAL.js → chunk-6PBZN4VI.js} +15 -3
  38. package/dist/cli/chunk-6PBZN4VI.js.map +1 -0
  39. package/dist/cli/{chunk-4W2CICFQ.js → chunk-6PZ3CXBP.js} +71 -60
  40. package/dist/cli/chunk-6PZ3CXBP.js.map +1 -0
  41. package/dist/cli/chunk-74EX7SUH.js +25293 -0
  42. package/dist/cli/chunk-74EX7SUH.js.map +1 -0
  43. package/dist/cli/{chunk-WE3YZULK.js → chunk-7O5ALB4C.js} +3 -2
  44. package/dist/cli/{chunk-WE3YZULK.js.map → chunk-7O5ALB4C.js.map} +1 -1
  45. package/dist/cli/{chunk-2CXPDAWX.js → chunk-DOYHN4KB.js} +3 -2
  46. package/dist/cli/{chunk-2CXPDAWX.js.map → chunk-DOYHN4KB.js.map} +1 -1
  47. package/dist/cli/{chunk-VFG4GIT3.js → chunk-F3PXYSNN.js} +3 -2
  48. package/dist/cli/{chunk-VFG4GIT3.js.map → chunk-F3PXYSNN.js.map} +1 -1
  49. package/dist/cli/{chunk-7SPOFTMT.js → chunk-FHOGSSCH.js} +4 -3
  50. package/dist/cli/{chunk-7SPOFTMT.js.map → chunk-FHOGSSCH.js.map} +1 -1
  51. package/dist/cli/{chunk-LTXADNCO.js → chunk-H6PS7IUE.js} +3 -2
  52. package/dist/cli/{chunk-LTXADNCO.js.map → chunk-H6PS7IUE.js.map} +1 -1
  53. package/dist/cli/{chunk-ZTLZO42A.js → chunk-HFEAY5DT.js} +3 -2
  54. package/dist/cli/{chunk-ZTLZO42A.js.map → chunk-HFEAY5DT.js.map} +1 -1
  55. package/dist/cli/{chunk-FWGEHRB7.js → chunk-J5XJHLWM.js} +2 -1
  56. package/dist/cli/{chunk-FWGEHRB7.js.map → chunk-J5XJHLWM.js.map} +1 -1
  57. package/dist/cli/{chunk-Y5XNV3NX.js → chunk-JMBMLOBP.js} +2 -1
  58. package/dist/cli/{chunk-Y5XNV3NX.js.map → chunk-JMBMLOBP.js.map} +1 -1
  59. package/dist/cli/{chunk-BYZGO3BX.js → chunk-O52OLQL3.js} +11 -3
  60. package/dist/cli/chunk-O52OLQL3.js.map +1 -0
  61. package/dist/cli/{chunk-4DCHFFEY.js → chunk-OSZC7C6F.js} +3 -2
  62. package/dist/cli/{chunk-4DCHFFEY.js.map → chunk-OSZC7C6F.js.map} +1 -1
  63. package/dist/cli/chunk-P7EKE5ZQ.js +60641 -0
  64. package/dist/cli/chunk-P7EKE5ZQ.js.map +1 -0
  65. package/dist/cli/{chunk-FM57FNPJ.js → chunk-PLHAZOLZ.js} +2 -1
  66. package/dist/cli/{chunk-FM57FNPJ.js.map → chunk-PLHAZOLZ.js.map} +1 -1
  67. package/dist/cli/{chunk-BOFL3T45.js → chunk-PQXPXJBJ.js} +12 -11
  68. package/dist/cli/chunk-PQXPXJBJ.js.map +1 -0
  69. package/dist/cli/{chunk-DAEAAVDF.js → chunk-PV55UMTO.js} +2 -1
  70. package/dist/cli/{chunk-DAEAAVDF.js.map → chunk-PV55UMTO.js.map} +1 -1
  71. package/dist/cli/{chunk-MHGPBJ2T.js → chunk-RE4RAVFF.js} +45 -10
  72. package/dist/cli/chunk-RE4RAVFF.js.map +1 -0
  73. package/dist/cli/chunk-S4XVGLRW.js +499 -0
  74. package/dist/cli/chunk-S4XVGLRW.js.map +1 -0
  75. package/dist/cli/{chunk-WJ3YX4PZ.js → chunk-SZ5XES2N.js} +3 -2
  76. package/dist/cli/{chunk-WJ3YX4PZ.js.map → chunk-SZ5XES2N.js.map} +1 -1
  77. package/dist/cli/{chunk-UV7XJUJH.js → chunk-TJX6BFZZ.js} +16 -9
  78. package/dist/cli/{chunk-UV7XJUJH.js.map → chunk-TJX6BFZZ.js.map} +1 -1
  79. package/dist/cli/chunk-TUK7OWJA.js +51 -0
  80. package/dist/cli/{chunk-KZYLMMU5.js → chunk-VK5HG73G.js} +13 -12
  81. package/dist/cli/{chunk-KZYLMMU5.js.map → chunk-VK5HG73G.js.map} +1 -1
  82. package/dist/cli/{chunk-4H3ZRJ2U.js → chunk-XCGGEJTI.js} +4 -3
  83. package/dist/cli/{chunk-4H3ZRJ2U.js.map → chunk-XCGGEJTI.js.map} +1 -1
  84. package/dist/cli/{chunk-SOZE7V7V.js → chunk-XJXDHAES.js} +3 -2
  85. package/dist/cli/{chunk-SOZE7V7V.js.map → chunk-XJXDHAES.js.map} +1 -1
  86. package/dist/cli/chunk-XPDVG52A.js +2648 -0
  87. package/dist/cli/chunk-XPDVG52A.js.map +1 -0
  88. package/dist/cli/{chunk-CRPQUBP6.js → chunk-XXC2BYTV.js} +2 -1
  89. package/dist/cli/{chunk-CRPQUBP6.js.map → chunk-XXC2BYTV.js.map} +1 -1
  90. package/dist/cli/{chunk-AT6GGIBV.js → chunk-YFGF5NKA.js} +17 -14
  91. package/dist/cli/{chunk-AT6GGIBV.js.map → chunk-YFGF5NKA.js.map} +1 -1
  92. package/dist/cli/{chunk-ORM6PK57.js → chunk-YQ6NTIIE.js} +2 -1
  93. package/dist/cli/{chunk-ORM6PK57.js.map → chunk-YQ6NTIIE.js.map} +1 -1
  94. package/dist/cli/{chunk-RAUPWSYA.js → chunk-YYQAUTTN.js} +3 -2
  95. package/dist/cli/{chunk-RAUPWSYA.js.map → chunk-YYQAUTTN.js.map} +1 -1
  96. package/dist/cli/chunk-ZZM6QJ4W.js +109 -0
  97. package/dist/cli/chunk-ZZM6QJ4W.js.map +1 -0
  98. package/dist/cli/{code-X3M6ENTQ.js → code-SMKEW6CD.js} +60 -56
  99. package/dist/cli/code-SMKEW6CD.js.map +1 -0
  100. package/dist/cli/{commands-QY7MSQG7.js → commands-FVVB5FZF.js} +7 -5
  101. package/dist/cli/{commands-QY7MSQG7.js.map → commands-FVVB5FZF.js.map} +1 -1
  102. package/dist/cli/{commit-BRCQ3OQO.js → commit-HE4VSPZ7.js} +7 -4
  103. package/dist/cli/{commit-BRCQ3OQO.js.map → commit-HE4VSPZ7.js.map} +1 -1
  104. package/dist/cli/{desktop-ZTMHQR2Y.js → desktop-Q7NDXCON.js} +162 -74
  105. package/dist/cli/desktop-Q7NDXCON.js.map +1 -0
  106. package/dist/cli/devtools-YECO25QO.js +3719 -0
  107. package/dist/cli/devtools-YECO25QO.js.map +1 -0
  108. package/dist/cli/diff-435UTPC5.js +165 -0
  109. package/dist/cli/{diff-YASCB7PU.js.map → diff-435UTPC5.js.map} +1 -1
  110. package/dist/cli/doctor-OT7KH75K.js +27 -0
  111. package/dist/cli/{events-2AJTXR7I.js → events-XEFAD5VX.js} +6 -4
  112. package/dist/cli/{events-2AJTXR7I.js.map → events-XEFAD5VX.js.map} +1 -1
  113. package/dist/cli/index.js +3209 -133
  114. package/dist/cli/index.js.map +1 -1
  115. package/dist/cli/{mcp-YMWBLRR7.js → mcp-WUL2WO75.js} +6 -4
  116. package/dist/cli/{mcp-YMWBLRR7.js.map → mcp-WUL2WO75.js.map} +1 -1
  117. package/dist/cli/{mcp-browse-XLDUE6SB.js → mcp-browse-RR7R4XET.js} +32 -21
  118. package/dist/cli/{mcp-browse-XLDUE6SB.js.map → mcp-browse-RR7R4XET.js.map} +1 -1
  119. package/dist/cli/{mcp-inspect-H4D2HSJP.js → mcp-inspect-REGLYBWT.js} +8 -5
  120. package/dist/cli/{mcp-inspect-H4D2HSJP.js.map → mcp-inspect-REGLYBWT.js.map} +1 -1
  121. package/dist/cli/package.json +3 -0
  122. package/dist/cli/prompt-UW6EFLVR.js +16 -0
  123. package/dist/cli/{prune-sessions-4N3BVST2.js → prune-sessions-3RWUBYRS.js} +4 -2
  124. package/dist/cli/{prune-sessions-4N3BVST2.js.map → prune-sessions-3RWUBYRS.js.map} +1 -1
  125. package/dist/cli/{replay-3GTWM75X.js → replay-YOURXV4C.js} +42 -30
  126. package/dist/cli/{replay-3GTWM75X.js.map → replay-YOURXV4C.js.map} +1 -1
  127. package/dist/cli/{run-BLZPTRDX.js → run-Q6BUXV66.js} +24 -21
  128. package/dist/cli/{run-BLZPTRDX.js.map → run-Q6BUXV66.js.map} +1 -1
  129. package/dist/cli/{server-DRFPXXSH.js → server-XGDBRWMB.js} +40 -43
  130. package/dist/cli/server-XGDBRWMB.js.map +1 -0
  131. package/dist/cli/{sessions-BOWFPTXT.js → sessions-FH7QVYSY.js} +22 -19
  132. package/dist/cli/{sessions-BOWFPTXT.js.map → sessions-FH7QVYSY.js.map} +1 -1
  133. package/dist/cli/setup-VDS6SVEP.js +618 -0
  134. package/dist/cli/setup-VDS6SVEP.js.map +1 -0
  135. package/dist/cli/stats-MQVI2XQH.js +14 -0
  136. package/dist/cli/update-6ITLPRDV.js +15 -0
  137. package/dist/cli/update-6ITLPRDV.js.map +1 -0
  138. package/dist/cli/version-DAHGZY5N.js +33 -0
  139. package/dist/cli/{version-XQXYSJ5L.js.map → version-DAHGZY5N.js.map} +1 -1
  140. package/dist/index.d.ts +12 -3
  141. package/dist/index.js +182 -97
  142. package/dist/index.js.map +1 -1
  143. package/package.json +1 -1
  144. package/dist/cli/acp-64VQZLDJ.js.map +0 -1
  145. package/dist/cli/chat-ZAGX52RV.js +0 -46
  146. package/dist/cli/chunk-4W2CICFQ.js.map +0 -1
  147. package/dist/cli/chunk-5X7LZJDE.js +0 -36
  148. package/dist/cli/chunk-5X7LZJDE.js.map +0 -1
  149. package/dist/cli/chunk-65Q5HQ26.js +0 -892
  150. package/dist/cli/chunk-65Q5HQ26.js.map +0 -1
  151. package/dist/cli/chunk-A3LL4XDV.js.map +0 -1
  152. package/dist/cli/chunk-A7VHMMDE.js.map +0 -1
  153. package/dist/cli/chunk-AFFZF3MW.js +0 -36
  154. package/dist/cli/chunk-AFFZF3MW.js.map +0 -1
  155. package/dist/cli/chunk-BOFL3T45.js.map +0 -1
  156. package/dist/cli/chunk-BYZGO3BX.js.map +0 -1
  157. package/dist/cli/chunk-CD4SCQL4.js.map +0 -1
  158. package/dist/cli/chunk-CPOV2O73.js +0 -39
  159. package/dist/cli/chunk-CPOV2O73.js.map +0 -1
  160. package/dist/cli/chunk-F2AV2QDK.js +0 -16514
  161. package/dist/cli/chunk-F2AV2QDK.js.map +0 -1
  162. package/dist/cli/chunk-H4OLWRSX.js.map +0 -1
  163. package/dist/cli/chunk-IEA6JOIP.js +0 -5430
  164. package/dist/cli/chunk-IEA6JOIP.js.map +0 -1
  165. package/dist/cli/chunk-L7W3HJZQ.js +0 -46
  166. package/dist/cli/chunk-LN27AKV3.js +0 -26
  167. package/dist/cli/chunk-LN27AKV3.js.map +0 -1
  168. package/dist/cli/chunk-MHGPBJ2T.js.map +0 -1
  169. package/dist/cli/chunk-SXLJBFIV.js.map +0 -1
  170. package/dist/cli/chunk-YJFKFTAL.js.map +0 -1
  171. package/dist/cli/code-X3M6ENTQ.js.map +0 -1
  172. package/dist/cli/desktop-ZTMHQR2Y.js.map +0 -1
  173. package/dist/cli/diff-YASCB7PU.js +0 -153
  174. package/dist/cli/doctor-XCN5ETVP.js +0 -24
  175. package/dist/cli/prompt-RSIHN62V.js +0 -14
  176. package/dist/cli/server-DRFPXXSH.js.map +0 -1
  177. package/dist/cli/setup-FQL2JJC2.js +0 -516
  178. package/dist/cli/setup-FQL2JJC2.js.map +0 -1
  179. package/dist/cli/stats-5RJCATCE.js +0 -12
  180. package/dist/cli/update-GUCWB4UN.js +0 -13
  181. package/dist/cli/version-XQXYSJ5L.js +0 -30
  182. /package/dist/cli/{chat-ZAGX52RV.js.map → chat-7ES4IBNH.js.map} +0 -0
  183. /package/dist/cli/{doctor-XCN5ETVP.js.map → chunk-TUK7OWJA.js.map} +0 -0
  184. /package/dist/cli/{prompt-RSIHN62V.js.map → doctor-OT7KH75K.js.map} +0 -0
  185. /package/dist/cli/{stats-5RJCATCE.js.map → prompt-UW6EFLVR.js.map} +0 -0
  186. /package/dist/cli/{update-GUCWB4UN.js.map → stats-MQVI2XQH.js.map} +0 -0
package/dist/index.js CHANGED
@@ -966,6 +966,7 @@ var EN = {
966
966
  applied: "applied",
967
967
  rejected: "rejected",
968
968
  noDashboard: "Suppress the auto-launched embedded web dashboard.",
969
+ openDashboardHint: "Open the dashboard URL in your default browser as soon as the server is ready. No-op when --no-dashboard is set.",
969
970
  dashboardPortHint: "Pin the dashboard to a fixed port (1\u201365535). Stable across restarts \u2014 required for SSH tunnels. Default: ephemeral.",
970
971
  dashboardPortInvalid: "\u25B2 ignoring --dashboard-port={value} (must be an integer 1\u201365535) \u2014 falling back to ephemeral",
971
972
  dashboardAutoStartFailed: "\u25B2 dashboard auto-start failed ({reason}) \u2014 try /dashboard, or pass --no-dashboard to silence",
@@ -1130,7 +1131,13 @@ var EN = {
1130
1131
  jsonHintCatalog: "output as JSON",
1131
1132
  jsonHintReport: "output the inspection report as JSON",
1132
1133
  modelOverrideFlash: "override the model (default: deepseek-v4-flash)",
1133
- skipConfirmHint: "skip the confirmation prompt"
1134
+ skipConfirmHint: "skip the confirmation prompt",
1135
+ yoloHint: "auto-approve plan checkpoints for this invocation (equivalent to editMode=yolo without mutating config)"
1136
+ },
1137
+ code: {
1138
+ workspaceConflict: "\u26A0 workspace contains another agent platform's files ({platforms}). Reasonix Code may read them as project content; relaunch with --dir <your-project> if that's not what you want.\n",
1139
+ systemAppendEmpty: "--system-append is empty \u2014 no prompt text will be appended\n",
1140
+ systemAppendFileReadError: 'Error: cannot read --system-append-file "{filePath}": {errorDetails}\n'
1134
1141
  },
1135
1142
  slash: {
1136
1143
  help: { description: "show the full command reference" },
@@ -1276,6 +1283,14 @@ var EN = {
1276
1283
  logs: {
1277
1284
  description: "tail a background job's output (default last 80 lines)",
1278
1285
  argsHint: "<id> [lines]"
1286
+ },
1287
+ btw: {
1288
+ description: "ask a quick side question \u2014 answered from a blank slate, never added to the conversation context",
1289
+ argsHint: "<question>"
1290
+ },
1291
+ "search-engine": {
1292
+ description: "switch web search backend \u2014 mojeek (default, no deps) or searxng (self-hosted)",
1293
+ argsHint: "<mojeek|searxng> [<endpoint>]"
1279
1294
  }
1280
1295
  },
1281
1296
  wizard: {
@@ -1478,7 +1493,6 @@ var EN = {
1478
1493
  flashEscalation: "\u21E7 flash requested escalation \u2014 retrying this turn on {model}{reasonSuffix}",
1479
1494
  harvestStatus: "extracting plan state from reasoning\u2026",
1480
1495
  autoEscalation: "\u21E7 auto-escalating to {model} for the rest of this turn \u2014 flash hit {breakdown}. Next turn falls back to {fallback} unless /pro is armed.",
1481
- readOnlyLoopEscalation: "\u21E7 auto-escalating to {model} \u2014 flash made {n} consecutive read-only calls without producing an edit or final answer. Next turn falls back to {fallback} unless /pro is armed.",
1482
1496
  repeatToolCallWarning: "Caught a repeated tool call \u2014 let the model see the issue and retry with a different approach.",
1483
1497
  stormStuck: "Stopped a stuck retry loop \u2014 the model kept calling the same tool with identical args after a self-correction nudge. Try /retry, rephrase, or rule out the underlying blocker.",
1484
1498
  stormSuppressed: "Suppressed {count} repeated tool call(s) \u2014 same name + args fired 3+ times.",
@@ -1859,7 +1873,8 @@ var EN = {
1859
1873
  mb: " MB",
1860
1874
  evt: " evt",
1861
1875
  editsLabel: "edits:",
1862
- mcpLoading: "MCP"
1876
+ mcpLoading: "MCP",
1877
+ ctx: "ctx"
1863
1878
  },
1864
1879
  editMode: {
1865
1880
  plan: "PLAN MODE",
@@ -2232,7 +2247,9 @@ var EN = {
2232
2247
  reconnect: "reconnect\u2026",
2233
2248
  initDetail: "initialise \u2192 tools/list \u2192 resources/list",
2234
2249
  reconnectDetail: "tearing down \xB7 re-handshake \xB7 listing tools",
2235
- disabledDetail: "via /mcp disable {name}"
2250
+ disabledDetail: "via /mcp disable {name}",
2251
+ failedSetupHint: "\u2192 run `reasonix setup` to remove this entry, or fix the underlying issue (missing npm package, network, etc.).",
2252
+ failedSetupConfigHint: "\u2192 run `reasonix setup` to remove broken entries from your saved config."
2236
2253
  },
2237
2254
  checkpointPicker: {
2238
2255
  title: "restore a checkpoint \u2014 {workspace}",
@@ -2330,6 +2347,7 @@ var zhCN = {
2330
2347
  applied: "\u5DF2\u5E94\u7528",
2331
2348
  rejected: "\u5DF2\u62D2\u7EDD",
2332
2349
  noDashboard: "\u7981\u6B62\u81EA\u52A8\u542F\u52A8\u5D4C\u5165\u5F0F Web \u4EEA\u8868\u677F\u3002",
2350
+ openDashboardHint: "\u670D\u52A1\u5C31\u7EEA\u540E\u7ACB\u5373\u5728\u9ED8\u8BA4\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u4EEA\u8868\u677F\u5730\u5740\u3002\u8BBE\u7F6E\u4E86 --no-dashboard \u65F6\u4E0D\u751F\u6548\u3002",
2333
2351
  dashboardPortHint: "\u5C06\u4EEA\u8868\u677F\u7ED1\u5B9A\u5230\u56FA\u5B9A\u7AEF\u53E3 (1\u201365535)\u3002\u91CD\u542F\u540E\u4FDD\u6301\u7A33\u5B9A \u2014 SSH \u96A7\u9053\u8BBF\u95EE\u5FC5\u9700\u3002\u9ED8\u8BA4\u4E3A\u4E34\u65F6\u7AEF\u53E3\u3002",
2334
2352
  dashboardPortInvalid: "\u25B2 \u5FFD\u7565 --dashboard-port={value} (\u5FC5\u987B\u4E3A 1\u201365535 \u4E4B\u95F4\u7684\u6574\u6570) \u2014 \u56DE\u9000\u5230\u4E34\u65F6\u7AEF\u53E3",
2335
2353
  dashboardAutoStartFailed: "\u25B2 \u4EEA\u8868\u677F\u81EA\u52A8\u542F\u52A8\u5931\u8D25 ({reason}) \u2014 \u5C1D\u8BD5 /dashboard\uFF0C\u6216\u4F20\u9012 --no-dashboard \u4EE5\u9759\u9ED8",
@@ -2491,7 +2509,13 @@ var zhCN = {
2491
2509
  jsonHintCatalog: "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA",
2492
2510
  jsonHintReport: "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA\u68C0\u67E5\u62A5\u544A",
2493
2511
  modelOverrideFlash: "\u8986\u76D6\u6A21\u578B\uFF08\u9ED8\u8BA4\uFF1Adeepseek-v4-flash\uFF09",
2494
- skipConfirmHint: "\u8DF3\u8FC7\u786E\u8BA4\u63D0\u793A"
2512
+ skipConfirmHint: "\u8DF3\u8FC7\u786E\u8BA4\u63D0\u793A",
2513
+ yoloHint: "\u81EA\u52A8\u6279\u51C6\u672C\u6B21\u8C03\u7528\u7684\u8BA1\u5212\u68C0\u67E5\u70B9\uFF08\u7B49\u540C\u4E8E editMode=yolo\uFF0C\u4F46\u4E0D\u4FEE\u6539\u914D\u7F6E\u6587\u4EF6\uFF09"
2514
+ },
2515
+ code: {
2516
+ workspaceConflict: "\u26A0 \u5DE5\u4F5C\u533A\u5305\u542B\u53E6\u4E00\u4E2A\u667A\u80FD\u4F53\u5E73\u53F0\u7684\u6587\u4EF6 ({platforms})\u3002Reasonix Code \u53EF\u80FD\u4F1A\u5C06\u5176\u4F5C\u4E3A\u9879\u76EE\u5185\u5BB9\u8BFB\u53D6\uFF1B\u5982\u679C\u4E0D\u662F\u60A8\u60F3\u8981\u7684\uFF0C\u8BF7\u4F7F\u7528 --dir <your-project> \u91CD\u65B0\u542F\u52A8\u3002\n",
2517
+ systemAppendEmpty: "--system-append \u4E3A\u7A7A \u2014 \u4E0D\u4F1A\u8FFD\u52A0\u4EFB\u4F55\u63D0\u793A\u6587\u672C\n",
2518
+ systemAppendFileReadError: '\u9519\u8BEF\uFF1A\u65E0\u6CD5\u8BFB\u53D6 --system-append-file "{filePath}"\uFF1A{errorDetails}\n'
2495
2519
  },
2496
2520
  slash: {
2497
2521
  help: { description: "\u663E\u793A\u5B8C\u6574\u547D\u4EE4\u53C2\u8003" },
@@ -2641,6 +2665,14 @@ var zhCN = {
2641
2665
  logs: {
2642
2666
  description: "\u8DDF\u8E2A\u540E\u53F0\u4F5C\u4E1A\u7684\u8F93\u51FA\uFF08\u9ED8\u8BA4\u6700\u540E 80 \u884C\uFF09",
2643
2667
  argsHint: "<id> [lines]"
2668
+ },
2669
+ btw: {
2670
+ description: "\u987A\u4FBF\u95EE\u4E00\u4E0B \u2014 \u4ECE\u7A7A\u767D\u4E0A\u4E0B\u6587\u56DE\u7B54\uFF0C\u4E0D\u5199\u5165\u4F1A\u8BDD\u5386\u53F2",
2671
+ argsHint: "<question>"
2672
+ },
2673
+ "search-engine": {
2674
+ description: "\u5207\u6362\u7F51\u7EDC\u641C\u7D22\u540E\u7AEF \u2014 mojeek\uFF08\u9ED8\u8BA4\uFF0C\u65E0\u4F9D\u8D56\uFF09\u6216 searxng\uFF08\u81EA\u6258\u7BA1\uFF09",
2675
+ argsHint: "<mojeek|searxng> [<endpoint>]"
2644
2676
  }
2645
2677
  },
2646
2678
  wizard: {
@@ -2843,7 +2875,6 @@ var zhCN = {
2843
2875
  flashEscalation: "\u21E7 flash \u8BF7\u6C42\u5347\u7EA7 \u2014 \u672C\u8F6E\u6539\u7528 {model}{reasonSuffix}",
2844
2876
  harvestStatus: "\u6B63\u5728\u4ECE\u63A8\u7406\u8FC7\u7A0B\u63D0\u53D6\u8BA1\u5212\u72B6\u6001\u2026",
2845
2877
  autoEscalation: "\u21E7 \u672C\u8F6E\u5269\u4F59\u8C03\u7528\u81EA\u52A8\u5347\u7EA7\u5230 {model} \u2014 flash \u547D\u4E2D {breakdown}\u3002\u4E0B\u4E00\u8F6E\u56DE\u9000\u5230 {fallback}\uFF0C\u9664\u975E\u5DF2\u88C5\u5907 /pro\u3002",
2846
- readOnlyLoopEscalation: "\u21E7 \u81EA\u52A8\u5347\u7EA7\u5230 {model} \u2014 flash \u8FDE\u7EED {n} \u6B21\u53EA\u8BFB\u8C03\u7528\uFF0C\u672A\u4EA7\u51FA\u4FEE\u6539\u6216\u6700\u7EC8\u7B54\u6848\u3002\u4E0B\u4E00\u8F6E\u56DE\u9000\u5230 {fallback}\uFF0C\u9664\u975E\u5DF2\u88C5\u5907 /pro\u3002",
2847
2878
  repeatToolCallWarning: "\u62E6\u622A\u5230\u91CD\u590D\u5DE5\u5177\u8C03\u7528 \u2014 \u8BA9\u6A21\u578B\u5BDF\u89C9\u95EE\u9898\u5E76\u6362\u79CD\u65B9\u5F0F\u91CD\u8BD5\u3002",
2848
2879
  stormStuck: "\u5DF2\u505C\u6B62\u5361\u6B7B\u7684\u91CD\u8BD5\u5FAA\u73AF \u2014 \u6A21\u578B\u5728\u81EA\u7EA0\u63D0\u793A\u540E\u4ECD\u4EE5\u76F8\u540C\u53C2\u6570\u91CD\u590D\u8C03\u7528\u540C\u4E00\u5DE5\u5177\u3002\u8BF7\u5C1D\u8BD5 /retry\u3001\u6362\u79CD\u8BF4\u6CD5\uFF0C\u6216\u6392\u67E5\u5E95\u5C42\u963B\u585E\u3002",
2849
2880
  stormSuppressed: "\u5DF2\u6291\u5236 {count} \u6B21\u91CD\u590D\u5DE5\u5177\u8C03\u7528 \u2014 \u540C\u4E00\u540D\u79F0 + \u53C2\u6570\u89E6\u53D1 3 \u6B21\u4EE5\u4E0A\u3002",
@@ -3224,7 +3255,8 @@ var zhCN = {
3224
3255
  mb: " MB",
3225
3256
  evt: " \u4E8B\u4EF6",
3226
3257
  editsLabel: "\u7F16\u8F91:",
3227
- mcpLoading: "MCP"
3258
+ mcpLoading: "MCP",
3259
+ ctx: "\u4E0A\u4E0B\u6587"
3228
3260
  },
3229
3261
  editMode: {
3230
3262
  plan: "\u8BA1\u5212",
@@ -3597,7 +3629,9 @@ var zhCN = {
3597
3629
  reconnect: "\u91CD\u8FDE\u4E2D\u2026",
3598
3630
  initDetail: "\u521D\u59CB\u5316 \u2192 tools/list \u2192 resources/list",
3599
3631
  reconnectDetail: "\u65AD\u5F00\u65E7\u8FDE\u63A5 \xB7 \u91CD\u65B0\u63E1\u624B \xB7 \u5217\u51FA\u5DE5\u5177",
3600
- disabledDetail: "\u901A\u8FC7 /mcp disable {name}"
3632
+ disabledDetail: "\u901A\u8FC7 /mcp disable {name}",
3633
+ failedSetupHint: "\u2192 \u8FD0\u884C `reasonix setup` \u79FB\u9664\u6B64\u6761\u76EE\uFF0C\u6216\u4FEE\u590D\u5E95\u5C42\u95EE\u9898\uFF08\u7F3A\u5C11 npm \u5305\u3001\u7F51\u7EDC\u7B49\uFF09\u3002",
3634
+ failedSetupConfigHint: "\u2192 \u8FD0\u884C `reasonix setup` \u4ECE\u5DF2\u4FDD\u5B58\u914D\u7F6E\u4E2D\u79FB\u9664\u635F\u574F\u7684\u6761\u76EE\u3002"
3601
3635
  },
3602
3636
  checkpointPicker: {
3603
3637
  title: "\u6062\u590D\u68C0\u67E5\u70B9 \u2014 {workspace}",
@@ -4581,7 +4615,7 @@ import {
4581
4615
  writeFileSync as writeFileSync2
4582
4616
  } from "fs";
4583
4617
  import { homedir as homedir3 } from "os";
4584
- import { dirname as dirname3, join as join4 } from "path";
4618
+ import { dirname as dirname3, join as join4, posix as posixPath, win32 as win32Path } from "path";
4585
4619
  function sessionsDir() {
4586
4620
  return join4(homedir3(), ".reasonix", "sessions");
4587
4621
  }
@@ -4868,6 +4902,23 @@ var HISTORY_FOLD_MIN_SAVINGS_FRACTION = 0.3;
4868
4902
  var FORCE_SUMMARY_THRESHOLD = 0.8;
4869
4903
  var PREFLIGHT_EMERGENCY_THRESHOLD = 0.95;
4870
4904
  var HISTORY_FOLD_MARKER = "[CONVERSATION HISTORY SUMMARY \u2014 earlier turns folded for context efficiency]\n\n";
4905
+ var SKILL_PIN_MEMO_HEADER = "[Active skill memos \u2014 preserved verbatim across the fold:]";
4906
+ var SKILL_PIN_REGEX = /<skill-pin name="([^"]+)">\n[\s\S]*?\n<\/skill-pin>/g;
4907
+ function extractPinnedSkills(head) {
4908
+ const pinned = /* @__PURE__ */ new Map();
4909
+ const stubbedHead = head.map((msg) => {
4910
+ if (typeof msg.content !== "string") return msg;
4911
+ let hit = false;
4912
+ const next = msg.content.replace(SKILL_PIN_REGEX, (full, name) => {
4913
+ pinned.delete(name);
4914
+ pinned.set(name, full);
4915
+ hit = true;
4916
+ return `[skill ${JSON.stringify(name)} memo \u2014 preserved separately, do not summarize.]`;
4917
+ });
4918
+ return hit ? { ...msg, content: next } : msg;
4919
+ });
4920
+ return { stubbedHead, pinnedBodies: [...pinned.values()] };
4921
+ }
4871
4922
  var ContextManager = class {
4872
4923
  constructor(deps) {
4873
4924
  this.deps = deps;
@@ -4937,11 +4988,17 @@ var ContextManager = class {
4937
4988
  const tail = all.slice(boundary);
4938
4989
  const headTokens = totalTokens - cumTokens;
4939
4990
  if (headTokens < totalTokens * HISTORY_FOLD_MIN_SAVINGS_FRACTION) return noop;
4940
- const summary = await this.summarizeForFold(head);
4991
+ const { stubbedHead, pinnedBodies } = extractPinnedSkills(head);
4992
+ const summary = await this.summarizeForFold(stubbedHead);
4941
4993
  if (!summary) return noop;
4994
+ const memoTail = pinnedBodies.length > 0 ? `
4995
+
4996
+ ${SKILL_PIN_MEMO_HEADER}
4997
+
4998
+ ${pinnedBodies.join("\n\n")}` : "";
4942
4999
  const summaryMsg = {
4943
5000
  role: "assistant",
4944
- content: HISTORY_FOLD_MARKER + summary
5001
+ content: HISTORY_FOLD_MARKER + summary + memoTail
4945
5002
  };
4946
5003
  const replacement = [summaryMsg, ...tail];
4947
5004
  this.deps.log.compactInPlace(replacement);
@@ -5166,6 +5223,8 @@ function buildSyntheticAssistantMessage(content, fallbackModel) {
5166
5223
  }
5167
5224
 
5168
5225
  // src/loop/force-summary.ts
5226
+ var PAUSE_SUMMARY_MODEL = "deepseek-v4-flash";
5227
+ var PAUSE_SUMMARY_EFFORT = "high";
5169
5228
  async function* forceSummaryAfterIterLimit(ctx, opts = { reason: "budget" }) {
5170
5229
  try {
5171
5230
  yield { turn: ctx.turn, role: "status", content: t("summary.status") };
@@ -5211,6 +5270,28 @@ ${summary}`;
5211
5270
  yield { turn: ctx.turn, role: "done", content: "" };
5212
5271
  }
5213
5272
  }
5273
+ async function summarizePartialProgress(ctx) {
5274
+ try {
5275
+ const messages = ctx.buildMessages();
5276
+ messages.push({
5277
+ role: "user",
5278
+ content: "You're being paused at a checkpoint, not stopped. In 3-6 sentences of plain prose, tell the parent agent: (1) what you accomplished so far, (2) what's still left, (3) any blockers or open questions. Be concrete \u2014 mention specific files / functions / tool results \u2014 so the parent can decide whether to resume you or take over. Do NOT emit any tool calls, function-call markup, DSML invocations, or SEARCH/REPLACE edit blocks \u2014 they will be silently discarded. Just plain text."
5279
+ });
5280
+ const resp = await ctx.client.chat({
5281
+ model: PAUSE_SUMMARY_MODEL,
5282
+ messages,
5283
+ signal: ctx.signal,
5284
+ thinking: thinkingModeForModel(PAUSE_SUMMARY_MODEL),
5285
+ reasoningEffort: PAUSE_SUMMARY_EFFORT
5286
+ });
5287
+ const cleaned = stripHallucinatedToolMarkup(resp.content?.trim() ?? "");
5288
+ if (!cleaned) return null;
5289
+ const stats = ctx.recordStats(PAUSE_SUMMARY_MODEL, resp.usage ?? new Usage());
5290
+ return { summary: cleaned, stats };
5291
+ } catch {
5292
+ return null;
5293
+ }
5294
+ }
5214
5295
 
5215
5296
  // src/loop/shrink.ts
5216
5297
  function looksLikeCompleteJson(s) {
@@ -5348,32 +5429,6 @@ function* hookWarnings(outcomes, turn) {
5348
5429
  }
5349
5430
  }
5350
5431
 
5351
- // src/loop/read-only-loop-tracker.ts
5352
- var READONLY_LOOP_ESCALATION_THRESHOLD = 8;
5353
- var ReadOnlyLoopTracker = class {
5354
- streak = 0;
5355
- threshold;
5356
- constructor(threshold = READONLY_LOOP_ESCALATION_THRESHOLD) {
5357
- this.threshold = Math.max(1, threshold);
5358
- }
5359
- reset() {
5360
- this.streak = 0;
5361
- }
5362
- /** True ONLY on the call where the streak crosses the configured threshold. */
5363
- noteAndCrossedThreshold(isReadOnly) {
5364
- if (!isReadOnly) {
5365
- this.streak = 0;
5366
- return false;
5367
- }
5368
- const before = this.streak;
5369
- this.streak += 1;
5370
- return before < this.threshold && this.streak >= this.threshold;
5371
- }
5372
- get currentStreak() {
5373
- return this.streak;
5374
- }
5375
- };
5376
-
5377
5432
  // src/loop/turn-failure-tracker.ts
5378
5433
  var FAILURE_ESCALATION_THRESHOLD = 3;
5379
5434
  var TurnFailureTracker = class {
@@ -5856,6 +5911,7 @@ var CacheFirstLoop = class {
5856
5911
  /** One-shot 80% warning latch — cleared by setBudget so a bump re-arms at the new boundary. */
5857
5912
  _budgetWarned = false;
5858
5913
  sessionName;
5914
+ onIterBudgetExhausted;
5859
5915
  hooks;
5860
5916
  hookCwd;
5861
5917
  /** PauseGate bridge — defaults to singleton, injectable for tests. */
@@ -5872,7 +5928,6 @@ var CacheFirstLoop = class {
5872
5928
  _proArmedForNextTurn = false;
5873
5929
  _escalateThisTurn = false;
5874
5930
  _turnFailures;
5875
- _readOnlyLoop;
5876
5931
  _turnSelfCorrected = false;
5877
5932
  _foldedThisTurn = false;
5878
5933
  _toolDispatchesThisStep = 0;
@@ -5895,10 +5950,8 @@ var CacheFirstLoop = class {
5895
5950
  this._turnFailures = new TurnFailureTracker(
5896
5951
  resolveFailureThreshold(opts.failureThreshold, FAILURE_ESCALATION_THRESHOLD)
5897
5952
  );
5898
- this._readOnlyLoop = new ReadOnlyLoopTracker(
5899
- parsePositiveIntEnv(process.env.REASONIX_READONLY_LOOP_THRESHOLD) ?? READONLY_LOOP_ESCALATION_THRESHOLD
5900
- );
5901
5953
  this.maxToolIters = opts.maxToolIters ?? 64;
5954
+ this.onIterBudgetExhausted = opts.onIterBudgetExhausted ?? "summarize";
5902
5955
  this.hooks = opts.hooks ?? [];
5903
5956
  this.hookCwd = opts.hookCwd ?? process.cwd();
5904
5957
  this.confirmationGate = opts.confirmationGate ?? pauseGate;
@@ -6074,14 +6127,6 @@ var CacheFirstLoop = class {
6074
6127
  this._escalateThisTurn = true;
6075
6128
  return true;
6076
6129
  }
6077
- /** Returns true ONLY on the call where the read-only streak crosses the threshold (#681). */
6078
- noteReadOnlyToolCall(call) {
6079
- const isReadOnly = !this.isMutating(call);
6080
- if (!this._readOnlyLoop.noteAndCrossedThreshold(isReadOnly)) return false;
6081
- if (this._escalateThisTurn || !this.autoEscalate) return false;
6082
- this._escalateThisTurn = true;
6083
- return true;
6084
- }
6085
6130
  /** A call counts as mutating when its definition reports `readOnly !== true` and any dynamic `readOnlyCheck` doesn't override that for these args. */
6086
6131
  isMutating(call) {
6087
6132
  const name = call.function?.name;
@@ -6223,7 +6268,6 @@ ${reason}`
6223
6268
  this.scratch.reset();
6224
6269
  this.repair.resetStorm();
6225
6270
  this._turnFailures.reset();
6226
- this._readOnlyLoop.reset();
6227
6271
  this._turnSelfCorrected = false;
6228
6272
  this._escalateThisTurn = false;
6229
6273
  this._foldedThisTurn = false;
@@ -6341,6 +6385,15 @@ ${reason}`
6341
6385
  thinking: thinkingModeForModel(callModel),
6342
6386
  reasoningEffort: this.reasoningEffort
6343
6387
  })) {
6388
+ if (chunk.reasoningDelta) {
6389
+ reasoningContent += chunk.reasoningDelta;
6390
+ yield {
6391
+ turn: this._turn,
6392
+ role: "assistant_delta",
6393
+ content: "",
6394
+ reasoningDelta: chunk.reasoningDelta
6395
+ };
6396
+ }
6344
6397
  if (chunk.contentDelta) {
6345
6398
  assistantContent += chunk.contentDelta;
6346
6399
  if (bufferForEscalation && !escalationBufFlushed) {
@@ -6365,15 +6418,6 @@ ${reason}`
6365
6418
  };
6366
6419
  }
6367
6420
  }
6368
- if (chunk.reasoningDelta) {
6369
- reasoningContent += chunk.reasoningDelta;
6370
- yield {
6371
- turn: this._turn,
6372
- role: "assistant_delta",
6373
- content: "",
6374
- reasoningDelta: chunk.reasoningDelta
6375
- };
6376
- }
6377
6421
  if (chunk.toolCallDelta) {
6378
6422
  const d = chunk.toolCallDelta;
6379
6423
  const cur = callBuf.get(d.index) ?? {
@@ -6650,17 +6694,6 @@ ${reason}`
6650
6694
  })
6651
6695
  };
6652
6696
  }
6653
- if (this.noteReadOnlyToolCall(call)) {
6654
- yield {
6655
- turn: this._turn,
6656
- role: "warning",
6657
- content: t("loop.readOnlyLoopEscalation", {
6658
- model: ESCALATION_MODEL,
6659
- n: this._readOnlyLoop.currentStreak,
6660
- fallback: this.model
6661
- })
6662
- };
6663
- }
6664
6697
  yield {
6665
6698
  turn: this._turn,
6666
6699
  role: "tool",
@@ -6672,6 +6705,19 @@ ${reason}`
6672
6705
  }
6673
6706
  }
6674
6707
  }
6708
+ if (this.onIterBudgetExhausted === "pause") {
6709
+ const partial = await summarizePartialProgress(this.summaryContext());
6710
+ yield {
6711
+ turn: this._turn,
6712
+ role: "paused",
6713
+ content: "",
6714
+ sessionName: this.sessionName ?? void 0,
6715
+ pausedAtIter: this.maxToolIters,
6716
+ partialSummary: partial?.summary
6717
+ };
6718
+ yield { turn: this._turn, role: "done", content: "" };
6719
+ return;
6720
+ }
6675
6721
  yield* forceSummaryAfterIterLimit(this.summaryContext(), { reason: "budget" });
6676
6722
  }
6677
6723
  summaryContext() {
@@ -7364,6 +7410,12 @@ function parseAllowedTools(raw) {
7364
7410
  const names = raw.split(",").map((s) => s.trim()).filter(Boolean);
7365
7411
  return names.length > 0 ? Object.freeze(names) : void 0;
7366
7412
  }
7413
+ function parseMaxToolIters(raw) {
7414
+ if (raw === void 0) return void 0;
7415
+ const n = Number.parseInt(raw.trim(), 10);
7416
+ if (!Number.isFinite(n) || n < 1) return void 0;
7417
+ return n;
7418
+ }
7367
7419
  var SkillStore = class {
7368
7420
  homeDir;
7369
7421
  projectRoot;
@@ -7494,7 +7546,8 @@ var SkillStore = class {
7494
7546
  path: path2,
7495
7547
  allowedTools: parseAllowedTools(data["allowed-tools"]),
7496
7548
  runAs: parseRunAs(data.runAs),
7497
- model: data.model?.startsWith("deepseek-") ? data.model : void 0
7549
+ model: data.model?.startsWith("deepseek-") ? data.model : void 0,
7550
+ maxToolIters: parseMaxToolIters(data["max-iters"])
7498
7551
  };
7499
7552
  }
7500
7553
  };
@@ -7515,6 +7568,7 @@ Tips:
7515
7568
  - Reference tools by name (run_command, edit_file, search_content, ...)
7516
7569
  - Add \`runAs: subagent\` to frontmatter to spawn an isolated subagent loop
7517
7570
  - Add \`allowed-tools: read_file, search_content\` to scope a subagent's tools
7571
+ - Add \`max-iters: N\` to change the subagent's pause cadence (default 16). This isn't a budget \u2014 the parent resumes on pause, so N is how often the parent gets a checkpoint, not how much total work the subagent gets.
7518
7572
  `;
7519
7573
  }
7520
7574
  function skillIndexLine(s) {
@@ -9886,9 +9940,7 @@ ${NEGATIVE_CLAIM_RULE}
9886
9940
 
9887
9941
  ${TUI_FORMATTING_RULES}`;
9888
9942
  var DEFAULT_MAX_RESULT_CHARS2 = 8e3;
9889
- var DEFAULT_MAX_ITERS = 16;
9890
- var MIN_MAX_ITERS = 1;
9891
- var MAX_MAX_ITERS = 32;
9943
+ var DEFAULT_PAUSE_EVERY = 16;
9892
9944
  var BUDGET_WARN_THRESHOLD = 3;
9893
9945
  function budgetParagraph(maxToolIters) {
9894
9946
  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.`;
@@ -9911,12 +9963,13 @@ function subagentBudgetHint(spawnCount, totalTokens) {
9911
9963
  }
9912
9964
  async function spawnSubagent(opts) {
9913
9965
  const model = opts.model ?? DEFAULT_SUBAGENT_MODEL;
9914
- const maxToolIters = opts.maxToolIters ?? DEFAULT_MAX_ITERS;
9966
+ const maxToolIters = opts.maxToolIters ?? DEFAULT_PAUSE_EVERY;
9915
9967
  const maxResultChars = opts.maxResultChars ?? DEFAULT_MAX_RESULT_CHARS2;
9916
9968
  const sink = opts.sink;
9917
9969
  const skillName = opts.skillName;
9918
- const startedAt = Date.now();
9919
9970
  const runId = nextRunId();
9971
+ const sessionName = opts.resumeSession ?? `subagent-${runId}-${timestampSuffix()}`;
9972
+ const startedAt = Date.now();
9920
9973
  const taskPreview = opts.task.length > 30 ? `${opts.task.slice(0, 30)}\u2026` : opts.task;
9921
9974
  sink?.current?.({
9922
9975
  kind: "start",
@@ -9996,10 +10049,9 @@ ${budgetParagraph(maxToolIters)}`,
9996
10049
  reasoningEffort: DEFAULT_SUBAGENT_EFFORT,
9997
10050
  maxToolIters,
9998
10051
  hooks: [],
9999
- // Streaming on so the parent UI can flip the "summarising" phase the
10000
- // moment the model starts emitting the final answer (first assistant_delta
10001
- // after the last tool result, before assistant_final lands).
10002
- stream: true
10052
+ stream: true,
10053
+ session: sessionName,
10054
+ onIterBudgetExhausted: "pause"
10003
10055
  });
10004
10056
  const onParentAbort = () => childLoop.abort();
10005
10057
  if (opts.parentSignal?.aborted) {
@@ -10011,8 +10063,13 @@ ${budgetParagraph(maxToolIters)}`,
10011
10063
  let errorMessage;
10012
10064
  let toolIter = 0;
10013
10065
  let summarisingEmitted = false;
10066
+ let paused = false;
10067
+ let partialSummary;
10068
+ const taskForLoop = opts.resumeSession ? `[Resume: your tool-call budget has been refreshed with ${maxToolIters} new calls. Earlier "wrap up" / "finalize NOW" budget hints in this conversation referred to the previous window \u2014 they no longer apply. Continue the work you were given.]
10069
+
10070
+ ${opts.task}` : opts.task;
10014
10071
  try {
10015
- for await (const ev of childLoop.step(opts.task)) {
10072
+ for await (const ev of childLoop.step(taskForLoop)) {
10016
10073
  sink?.current?.({ kind: "inner", runId, task: taskPreview, skillName, model, inner: ev });
10017
10074
  if (ev.role === "tool") {
10018
10075
  toolIter++;
@@ -10050,13 +10107,17 @@ ${budgetParagraph(maxToolIters)}`,
10050
10107
  if (ev.role === "error") {
10051
10108
  errorMessage = ev.error ?? "subagent error";
10052
10109
  }
10110
+ if (ev.role === "paused") {
10111
+ paused = true;
10112
+ if (ev.partialSummary) partialSummary = ev.partialSummary;
10113
+ }
10053
10114
  }
10054
10115
  } catch (err) {
10055
10116
  errorMessage = err.message;
10056
10117
  } finally {
10057
10118
  opts.parentSignal?.removeEventListener("abort", onParentAbort);
10058
10119
  }
10059
- if (!errorMessage && !final) {
10120
+ if (!errorMessage && !final && !paused) {
10060
10121
  errorMessage = opts.parentSignal?.aborted ? "subagent aborted before producing an answer" : "subagent ended without producing an answer";
10061
10122
  }
10062
10123
  const elapsedMs = Date.now() - startedAt;
@@ -10090,7 +10151,10 @@ ${budgetParagraph(maxToolIters)}`,
10090
10151
  costUsd: costUsd2,
10091
10152
  model,
10092
10153
  skillName,
10093
- usage
10154
+ usage,
10155
+ paused: paused || void 0,
10156
+ pausedSession: paused ? sessionName : void 0,
10157
+ partialSummary: paused ? partialSummary : void 0
10094
10158
  };
10095
10159
  }
10096
10160
  function aggregateChildUsage(loop) {
@@ -10105,6 +10169,18 @@ function aggregateChildUsage(loop) {
10105
10169
  return agg;
10106
10170
  }
10107
10171
  function formatSubagentResult(r) {
10172
+ if (r.paused) {
10173
+ return JSON.stringify({
10174
+ success: false,
10175
+ paused: true,
10176
+ resume_session: r.pausedSession,
10177
+ tool_iters: r.toolIters,
10178
+ elapsed_ms: r.elapsedMs,
10179
+ cost_usd: r.costUsd,
10180
+ partial_summary: r.partialSummary,
10181
+ note: `Subagent reached its pause-every interval (${r.toolIters} tool calls) without producing a final answer. Read partial_summary above to see what was done / left / blocked, then decide: resume by calling spawn_subagent again with resume_session="${r.pausedSession}" (the task arg becomes a continuation nudge \u2014 e.g. "finish what you started"), or accept the partial work and proceed with what you already know.`
10182
+ });
10183
+ }
10108
10184
  if (!r.success) {
10109
10185
  return JSON.stringify({
10110
10186
  success: false,
@@ -10127,7 +10203,7 @@ function registerSubagentTool(parentRegistry, opts) {
10127
10203
  const baseSystem = opts.defaultSystem ?? SUBAGENT_BASE_SYSTEM;
10128
10204
  const defaultSystemBase = opts.projectRoot ? applyProjectMemory(baseSystem, opts.projectRoot) : baseSystem;
10129
10205
  const defaultModel = opts.defaultModel ?? DEFAULT_SUBAGENT_MODEL;
10130
- const maxToolIters = opts.maxToolIters ?? DEFAULT_MAX_ITERS;
10206
+ const maxToolIters = opts.maxToolIters ?? DEFAULT_PAUSE_EVERY;
10131
10207
  const maxResultChars = opts.maxResultChars ?? DEFAULT_MAX_RESULT_CHARS2;
10132
10208
  const sink = opts.sink;
10133
10209
  let sessionSpawnCount = 0;
@@ -10135,17 +10211,17 @@ function registerSubagentTool(parentRegistry, opts) {
10135
10211
  parentRegistry.register({
10136
10212
  name: SUBAGENT_TOOL_NAME,
10137
10213
  parallelSafe: true,
10138
- description: "Spawn an isolated subagent to handle a self-contained subtask in a fresh context, returning only its final answer. **Prefer direct tools.** Spawn primarily for parallel fan-out (2+ independent investigations issued in one tool batch) or when the work would otherwise need >10 file reads/searches whose trail you don't need to keep. Single greps, 1-3 file cross-references, and 'keep my context clean for one question' are NOT good reasons to spawn \u2014 direct tools are cheaper and let you reference the evidence later. Each spawn pays a fresh prefix-cache miss plus a full child loop. The subagent inherits your tools but runs in its own isolated message log; only the final assistant message comes back. Keep tasks focused; the subagent has a stricter iter budget than you do.",
10214
+ description: "Spawn an isolated subagent to handle a self-contained subtask in a fresh context, returning only its final answer. **Prefer direct tools.** Spawn primarily for parallel fan-out (2+ independent investigations issued in one tool batch) or when the work would otherwise need >10 file reads/searches whose trail you don't need to keep. Single greps, 1-3 file cross-references, and 'keep my context clean for one question' are NOT good reasons to spawn \u2014 direct tools are cheaper and let you reference the evidence later. Each fresh spawn pays a prefix-cache miss plus a full child loop. The subagent inherits your tools but runs in its own isolated message log; only the final assistant message comes back. **Pause/resume**: subagents yield back to you every `max_iters` tool calls. A paused result returns `{ paused: true, resume_session: \"...\" }` \u2014 you can either accept the partial state, or call spawn_subagent again with `resume_session` set to continue where it left off (cheap: the prior prefix is cached). Tasks expected to run much longer than 16 tool calls don't need a huge `max_iters` \u2014 just resume.",
10139
10215
  parameters: {
10140
10216
  type: "object",
10141
10217
  properties: {
10142
10218
  task: {
10143
10219
  type: "string",
10144
- description: "The subtask the subagent should perform. Be specific and self-contained \u2014 the subagent has none of your conversation context, only what you write here."
10220
+ description: 'The subtask the subagent should perform. Be specific and self-contained \u2014 the subagent has none of your conversation context, only what you write here. When resuming via `resume_session`, this becomes a continuation nudge (e.g. "finish what you started" or a delta instruction).'
10145
10221
  },
10146
10222
  system: {
10147
10223
  type: "string",
10148
- description: "Optional override for the subagent's system prompt. The default tells it to stay focused and return a concise answer; override only when the subtask needs a specialized persona."
10224
+ description: "Optional override for the subagent's system prompt. The default tells it to stay focused and return a concise answer; override only when the subtask needs a specialized persona. Ignored on resume \u2014 the prior session keeps its original system prompt for cache stability."
10149
10225
  },
10150
10226
  model: {
10151
10227
  type: "string",
@@ -10154,9 +10230,11 @@ function registerSubagentTool(parentRegistry, opts) {
10154
10230
  },
10155
10231
  max_iters: {
10156
10232
  type: "integer",
10157
- minimum: MIN_MAX_ITERS,
10158
- maximum: MAX_MAX_ITERS,
10159
- description: `Cap on the subagent's tool-call iterations. Default 16 (or the type's default when 'type' is set). Hard range: ${MIN_MAX_ITERS}-${MAX_MAX_ITERS}; out-of-range values are clamped to the nearest end.`
10233
+ description: "How many tool calls the subagent runs before pausing back to you for a checkpoint. Default 16. This is checkpoint cadence, not a budget \u2014 work continues across pauses via `resume_session`. Pick what feels natural for the granularity of decisions you want to make: small (4-8) when you want frequent control, larger (32-64) when the subagent should mostly run autonomously."
10234
+ },
10235
+ resume_session: {
10236
+ type: "string",
10237
+ description: "Provide the `resume_session` value returned by a previous paused spawn to continue that subagent. When set, prior messages are loaded from disk and the original system prompt is reused (cache-friendly). `task` becomes a continuation nudge."
10160
10238
  },
10161
10239
  type: {
10162
10240
  type: "string",
@@ -10178,7 +10256,8 @@ function registerSubagentTool(parentRegistry, opts) {
10178
10256
  const system = typeof args.system === "string" && args.system.trim().length > 0 ? args.system.trim() : typeSpec?.system ?? `${defaultSystemBase}
10179
10257
 
10180
10258
  ${escalationContract(model)}`;
10181
- const callerIters = clampMaxIters(args.max_iters);
10259
+ const callerIters = parseMaxIters(args.max_iters);
10260
+ const resumeSession = typeof args.resume_session === "string" && args.resume_session.trim().length > 0 ? args.resume_session.trim() : void 0;
10182
10261
  const result = await spawnSubagent({
10183
10262
  client: opts.client,
10184
10263
  parentRegistry,
@@ -10188,7 +10267,8 @@ ${escalationContract(model)}`;
10188
10267
  maxToolIters: callerIters ?? typeSpec?.maxToolIters ?? maxToolIters,
10189
10268
  maxResultChars,
10190
10269
  sink,
10191
- parentSignal: ctx?.signal
10270
+ parentSignal: ctx?.signal,
10271
+ resumeSession
10192
10272
  });
10193
10273
  sessionSpawnCount++;
10194
10274
  sessionSpawnTokens += result.usage.totalTokens;
@@ -10200,12 +10280,10 @@ ${hint}` : formatted;
10200
10280
  });
10201
10281
  return parentRegistry;
10202
10282
  }
10203
- function clampMaxIters(raw) {
10283
+ function parseMaxIters(raw) {
10204
10284
  if (typeof raw !== "number" || !Number.isFinite(raw)) return void 0;
10205
10285
  const n = Math.floor(raw);
10206
- if (n < MIN_MAX_ITERS) return MIN_MAX_ITERS;
10207
- if (n > MAX_MAX_ITERS) return MAX_MAX_ITERS;
10208
- return n;
10286
+ return n >= 1 ? n : void 0;
10209
10287
  }
10210
10288
  function forkRegistryExcluding(parent, exclude) {
10211
10289
  const child = new ToolRegistry();
@@ -10607,6 +10685,7 @@ import * as pathMod8 from "path";
10607
10685
  // src/tools/shell-chain.ts
10608
10686
  import { spawn as spawn3 } from "child_process";
10609
10687
  import { closeSync, openSync } from "fs";
10688
+ import { devNull } from "os";
10610
10689
  import * as pathMod7 from "path";
10611
10690
  var UnsupportedSyntaxError = class extends Error {
10612
10691
  constructor(detail) {
@@ -10866,6 +10945,12 @@ async function runChain(chain, opts) {
10866
10945
  [\u2026 truncated ${output.length - opts.maxOutputChars} chars \u2026]` : output;
10867
10946
  return { exitCode: lastExit, output: truncated, timedOut };
10868
10947
  }
10948
+ function isNullDeviceAlias(target) {
10949
+ const lower = target.toLowerCase();
10950
+ if (lower === "/dev/null") return true;
10951
+ if (process.platform === "win32" && lower === "nul") return true;
10952
+ return false;
10953
+ }
10869
10954
  function openRedirects(redirects, cwd) {
10870
10955
  let stdinFd = null;
10871
10956
  let stdoutFd = null;
@@ -10874,7 +10959,7 @@ function openRedirects(redirects, cwd) {
10874
10959
  let bothFd = null;
10875
10960
  const toClose = [];
10876
10961
  const open = (target, flags) => {
10877
- const resolved = pathMod7.resolve(cwd, target);
10962
+ const resolved = isNullDeviceAlias(target) ? devNull : pathMod7.resolve(cwd, target);
10878
10963
  const fd = openSync(resolved, flags);
10879
10964
  toClose.push(fd);
10880
10965
  return fd;