reasonix 0.37.0 → 0.38.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 (74) hide show
  1. package/README.md +1 -0
  2. package/README.zh-CN.md +1 -0
  3. package/dist/cli/{chat-7257YAPG.js → chat-FPEYKTMI.js} +13 -14
  4. package/dist/cli/{chunk-T52GAWPP.js → chunk-3VTV4WAH.js} +2 -2
  5. package/dist/cli/{chunk-MSKUP6PD.js → chunk-4PNXH2MH.js} +910 -659
  6. package/dist/cli/chunk-4PNXH2MH.js.map +1 -0
  7. package/dist/cli/{chunk-YER7WCHF.js → chunk-A63QT566.js} +24 -10
  8. package/dist/cli/chunk-A63QT566.js.map +1 -0
  9. package/dist/cli/{chunk-4Q3GRJIU.js → chunk-AATCLE5N.js} +2 -2
  10. package/dist/cli/{chunk-BHLHOS5Y.js → chunk-BW2HWSYH.js} +315 -5
  11. package/dist/cli/chunk-BW2HWSYH.js.map +1 -0
  12. package/dist/cli/{chunk-ZJR4QLXB.js → chunk-FB46F6H4.js} +2 -2
  13. package/dist/cli/{chunk-GKZJXYMY.js → chunk-FYKZB6TX.js} +415 -11
  14. package/dist/cli/chunk-FYKZB6TX.js.map +1 -0
  15. package/dist/cli/{chunk-XQIFIB3U.js → chunk-JOFZ6AW5.js} +2 -2
  16. package/dist/cli/{chunk-JGZKTAOH.js → chunk-LMNAMITH.js} +2 -2
  17. package/dist/cli/{chunk-S4GF3HPO.js → chunk-LY352GTC.js} +6 -4
  18. package/dist/cli/chunk-LY352GTC.js.map +1 -0
  19. package/dist/cli/{chunk-VF57YX2M.js → chunk-NYP2DDDV.js} +40 -1
  20. package/dist/cli/chunk-NYP2DDDV.js.map +1 -0
  21. package/dist/cli/{chunk-JULZ7JTO.js → chunk-T5U5JO7Q.js} +11 -8
  22. package/dist/cli/chunk-T5U5JO7Q.js.map +1 -0
  23. package/dist/cli/{chunk-SEFXUF24.js → chunk-YJKLNYCP.js} +113 -24
  24. package/dist/cli/chunk-YJKLNYCP.js.map +1 -0
  25. package/dist/cli/{code-64EG5IU2.js → code-GTE65OUT.js} +23 -17
  26. package/dist/cli/{code-64EG5IU2.js.map → code-GTE65OUT.js.map} +1 -1
  27. package/dist/cli/{commands-FE2UDFBC.js → commands-R4JWISND.js} +3 -4
  28. package/dist/cli/{commands-FE2UDFBC.js.map → commands-R4JWISND.js.map} +1 -1
  29. package/dist/cli/{commit-3IAGB22T.js → commit-TQ4DMUNS.js} +2 -3
  30. package/dist/cli/{commit-3IAGB22T.js.map → commit-TQ4DMUNS.js.map} +1 -1
  31. package/dist/cli/{doctor-BW5HSQDW.js → doctor-GGK2JKTA.js} +6 -7
  32. package/dist/cli/index.js +24 -25
  33. package/dist/cli/index.js.map +1 -1
  34. package/dist/cli/{mcp-2RDEQST6.js → mcp-M7I23TQ7.js} +2 -3
  35. package/dist/cli/{mcp-2RDEQST6.js.map → mcp-M7I23TQ7.js.map} +1 -1
  36. package/dist/cli/{mcp-browse-VM5GLRBQ.js → mcp-browse-TWO7RYT4.js} +2 -3
  37. package/dist/cli/{mcp-browse-VM5GLRBQ.js.map → mcp-browse-TWO7RYT4.js.map} +1 -1
  38. package/dist/cli/{prompt-KGIUONO3.js → prompt-ODPFOKSH.js} +2 -2
  39. package/dist/cli/{replay-D7RT2DR7.js → replay-R3QRXPI2.js} +13 -9
  40. package/dist/cli/replay-R3QRXPI2.js.map +1 -0
  41. package/dist/cli/{run-RWCOA32G.js → run-WGSPYYOJ.js} +7 -8
  42. package/dist/cli/{run-RWCOA32G.js.map → run-WGSPYYOJ.js.map} +1 -1
  43. package/dist/cli/{server-6ZW4TQUP.js → server-IZPWQYG3.js} +8 -9
  44. package/dist/cli/{server-6ZW4TQUP.js.map → server-IZPWQYG3.js.map} +1 -1
  45. package/dist/cli/{sessions-5ISNWFMU.js → sessions-E4UH5JYL.js} +7 -8
  46. package/dist/cli/{sessions-5ISNWFMU.js.map → sessions-E4UH5JYL.js.map} +1 -1
  47. package/dist/cli/{setup-HJG23NKJ.js → setup-FTZNN3TZ.js} +60 -15
  48. package/dist/cli/setup-FTZNN3TZ.js.map +1 -0
  49. package/dist/cli/{version-BXAN7Q4V.js → version-MDVCFTKA.js} +7 -8
  50. package/dist/cli/{version-BXAN7Q4V.js.map → version-MDVCFTKA.js.map} +1 -1
  51. package/dist/index.d.ts +3 -0
  52. package/dist/index.js +568 -40
  53. package/dist/index.js.map +1 -1
  54. package/package.json +1 -1
  55. package/dist/cli/chunk-BHLHOS5Y.js.map +0 -1
  56. package/dist/cli/chunk-GKZJXYMY.js.map +0 -1
  57. package/dist/cli/chunk-JULZ7JTO.js.map +0 -1
  58. package/dist/cli/chunk-MSKUP6PD.js.map +0 -1
  59. package/dist/cli/chunk-S4GF3HPO.js.map +0 -1
  60. package/dist/cli/chunk-SEFXUF24.js.map +0 -1
  61. package/dist/cli/chunk-VF57YX2M.js.map +0 -1
  62. package/dist/cli/chunk-WUI3P4RA.js +0 -319
  63. package/dist/cli/chunk-WUI3P4RA.js.map +0 -1
  64. package/dist/cli/chunk-YER7WCHF.js.map +0 -1
  65. package/dist/cli/replay-D7RT2DR7.js.map +0 -1
  66. package/dist/cli/setup-HJG23NKJ.js.map +0 -1
  67. /package/dist/cli/{chat-7257YAPG.js.map → chat-FPEYKTMI.js.map} +0 -0
  68. /package/dist/cli/{chunk-T52GAWPP.js.map → chunk-3VTV4WAH.js.map} +0 -0
  69. /package/dist/cli/{chunk-4Q3GRJIU.js.map → chunk-AATCLE5N.js.map} +0 -0
  70. /package/dist/cli/{chunk-ZJR4QLXB.js.map → chunk-FB46F6H4.js.map} +0 -0
  71. /package/dist/cli/{chunk-XQIFIB3U.js.map → chunk-JOFZ6AW5.js.map} +0 -0
  72. /package/dist/cli/{chunk-JGZKTAOH.js.map → chunk-LMNAMITH.js.map} +0 -0
  73. /package/dist/cli/{doctor-BW5HSQDW.js.map → doctor-GGK2JKTA.js.map} +0 -0
  74. /package/dist/cli/{prompt-KGIUONO3.js.map → prompt-ODPFOKSH.js.map} +0 -0
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  MemoryStore,
4
4
  sanitizeMemoryName
5
- } from "./chunk-YER7WCHF.js";
5
+ } from "./chunk-A63QT566.js";
6
6
  import {
7
7
  countTokens,
8
8
  estimateConversationTokens,
@@ -13,7 +13,7 @@ import {
13
13
  } from "./chunk-KMWKGPFZ.js";
14
14
  import {
15
15
  pauseGate
16
- } from "./chunk-S4GF3HPO.js";
16
+ } from "./chunk-LY352GTC.js";
17
17
  import {
18
18
  NEGATIVE_CLAIM_RULE,
19
19
  TUI_FORMATTING_RULES
@@ -21,7 +21,7 @@ import {
21
21
  import {
22
22
  formatHookOutcomeMessage,
23
23
  runHooks
24
- } from "./chunk-JGZKTAOH.js";
24
+ } from "./chunk-LMNAMITH.js";
25
25
  import {
26
26
  ignoredByLayers,
27
27
  loadGitignoreAt,
@@ -36,12 +36,12 @@ import {
36
36
  } from "./chunk-6CXT5JRM.js";
37
37
  import {
38
38
  t
39
- } from "./chunk-GKZJXYMY.js";
39
+ } from "./chunk-FYKZB6TX.js";
40
40
  import {
41
41
  DEFAULT_INDEX_EXCLUDES,
42
42
  webSearchEndpoint,
43
43
  webSearchEngine
44
- } from "./chunk-BHLHOS5Y.js";
44
+ } from "./chunk-BW2HWSYH.js";
45
45
  import {
46
46
  DEEPSEEK_CONTEXT_TOKENS,
47
47
  DEFAULT_CONTEXT_TOKENS,
@@ -309,6 +309,10 @@ var ToolRegistry = class {
309
309
  setResultAugmenter(fn) {
310
310
  this._resultAugmenter = fn;
311
311
  }
312
+ /** True when an augmenter is already wired — lets late-installing callers skip clobbering an earlier one. */
313
+ get hasResultAugmenter() {
314
+ return this._resultAugmenter !== null;
315
+ }
312
316
  register(def) {
313
317
  if (!def.name) throw new Error("tool requires a name");
314
318
  const internal = { ...def };
@@ -1375,6 +1379,7 @@ function signature(call) {
1375
1379
 
1376
1380
  // src/loop.ts
1377
1381
  var ESCALATION_MODEL = "deepseek-v4-pro";
1382
+ var PARENT_BUDGET_WARN_THRESHOLD = 5;
1378
1383
  var CacheFirstLoop = class {
1379
1384
  client;
1380
1385
  prefix;
@@ -1411,6 +1416,7 @@ var CacheFirstLoop = class {
1411
1416
  _turnFailures = new TurnFailureTracker();
1412
1417
  _turnSelfCorrected = false;
1413
1418
  _foldedThisTurn = false;
1419
+ _toolDispatchesThisStep = 0;
1414
1420
  context;
1415
1421
  /** Subscribe API so UI hooks can derive `running` from finally-guaranteed insertions. */
1416
1422
  get inflight() {
@@ -1465,6 +1471,23 @@ var CacheFirstLoop = class {
1465
1471
  stormThreshold: parsePositiveIntEnv(process.env.REASONIX_STORM_THRESHOLD),
1466
1472
  stormWindow: parsePositiveIntEnv(process.env.REASONIX_STORM_WINDOW)
1467
1473
  });
1474
+ if (!this.tools.hasResultAugmenter) {
1475
+ this.tools.setResultAugmenter((_name, _args, result) => {
1476
+ this._toolDispatchesThisStep++;
1477
+ const remaining = this.maxToolIters - this._toolDispatchesThisStep;
1478
+ if (remaining <= 0) {
1479
+ return `${result}
1480
+
1481
+ [budget: 0 of ${this.maxToolIters} tool calls left this turn \u2014 finalize NOW; the next iter forces a summary]`;
1482
+ }
1483
+ if (remaining <= PARENT_BUDGET_WARN_THRESHOLD) {
1484
+ return `${result}
1485
+
1486
+ [budget: ${remaining} of ${this.maxToolIters} tool calls left this turn \u2014 wrap up soon]`;
1487
+ }
1488
+ return result;
1489
+ });
1490
+ }
1468
1491
  this.sessionName = opts.session ?? null;
1469
1492
  if (this.sessionName) {
1470
1493
  const prior = loadSessionMessages(this.sessionName);
@@ -1718,6 +1741,7 @@ ${reason}`
1718
1741
  this._turnSelfCorrected = false;
1719
1742
  this._escalateThisTurn = false;
1720
1743
  this._foldedThisTurn = false;
1744
+ this._toolDispatchesThisStep = 0;
1721
1745
  let armedConsumed = false;
1722
1746
  if (this._proArmedForNextTurn) {
1723
1747
  this._escalateThisTurn = true;
@@ -3129,6 +3153,74 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
3129
3153
  return `moved ${displayRel4(rootDir, src)} \u2192 ${displayRel4(rootDir, dst)}`;
3130
3154
  }
3131
3155
  });
3156
+ registry.register({
3157
+ name: "delete_file",
3158
+ description: "Delete one file under the sandbox root. Refuses directories \u2014 use delete_directory for those. Errors if the path doesn't exist.",
3159
+ parameters: {
3160
+ type: "object",
3161
+ properties: { path: { type: "string" } },
3162
+ required: ["path"]
3163
+ },
3164
+ fn: async (args) => {
3165
+ const abs = safePath(args.path);
3166
+ const st = await fs4.lstat(abs);
3167
+ if (st.isDirectory()) {
3168
+ throw new Error(
3169
+ `delete_file: ${args.path} is a directory \u2014 use delete_directory to remove it`
3170
+ );
3171
+ }
3172
+ await fs4.unlink(abs);
3173
+ return `deleted ${displayRel4(rootDir, abs)}`;
3174
+ }
3175
+ });
3176
+ registry.register({
3177
+ name: "delete_directory",
3178
+ description: "Recursively delete a directory under the sandbox root. Pass `recursive:false` to refuse non-empty directories. Errors if the path doesn't exist.",
3179
+ parameters: {
3180
+ type: "object",
3181
+ properties: {
3182
+ path: { type: "string" },
3183
+ recursive: {
3184
+ type: "boolean",
3185
+ description: "When true (default) deletes the directory and all its contents. When false, only removes empty directories \u2014 non-empty refuses with an error."
3186
+ }
3187
+ },
3188
+ required: ["path"]
3189
+ },
3190
+ fn: async (args) => {
3191
+ const abs = safePath(args.path);
3192
+ const st = await fs4.lstat(abs);
3193
+ if (!st.isDirectory()) {
3194
+ throw new Error(`delete_directory: ${args.path} is a file \u2014 use delete_file to remove it`);
3195
+ }
3196
+ const recursive = args.recursive !== false;
3197
+ if (recursive) {
3198
+ await fs4.rm(abs, { recursive: true, force: false });
3199
+ } else {
3200
+ await fs4.rmdir(abs);
3201
+ }
3202
+ return `deleted ${displayRel4(rootDir, abs)}/${recursive ? " (recursive)" : ""}`;
3203
+ }
3204
+ });
3205
+ registry.register({
3206
+ name: "copy_file",
3207
+ description: "Copy a file or directory under the sandbox root. Both source and destination resolve under the sandbox. Parent directories of the destination are created as needed. Refuses to overwrite an existing destination \u2014 delete it first if you want to replace it.",
3208
+ parameters: {
3209
+ type: "object",
3210
+ properties: {
3211
+ source: { type: "string" },
3212
+ destination: { type: "string" }
3213
+ },
3214
+ required: ["source", "destination"]
3215
+ },
3216
+ fn: async (args) => {
3217
+ const src = safePath(args.source);
3218
+ const dst = safePath(args.destination);
3219
+ await fs4.mkdir(pathMod4.dirname(dst), { recursive: true });
3220
+ await fs4.cp(src, dst, { recursive: true, force: false, errorOnExist: true });
3221
+ return `copied ${displayRel4(rootDir, src)} \u2192 ${displayRel4(rootDir, dst)}`;
3222
+ }
3223
+ });
3132
3224
  return registry;
3133
3225
  }
3134
3226
 
@@ -3667,16 +3759,19 @@ async function searchMojeek(query, opts = {}) {
3667
3759
  signal: opts.signal,
3668
3760
  redirect: "follow"
3669
3761
  });
3670
- if (!resp.ok) throw new Error(`web_search ${resp.status}`);
3762
+ if (!resp.ok) throw new Error(t("webErrors.status", { status: resp.status }));
3671
3763
  const html = await resp.text();
3672
3764
  const results = parseMojeekResults(html).slice(0, topK);
3673
3765
  if (results.length === 0) {
3674
3766
  if (/no results found|did not match any documents/i.test(html)) return [];
3675
3767
  if (/captcha|verify you are human|access denied|forbidden/i.test(html)) {
3676
- throw new Error("web_search: Mojeek anti-bot page \u2014 rate-limited or blocked");
3768
+ throw new Error(t("webErrors.mojeekBlocked"));
3677
3769
  }
3678
3770
  throw new Error(
3679
- `web_search: 0 results but response doesn't look like a real empty page (${html.length} chars, first 120: ${html.slice(0, 120).replace(/\s+/g, " ")})`
3771
+ t("webErrors.mojeekNoResults", {
3772
+ chars: html.length,
3773
+ preview: html.slice(0, 120).replace(/\s+/g, " ")
3774
+ })
3680
3775
  );
3681
3776
  }
3682
3777
  return results;
@@ -3686,10 +3781,10 @@ function normalizeSearxngEndpoint(raw) {
3686
3781
  try {
3687
3782
  url = new URL(raw.includes("://") ? raw : `http://${raw}`);
3688
3783
  } catch {
3689
- throw new Error(`web_search: invalid SearXNG endpoint "${raw}"`);
3784
+ throw new Error(t("webErrors.invalidEndpoint", { endpoint: raw }));
3690
3785
  }
3691
3786
  if (url.protocol !== "http:" && url.protocol !== "https:") {
3692
- throw new Error(`web_search: SearXNG endpoint must be http(s), got ${url.protocol}`);
3787
+ throw new Error(t("webErrors.endpointMustBeHttp", { protocol: url.protocol }));
3693
3788
  }
3694
3789
  return url.origin;
3695
3790
  }
@@ -3709,19 +3804,17 @@ async function searchSearxng(query, opts = {}) {
3709
3804
  } catch (err) {
3710
3805
  if (err instanceof TypeError && err.message.includes("fetch")) {
3711
3806
  throw new Error(
3712
- `web_search: Cannot reach SearXNG server at ${opts.endpoint ?? "http://localhost:8080"}. Please install SearXNG (https://github.com/searxng/searxng) and start it (e.g. \`docker run -d -p 8080:8080 searxng/searxng\`), or switch to the default engine with /search-engine mojeek.`
3807
+ t("webErrors.cannotReach", { endpoint: opts.endpoint ?? "http://localhost:8080" })
3713
3808
  );
3714
3809
  }
3715
3810
  throw err;
3716
3811
  }
3717
- if (!resp.ok) throw new Error(`web_search ${resp.status}`);
3812
+ if (!resp.ok) throw new Error(t("webErrors.status", { status: resp.status }));
3718
3813
  const html = await resp.text();
3719
3814
  const results = parseSearxngHtmlResults(html).slice(0, topK);
3720
3815
  if (results.length === 0) {
3721
3816
  if (/no results found|did not match any documents/i.test(html)) return [];
3722
- throw new Error(
3723
- `web_search: 0 results but SearXNG response doesn't look like an empty results page (${html.length} chars)`
3724
- );
3817
+ throw new Error(t("webErrors.searxngNoResults", { chars: html.length }));
3725
3818
  }
3726
3819
  return results;
3727
3820
  }
@@ -3815,13 +3908,11 @@ async function webFetch(url, opts = {}) {
3815
3908
  clearTimeout(timer);
3816
3909
  opts.signal?.removeEventListener("abort", cancel);
3817
3910
  }
3818
- if (!resp.ok) throw new Error(`web_fetch ${resp.status} for ${url}`);
3911
+ if (!resp.ok) throw new Error(t("webErrors.fetchStatus", { status: resp.status, url }));
3819
3912
  const contentType = resp.headers.get("content-type") ?? "";
3820
3913
  const declaredLen = Number(resp.headers.get("content-length") ?? "");
3821
3914
  if (Number.isFinite(declaredLen) && declaredLen > FETCH_MAX_BYTES) {
3822
- throw new Error(
3823
- `web_fetch refused: content-length ${declaredLen} bytes exceeds ${FETCH_MAX_BYTES}-byte cap (${url})`
3824
- );
3915
+ throw new Error(t("webErrors.fetchTooLarge", { len: declaredLen, cap: FETCH_MAX_BYTES, url }));
3825
3916
  }
3826
3917
  const raw = await readBodyCapped(resp, FETCH_MAX_BYTES);
3827
3918
  const title = extractTitle(raw);
@@ -3848,9 +3939,7 @@ async function readBodyCapped(resp, maxBytes) {
3848
3939
  await reader.cancel();
3849
3940
  } catch {
3850
3941
  }
3851
- throw new Error(
3852
- `web_fetch refused: response body exceeded ${maxBytes}-byte cap (${total} bytes seen)`
3853
- );
3942
+ throw new Error(t("webErrors.fetchBodyTooLarge", { cap: maxBytes, seen: total }));
3854
3943
  }
3855
3944
  out += decoder.decode(value, { stream: true });
3856
3945
  }
@@ -3978,7 +4067,7 @@ function registerWebTools(registry, opts = {}) {
3978
4067
  },
3979
4068
  fn: async (args, ctx) => {
3980
4069
  if (!/^https?:\/\//i.test(args.url)) {
3981
- throw new Error("web_fetch: url must start with http:// or https://");
4070
+ throw new Error(t("webErrors.fetchInvalidUrl"));
3982
4071
  }
3983
4072
  const page = await webFetch(args.url, { maxChars: maxFetchChars, signal: ctx?.signal });
3984
4073
  const header = page.title ? `${page.title}
@@ -5077,4 +5166,4 @@ export {
5077
5166
  snapshotBeforeEdits,
5078
5167
  restoreSnapshots
5079
5168
  };
5080
- //# sourceMappingURL=chunk-SEFXUF24.js.map
5169
+ //# sourceMappingURL=chunk-YJKLNYCP.js.map