u1s1-cli 1.5.0 → 1.6.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.
@@ -221,8 +221,10 @@ export function writeWebToolsExtension(cfg, features) {
221
221
  ` }\n` +
222
222
  ` });\n` +
223
223
  ` pi.registerTool(tools.createSubagentTool(getParentModel));\n` +
224
- ` const workflow = await import(${JSON.stringify(new URL("./workflow/tool.js", import.meta.url).href)});\n` +
225
- ` pi.registerTool(workflow.createRunWorkflowTool(getParentModel));\n` +
224
+ ` try {\n` +
225
+ ` const workflow = await import(${JSON.stringify(new URL("./workflow/tool.js", import.meta.url).href)});\n` +
226
+ ` pi.registerTool(workflow.createRunWorkflowTool(getParentModel));\n` +
227
+ ` } catch {}\n` +
226
228
  ` }\n`;
227
229
  // 密钥剥离守卫也从这里装:Desktop App 的 pi 跑在 pi-web-ui 子进程里,没有
228
230
  // 启动器代码,只有扩展能替它把 U1S1_API_KEY / U1S1_EP_KEY_* 挡在子进程之外
@@ -232,7 +234,17 @@ export function writeWebToolsExtension(cfg, features) {
232
234
  ` (await import(${JSON.stringify(secretEnvUrl)})).installChildEnvGuard();\n` +
233
235
  ` if (process.env.U1S1_TOOLS_VIA_EXTENSION !== "1") return;\n` +
234
236
  ` const baseUrl = process.env.U1S1_SIGNING_PROXY_URL || ${JSON.stringify(cfg.baseUrl)};\n` +
235
- ` const tools = await import(${JSON.stringify(toolsUrl)});\n` +
237
+ // 升级半途 / 权限问题导致 CLI dist 不可读时,宁可这一会话没有联网工具,
238
+ // 也不能让扩展加载失败刷英文堆栈(审计 B4);只在 UI 里提示一句中文
239
+ ` let tools;\n` +
240
+ ` try {\n` +
241
+ ` tools = await import(${JSON.stringify(toolsUrl)});\n` +
242
+ ` } catch {\n` +
243
+ ` pi.on("session_start", (_event, ctx) => {\n` +
244
+ ` try { if (ctx.hasUI) ctx.ui.notify("联网工具本次未能加载(程序文件可能正在更新),重启 u1s1 即可恢复", "warning"); } catch {}\n` +
245
+ ` });\n` +
246
+ ` return;\n` +
247
+ ` }\n` +
236
248
  searchLine +
237
249
  ` pi.registerTool(tools.createFetchTool({ baseUrl, apiKey: process.env.U1S1_API_KEY, renderFallback: ${features.webFetchRender} }));\n` +
238
250
  imageLine +
@@ -291,7 +303,12 @@ export function writeAnnouncementsExtension() {
291
303
  ` } catch {\n` +
292
304
  ` return;\n` +
293
305
  ` }\n` +
294
- ` const { Text } = await import("@earendil-works/pi-tui");\n` +
306
+ ` let Text;\n` +
307
+ ` try {\n` +
308
+ ` Text = (await import("@earendil-works/pi-tui")).Text;\n` +
309
+ ` } catch {\n` +
310
+ ` return;\n` +
311
+ ` }\n` +
295
312
  ` pi.registerEntryRenderer("u1s1-announcement", (entry, _opts, theme) => {\n` +
296
313
  ` const d = entry.data ?? {};\n` +
297
314
  ` let text = theme.fg("text", theme.bold("📢 " + String(d.text ?? "")));\n` +
package/dist/deploy.d.ts CHANGED
@@ -1,4 +1,9 @@
1
1
  import { type CliConfig } from "./config.js";
2
+ /**
3
+ * `u1s1 deploy remove` 的站点参数可以是 slug、完整网址或域名:
4
+ * `my-site` / `https://my-site.u1abc123.u1s1.app/` / `my-site.u1abc123.u1s1.app` 都指向 my-site。
5
+ */
6
+ export declare function siteReferenceFromInput(input: string): string;
2
7
  interface SiteFile {
3
8
  path: string;
4
9
  abs: string;
package/dist/deploy.js CHANGED
@@ -38,6 +38,30 @@ function rememberSite(dir, site) {
38
38
  // ignore
39
39
  }
40
40
  }
41
+ /** 站点删掉后把 deploys.json 里指向它的目录记录一并清掉,下次 deploy 重新问名字。 */
42
+ function forgetSite(site) {
43
+ try {
44
+ const all = readDeploys();
45
+ const remaining = Object.fromEntries(Object.entries(all).filter(([, value]) => value !== site));
46
+ if (Object.keys(remaining).length !== Object.keys(all).length) {
47
+ writeFileSync(deploysFile, JSON.stringify(remaining, null, 2) + "\n");
48
+ }
49
+ }
50
+ catch {
51
+ // ignore
52
+ }
53
+ }
54
+ /**
55
+ * `u1s1 deploy remove` 的站点参数可以是 slug、完整网址或域名:
56
+ * `my-site` / `https://my-site.u1abc123.u1s1.app/` / `my-site.u1abc123.u1s1.app` 都指向 my-site。
57
+ */
58
+ export function siteReferenceFromInput(input) {
59
+ let value = input.trim().toLowerCase();
60
+ value = value.replace(/^[a-z]+:\/\//, "");
61
+ value = value.replace(/[/?#].*$/, "");
62
+ const firstLabel = value.split(".")[0] ?? "";
63
+ return firstLabel;
64
+ }
41
65
  /** 找要部署的目录:显式参数 > 含 index.html 的构建产物目录 > 当前目录本身。 */
42
66
  function resolveSiteDir(explicit) {
43
67
  if (explicit) {
@@ -242,6 +266,44 @@ async function listDeployments(cfg) {
242
266
  }
243
267
  console.log("");
244
268
  }
269
+ async function confirmRemoval(url) {
270
+ const answer = (await askLine(` 确定删除 ${url} 吗?网址会立即失效,站点文件会被清除,无法恢复。输入 y 确认:`)).toLowerCase();
271
+ return answer === "y" || answer === "yes";
272
+ }
273
+ async function removeDeployment(cfg, args) {
274
+ const yes = args.some((arg) => arg === "--yes" || arg === "-y");
275
+ const positional = args.filter((arg) => !arg.startsWith("-"));
276
+ if (positional.length !== 1) {
277
+ throw new Error("用法:u1s1 deploy remove <站点名或网址> [--yes]");
278
+ }
279
+ const reference = siteReferenceFromInput(positional[0]);
280
+ if (!reference)
281
+ throw new Error("站点名不能为空");
282
+ // 先按列表核对,拿到完整网址给确认提示;名字打错在这里就能提前发现
283
+ const { sites } = await api(cfg, { method: "GET", path: "/deploy/sites" });
284
+ const site = sites.find((s) => (s.slug || s.name) === reference || s.name === reference);
285
+ if (!site) {
286
+ throw new Error(`没有找到站点「${reference}」。运行 u1s1 deploy list 查看已有站点`);
287
+ }
288
+ if (!yes) {
289
+ if (!process.stdin.isTTY) {
290
+ throw new Error("非交互环境下删除站点需要加 --yes 明确确认");
291
+ }
292
+ if (!(await confirmRemoval(site.url))) {
293
+ console.log(" 已取消,站点保留。");
294
+ return;
295
+ }
296
+ }
297
+ await api(cfg, {
298
+ method: "DELETE",
299
+ path: `/deploy/sites/${encodeURIComponent(site.name)}`,
300
+ });
301
+ forgetSite(site.slug || site.name);
302
+ console.log("");
303
+ console.log(` ✓ 已删除 ${site.url}`);
304
+ console.log(" 同一个名字可以再次 u1s1 deploy 重新发布。");
305
+ console.log("");
306
+ }
245
307
  async function selectDeployment(args) {
246
308
  const parsed = parseDeployArgs(args);
247
309
  let { name, dirArg, visibility } = parsed;
@@ -336,11 +398,13 @@ export function printDeployHelp() {
336
398
  console.log(" 不带参数时自动找构建产物目录(dist/build/out 等),否则用当前目录。");
337
399
  console.log(" 首次发布会问项目名和公开/私密,之后记住;再跑一次即为更新。");
338
400
  console.log("");
339
- console.log(" u1s1 deploy list 查看已发布的站点");
401
+ console.log(" u1s1 deploy list 查看已发布的站点");
402
+ console.log(" u1s1 deploy remove <站点名或网址> 删除站点(会二次确认,--yes 跳过)");
340
403
  console.log("");
341
404
  }
342
405
  export async function deployCommand(cfg, args) {
343
- // 参数:[dir] [--name xxx] [--public|--private];u1s1 deploy list 列出已有站点
406
+ // 参数:[dir] [--name xxx] [--public|--private];u1s1 deploy list 列出已有站点;
407
+ // u1s1 deploy remove <站点> 删除站点(rm/delete 同义)
344
408
  if (args.includes("--help") || args.includes("-h")) {
345
409
  printDeployHelp();
346
410
  return;
@@ -349,6 +413,10 @@ export async function deployCommand(cfg, args) {
349
413
  await listDeployments(cfg);
350
414
  return;
351
415
  }
416
+ if (args[0] === "remove" || args[0] === "rm" || args[0] === "delete") {
417
+ await removeDeployment(cfg, args.slice(1));
418
+ return;
419
+ }
352
420
  const selection = await selectDeployment(args);
353
421
  const { name, start } = await startDeployment(cfg, selection.name);
354
422
  validateDeploymentLimits(start, selection.files, selection.totalBytes);
@@ -1,3 +1,21 @@
1
+ /**
2
+ * 模型调用失败时,网关的中文说明会被 OpenAI SDK + pi-ai 包成
3
+ * `429: {"message":"…","type":"…","code":"…"}` 直出到聊天区。
4
+ * 这里把 JSON 里的 message 提出来当正文,状态码与错误代号收进尾巴。
5
+ *
6
+ * 尾巴不是装饰,必须保留:pi 按 errorMessage 正则做三类分类——
7
+ * - 重试:429/5xx 数字、rate_limit、fetch failed 等(retry.js RETRYABLE)
8
+ * - 不重试:insufficient_quota(额度用尽时快速失败,不做无谓退避)
9
+ * - 上下文超长:context_length_exceeded(触发自动压缩而非重试)
10
+ * 改写后的文本必须与原文落进同一分类,否则会破坏重试/压缩行为。
11
+ *
12
+ * 网关会在错误 JSON 里注入 error.request_id(见 gateway request-id.ts),
13
+ * 拼进尾巴让用户报障时有编号可给,客服凭它直查日志和 usage 记录。
14
+ *
15
+ * 额度用尽(reason=exhausted)时网关还带 error.resets_at(免费池刷新时刻,ISO),
16
+ * 这里换算成「距刷新还有 N 小时 M 分」接在正文后:比「北京时间 0 点」更省用户脑子。
17
+ */
18
+ export declare function quotaResetHint(resetsAt: unknown, now?: number): string;
1
19
  /** 只取错误里的 request_id(纯函数,给 request-trace 记「最近一次请求编号」用)。 */
2
20
  export declare function extractRequestId(raw: string): string | undefined;
3
- export declare function humanizeModelError(raw: string): string | undefined;
21
+ export declare function humanizeModelError(raw: string, now?: number): string | undefined;
@@ -11,7 +11,24 @@
11
11
  *
12
12
  * 网关会在错误 JSON 里注入 error.request_id(见 gateway request-id.ts),
13
13
  * 拼进尾巴让用户报障时有编号可给,客服凭它直查日志和 usage 记录。
14
+ *
15
+ * 额度用尽(reason=exhausted)时网关还带 error.resets_at(免费池刷新时刻,ISO),
16
+ * 这里换算成「距刷新还有 N 小时 M 分」接在正文后:比「北京时间 0 点」更省用户脑子。
14
17
  */
18
+ export function quotaResetHint(resetsAt, now = Date.now()) {
19
+ if (typeof resetsAt !== "string")
20
+ return "";
21
+ const at = Date.parse(resetsAt);
22
+ if (!Number.isFinite(at))
23
+ return "";
24
+ const remainingMinutes = Math.ceil((at - now) / 60_000);
25
+ if (remainingMinutes <= 0 || remainingMinutes > 48 * 60)
26
+ return "";
27
+ const hours = Math.floor(remainingMinutes / 60);
28
+ const minutes = remainingMinutes % 60;
29
+ const span = hours > 0 ? `${hours} 小时${minutes > 0 ? ` ${minutes} 分` : ""}` : `${minutes} 分`;
30
+ return `距免费额度刷新还有 ${span}`;
31
+ }
15
32
  function parseErrorPayload(raw) {
16
33
  const jsonStart = raw.indexOf("{");
17
34
  const jsonEnd = raw.lastIndexOf("}");
@@ -38,7 +55,7 @@ export function extractRequestId(raw) {
38
55
  const id = payload?.err["request_id"];
39
56
  return typeof id === "string" && id.length > 0 && id.length <= 128 ? id : undefined;
40
57
  }
41
- export function humanizeModelError(raw) {
58
+ export function humanizeModelError(raw, now = Date.now()) {
42
59
  const payload = parseErrorPayload(raw);
43
60
  if (!payload)
44
61
  return undefined;
@@ -55,6 +72,8 @@ export function humanizeModelError(raw) {
55
72
  type === "insufficient_quota" || code === "quota_exceeded" ? "insufficient_quota" : code,
56
73
  requestId ? `请求编号 ${requestId}` : "",
57
74
  ].filter(Boolean);
58
- const friendly = tags.length ? `${message} (${tags.join(" · ")})` : message;
75
+ const resetHint = err["reason"] === "exhausted" ? quotaResetHint(err["resets_at"], now) : "";
76
+ const body = resetHint ? `${message}〔${resetHint}〕` : message;
77
+ const friendly = tags.length ? `${body} (${tags.join(" · ")})` : body;
59
78
  return friendly === raw ? undefined : friendly;
60
79
  }
package/dist/index.js CHANGED
@@ -349,7 +349,7 @@ async function runAgent(cfg, args) {
349
349
  theme.fg("text", "/model 切换模型(留意免费/价格提示) · /clear 清空上下文开新会话"),
350
350
  theme.fg("text", "/usage 查剩余额度 · /resume 恢复历史会话 · /settings 设置"),
351
351
  theme.fg("text", "/feedback 一句话反馈问题或建议(自动附带版本与请求编号建工单)"),
352
- theme.fg("dim", "/compact 压缩上下文 · /hotkeys 全部快捷键(英文) · /exit 退出"),
352
+ theme.fg("dim", "/compact 压缩上下文 · /hotkeys 全部快捷键 · /exit 退出"),
353
353
  theme.fg("dim", "退出后在终端:u1s1 deploy 发布网页 · u1s1 update 升级"),
354
354
  theme.fg("dim", "匿名使用统计只记会话开始/结束等节点,不含任何内容;U1S1_TELEMETRY=0 可关闭"),
355
355
  theme.fg("dim", "新手教程 → https://u1s1.io/guides"),
@@ -527,6 +527,7 @@ async function run() {
527
527
  console.log(" u1s1 update 升级到最新版");
528
528
  console.log(" u1s1 deploy 发布网页(--public / --private)");
529
529
  console.log(" u1s1 deploy list 查看已发布的站点");
530
+ console.log(" u1s1 deploy remove 删除已发布的站点");
530
531
  console.log(" u1s1 feedback \"一句话\" 反馈问题或建议(自动建工单,--bug/--question…)");
531
532
  console.log(" u1s1 import 导入历史会话");
532
533
  console.log(" u1s1 bench 模型编码能力评测");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u1s1-cli",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "u1s1 — 有一说一,最省心的 AI 编程搭子。终端里用中文说需求,AI 帮你读文件、改代码、跑命令。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -90,7 +90,7 @@ const SLASH_COMMANDS_REPLACEMENTS = [
90
90
  ['{ name: "name", description: "Set session display name" },', '{ name: "name", description: "给当前会话起个名字" },'],
91
91
  ['{ name: "session", description: "Show session info and stats" },', '{ name: "session", description: "查看会话信息与统计" },'],
92
92
  ['{ name: "changelog", description: "Show changelog entries" },', '{ name: "changelog", description: "查看引擎更新日志(英文)" },'],
93
- ['{ name: "hotkeys", description: "Show all keyboard shortcuts" },', '{ name: "hotkeys", description: "查看全部快捷键(英文)" },'],
93
+ ['{ name: "hotkeys", description: "Show all keyboard shortcuts" },', '{ name: "hotkeys", description: "查看全部快捷键" },'],
94
94
  ['{ name: "fork", description: "Create a new fork from a previous user message" },', '{ name: "fork", description: "从之前某条消息分叉出一个新会话" },'],
95
95
  ['{ name: "clone", description: "Duplicate the current session at the current position" },', '{ name: "clone", description: "复制当前会话再继续" },'],
96
96
  ['{ name: "trust", description: "Save project trust decision for future sessions" },', '{ name: "trust", description: "记住对这个项目目录的信任选择" },'],
@@ -103,6 +103,114 @@ const SLASH_COMMANDS_REPLACEMENTS = [
103
103
  ['{ name: "quit", description: `Quit ${APP_NAME}` },', '{ name: "quit", description: `退出 ${APP_NAME}` },'],
104
104
  ];
105
105
 
106
+ // 扩展报错文案汉化 + 默认不刷堆栈(dist/modes/interactive/interactive-mode.js),与 pnpm coding-agent patch 同步。
107
+ // 小白看到一屏英文 JS 堆栈只会以为程序坏了;一句中文 + 下一步(重启 / u1s1 feedback)就够。
108
+ const EXTENSION_ERROR_REPLACEMENTS = [
109
+ ['const errorMsg = `Extension "${extensionPath}" error: ${error}`;', 'const errorMsg = `扩展 ${String(extensionPath).split(/[\\\\/]/).pop()} 出错(重启 u1s1 一般可恢复;反复出现请运行 u1s1 feedback 反馈):${error}`;'],
110
+ ['if (stack) {', 'if (stack && process.env.U1S1_DEBUG_EXTENSIONS === "1") {'],
111
+ ];
112
+
113
+ function patchExtensionError() {
114
+ for (const piDir of findPackageDirs("@earendil-works/pi-coding-agent")) {
115
+ const target = join(piDir, "dist", "modes", "interactive", "interactive-mode.js");
116
+ try {
117
+ let text = readFileSync(target, "utf8");
118
+ let applied = 0;
119
+ for (const [from, to] of EXTENSION_ERROR_REPLACEMENTS) {
120
+ if (text.includes(to)) {
121
+ applied++;
122
+ continue;
123
+ }
124
+ if (text.split(from).length !== 2) continue;
125
+ text = text.replace(from, to);
126
+ applied++;
127
+ }
128
+ if (applied < EXTENSION_ERROR_REPLACEMENTS.length) {
129
+ console.log(
130
+ `[u1s1] 提示: pi 版本可能已更新,扩展报错文案补丁只应用了 ${applied}/${EXTENSION_ERROR_REPLACEMENTS.length} 处(不影响使用)`,
131
+ );
132
+ }
133
+ writeFileSync(target, text);
134
+ } catch {
135
+ continue;
136
+ }
137
+ }
138
+ }
139
+
140
+ // /hotkeys 快捷键表汉化(dist/modes/interactive/interactive-mode.js),与 pnpm coding-agent patch 同步。
141
+ // 表格由 pi 硬编码英文拼出来,启动横幅/help 都在引导新手输入 /hotkeys,整页英文等于没给。
142
+ // 只译说明列,按键显示(`${cursorUp}` 等)原样保留;表头出现 4 次,用 expected 计数整体替换。
143
+ const HOTKEYS_REPLACEMENTS = [
144
+ ['"Keyboard Shortcuts"', '"快捷键一览"'],
145
+ ["**Navigation**", "**移动**"],
146
+ ["**Editing**", "**编辑**"],
147
+ ["**Other**", "**其他**"],
148
+ ["**Extensions**", "**扩展**"],
149
+ { from: "| Key | Action |", to: "| 按键 | 操作 |", expected: 4 },
150
+ ["| Move cursor / browse history |", "| 移动光标 / 翻看输入历史 |"],
151
+ ["| Move by word |", "| 按词移动 |"],
152
+ ["| Start of line |", "| 跳到行首 |"],
153
+ ["| End of line |", "| 跳到行尾 |"],
154
+ ["| Jump forward to character |", "| 向后跳到指定字符 |"],
155
+ ["| Jump backward to character |", "| 向前跳到指定字符 |"],
156
+ ["| Scroll by page |", "| 整页滚动 |"],
157
+ ["| Send message |", "| 发送消息 |"],
158
+ ['| New line${process.platform === "win32" ? " (Ctrl+Enter on Windows Terminal)" : ""} |', '| 换行${process.platform === "win32" ? "(Windows Terminal 用 Ctrl+Enter)" : ""} |'],
159
+ ["| Delete word backwards |", "| 删除光标前一个词 |"],
160
+ ["| Delete word forwards |", "| 删除光标后一个词 |"],
161
+ ["| Delete to start of line |", "| 删到行首 |"],
162
+ ["| Delete to end of line |", "| 删到行尾 |"],
163
+ ["| Paste the most-recently-deleted text |", "| 粘贴最近删除的文本 |"],
164
+ ["| Cycle through the deleted text after pasting |", "| 粘贴后在更早删除的文本间循环 |"],
165
+ ["| Undo |", "| 撤销 |"],
166
+ ["| Path completion / accept autocomplete |", "| 路径补全 / 接受自动补全 |"],
167
+ ["| Cancel autocomplete / abort streaming |", "| 取消补全 / 中断模型回复 |"],
168
+ ["| Clear editor (first) / exit (second) |", "| 清空输入框(再按一次退出) |"],
169
+ ["| Exit (when editor is empty) |", "| 退出(输入框为空时) |"],
170
+ ["| Suspend to background |", "| 挂起到后台 |"],
171
+ ["| Cycle thinking level |", "| 切换思考深度 |"],
172
+ ["| Cycle models |", "| 轮换模型 |"],
173
+ ["| Open model selector |", "| 打开模型选择器 |"],
174
+ ["| Toggle tool output expansion |", "| 展开 / 收起工具输出 |"],
175
+ ["| Toggle thinking block visibility |", "| 显示 / 隐藏思考过程 |"],
176
+ ["| Edit message in external editor |", "| 用外部编辑器写消息 |"],
177
+ ["| Copy last assistant message |", "| 复制最近一条回复 |"],
178
+ ["| Queue follow-up message |", "| 把消息排到当前回复之后 |"],
179
+ ["| Restore queued messages |", "| 取回排队中的消息 |"],
180
+ ["| Paste image or text from clipboard |", "| 从剪贴板粘贴图片或文本 |"],
181
+ ["| Slash commands |", "| 斜杠命令(/help 看常用操作) |"],
182
+ ["| Run bash command |", "| 运行终端命令 |"],
183
+ ["| Run bash command (excluded from context) |", "| 运行终端命令(结果不进上下文) |"],
184
+ ];
185
+
186
+ function patchHotkeys() {
187
+ for (const piDir of findPackageDirs("@earendil-works/pi-coding-agent")) {
188
+ const target = join(piDir, "dist", "modes", "interactive", "interactive-mode.js");
189
+ try {
190
+ let text = readFileSync(target, "utf8");
191
+ let applied = 0;
192
+ for (const rule of HOTKEYS_REPLACEMENTS) {
193
+ const { from, to, expected } = Array.isArray(rule) ? { from: rule[0], to: rule[1], expected: 1 } : rule;
194
+ if (text.includes(to)) {
195
+ applied++;
196
+ continue;
197
+ }
198
+ if (text.split(from).length !== expected + 1) continue;
199
+ text = text.split(from).join(to);
200
+ applied++;
201
+ }
202
+ if (applied < HOTKEYS_REPLACEMENTS.length) {
203
+ console.log(
204
+ `[u1s1] 提示: pi 版本可能已更新,/hotkeys 汉化补丁只应用了 ${applied}/${HOTKEYS_REPLACEMENTS.length} 处(不影响使用)`,
205
+ );
206
+ }
207
+ writeFileSync(target, text);
208
+ } catch {
209
+ continue;
210
+ }
211
+ }
212
+ }
213
+
106
214
  function patchSlashCommands() {
107
215
  for (const piDir of findPackageDirs("@earendil-works/pi-coding-agent")) {
108
216
  const target = join(piDir, "dist", "core", "slash-commands.js");
@@ -219,6 +327,8 @@ const ENABLE_AUTOWRAP = "\\x1b[?7h";`,
219
327
  try {
220
328
  patchCompactUi();
221
329
  patchSlashCommands();
330
+ patchExtensionError();
331
+ patchHotkeys();
222
332
  patchMainScreenAutowrap();
223
333
  } catch (err) {
224
334
  console.log(`[u1s1] 提示: pi 运行时补丁未生效(${err?.message ?? err}),不影响安装`);