@epoch-agent/cli 0.1.0 → 0.2.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.
package/dist/index.js CHANGED
@@ -1,59 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  writeProviderKey
4
- } from "./chunk-3SCZQI5W.js";
5
-
4
+ } from "./chunk-UEHBHZCZ.js";
6
5
  // src/index.ts
7
6
  import { Command } from "commander";
8
-
7
+ import { t as t38 } from "@epoch-agent/infra";
9
8
  // src/commands/agents.ts
10
- import { findRole, roleSourceLabel } from "@epoch-agent/core";
11
-
9
+ import { AGENT_ROLE_DIAG_MODULE, findRole, roleSourceLabel } from "@epoch-agent/core";
10
+ import { t as t2 } from "@epoch-agent/infra";
11
+ // src/errors.ts
12
+ import { t } from "@epoch-agent/infra";
12
13
  // src/exit-codes.ts
13
14
  var EXIT_CODES = {
14
- /** 任务完成 */
15
15
  SUCCESS: 0,
16
- /** 通用失败(provider 起不来、运行时报错……) */
17
16
  FAILURE: 1,
18
- /**
19
- * 非交互下有操作因**缺少预授权**被拒。
20
- *
21
- * 只在非交互路径出现:交互模式下用户亲手点的「拒绝」是他的决定,
22
- * 不算 agent 被卡住,仍然退 0。
23
- */
24
17
  PERMISSION_DENIED: 3,
25
- /**
26
- * 企业托管设置挡下了这次启动(方案 22)。
27
- *
28
- * 和 FAILURE 分开的理由同上:这不是「epoch 坏了」,是**策略生效了**。
29
- * 管理员按机器批量铺开一条 `disableBypassPermissionsMode` 之后,
30
- * 要能从退出码上一眼看出哪些机器是被自己的策略拦下的,
31
- * 而不是去每台机器上读 stderr 猜。
32
- */
33
18
  MANAGED_POLICY: 4,
34
- /**
35
- * `--max-turns` / `--max-budget-usd` 触顶,任务**没做完**就停了(方案 28)。
36
- *
37
- * 和 FAILURE 分开:CI 里这不是「epoch 坏了」,是**限额生效了** ——
38
- * 该做的是看一眼输出决定要不要加额重跑,而不是去查日志。
39
- * 跨会话预算(`budget.maxCostUsd`)触顶**不走这个码**,仍然退 0:
40
- * 那是长期配置,每次都退非 0 会让流水线天天红。
41
- *
42
- * ⚠️ 方案 28 立项时写的是 `4`,那是 2026-08-08 的现状(当时只有 0/1/3)。
43
- * `4` 后来被企业托管策略(方案 22)占了,而那个码已经在根 README 和
44
- * MDM 文档里对外承诺过,不能挪。所以这里往后排。
45
- */
46
19
  LIMIT_EXCEEDED: 5,
47
- /**
48
- * `--input-format stream-json` 的输入有坏行(方案 28)。
49
- *
50
- * **不是**用法错误(那是 1):命令敲对了,是宿主写进 stdin 的那一行不合协议。
51
- * 坏行会被报到 stderr 并**跳过**(NDJSON 是按行分帧的,跳一行不会失步),
52
- * 进程接着跑;这个码在收尾时才落。
53
- */
54
20
  INPUT_ERROR: 6
55
21
  };
56
-
57
22
  // src/errors.ts
58
23
  var CliError = class extends Error {
59
24
  constructor(message, exitCode = EXIT_CODES.FAILURE, hint) {
@@ -67,15 +32,12 @@ var CliError = class extends Error {
67
32
  };
68
33
  function isCancellation(err2) {
69
34
  if (!(err2 instanceof Error)) return false;
70
- return err2.name === "ExitPromptError" || err2.name === "AbortError" || // AbortController.abort() 默认抛的就是这个
35
+ return err2.name === "ExitPromptError" || err2.name === "AbortError" ||
71
36
  err2.name === "Error" && err2.message === "The operation was aborted.";
72
37
  }
73
38
  var EXPECTED_FAILURES = {
74
- /** 企业托管策略挡下的启动(方案 22 §2.6)。不是故障,是策略生效了 */
75
39
  ManagedPolicyError: EXIT_CODES.MANAGED_POLICY,
76
- /** `--agent` 给了不认识的角色名(方案 29 验收 #13)。可用角色都在 message 里 */
77
40
  AgentRoleError: EXIT_CODES.FAILURE,
78
- /** `--settings` 指的文件不是合法 JSON(方案 29 验收 #16)。hint 里带行号 */
79
41
  SettingsFileError: EXIT_CODES.FAILURE
80
42
  };
81
43
  function expectedFailureExitCode(err2) {
@@ -88,12 +50,14 @@ function hintOf(err2) {
88
50
  }
89
51
  function reportFatal(err2) {
90
52
  if (isCancellation(err2)) {
91
- process.stderr.write("\n\u5DF2\u53D6\u6D88\n");
53
+ process.stderr.write(`
54
+ ${t("errors.cancelled")}
55
+ `);
92
56
  process.exit(130);
93
57
  }
94
58
  const exitCode = err2 instanceof CliError ? err2.exitCode : expectedFailureExitCode(err2);
95
59
  if (exitCode !== void 0 && err2 instanceof Error) {
96
- process.stderr.write(`\u9519\u8BEF: ${err2.message}
60
+ process.stderr.write(`${t("errors.fatal", { message: err2.message })}
97
61
  `);
98
62
  const hint = hintOf(err2);
99
63
  if (hint) process.stderr.write(`${hint}
@@ -104,7 +68,6 @@ function reportFatal(err2) {
104
68
  `);
105
69
  process.exit(EXIT_CODES.FAILURE);
106
70
  }
107
-
108
71
  // src/commands/agents.ts
109
72
  var log = (msg) => {
110
73
  process.stdout.write(msg + "\n");
@@ -122,65 +85,65 @@ function renderRole(role) {
122
85
  const lines = [];
123
86
  lines.push(` ${role.name} [${roleSourceLabel(role.source)}]`);
124
87
  lines.push(` ${role.description}`);
125
- if (role.tools) {
126
- lines.push(` \u5DE5\u5177\uFF08\u58F0\u660E\uFF09: ${role.tools.join(", ")}`);
127
- } else {
128
- lines.push(" \u5DE5\u5177\uFF08\u58F0\u660E\uFF09: \u7EE7\u627F\u4E3B agent \u7684\u5168\u90E8\u53EF\u7528\u5DE5\u5177");
88
+ lines.push(
89
+ ` ${role.tools ? t2("agents.tools_declared", { tools: role.tools.join(", ") }) : t2("agents.tools_inherited")}`
90
+ );
91
+ if (role.maxTurns !== void 0) {
92
+ lines.push(` ${t2("agents.max_turns", { n: role.maxTurns })}`);
129
93
  }
130
- if (role.maxTurns !== void 0) lines.push(` \u8F6E\u6B21\u4E0A\u9650: ${role.maxTurns}`);
131
94
  return lines;
132
95
  }
133
96
  function renderRoleDetail(role) {
134
97
  const lines = [...renderRole(role), ""];
135
98
  if (role.prompt) {
136
- lines.push(" \u89D2\u8272 prompt:");
99
+ lines.push(` ${t2("agents.prompt_head")}`);
137
100
  for (const line of role.prompt.split("\n")) lines.push(` ${line}`);
138
101
  } else {
139
- lines.push(" \u89D2\u8272 prompt: \u65E0\uFF08\u53EA\u6362\u6389\u5B50 agent \u7684\u8EAB\u4EFD\u884C\uFF0C\u5176\u4F59\u6CBF\u7528\u901A\u7528 prompt\uFF09");
102
+ lines.push(` ${t2("agents.prompt_none")}`);
140
103
  }
141
104
  return lines;
142
105
  }
143
106
  function registerAgentsCommand(program2) {
144
- const agents = program2.command("agents").description("\u5217\u51FA\u53EF\u6D3E\u7ED9\u5B50 agent \u7684\u89D2\u8272\uFF08delegate_task \u7684 role \u53D6\u503C\uFF09").action(async () => {
107
+ const agents = program2.command("agents").description(t2("agents.cmd_root")).action(async () => {
145
108
  await withRuntime((rt) => {
146
109
  if (rt.agentRoles.length === 0) {
147
- log("\u6CA1\u6709\u53EF\u7528\u89D2\u8272\uFF08provider \u8D77\u4E0D\u6765\u65F6 delegate_task \u4E0D\u4F1A\u6CE8\u518C\uFF09");
148
- log("\u8FD0\u884C epoch doctor \u770B\u5177\u4F53\u662F\u54EA\u4E2A\u6A21\u5757\u7684\u95EE\u9898\u3002");
110
+ log(t2("agents.empty"));
111
+ log(t2("agents.empty_hint"));
149
112
  return;
150
113
  }
151
- log(`\u53EF\u6D3E\u89D2\u8272\uFF08${rt.agentRoles.length} \u4E2A\uFF09:
114
+ log(`${t2("agents.list_head", { count: rt.agentRoles.length })}
152
115
  `);
153
116
  for (const role of rt.agentRoles) {
154
117
  for (const line of renderRole(role)) log(line);
155
118
  log("");
156
119
  }
157
- const notes = rt.diagnosticList.filter((d) => d.module === "Agent \u89D2\u8272");
120
+ const notes = rt.diagnosticList.filter((d) => d.module === AGENT_ROLE_DIAG_MODULE);
158
121
  if (notes.length > 0) {
159
- log("\u52A0\u8F7D\u8BF4\u660E:");
122
+ log(t2("agents.load_notes"));
160
123
  for (const d of notes) log(` ${d.detail}`);
161
124
  log("");
162
125
  }
163
- log("\u5B9A\u4E49\u81EA\u5DF1\u7684\u89D2\u8272\uFF1A~/.epoch/agents/<\u540D\u5B57>.md\uFF08\u9879\u76EE\u7EA7\u653E .epoch/agents/\uFF0C\u9700\u4FE1\u4EFB\u5DE5\u4F5C\u533A\uFF09");
164
- log("\u26A0 \u5DE5\u5177\u767D\u540D\u5355\u5BF9\u542B terminal \u7684\u89D2\u8272\u4E0D\u662F\u5B89\u5168\u8FB9\u754C \u2014\u2014 \u6709 shell \u5C31\u80FD\u7ED5\u5F00\u300C\u6CA1\u6709\u5199\u5DE5\u5177\u300D\u3002");
126
+ log(t2("agents.define_hint"));
127
+ log(t2("agents.tools_not_a_boundary"));
165
128
  });
166
129
  });
167
- agents.command("show <name>").description("\u770B\u67D0\u4E2A\u89D2\u8272\u7684\u5B8C\u6574\u5B9A\u4E49\uFF08\u542B\u89D2\u8272 prompt\uFF09").action(async (name) => {
130
+ agents.command("show <name>").description(t2("agents.cmd_show")).action(async (name) => {
168
131
  await withRuntime((rt) => {
169
132
  const role = findRole(rt.agentRoles, name);
170
133
  if (!role) {
171
134
  const available = rt.agentRoles.map((r) => r.name).join(", ");
172
135
  throw new CliError(
173
- `\u6CA1\u6709\u540D\u4E3A "${name}" \u7684 agent \u89D2\u8272`,
136
+ t2("agents.err_not_found", { name }),
174
137
  EXIT_CODES.FAILURE,
175
- available ? `\u53EF\u7528\uFF1A${available}` : "\u4E00\u4E2A\u89D2\u8272\u90FD\u6CA1\u52A0\u8F7D\u51FA\u6765\uFF0C\u8FD0\u884C epoch doctor \u770B\u539F\u56E0"
138
+ available ? t2("agents.err_available", { names: available }) : t2("agents.err_none_loaded")
176
139
  );
177
140
  }
178
141
  for (const line of renderRoleDetail(role)) log(line);
179
142
  });
180
143
  });
181
144
  }
182
-
183
145
  // src/commands/completion.ts
146
+ import { t as t3 } from "@epoch-agent/infra";
184
147
  var SHELLS = ["bash", "zsh", "fish", "powershell"];
185
148
  function longFlags(cmd) {
186
149
  return cmd.options.map((o) => o.long).filter((l) => !!l);
@@ -192,11 +155,6 @@ function toSpec(cmd) {
192
155
  return {
193
156
  name: cmd.name(),
194
157
  description: cmd.description(),
195
- // `--help` 要手工补:commander 把它存在 `_helpOption` 里单独处理,**不进**
196
- // `cmd.options`,所以 longFlags 拿不到。`--version` 反过来是真选项
197
- // (run.ts 注册的,或者 `program.version()` 注册的),已经在里面了。
198
- // 过一次 Set 是防有人又显式声明了同名 flag —— 重复项在 bash 里表现为
199
- // 同一个候选出现两遍
200
158
  options: [.../* @__PURE__ */ new Set([...longFlags(cmd), "--help"])],
201
159
  subcommands: visibleSubcommands(cmd).map((sub) => toSpec(sub))
202
160
  };
@@ -226,9 +184,7 @@ function bashBranch(cmd) {
226
184
  ].join("\n");
227
185
  }
228
186
  function bashScript(root) {
229
- return `# epoch bash \u8865\u5168\u3002\u88C5\u6CD5\uFF1A
230
- # epoch completion bash > /usr/local/etc/bash_completion.d/epoch
231
- # \u6216\u76F4\u63A5 eval "$(epoch completion bash)"
187
+ return `${t3("completion.head_bash")}
232
188
  _epoch_completion() {
233
189
  local cur words
234
190
  cur="\${COMP_WORDS[COMP_CWORD]}"
@@ -243,7 +199,6 @@ ${root.subcommands.map(bashBranch).join("\n")}
243
199
  *) words='' ;;
244
200
  esac
245
201
 
246
- # \u6CA1\u6709\u5019\u9009\u65F6\u4EA4\u8FD8\u7ED9\u9ED8\u8BA4\u7684\u6587\u4EF6\u540D\u8865\u5168\uFF1A\`epoch "\u5206\u6790 <\u6587\u4EF6>"\` \u8981\u80FD\u8865\u8DEF\u5F84
247
202
  if [[ -z "$words" ]]; then
248
203
  COMPREPLY=($(compgen -f -- "$cur"))
249
204
  else
@@ -271,8 +226,7 @@ function zshBranch(cmd) {
271
226
  function zshScript(root) {
272
227
  const cmds = root.subcommands.map((c) => ` '${c.name}:${q(c.description)}'`).join("\n");
273
228
  return `#compdef epoch
274
- # epoch zsh \u8865\u5168\u3002\u88C5\u6CD5\uFF1A\u628A\u672C\u6587\u4EF6\u5B58\u6210 fpath \u91CC\u7684 _epoch\uFF0C
275
- # \u6216\u5728 .zshrc \u91CC eval "$(epoch completion zsh)"
229
+ ${t3("completion.head_zsh")}
276
230
  _epoch_completion() {
277
231
  local -a cmds opts
278
232
  cmds=(
@@ -280,7 +234,7 @@ ${cmds}
280
234
  )
281
235
 
282
236
  if (( CURRENT == 2 )); then
283
- _describe -t commands '\u547D\u4EE4' cmds
237
+ _describe -t commands '${t3("completion.zsh_group_commands")}' cmds
284
238
  compadd -- ${root.options.join(" ")}
285
239
  return
286
240
  fi
@@ -312,11 +266,7 @@ function fishLines(cmd, seen) {
312
266
  return lines;
313
267
  }
314
268
  function fishScript(root) {
315
- return [
316
- "# epoch fish \u8865\u5168\u3002\u88C5\u6CD5\uFF1A",
317
- "# epoch completion fish > ~/.config/fish/completions/epoch.fish",
318
- ...fishLines(root, "__fish_use_subcommand")
319
- ].join("\n");
269
+ return [t3("completion.head_fish"), ...fishLines(root, "__fish_use_subcommand")].join("\n");
320
270
  }
321
271
  function powershellEntries(cmd, prefix) {
322
272
  const lines = [` '${prefix}' = '${words(cmd)}'`];
@@ -370,17 +320,20 @@ function isShellName(value) {
370
320
  return SHELLS.includes(value);
371
321
  }
372
322
  function registerCompletionCommand(program2) {
373
- program2.command("completion").description(`\u751F\u6210 shell \u8865\u5168\u811A\u672C\uFF08${SHELLS.join(" / ")}\uFF09`).argument("<shell>", SHELLS.join(" / ")).action((shell) => {
323
+ program2.command("completion").description(t3("completion.cmd_root", { shells: SHELLS.join(" / ") })).argument("<shell>", SHELLS.join(" / ")).action((shell) => {
374
324
  if (!isShellName(shell)) {
375
- throw new CliError(`\u4E0D\u652F\u6301\u7684 shell: ${shell}`, 1, `\u53EF\u9009: ${SHELLS.join(" / ")}`);
325
+ throw new CliError(
326
+ t3("completion.err_unsupported", { shell }),
327
+ 1,
328
+ t3("completion.err_choices", { choices: SHELLS.join(" / ") })
329
+ );
376
330
  }
377
331
  process.stdout.write(renderCompletion(program2, shell));
378
332
  });
379
333
  }
380
-
381
334
  // src/commands/config.ts
382
335
  import { existsSync as existsSync2, readFileSync } from "fs";
383
- import { maskApiKey, t } from "@epoch-agent/infra";
336
+ import { maskApiKey, t as t4 } from "@epoch-agent/infra";
384
337
  import {
385
338
  API_KEY_ENV_VARS,
386
339
  isPermissionLevel,
@@ -390,7 +343,6 @@ import {
390
343
  PROVIDER_TYPES,
391
344
  SHELL_KINDS
392
345
  } from "@epoch-agent/protocol";
393
-
394
346
  // src/files/config-yaml.ts
395
347
  import {
396
348
  DEFAULT_YAML,
@@ -402,7 +354,6 @@ import {
402
354
  unsetSectionField,
403
355
  writeConfigYaml
404
356
  } from "@epoch-agent/infra";
405
-
406
357
  // src/paths.ts
407
358
  import { existsSync, mkdirSync } from "fs";
408
359
  import { hasProviderCredentials } from "@epoch-agent/core";
@@ -416,7 +367,6 @@ function ensureEpochHome() {
416
367
  function hasCredentials() {
417
368
  return hasProviderCredentials(EPOCH_HOME);
418
369
  }
419
-
420
370
  // src/commands/config.ts
421
371
  var log2 = (msg) => {
422
372
  process.stdout.write(msg + "\n");
@@ -442,65 +392,70 @@ function maskSecrets(yaml) {
442
392
  });
443
393
  }
444
394
  function registerConfigCommand(program2) {
445
- const cmd = program2.command("config").description(t("cli.config.summary"));
446
- cmd.command("show").description(t("cli.config.show")).option("--raw", t("cli.config.opt_raw")).action((opts) => {
395
+ const cmd = program2.command("config").description(t4("cli.config.summary"));
396
+ cmd.command("show").description(t4("cli.config.show")).option("--raw", t4("cli.config.opt_raw")).action((opts) => {
447
397
  if (!existsSync2(CONFIG_PATH)) {
448
- log2("\u5C1A\u672A\u914D\u7F6E\u3002\u8FD0\u884C epoch model \u521D\u59CB\u5316\u3002");
398
+ log2(t4("cli.config.not_configured"));
449
399
  } else {
450
400
  const yaml = readFileSync(CONFIG_PATH, "utf-8").trim();
451
401
  log2(opts.raw ? yaml : maskSecrets(yaml));
452
402
  }
453
403
  });
454
- cmd.command("path").description(t("cli.config.path")).action(() => log2(CONFIG_PATH));
455
- cmd.command("get").description(t("cli.config.get")).argument("<key>", t("cli.config.keys")).action((key) => {
404
+ cmd.command("path").description(t4("cli.config.path")).action(() => log2(CONFIG_PATH));
405
+ cmd.command("get").description(t4("cli.config.get")).argument("<key>", t4("cli.config.keys")).action((key) => {
456
406
  const value = readKey(
457
407
  readConfigYaml(CONFIG_PATH),
458
408
  key === "provider" ? "provider.type" : key
459
409
  );
460
- if (value === void 0) throw new CliError(`${key} \u672A\u8BBE\u7F6E`);
410
+ if (value === void 0) throw new CliError(t4("cli.config.unset_key", { key }));
461
411
  log2(value);
462
412
  });
463
- cmd.command("set").description(t("cli.config.set")).argument("<key>", t("cli.config.keys")).argument("<value>", t("cli.config.value")).action((key, value) => {
413
+ cmd.command("set").description(t4("cli.config.set")).argument("<key>", t4("cli.config.keys")).argument("<value>", t4("cli.config.value")).action((key, value) => {
464
414
  ensureEpochHome();
465
415
  writeConfigYaml(CONFIG_PATH, applySet(readConfigYaml(CONFIG_PATH), key, value));
466
416
  log2(`\u2705 ${key} = ${value}`);
467
417
  });
468
- cmd.command("unset").description(t("cli.config.unset")).argument("<key>", t("cli.config.keys_unset")).action((key) => {
418
+ cmd.command("unset").description(t4("cli.config.unset")).argument("<key>", t4("cli.config.keys_unset")).action((key) => {
469
419
  ensureEpochHome();
470
420
  writeConfigYaml(CONFIG_PATH, applyUnset(readConfigYaml(CONFIG_PATH), key));
471
- log2(`\u2705 \u5DF2\u5220\u9664 ${key}`);
421
+ log2(`\u2705 ${t4("cli.config.removed", { key })}`);
472
422
  });
473
423
  registerSchemaCommand(cmd);
474
424
  registerSecretCommand(cmd);
475
425
  }
476
426
  function registerSchemaCommand(cmd) {
477
- cmd.command("schema").description(t("cli.config.schema")).action(async () => {
427
+ cmd.command("schema").description(t4("cli.config.schema")).action(async () => {
478
428
  const { resolveProjectRoot: resolveProjectRoot4 } = await import("@epoch-agent/core");
479
429
  const root = resolveProjectRoot4(process.cwd());
480
430
  const { writeProjectSchemas } = await import("./schema-file-DH6X4N4K.js");
481
431
  const written = await writeProjectSchemas(root);
482
432
  for (const entry of written) log2(`\u2705 ${entry.path}`);
483
433
  log2("");
484
- log2(t("cli.config.schema_paste_hint"));
434
+ log2(t4("cli.config.schema_paste_hint"));
485
435
  log2(` { "$schema": "${written[0]?.reference ?? ""}", \u2026 }`);
486
436
  });
487
437
  }
488
438
  function registerSecretCommand(cmd) {
489
- cmd.command("secret").description(t("cli.config.secret")).option("--export", t("cli.config.opt_export")).action(async (opts) => {
439
+ cmd.command("secret").description(t4("cli.config.secret")).option("--export", t4("cli.config.opt_export")).action(async (opts) => {
490
440
  const { ensureSecretsReady } = await import("@epoch-agent/runtime");
491
441
  await ensureSecretsReady();
492
442
  const { getSecretStore } = await import("@epoch-agent/infra");
493
443
  const store = getSecretStore();
494
- if (!store) throw new CliError("\u51ED\u636E\u5B58\u50A8\u672A\u5C31\u7EEA");
444
+ if (!store) throw new CliError(t4("cli.config.secret_not_ready"));
495
445
  if (opts.export) await exportSecrets(store);
496
446
  else await printSecretStatus(store);
497
447
  });
498
448
  }
499
449
  async function printSecretStatus(store) {
500
- log2(`${store.encrypted ? "\u2713" : "\u26A0"} \u540E\u7AEF: ${store.backend} \u2014\u2014 ${store.detail}`);
450
+ log2(
451
+ `${store.encrypted ? "\u2713" : "\u26A0"} ${t4("cli.config.secret_backend", {
452
+ backend: store.backend,
453
+ detail: store.detail
454
+ })}`
455
+ );
501
456
  const names = (await store.list()).filter((n) => API_KEY_ENV_VARS.includes(n));
502
457
  if (names.length === 0) {
503
- log2(" (\u8FD8\u6CA1\u5B58\u8FC7 provider \u51ED\u636E\uFF0C\u8DD1 epoch model \u914D\u4E00\u4E2A)");
458
+ log2(t4("cli.config.secret_empty"));
504
459
  return;
505
460
  }
506
461
  for (const name of names) log2(` ${name}`);
@@ -508,71 +463,80 @@ async function printSecretStatus(store) {
508
463
  async function exportSecrets(store) {
509
464
  const { isNonInteractive: isNonInteractive9 } = await import("@epoch-agent/core");
510
465
  if (isNonInteractive9()) {
511
- throw new CliError(
512
- "epoch config secret --export \u9700\u8981\u4EA4\u4E92\u5F0F\u7EC8\u7AEF",
513
- 1,
514
- "\u5B83\u4F1A\u628A\u51ED\u636E\u5199\u6210\u660E\u6587\uFF0C\u5FC5\u987B\u7531\u4EBA\u786E\u8BA4\u4E24\u6B21\u3002CI / \u5BB9\u5668\u91CC\u8BF7\u6539\u7528\u73AF\u5883\u53D8\u91CF\uFF08\u4F18\u5148\u7EA7\u6700\u9AD8\uFF09\u3002"
515
- );
466
+ throw new CliError(t4("cli.config.export_needs_tty"), 1, t4("cli.config.export_needs_tty_hint"));
516
467
  }
517
468
  const { confirm } = await import("@inquirer/prompts");
518
- log2(`\u5373\u5C06\u628A\u51ED\u636E\u4EE5\u660E\u6587\u5199\u5165 ${ENV_PATH}\uFF08\u6743\u9650 0600\uFF09\u3002`);
519
- if (!await confirm({ message: "\u786E\u8BA4\u8981\u628A\u51ED\u636E\u5012\u6210\u660E\u6587\u5417\uFF1F", default: false })) {
520
- log2("\u5DF2\u53D6\u6D88");
469
+ log2(t4("cli.config.export_about_to", { path: ENV_PATH }));
470
+ if (!await confirm({ message: t4("cli.config.export_confirm"), default: false })) {
471
+ log2(t4("cli.config.cancelled"));
521
472
  return;
522
473
  }
523
474
  const again = await confirm({
524
- message: `\u518D\u786E\u8BA4\u4E00\u6B21\uFF1A${ENV_PATH} \u5C06\u5305\u542B\u53EF\u76F4\u63A5\u4F7F\u7528\u7684 API key`,
475
+ message: t4("cli.config.export_confirm_again", { path: ENV_PATH }),
525
476
  default: false
526
477
  });
527
478
  if (!again) {
528
- log2("\u5DF2\u53D6\u6D88");
479
+ log2(t4("cli.config.cancelled"));
529
480
  return;
530
481
  }
531
482
  ensureEpochHome();
532
- const { exportSecretsToEnv } = await import("./env-file-JUOFDESY.js");
483
+ const { exportSecretsToEnv } = await import("./env-file-O6P454IQ.js");
533
484
  const written = await exportSecretsToEnv(store, ENV_PATH, API_KEY_ENV_VARS);
534
485
  if (written.length === 0) {
535
- log2("\u6CA1\u6709\u53EF\u5012\u51FA\u7684\u51ED\u636E");
486
+ log2(t4("cli.config.export_nothing"));
536
487
  return;
537
488
  }
538
- log2(`\u2705 \u5DF2\u5199\u51FA ${written.length} \u4E2A\u51ED\u636E\u5230 ${ENV_PATH}\uFF080600\uFF09`);
539
- log2(" \u94A5\u5319\u4E32\u91CC\u90A3\u4EFD\u6CA1\u6709\u5220 \u2014\u2014 \u89E3\u6790\u94FE\u91CC .env \u538B\u5728\u94A5\u5319\u4E32\u4E4B\u4E0A\uFF0C\u8FD9\u4EFD\u4F1A\u751F\u6548\u3002");
489
+ log2(`\u2705 ${t4("cli.config.export_done", { count: written.length, path: ENV_PATH })}`);
490
+ log2(t4("cli.config.export_done_note"));
540
491
  }
541
492
  function applySet(yaml, key, value) {
542
493
  switch (key) {
543
494
  case "provider":
544
495
  if (!isProviderType(value)) {
545
- throw new CliError(`\u672A\u77E5 provider: ${value}`, 1, `\u652F\u6301: ${PROVIDER_TYPES.join(", ")}`);
496
+ throw new CliError(
497
+ t4("cli.config.unknown_provider", { value }),
498
+ 1,
499
+ t4("cli.config.supported", { list: PROVIDER_TYPES.join(", ") })
500
+ );
546
501
  }
547
502
  return yaml.replace(/^(\s*)type:.*$/m, `$1type: ${value}`);
548
503
  case "model":
549
504
  return setScalar(yaml, "model", value);
550
505
  case "permission":
551
506
  if (!isPermissionLevel(value)) {
552
- throw new CliError(`\u672A\u77E5\u6743\u9650\u7EA7\u522B: ${value}`, 1, `\u652F\u6301: ${PERMISSION_LEVELS.join(", ")}`);
507
+ throw new CliError(
508
+ t4("cli.config.unknown_permission", { value }),
509
+ 1,
510
+ t4("cli.config.supported", { list: PERMISSION_LEVELS.join(", ") })
511
+ );
553
512
  }
554
513
  return setScalar(yaml, "permission", value);
555
514
  case "shell": {
556
515
  if (!SHELL_KINDS.includes(value)) {
557
- throw new CliError(`\u672A\u77E5 shell: ${value}`, 1, `\u652F\u6301: ${SHELL_KINDS.join(", ")}`);
516
+ throw new CliError(
517
+ t4("cli.config.unknown_shell", { value }),
518
+ 1,
519
+ t4("cli.config.supported", { list: SHELL_KINDS.join(", ") })
520
+ );
558
521
  }
559
522
  return setScalar(yaml, "shell", value);
560
523
  }
561
524
  case "models.utility": {
562
525
  if (!parseModelRef(value)) {
563
- throw new CliError(
564
- `models.utility \u4E0D\u80FD\u4E3A\u7A7A`,
565
- 1,
566
- "\u5F62\u5982 gpt-4o-mini \u6216 deepseek/deepseek-chat"
567
- );
526
+ throw new CliError(t4("cli.config.utility_empty"), 1, t4("cli.config.utility_hint"));
568
527
  }
569
528
  return setSectionField(yaml, "models", "utility", value);
570
529
  }
571
530
  default: {
572
531
  const field = budgetField(key);
573
532
  const validate = BUDGET_FIELDS[field];
574
- if (!validate) throw new CliError(`\u672A\u77E5\u914D\u7F6E\u9879: ${key}`, 1, `\u652F\u6301: ${knownKeys()}`);
575
- if (!validate(value)) throw new CliError(`${key} \u7684\u503C\u4E0D\u5408\u6CD5: ${value}`);
533
+ if (!validate)
534
+ throw new CliError(
535
+ t4("cli.config.unknown_key", { key }),
536
+ 1,
537
+ t4("cli.config.supported", { list: knownKeys() })
538
+ );
539
+ if (!validate(value)) throw new CliError(t4("cli.config.bad_value", { key, value }));
576
540
  return setSectionField(yaml, "budget", field, value);
577
541
  }
578
542
  }
@@ -582,17 +546,13 @@ function applyUnset(yaml, key) {
582
546
  if (key === "models.utility") return unsetSectionField(yaml, "models", "utility");
583
547
  const field = budgetField(key);
584
548
  if (BUDGET_FIELDS[field]) return unsetSectionField(yaml, "budget", field);
585
- throw new CliError(
586
- `\u4E0D\u652F\u6301\u5220\u9664: ${key}`,
587
- 1,
588
- `\u53EF\u5220\u9664: model, permission, shell, models.utility, budget.<\u5B57\u6BB5>`
589
- );
549
+ throw new CliError(t4("cli.config.cannot_unset", { key }), 1, t4("cli.config.can_unset"));
590
550
  }
591
551
  function budgetField(key) {
592
552
  return key.startsWith("budget.") ? key.slice("budget.".length) : "";
593
553
  }
594
-
595
554
  // src/commands/mcp.ts
555
+ import { t as t5, uiDateLocale } from "@epoch-agent/infra";
596
556
  import {
597
557
  listMcpServers,
598
558
  mcpLogin,
@@ -601,30 +561,39 @@ import {
601
561
  } from "@epoch-agent/runtime";
602
562
  var log3 = (msg) => process.stdout.write(msg + "\n");
603
563
  var warn = (msg) => process.stderr.write(msg + "\n");
604
- var STATE_LABEL = {
605
- "not-applicable": "\u4E0D\u9700\u8981\uFF08stdio\uFF09",
606
- "bearer-header": "\u9759\u6001 Authorization header",
607
- authorized: "\u5DF2\u767B\u5F55",
608
- refreshable: "token \u8FC7\u671F\uFF0C\u4E0B\u6B21\u8FDE\u63A5\u81EA\u52A8\u7EED\u671F",
609
- "logged-out": "\u672A\u767B\u5F55"
610
- };
564
+ function stateLabel(state) {
565
+ switch (state) {
566
+ case "not-applicable":
567
+ return t5("mcp.state_not_applicable");
568
+ case "bearer-header":
569
+ return t5("mcp.state_bearer");
570
+ case "authorized":
571
+ return t5("mcp.state_authorized");
572
+ case "refreshable":
573
+ return t5("mcp.state_refreshable");
574
+ case "logged-out":
575
+ return t5("mcp.state_logged_out");
576
+ }
577
+ }
611
578
  function registerMcpCommand(program2) {
612
- const cmd = program2.command("mcp").description("\u7BA1\u7406 MCP server\uFF08\u5217\u8868 / \u63A2\u6D3B / OAuth \u767B\u5F55\uFF09");
613
- cmd.command("list", { isDefault: true }).description("\u5217\u51FA\u5DF2\u914D\u7F6E\u7684 MCP server \u548C\u5404\u81EA\u7684\u767B\u5F55\u6001\uFF08\u4E0D\u8054\u7F51\uFF09").action(() => guardAsync(() => printList()));
614
- cmd.command("status").description("\u8FDE\u4E00\u6B21\u6307\u5B9A server\uFF0C\u770B\u771F\u5B9E\u7684\u8FDE\u63A5\u7ED3\u679C\u548C\u5DE5\u5177\u8868").argument("<name>", "server \u540D").action(
579
+ const cmd = program2.command("mcp").description(t5("mcp.cmd_root"));
580
+ cmd.command("list", { isDefault: true }).description(t5("mcp.cmd_list")).action(() => guardAsync(() => printList()));
581
+ cmd.command("status").description(t5("mcp.cmd_status")).argument("<name>", t5("mcp.arg_name")).action(
615
582
  (name) => guardAsync(async () => {
616
- log3(`\u6B63\u5728\u8FDE\u63A5 ${name} \u2026\u2026`);
583
+ log3(t5("mcp.connecting", { name }));
617
584
  const result = await probeMcpServer(name, { homeDir: EPOCH_HOME });
618
585
  if (!result.connected) {
619
- warn(`\u2717 \u8FDE\u63A5\u5931\u8D25${result.error ? `\uFF1A${result.error}` : ""}`);
620
- if (result.needsLogin) warn(` \u2192 \u8DD1 epoch mcp login ${name}`);
586
+ warn(
587
+ result.error ? t5("mcp.connect_failed_reason", { reason: result.error }) : t5("mcp.connect_failed")
588
+ );
589
+ if (result.needsLogin) warn(t5("mcp.needs_login", { name }));
621
590
  process.exit(1);
622
591
  }
623
- log3(`\u2713 \u5DF2\u8FDE\u63A5\uFF0C${result.toolNames.length} \u4E2A\u5DE5\u5177`);
592
+ log3(`\u2713 ${t5("mcp.connected", { count: result.toolNames.length })}`);
624
593
  for (const tool of result.toolNames) log3(` ${tool}`);
625
594
  })
626
595
  );
627
- cmd.command("login").description("\u5BF9\u8FDC\u7A0B MCP server \u505A OAuth \u6388\u6743\uFF08\u4F1A\u6253\u5F00\u6D4F\u89C8\u5668\uFF09").argument("<name>", "server \u540D").option("--port <port>", "\u56FA\u5B9A\u56DE\u8C03\u7AEF\u53E3\uFF08\u6388\u6743\u670D\u52A1\u5668\u8981\u6C42\u767B\u8BB0 redirect_uri \u65F6\u7528\uFF09").option("--scope <scope>", "\u7533\u8BF7\u7684 scope\uFF0C\u7A7A\u683C\u5206\u9694").action(
596
+ cmd.command("login").description(t5("mcp.cmd_login")).argument("<name>", t5("mcp.arg_name")).option("--port <port>", t5("mcp.opt_port")).option("--scope <scope>", t5("mcp.opt_scope")).action(
628
597
  (name, opts) => guardAsync(async () => {
629
598
  const port = opts.port === void 0 ? void 0 : parsePort(opts.port);
630
599
  const { refreshedOnly } = await mcpLogin(name, {
@@ -633,22 +602,25 @@ function registerMcpCommand(program2) {
633
602
  ...opts.scope === void 0 ? {} : { scope: opts.scope },
634
603
  print: log3
635
604
  });
636
- log3(refreshedOnly ? `\u2713 ${name} \u7684\u51ED\u636E\u5DF2\u7EED\u671F\uFF08\u65E0\u9700\u91CD\u65B0\u6388\u6743\uFF09` : `\u2713 ${name} \u6388\u6743\u5B8C\u6210`);
605
+ log3(
606
+ refreshedOnly ? `\u2713 ${t5("mcp.refreshed", { name })}` : `\u2713 ${t5("mcp.authorized", { name })}`
607
+ );
637
608
  })
638
609
  );
639
- cmd.command("logout").description("\u5220\u6389\u672C\u5730\u4FDD\u5B58\u7684 OAuth \u51ED\u636E").argument("<name>", "server \u540D").action(
610
+ cmd.command("logout").description(t5("mcp.cmd_logout")).argument("<name>", t5("mcp.arg_name")).action(
640
611
  (name) => guardAsync(async () => {
641
612
  const removed = await mcpLogout(name, { homeDir: EPOCH_HOME });
642
- log3(removed ? `\u2713 \u5DF2\u5220\u9664 ${name} \u7684\u672C\u5730\u51ED\u636E` : `${name} \u672C\u6765\u5C31\u6CA1\u6709\u4FDD\u5B58\u51ED\u636E`);
613
+ log3(removed ? `\u2713 ${t5("mcp.removed", { name })}` : t5("mcp.nothing_stored", { name }));
643
614
  })
644
615
  );
645
616
  }
646
617
  async function printList() {
647
618
  const { servers, issues, configPath: configPath2 } = await listMcpServers({ homeDir: EPOCH_HOME });
648
- for (const issue of issues) warn(`\u26A0 \u914D\u7F6E\u95EE\u9898 ${issue.path}\uFF1A${issue.message}`);
649
- log3(`MCP servers\uFF08${configPath2}\uFF09`);
619
+ for (const issue of issues)
620
+ warn(t5("mcp.config_issue", { path: issue.path, message: issue.message }));
621
+ log3(t5("mcp.list_head", { path: configPath2 }));
650
622
  if (servers.length === 0) {
651
- log3(" (\u65E0)");
623
+ log3(t5("mcp.list_empty"));
652
624
  return;
653
625
  }
654
626
  for (const s of servers) log3(` ${describe(s)}`);
@@ -657,55 +629,54 @@ function describe(s) {
657
629
  const target = s.config.transport === "stdio" ? s.config.command : s.config.url;
658
630
  const parts = [
659
631
  `${s.config.name} [${s.config.transport}] ${target ?? ""}`,
660
- ` \u8BA4\u8BC1\uFF1A${STATE_LABEL[s.auth.state]}`
632
+ t5("mcp.auth_line", { state: stateLabel(s.auth.state) })
661
633
  ];
662
634
  if (s.auth.expiresAt) {
663
- parts[1] += `\uFF08\u5230\u671F ${new Date(s.auth.expiresAt).toLocaleString("zh-CN")}\uFF09`;
635
+ parts[1] += t5("mcp.expires_at", {
636
+ when: new Date(s.auth.expiresAt).toLocaleString(uiDateLocale())
637
+ });
664
638
  }
665
639
  return parts.join("\n");
666
640
  }
667
641
  function parsePort(raw) {
668
642
  const port = Number(raw);
669
643
  if (!Number.isInteger(port) || port < 1024 || port > 65535) {
670
- throw new Error(`--port \u5FC5\u987B\u662F 1024\u201365535 \u7684\u6574\u6570\uFF0C\u6536\u5230 "${raw}"`);
644
+ throw new Error(t5("mcp.bad_port", { raw }));
671
645
  }
672
646
  return port;
673
647
  }
674
648
  function guardAsync(fn) {
675
649
  fn().catch((err2) => {
676
- warn(`\u9519\u8BEF: ${err2 instanceof Error ? err2.message : String(err2)}`);
650
+ warn(t5("mcp.error", { message: err2 instanceof Error ? err2.message : String(err2) }));
677
651
  process.exit(1);
678
652
  });
679
653
  }
680
-
681
654
  // src/commands/model.ts
682
655
  import { input, password, select } from "@inquirer/prompts";
683
656
  import { isNonInteractive, readProviderKeyEnv } from "@epoch-agent/core";
657
+ import { t as t6 } from "@epoch-agent/infra";
684
658
  import {
685
659
  apiKeyEnvVar,
686
660
  PROVIDER_INFOS,
687
661
  PROVIDER_TYPES as PROVIDER_TYPES2
688
662
  } from "@epoch-agent/protocol";
663
+ import { providerLabel } from "@epoch-agent/runtime";
689
664
  var log4 = (msg) => process.stdout.write(msg + "\n");
690
665
  var CUSTOM = "__custom__";
691
666
  function registerModelCommand(program2) {
692
- program2.command("model").description("\u4EA4\u4E92\u5F0F\u9009\u62E9 provider \u548C\u6A21\u578B").option("-r, --refresh", "\u6E05\u9664\u6A21\u578B\u7F13\u5B58").action(async (opts) => {
667
+ program2.command("model").description(t6("model.cmd_root")).option("-r, --refresh", t6("model.opt_refresh")).action(async (opts) => {
693
668
  ensureEpochHome();
694
669
  if (opts.refresh) return refreshCache();
695
670
  if (isNonInteractive()) {
696
- throw new CliError(
697
- "epoch model \u9700\u8981\u4EA4\u4E92\u5F0F\u7EC8\u7AEF",
698
- 1,
699
- "\u5728 CI / \u7BA1\u9053\u91CC\u8BF7\u6539\u7528 epoch config set provider <\u540D\u5B57> \u548C epoch config set model <\u540D\u5B57>\uFF0CAPI key \u8D70\u73AF\u5883\u53D8\u91CF\u3002"
700
- );
671
+ throw new CliError(t6("model.needs_tty"), 1, t6("model.needs_tty_hint"));
701
672
  }
702
673
  await runWizard();
703
674
  });
704
675
  }
705
676
  async function refreshCache() {
706
677
  const { clearModelCache } = await import("@epoch-agent/core");
707
- for (const t14 of PROVIDER_TYPES2) clearModelCache(EPOCH_HOME, t14);
708
- log4("\u5DF2\u6E05\u9664\u5168\u90E8\u6A21\u578B\u7F13\u5B58");
678
+ for (const t39 of PROVIDER_TYPES2) clearModelCache(EPOCH_HOME, t39);
679
+ log4(t6("model.cache_cleared"));
709
680
  }
710
681
  function readCurrentSelection() {
711
682
  const yaml = readConfigYaml(CONFIG_PATH);
@@ -715,7 +686,7 @@ async function runWizard() {
715
686
  await ensureSecrets();
716
687
  const current = readCurrentSelection();
717
688
  const provider = await select({
718
- message: "\u9009\u62E9 Provider:",
689
+ message: t6("model.pick_provider"),
719
690
  choices: providerChoices(current.provider),
720
691
  pageSize: listPageSize(PROVIDER_INFOS.filter((p) => p.interactive).length),
721
692
  ...isKnownProvider(current.provider) ? { default: current.provider } : {}
@@ -733,21 +704,21 @@ async function runWizard() {
733
704
  writeConfigYaml(CONFIG_PATH, yaml);
734
705
  log4(`
735
706
  \u2705 ${provider} / ${model}
736
- \u5DF2\u5C31\u7EEA\uFF0C\u8F93\u5165 epoch "\u4F60\u597D" \u8BD5\u8BD5`);
707
+ ${t6("model.ready")}`);
737
708
  }
738
709
  async function ensureSecrets() {
739
710
  try {
740
711
  const { ensureSecretsReady } = await import("@epoch-agent/runtime");
741
712
  await ensureSecretsReady();
742
713
  } catch (err2) {
743
- log4(` \u26A0 \u51ED\u636E\u5B58\u50A8\u521D\u59CB\u5316\u5931\u8D25\uFF0C\u5DF2\u914D\u7F6E\u7684 key \u53EF\u80FD\u8BFB\u4E0D\u5230\uFF08${describeError(err2)}\uFF09`);
714
+ log4(t6("model.secret_init_failed", { reason: describeError(err2) }));
744
715
  }
745
716
  }
746
717
  async function resolveApiKey(provider) {
747
718
  const envKey = apiKeyEnvVar(provider);
748
719
  if (!envKey) return "";
749
720
  const existing = readProviderKeyEnv(EPOCH_HOME)[envKey];
750
- const message = existing ? `API key (${envKey})\uFF08\u5DF2\u914D\u7F6E\uFF0C\u56DE\u8F66\u6CBF\u7528\uFF09:` : `API key (${envKey}):`;
721
+ const message = existing ? t6("model.api_key_existing", { envKey }) : t6("model.api_key", { envKey });
751
722
  const typed = (await password({ message, mask: "*" })).trim();
752
723
  if (!typed) return existing ?? "";
753
724
  await saveApiKey(envKey, typed);
@@ -755,30 +726,30 @@ async function resolveApiKey(provider) {
755
726
  }
756
727
  async function saveApiKey(envKey, apiKey) {
757
728
  const result = await writeProviderKey(envKey, apiKey, ENV_PATH);
758
- log4(result.encrypted ? ` \u5DF2\u5B58\u5165 ${result.detail}` : ` \u26A0 ${result.detail}`);
729
+ log4(result.encrypted ? t6("model.key_stored", { detail: result.detail }) : ` \u26A0 ${result.detail}`);
759
730
  }
760
731
  async function askBaseUrl() {
761
732
  const answer = (await input({
762
- message: "API base URL\uFF08\u5982 http://localhost:8080/v1\uFF09:",
733
+ message: t6("model.ask_base_url"),
763
734
  default: "http://localhost:8080/v1"
764
735
  })).trim();
765
736
  return answer || void 0;
766
737
  }
767
738
  async function pickModel(models, source, currentModel) {
768
- const choices = modelChoices(models, source, currentModel);
769
- if (choices.length === 0) {
739
+ const choices3 = modelChoices(models, source, currentModel);
740
+ if (choices3.length === 0) {
770
741
  const fallback2 = currentModel ?? "gpt-4o-mini";
771
- return await input({ message: "\u8F93\u5165\u6A21\u578B\u540D:", default: fallback2 }) || fallback2;
742
+ return await input({ message: t6("model.type_model"), default: fallback2 }) || fallback2;
772
743
  }
773
744
  const picked = await select({
774
- message: "\u9009\u62E9\u6A21\u578B:",
775
- choices,
776
- pageSize: listPageSize(choices.length),
745
+ message: t6("model.pick_model"),
746
+ choices: choices3,
747
+ pageSize: listPageSize(choices3.length),
777
748
  ...currentModel && models.includes(currentModel) ? { default: currentModel } : {}
778
749
  });
779
750
  if (picked !== CUSTOM) return picked;
780
751
  const fallback = currentModel ?? models[0] ?? "gpt-4o-mini";
781
- return await input({ message: "\u8F93\u5165\u6A21\u578B\u540D:", default: fallback }) || fallback;
752
+ return await input({ message: t6("model.type_model"), default: fallback }) || fallback;
782
753
  }
783
754
  function isKnownProvider(value) {
784
755
  return PROVIDER_INFOS.some((p) => p.interactive && p.type === value);
@@ -787,37 +758,42 @@ function carriedModel(chosenProvider, current) {
787
758
  return chosenProvider === current.provider ? current.model : void 0;
788
759
  }
789
760
  function providerChoices(currentProvider) {
790
- return PROVIDER_INFOS.filter((p) => p.interactive).map((p) => ({
791
- name: p.type === currentProvider ? `${p.label}\uFF08\u5F53\u524D\uFF09` : p.label,
792
- value: p.type
793
- }));
761
+ return PROVIDER_INFOS.filter((p) => p.interactive).map((p) => {
762
+ const label = providerLabel(p);
763
+ return {
764
+ name: p.type === currentProvider ? t6("model.current", { label }) : label,
765
+ value: p.type
766
+ };
767
+ });
794
768
  }
795
769
  function modelChoices(models, source, currentModel) {
796
770
  if (models.length === 0) return [];
797
- const choices = models.map((m) => ({
798
- name: m === currentModel ? `${m}\uFF08\u5F53\u524D\uFF09` : m,
771
+ const choices3 = models.map((m) => ({
772
+ name: m === currentModel ? t6("model.current", { label: m }) : m,
799
773
  value: m
800
774
  }));
801
775
  if (currentModel && !models.includes(currentModel)) {
802
- choices.unshift({ name: `${currentModel}\uFF08\u5F53\u524D\uFF0C\u4E0D\u5728\u6B64\u76EE\u5F55\u4E2D\uFF09`, value: currentModel });
776
+ choices3.unshift({
777
+ name: t6("model.current_not_listed", { label: currentModel }),
778
+ value: currentModel
779
+ });
803
780
  }
804
- if (source !== "live") choices.push({ name: "\u2500\u2500 \u81EA\u5B9A\u4E49\u8F93\u5165 \u2500\u2500", value: CUSTOM });
805
- return choices;
781
+ if (source !== "live") choices3.push({ name: t6("model.custom_entry"), value: CUSTOM });
782
+ return choices3;
806
783
  }
807
784
  function listPageSize(itemCount, rows = process.stdout.rows) {
808
785
  const height = rows && rows > 0 ? rows : 24;
809
786
  return Math.min(itemCount, Math.max(3, height - 3));
810
787
  }
811
788
  function describeDiscovery(source, count, hasKey) {
812
- if (source === "live") return ` \u4ECE API \u62C9\u53D6\u5230 ${count} \u4E2A\u6A21\u578B`;
813
- const why = hasKey ? "\u62C9\u53D6\u6A21\u578B\u5217\u8868\u5931\u8D25\uFF08\u7F51\u7EDC / key \u65E0\u6548 / \u8BE5 provider \u4E0D\u63D0\u4F9B\u5217\u8868\u63A5\u53E3\uFF09" : "\u6CA1\u6709\u53EF\u7528\u7684 API key";
814
- const what = source === "cache" ? "\u4E0A\u6B21\u7F13\u5B58\u7684\u5217\u8868" : "\u5185\u7F6E\u7684\u9759\u6001\u76EE\u5F55\uFF08\u53EF\u80FD\u5DF2\u8FC7\u671F\uFF09";
815
- return ` \u26A0 ${why}\uFF0C\u6539\u7528${what}\u3002\u5217\u8868\u91CC\u6CA1\u6709\u4F60\u8981\u7684\u6A21\u578B\u5C31\u9009\u300C\u81EA\u5B9A\u4E49\u8F93\u5165\u300D`;
789
+ if (source === "live") return t6("model.from_api", { count });
790
+ const why = hasKey ? t6("model.why_fetch_failed") : t6("model.why_no_key");
791
+ const what = source === "cache" ? t6("model.fallback_cache") : t6("model.fallback_static");
792
+ return t6("model.fallback_line", { why, what });
816
793
  }
817
794
  function describeError(err2) {
818
795
  return err2 instanceof Error ? err2.message : String(err2);
819
796
  }
820
-
821
797
  // src/commands/plugin.ts
822
798
  import {
823
799
  addMarketplace,
@@ -842,7 +818,7 @@ import {
842
818
  marketplacesPath,
843
819
  pluginsDir,
844
820
  pluginsStatePath,
845
- t as t2
821
+ t as t7
846
822
  } from "@epoch-agent/infra";
847
823
  var log5 = (msg) => {
848
824
  process.stdout.write(msg + "\n");
@@ -851,72 +827,76 @@ function locations() {
851
827
  return { pluginsDir: pluginsDir(EPOCH_HOME), statePath: pluginsStatePath(EPOCH_HOME) };
852
828
  }
853
829
  function registerPluginCommand(program2) {
854
- const cmd = program2.command("plugin").description("\u7BA1\u7406\u63D2\u4EF6\uFF08\u4E00\u5305\u547D\u4EE4 / \u89D2\u8272 / \u6280\u80FD / hook / deny \u89C4\u5219 / MCP server\uFF09");
830
+ const cmd = program2.command("plugin").description(t7("plugin.cmd_root"));
855
831
  registerBasics(cmd);
856
832
  registerLifecycle(cmd);
857
833
  registerMarketplace(cmd);
858
834
  }
859
835
  function registerBasics(cmd) {
860
- cmd.command("list", { isDefault: true }).alias("ls").description("\u5217\u51FA\u5DF2\u5B89\u88C5\u7684\u63D2\u4EF6").option("--verbose", "\u8FDE\u6BCF\u4E2A\u63D2\u4EF6\u5E26\u6765\u7684\u6269\u5C55\u7269\u4E00\u8D77\u5217\u51FA\u6765").action((opts) => {
836
+ cmd.command("list", { isDefault: true }).alias("ls").description(t7("plugin.cmd_list")).option("--verbose", t7("plugin.opt_verbose")).action((opts) => {
861
837
  printList2(opts.verbose === true);
862
838
  });
863
- cmd.command("install").alias("add").description(
864
- "\u88C5\u4E00\u4E2A\u63D2\u4EF6\uFF08./\u672C\u5730\u76EE\u5F55 | github:owner/repo | npm:\u5305\u540D | https://\u2026.zip | <\u5E02\u573A>/<\u63D2\u4EF6>\uFF09"
865
- ).argument("<source>", "\u63D2\u4EF6\u6765\u6E90").option("-y, --yes", "\u8DF3\u8FC7\u5B89\u88C5\u524D\u786E\u8BA4").action(async (source, opts) => {
839
+ cmd.command("install").alias("add").description(t7("plugin.cmd_install")).argument("<source>", t7("plugin.arg_source")).option("-y, --yes", t7("plugin.opt_yes_install")).action(async (source, opts) => {
866
840
  await runInstall(source, opts.yes === true);
867
841
  });
868
- cmd.command("validate").description("\u81EA\u67E5\u4E00\u4E2A\u63D2\u4EF6\u76EE\u5F55\uFF08\u5199\u63D2\u4EF6\u65F6\u7528\uFF0C\u4E0D\u5B89\u88C5\uFF09").argument("[path]", "\u63D2\u4EF6\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF09").action((path) => {
842
+ cmd.command("validate").description(t7("plugin.cmd_validate")).argument("[path]", t7("plugin.arg_dir")).action((path) => {
869
843
  validateDir(path ?? process.cwd());
870
844
  });
871
845
  }
872
846
  function registerLifecycle(cmd) {
873
- cmd.command("uninstall").alias("rm").description("\u5378\u8F7D\u4E00\u4E2A\u63D2\u4EF6\uFF08\u672C\u5730\u8F6F\u94FE\u53EA\u89E3\u94FE\uFF0C\u4E0D\u5220\u4F60\u7684\u6E90\u7801\u76EE\u5F55\uFF09").argument("<name>", "\u63D2\u4EF6\u540D").action(async (name) => {
847
+ cmd.command("uninstall").alias("rm").description(t7("plugin.cmd_uninstall")).argument("<name>", t7("plugin.arg_name")).action(async (name) => {
874
848
  const outcome = await uninstallPlugin(name, { statePath: pluginsStatePath(EPOCH_HOME) });
875
849
  if (!outcome.ok) throw new CliError(outcome.reason);
876
850
  log5(`\u2713 ${outcome.message}`);
877
851
  });
878
- cmd.command("disable").description("\u505C\u7528\u4E00\u4E2A\u63D2\u4EF6\uFF08\u53EA\u6539\u8BB0\u5F55\uFF0C\u6587\u4EF6\u4E00\u4E2A\u5B57\u8282\u90FD\u4E0D\u52A8\uFF09").argument("<name>", "\u63D2\u4EF6\u540D").action(async (name) => {
852
+ cmd.command("disable").description(t7("plugin.cmd_disable")).argument("<name>", t7("plugin.arg_name")).action(async (name) => {
879
853
  await toggle(name, false);
880
854
  });
881
- cmd.command("enable").description("\u91CD\u65B0\u542F\u7528\u4E00\u4E2A\u63D2\u4EF6").argument("<name>", "\u63D2\u4EF6\u540D").action(async (name) => {
855
+ cmd.command("enable").description(t7("plugin.cmd_enable")).argument("<name>", t7("plugin.arg_name")).action(async (name) => {
882
856
  await toggle(name, true);
883
857
  });
884
- cmd.command("update").description("\u6309\u8BB0\u5F55\u91CC\u7684\u6765\u6E90\u91CD\u88C5\u4E00\u904D\uFF08\u8F6F\u94FE\u88C5\u7684\u4E0D\u7528\u66F4\u65B0\uFF09").argument("<name>", "\u63D2\u4EF6\u540D").option("-y, --yes", "\u8DF3\u8FC7\u786E\u8BA4").action(async (name, opts) => {
858
+ cmd.command("update").description(t7("plugin.cmd_update")).argument("<name>", t7("plugin.arg_name")).option("-y, --yes", t7("plugin.opt_yes")).action(async (name, opts) => {
885
859
  await runUpdate(name, opts.yes === true);
886
860
  });
887
861
  }
888
862
  function registerMarketplace(cmd) {
889
- const market = cmd.command("marketplace").alias("mp").description("\u7BA1\u7406\u63D2\u4EF6\u5E02\u573A");
890
- market.command("add").description("\u52A0\u4E00\u4E2A\u5E02\u573A\uFF08github:owner/repo | https://\u2026/epoch-marketplace.json | \u672C\u5730\u76EE\u5F55\uFF09").argument("<source>", "\u5E02\u573A\u5730\u5740").action(async (source) => {
863
+ const market = cmd.command("marketplace").alias("mp").description(t7("plugin.cmd_market"));
864
+ market.command("add").description(t7("plugin.cmd_market_add")).argument("<source>", t7("plugin.arg_market_source")).action(async (source) => {
891
865
  const outcome = await addMarketplace(source, { statePath: marketplacesPath(EPOCH_HOME) });
892
866
  if (!outcome.ok) throw new CliError(outcome.reason);
893
867
  const count = outcome.record.catalog.plugins.length;
894
- log5(`\u2713 \u5DF2\u52A0\u5E02\u573A ${outcome.record.name}\uFF08${count} \u4E2A\u63D2\u4EF6\uFF09`);
895
- log5(` \u641C\u4E00\u4E0B\uFF1Aepoch plugin search <\u5173\u952E\u8BCD>`);
868
+ log5(`\u2713 ${t7("plugin.market_added", { name: outcome.record.name, count })}`);
869
+ log5(t7("plugin.market_added_hint"));
896
870
  });
897
- market.command("list", { isDefault: true }).alias("ls").description("\u5217\u51FA\u5DF2\u52A0\u7684\u5E02\u573A").action(printMarketplaces);
898
- market.command("remove").alias("rm").description("\u5220\u6389\u4E00\u4E2A\u5E02\u573A\uFF08\u5DF2\u7ECF\u4ECE\u5B83\u88C5\u7684\u63D2\u4EF6\u4E0D\u53D7\u5F71\u54CD\uFF09").argument("<name>", "\u5E02\u573A\u540D").action(async (name) => {
871
+ market.command("list", { isDefault: true }).alias("ls").description(t7("plugin.cmd_market_list")).action(printMarketplaces);
872
+ market.command("remove").alias("rm").description(t7("plugin.cmd_market_remove")).argument("<name>", t7("plugin.arg_market_name")).action(async (name) => {
899
873
  const outcome = await removeMarketplace(name, { statePath: marketplacesPath(EPOCH_HOME) });
900
- if (!outcome.ok) throw new CliError(outcome.reason ?? `\u5220\u4E0D\u6389 ${name}`);
901
- log5(`\u2713 \u5DF2\u5220\u6389\u5E02\u573A ${name}`);
874
+ if (!outcome.ok)
875
+ throw new CliError(outcome.reason ?? t7("plugin.market_remove_failed", { name }));
876
+ log5(`\u2713 ${t7("plugin.market_removed", { name })}`);
902
877
  });
903
- market.command("update").description("\u91CD\u62C9\u5E02\u573A\u76EE\u5F55\uFF08\u76EE\u5F55\u662F\u52A0\u8FDB\u6765\u90A3\u4E00\u523B\u7F13\u5B58\u7684\uFF0C\u641C\u7D22\u8D70\u7F13\u5B58\uFF09").argument("<name>", "\u5E02\u573A\u540D").action(async (name) => {
878
+ market.command("update").description(t7("plugin.cmd_market_update")).argument("<name>", t7("plugin.arg_market_name")).action(async (name) => {
904
879
  const outcome = await updateMarketplace(name, { statePath: marketplacesPath(EPOCH_HOME) });
905
880
  if (!outcome.ok) throw new CliError(outcome.reason);
906
- log5(`\u2713 ${name} \u5DF2\u66F4\u65B0\uFF08${outcome.record.catalog.plugins.length} \u4E2A\u63D2\u4EF6\uFF09`);
881
+ log5(
882
+ `\u2713 ${t7("plugin.market_updated", {
883
+ name,
884
+ count: outcome.record.catalog.plugins.length
885
+ })}`
886
+ );
907
887
  });
908
- cmd.command("search").description("\u5728\u5DF2\u52A0\u7684\u5E02\u573A\u91CC\u641C\u63D2\u4EF6\uFF08\u4E0D\u7ED9\u5173\u952E\u8BCD\u5C31\u5217\u5168\u90E8\uFF09").argument("[keyword]", "\u5173\u952E\u8BCD").action((keyword) => {
888
+ cmd.command("search").description(t7("plugin.cmd_search")).argument("[keyword]", t7("plugin.arg_keyword")).action((keyword) => {
909
889
  printSearch(keyword ?? "");
910
890
  });
911
891
  }
912
892
  function printList2(verbose) {
913
893
  const artifacts = loadPlugins({ statePath: pluginsStatePath(EPOCH_HOME) });
914
894
  if (artifacts.loaded.length === 0 && artifacts.skipped.length === 0) {
915
- log5("\u6CA1\u6709\u5DF2\u5B89\u88C5\u7684\u63D2\u4EF6\u3002");
916
- log5("\u88C5\u4E00\u4E2A\uFF1Aepoch plugin install ./my-plugin \u6216 epoch plugin install github:owner/repo");
895
+ log5(t7("plugin.none_installed"));
896
+ log5(t7("plugin.none_installed_hint"));
917
897
  return;
918
898
  }
919
- log5(`\u5DF2\u5B89\u88C5\u63D2\u4EF6\uFF08${pluginsStatePath(EPOCH_HOME)}\uFF09
899
+ log5(`${t7("plugin.installed_head", { path: pluginsStatePath(EPOCH_HOME) })}
920
900
  `);
921
901
  for (const plugin of artifacts.loaded) {
922
902
  log5(loadedLine(plugin));
@@ -928,7 +908,7 @@ function printList2(verbose) {
928
908
  }
929
909
  for (const skipped of artifacts.skipped) log5(skippedLine(skipped));
930
910
  if (artifacts.skipped.length > 0) log5(`
931
- ${PLUGIN_MARK_LEGEND}`);
911
+ ${markLegend()}`);
932
912
  for (const detail of issueDetails(artifacts.issues)) log5(`
933
913
  \u26A0 ${detail}`);
934
914
  }
@@ -938,7 +918,7 @@ function loadedLine(plugin) {
938
918
  function countsLine(counts) {
939
919
  const parts = [];
940
920
  const add = (n, key) => {
941
- if (n > 0) parts.push(t2(key, { count: n }));
921
+ if (n > 0) parts.push(t7(key, { count: n }));
942
922
  };
943
923
  add(counts.commands, "plugin.n_commands");
944
924
  add(counts.roles, "plugin.n_roles");
@@ -946,19 +926,17 @@ function countsLine(counts) {
946
926
  add(counts.hooks, "plugin.n_hooks");
947
927
  add(counts.denyRules, "plugin.n_deny");
948
928
  add(counts.mcpServers, "plugin.n_mcp");
949
- return parts.length === 0 ? t2("plugin.brings_nothing") : parts.join(" \xB7 ");
929
+ return parts.length === 0 ? t7("plugin.brings_nothing") : parts.join(" \xB7 ");
930
+ }
931
+ function markLegend() {
932
+ return t7("plugin.mark_legend");
950
933
  }
951
- var PLUGIN_MARK_LEGEND = "\u2713 \u5DF2\u52A0\u8F7D \xB7 \u25CB \u5DF2\u505C\u7528 \xB7 \u2717 \u542F\u7528\u4E86\u4F46\u8FD9\u6B21\u6CA1\u52A0\u8F7D";
952
934
  function skippedLine(skipped) {
953
935
  return ` ${skipped.disabled ? "\u25CB" : "\u2717"} ${skipped.name} ${skipped.reason}`;
954
936
  }
955
937
  async function runInstall(input2, yes) {
956
938
  if (!yes && isNonInteractive2()) {
957
- throw new CliError(
958
- "epoch plugin install \u9700\u8981\u4EA4\u4E92\u5F0F\u7EC8\u7AEF\u6765\u786E\u8BA4\u8FD9\u6B21\u5B89\u88C5",
959
- 1,
960
- "\u63D2\u4EF6\u53EF\u4EE5\u5E26 hook\uFF08\u4F1A\u6267\u884C shell \u547D\u4EE4\uFF09\u3002CI / \u811A\u672C\u91CC\u8BF7\u663E\u5F0F\u52A0 --yes \u8868\u793A\u4F60\u5DF2\u7ECF\u77E5\u9053\u88C5\u8FDB\u6765\u7684\u662F\u4EC0\u4E48\u3002"
961
- );
939
+ throw new CliError(t7("plugin.install_needs_tty"), 1, t7("plugin.install_needs_tty_hint"));
962
940
  }
963
941
  const ref = resolveMarketplaceRef(input2, marketplacesPath(EPOCH_HOME));
964
942
  if (ref && !ref.ok) throw new CliError(ref.reason);
@@ -972,26 +950,28 @@ async function runInstall(input2, yes) {
972
950
  ...yes ? {} : { confirm: askInstall }
973
951
  });
974
952
  if (!outcome.ok) {
975
- if (outcome.reason === "\u5DF2\u53D6\u6D88") {
976
- log5("\u5DF2\u53D6\u6D88\uFF0C\u4EC0\u4E48\u90FD\u6CA1\u88C5\u3002");
953
+ if (outcome.cancelled) {
954
+ log5(t7("plugin.nothing_installed"));
977
955
  return;
978
956
  }
979
- throw new CliError(`\u88C5\u4E0D\u4E0A ${source}\uFF1A${outcome.reason}`);
957
+ throw new CliError(t7("plugin.install_failed", { source, reason: outcome.reason }));
980
958
  }
981
- log5(`
982
- \u2713 \u5DF2\u5B89\u88C5 ${outcome.record.name}@${outcome.record.version} \u2192 ${outcome.record.path}`);
959
+ log5(
960
+ `
961
+ \u2713 ${t7("plugin.installed", {
962
+ name: outcome.record.name,
963
+ version: outcome.record.version,
964
+ path: outcome.record.path
965
+ })}`
966
+ );
983
967
  if (outcome.record.linked) {
984
- log5(" \u8FD9\u662F\u4E00\u6761\u8F6F\u94FE\uFF1A\u6539\u63D2\u4EF6\u6E90\u7801\u3001\u91CD\u542F epoch \u5C31\u751F\u6548\u3002");
968
+ log5(t7("plugin.installed_linked"));
985
969
  }
986
- log5(" \u91CD\u542F epoch \u4E4B\u540E\u751F\u6548\uFF08\u6269\u5C55\u7269\u5728\u542F\u52A8\u65F6\u52A0\u8F7D\uFF09\u3002");
970
+ log5(t7("plugin.installed_restart"));
987
971
  }
988
972
  async function runUpdate(name, yes) {
989
973
  if (!yes && isNonInteractive2()) {
990
- throw new CliError(
991
- "epoch plugin update \u9700\u8981\u4EA4\u4E92\u5F0F\u7EC8\u7AEF\u6765\u786E\u8BA4\u65B0\u7248\u672C\u5E26\u6765\u7684\u4E1C\u897F",
992
- 1,
993
- "\u65B0\u7248\u672C\u53EF\u80FD\u52A0\u4E86 hook\uFF08\u4F1A\u6267\u884C shell \u547D\u4EE4\uFF09\u3002CI / \u811A\u672C\u91CC\u8BF7\u663E\u5F0F\u52A0 --yes\u3002"
994
- );
974
+ throw new CliError(t7("plugin.update_needs_tty"), 1, t7("plugin.update_needs_tty_hint"));
995
975
  }
996
976
  const outcome = await updatePlugin(name, {
997
977
  ...locations(),
@@ -1000,24 +980,30 @@ async function runUpdate(name, yes) {
1000
980
  ...yes ? {} : { confirm: askInstall }
1001
981
  });
1002
982
  if (!outcome.ok) throw new CliError(outcome.reason);
1003
- log5(`\u2713 ${"message" in outcome ? outcome.message : `\u5DF2\u66F4\u65B0\u5230 ${outcome.record.version}`}`);
983
+ log5(
984
+ `\u2713 ${"message" in outcome ? outcome.message : t7("plugin.updated_to", { version: outcome.record.version })}`
985
+ );
1004
986
  }
1005
987
  async function askInstall(preview2) {
1006
988
  for (const line of renderPreview(preview2)) log5(line);
1007
989
  const { confirm } = await import("@inquirer/prompts");
1008
- return confirm({ message: "\u786E\u8BA4\u5B89\u88C5\uFF1F", default: false });
990
+ return confirm({ message: t7("plugin.confirm_install"), default: false });
1009
991
  }
1010
992
  function renderPreview(preview2) {
1011
993
  const { manifest, source, inventory } = preview2;
1012
994
  const lines = [""];
1013
- lines.push(`\u5C06\u5B89\u88C5 ${manifest.name}@${manifest.version}`);
995
+ lines.push(t7("plugin.will_install", { name: manifest.name, version: manifest.version }));
1014
996
  if (manifest.description) lines.push(` ${manifest.description}`);
1015
- lines.push(` \u6765\u6E90: ${source.raw}\uFF08${source.type}\uFF09`);
1016
- if (source.sha256) lines.push(` \u6821\u9A8C\u548C: sha256:${source.sha256}\uFF08\u5DF2\u6838\u5BF9\uFF09`);
1017
- if (manifest.author) lines.push(` \u4F5C\u8005: ${manifest.author.name}`);
1018
- if (manifest.homepage) lines.push(` \u4E3B\u9875: ${manifest.homepage}`);
997
+ lines.push(t7("plugin.preview_source", { raw: source.raw, type: source.type }));
998
+ if (source.sha256) lines.push(t7("plugin.preview_sha", { sha: source.sha256 }));
999
+ if (manifest.author) lines.push(t7("plugin.preview_author", { name: manifest.author.name }));
1000
+ if (manifest.homepage) lines.push(t7("plugin.preview_homepage", { url: manifest.homepage }));
1001
+ if (preview2.versionUnmet) {
1002
+ const { required, current } = preview2.versionUnmet;
1003
+ lines.push(` ${t7("plugin.preview_version_unmet", { required, current })}`);
1004
+ }
1019
1005
  lines.push("");
1020
- lines.push(" \u5B83\u4F1A\u5E26\u6765:");
1006
+ lines.push(t7("plugin.brings_head_indented"));
1021
1007
  for (const line of renderInventory(inventory)) lines.push(` ${line}`);
1022
1008
  lines.push("");
1023
1009
  return lines;
@@ -1025,67 +1011,72 @@ function renderPreview(preview2) {
1025
1011
  function renderInventory(inv) {
1026
1012
  const lines = [];
1027
1013
  if (inventoryTotal(inv) === 0) {
1028
- lines.push("\uFF08\u6CA1\u6709\u4EFB\u4F55\u6269\u5C55\u7269 \u2014\u2014 \u88C5\u4E86\u7B49\u4E8E\u6CA1\u88C5\uFF0C\u786E\u8BA4\u4E00\u4E0B\u76EE\u5F55\u7ED3\u6784\u5BF9\u4E0D\u5BF9\uFF09");
1029
- }
1030
- if (inv.commands.length > 0) lines.push(`\u547D\u4EE4 ${inv.commands.length} \u6761: ${list(inv.commands)}`);
1031
- if (inv.roles.length > 0) lines.push(`\u89D2\u8272 ${inv.roles.length} \u4E2A: ${list(inv.roles)}`);
1032
- if (inv.skills.length > 0) lines.push(`\u6280\u80FD ${inv.skills.length} \u4E2A: ${list(inv.skills)}`);
1014
+ lines.push(t7("plugin.inventory_empty"));
1015
+ }
1016
+ if (inv.commands.length > 0)
1017
+ lines.push(t7("plugin.inv_commands", { count: inv.commands.length, names: list(inv.commands) }));
1018
+ if (inv.roles.length > 0)
1019
+ lines.push(t7("plugin.inv_roles", { count: inv.roles.length, names: list(inv.roles) }));
1020
+ if (inv.skills.length > 0)
1021
+ lines.push(t7("plugin.inv_skills", { count: inv.skills.length, names: list(inv.skills) }));
1033
1022
  if (inv.hooks.length > 0) {
1034
- const tally = inv.hooks.map((h) => `${h.type} \xD7 ${h.count}`).join("\uFF0C");
1035
- lines.push(`\u26A0 hook ${tally} \u2014\u2014 \u6BCF\u4E00\u6761\u90FD\u662F\u4F1A\u5728\u4F60\u673A\u5668\u4E0A\u6267\u884C\u7684 shell \u547D\u4EE4`);
1023
+ const tally = inv.hooks.map((h) => `${h.type} \xD7 ${h.count}`).join(t7("plugin.inv_join"));
1024
+ lines.push(t7("plugin.inv_hooks", { tally }));
1036
1025
  }
1037
- if (inv.denyRules > 0) lines.push(`deny \u89C4\u5219 ${inv.denyRules} \u6761\uFF08\u53EA\u4F1A\u8BA9 agent \u80FD\u505A\u7684\u66F4\u5C11\uFF09`);
1026
+ if (inv.denyRules > 0) lines.push(t7("plugin.inv_deny", { count: inv.denyRules }));
1038
1027
  if (inv.mcpServers.length > 0) {
1039
1028
  lines.push(
1040
- t2("plugin.preview_mcp", {
1029
+ t7("plugin.preview_mcp", {
1041
1030
  count: inv.mcpServers.length,
1042
1031
  names: list(inv.mcpServers)
1043
1032
  })
1044
1033
  );
1045
1034
  }
1046
1035
  if (inv.ignoredBuckets.length > 0) {
1047
- lines.push(
1048
- `settings.json \u91CC\u7684 ${inv.ignoredBuckets.join(" / ")} \u4F1A\u88AB\u5FFD\u7565 \u2014\u2014 \u63D2\u4EF6\u4E0D\u80FD\u66FF\u4F60\u653E\u5F00\u6743\u9650`
1049
- );
1036
+ lines.push(t7("plugin.inv_ignored", { buckets: inv.ignoredBuckets.join(" / ") }));
1050
1037
  }
1051
1038
  if (inv.jsTools) {
1052
- lines.push("tools/ \u91CC\u7684 JS \u5DE5\u5177\u4E0D\u4F1A\u88AB\u52A0\u8F7D\uFF08\u7B2C\u4E00\u7248\u4E0D\u5728 epoch \u8FDB\u7A0B\u91CC\u8DD1\u7B2C\u4E09\u65B9\u4EE3\u7801\uFF09");
1039
+ lines.push(t7("plugin.inv_js_tools"));
1053
1040
  }
1054
1041
  for (const detail of issueDetails(inv.issues)) lines.push(`\u26A0 ${detail}`);
1055
1042
  return lines;
1056
1043
  }
1057
1044
  function list(names) {
1058
1045
  const shown = names.slice(0, 8).join(", ");
1059
- return names.length > 8 ? `${shown} \u2026\uFF08\u8FD8\u6709 ${names.length - 8} \u4E2A\uFF09` : shown;
1046
+ return names.length > 8 ? t7("plugin.list_more", { shown, n: names.length - 8 }) : shown;
1060
1047
  }
1061
1048
  function printMarketplaces() {
1062
1049
  const state = readMarketplaces(marketplacesPath(EPOCH_HOME));
1063
1050
  for (const detail of issueDetails(state.issues)) log5(`\u26A0 ${detail}`);
1064
1051
  if (state.records.length === 0) {
1065
- log5("\u6CA1\u6709\u5DF2\u52A0\u7684\u5E02\u573A\u3002");
1066
- log5("\u52A0\u4E00\u4E2A\uFF1Aepoch plugin marketplace add github:owner/repo");
1052
+ log5(t7("plugin.no_markets"));
1053
+ log5(t7("plugin.no_markets_hint"));
1067
1054
  return;
1068
1055
  }
1069
- log5(`\u5DF2\u52A0\u5E02\u573A\uFF08${marketplacesPath(EPOCH_HOME)}\uFF09
1056
+ log5(`${t7("plugin.markets_head", { path: marketplacesPath(EPOCH_HOME) })}
1070
1057
  `);
1071
1058
  for (const record of state.records) {
1072
1059
  const owner = record.catalog.owner ? ` by ${record.catalog.owner.name}` : "";
1073
- log5(` ${record.name} ${record.catalog.plugins.length} \u4E2A\u63D2\u4EF6 \u2190 ${record.source}${owner}`);
1060
+ log5(
1061
+ ` ${record.name} ${t7("plugin.market_plugin_count", {
1062
+ count: record.catalog.plugins.length
1063
+ })} \u2190 ${record.source}${owner}`
1064
+ );
1074
1065
  if (record.catalog.description) log5(` ${record.catalog.description}`);
1075
1066
  }
1076
1067
  }
1077
1068
  function printSearch(keyword) {
1078
1069
  const statePath = marketplacesPath(EPOCH_HOME);
1079
1070
  if (readMarketplaces(statePath).records.length === 0) {
1080
- log5("\u8FD8\u6CA1\u6709\u52A0\u4EFB\u4F55\u5E02\u573A\uFF0C\u5148 epoch plugin marketplace add <\u5730\u5740>\u3002");
1071
+ log5(t7("plugin.search_no_markets"));
1081
1072
  return;
1082
1073
  }
1083
1074
  const hits = searchMarketplaces(keyword, statePath);
1084
1075
  if (hits.length === 0) {
1085
- log5(keyword ? `\u6CA1\u6709\u5339\u914D "${keyword}" \u7684\u63D2\u4EF6\u3002` : "\u5DF2\u52A0\u7684\u5E02\u573A\u91CC\u4E00\u4E2A\u63D2\u4EF6\u90FD\u6CA1\u6709\u3002");
1076
+ log5(keyword ? t7("plugin.search_no_hit", { keyword }) : t7("plugin.search_all_empty"));
1086
1077
  return;
1087
1078
  }
1088
- log5(`${hits.length} \u4E2A\u7ED3\u679C:
1079
+ log5(`${t7("plugin.search_count", { n: hits.length })}
1089
1080
  `);
1090
1081
  for (const hit of hits) {
1091
1082
  const category = hit.entry.category ? ` [${hit.entry.category}]` : "";
@@ -1093,7 +1084,7 @@ function printSearch(keyword) {
1093
1084
  if (hit.entry.description) log5(` ${hit.entry.description}`);
1094
1085
  }
1095
1086
  log5(`
1096
- \u88C5\u4E00\u4E2A\uFF1Aepoch plugin install ${hits[0]?.ref ?? "<\u5E02\u573A>/<\u63D2\u4EF6>"}`);
1087
+ ${t7("plugin.search_install_hint", { ref: hits[0]?.ref ?? t7("plugin.ref_placeholder") })}`);
1097
1088
  }
1098
1089
  async function toggle(name, enabled) {
1099
1090
  const outcome = await setPluginEnabled(name, enabled, {
@@ -1106,40 +1097,41 @@ function validateDir(dir) {
1106
1097
  const read = readPluginManifest(dir);
1107
1098
  if (!read.manifest) {
1108
1099
  const detail = issueDetails(read.issues).map((d) => ` - ${d}`).join("\n");
1109
- throw new CliError(`${dir} \u4E0D\u662F\u4E00\u4E2A\u53EF\u7528\u7684\u63D2\u4EF6:
1100
+ throw new CliError(`${t7("plugin.validate_bad", { dir })}
1110
1101
  ${detail}`);
1111
1102
  }
1112
1103
  const manifest = read.manifest;
1113
- log5(`\u2713 \u6E05\u5355\u53EF\u7528: ${manifest.name}@${manifest.version}`);
1104
+ log5(`\u2713 ${t7("plugin.validate_ok", { name: manifest.name, version: manifest.version })}`);
1114
1105
  if (manifest.epochVersion) log5(` epochVersion: ${manifest.epochVersion}`);
1115
1106
  for (const detail of issueDetails(read.issues)) log5(`\u26A0 ${detail}`);
1116
- log5("\n\u5B83\u4F1A\u5E26\u6765:");
1107
+ log5(`
1108
+ ${t7("plugin.brings_head")}`);
1117
1109
  const inventory = scanPluginDir(dir, manifest.name);
1118
1110
  for (const line of renderInventory(inventory)) log5(` ${line}`);
1119
1111
  if (inventory.issues.length > 0) {
1120
- throw new CliError(`\u6709 ${inventory.issues.length} \u5904\u95EE\u9898\uFF0C\u4FEE\u5B8C\u518D\u88C5`);
1112
+ throw new CliError(t7("plugin.inv_issues", { count: inventory.issues.length }));
1121
1113
  }
1122
1114
  }
1123
-
1124
1115
  // src/commands/run.ts
1125
1116
  import { existsSync as existsSync6 } from "fs";
1126
1117
  import { dirname as dirname3, join as join5 } from "path";
1127
1118
  import { fileURLToPath as fileURLToPath3 } from "url";
1128
1119
  import { isNonInteractive as isNonInteractive8, OPERATION_TYPES as OPERATION_TYPES3, PERMISSION_LEVELS as PERMISSION_LEVELS3 } from "@epoch-agent/core";
1120
+ import { t as t24 } from "@epoch-agent/infra";
1129
1121
  import {
1130
1122
  HEADLESS_INPUT_FORMATS as HEADLESS_INPUT_FORMATS2,
1131
1123
  HEADLESS_OUTPUT_FORMATS as HEADLESS_OUTPUT_FORMATS2,
1132
1124
  PROVIDER_TYPES as PROVIDER_TYPES4
1133
1125
  } from "@epoch-agent/protocol";
1134
-
1135
1126
  // src/headless/session.ts
1136
1127
  import { OPERATION_TYPES } from "@epoch-agent/core";
1128
+ import { t as t11 } from "@epoch-agent/infra";
1137
1129
  import { buildRuntime } from "@epoch-agent/runtime";
1138
-
1139
1130
  // src/commands/run-flags.ts
1140
1131
  import { existsSync as existsSync3 } from "fs";
1141
1132
  import { delimiter, resolve } from "path";
1142
1133
  import { isOperationType, resolveWorkspace } from "@epoch-agent/core";
1134
+ import { t as t8 } from "@epoch-agent/infra";
1143
1135
  import {
1144
1136
  HEADLESS_INPUT_FORMATS,
1145
1137
  HEADLESS_OUTPUT_FORMATS,
@@ -1159,18 +1151,14 @@ function parseResumeArg(raw) {
1159
1151
  }
1160
1152
  function assertResumeFlagsExclusive(opts) {
1161
1153
  if (opts.continue === true && opts.resume !== void 0) {
1162
- throw new CliError(
1163
- "--continue \u548C --resume \u4E0D\u80FD\u4E00\u8D77\u7528",
1164
- 1,
1165
- "--continue \u63A5\u672C\u9879\u76EE\u6700\u8FD1\u4E00\u4E2A\u4F1A\u8BDD\uFF1B--resume \u6307\u5B9A\u4E00\u4E2A\uFF08\u6216\u4EA4\u4E92\u5F0F\u9009\uFF09\u3002\u53EA\u7ED9\u4E00\u4E2A"
1166
- );
1154
+ throw new CliError(t8("flags.resume_conflict"), 1, t8("flags.resume_conflict_hint"));
1167
1155
  }
1168
1156
  }
1169
1157
  function applySettingsPath(path) {
1170
1158
  if (!path) return;
1171
1159
  const abs = resolve(path);
1172
1160
  if (!existsSync3(abs)) {
1173
- throw new CliError(`--settings \u6307\u5411\u7684\u6587\u4EF6\u4E0D\u5B58\u5728: ${path}`, 1, `\u89E3\u6790\u6210: ${abs}`);
1161
+ throw new CliError(t8("flags.settings_missing", { path }), 1, t8("flags.resolved_to", { abs }));
1174
1162
  }
1175
1163
  process.env.EPOCH_SETTINGS = abs;
1176
1164
  }
@@ -1181,9 +1169,9 @@ function applyAddDirs(dirs) {
1181
1169
  const fatal = issues.filter((i) => i.kind !== "redundant");
1182
1170
  if (fatal.length > 0) {
1183
1171
  throw new CliError(
1184
- `--add-dir \u6307\u5411\u7684\u76EE\u5F55\u4E0D\u53EF\u7528: ${fatal.map((i) => i.input).join(", ")}`,
1172
+ t8("flags.add_dir_unusable", { inputs: fatal.map((i) => i.input).join(", ") }),
1185
1173
  1,
1186
- fatal.map((i) => i.detail).join("\uFF1B")
1174
+ fatal.map((i) => i.detail).join(t8("flags.detail_sep"))
1187
1175
  );
1188
1176
  }
1189
1177
  process.env.EPOCH_ADD_DIR = abs.join(delimiter);
@@ -1215,17 +1203,17 @@ function resolveOutputFormat(opts) {
1215
1203
  const explicit = opts.outputFormat;
1216
1204
  if (explicit !== void 0 && !isHeadlessOutputFormat(explicit)) {
1217
1205
  throw new CliError(
1218
- `--output-format \u4E0D\u8BA4\u8BC6: ${explicit}`,
1206
+ t8("flags.output_format_unknown", { value: explicit }),
1219
1207
  1,
1220
- `\u53EF\u9009: ${HEADLESS_OUTPUT_FORMATS.join(" / ")}`
1208
+ t8("flags.choices", { choices: HEADLESS_OUTPUT_FORMATS.join(" / ") })
1221
1209
  );
1222
1210
  }
1223
1211
  if (explicit === void 0) return opts.json === true ? "json" : "text";
1224
1212
  if (opts.json === true && explicit !== "json") {
1225
1213
  throw new CliError(
1226
- `--json \u548C --output-format ${explicit} \u77DB\u76FE`,
1214
+ t8("flags.json_conflict", { value: explicit }),
1227
1215
  1,
1228
- "--json \u5C31\u662F --output-format json \u7684\u522B\u540D\uFF0C\u53EA\u7ED9\u4E00\u4E2A"
1216
+ t8("flags.json_conflict_hint")
1229
1217
  );
1230
1218
  }
1231
1219
  return explicit;
@@ -1234,9 +1222,9 @@ function resolveHeadlessFormats(opts) {
1234
1222
  const raw = opts.inputFormat;
1235
1223
  if (raw !== void 0 && !isHeadlessInputFormat(raw)) {
1236
1224
  throw new CliError(
1237
- `--input-format \u4E0D\u8BA4\u8BC6: ${raw}`,
1225
+ t8("flags.input_format_unknown", { value: raw }),
1238
1226
  1,
1239
- `\u53EF\u9009: ${HEADLESS_INPUT_FORMATS.join(" / ")}`
1227
+ t8("flags.choices", { choices: HEADLESS_INPUT_FORMATS.join(" / ") })
1240
1228
  );
1241
1229
  }
1242
1230
  const input2 = raw ?? "text";
@@ -1246,11 +1234,7 @@ function resolveHeadlessFormats(opts) {
1246
1234
  }
1247
1235
  const output = resolveOutputFormat(opts);
1248
1236
  if (output !== "stream-json") {
1249
- throw new CliError(
1250
- `--input-format stream-json \u53EA\u80FD\u914D --output-format stream-json\uFF08\u73B0\u5728\u662F ${output}\uFF09`,
1251
- 1,
1252
- "\u957F\u9A7B\u6A21\u5F0F\u4E0B\u5BBF\u4E3B\u8981\u5728\u4E8B\u4EF6\u6D41\u91CC\u6536 approval-request \u5E76\u7B54\u590D\uFF0C\u53E6\u5916\u4E24\u79CD\u8F93\u51FA\u683C\u5F0F\u6CA1\u6709\u8FD9\u4E00\u5E27 \u2014\u2014 \u6536\u4E0D\u5230\u95EE\u9898\u5374\u8981\u56DE\u7B54\u3002\u4E0D\u7ED9 --output-format \u65F6\u4F1A\u81EA\u52A8\u8865\u4E0A"
1253
- );
1237
+ throw new CliError(t8("flags.stream_pair", { output }), 1, t8("flags.stream_pair_hint"));
1254
1238
  }
1255
1239
  return { input: input2, output };
1256
1240
  }
@@ -1261,9 +1245,9 @@ function resolveApproverProgram(raw) {
1261
1245
  const abs = resolve(value);
1262
1246
  if (!existsSync3(abs)) {
1263
1247
  throw new CliError(
1264
- `--permission-prompt-tool \u6307\u5411\u7684\u7A0B\u5E8F\u4E0D\u5B58\u5728: ${value}`,
1248
+ t8("flags.prompt_tool_missing", { value }),
1265
1249
  1,
1266
- `\u89E3\u6790\u6210: ${abs}\u3002\u8981\u8DD1\u4E00\u4E2A\u89E3\u91CA\u5668\u5C31\u8FDE\u7740\u5199\uFF0C\u4F8B\u5982 --permission-prompt-tool "node ./approver.js"`
1250
+ t8("flags.prompt_tool_missing_hint", { abs })
1267
1251
  );
1268
1252
  }
1269
1253
  return `"${abs}"`;
@@ -1281,14 +1265,15 @@ function parsePositive(raw, flag, extra = () => true) {
1281
1265
  const n = Number(raw);
1282
1266
  if (!Number.isFinite(n) || n <= 0 || !extra(n)) {
1283
1267
  throw new CliError(
1284
- `${flag} \u4E0D\u662F\u5408\u6CD5\u7684\u503C: ${raw}`,
1268
+ t8("flags.not_a_number", { flag, raw }),
1285
1269
  1,
1286
- flag === "--max-turns" ? "\u8981\u4E00\u4E2A\u6B63\u6574\u6570\uFF0C\u4F8B\u5982 --max-turns 5" : "\u8981\u4E00\u4E2A\u6B63\u6570\uFF0C\u4F8B\u5982 0.50"
1270
+ flag === "--max-turns" ? t8("flags.want_positive_int") : t8("flags.want_positive")
1287
1271
  );
1288
1272
  }
1289
1273
  return n;
1290
1274
  }
1291
-
1275
+ // src/headless/driver.ts
1276
+ import { t as t10 } from "@epoch-agent/infra";
1292
1277
  // src/headless/emit.ts
1293
1278
  import {
1294
1279
  HEADLESS_PROTOCOL_VERSION
@@ -1297,31 +1282,14 @@ import { ApprovalRelay, QuestionRelay, toSerializable } from "@epoch-agent/runti
1297
1282
  var HeadlessEmitter = class {
1298
1283
  write;
1299
1284
  clock;
1300
- /**
1301
- * 审批闭包的登记表。
1302
- *
1303
- * 复用 runtime 那一份而不是自己写个 Map:它把两个很容易写错的点封住了
1304
- * (答复一次就出账、**界面没了不等于用户拒绝**),而 headless 这边
1305
- * 「宿主把 stdin 关了」和 web 那边「浏览器断连」是同一件事。
1306
- */
1307
1285
  relay = new ApprovalRelay();
1308
- /**
1309
- * 提问闭包的登记表(方案 34)。同样复用 runtime 那一份 ——
1310
- * 它比 `ApprovalRelay` 少一个「替用户答一个」的方法,而 headless 这边
1311
- * 「宿主把 stdin 关了」正是最容易顺手编一个答案的地方。
1312
- */
1313
1286
  questions = new QuestionRelay();
1314
- /** 全局广播游标。0 是保留值(`init`),所以其余帧从 1 开始 */
1315
1287
  seq = 0;
1316
1288
  sessionId = "";
1317
1289
  constructor(opts = {}) {
1318
1290
  this.write = opts.write ?? ((line) => void process.stdout.write(line + "\n"));
1319
1291
  this.clock = opts.clock ?? Date.now;
1320
1292
  }
1321
- /**
1322
- * 第一帧。`seq: 0`、**不带 `sessionId`** —— 它描述的是这条流本身,
1323
- * 会话 id 在它自己的载荷里(同 hub 的 `connected`)。
1324
- */
1325
1293
  init(info) {
1326
1294
  this.sessionId = info.sessionId;
1327
1295
  const event = {
@@ -1331,12 +1299,6 @@ var HeadlessEmitter = class {
1331
1299
  };
1332
1300
  this.write(JSON.stringify({ seq: 0, ts: this.clock(), event }));
1333
1301
  }
1334
- /**
1335
- * 发一个引擎事件。
1336
- *
1337
- * 返回**摘出 `requestId` 之后**的那份视图 —— 调用方(长驻模式的驱动)要靠
1338
- * 它知道刚发出去的审批叫什么 id。非审批事件返回 undefined 那一格。
1339
- */
1340
1302
  agentEvent(ev) {
1341
1303
  const serialized = toSerializable(ev);
1342
1304
  this.relay.track(serialized);
@@ -1344,65 +1306,27 @@ var HeadlessEmitter = class {
1344
1306
  this.publish(stripArtifactData(serialized.event));
1345
1307
  return serialized.event;
1346
1308
  }
1347
- /** 一轮的收尾帧 */
1348
1309
  result(result) {
1349
1310
  this.publish({ type: "result", ...result });
1350
1311
  }
1351
- /**
1352
- * 把宿主的答复接回引擎。
1353
- *
1354
- * @returns `false` = 这个 requestId 不认识(重复答复 / 上一轮的 id)。
1355
- * 调用方该把它当成 stderr 上的一行日志而不是错误:宿主的用户
1356
- * 在按钮上双击一下就会走到这里。
1357
- */
1358
1312
  respondApproval(requestId, outcome, note) {
1359
1313
  return this.relay.respond(requestId, outcome, note);
1360
1314
  }
1361
- /**
1362
- * 把宿主的提问答复接回引擎(方案 34 验收 10)。
1363
- *
1364
- * @returns `false` = 这个 requestId 不认识。处置同 `respondApproval`:
1365
- * stderr 上一行日志,不是错误。
1366
- */
1367
1315
  respondQuestion(requestId, answer) {
1368
1316
  return this.questions.respond(requestId, answer);
1369
1317
  }
1370
- /** 还挂着几条审批没答复 */
1371
1318
  get pendingApprovals() {
1372
1319
  return this.relay.pendingCount;
1373
1320
  }
1374
- /**
1375
- * 还挂着几条提问没答复。
1376
- *
1377
- * 和 `pendingApprovals` 分开数:驱动器要用它判「stdin 没了还有东西在等吗」,
1378
- * 而合成一个数之后,日志说不清放弃的是审批还是提问 —— 排查时那正是第一个问题。
1379
- */
1380
1321
  get pendingQuestions() {
1381
1322
  return this.questions.pendingCount;
1382
1323
  }
1383
- /**
1384
- * 宿主没了(stdin 关了 / 收到 `abort`):把挂起的审批**放弃**,返回放弃的个数。
1385
- *
1386
- * **一个 promise 都不 resolve** —— 逐个 `deny` 会让模型收到一串「用户拒绝」,
1387
- * 于是它认为这是有意否决,换个方式再试一遍。正确做法是放弃 + 中止整轮,
1388
- * 让 `AgentSession` 的 finally 去兜底。这条规矩写死在
1389
- * [runtime/src/serialize.ts](../../../runtime/src/serialize.ts) 的类注释里,
1390
- * hub 那边也守着同一条。
1391
- */
1392
1324
  abandonApprovals() {
1393
1325
  return this.relay.abandon();
1394
1326
  }
1395
- /**
1396
- * 同上,提问那一半(方案 34 验收 8)。
1397
- *
1398
- * **一个 promise 都不 resolve。** 逐个塞一个 `skipped` 进去等于告诉模型
1399
- * 「用户看到了、选择不答」,而真相是宿主已经不在了 —— 那个问题从没被人看到过。
1400
- * 放弃 + 中止之后模型看到的是「这一轮被中止了」,那才是实话。
1401
- */
1402
1327
  abandonQuestions() {
1403
1328
  return this.questions.abandon();
1404
1329
  }
1405
- /** 广播一帧:占一个序号、带上 sessionId、写出去 */
1406
1330
  publish(event) {
1407
1331
  const frame = {
1408
1332
  seq: ++this.seq,
@@ -1423,7 +1347,6 @@ function withoutData(artifact) {
1423
1347
  delete copy.data;
1424
1348
  return copy;
1425
1349
  }
1426
-
1427
1350
  // src/headless/outcome.ts
1428
1351
  function emptyOutcome(diagnostics) {
1429
1352
  return {
@@ -1467,14 +1390,14 @@ function initInfoOf(runtime, cwd) {
1467
1390
  model: runtime.config.model,
1468
1391
  ...provider ? { provider } : {},
1469
1392
  permissionLevel: runtime.permissions.level(),
1470
- tools: runtime.tools.map((t14) => t14.name),
1393
+ tools: runtime.tools.map((t39) => t39.name),
1471
1394
  usageScope: runtime.usageScope,
1472
1395
  diagnostics: runtime.diagnostics
1473
1396
  };
1474
1397
  }
1475
-
1476
1398
  // src/headless/read.ts
1477
1399
  import { createInterface } from "readline";
1400
+ import { t as t9 } from "@epoch-agent/infra";
1478
1401
  import {
1479
1402
  APPROVAL_OUTCOMES,
1480
1403
  HEADLESS_INPUT_EVENT_TYPES,
@@ -1488,14 +1411,22 @@ function parseInputLine(line) {
1488
1411
  try {
1489
1412
  raw = JSON.parse(text);
1490
1413
  } catch (err2) {
1491
- return { ok: false, message: `\u4E0D\u662F\u5408\u6CD5 JSON\uFF08${describe2(err2)}\uFF09\uFF1A${preview(text)}` };
1414
+ return {
1415
+ ok: false,
1416
+ message: t9("headless_input.bad_json", { why: describe2(err2), preview: preview(text) })
1417
+ };
1418
+ }
1419
+ if (!isRecord(raw)) {
1420
+ return { ok: false, message: t9("headless_input.not_object", { preview: preview(text) }) };
1492
1421
  }
1493
- if (!isRecord(raw)) return { ok: false, message: `\u9876\u5C42\u5FC5\u987B\u662F\u5BF9\u8C61\uFF1A${preview(text)}` };
1494
1422
  const type = raw["type"];
1495
1423
  if (typeof type !== "string" || !HEADLESS_INPUT_EVENT_TYPES.has(type)) {
1496
1424
  return {
1497
1425
  ok: false,
1498
- message: `\u4E0D\u8BA4\u8BC6\u7684 type: ${JSON.stringify(type)}\uFF0C\u53EF\u9009 ${[...HEADLESS_INPUT_EVENT_TYPES].join(" / ")}`
1426
+ message: t9("headless_input.bad_type", {
1427
+ value: JSON.stringify(type),
1428
+ choices: [...HEADLESS_INPUT_EVENT_TYPES].join(" / ")
1429
+ })
1499
1430
  };
1500
1431
  }
1501
1432
  if (type === "abort" || type === "close") return { ok: true, event: { type } };
@@ -1506,15 +1437,15 @@ function parseInputLine(line) {
1506
1437
  function parseUserMessage(raw) {
1507
1438
  const content = raw["content"];
1508
1439
  if (typeof content === "string") {
1509
- if (!content.trim()) return { ok: false, message: "user-message \u7684 content \u662F\u7A7A\u7684" };
1440
+ if (!content.trim()) return { ok: false, message: t9("headless_input.msg_empty") };
1510
1441
  return { ok: true, event: { type: "user-message", content } };
1511
1442
  }
1512
1443
  if (!Array.isArray(content) || content.length === 0) {
1513
- return { ok: false, message: "user-message \u7684 content \u8981\u4E48\u662F\u975E\u7A7A\u5B57\u7B26\u4E32\uFF0C\u8981\u4E48\u662F\u975E\u7A7A\u90E8\u4EF6\u6570\u7EC4" };
1444
+ return { ok: false, message: t9("headless_input.msg_bad_content") };
1514
1445
  }
1515
1446
  const bad = content.findIndex((p) => !isRecord(p) || typeof p["type"] !== "string");
1516
1447
  if (bad >= 0) {
1517
- return { ok: false, message: `user-message \u7684 content[${bad}] \u4E0D\u662F\u5E26 type \u7684\u5BF9\u8C61` };
1448
+ return { ok: false, message: t9("headless_input.msg_bad_part", { index: bad }) };
1518
1449
  }
1519
1450
  return {
1520
1451
  ok: true,
@@ -1524,18 +1455,21 @@ function parseUserMessage(raw) {
1524
1455
  function parseApprovalResponse(raw) {
1525
1456
  const requestId = raw["requestId"];
1526
1457
  if (typeof requestId !== "string" || !requestId) {
1527
- return { ok: false, message: "approval-response \u7F3A requestId\uFF08\u8981\u4E00\u4E2A\u975E\u7A7A\u5B57\u7B26\u4E32\uFF09" };
1458
+ return { ok: false, message: t9("headless_input.approval_no_id") };
1528
1459
  }
1529
1460
  const outcome = raw["outcome"];
1530
1461
  if (typeof outcome !== "string" || !isApprovalOutcome(outcome)) {
1531
1462
  return {
1532
1463
  ok: false,
1533
- message: `approval-response \u7684 outcome \u4E0D\u8BA4\u8BC6: ${JSON.stringify(outcome)}\uFF0C\u53EF\u9009 ${APPROVAL_OUTCOMES.join(" / ")}`
1464
+ message: t9("headless_input.approval_bad_outcome", {
1465
+ value: JSON.stringify(outcome),
1466
+ choices: APPROVAL_OUTCOMES.join(" / ")
1467
+ })
1534
1468
  };
1535
1469
  }
1536
1470
  const note = raw["note"];
1537
1471
  if (note !== void 0 && typeof note !== "string") {
1538
- return { ok: false, message: "approval-response \u7684 note \u8981\u662F\u5B57\u7B26\u4E32" };
1472
+ return { ok: false, message: t9("headless_input.approval_bad_note") };
1539
1473
  }
1540
1474
  return {
1541
1475
  ok: true,
@@ -1550,17 +1484,19 @@ function parseApprovalResponse(raw) {
1550
1484
  function parseQuestionResponse(raw) {
1551
1485
  const requestId = raw["requestId"];
1552
1486
  if (typeof requestId !== "string" || !requestId) {
1553
- return { ok: false, message: "question-response \u7F3A requestId\uFF08\u8981\u4E00\u4E2A\u975E\u7A7A\u5B57\u7B26\u4E32\uFF09" };
1487
+ return { ok: false, message: t9("headless_input.question_no_id") };
1554
1488
  }
1555
1489
  if (!isQuestionAnswerMap(raw["answers"])) {
1556
1490
  return {
1557
1491
  ok: false,
1558
- message: 'question-response \u7684 answers \u8981\u662F\u300C\u95EE\u53E5 \u2192 \u5B57\u7B26\u4E32\u6216\u5B57\u7B26\u4E32\u6570\u7EC4\u300D\u7684\u5BF9\u8C61\uFF08\u7B54\u4E0D\u4E0A\u6765\u5C31\u53D1 {"answers":{},"skipped":true}\uFF09'
1492
+ message: t9("headless_input.question_bad_answers", {
1493
+ json: '{"answers":{},"skipped":true}'
1494
+ })
1559
1495
  };
1560
1496
  }
1561
1497
  const skipped = raw["skipped"];
1562
1498
  if (skipped !== void 0 && typeof skipped !== "boolean") {
1563
- return { ok: false, message: "question-response \u7684 skipped \u8981\u662F\u5E03\u5C14" };
1499
+ return { ok: false, message: t9("headless_input.question_bad_skipped") };
1564
1500
  }
1565
1501
  return {
1566
1502
  ok: true,
@@ -1589,7 +1525,6 @@ function describe2(err2) {
1589
1525
  function preview(text) {
1590
1526
  return text.length > 80 ? `${text.slice(0, 80)}\u2026` : text;
1591
1527
  }
1592
-
1593
1528
  // src/headless/driver.ts
1594
1529
  async function driveStreamJson(opts) {
1595
1530
  const { limits } = opts;
@@ -1604,7 +1539,7 @@ async function driveStreamJson(opts) {
1604
1539
  const parsed = parseInputLine(line);
1605
1540
  if (parsed === void 0) continue;
1606
1541
  if (!parsed.ok) {
1607
- warn4(`[\u8F93\u5165\u9519\u8BEF] ${parsed.message}`);
1542
+ warn4(t10("headless_driver.bad_input", { message: parsed.message }));
1608
1543
  state.sawBadInput = true;
1609
1544
  continue;
1610
1545
  }
@@ -1624,45 +1559,12 @@ var DriverState = class {
1624
1559
  emitter;
1625
1560
  limits;
1626
1561
  warn;
1627
- /** 当前这一轮,idle 时为 null —— 于是「空转时收到 abort」不会毒掉下一轮 */
1628
1562
  turn = null;
1629
1563
  controller = null;
1630
- /**
1631
- * 还没轮到的用户消息,先进先出。
1632
- *
1633
- * **排队而不是拒绝**:stdin 是一条有序的单写者通道,宿主完全可能一口气写完
1634
- * 三条就不管了(`printf '…\n…\n' | epoch --input-format stream-json` 正是
1635
- * 最容易上手的用法)。拒绝的话第二条起全部无声消失。
1636
- *
1637
- * 排队**不等于交织**:下一轮要等上一轮的 `result` 之后才开,历史累积的顺序
1638
- * 仍然是确定的 —— hub 那边用 409 挡的是「两个标签页同时发」,
1639
- * 那是并发,这里是排队,两回事。
1640
- */
1641
1564
  queue = [];
1642
- /**
1643
- * stdin 已经没了(EOF 或宿主发了 `close`)。
1644
- *
1645
- * 之后再出现的审批请求没人答得了,得当场放弃 —— 否则那一轮会一直挂着。
1646
- */
1647
1565
  inputClosed = false;
1648
- /**
1649
- * 累计的退出码,取**最先出现的非 0** 那个。
1650
- *
1651
- * 显式标 `number` 而不是让它推成 `0`:`EXIT_CODES` 是 `as const`,
1652
- * 不标的话这个字段的类型就是字面量 `0`,`raise()` 一行都赋不进来。
1653
- */
1654
1566
  code = EXIT_CODES.SUCCESS;
1655
- /** 有过坏行。收尾时落 INPUT_ERROR,但不覆盖更主要的失败原因 */
1656
1567
  sawBadInput = false;
1657
- /**
1658
- * 处理一条入站事件。
1659
- *
1660
- * **同步返回,绝不 await 那一轮** —— 这是整个文件最容易写错的一行:
1661
- * 在这里 await 就等于「跑的时候不读 stdin」,而审批答复正是从 stdin 来的,
1662
- * 于是第一次需要确认的操作就把双方锁死。开轮之后立刻回去读下一行。
1663
- *
1664
- * @returns 是否该停止读 stdin(收到 `close`)
1665
- */
1666
1568
  handle(event) {
1667
1569
  if (event.type === "close") return true;
1668
1570
  if (event.type === "abort") {
@@ -1672,7 +1574,7 @@ var DriverState = class {
1672
1574
  }
1673
1575
  if (event.type === "approval-response") {
1674
1576
  if (!this.emitter.respondApproval(event.requestId, event.outcome, event.note)) {
1675
- this.warn(`[\u5FFD\u7565] \u4E0D\u8BA4\u8BC6\u7684 requestId: ${event.requestId}\uFF08\u91CD\u590D\u7B54\u590D\uFF1F\u4E0A\u4E00\u8F6E\u7684\uFF1F\uFF09`);
1577
+ this.warn(t10("headless_driver.unknown_request", { id: event.requestId }));
1676
1578
  }
1677
1579
  return false;
1678
1580
  }
@@ -1682,7 +1584,7 @@ var DriverState = class {
1682
1584
  ...event.skipped ? { skipped: true } : {}
1683
1585
  };
1684
1586
  if (!this.emitter.respondQuestion(event.requestId, answer)) {
1685
- this.warn(`[\u5FFD\u7565] \u4E0D\u8BA4\u8BC6\u7684 requestId: ${event.requestId}\uFF08\u91CD\u590D\u7B54\u590D\uFF1F\u4E0A\u4E00\u8F6E\u7684\uFF1F\uFF09`);
1587
+ this.warn(t10("headless_driver.unknown_request", { id: event.requestId }));
1686
1588
  }
1687
1589
  return false;
1688
1590
  }
@@ -1690,13 +1592,6 @@ var DriverState = class {
1690
1592
  this.pump();
1691
1593
  return false;
1692
1594
  }
1693
- /**
1694
- * 队首那条开跑。已经有一轮在跑就什么都不做 —— 它收尾时会回来再叫一次。
1695
- *
1696
- * **同步返回,绝不 await 那一轮**:这是整个文件最容易写错的一行。在这里 await
1697
- * 就等于「跑的时候不读 stdin」,而审批答复正是从 stdin 来的,于是第一次需要
1698
- * 确认的操作就把双方锁死。
1699
- */
1700
1595
  pump() {
1701
1596
  if (this.turn) return;
1702
1597
  const next = this.queue.shift();
@@ -1709,7 +1604,6 @@ var DriverState = class {
1709
1604
  this.pump();
1710
1605
  });
1711
1606
  }
1712
- /** 跑一轮,把事件逐个上线,收尾发 result */
1713
1607
  async runTurn(content, controller) {
1714
1608
  const out2 = emptyOutcome([]);
1715
1609
  try {
@@ -1742,17 +1636,6 @@ var DriverState = class {
1742
1636
  const limited = limitExitCode(out2, this.limits);
1743
1637
  if (limited !== void 0) this.raise(limited);
1744
1638
  }
1745
- /**
1746
- * 收摊:stdin 关了,或者宿主发了 `close`。
1747
- *
1748
- * **默认让在跑的那一轮跑完**,不是一关就杀。EOF 的含义是「没有更多输入」,
1749
- * 不是「把已经交代的活扔掉」—— 而 `echo '{"type":"user-message",…}' | epoch`
1750
- * 这种一次性用法里,stdin 恰好在消息发出的下一刻就 EOF 了,杀掉的话
1751
- * 这条命令永远只会吐一个 `aborted`。
1752
- *
1753
- * 唯一的例外是**有审批挂着**:那种情况下等下去是等一个永远不会来的答复。
1754
- * 见 `reapLostHost()`。
1755
- */
1756
1639
  async shutdown() {
1757
1640
  this.inputClosed = true;
1758
1641
  if (this.emitter.pendingApprovals > 0 || this.emitter.pendingQuestions > 0) {
@@ -1760,35 +1643,18 @@ var DriverState = class {
1760
1643
  }
1761
1644
  await this.drain();
1762
1645
  }
1763
- /**
1764
- * 宿主没了但还有审批 / 提问挂着:**放弃**它们 + 丢掉排队的 + 中止本轮。
1765
- *
1766
- * 逐个 `deny` 会让模型收到一串「用户拒绝」,于是它认为这是有意否决,
1767
- * 换个方式再试一遍。提问那边逐个塞 `skipped` 是同一类错误的另一种形态:
1768
- * 那等于说「用户看到了、选择不答」,而真相是宿主已经不在了。
1769
- * 放弃 + 中止之后模型看到的是「这一轮被中止了」。
1770
- * 这条规矩在 serialize.ts / hub.ts / 这里各守一遍,三处都不许改回去。
1771
- *
1772
- * 两个数分开报:排查「进程为什么退不掉」时,第一个问题就是「卡在哪种请求上」。
1773
- */
1774
1646
  reapLostHost() {
1775
1647
  const approvals = this.emitter.abandonApprovals();
1776
1648
  if (approvals > 0) {
1777
- this.warn(`[\u6536\u5C3E] \u653E\u5F03\u4E86 ${approvals} \u6761\u6CA1\u7B54\u590D\u7684\u5BA1\u6279\uFF0C\u672C\u8F6E\u4E2D\u6B62\uFF08\u4E0D\u6309\u62D2\u7EDD\u5904\u7406\uFF09`);
1649
+ this.warn(t10("headless_driver.abandoned_approvals", { count: approvals }));
1778
1650
  }
1779
1651
  const questions = this.emitter.abandonQuestions();
1780
1652
  if (questions > 0) {
1781
- this.warn(`[\u6536\u5C3E] \u653E\u5F03\u4E86 ${questions} \u6761\u6CA1\u7B54\u590D\u7684\u63D0\u95EE\uFF0C\u672C\u8F6E\u4E2D\u6B62\uFF08\u4E0D\u4F2A\u9020\u7B54\u6848\uFF09`);
1653
+ this.warn(t10("headless_driver.abandoned_questions", { count: questions }));
1782
1654
  }
1783
1655
  this.queue.length = 0;
1784
1656
  this.controller?.abort();
1785
1657
  }
1786
- /**
1787
- * 等到一轮都不剩。
1788
- *
1789
- * 必须是循环:`pump()` 挂在上一轮的 `finally` 里,所以 `await this.turn` 回来时
1790
- * 队列里的下一条可能已经开跑了。只 await 一次会漏掉后面全部。
1791
- */
1792
1658
  async drain() {
1793
1659
  while (this.turn) await this.turn.catch(() => {
1794
1660
  });
@@ -1797,7 +1663,6 @@ var DriverState = class {
1797
1663
  if (this.sawBadInput) this.raise(EXIT_CODES.INPUT_ERROR);
1798
1664
  return this.code;
1799
1665
  }
1800
- /** 记一个失败码。**先到先得** —— 后面那个通常是前面那个的后果 */
1801
1666
  raise(code) {
1802
1667
  if (this.code === EXIT_CODES.SUCCESS) this.code = code;
1803
1668
  }
@@ -1805,25 +1670,20 @@ var DriverState = class {
1805
1670
  function describe3(err2) {
1806
1671
  return err2 instanceof Error ? err2.message : String(err2);
1807
1672
  }
1808
-
1809
1673
  // src/headless/session.ts
1810
1674
  async function runStreamJsonSession(opts) {
1811
1675
  const { policy: headless, invalid } = parseHeadlessFlags(opts);
1812
1676
  if (invalid.length > 0) {
1813
1677
  throw new CliError(
1814
- `--allow-operation \u4E0D\u8BA4\u8BC6: ${invalid.join(", ")}`,
1678
+ t11("run_once.err_bad_operation", { values: invalid.join(", ") }),
1815
1679
  EXIT_CODES.FAILURE,
1816
- `\u53EF\u9009\u503C: ${OPERATION_TYPES.join(" / ")}`
1680
+ t11("run_once.err_bad_operation_hint", { choices: OPERATION_TYPES.join(" / ") })
1817
1681
  );
1818
1682
  }
1819
1683
  const limits = parseCallLimits(opts);
1820
1684
  warnInertFlags(opts);
1821
1685
  const runtime = await buildRuntime({
1822
- // 和 CLI 的其它入口一样显式传:Ctrl+C 要走 dispose() 关掉 SQLite 连接和
1823
- // MCP 子进程,不能因为库的默认值是 false 就退回「硬杀进程」。
1824
- // 守卫用例:cli/__tests__/signal-handlers.test.ts
1825
1686
  installSignalHandlers: true,
1826
- // 见文件头第 3 条。**不传 onApprovalRequest** —— 让 runtime 用事件桥
1827
1687
  interactive: true,
1828
1688
  ...headless ? { headless } : {}
1829
1689
  });
@@ -1846,49 +1706,46 @@ function reportDeadRuntime(runtime) {
1846
1706
  const detail = runtime.diagnostics.join("; ");
1847
1707
  emitter.agentEvent({
1848
1708
  type: "error",
1849
- message: `provider \u4E0D\u53EF\u7528\uFF0Cagent \u65E0\u6CD5\u542F\u52A8${detail ? `\uFF1A${detail}` : ""}`
1709
+ message: detail ? t11("headless_session.no_provider_detail", {
1710
+ message: t11("run_once.err_no_provider"),
1711
+ detail
1712
+ }) : t11("run_once.err_no_provider")
1850
1713
  });
1851
1714
  emitter.result({ ok: false, reason: "error", text: "", turns: 0 });
1852
1715
  return EXIT_CODES.FAILURE;
1853
1716
  }
1854
1717
  function warnInertFlags(opts) {
1855
1718
  if ((opts.allowTool ?? []).length > 0 || (opts.allowOperation ?? []).length > 0) {
1856
- process.stderr.write(
1857
- '\xB7 --allow-tool / --allow-operation \u5728 --input-format stream-json \u4E0B\u4E0D\u53C2\u4E0E\u5224\u5B9A\uFF1A\u5BBF\u4E3B\u80FD\u7B54\u5BA1\u6279\uFF0C\u6240\u4EE5\u6BCF\u4E00\u6B21\u90FD\u4F1A\u95EE\u5B83\u3002\u8981\u7528\u9884\u6388\u6743\u5C31\u8D70 epoch "<\u4EFB\u52A1>" \u5355\u6B21\u95EE\u7B54\u90A3\u6761\u8DEF\n'
1858
- );
1719
+ process.stderr.write(`${t11("headless_session.inert_allow")}
1720
+ `);
1859
1721
  }
1860
1722
  if ((opts.image ?? []).length > 0) {
1861
- process.stderr.write(
1862
- "\xB7 -i/--image \u5728 --input-format stream-json \u4E0B\u88AB\u5FFD\u7565\uFF1A\u56FE\u7247\u8D70 user-message \u7684 content \u90E8\u4EF6\u6570\u7EC4\n"
1863
- );
1723
+ process.stderr.write(`${t11("headless_session.inert_image")}
1724
+ `);
1864
1725
  }
1865
1726
  }
1866
-
1867
1727
  // src/update-check.ts
1868
1728
  import { spawn } from "child_process";
1869
1729
  import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "fs";
1870
1730
  import { get } from "https";
1871
1731
  import { dirname as dirname2, join as join3 } from "path";
1872
1732
  import { fileURLToPath as fileURLToPath2 } from "url";
1873
-
1733
+ import { t as t14 } from "@epoch-agent/infra";
1874
1734
  // src/version.ts
1875
1735
  import { readFileSync as readFileSync2 } from "fs";
1876
1736
  import { dirname, join as join2 } from "path";
1877
1737
  import { fileURLToPath } from "url";
1878
-
1738
+ import { t as t13 } from "@epoch-agent/infra";
1879
1739
  // src/installation.ts
1880
1740
  import { execFileSync } from "child_process";
1881
1741
  import { existsSync as existsSync4, realpathSync } from "fs";
1882
1742
  import { join } from "path";
1883
- import { normalizeForMatch } from "@epoch-agent/infra";
1743
+ import { normalizeForMatch, t as t12 } from "@epoch-agent/infra";
1884
1744
  var PATH_RULES = [
1885
1745
  {
1886
1746
  patterns: [
1887
1747
  "/.npm/_npx",
1888
1748
  "/npm/_npx",
1889
- // Windows:npm 的 cache 默认在 `%LocalAppData%\npm-cache`(老文档写
1890
- // `%AppData%\npm-cache`,两者都命中这一条),`_npx` 是它的子目录。
1891
- // 缺了它,Windows 上 npx 临时运行会被兜底成「npm 全局安装」。
1892
1749
  "/npm-cache/_npx",
1893
1750
  "/.cache/pnpm/dlx",
1894
1751
  "/.pnpm/_pnpx",
@@ -1897,17 +1754,16 @@ var PATH_RULES = [
1897
1754
  build: () => ({
1898
1755
  packageManager: "npx",
1899
1756
  isGlobal: false,
1900
- note: "\u901A\u8FC7 npx / pnpm dlx \u4E34\u65F6\u8FD0\u884C\uFF0C\u6BCF\u6B21\u90FD\u662F\u6700\u65B0\u7248\uFF0C\u65E0\u9700\u5347\u7EA7\u3002"
1757
+ note: t12("installation.npx")
1901
1758
  })
1902
1759
  },
1903
1760
  {
1904
- // Windows 上是 `%LocalAppData%\Volta`,小写化之后和 POSIX 的 `.volta` 同形
1905
1761
  patterns: ["/.volta/", "/volta/"],
1906
1762
  build: (pkg) => ({
1907
1763
  packageManager: "volta",
1908
1764
  isGlobal: true,
1909
1765
  updateCommand: `volta install ${pkg}@latest`,
1910
- note: "\u901A\u8FC7 Volta \u5B89\u88C5\u3002"
1766
+ note: t12("installation.volta")
1911
1767
  })
1912
1768
  },
1913
1769
  {
@@ -1921,16 +1777,13 @@ var PATH_RULES = [
1921
1777
  packageManager: "pnpm",
1922
1778
  isGlobal: true,
1923
1779
  updateCommand: `pnpm add -g ${pkg}@latest`,
1924
- note: "\u901A\u8FC7 pnpm \u5168\u5C40\u5B89\u88C5\u3002"
1780
+ note: t12("installation.pnpm")
1925
1781
  })
1926
1782
  },
1927
1783
  {
1928
1784
  patterns: [
1929
1785
  "/.yarn/global",
1930
1786
  "/yarn/global",
1931
- // Windows:yarn 1.5.1 起全局目录多了一层,变成 `Yarn\Data\global`;
1932
- // 更早的版本是 `Yarn\config\global`。两条都收,否则 yarn 全局装的用户
1933
- // 会被告知去跑 `npm install -g`。
1934
1787
  "/yarn/data/global",
1935
1788
  "/yarn/config/global"
1936
1789
  ],
@@ -1938,7 +1791,7 @@ var PATH_RULES = [
1938
1791
  packageManager: "yarn",
1939
1792
  isGlobal: true,
1940
1793
  updateCommand: `yarn global add ${pkg}@latest`,
1941
- note: "\u901A\u8FC7 yarn \u5168\u5C40\u5B89\u88C5\u3002"
1794
+ note: t12("installation.yarn")
1942
1795
  })
1943
1796
  },
1944
1797
  {
@@ -1947,7 +1800,7 @@ var PATH_RULES = [
1947
1800
  packageManager: "bun",
1948
1801
  isGlobal: true,
1949
1802
  updateCommand: `bun add -g ${pkg}@latest`,
1950
- note: "\u901A\u8FC7 bun \u5168\u5C40\u5B89\u88C5\u3002"
1803
+ note: t12("installation.bun")
1951
1804
  })
1952
1805
  }
1953
1806
  ];
@@ -1967,7 +1820,7 @@ function detectHomebrew(realPath, formula) {
1967
1820
  packageManager: "homebrew",
1968
1821
  isGlobal: true,
1969
1822
  updateCommand: `brew upgrade ${formula}`,
1970
- note: "\u901A\u8FC7 Homebrew \u5B89\u88C5\u3002"
1823
+ note: t12("installation.homebrew")
1971
1824
  };
1972
1825
  }
1973
1826
  } catch {
@@ -1992,11 +1845,13 @@ function detectLocalInstall(matchPath, cwd) {
1992
1845
  return {
1993
1846
  packageManager: manager,
1994
1847
  isGlobal: false,
1995
- note: "\u4F5C\u4E3A\u9879\u76EE\u4F9D\u8D56\u5B89\u88C5\uFF0C\u8BF7\u5728\u9879\u76EE\u7684 package.json \u91CC\u5347\u7EA7\u7248\u672C\u3002"
1848
+ note: t12("installation.local")
1996
1849
  };
1997
1850
  }
1998
1851
  function getInstallationInfo(packageName, cliPath = process.argv[1]) {
1999
- if (!cliPath) return { packageManager: "unknown", isGlobal: false, note: "\u8BC6\u522B\u4E0D\u51FA\u5B89\u88C5\u65B9\u5F0F\u3002" };
1852
+ if (!cliPath) {
1853
+ return { packageManager: "unknown", isGlobal: false, note: t12("installation.unknown") };
1854
+ }
2000
1855
  try {
2001
1856
  const realPath = realpathSync(cliPath).replace(/\\/g, "/");
2002
1857
  const matchPath = normalizeForMatch(realPath);
@@ -2008,7 +1863,7 @@ function getInstallationInfo(packageName, cliPath = process.argv[1]) {
2008
1863
  return {
2009
1864
  packageManager: "source",
2010
1865
  isGlobal: false,
2011
- note: "\u76F4\u63A5\u4ECE\u4ED3\u5E93\u6E90\u7801\u8FD0\u884C\uFF0C\u7528 git pull \u66F4\u65B0\u3002"
1866
+ note: t12("installation.source")
2012
1867
  };
2013
1868
  }
2014
1869
  const brew = detectHomebrew(realPath, packageName.replace(/^@[^/]+\//, ""));
@@ -2019,13 +1874,12 @@ function getInstallationInfo(packageName, cliPath = process.argv[1]) {
2019
1874
  packageManager: "npm",
2020
1875
  isGlobal: true,
2021
1876
  updateCommand: `npm install -g ${packageName}@latest`,
2022
- note: "\u901A\u8FC7 npm \u5168\u5C40\u5B89\u88C5\u3002"
1877
+ note: t12("installation.npm")
2023
1878
  };
2024
1879
  } catch {
2025
- return { packageManager: "unknown", isGlobal: false, note: "\u8BC6\u522B\u4E0D\u51FA\u5B89\u88C5\u65B9\u5F0F\u3002" };
1880
+ return { packageManager: "unknown", isGlobal: false, note: t12("installation.unknown") };
2026
1881
  }
2027
1882
  }
2028
-
2029
1883
  // src/version.ts
2030
1884
  function readVersion() {
2031
1885
  const here = dirname(fileURLToPath(import.meta.url));
@@ -2047,10 +1901,13 @@ function describeVersion(packageName) {
2047
1901
  return [
2048
1902
  VERSION,
2049
1903
  `Node: ${process.version} (${process.platform}/${process.arch})`,
2050
- `\u5B89\u88C5: ${info.packageManager}${info.isGlobal ? "\uFF08\u5168\u5C40\uFF09" : ""} \u2014\u2014 ${info.note}`
1904
+ t13("version.install", {
1905
+ manager: info.packageManager,
1906
+ global: info.isGlobal ? t13("installation.global_suffix") : "",
1907
+ note: info.note
1908
+ })
2051
1909
  ].join("\n");
2052
1910
  }
2053
-
2054
1911
  // src/update-check.ts
2055
1912
  var UPDATE_CHECK_SUBCOMMAND = "__update-check";
2056
1913
  var UPDATE_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
@@ -2190,7 +2047,7 @@ function readUpdateNotice(options = {}) {
2190
2047
  name,
2191
2048
  current: version,
2192
2049
  latest: cache.latest,
2193
- message: `${name} \u6709\u65B0\u7248\u672C\uFF1A${version} \u2192 ${cache.latest}\uFF08\u8FD0\u884C epoch upgrade \u67E5\u770B\u5347\u7EA7\u65B9\u5F0F\uFF09`
2050
+ message: t14("update_check.available", { name, current: version, latest: cache.latest })
2194
2051
  };
2195
2052
  }
2196
2053
  async function refreshUpdateCache(options = {}) {
@@ -2219,7 +2076,7 @@ async function refreshUpdateCache(options = {}) {
2219
2076
  name,
2220
2077
  current: version,
2221
2078
  latest,
2222
- message: `${name} \u6709\u65B0\u7248\u672C\uFF1A${version} \u2192 ${latest}\uFF08\u8FD0\u884C epoch upgrade \u67E5\u770B\u5347\u7EA7\u65B9\u5F0F\uFF09`
2079
+ message: t14("update_check.available", { name, current: version, latest })
2223
2080
  };
2224
2081
  }
2225
2082
  function scheduleUpdateCheck(options = {}) {
@@ -2252,13 +2109,12 @@ ${notice.message}
2252
2109
  }
2253
2110
  });
2254
2111
  }
2255
-
2256
2112
  // src/worktree.ts
2257
2113
  import { execFile } from "child_process";
2258
2114
  import { randomBytes } from "crypto";
2259
2115
  import { mkdirSync as mkdirSync3 } from "fs";
2260
2116
  import { basename, join as join4, resolve as resolve2 } from "path";
2261
- import { worktreesDir } from "@epoch-agent/infra";
2117
+ import { t as t15, worktreesDir } from "@epoch-agent/infra";
2262
2118
  var TIMEOUT_MS = 6e4;
2263
2119
  function git(args, cwd) {
2264
2120
  return new Promise((done) => {
@@ -2279,17 +2135,13 @@ function git(args, cwd) {
2279
2135
  });
2280
2136
  }
2281
2137
  function firstLine(text) {
2282
- return text.trim().split("\n")[0]?.trim() ?? "\u672A\u77E5\u539F\u56E0";
2138
+ return text.trim().split("\n")[0]?.trim() ?? t15("git.unknown_reason");
2283
2139
  }
2284
2140
  async function createWorktree(cwd = process.cwd(), container = worktreesDir()) {
2285
2141
  const repoRoot = await resolveRepoRoot(cwd);
2286
2142
  const head = await git(["rev-parse", "HEAD"], repoRoot);
2287
2143
  if (!head.ok) {
2288
- throw new CliError(
2289
- "\u8FD9\u4E2A\u4ED3\u5E93\u8FD8\u6CA1\u6709\u4EFB\u4F55\u63D0\u4EA4\uFF0C\u5EFA\u4E0D\u51FA worktree",
2290
- 1,
2291
- "worktree \u5FC5\u987B\u4ECE\u67D0\u4E2A commit \u957F\u51FA\u6765\u3002\u5148 git commit \u4E00\u6B21\uFF0C\u6216\u8005\u53BB\u6389 --worktree"
2292
- );
2144
+ throw new CliError(t15("worktree.err_no_commit"), 1, t15("worktree.err_no_commit_hint"));
2293
2145
  }
2294
2146
  const baseRef = head.stdout.trim();
2295
2147
  const shortId = randomBytes(3).toString("hex");
@@ -2298,24 +2150,20 @@ async function createWorktree(cwd = process.cwd(), container = worktreesDir()) {
2298
2150
  mkdirSync3(container, { recursive: true });
2299
2151
  const added = await git(["worktree", "add", "-b", branch, path, baseRef], repoRoot);
2300
2152
  if (!added.ok) {
2301
- throw new CliError(`\u5EFA worktree \u5931\u8D25\uFF1A${path}`, 1, firstLine(added.stderr));
2153
+ throw new CliError(t15("worktree.err_add_failed", { path }), 1, firstLine(added.stderr));
2302
2154
  }
2303
2155
  return { repoRoot, path, branch, baseRef };
2304
2156
  }
2305
2157
  async function resolveRepoRoot(cwd) {
2306
2158
  const root = await git(["rev-parse", "--show-toplevel"], cwd);
2307
2159
  if (root.missing) {
2308
- throw new CliError(
2309
- "--worktree \u8981\u7528 git\uFF0C\u4F46\u5B83\u4E0D\u5728 PATH \u91CC",
2310
- 1,
2311
- "\u88C5\u4E00\u4E2A git\uFF0C\u6216\u8005\u53BB\u6389 --worktree"
2312
- );
2160
+ throw new CliError(t15("worktree.err_git_missing"), 1, t15("worktree.err_git_missing_hint"));
2313
2161
  }
2314
2162
  if (!root.ok) {
2315
2163
  throw new CliError(
2316
- "--worktree \u53EA\u80FD\u5728 git \u4ED3\u5E93\u91CC\u7528",
2164
+ t15("worktree.err_not_a_repo"),
2317
2165
  1,
2318
- `\u5F53\u524D\u76EE\u5F55 ${cwd} \u4E0D\u5728\u4EFB\u4F55 git \u4ED3\u5E93\u91CC\uFF08${firstLine(root.stderr)}\uFF09`
2166
+ t15("worktree.err_not_a_repo_hint", { cwd, why: firstLine(root.stderr) })
2319
2167
  );
2320
2168
  }
2321
2169
  return resolve2(root.stdout.trim());
@@ -2333,16 +2181,18 @@ async function inspectWorktree(session) {
2333
2181
  };
2334
2182
  }
2335
2183
  function worktreeKeepReason(state) {
2336
- if (state.dirty === null || state.ahead === null) return "\u67E5\u4E0D\u51FA\u5B83\u5E72\u51C0\u4E0D\u5E72\u51C0";
2337
- if (state.dirty > 0) return `\u8FD8\u6709 ${state.dirty} \u5904\u672A\u63D0\u4EA4\u6539\u52A8`;
2338
- if (state.ahead > 0) return `\u6709 ${state.ahead} \u4E2A\u63D0\u4EA4\u8FD8\u6CA1\u5408\u56DE\u53BB`;
2184
+ if (state.dirty === null || state.ahead === null) return t15("worktree.keep_unknown");
2185
+ if (state.dirty > 0) return t15("worktree.keep_dirty", { count: state.dirty });
2186
+ if (state.ahead > 0) return t15("worktree.keep_ahead", { count: state.ahead });
2339
2187
  return null;
2340
2188
  }
2341
2189
  function worktreeBanner(session) {
2190
+ const dir = t15("worktree.banner_dir", { path: session.path });
2191
+ const branch = t15("worktree.banner_branch", { branch: session.branch });
2342
2192
  return `
2343
- \u{1F33F} \u5728\u9694\u79BB\u7684 worktree \u91CC\u8DD1\u8FD9\u4E00\u6B21
2344
- \u76EE\u5F55 ${session.path}
2345
- \u5206\u652F ${session.branch}
2193
+ ${t15("worktree.banner")}
2194
+ ${dir}
2195
+ ${branch}
2346
2196
  `;
2347
2197
  }
2348
2198
  async function finishWorktree(session, opts) {
@@ -2350,23 +2200,25 @@ async function finishWorktree(session, opts) {
2350
2200
  const state = await inspectWorktree(session);
2351
2201
  const forced = worktreeKeepReason(state);
2352
2202
  if (forced !== null) {
2353
- print(`
2354
- \u{1F33F} worktree \u4FDD\u7559\uFF08${forced}\uFF09\uFF1A
2203
+ print(
2204
+ `
2205
+ ${t15("worktree.kept_forced", { reason: forced })}
2355
2206
  ${session.path}
2356
- \u5206\u652F ${session.branch}`);
2207
+ ${t15("worktree.banner_branch", { branch: session.branch })}`
2208
+ );
2357
2209
  return;
2358
2210
  }
2359
2211
  const remove = opts.interactive ? await (opts.ask ?? askRemove)(session) : false;
2360
2212
  if (!remove) {
2361
2213
  print(`
2362
- \u{1F33F} worktree \u4FDD\u7559\u5728 ${session.path}\uFF08\u5206\u652F ${session.branch}\uFF09`);
2214
+ ${t15("worktree.kept", { path: session.path, branch: session.branch })}`);
2363
2215
  return;
2364
2216
  }
2365
2217
  const failure = await removeWorktree(session);
2366
2218
  print(
2367
2219
  failure === null ? `
2368
- \u{1F33F} \u5DF2\u5220\u9664 worktree ${session.path}\uFF08\u5206\u652F ${session.branch}\uFF09` : `
2369
- \u26A0 worktree \u6CA1\u5220\u5E72\u51C0\uFF0C\u7559\u7740\u4E86\uFF1A${session.path}
2220
+ ${t15("worktree.removed", { path: session.path, branch: session.branch })}` : `
2221
+ ${t15("worktree.remove_failed", { path: session.path })}
2370
2222
  ${failure}`
2371
2223
  );
2372
2224
  }
@@ -2374,56 +2226,65 @@ async function removeWorktree(session) {
2374
2226
  const removed = await git(["worktree", "remove", session.path], session.repoRoot);
2375
2227
  if (!removed.ok) return firstLine(removed.stderr);
2376
2228
  const branch = await git(["branch", "-D", session.branch], session.repoRoot);
2377
- return branch.ok ? null : `\u76EE\u5F55\u5220\u4E86\uFF0C\u5206\u652F ${session.branch} \u6CA1\u5220\u6389\uFF1A${firstLine(branch.stderr)}`;
2229
+ return branch.ok ? null : t15("worktree.branch_remove_failed", {
2230
+ branch: session.branch,
2231
+ why: firstLine(branch.stderr)
2232
+ });
2378
2233
  }
2379
2234
  async function askRemove(session) {
2380
2235
  const { confirm } = await import("@inquirer/prompts");
2381
2236
  try {
2382
2237
  return await confirm({
2383
- message: `worktree ${session.path} \u6CA1\u6709\u4EFB\u4F55\u6539\u52A8\uFF0C\u5220\u6389\u5B83\u5417\uFF1F`,
2238
+ message: t15("worktree.ask_remove", { path: session.path }),
2384
2239
  default: false
2385
2240
  });
2386
2241
  } catch {
2387
2242
  return false;
2388
2243
  }
2389
2244
  }
2390
-
2391
2245
  // src/commands/run-once.ts
2392
2246
  import { formatUsd, imageFromPath, OPERATION_TYPES as OPERATION_TYPES2 } from "@epoch-agent/core";
2247
+ import { t as t21 } from "@epoch-agent/infra";
2393
2248
  import {
2394
2249
  promptTokens
2395
2250
  } from "@epoch-agent/protocol";
2396
2251
  import { buildRuntime as buildRuntime2, listBackgroundTasks } from "@epoch-agent/runtime";
2397
-
2398
2252
  // src/approval.ts
2399
2253
  import {
2400
- isNonInteractive as isNonInteractive3,
2401
- PLAN_OUTCOME_LABELS
2254
+ isNonInteractive as isNonInteractive3
2402
2255
  } from "@epoch-agent/core";
2403
- var TYPE_LABEL = {
2404
- file_read: "\u8BFB\u6587\u4EF6",
2405
- file_write: "\u5199\u6587\u4EF6",
2406
- command: "\u6267\u884C\u547D\u4EE4",
2407
- network: "\u7F51\u7EDC\u8BF7\u6C42",
2408
- code_exec: "\u5728\u6C99\u7BB1\u5185\u6267\u884C\u4EE3\u7801"
2409
- };
2256
+ import { t as t16 } from "@epoch-agent/infra";
2257
+ function typeLabel(type) {
2258
+ switch (type) {
2259
+ case "file_read":
2260
+ return t16("tui.approval.type_file_read");
2261
+ case "file_write":
2262
+ return t16("tui.approval.type_file_write");
2263
+ case "command":
2264
+ return t16("tui.approval.type_command");
2265
+ case "network":
2266
+ return t16("tui.approval.type_network");
2267
+ case "code_exec":
2268
+ return t16("tui.approval.type_code_exec");
2269
+ }
2270
+ }
2410
2271
  function formatApprovalRequest(req) {
2411
2272
  if (req.plan) {
2412
2273
  return [
2413
- "\u25A3 \u6A21\u578B\u4EA4\u4E86\u4E00\u4EFD\u8BA1\u5212\uFF0C\u7B49\u4F60\u6279",
2414
- ` \u8FDB\u5165 plan \u6A21\u5F0F\u524D\u7684\u6743\u9650\u7EA7\u522B: ${req.plan.previousLevel}`,
2274
+ t16("approval.plan_head"),
2275
+ t16("approval.plan_previous_level", { level: req.plan.previousLevel }),
2415
2276
  "",
2416
2277
  ...req.plan.markdown.split("\n").map((l) => ` ${l}`)
2417
2278
  ];
2418
2279
  }
2419
2280
  const lines = [
2420
- `\u26A0\uFE0F ${req.toolName} \u8BF7\u6C42${TYPE_LABEL[req.type]}`,
2421
- ` \u76EE\u6807: ${req.target || "(\u65E0)"}`
2281
+ t16("approval.asks", { tool: req.toolName, what: typeLabel(req.type) }),
2282
+ t16("approval.target", { target: req.target || t16("approval.target_none") })
2422
2283
  ];
2423
2284
  if (req.detail && req.detail !== req.target) {
2424
- lines.push(` \u8BE6\u60C5: ${req.detail.slice(0, 300)}`);
2285
+ lines.push(t16("approval.detail", { detail: req.detail.slice(0, 300) }));
2425
2286
  }
2426
- if (req.reason) lines.push(` \u539F\u56E0: ${req.reason}`);
2287
+ if (req.reason) lines.push(t16("approval.reason", { reason: req.reason }));
2427
2288
  return lines;
2428
2289
  }
2429
2290
  function createInteractiveApproval(opts = {}) {
@@ -2431,12 +2292,15 @@ function createInteractiveApproval(opts = {}) {
2431
2292
  return async (req) => {
2432
2293
  if (!interactive()) {
2433
2294
  if (opts.permission?.()?.isPreauthorized(req.toolName, req.type)) {
2434
- console.error(`[\u5DF2\u653E\u884C] headless \u9884\u6388\u6743: ${req.toolName} ${req.target}`);
2295
+ console.error(t16("approval.headless_allowed", { tool: req.toolName, target: req.target }));
2435
2296
  return "allow-once";
2436
2297
  }
2437
2298
  console.error(
2438
- `[\u5DF2\u62D2\u7EDD] \u975E\u4EA4\u4E92\u73AF\u5883\u65E0\u6CD5\u786E\u8BA4: ${req.toolName} ${req.target}
2439
- \u5982\u9700\u653E\u884C\uFF0C\u52A0 --allow-tool ${req.toolName}\u3001\u5728 config.yaml \u91CC\u914D headless.allowTools\uFF0C\u6216\u5728 ~/.epoch/policies/ \u52A0\u7B56\u7565\u89C4\u5219\u3002`
2299
+ `${t16("approval.headless_denied", {
2300
+ tool: req.toolName,
2301
+ target: req.target
2302
+ })}
2303
+ ${t16("approval.headless_denied_hint", { tool: req.toolName })}`
2440
2304
  );
2441
2305
  return "deny";
2442
2306
  }
@@ -2445,12 +2309,12 @@ function createInteractiveApproval(opts = {}) {
2445
2309
  const { select: select2 } = await import("@inquirer/prompts");
2446
2310
  try {
2447
2311
  return await select2({
2448
- message: "\u662F\u5426\u5141\u8BB8\uFF1F",
2312
+ message: t16("approval.ask"),
2449
2313
  choices: [
2450
- { name: "\u5141\u8BB8\u4E00\u6B21", value: "allow-once" },
2451
- { name: "\u672C\u4F1A\u8BDD\u5185\u59CB\u7EC8\u5141\u8BB8", value: "allow-session" },
2452
- { name: "\u6C38\u4E45\u5141\u8BB8\uFF08\u5199\u5165 approvals.json\uFF09", value: "allow-always" },
2453
- { name: "\u62D2\u7EDD", value: "deny" }
2314
+ { name: t16("tui.approval.allow_once"), value: "allow-once" },
2315
+ { name: t16("tui.approval.allow_session"), value: "allow-session" },
2316
+ { name: t16("approval.allow_always_cli"), value: "allow-always" },
2317
+ { name: t16("tui.approval.deny"), value: "deny" }
2454
2318
  ],
2455
2319
  default: "allow-once"
2456
2320
  });
@@ -2461,27 +2325,29 @@ function createInteractiveApproval(opts = {}) {
2461
2325
  }
2462
2326
  async function askPlan(proposal) {
2463
2327
  const { input: input2, select: select2 } = await import("@inquirer/prompts");
2464
- const choices = [
2328
+ const choices3 = [
2465
2329
  ...proposal.canExecute ? [
2466
2330
  {
2467
- name: `${PLAN_OUTCOME_LABELS["plan-execute"]}\uFF08\u6743\u9650\u56DE\u5230 ${proposal.previousLevel}\uFF09`,
2331
+ name: t16("approval.plan_execute_back", {
2332
+ label: t16("approval.plan_execute"),
2333
+ level: proposal.previousLevel
2334
+ }),
2468
2335
  value: "plan-execute"
2469
2336
  }
2470
2337
  ] : [],
2471
- { name: PLAN_OUTCOME_LABELS["plan-readonly"], value: "plan-readonly" },
2472
- { name: PLAN_OUTCOME_LABELS["plan-revise"], value: "plan-revise" },
2473
- { name: PLAN_OUTCOME_LABELS.deny, value: "deny" }
2338
+ { name: t16("approval.plan_readonly"), value: "plan-readonly" },
2339
+ { name: t16("approval.plan_revise"), value: "plan-revise" },
2340
+ { name: t16("approval.deny"), value: "deny" }
2474
2341
  ];
2475
2342
  try {
2476
- const outcome = await select2({ message: "\u8FD9\u4EFD\u8BA1\u5212\uFF1F", choices });
2343
+ const outcome = await select2({ message: t16("approval.plan_ask"), choices: choices3 });
2477
2344
  if (outcome !== "plan-revise") return outcome;
2478
- const note = (await input2({ message: "\u8981\u6539\u4EC0\u4E48\uFF1F" })).trim();
2345
+ const note = (await input2({ message: t16("approval.plan_revise_ask") })).trim();
2479
2346
  return note ? { outcome, note } : outcome;
2480
2347
  } catch {
2481
2348
  return "deny";
2482
2349
  }
2483
2350
  }
2484
-
2485
2351
  // src/headless/approver.ts
2486
2352
  import { spawn as spawn2 } from "child_process";
2487
2353
  import {
@@ -2489,6 +2355,7 @@ import {
2489
2355
  matchPreauthorization,
2490
2356
  mergeHeadlessPolicy
2491
2357
  } from "@epoch-agent/core";
2358
+ import { t as t17 } from "@epoch-agent/infra";
2492
2359
  import { APPROVAL_OUTCOMES as APPROVAL_OUTCOMES2, isApprovalOutcome as isApprovalOutcome2 } from "@epoch-agent/protocol";
2493
2360
  var APPROVER_TIMEOUT_MS = 6e4;
2494
2361
  var MAX_STDOUT_BYTES = 64 * 1024;
@@ -2498,22 +2365,25 @@ function clip(s) {
2498
2365
  }
2499
2366
  function parseApproverVerdict(stdout) {
2500
2367
  const text = stdout.trim();
2501
- if (!text) return { ok: false, why: "\u5BA1\u6279\u7A0B\u5E8F\u6CA1\u6709\u8F93\u51FA" };
2368
+ if (!text) return { ok: false, why: t17("approver.no_output") };
2502
2369
  let raw;
2503
2370
  try {
2504
2371
  raw = JSON.parse(text);
2505
2372
  } catch {
2506
- return { ok: false, why: `\u5BA1\u6279\u7A0B\u5E8F\u7684\u8F93\u51FA\u4E0D\u662F JSON: ${clip(text)}` };
2373
+ return { ok: false, why: t17("approver.not_json", { text: clip(text) }) };
2507
2374
  }
2508
2375
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
2509
- return { ok: false, why: `\u5BA1\u6279\u7A0B\u5E8F\u7684\u8F93\u51FA\u4E0D\u662F\u4E00\u4E2A JSON \u5BF9\u8C61: ${clip(text)}` };
2376
+ return { ok: false, why: t17("approver.not_object", { text: clip(text) }) };
2510
2377
  }
2511
2378
  const rec = raw;
2512
2379
  const outcome = rec["outcome"];
2513
2380
  if (typeof outcome !== "string" || !isApprovalOutcome2(outcome)) {
2514
2381
  return {
2515
2382
  ok: false,
2516
- why: `\u5BA1\u6279\u7A0B\u5E8F\u7ED9\u7684 outcome \u4E0D\u8BA4\u8BC6: ${clip(String(outcome))}\uFF08\u53EF\u9009: ${APPROVAL_OUTCOMES2.join(" / ")}\uFF09`
2383
+ why: t17("approver.bad_outcome", {
2384
+ value: clip(String(outcome)),
2385
+ choices: APPROVAL_OUTCOMES2.join(" / ")
2386
+ })
2517
2387
  };
2518
2388
  }
2519
2389
  const note = rec["note"];
@@ -2542,12 +2412,11 @@ function askExternalApprover(program2, payload, opts = {}) {
2542
2412
  const child = spawn2(program2, {
2543
2413
  shell: true,
2544
2414
  ...opts.cwd ? { cwd: opts.cwd } : {},
2545
- // stdin/stdout 是这条问答的通道,stderr 让给它自己打日志
2546
2415
  stdio: ["pipe", "pipe", "inherit"]
2547
2416
  });
2548
2417
  const timer = setTimeout(() => {
2549
2418
  child.kill("SIGKILL");
2550
- settle({ ok: false, why: `\u5BA1\u6279\u7A0B\u5E8F ${timeoutMs}ms \u5185\u6CA1\u6709\u7B54\u590D` });
2419
+ settle({ ok: false, why: t17("approver.timeout", { ms: timeoutMs }) });
2551
2420
  }, timeoutMs);
2552
2421
  const done = (v) => {
2553
2422
  clearTimeout(timer);
@@ -2557,15 +2426,15 @@ function askExternalApprover(program2, payload, opts = {}) {
2557
2426
  out2 += d.toString("utf-8");
2558
2427
  if (out2.length > MAX_STDOUT_BYTES) {
2559
2428
  child.kill("SIGKILL");
2560
- done({ ok: false, why: `\u5BA1\u6279\u7A0B\u5E8F\u8F93\u51FA\u8D85\u8FC7 ${MAX_STDOUT_BYTES} \u5B57\u8282\u8FD8\u6CA1\u6536\u5C3E` });
2429
+ done({ ok: false, why: t17("approver.too_much_output", { bytes: MAX_STDOUT_BYTES }) });
2561
2430
  }
2562
2431
  });
2563
2432
  child.on("error", (err2) => {
2564
- done({ ok: false, why: `\u5BA1\u6279\u7A0B\u5E8F\u8D77\u4E0D\u6765: ${err2.message}` });
2433
+ done({ ok: false, why: t17("approver.spawn_failed", { message: err2.message }) });
2565
2434
  });
2566
2435
  child.on("close", (code) => {
2567
2436
  if (code !== 0) {
2568
- done({ ok: false, why: `\u5BA1\u6279\u7A0B\u5E8F\u9000\u51FA\u7801 ${code ?? -1}` });
2437
+ done({ ok: false, why: t17("approver.bad_exit", { code: code ?? -1 }) });
2569
2438
  return;
2570
2439
  }
2571
2440
  done(parseApproverVerdict(out2));
@@ -2598,33 +2467,47 @@ function createExternalApproval(opts) {
2598
2467
  return async (req) => {
2599
2468
  const hit = req.plan ? null : opts.preauth?.(req) ?? null;
2600
2469
  if (hit) {
2601
- write(`[\u9884\u6388\u6743\u653E\u884C] ${req.toolName} ${req.target} \u2014\u2014 ${hit}\uFF08\u6CA1\u8D77\u5BA1\u6279\u7A0B\u5E8F\uFF09
2602
- `);
2470
+ write(
2471
+ `${t17("approver.audit_preauth", {
2472
+ tool: req.toolName,
2473
+ target: req.target,
2474
+ reason: hit
2475
+ })}
2476
+ `
2477
+ );
2603
2478
  return "allow-once";
2604
2479
  }
2605
2480
  const verdict = await ask(payloadOf(req, cwd, opts.sessionId?.()));
2606
2481
  if (!verdict.ok) {
2607
- write(`[\u5916\u90E8\u5BA1\u6279\u62D2\u7EDD] ${req.toolName} ${req.target} \u2014\u2014 ${verdict.why}\uFF0C\u6309\u62D2\u7EDD\u5904\u7406
2608
- `);
2482
+ write(
2483
+ `${t17("approver.audit_rejected", {
2484
+ tool: req.toolName,
2485
+ target: req.target,
2486
+ why: verdict.why
2487
+ })}
2488
+ `
2489
+ );
2609
2490
  return "deny";
2610
2491
  }
2611
2492
  const { outcome } = verdict.answer;
2612
- const verb = outcome === "deny" ? "\u62D2\u7EDD" : "\u653E\u884C";
2613
- write(`[\u5916\u90E8\u5BA1\u6279${verb}] ${req.toolName} ${req.target} \u2014\u2014 ${outcome}
2614
- `);
2493
+ const vars = { tool: req.toolName, target: req.target, outcome };
2494
+ write(
2495
+ `${outcome === "deny" ? t17("approver.audit_denied", vars) : t17("approver.audit_allowed", vars)}
2496
+ `
2497
+ );
2615
2498
  return verdict.answer;
2616
2499
  };
2617
2500
  }
2618
-
2619
2501
  // src/headless/tasks.ts
2620
2502
  import { describeBackgroundTask } from "@epoch-agent/core";
2621
- function backgroundTaskSummary(tasks) {
2503
+ import { t as t18 } from "@epoch-agent/infra";
2504
+ function backgroundTaskSummary(all) {
2505
+ const tasks = all.filter((t39) => t39.kind === "command");
2622
2506
  if (tasks.length === 0) return "";
2623
- const running = tasks.filter((t14) => t14.status === "running").length;
2624
- const head = running > 0 ? `\u2699 \u540E\u53F0\u4EFB\u52A1 ${tasks.length} \u4E2A\uFF0C\u5176\u4E2D ${running} \u4E2A\u8FD8\u5728\u8DD1 \u2014\u2014 epoch \u9000\u51FA\u65F6\u4F1A\u4E00\u5E76\u7EC8\u6B62` : `\u2699 \u540E\u53F0\u4EFB\u52A1 ${tasks.length} \u4E2A\uFF0C\u90FD\u5DF2\u7ECF\u7ED3\u675F`;
2625
- return [head, ...tasks.map((t14) => ` ${describeBackgroundTask(t14)}`)].join("\n");
2507
+ const running = tasks.filter((t39) => t39.status === "running").length;
2508
+ const head = running > 0 ? t18("background_tasks.head_running", { total: tasks.length, running }) : t18("background_tasks.head_done", { total: tasks.length });
2509
+ return [head, ...tasks.map((t39) => ` ${describeBackgroundTask(t39)}`)].join("\n");
2626
2510
  }
2627
-
2628
2511
  // src/import-prompt.ts
2629
2512
  import {
2630
2513
  createImportGate,
@@ -2636,8 +2519,7 @@ import {
2636
2519
  setImportGate,
2637
2520
  TrustManager as TrustManager2
2638
2521
  } from "@epoch-agent/core";
2639
- import { trustedImportsPath, trustPath as trustPath2 } from "@epoch-agent/infra";
2640
-
2522
+ import { t as t20, trustedImportsPath, trustPath as trustPath2 } from "@epoch-agent/infra";
2641
2523
  // src/trust-prompt.ts
2642
2524
  import {
2643
2525
  findProjectInstructions,
@@ -2646,7 +2528,7 @@ import {
2646
2528
  resolveProjectRoot,
2647
2529
  TrustManager
2648
2530
  } from "@epoch-agent/core";
2649
- import { trustPath } from "@epoch-agent/infra";
2531
+ import { t as t19, trustPath } from "@epoch-agent/infra";
2650
2532
  function trustPromptTarget(deps) {
2651
2533
  if (!deps.gateEnabled) return null;
2652
2534
  if (!deps.interactive) return null;
@@ -2659,30 +2541,31 @@ function trustPromptTarget(deps) {
2659
2541
  function applyTrustChoice(store, root, choice) {
2660
2542
  if (choice.kind === "trust") {
2661
2543
  store.record(root, "trusted", choice.scope);
2662
- const suffix = choice.scope === "directory-tree" ? "\uFF08\u542B\u5B50\u76EE\u5F55\uFF09" : "";
2663
- return `\u2713 \u5DF2\u4FE1\u4EFB ${root}${suffix}\uFF0C\u672C\u76EE\u5F55\u7684\u9879\u76EE\u6307\u4EE4\u4F1A\u52A0\u8F7D`;
2544
+ const suffix = choice.scope === "directory-tree" ? t19("trust_prompt.granted_tree_suffix") : "";
2545
+ return t19("trust_prompt.granted", { root, suffix });
2664
2546
  }
2665
2547
  if (choice.kind === "never") {
2666
2548
  store.record(root, "untrusted", "directory");
2667
- return `\u2717 \u5DF2\u8BB0\u4E3A\u4E0D\u4FE1\u4EFB ${root}\uFF0C\u4E4B\u540E\u4E0D\u518D\u8BE2\u95EE\uFF08epoch trust rm ${root} \u53EF\u64A4\u9500\uFF09`;
2549
+ return t19("trust_prompt.denied", { root });
2668
2550
  }
2669
- return "\u672C\u6B21\u4E0D\u52A0\u8F7D\u9879\u76EE\u6307\u4EE4\u3002\u60F3\u8BA9\u5B83\u751F\u6548\u8FD0\u884C epoch trust add";
2551
+ return t19("trust_prompt.skipped");
2670
2552
  }
2671
2553
  function trustPromptMessage(target) {
2672
2554
  return [
2673
2555
  `
2674
- \u26A0 \u68C0\u6D4B\u5230\u9879\u76EE\u6307\u4EE4\u6587\u4EF6\uFF1A${target.instructionsPath}`,
2675
- " \u5B83\u7531\u4ED3\u5E93\u4F5C\u8005\u7F16\u5199\uFF0C\u4E00\u65E6\u52A0\u8F7D\u5C31\u4F1A\u76F4\u63A5\u8FDB\u5165 system prompt\uFF0C\u7B49\u4E8E\u8BA9\u8FD9\u4E2A\u4ED3\u5E93",
2676
- " \u6307\u6325\u4F60\u7684 agent\u3002\u6240\u4EE5\u672A\u7ECF\u786E\u8BA4\u4E0D\u4F1A\u52A0\u8F7D\u3002",
2556
+ ${t19("trust_prompt.head", { path: target.instructionsPath })}`,
2557
+ ` ${t19("trust_prompt.why")}`,
2677
2558
  ""
2678
2559
  ].join("\n");
2679
2560
  }
2680
- var CHOICES = [
2681
- { name: "\u672C\u6B21\u4E0D\u52A0\u8F7D\uFF08\u4E0B\u6B21\u8FD8\u4F1A\u95EE\uFF09", value: { kind: "skip" } },
2682
- { name: `\u4FE1\u4EFB\u672C\u76EE\u5F55`, value: { kind: "trust", scope: "directory" } },
2683
- { name: "\u4FE1\u4EFB\u672C\u76EE\u5F55\u53CA\u5176\u6240\u6709\u5B50\u76EE\u5F55", value: { kind: "trust", scope: "directory-tree" } },
2684
- { name: "\u6C38\u4E0D\u4FE1\u4EFB\u672C\u76EE\u5F55\uFF08\u4E0D\u518D\u8BE2\u95EE\uFF09", value: { kind: "never" } }
2685
- ];
2561
+ function choices() {
2562
+ return [
2563
+ { name: t19("trust_prompt.choice_skip"), value: { kind: "skip" } },
2564
+ { name: t19("trust_prompt.choice_dir"), value: { kind: "trust", scope: "directory" } },
2565
+ { name: t19("trust_prompt.choice_tree"), value: { kind: "trust", scope: "directory-tree" } },
2566
+ { name: t19("trust_prompt.choice_never"), value: { kind: "never" } }
2567
+ ];
2568
+ }
2686
2569
  function restoreStdinDefault() {
2687
2570
  const stdin = process.stdin;
2688
2571
  if (stdin.isTTY) stdin.setRawMode?.(false);
@@ -2710,23 +2593,27 @@ async function maybePromptForTrust(opts = {}) {
2710
2593
  try {
2711
2594
  print(applyTrustChoice(store, target.root, choice));
2712
2595
  } catch (err2) {
2713
- print(`\u26A0 \u4FE1\u4EFB\u8BB0\u5F55\u5199\u5165\u5931\u8D25\uFF0C\u672C\u6B21\u4ECD\u6309\u4E0D\u4FE1\u4EFB\u5904\u7406\uFF1A${err2 instanceof Error ? err2.message : err2}`);
2596
+ print(
2597
+ t19("trust_prompt.write_failed", {
2598
+ message: err2 instanceof Error ? err2.message : String(err2)
2599
+ })
2600
+ );
2714
2601
  }
2715
2602
  return true;
2716
2603
  }
2717
2604
  async function askTrustChoice(_target) {
2718
2605
  const { select: select2 } = await import("@inquirer/prompts");
2719
2606
  try {
2607
+ const options = choices();
2720
2608
  return await select2({
2721
- message: "\u662F\u5426\u4FE1\u4EFB\u672C\u76EE\u5F55\uFF1F",
2722
- choices: [...CHOICES],
2723
- default: CHOICES[0]?.value
2609
+ message: t19("trust_prompt.ask"),
2610
+ choices: [...options],
2611
+ default: options[0]?.value
2724
2612
  });
2725
2613
  } catch {
2726
2614
  return null;
2727
2615
  }
2728
2616
  }
2729
-
2730
2617
  // src/import-prompt.ts
2731
2618
  function externalImportTargets(deps) {
2732
2619
  if (!deps.gateEnabled) return [];
@@ -2752,35 +2639,36 @@ function externalImportTargets(deps) {
2752
2639
  function importPromptMessage(target) {
2753
2640
  return [
2754
2641
  `
2755
- \u26A0 ${target.from} \u5F15\u7528\u4E86\u9879\u76EE\u4E4B\u5916\u7684\u6587\u4EF6\uFF1A`,
2642
+ ${t20("import_prompt.head", { from: target.from })}`,
2756
2643
  ` ${target.path}`,
2757
- ` \uFF08\u6587\u4EF6\u91CC\u5199\u7684\u662F @${target.spec}\uFF09`,
2758
- " \u5B83\u4E0D\u5728\u8FD9\u4E2A\u4ED3\u5E93\u91CC\uFF0C\u4E5F\u6CA1\u6709\u88AB\u300C\u4FE1\u4EFB\u8FD9\u4E2A\u76EE\u5F55\u300D\u90A3\u6B21\u5224\u5B9A\u8986\u76D6\u8FC7\u3002",
2759
- " \u52A0\u8F7D\u5B83\u7B49\u4E8E\u628A\u4E00\u4EFD\u6CA1\u88AB\u5BA1\u8FC7\u7684\u5185\u5BB9\u62FC\u8FDB system prompt\u3002",
2644
+ ` ${t20("import_prompt.spec", { spec: target.spec })}`,
2645
+ ` ${t20("import_prompt.why")}`,
2760
2646
  ""
2761
2647
  ].join("\n");
2762
2648
  }
2763
2649
  function applyImportChoice(store, target, choice) {
2764
2650
  if (choice.kind === "once") {
2765
2651
  store.allowOnce(target.path);
2766
- return `\u2713 \u672C\u6B21\u52A0\u8F7D ${target.path}\uFF08\u4E0B\u6B21\u8FD8\u4F1A\u95EE\uFF09`;
2652
+ return t20("import_prompt.once", { path: target.path });
2767
2653
  }
2768
2654
  if (choice.kind === "remember") {
2769
2655
  store.remember(target.path);
2770
- return `\u2713 \u5DF2\u8BB0\u4F4F ${target.path}\uFF0C\u4E4B\u540E\u4E0D\u518D\u8BE2\u95EE`;
2656
+ return t20("import_prompt.remembered", { path: target.path });
2771
2657
  }
2772
2658
  if (choice.kind === "never") {
2773
2659
  store.denyAll();
2774
- return "\u2717 \u4E4B\u540E\u4E00\u5F8B\u4E0D\u52A0\u8F7D\u9879\u76EE\u5916\u7684 import\uFF08\u6539\u8FD9\u4E2A\u51B3\u5B9A\uFF1A\u5220\u6389 ~/.epoch/trusted-imports.json\uFF09";
2660
+ return t20("import_prompt.never");
2775
2661
  }
2776
- return `\u672C\u6B21\u4E0D\u52A0\u8F7D ${target.path}`;
2662
+ return t20("import_prompt.skipped", { path: target.path });
2663
+ }
2664
+ function choices2() {
2665
+ return [
2666
+ { name: t20("import_prompt.choice_skip"), value: { kind: "skip" } },
2667
+ { name: t20("import_prompt.choice_once"), value: { kind: "once" } },
2668
+ { name: t20("import_prompt.choice_remember"), value: { kind: "remember" } },
2669
+ { name: t20("import_prompt.choice_never"), value: { kind: "never" } }
2670
+ ];
2777
2671
  }
2778
- var CHOICES2 = [
2779
- { name: "\u672C\u6B21\u4E0D\u52A0\u8F7D\uFF08\u4E0B\u6B21\u8FD8\u4F1A\u95EE\uFF09", value: { kind: "skip" } },
2780
- { name: "\u52A0\u8F7D\u8FD9\u4E00\u4E2A\u6587\u4EF6", value: { kind: "once" } },
2781
- { name: "\u52A0\u8F7D\uFF0C\u5E76\u8BB0\u4F4F\u8FD9\u4E2A\u8DEF\u5F84\uFF08\u4E0B\u6B21\u4E0D\u95EE\uFF09", value: { kind: "remember" } },
2782
- { name: "\u6C38\u4E0D\u52A0\u8F7D\u9879\u76EE\u5916\u7684 import", value: { kind: "never" } }
2783
- ];
2784
2672
  async function maybePromptForExternalImports(opts = {}) {
2785
2673
  const config = opts.config ?? loadConfig2();
2786
2674
  const store = opts.store ?? new ImportTrustStore(trustedImportsPath(config.homeDir));
@@ -2805,7 +2693,9 @@ async function maybePromptForExternalImports(opts = {}) {
2805
2693
  print(applyImportChoice(store, target, choice));
2806
2694
  } catch (err2) {
2807
2695
  print(
2808
- `\u26A0 import \u653E\u884C\u8BB0\u5F55\u5199\u5165\u5931\u8D25\uFF0C\u672C\u6B21\u4ECD\u6309\u4E0D\u52A0\u8F7D\u5904\u7406\uFF1A${err2 instanceof Error ? err2.message : err2}`
2696
+ t20("import_prompt.write_failed", {
2697
+ message: err2 instanceof Error ? err2.message : String(err2)
2698
+ })
2809
2699
  );
2810
2700
  }
2811
2701
  if (choice.kind === "never") break;
@@ -2819,16 +2709,16 @@ async function maybePromptForExternalImports(opts = {}) {
2819
2709
  async function askImportChoice(_target) {
2820
2710
  const { select: select2 } = await import("@inquirer/prompts");
2821
2711
  try {
2712
+ const options = choices2();
2822
2713
  return await select2({
2823
- message: "\u662F\u5426\u52A0\u8F7D\u5B83\uFF1F",
2824
- choices: [...CHOICES2],
2825
- default: CHOICES2[0]?.value
2714
+ message: t20("import_prompt.ask"),
2715
+ choices: [...options],
2716
+ default: options[0]?.value
2826
2717
  });
2827
2718
  } catch {
2828
2719
  return null;
2829
2720
  }
2830
2721
  }
2831
-
2832
2722
  // src/commands/run-stream.ts
2833
2723
  var LIVE_PREFIX = "\u2502 ";
2834
2724
  function createStreamWriter(enabled, sink) {
@@ -2871,7 +2761,6 @@ function createStreamWriter(enabled, sink) {
2871
2761
  }
2872
2762
  };
2873
2763
  }
2874
-
2875
2764
  // src/commands/run-once.ts
2876
2765
  async function readStdin() {
2877
2766
  if (process.stdin.isTTY) return "";
@@ -2899,7 +2788,7 @@ function composeContent(query, images) {
2899
2788
  throw new CliError(
2900
2789
  err2 instanceof Error ? err2.message : String(err2),
2901
2790
  EXIT_CODES.FAILURE,
2902
- "\u652F\u6301 png / jpeg / gif / webp\uFF0C\u4E5F\u53EF\u4EE5\u76F4\u63A5\u7ED9 http(s) \u56FE\u7247 URL"
2791
+ t21("run_once.err_image")
2903
2792
  );
2904
2793
  }
2905
2794
  }
@@ -2908,9 +2797,9 @@ async function runOnce(query, opts, resumeId) {
2908
2797
  const { policy: headless, invalid } = parseHeadlessFlags(opts);
2909
2798
  if (invalid.length > 0) {
2910
2799
  throw new CliError(
2911
- `--allow-operation \u4E0D\u8BA4\u8BC6: ${invalid.join(", ")}`,
2800
+ t21("run_once.err_bad_operation", { values: invalid.join(", ") }),
2912
2801
  EXIT_CODES.FAILURE,
2913
- `\u53EF\u9009\u503C: ${OPERATION_TYPES2.join(" / ")}`
2802
+ t21("run_once.err_bad_operation_hint", { choices: OPERATION_TYPES2.join(" / ") })
2914
2803
  );
2915
2804
  }
2916
2805
  const format = resolveOutputFormat(opts);
@@ -2930,26 +2819,7 @@ async function runOnce(query, opts, resumeId) {
2930
2819
  sessionId: () => rt?.sessionId,
2931
2820
  preauth: (req) => preauthorizedBy(rt?.config.headless, headless, req)
2932
2821
  }) : createInteractiveApproval({ permission: () => rt?.permission }),
2933
- // 必须显式传:`buildRuntime` 的默认值是 false(库不替宿主决定进程怎么退),
2934
- // 而 CLI **就是**那个该被决定的宿主 —— Ctrl+C 要走 dispose() 关掉 SQLite
2935
- // 连接和 MCP 子进程,不能因为默认值翻转就悄悄退回「硬杀进程」。
2936
- // 守卫用例:cli/__tests__/signal-handlers.test.ts
2937
2822
  installSignalHandlers: true,
2938
- // 有外部审批器 = **有人能答**,所以告诉权限层按交互处理(方案 28)。
2939
- //
2940
- // 不这么做的话这个参数压根不会被调到:非交互下权限层的第 3 层会把每一次
2941
- // `ask_user` 直接收敛成 deny(见 manager.ts 的 applyPreauthorization),
2942
- // 审批回调连请求都收不到。这和长驻模式那条路是同一个决定、同一个理由
2943
- // (见 headless/session.ts 的文件头第 3 条)。
2944
- //
2945
- // 连带的三个后果,都是刻意的:
2946
- // 1. `--allow-tool` 那套预授权不再由权限层判 —— 所以下面 `preauth` 把
2947
- // 「预授权先命中就不起子进程」补在了审批回调里(验收 #18)
2948
- // 2. 混淆构造(`$(echo rm) -rf /`)从「非交互无人确认,拒绝」变成「问审批
2949
- // 程序」。那正是「有人能答」的含义,也是文档里要写明「等同于信任它」的
2950
- // 原因之一
2951
- // 3. 这条路上**不会出现退出码 3** —— 那个码的语义是「没人可问所以被卡住」,
2952
- // 而这里每一次都真的问过了。审批程序答 deny 是它自己的决定,退 0
2953
2823
  ...approver ? { interactive: true } : {},
2954
2824
  ...headless ? { headless } : {},
2955
2825
  ...resumeId ? { resumeId } : {}
@@ -2959,10 +2829,10 @@ async function runOnce(query, opts, resumeId) {
2959
2829
  const detail = runtime.diagnostics.join("\n ");
2960
2830
  await runtime.dispose();
2961
2831
  throw new CliError(
2962
- "provider \u4E0D\u53EF\u7528\uFF0Cagent \u65E0\u6CD5\u542F\u52A8",
2832
+ t21("run_once.err_no_provider"),
2963
2833
  EXIT_CODES.FAILURE,
2964
- detail ? `\u8BCA\u65AD:
2965
- ${detail}` : "\u8FD0\u884C epoch model \u914D\u7F6E"
2834
+ detail ? `${t21("run_once.err_no_provider_diag")}
2835
+ ${detail}` : t21("run_once.err_no_provider_hint")
2966
2836
  );
2967
2837
  }
2968
2838
  try {
@@ -2970,7 +2840,10 @@ async function runOnce(query, opts, resumeId) {
2970
2840
  if (resumeId) {
2971
2841
  const restored = runtime.session.getHistory().length;
2972
2842
  process.stderr.write(
2973
- `\u21BB \u5DF2\u6062\u590D ${resumeId.slice(0, SHORT_ID_LEN)}\uFF08${restored} \u6761\u5386\u53F2\u6D88\u606F\uFF09
2843
+ `${t21("run_once.resumed", {
2844
+ id: resumeId.slice(0, SHORT_ID_LEN),
2845
+ count: restored
2846
+ })}
2974
2847
  `
2975
2848
  );
2976
2849
  }
@@ -3030,7 +2903,7 @@ function collect2(out2, ev, w) {
3030
2903
  } else if (ev.type === "usage") {
3031
2904
  out2.usage = ev.cumulative;
3032
2905
  } else if (ev.type === "error") {
3033
- out2.text = `\u9519\u8BEF: ${ev.message}`;
2906
+ out2.text = t21("run_once.error_prefix", { message: ev.message });
3034
2907
  process.exitCode = EXIT_CODES.FAILURE;
3035
2908
  } else if (ev.type === "finish") {
3036
2909
  out2.finishReason = ev.reason;
@@ -3056,8 +2929,6 @@ function emitOutcome(out2, opts, format, emitter, tasks = []) {
3056
2929
  budgetExceeded: out2.budgetExceeded,
3057
2930
  aborted: out2.aborted,
3058
2931
  diagnostics: out2.diagnostics,
3059
- // 一个后台任务都没起时**整个字段都不出现** —— 绝大多数调用是这样,
3060
- // 而验收 4.1 第 1 条要求 `--json` 的输出与改造前逐字节相同
3061
2932
  ...tasks.length > 0 ? { backgroundTasks: tasks } : {}
3062
2933
  },
3063
2934
  null,
@@ -3066,12 +2937,16 @@ function emitOutcome(out2, opts, format, emitter, tasks = []) {
3066
2937
  );
3067
2938
  return;
3068
2939
  }
3069
- if (out2.reasoning) process.stderr.write(`\u23FA \u601D\u8003\u8FC7\u7A0B:
2940
+ if (out2.reasoning) {
2941
+ process.stderr.write(`${t21("run_once.reasoning_head")}
3070
2942
  ${out2.reasoning}
3071
2943
 
3072
2944
  `);
2945
+ }
3073
2946
  if (opts.stream === false && out2.text) process.stdout.write(out2.text + "\n");
3074
- if (out2.aborted) process.stderr.write("\n\u5DF2\u4E2D\u6B62\n");
2947
+ if (out2.aborted) process.stderr.write(`
2948
+ ${t21("run_once.aborted")}
2949
+ `);
3075
2950
  if (taskLines) process.stderr.write(taskLines + "\n");
3076
2951
  const summary = formatSummary(out2.usage, out2.budgetExceeded);
3077
2952
  if (summary) process.stderr.write(summary + "\n");
@@ -3087,19 +2962,19 @@ function noteworthy(diagnostics, verbose) {
3087
2962
  }
3088
2963
  function formatSummary(usage, budgetExceeded) {
3089
2964
  if (!usage) return "";
3090
- const cached = usage.cacheHitTokens ? `\uFF08\u7F13\u5B58\u547D\u4E2D ${usage.cacheHitTokens}\uFF09` : "";
2965
+ const cached = usage.cacheHitTokens ? t21("run_once.cache_hit", { count: usage.cacheHitTokens }) : "";
3091
2966
  const tokens = `${promptTokens(usage)} in${cached} / ${usage.outputTokens} out`;
3092
- const cost = usage.costUsd === void 0 ? "\u6210\u672C\u672A\u77E5\uFF08\u65E0\u5B9A\u4EF7\u6570\u636E\uFF09" : formatUsd(usage.costUsd);
3093
- const stopped = budgetExceeded ? " \u26A0 \u5DF2\u89E6\u8FBE\u9884\u7B97\u4E0A\u9650" : "";
2967
+ const cost = usage.costUsd === void 0 ? t21("run_once.cost_unknown") : formatUsd(usage.costUsd);
2968
+ const stopped = budgetExceeded ? t21("run_once.budget_reached") : "";
3094
2969
  return `\u2500 ${tokens} \xB7 ${cost}${stopped}`;
3095
2970
  }
3096
2971
  function reportHeadlessAudit(permission) {
3097
2972
  const audit = permission?.getAudit("headless").entries ?? [];
3098
2973
  if (audit.length === 0) return;
3099
2974
  for (const e of audit) {
2975
+ const vars = { tool: e.toolName, target: e.target, reason: e.reason };
3100
2976
  process.stderr.write(
3101
- e.outcome === "granted" ? `[\u9884\u6388\u6743\u653E\u884C] ${e.toolName} ${e.target} \u2014\u2014 ${e.reason}
3102
- ` : `[\u672A\u9884\u6388\u6743\u62D2\u7EDD] ${e.toolName} ${e.target} \u2014\u2014 ${e.reason}
2977
+ `${e.outcome === "granted" ? t21("run_once.audit_granted", vars) : t21("run_once.audit_denied", vars)}
3103
2978
  `
3104
2979
  );
3105
2980
  }
@@ -3107,10 +2982,9 @@ function reportHeadlessAudit(permission) {
3107
2982
  process.exitCode = EXIT_CODES.PERMISSION_DENIED;
3108
2983
  }
3109
2984
  }
3110
-
3111
2985
  // src/commands/run-overrides.ts
3112
2986
  import { isPermissionLevel as isPermissionLevel2, PERMISSION_LEVELS as PERMISSION_LEVELS2 } from "@epoch-agent/core";
3113
- import { t as t3 } from "@epoch-agent/infra";
2987
+ import { t as t22 } from "@epoch-agent/infra";
3114
2988
  import { isProviderType as isProviderType2, PROVIDER_TYPES as PROVIDER_TYPES3 } from "@epoch-agent/protocol";
3115
2989
  function applyRunFlags(opts) {
3116
2990
  applySettingsPath(opts.settings);
@@ -3129,9 +3003,9 @@ function applyOverrides(opts) {
3129
3003
  if (opts.provider) {
3130
3004
  if (!isProviderType2(opts.provider)) {
3131
3005
  throw new CliError(
3132
- t3("flags.provider_unknown", { value: opts.provider }),
3006
+ t22("flags.provider_unknown", { value: opts.provider }),
3133
3007
  1,
3134
- t3("flags.choices", { choices: PROVIDER_TYPES3.join(", ") })
3008
+ t22("flags.choices", { choices: PROVIDER_TYPES3.join(", ") })
3135
3009
  );
3136
3010
  }
3137
3011
  process.env.EPOCH_PROVIDER = opts.provider;
@@ -3139,9 +3013,9 @@ function applyOverrides(opts) {
3139
3013
  if (opts.permission) {
3140
3014
  if (!isPermissionLevel2(opts.permission)) {
3141
3015
  throw new CliError(
3142
- t3("flags.permission_unknown", { value: opts.permission }),
3016
+ t22("flags.permission_unknown", { value: opts.permission }),
3143
3017
  1,
3144
- t3("flags.choices", { choices: PERMISSION_LEVELS2.join(", ") })
3018
+ t22("flags.choices", { choices: PERMISSION_LEVELS2.join(", ") })
3145
3019
  );
3146
3020
  }
3147
3021
  process.env.EPOCH_PERMISSION = opts.permission;
@@ -3152,9 +3026,9 @@ function assertModelName(value, flag) {
3152
3026
  const spec = value.trim();
3153
3027
  if (MODEL_ID.test(spec)) return value;
3154
3028
  throw new CliError(
3155
- t3("flags.model_not_a_name", { flag, value }),
3029
+ t22("flags.model_not_a_name", { flag, value }),
3156
3030
  1,
3157
- t3("flags.model_not_a_name_hint", { value: spec })
3031
+ t22("flags.model_not_a_name_hint", { value: spec })
3158
3032
  );
3159
3033
  }
3160
3034
  function assertHttpUrl(value) {
@@ -3164,56 +3038,60 @@ function assertHttpUrl(value) {
3164
3038
  } catch {
3165
3039
  }
3166
3040
  throw new CliError(
3167
- t3("flags.base_url_invalid", { value }),
3041
+ t22("flags.base_url_invalid", { value }),
3168
3042
  1,
3169
- t3("flags.base_url_invalid_hint", { value: value.trim() })
3043
+ t22("flags.base_url_invalid_hint", { value: value.trim() })
3170
3044
  );
3171
3045
  }
3172
-
3173
3046
  // src/commands/sessions.ts
3174
3047
  import { isNonInteractive as isNonInteractive7, resolveProjectRoot as resolveProjectRoot3, SessionManager } from "@epoch-agent/core";
3175
- import { dbPath } from "@epoch-agent/infra";
3048
+ import { dbPath, t as t23, uiDateLocale as uiDateLocale2 } from "@epoch-agent/infra";
3176
3049
  var log6 = (msg) => process.stdout.write(msg + "\n");
3177
3050
  var SHORT_ID_LEN2 = 8;
3178
3051
  var PICK_LIMIT = 20;
3052
+ function whenOf(startedAt) {
3053
+ return startedAt ? new Date(startedAt).toLocaleString(uiDateLocale2()) : t23("sessions.when_unknown");
3054
+ }
3055
+ function titleOf(s) {
3056
+ return s.title || s.preview || t23("sessions.untitled");
3057
+ }
3179
3058
  function registerSessionsCommand(program2) {
3180
- const cmd = program2.command("sessions").alias("session").description("\u7BA1\u7406\u5386\u53F2\u4F1A\u8BDD");
3181
- cmd.command("list").description("\u5217\u51FA\u6700\u8FD1\u7684\u4F1A\u8BDD").option("-n, --limit <n>", "\u6570\u91CF", "10").action((opts) => {
3059
+ const cmd = program2.command("sessions").alias("session").description(t23("sessions.cmd_root"));
3060
+ cmd.command("list").description(t23("sessions.cmd_list")).option("-n, --limit <n>", t23("sessions.opt_limit"), "10").action((opts) => {
3182
3061
  withSessions((sm) => {
3183
3062
  const r = sm.list({ limit: parseInt(opts.limit, 10) });
3184
3063
  if (r.sessions.length === 0) {
3185
- log6(" (\u65E0)");
3064
+ log6(` ${t23("sessions.list_empty")}`);
3186
3065
  return;
3187
3066
  }
3188
3067
  for (const s of r.sessions) {
3189
- const when = s.startedAt ? new Date(s.startedAt).toLocaleString("zh-CN") : "\u672A\u77E5";
3190
- log6(` ${s.id.slice(0, SHORT_ID_LEN2)} ${when} ${s.title || s.preview || "(\u65E0)"}`);
3068
+ log6(` ${s.id.slice(0, SHORT_ID_LEN2)} ${whenOf(s.startedAt)} ${titleOf(s)}`);
3191
3069
  }
3192
3070
  });
3193
3071
  });
3194
- cmd.command("resume").description("\u6062\u590D\u4F1A\u8BDD\u7EE7\u7EED\u5BF9\u8BDD\uFF08id \u53EF\u4EE5\u53EA\u5199 list \u91CC\u663E\u793A\u7684\u524D 8 \u4F4D\uFF09").argument("[id]", "\u4F1A\u8BDD ID \u6216\u5176\u524D\u7F00\uFF08\u4E0D\u6307\u5B9A\u5219\u6062\u590D\u6700\u65B0\uFF09").argument("[query]", "\u8981\u53D1\u9001\u7684\u6D88\u606F\uFF08\u53EF\u9009\uFF09").action(async (id, query) => {
3072
+ cmd.command("resume").description(t23("sessions.cmd_resume")).argument("[id]", t23("sessions.arg_id")).argument("[query]", t23("sessions.arg_query")).action(async (id, query) => {
3195
3073
  const resolved = resolveSessionId(id);
3196
3074
  if (!resolved.ok) {
3197
3075
  throw new CliError(resolved.error, EXIT_CODES.FAILURE);
3198
3076
  }
3199
- await runOnce(query || "\u7EE7\u7EED", {}, resolved.id);
3077
+ await runOnce(query || t23("sessions.default_query"), {}, resolved.id);
3200
3078
  });
3201
3079
  }
3202
3080
  function resolveSessionId(input2) {
3203
3081
  return withSessions((sm) => {
3204
3082
  if (!input2) {
3205
3083
  const latest = sm.getLatest()?.meta.id;
3206
- return latest ? { ok: true, id: latest } : { ok: false, error: "\u65E0\u53EF\u6062\u590D\u7684\u4F1A\u8BDD" };
3084
+ return latest ? { ok: true, id: latest } : { ok: false, error: t23("sessions.err_none") };
3207
3085
  }
3208
3086
  if (sm.get(input2)) return { ok: true, id: input2 };
3209
3087
  const candidates = sm.list({ limit: 200 }).sessions.filter((s) => s.id.startsWith(input2)).map((s) => s.id);
3210
3088
  if (candidates.length === 1) return { ok: true, id: candidates[0] };
3211
3089
  if (candidates.length === 0) {
3212
- return { ok: false, error: `\u627E\u4E0D\u5230\u4F1A\u8BDD: ${input2}\uFF08\u7528 epoch sessions list \u67E5\u770B\uFF09` };
3090
+ return { ok: false, error: t23("sessions.err_not_found", { input: input2 }) };
3213
3091
  }
3214
3092
  return {
3215
3093
  ok: false,
3216
- error: `\u4F1A\u8BDD\u524D\u7F00 ${input2} \u5339\u914D\u5230 ${candidates.length} \u4E2A\uFF0C\u8BF7\u5199\u66F4\u957F\u7684\u524D\u7F00`
3094
+ error: t23("sessions.err_ambiguous", { input: input2, count: candidates.length })
3217
3095
  };
3218
3096
  });
3219
3097
  }
@@ -3224,33 +3102,32 @@ function latestSessionIdForProject(cwd = process.cwd()) {
3224
3102
  async function pickSessionId() {
3225
3103
  if (isNonInteractive7()) {
3226
3104
  throw new CliError(
3227
- "\u975E\u4EA4\u4E92\u73AF\u5883\u4E0B --resume \u5FC5\u987B\u5E26\u4F1A\u8BDD id",
3105
+ t23("sessions.err_needs_id"),
3228
3106
  EXIT_CODES.FAILURE,
3229
- "\u7528 epoch sessions list \u67E5 id\uFF0C\u6216\u7528 --continue \u63A5\u672C\u9879\u76EE\u6700\u8FD1\u4E00\u4E2A"
3107
+ t23("sessions.err_needs_id_hint")
3230
3108
  );
3231
3109
  }
3232
3110
  const rows = withSessions(
3233
3111
  (sm) => sm.list({ limit: PICK_LIMIT }).sessions.map((s) => ({
3234
3112
  id: s.id,
3235
- title: s.title || s.preview || "(\u65E0\u6807\u9898)",
3236
- when: s.startedAt ? new Date(s.startedAt).toLocaleString("zh-CN") : "\u672A\u77E5",
3237
- // 老会话的 cwd NULL(方案 29 之前没人写它),如实显示成「未记录」
3238
- where: s.cwd ?? "\u672A\u8BB0\u5F55",
3113
+ title: titleOf(s),
3114
+ when: whenOf(s.startedAt),
3115
+ where: s.cwd ?? t23("sessions.cwd_unrecorded"),
3239
3116
  messageCount: s.messageCount
3240
3117
  }))
3241
3118
  );
3242
3119
  if (rows.length === 0) {
3243
- log6("\u6CA1\u6709\u5386\u53F2\u4F1A\u8BDD\uFF0C\u5C06\u5F00\u59CB\u4E00\u6BB5\u65B0\u5BF9\u8BDD\u3002");
3120
+ log6(t23("sessions.pick_empty"));
3244
3121
  return void 0;
3245
3122
  }
3246
3123
  const { select: select2 } = await import("@inquirer/prompts");
3247
3124
  try {
3248
3125
  return await select2({
3249
- message: "\u6062\u590D\u54EA\u4E00\u6BB5\u4F1A\u8BDD\uFF1F",
3126
+ message: t23("sessions.pick_prompt"),
3250
3127
  choices: rows.map((r) => ({
3251
3128
  name: `${r.id.slice(0, SHORT_ID_LEN2)} ${r.when} ${r.title}`,
3252
3129
  value: r.id,
3253
- description: `${r.messageCount} \u6761\u6D88\u606F \xB7 ${r.where}`
3130
+ description: `${t23("tui.resume.message_count", { n: r.messageCount })} \xB7 ${r.where}`
3254
3131
  })),
3255
3132
  pageSize: 12
3256
3133
  });
@@ -3266,37 +3143,26 @@ function withSessions(fn) {
3266
3143
  sm.close();
3267
3144
  }
3268
3145
  }
3269
-
3270
3146
  // src/commands/run.ts
3271
3147
  var log7 = (msg) => {
3272
3148
  process.stdout.write(msg + "\n");
3273
3149
  };
3274
3150
  function registerRunCommand(program2) {
3275
- program2.argument("[query]", "\u8981\u6267\u884C\u7684\u4EFB\u52A1\uFF1B\u4E0D\u7ED9\u4E14 stdin \u6709\u5185\u5BB9\u65F6\u8BFB stdin").option("--allow-tool <name>", "\u975E\u4EA4\u4E92\uFF08CI / \u7BA1\u9053\uFF09\u4E0B\u9884\u6388\u6743\u7684\u5DE5\u5177\u540D\uFF0C\u53EF\u91CD\u590D", collect, []).option(
3151
+ program2.argument("[query]", t24("run.arg_query")).option("--allow-tool <name>", t24("run.opt_allow_tool"), collect, []).option(
3276
3152
  "--allow-operation <type>",
3277
- `\u975E\u4EA4\u4E92\u4E0B\u9884\u6388\u6743\u7684\u64CD\u4F5C\u7C7B\u578B\uFF08${OPERATION_TYPES3.join(" / ")}\uFF09\uFF0C\u53EF\u91CD\u590D`,
3153
+ t24("run.opt_allow_operation", { types: OPERATION_TYPES3.join(" / ") }),
3278
3154
  collect,
3279
3155
  []
3280
- ).option(
3281
- "-i, --image <path>",
3282
- "\u9644\u52A0\u56FE\u7247\uFF08\u8DEF\u5F84\u6216 http(s) URL\uFF09\uFF0C\u53EF\u91CD\u590D\u3002\u9700\u8981\u6A21\u578B\u652F\u6301\u89C6\u89C9",
3283
- collect,
3284
- []
3285
- ).option("-c, --continue", "\u63A5\u4E0A\u672C\u9879\u76EE\u6700\u8FD1\u4E00\u4E2A\u4F1A\u8BDD\u7EE7\u7EED").option("-r, --resume [id]", "\u6062\u590D\u67D0\u4E2A\u4F1A\u8BDD\uFF1B\u4E0D\u5E26 id \u5219\u4EA4\u4E92\u5F0F\u9009").option("-m, --model <name>", "\u672C\u6B21\u4F7F\u7528\u7684\u6A21\u578B\uFF0C\u8986\u76D6\u914D\u7F6E\u6587\u4EF6").option("--fallback-model <name>", "\u4E3B\u6A21\u578B\u4E0D\u53EF\u7528\u65F6\uFF0C\u540C\u4E00\u4E2A provider \u5185\u5148\u964D\u7EA7\u5230\u5B83").option("-p, --provider <type>", `\u672C\u6B21\u4F7F\u7528\u7684 provider\uFF08${PROVIDER_TYPES4.join(" / ")}\uFF09`).option("--base-url <url>", "\u672C\u6B21\u4F7F\u7528\u7684 API base URL").option("--permission <level>", `\u672C\u6B21\u7684\u6743\u9650\u7EA7\u522B\uFF08${PERMISSION_LEVELS3.join(" / ")}\uFF09`).option("--settings <file>", "\u989D\u5916\u7684\u8BBE\u7F6E\u6587\u4EF6\uFF0C\u538B\u8FC7\u9879\u76EE\u7EA7\u548C\u7528\u6237\u7EA7\uFF08CI \u7528\uFF09").option(
3286
- "--add-dir <dir>",
3287
- "\u628A\u989D\u5916\u76EE\u5F55\u52A0\u8FDB\u5DE5\u4F5C\u533A\uFF08\u6587\u4EF6\u5DE5\u5177\u548C\u6743\u9650\u5224\u5B9A\u90FD\u653E\u884C\u5B83\uFF09\uFF0C\u53EF\u91CD\u590D",
3288
- collect,
3289
- []
3290
- ).option("--agent <role>", "\u7528\u67D0\u4E2A\u89D2\u8272\u8DD1\u4E3B agent\uFF08\u5B83\u7684\u5DE5\u5177\u767D\u540D\u5355\u548C maxTurns \u751F\u6548\uFF09").option("--worktree", "\u5728\u4E00\u4E2A\u65B0\u5EFA\u7684 git worktree\uFF08\u65B0\u5206\u652F\uFF09\u91CC\u8DD1\u8FD9\u4E00\u6B21\uFF0C\u9000\u51FA\u65F6\u95EE\u662F\u5426\u5220\u9664").option("--json", "\u4EE5 JSON \u8F93\u51FA\uFF08stdout \u6070\u597D\u4E00\u4E2A\u6587\u6863\uFF09\uFF0C\u7ED9 CI \u7528").option(
3156
+ ).option("-i, --image <path>", t24("run.opt_image"), collect, []).option("-c, --continue", t24("run.opt_continue")).option("-r, --resume [id]", t24("run.opt_resume")).option("-m, --model <name>", t24("run.opt_model")).option("--fallback-model <name>", t24("run.opt_fallback_model")).option("-p, --provider <type>", t24("run.opt_provider", { types: PROVIDER_TYPES4.join(" / ") })).option("--base-url <url>", t24("run.opt_base_url")).option(
3157
+ "--permission <level>",
3158
+ t24("run.opt_permission", { levels: PERMISSION_LEVELS3.join(" / ") })
3159
+ ).option("--settings <file>", t24("run.opt_settings")).option("--add-dir <dir>", t24("run.opt_add_dir"), collect, []).option("--agent <role>", t24("run.opt_agent")).option("--worktree", t24("run.opt_worktree")).option("--json", t24("run.opt_json")).option(
3291
3160
  "--output-format <fmt>",
3292
- `stdout \u7684\u683C\u5F0F\uFF08${HEADLESS_OUTPUT_FORMATS2.join(" / ")}\uFF09\u3002--json \u662F json \u7684\u522B\u540D`
3161
+ t24("run.opt_output_format", { formats: HEADLESS_OUTPUT_FORMATS2.join(" / ") })
3293
3162
  ).option(
3294
3163
  "--input-format <fmt>",
3295
- `stdin \u7684\u683C\u5F0F\uFF08${HEADLESS_INPUT_FORMATS2.join(" / ")}\uFF09\u3002stream-json \u8FDB\u957F\u9A7B\u6A21\u5F0F`
3296
- ).option(
3297
- "--permission-prompt-tool <program>",
3298
- "\u628A\u6BCF\u4E00\u6B21\u9700\u8981\u786E\u8BA4\u7684\u64CD\u4F5C\u4EA4\u7ED9\u8FD9\u4E2A\u5916\u90E8\u7A0B\u5E8F\u88C1\u51B3\uFF08\u7B49\u540C\u4E8E\u5B8C\u5168\u4FE1\u4EFB\u5B83\uFF09"
3299
- ).option("--max-turns <n>", "\u672C\u6B21\u8C03\u7528\u6700\u591A\u51E0\u8F6E\u5DE5\u5177\u5FAA\u73AF\uFF0C\u538B\u8FC7\u914D\u7F6E\u91CC\u7684 maxTurns").option("--max-budget-usd <x>", "\u672C\u6B21\u8C03\u7528\u82B1\u8D85\u8FD9\u4E2A\u91D1\u989D\u5C31\u505C\uFF08\u4E0E\u8DE8\u4F1A\u8BDD\u9884\u7B97\u53E0\u52A0\uFF09").option("--no-stream", "\u4E0D\u6D41\u5F0F\u8F93\u51FA\uFF0C\u7B49\u8DD1\u5B8C\u4E00\u6B21\u6027\u6253\u5370").option("-v, --verbose", "\u6253\u5370\u5168\u90E8\u542F\u52A8\u8BCA\u65AD\uFF08\u9ED8\u8BA4\u53EA\u6253\u975E OK \u7684\uFF09").option("-V, --version", "\u6253\u5370\u7248\u672C\u3001Node\u3001\u5E73\u53F0\u4E0E\u5B89\u88C5\u65B9\u5F0F").action(async (query, opts) => {
3164
+ t24("run.opt_input_format", { formats: HEADLESS_INPUT_FORMATS2.join(" / ") })
3165
+ ).option("--permission-prompt-tool <program>", t24("run.opt_permission_prompt_tool")).option("--max-turns <n>", t24("run.opt_max_turns")).option("--max-budget-usd <x>", t24("run.opt_max_budget")).option("--no-stream", t24("run.opt_no_stream")).option("-v, --verbose", t24("run.opt_verbose")).option("-V, --version", t24("run.opt_version")).action(async (query, opts) => {
3300
3166
  if (opts.version === true) {
3301
3167
  log7(describeVersion(selfPackage().name));
3302
3168
  return;
@@ -3308,30 +3174,30 @@ function registerRunCommand(program2) {
3308
3174
  if (formats.input === "stream-json") {
3309
3175
  if (opts.continue === true || resumeArg.kind !== "off") {
3310
3176
  throw new CliError(
3311
- "--continue / --resume \u4E0D\u80FD\u548C --input-format stream-json \u4E00\u8D77\u7528",
3177
+ t24("run.conflict_resume"),
3312
3178
  EXIT_CODES.FAILURE,
3313
- "\u957F\u9A7B\u6A21\u5F0F\u4E0B\u8981\u6062\u590D\u54EA\u6BB5\u4F1A\u8BDD\u7531\u5BBF\u4E3B\u5728 init \u5E27\u7684 sessionId \u91CC\u6307\u5B9A"
3179
+ t24("run.conflict_resume_hint")
3314
3180
  );
3315
3181
  }
3316
3182
  if (opts.worktree === true) {
3317
3183
  throw new CliError(
3318
- "--worktree \u4E0D\u80FD\u548C --input-format stream-json \u4E00\u8D77\u7528",
3184
+ t24("run.conflict_worktree"),
3319
3185
  EXIT_CODES.FAILURE,
3320
- "\u957F\u9A7B\u6A21\u5F0F\u4E0B stdout \u662F\u534F\u8BAE\u901A\u9053\uFF0Cworktree \u7684\u63D0\u793A\u548C\u8BE2\u95EE\u6CA1\u6709\u843D\u70B9\uFF1B\u5BBF\u4E3B\u53EF\u4EE5\u81EA\u5DF1 git worktree add \u518D\u7528\u90A3\u4E2A\u76EE\u5F55\u5F53 cwd \u542F\u52A8"
3186
+ t24("run.conflict_worktree_hint")
3321
3187
  );
3322
3188
  }
3323
3189
  if (opts.permissionPromptTool !== void 0) {
3324
3190
  throw new CliError(
3325
- "--permission-prompt-tool \u4E0D\u80FD\u548C --input-format stream-json \u4E00\u8D77\u7528",
3191
+ t24("run.conflict_prompt_tool"),
3326
3192
  EXIT_CODES.FAILURE,
3327
- "\u957F\u9A7B\u6A21\u5F0F\u4E0B\u5BA1\u6279\u5DF2\u7ECF\u662F\u6D41\u91CC\u7684 approval-request \u5E27\uFF0C\u4F60\u76F4\u63A5\u7B54\u5C31\u884C \u2014\u2014 \u90A3\u6761\u8DEF\u66F4\u5E72\u51C0\uFF08\u4E0D\u7528\u4E3A\u6BCF\u6B21\u5BA1\u6279\u8D77\u4E00\u4E2A\u5B50\u8FDB\u7A0B\uFF0C\u4E5F\u4E0D\u7528\u7F16\u89E3\u7801\uFF09"
3193
+ t24("run.conflict_prompt_tool_hint")
3328
3194
  );
3329
3195
  }
3330
3196
  if (!hasCredentials()) {
3331
3197
  throw new CliError(
3332
- "\u6CA1\u6709\u914D\u7F6E\u6A21\u578B\u51ED\u636E\uFF0C\u957F\u9A7B\u6A21\u5F0F\u8D77\u4E0D\u6765",
3198
+ t24("run.no_credentials_stream"),
3333
3199
  EXIT_CODES.FAILURE,
3334
- "\u5148\u8FD0\u884C epoch model \u914D\u7F6E provider \u548C API key"
3200
+ t24("run.no_credentials_hint")
3335
3201
  );
3336
3202
  }
3337
3203
  process.exitCode = await runStreamJsonSession(opts);
@@ -3340,7 +3206,7 @@ function registerRunCommand(program2) {
3340
3206
  const carried = resumeArg.kind === "pick" ? resumeArg.query : void 0;
3341
3207
  const prompt = composePrompt(query ?? carried, await readStdin());
3342
3208
  if (!hasCredentials()) {
3343
- log7("\u{1F44B} \u6B22\u8FCE\u4F7F\u7528 epoch-agent\uFF01\n\n\u8FD0\u884C epoch model \u914D\u7F6E\u6A21\u578B\u548C API key \u5373\u53EF\u5F00\u59CB\u5BF9\u8BDD\u3002");
3209
+ log7(t24("run.welcome"));
3344
3210
  return;
3345
3211
  }
3346
3212
  const resumeId = await resolveResumeTarget(opts, resumeArg);
@@ -3366,7 +3232,8 @@ async function resolveResumeTarget(opts, resumeArg) {
3366
3232
  if (opts.continue === true) {
3367
3233
  const latest = latestSessionIdForProject();
3368
3234
  if (!latest) {
3369
- process.stderr.write("\u672C\u9879\u76EE\u8FD8\u6CA1\u6709\u5386\u53F2\u4F1A\u8BDD\uFF0C\u5F00\u59CB\u4E00\u6BB5\u65B0\u7684\u3002\n");
3235
+ process.stderr.write(`${t24("run.no_history")}
3236
+ `);
3370
3237
  }
3371
3238
  return latest;
3372
3239
  }
@@ -3376,8 +3243,7 @@ async function resolveResumeTarget(opts, resumeArg) {
3376
3243
  throw new CliError(
3377
3244
  resolved.error,
3378
3245
  EXIT_CODES.FAILURE,
3379
- // `-r` 的值是纯十六进制的一句话时会走到这儿(见 looksLikeSessionId)
3380
- '\u5982\u679C\u90A3\u4E0D\u662F id \u800C\u662F\u8981\u53D1\u7684\u6D88\u606F\uFF0C\u5199\u6210 epoch --resume "\u6D88\u606F" \u4E4B\u5916\u7684\u5F62\u5F0F\uFF1Aepoch -r -- "\u6D88\u606F" \u4F1A\u628A\u5B83\u5F53\u4F4D\u7F6E\u53C2\u6570\uFF0C\u6216\u5148 epoch sessions list \u67E5 id'
3246
+ t24("run.resume_not_id_hint")
3381
3247
  );
3382
3248
  }
3383
3249
  return resolved.id;
@@ -3396,7 +3262,8 @@ function resolveTuiEntry(baseDir, exists = existsSync6) {
3396
3262
  async function launchTui(resumeId) {
3397
3263
  const target = resolveTuiEntry(dirname3(fileURLToPath3(import.meta.url)));
3398
3264
  if (!target) {
3399
- process.stderr.write("\u542F\u52A8\u5931\u8D25\uFF1A\u627E\u4E0D\u5230 TUI \u5165\u53E3 tui-entry\uFF0C\u8BF7\u5148\u8FD0\u884C pnpm build\n");
3265
+ process.stderr.write(`${t24("run.tui_entry_missing")}
3266
+ `);
3400
3267
  process.exitCode = EXIT_CODES.FAILURE;
3401
3268
  return;
3402
3269
  }
@@ -3410,7 +3277,7 @@ async function launchTui(resumeId) {
3410
3277
  });
3411
3278
  await new Promise((done) => {
3412
3279
  child.on("error", (err2) => {
3413
- process.stderr.write(`TUI \u542F\u52A8\u5931\u8D25: ${err2.message}
3280
+ process.stderr.write(`${t24("run.tui_spawn_failed", { message: err2.message })}
3414
3281
  `);
3415
3282
  process.exitCode = EXIT_CODES.FAILURE;
3416
3283
  done();
@@ -3421,16 +3288,14 @@ async function launchTui(resumeId) {
3421
3288
  });
3422
3289
  });
3423
3290
  }
3424
-
3425
3291
  // src/commands/schedule.ts
3426
- import { t as t11 } from "@epoch-agent/infra";
3427
-
3292
+ import { t as t32 } from "@epoch-agent/infra";
3428
3293
  // src/schedule/add.ts
3429
3294
  import {
3430
3295
  SCHEDULE_DEFAULTS,
3431
3296
  validateSchedule
3432
3297
  } from "@epoch-agent/core";
3433
- import { automationWorkDir, t as t6 } from "@epoch-agent/infra";
3298
+ import { automationWorkDir, t as t27 } from "@epoch-agent/infra";
3434
3299
  import {
3435
3300
  isOperationType as isOperationType2,
3436
3301
  isPermissionLevel as isPermissionLevel3,
@@ -3438,9 +3303,11 @@ import {
3438
3303
  PERMISSION_LEVELS as PERMISSION_LEVELS4,
3439
3304
  SCHEDULE_INTERVAL_MINUTES
3440
3305
  } from "@epoch-agent/protocol";
3441
-
3442
3306
  // src/schedule/labels.ts
3443
- import { t as t4 } from "@epoch-agent/infra";
3307
+ import { t as t25 } from "@epoch-agent/infra";
3308
+ import {
3309
+ collectPendingApprovals
3310
+ } from "@epoch-agent/protocol";
3444
3311
  var ISSUE_KEYS = {
3445
3312
  "name-empty": "schedule.issue.name_empty",
3446
3313
  "prompt-empty": "schedule.issue.prompt_empty",
@@ -3489,37 +3356,36 @@ var DRIFT_KEYS = {
3489
3356
  "never-registered": "schedule.drift.never_registered"
3490
3357
  };
3491
3358
  function renderIssues(issues) {
3492
- return issues.map((i) => ` \xB7 ${t4(ISSUE_KEYS[i.code], { detail: i.detail ?? "" })}`).join("\n");
3359
+ return issues.map((i) => ` \xB7 ${t25(ISSUE_KEYS[i.code], { detail: i.detail ?? "" })}`).join("\n");
3493
3360
  }
3494
3361
  function statusLabel(status) {
3495
3362
  const mark = status === "ok" ? "\u2705" : status === "failed" || status === "denied" ? "\u274C" : "\u26A0\uFE0F";
3496
- return `${mark} ${t4(STATUS_KEYS[status])}`;
3363
+ return `${mark} ${t25(STATUS_KEYS[status])}`;
3497
3364
  }
3498
3365
  function driftLabel(kind) {
3499
- return t4(DRIFT_KEYS[kind]);
3366
+ return t25(DRIFT_KEYS[kind]);
3500
3367
  }
3501
3368
  function describeTrigger(trigger) {
3502
3369
  if (trigger.kind === "interval") {
3503
- return trigger.everyMinutes < 60 ? t4("schedule.trigger.every_minutes", { n: trigger.everyMinutes }) : t4("schedule.trigger.every_hours", { n: trigger.everyMinutes / 60 });
3370
+ return trigger.everyMinutes < 60 ? t25("schedule.trigger.every_minutes", { n: trigger.everyMinutes }) : t25("schedule.trigger.every_hours", { n: trigger.everyMinutes / 60 });
3504
3371
  }
3505
3372
  if (trigger.kind === "once") {
3506
- return t4("schedule.trigger.once", { date: trigger.date, at: trigger.at });
3373
+ return t25("schedule.trigger.once", { date: trigger.date, at: trigger.at });
3507
3374
  }
3508
- if (trigger.cycle === "daily") return t4("schedule.trigger.daily", { at: trigger.at });
3375
+ if (trigger.cycle === "daily") return t25("schedule.trigger.daily", { at: trigger.at });
3509
3376
  if (trigger.cycle === "weekly") {
3510
- const days = trigger.weekdays.map((d) => t4(WEEKDAY_KEYS[d])).join("/");
3511
- return t4("schedule.trigger.weekly", { days, at: trigger.at });
3377
+ const days = trigger.weekdays.map((d) => t25(WEEKDAY_KEYS[d])).join("/");
3378
+ return t25("schedule.trigger.weekly", { days, at: trigger.at });
3512
3379
  }
3513
- return t4("schedule.trigger.monthly", { days: trigger.days.join("/"), at: trigger.at });
3380
+ return t25("schedule.trigger.monthly", { days: trigger.days.join("/"), at: trigger.at });
3514
3381
  }
3515
3382
  function permissionLabel(level) {
3516
- if (level === "plan") return t4("schedule.permission.plan");
3517
- if (level === "acceptEdits") return t4("schedule.permission.accept_edits");
3518
- if (level === "bypass") return t4("schedule.permission.bypass");
3519
- if (level === "auto") return t4("schedule.permission.auto");
3520
- return t4("schedule.permission.default");
3383
+ if (level === "plan") return t25("schedule.permission.plan");
3384
+ if (level === "acceptEdits") return t25("schedule.permission.accept_edits");
3385
+ if (level === "bypass") return t25("schedule.permission.bypass");
3386
+ if (level === "auto") return t25("schedule.permission.auto");
3387
+ return t25("schedule.permission.default");
3521
3388
  }
3522
-
3523
3389
  // src/schedule/shared.ts
3524
3390
  import { realpathSync as realpathSync2 } from "fs";
3525
3391
  import {
@@ -3527,7 +3393,7 @@ import {
3527
3393
  ScheduleRegistrar,
3528
3394
  ScheduleStore
3529
3395
  } from "@epoch-agent/core";
3530
- import { dbPath as dbPath2, t as t5 } from "@epoch-agent/infra";
3396
+ import { dbPath as dbPath2, t as t26, uiDateLocale as uiDateLocale3 } from "@epoch-agent/infra";
3531
3397
  var log8 = (msg) => void process.stdout.write(`${msg}
3532
3398
  `);
3533
3399
  var warn2 = (msg) => void process.stderr.write(`${msg}
@@ -3556,24 +3422,24 @@ function mustFind(store, idOrPrefix) {
3556
3422
  if (hits.length === 1) return hits[0];
3557
3423
  if (hits.length === 0) {
3558
3424
  throw new CliError(
3559
- t5("schedule.err_not_found", { id: idOrPrefix }),
3425
+ t26("schedule.err_not_found", { id: idOrPrefix }),
3560
3426
  EXIT_CODES.FAILURE,
3561
- t5("schedule.err_not_found_hint")
3427
+ t26("schedule.err_not_found_hint")
3562
3428
  );
3563
3429
  }
3564
3430
  throw new CliError(
3565
- t5("schedule.err_ambiguous", { id: idOrPrefix }),
3431
+ t26("schedule.err_ambiguous", { id: idOrPrefix }),
3566
3432
  EXIT_CODES.FAILURE,
3567
3433
  hits.map((h) => ` ${h.id} ${h.name}`).join("\n")
3568
3434
  );
3569
3435
  }
3570
3436
  function reportRegistration(outcome, fatal) {
3571
3437
  if (outcome.ok) {
3572
- if (outcome.warnings.includes("source")) warn2(t5("schedule.warn_source_entry"));
3438
+ if (outcome.warnings.includes("source")) warn2(t26("schedule.warn_source_entry"));
3573
3439
  return;
3574
3440
  }
3575
- const message = outcome.reason === "unsupported-platform" ? t5("schedule.err_platform_unsupported") : outcome.reason === "backend-error" ? t5("schedule.err_backend", { detail: outcome.detail }) : outcome.refusal === "npx" ? t5("schedule.err_npx") : t5("schedule.err_no_host_runner");
3576
- const hint = outcome.reason === "refused" && outcome.refusal === "npx" ? t5("schedule.err_npx_hint") : outcome.reason === "refused" ? t5("schedule.err_no_host_runner_hint") : void 0;
3441
+ const message = outcome.reason === "unsupported-platform" ? t26("schedule.err_platform_unsupported") : outcome.reason === "backend-error" ? t26("schedule.err_backend", { detail: outcome.detail }) : outcome.refusal === "npx" ? t26("schedule.err_npx") : t26("schedule.err_no_host_runner");
3442
+ const hint = outcome.reason === "refused" && outcome.refusal === "npx" ? t26("schedule.err_npx_hint") : outcome.reason === "refused" ? t26("schedule.err_no_host_runner_hint") : void 0;
3577
3443
  if (!fatal) {
3578
3444
  warn2(message);
3579
3445
  if (hint) warn2(hint);
@@ -3583,9 +3449,8 @@ function reportRegistration(outcome, fatal) {
3583
3449
  }
3584
3450
  function formatWhen(ms) {
3585
3451
  if (ms === void 0) return "\u2014";
3586
- return new Date(ms).toLocaleString();
3452
+ return new Date(ms).toLocaleString(uiDateLocale3());
3587
3453
  }
3588
-
3589
3454
  // src/schedule/add.ts
3590
3455
  var WEEKDAY_CODES = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
3591
3456
  async function runAdd(opts) {
@@ -3599,10 +3464,6 @@ async function runAdd(opts) {
3599
3464
  name: input2.name,
3600
3465
  prompt: input2.prompt,
3601
3466
  permission,
3602
- // 工作区留空时它会落到 `~/.epoch/automation/<id>/`,而那个目录要等 id
3603
- // 才算得出来 —— 校验这一步先用一个必然存在的替身(数据目录本身)。
3604
- // ⚠️ `bypass` 那一档下**不许**这么替:它的第 1 条硬约束就是「必须绑一个
3605
- // 工作区」,拿数据目录顶上等于把那条约束绕过去
3606
3467
  workDir: input2.workDir ?? (permission === "bypass" ? "" : ctx.homeDir),
3607
3468
  maxTurns: input2.maxTurns ?? SCHEDULE_DEFAULTS.maxTurns,
3608
3469
  maxBudgetUsd: input2.maxBudgetUsd,
@@ -3616,7 +3477,7 @@ async function runAdd(opts) {
3616
3477
  });
3617
3478
  if (validation.issues.length > 0) {
3618
3479
  throw new CliError(
3619
- t6("schedule.err_invalid"),
3480
+ t27("schedule.err_invalid"),
3620
3481
  EXIT_CODES.FAILURE,
3621
3482
  renderIssues(validation.issues)
3622
3483
  );
@@ -3646,26 +3507,26 @@ async function fillInteractively(opts) {
3646
3507
  if (!missing) return opts;
3647
3508
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
3648
3509
  throw new CliError(
3649
- t6("schedule.err_missing_flags"),
3510
+ t27("schedule.err_missing_flags"),
3650
3511
  EXIT_CODES.FAILURE,
3651
- t6("schedule.err_missing_flags_hint")
3512
+ t27("schedule.err_missing_flags_hint")
3652
3513
  );
3653
3514
  }
3654
3515
  const { input: input2, select: select2 } = await import("@inquirer/prompts");
3655
3516
  const next = { ...opts };
3656
- next.name ||= await input2({ message: t6("schedule.ask_name") });
3657
- next.prompt ||= await input2({ message: t6("schedule.ask_prompt") });
3517
+ next.name ||= await input2({ message: t27("schedule.ask_name") });
3518
+ next.prompt ||= await input2({ message: t27("schedule.ask_prompt") });
3658
3519
  if (!hasTrigger) {
3659
- const at = await input2({ message: t6("schedule.ask_at"), default: "09:00" });
3520
+ const at = await input2({ message: t27("schedule.ask_at"), default: "09:00" });
3660
3521
  next.daily = at;
3661
3522
  }
3662
- next.budget ||= await input2({ message: t6("schedule.ask_budget"), default: "0.50" });
3523
+ next.budget ||= await input2({ message: t27("schedule.ask_budget"), default: "0.50" });
3663
3524
  next.permission ||= await select2({
3664
- message: t6("schedule.ask_permission"),
3525
+ message: t27("schedule.ask_permission"),
3665
3526
  choices: [
3666
- { name: t6("schedule.permission.default"), value: "default" },
3667
- { name: t6("schedule.permission.plan"), value: "plan" },
3668
- { name: t6("schedule.permission.accept_edits"), value: "acceptEdits" }
3527
+ { name: t27("schedule.permission.default"), value: "default" },
3528
+ { name: t27("schedule.permission.plan"), value: "plan" },
3529
+ { name: t27("schedule.permission.accept_edits"), value: "acceptEdits" }
3669
3530
  ]
3670
3531
  });
3671
3532
  return next;
@@ -3674,8 +3535,8 @@ function parseTrigger(opts) {
3674
3535
  const given = [opts.daily, opts.weekly, opts.monthly, opts.every, opts.once].filter(
3675
3536
  (v) => v !== void 0
3676
3537
  );
3677
- if (given.length === 0) throw new CliError(t6("schedule.err_no_trigger"), EXIT_CODES.FAILURE);
3678
- if (given.length > 1) throw new CliError(t6("schedule.err_many_triggers"), EXIT_CODES.FAILURE);
3538
+ if (given.length === 0) throw new CliError(t27("schedule.err_no_trigger"), EXIT_CODES.FAILURE);
3539
+ if (given.length > 1) throw new CliError(t27("schedule.err_many_triggers"), EXIT_CODES.FAILURE);
3679
3540
  if (opts.daily !== void 0) return { kind: "cron", cycle: "daily", at: opts.daily };
3680
3541
  if (opts.weekly !== void 0) {
3681
3542
  return {
@@ -3699,14 +3560,14 @@ function parseTrigger(opts) {
3699
3560
  return { kind: "interval", everyMinutes: parseEvery(opts.every) };
3700
3561
  }
3701
3562
  function requireAt(opts) {
3702
- if (!opts.at) throw new CliError(t6("schedule.err_at_required"), EXIT_CODES.FAILURE);
3563
+ if (!opts.at) throw new CliError(t27("schedule.err_at_required"), EXIT_CODES.FAILURE);
3703
3564
  return opts.at;
3704
3565
  }
3705
3566
  function parseWeekday(raw) {
3706
3567
  const idx = WEEKDAY_CODES.indexOf(raw.trim().toUpperCase());
3707
3568
  if (idx < 0) {
3708
3569
  throw new CliError(
3709
- t6("schedule.err_weekday", { value: raw }),
3570
+ t27("schedule.err_weekday", { value: raw }),
3710
3571
  EXIT_CODES.FAILURE,
3711
3572
  WEEKDAY_CODES.join(",")
3712
3573
  );
@@ -3718,9 +3579,9 @@ function parseEvery(raw) {
3718
3579
  const minutes = m ? Number(m[1]) * (m[2]?.toLowerCase() === "h" ? 60 : 1) : Number.NaN;
3719
3580
  if (!SCHEDULE_INTERVAL_MINUTES.includes(minutes)) {
3720
3581
  throw new CliError(
3721
- t6("schedule.err_every", { value: raw }),
3582
+ t27("schedule.err_every", { value: raw }),
3722
3583
  EXIT_CODES.FAILURE,
3723
- t6("schedule.err_every_hint", {
3584
+ t27("schedule.err_every_hint", {
3724
3585
  values: SCHEDULE_INTERVAL_MINUTES.map((n) => n < 60 ? `${n}m` : `${n / 60}h`).join(" / ")
3725
3586
  })
3726
3587
  );
@@ -3731,7 +3592,7 @@ function parsePermission(raw) {
3731
3592
  if (raw === void 0) return "default";
3732
3593
  if (!isPermissionLevel3(raw)) {
3733
3594
  throw new CliError(
3734
- t6("schedule.err_permission", { value: raw }),
3595
+ t27("schedule.err_permission", { value: raw }),
3735
3596
  EXIT_CODES.FAILURE,
3736
3597
  PERMISSION_LEVELS4.join(" / ")
3737
3598
  );
@@ -3743,7 +3604,7 @@ function parseOperations(raw) {
3743
3604
  for (const value of raw ?? []) {
3744
3605
  if (!isOperationType2(value)) {
3745
3606
  throw new CliError(
3746
- t6("schedule.err_operation", { value }),
3607
+ t27("schedule.err_operation", { value }),
3747
3608
  EXIT_CODES.FAILURE,
3748
3609
  OPERATION_TYPES4.join(" / ")
3749
3610
  );
@@ -3756,9 +3617,9 @@ function buildInput(opts, trigger, permission) {
3756
3617
  const budget = Number(opts.budget);
3757
3618
  if (!Number.isFinite(budget) || budget <= 0) {
3758
3619
  throw new CliError(
3759
- t6("schedule.err_budget", { value: opts.budget ?? "" }),
3620
+ t27("schedule.err_budget", { value: opts.budget ?? "" }),
3760
3621
  EXIT_CODES.FAILURE,
3761
- t6("schedule.err_budget_hint")
3622
+ t27("schedule.err_budget_hint")
3762
3623
  );
3763
3624
  }
3764
3625
  return {
@@ -3794,28 +3655,27 @@ function applyDefaultAllowlist(store, def, opts) {
3794
3655
  }) ?? def;
3795
3656
  }
3796
3657
  function printSummary(def) {
3797
- log8(t6("schedule.created", { id: def.id, name: def.name }));
3658
+ log8(t27("schedule.created", { id: def.id, name: def.name }));
3798
3659
  log8("");
3799
- log8(t6("schedule.created_next", { id: def.id.slice(0, 12) }));
3660
+ log8(t27("schedule.created_next", { id: def.id.slice(0, 12) }));
3800
3661
  }
3801
-
3802
3662
  // src/schedule/doctor.ts
3803
3663
  import { diagnoseSchedules, pruneSchedules, repairSchedules } from "@epoch-agent/core";
3804
- import { t as t7 } from "@epoch-agent/infra";
3664
+ import { t as t28 } from "@epoch-agent/infra";
3805
3665
  async function runDoctor(opts) {
3806
3666
  const ctx = await openSchedule();
3807
3667
  try {
3808
3668
  const deps = { store: ctx.store, backend: ctx.backend, registrar: ctx.registrar };
3809
3669
  const report = await diagnoseSchedules(deps);
3810
3670
  if (report.unsupported) {
3811
- warn2(t7("schedule.doctor_unsupported"));
3671
+ warn2(t28("schedule.doctor_unsupported"));
3812
3672
  return;
3813
3673
  }
3814
3674
  if (report.drifts.length === 0) {
3815
- log8(t7("schedule.doctor_clean", { n: report.checked }));
3675
+ log8(t28("schedule.doctor_clean", { n: report.checked }));
3816
3676
  return;
3817
3677
  }
3818
- log8(t7("schedule.doctor_found", { n: report.drifts.length }));
3678
+ log8(t28("schedule.doctor_found", { n: report.drifts.length }));
3819
3679
  for (const drift of report.drifts) {
3820
3680
  log8(
3821
3681
  ` \xB7 ${driftLabel(drift.kind)} ${drift.name ?? drift.scheduleId ?? ""} [${drift.osTaskId || "\u2014"}]${drift.detail ? `
@@ -3824,23 +3684,23 @@ async function runDoctor(opts) {
3824
3684
  }
3825
3685
  if (opts.repair !== true && opts.prune !== true) {
3826
3686
  log8("");
3827
- log8(t7("schedule.doctor_hint"));
3687
+ log8(t28("schedule.doctor_hint"));
3828
3688
  return;
3829
3689
  }
3830
3690
  if (opts.repair === true) {
3831
3691
  for (const outcome of await repairSchedules(report.drifts, deps)) {
3832
3692
  const label = outcome.drift.name ?? outcome.drift.scheduleId ?? "";
3833
- if (outcome.ok) log8(t7("schedule.doctor_repaired", { name: label }));
3693
+ if (outcome.ok) log8(t28("schedule.doctor_repaired", { name: label }));
3834
3694
  else
3835
- warn2(t7("schedule.doctor_repair_failed", { name: label, detail: outcome.detail ?? "" }));
3695
+ warn2(t28("schedule.doctor_repair_failed", { name: label, detail: outcome.detail ?? "" }));
3836
3696
  }
3837
3697
  }
3838
3698
  if (opts.prune === true) {
3839
3699
  for (const outcome of await pruneSchedules(report.drifts, deps)) {
3840
- if (outcome.ok) log8(t7("schedule.doctor_pruned", { id: outcome.drift.osTaskId }));
3700
+ if (outcome.ok) log8(t28("schedule.doctor_pruned", { id: outcome.drift.osTaskId }));
3841
3701
  else {
3842
3702
  warn2(
3843
- t7("schedule.doctor_prune_failed", {
3703
+ t28("schedule.doctor_prune_failed", {
3844
3704
  id: outcome.drift.osTaskId,
3845
3705
  detail: outcome.detail ?? ""
3846
3706
  })
@@ -3853,9 +3713,8 @@ async function runDoctor(opts) {
3853
3713
  ctx.close();
3854
3714
  }
3855
3715
  }
3856
-
3857
3716
  // src/schedule/manage.ts
3858
- import { automationLogsDir, t as t8 } from "@epoch-agent/infra";
3717
+ import { automationLogsDir, t as t29 } from "@epoch-agent/infra";
3859
3718
  async function runSetEnabled(idOrPrefix, enabled) {
3860
3719
  const ctx = await openSchedule();
3861
3720
  try {
@@ -3863,7 +3722,7 @@ async function runSetEnabled(idOrPrefix, enabled) {
3863
3722
  const outcome = await ctx.registrar.setEnabled(def, enabled);
3864
3723
  reportRegistration(outcome, false);
3865
3724
  log8(
3866
- enabled ? t8("schedule.enabled_done", { name: def.name }) : t8("schedule.disabled_done", { name: def.name })
3725
+ enabled ? t29("schedule.enabled_done", { name: def.name }) : t29("schedule.disabled_done", { name: def.name })
3867
3726
  );
3868
3727
  } finally {
3869
3728
  ctx.close();
@@ -3875,15 +3734,15 @@ async function runRemove(idOrPrefix) {
3875
3734
  const def = mustFind(ctx.store, idOrPrefix);
3876
3735
  if (def.osTaskId) reportRegistration(await ctx.registrar.unregister(def), false);
3877
3736
  ctx.store.remove(def.id);
3878
- log8(t8("schedule.removed", { name: def.name }));
3879
- log8(t8("schedule.removed_logs_kept", { dir: automationLogsDir(def.id, ctx.homeDir) }));
3737
+ log8(t29("schedule.removed", { name: def.name }));
3738
+ log8(t29("schedule.removed_logs_kept", { dir: automationLogsDir(def.id, ctx.homeDir) }));
3880
3739
  } finally {
3881
3740
  ctx.close();
3882
3741
  }
3883
3742
  }
3884
-
3885
3743
  // src/schedule/run.ts
3886
- import { t as t9 } from "@epoch-agent/infra";
3744
+ import { t as t30 } from "@epoch-agent/infra";
3745
+ import { unfixedRules } from "@epoch-agent/protocol";
3887
3746
  import { fireSchedule } from "@epoch-agent/runtime";
3888
3747
  async function runFire(scheduleId, homeDir) {
3889
3748
  const home = homeDir ?? EPOCH_HOME;
@@ -3895,9 +3754,9 @@ async function runFire(scheduleId, homeDir) {
3895
3754
  ctx.close();
3896
3755
  }
3897
3756
  if (result.missing) {
3898
- warn2(t9("schedule.fire_missing", { id: scheduleId }));
3757
+ warn2(t30("schedule.fire_missing", { id: scheduleId }));
3899
3758
  } else if (result.disabled) {
3900
- warn2(t9("schedule.fire_disabled", { id: scheduleId }));
3759
+ warn2(t30("schedule.fire_disabled", { id: scheduleId }));
3901
3760
  } else {
3902
3761
  warn2(`${statusLabel(result.status)}${result.detail ? ` ${result.detail}` : ""}`);
3903
3762
  }
@@ -3907,7 +3766,7 @@ async function runManual(idOrPrefix, opts) {
3907
3766
  const ctx = await openSchedule();
3908
3767
  try {
3909
3768
  const def = mustFind(ctx.store, idOrPrefix);
3910
- log8(t9("schedule.run_start", { name: def.name }));
3769
+ log8(t30("schedule.run_start", { name: def.name }));
3911
3770
  const result = await fireSchedule({
3912
3771
  scheduleId: def.id,
3913
3772
  homeDir: ctx.homeDir,
@@ -3923,10 +3782,10 @@ async function runManual(idOrPrefix, opts) {
3923
3782
  printPending(pending);
3924
3783
  if (opts.fix === true) {
3925
3784
  const added = applyFix(ctx.store, def.id, pending);
3926
- log8(added.length > 0 ? t9("schedule.fix_done", { n: added.length }) : t9("schedule.fix_none"));
3785
+ log8(added.length > 0 ? t30("schedule.fix_done", { n: added.length }) : t30("schedule.fix_none"));
3927
3786
  return;
3928
3787
  }
3929
- log8(t9("schedule.fix_hint", { id: def.id.slice(0, 12) }));
3788
+ log8(t30("schedule.fix_hint", { id: def.id.slice(0, 12) }));
3930
3789
  process.exitCode = result.exitCode;
3931
3790
  } finally {
3932
3791
  ctx.close();
@@ -3934,45 +3793,38 @@ async function runManual(idOrPrefix, opts) {
3934
3793
  }
3935
3794
  function printPending(pending) {
3936
3795
  log8("");
3937
- log8(t9("schedule.pending_header", { n: pending.length }));
3796
+ log8(t30("schedule.pending_header", { n: pending.length }));
3938
3797
  for (const item of pending) {
3939
- log8(` \xB7 ${t9("schedule.pending_line", { tool: item.toolName, target: item.target })}`);
3798
+ log8(` \xB7 ${t30("schedule.pending_line", { tool: item.toolName, target: item.target })}`);
3940
3799
  if (item.suggestedRule) log8(` \u2192 ${item.suggestedRule}`);
3941
3800
  }
3942
3801
  }
3943
3802
  function applyFix(store, scheduleId, pending) {
3944
3803
  const def = store.get(scheduleId);
3945
3804
  if (!def) return [];
3946
- const existing = new Set(def.allowRules);
3947
- const added = [];
3948
- for (const item of pending) {
3949
- if (!item.suggestedRule || existing.has(item.suggestedRule)) continue;
3950
- existing.add(item.suggestedRule);
3951
- added.push(item.suggestedRule);
3952
- }
3953
- if (added.length > 0) store.update(scheduleId, { allowRules: [...existing] });
3805
+ const added = unfixedRules(def.allowRules, pending);
3806
+ if (added.length > 0) store.update(scheduleId, { allowRules: [...def.allowRules, ...added] });
3954
3807
  return added;
3955
3808
  }
3956
-
3957
3809
  // src/schedule/view.ts
3958
3810
  import { nextRunAt, readRecording } from "@epoch-agent/core";
3959
- import { t as t10 } from "@epoch-agent/infra";
3811
+ import { t as t31 } from "@epoch-agent/infra";
3960
3812
  async function runList() {
3961
3813
  const ctx = await openSchedule();
3962
3814
  try {
3963
3815
  const defs = ctx.store.list();
3964
3816
  if (defs.length === 0) {
3965
- log8(t10("schedule.list_empty"));
3966
- log8(t10("schedule.list_empty_hint"));
3817
+ log8(t31("schedule.list_empty"));
3818
+ log8(t31("schedule.list_empty_hint"));
3967
3819
  return;
3968
3820
  }
3969
- log8(t10("schedule.list_header"));
3821
+ log8(t31("schedule.list_header"));
3970
3822
  for (const def of defs) {
3971
- const next = def.enabled ? formatWhen(nextRunAt(def.trigger, Date.now(), def.startDate, def.endDate) ?? void 0) : t10("schedule.disabled_mark");
3823
+ const next = def.enabled ? formatWhen(nextRunAt(def.trigger, Date.now(), def.startDate, def.endDate) ?? void 0) : t31("schedule.disabled_mark");
3972
3824
  const danger = def.permission === "bypass" ? " \u{1F534}" : "";
3973
3825
  log8(
3974
3826
  ` ${def.id.slice(0, 12)} ${def.name}${danger}
3975
- ${describeTrigger(def.trigger)} \xB7 ${t10("schedule.col_next")} ${next} \xB7 ${t10("schedule.col_last")} ${def.lastStatus ? statusLabel(def.lastStatus) : "\u2014"} \xB7 ${permissionLabel(def.permission)}`
3827
+ ${describeTrigger(def.trigger)} \xB7 ${t31("schedule.col_next")} ${next} \xB7 ${t31("schedule.col_last")} ${def.lastStatus ? statusLabel(def.lastStatus) : "\u2014"} \xB7 ${permissionLabel(def.permission)}`
3976
3828
  );
3977
3829
  }
3978
3830
  } finally {
@@ -3984,44 +3836,44 @@ async function runShow(idOrPrefix) {
3984
3836
  try {
3985
3837
  const def = mustFind(ctx.store, idOrPrefix);
3986
3838
  log8(`${def.name} (${def.id})`);
3987
- log8(` ${t10("schedule.field_enabled")}: ${def.enabled ? t10("schedule.yes") : t10("schedule.no")}`);
3988
- log8(` ${t10("schedule.field_trigger")}: ${describeTrigger(def.trigger)}`);
3839
+ log8(` ${t31("schedule.field_enabled")}: ${def.enabled ? t31("schedule.yes") : t31("schedule.no")}`);
3840
+ log8(` ${t31("schedule.field_trigger")}: ${describeTrigger(def.trigger)}`);
3989
3841
  if (def.startDate ?? def.endDate) {
3990
- log8(` ${t10("schedule.field_window")}: ${def.startDate ?? "\u2014"} .. ${def.endDate ?? "\u2014"}`);
3842
+ log8(` ${t31("schedule.field_window")}: ${def.startDate ?? "\u2014"} .. ${def.endDate ?? "\u2014"}`);
3991
3843
  }
3992
3844
  log8(
3993
- ` ${t10("schedule.field_next")}: ${formatWhen(
3845
+ ` ${t31("schedule.field_next")}: ${formatWhen(
3994
3846
  nextRunAt(def.trigger, Date.now(), def.startDate, def.endDate) ?? void 0
3995
3847
  )}`
3996
3848
  );
3997
- log8(` ${t10("schedule.field_workdir")}: ${def.workDir}`);
3998
- if (def.model) log8(` ${t10("schedule.field_model")}: ${def.model}`);
3999
- log8(` ${t10("schedule.field_permission")}: ${permissionLabel(def.permission)}`);
3849
+ log8(` ${t31("schedule.field_workdir")}: ${def.workDir}`);
3850
+ if (def.model) log8(` ${t31("schedule.field_model")}: ${def.model}`);
3851
+ log8(` ${t31("schedule.field_permission")}: ${permissionLabel(def.permission)}`);
4000
3852
  log8(
4001
- ` ${t10("schedule.field_limits")}: ` + t10("schedule.limits_value", {
3853
+ ` ${t31("schedule.field_limits")}: ` + t31("schedule.limits_value", {
4002
3854
  turns: def.maxTurns,
4003
3855
  budget: def.maxBudgetUsd.toFixed(2),
4004
3856
  minutes: Math.round(def.timeoutMs / 6e4)
4005
3857
  })
4006
3858
  );
4007
- log8(` ${t10("schedule.field_backend")}: ${def.osBackend} ${def.osTaskId || "\u2014"}`);
4008
- log8(` ${t10("schedule.field_prompt")}:`);
3859
+ log8(` ${t31("schedule.field_backend")}: ${def.osBackend} ${def.osTaskId || "\u2014"}`);
3860
+ log8(` ${t31("schedule.field_prompt")}:`);
4009
3861
  for (const line of def.prompt.split("\n")) log8(` ${line}`);
4010
3862
  log8("");
4011
3863
  printAllowlist(def);
4012
3864
  log8("");
4013
- log8(t10("schedule.field_last_run", { when: formatWhen(def.lastRunAt) }));
3865
+ log8(t31("schedule.field_last_run", { when: formatWhen(def.lastRunAt) }));
4014
3866
  } finally {
4015
3867
  ctx.close();
4016
3868
  }
4017
3869
  }
4018
3870
  function printAllowlist(def) {
4019
3871
  if (def.permission === "bypass") {
4020
- log8(t10("schedule.allowlist_bypass"));
3872
+ log8(t31("schedule.allowlist_bypass"));
4021
3873
  return;
4022
3874
  }
4023
3875
  if (def.permission === "plan") {
4024
- log8(t10("schedule.allowlist_readonly"));
3876
+ log8(t31("schedule.allowlist_readonly"));
4025
3877
  return;
4026
3878
  }
4027
3879
  const lines = [
@@ -4029,9 +3881,9 @@ function printAllowlist(def) {
4029
3881
  ...def.allowOperations.map((v) => ` ${v}`),
4030
3882
  ...def.allowRules.map((v) => ` ${v}`)
4031
3883
  ];
4032
- log8(t10("schedule.allowlist_header"));
4033
- log8(lines.length > 0 ? lines.join("\n") : ` ${t10("schedule.allowlist_empty")}`);
4034
- log8(t10("schedule.allowlist_scope_note"));
3884
+ log8(t31("schedule.allowlist_header"));
3885
+ log8(lines.length > 0 ? lines.join("\n") : ` ${t31("schedule.allowlist_empty")}`);
3886
+ log8(t31("schedule.allowlist_scope_note"));
4035
3887
  }
4036
3888
  async function runLogs(idOrPrefix, limit, tail) {
4037
3889
  const ctx = await openSchedule();
@@ -4039,7 +3891,7 @@ async function runLogs(idOrPrefix, limit, tail) {
4039
3891
  const def = mustFind(ctx.store, idOrPrefix);
4040
3892
  const runs = ctx.store.listRuns(def.id, limit);
4041
3893
  if (runs.length === 0) {
4042
- log8(t10("schedule.logs_empty"));
3894
+ log8(t31("schedule.logs_empty"));
4043
3895
  return;
4044
3896
  }
4045
3897
  for (const run of runs) printRun(run);
@@ -4050,26 +3902,26 @@ async function runLogs(idOrPrefix, limit, tail) {
4050
3902
  }
4051
3903
  function printRun(run) {
4052
3904
  log8(
4053
- ` ${formatWhen(run.startedAt)} ${statusLabel(run.status)} ` + t10("schedule.run_meta", {
3905
+ ` ${formatWhen(run.startedAt)} ${statusLabel(run.status)} ` + t31("schedule.run_meta", {
4054
3906
  turns: run.turns,
4055
3907
  cost: run.costUsd.toFixed(4),
4056
- manual: run.manual ? t10("schedule.run_manual") : ""
3908
+ manual: run.manual ? t31("schedule.run_manual") : ""
4057
3909
  })
4058
3910
  );
4059
3911
  if (run.reason) log8(` ${run.reason}`);
4060
3912
  for (const pending of run.pendingApprovals) {
4061
3913
  log8(
4062
- ` \u26A0 ${t10("schedule.pending_line", { tool: pending.toolName, target: pending.target })}`
3914
+ ` \u26A0 ${t31("schedule.pending_line", { tool: pending.toolName, target: pending.target })}`
4063
3915
  );
4064
3916
  if (pending.suggestedRule) {
4065
- log8(` ${t10("schedule.pending_fix", { rule: pending.suggestedRule })}`);
3917
+ log8(` ${t31("schedule.pending_fix", { rule: pending.suggestedRule })}`);
4066
3918
  }
4067
3919
  }
4068
3920
  }
4069
3921
  function printRecording(run) {
4070
3922
  if (!run.logPath) return;
4071
3923
  log8("");
4072
- log8(t10("schedule.logs_recording", { path: run.logPath }));
3924
+ log8(t31("schedule.logs_recording", { path: run.logPath }));
4073
3925
  for (const envelope of readRecording(run.logPath)) {
4074
3926
  const event = envelope.event;
4075
3927
  if (event.type === "text-delta" && event.text) process.stdout.write(event.text);
@@ -4078,28 +3930,26 @@ function printRecording(run) {
4078
3930
  }
4079
3931
  process.stdout.write("\n");
4080
3932
  }
4081
-
4082
3933
  // src/commands/schedule.ts
4083
3934
  var DEFAULT_LOG_LIMIT = 10;
4084
3935
  function registerScheduleCommand(program2) {
4085
- const cmd = program2.command("schedule").description(t11("cli.schedule.summary"));
4086
- cmd.command("add").description(t11("cli.schedule.add")).option("--name <name>", t11("cli.schedule.opt_name")).option("--prompt <text>", t11("cli.schedule.opt_prompt")).option("--daily <HH:mm>", t11("cli.schedule.opt_daily")).option("--weekly <days>", t11("cli.schedule.opt_weekly")).option("--monthly <days>", t11("cli.schedule.opt_monthly")).option("--every <interval>", t11("cli.schedule.opt_every")).option("--once <YYYY-MM-DD>", t11("cli.schedule.opt_once")).option("--at <HH:mm>", t11("cli.schedule.opt_at")).option("--start <YYYY-MM-DD>", t11("cli.schedule.opt_start")).option("--end <YYYY-MM-DD>", t11("cli.schedule.opt_end")).option("--workdir <dir>", t11("cli.schedule.opt_workdir")).option("--model <model>", t11("cli.schedule.opt_model")).option("--permission <level>", t11("cli.schedule.opt_permission")).option("--allow-tool <name>", t11("cli.schedule.opt_allow_tool"), collect3, []).option("--allow-operation <type>", t11("cli.schedule.opt_allow_operation"), collect3, []).option("--allow-rule <rule>", t11("cli.schedule.opt_allow_rule"), collect3, []).option("--max-turns <n>", t11("cli.schedule.opt_max_turns")).option("--budget <usd>", t11("cli.schedule.opt_budget")).option("--timeout <minutes>", t11("cli.schedule.opt_timeout")).option("--disabled", t11("cli.schedule.opt_disabled")).option("--i-understand-bypass", t11("cli.schedule.opt_bypass_ack")).action((opts) => runAdd(opts));
4087
- cmd.command("list", { isDefault: true }).description(t11("cli.schedule.list")).action(() => runList());
4088
- cmd.command("show").description(t11("cli.schedule.show")).argument("<id>", t11("cli.schedule.arg_id")).action((id) => runShow(id));
4089
- cmd.command("enable").description(t11("cli.schedule.enable")).argument("<id>", t11("cli.schedule.arg_id")).action((id) => runSetEnabled(id, true));
4090
- cmd.command("disable").description(t11("cli.schedule.disable")).argument("<id>", t11("cli.schedule.arg_id")).action((id) => runSetEnabled(id, false));
4091
- cmd.command("rm").alias("remove").description(t11("cli.schedule.rm")).argument("<id>", t11("cli.schedule.arg_id")).action((id) => runRemove(id));
4092
- cmd.command("run").description(t11("cli.schedule.run")).argument("<id>", t11("cli.schedule.arg_id")).option("--fix", t11("cli.schedule.opt_fix")).action((id, opts) => runManual(id, opts));
4093
- cmd.command("logs").description(t11("cli.schedule.logs")).argument("<id>", t11("cli.schedule.arg_id")).option("-n, --limit <n>", t11("cli.schedule.opt_limit"), String(DEFAULT_LOG_LIMIT)).option("--tail", t11("cli.schedule.opt_tail")).action(
3936
+ const cmd = program2.command("schedule").description(t32("cli.schedule.summary"));
3937
+ cmd.command("add").description(t32("cli.schedule.add")).option("--name <name>", t32("cli.schedule.opt_name")).option("--prompt <text>", t32("cli.schedule.opt_prompt")).option("--daily <HH:mm>", t32("cli.schedule.opt_daily")).option("--weekly <days>", t32("cli.schedule.opt_weekly")).option("--monthly <days>", t32("cli.schedule.opt_monthly")).option("--every <interval>", t32("cli.schedule.opt_every")).option("--once <YYYY-MM-DD>", t32("cli.schedule.opt_once")).option("--at <HH:mm>", t32("cli.schedule.opt_at")).option("--start <YYYY-MM-DD>", t32("cli.schedule.opt_start")).option("--end <YYYY-MM-DD>", t32("cli.schedule.opt_end")).option("--workdir <dir>", t32("cli.schedule.opt_workdir")).option("--model <model>", t32("cli.schedule.opt_model")).option("--permission <level>", t32("cli.schedule.opt_permission")).option("--allow-tool <name>", t32("cli.schedule.opt_allow_tool"), collect3, []).option("--allow-operation <type>", t32("cli.schedule.opt_allow_operation"), collect3, []).option("--allow-rule <rule>", t32("cli.schedule.opt_allow_rule"), collect3, []).option("--max-turns <n>", t32("cli.schedule.opt_max_turns")).option("--budget <usd>", t32("cli.schedule.opt_budget")).option("--timeout <minutes>", t32("cli.schedule.opt_timeout")).option("--disabled", t32("cli.schedule.opt_disabled")).option("--i-understand-bypass", t32("cli.schedule.opt_bypass_ack")).action((opts) => runAdd(opts));
3938
+ cmd.command("list", { isDefault: true }).description(t32("cli.schedule.list")).action(() => runList());
3939
+ cmd.command("show").description(t32("cli.schedule.show")).argument("<id>", t32("cli.schedule.arg_id")).action((id) => runShow(id));
3940
+ cmd.command("enable").description(t32("cli.schedule.enable")).argument("<id>", t32("cli.schedule.arg_id")).action((id) => runSetEnabled(id, true));
3941
+ cmd.command("disable").description(t32("cli.schedule.disable")).argument("<id>", t32("cli.schedule.arg_id")).action((id) => runSetEnabled(id, false));
3942
+ cmd.command("rm").alias("remove").description(t32("cli.schedule.rm")).argument("<id>", t32("cli.schedule.arg_id")).action((id) => runRemove(id));
3943
+ cmd.command("run").description(t32("cli.schedule.run")).argument("<id>", t32("cli.schedule.arg_id")).option("--fix", t32("cli.schedule.opt_fix")).action((id, opts) => runManual(id, opts));
3944
+ cmd.command("logs").description(t32("cli.schedule.logs")).argument("<id>", t32("cli.schedule.arg_id")).option("-n, --limit <n>", t32("cli.schedule.opt_limit"), String(DEFAULT_LOG_LIMIT)).option("--tail", t32("cli.schedule.opt_tail")).action(
4094
3945
  (id, opts) => runLogs(id, Number(opts.limit ?? DEFAULT_LOG_LIMIT), opts.tail === true)
4095
3946
  );
4096
- cmd.command("fire").description(t11("cli.schedule.fire")).argument("<id>", t11("cli.schedule.arg_id")).option("--home <dir>", t11("cli.schedule.opt_home")).action((id, opts) => runFire(id, opts.home));
4097
- cmd.command("doctor").description(t11("cli.schedule.doctor")).option("--repair", t11("cli.schedule.opt_repair")).option("--prune", t11("cli.schedule.opt_prune")).action((opts) => runDoctor(opts));
3947
+ cmd.command("fire").description(t32("cli.schedule.fire")).argument("<id>", t32("cli.schedule.arg_id")).option("--home <dir>", t32("cli.schedule.opt_home")).action((id, opts) => runFire(id, opts.home));
3948
+ cmd.command("doctor").description(t32("cli.schedule.doctor")).option("--repair", t32("cli.schedule.opt_repair")).option("--prune", t32("cli.schedule.opt_prune")).action((opts) => runDoctor(opts));
4098
3949
  }
4099
3950
  function collect3(value, previous) {
4100
3951
  return [...previous, value];
4101
3952
  }
4102
-
4103
3953
  // src/commands/status.ts
4104
3954
  import { existsSync as existsSync7 } from "fs";
4105
3955
  import { statfs } from "fs/promises";
@@ -4110,13 +3960,21 @@ import {
4110
3960
  SANDBOX_COVERS,
4111
3961
  SANDBOX_EXCLUDES
4112
3962
  } from "@epoch-agent/core";
4113
- import { confine, resolveProfile, t as t12 } from "@epoch-agent/infra";
3963
+ import {
3964
+ allJobs,
3965
+ confine,
3966
+ resolveProfile,
3967
+ t as t34
3968
+ } from "@epoch-agent/infra";
4114
3969
  import { hasFailure } from "@epoch-agent/protocol";
4115
-
3970
+ import {
3971
+ classifyTerminal,
3972
+ probeShiftEnter
3973
+ } from "@epoch-agent/tui";
4116
3974
  // src/statusline.ts
4117
3975
  import { execFile as execFile2 } from "child_process";
4118
3976
  import { sanitizeToolOutput } from "@epoch-agent/core";
4119
- import { shellSpawnArgs } from "@epoch-agent/infra";
3977
+ import { shellSpawnArgs, t as t33 } from "@epoch-agent/infra";
4120
3978
  var TIMEOUT_MS2 = 2e3;
4121
3979
  var MAX_WIDTH = 60;
4122
3980
  var MAX_BUFFER = 64 * 1024;
@@ -4137,7 +3995,6 @@ function runStatusLineCommand(command, cwd) {
4137
3995
  timeout: TIMEOUT_MS2,
4138
3996
  maxBuffer: MAX_BUFFER,
4139
3997
  encoding: "utf-8",
4140
- // 不弹黑框:Windows 上每 5 秒闪一个控制台窗口是没法用的
4141
3998
  windowsHide: true
4142
3999
  },
4143
4000
  (err2, stdout) => {
@@ -4145,7 +4002,7 @@ function runStatusLineCommand(command, cwd) {
4145
4002
  const killed = err2.killed === true;
4146
4003
  settle({
4147
4004
  text: null,
4148
- error: killed ? `\u8D85\u8FC7 ${TIMEOUT_MS2}ms \u6CA1\u8FD4\u56DE` : err2.message.trim()
4005
+ error: killed ? t33("statusline.timeout", { ms: TIMEOUT_MS2 }) : err2.message.trim()
4149
4006
  });
4150
4007
  return;
4151
4008
  }
@@ -4159,11 +4016,10 @@ async function probeStatusLine(config, cwd) {
4159
4016
  if (!command) return null;
4160
4017
  const result = await runStatusLineCommand(command, cwd);
4161
4018
  if (result.text === null) {
4162
- return { command, ok: false, detail: result.error ?? "\u6CA1\u6709\u8F93\u51FA" };
4019
+ return { command, ok: false, detail: result.error ?? t33("statusline.no_output") };
4163
4020
  }
4164
4021
  return { command, ok: true, detail: result.text };
4165
4022
  }
4166
-
4167
4023
  // src/commands/status.ts
4168
4024
  var log9 = (msg) => {
4169
4025
  process.stdout.write(msg + "\n");
@@ -4193,10 +4049,10 @@ async function describeDisk(path = ".") {
4193
4049
  }
4194
4050
  }
4195
4051
  function describeConfig() {
4196
- if (!existsSync7(CONFIG_PATH)) return "\u4E0D\u5B58\u5728";
4052
+ if (!existsSync7(CONFIG_PATH)) return t34("cli.status.config_absent");
4197
4053
  loadConfig3();
4198
4054
  const n = getConfigIssues().length;
4199
- return n === 0 ? "\u5B58\u5728" : `\u5B58\u5728\uFF0C\u4F46\u6709 ${n} \u5904\u95EE\u9898\uFF08\u8DD1 epoch doctor \u770B\u662F\u54EA\u4E9B\u5B57\u6BB5\uFF09`;
4055
+ return n === 0 ? t34("cli.status.config_present") : t34("cli.status.config_present_with_issues", { count: n });
4200
4056
  }
4201
4057
  async function describeSecretBackend() {
4202
4058
  try {
@@ -4204,91 +4060,318 @@ async function describeSecretBackend() {
4204
4060
  const store = await createSecretStore();
4205
4061
  return store.encrypted ? store.detail : `\u26A0 ${store.detail}`;
4206
4062
  } catch (err2) {
4207
- return `\u63A2\u6D4B\u5931\u8D25\uFF08${err2 instanceof Error ? err2.message : String(err2)}\uFF09`;
4063
+ return t34("cli.status.secret_probe_failed", {
4064
+ message: err2 instanceof Error ? err2.message : String(err2)
4065
+ });
4208
4066
  }
4209
4067
  }
4210
4068
  function workspaceLines(rt) {
4211
- const lines = [` \u9879\u76EE\u6839: ${rt.workspace.root}`];
4069
+ const lines = [` ${t34("cli.doctor.workspace_root", { root: rt.workspace.root })}`];
4212
4070
  lines.push(
4213
- rt.trusted ? " \u4FE1\u4EFB\u72B6\u6001: \u5DF2\u4FE1\u4EFB\uFF08\u9879\u76EE\u6307\u4EE4\u6587\u4EF6\u4F1A\u52A0\u8F7D\uFF09" : " \u4FE1\u4EFB\u72B6\u6001: \u672A\u4FE1\u4EFB\uFF08EPOCH.md / AGENTS.md / CLAUDE.md \u4E0D\u4F1A\u52A0\u8F7D\uFF0Cepoch trust add \u53EF\u6388\u6743\uFF09"
4071
+ ` ${rt.trusted ? t34("cli.doctor.workspace_trusted") : t34("cli.doctor.workspace_untrusted")}`
4214
4072
  );
4215
4073
  lines.push(
4216
- rt.workspace.extra.length === 0 ? " \u989D\u5916\u76EE\u5F55: \u65E0\uFF08--add-dir \u53EF\u52A0\uFF09" : ` \u989D\u5916\u76EE\u5F55: ${rt.workspace.extra.length} \u4E2A`
4074
+ ` ${rt.workspace.extra.length === 0 ? t34("cli.doctor.workspace_no_extra") : t34("cli.doctor.workspace_extra", { count: rt.workspace.extra.length })}`
4217
4075
  );
4218
4076
  for (const dir of rt.workspace.extra) lines.push(` \xB7 ${dir}`);
4219
4077
  return lines;
4220
4078
  }
4221
- function sandboxLines(cwd) {
4079
+ function sandboxLines(cwd, sandbox) {
4222
4080
  const iso = describeIsolation();
4223
4081
  const lines = [`
4224
- ${t12("cli.doctor.sandbox_head")}`];
4082
+ ${t34("cli.doctor.sandbox_head")}`];
4225
4083
  if (iso.backend === "none") {
4226
4084
  lines.push(` ${STATUS_MARK.skipped} ${iso.detail}`);
4227
- lines.push(` ${STATUS_MARK.warn} ${t12("cli.doctor.sandbox_absent_tools")}`);
4085
+ lines.push(
4086
+ ` ${STATUS_MARK.warn} ${t34(
4087
+ iso.reason === "platform-unsupported" ? "cli.doctor.sandbox_absent_by_design" : "cli.doctor.sandbox_absent_tools"
4088
+ )}`
4089
+ );
4228
4090
  return lines;
4229
4091
  }
4230
4092
  lines.push(
4231
- ` ${STATUS_MARK.ok} ${t12("cli.doctor.sandbox_backend", { backend: iso.backend, platform: iso.platform })}`
4093
+ ` ${STATUS_MARK.ok} ${t34("cli.doctor.sandbox_backend", { backend: iso.backend, platform: iso.platform })}`
4232
4094
  );
4233
- lines.push(` \xB7 ${t12("cli.doctor.sandbox_code_exec", { detail: iso.detail })}`);
4095
+ lines.push(` \xB7 ${t34("cli.doctor.sandbox_code_exec", { detail: iso.detail })}`);
4234
4096
  const probe = (mode) => confine("/bin/sh", ["-c", "true"], { mode, workspaceRoot: cwd, allowNetwork: true });
4235
4097
  for (const mode of ["workspace-write", "read-only"]) {
4236
4098
  const c = probe(mode);
4237
4099
  if (!c.confined) continue;
4238
- lines.push(` \xB7 ${t12("cli.doctor.sandbox_terminal", { mode, enforcement: c.enforcement })}`);
4239
- if (c.enforcement === "partial") lines.push(` ${t12("cli.doctor.sandbox_partial_devnull")}`);
4100
+ lines.push(` \xB7 ${t34("cli.doctor.sandbox_terminal", { mode, enforcement: c.enforcement })}`);
4101
+ if (c.enforcement === "partial") lines.push(` ${t34("cli.doctor.sandbox_partial_devnull")}`);
4240
4102
  }
4241
4103
  const writable = probe("workspace-write");
4242
4104
  if (writable.confined) {
4243
- lines.push(` \xB7 ${t12("cli.doctor.sandbox_writable")}`);
4105
+ lines.push(` \xB7 ${t34("cli.doctor.sandbox_writable")}`);
4244
4106
  for (const dir of writable.writableDirs) lines.push(` \xB7 ${dir}`);
4245
4107
  }
4246
- lines.push(` \xB7 ${t12("cli.doctor.sandbox_covers", { tools: SANDBOX_COVERS.join(", ") })}`);
4247
4108
  lines.push(
4248
- SANDBOX_EXCLUDES.length === 0 ? ` ${STATUS_MARK.ok} ${t12("cli.doctor.sandbox_excludes_none")}` : ` ${STATUS_MARK.warn} ${t12("cli.doctor.sandbox_excludes", { tools: SANDBOX_EXCLUDES.join(", ") })}`
4109
+ sandbox?.terminal === false ? ` ${STATUS_MARK.warn} ${t34("cli.doctor.sandbox_switch_off")}` : ` ${STATUS_MARK.ok} ${t34("cli.doctor.sandbox_switch_on")}`
4249
4110
  );
4111
+ lines.push(` \xB7 ${t34("cli.doctor.sandbox_covers", { tools: SANDBOX_COVERS.join(", ") })}`);
4112
+ lines.push(
4113
+ SANDBOX_EXCLUDES.length === 0 ? ` ${STATUS_MARK.ok} ${t34("cli.doctor.sandbox_excludes_none")}` : ` ${STATUS_MARK.warn} ${t34("cli.doctor.sandbox_excludes", { tools: SANDBOX_EXCLUDES.join(", ") })}`
4114
+ );
4115
+ return lines;
4116
+ }
4117
+ function ftsLines(health) {
4118
+ const lines = [`
4119
+ ${t34("cli.doctor.fts_head")}`];
4120
+ const indexed = health.tables.map((tb) => `${tb.table} ${tb.rows}`).join(" \xB7 ");
4121
+ lines.push(` \xB7 ${t34("cli.doctor.fts_rows", { messages: health.messages, indexed })}`);
4122
+ const behind = health.tables.filter((tb) => tb.rows !== health.messages);
4123
+ lines.push(
4124
+ behind.length === 0 ? ` ${STATUS_MARK.ok} ${t34("cli.doctor.fts_in_sync")}` : (
4125
+ ` ${STATUS_MARK.warn} ${t34("cli.doctor.fts_out_of_sync", {
4126
+ tables: behind.map((tb) => `${tb.table} (${tb.rows - health.messages})`).join(", ")
4127
+ })}`
4128
+ )
4129
+ );
4130
+ lines.push(
4131
+ health.missingTriggers.length === 0 ? ` ${STATUS_MARK.ok} ${t34("cli.doctor.fts_triggers_ok")}` : ` ${STATUS_MARK.failed} ${t34("cli.doctor.fts_triggers_missing", {
4132
+ triggers: health.missingTriggers.join(", ")
4133
+ })}`
4134
+ );
4135
+ for (const tb of health.tables) {
4136
+ if (tb.error !== null) {
4137
+ lines.push(
4138
+ ` ${STATUS_MARK.failed} ${t34("cli.doctor.fts_corrupt", { table: tb.table, detail: tb.error })}`
4139
+ );
4140
+ }
4141
+ if (tb.drift !== null) {
4142
+ lines.push(
4143
+ ` ${STATUS_MARK.warn} ${t34("cli.doctor.fts_drift", { table: tb.table, detail: tb.drift })}`
4144
+ );
4145
+ }
4146
+ }
4147
+ if (health.tables.some((tb) => !tb.external)) {
4148
+ lines.push(` ${STATUS_MARK.warn} ${t34("cli.doctor.fts_legacy_schema")}`);
4149
+ }
4150
+ if (health.indexBytes !== void 0) {
4151
+ lines.push(` \xB7 ${t34("cli.doctor.fts_size", { size: formatBytes(health.indexBytes) })}`);
4152
+ }
4153
+ if (health.space?.fileBytes !== void 0) {
4154
+ lines.push(
4155
+ ` \xB7 ${t34("cli.doctor.fts_space", {
4156
+ file: formatBytes(health.space.fileBytes),
4157
+ free: formatBytes(health.space.freeBytes)
4158
+ })}`
4159
+ );
4160
+ lines.push(` \xB7 ${t34("cli.doctor.fts_reclaim_hint")}`);
4161
+ }
4250
4162
  return lines;
4251
4163
  }
4164
+ function reclaimLines(report) {
4165
+ const lines = [`
4166
+ ${t34("cli.doctor.reclaim_head")}`];
4167
+ const secs = (ms) => `${(ms / 1e3).toFixed(1)}s`;
4168
+ lines.push(
4169
+ ` ${STATUS_MARK.ok} ${t34("cli.doctor.reclaim_optimize", {
4170
+ before: formatBytes(report.before.freeBytes),
4171
+ after: formatBytes(report.afterOptimize.freeBytes),
4172
+ ms: secs(report.optimizeMs)
4173
+ })}`
4174
+ );
4175
+ if (report.vacuumed) {
4176
+ lines.push(
4177
+ ` ${STATUS_MARK.ok} ${t34("cli.doctor.reclaim_vacuum", {
4178
+ before: formatBytes(report.before.pageBytes),
4179
+ after: formatBytes(report.after.pageBytes),
4180
+ ms: secs(report.vacuumMs)
4181
+ })}`
4182
+ );
4183
+ const saved = report.before.pageBytes - report.after.pageBytes;
4184
+ lines.push(
4185
+ ` ${STATUS_MARK.ok} ${t34("cli.doctor.reclaim_total", {
4186
+ saved: formatBytes(saved),
4187
+ pct: `${(saved / Math.max(1, report.before.pageBytes) * 100).toFixed(1)}%`
4188
+ })}`
4189
+ );
4190
+ } else if (report.vacuumError !== null) {
4191
+ lines.push(
4192
+ ` ${STATUS_MARK.warn} ${t34("cli.doctor.reclaim_vacuum_skipped", {
4193
+ detail: report.vacuumError
4194
+ })}`
4195
+ );
4196
+ }
4197
+ return lines;
4198
+ }
4199
+ var HOOK_SOURCE_LABEL = {
4200
+ plugin: "cli.doctor.hook_source_plugin",
4201
+ legacy: "cli.doctor.hook_source_legacy",
4202
+ user: "cli.doctor.hook_source_user",
4203
+ "claude-user": "cli.doctor.hook_source_claude_user",
4204
+ project: "cli.doctor.hook_source_project",
4205
+ "claude-project": "cli.doctor.hook_source_claude_project",
4206
+ "claude-project-local": "cli.doctor.hook_source_claude_project_local"
4207
+ };
4208
+ function hookLines(rt) {
4209
+ const { sources } = rt.hook;
4210
+ if (sources.length === 0) return [];
4211
+ const lines = [`
4212
+ ${t34("cli.doctor.hook_head")}`];
4213
+ for (const source of sources) {
4214
+ const label = t34(HOOK_SOURCE_LABEL[source.kind]);
4215
+ if (source.skipped) {
4216
+ lines.push(` ${STATUS_MARK.skipped} ${label}: ${source.path}`);
4217
+ lines.push(` ${t34("cli.doctor.hook_skipped")}`);
4218
+ continue;
4219
+ }
4220
+ const name = source.plugin === void 0 ? label : `${label} ${source.plugin}`;
4221
+ lines.push(
4222
+ ` ${STATUS_MARK.ok} ${name}: ${source.path}${t34("cli.doctor.hook_count", {
4223
+ n: source.count
4224
+ })}`
4225
+ );
4226
+ }
4227
+ return lines;
4228
+ }
4229
+ var JOB_KIND_LABEL = {
4230
+ command: "cli.doctor.jobs_kind_command",
4231
+ shell: "cli.doctor.jobs_kind_shell",
4232
+ agent: "cli.doctor.jobs_kind_agent"
4233
+ };
4234
+ function jobLines(jobs) {
4235
+ const lines = [`
4236
+ ${t34("cli.doctor.jobs_head")}`];
4237
+ const running = jobs.filter((job) => job.status === "running");
4238
+ if (running.length === 0) {
4239
+ lines.push(` ${STATUS_MARK.ok} ${t34("cli.doctor.jobs_none")}`);
4240
+ return lines;
4241
+ }
4242
+ lines.push(` ${STATUS_MARK.warn} ${t34("cli.doctor.jobs_running", { n: running.length })}`);
4243
+ for (const job of running) {
4244
+ lines.push(
4245
+ ` \xB7 ${t34("cli.doctor.jobs_line", {
4246
+ kind: t34(JOB_KIND_LABEL[job.kind]),
4247
+ id: job.id,
4248
+ label: job.label
4249
+ })}`
4250
+ );
4251
+ }
4252
+ lines.push(` \xB7 ${t34("cli.doctor.jobs_lifetime")}`);
4253
+ return lines;
4254
+ }
4255
+ function terminalLines(result, terminal) {
4256
+ const head = `
4257
+ ${t34("cli.doctor.terminal_head")}`;
4258
+ if (!result.ok) {
4259
+ if (result.failure.kind === "no-tty") {
4260
+ return [
4261
+ head,
4262
+ ` ${STATUS_MARK.warn} ${t34("cli.doctor.terminal_no_tty")}`,
4263
+ ` \xB7 ${t34("cli.doctor.terminal_alternatives")}`
4264
+ ];
4265
+ }
4266
+ return [
4267
+ head,
4268
+ ` ${STATUS_MARK.warn} ${t34("cli.doctor.terminal_timeout", { n: result.failure.timeoutMs / 1e3 })}`
4269
+ ];
4270
+ }
4271
+ switch (result.sample.kind) {
4272
+ case "shift-enter":
4273
+ return [head, ` ${STATUS_MARK.ok} ${t34("cli.doctor.terminal_shift_enter")}`];
4274
+ case "enter-only":
4275
+ if (terminal?.kind === "apple-terminal") {
4276
+ return [
4277
+ head,
4278
+ ` ${STATUS_MARK.warn} ${t34("cli.doctor.terminal_apple")}`,
4279
+ ` \xB7 ${t34("cli.doctor.terminal_alternatives")}`
4280
+ ];
4281
+ }
4282
+ return [
4283
+ head,
4284
+ ` ${STATUS_MARK.warn} ${t34("cli.doctor.terminal_enter_only")}`,
4285
+ ` \xB7 ${t34("cli.doctor.terminal_alternatives")}`,
4286
+ ` \xB7 ${t34("cli.doctor.terminal_setup_hint")}`
4287
+ ];
4288
+ case "other":
4289
+ return [
4290
+ head,
4291
+ ` ${STATUS_MARK.warn} ${t34("cli.doctor.terminal_other", { hex: result.sample.hex })}`
4292
+ ];
4293
+ case "interrupted":
4294
+ return [head, ` \xB7 ${t34("cli.doctor.terminal_interrupted")}`];
4295
+ }
4296
+ }
4252
4297
  async function statusLineLines(config, cwd) {
4253
4298
  const probe = await probeStatusLine(config, cwd);
4254
4299
  if (!probe) return [];
4255
4300
  return [
4256
- "\n\u81EA\u5B9A\u4E49\u72B6\u6001\u680F:",
4257
- ` \u547D\u4EE4: ${probe.command}`,
4258
- probe.ok ? ` \u2713 \u663E\u793A: ${probe.detail}` : ` \u26A0 \u8DD1\u4E0D\u8D77\u6765\uFF08\u72B6\u6001\u680F\u4F1A\u7559\u7A7A\uFF09: ${probe.detail}`
4301
+ `
4302
+ ${t34("cli.doctor.status_line_head")}`,
4303
+ ` ${t34("cli.doctor.status_line_command", { command: probe.command })}`,
4304
+ ` ${probe.ok ? t34("cli.doctor.status_line_ok", { detail: probe.detail }) : t34("cli.doctor.status_line_broken", { detail: probe.detail })}`
4259
4305
  ];
4260
4306
  }
4261
4307
  function registerStatusCommands(program2) {
4262
- program2.command("status").description("\u663E\u793A\u7CFB\u7EDF\u72B6\u6001").action(async () => {
4308
+ program2.command("status").description(t34("cli.status.cmd_root")).action(async () => {
4263
4309
  log9(
4264
4310
  [
4265
- `\u7248\u672C: ${VERSION}`,
4311
+ t34("cli.status.version", { version: VERSION }),
4266
4312
  `Node: ${process.version} (${process.platform}/${process.arch})`,
4267
- `\u914D\u7F6E\u76EE\u5F55: ${EPOCH_HOME}`,
4313
+ t34("cli.status.home", { path: EPOCH_HOME }),
4268
4314
  `Profile: ${resolveProfile()}`,
4269
- `\u51ED\u8BC1: ${hasCredentials() ? "\u5DF2\u914D\u7F6E" : "\u672A\u914D\u7F6E"}`,
4270
- `\u51ED\u636E\u5B58\u50A8: ${await describeSecretBackend()}`,
4315
+ t34("cli.status.credentials", {
4316
+ state: hasCredentials() ? t34("cli.status.credentials_set") : t34("cli.status.credentials_unset")
4317
+ }),
4318
+ t34("cli.status.secret_backend", { detail: await describeSecretBackend() }),
4271
4319
  `config.yaml: ${describeConfig()}`,
4272
- `.env: ${existsSync7(ENV_PATH) ? "\u5B58\u5728" : "\u4E0D\u5B58\u5728"}`
4320
+ `.env: ${existsSync7(ENV_PATH) ? t34("cli.status.env_present") : t34("cli.status.env_absent")}`
4273
4321
  ].join("\n")
4274
4322
  );
4275
4323
  });
4276
- program2.command("doctor").description("\u8BCA\u65AD\u7CFB\u7EDF\u73AF\u5883").action(async () => {
4277
- log9(`\u2713 Node.js: ${process.version}`);
4324
+ program2.command("doctor").description(t34("cli.doctor.cmd_root")).option("--reclaim", t34("cli.doctor.reclaim_opt")).action(async (opts) => {
4325
+ log9(t34("cli.doctor.node", { version: process.version }));
4278
4326
  const avail = await describeDisk();
4279
- if (avail) log9(`\u2713 \u78C1\u76D8: ${avail} \u53EF\u7528`);
4280
- log9(`${hasCredentials() ? "\u2713" : "\u25CB"} \u51ED\u8BC1`);
4327
+ if (avail) log9(t34("cli.doctor.disk", { avail }));
4328
+ log9(`${hasCredentials() ? "\u2713" : "\u25CB"} ${t34("cli.doctor.credentials")}`);
4281
4329
  const configIssues = existsSync7(CONFIG_PATH) ? (loadConfig3(), getConfigIssues()) : [];
4282
4330
  const configMark = !existsSync7(CONFIG_PATH) ? "\u25CB" : configIssues.length > 0 ? "\u26A0" : "\u2713";
4283
4331
  log9(`${configMark} config: ${CONFIG_PATH}`);
4284
4332
  try {
4285
4333
  const { buildRuntime: buildRuntime4 } = await import("@epoch-agent/runtime");
4286
4334
  const rt = await buildRuntime4({ installSignalHandlers: true });
4287
- log9("\n\u5DE5\u4F5C\u533A:");
4335
+ log9(`
4336
+ ${t34("cli.doctor.workspace_head")}`);
4288
4337
  for (const line of workspaceLines(rt)) log9(line);
4289
- for (const line of sandboxLines(rt.workspace.root)) log9(line);
4338
+ for (const line of sandboxLines(rt.workspace.root, rt.config.sandbox)) log9(line);
4339
+ for (const line of hookLines(rt)) log9(line);
4340
+ for (const line of jobLines(allJobs().map((j) => j.info))) log9(line);
4341
+ for (const line of terminalLines(
4342
+ await probeShiftEnter({ prompt: t34("cli.doctor.terminal_prompt"), timeoutMs: 8e3 }),
4343
+ classifyTerminal(process.env)
4344
+ ))
4345
+ log9(line);
4290
4346
  for (const line of await statusLineLines(rt.config, process.cwd())) log9(line);
4291
- log9("\n\u6A21\u5757\u81EA\u68C0:");
4347
+ if (rt.sessionStore) {
4348
+ try {
4349
+ for (const line of ftsLines(rt.sessionStore.ftsHealth())) log9(line);
4350
+ } catch (err2) {
4351
+ log9(`
4352
+ ${t34("cli.doctor.fts_head")}`);
4353
+ log9(
4354
+ ` ${STATUS_MARK.warn} ${t34("cli.doctor.fts_probe_failed", {
4355
+ detail: err2 instanceof Error ? err2.message : String(err2)
4356
+ })}`
4357
+ );
4358
+ }
4359
+ if (opts.reclaim) {
4360
+ try {
4361
+ for (const line of reclaimLines(rt.sessionStore.reclaimFts())) log9(line);
4362
+ } catch (err2) {
4363
+ log9(`
4364
+ ${t34("cli.doctor.reclaim_head")}`);
4365
+ log9(
4366
+ ` ${STATUS_MARK.failed} ${t34("cli.doctor.reclaim_failed", {
4367
+ detail: err2 instanceof Error ? err2.message : String(err2)
4368
+ })}`
4369
+ );
4370
+ }
4371
+ }
4372
+ }
4373
+ log9(`
4374
+ ${t34("cli.doctor.modules_head")}`);
4292
4375
  const order = { failed: 0, warn: 1, skipped: 2, ok: 3 };
4293
4376
  const sorted = [...rt.diagnosticList].sort(
4294
4377
  (a, b) => (order[a.status] ?? 9) - (order[b.status] ?? 9)
@@ -4298,32 +4381,39 @@ function registerStatusCommands(program2) {
4298
4381
  if (hasFailure(rt.diagnosticList)) {
4299
4382
  const bad = rt.diagnosticList.filter((d) => d.status === "failed").length;
4300
4383
  log9(`
4301
- \u2717 ${bad} \u4E2A\u6A21\u5757\u8D77\u4E0D\u6765`);
4384
+ ${t34("cli.doctor.modules_failed", { count: bad })}`);
4302
4385
  process.exitCode = EXIT_CODES.FAILURE;
4303
4386
  }
4304
4387
  } catch (err2) {
4305
- log9(`
4306
- \u2717 \u88C5\u914D\u5931\u8D25: ${err2 instanceof Error ? err2.message : String(err2)}`);
4388
+ log9(
4389
+ `
4390
+ ${t34("cli.doctor.build_failed", {
4391
+ message: err2 instanceof Error ? err2.message : String(err2)
4392
+ })}`
4393
+ );
4307
4394
  }
4308
4395
  });
4309
4396
  }
4310
-
4311
4397
  // src/commands/trust.ts
4312
4398
  import { existsSync as existsSync8, realpathSync as realpathSync3 } from "fs";
4313
4399
  import { resolve as resolve3 } from "path";
4314
4400
  import { loadConfig as loadConfig4, TrustManager as TrustManager3 } from "@epoch-agent/core";
4315
- import { trustPath as trustPath3 } from "@epoch-agent/infra";
4401
+ import { t as t35, trustPath as trustPath3, uiDateLocale as uiDateLocale4 } from "@epoch-agent/infra";
4316
4402
  var log10 = (msg) => process.stdout.write(msg + "\n");
4317
4403
  var warn3 = (msg) => process.stderr.write(msg + "\n");
4318
- var SCOPE_LABEL = {
4319
- directory: "\u4EC5\u6B64\u76EE\u5F55",
4320
- "directory-tree": "\u542B\u5B50\u76EE\u5F55"
4321
- };
4322
- var LEVEL_LABEL = {
4323
- trusted: "\u5DF2\u4FE1\u4EFB",
4324
- untrusted: "\u5DF2\u62D2\u7EDD",
4325
- unknown: "\u672A\u51B3\u5B9A"
4326
- };
4404
+ function scopeLabel(scope) {
4405
+ return scope === "directory-tree" ? t35("trust.scope_tree") : t35("trust.scope_dir");
4406
+ }
4407
+ function levelLabel(level) {
4408
+ switch (level) {
4409
+ case "trusted":
4410
+ return t35("trust.level_trusted");
4411
+ case "untrusted":
4412
+ return t35("trust.level_untrusted");
4413
+ case "unknown":
4414
+ return t35("trust.level_unknown");
4415
+ }
4416
+ }
4327
4417
  function physicalPath(input2) {
4328
4418
  const abs = resolve3(input2);
4329
4419
  if (!existsSync8(abs)) return abs;
@@ -4334,58 +4424,64 @@ function physicalPath(input2) {
4334
4424
  }
4335
4425
  }
4336
4426
  function registerTrustCommand(program2) {
4337
- const cmd = program2.command("trust").description("\u7BA1\u7406\u5DE5\u4F5C\u533A\u4FE1\u4EFB\uFF08\u54EA\u4E9B\u76EE\u5F55\u53EF\u4EE5\u628A\u81EA\u5DF1\u7684\u9879\u76EE\u6307\u4EE4\u52A0\u8FDB system prompt\uFF09");
4338
- cmd.command("list", { isDefault: true }).description("\u5217\u51FA\u6240\u6709\u4FE1\u4EFB\u8BB0\u5F55").action(() => withTrust((tm) => printList3(tm)));
4339
- cmd.command("add").description("\u4FE1\u4EFB\u4E00\u4E2A\u76EE\u5F55").argument("[path]", "\u76EE\u5F55\u8DEF\u5F84\uFF08\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF09").option("--tree", "\u8FDE\u540C\u6240\u6709\u5B50\u76EE\u5F55\u4E00\u8D77\u4FE1\u4EFB\uFF08\u542B\u4EE5\u540E\u65B0\u5EFA / clone \u8FDB\u6765\u7684\uFF09").action((path, opts) => {
4427
+ const cmd = program2.command("trust").description(t35("trust.cmd_root"));
4428
+ cmd.command("list", { isDefault: true }).description(t35("trust.cmd_list")).action(() => withTrust((tm) => printList3(tm)));
4429
+ cmd.command("add").description(t35("trust.cmd_add")).argument("[path]", t35("trust.arg_path_default")).option("--tree", t35("trust.opt_tree_add")).action((path, opts) => {
4340
4430
  withTrust((tm) => {
4341
4431
  const dir = physicalPath(path ?? process.cwd());
4342
4432
  tm.record(dir, "trusted", opts.tree ? "directory-tree" : "directory");
4343
4433
  log10(
4344
- `\u2713 \u5DF2\u4FE1\u4EFB ${dir}\uFF08${opts.tree ? SCOPE_LABEL["directory-tree"] : SCOPE_LABEL.directory}\uFF09`
4434
+ `\u2713 ${t35("trust.added", {
4435
+ dir,
4436
+ scope: scopeLabel(opts.tree ? "directory-tree" : "directory")
4437
+ })}`
4345
4438
  );
4346
4439
  });
4347
4440
  });
4348
- cmd.command("deny").description("\u660E\u786E\u62D2\u7EDD\u4E00\u4E2A\u76EE\u5F55\uFF08\u4E4B\u540E\u4E0D\u518D\u8BE2\u95EE\uFF0C\u4E5F\u4E0D\u52A0\u8F7D\u5B83\u7684\u9879\u76EE\u6307\u4EE4\uFF09").argument("[path]", "\u76EE\u5F55\u8DEF\u5F84\uFF08\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF09").option("--tree", "\u8FDE\u540C\u6240\u6709\u5B50\u76EE\u5F55\u4E00\u8D77\u62D2\u7EDD").action((path, opts) => {
4441
+ cmd.command("deny").description(t35("trust.cmd_deny")).argument("[path]", t35("trust.arg_path_default")).option("--tree", t35("trust.opt_tree_deny")).action((path, opts) => {
4349
4442
  withTrust((tm) => {
4350
4443
  const dir = physicalPath(path ?? process.cwd());
4351
4444
  tm.record(dir, "untrusted", opts.tree ? "directory-tree" : "directory");
4352
4445
  log10(
4353
- `\u2717 \u5DF2\u62D2\u7EDD ${dir}\uFF08${opts.tree ? SCOPE_LABEL["directory-tree"] : SCOPE_LABEL.directory}\uFF09`
4446
+ `\u2717 ${t35("trust.denied", {
4447
+ dir,
4448
+ scope: scopeLabel(opts.tree ? "directory-tree" : "directory")
4449
+ })}`
4354
4450
  );
4355
4451
  });
4356
4452
  });
4357
- cmd.command("remove").alias("rm").description("\u5220\u6389\u4E00\u6761\u8BB0\u5F55\uFF0C\u8BE5\u76EE\u5F55\u56DE\u5230\u300C\u672A\u51B3\u5B9A\u300D").argument("<path>", "\u76EE\u5F55\u8DEF\u5F84").action((path) => {
4453
+ cmd.command("remove").alias("rm").description(t35("trust.cmd_remove")).argument("<path>", t35("trust.arg_path")).action((path) => {
4358
4454
  withTrust((tm) => {
4359
4455
  const dir = physicalPath(path);
4360
4456
  if (!tm.list().some((r) => resolve3(r.path) === dir)) {
4361
- warn3(`${dir} \u4E0A\u6CA1\u6709\u4FE1\u4EFB\u8BB0\u5F55\uFF08\u7528 epoch trust \u67E5\u770B\u73B0\u6709\u8BB0\u5F55\uFF09`);
4457
+ warn3(t35("trust.no_record", { dir }));
4362
4458
  process.exit(1);
4363
4459
  }
4364
4460
  tm.revoke(dir);
4365
- log10(`\u5DF2\u5220\u9664 ${dir} \u7684\u8BB0\u5F55\uFF0C\u73B0\u5728\u5224\u5B9A\u4E3A\uFF1A${LEVEL_LABEL[tm.check(dir)]}`);
4461
+ log10(t35("trust.removed", { dir, level: levelLabel(tm.check(dir)) }));
4366
4462
  });
4367
4463
  });
4368
4464
  }
4369
4465
  function printList3(tm) {
4370
4466
  if (loadConfig4().trust?.enabled === false) {
4371
- warn3(
4372
- "\u26A0 config.trust.enabled = false\uFF1A\u95F8\u95E8\u5DF2\u5173\u95ED\uFF0C\u4E0B\u9762\u7684\u8BB0\u5F55\u4E0D\u751F\u6548\uFF0C\u4EFB\u4F55\u76EE\u5F55\u7684\u9879\u76EE\u6307\u4EE4\u90FD\u4F1A\u52A0\u8F7D"
4373
- );
4467
+ warn3(t35("trust.gate_off"));
4374
4468
  }
4375
- log10(`\u4FE1\u4EFB\u8BB0\u5F55\uFF08${trustPath3(EPOCH_HOME)}\uFF09`);
4469
+ log10(t35("trust.list_head", { path: trustPath3(EPOCH_HOME) }));
4376
4470
  const records = [...tm.list()].sort((a, b) => a.path.localeCompare(b.path));
4377
4471
  if (records.length === 0) {
4378
- log10(" (\u65E0)");
4472
+ log10(t35("trust.list_empty"));
4379
4473
  }
4380
4474
  for (const r of records) {
4381
4475
  const mark = r.level === "trusted" ? "\u2713" : "\u2717";
4382
- const when = r.decidedAt ? new Date(r.decidedAt).toLocaleString("zh-CN") : "\u672A\u77E5";
4383
- log10(` ${mark} ${r.path} [${SCOPE_LABEL[r.scope]}] ${when}`);
4476
+ const when = r.decidedAt ? new Date(r.decidedAt).toLocaleString(uiDateLocale4()) : t35("trust.when_unknown");
4477
+ log10(` ${mark} ${r.path} [${scopeLabel(r.scope)}] ${when}`);
4384
4478
  }
4385
4479
  const cwd = process.cwd();
4386
- log10(`
4387
- \u5F53\u524D\u76EE\u5F55 ${cwd}
4388
- \u5224\u5B9A\uFF1A${LEVEL_LABEL[tm.check(cwd)]}`);
4480
+ log10(
4481
+ `
4482
+ ${t35("trust.cwd_line", { cwd })}
4483
+ ${t35("trust.cwd_verdict", { level: levelLabel(tm.check(cwd)) })}`
4484
+ );
4389
4485
  }
4390
4486
  function withTrust(fn) {
4391
4487
  const tm = new TrustManager3(trustPath3(EPOCH_HOME));
@@ -4393,38 +4489,46 @@ function withTrust(fn) {
4393
4489
  try {
4394
4490
  fn(tm);
4395
4491
  } catch (err2) {
4396
- warn3(`\u9519\u8BEF: ${err2 instanceof Error ? err2.message : String(err2)}`);
4492
+ warn3(t35("trust.error", { message: err2 instanceof Error ? err2.message : String(err2) }));
4397
4493
  process.exit(1);
4398
4494
  }
4399
4495
  }
4400
-
4401
4496
  // src/commands/upgrade.ts
4497
+ import { t as t36 } from "@epoch-agent/infra";
4402
4498
  var log11 = (msg) => process.stdout.write(`${msg}
4403
4499
  `);
4404
4500
  function registerUpgradeCommand(program2) {
4405
- program2.command("upgrade").description("\u68C0\u67E5\u65B0\u7248\u672C\u5E76\u7ED9\u51FA\u5347\u7EA7\u547D\u4EE4").action(async () => {
4501
+ program2.command("upgrade").description(t36("upgrade.cmd_root")).action(async () => {
4406
4502
  const self = selfPackage();
4407
4503
  const info = getInstallationInfo(self.name);
4408
- log11(`\u5F53\u524D\u7248\u672C: ${self.version}`);
4409
- log11(`\u5B89\u88C5\u65B9\u5F0F: ${info.packageManager}${info.isGlobal ? "\uFF08\u5168\u5C40\uFF09" : ""} \u2014\u2014 ${info.note}`);
4504
+ log11(t36("upgrade.current", { version: self.version }));
4505
+ log11(
4506
+ t36("upgrade.installed_via", {
4507
+ manager: info.packageManager,
4508
+ global: info.isGlobal ? t36("installation.global_suffix") : "",
4509
+ note: info.note
4510
+ })
4511
+ );
4410
4512
  const notice = isUpdateCheckDisabled() ? readUpdateNotice() : await refreshUpdateCache({ ttlMs: 0 }).catch(() => null);
4411
- if (notice) log11(`
4412
- \u6709\u65B0\u7248\u672C: ${notice.current} \u2192 ${notice.latest}`);
4413
- else log11("\n\u6CA1\u6709\u67E5\u5230\u66F4\u65B0\uFF08\u5DF2\u662F\u6700\u65B0\uFF0C\u6216 registry \u6682\u65F6\u4E0D\u53EF\u8FBE\uFF09");
4513
+ if (notice) {
4514
+ log11(`
4515
+ ${t36("upgrade.available", { current: notice.current, latest: notice.latest })}`);
4516
+ } else log11(`
4517
+ ${t36("upgrade.none")}`);
4414
4518
  if (info.updateCommand) {
4415
4519
  log11(`
4416
- \u5347\u7EA7\u8BF7\u81EA\u5DF1\u6267\u884C:
4520
+ ${t36("upgrade.how")}
4417
4521
  ${info.updateCommand}`);
4418
- log11("\n\uFF08epoch \u4E0D\u4F1A\u81EA\u52A8\u66FF\u4F60\u5347\u7EA7\uFF1A\u5168\u5C40\u76EE\u5F55\u6743\u9650\u3001\u5305\u7BA1\u7406\u5668\u5DEE\u5F02\u3001\u8FDB\u7A0B\u81EA\u5DF1\u8986\u76D6\u81EA\u5DF1\u90FD\u662F\u5751\uFF09");
4522
+ log11(`
4523
+ ${t36("upgrade.why_manual")}`);
4419
4524
  }
4420
4525
  });
4421
- program2.command(UPDATE_CHECK_SUBCOMMAND, { hidden: true }).description("\u5185\u90E8\u547D\u4EE4\uFF1A\u540E\u53F0\u5237\u65B0\u7248\u672C\u7F13\u5B58").action(async () => {
4526
+ program2.command(UPDATE_CHECK_SUBCOMMAND, { hidden: true }).description(t36("upgrade.cmd_refresh")).action(async () => {
4422
4527
  await refreshUpdateCache().catch(() => null);
4423
4528
  });
4424
4529
  }
4425
-
4426
4530
  // src/commands/web.ts
4427
- import { t as t13 } from "@epoch-agent/infra";
4531
+ import { t as t37 } from "@epoch-agent/infra";
4428
4532
  import { buildRuntime as buildRuntime3 } from "@epoch-agent/runtime";
4429
4533
  import {
4430
4534
  createWebServer,
@@ -4432,7 +4536,6 @@ import {
4432
4536
  DEFAULT_WEB_HOST,
4433
4537
  DEFAULT_WEB_PORT
4434
4538
  } from "@epoch-agent/server";
4435
-
4436
4539
  // src/commands/web-open.ts
4437
4540
  import { execFile as execFile3 } from "child_process";
4438
4541
  function isBrowsableUrl(url) {
@@ -4462,7 +4565,6 @@ function openInBrowser(url) {
4462
4565
  return false;
4463
4566
  }
4464
4567
  }
4465
-
4466
4568
  // src/commands/web.ts
4467
4569
  var out = (msg) => {
4468
4570
  process.stdout.write(`${msg}
@@ -4473,7 +4575,7 @@ var err = (msg) => {
4473
4575
  `);
4474
4576
  };
4475
4577
  function registerWebCommand(program2) {
4476
- program2.command("web").description(t13("cli.web.summary")).option("-p, --port <port>", t13("cli.web.opt_port", { port: DEFAULT_WEB_PORT })).option("--host <host>", t13("cli.web.opt_host", { host: DEFAULT_WEB_HOST })).option("--token <token>", t13("cli.web.opt_token")).option("--no-open", t13("cli.web.opt_no_open")).option("--json", t13("cli.web.opt_json")).action(async (opts, command) => {
4578
+ program2.command("web").description(t37("cli.web.summary")).option("-p, --port <port>", t37("cli.web.opt_port", { port: DEFAULT_WEB_PORT })).option("--host <host>", t37("cli.web.opt_host", { host: DEFAULT_WEB_HOST })).option("--token <token>", t37("cli.web.opt_token")).option("--no-open", t37("cli.web.opt_no_open")).option("--json", t37("cli.web.opt_json")).option("--plugins", t37("cli.web.opt_plugins")).action(async (opts, command) => {
4477
4579
  await runWeb({ ...opts, json: jsonRequested(opts.json, command.parent?.opts()["json"]) });
4478
4580
  });
4479
4581
  }
@@ -4482,7 +4584,7 @@ function jsonRequested(own, fromProgram) {
4482
4584
  }
4483
4585
  async function runWeb(opts) {
4484
4586
  const port = parsePort2(opts.port);
4485
- if (port === null) return fail(`\u7AEF\u53E3\u5FC5\u987B\u662F\u6570\u5B57\uFF1A${String(opts.port)}`);
4587
+ if (port === null) return fail(t37("cli.web.err_port", { value: String(opts.port) }));
4486
4588
  const binding = decideBinding({
4487
4589
  ...opts.host === void 0 ? {} : { host: opts.host },
4488
4590
  ...port === void 0 ? {} : { port },
@@ -4491,7 +4593,7 @@ async function runWeb(opts) {
4491
4593
  if (!binding.ok) return fail(binding.error);
4492
4594
  if (!await maybePromptForTrust()) process.exit(1);
4493
4595
  if (!await maybePromptForExternalImports()) process.exit(1);
4494
- const server = await boot(binding);
4596
+ const server = await boot(binding, opts.plugins === true);
4495
4597
  if (!server) return;
4496
4598
  const json = opts.json === true;
4497
4599
  const say = json ? err : out;
@@ -4506,52 +4608,32 @@ async function runWeb(opts) {
4506
4608
  });
4507
4609
  if (lines.machine) out(lines.machine);
4508
4610
  for (const line of lines.human) say(line);
4509
- if (opts.open && !openInBrowser(server.url)) say(BROWSER_FAILED);
4611
+ if (opts.open && !openInBrowser(server.url)) say(browserFailed());
4510
4612
  installShutdown(server, say);
4511
4613
  }
4512
- var MISSING_WEB_ROOT = "\u524D\u7AEF\u4EA7\u7269\u6CA1\u627E\u5230\uFF0C\u672C\u6B21\u53EA\u63D0\u4F9B REST / SSE\uFF0C\u6253\u5F00\u662F\u5360\u4F4D\u9875\u3002\u8DD1\u4E00\u6B21 pnpm build \u518D\u8BD5";
4513
- var BROWSER_FAILED = " \uFF08\u6CA1\u80FD\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668\uFF0C\u590D\u5236\u4E0A\u9762\u90A3\u6761\u5730\u5740\u5373\u53EF\uFF09";
4514
- async function boot(binding) {
4614
+ var missingWebRoot = () => t37("cli.web.missing_root");
4615
+ var browserFailed = () => ` ${t37("cli.web.browser_failed")}`;
4616
+ function pluginsOption(plugins) {
4617
+ return plugins ? { hostMarketplaces: { sources: [] } } : {};
4618
+ }
4619
+ async function boot(binding, plugins) {
4515
4620
  const runtime = await buildRuntime3({
4516
- // **显式 false,且不许改成 true。** runtime 装的那个处理器是
4517
- // `dispose(); process.exit(0)`,它注册得比下面 installShutdown() 早,
4518
- // 于是 Ctrl+C 会在 HTTP 服务关闭、挂起审批 abandon 之前就把进程打死。
4519
- // web 的收尾顺序是 §4.3 定的:abandon + abort → 断连接 → dispose
4520
4621
  installSignalHandlers: false,
4521
- // 浏览器**能**弹审批框,所以这里是交互式的。不显式给的话
4522
- // `isNonInteractive()` 会去猜 TTY —— 从桌面启动器拉起时没有 TTY,
4523
- // 于是 `config.headless.allowTools` 这份只该在 CI 里生效的预授权白名单
4524
- // 会在一个完全能弹框的界面下悄悄生效,审批被跳过而没人知道
4525
- interactive: true
4622
+ interactive: true,
4623
+ ...pluginsOption(plugins)
4526
4624
  });
4527
4625
  if (!runtime.session) {
4528
4626
  await runtime.dispose();
4529
- fail(`\u542F\u52A8\u5931\u8D25\uFF1Aprovider \u4E0D\u53EF\u7528\uFF0C\u8FD0\u884C epoch model \u914D\u7F6E
4627
+ fail(`${t37("cli.web.no_provider")}
4530
4628
  ${runtime.diagnostics.join("\n")}`);
4531
4629
  return null;
4532
4630
  }
4533
4631
  const created = await createWebServer({
4534
4632
  runtime,
4535
4633
  version: VERSION,
4536
- // **不再传 `artifactsRoot`**(方案 54 §二):缺省就是 `runtime.artifactsRoot`,
4537
- // 也就是引擎这次真的往里写产物的那个目录。以前这里是
4538
- // `artifactsDir(undefined, runtime.config.homeDir)` —— 算得对,但它是**第二处**
4539
- // 在算同一个路径,而第二处存在本身就是那个 bug 的形状:嵌入宿主照抄这一行、
4540
- // 少传一个 homeDir,就是产物 404 而全链路 200。
4541
- // CLI 自己也走缺省,是为了让宿主走的那条路每天被跑到。
4542
- //
4543
- // **不再传 workDir**(决定 18):工作区变成逐会话绑定之后,「这个服务的工作
4544
- // 目录」不再是一个能回答问题的事实。服务端问的是 `runtime.workspaces`,
4545
- // 而引导会话那份绑定就是 `buildRuntime()` 里的 workDir(默认 `process.cwd()`)——
4546
- // 同一个值,但只有一个真源
4547
4634
  host: binding.host,
4548
4635
  port: binding.port,
4549
- // 用上面那一次判定产出的 token,不让 server 再生成一个 ——
4550
- // 两个 token 里只有一个会被写进 URL,另一个就是纯粹的困惑来源
4551
4636
  token: binding.token
4552
- // **不传 webRoot**:前端产物 2026-08-14 起随 server 一起发,由它自己定位
4553
- // (`server/src/web-root.ts`)。cli 原来那份探测是同一段逻辑的第二个副本,
4554
- // 而副本只有 cli 这一条路走得到 —— 嵌入宿主照样是占位页
4555
4637
  });
4556
4638
  if (!created.ok) {
4557
4639
  await runtime.dispose();
@@ -4564,13 +4646,13 @@ function announceLines(input2) {
4564
4646
  const human = [];
4565
4647
  if (!input2.json) {
4566
4648
  human.push(` Epoch Web http://${input2.host}:${input2.port}`);
4567
- human.push(` \u6253\u5F00\u8FD9\u6761\uFF08\u542B\u4E00\u6B21\u6027 token\uFF09\uFF1A
4649
+ human.push(` ${t37("cli.web.open_this")}
4568
4650
  ${input2.url}`);
4569
4651
  }
4570
4652
  if (input2.lanExposed) {
4571
- human.push(" \u26A0 \u5DF2\u66B4\u9732\u5230\u56DE\u73AF\u4E4B\u5916\uFF1A\u540C\u7F51\u6BB5\u7684\u4EFB\u4F55\u4EBA\u62FF\u5230\u8FD9\u6761 URL \u90FD\u80FD\u64CD\u4F5C\u4F60\u7684 agent");
4653
+ human.push(` ${t37("cli.web.lan_exposed")}`);
4572
4654
  }
4573
- if (input2.placeholder) human.push(` \u26A0 ${MISSING_WEB_ROOT}`);
4655
+ if (input2.placeholder) human.push(` \u26A0 ${missingWebRoot()}`);
4574
4656
  if (!input2.json) return { human };
4575
4657
  const machine = JSON.stringify({
4576
4658
  url: input2.url,
@@ -4585,7 +4667,8 @@ function installShutdown(server, say) {
4585
4667
  const onSignal = () => {
4586
4668
  if (shuttingDown) process.exit(1);
4587
4669
  shuttingDown = true;
4588
- say("\n \u6536\u5C3E\u4E2D\u2026");
4670
+ say(`
4671
+ ${t37("cli.web.shutting_down")}`);
4589
4672
  server.close().then(
4590
4673
  () => process.exit(0),
4591
4674
  () => process.exit(1)
@@ -4603,10 +4686,19 @@ function fail(message) {
4603
4686
  err(message);
4604
4687
  process.exit(1);
4605
4688
  }
4606
-
4689
+ // src/language.ts
4690
+ import { loadConfig as loadConfig5 } from "@epoch-agent/core";
4691
+ import { resolveLang, setLang } from "@epoch-agent/infra";
4692
+ function applyConfiguredLanguage() {
4693
+ try {
4694
+ setLang(resolveLang(loadConfig5().display?.language));
4695
+ } catch {
4696
+ }
4697
+ }
4607
4698
  // src/index.ts
4699
+ applyConfiguredLanguage();
4608
4700
  var program = new Command();
4609
- program.name("epoch").description("Epoch Agent CLI \u667A\u80FD\u4F53");
4701
+ program.name("epoch").description(t38("cli.program_description"));
4610
4702
  registerConfigCommand(program);
4611
4703
  registerMcpCommand(program);
4612
4704
  registerModelCommand(program);