reasonix 0.47.2 → 0.48.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 (122) hide show
  1. package/README.md +5 -26
  2. package/README.zh-CN.md +5 -26
  3. package/dist/cli/{acp-GEOAKSTU.js → acp-4ROCGYNH.js} +17 -17
  4. package/dist/cli/{chat-YTPATMMG.js → chat-GZNB5625.js} +24 -24
  5. package/dist/cli/{chunk-BQ6HC66J.js → chunk-2QSTA2QV.js} +3 -13
  6. package/dist/cli/chunk-2QSTA2QV.js.map +1 -0
  7. package/dist/cli/{chunk-DQ6K5ZQ7.js → chunk-3WGTGXO4.js} +2 -2
  8. package/dist/cli/{chunk-6QC5RQLE.js → chunk-5OHHAQ4W.js} +2 -2
  9. package/dist/cli/{chunk-XMHP7BEE.js → chunk-6MZTZO7A.js} +514 -791
  10. package/dist/cli/chunk-6MZTZO7A.js.map +1 -0
  11. package/dist/cli/chunk-7M4YYMKW.js +5198 -0
  12. package/dist/cli/chunk-7M4YYMKW.js.map +1 -0
  13. package/dist/cli/{chunk-KYQVQ5X4.js → chunk-B5CZL2SE.js} +9 -4
  14. package/dist/cli/chunk-B5CZL2SE.js.map +1 -0
  15. package/dist/cli/chunk-CDVSFSAK.js +17732 -0
  16. package/dist/cli/chunk-CDVSFSAK.js.map +1 -0
  17. package/dist/cli/{chunk-TRWHTFG7.js → chunk-DOWEOA6E.js} +2 -2
  18. package/dist/cli/chunk-EMMENC4O.js +831 -0
  19. package/dist/cli/chunk-EMMENC4O.js.map +1 -0
  20. package/dist/cli/{chunk-TDHXB2ER.js → chunk-H4CCXMDD.js} +2 -2
  21. package/dist/cli/{chunk-T5A7EY6B.js → chunk-HR5NBKEM.js} +2 -2
  22. package/dist/cli/{chunk-CNG32VAB.js → chunk-I4M5QJNL.js} +2 -2
  23. package/dist/cli/{chunk-5QCB62C4.js → chunk-J2TQAWOM.js} +135 -18
  24. package/dist/cli/{chunk-5QCB62C4.js.map → chunk-J2TQAWOM.js.map} +1 -1
  25. package/dist/cli/{chunk-DN4B5S6Y.js → chunk-JMDE6IO3.js} +2 -2
  26. package/dist/cli/{chunk-4MFCAZ2W.js → chunk-MOJYKO2A.js} +3 -3
  27. package/dist/cli/{chunk-HUILPCYX.js → chunk-MRZG4GBF.js} +3 -3
  28. package/dist/cli/{chunk-GH7DC2Y5.js → chunk-NMQSUNLB.js} +2 -2
  29. package/dist/cli/{chunk-ZXSCAODE.js → chunk-OB4BUJBL.js} +67 -2
  30. package/dist/cli/chunk-OB4BUJBL.js.map +1 -0
  31. package/dist/cli/{chunk-QCFLPSPH.js → chunk-OG5JANQ4.js} +2 -2
  32. package/dist/cli/{chunk-JBH5RM7X.js → chunk-OPYALNTT.js} +326 -55
  33. package/dist/cli/chunk-OPYALNTT.js.map +1 -0
  34. package/dist/cli/{chunk-CCJAP7G3.js → chunk-RUDBUHO4.js} +2 -2
  35. package/dist/cli/{chunk-2XY77LW7.js → chunk-S2RMQULY.js} +56 -24
  36. package/dist/cli/chunk-S2RMQULY.js.map +1 -0
  37. package/dist/cli/{chunk-DWPAKZTY.js → chunk-TE5UIIFL.js} +2 -2
  38. package/dist/cli/{chunk-KVZZ5U75.js → chunk-V4Y732RQ.js} +2 -2
  39. package/dist/cli/{chunk-TRSAHHCL.js → chunk-WZGNXR6E.js} +3 -3
  40. package/dist/cli/chunk-WZGNXR6E.js.map +1 -0
  41. package/dist/cli/{chunk-NRQ5UP5T.js → chunk-YW63N3ZR.js} +116 -28
  42. package/dist/cli/chunk-YW63N3ZR.js.map +1 -0
  43. package/dist/cli/{code-Q4NRVEDG.js → code-PMPJWXEO.js} +30 -31
  44. package/dist/cli/code-PMPJWXEO.js.map +1 -0
  45. package/dist/cli/{commands-4CDI4GFM.js → commands-QS6TG4G3.js} +4 -4
  46. package/dist/cli/{commit-GW7LDQP5.js → commit-XPRSKUBF.js} +3 -3
  47. package/dist/cli/{desktop-EG6P5SF2.js → desktop-562OPWIU.js} +461 -43
  48. package/dist/cli/desktop-562OPWIU.js.map +1 -0
  49. package/dist/cli/{diff-VI2YX4FN.js → diff-I6W4AUWJ.js} +8 -8
  50. package/dist/cli/{doctor-CQTTZP27.js → doctor-6XVZKT4U.js} +9 -9
  51. package/dist/cli/index.js +52 -40
  52. package/dist/cli/index.js.map +1 -1
  53. package/dist/cli/{mcp-J2UCD4RZ.js → mcp-7W7ANO2Y.js} +2 -2
  54. package/dist/cli/{mcp-browse-GSX34JEK.js → mcp-browse-LA4I4YIZ.js} +2 -2
  55. package/dist/cli/{mcp-inspect-RRFYF4ZV.js → mcp-inspect-LWXXU7BY.js} +2 -2
  56. package/dist/cli/{prompt-5TQPIVHV.js → prompt-RKZD4X6Y.js} +3 -3
  57. package/dist/cli/{replay-MJCEMODU.js → replay-2X7MVXOI.js} +8 -8
  58. package/dist/cli/{run-P4D5VDYE.js → run-TPKXIJ27.js} +13 -13
  59. package/dist/cli/{server-C25JNNZV.js → server-NHQ3QXOZ.js} +15 -14
  60. package/dist/cli/{server-C25JNNZV.js.map → server-NHQ3QXOZ.js.map} +1 -1
  61. package/dist/cli/{sessions-QIONZJQ6.js → sessions-2A4DGSHA.js} +12 -12
  62. package/dist/cli/{setup-NLQ6G5G4.js → setup-GOLP7J4C.js} +5 -5
  63. package/dist/cli/{stats-DFZEXHP4.js → stats-CGDAFDKI.js} +6 -6
  64. package/dist/cli/{version-GR3X3MPI.js → version-FIL4ZFOS.js} +12 -12
  65. package/dist/grammars/tree-sitter-go.wasm +0 -0
  66. package/dist/grammars/tree-sitter-java.wasm +0 -0
  67. package/dist/grammars/tree-sitter-javascript.wasm +0 -0
  68. package/dist/grammars/tree-sitter-python.wasm +0 -0
  69. package/dist/grammars/tree-sitter-rust.wasm +0 -0
  70. package/dist/grammars/tree-sitter-tsx.wasm +0 -0
  71. package/dist/grammars/tree-sitter-typescript.wasm +0 -0
  72. package/dist/grammars/web-tree-sitter.wasm +0 -0
  73. package/dist/index.d.ts +38 -10
  74. package/dist/index.js +488 -87
  75. package/dist/index.js.map +1 -1
  76. package/package.json +12 -3
  77. package/dist/cli/chunk-2XY77LW7.js.map +0 -1
  78. package/dist/cli/chunk-6CRPCJAU.js +0 -3141
  79. package/dist/cli/chunk-6CRPCJAU.js.map +0 -1
  80. package/dist/cli/chunk-BQ6HC66J.js.map +0 -1
  81. package/dist/cli/chunk-JBH5RM7X.js.map +0 -1
  82. package/dist/cli/chunk-KYQVQ5X4.js.map +0 -1
  83. package/dist/cli/chunk-NRQ5UP5T.js.map +0 -1
  84. package/dist/cli/chunk-TRSAHHCL.js.map +0 -1
  85. package/dist/cli/chunk-XD6P7AFH.js +0 -375
  86. package/dist/cli/chunk-XD6P7AFH.js.map +0 -1
  87. package/dist/cli/chunk-XMHP7BEE.js.map +0 -1
  88. package/dist/cli/chunk-YFP3MYMY.js +0 -323
  89. package/dist/cli/chunk-YFP3MYMY.js.map +0 -1
  90. package/dist/cli/chunk-ZXSCAODE.js.map +0 -1
  91. package/dist/cli/code-Q4NRVEDG.js.map +0 -1
  92. package/dist/cli/desktop-EG6P5SF2.js.map +0 -1
  93. /package/dist/cli/{acp-GEOAKSTU.js.map → acp-4ROCGYNH.js.map} +0 -0
  94. /package/dist/cli/{chat-YTPATMMG.js.map → chat-GZNB5625.js.map} +0 -0
  95. /package/dist/cli/{chunk-DQ6K5ZQ7.js.map → chunk-3WGTGXO4.js.map} +0 -0
  96. /package/dist/cli/{chunk-6QC5RQLE.js.map → chunk-5OHHAQ4W.js.map} +0 -0
  97. /package/dist/cli/{chunk-TRWHTFG7.js.map → chunk-DOWEOA6E.js.map} +0 -0
  98. /package/dist/cli/{chunk-TDHXB2ER.js.map → chunk-H4CCXMDD.js.map} +0 -0
  99. /package/dist/cli/{chunk-T5A7EY6B.js.map → chunk-HR5NBKEM.js.map} +0 -0
  100. /package/dist/cli/{chunk-CNG32VAB.js.map → chunk-I4M5QJNL.js.map} +0 -0
  101. /package/dist/cli/{chunk-DN4B5S6Y.js.map → chunk-JMDE6IO3.js.map} +0 -0
  102. /package/dist/cli/{chunk-4MFCAZ2W.js.map → chunk-MOJYKO2A.js.map} +0 -0
  103. /package/dist/cli/{chunk-HUILPCYX.js.map → chunk-MRZG4GBF.js.map} +0 -0
  104. /package/dist/cli/{chunk-GH7DC2Y5.js.map → chunk-NMQSUNLB.js.map} +0 -0
  105. /package/dist/cli/{chunk-QCFLPSPH.js.map → chunk-OG5JANQ4.js.map} +0 -0
  106. /package/dist/cli/{chunk-CCJAP7G3.js.map → chunk-RUDBUHO4.js.map} +0 -0
  107. /package/dist/cli/{chunk-DWPAKZTY.js.map → chunk-TE5UIIFL.js.map} +0 -0
  108. /package/dist/cli/{chunk-KVZZ5U75.js.map → chunk-V4Y732RQ.js.map} +0 -0
  109. /package/dist/cli/{commands-4CDI4GFM.js.map → commands-QS6TG4G3.js.map} +0 -0
  110. /package/dist/cli/{commit-GW7LDQP5.js.map → commit-XPRSKUBF.js.map} +0 -0
  111. /package/dist/cli/{diff-VI2YX4FN.js.map → diff-I6W4AUWJ.js.map} +0 -0
  112. /package/dist/cli/{doctor-CQTTZP27.js.map → doctor-6XVZKT4U.js.map} +0 -0
  113. /package/dist/cli/{mcp-J2UCD4RZ.js.map → mcp-7W7ANO2Y.js.map} +0 -0
  114. /package/dist/cli/{mcp-browse-GSX34JEK.js.map → mcp-browse-LA4I4YIZ.js.map} +0 -0
  115. /package/dist/cli/{mcp-inspect-RRFYF4ZV.js.map → mcp-inspect-LWXXU7BY.js.map} +0 -0
  116. /package/dist/cli/{prompt-5TQPIVHV.js.map → prompt-RKZD4X6Y.js.map} +0 -0
  117. /package/dist/cli/{replay-MJCEMODU.js.map → replay-2X7MVXOI.js.map} +0 -0
  118. /package/dist/cli/{run-P4D5VDYE.js.map → run-TPKXIJ27.js.map} +0 -0
  119. /package/dist/cli/{sessions-QIONZJQ6.js.map → sessions-2A4DGSHA.js.map} +0 -0
  120. /package/dist/cli/{setup-NLQ6G5G4.js.map → setup-GOLP7J4C.js.map} +0 -0
  121. /package/dist/cli/{stats-DFZEXHP4.js.map → stats-CGDAFDKI.js.map} +0 -0
  122. /package/dist/cli/{version-GR3X3MPI.js.map → version-FIL4ZFOS.js.map} +0 -0
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import { createParser } from "eventsource-parser";
5
5
  import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "fs";
6
6
  import { homedir } from "os";
7
7
  import { dirname, isAbsolute, join, resolve } from "path";
8
+ import { z } from "zod";
8
9
 
9
10
  // src/cli/ui/theme/tokens.ts
10
11
  function card(fg, tone) {
@@ -478,14 +479,65 @@ function loadTavilyApiKey(path2 = defaultConfigPath()) {
478
479
  if (cfg && typeof cfg === "string" && cfg.trim()) return cfg.trim();
479
480
  return void 0;
480
481
  }
482
+ function loadPerplexityApiKey(path2 = defaultConfigPath()) {
483
+ if (process.env.PERPLEXITY_API_KEY) return process.env.PERPLEXITY_API_KEY.trim();
484
+ const cfg = readConfig(path2).perplexityApiKey;
485
+ if (cfg && typeof cfg === "string" && cfg.trim()) return cfg.trim();
486
+ return void 0;
487
+ }
488
+ function loadExaApiKey(path2 = defaultConfigPath()) {
489
+ if (process.env.EXA_API_KEY) return process.env.EXA_API_KEY.trim();
490
+ const cfg = readConfig(path2).exaApiKey;
491
+ if (cfg && typeof cfg === "string" && cfg.trim()) return cfg.trim();
492
+ return void 0;
493
+ }
481
494
  function defaultConfigPath() {
482
495
  return join(homedir(), ".reasonix", "config.json");
483
496
  }
497
+ var STRING_ARRAY_FIELDS = [
498
+ ["mcp"],
499
+ ["mcpDisabled"],
500
+ ["recentWorkspaces"],
501
+ ["skills", "paths"]
502
+ ];
503
+ var stringArraySchema = z.array(z.string());
504
+ function sanitizeStringArrayField(cfg, segments, filePath) {
505
+ if (segments.length === 0) return;
506
+ let parent = cfg;
507
+ for (let i = 0; i < segments.length - 1; i++) {
508
+ const seg = segments[i];
509
+ const next = parent[seg];
510
+ if (!next || typeof next !== "object" || Array.isArray(next)) return;
511
+ parent = next;
512
+ }
513
+ const leaf = segments[segments.length - 1];
514
+ const value = parent[leaf];
515
+ if (value === void 0) return;
516
+ const fieldName = segments.join(".");
517
+ if (!Array.isArray(value)) {
518
+ console.warn(`reasonix: config "${filePath}" field "${fieldName}" is not an array \u2014 ignoring`);
519
+ delete parent[leaf];
520
+ return;
521
+ }
522
+ const parsed = stringArraySchema.safeParse(value);
523
+ if (parsed.success) return;
524
+ const filtered = value.filter((x) => typeof x === "string");
525
+ console.warn(
526
+ `reasonix: config "${filePath}" field "${fieldName}" had ${value.length - filtered.length} non-string item(s) \u2014 dropped`
527
+ );
528
+ parent[leaf] = filtered;
529
+ }
484
530
  function readConfig(path2 = defaultConfigPath()) {
485
531
  try {
486
- const raw = readFileSync(path2, "utf8");
532
+ const raw = readFileSync(path2, "utf8").replace(/^\uFEFF/, "");
487
533
  const parsed = JSON.parse(raw);
488
- if (parsed && typeof parsed === "object") return parsed;
534
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
535
+ const cfg = parsed;
536
+ for (const segments of STRING_ARRAY_FIELDS) {
537
+ sanitizeStringArrayField(cfg, segments, path2);
538
+ }
539
+ return cfg;
540
+ }
489
541
  } catch {
490
542
  }
491
543
  return {};
@@ -580,6 +632,8 @@ function webSearchEngine(path2 = defaultConfigPath()) {
580
632
  if (cfg === "searxng") return "searxng";
581
633
  if (cfg === "metaso") return "metaso";
582
634
  if (cfg === "tavily") return "tavily";
635
+ if (cfg === "perplexity") return "perplexity";
636
+ if (cfg === "exa") return "exa";
583
637
  return "mojeek";
584
638
  }
585
639
  function webSearchEndpoint(path2 = defaultConfigPath()) {
@@ -1286,6 +1340,8 @@ var EN = {
1286
1340
  tipShownOnce: "shown once",
1287
1341
  modelOverride: "override the default model",
1288
1342
  noSession: "disable session persistence for this run",
1343
+ noMouseHint: "disable SGR mouse tracking; restores native drag-select and right-click",
1344
+ noProxyHint: "ignore HTTPS_PROXY / HTTP_PROXY for this run; go direct",
1289
1345
  resumeHint: "force-resume the named session (even if idle)",
1290
1346
  newHint: "force a fresh session (ignore --session / --continue)",
1291
1347
  transcriptHint: "path to write the JSONL transcript",
@@ -1427,6 +1483,7 @@ var EN = {
1427
1483
  },
1428
1484
  stop: { description: "abort the current model turn (typed alternative to Esc)" },
1429
1485
  feedback: { description: "open a GitHub issue with diagnostic info copied to clipboard" },
1486
+ about: { description: "project info \u2014 version, website, repo, license" },
1430
1487
  keys: { description: "keyboard + mouse + copy/paste reference" },
1431
1488
  plans: { description: "list this session's active + archived plans, newest first" },
1432
1489
  replay: {
@@ -1502,8 +1559,8 @@ var EN = {
1502
1559
  argsHint: "<question>"
1503
1560
  },
1504
1561
  "search-engine": {
1505
- description: "switch web search backend \u2014 mojeek (default, no deps), searxng (self-hosted), or metaso (free quota 100/d)",
1506
- argsHint: "<mojeek|searxng|metaso> [<endpoint>]"
1562
+ description: "switch web search backend \u2014 mojeek (default, no deps), searxng (self-hosted), metaso (free 100/d), tavily (free 1000/mo), perplexity (AI-native), or exa (AI-native)",
1563
+ argsHint: "<mojeek|searxng|metaso|tavily|perplexity|exa> [<key>]"
1507
1564
  }
1508
1565
  },
1509
1566
  wizard: {
@@ -1694,7 +1751,22 @@ var EN = {
1694
1751
  continuingAfter: "\u25B8 continuing after {label}{counter}",
1695
1752
  planStoppedAt: "\u25B8 plan stopped at {label}{counter}",
1696
1753
  revisingAfter: "\u25B8 revising after {label} \u2014 {feedback}",
1697
- historyScrollHint: " \u2191 reading history \xB7 End / PgDn returns to bottom \xB7 \u2193 advances one line"
1754
+ historyScrollHint: " \u2191 reading history \xB7 End / PgDn returns to bottom \xB7 \u2193 advances one line",
1755
+ editHistoryTitle: "Edit history (oldest first):",
1756
+ editHistoryNoCodeMode: "not in code mode",
1757
+ editHistoryNoEdits: "no edits recorded this session yet",
1758
+ editHistoryNoShowId: "usage: /show [id] [path] (omit id for newest; path from the per-file summary)",
1759
+ editHistoryIdNotFound: "no edit #{id} \u2014 run /history to see valid ids",
1760
+ editHistoryLookupFailed: "unexpected: history lookup failed",
1761
+ editHistoryBatchNoFile: `batch #{id} doesn't include "{path}" \u2014 files in this batch: {files}`,
1762
+ editHistoryNoEdits2: "no edits recorded this session \u2014 /history is empty",
1763
+ editHistoryStatusApplied: "applied",
1764
+ editHistoryStatusPartial: "PARTIAL",
1765
+ editHistoryStatusUndone: "UNDONE",
1766
+ editHistoryHelpShow: "/show <id> \u2192 per-file summary \xB7 /show <id> <path> \u2192 full diff of one file",
1767
+ editHistoryHelpUndo: "/undo \u2192 newest non-undone \xB7 /undo <id> [path] \u2192 target a specific batch or file",
1768
+ editHistoryAlreadyReverted: "(already reverted \u2014 /history shows the batch-level status)",
1769
+ editHistoryRevertFile: "/undo {id} {path} \u2192 revert just this file"
1698
1770
  },
1699
1771
  hooks: {
1700
1772
  head: "hook {tag} `{cmd}` {decision}{truncTag}",
@@ -1795,6 +1867,10 @@ var EN = {
1795
1867
  loopNoActiveHint: "no active loop. Start one with `/loop <interval> <prompt>` (e.g. /loop 30s npm test).\nCancels on: /loop stop \xB7 Esc \xB7 /clear /new \xB7 any user-typed prompt.",
1796
1868
  loopStarted: '\u25B8 loop started \u2014 re-submitting "{prompt}" every {duration}. Type anything (or /loop stop) to cancel.',
1797
1869
  keysNeedsTui: "/keys needs a TUI context (postKeys wired).",
1870
+ aboutHeader: "Reasonix v{version} \u2014 a cache-first DeepSeek coding agent",
1871
+ aboutWebsiteLabel: "Website",
1872
+ aboutRepoLabel: "GitHub ",
1873
+ aboutLicenseLabel: "License",
1798
1874
  unknownCommand: "unknown command: /{cmd} \u2014 did you mean {list}?",
1799
1875
  unknownCommandShort: "unknown command: /{cmd} (try /help)"
1800
1876
  },
@@ -2112,6 +2188,8 @@ var EN = {
2112
2188
  usageSearxngUrl: " /search-engine searxng <url> use SearXNG at custom endpoint",
2113
2189
  usageMetaso: " /search-engine metaso use Metaso API (100/d free, configure your own API key for more)",
2114
2190
  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)",
2191
+ usagePerplexity: " /search-engine perplexity use Perplexity AI (AI-native answer + citations \u2014 set PERPLEXITY_API_KEY or perplexityApiKey in config; get one at https://perplexity.ai/settings/api)",
2192
+ usageExa: " /search-engine exa use Exa API (AI-native answer + citations, free 1000/mo \u2014 set EXA_API_KEY or exaApiKey in config; sign up at https://exa.ai)",
2115
2193
  alias: "Alias: /se",
2116
2194
  searxngInfo: "SearXNG is a self-hosted metasearch engine (https://github.com/searxng/searxng).",
2117
2195
  searxngInstall: "Install it with: docker run -d -p 8080:8080 searxng/searxng",
@@ -2119,7 +2197,11 @@ var EN = {
2119
2197
  switchedSearxngNote: " Make sure SearXNG is running at {endpoint}.",
2120
2198
  switchedMetasoNote: " There is a daily quota of 100 (configure your own API key for higher limits).",
2121
2199
  switchedTavilyNote: " Set TAVILY_API_KEY or `tavilyApiKey` in config; free 1000/mo at https://tavily.com.",
2122
- confirmed: '\u2713 Web search engine set to "{engine}"{detail}. Next assistant turn will pick up the change.',
2200
+ switchedPerplexityNote: " Set PERPLEXITY_API_KEY or `perplexityApiKey` in config; get one at https://perplexity.ai/settings/api.",
2201
+ switchedExaNote: " Set EXA_API_KEY or `exaApiKey` in config; sign up at https://exa.ai.",
2202
+ keyNeeded: 'No API key configured for "{engine}".\n\n 1. Set the {envVar} environment variable\n 2. Or provide one inline: /search-engine {engine} <your-key>\n 3. Or add "{engine}ApiKey" to ~/.reasonix/config.json\n\nThen retry /search-engine {engine}.',
2203
+ keySaved: " API key saved to config.",
2204
+ confirmed: 'Web search engine set to "{engine}"{detail}. Next assistant turn will pick up the change.',
2123
2205
  confirmedDetail: " ({endpoint})"
2124
2206
  },
2125
2207
  skill: {
@@ -2378,27 +2460,37 @@ var EN = {
2378
2460
  probeFailed: "probe failed \u2014 {message}"
2379
2461
  },
2380
2462
  webErrors: {
2381
- status: "web_search {status} \u2014 try: the search backend returned an error; rephrase the query, or switch engine with /search-engine mojeek|searxng",
2463
+ status: "web_search {status} \u2014 try: the search backend returned an error; rephrase the query, or switch engine with /search-engine mojeek|searxng|metaso|tavily|perplexity|exa",
2382
2464
  rateLimit429: "web_search 429 \u2014 try: wait 10s before retrying, or rephrase the query; the search backend is rate-limiting this client",
2383
- forbidden403: "web_search 403 \u2014 try: the search backend is blocking this client; switch engine with /search-engine mojeek|searxng, or wait and retry later",
2465
+ forbidden403: "web_search 403 \u2014 try: the search backend is blocking this client; switch engine with /search-engine mojeek|searxng|metaso|tavily|perplexity|exa, or wait and retry later",
2384
2466
  serverError5xx: "web_search {status} \u2014 try: open the search URL in a browser; if it loads this is transient and a retry in 30s may help",
2385
- mojeekBlocked: "web_search: Mojeek anti-bot page \u2014 rate-limited or blocked \u2014 try: wait 30s and retry, or switch engine with /search-engine searxng",
2386
- mojeekNoResults: "web_search: 0 results but response doesn't look like a real empty page ({chars} chars, first 120: {preview}) \u2014 try: rephrase the query with simpler terms, or switch engine with /search-engine searxng",
2467
+ mojeekBlocked: "web_search: Mojeek anti-bot page \u2014 rate-limited or blocked \u2014 try: wait 30s and retry, or switch engine with /search-engine mojeek|searxng|metaso|tavily|perplexity|exa",
2468
+ mojeekNoResults: "web_search: 0 results but response doesn't look like a real empty page ({chars} chars, first 120: {preview}) \u2014 try: rephrase the query with simpler terms, or switch engine with /search-engine mojeek|searxng|metaso|tavily|perplexity|exa",
2387
2469
  invalidEndpoint: 'web_search: invalid SearXNG endpoint "{endpoint}" \u2014 try: set a valid URL with /search-endpoint http://host:port',
2388
2470
  endpointMustBeHttp: "web_search: SearXNG endpoint must be http(s), got {protocol} \u2014 try: set a valid URL with /search-endpoint http://host:port",
2389
- cannotReach: "web_search: Cannot reach SearXNG server at {endpoint} \u2014 try: install and start SearXNG (https://github.com/searxng/searxng, e.g. `docker run -d -p 8080:8080 searxng/searxng`), or switch to the default engine with /search-engine mojeek",
2390
- searxngNoResults: "web_search: 0 results but SearXNG response doesn't look like an empty results page ({chars} chars) \u2014 try: rephrase the query with simpler terms, or switch engine with /search-engine mojeek",
2471
+ cannotReach: "web_search: Cannot reach SearXNG server at {endpoint} \u2014 try: install and start SearXNG (https://github.com/searxng/searxng, e.g. `docker run -d -p 8080:8080 searxng/searxng`), or switch to another engine with /search-engine mojeek|searxng|metaso|tavily|perplexity|exa",
2472
+ searxngNoResults: "web_search: 0 results but SearXNG response doesn't look like an empty results page ({chars} chars) \u2014 try: rephrase the query with simpler terms, or switch engine with /search-engine mojeek|searxng|metaso|tavily|perplexity|exa",
2391
2473
  metasoDailyLimit: "web_search: daily search limit reached for the default API key \u2014 set your own METASO_API_KEY env var or get one at https://metaso.cn/search-api/playground",
2392
2474
  metasoUnauthorized: "web_search: Metaso API key rejected \u2014 check METASO_API_KEY or get one at https://metaso.cn/search-api/playground",
2393
2475
  metasoRateLimit: "web_search: Metaso rate-limited \u2014 wait and retry, or get your own API key at https://metaso.cn/search-api/playground",
2394
- metasoServerError: "web_search: Metaso server error ({status}) \u2014 try again later, or switch engine with /search-engine mojeek",
2476
+ metasoServerError: "web_search: Metaso server error ({status}) \u2014 try again later, or switch engine with /search-engine mojeek|searxng|metaso|tavily|perplexity|exa",
2395
2477
  metasoParseError: "web_search: Metaso returned unparseable response (HTTP {status}) \u2014 try again later",
2396
2478
  metasoApiError: "web_search: Metaso API error (code {code}: {message}) \u2014 try again later",
2397
2479
  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",
2398
2480
  tavilyUnauthorized: "web_search: Tavily API key rejected \u2014 check TAVILY_API_KEY or get one at https://tavily.com",
2399
- tavilyRateLimit: "web_search: Tavily rate-limited or monthly quota exceeded \u2014 wait, switch engine with /search-engine mojeek, or upgrade your Tavily plan",
2400
- tavilyServerError: "web_search: Tavily server error ({status}) \u2014 try again later, or switch engine with /search-engine mojeek",
2481
+ tavilyRateLimit: "web_search: Tavily rate-limited or monthly quota exceeded \u2014 wait, switch engine with /search-engine mojeek|searxng|metaso|tavily|perplexity|exa, or upgrade your Tavily plan",
2482
+ tavilyServerError: "web_search: Tavily server error ({status}) \u2014 try again later, or switch engine with /search-engine mojeek|searxng|metaso|tavily|perplexity|exa",
2401
2483
  tavilyParseError: "web_search: Tavily returned unparseable response (HTTP {status}) \u2014 try again later",
2484
+ perplexityMissingKey: "web_search: Perplexity backend requires an API key \u2014 set PERPLEXITY_API_KEY env var or `perplexityApiKey` in ~/.reasonix/config.json; get one at https://perplexity.ai/settings/api",
2485
+ perplexityUnauthorized: "web_search: Perplexity API key rejected \u2014 check PERPLEXITY_API_KEY or get one at https://perplexity.ai/settings/api",
2486
+ perplexityRateLimit: "web_search: Perplexity rate-limited \u2014 wait and retry, or switch engine with /search-engine mojeek|searxng|metaso|tavily|perplexity|exa",
2487
+ perplexityServerError: "web_search: Perplexity server error ({status}) \u2014 try again later, or switch engine with /search-engine mojeek|searxng|metaso|tavily|perplexity|exa",
2488
+ perplexityParseError: "web_search: Perplexity returned unparseable response (HTTP {status}) \u2014 try again later",
2489
+ exaMissingKey: "web_search: Exa backend requires an API key \u2014 set EXA_API_KEY env var or `exaApiKey` in ~/.reasonix/config.json; free 1000/mo signup at https://exa.ai",
2490
+ exaUnauthorized: "web_search: Exa API key rejected \u2014 check EXA_API_KEY or get one at https://exa.ai",
2491
+ exaRateLimit: "web_search: Exa API rate-limited or monthly quota exceeded \u2014 wait or upgrade at https://exa.ai/pricing",
2492
+ exaServerError: "web_search: Exa server error ({status}) \u2014 try again later, or switch engine with /search-engine mojeek|searxng|metaso|tavily|perplexity|exa",
2493
+ exaParseError: "web_search: Exa returned unparseable response (HTTP {status}) \u2014 try again later",
2402
2494
  fetchStatus: "web_fetch {status} for {url} \u2014 try: confirm the URL resolves in a browser; status suggests the host returned an error page",
2403
2495
  fetchRateLimit429: "web_fetch 429 for {url} \u2014 try: wait 10s before retrying; the host is rate-limiting this client",
2404
2496
  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",
@@ -2566,6 +2658,12 @@ var EN = {
2566
2658
  serverCount: "{count} server{s}",
2567
2659
  footer: "\u2191\u2193 pick \xB7 [r] reconnect \xB7 [d] disable \xB7 esc quit"
2568
2660
  },
2661
+ mcpBrowse: {
2662
+ noResources: "No resources on any connected MCP server (or no servers connected). `/mcp` shows the current set.",
2663
+ readOne: "Read one: `/resource <uri>` \u2014 or use Tab in the picker.",
2664
+ noPrompts: "No prompts on any connected MCP server (or no servers connected). `/mcp` shows the current set.",
2665
+ fetchOne: "Fetch one: `/prompt <name>` \u2014 args are not supported yet; prompts with required args will surface an error from the server."
2666
+ },
2569
2667
  mcpLifecycle: {
2570
2668
  handshake: "handshake\u2026",
2571
2669
  connected: "connected",
@@ -2796,6 +2894,8 @@ var zhCN = {
2796
2894
  tipShownOnce: "\u4EC5\u663E\u793A\u4E00\u6B21",
2797
2895
  modelOverride: "\u8986\u76D6\u9ED8\u8BA4\u6A21\u578B",
2798
2896
  noSession: "\u7981\u7528\u672C\u6B21\u8FD0\u884C\u7684\u4F1A\u8BDD\u6301\u4E45\u5316",
2897
+ noMouseHint: "\u5173\u95ED SGR \u9F20\u6807\u8DDF\u8E2A\uFF1B\u6062\u590D\u7EC8\u7AEF\u539F\u751F\u62D6\u9009\u548C\u53F3\u952E\u884C\u4E3A",
2898
+ noProxyHint: "\u672C\u6B21\u8FD0\u884C\u5FFD\u7565 HTTPS_PROXY / HTTP_PROXY\uFF0C\u76F4\u8FDE",
2799
2899
  resumeHint: "\u5F3A\u5236\u6062\u590D\u6307\u5B9A\u4F1A\u8BDD\uFF08\u5373\u4F7F\u7A7A\u95F2\uFF09",
2800
2900
  newHint: "\u5F3A\u5236\u521B\u5EFA\u65B0\u4F1A\u8BDD\uFF08\u5FFD\u7565 --session / --continue\uFF09",
2801
2901
  transcriptHint: "JSONL \u8F6C\u5F55\u7A3F\u7684\u5199\u5165\u8DEF\u5F84",
@@ -2940,6 +3040,7 @@ var zhCN = {
2940
3040
  },
2941
3041
  stop: { description: "\u4E2D\u6B62\u5F53\u524D\u6A21\u578B\u56DE\u5408\uFF08\u6309 Esc \u7684\u66FF\u4EE3\u65B9\u5F0F\uFF09" },
2942
3042
  feedback: { description: "\u6253\u5F00 GitHub Issue\uFF0C\u8BCA\u65AD\u4FE1\u606F\u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F" },
3043
+ about: { description: "\u9879\u76EE\u4FE1\u606F \u2014 \u7248\u672C\u3001\u5B98\u7F51\u3001\u4ED3\u5E93\u3001\u534F\u8BAE" },
2943
3044
  plans: { description: "\u5217\u51FA\u6B64\u4F1A\u8BDD\u7684\u6D3B\u8DC3 + \u5F52\u6863\u8BA1\u5212\uFF08\u6700\u65B0\u5728\u524D\uFF09" },
2944
3045
  replay: {
2945
3046
  description: "\u52A0\u8F7D\u5F52\u6863\u8BA1\u5212\u4E3A\u53EA\u8BFB\u7684\u65F6\u95F4\u65C5\u884C\u5FEB\u7167\uFF08\u9ED8\u8BA4\uFF1A\u6700\u65B0\uFF09",
@@ -3016,8 +3117,8 @@ var zhCN = {
3016
3117
  argsHint: "<question>"
3017
3118
  },
3018
3119
  "search-engine": {
3019
- description: "\u5207\u6362\u7F51\u7EDC\u641C\u7D22\u540E\u7AEF \u2014 mojeek\uFF08\u9ED8\u8BA4\uFF0C\u65E0\u4F9D\u8D56\uFF09\u3001searxng\uFF08\u81EA\u6258\u7BA1\uFF09\u6216 metaso\uFF08\u6BCF\u65E5 100 \u6B21\u514D\u8D39\u989D\u5EA6\uFF09",
3020
- argsHint: "<mojeek|searxng|metaso> [<endpoint>]"
3120
+ description: "\u5207\u6362\u7F51\u7EDC\u641C\u7D22\u540E\u7AEF \u2014 mojeek\uFF08\u9ED8\u8BA4\uFF0C\u65E0\u4F9D\u8D56\uFF09\u3001searxng\uFF08\u81EA\u6258\u7BA1\uFF09\u3001metaso\uFF08\u6BCF\u65E5 100 \u6B21\uFF09\u3001tavily\uFF08\u6BCF\u6708 1000 \u6B21\u514D\u8D39\uFF09\u3001perplexity\uFF08AI \u76F4\u63A5\u56DE\u7B54\uFF09\u6216 exa\uFF08AI \u76F4\u63A5\u56DE\u7B54\uFF09",
3121
+ argsHint: "<mojeek|searxng|metaso|tavily|perplexity|exa> [<key>]"
3021
3122
  }
3022
3123
  },
3023
3124
  wizard: {
@@ -3208,7 +3309,22 @@ var zhCN = {
3208
3309
  continuingAfter: "\u25B8 \u5728 {label}{counter} \u4E4B\u540E\u7EE7\u7EED",
3209
3310
  planStoppedAt: "\u25B8 \u8BA1\u5212\u5728 {label}{counter} \u5904\u505C\u6B62",
3210
3311
  revisingAfter: "\u25B8 \u5728 {label} \u4E4B\u540E\u4FEE\u8BA2 \u2014 {feedback}",
3211
- historyScrollHint: " \u2191 \u6B63\u5728\u67E5\u770B\u5386\u53F2 \xB7 End / PgDn \u8FD4\u56DE\u5E95\u90E8 \xB7 \u2193 \u5411\u4E0B\u6EDA\u52A8\u4E00\u884C"
3312
+ historyScrollHint: " \u2191 \u6B63\u5728\u67E5\u770B\u5386\u53F2 \xB7 End / PgDn \u8FD4\u56DE\u5E95\u90E8 \xB7 \u2193 \u5411\u4E0B\u6EDA\u52A8\u4E00\u884C",
3313
+ editHistoryTitle: "\u7F16\u8F91\u5386\u53F2\uFF08\u4ECE\u65E7\u5230\u65B0\uFF09\uFF1A",
3314
+ editHistoryNoCodeMode: "\u4E0D\u5728\u4EE3\u7801\u6A21\u5F0F\u4E2D",
3315
+ editHistoryNoEdits: "\u6B64\u4F1A\u8BDD\u5C1A\u672A\u8BB0\u5F55\u4EFB\u4F55\u7F16\u8F91",
3316
+ editHistoryNoShowId: "\u7528\u6CD5\uFF1A/show [id] [path] \uFF08\u7701\u7565 id \u67E5\u770B\u6700\u65B0\uFF1Bpath \u6765\u81EA\u6587\u4EF6\u6458\u8981\uFF09",
3317
+ editHistoryIdNotFound: "\u672A\u627E\u5230\u7F16\u8F91 #{id} \u2014 \u8FD0\u884C /history \u67E5\u770B\u6709\u6548 ID",
3318
+ editHistoryLookupFailed: "\u610F\u5916\u9519\u8BEF\uFF1A\u5386\u53F2\u67E5\u627E\u5931\u8D25",
3319
+ editHistoryBatchNoFile: '\u6279\u6B21 #{id} \u4E0D\u5305\u542B "{path}" \u2014 \u6B64\u6279\u6B21\u4E2D\u7684\u6587\u4EF6\uFF1A{files}',
3320
+ editHistoryNoEdits2: "\u6B64\u4F1A\u8BDD\u5C1A\u672A\u8BB0\u5F55\u7F16\u8F91 \u2014 /history \u4E3A\u7A7A",
3321
+ editHistoryStatusApplied: "\u5DF2\u5E94\u7528",
3322
+ editHistoryStatusPartial: "\u90E8\u5206\u5E94\u7528",
3323
+ editHistoryStatusUndone: "\u5DF2\u64A4\u9500",
3324
+ editHistoryHelpShow: "/show <id> \u2192 \u6587\u4EF6\u6458\u8981 \xB7 /show <id> <path> \u2192 \u67D0\u4E2A\u6587\u4EF6\u7684\u5B8C\u6574 diff",
3325
+ editHistoryHelpUndo: "/undo \u2192 \u6700\u65B0\u7684\u672A\u64A4\u9500\u9879 \xB7 /undo <id> [path] \u2192 \u6307\u5B9A\u6279\u6B21\u6216\u6587\u4EF6",
3326
+ editHistoryAlreadyReverted: "\uFF08\u5DF2\u64A4\u9500 \u2014 /history \u663E\u793A\u6279\u6B21\u7EA7\u72B6\u6001\uFF09",
3327
+ editHistoryRevertFile: "/undo {id} {path} \u2192 \u4EC5\u8FD8\u539F\u6B64\u6587\u4EF6"
3212
3328
  },
3213
3329
  hooks: {
3214
3330
  head: "\u94A9\u5B50 {tag} `{cmd}` {decision}{truncTag}",
@@ -3309,6 +3425,10 @@ var zhCN = {
3309
3425
  loopNoActiveHint: "\u6CA1\u6709\u6D3B\u52A8\u7684\u5FAA\u73AF\u3002\u4F7F\u7528 `/loop <interval> <prompt>` \u542F\u52A8\u4E00\u4E2A\uFF08\u4F8B\u5982 /loop 30s npm test\uFF09\u3002\n\u53D6\u6D88\u65B9\u5F0F\uFF1A/loop stop \xB7 Esc \xB7 /clear /new \xB7 \u4EFB\u4F55\u7528\u6237\u8F93\u5165\u7684\u63D0\u793A\u3002",
3310
3426
  loopStarted: '\u25B8 \u5FAA\u73AF\u5DF2\u542F\u52A8 \u2014 \u6BCF {duration} \u91CD\u65B0\u63D0\u4EA4 "{prompt}"\u3002\u8F93\u5165\u4EFB\u4F55\u5185\u5BB9\uFF08\u6216 /loop stop\uFF09\u53D6\u6D88\u3002',
3311
3427
  keysNeedsTui: "/keys \u9700\u8981 TUI \u4E0A\u4E0B\u6587\uFF08postKeys \u5DF2\u8FDE\u63A5\uFF09\u3002",
3428
+ aboutHeader: "Reasonix v{version} \u2014 \u7F13\u5B58\u4F18\u5148\u7684 DeepSeek \u7F16\u7801\u4EE3\u7406",
3429
+ aboutWebsiteLabel: "\u5B98\u7F51",
3430
+ aboutRepoLabel: "\u4ED3\u5E93",
3431
+ aboutLicenseLabel: "\u534F\u8BAE",
3312
3432
  unknownCommand: "\u672A\u77E5\u547D\u4EE4\uFF1A/{cmd} \u2014 \u4F60\u662F\u4E0D\u662F\u60F3\u7528 {list}\uFF1F",
3313
3433
  unknownCommandShort: "\u672A\u77E5\u547D\u4EE4\uFF1A/{cmd} \uFF08\u8BD5\u8BD5 /help\uFF09"
3314
3434
  },
@@ -3626,6 +3746,8 @@ var zhCN = {
3626
3746
  usageSearxngUrl: " /search-engine searxng <url> \u4F7F\u7528 SearXNG \u81EA\u5B9A\u4E49\u7AEF\u70B9",
3627
3747
  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",
3628
3748
  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",
3749
+ usagePerplexity: " /search-engine perplexity \u4F7F\u7528 Perplexity AI\uFF08AI \u76F4\u63A5\u56DE\u7B54 + \u5F15\u7528 \u2014 \u8BBE\u7F6E PERPLEXITY_API_KEY \u6216 config \u7684 perplexityApiKey\uFF1B\u5728 https://perplexity.ai/settings/api \u83B7\u53D6\u5BC6\u94A5\uFF09",
3750
+ usageExa: " /search-engine exa \u4F7F\u7528 Exa API\uFF08AI \u76F4\u63A5\u56DE\u7B54 + \u5F15\u7528\uFF0C\u6BCF\u6708 1000 \u6B21\u514D\u8D39 \u2014 \u8BBE\u7F6E EXA_API_KEY \u6216 config \u7684 exaApiKey\uFF1B\u6CE8\u518C https://exa.ai\uFF09",
3629
3751
  alias: "\u522B\u540D\uFF1A/se",
3630
3752
  searxngInfo: "SearXNG \u662F\u4E00\u4E2A\u81EA\u6258\u7BA1\u7684\u5143\u641C\u7D22\u5F15\u64CE\uFF08https://github.com/searxng/searxng\uFF09\u3002",
3631
3753
  searxngInstall: "\u5B89\u88C5\u547D\u4EE4\uFF1A docker run -d -p 8080:8080 searxng/searxng",
@@ -3633,7 +3755,11 @@ var zhCN = {
3633
3755
  switchedSearxngNote: " \u8BF7\u786E\u4FDD SearXNG \u5728 {endpoint} \u8FD0\u884C\u3002",
3634
3756
  switchedMetasoNote: " \u6BCF\u65E5\u9650\u989D 100 \u6B21\uFF08\u914D\u7F6E\u4F60\u81EA\u5DF1\u7684 API \u5BC6\u94A5\u53EF\u63D0\u5347\u9650\u989D\uFF09\u3002",
3635
3757
  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",
3636
- 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',
3758
+ switchedPerplexityNote: " \u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF PERPLEXITY_API_KEY \u6216 config \u4E2D\u7684 `perplexityApiKey`\uFF1B\u5728 https://perplexity.ai/settings/api \u83B7\u53D6\u5BC6\u94A5\u3002",
3759
+ switchedExaNote: " \u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF EXA_API_KEY \u6216 config \u4E2D\u7684 `exaApiKey`\uFF1B\u6CE8\u518C https://exa.ai\u3002",
3760
+ keyNeeded: '\u672A\u914D\u7F6E "{engine}" \u7684 API \u5BC6\u94A5\u3002\n\n 1. \u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF {envVar}\n 2. \u6216\u5185\u8054\u63D0\u4F9B\uFF1A/search-engine {engine} <your-key>\n 3. \u6216\u5728 ~/.reasonix/config.json \u4E2D\u6DFB\u52A0 "{engine}ApiKey"\n\n\u5B8C\u6210\u540E\u91CD\u65B0\u6267\u884C /search-engine {engine}\u3002',
3761
+ keySaved: " API \u5BC6\u94A5\u5DF2\u4FDD\u5B58\u5230\u914D\u7F6E\u3002",
3762
+ confirmed: '\u7F51\u9875\u641C\u7D22\u5F15\u64CE\u5DF2\u8BBE\u4E3A "{engine}"{detail}\u3002\u4E0B\u4E00\u8F6E\u6A21\u578B\u8C03\u7528\u5C06\u751F\u6548\u3002',
3637
3763
  confirmedDetail: "\uFF08{endpoint}\uFF09"
3638
3764
  },
3639
3765
  skill: {
@@ -3892,27 +4018,37 @@ var zhCN = {
3892
4018
  probeFailed: "\u63A2\u6D4B\u5931\u8D25 \u2014 {message}"
3893
4019
  },
3894
4020
  webErrors: {
3895
- status: "web_search {status} \u2014 try: \u641C\u7D22\u540E\u7AEF\u8FD4\u56DE\u9519\u8BEF\uFF1B\u8BF7\u6539\u5199\u67E5\u8BE2\uFF0C\u6216\u4F7F\u7528 /search-engine mojeek|searxng \u5207\u6362\u5F15\u64CE",
4021
+ status: "web_search {status} \u2014 try: \u641C\u7D22\u540E\u7AEF\u8FD4\u56DE\u9519\u8BEF\uFF1B\u8BF7\u6539\u5199\u67E5\u8BE2\uFF0C\u6216\u4F7F\u7528 /search-engine mojeek|searxng|metaso|tavily|perplexity|exa \u5207\u6362\u5F15\u64CE",
3896
4022
  rateLimit429: "web_search 429 \u2014 try: \u7B49\u5F85 10 \u79D2\u540E\u91CD\u8BD5\uFF0C\u6216\u6539\u5199\u67E5\u8BE2\uFF1B\u641C\u7D22\u540E\u7AEF\u6B63\u5728\u5BF9\u8BE5\u5BA2\u6237\u7AEF\u8FDB\u884C\u9650\u6D41",
3897
- forbidden403: "web_search 403 \u2014 try: \u641C\u7D22\u540E\u7AEF\u62D2\u7EDD\u8BE5\u5BA2\u6237\u7AEF\u8BBF\u95EE\uFF1B\u4F7F\u7528 /search-engine mojeek|searxng \u5207\u6362\u5F15\u64CE\uFF0C\u6216\u7A0D\u540E\u91CD\u8BD5",
4023
+ forbidden403: "web_search 403 \u2014 try: \u641C\u7D22\u540E\u7AEF\u62D2\u7EDD\u8BE5\u5BA2\u6237\u7AEF\u8BBF\u95EE\uFF1B\u4F7F\u7528 /search-engine mojeek|searxng|metaso|tavily|perplexity|exa \u5207\u6362\u5F15\u64CE\uFF0C\u6216\u7A0D\u540E\u91CD\u8BD5",
3898
4024
  serverError5xx: "web_search {status} \u2014 try: \u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u641C\u7D22 URL\uFF1B\u82E5\u80FD\u52A0\u8F7D\u5219\u5C5E\u4E34\u65F6\u6545\u969C\uFF0C\u7B49 30 \u79D2\u91CD\u8BD5\u5373\u53EF",
3899
- mojeekBlocked: "web_search: Mojeek \u53CD\u722C\u9875\u9762 \u2014 \u9891\u7387\u9650\u5236\u6216\u88AB\u5C4F\u853D \u2014 try: \u7B49\u5F85 30 \u79D2\u540E\u91CD\u8BD5\uFF0C\u6216\u4F7F\u7528 /search-engine searxng \u5207\u6362\u5F15\u64CE",
3900
- mojeekNoResults: "web_search: \u8FD4\u56DE 0 \u6761\u7ED3\u679C\u4F46\u54CD\u5E94\u770B\u8D77\u6765\u4E0D\u662F\u6B63\u5E38\u7A7A\u7ED3\u679C\u9875\uFF08{chars} \u5B57\u7B26\uFF0C\u524D 120 \u5B57\u7B26\uFF1A{preview}\uFF09\u2014 try: \u4F7F\u7528\u66F4\u7B80\u5355\u7684\u5173\u952E\u8BCD\u6539\u5199\u67E5\u8BE2\uFF0C\u6216\u4F7F\u7528 /search-engine searxng \u5207\u6362\u5F15\u64CE",
4025
+ mojeekBlocked: "web_search: Mojeek \u53CD\u722C\u9875\u9762 \u2014 \u9891\u7387\u9650\u5236\u6216\u88AB\u5C4F\u853D \u2014 try: \u7B49\u5F85 30 \u79D2\u540E\u91CD\u8BD5\uFF0C\u6216\u4F7F\u7528 /search-engine mojeek|searxng|metaso|tavily|perplexity|exa \u5207\u6362\u5F15\u64CE",
4026
+ mojeekNoResults: "web_search: \u8FD4\u56DE 0 \u6761\u7ED3\u679C\u4F46\u54CD\u5E94\u770B\u8D77\u6765\u4E0D\u662F\u6B63\u5E38\u7A7A\u7ED3\u679C\u9875\uFF08{chars} \u5B57\u7B26\uFF0C\u524D 120 \u5B57\u7B26\uFF1A{preview}\uFF09\u2014 try: \u4F7F\u7528\u66F4\u7B80\u5355\u7684\u5173\u952E\u8BCD\u6539\u5199\u67E5\u8BE2\uFF0C\u6216\u4F7F\u7528 /search-engine mojeek|searxng|metaso|tavily|perplexity|exa \u5207\u6362\u5F15\u64CE",
3901
4027
  invalidEndpoint: 'web_search: \u65E0\u6548\u7684 SearXNG \u7AEF\u70B9 "{endpoint}" \u2014 try: \u4F7F\u7528 /search-endpoint http://host:port \u8BBE\u7F6E\u6709\u6548\u7684 URL',
3902
4028
  endpointMustBeHttp: "web_search: SearXNG \u7AEF\u70B9\u5FC5\u987B\u662F http(s) \u534F\u8BAE\uFF0C\u5F53\u524D\u4E3A {protocol} \u2014 try: \u4F7F\u7528 /search-endpoint http://host:port \u8BBE\u7F6E\u6709\u6548\u7684 URL",
3903
- cannotReach: "web_search: \u65E0\u6CD5\u8BBF\u95EE SearXNG \u670D\u52A1\u5668 {endpoint} \u2014 try: \u5B89\u88C5\u5E76\u542F\u52A8 SearXNG\uFF08https://github.com/searxng/searxng\uFF0C\u4F8B\u5982 `docker run -d -p 8080:8080 searxng/searxng`\uFF09\uFF0C\u6216\u4F7F\u7528 /search-engine mojeek \u5207\u6362\u5230\u9ED8\u8BA4\u5F15\u64CE",
3904
- searxngNoResults: "web_search: \u8FD4\u56DE 0 \u6761\u7ED3\u679C\u4F46 SearXNG \u54CD\u5E94\u770B\u8D77\u6765\u4E0D\u662F\u6B63\u5E38\u7A7A\u7ED3\u679C\u9875\uFF08{chars} \u5B57\u7B26\uFF09\u2014 try: \u4F7F\u7528\u66F4\u7B80\u5355\u7684\u5173\u952E\u8BCD\u6539\u5199\u67E5\u8BE2\uFF0C\u6216\u4F7F\u7528 /search-engine mojeek \u5207\u6362\u5F15\u64CE",
4029
+ cannotReach: "web_search: \u65E0\u6CD5\u8BBF\u95EE SearXNG \u670D\u52A1\u5668 {endpoint} \u2014 try: \u5B89\u88C5\u5E76\u542F\u52A8 SearXNG\uFF08https://github.com/searxng/searxng\uFF0C\u4F8B\u5982 `docker run -d -p 8080:8080 searxng/searxng`\uFF09\uFF0C\u6216\u4F7F\u7528 /search-engine mojeek|searxng|metaso|tavily|perplexity|exa \u5207\u6362\u5F15\u64CE",
4030
+ searxngNoResults: "web_search: \u8FD4\u56DE 0 \u6761\u7ED3\u679C\u4F46 SearXNG \u54CD\u5E94\u770B\u8D77\u6765\u4E0D\u662F\u6B63\u5E38\u7A7A\u7ED3\u679C\u9875\uFF08{chars} \u5B57\u7B26\uFF09\u2014 try: \u4F7F\u7528\u66F4\u7B80\u5355\u7684\u5173\u952E\u8BCD\u6539\u5199\u67E5\u8BE2\uFF0C\u6216\u4F7F\u7528 /search-engine mojeek|searxng|metaso|tavily|perplexity|exa \u5207\u6362\u5F15\u64CE",
3905
4031
  metasoDailyLimit: "web_search: \u9ED8\u8BA4 API \u5BC6\u94A5\u7684\u6BCF\u65E5\u641C\u7D22\u6B21\u6570\u5DF2\u8FBE\u4E0A\u9650 \u2014 \u8BBE\u7F6E METASO_API_KEY \u73AF\u5883\u53D8\u91CF\uFF0C\u6216\u5728 https://metaso.cn/search-api/playground \u83B7\u53D6\u81EA\u5DF1\u7684\u5BC6\u94A5",
3906
4032
  metasoUnauthorized: "web_search: Metaso API \u5BC6\u94A5\u88AB\u62D2\u7EDD \u2014 \u68C0\u67E5 METASO_API_KEY\uFF0C\u6216\u5728 https://metaso.cn/search-api/playground \u83B7\u53D6\u5BC6\u94A5",
3907
4033
  metasoRateLimit: "web_search: Metaso \u8BF7\u6C42\u9891\u7387\u9650\u5236 \u2014 \u7B49\u5F85\u540E\u91CD\u8BD5\uFF0C\u6216\u5728 https://metaso.cn/search-api/playground \u83B7\u53D6\u81EA\u5DF1\u7684\u5BC6\u94A5",
3908
- 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",
4034
+ metasoServerError: "web_search: Metaso \u670D\u52A1\u5668\u9519\u8BEF\uFF08{status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u4F7F\u7528 /search-engine mojeek|searxng|metaso|tavily|perplexity|exa \u5207\u6362\u5F15\u64CE",
3909
4035
  metasoParseError: "web_search: Metaso \u8FD4\u56DE\u65E0\u6CD5\u89E3\u6790\u7684\u54CD\u5E94\uFF08HTTP {status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5",
3910
4036
  metasoApiError: "web_search: Metaso API \u9519\u8BEF\uFF08code {code}: {message}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5",
3911
4037
  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",
3912
4038
  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",
3913
- 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",
3914
- 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",
4039
+ 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|searxng|metaso|tavily|perplexity|exa \u5207\u6362\u5F15\u64CE\uFF0C\u6216\u5347\u7EA7 Tavily \u8BA1\u5212",
4040
+ tavilyServerError: "web_search: Tavily \u670D\u52A1\u5668\u9519\u8BEF\uFF08{status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u4F7F\u7528 /search-engine mojeek|searxng|metaso|tavily|perplexity|exa \u5207\u6362\u5F15\u64CE",
3915
4041
  tavilyParseError: "web_search: Tavily \u8FD4\u56DE\u65E0\u6CD5\u89E3\u6790\u7684\u54CD\u5E94\uFF08HTTP {status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5",
4042
+ perplexityMissingKey: "web_search: Perplexity \u540E\u7AEF\u9700\u8981 API \u5BC6\u94A5 \u2014 \u8BBE\u7F6E PERPLEXITY_API_KEY \u73AF\u5883\u53D8\u91CF\uFF0C\u6216\u5728 ~/.reasonix/config.json \u4E2D\u914D\u7F6E `perplexityApiKey`\uFF1B\u5728 https://perplexity.ai/settings/api \u83B7\u53D6\u5BC6\u94A5",
4043
+ perplexityUnauthorized: "web_search: Perplexity API \u5BC6\u94A5\u88AB\u62D2\u7EDD \u2014 \u68C0\u67E5 PERPLEXITY_API_KEY\uFF0C\u6216\u5728 https://perplexity.ai/settings/api \u83B7\u53D6\u5BC6\u94A5",
4044
+ perplexityRateLimit: "web_search: Perplexity \u8BF7\u6C42\u9891\u7387\u9650\u5236 \u2014 \u7B49\u5F85\u540E\u91CD\u8BD5\uFF0C\u6216\u4F7F\u7528 /search-engine mojeek|searxng|metaso|tavily|perplexity|exa \u5207\u6362\u5F15\u64CE",
4045
+ perplexityServerError: "web_search: Perplexity \u670D\u52A1\u5668\u9519\u8BEF\uFF08{status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u4F7F\u7528 /search-engine mojeek|searxng|metaso|tavily|perplexity|exa \u5207\u6362\u5F15\u64CE",
4046
+ perplexityParseError: "web_search: Perplexity \u8FD4\u56DE\u65E0\u6CD5\u89E3\u6790\u7684\u54CD\u5E94\uFF08HTTP {status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5",
4047
+ exaMissingKey: "web_search: Exa \u540E\u7AEF\u9700\u8981 API \u5BC6\u94A5 \u2014 \u8BBE\u7F6E EXA_API_KEY \u73AF\u5883\u53D8\u91CF\uFF0C\u6216\u5728 ~/.reasonix/config.json \u4E2D\u914D\u7F6E `exaApiKey`\uFF1Bhttps://exa.ai \u6BCF\u6708 1000 \u6B21\u514D\u8D39",
4048
+ exaUnauthorized: "web_search: Exa API \u5BC6\u94A5\u88AB\u62D2\u7EDD \u2014 \u68C0\u67E5 EXA_API_KEY\uFF0C\u6216\u5728 https://exa.ai \u83B7\u53D6\u5BC6\u94A5",
4049
+ exaRateLimit: "web_search: Exa \u8BF7\u6C42\u9891\u7387\u9650\u5236\u6216\u6708\u5EA6\u914D\u989D\u7528\u5C3D \u2014 \u7B49\u5F85\u5347\u7EA7\uFF0C\u6216\u5728 https://exa.ai/pricing \u67E5\u770B\u8BA1\u5212",
4050
+ exaServerError: "web_search: Exa \u670D\u52A1\u5668\u9519\u8BEF\uFF08{status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u4F7F\u7528 /search-engine mojeek|searxng|metaso|tavily|perplexity|exa \u5207\u6362\u5F15\u64CE",
4051
+ exaParseError: "web_search: Exa \u8FD4\u56DE\u65E0\u6CD5\u89E3\u6790\u7684\u54CD\u5E94\uFF08HTTP {status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5",
3916
4052
  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",
3917
4053
  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",
3918
4054
  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",
@@ -4080,6 +4216,12 @@ var zhCN = {
4080
4216
  serverCount: "{count} \u4E2A\u670D\u52A1\u5668",
4081
4217
  footer: "\u2191\u2193 \u9009\u62E9 \xB7 [r] \u91CD\u8FDE \xB7 [d] \u7981\u7528 \xB7 Esc \u9000\u51FA"
4082
4218
  },
4219
+ mcpBrowse: {
4220
+ noResources: "\u6CA1\u6709\u4EFB\u4F55\u5DF2\u8FDE\u63A5 MCP \u670D\u52A1\u5668\u4E0A\u7684\u8D44\u6E90\uFF08\u6216\u65E0\u670D\u52A1\u5668\u8FDE\u63A5\uFF09\u3002`/mcp` \u663E\u793A\u5F53\u524D\u5217\u8868\u3002",
4221
+ readOne: "\u8BFB\u53D6\uFF1A`/resource <uri>` \u2014 \u6216\u5728\u9009\u62E9\u5668\u4E2D\u4F7F\u7528 Tab \u952E\u3002",
4222
+ noPrompts: "\u6CA1\u6709\u4EFB\u4F55\u5DF2\u8FDE\u63A5 MCP \u670D\u52A1\u5668\u4E0A\u7684\u63D0\u793A\uFF08\u6216\u65E0\u670D\u52A1\u5668\u8FDE\u63A5\uFF09\u3002`/mcp` \u663E\u793A\u5F53\u524D\u5217\u8868\u3002",
4223
+ fetchOne: "\u83B7\u53D6\uFF1A`/prompt <name>` \u2014 \u6682\u4E0D\u652F\u6301\u53C2\u6570\uFF1B\u5E26\u5FC5\u9700\u53C2\u6570\u7684\u63D0\u793A\u5C06\u8FD4\u56DE\u670D\u52A1\u5668\u9519\u8BEF\u3002"
4224
+ },
4083
4225
  mcpLifecycle: {
4084
4226
  handshake: "\u63E1\u624B\u4E2D\u2026",
4085
4227
  connected: "\u5DF2\u8FDE\u63A5",
@@ -4826,8 +4968,8 @@ var ToolRegistry = class {
4826
4968
  _resultAugmenter = null;
4827
4969
  /** Per-tool fingerprint of the last call that failed schema validation. Cleared by any successful validation for that tool. */
4828
4970
  _lastMalformed = /* @__PURE__ */ new Map();
4829
- /** Per-tool fingerprint of the last host-side interceptor rejection. */
4830
- _lastInterceptorRejection = /* @__PURE__ */ new Map();
4971
+ /** Per-tool fingerprint of the last host-side gate rejection. */
4972
+ _lastGateRejection = /* @__PURE__ */ new Map();
4831
4973
  constructor(opts = {}) {
4832
4974
  this._autoFlatten = opts.autoFlatten !== false;
4833
4975
  }
@@ -4914,20 +5056,21 @@ var ToolRegistry = class {
4914
5056
  if (!tool) {
4915
5057
  return JSON.stringify({ error: `unknown tool: ${name}` });
4916
5058
  }
4917
- const fingerprint = fingerprintArgs(argumentsRaw);
5059
+ const rawFingerprint = rawFingerprintArgs(argumentsRaw);
4918
5060
  let args;
4919
5061
  try {
4920
5062
  args = typeof argumentsRaw === "string" ? argumentsRaw.trim() ? JSON.parse(argumentsRaw) ?? {} : {} : argumentsRaw ?? {};
4921
5063
  } catch (err) {
4922
5064
  return this._noteMalformed(
4923
5065
  name,
4924
- fingerprint,
5066
+ rawFingerprint,
4925
5067
  `invalid tool arguments JSON: ${err.message}`
4926
5068
  );
4927
5069
  }
4928
5070
  if (tool.flatSchema && args && typeof args === "object" && hasDotKey(args)) {
4929
5071
  args = nestArguments(args);
4930
5072
  }
5073
+ const fingerprint = fingerprintArgs(args);
4931
5074
  const missing = tool.parameters ? missingRequiredParam(tool.parameters, args) : null;
4932
5075
  if (missing) {
4933
5076
  return this._noteMalformed(
@@ -4948,7 +5091,7 @@ var ToolRegistry = class {
4948
5091
  try {
4949
5092
  const short = await interceptor(name, args);
4950
5093
  if (typeof short === "string") {
4951
- const guarded = this._noteInterceptorRejection(name, fingerprint, short);
5094
+ const guarded = this._noteGateRejection(name, fingerprint, short);
4952
5095
  return this._augmentResult(name, args, guarded);
4953
5096
  }
4954
5097
  } catch (err) {
@@ -4957,7 +5100,6 @@ var ToolRegistry = class {
4957
5100
  });
4958
5101
  }
4959
5102
  }
4960
- this._lastInterceptorRejection.delete(name);
4961
5103
  if (opts.signal?.aborted) {
4962
5104
  return JSON.stringify({
4963
5105
  error: `${name}: aborted before dispatch (user interrupt)`,
@@ -4995,6 +5137,7 @@ var ToolRegistry = class {
4995
5137
  finalResult = JSON.stringify({ error: `${e.name}: ${e.message}` });
4996
5138
  }
4997
5139
  }
5140
+ finalResult = this._noteGateRejection(name, fingerprint, finalResult);
4998
5141
  return this._augmentResult(name, args, finalResult);
4999
5142
  }
5000
5143
  _augmentResult(name, args, result) {
@@ -5018,18 +5161,18 @@ var ToolRegistry = class {
5018
5161
  }
5019
5162
  return JSON.stringify({ error: `${name}: ${detail}` });
5020
5163
  }
5021
- _noteInterceptorRejection(name, fingerprint, result) {
5022
- const reason = rejectedReason(result);
5164
+ _noteGateRejection(name, fingerprint, result) {
5165
+ const reason = rejectedReason(name, result);
5023
5166
  if (!reason) {
5024
- this._lastInterceptorRejection.delete(name);
5167
+ this._lastGateRejection.delete(name);
5025
5168
  return result;
5026
5169
  }
5027
5170
  const key = `${reason}:${fingerprint}`;
5028
- const prev = this._lastInterceptorRejection.get(name);
5029
- this._lastInterceptorRejection.set(name, key);
5171
+ const prev = this._lastGateRejection.get(name);
5172
+ this._lastGateRejection.set(name, key);
5030
5173
  if (prev === key) {
5031
5174
  return JSON.stringify({
5032
- error: `${name}: same call was just rejected by ${reason} \u2014 do not retry identical args. Switch to read-only exploration, submit or revise the plan, or choose a different tool call.`,
5175
+ error: `${name}: same call was just rejected by ${reason} \u2014 do not retry identical args. ${rejectionRecoveryHint(reason)}`,
5033
5176
  rejectedReason: reason,
5034
5177
  consecutiveInterceptorRejection: true
5035
5178
  });
@@ -5037,16 +5180,44 @@ var ToolRegistry = class {
5037
5180
  return result;
5038
5181
  }
5039
5182
  };
5040
- function rejectedReason(result) {
5183
+ function rejectedReason(name, result) {
5184
+ const textReason = plainTextRejectedReason(name, result);
5185
+ if (textReason) return textReason;
5041
5186
  try {
5042
5187
  const parsed = JSON.parse(result);
5043
5188
  if (!parsed || typeof parsed !== "object") return null;
5044
5189
  const reason = parsed.rejectedReason;
5045
- return typeof reason === "string" && reason ? reason : null;
5190
+ if (typeof reason === "string" && reason) return reason;
5191
+ const error = parsed.error;
5192
+ if (typeof error === "string") return plainTextRejectedReason(name, error);
5193
+ return null;
5046
5194
  } catch {
5047
5195
  return null;
5048
5196
  }
5049
5197
  }
5198
+ function plainTextRejectedReason(name, result) {
5199
+ if ((name === "edit_file" || name === "write_file") && /rejected this edit/i.test(result)) {
5200
+ return "edit-gate";
5201
+ }
5202
+ if ((name === "run_command" || name === "run_background") && /\buser denied:/i.test(result)) {
5203
+ return "shell-gate";
5204
+ }
5205
+ return null;
5206
+ }
5207
+ function rejectionRecoveryHint(reason) {
5208
+ switch (reason) {
5209
+ case "edit-gate":
5210
+ return "Do not re-emit the same edit. Try a genuinely different edit or ask the user how to proceed.";
5211
+ case "shell-gate":
5212
+ return "Do not retry the same command. Use an allowlisted/read-only command, wait for approval, or ask the user how to proceed.";
5213
+ case "engineering-lifecycle":
5214
+ return "Switch to read-only exploration, submit or revise the plan, or choose a different tool call.";
5215
+ case "engineering-lifecycle-evidence":
5216
+ return "Submit completion evidence or revise/checkpoint the plan before marking the step complete.";
5217
+ default:
5218
+ return "Choose a different tool call or ask the user how to proceed.";
5219
+ }
5220
+ }
5050
5221
  function isReadOnlyCall(tool, args) {
5051
5222
  if (tool.readOnlyCheck) {
5052
5223
  try {
@@ -5065,14 +5236,27 @@ function hasDotKey(obj) {
5065
5236
  }
5066
5237
  return false;
5067
5238
  }
5068
- function fingerprintArgs(argumentsRaw) {
5239
+ function rawFingerprintArgs(argumentsRaw) {
5069
5240
  if (typeof argumentsRaw === "string") return argumentsRaw;
5241
+ return fingerprintArgs(argumentsRaw);
5242
+ }
5243
+ function fingerprintArgs(args) {
5070
5244
  try {
5071
- return JSON.stringify(argumentsRaw);
5245
+ return JSON.stringify(sortJson(args));
5072
5246
  } catch {
5073
5247
  return "";
5074
5248
  }
5075
5249
  }
5250
+ function sortJson(value) {
5251
+ if (Array.isArray(value)) return value.map(sortJson);
5252
+ if (!value || typeof value !== "object") return value;
5253
+ const out = {};
5254
+ for (const key of Object.keys(value).sort()) {
5255
+ const item = value[key];
5256
+ if (item !== void 0) out[key] = sortJson(item);
5257
+ }
5258
+ return out;
5259
+ }
5076
5260
  function missingRequiredParam(schema, args) {
5077
5261
  const required = schema.required;
5078
5262
  if (!required || required.length === 0) return null;
@@ -5764,6 +5948,20 @@ var ContextManager = class {
5764
5948
  this.deps = deps;
5765
5949
  }
5766
5950
  deps;
5951
+ /** Real-time token count of the current log — used by Desktop to refresh the
5952
+ * context meter after /compact when no API usage event is available. */
5953
+ getLogTokens() {
5954
+ const entries = this.deps.log.toMessages();
5955
+ let total = 0;
5956
+ for (const e of entries) {
5957
+ const content = typeof e.content === "string" ? e.content : "";
5958
+ total += countTokensBounded(content);
5959
+ if (e.role === "assistant" && Array.isArray(e.tool_calls) && e.tool_calls.length > 0) {
5960
+ total += countTokensBounded(JSON.stringify(e.tool_calls));
5961
+ }
5962
+ }
5963
+ return total;
5964
+ }
5767
5965
  /** Decision after a turn's response — fold, exit with summary, or carry on. */
5768
5966
  decideAfterUsage(usage, model, alreadyFoldedThisTurn) {
5769
5967
  const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model] ?? DEFAULT_CONTEXT_TOKENS;
@@ -6115,13 +6313,11 @@ async function* forceSummaryAfterIterLimit(ctx, opts) {
6115
6313
  content: "The turn is being force-summarized (context guard or stuck-state). Summarize in plain prose what you learned from the tool results above. Do NOT emit any tool calls, function-call markup, DSML invocations, or SEARCH/REPLACE edit blocks \u2014 they will be silently discarded. Just plain text."
6116
6314
  });
6117
6315
  const summaryModel = "deepseek-v4-flash";
6118
- const summaryEffort = "high";
6119
6316
  const resp = await ctx.client.chat({
6120
6317
  model: summaryModel,
6121
6318
  messages,
6122
6319
  signal: ctx.signal,
6123
- thinking: thinkingModeForModel(summaryModel),
6124
- reasoningEffort: summaryEffort
6320
+ thinking: "disabled"
6125
6321
  });
6126
6322
  const rawContent = resp.content?.trim() ?? "";
6127
6323
  const cleaned = stripHallucinatedToolMarkup(rawContent);
@@ -6860,6 +7056,10 @@ var CacheFirstLoop = class {
6860
7056
  async compactHistory(opts) {
6861
7057
  return this.context.fold(this.model, opts);
6862
7058
  }
7059
+ /** Real-time token count of the current log — forwarded to Desktop for meter refresh. */
7060
+ getCurrentLogTokens() {
7061
+ return this.context.getLogTokens();
7062
+ }
6863
7063
  appendAndPersist(message) {
6864
7064
  this.log.append(message);
6865
7065
  if (this.sessionName) {
@@ -7175,21 +7375,24 @@ ${reason}`
7175
7375
  const toolSpecs = this.prefix.tools();
7176
7376
  for (let iter = 0; ; iter++) {
7177
7377
  if (signal.aborted) {
7178
- yield {
7179
- turn: this._turn,
7180
- role: "warning",
7181
- content: t("loop.abortedAtIter", { iter })
7182
- };
7183
- const stoppedMsg = "[aborted by user (Esc) \u2014 no summary produced. Ask again or /retry when ready; prior tool output is still in the log.]";
7184
- this.appendAndPersist(buildSyntheticAssistantMessage(stoppedMsg, this.model));
7185
- yield {
7186
- turn: this._turn,
7187
- role: "assistant_final",
7188
- content: stoppedMsg,
7189
- forcedSummary: true
7190
- };
7191
- yield { turn: this._turn, role: "done", content: stoppedMsg };
7192
- this._turnAbort = new AbortController();
7378
+ try {
7379
+ yield {
7380
+ turn: this._turn,
7381
+ role: "warning",
7382
+ content: t("loop.abortedAtIter", { iter })
7383
+ };
7384
+ const stoppedMsg = "[aborted by user (Esc) \u2014 no summary produced. Ask again or /retry when ready; prior tool output is still in the log.]";
7385
+ this.appendAndPersist(buildSyntheticAssistantMessage(stoppedMsg, this.model));
7386
+ yield {
7387
+ turn: this._turn,
7388
+ role: "assistant_final",
7389
+ content: stoppedMsg,
7390
+ forcedSummary: true
7391
+ };
7392
+ yield { turn: this._turn, role: "done", content: stoppedMsg };
7393
+ } finally {
7394
+ this._turnAbort = new AbortController();
7395
+ }
7193
7396
  return;
7194
7397
  }
7195
7398
  if (iter > 0) {
@@ -7365,8 +7568,11 @@ ${reason}`
7365
7568
  }
7366
7569
  } catch (err) {
7367
7570
  if (signal.aborted) {
7368
- yield { turn: this._turn, role: "done", content: "" };
7369
- this._turnAbort = new AbortController();
7571
+ try {
7572
+ yield { turn: this._turn, role: "done", content: "" };
7573
+ } finally {
7574
+ this._turnAbort = new AbortController();
7575
+ }
7370
7576
  return;
7371
7577
  }
7372
7578
  const probe = is5xxError(err) ? await probeDeepSeekReachable(this.client) : void 0;
@@ -9841,7 +10047,9 @@ var DEFAULT_OUTLINE_THRESHOLD_BYTES = 64 * 1024;
9841
10047
  var DEFAULT_MAX_LIST_BYTES = 256 * 1024;
9842
10048
  var HARD_MAX_FILE_BYTES = 32 * 1024 * 1024;
9843
10049
  var OUTLINE_HEAD_LINES = 80;
9844
- var SKIP_DIR_NAMES = new Set(DEFAULT_INDEX_EXCLUDES.dirs);
10050
+ var SKIP_DIR_NAMES = new Set(
10051
+ DEFAULT_INDEX_EXCLUDES.dirs.filter((d) => d !== ".reasonix")
10052
+ );
9845
10053
  var BINARY_EXTENSIONS = new Set(DEFAULT_INDEX_EXCLUDES.exts);
9846
10054
  function displayRel4(rootDir, full) {
9847
10055
  return pathMod5.relative(rootDir, full).replaceAll("\\", "/");
@@ -10868,6 +11076,21 @@ function sanitizeEvidence(raw) {
10868
11076
  }
10869
11077
  return out.length > 0 ? out : void 0;
10870
11078
  }
11079
+ function summarizeEvidence(evidence) {
11080
+ if (!evidence || evidence.length === 0) return void 0;
11081
+ const parts = evidence.map((item) => `${item.kind}: ${item.summary}`);
11082
+ return parts.join("; ");
11083
+ }
11084
+ function compactStepCompletion(update) {
11085
+ const compact = {
11086
+ kind: "step_completed",
11087
+ stepId: update.stepId,
11088
+ result: update.result
11089
+ };
11090
+ const evidenceSummary = summarizeEvidence(update.evidence);
11091
+ if (evidenceSummary) compact.evidenceSummary = evidenceSummary;
11092
+ return compact;
11093
+ }
10871
11094
  function registerSubmitPlan(registry, opts) {
10872
11095
  registry.register({
10873
11096
  name: "submit_plan",
@@ -10981,9 +11204,9 @@ function registerMarkStepComplete(registry, opts) {
10981
11204
  opts.onStepCompleted?.(update);
10982
11205
  const verdict = await (ctx?.confirmationGate ?? pauseGate).ask({
10983
11206
  kind: "plan_checkpoint",
10984
- payload: { stepId, title, result, notes }
11207
+ payload: { stepId, title, result, notes, completion: update }
10985
11208
  });
10986
- if (verdict.type === "continue") return JSON.stringify(update);
11209
+ if (verdict.type === "continue") return JSON.stringify(compactStepCompletion(update));
10987
11210
  if (verdict.type === "revise") {
10988
11211
  if (verdict.feedback) return `revision requested: ${verdict.feedback}`;
10989
11212
  throw new Error("user requested revision at checkpoint");
@@ -11310,12 +11533,40 @@ async function spawnSubagent(opts) {
11310
11533
  let toolIter = 0;
11311
11534
  let summarisingEmitted = false;
11312
11535
  let forcedSummaryFired = false;
11536
+ let outputChars = 0;
11537
+ let reasoningChars = 0;
11538
+ let toolReadChars = 0;
11539
+ let lastStreamEmitAt = 0;
11540
+ let charsSinceLastEmit = 0;
11541
+ const STREAM_EMIT_INTERVAL_MS = 200;
11542
+ const STREAM_EMIT_CHARS = 400;
11543
+ const maybeEmitStreamProgress = (now, force) => {
11544
+ if (!sink?.current) return;
11545
+ if (!force && now - lastStreamEmitAt < STREAM_EMIT_INTERVAL_MS && charsSinceLastEmit < STREAM_EMIT_CHARS) {
11546
+ return;
11547
+ }
11548
+ lastStreamEmitAt = now;
11549
+ charsSinceLastEmit = 0;
11550
+ sink.current({
11551
+ kind: "stream-progress",
11552
+ runId,
11553
+ task: taskPreview,
11554
+ skillName,
11555
+ model,
11556
+ iter: toolIter,
11557
+ elapsedMs: now - startedAt,
11558
+ outputChars,
11559
+ reasoningChars,
11560
+ toolReadChars
11561
+ });
11562
+ };
11313
11563
  try {
11314
11564
  for await (const ev of childLoop.step(opts.task)) {
11315
11565
  sink?.current?.({ kind: "inner", runId, task: taskPreview, skillName, model, inner: ev });
11316
11566
  if (ev.role === "tool") {
11317
11567
  toolIter++;
11318
11568
  summarisingEmitted = false;
11569
+ toolReadChars += ev.content?.length ?? 0;
11319
11570
  sink?.current?.({
11320
11571
  kind: "progress",
11321
11572
  runId,
@@ -11325,6 +11576,17 @@ async function spawnSubagent(opts) {
11325
11576
  iter: toolIter,
11326
11577
  elapsedMs: Date.now() - startedAt
11327
11578
  });
11579
+ maybeEmitStreamProgress(Date.now(), true);
11580
+ }
11581
+ if (ev.role === "assistant_delta") {
11582
+ const dContent = ev.content?.length ?? 0;
11583
+ const dReason = ev.reasoningDelta?.length ?? 0;
11584
+ if (dContent > 0 || dReason > 0) {
11585
+ outputChars += dContent;
11586
+ reasoningChars += dReason;
11587
+ charsSinceLastEmit += dContent + dReason;
11588
+ maybeEmitStreamProgress(Date.now(), false);
11589
+ }
11328
11590
  }
11329
11591
  if (ev.role === "assistant_delta" && !summarisingEmitted && (ev.content ?? "").length > 0) {
11330
11592
  summarisingEmitted = true;
@@ -13040,7 +13302,7 @@ function registerShellTools(registry, opts) {
13040
13302
  properties: {
13041
13303
  command: {
13042
13304
  type: "string",
13043
- description: 'Full command line. POSIX-ish quoting. Chain operators `|`, `||`, `&&`, `;` and file redirects `>` / `>>` / `<` / `2>` / `2>>` / `2>&1` / `&>` work natively (no shell). Background `&`, heredoc `<<`, env-var expansion `$VAR`, and command substitution `$(\u2026)` are rejected (or passed through as literal in the case of `$VAR`). To pass an operator character as a literal argument (e.g. a regex), wrap it in quotes: `grep "a|b" file.txt`.'
13305
+ description: "Full command line. Quoting + chain/redirect rules per the top-level description."
13044
13306
  },
13045
13307
  timeoutSec: {
13046
13308
  type: "integer",
@@ -13287,6 +13549,8 @@ var USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (
13287
13549
  var MOJEEK_ENDPOINT = "https://www.mojeek.com/search";
13288
13550
  var METASO_ENDPOINT = "https://metaso.cn/api/v1";
13289
13551
  var TAVILY_ENDPOINT = "https://api.tavily.com/search";
13552
+ var PERPLEXITY_ENDPOINT = "https://api.perplexity.ai/chat/completions";
13553
+ var EXA_ENDPOINT = "https://api.exa.ai/answer";
13290
13554
  function searchStatusError(status) {
13291
13555
  if (status === 429) return t("webErrors.rateLimit429");
13292
13556
  if (status === 403) return t("webErrors.forbidden403");
@@ -13309,6 +13573,12 @@ async function webSearch(query, opts = {}) {
13309
13573
  if (opts.engine === "tavily") {
13310
13574
  return searchTavily(query, opts);
13311
13575
  }
13576
+ if (opts.engine === "perplexity") {
13577
+ return searchPerplexity(query, opts);
13578
+ }
13579
+ if (opts.engine === "exa") {
13580
+ return searchExa(query, opts);
13581
+ }
13312
13582
  return searchMojeek(query, opts);
13313
13583
  }
13314
13584
  async function searchMojeek(query, opts = {}) {
@@ -13492,6 +13762,121 @@ async function searchTavily(query, opts = {}) {
13492
13762
  snippet: r.content ?? ""
13493
13763
  }));
13494
13764
  }
13765
+ async function searchPerplexity(query, opts = {}) {
13766
+ const topK = Math.max(1, Math.min(20, opts.topK ?? DEFAULT_TOPK));
13767
+ const apiKey = loadPerplexityApiKey();
13768
+ if (!apiKey) throw new Error(t("webErrors.perplexityMissingKey"));
13769
+ let resp;
13770
+ try {
13771
+ resp = await fetch(PERPLEXITY_ENDPOINT, {
13772
+ method: "POST",
13773
+ headers: {
13774
+ Authorization: `Bearer ${apiKey}`,
13775
+ "Content-Type": "application/json"
13776
+ },
13777
+ body: JSON.stringify({
13778
+ model: "sonar",
13779
+ messages: [{ role: "user", content: query }],
13780
+ max_tokens: 1024,
13781
+ return_related_questions: false
13782
+ }),
13783
+ signal: opts.signal
13784
+ });
13785
+ } catch (err) {
13786
+ if (err instanceof TypeError && err.message.includes("fetch")) {
13787
+ throw new Error(t("webErrors.cannotReach", { endpoint: PERPLEXITY_ENDPOINT }));
13788
+ }
13789
+ throw err;
13790
+ }
13791
+ if (!resp.ok) {
13792
+ if (resp.status === 401 || resp.status === 403) {
13793
+ throw new Error(t("webErrors.perplexityUnauthorized"));
13794
+ }
13795
+ if (resp.status === 429) throw new Error(t("webErrors.perplexityRateLimit"));
13796
+ throw new Error(t("webErrors.perplexityServerError", { status: resp.status }));
13797
+ }
13798
+ const raw = await resp.text();
13799
+ let data;
13800
+ try {
13801
+ data = JSON.parse(raw);
13802
+ } catch {
13803
+ throw new Error(t("webErrors.perplexityParseError", { status: resp.status }));
13804
+ }
13805
+ const answer = data.choices?.[0]?.message?.content ?? "";
13806
+ const citations = Array.isArray(data.citations) ? data.citations : [];
13807
+ const results = [];
13808
+ if (answer) {
13809
+ results.push({ title: answer, url: "", snippet: "", answer });
13810
+ }
13811
+ const count = Math.min(citations.length, topK);
13812
+ for (let i = 0; i < count; i++) {
13813
+ const c = citations[i];
13814
+ if (typeof c === "string") {
13815
+ results.push({ title: `Source ${i + 1}`, url: c, snippet: "" });
13816
+ } else if (c && typeof c === "object" && typeof c.url === "string") {
13817
+ const item = c;
13818
+ results.push({
13819
+ title: typeof item.title === "string" ? item.title : `Source ${i + 1}`,
13820
+ url: item.url,
13821
+ snippet: ""
13822
+ });
13823
+ }
13824
+ }
13825
+ return results;
13826
+ }
13827
+ async function searchExa(query, opts = {}) {
13828
+ const topK = Math.max(1, Math.min(20, opts.topK ?? DEFAULT_TOPK));
13829
+ const apiKey = loadExaApiKey();
13830
+ if (!apiKey) throw new Error(t("webErrors.exaMissingKey"));
13831
+ let resp;
13832
+ try {
13833
+ resp = await fetch(EXA_ENDPOINT, {
13834
+ method: "POST",
13835
+ headers: {
13836
+ "x-api-key": apiKey,
13837
+ "Content-Type": "application/json"
13838
+ },
13839
+ body: JSON.stringify({ query, text: true }),
13840
+ signal: opts.signal
13841
+ });
13842
+ } catch (err) {
13843
+ if (err instanceof TypeError && err.message.includes("fetch")) {
13844
+ throw new Error(t("webErrors.cannotReach", { endpoint: EXA_ENDPOINT }));
13845
+ }
13846
+ throw err;
13847
+ }
13848
+ if (!resp.ok) {
13849
+ if (resp.status === 401 || resp.status === 403) {
13850
+ throw new Error(t("webErrors.exaUnauthorized"));
13851
+ }
13852
+ if (resp.status === 429) throw new Error(t("webErrors.exaRateLimit"));
13853
+ throw new Error(t("webErrors.exaServerError", { status: resp.status }));
13854
+ }
13855
+ const raw = await resp.text();
13856
+ let data;
13857
+ try {
13858
+ data = JSON.parse(raw);
13859
+ } catch {
13860
+ throw new Error(t("webErrors.exaParseError", { status: resp.status }));
13861
+ }
13862
+ const answer = data.answer ?? "";
13863
+ const citations = data.citations ?? [];
13864
+ const results = [];
13865
+ if (answer) {
13866
+ results.push({ title: answer, url: "", snippet: "", answer });
13867
+ }
13868
+ const count = Math.min(citations.length, topK);
13869
+ for (let i = 0; i < count; i++) {
13870
+ const c = citations[i];
13871
+ if (!c.url) continue;
13872
+ results.push({
13873
+ title: c.title || `Source ${i + 1}`,
13874
+ url: c.url,
13875
+ snippet: c.text ?? ""
13876
+ });
13877
+ }
13878
+ return results;
13879
+ }
13495
13880
  function parseSearxngHtmlResults(html) {
13496
13881
  const root = parseHtml(html);
13497
13882
  const results = [];
@@ -13710,7 +14095,7 @@ function registerWebTools(registry, opts = {}) {
13710
14095
  const maxFetchChars = opts.maxFetchChars ?? DEFAULT_FETCH_MAX_CHARS;
13711
14096
  registry.register({
13712
14097
  name: "web_search",
13713
- 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.",
14098
+ 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.",
13714
14099
  readOnly: true,
13715
14100
  parallelSafe: true,
13716
14101
  parameters: {
@@ -13719,7 +14104,7 @@ function registerWebTools(registry, opts = {}) {
13719
14104
  query: { type: "string", description: "Natural-language search query." },
13720
14105
  topK: {
13721
14106
  type: "integer",
13722
- description: `Number of results to return (1..10). Default ${defaultTopK}.`
14107
+ description: `Number of results to return. Default ${defaultTopK}.`
13723
14108
  }
13724
14109
  },
13725
14110
  required: ["query"]
@@ -13763,14 +14148,30 @@ ${page.text}`;
13763
14148
  return registry;
13764
14149
  }
13765
14150
  function formatSearchResults(query, results) {
13766
- const lines = [`query: ${query}`, `
13767
- results (${results.length}):`];
13768
- results.forEach((r, i) => {
14151
+ const lines = [`query: ${query}`];
14152
+ const hasAnswer = results.length > 0 && results[0]?.url === "" && results[0]?.answer;
14153
+ if (hasAnswer) {
14154
+ lines.push("\nanswer:");
14155
+ lines.push(` ${results[0].answer}`);
14156
+ const sources = results.slice(1);
13769
14157
  lines.push(`
14158
+ sources (${sources.length}):`);
14159
+ sources.forEach((r, i) => {
14160
+ lines.push(`
13770
14161
  ${i + 1}. ${r.title}`);
13771
- lines.push(` ${r.url}`);
13772
- if (r.snippet) lines.push(` ${r.snippet}`);
13773
- });
14162
+ lines.push(` ${r.url}`);
14163
+ if (r.snippet) lines.push(` ${r.snippet}`);
14164
+ });
14165
+ } else {
14166
+ lines.push(`
14167
+ results (${results.length}):`);
14168
+ results.forEach((r, i) => {
14169
+ lines.push(`
14170
+ ${i + 1}. ${r.title}`);
14171
+ lines.push(` ${r.url}`);
14172
+ if (r.snippet) lines.push(` ${r.snippet}`);
14173
+ });
14174
+ }
13774
14175
  return lines.join("\n");
13775
14176
  }
13776
14177
 
@@ -15132,6 +15533,14 @@ function applyEditBlock(block, rootDir) {
15132
15533
  message: "SEARCH text does not match the current file content exactly"
15133
15534
  };
15134
15535
  }
15536
+ const nextIdx = content.indexOf(adaptedSearch, idx + 1);
15537
+ if (nextIdx !== -1) {
15538
+ return {
15539
+ path: block.path,
15540
+ status: "not-found",
15541
+ message: "SEARCH text appears multiple times; include more context to disambiguate"
15542
+ };
15543
+ }
15135
15544
  const replaced = `${content.slice(0, idx)}${adaptedReplace}${content.slice(idx + adaptedSearch.length)}`;
15136
15545
  const outBuf = Buffer.from(replaced, "utf8");
15137
15546
  ftruncateSync(fd, outBuf.length);
@@ -15333,18 +15742,8 @@ You have BOTH \`semantic_search\` (vector index) and \`search_content\` (literal
15333
15742
  - **Exact-token queries** (a specific identifier, regex, or "find every call to foo") \u2192 call \`search_content\`.
15334
15743
 
15335
15744
  If \`semantic_search\` returns nothing useful (low scores, off-topic), THEN fall back to \`search_content\`. Don't go the other way \u2014 grepping a paraphrased question wastes turns.`;
15336
- var ENGINEERING_LIFECYCLE_CONTRACT = `
15337
-
15338
- # Engineering lifecycle contract
15339
-
15340
- Reasonix may enforce a prefix-stable Engineering Lifecycle for explicitly enabled high-risk engineering work. The runtime keeps lifecycle state outside the system prompt and tool list, so do not expect stage-specific prompt changes or new tools to appear. Treat any lifecycle block as a host constraint, not as a suggestion.
15341
-
15342
- When high-risk mutations are bounced with \`rejectedReason: "engineering-lifecycle"\`, switch to read-only exploration, then call \`submit_plan\` with concrete steps before trying the mutation again. Add optional per-step \`targets\`, \`acceptance\`, and \`verification\` fields when they clarify scope or success criteria. For medium/high-risk steps, steps with verification criteria, or steps that changed code, \`mark_step_complete\` requires \`evidence\` entries such as verification output, diff summary, checkpoint id, or manual rationale.`;
15343
15745
  function codeSystemPrompt(rootDir, opts = {}) {
15344
- let codeBase = codeSystemBase(opts.modelId ?? DEFAULT_CODE_MODEL);
15345
- if (opts.engineeringLifecycleMode === "strict") {
15346
- codeBase = `${codeBase}${ENGINEERING_LIFECYCLE_CONTRACT}`;
15347
- }
15746
+ const codeBase = codeSystemBase(opts.modelId ?? DEFAULT_CODE_MODEL);
15348
15747
  const base = opts.hasSemanticSearch ? `${codeBase}${SEMANTIC_SEARCH_ROUTING}` : codeBase;
15349
15748
  const withMemory = applyMemoryStack(base, rootDir);
15350
15749
  const gitignorePath = join15(rootDir, ".gitignore");
@@ -15698,8 +16097,10 @@ export {
15698
16097
  loadApiKey,
15699
16098
  loadBaseUrl,
15700
16099
  loadDotenv,
16100
+ loadExaApiKey,
15701
16101
  loadHooks,
15702
16102
  loadMetasoApiKey,
16103
+ loadPerplexityApiKey,
15703
16104
  loadSessionMessages,
15704
16105
  matchesTool,
15705
16106
  memoryEnabled,