reasonix 0.46.1 → 0.47.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 (131) hide show
  1. package/README.md +62 -13
  2. package/README.zh-CN.md +52 -10
  3. package/dashboard/dist/app.js +217 -60
  4. package/dashboard/dist/app.js.map +1 -1
  5. package/dist/cli/{acp-LKJU5DZX.js → acp-QK3DMC53.js} +22 -22
  6. package/dist/cli/chat-VV5UWY4V.js +51 -0
  7. package/dist/cli/{chunk-DGA5QYFM.js → chunk-24A7FHGJ.js} +42 -13
  8. package/dist/cli/chunk-24A7FHGJ.js.map +1 -0
  9. package/dist/cli/{chunk-K3AIFMI6.js → chunk-6J6BSUCR.js} +2 -2
  10. package/dist/cli/{chunk-C72TNHDE.js → chunk-BWYVFFKR.js} +2 -2
  11. package/dist/cli/{chunk-EAOL43HB.js → chunk-BYYVYJDX.js} +3 -3
  12. package/dist/cli/{chunk-HNXDZGC6.js → chunk-CI2PF5QX.js} +2 -2
  13. package/dist/cli/{chunk-JVQT5IYP.js → chunk-COWPEX54.js} +19 -9
  14. package/dist/cli/chunk-COWPEX54.js.map +1 -0
  15. package/dist/cli/{chunk-IYQ325V7.js → chunk-E5WCLUIU.js} +2 -2
  16. package/dist/cli/{chunk-YRLC2EDF.js → chunk-EQATK2L2.js} +2 -2
  17. package/dist/cli/{chunk-R2ASNSEO.js → chunk-FDKOUJKZ.js} +8 -8
  18. package/dist/cli/{chunk-TEUDEGX2.js → chunk-FY4S7TJZ.js} +19 -5
  19. package/dist/cli/chunk-FY4S7TJZ.js.map +1 -0
  20. package/dist/cli/{chunk-JVFEJAJX.js → chunk-GDKB2PPK.js} +2 -2
  21. package/dist/cli/{chunk-WQ6ZRDQM.js → chunk-HIYTRCSW.js} +16 -12
  22. package/dist/cli/chunk-HIYTRCSW.js.map +1 -0
  23. package/dist/cli/{chunk-ZOQHVQON.js → chunk-ICAFSZHS.js} +307 -30
  24. package/dist/cli/chunk-ICAFSZHS.js.map +1 -0
  25. package/dist/cli/{chunk-SPXN5JIT.js → chunk-ICSYGIPN.js} +1386 -1021
  26. package/dist/cli/chunk-ICSYGIPN.js.map +1 -0
  27. package/dist/cli/{chunk-XPAUNFOL.js → chunk-K6GUKSXH.js} +3 -2
  28. package/dist/cli/chunk-K6GUKSXH.js.map +1 -0
  29. package/dist/cli/{chunk-6VANO7KB.js → chunk-KDRUEXII.js} +147 -20
  30. package/dist/cli/chunk-KDRUEXII.js.map +1 -0
  31. package/dist/cli/{chunk-NCBP5D6E.js → chunk-LBLR4CUZ.js} +2 -2
  32. package/dist/cli/{chunk-2425HK6U.js → chunk-LGEKVMMV.js} +7 -2
  33. package/dist/cli/{chunk-2425HK6U.js.map → chunk-LGEKVMMV.js.map} +1 -1
  34. package/dist/cli/{chunk-7SGGXNB2.js → chunk-OJVITDGB.js} +2 -2
  35. package/dist/cli/{chunk-SE7C5ZSI.js → chunk-QVDWH2A2.js} +3 -3
  36. package/dist/cli/{chunk-WRONKNIH.js → chunk-QVUFWDD2.js} +2 -2
  37. package/dist/cli/{chunk-3AAG2CUT.js → chunk-R6GQKKBW.js} +2 -2
  38. package/dist/cli/{chunk-E7TAHQ4A.js → chunk-RRXUIPWG.js} +19 -18
  39. package/dist/cli/chunk-RRXUIPWG.js.map +1 -0
  40. package/dist/cli/{chunk-CXVWUPA3.js → chunk-TKVXTQ3T.js} +26 -26
  41. package/dist/cli/chunk-TKVXTQ3T.js.map +1 -0
  42. package/dist/cli/{chunk-DHRVZJ2D.js → chunk-UDVFBEXC.js} +3 -3
  43. package/dist/cli/{chunk-7YW6TPXK.js → chunk-VC2CQA5D.js} +9 -9
  44. package/dist/cli/{chunk-M4E5JK6S.js → chunk-VJMBISEI.js} +2 -2
  45. package/dist/cli/{chunk-TDSBASOF.js → chunk-VKYSZKH2.js} +2 -2
  46. package/dist/cli/{chunk-7LOJS3LV.js → chunk-VMUUFWFF.js} +2 -2
  47. package/dist/cli/{chunk-MIIZJD5O.js → chunk-VNQGCA3Q.js} +2 -2
  48. package/dist/cli/{chunk-2AASOSD5.js → chunk-WF7TPVZM.js} +2 -2
  49. package/dist/cli/{chunk-JLQDNLZF.js → chunk-YDPLF7XR.js} +26 -14
  50. package/dist/cli/chunk-YDPLF7XR.js.map +1 -0
  51. package/dist/cli/{code-2JIHL5M2.js → code-C24TUAE5.js} +39 -34
  52. package/dist/cli/code-C24TUAE5.js.map +1 -0
  53. package/dist/cli/{commands-OPT5AJNH.js → commands-RR3GIYOK.js} +4 -4
  54. package/dist/cli/{commit-KA37H6GM.js → commit-FSHPIINM.js} +3 -3
  55. package/dist/cli/{desktop-5ONTRU3C.js → desktop-7NCHPEFB.js} +263 -36
  56. package/dist/cli/desktop-7NCHPEFB.js.map +1 -0
  57. package/dist/cli/{diff-SOIA7AKH.js → diff-RAAHHLHV.js} +8 -8
  58. package/dist/cli/{doctor-RCUP4XRV.js → doctor-PKVQIXRT.js} +9 -9
  59. package/dist/cli/{events-6KHITNX4.js → events-VRYXOSKI.js} +3 -3
  60. package/dist/cli/index.js +81 -40
  61. package/dist/cli/index.js.map +1 -1
  62. package/dist/cli/{mcp-JP5OWD6R.js → mcp-CRJ26PP4.js} +2 -2
  63. package/dist/cli/{mcp-browse-ONCJJPJN.js → mcp-browse-QPAOWZOP.js} +2 -2
  64. package/dist/cli/{mcp-inspect-TPLHW5JA.js → mcp-inspect-CVCLABRS.js} +4 -4
  65. package/dist/cli/{prompt-RJDNCQAP.js → prompt-SKYXERSI.js} +4 -4
  66. package/dist/cli/{prune-sessions-MKEATRVL.js → prune-sessions-SEWX7GP6.js} +2 -2
  67. package/dist/cli/{replay-4NILJG4U.js → replay-KPDW2ZMJ.js} +9 -9
  68. package/dist/cli/{run-WFGXB4SB.js → run-WIKDIXTG.js} +17 -17
  69. package/dist/cli/{server-5VFQP3PV.js → server-P6V2G3P6.js} +82 -34
  70. package/dist/cli/server-P6V2G3P6.js.map +1 -0
  71. package/dist/cli/{sessions-5XDJDALO.js → sessions-2NULRMSA.js} +15 -15
  72. package/dist/cli/{setup-F6XSWLRA.js → setup-Y5WDBQFL.js} +6 -6
  73. package/dist/cli/{stats-ALHBZICE.js → stats-T7BL2YOR.js} +6 -6
  74. package/dist/cli/{version-JVRAHBMM.js → version-3KWDNWLN.js} +15 -15
  75. package/dist/index.d.ts +31 -10
  76. package/dist/index.js +505 -66
  77. package/dist/index.js.map +1 -1
  78. package/package.json +1 -1
  79. package/dist/cli/chat-W7LAWEN6.js +0 -51
  80. package/dist/cli/chunk-6VANO7KB.js.map +0 -1
  81. package/dist/cli/chunk-CXVWUPA3.js.map +0 -1
  82. package/dist/cli/chunk-DGA5QYFM.js.map +0 -1
  83. package/dist/cli/chunk-E7TAHQ4A.js.map +0 -1
  84. package/dist/cli/chunk-JLQDNLZF.js.map +0 -1
  85. package/dist/cli/chunk-JVQT5IYP.js.map +0 -1
  86. package/dist/cli/chunk-SPXN5JIT.js.map +0 -1
  87. package/dist/cli/chunk-TEUDEGX2.js.map +0 -1
  88. package/dist/cli/chunk-WQ6ZRDQM.js.map +0 -1
  89. package/dist/cli/chunk-XPAUNFOL.js.map +0 -1
  90. package/dist/cli/chunk-ZOQHVQON.js.map +0 -1
  91. package/dist/cli/code-2JIHL5M2.js.map +0 -1
  92. package/dist/cli/desktop-5ONTRU3C.js.map +0 -1
  93. package/dist/cli/server-5VFQP3PV.js.map +0 -1
  94. /package/dist/cli/{acp-LKJU5DZX.js.map → acp-QK3DMC53.js.map} +0 -0
  95. /package/dist/cli/{chat-W7LAWEN6.js.map → chat-VV5UWY4V.js.map} +0 -0
  96. /package/dist/cli/{chunk-K3AIFMI6.js.map → chunk-6J6BSUCR.js.map} +0 -0
  97. /package/dist/cli/{chunk-C72TNHDE.js.map → chunk-BWYVFFKR.js.map} +0 -0
  98. /package/dist/cli/{chunk-EAOL43HB.js.map → chunk-BYYVYJDX.js.map} +0 -0
  99. /package/dist/cli/{chunk-HNXDZGC6.js.map → chunk-CI2PF5QX.js.map} +0 -0
  100. /package/dist/cli/{chunk-IYQ325V7.js.map → chunk-E5WCLUIU.js.map} +0 -0
  101. /package/dist/cli/{chunk-YRLC2EDF.js.map → chunk-EQATK2L2.js.map} +0 -0
  102. /package/dist/cli/{chunk-R2ASNSEO.js.map → chunk-FDKOUJKZ.js.map} +0 -0
  103. /package/dist/cli/{chunk-JVFEJAJX.js.map → chunk-GDKB2PPK.js.map} +0 -0
  104. /package/dist/cli/{chunk-NCBP5D6E.js.map → chunk-LBLR4CUZ.js.map} +0 -0
  105. /package/dist/cli/{chunk-7SGGXNB2.js.map → chunk-OJVITDGB.js.map} +0 -0
  106. /package/dist/cli/{chunk-SE7C5ZSI.js.map → chunk-QVDWH2A2.js.map} +0 -0
  107. /package/dist/cli/{chunk-WRONKNIH.js.map → chunk-QVUFWDD2.js.map} +0 -0
  108. /package/dist/cli/{chunk-3AAG2CUT.js.map → chunk-R6GQKKBW.js.map} +0 -0
  109. /package/dist/cli/{chunk-DHRVZJ2D.js.map → chunk-UDVFBEXC.js.map} +0 -0
  110. /package/dist/cli/{chunk-7YW6TPXK.js.map → chunk-VC2CQA5D.js.map} +0 -0
  111. /package/dist/cli/{chunk-M4E5JK6S.js.map → chunk-VJMBISEI.js.map} +0 -0
  112. /package/dist/cli/{chunk-TDSBASOF.js.map → chunk-VKYSZKH2.js.map} +0 -0
  113. /package/dist/cli/{chunk-7LOJS3LV.js.map → chunk-VMUUFWFF.js.map} +0 -0
  114. /package/dist/cli/{chunk-MIIZJD5O.js.map → chunk-VNQGCA3Q.js.map} +0 -0
  115. /package/dist/cli/{chunk-2AASOSD5.js.map → chunk-WF7TPVZM.js.map} +0 -0
  116. /package/dist/cli/{commands-OPT5AJNH.js.map → commands-RR3GIYOK.js.map} +0 -0
  117. /package/dist/cli/{commit-KA37H6GM.js.map → commit-FSHPIINM.js.map} +0 -0
  118. /package/dist/cli/{diff-SOIA7AKH.js.map → diff-RAAHHLHV.js.map} +0 -0
  119. /package/dist/cli/{doctor-RCUP4XRV.js.map → doctor-PKVQIXRT.js.map} +0 -0
  120. /package/dist/cli/{events-6KHITNX4.js.map → events-VRYXOSKI.js.map} +0 -0
  121. /package/dist/cli/{mcp-JP5OWD6R.js.map → mcp-CRJ26PP4.js.map} +0 -0
  122. /package/dist/cli/{mcp-browse-ONCJJPJN.js.map → mcp-browse-QPAOWZOP.js.map} +0 -0
  123. /package/dist/cli/{mcp-inspect-TPLHW5JA.js.map → mcp-inspect-CVCLABRS.js.map} +0 -0
  124. /package/dist/cli/{prompt-RJDNCQAP.js.map → prompt-SKYXERSI.js.map} +0 -0
  125. /package/dist/cli/{prune-sessions-MKEATRVL.js.map → prune-sessions-SEWX7GP6.js.map} +0 -0
  126. /package/dist/cli/{replay-4NILJG4U.js.map → replay-KPDW2ZMJ.js.map} +0 -0
  127. /package/dist/cli/{run-WFGXB4SB.js.map → run-WIKDIXTG.js.map} +0 -0
  128. /package/dist/cli/{sessions-5XDJDALO.js.map → sessions-2NULRMSA.js.map} +0 -0
  129. /package/dist/cli/{setup-F6XSWLRA.js.map → setup-Y5WDBQFL.js.map} +0 -0
  130. /package/dist/cli/{stats-ALHBZICE.js.map → stats-T7BL2YOR.js.map} +0 -0
  131. /package/dist/cli/{version-JVRAHBMM.js.map → version-3KWDNWLN.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-DHRVZJ2D.js";
6
+ } from "./chunk-UDVFBEXC.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-MIIZJD5O.js";
15
+ } from "./chunk-VNQGCA3Q.js";
16
16
  import {
17
17
  applyEdit,
18
18
  applyMultiEdit,
19
19
  pauseGate
20
- } from "./chunk-JVFEJAJX.js";
20
+ } from "./chunk-GDKB2PPK.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-TEUDEGX2.js";
27
+ } from "./chunk-FY4S7TJZ.js";
28
28
  import {
29
29
  formatHookOutcomeMessage,
30
30
  runHooks
31
- } from "./chunk-C72TNHDE.js";
31
+ } from "./chunk-BWYVFFKR.js";
32
32
  import {
33
33
  ignoredByLayers,
34
34
  loadGitignoreAt,
@@ -41,25 +41,26 @@ import {
41
41
  loadSessionMeta,
42
42
  rewriteSession,
43
43
  timestampSuffix
44
- } from "./chunk-E7TAHQ4A.js";
44
+ } from "./chunk-RRXUIPWG.js";
45
45
  import {
46
46
  DEEPSEEK_CONTEXT_TOKENS,
47
47
  DEFAULT_CONTEXT_TOKENS,
48
48
  SessionStats
49
- } from "./chunk-M4E5JK6S.js";
49
+ } from "./chunk-VJMBISEI.js";
50
50
  import {
51
51
  t
52
- } from "./chunk-6VANO7KB.js";
52
+ } from "./chunk-KDRUEXII.js";
53
53
  import {
54
54
  DEFAULT_INDEX_EXCLUDES,
55
55
  addProjectPathAllowed,
56
56
  loadMemoryTypeRegistry,
57
57
  loadMetasoApiKey,
58
58
  loadProjectPathAllowed,
59
+ loadTavilyApiKey,
59
60
  require_picomatch,
60
61
  webSearchEndpoint,
61
62
  webSearchEngine
62
- } from "./chunk-DGA5QYFM.js";
63
+ } from "./chunk-24A7FHGJ.js";
63
64
  import {
64
65
  __commonJS,
65
66
  __esm,
@@ -6124,11 +6125,42 @@ async function bridgeMcpTools(client, opts = {}) {
6124
6125
  return { ...result, env };
6125
6126
  }
6126
6127
  function flattenMcpResult(result, opts = {}) {
6128
+ validateResultShape(result);
6127
6129
  const parts = result.content.map(blockToString);
6128
6130
  const joined = parts.join("\n").trim();
6129
6131
  const prefixed = result.isError ? `ERROR: ${joined || "(no error message from server)"}` : joined;
6130
6132
  return opts.maxChars ? truncateForModel(prefixed, opts.maxChars) : prefixed;
6131
6133
  }
6134
+ function validateResultShape(result) {
6135
+ if (typeof result !== "object" || !result)
6136
+ throw new Error(`MCP server returned non-object result: ${typeof result}`);
6137
+ const { content, isError: _isError } = result;
6138
+ if (!Array.isArray(content))
6139
+ throw new Error(`MCP server returned result with non-array content: ${typeof content}`);
6140
+ for (let i = 0; i < content.length; i++) {
6141
+ const block = content[i];
6142
+ if (typeof block !== "object" || !block)
6143
+ throw new Error(`MCP server returned result.content[${i}] is not an object`);
6144
+ if (block.type !== "text" && block.type !== "image")
6145
+ throw new Error(
6146
+ `MCP server returned result.content[${i}] with unknown type ${JSON.stringify(block.type)}`
6147
+ );
6148
+ if (block.type === "text" && typeof block.text !== "string")
6149
+ throw new Error(
6150
+ `MCP server returned result.content[${i}] with non-string text (${typeof block.text})`
6151
+ );
6152
+ if (block.type === "image") {
6153
+ if (typeof block.data !== "string")
6154
+ throw new Error(
6155
+ `MCP server returned result.content[${i}] with non-string data (${typeof block.data})`
6156
+ );
6157
+ if (typeof block.mimeType !== "string")
6158
+ throw new Error(
6159
+ `MCP server returned result.content[${i}] with non-string mimeType (${typeof block.mimeType})`
6160
+ );
6161
+ }
6162
+ }
6163
+ }
6132
6164
  function truncateForModel(s, maxChars) {
6133
6165
  if (s.length <= maxChars) return s;
6134
6166
  const tailBudget = Math.min(1024, Math.floor(maxChars * 0.1));
@@ -6394,6 +6426,12 @@ var ToolRegistry = class {
6394
6426
  });
6395
6427
  }
6396
6428
  }
6429
+ if (opts.signal?.aborted) {
6430
+ return JSON.stringify({
6431
+ error: `${name}: aborted before dispatch (user interrupt)`,
6432
+ rejectedReason: "aborted"
6433
+ });
6434
+ }
6397
6435
  let finalResult;
6398
6436
  try {
6399
6437
  try {
@@ -7877,6 +7915,32 @@ ${reason}`
7877
7915
  }
7878
7916
  return userText;
7879
7917
  }
7918
+ /** Rewind to the N-th user turn (0-indexed). Drops that turn + everything after. */
7919
+ rewindToUserTurn(userTurnIndex) {
7920
+ const entries = this.log.entries;
7921
+ let count = 0;
7922
+ let targetIdx = -1;
7923
+ for (let i = 0; i < entries.length; i++) {
7924
+ if (entries[i].role !== "user") continue;
7925
+ if (count === userTurnIndex) {
7926
+ targetIdx = i;
7927
+ break;
7928
+ }
7929
+ count++;
7930
+ }
7931
+ if (targetIdx < 0) return null;
7932
+ const raw = entries[targetIdx].content;
7933
+ const userText = typeof raw === "string" ? raw : "";
7934
+ const preserved = entries.slice(0, targetIdx).map((m) => ({ ...m }));
7935
+ this.log.compactInPlace(preserved);
7936
+ if (this.sessionName) {
7937
+ try {
7938
+ rewriteSession(this.sessionName, preserved);
7939
+ } catch {
7940
+ }
7941
+ }
7942
+ return userText;
7943
+ }
7880
7944
  async *step(userInput) {
7881
7945
  this._steerConsumed = false;
7882
7946
  if (this.budgetUsd !== null) {
@@ -9157,6 +9221,7 @@ var FETCH_MAX_BYTES = 10 * 1024 * 1024;
9157
9221
  var USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
9158
9222
  var MOJEEK_ENDPOINT = "https://www.mojeek.com/search";
9159
9223
  var METASO_ENDPOINT = "https://metaso.cn/api/v1";
9224
+ var TAVILY_ENDPOINT = "https://api.tavily.com/search";
9160
9225
  function searchStatusError(status) {
9161
9226
  if (status === 429) return t("webErrors.rateLimit429");
9162
9227
  if (status === 403) return t("webErrors.forbidden403");
@@ -9176,6 +9241,9 @@ async function webSearch(query, opts = {}) {
9176
9241
  if (opts.engine === "searxng") {
9177
9242
  return searchSearxng(query, opts);
9178
9243
  }
9244
+ if (opts.engine === "tavily") {
9245
+ return searchTavily(query, opts);
9246
+ }
9179
9247
  return searchMojeek(query, opts);
9180
9248
  }
9181
9249
  async function searchMojeek(query, opts = {}) {
@@ -9310,6 +9378,55 @@ async function searchMetaso(query, opts = {}) {
9310
9378
  snippet: wp.snippet ?? wp.summary ?? ""
9311
9379
  }));
9312
9380
  }
9381
+ async function searchTavily(query, opts = {}) {
9382
+ const topK = Math.max(1, Math.min(20, opts.topK ?? DEFAULT_TOPK));
9383
+ const apiKey = loadTavilyApiKey();
9384
+ if (!apiKey) throw new Error(t("webErrors.tavilyMissingKey"));
9385
+ let resp;
9386
+ try {
9387
+ resp = await fetch(TAVILY_ENDPOINT, {
9388
+ method: "POST",
9389
+ headers: {
9390
+ "Content-Type": "application/json",
9391
+ Accept: "application/json"
9392
+ },
9393
+ body: JSON.stringify({
9394
+ api_key: apiKey,
9395
+ query,
9396
+ search_depth: "basic",
9397
+ max_results: topK,
9398
+ include_answer: false,
9399
+ include_raw_content: false,
9400
+ include_images: false
9401
+ }),
9402
+ signal: opts.signal
9403
+ });
9404
+ } catch (err) {
9405
+ if (err instanceof TypeError && err.message.includes("fetch")) {
9406
+ throw new Error(t("webErrors.cannotReach", { endpoint: TAVILY_ENDPOINT }));
9407
+ }
9408
+ throw err;
9409
+ }
9410
+ if (!resp.ok) {
9411
+ if (resp.status === 401 || resp.status === 403) {
9412
+ throw new Error(t("webErrors.tavilyUnauthorized"));
9413
+ }
9414
+ if (resp.status === 429) throw new Error(t("webErrors.tavilyRateLimit"));
9415
+ throw new Error(t("webErrors.tavilyServerError", { status: resp.status }));
9416
+ }
9417
+ let data;
9418
+ try {
9419
+ data = await resp.json();
9420
+ } catch {
9421
+ throw new Error(t("webErrors.tavilyParseError", { status: resp.status }));
9422
+ }
9423
+ const results = data.results ?? [];
9424
+ return results.slice(0, topK).map((r) => ({
9425
+ title: r.title,
9426
+ url: r.url,
9427
+ snippet: r.content ?? ""
9428
+ }));
9429
+ }
9313
9430
  function parseSearxngHtmlResults(html) {
9314
9431
  const root = (0, import_node_html_parser.parse)(html);
9315
9432
  const results = [];
@@ -9528,7 +9645,7 @@ function registerWebTools(registry, opts = {}) {
9528
9645
  const maxFetchChars = opts.maxFetchChars ?? DEFAULT_FETCH_MAX_CHARS;
9529
9646
  registry.register({
9530
9647
  name: "web_search",
9531
- 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.",
9648
+ 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.",
9532
9649
  readOnly: true,
9533
9650
  parallelSafe: true,
9534
9651
  parameters: {
@@ -9600,13 +9717,13 @@ import * as pathMod4 from "path";
9600
9717
  // src/memory/subdir.ts
9601
9718
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
9602
9719
  import { dirname, join as join2, relative as relative2, resolve as resolve2 } from "path";
9603
- function findSubdirMemoryAncestors(absPath, rootDir) {
9720
+ function findDirMemory(absDir, rootDir) {
9604
9721
  const root = resolve2(rootDir);
9605
- const target = resolve2(absPath);
9722
+ const target = resolve2(absDir);
9606
9723
  const rel = relative2(root, target);
9607
- if (!rel || rel.startsWith("..")) return [];
9724
+ if (rel.startsWith("..")) return [];
9608
9725
  const found = [];
9609
- let cur = dirname(target);
9726
+ let cur = target;
9610
9727
  while (cur !== root) {
9611
9728
  const r = relative2(root, cur);
9612
9729
  if (!r || r.startsWith("..")) break;
@@ -9623,6 +9740,9 @@ function findSubdirMemoryAncestors(absPath, rootDir) {
9623
9740
  }
9624
9741
  return found;
9625
9742
  }
9743
+ function findSubdirMemoryAncestors(absPath, rootDir) {
9744
+ return findDirMemory(dirname(resolve2(absPath)), rootDir);
9745
+ }
9626
9746
  function readSubdirMemoryContent(path) {
9627
9747
  let raw;
9628
9748
  try {
@@ -9891,6 +10011,129 @@ function formatOutline(entries) {
9891
10011
  // src/tools/fs/search.ts
9892
10012
  import { promises as fs2 } from "fs";
9893
10013
  import * as pathMod3 from "path";
10014
+
10015
+ // src/tools/fs/regex-runner.ts
10016
+ import { Worker } from "worker_threads";
10017
+ var WORKER_SOURCE = `
10018
+ const { parentPort } = require("node:worker_threads");
10019
+ parentPort.on("message", (msg) => {
10020
+ const { id, text, source, flags } = msg;
10021
+ let re;
10022
+ try {
10023
+ re = new RegExp(source, flags);
10024
+ } catch (err) {
10025
+ parentPort.postMessage({ id, error: (err && err.message) ? err.message : String(err) });
10026
+ return;
10027
+ }
10028
+ const lines = text.split(/\\r?\\n/);
10029
+ const hits = [];
10030
+ for (let i = 0; i < lines.length; i++) {
10031
+ if (re.test(lines[i])) hits.push(i);
10032
+ }
10033
+ parentPort.postMessage({ id, hits });
10034
+ });
10035
+ `;
10036
+ var DEFAULT_TIMEOUT_MS = 6e4;
10037
+ var RegexRunner = class {
10038
+ worker = null;
10039
+ pending = /* @__PURE__ */ new Map();
10040
+ nextId = 1;
10041
+ defaultTimeoutMs;
10042
+ constructor(opts = {}) {
10043
+ this.defaultTimeoutMs = opts.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
10044
+ }
10045
+ testLines(text, source, flags, opts = {}) {
10046
+ return new Promise((resolve5, reject) => {
10047
+ if (opts.signal?.aborted) {
10048
+ reject(new Error("regex evaluation aborted"));
10049
+ return;
10050
+ }
10051
+ if (!this.worker) this.worker = this.spawn();
10052
+ const id = this.nextId++;
10053
+ const timeoutMs = opts.timeoutMs ?? this.defaultTimeoutMs;
10054
+ const timer = setTimeout(() => {
10055
+ this.pending.delete(id);
10056
+ this.killWorker();
10057
+ reject(new Error(`regex evaluation exceeded ${timeoutMs}ms`));
10058
+ }, timeoutMs);
10059
+ const entry = { resolve: resolve5, reject, timer };
10060
+ if (opts.signal) {
10061
+ entry.signal = opts.signal;
10062
+ entry.onAbort = () => {
10063
+ this.pending.delete(id);
10064
+ clearTimeout(timer);
10065
+ this.killWorker();
10066
+ reject(new Error("regex evaluation aborted"));
10067
+ };
10068
+ opts.signal.addEventListener("abort", entry.onAbort, { once: true });
10069
+ }
10070
+ this.pending.set(id, entry);
10071
+ this.worker.postMessage({ id, text, source, flags });
10072
+ });
10073
+ }
10074
+ async shutdown() {
10075
+ if (this.worker) {
10076
+ const w = this.worker;
10077
+ this.worker = null;
10078
+ await w.terminate();
10079
+ }
10080
+ for (const entry of this.pending.values()) {
10081
+ clearTimeout(entry.timer);
10082
+ if (entry.onAbort && entry.signal) {
10083
+ entry.signal.removeEventListener("abort", entry.onAbort);
10084
+ }
10085
+ entry.reject(new Error("regex runner shut down"));
10086
+ }
10087
+ this.pending.clear();
10088
+ }
10089
+ spawn() {
10090
+ const w = new Worker(WORKER_SOURCE, { eval: true });
10091
+ w.on("message", (msg) => {
10092
+ const entry = this.pending.get(msg.id);
10093
+ if (!entry) return;
10094
+ clearTimeout(entry.timer);
10095
+ if (entry.onAbort && entry.signal) {
10096
+ entry.signal.removeEventListener("abort", entry.onAbort);
10097
+ }
10098
+ this.pending.delete(msg.id);
10099
+ if (msg.error !== void 0) entry.reject(new Error(msg.error));
10100
+ else entry.resolve(msg.hits ?? []);
10101
+ });
10102
+ w.on("error", (err) => {
10103
+ if (this.worker !== w) return;
10104
+ this.failPending(err);
10105
+ });
10106
+ w.on("exit", () => {
10107
+ if (this.worker !== w) return;
10108
+ this.worker = null;
10109
+ if (this.pending.size > 0) this.failPending(new Error("regex worker exited"));
10110
+ });
10111
+ return w;
10112
+ }
10113
+ killWorker() {
10114
+ if (!this.worker) return;
10115
+ const w = this.worker;
10116
+ this.worker = null;
10117
+ void w.terminate();
10118
+ }
10119
+ failPending(err) {
10120
+ for (const entry of this.pending.values()) {
10121
+ clearTimeout(entry.timer);
10122
+ if (entry.onAbort && entry.signal) {
10123
+ entry.signal.removeEventListener("abort", entry.onAbort);
10124
+ }
10125
+ entry.reject(err);
10126
+ }
10127
+ this.pending.clear();
10128
+ }
10129
+ };
10130
+ var _runner = null;
10131
+ function getRegexRunner() {
10132
+ if (!_runner) _runner = new RegexRunner();
10133
+ return _runner;
10134
+ }
10135
+
10136
+ // src/tools/fs/search.ts
9894
10137
  function throwIfAborted(signal) {
9895
10138
  if (!signal?.aborted) return;
9896
10139
  throw new DOMException("search aborted by user", "AbortError");
@@ -9943,17 +10186,20 @@ async function searchFiles(ctx, startAbs, args) {
9943
10186
  }
9944
10187
  var MAX_HITS_PER_FILE = 30;
9945
10188
  var SUMMARY_MODE_TRIGGER_RATIO = 0.8;
10189
+ var WALK_DEADLINE_MS = 12e4;
9946
10190
  async function searchContent(ctx, startAbs, args) {
9947
10191
  throwIfAborted(args.signal);
9948
10192
  const caseSensitive = args.case_sensitive === true;
9949
10193
  const includeDeps = args.include_deps === true;
9950
10194
  const ctxLines = Math.max(0, Math.min(20, Math.floor(args.context ?? 0)));
9951
10195
  const summaryOnly = args.summary_only === true;
9952
- let re = null;
10196
+ const reFlags = caseSensitive ? "" : "i";
10197
+ let reSource = null;
9953
10198
  try {
9954
- re = new RegExp(args.pattern, caseSensitive ? "" : "i");
10199
+ new RegExp(args.pattern, reFlags);
10200
+ reSource = args.pattern;
9955
10201
  } catch {
9956
- re = null;
10202
+ reSource = null;
9957
10203
  }
9958
10204
  const needle = caseSensitive ? args.pattern : args.pattern.toLowerCase();
9959
10205
  const matches = [];
@@ -9963,6 +10209,15 @@ async function searchContent(ctx, startAbs, args) {
9963
10209
  let summaryMode = summaryOnly;
9964
10210
  let summaryNoticeEmitted = false;
9965
10211
  const fileHitCounts = /* @__PURE__ */ new Map();
10212
+ const regexSkippedFiles = [];
10213
+ const t0 = Date.now();
10214
+ const throwIfTimedOut = () => {
10215
+ if (Date.now() - t0 > WALK_DEADLINE_MS) {
10216
+ throw new Error(
10217
+ `search_content exceeded ${WALK_DEADLINE_MS}ms \u2014 narrow the scope (path/glob) or simplify the pattern`
10218
+ );
10219
+ }
10220
+ };
9966
10221
  const pushLine = (out) => {
9967
10222
  if (totalBytes + out.length + 1 > ctx.maxListBytes) {
9968
10223
  matches.push(`[\u2026 truncated at ${ctx.maxListBytes} bytes \u2014 refine pattern or path \u2026]`);
@@ -9997,6 +10252,7 @@ async function searchContent(ctx, startAbs, args) {
9997
10252
  for (const e of entries) {
9998
10253
  if (truncated) return;
9999
10254
  throwIfAborted(args.signal);
10255
+ throwIfTimedOut();
10000
10256
  if (e.isDirectory()) {
10001
10257
  if (!includeDeps && ctx.skipDirNames.has(e.name)) continue;
10002
10258
  await walk2(pathMod3.join(dir, e.name));
@@ -10033,13 +10289,25 @@ async function searchContent(ctx, startAbs, args) {
10033
10289
  const text = raw.toString("utf8");
10034
10290
  const rel = displayRel2(ctx.rootDir, full);
10035
10291
  const lines = text.split(/\r?\n/);
10036
- const hits = [];
10037
- for (let li = 0; li < lines.length; li++) {
10038
- throwIfAborted(args.signal);
10039
- const line = lines[li];
10040
- const lineForCheck = caseSensitive ? line : line.toLowerCase();
10041
- const hit = re ? re.test(line) : lineForCheck.includes(needle);
10042
- if (hit) hits.push(li);
10292
+ let hits;
10293
+ if (reSource !== null) {
10294
+ try {
10295
+ hits = await getRegexRunner().testLines(text, reSource, reFlags, {
10296
+ signal: args.signal
10297
+ });
10298
+ } catch (err) {
10299
+ const reason = err.message;
10300
+ if (reason.includes("aborted")) throw err;
10301
+ regexSkippedFiles.push({ rel, reason });
10302
+ continue;
10303
+ }
10304
+ } else {
10305
+ hits = [];
10306
+ for (let li = 0; li < lines.length; li++) {
10307
+ throwIfAborted(args.signal);
10308
+ const lineForCheck = caseSensitive ? lines[li] : lines[li].toLowerCase();
10309
+ if (lineForCheck.includes(needle)) hits.push(li);
10310
+ }
10043
10311
  }
10044
10312
  scanned++;
10045
10313
  if (hits.length === 0) continue;
@@ -10088,6 +10356,11 @@ async function searchContent(ctx, startAbs, args) {
10088
10356
  }
10089
10357
  };
10090
10358
  await walk2(startAbs);
10359
+ if (regexSkippedFiles.length > 0) {
10360
+ pushLine(
10361
+ `[regex timed out on ${regexSkippedFiles.length} file${regexSkippedFiles.length === 1 ? "" : "s"} \u2014 pattern may have catastrophic backtracking; first: ${regexSkippedFiles[0].rel}]`
10362
+ );
10363
+ }
10091
10364
  if (matches.length === 0) {
10092
10365
  return scanned === 0 ? "(no files scanned \u2014 path empty or all files filtered out)" : `(no matches across ${scanned} file${scanned === 1 ? "" : "s"})`;
10093
10366
  }
@@ -10152,11 +10425,15 @@ function registerFilesystemTools(registry, opts) {
10152
10425
  const sessionApproved = /* @__PURE__ */ new Set();
10153
10426
  const shownSubdirMemory = /* @__PURE__ */ new Set();
10154
10427
  function withSubdirMemory(absPath, body) {
10155
- if (!memoryEnabled()) return body;
10156
- const ancestors = findSubdirMemoryAncestors(absPath, rootDir);
10157
- if (ancestors.length === 0) return body;
10428
+ return prependMemorySections(findSubdirMemoryAncestors(absPath, rootDir), body);
10429
+ }
10430
+ function withDirMemory(absDir, body) {
10431
+ return prependMemorySections(findDirMemory(absDir, rootDir), body);
10432
+ }
10433
+ function prependMemorySections(memPaths, body) {
10434
+ if (!memoryEnabled() || memPaths.length === 0) return body;
10158
10435
  const sections = [];
10159
- for (const memPath of [...ancestors].reverse()) {
10436
+ for (const memPath of [...memPaths].reverse()) {
10160
10437
  if (shownSubdirMemory.has(memPath)) continue;
10161
10438
  const content = readSubdirMemoryContent(memPath);
10162
10439
  if (!content) continue;
@@ -10349,7 +10626,7 @@ ${slice.join("\n")}`);
10349
10626
  for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
10350
10627
  lines.push(e.isDirectory() ? `${e.name}/` : e.name);
10351
10628
  }
10352
- return lines.join("\n") || "(empty directory)";
10629
+ return withDirMemory(abs, lines.join("\n") || "(empty directory)");
10353
10630
  }
10354
10631
  });
10355
10632
  registry.register({
@@ -11571,4 +11848,4 @@ export {
11571
11848
  he/he.js:
11572
11849
  (*! https://mths.be/he v1.2.0 by @mathias | MIT license *)
11573
11850
  */
11574
- //# sourceMappingURL=chunk-ZOQHVQON.js.map
11851
+ //# sourceMappingURL=chunk-ICAFSZHS.js.map