@keyou007/agent-bot 0.1.3 → 0.1.5

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