reasonix 0.50.0 → 0.51.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 (147) hide show
  1. package/dashboard/dist/app.css +1 -1
  2. package/dashboard/dist/app.js +24 -22
  3. package/dashboard/dist/app.js.map +1 -1
  4. package/dist/cli/{acp-6B25WIFF.js → acp-XEUHGG7X.js} +34 -31
  5. package/dist/cli/acp-XEUHGG7X.js.map +1 -0
  6. package/dist/cli/chat-NJ2Q5KHG.js +50 -0
  7. package/dist/cli/{chunk-OPGWCKKU.js → chunk-2HVTBFCI.js} +3 -3
  8. package/dist/cli/{chunk-AJIZ5KFK.js → chunk-2WUEAI2I.js} +3 -3
  9. package/dist/cli/{chunk-I4Q3QT4W.js → chunk-36BM7INR.js} +2 -2
  10. package/dist/cli/{chunk-3RNFYDDM.js → chunk-3BTK5BHI.js} +11 -7
  11. package/dist/cli/chunk-3BTK5BHI.js.map +1 -0
  12. package/dist/cli/{chunk-GMSAB2TC.js → chunk-3YRTIWFX.js} +2 -2
  13. package/dist/cli/{chunk-NLRC3DWQ.js → chunk-544J4PXD.js} +5 -5
  14. package/dist/cli/{chunk-7WITYWKN.js → chunk-5AIDYVH2.js} +2 -2
  15. package/dist/cli/{chunk-ALCOQP6R.js → chunk-5BBC6YMV.js} +5 -5
  16. package/dist/cli/{chunk-S4XVGLRW.js → chunk-6UNHNVJR.js} +72 -5
  17. package/dist/cli/chunk-6UNHNVJR.js.map +1 -0
  18. package/dist/cli/{chunk-IK6WWRIX.js → chunk-6XWXIVQ3.js} +38 -22
  19. package/dist/cli/chunk-6XWXIVQ3.js.map +1 -0
  20. package/dist/cli/{chunk-AAHB2PFX.js → chunk-7YB26OQO.js} +4 -4
  21. package/dist/cli/chunk-7YB26OQO.js.map +1 -0
  22. package/dist/cli/{chunk-MXWPAPZW.js → chunk-A5PBEIJ7.js} +53 -10
  23. package/dist/cli/chunk-A5PBEIJ7.js.map +1 -0
  24. package/dist/cli/{chunk-FQSQFCBI.js → chunk-BA5R6BAE.js} +2 -2
  25. package/dist/cli/{chunk-XWPZHWC2.js → chunk-BM6BBFAV.js} +2 -2
  26. package/dist/cli/{chunk-CAGKEGNE.js → chunk-BOWSNGQC.js} +52 -140
  27. package/dist/cli/chunk-BOWSNGQC.js.map +1 -0
  28. package/dist/cli/{chunk-EZ57UEZQ.js → chunk-C2MRSJTV.js} +2 -2
  29. package/dist/cli/{chunk-PYIZZAVQ.js → chunk-DVD67FXQ.js} +1716 -4
  30. package/dist/cli/chunk-DVD67FXQ.js.map +1 -0
  31. package/dist/cli/{chunk-ZAXMJANP.js → chunk-EAMXOWUW.js} +3 -3
  32. package/dist/cli/{chunk-TX652NBA.js → chunk-EWVFGYT6.js} +2 -2
  33. package/dist/cli/{chunk-IBRTU5WO.js → chunk-FP7IOWBQ.js} +18 -1182
  34. package/dist/cli/chunk-FP7IOWBQ.js.map +1 -0
  35. package/dist/cli/{chunk-I6FBSTTR.js → chunk-HGK57NBN.js} +9 -353
  36. package/dist/cli/chunk-HGK57NBN.js.map +1 -0
  37. package/dist/cli/chunk-JHWQDJZA.js +80 -0
  38. package/dist/cli/chunk-JHWQDJZA.js.map +1 -0
  39. package/dist/cli/{chunk-X2BQZQEE.js → chunk-K3QJ3GKI.js} +3 -3
  40. package/dist/cli/{chunk-GPUH2BNM.js → chunk-K4YQFULP.js} +612 -254
  41. package/dist/cli/chunk-K4YQFULP.js.map +1 -0
  42. package/dist/cli/chunk-L3VPEESB.js +31 -0
  43. package/dist/cli/chunk-L3VPEESB.js.map +1 -0
  44. package/dist/cli/{chunk-ENFBF6HI.js → chunk-N4SEBLU4.js} +383 -5
  45. package/dist/cli/chunk-N4SEBLU4.js.map +1 -0
  46. package/dist/cli/chunk-NRROJXXT.js +879 -0
  47. package/dist/cli/chunk-NRROJXXT.js.map +1 -0
  48. package/dist/cli/{chunk-3KRRTLC5.js → chunk-R6KIHEF3.js} +1619 -1036
  49. package/dist/cli/chunk-R6KIHEF3.js.map +1 -0
  50. package/dist/cli/{chunk-VVMY4M7J.js → chunk-SBHF5NWD.js} +27 -4
  51. package/dist/cli/chunk-SBHF5NWD.js.map +1 -0
  52. package/dist/cli/{chunk-OWA42BKS.js → chunk-SXSAWOB7.js} +14 -14
  53. package/dist/cli/{chunk-6IUMTRFP.js → chunk-UMZ6KHTS.js} +2 -2
  54. package/dist/cli/{chunk-7X4JJOO7.js → chunk-UO6E7FN3.js} +69 -5
  55. package/dist/cli/{chunk-7X4JJOO7.js.map → chunk-UO6E7FN3.js.map} +1 -1
  56. package/dist/cli/{chunk-3ZZXQ3CZ.js → chunk-UPW544V3.js} +2 -2
  57. package/dist/cli/{chunk-XJZWMU5P.js → chunk-WPOKBW5E.js} +2 -2
  58. package/dist/cli/{chunk-WSBFVOCO.js → chunk-Z3MKG7MQ.js} +2 -2
  59. package/dist/cli/{code-TBK2TASK.js → code-BMXLBC7D.js} +37 -36
  60. package/dist/cli/{code-TBK2TASK.js.map → code-BMXLBC7D.js.map} +1 -1
  61. package/dist/cli/{commands-NXTKSQTN.js → commands-E4RZXMF6.js} +5 -5
  62. package/dist/cli/{commit-IR5SPP7A.js → commit-KSRQ64IL.js} +3 -3
  63. package/dist/cli/{config-XK5WQGTS.js → config-QNDONOTU.js} +4 -2
  64. package/dist/cli/{desktop-5NTQBADL.js → desktop-H3ZHIMDA.js} +83 -37
  65. package/dist/cli/desktop-H3ZHIMDA.js.map +1 -0
  66. package/dist/cli/{diff-JNYX5BSZ.js → diff-I4PYI43W.js} +9 -9
  67. package/dist/cli/{doctor-IKYLUFXX.js → doctor-Y2E4MY2F.js} +12 -12
  68. package/dist/cli/{events-HSC57ONU.js → events-47HOT7ZA.js} +5 -5
  69. package/dist/cli/find-in-code-YLEIK5FK.js +145 -0
  70. package/dist/cli/find-in-code-YLEIK5FK.js.map +1 -0
  71. package/dist/cli/index.js +95 -44
  72. package/dist/cli/index.js.map +1 -1
  73. package/dist/cli/{mcp-BDJJWOCD.js → mcp-76DK63ZB.js} +3 -3
  74. package/dist/cli/{mcp-browse-NJRZDI6V.js → mcp-browse-SDNUGO74.js} +3 -3
  75. package/dist/cli/{mcp-inspect-Y62NWZQL.js → mcp-inspect-BL5DEO5M.js} +3 -3
  76. package/dist/cli/{prompt-UTOIFUQC.js → prompt-JLATI3P7.js} +5 -5
  77. package/dist/cli/{prune-sessions-UCUD4XAP.js → prune-sessions-WHZDFUKD.js} +4 -4
  78. package/dist/cli/{replay-VVIN64MN.js → replay-MHXS7C7Z.js} +10 -10
  79. package/dist/cli/{run-76OBDZFB.js → run-SXNCPRJE.js} +22 -22
  80. package/dist/cli/{server-SZZDKTH2.js → server-GEHOE6CO.js} +61 -35
  81. package/dist/cli/server-GEHOE6CO.js.map +1 -0
  82. package/dist/cli/{sessions-FZTGRCM5.js → sessions-EPBFYISL.js} +18 -18
  83. package/dist/cli/{setup-4UNENGOE.js → setup-IW2XR5XI.js} +8 -7
  84. package/dist/cli/setup-IW2XR5XI.js.map +1 -0
  85. package/dist/cli/{stats-F4NDOD7D.js → stats-4WB4XHBP.js} +6 -6
  86. package/dist/cli/symbols-UQ274IOB.js +167 -0
  87. package/dist/cli/symbols-UQ274IOB.js.map +1 -0
  88. package/dist/cli/version-4SP3DLLH.js +33 -0
  89. package/dist/index.d.ts +25 -6
  90. package/dist/index.js +2700 -578
  91. package/dist/index.js.map +1 -1
  92. package/package.json +6 -3
  93. package/scripts/postinstall.mjs +10 -0
  94. package/dist/cli/acp-6B25WIFF.js.map +0 -1
  95. package/dist/cli/chat-7WASPB4O.js +0 -50
  96. package/dist/cli/chunk-3KRRTLC5.js.map +0 -1
  97. package/dist/cli/chunk-3RNFYDDM.js.map +0 -1
  98. package/dist/cli/chunk-AAHB2PFX.js.map +0 -1
  99. package/dist/cli/chunk-CAGKEGNE.js.map +0 -1
  100. package/dist/cli/chunk-ENFBF6HI.js.map +0 -1
  101. package/dist/cli/chunk-GPUH2BNM.js.map +0 -1
  102. package/dist/cli/chunk-I6FBSTTR.js.map +0 -1
  103. package/dist/cli/chunk-IBRTU5WO.js.map +0 -1
  104. package/dist/cli/chunk-IK6WWRIX.js.map +0 -1
  105. package/dist/cli/chunk-MXWPAPZW.js.map +0 -1
  106. package/dist/cli/chunk-PYIZZAVQ.js.map +0 -1
  107. package/dist/cli/chunk-S4XVGLRW.js.map +0 -1
  108. package/dist/cli/chunk-VVMY4M7J.js.map +0 -1
  109. package/dist/cli/desktop-5NTQBADL.js.map +0 -1
  110. package/dist/cli/server-SZZDKTH2.js.map +0 -1
  111. package/dist/cli/setup-4UNENGOE.js.map +0 -1
  112. package/dist/cli/version-LUVTWHLL.js +0 -33
  113. /package/dist/cli/{chat-7WASPB4O.js.map → chat-NJ2Q5KHG.js.map} +0 -0
  114. /package/dist/cli/{chunk-OPGWCKKU.js.map → chunk-2HVTBFCI.js.map} +0 -0
  115. /package/dist/cli/{chunk-AJIZ5KFK.js.map → chunk-2WUEAI2I.js.map} +0 -0
  116. /package/dist/cli/{chunk-I4Q3QT4W.js.map → chunk-36BM7INR.js.map} +0 -0
  117. /package/dist/cli/{chunk-GMSAB2TC.js.map → chunk-3YRTIWFX.js.map} +0 -0
  118. /package/dist/cli/{chunk-NLRC3DWQ.js.map → chunk-544J4PXD.js.map} +0 -0
  119. /package/dist/cli/{chunk-7WITYWKN.js.map → chunk-5AIDYVH2.js.map} +0 -0
  120. /package/dist/cli/{chunk-ALCOQP6R.js.map → chunk-5BBC6YMV.js.map} +0 -0
  121. /package/dist/cli/{chunk-FQSQFCBI.js.map → chunk-BA5R6BAE.js.map} +0 -0
  122. /package/dist/cli/{chunk-XWPZHWC2.js.map → chunk-BM6BBFAV.js.map} +0 -0
  123. /package/dist/cli/{chunk-EZ57UEZQ.js.map → chunk-C2MRSJTV.js.map} +0 -0
  124. /package/dist/cli/{chunk-ZAXMJANP.js.map → chunk-EAMXOWUW.js.map} +0 -0
  125. /package/dist/cli/{chunk-TX652NBA.js.map → chunk-EWVFGYT6.js.map} +0 -0
  126. /package/dist/cli/{chunk-X2BQZQEE.js.map → chunk-K3QJ3GKI.js.map} +0 -0
  127. /package/dist/cli/{chunk-OWA42BKS.js.map → chunk-SXSAWOB7.js.map} +0 -0
  128. /package/dist/cli/{chunk-6IUMTRFP.js.map → chunk-UMZ6KHTS.js.map} +0 -0
  129. /package/dist/cli/{chunk-3ZZXQ3CZ.js.map → chunk-UPW544V3.js.map} +0 -0
  130. /package/dist/cli/{chunk-XJZWMU5P.js.map → chunk-WPOKBW5E.js.map} +0 -0
  131. /package/dist/cli/{chunk-WSBFVOCO.js.map → chunk-Z3MKG7MQ.js.map} +0 -0
  132. /package/dist/cli/{commands-NXTKSQTN.js.map → commands-E4RZXMF6.js.map} +0 -0
  133. /package/dist/cli/{commit-IR5SPP7A.js.map → commit-KSRQ64IL.js.map} +0 -0
  134. /package/dist/cli/{config-XK5WQGTS.js.map → config-QNDONOTU.js.map} +0 -0
  135. /package/dist/cli/{diff-JNYX5BSZ.js.map → diff-I4PYI43W.js.map} +0 -0
  136. /package/dist/cli/{doctor-IKYLUFXX.js.map → doctor-Y2E4MY2F.js.map} +0 -0
  137. /package/dist/cli/{events-HSC57ONU.js.map → events-47HOT7ZA.js.map} +0 -0
  138. /package/dist/cli/{mcp-BDJJWOCD.js.map → mcp-76DK63ZB.js.map} +0 -0
  139. /package/dist/cli/{mcp-browse-NJRZDI6V.js.map → mcp-browse-SDNUGO74.js.map} +0 -0
  140. /package/dist/cli/{mcp-inspect-Y62NWZQL.js.map → mcp-inspect-BL5DEO5M.js.map} +0 -0
  141. /package/dist/cli/{prompt-UTOIFUQC.js.map → prompt-JLATI3P7.js.map} +0 -0
  142. /package/dist/cli/{prune-sessions-UCUD4XAP.js.map → prune-sessions-WHZDFUKD.js.map} +0 -0
  143. /package/dist/cli/{replay-VVIN64MN.js.map → replay-MHXS7C7Z.js.map} +0 -0
  144. /package/dist/cli/{run-76OBDZFB.js.map → run-SXNCPRJE.js.map} +0 -0
  145. /package/dist/cli/{sessions-FZTGRCM5.js.map → sessions-EPBFYISL.js.map} +0 -0
  146. /package/dist/cli/{stats-F4NDOD7D.js.map → stats-4WB4XHBP.js.map} +0 -0
  147. /package/dist/cli/{version-LUVTWHLL.js.map → version-4SP3DLLH.js.map} +0 -0
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { createParser } from "eventsource-parser";
3
3
 
4
4
  // src/config.ts
5
5
  import { randomBytes } from "crypto";
6
- import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
6
+ import { mkdirSync, readFileSync } from "fs";
7
7
  import { homedir } from "os";
8
8
  import { dirname, isAbsolute, join, resolve } from "path";
9
9
  import { z } from "zod";
@@ -231,11 +231,52 @@ var TONE_ACTIVE = proxyTokens((theme) => theme.toneActive);
231
231
  var SURFACE = proxyTokens((theme) => theme.surface);
232
232
  var CARD = proxyTokens((theme) => theme.card);
233
233
 
234
+ // src/core/atomic-write.ts
235
+ import { chmodSync, copyFileSync, renameSync, unlinkSync, writeFileSync } from "fs";
236
+ var defaultFs = {
237
+ writeFileSync,
238
+ chmodSync,
239
+ renameSync,
240
+ copyFileSync,
241
+ unlinkSync
242
+ };
243
+ function atomicWriteSync(path2, body, tmp, mode = 384, fs5 = defaultFs) {
244
+ try {
245
+ fs5.writeFileSync(tmp, body, "utf8");
246
+ try {
247
+ fs5.chmodSync(tmp, mode);
248
+ } catch {
249
+ }
250
+ try {
251
+ fs5.renameSync(tmp, path2);
252
+ } catch (err) {
253
+ if (err.code !== "EXDEV") throw err;
254
+ fs5.copyFileSync(tmp, path2);
255
+ try {
256
+ fs5.chmodSync(path2, mode);
257
+ } catch {
258
+ }
259
+ }
260
+ } catch (err) {
261
+ try {
262
+ fs5.unlinkSync(tmp);
263
+ } catch {
264
+ }
265
+ throw err;
266
+ }
267
+ try {
268
+ fs5.unlinkSync(tmp);
269
+ } catch {
270
+ }
271
+ }
272
+
234
273
  // src/index/config.ts
235
274
  import picomatch from "picomatch";
236
275
  var DEFAULT_INDEX_EXCLUDES = {
237
276
  dirs: [
238
277
  "node_modules",
278
+ ".devenv",
279
+ ".direnv",
239
280
  ".git",
240
281
  ".hg",
241
282
  ".svn",
@@ -621,12 +662,7 @@ function readConfig(path2 = defaultConfigPath()) {
621
662
  function writeConfig(cfg, path2 = defaultConfigPath()) {
622
663
  mkdirSync(dirname(path2), { recursive: true });
623
664
  const tmp = `${path2}.${process.pid}.tmp`;
624
- writeFileSync(tmp, JSON.stringify(cfg, null, 2), "utf8");
625
- try {
626
- chmodSync(tmp, 384);
627
- } catch {
628
- }
629
- renameSync(tmp, path2);
665
+ atomicWriteSync(path2, JSON.stringify(cfg, null, 2), tmp);
630
666
  }
631
667
  function loadLanguage(path2 = defaultConfigPath()) {
632
668
  return readConfig(path2).lang;
@@ -840,8 +876,8 @@ function computeWait(attempt, initial, cap, retryAfter) {
840
876
  }
841
877
  function sleep(ms, signal) {
842
878
  if (ms <= 0) return Promise.resolve();
843
- return new Promise((resolve15, reject) => {
844
- const timer = setTimeout(resolve15, ms);
879
+ return new Promise((resolve16, reject) => {
880
+ const timer = setTimeout(resolve16, ms);
845
881
  if (signal) {
846
882
  const onAbort = () => {
847
883
  clearTimeout(timer);
@@ -929,8 +965,8 @@ var DeepSeekClient = class {
929
965
  const waitMs = Math.max(0, this.nextChatRequestAt - now);
930
966
  this.nextChatRequestAt = Math.max(now, this.nextChatRequestAt) + this.minChatIntervalMs;
931
967
  if (waitMs <= 0) return;
932
- await new Promise((resolve15, reject) => {
933
- const timer = setTimeout(resolve15, waitMs);
968
+ await new Promise((resolve16, reject) => {
969
+ const timer = setTimeout(resolve16, waitMs);
934
970
  signal?.addEventListener(
935
971
  "abort",
936
972
  () => {
@@ -1146,10 +1182,10 @@ var PauseGate = class {
1146
1182
  `${kind}: no confirmation listener registered \u2014 cannot prompt the user. This tool can only be used inside an interactive Reasonix session.`
1147
1183
  );
1148
1184
  }
1149
- return new Promise((resolve15) => {
1185
+ return new Promise((resolve16) => {
1150
1186
  const id = this._nextId++;
1151
1187
  const request = { id, kind, payload };
1152
- this._pending.set(id, { resolve: resolve15, request });
1188
+ this._pending.set(id, { resolve: resolve16, request });
1153
1189
  for (const fn of this._listeners) {
1154
1190
  try {
1155
1191
  fn(request);
@@ -1920,6 +1956,8 @@ var EN = {
1920
1956
  deepseek5xxUnreachable: " DeepSeek API is unreachable from your network \u2014 could be a wider DS outage or a local network issue.",
1921
1957
  deepseek5xxActionNetwork: " Try: (1) check your network, (2) wait 30s and retry, (3) status page: https://status.deepseek.com.",
1922
1958
  deepseek5xxActionRetry: " Try: (1) wait 30s and retry, (2) /model to switch model, (3) status page: https://status.deepseek.com.",
1959
+ upstream5xxHead: "Upstream service unavailable ({status}) at {host} \u2014 the configured API endpoint returned a server error, not a Reasonix bug. Already retried 4\xD7 with backoff.",
1960
+ upstream5xxActionRetry: " Try: (1) check that the local/proxy model server is up, (2) wait and retry, (3) /model to switch model.",
1923
1961
  innerNoMessage: "(no message)",
1924
1962
  reasonAborted: "[aborted by user (Esc) \u2014 summarizing what I found so far]",
1925
1963
  reasonContextGuard: "[context budget running low \u2014 summarizing before the next call would overflow]",
@@ -2100,6 +2138,7 @@ var EN = {
2100
2138
  modelSet: "model \u2192 {id}",
2101
2139
  effortStatus: "effort \u2192 {current} (pick: {list})",
2102
2140
  effortUsage: "usage: /effort <{list}> (high is the safe default; max is a DeepSeek extension)",
2141
+ effortUsageNoMax: "usage: /effort <{list}>",
2103
2142
  effortSet: "effort \u2192 {effort}",
2104
2143
  budgetNoCap: "no session budget set \u2014 Reasonix will keep going until you stop it. Set one with: /budget <usd> (e.g. /budget 5)",
2105
2144
  budgetStatus: "budget: ${spent} of ${cap} ({pct}%) \xB7 /budget off to clear, /budget <usd> to change",
@@ -2912,6 +2951,1707 @@ var EN = {
2912
2951
  }
2913
2952
  };
2914
2953
 
2954
+ // src/i18n/de.ts
2955
+ var de = {
2956
+ ...EN,
2957
+ common: {
2958
+ ...EN.common,
2959
+ error: "Fehler",
2960
+ warning: "Warnung",
2961
+ loading: "Wird geladen...",
2962
+ done: "Fertig",
2963
+ cancel: "Abbrechen",
2964
+ confirm: "Best\xE4tigen",
2965
+ back: "Zur\xFCck",
2966
+ next: "Weiter",
2967
+ tool: "Werkzeug",
2968
+ running: "l\xE4uft",
2969
+ noTurns: "(noch keine Turns)"
2970
+ },
2971
+ cli: {
2972
+ ...EN.cli,
2973
+ description: "DeepSeek-natives Agent-Framework, gebaut f\xFCr Cache-Treffer und g\xFCnstige Tokens.",
2974
+ continue: "Die zuletzt verwendete Chat-Sitzung fortsetzen, ohne die Auswahl anzuzeigen.",
2975
+ setup: "Interaktiver Assistent f\xFCr API-Schl\xFCssel und MCP-Server. Jederzeit erneut ausf\xFChrbar.",
2976
+ chat: "Interaktive Ink-TUI mit Live-Cache- und Kostenanzeige.",
2977
+ run: "Eine einzelne Aufgabe nicht-interaktiv ausf\xFChren, Ausgabe wird gestreamt.",
2978
+ stats: "Nutzungsdashboard anzeigen.",
2979
+ doctor: "Gesundheitscheck mit einem Befehl.",
2980
+ code: "Code-Editor-Chat \u2014 Dateisystem-Werkzeuge mit Wurzel in <dir> (Standard: cwd), Coding-System-Prompt, v4-flash-Baseline.",
2981
+ commit: "Commit-Nachricht aus der gestagten Diff entwerfen.",
2982
+ sessions: "Gespeicherte Chat-Sitzungen auflisten oder nach Name anzeigen.",
2983
+ pruneSessions: "Inaktive Sitzungen ab N Tagen l\xF6schen (Standard 90). Mit --dry-run zur Vorschau.",
2984
+ events: "Kernel-Event-Log-Seite lesbar ausgeben.",
2985
+ replay: "Interaktive Ink-TUI zum Durchbl\xE4ttern eines Transkripts.",
2986
+ diff: "Zwei Transkripte in einer geteilten Ink-TUI vergleichen.",
2987
+ mcp: "Model-Context-Protocol-Hilfsprogramme \u2014 Server entdecken, Setup testen.",
2988
+ index: "Lokalen semantischen Suchindex erstellen (oder inkrementell aktualisieren).",
2989
+ version: "Reasonix-Version ausgeben.",
2990
+ update: "Nach einer neueren Reasonix-Version suchen und installieren."
2991
+ },
2992
+ stats: {
2993
+ ...EN.stats,
2994
+ usageHint: "F\xFChre `reasonix chat`, `reasonix code` oder `reasonix run <task>` aus \u2013 jeden Turn",
2995
+ usageDetail: "H\xE4ngt eine Zeile an das Log an; `reasonix stats` fasst sie zusammen."
2996
+ },
2997
+ run: {
2998
+ ...EN.run,
2999
+ missingApiKey: "DEEPSEEK_API_KEY ist nicht gesetzt und stdin ist kein TTY (Nachfrage nicht m\xF6glich).\nSetze die Umgebungsvariable oder starte einmal interaktiv `reasonix chat`, um einen Schl\xFCssel zu speichern.\n"
3000
+ },
3001
+ sessions: {
3002
+ ...EN.sessions,
3003
+ emptyHint: "noch keine gespeicherten Sitzungen \u2013 starte `reasonix chat` (Sitzungen werden automatisch gespeichert, au\xDFer mit --no-session).",
3004
+ listHeader: "Gespeicherte Sitzungen (~/.reasonix/sessions/):",
3005
+ inspectHint: "Ansehen: reasonix sessions <name>",
3006
+ resumeHint: "Fortsetzen: reasonix chat --session <name>",
3007
+ noSession: 'keine Sitzung namens "{name}" (oder sie ist leer).',
3008
+ lookedAt: "Angesehen: {path}",
3009
+ noIdleSessions: "Keine Sitzungen seit >= {days} Tagen inaktiv. Nichts bereinigt.",
3010
+ wouldPrune: "W\xFCrde {count} Sitzung(en) bereinigen, die >= {days} Tage inaktiv sind:",
3011
+ dryRunHint: "Ohne --dry-run erneut ausf\xFChren, um wirklich zu l\xF6schen.",
3012
+ prunedCount: "{count} Sitzung(en) bereinigt, die >= {days} Tage inaktiv waren:",
3013
+ daysInvalid: "--days muss eine positive ganze Zahl sein (erhalten: {days})."
3014
+ },
3015
+ ui: {
3016
+ ...EN.ui,
3017
+ tipShownOnce: "einmal angezeigt",
3018
+ modelOverride: "das Standardmodell \xFCberschreiben",
3019
+ noSession: "Sitzungsspeicherung f\xFCr diesen Durchlauf deaktivieren",
3020
+ noMouseHint: "SGR-Mausverfolgung deaktivieren; stellt die native Auswahl per Ziehen und Rechtsklick wieder her",
3021
+ noProxyHint: "HTTPS_PROXY / HTTP_PROXY f\xFCr diesen Durchlauf ignorieren; direkt verbinden",
3022
+ resumeHint: "die angegebene Sitzung zwangsweise fortsetzen (auch wenn sie inaktiv ist)",
3023
+ newHint: "Eine neue Sitzung erzwingen (--session / --continue ignorieren)",
3024
+ transcriptHint: "Pfad zum Speichern der JSONL-Ausgabe",
3025
+ budgetHint: "Sitzungs-USD-Obergrenze \u2013 warnt bei 80 %, verweigert den n\xE4chsten Zug bei 100 %",
3026
+ modelIdHint: "DeepSeek-Modell-ID (z. B. deepseek-v4-flash)",
3027
+ systemPromptHint: "die Standard-Systemeingabeaufforderung \xFCberschreiben",
3028
+ effortHint: "Denkaufwand \u2013 niedrig|mittel|hoch|maximal",
3029
+ sessionNameHint: "Sitzungsname (Standard: \u201Edefault\u201C)",
3030
+ ephemeralHint: "Sitzungsspeicherung f\xFCr diesen Durchlauf deaktivieren",
3031
+ mcpSpecHint: "MCP-Server-Spezifikation (wiederholbar)",
3032
+ mcpPrefixHint: "Stelle diesen String vor die Namen der MCP-Tools",
3033
+ noConfigHint: "Ignoriere bei diesem Durchlauf die Datei ~/.reasonix/config.json",
3034
+ effortHintShort: "Denkaufwand \u2013 niedrig|mittel|hoch|maximal",
3035
+ budgetHintShort: "Sitzungs-USD-Obergrenze",
3036
+ transcriptHintShort: "Pfad zum JSONL-Transkript",
3037
+ mcpSpecHintShort: "MCP-Server-Spezifikation (wiederholbar)",
3038
+ mcpPrefixHintShort: "Pr\xE4fix f\xFCr MCP-Toolnamen",
3039
+ dryRunHint: "anzeigen, was installiert w\xFCrde, ohne es tats\xE4chlich zu installieren",
3040
+ rebuildHint: "den Index komplett neu erstellen",
3041
+ embedModelHint: "Name des Einbettungsmodells",
3042
+ projectDirHint: "Projektstammverzeichnis",
3043
+ ollamaUrlHint: "Ollama-Server-URL",
3044
+ skipPromptsHint: "Best\xE4tigungsaufforderungen \xFCberspringen",
3045
+ verboseHint: "Alle Metadaten der Sitzung anzeigen",
3046
+ pruneDaysHint: "Sitzungen l\xF6schen, die seit mindestens dieser Anzahl von Tagen inaktiv sind (Standard: 90)",
3047
+ pruneDryRunHint: "Liste auf, was gel\xF6scht w\xFCrde, ohne etwas zu entfernen",
3048
+ eventTypeHint: "Nach Veranstaltungstyp filtern",
3049
+ eventSinceHint: "Beginne mit dieser Ereignis-ID",
3050
+ eventTailHint: "Nur die letzten N Ereignisse anzeigen",
3051
+ jsonHint: "Ausgabe als JSON",
3052
+ projectionHint: "Zeige den voraussichtlichen Zustand bei jedem Ereignis an",
3053
+ printHint: "Anzeige \xFCber stdout statt \xFCber die TUI",
3054
+ headHint: "Zeige nur die ersten N Ereignisse an",
3055
+ tailHint: "Nur die letzten N Ereignisse anzeigen",
3056
+ mdReportHint: "Erstelle einen Markdown-Diff-Bericht unter diesem Pfad",
3057
+ printHintTable: "Eine Tabelle auf die Standardausgabe ausgeben",
3058
+ tuiHint: "\xD6ffne die interaktive TUI",
3059
+ labelAHint: "Bezeichnung f\xFCr den linken Bereich",
3060
+ labelBHint: "Bezeichnung f\xFCr den rechten Bereich",
3061
+ mcpListDescription: "Durchsuche das MCP-Register (offiziell \u2192 smithery \u2192 lokaler Fallback)",
3062
+ mcpInspectDescription: "die Spezifikationen eines MCP-Servers pr\xFCfen (Tools, Ressourcen, Eingabeaufforderungen)",
3063
+ mcpSearchDescription: "Suche in der MCP-Registrierung nach Servern, die einer Suchanfrage entsprechen",
3064
+ mcpInstallDescription: "Einen MCP-Server anhand seines Namens installieren (schreibt dessen Spezifikation in deine Konfiguration)",
3065
+ mcpBrowseDescription: "Interaktiver Marktplatz-Browser \u2013 tippe, um zu filtern, dr\xFCcke die Eingabetaste, um zu installieren",
3066
+ mcpLocalHint: "Nur den mitgelieferten Offline-Katalog anzeigen",
3067
+ mcpRefreshHint: "den 24-Stunden-Cache umgehen und neu abrufen",
3068
+ mcpLimitHint: "Maximale Anzahl der anzuzeigenden Eintr\xE4ge",
3069
+ mcpPagesHint: "Lade gleich so viele Seiten (Standard: 1)",
3070
+ mcpAllHint: "Jede Seite laden (beim ersten Mal etwas langsam)",
3071
+ mcpMaxPagesHint: "Begrenze die Anzahl der Seiten, die bei der Suche durchsucht werden sollen (Standard: 20)",
3072
+ jsonHintCatalog: "Ausgabe als JSON",
3073
+ jsonHintReport: "Gib den Inspektionsbericht als JSON aus",
3074
+ modelOverrideFlash: "das Modell \xFCberschreiben (Standard: deepseek-v4-flash)",
3075
+ skipConfirmHint: "Die Best\xE4tigungsabfrage \xFCberspringen",
3076
+ welcome: "Starte jederzeit `reasonix`, um zu chatten \u2013 deine Einstellungen bleiben gespeichert.",
3077
+ taglineChat: "DeepSeek-nativer Agent",
3078
+ taglineCode: "DeepSeek-nativer Coding-Agent",
3079
+ taglineSub: "cache-first \xB7 flash-first",
3080
+ startSessionHint: "Tippe eine Nachricht, um deine Sitzung zu starten",
3081
+ inputPlaceholder: "Frag etwas... (tippe / f\xFCr Befehle, @ f\xFCr Dateien)",
3082
+ busy: "Denke nach...",
3083
+ thinking: "\u25B8 denke nach...",
3084
+ undo: "R\xFCckg\xE4ngig",
3085
+ undoHint: "Dr\xFCcke innerhalb von 5s zum R\xFCckg\xE4ngig machen",
3086
+ applied: "angewendet",
3087
+ rejected: "abgelehnt",
3088
+ noDashboard: "Automatisch gestartetes eingebettetes Web-Dashboard unterdr\xFCcken.",
3089
+ openDashboardHint: "Dashboard-URL sofort im Standard-Browser \xF6ffnen, sobald der Server bereit ist. Keine Wirkung bei --no-dashboard.",
3090
+ dashboardPortHint: "Dashboard auf einen festen Port (1\u201365535) festlegen. Stabil \xFCber Neustarts hinweg \u2014 erforderlich f\xFCr SSH-Tunnel. Standard: ephemeral.",
3091
+ dashboardPortInvalid: "\u25B2 --dashboard-port={value} wird ignoriert (muss eine ganze Zahl 1\u201365535 sein) \u2014 R\xFCckfall auf ephemeral",
3092
+ dashboardAutoStartFailed: "\u25B2 Dashboard-Autostart fehlgeschlagen ({reason}) \u2014 /dashboard versuchen oder --no-dashboard zum Unterdr\xFCcken",
3093
+ systemAppendHint: "Anweisungen an den Code-System-Prompt anh\xE4ngen. Ersetzt NICHT den Standard-Prompt \u2014 wird danach eingef\xFCgt.",
3094
+ systemAppendFileHint: "Dateiinhalte an den Code-System-Prompt anh\xE4ngen. Ersetzt NICHT den Standard-Prompt. UTF-8, relativ zu cwd oder absolut.",
3095
+ resumedSession: '\u25B8 Sitzung "{name}" fortgesetzt mit {count} vorherigen Nachrichten \xB7 /new f\xFCr frischen Start \xB7 /sessions zum Verwalten',
3096
+ newSession: '\u25B8 Sitzung "{name}" (neu) \u2014 automatisch gespeichert w\xE4hrend des Chattens \xB7 /sessions zum Umbenennen oder L\xF6schen',
3097
+ ephemeralSession: "\u25B8 ephemerer Chat (keine Sitzungspersistenz) \u2014 --no-session weglassen zum Aktivieren",
3098
+ restoredEdits: "\u25B8 {count} ausstehende Edit-Block(s) aus einem unterbrochenen vorherigen Durchlauf wiederhergestellt \u2014 /apply zum \xDCbernehmen oder /discard zum Verwerfen.",
3099
+ resumedPlan: "Fortgesetzter Plan \xB7 {when}{summary}"
3100
+ },
3101
+ code: {
3102
+ ...EN.code,
3103
+ workspaceConflict: "\u26A0 Arbeitsbereich enth\xE4lt Dateien einer anderen Agent-Plattform ({platforms}). Reasonix Code kann sie als Projektinhalt lesen; starte mit --dir <dein-projekt> neu, falls das nicht gew\xFCnscht ist.\n",
3104
+ systemAppendEmpty: "--system-append ist leer \u2014 kein Prompt-Text wird angeh\xE4ngt\n",
3105
+ systemAppendFileReadError: 'Fehler: kann --system-append-file "{filePath}" nicht lesen: {errorDetails}\n'
3106
+ },
3107
+ slash: {
3108
+ ...EN.slash,
3109
+ help: { ...EN.slash.help, description: "Vollst\xE4ndige Befehlsreferenz anzeigen" },
3110
+ status: { ...EN.slash.status, description: "Aktuelles Modell, Flags, Kontext und Sitzung" },
3111
+ effort: {
3112
+ ...EN.slash.effort,
3113
+ argsHint: "<niedrig|mittel|hoch|max>",
3114
+ description: "Reasoning-Effort-Grenze (low|medium|high|max); high ist der sichere Standard f\xFCr vLLM/Azure"
3115
+ },
3116
+ model: {
3117
+ ...EN.slash.model,
3118
+ description: "DeepSeek-Modell-ID wechseln"
3119
+ },
3120
+ models: {
3121
+ ...EN.slash.models,
3122
+ description: "Verf\xFCgbare Modelle von DeepSeek /models abrufen"
3123
+ },
3124
+ language: {
3125
+ description: "Laufzeitsprache wechseln",
3126
+ argsHint: "<EN|zh-CN|de>",
3127
+ success: "Sprache auf Deutsch umgestellt.",
3128
+ unsupported: "Nicht unterst\xFCtzter Sprachcode: {code}. Unterst\xFCtzt: {supported}."
3129
+ },
3130
+ budget: {
3131
+ ...EN.slash.budget,
3132
+ description: "Session-USD-Grenze \u2014 warnt bei 80 %, verweigert n\xE4chsten Turn bei 100 %. Standardm\xE4\xDFig aus. /budget allein zeigt Status."
3133
+ },
3134
+ mcp: { ...EN.slash.mcp, description: "MCP-Server + Tools dieser Sitzung auflisten" },
3135
+ resource: {
3136
+ ...EN.slash.resource,
3137
+ description: "MCP-Ressourcen durchsuchen + lesen (kein Arg \u2192 URIs auflisten; <uri> \u2192 Inhalt abrufen)"
3138
+ },
3139
+ prompt: {
3140
+ ...EN.slash.prompt,
3141
+ argsHint: "[Name]",
3142
+ description: "MCP-Prompts durchsuchen + abrufen (kein Arg \u2192 Namen auflisten; <name> \u2192 Prompt rendern)"
3143
+ },
3144
+ memory: {
3145
+ ...EN.slash.memory,
3146
+ argsHint: "[Liste|<Name> anzeigen|<Name> vergessen|<Bereich> l\xF6schen \u2013 Best\xE4tigen]",
3147
+ description: "Pinned Memory anzeigen / verwalten (REASONIX.md + ~/.reasonix/memory)"
3148
+ },
3149
+ skill: {
3150
+ ...EN.slash.skill,
3151
+ description: "Benutzer-Skills auflisten / ausf\xFChren (Projekt + benutzerdefiniert + global + builtin)"
3152
+ },
3153
+ hooks: {
3154
+ ...EN.slash.hooks,
3155
+ argsHint: "[Neu laden]",
3156
+ description: "Aktive Hooks auflisten (settings.json unter .reasonix/) \xB7 reload liest von Platte neu"
3157
+ },
3158
+ permissions: {
3159
+ ...EN.slash.permissions,
3160
+ argsHint: "[Liste|<Pr\xE4fix> hinzuf\xFCgen|<Pr\xE4fix|N> entfernen|L\xF6schen (Best\xE4tigung erforderlich)]",
3161
+ description: "Shell-Allowlist anzeigen / bearbeiten (builtin schreibgesch\xFCtzt \xB7 pro Projekt: ~/.reasonix/config.json)"
3162
+ },
3163
+ dashboard: {
3164
+ ...EN.slash.dashboard,
3165
+ argsHint: "[Stopp]",
3166
+ description: "Eingebettetes Web-Dashboard starten (127.0.0.1, token-gesichert)"
3167
+ },
3168
+ update: {
3169
+ ...EN.slash.update,
3170
+ description: "Aktuelle vs. neueste Version anzeigen + Upgrade-Befehl"
3171
+ },
3172
+ stats: {
3173
+ ...EN.slash.stats,
3174
+ description: "Sitzungs\xFCbergreifendes Kosten-Dashboard (heute / Woche / Monat / gesamt \xB7 Cache-Treffer \xB7 vs. Claude)"
3175
+ },
3176
+ cost: {
3177
+ ...EN.slash.cost,
3178
+ argsHint: "[Text]",
3179
+ description: "ohne Text \u2192 Ausgaben letzter Turn (Kostenkarte); mit Text \u2192 Kostensch\xE4tzung f\xFCr als n\xE4chster Senden (worst-case + likely-cache)"
3180
+ },
3181
+ doctor: {
3182
+ ...EN.slash.doctor,
3183
+ description: "Gesundheitscheck (API / Config / API-Reichweite / Index / Hooks / Projekt)"
3184
+ },
3185
+ context: {
3186
+ ...EN.slash.context,
3187
+ description: "Context-Window-Aufschl\xFCsselung (System / Tools / Log / Input)"
3188
+ },
3189
+ retry: {
3190
+ ...EN.slash.retry,
3191
+ description: "Letzte Nachricht k\xFCrzen & erneut senden (frischer Sample)"
3192
+ },
3193
+ compact: {
3194
+ ...EN.slash.compact,
3195
+ argsHint: "[Token]",
3196
+ description: "\xDCberdimensionierte Tool-Ergebnisse + Tool-Call-Args im Log k\xFCrzen; Grenze in Tokens, Standard 4000"
3197
+ },
3198
+ cwd: {
3199
+ ...EN.slash.cwd,
3200
+ argsHint: "[Pfad]",
3201
+ description: "Workspace-Root mid-Session wechseln \u2014 FS-/Shell-/Memory-Tools neu ausrichten, Projekt-Hooks neu laden, @-Mention-Walker aktualisieren"
3202
+ },
3203
+ stop: {
3204
+ ...EN.slash.stop,
3205
+ description: "Aktuellen Modell-Turn abbrechen (getippte Alternative zu Esc)"
3206
+ },
3207
+ feedback: {
3208
+ ...EN.slash.feedback,
3209
+ description: "GitHub-Issue mit Diagnoseinfo \xF6ffnen (in Zwischenablage kopiert)"
3210
+ },
3211
+ about: { ...EN.slash.about, description: "Projektinfo \u2014 Version, Website, Repo, Lizenz" },
3212
+ keys: { ...EN.slash.keys, description: "Tastatur + Maus + Kopieren/Einf\xFCgen-Referenz" },
3213
+ plans: {
3214
+ ...EN.slash.plans,
3215
+ description: "Aktive + archivierte Pl\xE4ne dieser Sitzung auflisten, neueste zuerst"
3216
+ },
3217
+ replay: {
3218
+ ...EN.slash.replay,
3219
+ description: "Archivierten Plan als schreibgesch\xFCtzte Time-Travel-Schnappschuss laden (Standard: neuester)"
3220
+ },
3221
+ sessions: {
3222
+ ...EN.slash.sessions,
3223
+ description: "Gespeicherte Sitzungen auflisten (aktuelle mit \u25B8 markiert)"
3224
+ },
3225
+ title: {
3226
+ ...EN.slash.title,
3227
+ description: "Modell bitten, diese Sitzung anhand des Gespr\xE4chs umzubenennen"
3228
+ },
3229
+ qq: {
3230
+ ...EN.slash.qq,
3231
+ description: "QQ-Kanal verbinden, inspizieren oder trennen (erste Verbindung f\xFChrt durch App-ID / App-Secret-Setup)"
3232
+ },
3233
+ setup: { ...EN.slash.setup, description: "Erinnert dich daran, `reasonix setup` auszuf\xFChren" },
3234
+ semantic: {
3235
+ ...EN.slash.semantic,
3236
+ description: "Semantic-Search-Status anzeigen \u2014 Index erstellt? Ollama installiert? Wie aktivieren?"
3237
+ },
3238
+ clear: {
3239
+ ...EN.slash.clear,
3240
+ description: "Nur sichtbaren Scrollback leeren (Log/Kontext bleibt)"
3241
+ },
3242
+ new: {
3243
+ ...EN.slash.new,
3244
+ description: "Frisches Gespr\xE4ch beginnen (Kontext + Scrollback l\xF6schen)"
3245
+ },
3246
+ loop: {
3247
+ ...EN.slash.loop,
3248
+ argsHint: "<5s..6h> <Eingabeaufforderung> \xB7 Stopp \xB7 (keine Argumente = Status)",
3249
+ description: "Prompt automatisch alle <intervall> erneut senden, bis du etwas eingibst / Esc / /loop stop"
3250
+ },
3251
+ init: {
3252
+ ...EN.slash.init,
3253
+ description: "Projekt scannen und eine REASONIX.md-Baseline erstellen (Modell schreibt; mit /apply reviewen). `force` \xFCberschreibt vorhandene Datei."
3254
+ },
3255
+ apply: {
3256
+ ...EN.slash.apply,
3257
+ description: "Ausstehende Edit-Blocks auf Platte schreiben (kein Arg \u2192 alle; `1`, `1,3` oder `1-4` \u2192 diese Teilmenge, Rest bleibt ausstehend)"
3258
+ },
3259
+ discard: {
3260
+ ...EN.slash.discard,
3261
+ description: "Ausstehende Edit-Blocks ohne Schreiben verwerfen (kein Arg \u2192 alle; Indizes \u2192 diese Teilmenge)"
3262
+ },
3263
+ walk: {
3264
+ ...EN.slash.walk,
3265
+ description: "Schrittweise durch ausstehende Edits gehen (git-add-p-Stil: y/n pro Block, a = Rest anwenden, A = AUTO umschalten)"
3266
+ },
3267
+ undo: { ...EN.slash.undo, description: "Letzten angewandten Edit-Batch r\xFCckg\xE4ngig machen" },
3268
+ history: {
3269
+ ...EN.slash.history,
3270
+ description: "Jeden Edit-Batch dieser Sitzung auflisten (IDs f\xFCr /show, r\xFCckg\xE4ngig-Markierungen)"
3271
+ },
3272
+ show: {
3273
+ ...EN.slash.show,
3274
+ description: "Gespeicherte Edit-Diff ausgeben (ID weglassen f\xFCr neuesten nicht-r\xFCckg\xE4ngigen)"
3275
+ },
3276
+ commit: { ...EN.slash.commit, description: "git add -A && git commit -m ..." },
3277
+ checkpoint: {
3278
+ ...EN.slash.checkpoint,
3279
+ argsHint: "[Name|Liste|<ID> l\xF6schen]",
3280
+ description: "Jede Datei, die die Sitzung ber\xFChrt hat, als Schnappschuss sichern (Cursor-artiger interner Speicher, nicht Git). /checkpoint allein listet auf."
3281
+ },
3282
+ restore: {
3283
+ ...EN.slash.restore,
3284
+ description: "Dateien auf einen benannten Checkpoint zur\xFCcksetzen (siehe /checkpoint list)"
3285
+ },
3286
+ plan: {
3287
+ ...EN.slash.plan,
3288
+ argsHint: "[Ein|Aus]",
3289
+ description: "Schreibgesch\xFCtzten Plan-Modus umschalten (Schreibzugriffe blockiert bis submit_plan + Genehmigung)"
3290
+ },
3291
+ mode: {
3292
+ ...EN.slash.mode,
3293
+ argsHint: "[Rezension|Auto|YOLO]",
3294
+ description: "Edit-Gate: review (Warteschlange) \xB7 auto (anwenden+r\xFCckg\xE4ngig) \xB7 yolo (anwenden+auto-shell). Shift+Tab schaltet um."
3295
+ },
3296
+ jobs: {
3297
+ ...EN.slash.jobs,
3298
+ description: "Hintergrund-Jobs auflisten, die mit run_background gestartet wurden"
3299
+ },
3300
+ kill: {
3301
+ ...EN.slash.kill,
3302
+ argsHint: "Bezeichner",
3303
+ description: "Hintergrund-Job nach ID beenden (SIGTERM \u2192 SIGKILL nach Gnadenfrist)"
3304
+ },
3305
+ logs: {
3306
+ ...EN.slash.logs,
3307
+ argsHint: "<id> [Zeilen]",
3308
+ description: "Ausgabe eines Hintergrund-Jobs anzeigen (Standard letzte 80 Zeilen)"
3309
+ },
3310
+ btw: {
3311
+ ...EN.slash.btw,
3312
+ argsHint: "<Frage>",
3313
+ description: "Kurze Randfrage stellen \u2014 wird von Grund auf beantwortet, nie zum Gespr\xE4chskontext hinzugef\xFCgt"
3314
+ },
3315
+ "search-engine": {
3316
+ ...EN.slash["search-engine"],
3317
+ description: "Web-Search-Backend wechseln \u2014 bing (Standard, funktioniert von CN ohne Proxy), searxng (selbst gehostet), metaso (kostenlos 100/Tag), tavily (kostenlos 1000/Monat), perplexity (AI-native) oder exa (AI-native)"
3318
+ },
3319
+ theme: {
3320
+ ...EN.slash.theme,
3321
+ argsHint: "[auto|dunkel|hell|mitternachtsblau|tiefblau|hoher Kontrast]",
3322
+ description: "Terminal-Theme anzeigen oder speichern. Ohne Argument \xF6ffnet die Auswahl."
3323
+ },
3324
+ exit: { ...EN.slash.exit, description: "TUI beenden" }
3325
+ },
3326
+ wizard: {
3327
+ ...EN.wizard,
3328
+ languageTitle: "Sprache ausw\xE4hlen",
3329
+ languageSubtitle: "Aus der Systemsprache erkannt. Sp\xE4ter mit /language wechselbar.",
3330
+ welcomeTitle: "Willkommen bei Reasonix.",
3331
+ apiKeyPrompt: "F\xFCge deinen DeepSeek-API-Schl\xFCssel ein, um loszulegen.",
3332
+ apiKeyGetOne: "Erhalte einen unter: https://platform.deepseek.com/api_keys",
3333
+ apiKeySavedLocally: "Lokal gespeichert unter {path}",
3334
+ apiKeyInputLabel: "Schl\xFCssel > ",
3335
+ apiKeyPlaceholder: "sk-...",
3336
+ apiKeyInvalid: "Der Schl\xFCssel wirkt zu kurz \u2013 f\xFCge den vollst\xE4ndigen Token ein (16+ Zeichen, keine Leerzeichen).",
3337
+ apiKeyChecking: "API-Schl\xFCssel wird gepr\xFCft...",
3338
+ apiKeyRejected: "DeepSeek hat diesen API-Schl\xFCssel abgelehnt. F\xFCge einen g\xFCltigen Schl\xFCssel ein oder brich das Setup mit Esc ab.",
3339
+ apiKeyCheckFailed: "Konnte diesen API-Schl\xFCssel gerade nicht verifizieren ({message}). \xDCberpr\xFCfe deine Netzwerkverbindung oder versuche es erneut.",
3340
+ apiKeyPreview: "Vorschau: {redacted}",
3341
+ themeTitle: "Theme ausw\xE4hlen",
3342
+ themeSubtitle: "Die Vorschau aktualisiert sich beim Navigieren. Sp\xE4ter mit /theme \xE4nderbar.",
3343
+ themeSampleHeading: "Beispiel",
3344
+ themeFooter: "[\u2191\u2193] navigieren \xB7 [Enter] best\xE4tigen \xB7 [Esc] abbrechen",
3345
+ themeCaption: {
3346
+ ...EN.wizard.themeCaption,
3347
+ dark: "K\xFChle dunkle T\xF6ne (Standard)",
3348
+ light: "Helle klare Ansicht",
3349
+ midnight: "Tokyo-Night-Palette",
3350
+ "deep-blue": "Tiefblau auf Schwarz",
3351
+ "high-contrast": "Barrierefreiheit"
3352
+ },
3353
+ mcpTitle: "Welche MCP-Server soll Reasonix f\xFCr dich einrichten?",
3354
+ mcpUserArgsHint: "(du wirst {arg} bereitstellen)",
3355
+ mcpFooterMulti: "[\u2191\u2193] navigieren \xB7 [Leertaste] umschalten \xB7 [Enter] best\xE4tigen \xB7 [Esc] abbrechen \xB7 leer = \xFCberspringen",
3356
+ mcpArgsTitle: "{name} konfigurieren",
3357
+ mcpArgsDirMissing: "Verzeichnis {path} existiert nicht.",
3358
+ mcpArgsDirCreateHint: "[Y/Enter] erstellen (mkdir -p) \xB7 [N/Esc] anderen Pfad eingeben",
3359
+ mcpArgsDirCreateFailed: "Konnte {path} nicht erstellen: {message}",
3360
+ mcpArgsRequiredParam: "Erforderlicher Parameter: ",
3361
+ mcpArgsEmpty: "{name} ben\xF6tigt einen Wert \u2014 leere Zeichenkette erhalten.",
3362
+ mcpArgsNotADir: "{path} existiert, ist aber kein Verzeichnis.",
3363
+ reviewTitle: "Bereit zum Speichern",
3364
+ reviewLabelApiKey: "API-Schl\xFCssel",
3365
+ reviewLabelLanguage: "Sprache",
3366
+ reviewLabelTheme: "Theme",
3367
+ reviewLabelMcp: "MCP",
3368
+ reviewMcpNone: "(keine)",
3369
+ reviewMcpServers: "{count} Server",
3370
+ reviewSavesTo: "Speichert nach {path}",
3371
+ reviewSaveError: "Konfiguration konnte nicht gespeichert werden: {message}",
3372
+ reviewFooter: "[Enter] speichern \xB7 [Esc] abbrechen",
3373
+ savedTitle: "\u25B8 Gespeichert.",
3374
+ savedShellHint: 'Shell-Befehle, die das Modell ausf\xFChren m\xF6chte, fragen jedes Mal nach \u2013 w\xE4hle \xBBimmer erlauben" in der Eingabeaufforderung, um diesen genauen Befehl f\xFCr dieses Projekt auf die Whitelist zu setzen. Kein globales Allow-All-Flag (designbedingt).',
3375
+ savedFooter: "[Enter] zum Beenden",
3376
+ selectFooter: "[\u2191\u2193] navigieren \xB7 [Enter] best\xE4tigen \xB7 [Esc] abbrechen",
3377
+ stepCounter: "Schritt {step}/{total} \xB7 ",
3378
+ exitHint: "/exit zum Abbrechen",
3379
+ themeSampleReasoning: "Denken"
3380
+ },
3381
+ themePicker: {
3382
+ ...EN.themePicker,
3383
+ header: "Theme",
3384
+ footer: "\u2191\u2193 ausw\xE4hlen \xB7 \u23CE best\xE4tigen \xB7 Esc abbrechen",
3385
+ currentPref: "Aktuelle Einstellung",
3386
+ activeNow: "Jetzt aktiv",
3387
+ autoDesc: "REASONIX_THEME oder Standard verwenden"
3388
+ },
3389
+ planFlow: {
3390
+ ...EN.planFlow,
3391
+ approveCardTitle: "Plan genehmigen",
3392
+ approveCardMetaRight: "wartet",
3393
+ openQuestionsBanner: "\u25B2 der Plan zeigt offene Fragen oder Risiken \u2014 w\xE4hle {refine}, um konkrete Antworten zu schreiben, bevor das Modell fortf\xE4hrt.",
3394
+ openQuestionsHeader: "Offene Fragen / Risiken",
3395
+ truncatedBodyMore: "\u2026 {n} weitere Zeile oben im Scrollback",
3396
+ truncatedBodyMorePlural: "\u2026 {n} weitere Zeilen oben im Scrollback",
3397
+ picker: {
3398
+ ...EN.planFlow.picker,
3399
+ accept: "akzeptieren",
3400
+ acceptHint: "Jetzt ausf\xFChren, in Reihenfolge",
3401
+ refine: "verfeinern",
3402
+ refineHint: "Dem Agenten mehr Anweisungen geben, neuen Plan entwerfen",
3403
+ revise: "\xFCberarbeiten",
3404
+ reviseHint: "Plan inline bearbeiten vor der Ausf\xFChrung (Schritte \xFCberspringen/neu ordnen)",
3405
+ reject: "ablehnen",
3406
+ rejectHint: "Verwerfen, Agent versucht von Grund auf neu"
3407
+ },
3408
+ refineFooter: "\u23CE senden \xB7 Esc zur\xFCck zur Auswahl",
3409
+ refineQuestionsHeading: "Beantworte diese oder beschreibe die gew\xFCnschte \xC4nderung:",
3410
+ modes: {
3411
+ ...EN.planFlow.modes,
3412
+ approve: {
3413
+ ...EN.planFlow.modes.approve,
3414
+ title: "Genehmigen \u2014 letzte Anweisungen?",
3415
+ hint: "Beantworte Fragen aus dem Plan, f\xFCge Einschr\xE4nkungen hinzu oder dr\xFCcke einfach Enter zur Genehmigung.",
3416
+ blankHint: " (Enter ohne Text = ohne Zusatzanweisungen genehmigen.)"
3417
+ },
3418
+ refine: {
3419
+ ...EN.planFlow.modes.refine,
3420
+ title: "Verfeinern \u2014 was soll das Modell \xE4ndern?",
3421
+ hint: "Beschreibe, was falsch ist oder fehlt, oder beantworte Fragen aus dem Plan.",
3422
+ blankHint: " (Enter ohne Text = Modell w\xE4hlt sichere Standardwerte f\xFCr offene Fragen.)"
3423
+ },
3424
+ reject: {
3425
+ ...EN.planFlow.modes.reject,
3426
+ title: "Ablehnen \u2014 sag dem Modell warum (optional)",
3427
+ hint: "Sag dem Modell, was es an deinem Ziel falsch verstanden hat oder was du stattdessen m\xF6chtest.",
3428
+ blankHint: " (Enter ohne Text = ohne Erkl\xE4rung abbrechen; das Modell fragt, was du m\xF6chtest.)"
3429
+ },
3430
+ "checkpoint-revise": {
3431
+ ...EN.planFlow.modes["checkpoint-revise"],
3432
+ title: "\xDCberarbeiten \u2014 was soll sich vor dem n\xE4chsten Schritt \xE4ndern?",
3433
+ hint: "Umfangs\xE4nderung, Schritte \xFCberspringen, alternativer Ansatz \u2014 das Modell passt den Restplan an.",
3434
+ blankHint: " (Enter ohne Text = mit aktuellem Plan fortfahren.)"
3435
+ },
3436
+ "choice-custom": {
3437
+ ...EN.planFlow.modes["choice-custom"],
3438
+ title: "Benutzerdefinierte Antwort \u2014 schreibe, was passt",
3439
+ hint: "Freitext-Antwort. Das Modell liest sie w\xF6rtlich und f\xE4hrt fort \u2014 keine Notwendigkeit, die aufgef\xFChrten Optionen zu treffen.",
3440
+ blankHint: " (Enter ohne Text = Modell fragen, was du eigentlich m\xF6chtest.)"
3441
+ }
3442
+ },
3443
+ checkpoint: {
3444
+ ...EN.planFlow.checkpoint,
3445
+ title: "Checkpoint \u2014 Schritt erledigt",
3446
+ continue: "Fortfahren \u2014 n\xE4chsten Schritt ausf\xFChren",
3447
+ continueHint: "Modell f\xE4hrt mit dem n\xE4chsten Schritt fort.",
3448
+ finish: "Abschlie\xDFen \u2014 zusammenfassen und beenden",
3449
+ finishHint: "Modell zeichnet den letzten Schritt auf und fasst den abgeschlossenen Plan zusammen.",
3450
+ revise: "\xDCberarbeiten \u2014 Feedback vor dem n\xE4chsten Schritt geben",
3451
+ reviseHint: "Bleibe pausiert, tippe Anweisungen; Modell passt den Restplan an.",
3452
+ stop: "Anhalten \u2014 Plan hier beenden",
3453
+ stopHint: "Modell fasst zusammen, was getan wurde, und beendet."
3454
+ },
3455
+ stepList: {
3456
+ ...EN.planFlow.stepList,
3457
+ counter: "{total} Schritte",
3458
+ counterSingular: "{total} Schritt",
3459
+ counterDone: "{done}/{total} erledigt ({pct}%) \xB7 {total} Schritte",
3460
+ counterDoneSingular: "{done}/{total} erledigt ({pct}%) \xB7 {total} Schritt"
3461
+ },
3462
+ noPlanSummary: "Noch kein Plan-Body \xFCbermittelt.",
3463
+ detailCollapsedHint: "Strg+P erweitert die vollst\xE4ndigen Plan-Details.",
3464
+ detailExpandedHint: "Strg+P klappt Details ein.",
3465
+ detailHeader: "Plan-Details",
3466
+ detailWindow: "Zeige Zeilen {start}-{end} von {total}",
3467
+ detailScrollHint: "Bild\u2191/Bild\u2193 scrollt Details \xB7 Pos1/Ende springt",
3468
+ reviseTitle: "Plan \xFCberarbeiten",
3469
+ reviseSteps: "{count} Schritte",
3470
+ reviseFooter: "\u2191\u2193 fokussieren \xB7 Leertaste \xFCberspringen umschalten \xB7 k/j verschieben \xB7 \u23CE akzeptieren \xB7 Esc abbrechen",
3471
+ riskMed: " mittel",
3472
+ riskHigh: " hoch",
3473
+ completeMsg: "\u25B8 Plan abgeschlossen \u2014 alle {total} Schritt{e} erledigt \xB7 archiviert"
3474
+ },
3475
+ app: {
3476
+ ...EN.app,
3477
+ dashboardStopped: "\u25B8 Dashboard gestoppt.",
3478
+ notedScopeProject: "Projekt",
3479
+ notedScopeGlobal: "global",
3480
+ commandFailed: "! Befehl fehlgeschlagen",
3481
+ btwFailed: "/btw fehlgeschlagen",
3482
+ walkCancelledRemaining: "\u25B8 Walk abgebrochen \u2014 {count} Block(s) noch ausstehend.",
3483
+ walkCancelled: "\u25B8 Walk abgebrochen.",
3484
+ editModeYolo: "\u25B8 Edit-Modus: YOLO \u2014 Edits UND Shell-Befehle auto-ausf\xFChren. /undo macht Edits immer noch r\xFCckg\xE4ngig. Vorsicht.",
3485
+ editModeAuto: "\u25B8 Edit-Modus: AUTO \u2014 Edits werden sofort angewandt; dr\xFCcke u innerhalb von 5s zum R\xFCckg\xE4ngigmachen (Leertaste pausiert den Timer). Shell-Befehle fragen weiterhin.",
3486
+ editModeReview: "\u25B8 Edit-Modus: review \u2014 Edits warten auf /apply (oder y) / /discard (oder n)",
3487
+ rejectedEdit: "\u25B8 Edit abgelehnt: {path}{context}",
3488
+ autoApprovingRest: "\u25B8 Restliche Edits f\xFCr diesen Turn werden automatisch genehmigt",
3489
+ flippedAutoSession: "\u25B8 F\xFCr den Rest der Sitzung auf AUTO umgeschaltet (gespeichert)",
3490
+ flippedAutoWalk: "\u25B8 Auf AUTO umgeschaltet \u2014 zuk\xFCnftige Edits werden sofort angewandt. Walk beendet.",
3491
+ notedMemory: "\u25B8 vermerkt ({scope}) \u2014 {verb} {path}",
3492
+ notedVerbCreated: "erstellt",
3493
+ notedVerbAppended: "Angeh\xE4ngt an",
3494
+ memoryWriteFailed: "# Speicherschreibfehler",
3495
+ verboseOn: "\u25B8 Ausf\xFChrlicher Modus an \u2014 vollst\xE4ndiges Reasoning + Tool-Ausgabe",
3496
+ verboseOff: "\u25B8 Ausf\xFChrlicher Modus aus \u2014 head/tail-K\xFCrzung wiederhergestellt",
3497
+ steerInjected: "\u25B8 Steuerung in Warteschlange \u2014 wird nach dem aktuellen Schritt hinzugef\xFCgt",
3498
+ steerCommandRejected: "\u25B8 Befehle sind deaktiviert, w\xE4hrend ein Turn gesteuert wird",
3499
+ btwUsage: "\u25B8 /btw <Frage> \u2014 eine Randfrage stellen, ohne den Gespr\xE4chskontext zu verschmutzen.",
3500
+ btwHeader: "\u226B btw",
3501
+ restoreCodeOnly: "\u25B8 /restore ist nur im Code-Modus verf\xFCgbar",
3502
+ hookUserPromptSubmit: "UserPromptSubmit-Hook",
3503
+ hookStop: "Stop-Hook",
3504
+ atMentions: "\u25B8 @mentions: {parts}",
3505
+ atUrl: "\u25B8 @url: {parts}",
3506
+ atUrlFailed: "@url Erweiterung fehlgeschlagen",
3507
+ sessionTitleNoSession: "\u25B8 Keine persistierte Sitzung aktiv, also nichts umzubenennen.",
3508
+ sessionTitleNoContent: "\u25B8 Noch nicht genug Gespr\xE4chsinhalt, um diese Sitzung zu benennen.",
3509
+ sessionTitleNoTitle: "\u25B8 Das Modell hat keinen brauchbaren Sitzungstitel zur\xFCckgegeben.",
3510
+ sessionTitleUpdated: '\u25B8 Sitzungstitel aktualisiert: "{title}"',
3511
+ sessionTitleRenameFailed: '\u25B8 Sitzung konnte nicht f\xFCr Titel "{title}" umbenannt werden.',
3512
+ sessionTitleRenamed: '\u25B8 Sitzung umbenannt in "{name}" \u2014 {title}',
3513
+ sessionTitleAutoRenamed: '\u25B8 Automatisch benannte Sitzung "{name}" \u2014 {title}',
3514
+ workspaceSwitched: "\u25B8 Arbeitsbereich gewechselt zu {root}",
3515
+ semanticRepointed: "\u25B8 Semantic-Search umgeleitet nach {root}",
3516
+ semanticDisabledForRoot: "\u25B8 Semantic-Search deaktiviert (kein kompatibler Index in {root})",
3517
+ semanticRebootstrapFailed: "\u25B8 Semantic-Search-Neustart fehlgeschlagen: {reason}",
3518
+ denied: "\u25B8 verweigert: {cmd}{context}",
3519
+ alwaysAllowed: '\u25B8 "{prefix}" f\xFCr {dir} dauerhaft erlaubt',
3520
+ runningCommand: "\u25B8 f\xFChre aus: {cmd}",
3521
+ startingBackground: "\u25B8 starte (Hintergrund): {cmd}",
3522
+ checkpointSaved: "\u26C1 Checkpoint gespeichert \xB7 {id} \xB7 {count} Datei{en} \xB7 /restore {id} zum Zur\xFCcksetzen",
3523
+ continuingAfter: "\u25B8 fortgesetzen nach {label}{counter}",
3524
+ planStoppedAt: "\u25B8 Plan angehalten bei {label}{counter}",
3525
+ revisingAfter: "\u25B8 \xFCberarbeite nach {label} \u2014 {feedback}",
3526
+ historyScrollHint: " \u2191 lese Verlauf \xB7 Ende / Bild\u2193 zur\xFCck zum Ende \xB7 \u2193 eine Zeile vor",
3527
+ editHistoryTitle: "Edit-Verlauf (\xE4lteste zuerst):",
3528
+ editHistoryNoCodeMode: "Nicht im Code-Modus",
3529
+ editHistoryNoEdits: "Noch keine Edits in dieser Sitzung aufgezeichnet",
3530
+ editHistoryNoShowId: "Verwendung: /show [id] [pfad] (ID weglassen f\xFCr neueste; Pfad aus der Datei-Zusammenfassung)",
3531
+ editHistoryIdNotFound: "Kein Edit #{id} \u2014 /history ausf\xFChren f\xFCr g\xFCltige IDs",
3532
+ editHistoryLookupFailed: "Unerwartet: History-Lookup fehlgeschlagen",
3533
+ editHistoryBatchNoFile: 'Batch #{id} enth\xE4lt kein "{path}" \u2014 Dateien in diesem Batch: {files}',
3534
+ editHistoryNoEdits2: "Keine Edits in dieser Sitzung aufgezeichnet \u2014 /history ist leer",
3535
+ editHistoryStatusApplied: "angewandt",
3536
+ editHistoryStatusPartial: "TEILWEISE",
3537
+ editHistoryStatusUndone: "R\xDCCKG\xC4NGIG",
3538
+ editHistoryHelpShow: "/show <id> \u2192 Zusammenfassung pro Datei \xB7 /show <id> <pfad> \u2192 vollst\xE4ndige Diff einer Datei",
3539
+ editHistoryHelpUndo: "/undo \u2192 neueste nicht-r\xFCckg\xE4ngige \xB7 /undo <id> [pfad] \u2192 gezielten Batch oder Datei r\xFCckg\xE4ngig machen",
3540
+ editHistoryAlreadyReverted: "(bereits r\xFCckg\xE4ngig gemacht \u2014 /history zeigt den batch-level Status)",
3541
+ editHistoryRevertFile: "/undo {id} {path} \u2192 nur diese Datei r\xFCckg\xE4ngig machen",
3542
+ mcpFailed: "MCP {name} fehlgeschlagen",
3543
+ mcpWarn: "MCP {name} Warnung",
3544
+ unknownTheme: "Unbekanntes Theme: {name}\nVerf\xFCgbar: {choices}",
3545
+ themeSaved: "Theme gespeichert: {name}\nAktiv beim n\xE4chsten Start: {active}",
3546
+ noPendingEdits: "Nichts ausstehend \u2014 das Modell hat seit dem letzten /apply oder /discard keine Edits vorgeschlagen.",
3547
+ noMatchedApply: "\u25B8 Keine Edits mit diesen Indizes gefunden \u2014 nichts angewandt. Verwende /apply ohne Argumente, um alle zu \xFCbernehmen.",
3548
+ noPendingDiscard: "Nichts ausstehend zum Verwerfen.",
3549
+ noMatchedDiscard: "\u25B8 Keine Edits mit diesen Indizes gefunden \u2014 nichts verworfen.",
3550
+ blocksStillPending: "\u25B8 {count} Edit-Block(s) noch ausstehend \u2014 /apply oder /discard zum Bereinigen.",
3551
+ nothingWritten: ". Nichts auf Platte geschrieben.",
3552
+ discardedCount: "\u25B8 {count} ausstehende Edit-Block(s) verworfen",
3553
+ noEventsFor: 'Keine Ereignisse f\xFCr Sitzung "{name}"',
3554
+ lookedAtFile: "Angesehen: {path}",
3555
+ sidecarHint: "(Sitzungen erstellen den Sidecar automatisch beim ersten Turn \u2014 wurde diese Sitzung bereits ausgef\xFChrt?)"
3556
+ },
3557
+ hooks: {
3558
+ ...EN.hooks,
3559
+ head: "Hook {tag} `{cmd}` {decision}{truncTag}",
3560
+ headWithDetail: "Hook {tag} `{cmd}` {decision}{truncTag}: {detail}",
3561
+ truncated: " (Ausgabe bei 256 KB gek\xFCrzt)",
3562
+ decisionBlock: "blockieren",
3563
+ decisionWarn: "warnen",
3564
+ decisionTimeout: "Timeout",
3565
+ decisionError: "Fehler"
3566
+ },
3567
+ summary: {
3568
+ ...EN.summary,
3569
+ status: "Zusammenfassung der gesammelten Informationen...",
3570
+ hallucinatedFallback: "(Modell hat gef\xE4lschte Tool-Call-Markup statt einer Prosa-Zusammenfassung ausgegeben \u2014 versuche /retry mit einer engeren Frage, oder /think zur Inspektion von R1s Reasoning)",
3571
+ failedAfterReason: "{label} und der Fallback-Summary-Aufruf sind fehlgeschlagen: {message}. F\xFChre /clear aus und versuche es mit einer engeren Frage, oder erh\xF6he --max-tool-iters."
3572
+ },
3573
+ loop: {
3574
+ ...EN.loop,
3575
+ budgetExhausted: "Sitzungsbudget ersch\xF6pft \u2014 ${spent} ausgegeben \u2265 Grenze ${cap}. Erh\xF6he die Grenze mit /budget <usd>, schalte sie mit /budget off aus oder beende die Sitzung.",
3576
+ budget80Pct: "\u25B2 Budget zu 80 % verbraucht \u2014 ${spent} von ${cap}. Der n\xE4chste oder \xFCbern\xE4chste Turn erreicht wahrscheinlich die Grenze.",
3577
+ proArmed: "\u21E7 /pro aktiviert \u2014 dieser Turn l\xE4uft auf deepseek-v4-pro (einmalig \xB7 deaktiviert nach dem Turn)",
3578
+ toolUploadStatus: "Tool-Ergebnis hochgeladen \u2013 Modell denkt vor der n\xE4chsten Antwort...",
3579
+ turnStartFoldStatus: "Turn-Start: Kontext n\xE4hert sich Grenze, komprimiere Verlauf...",
3580
+ turnStartFolded: "Turn-Start: Anfrage ~{estimate}/{ctxMax} Tokens ({pct}%) \u2014 {beforeMessages} Nachrichten \u2192 {afterMessages} komprimiert. Sende.",
3581
+ harvestStatus: "Planstatus wird aus dem Reasoning extrahiert...",
3582
+ repeatToolCallWarning: "Wiederholten Tool-Aufruf erkannt \u2014 lasse das Modell das Problem sehen und es mit einem anderen Ansatz erneut versuchen.",
3583
+ stormStuck: "Festgefahrene Wiederholungsschleife gestoppt \u2014 das Modell rief dasselbe Tool mit identischen Argumenten auf, selbst nach einem Selbstkorrektur-Hinweis. Versuche /retry, umformulieren oder schlie\xDFe den zugrunde liegenden Blocker aus.",
3584
+ stormSuppressed: "{count} wiederholte Tool-Aufrufe unterdr\xFCckt \u2014 gleicher Name + Argumente 3+ Mal gesendet.",
3585
+ compactingHistoryStatus: "Komprimiere Verlauf{aggressiveTag}...",
3586
+ aggressiveTag: " (aggressiv)",
3587
+ foldedHistory: "Kontext {before}/{ctxMax} ({pct}%) \u2014 {beforeMessages} Nachrichten \u2192 {afterMessages} gefaltet (Zusammenfassung {summaryChars} Zeichen). Fahre fort.",
3588
+ aggressivelyFoldedHistory: "Kontext {before}/{ctxMax} ({pct}%) \u2014 {beforeMessages} Nachrichten \u2192 {afterMessages} aggressiv gefaltet (Zusammenfassung {summaryChars} Zeichen). Fahre fort.",
3589
+ forcingSummary: "Kontext {before}/{ctxMax} ({pct}%) \u2014 erzwinge Zusammenfassung aus dem Gesammelten. F\xFChre /compact, /clear oder /new aus, um zur\xFCckzusetzen."
3590
+ },
3591
+ errors: {
3592
+ ...EN.errors,
3593
+ contextOverflow: "Context-\xDCberlauf (DeepSeek 400): Sitzungsverlauf ist {requested}, \xFCber dem Prompt-Limit des Modells (V4: 1M Tokens; legacy chat/reasoner: 131k). Meist ist ein einzelnes Tool-Ergebnis zu gro\xDF geworden. Reasonix begrenzt neue Tool-Ergebnisse auf 8k Tokens und heilt \xFCberdimensionierte Verl\xE4ufe automatisch beim Sitzungsladen \u2013 ein Neustart behebt es oft. Falls es weiterhin \xFCberl\xE4uft, f\xFChre /new f\xFCr einen frischen Start aus oder \xF6ffne /sessions und dr\xFCcke [d], um diese Sitzung zu l\xF6schen.",
3594
+ contextOverflowTooMany: "Zu viele Tokens",
3595
+ auth401: "Authentifizierung fehlgeschlagen (DeepSeek 401): {inner}. Dein API-Schl\xFCssel wird abgewiesen. Behebe mit `reasonix setup` oder `export DEEPSEEK_API_KEY=sk-...`. Erhalte einen unter https://platform.deepseek.com/api_keys.",
3596
+ balance402: "Kontoguthaben aufgebraucht (DeepSeek 402): {inner}. Lade auf unter https://platform.deepseek.com/top_up \u2014 der Panel-Header zeigt dein Guthaben, sobald es nicht Null ist.",
3597
+ badparam422: "Ung\xFCltiger Parameter (DeepSeek 422): {inner}",
3598
+ badrequest400: "Fehlerhafte Anfrage (DeepSeek 400): {inner}",
3599
+ concurrency429: "DeepSeek-Gleichzeitigkeitslimit erreicht (429): {inner}. Das Konto hat zu viele gleichzeitige Anfragen (Grenze: 500 f\xFCr v4-pro, 2500 f\xFCr v4-flash, summiert \xFCber alle API-Schl\xFCssel des Kontos). Meist l\xE4uft ein weiterer Reasonix-Prozess mit demselben Schl\xFCssel oder ein paralleler Subagent-Fan-out hat \xFCberzogen. Warte einige Sekunden und wiederhole, reduziere die Parallelit\xE4t oder beantrage eine h\xF6here Grenze unter https://platform.deepseek.com.",
3600
+ deepseek5xxHead: "DeepSeek-Dienst nicht verf\xFCgbar ({status}) \u2014 dies ist ein DeepSeek-seitiges Problem, nicht Reasonix. Bereits 4\xD7 mit Backoff wiederholt.",
3601
+ deepseek5xxReachable: " DeepSeek's Haupt-API hat auf unseren Health-Check geantwortet, aber /chat/completions schl\xE4gt fehl \u2014 partieller Ausfall auf ihrer Seite.",
3602
+ deepseek5xxUnreachable: " DeepSeek-API ist von deinem Netzwerk aus nicht erreichbar \u2014 k\xF6nnte ein gr\xF6\xDFerer DS-Ausfall oder ein lokales Netzwerkproblem sein.",
3603
+ deepseek5xxActionNetwork: " Versuche: (1) Netzwerk pr\xFCfen, (2) 30s warten und wiederholen, (3) Statusseite: https://status.deepseek.com.",
3604
+ deepseek5xxActionRetry: " Versuche: (1) 30s warten und wiederholen, (2) /model zum Modellwechsel, (3) Statusseite: https://status.deepseek.com.",
3605
+ upstream5xxHead: "Upstream-Dienst nicht verf\xFCgbar ({status}) bei {host} \u2014 der konfigurierte API-Endpunkt hat einen Serverfehler zur\xFCckgegeben, kein Reasonix-Fehler. Bereits 4\xD7 mit Backoff wiederholt.",
3606
+ upstream5xxActionRetry: " Versuche: (1) Pr\xFCfen, ob der lokale/Proxy-Modell-Server l\xE4uft, (2) warten und wiederholen, (3) /model zum Modellwechsel.",
3607
+ innerNoMessage: "(keine Nachricht)",
3608
+ reasonAborted: "[vom Benutzer abgebrochen (Esc) \u2014 fasse zusammen, was ich bisher gefunden habe]",
3609
+ reasonContextGuard: "[Context-Budget wird knapp \u2014 fasse zusammen, bevor der n\xE4chste Aufruf \xFCberl\xE4uft]",
3610
+ reasonStuck: "[festgefahren bei wiederholtem Tool-Aufruf \u2014 erkl\xE4re, was versucht wurde und was den Fortschritt blockiert]",
3611
+ labelAborted: "Vom Benutzer abgebrochen",
3612
+ labelContextGuard: "Context-Guard ausgel\xF6st (Prompt > 80 % des Fensters)",
3613
+ labelStuck: "Festgefahren (wiederholter Tool-Aufruf durch Storm-Breaker unterdr\xFCckt)"
3614
+ },
3615
+ handlers: {
3616
+ ...EN.handlers,
3617
+ basic: {
3618
+ ...EN.handlers.basic,
3619
+ newInfo: "\u25B8 neues Gespr\xE4ch \u2014 {count} Nachricht(en) aus dem Kontext entfernt. Gleiche Sitzung, frische Grundlage.",
3620
+ newInfoArchived: '\u25B8 neues Gespr\xE4ch \u2014 {count} Nachricht(en) aus dem Kontext entfernt. Vorheriges Transkript als "{archived}" archiviert (sichtbar unter Sitzungen).',
3621
+ newInfoSystemReloaded: " \xB7 REASONIX.md / Projekt-Memory neu geladen (n\xE4chster Turn zahlt einen Cache-Fehler)",
3622
+ helpTitle: "Befehle:",
3623
+ helpShellTitle: "Shell-K\xFCrzel:",
3624
+ helpShell: " !<befehl> <befehl> im Sandbox-Root ausf\xFChren; Ausgabe kommt",
3625
+ helpShellDetail: " in die Konversation, sodass das Modell sie im n\xE4chsten Turn sieht.",
3626
+ helpShellConsent: " Kein Allowlist-Gate \u2014 vom Benutzer getippt = explizite Zustimmung.",
3627
+ helpShellExample: " Beispiel: !git status !ls src/ !npm test",
3628
+ helpShellGateTitle: "Vom Modell aufgerufene Shell-Befehle (pro Aufruf Genehmigung):",
3629
+ helpShellGate: " \u2191\u2193 + \u23CE jeder Aufruf zeigt eine Eingabeaufforderung mit \xBBEinmal erlauben\xAB / \xBBImmer erlauben\xAB",
3630
+ helpShellGateDetail: " / \xBBAblehnen\xAB. W\xE4hle \xBBImmer erlauben\xAB, um diesen genauen",
3631
+ helpShellGatePolicy: " Befehlspr\xE4fix f\xFCr dieses Projekt auf die Whitelist zu setzen. Kein globales Allow-All-Flag.",
3632
+ helpMemoryTitle: "Kurzzeit-Memory:",
3633
+ helpMemoryPin: " #<notiz> <notiz> an <projekt>/REASONIX.md anh\xE4ngen (commitierbar).",
3634
+ helpMemoryPinEx: " Beispiel: #findByEmail muss case-insensitive sein",
3635
+ helpMemoryGlobal: " #g <notiz> <notiz> an ~/.reasonix/REASONIX.md anh\xE4ngen (global, niemals committed).",
3636
+ helpMemoryGlobalEx: " Beispiel: #g immer pnpm, nicht npm verwenden",
3637
+ helpMemoryPinBoth: " Beide werden in jedes zuk\xFCnftige Sitzungs-Pr\xE4fix eingef\xFCgt. Schneller als /memory.",
3638
+ helpMemoryEscape: " Verwende `\\#text`, um ein literales `#text` an das Modell zu senden.",
3639
+ helpFileTitle: "Dateiverweise (Code-Modus):",
3640
+ helpFile: " @pfad/zu/datei Dateiinhalt unter [Referenzierte Dateien] beim Senden einf\xFCgen.",
3641
+ helpFilePicker: " Tippe `@`, um die Auswahl zu \xF6ffnen (\u2191\u2193 navigieren, Tab/Enter ausw\xE4hlen).",
3642
+ helpUrlTitle: "URL-Verweise:",
3643
+ helpUrl: " @https://example.com URL abrufen, HTML entfernen, unter [Referenzierte URLs] einf\xFCgen.",
3644
+ helpUrlCache: " Gleiche URL zweimal in einer Sitzung wird nur einmal abgerufen (In-Mem-Cache).",
3645
+ helpUrlPunct: " Abschluss-Satzzeichen (./,/)) werden automatisch entfernt.",
3646
+ helpSessionsTitle: "Sitzungen (standardm\xE4\xDFig aktiviert, hei\xDFen 'default'):",
3647
+ helpSessionCustom: " reasonix chat --session <name> eine andere benannte Sitzung verwenden",
3648
+ helpSessionNone: " reasonix chat --no-session Persistenz f\xFCr diesen Lauf deaktivieren",
3649
+ retryNone: "Nichts zu wiederholen \u2014 keine vorherige Benutzernachricht im Log dieser Sitzung.",
3650
+ retryInfo: '\u25B8 wiederhole: "{preview}"',
3651
+ loopTuiOnly: "/loop ist nur in der interaktiven TUI verf\xFCgbar (nicht in run/replay).",
3652
+ loopStopped: "\u25B8 Loop gestoppt.",
3653
+ loopNoActive: "Kein aktiver Loop zum Stoppen.",
3654
+ loopNoActiveHint: "kein aktiver Loop. Starte einen mit `/loop <intervall> <prompt>` (z.B. /loop 30s npm test).\nWird abgebrochen bei: /loop stop \xB7 Esc \xB7 /clear /new \xB7 jeder benutzereingegebene Prompt.",
3655
+ loopStarted: '\u25B8 Loop gestartet \u2014 \xBB{prompt}" wird alle {duration} erneut gesendet. Tippe etwas (oder /loop stop) zum Abbrechen.',
3656
+ keysNeedsTui: "/keys ben\xF6tigt einen TUI-Kontext (postKeys angeschlossen).",
3657
+ aboutHeader: "Reasonix v{version} \u2014 ein Cache-First-DeepSeek-Coding-Agent",
3658
+ aboutWebsiteLabel: "Webseite",
3659
+ aboutRepoLabel: "GitHub ",
3660
+ aboutLicenseLabel: "Lizenz",
3661
+ unknownCommand: "Unbekannter Befehl: /{cmd} \u2014 meintest du {list}?",
3662
+ unknownCommandShort: "Unbekannter Befehl: /{cmd} (siehe /help)"
3663
+ },
3664
+ sessions: {
3665
+ ...EN.handlers.sessions,
3666
+ titleUnavailable: "/title ist nur in einer aktiven persistierten TUI-Sitzung verf\xFCgbar.",
3667
+ titleStarted: "\u25B8 benenne Sitzung...",
3668
+ titleFailed: "\u25B8 Sitzungstitel fehlgeschlagen: {reason}"
3669
+ },
3670
+ qq: {
3671
+ ...EN.handlers.qq,
3672
+ unavailable: "/qq ist in dieser Sitzung nicht verf\xFCgbar.",
3673
+ connecting: "QQ: verbinde...",
3674
+ connectFailed: "QQ-Verbindung fehlgeschlagen: {reason}",
3675
+ disconnecting: "QQ: trenne...",
3676
+ disconnectFailed: "QQ-Trennung fehlgeschlagen: {reason}",
3677
+ usage: "Verwendung: /qq connect [appId appSecret [sandbox]] | /qq status | /qq disconnect",
3678
+ promptAppId: "QQ-Setup: gib deine QQ-Open-Platform-App-ID ein, dann Enter. Tippe /cancel zum Abbrechen.",
3679
+ promptAppSecret: "QQ-Setup: gib dein QQ-Open-Platform-App-Secret ein, dann Enter. Tippe /cancel zum Abbrechen.",
3680
+ setupWaitingAppId: "Warte auf App-ID",
3681
+ setupWaitingAppSecret: "Warte auf App-Secret",
3682
+ setupCancelled: "QQ-Setup abgebrochen.",
3683
+ credentialsRequired: "QQ-App-ID und App-Secret sind erforderlich.",
3684
+ connected: "QQ im {mode}-Modus verbunden. Es wird bei zuk\xFCnftigen Starts automatisch gestartet.",
3685
+ alreadyConnected: "QQ ist bereits im {mode}-Modus verbunden. Autostart ist aktiviert.",
3686
+ disconnected: "QQ getrennt. Autostart ist deaktiviert.",
3687
+ status: "QQ: {connected}, Autostart {enabled}, Anmeldedaten {configured}, App-ID {appId}, {sandbox}, Zugriff {access}, aktueller Modus {mode}.",
3688
+ statusSetup: "QQ: Setup l\xE4uft \u2014 {step}",
3689
+ stateConnected: "verbunden",
3690
+ stateDisconnected: "getrennt",
3691
+ stateEnabled: "aktiviert",
3692
+ stateDisabled: "deaktiviert",
3693
+ stateConfigured: "konfiguriert",
3694
+ stateNotConfigured: "Nicht konfiguriert",
3695
+ sandbox: "Sandbox",
3696
+ production: "Produktion",
3697
+ none: "keine",
3698
+ modeChat: "Chat",
3699
+ modeCode: "Code",
3700
+ accessOwner: "Besitzer {owner}",
3701
+ accessOwnerWithAllowlist: "Besitzer {owner}, Allowlist {count}",
3702
+ accessAllowlist: "Allowlist {count}",
3703
+ accessRuntime: "Erstabsender (nur zur Laufzeit, {owner})",
3704
+ accessOpen: "Offen (ungebunden)",
3705
+ lockAlreadyRunning: "QQ-Kanal l\xE4uft bereits in Prozess {pid}. Stoppe diesen Prozess, bevor du einen weiteren QQ-Kanal startest.",
3706
+ unauthorizedMessage: "QQ hat Nachricht von nicht autorisierter OpenID {openid} ignoriert. Aktueller Zugriff: {access}.",
3707
+ runtimeBound: "QQ hat diesen Lauf vor\xFCbergehend an den Erstabsender {openid} gebunden. Setze `qq.ownerOpenId` in der Konfiguration, um den Zugriff dauerhaft zu machen.",
3708
+ missingAppId: "QQ-App-ID erforderlich. F\xFChre `/qq connect` zum Konfigurieren aus.",
3709
+ missingAppSecret: "QQ-App-Secret erforderlich. F\xFChre `/qq connect` zum Konfigurieren aus.",
3710
+ authFailed: "QQ-Bot-Authentifizierung fehlgeschlagen \u2014 \xFCberpr\xFCfe deine App-ID und dein App-Secret.",
3711
+ readyTimeout: "QQ-Bot hat READY nicht innerhalb von 15s erhalten \u2014 \xFCberpr\xFCfe deine App-ID und dein App-Secret."
3712
+ },
3713
+ admin: {
3714
+ ...EN.handlers.admin,
3715
+ doctorNeedsTui: "/doctor ben\xF6tigt einen TUI-Kontext (postDoctor angeschlossen).",
3716
+ doctorRunning: "\u2695 Doctor \u2014 f\xFChre Gesundheitschecks aus...",
3717
+ hooksReloadUnavailable: "/hooks reload ist in diesem Kontext nicht verf\xFCgbar (kein Reload-Callback angeschlossen).",
3718
+ hooksReloaded: "\u25B8 Hooks neu geladen \xB7 {count} aktiv",
3719
+ hooksUsage: "Verwendung: /hooks aktive Hooks auflisten\n /hooks reload settings.json-Dateien neu lesen",
3720
+ hooksNone: "Keine Hooks konfiguriert.",
3721
+ hooksDropHint: "Lege eine settings.json mit einem `hooks`-Schl\xFCssel in einem der folgenden Pfade ab:",
3722
+ hooksProject: " \xB7 {path} (Projekt)",
3723
+ hooksProjectFallback: " \xB7 <projekt>/.reasonix/settings.json (Projekt)",
3724
+ hooksGlobal: " \xB7 {path} (global)",
3725
+ hooksEvents: "Ereignisse: PreToolUse, PostToolUse, UserPromptSubmit, Stop",
3726
+ hooksExitCodes: "Exit 0 = bestanden \xB7 Exit 2 = blockieren (Pre*) \xB7 andere = warnen",
3727
+ hooksLoaded: "\u25B8 {count} Hook(s) geladen",
3728
+ hooksSources: "Quellen: Projekt={project} \xB7 global={global}",
3729
+ updateCurrent: "Aktuell: reasonix {version}",
3730
+ updateLatestPending: "Neueste: (noch nicht aufgel\xF6st \u2014 Hintergrundpr\xFCfung l\xE4uft oder offline)",
3731
+ updateRetryHint: "hat einen frischen Registry-Abruf ausgel\xF6st \u2014 versuche `/update` in ein paar Sekunden erneut,",
3732
+ updateRetryHint2: "oder f\xFChre `reasonix update` in einem anderen Terminal aus, um es synchron zu erzwingen.",
3733
+ updateLatest: "Neueste: reasonix {version}",
3734
+ updateUpToDate: "Du bist auf dem neuesten Stand. Nichts zu tun.",
3735
+ updateNpxHint: "du verwendest npx \u2014 der n\xE4chste `npx reasonix ...`-Start l\xE4dt automatisch die neueste Version.",
3736
+ updateNpxForce: "Um fr\xFCher zu aktualisieren: `npm cache clean --force`.",
3737
+ updateUpgradeHint: "Zum Aktualisieren beende diese Sitzung und f\xFChre aus:",
3738
+ updateUpgradeCmd1: " reasonix update (interaktiv, --dry-run wird unterst\xFCtzt)",
3739
+ updateUpgradeCmd2: " {command} (direkt)",
3740
+ updateInSessionDisabled: "Die Installation innerhalb einer Sitzung ist bewusst deaktiviert \u2014 der Installationsprozess w\xFCrde",
3741
+ updateInSessionDisabled2: "die Darstellung dieser TUI beeintr\xE4chtigen und Windows kann die laufende Bin\xE4rdatei sperren.",
3742
+ statsNoData: "Noch keine Nutzungsdaten.",
3743
+ statsEveryTurn: "jeder hier ausgef\xFChrte Turn h\xE4ngt einen Datensatz an \u2014 die Turns dieser Sitzung",
3744
+ statsWillAppear: "Werden im Dashboard angezeigt, sobald du eine Nachricht sendest."
3745
+ },
3746
+ edits: {
3747
+ ...EN.handlers.edits,
3748
+ undoCodeOnly: "/undo ist nur innerhalb von `reasonix code` verf\xFCgbar \u2014 der Chat-Modus wendet keine Edits an.",
3749
+ historyCodeOnly: "/history ist nur innerhalb von `reasonix code` verf\xFCgbar.",
3750
+ showCodeOnly: "/show ist nur innerhalb von `reasonix code` verf\xFCgbar.",
3751
+ applyCodeOnly: "/apply ist nur innerhalb von `reasonix code` verf\xFCgbar (hier gibt es nichts anzuwenden).",
3752
+ discardCodeOnly: "/discard ist nur innerhalb von `reasonix code` verf\xFCgbar.",
3753
+ planCodeOnly: "/plan ist nur innerhalb von `reasonix code` verf\xFCgbar \u2014 der Chat-Modus blockiert keine Tool-Schreibzugriffe.",
3754
+ planOn: "\u25B8 Plan-Modus EIN \u2014 Schreibwerkzeuge sind blockiert; das Modell MUSS `submit_plan` aufrufen, bevor etwas ausgef\xFChrt wird. (Das Modell kann auch eigenst\xE4ndig submit_plan f\xFCr gro\xDFe Aufgaben aufrufen, selbst wenn der Plan-Modus aus ist \u2014 dieser Schalter ist die strengere, explizite Einschr\xE4nkung.) Tippe /plan off zum Verlassen.",
3755
+ planOff: "\u25B8 Plan-Modus AUS \u2014 Schreibwerkzeuge sind wieder aktiv. Modelle k\xF6nnen weiterhin eigenst\xE4ndig Pl\xE4ne f\xFCr gro\xDFe Aufgaben vorschlagen.",
3756
+ modeCodeOnly: "/mode ist nur innerhalb von `reasonix code` verf\xFCgbar.",
3757
+ modeUsage: "Verwendung: /mode <review|auto|yolo> (Shift+Tab schaltet auch um)",
3758
+ modeYolo: "\u25B8 Edit-Modus: YOLO \u2014 Edits UND Shell-Befehle auto-ausf\xFChren ohne Nachfrage. /undo macht Edits immer noch r\xFCckg\xE4ngig. Vorsicht.",
3759
+ modeAuto: "\u25B8 Edit-Modus: AUTO \u2014 Edits werden sofort angewandt; dr\xFCcke u innerhalb von 5s zum R\xFCckg\xE4ngigmachen, oder /undo sp\xE4ter. Shell-Befehle fragen weiterhin.",
3760
+ modeReview: "\u25B8 Edit-Modus: review \u2014 Edits warten auf /apply (oder y) / /discard (oder n)",
3761
+ commitCodeOnly: "/commit ist nur innerhalb von `reasonix code` verf\xFCgbar (ben\xF6tigt ein Git-Repo als Wurzel).",
3762
+ commitUsage: 'Verwendung: /commit "deine Commit-Nachricht" \u2014 f\xFChrt `git add -A && git commit -m "\u2026"` in {root} aus',
3763
+ walkCodeOnly: "/walk ist nur innerhalb von `reasonix code` verf\xFCgbar.",
3764
+ checkpointCodeOnly: "/checkpoint ist nur innerhalb von `reasonix code` verf\xFCgbar \u2014 der Chat-Modus wendet keine Edits an.",
3765
+ checkpointNone: "noch keine Checkpoints \u2014 `/checkpoint <name>` sichert jede Datei, die die Sitzung ber\xFChrt hat. Sp\xE4ter mit `/restore <name>` wiederherstellbar.",
3766
+ checkpointHeader: "\u25C8 Checkpoints \xB7 {count} gespeichert",
3767
+ checkpointRestoreHint: " /restore <name|id> \xB7 /checkpoint forget <id> \xB7 /checkpoint <name> zum Hinzuf\xFCgen",
3768
+ checkpointForgetUsage: "Verwendung: /checkpoint forget <id|name>",
3769
+ checkpointNoMatch: '\u25B8 kein Checkpoint gefunden f\xFCr "{name}" \u2014 siehe /checkpoint list',
3770
+ checkpointDeleted: "\u25B8 Checkpoint {id} gel\xF6scht ({name})",
3771
+ checkpointDeleteFailed: "\u25B8 Konnte {id} nicht l\xF6schen (bereits entfernt?)",
3772
+ checkpointSaveUsage: "Verwendung: /checkpoint <name> (oder /checkpoint list zum Anzeigen vorhandener)",
3773
+ checkpointSavedEmpty: '\u25B8 Checkpoint "{name}" gespeichert ({id}) \u2014 aber es wurden noch keine Dateien ber\xFChrt, daher ist es eine leere Basislinie. Nach diesem Punkt vorgenommene Edits k\xF6nnen r\xFCckg\xE4ngig gemacht werden.',
3774
+ checkpointSaved: '\u25B8 Checkpoint "{name}" gespeichert ({id}) \u2014 {files} Datei{en}, {size} KB. Wiederherstellen: /restore {name}',
3775
+ restoreCodeOnly: "/restore ist nur innerhalb von `reasonix code` verf\xFCgbar.",
3776
+ restoreUsage: "Verwendung: /restore <name|id> (siehe /checkpoint list f\xFCr IDs)",
3777
+ restoreNoMatch: '\u25B8 kein Checkpoint gefunden f\xFCr "{target}" \u2014 versuche /checkpoint list',
3778
+ restoreInfo: '\u25B8 "{name}" ({id}) wiederhergestellt von {when}',
3779
+ restoreWrote: " \xB7 {count} Datei{en} zur\xFCckgeschrieben",
3780
+ restoreRemoved: " \xB7 {count} Datei{en} entfernt (existierten zum Checkpoint-Zeitpunkt nicht)",
3781
+ restoreSkipped: " \u2717 {count} Datei{en} \xFCbersprungen:",
3782
+ cwdCodeOnly: "/cwd ist nur innerhalb von `reasonix code` verf\xFCgbar.",
3783
+ cwdUsage: "Verwendung: /cwd <pfad> (aktuelles Root: {current}). Richtet Dateisystem-/Shell-/Memory-Tools auf <pfad> neu aus.",
3784
+ cwdUsageNoCurrent: "Verwendung: /cwd <pfad> richtet den Workspace-Root auf <pfad> neu aus."
3785
+ },
3786
+ model: {
3787
+ ...EN.handlers.model,
3788
+ modelHint: "versuche deepseek-v4-flash oder deepseek-v4-pro \u2014 f\xFChre /models aus, um die Live-Liste abzurufen",
3789
+ modelUsage: "Verwendung: /model <id> ({hint})",
3790
+ modelNotInCatalog: "Modell \u2192 {id} (\u26A0 nicht im abgerufenen Katalog: {list}. Falls das falsch ist, wird der n\xE4chste Aufruf 400 geben \u2014 f\xFChre /models zum Aktualisieren aus.)",
3791
+ modelSet: "Modell \u2192 {id}",
3792
+ effortStatus: "Effort \u2192 {current} (Auswahl: {list})",
3793
+ effortUsage: "Verwendung: /effort <{list}> (high ist der sichere Standard; max ist eine DeepSeek-Erweiterung)",
3794
+ effortUsageNoMax: "Verwendung: /effort <{list}>",
3795
+ effortSet: "Effort \u2192 {effort}",
3796
+ budgetNoCap: "Kein Sitzungsbudget festgelegt \u2014 Reasonix wird weiterlaufen, bis du es stoppst. Setze eines mit: /budget <usd> (z.B. /budget 5)",
3797
+ budgetStatus: "Budget: ${spent} von ${cap} ({pct}%) \xB7 /budget off zum Entfernen, /budget <usd> zum \xC4ndern",
3798
+ budgetOff: "Budget \u2192 aus (keine Grenze)",
3799
+ budgetUsage: 'Verwendung: /budget <usd> (erhalten: "{arg}" \u2014 muss eine positive Zahl sein, z.B. /budget 5 oder /budget 12.50)',
3800
+ budgetExhausted: "\u25B2 Budget \u2192 ${cap} aber bereits ${spent} ausgegeben. Der n\xE4chste Turn wird verweigert \u2014 erh\xF6he die Grenze, um fortzufahren, oder beende die Sitzung.",
3801
+ budgetSet: "Budget \u2192 ${cap} (bisher: ${spent} \xB7 warnt bei 80 %, verweigert n\xE4chsten Turn bei 100 % \xB7 /budget off zum Entfernen)"
3802
+ },
3803
+ permissions: {
3804
+ ...EN.handlers.permissions,
3805
+ mutateCodeOnly: "/permissions add / remove / clear sind nur innerhalb von `reasonix code` verf\xFCgbar \u2014 sie bearbeiten die projektbezogene Allowlist (`~/.reasonix/config.json` projects[<root>].shellAllowed).",
3806
+ addUsage: 'Verwendung: /permissions add <pr\xE4fix> (mehrere Tokens OK: /permissions add "git push origin")',
3807
+ addAlready: "\u25B8 bereits erlaubt: {prefix}",
3808
+ addBuiltin: "\u25B8 `{prefix}` ist bereits in der Builtin-Allowlist \u2014 kein projektspezifischer Eintrag n\xF6tig. (Builtin-Eintr\xE4ge sind immer aktiv.)",
3809
+ addInfo: "\u25B8 hinzugef\xFCgt: {prefix}\n \u2192 n\xE4chste `{prefix}`-Ausf\xFChrung erfolgt ohne Nachfrage in diesem Projekt.",
3810
+ removeUsage: "Verwendung: /permissions remove <pr\xE4fix-oder-index> (z.B. /permissions remove 3, oder /permissions remove npm)",
3811
+ removeEmpty: "\u25B8 keine Projekt-Allowlist-Eintr\xE4ge zum Entfernen.",
3812
+ removeIndexOob: "\u25B8 Index au\xDFerhalb des Bereichs: {idx} (Projektliste hat {count} Eintr\xE4ge)",
3813
+ removeNothing: "\u25B8 nichts zu entfernen.",
3814
+ removeBuiltin: "\u25B8 `{prefix}` ist in der Builtin-Allowlist (schreibgesch\xFCtzt). Builtin-Eintr\xE4ge k\xF6nnen zur Laufzeit nicht entfernt werden \u2014 sie sind in die Bin\xE4rdatei eingebrannt.",
3815
+ removeInfo: "\u25B8 entfernt: {prefix}",
3816
+ removeNotFound: "\u25B8 kein solcher Projekt-Eintrag: {prefix} (versuche /permissions list, um zu sehen, was gespeichert ist)",
3817
+ clearAlready: "\u25B8 Projekt-Allowlist ist bereits leer.",
3818
+ clearConfirm: "Es werden {count} Projekt-Allowlist-Eintr\xE4g{e} f\xFCr {root} gel\xF6scht. F\xFChre den Befehl mit dem Wort 'confirm' erneut aus: /permissions clear confirm",
3819
+ clearedNone: "\u25B8 Projekt-Allowlist war bereits leer \u2014 nichts ge\xE4ndert.",
3820
+ cleared: "\u25B8 {count} Projekt-Allowlist-Eintr\xE4g{e} gel\xF6scht.",
3821
+ usage: 'Verwendung: /permissions [list] aktuellen Status anzeigen\n /permissions add <pr\xE4fix> speichern (z.B. "npm run build")\n /permissions remove <pr\xE4fix-oder-N> Eintrag entfernen\n /permissions clear confirm alle Projekteintr\xE4ge l\xF6schen',
3822
+ modeYolo: "\u25B8 Edit-Modus: YOLO \u2014 jeder Shell-Befehl l\xE4uft automatisch, Allowlist wird umgangen. /mode review zum Reaktivieren der Nachfragen.",
3823
+ modeAuto: "\u25B8 Edit-Modus: auto \u2014 Edits auto-anwenden, Shell weiterhin durch Allowlist gesch\xFCtzt (oder ShellConfirm-Nachfrage bei nicht-allowlisteten).",
3824
+ modeReview: "\u25B8 Edit-Modus: review \u2014 sowohl Edits als auch nicht-allowlistete Shell-Befehle fragen vor der Ausf\xFChrung.",
3825
+ projectHeader: "Projekt-Allowlist ({count}) \u2014 {root}",
3826
+ projectNone1: ' (keine \u2014 w\xE4hle \xBBimmer erlauben" in einer ShellConfirm-Eingabeaufforderung, um einen hinzuzuf\xFCgen,',
3827
+ projectNone2: " oder `/permissions add <pr\xE4fix>` direkt.)",
3828
+ projectNoRoot: "Projekt-Allowlist \u2014 (kein Projekt-Root; Chat-Modus zeigt nur Builtin-Eintr\xE4ge)",
3829
+ builtinHeader: "Builtin-Allowlist ({count}) \u2014 schreibgesch\xFCtzt, fest eincompiliert",
3830
+ subcommands: "Unterbefehle: /permissions add <pr\xE4fix> \xB7 /permissions remove <pr\xE4fix-oder-N> \xB7 /permissions clear confirm"
3831
+ },
3832
+ dashboard: {
3833
+ ...EN.handlers.dashboard,
3834
+ notAvailable: "/dashboard ist in diesem Kontext nicht verf\xFCgbar (kein startDashboard-Callback angeschlossen).",
3835
+ stopNoCallback: "/dashboard stop: kein Stop-Callback angeschlossen.",
3836
+ notRunning: "\u25B8 Dashboard l\xE4uft nicht.",
3837
+ stopping: "\u25B8 Dashboard wird gestoppt...",
3838
+ alreadyRunning: "\u25B8 Dashboard l\xE4uft bereits:",
3839
+ alreadyRunningHint: "\xD6ffne es in einem beliebigen Browser. Tippe `/dashboard stop` zum Herunterfahren.",
3840
+ ready: "\u25B8 Dashboard bereit:",
3841
+ readyHint: "127.0.0.1 only \xB7 token-gesichert. Tippe `/dashboard stop` zum Herunterfahren.",
3842
+ failed: "\u25B8 Dashboard konnte nicht gestartet werden: {reason}",
3843
+ starting: "\u25B8 starte Dashboard-Server...",
3844
+ copied: "\u25B8 Dashboard-URL in Zwischenablage kopiert: {url}",
3845
+ tokenResetting: "\u25B8 rotiere Dashboard-Token \u2014 starte Server neu...",
3846
+ tokenReset: "\u25B8 Dashboard-Token rotiert. Neue URL:"
3847
+ },
3848
+ observability: {
3849
+ ...EN.handlers.observability,
3850
+ contextInfo: "Kontext: ~{total} von {max} ({pct}%) \xB7 System {sys} \xB7 Tools {tools} \xB7 Log {log}",
3851
+ compactStarting: "\u25B8 falte \xE4ltere Turns in eine Zusammenfassung...",
3852
+ compactNoop: "\u25B8 nichts zu falten \u2014 Log bereits klein oder aktuelle Turns allein \xFCberschreiten das Budget.",
3853
+ compactDone: "\u25B8 {before} Nachrichten \u2192 {after} gefaltet (Zusammenfassung {chars} Zeichen). Fahre fort.",
3854
+ compactFailed: "\u25B8 Falten fehlgeschlagen: {reason}",
3855
+ costNoTurn: "noch kein Turn \u2014 `/cost` zeigt die Token- und Kostenaufschl\xFCsselung des letzten Turns.",
3856
+ costNeedsTui: "/cost ben\xF6tigt einen TUI-Kontext (postUsage angeschlossen).",
3857
+ costNoPricing: '\u25B8 /cost: keine Preistabelle f\xFCr Modell "{model}". F\xFCge eine in telemetry/stats.ts hinzu.',
3858
+ costEstimate: "\u25B8 /cost Sch\xE4tzung \xB7 {model} \xB7 {prompt} Prompt-Tokens (sys {sys} + tools {tools} + log {log} + msg {msg})",
3859
+ costWorstCase: " schlimmster Fall (vollst\xE4ndiger Fehlschlag): {input} Eingabe + ~{output} Ausgabe ({avg} \xD8) \u2248 {total}",
3860
+ costLikely: " wahrscheinlich ({pct}% Session-Cache-Treffer): {input} Eingabe + ~{output} Ausgabe \u2248 {total}",
3861
+ costLikelyCold: " wahrscheinlich: entspricht worst case bis der Cache gef\xFCllt ist (noch keine abgeschlossenen Turns)",
3862
+ statusModel: " Modell {model}",
3863
+ statusFlags: " Flags stream={stream} \xB7 effort={effort}",
3864
+ statusCtx: " Kontext {bar} {used}/{max} ({pct}%)",
3865
+ statusCtxNone: " Kontext noch keine Turns",
3866
+ statusCost: " Kosten ${cost} \xB7 Cache {bar} {pct}% \xB7 Turns {turns}",
3867
+ statusCostCold: " Kosten ${cost} \xB7 Turns {turns} (Cache w\xE4rmt sich auf)",
3868
+ statusBudget: " Budget ${spent} / ${cap} ({pct}%){tag}",
3869
+ statusSession: ' Sitzung "{name}" \xB7 {count} Nachrichten im Log (fortgesetzt {resumed})',
3870
+ statusSessionEphemeral: " Sitzung (ephemer \u2014 keine Persistenz)",
3871
+ statusWorkspace: " Arbeitsbereich {path} \xB7 beim Start festgelegt (mit --dir <pfad> neu starten zum Wechseln)",
3872
+ statusMcp: " MCP {servers} Server, {tools} Tools im Register",
3873
+ statusEdits: " Edits {count} ausstehend (/apply zum \xDCbernehmen, /discard zum Verwerfen)",
3874
+ statusPlan: " Plan EIN \u2014 Schreibzugriffe blockiert (submit_plan + Genehmigung)",
3875
+ statusLifecycle: " Lebenszyklus {mode}/{state} \xB7 {progress}{evidence}",
3876
+ lifecycleNoPlan: "Kein Plan",
3877
+ lifecycleEvidencePending: "Nachweis ausstehend",
3878
+ lifecycleRejected: "Lebenszyklus: {tool} blockiert in {state} \u2014 n\xE4chster: {next}",
3879
+ lifecycleEvidenceRejected: "Lebenszyklus: Schritt {stepId} ben\xF6tigt Nachweis \u2014 n\xE4chster: {next}",
3880
+ lifecycleRepeatedRejected: "Lebenszyklus: wiederholte {tool}-Ablehnung \u2014 wiederhole nicht identische Argumente",
3881
+ statusModeYolo: " Modus YOLO \u2014 Edits + Shell auto-ausf\xFChren ohne Nachfrage (/undo macht immer noch r\xFCckg\xE4ngig \xB7 Shift+Tab zum Umschalten)",
3882
+ statusModeAuto: " Modus AUTO \u2014 Edits werden sofort angewandt (u zum R\xFCckg\xE4ngigmachen innerhalb von 5s \xB7 Shift+Tab zum Umschalten)",
3883
+ statusModeReview: " Modus review \u2014 Edits warten auf /apply oder y (Shift+Tab zum Umschalten)",
3884
+ statusDash: " Dash {url} (im Browser \xF6ffnen \xB7 /dashboard stop)"
3885
+ },
3886
+ plans: {
3887
+ ...EN.handlers.plans,
3888
+ noSession: "keine Sitzung angeh\xE4ngt \u2014 `/plans` ist pro Sitzung. F\xFChre `reasonix code` in einem Projekt aus, um eine Sitzung zu erhalten.",
3889
+ activePlan: "\u25B8 aktiver Plan{label} \u2014 {done}/{total} Schritt{e} erledigt \xB7 zuletzt bearbeitet {when}",
3890
+ activeNone: "\u25B8 aktiver Plan: (keiner)",
3891
+ noArchives: "noch keine archivierten Pl\xE4ne f\xFCr diese Sitzung \u2014 sie werden automatisch archiviert, wenn alle Schritte erledigt sind",
3892
+ archivedHeader: "Archiviert ({count}):",
3893
+ evidencePending: " ! Nachweis ausstehend \u2014 aktueller Schritt ben\xF6tigt Verifikation/Diff/Checkpoint/manuellen Nachweis",
3894
+ evidenceLine: " Nachweis {stepId}: {summary}",
3895
+ archivedEvidenceLine: " Nachweis: {summary}",
3896
+ replayNoSession: "keine Sitzung angeh\xE4ngt \u2014 `/replay` ist pro Sitzung. F\xFChre `reasonix code` in einem Projekt aus, um eine Sitzung zu erhalten.",
3897
+ replayNoArchives: "noch keine archivierten Pl\xE4ne f\xFCr diese Sitzung \u2014 `/replay` wird aktiv, sobald ein Plan abgeschlossen ist (auto-archiviert wenn alle Schritte erledigt).",
3898
+ replayInvalidIndex: "ung\xFCltiger Index \u2014 `/replay` akzeptiert 1..{max} (neuester = 1). Verwende `/plans`, um die Liste zu sehen.",
3899
+ archivedRow: " \u2713 {when} {total} Schritt{e} \xB7 {completion} {label}",
3900
+ completionComplete: "abgeschlossen",
3901
+ stopAborted: "\u25B8 Plan gestoppt \u2014 Modell abgebrochen; tippe eine Folgenachricht, um fortzufahren oder eine neue Aufgabe zu starten.",
3902
+ doneUsage: "Verwendung: /plans done <stepId> \xB7 /plans done all \u2014 manuelle \xDCberschreibung, wenn das Modell vergessen hat, mark_step_complete aufzurufen",
3903
+ doneUnavailable: "/plans done ist nur innerhalb einer aktiven Sitzung verf\xFCgbar.",
3904
+ doneNoPlan: "Kein aktiver Plan \u2014 nichts als erledigt zu markieren.",
3905
+ doneNotInPlan: "Schritt `{id}` ist nicht im aktiven Plan. F\xFChre /plans aus, um die Schritt-IDs zu sehen.",
3906
+ doneAlready: "Schritt `{id}` wurde bereits als erledigt markiert.",
3907
+ doneOk: "\u25B8 Schritt `{id}` als erledigt markiert.",
3908
+ doneAllNoop: "Jeder Schritt ist bereits erledigt.",
3909
+ doneAllOk: "\u25B8 {count} Schritt(e) als erledigt markiert."
3910
+ },
3911
+ jobs: {
3912
+ ...EN.handlers.jobs,
3913
+ codeOnly: "/jobs ist nur innerhalb von `reasonix code` verf\xFCgbar.",
3914
+ killCodeOnly: "/kill ist nur innerhalb von `reasonix code` verf\xFCgbar.",
3915
+ logsCodeOnly: "/logs ist nur innerhalb von `reasonix code` verf\xFCgbar.",
3916
+ empty: "\u25C8 Jobs \xB7 0 laufend \xB7 0 gesamt\n (run_background startet einen \u2014 Dev-Server, Watcher, langlebige Skripte)",
3917
+ header: "\u25C8 Jobs \xB7 {running} laufend \xB7 {total} gesamt",
3918
+ footer: " /logs <id> tail \xB7 /kill <id> SIGTERM \u2192 SIGKILL",
3919
+ killUsage: "Verwendung: /kill <id> (siehe /jobs f\xFCr IDs)",
3920
+ killNotFound: "Job {id}: nicht gefunden",
3921
+ killAlreadyExited: "Job {id} bereits beendet ({code})",
3922
+ killStopping: "\u25B8 stoppe Job {id} (Tree-Kill: SIGTERM \u2192 SIGKILL nach 2s Gnadenfrist; Windows: taskkill /T /F)",
3923
+ killStatus: "\u25B8 Job {id} {status}",
3924
+ killStillAlive: "Nach SIGKILL noch am Leben (!) \u2014 melde dies als Fehler",
3925
+ logsUsage: "Verwendung: /logs <id> [zeilen] (Standard letzte 80 Zeilen)",
3926
+ logsNotFound: "Job {id}: nicht gefunden",
3927
+ logsStatus: "[Job {id} \xB7 {status}]\n$ {command}",
3928
+ logsRunning: "L\xE4uft \xB7 PID {pid}",
3929
+ logsExited: "Beendet {code}",
3930
+ logsFailed: "Fehlgeschlagen ({reason})",
3931
+ logsStopped: "gestoppt"
3932
+ },
3933
+ memory: {
3934
+ ...EN.handlers.memory,
3935
+ disabled: "Memory ist deaktiviert (REASONIX_MEMORY=off in der Umgebung). Entferne die Variable zur Reaktivierung \u2014 es werden weder REASONIX.md noch ~/.reasonix/memory-Inhalte eingef\xFCgt.",
3936
+ noRoot: "kein Arbeitsverzeichnis in dieser Sitzung \u2014 `/memory` ben\xF6tigt ein Root, um REASONIX.md aufzul\xF6sen. (L\xE4uft in einer Test-Umgebung?)",
3937
+ listEmpty: "noch keine Benutzer-Memories. Das Modell kann `remember` aufrufen, um einen zu speichern, oder du kannst Dateien manuell in ~/.reasonix/memory/global/ oder dem projektspezifischen Unterverzeichnis erstellen.",
3938
+ listHeader: "Benutzer-Memories ({count}):",
3939
+ listFooter: "Body anzeigen: /memory show <name> L\xF6schen: /memory forget <name>",
3940
+ showUsage: "Verwendung: /memory show <name> oder /memory show <scope>/<name>",
3941
+ showNotFound: "Kein Memory gefunden: {target}",
3942
+ showFailed: "Anzeige fehlgeschlagen: {reason}",
3943
+ forgetUsage: "Verwendung: /memory forget <name> oder /memory forget <scope>/<name>",
3944
+ forgetNotFound: "Kein Memory gefunden: {target}",
3945
+ forgetInfo: "\u25B8 {scope}/{name} entfernt. N\xE4chstes /new oder der n\xE4chste Start wird es nicht mehr sehen.",
3946
+ forgetFailed: "Konnte {scope}/{name} nicht entfernen (bereits weg?)",
3947
+ forgetError: "Entfernen fehlgeschlagen: {reason}",
3948
+ clearUsage: "Verwendung: /memory clear <global|project> confirm",
3949
+ clearConfirm: "Alle Memories im Bereich {scope} werden gel\xF6scht. F\xFChre den Befehl mit dem Wort 'confirm' erneut aus: /memory clear {scope} confirm",
3950
+ cleared: "\u25B8 Bereich {scope} geleert \u2014 {count} Memory-Datei(en) gel\xF6scht.",
3951
+ noMemory: "Kein Memory in {root} eingef\xFCgt.",
3952
+ layers: "Drei Ebenen sind verf\xFCgbar:",
3953
+ layerProject: " 1. {file} \u2014 commitierbares Team-Memory (im Repo).",
3954
+ layerGlobal: " 2. ~/.reasonix/memory/global/ \u2014 dein projekt\xFCbergreifendes privates Memory.",
3955
+ layerProjectHash: " 3. ~/.reasonix/memory/<projekt-hash>/ \u2014 privates Memory dieses Projekts.",
3956
+ askModel: "Bitte das Modell, etwas zu `remember`, oder bearbeite die Dateien direkt.",
3957
+ changesNote: "\xC4nderungen werden beim n\xE4chsten /new oder Start wirksam \u2014 der System-Prompt wird einmal pro Sitzung gehasht, um den Prefix-Cache warm zu halten.",
3958
+ subcommands: "Unterbefehle: /memory list | /memory show <name> | /memory forget <name> | /memory clear <scope> confirm",
3959
+ changesNoteShort: "\xC4nderungen werden beim n\xE4chsten /new oder Start wirksam. Unterbefehle: /memory list | show | forget | clear"
3960
+ },
3961
+ mcp: {
3962
+ ...EN.handlers.mcp,
3963
+ noServers: 'keine MCP-Server angeh\xE4ngt. F\xFChre `reasonix setup` aus, um welche auszuw\xE4hlen, oder starte mit --mcp "<spec>". `reasonix mcp list` zeigt den Katalog. Hinweis: vom Modell aufgerufene Shell-Befehle werden pro Aufruf abgefragt (einmal erlauben / immer erlauben / ablehnen) \u2014 kein globales Allow-All-Flag.',
3964
+ toolsLabel: " Tools {count}",
3965
+ resourcesHint: "`/resource` zum Durchsuchen+Lesen",
3966
+ promptsHint: "`/prompt` zum Durchsuchen+Abrufen",
3967
+ awarenessOnly: "Der Chat-Modus verbraucht Tools aktuell; Ressourcen+Prompts werden hier zur Information angezeigt.",
3968
+ catalogHint: "Vollst\xE4ndiger Katalog: `reasonix mcp list` \xB7 tiefere Diagnose: `reasonix mcp inspect <spec>`.",
3969
+ fallbackServers: "MCP-Server ({count}):",
3970
+ fallbackTools: "Tools im Register ({count}):",
3971
+ fallbackChange: "Um diesen Satz zu \xE4ndern, beende und f\xFChre `reasonix setup` aus.",
3972
+ usageDisableEnable: "Verwendung: /mcp {action} <name> \xB7 w\xE4hle einen in /mcp angezeigten Namen (anonyme Server k\xF6nnen nicht nach Namen umgeschaltet werden).",
3973
+ usageReconnect: "Verwendung: /mcp reconnect <name> \xB7 w\xE4hle einen in /mcp angezeigten Namen.",
3974
+ unknownServer: 'unbekannter MCP-Server "{name}". Bekannt: {list}.',
3975
+ noneList: "(keine)",
3976
+ reconnectNoTui: "/mcp reconnect ben\xF6tigt die interaktive TUI (postInfo nicht angeschlossen).",
3977
+ liveTab: "Live",
3978
+ marketplaceTab: "Marktplatz",
3979
+ tabHint: "Tab zum Umschalten"
3980
+ },
3981
+ init: {
3982
+ ...EN.handlers.init,
3983
+ codeOnly: "/init funktioniert nur im Code-Modus (es ben\xF6tigt Dateisystem-Werkzeuge).\nF\xFChre `reasonix code [pfad]` aus, um eine Sitzung zu starten, die im\nProjekt verwurzelt ist, das du initialisieren m\xF6chtest, und f\xFChre dann /init aus.",
3984
+ exists: "\u25B8 REASONIX.md existiert bereits unter {path}",
3985
+ existsForce: " /init force von Grund auf neu generieren (\xFCberschreibt)",
3986
+ existsEdit: " Oder bearbeite es von Hand \u2014 es ist nur Markdown. Die aktuelle Datei wird",
3987
+ existsPinned: " bei jedem Start unver\xE4ndert in den System-Prompt eingef\xFCgt.",
3988
+ info: "\u25B8 /init \u2014 Modell scannt das Projekt und synthetisiert REASONIX.md.\n Das Ergebnis landet als ausstehender Edit; mit /apply oder /walk reviewen."
3989
+ },
3990
+ webSearchEngine: {
3991
+ ...EN.handlers.webSearchEngine,
3992
+ currentEngine: "Aktuelle Websuchmaschine: {engine}",
3993
+ endpoint: "SearXNG-Endpunkt: {url}",
3994
+ usageHeader: "Verwendung:",
3995
+ usageBing: " /search-engine bing Bing verwenden (Standard, funktioniert von CN ohne Proxy)",
3996
+ usageSearxng: " /search-engine searxng SearXNG verwenden (Standard-Endpunkt)",
3997
+ usageSearxngUrl: " /search-engine searxng <url> SearXNG mit benutzerdefiniertem Endpunkt",
3998
+ usageMetaso: " /search-engine metaso Metaso-API verwenden (100/Tag kostenlos, konfiguriere eigenen API-Schl\xFCssel f\xFCr mehr)",
3999
+ usageTavily: " /search-engine tavily Tavily-API verwenden (LLM-freundlich, kostenlos 1000/Monat \u2014 setze TAVILY_API_KEY oder tavilyApiKey in der Konfiguration; erhalte einen unter https://tavily.com)",
4000
+ usagePerplexity: " /search-engine perplexity Perplexity AI verwenden (AI-native Antwort + Quellenangaben \u2014 setze PERPLEXITY_API_KEY oder perplexityApiKey in der Konfiguration; erhalte einen unter https://perplexity.ai/settings/api)",
4001
+ usageExa: " /search-engine exa Exa-API verwenden (AI-native Antwort + Quellenangaben, kostenlos 1000/Monat \u2014 setze EXA_API_KEY oder exaApiKey in der Konfiguration; registriere dich unter https://exa.ai)",
4002
+ alias: "Alias: /se",
4003
+ searxngInfo: "SearXNG ist eine selbst gehostete Metasuchmaschine (https://github.com/searxng/searxng).",
4004
+ searxngInstall: "Installiere mit: docker run -d -p 8080:8080 searxng/searxng",
4005
+ switched: 'Websuchmaschine auf "{engine}" umgestellt.{note}',
4006
+ switchedSearxngNote: " Stelle sicher, dass SearXNG unter {endpoint} l\xE4uft.",
4007
+ switchedMetasoNote: " Es gibt ein t\xE4gliches Kontingent von 100 (konfiguriere einen eigenen API-Schl\xFCssel f\xFCr h\xF6here Grenzen).",
4008
+ switchedTavilyNote: " Setze TAVILY_API_KEY oder `tavilyApiKey` in der Konfiguration; kostenlos 1000/Monat unter https://tavily.com.",
4009
+ switchedPerplexityNote: " Setze PERPLEXITY_API_KEY oder `perplexityApiKey` in der Konfiguration; erhalte einen unter https://perplexity.ai/settings/api.",
4010
+ switchedExaNote: " Setze EXA_API_KEY oder `exaApiKey` in der Konfiguration; registriere dich unter https://exa.ai.",
4011
+ keyNeeded: 'Kein API-Schl\xFCssel f\xFCr "{engine}" konfiguriert.\n\n 1. Setze die {envVar}-Umgebungsvariable\n 2. Oder gib ihn inline an: /search-engine {engine} <dein-schl\xFCssel>\n 3. Oder f\xFCge "{engine}ApiKey" zu ~/.reasonix/config.json hinzu\n\nWiederhole dann /search-engine {engine}.',
4012
+ keySaved: " API-Schl\xFCssel in der Konfiguration gespeichert.",
4013
+ confirmed: 'Websuchmaschine auf "{engine}" gesetzt{detail}. Der n\xE4chste Assistenten-Turn \xFCbernimmt die \xC4nderung.',
4014
+ confirmedDetail: " ({endpoint})"
4015
+ },
4016
+ skill: {
4017
+ ...EN.handlers.skill,
4018
+ listEmpty: "Keine Skills gefunden. Reasonix liest Skills von:",
4019
+ listProjectScope: " \xB7 <projekt>/.reasonix/skills/<name>/SKILL.md (oder <name>.md) \u2014 Projekt-Bereich",
4020
+ listGlobalScope: " \xB7 ~/.reasonix/skills/<name>/SKILL.md (oder <name>.md) \u2014 globaler Bereich",
4021
+ listProjectOnly: " (Projekt-Bereich ist nur in `reasonix code` aktiv)",
4022
+ listFrontmatter: "Die Frontmatter jeder Datei ben\xF6tigt mindestens `name` und `description`.",
4023
+ listInvoke: "F\xFChre einen Skill aus mit `/skill <name> [args]` oder indem du das Modell bittest, `run_skill` aufzurufen.",
4024
+ listHeader: "Benutzer-Skills ({count}):",
4025
+ listFooter: "Anzeigen: /skill show <name> Ausf\xFChren: /skill <name> [args] Neu: /skill new <name>",
4026
+ listEmptyNewHint: "Erstelle einen mit: /skill new <name> (Projekt-Bereich) \u2014 es gibt noch kein entferntes Register; du erstellst Skills direkt.",
4027
+ showUsage: "Verwendung: /skill show <name>",
4028
+ showNotFound: "Kein Skill gefunden: {name}",
4029
+ runNotFound: "Kein Skill gefunden: {name} (versuche /skill list)",
4030
+ runInfo: "\u25B8 f\xFChre Skill aus: {name}{args}",
4031
+ newUsage: "Verwendung: /skill new <name> [--global]",
4032
+ newCreated: "\u25B8 Skill erstellt: {name}\n {path}\n bearbeite ihn, dann `/skill {name}` zum Ausf\xFChren",
4033
+ newError: "\u25B2 /skill new fehlgeschlagen: {reason}",
4034
+ pathsHeader: "Skill-Pfade (Priorit\xE4tsreihenfolge):",
4035
+ pathsPriority: "Priorit\xE4t: Projekt > benutzerdefinierte Pfade in Konfigurationsreihenfolge > global > builtin. \xC4nderungen wirken sich auf den System-Prompt beim n\xE4chsten /new oder einer neuen Sitzung aus.",
4036
+ pathsUsage: "Verwendung: /skill paths [list]\n /skill paths add <pfad>\n /skill paths remove <pfad|N>",
4037
+ pathsAddUsage: "Verwendung: /skill paths add <pfad>",
4038
+ pathsRemoveUsage: "Verwendung: /skill paths remove <pfad|N>",
4039
+ pathsAdded: "\u25B8 benutzerdefinierten Skill-Pfad hinzugef\xFCgt: {path}",
4040
+ pathsAlready: "\u25B8 benutzerdefinierter Skill-Pfad bereits konfiguriert: {path}",
4041
+ pathsRemoved: "\u25B8 benutzerdefinierten Skill-Pfad entfernt: {path}",
4042
+ pathsRemoveNotFound: "\u25B8 kein benutzerdefinierter Skill-Pfad entspricht: {target}",
4043
+ pathsRestartHint: "Der System-Prompt der aktuellen Sitzung ist unver\xE4ndert; f\xFChre /new aus oder starte eine neue Sitzung, um das Skills-Register zu aktualisieren."
4044
+ }
4045
+ },
4046
+ statusBar: {
4047
+ ...EN.statusBar,
4048
+ turn: "Turn",
4049
+ cache: "Cache",
4050
+ spent: "ausgegeben",
4051
+ left: " \xFCbrig",
4052
+ slow: "langsam",
4053
+ disconnect: "trennen",
4054
+ reconnecting: "Verbinde neu\u2026",
4055
+ approvingIn: "Genehmige in ",
4056
+ escToInterrupt: "Esc zum Unterbrechen",
4057
+ recordingGlyph: "Aufnahme",
4058
+ mb: " MB",
4059
+ evt: " Ereignis",
4060
+ editsLabel: "Edits:",
4061
+ mcpLoading: "MCP",
4062
+ ctx: "Kontext",
4063
+ shortcutsHint: "Strg+P Tastenk\xFCrzel"
4064
+ },
4065
+ editMode: {
4066
+ ...EN.editMode,
4067
+ plan: "PLAN-MODUS",
4068
+ yolo: "YOLO",
4069
+ auto: "AUTO",
4070
+ review: "REVIEW",
4071
+ writesGated: " Schreibzugriffe blockiert \xB7 /plan off zum Verlassen",
4072
+ editsShellAuto: "Edits + Shell auto \xB7 /undo zum R\xFCckg\xE4ngigmachen",
4073
+ editsLandNow: "Edits werden sofort angewandt \xB7 u zum R\xFCckg\xE4ngigmachen",
4074
+ queuedApplyDiscard: "{count} in Warteschlange \xB7 y anwenden \xB7 n verwerfen",
4075
+ editsQueued: "Edits in Warteschlange \xB7 y anwenden \xB7 n verwerfen",
4076
+ shiftTabFlip: " {mid} \xB7 Shift+Tab zum Umschalten",
4077
+ queuedDots: "In Warteschlange\u2026"
4078
+ },
4079
+ composer: {
4080
+ ...EN.composer,
4081
+ placeholder: "Frag etwas \xB7 slash f\xFCr Befehle \xB7 at-Zeichen f\xFCr Dateien",
4082
+ waitingForResponse: "\u2026warte auf Antwort\u2026",
4083
+ hintSend: "senden",
4084
+ hintNewline: "Neue Zeile",
4085
+ hintClear: "leeren",
4086
+ hintScroll: "scrollen",
4087
+ hintHistory: "Verlauf",
4088
+ hintAbort: "abbrechen",
4089
+ hintQuit: "beenden",
4090
+ abortedHint: "Turn vom Benutzer abgebrochen \xB7 erneut Esc zum Leeren \xB7 \u23CE f\xFCr eine Folgefrage",
4091
+ editorNoRawMode: "externer Editor nicht verf\xFCgbar \u2014 stdin unterst\xFCtzt Raw-Mode-Umschaltung auf diesem Terminal nicht",
4092
+ editorFailed: "Externer Editor:",
4093
+ editorMissing: "kein $EDITOR / $VISUAL / $GIT_EDITOR gesetzt \u2014 exportiere einen (z.B. `export EDITOR=nano`) und versuche es erneut",
4094
+ editorExited: "Editor mit Code {code} beendet",
4095
+ typeaheadStaged: "\u25B8 {count} Zeile(n) bereitgestellt \xB7 Esc zur\xFCckrufen",
4096
+ steerPlaceholder: "tippe, um die aktuelle Aufgabe zu steuern \u2014 Befehle sind deaktiviert, solange besch\xE4ftigt",
4097
+ steerHint: "Senden \u2014 mid-Turn eingef\xFCgt",
4098
+ stashNothing: "Nichts zu speichern",
4099
+ stashSaved: "Gespeichert",
4100
+ stashRecall: "Abgerufen"
4101
+ },
4102
+ pathConfirm: {
4103
+ ...EN.pathConfirm,
4104
+ title: "Pfad au\xDFerhalb des Sandbox",
4105
+ subtitleRead: "{tool} m\xF6chte eine Datei AUSSERHALB des Projekt-Sandbox lesen",
4106
+ subtitleWrite: "{tool} m\xF6chte eine Datei AUSSERHALB des Projekt-Sandbox schreiben",
4107
+ awaiting: "wartet",
4108
+ denyTitle: "Ablehnen \u2014 Kontext angeben",
4109
+ optional: "optional",
4110
+ denyFooter: "Kontext eingeben \xB7 \u23CE mit Grund absenden \xB7 Esc \xFCberspringen (ohne Grund ablehnen)",
4111
+ pickFooter: "\u2191\u2193 ausw\xE4hlen \xB7 \u23CE best\xE4tigen \xB7 Tab Kontext hinzuf\xFCgen \xB7 Esc abbrechen",
4112
+ allowOnce: "Einmal erlauben",
4113
+ allowOnceDesc: "Diesen Zugriff erlauben; das Verzeichnis f\xFCr den Rest dieser Sitzung merken",
4114
+ allowAlways: "Immer erlauben",
4115
+ allowAlwaysDesc: "`{prefix}` f\xFCr dieses Projekt merken (gespeichert in ~/.reasonix/config.json)",
4116
+ deny: "ablehnen",
4117
+ denyDesc: "Tab dr\xFCcken, um dem Modell den Grund mitzuteilen",
4118
+ pathLabel: "Pfad",
4119
+ sandboxLabel: "Sandbox",
4120
+ allowPrefixLabel: "Pr\xE4fix",
4121
+ promptTitleRead: "Pfadzugriff \u2014 lesen",
4122
+ promptTitleWrite: "Pfadzugriff \u2014 schreiben",
4123
+ actionAllowRead: "Lesen erlauben",
4124
+ actionAllowWrite: "Schreiben erlauben",
4125
+ actionAlwaysAllow: "Immer erlauben \u2014 {prefix}",
4126
+ actionDeny: "Ablehnen"
4127
+ },
4128
+ shellConfirm: {
4129
+ ...EN.shellConfirm,
4130
+ title: "Shell-Befehl",
4131
+ bgTitle: "Hintergrundprozess",
4132
+ subtitle: "Modell m\xF6chte einen Shell-Befehl ausf\xFChren",
4133
+ bgSubtitle: "Langlebiger Prozess \u2014 l\xE4uft nach Genehmigung weiter, /kill zum Stoppen",
4134
+ denyTitle: "Ablehnen \u2014 Kontext angeben",
4135
+ optional: "optional",
4136
+ denyFooter: "Kontext eingeben \xB7 \u23CE mit Grund absenden \xB7 Esc \xFCberspringen (ohne Grund ablehnen)",
4137
+ awaiting: "wartet",
4138
+ pickFooter: "\u2191\u2193 ausw\xE4hlen \xB7 \u23CE best\xE4tigen \xB7 Tab Kontext hinzuf\xFCgen \xB7 Esc abbrechen",
4139
+ allowOnce: "Einmal erlauben",
4140
+ allowOnceDesc: "Diesen Befehl ausf\xFChren, beim n\xE4chsten Mal erneut fragen",
4141
+ allowAlways: "Immer erlauben",
4142
+ allowAlwaysDesc: "`{prefix}` f\xFCr dieses Projekt merken",
4143
+ deny: "ablehnen",
4144
+ denyDesc: "Tab dr\xFCcken, um dem Modell den Grund mitzuteilen",
4145
+ cwdLabel: "awd",
4146
+ timeoutLabel: "Timeout",
4147
+ waitLabel: "warten",
4148
+ previewMore: "\u2026 {n} weitere Zeile ausgeblendet \u2014 Esc dr\xFCcken, Modell bitten, sie aufzuteilen",
4149
+ previewMorePlural: "\u2026 {n} weitere Zeilen ausgeblendet \u2014 Esc dr\xFCcken, Modell bitten, sie aufzuteilen",
4150
+ promptTitleRunCommand: "Befehl ausf\xFChren",
4151
+ promptTitleRunBackground: "Hintergrundbefehl ausf\xFChren",
4152
+ actionRunOnce: "Einmal ausf\xFChren",
4153
+ actionAlwaysAllow: "Immer erlauben \u2014 {prefix}",
4154
+ actionDeny: "Ablehnen"
4155
+ },
4156
+ editConfirm: {
4157
+ ...EN.editConfirm,
4158
+ footer: "[y/Enter] anwenden \xB7 [n] mit Grund ablehnen \xB7 [a] Rest anwenden \xB7 [A] AUTO umschalten \xB7 [\u2191\u2193/Leertaste] scrollen \xB7 [Esc] abbrechen",
4159
+ newTag: "NEU",
4160
+ editTag: "BEARBEITET",
4161
+ linesCount: "-{removed} +{added} Zeilen",
4162
+ viewingRange: "Zeige {start}-{end}/{total}",
4163
+ denyFooter: "\u23CE absenden \xB7 Esc \xFCberspringen (ohne Grund ablehnen)",
4164
+ oldLabel: " - alt",
4165
+ newLabel: " + neu",
4166
+ sideBySide: " nebeneinander \xB7 entfernte Zeilen links, hinzugef\xFCgte rechts \xB7 paarweise nach Versatz",
4167
+ linesAbove: " \u2191 {count} Zeile dar\xFCber (\u2191/k oder Bild\u2191)",
4168
+ linesAbovePlural: " \u2191 {count} Zeilen dar\xFCber (\u2191/k oder Bild\u2191)",
4169
+ linesBelow: " \u2193 {count} Zeile darunter (\u2193/j oder Leertaste/Bild\u2193)",
4170
+ linesBelowPlural: " \u2193 {count} Zeilen darunter (\u2193/j oder Leertaste/Bild\u2193)"
4171
+ },
4172
+ editPicker: {
4173
+ ...EN.editPicker,
4174
+ title: "Vorherige Nachricht bearbeiten",
4175
+ hint: "\u2191\u2193 ausw\xE4hlen \xB7 Enter zum Laden in den Composer \xB7 Esc abbrechen",
4176
+ empty: "Noch keine Benutzer-Turns \u2014 nichts zu bearbeiten",
4177
+ dismiss: "Esc zum Schlie\xDFen",
4178
+ forked: "\u25B8 bei Turn #{turn} abgezweigt \u2014 Puffer enth\xE4lt den Originaltext"
4179
+ },
4180
+ sessionPicker: {
4181
+ ...EN.sessionPicker,
4182
+ header: " \u25C8 REASONIX \xB7 Sitzung ausw\xE4hlen ",
4183
+ title: "Sitzung ausw\xE4hlen \u2014 {workspace}",
4184
+ messages: "{count} Nachricht",
4185
+ messagesPlural: "{count} Nachrichten",
4186
+ turns: "{count} Turns",
4187
+ pickerHint: "\u2191\u2193 ausw\xE4hlen \xB7 / suchen \xB7 \u23CE \xF6ffnen \xB7 [n] neu \xB7 [d] l\xF6schen \xB7 [r] umbenennen \xB7 Esc beenden",
4188
+ empty: " noch keine gespeicherten Sitzungen in diesem Arbeitsbereich \u2014 dr\xFCcke ",
4189
+ emptyNew: " um eine neue zu starten",
4190
+ renamePrompt: ' "{from}" umbenennen \u2192 ',
4191
+ renameHint: " \u23CE Umbenennung best\xE4tigen \xB7 Esc abbrechen",
4192
+ searchPrompt: " Sitzungen durchsuchen: /",
4193
+ searchHint: " tippen zum Filtern \xB7 \u23CE Treffer \xF6ffnen \xB7 Esc zur\xFCcksetzen",
4194
+ searchEmpty: " keine Sitzungen entsprechen dieser Suche",
4195
+ emptyHint: " \u23CE neue Sitzung \xB7 Esc beenden",
4196
+ justNow: "Gerade eben",
4197
+ minAgo: "Vor {count} Min",
4198
+ yesterday: "gestern",
4199
+ hoursAgo: "Vor {count}h",
4200
+ daysAgo: "Vor {count} Tagen"
4201
+ },
4202
+ workspacePicker: {
4203
+ ...EN.workspacePicker,
4204
+ header: " \u25C8 REASONIX \xB7 Arbeitsbereich ausw\xE4hlen ",
4205
+ title: "Arbeitsbereich ausw\xE4hlen \u2014 {workspace}",
4206
+ sessions: "{count} Sitzung",
4207
+ sessionsPlural: "{count} Sitzungen",
4208
+ current: "aktuell",
4209
+ pickerHint: "\u2191\u2193 ausw\xE4hlen \xB7 / suchen \xB7 \u23CE wechseln + Sitzung ausw\xE4hlen \xB7 Esc beenden \xB7 /cwd <pfad> f\xFCgt einen hinzu",
4210
+ empty: " noch keine bekannten Arbeitsbereiche \u2014 f\xFChre /cwd <pfad> einmal aus, um einen hinzuzuf\xFCgen",
4211
+ searchPrompt: " Arbeitsbereiche durchsuchen: /",
4212
+ searchHint: " tippen zum Filtern \xB7 \u23CE wechseln + Sitzung ausw\xE4hlen \xB7 Esc zur\xFCcksetzen",
4213
+ searchEmpty: " keine Arbeitsbereiche entsprechen dieser Suche"
4214
+ },
4215
+ modelPicker: {
4216
+ ...EN.modelPicker,
4217
+ header: " \u25C8 REASONIX \xB7 Einrichtung ausw\xE4hlen ",
4218
+ loading: " \xB7 lade Katalog\u2026",
4219
+ catalogEmpty: " \xB7 Katalog leer \u2014 verwende bekannte Fallbacks",
4220
+ modelsAvailable: " \xB7 {count} Modelle verf\xFCgbar",
4221
+ effortHeader: " EFFORT \xB7 Reasoning-Effort-Grenze",
4222
+ modelsHeader: " MODELLE \xB7 DeepSeek-kompatible IDs",
4223
+ effortDesc: {
4224
+ ...EN.modelPicker.effortDesc,
4225
+ low: "Am schnellsten \u2014 minimales Reasoning",
4226
+ medium: "ausgewogen",
4227
+ high: "Standard \u2014 sicher f\xFCr vLLM / Azure",
4228
+ max: "DeepSeek-Erweiterung; von stock OpenAI / vLLM abgelehnt"
4229
+ },
4230
+ pickerFooter: " \u2191\u2193 ausw\xE4hlen \xB7 \u23CE best\xE4tigen \xB7 [r] aktualisieren \xB7 Esc abbrechen",
4231
+ currentLabel: " \xB7 aktuell"
4232
+ },
4233
+ slashSuggestions: {
4234
+ ...EN.slashSuggestions,
4235
+ noMatch: "Kein Slash-Befehl entspricht diesem Pr\xE4fix",
4236
+ backspaceHint: " \u2014 R\xFCcktaste zum Bearbeiten, oder /help f\xFCr die vollst\xE4ndige Liste",
4237
+ commandCount: "{count} Befehl",
4238
+ commandCountPlural: "{count} Befehle",
4239
+ aboveLabel: " \u2191 {count} dar\xFCber",
4240
+ belowLabel: " \u2193 {count} darunter",
4241
+ advancedHint: " + {count} erweitert \xB7 tippe einen Buchstaben zum Suchen",
4242
+ footerHint: " \u2191\u2193 navigieren \xB7 Tab / \u23CE ausw\xE4hlen \xB7 Esc abbrechen",
4243
+ groupChat: "CHAT",
4244
+ groupSetup: "SETUP",
4245
+ groupInfo: "INFO",
4246
+ groupSession: "SITZUNG",
4247
+ groupExtend: "ERWEITERN",
4248
+ groupCode: "CODE",
4249
+ groupJobs: "JOBS",
4250
+ groupAdvanced: "ERWEITERT",
4251
+ groupDetailSetup: "Modell + Kosten",
4252
+ groupDetailInfo: "Aktueller Zustand",
4253
+ groupDetailChat: "T\xE4gliche Turn-Operationen",
4254
+ groupDetailExtend: "MCP, Memory, Skills",
4255
+ groupDetailSession: "Gespeicherte Sitzungen",
4256
+ groupDetailCode: "Edits + Pl\xE4ne (Code-Modus)",
4257
+ groupDetailJobs: "Hintergrundprozesse (Code-Modus)",
4258
+ groupDetailAdvanced: "Selten oder einmalig"
4259
+ },
4260
+ atMentions: {
4261
+ ...EN.atMentions,
4262
+ loading: "lade\u2026",
4263
+ entrySingular: "{count} Eintrag",
4264
+ entryPlural: "{count} Eintr\xE4ge",
4265
+ searching: "suche\u2026",
4266
+ scanned: "gescannt",
4267
+ match: "Treffer",
4268
+ matches: "Treffer",
4269
+ forFilter: 'f\xFCr "{filter}"',
4270
+ noMatch: 'keine Dateien entsprechen "{filter}"',
4271
+ emptyDir: "Leeres Verzeichnis",
4272
+ scanning: "Durchsuche Verzeichnisbaum\u2026",
4273
+ footerBrowse: "\u2191\u2193 navigieren \xB7 Tab in Ordner eintauchen \xB7 \u23CE einf\xFCgen \xB7 Esc abbrechen",
4274
+ footerBrowseSearch: "\u2191\u2193 navigieren \xB7 Tab / \u23CE als @pfad einf\xFCgen \xB7 Esc abbrechen",
4275
+ footerInsert: "\u2191\u2193 navigieren \xB7 Tab / \u23CE als @pfad einf\xFCgen \xB7 Esc abbrechen"
4276
+ },
4277
+ statsPanel: {
4278
+ ...EN.statsPanel,
4279
+ modePlan: "PLAN",
4280
+ modeYolo: "yolo",
4281
+ modeAuto: "auto",
4282
+ modeReview: "review",
4283
+ pro: "\u21E7 pro",
4284
+ budget: " Budget "
4285
+ },
4286
+ welcomeBanner: {
4287
+ ...EN.welcomeBanner,
4288
+ workspace: "\u25B8 Arbeitsbereich",
4289
+ relaunchHint: " (mit --dir <pfad> neu starten zum Wechseln)",
4290
+ dashboard: "\u25B8 Web"
4291
+ },
4292
+ ctxBreakdown: {
4293
+ ...EN.ctxBreakdown,
4294
+ title: "\u25A3 Kontext",
4295
+ compactHint: " /compact faltet (automatisch bei 50 %) \xB7 /new l\xF6scht Log",
4296
+ topTools: " Top-Tool-Ergebnisse nach Kosten ({count}):",
4297
+ msg: "Nachr",
4298
+ turnLabel: "Turn"
4299
+ },
4300
+ startup: {
4301
+ ...EN.startup,
4302
+ codeRooted: '\u25B8 reasonix code: verwurzelt in {rootDir}, Sitzung "{session}" \xB7 {tools} native Tool{s}{semantic}',
4303
+ ephemeral: "(ephemer)",
4304
+ semanticOn: " \xB7 Semantic-Search an"
4305
+ },
4306
+ doctorErrors: {
4307
+ ...EN.doctorErrors,
4308
+ unreadable: "{path} nicht lesbar \u2014 {message}",
4309
+ cannotList: "Kann nicht auflisten \u2014 {message}",
4310
+ parseFailed: "settings.json konnte nicht geparst werden \u2014 {message}",
4311
+ probeFailed: "Test fehlgeschlagen \u2014 {message}"
4312
+ },
4313
+ webErrors: {
4314
+ ...EN.webErrors,
4315
+ status: "web_search {status} \u2014 versuche: Das Such-Backend hat einen Fehler zur\xFCckgegeben; formuliere die Abfrage um oder wechsle die Engine mit /search-engine bing|searxng|metaso|tavily|perplexity|exa",
4316
+ rateLimit429: "web_search 429 \u2014 versuche: 10s warten vor erneuter Abfrage oder Abfrage umformulieren; das Such-Backend ratelimited diesen Client",
4317
+ forbidden403: "web_search 403 \u2014 versuche: Das Such-Backend blockiert diesen Client; wechsle die Engine mit /search-engine bing|searxng|metaso|tavily|perplexity|exa oder warte und versuche es sp\xE4ter erneut",
4318
+ serverError5xx: "web_search {status} \u2014 versuche: \xD6ffne die Such-URL in einem Browser; falls sie l\xE4dt, ist dies vor\xFCbergehend und ein erneuter Versuch in 30s kann helfen",
4319
+ bingBlocked: "web_search: Bing-Anti-Bot-Seite \u2014 ratelimited oder blockiert \u2014 versuche: 30s warten und erneut versuchen, oder wechsle die Engine mit /search-engine bing|searxng|metaso|tavily|perplexity|exa",
4320
+ bingNoResults: "web_search: 0 Ergebnisse, aber die Antwort sieht nicht wie eine echte leere Seite aus ({chars} Zeichen, erste 120: {preview}) \u2014 versuche: formuliere die Abfrage mit einfacheren Begriffen um oder wechsle die Engine mit /search-engine bing|searxng|metaso|tavily|perplexity|exa",
4321
+ invalidEndpoint: 'web_search: ung\xFCltiger SearXNG-Endpunkt "{endpoint}" \u2014 versuche: setze eine g\xFCltige URL mit /search-endpoint http://host:port',
4322
+ endpointMustBeHttp: "web_search: SearXNG-Endpunkt muss http(s) sein, {protocol} erhalten \u2014 versuche: setze eine g\xFCltige URL mit /search-endpoint http://host:port",
4323
+ cannotReach: "web_search: SearXNG-Server unter {endpoint} nicht erreichbar \u2014 versuche: SearXNG installieren und starten (https://github.com/searxng/searxng, z.B. `docker run -d -p 8080:8080 searxng/searxng`), oder wechsle zu einer anderen Engine mit /search-engine bing|searxng|metaso|tavily|perplexity|exa",
4324
+ searxngNoResults: "web_search: 0 Ergebnisse, aber SearXNG-Antwort sieht nicht wie eine leere Ergebnisseite aus ({chars} Zeichen) \u2014 versuche: formuliere die Abfrage mit einfacheren Begriffen um oder wechsle die Engine mit /search-engine bing|searxng|metaso|tavily|perplexity|exa",
4325
+ metasoMissingKey: "web_search: Metaso ben\xF6tigt einen API-Schl\xFCssel \u2014 setze METASO_API_KEY oder konfiguriere einen mit /search-engine metaso <schl\xFCssel>. Erhalte einen unter https://metaso.cn/search-api/playground",
4326
+ metasoDailyLimit: "web_search: Metaso-Tageslimit erreicht \u2014 setze METASO_API_KEY oder erhalte einen Schl\xFCssel unter https://metaso.cn/search-api/playground",
4327
+ metasoUnauthorized: "web_search: Metaso-API-Schl\xFCssel abgelehnt \u2014 \xFCberpr\xFCfe METASO_API_KEY oder erhalte einen unter https://metaso.cn/search-api/playground",
4328
+ metasoRateLimit: "web_search: Metaso ratelimited \u2014 warte und versuche es erneut, oder erhalte einen eigenen API-Schl\xFCssel unter https://metaso.cn/search-api/playground",
4329
+ metasoServerError: "web_search: Metaso-Serverfehler ({status}) \u2014 versuche es sp\xE4ter erneut oder wechsle die Engine mit /search-engine bing|searxng|metaso|tavily|perplexity|exa",
4330
+ metasoParseError: "web_search: Metaso hat unparsbare Antwort zur\xFCckgegeben (HTTP {status}) \u2014 versuche es sp\xE4ter erneut",
4331
+ metasoApiError: "web_search: Metaso-API-Fehler (Code {code}: {message}) \u2014 versuche es sp\xE4ter erneut",
4332
+ tavilyMissingKey: "web_search: Tavily-Backend ben\xF6tigt einen API-Schl\xFCssel \u2014 setze TAVILY_API_KEY-Umgebungsvariable oder `tavilyApiKey` in ~/.reasonix/config.json; kostenlose 1000/Monat-Registrierung unter https://tavily.com",
4333
+ tavilyUnauthorized: "web_search: Tavily-API-Schl\xFCssel abgelehnt \u2014 \xFCberpr\xFCfe TAVILY_API_KEY oder erhalte einen unter https://tavily.com",
4334
+ tavilyRateLimit: "web_search: Tavily ratelimited oder monatliches Kontingent \xFCberschritten \u2014 warte, wechsle die Engine mit /search-engine bing|searxng|metaso|tavily|perplexity|exa oder upgrade deinen Tavily-Plan",
4335
+ tavilyServerError: "web_search: Tavily-Serverfehler ({status}) \u2014 versuche es sp\xE4ter erneut oder wechsle die Engine mit /search-engine bing|searxng|metaso|tavily|perplexity|exa",
4336
+ tavilyParseError: "web_search: Tavily hat unparsbare Antwort zur\xFCckgegeben (HTTP {status}) \u2014 versuche es sp\xE4ter erneut",
4337
+ perplexityMissingKey: "web_search: Perplexity-Backend ben\xF6tigt einen API-Schl\xFCssel \u2014 setze PERPLEXITY_API_KEY-Umgebungsvariable oder `perplexityApiKey` in ~/.reasonix/config.json; erhalte einen unter https://perplexity.ai/settings/api",
4338
+ perplexityUnauthorized: "web_search: Perplexity-API-Schl\xFCssel abgelehnt \u2014 \xFCberpr\xFCfe PERPLEXITY_API_KEY oder erhalte einen unter https://perplexity.ai/settings/api",
4339
+ perplexityRateLimit: "web_search: Perplexity ratelimited \u2014 warte und versuche es erneut, oder wechsle die Engine mit /search-engine bing|searxng|metaso|tavily|perplexity|exa",
4340
+ perplexityServerError: "web_search: Perplexity-Serverfehler ({status}) \u2014 versuche es sp\xE4ter erneut oder wechsle die Engine mit /search-engine bing|searxng|metaso|tavily|perplexity|exa",
4341
+ perplexityParseError: "web_search: Perplexity hat unparsbare Antwort zur\xFCckgegeben (HTTP {status}) \u2014 versuche es sp\xE4ter erneut",
4342
+ exaMissingKey: "web_search: Exa-Backend ben\xF6tigt einen API-Schl\xFCssel \u2014 setze EXA_API_KEY-Umgebungsvariable oder `exaApiKey` in ~/.reasonix/config.json; kostenlose 1000/Monat-Registrierung unter https://exa.ai",
4343
+ exaUnauthorized: "web_search: Exa-API-Schl\xFCssel abgelehnt \u2014 \xFCberpr\xFCfe EXA_API_KEY oder erhalte einen unter https://exa.ai",
4344
+ exaRateLimit: "web_search: Exa-API ratelimited oder monatliches Kontingent \xFCberschritten \u2014 warte oder upgrade unter https://exa.ai/pricing",
4345
+ exaServerError: "web_search: Exa-Serverfehler ({status}) \u2014 versuche es sp\xE4ter erneut oder wechsle die Engine mit /search-engine bing|searxng|metaso|tavily|perplexity|exa",
4346
+ exaParseError: "web_search: Exa hat unparsbare Antwort zur\xFCckgegeben (HTTP {status}) \u2014 versuche es sp\xE4ter erneut",
4347
+ fetchStatus: "web_fetch {status} f\xFCr {url} \u2014 versuche: Best\xE4tige, dass die URL im Browser aufgel\xF6st wird; der Status deutet darauf hin, dass der Host eine Fehlerseite zur\xFCckgegeben hat",
4348
+ fetchRateLimit429: "web_fetch 429 f\xFCr {url} \u2014 versuche: 10s warten vor erneuter Abfrage; der Host ratelimitet diesen Client",
4349
+ fetchForbidden403: "web_fetch 403 f\xFCr {url} \u2014 versuche: Der Host blockiert diesen Client; die Seite erfordert m\xF6glicherweise eine Anmeldung oder blockiert Bots \u2014 verwende stattdessen web_search-Ausz\xFCge",
4350
+ fetchServerError5xx: "web_fetch {status} f\xFCr {url} \u2014 versuche: \xD6ffne die URL in einem Browser; falls sie l\xE4dt, ist dies vor\xFCbergehend und ein erneuter Versuch in 30s kann helfen",
4351
+ fetchTimeout: "web_fetch: Zeit\xFCberschreitung nach {ms}ms f\xFCr {url} \u2014 versuche: eine k\xFCrzere URL oder kleinere Inhalte; dies k\xF6nnte ein langsames CDN sein, oder einmal erneut versuchen",
4352
+ fetchTooLarge: "web_fetch abgelehnt: content-length {len} Bytes \xFCberschreitet {cap}-Byte-Grenze ({url}) \u2014 versuche: eine andere URL mit kleineren Inhalten; diese Seite ist zu gro\xDF zum Abrufen",
4353
+ fetchBodyTooLarge: "web_fetch abgelehnt: Antwortbody \xFCberschritt {cap}-Byte-Grenze ({seen} Bytes gesehen) \u2014 versuche: eine andere URL mit kleineren Inhalten; diese Seite hat die Gr\xF6\xDFenbeschr\xE4nkung \xFCberschritten",
4354
+ fetchInvalidUrl: "web_fetch: URL muss mit http:// oder https:// beginnen \u2014 versuche: eine absolute http(s)-URL \xFCbergeben (die URL ist fehlerhaft oder verwendet ein nicht unterst\xFCtztes Schema)"
4355
+ },
4356
+ choiceConfirm: {
4357
+ ...EN.choiceConfirm,
4358
+ customLabel: "Eigene Antwort eingeben",
4359
+ customDesc: "Keine der Optionen passt \u2014 gib eine Freitext-Antwort ein. Das Modell liest sie w\xF6rtlich.",
4360
+ cancelLabel: "Abbrechen \u2014 Frage verwerfen",
4361
+ cancelDesc: "Modell stoppt und fragt, was du stattdessen m\xF6chtest."
4362
+ },
4363
+ cardTitles: {
4364
+ ...EN.cardTitles,
4365
+ usage: "Nutzung",
4366
+ context: "Kontext",
4367
+ search: "Suche",
4368
+ subagent: "Subagent",
4369
+ reply: "Antwort",
4370
+ reasoning: "Reasoning",
4371
+ reasoningAborted: "Reasoning (abgebrochen)",
4372
+ reasoningEllipsis: "Reasoning\u2026",
4373
+ error: "Fehler",
4374
+ doctor: "Doctor",
4375
+ you: "du",
4376
+ task: "Aufgabe"
4377
+ },
4378
+ cardLabels: {
4379
+ ...EN.cardLabels,
4380
+ prompt: "Prompt",
4381
+ reason: "Grund",
4382
+ output: "Ausgabe",
4383
+ cache: "Cache",
4384
+ session: "Sitzung",
4385
+ balance: "Guthaben",
4386
+ turn: "Turn",
4387
+ system: "System",
4388
+ tools: "Tools",
4389
+ log: "Log",
4390
+ input: "Eingabe",
4391
+ topTools: "Top-Tools",
4392
+ logMsgs: "Log-Nachr",
4393
+ hitSingular: "{count} Treffer \xB7 {files} Datei",
4394
+ hitsPlural: "{count} Treffer \xB7 {files} Dateien",
4395
+ moreHitSingular: "\u22EE +{count} weiterer Treffer",
4396
+ moreHitsPlural: "\u22EE +{count} weitere Treffer",
4397
+ earlierLine: "\u22EE {count} ausgeblendete Zeile (Strg+R f\xFCr vollst\xE4ndige Ausgabe)",
4398
+ earlierLines: "\u22EE {count} ausgeblendete Zeilen (Strg+R f\xFCr vollst\xE4ndige Ausgabe)",
4399
+ hiddenLine: "\u22EE {count} ausgeblendete Zeile",
4400
+ hiddenLines: "\u22EE {count} ausgeblendete Zeilen",
4401
+ earlierStackLine: "\u22EE {count} fr\xFChere Stack-Zeile ausgeblendet",
4402
+ earlierStackLines: "\u22EE {count} fr\xFChere Stack-Zeilen ausgeblendet",
4403
+ agent: "Agent \xB7 {name}",
4404
+ response: "Antwort",
4405
+ writing: "Schreibe \u2026",
4406
+ tok: "Tok",
4407
+ pilcrow: "\xB6",
4408
+ aborted: "abgebrochen",
4409
+ truncatedByEsc: "[durch Esc gek\xFCrzt]",
4410
+ rejected: "abgelehnt",
4411
+ exit: "Exit {code}",
4412
+ bytesIn: "{bytes} rein",
4413
+ elapsedSec: "{secs}s",
4414
+ stackTrace: "Stacktrace",
4415
+ retries: "Wiederholungen",
4416
+ reasoningLabel: "Reasoning \xB7 {count} \xB6",
4417
+ runningLabel: "l\xE4uft",
4418
+ workingLabel: "arbeitet",
4419
+ defaultFooter: "\u2191\u2193 ausw\xE4hlen \xB7 \u23CE best\xE4tigen \xB7 Esc abbrechen",
4420
+ applyAction: "[a] anwenden",
4421
+ skipAction: "[s] \xFCberspringen",
4422
+ rejectAction: "[r] ablehnen",
4423
+ levelOk: "OK",
4424
+ levelWarn: "Warn",
4425
+ levelFail: "FEHLGESCHLAGEN",
4426
+ checksLabel: "Pr\xFCfungen",
4427
+ passed: "bestanden",
4428
+ warnTag: "Warn",
4429
+ failTag: "Fehl",
4430
+ stepLabel: "Schritt",
4431
+ done: "erledigt",
4432
+ inProgress: "\u2190 in Bearbeitung",
4433
+ upcoming: "bevorstehend",
4434
+ resumed: "fortgesetzt \xB7 ",
4435
+ archive: "\u23E4 archivieren \xB7 ",
4436
+ more: "\u22EE +{count} weitere",
4437
+ categoryUser: "Benutzer",
4438
+ categoryFeedback: "Feedback",
4439
+ categoryProject: "Projekt",
4440
+ categoryReference: "Referenz"
4441
+ },
4442
+ mcpHealth: {
4443
+ ...EN.mcpHealth,
4444
+ noData: "Keine Inspektionsdaten",
4445
+ healthy: "Gesund \xB7 {ms}ms",
4446
+ slow: "Langsam \xB7 {ms}ms",
4447
+ verySlow: "Sehr langsam \xB7 {ms}ms",
4448
+ slowToast: "\u26A0 MCP `{name}` langsam \xB7 {seconds}s p95 \xFCber die letzten {sampleSize} Aufrufe",
4449
+ emptyHint: "\u2139 keine MCP-Server konfiguriert \u2014 versuche: `reasonix setup` zur erneuten Auswahl, oder `reasonix mcp install filesystem` \xB7 Shell-Befehle werden pro Aufruf abgefragt (einmal erlauben / immer erlauben / ablehnen), kein globales Allow-All"
4450
+ },
4451
+ denyContextInput: {
4452
+ ...EN.denyContextInput,
4453
+ description: "Sag dem Agenten, warum du abgelehnt hast. Der n\xE4chste Versuch sieht deinen Grund als zus\xE4tzlichen Kontext."
4454
+ },
4455
+ cardStream: {
4456
+ ...EN.cardStream,
4457
+ scrollAbove: " \u2191 {scroll} / {max} Zeile dar\xFCber",
4458
+ scrollAbovePlural: " \u2191 {scroll} / {max} Zeilen dar\xFCber",
4459
+ scrollMore: " \u2014 {remaining} weitere",
4460
+ scrollPgUp: " \xB7 Bild\u2191 / Mausrad",
4461
+ scrollCopy: " \xB7 /copy aktiviert Kopiermodus"
4462
+ },
4463
+ slashArgPicker: {
4464
+ ...EN.slashArgPicker,
4465
+ noMatch: 'keine \xDCbereinstimmung f\xFCr "{partial}"',
4466
+ keepTyping: " \u2014 tippe weiter, oder R\xFCcktaste zum Bearbeiten",
4467
+ above: " \u2191 {hidden} dar\xFCber",
4468
+ below: " \u2193 {hidden} darunter",
4469
+ footer: " \u2191\u2193 navigieren \xB7 Tab / \u23CE ausw\xE4hlen \xB7 Esc abbrechen"
4470
+ },
4471
+ mcpMarketplace: {
4472
+ ...EN.mcpMarketplace,
4473
+ title: "MCP-Marktplatz",
4474
+ filter: "Filter: ",
4475
+ filterPlaceholder: "(tippen zum Filtern)",
4476
+ matchSingular: "{n} Treffer",
4477
+ matchPlural: "{n} Treffer",
4478
+ loading: "lade\u2026",
4479
+ noEntries: "Keine Eintr\xE4ge",
4480
+ opening: "\xD6ffne Registry\u2026",
4481
+ cached: "\xB7 zwischengespeichert",
4482
+ exhausted: "\xB7 ersch\xF6pft",
4483
+ loadingMore: "Lade mehr\u2026",
4484
+ allLoaded: "Alle Seiten geladen",
4485
+ fetchingDetail: "Hole Smithery-Details\u2026",
4486
+ noInstallInfo: "keine Installationsinfo f\xFCr {name} - versuche `npx -y @smithery/cli install {name}`",
4487
+ alreadyInstalled: "Bereits installiert: {spec}",
4488
+ installed: "Installiert \u2192 {spec}",
4489
+ uninstalled: "{name} deinstalliert",
4490
+ installFailed: "Installation fehlgeschlagen: {message}",
4491
+ notInstalled: "Nicht installiert: {name}",
4492
+ bridged: "\u2713 {name} installiert - verbunden",
4493
+ bridgeFailed: "\u25B2 {name} installiert - Verbindung fehlgeschlagen: {reason}",
4494
+ bridgeReloadFailed: "\u2713 {name} installiert - starte `reasonix code` neu zur Verbindung (Neuladen fehlgeschlagen: {message})",
4495
+ restartBridge: "\u2713 {name} installiert - starte `reasonix code` neu zur Verbindung",
4496
+ needsEnv: " \xB7 ben\xF6tigt Umgebungsvariable: {env}",
4497
+ badgeOfficial: "[off]",
4498
+ badgeSmithery: "[smt]",
4499
+ badgeLocal: "[loc]",
4500
+ footerHint: "Filter eingeben \xB7 \u2191\u2193 ausw\xE4hlen \xB7 \u23CE installieren/umschalten \xB7 Bild\u2193 mehr laden \xB7 Esc schlie\xDFen",
4501
+ specLine: "Spec: {runtime} {id} \xB7 {transport}",
4502
+ smitheryDetail: "(Smithery-Eintrag \u2014 Installationsdetails werden bei Enter abgerufen)",
4503
+ statusError: "Fehler: {message}"
4504
+ },
4505
+ mcpBrowser: {
4506
+ ...EN.mcpBrowser,
4507
+ title: "\u25C8 MCP-Browser",
4508
+ empty: "Keine MCP-Server angeh\xE4ngt. F\xFChre `reasonix setup` aus, um welche auszuw\xE4hlen, oder starte mit --mcp.",
4509
+ serverCount: "{count} Server",
4510
+ footer: "\u2191\u2193 ausw\xE4hlen \xB7 [r] neu verbinden \xB7 [d] deaktivieren \xB7 Esc beenden"
4511
+ },
4512
+ mcpBrowse: {
4513
+ ...EN.mcpBrowse,
4514
+ noResources: "Keine Ressourcen auf einem verbundenen MCP-Server (oder keine Server verbunden). `/mcp` zeigt den aktuellen Satz.",
4515
+ readOne: "Lese einen: `/resource <uri>` \u2014 oder verwende Tab in der Auswahl.",
4516
+ noPrompts: "Keine Prompts auf einem verbundenen MCP-Server (oder keine Server verbunden). `/mcp` zeigt den aktuellen Satz.",
4517
+ fetchOne: "Rufe einen ab: `/prompt <name>` \u2014 Argumente werden noch nicht unterst\xFCtzt; Prompts mit erforderlichen Argumenten geben einen Fehler vom Server zur\xFCck.",
4518
+ noServerForResource: 'kein Server bietet Ressource "{name}"',
4519
+ resourceHint: "`/resource` ohne Argument listet verf\xFCgbare Ressourcen.",
4520
+ readFailed: "readResource fehlgeschlagen",
4521
+ noServerForPrompt: 'kein Server bietet Prompt "{name}"',
4522
+ promptHint: "`/prompt` ohne Argument listet verf\xFCgbare Prompts.",
4523
+ fetchFailed: "getPrompt fehlgeschlagen"
4524
+ },
4525
+ mcpLifecycle: {
4526
+ ...EN.mcpLifecycle,
4527
+ handshake: "Handshake\u2026",
4528
+ connected: "verbunden",
4529
+ failed: "fehlgeschlagen",
4530
+ disabled: "deaktiviert",
4531
+ reconnect: "Wiederverbinden\u2026",
4532
+ initDetail: "initialisiere \u2192 tools/list \u2192 resources/list",
4533
+ reconnectDetail: "baue ab \xB7 neuer Handshake \xB7 liste Tools",
4534
+ disabledDetail: "via /mcp disable {name}",
4535
+ failedSetupHint: "\u2192 f\xFChre `reasonix setup` aus, um diesen Eintrag zu entfernen, oder behebe das zugrunde liegende Problem (fehlendes npm-Paket, Netzwerk usw.).",
4536
+ failedSetupConfigHint: "\u2192 f\xFChre `reasonix setup` aus, um fehlerhafte Eintr\xE4ge aus deiner gespeicherten Konfiguration zu entfernen.",
4537
+ abortedHint: "MCP-Start abgebrochen \u2014 {count} Server \xFCbersprungen. F\xFChre /mcp aus, um es erneut zu versuchen, sobald du das zugrunde liegende Problem behoben hast.",
4538
+ toolsReady: "Tools bereit",
4539
+ warnLabel: "Warn"
4540
+ },
4541
+ checkpointPicker: {
4542
+ ...EN.checkpointPicker,
4543
+ title: "Checkpoint wiederherstellen \u2014 {workspace}",
4544
+ header: " \u25C8 REASONIX \xB7 Checkpoint ausw\xE4hlen ",
4545
+ empty: " noch keine Checkpoints in diesem Arbeitsbereich - siehe /checkpoint zum Erstellen",
4546
+ more: " \u2026 {hidden} weitere",
4547
+ footer: " \u2191\u2193 ausw\xE4hlen \xB7 \u23CE wiederherstellen \xB7 [d] vergessen \xB7 Esc beenden",
4548
+ footerEmpty: " Esc beenden"
4549
+ },
4550
+ planReviseConfirm: {
4551
+ ...EN.planReviseConfirm,
4552
+ title: "Plan-\xDCberarbeitung vorgeschlagen",
4553
+ metaRight: "\u2212{removed} +{added} \xB7 {kept} behalten",
4554
+ updatedSummary: "Aktualisierte Zusammenfassung: {summary}",
4555
+ acceptLabel: "\xDCberarbeitung annehmen \u2014 neue Schrittliste anwenden",
4556
+ acceptHint: "Ersetzt den Restplan durch die vorgeschlagenen Schritte. Erledigte Schritte bleiben unber\xFChrt.",
4557
+ rejectLabel: "Ablehnen \u2014 Originalplan behalten",
4558
+ rejectHint: "Vorschlag verwerfen. Modell f\xE4hrt mit den urspr\xFCnglichen verbleibenden Schritten fort."
4559
+ },
4560
+ diffApp: {
4561
+ ...EN.diffApp,
4562
+ title: "reasonix diff",
4563
+ turnLabel: "Turn {turn} ({current}/{total})",
4564
+ turnsAligned: "{count} Turns ausgerichtet",
4565
+ paneEmpty: "(keine Datens\xE4tze auf dieser Seite f\xFCr diesen Turn)",
4566
+ kindMatch: "\u2713 \xDCbereinstimmung",
4567
+ kindDiverge: "\u2605 Abweichung",
4568
+ kindOnlyInA: "\u2190 nur in A",
4569
+ kindOnlyInB: "\u2192 nur in B"
4570
+ },
4571
+ recordView: {
4572
+ ...EN.recordView,
4573
+ userPrefix: "Du \xBB ",
4574
+ assistant: "Assistent",
4575
+ toolPrefix: "Tool<",
4576
+ argsLabel: " Args: ",
4577
+ resultArrow: " \u2192 ",
4578
+ error: "Fehler ",
4579
+ cache: " \xB7 Cache ",
4580
+ toolCallOnly: "(nur Tool-Call-Antwort)",
4581
+ truncateExtra: "(+{extra} Zeichen)"
4582
+ },
4583
+ replayApp: {
4584
+ ...EN.replayApp,
4585
+ emptyTranscript: "Leeres Transkript",
4586
+ turnProgress: "Turn {current}/{total}",
4587
+ noRecords: "Keine Datens\xE4tze",
4588
+ untracked: "(nicht verfolgt)",
4589
+ churned: "(umgewandelt \xD7{count})"
4590
+ },
4591
+ builtinSkills: {
4592
+ ...EN.builtinSkills,
4593
+ explore: 'Durchsuche die Codebasis in einem isolierten Subagenten \u2014 breit angelegte, schreibgesch\xFCtzte Untersuchung, die eine destillierte Antwort zur\xFCckgibt. Am besten f\xFCr: \xBBFinde alle Stellen, die\u2026", \xBBWie funktioniert X im gesamten Projekt", \xBBDurchsuche den Code nach Y".',
4594
+ research: 'Recherchiere eine Frage durch Kombination von Websuche + Codelesen in einem isolierten Subagenten. Am besten f\xFCr: \xBBWird X-Feature von Bibliothek Y unterst\xFCtzt?", \xBBWas ist der kanonische Weg, Z zu tun?", \xBBVergleiche unsere Implementierung mit dem Standard".',
4595
+ review: "\xDCberpr\xFCfe die ausstehenden \xC4nderungen (aktueller Branch-Diff) in einem isolierten Subagenten \u2014 kennzeichnet Korrektheit, Sicherheit, fehlende Tests, versteckte Verhaltens\xE4nderungen; meldet Befund + pro-Problem datei:zeile. Schreibgesch\xFCtzt; das \xFCbergeordnete Element entscheidet, was zu tun ist.",
4596
+ securityReview: "Sicherheitsfokussierte \xDCberpr\xFCfung des aktuellen Branch-Diffs in einem isolierten Subagenten \u2014 kennzeichnet Injection/Authz/Secrets/Deserialisierung/Pfad-Traversal/Krypto-Probleme, mit Schweregrad. Schreibgesch\xFCtzt. Verwende beim Ausliefern von \xC4nderungen, die Auth, Eingabeanalyse, Datei-E/A oder externe Anfragen betreffen.",
4597
+ test: "F\xFChre die Testsuite des Projekts aus, diagnostiziere Fehler, schlage SEARCH/REPLACE-Fixes vor, wiederhole bis gr\xFCn (oder stoppe nach 2 Fixversuchen beim gleichen Fehler). Inline \u2014 l\xE4uft in der \xFCbergeordneten Schleife, sodass du die Edit-Blocks siehst und /apply verwenden kannst. Erkennt npm/pnpm/yarn/pytest/go/cargo."
4598
+ },
4599
+ shortcutsHelp: {
4600
+ ...EN.shortcutsHelp,
4601
+ title: "Tastenk\xFCrzel",
4602
+ groupInput: "Eingabe",
4603
+ groupNavigation: "Navigation",
4604
+ groupSession: "Sitzung",
4605
+ groupSystem: "System",
4606
+ descEnter: "Nachricht senden",
4607
+ descShiftEnter: "Neue Zeile",
4608
+ descCtrlEnter: "Neue Zeile",
4609
+ descCtrlJ: "Neue Zeile",
4610
+ descCtrlU: "Eingabe leeren",
4611
+ descCtrlW: "Wort l\xF6schen",
4612
+ descCtrlP: "Tastenk\xFCrzel anzeigen/ausblenden",
4613
+ descCtrlX: "Im Editor \xF6ffnen",
4614
+ descArrows: "Eingabeverlauf",
4615
+ descPgUpDown: "Seite scrollen",
4616
+ descCtrlL: "Bildschirm leeren",
4617
+ descCtrlB: "Seitenleiste umschalten",
4618
+ descNewSession: "Neue Sitzung",
4619
+ descListSessions: "Sitzungen auflisten",
4620
+ descSwitchModel: "Modell wechseln",
4621
+ descSwitchEffort: "Reasoning-Effort wechseln",
4622
+ descSwitchTheme: "Theme wechseln",
4623
+ descCtrlC: "Beenden",
4624
+ descEsc: "Stoppen / Abbrechen",
4625
+ descCtrlR: "Ausf\xFChrlich umschalten",
4626
+ descCtrlO: "Antwort erweitern (nur w\xE4hrend Streaming)",
4627
+ descHelp: "Alle Befehle anzeigen",
4628
+ descShiftTab: "Edit-Modus wechseln",
4629
+ descAltS: "Eingabe speichern / abrufen"
4630
+ },
4631
+ mcpCli: {
4632
+ ...EN.mcpCli,
4633
+ bundledCatalog: "Mitgelieferte MCP-Server (Offline-Katalog):",
4634
+ justFetched: "Gerade abgerufen",
4635
+ cachedAge: "Zwischengespeichert, {age}",
4636
+ moreAvailable: "Mehr verf\xFCgbar",
4637
+ allLoaded: "Alle geladen",
4638
+ morePagesAvailable: "\u25B8 mehr Seiten verf\xFCgbar \u2014 `reasonix mcp list --pages <n>` oder --all",
4639
+ installHint: "Installieren: reasonix mcp install <name>",
4640
+ usageSearch: "Verwendung: reasonix mcp search <abfrage>",
4641
+ usageInstall: "Verwendung: reasonix mcp install <name>",
4642
+ noMatchesFor: 'Keine Treffer f\xFCr "{q}" in {count} geladenen Eintr\xE4gen ({source})',
4643
+ matchCount: '{count} Treffer f\xFCr "{q}" in {source}-Registry ({loaded} durchsuchte Eintr\xE4ge):',
4644
+ moreLoaded: "\u2026 {count} weitere geladen \u2014 verwende `reasonix mcp search <abfrage>` zum Filtern",
4645
+ moreMatches: "\u2026 {count} weitere Treffer",
4646
+ installed: "Installiert: {spec}",
4647
+ noServerFound: 'Kein MCP-Server namens "{target}" gefunden nach {pages} Seite(n) der {source}-Registry.',
4648
+ noServerTryMore: "Versuche: reasonix mcp install {target} --max-pages 100",
4649
+ noInstallMeta: 'Konnte Installationsmetadaten f\xFCr "{name}" nicht ableiten \u2014 versuche `npx -y @smithery/cli install {name}` direkt.',
4650
+ buildSpecFailed: "Kann Installationsspec f\xFCr {name} nicht erstellen: {message}",
4651
+ alreadyInstalled: "Bereits installiert: {spec}"
4652
+ }
4653
+ };
4654
+
2915
4655
  // src/i18n/zh-CN.ts
2916
4656
  var zhCN = {
2917
4657
  common: {
@@ -3569,6 +5309,8 @@ var zhCN = {
3569
5309
  deepseek5xxUnreachable: " \u65E0\u6CD5\u4ECE\u4F60\u7684\u7F51\u7EDC\u8BBF\u95EE DeepSeek API \u2014 \u53EF\u80FD\u662F DS \u6574\u4F53\u6545\u969C\uFF0C\u4E5F\u53EF\u80FD\u662F\u672C\u5730\u7F51\u7EDC\u95EE\u9898\u3002",
3570
5310
  deepseek5xxActionNetwork: " \u5EFA\u8BAE\uFF1A(1) \u68C0\u67E5\u7F51\u7EDC\uFF0C(2) \u7B49 30 \u79D2\u540E\u91CD\u8BD5\uFF0C(3) \u67E5\u770B\u72B6\u6001\u9875 https://status.deepseek.com\u3002",
3571
5311
  deepseek5xxActionRetry: " \u5EFA\u8BAE\uFF1A(1) \u7B49 30 \u79D2\u540E\u91CD\u8BD5\uFF0C(2) \u7528 /model \u5207\u6362\u6A21\u578B\uFF0C(3) \u67E5\u770B\u72B6\u6001\u9875 https://status.deepseek.com\u3002",
5312
+ upstream5xxHead: "\u4E0A\u6E38\u670D\u52A1\u4E0D\u53EF\u7528\uFF08{status}\uFF09\uFF0C\u76EE\u6807\u5730\u5740 {host} \u2014 \u4F60\u914D\u7F6E\u7684 API \u7AEF\u70B9\u8FD4\u56DE\u4E86\u670D\u52A1\u5668\u9519\u8BEF\uFF0C\u4E0D\u662F Reasonix \u6545\u969C\u3002\u5DF2\u6309\u6307\u6570\u9000\u907F\u91CD\u8BD5 4 \u6B21\u3002",
5313
+ upstream5xxActionRetry: " \u5EFA\u8BAE\uFF1A(1) \u786E\u8BA4\u672C\u5730/\u4EE3\u7406\u6A21\u578B\u670D\u52A1\u5728\u7EBF\uFF0C(2) \u7B49\u4E00\u4F1A\u513F\u518D\u91CD\u8BD5\uFF0C(3) \u7528 /model \u5207\u6362\u6A21\u578B\u3002",
3572
5314
  innerNoMessage: "\uFF08\u65E0\u9519\u8BEF\u4FE1\u606F\uFF09",
3573
5315
  reasonAborted: "[\u7528\u6237\u5DF2\u4E2D\u65AD\uFF08Esc\uFF09 \u2014 \u6B63\u5728\u603B\u7ED3\u5230\u76EE\u524D\u4E3A\u6B62\u7684\u53D1\u73B0]",
3574
5316
  reasonContextGuard: "[\u4E0A\u4E0B\u6587\u989D\u5EA6\u5373\u5C06\u8017\u5C3D \u2014 \u5728\u4E0B\u4E00\u6B21\u8C03\u7528\u6EA2\u51FA\u4E4B\u524D\u5148\u603B\u7ED3]",
@@ -3749,6 +5491,7 @@ var zhCN = {
3749
5491
  modelSet: "model \u2192 {id}",
3750
5492
  effortStatus: "effort \u2192 {current} \uFF08\u53EF\u9009\uFF1A{list}\uFF09",
3751
5493
  effortUsage: "\u7528\u6CD5\uFF1A/effort <{list}> \uFF08high \u4E3A\u5B89\u5168\u9ED8\u8BA4\uFF1Bmax \u662F DeepSeek \u6269\u5C55\uFF09",
5494
+ effortUsageNoMax: "\u7528\u6CD5\uFF1A/effort <{list}>",
3752
5495
  effortSet: "effort \u2192 {effort}",
3753
5496
  budgetNoCap: "\u672A\u8BBE\u7F6E\u4F1A\u8BDD\u9884\u7B97 \u2014 Reasonix \u5C06\u6301\u7EED\u8FD0\u884C\u76F4\u5230\u60A8\u505C\u6B62\u3002\u4F7F\u7528\u4EE5\u4E0B\u65B9\u5F0F\u8BBE\u7F6E\uFF1A/budget <usd> \uFF08\u4F8B\u5982 /budget 5\uFF09",
3754
5497
  budgetStatus: "\u9884\u7B97\uFF1A${spent} / ${cap}\uFF08{pct}%\uFF09\xB7 /budget off \u6E05\u9664\uFF0C/budget <usd> \u66F4\u6539",
@@ -4564,13 +6307,18 @@ var zhCN = {
4564
6307
  // src/i18n/index.ts
4565
6308
  var translations = {
4566
6309
  EN,
4567
- "zh-CN": zhCN
6310
+ "zh-CN": zhCN,
6311
+ de
4568
6312
  };
4569
- function detectSystemLanguage(locale = Intl.DateTimeFormat().resolvedOptions().locale) {
6313
+ function detectSystemLanguage(locale = systemLocale()) {
4570
6314
  if (locale.startsWith("zh")) return "zh-CN";
4571
6315
  if (locale.startsWith("en")) return "EN";
6316
+ if (locale.startsWith("de")) return "de";
4572
6317
  return null;
4573
6318
  }
6319
+ function systemLocale() {
6320
+ return process.env.LC_ALL || process.env.LC_MESSAGES || process.env.LANG || Intl.DateTimeFormat().resolvedOptions().locale;
6321
+ }
4574
6322
  var currentLang = loadLanguage() ?? detectSystemLanguage() ?? "EN";
4575
6323
  function t(path2, params) {
4576
6324
  const parts = path2.split(".");
@@ -4667,7 +6415,7 @@ function matchesTool(hook, toolName) {
4667
6415
  }
4668
6416
  var HOOK_OUTPUT_CAP_BYTES = 256 * 1024;
4669
6417
  function defaultSpawner(input) {
4670
- return new Promise((resolve15) => {
6418
+ return new Promise((resolve16) => {
4671
6419
  const child = spawn(input.command, {
4672
6420
  cwd: input.cwd,
4673
6421
  shell: true,
@@ -4712,7 +6460,7 @@ function defaultSpawner(input) {
4712
6460
  child.stderr.on("data", (chunk) => onChunk("stderr", chunk));
4713
6461
  child.once("error", (err) => {
4714
6462
  clearTimeout(timer);
4715
- resolve15({
6463
+ resolve16({
4716
6464
  exitCode: null,
4717
6465
  stdout: Buffer.concat(stdoutChunks).toString("utf8"),
4718
6466
  stderr: Buffer.concat(stderrChunks).toString("utf8"),
@@ -4723,7 +6471,7 @@ function defaultSpawner(input) {
4723
6471
  });
4724
6472
  child.once("close", (code) => {
4725
6473
  clearTimeout(timer);
4726
- resolve15({
6474
+ resolve16({
4727
6475
  exitCode: code,
4728
6476
  stdout: Buffer.concat(stdoutChunks).toString("utf8").trim(),
4729
6477
  stderr: Buffer.concat(stderrChunks).toString("utf8").trim(),
@@ -4795,6 +6543,59 @@ import { createRequire } from "module";
4795
6543
  import { dirname as dirname2, join as join3 } from "path";
4796
6544
  import { fileURLToPath } from "url";
4797
6545
  import { gunzipSync } from "zlib";
6546
+
6547
+ // src/core/lru.ts
6548
+ var LruCache = class {
6549
+ constructor(limit) {
6550
+ this.limit = limit;
6551
+ }
6552
+ limit;
6553
+ map = /* @__PURE__ */ new Map();
6554
+ get(key) {
6555
+ if (!this.map.has(key)) return void 0;
6556
+ const v = this.map.get(key);
6557
+ this.map.delete(key);
6558
+ this.map.set(key, v);
6559
+ return v;
6560
+ }
6561
+ set(key, value) {
6562
+ if (this.map.has(key)) {
6563
+ this.map.delete(key);
6564
+ } else if (this.map.size >= this.limit) {
6565
+ const oldest = this.map.keys().next().value;
6566
+ if (oldest !== void 0) this.map.delete(oldest);
6567
+ }
6568
+ this.map.set(key, value);
6569
+ }
6570
+ get size() {
6571
+ return this.map.size;
6572
+ }
6573
+ clear() {
6574
+ this.map.clear();
6575
+ }
6576
+ };
6577
+ var TtlLruCache = class {
6578
+ constructor(limit, ttlMs) {
6579
+ this.ttlMs = ttlMs;
6580
+ this.inner = new LruCache(limit);
6581
+ }
6582
+ ttlMs;
6583
+ inner;
6584
+ get(key) {
6585
+ const e = this.inner.get(key);
6586
+ if (!e) return void 0;
6587
+ if (e.expiresAt <= Date.now()) return void 0;
6588
+ return e.v;
6589
+ }
6590
+ set(key, value) {
6591
+ this.inner.set(key, { v: value, expiresAt: Date.now() + this.ttlMs });
6592
+ }
6593
+ clear() {
6594
+ this.inner.clear();
6595
+ }
6596
+ };
6597
+
6598
+ // src/tokenizer.ts
4798
6599
  function buildByteToChar() {
4799
6600
  const result = new Array(256);
4800
6601
  const bs = [];
@@ -4897,10 +6698,13 @@ function byteLevelEncode(s, byteToChar) {
4897
6698
  for (let i = 0; i < bytes.length; i++) out += byteToChar[bytes[i]];
4898
6699
  return out;
4899
6700
  }
6701
+ var bpeCache = new LruCache(8192);
4900
6702
  function bpeEncode(piece, mergeRank) {
4901
6703
  if (piece.length <= 1) return piece ? [piece] : [];
4902
- let word = Array.from(piece);
4903
- while (true) {
6704
+ const cached2 = bpeCache.get(piece);
6705
+ if (cached2 !== void 0) return cached2;
6706
+ const word = Array.from(piece);
6707
+ while (word.length > 1) {
4904
6708
  let bestIdx = -1;
4905
6709
  let bestRank = Number.POSITIVE_INFINITY;
4906
6710
  for (let i = 0; i < word.length - 1; i++) {
@@ -4913,13 +6717,9 @@ function bpeEncode(piece, mergeRank) {
4913
6717
  }
4914
6718
  }
4915
6719
  if (bestIdx < 0) break;
4916
- word = [
4917
- ...word.slice(0, bestIdx),
4918
- word[bestIdx] + word[bestIdx + 1],
4919
- ...word.slice(bestIdx + 2)
4920
- ];
4921
- if (word.length === 1) break;
6720
+ word.splice(bestIdx, 2, word[bestIdx] + word[bestIdx + 1]);
4922
6721
  }
6722
+ bpeCache.set(piece, word);
4923
6723
  return word;
4924
6724
  }
4925
6725
  function encode(text) {
@@ -4974,10 +6774,6 @@ function countTokensBounded(text, maxChars = DEFAULT_BOUNDED_TOKENIZE_CHARS) {
4974
6774
  const ratio = sampleChars > 0 ? sampleTokens / sampleChars : 0.3;
4975
6775
  return Math.max(1, Math.ceil(text.length * ratio));
4976
6776
  }
4977
- var BOS = "<\uFF5Cbegin\u2581of\u2581sentence\uFF5C>";
4978
- var EOS = "<\uFF5Cend\u2581of\u2581sentence\uFF5C>";
4979
- var USER_SP = "<\uFF5CUser\uFF5C>";
4980
- var ASSISTANT_SP = "<\uFF5CAssistant\uFF5C>";
4981
6777
  var THINK_START = "<think>";
4982
6778
  var THINK_END = "</think>";
4983
6779
  var DSML = "\uFF5CDSML\uFF5C";
@@ -4986,7 +6782,6 @@ var TC_END = `</${DSML}tool_calls>`;
4986
6782
  var INVOKE_BEGIN = `<${DSML}invoke name="`;
4987
6783
  var INVOKE_END = `</${DSML}invoke>`;
4988
6784
  var PARAM_TEMPLATE = `<${DSML}parameter name="{key}" string="{is_str}">{value}</${DSML}parameter>`;
4989
- var TOOL_RESULT_TEMPLATE = "<tool_result>{content}</tool_result>";
4990
6785
  var toolsTemplateCache = /* @__PURE__ */ new WeakMap();
4991
6786
  function renderTools(tools) {
4992
6787
  const cached2 = toolsTemplateCache.get(tools);
@@ -5023,142 +6818,53 @@ You MUST strictly follow the above defined tool name and parameter schemas to in
5023
6818
  toolsTemplateCache.set(tools, rendered);
5024
6819
  return rendered;
5025
6820
  }
5026
- function encodeArgumentsToDsml(argsJson) {
5027
- let args;
5028
- try {
5029
- args = JSON.parse(argsJson);
5030
- } catch {
5031
- args = { arguments: argsJson };
5032
- }
5033
- return Object.entries(args).map(
5034
- ([k, v]) => PARAM_TEMPLATE.replace("{key}", k).replace("{is_str}", typeof v === "string" ? "true" : "false").replace("{value}", typeof v === "string" ? v : JSON.stringify(v))
5035
- ).join("\n");
5036
- }
5037
- function renderToolCallsDsml(toolCalls) {
5038
- const invokes = toolCalls.map((tc) => {
5039
- const name = tc.function?.name ?? "";
5040
- const argsJson = tc.function?.arguments ?? "{}";
5041
- return `${INVOKE_BEGIN + name}">
5042
- ${encodeArgumentsToDsml(argsJson)}
5043
- ${INVOKE_END}`;
5044
- }).join("\n");
5045
- return `
5046
-
5047
- ${TC_BEGIN}
5048
- ${invokes}
5049
- ${TC_END}`;
6821
+ var PER_MESSAGE_TEMPLATE_TOKENS = 6;
6822
+ var contentTokenCache = new LruCache(4096);
6823
+ function cachedBoundedTokens(s) {
6824
+ if (s.length === 0) return 0;
6825
+ const cached2 = contentTokenCache.get(s);
6826
+ if (cached2 !== void 0) return cached2;
6827
+ const n = countTokensBounded(s);
6828
+ contentTokenCache.set(s, n);
6829
+ return n;
5050
6830
  }
5051
- function mergeToolMessages(messages) {
5052
- const merged = [];
5053
- for (const msg of messages) {
5054
- const role = msg.role ?? "user";
5055
- if (role === "tool") {
5056
- const toolBlock = TOOL_RESULT_TEMPLATE.replace("{content}", msg.content ?? "");
5057
- const last = merged[merged.length - 1];
5058
- if (last && last.role === "user" && Array.isArray(last._toolBlocks) && Array.isArray(last._textParts)) {
5059
- last._toolBlocks.push(toolBlock);
5060
- last.content = `${last._textParts.join("\n\n")}
5061
-
5062
- ${last._toolBlocks.join("\n")}`.replace(
5063
- /^\n\n/,
5064
- ""
5065
- );
5066
- } else {
5067
- merged.push({
5068
- role: "user",
5069
- content: toolBlock,
5070
- _textParts: [],
5071
- _toolBlocks: [toolBlock]
5072
- });
5073
- }
5074
- } else if (role === "user") {
5075
- const text = msg.content ?? "";
5076
- const last = merged[merged.length - 1];
5077
- if (last && last.role === "user" && Array.isArray(last._toolBlocks) && Array.isArray(last._textParts)) {
5078
- last._textParts.push(text);
5079
- last.content = `${last._textParts.join("\n\n")}
5080
-
5081
- ${last._toolBlocks.join("\n\n")}`.replace(
5082
- /^\n\n/,
5083
- ""
5084
- );
5085
- } else {
5086
- merged.push({
5087
- ...msg,
5088
- role: "user",
5089
- content: text,
5090
- _textParts: [text],
5091
- _toolBlocks: []
5092
- });
5093
- }
5094
- } else {
5095
- merged.push({ ...msg });
5096
- }
5097
- }
5098
- for (const m of merged) {
5099
- m._textParts = void 0;
5100
- m._toolBlocks = void 0;
6831
+ function tokensForMessage(m, dropThisReasoning) {
6832
+ let n = 0;
6833
+ if (typeof m.content === "string" && m.content.length > 0) {
6834
+ n += cachedBoundedTokens(m.content);
5101
6835
  }
5102
- return merged;
5103
- }
5104
- function dropThinkingMessages(messages) {
5105
- let lastUserIdx = -1;
5106
- for (let i = messages.length - 1; i >= 0; i--) {
5107
- const role = messages[i].role;
5108
- if (role === "user" || role === "developer") {
5109
- lastUserIdx = i;
5110
- break;
6836
+ if (m.role === "assistant") {
6837
+ if (!dropThisReasoning && typeof m.reasoning_content === "string" && m.reasoning_content.length > 0) {
6838
+ n += cachedBoundedTokens(m.reasoning_content);
5111
6839
  }
5112
- }
5113
- if (lastUserIdx < 0) return messages;
5114
- const result = [];
5115
- for (let i = 0; i < messages.length; i++) {
5116
- const msg = messages[i];
5117
- if (i < lastUserIdx && msg.role === "developer") continue;
5118
- if (i < lastUserIdx && msg.role === "assistant") {
5119
- result.push({ ...msg, reasoning_content: null });
5120
- } else {
5121
- result.push(msg);
6840
+ const tcs = m.tool_calls;
6841
+ if (Array.isArray(tcs) && tcs.length > 0) {
6842
+ n += cachedBoundedTokens(JSON.stringify(tcs));
5122
6843
  }
5123
6844
  }
5124
- return result;
6845
+ return n;
5125
6846
  }
5126
- function formatDeepSeekPrompt(messages, drop_thinking = false) {
5127
- if (messages.length === 0) return ASSISTANT_SP + THINK_END;
5128
- let msgs = messages;
6847
+ function estimateConversationTokens(messages, drop_thinking = false) {
6848
+ if (messages.length === 0) return 0;
6849
+ let lastUserOrDev = -1;
5129
6850
  if (drop_thinking) {
5130
- msgs = dropThinkingMessages(msgs);
5131
- }
5132
- const merged = mergeToolMessages(msgs);
5133
- let prompt = BOS;
5134
- for (let i = 0; i < merged.length; i++) {
5135
- const msg = merged[i];
5136
- const role = msg.role ?? "user";
5137
- const nextRole = i + 1 < merged.length ? merged[i + 1].role ?? "user" : null;
5138
- if (role === "system") {
5139
- prompt += msg.content ?? "";
5140
- } else if (role === "user") {
5141
- prompt += USER_SP + (msg.content ?? "");
5142
- if (nextRole === "assistant" || nextRole === null) {
5143
- prompt += ASSISTANT_SP + THINK_END;
5144
- }
5145
- } else if (role === "assistant") {
5146
- if (msg.reasoning_content) {
5147
- prompt += THINK_START + msg.reasoning_content + THINK_END;
5148
- }
5149
- if (msg.content) prompt += msg.content;
5150
- const tcs = msg.tool_calls;
5151
- if (Array.isArray(tcs) && tcs.length > 0) {
5152
- prompt += renderToolCallsDsml(tcs);
6851
+ for (let i = messages.length - 1; i >= 0; i--) {
6852
+ const r = messages[i].role;
6853
+ if (r === "user" || r === "developer") {
6854
+ lastUserOrDev = i;
6855
+ break;
5153
6856
  }
5154
- prompt += EOS;
5155
6857
  }
5156
6858
  }
5157
- return prompt;
5158
- }
5159
- function estimateConversationTokens(messages, drop_thinking = false) {
5160
- if (messages.length === 0) return 0;
5161
- return countTokensBounded(formatDeepSeekPrompt(messages, drop_thinking));
6859
+ let total = 2;
6860
+ for (let i = 0; i < messages.length; i++) {
6861
+ const m = messages[i];
6862
+ if (drop_thinking && i < lastUserOrDev && m.role === "developer") continue;
6863
+ total += PER_MESSAGE_TEMPLATE_TOKENS;
6864
+ const dropReasoning = drop_thinking && i < lastUserOrDev && m.role === "assistant";
6865
+ total += tokensForMessage(m, dropReasoning);
6866
+ }
6867
+ return total;
5162
6868
  }
5163
6869
  function estimateRequestTokens(messages, toolSpecs, drop_thinking = false) {
5164
6870
  let total = estimateConversationTokens(messages, drop_thinking);
@@ -5236,6 +6942,81 @@ function setByPath(target, path2, value) {
5236
6942
  cur[path2[path2.length - 1]] = value;
5237
6943
  }
5238
6944
 
6945
+ // src/tools/truncated-result-saver.ts
6946
+ import { randomUUID } from "crypto";
6947
+ import {
6948
+ chmodSync as chmodSync2,
6949
+ existsSync as existsSync3,
6950
+ mkdirSync as mkdirSync2,
6951
+ readdirSync,
6952
+ rmSync,
6953
+ statSync,
6954
+ writeFileSync as writeFileSync2
6955
+ } from "fs";
6956
+ import { homedir as homedir3 } from "os";
6957
+ import { join as join4, relative, resolve as resolve2 } from "path";
6958
+ var TRUNCATED_DIR = "truncated-results";
6959
+ var DEFAULT_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
6960
+ function sanitizeToolName(name) {
6961
+ return name.replace(/[^\w\-]/g, "_").slice(0, 48) || "unknown";
6962
+ }
6963
+ function storageDir(rootDir) {
6964
+ const base = rootDir ? join4(resolve2(rootDir), ".reasonix") : join4(homedir3(), ".reasonix");
6965
+ return join4(base, TRUNCATED_DIR);
6966
+ }
6967
+ function resultFilename(toolName) {
6968
+ const ts = Date.now().toString();
6969
+ const suffix = randomUUID().slice(0, 8);
6970
+ const safeName = sanitizeToolName(toolName);
6971
+ return `${ts}-${suffix}-${safeName}.txt`;
6972
+ }
6973
+ function saveTruncatedResult(content, toolName, rootDir) {
6974
+ cleanupOldResults(rootDir);
6975
+ const dir = storageDir(rootDir);
6976
+ if (!existsSync3(dir)) {
6977
+ mkdirSync2(dir, { recursive: true });
6978
+ }
6979
+ const filename = resultFilename(toolName);
6980
+ const absPath = join4(dir, filename);
6981
+ writeFileSync2(absPath, content, "utf-8");
6982
+ try {
6983
+ chmodSync2(absPath, 384);
6984
+ } catch {
6985
+ }
6986
+ if (rootDir) {
6987
+ const absRoot = resolve2(rootDir);
6988
+ return relative(absRoot, absPath).replaceAll("\\", "/");
6989
+ }
6990
+ return absPath.replaceAll("\\", "/");
6991
+ }
6992
+ function cleanupOldResults(rootDir, maxAgeMs = DEFAULT_MAX_AGE_MS) {
6993
+ const dir = storageDir(rootDir);
6994
+ if (!existsSync3(dir)) return;
6995
+ const cutoff = Date.now() - maxAgeMs;
6996
+ let entries;
6997
+ try {
6998
+ entries = readdirSync(dir);
6999
+ } catch {
7000
+ return;
7001
+ }
7002
+ for (const entry of entries) {
7003
+ if (!entry.endsWith(".txt")) continue;
7004
+ const abs = join4(dir, entry);
7005
+ try {
7006
+ const st = statSync(abs);
7007
+ if (st.isFile() && st.mtimeMs < cutoff) {
7008
+ rmSync(abs);
7009
+ }
7010
+ } catch {
7011
+ }
7012
+ }
7013
+ }
7014
+ function shouldSkipSave(toolName, skipTruncationSave) {
7015
+ if (skipTruncationSave) return true;
7016
+ const alwaysSkip = /* @__PURE__ */ new Set(["get_env", "everything_get-env"]);
7017
+ return alwaysSkip.has(toolName);
7018
+ }
7019
+
5239
7020
  // src/tools.ts
5240
7021
  var ToolRegistry = class {
5241
7022
  _tools = /* @__PURE__ */ new Map();
@@ -5413,7 +7194,20 @@ var ToolRegistry = class {
5413
7194
  if (opts.maxResultChars !== void 0) {
5414
7195
  clipped = truncateForModel(clipped, opts.maxResultChars);
5415
7196
  }
5416
- finalResult = clipped;
7197
+ if (clipped !== str && !shouldSkipSave(name, tool?.skipTruncationSave)) {
7198
+ const relPath = saveTruncatedResult(str, name, opts.rootDir ?? process.cwd());
7199
+ const note = `Full result saved at: ${relPath}`;
7200
+ let annotated = str;
7201
+ if (opts.maxResultTokens !== void 0) {
7202
+ annotated = truncateForModelByTokens(annotated, opts.maxResultTokens, note);
7203
+ }
7204
+ if (opts.maxResultChars !== void 0) {
7205
+ annotated = truncateForModel(annotated, opts.maxResultChars, note);
7206
+ }
7207
+ finalResult = annotated;
7208
+ } else {
7209
+ finalResult = clipped;
7210
+ }
5417
7211
  } catch (err) {
5418
7212
  const e = err;
5419
7213
  if (typeof e.toToolResult === "function") {
@@ -5633,12 +7427,12 @@ async function waitForReady(ready, timeoutMs, serverName, signal) {
5633
7427
  let timer;
5634
7428
  let onAbort;
5635
7429
  try {
5636
- await new Promise((resolve15, reject) => {
7430
+ await new Promise((resolve16, reject) => {
5637
7431
  ready.then(
5638
7432
  () => {
5639
7433
  if (settled) return;
5640
7434
  settled = true;
5641
- resolve15();
7435
+ resolve16();
5642
7436
  },
5643
7437
  (err) => {
5644
7438
  if (settled) return;
@@ -5744,25 +7538,30 @@ function validateResultShape(result) {
5744
7538
  }
5745
7539
  }
5746
7540
  }
5747
- function truncateForModel(s, maxChars) {
7541
+ function truncateForModel(s, maxChars, extraNote) {
5748
7542
  if (s.length <= maxChars) return s;
5749
7543
  const tailBudget = Math.min(1024, Math.floor(maxChars * 0.1));
5750
7544
  const headBudget = Math.max(0, maxChars - tailBudget);
5751
7545
  const head = s.slice(0, headBudget);
5752
7546
  const tail = s.slice(-tailBudget);
5753
7547
  const dropped = s.length - head.length - tail.length;
7548
+ const note = extraNote ? ` \u2014 ${extraNote}` : "";
5754
7549
  return `${head}
5755
7550
 
5756
- [\u2026truncated ${dropped} chars \u2014 raise BridgeOptions.maxResultChars, or call the tool with a narrower scope (filter, head, pagination)\u2026]
7551
+ [\u2026truncated ${dropped} chars \u2014 raise BridgeOptions.maxResultChars, or call the tool with a narrower scope (filter, head, pagination)${note}\u2026]
5757
7552
 
5758
7553
  ${tail}`;
5759
7554
  }
5760
- function truncateForModelByTokens(s, maxTokens) {
7555
+ function truncateForModelByTokens(s, maxTokens, extraNote) {
5761
7556
  if (maxTokens <= 0) return "";
5762
7557
  if (s.length <= maxTokens) return s;
5763
7558
  if (s.length <= maxTokens * 4) {
5764
- const tokens = countTokens(s);
5765
- if (tokens <= maxTokens) return s;
7559
+ const est = countTokensBounded(s);
7560
+ if (Math.ceil(est * 1.15) <= maxTokens) return s;
7561
+ if (est <= maxTokens) {
7562
+ const tokens = countTokens(s);
7563
+ if (tokens <= maxTokens) return s;
7564
+ }
5766
7565
  }
5767
7566
  const markerOverhead = 48;
5768
7567
  const contentBudget = Math.max(0, maxTokens - markerOverhead);
@@ -5778,9 +7577,10 @@ function truncateForModelByTokens(s, maxTokens) {
5778
7577
  const ratio = sampleChars > 0 ? sampleTokens / sampleChars : 0.3;
5779
7578
  const estTotalTokens = Math.ceil(s.length * ratio);
5780
7579
  const droppedTokens = Math.max(0, estTotalTokens - sampleTokens);
7580
+ const note = extraNote ? ` \u2014 ${extraNote}` : "";
5781
7581
  return `${head}
5782
7582
 
5783
- [\u2026truncated ~${droppedTokens} tokens (${droppedChars} chars) \u2014 raise BridgeOptions.maxResultTokens, or call the tool with a narrower scope (filter, head, pagination)\u2026]
7583
+ [\u2026truncated ~${droppedTokens} tokens (${droppedChars} chars) \u2014 raise BridgeOptions.maxResultTokens, or call the tool with a narrower scope (filter, head, pagination)${note}\u2026]
5784
7584
 
5785
7585
  ${tail}`;
5786
7586
  }
@@ -5822,7 +7622,7 @@ function blockToString(block) {
5822
7622
  var COMPACTION_SUMMARY_MARKER = "[CONVERSATION HISTORY SUMMARY \u2014 earlier turns folded for context efficiency]\n\n";
5823
7623
 
5824
7624
  // packages/core-utils/src/tildeify.ts
5825
- import { homedir as homedir3 } from "os";
7625
+ import { homedir as homedir4 } from "os";
5826
7626
 
5827
7627
  // src/loop/thinking.ts
5828
7628
  function isThinkingModeModel(model) {
@@ -5862,19 +7662,19 @@ function buildSyntheticAssistantMessage(content, fallbackModel) {
5862
7662
  import { execFileSync } from "child_process";
5863
7663
  import {
5864
7664
  appendFileSync,
5865
- chmodSync as chmodSync2,
5866
- copyFileSync,
5867
- existsSync as existsSync3,
5868
- mkdirSync as mkdirSync2,
7665
+ chmodSync as chmodSync3,
7666
+ copyFileSync as copyFileSync2,
7667
+ existsSync as existsSync4,
7668
+ mkdirSync as mkdirSync3,
5869
7669
  readFileSync as readFileSync4,
5870
- readdirSync,
7670
+ readdirSync as readdirSync2,
5871
7671
  renameSync as renameSync2,
5872
- statSync,
5873
- unlinkSync,
5874
- writeFileSync as writeFileSync2
7672
+ statSync as statSync2,
7673
+ unlinkSync as unlinkSync2,
7674
+ writeFileSync as writeFileSync3
5875
7675
  } from "fs";
5876
- import { homedir as homedir4 } from "os";
5877
- import { dirname as dirname3, join as join4, posix as posixPath, win32 as win32Path } from "path";
7676
+ import { homedir as homedir5 } from "os";
7677
+ import { dirname as dirname3, join as join5, posix as posixPath, win32 as win32Path } from "path";
5878
7678
  var SESSION_SIDECAR_EXTS = [
5879
7679
  ".events.jsonl",
5880
7680
  ".meta.json",
@@ -5883,10 +7683,10 @@ var SESSION_SIDECAR_EXTS = [
5883
7683
  ".jsonl.bak"
5884
7684
  ];
5885
7685
  function sessionsDir() {
5886
- return join4(homedir4(), ".reasonix", "sessions");
7686
+ return join5(homedir5(), ".reasonix", "sessions");
5887
7687
  }
5888
7688
  function sessionPath(name) {
5889
- return join4(sessionsDir(), `${sanitizeName(name)}.jsonl`);
7689
+ return join5(sessionsDir(), `${sanitizeName(name)}.jsonl`);
5890
7690
  }
5891
7691
  function sanitizeName(name) {
5892
7692
  const cleaned = name.replace(/[^\w\-\u4e00-\u9fa5]/g, "_").slice(0, 64);
@@ -5897,7 +7697,7 @@ function timestampSuffix() {
5897
7697
  }
5898
7698
  function loadSessionMessages(name) {
5899
7699
  const path2 = sessionPath(name);
5900
- if (!existsSync3(path2)) return [];
7700
+ if (!existsSync4(path2)) return [];
5901
7701
  const live = readSessionMessages(path2);
5902
7702
  if (live && (live.messages.length > 0 || !live.hadContent)) return live.messages;
5903
7703
  const backup = readSessionMessages(sessionBackupPath(path2));
@@ -5924,33 +7724,43 @@ function readSessionMessages(path2) {
5924
7724
  }
5925
7725
  function appendSessionMessage(name, message) {
5926
7726
  const path2 = sessionPath(name);
5927
- mkdirSync2(dirname3(path2), { recursive: true });
7727
+ mkdirSync3(dirname3(path2), { recursive: true });
5928
7728
  appendFileSync(path2, `${JSON.stringify(message)}
5929
7729
  `, "utf8");
5930
7730
  try {
5931
- chmodSync2(path2, 384);
7731
+ chmodSync3(path2, 384);
5932
7732
  } catch {
5933
7733
  }
5934
7734
  }
5935
7735
  function listSessions(opts) {
5936
7736
  const dir = sessionsDir();
5937
- if (!existsSync3(dir)) return [];
7737
+ if (!existsSync4(dir)) return [];
5938
7738
  const want = opts?.workspaceFilter ? normalizeWorkspace(opts.workspaceFilter) : null;
7739
+ const legacyPrefix = want && opts?.includeLegacyWorkspaceMatches ? legacySessionPrefixForWorkspace(opts.workspaceFilter) : null;
5939
7740
  try {
5940
- const files = readdirSync(dir).filter(
7741
+ const files = readdirSync2(dir).filter(
5941
7742
  (f) => f.endsWith(".jsonl") && !f.endsWith(".events.jsonl")
5942
7743
  );
5943
7744
  return files.flatMap((file) => {
5944
- const path2 = join4(dir, file);
7745
+ const path2 = join5(dir, file);
5945
7746
  const name = file.replace(/\.jsonl$/, "");
5946
7747
  const meta = loadSessionMeta(name);
7748
+ let workspaceStatus;
5947
7749
  if (want !== null) {
5948
- if (typeof meta.workspace !== "string") return [];
5949
- if (normalizeWorkspace(meta.workspace) !== want) return [];
7750
+ if (typeof meta.workspace === "string") {
7751
+ if (normalizeWorkspace(meta.workspace) !== want) return [];
7752
+ workspaceStatus = "matched";
7753
+ } else if (legacyPrefix && name.startsWith(legacyPrefix)) {
7754
+ workspaceStatus = "legacy_missing_meta";
7755
+ } else {
7756
+ return [];
7757
+ }
5950
7758
  }
5951
- const stat2 = statSync(path2);
7759
+ const stat2 = statSync2(path2);
5952
7760
  const messageCount = countLines(path2);
5953
- return [{ name, path: path2, size: stat2.size, messageCount, mtime: stat2.mtime, meta }];
7761
+ return [
7762
+ { name, path: path2, size: stat2.size, messageCount, mtime: stat2.mtime, meta, workspaceStatus }
7763
+ ];
5954
7764
  }).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
5955
7765
  } catch {
5956
7766
  return [];
@@ -5964,12 +7774,17 @@ function normalizeWorkspace(p, platform = process.platform) {
5964
7774
  }
5965
7775
  return posixPath.resolve(p);
5966
7776
  }
7777
+ function legacySessionPrefixForWorkspace(workspace) {
7778
+ const normalized = normalizeWorkspace(workspace);
7779
+ const base = process.platform === "win32" ? win32Path.basename(normalized) : posixPath.basename(normalized);
7780
+ return `${sanitizeName(`code-${base}`)}-`;
7781
+ }
5967
7782
  function metaPath(name) {
5968
- return join4(sessionsDir(), `${sanitizeName(name)}.meta.json`);
7783
+ return join5(sessionsDir(), `${sanitizeName(name)}.meta.json`);
5969
7784
  }
5970
7785
  function loadSessionMeta(name) {
5971
7786
  const p = metaPath(name);
5972
- if (!existsSync3(p)) return {};
7787
+ if (!existsSync4(p)) return {};
5973
7788
  try {
5974
7789
  const raw = JSON.parse(readFileSync4(p, "utf8"));
5975
7790
  return raw && typeof raw === "object" ? raw : {};
@@ -5981,10 +7796,10 @@ function patchSessionMeta(name, patch) {
5981
7796
  const cur = loadSessionMeta(name);
5982
7797
  const next = { ...cur, ...patch };
5983
7798
  const p = metaPath(name);
5984
- mkdirSync2(dirname3(p), { recursive: true });
5985
- writeFileSync2(p, JSON.stringify(next), "utf8");
7799
+ mkdirSync3(dirname3(p), { recursive: true });
7800
+ writeFileSync3(p, JSON.stringify(next), "utf8");
5986
7801
  try {
5987
- chmodSync2(p, 384);
7802
+ chmodSync3(p, 384);
5988
7803
  } catch {
5989
7804
  }
5990
7805
  return next;
@@ -5995,12 +7810,12 @@ function renameSession(oldName, newName) {
5995
7810
  if (safeOld === safeNew) return false;
5996
7811
  const oldJsonl = sessionPath(oldName);
5997
7812
  const newJsonl = sessionPath(newName);
5998
- if (!existsSync3(oldJsonl) || existsSync3(newJsonl)) return false;
7813
+ if (!existsSync4(oldJsonl) || existsSync4(newJsonl)) return false;
5999
7814
  renameSync2(oldJsonl, newJsonl);
6000
7815
  for (const ext of SESSION_SIDECAR_EXTS) {
6001
7816
  const oldP = oldJsonl.replace(/\.jsonl$/, ext);
6002
7817
  const newP = newJsonl.replace(/\.jsonl$/, ext);
6003
- if (existsSync3(oldP)) {
7818
+ if (existsSync4(oldP)) {
6004
7819
  try {
6005
7820
  renameSync2(oldP, newP);
6006
7821
  } catch {
@@ -6012,11 +7827,11 @@ function renameSession(oldName, newName) {
6012
7827
  function deleteSession(name) {
6013
7828
  const path2 = sessionPath(name);
6014
7829
  try {
6015
- unlinkSync(path2);
7830
+ unlinkSync2(path2);
6016
7831
  for (const ext of SESSION_SIDECAR_EXTS) {
6017
7832
  const sidecar = path2.replace(/\.jsonl$/, ext);
6018
7833
  try {
6019
- unlinkSync(sidecar);
7834
+ unlinkSync2(sidecar);
6020
7835
  } catch {
6021
7836
  }
6022
7837
  }
@@ -6027,33 +7842,22 @@ function deleteSession(name) {
6027
7842
  }
6028
7843
  function rewriteSession(name, messages) {
6029
7844
  const path2 = sessionPath(name);
6030
- mkdirSync2(dirname3(path2), { recursive: true });
7845
+ mkdirSync3(dirname3(path2), { recursive: true });
6031
7846
  const body = messages.map((m) => JSON.stringify(m)).join("\n");
6032
7847
  const tmp = `${path2}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
6033
- try {
6034
- writeFileSync2(tmp, body ? `${body}
6035
- ` : "", "utf8");
6036
- chmodPrivate(tmp);
6037
- if (existsSync3(path2) && statSync(path2).size > 0) {
6038
- const backup = sessionBackupPath(path2);
6039
- copyFileSync(path2, backup);
6040
- chmodPrivate(backup);
6041
- }
6042
- renameSync2(tmp, path2);
6043
- chmodPrivate(path2);
6044
- } catch (err) {
6045
- try {
6046
- unlinkSync(tmp);
6047
- } catch {
6048
- }
6049
- throw err;
7848
+ if (existsSync4(path2) && statSync2(path2).size > 0) {
7849
+ const backup = sessionBackupPath(path2);
7850
+ copyFileSync2(path2, backup);
7851
+ chmodPrivate(backup);
6050
7852
  }
7853
+ atomicWriteSync(path2, body ? `${body}
7854
+ ` : "", tmp);
6051
7855
  }
6052
7856
  function archiveSession(name) {
6053
7857
  const path2 = sessionPath(name);
6054
- if (!existsSync3(path2)) return null;
7858
+ if (!existsSync4(path2)) return null;
6055
7859
  try {
6056
- if (statSync(path2).size === 0) return null;
7860
+ if (statSync2(path2).size === 0) return null;
6057
7861
  } catch {
6058
7862
  return null;
6059
7863
  }
@@ -6081,7 +7885,7 @@ function sessionBackupPath(path2) {
6081
7885
  }
6082
7886
  function chmodPrivate(path2) {
6083
7887
  try {
6084
- chmodSync2(path2, 384);
7888
+ chmodSync3(path2, 384);
6085
7889
  } catch {
6086
7890
  }
6087
7891
  }
@@ -6544,8 +8348,88 @@ var InflightSet = class {
6544
8348
  }
6545
8349
  };
6546
8350
 
8351
+ // src/loop/dispatch.ts
8352
+ function readParallelMax() {
8353
+ const raw = Number.parseInt(process.env.REASONIX_PARALLEL_MAX ?? "", 10);
8354
+ return Number.isFinite(raw) && raw >= 1 ? Math.min(raw, 16) : 3;
8355
+ }
8356
+ function readDispatchSerial() {
8357
+ return (process.env.REASONIX_TOOL_DISPATCH ?? "auto").toLowerCase() === "serial";
8358
+ }
8359
+ async function* dispatchToolCallsChunked(repairedCalls, ctx) {
8360
+ const dispatchSerial = readDispatchSerial();
8361
+ const parallelMax = readParallelMax();
8362
+ let callIdx = 0;
8363
+ while (callIdx < repairedCalls.length) {
8364
+ const chunk = [];
8365
+ if (!dispatchSerial) {
8366
+ while (callIdx < repairedCalls.length && chunk.length < parallelMax && ctx.isParallelSafe(repairedCalls[callIdx]?.function?.name ?? "")) {
8367
+ chunk.push(repairedCalls[callIdx++]);
8368
+ }
8369
+ }
8370
+ if (chunk.length === 0) {
8371
+ chunk.push(repairedCalls[callIdx++]);
8372
+ }
8373
+ for (const call of chunk) {
8374
+ const callId = ctx.inflightIdFor(call);
8375
+ ctx.inflightAdd(callId);
8376
+ yield {
8377
+ turn: ctx.turn,
8378
+ role: "tool_start",
8379
+ content: "",
8380
+ toolName: call.function?.name ?? "",
8381
+ toolArgs: call.function?.arguments ?? "{}",
8382
+ callId
8383
+ };
8384
+ }
8385
+ const settled = await Promise.allSettled(chunk.map((c) => ctx.runOne(c, ctx.signal)));
8386
+ for (let k = 0; k < chunk.length; k++) {
8387
+ const call = chunk[k];
8388
+ const name = call.function?.name ?? "";
8389
+ const args = call.function?.arguments ?? "{}";
8390
+ const s = settled[k];
8391
+ let result;
8392
+ let preWarnings = [];
8393
+ let postWarnings = [];
8394
+ if (s.status === "fulfilled") {
8395
+ preWarnings = s.value.preWarnings;
8396
+ postWarnings = s.value.postWarnings;
8397
+ result = s.value.result;
8398
+ } else {
8399
+ const err = s.reason instanceof Error ? s.reason : new Error(String(s.reason));
8400
+ result = JSON.stringify({ error: `${err.name}: ${err.message}` });
8401
+ }
8402
+ for (const w of preWarnings) yield w;
8403
+ for (const w of postWarnings) yield w;
8404
+ const rateLimited = parseRateLimitedToolResult(result);
8405
+ if (rateLimited && !ctx.rateLimitState.shown) {
8406
+ ctx.rateLimitState.shown = true;
8407
+ yield {
8408
+ turn: ctx.turn,
8409
+ role: "warning",
8410
+ content: rateLimited.message
8411
+ };
8412
+ }
8413
+ ctx.appendAndPersist({
8414
+ role: "tool",
8415
+ tool_call_id: call.id ?? "",
8416
+ name,
8417
+ content: result
8418
+ });
8419
+ yield {
8420
+ turn: ctx.turn,
8421
+ role: "tool",
8422
+ content: result,
8423
+ toolName: name,
8424
+ toolArgs: args,
8425
+ callId: ctx.inflightIdFor(call)
8426
+ };
8427
+ }
8428
+ }
8429
+ }
8430
+
6547
8431
  // src/loop/errors.ts
6548
- function formatLoopError(err, probe) {
8432
+ function formatLoopError(err, probe, opts) {
6549
8433
  const msg = err.message ?? "";
6550
8434
  if (msg.includes("maximum context length")) {
6551
8435
  const reqMatch = msg.match(/requested\s+(\d+)\s+tokens/);
@@ -6562,7 +8446,7 @@ function formatLoopError(err, probe) {
6562
8446
  if (status === "422") return t("errors.badparam422", { inner });
6563
8447
  if (status === "400") return t("errors.badrequest400", { inner });
6564
8448
  if (status === "429") return t("errors.concurrency429", { inner });
6565
- if (is5xxStatus(status)) return formatDeepSeek5xx(status, probe);
8449
+ if (is5xxStatus(status)) return format5xx(status, probe, opts?.upstreamHost);
6566
8450
  return msg;
6567
8451
  }
6568
8452
  function is5xxError(err) {
@@ -6574,15 +8458,40 @@ async function probeDeepSeekReachable(client, timeoutMs = 1500) {
6574
8458
  const balance = await client.getBalance({ signal: AbortSignal.timeout(timeoutMs) });
6575
8459
  return { reachable: balance !== null };
6576
8460
  }
8461
+ function isDeepSeekHost(baseUrl) {
8462
+ if (!baseUrl) return false;
8463
+ try {
8464
+ const host = new URL(baseUrl).hostname.toLowerCase();
8465
+ return host === "api.deepseek.com";
8466
+ } catch {
8467
+ return false;
8468
+ }
8469
+ }
6577
8470
  function is5xxStatus(status) {
6578
8471
  return status === "500" || status === "502" || status === "503" || status === "504";
6579
8472
  }
8473
+ function format5xx(status, probe, upstreamHost) {
8474
+ if (upstreamHost !== void 0 && !isDeepSeekHost(upstreamHost)) {
8475
+ return formatUpstream5xx(status, upstreamHost);
8476
+ }
8477
+ return formatDeepSeek5xx(status, probe);
8478
+ }
6580
8479
  function formatDeepSeek5xx(status, probe) {
6581
8480
  const head = t("errors.deepseek5xxHead", { status });
6582
8481
  const probeNote = probe === void 0 ? "" : probe.reachable ? t("errors.deepseek5xxReachable") : t("errors.deepseek5xxUnreachable");
6583
8482
  const action = probe?.reachable === false ? t("errors.deepseek5xxActionNetwork") : t("errors.deepseek5xxActionRetry");
6584
8483
  return `${head}${probeNote}${action}`;
6585
8484
  }
8485
+ function formatUpstream5xx(status, baseUrl) {
8486
+ let host = baseUrl;
8487
+ try {
8488
+ host = new URL(baseUrl).host || baseUrl;
8489
+ } catch {
8490
+ }
8491
+ const head = t("errors.upstream5xxHead", { status, host });
8492
+ const action = t("errors.upstream5xxActionRetry");
8493
+ return `${head}${action}`;
8494
+ }
6586
8495
  function reasonPrefixFor(reason) {
6587
8496
  if (reason === "aborted") return t("errors.reasonAborted");
6588
8497
  if (reason === "context-guard") return t("errors.reasonContextGuard");
@@ -6695,6 +8604,56 @@ function shrinkOversizedToolResultsByTokens(messages, maxTokens) {
6695
8604
  });
6696
8605
  return { messages: out, healedCount, tokensSaved, charsSaved };
6697
8606
  }
8607
+ function shrinkOversizedToolCallArgsByTokens(messages, maxTokens) {
8608
+ let healedCount = 0;
8609
+ let tokensSaved = 0;
8610
+ let charsSaved = 0;
8611
+ const out = messages.map((msg) => {
8612
+ if (msg.role !== "assistant" || !Array.isArray(msg.tool_calls)) return msg;
8613
+ let changed = false;
8614
+ const newCalls = msg.tool_calls.map((call) => {
8615
+ const args = call.function?.arguments;
8616
+ if (typeof args !== "string" || args.length <= maxTokens) return call;
8617
+ const beforeTokens = countTokensBounded(args);
8618
+ if (beforeTokens <= maxTokens) return call;
8619
+ const shrunk = shrinkJsonLongStrings(args);
8620
+ const afterTokens = countTokens(shrunk);
8621
+ if (afterTokens >= beforeTokens) return call;
8622
+ changed = true;
8623
+ healedCount += 1;
8624
+ tokensSaved += beforeTokens - afterTokens;
8625
+ charsSaved += args.length - shrunk.length;
8626
+ return { ...call, function: { ...call.function, arguments: shrunk } };
8627
+ });
8628
+ if (!changed) return msg;
8629
+ return { ...msg, tool_calls: newCalls };
8630
+ });
8631
+ return { messages: out, healedCount, tokensSaved, charsSaved };
8632
+ }
8633
+ function shrinkJsonLongStrings(jsonStr) {
8634
+ let parsed;
8635
+ try {
8636
+ parsed = JSON.parse(jsonStr);
8637
+ } catch {
8638
+ const head = jsonStr.slice(0, 200);
8639
+ return `${head}\u2026[shrunk: ${jsonStr.length} chars, unparsed]`;
8640
+ }
8641
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
8642
+ return jsonStr;
8643
+ }
8644
+ const LONG_THRESHOLD = 300;
8645
+ const input = parsed;
8646
+ const output = {};
8647
+ for (const [k, v] of Object.entries(input)) {
8648
+ if (typeof v === "string" && v.length > LONG_THRESHOLD) {
8649
+ const newlines = v.match(/\n/g)?.length ?? 0;
8650
+ output[k] = `[\u2026shrunk: ${v.length} chars, ${newlines} lines \u2014 tool already responded, see result]`;
8651
+ } else {
8652
+ output[k] = v;
8653
+ }
8654
+ }
8655
+ return JSON.stringify(output);
8656
+ }
6698
8657
 
6699
8658
  // src/loop/healing.ts
6700
8659
  var _stampSeq = 0;
@@ -6765,12 +8724,13 @@ function stampMissingReasoningForThinkingMode(messages, model) {
6765
8724
  function healLoadedMessagesByTokens(messages, maxTokens) {
6766
8725
  const shrunk = shrinkOversizedToolResultsByTokens(messages, maxTokens);
6767
8726
  const paired = fixToolCallPairing(shrunk.messages);
6768
- const healedCount = shrunk.healedCount + paired.droppedAssistantCalls + paired.droppedStrayTools;
8727
+ const argsShrunk = shrinkOversizedToolCallArgsByTokens(paired.messages, maxTokens);
8728
+ const healedCount = shrunk.healedCount + argsShrunk.healedCount + paired.droppedAssistantCalls + paired.droppedStrayTools;
6769
8729
  return {
6770
- messages: paired.messages,
8730
+ messages: argsShrunk.messages,
6771
8731
  healedCount,
6772
- tokensSaved: shrunk.tokensSaved,
6773
- charsSaved: shrunk.charsSaved
8732
+ tokensSaved: shrunk.tokensSaved + argsShrunk.tokensSaved,
8733
+ charsSaved: shrunk.charsSaved + argsShrunk.charsSaved
6774
8734
  };
6775
8735
  }
6776
8736
 
@@ -6789,6 +8749,112 @@ function* hookWarnings(outcomes, turn) {
6789
8749
  }
6790
8750
  }
6791
8751
 
8752
+ // src/loop/reasoning-retention.ts
8753
+ function hasToolCalls(msg) {
8754
+ return Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0;
8755
+ }
8756
+ function stripDroppableReasoningContent(messages) {
8757
+ let lastUser = -1;
8758
+ for (let i = messages.length - 1; i >= 0; i--) {
8759
+ if (messages[i].role === "user") {
8760
+ lastUser = i;
8761
+ break;
8762
+ }
8763
+ }
8764
+ if (lastUser < 0) {
8765
+ return { messages, prunedCount: 0, charsDropped: 0 };
8766
+ }
8767
+ let next = null;
8768
+ let prunedCount = 0;
8769
+ let charsDropped = 0;
8770
+ for (let i = 0; i < messages.length; i++) {
8771
+ const msg = messages[i];
8772
+ if (msg.role !== "assistant" || i > lastUser || hasToolCalls(msg) || !Object.hasOwn(msg, "reasoning_content")) {
8773
+ continue;
8774
+ }
8775
+ if (next === null) next = messages.slice();
8776
+ const { reasoning_content: dropped, ...replacement } = msg;
8777
+ if (typeof dropped === "string") charsDropped += dropped.length;
8778
+ next[i] = replacement;
8779
+ prunedCount += 1;
8780
+ }
8781
+ return {
8782
+ messages: next ?? messages,
8783
+ prunedCount,
8784
+ charsDropped
8785
+ };
8786
+ }
8787
+
8788
+ // src/loop/streaming.ts
8789
+ async function* streamModelResponse(opts) {
8790
+ const { client, model, messages, toolSpecs, signal, reasoningEffort, turn } = opts;
8791
+ let assistantContent = "";
8792
+ let reasoningContent = "";
8793
+ let usage = null;
8794
+ const callBuf = /* @__PURE__ */ new Map();
8795
+ const readyIndices = /* @__PURE__ */ new Set();
8796
+ for await (const chunk of client.stream({
8797
+ model,
8798
+ messages,
8799
+ tools: toolSpecs.length ? toolSpecs : void 0,
8800
+ signal,
8801
+ thinking: thinkingModeForModel(model),
8802
+ reasoningEffort
8803
+ })) {
8804
+ if (chunk.reasoningDelta) {
8805
+ reasoningContent += chunk.reasoningDelta;
8806
+ yield {
8807
+ turn,
8808
+ role: "assistant_delta",
8809
+ content: "",
8810
+ reasoningDelta: chunk.reasoningDelta
8811
+ };
8812
+ }
8813
+ if (chunk.contentDelta) {
8814
+ assistantContent += chunk.contentDelta;
8815
+ yield {
8816
+ turn,
8817
+ role: "assistant_delta",
8818
+ content: chunk.contentDelta
8819
+ };
8820
+ }
8821
+ if (chunk.toolCallDelta) {
8822
+ const d = chunk.toolCallDelta;
8823
+ const cur = callBuf.get(d.index) ?? {
8824
+ id: d.id,
8825
+ type: "function",
8826
+ function: { name: "", arguments: "" }
8827
+ };
8828
+ if (d.id) cur.id = d.id;
8829
+ if (d.name) cur.function.name = (cur.function.name ?? "") + d.name;
8830
+ if (d.argumentsDelta)
8831
+ cur.function.arguments = (cur.function.arguments ?? "") + d.argumentsDelta;
8832
+ callBuf.set(d.index, cur);
8833
+ if (!readyIndices.has(d.index) && cur.function.name && looksLikeCompleteJson(cur.function.arguments ?? "")) {
8834
+ readyIndices.add(d.index);
8835
+ }
8836
+ if (cur.function.name) {
8837
+ yield {
8838
+ turn,
8839
+ role: "tool_call_delta",
8840
+ content: "",
8841
+ toolName: cur.function.name,
8842
+ toolCallArgsChars: (cur.function.arguments ?? "").length,
8843
+ toolCallIndex: d.index,
8844
+ toolCallReadyCount: readyIndices.size
8845
+ };
8846
+ }
8847
+ }
8848
+ if (chunk.usage) usage = chunk.usage;
8849
+ }
8850
+ return {
8851
+ assistantContent,
8852
+ reasoningContent,
8853
+ toolCalls: [...callBuf.values()],
8854
+ usage
8855
+ };
8856
+ }
8857
+
6792
8858
  // src/memory/runtime.ts
6793
8859
  import { createHash } from "crypto";
6794
8860
  var ImmutablePrefix = class {
@@ -7254,6 +9320,10 @@ var MID_TURN_STEER_WRAPPER = "[Mid-turn steer queued by the user. Do not treat t
7254
9320
  function formatSteerUserMessage(content) {
7255
9321
  return [MID_TURN_STEER_WRAPPER, content].join("\n");
7256
9322
  }
9323
+ function shrinkMessageForRetention(message) {
9324
+ if (message.role !== "assistant" || !Array.isArray(message.tool_calls)) return message;
9325
+ return shrinkOversizedToolCallArgsByTokens([message], DEFAULT_MAX_RESULT_TOKENS).messages[0] ?? message;
9326
+ }
7257
9327
  var CacheFirstLoop = class {
7258
9328
  client;
7259
9329
  prefix;
@@ -7347,7 +9417,8 @@ var CacheFirstLoop = class {
7347
9417
  const prior = loadSessionMessages(this.sessionName);
7348
9418
  const shrunk = healLoadedMessagesByTokens(prior, DEFAULT_MAX_RESULT_TOKENS);
7349
9419
  const stamped = stampMissingReasoningForThinkingMode(shrunk.messages, this.model);
7350
- const messages = stamped.messages;
9420
+ const pruned = stripDroppableReasoningContent(stamped.messages);
9421
+ const messages = pruned.messages;
7351
9422
  const healedCount = shrunk.healedCount + stamped.stampedCount;
7352
9423
  const tokensSaved = shrunk.tokensSaved;
7353
9424
  for (const msg of messages) this.log.append(msg);
@@ -7364,15 +9435,17 @@ var CacheFirstLoop = class {
7364
9435
  lastPromptTokens: meta.lastPromptTokens
7365
9436
  });
7366
9437
  }
7367
- if (healedCount > 0) {
9438
+ if (healedCount > 0 || pruned.prunedCount > 0) {
7368
9439
  try {
7369
9440
  rewriteSession(this.sessionName, messages);
7370
9441
  } catch {
7371
9442
  }
7372
- process.stderr.write(
7373
- `\u25B8 session "${this.sessionName}": healed ${healedCount} entr${healedCount === 1 ? "y" : "ies"}${tokensSaved > 0 ? ` (shrunk ${tokensSaved.toLocaleString()} tokens of oversized tool output)` : " (dropped dangling tool_calls tail)"}. Rewrote session file.
9443
+ if (healedCount > 0) {
9444
+ process.stderr.write(
9445
+ `\u25B8 session "${this.sessionName}": healed ${healedCount} entr${healedCount === 1 ? "y" : "ies"}${tokensSaved > 0 ? ` (shrunk ${tokensSaved.toLocaleString()} tokens of oversized tool output/arguments)` : " (dropped dangling tool_calls tail)"}. Rewrote session file.
7374
9446
  `
7375
- );
9447
+ );
9448
+ }
7376
9449
  }
7377
9450
  } else {
7378
9451
  this.resumedMessageCount = 0;
@@ -7399,21 +9472,23 @@ var CacheFirstLoop = class {
7399
9472
  return this.context.getLogTokens();
7400
9473
  }
7401
9474
  appendAndPersist(message) {
7402
- this.log.append(message);
9475
+ const retained = shrinkMessageForRetention(message);
9476
+ this.log.append(retained);
7403
9477
  if (this.sessionName) {
7404
9478
  try {
7405
- appendSessionMessage(this.sessionName, message);
9479
+ appendSessionMessage(this.sessionName, retained);
7406
9480
  } catch {
7407
9481
  }
7408
9482
  }
7409
9483
  }
7410
9484
  /** Swap the just-appended assistant entry — used by self-correction to restore the original tool_calls without dropping reasoning_content. */
7411
9485
  replaceTailAssistantMessage(message) {
9486
+ const retained = shrinkMessageForRetention(message);
7412
9487
  const entries = this.log.entries;
7413
9488
  const tail = entries[entries.length - 1];
7414
9489
  if (!tail || tail.role !== "assistant") return;
7415
9490
  const kept = entries.slice(0, -1);
7416
- kept.push(message);
9491
+ kept.push(retained);
7417
9492
  this.log.compactInPlace(kept);
7418
9493
  if (this.sessionName) {
7419
9494
  try {
@@ -7439,6 +9514,8 @@ var CacheFirstLoop = class {
7439
9514
  this.stats.reset();
7440
9515
  this._turn = 0;
7441
9516
  this._budgetWarned = false;
9517
+ this._steerQueue.length = 0;
9518
+ this._steerConsumed = false;
7442
9519
  let systemRebuilt = false;
7443
9520
  if (this._rebuildSystem) {
7444
9521
  try {
@@ -7462,6 +9539,8 @@ var CacheFirstLoop = class {
7462
9539
  this.log.compactInPlace([]);
7463
9540
  this.scratch.reset();
7464
9541
  this._inflight.clear();
9542
+ this._steerQueue.length = 0;
9543
+ this._steerConsumed = false;
7465
9544
  this.sessionName = opts.sessionName;
7466
9545
  if (this._rebuildSystem) {
7467
9546
  try {
@@ -7539,7 +9618,8 @@ ${reason}`
7539
9618
  signal,
7540
9619
  maxResultTokens: DEFAULT_MAX_RESULT_TOKENS,
7541
9620
  confirmationGate: this.confirmationGate,
7542
- readTracker: this.readTracker
9621
+ readTracker: this.readTracker,
9622
+ rootDir: this.hookCwd
7543
9623
  });
7544
9624
  const postReport = await runHooks({
7545
9625
  hooks: this.hooks,
@@ -7574,15 +9654,22 @@ ${reason}`
7574
9654
  healActiveLogBeforeSend() {
7575
9655
  const current = this.log.toMessages();
7576
9656
  const healed = healLoadedMessages(current, DEFAULT_MAX_RESULT_CHARS);
7577
- if (healed.healedCount === 0) return current;
7578
- this.log.compactInPlace(healed.messages);
9657
+ const argsShrunk = shrinkOversizedToolCallArgsByTokens(
9658
+ healed.messages,
9659
+ DEFAULT_MAX_RESULT_TOKENS
9660
+ );
9661
+ const pruned = stripDroppableReasoningContent(argsShrunk.messages);
9662
+ if (healed.healedCount === 0 && argsShrunk.healedCount === 0 && pruned.prunedCount === 0) {
9663
+ return current;
9664
+ }
9665
+ this.log.compactInPlace(pruned.messages);
7579
9666
  if (this.sessionName) {
7580
9667
  try {
7581
- rewriteSession(this.sessionName, healed.messages);
9668
+ rewriteSession(this.sessionName, pruned.messages);
7582
9669
  } catch {
7583
9670
  }
7584
9671
  }
7585
- return healed.messages;
9672
+ return pruned.messages;
7586
9673
  }
7587
9674
  abort(opts = {}) {
7588
9675
  if (opts.discardCurrentTurn) this._discardAbortRequested = true;
@@ -7692,7 +9779,7 @@ ${reason}`
7692
9779
  const turnStartLogIndex = this.log.length;
7693
9780
  this.appendAndPersist({ role: "user", content: userInput });
7694
9781
  const toolSpecs = this.prefix.tools();
7695
- let rateLimitWarningShown = false;
9782
+ const rateLimitState = { shown: false };
7696
9783
  {
7697
9784
  const turnStart = this.context.estimateTurnStart(
7698
9785
  this.buildMessages(),
@@ -7705,7 +9792,9 @@ ${reason}`
7705
9792
  role: "status",
7706
9793
  content: t("loop.turnStartFoldStatus")
7707
9794
  };
7708
- const result = await this.context.fold(this.model, { requireTailBoundary: true });
9795
+ const result = await this.context.fold(this.model, {
9796
+ requireTailBoundary: true
9797
+ });
7709
9798
  if (result.folded) {
7710
9799
  this._foldedThisTurn = true;
7711
9800
  yield {
@@ -7773,64 +9862,19 @@ ${reason}`
7773
9862
  let usage = null;
7774
9863
  try {
7775
9864
  if (this.stream) {
7776
- const callBuf = /* @__PURE__ */ new Map();
7777
- const readyIndices = /* @__PURE__ */ new Set();
7778
- const callModel = this.model;
7779
- for await (const chunk of this.client.stream({
7780
- model: callModel,
9865
+ const result = yield* streamModelResponse({
9866
+ client: this.client,
9867
+ model: this.model,
7781
9868
  messages,
7782
- tools: toolSpecs.length ? toolSpecs : void 0,
9869
+ toolSpecs,
7783
9870
  signal,
7784
- thinking: thinkingModeForModel(callModel),
7785
- reasoningEffort: this.reasoningEffort
7786
- })) {
7787
- if (chunk.reasoningDelta) {
7788
- reasoningContent += chunk.reasoningDelta;
7789
- yield {
7790
- turn: this._turn,
7791
- role: "assistant_delta",
7792
- content: "",
7793
- reasoningDelta: chunk.reasoningDelta
7794
- };
7795
- }
7796
- if (chunk.contentDelta) {
7797
- assistantContent += chunk.contentDelta;
7798
- yield {
7799
- turn: this._turn,
7800
- role: "assistant_delta",
7801
- content: chunk.contentDelta
7802
- };
7803
- }
7804
- if (chunk.toolCallDelta) {
7805
- const d = chunk.toolCallDelta;
7806
- const cur = callBuf.get(d.index) ?? {
7807
- id: d.id,
7808
- type: "function",
7809
- function: { name: "", arguments: "" }
7810
- };
7811
- if (d.id) cur.id = d.id;
7812
- if (d.name) cur.function.name = (cur.function.name ?? "") + d.name;
7813
- if (d.argumentsDelta)
7814
- cur.function.arguments = (cur.function.arguments ?? "") + d.argumentsDelta;
7815
- callBuf.set(d.index, cur);
7816
- if (!readyIndices.has(d.index) && cur.function.name && looksLikeCompleteJson(cur.function.arguments ?? "")) {
7817
- readyIndices.add(d.index);
7818
- }
7819
- if (cur.function.name) {
7820
- yield {
7821
- turn: this._turn,
7822
- role: "tool_call_delta",
7823
- content: "",
7824
- toolName: cur.function.name,
7825
- toolCallArgsChars: (cur.function.arguments ?? "").length,
7826
- toolCallIndex: d.index,
7827
- toolCallReadyCount: readyIndices.size
7828
- };
7829
- }
7830
- }
7831
- if (chunk.usage) usage = chunk.usage;
7832
- }
7833
- toolCalls = [...callBuf.values()];
9871
+ reasoningEffort: this.reasoningEffort,
9872
+ turn: this._turn
9873
+ });
9874
+ assistantContent = result.assistantContent;
9875
+ reasoningContent = result.reasoningContent;
9876
+ toolCalls = result.toolCalls;
9877
+ usage = result.usage;
7834
9878
  } else {
7835
9879
  const callModel = this.model;
7836
9880
  const resp = await this.client.chat({
@@ -7857,12 +9901,14 @@ ${reason}`
7857
9901
  this._steerQueue.length = 0;
7858
9902
  return;
7859
9903
  }
7860
- const probe = is5xxError(err) ? await probeDeepSeekReachable(this.client) : void 0;
9904
+ const upstreamHost = this.client.baseUrl;
9905
+ const dsHost = isDeepSeekHost(upstreamHost);
9906
+ const probe = is5xxError(err) && dsHost ? await probeDeepSeekReachable(this.client) : void 0;
7861
9907
  yield {
7862
9908
  turn: this._turn,
7863
9909
  role: "error",
7864
9910
  content: "",
7865
- error: formatLoopError(err, probe)
9911
+ error: formatLoopError(err, probe, { upstreamHost })
7866
9912
  };
7867
9913
  this._steerQueue.length = 0;
7868
9914
  return;
@@ -7988,76 +10034,16 @@ ${reason}`
7988
10034
  this._steerQueue.length = 0;
7989
10035
  return;
7990
10036
  }
7991
- const dispatchSerial = (process.env.REASONIX_TOOL_DISPATCH ?? "auto").toLowerCase() === "serial";
7992
- const parallelMaxParsed = Number.parseInt(process.env.REASONIX_PARALLEL_MAX ?? "", 10);
7993
- const parallelMax = Number.isFinite(parallelMaxParsed) && parallelMaxParsed >= 1 ? Math.min(parallelMaxParsed, 16) : 3;
7994
- let callIdx = 0;
7995
- while (callIdx < repairedCalls.length) {
7996
- const chunk = [];
7997
- if (!dispatchSerial) {
7998
- while (callIdx < repairedCalls.length && chunk.length < parallelMax && this.tools.isParallelSafe(repairedCalls[callIdx]?.function?.name ?? "")) {
7999
- chunk.push(repairedCalls[callIdx++]);
8000
- }
8001
- }
8002
- if (chunk.length === 0) {
8003
- chunk.push(repairedCalls[callIdx++]);
8004
- }
8005
- for (const call of chunk) {
8006
- const callId = this.inflightIdFor(call);
8007
- this._inflight.add(callId);
8008
- yield {
8009
- turn: this._turn,
8010
- role: "tool_start",
8011
- content: "",
8012
- toolName: call.function?.name ?? "",
8013
- toolArgs: call.function?.arguments ?? "{}",
8014
- callId
8015
- };
8016
- }
8017
- const settled = await Promise.allSettled(chunk.map((c) => this.runOneToolCall(c, signal)));
8018
- for (let k = 0; k < chunk.length; k++) {
8019
- const call = chunk[k];
8020
- const name = call.function?.name ?? "";
8021
- const args = call.function?.arguments ?? "{}";
8022
- const s = settled[k];
8023
- let result;
8024
- let preWarnings = [];
8025
- let postWarnings = [];
8026
- if (s.status === "fulfilled") {
8027
- preWarnings = s.value.preWarnings;
8028
- postWarnings = s.value.postWarnings;
8029
- result = s.value.result;
8030
- } else {
8031
- const err = s.reason instanceof Error ? s.reason : new Error(String(s.reason));
8032
- result = JSON.stringify({ error: `${err.name}: ${err.message}` });
8033
- }
8034
- for (const w of preWarnings) yield w;
8035
- for (const w of postWarnings) yield w;
8036
- const rateLimited = parseRateLimitedToolResult(result);
8037
- if (rateLimited && !rateLimitWarningShown) {
8038
- rateLimitWarningShown = true;
8039
- yield {
8040
- turn: this._turn,
8041
- role: "warning",
8042
- content: rateLimited.message
8043
- };
8044
- }
8045
- this.appendAndPersist({
8046
- role: "tool",
8047
- tool_call_id: call.id ?? "",
8048
- name,
8049
- content: result
8050
- });
8051
- yield {
8052
- turn: this._turn,
8053
- role: "tool",
8054
- content: result,
8055
- toolName: name,
8056
- toolArgs: args,
8057
- callId: this.inflightIdFor(call)
8058
- };
8059
- }
8060
- }
10037
+ yield* dispatchToolCallsChunked(repairedCalls, {
10038
+ turn: this._turn,
10039
+ signal,
10040
+ isParallelSafe: (name) => this.tools.isParallelSafe(name),
10041
+ inflightIdFor: (call) => this.inflightIdFor(call),
10042
+ inflightAdd: (id) => this._inflight.add(id),
10043
+ runOne: (call, sig) => this.runOneToolCall(call, sig),
10044
+ appendAndPersist: (m) => this.appendAndPersist(m),
10045
+ rateLimitState
10046
+ });
8061
10047
  }
8062
10048
  }
8063
10049
  summaryContext() {
@@ -8087,28 +10073,42 @@ function parsePositiveIntEnv(raw) {
8087
10073
  }
8088
10074
 
8089
10075
  // src/at-mentions.ts
8090
- import { existsSync as existsSync4, readFileSync as readFileSync6, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
10076
+ import { existsSync as existsSync5, readFileSync as readFileSync6, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
8091
10077
  import { readdir, stat } from "fs/promises";
8092
- import { isAbsolute as isAbsolute2, join as join5, relative, resolve as resolve3 } from "path";
10078
+ import { isAbsolute as isAbsolute2, join as join6, relative as relative2, resolve as resolve4 } from "path";
8093
10079
 
8094
10080
  // src/gitignore.ts
8095
10081
  import { readFileSync as readFileSync5 } from "fs";
8096
10082
  import { readFile } from "fs/promises";
8097
10083
  import path from "path";
8098
10084
  import ignore from "ignore";
10085
+ var gitignoreCache = new TtlLruCache(256, 5e3);
10086
+ function buildIgnore(text) {
10087
+ return ignore().add(text);
10088
+ }
8099
10089
  async function loadGitignoreAt(dirAbs) {
10090
+ const cached2 = gitignoreCache.get(dirAbs);
10091
+ if (cached2 !== void 0) return cached2;
10092
+ let result;
8100
10093
  try {
8101
- return ignore().add(await readFile(path.join(dirAbs, ".gitignore"), "utf8"));
10094
+ result = buildIgnore(await readFile(path.join(dirAbs, ".gitignore"), "utf8"));
8102
10095
  } catch {
8103
- return null;
10096
+ result = null;
8104
10097
  }
10098
+ gitignoreCache.set(dirAbs, result);
10099
+ return result;
8105
10100
  }
8106
10101
  function loadGitignoreAtSync(dirAbs) {
10102
+ const cached2 = gitignoreCache.get(dirAbs);
10103
+ if (cached2 !== void 0) return cached2;
10104
+ let result;
8107
10105
  try {
8108
- return ignore().add(readFileSync5(path.join(dirAbs, ".gitignore"), "utf8"));
10106
+ result = buildIgnore(readFileSync5(path.join(dirAbs, ".gitignore"), "utf8"));
8109
10107
  } catch {
8110
- return null;
10108
+ result = null;
8111
10109
  }
10110
+ gitignoreCache.set(dirAbs, result);
10111
+ return result;
8112
10112
  }
8113
10113
  function ignoredByLayers(layers, abs, isDir) {
8114
10114
  for (const layer of layers) {
@@ -8144,7 +10144,7 @@ function listFilesSync(root, opts = {}) {
8144
10144
  function listFilesWithStatsSync(root, opts = {}) {
8145
10145
  const maxResults = Math.max(1, opts.maxResults ?? 2e3);
8146
10146
  const ignoreDirs = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
8147
- const rootAbs = resolve3(root);
10147
+ const rootAbs = resolve4(root);
8148
10148
  const respectGi = opts.respectGitignore !== false;
8149
10149
  const out = [];
8150
10150
  const walk2 = (dirAbs, dirRel, layers) => {
@@ -8156,7 +10156,7 @@ function listFilesWithStatsSync(root, opts = {}) {
8156
10156
  }
8157
10157
  let entries;
8158
10158
  try {
8159
- entries = readdirSync2(dirAbs, { withFileTypes: true });
10159
+ entries = readdirSync3(dirAbs, { withFileTypes: true });
8160
10160
  } catch {
8161
10161
  return;
8162
10162
  }
@@ -8164,7 +10164,7 @@ function listFilesWithStatsSync(root, opts = {}) {
8164
10164
  for (const ent of entries) {
8165
10165
  if (out.length >= maxResults) return;
8166
10166
  const relPath = dirRel ? `${dirRel}/${ent.name}` : ent.name;
8167
- const absPath = join5(dirAbs, ent.name);
10167
+ const absPath = join6(dirAbs, ent.name);
8168
10168
  if (ent.isDirectory()) {
8169
10169
  if (ent.name.startsWith(".") || ignoreDirs.has(ent.name)) continue;
8170
10170
  if (ignoredByLayers(effectiveLayers, absPath, true)) continue;
@@ -8173,14 +10173,14 @@ function listFilesWithStatsSync(root, opts = {}) {
8173
10173
  if (ignoredByLayers(effectiveLayers, absPath, false)) continue;
8174
10174
  let mtimeMs = 0;
8175
10175
  try {
8176
- mtimeMs = statSync2(absPath).mtimeMs;
10176
+ mtimeMs = statSync3(absPath).mtimeMs;
8177
10177
  } catch {
8178
10178
  }
8179
10179
  out.push({ path: relPath, mtimeMs });
8180
10180
  } else if (ent.isSymbolicLink()) {
8181
10181
  let target = null;
8182
10182
  try {
8183
- target = statSync2(absPath);
10183
+ target = statSync3(absPath);
8184
10184
  } catch {
8185
10185
  continue;
8186
10186
  }
@@ -8208,7 +10208,7 @@ async function listFilesWithStatsAsync(root, opts = {}) {
8208
10208
  async function walkFilesStream(root, opts) {
8209
10209
  const ignoreDirs = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
8210
10210
  const respectGi = opts.respectGitignore !== false;
8211
- const rootAbs = resolve3(root);
10211
+ const rootAbs = resolve4(root);
8212
10212
  const progressGap = Math.max(0, opts.progressIntervalMs ?? 100);
8213
10213
  let scanned = 0;
8214
10214
  let halted = false;
@@ -8244,7 +10244,7 @@ async function walkFilesStream(root, opts) {
8244
10244
  const fileEnts = [];
8245
10245
  for (const ent of entries) {
8246
10246
  if (halted || opts.signal?.aborted) break;
8247
- const absPath = join5(dirAbs, ent.name);
10247
+ const absPath = join6(dirAbs, ent.name);
8248
10248
  if (ent.isDirectory()) {
8249
10249
  if (ent.name.startsWith(".") || ignoreDirs.has(ent.name)) continue;
8250
10250
  if (ignoredByLayers(effectiveLayers, absPath, true)) continue;
@@ -8267,10 +10267,10 @@ async function walkFilesStream(root, opts) {
8267
10267
  return { scanned, cancelled: !!opts.signal?.aborted };
8268
10268
  }
8269
10269
  async function flushFiles(ents, dirAbs, dirRel, layers, emit) {
8270
- const accepted = ents.filter((e) => !ignoredByLayers(layers, join5(dirAbs, e.name), false));
10270
+ const accepted = ents.filter((e) => !ignoredByLayers(layers, join6(dirAbs, e.name), false));
8271
10271
  const stats = await Promise.all(
8272
10272
  accepted.map(
8273
- (e) => stat(join5(dirAbs, e.name)).then((s) => ({ mtimeMs: s.mtimeMs, isFile: s.isFile() })).catch(() => null)
10273
+ (e) => stat(join6(dirAbs, e.name)).then((s) => ({ mtimeMs: s.mtimeMs, isFile: s.isFile() })).catch(() => null)
8274
10274
  )
8275
10275
  );
8276
10276
  for (let i = 0; i < accepted.length; i++) {
@@ -8283,13 +10283,17 @@ async function flushFiles(ents, dirAbs, dirRel, layers, emit) {
8283
10283
  });
8284
10284
  }
8285
10285
  }
10286
+ var listDirectoryCache = new TtlLruCache(64, 5e3);
8286
10287
  async function listDirectory(root, relDir, opts = {}) {
8287
10288
  const ignoreDirs = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
8288
10289
  const respectGi = opts.respectGitignore !== false;
8289
- const rootAbs = resolve3(root);
8290
- const dirAbs = resolve3(rootAbs, relDir);
8291
- const rel = relative(rootAbs, dirAbs);
10290
+ const rootAbs = resolve4(root);
10291
+ const dirAbs = resolve4(rootAbs, relDir);
10292
+ const rel = relative2(rootAbs, dirAbs);
8292
10293
  if (rel.startsWith("..") || isAbsolute2(rel)) return [];
10294
+ const cacheKey = `${dirAbs}\0${respectGi ? "g" : ""}\0${[...ignoreDirs].sort().join(",")}`;
10295
+ const cached2 = listDirectoryCache.get(cacheKey);
10296
+ if (cached2) return cached2;
8293
10297
  const layers = [];
8294
10298
  if (respectGi) {
8295
10299
  const segs = rel ? rel.split(/[\\/]/) : [];
@@ -8297,7 +10301,7 @@ async function listDirectory(root, relDir, opts = {}) {
8297
10301
  const ig = await loadGitignoreAt(cursor);
8298
10302
  if (ig) layers.push({ dirAbs: cursor, ig });
8299
10303
  for (const seg of segs) {
8300
- cursor = join5(cursor, seg);
10304
+ cursor = join6(cursor, seg);
8301
10305
  const igSeg = await loadGitignoreAt(cursor);
8302
10306
  if (igSeg) layers.push({ dirAbs: cursor, ig: igSeg });
8303
10307
  }
@@ -8312,7 +10316,7 @@ async function listDirectory(root, relDir, opts = {}) {
8312
10316
  const dirs = [];
8313
10317
  const files = [];
8314
10318
  for (const ent of raw) {
8315
- const absPath = join5(dirAbs, ent.name);
10319
+ const absPath = join6(dirAbs, ent.name);
8316
10320
  if (ent.isDirectory()) {
8317
10321
  if (ent.name.startsWith(".") || ignoreDirs.has(ent.name)) continue;
8318
10322
  if (ignoredByLayers(layers, absPath, true)) continue;
@@ -8329,7 +10333,7 @@ async function listDirectory(root, relDir, opts = {}) {
8329
10333
  }
8330
10334
  const stats = await Promise.all(
8331
10335
  files.map(
8332
- (e) => stat(join5(dirAbs, e.name)).then((s) => ({ mtimeMs: s.mtimeMs, isFile: s.isFile() })).catch(() => null)
10336
+ (e) => stat(join6(dirAbs, e.name)).then((s) => ({ mtimeMs: s.mtimeMs, isFile: s.isFile() })).catch(() => null)
8333
10337
  )
8334
10338
  );
8335
10339
  const fileEntries = [];
@@ -8346,7 +10350,9 @@ async function listDirectory(root, relDir, opts = {}) {
8346
10350
  }
8347
10351
  dirs.sort((a, b) => a.name.localeCompare(b.name));
8348
10352
  fileEntries.sort((a, b) => a.name.localeCompare(b.name));
8349
- return [...dirs, ...fileEntries];
10353
+ const result = [...dirs, ...fileEntries];
10354
+ listDirectoryCache.set(cacheKey, result);
10355
+ return result;
8350
10356
  }
8351
10357
  function parseAtQuery(query) {
8352
10358
  const normalized = query.replace(/\\/g, "/");
@@ -8451,8 +10457,8 @@ var AT_MENTION_PATTERN = /(?<=^|\s)@([\p{L}\p{N}_./\\-]+)/gu;
8451
10457
  function expandAtMentions(text, rootDir, opts = {}) {
8452
10458
  const maxBytes = opts.maxBytes ?? DEFAULT_AT_MENTION_MAX_BYTES;
8453
10459
  const maxDirEntries = Math.max(1, opts.maxDirEntries ?? DEFAULT_AT_DIR_MAX_ENTRIES);
8454
- const fs5 = opts.fs ?? defaultFs;
8455
- const root = resolve3(rootDir);
10460
+ const fs5 = opts.fs ?? defaultFs2;
10461
+ const root = resolve4(rootDir);
8456
10462
  const seen = /* @__PURE__ */ new Map();
8457
10463
  const expansions = [];
8458
10464
  const dirListings = /* @__PURE__ */ new Map();
@@ -8499,8 +10505,8 @@ function resolveMention(rawPath, root, maxBytes, maxDirEntries, fs5, dirListings
8499
10505
  if (isAbsolute2(rawPath)) {
8500
10506
  return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "escape" };
8501
10507
  }
8502
- const resolved = resolve3(root, rawPath);
8503
- const rel = relative(root, resolved);
10508
+ const resolved = resolve4(root, rawPath);
10509
+ const rel = relative2(root, resolved);
8504
10510
  if (rel.startsWith("..") || isAbsolute2(rel)) {
8505
10511
  return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "escape" };
8506
10512
  }
@@ -8529,31 +10535,31 @@ function resolveMention(rawPath, root, maxBytes, maxDirEntries, fs5, dirListings
8529
10535
  return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "not-file" };
8530
10536
  }
8531
10537
  function readSafe(root, rawPath, fs5) {
8532
- const resolved = resolve3(root, rawPath);
10538
+ const resolved = resolve4(root, rawPath);
8533
10539
  try {
8534
10540
  return fs5.read(resolved);
8535
10541
  } catch {
8536
10542
  return "(read failed)";
8537
10543
  }
8538
10544
  }
8539
- var defaultFs = {
8540
- exists: (p) => existsSync4(p),
10545
+ var defaultFs2 = {
10546
+ exists: (p) => existsSync5(p),
8541
10547
  isFile: (p) => {
8542
10548
  try {
8543
- return statSync2(p).isFile();
10549
+ return statSync3(p).isFile();
8544
10550
  } catch {
8545
10551
  return false;
8546
10552
  }
8547
10553
  },
8548
10554
  isDir: (p) => {
8549
10555
  try {
8550
- return statSync2(p).isDirectory();
10556
+ return statSync3(p).isDirectory();
8551
10557
  } catch {
8552
10558
  return false;
8553
10559
  }
8554
10560
  },
8555
10561
  listDir: (dirAbs, root, max) => {
8556
- const dirRel = relative(root, dirAbs).split(/[\\/]/).join("/");
10562
+ const dirRel = relative2(root, dirAbs).split(/[\\/]/).join("/");
8557
10563
  const walkCap = Math.max(max * 4, 5e3);
8558
10564
  const all = listFilesSync(root, { maxResults: walkCap });
8559
10565
  const prefix = dirRel ? `${dirRel}/` : "";
@@ -8565,7 +10571,7 @@ var defaultFs = {
8565
10571
  },
8566
10572
  size: (p) => {
8567
10573
  try {
8568
- return statSync2(p).size;
10574
+ return statSync3(p).size;
8569
10575
  } catch {
8570
10576
  return 0;
8571
10577
  }
@@ -8574,20 +10580,26 @@ var defaultFs = {
8574
10580
  };
8575
10581
 
8576
10582
  // src/memory/project.ts
8577
- import { existsSync as existsSync5, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
8578
- import { basename, join as join6 } from "path";
10583
+ import { existsSync as existsSync6, readFileSync as readFileSync7, statSync as statSync4 } from "fs";
10584
+ import { basename, join as join7 } from "path";
8579
10585
  var PROJECT_MEMORY_FILE = "REASONIX.md";
8580
- var PROJECT_MEMORY_FILES = ["REASONIX.md", "AGENTS.md", "AGENT.md"];
10586
+ var PROJECT_MEMORY_FILES = [
10587
+ "REASONIX.md",
10588
+ ".claude/CLAUDE.md",
10589
+ "CLAUDE.md",
10590
+ "AGENTS.md",
10591
+ "AGENT.md"
10592
+ ];
8581
10593
  var PROJECT_MEMORY_MAX_CHARS = 8e3;
8582
10594
  function findProjectMemoryPath(rootDir) {
8583
10595
  for (const name of PROJECT_MEMORY_FILES) {
8584
- const path2 = join6(rootDir, name);
8585
- if (existsSync5(path2)) return path2;
10596
+ const path2 = join7(rootDir, name);
10597
+ if (existsSync6(path2)) return path2;
8586
10598
  }
8587
10599
  return null;
8588
10600
  }
8589
10601
  function resolveProjectMemoryWritePath(rootDir) {
8590
- return findProjectMemoryPath(rootDir) ?? join6(rootDir, PROJECT_MEMORY_FILE);
10602
+ return findProjectMemoryPath(rootDir) ?? join7(rootDir, PROJECT_MEMORY_FILE);
8591
10603
  }
8592
10604
  function readProjectMemory(rootDir) {
8593
10605
  const path2 = findProjectMemoryPath(rootDir);
@@ -8631,15 +10643,15 @@ ${mem.content}
8631
10643
  // src/memory/user.ts
8632
10644
  import { createHash as createHash2 } from "crypto";
8633
10645
  import {
8634
- existsSync as existsSync7,
8635
- mkdirSync as mkdirSync4,
10646
+ existsSync as existsSync8,
10647
+ mkdirSync as mkdirSync5,
8636
10648
  readFileSync as readFileSync9,
8637
- readdirSync as readdirSync4,
8638
- unlinkSync as unlinkSync2,
8639
- writeFileSync as writeFileSync4
10649
+ readdirSync as readdirSync5,
10650
+ unlinkSync as unlinkSync3,
10651
+ writeFileSync as writeFileSync5
8640
10652
  } from "fs";
8641
- import { homedir as homedir6 } from "os";
8642
- import { join as join8, resolve as resolve5 } from "path";
10653
+ import { homedir as homedir7 } from "os";
10654
+ import { join as join9, resolve as resolve6 } from "path";
8643
10655
 
8644
10656
  // src/frontmatter.ts
8645
10657
  var KEY_RE = /^([a-zA-Z_][a-zA-Z0-9_-]*):\s*(.*)$/;
@@ -8691,16 +10703,16 @@ function parseFrontmatter(raw) {
8691
10703
  // src/skills.ts
8692
10704
  import {
8693
10705
  constants,
8694
- existsSync as existsSync6,
8695
- mkdirSync as mkdirSync3,
10706
+ existsSync as existsSync7,
10707
+ mkdirSync as mkdirSync4,
8696
10708
  readFileSync as readFileSync8,
8697
- readdirSync as readdirSync3,
8698
- statSync as statSync4,
8699
- writeFileSync as writeFileSync3
10709
+ readdirSync as readdirSync4,
10710
+ statSync as statSync5,
10711
+ writeFileSync as writeFileSync4
8700
10712
  } from "fs";
8701
10713
  import { accessSync } from "fs";
8702
- import { homedir as homedir5 } from "os";
8703
- import { dirname as dirname4, isAbsolute as isAbsolute3, join as join7, resolve as resolve4 } from "path";
10714
+ import { homedir as homedir6 } from "os";
10715
+ import { dirname as dirname4, isAbsolute as isAbsolute3, join as join8, resolve as resolve5 } from "path";
8704
10716
 
8705
10717
  // src/prompt-fragments.ts
8706
10718
  var TUI_FORMATTING_RULES = `Formatting (rendered in a TUI with a real markdown renderer):
@@ -8755,8 +10767,8 @@ var SkillStore = class {
8755
10767
  disableBuiltins;
8756
10768
  subagentModels;
8757
10769
  constructor(opts = {}) {
8758
- this.homeDir = opts.homeDir ?? homedir5();
8759
- this.projectRoot = opts.projectRoot ? resolve4(opts.projectRoot) : void 0;
10770
+ this.homeDir = opts.homeDir ?? homedir6();
10771
+ this.projectRoot = opts.projectRoot ? resolve5(opts.projectRoot) : void 0;
8760
10772
  const baseDir = this.projectRoot ?? process.cwd();
8761
10773
  this.customSkillPaths = dedupePaths(
8762
10774
  opts.customSkillPaths?.map((p) => resolveCustomSkillPath(p, baseDir, this.homeDir)) ?? []
@@ -8773,22 +10785,22 @@ var SkillStore = class {
8773
10785
  const out = [];
8774
10786
  if (this.projectRoot) {
8775
10787
  out.push({
8776
- dir: join7(this.projectRoot, ".reasonix", SKILLS_DIRNAME),
10788
+ dir: join8(this.projectRoot, ".reasonix", SKILLS_DIRNAME),
8777
10789
  scope: "project"
8778
10790
  });
8779
10791
  out.push({
8780
- dir: join7(this.projectRoot, ".agents", SKILLS_DIRNAME),
10792
+ dir: join8(this.projectRoot, ".agents", SKILLS_DIRNAME),
8781
10793
  scope: "project"
8782
10794
  });
8783
10795
  out.push({
8784
- dir: join7(this.projectRoot, ".claude", SKILLS_DIRNAME),
10796
+ dir: join8(this.projectRoot, ".claude", SKILLS_DIRNAME),
8785
10797
  scope: "project"
8786
10798
  });
8787
10799
  }
8788
10800
  for (const dir of this.customSkillPaths) out.push({ dir, scope: "custom" });
8789
- out.push({ dir: join7(this.homeDir, ".reasonix", SKILLS_DIRNAME), scope: "global" });
8790
- out.push({ dir: join7(this.homeDir, ".agents", SKILLS_DIRNAME), scope: "global" });
8791
- out.push({ dir: join7(this.homeDir, ".claude", SKILLS_DIRNAME), scope: "global" });
10801
+ out.push({ dir: join8(this.homeDir, ".reasonix", SKILLS_DIRNAME), scope: "global" });
10802
+ out.push({ dir: join8(this.homeDir, ".agents", SKILLS_DIRNAME), scope: "global" });
10803
+ out.push({ dir: join8(this.homeDir, ".claude", SKILLS_DIRNAME), scope: "global" });
8792
10804
  return out.map((root, priority) => ({ ...root, priority, status: skillPathStatus(root.dir) }));
8793
10805
  }
8794
10806
  customRoots() {
@@ -8801,7 +10813,7 @@ var SkillStore = class {
8801
10813
  if (status !== "ok") continue;
8802
10814
  let entries;
8803
10815
  try {
8804
- entries = readdirSync3(dir, { withFileTypes: true });
10816
+ entries = readdirSync4(dir, { withFileTypes: true });
8805
10817
  } catch {
8806
10818
  continue;
8807
10819
  }
@@ -8837,15 +10849,15 @@ var SkillStore = class {
8837
10849
  if (scope === "project" && !this.projectRoot) {
8838
10850
  return { error: "project scope requires a workspace \u2014 run from `reasonix code`" };
8839
10851
  }
8840
- const root = scope === "project" ? join7(this.projectRoot ?? "", ".reasonix", SKILLS_DIRNAME) : join7(this.homeDir, ".reasonix", SKILLS_DIRNAME);
8841
- const flat = join7(root, `${name}.md`);
8842
- const folder = join7(root, name, SKILL_FILE);
8843
- if (existsSync6(folder)) {
10852
+ const root = scope === "project" ? join8(this.projectRoot ?? "", ".reasonix", SKILLS_DIRNAME) : join8(this.homeDir, ".reasonix", SKILLS_DIRNAME);
10853
+ const flat = join8(root, `${name}.md`);
10854
+ const folder = join8(root, name, SKILL_FILE);
10855
+ if (existsSync7(folder)) {
8844
10856
  return { error: `skill "${name}" already exists at ${folder}` };
8845
10857
  }
8846
- mkdirSync3(dirname4(flat), { recursive: true });
10858
+ mkdirSync4(dirname4(flat), { recursive: true });
8847
10859
  try {
8848
- writeFileSync3(flat, content, { encoding: "utf8", flag: "wx" });
10860
+ writeFileSync4(flat, content, { encoding: "utf8", flag: "wx" });
8849
10861
  } catch (err) {
8850
10862
  if (err.code === "EEXIST") {
8851
10863
  return { error: `skill "${name}" already exists at ${flat}` };
@@ -8859,12 +10871,12 @@ var SkillStore = class {
8859
10871
  if (!isValidSkillName(name)) return null;
8860
10872
  for (const { dir, scope, status } of this.roots()) {
8861
10873
  if (status !== "ok") continue;
8862
- const dirCandidate = join7(dir, name, SKILL_FILE);
8863
- if (existsSync6(dirCandidate) && statSync4(dirCandidate).isFile()) {
10874
+ const dirCandidate = join8(dir, name, SKILL_FILE);
10875
+ if (existsSync7(dirCandidate) && statSync5(dirCandidate).isFile()) {
8864
10876
  return this.parse(dirCandidate, name, scope);
8865
10877
  }
8866
- const flatCandidate = join7(dir, `${name}.md`);
8867
- if (existsSync6(flatCandidate) && statSync4(flatCandidate).isFile()) {
10878
+ const flatCandidate = join8(dir, `${name}.md`);
10879
+ if (existsSync7(flatCandidate) && statSync5(flatCandidate).isFile()) {
8868
10880
  return this.parse(flatCandidate, name, scope);
8869
10881
  }
8870
10882
  }
@@ -8878,14 +10890,14 @@ var SkillStore = class {
8878
10890
  readEntry(dir, scope, entry) {
8879
10891
  if (entry.isDirectory()) {
8880
10892
  if (!isValidSkillName(entry.name)) return null;
8881
- const file = join7(dir, entry.name, SKILL_FILE);
8882
- if (!existsSync6(file)) return null;
10893
+ const file = join8(dir, entry.name, SKILL_FILE);
10894
+ if (!existsSync7(file)) return null;
8883
10895
  return this.parse(file, entry.name, scope);
8884
10896
  }
8885
10897
  if (entry.isFile() && entry.name.endsWith(".md")) {
8886
10898
  const stem = entry.name.slice(0, -3);
8887
10899
  if (!isValidSkillName(stem)) return null;
8888
- return this.parse(join7(dir, entry.name), stem, scope);
10900
+ return this.parse(join8(dir, entry.name), stem, scope);
8889
10901
  }
8890
10902
  return null;
8891
10903
  }
@@ -8929,12 +10941,12 @@ function dedupePaths(paths) {
8929
10941
  }
8930
10942
  function resolveCustomSkillPath(path2, baseDir, homeDir) {
8931
10943
  const trimmed = path2.trim();
8932
- const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") || trimmed.startsWith("~\\") ? join7(homeDir, trimmed.slice(2)) : trimmed;
8933
- return resolve4(isAbsolute3(expanded) ? expanded : join7(baseDir, expanded));
10944
+ const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") || trimmed.startsWith("~\\") ? join8(homeDir, trimmed.slice(2)) : trimmed;
10945
+ return resolve5(isAbsolute3(expanded) ? expanded : join8(baseDir, expanded));
8934
10946
  }
8935
10947
  function skillPathStatus(dir) {
8936
10948
  try {
8937
- const stat2 = statSync4(dir);
10949
+ const stat2 = statSync5(dir);
8938
10950
  if (!stat2.isDirectory()) return "not-directory";
8939
10951
  accessSync(dir, constants.R_OK);
8940
10952
  return "ok";
@@ -9203,20 +11215,20 @@ function sanitizeMemoryName(raw) {
9203
11215
  return trimmed;
9204
11216
  }
9205
11217
  function projectHash(rootDir) {
9206
- const abs = resolve5(rootDir);
11218
+ const abs = resolve6(rootDir);
9207
11219
  return createHash2("sha1").update(abs).digest("hex").slice(0, 16);
9208
11220
  }
9209
11221
  function scopeDir(opts) {
9210
11222
  if (opts.scope === "global") {
9211
- return join8(opts.homeDir, USER_MEMORY_DIR, "global");
11223
+ return join9(opts.homeDir, USER_MEMORY_DIR, "global");
9212
11224
  }
9213
11225
  if (!opts.projectRoot) {
9214
11226
  throw new Error("scope=project requires a projectRoot on MemoryStore");
9215
11227
  }
9216
- return join8(opts.homeDir, USER_MEMORY_DIR, projectHash(opts.projectRoot));
11228
+ return join9(opts.homeDir, USER_MEMORY_DIR, projectHash(opts.projectRoot));
9217
11229
  }
9218
11230
  function ensureDir(p) {
9219
- if (!existsSync7(p)) mkdirSync4(p, { recursive: true });
11231
+ if (!existsSync8(p)) mkdirSync5(p, { recursive: true });
9220
11232
  }
9221
11233
  function formatFrontmatter(e) {
9222
11234
  const lines = [
@@ -9252,8 +11264,8 @@ var MemoryStore = class {
9252
11264
  homeDir;
9253
11265
  projectRoot;
9254
11266
  constructor(opts = {}) {
9255
- this.homeDir = opts.homeDir ?? join8(homedir6(), ".reasonix");
9256
- this.projectRoot = opts.projectRoot ? resolve5(opts.projectRoot) : void 0;
11267
+ this.homeDir = opts.homeDir ?? join9(homedir7(), ".reasonix");
11268
+ this.projectRoot = opts.projectRoot ? resolve6(opts.projectRoot) : void 0;
9257
11269
  }
9258
11270
  /** Directory this store writes `scope` files into, creating it if needed. */
9259
11271
  dir(scope) {
@@ -9263,7 +11275,7 @@ var MemoryStore = class {
9263
11275
  }
9264
11276
  /** Absolute path to a memory file (no existence check). */
9265
11277
  pathFor(scope, name) {
9266
- return join8(this.dir(scope), `${sanitizeMemoryName(name)}.md`);
11278
+ return join9(this.dir(scope), `${sanitizeMemoryName(name)}.md`);
9267
11279
  }
9268
11280
  /** True iff this store is configured with a project scope available. */
9269
11281
  hasProjectScope() {
@@ -9271,11 +11283,11 @@ var MemoryStore = class {
9271
11283
  }
9272
11284
  loadIndex(scope) {
9273
11285
  if (scope === "project" && !this.projectRoot) return null;
9274
- const file = join8(
11286
+ const file = join9(
9275
11287
  scopeDir({ homeDir: this.homeDir, scope, projectRoot: this.projectRoot }),
9276
11288
  MEMORY_INDEX_FILE
9277
11289
  );
9278
- if (!existsSync7(file)) return null;
11290
+ if (!existsSync8(file)) return null;
9279
11291
  let raw;
9280
11292
  try {
9281
11293
  raw = readFileSync9(file, "utf8");
@@ -9293,7 +11305,7 @@ var MemoryStore = class {
9293
11305
  /** Read one memory file's body (frontmatter stripped). Throws if missing. */
9294
11306
  read(scope, name) {
9295
11307
  const file = this.pathFor(scope, name);
9296
- if (!existsSync7(file)) {
11308
+ if (!existsSync8(file)) {
9297
11309
  throw new Error(`memory not found: scope=${scope} name=${name}`);
9298
11310
  }
9299
11311
  const raw = readFileSync9(file, "utf8");
@@ -9318,10 +11330,10 @@ var MemoryStore = class {
9318
11330
  const scopes = this.projectRoot ? ["global", "project"] : ["global"];
9319
11331
  for (const scope of scopes) {
9320
11332
  const dir = scopeDir({ homeDir: this.homeDir, scope, projectRoot: this.projectRoot });
9321
- if (!existsSync7(dir)) continue;
11333
+ if (!existsSync8(dir)) continue;
9322
11334
  let entries;
9323
11335
  try {
9324
- entries = readdirSync4(dir);
11336
+ entries = readdirSync5(dir);
9325
11337
  } catch {
9326
11338
  continue;
9327
11339
  }
@@ -9356,10 +11368,10 @@ var MemoryStore = class {
9356
11368
  if (input.priority) entry.priority = input.priority;
9357
11369
  if (input.expires) entry.expires = input.expires;
9358
11370
  const dir = this.dir(input.scope);
9359
- const file = join8(dir, `${name}.md`);
11371
+ const file = join9(dir, `${name}.md`);
9360
11372
  const content = `${formatFrontmatter(entry)}${body}
9361
11373
  `;
9362
- writeFileSync4(file, content, "utf8");
11374
+ writeFileSync5(file, content, "utf8");
9363
11375
  this.regenerateIndex(input.scope);
9364
11376
  return file;
9365
11377
  }
@@ -9369,25 +11381,25 @@ var MemoryStore = class {
9369
11381
  throw new Error("cannot delete project-scoped memory: no projectRoot configured");
9370
11382
  }
9371
11383
  const file = this.pathFor(scope, rawName);
9372
- if (!existsSync7(file)) return false;
9373
- unlinkSync2(file);
11384
+ if (!existsSync8(file)) return false;
11385
+ unlinkSync3(file);
9374
11386
  this.regenerateIndex(scope);
9375
11387
  return true;
9376
11388
  }
9377
11389
  /** Sorted by name — same file set must produce byte-identical MEMORY.md for stable prefix hashing. */
9378
11390
  regenerateIndex(scope) {
9379
11391
  const dir = scopeDir({ homeDir: this.homeDir, scope, projectRoot: this.projectRoot });
9380
- if (!existsSync7(dir)) return;
11392
+ if (!existsSync8(dir)) return;
9381
11393
  let files;
9382
11394
  try {
9383
- files = readdirSync4(dir);
11395
+ files = readdirSync5(dir);
9384
11396
  } catch {
9385
11397
  return;
9386
11398
  }
9387
11399
  const mdFiles = files.filter((f) => f !== MEMORY_INDEX_FILE && f.endsWith(".md")).sort((a, b) => a.localeCompare(b));
9388
- const indexPath = join8(dir, MEMORY_INDEX_FILE);
11400
+ const indexPath = join9(dir, MEMORY_INDEX_FILE);
9389
11401
  if (mdFiles.length === 0) {
9390
- if (existsSync7(indexPath)) unlinkSync2(indexPath);
11402
+ if (existsSync8(indexPath)) unlinkSync3(indexPath);
9391
11403
  return;
9392
11404
  }
9393
11405
  const lines = [];
@@ -9400,13 +11412,13 @@ var MemoryStore = class {
9400
11412
  lines.push(`- [${name}](${name}.md) \u2014 (malformed, check frontmatter)`);
9401
11413
  }
9402
11414
  }
9403
- writeFileSync4(indexPath, `${lines.join("\n")}
11415
+ writeFileSync5(indexPath, `${lines.join("\n")}
9404
11416
  `, "utf8");
9405
11417
  }
9406
11418
  };
9407
- function readGlobalReasonixMemory(homeDir = join8(homedir6(), ".reasonix")) {
9408
- const path2 = join8(homeDir, "REASONIX.md");
9409
- if (!existsSync7(path2)) return null;
11419
+ function readGlobalReasonixMemory(homeDir = join9(homedir7(), ".reasonix")) {
11420
+ const path2 = join9(homeDir, "REASONIX.md");
11421
+ if (!existsSync8(path2)) return null;
9410
11422
  let raw;
9411
11423
  try {
9412
11424
  raw = readFileSync9(path2, "utf8");
@@ -9423,7 +11435,7 @@ function readGlobalReasonixMemory(homeDir = join8(homedir6(), ".reasonix")) {
9423
11435
  }
9424
11436
  function applyGlobalReasonixMemory(basePrompt, homeDir) {
9425
11437
  if (!memoryEnabled()) return basePrompt;
9426
- const dir = homeDir ?? join8(homedir6(), ".reasonix");
11438
+ const dir = homeDir ?? join9(homedir7(), ".reasonix");
9427
11439
  const mem = readGlobalReasonixMemory(dir);
9428
11440
  if (!mem) return basePrompt;
9429
11441
  return [
@@ -9438,6 +11450,39 @@ function applyGlobalReasonixMemory(basePrompt, homeDir) {
9438
11450
  "```"
9439
11451
  ].join("\n");
9440
11452
  }
11453
+ function readGlobalClaudeMemory(homeDir = homedir7()) {
11454
+ const path2 = join9(homeDir, ".claude", "CLAUDE.md");
11455
+ if (!existsSync8(path2)) return null;
11456
+ let raw;
11457
+ try {
11458
+ raw = readFileSync9(path2, "utf8");
11459
+ } catch {
11460
+ return null;
11461
+ }
11462
+ const trimmed = raw.trim();
11463
+ if (!trimmed) return null;
11464
+ const originalChars = trimmed.length;
11465
+ const truncated = originalChars > 8e3;
11466
+ const content = truncated ? `${trimmed.slice(0, 8e3)}
11467
+ \u2026 (truncated ${originalChars - 8e3} chars)` : trimmed;
11468
+ return { path: path2, content, originalChars, truncated };
11469
+ }
11470
+ function applyGlobalClaudeMemory(basePrompt) {
11471
+ if (!memoryEnabled()) return basePrompt;
11472
+ const mem = readGlobalClaudeMemory();
11473
+ if (!mem) return basePrompt;
11474
+ return [
11475
+ basePrompt,
11476
+ "",
11477
+ "# Global memory (~/.claude/CLAUDE.md)",
11478
+ "",
11479
+ "Cross-project notes from your Claude Code configuration. Treat as authoritative \u2014 same level of trust as project memory.",
11480
+ "",
11481
+ "```",
11482
+ mem.content,
11483
+ "```"
11484
+ ].join("\n");
11485
+ }
9441
11486
  function effectivePriority(entry, cfg) {
9442
11487
  if (entry.priority) return entry.priority;
9443
11488
  return memoryTypeDefaults(entry.type, cfg).priority;
@@ -9500,9 +11545,10 @@ function applyMemoryStack(basePrompt, rootDir, opts = {}) {
9500
11545
  const withProject = applyProjectMemory(basePrompt, rootDir);
9501
11546
  const withGlobal = applyGlobalReasonixMemory(
9502
11547
  withProject,
9503
- homeDir ? join8(homeDir, ".reasonix") : void 0
11548
+ homeDir ? join9(homeDir, ".reasonix") : void 0
9504
11549
  );
9505
- const withMemory = applyUserMemory(withGlobal, { projectRoot: rootDir, homeDir, cfg });
11550
+ const withGlobalClaude = applyGlobalClaudeMemory(withGlobal);
11551
+ const withMemory = applyUserMemory(withGlobalClaude, { projectRoot: rootDir, homeDir, cfg });
9506
11552
  const customSkillPaths = cfg?.skills?.paths ? resolveSkillPaths(cfg.skills.paths, rootDir) : loadResolvedSkillPaths(rootDir);
9507
11553
  return applySkillsIndex(withMemory, { projectRoot: rootDir, homeDir, customSkillPaths });
9508
11554
  }
@@ -9513,7 +11559,7 @@ import * as pathMod6 from "path";
9513
11559
  import picomatch3 from "picomatch";
9514
11560
 
9515
11561
  // src/code/file-encoding.ts
9516
- import { promises as fsp, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
11562
+ import { promises as fsp, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
9517
11563
  import iconv from "iconv-lite";
9518
11564
  var UTF8_BOM = Buffer.from([239, 187, 191]);
9519
11565
  function decodeFileBuffer(buf) {
@@ -9542,21 +11588,21 @@ function encodeFile(text, encoding) {
9542
11588
  }
9543
11589
 
9544
11590
  // src/memory/subdir.ts
9545
- import { existsSync as existsSync8, readFileSync as readFileSync11 } from "fs";
9546
- import { dirname as dirname5, join as join9, relative as relative2, resolve as resolve6 } from "path";
11591
+ import { existsSync as existsSync9, readFileSync as readFileSync11 } from "fs";
11592
+ import { dirname as dirname5, join as join10, relative as relative3, resolve as resolve7 } from "path";
9547
11593
  function findDirMemory(absDir, rootDir) {
9548
- const root = resolve6(rootDir);
9549
- const target = resolve6(absDir);
9550
- const rel = relative2(root, target);
11594
+ const root = resolve7(rootDir);
11595
+ const target = resolve7(absDir);
11596
+ const rel = relative3(root, target);
9551
11597
  if (rel.startsWith("..")) return [];
9552
11598
  const found = [];
9553
11599
  let cur = target;
9554
11600
  while (cur !== root) {
9555
- const r = relative2(root, cur);
11601
+ const r = relative3(root, cur);
9556
11602
  if (!r || r.startsWith("..")) break;
9557
11603
  for (const name of PROJECT_MEMORY_FILES) {
9558
- const path2 = join9(cur, name);
9559
- if (existsSync8(path2)) {
11604
+ const path2 = join10(cur, name);
11605
+ if (existsSync9(path2)) {
9560
11606
  found.push(path2);
9561
11607
  break;
9562
11608
  }
@@ -9568,7 +11614,7 @@ function findDirMemory(absDir, rootDir) {
9568
11614
  return found;
9569
11615
  }
9570
11616
  function findSubdirMemoryAncestors(absPath, rootDir) {
9571
- return findDirMemory(dirname5(resolve6(absPath)), rootDir);
11617
+ return findDirMemory(dirname5(resolve7(absPath)), rootDir);
9572
11618
  }
9573
11619
  function readSubdirMemoryContent(path2) {
9574
11620
  let raw;
@@ -10061,7 +12107,7 @@ var RegexRunner = class {
10061
12107
  this.defaultTimeoutMs = opts.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
10062
12108
  }
10063
12109
  testLines(text, source, flags, opts = {}) {
10064
- return new Promise((resolve15, reject) => {
12110
+ return new Promise((resolve16, reject) => {
10065
12111
  if (opts.signal?.aborted) {
10066
12112
  reject(new Error("regex evaluation aborted"));
10067
12113
  return;
@@ -10074,7 +12120,7 @@ var RegexRunner = class {
10074
12120
  this.killWorker();
10075
12121
  reject(new Error(`regex evaluation exceeded ${timeoutMs}ms`));
10076
12122
  }, timeoutMs);
10077
- const entry = { resolve: resolve15, reject, timer };
12123
+ const entry = { resolve: resolve16, reject, timer };
10078
12124
  if (opts.signal) {
10079
12125
  entry.signal = opts.signal;
10080
12126
  entry.onAbort = () => {
@@ -10205,6 +12251,7 @@ async function searchFiles(ctx, startAbs, args) {
10205
12251
  var MAX_HITS_PER_FILE = 30;
10206
12252
  var SUMMARY_MODE_TRIGGER_RATIO = 0.8;
10207
12253
  var WALK_DEADLINE_MS = 12e4;
12254
+ var REGEX_METACHARS = /[\\.+*?()[\]{}|^$]/;
10208
12255
  async function searchContent(ctx, startAbs, args) {
10209
12256
  throwIfAborted(args.signal);
10210
12257
  const caseSensitive = args.case_sensitive === true;
@@ -10212,12 +12259,15 @@ async function searchContent(ctx, startAbs, args) {
10212
12259
  const ctxLines = Math.max(0, Math.min(20, Math.floor(args.context ?? 0)));
10213
12260
  const summaryOnly = args.summary_only === true;
10214
12261
  const reFlags = caseSensitive ? "" : "i";
12262
+ const hasMeta = REGEX_METACHARS.test(args.pattern);
10215
12263
  let reSource = null;
10216
- try {
10217
- new RegExp(args.pattern, reFlags);
10218
- reSource = args.pattern;
10219
- } catch {
10220
- reSource = null;
12264
+ if (hasMeta) {
12265
+ try {
12266
+ new RegExp(args.pattern, reFlags);
12267
+ reSource = args.pattern;
12268
+ } catch {
12269
+ reSource = null;
12270
+ }
10221
12271
  }
10222
12272
  const needle = caseSensitive ? args.pattern : args.pattern.toLowerCase();
10223
12273
  const matches = [];
@@ -10306,9 +12356,10 @@ async function searchContent(ctx, startAbs, args) {
10306
12356
  if (firstNul !== -1 && firstNul < 8 * 1024) continue;
10307
12357
  const text = raw.toString("utf8");
10308
12358
  const rel = displayRel3(ctx.rootDir, full);
10309
- const lines = text.split(/\r?\n/);
10310
12359
  let hits;
12360
+ let lines;
10311
12361
  if (reSource !== null) {
12362
+ lines = text.split(/\r?\n/);
10312
12363
  try {
10313
12364
  hits = await getRegexRunner().testLines(text, reSource, reFlags, {
10314
12365
  signal: args.signal
@@ -10320,9 +12371,14 @@ async function searchContent(ctx, startAbs, args) {
10320
12371
  continue;
10321
12372
  }
10322
12373
  } else {
12374
+ const haystack = caseSensitive ? text : text.toLowerCase();
12375
+ if (haystack.indexOf(needle) === -1) {
12376
+ scanned++;
12377
+ continue;
12378
+ }
12379
+ lines = text.split(/\r?\n/);
10323
12380
  hits = [];
10324
12381
  for (let li = 0; li < lines.length; li++) {
10325
- throwIfAborted(args.signal);
10326
12382
  const lineForCheck = caseSensitive ? lines[li] : lines[li].toLowerCase();
10327
12383
  if (lineForCheck.includes(needle)) hits.push(li);
10328
12384
  }
@@ -10530,6 +12586,7 @@ ${body}`;
10530
12586
  registry.register({
10531
12587
  name: "read_file",
10532
12588
  parallelSafe: true,
12589
+ skipTruncationSave: true,
10533
12590
  description: `Read a file under the sandbox root. Default returns FULL CONTENT for files \u2264 ${Math.round(DEFAULT_OUTLINE_THRESHOLD_BYTES / 1024)} KiB. Optional scoping: head/tail (N lines), range "A-B" (1-indexed inclusive). Larger files auto-switch to outline mode (metadata + head + symbol outline for TS/JS/Python/Go/Rust/Markdown/Protobuf/text) \u2014 drill in with range or search_content. Files over ${Math.round(HARD_MAX_FILE_BYTES / (1024 * 1024))} MiB and binaries are refused \u2014 use get_file_info for stat.`,
10534
12591
  readOnly: true,
10535
12592
  stormExempt: true,
@@ -10627,6 +12684,7 @@ ${slice.join("\n")}`);
10627
12684
  registry.register({
10628
12685
  name: "list_directory",
10629
12686
  parallelSafe: true,
12687
+ skipTruncationSave: true,
10630
12688
  description: "List entries in a directory under the sandbox root. Returns one line per entry, marking directories with a trailing slash. Not recursive \u2014 use directory_tree for that.",
10631
12689
  readOnly: true,
10632
12690
  stormExempt: true,
@@ -10649,6 +12707,7 @@ ${slice.join("\n")}`);
10649
12707
  registry.register({
10650
12708
  name: "directory_tree",
10651
12709
  parallelSafe: true,
12710
+ skipTruncationSave: true,
10652
12711
  description: `Recursively list entries with indented tree structure (dirs marked '/'). Budget-aware: maxDepth defaults to 2, large subtrees (>50 children) auto-collapse to "[N hidden \u2014 list_directory to inspect]", and ${[...SKIP_DIR_NAMES].sort().join(" / ")} are skipped unless include_deps:true. For single-level use list_directory; for path lookups use search_files; for code lookups use search_content.`,
10653
12712
  readOnly: true,
10654
12713
  parameters: {
@@ -10724,6 +12783,7 @@ ${slice.join("\n")}`);
10724
12783
  registry.register({
10725
12784
  name: "search_files",
10726
12785
  parallelSafe: true,
12786
+ skipTruncationSave: true,
10727
12787
  description: "Find files whose NAME matches a substring or regex. Case-insensitive. Walks the directory recursively under the sandbox root. Returns one path per line. Skips dependency / VCS / build directories (node_modules, .git, dist, build, .next, target, .venv) by default.",
10728
12788
  readOnly: true,
10729
12789
  parameters: {
@@ -10750,6 +12810,7 @@ ${slice.join("\n")}`);
10750
12810
  registry.register({
10751
12811
  name: "search_content",
10752
12812
  parallelSafe: true,
12813
+ skipTruncationSave: true,
10753
12814
  description: "Recursively grep file CONTENTS for a substring or regex \u2014 'where is X called', 'what files contain Y'. Returns one match per line as `path:line: text`. Per-file hit cap 30; when the byte budget is mostly spent, remaining files switch to a `rel: N matches` histogram. Pass `summary_only:true` for just the histogram. Skips dependency / VCS / build dirs and binary files. For file NAMES use search_files.",
10754
12815
  readOnly: true,
10755
12816
  parameters: {
@@ -10801,6 +12862,7 @@ ${slice.join("\n")}`);
10801
12862
  registry.register({
10802
12863
  name: "glob",
10803
12864
  parallelSafe: true,
12865
+ skipTruncationSave: true,
10804
12866
  description: "List files matching a glob pattern, sorted by mtime (most-recently-modified first) by default. Use this for 'what changed lately', 'find all *.test.ts', 'all configs under src/'. Glob syntax matches the cross-tool standard: `*` (any chars in one segment), `**` (any segments), `?` (one char), `{a,b}` (alternation). Pattern matches against the path RELATIVE to the search root (e.g. 'src/**/*.ts' from project root). Skips node_modules / .git / dist / build / etc by default. Default limit 200; raise via `limit` (max 1000). Different from `search_files` (substring on basename) and `search_content` (matches inside file contents).",
10805
12867
  readOnly: true,
10806
12868
  parameters: {
@@ -10839,6 +12901,7 @@ ${slice.join("\n")}`);
10839
12901
  registry.register({
10840
12902
  name: "get_file_info",
10841
12903
  parallelSafe: true,
12904
+ skipTruncationSave: true,
10842
12905
  description: "Stat a path under the sandbox root. Returns type (file|directory|symlink), size in bytes, mtime in ISO-8601.",
10843
12906
  readOnly: true,
10844
12907
  parameters: {
@@ -12512,16 +14575,16 @@ ${job.output.slice(start)}`;
12512
14575
  let wakeOutput = null;
12513
14576
  if (waitFor === "output-or-exit") {
12514
14577
  racers.push(
12515
- new Promise((resolve15) => {
12516
- wakeOutput = resolve15;
12517
- job.outputWaiters.add(resolve15);
14578
+ new Promise((resolve16) => {
14579
+ wakeOutput = resolve16;
14580
+ job.outputWaiters.add(resolve16);
12518
14581
  })
12519
14582
  );
12520
14583
  }
12521
14584
  let timer = null;
12522
14585
  racers.push(
12523
- new Promise((resolve15) => {
12524
- timer = setTimeout(resolve15, timeoutMs);
14586
+ new Promise((resolve16) => {
14587
+ timer = setTimeout(resolve16, timeoutMs);
12525
14588
  })
12526
14589
  );
12527
14590
  await Promise.race(racers);
@@ -12643,7 +14706,7 @@ function latestOutputSince(before, after) {
12643
14706
 
12644
14707
  // src/tools/shell/exec.ts
12645
14708
  import { spawn as spawn4, spawnSync } from "child_process";
12646
- import { existsSync as existsSync9, statSync as statSync5 } from "fs";
14709
+ import { existsSync as existsSync10, statSync as statSync6 } from "fs";
12647
14710
  import * as pathMod10 from "path";
12648
14711
 
12649
14712
  // src/tools/shell-chain.ts
@@ -13073,9 +15136,9 @@ async function runPipeGroup(segments, opts) {
13073
15136
  }
13074
15137
  const exits = await Promise.all(
13075
15138
  children.map(
13076
- (c) => new Promise((resolve15) => {
13077
- c.once("error", () => resolve15(null));
13078
- c.once("close", (code) => resolve15(code));
15139
+ (c) => new Promise((resolve16) => {
15140
+ c.once("error", () => resolve16(null));
15141
+ c.once("close", (code) => resolve16(code));
13079
15142
  })
13080
15143
  )
13081
15144
  );
@@ -13119,7 +15182,7 @@ var OutputBuffer = class {
13119
15182
  };
13120
15183
 
13121
15184
  // src/tools/shell/parse.ts
13122
- import { homedir as homedir7 } from "os";
15185
+ import { homedir as homedir8 } from "os";
13123
15186
  import * as pathMod9 from "path";
13124
15187
  var BUILTIN_ALLOWLIST = [
13125
15188
  // Repo inspection
@@ -13320,12 +15383,12 @@ function resolveSensitivePath(token, projectRoot) {
13320
15383
  return null;
13321
15384
  let expanded = token;
13322
15385
  if (expanded.startsWith("~")) {
13323
- expanded = pathMod9.join(homedir7(), expanded.slice(1));
15386
+ expanded = pathMod9.join(homedir8(), expanded.slice(1));
13324
15387
  }
13325
15388
  return pathMod9.resolve(projectRoot, expanded);
13326
15389
  }
13327
15390
  function expandPrefix(prefix) {
13328
- if (prefix.startsWith("~")) return pathMod9.join(homedir7(), prefix.slice(1));
15391
+ if (prefix.startsWith("~")) return pathMod9.join(homedir8(), prefix.slice(1));
13329
15392
  return pathMod9.resolve(prefix);
13330
15393
  }
13331
15394
  function pathStartsWithPrefix(normalized, prefix) {
@@ -13498,7 +15561,7 @@ async function runCommand(cmd, opts) {
13498
15561
  };
13499
15562
  const { bin, args, spawnOverrides } = prepareSpawn(argv, { env: normalizedEnv });
13500
15563
  const effectiveSpawnOpts = { ...spawnOpts, ...spawnOverrides };
13501
- return await new Promise((resolve15, reject) => {
15564
+ return await new Promise((resolve16, reject) => {
13502
15565
  let child;
13503
15566
  try {
13504
15567
  child = spawn4(bin, args, effectiveSpawnOpts);
@@ -13552,7 +15615,7 @@ async function runCommand(cmd, opts) {
13552
15615
  const output = buf.length > maxChars ? `${buf.slice(0, maxChars)}
13553
15616
 
13554
15617
  [\u2026 truncated ${buf.length - maxChars} chars \u2026]` : buf;
13555
- resolve15({ exitCode: code, output, timedOut });
15618
+ resolve16({ exitCode: code, output, timedOut });
13556
15619
  });
13557
15620
  });
13558
15621
  }
@@ -13637,7 +15700,7 @@ function mergeWindowsPathLike(values, delimiter2) {
13637
15700
  }
13638
15701
  function defaultIsFile(full) {
13639
15702
  try {
13640
- return existsSync9(full) && statSync5(full).isFile();
15703
+ return existsSync10(full) && statSync6(full).isFile();
13641
15704
  } catch {
13642
15705
  return false;
13643
15706
  }
@@ -14676,11 +16739,11 @@ ${i + 1}. ${r.title}`);
14676
16739
 
14677
16740
  // src/env.ts
14678
16741
  import { readFileSync as readFileSync12 } from "fs";
14679
- import { resolve as resolve12 } from "path";
16742
+ import { resolve as resolve13 } from "path";
14680
16743
  function loadDotenv(path2 = ".env") {
14681
16744
  let raw;
14682
16745
  try {
14683
- raw = readFileSync12(resolve12(process.cwd(), path2), "utf8");
16746
+ raw = readFileSync12(resolve13(process.cwd(), path2), "utf8");
14684
16747
  } catch {
14685
16748
  return;
14686
16749
  }
@@ -15129,13 +17192,13 @@ function truncate(s, n) {
15129
17192
  }
15130
17193
 
15131
17194
  // src/mcp/client.ts
15132
- import { basename as basename3, resolve as resolve13 } from "path";
17195
+ import { basename as basename3, resolve as resolve14 } from "path";
15133
17196
  import { pathToFileURL } from "url";
15134
17197
 
15135
17198
  // src/version.ts
15136
- import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "fs";
15137
- import { homedir as homedir8 } from "os";
15138
- import { dirname as dirname8, join as join14 } from "path";
17199
+ import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
17200
+ import { homedir as homedir9 } from "os";
17201
+ import { dirname as dirname8, join as join15 } from "path";
15139
17202
  import { fileURLToPath as fileURLToPath2 } from "url";
15140
17203
  var REGISTRY_URL = "https://registry.npmjs.org/reasonix/latest";
15141
17204
  var LATEST_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
@@ -15144,8 +17207,8 @@ function readPackageVersion() {
15144
17207
  try {
15145
17208
  let dir = dirname8(fileURLToPath2(import.meta.url));
15146
17209
  for (let i = 0; i < 6; i++) {
15147
- const p = join14(dir, "package.json");
15148
- if (existsSync10(p)) {
17210
+ const p = join15(dir, "package.json");
17211
+ if (existsSync11(p)) {
15149
17212
  const pkg = JSON.parse(readFileSync14(p, "utf8"));
15150
17213
  if (pkg?.name === "reasonix" && typeof pkg.version === "string") {
15151
17214
  return pkg.version;
@@ -15161,7 +17224,7 @@ function readPackageVersion() {
15161
17224
  }
15162
17225
  var VERSION = readPackageVersion();
15163
17226
  function cachePath(homeDirOverride) {
15164
- return join14(homeDirOverride ?? homedir8(), ".reasonix", "version-cache.json");
17227
+ return join15(homeDirOverride ?? homedir9(), ".reasonix", "version-cache.json");
15165
17228
  }
15166
17229
  function readCache(homeDirOverride) {
15167
17230
  try {
@@ -15177,8 +17240,8 @@ function readCache(homeDirOverride) {
15177
17240
  function writeCache(entry, homeDirOverride) {
15178
17241
  try {
15179
17242
  const p = cachePath(homeDirOverride);
15180
- mkdirSync5(dirname8(p), { recursive: true });
15181
- writeFileSync6(p, JSON.stringify(entry), "utf8");
17243
+ mkdirSync6(dirname8(p), { recursive: true });
17244
+ writeFileSync7(p, JSON.stringify(entry), "utf8");
15182
17245
  } catch {
15183
17246
  }
15184
17247
  }
@@ -15285,7 +17348,7 @@ var McpClient = class {
15285
17348
  this.clientInfo = opts.clientInfo ?? { name: "reasonix", version: VERSION };
15286
17349
  const workspaceDir = opts.workspaceDir?.trim();
15287
17350
  if (workspaceDir) {
15288
- this.workspaceDir = resolve13(workspaceDir);
17351
+ this.workspaceDir = resolve14(workspaceDir);
15289
17352
  this.workspaceRoot = {
15290
17353
  uri: pathToFileURL(this.workspaceDir).href,
15291
17354
  name: basename3(this.workspaceDir) || this.workspaceDir
@@ -15408,7 +17471,7 @@ var McpClient = class {
15408
17471
  const id = this.nextId++;
15409
17472
  const frame = { jsonrpc: "2.0", id, method, params };
15410
17473
  let abortHandler = null;
15411
- const promise = new Promise((resolve15, reject) => {
17474
+ const promise = new Promise((resolve16, reject) => {
15412
17475
  const timeout = setTimeout(() => {
15413
17476
  this.pending.delete(id);
15414
17477
  if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
@@ -15417,7 +17480,7 @@ var McpClient = class {
15417
17480
  );
15418
17481
  }, this.requestTimeoutMs);
15419
17482
  this.pending.set(id, {
15420
- resolve: resolve15,
17483
+ resolve: resolve16,
15421
17484
  reject,
15422
17485
  timeout
15423
17486
  });
@@ -15567,12 +17630,12 @@ var StdioTransport = class {
15567
17630
  }
15568
17631
  async send(message) {
15569
17632
  if (this.closed) throw new Error("MCP transport is closed");
15570
- return new Promise((resolve15, reject) => {
17633
+ return new Promise((resolve16, reject) => {
15571
17634
  const line = `${JSON.stringify(message)}
15572
17635
  `;
15573
17636
  this.child.stdin.write(line, "utf8", (err) => {
15574
17637
  if (err) reject(err);
15575
- else resolve15();
17638
+ else resolve16();
15576
17639
  });
15577
17640
  });
15578
17641
  }
@@ -15583,8 +17646,8 @@ var StdioTransport = class {
15583
17646
  continue;
15584
17647
  }
15585
17648
  if (this.closed) return;
15586
- const next = await new Promise((resolve15) => {
15587
- this.waiters.push(resolve15);
17649
+ const next = await new Promise((resolve16) => {
17650
+ this.waiters.push(resolve16);
15588
17651
  });
15589
17652
  if (next === null) return;
15590
17653
  yield next;
@@ -15664,8 +17727,8 @@ var SseTransport = class {
15664
17727
  constructor(opts) {
15665
17728
  this.url = opts.url;
15666
17729
  this.headers = opts.headers ?? {};
15667
- this.endpointReady = new Promise((resolve15, reject) => {
15668
- this.resolveEndpoint = resolve15;
17730
+ this.endpointReady = new Promise((resolve16, reject) => {
17731
+ this.resolveEndpoint = resolve16;
15669
17732
  this.rejectEndpoint = reject;
15670
17733
  });
15671
17734
  this.endpointReady.catch(() => void 0);
@@ -15692,8 +17755,8 @@ var SseTransport = class {
15692
17755
  continue;
15693
17756
  }
15694
17757
  if (this.closed) return;
15695
- const next = await new Promise((resolve15) => {
15696
- this.waiters.push(resolve15);
17758
+ const next = await new Promise((resolve16) => {
17759
+ this.waiters.push(resolve16);
15697
17760
  });
15698
17761
  if (next === null) return;
15699
17762
  yield next;
@@ -15879,8 +17942,8 @@ var StreamableHttpTransport = class {
15879
17942
  continue;
15880
17943
  }
15881
17944
  if (this.closed) return;
15882
- const next = await new Promise((resolve15) => {
15883
- this.waiters.push(resolve15);
17945
+ const next = await new Promise((resolve16) => {
17946
+ this.waiters.push(resolve16);
15884
17947
  });
15885
17948
  if (next === null) return;
15886
17949
  yield next;
@@ -15972,19 +18035,22 @@ async function trySection(load) {
15972
18035
 
15973
18036
  // src/code/edit-blocks.ts
15974
18037
  import {
18038
+ chmodSync as chmodSync4,
15975
18039
  closeSync as closeSync2,
15976
- existsSync as existsSync11,
18040
+ existsSync as existsSync12,
15977
18041
  fstatSync,
15978
- ftruncateSync,
15979
- mkdirSync as mkdirSync6,
18042
+ fsyncSync,
18043
+ mkdirSync as mkdirSync7,
15980
18044
  openSync as openSync2,
15981
18045
  readFileSync as readFileSync15,
15982
18046
  readSync,
15983
- unlinkSync as unlinkSync3,
15984
- writeFileSync as writeFileSync7,
18047
+ realpathSync as realpathSync2,
18048
+ renameSync as renameSync3,
18049
+ unlinkSync as unlinkSync4,
18050
+ writeFileSync as writeFileSync8,
15985
18051
  writeSync
15986
18052
  } from "fs";
15987
- import { dirname as dirname9, isAbsolute as isAbsolute9, relative as relative10, resolve as resolve14 } from "path";
18053
+ import { dirname as dirname9, isAbsolute as isAbsolute9, relative as relative11, resolve as resolve15 } from "path";
15988
18054
  var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
15989
18055
  function parseEditBlocks(text) {
15990
18056
  const out = [];
@@ -16002,15 +18068,15 @@ function parseEditBlocks(text) {
16002
18068
  return out;
16003
18069
  }
16004
18070
  function resolveEditPath(rootDir, rawPath) {
16005
- const absRoot = resolve14(rootDir);
18071
+ const absRoot = resolve15(rootDir);
16006
18072
  if (/^[A-Za-z]:[\\/]/.test(rawPath) || looksLikeAbsoluteSystemPath2(rawPath)) {
16007
- return resolve14(rawPath);
18073
+ return resolve15(rawPath);
16008
18074
  }
16009
18075
  let rooted = rawPath;
16010
18076
  while (rooted.startsWith("/") || rooted.startsWith("\\")) {
16011
18077
  rooted = rooted.slice(1);
16012
18078
  }
16013
- return resolve14(absRoot, rooted || ".");
18079
+ return resolve15(absRoot, rooted || ".");
16014
18080
  }
16015
18081
  function looksLikeAbsoluteSystemPath2(rawPath) {
16016
18082
  return /^\/(?:home|Users|etc|var|opt|tmp|usr|mnt|Library|Volumes|proc|sys|dev|run|srv|media|Applications|System|root|boot|private)(?:[/\\]|$)/.test(
@@ -16018,11 +18084,59 @@ function looksLikeAbsoluteSystemPath2(rawPath) {
16018
18084
  );
16019
18085
  }
16020
18086
  function pathIsUnder4(child, parent) {
16021
- const rel = relative10(parent, child);
18087
+ const rel = relative11(parent, child);
16022
18088
  return rel === "" || !rel.startsWith("..") && !isAbsolute9(rel);
16023
18089
  }
18090
+ function writeAllSync(fd, buf) {
18091
+ let written = 0;
18092
+ while (written < buf.length) {
18093
+ const n = writeSync(fd, buf, written, buf.length - written, written);
18094
+ if (n <= 0) throw new Error("write returned 0 bytes before completing");
18095
+ written += n;
18096
+ }
18097
+ }
18098
+ function fsyncDirectoryBestEffort(path2) {
18099
+ let fd;
18100
+ try {
18101
+ fd = openSync2(path2, "r");
18102
+ fsyncSync(fd);
18103
+ } catch {
18104
+ } finally {
18105
+ if (fd !== void 0) closeSync2(fd);
18106
+ }
18107
+ }
18108
+ function atomicReplaceFileSync(path2, buf, mode) {
18109
+ const tmp = `${path2}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
18110
+ const permissions = mode & 4095;
18111
+ let fd;
18112
+ try {
18113
+ fd = openSync2(tmp, "wx", permissions);
18114
+ writeAllSync(fd, buf);
18115
+ try {
18116
+ chmodSync4(tmp, permissions);
18117
+ } catch {
18118
+ }
18119
+ fsyncSync(fd);
18120
+ closeSync2(fd);
18121
+ fd = void 0;
18122
+ renameSync3(tmp, path2);
18123
+ fsyncDirectoryBestEffort(dirname9(path2));
18124
+ } catch (err) {
18125
+ if (fd !== void 0) {
18126
+ try {
18127
+ closeSync2(fd);
18128
+ } catch {
18129
+ }
18130
+ }
18131
+ try {
18132
+ unlinkSync4(tmp);
18133
+ } catch {
18134
+ }
18135
+ throw err;
18136
+ }
18137
+ }
16024
18138
  function applyEditBlock(block, rootDir) {
16025
- const absRoot = resolve14(rootDir);
18139
+ const absRoot = resolve15(rootDir);
16026
18140
  const absTarget = resolveEditPath(rootDir, block.path);
16027
18141
  if (!pathIsUnder4(absTarget, absRoot)) {
16028
18142
  return {
@@ -16034,7 +18148,7 @@ function applyEditBlock(block, rootDir) {
16034
18148
  const searchEmpty = block.search.length === 0;
16035
18149
  if (searchEmpty) {
16036
18150
  try {
16037
- mkdirSync6(dirname9(absTarget), { recursive: true });
18151
+ mkdirSync7(dirname9(absTarget), { recursive: true });
16038
18152
  const fd = openSync2(absTarget, "wx");
16039
18153
  try {
16040
18154
  writeSync(fd, block.replace);
@@ -16055,9 +18169,22 @@ function applyEditBlock(block, rootDir) {
16055
18169
  }
16056
18170
  }
16057
18171
  try {
18172
+ let writeTarget;
18173
+ try {
18174
+ writeTarget = realpathSync2(absTarget);
18175
+ } catch (err) {
18176
+ if (err.code === "ENOENT") {
18177
+ return {
18178
+ path: block.path,
18179
+ status: "file-missing",
18180
+ message: "file does not exist; to create it, use an empty SEARCH block"
18181
+ };
18182
+ }
18183
+ throw err;
18184
+ }
16058
18185
  let fd;
16059
18186
  try {
16060
- fd = openSync2(absTarget, "r+");
18187
+ fd = openSync2(writeTarget, "r+");
16061
18188
  } catch (err) {
16062
18189
  if (err.code === "ENOENT") {
16063
18190
  return {
@@ -16098,17 +18225,12 @@ function applyEditBlock(block, rootDir) {
16098
18225
  };
16099
18226
  }
16100
18227
  const replaced = `${content.slice(0, idx)}${adaptedReplace}${content.slice(idx + adaptedSearch.length)}`;
16101
- const outBuf = encodeFile(replaced, encoding);
16102
- ftruncateSync(fd, outBuf.length);
16103
- let written = 0;
16104
- while (written < outBuf.length) {
16105
- const n = writeSync(fd, outBuf, written, outBuf.length - written, written);
16106
- if (n <= 0) break;
16107
- written += n;
16108
- }
18228
+ closeSync2(fd);
18229
+ fd = void 0;
18230
+ atomicReplaceFileSync(writeTarget, encodeFile(replaced, encoding), stat2.mode);
16109
18231
  return { path: block.path, status: "applied" };
16110
18232
  } finally {
16111
- closeSync2(fd);
18233
+ if (fd !== void 0) closeSync2(fd);
16112
18234
  }
16113
18235
  } catch (err) {
16114
18236
  return { path: block.path, status: "error", message: err.message };
@@ -16118,7 +18240,7 @@ function applyEditBlocks(blocks, rootDir) {
16118
18240
  return blocks.map((b) => applyEditBlock(b, rootDir));
16119
18241
  }
16120
18242
  function snapshotBeforeEdits(blocks, rootDir) {
16121
- const absRoot = resolve14(rootDir);
18243
+ const absRoot = resolve15(rootDir);
16122
18244
  const seen = /* @__PURE__ */ new Set();
16123
18245
  const snapshots = [];
16124
18246
  for (const b of blocks) {
@@ -16126,7 +18248,7 @@ function snapshotBeforeEdits(blocks, rootDir) {
16126
18248
  if (!pathIsUnder4(abs, absRoot)) continue;
16127
18249
  if (seen.has(abs)) continue;
16128
18250
  seen.add(abs);
16129
- if (!existsSync11(abs)) {
18251
+ if (!existsSync12(abs)) {
16130
18252
  snapshots.push({ path: b.path, prevContent: null });
16131
18253
  continue;
16132
18254
  }
@@ -16140,7 +18262,7 @@ function snapshotBeforeEdits(blocks, rootDir) {
16140
18262
  return snapshots;
16141
18263
  }
16142
18264
  function restoreSnapshots(snapshots, rootDir) {
16143
- const absRoot = resolve14(rootDir);
18265
+ const absRoot = resolve15(rootDir);
16144
18266
  return snapshots.map((snap) => {
16145
18267
  const abs = resolveEditPath(rootDir, snap.path);
16146
18268
  if (!pathIsUnder4(abs, absRoot)) {
@@ -16152,14 +18274,14 @@ function restoreSnapshots(snapshots, rootDir) {
16152
18274
  }
16153
18275
  try {
16154
18276
  if (snap.prevContent === null) {
16155
- if (existsSync11(abs)) unlinkSync3(abs);
18277
+ if (existsSync12(abs)) unlinkSync4(abs);
16156
18278
  return {
16157
18279
  path: snap.path,
16158
18280
  status: "applied",
16159
18281
  message: "removed (the edit had created it)"
16160
18282
  };
16161
18283
  }
16162
- writeFileSync7(abs, encodeFile(snap.prevContent, snap.prevEncoding ?? "utf8"));
18284
+ writeFileSync8(abs, encodeFile(snap.prevContent, snap.prevEncoding ?? "utf8"));
16163
18285
  return {
16164
18286
  path: snap.path,
16165
18287
  status: "applied",
@@ -16175,8 +18297,8 @@ function lineEndingOf(text) {
16175
18297
  }
16176
18298
 
16177
18299
  // src/code/prompt.ts
16178
- import { existsSync as existsSync12, readFileSync as readFileSync16 } from "fs";
16179
- import { join as join15 } from "path";
18300
+ import { existsSync as existsSync13, readFileSync as readFileSync16 } from "fs";
18301
+ import { join as join16 } from "path";
16180
18302
  var DEFAULT_CODE_MODEL = "deepseek-v4-flash";
16181
18303
  function codeSystemBase(modelId) {
16182
18304
  return CODE_SYSTEM_TEMPLATE.replace("__ESCALATION_CONTRACT__", escalationContract(modelId));
@@ -16305,9 +18427,9 @@ function codeSystemPrompt(rootDir, opts = {}) {
16305
18427
  const codeBase = codeSystemBase(opts.modelId ?? DEFAULT_CODE_MODEL);
16306
18428
  const base = opts.hasSemanticSearch ? `${codeBase}${SEMANTIC_SEARCH_ROUTING}` : codeBase;
16307
18429
  const withMemory = applyMemoryStack(base, rootDir);
16308
- const gitignorePath = join15(rootDir, ".gitignore");
18430
+ const gitignorePath = join16(rootDir, ".gitignore");
16309
18431
  let result = withMemory;
16310
- if (existsSync12(gitignorePath)) {
18432
+ if (existsSync13(gitignorePath)) {
16311
18433
  let content;
16312
18434
  try {
16313
18435
  content = readFileSync16(gitignorePath, "utf8");
@@ -16344,21 +18466,21 @@ ${appendParts.join("\n\n")}`;
16344
18466
  import {
16345
18467
  appendFileSync as appendFileSync2,
16346
18468
  closeSync as closeSync3,
16347
- existsSync as existsSync13,
18469
+ existsSync as existsSync14,
16348
18470
  fstatSync as fstatSync2,
16349
- mkdirSync as mkdirSync7,
18471
+ mkdirSync as mkdirSync8,
16350
18472
  openSync as openSync3,
16351
18473
  readFileSync as readFileSync17,
16352
18474
  readSync as readSync2,
16353
- renameSync as renameSync3,
16354
- statSync as statSync6,
16355
- unlinkSync as unlinkSync4,
16356
- writeFileSync as writeFileSync8
18475
+ renameSync as renameSync4,
18476
+ statSync as statSync7,
18477
+ unlinkSync as unlinkSync5,
18478
+ writeFileSync as writeFileSync9
16357
18479
  } from "fs";
16358
- import { homedir as homedir9 } from "os";
16359
- import { dirname as dirname10, join as join16 } from "path";
18480
+ import { homedir as homedir10 } from "os";
18481
+ import { dirname as dirname10, join as join17 } from "path";
16360
18482
  function defaultUsageLogPath(homeDirOverride) {
16361
- return join16(homeDirOverride ?? homedir9(), ".reasonix", "usage.jsonl");
18483
+ return join17(homeDirOverride ?? homedir10(), ".reasonix", "usage.jsonl");
16362
18484
  }
16363
18485
  var USAGE_COMPACTION_THRESHOLD_BYTES = 5 * 1024 * 1024;
16364
18486
  var USAGE_RETENTION_DAYS = 365;
@@ -16397,12 +18519,12 @@ function compactUsageLogIfLarge(path2, now) {
16397
18519
  if (kept.length === lines.filter((l) => l.trim()).length) return;
16398
18520
  const tmp = `${path2}.compacting`;
16399
18521
  try {
16400
- writeFileSync8(tmp, kept.length > 0 ? `${kept.join("\n")}
18522
+ writeFileSync9(tmp, kept.length > 0 ? `${kept.join("\n")}
16401
18523
  ` : "", "utf8");
16402
- renameSync3(tmp, path2);
18524
+ renameSync4(tmp, path2);
16403
18525
  } catch {
16404
18526
  try {
16405
- unlinkSync4(tmp);
18527
+ unlinkSync5(tmp);
16406
18528
  } catch {
16407
18529
  }
16408
18530
  }
@@ -16423,7 +18545,7 @@ function appendUsage(input) {
16423
18545
  if (input.subagent) record.subagent = input.subagent;
16424
18546
  const path2 = input.path ?? defaultUsageLogPath();
16425
18547
  try {
16426
- mkdirSync7(dirname10(path2), { recursive: true });
18548
+ mkdirSync8(dirname10(path2), { recursive: true });
16427
18549
  appendFileSync2(path2, `${JSON.stringify(record)}
16428
18550
  `, "utf8");
16429
18551
  compactUsageLogIfLarge(path2, record.ts);
@@ -16432,7 +18554,7 @@ function appendUsage(input) {
16432
18554
  return record;
16433
18555
  }
16434
18556
  function readUsageLog(path2 = defaultUsageLogPath()) {
16435
- if (!existsSync13(path2)) return [];
18557
+ if (!existsSync14(path2)) return [];
16436
18558
  let raw;
16437
18559
  try {
16438
18560
  raw = readFileSync17(path2, "utf8");
@@ -16542,9 +18664,9 @@ function aggregateUsage(records, opts = {}) {
16542
18664
  };
16543
18665
  }
16544
18666
  function formatLogSize(path2 = defaultUsageLogPath()) {
16545
- if (!existsSync13(path2)) return "";
18667
+ if (!existsSync14(path2)) return "";
16546
18668
  try {
16547
- const s = statSync6(path2);
18669
+ const s = statSync7(path2);
16548
18670
  const bytes = s.size;
16549
18671
  if (bytes < 1024) return `${bytes} B`;
16550
18672
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;