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
@@ -1,4 +1,5 @@
1
1
  import { loadMcpConfig, loadMcpToolCache, sameToolList, saveMcpToolCache, } from "./config.js";
2
+ import { tr } from "../i18n.js";
2
3
  import { McpClient } from "./client.js";
3
4
  import { createMcpToolSet } from "./tools.js";
4
5
  /**
@@ -63,7 +64,7 @@ export async function registerMcpServers(pi) {
63
64
  catch (err) {
64
65
  const message = err instanceof Error ? err.message : String(err);
65
66
  result.failed.push({ server, error: message });
66
- warnings.push(`MCP 服务 ${server} 连接失败,本次会话不可用:${message.split("\n")[0]}\n 用 u1s1 mcp test ${server} 排查`);
67
+ warnings.push(tr(`MCP 服务 ${server} 连接失败,本次会话不可用:${message.split("\n")[0]}\n 用 u1s1 mcp test ${server} 排查`, `MCP server ${server} failed to connect and is unavailable this session: ${message.split("\n")[0]}\n Run u1s1 mcp test ${server} to troubleshoot`));
67
68
  }
68
69
  }));
69
70
  if (cacheDirty) {
@@ -123,6 +124,7 @@ async function refreshCache(config, cache, warn) {
123
124
  }
124
125
  catch { }
125
126
  }
126
- if (changed.length)
127
- warn(`MCP 服务 ${changed.join("、")} 的工具列表有变化,重新进入会话后生效`);
127
+ if (changed.length) {
128
+ warn(tr(`MCP 服务 ${changed.join("、")} 的工具列表有变化,重新进入会话后生效`, `Tool list changed for MCP server ${changed.join(", ")}; takes effect in your next session`));
129
+ }
128
130
  }
@@ -9,6 +9,8 @@ import type { McpServerConfig, McpToolInfo } from "./config.js";
9
9
  */
10
10
  export declare const MCP_MAX_TEXT_CHARS = 100000;
11
11
  export declare function mcpToolName(server: string, tool: string): string;
12
+ /** pi 内置 MCP 给模型的工具名(mcp__<服务>__<工具>),u1s1 mcp list/test 展示用。 */
13
+ export declare function piMcpToolName(server: string, tool: string): string;
12
14
  export declare function truncateMcpText(text: string, max?: number): string;
13
15
  /** 模型 API 只认 object 顶层;$schema 之类元字段去掉,缺 properties 补空 */
14
16
  export declare function toParameterSchema(inputSchema: Record<string, unknown>): TSchema;
package/dist/mcp/tools.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Text } from "@earendil-works/pi-tui";
2
+ import { tr } from "../i18n.js";
2
3
  import { compactResultRender } from "../tools.js";
3
4
  import { McpClient } from "./client.js";
4
5
  /**
@@ -12,10 +13,14 @@ export function mcpToolName(server, tool) {
12
13
  const clean = tool.replace(/[^a-zA-Z0-9_-]+/g, "_").replace(/^_+|_+$/g, "") || "tool";
13
14
  return `mcp_${server}_${clean}`.slice(0, MODEL_TOOL_NAME_MAX);
14
15
  }
16
+ /** pi 内置 MCP 给模型的工具名(mcp__<服务>__<工具>),u1s1 mcp list/test 展示用。 */
17
+ export function piMcpToolName(server, tool) {
18
+ return `mcp__${server}__${tool}`.replace(/[^A-Za-z0-9_]/g, "_");
19
+ }
15
20
  export function truncateMcpText(text, max = MCP_MAX_TEXT_CHARS) {
16
21
  if (text.length <= max)
17
22
  return text;
18
- return `${text.slice(0, max)}\n\n…(MCP 工具返回内容过长已截断,共 ${text.length} 字符,保留前 ${max} 字符)`;
23
+ return `${text.slice(0, max)}\n\n${tr(`…(MCP 工具返回内容过长已截断,共 ${text.length} 字符,保留前 ${max} 字符)`, `… (MCP tool output too long, truncated: ${text.length} chars total, first ${max} kept)`)}`;
19
24
  }
20
25
  /** 模型 API 只认 object 顶层;$schema 之类元字段去掉,缺 properties 补空 */
21
26
  export function toParameterSchema(inputSchema) {
@@ -46,12 +51,12 @@ export function createMcpToolSet(server, config, tools, opts = {}) {
46
51
  catch (err) {
47
52
  const message = err instanceof Error ? err.message : String(err);
48
53
  // 进程退出 / 连接断了:丢掉旧客户端,下次调用重新拉起
49
- if (/已退出|已关闭|stdin/.test(message)) {
54
+ if (/已退出|已关闭|exited|closed|stdin/.test(message)) {
50
55
  const old = client;
51
56
  client = new McpClient(server, config, opts);
52
57
  old.close().catch(() => { });
53
58
  }
54
- throw new Error(`MCP 服务 ${server} 调用 ${tool.name} 失败:${message}`, { cause: err });
59
+ throw new Error(tr(`MCP 服务 ${server} 调用 ${tool.name} 失败:${message}`, `MCP server ${server}: calling ${tool.name} failed: ${message}`), { cause: err });
55
60
  }
56
61
  }
57
62
  for (const tool of tools) {
@@ -69,14 +74,14 @@ export function createMcpToolSet(server, config, tools, opts = {}) {
69
74
  renderResult(result, options, theme, context) {
70
75
  const d = result.details;
71
76
  const args = d?.args ? ` ${d.args}` : "";
72
- const n = typeof d?.chars === "number" ? ` · ${d.chars} 字符${d.truncated ? "(已截断)" : ""}` : "";
77
+ const n = typeof d?.chars === "number" ? ` · ${tr(`${d.chars} 字符`, `${d.chars} chars`)}${d.truncated ? tr("(已截断)", " (truncated)") : ""}` : "";
73
78
  return compactResultRender({ result, options, theme, context, summaryLine: `🔌 ${server}:${tool.name}${args}${n}` });
74
79
  },
75
80
  async execute(_toolCallId, params, signal) {
76
81
  const args = (params && typeof params === "object" ? params : {});
77
82
  const res = await call(tool, args, signal);
78
83
  if (res.isError)
79
- throw new Error(`MCP 服务 ${server} 的 ${tool.name} 返回错误:${res.text || "(无详情)"}`);
84
+ throw new Error(tr(`MCP 服务 ${server} 的 ${tool.name} 返回错误:${res.text || "(无详情)"}`, `MCP server ${server}: ${tool.name} returned an error: ${res.text || "(no details)"}`));
80
85
  const text = res.text || "(empty result)";
81
86
  return {
82
87
  content: [{ type: "text", text: truncateMcpText(text) }],
package/dist/model.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import { fetchModels, loadCustomEndpoints } from "./api.js";
2
2
  import { apiModelToDef, CUSTOM_ENDPOINTS, findModel, loadConfig, MODELS, persistPreferredModel, PROVIDER_ID, resolvePreferredModel, setModelsFromApi, } from "./config.js";
3
3
  import { siteLinks, siteOf } from "./site.js";
4
+ import { tr } from "./i18n.js";
4
5
  /** Refresh official models while preserving the built-in list as an explicit offline fallback. */
5
6
  export async function refreshOfficialModels(cfg, fetcher = fetchModels) {
6
7
  try {
7
8
  const { models } = await fetcher(cfg);
8
9
  if (models.length === 0)
9
- return "线上模型列表为空";
10
+ return tr("线上模型列表为空", "the online model list is empty");
10
11
  setModelsFromApi(models.map(apiModelToDef));
11
12
  return null;
12
13
  }
@@ -25,7 +26,7 @@ export async function modelCommand(nameOrAlias) {
25
26
  loadCustomEndpoints(cfg),
26
27
  ]);
27
28
  if (officialModelsError) {
28
- console.error(` 获取线上模型列表失败,使用内置列表:${officialModelsError}`);
29
+ console.error(tr(` 获取线上模型列表失败,使用内置列表:${officialModelsError}`, ` Couldn't fetch the online model list; using the built-in list: ${officialModelsError}`));
29
30
  }
30
31
  const current = resolvePreferredModel(cfg);
31
32
  if (!nameOrAlias) {
@@ -34,26 +35,26 @@ export async function modelCommand(nameOrAlias) {
34
35
  const mark = current.provider === PROVIDER_ID && m.id === current.id ? "●" : " ";
35
36
  console.log(` ${mark} ${m.aliases[0].padEnd(10)} ${m.name}`);
36
37
  // 价格来自网关目录;离线兜底目录不带价格,只列名字与提示
37
- const price = m.cost ? `$${m.cost.input}/$${m.cost.output} 每百万 token` : "";
38
+ const price = m.cost ? tr(`$${m.cost.input}/$${m.cost.output} 每百万 token`, `$${m.cost.input}/$${m.cost.output} per 1M tokens`) : "";
38
39
  const detail = [m.note, price].filter(Boolean).join(" · ");
39
40
  if (detail)
40
41
  console.log(` ${detail}`);
41
42
  }
42
43
  for (const ep of CUSTOM_ENDPOINTS) {
43
44
  console.log("");
44
- console.log(` ─ 自定义端点 ${ep.name}(${ep.baseUrl},走本地直连不计费)`);
45
+ console.log(tr(` ─ 自定义端点 ${ep.name}(${ep.baseUrl},走本地直连不计费)`, ` ─ Custom endpoint ${ep.name} (${ep.baseUrl}, direct connection, not billed)`));
45
46
  for (const m of ep.models) {
46
47
  const mark = current.provider === ep.id && m.id === current.id ? "●" : " ";
47
48
  console.log(` ${mark} ${m.id.padEnd(10)} ${m.name === m.id ? "" : m.name}`);
48
49
  }
49
50
  }
50
51
  console.log("");
51
- console.log(" 切换:u1s1 model <model-id>(对话里 /model 同样会记住)");
52
+ console.log(tr(" 切换:u1s1 model <model-id>(对话里 /model 同样会记住)", " Switch: u1s1 model <model-id> (/model in a session is remembered too)"));
52
53
  if (CUSTOM_ENDPOINTS.length) {
53
- console.log(" 端点模型重名时可加限定:u1s1 model 端点名:模型id");
54
+ console.log(tr(" 端点模型重名时可加限定:u1s1 model 端点名:模型id", " If endpoint model names clash, qualify them: u1s1 model <endpoint>:<model-id>"));
54
55
  }
55
56
  else {
56
- console.log(` 想接自己的 API(含本机 Ollama)?去 ${siteLinks(siteOf(cfg)).dashboard} 配「自定义模型端点」`);
57
+ console.log(tr(` 想接自己的 API(含本机 Ollama)?去 ${siteLinks(siteOf(cfg)).dashboard} 配「自定义模型端点」`, ` Want to use your own API (including local Ollama)? Add a custom model endpoint at ${siteLinks(siteOf(cfg)).dashboard}`));
57
58
  }
58
59
  console.log("");
59
60
  return;
@@ -62,14 +63,14 @@ export async function modelCommand(nameOrAlias) {
62
63
  if (!m) {
63
64
  const custom = CUSTOM_ENDPOINTS.flatMap((e) => e.models.map((x) => x.id));
64
65
  const all = [...MODELS.map((x) => x.aliases[0]), ...custom];
65
- console.error(` 没有叫「${nameOrAlias}」的模型,可选:${all.join(" / ")}`);
66
+ console.error(tr(` 没有叫「${nameOrAlias}」的模型,可选:${all.join(" / ")}`, ` No model named "${nameOrAlias}". Available: ${all.join(" / ")}`));
66
67
  process.exit(1);
67
68
  }
68
69
  persistPreferredModel(cfg, m.provider, m.id);
69
70
  if (m.provider === PROVIDER_ID) {
70
- console.log(` ✓ 默认模型已切到 ${m.model.name}${m.model.note ? `(${m.model.note})` : ""}`);
71
+ console.log(tr(` ✓ 默认模型已切到 ${m.model.name}${m.model.note ? `(${m.model.note})` : ""}`, ` ✓ Default model set to ${m.model.name}${m.model.note ? ` (${m.model.note})` : ""}`));
71
72
  }
72
73
  else {
73
- console.log(` ✓ 默认模型已切到 ${m.model.name}(自定义端点 ${m.providerName},本地直连不计费)`);
74
+ console.log(tr(` ✓ 默认模型已切到 ${m.model.name}(自定义端点 ${m.providerName},本地直连不计费)`, ` ✓ Default model set to ${m.model.name} (custom endpoint ${m.providerName}, direct connection, not billed)`));
74
75
  }
75
76
  }
package/dist/nudges.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { SiteId } from "./site.js";
1
2
  /**
2
3
  * 「只提示一次」的机器级标记,落在 ~/.u1s1/nudges.json,CLI 与桌面版共用
3
4
  * (桌面版经 u1s1-cli/embed 拿到同一份 helper)。值是 ISO 时间戳,便于事后
@@ -16,6 +17,11 @@ export declare const nudgesFile: string;
16
17
  export declare function readNudges(file?: string): NudgeState;
17
18
  export declare function hasNudge(key: NudgeKey, file?: string): boolean;
18
19
  export declare function markNudge(key: NudgeKey, file?: string): void;
19
- /** 会话里首次写/改文件后要不要提示 deploy:没提示过、也没部署过才提示。 */
20
- export declare function shouldShowDeployHint(file?: string): boolean;
20
+ /**
21
+ * 会话里首次写/改文件后要不要提示 deploy:没提示过、也没部署过才提示。
22
+ * 国际站没有站点托管(网关拒绝 deploy),一律不提示。
23
+ */
24
+ export declare function shouldShowDeployHint(file?: string, site?: SiteId): boolean;
25
+ /** 老导出,固定中文;新代码用 deployHintText()(按当前语言现取)。 */
21
26
  export declare const DEPLOY_HINT_TEXT = "\u505A\u597D\u4E86\u60F3\u7ED9\u670B\u53CB\u770B\uFF1F\u8F93\u5165 /exit \u56DE\u5230\u7EC8\u7AEF,\u8FD0\u884C u1s1 deploy \u4E00\u952E\u4E0A\u7EBF";
27
+ export declare function deployHintText(): string;
package/dist/nudges.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { u1s1Dir } from "./config.js";
3
+ import { activeSite, u1s1Dir } from "./config.js";
4
+ import { tr } from "./i18n.js";
4
5
  export const nudgesFile = join(u1s1Dir, "nudges.json");
5
6
  export function readNudges(file = nudgesFile) {
6
7
  try {
@@ -32,9 +33,27 @@ export function markNudge(key, file = nudgesFile) {
32
33
  // ignore
33
34
  }
34
35
  }
35
- /** 会话里首次写/改文件后要不要提示 deploy:没提示过、也没部署过才提示。 */
36
- export function shouldShowDeployHint(file = nudgesFile) {
36
+ /**
37
+ * 会话里首次写/改文件后要不要提示 deploy:没提示过、也没部署过才提示。
38
+ * 国际站没有站点托管(网关拒绝 deploy),一律不提示。
39
+ */
40
+ export function shouldShowDeployHint(file = nudgesFile, site) {
41
+ let siteId = site;
42
+ if (!siteId) {
43
+ try {
44
+ siteId = activeSite().id;
45
+ }
46
+ catch {
47
+ siteId = "cn";
48
+ }
49
+ }
50
+ if (siteId === "intl")
51
+ return false;
37
52
  const state = readNudges(file);
38
53
  return state.deploy_hint_shown === undefined && state.deploy_done === undefined;
39
54
  }
55
+ /** 老导出,固定中文;新代码用 deployHintText()(按当前语言现取)。 */
40
56
  export const DEPLOY_HINT_TEXT = "做好了想给朋友看?输入 /exit 回到终端,运行 u1s1 deploy 一键上线";
57
+ export function deployHintText() {
58
+ return tr(DEPLOY_HINT_TEXT, "Want to show it off? Type /exit to return to the terminal, then run u1s1 deploy to publish it.");
59
+ }
@@ -5,6 +5,7 @@ import { join } from "node:path";
5
5
  import { Readable, Transform } from "node:stream";
6
6
  import { pipeline } from "node:stream/promises";
7
7
  import { agentDir } from "./config.js";
8
+ import { tr } from "./i18n.js";
8
9
  /**
9
10
  * 预 seed pi 的搜索组件(fd / ripgrep)到 ~/.u1s1/agent/bin。
10
11
  *
@@ -104,7 +105,7 @@ function findBinary(root, name) {
104
105
  async function install(tool, baseUrl, binName) {
105
106
  const asset = tool.asset(tool.version);
106
107
  if (!asset)
107
- throw new Error(`不支持的平台 ${platform()}/${arch()}`);
108
+ throw new Error(tr(`不支持的平台 ${platform()}/${arch()}`, `Unsupported platform ${platform()}/${arch()}`));
108
109
  mkdirSync(binDir, { recursive: true });
109
110
  const res = await fetch(new URL(`/tools/${asset}`, baseUrl), {
110
111
  signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS),
@@ -114,7 +115,7 @@ async function install(tool, baseUrl, binName) {
114
115
  const declaredLength = Number(res.headers.get("content-length"));
115
116
  if (Number.isFinite(declaredLength) && declaredLength > MAX_TOOL_ARCHIVE_BYTES) {
116
117
  void res.body.cancel("tool archive exceeded limit").catch(() => { });
117
- throw new Error(`下载文件超过 ${MAX_TOOL_ARCHIVE_BYTES} bytes`);
118
+ throw new Error(tr(`下载文件超过 ${MAX_TOOL_ARCHIVE_BYTES} bytes`, `Download exceeds ${MAX_TOOL_ARCHIVE_BYTES} bytes`));
118
119
  }
119
120
  const archive = join(binDir, asset);
120
121
  // fd/rg 并行安装,解压目录按工具名+pid 隔离,避免互踩
@@ -130,10 +131,10 @@ async function install(tool, baseUrl, binName) {
130
131
  await pipeline(Readable.fromWeb(res.body), limiter, createWriteStream(archive));
131
132
  mkdirSync(extractDir, { recursive: true });
132
133
  if (!extract(archive, extractDir))
133
- throw new Error(`解压失败 ${asset}`);
134
+ throw new Error(tr(`解压失败 ${asset}`, `Failed to extract ${asset}`));
134
135
  const found = findBinary(extractDir, binName);
135
136
  if (!found)
136
- throw new Error(`包内没有 ${binName}`);
137
+ throw new Error(tr(`包内没有 ${binName}`, `${binName} not found in the archive`));
137
138
  renameSync(found, join(binDir, binName));
138
139
  if (platform() !== "win32")
139
140
  chmodSync(join(binDir, binName), 0o755);
@@ -151,10 +152,10 @@ export async function ensureSearchTools(cfg) {
151
152
  if (missing.length === 0)
152
153
  return;
153
154
  // 这是新手首启最早看到的输出之一:说清是首次准备、不用管;fd/rg 等术语不外露
154
- console.log(" 首次运行,正在准备文件搜索组件(只需一次)…");
155
+ console.log(tr(" 首次运行,正在准备文件搜索组件(只需一次)…", " First run: setting up file search components (one-time)…"));
155
156
  const results = await Promise.allSettled(missing.map((t) => install(t, cfg.baseUrl, t.bin + ext)));
156
157
  if (results.some((r) => r.status === "rejected")) {
157
- console.log(" 部分搜索组件暂时没装上,不影响使用,之后会自动重试。");
158
+ console.log(tr(" 部分搜索组件暂时没装上,不影响使用,之后会自动重试。", " Some search components couldn't be installed yet. You can keep working; we'll retry automatically later."));
158
159
  }
159
160
  }
160
161
  catch {
@@ -2,6 +2,7 @@ import { spawn } from "node:child_process";
2
2
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { join, win32 } from "node:path";
4
4
  import { agentDir, agentSettingsFile } from "./config.js";
5
+ import { tr } from "./i18n.js";
5
6
  /** System32/Sysnative 的 bash.exe 是 WSL 转发器,不是真 bash(与 pi 同款判断)。 */
6
7
  function isWslRelayBash(p) {
7
8
  const normalized = p.replace(/\//g, "\\").toLowerCase();
@@ -139,8 +140,8 @@ function persistShellPath(p) {
139
140
  return true;
140
141
  }
141
142
  function adviseInstallGitBash() {
142
- console.error(" ⚠ 未找到 Git Bash,将使用 WSL 的 bash:命令会在 WSL 的 Linux 里执行,不是 Windows 本机。");
143
- console.error(" 建议安装 Git for Windows(PowerShell 运行: winget install --id Git.Git)后重启 u1s1。");
143
+ console.error(tr(" ⚠ 未找到 Git Bash,将使用 WSL 的 bash:命令会在 WSL 的 Linux 里执行,不是 Windows 本机。", " ⚠ Git Bash not found; using WSL's bash. Commands will run inside WSL's Linux, not natively on Windows."));
144
+ console.error(tr(" 建议安装 Git for Windows(PowerShell 运行: winget install --id Git.Git)后重启 u1s1。", " We recommend installing Git for Windows (in PowerShell: winget install --id Git.Git), then restarting u1s1."));
144
145
  }
145
146
  export async function ensureUsableShell() {
146
147
  if (process.platform !== "win32")
@@ -164,7 +165,7 @@ export async function ensureUsableShell() {
164
165
  const candidate = await findUsableGitBash();
165
166
  if (candidate) {
166
167
  persistShellPath(candidate);
167
- console.error(` 检测到 Git Bash,命令改回 Windows 本机执行: ${candidate}`);
168
+ console.error(tr(` 检测到 Git Bash,命令改回 Windows 本机执行: ${candidate}`, ` Found Git Bash; commands will run natively on Windows again: ${candidate}`));
168
169
  return { status: "bash", shellPath: candidate };
169
170
  }
170
171
  adviseInstallGitBash();
@@ -175,7 +176,7 @@ export async function ensureUsableShell() {
175
176
  const adopt = (candidate) => {
176
177
  persistShellPath(candidate);
177
178
  if (typeof configured === "string") {
178
- console.error(` 原 shellPath 已失效,自动改用: ${candidate}`);
179
+ console.error(tr(` 原 shellPath 已失效,自动改用: ${candidate}`, ` The configured shellPath no longer works; switched to: ${candidate}`));
179
180
  }
180
181
  return { status: "bash", shellPath: candidate };
181
182
  };
@@ -195,14 +196,14 @@ export async function ensureUsableShell() {
195
196
  return { status: "bash", shellPath: fallback };
196
197
  }
197
198
  if (fallback) {
198
- console.error(` ⚠ 检测到的 bash(${fallback})无法执行命令,u1s1 的命令工具会全部失败。`);
199
+ console.error(tr(` ⚠ 检测到的 bash(${fallback})无法执行命令,u1s1 的命令工具会全部失败。`, ` ⚠ The bash found (${fallback}) can't run commands, so all of u1s1's command tools will fail.`));
199
200
  if (isWslRelayBash(fallback)) {
200
- console.error(" 它是 WSL 转发器,当前默认发行版里没有 /bin/bash(docker-desktop 被设为默认时就会这样)。");
201
+ console.error(tr(" 它是 WSL 转发器,当前默认发行版里没有 /bin/bash(docker-desktop 被设为默认时就会这样)。", " It's the WSL relay, and the default distro has no /bin/bash (this happens when docker-desktop is the default)."));
201
202
  }
202
203
  }
203
204
  else {
204
- console.error(" ⚠ 没找到 bash,u1s1 无法执行命令。");
205
+ console.error(tr(" ⚠ 没找到 bash,u1s1 无法执行命令。", " ⚠ No bash found; u1s1 can't run commands."));
205
206
  }
206
- console.error(" 修复:PowerShell 里运行 winget install --id Git.Git 安装 Git for Windows,装完重启 u1s1。");
207
+ console.error(tr(" 修复:PowerShell 里运行 winget install --id Git.Git 安装 Git for Windows,装完重启 u1s1。", " Fix: in PowerShell, run winget install --id Git.Git to install Git for Windows, then restart u1s1."));
207
208
  return { status: "none" };
208
209
  }
package/dist/subagent.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { createAgentSession, DefaultResourceLoader, getAgentDir, hasTrustRequiringProjectResources, ModelRuntime, ProjectTrustStore, SessionManager, SettingsManager, } from "@earendil-works/pi-coding-agent";
2
+ import { tr } from "./i18n.js";
2
3
  // ---- 子 agent spawn 基础设施:spawn_subagent 工具与 workflow runner 共用 ----
3
4
  /** 同时在跑的子 agent 上限;超额排队,防止打爆网关。 */
4
5
  export const SUBAGENT_CONCURRENCY = 4;
@@ -20,7 +21,7 @@ export function resolveStoredProjectTrust(cwd, agentDir = getAgentDir()) {
20
21
  return new ProjectTrustStore(agentDir).get(cwd) === true;
21
22
  }
22
23
  function cancelledError(signal) {
23
- return new Error("已取消", { cause: signal.reason });
24
+ return new Error(tr("已取消", "Cancelled"), { cause: signal.reason });
24
25
  }
25
26
  /**
26
27
  * Run one prompt while preserving the reason that stopped it. AgentSession.abort()
@@ -54,7 +55,7 @@ export async function promptSubagentSession(session, task, options) {
54
55
  if (options.signal?.aborted)
55
56
  throw cancelledError(options.signal);
56
57
  if (timedOut)
57
- throw new Error(`子任务超时(${options.timeoutMs}ms)`, { cause: promptError });
58
+ throw new Error(tr(`子任务超时(${options.timeoutMs}ms)`, `Subtask timed out (${options.timeoutMs}ms)`), { cause: promptError });
58
59
  if (promptError)
59
60
  throw promptError;
60
61
  }
@@ -72,7 +73,7 @@ function extractFinalText(messages) {
72
73
  if (text)
73
74
  return text;
74
75
  }
75
- return "(子 agent 结束但没有文本输出)";
76
+ return tr("(子 agent 结束但没有文本输出)", "(sub-agent finished without any text output)");
76
77
  }
77
78
  /**
78
79
  * 汇总会话内全部 assistant 消息的 token 与费用。
@@ -123,7 +124,7 @@ export async function runSubagent(opts) {
123
124
  sharedModelRuntime.getModel(providerId, modelId) ??
124
125
  (slash === -1 ? sharedModelRuntime.getModel("u1s1", opts.model) : undefined);
125
126
  if (!model)
126
- throw new Error(`找不到模型 "${opts.model}"(可用 /model 查看已配置的模型)`);
127
+ throw new Error(tr(`找不到模型 "${opts.model}"(可用 /model 查看已配置的模型)`, `Model "${opts.model}" not found (use /model to see configured models)`));
127
128
  }
128
129
  else if (opts.parentModel) {
129
130
  // 继承主会话模型;解析不到(端点被删等)就落回默认,不硬失败