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