u1s1-cli 1.13.2 → 1.14.1

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.
@@ -3,7 +3,7 @@
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, Windows TUI and UI-text (zh/en by
6
+ * This script applies the same Windows TUI and UI-text (zh/en by
7
7
  * U1S1_LANG) fixes to installed dependencies so both installation paths behave identically.
8
8
  *
9
9
  * Properties:
@@ -60,55 +60,6 @@ function findTuiDirs() {
60
60
  return [...found];
61
61
  }
62
62
 
63
- // Each tuple is [original, replacement], matching the pnpm coding-agent patch.
64
- const COMPACT_UI_REPLACEMENTS = [
65
- // Hidden thinking does not count as visible content.
66
- [
67
- '|| (c.type === "thinking" && c.thinking.trim())',
68
- '|| (c.type === "thinking" && !this.hideThinkingBlock && c.thinking.trim())',
69
- ],
70
- // Skip a hidden thinking run with an empty label entirely: no ANSI-only row, no trailing spacer.
71
- [
72
- "const hidden = this.thinkingVisibilityOverrides.get(runIndex) ?? this.hideThinkingBlock;",
73
- 'const hidden = this.thinkingVisibilityOverrides.get(runIndex) ?? this.hideThinkingBlock;\nif (hidden && this.hiddenThinkingLabel === "") { continue; }',
74
- ],
75
- ];
76
-
77
- function patchCompactUi() {
78
- const piDirs = findPackageDirs("@earendil-works/pi-coding-agent");
79
- if (piDirs.length === 0) {
80
- console.log("[u1s1] 提示: 未找到 @earendil-works/pi-coding-agent,精简 UI 空行补丁未应用(不影响使用)");
81
- return;
82
- }
83
- for (const piDir of piDirs) {
84
- const target = join(piDir, "dist", "modes", "interactive", "components", "assistant-message.js");
85
- try {
86
- let text = readFileSync(target, "utf8");
87
- if (text.includes("!this.hideThinkingBlock && c.thinking.trim()")) continue;
88
-
89
- let applied = 0;
90
- for (const [from, to] of COMPACT_UI_REPLACEMENTS) {
91
- if (text.includes(to)) {
92
- applied++;
93
- continue;
94
- }
95
- if (!text.includes(from)) continue;
96
- text = text.split(from).join(to);
97
- applied++;
98
- }
99
-
100
- if (applied < COMPACT_UI_REPLACEMENTS.length) {
101
- console.log(
102
- `[u1s1] 提示: pi 版本可能已更新,精简 UI 空行补丁只应用了 ${applied}/${COMPACT_UI_REPLACEMENTS.length} 处(不影响使用)`,
103
- );
104
- }
105
- writeFileSync(target, text);
106
- } catch {
107
- continue;
108
- }
109
- }
110
- }
111
-
112
63
  // ---- TUI 文案(与 pnpm coding-agent patch 同步,patch-pi.test.ts 校验双轨一致) ----
113
64
  // 中英双语:u1s1 CLI 拉起 pi 前写 process.env.U1S1_LANG(国际站 "en",中国站 "zh"),
114
65
  // 补丁注入的文案一律写成 `process.env.U1S1_LANG === "en" ? "English" : "中文"`,
@@ -343,81 +294,85 @@ const LOGIN_PROVIDER_REPLACEMENTS = [
343
294
  // 只换给人看的文案;mcp_servers 系统提示段、工具描述等给模型看的保持英文。
344
295
  // "Sign-in cancelled." 既是返回值又被 === 比较,两处必须一起换。
345
296
  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");'],
297
+ ["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() 查找)\","],
298
+ ["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\","],
299
+ ["direct: \"declared to the model like built-in tools\",", "direct: process.env.U1S1_LANG === \"en\" ? \"declared to the model like built-in tools\" : \"像内置工具一样直接提供给模型\","],
300
+ ["return \"disabled\";", "return process.env.U1S1_LANG === \"en\" ? \"disabled\" : \"已停用\";"],
301
+ ["return \"starting\";", "return process.env.U1S1_LANG === \"en\" ? \"starting\" : \"启动中\";"],
302
+ ["return \"needs sign-in\";", "return process.env.U1S1_LANG === \"en\" ? \"needs sign-in\" : \"需要登录\";"],
303
+ ["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\" : \"连接失败\");"],
304
+ ["return `connected · ${tools.length} tool${tools.length === 1 ? \"\" : \"s\"}${hasResources ? \" · resources\" : \"\"}`;", "return process.env.U1S1_LANG === \"en\" ? `connected · ${tools.length} tool${tools.length === 1 ? \"\" : \"s\"}${hasResources ? \" · resources\" : \"\"}` : `已连接 · ${tools.length} 个工具${hasResources ? \" · 含资源\" : \"\"}`;"],
305
+ ["return \"connecting…\";", "return process.env.U1S1_LANG === \"en\" ? \"connecting…\" : \"连接中…\";"],
306
+ ["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 [服务名]\";"],
307
+ ["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\");"],
308
+ ["const lines = only ? [] : configErrors.map((error) => `config: ${error}`);", "const lines = only ? [] : configErrors.map((error) => (process.env.U1S1_LANG === \"en\" ? `config: ${error}` : `配置错误:${error}`));"],
309
+ ["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\");"],
310
+ ["return `Could not update ${entry.override ?? entry.source}: ${errorMessage(error)}`;", "return process.env.U1S1_LANG === \"en\" ? `Could not update ${entry.override ?? entry.source}: ${errorMessage(error)}` : `无法写入 ${entry.override ?? entry.source}:${errorMessage(error)}`;"],
311
+ ["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 登录。`;"],
312
+ ["return \"Sign-in cancelled.\";", "return process.env.U1S1_LANG === \"en\" ? \"Sign-in cancelled.\" : \"已取消登录。\";"],
313
+ ["return `Sign-in failed: ${errorMessage(error)}`;", "return process.env.U1S1_LANG === \"en\" ? `Sign-in failed: ${errorMessage(error)}` : `登录失败:${errorMessage(error)}`;"],
314
+ ["return `Signed in, but ${errorMessage(error)}`;", "return process.env.U1S1_LANG === \"en\" ? `Signed in, but ${errorMessage(error)}` : `已登录,但连接失败:${errorMessage(error)}`;"],
315
+ ["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}」已停用。`;"],
316
+ ["...configErrors.map((error) => `config: ${error}`),", "...configErrors.map((error) => (process.env.U1S1_LANG === \"en\" ? `config: ${error}` : `配置错误:${error}`)),"],
317
+ ["...overridden.map((line) => `overridden: ${line}`),", "...overridden.map((line) => (process.env.U1S1_LANG === \"en\" ? `overridden: ${line}` : `被覆盖:${line}`)),"],
318
+ ["title: \"MCP servers\",", "title: process.env.U1S1_LANG === \"en\" ? \"MCP servers\" : \"MCP 服务\","],
319
+ ["description: `${describeState(server)} · ${exposureOf(server.entry)} · ${server.entry.override ? \"global, project override\" : (server.entry.scope ?? server.entry.source)}`,", "description: `${describeState(server)} · ${exposureOf(server.entry)} · ${server.entry.override ? process.env.U1S1_LANG === \"en\" ? \"global, project override\" : \"全局,项目覆盖\" : (server.entry.scope ?? server.entry.source)}`,"],
320
+ ["empty: `No MCP servers configured. Add them to ${resolve(getAgentDir(), \"mcp.json\")} or .pi/mcp.json.`,", "empty: (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\")}。`),"],
321
+ ["confirmLabel: \"manage\",", "confirmLabel: process.env.U1S1_LANG === \"en\" ? \"manage\" : \"管理\","],
322
+ ["cancelLabel: \"close\",", "cancelLabel: process.env.U1S1_LANG === \"en\" ? \"close\" : \"关闭\","],
323
+ ["empty: \"This server is no longer configured.\",", "empty: process.env.U1S1_LANG === \"en\" ? \"This server is no longer configured.\" : \"这个服务已不在配置里了。\","],
324
+ { from: "cancelLabel: \"back\",", to: "cancelLabel: process.env.U1S1_LANG === \"en\" ? \"back\" : \"返回\",", expected: 4 },
325
+ ["? \"for this session\"", "? (process.env.U1S1_LANG === \"en\" ? \"for this session\" : \"仅本次会话\")"],
326
+ ["? \"saved to the project mcp.json\"", "? (process.env.U1S1_LANG === \"en\" ? \"saved to the project mcp.json\" : \"保存到项目 mcp.json\")"],
327
+ ["? `saved to the ${entry.scope} mcp.json`", "? (process.env.U1S1_LANG === \"en\" ? `saved to the ${entry.scope} mcp.json` : `保存到 mcp.json(${entry.scope})`)"],
328
+ [": \"saved to mcp.json\";", ": (process.env.U1S1_LANG === \"en\" ? \"saved to mcp.json\" : \"保存到 mcp.json\");"],
329
+ ["const inProjectSaved = \"saved to the project mcp.json\";", "const inProjectSaved = process.env.U1S1_LANG === \"en\" ? \"saved to the project mcp.json\" : \"保存到项目 mcp.json\";"],
330
+ ["items.push({ value: \"enable\", label: \"Enable\", description: saved });", "items.push({ value: \"enable\", label: process.env.U1S1_LANG === \"en\" ? \"Enable\" : \"启用\", description: saved });"],
331
+ ["items.push({ value: \"enable-project\", label: \"Enable in this project\", description: inProjectSaved });", "items.push({ value: \"enable-project\", label: process.env.U1S1_LANG === \"en\" ? \"Enable in this project\" : \"在本项目启用\", description: inProjectSaved });"],
332
+ ["items.push({ value: \"signin\", label: \"Sign in\", description: \"opens the browser\" });", "items.push({ value: \"signin\", label: process.env.U1S1_LANG === \"en\" ? \"Sign in\" : \"登录\", description: process.env.U1S1_LANG === \"en\" ? \"opens the browser\" : \"会打开浏览器\" });"],
333
+ ["items.push({ value: \"tools\", label: \"Tools\", description: `${connection.tools.length} offered` });", "items.push({ value: \"tools\", label: process.env.U1S1_LANG === \"en\" ? \"Tools\" : \"工具\", description: process.env.U1S1_LANG === \"en\" ? `${connection.tools.length} offered` : `共 ${connection.tools.length} 个` });"],
334
+ ["items.push({ value: \"reconnect\", label: \"Reconnect\" });", "items.push({ value: \"reconnect\", label: process.env.U1S1_LANG === \"en\" ? \"Reconnect\" : \"重新连接\" });"],
335
+ ["items.push({ value: \"signout\", label: \"Sign out\", description: \"deletes the stored credentials\" });", "items.push({ value: \"signout\", label: process.env.U1S1_LANG === \"en\" ? \"Sign out\" : \"退出登录\", description: process.env.U1S1_LANG === \"en\" ? \"deletes the stored credentials\" : \"删除已保存的登录凭据\" });"],
336
+ ["items.push({ value: \"exposure\", label: \"Exposure\", description: exposureOf(entry) });", "items.push({ value: \"exposure\", label: process.env.U1S1_LANG === \"en\" ? \"Exposure\" : \"暴露方式\", description: exposureOf(entry) });"],
337
+ ["items.push({ value: \"disable\", label: \"Disable\", description: saved });", "items.push({ value: \"disable\", label: process.env.U1S1_LANG === \"en\" ? \"Disable\" : \"停用\", description: saved });"],
338
+ ["items.push({ value: \"disable-project\", label: \"Disable in this project\", description: inProjectSaved });", "items.push({ value: \"disable-project\", label: process.env.U1S1_LANG === \"en\" ? \"Disable in this project\" : \"在本项目停用\", description: inProjectSaved });"],
339
+ ["...(entry.override ? [`project override: ${entry.override}`] : []),", "...(entry.override ? [process.env.U1S1_LANG === \"en\" ? `project override: ${entry.override}` : `项目覆盖:${entry.override}`] : []),"],
340
+ ["title: `MCP server ${name}`,", "title: (process.env.U1S1_LANG === \"en\" ? `MCP server ${name}` : `MCP 服务 ${name}`),"],
341
+ ["confirmLabel: \"select\",", "confirmLabel: process.env.U1S1_LANG === \"en\" ? \"select\" : \"选择\","],
342
+ ["title: `Tools of ${server.entry.name}`,", "title: process.env.U1S1_LANG === \"en\" ? `Tools of ${server.entry.name}` : `${server.entry.name} 的工具`,"],
343
+ ["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 单独设置了。\" : \"\"}`,"],
344
+ ["empty: \"The server offers no tools.\",", "empty: process.env.U1S1_LANG === \"en\" ? \"The server offers no tools.\" : \"这个服务没有提供工具。\","],
345
+ ["confirmLabel: \"back\",", "confirmLabel: process.env.U1S1_LANG === \"en\" ? \"back\" : \"返回\","],
346
+ ["title: `Exposure of ${server.entry.name}`,", "title: process.env.U1S1_LANG === \"en\" ? `Exposure of ${server.entry.name}` : `${server.entry.name} 的暴露方式`,"],
347
+ [": `Saved to ${server.entry.override ?? server.entry.source}.`,", ": (process.env.U1S1_LANG === \"en\" ? `Saved to ${server.entry.override ?? server.entry.source}.` : `保存到 ${server.entry.override ?? server.entry.source}。`),"],
348
+ ["confirmLabel: \"save\",", "confirmLabel: process.env.U1S1_LANG === \"en\" ? \"save\" : \"保存\","],
349
+ ["const title = `Sign in to ${server.entry.name}`;", "const title = process.env.U1S1_LANG === \"en\" ? `Sign in to ${server.entry.name}` : `登录 ${server.entry.name}`;"],
350
+ ["status(\"Contacting the authorization server…\");", "status(process.env.U1S1_LANG === \"en\" ? \"Contacting the authorization server…\" : \"正在联系授权服务器…\");"],
351
+ ["status(\"Connecting…\");", "status(process.env.U1S1_LANG === \"en\" ? \"Connecting…\" : \"连接中…\");"],
352
+ ["return `No MCP servers configured. Add them to ${resolve(getAgentDir(), \"mcp.json\")} or .pi/mcp.json.`;", "return (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\")}。`);"],
353
+ ["lines.push(`overridden: ${line}`);", "lines.push((process.env.U1S1_LANG === \"en\" ? `overridden: ${line}` : `被覆盖:${line}`));"],
354
+ ["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\");"],
355
+ ["const choice = await ctx.ui.select(\"MCP server\", candidates.map((server) => server.entry.name));", "const choice = await ctx.ui.select(process.env.U1S1_LANG === \"en\" ? \"MCP server\" : \"选择 MCP 服务\", candidates.map((server) => server.entry.name));"],
356
+ ["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 服务才用)。\","],
357
+ ["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\");"],
358
+ ["ctx.ui.notify(`Sign in to MCP server \"${name}\" in your browser:\\n${url.href}`, \"info\");", "ctx.ui.notify(process.env.U1S1_LANG === \"en\" ? `Sign in to MCP server \"${name}\" in your browser:\\n${url.href}` : `请在浏览器里登录 MCP 服务「${name}」:\\n${url.href}`, \"info\");"],
359
+ ["promptForRedirectUrl: (signal) => ctx.ui.input(`Waiting for sign-in to \"${name}\". If the browser cannot reach this machine, paste the URL it was redirected to.`, \"http://127.0.0.1:.../callback?code=...\", { signal }),", "promptForRedirectUrl: (signal) => 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}」。浏览器连不到这台机器的话,把它跳转到的网址粘贴进来。`, \"http://127.0.0.1:.../callback?code=...\", { signal }),"],
360
+ ["ctx.ui.notify(failure, failure === \"Sign-in cancelled.\" ? \"info\" : \"error\");", "ctx.ui.notify(failure, failure === (process.env.U1S1_LANG === \"en\" ? \"Sign-in cancelled.\" : \"已取消登录。\") ? \"info\" : \"error\");"],
361
+ ["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\");"],
362
+ { from: "ctx.ui.notify(`MCP failed to load: ${errorMessage(error)}`, \"error\");", to: "ctx.ui.notify((process.env.U1S1_LANG === \"en\" ? `MCP failed to load: ${errorMessage(error)}` : `MCP 加载失败:${errorMessage(error)}`), \"error\");", expected: 2 },
363
+ ["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\");"],
364
+ ["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 服务:登录、重连、启用/停用、调整暴露方式\","],
365
+ ["? `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}」的登录。`)"],
366
+ [": `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\");"],
367
+ ["none: \"No enabled MCP server to reconnect.\",", "none: process.env.U1S1_LANG === \"en\" ? \"No enabled MCP server to reconnect.\" : \"没有可重连的已启用 MCP 服务。\","],
368
+ ["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
369
  ];
414
370
  // /mcp 面板外框与登录页(dist/extensions/mcp/ui.js)
415
371
  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:" : "浏览器在另一台机器上的话,把它跳转到的网址粘贴到这里:")'],
372
+ ["this.content = frame(theme, \"MCP servers\", [new Text(theme.fg(\"muted\", \"Loading…\"), 1, 1)]);", "this.content = frame(theme, process.env.U1S1_LANG === \"en\" ? \"MCP servers\" : \"MCP 服务\", [new Text(theme.fg(\"muted\", process.env.U1S1_LANG === \"en\" ? \"Loading…\" : \"加载中…\"), 1, 1)]);"],
373
+ ["body.push(new Text(this.theme.fg(\"muted\", menu.empty ?? \"Nothing to show.\"), 1, 0));", "body.push(new Text(this.theme.fg(\"muted\", menu.empty ?? (process.env.U1S1_LANG === \"en\" ? \"Nothing to show.\" : \"没有内容。\")), 1, 0));"],
374
+ ["new Text(this.theme.fg(\"muted\", \"Approve access in your browser. If it did not open, visit:\"), 1, 0),", "new Text(this.theme.fg(\"muted\", process.env.U1S1_LANG === \"en\" ? \"Approve access in your browser. If it did not open, visit:\" : \"请在浏览器里同意授权。浏览器没自动打开的话,访问:\"), 1, 0),"],
375
+ ["new Text(this.theme.fg(\"muted\", \"If the browser runs on another machine, paste the URL it was redirected to:\"), 1, 0),", "new Text(this.theme.fg(\"muted\", process.env.U1S1_LANG === \"en\" ? \"If the browser runs on another machine, paste the URL it was redirected to:\" : \"浏览器在另一台机器上的话,把它跳转到的网址粘贴到这里:\"), 1, 0),"],
421
376
  ];
422
377
  // codemode 工具结果(dist/extensions/codemode/renderer.js)的固定文案
423
378
  const CODEMODE_RENDER_REPLACEMENTS = [
@@ -563,7 +518,6 @@ const ENABLE_AUTOWRAP = "\\x1b[?7h";`,
563
518
  }
564
519
 
565
520
  try {
566
- patchCompactUi();
567
521
  patchTuiText();
568
522
  patchMainScreenAutowrap();
569
523
  } catch (err) {
@@ -1,18 +1,17 @@
1
1
  /**
2
- * 渲染回归测试 —— 防止「修好的空行问题在发版后又回来」。
2
+ * 渲染回归测试 —— 守住「界面回到 Pi 默认」和「中英文案补丁」。
3
3
  *
4
- * 背景:精简 UI 依赖两处修复才能保持每轮回复只占必要的行数:
5
- * 1. pi 的 AssistantMessageComponent 补丁(patches/*.patch + postinstall patch-pi.js)
6
- * —— 隐藏的思考块不算可见内容、空标签跳过、隐形思考块后不加间隔空行
7
- * 2. 扩展给内置工具的 renderShell:"self" + 空 Text —— 工具行零行渲染
4
+ * 背景:u1s1 曾用扩展隐藏内置工具行、补丁隐藏思考块来做「精简 UI」,现已下线,
5
+ * 工具调用和思考块都按 Pi 原样显示。本脚本用 pi 真实组件断言:
6
+ * 1. 内置工具行(bash)没被任何东西覆盖成 0 行——有调用、有结果就必须有可见行
7
+ * 2. 思考块未隐藏时必须出现在助手消息里(没有被补丁吞掉)
8
+ * 3. Windows ConPTY 的 autowrap 保护、补丁文案跟 U1S1_LANG 切中英
9
+ * pi 升级后补丁没跟上、或又有人把工具行/思考块藏起来时,这里直接失败,挡在发布前。
8
10
  *
9
- * 本脚本用 pi 真实组件模拟一轮对话并断言行数;另外核对补丁文案跟 U1S1_LANG 切中英。
10
- * pi 升级后补丁没跟上、或扩展渲染被改动导致行数膨胀时,这里直接失败,挡在发布前。
11
- *
12
- * 运行:node scripts/render-regression.mjs(prepublishOnly 会自动跑)
11
+ * 运行:node scripts/render-regression.mjs(prepublishOnly 会自动跑)
13
12
  */
14
13
 
15
- import { Container, Spacer, Text, TuiMainScreen } from "@earendil-works/pi-tui";
14
+ import { Container, TuiMainScreen } from "@earendil-works/pi-tui";
16
15
  import { existsSync, readFileSync } from "node:fs";
17
16
  import { join, dirname } from "node:path";
18
17
  import { fileURLToPath, pathToFileURL } from "node:url";
@@ -37,99 +36,47 @@ const { AssistantMessageComponent } = await import(
37
36
  const { ToolExecutionComponent } = await import(
38
37
  join(piRoot, "dist/modes/interactive/components/tool-execution.js")
39
38
  );
40
- const { createBashTool } = await import(join(piRoot, "dist/core/tools/bash.js"));
39
+ const { createBashToolDefinition } = await import(join(piRoot, "dist/core/tools/bash.js"));
41
40
 
42
41
  initTheme("dark");
43
42
 
44
43
  let failed = 0;
45
- function check(name, actual, expected) {
46
- const ok = actual === expected;
47
- if (!ok) failed++;
48
- console.log(`${ok ? "✓" : "✗"} ${name}: ${actual} 行(期望 ${expected})`);
49
- }
50
-
51
44
  function checkTrue(name, actual) {
52
45
  const ok = actual === true;
53
46
  if (!ok) failed++;
54
47
  console.log(`${ok ? "✓" : "✗"} ${name}`);
55
48
  }
56
49
 
57
- // 与 u1s1-compact-ui 扩展一致的隐藏渲染器
58
- function hiddenToolDef(orig) {
59
- return {
60
- name: "bash",
61
- label: "bash",
62
- description: orig.description,
63
- parameters: orig.parameters,
64
- renderShell: "self",
65
- async execute() {},
66
- renderCall() {
67
- return new Text("", 0, 0);
68
- },
69
- // pi owns this positional callback contract; keep the adapter narrow here.
70
- renderResult(...args) {
71
- const [result, { expanded }, theme, context] = args;
72
- if (context.isError) {
73
- const c = result.content.find((x) => x.type === "text");
74
- return new Text(theme.fg("warning", "✗ ") + theme.fg("muted", c?.text ?? "error"), 0, 0);
75
- }
76
- if (!expanded) return new Text("", 0, 0);
77
- const c = result.content.find((x) => x.type === "text");
78
- return new Text(c?.text ?? "", 0, 0);
79
- },
80
- };
81
- }
50
+ const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
82
51
 
83
- // ---- 1. 助手消息(思考+正文,思考隐藏、标签为空):只应有 间隔行+正文 = 2 行 ----
84
- // pi 补丁失效时这里会变成 4 行(思考块前后各多一个空行)
85
- const msg = {
52
+ // ---- 1. 内置工具行保持 Pi 原样:调用和结果都可见 ----
53
+ const bashDef = createBashToolDefinition(process.cwd());
54
+ const toolComp = new ToolExecutionComponent("bash", "t1", { command: "echo native-tool-line" }, {}, bashDef, null, process.cwd());
55
+ toolComp.updateResult({ content: [{ type: "text", text: "native-tool-output\nexit code: 0" }] }, false);
56
+ const chatTool = new Container();
57
+ chatTool.addChild(toolComp);
58
+ const toolText = stripAnsi(chatTool.render(80).join("\n"));
59
+ checkTrue("内置工具调用行可见(含命令)", toolText.includes("native-tool-line"));
60
+ checkTrue("内置工具结果可见", toolText.includes("native-tool-output"));
61
+
62
+ // ---- 2. 思考块未隐藏时可见,隐藏时只留标签(Pi 默认行为,没被补丁吞掉) ----
63
+ const thinkingMsg = {
86
64
  role: "assistant",
87
65
  content: [
88
66
  { type: "thinking", thinking: "模型的一段思考" },
89
67
  { type: "text", text: "回复正文" },
90
68
  ],
91
69
  };
92
- const am = new AssistantMessageComponent(msg, true, undefined, "", 1, []);
93
- const chat1 = new Container();
94
- chat1.addChild(am);
95
- check("助手消息(思考+正文,思考隐藏)", chat1.render(80).length, 2);
96
-
97
- // ---- 2. 隐藏的工具行(带结果):0 行 ----
98
- const origBash = createBashTool(process.cwd());
99
- const comp = new ToolExecutionComponent(
100
- "bash",
101
- "t1",
102
- { command: "ls" },
103
- {},
104
- hiddenToolDef(origBash),
105
- null,
106
- process.cwd(),
107
- );
108
- comp.updateResult({ content: [{ type: "text", text: "file1\nfile2\nexit code: 0" }] });
109
- const chat2 = new Container();
110
- chat2.addChild(comp);
111
- check("隐藏的工具行(已完成)", chat2.render(80).length, 0);
112
-
113
- // ---- 3. 完整一轮:用户消息 + 思考正文回复 + 隐藏工具行 + 第二条回复 ----
114
- // 期望:间隔 + 用户 + (间隔+正文) + 0 + (间隔+正文) = 6 行
115
- const chat3 = new Container();
116
- chat3.addChild(new Spacer(1));
117
- chat3.addChild(new Text("用户提问", 1, 0));
118
- chat3.addChild(new AssistantMessageComponent(mkMsg("思考1", "我先看看。"), true, undefined, "", 1, []));
119
- chat3.addChild(comp);
120
- chat3.addChild(new AssistantMessageComponent(mkMsg("思考2", "我去查一下。"), true, undefined, "", 1, []));
121
- function mkMsg(thinking, text) {
122
- return {
123
- role: "assistant",
124
- content: [
125
- ...(thinking ? [{ type: "thinking", thinking }] : []),
126
- { type: "text", text },
127
- ],
128
- };
129
- }
130
- check("完整一轮对话", chat3.render(80).length, 6);
131
-
132
- // ---- 4. Regular-screen frames guard full-width lines against ConPTY eager autowrap ----
70
+ const shown = new Container();
71
+ shown.addChild(new AssistantMessageComponent(thinkingMsg, false, undefined, "Thinking...", 1, []));
72
+ const shownText = stripAnsi(shown.render(80).join("\n"));
73
+ checkTrue("思考块未隐藏时显示思考内容", shownText.includes("模型的一段思考") && shownText.includes("回复正文"));
74
+ const folded = new Container();
75
+ folded.addChild(new AssistantMessageComponent(thinkingMsg, true, undefined, "Thinking...", 1, []));
76
+ const foldedText = stripAnsi(folded.render(80).join("\n"));
77
+ checkTrue("思考块隐藏时显示折叠标签而不是整块消失", foldedText.includes("Thinking...") && foldedText.includes("回复正文") && !foldedText.includes("模型的一段思考"));
78
+
79
+ // ---- 3. Regular-screen frames guard full-width lines against ConPTY eager autowrap ----
133
80
  class RecordingTerminal {
134
81
  columns = 40;
135
82
  rows = 10;
@@ -177,11 +124,10 @@ tui.renderNow();
177
124
  const deletionFrame = terminal.writes.join("");
178
125
  checkTrue("Windows 删除行绘制关闭并恢复 autowrap", deletionFrame.includes("\x1b[?2026h\x1b[?7l") && deletionFrame.includes("\x1b[?7h\x1b[?2026l"));
179
126
 
180
- // ---- 5. 补丁文案跟 U1S1_LANG 走:en(国际站)不能漏出中文,其余保持中文 ----
127
+ // ---- 4. 补丁文案跟 U1S1_LANG 走:en(国际站)不能漏出中文,其余保持中文 ----
181
128
  // 斜杠命令描述在模块加载时求值,换语言要带 query 重新 import 一份新实例;
182
129
  // /hotkeys 表格是 handleHotkeysCommand 里现拼的模板串,从源码抠出来用桩变量求值。
183
130
  const CJK = /[\u4e00-\u9fff\u3000-\u303f\uff00-\uffef]/;
184
- const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
185
131
  const prevLang = process.env.U1S1_LANG;
186
132
  const interactiveSrc = readFileSync(join(piRoot, "dist/modes/interactive/interactive-mode.js"), "utf8");
187
133
  const hotkeysSrc = interactiveSrc.slice(
@@ -229,8 +175,8 @@ try {
229
175
  }
230
176
 
231
177
  if (failed > 0) {
232
- console.error(`\n渲染回归测试失败:${failed} 项。多半是 pi 升级后补丁没跟上,或扩展渲染被改动。`);
233
- console.error("对照 patches/@earendil-works__pi-*.patch、patch-pi.js 和 agent-setup.ts 里的 writeCompactUiExtension 检查。");
178
+ console.error(`\n渲染回归测试失败:${failed} 项。多半是 pi 升级后补丁没跟上,或界面又被改成隐藏工具行/思考块。`);
179
+ console.error("对照 patches/@earendil-works__pi-*.patch 与 patch-pi.js 检查。");
234
180
  process.exit(1);
235
181
  }
236
182
  console.log("\n渲染回归测试全部通过 ✓");
@@ -1,67 +0,0 @@
1
- import { type ParentModelRef } from "../subagent.js";
2
- /** 整个 run 的默认墙钟上限。 */
3
- export declare const WORKFLOW_TIMEOUT_MS: number;
4
- /** 默认整个 run 的 token 预算;0 或负数表示不设限。 */
5
- export declare const WORKFLOW_DEFAULT_BUDGET_TOKENS = 20000000;
6
- interface ProgressEntry {
7
- key: string;
8
- /** 任务摘要(前 120 字),调试/排查用,key 才是身份。 */
9
- task: string;
10
- ok: boolean;
11
- text: string;
12
- ms: number;
13
- }
14
- /** JSONL 进度存档:一行一个已完成的子任务,断点续跑时按 key 跳过。 */
15
- export declare class ProgressStore {
16
- #private;
17
- readonly path: string;
18
- constructor(path: string);
19
- getSuccessful(key: string): ProgressEntry | undefined;
20
- append(entry: ProgressEntry): void;
21
- }
22
- /** 静态白名单校验:语法能编译 + 不碰沙箱之外的任何能力。 */
23
- export declare function validateScript(code: string): string[];
24
- export interface WorkflowRunResult {
25
- /** 给主 agent 的完整报告(markdown)。 */
26
- report: string;
27
- details: {
28
- spawns: number;
29
- ok: number;
30
- failed: number;
31
- cached: number;
32
- seconds: number;
33
- aborted: boolean;
34
- };
35
- }
36
- export interface WorkflowProgress {
37
- spawns: number;
38
- ok: number;
39
- failed: number;
40
- cached: number;
41
- running: number;
42
- spentTokens: number;
43
- budgetTokens: number;
44
- }
45
- export interface WorkflowRunOptions {
46
- /** 脚本源码(调用方负责落盘)。 */
47
- code: string;
48
- /** 进度存档路径;同名文件存在且 resume=true 时跳过已完成任务。 */
49
- progressPath: string;
50
- resume?: boolean;
51
- parentModel: ParentModelRef;
52
- /** 主会话的项目信任裁决;子 agent 与临时 worktree 都沿用它,缺省按 trust.json 非交互裁决。 */
53
- projectTrusted?: boolean;
54
- signal?: AbortSignal;
55
- timeoutMs?: number;
56
- /** 整个 run 的 token 上限;缺省 WORKFLOW_DEFAULT_BUDGET_TOKENS,<=0 不限。 */
57
- budgetTokens?: number;
58
- /** 每次子任务状态变化时回调(工具层节流后转成流式进度)。 */
59
- onProgress?: (p: WorkflowProgress) => void;
60
- }
61
- /** 执行一个 workflow 脚本,返回给主对话的报告。不抛错——失败也进报告让模型自行修复。 */
62
- export declare function runWorkflow(opts: WorkflowRunOptions): Promise<WorkflowRunResult>;
63
- /** 工作区目录:项目根 .u1s1/workflows/(脚本 + 进度存档都在这里)。 */
64
- export declare function workflowsDir(): string;
65
- /** 内联脚本落盘,返回脚本路径(进度存档按同名约定派生)。 */
66
- export declare function saveWorkflowScript(code: string): string;
67
- export {};