@epoch-agent/cli 0.1.0 → 0.3.1

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