@keyou007/agent-bot 0.1.3 → 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 (56) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +3 -1
  3. package/README.zh.md +3 -1
  4. package/dist/cli/FeishuAppConfiguration.js +10 -10
  5. package/dist/cli/FeishuAppConfiguration.js.map +1 -1
  6. package/dist/cli/FeishuAppRegistration.js +9 -9
  7. package/dist/cli/FeishuAppRegistration.js.map +1 -1
  8. package/dist/cli/Initializer.js +4 -4
  9. package/dist/cli/Initializer.js.map +1 -1
  10. package/dist/cli/LocalControlClient.js +2 -2
  11. package/dist/cli/LocalControlClient.js.map +1 -1
  12. package/dist/cli/LocalControlServer.js +1 -1
  13. package/dist/cli/LocalControlServer.js.map +1 -1
  14. package/dist/cli/ServerStarter.js +3 -3
  15. package/dist/cli/ServerStarter.js.map +1 -1
  16. package/dist/cli/SkillRegistry.js +4 -4
  17. package/dist/cli/SkillRegistry.js.map +1 -1
  18. package/dist/cli/help.js +2 -57
  19. package/dist/cli/help.js.map +1 -1
  20. package/dist/cli/packageVersion.js +1 -1
  21. package/dist/cli/packageVersion.js.map +1 -1
  22. package/dist/cli/profile.js +3 -3
  23. package/dist/cli/profile.js.map +1 -1
  24. package/dist/cli/serverStatus.js +37 -0
  25. package/dist/cli/serverStatus.js.map +1 -0
  26. package/dist/cli/taskChatRoute.js +2 -2
  27. package/dist/cli/taskChatRoute.js.map +1 -1
  28. package/dist/cli.js +112 -116
  29. package/dist/cli.js.map +1 -1
  30. package/dist/feishu/transport.js +1 -1
  31. package/dist/feishu/transport.js.map +1 -1
  32. package/dist/index.js +5 -4
  33. package/dist/index.js.map +1 -1
  34. package/dist/proxy/ProxySessionController.js +10 -10
  35. package/dist/proxy/ProxySessionController.js.map +1 -1
  36. package/docs/technical-reference.md +2 -0
  37. package/docs/technical-reference.zh.md +2 -0
  38. package/npm-shrinkwrap.json +2 -2
  39. package/package.json +1 -1
  40. package/skills/agent-bot/SKILL.md +1 -1
  41. package/src/cli/FeishuAppConfiguration.ts +10 -10
  42. package/src/cli/FeishuAppRegistration.ts +9 -9
  43. package/src/cli/Initializer.ts +4 -4
  44. package/src/cli/LocalControlClient.ts +2 -2
  45. package/src/cli/LocalControlServer.ts +1 -1
  46. package/src/cli/ServerStarter.ts +3 -3
  47. package/src/cli/SkillRegistry.ts +4 -4
  48. package/src/cli/help.ts +2 -66
  49. package/src/cli/packageVersion.ts +1 -1
  50. package/src/cli/profile.ts +3 -3
  51. package/src/cli/serverStatus.ts +39 -0
  52. package/src/cli/taskChatRoute.ts +2 -2
  53. package/src/cli.ts +113 -119
  54. package/src/feishu/transport.ts +1 -1
  55. package/src/index.ts +5 -4
  56. package/src/proxy/ProxySessionController.ts +10 -10
package/src/cli.ts CHANGED
@@ -30,6 +30,7 @@ import {
30
30
  type FeishuConfigurationChallenge,
31
31
  } from "./cli/FeishuAppConfiguration.js";
32
32
  import { renderCliHelp } from "./cli/help.js";
33
+ import { formatServerStatus, withConfiguredFeishuAppId } from "./cli/serverStatus.js";
33
34
  import { resolveSystemSkillsRoot, SkillRegistry, type SkillRegistrationStatus } from "./cli/SkillRegistry.js";
34
35
  import { readPackageVersion } from "./cli/packageVersion.js";
35
36
  import { applyExplicitProfile, parseGlobalOptions } from "./cli/profile.js";
@@ -50,7 +51,7 @@ import {
50
51
  const args = process.argv.slice(2);
51
52
 
52
53
  void main(args).catch((error: unknown) => {
53
- process.stderr.write(`错误:${error instanceof Error ? error.message : String(error)}\n`);
54
+ process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}\n`);
54
55
  process.exitCode = 1;
55
56
  });
56
57
 
@@ -87,7 +88,7 @@ async function main(input: string[]): Promise<void> {
87
88
  skillsCommand(rest);
88
89
  return;
89
90
  }
90
- throw new Error(`未知命令:${command}。使用 agent-bot --help 查看帮助。`);
91
+ throw new Error(`Unknown command: ${command}. Run agent-bot --help for usage.`);
91
92
  }
92
93
 
93
94
  type FeishuInitializationStatus = "created" | "existing" | "skipped";
@@ -104,12 +105,12 @@ interface InitCommandResult extends InitializationResult {
104
105
  async function initCommand(input: string[], configPath?: string): Promise<void> {
105
106
  const supported = new Set(["--json", "--skip-feishu", "--reconfigure-feishu"]);
106
107
  const unsupported = input.filter((value) => !supported.has(value));
107
- if (unsupported.length > 0) throw new Error(`init 不支持参数:${unsupported.join(" ")}`);
108
+ if (unsupported.length > 0) throw new Error(`Unsupported init options: ${unsupported.join(" ")}`);
108
109
  const json = input.includes("--json");
109
110
  const skipFeishu = input.includes("--skip-feishu");
110
111
  const reconfigureFeishu = input.includes("--reconfigure-feishu");
111
112
  if (skipFeishu && reconfigureFeishu) {
112
- throw new Error("--skip-feishu 和 --reconfigure-feishu 不能同时使用。");
113
+ throw new Error("--skip-feishu and --reconfigure-feishu cannot be used together.");
113
114
  }
114
115
 
115
116
  const paths = initializeAgentBot({ configPath });
@@ -129,7 +130,7 @@ async function initCommand(input: string[], configPath?: string): Promise<void>
129
130
  }
130
131
 
131
132
  if (!json) printInitializationResult(initialized);
132
- if (!json && !skipFeishu) process.stdout.write("\n正在启动 Agent Bot server...\n");
133
+ if (!json && !skipFeishu) process.stdout.write("\nStarting Agent Bot server...\n");
133
134
  const server = await startInitializedServer({ skipFeishu, configPath });
134
135
  const result: InitCommandResult = { ...initialized, server };
135
136
  if (json) printJson(result);
@@ -149,7 +150,7 @@ async function initializeFeishu(
149
150
  }
150
151
 
151
152
  const controller = new AbortController();
152
- const onInterrupt = (): void => controller.abort(new Error("已取消初始化。"));
153
+ const onInterrupt = (): void => controller.abort(new Error("Initialization was cancelled."));
153
154
  process.once("SIGINT", onInterrupt);
154
155
  process.once("SIGTERM", onInterrupt);
155
156
  try {
@@ -164,7 +165,7 @@ async function initializeFeishu(
164
165
  status = "existing";
165
166
  } else {
166
167
  if (existing.status === "incomplete" && !options.json) {
167
- process.stdout.write("\n检测到未完整保存的飞书凭据,将重新创建机器人。\n");
168
+ process.stdout.write("\nIncomplete Lark credentials were found. A new bot will be created.\n");
168
169
  }
169
170
  credentials = await registerFeishuApp({
170
171
  signal: controller.signal,
@@ -174,7 +175,7 @@ async function initializeFeishu(
174
175
  status = "created";
175
176
  }
176
177
 
177
- if (!options.json) process.stdout.write("\n正在检查飞书应用权限、事件和回调配置...\n");
178
+ if (!options.json) process.stdout.write("\nChecking Lark app permissions, events, and callbacks...\n");
178
179
  const configuration = await ensureFeishuAppConfiguration(credentials, {
179
180
  signal: controller.signal,
180
181
  onVerification: (challenge) => printFeishuConfigurationVerification(challenge, options.json),
@@ -201,16 +202,16 @@ function skillsCommand(input: string[]): void {
201
202
  const result = registry.install();
202
203
  if (json) printJson(result);
203
204
  else process.stdout.write(result.updated
204
- ? `已注册 Agent Bot Skill:${result.status.targetPath}\n`
205
- : `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`);
206
207
  return;
207
208
  }
208
209
  if (action === "uninstall" || action === "unregister") {
209
210
  const removed = registry.uninstall();
210
211
  if (json) printJson({ removed, targetPath: registry.targetPath });
211
212
  else process.stdout.write(removed
212
- ? `已反注册 Agent Bot Skill:${registry.targetPath}\n`
213
- : `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`);
214
215
  return;
215
216
  }
216
217
  if (action === "status") {
@@ -223,13 +224,13 @@ function skillsCommand(input: string[]): void {
223
224
  const paths = { sourcePath: registry.sourcePath, skillsRoot: registry.skillsRoot, targetPath: registry.targetPath };
224
225
  if (json) printJson(paths);
225
226
  else {
226
- process.stdout.write(`内置 Skill:${paths.sourcePath}\n`);
227
- process.stdout.write(`系统 Skills:${paths.skillsRoot}\n`);
228
- 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`);
229
230
  }
230
231
  return;
231
232
  }
232
- throw new Error(`未知 skills 命令:${action}`);
233
+ throw new Error(`Unknown skills command: ${action}`);
233
234
  }
234
235
 
235
236
  async function consoleCommand(input: string[]): Promise<void> {
@@ -237,7 +238,9 @@ async function consoleCommand(input: string[]): Promise<void> {
237
238
  const endpoint = controlEndpoint(config.storage.sqlitePath);
238
239
  const force = input.includes("--force");
239
240
  if (!force && await isServerReachable(endpoint)) {
240
- 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
+ );
241
244
  }
242
245
  const entry = fileURLToPath(new URL("./index.js", import.meta.url));
243
246
  const reportDirectory = resolveSupervisorDiagnosticsPaths(config).crashReportDirectory;
@@ -262,11 +265,16 @@ async function serverCommand(input: string[]): Promise<void> {
262
265
  try {
263
266
  const response = await sendControlRequest(endpoint, { action: "health" }, 1_500);
264
267
  ensureOk(response);
265
- if (rest.includes("--json")) printJson(response.data);
266
- 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));
267
271
  } catch {
268
- if (rest.includes("--json")) printJson({ running: false });
269
- 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));
270
278
  process.exitCode = 3;
271
279
  }
272
280
  return;
@@ -276,21 +284,23 @@ async function serverCommand(input: string[]): Promise<void> {
276
284
  return;
277
285
  }
278
286
  if (action === "stop") {
279
- 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");
280
289
  return;
281
290
  }
282
291
  if (action === "restart") {
283
292
  const immediate = rest.includes("--immediate") || rest.includes("--force");
284
293
  const reason = optionValue(rest, "--reason")
285
- ?? (immediate ? "通过 agent-bot CLI 立即重启" : "通过 agent-bot CLI 安全重启");
286
- printResponse(await sendControlRequest(endpoint, {
294
+ ?? (immediate ? "通过 Agent Bot CLI 立即重启" : "通过 Agent Bot CLI 安全重启");
295
+ ensureOk(await sendControlRequest(endpoint, {
287
296
  action: "server_restart",
288
297
  mode: immediate ? "immediate" : "safe",
289
298
  reason,
290
299
  }));
300
+ process.stdout.write(immediate ? "Immediate restart requested.\n" : "Safe restart requested.\n");
291
301
  return;
292
302
  }
293
- throw new Error(`未知 server 命令:${action}`);
303
+ throw new Error(`Unknown server command: ${action}`);
294
304
  }
295
305
 
296
306
  async function taskCommand(input: string[]): Promise<void> {
@@ -301,16 +311,17 @@ async function taskCommand(input: string[]): Promise<void> {
301
311
  const allSessions = store.listAllSessions();
302
312
  if (action === "prompt" || action === "send") {
303
313
  const [reference, ...promptParts] = rest;
304
- if (!reference) throw new Error(`task ${action} 需要任务序号或任务 ID。`);
314
+ if (!reference) throw new Error(`task ${action} requires a task number or task ID.`);
305
315
  const text = promptParts.join(" ").trim();
306
- if (!text) throw new Error(`task ${action} 需要要发送的 Prompt。`);
316
+ if (!text) throw new Error(`task ${action} requires a Prompt.`);
307
317
  const session = resolveTask(allSessions, reference);
308
318
  const endpoint = controlEndpoint(config.storage.sqlitePath);
309
- printResponse(await sendControlRequest(endpoint, {
319
+ ensureOk(await sendControlRequest(endpoint, {
310
320
  action: "task_prompt",
311
321
  localSessionId: session.localSessionId,
312
322
  text,
313
323
  }, 60_000));
324
+ process.stdout.write("Prompt submitted.\n");
314
325
  return;
315
326
  }
316
327
  const sessions = filterSessions(allSessions, rest);
@@ -320,7 +331,7 @@ async function taskCommand(input: string[]): Promise<void> {
320
331
  return;
321
332
  }
322
333
  const reference = rest.find((value) => !value.startsWith("--"));
323
- if (!reference) throw new Error(`task ${action} 需要任务序号或任务 ID。`);
334
+ if (!reference) throw new Error(`task ${action} requires a task number or task ID.`);
324
335
  const session = resolveTask(sessions, reference);
325
336
  if (action === "chat") {
326
337
  const route = taskChatRoute(session);
@@ -355,7 +366,8 @@ async function taskCommand(input: string[]): Promise<void> {
355
366
  }
356
367
  const endpoint = controlEndpoint(config.storage.sqlitePath);
357
368
  if (action === "stop") {
358
- 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");
359
371
  return;
360
372
  }
361
373
  if (action === "title") {
@@ -363,15 +375,16 @@ async function taskCommand(input: string[]): Promise<void> {
363
375
  const titleArgs = rest.slice(referenceIndex + 1);
364
376
  const optionIndex = titleArgs.findIndex((value) => value.startsWith("--"));
365
377
  const title = titleArgs.slice(0, optionIndex < 0 ? undefined : optionIndex).join(" ").trim();
366
- if (!title) throw new Error("task title 需要新标题。");
367
- printResponse(await sendControlRequest(endpoint, {
378
+ if (!title) throw new Error("task title requires a new title.");
379
+ ensureOk(await sendControlRequest(endpoint, {
368
380
  action: "task_title",
369
381
  localSessionId: session.localSessionId,
370
382
  title,
371
383
  }));
384
+ process.stdout.write("Task title updated.\n");
372
385
  return;
373
386
  }
374
- throw new Error(`未知 task 命令:${action}`);
387
+ throw new Error(`Unknown task command: ${action}`);
375
388
  } finally {
376
389
  store.close();
377
390
  }
@@ -389,7 +402,7 @@ function resolveTask(sessions: SessionRecord[], reference: string): SessionRecor
389
402
  if (/^\d+$/.test(reference)) {
390
403
  const index = Number(reference) - 1;
391
404
  const session = sessions[index];
392
- if (!session) throw new Error(`任务序号超出范围:${reference}`);
405
+ if (!session) throw new Error(`Task number is out of range: ${reference}`);
393
406
  return session;
394
407
  }
395
408
  const exact = sessions.find((session) =>
@@ -400,93 +413,75 @@ function resolveTask(sessions: SessionRecord[], reference: string): SessionRecor
400
413
  || session.remoteSessionId?.startsWith(reference)
401
414
  || session.acpSessionId?.startsWith(reference));
402
415
  if (matches.length === 1) return matches[0]!;
403
- if (matches.length > 1) throw new Error(`任务 ID 前缀不唯一:${reference}`);
404
- 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}`);
405
418
  }
406
419
 
407
420
  function printTaskList(sessions: SessionRecord[]): void {
408
421
  if (sessions.length === 0) {
409
- process.stdout.write("没有任务。\n");
422
+ process.stdout.write("No tasks.\n");
410
423
  return;
411
424
  }
412
425
  for (const [index, session] of sessions.entries()) {
413
426
  const id = session.remoteSessionId ?? session.acpSessionId ?? session.localSessionId;
414
- 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`);
415
428
  process.stdout.write(` ${id} · ${session.contextKey} · ${session.updatedAt}\n`);
416
429
  }
417
430
  }
418
431
 
419
432
  function printTaskStatus(session: SessionRecord, snapshot: unknown, remote?: TaskStatusControlData["remote"]): void {
420
- process.stdout.write(`标题:${session.title ?? "未命名任务"}\n`);
421
- process.stdout.write(`任务 ID:${session.remoteSessionId ?? session.acpSessionId ?? session.localSessionId}\n`);
422
- process.stdout.write(`本地 ID:${session.localSessionId}\n`);
423
- process.stdout.write(`上下文:${session.contextKey}\n`);
424
- process.stdout.write(`状态:${session.status} / ${session.lastTurnStatus ?? "-"}\n`);
425
- process.stdout.write(`目录:${session.cwd}\n`);
426
- 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`);
427
440
  let displayedFinalResponse: string | undefined;
428
441
  if (snapshot && typeof snapshot === "object") {
429
442
  const state = snapshot as Record<string, unknown>;
430
- 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`);
431
444
  if (typeof state.totalTokens === "number") process.stdout.write(`Tokens:${state.totalTokens}\n`);
432
- 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`);
433
446
  if (typeof state.finalResponse === "string" && state.finalResponse.trim()) displayedFinalResponse = state.finalResponse.trim();
434
447
  }
435
- 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`);
436
449
  displayedFinalResponse = remote?.finalResponse?.trim() || displayedFinalResponse;
437
- if (displayedFinalResponse) process.stdout.write(`最终结果:\n${displayedFinalResponse}\n`);
438
- }
439
-
440
- function printServerStatus(data: unknown): void {
441
- const value = data && typeof data === "object" ? data as Record<string, unknown> : {};
442
- const activity = value.activity && typeof value.activity === "object"
443
- ? value.activity as Record<string, unknown>
444
- : {};
445
- process.stdout.write(
446
- value.ready === false
447
- ? "agent-bot server:启动中(正在连接飞书机器人)\n"
448
- : "agent-bot server:运行中\n",
449
- );
450
- process.stdout.write(`PID:${value.pid ?? "-"}\n`);
451
- process.stdout.write(`启动时间:${value.startedAt ?? "-"}\n`);
452
- process.stdout.write(`Supervisor:${value.supervised ? "已启用" : "未启用"}\n`);
453
- process.stdout.write(`运行任务:${activity.runningSessions ?? 0}\n`);
454
- process.stdout.write(`待投递结果:${activity.pendingFinalDeliveries ?? 0}\n`);
455
- process.stdout.write(`安全重启:${value.safeRestartScheduled ? `等待中(${value.safeRestartReason ?? "未注明原因"})` : "未安排"}\n`);
450
+ if (displayedFinalResponse) process.stdout.write(`Final response:\n${displayedFinalResponse}\n`);
456
451
  }
457
452
 
458
453
  function printSkillStatus(status: SkillRegistrationStatus): void {
459
- process.stdout.write(`Agent Bot Skill:${status.registered ? "已注册" : "未注册"}\n`);
460
- process.stdout.write(`系统目录:${status.skillsRoot}\n`);
461
- 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`);
462
457
  if (status.registered) {
463
- process.stdout.write(`归属:${status.managed ? "由 Agent Bot 管理" : "外部目录(不会覆盖或删除)"}\n`);
464
- 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`);
465
460
  }
466
461
  }
467
462
 
468
463
  function printInitializationPaths(result: InitializationResult): void {
469
- process.stdout.write("正在准备 Agent Bot 用户环境...\n");
470
- process.stdout.write(`用户目录:${result.home.path}(${initializationStatusLabel(result.home.status)})\n`);
471
- process.stdout.write(`配置文件:${result.config.path}(${initializationStatusLabel(result.config.status)})\n`);
472
- process.stdout.write(`环境文件:${result.env.path}(${initializationStatusLabel(result.env.status)})\n`);
473
- process.stdout.write(`数据目录:${result.data.path}(${initializationStatusLabel(result.data.status)})\n`);
474
- 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`);
475
470
  }
476
471
 
477
472
  function printInitializationResult(result: Omit<InitCommandResult, "server">): void {
478
- process.stdout.write("\nAgent Bot 初始化完成。\n");
473
+ process.stdout.write("\nAgent Bot initialization completed.\n");
479
474
  if (result.feishu.status === "created") {
480
- process.stdout.write(`飞书应用:已创建并保存凭证(${result.feishu.appId})\n`);
475
+ process.stdout.write(`Lark app: created and credentials saved (${result.feishu.appId})\n`);
481
476
  } else if (result.feishu.status === "existing") {
482
- process.stdout.write(`飞书应用:已配置,未修改(${result.feishu.appId})\n`);
477
+ process.stdout.write(`Lark app: already configured and unchanged (${result.feishu.appId})\n`);
483
478
  } else {
484
- process.stdout.write("飞书应用:已跳过,可使用 Console 模式或稍后重新运行 init。\n");
479
+ process.stdout.write("Lark app: skipped; use Console mode or run init again later.\n");
485
480
  }
486
481
  if (result.feishu.configuration?.status === "updated") {
487
482
  const added = result.feishu.configuration.added;
488
483
  process.stdout.write(
489
- `飞书配置:已补齐 ${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`,
490
485
  );
491
486
  } else if (result.feishu.configuration?.status === "partial") {
492
487
  const configuration = result.feishu.configuration;
@@ -494,21 +489,21 @@ function printInitializationResult(result: Omit<InitCommandResult, "server">): v
494
489
  configuration.added.scopes.length + configuration.added.events.length + configuration.added.callbacks.length;
495
490
  if (addedCount > 0) {
496
491
  process.stdout.write(
497
- `飞书配置:核心能力已就绪,已补齐 ${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`,
498
493
  );
499
494
  } else {
500
- process.stdout.write("飞书配置:核心能力已就绪\n");
495
+ process.stdout.write("Lark configuration: core capabilities are ready\n");
501
496
  }
502
497
  printOptionalFeishuConfigurationWarnings(configuration.remaining);
503
498
  } else if (result.feishu.configuration?.status === "ready") {
504
- process.stdout.write("飞书配置:权限、事件和回调均已就绪\n");
499
+ process.stdout.write("Lark configuration: scopes, events, and callbacks are ready\n");
505
500
  }
506
- process.stdout.write(`配置文件:${result.config.path}\n`);
501
+ process.stdout.write(`Config file: ${result.config.path}\n`);
507
502
  }
508
503
 
509
504
  function printInitializationServerResult(result: InitCommandResult["server"]): void {
510
505
  if (result.status === "skipped") {
511
- process.stdout.write("Agent Bot server:已跳过(未配置飞书,可使用 Console 模式)\n");
506
+ process.stdout.write("Agent Bot server: skipped (Lark is not configured; Console mode is available)\n");
512
507
  return;
513
508
  }
514
509
  printServerStartResult(result);
@@ -517,37 +512,37 @@ function printInitializationServerResult(result: InitCommandResult["server"]): v
517
512
  function printServerStartResult(result: ServerStartResult): void {
518
513
  process.stdout.write(
519
514
  result.status === "already-running"
520
- ? "agent-bot server 已在运行。\n"
521
- : "agent-bot server 已启动。\n",
515
+ ? "Agent Bot server is already running.\n"
516
+ : "Agent Bot server started.\n",
522
517
  );
523
518
  }
524
519
 
525
520
  function printFeishuVerification(challenge: FeishuAppRegistrationChallenge, json: boolean): void {
526
- 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");
527
522
  process.stderr.write(`${challenge.verificationUrl}\n\n`);
528
523
  if (!json) {
529
524
  qrcode.generate(challenge.verificationUrl, { small: true }, (output) => {
530
525
  process.stderr.write(`${output}\n`);
531
526
  });
532
527
  }
533
- 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`);
534
529
  }
535
530
 
536
531
  function printFeishuConfigurationVerification(
537
532
  challenge: FeishuConfigurationChallenge,
538
533
  json: boolean,
539
534
  ): void {
540
- process.stderr.write("\n飞书应用还缺少以下配置:\n");
535
+ process.stderr.write("\nThe Lark app is missing the following configuration:\n");
541
536
  if (challenge.missing.scopes.length > 0) {
542
- process.stderr.write(`权限:${challenge.missing.scopes.join(", ")}\n`);
537
+ process.stderr.write(`Scopes: ${challenge.missing.scopes.join(", ")}\n`);
543
538
  }
544
539
  if (challenge.missing.events.length > 0) {
545
- process.stderr.write(`事件:${challenge.missing.events.join(", ")}\n`);
540
+ process.stderr.write(`Events: ${challenge.missing.events.join(", ")}\n`);
546
541
  }
547
542
  if (challenge.missing.callbacks.length > 0) {
548
- process.stderr.write(`回调:${challenge.missing.callbacks.join(", ")}\n`);
543
+ process.stderr.write(`Callbacks: ${challenge.missing.callbacks.join(", ")}\n`);
549
544
  }
550
- 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");
551
546
  process.stderr.write(`${challenge.verificationUrl}\n\n`);
552
547
  if (!json) {
553
548
  qrcode.generate(challenge.verificationUrl, { small: true }, (output) => {
@@ -555,25 +550,25 @@ function printFeishuConfigurationVerification(
555
550
  });
556
551
  }
557
552
  if (challenge.blocking) {
558
- process.stderr.write("正在等待核心权限和消息事件发布生效...\n");
553
+ process.stderr.write("Waiting for the core scopes and message event to become active...\n");
559
554
  } else {
560
- 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");
561
556
  }
562
557
  }
563
558
 
564
559
  function printOptionalFeishuConfigurationWarnings(missing: FeishuConfigurationChallenge["missing"]): void {
565
- process.stdout.write("提醒:以下非核心配置尚未生效,部分功能可能不可用。\n");
560
+ process.stdout.write("Warning: some optional configuration is not active, so some features may be unavailable.\n");
566
561
  if (missing.scopes.length > 0) {
567
- process.stdout.write(`未生效权限:${missing.scopes.join(", ")}\n`);
562
+ process.stdout.write(`Inactive scopes: ${missing.scopes.join(", ")}\n`);
568
563
  }
569
564
  if (missing.events.length > 0) {
570
- process.stdout.write(`未生效事件:${missing.events.join(", ")}\n`);
565
+ process.stdout.write(`Inactive events: ${missing.events.join(", ")}\n`);
571
566
  }
572
567
  if (missing.callbacks.length > 0) {
573
- process.stdout.write(`未生效回调:${missing.callbacks.join(", ")}\n`);
568
+ process.stdout.write(`Inactive callbacks: ${missing.callbacks.join(", ")}\n`);
574
569
  }
575
570
  for (const feature of optionalFeishuFeatureWarnings(missing)) {
576
- process.stdout.write(`可能缺失功能:${feature}\n`);
571
+ process.stdout.write(`Affected feature: ${feature}\n`);
577
572
  }
578
573
  }
579
574
 
@@ -583,41 +578,40 @@ function optionalFeishuFeatureWarnings(missing: FeishuConfigurationChallenge["mi
583
578
  const callbacks = new Set(missing.callbacks);
584
579
  const warnings: string[] = [];
585
580
  if (scopes.has("im:chat:create")) {
586
- warnings.push("无法使用 /newgroup 和 /forkgroup 创建飞书群");
581
+ warnings.push("creating Lark groups with /newgroup and /forkgroup");
587
582
  }
588
583
  if (scopes.has("im:chat:read") || events.has("im.chat.updated_v1")) {
589
- warnings.push("修改飞书群名称时无法同步 Agent Bot 任务标题");
584
+ warnings.push("synchronizing Agent Bot task titles after Lark group renames");
590
585
  }
591
586
  if (scopes.has("im:message.reactions:write_only")) {
592
- warnings.push("无法用表情显示消息处理状态");
587
+ warnings.push("showing message-processing status with reactions");
593
588
  }
594
589
  if (scopes.has("im:message:readonly")) {
595
- warnings.push("可能无法读取用户消息中的图片");
590
+ warnings.push("reading images from user messages");
596
591
  }
597
592
  if (scopes.has("im:resource")) {
598
- warnings.push("可能无法上传图片、发送本地图片或设置群头像");
593
+ warnings.push("uploading images, sending local images, or setting group avatars");
599
594
  }
600
595
  if (scopes.has("im:message:update")) {
601
- warnings.push("可能无法更新已发送的进度卡片");
596
+ warnings.push("updating sent progress cards");
602
597
  }
603
598
  if (callbacks.has("card.action.trigger")) {
604
- warnings.push("卡片按钮和交互操作不可用");
599
+ warnings.push("card buttons and interactive actions");
605
600
  }
606
601
  return warnings;
607
602
  }
608
603
 
609
604
  function initializationStatusLabel(status: InitializationStatus): string {
610
- if (status === "created") return "已创建";
611
- return "已存在,未修改";
612
- }
613
-
614
- function printResponse(response: ControlResponse): void {
615
- ensureOk(response);
616
- process.stdout.write(`${response.message ?? "操作成功。"}\n`);
605
+ if (status === "created") return "created";
606
+ return "already exists; unchanged";
617
607
  }
618
608
 
619
609
  function ensureOk(response: ControlResponse): void {
620
- 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.");
621
615
  }
622
616
 
623
617
  function optionValue(args: string[], name: string): string | undefined {
@@ -8,7 +8,7 @@ type FeishuTransportConfig = Pick<
8
8
  export function requireServerFeishuTransport(config: FeishuTransportConfig): "sdk" {
9
9
  if (!config.appId || !config.appSecret) {
10
10
  throw new Error(
11
- "飞书机器人尚未配置。请先运行 agent-bot init 完成初始化;仅需本地调试时请使用 agent-bot console。",
11
+ "The Lark bot is not configured. Run agent-bot init first, or use agent-bot console for local-only testing.",
12
12
  );
13
13
  }
14
14
  return "sdk";
package/src/index.ts CHANGED
@@ -177,6 +177,7 @@ async function handleControlRequest(request: ControlRequest): Promise<ControlRes
177
177
  pid: process.pid,
178
178
  startedAt: processStartedAt.toISOString(),
179
179
  supervised,
180
+ feishuAppId: config.feishu.appId ?? null,
180
181
  safeRestartScheduled: safeRestart.scheduled,
181
182
  safeRestartReason: safeRestart.pendingReason,
182
183
  activity: store.getServerActivityState(),
@@ -188,16 +189,16 @@ async function handleControlRequest(request: ControlRequest): Promise<ControlRes
188
189
  return {
189
190
  ok: true,
190
191
  message: newlyScheduled
191
- ? "已安排安全重启;将在全部任务完成并持续空闲 15 秒后执行。"
192
- : "安全重启已在等待中,已更新重启原因。",
192
+ ? "Safe restart scheduled. It will run after all tasks finish and the server stays idle for 15 seconds."
193
+ : "A safe restart is already pending. Its reason has been updated.",
193
194
  };
194
195
  }
195
196
  setTimeout(() => void initiateRestart(request.reason), 25);
196
- return { ok: true, message: "已请求立即重启。" };
197
+ return { ok: true, message: "Immediate restart requested." };
197
198
  case "server_stop":
198
199
  safeRestart.cancel();
199
200
  setTimeout(() => void shutdown(STOP_EXIT_CODE), 25);
200
- return { ok: true, message: "已请求停止 Agent Bot server。" };
201
+ return { ok: true, message: "Agent Bot server stop requested." };
201
202
  case "task_status":
202
203
  return { ok: true, data: await controller.controlGetTaskStatus(request.localSessionId) };
203
204
  case "task_stop":
@@ -415,9 +415,9 @@ export class ProxySessionController {
415
415
 
416
416
  async controlStopTask(localSessionId: string): Promise<string> {
417
417
  const record = this.store.getSession(localSessionId);
418
- if (!record || record.status === "closed") throw new Error(`找不到任务:${localSessionId}`);
418
+ if (!record || record.status === "closed") throw new Error(`Task not found: ${localSessionId}`);
419
419
  await this.cancelSession(record);
420
- return `已请求停止任务:${record.title ?? record.remoteSessionId ?? record.localSessionId}`;
420
+ return `Task stop requested: ${record.title ?? record.remoteSessionId ?? record.localSessionId}`;
421
421
  }
422
422
 
423
423
  async controlGetTaskStatus(localSessionId: string): Promise<{
@@ -426,7 +426,7 @@ export class ProxySessionController {
426
426
  remote?: RemoteSessionSummary;
427
427
  }> {
428
428
  const record = this.store.getSession(localSessionId);
429
- if (!record) throw new Error(`找不到任务:${localSessionId}`);
429
+ if (!record) throw new Error(`Task not found: ${localSessionId}`);
430
430
  let remote: RemoteSessionSummary | undefined;
431
431
  if (record.remoteSessionId) {
432
432
  const runtime = this.runtimes.forAgent(this.ensureAgent(record.agentName));
@@ -447,9 +447,9 @@ export class ProxySessionController {
447
447
 
448
448
  async controlSetTaskTitle(localSessionId: string, title: string): Promise<string> {
449
449
  const normalizedTitle = normalizeTaskTitle(title);
450
- if (!normalizedTitle) throw new Error("任务标题不能为空。");
450
+ if (!normalizedTitle) throw new Error("The task title cannot be empty.");
451
451
  const record = this.store.getSession(localSessionId);
452
- if (!record || record.status === "closed") throw new Error(`找不到任务:${localSessionId}`);
452
+ if (!record || record.status === "closed") throw new Error(`Task not found: ${localSessionId}`);
453
453
  const loaded = await this.loadSession(record);
454
454
  if (loaded.runtime.setTitle) await loaded.runtime.setTitle(record.localSessionId, normalizedTitle);
455
455
  else loaded.session.title = normalizedTitle;
@@ -460,14 +460,14 @@ export class ProxySessionController {
460
460
  title: normalizedTitle,
461
461
  source: "cli",
462
462
  });
463
- return `任务标题已修改为:${normalizedTitle}`;
463
+ return `Task title changed to: ${normalizedTitle}`;
464
464
  }
465
465
 
466
466
  async controlSendTaskPrompt(localSessionId: string, text: string): Promise<string> {
467
467
  const promptText = text.trim();
468
- if (!promptText) throw new Error("Prompt 不能为空。");
468
+ if (!promptText) throw new Error("The Prompt cannot be empty.");
469
469
  const record = this.store.getSession(localSessionId);
470
- if (!record || record.status === "closed") throw new Error(`找不到任务:${localSessionId}`);
470
+ if (!record || record.status === "closed") throw new Error(`Task not found: ${localSessionId}`);
471
471
  const runtime = this.runtimes.forAgent(this.ensureAgent(record.agentName));
472
472
  const activeTurnId = runtime.getSession(localSessionId)?.activeTurnId;
473
473
  const routedContextKey = this.outbound.getSessionContextKey(localSessionId);
@@ -487,7 +487,7 @@ export class ProxySessionController {
487
487
  ?? lastSnapshot?.replyTarget
488
488
  ?? routedReplyTarget;
489
489
  if (isThreadContextKey(responseContextKey) && !existingReplyTarget) {
490
- throw new Error("无法确定目标任务的话题回复位置,未发送 Prompt。");
490
+ throw new Error("Could not resolve the target task's thread reply location. The Prompt was not sent.");
491
491
  }
492
492
  const scopedRecord = this.store.getSessionForContext(localSessionId, responseContextKey)
493
493
  ?? { ...record, contextKey: responseContextKey };
@@ -521,7 +521,7 @@ export class ProxySessionController {
521
521
  } finally {
522
522
  if (this.messageQueues.get(responseContextKey) === next) this.messageQueues.delete(responseContextKey);
523
523
  }
524
- return `已通过机器人向原会话发送 Prompt,并提交给任务:${record.title ?? record.remoteSessionId ?? record.localSessionId}`;
524
+ return `The Prompt was posted to the original chat and submitted to the task: ${record.title ?? record.remoteSessionId ?? record.localSessionId}`;
525
525
  }
526
526
 
527
527
  private cardActionContextKey(action: CardAction): string {