@keyou007/agent-bot 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/.env.example +3 -1
  2. package/CHANGELOG.md +15 -0
  3. package/README.md +23 -8
  4. package/README.zh.md +23 -8
  5. package/config.example.yaml +1 -0
  6. package/dist/cli/FeishuAppConfiguration.js +10 -10
  7. package/dist/cli/FeishuAppConfiguration.js.map +1 -1
  8. package/dist/cli/FeishuAppRegistration.js +21 -11
  9. package/dist/cli/FeishuAppRegistration.js.map +1 -1
  10. package/dist/cli/Initializer.js +14 -8
  11. package/dist/cli/Initializer.js.map +1 -1
  12. package/dist/cli/LocalControlClient.js +2 -2
  13. package/dist/cli/LocalControlClient.js.map +1 -1
  14. package/dist/cli/LocalControlServer.js +1 -1
  15. package/dist/cli/LocalControlServer.js.map +1 -1
  16. package/dist/cli/ServerStarter.js +69 -0
  17. package/dist/cli/ServerStarter.js.map +1 -0
  18. package/dist/cli/SkillRegistry.js +4 -4
  19. package/dist/cli/SkillRegistry.js.map +1 -1
  20. package/dist/cli/help.js +5 -56
  21. package/dist/cli/help.js.map +1 -1
  22. package/dist/cli/packageVersion.js +1 -1
  23. package/dist/cli/packageVersion.js.map +1 -1
  24. package/dist/cli/profile.js +40 -0
  25. package/dist/cli/profile.js.map +1 -0
  26. package/dist/cli/serverStatus.js +37 -0
  27. package/dist/cli/serverStatus.js.map +1 -0
  28. package/dist/cli/taskChatRoute.js +2 -2
  29. package/dist/cli/taskChatRoute.js.map +1 -1
  30. package/dist/cli.js +148 -168
  31. package/dist/cli.js.map +1 -1
  32. package/dist/config/loadConfig.js +7 -2
  33. package/dist/config/loadConfig.js.map +1 -1
  34. package/dist/config/paths.js +1 -0
  35. package/dist/config/paths.js.map +1 -1
  36. package/dist/config/schema.js +1 -0
  37. package/dist/config/schema.js.map +1 -1
  38. package/dist/feishu/CardRenderer.js +21 -3
  39. package/dist/feishu/CardRenderer.js.map +1 -1
  40. package/dist/feishu/FeishuConnector.js +7 -76
  41. package/dist/feishu/FeishuConnector.js.map +1 -1
  42. package/dist/feishu/transport.js +1 -1
  43. package/dist/feishu/transport.js.map +1 -1
  44. package/dist/index.js +18 -9
  45. package/dist/index.js.map +1 -1
  46. package/dist/proxy/ProxySessionController.js +28 -13
  47. package/dist/proxy/ProxySessionController.js.map +1 -1
  48. package/dist/startup/StartupNotifier.js +13 -2
  49. package/dist/startup/StartupNotifier.js.map +1 -1
  50. package/dist/startup/startFeishu.js +1 -1
  51. package/dist/startup/startFeishu.js.map +1 -1
  52. package/dist/supervision/SafeRestartNotifier.js +23 -7
  53. package/dist/supervision/SafeRestartNotifier.js.map +1 -1
  54. package/dist/supervision/SafeRestartScheduler.js +20 -2
  55. package/dist/supervision/SafeRestartScheduler.js.map +1 -1
  56. package/dist/supervision/SupervisorDiagnostics.js +209 -0
  57. package/dist/supervision/SupervisorDiagnostics.js.map +1 -0
  58. package/dist/supervisor.js +47 -12
  59. package/dist/supervisor.js.map +1 -1
  60. package/docs/technical-reference.md +25 -5
  61. package/docs/technical-reference.zh.md +25 -5
  62. package/npm-shrinkwrap.json +2 -2
  63. package/package.json +1 -1
  64. package/skills/agent-bot/SKILL.md +24 -4
  65. package/src/cli/FeishuAppConfiguration.ts +10 -10
  66. package/src/cli/FeishuAppRegistration.ts +23 -10
  67. package/src/cli/Initializer.ts +14 -7
  68. package/src/cli/LocalControlClient.ts +2 -2
  69. package/src/cli/LocalControlServer.ts +1 -1
  70. package/src/cli/ServerStarter.ts +106 -0
  71. package/src/cli/SkillRegistry.ts +4 -4
  72. package/src/cli/help.ts +5 -65
  73. package/src/cli/packageVersion.ts +1 -1
  74. package/src/cli/profile.ts +59 -0
  75. package/src/cli/serverStatus.ts +39 -0
  76. package/src/cli/taskChatRoute.ts +2 -2
  77. package/src/cli.ts +160 -167
  78. package/src/config/loadConfig.ts +12 -2
  79. package/src/config/paths.ts +1 -0
  80. package/src/config/schema.ts +1 -0
  81. package/src/feishu/CardRenderer.ts +25 -3
  82. package/src/feishu/FeishuConnector.ts +6 -87
  83. package/src/feishu/transport.ts +1 -1
  84. package/src/index.ts +22 -9
  85. package/src/proxy/ProxySessionController.ts +28 -13
  86. package/src/startup/StartupNotifier.ts +21 -4
  87. package/src/startup/startFeishu.ts +1 -1
  88. package/src/supervision/SafeRestartNotifier.ts +22 -7
  89. package/src/supervision/SafeRestartScheduler.ts +27 -3
  90. package/src/supervision/SupervisorDiagnostics.ts +268 -0
  91. package/src/supervisor.ts +46 -11
package/src/cli.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { spawn, spawnSync } from "node:child_process";
2
+ import { spawnSync } from "node:child_process";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import qrcode from "qrcode-terminal";
5
5
  import { loadConfig } from "./config/loadConfig.js";
6
- import { sendControlRequest, isServerReachable, isServerRunning } from "./cli/LocalControlClient.js";
6
+ import { sendControlRequest, isServerReachable } from "./cli/LocalControlClient.js";
7
7
  import {
8
8
  controlEndpoint,
9
9
  type ControlResponse,
@@ -30,22 +30,35 @@ import {
30
30
  type FeishuConfigurationChallenge,
31
31
  } from "./cli/FeishuAppConfiguration.js";
32
32
  import { renderCliHelp } from "./cli/help.js";
33
- import { requireServerFeishuTransport } from "./feishu/transport.js";
33
+ import { formatServerStatus, withConfiguredFeishuAppId } from "./cli/serverStatus.js";
34
34
  import { resolveSystemSkillsRoot, SkillRegistry, type SkillRegistrationStatus } from "./cli/SkillRegistry.js";
35
35
  import { readPackageVersion } from "./cli/packageVersion.js";
36
+ import { applyExplicitProfile, parseGlobalOptions } from "./cli/profile.js";
37
+ import {
38
+ startInitializedServer,
39
+ startServer,
40
+ type InitializationServerResult,
41
+ type ServerStartResult,
42
+ } from "./cli/ServerStarter.js";
36
43
  import { taskChatRoute } from "./cli/taskChatRoute.js";
37
44
  import { StateStore, type SessionRecord } from "./state/StateStore.js";
45
+ import {
46
+ nodeDiagnosticReportArguments,
47
+ prepareCrashReportDirectory,
48
+ resolveSupervisorDiagnosticsPaths,
49
+ } from "./supervision/SupervisorDiagnostics.js";
38
50
 
39
51
  const args = process.argv.slice(2);
40
52
 
41
53
  void main(args).catch((error: unknown) => {
42
- process.stderr.write(`错误:${error instanceof Error ? error.message : String(error)}\n`);
54
+ process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}\n`);
43
55
  process.exitCode = 1;
44
56
  });
45
57
 
46
58
  async function main(input: string[]): Promise<void> {
47
59
  const parsed = parseGlobalOptions(input);
48
- if (parsed.configPath) process.env.AGENT_BOT_CONFIG = parsed.configPath;
60
+ if (parsed.profilePath) applyExplicitProfile(parsed.profilePath);
61
+ else if (parsed.configPath) process.env.AGENT_BOT_CONFIG = parsed.configPath;
49
62
  const [command, ...rest] = parsed.args;
50
63
  if (!command || command === "help" || command === "--help" || command === "-h") {
51
64
  printHelp();
@@ -75,7 +88,7 @@ async function main(input: string[]): Promise<void> {
75
88
  skillsCommand(rest);
76
89
  return;
77
90
  }
78
- throw new Error(`未知命令:${command}。使用 agent-bot --help 查看帮助。`);
91
+ throw new Error(`Unknown command: ${command}. Run agent-bot --help for usage.`);
79
92
  }
80
93
 
81
94
  type FeishuInitializationStatus = "created" | "existing" | "skipped";
@@ -86,35 +99,42 @@ interface InitCommandResult extends InitializationResult {
86
99
  appId?: string;
87
100
  configuration?: EnsureFeishuAppConfigurationResult;
88
101
  };
102
+ server: InitializationServerResult;
89
103
  }
90
104
 
91
105
  async function initCommand(input: string[], configPath?: string): Promise<void> {
92
106
  const supported = new Set(["--json", "--skip-feishu", "--reconfigure-feishu"]);
93
107
  const unsupported = input.filter((value) => !supported.has(value));
94
- if (unsupported.length > 0) throw new Error(`init 不支持参数:${unsupported.join(" ")}`);
108
+ if (unsupported.length > 0) throw new Error(`Unsupported init options: ${unsupported.join(" ")}`);
95
109
  const json = input.includes("--json");
96
110
  const skipFeishu = input.includes("--skip-feishu");
97
111
  const reconfigureFeishu = input.includes("--reconfigure-feishu");
98
112
  if (skipFeishu && reconfigureFeishu) {
99
- throw new Error("--skip-feishu 和 --reconfigure-feishu 不能同时使用。");
113
+ throw new Error("--skip-feishu and --reconfigure-feishu cannot be used together.");
100
114
  }
101
115
 
102
116
  const paths = initializeAgentBot({ configPath });
103
117
  if (!json) printInitializationPaths(paths);
104
118
  const initializationLock = acquireInitializationLock(paths.home.path);
105
119
 
120
+ let initialized: Omit<InitCommandResult, "server">;
106
121
  try {
107
122
  cleanupFeishuCredentialTemporaryFiles(paths.env.path);
108
123
  const feishu = await initializeFeishu(
109
124
  paths,
110
125
  { json, skipFeishu, reconfigureFeishu },
111
126
  );
112
- const result: InitCommandResult = { ...paths, feishu };
113
- if (json) printJson(result);
114
- else printInitializationResult(result);
127
+ initialized = { ...paths, feishu };
115
128
  } finally {
116
129
  initializationLock.release();
117
130
  }
131
+
132
+ if (!json) printInitializationResult(initialized);
133
+ if (!json && !skipFeishu) process.stdout.write("\nStarting Agent Bot server...\n");
134
+ const server = await startInitializedServer({ skipFeishu, configPath });
135
+ const result: InitCommandResult = { ...initialized, server };
136
+ if (json) printJson(result);
137
+ else printInitializationServerResult(server);
118
138
  }
119
139
 
120
140
  async function initializeFeishu(
@@ -130,7 +150,7 @@ async function initializeFeishu(
130
150
  }
131
151
 
132
152
  const controller = new AbortController();
133
- const onInterrupt = (): void => controller.abort(new Error("已取消初始化。"));
153
+ const onInterrupt = (): void => controller.abort(new Error("Initialization was cancelled."));
134
154
  process.once("SIGINT", onInterrupt);
135
155
  process.once("SIGTERM", onInterrupt);
136
156
  try {
@@ -140,11 +160,12 @@ async function initializeFeishu(
140
160
  credentials = {
141
161
  appId: existing.appId!,
142
162
  appSecret: existing.appSecret!,
163
+ userOpenId: existing.userOpenId,
143
164
  };
144
165
  status = "existing";
145
166
  } else {
146
167
  if (existing.status === "incomplete" && !options.json) {
147
- process.stdout.write("\n检测到未完整保存的飞书凭据,将重新创建机器人。\n");
168
+ process.stdout.write("\nIncomplete Lark credentials were found. A new bot will be created.\n");
148
169
  }
149
170
  credentials = await registerFeishuApp({
150
171
  signal: controller.signal,
@@ -154,7 +175,7 @@ async function initializeFeishu(
154
175
  status = "created";
155
176
  }
156
177
 
157
- if (!options.json) process.stdout.write("\n正在检查飞书应用权限、事件和回调配置...\n");
178
+ if (!options.json) process.stdout.write("\nChecking Lark app permissions, events, and callbacks...\n");
158
179
  const configuration = await ensureFeishuAppConfiguration(credentials, {
159
180
  signal: controller.signal,
160
181
  onVerification: (challenge) => printFeishuConfigurationVerification(challenge, options.json),
@@ -181,16 +202,16 @@ function skillsCommand(input: string[]): void {
181
202
  const result = registry.install();
182
203
  if (json) printJson(result);
183
204
  else process.stdout.write(result.updated
184
- ? `已注册 Agent Bot Skill:${result.status.targetPath}\n`
185
- : `Agent Bot Skill 已是最新版本:${result.status.targetPath}\n`);
205
+ ? `Installed the Agent Bot Skill: ${result.status.targetPath}\n`
206
+ : `The Agent Bot Skill is already up to date: ${result.status.targetPath}\n`);
186
207
  return;
187
208
  }
188
209
  if (action === "uninstall" || action === "unregister") {
189
210
  const removed = registry.uninstall();
190
211
  if (json) printJson({ removed, targetPath: registry.targetPath });
191
212
  else process.stdout.write(removed
192
- ? `已反注册 Agent Bot Skill:${registry.targetPath}\n`
193
- : `Agent Bot Skill 尚未注册:${registry.targetPath}\n`);
213
+ ? `Uninstalled the Agent Bot Skill: ${registry.targetPath}\n`
214
+ : `The Agent Bot Skill is not installed: ${registry.targetPath}\n`);
194
215
  return;
195
216
  }
196
217
  if (action === "status") {
@@ -203,13 +224,13 @@ function skillsCommand(input: string[]): void {
203
224
  const paths = { sourcePath: registry.sourcePath, skillsRoot: registry.skillsRoot, targetPath: registry.targetPath };
204
225
  if (json) printJson(paths);
205
226
  else {
206
- process.stdout.write(`内置 Skill:${paths.sourcePath}\n`);
207
- process.stdout.write(`系统 Skills:${paths.skillsRoot}\n`);
208
- process.stdout.write(`注册位置:${paths.targetPath}\n`);
227
+ process.stdout.write(`Built-in Skill: ${paths.sourcePath}\n`);
228
+ process.stdout.write(`System Skills directory: ${paths.skillsRoot}\n`);
229
+ process.stdout.write(`Installation path: ${paths.targetPath}\n`);
209
230
  }
210
231
  return;
211
232
  }
212
- throw new Error(`未知 skills 命令:${action}`);
233
+ throw new Error(`Unknown skills command: ${action}`);
213
234
  }
214
235
 
215
236
  async function consoleCommand(input: string[]): Promise<void> {
@@ -217,10 +238,17 @@ async function consoleCommand(input: string[]): Promise<void> {
217
238
  const endpoint = controlEndpoint(config.storage.sqlitePath);
218
239
  const force = input.includes("--force");
219
240
  if (!force && await isServerReachable(endpoint)) {
220
- throw new Error("agent-bot server 正在运行。为避免争用同一任务状态,请先停止 server,或明确使用 --force。");
241
+ throw new Error(
242
+ "The Agent Bot server is running. Stop it first to avoid concurrent task-state access, or explicitly use --force.",
243
+ );
221
244
  }
222
245
  const entry = fileURLToPath(new URL("./index.js", import.meta.url));
223
- const result = spawnSync(process.execPath, [entry], {
246
+ const reportDirectory = resolveSupervisorDiagnosticsPaths(config).crashReportDirectory;
247
+ prepareCrashReportDirectory(reportDirectory);
248
+ const result = spawnSync(process.execPath, [
249
+ ...nodeDiagnosticReportArguments(reportDirectory),
250
+ entry,
251
+ ], {
224
252
  cwd: process.cwd(),
225
253
  stdio: "inherit",
226
254
  env: { ...process.env, AGENT_BOT_CONSOLE_ONLY: "1" },
@@ -237,57 +265,42 @@ async function serverCommand(input: string[]): Promise<void> {
237
265
  try {
238
266
  const response = await sendControlRequest(endpoint, { action: "health" }, 1_500);
239
267
  ensureOk(response);
240
- if (rest.includes("--json")) printJson(response.data);
241
- else printServerStatus(response.data);
268
+ const status = withConfiguredFeishuAppId(response.data, config.feishu.appId);
269
+ if (rest.includes("--json")) printJson(status);
270
+ else process.stdout.write(formatServerStatus(status));
242
271
  } catch {
243
- if (rest.includes("--json")) printJson({ running: false });
244
- else process.stdout.write("agent-bot server:未运行\n");
272
+ const status = {
273
+ running: false,
274
+ feishuAppId: config.feishu.appId ?? null,
275
+ };
276
+ if (rest.includes("--json")) printJson(status);
277
+ else process.stdout.write(formatServerStatus(status));
245
278
  process.exitCode = 3;
246
279
  }
247
280
  return;
248
281
  }
249
282
  if (action === "start") {
250
- requireServerFeishuTransport(config.feishu);
251
- if (await isServerRunning(endpoint)) {
252
- process.stdout.write("agent-bot server 已在运行。\n");
253
- return;
254
- }
255
- if (await isServerReachable(endpoint)) {
256
- const running = await waitForServer(endpoint, 45_000);
257
- if (!running) throw new Error("agent-bot server 已启动,但未能连接飞书机器人。请检查日志。");
258
- process.stdout.write("agent-bot server 已启动。\n");
259
- return;
260
- }
261
- const entry = fileURLToPath(new URL("./supervisor.js", import.meta.url));
262
- const child = spawn(process.execPath, [entry], {
263
- cwd: process.cwd(),
264
- detached: true,
265
- windowsHide: true,
266
- stdio: "ignore",
267
- env: { ...process.env, AGENT_BOT_RESTART_REASON: "通过 agent-bot CLI 启动" },
268
- });
269
- child.unref();
270
- const running = await waitForServer(endpoint, 45_000);
271
- if (!running) throw new Error("已启动 Supervisor,但 server 未在 45 秒内连接飞书机器人。请检查日志。");
272
- process.stdout.write("agent-bot server 已启动。\n");
283
+ printServerStartResult(await startServer(config));
273
284
  return;
274
285
  }
275
286
  if (action === "stop") {
276
- printResponse(await sendControlRequest(endpoint, { action: "server_stop" }));
287
+ ensureOk(await sendControlRequest(endpoint, { action: "server_stop" }));
288
+ process.stdout.write("Agent Bot server stop requested.\n");
277
289
  return;
278
290
  }
279
291
  if (action === "restart") {
280
292
  const immediate = rest.includes("--immediate") || rest.includes("--force");
281
293
  const reason = optionValue(rest, "--reason")
282
- ?? (immediate ? "通过 agent-bot CLI 立即重启" : "通过 agent-bot CLI 安全重启");
283
- printResponse(await sendControlRequest(endpoint, {
294
+ ?? (immediate ? "通过 Agent Bot CLI 立即重启" : "通过 Agent Bot CLI 安全重启");
295
+ ensureOk(await sendControlRequest(endpoint, {
284
296
  action: "server_restart",
285
297
  mode: immediate ? "immediate" : "safe",
286
298
  reason,
287
299
  }));
300
+ process.stdout.write(immediate ? "Immediate restart requested.\n" : "Safe restart requested.\n");
288
301
  return;
289
302
  }
290
- throw new Error(`未知 server 命令:${action}`);
303
+ throw new Error(`Unknown server command: ${action}`);
291
304
  }
292
305
 
293
306
  async function taskCommand(input: string[]): Promise<void> {
@@ -298,16 +311,17 @@ async function taskCommand(input: string[]): Promise<void> {
298
311
  const allSessions = store.listAllSessions();
299
312
  if (action === "prompt" || action === "send") {
300
313
  const [reference, ...promptParts] = rest;
301
- if (!reference) throw new Error(`task ${action} 需要任务序号或任务 ID。`);
314
+ if (!reference) throw new Error(`task ${action} requires a task number or task ID.`);
302
315
  const text = promptParts.join(" ").trim();
303
- if (!text) throw new Error(`task ${action} 需要要发送的 Prompt。`);
316
+ if (!text) throw new Error(`task ${action} requires a Prompt.`);
304
317
  const session = resolveTask(allSessions, reference);
305
318
  const endpoint = controlEndpoint(config.storage.sqlitePath);
306
- printResponse(await sendControlRequest(endpoint, {
319
+ ensureOk(await sendControlRequest(endpoint, {
307
320
  action: "task_prompt",
308
321
  localSessionId: session.localSessionId,
309
322
  text,
310
323
  }, 60_000));
324
+ process.stdout.write("Prompt submitted.\n");
311
325
  return;
312
326
  }
313
327
  const sessions = filterSessions(allSessions, rest);
@@ -317,7 +331,7 @@ async function taskCommand(input: string[]): Promise<void> {
317
331
  return;
318
332
  }
319
333
  const reference = rest.find((value) => !value.startsWith("--"));
320
- if (!reference) throw new Error(`task ${action} 需要任务序号或任务 ID。`);
334
+ if (!reference) throw new Error(`task ${action} requires a task number or task ID.`);
321
335
  const session = resolveTask(sessions, reference);
322
336
  if (action === "chat") {
323
337
  const route = taskChatRoute(session);
@@ -352,7 +366,8 @@ async function taskCommand(input: string[]): Promise<void> {
352
366
  }
353
367
  const endpoint = controlEndpoint(config.storage.sqlitePath);
354
368
  if (action === "stop") {
355
- printResponse(await sendControlRequest(endpoint, { action: "task_stop", localSessionId: session.localSessionId }));
369
+ ensureOk(await sendControlRequest(endpoint, { action: "task_stop", localSessionId: session.localSessionId }));
370
+ process.stdout.write("Task stop requested.\n");
356
371
  return;
357
372
  }
358
373
  if (action === "title") {
@@ -360,30 +375,21 @@ async function taskCommand(input: string[]): Promise<void> {
360
375
  const titleArgs = rest.slice(referenceIndex + 1);
361
376
  const optionIndex = titleArgs.findIndex((value) => value.startsWith("--"));
362
377
  const title = titleArgs.slice(0, optionIndex < 0 ? undefined : optionIndex).join(" ").trim();
363
- if (!title) throw new Error("task title 需要新标题。");
364
- printResponse(await sendControlRequest(endpoint, {
378
+ if (!title) throw new Error("task title requires a new title.");
379
+ ensureOk(await sendControlRequest(endpoint, {
365
380
  action: "task_title",
366
381
  localSessionId: session.localSessionId,
367
382
  title,
368
383
  }));
384
+ process.stdout.write("Task title updated.\n");
369
385
  return;
370
386
  }
371
- throw new Error(`未知 task 命令:${action}`);
387
+ throw new Error(`Unknown task command: ${action}`);
372
388
  } finally {
373
389
  store.close();
374
390
  }
375
391
  }
376
392
 
377
- function parseGlobalOptions(input: string[]): { configPath?: string; args: string[] } {
378
- const args = [...input];
379
- const configIndex = args.indexOf("--config");
380
- if (configIndex < 0) return { args };
381
- const configPath = args[configIndex + 1];
382
- if (!configPath) throw new Error("--config 需要配置文件路径。");
383
- args.splice(configIndex, 2);
384
- return { configPath, args };
385
- }
386
-
387
393
  function filterSessions(sessions: SessionRecord[], args: string[]): SessionRecord[] {
388
394
  const context = optionValue(args, "--context");
389
395
  const status = optionValue(args, "--status");
@@ -396,7 +402,7 @@ function resolveTask(sessions: SessionRecord[], reference: string): SessionRecor
396
402
  if (/^\d+$/.test(reference)) {
397
403
  const index = Number(reference) - 1;
398
404
  const session = sessions[index];
399
- if (!session) throw new Error(`任务序号超出范围:${reference}`);
405
+ if (!session) throw new Error(`Task number is out of range: ${reference}`);
400
406
  return session;
401
407
  }
402
408
  const exact = sessions.find((session) =>
@@ -407,93 +413,75 @@ function resolveTask(sessions: SessionRecord[], reference: string): SessionRecor
407
413
  || session.remoteSessionId?.startsWith(reference)
408
414
  || session.acpSessionId?.startsWith(reference));
409
415
  if (matches.length === 1) return matches[0]!;
410
- if (matches.length > 1) throw new Error(`任务 ID 前缀不唯一:${reference}`);
411
- throw new Error(`找不到任务:${reference}`);
416
+ if (matches.length > 1) throw new Error(`Task ID prefix is ambiguous: ${reference}`);
417
+ throw new Error(`Task not found: ${reference}`);
412
418
  }
413
419
 
414
420
  function printTaskList(sessions: SessionRecord[]): void {
415
421
  if (sessions.length === 0) {
416
- process.stdout.write("没有任务。\n");
422
+ process.stdout.write("No tasks.\n");
417
423
  return;
418
424
  }
419
425
  for (const [index, session] of sessions.entries()) {
420
426
  const id = session.remoteSessionId ?? session.acpSessionId ?? session.localSessionId;
421
- process.stdout.write(`${index + 1}. [${session.status}/${session.lastTurnStatus ?? "-"}] ${session.title ?? "未命名任务"}\n`);
427
+ process.stdout.write(`${index + 1}. [${session.status}/${session.lastTurnStatus ?? "-"}] ${session.title ?? "Untitled task"}\n`);
422
428
  process.stdout.write(` ${id} · ${session.contextKey} · ${session.updatedAt}\n`);
423
429
  }
424
430
  }
425
431
 
426
432
  function printTaskStatus(session: SessionRecord, snapshot: unknown, remote?: TaskStatusControlData["remote"]): void {
427
- process.stdout.write(`标题:${session.title ?? "未命名任务"}\n`);
428
- process.stdout.write(`任务 ID:${session.remoteSessionId ?? session.acpSessionId ?? session.localSessionId}\n`);
429
- process.stdout.write(`本地 ID:${session.localSessionId}\n`);
430
- process.stdout.write(`上下文:${session.contextKey}\n`);
431
- process.stdout.write(`状态:${session.status} / ${session.lastTurnStatus ?? "-"}\n`);
432
- process.stdout.write(`目录:${session.cwd}\n`);
433
- process.stdout.write(`最后轮次:${session.lastTurnId ?? "-"}\n`);
433
+ process.stdout.write(`Title: ${session.title ?? "Untitled task"}\n`);
434
+ process.stdout.write(`Task ID: ${session.remoteSessionId ?? session.acpSessionId ?? session.localSessionId}\n`);
435
+ process.stdout.write(`Local ID: ${session.localSessionId}\n`);
436
+ process.stdout.write(`Context: ${session.contextKey}\n`);
437
+ process.stdout.write(`Status: ${session.status} / ${session.lastTurnStatus ?? "-"}\n`);
438
+ process.stdout.write(`Directory: ${session.cwd}\n`);
439
+ process.stdout.write(`Last turn: ${session.lastTurnId ?? "-"}\n`);
434
440
  let displayedFinalResponse: string | undefined;
435
441
  if (snapshot && typeof snapshot === "object") {
436
442
  const state = snapshot as Record<string, unknown>;
437
- if (typeof state.durationMs === "number") process.stdout.write(`耗时:${Math.round(state.durationMs / 1_000)}s\n`);
443
+ if (typeof state.durationMs === "number") process.stdout.write(`Duration: ${Math.round(state.durationMs / 1_000)}s\n`);
438
444
  if (typeof state.totalTokens === "number") process.stdout.write(`Tokens:${state.totalTokens}\n`);
439
- if (typeof state.totalToolCount === "number") process.stdout.write(`工具调用:${state.totalToolCount}\n`);
445
+ if (typeof state.totalToolCount === "number") process.stdout.write(`Tool calls: ${state.totalToolCount}\n`);
440
446
  if (typeof state.finalResponse === "string" && state.finalResponse.trim()) displayedFinalResponse = state.finalResponse.trim();
441
447
  }
442
- if (!snapshot && remote?.lastTurnToolCount !== undefined) process.stdout.write(`工具调用:${remote.lastTurnToolCount}\n`);
448
+ if (!snapshot && remote?.lastTurnToolCount !== undefined) process.stdout.write(`Tool calls: ${remote.lastTurnToolCount}\n`);
443
449
  displayedFinalResponse = remote?.finalResponse?.trim() || displayedFinalResponse;
444
- if (displayedFinalResponse) process.stdout.write(`最终结果:\n${displayedFinalResponse}\n`);
445
- }
446
-
447
- function printServerStatus(data: unknown): void {
448
- const value = data && typeof data === "object" ? data as Record<string, unknown> : {};
449
- const activity = value.activity && typeof value.activity === "object"
450
- ? value.activity as Record<string, unknown>
451
- : {};
452
- process.stdout.write(
453
- value.ready === false
454
- ? "agent-bot server:启动中(正在连接飞书机器人)\n"
455
- : "agent-bot server:运行中\n",
456
- );
457
- process.stdout.write(`PID:${value.pid ?? "-"}\n`);
458
- process.stdout.write(`启动时间:${value.startedAt ?? "-"}\n`);
459
- process.stdout.write(`Supervisor:${value.supervised ? "已启用" : "未启用"}\n`);
460
- process.stdout.write(`运行任务:${activity.runningSessions ?? 0}\n`);
461
- process.stdout.write(`待投递结果:${activity.pendingFinalDeliveries ?? 0}\n`);
462
- process.stdout.write(`安全重启:${value.safeRestartScheduled ? `等待中(${value.safeRestartReason ?? "未注明原因"})` : "未安排"}\n`);
450
+ if (displayedFinalResponse) process.stdout.write(`Final response:\n${displayedFinalResponse}\n`);
463
451
  }
464
452
 
465
453
  function printSkillStatus(status: SkillRegistrationStatus): void {
466
- process.stdout.write(`Agent Bot Skill:${status.registered ? "已注册" : "未注册"}\n`);
467
- process.stdout.write(`系统目录:${status.skillsRoot}\n`);
468
- process.stdout.write(`注册位置:${status.targetPath}\n`);
454
+ process.stdout.write(`Agent Bot Skill: ${status.registered ? "installed" : "not installed"}\n`);
455
+ process.stdout.write(`System directory: ${status.skillsRoot}\n`);
456
+ process.stdout.write(`Installation path: ${status.targetPath}\n`);
469
457
  if (status.registered) {
470
- process.stdout.write(`归属:${status.managed ? "由 Agent Bot 管理" : "外部目录(不会覆盖或删除)"}\n`);
471
- process.stdout.write(`版本:${status.upToDate ? "最新" : "需要更新"}\n`);
458
+ process.stdout.write(`Ownership: ${status.managed ? "managed by Agent Bot" : "external directory (will not be modified or removed)"}\n`);
459
+ process.stdout.write(`Version: ${status.upToDate ? "up to date" : "update required"}\n`);
472
460
  }
473
461
  }
474
462
 
475
463
  function printInitializationPaths(result: InitializationResult): void {
476
- process.stdout.write("正在准备 Agent Bot 用户环境...\n");
477
- process.stdout.write(`用户目录:${result.home.path}(${initializationStatusLabel(result.home.status)})\n`);
478
- process.stdout.write(`配置文件:${result.config.path}(${initializationStatusLabel(result.config.status)})\n`);
479
- process.stdout.write(`环境文件:${result.env.path}(${initializationStatusLabel(result.env.status)})\n`);
480
- process.stdout.write(`数据目录:${result.data.path}(${initializationStatusLabel(result.data.status)})\n`);
481
- process.stdout.write(`日志目录:${result.logs.path}(${initializationStatusLabel(result.logs.status)})\n`);
464
+ process.stdout.write("Preparing the Agent Bot user environment...\n");
465
+ process.stdout.write(`Home directory: ${result.home.path} (${initializationStatusLabel(result.home.status)})\n`);
466
+ process.stdout.write(`Config file: ${result.config.path} (${initializationStatusLabel(result.config.status)})\n`);
467
+ process.stdout.write(`Environment file: ${result.env.path} (${initializationStatusLabel(result.env.status)})\n`);
468
+ process.stdout.write(`Data directory: ${result.data.path} (${initializationStatusLabel(result.data.status)})\n`);
469
+ process.stdout.write(`Log directory: ${result.logs.path} (${initializationStatusLabel(result.logs.status)})\n`);
482
470
  }
483
471
 
484
- function printInitializationResult(result: InitCommandResult): void {
485
- process.stdout.write("\nAgent Bot 初始化完成。\n");
472
+ function printInitializationResult(result: Omit<InitCommandResult, "server">): void {
473
+ process.stdout.write("\nAgent Bot initialization completed.\n");
486
474
  if (result.feishu.status === "created") {
487
- process.stdout.write(`飞书应用:已创建并保存凭证(${result.feishu.appId})\n`);
475
+ process.stdout.write(`Lark app: created and credentials saved (${result.feishu.appId})\n`);
488
476
  } else if (result.feishu.status === "existing") {
489
- process.stdout.write(`飞书应用:已配置,未修改(${result.feishu.appId})\n`);
477
+ process.stdout.write(`Lark app: already configured and unchanged (${result.feishu.appId})\n`);
490
478
  } else {
491
- process.stdout.write("飞书应用:已跳过,可使用 Console 模式或稍后重新运行 init。\n");
479
+ process.stdout.write("Lark app: skipped; use Console mode or run init again later.\n");
492
480
  }
493
481
  if (result.feishu.configuration?.status === "updated") {
494
482
  const added = result.feishu.configuration.added;
495
483
  process.stdout.write(
496
- `飞书配置:已补齐 ${added.scopes.length} 项权限、${added.events.length} 项事件、${added.callbacks.length} 项回调\n`,
484
+ `Lark configuration: added ${added.scopes.length} scopes, ${added.events.length} events, and ${added.callbacks.length} callbacks\n`,
497
485
  );
498
486
  } else if (result.feishu.configuration?.status === "partial") {
499
487
  const configuration = result.feishu.configuration;
@@ -501,45 +489,60 @@ function printInitializationResult(result: InitCommandResult): void {
501
489
  configuration.added.scopes.length + configuration.added.events.length + configuration.added.callbacks.length;
502
490
  if (addedCount > 0) {
503
491
  process.stdout.write(
504
- `飞书配置:核心能力已就绪,已补齐 ${configuration.added.scopes.length} 项权限、${configuration.added.events.length} 项事件、${configuration.added.callbacks.length} 项回调\n`,
492
+ `Lark configuration: core capabilities are ready; added ${configuration.added.scopes.length} scopes, ${configuration.added.events.length} events, and ${configuration.added.callbacks.length} callbacks\n`,
505
493
  );
506
494
  } else {
507
- process.stdout.write("飞书配置:核心能力已就绪\n");
495
+ process.stdout.write("Lark configuration: core capabilities are ready\n");
508
496
  }
509
497
  printOptionalFeishuConfigurationWarnings(configuration.remaining);
510
498
  } else if (result.feishu.configuration?.status === "ready") {
511
- process.stdout.write("飞书配置:权限、事件和回调均已就绪\n");
499
+ process.stdout.write("Lark configuration: scopes, events, and callbacks are ready\n");
500
+ }
501
+ process.stdout.write(`Config file: ${result.config.path}\n`);
502
+ }
503
+
504
+ function printInitializationServerResult(result: InitCommandResult["server"]): void {
505
+ if (result.status === "skipped") {
506
+ process.stdout.write("Agent Bot server: skipped (Lark is not configured; Console mode is available)\n");
507
+ return;
512
508
  }
513
- process.stdout.write(`配置文件:${result.config.path}\n`);
514
- process.stdout.write("下一步:运行 agent-bot server start。\n");
509
+ printServerStartResult(result);
510
+ }
511
+
512
+ function printServerStartResult(result: ServerStartResult): void {
513
+ process.stdout.write(
514
+ result.status === "already-running"
515
+ ? "Agent Bot server is already running.\n"
516
+ : "Agent Bot server started.\n",
517
+ );
515
518
  }
516
519
 
517
520
  function printFeishuVerification(challenge: FeishuAppRegistrationChallenge, json: boolean): void {
518
- process.stderr.write("\n请使用飞书扫码,或在浏览器中打开下面的链接创建机器人应用:\n\n");
521
+ process.stderr.write("\nScan with Lark or open the link below in a browser to create the bot app:\n\n");
519
522
  process.stderr.write(`${challenge.verificationUrl}\n\n`);
520
523
  if (!json) {
521
524
  qrcode.generate(challenge.verificationUrl, { small: true }, (output) => {
522
525
  process.stderr.write(`${output}\n`);
523
526
  });
524
527
  }
525
- process.stderr.write(`链接约在 ${Math.ceil(challenge.expiresIn / 60)} 分钟后过期,正在等待确认...\n`);
528
+ process.stderr.write(`The link expires in about ${Math.ceil(challenge.expiresIn / 60)} minutes. Waiting for confirmation...\n`);
526
529
  }
527
530
 
528
531
  function printFeishuConfigurationVerification(
529
532
  challenge: FeishuConfigurationChallenge,
530
533
  json: boolean,
531
534
  ): void {
532
- process.stderr.write("\n飞书应用还缺少以下配置:\n");
535
+ process.stderr.write("\nThe Lark app is missing the following configuration:\n");
533
536
  if (challenge.missing.scopes.length > 0) {
534
- process.stderr.write(`权限:${challenge.missing.scopes.join(", ")}\n`);
537
+ process.stderr.write(`Scopes: ${challenge.missing.scopes.join(", ")}\n`);
535
538
  }
536
539
  if (challenge.missing.events.length > 0) {
537
- process.stderr.write(`事件:${challenge.missing.events.join(", ")}\n`);
540
+ process.stderr.write(`Events: ${challenge.missing.events.join(", ")}\n`);
538
541
  }
539
542
  if (challenge.missing.callbacks.length > 0) {
540
- process.stderr.write(`回调:${challenge.missing.callbacks.join(", ")}\n`);
543
+ process.stderr.write(`Callbacks: ${challenge.missing.callbacks.join(", ")}\n`);
541
544
  }
542
- process.stderr.write("\n请使用飞书扫码,或在浏览器中打开下面的链接补齐配置:\n\n");
545
+ process.stderr.write("\nScan with Lark or open the link below in a browser to complete the configuration:\n\n");
543
546
  process.stderr.write(`${challenge.verificationUrl}\n\n`);
544
547
  if (!json) {
545
548
  qrcode.generate(challenge.verificationUrl, { small: true }, (output) => {
@@ -547,25 +550,25 @@ function printFeishuConfigurationVerification(
547
550
  });
548
551
  }
549
552
  if (challenge.blocking) {
550
- process.stderr.write("正在等待核心权限和消息事件发布生效...\n");
553
+ process.stderr.write("Waiting for the core scopes and message event to become active...\n");
551
554
  } else {
552
- process.stderr.write("以上配置不影响基础消息收发;初始化将继续,可稍后打开链接授权。\n");
555
+ process.stderr.write("These optional items do not block basic messaging. Initialization will continue; grant them later using the link.\n");
553
556
  }
554
557
  }
555
558
 
556
559
  function printOptionalFeishuConfigurationWarnings(missing: FeishuConfigurationChallenge["missing"]): void {
557
- process.stdout.write("提醒:以下非核心配置尚未生效,部分功能可能不可用。\n");
560
+ process.stdout.write("Warning: some optional configuration is not active, so some features may be unavailable.\n");
558
561
  if (missing.scopes.length > 0) {
559
- process.stdout.write(`未生效权限:${missing.scopes.join(", ")}\n`);
562
+ process.stdout.write(`Inactive scopes: ${missing.scopes.join(", ")}\n`);
560
563
  }
561
564
  if (missing.events.length > 0) {
562
- process.stdout.write(`未生效事件:${missing.events.join(", ")}\n`);
565
+ process.stdout.write(`Inactive events: ${missing.events.join(", ")}\n`);
563
566
  }
564
567
  if (missing.callbacks.length > 0) {
565
- process.stdout.write(`未生效回调:${missing.callbacks.join(", ")}\n`);
568
+ process.stdout.write(`Inactive callbacks: ${missing.callbacks.join(", ")}\n`);
566
569
  }
567
570
  for (const feature of optionalFeishuFeatureWarnings(missing)) {
568
- process.stdout.write(`可能缺失功能:${feature}\n`);
571
+ process.stdout.write(`Affected feature: ${feature}\n`);
569
572
  }
570
573
  }
571
574
 
@@ -575,41 +578,40 @@ function optionalFeishuFeatureWarnings(missing: FeishuConfigurationChallenge["mi
575
578
  const callbacks = new Set(missing.callbacks);
576
579
  const warnings: string[] = [];
577
580
  if (scopes.has("im:chat:create")) {
578
- warnings.push("无法使用 /newgroup 和 /forkgroup 创建飞书群");
581
+ warnings.push("creating Lark groups with /newgroup and /forkgroup");
579
582
  }
580
583
  if (scopes.has("im:chat:read") || events.has("im.chat.updated_v1")) {
581
- warnings.push("修改飞书群名称时无法同步 Agent Bot 任务标题");
584
+ warnings.push("synchronizing Agent Bot task titles after Lark group renames");
582
585
  }
583
586
  if (scopes.has("im:message.reactions:write_only")) {
584
- warnings.push("无法用表情显示消息处理状态");
587
+ warnings.push("showing message-processing status with reactions");
585
588
  }
586
589
  if (scopes.has("im:message:readonly")) {
587
- warnings.push("可能无法读取用户消息中的图片");
590
+ warnings.push("reading images from user messages");
588
591
  }
589
592
  if (scopes.has("im:resource")) {
590
- warnings.push("可能无法上传图片、发送本地图片或设置群头像");
593
+ warnings.push("uploading images, sending local images, or setting group avatars");
591
594
  }
592
595
  if (scopes.has("im:message:update")) {
593
- warnings.push("可能无法更新已发送的进度卡片");
596
+ warnings.push("updating sent progress cards");
594
597
  }
595
598
  if (callbacks.has("card.action.trigger")) {
596
- warnings.push("卡片按钮和交互操作不可用");
599
+ warnings.push("card buttons and interactive actions");
597
600
  }
598
601
  return warnings;
599
602
  }
600
603
 
601
604
  function initializationStatusLabel(status: InitializationStatus): string {
602
- if (status === "created") return "已创建";
603
- return "已存在,未修改";
604
- }
605
-
606
- function printResponse(response: ControlResponse): void {
607
- ensureOk(response);
608
- process.stdout.write(`${response.message ?? "操作成功。"}\n`);
605
+ if (status === "created") return "created";
606
+ return "already exists; unchanged";
609
607
  }
610
608
 
611
609
  function ensureOk(response: ControlResponse): void {
612
- if (!response.ok) throw new Error(response.message ?? "Agent Bot 控制操作失败。");
610
+ if (response.ok) return;
611
+ const message = response.message?.trim();
612
+ throw new Error(message && !/[一-龥]/u.test(message)
613
+ ? message
614
+ : "Agent Bot control operation failed. Check the server logs for details.");
613
615
  }
614
616
 
615
617
  function optionValue(args: string[], name: string): string | undefined {
@@ -617,15 +619,6 @@ function optionValue(args: string[], name: string): string | undefined {
617
619
  return index >= 0 ? args[index + 1] : undefined;
618
620
  }
619
621
 
620
- async function waitForServer(endpoint: string, timeoutMs: number): Promise<boolean> {
621
- const deadline = Date.now() + timeoutMs;
622
- while (Date.now() < deadline) {
623
- if (await isServerRunning(endpoint)) return true;
624
- await new Promise((resolve) => setTimeout(resolve, 200));
625
- }
626
- return false;
627
- }
628
-
629
622
  function printJson(value: unknown): void {
630
623
  process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
631
624
  }