u1s1-cli 1.11.8 → 1.13.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 (49) hide show
  1. package/dist/agent-setup.d.ts +19 -3
  2. package/dist/agent-setup.js +85 -10
  3. package/dist/api.js +42 -34
  4. package/dist/bench-report.js +12 -9
  5. package/dist/bench.js +34 -31
  6. package/dist/brand.d.ts +2 -1
  7. package/dist/brand.js +38 -12
  8. package/dist/config.d.ts +11 -1
  9. package/dist/config.js +25 -8
  10. package/dist/context-usage.js +12 -9
  11. package/dist/deploy.js +49 -45
  12. package/dist/device-auth.js +7 -5
  13. package/dist/error-humanize.d.ts +1 -0
  14. package/dist/error-humanize.js +18 -6
  15. package/dist/feedback.js +48 -31
  16. package/dist/i18n.d.ts +21 -0
  17. package/dist/i18n.js +46 -0
  18. package/dist/import/index.js +64 -28
  19. package/dist/import/skills.js +72 -41
  20. package/dist/import/util.js +3 -2
  21. package/dist/import/write.js +2 -1
  22. package/dist/index.js +140 -49
  23. package/dist/login.js +39 -28
  24. package/dist/loop.js +18 -12
  25. package/dist/mcp/client.js +15 -14
  26. package/dist/mcp/command.d.ts +2 -2
  27. package/dist/mcp/command.js +109 -69
  28. package/dist/mcp/config.d.ts +22 -6
  29. package/dist/mcp/config.js +0 -0
  30. package/dist/mcp/extension.js +5 -3
  31. package/dist/mcp/tools.d.ts +2 -0
  32. package/dist/mcp/tools.js +10 -5
  33. package/dist/model.js +11 -10
  34. package/dist/nudges.d.ts +8 -2
  35. package/dist/nudges.js +22 -3
  36. package/dist/search-tools.js +7 -6
  37. package/dist/shell-doctor.js +9 -8
  38. package/dist/subagent.js +5 -4
  39. package/dist/tools.js +53 -37
  40. package/dist/update.js +29 -28
  41. package/dist/usage.d.ts +1 -1
  42. package/dist/usage.js +96 -43
  43. package/dist/web.js +2 -1
  44. package/dist/workflow/runner.js +33 -29
  45. package/dist/workflow/templates.js +70 -45
  46. package/dist/workflow/tool.js +15 -12
  47. package/package.json +4 -4
  48. package/scripts/patch-pi.js +347 -278
  49. package/scripts/render-regression.mjs +55 -4
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * The repository applies patches/*.patch through pnpm for development, while
5
5
  * global `npm i -g u1s1-cli` installations do not understand pnpm patches.
6
- * This script applies the same compact-UI and Windows TUI fixes to installed
7
- * dependencies so both installation paths behave identically.
6
+ * This script applies the same compact-UI, Windows TUI and UI-text (zh/en by
7
+ * U1S1_LANG) fixes to installed dependencies so both installation paths behave identically.
8
8
  *
9
9
  * Properties:
10
10
  * - Pure Node implementation with no patch(1) dependency, including Windows
@@ -13,7 +13,7 @@
13
13
  */
14
14
 
15
15
  import { readFileSync, writeFileSync, existsSync, realpathSync } from "node:fs";
16
- import { join, dirname } from "node:path";
16
+ import { join, dirname, resolve, sep } from "node:path";
17
17
  import { fileURLToPath } from "node:url";
18
18
  import { createRequire } from "node:module";
19
19
 
@@ -30,6 +30,9 @@ function addPackageDir(found, candidate) {
30
30
  }
31
31
  }
32
32
 
33
+ // Tests set U1S1_PATCH_PI_SCAN_ROOT so fixtures under the OS temp dir never touch stray installs above them.
34
+ const scanRoot = process.env.U1S1_PATCH_PI_SCAN_ROOT ? realpathSync(resolve(process.env.U1S1_PATCH_PI_SCAN_ROOT)) : undefined;
35
+
33
36
  function findPackageDirs(packageName) {
34
37
  const found = new Set();
35
38
  try {
@@ -43,10 +46,10 @@ function findPackageDirs(packageName) {
43
46
  while (true) {
44
47
  addPackageDir(found, join(dir, "node_modules", scope, name));
45
48
  const parent = dirname(dir);
46
- if (parent === dir) break;
49
+ if (parent === dir || resolve(dir) === scanRoot) break;
47
50
  dir = parent;
48
51
  }
49
- return [...found];
52
+ return [...found].filter((d) => !scanRoot || d === scanRoot || d.startsWith(scanRoot + sep));
50
53
  }
51
54
 
52
55
  function findTuiDirs() {
@@ -71,168 +74,6 @@ const COMPACT_UI_REPLACEMENTS = [
71
74
  ],
72
75
  ];
73
76
 
74
- // 内置斜杠命令描述汉化(dist/core/slash-commands.js),与 pnpm coding-agent patch 同步。
75
- const SLASH_COMMANDS_REPLACEMENTS = [
76
- ['{ name: "settings", description: "Open settings menu" },', '{ name: "settings", description: "打开设置菜单" },'],
77
- ['{ name: "model", description: "Select model (opens selector UI)", argumentHint: "<provider/model>" },', '{ name: "model", description: "切换模型(列表里有免费/价格提示)", argumentHint: "<provider/model>" },'],
78
- ['{ name: "tree", description: "Navigate session tree (switch branches)" },', '{ name: "tree", description: "查看会话分支树,切换分支" },'],
79
- ['{ name: "thinking", description: "Set thinking level", argumentHint: "<level>" },', '{ name: "thinking", description: "调整思考强度(想得越深越费额度)", argumentHint: "<level>" },'],
80
- ['{ name: "scoped-models", description: "Enable/disable models for Ctrl+P cycling" },', '{ name: "scoped-models", description: "设置 Ctrl+P 轮换的模型范围" },'],
81
- ['{ name: "export", description: "Export session (HTML default, or specify path: .html/.jsonl)" },', '{ name: "export", description: "导出会话(默认 HTML,也可指定 .html/.jsonl 路径)" },'],
82
- ['{ name: "import", description: "Import and resume a session from a JSONL file" },', '{ name: "import", description: "从 JSONL 文件导入并继续会话" },'],
83
- ['{ name: "share", description: "Share session as a secret GitHub gist" },', '{ name: "share", description: "把会话分享为 GitHub 私密 gist" },'],
84
- // /bug 会把诊断信息上传给 Pi 上游开发者,u1s1 用户的问题应走 /feedback 到我们这边,从候选列表里去掉
85
- ['{ name: "bug", description: "Report a bug to the Pi developers", argumentHint: "<description>" },', ''],
86
- ['{ name: "copy", description: "Copy last agent message to clipboard" },', '{ name: "copy", description: "复制上一条回复到剪贴板" },'],
87
- ['{ name: "name", description: "Set session display name" },', '{ name: "name", description: "给当前会话起个名字" },'],
88
- ['{ name: "session", description: "Show session info and stats" },', '{ name: "session", description: "查看会话信息与统计" },'],
89
- ['{ name: "changelog", description: "Show changelog entries" },', '{ name: "changelog", description: "查看引擎更新日志(英文)" },'],
90
- ['{ name: "hotkeys", description: "Show all keyboard shortcuts" },', '{ name: "hotkeys", description: "查看全部快捷键" },'],
91
- ['{ name: "fork", description: "Create a new fork from a previous user message" },', '{ name: "fork", description: "从之前某条消息分叉出一个新会话" },'],
92
- ['{ name: "clone", description: "Duplicate the current session at the current position" },', '{ name: "clone", description: "复制当前会话再继续" },'],
93
- ['{ name: "trust", description: "Save project trust decision for future sessions" },', '{ name: "trust", description: "记住对这个项目目录的信任选择" },'],
94
- ['{ name: "login", description: "Configure provider authentication", argumentHint: "<provider>" },', '{ name: "login", description: "换 u1s1 账号请先 /exit,再运行 u1s1 login", argumentHint: "<provider>" },'],
95
- ['{ name: "logout", description: "Remove provider authentication" },', '{ name: "logout", description: "退出登录请先 /exit,再运行 u1s1 logout" },'],
96
- ['{ name: "new", description: "Start a new session" },', '{ name: "new", description: "开始新会话" },'],
97
- ['{ name: "compact", description: "Manually compact the session context" },', '{ name: "compact", description: "压缩会话上下文,腾出空间继续聊" },'],
98
- ['{ name: "resume", description: "Resume a different session" },', '{ name: "resume", description: "恢复或切换到其他会话" },'],
99
- ['{ name: "reload", description: "Reload keybindings, extensions, skills, prompts, themes, and context files" },', '{ name: "reload", description: "重新加载扩展、技能、主题等资源" },'],
100
- ['{ name: "quit", description: `Quit ${APP_NAME}` },', '{ name: "quit", description: `退出 ${APP_NAME}` },'],
101
- ];
102
-
103
- // 扩展报错文案汉化 + 默认不刷堆栈(dist/modes/interactive/interactive-mode.js),与 pnpm coding-agent patch 同步。
104
- // 小白看到一屏英文 JS 堆栈只会以为程序坏了;一句中文 + 下一步(重启 / u1s1 feedback)就够。
105
- const EXTENSION_ERROR_REPLACEMENTS = [
106
- ['const errorMsg = `Extension "${extensionPath}" error: ${error}`;', 'const errorMsg = `扩展 ${String(extensionPath).split(/[\\\\/]/).pop()} 出错(重启 u1s1 一般可恢复;反复出现请运行 u1s1 feedback 反馈):${error}`;'],
107
- ['if (stack) {', 'if (stack && process.env.U1S1_DEBUG_EXTENSIONS === "1") {'],
108
- ];
109
-
110
- function patchExtensionError() {
111
- for (const piDir of findPackageDirs("@earendil-works/pi-coding-agent")) {
112
- const target = join(piDir, "dist", "modes", "interactive", "interactive-mode.js");
113
- try {
114
- let text = readFileSync(target, "utf8");
115
- let applied = 0;
116
- for (const [from, to] of EXTENSION_ERROR_REPLACEMENTS) {
117
- if (text.includes(to)) {
118
- applied++;
119
- continue;
120
- }
121
- if (text.split(from).length !== 2) continue;
122
- text = text.replace(from, to);
123
- applied++;
124
- }
125
- if (applied < EXTENSION_ERROR_REPLACEMENTS.length) {
126
- console.log(
127
- `[u1s1] 提示: pi 版本可能已更新,扩展报错文案补丁只应用了 ${applied}/${EXTENSION_ERROR_REPLACEMENTS.length} 处(不影响使用)`,
128
- );
129
- }
130
- writeFileSync(target, text);
131
- } catch {
132
- continue;
133
- }
134
- }
135
- }
136
-
137
- // /hotkeys 快捷键表汉化(dist/modes/interactive/interactive-mode.js),与 pnpm coding-agent patch 同步。
138
- // 表格由 pi 硬编码英文拼出来,启动横幅/help 都在引导新手输入 /hotkeys,整页英文等于没给。
139
- // 只译说明列,按键显示(`${cursorUp}` 等)原样保留;表头出现 4 次,用 expected 计数整体替换。
140
- const HOTKEYS_REPLACEMENTS = [
141
- ['"Keyboard Shortcuts"', '"快捷键一览"'],
142
- ["**Navigation**", "**移动**"],
143
- ["**Editing**", "**编辑**"],
144
- ["**Other**", "**其他**"],
145
- ["**Extensions**", "**扩展**"],
146
- { from: "| Key | Action |", to: "| 按键 | 操作 |", expected: 4 },
147
- ["| Move cursor / browse history |", "| 移动光标 / 翻看输入历史 |"],
148
- ["| Move by word |", "| 按词移动 |"],
149
- ["| Start of line |", "| 跳到行首 |"],
150
- ["| End of line |", "| 跳到行尾 |"],
151
- ["| Jump forward to character |", "| 向后跳到指定字符 |"],
152
- ["| Jump backward to character |", "| 向前跳到指定字符 |"],
153
- ["| Scroll by page |", "| 整页滚动 |"],
154
- ["| Send message |", "| 发送消息 |"],
155
- ['| New line${process.platform === "win32" ? " (Ctrl+Enter on Windows Terminal)" : ""} |', '| 换行${process.platform === "win32" ? "(Windows Terminal 用 Ctrl+Enter)" : ""} |'],
156
- ["| Delete word backwards |", "| 删除光标前一个词 |"],
157
- ["| Delete word forwards |", "| 删除光标后一个词 |"],
158
- ["| Delete to start of line |", "| 删到行首 |"],
159
- ["| Delete to end of line |", "| 删到行尾 |"],
160
- ["| Paste the most-recently-deleted text |", "| 粘贴最近删除的文本 |"],
161
- ["| Cycle through the deleted text after pasting |", "| 粘贴后在更早删除的文本间循环 |"],
162
- ["| Undo |", "| 撤销 |"],
163
- ["| Path completion / accept autocomplete |", "| 路径补全 / 接受自动补全 |"],
164
- ["| Cancel autocomplete / abort streaming |", "| 取消补全 / 中断模型回复 |"],
165
- ["| Clear editor (first) / exit (second) |", "| 清空输入框(再按一次退出) |"],
166
- ["| Exit (when editor is empty) |", "| 退出(输入框为空时) |"],
167
- ["| Suspend to background |", "| 挂起到后台 |"],
168
- ["| Cycle thinking level |", "| 切换思考深度 |"],
169
- ["| Cycle models |", "| 轮换模型 |"],
170
- ["| Open model selector |", "| 打开模型选择器 |"],
171
- ["| Toggle tool output expansion |", "| 展开 / 收起工具输出 |"],
172
- ["| Toggle thinking block visibility |", "| 显示 / 隐藏思考过程 |"],
173
- ["| Edit message in external editor |", "| 用外部编辑器写消息 |"],
174
- ["| Copy selection or last assistant message |", "| 复制选中内容或最近一条回复 |"],
175
- ["| Queue follow-up message |", "| 把消息排到当前回复之后 |"],
176
- ["| Restore queued messages |", "| 取回排队中的消息 |"],
177
- ["| Paste image or text from clipboard |", "| 从剪贴板粘贴图片或文本 |"],
178
- ["| Slash commands |", "| 斜杠命令(/help 看常用操作) |"],
179
- ["| Run bash command |", "| 运行终端命令 |"],
180
- ["| Run bash command (excluded from context) |", "| 运行终端命令(结果不进上下文) |"],
181
- ];
182
-
183
- function patchHotkeys() {
184
- for (const piDir of findPackageDirs("@earendil-works/pi-coding-agent")) {
185
- const target = join(piDir, "dist", "modes", "interactive", "interactive-mode.js");
186
- try {
187
- let text = readFileSync(target, "utf8");
188
- let applied = 0;
189
- for (const rule of HOTKEYS_REPLACEMENTS) {
190
- const { from, to, expected } = Array.isArray(rule) ? { from: rule[0], to: rule[1], expected: 1 } : rule;
191
- if (text.includes(to)) {
192
- applied++;
193
- continue;
194
- }
195
- if (text.split(from).length !== expected + 1) continue;
196
- text = text.split(from).join(to);
197
- applied++;
198
- }
199
- if (applied < HOTKEYS_REPLACEMENTS.length) {
200
- console.log(
201
- `[u1s1] 提示: pi 版本可能已更新,/hotkeys 汉化补丁只应用了 ${applied}/${HOTKEYS_REPLACEMENTS.length} 处(不影响使用)`,
202
- );
203
- }
204
- writeFileSync(target, text);
205
- } catch {
206
- continue;
207
- }
208
- }
209
- }
210
-
211
- function patchSlashCommands() {
212
- for (const piDir of findPackageDirs("@earendil-works/pi-coding-agent")) {
213
- const target = join(piDir, "dist", "core", "slash-commands.js");
214
- try {
215
- let text = readFileSync(target, "utf8");
216
- if (text.includes("打开设置菜单")) continue; // pnpm patch 或此前运行已应用
217
-
218
- let applied = 0;
219
- for (const [from, to] of SLASH_COMMANDS_REPLACEMENTS) {
220
- if (!text.includes(from)) continue;
221
- text = text.split(from).join(to);
222
- applied++;
223
- }
224
- if (applied < SLASH_COMMANDS_REPLACEMENTS.length) {
225
- console.log(
226
- `[u1s1] 提示: pi 版本可能已更新,命令描述汉化只应用了 ${applied}/${SLASH_COMMANDS_REPLACEMENTS.length} 处(不影响使用)`,
227
- );
228
- }
229
- writeFileSync(target, text);
230
- } catch {
231
- continue;
232
- }
233
- }
234
- }
235
-
236
77
  function patchCompactUi() {
237
78
  const piDirs = findPackageDirs("@earendil-works/pi-coding-agent");
238
79
  if (piDirs.length === 0) {
@@ -268,152 +109,382 @@ function patchCompactUi() {
268
109
  }
269
110
  }
270
111
 
271
- // ---- TUI 零散英文汉化(与 pnpm coding-agent patch 同步,patch-pi.test.ts 校验双轨一致) ----
112
+ // ---- TUI 文案(与 pnpm coding-agent patch 同步,patch-pi.test.ts 校验双轨一致) ----
113
+ // 中英双语:u1s1 CLI 拉起 pi 前写 process.env.U1S1_LANG(国际站 "en",中国站 "zh"),
114
+ // 补丁注入的文案一律写成 `process.env.U1S1_LANG === "en" ? "English" : "中文"`,
115
+ // 英文优先用 pi 上游原文;非 "en" 时中文与之前逐字节一致。
116
+ // 不抽成文件内 helper:规则逐条独立应用,helper 那条没打上而用到它的打上了会直接 ReferenceError。
117
+ // 旧版安装打的是纯中文替换,zhOnly(to) 能还原出那个旧结果,applyTextRules 会把它升级成双语版。
118
+
119
+ // 内置斜杠命令描述(dist/core/slash-commands.js)。模块加载时求值:CLI 在动态 import pi 之前就设好了 U1S1_LANG。
120
+ const SLASH_COMMANDS_REPLACEMENTS = [
121
+ ['{ name: "settings", description: "Open settings menu" },', '{ name: "settings", description: process.env.U1S1_LANG === "en" ? "Open settings menu" : "打开设置菜单" },'],
122
+ ['{ name: "model", description: "Select model (opens selector UI)", argumentHint: "<provider/model>" },', '{ name: "model", description: process.env.U1S1_LANG === "en" ? "Select model (opens selector UI)" : "切换模型(列表里有免费/价格提示)", argumentHint: "<provider/model>" },'],
123
+ ['{ name: "tree", description: "Navigate session tree (switch branches)" },', '{ name: "tree", description: process.env.U1S1_LANG === "en" ? "Navigate session tree (switch branches)" : "查看会话分支树,切换分支" },'],
124
+ ['{ name: "thinking", description: "Set thinking level", argumentHint: "<level>" },', '{ name: "thinking", description: process.env.U1S1_LANG === "en" ? "Set thinking level" : "调整思考强度(想得越深越费额度)", argumentHint: "<level>" },'],
125
+ ['{ name: "scoped-models", description: "Enable/disable models for Ctrl+P cycling" },', '{ name: "scoped-models", description: process.env.U1S1_LANG === "en" ? "Enable/disable models for Ctrl+P cycling" : "设置 Ctrl+P 轮换的模型范围" },'],
126
+ ['{ name: "export", description: "Export session (HTML default, or specify path: .html/.jsonl)" },', '{ name: "export", description: process.env.U1S1_LANG === "en" ? "Export session (HTML default, or specify path: .html/.jsonl)" : "导出会话(默认 HTML,也可指定 .html/.jsonl 路径)" },'],
127
+ ['{ name: "import", description: "Import and resume a session from a JSONL file" },', '{ name: "import", description: process.env.U1S1_LANG === "en" ? "Import and resume a session from a JSONL file" : "从 JSONL 文件导入并继续会话" },'],
128
+ ['{ name: "share", description: "Share session as a secret GitHub gist" },', '{ name: "share", description: process.env.U1S1_LANG === "en" ? "Share session as a secret GitHub gist" : "把会话分享为 GitHub 私密 gist" },'],
129
+ // /bug 会把诊断信息上传给 Pi 上游开发者,u1s1 用户的问题应走 /feedback 到我们这边,从候选列表里去掉
130
+ ['{ name: "bug", description: "Report a bug to the Pi developers", argumentHint: "<description>" },', ''],
131
+ ['{ name: "copy", description: "Copy last agent message to clipboard" },', '{ name: "copy", description: process.env.U1S1_LANG === "en" ? "Copy last agent message to clipboard" : "复制上一条回复到剪贴板" },'],
132
+ ['{ name: "name", description: "Set session display name" },', '{ name: "name", description: process.env.U1S1_LANG === "en" ? "Set session display name" : "给当前会话起个名字" },'],
133
+ ['{ name: "session", description: "Show session info and stats" },', '{ name: "session", description: process.env.U1S1_LANG === "en" ? "Show session info and stats" : "查看会话信息与统计" },'],
134
+ ['{ name: "changelog", description: "Show changelog entries" },', '{ name: "changelog", description: process.env.U1S1_LANG === "en" ? "Show changelog entries" : "查看引擎更新日志(英文)" },'],
135
+ ['{ name: "hotkeys", description: "Show all keyboard shortcuts" },', '{ name: "hotkeys", description: process.env.U1S1_LANG === "en" ? "Show all keyboard shortcuts" : "查看全部快捷键" },'],
136
+ ['{ name: "fork", description: "Create a new fork from a previous user message" },', '{ name: "fork", description: process.env.U1S1_LANG === "en" ? "Create a new fork from a previous user message" : "从之前某条消息分叉出一个新会话" },'],
137
+ ['{ name: "clone", description: "Duplicate the current session at the current position" },', '{ name: "clone", description: process.env.U1S1_LANG === "en" ? "Duplicate the current session at the current position" : "复制当前会话再继续" },'],
138
+ ['{ name: "trust", description: "Save project trust decision for future sessions" },', '{ name: "trust", description: process.env.U1S1_LANG === "en" ? "Save project trust decision for future sessions" : "记住对这个项目目录的信任选择" },'],
139
+ // /login /logout 管的是 pi 自己的渠道登录,u1s1 账号要退出 TUI 用 u1s1 login/logout,英文也照这个意思写
140
+ ['{ name: "login", description: "Configure provider authentication", argumentHint: "<provider>" },', '{ name: "login", description: process.env.U1S1_LANG === "en" ? "To switch u1s1 accounts, /exit and run u1s1 login" : "换 u1s1 账号请先 /exit,再运行 u1s1 login", argumentHint: "<provider>" },'],
141
+ ['{ name: "logout", description: "Remove provider authentication" },', '{ name: "logout", description: process.env.U1S1_LANG === "en" ? "To sign out, /exit and run u1s1 logout" : "退出登录请先 /exit,再运行 u1s1 logout" },'],
142
+ ['{ name: "new", description: "Start a new session" },', '{ name: "new", description: process.env.U1S1_LANG === "en" ? "Start a new session" : "开始新会话" },'],
143
+ ['{ name: "compact", description: "Manually compact the session context" },', '{ name: "compact", description: process.env.U1S1_LANG === "en" ? "Manually compact the session context" : "压缩会话上下文,腾出空间继续聊" },'],
144
+ ['{ name: "resume", description: "Resume a different session" },', '{ name: "resume", description: process.env.U1S1_LANG === "en" ? "Resume a different session" : "恢复或切换到其他会话" },'],
145
+ ['{ name: "reload", description: "Reload keybindings, extensions, skills, prompts, themes, and context files" },', '{ name: "reload", description: process.env.U1S1_LANG === "en" ? "Reload keybindings, extensions, skills, prompts, themes, and context files" : "重新加载扩展、技能、主题等资源" },'],
146
+ ['{ name: "quit", description: `Quit ${APP_NAME}` },', '{ name: "quit", description: process.env.U1S1_LANG === "en" ? `Quit ${APP_NAME}` : `退出 ${APP_NAME}` },'],
147
+ ];
148
+
149
+ // 扩展报错一句话 + 默认不刷堆栈(dist/modes/interactive/interactive-mode.js)。
150
+ // 小白看到一屏 JS 堆栈只会以为程序坏了;一句话 + 下一步(重启 / u1s1 feedback)就够。
151
+ const EXTENSION_ERROR_REPLACEMENTS = [
152
+ ['const errorMsg = `Extension "${extensionPath}" error: ${error}`;', 'const errorMsg = process.env.U1S1_LANG === "en" ? `Extension ${String(extensionPath).split(/[\\\\/]/).pop()} failed (restarting u1s1 usually fixes it; if it keeps happening, run u1s1 feedback): ${error}` : `扩展 ${String(extensionPath).split(/[\\\\/]/).pop()} 出错(重启 u1s1 一般可恢复;反复出现请运行 u1s1 feedback 反馈):${error}`;'],
153
+ ['if (stack) {', 'if (stack && process.env.U1S1_DEBUG_EXTENSIONS === "1") {'],
154
+ ];
155
+
156
+ // /hotkeys 快捷键表(dist/modes/interactive/interactive-mode.js)。
157
+ // 表格由 pi 硬编码拼出来,启动横幅/help 都在引导新手输入 /hotkeys,整页英文等于没给。
158
+ // 只换说明列,按键显示(`${cursorUp}` 等)原样保留;表头出现 4 次,用 expected 计数整体替换。
159
+ const HOTKEYS_REPLACEMENTS = [
160
+ ['"Keyboard Shortcuts"', 'process.env.U1S1_LANG === "en" ? "Keyboard Shortcuts" : "快捷键一览"'],
161
+ ["**Navigation**", '${process.env.U1S1_LANG === "en" ? "**Navigation**" : "**移动**"}'],
162
+ ["**Editing**", '${process.env.U1S1_LANG === "en" ? "**Editing**" : "**编辑**"}'],
163
+ ["**Other**", '${process.env.U1S1_LANG === "en" ? "**Other**" : "**其他**"}'],
164
+ ["**Extensions**", '${process.env.U1S1_LANG === "en" ? "**Extensions**" : "**扩展**"}'],
165
+ { from: "| Key | Action |", to: '| ${process.env.U1S1_LANG === "en" ? "Key" : "按键"} | ${process.env.U1S1_LANG === "en" ? "Action" : "操作"} |', expected: 4 },
166
+ ["| Move cursor / browse history |", '| ${process.env.U1S1_LANG === "en" ? "Move cursor / browse history" : "移动光标 / 翻看输入历史"} |'],
167
+ ["| Move by word |", '| ${process.env.U1S1_LANG === "en" ? "Move by word" : "按词移动"} |'],
168
+ ["| Start of line |", '| ${process.env.U1S1_LANG === "en" ? "Start of line" : "跳到行首"} |'],
169
+ ["| End of line |", '| ${process.env.U1S1_LANG === "en" ? "End of line" : "跳到行尾"} |'],
170
+ ["| Jump forward to character |", '| ${process.env.U1S1_LANG === "en" ? "Jump forward to character" : "向后跳到指定字符"} |'],
171
+ ["| Jump backward to character |", '| ${process.env.U1S1_LANG === "en" ? "Jump backward to character" : "向前跳到指定字符"} |'],
172
+ ["| Scroll by page |", '| ${process.env.U1S1_LANG === "en" ? "Scroll by page" : "整页滚动"} |'],
173
+ ["| Send message |", '| ${process.env.U1S1_LANG === "en" ? "Send message" : "发送消息"} |'],
174
+ ['| New line${process.platform === "win32" ? " (Ctrl+Enter on Windows Terminal)" : ""} |', '| ${process.env.U1S1_LANG === "en" ? "New line" : "换行"}${process.platform === "win32" ? (process.env.U1S1_LANG === "en" ? " (Ctrl+Enter on Windows Terminal)" : "(Windows Terminal 用 Ctrl+Enter)") : ""} |'],
175
+ ["| Delete word backwards |", '| ${process.env.U1S1_LANG === "en" ? "Delete word backwards" : "删除光标前一个词"} |'],
176
+ ["| Delete word forwards |", '| ${process.env.U1S1_LANG === "en" ? "Delete word forwards" : "删除光标后一个词"} |'],
177
+ ["| Delete to start of line |", '| ${process.env.U1S1_LANG === "en" ? "Delete to start of line" : "删到行首"} |'],
178
+ ["| Delete to end of line |", '| ${process.env.U1S1_LANG === "en" ? "Delete to end of line" : "删到行尾"} |'],
179
+ ["| Paste the most-recently-deleted text |", '| ${process.env.U1S1_LANG === "en" ? "Paste the most-recently-deleted text" : "粘贴最近删除的文本"} |'],
180
+ ["| Cycle through the deleted text after pasting |", '| ${process.env.U1S1_LANG === "en" ? "Cycle through the deleted text after pasting" : "粘贴后在更早删除的文本间循环"} |'],
181
+ ["| Undo |", '| ${process.env.U1S1_LANG === "en" ? "Undo" : "撤销"} |'],
182
+ ["| Path completion / accept autocomplete |", '| ${process.env.U1S1_LANG === "en" ? "Path completion / accept autocomplete" : "路径补全 / 接受自动补全"} |'],
183
+ ["| Cancel autocomplete / abort streaming |", '| ${process.env.U1S1_LANG === "en" ? "Cancel autocomplete / abort streaming" : "取消补全 / 中断模型回复"} |'],
184
+ ["| Clear editor (first) / exit (second) |", '| ${process.env.U1S1_LANG === "en" ? "Clear editor (first) / exit (second)" : "清空输入框(再按一次退出)"} |'],
185
+ ["| Exit (when editor is empty) |", '| ${process.env.U1S1_LANG === "en" ? "Exit (when editor is empty)" : "退出(输入框为空时)"} |'],
186
+ ["| Suspend to background |", '| ${process.env.U1S1_LANG === "en" ? "Suspend to background" : "挂起到后台"} |'],
187
+ ["| Cycle thinking level |", '| ${process.env.U1S1_LANG === "en" ? "Cycle thinking level" : "切换思考深度"} |'],
188
+ ["| Cycle models |", '| ${process.env.U1S1_LANG === "en" ? "Cycle models" : "轮换模型"} |'],
189
+ ["| Open model selector |", '| ${process.env.U1S1_LANG === "en" ? "Open model selector" : "打开模型选择器"} |'],
190
+ ["| Toggle tool output expansion |", '| ${process.env.U1S1_LANG === "en" ? "Toggle tool output expansion" : "展开 / 收起工具输出"} |'],
191
+ ["| Toggle thinking block visibility |", '| ${process.env.U1S1_LANG === "en" ? "Toggle thinking block visibility" : "显示 / 隐藏思考过程"} |'],
192
+ ["| Edit message in external editor |", '| ${process.env.U1S1_LANG === "en" ? "Edit message in external editor" : "用外部编辑器写消息"} |'],
193
+ ["| Copy selection or last assistant message |", '| ${process.env.U1S1_LANG === "en" ? "Copy selection or last assistant message" : "复制选中内容或最近一条回复"} |'],
194
+ ["| Queue follow-up message |", '| ${process.env.U1S1_LANG === "en" ? "Queue follow-up message" : "把消息排到当前回复之后"} |'],
195
+ ["| Restore queued messages |", '| ${process.env.U1S1_LANG === "en" ? "Restore queued messages" : "取回排队中的消息"} |'],
196
+ ["| Paste files on macOS, images, or text from clipboard |", '| ${process.env.U1S1_LANG === "en" ? "Paste files on macOS, images, or text from clipboard" : "从剪贴板粘贴图片、文本或(macOS)文件"} |'],
197
+ ["| Slash commands |", '| ${process.env.U1S1_LANG === "en" ? "Slash commands (/help for common actions)" : "斜杠命令(/help 看常用操作)"} |'],
198
+ ["| Run bash command |", '| ${process.env.U1S1_LANG === "en" ? "Run bash command" : "运行终端命令"} |'],
199
+ ["| Run bash command (excluded from context) |", '| ${process.env.U1S1_LANG === "en" ? "Run bash command (excluded from context)" : "运行终端命令(结果不进上下文)"} |'],
200
+ ];
201
+
272
202
  // 新手最常撞见的几句:Esc 中断后的 "Operation aborted"、每条报错前缀 "Error:"、
273
203
  // 每轮都在转的 "Working..."、选择器脚注的 "navigate/select/cancel"、/model 列表只显示 id
274
204
  // 看不到免费/价格提示、/settings 整页英文。行为不变,只换文案;选择器改为渲染 name(带提示)。
275
205
  const ASSISTANT_TEXT_REPLACEMENTS = [
276
- ['theme.fg("error", "Response was truncated before completion.")', 'theme.fg("error", "回复超出单次长度上限被截断了,输入「继续」可以接着写。")'],
277
- [': "Operation aborted";', ': "已中断";'],
278
- ['const errorMsg = message.errorMessage || "Unknown error";', 'const errorMsg = message.errorMessage || "未知错误(反复出现可运行 u1s1 feedback 反馈)";'],
279
- ['theme.fg("error", `Error: ${errorMsg}`)', 'theme.fg("error", `出错:${errorMsg}`)'],
206
+ ['theme.fg("error", "Response was truncated before completion.")', 'theme.fg("error", process.env.U1S1_LANG === "en" ? "Response was truncated before completion. Type \\"continue\\" to pick up where it left off." : "回复超出单次长度上限被截断了,输入「继续」可以接着写。")'],
207
+ [': "Operation aborted";', ': (process.env.U1S1_LANG === "en" ? "Operation aborted" : "已中断");'],
208
+ ['const errorMsg = message.errorMessage || "Unknown error";', 'const errorMsg = message.errorMessage || (process.env.U1S1_LANG === "en" ? "Unknown error (if it keeps happening, run u1s1 feedback)" : "未知错误(反复出现可运行 u1s1 feedback 反馈)");'],
209
+ ['theme.fg("error", `Error: ${errorMsg}`)', 'theme.fg("error", process.env.U1S1_LANG === "en" ? `Error: ${errorMsg}` : `出错:${errorMsg}`)'],
280
210
  ];
281
211
  const WORKING_TEXT_REPLACEMENTS = [
282
- ['defaultWorkingMessage = "Working";', 'defaultWorkingMessage = "思考中…";'],
283
- ['defaultHiddenThinkingLabel = "Thinking...";', 'defaultHiddenThinkingLabel = "思考中…";'],
212
+ ['defaultWorkingMessage = "Working";', 'defaultWorkingMessage = process.env.U1S1_LANG === "en" ? "Working" : "思考中…";'],
213
+ ['defaultHiddenThinkingLabel = "Thinking...";', 'defaultHiddenThinkingLabel = process.env.U1S1_LANG === "en" ? "Thinking..." : "思考中…";'],
284
214
  ];
285
215
  // 脚注提示(↑↓ navigate · enter select · esc cancel …)在十几个组件里各写各的,
286
- // 在 keyHint/rawKeyHint 出口处统一查表翻译,新组件加进来也能覆盖到。
216
+ // 在 keyHint/rawKeyHint 出口处统一查表翻译,新组件加进来也能覆盖到;英文直接用原文。
287
217
  const KEY_HINT_REPLACEMENTS = [
288
- [
289
- 'export function keyHint(keybinding, description) {',
290
- 'const U1S1_HINTS = { cancel: "取消", "to cancel": "取消", "to cancel,": "取消,", navigate: "上下选择", submit: "提交", "to submit": "提交", save: "保存", select: "选择", confirm: "确认", scope: "切换范围", "to expand": "展开", "to collapse": "收起", "to run bash": "直接跑命令", "to run bash (no context)": "跑命令(不进上下文)", toggle: "切换", "cycle inherit/+/-": "循环 继承/+/-", "for commands": "输入命令", commands: "命令", bash: "命令", close: "关闭", "to close": "关闭", "to attach": "拖入文件即附加", "skip setup": "跳过设置", "switch mode": "切换模式", newline: "换行", "to delete to end": "删到行尾", sort: "排序", named: "只看已命名", rename: "重命名", delete: "删除", "to exit": "退出", "to cycle models": "切换模型", "external editor": "外部编辑器", "clear/exit": "清空/退出" };\nconst u1s1Hint = (d) => U1S1_HINTS[d] ?? d;\nexport function keyHint(keybinding, description) {',
291
- ],
218
+ {
219
+ from: 'export function keyHint(keybinding, description) {',
220
+ to: 'const U1S1_HINTS = { cancel: "取消", "to cancel": "取消", "to cancel,": "取消,", navigate: "上下选择", submit: "提交", "to submit": "提交", save: "保存", select: "选择", confirm: "确认", scope: "切换范围", "to expand": "展开", "to collapse": "收起", "to run bash": "直接跑命令", "to run bash (no context)": "跑命令(不进上下文)", toggle: "切换", "cycle inherit/+/-": "循环 继承/+/-", "for commands": "输入命令", commands: "命令", bash: "命令", close: "关闭", "to close": "关闭", "to attach": "拖入文件即附加", "skip setup": "跳过设置", "switch mode": "切换模式", newline: "换行", "to delete to end": "删到行尾", sort: "排序", named: "只看已命名", rename: "重命名", delete: "删除", "to exit": "退出", "to cycle models": "切换模型", "external editor": "外部编辑器", "clear/exit": "清空/退出" };\nconst u1s1Hint = (d) => (process.env.U1S1_LANG === "en" ? d : U1S1_HINTS[d] ?? d);\nexport function keyHint(keybinding, description) {',
221
+ legacy: "const u1s1Hint = (d) => U1S1_HINTS[d] ?? d;",
222
+ legacyTo: 'const u1s1Hint = (d) => (process.env.U1S1_LANG === "en" ? d : U1S1_HINTS[d] ?? d);',
223
+ },
292
224
  { from: 'theme.fg("muted", ` ${description}`)', to: 'theme.fg("muted", ` ${u1s1Hint(description)}`)', expected: 2 },
293
225
  ];
294
226
  const MODEL_SELECTOR_REPLACEMENTS = [
295
227
  ['const modelText = isSelected ? theme.fg("accent", item.id) : item.id;', 'const modelText = isSelected ? theme.fg("accent", item.model.name || item.id) : (item.model.name || item.id);'],
296
- ['Model Name: ${selected.model.name}`)', '模型 ID: ${selected.model.id}`)'],
297
- ['No matching models")', '没有匹配的模型")'],
298
- ['theme.fg("muted", " · default")', 'theme.fg("muted", " · 默认")'],
299
- ['const allText = this.scope === "all" ? theme.fg("accent", "all") : theme.fg("muted", "all");', 'const allText = this.scope === "all" ? theme.fg("accent", "全部") : theme.fg("muted", "全部");'],
300
- ['const scopedText = this.scope === "scoped" ? theme.fg("accent", "scoped") : theme.fg("muted", "scoped");', 'const scopedText = this.scope === "scoped" ? theme.fg("accent", "已配置") : theme.fg("muted", "已配置");'],
301
- ['theme.fg("muted", "Scope: ")', 'theme.fg("muted", "范围: ")'],
302
- ['theme.fg("muted", " (all/scoped)")', 'theme.fg("muted", " (全部/已配置)")'],
303
- ['refreshStatusMessage = "Refreshing model catalogs…";', 'refreshStatusMessage = "正在刷新模型列表…";'],
304
- ['"Model catalogs refreshed."', '"模型列表已刷新。"'],
305
- ['const hintText = "Only showing models from configured providers. Use /login to add providers.";', 'const hintText = "只显示已配置渠道的模型。";'],
228
+ ['theme.fg("muted", ` Model Name: ${selected.model.name}`)', 'theme.fg("muted", ` ${process.env.U1S1_LANG === "en" ? "Model ID" : "模型 ID"}: ${selected.model.id}`)'],
229
+ ['theme.fg("muted", " No matching models")', 'theme.fg("muted", process.env.U1S1_LANG === "en" ? " No matching models" : " 没有匹配的模型")'],
230
+ ['theme.fg("muted", " · default")', 'theme.fg("muted", process.env.U1S1_LANG === "en" ? " · default" : " · 默认")'],
231
+ ['const allText = this.scope === "all" ? theme.fg("accent", "all") : theme.fg("muted", "all");', 'const allText = this.scope === "all" ? theme.fg("accent", process.env.U1S1_LANG === "en" ? "all" : "全部") : theme.fg("muted", process.env.U1S1_LANG === "en" ? "all" : "全部");'],
232
+ ['const scopedText = this.scope === "scoped" ? theme.fg("accent", "scoped") : theme.fg("muted", "scoped");', 'const scopedText = this.scope === "scoped" ? theme.fg("accent", process.env.U1S1_LANG === "en" ? "scoped" : "已配置") : theme.fg("muted", process.env.U1S1_LANG === "en" ? "scoped" : "已配置");'],
233
+ ['theme.fg("muted", "Scope: ")', 'theme.fg("muted", process.env.U1S1_LANG === "en" ? "Scope: " : "范围: ")'],
234
+ ['theme.fg("muted", " (all/scoped)")', 'theme.fg("muted", process.env.U1S1_LANG === "en" ? " (all/scoped)" : " (全部/已配置)")'],
235
+ ['refreshStatusMessage = "Refreshing model catalogs…";', 'refreshStatusMessage = process.env.U1S1_LANG === "en" ? "Refreshing model catalogs…" : "正在刷新模型列表…";'],
236
+ ['"Model catalogs refreshed."', '(process.env.U1S1_LANG === "en" ? "Model catalogs refreshed." : "模型列表已刷新。")'],
237
+ // 去掉 "Use /login":u1s1 的模型由账号决定,不走 pi 的渠道登录
238
+ ['const hintText = "Only showing models from configured providers. Use /login to add providers.";', 'const hintText = process.env.U1S1_LANG === "en" ? "Only showing models from configured providers." : "只显示已配置渠道的模型。";'],
306
239
  ];
240
+ // THINKING_DESCRIPTIONS / DEFAULT_PROJECT_TRUST_LABELS 是模块顶层常量,加载时求值(同斜杠命令,见上)。
307
241
  const SETTINGS_REPLACEMENTS = [
308
- ['off: "No reasoning",', 'off: "不推理",'],
309
- ['minimal: "Very brief reasoning (~1k tokens)",', 'minimal: "极简推理(约 1k token)",'],
310
- ['low: "Light reasoning (~2k tokens)",', 'low: "轻度推理(约 2k token)",'],
311
- ['medium: "Moderate reasoning (~8k tokens)",', 'medium: "中等推理(约 8k token)",'],
312
- ['high: "Deep reasoning (~16k tokens)",', 'high: "深度推理(约 16k token)",'],
313
- ['xhigh: "Extra-high reasoning (~32k tokens)",', 'xhigh: "超深推理(约 32k token)",'],
314
- ['max: "Maximum reasoning",', 'max: "最大推理",'],
315
- ['ask: "Ask",', 'ask: "每次询问",'],
316
- ['always: "Always trust",', 'always: "始终信任",'],
317
- ['never: "Never trust",', 'never: "从不信任",'],
318
- ['new SelectSubmenu("Theme", "Select a theme, or choose Automatic to follow terminal appearance."', 'new SelectSubmenu("主题", "选择一个主题,或选「自动」跟随终端明暗。"'],
319
- ['label: " Automatic",', 'label: " 自动",'],
320
- ['description: "Use separate themes for light and dark terminal appearance",', 'description: "终端明暗各用一套主题",'],
321
- ['theme.fg("accent", "Automatic Theme")', 'theme.fg("accent", "自动主题")'],
322
- ['theme.fg("muted", "Choose themes for terminal light and dark appearance.")', 'theme.fg("muted", "分别为终端的浅色/深色外观选主题。")'],
323
- ['theme.fg("muted", "Light/dark detection requires terminal support.")', 'theme.fg("muted", "明暗检测需要终端支持。")'],
324
- ['label: "Light theme",', 'label: "浅色主题",'],
325
- ['description: "Theme to use in automatic mode when the terminal is light",', 'description: "自动模式下终端为浅色时使用的主题",'],
326
- ['label: "Dark theme",', 'label: "深色主题",'],
327
- ['description: "Theme to use in automatic mode when the terminal is dark",', 'description: "自动模式下终端为深色时使用的主题",'],
328
- ['label: "Apply",', 'label: "应用",'],
329
- ['description: "Save and go back",', 'description: "保存并返回",'],
330
- ['label: "Change mode",', 'label: "切换模式",'],
331
- ['description: "Switch to one theme for light and dark",', 'description: "明暗共用一个主题",'],
332
- ['label: "Auto-compact",', 'label: "自动压缩上下文",'],
333
- ['description: "Automatically compact context when it gets too large",', 'description: "上下文过长时自动压缩",'],
334
- ['label: "Steering mode",', 'label: "插话模式",'],
335
- ['description: "Enter while streaming queues steering messages. \'one-at-a-time\': deliver one, wait for response. \'all\': deliver all at once.",', 'description: "回复进行中按 Enter 发送插话。one-at-a-time:发一条等回复;all:一次全发。",'],
336
- ['label: "Follow-up mode",', 'label: "追问模式",'],
337
- ['description: `${followUpKey} queues follow-up messages until agent stops. \'one-at-a-time\': deliver one, wait for response. \'all\': deliver all at once.`,', 'description: `${followUpKey} 把追问排队到本轮结束再发。one-at-a-time:发一条等回复;all:一次全发。`,'],
338
- ['label: "Transport",', 'label: "传输方式",'],
339
- ['description: "Preferred transport for providers that support multiple transports",', 'description: "支持多种传输方式的渠道优先用哪种",'],
340
- ['label: "Cache warming",', 'label: "缓存预热",'],
341
- ['description: "off; streaming while the agent runs; idle also between runs while continuation stays profitable",', 'description: "off:不预热;streaming:回复进行中保温;idle:两轮之间只要续聊划算也保温",'],
342
- ['label: "HTTP idle timeout",', 'label: "HTTP 空闲超时",'],
343
- ['description: "Maximum idle gap while waiting for HTTP headers or body chunks. Disable for local models that pause longer than five minutes.",', 'description: "等待响应头或数据块的最长空闲间隔;本地模型停顿超过五分钟可关闭。",'],
344
- ['label: "Hide thinking",', 'label: "隐藏思考过程",'],
345
- ['description: "Hide thinking blocks in assistant responses",', 'description: "不显示回复里的思考块",'],
346
- ['label: "Mermaid diagrams",', 'label: "Mermaid 图表",'],
347
- ['description: "Render Mermaid code blocks as Unicode diagrams",', 'description: "把 Mermaid 代码块渲染成字符图",'],
348
- ['label: "Cache miss notices",', 'label: "缓存未命中提示",'],
349
- ['description: "Show transcript notices for cache costs and provider recovery diagnostics",', 'description: "提示缓存开销与渠道恢复诊断",'],
350
- ['label: "Collapse changelog",', 'label: "折叠更新日志",'],
351
- ['description: "Show condensed changelog after updates",', 'description: "更新后只显示精简的更新日志",'],
352
- ['label: "Quiet startup",', 'label: "安静启动",'],
353
- ['description: "Disable verbose printing at startup",', 'description: "启动时不打印冗长信息",'],
354
- ['label: "Install telemetry",', 'label: "安装统计",'],
355
- ['description: "Send an anonymous version/update ping after changelog-detected updates",', 'description: "更新后匿名上报一次版本信息",'],
356
- ['label: "Default project trust",', 'label: "默认项目信任",'],
357
- ['description: "Fallback behavior when no extension or saved trust decision decides project trust",', 'description: "没有扩展或已保存决定时,项目信任的兜底行为",'],
358
- ['label: "Double-escape action",', 'label: "双击 Esc 动作",'],
359
- ['description: "Action when pressing Escape twice with empty editor",', 'description: "输入框为空时连按两次 Esc 的动作",'],
360
- ['label: "Tree filter mode",', 'label: "会话树筛选",'],
361
- ['description: "Default filter when opening /tree",', 'description: "打开 /tree 时的默认筛选",'],
362
- ['label: "Warnings",', 'label: "警告提示",'],
363
- ['description: "Enable or disable individual warnings",', 'description: "逐项开关各类警告",'],
364
- ['label: "Default thinking level per model",', 'label: "各模型默认推理强度",'],
365
- ['description: `Override the default thinking level for specific models. ${cycleThinkingKey} cycles in-session.`,', 'description: `按模型覆盖默认推理强度;会话内用 ${cycleThinkingKey} 切换。`,'],
366
- ['description: "Select a model to configure",', 'description: "选择要设置的模型",'],
367
- ['label: "No models available",', 'label: "没有可用模型",'],
368
- ['description: "Log in to a provider or configure an API key first",', 'description: "请先登录",'],
369
- ['description: "Select default thinking level for this model",', 'description: "选择这个模型的默认推理强度",'],
370
- ['label: " (clear override)",', 'label: " (清除覆盖)",'],
371
- ['description: `Revert to global default (${config.thinkingLevel})`,', 'description: `恢复为全局默认(${config.thinkingLevel})`,'],
372
- ['label: "TUI mode",', 'label: "界面模式",'],
373
- ['description: "Interface layout; fullscreen mode is experimental",', 'description: "界面布局;全屏模式为实验功能",'],
374
- ['label: "Fullscreen exit output",', 'label: "退出全屏时的输出",'],
375
- ['description: "Print the transcript or only a session resume hint when exiting fullscreen mode",', 'description: "退出全屏时打印完整对话,还是只留一条恢复会话提示",'],
376
- ['label: "Fullscreen scrollbar",', 'label: "全屏滚动条",'],
377
- ['description: "Scrollbar behavior in fullscreen mode; has no effect in regular mode",', 'description: "全屏模式下的滚动条行为;普通模式无效",'],
378
- ['label: "Fullscreen copy on select",', 'label: "全屏选中即复制",'],
379
- ['description: "Automatically copy selected text in fullscreen mode; disable to copy selections with Ctrl+X",', 'description: "全屏模式下选中文字自动复制;关闭后用 Ctrl+X 复制",'],
380
- ['label: "Theme",', 'label: "主题",'],
381
- ['description: "Color theme for the interface",', 'description: "界面配色主题",'],
242
+ ['off: "No reasoning",', 'off: process.env.U1S1_LANG === "en" ? "No reasoning" : "不推理",'],
243
+ ['minimal: "Very brief reasoning (~1k tokens)",', 'minimal: process.env.U1S1_LANG === "en" ? "Very brief reasoning (~1k tokens)" : "极简推理(约 1k token)",'],
244
+ ['low: "Light reasoning (~2k tokens)",', 'low: process.env.U1S1_LANG === "en" ? "Light reasoning (~2k tokens)" : "轻度推理(约 2k token)",'],
245
+ ['medium: "Moderate reasoning (~8k tokens)",', 'medium: process.env.U1S1_LANG === "en" ? "Moderate reasoning (~8k tokens)" : "中等推理(约 8k token)",'],
246
+ ['high: "Deep reasoning (~16k tokens)",', 'high: process.env.U1S1_LANG === "en" ? "Deep reasoning (~16k tokens)" : "深度推理(约 16k token)",'],
247
+ ['xhigh: "Extra-high reasoning (~32k tokens)",', 'xhigh: process.env.U1S1_LANG === "en" ? "Extra-high reasoning (~32k tokens)" : "超深推理(约 32k token)",'],
248
+ ['max: "Maximum reasoning",', 'max: process.env.U1S1_LANG === "en" ? "Maximum reasoning" : "最大推理",'],
249
+ ['ask: "Ask",', 'ask: process.env.U1S1_LANG === "en" ? "Ask" : "每次询问",'],
250
+ ['always: "Always trust",', 'always: process.env.U1S1_LANG === "en" ? "Always trust" : "始终信任",'],
251
+ ['never: "Never trust",', 'never: process.env.U1S1_LANG === "en" ? "Never trust" : "从不信任",'],
252
+ ['new SelectSubmenu("Theme", "Select a theme, or choose automatic to follow terminal appearance."', 'new SelectSubmenu(process.env.U1S1_LANG === "en" ? "Theme" : "主题", process.env.U1S1_LANG === "en" ? "Select a theme, or choose automatic to follow terminal appearance." : "选择一个主题,或选「自动」跟随终端明暗。"'],
253
+ ['{ description: "Theme created from your terminal\'s colors" }', '{ description: process.env.U1S1_LANG === "en" ? "Theme created from your terminal\'s colors" : "按终端自身配色生成的主题" }'],
254
+ ['label: " automatic",', 'label: process.env.U1S1_LANG === "en" ? " automatic" : " 自动",'],
255
+ ['description: "Use separate themes for light and dark terminal appearance",', 'description: process.env.U1S1_LANG === "en" ? "Use separate themes for light and dark terminal appearance" : "终端明暗各用一套主题",'],
256
+ ['theme.fg("accent", "Automatic Theme")', 'theme.fg("accent", process.env.U1S1_LANG === "en" ? "Automatic Theme" : "自动主题")'],
257
+ ['theme.fg("muted", "Choose themes for terminal light and dark appearance.")', 'theme.fg("muted", process.env.U1S1_LANG === "en" ? "Choose themes for terminal light and dark appearance." : "分别为终端的浅色/深色外观选主题。")'],
258
+ ['theme.fg("muted", "Light/dark detection requires terminal support.")', 'theme.fg("muted", process.env.U1S1_LANG === "en" ? "Light/dark detection requires terminal support." : "明暗检测需要终端支持。")'],
259
+ ['label: "Light theme",', 'label: process.env.U1S1_LANG === "en" ? "Light theme" : "浅色主题",'],
260
+ ['description: "Theme to use in automatic mode when the terminal is light",', 'description: process.env.U1S1_LANG === "en" ? "Theme to use in automatic mode when the terminal is light" : "自动模式下终端为浅色时使用的主题",'],
261
+ ['label: "Dark theme",', 'label: process.env.U1S1_LANG === "en" ? "Dark theme" : "深色主题",'],
262
+ ['description: "Theme to use in automatic mode when the terminal is dark",', 'description: process.env.U1S1_LANG === "en" ? "Theme to use in automatic mode when the terminal is dark" : "自动模式下终端为深色时使用的主题",'],
263
+ ['label: "Apply",', 'label: process.env.U1S1_LANG === "en" ? "Apply" : "应用",'],
264
+ ['description: "Save and go back",', 'description: process.env.U1S1_LANG === "en" ? "Save and go back" : "保存并返回",'],
265
+ ['label: "Change mode",', 'label: process.env.U1S1_LANG === "en" ? "Change mode" : "切换模式",'],
266
+ ['description: "Switch to one theme for light and dark",', 'description: process.env.U1S1_LANG === "en" ? "Switch to one theme for light and dark" : "明暗共用一个主题",'],
267
+ ['label: "Auto-compact",', 'label: process.env.U1S1_LANG === "en" ? "Auto-compact" : "自动压缩上下文",'],
268
+ ['description: "Automatically compact context when it gets too large",', 'description: process.env.U1S1_LANG === "en" ? "Automatically compact context when it gets too large" : "上下文过长时自动压缩",'],
269
+ ['label: "Steering mode",', 'label: process.env.U1S1_LANG === "en" ? "Steering mode" : "插话模式",'],
270
+ ['description: "Enter while streaming queues steering messages. \'one-at-a-time\': deliver one, wait for response. \'all\': deliver all at once.",', 'description: process.env.U1S1_LANG === "en" ? "Enter while streaming queues steering messages. \'one-at-a-time\': deliver one, wait for response. \'all\': deliver all at once." : "回复进行中按 Enter 发送插话。one-at-a-time:发一条等回复;all:一次全发。",'],
271
+ ['label: "Follow-up mode",', 'label: process.env.U1S1_LANG === "en" ? "Follow-up mode" : "追问模式",'],
272
+ ['description: `${followUpKey} queues follow-up messages until agent stops. \'one-at-a-time\': deliver one, wait for response. \'all\': deliver all at once.`,', 'description: process.env.U1S1_LANG === "en" ? `${followUpKey} queues follow-up messages until agent stops. \'one-at-a-time\': deliver one, wait for response. \'all\': deliver all at once.` : `${followUpKey} 把追问排队到本轮结束再发。one-at-a-time:发一条等回复;all:一次全发。`,'],
273
+ ['label: "Transport",', 'label: process.env.U1S1_LANG === "en" ? "Transport" : "传输方式",'],
274
+ ['description: "Preferred transport for providers that support multiple transports",', 'description: process.env.U1S1_LANG === "en" ? "Preferred transport for providers that support multiple transports" : "支持多种传输方式的渠道优先用哪种",'],
275
+ ['label: "Cache warming",', 'label: process.env.U1S1_LANG === "en" ? "Cache warming" : "缓存预热",'],
276
+ ['description: "off; streaming while the agent runs; idle also between runs while continuation stays profitable",', 'description: process.env.U1S1_LANG === "en" ? "off; streaming while the agent runs; idle also between runs while continuation stays profitable" : "off:不预热;streaming:回复进行中保温;idle:两轮之间只要续聊划算也保温",'],
277
+ ['label: "HTTP idle timeout",', 'label: process.env.U1S1_LANG === "en" ? "HTTP idle timeout" : "HTTP 空闲超时",'],
278
+ ['description: "Maximum idle gap while waiting for HTTP headers or body chunks. Disable for local models that pause longer than five minutes.",', 'description: process.env.U1S1_LANG === "en" ? "Maximum idle gap while waiting for HTTP headers or body chunks. Disable for local models that pause longer than five minutes." : "等待响应头或数据块的最长空闲间隔;本地模型停顿超过五分钟可关闭。",'],
279
+ ['label: "Hide thinking",', 'label: process.env.U1S1_LANG === "en" ? "Hide thinking" : "隐藏思考过程",'],
280
+ ['description: "Hide thinking blocks in assistant responses",', 'description: process.env.U1S1_LANG === "en" ? "Hide thinking blocks in assistant responses" : "不显示回复里的思考块",'],
281
+ ['label: "Mermaid diagrams",', 'label: process.env.U1S1_LANG === "en" ? "Mermaid diagrams" : "Mermaid 图表",'],
282
+ ['description: "Render Mermaid code blocks as Unicode diagrams",', 'description: process.env.U1S1_LANG === "en" ? "Render Mermaid code blocks as Unicode diagrams" : "把 Mermaid 代码块渲染成字符图",'],
283
+ ['label: "Cache miss notices",', 'label: process.env.U1S1_LANG === "en" ? "Cache miss notices" : "缓存未命中提示",'],
284
+ ['description: "Show transcript notices for cache costs and provider recovery diagnostics",', 'description: process.env.U1S1_LANG === "en" ? "Show transcript notices for cache costs and provider recovery diagnostics" : "提示缓存开销与渠道恢复诊断",'],
285
+ ['label: "Collapse changelog",', 'label: process.env.U1S1_LANG === "en" ? "Collapse changelog" : "折叠更新日志",'],
286
+ ['description: "Show condensed changelog after updates",', 'description: process.env.U1S1_LANG === "en" ? "Show condensed changelog after updates" : "更新后只显示精简的更新日志",'],
287
+ ['label: "Quiet startup",', 'label: process.env.U1S1_LANG === "en" ? "Quiet startup" : "安静启动",'],
288
+ ['description: "Disable verbose printing at startup (header: keep only the startup header)",', 'description: process.env.U1S1_LANG === "en" ? "Disable verbose printing at startup (header: keep only the startup header)" : "启动时不打印冗长信息(header:只保留启动标题)",'],
289
+ ['label: "Install telemetry",', 'label: process.env.U1S1_LANG === "en" ? "Install telemetry" : "安装统计",'],
290
+ ['description: "Send an anonymous version/update ping after changelog-detected updates",', 'description: process.env.U1S1_LANG === "en" ? "Send an anonymous version/update ping after changelog-detected updates" : "更新后匿名上报一次版本信息",'],
291
+ ['label: "Default project trust",', 'label: process.env.U1S1_LANG === "en" ? "Default project trust" : "默认项目信任",'],
292
+ ['description: "Fallback behavior when no extension or saved trust decision decides project trust",', 'description: process.env.U1S1_LANG === "en" ? "Fallback behavior when no extension or saved trust decision decides project trust" : "没有扩展或已保存决定时,项目信任的兜底行为",'],
293
+ ['label: "Double-escape action",', 'label: process.env.U1S1_LANG === "en" ? "Double-escape action" : "双击 Esc 动作",'],
294
+ ['description: "Action when pressing Escape twice with empty editor",', 'description: process.env.U1S1_LANG === "en" ? "Action when pressing Escape twice with empty editor" : "输入框为空时连按两次 Esc 的动作",'],
295
+ ['label: "Tree filter mode",', 'label: process.env.U1S1_LANG === "en" ? "Tree filter mode" : "会话树筛选",'],
296
+ ['description: "Default filter when opening /tree",', 'description: process.env.U1S1_LANG === "en" ? "Default filter when opening /tree" : "打开 /tree 时的默认筛选",'],
297
+ ['label: "Warnings",', 'label: process.env.U1S1_LANG === "en" ? "Warnings" : "警告提示",'],
298
+ ['description: "Enable or disable individual warnings",', 'description: process.env.U1S1_LANG === "en" ? "Enable or disable individual warnings" : "逐项开关各类警告",'],
299
+ ['label: "Default thinking level per model",', 'label: process.env.U1S1_LANG === "en" ? "Default thinking level per model" : "各模型默认推理强度",'],
300
+ ['description: `Override the default thinking level for specific models. ${cycleThinkingKey} cycles in-session.`,', 'description: process.env.U1S1_LANG === "en" ? `Override the default thinking level for specific models. ${cycleThinkingKey} cycles in-session.` : `按模型覆盖默认推理强度;会话内用 ${cycleThinkingKey} 切换。`,'],
301
+ ['description: "Select a model to configure",', 'description: process.env.U1S1_LANG === "en" ? "Select a model to configure" : "选择要设置的模型",'],
302
+ ['label: "No models available",', 'label: process.env.U1S1_LANG === "en" ? "No models available" : "没有可用模型",'],
303
+ // u1s1 的模型来自账号登录,不提 pi 的渠道登录 / API key
304
+ ['description: "Log in to a provider or configure an API key first",', 'description: process.env.U1S1_LANG === "en" ? "Sign in first" : "请先登录",'],
305
+ ['description: "Select default thinking level for this model",', 'description: process.env.U1S1_LANG === "en" ? "Select default thinking level for this model" : "选择这个模型的默认推理强度",'],
306
+ ['label: " (clear override)",', 'label: process.env.U1S1_LANG === "en" ? " (clear override)" : " (清除覆盖)",'],
307
+ ['description: `Revert to global default (${config.thinkingLevel})`,', 'description: process.env.U1S1_LANG === "en" ? `Revert to global default (${config.thinkingLevel})` : `恢复为全局默认(${config.thinkingLevel})`,'],
308
+ ['label: "TUI mode",', 'label: process.env.U1S1_LANG === "en" ? "TUI mode" : "界面模式",'],
309
+ ['description: "Interface layout; regular mode uses the terminal\'s normal scrollback",', 'description: process.env.U1S1_LANG === "en" ? "Interface layout; regular mode uses the terminal\'s normal scrollback" : "界面布局;regular 模式保留终端原生的滚动记录",'],
310
+ ['label: "Fullscreen exit output",', 'label: process.env.U1S1_LANG === "en" ? "Fullscreen exit output" : "退出全屏时的输出",'],
311
+ ['description: "Print the transcript or only a session resume hint when exiting fullscreen mode",', 'description: process.env.U1S1_LANG === "en" ? "Print the transcript or only a session resume hint when exiting fullscreen mode" : "退出全屏时打印完整对话,还是只留一条恢复会话提示",'],
312
+ ['label: "Fullscreen scrollbar",', 'label: process.env.U1S1_LANG === "en" ? "Fullscreen scrollbar" : "全屏滚动条",'],
313
+ ['description: "Scrollbar behavior in fullscreen mode; has no effect in regular mode",', 'description: process.env.U1S1_LANG === "en" ? "Scrollbar behavior in fullscreen mode; has no effect in regular mode" : "全屏模式下的滚动条行为;普通模式无效",'],
314
+ ['label: "Fullscreen copy on select",', 'label: process.env.U1S1_LANG === "en" ? "Fullscreen copy on select" : "全屏选中即复制",'],
315
+ ['description: "Automatically copy selected text in fullscreen mode; disable to copy selections with Ctrl+X",', 'description: process.env.U1S1_LANG === "en" ? "Automatically copy selected text in fullscreen mode; disable to copy selections with Ctrl+X" : "全屏模式下选中文字自动复制;关闭后用 Ctrl+X 复制",'],
316
+ ['label: "Fullscreen wheel scrolling",', 'label: process.env.U1S1_LANG === "en" ? "Fullscreen wheel scrolling" : "全屏滚轮速度",'],
317
+ ['description: "Lines per mouse-wheel event in fullscreen mode; \'auto\' speeds up fast wheel spins where the terminal does not",', 'description: process.env.U1S1_LANG === "en" ? "Lines per mouse-wheel event in fullscreen mode; \'auto\' speeds up fast wheel spins where the terminal does not" : "全屏模式下滚轮每格滚动的行数;auto 在快速滚动时自动加速",'],
318
+ ['label: "Theme",', 'label: process.env.U1S1_LANG === "en" ? "Theme" : "主题",'],
319
+ ['description: "Color theme for the interface",', 'description: process.env.U1S1_LANG === "en" ? "Color theme for the interface" : "界面配色主题",'],
382
320
  ];
383
321
 
384
322
  // /bug 入口改指向 u1s1 的 /feedback(dist/modes/interactive/interactive-mode.js):
385
323
  // 手动输入 /bug、启动时的上次崩溃提示、崩溃退出说明、报错后的一次性提示,都不再把用户引到 Pi 上游。
386
324
  const BUG_REPORT_REPLACEMENTS = [
387
- ["await this.handleBugCommand(hint ? hint : undefined);", 'this.showStatus("反馈问题请用 /feedback(会附带诊断信息建工单)");'],
388
- ["this.showWarning(`${APP_NAME} crashed on ${when} (${crash.message}). Run /bug to report it; the crash details are attached automatically.`);", "this.showWarning(`u1s1 上次异常退出(${when}:${crash.message})。反复出现请用 /feedback 反馈,并写上发生时间。`);"],
389
- ["return `To report this crash: ${resume} run /bug. The crash details are attached automatically.`;", 'return "u1s1 异常退出了。反复出现请运行 u1s1 feedback 反馈。";'],
390
- ["`If this looks like a ${APP_NAME} bug, /bug sends a report to the developers.`", '"如果这看起来是 u1s1 自身的问题,可以用 /feedback 反馈。"'],
325
+ ["await this.handleBugCommand(hint ? hint : undefined);", 'this.showStatus(process.env.U1S1_LANG === "en" ? "To report a problem, use /feedback (it attaches diagnostics and opens a support ticket)" : "反馈问题请用 /feedback(会附带诊断信息建工单)");'],
326
+ ["this.showWarning(`${APP_NAME} crashed on ${when} (${crash.message}). Run /bug to report it; the crash details are attached automatically.`);", 'this.showWarning(process.env.U1S1_LANG === "en" ? `u1s1 exited unexpectedly last time (${when}: ${crash.message}). If it keeps happening, report it with /feedback and mention when it happened.` : `u1s1 上次异常退出(${when}:${crash.message})。反复出现请用 /feedback 反馈,并写上发生时间。`);'],
327
+ ["return `To report this crash: ${resume} run /bug. The crash details are attached automatically.`;", 'return process.env.U1S1_LANG === "en" ? "u1s1 exited unexpectedly. If it keeps happening, run u1s1 feedback to report it." : "u1s1 异常退出了。反复出现请运行 u1s1 feedback 反馈。";'],
328
+ ["`If this looks like a ${APP_NAME} bug, /bug sends a report to the developers.`", 'process.env.U1S1_LANG === "en" ? "If this looks like a u1s1 bug, report it with /feedback." : "如果这看起来是 u1s1 自身的问题,可以用 /feedback 反馈。"'],
329
+ ];
330
+
331
+ // /login 去掉 Pi 自家渠道(dist/modes/interactive/interactive-mode.js):
332
+ // Radius(Pi 开发方的付费服务,登录后还会往 mcp.json 写它的 MCP)和 OpenAI 的「Sign in with ChatGPT」。
333
+ // u1s1 用户在 /login 里点进去只会配上与 u1s1 账号无关的第三方渠道;菜单、/login radius、/login openai 都走这个列表。
334
+ const LOGIN_PROVIDER_REPLACEMENTS = [
335
+ [
336
+ "const authStatus = this.session.modelRuntime.getProviderAuthStatus(provider.id);",
337
+ 'if (provider.id === "radius") continue;\n const authStatus = this.session.modelRuntime.getProviderAuthStatus(provider.id);',
338
+ ],
339
+ ['if ((!authType || authType === "oauth") && provider.auth.oauth) {', 'if ((!authType || authType === "oauth") && provider.auth.oauth && provider.id !== "openai") {'],
340
+ ];
341
+
342
+ // Pi 0.99 起内置的 MCP 扩展(dist/extensions/mcp/):/mcp 管理面板、登录、状态与提示。
343
+ // 只换给人看的文案;mcp_servers 系统提示段、工具描述等给模型看的保持英文。
344
+ // "Sign-in cancelled." 既是返回值又被 === 比较,两处必须一起换。
345
+ const MCP_TEXT_REPLACEMENTS = [
346
+ ['codemode: "called from codemode scripts, which find them with searchTools()",', 'codemode: process.env.U1S1_LANG === "en" ? "called from codemode scripts, which find them with searchTools()" : "由 codemode 脚本调用(脚本用 searchTools() 查找)",'],
347
+ ['deferred: "not declared until tool_search loads them, then called directly; no codemode needed",', 'deferred: process.env.U1S1_LANG === "en" ? "not declared until tool_search loads them, then called directly; no codemode needed" : "先不告诉模型,tool_search 找到后再直接调用;不需要 codemode",'],
348
+ ['direct: "declared to the model like built-in tools",', 'direct: process.env.U1S1_LANG === "en" ? "declared to the model like built-in tools" : "像内置工具一样直接提供给模型",'],
349
+ ['return "disabled";', 'return process.env.U1S1_LANG === "en" ? "disabled" : "已停用";'],
350
+ ['return "starting";', 'return process.env.U1S1_LANG === "en" ? "starting" : "启动中";'],
351
+ ['return "needs sign-in";', 'return process.env.U1S1_LANG === "en" ? "needs sign-in" : "需要登录";'],
352
+ ['return withError ? `failed: ${firstLine(connection.error ?? "unknown error")}` : "failed";', 'return withError ? (process.env.U1S1_LANG === "en" ? `failed: ${firstLine(connection.error ?? "unknown error")}` : `连接失败:${firstLine(connection.error ?? "未知错误")}`) : (process.env.U1S1_LANG === "en" ? "failed" : "连接失败");'],
353
+ ['const resourceCount = count > 0 ? ` · ${count} resource${count === 1 ? "" : "s"}` : "";', 'const resourceCount = count > 0 ? (process.env.U1S1_LANG === "en" ? ` · ${count} resource${count === 1 ? "" : "s"}` : ` · ${count} 个资源`) : "";'],
354
+ ['return `connected · ${tools.length} tool${tools.length === 1 ? "" : "s"}${resourceCount}`;', 'return process.env.U1S1_LANG === "en" ? `connected · ${tools.length} tool${tools.length === 1 ? "" : "s"}${resourceCount}` : `已连接 · ${tools.length} 个工具${resourceCount}`;'],
355
+ ['return "connecting…";', 'return process.env.U1S1_LANG === "en" ? "connecting…" : "连接中…";'],
356
+ ['const MCP_USAGE = "Usage: /mcp, /mcp login [server], /mcp logout [server], /mcp reconnect [server]";', 'const MCP_USAGE = process.env.U1S1_LANG === "en" ? "Usage: /mcp, /mcp login [server], /mcp logout [server], /mcp reconnect [server]" : "用法:/mcp、/mcp login [服务名]、/mcp logout [服务名]、/mcp reconnect [服务名]";'],
357
+ ['ctx.ui.notify(`MCP tools are only reachable from the codemode or tool_search tool, but neither is active${reason}; they cannot be called.`, "warning");', 'ctx.ui.notify(process.env.U1S1_LANG === "en" ? `MCP tools are only reachable from the codemode or tool_search tool, but neither is active${reason}; they cannot be called.` : `MCP 工具只能经 codemode 或 tool_search 调用,但两者都没开启${reason},暂时用不了。`, "warning");'],
358
+ { from: '`config: ${error}`', to: '(process.env.U1S1_LANG === "en" ? `config: ${error}` : `配置错误:${error}`)', expected: 2 },
359
+ ['ctx.ui.notify(`MCP servers need attention:\\n${lines.map((line) => ` ${line}`).join("\\n")}\\nRun /mcp to fix.`, "warning");', 'ctx.ui.notify(process.env.U1S1_LANG === "en" ? `MCP servers need attention:\\n${lines.map((line) => ` ${line}`).join("\\n")}\\nRun /mcp to fix.` : `MCP 服务需要处理:\\n${lines.map((line) => ` ${line}`).join("\\n")}\\n输入 /mcp 处理。`, "warning");'],
360
+ ['return `Could not update ${server.entry.source}: ${errorMessage(error)}`;', 'return process.env.U1S1_LANG === "en" ? `Could not update ${server.entry.source}: ${errorMessage(error)}` : `无法写入 ${server.entry.source}:${errorMessage(error)}`;'],
361
+ ['return `MCP server "${server.entry.name}" does not use OAuth.`;', 'return process.env.U1S1_LANG === "en" ? `MCP server "${server.entry.name}" does not use OAuth.` : `MCP 服务「${server.entry.name}」不用 OAuth 登录。`;'],
362
+ ['return "Sign-in cancelled.";', 'return process.env.U1S1_LANG === "en" ? "Sign-in cancelled." : "已取消登录。";'],
363
+ ['failure === "Sign-in cancelled." ? "info" : "error"', 'failure === (process.env.U1S1_LANG === "en" ? "Sign-in cancelled." : "已取消登录。") ? "info" : "error"'],
364
+ ['return `Sign-in failed: ${errorMessage(error)}`;', 'return process.env.U1S1_LANG === "en" ? `Sign-in failed: ${errorMessage(error)}` : `登录失败:${errorMessage(error)}`;'],
365
+ ['return `Signed in, but ${errorMessage(error)}`;', 'return process.env.U1S1_LANG === "en" ? `Signed in, but ${errorMessage(error)}` : `已登录,但连接失败:${errorMessage(error)}`;'],
366
+ ['return `MCP server "${server.entry.name}" is disabled.`;', 'return process.env.U1S1_LANG === "en" ? `MCP server "${server.entry.name}" is disabled.` : `MCP 服务「${server.entry.name}」已停用。`;'],
367
+ { from: '`overridden: ${line}`', to: '(process.env.U1S1_LANG === "en" ? `overridden: ${line}` : `被覆盖:${line}`)', expected: 2 },
368
+ // 面板脚注经 keyHint 走按键脚注的对照表;close/select/save 已在表里,这两个表里没有,就地换
369
+ ['confirmLabel: "manage",', 'confirmLabel: process.env.U1S1_LANG === "en" ? "manage" : "管理",'],
370
+ { from: 'Label: "back",', to: 'Label: process.env.U1S1_LANG === "en" ? "back" : "返回",', expected: 5 },
371
+ ['title: "MCP servers",', 'title: process.env.U1S1_LANG === "en" ? "MCP servers" : "MCP 服务",'],
372
+ // 空列表引导到 u1s1 mcp add(CLI 命令),英文同义
373
+ { from: '`No MCP servers configured. Add them to ${resolve(getAgentDir(), "mcp.json")} or .pi/mcp.json.`', to: '(process.env.U1S1_LANG === "en" ? `No MCP servers configured. Add one with u1s1 mcp add, or edit ${resolve(getAgentDir(), "mcp.json")}.` : `还没有配置 MCP 服务。用 u1s1 mcp add 添加,或直接编辑 ${resolve(getAgentDir(), "mcp.json")}。`)', expected: 2 },
374
+ ['empty: "This server is no longer configured.",', 'empty: process.env.U1S1_LANG === "en" ? "This server is no longer configured." : "这个服务已不在配置里了。",'],
375
+ ['? "for this session"', '? (process.env.U1S1_LANG === "en" ? "for this session" : "仅本次会话")'],
376
+ ['? `saved to the ${entry.scope} mcp.json`', '? (process.env.U1S1_LANG === "en" ? `saved to the ${entry.scope} mcp.json` : `保存到 mcp.json(${entry.scope})`)'],
377
+ [': "saved to mcp.json";', ': (process.env.U1S1_LANG === "en" ? "saved to mcp.json" : "保存到 mcp.json");'],
378
+ ['label: "Enable", description: saved', 'label: process.env.U1S1_LANG === "en" ? "Enable" : "启用", description: saved'],
379
+ ['label: "Sign in", description: "opens the browser"', 'label: process.env.U1S1_LANG === "en" ? "Sign in" : "登录", description: process.env.U1S1_LANG === "en" ? "opens the browser" : "会打开浏览器"'],
380
+ ['label: "Tools", description: `${connection.tools.length} offered`', 'label: process.env.U1S1_LANG === "en" ? "Tools" : "工具", description: process.env.U1S1_LANG === "en" ? `${connection.tools.length} offered` : `共 ${connection.tools.length} 个`'],
381
+ ['label: "Reconnect" }', 'label: process.env.U1S1_LANG === "en" ? "Reconnect" : "重新连接" }'],
382
+ ['label: "Sign out", description: "deletes the stored credentials"', 'label: process.env.U1S1_LANG === "en" ? "Sign out" : "退出登录", description: process.env.U1S1_LANG === "en" ? "deletes the stored credentials" : "删除已保存的登录凭据"'],
383
+ ['label: "Exposure", description: exposureOf(entry)', 'label: process.env.U1S1_LANG === "en" ? "Exposure" : "暴露方式", description: exposureOf(entry)'],
384
+ ['label: "Disable", description: saved', 'label: process.env.U1S1_LANG === "en" ? "Disable" : "停用", description: saved'],
385
+ ['`State: ${describeState(server, false)}`', '(process.env.U1S1_LANG === "en" ? `State: ${describeState(server, false)}` : `状态:${describeState(server, false)}`)'],
386
+ { from: '`MCP server ${name}`', to: '(process.env.U1S1_LANG === "en" ? `MCP server ${name}` : `MCP 服务 ${name}`)', expected: 3 },
387
+ ['title: `Tools of ${server.entry.name}`,', 'title: process.env.U1S1_LANG === "en" ? `Tools of ${server.entry.name}` : `${server.entry.name} 的工具`,'],
388
+ ['details: `Exposure ${exposure}: ${exposure === "hidden" ? "unreachable" : EXPOSURE_DESCRIPTIONS[exposure]}${overridden ? "\\nSome tools override it with toolExposure." : ""}`,', 'details: process.env.U1S1_LANG === "en" ? `Exposure ${exposure}: ${exposure === "hidden" ? "unreachable" : EXPOSURE_DESCRIPTIONS[exposure]}${overridden ? "\\nSome tools override it with toolExposure." : ""}` : `暴露方式 ${exposure}:${exposure === "hidden" ? "不可调用" : EXPOSURE_DESCRIPTIONS[exposure]}${overridden ? "\\n部分工具用 toolExposure 单独设置了。" : ""}`,'],
389
+ ['empty: "The server offers no tools.",', 'empty: process.env.U1S1_LANG === "en" ? "The server offers no tools." : "这个服务没有提供工具。",'],
390
+ ['title: `Exposure of ${server.entry.name}`,', 'title: process.env.U1S1_LANG === "en" ? `Exposure of ${server.entry.name}` : `${server.entry.name} 的暴露方式`,'],
391
+ ['? `Applies to this session; the server is registered by ${server.entry.source}.`', '? (process.env.U1S1_LANG === "en" ? `Applies to this session; the server is registered by ${server.entry.source}.` : `仅本次会话生效;这个服务由 ${server.entry.source} 注册。`)'],
392
+ [': `Saved to ${server.entry.source}.`,', ': (process.env.U1S1_LANG === "en" ? `Saved to ${server.entry.source}.` : `保存到 ${server.entry.source}。`),'],
393
+ ['const title = `Sign in to ${name}`;', 'const title = process.env.U1S1_LANG === "en" ? `Sign in to ${name}` : `登录 ${name}`;'],
394
+ ['ui.status(title, "Contacting the authorization server…");', 'ui.status(title, process.env.U1S1_LANG === "en" ? "Contacting the authorization server…" : "正在联系授权服务器…");'],
395
+ ['ui.status(title, "Connecting…");', 'ui.status(title, process.env.U1S1_LANG === "en" ? "Connecting…" : "连接中…");'],
396
+ ['"Reconnecting…"', '(process.env.U1S1_LANG === "en" ? "Reconnecting…" : "重新连接中…")'],
397
+ ['action === "enable" ? "Connecting…" : "Disconnecting…"', 'action === "enable" ? (process.env.U1S1_LANG === "en" ? "Connecting…" : "连接中…") : (process.env.U1S1_LANG === "en" ? "Disconnecting…" : "断开中…")'],
398
+ ['ctx.ui.notify(`No MCP server named "${name}".`, "error");', 'ctx.ui.notify(process.env.U1S1_LANG === "en" ? `No MCP server named "${name}".` : `没有叫「${name}」的 MCP 服务。`, "error");'],
399
+ ['ctx.ui.select("MCP server", ', 'ctx.ui.select(process.env.U1S1_LANG === "en" ? "MCP server" : "选择 MCP 服务", '],
400
+ ['none: "No enabled MCP server uses OAuth. Only HTTP servers without an Authorization header do.",', 'none: process.env.U1S1_LANG === "en" ? "No enabled MCP server uses OAuth. Only HTTP servers without an Authorization header do." : "没有用 OAuth 登录的已启用 MCP 服务(只有没配 Authorization 头的 HTTP 服务才用)。",'],
401
+ ['ctx.ui.notify(`Signing in to MCP server "${name}" requires interactive mode.`, "error");', 'ctx.ui.notify(process.env.U1S1_LANG === "en" ? `Signing in to MCP server "${name}" requires interactive mode.` : `登录 MCP 服务「${name}」需要在交互界面里进行。`, "error");'],
402
+ ['process.platform === "darwin" ? "Cmd+click to open" : "Ctrl+click to open"', 'process.platform === "darwin" ? (process.env.U1S1_LANG === "en" ? "Cmd+click to open" : "Cmd+点击打开") : (process.env.U1S1_LANG === "en" ? "Ctrl+click to open" : "Ctrl+点击打开")'],
403
+ ['ctx.ui.notify(`Sign in to MCP server "${name}" in your browser:\\n${lines}`, "info");', 'ctx.ui.notify(process.env.U1S1_LANG === "en" ? `Sign in to MCP server "${name}" in your browser:\\n${lines}` : `请在浏览器里登录 MCP 服务「${name}」:\\n${lines}`, "info");'],
404
+ ['ctx.ui.input(`Waiting for sign-in to "${name}". If the browser cannot reach this machine, paste the URL it was redirected to.`,', 'ctx.ui.input(process.env.U1S1_LANG === "en" ? `Waiting for sign-in to "${name}". If the browser cannot reach this machine, paste the URL it was redirected to.` : `等待登录「${name}」。浏览器连不到这台机器的话,把它跳转到的网址粘贴进来。`,'],
405
+ ['ctx.ui.notify(`Signed in to MCP server "${name}" (${server.connection?.tools.length ?? 0} tools).`, "info");', 'ctx.ui.notify(process.env.U1S1_LANG === "en" ? `Signed in to MCP server "${name}" (${server.connection?.tools.length ?? 0} tools).` : `已登录 MCP 服务「${name}」(${server.connection?.tools.length ?? 0} 个工具)。`, "info");'],
406
+ { from: '`MCP failed to load: ${errorMessage(error)}`', to: '(process.env.U1S1_LANG === "en" ? `MCP failed to load: ${errorMessage(error)}` : `MCP 加载失败:${errorMessage(error)}`)', expected: 2 },
407
+ ['ctx.ui.notify("MCP servers are still connecting; their tools become available once connected.", "info");', 'ctx.ui.notify(process.env.U1S1_LANG === "en" ? "MCP servers are still connecting; their tools become available once connected." : "MCP 服务还在连接,连上后工具就能用了。", "info");'],
408
+ ['description: "Manage MCP servers: sign in, reconnect, enable or disable, and change exposure",', 'description: process.env.U1S1_LANG === "en" ? "Manage MCP servers: sign in, reconnect, enable or disable, and change exposure" : "管理 MCP 服务:登录、重连、启用/停用、调整暴露方式",'],
409
+ ['? `Signed out of MCP server "${server.entry.name}".`', '? (process.env.U1S1_LANG === "en" ? `Signed out of MCP server "${server.entry.name}".` : `已退出 MCP 服务「${server.entry.name}」的登录。`)'],
410
+ [': `No stored credentials for MCP server "${server.entry.name}".`, "info");', ': (process.env.U1S1_LANG === "en" ? `No stored credentials for MCP server "${server.entry.name}".` : `MCP 服务「${server.entry.name}」没有保存的登录凭据。`), "info");'],
411
+ ['none: "No enabled MCP server to reconnect.",', 'none: process.env.U1S1_LANG === "en" ? "No enabled MCP server to reconnect." : "没有可重连的已启用 MCP 服务。",'],
412
+ ['ctx.ui.notify(`Reconnected to MCP server "${server.entry.name}" (${describeState(server)}).`, "info");', 'ctx.ui.notify(process.env.U1S1_LANG === "en" ? `Reconnected to MCP server "${server.entry.name}" (${describeState(server)}).` : `已重新连接 MCP 服务「${server.entry.name}」(${describeState(server)})。`, "info");'],
413
+ ];
414
+ // /mcp 面板外框与登录页(dist/extensions/mcp/ui.js)
415
+ const MCP_UI_REPLACEMENTS = [
416
+ ['frame(theme, "MCP servers", [new Text(theme.fg("muted", "Loading…"), 1, 1)])', 'frame(theme, process.env.U1S1_LANG === "en" ? "MCP servers" : "MCP 服务", [new Text(theme.fg("muted", process.env.U1S1_LANG === "en" ? "Loading…" : "加载中…"), 1, 1)])'],
417
+ ['menu.empty ?? "Nothing to show."', 'menu.empty ?? (process.env.U1S1_LANG === "en" ? "Nothing to show." : "没有内容。")'],
418
+ ['process.platform === "darwin" ? "Cmd+click to open" : "Ctrl+click to open"', 'process.platform === "darwin" ? (process.env.U1S1_LANG === "en" ? "Cmd+click to open" : "Cmd+点击打开") : (process.env.U1S1_LANG === "en" ? "Ctrl+click to open" : "Ctrl+点击打开")'],
419
+ ['theme.fg("muted", "Approve access in your browser. If it did not open, visit:")', 'theme.fg("muted", process.env.U1S1_LANG === "en" ? "Approve access in your browser. If it did not open, visit:" : "请在浏览器里同意授权。浏览器没自动打开的话,访问:")'],
420
+ ['theme.fg("muted", "If the browser runs on another machine, paste the URL it was redirected to:")', 'theme.fg("muted", process.env.U1S1_LANG === "en" ? "If the browser runs on another machine, paste the URL it was redirected to:" : "浏览器在另一台机器上的话,把它跳转到的网址粘贴到这里:")'],
421
+ ];
422
+ // codemode 工具结果(dist/extensions/codemode/renderer.js)的固定文案
423
+ const CODEMODE_RENDER_REPLACEMENTS = [
424
+ ['theme.fg("muted", `Model calls: ${formatCost(total)}`)', 'theme.fg("muted", process.env.U1S1_LANG === "en" ? `Model calls: ${formatCost(total)}` : `模型调用:${formatCost(total)}`)'],
425
+ ['theme.fg("muted", `Full output: ${fullOutputPath}`)', 'theme.fg("muted", process.env.U1S1_LANG === "en" ? `Full output: ${fullOutputPath}` : `完整输出:${fullOutputPath}`)'],
391
426
  ];
392
427
 
393
428
  const TUI_TEXT_TARGETS = [
429
+ [["core", "slash-commands.js"], SLASH_COMMANDS_REPLACEMENTS, "命令描述"],
394
430
  [["modes", "interactive", "components", "assistant-message.js"], ASSISTANT_TEXT_REPLACEMENTS, "回复状态文案"],
431
+ [["modes", "interactive", "interactive-mode.js"], EXTENSION_ERROR_REPLACEMENTS, "扩展报错"],
432
+ [["modes", "interactive", "interactive-mode.js"], HOTKEYS_REPLACEMENTS, "/hotkeys"],
395
433
  [["modes", "interactive", "interactive-mode.js"], WORKING_TEXT_REPLACEMENTS, "工作中提示"],
396
434
  [["modes", "interactive", "interactive-mode.js"], BUG_REPORT_REPLACEMENTS, "/bug 入口"],
435
+ [["modes", "interactive", "interactive-mode.js"], LOGIN_PROVIDER_REPLACEMENTS, "/login 渠道"],
397
436
  [["modes", "interactive", "components", "keybinding-hints.js"], KEY_HINT_REPLACEMENTS, "按键脚注"],
398
437
  [["modes", "interactive", "components", "model-selector.js"], MODEL_SELECTOR_REPLACEMENTS, "模型选择器"],
399
438
  [["modes", "interactive", "components", "settings-selector.js"], SETTINGS_REPLACEMENTS, "设置菜单"],
439
+ [["extensions", "mcp", "index.js"], MCP_TEXT_REPLACEMENTS, "/mcp 面板"],
440
+ [["extensions", "mcp", "ui.js"], MCP_UI_REPLACEMENTS, "/mcp 界面"],
441
+ [["extensions", "codemode", "renderer.js"], CODEMODE_RENDER_REPLACEMENTS, "codemode 结果"],
400
442
  ];
401
443
 
444
+ /**
445
+ * 双语替换结果还原成「旧版纯中文补丁」的样子:把每个
446
+ * `process.env.U1S1_LANG === "en" ? 英文 : 中文` 收成中文那一支。
447
+ * 三种写法:模板字符串里的 `${…}`(去掉引号直接内联)、加了括号的 `(…)`(不含函数调用括号)、裸三元。
448
+ */
449
+ function zhOnly(to) {
450
+ const str = '(?:"(?:[^"\\\\\\n]|\\\\.)*"|`(?:[^`\\\\]|\\\\.)*`)';
451
+ const ternary = `process\\.env\\.U1S1_LANG === "en" \\? ${str} : (${str})`;
452
+ return to
453
+ .replace(new RegExp(`\\$\\{${ternary}\\}`, "g"), (_, zh) => zh.slice(1, -1))
454
+ .replace(new RegExp(`(?<![\\w$)\\]])\\(${ternary}\\)`, "g"), (_, zh) => zh)
455
+ .replace(new RegExp(ternary, "g"), (_, zh) => zh);
456
+ }
457
+
402
458
  /**
403
459
  * 逐条套文案替换规则:已替换过的算应用成功(幂等),出现次数与预期不符的跳过
404
- * (pi 升级后上下文变了,宁可不改也不误改)。
460
+ * (pi 升级后上下文变了,宁可不改也不误改)。旧版装过纯中文补丁的(找得到 zhOnly(to)
461
+ * 或规则显式给的 legacy),原地升级成双语版。to 为空的规则表示删除 from。
405
462
  */
406
463
  function applyTextRules(text, rules) {
407
464
  let applied = 0;
465
+ const count = (needle) => text.split(needle).length - 1;
408
466
  for (const rule of rules) {
409
- const { from, to, expected } = Array.isArray(rule) ? { from: rule[0], to: rule[1], expected: 1 } : rule;
467
+ const { from, to, expected = 1, legacy, legacyTo } = Array.isArray(rule) ? { from: rule[0], to: rule[1] } : rule;
468
+ if (to === "") {
469
+ if (count(from) === expected) text = text.split(from).join("");
470
+ if (count(from) === 0) applied++;
471
+ continue;
472
+ }
410
473
  if (text.includes(to)) {
411
474
  applied++;
412
475
  continue;
413
476
  }
414
- if (text.split(from).length !== expected + 1) continue;
415
- text = text.split(from).join(to);
416
- applied++;
477
+ // 先认旧版纯中文结果:有的 to 里含着 from(如按键脚注整行前插),先套 from 会重复插入
478
+ const old = legacy ?? zhOnly(to);
479
+ if (old !== to && count(old) === expected) {
480
+ text = text.split(old).join(legacyTo ?? to);
481
+ applied++;
482
+ continue;
483
+ }
484
+ if (count(from) === expected) {
485
+ text = text.split(from).join(to);
486
+ applied++;
487
+ }
417
488
  }
418
489
  return { text, applied };
419
490
  }
@@ -423,13 +494,14 @@ function patchTuiText() {
423
494
  for (const [parts, rules, label] of TUI_TEXT_TARGETS) {
424
495
  const target = join(piDir, "dist", ...parts);
425
496
  try {
426
- const { text, applied } = applyTextRules(readFileSync(target, "utf8"), rules);
497
+ const before = readFileSync(target, "utf8");
498
+ const { text, applied } = applyTextRules(before, rules);
427
499
  if (applied < rules.length) {
428
500
  console.log(
429
- `[u1s1] 提示: pi 版本可能已更新,${label}汉化补丁只应用了 ${applied}/${rules.length} 处(不影响使用)`,
501
+ `[u1s1] 提示: pi 版本可能已更新,${label}文案补丁只应用了 ${applied}/${rules.length} 处(不影响使用)`,
430
502
  );
431
503
  }
432
- writeFileSync(target, text);
504
+ if (text !== before) writeFileSync(target, text);
433
505
  } catch {
434
506
  continue;
435
507
  }
@@ -492,9 +564,6 @@ const ENABLE_AUTOWRAP = "\\x1b[?7h";`,
492
564
 
493
565
  try {
494
566
  patchCompactUi();
495
- patchSlashCommands();
496
- patchExtensionError();
497
- patchHotkeys();
498
567
  patchTuiText();
499
568
  patchMainScreenAutowrap();
500
569
  } catch (err) {