reasonix 0.36.2 → 0.38.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 (87) hide show
  1. package/README.md +48 -75
  2. package/README.zh-CN.md +48 -32
  3. package/dist/cli/{chat-QSM6JKUA.js → chat-FPEYKTMI.js} +16 -17
  4. package/dist/cli/{chunk-T52GAWPP.js → chunk-3VTV4WAH.js} +2 -2
  5. package/dist/cli/{chunk-NHV5YGTB.js → chunk-4PNXH2MH.js} +1860 -1249
  6. package/dist/cli/chunk-4PNXH2MH.js.map +1 -0
  7. package/dist/cli/{chunk-DFP4YSVM.js → chunk-6CXT5JRM.js} +17 -2
  8. package/dist/cli/{chunk-DFP4YSVM.js.map → chunk-6CXT5JRM.js.map} +1 -1
  9. package/dist/cli/{chunk-G3XNWSFN.js → chunk-6NMWJSES.js} +2 -2
  10. package/dist/cli/{chunk-4D6TT2IB.js → chunk-A63QT566.js} +36 -15
  11. package/dist/cli/chunk-A63QT566.js.map +1 -0
  12. package/dist/cli/{chunk-4Q3GRJIU.js → chunk-AATCLE5N.js} +2 -2
  13. package/dist/cli/{chunk-BHLHOS5Y.js → chunk-BW2HWSYH.js} +315 -5
  14. package/dist/cli/chunk-BW2HWSYH.js.map +1 -0
  15. package/dist/cli/{chunk-ZJR4QLXB.js → chunk-FB46F6H4.js} +2 -2
  16. package/dist/cli/{chunk-MLXUGPJE.js → chunk-FYKZB6TX.js} +490 -8
  17. package/dist/cli/chunk-FYKZB6TX.js.map +1 -0
  18. package/dist/cli/{chunk-XQIFIB3U.js → chunk-JOFZ6AW5.js} +2 -2
  19. package/dist/cli/{chunk-IPCPEZWQ.js → chunk-LMNAMITH.js} +2 -2
  20. package/dist/cli/{chunk-S4GF3HPO.js → chunk-LY352GTC.js} +6 -4
  21. package/dist/cli/chunk-LY352GTC.js.map +1 -0
  22. package/dist/cli/{chunk-C5543CRX.js → chunk-NYP2DDDV.js} +41 -2
  23. package/dist/cli/chunk-NYP2DDDV.js.map +1 -0
  24. package/dist/cli/{chunk-BJ376EN3.js → chunk-T5U5JO7Q.js} +12 -9
  25. package/dist/cli/chunk-T5U5JO7Q.js.map +1 -0
  26. package/dist/cli/{chunk-K6W64QVE.js → chunk-XOIDSPMQ.js} +27 -7
  27. package/dist/cli/chunk-XOIDSPMQ.js.map +1 -0
  28. package/dist/cli/{chunk-RNSZYYGB.js → chunk-YJKLNYCP.js} +122 -33
  29. package/dist/cli/chunk-YJKLNYCP.js.map +1 -0
  30. package/dist/cli/{code-6C5A2CY3.js → code-GTE65OUT.js} +28 -21
  31. package/dist/cli/code-GTE65OUT.js.map +1 -0
  32. package/dist/cli/{commands-FE2UDFBC.js → commands-R4JWISND.js} +3 -4
  33. package/dist/cli/{commands-FE2UDFBC.js.map → commands-R4JWISND.js.map} +1 -1
  34. package/dist/cli/{commit-3IAGB22T.js → commit-TQ4DMUNS.js} +2 -3
  35. package/dist/cli/{commit-3IAGB22T.js.map → commit-TQ4DMUNS.js.map} +1 -1
  36. package/dist/cli/{doctor-DKD34EFD.js → doctor-GGK2JKTA.js} +7 -8
  37. package/dist/cli/{events-P27CX7LN.js → events-SQXPVV7B.js} +3 -3
  38. package/dist/cli/index.js +38 -37
  39. package/dist/cli/index.js.map +1 -1
  40. package/dist/cli/{mcp-2RDEQST6.js → mcp-M7I23TQ7.js} +2 -3
  41. package/dist/cli/{mcp-2RDEQST6.js.map → mcp-M7I23TQ7.js.map} +1 -1
  42. package/dist/cli/{mcp-browse-VM5GLRBQ.js → mcp-browse-TWO7RYT4.js} +2 -3
  43. package/dist/cli/{mcp-browse-VM5GLRBQ.js.map → mcp-browse-TWO7RYT4.js.map} +1 -1
  44. package/dist/cli/prompt-ODPFOKSH.js +13 -0
  45. package/dist/cli/{prune-sessions-ERL6B4G5.js → prune-sessions-FCFOYCBP.js} +2 -2
  46. package/dist/cli/{replay-D7RT2DR7.js → replay-R3QRXPI2.js} +13 -9
  47. package/dist/cli/replay-R3QRXPI2.js.map +1 -0
  48. package/dist/cli/{run-AG4Y45X7.js → run-WGSPYYOJ.js} +9 -10
  49. package/dist/cli/{run-AG4Y45X7.js.map → run-WGSPYYOJ.js.map} +1 -1
  50. package/dist/cli/{server-GNHR5K3N.js → server-IZPWQYG3.js} +98 -53
  51. package/dist/cli/{server-GNHR5K3N.js.map → server-IZPWQYG3.js.map} +1 -1
  52. package/dist/cli/{sessions-MHRF3GU4.js → sessions-E4UH5JYL.js} +9 -10
  53. package/dist/cli/{sessions-MHRF3GU4.js.map → sessions-E4UH5JYL.js.map} +1 -1
  54. package/dist/cli/{setup-IIAJXHP4.js → setup-FTZNN3TZ.js} +60 -15
  55. package/dist/cli/setup-FTZNN3TZ.js.map +1 -0
  56. package/dist/cli/{version-7AL4JZ63.js → version-MDVCFTKA.js} +9 -10
  57. package/dist/cli/{version-7AL4JZ63.js.map → version-MDVCFTKA.js.map} +1 -1
  58. package/dist/index.d.ts +9 -2
  59. package/dist/index.js +714 -54
  60. package/dist/index.js.map +1 -1
  61. package/package.json +1 -1
  62. package/dist/cli/chunk-4D6TT2IB.js.map +0 -1
  63. package/dist/cli/chunk-BHLHOS5Y.js.map +0 -1
  64. package/dist/cli/chunk-BJ376EN3.js.map +0 -1
  65. package/dist/cli/chunk-C5543CRX.js.map +0 -1
  66. package/dist/cli/chunk-K6W64QVE.js.map +0 -1
  67. package/dist/cli/chunk-MLXUGPJE.js.map +0 -1
  68. package/dist/cli/chunk-NHV5YGTB.js.map +0 -1
  69. package/dist/cli/chunk-RNSZYYGB.js.map +0 -1
  70. package/dist/cli/chunk-S4GF3HPO.js.map +0 -1
  71. package/dist/cli/chunk-WUI3P4RA.js +0 -319
  72. package/dist/cli/chunk-WUI3P4RA.js.map +0 -1
  73. package/dist/cli/code-6C5A2CY3.js.map +0 -1
  74. package/dist/cli/prompt-QSEB7HNQ.js +0 -11
  75. package/dist/cli/replay-D7RT2DR7.js.map +0 -1
  76. package/dist/cli/setup-IIAJXHP4.js.map +0 -1
  77. /package/dist/cli/{chat-QSM6JKUA.js.map → chat-FPEYKTMI.js.map} +0 -0
  78. /package/dist/cli/{chunk-T52GAWPP.js.map → chunk-3VTV4WAH.js.map} +0 -0
  79. /package/dist/cli/{chunk-G3XNWSFN.js.map → chunk-6NMWJSES.js.map} +0 -0
  80. /package/dist/cli/{chunk-4Q3GRJIU.js.map → chunk-AATCLE5N.js.map} +0 -0
  81. /package/dist/cli/{chunk-ZJR4QLXB.js.map → chunk-FB46F6H4.js.map} +0 -0
  82. /package/dist/cli/{chunk-XQIFIB3U.js.map → chunk-JOFZ6AW5.js.map} +0 -0
  83. /package/dist/cli/{chunk-IPCPEZWQ.js.map → chunk-LMNAMITH.js.map} +0 -0
  84. /package/dist/cli/{doctor-DKD34EFD.js.map → doctor-GGK2JKTA.js.map} +0 -0
  85. /package/dist/cli/{events-P27CX7LN.js.map → events-SQXPVV7B.js.map} +0 -0
  86. /package/dist/cli/{prompt-QSEB7HNQ.js.map → prompt-ODPFOKSH.js.map} +0 -0
  87. /package/dist/cli/{prune-sessions-ERL6B4G5.js.map → prune-sessions-FCFOYCBP.js.map} +0 -0
package/dist/index.js CHANGED
@@ -980,6 +980,10 @@ var EN = {
980
980
  title: "copy / paste",
981
981
  rows: [
982
982
  { key: "select text", text: "drag to select \u2014 terminal-native (no modifier needed)" },
983
+ {
984
+ key: "/copy",
985
+ text: "vim/tmux-style copy mode \u2014 works in SSH/mosh/tmux where drag-select can't extend past the viewport"
986
+ },
983
987
  {
984
988
  key: "copy",
985
989
  text: "Ctrl+Shift+C (Win/Linux) \xB7 Cmd+C (macOS) \u2014 or auto-copy-on-select if your terminal does it"
@@ -1062,6 +1066,9 @@ var EN = {
1062
1066
  },
1063
1067
  slash: {
1064
1068
  help: { description: "show the full command reference" },
1069
+ copy: {
1070
+ description: "open vim/tmux-style copy mode \u2014 j/k navigate, v select, y yank to clipboard"
1071
+ },
1065
1072
  status: { description: "current model, flags, context, session" },
1066
1073
  preset: {
1067
1074
  description: "model bundle \u2014 auto escalates flash \u2192 pro, flash/pro lock",
@@ -1069,6 +1076,10 @@ var EN = {
1069
1076
  },
1070
1077
  model: { description: "switch DeepSeek model id", argsHint: "<id>" },
1071
1078
  models: { description: "list available models fetched from DeepSeek /models" },
1079
+ theme: {
1080
+ description: "show or persist the terminal theme preference. Bare opens picker.",
1081
+ argsHint: "[auto|default|dark|light|tokyo-night|github-dark|github-light|high-contrast]"
1082
+ },
1072
1083
  language: {
1073
1084
  description: "switch the runtime language",
1074
1085
  argsHint: "<EN|zh-CN>",
@@ -1124,9 +1135,15 @@ var EN = {
1124
1135
  context: { description: "show context-window breakdown (system / tools / log / input)" },
1125
1136
  retry: { description: "truncate & resend your last message (fresh sample)" },
1126
1137
  compact: {
1127
- description: "shrink oversized tool results AND tool-call args (edit_file search/replace) in the log; cap in tokens, default 4000",
1138
+ description: "narrow oversized tool results + tool-call args in the log; cap at tokens, default 4000",
1128
1139
  argsHint: "[tokens]"
1129
1140
  },
1141
+ cwd: {
1142
+ description: "switch the workspace root mid-session \u2014 re-points fs / shell / memory tools, reloads project hooks, refreshes the at-mention walker",
1143
+ argsHint: "<path>"
1144
+ },
1145
+ stop: { description: "abort the current model turn (typed alternative to Esc)" },
1146
+ feedback: { description: "open a GitHub issue with diagnostic info copied to clipboard" },
1130
1147
  keys: { description: "keyboard + mouse + copy/paste reference" },
1131
1148
  plans: { description: "list this session's active + archived plans, newest first" },
1132
1149
  replay: {
@@ -1202,6 +1219,9 @@ var EN = {
1202
1219
  apiKeySavedLocally: "Saved locally to {path}",
1203
1220
  apiKeyInputLabel: "key \u203A ",
1204
1221
  apiKeyInvalid: "Key looks too short \u2014 paste the full token (16+ chars, no spaces).",
1222
+ apiKeyChecking: "Checking API key\u2026",
1223
+ apiKeyRejected: "DeepSeek rejected this API key. Paste a valid key, or press Esc to cancel setup.",
1224
+ apiKeyCheckFailed: "Could not verify this API key right now ({message}). Check your network or try again.",
1205
1225
  apiKeyPreview: "preview: {redacted}",
1206
1226
  themeTitle: "Choose a theme",
1207
1227
  themeSubtitle: "Preview updates live as you navigate. Change later with /theme.",
@@ -1399,6 +1419,7 @@ var EN = {
1399
1419
  handlers: {
1400
1420
  basic: {
1401
1421
  newInfo: "\u25B8 new conversation \u2014 dropped {count} message(s) from context. Same session, fresh slate.",
1422
+ newInfoArchived: '\u25B8 new conversation \u2014 dropped {count} message(s) from context. Prior transcript archived as "{archived}" (visible under Sessions).',
1402
1423
  helpTitle: "Commands:",
1403
1424
  helpShellTitle: "Shell shortcut:",
1404
1425
  helpShell: " !<cmd> run <cmd> in the sandbox root; output goes into",
@@ -1716,6 +1737,189 @@ var EN = {
1716
1737
  newError: "\u25B2 /skill new failed: {reason}"
1717
1738
  }
1718
1739
  },
1740
+ statusBar: {
1741
+ turn: "turn",
1742
+ cache: "cache",
1743
+ spent: "spent",
1744
+ left: " left",
1745
+ slow: "slow",
1746
+ disconnect: "disconnect",
1747
+ reconnecting: "reconnecting\u2026",
1748
+ approvingIn: "approving in ",
1749
+ escToInterrupt: "s \xB7 esc to interrupt",
1750
+ recordingGlyph: "\u25CFREC",
1751
+ mb: " MB",
1752
+ evt: " evt"
1753
+ },
1754
+ editMode: {
1755
+ plan: "PLAN MODE",
1756
+ yolo: "YOLO",
1757
+ auto: "AUTO",
1758
+ review: "REVIEW",
1759
+ writesGated: " writes gated \xB7 /plan off to leave",
1760
+ editsShellAuto: "edits + shell auto \xB7 /undo to roll back",
1761
+ editsLandNow: "edits land now \xB7 u to undo",
1762
+ queuedApplyDiscard: "{count} queued \xB7 y apply \xB7 n discard",
1763
+ editsQueued: "edits queued \xB7 y apply \xB7 n discard",
1764
+ shiftTabFlip: " {mid} \xB7 Shift+Tab to flip",
1765
+ queuedDots: "queued\u2026"
1766
+ },
1767
+ composer: {
1768
+ placeholder: "ask anything \xB7 slash for commands \xB7 at-sign for files",
1769
+ waitingForResponse: "\u2026waiting for response\u2026",
1770
+ hintSend: "send",
1771
+ hintNewline: "newline",
1772
+ hintClear: "clear",
1773
+ hintScroll: "scroll",
1774
+ hintHistory: "history",
1775
+ hintAbort: "abort",
1776
+ hintQuit: "quit",
1777
+ abortedHint: "turn aborted by user \xB7 esc again to clear \xB7 \u23CE to ask a follow-up"
1778
+ },
1779
+ shellConfirm: {
1780
+ title: "Shell command",
1781
+ bgTitle: "Background process",
1782
+ subtitle: "model wants to run a shell command",
1783
+ bgSubtitle: "long-running process \u2014 keeps running after approval, /kill to stop",
1784
+ denyTitle: "Deny \u2014 provide context",
1785
+ optional: "optional",
1786
+ denyFooter: "type context \xB7 \u23CE submit with reason \xB7 esc skip (deny without reason)",
1787
+ awaiting: "awaiting",
1788
+ pickFooter: "\u2191\u2193 pick \xB7 \u23CE confirm \xB7 Tab add context \xB7 esc cancel",
1789
+ allowOnce: "allow once",
1790
+ allowOnceDesc: "run this command, ask again next time",
1791
+ allowAlways: "allow always",
1792
+ allowAlwaysDesc: "remember `{prefix}` for this project",
1793
+ deny: "deny",
1794
+ denyDesc: "press Tab to add context telling the model why"
1795
+ },
1796
+ editConfirm: {
1797
+ footer: "[y/Enter] apply \xB7 [n] reject with reason \xB7 [a] apply rest \xB7 [A] flip AUTO \xB7 [\u2191\u2193/Space] scroll \xB7 [Esc] abort",
1798
+ newTag: "NEW",
1799
+ editTag: "EDIT",
1800
+ linesCount: "-{removed} +{added} lines",
1801
+ viewingRange: "viewing {start}-{end}/{total}",
1802
+ denyFooter: "\u23CE submit \xB7 esc skip (deny without reason)",
1803
+ oldLabel: " - old",
1804
+ newLabel: " + new",
1805
+ sideBySide: " side-by-side \xB7 removed lines on the left, added on the right \xB7 paired by offset",
1806
+ linesAbove: " \u2191 {count} line above (\u2191/k or PgUp)",
1807
+ linesAbovePlural: " \u2191 {count} lines above (\u2191/k or PgUp)",
1808
+ linesBelow: " \u2193 {count} line below (\u2193/j or Space/PgDn)",
1809
+ linesBelowPlural: " \u2193 {count} lines below (\u2193/j or Space/PgDn)"
1810
+ },
1811
+ sessionPicker: {
1812
+ header: " \u25C8 REASONIX \xB7 pick a session ",
1813
+ title: "pick a session \u2014 {workspace}",
1814
+ messages: "{count} message",
1815
+ messagesPlural: "{count} messages",
1816
+ turns: "{count} turns",
1817
+ pickerHint: "\u2191\u2193 pick \xB7 \u23CE open \xB7 [n] new \xB7 [d] delete \xB7 [r] rename \xB7 esc quit",
1818
+ empty: " no saved sessions in this workspace yet \u2014 press ",
1819
+ emptyNew: " to start a new one",
1820
+ renamePrompt: ' rename "{from}" \u2192 ',
1821
+ renameHint: " \u23CE confirm rename \xB7 esc cancel",
1822
+ emptyHint: " \u23CE new session \xB7 esc quit",
1823
+ justNow: "just now",
1824
+ minAgo: "{count} min ago",
1825
+ yesterday: "yesterday",
1826
+ hoursAgo: "{count}h ago",
1827
+ daysAgo: "{count} days ago"
1828
+ },
1829
+ modelPicker: {
1830
+ header: " \u25C8 REASONIX \xB7 pick a setup ",
1831
+ loading: " \xB7 loading catalog\u2026",
1832
+ catalogEmpty: " \xB7 catalog empty \u2014 using known fallbacks",
1833
+ modelsAvailable: " \xB7 {count} models available",
1834
+ presetsHeader: " PRESETS \xB7 recommended \u2014 model + effort + auto-escalate",
1835
+ modelsHeader: " MODELS \xB7 raw pick \u2014 auto-escalate stays as-is",
1836
+ pickerFooter: " \u2191\u2193 pick \xB7 \u23CE confirm \xB7 [r] refresh \xB7 esc cancel",
1837
+ currentLabel: " \xB7 current"
1838
+ },
1839
+ slashSuggestions: {
1840
+ noMatch: "no slash command matches that prefix",
1841
+ backspaceHint: " \u2014 Backspace to edit, or /help for the full list",
1842
+ commandCount: "{count} command",
1843
+ commandCountPlural: "{count} commands",
1844
+ aboveLabel: " \u2191 {count} above",
1845
+ belowLabel: " \u2193 {count} below",
1846
+ advancedHint: " + {count} advanced \xB7 type a letter to search",
1847
+ footerHint: " \u2191\u2193 navigate \xB7 Tab / \u23CE pick \xB7 esc cancel",
1848
+ groupChat: "CHAT",
1849
+ groupSetup: "SETUP",
1850
+ groupInfo: "INFO",
1851
+ groupSession: "SESSION",
1852
+ groupExtend: "EXTEND",
1853
+ groupCode: "CODE",
1854
+ groupJobs: "JOBS",
1855
+ groupAdvanced: "ADVANCED"
1856
+ },
1857
+ atMentions: {
1858
+ loading: "loading\u2026",
1859
+ entrySingular: "{count} entry",
1860
+ entryPlural: "{count} entries",
1861
+ searching: "searching\u2026",
1862
+ scanned: "scanned",
1863
+ match: "match",
1864
+ matches: "matches",
1865
+ forFilter: 'for "{filter}"',
1866
+ noMatch: 'no files match "{filter}"',
1867
+ emptyDir: "empty directory",
1868
+ scanning: "scanning the tree\u2026",
1869
+ footerBrowse: "\u2191\u2193 navigate \xB7 Tab drill into folder \xB7 \u23CE insert \xB7 esc cancel",
1870
+ footerBrowseSearch: "\u2191\u2193 navigate \xB7 Tab / \u23CE insert as @path \xB7 esc cancel",
1871
+ footerInsert: "\u2191\u2193 navigate \xB7 Tab / \u23CE insert as @path \xB7 esc cancel"
1872
+ },
1873
+ statsPanel: {
1874
+ modePlan: "PLAN",
1875
+ modeYolo: "yolo",
1876
+ modeAuto: "auto",
1877
+ modeReview: "review",
1878
+ pro: "\u21E7 pro",
1879
+ budget: " budget "
1880
+ },
1881
+ welcomeBanner: {
1882
+ workspace: "\u25B8 workspace",
1883
+ relaunchHint: " (relaunch with --dir <path> to switch)",
1884
+ dashboard: "\u25B8 web"
1885
+ },
1886
+ ctxBreakdown: {
1887
+ title: "\u25A3 context",
1888
+ compactHint: " /compact folds (auto at 50%) \xB7 /new wipes log",
1889
+ topTools: " top tool results by cost ({count}):",
1890
+ msg: "msg",
1891
+ turnLabel: "turn"
1892
+ },
1893
+ startup: {
1894
+ codeRooted: '\u25B8 reasonix code: rooted at {rootDir}, session "{session}" \xB7 {tools} native tool(s){semantic}',
1895
+ ephemeral: "(ephemeral)",
1896
+ semanticOn: " \xB7 semantic_search on"
1897
+ },
1898
+ doctorErrors: {
1899
+ unreadable: "{path} unreadable \u2014 {message}",
1900
+ cannotList: "cannot list \u2014 {message}",
1901
+ parseFailed: "couldn't parse settings.json \u2014 {message}",
1902
+ probeFailed: "probe failed \u2014 {message}"
1903
+ },
1904
+ webErrors: {
1905
+ status: "web_search {status}",
1906
+ mojeekBlocked: "web_search: Mojeek anti-bot page \u2014 rate-limited or blocked",
1907
+ mojeekNoResults: "web_search: 0 results but response doesn't look like a real empty page ({chars} chars, first 120: {preview})",
1908
+ invalidEndpoint: 'web_search: invalid SearXNG endpoint "{endpoint}"',
1909
+ endpointMustBeHttp: "web_search: SearXNG endpoint must be http(s), got {protocol}",
1910
+ cannotReach: "web_search: Cannot reach SearXNG server at {endpoint}. Please install SearXNG (https://github.com/searxng/searxng) and start it (e.g. `docker run -d -p 8080:8080 searxng/searxng`), or switch to the default engine with /search-engine mojeek.",
1911
+ searxngNoResults: "web_search: 0 results but SearXNG response doesn't look like an empty results page ({chars} chars)",
1912
+ fetchStatus: "web_fetch {status} for {url}",
1913
+ fetchTooLarge: "web_fetch refused: content-length {len} bytes exceeds {cap}-byte cap ({url})",
1914
+ fetchBodyTooLarge: "web_fetch refused: response body exceeded {cap}-byte cap ({seen} bytes seen)",
1915
+ fetchInvalidUrl: "web_fetch: url must start with http:// or https://"
1916
+ },
1917
+ choiceConfirm: {
1918
+ customLabel: "Let me type my own answer",
1919
+ customDesc: "None of the above fits \u2014 type a free-form reply. The model reads it verbatim.",
1920
+ cancelLabel: "Cancel \u2014 drop the question",
1921
+ cancelDesc: "Model stops and asks what you want instead."
1922
+ },
1719
1923
  cardTitles: {
1720
1924
  usage: "usage",
1721
1925
  context: "context",
@@ -1725,7 +1929,9 @@ var EN = {
1725
1929
  reasoning: "reasoning",
1726
1930
  reasoningAborted: "reasoning (aborted)",
1727
1931
  reasoningEllipsis: "reasoning\u2026",
1728
- error: "error"
1932
+ error: "error",
1933
+ doctor: "doctor",
1934
+ you: "you"
1729
1935
  },
1730
1936
  cardLabels: {
1731
1937
  prompt: "prompt",
@@ -1764,7 +1970,42 @@ var EN = {
1764
1970
  retries: "retries",
1765
1971
  reasoningLabel: "reasoning \xB7 {count} \xB6",
1766
1972
  runningLabel: "running",
1767
- workingLabel: "working"
1973
+ workingLabel: "working",
1974
+ defaultFooter: "\u2191\u2193 pick \xB7 \u23CE confirm \xB7 esc cancel",
1975
+ applyAction: "[a] apply",
1976
+ skipAction: "[s] skip",
1977
+ rejectAction: "[r] reject",
1978
+ levelOk: "OK",
1979
+ levelWarn: "warn",
1980
+ levelFail: "FAIL",
1981
+ checksLabel: "checks",
1982
+ passed: "passed",
1983
+ warnTag: "warn",
1984
+ failTag: "fail",
1985
+ stepLabel: "step",
1986
+ done: "done",
1987
+ inProgress: "\u2190 in progress",
1988
+ upcoming: "upcoming",
1989
+ resumed: "resumed \xB7 ",
1990
+ archive: "\u23EA archive \xB7 ",
1991
+ more: "\u22EE +{count} more",
1992
+ categoryUser: "user",
1993
+ categoryFeedback: "feedback",
1994
+ categoryProject: "project",
1995
+ categoryReference: "reference"
1996
+ },
1997
+ copyMode: {
1998
+ title: "\u2500\u2500 COPY MODE \u2500\u2500",
1999
+ help: "j/k or \u2191/\u2193 move \xB7 v select \xB7 y yank \xB7 g/G top/bottom \xB7 q quit",
2000
+ statusBar: "line {cur}/{total} \xB7 selection: {sel}",
2001
+ statusYanked: "yanked {size} chars (osc52={osc52})",
2002
+ statusEmpty: "nothing selected",
2003
+ empty: "(no chat content yet \u2014 say something to the model first)",
2004
+ labelUser: "you",
2005
+ labelAssistant: "assistant",
2006
+ labelReasoning: "reasoning",
2007
+ yankedToast: "\u25B8 copied {size} chars to clipboard (osc52)",
2008
+ yankedToastFile: "\u25B8 copied {size} chars \xB7 file: {path}"
1768
2009
  }
1769
2010
  };
1770
2011
 
@@ -1891,6 +2132,10 @@ var zhCN = {
1891
2132
  title: "\u590D\u5236 / \u7C98\u8D34",
1892
2133
  rows: [
1893
2134
  { key: "\u9009\u4E2D\u6587\u5B57", text: "\u76F4\u63A5\u62D6\u52A8 \u2014 \u7EC8\u7AEF\u539F\u751F\uFF08\u4E0D\u9700\u8981\u4EFB\u4F55\u4FEE\u9970\u952E\uFF09" },
2135
+ {
2136
+ key: "/copy",
2137
+ text: "vim/tmux \u98CE\u683C\u590D\u5236\u6A21\u5F0F \u2014 SSH / mosh / tmux \u4E0B\u62D6\u9009\u8D8A\u8FC7\u53EF\u89C6\u533A\u65E0\u6548\u65F6\u7528\u8FD9\u4E2A"
2138
+ },
1894
2139
  {
1895
2140
  key: "\u590D\u5236",
1896
2141
  text: "Ctrl+Shift+C\uFF08Win/Linux\uFF09\xB7 Cmd+C\uFF08macOS\uFF09\u2014 \u6216\u9009\u4E2D\u5373\u590D\u5236\uFF08\u770B\u7EC8\u7AEF\u8BBE\u7F6E\uFF09"
@@ -1973,6 +2218,9 @@ var zhCN = {
1973
2218
  },
1974
2219
  slash: {
1975
2220
  help: { description: "\u663E\u793A\u5B8C\u6574\u547D\u4EE4\u53C2\u8003" },
2221
+ copy: {
2222
+ description: "\u8FDB\u5165 vim/tmux \u98CE\u683C\u590D\u5236\u6A21\u5F0F \u2014 j/k \u79FB\u52A8\u3001v \u8D77\u9009\u533A\u3001y \u590D\u5236\u5230\u526A\u8D34\u677F"
2223
+ },
1976
2224
  status: { description: "\u5F53\u524D\u6A21\u578B\u3001\u6807\u5FD7\u3001\u4E0A\u4E0B\u6587\u3001\u4F1A\u8BDD" },
1977
2225
  preset: {
1978
2226
  description: "\u6A21\u578B\u7EC4\u5408 \u2014 \u81EA\u52A8\u5728 flash \u2192 pro \u4E4B\u95F4\u5207\u6362\uFF0C\u6216\u9501\u5B9A flash/pro",
@@ -1980,6 +2228,10 @@ var zhCN = {
1980
2228
  },
1981
2229
  model: { description: "\u5207\u6362 DeepSeek \u6A21\u578B ID", argsHint: "<id>" },
1982
2230
  models: { description: "\u5217\u51FA\u4ECE DeepSeek /models \u83B7\u53D6\u7684\u53EF\u7528\u6A21\u578B" },
2231
+ theme: {
2232
+ description: "\u663E\u793A\u6216\u6301\u4E45\u5316\u7EC8\u7AEF\u4E3B\u9898\u504F\u597D\u3002\u65E0\u53C2\u6570\u65F6\u6253\u5F00\u9009\u62E9\u5668\u3002",
2233
+ argsHint: "[auto|default|dark|light|tokyo-night|github-dark|github-light|high-contrast]"
2234
+ },
1983
2235
  language: {
1984
2236
  description: "\u5207\u6362\u8FD0\u884C\u65F6\u8BED\u8A00",
1985
2237
  argsHint: "<en|zh-CN>",
@@ -2041,6 +2293,12 @@ var zhCN = {
2041
2293
  argsHint: "[tokens]"
2042
2294
  },
2043
2295
  keys: { description: "\u952E\u76D8 + \u9F20\u6807 + \u590D\u5236\u7C98\u8D34\u53C2\u8003" },
2296
+ cwd: {
2297
+ description: "\u5207\u6362\u5DE5\u4F5C\u533A\u6839\u76EE\u5F55 \u2014 \u91CD\u65B0\u6307\u5411\u6587\u4EF6/Shell/\u8BB0\u5FC6\u5DE5\u5177\uFF0C\u91CD\u8F7D\u9879\u76EE hooks\uFF0C\u5237\u65B0 @ \u5F15\u7528\u904D\u5386\u5668",
2298
+ argsHint: "<path>"
2299
+ },
2300
+ stop: { description: "\u4E2D\u6B62\u5F53\u524D\u6A21\u578B\u56DE\u5408\uFF08\u6309 Esc \u7684\u66FF\u4EE3\u65B9\u5F0F\uFF09" },
2301
+ feedback: { description: "\u6253\u5F00 GitHub Issue\uFF0C\u8BCA\u65AD\u4FE1\u606F\u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F" },
2044
2302
  plans: { description: "\u5217\u51FA\u6B64\u4F1A\u8BDD\u7684\u6D3B\u8DC3 + \u5F52\u6863\u8BA1\u5212\uFF08\u6700\u65B0\u5728\u524D\uFF09" },
2045
2303
  replay: {
2046
2304
  description: "\u52A0\u8F7D\u5F52\u6863\u8BA1\u5212\u4E3A\u53EA\u8BFB\u7684\u65F6\u95F4\u65C5\u884C\u5FEB\u7167\uFF08\u9ED8\u8BA4\uFF1A\u6700\u65B0\uFF09",
@@ -2117,6 +2375,9 @@ var zhCN = {
2117
2375
  apiKeySavedLocally: "\u4FDD\u5B58\u5728\u672C\u5730\uFF1A{path}",
2118
2376
  apiKeyInputLabel: "key \u203A ",
2119
2377
  apiKeyInvalid: "key \u957F\u5EA6\u4E0D\u8DB3\u2014\u2014\u8BF7\u7C98\u8D34\u5B8C\u6574 token\uFF0816+ \u5B57\u7B26\uFF0C\u4E0D\u542B\u7A7A\u683C\uFF09\u3002",
2378
+ apiKeyChecking: "\u6B63\u5728\u68C0\u67E5 API key\u2026",
2379
+ apiKeyRejected: "DeepSeek \u62D2\u7EDD\u4E86\u8FD9\u4E2A API key\u3002\u8BF7\u7C98\u8D34\u6709\u6548 key\uFF0C\u6216\u6309 Esc \u53D6\u6D88\u8BBE\u7F6E\u3002",
2380
+ apiKeyCheckFailed: "\u6682\u65F6\u65E0\u6CD5\u9A8C\u8BC1 API key\uFF08{message}\uFF09\u3002\u8BF7\u68C0\u67E5\u7F51\u7EDC\u540E\u91CD\u8BD5\u3002",
2120
2381
  apiKeyPreview: "\u9884\u89C8\uFF1A{redacted}",
2121
2382
  themeTitle: "\u9009\u62E9\u4E3B\u9898",
2122
2383
  themeSubtitle: "\u65B9\u5411\u952E\u5207\u6362\u65F6\u5373\u65F6\u9884\u89C8\u6548\u679C\uFF0C\u4E4B\u540E\u53EF\u7528 /theme \u66F4\u6539\u3002",
@@ -2314,6 +2575,7 @@ var zhCN = {
2314
2575
  handlers: {
2315
2576
  basic: {
2316
2577
  newInfo: "\u25B8 \u65B0\u5BF9\u8BDD \u2014 \u5DF2\u4ECE\u4E0A\u4E0B\u6587\u4E2D\u4E22\u5F03 {count} \u6761\u6D88\u606F\u3002\u540C\u4E00\u4F1A\u8BDD\uFF0C\u5168\u65B0\u5F00\u59CB\u3002",
2578
+ newInfoArchived: "\u25B8 \u65B0\u5BF9\u8BDD \u2014 \u5DF2\u4ECE\u4E0A\u4E0B\u6587\u4E2D\u4E22\u5F03 {count} \u6761\u6D88\u606F\u3002\u539F\u5BF9\u8BDD\u5DF2\u5F52\u6863\u4E3A\u300C{archived}\u300D\uFF0C\u53EF\u5728 Sessions \u9762\u677F\u67E5\u770B\u3002",
2317
2579
  helpTitle: "\u547D\u4EE4\uFF1A",
2318
2580
  helpShellTitle: "Shell \u5FEB\u6377\u65B9\u5F0F\uFF1A",
2319
2581
  helpShell: " !<cmd> \u5728\u6C99\u7BB1\u6839\u76EE\u5F55\u8FD0\u884C <cmd>\uFF1B\u8F93\u51FA\u8FDB\u5165\u5BF9\u8BDD",
@@ -2631,6 +2893,189 @@ var zhCN = {
2631
2893
  newError: "\u25B2 /skill new \u5931\u8D25\uFF1A{reason}"
2632
2894
  }
2633
2895
  },
2896
+ statusBar: {
2897
+ turn: "\u8F6E",
2898
+ cache: "\u7F13\u5B58",
2899
+ spent: "\u5DF2\u82B1\u8D39",
2900
+ left: " \u5269\u4F59",
2901
+ slow: "\u6162\u901F",
2902
+ disconnect: "\u65AD\u5F00",
2903
+ reconnecting: "\u91CD\u8FDE\u4E2D\u2026",
2904
+ approvingIn: "\u5373\u5C06\u6279\u51C6\uFF0C",
2905
+ escToInterrupt: "\u79D2 \xB7 Esc \u4E2D\u65AD",
2906
+ recordingGlyph: "\u25CFREC",
2907
+ mb: " MB",
2908
+ evt: " \u4E8B\u4EF6"
2909
+ },
2910
+ editMode: {
2911
+ plan: "\u8BA1\u5212",
2912
+ yolo: "\u81EA\u7531",
2913
+ auto: "\u81EA\u52A8",
2914
+ review: "\u5BA1\u67E5",
2915
+ writesGated: " \u5DF2\u9650\u5236\u5199\u5165 \xB7 /plan off \u89E3\u9664",
2916
+ editsShellAuto: "\u7F16\u8F91 + Shell \u81EA\u52A8 \xB7 /undo \u53EF\u56DE\u6EDA",
2917
+ editsLandNow: "\u7F16\u8F91\u5DF2\u751F\u6548 \xB7 \u6309 u \u64A4\u6D88",
2918
+ queuedApplyDiscard: "{count} \u4E2A\u5F85\u5904\u7406 \xB7 y \u5E94\u7528 \xB7 n \u4E22\u5F03",
2919
+ editsQueued: "\u7F16\u8F91\u5DF2\u6392\u961F \xB7 y \u5E94\u7528 \xB7 n \u4E22\u5F03",
2920
+ shiftTabFlip: " {mid} \xB7 Shift+Tab \u5207\u6362",
2921
+ queuedDots: "\u6392\u961F\u4E2D\u2026"
2922
+ },
2923
+ composer: {
2924
+ placeholder: "\u8F93\u5165\u4EFB\u4F55\u5185\u5BB9 \xB7 / \u4F7F\u7528\u547D\u4EE4 \xB7 @ \u5F15\u7528\u6587\u4EF6",
2925
+ waitingForResponse: "\u2026\u7B49\u5F85\u54CD\u5E94\u2026",
2926
+ hintSend: "\u53D1\u9001",
2927
+ hintNewline: "\u6362\u884C",
2928
+ hintClear: "\u6E05\u7A7A",
2929
+ hintScroll: "\u6EDA\u52A8",
2930
+ hintHistory: "\u5386\u53F2",
2931
+ hintAbort: "\u4E2D\u6B62",
2932
+ hintQuit: "\u9000\u51FA",
2933
+ abortedHint: "\u7528\u6237\u5DF2\u4E2D\u6B62\u672C\u8F6E \xB7 \u518D\u6309 Esc \u6E05\u9664 \xB7 \u23CE \u7EE7\u7EED\u63D0\u95EE"
2934
+ },
2935
+ shellConfirm: {
2936
+ title: "Shell \u547D\u4EE4",
2937
+ bgTitle: "\u540E\u53F0\u8FDB\u7A0B",
2938
+ subtitle: "\u6A21\u578B\u8BF7\u6C42\u6267\u884C Shell \u547D\u4EE4",
2939
+ bgSubtitle: "\u957F\u65F6\u95F4\u8FD0\u884C \u2014 \u6279\u51C6\u540E\u6301\u7EED\u8FD0\u884C\uFF0C/kill \u53EF\u505C\u6B62",
2940
+ denyTitle: "\u62D2\u7EDD \u2014 \u63D0\u4F9B\u539F\u56E0",
2941
+ optional: "\u53EF\u9009",
2942
+ denyFooter: "\u8F93\u5165\u539F\u56E0 \xB7 \u23CE \u63D0\u4EA4 \xB7 Esc \u8DF3\u8FC7\uFF08\u76F4\u63A5\u62D2\u7EDD\uFF09",
2943
+ awaiting: "\u7B49\u5F85\u4E2D",
2944
+ pickFooter: "\u2191\u2193 \u9009\u62E9 \xB7 \u23CE \u786E\u8BA4 \xB7 Tab \u6DFB\u52A0\u8BF4\u660E \xB7 Esc \u53D6\u6D88",
2945
+ allowOnce: "\u5141\u8BB8\u4E00\u6B21",
2946
+ allowOnceDesc: "\u6267\u884C\u6B64\u547D\u4EE4\uFF0C\u4E0B\u6B21\u518D\u95EE",
2947
+ allowAlways: "\u59CB\u7EC8\u5141\u8BB8",
2948
+ allowAlwaysDesc: "\u8BB0\u4F4F `{prefix}`\uFF0C\u672C\u9879\u76EE\u5185\u4E0D\u518D\u8BE2\u95EE",
2949
+ deny: "\u62D2\u7EDD",
2950
+ denyDesc: "\u6309 Tab \u6DFB\u52A0\u8BF4\u660E\uFF0C\u544A\u8BC9\u6A21\u578B\u539F\u56E0"
2951
+ },
2952
+ editConfirm: {
2953
+ footer: "[y/Enter] \u5E94\u7528 \xB7 [n] \u62D2\u7EDD\u5E76\u8BF4\u660E \xB7 [a] \u5E94\u7528\u5269\u4F59 \xB7 [A] \u5207\u6362 AUTO \xB7 [\u2191\u2193/Space] \u6EDA\u52A8 \xB7 [Esc] \u4E2D\u6B62",
2954
+ newTag: "\u65B0\u589E",
2955
+ editTag: "\u7F16\u8F91",
2956
+ linesCount: "-{removed} +{added} \u884C",
2957
+ viewingRange: "\u6B63\u5728\u67E5\u770B {start}-{end}/{total}",
2958
+ denyFooter: "\u23CE \u63D0\u4EA4 \xB7 Esc \u8DF3\u8FC7\uFF08\u76F4\u63A5\u62D2\u7EDD\uFF09",
2959
+ oldLabel: " \u65E7\u5185\u5BB9",
2960
+ newLabel: " \u65B0\u5185\u5BB9",
2961
+ sideBySide: " \u5DE6\u53F3\u5BF9\u6BD4 \xB7 \u5DE6\u4FA7\u5220\u9664\uFF0C\u53F3\u4FA7\u65B0\u589E \xB7 \u6309\u504F\u79FB\u914D\u5BF9",
2962
+ linesAbove: " \u2191 \u4E0A\u65B9 {count} \u884C\uFF08\u2191/k \u6216 PgUp\uFF09",
2963
+ linesAbovePlural: " \u2191 \u4E0A\u65B9 {count} \u884C\uFF08\u2191/k \u6216 PgUp\uFF09",
2964
+ linesBelow: " \u2193 \u4E0B\u65B9 {count} \u884C\uFF08\u2193/j \u6216 Space/PgDn\uFF09",
2965
+ linesBelowPlural: " \u2193 \u4E0B\u65B9 {count} \u884C\uFF08\u2193/j \u6216 Space/PgDn\uFF09"
2966
+ },
2967
+ sessionPicker: {
2968
+ header: " \u25C8 REASONIX \xB7 \u9009\u62E9\u4F1A\u8BDD ",
2969
+ title: "\u9009\u62E9\u4F1A\u8BDD \u2014 {workspace}",
2970
+ messages: "{count} \u6761\u6D88\u606F",
2971
+ messagesPlural: "{count} \u6761\u6D88\u606F",
2972
+ turns: "{count} \u8F6E",
2973
+ pickerHint: "\u2191\u2193 \u9009\u62E9 \xB7 \u23CE \u6253\u5F00 \xB7 [n] \u65B0\u5EFA \xB7 [d] \u5220\u9664 \xB7 [r] \u91CD\u547D\u540D \xB7 Esc \u9000\u51FA",
2974
+ empty: " \u6B64\u5DE5\u4F5C\u533A\u6682\u65E0\u5DF2\u4FDD\u5B58\u7684\u4F1A\u8BDD \u2014 \u6309 ",
2975
+ emptyNew: " \u5F00\u59CB\u65B0\u4F1A\u8BDD",
2976
+ renamePrompt: ' \u91CD\u547D\u540D "{from}" \u2192 ',
2977
+ renameHint: " \u23CE \u786E\u8BA4\u91CD\u547D\u540D \xB7 Esc \u53D6\u6D88",
2978
+ emptyHint: " \u23CE \u65B0\u5EFA\u4F1A\u8BDD \xB7 Esc \u9000\u51FA",
2979
+ justNow: "\u521A\u521A",
2980
+ minAgo: "{count} \u5206\u949F\u524D",
2981
+ yesterday: "\u6628\u5929",
2982
+ hoursAgo: "{count} \u5C0F\u65F6\u524D",
2983
+ daysAgo: "{count} \u5929\u524D"
2984
+ },
2985
+ modelPicker: {
2986
+ header: " \u25C8 REASONIX \xB7 \u9009\u62E9\u914D\u7F6E ",
2987
+ loading: " \xB7 \u52A0\u8F7D\u76EE\u5F55\u2026",
2988
+ catalogEmpty: " \xB7 \u76EE\u5F55\u4E3A\u7A7A \u2014 \u4F7F\u7528\u5DF2\u77E5\u5907\u9009",
2989
+ modelsAvailable: " \xB7 {count} \u4E2A\u6A21\u578B\u53EF\u7528",
2990
+ presetsHeader: " \u9884\u8BBE \xB7 \u63A8\u8350 \u2014 \u6A21\u578B + \u5F3A\u5EA6 + \u81EA\u52A8\u5347\u7EA7",
2991
+ modelsHeader: " \u6A21\u578B \xB7 \u76F4\u63A5\u9009\u62E9 \u2014 \u81EA\u52A8\u5347\u7EA7\u4FDD\u6301\u4E0D\u53D8",
2992
+ pickerFooter: " \u2191\u2193 \u9009\u62E9 \xB7 \u23CE \u786E\u8BA4 \xB7 [r] \u5237\u65B0 \xB7 Esc \u53D6\u6D88",
2993
+ currentLabel: " \xB7 \u5F53\u524D"
2994
+ },
2995
+ slashSuggestions: {
2996
+ noMatch: "\u6CA1\u6709\u5339\u914D\u6B64\u524D\u7F00\u7684\u659C\u6760\u547D\u4EE4",
2997
+ backspaceHint: " \u2014 \u6309 Backspace \u4FEE\u6539\uFF0C\u6216 /help \u67E5\u770B\u5B8C\u6574\u5217\u8868",
2998
+ commandCount: "{count} \u4E2A\u547D\u4EE4",
2999
+ commandCountPlural: "{count} \u4E2A\u547D\u4EE4",
3000
+ aboveLabel: " \u2191 {count} \u4E2A\u4EE5\u4E0A",
3001
+ belowLabel: " \u2193 {count} \u4E2A\u4EE5\u4E0B",
3002
+ advancedHint: " + {count} \u4E2A\u9AD8\u7EA7\u547D\u4EE4 \xB7 \u8F93\u5165\u5B57\u6BCD\u641C\u7D22",
3003
+ footerHint: " \u2191\u2193 \u5BFC\u822A \xB7 Tab / \u23CE \u9009\u62E9 \xB7 Esc \u53D6\u6D88",
3004
+ groupChat: "\u804A\u5929",
3005
+ groupSetup: "\u8BBE\u7F6E",
3006
+ groupInfo: "\u4FE1\u606F",
3007
+ groupSession: "\u4F1A\u8BDD",
3008
+ groupExtend: "\u6269\u5C55",
3009
+ groupCode: "\u4EE3\u7801",
3010
+ groupJobs: "\u4EFB\u52A1",
3011
+ groupAdvanced: "\u9AD8\u7EA7"
3012
+ },
3013
+ atMentions: {
3014
+ loading: "\u52A0\u8F7D\u4E2D\u2026",
3015
+ entrySingular: "{count} \u6761",
3016
+ entryPlural: "{count} \u6761",
3017
+ searching: "\u641C\u7D22\u4E2D\u2026",
3018
+ scanned: "\u5DF2\u626B\u63CF",
3019
+ match: "\u4E2A\u5339\u914D",
3020
+ matches: "\u4E2A\u5339\u914D",
3021
+ forFilter: '\u5339\u914D "{filter}"',
3022
+ noMatch: '\u6CA1\u6709\u5339\u914D "{filter}" \u7684\u6587\u4EF6',
3023
+ emptyDir: "\u7A7A\u76EE\u5F55",
3024
+ scanning: "\u6B63\u5728\u626B\u63CF\u76EE\u5F55\u6811\u2026",
3025
+ footerBrowse: "\u2191\u2193 \u5BFC\u822A \xB7 Tab \u8FDB\u5165\u6587\u4EF6\u5939 \xB7 \u23CE \u63D2\u5165 \xB7 Esc \u53D6\u6D88",
3026
+ footerBrowseSearch: "\u2191\u2193 \u5BFC\u822A \xB7 Tab / \u23CE \u4EE5 @path \u63D2\u5165 \xB7 Esc \u53D6\u6D88",
3027
+ footerInsert: "\u2191\u2193 \u5BFC\u822A \xB7 Tab / \u23CE \u4EE5 @path \u63D2\u5165 \xB7 Esc \u53D6\u6D88"
3028
+ },
3029
+ statsPanel: {
3030
+ modePlan: "\u8BA1\u5212",
3031
+ modeYolo: "\u81EA\u7531",
3032
+ modeAuto: "\u81EA\u52A8",
3033
+ modeReview: "\u5BA1\u67E5",
3034
+ pro: "\u21E7 \u4E13\u4E1A",
3035
+ budget: " \u9884\u7B97 "
3036
+ },
3037
+ welcomeBanner: {
3038
+ workspace: "\u25B8 \u5DE5\u4F5C\u533A",
3039
+ relaunchHint: "\uFF08\u91CD\u542F\u65F6\u7528 --dir <path> \u5207\u6362\uFF09",
3040
+ dashboard: "\u25B8 \u7F51\u9875"
3041
+ },
3042
+ ctxBreakdown: {
3043
+ title: "\u25A3 \u4E0A\u4E0B\u6587",
3044
+ compactHint: " /compact \u6298\u53E0\uFF08\u8D85\u8FC7 50% \u81EA\u52A8\u89E6\u53D1\uFF09\xB7 /new \u6E05\u7A7A\u65E5\u5FD7",
3045
+ topTools: " \u5E38\u7528\u5DE5\u5177\uFF08\u6309\u6210\u672C\u6392\u5E8F\uFF0C{count} \u4E2A\uFF09\uFF1A",
3046
+ msg: "\u6761",
3047
+ turnLabel: "\u8F6E"
3048
+ },
3049
+ startup: {
3050
+ codeRooted: '\u25B8 reasonix code\uFF1A\u6839\u76EE\u5F55 {rootDir}\uFF0C\u4F1A\u8BDD "{session}" \xB7 {tools} \u4E2A\u539F\u751F\u5DE5\u5177{semantic}',
3051
+ ephemeral: "\uFF08\u4E34\u65F6\uFF09",
3052
+ semanticOn: " \xB7 \u8BED\u4E49\u641C\u7D22\u5DF2\u5F00\u542F"
3053
+ },
3054
+ doctorErrors: {
3055
+ unreadable: "{path} \u65E0\u6CD5\u8BFB\u53D6 \u2014 {message}",
3056
+ cannotList: "\u65E0\u6CD5\u5217\u51FA \u2014 {message}",
3057
+ parseFailed: "\u65E0\u6CD5\u89E3\u6790 settings.json \u2014 {message}",
3058
+ probeFailed: "\u63A2\u6D4B\u5931\u8D25 \u2014 {message}"
3059
+ },
3060
+ webErrors: {
3061
+ status: "web_search \u72B6\u6001\u7801 {status}",
3062
+ mojeekBlocked: "web_search: Mojeek \u53CD\u722C\u9875\u9762 \u2014 \u9891\u7387\u9650\u5236\u6216\u88AB\u5C4F\u853D",
3063
+ mojeekNoResults: "web_search: \u8FD4\u56DE 0 \u6761\u7ED3\u679C\u4F46\u54CD\u5E94\u770B\u8D77\u6765\u4E0D\u662F\u6B63\u5E38\u7A7A\u7ED3\u679C\u9875\uFF08{chars} \u5B57\u7B26\uFF0C\u524D 120 \u5B57\u7B26\uFF1A{preview}\uFF09",
3064
+ invalidEndpoint: 'web_search: \u65E0\u6548\u7684 SearXNG \u7AEF\u70B9 "{endpoint}"',
3065
+ endpointMustBeHttp: "web_search: SearXNG \u7AEF\u70B9\u5FC5\u987B\u662F http(s) \u534F\u8BAE\uFF0C\u5F53\u524D\u4E3A {protocol}",
3066
+ cannotReach: "web_search: \u65E0\u6CD5\u8BBF\u95EE SearXNG \u670D\u52A1\u5668 {endpoint}\u3002\u8BF7\u5B89\u88C5 SearXNG\uFF08https://github.com/searxng/searxng\uFF09\u5E76\u542F\u52A8\uFF08\u4F8B\u5982 `docker run -d -p 8080:8080 searxng/searxng`\uFF09\uFF0C\u6216\u4F7F\u7528 /search-engine mojeek \u5207\u6362\u5230\u9ED8\u8BA4\u5F15\u64CE\u3002",
3067
+ searxngNoResults: "web_search: \u8FD4\u56DE 0 \u6761\u7ED3\u679C\u4F46 SearXNG \u54CD\u5E94\u770B\u8D77\u6765\u4E0D\u662F\u6B63\u5E38\u7A7A\u7ED3\u679C\u9875\uFF08{chars} \u5B57\u7B26\uFF09",
3068
+ fetchStatus: "web_fetch \u72B6\u6001\u7801 {status}\uFF08{url}\uFF09",
3069
+ fetchTooLarge: "web_fetch \u62D2\u7EDD\uFF1Acontent-length {len} \u5B57\u8282\u8D85\u8FC7\u4E0A\u9650 {cap} \u5B57\u8282\uFF08{url}\uFF09",
3070
+ fetchBodyTooLarge: "web_fetch \u62D2\u7EDD\uFF1A\u54CD\u5E94\u4F53\u8D85\u8FC7 {cap} \u5B57\u8282\u4E0A\u9650\uFF08\u5DF2\u63A5\u6536 {seen} \u5B57\u8282\uFF09",
3071
+ fetchInvalidUrl: "web_fetch: URL \u5FC5\u987B\u4EE5 http:// \u6216 https:// \u5F00\u5934"
3072
+ },
3073
+ choiceConfirm: {
3074
+ customLabel: "\u81EA\u5B9A\u4E49\u56DE\u7B54",
3075
+ customDesc: "\u4EE5\u4E0A\u9009\u9879\u90FD\u4E0D\u5408\u9002 \u2014 \u8F93\u5165\u81EA\u7531\u683C\u5F0F\u56DE\u590D\uFF0C\u6A21\u578B\u4F1A\u539F\u6837\u8BFB\u53D6",
3076
+ cancelLabel: "\u53D6\u6D88 \u2014 \u653E\u5F03\u95EE\u9898",
3077
+ cancelDesc: "\u6A21\u578B\u505C\u6B62\u5E76\u8BE2\u95EE\u4F60\u771F\u6B63\u7684\u9700\u6C42"
3078
+ },
2634
3079
  cardTitles: {
2635
3080
  usage: "\u7528\u91CF",
2636
3081
  context: "\u4E0A\u4E0B\u6587",
@@ -2640,7 +3085,9 @@ var zhCN = {
2640
3085
  reasoning: "\u63A8\u7406\u4E2D",
2641
3086
  reasoningAborted: "\u63A8\u7406\uFF08\u5DF2\u4E2D\u6B62\uFF09",
2642
3087
  reasoningEllipsis: "\u63A8\u7406\u4E2D\u2026",
2643
- error: "\u9519\u8BEF"
3088
+ error: "\u9519\u8BEF",
3089
+ doctor: "\u73AF\u5883\u8BCA\u65AD",
3090
+ you: "\u4F60"
2644
3091
  },
2645
3092
  cardLabels: {
2646
3093
  prompt: "\u63D0\u793A",
@@ -2654,7 +3101,7 @@ var zhCN = {
2654
3101
  tools: "\u5DE5\u5177",
2655
3102
  log: "\u65E5\u5FD7",
2656
3103
  input: "\u8F93\u5165",
2657
- topTools: "Top \u5DE5\u5177",
3104
+ topTools: "\u5E38\u7528\u5DE5\u5177",
2658
3105
  logMsgs: "\u65E5\u5FD7\u6D88\u606F",
2659
3106
  hitSingular: "{count} \u6761\u7ED3\u679C \xB7 {files} \u4E2A\u6587\u4EF6",
2660
3107
  hitsPlural: "{count} \u6761\u7ED3\u679C \xB7 {files} \u4E2A\u6587\u4EF6",
@@ -2679,7 +3126,42 @@ var zhCN = {
2679
3126
  retries: "\u6B21\u91CD\u8BD5",
2680
3127
  reasoningLabel: "\u63A8\u7406 \xB7 {count} \xB6",
2681
3128
  runningLabel: "\u8FD0\u884C\u4E2D",
2682
- workingLabel: "\u5DE5\u4F5C\u4E2D"
3129
+ workingLabel: "\u5904\u7406\u4E2D",
3130
+ defaultFooter: "\u2191\u2193 \u9009\u62E9 \xB7 \u23CE \u786E\u8BA4 \xB7 Esc \u53D6\u6D88",
3131
+ applyAction: "[a] \u5E94\u7528",
3132
+ skipAction: "[s] \u8DF3\u8FC7",
3133
+ rejectAction: "[r] \u62D2\u7EDD",
3134
+ levelOk: "\u6B63\u5E38",
3135
+ levelWarn: "\u8B66\u544A",
3136
+ levelFail: "\u5931\u8D25",
3137
+ checksLabel: "\u68C0\u67E5\u9879",
3138
+ passed: "\u901A\u8FC7",
3139
+ warnTag: "\u8B66\u544A",
3140
+ failTag: "\u5931\u8D25",
3141
+ stepLabel: "\u6B65\u9AA4",
3142
+ done: "\u5DF2\u5B8C\u6210",
3143
+ inProgress: "\u2190 \u8FDB\u884C\u4E2D",
3144
+ upcoming: "\u5F85\u5904\u7406",
3145
+ resumed: "\u5DF2\u6062\u590D \xB7 ",
3146
+ archive: "\u23EA \u5F52\u6863 \xB7 ",
3147
+ more: "\u22EE +{count} \u66F4\u591A",
3148
+ categoryUser: "\u7528\u6237",
3149
+ categoryFeedback: "\u53CD\u9988",
3150
+ categoryProject: "\u9879\u76EE",
3151
+ categoryReference: "\u53C2\u8003"
3152
+ },
3153
+ copyMode: {
3154
+ title: "\u2500\u2500 \u590D\u5236\u6A21\u5F0F \u2500\u2500",
3155
+ help: "j/k \u6216 \u2191/\u2193 \u79FB\u52A8 \xB7 v \u8D77\u9009\u533A \xB7 y \u590D\u5236 \xB7 g/G \u9876/\u5E95 \xB7 q \u9000\u51FA",
3156
+ statusBar: "\u7B2C {cur}/{total} \u884C \xB7 \u9009\u533A\uFF1A{sel}",
3157
+ statusYanked: "\u5DF2\u590D\u5236 {size} \u5B57\u7B26\uFF08osc52={osc52}\uFF09",
3158
+ statusEmpty: "\u672A\u9009\u4E2D\u5185\u5BB9",
3159
+ empty: "\uFF08\u8FD8\u6CA1\u6709\u804A\u5929\u5185\u5BB9 \u2014 \u5148\u548C\u6A21\u578B\u8BF4\u70B9\u4EC0\u4E48\uFF09",
3160
+ labelUser: "\u4F60",
3161
+ labelAssistant: "\u52A9\u624B",
3162
+ labelReasoning: "\u63A8\u7406",
3163
+ yankedToast: "\u25B8 \u5DF2\u590D\u5236 {size} \u5B57\u7B26\u5230\u526A\u8D34\u677F (osc52)",
3164
+ yankedToastFile: "\u25B8 \u5DF2\u590D\u5236 {size} \u5B57\u7B26 \xB7 \u6587\u4EF6\uFF1A{path}"
2683
3165
  }
2684
3166
  };
2685
3167
 
@@ -3199,6 +3681,10 @@ var ToolRegistry = class {
3199
3681
  setResultAugmenter(fn) {
3200
3682
  this._resultAugmenter = fn;
3201
3683
  }
3684
+ /** True when an augmenter is already wired — lets late-installing callers skip clobbering an earlier one. */
3685
+ get hasResultAugmenter() {
3686
+ return this._resultAugmenter !== null;
3687
+ }
3202
3688
  register(def) {
3203
3689
  if (!def.name) throw new Error("tool requires a name");
3204
3690
  const internal = { ...def };
@@ -3520,6 +4006,9 @@ function sanitizeName(name) {
3520
4006
  const cleaned = name.replace(/[^\w\-\u4e00-\u9fa5]/g, "_").slice(0, 64);
3521
4007
  return cleaned || "default";
3522
4008
  }
4009
+ function timestampSuffix() {
4010
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/[^\d]/g, "").slice(0, 12);
4011
+ }
3523
4012
  function loadSessionMessages(name) {
3524
4013
  const path2 = sessionPath(name);
3525
4014
  if (!existsSync3(path2)) return [];
@@ -3588,6 +4077,26 @@ function loadSessionMeta(name) {
3588
4077
  return {};
3589
4078
  }
3590
4079
  }
4080
+ function renameSession(oldName, newName) {
4081
+ const safeOld = sanitizeName(oldName);
4082
+ const safeNew = sanitizeName(newName);
4083
+ if (safeOld === safeNew) return false;
4084
+ const oldJsonl = sessionPath(oldName);
4085
+ const newJsonl = sessionPath(newName);
4086
+ if (!existsSync3(oldJsonl) || existsSync3(newJsonl)) return false;
4087
+ renameSync(oldJsonl, newJsonl);
4088
+ for (const ext of [".events.jsonl", ".meta.json", ".pending.json", ".plan.json"]) {
4089
+ const oldP = oldJsonl.replace(/\.jsonl$/, ext);
4090
+ const newP = newJsonl.replace(/\.jsonl$/, ext);
4091
+ if (existsSync3(oldP)) {
4092
+ try {
4093
+ renameSync(oldP, newP);
4094
+ } catch {
4095
+ }
4096
+ }
4097
+ }
4098
+ return true;
4099
+ }
3591
4100
  function deleteSession(name) {
3592
4101
  const path2 = sessionPath(name);
3593
4102
  try {
@@ -3615,6 +4124,20 @@ function rewriteSession(name, messages) {
3615
4124
  } catch {
3616
4125
  }
3617
4126
  }
4127
+ function archiveSession(name) {
4128
+ const path2 = sessionPath(name);
4129
+ if (!existsSync3(path2)) return null;
4130
+ try {
4131
+ if (statSync(path2).size === 0) return null;
4132
+ } catch {
4133
+ return null;
4134
+ }
4135
+ for (let attempt = 0; attempt < 5; attempt++) {
4136
+ const target = `${name}__archive_${timestampSuffix()}${attempt > 0 ? `_${attempt}` : ""}`;
4137
+ if (renameSession(name, target)) return target;
4138
+ }
4139
+ return null;
4140
+ }
3618
4141
  function countLines(path2) {
3619
4142
  try {
3620
4143
  const raw = readFileSync4(path2, "utf8");
@@ -4684,6 +5207,7 @@ function signature(call) {
4684
5207
 
4685
5208
  // src/loop.ts
4686
5209
  var ESCALATION_MODEL = "deepseek-v4-pro";
5210
+ var PARENT_BUDGET_WARN_THRESHOLD = 5;
4687
5211
  var CacheFirstLoop = class {
4688
5212
  client;
4689
5213
  prefix;
@@ -4720,6 +5244,7 @@ var CacheFirstLoop = class {
4720
5244
  _turnFailures = new TurnFailureTracker();
4721
5245
  _turnSelfCorrected = false;
4722
5246
  _foldedThisTurn = false;
5247
+ _toolDispatchesThisStep = 0;
4723
5248
  context;
4724
5249
  /** Subscribe API so UI hooks can derive `running` from finally-guaranteed insertions. */
4725
5250
  get inflight() {
@@ -4774,6 +5299,23 @@ var CacheFirstLoop = class {
4774
5299
  stormThreshold: parsePositiveIntEnv(process.env.REASONIX_STORM_THRESHOLD),
4775
5300
  stormWindow: parsePositiveIntEnv(process.env.REASONIX_STORM_WINDOW)
4776
5301
  });
5302
+ if (!this.tools.hasResultAugmenter) {
5303
+ this.tools.setResultAugmenter((_name, _args, result) => {
5304
+ this._toolDispatchesThisStep++;
5305
+ const remaining = this.maxToolIters - this._toolDispatchesThisStep;
5306
+ if (remaining <= 0) {
5307
+ return `${result}
5308
+
5309
+ [budget: 0 of ${this.maxToolIters} tool calls left this turn \u2014 finalize NOW; the next iter forces a summary]`;
5310
+ }
5311
+ if (remaining <= PARENT_BUDGET_WARN_THRESHOLD) {
5312
+ return `${result}
5313
+
5314
+ [budget: ${remaining} of ${this.maxToolIters} tool calls left this turn \u2014 wrap up soon]`;
5315
+ }
5316
+ return result;
5317
+ });
5318
+ }
4777
5319
  this.sessionName = opts.session ?? null;
4778
5320
  if (this.sessionName) {
4779
5321
  const prior = loadSessionMessages(this.sessionName);
@@ -4844,19 +5386,21 @@ var CacheFirstLoop = class {
4844
5386
  }
4845
5387
  }
4846
5388
  }
4847
- /** "New chat" — drops messages but keeps session + immutable prefix (cache-first invariant). */
5389
+ /** "New chat" — drops in-memory messages, archives the on-disk transcript so it survives in Sessions, keeps sessionName so the prefix cache stays warm. */
4848
5390
  clearLog() {
4849
5391
  const dropped = this.log.length;
4850
5392
  this.log.compactInPlace([]);
5393
+ let archived = null;
4851
5394
  if (this.sessionName) {
4852
5395
  try {
4853
- rewriteSession(this.sessionName, []);
5396
+ archived = archiveSession(this.sessionName);
5397
+ if (archived === null) rewriteSession(this.sessionName, []);
4854
5398
  } catch {
4855
5399
  }
4856
5400
  }
4857
5401
  this.scratch.reset();
4858
5402
  this._inflight.clear();
4859
- return { dropped };
5403
+ return { dropped, archived };
4860
5404
  }
4861
5405
  configure(opts) {
4862
5406
  if (opts.model !== void 0) this.model = opts.model;
@@ -5025,6 +5569,7 @@ ${reason}`
5025
5569
  this._turnSelfCorrected = false;
5026
5570
  this._escalateThisTurn = false;
5027
5571
  this._foldedThisTurn = false;
5572
+ this._toolDispatchesThisStep = 0;
5028
5573
  let armedConsumed = false;
5029
5574
  if (this._proArmedForNextTurn) {
5030
5575
  this._escalateThisTurn = true;
@@ -6042,15 +6587,21 @@ var TUI_FORMATTING_RULES = `Formatting (rendered in a TUI with a real markdown r
6042
6587
  - Code, file paths with line ranges, and shell commands \u2192 fenced code blocks (\`\`\`).
6043
6588
  - Do NOT draw decorative frames around content with \`\u250C\u2500\u2500\u2510 \u2502 \u2514\u2500\u2500\u2518\` characters. The renderer adds its own borders; extra ASCII art adds noise and shatters at narrow widths.
6044
6589
  - For flow charts and diagrams: a plain bullet list with \`\u2192\` or \`\u2193\` between steps. Don't try to draw boxes-and-arrows in ASCII; it never survives word-wrap.`;
6045
- var ESCALATION_CONTRACT = `Cost-aware escalation (when you're running on deepseek-v4-flash):
6590
+ function escalationContract(modelId) {
6591
+ if (modelId === "deepseek-v4-pro") {
6592
+ return `Cost-aware escalation note: you are running on \`${modelId}\` \u2014 the escalation tier. There is no higher tier to escalate to, so the \`<<<NEEDS_PRO>>>\` marker is a no-op for you; deliver the strongest answer you can directly. If asked which model you are, answer \`${modelId}\`.`;
6593
+ }
6594
+ return `Cost-aware escalation (you are running on \`${modelId}\`):
6046
6595
 
6047
- If a task CLEARLY exceeds what flash can do well \u2014 complex cross-file architecture refactors, subtle concurrency / security / correctness invariants you can't resolve with confidence, or a design trade-off you'd be guessing at \u2014 output the marker as the FIRST line of your response (nothing before it, not even whitespace on a separate line). This aborts the current call and retries this turn on deepseek-v4-pro, one shot.
6596
+ If a task CLEARLY exceeds what this tier can do well \u2014 complex cross-file architecture refactors, subtle concurrency / security / correctness invariants you can't resolve with confidence, or a design trade-off you'd be guessing at \u2014 output the marker as the FIRST line of your response (nothing before it, not even whitespace on a separate line). This aborts the current call and retries this turn on deepseek-v4-pro, one shot.
6048
6597
 
6049
6598
  Two accepted forms:
6050
6599
  - \`<<<NEEDS_PRO>>>\` \u2014 bare marker, no rationale.
6051
6600
  - \`<<<NEEDS_PRO: <one-sentence reason>>>>\` \u2014 preferred. The reason text appears in the user-visible warning ("\u21E7 flash requested escalation \u2014 <your reason>"), so they understand WHY a more expensive call is happening. Keep it under ~150 chars, no newlines, no nested \`>\` characters. Examples: \`<<<NEEDS_PRO: cross-file refactor across 6 modules with circular imports>>>\` or \`<<<NEEDS_PRO: subtle session-token race; flash would likely miss the locking invariant>>>\`.
6052
6601
 
6053
- Do NOT emit any other content in the same response when you request escalation. Use this sparingly: normal tasks \u2014 reading files, small edits, clear bug fixes, straightforward feature additions \u2014 stay on flash. Request escalation ONLY when you would otherwise produce a guess or a visibly-mediocre answer. If in doubt, attempt the task on flash first; the system also escalates automatically if you hit 3+ repair / SEARCH-mismatch errors in a single turn (the user sees a typed breakdown).`;
6602
+ Do NOT emit any other content in the same response when you request escalation. Use this sparingly: normal tasks \u2014 reading files, small edits, clear bug fixes, straightforward feature additions \u2014 stay on this tier. Request escalation ONLY when you would otherwise produce a guess or a visibly-mediocre answer. If in doubt, attempt the task here first; the system also escalates automatically if you hit 3+ repair / SEARCH-mismatch errors in a single turn (the user sees a typed breakdown). If asked which model you are, answer \`${modelId}\`.`;
6603
+ }
6604
+ var ESCALATION_CONTRACT = escalationContract("deepseek-v4-flash");
6054
6605
  var NEGATIVE_CLAIM_RULE = `Negative claims ("X is missing", "Y isn't implemented", "there's no Z") are the #1 hallucination shape. They feel safe to write because no citation seems possible \u2014 but that's exactly why you must NOT write them on instinct.
6055
6606
 
6056
6607
  If you have a search tool (\`search_content\`, \`grep\`, web search), call it FIRST before asserting absence:
@@ -6249,11 +6800,14 @@ function skillIndexLine(s) {
6249
6800
  const clipped = safeDesc.length > max ? `${safeDesc.slice(0, Math.max(1, max - 1))}\u2026` : safeDesc;
6250
6801
  return clipped ? `- ${s.name}${tag} \u2014 ${clipped}` : `- ${s.name}${tag}`;
6251
6802
  }
6803
+ var MISSING_DESCRIPTION_PLACEHOLDER = '(no description \u2014 frontmatter is missing a "description:" line; tell the user to add one)';
6252
6804
  function applySkillsIndex(basePrompt, opts = {}) {
6253
6805
  const store = new SkillStore(opts);
6254
- const skills = store.list().filter((s) => s.description);
6806
+ const skills = store.list();
6255
6807
  if (skills.length === 0) return basePrompt;
6256
- const lines = skills.map(skillIndexLine);
6808
+ const lines = skills.map(
6809
+ (s) => skillIndexLine(s.description ? s : { ...s, description: MISSING_DESCRIPTION_PLACEHOLDER })
6810
+ );
6257
6811
  const joined = lines.join("\n");
6258
6812
  const truncated = joined.length > SKILLS_INDEX_MAX_CHARS ? `${joined.slice(0, SKILLS_INDEX_MAX_CHARS)}
6259
6813
  \u2026 (truncated ${joined.length - SKILLS_INDEX_MAX_CHARS} chars)` : joined;
@@ -7697,6 +8251,74 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
7697
8251
  return `moved ${displayRel4(rootDir, src)} \u2192 ${displayRel4(rootDir, dst)}`;
7698
8252
  }
7699
8253
  });
8254
+ registry.register({
8255
+ name: "delete_file",
8256
+ description: "Delete one file under the sandbox root. Refuses directories \u2014 use delete_directory for those. Errors if the path doesn't exist.",
8257
+ parameters: {
8258
+ type: "object",
8259
+ properties: { path: { type: "string" } },
8260
+ required: ["path"]
8261
+ },
8262
+ fn: async (args) => {
8263
+ const abs = safePath(args.path);
8264
+ const st = await fs4.lstat(abs);
8265
+ if (st.isDirectory()) {
8266
+ throw new Error(
8267
+ `delete_file: ${args.path} is a directory \u2014 use delete_directory to remove it`
8268
+ );
8269
+ }
8270
+ await fs4.unlink(abs);
8271
+ return `deleted ${displayRel4(rootDir, abs)}`;
8272
+ }
8273
+ });
8274
+ registry.register({
8275
+ name: "delete_directory",
8276
+ description: "Recursively delete a directory under the sandbox root. Pass `recursive:false` to refuse non-empty directories. Errors if the path doesn't exist.",
8277
+ parameters: {
8278
+ type: "object",
8279
+ properties: {
8280
+ path: { type: "string" },
8281
+ recursive: {
8282
+ type: "boolean",
8283
+ description: "When true (default) deletes the directory and all its contents. When false, only removes empty directories \u2014 non-empty refuses with an error."
8284
+ }
8285
+ },
8286
+ required: ["path"]
8287
+ },
8288
+ fn: async (args) => {
8289
+ const abs = safePath(args.path);
8290
+ const st = await fs4.lstat(abs);
8291
+ if (!st.isDirectory()) {
8292
+ throw new Error(`delete_directory: ${args.path} is a file \u2014 use delete_file to remove it`);
8293
+ }
8294
+ const recursive = args.recursive !== false;
8295
+ if (recursive) {
8296
+ await fs4.rm(abs, { recursive: true, force: false });
8297
+ } else {
8298
+ await fs4.rmdir(abs);
8299
+ }
8300
+ return `deleted ${displayRel4(rootDir, abs)}/${recursive ? " (recursive)" : ""}`;
8301
+ }
8302
+ });
8303
+ registry.register({
8304
+ name: "copy_file",
8305
+ description: "Copy a file or directory under the sandbox root. Both source and destination resolve under the sandbox. Parent directories of the destination are created as needed. Refuses to overwrite an existing destination \u2014 delete it first if you want to replace it.",
8306
+ parameters: {
8307
+ type: "object",
8308
+ properties: {
8309
+ source: { type: "string" },
8310
+ destination: { type: "string" }
8311
+ },
8312
+ required: ["source", "destination"]
8313
+ },
8314
+ fn: async (args) => {
8315
+ const src = safePath(args.source);
8316
+ const dst = safePath(args.destination);
8317
+ await fs4.mkdir(pathMod4.dirname(dst), { recursive: true });
8318
+ await fs4.cp(src, dst, { recursive: true, force: false, errorOnExist: true });
8319
+ return `copied ${displayRel4(rootDir, src)} \u2192 ${displayRel4(rootDir, dst)}`;
8320
+ }
8321
+ });
7700
8322
  return registry;
7701
8323
  }
7702
8324
 
@@ -8330,7 +8952,7 @@ function nextRunId() {
8330
8952
  runIdCounter++;
8331
8953
  return `sub-${runIdCounter.toString(36)}`;
8332
8954
  }
8333
- var DEFAULT_SUBAGENT_SYSTEM = `You are a Reasonix subagent. The parent agent spawned you to handle one focused subtask, then return.
8955
+ var SUBAGENT_BASE_SYSTEM = `You are a Reasonix subagent. The parent agent spawned you to handle one focused subtask, then return.
8334
8956
 
8335
8957
  Rules:
8336
8958
  - Stay on the task you were given. Do not expand scope.
@@ -8340,8 +8962,6 @@ Rules:
8340
8962
 
8341
8963
  ${NEGATIVE_CLAIM_RULE}
8342
8964
 
8343
- ${ESCALATION_CONTRACT}
8344
-
8345
8965
  ${TUI_FORMATTING_RULES}`;
8346
8966
  var DEFAULT_MAX_RESULT_CHARS2 = 8e3;
8347
8967
  var DEFAULT_MAX_ITERS = 16;
@@ -8355,6 +8975,18 @@ var DEFAULT_SUBAGENT_MODEL = "deepseek-v4-flash";
8355
8975
  var DEFAULT_SUBAGENT_EFFORT = "high";
8356
8976
  var SUBAGENT_TOOL_NAME = "spawn_subagent";
8357
8977
  var NEVER_INHERITED_TOOLS = /* @__PURE__ */ new Set([SUBAGENT_TOOL_NAME, "submit_plan"]);
8978
+ var SOFT_HINT_AFTER_SPAWNS = 1;
8979
+ var STRONG_HINT_AFTER_SPAWNS = 4;
8980
+ var STRONG_HINT_TOKEN_THRESHOLD = 5e4;
8981
+ function subagentBudgetHint(spawnCount, totalTokens) {
8982
+ if (spawnCount > STRONG_HINT_AFTER_SPAWNS || totalTokens >= STRONG_HINT_TOKEN_THRESHOLD) {
8983
+ return `[budget: this session has now spawned ${spawnCount} subagents totalling ${totalTokens} tokens. Each spawn pays a fresh prefix-cache miss plus a full child loop \u2014 confirm the next spawn is genuinely needed (parallel fan-out or >10-read context blow-up) before calling spawn_subagent again. If you can answer with direct tools, do that instead.]`;
8984
+ }
8985
+ if (spawnCount > SOFT_HINT_AFTER_SPAWNS) {
8986
+ return `[note: this session has spawned ${spawnCount} subagents totalling ${totalTokens} tokens; confirm this one is worth it.]`;
8987
+ }
8988
+ return null;
8989
+ }
8358
8990
  async function spawnSubagent(opts) {
8359
8991
  const model = opts.model ?? DEFAULT_SUBAGENT_MODEL;
8360
8992
  const maxToolIters = opts.maxToolIters ?? DEFAULT_MAX_ITERS;
@@ -8570,16 +9202,18 @@ function formatSubagentResult(r) {
8570
9202
  });
8571
9203
  }
8572
9204
  function registerSubagentTool(parentRegistry, opts) {
8573
- const baseSystem = opts.defaultSystem ?? DEFAULT_SUBAGENT_SYSTEM;
8574
- const defaultSystem = opts.projectRoot ? applyProjectMemory(baseSystem, opts.projectRoot) : baseSystem;
9205
+ const baseSystem = opts.defaultSystem ?? SUBAGENT_BASE_SYSTEM;
9206
+ const defaultSystemBase = opts.projectRoot ? applyProjectMemory(baseSystem, opts.projectRoot) : baseSystem;
8575
9207
  const defaultModel = opts.defaultModel ?? DEFAULT_SUBAGENT_MODEL;
8576
9208
  const maxToolIters = opts.maxToolIters ?? DEFAULT_MAX_ITERS;
8577
9209
  const maxResultChars = opts.maxResultChars ?? DEFAULT_MAX_RESULT_CHARS2;
8578
9210
  const sink = opts.sink;
9211
+ let sessionSpawnCount = 0;
9212
+ let sessionSpawnTokens = 0;
8579
9213
  parentRegistry.register({
8580
9214
  name: SUBAGENT_TOOL_NAME,
8581
9215
  parallelSafe: true,
8582
- description: "Spawn an isolated subagent to handle a self-contained subtask in a fresh context, returning only its final answer. Use for: deep codebase exploration that would flood the main context, multi-step research where you only need the conclusion, or any focused subtask whose intermediate reasoning the user does not need to see. The subagent inherits all your tools (filesystem, shell, web, MCP, etc.) but runs in its own isolated message log \u2014 its tool calls and reasoning never enter your context. Only the final assistant message comes back as this tool's result. Keep tasks focused; the subagent has a stricter iter budget than you do.",
9216
+ description: "Spawn an isolated subagent to handle a self-contained subtask in a fresh context, returning only its final answer. **Prefer direct tools.** Spawn primarily for parallel fan-out (2+ independent investigations issued in one tool batch) or when the work would otherwise need >10 file reads/searches whose trail you don't need to keep. Single greps, 1-3 file cross-references, and 'keep my context clean for one question' are NOT good reasons to spawn \u2014 direct tools are cheaper and let you reference the evidence later. Each spawn pays a fresh prefix-cache miss plus a full child loop. The subagent inherits your tools but runs in its own isolated message log; only the final assistant message comes back. Keep tasks focused; the subagent has a stricter iter budget than you do.",
8583
9217
  parameters: {
8584
9218
  type: "object",
8585
9219
  properties: {
@@ -8618,8 +9252,10 @@ function registerSubagentTool(parentRegistry, opts) {
8618
9252
  });
8619
9253
  }
8620
9254
  const typeSpec = getSubagentType(args.type);
8621
- const system = typeof args.system === "string" && args.system.trim().length > 0 ? args.system.trim() : typeSpec?.system ?? defaultSystem;
8622
9255
  const model = typeof args.model === "string" && args.model.startsWith("deepseek-") ? args.model : defaultModel;
9256
+ const system = typeof args.system === "string" && args.system.trim().length > 0 ? args.system.trim() : typeSpec?.system ?? `${defaultSystemBase}
9257
+
9258
+ ${escalationContract(model)}`;
8623
9259
  const callerIters = clampMaxIters(args.max_iters);
8624
9260
  const result = await spawnSubagent({
8625
9261
  client: opts.client,
@@ -8632,7 +9268,12 @@ function registerSubagentTool(parentRegistry, opts) {
8632
9268
  sink,
8633
9269
  parentSignal: ctx?.signal
8634
9270
  });
8635
- return formatSubagentResult(result);
9271
+ sessionSpawnCount++;
9272
+ sessionSpawnTokens += result.usage.totalTokens;
9273
+ const formatted = formatSubagentResult(result);
9274
+ const hint = subagentBudgetHint(sessionSpawnCount, sessionSpawnTokens);
9275
+ return hint ? `${formatted}
9276
+ ${hint}` : formatted;
8636
9277
  }
8637
9278
  });
8638
9279
  return parentRegistry;
@@ -9948,7 +10589,7 @@ function registerShellTools(registry, opts) {
9948
10589
  const isAllowAll = typeof opts.allowAll === "function" ? opts.allowAll : () => opts.allowAll === true;
9949
10590
  registry.register({
9950
10591
  name: "run_command",
9951
- description: "Run a shell command in the project root and return its combined stdout+stderr.\n\nConstraints (read these before the first call):\n\u2022 Chain operators `|`, `||`, `&&`, `;` ARE supported \u2014 parsed natively, no shell invoked, so semantics are identical on Windows / macOS / Linux. Each chain segment is allowlist-checked individually: `git status | grep main` runs if both halves are allowed.\n\u2022 File redirects ARE supported: `>` truncate, `>>` append, `<` stdin from file, `2>` / `2>>` stderr to file, `2>&1` merge stderr\u2192stdout, `&>` both to file. Targets resolve relative to the project root. At most one redirect per fd per segment.\n\u2022 Background `&`, heredoc `<<`, command substitution `$(\u2026)`, subshells `(\u2026)`, and process substitution `<(\u2026)` are NOT supported. Wrap a literal `&` arg in quotes; for input use a `<` file or the binary's own --input flag.\n\u2022 Env-var expansion `$VAR` is NOT performed \u2014 `$VAR` is passed as a literal string. Use the binary's own --env flag or substitute the value yourself.\n\u2022 `cd` DOES NOT PERSIST between calls \u2014 each call spawns a fresh process rooted at the project. `cd` also does not persist within parsed chains like `cd dir && command`. Use a command-native cwd flag instead: `npm --prefix <dir> run <script>`, `npm --prefix <dir> exec -- <bin>`, `git -C <dir> ...`, `cargo -C <dir> ...`, `pytest <dir>/tests`.\n\u2022 Glob patterns (`*.ts`) are passed through as literal arguments \u2014 no shell expansion. Use `grep -r`, `rg`, `find -name`, etc.\n\u2022 Avoid commands with unbounded output (`netstat -ano`, `find /`, etc.) \u2014 they waste tokens. Filter at source: `netstat -ano -p TCP`, `find src -name '*.ts'`, `grep -c`, `wc -l`.\n\nCommon read-only inspection and test/lint/typecheck commands run immediately; anything that could mutate state, install dependencies, or touch the network is refused until the user confirms it in the TUI. Prefer this over asking the user to run a command manually \u2014 after edits, run the project's tests to verify.",
10592
+ description: "Run a shell command in the project root; returns combined stdout+stderr. Allowlisted read-only / test / lint / typecheck commands run immediately; anything that could mutate state, install deps, or touch the network is gated by user confirmation. Prefer this over asking the user to run a command manually \u2014 after edits, run the project's tests to verify.\n\nConstraints (no real shell \u2014 argv is parsed natively for cross-platform parity):\n\u2022 Supported: chain ops `|` / `||` / `&&` / `;` (each segment allowlist-checked individually), file redirects `>` / `>>` / `<` / `2>` / `2>>` / `2>&1` / `&>` (target paths resolve relative to project root, max one redirect per fd per segment).\n\u2022 NOT supported: background `&`, heredoc `<<`, command substitution `$(\u2026)`, subshells `(\u2026)`, process substitution `<(\u2026)`, `$VAR` env expansion, glob expansion. To pass an operator char as literal arg, quote it (`grep \"a|b\" file`).\n\u2022 `cd` does NOT persist \u2014 between calls OR within a chain like `cd dir && cmd`. Use the binary's own cwd flag: `npm --prefix <dir>`, `git -C <dir>`, `cargo -C <dir>`, `pytest <dir>/tests`.\n\u2022 Filter at source \u2014 unbounded output (`netstat -ano`, `find /`) wastes tokens. Use `grep -c`, `wc -l`, narrower paths, etc.",
9952
10593
  // Plan-mode gate: allow allowlisted commands through (git status,
9953
10594
  // cargo check, ls, grep …) so the model can actually investigate
9954
10595
  // during planning. Anything that would otherwise trigger a
@@ -10000,7 +10641,7 @@ function registerShellTools(registry, opts) {
10000
10641
  });
10001
10642
  registry.register({
10002
10643
  name: "run_background",
10003
- description: "Spawn a long-running process (dev server, watcher, any command that doesn't naturally exit) and detach. Waits up to `waitSec` seconds for startup (or until the output matches a readiness signal like 'Local:', 'listening on', 'compiled successfully'), then returns the job id + startup preview. The process keeps running; call `job_output` to tail its logs, `stop_job` to kill it, `list_jobs` to see all running jobs.\n\nSame shell constraints as run_command: NO `&&` / `||` / `|` / `;` / `>` / `<` / `2>&1`, `cd` doesn't persist. Dev servers that need a subdirectory: use the tool's own --prefix / --cwd flag. For Vite specifically, `--prefix` on npm only tells npm where package.json is; vite's server root still defaults to process cwd, so pass `vite <project-dir>` or configure via `vite.config.ts` root.\n\nUSE THIS \u2014 not `run_command` \u2014 for: npm/yarn/pnpm run dev, uvicorn / flask run, go run, cargo watch, tsc --watch, webpack serve, anything with 'dev' / 'serve' / 'watch' in the name.",
10644
+ description: "Spawn a long-running process (dev server, watcher) and detach. Waits up to `waitSec` for startup or a readiness signal ('Local:', 'listening on', 'compiled successfully'), then returns the job id + startup preview. Tail logs with `job_output`, kill with `stop_job`, list with `list_jobs`.\n\nSingle process only \u2014 chains / redirects / `cd` work as in run_command, but a typical dev-server invocation is one binary. Use the binary's own --cwd / --prefix flag for subdirectories. Vite gotcha: npm's `--prefix` only finds package.json; vite's server root still uses process cwd \u2014 pass `vite <project-dir>` instead.\n\nUSE THIS \u2014 not run_command \u2014 for: npm/yarn/pnpm dev, uvicorn / flask run, cargo watch, tsc --watch, webpack serve, anything with dev/serve/watch in the name.",
10004
10645
  parameters: {
10005
10646
  type: "object",
10006
10647
  properties: {
@@ -10072,6 +10713,8 @@ function registerShellTools(registry, opts) {
10072
10713
  name: "wait_for_job",
10073
10714
  description: "Block until a background job exits or produces new output, bounded by `timeoutMs`. Use this instead of polling `job_output` with identical args when you're intentionally waiting for state to change. Returns JSON with `exited`, `exitCode`, and `latestOutput`.",
10074
10715
  readOnly: true,
10716
+ parallelSafe: true,
10717
+ stormExempt: true,
10075
10718
  parameters: {
10076
10719
  type: "object",
10077
10720
  properties: {
@@ -10190,16 +10833,19 @@ async function searchMojeek(query, opts = {}) {
10190
10833
  signal: opts.signal,
10191
10834
  redirect: "follow"
10192
10835
  });
10193
- if (!resp.ok) throw new Error(`web_search ${resp.status}`);
10836
+ if (!resp.ok) throw new Error(t("webErrors.status", { status: resp.status }));
10194
10837
  const html = await resp.text();
10195
10838
  const results = parseMojeekResults(html).slice(0, topK);
10196
10839
  if (results.length === 0) {
10197
10840
  if (/no results found|did not match any documents/i.test(html)) return [];
10198
10841
  if (/captcha|verify you are human|access denied|forbidden/i.test(html)) {
10199
- throw new Error("web_search: Mojeek anti-bot page \u2014 rate-limited or blocked");
10842
+ throw new Error(t("webErrors.mojeekBlocked"));
10200
10843
  }
10201
10844
  throw new Error(
10202
- `web_search: 0 results but response doesn't look like a real empty page (${html.length} chars, first 120: ${html.slice(0, 120).replace(/\s+/g, " ")})`
10845
+ t("webErrors.mojeekNoResults", {
10846
+ chars: html.length,
10847
+ preview: html.slice(0, 120).replace(/\s+/g, " ")
10848
+ })
10203
10849
  );
10204
10850
  }
10205
10851
  return results;
@@ -10209,10 +10855,10 @@ function normalizeSearxngEndpoint(raw) {
10209
10855
  try {
10210
10856
  url = new URL(raw.includes("://") ? raw : `http://${raw}`);
10211
10857
  } catch {
10212
- throw new Error(`web_search: invalid SearXNG endpoint "${raw}"`);
10858
+ throw new Error(t("webErrors.invalidEndpoint", { endpoint: raw }));
10213
10859
  }
10214
10860
  if (url.protocol !== "http:" && url.protocol !== "https:") {
10215
- throw new Error(`web_search: SearXNG endpoint must be http(s), got ${url.protocol}`);
10861
+ throw new Error(t("webErrors.endpointMustBeHttp", { protocol: url.protocol }));
10216
10862
  }
10217
10863
  return url.origin;
10218
10864
  }
@@ -10232,19 +10878,17 @@ async function searchSearxng(query, opts = {}) {
10232
10878
  } catch (err) {
10233
10879
  if (err instanceof TypeError && err.message.includes("fetch")) {
10234
10880
  throw new Error(
10235
- `web_search: Cannot reach SearXNG server at ${opts.endpoint ?? "http://localhost:8080"}. Please install SearXNG (https://github.com/searxng/searxng) and start it (e.g. \`docker run -d -p 8080:8080 searxng/searxng\`), or switch to the default engine with /search-engine mojeek.`
10881
+ t("webErrors.cannotReach", { endpoint: opts.endpoint ?? "http://localhost:8080" })
10236
10882
  );
10237
10883
  }
10238
10884
  throw err;
10239
10885
  }
10240
- if (!resp.ok) throw new Error(`web_search ${resp.status}`);
10886
+ if (!resp.ok) throw new Error(t("webErrors.status", { status: resp.status }));
10241
10887
  const html = await resp.text();
10242
10888
  const results = parseSearxngHtmlResults(html).slice(0, topK);
10243
10889
  if (results.length === 0) {
10244
10890
  if (/no results found|did not match any documents/i.test(html)) return [];
10245
- throw new Error(
10246
- `web_search: 0 results but SearXNG response doesn't look like an empty results page (${html.length} chars)`
10247
- );
10891
+ throw new Error(t("webErrors.searxngNoResults", { chars: html.length }));
10248
10892
  }
10249
10893
  return results;
10250
10894
  }
@@ -10338,13 +10982,11 @@ async function webFetch(url, opts = {}) {
10338
10982
  clearTimeout(timer);
10339
10983
  opts.signal?.removeEventListener("abort", cancel);
10340
10984
  }
10341
- if (!resp.ok) throw new Error(`web_fetch ${resp.status} for ${url}`);
10985
+ if (!resp.ok) throw new Error(t("webErrors.fetchStatus", { status: resp.status, url }));
10342
10986
  const contentType = resp.headers.get("content-type") ?? "";
10343
10987
  const declaredLen = Number(resp.headers.get("content-length") ?? "");
10344
10988
  if (Number.isFinite(declaredLen) && declaredLen > FETCH_MAX_BYTES) {
10345
- throw new Error(
10346
- `web_fetch refused: content-length ${declaredLen} bytes exceeds ${FETCH_MAX_BYTES}-byte cap (${url})`
10347
- );
10989
+ throw new Error(t("webErrors.fetchTooLarge", { len: declaredLen, cap: FETCH_MAX_BYTES, url }));
10348
10990
  }
10349
10991
  const raw = await readBodyCapped(resp, FETCH_MAX_BYTES);
10350
10992
  const title = extractTitle(raw);
@@ -10371,9 +11013,7 @@ async function readBodyCapped(resp, maxBytes) {
10371
11013
  await reader.cancel();
10372
11014
  } catch {
10373
11015
  }
10374
- throw new Error(
10375
- `web_fetch refused: response body exceeded ${maxBytes}-byte cap (${total} bytes seen)`
10376
- );
11016
+ throw new Error(t("webErrors.fetchBodyTooLarge", { cap: maxBytes, seen: total }));
10377
11017
  }
10378
11018
  out += decoder.decode(value, { stream: true });
10379
11019
  }
@@ -10501,7 +11141,7 @@ function registerWebTools(registry, opts = {}) {
10501
11141
  },
10502
11142
  fn: async (args, ctx) => {
10503
11143
  if (!/^https?:\/\//i.test(args.url)) {
10504
- throw new Error("web_fetch: url must start with http:// or https://");
11144
+ throw new Error(t("webErrors.fetchInvalidUrl"));
10505
11145
  }
10506
11146
  const page = await webFetch(args.url, { maxChars: maxFetchChars, signal: ctx?.signal });
10507
11147
  const header = page.title ? `${page.title}
@@ -12022,7 +12662,11 @@ function lineEndingOf(text) {
12022
12662
  // src/code/prompt.ts
12023
12663
  import { existsSync as existsSync11, readFileSync as readFileSync14 } from "fs";
12024
12664
  import { join as join13 } from "path";
12025
- var CODE_SYSTEM_PROMPT = `You are Reasonix Code, a coding assistant. You have filesystem tools (read_file, write_file, edit_file, multi_edit, list_directory, directory_tree, search_files, search_content, glob, get_file_info) rooted at the user's working directory, plus run_command / run_background for shell, plus \`todo_write\` for in-session multi-step tracking.
12665
+ var DEFAULT_CODE_MODEL = "deepseek-v4-flash";
12666
+ function codeSystemBase(modelId) {
12667
+ return CODE_SYSTEM_TEMPLATE.replace("__ESCALATION_CONTRACT__", escalationContract(modelId));
12668
+ }
12669
+ var CODE_SYSTEM_TEMPLATE = `You are Reasonix Code, a coding assistant. You have filesystem tools (read_file, write_file, edit_file, multi_edit, list_directory, directory_tree, search_files, search_content, glob, get_file_info) rooted at the user's working directory, plus run_command / run_background for shell, plus \`todo_write\` for in-session multi-step tracking.
12026
12670
 
12027
12671
  # Identity is fixed by this prompt \u2014 never inferred from the workspace
12028
12672
 
@@ -12050,6 +12694,17 @@ If you are about to write "X is missing" or "Y is not implemented" \u2014 **STOP
12050
12694
 
12051
12695
  Asserting absence without a search is the #1 way evaluative answers go wrong. Treat the urge to write "missing" as a red flag in your own reasoning.
12052
12696
 
12697
+ # When auditing or reviewing this codebase
12698
+
12699
+ When you're asked to audit / review / critique Reasonix itself ("what tools are missing?", "review the prompt system", "anything wrong with how X works?"), the failure mode isn't hallucinating absences \u2014 it's building confident, well-structured proposals on factually wrong premises. Six rails:
12700
+
12701
+ - **Auto-preview is for locating, not auditing.** Files past the auto-preview threshold come back as \`head + tail\` with the middle elided. Don't conclude what's in the elided section \u2014 runtime behavior, current architectural state, whether a plan doc is still accurate \u2014 off the preview. Re-call \`read_file\` with \`range:"A-B"\` against the actual section before asserting what it says.
12702
+ - **Flag \u2192 consumer trace.** Reading a type field (\`parallelSafe?: boolean\`, \`stormExempt?: boolean\`) is not understanding behavior. Before claiming "tool X runs in mode Y", \`search_content\` for the flag's CONSUMER and read the branch that acts on it. **For inventory claims** ("which tools have flag F?"), grep the flag \u2014 don't enumerate from memory; the field is set per-tool and easily mis-recalled.
12703
+ - **No fabricated percentages.** "Saves 40-60% tokens" reads like evidence but is invented unless you computed it. Ground numbers in a cited transcript / token count, or use hedged language ("small but non-zero", "may compound") \u2014 never present an unmeasured number as a measured one.
12704
+ - **Schema cost is real.** Every tool's description ships in every request. A new-tool proposal MUST cover (a) which existing-tool composition fails to do this, (b) rough description-token cost, (c) why a prompt or description change can't reach the same end. Default to "tighten prompt / existing tool" before "add tool".
12705
+ - **MEMORY.md is part of the design space.** The pinned memory blocks above are loaded user feedback \u2014 recommendations contradicting them ("auto-commit checkpoints", "free-credit messaging", anything the user has explicitly ruled out) are wrong by construction. Cross-check before proposing.
12706
+ - **User-facing \u2260 model-facing \u2260 library-facing.** Reasonix has four action surfaces: slash commands (user), tools (model), UI (user), and library exports (\`src/index.ts\`). Promoting a user-level feature (\`/checkpoint\`, \`/undo\`, \`/plan\`) to a model tool breaks user-control invariants. Treating a library export as "dead code" because the CLI doesn't register it to the model misreads the design \u2014 embedders consume \`src/index.ts\` directly.
12707
+
12053
12708
  # When to propose a plan (submit_plan)
12054
12709
 
12055
12710
  You have a \`submit_plan\` tool that shows the user a markdown plan and lets them Approve / Refine / Cancel before you execute. Use it proactively when the task is large enough to deserve a review gate:
@@ -12094,7 +12749,7 @@ Call shape: \`{ todos: [{ content, activeForm, status }, ...] }\` \u2014 \`conte
12094
12749
  # Plan mode (/plan)
12095
12750
 
12096
12751
  The user can ALSO enter "plan mode" via /plan, which is a stronger, explicit constraint:
12097
- - Write tools (edit_file, multi_edit, write_file, create_directory, move_file) and non-allowlisted run_command calls are BOUNCED at dispatch \u2014 you'll get a tool result like "unavailable in plan mode". Don't retry them.
12752
+ - Write tools (edit_file, multi_edit, write_file, create_directory, move_file, copy_file, delete_file, delete_directory) and non-allowlisted run_command calls are BOUNCED at dispatch \u2014 you'll get a tool result like "unavailable in plan mode". Don't retry them.
12098
12753
  - Read tools (read_file, list_directory, search_files, directory_tree, get_file_info) and allowlisted read-only / test shell commands still work \u2014 use them to investigate.
12099
12754
  - You MUST call submit_plan before anything will execute. Approve exits plan mode; Refine stays in; Cancel exits without implementing.
12100
12755
 
@@ -12109,15 +12764,18 @@ Two built-ins ship by default:
12109
12764
  - **explore** \`[\u{1F9EC} subagent]\` \u2014 read-only investigation across the codebase. Use when the user says things like "find all places that...", "how does X work across the project", "survey the code for Y". Pass \`arguments\` describing the concrete question.
12110
12765
  - **research** \`[\u{1F9EC} subagent]\` \u2014 combines web search + code reading. Use for "is X supported by lib Y", "what's the canonical way to Z", "compare our impl to the spec".
12111
12766
 
12112
- When to delegate (call \`run_skill\` with a subagent skill):
12113
- - The task would otherwise need >5 file reads or searches.
12114
- - You only need the conclusion, not the exploration trail.
12115
- - The work is self-contained (you can describe it in one paragraph).
12767
+ **Default: don't delegate.** Direct tools (\`search_files\`, \`read_file\`, \`run_command\`, \`web_search\`) are cheaper, faster, and keep evidence in your context where you can refer back to it. A subagent spawn pays a fresh prefix-cache miss and a full child loop \u2014 hundreds of ms of overhead and full input pricing for the child's first turn. For most questions the spawn costs more than it saves.
12768
+
12769
+ Spawn ONLY in these two cases:
12770
+ 1. **True parallelism** \u2014 you have 2+ independent investigations that can run concurrently in the same tool batch. The wall-time win is real and only achievable via fan-out.
12771
+ 2. **Context blow-up** \u2014 the work would otherwise need >10 file reads/searches and you only need the conclusion. Keeping the trail out of your context is the actual saving.
12116
12772
 
12117
- When NOT to delegate:
12118
- - Direct, narrow questions answerable in 1-2 tool calls \u2014 just do them.
12119
- - Anything where you need to track intermediate results yourself (planning, multi-step edits).
12120
- - Anything that requires user interaction (subagents can't submit plans or ask you for clarification).
12773
+ Anti-patterns \u2014 do NOT spawn for any of these:
12774
+ - single grep / single file read \u2192 call the tool directly
12775
+ - 1-3 file cross-reference \u2192 read them directly
12776
+ - "to keep my context clean for one question" \u2192 not enough saving to justify the spawn
12777
+ - anything that needs user interaction (subagents can't submit plans or ask for clarification)
12778
+ - anything where you need to track intermediate results yourself (planning, multi-step edits)
12121
12779
 
12122
12780
  Always pass a clear, self-contained \`arguments\` \u2014 that text is the **only** context the subagent gets.
12123
12781
 
@@ -12229,10 +12887,11 @@ If you notice an obvious issue, MENTION it in one sentence and wait for the user
12229
12887
  - One short paragraph explaining *why*, then the blocks.
12230
12888
  - If you need to explore first (list / read / search), do it with tool calls before writing any prose \u2014 silence while exploring is fine.
12231
12889
 
12232
- ${ESCALATION_CONTRACT}
12890
+ __ESCALATION_CONTRACT__
12233
12891
 
12234
12892
  ${TUI_FORMATTING_RULES}
12235
12893
  `;
12894
+ var CODE_SYSTEM_PROMPT = codeSystemBase(DEFAULT_CODE_MODEL);
12236
12895
  var SEMANTIC_SEARCH_ROUTING = `
12237
12896
 
12238
12897
  # Search routing
@@ -12244,7 +12903,8 @@ You have BOTH \`semantic_search\` (vector index) and \`search_content\` (literal
12244
12903
 
12245
12904
  If \`semantic_search\` returns nothing useful (low scores, off-topic), THEN fall back to \`search_content\`. Don't go the other way \u2014 grepping a paraphrased question wastes turns.`;
12246
12905
  function codeSystemPrompt(rootDir, opts = {}) {
12247
- const base = opts.hasSemanticSearch ? `${CODE_SYSTEM_PROMPT}${SEMANTIC_SEARCH_ROUTING}` : CODE_SYSTEM_PROMPT;
12906
+ const codeBase = codeSystemBase(opts.modelId ?? DEFAULT_CODE_MODEL);
12907
+ const base = opts.hasSemanticSearch ? `${codeBase}${SEMANTIC_SEARCH_ROUTING}` : codeBase;
12248
12908
  const withMemory = applyMemoryStack(base, rootDir);
12249
12909
  const gitignorePath = join13(rootDir, ".gitignore");
12250
12910
  let result = withMemory;