reasonix 0.47.2 → 0.48.1

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 (129) hide show
  1. package/README.md +14 -26
  2. package/README.zh-CN.md +5 -26
  3. package/dist/cli/{acp-GEOAKSTU.js → acp-QJGGHQLA.js} +52 -135
  4. package/dist/cli/acp-QJGGHQLA.js.map +1 -0
  5. package/dist/cli/{chat-YTPATMMG.js → chat-ZXF227MP.js} +25 -25
  6. package/dist/cli/{chunk-DN4B5S6Y.js → chunk-3FULTFRB.js} +2 -2
  7. package/dist/cli/chunk-43ROGEX2.js +5190 -0
  8. package/dist/cli/chunk-43ROGEX2.js.map +1 -0
  9. package/dist/cli/{chunk-DQ6K5ZQ7.js → chunk-4E2BHJU4.js} +4 -4
  10. package/dist/cli/chunk-4E2BHJU4.js.map +1 -0
  11. package/dist/cli/{chunk-T5A7EY6B.js → chunk-5AW6NIHU.js} +2 -2
  12. package/dist/cli/{chunk-DWPAKZTY.js → chunk-5U5LMMFF.js} +2 -2
  13. package/dist/cli/{chunk-TRSAHHCL.js → chunk-6FRNXWDZ.js} +321 -76
  14. package/dist/cli/chunk-6FRNXWDZ.js.map +1 -0
  15. package/dist/cli/{chunk-KYQVQ5X4.js → chunk-B5CZL2SE.js} +9 -4
  16. package/dist/cli/chunk-B5CZL2SE.js.map +1 -0
  17. package/dist/cli/{chunk-XMHP7BEE.js → chunk-DABAOQSV.js} +2273 -2517
  18. package/dist/cli/chunk-DABAOQSV.js.map +1 -0
  19. package/dist/cli/chunk-EO6RPTJG.js +831 -0
  20. package/dist/cli/chunk-EO6RPTJG.js.map +1 -0
  21. package/dist/cli/{chunk-BQ6HC66J.js → chunk-FD7SNDWW.js} +4 -14
  22. package/dist/cli/chunk-FD7SNDWW.js.map +1 -0
  23. package/dist/cli/chunk-FPME5QOO.js +17747 -0
  24. package/dist/cli/chunk-FPME5QOO.js.map +1 -0
  25. package/dist/cli/{chunk-6QC5RQLE.js → chunk-H2F4LDNH.js} +2 -2
  26. package/dist/cli/{chunk-QCFLPSPH.js → chunk-IKSYVBBZ.js} +2 -2
  27. package/dist/cli/{chunk-5QCB62C4.js → chunk-J2TQAWOM.js} +135 -18
  28. package/dist/cli/{chunk-5QCB62C4.js.map → chunk-J2TQAWOM.js.map} +1 -1
  29. package/dist/cli/{chunk-JBH5RM7X.js → chunk-JFBGSWQB.js} +395 -85
  30. package/dist/cli/chunk-JFBGSWQB.js.map +1 -0
  31. package/dist/cli/{chunk-CCJAP7G3.js → chunk-KH5JIJJD.js} +2 -2
  32. package/dist/cli/{chunk-TDHXB2ER.js → chunk-NQZ5U37J.js} +2 -2
  33. package/dist/cli/{chunk-CNG32VAB.js → chunk-O3AGYTG2.js} +2 -2
  34. package/dist/cli/{chunk-NRQ5UP5T.js → chunk-PIC5HJRD.js} +234 -40
  35. package/dist/cli/chunk-PIC5HJRD.js.map +1 -0
  36. package/dist/cli/{chunk-GH7DC2Y5.js → chunk-PJIQIYTV.js} +6 -3
  37. package/dist/cli/chunk-PJIQIYTV.js.map +1 -0
  38. package/dist/cli/{chunk-KVZZ5U75.js → chunk-R7U44O3Y.js} +2 -2
  39. package/dist/cli/{chunk-ZXSCAODE.js → chunk-RCLS63KE.js} +71 -3
  40. package/dist/cli/chunk-RCLS63KE.js.map +1 -0
  41. package/dist/cli/{chunk-FY4S7TJZ.js → chunk-SLAFMXAZ.js} +10 -2
  42. package/dist/cli/chunk-SLAFMXAZ.js.map +1 -0
  43. package/dist/cli/{chunk-TRWHTFG7.js → chunk-SWUMD2LX.js} +2 -2
  44. package/dist/cli/{chunk-2XY77LW7.js → chunk-TIJ4ZHD6.js} +56 -24
  45. package/dist/cli/chunk-TIJ4ZHD6.js.map +1 -0
  46. package/dist/cli/{chunk-4MFCAZ2W.js → chunk-WKOXKCF3.js} +3 -3
  47. package/dist/cli/{chunk-HUILPCYX.js → chunk-XMR2VCGT.js} +3 -3
  48. package/dist/cli/{code-Q4NRVEDG.js → code-OKA5YPOH.js} +31 -32
  49. package/dist/cli/code-OKA5YPOH.js.map +1 -0
  50. package/dist/cli/{commands-4CDI4GFM.js → commands-3U6PUVSS.js} +4 -4
  51. package/dist/cli/{commit-GW7LDQP5.js → commit-HFB7SRBU.js} +3 -3
  52. package/dist/cli/{desktop-EG6P5SF2.js → desktop-G7UMW3CJ.js} +503 -48
  53. package/dist/cli/desktop-G7UMW3CJ.js.map +1 -0
  54. package/dist/cli/{diff-VI2YX4FN.js → diff-PGXW4YZD.js} +8 -8
  55. package/dist/cli/{doctor-CQTTZP27.js → doctor-WWJFLVCB.js} +9 -9
  56. package/dist/cli/index.js +53 -41
  57. package/dist/cli/index.js.map +1 -1
  58. package/dist/cli/{mcp-J2UCD4RZ.js → mcp-Y3VKIK3T.js} +2 -2
  59. package/dist/cli/{mcp-browse-GSX34JEK.js → mcp-browse-4IN2QIFR.js} +2 -2
  60. package/dist/cli/{mcp-inspect-RRFYF4ZV.js → mcp-inspect-BUXFXDHB.js} +2 -2
  61. package/dist/cli/{prompt-5TQPIVHV.js → prompt-US57R6BA.js} +5 -4
  62. package/dist/cli/{replay-MJCEMODU.js → replay-EQJMZRB3.js} +8 -8
  63. package/dist/cli/{run-P4D5VDYE.js → run-KVEI66TR.js} +14 -14
  64. package/dist/cli/{server-C25JNNZV.js → server-D6C4GHWN.js} +18 -15
  65. package/dist/cli/server-D6C4GHWN.js.map +1 -0
  66. package/dist/cli/{sessions-QIONZJQ6.js → sessions-TGVS2RQZ.js} +13 -13
  67. package/dist/cli/{setup-NLQ6G5G4.js → setup-WLKX6GGG.js} +5 -5
  68. package/dist/cli/{stats-DFZEXHP4.js → stats-TCD7Q6MB.js} +6 -6
  69. package/dist/cli/{version-GR3X3MPI.js → version-BCWWS2OU.js} +13 -13
  70. package/dist/grammars/tree-sitter-go.wasm +0 -0
  71. package/dist/grammars/tree-sitter-java.wasm +0 -0
  72. package/dist/grammars/tree-sitter-javascript.wasm +0 -0
  73. package/dist/grammars/tree-sitter-python.wasm +0 -0
  74. package/dist/grammars/tree-sitter-rust.wasm +0 -0
  75. package/dist/grammars/tree-sitter-tsx.wasm +0 -0
  76. package/dist/grammars/tree-sitter-typescript.wasm +0 -0
  77. package/dist/grammars/web-tree-sitter.wasm +0 -0
  78. package/dist/index.d.ts +46 -12
  79. package/dist/index.js +684 -129
  80. package/dist/index.js.map +1 -1
  81. package/package.json +16 -4
  82. package/dist/cli/acp-GEOAKSTU.js.map +0 -1
  83. package/dist/cli/chunk-2XY77LW7.js.map +0 -1
  84. package/dist/cli/chunk-6CRPCJAU.js +0 -3141
  85. package/dist/cli/chunk-6CRPCJAU.js.map +0 -1
  86. package/dist/cli/chunk-BQ6HC66J.js.map +0 -1
  87. package/dist/cli/chunk-DQ6K5ZQ7.js.map +0 -1
  88. package/dist/cli/chunk-FY4S7TJZ.js.map +0 -1
  89. package/dist/cli/chunk-GH7DC2Y5.js.map +0 -1
  90. package/dist/cli/chunk-JBH5RM7X.js.map +0 -1
  91. package/dist/cli/chunk-KYQVQ5X4.js.map +0 -1
  92. package/dist/cli/chunk-NRQ5UP5T.js.map +0 -1
  93. package/dist/cli/chunk-TRSAHHCL.js.map +0 -1
  94. package/dist/cli/chunk-XD6P7AFH.js +0 -375
  95. package/dist/cli/chunk-XD6P7AFH.js.map +0 -1
  96. package/dist/cli/chunk-XMHP7BEE.js.map +0 -1
  97. package/dist/cli/chunk-YFP3MYMY.js +0 -323
  98. package/dist/cli/chunk-YFP3MYMY.js.map +0 -1
  99. package/dist/cli/chunk-ZXSCAODE.js.map +0 -1
  100. package/dist/cli/code-Q4NRVEDG.js.map +0 -1
  101. package/dist/cli/desktop-EG6P5SF2.js.map +0 -1
  102. package/dist/cli/server-C25JNNZV.js.map +0 -1
  103. /package/dist/cli/{chat-YTPATMMG.js.map → chat-ZXF227MP.js.map} +0 -0
  104. /package/dist/cli/{chunk-DN4B5S6Y.js.map → chunk-3FULTFRB.js.map} +0 -0
  105. /package/dist/cli/{chunk-T5A7EY6B.js.map → chunk-5AW6NIHU.js.map} +0 -0
  106. /package/dist/cli/{chunk-DWPAKZTY.js.map → chunk-5U5LMMFF.js.map} +0 -0
  107. /package/dist/cli/{chunk-6QC5RQLE.js.map → chunk-H2F4LDNH.js.map} +0 -0
  108. /package/dist/cli/{chunk-QCFLPSPH.js.map → chunk-IKSYVBBZ.js.map} +0 -0
  109. /package/dist/cli/{chunk-CCJAP7G3.js.map → chunk-KH5JIJJD.js.map} +0 -0
  110. /package/dist/cli/{chunk-TDHXB2ER.js.map → chunk-NQZ5U37J.js.map} +0 -0
  111. /package/dist/cli/{chunk-CNG32VAB.js.map → chunk-O3AGYTG2.js.map} +0 -0
  112. /package/dist/cli/{chunk-KVZZ5U75.js.map → chunk-R7U44O3Y.js.map} +0 -0
  113. /package/dist/cli/{chunk-TRWHTFG7.js.map → chunk-SWUMD2LX.js.map} +0 -0
  114. /package/dist/cli/{chunk-4MFCAZ2W.js.map → chunk-WKOXKCF3.js.map} +0 -0
  115. /package/dist/cli/{chunk-HUILPCYX.js.map → chunk-XMR2VCGT.js.map} +0 -0
  116. /package/dist/cli/{commands-4CDI4GFM.js.map → commands-3U6PUVSS.js.map} +0 -0
  117. /package/dist/cli/{commit-GW7LDQP5.js.map → commit-HFB7SRBU.js.map} +0 -0
  118. /package/dist/cli/{diff-VI2YX4FN.js.map → diff-PGXW4YZD.js.map} +0 -0
  119. /package/dist/cli/{doctor-CQTTZP27.js.map → doctor-WWJFLVCB.js.map} +0 -0
  120. /package/dist/cli/{mcp-J2UCD4RZ.js.map → mcp-Y3VKIK3T.js.map} +0 -0
  121. /package/dist/cli/{mcp-browse-GSX34JEK.js.map → mcp-browse-4IN2QIFR.js.map} +0 -0
  122. /package/dist/cli/{mcp-inspect-RRFYF4ZV.js.map → mcp-inspect-BUXFXDHB.js.map} +0 -0
  123. /package/dist/cli/{prompt-5TQPIVHV.js.map → prompt-US57R6BA.js.map} +0 -0
  124. /package/dist/cli/{replay-MJCEMODU.js.map → replay-EQJMZRB3.js.map} +0 -0
  125. /package/dist/cli/{run-P4D5VDYE.js.map → run-KVEI66TR.js.map} +0 -0
  126. /package/dist/cli/{sessions-QIONZJQ6.js.map → sessions-TGVS2RQZ.js.map} +0 -0
  127. /package/dist/cli/{setup-NLQ6G5G4.js.map → setup-WLKX6GGG.js.map} +0 -0
  128. /package/dist/cli/{stats-DFZEXHP4.js.map → stats-TCD7Q6MB.js.map} +0 -0
  129. /package/dist/cli/{version-GR3X3MPI.js.map → version-BCWWS2OU.js.map} +0 -0
@@ -3,7 +3,7 @@ import { createRequire as __cr } from 'node:module'; if (typeof globalThis.requi
3
3
  import {
4
4
  MemoryStore,
5
5
  sanitizeMemoryName
6
- } from "./chunk-BQ6HC66J.js";
6
+ } from "./chunk-FD7SNDWW.js";
7
7
  import {
8
8
  countTokens,
9
9
  countTokensBounded,
@@ -12,23 +12,23 @@ import {
12
12
  } from "./chunk-6OWJV3YW.js";
13
13
  import {
14
14
  Usage
15
- } from "./chunk-DWPAKZTY.js";
15
+ } from "./chunk-5U5LMMFF.js";
16
16
  import {
17
17
  applyEdit,
18
18
  applyMultiEdit,
19
19
  pauseGate
20
- } from "./chunk-TRSAHHCL.js";
20
+ } from "./chunk-6FRNXWDZ.js";
21
21
  import {
22
22
  NEGATIVE_CLAIM_RULE,
23
23
  PROJECT_MEMORY_FILES,
24
24
  PROJECT_MEMORY_MAX_CHARS,
25
25
  TUI_FORMATTING_RULES,
26
26
  memoryEnabled
27
- } from "./chunk-FY4S7TJZ.js";
27
+ } from "./chunk-SLAFMXAZ.js";
28
28
  import {
29
29
  formatHookOutcomeMessage,
30
30
  runHooks
31
- } from "./chunk-GH7DC2Y5.js";
31
+ } from "./chunk-PJIQIYTV.js";
32
32
  import {
33
33
  ignoredByLayers,
34
34
  loadGitignoreAt,
@@ -46,21 +46,23 @@ import {
46
46
  DEEPSEEK_CONTEXT_TOKENS,
47
47
  DEFAULT_CONTEXT_TOKENS,
48
48
  SessionStats
49
- } from "./chunk-QCFLPSPH.js";
49
+ } from "./chunk-IKSYVBBZ.js";
50
50
  import {
51
51
  t
52
- } from "./chunk-NRQ5UP5T.js";
52
+ } from "./chunk-PIC5HJRD.js";
53
53
  import {
54
54
  DEFAULT_INDEX_EXCLUDES,
55
55
  addProjectPathAllowed,
56
+ loadExaApiKey,
56
57
  loadMemoryTypeRegistry,
57
58
  loadMetasoApiKey,
59
+ loadPerplexityApiKey,
58
60
  loadProjectPathAllowed,
59
61
  loadTavilyApiKey,
60
62
  require_picomatch,
61
63
  webSearchEndpoint,
62
64
  webSearchEngine
63
- } from "./chunk-6CRPCJAU.js";
65
+ } from "./chunk-FPME5QOO.js";
64
66
  import {
65
67
  __commonJS,
66
68
  __esm,
@@ -2597,10 +2599,10 @@ var require_helpers = __commonJS({
2597
2599
  return !arr.includes(node, i + 1);
2598
2600
  });
2599
2601
  nodes.sort(function(a, b) {
2600
- var relative6 = compareDocumentPosition(a, b);
2601
- if (relative6 & DocumentPosition.PRECEDING) {
2602
+ var relative7 = compareDocumentPosition(a, b);
2603
+ if (relative7 & DocumentPosition.PRECEDING) {
2602
2604
  return -1;
2603
- } else if (relative6 & DocumentPosition.FOLLOWING) {
2605
+ } else if (relative7 & DocumentPosition.FOLLOWING) {
2604
2606
  return 1;
2605
2607
  }
2606
2608
  return 0;
@@ -6314,8 +6316,8 @@ var ToolRegistry = class {
6314
6316
  _resultAugmenter = null;
6315
6317
  /** Per-tool fingerprint of the last call that failed schema validation. Cleared by any successful validation for that tool. */
6316
6318
  _lastMalformed = /* @__PURE__ */ new Map();
6317
- /** Per-tool fingerprint of the last host-side interceptor rejection. */
6318
- _lastInterceptorRejection = /* @__PURE__ */ new Map();
6319
+ /** Per-tool fingerprint of the last host-side gate rejection. */
6320
+ _lastGateRejection = /* @__PURE__ */ new Map();
6319
6321
  constructor(opts = {}) {
6320
6322
  this._autoFlatten = opts.autoFlatten !== false;
6321
6323
  }
@@ -6402,20 +6404,21 @@ var ToolRegistry = class {
6402
6404
  if (!tool) {
6403
6405
  return JSON.stringify({ error: `unknown tool: ${name}` });
6404
6406
  }
6405
- const fingerprint = fingerprintArgs(argumentsRaw);
6407
+ const rawFingerprint = rawFingerprintArgs(argumentsRaw);
6406
6408
  let args;
6407
6409
  try {
6408
6410
  args = typeof argumentsRaw === "string" ? argumentsRaw.trim() ? JSON.parse(argumentsRaw) ?? {} : {} : argumentsRaw ?? {};
6409
6411
  } catch (err) {
6410
6412
  return this._noteMalformed(
6411
6413
  name,
6412
- fingerprint,
6414
+ rawFingerprint,
6413
6415
  `invalid tool arguments JSON: ${err.message}`
6414
6416
  );
6415
6417
  }
6416
6418
  if (tool.flatSchema && args && typeof args === "object" && hasDotKey(args)) {
6417
6419
  args = nestArguments(args);
6418
6420
  }
6421
+ const fingerprint = fingerprintArgs(args);
6419
6422
  const missing = tool.parameters ? missingRequiredParam(tool.parameters, args) : null;
6420
6423
  if (missing) {
6421
6424
  return this._noteMalformed(
@@ -6436,7 +6439,7 @@ var ToolRegistry = class {
6436
6439
  try {
6437
6440
  const short = await interceptor(name, args);
6438
6441
  if (typeof short === "string") {
6439
- const guarded = this._noteInterceptorRejection(name, fingerprint, short);
6442
+ const guarded = this._noteGateRejection(name, fingerprint, short);
6440
6443
  return this._augmentResult(name, args, guarded);
6441
6444
  }
6442
6445
  } catch (err) {
@@ -6445,7 +6448,6 @@ var ToolRegistry = class {
6445
6448
  });
6446
6449
  }
6447
6450
  }
6448
- this._lastInterceptorRejection.delete(name);
6449
6451
  if (opts.signal?.aborted) {
6450
6452
  return JSON.stringify({
6451
6453
  error: `${name}: aborted before dispatch (user interrupt)`,
@@ -6483,6 +6485,7 @@ var ToolRegistry = class {
6483
6485
  finalResult = JSON.stringify({ error: `${e.name}: ${e.message}` });
6484
6486
  }
6485
6487
  }
6488
+ finalResult = this._noteGateRejection(name, fingerprint, finalResult);
6486
6489
  return this._augmentResult(name, args, finalResult);
6487
6490
  }
6488
6491
  _augmentResult(name, args, result) {
@@ -6506,18 +6509,18 @@ var ToolRegistry = class {
6506
6509
  }
6507
6510
  return JSON.stringify({ error: `${name}: ${detail}` });
6508
6511
  }
6509
- _noteInterceptorRejection(name, fingerprint, result) {
6510
- const reason = rejectedReason(result);
6512
+ _noteGateRejection(name, fingerprint, result) {
6513
+ const reason = rejectedReason(name, result);
6511
6514
  if (!reason) {
6512
- this._lastInterceptorRejection.delete(name);
6515
+ this._lastGateRejection.delete(name);
6513
6516
  return result;
6514
6517
  }
6515
6518
  const key = `${reason}:${fingerprint}`;
6516
- const prev = this._lastInterceptorRejection.get(name);
6517
- this._lastInterceptorRejection.set(name, key);
6519
+ const prev = this._lastGateRejection.get(name);
6520
+ this._lastGateRejection.set(name, key);
6518
6521
  if (prev === key) {
6519
6522
  return JSON.stringify({
6520
- error: `${name}: same call was just rejected by ${reason} \u2014 do not retry identical args. Switch to read-only exploration, submit or revise the plan, or choose a different tool call.`,
6523
+ error: `${name}: same call was just rejected by ${reason} \u2014 do not retry identical args. ${rejectionRecoveryHint(reason)}`,
6521
6524
  rejectedReason: reason,
6522
6525
  consecutiveInterceptorRejection: true
6523
6526
  });
@@ -6525,16 +6528,44 @@ var ToolRegistry = class {
6525
6528
  return result;
6526
6529
  }
6527
6530
  };
6528
- function rejectedReason(result) {
6531
+ function rejectedReason(name, result) {
6532
+ const textReason = plainTextRejectedReason(name, result);
6533
+ if (textReason) return textReason;
6529
6534
  try {
6530
6535
  const parsed = JSON.parse(result);
6531
6536
  if (!parsed || typeof parsed !== "object") return null;
6532
6537
  const reason = parsed.rejectedReason;
6533
- return typeof reason === "string" && reason ? reason : null;
6538
+ if (typeof reason === "string" && reason) return reason;
6539
+ const error = parsed.error;
6540
+ if (typeof error === "string") return plainTextRejectedReason(name, error);
6541
+ return null;
6534
6542
  } catch {
6535
6543
  return null;
6536
6544
  }
6537
6545
  }
6546
+ function plainTextRejectedReason(name, result) {
6547
+ if ((name === "edit_file" || name === "write_file") && /rejected this edit/i.test(result)) {
6548
+ return "edit-gate";
6549
+ }
6550
+ if ((name === "run_command" || name === "run_background") && /\buser denied:/i.test(result)) {
6551
+ return "shell-gate";
6552
+ }
6553
+ return null;
6554
+ }
6555
+ function rejectionRecoveryHint(reason) {
6556
+ switch (reason) {
6557
+ case "edit-gate":
6558
+ return "Do not re-emit the same edit. Try a genuinely different edit or ask the user how to proceed.";
6559
+ case "shell-gate":
6560
+ return "Do not retry the same command. Use an allowlisted/read-only command, wait for approval, or ask the user how to proceed.";
6561
+ case "engineering-lifecycle":
6562
+ return "Switch to read-only exploration, submit or revise the plan, or choose a different tool call.";
6563
+ case "engineering-lifecycle-evidence":
6564
+ return "Submit completion evidence or revise/checkpoint the plan before marking the step complete.";
6565
+ default:
6566
+ return "Choose a different tool call or ask the user how to proceed.";
6567
+ }
6568
+ }
6538
6569
  function isReadOnlyCall(tool, args) {
6539
6570
  if (tool.readOnlyCheck) {
6540
6571
  try {
@@ -6553,14 +6584,27 @@ function hasDotKey(obj) {
6553
6584
  }
6554
6585
  return false;
6555
6586
  }
6556
- function fingerprintArgs(argumentsRaw) {
6587
+ function rawFingerprintArgs(argumentsRaw) {
6557
6588
  if (typeof argumentsRaw === "string") return argumentsRaw;
6589
+ return fingerprintArgs(argumentsRaw);
6590
+ }
6591
+ function fingerprintArgs(args) {
6558
6592
  try {
6559
- return JSON.stringify(argumentsRaw);
6593
+ return JSON.stringify(sortJson(args));
6560
6594
  } catch {
6561
6595
  return "";
6562
6596
  }
6563
6597
  }
6598
+ function sortJson(value) {
6599
+ if (Array.isArray(value)) return value.map(sortJson);
6600
+ if (!value || typeof value !== "object") return value;
6601
+ const out = {};
6602
+ for (const key of Object.keys(value).sort()) {
6603
+ const item = value[key];
6604
+ if (item !== void 0) out[key] = sortJson(item);
6605
+ }
6606
+ return out;
6607
+ }
6564
6608
  function missingRequiredParam(schema, args) {
6565
6609
  const required = schema.required;
6566
6610
  if (!required || required.length === 0) return null;
@@ -6713,14 +6757,16 @@ function buildSyntheticAssistantMessage(content, fallbackModel) {
6713
6757
  }
6714
6758
 
6715
6759
  // src/context-manager.ts
6716
- var HISTORY_FOLD_THRESHOLD = 0.5;
6760
+ var HISTORY_FOLD_THRESHOLD = 0.75;
6717
6761
  var HISTORY_FOLD_TAIL_FRACTION = 0.2;
6718
- var HISTORY_FOLD_AGGRESSIVE_THRESHOLD = 0.7;
6762
+ var HISTORY_FOLD_AGGRESSIVE_THRESHOLD = 0.78;
6719
6763
  var HISTORY_FOLD_AGGRESSIVE_TAIL_FRACTION = 0.1;
6720
6764
  var HISTORY_FOLD_MIN_SAVINGS_FRACTION = 0.3;
6721
6765
  var FORCE_SUMMARY_THRESHOLD = 0.8;
6722
6766
  var PREFLIGHT_EMERGENCY_THRESHOLD = 0.95;
6723
6767
  var PREFLIGHT_MECHANICAL_TARGET_FRACTION = 0.7;
6768
+ var MAX_BODY_BYTES = 7e5;
6769
+ var MAX_BODY_BYTES_TARGET = 5e5;
6724
6770
  var HISTORY_FOLD_SUMMARY_TIMEOUT_MS = 15e3;
6725
6771
  var HISTORY_FOLD_MARKER = "[CONVERSATION HISTORY SUMMARY \u2014 earlier turns folded for context efficiency]\n\n";
6726
6772
  var SKILL_PIN_MEMO_HEADER = "[Active skill memos \u2014 preserved verbatim across the fold:]";
@@ -6745,6 +6791,20 @@ var ContextManager = class {
6745
6791
  this.deps = deps;
6746
6792
  }
6747
6793
  deps;
6794
+ /** Real-time token count of the current log — used by Desktop to refresh the
6795
+ * context meter after /compact when no API usage event is available. */
6796
+ getLogTokens() {
6797
+ const entries = this.deps.log.toMessages();
6798
+ let total = 0;
6799
+ for (const e of entries) {
6800
+ const content = typeof e.content === "string" ? e.content : "";
6801
+ total += countTokensBounded(content);
6802
+ if (e.role === "assistant" && Array.isArray(e.tool_calls) && e.tool_calls.length > 0) {
6803
+ total += countTokensBounded(JSON.stringify(e.tool_calls));
6804
+ }
6805
+ }
6806
+ return total;
6807
+ }
6748
6808
  /** Decision after a turn's response — fold, exit with summary, or carry on. */
6749
6809
  decideAfterUsage(usage, model, alreadyFoldedThisTurn) {
6750
6810
  const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model] ?? DEFAULT_CONTEXT_TOKENS;
@@ -6773,14 +6833,25 @@ var ContextManager = class {
6773
6833
  }
6774
6834
  return { kind: "none", ...base };
6775
6835
  }
6776
- /** Local-side preflight before sending a request — catches oversized payloads early. */
6836
+ /** Local-side preflight before sending a request — catches oversized payloads early.
6837
+ * Two independent signals trip mechanical truncate: token estimate above the context-window
6838
+ * fraction, OR JSON body bytes above the gateway limit (see `MAX_BODY_BYTES`). */
6777
6839
  decidePreflight(messages, toolSpecs, model) {
6778
6840
  const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model] ?? DEFAULT_CONTEXT_TOKENS;
6779
6841
  const estimate = estimateRequestTokens(messages, toolSpecs ?? null, true);
6842
+ const estimateBytes = Buffer.byteLength(JSON.stringify(messages), "utf8");
6843
+ const tokensOver = estimate / ctxMax > PREFLIGHT_EMERGENCY_THRESHOLD;
6844
+ const bytesOver = estimateBytes > MAX_BODY_BYTES;
6845
+ let trigger = "none";
6846
+ if (tokensOver && bytesOver) trigger = "both";
6847
+ else if (tokensOver) trigger = "tokens";
6848
+ else if (bytesOver) trigger = "bytes";
6780
6849
  return {
6781
- needsAction: estimate / ctxMax > PREFLIGHT_EMERGENCY_THRESHOLD,
6850
+ needsAction: tokensOver || bytesOver,
6782
6851
  estimateTokens: estimate,
6783
- ctxMax
6852
+ estimateBytes,
6853
+ ctxMax,
6854
+ trigger
6784
6855
  };
6785
6856
  }
6786
6857
  /** Replace older turns with one summary message; keep tail within keepRecentTokens budget. */
@@ -6833,10 +6904,13 @@ ${pinnedBodies.join("\n\n")}` : "";
6833
6904
  summaryChars: summary.content.length
6834
6905
  };
6835
6906
  }
6836
- /** Pure local emergency compaction for preflight: drop oldest log entries and keep a valid tail. */
6907
+ /** Pure local emergency compaction for preflight: drop oldest log entries and keep a valid tail.
6908
+ * Bounded by tokens AND bytes — bytes matter because DeepSeek's gateway 400s on bodies past
6909
+ * `MAX_BODY_BYTES` even when the token budget is far from exhausted. */
6837
6910
  mechanicalTruncate(model, opts) {
6838
6911
  const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model] ?? DEFAULT_CONTEXT_TOKENS;
6839
6912
  const targetTokens = opts?.targetTokens ?? Math.floor(ctxMax * PREFLIGHT_MECHANICAL_TARGET_FRACTION);
6913
+ const targetBytes = opts?.targetBytes ?? MAX_BODY_BYTES_TARGET;
6840
6914
  const all = this.deps.log.toMessages();
6841
6915
  const noop = {
6842
6916
  folded: false,
@@ -6846,6 +6920,7 @@ ${pinnedBodies.join("\n\n")}` : "";
6846
6920
  };
6847
6921
  if (all.length === 0) return noop;
6848
6922
  const tokenCounts = all.map((m) => estimateConversationTokens([m], true));
6923
+ const byteCounts = all.map((m) => Buffer.byteLength(JSON.stringify(m), "utf8"));
6849
6924
  let latestUserBoundary = -1;
6850
6925
  for (let i = all.length - 1; i >= 0; i--) {
6851
6926
  if (all[i].role === "user") {
@@ -6854,12 +6929,15 @@ ${pinnedBodies.join("\n\n")}` : "";
6854
6929
  }
6855
6930
  }
6856
6931
  let cumTokens = 0;
6932
+ let cumBytes = 0;
6857
6933
  let boundary = all.length;
6858
6934
  let foundSafeBoundary = false;
6859
6935
  for (let i = all.length - 1; i >= 0; i--) {
6860
- const next = cumTokens + tokenCounts[i];
6861
- if (next > targetTokens) break;
6862
- cumTokens = next;
6936
+ const nextTokens = cumTokens + tokenCounts[i];
6937
+ const nextBytes = cumBytes + byteCounts[i];
6938
+ if (nextTokens > targetTokens || nextBytes > targetBytes) break;
6939
+ cumTokens = nextTokens;
6940
+ cumBytes = nextBytes;
6863
6941
  if (all[i].role === "user") {
6864
6942
  boundary = i;
6865
6943
  foundSafeBoundary = true;
@@ -7096,13 +7174,11 @@ async function* forceSummaryAfterIterLimit(ctx, opts) {
7096
7174
  content: "The turn is being force-summarized (context guard or stuck-state). Summarize in plain prose what you learned from the tool results above. 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."
7097
7175
  });
7098
7176
  const summaryModel = "deepseek-v4-flash";
7099
- const summaryEffort = "high";
7100
7177
  const resp = await ctx.client.chat({
7101
7178
  model: summaryModel,
7102
7179
  messages,
7103
7180
  signal: ctx.signal,
7104
- thinking: thinkingModeForModel(summaryModel),
7105
- reasoningEffort: summaryEffort
7181
+ thinking: "disabled"
7106
7182
  });
7107
7183
  const rawContent = resp.content?.trim() ?? "";
7108
7184
  const cleaned = stripHallucinatedToolMarkup(rawContent);
@@ -7733,6 +7809,10 @@ var CacheFirstLoop = class {
7733
7809
  async compactHistory(opts) {
7734
7810
  return this.context.fold(this.model, opts);
7735
7811
  }
7812
+ /** Real-time token count of the current log — forwarded to Desktop for meter refresh. */
7813
+ getCurrentLogTokens() {
7814
+ return this.context.getLogTokens();
7815
+ }
7736
7816
  appendAndPersist(message) {
7737
7817
  this.log.append(message);
7738
7818
  if (this.sessionName) {
@@ -8048,21 +8128,24 @@ ${reason}`
8048
8128
  const toolSpecs = this.prefix.tools();
8049
8129
  for (let iter = 0; ; iter++) {
8050
8130
  if (signal.aborted) {
8051
- yield {
8052
- turn: this._turn,
8053
- role: "warning",
8054
- content: t("loop.abortedAtIter", { iter })
8055
- };
8056
- const stoppedMsg = "[aborted by user (Esc) \u2014 no summary produced. Ask again or /retry when ready; prior tool output is still in the log.]";
8057
- this.appendAndPersist(buildSyntheticAssistantMessage(stoppedMsg, this.model));
8058
- yield {
8059
- turn: this._turn,
8060
- role: "assistant_final",
8061
- content: stoppedMsg,
8062
- forcedSummary: true
8063
- };
8064
- yield { turn: this._turn, role: "done", content: stoppedMsg };
8065
- this._turnAbort = new AbortController();
8131
+ try {
8132
+ yield {
8133
+ turn: this._turn,
8134
+ role: "warning",
8135
+ content: t("loop.abortedAtIter", { iter })
8136
+ };
8137
+ const stoppedMsg = "[aborted by user (Esc) \u2014 no summary produced. Ask again or /retry when ready; prior tool output is still in the log.]";
8138
+ this.appendAndPersist(buildSyntheticAssistantMessage(stoppedMsg, this.model));
8139
+ yield {
8140
+ turn: this._turn,
8141
+ role: "assistant_final",
8142
+ content: stoppedMsg,
8143
+ forcedSummary: true
8144
+ };
8145
+ yield { turn: this._turn, role: "done", content: stoppedMsg };
8146
+ } finally {
8147
+ this._turnAbort = new AbortController();
8148
+ }
8066
8149
  return;
8067
8150
  }
8068
8151
  if (iter > 0) {
@@ -8089,7 +8172,7 @@ ${reason}`
8089
8172
  {
8090
8173
  const decision2 = this.context.decidePreflight(messages, this.prefix.toolSpecs, this.model);
8091
8174
  if (decision2.needsAction) {
8092
- const { estimateTokens: estimate, ctxMax } = decision2;
8175
+ const { estimateTokens: estimate, estimateBytes, ctxMax } = decision2;
8093
8176
  yield {
8094
8177
  turn: this._turn,
8095
8178
  role: "status",
@@ -8111,6 +8194,7 @@ ${reason}`
8111
8194
  estimate: after.estimateTokens.toLocaleString(),
8112
8195
  ctxMax: after.ctxMax.toLocaleString(),
8113
8196
  pct: Math.round(after.estimateTokens / after.ctxMax * 100),
8197
+ bodyKB: Math.round(after.estimateBytes / 1024).toLocaleString(),
8114
8198
  beforeMessages: result.beforeMessages,
8115
8199
  afterMessages: result.afterMessages
8116
8200
  }
@@ -8123,7 +8207,8 @@ ${reason}`
8123
8207
  content: t("loop.preflightNoFold", {
8124
8208
  estimate: estimate.toLocaleString(),
8125
8209
  ctxMax: ctxMax.toLocaleString(),
8126
- pct: Math.round(estimate / ctxMax * 100)
8210
+ pct: Math.round(estimate / ctxMax * 100),
8211
+ bodyKB: Math.round(estimateBytes / 1024).toLocaleString()
8127
8212
  })
8128
8213
  };
8129
8214
  }
@@ -8238,8 +8323,11 @@ ${reason}`
8238
8323
  }
8239
8324
  } catch (err) {
8240
8325
  if (signal.aborted) {
8241
- yield { turn: this._turn, role: "done", content: "" };
8242
- this._turnAbort = new AbortController();
8326
+ try {
8327
+ yield { turn: this._turn, role: "done", content: "" };
8328
+ } finally {
8329
+ this._turnAbort = new AbortController();
8330
+ }
8243
8331
  return;
8244
8332
  }
8245
8333
  const probe = is5xxError(err) ? await probeDeepSeekReachable(this.client) : void 0;
@@ -9273,6 +9361,8 @@ var USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (
9273
9361
  var MOJEEK_ENDPOINT = "https://www.mojeek.com/search";
9274
9362
  var METASO_ENDPOINT = "https://metaso.cn/api/v1";
9275
9363
  var TAVILY_ENDPOINT = "https://api.tavily.com/search";
9364
+ var PERPLEXITY_ENDPOINT = "https://api.perplexity.ai/chat/completions";
9365
+ var EXA_ENDPOINT = "https://api.exa.ai/answer";
9276
9366
  function searchStatusError(status) {
9277
9367
  if (status === 429) return t("webErrors.rateLimit429");
9278
9368
  if (status === 403) return t("webErrors.forbidden403");
@@ -9295,6 +9385,12 @@ async function webSearch(query, opts = {}) {
9295
9385
  if (opts.engine === "tavily") {
9296
9386
  return searchTavily(query, opts);
9297
9387
  }
9388
+ if (opts.engine === "perplexity") {
9389
+ return searchPerplexity(query, opts);
9390
+ }
9391
+ if (opts.engine === "exa") {
9392
+ return searchExa(query, opts);
9393
+ }
9298
9394
  return searchMojeek(query, opts);
9299
9395
  }
9300
9396
  async function searchMojeek(query, opts = {}) {
@@ -9370,6 +9466,7 @@ async function searchSearxng(query, opts = {}) {
9370
9466
  async function searchMetaso(query, opts = {}) {
9371
9467
  const topK = Math.max(1, Math.min(100, opts.topK ?? DEFAULT_TOPK));
9372
9468
  const apiKey = loadMetasoApiKey();
9469
+ if (!apiKey) throw new Error(t("webErrors.metasoMissingKey"));
9373
9470
  let resp;
9374
9471
  try {
9375
9472
  resp = await fetch(`${METASO_ENDPOINT}/search`, {
@@ -9478,6 +9575,121 @@ async function searchTavily(query, opts = {}) {
9478
9575
  snippet: r.content ?? ""
9479
9576
  }));
9480
9577
  }
9578
+ async function searchPerplexity(query, opts = {}) {
9579
+ const topK = Math.max(1, Math.min(20, opts.topK ?? DEFAULT_TOPK));
9580
+ const apiKey = loadPerplexityApiKey();
9581
+ if (!apiKey) throw new Error(t("webErrors.perplexityMissingKey"));
9582
+ let resp;
9583
+ try {
9584
+ resp = await fetch(PERPLEXITY_ENDPOINT, {
9585
+ method: "POST",
9586
+ headers: {
9587
+ Authorization: `Bearer ${apiKey}`,
9588
+ "Content-Type": "application/json"
9589
+ },
9590
+ body: JSON.stringify({
9591
+ model: "sonar",
9592
+ messages: [{ role: "user", content: query }],
9593
+ max_tokens: 1024,
9594
+ return_related_questions: false
9595
+ }),
9596
+ signal: opts.signal
9597
+ });
9598
+ } catch (err) {
9599
+ if (err instanceof TypeError && err.message.includes("fetch")) {
9600
+ throw new Error(t("webErrors.cannotReach", { endpoint: PERPLEXITY_ENDPOINT }));
9601
+ }
9602
+ throw err;
9603
+ }
9604
+ if (!resp.ok) {
9605
+ if (resp.status === 401 || resp.status === 403) {
9606
+ throw new Error(t("webErrors.perplexityUnauthorized"));
9607
+ }
9608
+ if (resp.status === 429) throw new Error(t("webErrors.perplexityRateLimit"));
9609
+ throw new Error(t("webErrors.perplexityServerError", { status: resp.status }));
9610
+ }
9611
+ const raw = await resp.text();
9612
+ let data;
9613
+ try {
9614
+ data = JSON.parse(raw);
9615
+ } catch {
9616
+ throw new Error(t("webErrors.perplexityParseError", { status: resp.status }));
9617
+ }
9618
+ const answer = data.choices?.[0]?.message?.content ?? "";
9619
+ const citations = Array.isArray(data.citations) ? data.citations : [];
9620
+ const results = [];
9621
+ if (answer) {
9622
+ results.push({ title: answer, url: "", snippet: "", answer });
9623
+ }
9624
+ const count = Math.min(citations.length, topK);
9625
+ for (let i = 0; i < count; i++) {
9626
+ const c = citations[i];
9627
+ if (typeof c === "string") {
9628
+ results.push({ title: `Source ${i + 1}`, url: c, snippet: "" });
9629
+ } else if (c && typeof c === "object" && typeof c.url === "string") {
9630
+ const item = c;
9631
+ results.push({
9632
+ title: typeof item.title === "string" ? item.title : `Source ${i + 1}`,
9633
+ url: item.url,
9634
+ snippet: ""
9635
+ });
9636
+ }
9637
+ }
9638
+ return results;
9639
+ }
9640
+ async function searchExa(query, opts = {}) {
9641
+ const topK = Math.max(1, Math.min(20, opts.topK ?? DEFAULT_TOPK));
9642
+ const apiKey = loadExaApiKey();
9643
+ if (!apiKey) throw new Error(t("webErrors.exaMissingKey"));
9644
+ let resp;
9645
+ try {
9646
+ resp = await fetch(EXA_ENDPOINT, {
9647
+ method: "POST",
9648
+ headers: {
9649
+ "x-api-key": apiKey,
9650
+ "Content-Type": "application/json"
9651
+ },
9652
+ body: JSON.stringify({ query, text: true }),
9653
+ signal: opts.signal
9654
+ });
9655
+ } catch (err) {
9656
+ if (err instanceof TypeError && err.message.includes("fetch")) {
9657
+ throw new Error(t("webErrors.cannotReach", { endpoint: EXA_ENDPOINT }));
9658
+ }
9659
+ throw err;
9660
+ }
9661
+ if (!resp.ok) {
9662
+ if (resp.status === 401 || resp.status === 403) {
9663
+ throw new Error(t("webErrors.exaUnauthorized"));
9664
+ }
9665
+ if (resp.status === 429) throw new Error(t("webErrors.exaRateLimit"));
9666
+ throw new Error(t("webErrors.exaServerError", { status: resp.status }));
9667
+ }
9668
+ const raw = await resp.text();
9669
+ let data;
9670
+ try {
9671
+ data = JSON.parse(raw);
9672
+ } catch {
9673
+ throw new Error(t("webErrors.exaParseError", { status: resp.status }));
9674
+ }
9675
+ const answer = data.answer ?? "";
9676
+ const citations = data.citations ?? [];
9677
+ const results = [];
9678
+ if (answer) {
9679
+ results.push({ title: answer, url: "", snippet: "", answer });
9680
+ }
9681
+ const count = Math.min(citations.length, topK);
9682
+ for (let i = 0; i < count; i++) {
9683
+ const c = citations[i];
9684
+ if (!c.url) continue;
9685
+ results.push({
9686
+ title: c.title || `Source ${i + 1}`,
9687
+ url: c.url,
9688
+ snippet: c.text ?? ""
9689
+ });
9690
+ }
9691
+ return results;
9692
+ }
9481
9693
  function parseSearxngHtmlResults(html) {
9482
9694
  const root = (0, import_node_html_parser.parse)(html);
9483
9695
  const results = [];
@@ -9696,7 +9908,7 @@ function registerWebTools(registry, opts = {}) {
9696
9908
  const maxFetchChars = opts.maxFetchChars ?? DEFAULT_FETCH_MAX_CHARS;
9697
9909
  registry.register({
9698
9910
  name: "web_search",
9699
- description: "Search the public web. Returns ranked results with title, url, and snippet. Call this when the answer's correctness depends on current state \u2014 anything that changes over time (events, prices, releases, status of a thing in the real world). Composing such answers from training memory invents stale numbers; search first, then ground the answer in the results. For evergreen / definitional questions you don't need this. To change the backend, use /search-engine mojeek|searxng|metaso|tavily.",
9911
+ description: "Search the public web. Returns ranked results with title, url, and snippet. Call this when the answer's correctness depends on current state \u2014 anything that changes over time (events, prices, releases, status of a thing in the real world). Composing such answers from training memory invents stale numbers; search first, then ground the answer in the results. For evergreen / definitional questions you don't need this.",
9700
9912
  readOnly: true,
9701
9913
  parallelSafe: true,
9702
9914
  parameters: {
@@ -9705,7 +9917,7 @@ function registerWebTools(registry, opts = {}) {
9705
9917
  query: { type: "string", description: "Natural-language search query." },
9706
9918
  topK: {
9707
9919
  type: "integer",
9708
- description: `Number of results to return (1..10). Default ${defaultTopK}.`
9920
+ description: `Number of results to return. Default ${defaultTopK}.`
9709
9921
  }
9710
9922
  },
9711
9923
  required: ["query"]
@@ -9749,14 +9961,30 @@ ${page.text}`;
9749
9961
  return registry;
9750
9962
  }
9751
9963
  function formatSearchResults(query, results) {
9752
- const lines = [`query: ${query}`, `
9753
- results (${results.length}):`];
9754
- results.forEach((r, i) => {
9964
+ const lines = [`query: ${query}`];
9965
+ const hasAnswer = results.length > 0 && results[0]?.url === "" && results[0]?.answer;
9966
+ if (hasAnswer) {
9967
+ lines.push("\nanswer:");
9968
+ lines.push(` ${results[0].answer}`);
9969
+ const sources = results.slice(1);
9755
9970
  lines.push(`
9971
+ sources (${sources.length}):`);
9972
+ sources.forEach((r, i) => {
9973
+ lines.push(`
9756
9974
  ${i + 1}. ${r.title}`);
9757
- lines.push(` ${r.url}`);
9758
- if (r.snippet) lines.push(` ${r.snippet}`);
9759
- });
9975
+ lines.push(` ${r.url}`);
9976
+ if (r.snippet) lines.push(` ${r.snippet}`);
9977
+ });
9978
+ } else {
9979
+ lines.push(`
9980
+ results (${results.length}):`);
9981
+ results.forEach((r, i) => {
9982
+ lines.push(`
9983
+ ${i + 1}. ${r.title}`);
9984
+ lines.push(` ${r.url}`);
9985
+ if (r.snippet) lines.push(` ${r.snippet}`);
9986
+ });
9987
+ }
9760
9988
  return lines.join("\n");
9761
9989
  }
9762
9990
 
@@ -10391,8 +10619,8 @@ async function searchContent(ctx, startAbs, args) {
10391
10619
  for (let i = realStart; i <= winEnd; i++) {
10392
10620
  const line = lines[i];
10393
10621
  const display = line.length > 200 ? `${line.slice(0, 200)}\u2026` : line;
10394
- const sep2 = hitSet.has(i) ? ":" : "-";
10395
- if (!pushLine(`${rel}:${i + 1}${sep2} ${display}`)) return;
10622
+ const sep = hitSet.has(i) ? ":" : "-";
10623
+ if (!pushLine(`${rel}:${i + 1}${sep} ${display}`)) return;
10396
10624
  }
10397
10625
  prevWindowEnd = winEnd;
10398
10626
  }
@@ -10423,7 +10651,9 @@ var DEFAULT_OUTLINE_THRESHOLD_BYTES = 64 * 1024;
10423
10651
  var DEFAULT_MAX_LIST_BYTES = 256 * 1024;
10424
10652
  var HARD_MAX_FILE_BYTES = 32 * 1024 * 1024;
10425
10653
  var OUTLINE_HEAD_LINES = 80;
10426
- var SKIP_DIR_NAMES = new Set(DEFAULT_INDEX_EXCLUDES.dirs);
10654
+ var SKIP_DIR_NAMES = new Set(
10655
+ DEFAULT_INDEX_EXCLUDES.dirs.filter((d) => d !== ".reasonix")
10656
+ );
10427
10657
  var BINARY_EXTENSIONS = new Set(DEFAULT_INDEX_EXCLUDES.exts);
10428
10658
  function displayRel3(rootDir, full) {
10429
10659
  return pathMod4.relative(rootDir, full).replaceAll("\\", "/");
@@ -11150,6 +11380,21 @@ function sanitizeEvidence(raw) {
11150
11380
  }
11151
11381
  return out.length > 0 ? out : void 0;
11152
11382
  }
11383
+ function summarizeEvidence(evidence) {
11384
+ if (!evidence || evidence.length === 0) return void 0;
11385
+ const parts = evidence.map((item) => `${item.kind}: ${item.summary}`);
11386
+ return parts.join("; ");
11387
+ }
11388
+ function compactStepCompletion(update) {
11389
+ const compact = {
11390
+ kind: "step_completed",
11391
+ stepId: update.stepId,
11392
+ result: update.result
11393
+ };
11394
+ const evidenceSummary = summarizeEvidence(update.evidence);
11395
+ if (evidenceSummary) compact.evidenceSummary = evidenceSummary;
11396
+ return compact;
11397
+ }
11153
11398
  function registerSubmitPlan(registry, opts) {
11154
11399
  registry.register({
11155
11400
  name: "submit_plan",
@@ -11263,9 +11508,9 @@ function registerMarkStepComplete(registry, opts) {
11263
11508
  opts.onStepCompleted?.(update);
11264
11509
  const verdict = await (ctx?.confirmationGate ?? pauseGate).ask({
11265
11510
  kind: "plan_checkpoint",
11266
- payload: { stepId, title, result, notes }
11511
+ payload: { stepId, title, result, notes, completion: update }
11267
11512
  });
11268
- if (verdict.type === "continue") return JSON.stringify(update);
11513
+ if (verdict.type === "continue") return JSON.stringify(compactStepCompletion(update));
11269
11514
  if (verdict.type === "revise") {
11270
11515
  if (verdict.feedback) return `revision requested: ${verdict.feedback}`;
11271
11516
  throw new Error("user requested revision at checkpoint");
@@ -11478,6 +11723,7 @@ function nextRunId() {
11478
11723
  runIdCounter++;
11479
11724
  return `sub-${runIdCounter.toString(36)}`;
11480
11725
  }
11726
+ var SHARED_SUBAGENT_SINK = { current: null };
11481
11727
  var SUBAGENT_BASE_SYSTEM = `You are a Reasonix subagent. The parent agent spawned you to handle one focused subtask, then return.
11482
11728
 
11483
11729
  Rules:
@@ -11576,12 +11822,40 @@ async function spawnSubagent(opts) {
11576
11822
  let toolIter = 0;
11577
11823
  let summarisingEmitted = false;
11578
11824
  let forcedSummaryFired = false;
11825
+ let outputChars = 0;
11826
+ let reasoningChars = 0;
11827
+ let toolReadChars = 0;
11828
+ let lastStreamEmitAt = 0;
11829
+ let charsSinceLastEmit = 0;
11830
+ const STREAM_EMIT_INTERVAL_MS = 200;
11831
+ const STREAM_EMIT_CHARS = 400;
11832
+ const maybeEmitStreamProgress = (now, force) => {
11833
+ if (!sink?.current) return;
11834
+ if (!force && now - lastStreamEmitAt < STREAM_EMIT_INTERVAL_MS && charsSinceLastEmit < STREAM_EMIT_CHARS) {
11835
+ return;
11836
+ }
11837
+ lastStreamEmitAt = now;
11838
+ charsSinceLastEmit = 0;
11839
+ sink.current({
11840
+ kind: "stream-progress",
11841
+ runId,
11842
+ task: taskPreview,
11843
+ skillName,
11844
+ model,
11845
+ iter: toolIter,
11846
+ elapsedMs: now - startedAt,
11847
+ outputChars,
11848
+ reasoningChars,
11849
+ toolReadChars
11850
+ });
11851
+ };
11579
11852
  try {
11580
11853
  for await (const ev of childLoop.step(opts.task)) {
11581
11854
  sink?.current?.({ kind: "inner", runId, task: taskPreview, skillName, model, inner: ev });
11582
11855
  if (ev.role === "tool") {
11583
11856
  toolIter++;
11584
11857
  summarisingEmitted = false;
11858
+ toolReadChars += ev.content?.length ?? 0;
11585
11859
  sink?.current?.({
11586
11860
  kind: "progress",
11587
11861
  runId,
@@ -11591,6 +11865,17 @@ async function spawnSubagent(opts) {
11591
11865
  iter: toolIter,
11592
11866
  elapsedMs: Date.now() - startedAt
11593
11867
  });
11868
+ maybeEmitStreamProgress(Date.now(), true);
11869
+ }
11870
+ if (ev.role === "assistant_delta") {
11871
+ const dContent = ev.content?.length ?? 0;
11872
+ const dReason = ev.reasoningDelta?.length ?? 0;
11873
+ if (dContent > 0 || dReason > 0) {
11874
+ outputChars += dContent;
11875
+ reasoningChars += dReason;
11876
+ charsSinceLastEmit += dContent + dReason;
11877
+ maybeEmitStreamProgress(Date.now(), false);
11878
+ }
11594
11879
  }
11595
11880
  if (ev.role === "assistant_delta" && !summarisingEmitted && (ev.content ?? "").length > 0) {
11596
11881
  summarisingEmitted = true;
@@ -11746,7 +12031,7 @@ import {
11746
12031
  writeFileSync,
11747
12032
  writeSync
11748
12033
  } from "fs";
11749
- import { dirname as dirname3, resolve as resolve4 } from "path";
12034
+ import { dirname as dirname3, isAbsolute as isAbsolute3, relative as relative6, resolve as resolve4 } from "path";
11750
12035
  var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
11751
12036
  function parseEditBlocks(text) {
11752
12037
  const out = [];
@@ -11763,10 +12048,30 @@ function parseEditBlocks(text) {
11763
12048
  }
11764
12049
  return out;
11765
12050
  }
12051
+ function resolveEditPath(rootDir, rawPath) {
12052
+ const absRoot = resolve4(rootDir);
12053
+ if (/^[A-Za-z]:[\\/]/.test(rawPath) || looksLikeAbsoluteSystemPath2(rawPath)) {
12054
+ return resolve4(rawPath);
12055
+ }
12056
+ let rooted = rawPath;
12057
+ while (rooted.startsWith("/") || rooted.startsWith("\\")) {
12058
+ rooted = rooted.slice(1);
12059
+ }
12060
+ return resolve4(absRoot, rooted || ".");
12061
+ }
12062
+ function looksLikeAbsoluteSystemPath2(rawPath) {
12063
+ return /^\/(?:home|Users|etc|var|opt|tmp|usr|mnt|Library|Volumes|proc|sys|dev|run|srv|media|Applications|System|root|boot|private)(?:[/\\]|$)/.test(
12064
+ rawPath
12065
+ );
12066
+ }
12067
+ function pathIsUnder2(child, parent) {
12068
+ const rel = relative6(parent, child);
12069
+ return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
12070
+ }
11766
12071
  function applyEditBlock(block, rootDir) {
11767
12072
  const absRoot = resolve4(rootDir);
11768
- const absTarget = resolve4(absRoot, block.path);
11769
- if (absTarget !== absRoot && !absTarget.startsWith(`${absRoot}${sep()}`)) {
12073
+ const absTarget = resolveEditPath(rootDir, block.path);
12074
+ if (!pathIsUnder2(absTarget, absRoot)) {
11770
12075
  return {
11771
12076
  path: block.path,
11772
12077
  status: "path-escape",
@@ -11831,6 +12136,14 @@ function applyEditBlock(block, rootDir) {
11831
12136
  message: "SEARCH text does not match the current file content exactly"
11832
12137
  };
11833
12138
  }
12139
+ const nextIdx = content.indexOf(adaptedSearch, idx + 1);
12140
+ if (nextIdx !== -1) {
12141
+ return {
12142
+ path: block.path,
12143
+ status: "not-found",
12144
+ message: "SEARCH text appears multiple times; include more context to disambiguate"
12145
+ };
12146
+ }
11834
12147
  const replaced = `${content.slice(0, idx)}${adaptedReplace}${content.slice(idx + adaptedSearch.length)}`;
11835
12148
  const outBuf = Buffer.from(replaced, "utf8");
11836
12149
  ftruncateSync(fd, outBuf.length);
@@ -11852,7 +12165,7 @@ function applyEditBlocks(blocks, rootDir) {
11852
12165
  return blocks.map((b) => applyEditBlock(b, rootDir));
11853
12166
  }
11854
12167
  function toWholeFileEditBlock(path, content, rootDir) {
11855
- const abs = resolve4(rootDir, path);
12168
+ const abs = resolveEditPath(rootDir, path);
11856
12169
  let search = "";
11857
12170
  if (existsSync3(abs)) {
11858
12171
  try {
@@ -11864,13 +12177,12 @@ function toWholeFileEditBlock(path, content, rootDir) {
11864
12177
  return { path, search, replace: content, offset: 0 };
11865
12178
  }
11866
12179
  function snapshotBeforeEdits(blocks, rootDir) {
11867
- const absRoot = resolve4(rootDir);
11868
12180
  const seen = /* @__PURE__ */ new Set();
11869
12181
  const snapshots = [];
11870
12182
  for (const b of blocks) {
11871
- if (seen.has(b.path)) continue;
11872
- seen.add(b.path);
11873
- const abs = resolve4(absRoot, b.path);
12183
+ const abs = resolveEditPath(rootDir, b.path);
12184
+ if (seen.has(abs)) continue;
12185
+ seen.add(abs);
11874
12186
  if (!existsSync3(abs)) {
11875
12187
  snapshots.push({ path: b.path, prevContent: null });
11876
12188
  continue;
@@ -11886,8 +12198,8 @@ function snapshotBeforeEdits(blocks, rootDir) {
11886
12198
  function restoreSnapshots(snapshots, rootDir) {
11887
12199
  const absRoot = resolve4(rootDir);
11888
12200
  return snapshots.map((snap) => {
11889
- const abs = resolve4(absRoot, snap.path);
11890
- if (abs !== absRoot && !abs.startsWith(`${absRoot}${sep()}`)) {
12201
+ const abs = resolveEditPath(rootDir, snap.path);
12202
+ if (!pathIsUnder2(abs, absRoot)) {
11891
12203
  return {
11892
12204
  path: snap.path,
11893
12205
  status: "path-escape",
@@ -11914,9 +12226,6 @@ function restoreSnapshots(snapshots, rootDir) {
11914
12226
  }
11915
12227
  });
11916
12228
  }
11917
- function sep() {
11918
- return process.platform === "win32" ? "\\" : "/";
11919
- }
11920
12229
  function lineEndingOf(text) {
11921
12230
  return text.includes("\r\n") ? "\r\n" : "\n";
11922
12231
  }
@@ -11942,6 +12251,7 @@ export {
11942
12251
  registerChoiceTool,
11943
12252
  registerPlanTool,
11944
12253
  registerTodoTool,
12254
+ SHARED_SUBAGENT_SINK,
11945
12255
  spawnSubagent,
11946
12256
  formatSubagentResult,
11947
12257
  webFetch,
@@ -11957,4 +12267,4 @@ export {
11957
12267
  he/he.js:
11958
12268
  (*! https://mths.be/he v1.2.0 by @mathias | MIT license *)
11959
12269
  */
11960
- //# sourceMappingURL=chunk-JBH5RM7X.js.map
12270
+ //# sourceMappingURL=chunk-JFBGSWQB.js.map