reasonix 0.46.0 → 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 (159) hide show
  1. package/README.md +64 -12
  2. package/README.zh-CN.md +54 -9
  3. package/dashboard/dist/app.js +293 -66
  4. package/dashboard/dist/app.js.map +1 -1
  5. package/dist/cli/{acp-LGBLHBKY.js → acp-QK3DMC53.js} +22 -22
  6. package/dist/cli/chat-VV5UWY4V.js +51 -0
  7. package/dist/cli/{chunk-AVFXO2EZ.js → chunk-24A7FHGJ.js} +148 -16
  8. package/dist/cli/chunk-24A7FHGJ.js.map +1 -0
  9. package/dist/cli/chunk-25T6CVUP.js +0 -0
  10. package/dist/cli/chunk-2UQP6H6T.js +0 -0
  11. package/dist/cli/chunk-5QCB62C4.js +0 -0
  12. package/dist/cli/{chunk-YY227BIQ.js → chunk-6J6BSUCR.js} +2 -2
  13. package/dist/cli/chunk-6OWJV3YW.js +0 -0
  14. package/dist/cli/{chunk-A3TSSDS2.js → chunk-BWYVFFKR.js} +2 -2
  15. package/dist/cli/{chunk-C53JQES5.js → chunk-BYYVYJDX.js} +3 -3
  16. package/dist/cli/{chunk-HNXDZGC6.js → chunk-CI2PF5QX.js} +2 -2
  17. package/dist/cli/{chunk-GTZTQNX5.js → chunk-COWPEX54.js} +19 -9
  18. package/dist/cli/chunk-COWPEX54.js.map +1 -0
  19. package/dist/cli/{chunk-QJDDIK3Z.js → chunk-E5WCLUIU.js} +2 -2
  20. package/dist/cli/{chunk-NVURFF27.js → chunk-EQATK2L2.js} +2 -2
  21. package/dist/cli/{chunk-HKWSPKMU.js → chunk-FDKOUJKZ.js} +8 -8
  22. package/dist/cli/chunk-FEZK652I.js +0 -0
  23. package/dist/cli/{chunk-TEUDEGX2.js → chunk-FY4S7TJZ.js} +19 -5
  24. package/dist/cli/chunk-FY4S7TJZ.js.map +1 -0
  25. package/dist/cli/{chunk-RDRC3XDT.js → chunk-GDKB2PPK.js} +2 -2
  26. package/dist/cli/{chunk-XSU4QVFW.js → chunk-HIYTRCSW.js} +27 -14
  27. package/dist/cli/chunk-HIYTRCSW.js.map +1 -0
  28. package/dist/cli/{chunk-WL6SNQ5T.js → chunk-ICAFSZHS.js} +307 -114
  29. package/dist/cli/chunk-ICAFSZHS.js.map +1 -0
  30. package/dist/cli/{chunk-KQU2TYIL.js → chunk-ICSYGIPN.js} +1916 -1098
  31. package/dist/cli/chunk-ICSYGIPN.js.map +1 -0
  32. package/dist/cli/chunk-J5XJHLWM.js +0 -0
  33. package/dist/cli/chunk-JMBMLOBP.js +0 -0
  34. package/dist/cli/{chunk-MJ6W5UN3.js → chunk-K6GUKSXH.js} +3 -2
  35. package/dist/cli/chunk-K6GUKSXH.js.map +1 -0
  36. package/dist/cli/{chunk-IJ7JA32V.js → chunk-KDRUEXII.js} +189 -26
  37. package/dist/cli/chunk-KDRUEXII.js.map +1 -0
  38. package/dist/cli/{chunk-4HCP2UQW.js → chunk-LBLR4CUZ.js} +2 -2
  39. package/dist/cli/{chunk-2425HK6U.js → chunk-LGEKVMMV.js} +7 -2
  40. package/dist/cli/{chunk-2425HK6U.js.map → chunk-LGEKVMMV.js.map} +1 -1
  41. package/dist/cli/{chunk-I4L2GTSE.js → chunk-OJVITDGB.js} +2 -2
  42. package/dist/cli/chunk-PLHAZOLZ.js +0 -0
  43. package/dist/cli/{chunk-W7YGWUWU.js → chunk-QVDWH2A2.js} +3 -3
  44. package/dist/cli/{chunk-R3CTO2HM.js → chunk-QVUFWDD2.js} +2 -2
  45. package/dist/cli/{chunk-HVUZWNSP.js → chunk-R6GQKKBW.js} +2 -2
  46. package/dist/cli/{chunk-5ACMUK4Q.js → chunk-RRXUIPWG.js} +20 -18
  47. package/dist/cli/chunk-RRXUIPWG.js.map +1 -0
  48. package/dist/cli/chunk-S4XVGLRW.js +0 -0
  49. package/dist/cli/chunk-SZ5XES2N.js +0 -0
  50. package/dist/cli/{chunk-CXVWUPA3.js → chunk-TKVXTQ3T.js} +26 -26
  51. package/dist/cli/chunk-TKVXTQ3T.js.map +1 -0
  52. package/dist/cli/chunk-TUK7OWJA.js +0 -0
  53. package/dist/cli/{chunk-JNAQYELD.js → chunk-UDVFBEXC.js} +3 -3
  54. package/dist/cli/{chunk-CBIQWMS6.js → chunk-VC2CQA5D.js} +9 -9
  55. package/dist/cli/{chunk-ZZYBBX5N.js → chunk-VJMBISEI.js} +23 -9
  56. package/dist/cli/chunk-VJMBISEI.js.map +1 -0
  57. package/dist/cli/{chunk-WK3UFQY3.js → chunk-VKYSZKH2.js} +2 -2
  58. package/dist/cli/{chunk-LIR2HBQH.js → chunk-VMUUFWFF.js} +2 -2
  59. package/dist/cli/{chunk-V26WPN3J.js → chunk-VNQGCA3Q.js} +28 -1
  60. package/dist/cli/chunk-VNQGCA3Q.js.map +1 -0
  61. package/dist/cli/{chunk-5I2C4JEO.js → chunk-WF7TPVZM.js} +6 -6
  62. package/dist/cli/{chunk-5I2C4JEO.js.map → chunk-WF7TPVZM.js.map} +1 -1
  63. package/dist/cli/chunk-X53B3JIX.js +0 -0
  64. package/dist/cli/chunk-XJXDHAES.js +0 -0
  65. package/dist/cli/chunk-XXC2BYTV.js +0 -0
  66. package/dist/cli/{chunk-4CTDEJUF.js → chunk-YDPLF7XR.js} +26 -14
  67. package/dist/cli/chunk-YDPLF7XR.js.map +1 -0
  68. package/dist/cli/chunk-ZZM6QJ4W.js +0 -0
  69. package/dist/cli/{code-DFHSASJ4.js → code-C24TUAE5.js} +39 -35
  70. package/dist/cli/code-C24TUAE5.js.map +1 -0
  71. package/dist/cli/{commands-OCU42XG4.js → commands-RR3GIYOK.js} +4 -4
  72. package/dist/cli/{commit-XCQIQCYG.js → commit-FSHPIINM.js} +3 -3
  73. package/dist/cli/{desktop-ZCUG7LMF.js → desktop-7NCHPEFB.js} +263 -36
  74. package/dist/cli/desktop-7NCHPEFB.js.map +1 -0
  75. package/dist/cli/devtools-HW3WDT3Q.js +0 -0
  76. package/dist/cli/{diff-66B2KWOJ.js → diff-RAAHHLHV.js} +8 -8
  77. package/dist/cli/{doctor-Y73CPPRZ.js → doctor-PKVQIXRT.js} +9 -9
  78. package/dist/cli/{events-NGZ2OJYH.js → events-VRYXOSKI.js} +3 -3
  79. package/dist/cli/index.js +84 -92
  80. package/dist/cli/index.js.map +1 -1
  81. package/dist/cli/{mcp-MPVGBBJF.js → mcp-CRJ26PP4.js} +2 -2
  82. package/dist/cli/{mcp-browse-4XOTC3FJ.js → mcp-browse-QPAOWZOP.js} +2 -2
  83. package/dist/cli/{mcp-inspect-CEMGKKAH.js → mcp-inspect-CVCLABRS.js} +4 -4
  84. package/dist/cli/{prompt-2D7ID24X.js → prompt-SKYXERSI.js} +4 -4
  85. package/dist/cli/{prune-sessions-OJEYYLHY.js → prune-sessions-SEWX7GP6.js} +2 -2
  86. package/dist/cli/{replay-AKYQNAQJ.js → replay-KPDW2ZMJ.js} +9 -9
  87. package/dist/cli/{run-5DPQFSP6.js → run-WIKDIXTG.js} +18 -19
  88. package/dist/cli/run-WIKDIXTG.js.map +1 -0
  89. package/dist/cli/{server-TQ2IHYQJ.js → server-P6V2G3P6.js} +82 -34
  90. package/dist/cli/server-P6V2G3P6.js.map +1 -0
  91. package/dist/cli/{sessions-KY54NG45.js → sessions-2NULRMSA.js} +29 -15
  92. package/dist/cli/sessions-2NULRMSA.js.map +1 -0
  93. package/dist/cli/{setup-XPIOZWS7.js → setup-Y5WDBQFL.js} +8 -8
  94. package/dist/cli/setup-Y5WDBQFL.js.map +1 -0
  95. package/dist/cli/{stats-X2VTWKNS.js → stats-T7BL2YOR.js} +6 -6
  96. package/dist/cli/update-6ITLPRDV.js +0 -0
  97. package/dist/cli/{version-7O6A5T7Q.js → version-3KWDNWLN.js} +15 -15
  98. package/dist/index.d.ts +54 -23
  99. package/dist/index.js +1613 -1152
  100. package/dist/index.js.map +1 -1
  101. package/package.json +1 -1
  102. package/dist/cli/.-3G6VX5S7.js +0 -327
  103. package/dist/cli/.-6YRPB2C7.js +0 -329
  104. package/dist/cli/.-EYSVINK3.js +0 -317
  105. package/dist/cli/chat-ECK5ZGMV.js +0 -51
  106. package/dist/cli/chunk-4CTDEJUF.js.map +0 -1
  107. package/dist/cli/chunk-5ACMUK4Q.js.map +0 -1
  108. package/dist/cli/chunk-AVFXO2EZ.js.map +0 -1
  109. package/dist/cli/chunk-CXVWUPA3.js.map +0 -1
  110. package/dist/cli/chunk-GTZTQNX5.js.map +0 -1
  111. package/dist/cli/chunk-IJ7JA32V.js.map +0 -1
  112. package/dist/cli/chunk-KQU2TYIL.js.map +0 -1
  113. package/dist/cli/chunk-MJ6W5UN3.js.map +0 -1
  114. package/dist/cli/chunk-TEUDEGX2.js.map +0 -1
  115. package/dist/cli/chunk-V26WPN3J.js.map +0 -1
  116. package/dist/cli/chunk-WL6SNQ5T.js.map +0 -1
  117. package/dist/cli/chunk-XSU4QVFW.js.map +0 -1
  118. package/dist/cli/chunk-ZZYBBX5N.js.map +0 -1
  119. package/dist/cli/code-DFHSASJ4.js.map +0 -1
  120. package/dist/cli/desktop-ZCUG7LMF.js.map +0 -1
  121. package/dist/cli/doctor-Y73CPPRZ.js.map +0 -1
  122. package/dist/cli/prompt-2D7ID24X.js.map +0 -1
  123. package/dist/cli/run-5DPQFSP6.js.map +0 -1
  124. package/dist/cli/server-TQ2IHYQJ.js.map +0 -1
  125. package/dist/cli/sessions-KY54NG45.js.map +0 -1
  126. package/dist/cli/setup-XPIOZWS7.js.map +0 -1
  127. package/dist/cli/stats-X2VTWKNS.js.map +0 -1
  128. /package/dist/cli/{acp-LGBLHBKY.js.map → acp-QK3DMC53.js.map} +0 -0
  129. /package/dist/cli/{.-3G6VX5S7.js.map → chat-VV5UWY4V.js.map} +0 -0
  130. /package/dist/cli/{chunk-YY227BIQ.js.map → chunk-6J6BSUCR.js.map} +0 -0
  131. /package/dist/cli/{chunk-A3TSSDS2.js.map → chunk-BWYVFFKR.js.map} +0 -0
  132. /package/dist/cli/{chunk-C53JQES5.js.map → chunk-BYYVYJDX.js.map} +0 -0
  133. /package/dist/cli/{chunk-HNXDZGC6.js.map → chunk-CI2PF5QX.js.map} +0 -0
  134. /package/dist/cli/{chunk-QJDDIK3Z.js.map → chunk-E5WCLUIU.js.map} +0 -0
  135. /package/dist/cli/{chunk-NVURFF27.js.map → chunk-EQATK2L2.js.map} +0 -0
  136. /package/dist/cli/{chunk-HKWSPKMU.js.map → chunk-FDKOUJKZ.js.map} +0 -0
  137. /package/dist/cli/{chunk-RDRC3XDT.js.map → chunk-GDKB2PPK.js.map} +0 -0
  138. /package/dist/cli/{chunk-4HCP2UQW.js.map → chunk-LBLR4CUZ.js.map} +0 -0
  139. /package/dist/cli/{chunk-I4L2GTSE.js.map → chunk-OJVITDGB.js.map} +0 -0
  140. /package/dist/cli/{chunk-W7YGWUWU.js.map → chunk-QVDWH2A2.js.map} +0 -0
  141. /package/dist/cli/{chunk-R3CTO2HM.js.map → chunk-QVUFWDD2.js.map} +0 -0
  142. /package/dist/cli/{chunk-HVUZWNSP.js.map → chunk-R6GQKKBW.js.map} +0 -0
  143. /package/dist/cli/{chunk-JNAQYELD.js.map → chunk-UDVFBEXC.js.map} +0 -0
  144. /package/dist/cli/{chunk-CBIQWMS6.js.map → chunk-VC2CQA5D.js.map} +0 -0
  145. /package/dist/cli/{chunk-WK3UFQY3.js.map → chunk-VKYSZKH2.js.map} +0 -0
  146. /package/dist/cli/{chunk-LIR2HBQH.js.map → chunk-VMUUFWFF.js.map} +0 -0
  147. /package/dist/cli/{commands-OCU42XG4.js.map → commands-RR3GIYOK.js.map} +0 -0
  148. /package/dist/cli/{commit-XCQIQCYG.js.map → commit-FSHPIINM.js.map} +0 -0
  149. /package/dist/cli/{diff-66B2KWOJ.js.map → diff-RAAHHLHV.js.map} +0 -0
  150. /package/dist/cli/{.-6YRPB2C7.js.map → doctor-PKVQIXRT.js.map} +0 -0
  151. /package/dist/cli/{events-NGZ2OJYH.js.map → events-VRYXOSKI.js.map} +0 -0
  152. /package/dist/cli/{mcp-MPVGBBJF.js.map → mcp-CRJ26PP4.js.map} +0 -0
  153. /package/dist/cli/{mcp-browse-4XOTC3FJ.js.map → mcp-browse-QPAOWZOP.js.map} +0 -0
  154. /package/dist/cli/{mcp-inspect-CEMGKKAH.js.map → mcp-inspect-CVCLABRS.js.map} +0 -0
  155. /package/dist/cli/{.-EYSVINK3.js.map → prompt-SKYXERSI.js.map} +0 -0
  156. /package/dist/cli/{prune-sessions-OJEYYLHY.js.map → prune-sessions-SEWX7GP6.js.map} +0 -0
  157. /package/dist/cli/{replay-AKYQNAQJ.js.map → replay-KPDW2ZMJ.js.map} +0 -0
  158. /package/dist/cli/{chat-ECK5ZGMV.js.map → stats-T7BL2YOR.js.map} +0 -0
  159. /package/dist/cli/{version-7O6A5T7Q.js.map → version-3KWDNWLN.js.map} +0 -0
package/dist/index.js CHANGED
@@ -1,1045 +1,1102 @@
1
1
  // src/client.ts
2
2
  import { createParser } from "eventsource-parser";
3
3
 
4
- // src/retry.ts
5
- var DEFAULT_RETRYABLE_STATUSES = [408, 429, 500, 502, 503, 504];
6
- async function fetchWithRetry(fetchFn, url, init, opts = {}) {
7
- const maxAttempts = opts.maxAttempts ?? 4;
8
- const initial = opts.initialBackoffMs ?? 500;
9
- const cap = opts.maxBackoffMs ?? 1e4;
10
- const retryable = new Set(opts.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES);
11
- let lastError;
12
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
13
- if (opts.signal?.aborted) throw new Error("aborted");
14
- try {
15
- const resp = await fetchFn(url, init);
16
- if (resp.ok || !retryable.has(resp.status)) return resp;
17
- if (attempt === maxAttempts - 1) return resp;
18
- await resp.text().catch(() => void 0);
19
- const waitMs = computeWait(attempt, initial, cap, resp.headers.get("Retry-After"));
20
- opts.onRetry?.({ attempt: attempt + 1, reason: `http ${resp.status}`, waitMs });
21
- await sleep(waitMs, opts.signal);
22
- } catch (err) {
23
- lastError = err;
24
- if (isAbortError(err) || opts.signal?.aborted) throw err;
25
- if (attempt === maxAttempts - 1) throw err;
26
- const waitMs = computeWait(attempt, initial, cap, null);
27
- opts.onRetry?.({
28
- attempt: attempt + 1,
29
- reason: `network: ${messageOf(err)}`,
30
- waitMs
31
- });
32
- await sleep(waitMs, opts.signal);
33
- }
34
- }
35
- throw lastError ?? new Error("fetchWithRetry: loop exited unexpectedly");
36
- }
37
- function computeWait(attempt, initial, cap, retryAfter) {
38
- if (retryAfter) {
39
- const seconds = Number.parseFloat(retryAfter);
40
- if (Number.isFinite(seconds) && seconds > 0) {
41
- return Math.min(seconds * 1e3, cap);
42
- }
43
- }
44
- const exp = initial * 2 ** attempt;
45
- const jitter = exp * (0.75 + Math.random() * 0.5);
46
- return Math.min(Math.max(jitter, 0), cap);
47
- }
48
- function sleep(ms, signal) {
49
- if (ms <= 0) return Promise.resolve();
50
- return new Promise((resolve13, reject) => {
51
- const timer = setTimeout(resolve13, ms);
52
- if (signal) {
53
- const onAbort = () => {
54
- clearTimeout(timer);
55
- reject(new Error("aborted"));
56
- };
57
- if (signal.aborted) onAbort();
58
- else signal.addEventListener("abort", onAbort, { once: true });
59
- }
60
- });
4
+ // src/config.ts
5
+ import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "fs";
6
+ import { homedir } from "os";
7
+ import { dirname, isAbsolute, join, resolve } from "path";
8
+
9
+ // src/cli/ui/theme/tokens.ts
10
+ function card(fg, tone) {
11
+ return {
12
+ user: { color: tone.brand, glyph: "\u25C7" },
13
+ reasoning: { color: tone.accent, glyph: "\u25C6" },
14
+ streaming: { color: tone.brand, glyph: "\u25C8" },
15
+ task: { color: tone.warn, glyph: "\u25B6" },
16
+ tool: { color: tone.info, glyph: "\u25A3" },
17
+ plan: { color: tone.accent, glyph: "\u229E" },
18
+ diff: { color: tone.ok, glyph: "\xB1" },
19
+ error: { color: tone.err, glyph: "\u2716" },
20
+ warn: { color: tone.warn, glyph: "\u26A0" },
21
+ usage: { color: fg.meta, glyph: "\u03A3" },
22
+ subagent: { color: tone.violet, glyph: "\u232C" },
23
+ approval: { color: tone.warn, glyph: "?" },
24
+ search: { color: tone.info, glyph: "\u2299" },
25
+ memory: { color: fg.meta, glyph: "\u2311" },
26
+ ctx: { color: tone.brand, glyph: "\u25D4" },
27
+ doctor: { color: fg.meta, glyph: "\u2695" },
28
+ branch: { color: tone.violet, glyph: "\u2387" }
29
+ };
61
30
  }
62
- function isAbortError(err) {
63
- if (!err || typeof err !== "object") return false;
64
- const name = err.name;
65
- return name === "AbortError";
31
+ function defineTheme(base) {
32
+ return { ...base, card: card(base.fg, base.tone) };
66
33
  }
67
- function messageOf(err) {
68
- if (err instanceof Error) return err.message;
69
- try {
70
- return String(err);
71
- } catch {
72
- return "unknown error";
34
+ var githubDark = defineTheme({
35
+ fg: {
36
+ strong: "#e6edf3",
37
+ body: "#c9d1d9",
38
+ sub: "#8b949e",
39
+ meta: "#6e7681",
40
+ faint: "#484f58"
41
+ },
42
+ tone: {
43
+ brand: "#79c0ff",
44
+ accent: "#d2a8ff",
45
+ violet: "#b395f5",
46
+ ok: "#7ee787",
47
+ warn: "#f0b07d",
48
+ err: "#ff8b81",
49
+ info: "#79c0ff"
50
+ },
51
+ toneActive: {
52
+ brand: "#a5d6ff",
53
+ accent: "#e2c5ff",
54
+ violet: "#c8aaff",
55
+ ok: "#a8f5ad",
56
+ warn: "#ffc99e",
57
+ err: "#ffaba3",
58
+ info: "#a5d6ff"
59
+ },
60
+ surface: {
61
+ bg: "#0a0c10",
62
+ bgInput: "#0d1015",
63
+ bgCode: "#06080c",
64
+ bgElev: "#11141a"
73
65
  }
74
- }
75
-
76
- // src/client.ts
77
- var Usage = class _Usage {
78
- constructor(promptTokens = 0, completionTokens = 0, totalTokens = 0, promptCacheHitTokens = 0, promptCacheMissTokens = 0) {
79
- this.promptTokens = promptTokens;
80
- this.completionTokens = completionTokens;
81
- this.totalTokens = totalTokens;
82
- this.promptCacheHitTokens = promptCacheHitTokens;
83
- this.promptCacheMissTokens = promptCacheMissTokens;
66
+ });
67
+ var dark = defineTheme({
68
+ fg: {
69
+ strong: "#f4f7fb",
70
+ body: "#d8dee9",
71
+ sub: "#a7b1c2",
72
+ meta: "#778294",
73
+ faint: "#4d5666"
74
+ },
75
+ tone: {
76
+ brand: "#7dd3fc",
77
+ accent: "#c084fc",
78
+ violet: "#a78bfa",
79
+ ok: "#86efac",
80
+ warn: "#fbbf24",
81
+ err: "#f87171",
82
+ info: "#60a5fa"
83
+ },
84
+ toneActive: {
85
+ brand: "#bae6fd",
86
+ accent: "#e9d5ff",
87
+ violet: "#ddd6fe",
88
+ ok: "#bbf7d0",
89
+ warn: "#fde68a",
90
+ err: "#fecaca",
91
+ info: "#bfdbfe"
92
+ },
93
+ surface: {
94
+ bg: "#0b1020",
95
+ bgInput: "#111827",
96
+ bgCode: "#080c16",
97
+ bgElev: "#151d2f"
84
98
  }
85
- promptTokens;
86
- completionTokens;
87
- totalTokens;
88
- promptCacheHitTokens;
89
- promptCacheMissTokens;
90
- get cacheHitRatio() {
91
- const denom = this.promptCacheHitTokens + this.promptCacheMissTokens;
92
- return denom > 0 ? this.promptCacheHitTokens / denom : 0;
99
+ });
100
+ var light = defineTheme({
101
+ fg: {
102
+ strong: "#111827",
103
+ body: "#1f2937",
104
+ sub: "#4b5563",
105
+ meta: "#6b7280",
106
+ faint: "#9ca3af"
107
+ },
108
+ tone: {
109
+ brand: "#2563eb",
110
+ accent: "#7c3aed",
111
+ violet: "#6d28d9",
112
+ ok: "#15803d",
113
+ warn: "#b45309",
114
+ err: "#dc2626",
115
+ info: "#0369a1"
116
+ },
117
+ toneActive: {
118
+ brand: "#1d4ed8",
119
+ accent: "#6d28d9",
120
+ violet: "#5b21b6",
121
+ ok: "#166534",
122
+ warn: "#92400e",
123
+ err: "#b91c1c",
124
+ info: "#075985"
125
+ },
126
+ surface: {
127
+ bg: "#ffffff",
128
+ bgInput: "#f8fafc",
129
+ bgCode: "#f3f4f6",
130
+ bgElev: "#eef2f7"
93
131
  }
94
- static fromApi(raw) {
95
- const u = raw ?? {};
96
- const promptTokens = u.prompt_tokens ?? 0;
97
- const cacheHitTokens = u.prompt_cache_hit_tokens ?? 0;
98
- const cacheMissTokens = u.prompt_cache_miss_tokens ?? Math.max(0, promptTokens - cacheHitTokens);
99
- return new _Usage(
100
- promptTokens,
101
- u.completion_tokens ?? 0,
102
- u.total_tokens ?? 0,
103
- cacheHitTokens,
104
- cacheMissTokens
105
- );
132
+ });
133
+ var tokyoNight = defineTheme({
134
+ fg: {
135
+ strong: "#c0caf5",
136
+ body: "#a9b1d6",
137
+ sub: "#9aa5ce",
138
+ meta: "#565f89",
139
+ faint: "#414868"
140
+ },
141
+ tone: {
142
+ brand: "#7aa2f7",
143
+ accent: "#bb9af7",
144
+ violet: "#9d7cd8",
145
+ ok: "#9ece6a",
146
+ warn: "#e0af68",
147
+ err: "#f7768e",
148
+ info: "#2ac3de"
149
+ },
150
+ toneActive: {
151
+ brand: "#a9c7ff",
152
+ accent: "#d7b9ff",
153
+ violet: "#c6a0f6",
154
+ ok: "#b9f27c",
155
+ warn: "#ffd089",
156
+ err: "#ff9cac",
157
+ info: "#7dcfff"
158
+ },
159
+ surface: {
160
+ bg: "#1a1b26",
161
+ bgInput: "#1f2335",
162
+ bgCode: "#16161e",
163
+ bgElev: "#24283b"
106
164
  }
107
- };
108
- var DeepSeekClient = class {
109
- apiKey;
110
- baseUrl;
111
- timeoutMs;
112
- retry;
113
- _fetch;
114
- constructor(opts = {}) {
115
- const apiKey = opts.apiKey ?? process.env.DEEPSEEK_API_KEY;
116
- if (!apiKey) {
117
- throw new Error(
118
- "DEEPSEEK_API_KEY is not set. Put it in .env or pass apiKey to DeepSeekClient."
119
- );
120
- }
121
- this.apiKey = apiKey;
122
- let url = opts.baseUrl ?? process.env.DEEPSEEK_BASE_URL ?? "https://api.deepseek.com";
123
- while (url.endsWith("/")) url = url.slice(0, -1);
124
- this.baseUrl = url;
125
- this.timeoutMs = opts.timeoutMs ?? 66e4;
126
- this._fetch = opts.fetch ?? globalThis.fetch.bind(globalThis);
127
- this.retry = opts.retry ?? {};
128
- }
129
- buildPayload(opts, stream) {
130
- const payload = {
131
- model: opts.model,
132
- messages: opts.messages,
133
- stream
134
- };
135
- if (opts.tools?.length) payload.tools = opts.tools;
136
- if (opts.temperature !== void 0) payload.temperature = opts.temperature;
137
- if (opts.maxTokens !== void 0) payload.max_tokens = opts.maxTokens;
138
- if (opts.responseFormat) payload.response_format = opts.responseFormat;
139
- if (opts.thinking) {
140
- payload.extra_body = { thinking: { type: opts.thinking } };
141
- }
142
- if (opts.reasoningEffort) {
143
- payload.reasoning_effort = opts.reasoningEffort;
144
- }
145
- return payload;
146
- }
147
- /** Returns null on failure so callers can degrade — session must keep working without balance UI. */
148
- async getBalance(opts = {}) {
149
- try {
150
- const resp = await this._fetch(`${this.baseUrl}/user/balance`, {
151
- method: "GET",
152
- headers: { Authorization: `Bearer ${this.apiKey}` },
153
- signal: opts.signal
154
- });
155
- if (!resp.ok) return null;
156
- const data = await resp.json();
157
- if (!data || !Array.isArray(data.balance_infos)) return null;
158
- return data;
159
- } catch {
160
- return null;
161
- }
162
- }
163
- /** Returns null on failure — callers fall back to a hardcoded model hint. */
164
- async listModels(opts = {}) {
165
- try {
166
- const resp = await this._fetch(`${this.baseUrl}/models`, {
167
- method: "GET",
168
- headers: { Authorization: `Bearer ${this.apiKey}` },
169
- signal: opts.signal
170
- });
171
- if (!resp.ok) return null;
172
- const data = await resp.json();
173
- if (!data || !Array.isArray(data.data)) return null;
174
- return data;
175
- } catch {
176
- return null;
177
- }
165
+ });
166
+ var githubLight = defineTheme({
167
+ fg: {
168
+ strong: "#1f2328",
169
+ body: "#24292f",
170
+ sub: "#57606a",
171
+ meta: "#6e7781",
172
+ faint: "#8c959f"
173
+ },
174
+ tone: {
175
+ brand: "#0969da",
176
+ accent: "#8250df",
177
+ violet: "#6639ba",
178
+ ok: "#1a7f37",
179
+ warn: "#9a6700",
180
+ err: "#cf222e",
181
+ info: "#0969da"
182
+ },
183
+ toneActive: {
184
+ brand: "#0550ae",
185
+ accent: "#6639ba",
186
+ violet: "#512a97",
187
+ ok: "#116329",
188
+ warn: "#7d4e00",
189
+ err: "#a40e26",
190
+ info: "#0550ae"
191
+ },
192
+ surface: {
193
+ bg: "#ffffff",
194
+ bgInput: "#f6f8fa",
195
+ bgCode: "#f6f8fa",
196
+ bgElev: "#eaeef2"
178
197
  }
179
- async chat(opts) {
180
- const ctrl = new AbortController();
181
- const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
182
- const signal = opts.signal ?? ctrl.signal;
183
- try {
184
- const resp = await fetchWithRetry(
185
- this._fetch,
186
- `${this.baseUrl}/chat/completions`,
187
- {
188
- method: "POST",
189
- headers: {
190
- Authorization: `Bearer ${this.apiKey}`,
191
- "Content-Type": "application/json"
192
- },
193
- body: JSON.stringify(this.buildPayload(opts, false)),
194
- signal
195
- },
196
- { ...this.retry, signal }
197
- );
198
- if (!resp.ok) {
199
- throw new Error(`DeepSeek ${resp.status}: ${await resp.text()}`);
200
- }
201
- const data = await resp.json();
202
- const choice = data.choices?.[0]?.message ?? {};
203
- return {
204
- content: choice.content ?? "",
205
- reasoningContent: choice.reasoning_content ?? null,
206
- toolCalls: choice.tool_calls ?? [],
207
- usage: Usage.fromApi(data.usage),
208
- raw: data
209
- };
210
- } finally {
211
- clearTimeout(timer);
212
- }
198
+ });
199
+ var highContrast = defineTheme({
200
+ fg: {
201
+ strong: "#ffffff",
202
+ body: "#f5f5f5",
203
+ sub: "#d4d4d4",
204
+ meta: "#bdbdbd",
205
+ faint: "#8a8a8a"
206
+ },
207
+ tone: {
208
+ brand: "#00e5ff",
209
+ accent: "#ff4dff",
210
+ violet: "#b388ff",
211
+ ok: "#00ff66",
212
+ warn: "#ffdd00",
213
+ err: "#ff4d4d",
214
+ info: "#4da3ff"
215
+ },
216
+ toneActive: {
217
+ brand: "#80f2ff",
218
+ accent: "#ff99ff",
219
+ violet: "#d0b3ff",
220
+ ok: "#80ffb3",
221
+ warn: "#ffee80",
222
+ err: "#ff9999",
223
+ info: "#99c9ff"
224
+ },
225
+ surface: {
226
+ bg: "#000000",
227
+ bgInput: "#0a0a0a",
228
+ bgCode: "#050505",
229
+ bgElev: "#141414"
213
230
  }
214
- async *stream(opts) {
215
- const ctrl = new AbortController();
216
- const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
217
- const signal = opts.signal ?? ctrl.signal;
218
- let resp;
219
- try {
220
- resp = await fetchWithRetry(
221
- this._fetch,
222
- `${this.baseUrl}/chat/completions`,
223
- {
224
- method: "POST",
225
- headers: {
226
- Authorization: `Bearer ${this.apiKey}`,
227
- "Content-Type": "application/json",
228
- Accept: "text/event-stream"
229
- },
230
- body: JSON.stringify(this.buildPayload(opts, true)),
231
- signal
232
- },
233
- { ...this.retry, signal }
234
- );
235
- } catch (err) {
236
- clearTimeout(timer);
237
- throw err;
238
- }
239
- if (!resp.ok || !resp.body) {
240
- clearTimeout(timer);
241
- throw new Error(`DeepSeek ${resp.status}: ${await resp.text().catch(() => "")}`);
231
+ });
232
+ var THEMES = {
233
+ default: githubDark,
234
+ dark,
235
+ light,
236
+ "tokyo-night": tokyoNight,
237
+ "github-dark": githubDark,
238
+ "github-light": githubLight,
239
+ "high-contrast": highContrast
240
+ };
241
+ var DEFAULT_THEME_NAME = "default";
242
+ var DEFAULT_THEME = THEMES[DEFAULT_THEME_NAME];
243
+ var activeTheme = DEFAULT_THEME;
244
+ function proxyTokens(select) {
245
+ const target = select(DEFAULT_THEME);
246
+ return new Proxy(target, {
247
+ get(_target, prop) {
248
+ return select(activeTheme)[prop];
249
+ },
250
+ getOwnPropertyDescriptor(_target, prop) {
251
+ return Reflect.getOwnPropertyDescriptor(select(activeTheme), prop);
252
+ },
253
+ has(_target, prop) {
254
+ return prop in select(activeTheme);
255
+ },
256
+ ownKeys() {
257
+ return Reflect.ownKeys(select(activeTheme));
242
258
  }
243
- const queue = [];
244
- let done = false;
245
- const parser = createParser({
246
- onEvent: (ev) => {
247
- if (!ev.data || ev.data === "[DONE]") {
248
- done = true;
249
- return;
250
- }
251
- try {
252
- const json = JSON.parse(ev.data);
253
- const delta = json.choices?.[0]?.delta ?? {};
254
- const finishReason = json.choices?.[0]?.finish_reason ?? void 0;
255
- const chunk = { raw: json, finishReason };
256
- if (typeof delta.content === "string" && delta.content.length > 0) {
257
- chunk.contentDelta = delta.content;
258
- }
259
- if (typeof delta.reasoning_content === "string" && delta.reasoning_content.length > 0) {
260
- chunk.reasoningDelta = delta.reasoning_content;
261
- }
262
- if (Array.isArray(delta.tool_calls) && delta.tool_calls.length > 0) {
263
- const tc = delta.tool_calls[0];
264
- chunk.toolCallDelta = {
265
- index: tc.index ?? 0,
266
- id: tc.id,
267
- name: tc.function?.name,
268
- argumentsDelta: tc.function?.arguments
269
- };
270
- }
271
- if (json.usage) {
272
- chunk.usage = Usage.fromApi(json.usage);
273
- }
274
- queue.push(chunk);
275
- } catch {
276
- }
277
- }
278
- });
279
- const reader = resp.body.getReader();
280
- const decoder = new TextDecoder();
281
- try {
282
- while (true) {
283
- if (queue.length > 0) {
284
- yield queue.shift();
285
- continue;
286
- }
287
- if (done) break;
288
- const { value, done: streamDone } = await reader.read();
289
- if (streamDone) break;
290
- parser.feed(decoder.decode(value, { stream: true }));
259
+ });
260
+ }
261
+ var FG = proxyTokens((theme) => theme.fg);
262
+ var TONE = proxyTokens((theme) => theme.tone);
263
+ var TONE_ACTIVE = proxyTokens((theme) => theme.toneActive);
264
+ var SURFACE = proxyTokens((theme) => theme.surface);
265
+ var CARD = proxyTokens((theme) => theme.card);
266
+
267
+ // src/index/config.ts
268
+ import picomatch from "picomatch";
269
+ var DEFAULT_INDEX_EXCLUDES = {
270
+ dirs: [
271
+ "node_modules",
272
+ ".git",
273
+ ".hg",
274
+ ".svn",
275
+ "dist",
276
+ "build",
277
+ "out",
278
+ ".next",
279
+ ".nuxt",
280
+ "target",
281
+ ".venv",
282
+ "venv",
283
+ "__pycache__",
284
+ ".pytest_cache",
285
+ ".mypy_cache",
286
+ ".cache",
287
+ "coverage",
288
+ ".turbo",
289
+ ".vercel",
290
+ ".reasonix"
291
+ ],
292
+ files: [
293
+ "package-lock.json",
294
+ "yarn.lock",
295
+ "pnpm-lock.yaml",
296
+ "Cargo.lock",
297
+ "poetry.lock",
298
+ "Pipfile.lock",
299
+ "go.sum",
300
+ ".DS_Store"
301
+ ],
302
+ exts: [
303
+ ".png",
304
+ ".jpg",
305
+ ".jpeg",
306
+ ".gif",
307
+ ".webp",
308
+ ".bmp",
309
+ ".ico",
310
+ ".tiff",
311
+ ".woff",
312
+ ".woff2",
313
+ ".ttf",
314
+ ".otf",
315
+ ".eot",
316
+ ".zip",
317
+ ".tar",
318
+ ".gz",
319
+ ".bz2",
320
+ ".xz",
321
+ ".rar",
322
+ ".7z",
323
+ ".exe",
324
+ ".dll",
325
+ ".so",
326
+ ".dylib",
327
+ ".bin",
328
+ ".class",
329
+ ".jar",
330
+ ".war",
331
+ ".wasm",
332
+ ".o",
333
+ ".obj",
334
+ ".lib",
335
+ ".a",
336
+ ".pyc",
337
+ ".pyo",
338
+ ".mp3",
339
+ ".mp4",
340
+ ".wav",
341
+ ".ogg",
342
+ ".webm",
343
+ ".mov",
344
+ ".avi",
345
+ ".pdf",
346
+ ".sqlite",
347
+ ".db"
348
+ ]
349
+ };
350
+ var DEFAULT_MAX_FILE_BYTES = 256 * 1024;
351
+
352
+ // src/mcp/shell-split.ts
353
+ function shellSplit(input) {
354
+ const tokens = [];
355
+ let cur = "";
356
+ let quote = null;
357
+ let i = 0;
358
+ const s = input;
359
+ while (i < s.length) {
360
+ const ch = s[i];
361
+ if (quote) {
362
+ if (ch === quote) {
363
+ quote = null;
364
+ i++;
365
+ continue;
291
366
  }
292
- while (queue.length > 0) yield queue.shift();
293
- } finally {
294
- clearTimeout(timer);
295
- reader.releaseLock();
367
+ if (ch === "\\" && quote === '"' && i + 1 < s.length) {
368
+ cur += s[i + 1];
369
+ i += 2;
370
+ continue;
371
+ }
372
+ cur += ch;
373
+ i++;
374
+ continue;
296
375
  }
297
- }
298
- };
299
-
300
- // src/core/pause-gate.ts
301
- var PauseGate = class {
302
- _nextId = 0;
303
- _pending = /* @__PURE__ */ new Map();
304
- _listeners = /* @__PURE__ */ new Set();
305
- _auditListener = null;
306
- /** Block until the user responds. Takes a named options object so the
307
- * kind and payload fields don't get confused at the call site. */
308
- ask(opts) {
309
- const { kind, payload } = opts;
310
- if (this._listeners.size === 0) {
311
- throw new Error(
312
- `${kind}: no confirmation listener registered \u2014 cannot prompt the user. This tool can only be used inside an interactive Reasonix session.`
313
- );
376
+ if (ch === '"' || ch === "'") {
377
+ quote = ch;
378
+ i++;
379
+ continue;
314
380
  }
315
- return new Promise((resolve13) => {
316
- const id = this._nextId++;
317
- const request = { id, kind, payload };
318
- this._pending.set(id, { resolve: resolve13, request });
319
- for (const fn of this._listeners) {
320
- try {
321
- fn(request);
322
- } catch {
323
- }
381
+ if (ch === " " || ch === " ") {
382
+ if (cur.length > 0) {
383
+ tokens.push(cur);
384
+ cur = "";
324
385
  }
325
- });
326
- }
327
- /** Resolve a pending request. Called by the App's modal callback. */
328
- resolve(id, data) {
329
- const p = this._pending.get(id);
330
- if (!p) return;
331
- this._pending.delete(id);
332
- this.emitAuditEvent(p.request, data);
333
- p.resolve(data);
334
- }
335
- /** Safe-cancel every outstanding request — frees stranded tool fns on Esc / /new. */
336
- cancelAll() {
337
- const ids = [...this._pending.keys()];
338
- for (const id of ids) {
339
- const p = this._pending.get(id);
340
- if (!p) continue;
341
- this._pending.delete(id);
342
- p.resolve(safeCancelVerdict(p.request.kind));
386
+ i++;
387
+ continue;
343
388
  }
389
+ cur += ch;
390
+ i++;
344
391
  }
345
- /** Cancel one pending request — used by multi-tab hosts that need per-scope abort. */
346
- cancel(id) {
347
- const p = this._pending.get(id);
348
- if (!p) return false;
349
- this._pending.delete(id);
350
- p.resolve(safeCancelVerdict(p.request.kind));
351
- return true;
392
+ if (quote) {
393
+ throw new Error(
394
+ `shellSplit: unterminated ${quote === '"' ? "double" : "single"} quote in input`
395
+ );
352
396
  }
353
- setAuditListener(fn) {
354
- this._auditListener = fn;
397
+ if (cur.length > 0) tokens.push(cur);
398
+ return tokens;
399
+ }
400
+
401
+ // src/mcp/spec.ts
402
+ var NAME_PREFIX = /^([a-zA-Z_][a-zA-Z0-9_-]*)=(.*)$/;
403
+ var HTTP_URL = /^https?:\/\//i;
404
+ var STREAMABLE_PREFIX = /^streamable\+(https?:\/\/.+)$/i;
405
+ function parseMcpSpec(input) {
406
+ const trimmed = input.trim();
407
+ if (!trimmed) {
408
+ throw new Error("empty MCP spec");
355
409
  }
356
- /** Subscribe to new pause requests. Returns an unsubscribe function. */
357
- on(fn) {
358
- this._listeners.add(fn);
359
- return () => {
360
- this._listeners.delete(fn);
361
- };
410
+ const nameMatch = NAME_PREFIX.exec(trimmed);
411
+ const name = nameMatch ? nameMatch[1] : null;
412
+ const body = (nameMatch ? nameMatch[2] : trimmed).trim();
413
+ if (!body) {
414
+ throw new Error(`MCP spec has name but no command: ${input}`);
362
415
  }
363
- /** Current pending request, if any (polling fallback). */
364
- get current() {
365
- for (const [, p] of this._pending) return p.request;
366
- return null;
416
+ const streamMatch = STREAMABLE_PREFIX.exec(body);
417
+ if (streamMatch) {
418
+ return { transport: "streamable-http", name, url: streamMatch[1] };
367
419
  }
368
- emitAuditEvent(request, data) {
369
- if (!this._auditListener) return;
370
- if (request.kind !== "run_command" && request.kind !== "run_background") return;
371
- if (!data || typeof data !== "object") return;
372
- const choice = data;
373
- try {
374
- switch (choice.type) {
375
- case "run_once":
376
- this._auditListener({
377
- type: "tool.confirm.allow",
378
- kind: request.kind,
379
- payload: request.payload
380
- });
381
- break;
382
- case "deny":
383
- this._auditListener({
384
- type: "tool.confirm.deny",
385
- kind: request.kind,
386
- payload: request.payload,
387
- denyContext: choice.denyContext
388
- });
389
- break;
390
- case "always_allow":
391
- if (typeof choice.prefix !== "string") return;
392
- this._auditListener({
393
- type: "tool.confirm.always_allow",
394
- kind: request.kind,
395
- payload: request.payload,
396
- prefix: choice.prefix
397
- });
398
- break;
399
- default:
400
- break;
401
- }
402
- } catch {
403
- }
420
+ if (HTTP_URL.test(body)) {
421
+ return { transport: "sse", name, url: body };
404
422
  }
405
- };
406
- function safeCancelVerdict(kind) {
407
- switch (kind) {
408
- case "run_command":
409
- case "run_background":
410
- case "path_access":
411
- return { type: "deny" };
412
- case "plan_proposed":
413
- return { type: "cancel" };
414
- case "plan_checkpoint":
415
- return { type: "stop" };
416
- case "plan_revision":
417
- return { type: "cancelled" };
418
- case "choice":
419
- return { type: "cancel" };
423
+ const argv = shellSplit(body);
424
+ if (argv.length === 0) {
425
+ throw new Error(`MCP spec has name but no command: ${input}`);
420
426
  }
427
+ const [command, ...args] = argv;
428
+ return { transport: "stdio", name, command, args };
421
429
  }
422
- var pauseGate = new PauseGate();
423
-
424
- // src/hooks.ts
425
- import { spawn } from "child_process";
426
- import { existsSync, readFileSync as readFileSync2 } from "fs";
427
- import { homedir as homedir2 } from "os";
428
- import { join as join2 } from "path";
429
430
 
430
431
  // src/config.ts
431
- import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "fs";
432
- import { homedir } from "os";
433
- import { dirname, isAbsolute, join, resolve } from "path";
434
-
435
- // src/cli/ui/theme/tokens.ts
436
- function card(fg, tone) {
437
- return {
438
- user: { color: tone.brand, glyph: "\u25C7" },
439
- reasoning: { color: tone.accent, glyph: "\u25C6" },
440
- streaming: { color: tone.brand, glyph: "\u25C8" },
441
- task: { color: tone.warn, glyph: "\u25B6" },
442
- tool: { color: tone.info, glyph: "\u25A3" },
443
- plan: { color: tone.accent, glyph: "\u229E" },
444
- diff: { color: tone.ok, glyph: "\xB1" },
445
- error: { color: tone.err, glyph: "\u2716" },
446
- warn: { color: tone.warn, glyph: "\u26A0" },
447
- usage: { color: fg.meta, glyph: "\u03A3" },
448
- subagent: { color: tone.violet, glyph: "\u232C" },
449
- approval: { color: tone.warn, glyph: "?" },
450
- search: { color: tone.info, glyph: "\u2299" },
451
- memory: { color: fg.meta, glyph: "\u2311" },
452
- ctx: { color: tone.brand, glyph: "\u25D4" },
453
- doctor: { color: fg.meta, glyph: "\u2695" },
454
- branch: { color: tone.violet, glyph: "\u2387" }
455
- };
456
- }
457
- function defineTheme(base) {
458
- return { ...base, card: card(base.fg, base.tone) };
432
+ var BUILTIN_TYPE_DOCS = {
433
+ user: "role / skills / preferences",
434
+ feedback: "corrections or confirmed approaches",
435
+ project: "facts / decisions about the current work",
436
+ reference: "pointers to external systems the user uses"
437
+ };
438
+ function loadMemoryTypeRegistry(cfg = readConfig()) {
439
+ const out = [];
440
+ for (const name of ["user", "feedback", "project", "reference"]) {
441
+ out.push({ name, builtin: true, description: BUILTIN_TYPE_DOCS[name] });
442
+ }
443
+ const seen = new Set(out.map((e) => e.name));
444
+ for (const raw of cfg.memory?.customTypes ?? []) {
445
+ if (!raw || typeof raw.name !== "string") continue;
446
+ const name = raw.name.trim();
447
+ if (!name || !/^[a-zA-Z][a-zA-Z0-9_-]{0,31}$/.test(name)) continue;
448
+ if (seen.has(name)) continue;
449
+ seen.add(name);
450
+ const entry = { name, builtin: false };
451
+ if (typeof raw.description === "string") entry.description = raw.description;
452
+ if (raw.priority === "low" || raw.priority === "medium" || raw.priority === "high") {
453
+ entry.priority = raw.priority;
454
+ }
455
+ if (raw.expires === "project_end") entry.expires = raw.expires;
456
+ out.push(entry);
457
+ }
458
+ return out;
459
459
  }
460
- var githubDark = defineTheme({
461
- fg: {
462
- strong: "#e6edf3",
463
- body: "#c9d1d9",
464
- sub: "#8b949e",
465
- meta: "#6e7681",
466
- faint: "#484f58"
467
- },
468
- tone: {
469
- brand: "#79c0ff",
470
- accent: "#d2a8ff",
471
- violet: "#b395f5",
472
- ok: "#7ee787",
473
- warn: "#f0b07d",
474
- err: "#ff8b81",
475
- info: "#79c0ff"
476
- },
477
- toneActive: {
478
- brand: "#a5d6ff",
479
- accent: "#e2c5ff",
480
- violet: "#c8aaff",
481
- ok: "#a8f5ad",
482
- warn: "#ffc99e",
483
- err: "#ffaba3",
484
- info: "#a5d6ff"
485
- },
486
- surface: {
487
- bg: "#0a0c10",
488
- bgInput: "#0d1015",
489
- bgCode: "#06080c",
490
- bgElev: "#11141a"
460
+ function memoryTypeDefaults(typeName, cfg = readConfig()) {
461
+ const found = loadMemoryTypeRegistry(cfg).find((e) => e.name === typeName);
462
+ if (!found) return {};
463
+ const out = {};
464
+ if (found.priority) out.priority = found.priority;
465
+ if (found.expires) out.expires = found.expires;
466
+ return out;
467
+ }
468
+ var DEFAULT_METASO_API_KEY = "mk-E384C1DD5E8501BB7EFE27C949AFDE5B";
469
+ function loadMetasoApiKey(path2 = defaultConfigPath()) {
470
+ if (process.env.METASO_API_KEY) return process.env.METASO_API_KEY;
471
+ const cfg = readConfig(path2).metasoApiKey;
472
+ if (cfg && typeof cfg === "string" && cfg.trim()) return cfg.trim();
473
+ return DEFAULT_METASO_API_KEY;
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
+ }
481
+ function defaultConfigPath() {
482
+ return join(homedir(), ".reasonix", "config.json");
483
+ }
484
+ function readConfig(path2 = defaultConfigPath()) {
485
+ try {
486
+ const raw = readFileSync(path2, "utf8");
487
+ const parsed = JSON.parse(raw);
488
+ if (parsed && typeof parsed === "object") return parsed;
489
+ } catch {
491
490
  }
492
- });
493
- var dark = defineTheme({
494
- fg: {
495
- strong: "#f4f7fb",
496
- body: "#d8dee9",
497
- sub: "#a7b1c2",
498
- meta: "#778294",
499
- faint: "#4d5666"
500
- },
501
- tone: {
502
- brand: "#7dd3fc",
503
- accent: "#c084fc",
504
- violet: "#a78bfa",
505
- ok: "#86efac",
506
- warn: "#fbbf24",
507
- err: "#f87171",
508
- info: "#60a5fa"
509
- },
510
- toneActive: {
511
- brand: "#bae6fd",
512
- accent: "#e9d5ff",
513
- violet: "#ddd6fe",
514
- ok: "#bbf7d0",
515
- warn: "#fde68a",
516
- err: "#fecaca",
517
- info: "#bfdbfe"
518
- },
519
- surface: {
520
- bg: "#0b1020",
521
- bgInput: "#111827",
522
- bgCode: "#080c16",
523
- bgElev: "#151d2f"
491
+ return {};
492
+ }
493
+ function writeConfig(cfg, path2 = defaultConfigPath()) {
494
+ mkdirSync(dirname(path2), { recursive: true });
495
+ writeFileSync(path2, JSON.stringify(cfg, null, 2), "utf8");
496
+ try {
497
+ chmodSync(path2, 384);
498
+ } catch {
524
499
  }
525
- });
526
- var light = defineTheme({
527
- fg: {
528
- strong: "#111827",
529
- body: "#1f2937",
530
- sub: "#4b5563",
531
- meta: "#6b7280",
532
- faint: "#9ca3af"
533
- },
534
- tone: {
535
- brand: "#2563eb",
536
- accent: "#7c3aed",
537
- violet: "#6d28d9",
538
- ok: "#15803d",
539
- warn: "#b45309",
540
- err: "#dc2626",
541
- info: "#0369a1"
542
- },
543
- toneActive: {
544
- brand: "#1d4ed8",
545
- accent: "#6d28d9",
546
- violet: "#5b21b6",
547
- ok: "#166534",
548
- warn: "#92400e",
549
- err: "#b91c1c",
550
- info: "#075985"
551
- },
552
- surface: {
553
- bg: "#ffffff",
554
- bgInput: "#f8fafc",
555
- bgCode: "#f3f4f6",
556
- bgElev: "#eef2f7"
500
+ }
501
+ function loadLanguage(path2 = defaultConfigPath()) {
502
+ return readConfig(path2).lang;
503
+ }
504
+ function loadApiKey(path2 = defaultConfigPath()) {
505
+ if (process.env.DEEPSEEK_API_KEY) return process.env.DEEPSEEK_API_KEY;
506
+ return readConfig(path2).apiKey;
507
+ }
508
+ function loadBaseUrl(path2 = defaultConfigPath()) {
509
+ if (process.env.DEEPSEEK_BASE_URL) return process.env.DEEPSEEK_BASE_URL;
510
+ return readConfig(path2).baseUrl;
511
+ }
512
+ function isNonNegativeNumber(value) {
513
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
514
+ }
515
+ function loadPricingOverride(path2 = defaultConfigPath()) {
516
+ const raw = readConfig(path2).pricingOverride;
517
+ if (!isPlainObject(raw)) return {};
518
+ const result = {};
519
+ for (const [model, value] of Object.entries(raw)) {
520
+ if (!isPlainObject(value)) continue;
521
+ const pricing = {};
522
+ if (isNonNegativeNumber(value.inputCacheHit)) pricing.inputCacheHit = value.inputCacheHit;
523
+ if (isNonNegativeNumber(value.inputCacheMiss)) pricing.inputCacheMiss = value.inputCacheMiss;
524
+ if (isNonNegativeNumber(value.output)) pricing.output = value.output;
525
+ if (Object.keys(pricing).length > 0) result[model] = pricing;
557
526
  }
558
- });
559
- var tokyoNight = defineTheme({
560
- fg: {
561
- strong: "#c0caf5",
562
- body: "#a9b1d6",
563
- sub: "#9aa5ce",
564
- meta: "#565f89",
565
- faint: "#414868"
566
- },
567
- tone: {
568
- brand: "#7aa2f7",
569
- accent: "#bb9af7",
570
- violet: "#9d7cd8",
571
- ok: "#9ece6a",
572
- warn: "#e0af68",
573
- err: "#f7768e",
574
- info: "#2ac3de"
575
- },
576
- toneActive: {
577
- brand: "#a9c7ff",
578
- accent: "#d7b9ff",
579
- violet: "#c6a0f6",
580
- ok: "#b9f27c",
581
- warn: "#ffd089",
582
- err: "#ff9cac",
583
- info: "#7dcfff"
584
- },
585
- surface: {
586
- bg: "#1a1b26",
587
- bgInput: "#1f2335",
588
- bgCode: "#16161e",
589
- bgElev: "#24283b"
527
+ return result;
528
+ }
529
+ function loadRateLimit(path2 = defaultConfigPath()) {
530
+ const rpm = readConfig(path2).rateLimit?.rpm;
531
+ if (typeof rpm !== "number" || !Number.isInteger(rpm) || rpm <= 0) return void 0;
532
+ return { rpm };
533
+ }
534
+ function saveBaseUrl(url, path2 = defaultConfigPath()) {
535
+ const cfg = readConfig(path2);
536
+ const trimmed = url.trim();
537
+ if (trimmed) {
538
+ cfg.baseUrl = trimmed;
539
+ } else {
540
+ cfg.baseUrl = void 0;
590
541
  }
591
- });
592
- var githubLight = defineTheme({
593
- fg: {
594
- strong: "#1f2328",
595
- body: "#24292f",
596
- sub: "#57606a",
597
- meta: "#6e7781",
598
- faint: "#8c959f"
599
- },
600
- tone: {
601
- brand: "#0969da",
602
- accent: "#8250df",
603
- violet: "#6639ba",
604
- ok: "#1a7f37",
605
- warn: "#9a6700",
606
- err: "#cf222e",
607
- info: "#0969da"
608
- },
609
- toneActive: {
610
- brand: "#0550ae",
611
- accent: "#6639ba",
612
- violet: "#512a97",
613
- ok: "#116329",
614
- warn: "#7d4e00",
615
- err: "#a40e26",
616
- info: "#0550ae"
617
- },
618
- surface: {
619
- bg: "#ffffff",
620
- bgInput: "#f6f8fa",
621
- bgCode: "#f6f8fa",
622
- bgElev: "#eaeef2"
542
+ writeConfig(cfg, path2);
543
+ }
544
+ function resolveSkillPath(raw, baseDir) {
545
+ const homeExpanded = expandCurrentUserHome(raw.trim());
546
+ return resolve(isAbsolute(homeExpanded) ? homeExpanded : join(baseDir, homeExpanded));
547
+ }
548
+ function normalizeSkillPathEntries(paths, baseDir) {
549
+ const out = [];
550
+ const seen = /* @__PURE__ */ new Set();
551
+ for (const value of paths) {
552
+ if (typeof value !== "string") continue;
553
+ const raw = value.trim();
554
+ if (!raw) continue;
555
+ const resolved = resolveSkillPath(raw, baseDir);
556
+ const key = skillPathKey(resolved);
557
+ if (seen.has(key)) continue;
558
+ seen.add(key);
559
+ out.push({ raw, resolved });
623
560
  }
624
- });
625
- var highContrast = defineTheme({
626
- fg: {
627
- strong: "#ffffff",
628
- body: "#f5f5f5",
629
- sub: "#d4d4d4",
630
- meta: "#bdbdbd",
631
- faint: "#8a8a8a"
632
- },
633
- tone: {
634
- brand: "#00e5ff",
635
- accent: "#ff4dff",
636
- violet: "#b388ff",
637
- ok: "#00ff66",
638
- warn: "#ffdd00",
639
- err: "#ff4d4d",
640
- info: "#4da3ff"
641
- },
642
- toneActive: {
643
- brand: "#80f2ff",
644
- accent: "#ff99ff",
645
- violet: "#d0b3ff",
646
- ok: "#80ffb3",
647
- warn: "#ffee80",
648
- err: "#ff9999",
649
- info: "#99c9ff"
650
- },
651
- surface: {
652
- bg: "#000000",
653
- bgInput: "#0a0a0a",
654
- bgCode: "#050505",
655
- bgElev: "#141414"
561
+ return out;
562
+ }
563
+ function resolveSkillPaths(paths, baseDir) {
564
+ return normalizeSkillPathEntries(paths, baseDir).map((entry) => entry.resolved);
565
+ }
566
+ function skillPathKey(path2) {
567
+ return process.platform === "win32" ? path2.toLowerCase() : path2;
568
+ }
569
+ function expandCurrentUserHome(path2) {
570
+ if (path2 === "~") return homedir();
571
+ if (path2.startsWith("~/") || path2.startsWith("~\\")) return join(homedir(), path2.slice(2));
572
+ return path2;
573
+ }
574
+ function loadResolvedSkillPaths(baseDir = process.cwd(), path2 = defaultConfigPath()) {
575
+ const raw = readConfig(path2).skills?.paths;
576
+ return Array.isArray(raw) ? resolveSkillPaths(raw, baseDir) : [];
577
+ }
578
+ function webSearchEngine(path2 = defaultConfigPath()) {
579
+ const cfg = readConfig(path2).webSearchEngine;
580
+ if (cfg === "searxng") return "searxng";
581
+ if (cfg === "metaso") return "metaso";
582
+ return "mojeek";
583
+ }
584
+ function webSearchEndpoint(path2 = defaultConfigPath()) {
585
+ const cfg = readConfig(path2).webSearchEndpoint;
586
+ if (cfg && typeof cfg === "string") return cfg;
587
+ return "http://localhost:8080";
588
+ }
589
+ function saveApiKey(key, path2 = defaultConfigPath()) {
590
+ const cfg = readConfig(path2);
591
+ cfg.apiKey = key.trim();
592
+ writeConfig(cfg, path2);
593
+ }
594
+ function findProjectKey(cfg, rootDir) {
595
+ const projects = cfg.projects;
596
+ if (!projects) return void 0;
597
+ if (Object.hasOwn(projects, rootDir)) return rootDir;
598
+ if (process.platform !== "win32") return void 0;
599
+ const lower = rootDir.toLowerCase();
600
+ for (const k of Object.keys(projects)) {
601
+ if (k.toLowerCase() === lower) return k;
656
602
  }
657
- });
658
- var THEMES = {
659
- default: githubDark,
660
- dark,
661
- light,
662
- "tokyo-night": tokyoNight,
663
- "github-dark": githubDark,
664
- "github-light": githubLight,
665
- "high-contrast": highContrast
666
- };
667
- var DEFAULT_THEME_NAME = "default";
668
- var DEFAULT_THEME = THEMES[DEFAULT_THEME_NAME];
669
- var activeTheme = DEFAULT_THEME;
670
- function proxyTokens(select) {
671
- const target = select(DEFAULT_THEME);
672
- return new Proxy(target, {
673
- get(_target, prop) {
674
- return select(activeTheme)[prop];
675
- },
676
- getOwnPropertyDescriptor(_target, prop) {
677
- return Reflect.getOwnPropertyDescriptor(select(activeTheme), prop);
678
- },
679
- has(_target, prop) {
680
- return prop in select(activeTheme);
681
- },
682
- ownKeys() {
683
- return Reflect.ownKeys(select(activeTheme));
684
- }
685
- });
603
+ return void 0;
604
+ }
605
+ function addProjectShellAllowed(rootDir, prefix, path2 = defaultConfigPath()) {
606
+ const trimmed = prefix.trim();
607
+ if (!trimmed) return;
608
+ const cfg = readConfig(path2);
609
+ if (!cfg.projects) cfg.projects = {};
610
+ const key = findProjectKey(cfg, rootDir) ?? rootDir;
611
+ if (!cfg.projects[key]) cfg.projects[key] = {};
612
+ const existing = cfg.projects[key].shellAllowed ?? [];
613
+ if (existing.includes(trimmed)) return;
614
+ cfg.projects[key].shellAllowed = [...existing, trimmed];
615
+ writeConfig(cfg, path2);
616
+ }
617
+ function loadProjectPathAllowed(rootDir, path2 = defaultConfigPath()) {
618
+ const cfg = readConfig(path2);
619
+ const key = findProjectKey(cfg, rootDir);
620
+ if (key === void 0) return [];
621
+ return cfg.projects?.[key]?.pathAllowed ?? [];
622
+ }
623
+ function addProjectPathAllowed(rootDir, prefix, path2 = defaultConfigPath()) {
624
+ const trimmed = prefix.trim();
625
+ if (!trimmed) return;
626
+ const cfg = readConfig(path2);
627
+ if (!cfg.projects) cfg.projects = {};
628
+ const key = findProjectKey(cfg, rootDir) ?? rootDir;
629
+ if (!cfg.projects[key]) cfg.projects[key] = {};
630
+ const existing = cfg.projects[key].pathAllowed ?? [];
631
+ if (existing.includes(trimmed)) return;
632
+ cfg.projects[key].pathAllowed = [...existing, trimmed];
633
+ writeConfig(cfg, path2);
634
+ }
635
+ function isPlausibleKey(key) {
636
+ const trimmed = key.trim();
637
+ if (trimmed.length < 16) return false;
638
+ return !/\s/.test(trimmed);
639
+ }
640
+ function redactKey(key) {
641
+ if (!key) return "";
642
+ if (key.length <= 12) return "****";
643
+ return `${key.slice(0, 6)}\u2026${key.slice(-4)}`;
644
+ }
645
+ function isPlainObject(value) {
646
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
647
+ const proto = Object.getPrototypeOf(value);
648
+ return proto === Object.prototype || proto === null;
686
649
  }
687
- var FG = proxyTokens((theme) => theme.fg);
688
- var TONE = proxyTokens((theme) => theme.tone);
689
- var TONE_ACTIVE = proxyTokens((theme) => theme.toneActive);
690
- var SURFACE = proxyTokens((theme) => theme.surface);
691
- var CARD = proxyTokens((theme) => theme.card);
692
-
693
- // src/index/config.ts
694
- import picomatch from "picomatch";
695
- var DEFAULT_INDEX_EXCLUDES = {
696
- dirs: [
697
- "node_modules",
698
- ".git",
699
- ".hg",
700
- ".svn",
701
- "dist",
702
- "build",
703
- "out",
704
- ".next",
705
- ".nuxt",
706
- "target",
707
- ".venv",
708
- "venv",
709
- "__pycache__",
710
- ".pytest_cache",
711
- ".mypy_cache",
712
- ".cache",
713
- "coverage",
714
- ".turbo",
715
- ".vercel",
716
- ".reasonix"
717
- ],
718
- files: [
719
- "package-lock.json",
720
- "yarn.lock",
721
- "pnpm-lock.yaml",
722
- "Cargo.lock",
723
- "poetry.lock",
724
- "Pipfile.lock",
725
- "go.sum",
726
- ".DS_Store"
727
- ],
728
- exts: [
729
- ".png",
730
- ".jpg",
731
- ".jpeg",
732
- ".gif",
733
- ".webp",
734
- ".bmp",
735
- ".ico",
736
- ".tiff",
737
- ".woff",
738
- ".woff2",
739
- ".ttf",
740
- ".otf",
741
- ".eot",
742
- ".zip",
743
- ".tar",
744
- ".gz",
745
- ".bz2",
746
- ".xz",
747
- ".rar",
748
- ".7z",
749
- ".exe",
750
- ".dll",
751
- ".so",
752
- ".dylib",
753
- ".bin",
754
- ".class",
755
- ".jar",
756
- ".war",
757
- ".wasm",
758
- ".o",
759
- ".obj",
760
- ".lib",
761
- ".a",
762
- ".pyc",
763
- ".pyo",
764
- ".mp3",
765
- ".mp4",
766
- ".wav",
767
- ".ogg",
768
- ".webm",
769
- ".mov",
770
- ".avi",
771
- ".pdf",
772
- ".sqlite",
773
- ".db"
774
- ]
775
- };
776
- var DEFAULT_MAX_FILE_BYTES = 256 * 1024;
777
650
 
778
- // src/mcp/shell-split.ts
779
- function shellSplit(input) {
780
- const tokens = [];
781
- let cur = "";
782
- let quote = null;
783
- let i = 0;
784
- const s = input;
785
- while (i < s.length) {
786
- const ch = s[i];
787
- if (quote) {
788
- if (ch === quote) {
789
- quote = null;
790
- i++;
791
- continue;
792
- }
793
- if (ch === "\\" && quote === '"' && i + 1 < s.length) {
794
- cur += s[i + 1];
795
- i += 2;
796
- continue;
797
- }
798
- cur += ch;
799
- i++;
800
- continue;
801
- }
802
- if (ch === '"' || ch === "'") {
803
- quote = ch;
804
- i++;
805
- continue;
806
- }
807
- if (ch === " " || ch === " ") {
808
- if (cur.length > 0) {
809
- tokens.push(cur);
810
- cur = "";
811
- }
812
- i++;
813
- continue;
651
+ // src/retry.ts
652
+ var DEFAULT_RETRYABLE_STATUSES = [408, 429, 500, 502, 503, 504];
653
+ async function fetchWithRetry(fetchFn, url, init, opts = {}) {
654
+ const maxAttempts = opts.maxAttempts ?? 4;
655
+ const initial = opts.initialBackoffMs ?? 500;
656
+ const cap = opts.maxBackoffMs ?? 1e4;
657
+ const retryable = new Set(opts.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES);
658
+ let lastError;
659
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
660
+ if (opts.signal?.aborted) throw new Error("aborted");
661
+ try {
662
+ const resp = await fetchFn(url, init);
663
+ if (resp.ok || !retryable.has(resp.status)) return resp;
664
+ if (attempt === maxAttempts - 1) return resp;
665
+ await resp.text().catch(() => void 0);
666
+ const waitMs = computeWait(attempt, initial, cap, resp.headers.get("Retry-After"));
667
+ opts.onRetry?.({ attempt: attempt + 1, reason: `http ${resp.status}`, waitMs });
668
+ await sleep(waitMs, opts.signal);
669
+ } catch (err) {
670
+ lastError = err;
671
+ if (isAbortError(err) || opts.signal?.aborted) throw err;
672
+ if (attempt === maxAttempts - 1) throw err;
673
+ const waitMs = computeWait(attempt, initial, cap, null);
674
+ opts.onRetry?.({
675
+ attempt: attempt + 1,
676
+ reason: `network: ${messageOf(err)}`,
677
+ waitMs
678
+ });
679
+ await sleep(waitMs, opts.signal);
680
+ }
681
+ }
682
+ throw lastError ?? new Error("fetchWithRetry: loop exited unexpectedly");
683
+ }
684
+ function computeWait(attempt, initial, cap, retryAfter) {
685
+ if (retryAfter) {
686
+ const seconds = Number.parseFloat(retryAfter);
687
+ if (Number.isFinite(seconds) && seconds > 0) {
688
+ return Math.min(seconds * 1e3, cap);
814
689
  }
815
- cur += ch;
816
- i++;
817
690
  }
818
- if (quote) {
819
- throw new Error(
820
- `shellSplit: unterminated ${quote === '"' ? "double" : "single"} quote in input`
821
- );
691
+ const exp = initial * 2 ** attempt;
692
+ const jitter = exp * (0.75 + Math.random() * 0.5);
693
+ return Math.min(Math.max(jitter, 0), cap);
694
+ }
695
+ function sleep(ms, signal) {
696
+ if (ms <= 0) return Promise.resolve();
697
+ return new Promise((resolve13, reject) => {
698
+ const timer = setTimeout(resolve13, ms);
699
+ if (signal) {
700
+ const onAbort = () => {
701
+ clearTimeout(timer);
702
+ reject(new Error("aborted"));
703
+ };
704
+ if (signal.aborted) onAbort();
705
+ else signal.addEventListener("abort", onAbort, { once: true });
706
+ }
707
+ });
708
+ }
709
+ function isAbortError(err) {
710
+ if (!err || typeof err !== "object") return false;
711
+ const name = err.name;
712
+ return name === "AbortError";
713
+ }
714
+ function messageOf(err) {
715
+ if (err instanceof Error) return err.message;
716
+ try {
717
+ return String(err);
718
+ } catch {
719
+ return "unknown error";
822
720
  }
823
- if (cur.length > 0) tokens.push(cur);
824
- return tokens;
825
721
  }
826
722
 
827
- // src/mcp/spec.ts
828
- var NAME_PREFIX = /^([a-zA-Z_][a-zA-Z0-9_-]*)=(.*)$/;
829
- var HTTP_URL = /^https?:\/\//i;
830
- var STREAMABLE_PREFIX = /^streamable\+(https?:\/\/.+)$/i;
831
- function parseMcpSpec(input) {
832
- const trimmed = input.trim();
833
- if (!trimmed) {
834
- throw new Error("empty MCP spec");
723
+ // src/client.ts
724
+ var Usage = class _Usage {
725
+ constructor(promptTokens = 0, completionTokens = 0, totalTokens = 0, promptCacheHitTokens = 0, promptCacheMissTokens = 0) {
726
+ this.promptTokens = promptTokens;
727
+ this.completionTokens = completionTokens;
728
+ this.totalTokens = totalTokens;
729
+ this.promptCacheHitTokens = promptCacheHitTokens;
730
+ this.promptCacheMissTokens = promptCacheMissTokens;
835
731
  }
836
- const nameMatch = NAME_PREFIX.exec(trimmed);
837
- const name = nameMatch ? nameMatch[1] : null;
838
- const body = (nameMatch ? nameMatch[2] : trimmed).trim();
839
- if (!body) {
840
- throw new Error(`MCP spec has name but no command: ${input}`);
732
+ promptTokens;
733
+ completionTokens;
734
+ totalTokens;
735
+ promptCacheHitTokens;
736
+ promptCacheMissTokens;
737
+ get cacheHitRatio() {
738
+ const denom = this.promptCacheHitTokens + this.promptCacheMissTokens;
739
+ return denom > 0 ? this.promptCacheHitTokens / denom : 0;
841
740
  }
842
- const streamMatch = STREAMABLE_PREFIX.exec(body);
843
- if (streamMatch) {
844
- return { transport: "streamable-http", name, url: streamMatch[1] };
741
+ static fromApi(raw) {
742
+ const u = raw ?? {};
743
+ const promptTokens = u.prompt_tokens ?? 0;
744
+ const cacheHitTokens = u.prompt_cache_hit_tokens ?? 0;
745
+ const cacheMissTokens = u.prompt_cache_miss_tokens ?? Math.max(0, promptTokens - cacheHitTokens);
746
+ return new _Usage(
747
+ promptTokens,
748
+ u.completion_tokens ?? 0,
749
+ u.total_tokens ?? 0,
750
+ cacheHitTokens,
751
+ cacheMissTokens
752
+ );
845
753
  }
846
- if (HTTP_URL.test(body)) {
847
- return { transport: "sse", name, url: body };
754
+ };
755
+ var DeepSeekClient = class {
756
+ apiKey;
757
+ baseUrl;
758
+ timeoutMs;
759
+ retry;
760
+ _fetch;
761
+ minChatIntervalMs;
762
+ nextChatRequestAt = 0;
763
+ constructor(opts = {}) {
764
+ const apiKey = opts.apiKey ?? process.env.DEEPSEEK_API_KEY;
765
+ if (!apiKey) {
766
+ throw new Error(
767
+ "DEEPSEEK_API_KEY is not set. Put it in .env or pass apiKey to DeepSeekClient."
768
+ );
769
+ }
770
+ this.apiKey = apiKey;
771
+ let url = opts.baseUrl ?? process.env.DEEPSEEK_BASE_URL ?? "https://api.deepseek.com";
772
+ while (url.endsWith("/")) url = url.slice(0, -1);
773
+ this.baseUrl = url;
774
+ this.timeoutMs = opts.timeoutMs ?? 66e4;
775
+ this._fetch = opts.fetch ?? globalThis.fetch.bind(globalThis);
776
+ this.retry = opts.retry ?? {};
777
+ const rpm = opts.rateLimit?.rpm ?? loadRateLimit()?.rpm;
778
+ this.minChatIntervalMs = rpm ? Math.ceil(6e4 / rpm) : 0;
848
779
  }
849
- const argv = shellSplit(body);
850
- if (argv.length === 0) {
851
- throw new Error(`MCP spec has name but no command: ${input}`);
780
+ async waitForChatRateLimit(signal) {
781
+ if (this.minChatIntervalMs <= 0) return;
782
+ const now = Date.now();
783
+ const waitMs = Math.max(0, this.nextChatRequestAt - now);
784
+ this.nextChatRequestAt = Math.max(now, this.nextChatRequestAt) + this.minChatIntervalMs;
785
+ if (waitMs <= 0) return;
786
+ await new Promise((resolve13, reject) => {
787
+ const timer = setTimeout(resolve13, waitMs);
788
+ signal?.addEventListener(
789
+ "abort",
790
+ () => {
791
+ clearTimeout(timer);
792
+ reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
793
+ },
794
+ { once: true }
795
+ );
796
+ });
797
+ }
798
+ buildPayload(opts, stream) {
799
+ const payload = {
800
+ model: opts.model,
801
+ messages: opts.messages,
802
+ stream
803
+ };
804
+ if (opts.tools?.length) payload.tools = opts.tools;
805
+ if (opts.temperature !== void 0) payload.temperature = opts.temperature;
806
+ if (opts.maxTokens !== void 0) payload.max_tokens = opts.maxTokens;
807
+ if (opts.responseFormat) payload.response_format = opts.responseFormat;
808
+ if (opts.thinking) {
809
+ payload.extra_body = { thinking: { type: opts.thinking } };
810
+ }
811
+ if (opts.reasoningEffort) {
812
+ payload.reasoning_effort = opts.reasoningEffort;
813
+ }
814
+ return payload;
815
+ }
816
+ /** Returns null on failure so callers can degrade — session must keep working without balance UI. */
817
+ async getBalance(opts = {}) {
818
+ try {
819
+ const resp = await this._fetch(`${this.baseUrl}/user/balance`, {
820
+ method: "GET",
821
+ headers: { Authorization: `Bearer ${this.apiKey}` },
822
+ signal: opts.signal
823
+ });
824
+ if (!resp.ok) return null;
825
+ const data = await resp.json();
826
+ if (!data || !Array.isArray(data.balance_infos)) return null;
827
+ return data;
828
+ } catch {
829
+ return null;
830
+ }
831
+ }
832
+ /** Returns null on failure — callers fall back to a hardcoded model hint. */
833
+ async listModels(opts = {}) {
834
+ try {
835
+ const resp = await this._fetch(`${this.baseUrl}/models`, {
836
+ method: "GET",
837
+ headers: { Authorization: `Bearer ${this.apiKey}` },
838
+ signal: opts.signal
839
+ });
840
+ if (!resp.ok) return null;
841
+ const data = await resp.json();
842
+ if (!data || !Array.isArray(data.data)) return null;
843
+ return data;
844
+ } catch {
845
+ return null;
846
+ }
847
+ }
848
+ async chat(opts) {
849
+ const ctrl = new AbortController();
850
+ const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
851
+ const signal = opts.signal ?? ctrl.signal;
852
+ try {
853
+ await this.waitForChatRateLimit(signal);
854
+ const resp = await fetchWithRetry(
855
+ this._fetch,
856
+ `${this.baseUrl}/chat/completions`,
857
+ {
858
+ method: "POST",
859
+ headers: {
860
+ Authorization: `Bearer ${this.apiKey}`,
861
+ "Content-Type": "application/json"
862
+ },
863
+ body: JSON.stringify(this.buildPayload(opts, false)),
864
+ signal
865
+ },
866
+ { ...this.retry, signal }
867
+ );
868
+ if (!resp.ok) {
869
+ throw new Error(`DeepSeek ${resp.status}: ${await resp.text()}`);
870
+ }
871
+ const data = await resp.json();
872
+ const choice = data.choices?.[0]?.message ?? {};
873
+ return {
874
+ content: choice.content ?? "",
875
+ reasoningContent: choice.reasoning_content ?? null,
876
+ toolCalls: choice.tool_calls ?? [],
877
+ usage: Usage.fromApi(data.usage),
878
+ raw: data
879
+ };
880
+ } finally {
881
+ clearTimeout(timer);
882
+ }
883
+ }
884
+ async *stream(opts) {
885
+ const ctrl = new AbortController();
886
+ const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
887
+ const signal = opts.signal ?? ctrl.signal;
888
+ let resp;
889
+ try {
890
+ await this.waitForChatRateLimit(signal);
891
+ resp = await fetchWithRetry(
892
+ this._fetch,
893
+ `${this.baseUrl}/chat/completions`,
894
+ {
895
+ method: "POST",
896
+ headers: {
897
+ Authorization: `Bearer ${this.apiKey}`,
898
+ "Content-Type": "application/json",
899
+ Accept: "text/event-stream"
900
+ },
901
+ body: JSON.stringify(this.buildPayload(opts, true)),
902
+ signal
903
+ },
904
+ { ...this.retry, signal }
905
+ );
906
+ } catch (err) {
907
+ clearTimeout(timer);
908
+ throw err;
909
+ }
910
+ if (!resp.ok || !resp.body) {
911
+ clearTimeout(timer);
912
+ throw new Error(`DeepSeek ${resp.status}: ${await resp.text().catch(() => "")}`);
913
+ }
914
+ const queue = [];
915
+ let done = false;
916
+ const parser = createParser({
917
+ onEvent: (ev) => {
918
+ if (!ev.data || ev.data === "[DONE]") {
919
+ done = true;
920
+ return;
921
+ }
922
+ try {
923
+ const json = JSON.parse(ev.data);
924
+ const delta = json.choices?.[0]?.delta ?? {};
925
+ const finishReason = json.choices?.[0]?.finish_reason ?? void 0;
926
+ const chunk = { raw: json, finishReason };
927
+ if (typeof delta.content === "string" && delta.content.length > 0) {
928
+ chunk.contentDelta = delta.content;
929
+ }
930
+ if (typeof delta.reasoning_content === "string" && delta.reasoning_content.length > 0) {
931
+ chunk.reasoningDelta = delta.reasoning_content;
932
+ }
933
+ if (Array.isArray(delta.tool_calls) && delta.tool_calls.length > 0) {
934
+ const tc = delta.tool_calls[0];
935
+ chunk.toolCallDelta = {
936
+ index: tc.index ?? 0,
937
+ id: tc.id,
938
+ name: tc.function?.name,
939
+ argumentsDelta: tc.function?.arguments
940
+ };
941
+ }
942
+ if (json.usage) {
943
+ chunk.usage = Usage.fromApi(json.usage);
944
+ }
945
+ queue.push(chunk);
946
+ } catch {
947
+ }
948
+ }
949
+ });
950
+ const reader = resp.body.getReader();
951
+ const decoder = new TextDecoder();
952
+ try {
953
+ while (true) {
954
+ if (queue.length > 0) {
955
+ yield queue.shift();
956
+ continue;
957
+ }
958
+ if (done) break;
959
+ const { value, done: streamDone } = await reader.read();
960
+ if (streamDone) break;
961
+ parser.feed(decoder.decode(value, { stream: true }));
962
+ }
963
+ while (queue.length > 0) yield queue.shift();
964
+ } finally {
965
+ clearTimeout(timer);
966
+ reader.releaseLock();
967
+ }
852
968
  }
853
- const [command, ...args] = argv;
854
- return { transport: "stdio", name, command, args };
855
- }
856
-
857
- // src/config.ts
858
- var BUILTIN_TYPE_DOCS = {
859
- user: "role / skills / preferences",
860
- feedback: "corrections or confirmed approaches",
861
- project: "facts / decisions about the current work",
862
- reference: "pointers to external systems the user uses"
863
969
  };
864
- function loadMemoryTypeRegistry(cfg = readConfig()) {
865
- const out = [];
866
- for (const name of ["user", "feedback", "project", "reference"]) {
867
- out.push({ name, builtin: true, description: BUILTIN_TYPE_DOCS[name] });
970
+
971
+ // src/core/pause-gate.ts
972
+ var PauseGate = class {
973
+ _nextId = 0;
974
+ _pending = /* @__PURE__ */ new Map();
975
+ _listeners = /* @__PURE__ */ new Set();
976
+ _auditListener = null;
977
+ /** Block until the user responds. Takes a named options object so the
978
+ * kind and payload fields don't get confused at the call site. */
979
+ ask(opts) {
980
+ const { kind, payload } = opts;
981
+ if (this._listeners.size === 0) {
982
+ throw new Error(
983
+ `${kind}: no confirmation listener registered \u2014 cannot prompt the user. This tool can only be used inside an interactive Reasonix session.`
984
+ );
985
+ }
986
+ return new Promise((resolve13) => {
987
+ const id = this._nextId++;
988
+ const request = { id, kind, payload };
989
+ this._pending.set(id, { resolve: resolve13, request });
990
+ for (const fn of this._listeners) {
991
+ try {
992
+ fn(request);
993
+ } catch {
994
+ }
995
+ }
996
+ });
868
997
  }
869
- const seen = new Set(out.map((e) => e.name));
870
- for (const raw of cfg.memory?.customTypes ?? []) {
871
- if (!raw || typeof raw.name !== "string") continue;
872
- const name = raw.name.trim();
873
- if (!name || !/^[a-zA-Z][a-zA-Z0-9_-]{0,31}$/.test(name)) continue;
874
- if (seen.has(name)) continue;
875
- seen.add(name);
876
- const entry = { name, builtin: false };
877
- if (typeof raw.description === "string") entry.description = raw.description;
878
- if (raw.priority === "low" || raw.priority === "medium" || raw.priority === "high") {
879
- entry.priority = raw.priority;
998
+ /** Resolve a pending request. Called by the App's modal callback. */
999
+ resolve(id, data) {
1000
+ const p = this._pending.get(id);
1001
+ if (!p) return;
1002
+ this._pending.delete(id);
1003
+ this.emitAuditEvent(p.request, data);
1004
+ p.resolve(data);
1005
+ }
1006
+ /** Safe-cancel every outstanding request frees stranded tool fns on Esc / /new. */
1007
+ cancelAll() {
1008
+ const ids = [...this._pending.keys()];
1009
+ for (const id of ids) {
1010
+ const p = this._pending.get(id);
1011
+ if (!p) continue;
1012
+ this._pending.delete(id);
1013
+ p.resolve(safeCancelVerdict(p.request.kind));
880
1014
  }
881
- if (raw.expires === "project_end") entry.expires = raw.expires;
882
- out.push(entry);
883
1015
  }
884
- return out;
885
- }
886
- function memoryTypeDefaults(typeName, cfg = readConfig()) {
887
- const found = loadMemoryTypeRegistry(cfg).find((e) => e.name === typeName);
888
- if (!found) return {};
889
- const out = {};
890
- if (found.priority) out.priority = found.priority;
891
- if (found.expires) out.expires = found.expires;
892
- return out;
893
- }
894
- var DEFAULT_METASO_API_KEY = "mk-E384C1DD5E8501BB7EFE27C949AFDE5B";
895
- function loadMetasoApiKey(path2 = defaultConfigPath()) {
896
- if (process.env.METASO_API_KEY) return process.env.METASO_API_KEY;
897
- const cfg = readConfig(path2).metasoApiKey;
898
- if (cfg && typeof cfg === "string" && cfg.trim()) return cfg.trim();
899
- return DEFAULT_METASO_API_KEY;
900
- }
901
- function defaultConfigPath() {
902
- return join(homedir(), ".reasonix", "config.json");
903
- }
904
- function readConfig(path2 = defaultConfigPath()) {
905
- try {
906
- const raw = readFileSync(path2, "utf8");
907
- const parsed = JSON.parse(raw);
908
- if (parsed && typeof parsed === "object") return parsed;
909
- } catch {
1016
+ /** Cancel one pending request — used by multi-tab hosts that need per-scope abort. */
1017
+ cancel(id) {
1018
+ const p = this._pending.get(id);
1019
+ if (!p) return false;
1020
+ this._pending.delete(id);
1021
+ p.resolve(safeCancelVerdict(p.request.kind));
1022
+ return true;
910
1023
  }
911
- return {};
912
- }
913
- function writeConfig(cfg, path2 = defaultConfigPath()) {
914
- mkdirSync(dirname(path2), { recursive: true });
915
- writeFileSync(path2, JSON.stringify(cfg, null, 2), "utf8");
916
- try {
917
- chmodSync(path2, 384);
918
- } catch {
1024
+ setAuditListener(fn) {
1025
+ this._auditListener = fn;
919
1026
  }
920
- }
921
- function loadLanguage(path2 = defaultConfigPath()) {
922
- return readConfig(path2).lang;
923
- }
924
- function loadApiKey(path2 = defaultConfigPath()) {
925
- if (process.env.DEEPSEEK_API_KEY) return process.env.DEEPSEEK_API_KEY;
926
- return readConfig(path2).apiKey;
927
- }
928
- function loadBaseUrl(path2 = defaultConfigPath()) {
929
- if (process.env.DEEPSEEK_BASE_URL) return process.env.DEEPSEEK_BASE_URL;
930
- return readConfig(path2).baseUrl;
931
- }
932
- function saveBaseUrl(url, path2 = defaultConfigPath()) {
933
- const cfg = readConfig(path2);
934
- const trimmed = url.trim();
935
- if (trimmed) {
936
- cfg.baseUrl = trimmed;
937
- } else {
938
- cfg.baseUrl = void 0;
1027
+ /** Subscribe to new pause requests. Returns an unsubscribe function. */
1028
+ on(fn) {
1029
+ this._listeners.add(fn);
1030
+ return () => {
1031
+ this._listeners.delete(fn);
1032
+ };
939
1033
  }
940
- writeConfig(cfg, path2);
941
- }
942
- function resolveSkillPath(raw, baseDir) {
943
- const homeExpanded = expandCurrentUserHome(raw.trim());
944
- return resolve(isAbsolute(homeExpanded) ? homeExpanded : join(baseDir, homeExpanded));
945
- }
946
- function normalizeSkillPathEntries(paths, baseDir) {
947
- const out = [];
948
- const seen = /* @__PURE__ */ new Set();
949
- for (const value of paths) {
950
- if (typeof value !== "string") continue;
951
- const raw = value.trim();
952
- if (!raw) continue;
953
- const resolved = resolveSkillPath(raw, baseDir);
954
- const key = skillPathKey(resolved);
955
- if (seen.has(key)) continue;
956
- seen.add(key);
957
- out.push({ raw, resolved });
1034
+ /** Current pending request, if any (polling fallback). */
1035
+ get current() {
1036
+ for (const [, p] of this._pending) return p.request;
1037
+ return null;
958
1038
  }
959
- return out;
960
- }
961
- function resolveSkillPaths(paths, baseDir) {
962
- return normalizeSkillPathEntries(paths, baseDir).map((entry) => entry.resolved);
963
- }
964
- function skillPathKey(path2) {
965
- return process.platform === "win32" ? path2.toLowerCase() : path2;
966
- }
967
- function expandCurrentUserHome(path2) {
968
- if (path2 === "~") return homedir();
969
- if (path2.startsWith("~/") || path2.startsWith("~\\")) return join(homedir(), path2.slice(2));
970
- return path2;
971
- }
972
- function loadResolvedSkillPaths(baseDir = process.cwd(), path2 = defaultConfigPath()) {
973
- const raw = readConfig(path2).skills?.paths;
974
- return Array.isArray(raw) ? resolveSkillPaths(raw, baseDir) : [];
975
- }
976
- function webSearchEngine(path2 = defaultConfigPath()) {
977
- const cfg = readConfig(path2).webSearchEngine;
978
- if (cfg === "searxng") return "searxng";
979
- if (cfg === "metaso") return "metaso";
980
- return "mojeek";
981
- }
982
- function webSearchEndpoint(path2 = defaultConfigPath()) {
983
- const cfg = readConfig(path2).webSearchEndpoint;
984
- if (cfg && typeof cfg === "string") return cfg;
985
- return "http://localhost:8080";
986
- }
987
- function saveApiKey(key, path2 = defaultConfigPath()) {
988
- const cfg = readConfig(path2);
989
- cfg.apiKey = key.trim();
990
- writeConfig(cfg, path2);
991
- }
992
- function findProjectKey(cfg, rootDir) {
993
- const projects = cfg.projects;
994
- if (!projects) return void 0;
995
- if (Object.hasOwn(projects, rootDir)) return rootDir;
996
- if (process.platform !== "win32") return void 0;
997
- const lower = rootDir.toLowerCase();
998
- for (const k of Object.keys(projects)) {
999
- if (k.toLowerCase() === lower) return k;
1039
+ emitAuditEvent(request, data) {
1040
+ if (!this._auditListener) return;
1041
+ if (request.kind !== "run_command" && request.kind !== "run_background") return;
1042
+ if (!data || typeof data !== "object") return;
1043
+ const choice = data;
1044
+ try {
1045
+ switch (choice.type) {
1046
+ case "run_once":
1047
+ this._auditListener({
1048
+ type: "tool.confirm.allow",
1049
+ kind: request.kind,
1050
+ payload: request.payload
1051
+ });
1052
+ break;
1053
+ case "deny":
1054
+ this._auditListener({
1055
+ type: "tool.confirm.deny",
1056
+ kind: request.kind,
1057
+ payload: request.payload,
1058
+ denyContext: choice.denyContext
1059
+ });
1060
+ break;
1061
+ case "always_allow":
1062
+ if (typeof choice.prefix !== "string") return;
1063
+ this._auditListener({
1064
+ type: "tool.confirm.always_allow",
1065
+ kind: request.kind,
1066
+ payload: request.payload,
1067
+ prefix: choice.prefix
1068
+ });
1069
+ break;
1070
+ default:
1071
+ break;
1072
+ }
1073
+ } catch {
1074
+ }
1075
+ }
1076
+ };
1077
+ function safeCancelVerdict(kind) {
1078
+ switch (kind) {
1079
+ case "run_command":
1080
+ case "run_background":
1081
+ case "path_access":
1082
+ return { type: "deny" };
1083
+ case "plan_proposed":
1084
+ return { type: "cancel" };
1085
+ case "plan_checkpoint":
1086
+ return { type: "stop" };
1087
+ case "plan_revision":
1088
+ return { type: "cancelled" };
1089
+ case "choice":
1090
+ return { type: "cancel" };
1000
1091
  }
1001
- return void 0;
1002
- }
1003
- function addProjectShellAllowed(rootDir, prefix, path2 = defaultConfigPath()) {
1004
- const trimmed = prefix.trim();
1005
- if (!trimmed) return;
1006
- const cfg = readConfig(path2);
1007
- if (!cfg.projects) cfg.projects = {};
1008
- const key = findProjectKey(cfg, rootDir) ?? rootDir;
1009
- if (!cfg.projects[key]) cfg.projects[key] = {};
1010
- const existing = cfg.projects[key].shellAllowed ?? [];
1011
- if (existing.includes(trimmed)) return;
1012
- cfg.projects[key].shellAllowed = [...existing, trimmed];
1013
- writeConfig(cfg, path2);
1014
- }
1015
- function loadProjectPathAllowed(rootDir, path2 = defaultConfigPath()) {
1016
- const cfg = readConfig(path2);
1017
- const key = findProjectKey(cfg, rootDir);
1018
- if (key === void 0) return [];
1019
- return cfg.projects?.[key]?.pathAllowed ?? [];
1020
- }
1021
- function addProjectPathAllowed(rootDir, prefix, path2 = defaultConfigPath()) {
1022
- const trimmed = prefix.trim();
1023
- if (!trimmed) return;
1024
- const cfg = readConfig(path2);
1025
- if (!cfg.projects) cfg.projects = {};
1026
- const key = findProjectKey(cfg, rootDir) ?? rootDir;
1027
- if (!cfg.projects[key]) cfg.projects[key] = {};
1028
- const existing = cfg.projects[key].pathAllowed ?? [];
1029
- if (existing.includes(trimmed)) return;
1030
- cfg.projects[key].pathAllowed = [...existing, trimmed];
1031
- writeConfig(cfg, path2);
1032
- }
1033
- function isPlausibleKey(key) {
1034
- const trimmed = key.trim();
1035
- if (trimmed.length < 16) return false;
1036
- return !/\s/.test(trimmed);
1037
- }
1038
- function redactKey(key) {
1039
- if (!key) return "";
1040
- if (key.length <= 12) return "****";
1041
- return `${key.slice(0, 6)}\u2026${key.slice(-4)}`;
1042
1092
  }
1093
+ var pauseGate = new PauseGate();
1094
+
1095
+ // src/hooks.ts
1096
+ import { spawn } from "child_process";
1097
+ import { existsSync, readFileSync as readFileSync2 } from "fs";
1098
+ import { homedir as homedir2 } from "os";
1099
+ import { join as join2 } from "path";
1043
1100
 
1044
1101
  // src/i18n/EN.ts
1045
1102
  var EN = {
@@ -1140,8 +1197,9 @@ var EN = {
1140
1197
  { key: "wheel", text: "scrolls chat history (works on web/cloud/SSH terminals too)" },
1141
1198
  {
1142
1199
  key: "\u2191 / \u2193",
1143
- text: "scroll chat \xB7 use Ctrl+P / Ctrl+N for prompt history + multi-line cursor"
1144
- }
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)" }
1145
1203
  ]
1146
1204
  }
1147
1205
  ],
@@ -1155,11 +1213,11 @@ var EN = {
1155
1213
  rows: [
1156
1214
  { key: "Enter", text: "submit the prompt" },
1157
1215
  { key: "Shift+Enter", text: "insert a newline in the prompt" },
1158
- { key: "\u2191 / \u2193", text: "scroll chat history (mouse wheel routes here too)" },
1159
1216
  {
1160
- key: "Ctrl+P / Ctrl+N",
1217
+ key: "\u2191 / \u2193",
1161
1218
  text: "previous / next prompt history \xB7 cursor up / down in a multi-line draft"
1162
1219
  },
1220
+ { key: "Ctrl+P / Ctrl+N", text: "readline alias for \u2191 / \u2193" },
1163
1221
  { key: "Ctrl+A / Ctrl+E", text: "jump to start / end of the current line" },
1164
1222
  { key: "Ctrl+W", text: "delete the word before the cursor" },
1165
1223
  { key: "Ctrl+U", text: "clear the entire prompt buffer" },
@@ -1168,7 +1226,11 @@ var EN = {
1168
1226
  { key: "Esc", text: "dismiss picker \xB7 abort the running model turn" },
1169
1227
  { key: "Ctrl+C", text: "abort the running model turn (NOT copy \u2014 see clipboard)" },
1170
1228
  { key: "PgUp / PgDn", text: "scroll chat history a page at a time" },
1171
- { 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
+ }
1172
1234
  ]
1173
1235
  },
1174
1236
  {
@@ -1207,7 +1269,7 @@ var EN = {
1207
1269
  ]
1208
1270
  }
1209
1271
  ],
1210
- 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."
1211
1273
  },
1212
1274
  tipShownOnce: "shown once",
1213
1275
  modelOverride: "override the default model",
@@ -1349,7 +1411,7 @@ var EN = {
1349
1411
  },
1350
1412
  cwd: {
1351
1413
  description: "switch the workspace root mid-session \u2014 re-points fs / shell / memory tools, reloads project hooks, refreshes the at-mention walker",
1352
- argsHint: "<path>"
1414
+ argsHint: "[path]"
1353
1415
  },
1354
1416
  stop: { description: "abort the current model turn (typed alternative to Esc)" },
1355
1417
  feedback: { description: "open a GitHub issue with diagnostic info copied to clipboard" },
@@ -1362,7 +1424,7 @@ var EN = {
1362
1424
  sessions: { description: "list saved sessions (current marked with \u25B8)" },
1363
1425
  title: { description: "ask the model to rename this session from the conversation" },
1364
1426
  qq: {
1365
- 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)",
1366
1428
  argsHint: "[connect [appId appSecret [sandbox]]|status|disconnect]"
1367
1429
  },
1368
1430
  setup: { description: "reminds you to exit and run `reasonix setup`" },
@@ -1586,6 +1648,8 @@ var EN = {
1586
1648
  notedVerbCreated: "created",
1587
1649
  notedVerbAppended: "appended to",
1588
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",
1589
1653
  commandFailed: "! command failed",
1590
1654
  btwUsage: "\u25B8 /btw <question> \u2014 ask a side question without polluting the conversation context.",
1591
1655
  btwHeader: "\u226B btw",
@@ -1603,6 +1667,10 @@ var EN = {
1603
1667
  sessionTitleRenameFailed: '\u25B8 could not rename the session for title "{title}".',
1604
1668
  sessionTitleRenamed: '\u25B8 session renamed to "{name}" \u2014 {title}',
1605
1669
  sessionTitleAutoRenamed: '\u25B8 auto-named session "{name}" \u2014 {title}',
1670
+ workspaceSwitched: "\u25B8 workspace switched to {root}",
1671
+ semanticRepointed: "\u25B8 semantic_search re-pointed at {root}",
1672
+ semanticDisabledForRoot: "\u25B8 semantic_search disabled (no compatible index in {root})",
1673
+ semanticRebootstrapFailed: "\u25B8 semantic_search re-bootstrap failed: {reason}",
1606
1674
  denied: "\u25B8 denied: {cmd}{context}",
1607
1675
  alwaysAllowed: '\u25B8 always allowed "{prefix}" for {dir}',
1608
1676
  runningCommand: "\u25B8 running: {cmd}",
@@ -1639,7 +1707,6 @@ var EN = {
1639
1707
  preflightNoFold: "preflight: request ~{estimate}/{ctxMax} tokens ({pct}%) and nothing left to truncate \u2014 DeepSeek will likely 400. Run /clear or /new to start fresh.",
1640
1708
  flashEscalation: "\u21E7 flash requested escalation \u2014 retrying this turn on {model}{reasonSuffix}",
1641
1709
  harvestStatus: "extracting plan state from reasoning\u2026",
1642
- autoEscalation: "\u21E7 auto-escalating to {model} for the rest of this turn \u2014 flash hit {breakdown}. Next turn falls back to {fallback} unless /pro is armed.",
1643
1710
  repeatToolCallWarning: "Caught a repeated tool call \u2014 let the model see the issue and retry with a different approach.",
1644
1711
  stormStuck: "Stopped a stuck retry loop \u2014 the model kept calling the same tool with identical args after a self-correction nudge. Try /retry, rephrase, or rule out the underlying blocker.",
1645
1712
  stormSuppressed: "Suppressed {count} repeated tool call(s) \u2014 same name + args fired 3+ times.",
@@ -1717,6 +1784,48 @@ var EN = {
1717
1784
  titleStarted: "\u25B8 naming session\u2026",
1718
1785
  titleFailed: "\u25B8 session title failed: {reason}"
1719
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
+ },
1720
1829
  admin: {
1721
1830
  doctorNeedsTui: "/doctor needs a TUI context (postDoctor wired).",
1722
1831
  doctorRunning: "\u2695 Doctor \u2014 running health checks\u2026",
@@ -1983,12 +2092,14 @@ var EN = {
1983
2092
  usageSearxng: " /search-engine searxng use SearXNG at default endpoint",
1984
2093
  usageSearxngUrl: " /search-engine searxng <url> use SearXNG at custom endpoint",
1985
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)",
1986
2096
  alias: "Alias: /se",
1987
2097
  searxngInfo: "SearXNG is a self-hosted metasearch engine (https://github.com/searxng/searxng).",
1988
2098
  searxngInstall: "Install it with: docker run -d -p 8080:8080 searxng/searxng",
1989
2099
  switched: 'Switched web search engine to "{engine}".{note}',
1990
2100
  switchedSearxngNote: " Make sure SearXNG is running at {endpoint}.",
1991
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.",
1992
2103
  confirmed: '\u2713 Web search engine set to "{engine}"{detail}. Next assistant turn will pick up the change.',
1993
2104
  confirmedDetail: " ({endpoint})"
1994
2105
  },
@@ -2124,17 +2235,27 @@ var EN = {
2124
2235
  linesBelow: " \u2193 {count} line below (\u2193/j or Space/PgDn)",
2125
2236
  linesBelowPlural: " \u2193 {count} lines below (\u2193/j or Space/PgDn)"
2126
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
+ },
2127
2245
  sessionPicker: {
2128
2246
  header: " \u25C8 REASONIX \xB7 pick a session ",
2129
2247
  title: "pick a session \u2014 {workspace}",
2130
2248
  messages: "{count} message",
2131
2249
  messagesPlural: "{count} messages",
2132
2250
  turns: "{count} turns",
2133
- pickerHint: "\u2191\u2193 pick \xB7 \u23CE open \xB7 [n] new \xB7 [d] delete \xB7 [r] rename \xB7 esc quit",
2251
+ pickerHint: "\u2191\u2193 pick \xB7 / search \xB7 \u23CE open \xB7 [n] new \xB7 [d] delete \xB7 [r] rename \xB7 esc quit",
2134
2252
  empty: " no saved sessions in this workspace yet \u2014 press ",
2135
2253
  emptyNew: " to start a new one",
2136
2254
  renamePrompt: ' rename "{from}" \u2192 ',
2137
2255
  renameHint: " \u23CE confirm rename \xB7 esc cancel",
2256
+ searchPrompt: " search sessions: /",
2257
+ searchHint: " type to filter \xB7 \u23CE open match \xB7 esc clear",
2258
+ searchEmpty: " no sessions match this search",
2138
2259
  emptyHint: " \u23CE new session \xB7 esc quit",
2139
2260
  justNow: "just now",
2140
2261
  minAgo: "{count} min ago",
@@ -2142,6 +2263,18 @@ var EN = {
2142
2263
  hoursAgo: "{count}h ago",
2143
2264
  daysAgo: "{count} days ago"
2144
2265
  },
2266
+ workspacePicker: {
2267
+ header: " \u25C8 REASONIX \xB7 pick a workspace ",
2268
+ title: "pick a workspace \u2014 {workspace}",
2269
+ sessions: "{count} session",
2270
+ sessionsPlural: "{count} sessions",
2271
+ current: "current",
2272
+ pickerHint: "\u2191\u2193 pick \xB7 / search \xB7 \u23CE switch + pick session \xB7 esc quit \xB7 /cwd <path> adds one",
2273
+ empty: " no known workspaces yet \u2014 run /cwd <path> once to add one",
2274
+ searchPrompt: " search workspaces: /",
2275
+ searchHint: " type to filter \xB7 \u23CE switch + pick session \xB7 esc clear",
2276
+ searchEmpty: " no workspaces match this search"
2277
+ },
2145
2278
  modelPicker: {
2146
2279
  header: " \u25C8 REASONIX \xB7 pick a setup ",
2147
2280
  loading: " \xB7 loading catalog\u2026",
@@ -2242,6 +2375,11 @@ var EN = {
2242
2375
  metasoServerError: "web_search: Metaso server error ({status}) \u2014 try again later, or switch engine with /search-engine mojeek",
2243
2376
  metasoParseError: "web_search: Metaso returned unparseable response (HTTP {status}) \u2014 try again later",
2244
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",
2245
2383
  fetchStatus: "web_fetch {status} for {url} \u2014 try: confirm the URL resolves in a browser; status suggests the host returned an error page",
2246
2384
  fetchRateLimit429: "web_fetch 429 for {url} \u2014 try: wait 10s before retrying; the host is rate-limiting this client",
2247
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",
@@ -2360,7 +2498,8 @@ var EN = {
2360
2498
  scrollAbove: " \u2191 {scroll} / {max} row above",
2361
2499
  scrollAbovePlural: " \u2191 {scroll} / {max} rows above",
2362
2500
  scrollMore: " \u2014 {remaining} more",
2363
- scrollPgUp: " \xB7 PgUp / wheel / \u2191"
2501
+ scrollPgUp: " \xB7 PgUp / wheel",
2502
+ scrollCopy: " \xB7 /copy enters copy mode"
2364
2503
  },
2365
2504
  slashArgPicker: {
2366
2505
  noMatch: 'no match for "{partial}"',
@@ -2418,7 +2557,8 @@ var EN = {
2418
2557
  reconnectDetail: "tearing down \xB7 re-handshake \xB7 listing tools",
2419
2558
  disabledDetail: "via /mcp disable {name}",
2420
2559
  failedSetupHint: "\u2192 run `reasonix setup` to remove this entry, or fix the underlying issue (missing npm package, network, etc.).",
2421
- 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."
2422
2562
  },
2423
2563
  checkpointPicker: {
2424
2564
  title: "restore a checkpoint \u2014 {workspace}",
@@ -2563,8 +2703,9 @@ var zhCN = {
2563
2703
  { key: "\u6EDA\u8F6E", text: "\u6EDA\u52A8\u804A\u5929\u8BB0\u5F55\uFF08Web / \u4E91\u7AEF / SSH \u7EC8\u7AEF\u4E5F\u80FD\u7528\uFF09" },
2564
2704
  {
2565
2705
  key: "\u2191 / \u2193",
2566
- text: "\u6EDA\u52A8\u804A\u5929 \xB7 \u8F93\u5165\u6846\u5386\u53F2 + \u591A\u884C\u5149\u6807\u7528 Ctrl+P / Ctrl+N"
2567
- }
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" }
2568
2709
  ]
2569
2710
  }
2570
2711
  ],
@@ -2578,11 +2719,11 @@ var zhCN = {
2578
2719
  rows: [
2579
2720
  { key: "Enter", text: "\u63D0\u4EA4\u8F93\u5165" },
2580
2721
  { key: "Shift+Enter", text: "\u5728\u8F93\u5165\u6846\u4E2D\u63D2\u5165\u6362\u884C" },
2581
- { key: "\u2191 / \u2193", text: "\u6EDA\u52A8\u804A\u5929\u8BB0\u5F55\uFF08\u9F20\u6807\u6EDA\u8F6E\u4E5F\u8D70\u8FD9\u6761\u8DEF\u5F84\uFF09" },
2582
2722
  {
2583
- key: "Ctrl+P / Ctrl+N",
2723
+ key: "\u2191 / \u2193",
2584
2724
  text: "\u4E0A\u4E00\u6761 / \u4E0B\u4E00\u6761\u8F93\u5165\u5386\u53F2 \xB7 \u591A\u884C\u8349\u7A3F\u4E2D\u6309\u884C\u79FB\u52A8\u5149\u6807"
2585
2725
  },
2726
+ { key: "Ctrl+P / Ctrl+N", text: "\u2191 / \u2193 \u7684 readline \u540C\u4E49\u952E" },
2586
2727
  { key: "Ctrl+A / Ctrl+E", text: "\u8DF3\u5230\u5F53\u524D\u884C\u7684\u5F00\u5934 / \u7ED3\u5C3E" },
2587
2728
  { key: "Ctrl+W", text: "\u5220\u9664\u5149\u6807\u524D\u7684\u4E00\u4E2A\u8BCD" },
2588
2729
  { key: "Ctrl+U", text: "\u6E05\u7A7A\u6574\u4E2A\u8F93\u5165\u7F13\u51B2\u533A" },
@@ -2591,7 +2732,8 @@ var zhCN = {
2591
2732
  { key: "Esc", text: "\u5173\u95ED\u5F39\u51FA\u9009\u62E9\u5668 \xB7 \u4E2D\u6B62\u5F53\u524D\u6A21\u578B\u56DE\u5408" },
2592
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" },
2593
2734
  { key: "PgUp / PgDn", text: "\u6574\u9875\u6EDA\u52A8\u804A\u5929\u8BB0\u5F55" },
2594
- { 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" }
2595
2737
  ]
2596
2738
  },
2597
2739
  {
@@ -2630,7 +2772,7 @@ var zhCN = {
2630
2772
  ]
2631
2773
  }
2632
2774
  ],
2633
- 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"
2634
2776
  },
2635
2777
  tipShownOnce: "\u4EC5\u663E\u793A\u4E00\u6B21",
2636
2778
  modelOverride: "\u8986\u76D6\u9ED8\u8BA4\u6A21\u578B",
@@ -2775,7 +2917,7 @@ var zhCN = {
2775
2917
  keys: { description: "\u952E\u76D8 + \u9F20\u6807 + \u590D\u5236\u7C98\u8D34\u53C2\u8003" },
2776
2918
  cwd: {
2777
2919
  description: "\u5207\u6362\u5DE5\u4F5C\u533A\u6839\u76EE\u5F55 \u2014 \u91CD\u65B0\u6307\u5411\u6587\u4EF6/Shell/\u8BB0\u5FC6\u5DE5\u5177\uFF0C\u91CD\u8F7D\u9879\u76EE hooks\uFF0C\u5237\u65B0 @ \u5F15\u7528\u904D\u5386\u5668",
2778
- argsHint: "<path>"
2920
+ argsHint: "[path]"
2779
2921
  },
2780
2922
  stop: { description: "\u4E2D\u6B62\u5F53\u524D\u6A21\u578B\u56DE\u5408\uFF08\u6309 Esc \u7684\u66FF\u4EE3\u65B9\u5F0F\uFF09" },
2781
2923
  feedback: { description: "\u6253\u5F00 GitHub Issue\uFF0C\u8BCA\u65AD\u4FE1\u606F\u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F" },
@@ -2787,7 +2929,7 @@ var zhCN = {
2787
2929
  sessions: { description: "\u5217\u51FA\u5DF2\u4FDD\u5B58\u7684\u4F1A\u8BDD\uFF08\u5F53\u524D\u6807\u8BB0\u4E3A \u25B8\uFF09" },
2788
2930
  title: { description: "\u8BA9\u6A21\u578B\u6839\u636E\u5F53\u524D\u5BF9\u8BDD\u91CD\u547D\u540D\u6B64\u4F1A\u8BDD" },
2789
2931
  qq: {
2790
- 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",
2791
2933
  argsHint: "[connect [appId appSecret [sandbox]]|status|disconnect]"
2792
2934
  },
2793
2935
  setup: { description: "\u63D0\u9192\u60A8\u9000\u51FA\u5E76\u8FD0\u884C `reasonix setup`" },
@@ -3013,6 +3155,8 @@ var zhCN = {
3013
3155
  notedVerbCreated: "\u521B\u5EFA",
3014
3156
  notedVerbAppended: "\u8FFD\u52A0\u5230",
3015
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",
3016
3160
  commandFailed: "! \u547D\u4EE4\u5931\u8D25",
3017
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",
3018
3162
  btwHeader: "\u226B btw",
@@ -3030,6 +3174,10 @@ var zhCN = {
3030
3174
  sessionTitleRenameFailed: '\u25B8 \u65E0\u6CD5\u6309\u6807\u9898 "{title}" \u91CD\u547D\u540D\u4F1A\u8BDD\u3002',
3031
3175
  sessionTitleRenamed: '\u25B8 \u4F1A\u8BDD\u5DF2\u91CD\u547D\u540D\u4E3A "{name}" \u2014 {title}',
3032
3176
  sessionTitleAutoRenamed: '\u25B8 \u5DF2\u81EA\u52A8\u547D\u540D\u4F1A\u8BDD "{name}" \u2014 {title}',
3177
+ workspaceSwitched: "\u25B8 \u5DE5\u4F5C\u533A\u5DF2\u5207\u6362\u5230 {root}",
3178
+ semanticRepointed: "\u25B8 semantic_search \u5DF2\u6307\u5411 {root}",
3179
+ semanticDisabledForRoot: "\u25B8 semantic_search \u5DF2\u7981\u7528\uFF08{root} \u6CA1\u6709\u517C\u5BB9\u7D22\u5F15\uFF09",
3180
+ semanticRebootstrapFailed: "\u25B8 semantic_search \u91CD\u65B0\u521D\u59CB\u5316\u5931\u8D25\uFF1A{reason}",
3033
3181
  denied: "\u25B8 \u5DF2\u62D2\u7EDD\uFF1A{cmd}{context}",
3034
3182
  alwaysAllowed: '\u25B8 \u5DF2\u5BF9 {dir} \u6C38\u4E45\u5141\u8BB8 "{prefix}"',
3035
3183
  runningCommand: "\u25B8 \u6B63\u5728\u6267\u884C\uFF1A{cmd}",
@@ -3066,7 +3214,6 @@ var zhCN = {
3066
3214
  preflightNoFold: "\u9884\u68C0\uFF1A\u8BF7\u6C42\u7EA6 {estimate}/{ctxMax} tokens\uFF08{pct}%\uFF09\u4E14\u6CA1\u6709\u53EF\u88C1\u526A\u7684\u5185\u5BB9 \u2014 DeepSeek \u5927\u6982\u7387\u4F1A\u8FD4\u56DE 400\u3002\u8BF7\u8FD0\u884C /clear \u6216 /new \u91CD\u65B0\u5F00\u59CB\u3002",
3067
3215
  flashEscalation: "\u21E7 flash \u8BF7\u6C42\u5347\u7EA7 \u2014 \u672C\u8F6E\u6539\u7528 {model}{reasonSuffix}",
3068
3216
  harvestStatus: "\u6B63\u5728\u4ECE\u63A8\u7406\u8FC7\u7A0B\u63D0\u53D6\u8BA1\u5212\u72B6\u6001\u2026",
3069
- autoEscalation: "\u21E7 \u672C\u8F6E\u5269\u4F59\u8C03\u7528\u81EA\u52A8\u5347\u7EA7\u5230 {model} \u2014 flash \u547D\u4E2D {breakdown}\u3002\u4E0B\u4E00\u8F6E\u56DE\u9000\u5230 {fallback}\uFF0C\u9664\u975E\u5DF2\u88C5\u5907 /pro\u3002",
3070
3217
  repeatToolCallWarning: "\u62E6\u622A\u5230\u91CD\u590D\u5DE5\u5177\u8C03\u7528 \u2014 \u8BA9\u6A21\u578B\u5BDF\u89C9\u95EE\u9898\u5E76\u6362\u79CD\u65B9\u5F0F\u91CD\u8BD5\u3002",
3071
3218
  stormStuck: "\u5DF2\u505C\u6B62\u5361\u6B7B\u7684\u91CD\u8BD5\u5FAA\u73AF \u2014 \u6A21\u578B\u5728\u81EA\u7EA0\u63D0\u793A\u540E\u4ECD\u4EE5\u76F8\u540C\u53C2\u6570\u91CD\u590D\u8C03\u7528\u540C\u4E00\u5DE5\u5177\u3002\u8BF7\u5C1D\u8BD5 /retry\u3001\u6362\u79CD\u8BF4\u6CD5\uFF0C\u6216\u6392\u67E5\u5E95\u5C42\u963B\u585E\u3002",
3072
3219
  stormSuppressed: "\u5DF2\u6291\u5236 {count} \u6B21\u91CD\u590D\u5DE5\u5177\u8C03\u7528 \u2014 \u540C\u4E00\u540D\u79F0 + \u53C2\u6570\u89E6\u53D1 3 \u6B21\u4EE5\u4E0A\u3002",
@@ -3144,6 +3291,48 @@ var zhCN = {
3144
3291
  titleStarted: "\u25B8 \u6B63\u5728\u547D\u540D\u4F1A\u8BDD\u2026",
3145
3292
  titleFailed: "\u25B8 \u4F1A\u8BDD\u547D\u540D\u5931\u8D25\uFF1A{reason}"
3146
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
+ },
3147
3336
  admin: {
3148
3337
  doctorNeedsTui: "/doctor \u9700\u8981 TUI \u4E0A\u4E0B\u6587\uFF08postDoctor \u5DF2\u8FDE\u63A5\uFF09\u3002",
3149
3338
  doctorRunning: "\u2695 \u5065\u5EB7\u68C0\u67E5 \u2014 \u6B63\u5728\u8FD0\u884C\u2026",
@@ -3410,12 +3599,14 @@ var zhCN = {
3410
3599
  usageSearxng: " /search-engine searxng \u4F7F\u7528 SearXNG \u9ED8\u8BA4\u7AEF\u70B9",
3411
3600
  usageSearxngUrl: " /search-engine searxng <url> \u4F7F\u7528 SearXNG \u81EA\u5B9A\u4E49\u7AEF\u70B9",
3412
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",
3413
3603
  alias: "\u522B\u540D\uFF1A/se",
3414
3604
  searxngInfo: "SearXNG \u662F\u4E00\u4E2A\u81EA\u6258\u7BA1\u7684\u5143\u641C\u7D22\u5F15\u64CE\uFF08https://github.com/searxng/searxng\uFF09\u3002",
3415
3605
  searxngInstall: "\u5B89\u88C5\u547D\u4EE4\uFF1A docker run -d -p 8080:8080 searxng/searxng",
3416
3606
  switched: '\u5DF2\u5207\u6362\u7F51\u9875\u641C\u7D22\u5F15\u64CE\u4E3A "{engine}"\u3002{note}',
3417
3607
  switchedSearxngNote: " \u8BF7\u786E\u4FDD SearXNG \u5728 {endpoint} \u8FD0\u884C\u3002",
3418
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",
3419
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',
3420
3611
  confirmedDetail: "\uFF08{endpoint}\uFF09"
3421
3612
  },
@@ -3551,17 +3742,27 @@ var zhCN = {
3551
3742
  linesBelow: " \u2193 \u4E0B\u65B9 {count} \u884C\uFF08\u2193/j \u6216 Space/PgDn\uFF09",
3552
3743
  linesBelowPlural: " \u2193 \u4E0B\u65B9 {count} \u884C\uFF08\u2193/j \u6216 Space/PgDn\uFF09"
3553
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
+ },
3554
3752
  sessionPicker: {
3555
3753
  header: " \u25C8 REASONIX \xB7 \u9009\u62E9\u4F1A\u8BDD ",
3556
3754
  title: "\u9009\u62E9\u4F1A\u8BDD \u2014 {workspace}",
3557
3755
  messages: "{count} \u6761\u6D88\u606F",
3558
3756
  messagesPlural: "{count} \u6761\u6D88\u606F",
3559
3757
  turns: "{count} \u8F6E",
3560
- pickerHint: "\u2191\u2193 \u9009\u62E9 \xB7 \u23CE \u6253\u5F00 \xB7 [n] \u65B0\u5EFA \xB7 [d] \u5220\u9664 \xB7 [r] \u91CD\u547D\u540D \xB7 Esc \u9000\u51FA",
3758
+ pickerHint: "\u2191\u2193 \u9009\u62E9 \xB7 / \u641C\u7D22 \xB7 \u23CE \u6253\u5F00 \xB7 [n] \u65B0\u5EFA \xB7 [d] \u5220\u9664 \xB7 [r] \u91CD\u547D\u540D \xB7 Esc \u9000\u51FA",
3561
3759
  empty: " \u6B64\u5DE5\u4F5C\u533A\u6682\u65E0\u5DF2\u4FDD\u5B58\u7684\u4F1A\u8BDD \u2014 \u6309 ",
3562
3760
  emptyNew: " \u5F00\u59CB\u65B0\u4F1A\u8BDD",
3563
3761
  renamePrompt: ' \u91CD\u547D\u540D "{from}" \u2192 ',
3564
3762
  renameHint: " \u23CE \u786E\u8BA4\u91CD\u547D\u540D \xB7 Esc \u53D6\u6D88",
3763
+ searchPrompt: " \u641C\u7D22\u4F1A\u8BDD\uFF1A/",
3764
+ searchHint: " \u8F93\u5165\u8FC7\u6EE4 \xB7 \u23CE \u6253\u5F00\u5339\u914D\u9879 \xB7 Esc \u6E05\u9664",
3765
+ searchEmpty: " \u6CA1\u6709\u5339\u914D\u7684\u4F1A\u8BDD",
3565
3766
  emptyHint: " \u23CE \u65B0\u5EFA\u4F1A\u8BDD \xB7 Esc \u9000\u51FA",
3566
3767
  justNow: "\u521A\u521A",
3567
3768
  minAgo: "{count} \u5206\u949F\u524D",
@@ -3569,6 +3770,18 @@ var zhCN = {
3569
3770
  hoursAgo: "{count} \u5C0F\u65F6\u524D",
3570
3771
  daysAgo: "{count} \u5929\u524D"
3571
3772
  },
3773
+ workspacePicker: {
3774
+ header: " \u25C8 REASONIX \xB7 \u9009\u62E9\u5DE5\u4F5C\u533A ",
3775
+ title: "\u9009\u62E9\u5DE5\u4F5C\u533A \u2014 {workspace}",
3776
+ sessions: "{count} \u4E2A\u4F1A\u8BDD",
3777
+ sessionsPlural: "{count} \u4E2A\u4F1A\u8BDD",
3778
+ current: "\u5F53\u524D",
3779
+ pickerHint: "\u2191\u2193 \u9009\u62E9 \xB7 / \u641C\u7D22 \xB7 \u23CE \u5207\u6362\u5E76\u9009\u62E9\u4F1A\u8BDD \xB7 Esc \u9000\u51FA \xB7 /cwd <path> \u6DFB\u52A0",
3780
+ empty: " \u6682\u65E0\u5DF2\u77E5\u5DE5\u4F5C\u533A \u2014 \u5148\u8FD0\u884C\u4E00\u6B21 /cwd <path> \u6DFB\u52A0",
3781
+ searchPrompt: " \u641C\u7D22\u5DE5\u4F5C\u533A\uFF1A/",
3782
+ searchHint: " \u8F93\u5165\u8FC7\u6EE4 \xB7 \u23CE \u5207\u6362\u5E76\u9009\u62E9\u4F1A\u8BDD \xB7 Esc \u6E05\u9664",
3783
+ searchEmpty: " \u6CA1\u6709\u5339\u914D\u7684\u5DE5\u4F5C\u533A"
3784
+ },
3572
3785
  modelPicker: {
3573
3786
  header: " \u25C8 REASONIX \xB7 \u9009\u62E9\u914D\u7F6E ",
3574
3787
  loading: " \xB7 \u52A0\u8F7D\u76EE\u5F55\u2026",
@@ -3669,6 +3882,11 @@ var zhCN = {
3669
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",
3670
3883
  metasoParseError: "web_search: Metaso \u8FD4\u56DE\u65E0\u6CD5\u89E3\u6790\u7684\u54CD\u5E94\uFF08HTTP {status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5",
3671
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",
3672
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",
3673
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",
3674
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",
@@ -3787,7 +4005,8 @@ var zhCN = {
3787
4005
  scrollAbove: " \u2191 {scroll}/{max} \u884C",
3788
4006
  scrollAbovePlural: " \u2191 {scroll}/{max} \u884C",
3789
4007
  scrollMore: " \u2014 \u8FD8\u6709 {remaining} \u884C",
3790
- scrollPgUp: " \xB7 PgUp/\u6EDA\u8F6E/\u2191"
4008
+ scrollPgUp: " \xB7 PgUp/\u6EDA\u8F6E",
4009
+ scrollCopy: " \xB7 /copy \u8FDB\u5165\u590D\u5236\u6A21\u5F0F"
3791
4010
  },
3792
4011
  slashArgPicker: {
3793
4012
  noMatch: '\u6CA1\u6709\u5339\u914D "{partial}"',
@@ -3845,7 +4064,8 @@ var zhCN = {
3845
4064
  reconnectDetail: "\u65AD\u5F00\u65E7\u8FDE\u63A5 \xB7 \u91CD\u65B0\u63E1\u624B \xB7 \u5217\u51FA\u5DE5\u5177",
3846
4065
  disabledDetail: "\u901A\u8FC7 /mcp disable {name}",
3847
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",
3848
- 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"
3849
4069
  },
3850
4070
  checkpointPicker: {
3851
4071
  title: "\u6062\u590D\u68C0\u67E5\u70B9 \u2014 {workspace}",
@@ -4692,6 +4912,12 @@ var ToolRegistry = class {
4692
4912
  });
4693
4913
  }
4694
4914
  }
4915
+ if (opts.signal?.aborted) {
4916
+ return JSON.stringify({
4917
+ error: `${name}: aborted before dispatch (user interrupt)`,
4918
+ rejectedReason: "aborted"
4919
+ });
4920
+ }
4695
4921
  let finalResult;
4696
4922
  try {
4697
4923
  try {
@@ -4924,11 +5150,42 @@ async function bridgeMcpTools(client, opts = {}) {
4924
5150
  return { ...result, env };
4925
5151
  }
4926
5152
  function flattenMcpResult(result, opts = {}) {
5153
+ validateResultShape(result);
4927
5154
  const parts = result.content.map(blockToString);
4928
5155
  const joined = parts.join("\n").trim();
4929
5156
  const prefixed = result.isError ? `ERROR: ${joined || "(no error message from server)"}` : joined;
4930
5157
  return opts.maxChars ? truncateForModel(prefixed, opts.maxChars) : prefixed;
4931
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
+ }
4932
5189
  function truncateForModel(s, maxChars) {
4933
5190
  if (s.length <= maxChars) return s;
4934
5191
  const tailBudget = Math.min(1024, Math.floor(maxChars * 0.1));
@@ -5111,31 +5368,38 @@ function appendSessionMessage(name, message) {
5111
5368
  } catch {
5112
5369
  }
5113
5370
  }
5114
- function listSessions() {
5371
+ function listSessions(opts) {
5115
5372
  const dir = sessionsDir();
5116
5373
  if (!existsSync3(dir)) return [];
5374
+ const want = opts?.workspaceFilter ? normalizeWorkspace(opts.workspaceFilter) : null;
5117
5375
  try {
5118
5376
  const files = readdirSync(dir).filter(
5119
5377
  (f) => f.endsWith(".jsonl") && !f.endsWith(".events.jsonl")
5120
5378
  );
5121
- return files.map((file) => {
5379
+ return files.flatMap((file) => {
5122
5380
  const path2 = join4(dir, file);
5123
- const stat2 = statSync(path2);
5124
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);
5125
5388
  const messageCount = countLines(path2);
5126
- return {
5127
- name,
5128
- path: path2,
5129
- size: stat2.size,
5130
- messageCount,
5131
- mtime: stat2.mtime,
5132
- meta: loadSessionMeta(name)
5133
- };
5389
+ return [{ name, path: path2, size: stat2.size, messageCount, mtime: stat2.mtime, meta }];
5134
5390
  }).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
5135
5391
  } catch {
5136
5392
  return [];
5137
5393
  }
5138
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
+ }
5139
5403
  function metaPath(name) {
5140
5404
  return join4(sessionsDir(), `${sanitizeName(name)}.meta.json`);
5141
5405
  }
@@ -5225,8 +5489,13 @@ function archiveSession(name) {
5225
5489
  }
5226
5490
  function countLines(path2) {
5227
5491
  try {
5228
- const raw = readFileSync4(path2, "utf8");
5229
- 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;
5230
5499
  } catch {
5231
5500
  return 0;
5232
5501
  }
@@ -5249,6 +5518,16 @@ var DEEPSEEK_PRICING = {
5249
5518
  "deepseek-chat": { inputCacheHit: 28e-4, inputCacheMiss: 0.14, output: 0.28 },
5250
5519
  "deepseek-reasoner": { inputCacheHit: 28e-4, inputCacheMiss: 0.14, output: 0.28 }
5251
5520
  };
5521
+ function pricingFor(model, path2) {
5522
+ const defaults = DEEPSEEK_PRICING[model];
5523
+ const override = loadPricingOverride(path2)[model];
5524
+ if (!override) return defaults;
5525
+ const pricing = { ...defaults, ...override };
5526
+ if (pricing.inputCacheHit === void 0 || pricing.inputCacheMiss === void 0 || pricing.output === void 0) {
5527
+ return void 0;
5528
+ }
5529
+ return pricing;
5530
+ }
5252
5531
  var CLAUDE_SONNET_PRICING = { input: 3, output: 15 };
5253
5532
  var DEEPSEEK_CONTEXT_TOKENS = {
5254
5533
  "deepseek-v4-flash": 1e6,
@@ -5257,24 +5536,24 @@ var DEEPSEEK_CONTEXT_TOKENS = {
5257
5536
  "deepseek-reasoner": 1e6
5258
5537
  };
5259
5538
  var DEFAULT_CONTEXT_TOKENS = 131072;
5260
- function costUsd(model, usage) {
5261
- const p = DEEPSEEK_PRICING[model];
5539
+ function costUsd(model, usage, path2) {
5540
+ const p = pricingFor(model, path2);
5262
5541
  if (!p) return 0;
5263
5542
  return (usage.promptCacheHitTokens * p.inputCacheHit + usage.promptCacheMissTokens * p.inputCacheMiss + usage.completionTokens * p.output) / 1e6;
5264
5543
  }
5265
- function inputCostUsd(model, usage) {
5266
- const p = DEEPSEEK_PRICING[model];
5544
+ function inputCostUsd(model, usage, path2) {
5545
+ const p = pricingFor(model, path2);
5267
5546
  if (!p) return 0;
5268
5547
  return (usage.promptCacheHitTokens * p.inputCacheHit + usage.promptCacheMissTokens * p.inputCacheMiss) / 1e6;
5269
5548
  }
5270
- function outputCostUsd(model, usage) {
5271
- const p = DEEPSEEK_PRICING[model];
5549
+ function outputCostUsd(model, usage, path2) {
5550
+ const p = pricingFor(model, path2);
5272
5551
  if (!p) return 0;
5273
5552
  return usage.completionTokens * p.output / 1e6;
5274
5553
  }
5275
- function cacheSavingsUsd(model, hitTokens) {
5554
+ function cacheSavingsUsd(model, hitTokens, path2) {
5276
5555
  if (hitTokens <= 0) return 0;
5277
- const p = DEEPSEEK_PRICING[model];
5556
+ const p = pricingFor(model, path2);
5278
5557
  if (!p) return 0;
5279
5558
  return hitTokens * (p.inputCacheMiss - p.inputCacheHit) / 1e6;
5280
5559
  }
@@ -5932,42 +6211,6 @@ function* hookWarnings(outcomes, turn) {
5932
6211
  }
5933
6212
  }
5934
6213
 
5935
- // src/loop/turn-failure-tracker.ts
5936
- var FAILURE_ESCALATION_THRESHOLD = 3;
5937
- var TurnFailureTracker = class {
5938
- count = 0;
5939
- types = {};
5940
- threshold;
5941
- constructor(threshold = FAILURE_ESCALATION_THRESHOLD) {
5942
- this.threshold = threshold;
5943
- }
5944
- reset() {
5945
- this.count = 0;
5946
- this.types = {};
5947
- }
5948
- /** True ONLY on the call where the count crosses the configured threshold. */
5949
- noteAndCrossedThreshold(resultJson, repair) {
5950
- const before = this.count;
5951
- const bump = (kind, by = 1) => {
5952
- this.count += by;
5953
- this.types[kind] = (this.types[kind] ?? 0) + by;
5954
- };
5955
- if (resultJson.includes('"error"') && resultJson.includes("search text not found")) {
5956
- bump("search-mismatch");
5957
- }
5958
- if (repair) {
5959
- if (repair.scavenged > 0) bump("scavenged", repair.scavenged);
5960
- if (repair.truncationsFixed > 0) bump("truncated", repair.truncationsFixed);
5961
- if (repair.stormsBroken > 0) bump("repeat-loop", repair.stormsBroken);
5962
- }
5963
- return before < this.threshold && this.count >= this.threshold;
5964
- }
5965
- formatBreakdown() {
5966
- const parts = Object.entries(this.types).filter(([, n]) => n > 0).map(([kind, n]) => `${n}\xD7 ${kind}`);
5967
- return parts.length > 0 ? parts.join(", ") : `${this.count} repair/error signal(s)`;
5968
- }
5969
- };
5970
-
5971
6214
  // src/memory/runtime.ts
5972
6215
  import { createHash } from "crypto";
5973
6216
  var ImmutablePrefix = class {
@@ -6455,7 +6698,6 @@ var CacheFirstLoop = class {
6455
6698
  }
6456
6699
  _proArmedForNextTurn = false;
6457
6700
  _escalateThisTurn = false;
6458
- _turnFailures;
6459
6701
  _turnSelfCorrected = false;
6460
6702
  _foldedThisTurn = false;
6461
6703
  context;
@@ -6474,9 +6716,6 @@ var CacheFirstLoop = class {
6474
6716
  this.reasoningEffort = opts.reasoningEffort ?? "max";
6475
6717
  if (opts.autoEscalate !== void 0) this.autoEscalate = opts.autoEscalate;
6476
6718
  this.budgetUsd = typeof opts.budgetUsd === "number" && opts.budgetUsd > 0 ? opts.budgetUsd : null;
6477
- this._turnFailures = new TurnFailureTracker(
6478
- resolveFailureThreshold(opts.failureThreshold, FAILURE_ESCALATION_THRESHOLD)
6479
- );
6480
6719
  this.hooks = opts.hooks ?? [];
6481
6720
  this.hookCwd = opts.hookCwd ?? process.cwd();
6482
6721
  this.confirmationGate = opts.confirmationGate ?? pauseGate;
@@ -6584,7 +6823,6 @@ var CacheFirstLoop = class {
6584
6823
  this._inflight.clear();
6585
6824
  this.stats.reset();
6586
6825
  this._turn = 0;
6587
- this._turnFailures.reset();
6588
6826
  this._budgetWarned = false;
6589
6827
  let systemRebuilt = false;
6590
6828
  if (this._rebuildSystem) {
@@ -6655,13 +6893,6 @@ var CacheFirstLoop = class {
6655
6893
  modelForCurrentCall() {
6656
6894
  return this._escalateThisTurn ? ESCALATION_MODEL : this.model;
6657
6895
  }
6658
- /** Returns true ONLY on the tipping call — caller surfaces a one-shot warning. */
6659
- noteToolFailureSignal(resultJson, repair) {
6660
- if (!this._turnFailures.noteAndCrossedThreshold(resultJson, repair)) return false;
6661
- if (this._escalateThisTurn || !this.autoEscalate) return false;
6662
- this._escalateThisTurn = true;
6663
- return true;
6664
- }
6665
6896
  /** A call counts as mutating when its definition reports `readOnly !== true` and any dynamic `readOnlyCheck` doesn't override that for these args. */
6666
6897
  isMutating(call) {
6667
6898
  const name = call.function?.name;
@@ -6785,6 +7016,32 @@ ${reason}`
6785
7016
  }
6786
7017
  return userText;
6787
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
+ }
6788
7045
  async *step(userInput) {
6789
7046
  this._steerConsumed = false;
6790
7047
  if (this.budgetUsd !== null) {
@@ -6816,7 +7073,6 @@ ${reason}`
6816
7073
  this._turn++;
6817
7074
  this.scratch.reset();
6818
7075
  this.repair.resetStorm();
6819
- this._turnFailures.reset();
6820
7076
  this._turnSelfCorrected = false;
6821
7077
  this._escalateThisTurn = false;
6822
7078
  this._foldedThisTurn = false;
@@ -7087,17 +7343,6 @@ ${reason}`
7087
7343
  stats: turnStats,
7088
7344
  repair: report
7089
7345
  };
7090
- if (this.noteToolFailureSignal("", report)) {
7091
- yield {
7092
- turn: this._turn,
7093
- role: "warning",
7094
- content: t("loop.autoEscalation", {
7095
- model: ESCALATION_MODEL,
7096
- breakdown: this._turnFailures.formatBreakdown(),
7097
- fallback: this.model
7098
- })
7099
- };
7100
- }
7101
7346
  const allSuppressed = report.stormsBroken > 0 && repairedCalls.length === 0 && toolCalls.length > 0;
7102
7347
  if (allSuppressed && !this._turnSelfCorrected) {
7103
7348
  this._turnSelfCorrected = true;
@@ -7237,17 +7482,6 @@ ${reason}`
7237
7482
  name,
7238
7483
  content: result
7239
7484
  });
7240
- if (this.noteToolFailureSignal(result)) {
7241
- yield {
7242
- turn: this._turn,
7243
- role: "warning",
7244
- content: t("loop.autoEscalation", {
7245
- model: ESCALATION_MODEL,
7246
- breakdown: this._turnFailures.formatBreakdown(),
7247
- fallback: this.model
7248
- })
7249
- };
7250
- }
7251
7485
  yield {
7252
7486
  turn: this._turn,
7253
7487
  role: "tool",
@@ -7285,19 +7519,6 @@ function parsePositiveIntEnv(raw) {
7285
7519
  const n = Number.parseInt(raw, 10);
7286
7520
  return Number.isFinite(n) && n > 0 ? n : void 0;
7287
7521
  }
7288
- var FAILURE_THRESHOLD_MIN = 1;
7289
- var FAILURE_THRESHOLD_MAX = 20;
7290
- function resolveFailureThreshold(raw, fallback) {
7291
- if (raw === void 0) return fallback;
7292
- if (!Number.isInteger(raw) || raw < FAILURE_THRESHOLD_MIN || raw > FAILURE_THRESHOLD_MAX) {
7293
- process.stderr.write(
7294
- `\u25B2 ignoring escalation failureThreshold=${raw} (must be an integer in [${FAILURE_THRESHOLD_MIN},${FAILURE_THRESHOLD_MAX}]) \u2014 using default ${fallback}
7295
- `
7296
- );
7297
- return fallback;
7298
- }
7299
- return raw;
7300
- }
7301
7522
 
7302
7523
  // src/at-mentions.ts
7303
7524
  import { existsSync as existsSync4, readFileSync as readFileSync6, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
@@ -7988,10 +8209,15 @@ var SkillStore = class {
7988
8209
  dir: join7(this.projectRoot, ".agents", SKILLS_DIRNAME),
7989
8210
  scope: "project"
7990
8211
  });
8212
+ out.push({
8213
+ dir: join7(this.projectRoot, ".claude", SKILLS_DIRNAME),
8214
+ scope: "project"
8215
+ });
7991
8216
  }
7992
8217
  for (const dir of this.customSkillPaths) out.push({ dir, scope: "custom" });
7993
8218
  out.push({ dir: join7(this.homeDir, ".reasonix", SKILLS_DIRNAME), scope: "global" });
7994
8219
  out.push({ dir: join7(this.homeDir, ".agents", SKILLS_DIRNAME), scope: "global" });
8220
+ out.push({ dir: join7(this.homeDir, ".claude", SKILLS_DIRNAME), scope: "global" });
7995
8221
  return out.map((root, priority) => ({ ...root, priority, status: skillPathStatus(root.dir) }));
7996
8222
  }
7997
8223
  customRoots() {
@@ -8094,14 +8320,20 @@ var SkillStore = class {
8094
8320
  }
8095
8321
  const { data, body } = parseFrontmatter(raw);
8096
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
+ }
8097
8329
  return {
8098
8330
  name,
8099
- description: (data.description ?? "").trim(),
8331
+ description,
8100
8332
  body: body.trim(),
8101
8333
  scope,
8102
8334
  path: path2,
8103
8335
  allowedTools: parseAllowedTools(data["allowed-tools"]),
8104
- runAs: parseRunAs(data.runAs),
8336
+ runAs: parseRunAs(data.runAs, data.context, data.agent),
8105
8337
  model: data.model?.startsWith("deepseek-") ? data.model : void 0
8106
8338
  };
8107
8339
  }
@@ -8134,8 +8366,11 @@ function skillPathStatus(dir) {
8134
8366
  return "unreadable";
8135
8367
  }
8136
8368
  }
8137
- function parseRunAs(raw) {
8138
- 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";
8139
8374
  }
8140
8375
  function skillStubBody(name) {
8141
8376
  return `---
@@ -8697,13 +8932,13 @@ import picomatch3 from "picomatch";
8697
8932
  // src/memory/subdir.ts
8698
8933
  import { existsSync as existsSync8, readFileSync as readFileSync10 } from "fs";
8699
8934
  import { dirname as dirname5, join as join9, relative as relative2, resolve as resolve5 } from "path";
8700
- function findSubdirMemoryAncestors(absPath, rootDir) {
8935
+ function findDirMemory(absDir, rootDir) {
8701
8936
  const root = resolve5(rootDir);
8702
- const target = resolve5(absPath);
8937
+ const target = resolve5(absDir);
8703
8938
  const rel = relative2(root, target);
8704
- if (!rel || rel.startsWith("..")) return [];
8939
+ if (rel.startsWith("..")) return [];
8705
8940
  const found = [];
8706
- let cur = dirname5(target);
8941
+ let cur = target;
8707
8942
  while (cur !== root) {
8708
8943
  const r = relative2(root, cur);
8709
8944
  if (!r || r.startsWith("..")) break;
@@ -8720,6 +8955,9 @@ function findSubdirMemoryAncestors(absPath, rootDir) {
8720
8955
  }
8721
8956
  return found;
8722
8957
  }
8958
+ function findSubdirMemoryAncestors(absPath, rootDir) {
8959
+ return findDirMemory(dirname5(resolve5(absPath)), rootDir);
8960
+ }
8723
8961
  function readSubdirMemoryContent(path2) {
8724
8962
  let raw;
8725
8963
  try {
@@ -9144,6 +9382,129 @@ function formatOutline(entries) {
9144
9382
  // src/tools/fs/search.ts
9145
9383
  import { promises as fs3 } from "fs";
9146
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
9147
9508
  function throwIfAborted(signal) {
9148
9509
  if (!signal?.aborted) return;
9149
9510
  throw new DOMException("search aborted by user", "AbortError");
@@ -9196,17 +9557,20 @@ async function searchFiles(ctx, startAbs, args) {
9196
9557
  }
9197
9558
  var MAX_HITS_PER_FILE = 30;
9198
9559
  var SUMMARY_MODE_TRIGGER_RATIO = 0.8;
9560
+ var WALK_DEADLINE_MS = 12e4;
9199
9561
  async function searchContent(ctx, startAbs, args) {
9200
9562
  throwIfAborted(args.signal);
9201
9563
  const caseSensitive = args.case_sensitive === true;
9202
9564
  const includeDeps = args.include_deps === true;
9203
9565
  const ctxLines = Math.max(0, Math.min(20, Math.floor(args.context ?? 0)));
9204
9566
  const summaryOnly = args.summary_only === true;
9205
- let re = null;
9567
+ const reFlags = caseSensitive ? "" : "i";
9568
+ let reSource = null;
9206
9569
  try {
9207
- re = new RegExp(args.pattern, caseSensitive ? "" : "i");
9570
+ new RegExp(args.pattern, reFlags);
9571
+ reSource = args.pattern;
9208
9572
  } catch {
9209
- re = null;
9573
+ reSource = null;
9210
9574
  }
9211
9575
  const needle = caseSensitive ? args.pattern : args.pattern.toLowerCase();
9212
9576
  const matches = [];
@@ -9216,6 +9580,15 @@ async function searchContent(ctx, startAbs, args) {
9216
9580
  let summaryMode = summaryOnly;
9217
9581
  let summaryNoticeEmitted = false;
9218
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
+ };
9219
9592
  const pushLine = (out) => {
9220
9593
  if (totalBytes + out.length + 1 > ctx.maxListBytes) {
9221
9594
  matches.push(`[\u2026 truncated at ${ctx.maxListBytes} bytes \u2014 refine pattern or path \u2026]`);
@@ -9250,6 +9623,7 @@ async function searchContent(ctx, startAbs, args) {
9250
9623
  for (const e of entries) {
9251
9624
  if (truncated) return;
9252
9625
  throwIfAborted(args.signal);
9626
+ throwIfTimedOut();
9253
9627
  if (e.isDirectory()) {
9254
9628
  if (!includeDeps && ctx.skipDirNames.has(e.name)) continue;
9255
9629
  await walk2(pathMod4.join(dir, e.name));
@@ -9286,13 +9660,25 @@ async function searchContent(ctx, startAbs, args) {
9286
9660
  const text = raw.toString("utf8");
9287
9661
  const rel = displayRel3(ctx.rootDir, full);
9288
9662
  const lines = text.split(/\r?\n/);
9289
- const hits = [];
9290
- for (let li = 0; li < lines.length; li++) {
9291
- throwIfAborted(args.signal);
9292
- const line = lines[li];
9293
- const lineForCheck = caseSensitive ? line : line.toLowerCase();
9294
- const hit = re ? re.test(line) : lineForCheck.includes(needle);
9295
- 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
+ }
9296
9682
  }
9297
9683
  scanned++;
9298
9684
  if (hits.length === 0) continue;
@@ -9341,6 +9727,11 @@ async function searchContent(ctx, startAbs, args) {
9341
9727
  }
9342
9728
  };
9343
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
+ }
9344
9735
  if (matches.length === 0) {
9345
9736
  return scanned === 0 ? "(no files scanned \u2014 path empty or all files filtered out)" : `(no matches across ${scanned} file${scanned === 1 ? "" : "s"})`;
9346
9737
  }
@@ -9405,11 +9796,15 @@ function registerFilesystemTools(registry, opts) {
9405
9796
  const sessionApproved = /* @__PURE__ */ new Set();
9406
9797
  const shownSubdirMemory = /* @__PURE__ */ new Set();
9407
9798
  function withSubdirMemory(absPath, body) {
9408
- if (!memoryEnabled()) return body;
9409
- const ancestors = findSubdirMemoryAncestors(absPath, rootDir);
9410
- 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;
9411
9806
  const sections = [];
9412
- for (const memPath of [...ancestors].reverse()) {
9807
+ for (const memPath of [...memPaths].reverse()) {
9413
9808
  if (shownSubdirMemory.has(memPath)) continue;
9414
9809
  const content = readSubdirMemoryContent(memPath);
9415
9810
  if (!content) continue;
@@ -9602,7 +9997,7 @@ ${slice.join("\n")}`);
9602
9997
  for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
9603
9998
  lines.push(e.isDirectory() ? `${e.name}/` : e.name);
9604
9999
  }
9605
- return lines.join("\n") || "(empty directory)";
10000
+ return withDirMemory(abs, lines.join("\n") || "(empty directory)");
9606
10001
  }
9607
10002
  });
9608
10003
  registry.register({
@@ -12730,6 +13125,7 @@ var FETCH_MAX_BYTES = 10 * 1024 * 1024;
12730
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";
12731
13126
  var MOJEEK_ENDPOINT = "https://www.mojeek.com/search";
12732
13127
  var METASO_ENDPOINT = "https://metaso.cn/api/v1";
13128
+ var TAVILY_ENDPOINT = "https://api.tavily.com/search";
12733
13129
  function searchStatusError(status) {
12734
13130
  if (status === 429) return t("webErrors.rateLimit429");
12735
13131
  if (status === 403) return t("webErrors.forbidden403");
@@ -12749,6 +13145,9 @@ async function webSearch(query, opts = {}) {
12749
13145
  if (opts.engine === "searxng") {
12750
13146
  return searchSearxng(query, opts);
12751
13147
  }
13148
+ if (opts.engine === "tavily") {
13149
+ return searchTavily(query, opts);
13150
+ }
12752
13151
  return searchMojeek(query, opts);
12753
13152
  }
12754
13153
  async function searchMojeek(query, opts = {}) {
@@ -12883,6 +13282,55 @@ async function searchMetaso(query, opts = {}) {
12883
13282
  snippet: wp.snippet ?? wp.summary ?? ""
12884
13283
  }));
12885
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
+ }
12886
13334
  function parseSearxngHtmlResults(html) {
12887
13335
  const root = parseHtml(html);
12888
13336
  const results = [];
@@ -13101,7 +13549,7 @@ function registerWebTools(registry, opts = {}) {
13101
13549
  const maxFetchChars = opts.maxFetchChars ?? DEFAULT_FETCH_MAX_CHARS;
13102
13550
  registry.register({
13103
13551
  name: "web_search",
13104
- 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.",
13105
13553
  readOnly: true,
13106
13554
  parallelSafe: true,
13107
13555
  parameters: {
@@ -13787,19 +14235,23 @@ var McpClient = class {
13787
14235
  return this._instructions;
13788
14236
  }
13789
14237
  /** Compliant servers reject other methods until this completes. */
13790
- async initialize() {
14238
+ async initialize(opts = {}) {
13791
14239
  if (this.initialized) throw new Error("MCP client already initialized");
13792
14240
  this.startReaderIfNeeded();
13793
- const result = await this.request("initialize", {
13794
- protocolVersion: MCP_PROTOCOL_VERSION,
13795
- // Advertise every method the client can consume so servers know
13796
- // they can send listChanged notifications etc. Sub-feature flags
13797
- // (e.g. `resources.subscribe`) are omitted we don't implement
13798
- // those yet and the empty object means "method-level support, no
13799
- // sub-features."
13800
- capabilities: { tools: {}, resources: {}, prompts: {} },
13801
- clientInfo: this.clientInfo
13802
- });
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
+ );
13803
14255
  this._serverCapabilities = result.capabilities ?? {};
13804
14256
  this._serverInfo = result.serverInfo ?? { name: "", version: "" };
13805
14257
  this._protocolVersion = result.protocolVersion ?? "";
@@ -13992,18 +14444,20 @@ var StdioTransport = class {
13992
14444
  this.child = spawn5(line, [], {
13993
14445
  env,
13994
14446
  cwd: opts.cwd,
13995
- stdio: ["pipe", "pipe", "inherit"],
14447
+ stdio: ["pipe", "pipe", "pipe"],
13996
14448
  shell: true
13997
14449
  });
13998
14450
  } else {
13999
14451
  this.child = spawn5(opts.command, opts.args ?? [], {
14000
14452
  env,
14001
14453
  cwd: opts.cwd,
14002
- stdio: ["pipe", "pipe", "inherit"]
14454
+ stdio: ["pipe", "pipe", "pipe"]
14003
14455
  });
14004
14456
  }
14005
14457
  this.child.stdout.setEncoding("utf8");
14006
14458
  this.child.stdout.on("data", (chunk) => this.onStdout(chunk));
14459
+ this.child.stderr.setEncoding("utf8");
14460
+ this.child.stderr.on("data", (chunk) => this.onStderr(chunk));
14007
14461
  this.child.on("close", () => this.onClose());
14008
14462
  this.child.on("error", (err) => {
14009
14463
  this.push({
@@ -14072,6 +14526,13 @@ var StdioTransport = class {
14072
14526
  }
14073
14527
  }
14074
14528
  }
14529
+ // Python MCP SDK writes info logs (`server.py:534 ListPromptsRequest`)
14530
+ // to stderr — letting those through would corrupt the TUI render.
14531
+ onStderr(chunk) {
14532
+ if (process.env.REASONIX_DEBUG_MCP === "1") {
14533
+ process.stderr.write(chunk);
14534
+ }
14535
+ }
14075
14536
  onClose() {
14076
14537
  this.closed = true;
14077
14538
  while (this.waiters.length > 0) this.waiters.shift()(null);