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