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
package/dist/index.js CHANGED
@@ -472,6 +472,12 @@ function loadMetasoApiKey(path2 = defaultConfigPath()) {
472
472
  if (cfg && typeof cfg === "string" && cfg.trim()) return cfg.trim();
473
473
  return DEFAULT_METASO_API_KEY;
474
474
  }
475
+ function loadTavilyApiKey(path2 = defaultConfigPath()) {
476
+ if (process.env.TAVILY_API_KEY) return process.env.TAVILY_API_KEY.trim();
477
+ const cfg = readConfig(path2).tavilyApiKey;
478
+ if (cfg && typeof cfg === "string" && cfg.trim()) return cfg.trim();
479
+ return void 0;
480
+ }
475
481
  function defaultConfigPath() {
476
482
  return join(homedir(), ".reasonix", "config.json");
477
483
  }
@@ -1191,8 +1197,9 @@ var EN = {
1191
1197
  { key: "wheel", text: "scrolls chat history (works on web/cloud/SSH terminals too)" },
1192
1198
  {
1193
1199
  key: "\u2191 / \u2193",
1194
- text: "scroll chat \xB7 use Ctrl+P / Ctrl+N for prompt history + multi-line cursor"
1195
- }
1200
+ text: "prompt history (or per-line cursor in a multi-line draft) \u2014 Ctrl+P / Ctrl+N alias"
1201
+ },
1202
+ { key: "PgUp / PgDn", text: "scroll chat history (mouse wheel routes here too)" }
1196
1203
  ]
1197
1204
  }
1198
1205
  ],
@@ -1206,11 +1213,11 @@ var EN = {
1206
1213
  rows: [
1207
1214
  { key: "Enter", text: "submit the prompt" },
1208
1215
  { key: "Shift+Enter", text: "insert a newline in the prompt" },
1209
- { key: "\u2191 / \u2193", text: "scroll chat history (mouse wheel routes here too)" },
1210
1216
  {
1211
- key: "Ctrl+P / Ctrl+N",
1217
+ key: "\u2191 / \u2193",
1212
1218
  text: "previous / next prompt history \xB7 cursor up / down in a multi-line draft"
1213
1219
  },
1220
+ { key: "Ctrl+P / Ctrl+N", text: "readline alias for \u2191 / \u2193" },
1214
1221
  { key: "Ctrl+A / Ctrl+E", text: "jump to start / end of the current line" },
1215
1222
  { key: "Ctrl+W", text: "delete the word before the cursor" },
1216
1223
  { key: "Ctrl+U", text: "clear the entire prompt buffer" },
@@ -1219,7 +1226,11 @@ var EN = {
1219
1226
  { key: "Esc", text: "dismiss picker \xB7 abort the running model turn" },
1220
1227
  { key: "Ctrl+C", text: "abort the running model turn (NOT copy \u2014 see clipboard)" },
1221
1228
  { key: "PgUp / PgDn", text: "scroll chat history a page at a time" },
1222
- { key: "End", text: "jump chat to the most recent line" }
1229
+ { key: "End", text: "jump chat to the most recent line" },
1230
+ {
1231
+ key: "Ctrl+R",
1232
+ text: "toggle verbose mode \u2014 full reasoning + tool output, no head/tail elision"
1233
+ }
1223
1234
  ]
1224
1235
  },
1225
1236
  {
@@ -1258,7 +1269,7 @@ var EN = {
1258
1269
  ]
1259
1270
  }
1260
1271
  ],
1261
- footer: "Wheel\u2192\u2191/\u2193 via DECSET 1007 (alternate-scroll) \u2014 wheel scrolls chat on most terminals (web/cloud/SSH included) without disturbing native selection. Drag to select stays modifier-free. Pass --no-mouse to opt out."
1272
+ footer: "Wheel scrolls chat on most terminals (web/cloud/SSH included) \u2014 SGR mouse tracking is on by default and stays out of the way of native drag-select and right-click. Pass --no-mouse to opt out."
1262
1273
  },
1263
1274
  tipShownOnce: "shown once",
1264
1275
  modelOverride: "override the default model",
@@ -1413,7 +1424,7 @@ var EN = {
1413
1424
  sessions: { description: "list saved sessions (current marked with \u25B8)" },
1414
1425
  title: { description: "ask the model to rename this session from the conversation" },
1415
1426
  qq: {
1416
- description: "connect, inspect, or disconnect the QQ channel for this session",
1427
+ description: "connect, inspect, or disconnect the QQ channel for this session (first connect guides App ID / App Secret setup)",
1417
1428
  argsHint: "[connect [appId appSecret [sandbox]]|status|disconnect]"
1418
1429
  },
1419
1430
  setup: { description: "reminds you to exit and run `reasonix setup`" },
@@ -1637,6 +1648,8 @@ var EN = {
1637
1648
  notedVerbCreated: "created",
1638
1649
  notedVerbAppended: "appended to",
1639
1650
  memoryWriteFailed: "# memory write failed",
1651
+ verboseOn: "\u25B8 verbose mode on \u2014 full reasoning + tool output",
1652
+ verboseOff: "\u25B8 verbose mode off \u2014 head/tail elision restored",
1640
1653
  commandFailed: "! command failed",
1641
1654
  btwUsage: "\u25B8 /btw <question> \u2014 ask a side question without polluting the conversation context.",
1642
1655
  btwHeader: "\u226B btw",
@@ -1771,6 +1784,48 @@ var EN = {
1771
1784
  titleStarted: "\u25B8 naming session\u2026",
1772
1785
  titleFailed: "\u25B8 session title failed: {reason}"
1773
1786
  },
1787
+ qq: {
1788
+ unavailable: "/qq is not available in this session.",
1789
+ connecting: "QQ: connecting\u2026",
1790
+ connectFailed: "QQ connect failed: {reason}",
1791
+ disconnecting: "QQ: disconnecting\u2026",
1792
+ disconnectFailed: "QQ disconnect failed: {reason}",
1793
+ usage: "Usage: /qq connect [appId appSecret [sandbox]] | /qq status | /qq disconnect",
1794
+ promptAppId: "QQ setup: enter your QQ Open Platform App ID, then press Enter. Type /cancel to abort.",
1795
+ promptAppSecret: "QQ setup: enter your QQ Open Platform App Secret, then press Enter. Type /cancel to abort.",
1796
+ setupWaitingAppId: "waiting for App ID",
1797
+ setupWaitingAppSecret: "waiting for App Secret",
1798
+ setupCancelled: "QQ setup cancelled.",
1799
+ credentialsRequired: "QQ App ID and App Secret are required.",
1800
+ connected: "QQ connected in {mode} mode. It will auto-start on future launches.",
1801
+ alreadyConnected: "QQ is already connected in {mode} mode. Auto-start is enabled.",
1802
+ disconnected: "QQ disconnected. Auto-start is disabled.",
1803
+ status: "QQ: {connected}, auto-start {enabled}, credentials {configured}, appId {appId}, {sandbox}, access {access}, current mode {mode}.",
1804
+ statusSetup: "QQ: setup in progress \u2014 {step}",
1805
+ stateConnected: "connected",
1806
+ stateDisconnected: "disconnected",
1807
+ stateEnabled: "enabled",
1808
+ stateDisabled: "disabled",
1809
+ stateConfigured: "configured",
1810
+ stateNotConfigured: "not configured",
1811
+ sandbox: "sandbox",
1812
+ production: "production",
1813
+ none: "none",
1814
+ modeChat: "chat",
1815
+ modeCode: "code",
1816
+ accessOwner: "owner {owner}",
1817
+ accessOwnerWithAllowlist: "owner {owner}, allowlist {count}",
1818
+ accessAllowlist: "allowlist {count}",
1819
+ accessRuntime: "first-sender (runtime only, {owner})",
1820
+ accessOpen: "open (unbound)",
1821
+ lockAlreadyRunning: "QQ channel is already running in process {pid}. Stop that process before starting another QQ channel.",
1822
+ unauthorizedMessage: "QQ ignored message from unauthorized openid {openid}. Current access: {access}.",
1823
+ runtimeBound: "QQ temporarily bound this run to first sender {openid}. Set `qq.ownerOpenId` in config to persist access.",
1824
+ missingAppId: "QQ App ID is required. Run `/qq connect` to configure.",
1825
+ missingAppSecret: "QQ App Secret is required. Run `/qq connect` to configure.",
1826
+ authFailed: "QQ bot authentication failed \u2014 check your App ID and App Secret.",
1827
+ readyTimeout: "QQ bot did not receive READY within 15s \u2014 check your App ID and App Secret."
1828
+ },
1774
1829
  admin: {
1775
1830
  doctorNeedsTui: "/doctor needs a TUI context (postDoctor wired).",
1776
1831
  doctorRunning: "\u2695 Doctor \u2014 running health checks\u2026",
@@ -2037,12 +2092,14 @@ var EN = {
2037
2092
  usageSearxng: " /search-engine searxng use SearXNG at default endpoint",
2038
2093
  usageSearxngUrl: " /search-engine searxng <url> use SearXNG at custom endpoint",
2039
2094
  usageMetaso: " /search-engine metaso use Metaso API (100/d free, configure your own API key for more)",
2095
+ usageTavily: " /search-engine tavily use Tavily API (LLM-friendly, free 1000/mo \u2014 set TAVILY_API_KEY or tavilyApiKey in config; get one at https://tavily.com)",
2040
2096
  alias: "Alias: /se",
2041
2097
  searxngInfo: "SearXNG is a self-hosted metasearch engine (https://github.com/searxng/searxng).",
2042
2098
  searxngInstall: "Install it with: docker run -d -p 8080:8080 searxng/searxng",
2043
2099
  switched: 'Switched web search engine to "{engine}".{note}',
2044
2100
  switchedSearxngNote: " Make sure SearXNG is running at {endpoint}.",
2045
2101
  switchedMetasoNote: " There is a daily quota of 100 (configure your own API key for higher limits).",
2102
+ switchedTavilyNote: " Set TAVILY_API_KEY or `tavilyApiKey` in config; free 1000/mo at https://tavily.com.",
2046
2103
  confirmed: '\u2713 Web search engine set to "{engine}"{detail}. Next assistant turn will pick up the change.',
2047
2104
  confirmedDetail: " ({endpoint})"
2048
2105
  },
@@ -2178,6 +2235,13 @@ var EN = {
2178
2235
  linesBelow: " \u2193 {count} line below (\u2193/j or Space/PgDn)",
2179
2236
  linesBelowPlural: " \u2193 {count} lines below (\u2193/j or Space/PgDn)"
2180
2237
  },
2238
+ editPicker: {
2239
+ title: "edit a previous message",
2240
+ hint: "\u2191\u2193 pick \xB7 Enter to load into composer \xB7 Esc to cancel",
2241
+ empty: "no user turns yet \u2014 nothing to edit",
2242
+ dismiss: "Esc to dismiss",
2243
+ forked: "\u25B8 forked at turn #{turn} \u2014 buffer holds the original text"
2244
+ },
2181
2245
  sessionPicker: {
2182
2246
  header: " \u25C8 REASONIX \xB7 pick a session ",
2183
2247
  title: "pick a session \u2014 {workspace}",
@@ -2311,6 +2375,11 @@ var EN = {
2311
2375
  metasoServerError: "web_search: Metaso server error ({status}) \u2014 try again later, or switch engine with /search-engine mojeek",
2312
2376
  metasoParseError: "web_search: Metaso returned unparseable response (HTTP {status}) \u2014 try again later",
2313
2377
  metasoApiError: "web_search: Metaso API error (code {code}: {message}) \u2014 try again later",
2378
+ tavilyMissingKey: "web_search: Tavily backend requires an API key \u2014 set TAVILY_API_KEY env var or `tavilyApiKey` in ~/.reasonix/config.json; free 1000/mo signup at https://tavily.com",
2379
+ tavilyUnauthorized: "web_search: Tavily API key rejected \u2014 check TAVILY_API_KEY or get one at https://tavily.com",
2380
+ tavilyRateLimit: "web_search: Tavily rate-limited or monthly quota exceeded \u2014 wait, switch engine with /search-engine mojeek, or upgrade your Tavily plan",
2381
+ tavilyServerError: "web_search: Tavily server error ({status}) \u2014 try again later, or switch engine with /search-engine mojeek",
2382
+ tavilyParseError: "web_search: Tavily returned unparseable response (HTTP {status}) \u2014 try again later",
2314
2383
  fetchStatus: "web_fetch {status} for {url} \u2014 try: confirm the URL resolves in a browser; status suggests the host returned an error page",
2315
2384
  fetchRateLimit429: "web_fetch 429 for {url} \u2014 try: wait 10s before retrying; the host is rate-limiting this client",
2316
2385
  fetchForbidden403: "web_fetch 403 for {url} \u2014 try: the host is blocking this client; the page may require login or block bots \u2014 use web_search snippets instead",
@@ -2429,7 +2498,8 @@ var EN = {
2429
2498
  scrollAbove: " \u2191 {scroll} / {max} row above",
2430
2499
  scrollAbovePlural: " \u2191 {scroll} / {max} rows above",
2431
2500
  scrollMore: " \u2014 {remaining} more",
2432
- scrollPgUp: " \xB7 PgUp / wheel / \u2191"
2501
+ scrollPgUp: " \xB7 PgUp / wheel",
2502
+ scrollCopy: " \xB7 /copy enters copy mode"
2433
2503
  },
2434
2504
  slashArgPicker: {
2435
2505
  noMatch: 'no match for "{partial}"',
@@ -2487,7 +2557,8 @@ var EN = {
2487
2557
  reconnectDetail: "tearing down \xB7 re-handshake \xB7 listing tools",
2488
2558
  disabledDetail: "via /mcp disable {name}",
2489
2559
  failedSetupHint: "\u2192 run `reasonix setup` to remove this entry, or fix the underlying issue (missing npm package, network, etc.).",
2490
- failedSetupConfigHint: "\u2192 run `reasonix setup` to remove broken entries from your saved config."
2560
+ failedSetupConfigHint: "\u2192 run `reasonix setup` to remove broken entries from your saved config.",
2561
+ abortedHint: "MCP startup aborted \u2014 {count} server(s) skipped. Run /mcp to retry once you've fixed the underlying issue."
2491
2562
  },
2492
2563
  checkpointPicker: {
2493
2564
  title: "restore a checkpoint \u2014 {workspace}",
@@ -2632,8 +2703,9 @@ var zhCN = {
2632
2703
  { key: "\u6EDA\u8F6E", text: "\u6EDA\u52A8\u804A\u5929\u8BB0\u5F55\uFF08Web / \u4E91\u7AEF / SSH \u7EC8\u7AEF\u4E5F\u80FD\u7528\uFF09" },
2633
2704
  {
2634
2705
  key: "\u2191 / \u2193",
2635
- text: "\u6EDA\u52A8\u804A\u5929 \xB7 \u8F93\u5165\u6846\u5386\u53F2 + \u591A\u884C\u5149\u6807\u7528 Ctrl+P / Ctrl+N"
2636
- }
2706
+ text: "\u8F93\u5165\u5386\u53F2\uFF08\u591A\u884C\u8349\u7A3F\u65F6\u6309\u884C\u79FB\u52A8\u5149\u6807\uFF09\u2014 Ctrl+P / Ctrl+N \u540C\u4E49"
2707
+ },
2708
+ { key: "PgUp / PgDn", text: "\u6EDA\u52A8\u804A\u5929\u8BB0\u5F55\uFF08\u9F20\u6807\u6EDA\u8F6E\u4E5F\u8D70\u8FD9\u6761\u8DEF\u5F84\uFF09" }
2637
2709
  ]
2638
2710
  }
2639
2711
  ],
@@ -2647,11 +2719,11 @@ var zhCN = {
2647
2719
  rows: [
2648
2720
  { key: "Enter", text: "\u63D0\u4EA4\u8F93\u5165" },
2649
2721
  { key: "Shift+Enter", text: "\u5728\u8F93\u5165\u6846\u4E2D\u63D2\u5165\u6362\u884C" },
2650
- { key: "\u2191 / \u2193", text: "\u6EDA\u52A8\u804A\u5929\u8BB0\u5F55\uFF08\u9F20\u6807\u6EDA\u8F6E\u4E5F\u8D70\u8FD9\u6761\u8DEF\u5F84\uFF09" },
2651
2722
  {
2652
- key: "Ctrl+P / Ctrl+N",
2723
+ key: "\u2191 / \u2193",
2653
2724
  text: "\u4E0A\u4E00\u6761 / \u4E0B\u4E00\u6761\u8F93\u5165\u5386\u53F2 \xB7 \u591A\u884C\u8349\u7A3F\u4E2D\u6309\u884C\u79FB\u52A8\u5149\u6807"
2654
2725
  },
2726
+ { key: "Ctrl+P / Ctrl+N", text: "\u2191 / \u2193 \u7684 readline \u540C\u4E49\u952E" },
2655
2727
  { key: "Ctrl+A / Ctrl+E", text: "\u8DF3\u5230\u5F53\u524D\u884C\u7684\u5F00\u5934 / \u7ED3\u5C3E" },
2656
2728
  { key: "Ctrl+W", text: "\u5220\u9664\u5149\u6807\u524D\u7684\u4E00\u4E2A\u8BCD" },
2657
2729
  { key: "Ctrl+U", text: "\u6E05\u7A7A\u6574\u4E2A\u8F93\u5165\u7F13\u51B2\u533A" },
@@ -2660,7 +2732,8 @@ var zhCN = {
2660
2732
  { key: "Esc", text: "\u5173\u95ED\u5F39\u51FA\u9009\u62E9\u5668 \xB7 \u4E2D\u6B62\u5F53\u524D\u6A21\u578B\u56DE\u5408" },
2661
2733
  { key: "Ctrl+C", text: "\u4E2D\u6B62\u5F53\u524D\u6A21\u578B\u56DE\u5408\uFF08\u4E0D\u662F\u590D\u5236 \u2014 \u89C1\u526A\u8D34\u677F\u6BB5\uFF09" },
2662
2734
  { key: "PgUp / PgDn", text: "\u6574\u9875\u6EDA\u52A8\u804A\u5929\u8BB0\u5F55" },
2663
- { key: "End", text: "\u8DF3\u5230\u804A\u5929\u7684\u6700\u65B0\u4E00\u884C" }
2735
+ { key: "End", text: "\u8DF3\u5230\u804A\u5929\u7684\u6700\u65B0\u4E00\u884C" },
2736
+ { key: "Ctrl+R", text: "\u5207\u6362\u8BE6\u7EC6\u6A21\u5F0F \u2014 \u663E\u793A\u5B8C\u6574\u63A8\u7406 + \u5DE5\u5177\u8F93\u51FA\uFF0C\u4E0D\u7701\u7565" }
2664
2737
  ]
2665
2738
  },
2666
2739
  {
@@ -2699,7 +2772,7 @@ var zhCN = {
2699
2772
  ]
2700
2773
  }
2701
2774
  ],
2702
- footer: "\u901A\u8FC7 DECSET 1007\uFF08alternate-scroll\uFF09\uFF0C\u7EC8\u7AEF\u628A\u6EDA\u8F6E\u7FFB\u8BD1\u6210 \u2191/\u2193 \u53D1\u7ED9\u5E94\u7528 \u2014 \u5927\u591A\u6570\u7EC8\u7AEF\uFF08\u542B Web / \u4E91\u7AEF / SSH\uFF09\u90FD\u80FD\u6EDA\uFF0C\u4E14\u4E0D\u5F71\u54CD\u7EC8\u7AEF\u539F\u751F\u9009\u533A\u3002\u76F4\u63A5\u62D6\u52A8\u9009\u4E2D\u6587\u672C\u65E0\u9700 Shift\u3002\u4F20\u5165 --no-mouse \u53EF\u5173\u95ED\u3002"
2775
+ footer: "\u6EDA\u8F6E\u5728\u5927\u591A\u6570\u7EC8\u7AEF\uFF08\u542B Web / \u4E91\u7AEF / SSH\uFF09\u90FD\u80FD\u6EDA\u804A\u5929 \u2014 \u9ED8\u8BA4\u5F00\u542F SGR \u9F20\u6807\u8DDF\u8E2A\uFF0C\u4F46\u4E0D\u4F1A\u5F71\u54CD\u7EC8\u7AEF\u539F\u751F\u62D6\u9009\u548C\u53F3\u952E\u83DC\u5355\u3002\u76F4\u63A5\u62D6\u52A8\u9009\u4E2D\u6587\u672C\u65E0\u9700 Shift\u3002\u4F20\u5165 --no-mouse \u53EF\u5173\u95ED\u3002"
2703
2776
  },
2704
2777
  tipShownOnce: "\u4EC5\u663E\u793A\u4E00\u6B21",
2705
2778
  modelOverride: "\u8986\u76D6\u9ED8\u8BA4\u6A21\u578B",
@@ -2856,7 +2929,7 @@ var zhCN = {
2856
2929
  sessions: { description: "\u5217\u51FA\u5DF2\u4FDD\u5B58\u7684\u4F1A\u8BDD\uFF08\u5F53\u524D\u6807\u8BB0\u4E3A \u25B8\uFF09" },
2857
2930
  title: { description: "\u8BA9\u6A21\u578B\u6839\u636E\u5F53\u524D\u5BF9\u8BDD\u91CD\u547D\u540D\u6B64\u4F1A\u8BDD" },
2858
2931
  qq: {
2859
- description: "\u8FDE\u63A5\u3001\u67E5\u770B\u6216\u65AD\u5F00\u5F53\u524D\u4F1A\u8BDD\u7684 QQ \u901A\u9053",
2932
+ description: "\u8FDE\u63A5\u3001\u67E5\u770B\u6216\u65AD\u5F00\u5F53\u524D\u4F1A\u8BDD\u7684 QQ \u901A\u9053\uFF08\u9996\u6B21\u8FDE\u63A5\u4F1A\u5F15\u5BFC\u5F55\u5165 App ID / App Secret\uFF09",
2860
2933
  argsHint: "[connect [appId appSecret [sandbox]]|status|disconnect]"
2861
2934
  },
2862
2935
  setup: { description: "\u63D0\u9192\u60A8\u9000\u51FA\u5E76\u8FD0\u884C `reasonix setup`" },
@@ -3082,6 +3155,8 @@ var zhCN = {
3082
3155
  notedVerbCreated: "\u521B\u5EFA",
3083
3156
  notedVerbAppended: "\u8FFD\u52A0\u5230",
3084
3157
  memoryWriteFailed: "# \u8BB0\u5FC6\u5199\u5165\u5931\u8D25",
3158
+ verboseOn: "\u25B8 \u8BE6\u7EC6\u6A21\u5F0F\u5DF2\u5F00 \u2014 \u663E\u793A\u5B8C\u6574\u63A8\u7406 + \u5DE5\u5177\u8F93\u51FA",
3159
+ verboseOff: "\u25B8 \u8BE6\u7EC6\u6A21\u5F0F\u5DF2\u5173 \u2014 \u6062\u590D\u5934\u5C3E\u7701\u7565",
3085
3160
  commandFailed: "! \u547D\u4EE4\u5931\u8D25",
3086
3161
  btwUsage: "\u25B8 /btw <\u95EE\u9898> \u2014 \u987A\u4FBF\u95EE\u4E2A\u9898\u5916\u8BDD\uFF0C\u4E0D\u4F1A\u5199\u5165\u5F53\u524D\u4F1A\u8BDD\u4E0A\u4E0B\u6587\u3002",
3087
3162
  btwHeader: "\u226B btw",
@@ -3216,6 +3291,48 @@ var zhCN = {
3216
3291
  titleStarted: "\u25B8 \u6B63\u5728\u547D\u540D\u4F1A\u8BDD\u2026",
3217
3292
  titleFailed: "\u25B8 \u4F1A\u8BDD\u547D\u540D\u5931\u8D25\uFF1A{reason}"
3218
3293
  },
3294
+ qq: {
3295
+ unavailable: "/qq \u5728\u5F53\u524D\u4F1A\u8BDD\u4E2D\u4E0D\u53EF\u7528\u3002",
3296
+ connecting: "QQ\uFF1A\u6B63\u5728\u8FDE\u63A5\u2026",
3297
+ connectFailed: "QQ \u8FDE\u63A5\u5931\u8D25\uFF1A{reason}",
3298
+ disconnecting: "QQ\uFF1A\u6B63\u5728\u65AD\u5F00\u2026",
3299
+ disconnectFailed: "QQ \u65AD\u5F00\u5931\u8D25\uFF1A{reason}",
3300
+ usage: "\u7528\u6CD5\uFF1A/qq connect [appId appSecret [sandbox]] | /qq status | /qq disconnect",
3301
+ promptAppId: "QQ \u9996\u6B21\u914D\u7F6E\uFF1A\u8BF7\u8F93\u5165 QQ \u5F00\u653E\u5E73\u53F0 App ID \u540E\u56DE\u8F66\u3002\u8F93\u5165 /cancel \u53EF\u53D6\u6D88\u3002",
3302
+ promptAppSecret: "QQ \u9996\u6B21\u914D\u7F6E\uFF1A\u8BF7\u8F93\u5165 QQ \u5F00\u653E\u5E73\u53F0 App Secret \u540E\u56DE\u8F66\u3002\u8F93\u5165 /cancel \u53EF\u53D6\u6D88\u3002",
3303
+ setupWaitingAppId: "\u7B49\u5F85\u8F93\u5165 App ID",
3304
+ setupWaitingAppSecret: "\u7B49\u5F85\u8F93\u5165 App Secret",
3305
+ setupCancelled: "QQ \u9996\u6B21\u914D\u7F6E\u5DF2\u53D6\u6D88\u3002",
3306
+ credentialsRequired: "QQ App ID \u548C App Secret \u4E0D\u80FD\u4E3A\u7A7A\u3002",
3307
+ connected: "QQ \u5DF2\u5728{mode}\u6A21\u5F0F\u4E0B\u8FDE\u63A5\u6210\u529F\uFF0C\u540E\u7EED\u542F\u52A8\u4F1A\u81EA\u52A8\u542F\u7528\u3002",
3308
+ alreadyConnected: "QQ \u5DF2\u5728{mode}\u6A21\u5F0F\u4E0B\u8FDE\u63A5\uFF0C\u81EA\u52A8\u542F\u52A8\u5DF2\u542F\u7528\u3002",
3309
+ disconnected: "QQ \u5DF2\u65AD\u5F00\u8FDE\u63A5\uFF0C\u81EA\u52A8\u542F\u52A8\u5DF2\u5173\u95ED\u3002",
3310
+ status: "QQ\uFF1A{connected}\uFF0C\u81EA\u52A8\u542F\u52A8{enabled}\uFF0C\u51ED\u636E{configured}\uFF0CappId {appId}\uFF0C{sandbox}\uFF0C\u8BBF\u95EE\u63A7\u5236 {access}\uFF0C\u5F53\u524D\u6A21\u5F0F {mode}\u3002",
3311
+ statusSetup: "QQ\uFF1A\u9996\u6B21\u914D\u7F6E\u8FDB\u884C\u4E2D \u2014\u2014 {step}",
3312
+ stateConnected: "\u5DF2\u8FDE\u63A5",
3313
+ stateDisconnected: "\u672A\u8FDE\u63A5",
3314
+ stateEnabled: "\u5DF2\u542F\u7528",
3315
+ stateDisabled: "\u672A\u542F\u7528",
3316
+ stateConfigured: "\u5DF2\u914D\u7F6E",
3317
+ stateNotConfigured: "\u672A\u914D\u7F6E",
3318
+ sandbox: "\u6C99\u7BB1\u73AF\u5883",
3319
+ production: "\u6B63\u5F0F\u73AF\u5883",
3320
+ none: "\u65E0",
3321
+ modeChat: "\u804A\u5929",
3322
+ modeCode: "\u4EE3\u7801",
3323
+ accessOwner: "\u6240\u6709\u8005 {owner}",
3324
+ accessOwnerWithAllowlist: "\u6240\u6709\u8005 {owner}\uFF0C\u767D\u540D\u5355 {count}",
3325
+ accessAllowlist: "\u767D\u540D\u5355 {count}",
3326
+ accessRuntime: "\u9996\u4E2A\u79C1\u804A\u7528\u6237\uFF08\u4EC5\u672C\u6B21\u8FD0\u884C\uFF0C{owner}\uFF09",
3327
+ accessOpen: "\u5F00\u653E\uFF08\u672A\u7ED1\u5B9A\uFF09",
3328
+ lockAlreadyRunning: "QQ \u901A\u9053\u5DF2\u5728\u8FDB\u7A0B {pid} \u4E2D\u8FD0\u884C\u3002\u8BF7\u5148\u505C\u6B62\u8BE5\u8FDB\u7A0B\uFF0C\u518D\u542F\u52A8\u65B0\u7684 QQ \u901A\u9053\u3002",
3329
+ unauthorizedMessage: "QQ \u5FFD\u7565\u4E86\u672A\u6388\u6743 openid {openid} \u7684\u6D88\u606F\u3002\u5F53\u524D\u8BBF\u95EE\u63A7\u5236\uFF1A{access}\u3002",
3330
+ runtimeBound: "QQ \u5DF2\u5728\u672C\u6B21\u8FD0\u884C\u4E2D\u4E34\u65F6\u7ED1\u5B9A\u5230\u9996\u4E2A\u53D1\u9001\u8005 {openid}\u3002\u5982\u9700\u6301\u4E45\u5316\uFF0C\u8BF7\u5728\u914D\u7F6E\u4E2D\u8BBE\u7F6E `qq.ownerOpenId`\u3002",
3331
+ missingAppId: "\u7F3A\u5C11 QQ App ID\u3002\u8BF7\u5148\u8FD0\u884C `/qq connect` \u5B8C\u6210\u914D\u7F6E\u3002",
3332
+ missingAppSecret: "\u7F3A\u5C11 QQ App Secret\u3002\u8BF7\u5148\u8FD0\u884C `/qq connect` \u5B8C\u6210\u914D\u7F6E\u3002",
3333
+ authFailed: "QQ \u673A\u5668\u4EBA\u9274\u6743\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5 App ID \u548C App Secret\u3002",
3334
+ readyTimeout: "QQ \u673A\u5668\u4EBA 15 \u79D2\u5185\u672A\u6536\u5230 READY\uFF0C\u8BF7\u68C0\u67E5 App ID \u548C App Secret\u3002"
3335
+ },
3219
3336
  admin: {
3220
3337
  doctorNeedsTui: "/doctor \u9700\u8981 TUI \u4E0A\u4E0B\u6587\uFF08postDoctor \u5DF2\u8FDE\u63A5\uFF09\u3002",
3221
3338
  doctorRunning: "\u2695 \u5065\u5EB7\u68C0\u67E5 \u2014 \u6B63\u5728\u8FD0\u884C\u2026",
@@ -3482,12 +3599,14 @@ var zhCN = {
3482
3599
  usageSearxng: " /search-engine searxng \u4F7F\u7528 SearXNG \u9ED8\u8BA4\u7AEF\u70B9",
3483
3600
  usageSearxngUrl: " /search-engine searxng <url> \u4F7F\u7528 SearXNG \u81EA\u5B9A\u4E49\u7AEF\u70B9",
3484
3601
  usageMetaso: " /search-engine metaso \u4F7F\u7528 Metaso API\uFF08\u6BCF\u5929 100 \u6B21\u514D\u8D39\uFF0C\u914D\u7F6E\u4F60\u81EA\u5DF1\u7684 API \u5BC6\u94A5\u53EF\u63D0\u5347\u9650\u989D\uFF09",
3602
+ usageTavily: " /search-engine tavily \u4F7F\u7528 Tavily API\uFF08LLM \u53CB\u597D\uFF0C\u6BCF\u6708 1000 \u6B21\u514D\u8D39 \u2014 \u8BBE\u7F6E TAVILY_API_KEY \u6216 config \u7684 tavilyApiKey\uFF1B\u6CE8\u518C https://tavily.com\uFF09",
3485
3603
  alias: "\u522B\u540D\uFF1A/se",
3486
3604
  searxngInfo: "SearXNG \u662F\u4E00\u4E2A\u81EA\u6258\u7BA1\u7684\u5143\u641C\u7D22\u5F15\u64CE\uFF08https://github.com/searxng/searxng\uFF09\u3002",
3487
3605
  searxngInstall: "\u5B89\u88C5\u547D\u4EE4\uFF1A docker run -d -p 8080:8080 searxng/searxng",
3488
3606
  switched: '\u5DF2\u5207\u6362\u7F51\u9875\u641C\u7D22\u5F15\u64CE\u4E3A "{engine}"\u3002{note}',
3489
3607
  switchedSearxngNote: " \u8BF7\u786E\u4FDD SearXNG \u5728 {endpoint} \u8FD0\u884C\u3002",
3490
3608
  switchedMetasoNote: " \u6BCF\u65E5\u9650\u989D 100 \u6B21\uFF08\u914D\u7F6E\u4F60\u81EA\u5DF1\u7684 API \u5BC6\u94A5\u53EF\u63D0\u5347\u9650\u989D\uFF09\u3002",
3609
+ switchedTavilyNote: " \u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF TAVILY_API_KEY \u6216 config \u4E2D\u7684 `tavilyApiKey`\uFF1Bhttps://tavily.com \u6BCF\u6708 1000 \u6B21\u514D\u8D39\u3002",
3491
3610
  confirmed: '\u2713 \u7F51\u9875\u641C\u7D22\u5F15\u64CE\u5DF2\u8BBE\u4E3A "{engine}"{detail}\u3002\u4E0B\u4E00\u8F6E\u6A21\u578B\u8C03\u7528\u5C06\u751F\u6548\u3002',
3492
3611
  confirmedDetail: "\uFF08{endpoint}\uFF09"
3493
3612
  },
@@ -3623,6 +3742,13 @@ var zhCN = {
3623
3742
  linesBelow: " \u2193 \u4E0B\u65B9 {count} \u884C\uFF08\u2193/j \u6216 Space/PgDn\uFF09",
3624
3743
  linesBelowPlural: " \u2193 \u4E0B\u65B9 {count} \u884C\uFF08\u2193/j \u6216 Space/PgDn\uFF09"
3625
3744
  },
3745
+ editPicker: {
3746
+ title: "\u7F16\u8F91\u4E4B\u524D\u7684\u6D88\u606F",
3747
+ hint: "\u2191\u2193 \u9009\u62E9 \xB7 Enter \u52A0\u8F7D\u5230\u8F93\u5165\u6846 \xB7 Esc \u53D6\u6D88",
3748
+ empty: "\u8FD8\u6CA1\u6709\u7528\u6237\u53D1\u8A00 \u2014 \u6CA1\u4EC0\u4E48\u53EF\u4EE5\u7F16\u8F91\u7684",
3749
+ dismiss: "Esc \u5173\u95ED",
3750
+ forked: "\u25B8 \u4ECE\u7B2C #{turn} \u8F6E\u5206\u53C9 \u2014 \u539F\u6587\u5DF2\u586B\u56DE\u8F93\u5165\u6846"
3751
+ },
3626
3752
  sessionPicker: {
3627
3753
  header: " \u25C8 REASONIX \xB7 \u9009\u62E9\u4F1A\u8BDD ",
3628
3754
  title: "\u9009\u62E9\u4F1A\u8BDD \u2014 {workspace}",
@@ -3756,6 +3882,11 @@ var zhCN = {
3756
3882
  metasoServerError: "web_search: Metaso \u670D\u52A1\u5668\u9519\u8BEF\uFF08{status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u4F7F\u7528 /search-engine mojeek \u5207\u6362\u5F15\u64CE",
3757
3883
  metasoParseError: "web_search: Metaso \u8FD4\u56DE\u65E0\u6CD5\u89E3\u6790\u7684\u54CD\u5E94\uFF08HTTP {status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5",
3758
3884
  metasoApiError: "web_search: Metaso API \u9519\u8BEF\uFF08code {code}: {message}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5",
3885
+ tavilyMissingKey: "web_search: Tavily \u540E\u7AEF\u9700\u8981 API \u5BC6\u94A5 \u2014 \u8BBE\u7F6E TAVILY_API_KEY \u73AF\u5883\u53D8\u91CF\uFF0C\u6216\u5728 ~/.reasonix/config.json \u4E2D\u914D\u7F6E `tavilyApiKey`\uFF1Bhttps://tavily.com \u6BCF\u6708 1000 \u6B21\u514D\u8D39",
3886
+ tavilyUnauthorized: "web_search: Tavily API \u5BC6\u94A5\u88AB\u62D2\u7EDD \u2014 \u68C0\u67E5 TAVILY_API_KEY\uFF0C\u6216\u5728 https://tavily.com \u83B7\u53D6\u5BC6\u94A5",
3887
+ tavilyRateLimit: "web_search: Tavily \u8BF7\u6C42\u9891\u7387\u9650\u5236\u6216\u6708\u5EA6\u914D\u989D\u7528\u5C3D \u2014 \u7B49\u5F85\u3001\u7528 /search-engine mojeek \u5207\u6362\u5F15\u64CE\uFF0C\u6216\u5347\u7EA7 Tavily \u8BA1\u5212",
3888
+ tavilyServerError: "web_search: Tavily \u670D\u52A1\u5668\u9519\u8BEF\uFF08{status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u4F7F\u7528 /search-engine mojeek \u5207\u6362\u5F15\u64CE",
3889
+ tavilyParseError: "web_search: Tavily \u8FD4\u56DE\u65E0\u6CD5\u89E3\u6790\u7684\u54CD\u5E94\uFF08HTTP {status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5",
3759
3890
  fetchStatus: "web_fetch {status} for {url} \u2014 try: \u5728\u6D4F\u89C8\u5668\u4E2D\u786E\u8BA4\u8BE5 URL \u80FD\u5426\u8BBF\u95EE\uFF1B\u8BE5\u72B6\u6001\u7801\u8868\u660E\u76EE\u6807\u4E3B\u673A\u8FD4\u56DE\u4E86\u9519\u8BEF\u9875\u9762",
3760
3891
  fetchRateLimit429: "web_fetch 429 for {url} \u2014 try: \u7B49\u5F85 10 \u79D2\u540E\u91CD\u8BD5\uFF1B\u76EE\u6807\u4E3B\u673A\u6B63\u5728\u5BF9\u8BE5\u5BA2\u6237\u7AEF\u8FDB\u884C\u9650\u6D41",
3761
3892
  fetchForbidden403: "web_fetch 403 for {url} \u2014 try: \u76EE\u6807\u4E3B\u673A\u62D2\u7EDD\u8BE5\u5BA2\u6237\u7AEF\u8BBF\u95EE\uFF1B\u8BE5\u9875\u9762\u53EF\u80FD\u9700\u8981\u767B\u5F55\u6216\u5C4F\u853D\u722C\u866B \u2014 \u6539\u7528 web_search \u6458\u8981",
@@ -3874,7 +4005,8 @@ var zhCN = {
3874
4005
  scrollAbove: " \u2191 {scroll}/{max} \u884C",
3875
4006
  scrollAbovePlural: " \u2191 {scroll}/{max} \u884C",
3876
4007
  scrollMore: " \u2014 \u8FD8\u6709 {remaining} \u884C",
3877
- scrollPgUp: " \xB7 PgUp/\u6EDA\u8F6E/\u2191"
4008
+ scrollPgUp: " \xB7 PgUp/\u6EDA\u8F6E",
4009
+ scrollCopy: " \xB7 /copy \u8FDB\u5165\u590D\u5236\u6A21\u5F0F"
3878
4010
  },
3879
4011
  slashArgPicker: {
3880
4012
  noMatch: '\u6CA1\u6709\u5339\u914D "{partial}"',
@@ -3932,7 +4064,8 @@ var zhCN = {
3932
4064
  reconnectDetail: "\u65AD\u5F00\u65E7\u8FDE\u63A5 \xB7 \u91CD\u65B0\u63E1\u624B \xB7 \u5217\u51FA\u5DE5\u5177",
3933
4065
  disabledDetail: "\u901A\u8FC7 /mcp disable {name}",
3934
4066
  failedSetupHint: "\u2192 \u8FD0\u884C `reasonix setup` \u79FB\u9664\u6B64\u6761\u76EE\uFF0C\u6216\u4FEE\u590D\u5E95\u5C42\u95EE\u9898\uFF08\u7F3A\u5C11 npm \u5305\u3001\u7F51\u7EDC\u7B49\uFF09\u3002",
3935
- failedSetupConfigHint: "\u2192 \u8FD0\u884C `reasonix setup` \u4ECE\u5DF2\u4FDD\u5B58\u914D\u7F6E\u4E2D\u79FB\u9664\u635F\u574F\u7684\u6761\u76EE\u3002"
4067
+ failedSetupConfigHint: "\u2192 \u8FD0\u884C `reasonix setup` \u4ECE\u5DF2\u4FDD\u5B58\u914D\u7F6E\u4E2D\u79FB\u9664\u635F\u574F\u7684\u6761\u76EE\u3002",
4068
+ abortedHint: "\u5DF2\u4E2D\u65AD MCP \u542F\u52A8 \u2014 \u8DF3\u8FC7 {count} \u4E2A\u670D\u52A1\u5668\u3002\u95EE\u9898\u4FEE\u590D\u540E\u7528 /mcp \u91CD\u65B0\u8FDE\u63A5\u3002"
3936
4069
  },
3937
4070
  checkpointPicker: {
3938
4071
  title: "\u6062\u590D\u68C0\u67E5\u70B9 \u2014 {workspace}",
@@ -4779,6 +4912,12 @@ var ToolRegistry = class {
4779
4912
  });
4780
4913
  }
4781
4914
  }
4915
+ if (opts.signal?.aborted) {
4916
+ return JSON.stringify({
4917
+ error: `${name}: aborted before dispatch (user interrupt)`,
4918
+ rejectedReason: "aborted"
4919
+ });
4920
+ }
4782
4921
  let finalResult;
4783
4922
  try {
4784
4923
  try {
@@ -5011,11 +5150,42 @@ async function bridgeMcpTools(client, opts = {}) {
5011
5150
  return { ...result, env };
5012
5151
  }
5013
5152
  function flattenMcpResult(result, opts = {}) {
5153
+ validateResultShape(result);
5014
5154
  const parts = result.content.map(blockToString);
5015
5155
  const joined = parts.join("\n").trim();
5016
5156
  const prefixed = result.isError ? `ERROR: ${joined || "(no error message from server)"}` : joined;
5017
5157
  return opts.maxChars ? truncateForModel(prefixed, opts.maxChars) : prefixed;
5018
5158
  }
5159
+ function validateResultShape(result) {
5160
+ if (typeof result !== "object" || !result)
5161
+ throw new Error(`MCP server returned non-object result: ${typeof result}`);
5162
+ const { content, isError: _isError } = result;
5163
+ if (!Array.isArray(content))
5164
+ throw new Error(`MCP server returned result with non-array content: ${typeof content}`);
5165
+ for (let i = 0; i < content.length; i++) {
5166
+ const block = content[i];
5167
+ if (typeof block !== "object" || !block)
5168
+ throw new Error(`MCP server returned result.content[${i}] is not an object`);
5169
+ if (block.type !== "text" && block.type !== "image")
5170
+ throw new Error(
5171
+ `MCP server returned result.content[${i}] with unknown type ${JSON.stringify(block.type)}`
5172
+ );
5173
+ if (block.type === "text" && typeof block.text !== "string")
5174
+ throw new Error(
5175
+ `MCP server returned result.content[${i}] with non-string text (${typeof block.text})`
5176
+ );
5177
+ if (block.type === "image") {
5178
+ if (typeof block.data !== "string")
5179
+ throw new Error(
5180
+ `MCP server returned result.content[${i}] with non-string data (${typeof block.data})`
5181
+ );
5182
+ if (typeof block.mimeType !== "string")
5183
+ throw new Error(
5184
+ `MCP server returned result.content[${i}] with non-string mimeType (${typeof block.mimeType})`
5185
+ );
5186
+ }
5187
+ }
5188
+ }
5019
5189
  function truncateForModel(s, maxChars) {
5020
5190
  if (s.length <= maxChars) return s;
5021
5191
  const tailBudget = Math.min(1024, Math.floor(maxChars * 0.1));
@@ -5198,31 +5368,38 @@ function appendSessionMessage(name, message) {
5198
5368
  } catch {
5199
5369
  }
5200
5370
  }
5201
- function listSessions() {
5371
+ function listSessions(opts) {
5202
5372
  const dir = sessionsDir();
5203
5373
  if (!existsSync3(dir)) return [];
5374
+ const want = opts?.workspaceFilter ? normalizeWorkspace(opts.workspaceFilter) : null;
5204
5375
  try {
5205
5376
  const files = readdirSync(dir).filter(
5206
5377
  (f) => f.endsWith(".jsonl") && !f.endsWith(".events.jsonl")
5207
5378
  );
5208
- return files.map((file) => {
5379
+ return files.flatMap((file) => {
5209
5380
  const path2 = join4(dir, file);
5210
- const stat2 = statSync(path2);
5211
5381
  const name = file.replace(/\.jsonl$/, "");
5382
+ const meta = loadSessionMeta(name);
5383
+ if (want !== null) {
5384
+ if (typeof meta.workspace !== "string") return [];
5385
+ if (normalizeWorkspace(meta.workspace) !== want) return [];
5386
+ }
5387
+ const stat2 = statSync(path2);
5212
5388
  const messageCount = countLines(path2);
5213
- return {
5214
- name,
5215
- path: path2,
5216
- size: stat2.size,
5217
- messageCount,
5218
- mtime: stat2.mtime,
5219
- meta: loadSessionMeta(name)
5220
- };
5389
+ return [{ name, path: path2, size: stat2.size, messageCount, mtime: stat2.mtime, meta }];
5221
5390
  }).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
5222
5391
  } catch {
5223
5392
  return [];
5224
5393
  }
5225
5394
  }
5395
+ function normalizeWorkspace(p, platform = process.platform) {
5396
+ if (typeof p !== "string" || p.length === 0) return "";
5397
+ if (platform === "win32") {
5398
+ const resolved = win32Path.resolve(p);
5399
+ return resolved.replace(/\\/g, "/").replace(/^([A-Z]):/i, (_, d) => `${d.toLowerCase()}:`);
5400
+ }
5401
+ return posixPath.resolve(p);
5402
+ }
5226
5403
  function metaPath(name) {
5227
5404
  return join4(sessionsDir(), `${sanitizeName(name)}.meta.json`);
5228
5405
  }
@@ -5312,8 +5489,13 @@ function archiveSession(name) {
5312
5489
  }
5313
5490
  function countLines(path2) {
5314
5491
  try {
5315
- const raw = readFileSync4(path2, "utf8");
5316
- return raw.split(/\r?\n/).filter((l) => l.trim()).length;
5492
+ const buf = readFileSync4(path2);
5493
+ let count = 0;
5494
+ for (let i = 0; i < buf.length; i++) {
5495
+ if (buf[i] === 10) count++;
5496
+ }
5497
+ if (buf.length > 0 && buf[buf.length - 1] !== 10) count++;
5498
+ return count;
5317
5499
  } catch {
5318
5500
  return 0;
5319
5501
  }
@@ -6834,6 +7016,32 @@ ${reason}`
6834
7016
  }
6835
7017
  return userText;
6836
7018
  }
7019
+ /** Rewind to the N-th user turn (0-indexed). Drops that turn + everything after. */
7020
+ rewindToUserTurn(userTurnIndex) {
7021
+ const entries = this.log.entries;
7022
+ let count = 0;
7023
+ let targetIdx = -1;
7024
+ for (let i = 0; i < entries.length; i++) {
7025
+ if (entries[i].role !== "user") continue;
7026
+ if (count === userTurnIndex) {
7027
+ targetIdx = i;
7028
+ break;
7029
+ }
7030
+ count++;
7031
+ }
7032
+ if (targetIdx < 0) return null;
7033
+ const raw = entries[targetIdx].content;
7034
+ const userText = typeof raw === "string" ? raw : "";
7035
+ const preserved = entries.slice(0, targetIdx).map((m) => ({ ...m }));
7036
+ this.log.compactInPlace(preserved);
7037
+ if (this.sessionName) {
7038
+ try {
7039
+ rewriteSession(this.sessionName, preserved);
7040
+ } catch {
7041
+ }
7042
+ }
7043
+ return userText;
7044
+ }
6837
7045
  async *step(userInput) {
6838
7046
  this._steerConsumed = false;
6839
7047
  if (this.budgetUsd !== null) {
@@ -8001,10 +8209,15 @@ var SkillStore = class {
8001
8209
  dir: join7(this.projectRoot, ".agents", SKILLS_DIRNAME),
8002
8210
  scope: "project"
8003
8211
  });
8212
+ out.push({
8213
+ dir: join7(this.projectRoot, ".claude", SKILLS_DIRNAME),
8214
+ scope: "project"
8215
+ });
8004
8216
  }
8005
8217
  for (const dir of this.customSkillPaths) out.push({ dir, scope: "custom" });
8006
8218
  out.push({ dir: join7(this.homeDir, ".reasonix", SKILLS_DIRNAME), scope: "global" });
8007
8219
  out.push({ dir: join7(this.homeDir, ".agents", SKILLS_DIRNAME), scope: "global" });
8220
+ out.push({ dir: join7(this.homeDir, ".claude", SKILLS_DIRNAME), scope: "global" });
8008
8221
  return out.map((root, priority) => ({ ...root, priority, status: skillPathStatus(root.dir) }));
8009
8222
  }
8010
8223
  customRoots() {
@@ -8107,14 +8320,20 @@ var SkillStore = class {
8107
8320
  }
8108
8321
  const { data, body } = parseFrontmatter(raw);
8109
8322
  const name = data.name && isValidSkillName(data.name) ? data.name : stem;
8323
+ const description = (data.description ?? "").trim();
8324
+ if (!description) {
8325
+ console.warn(
8326
+ `[skills] "${name}" at ${path2} has no description: \u2014 it will be loaded but won't appear in the skills index.`
8327
+ );
8328
+ }
8110
8329
  return {
8111
8330
  name,
8112
- description: (data.description ?? "").trim(),
8331
+ description,
8113
8332
  body: body.trim(),
8114
8333
  scope,
8115
8334
  path: path2,
8116
8335
  allowedTools: parseAllowedTools(data["allowed-tools"]),
8117
- runAs: parseRunAs(data.runAs),
8336
+ runAs: parseRunAs(data.runAs, data.context, data.agent),
8118
8337
  model: data.model?.startsWith("deepseek-") ? data.model : void 0
8119
8338
  };
8120
8339
  }
@@ -8147,8 +8366,11 @@ function skillPathStatus(dir) {
8147
8366
  return "unreadable";
8148
8367
  }
8149
8368
  }
8150
- function parseRunAs(raw) {
8151
- return raw?.trim() === "subagent" ? "subagent" : "inline";
8369
+ function parseRunAs(raw, context, agent) {
8370
+ if (raw?.trim() === "subagent") return "subagent";
8371
+ if (context?.trim().toLowerCase() === "fork") return "subagent";
8372
+ if (agent?.trim()) return "subagent";
8373
+ return "inline";
8152
8374
  }
8153
8375
  function skillStubBody(name) {
8154
8376
  return `---
@@ -8710,13 +8932,13 @@ import picomatch3 from "picomatch";
8710
8932
  // src/memory/subdir.ts
8711
8933
  import { existsSync as existsSync8, readFileSync as readFileSync10 } from "fs";
8712
8934
  import { dirname as dirname5, join as join9, relative as relative2, resolve as resolve5 } from "path";
8713
- function findSubdirMemoryAncestors(absPath, rootDir) {
8935
+ function findDirMemory(absDir, rootDir) {
8714
8936
  const root = resolve5(rootDir);
8715
- const target = resolve5(absPath);
8937
+ const target = resolve5(absDir);
8716
8938
  const rel = relative2(root, target);
8717
- if (!rel || rel.startsWith("..")) return [];
8939
+ if (rel.startsWith("..")) return [];
8718
8940
  const found = [];
8719
- let cur = dirname5(target);
8941
+ let cur = target;
8720
8942
  while (cur !== root) {
8721
8943
  const r = relative2(root, cur);
8722
8944
  if (!r || r.startsWith("..")) break;
@@ -8733,6 +8955,9 @@ function findSubdirMemoryAncestors(absPath, rootDir) {
8733
8955
  }
8734
8956
  return found;
8735
8957
  }
8958
+ function findSubdirMemoryAncestors(absPath, rootDir) {
8959
+ return findDirMemory(dirname5(resolve5(absPath)), rootDir);
8960
+ }
8736
8961
  function readSubdirMemoryContent(path2) {
8737
8962
  let raw;
8738
8963
  try {
@@ -9157,6 +9382,129 @@ function formatOutline(entries) {
9157
9382
  // src/tools/fs/search.ts
9158
9383
  import { promises as fs3 } from "fs";
9159
9384
  import * as pathMod4 from "path";
9385
+
9386
+ // src/tools/fs/regex-runner.ts
9387
+ import { Worker } from "worker_threads";
9388
+ var WORKER_SOURCE = `
9389
+ const { parentPort } = require("node:worker_threads");
9390
+ parentPort.on("message", (msg) => {
9391
+ const { id, text, source, flags } = msg;
9392
+ let re;
9393
+ try {
9394
+ re = new RegExp(source, flags);
9395
+ } catch (err) {
9396
+ parentPort.postMessage({ id, error: (err && err.message) ? err.message : String(err) });
9397
+ return;
9398
+ }
9399
+ const lines = text.split(/\\r?\\n/);
9400
+ const hits = [];
9401
+ for (let i = 0; i < lines.length; i++) {
9402
+ if (re.test(lines[i])) hits.push(i);
9403
+ }
9404
+ parentPort.postMessage({ id, hits });
9405
+ });
9406
+ `;
9407
+ var DEFAULT_TIMEOUT_MS = 6e4;
9408
+ var RegexRunner = class {
9409
+ worker = null;
9410
+ pending = /* @__PURE__ */ new Map();
9411
+ nextId = 1;
9412
+ defaultTimeoutMs;
9413
+ constructor(opts = {}) {
9414
+ this.defaultTimeoutMs = opts.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
9415
+ }
9416
+ testLines(text, source, flags, opts = {}) {
9417
+ return new Promise((resolve13, reject) => {
9418
+ if (opts.signal?.aborted) {
9419
+ reject(new Error("regex evaluation aborted"));
9420
+ return;
9421
+ }
9422
+ if (!this.worker) this.worker = this.spawn();
9423
+ const id = this.nextId++;
9424
+ const timeoutMs = opts.timeoutMs ?? this.defaultTimeoutMs;
9425
+ const timer = setTimeout(() => {
9426
+ this.pending.delete(id);
9427
+ this.killWorker();
9428
+ reject(new Error(`regex evaluation exceeded ${timeoutMs}ms`));
9429
+ }, timeoutMs);
9430
+ const entry = { resolve: resolve13, reject, timer };
9431
+ if (opts.signal) {
9432
+ entry.signal = opts.signal;
9433
+ entry.onAbort = () => {
9434
+ this.pending.delete(id);
9435
+ clearTimeout(timer);
9436
+ this.killWorker();
9437
+ reject(new Error("regex evaluation aborted"));
9438
+ };
9439
+ opts.signal.addEventListener("abort", entry.onAbort, { once: true });
9440
+ }
9441
+ this.pending.set(id, entry);
9442
+ this.worker.postMessage({ id, text, source, flags });
9443
+ });
9444
+ }
9445
+ async shutdown() {
9446
+ if (this.worker) {
9447
+ const w = this.worker;
9448
+ this.worker = null;
9449
+ await w.terminate();
9450
+ }
9451
+ for (const entry of this.pending.values()) {
9452
+ clearTimeout(entry.timer);
9453
+ if (entry.onAbort && entry.signal) {
9454
+ entry.signal.removeEventListener("abort", entry.onAbort);
9455
+ }
9456
+ entry.reject(new Error("regex runner shut down"));
9457
+ }
9458
+ this.pending.clear();
9459
+ }
9460
+ spawn() {
9461
+ const w = new Worker(WORKER_SOURCE, { eval: true });
9462
+ w.on("message", (msg) => {
9463
+ const entry = this.pending.get(msg.id);
9464
+ if (!entry) return;
9465
+ clearTimeout(entry.timer);
9466
+ if (entry.onAbort && entry.signal) {
9467
+ entry.signal.removeEventListener("abort", entry.onAbort);
9468
+ }
9469
+ this.pending.delete(msg.id);
9470
+ if (msg.error !== void 0) entry.reject(new Error(msg.error));
9471
+ else entry.resolve(msg.hits ?? []);
9472
+ });
9473
+ w.on("error", (err) => {
9474
+ if (this.worker !== w) return;
9475
+ this.failPending(err);
9476
+ });
9477
+ w.on("exit", () => {
9478
+ if (this.worker !== w) return;
9479
+ this.worker = null;
9480
+ if (this.pending.size > 0) this.failPending(new Error("regex worker exited"));
9481
+ });
9482
+ return w;
9483
+ }
9484
+ killWorker() {
9485
+ if (!this.worker) return;
9486
+ const w = this.worker;
9487
+ this.worker = null;
9488
+ void w.terminate();
9489
+ }
9490
+ failPending(err) {
9491
+ for (const entry of this.pending.values()) {
9492
+ clearTimeout(entry.timer);
9493
+ if (entry.onAbort && entry.signal) {
9494
+ entry.signal.removeEventListener("abort", entry.onAbort);
9495
+ }
9496
+ entry.reject(err);
9497
+ }
9498
+ this.pending.clear();
9499
+ }
9500
+ };
9501
+ var _runner = null;
9502
+ function getRegexRunner() {
9503
+ if (!_runner) _runner = new RegexRunner();
9504
+ return _runner;
9505
+ }
9506
+
9507
+ // src/tools/fs/search.ts
9160
9508
  function throwIfAborted(signal) {
9161
9509
  if (!signal?.aborted) return;
9162
9510
  throw new DOMException("search aborted by user", "AbortError");
@@ -9209,17 +9557,20 @@ async function searchFiles(ctx, startAbs, args) {
9209
9557
  }
9210
9558
  var MAX_HITS_PER_FILE = 30;
9211
9559
  var SUMMARY_MODE_TRIGGER_RATIO = 0.8;
9560
+ var WALK_DEADLINE_MS = 12e4;
9212
9561
  async function searchContent(ctx, startAbs, args) {
9213
9562
  throwIfAborted(args.signal);
9214
9563
  const caseSensitive = args.case_sensitive === true;
9215
9564
  const includeDeps = args.include_deps === true;
9216
9565
  const ctxLines = Math.max(0, Math.min(20, Math.floor(args.context ?? 0)));
9217
9566
  const summaryOnly = args.summary_only === true;
9218
- let re = null;
9567
+ const reFlags = caseSensitive ? "" : "i";
9568
+ let reSource = null;
9219
9569
  try {
9220
- re = new RegExp(args.pattern, caseSensitive ? "" : "i");
9570
+ new RegExp(args.pattern, reFlags);
9571
+ reSource = args.pattern;
9221
9572
  } catch {
9222
- re = null;
9573
+ reSource = null;
9223
9574
  }
9224
9575
  const needle = caseSensitive ? args.pattern : args.pattern.toLowerCase();
9225
9576
  const matches = [];
@@ -9229,6 +9580,15 @@ async function searchContent(ctx, startAbs, args) {
9229
9580
  let summaryMode = summaryOnly;
9230
9581
  let summaryNoticeEmitted = false;
9231
9582
  const fileHitCounts = /* @__PURE__ */ new Map();
9583
+ const regexSkippedFiles = [];
9584
+ const t0 = Date.now();
9585
+ const throwIfTimedOut = () => {
9586
+ if (Date.now() - t0 > WALK_DEADLINE_MS) {
9587
+ throw new Error(
9588
+ `search_content exceeded ${WALK_DEADLINE_MS}ms \u2014 narrow the scope (path/glob) or simplify the pattern`
9589
+ );
9590
+ }
9591
+ };
9232
9592
  const pushLine = (out) => {
9233
9593
  if (totalBytes + out.length + 1 > ctx.maxListBytes) {
9234
9594
  matches.push(`[\u2026 truncated at ${ctx.maxListBytes} bytes \u2014 refine pattern or path \u2026]`);
@@ -9263,6 +9623,7 @@ async function searchContent(ctx, startAbs, args) {
9263
9623
  for (const e of entries) {
9264
9624
  if (truncated) return;
9265
9625
  throwIfAborted(args.signal);
9626
+ throwIfTimedOut();
9266
9627
  if (e.isDirectory()) {
9267
9628
  if (!includeDeps && ctx.skipDirNames.has(e.name)) continue;
9268
9629
  await walk2(pathMod4.join(dir, e.name));
@@ -9299,13 +9660,25 @@ async function searchContent(ctx, startAbs, args) {
9299
9660
  const text = raw.toString("utf8");
9300
9661
  const rel = displayRel3(ctx.rootDir, full);
9301
9662
  const lines = text.split(/\r?\n/);
9302
- const hits = [];
9303
- for (let li = 0; li < lines.length; li++) {
9304
- throwIfAborted(args.signal);
9305
- const line = lines[li];
9306
- const lineForCheck = caseSensitive ? line : line.toLowerCase();
9307
- const hit = re ? re.test(line) : lineForCheck.includes(needle);
9308
- if (hit) hits.push(li);
9663
+ let hits;
9664
+ if (reSource !== null) {
9665
+ try {
9666
+ hits = await getRegexRunner().testLines(text, reSource, reFlags, {
9667
+ signal: args.signal
9668
+ });
9669
+ } catch (err) {
9670
+ const reason = err.message;
9671
+ if (reason.includes("aborted")) throw err;
9672
+ regexSkippedFiles.push({ rel, reason });
9673
+ continue;
9674
+ }
9675
+ } else {
9676
+ hits = [];
9677
+ for (let li = 0; li < lines.length; li++) {
9678
+ throwIfAborted(args.signal);
9679
+ const lineForCheck = caseSensitive ? lines[li] : lines[li].toLowerCase();
9680
+ if (lineForCheck.includes(needle)) hits.push(li);
9681
+ }
9309
9682
  }
9310
9683
  scanned++;
9311
9684
  if (hits.length === 0) continue;
@@ -9354,6 +9727,11 @@ async function searchContent(ctx, startAbs, args) {
9354
9727
  }
9355
9728
  };
9356
9729
  await walk2(startAbs);
9730
+ if (regexSkippedFiles.length > 0) {
9731
+ pushLine(
9732
+ `[regex timed out on ${regexSkippedFiles.length} file${regexSkippedFiles.length === 1 ? "" : "s"} \u2014 pattern may have catastrophic backtracking; first: ${regexSkippedFiles[0].rel}]`
9733
+ );
9734
+ }
9357
9735
  if (matches.length === 0) {
9358
9736
  return scanned === 0 ? "(no files scanned \u2014 path empty or all files filtered out)" : `(no matches across ${scanned} file${scanned === 1 ? "" : "s"})`;
9359
9737
  }
@@ -9418,11 +9796,15 @@ function registerFilesystemTools(registry, opts) {
9418
9796
  const sessionApproved = /* @__PURE__ */ new Set();
9419
9797
  const shownSubdirMemory = /* @__PURE__ */ new Set();
9420
9798
  function withSubdirMemory(absPath, body) {
9421
- if (!memoryEnabled()) return body;
9422
- const ancestors = findSubdirMemoryAncestors(absPath, rootDir);
9423
- if (ancestors.length === 0) return body;
9799
+ return prependMemorySections(findSubdirMemoryAncestors(absPath, rootDir), body);
9800
+ }
9801
+ function withDirMemory(absDir, body) {
9802
+ return prependMemorySections(findDirMemory(absDir, rootDir), body);
9803
+ }
9804
+ function prependMemorySections(memPaths, body) {
9805
+ if (!memoryEnabled() || memPaths.length === 0) return body;
9424
9806
  const sections = [];
9425
- for (const memPath of [...ancestors].reverse()) {
9807
+ for (const memPath of [...memPaths].reverse()) {
9426
9808
  if (shownSubdirMemory.has(memPath)) continue;
9427
9809
  const content = readSubdirMemoryContent(memPath);
9428
9810
  if (!content) continue;
@@ -9615,7 +9997,7 @@ ${slice.join("\n")}`);
9615
9997
  for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
9616
9998
  lines.push(e.isDirectory() ? `${e.name}/` : e.name);
9617
9999
  }
9618
- return lines.join("\n") || "(empty directory)";
10000
+ return withDirMemory(abs, lines.join("\n") || "(empty directory)");
9619
10001
  }
9620
10002
  });
9621
10003
  registry.register({
@@ -12743,6 +13125,7 @@ var FETCH_MAX_BYTES = 10 * 1024 * 1024;
12743
13125
  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";
12744
13126
  var MOJEEK_ENDPOINT = "https://www.mojeek.com/search";
12745
13127
  var METASO_ENDPOINT = "https://metaso.cn/api/v1";
13128
+ var TAVILY_ENDPOINT = "https://api.tavily.com/search";
12746
13129
  function searchStatusError(status) {
12747
13130
  if (status === 429) return t("webErrors.rateLimit429");
12748
13131
  if (status === 403) return t("webErrors.forbidden403");
@@ -12762,6 +13145,9 @@ async function webSearch(query, opts = {}) {
12762
13145
  if (opts.engine === "searxng") {
12763
13146
  return searchSearxng(query, opts);
12764
13147
  }
13148
+ if (opts.engine === "tavily") {
13149
+ return searchTavily(query, opts);
13150
+ }
12765
13151
  return searchMojeek(query, opts);
12766
13152
  }
12767
13153
  async function searchMojeek(query, opts = {}) {
@@ -12896,6 +13282,55 @@ async function searchMetaso(query, opts = {}) {
12896
13282
  snippet: wp.snippet ?? wp.summary ?? ""
12897
13283
  }));
12898
13284
  }
13285
+ async function searchTavily(query, opts = {}) {
13286
+ const topK = Math.max(1, Math.min(20, opts.topK ?? DEFAULT_TOPK));
13287
+ const apiKey = loadTavilyApiKey();
13288
+ if (!apiKey) throw new Error(t("webErrors.tavilyMissingKey"));
13289
+ let resp;
13290
+ try {
13291
+ resp = await fetch(TAVILY_ENDPOINT, {
13292
+ method: "POST",
13293
+ headers: {
13294
+ "Content-Type": "application/json",
13295
+ Accept: "application/json"
13296
+ },
13297
+ body: JSON.stringify({
13298
+ api_key: apiKey,
13299
+ query,
13300
+ search_depth: "basic",
13301
+ max_results: topK,
13302
+ include_answer: false,
13303
+ include_raw_content: false,
13304
+ include_images: false
13305
+ }),
13306
+ signal: opts.signal
13307
+ });
13308
+ } catch (err) {
13309
+ if (err instanceof TypeError && err.message.includes("fetch")) {
13310
+ throw new Error(t("webErrors.cannotReach", { endpoint: TAVILY_ENDPOINT }));
13311
+ }
13312
+ throw err;
13313
+ }
13314
+ if (!resp.ok) {
13315
+ if (resp.status === 401 || resp.status === 403) {
13316
+ throw new Error(t("webErrors.tavilyUnauthorized"));
13317
+ }
13318
+ if (resp.status === 429) throw new Error(t("webErrors.tavilyRateLimit"));
13319
+ throw new Error(t("webErrors.tavilyServerError", { status: resp.status }));
13320
+ }
13321
+ let data;
13322
+ try {
13323
+ data = await resp.json();
13324
+ } catch {
13325
+ throw new Error(t("webErrors.tavilyParseError", { status: resp.status }));
13326
+ }
13327
+ const results = data.results ?? [];
13328
+ return results.slice(0, topK).map((r) => ({
13329
+ title: r.title,
13330
+ url: r.url,
13331
+ snippet: r.content ?? ""
13332
+ }));
13333
+ }
12899
13334
  function parseSearxngHtmlResults(html) {
12900
13335
  const root = parseHtml(html);
12901
13336
  const results = [];
@@ -13114,7 +13549,7 @@ function registerWebTools(registry, opts = {}) {
13114
13549
  const maxFetchChars = opts.maxFetchChars ?? DEFAULT_FETCH_MAX_CHARS;
13115
13550
  registry.register({
13116
13551
  name: "web_search",
13117
- 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.",
13552
+ 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.",
13118
13553
  readOnly: true,
13119
13554
  parallelSafe: true,
13120
13555
  parameters: {
@@ -13800,19 +14235,23 @@ var McpClient = class {
13800
14235
  return this._instructions;
13801
14236
  }
13802
14237
  /** Compliant servers reject other methods until this completes. */
13803
- async initialize() {
14238
+ async initialize(opts = {}) {
13804
14239
  if (this.initialized) throw new Error("MCP client already initialized");
13805
14240
  this.startReaderIfNeeded();
13806
- const result = await this.request("initialize", {
13807
- protocolVersion: MCP_PROTOCOL_VERSION,
13808
- // Advertise every method the client can consume so servers know
13809
- // they can send listChanged notifications etc. Sub-feature flags
13810
- // (e.g. `resources.subscribe`) are omitted we don't implement
13811
- // those yet and the empty object means "method-level support, no
13812
- // sub-features."
13813
- capabilities: { tools: {}, resources: {}, prompts: {} },
13814
- clientInfo: this.clientInfo
13815
- });
14241
+ const result = await this.request(
14242
+ "initialize",
14243
+ {
14244
+ protocolVersion: MCP_PROTOCOL_VERSION,
14245
+ // Advertise every method the client can consume so servers know
14246
+ // they can send listChanged notifications etc. Sub-feature flags
14247
+ // (e.g. `resources.subscribe`) are omitted — we don't implement
14248
+ // those yet and the empty object means "method-level support, no
14249
+ // sub-features."
14250
+ capabilities: { tools: {}, resources: {}, prompts: {} },
14251
+ clientInfo: this.clientInfo
14252
+ },
14253
+ opts.signal
14254
+ );
13816
14255
  this._serverCapabilities = result.capabilities ?? {};
13817
14256
  this._serverInfo = result.serverInfo ?? { name: "", version: "" };
13818
14257
  this._protocolVersion = result.protocolVersion ?? "";