@keyou007/agent-bot 0.1.11 → 0.1.12

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 (82) hide show
  1. package/.env.example +1 -1
  2. package/CHANGELOG.md +10 -0
  3. package/README.md +41 -35
  4. package/README.zh.md +41 -35
  5. package/config.example.yaml +11 -1
  6. package/dist/cli/FeishuAppConfiguration.js +1 -1
  7. package/dist/cli/FeishuAppConfiguration.js.map +1 -1
  8. package/dist/cli/FeishuAppRegistration.js +2 -2
  9. package/dist/cli/FeishuAppRegistration.js.map +1 -1
  10. package/dist/cli/Initializer.js +1 -1
  11. package/dist/cli/Initializer.js.map +1 -1
  12. package/dist/cli/ServerStarter.js +7 -2
  13. package/dist/cli/ServerStarter.js.map +1 -1
  14. package/dist/cli/controlProtocol.js.map +1 -1
  15. package/dist/cli/help.js +20 -16
  16. package/dist/cli/help.js.map +1 -1
  17. package/dist/cli/taskGroupOptions.js +95 -0
  18. package/dist/cli/taskGroupOptions.js.map +1 -0
  19. package/dist/cli.js +74 -2
  20. package/dist/cli.js.map +1 -1
  21. package/dist/codex/CodexRuntime.js +35 -9
  22. package/dist/codex/CodexRuntime.js.map +1 -1
  23. package/dist/config/loadConfig.js +11 -1
  24. package/dist/config/loadConfig.js.map +1 -1
  25. package/dist/config/schema.js +6 -1
  26. package/dist/config/schema.js.map +1 -1
  27. package/dist/console/ConsoleConnector.js +1 -1
  28. package/dist/console/ConsoleConnector.js.map +1 -1
  29. package/dist/console/ConsoleTurnPresenter.js +14 -7
  30. package/dist/console/ConsoleTurnPresenter.js.map +1 -1
  31. package/dist/deprecated-cli.js +5 -0
  32. package/dist/deprecated-cli.js.map +1 -0
  33. package/dist/feishu/CardRenderer.js +5 -4
  34. package/dist/feishu/CardRenderer.js.map +1 -1
  35. package/dist/feishu/FeishuTurnPresenter.js +9 -5
  36. package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
  37. package/dist/feishu/transport.js +1 -1
  38. package/dist/feishu/transport.js.map +1 -1
  39. package/dist/index.js +20 -2
  40. package/dist/index.js.map +1 -1
  41. package/dist/presentation/OutboundRouter.js +2 -2
  42. package/dist/presentation/OutboundRouter.js.map +1 -1
  43. package/dist/presentation/TurnStateReducer.js +2 -1
  44. package/dist/presentation/TurnStateReducer.js.map +1 -1
  45. package/dist/proxy/ProxySessionController.js +78 -22
  46. package/dist/proxy/ProxySessionController.js.map +1 -1
  47. package/dist/runtime/createAgentRuntimeRegistry.js +1 -1
  48. package/dist/runtime/createAgentRuntimeRegistry.js.map +1 -1
  49. package/dist/supervision/systemEnvironment.js +129 -0
  50. package/dist/supervision/systemEnvironment.js.map +1 -0
  51. package/dist/supervisor.js +13 -1
  52. package/dist/supervisor.js.map +1 -1
  53. package/docs/technical-reference.md +21 -17
  54. package/docs/technical-reference.zh.md +21 -17
  55. package/npm-shrinkwrap.json +4 -3
  56. package/package.json +3 -2
  57. package/skills/agent-bot/SKILL.md +61 -28
  58. package/src/cli/FeishuAppConfiguration.ts +2 -2
  59. package/src/cli/FeishuAppRegistration.ts +4 -4
  60. package/src/cli/Initializer.ts +2 -2
  61. package/src/cli/ServerStarter.ts +8 -3
  62. package/src/cli/controlProtocol.ts +21 -1
  63. package/src/cli/help.ts +20 -16
  64. package/src/cli/taskGroupOptions.ts +149 -0
  65. package/src/cli.ts +98 -4
  66. package/src/codex/CodexRuntime.ts +46 -13
  67. package/src/config/loadConfig.ts +11 -1
  68. package/src/config/schema.ts +7 -1
  69. package/src/console/ConsoleConnector.ts +1 -1
  70. package/src/console/ConsoleTurnPresenter.ts +20 -7
  71. package/src/deprecated-cli.ts +9 -0
  72. package/src/feishu/CardRenderer.ts +5 -4
  73. package/src/feishu/FeishuTurnPresenter.ts +15 -1
  74. package/src/feishu/transport.ts +1 -1
  75. package/src/index.ts +33 -2
  76. package/src/presentation/OutboundRouter.ts +15 -3
  77. package/src/presentation/TurnStateReducer.ts +2 -0
  78. package/src/presentation/turnViewTypes.ts +1 -0
  79. package/src/proxy/ProxySessionController.ts +166 -22
  80. package/src/runtime/createAgentRuntimeRegistry.ts +1 -1
  81. package/src/supervision/systemEnvironment.ts +174 -0
  82. package/src/supervisor.ts +12 -1
package/src/cli.ts CHANGED
@@ -7,6 +7,7 @@ import { sendControlRequest, isServerReachable } from "./cli/LocalControlClient.
7
7
  import {
8
8
  controlEndpoint,
9
9
  type ControlResponse,
10
+ type TaskGroupControlData,
10
11
  type TaskStatusControlData,
11
12
  } from "./cli/controlProtocol.js";
12
13
  import {
@@ -49,6 +50,10 @@ import {
49
50
  type ServerStartResult,
50
51
  } from "./cli/ServerStarter.js";
51
52
  import { taskChatRoute } from "./cli/taskChatRoute.js";
53
+ import {
54
+ parseTaskForkGroupOptions,
55
+ parseTaskNewGroupOptions,
56
+ } from "./cli/taskGroupOptions.js";
52
57
  import { StateStore, type SessionRecord } from "./state/StateStore.js";
53
58
  import {
54
59
  nodeDiagnosticReportArguments,
@@ -99,8 +104,8 @@ async function main(input: string[]): Promise<void> {
99
104
  return;
100
105
  }
101
106
  throw new Error(cliText(
102
- `Unknown command: ${command}. Run agent-bot --help for usage.`,
103
- `未知命令:${command}。运行 agent-bot --help 查看用法。`,
107
+ `Unknown command: ${command}. Run agentbot --help for usage.`,
108
+ `未知命令:${command}。运行 agentbot --help 查看用法。`,
104
109
  ));
105
110
  }
106
111
 
@@ -246,8 +251,8 @@ async function assertResetProfileServerStopped(configPath?: string): Promise<voi
246
251
  if (await isServerReachable(endpoint)) {
247
252
  throw new Error(
248
253
  cliText(
249
- "Cannot reset a running profile. Stop it with agent-bot --profile <directory> server stop, then try again.",
250
- "无法重置正在运行的 Profile。请先执行 agent-bot --profile <目录> server stop,然后重试。",
254
+ "Cannot reset a running profile. Stop it with agentbot --profile <directory> server stop, then try again.",
255
+ "无法重置正在运行的 Profile。请先执行 agentbot --profile <目录> server stop,然后重试。",
251
256
  ),
252
257
  );
253
258
  }
@@ -410,6 +415,51 @@ async function taskCommand(input: string[]): Promise<void> {
410
415
  process.stdout.write(cliText("Prompt submitted.\n", "提示词已提交。\n"));
411
416
  return;
412
417
  }
418
+ if (action === "newgroup") {
419
+ const options = parseTaskNewGroupOptions(rest);
420
+ const session = resolveTask(allSessions, options.reference);
421
+ const targetAgentName = options.agentName ?? session.agentName;
422
+ const targetAgent = config.agents[targetAgentName];
423
+ if (!targetAgent) {
424
+ throw new Error(cliText(
425
+ `Unknown Agent standard name: ${targetAgentName}.`,
426
+ `未知的 Agent 标准名:${targetAgentName}。`,
427
+ ));
428
+ }
429
+ if (options.projectless && targetAgent.kind !== "app-server") {
430
+ throw new Error(cliText(
431
+ "task newgroup --nodir is only available for App Server agents.",
432
+ "task newgroup --nodir 仅适用于 App Server Agent。",
433
+ ));
434
+ }
435
+ requireCliGroupUser(config.feishu.userOpenId);
436
+ const response = await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
437
+ action: "task_new_group",
438
+ localSessionId: session.localSessionId,
439
+ ...(options.title ? { title: options.title } : {}),
440
+ ...(options.cwd ? { cwd: options.cwd } : {}),
441
+ ...(options.agentName ? { agentName: options.agentName } : {}),
442
+ ...(options.projectless ? { projectless: true } : {}),
443
+ }, 120_000);
444
+ const result = taskGroupControlData(response);
445
+ if (options.json) printJson(result);
446
+ else printTaskGroupResult(result, "newgroup");
447
+ return;
448
+ }
449
+ if (action === "forkgroup") {
450
+ const options = parseTaskForkGroupOptions(rest);
451
+ const session = resolveTask(allSessions, options.reference);
452
+ requireCliGroupUser(config.feishu.userOpenId);
453
+ const response = await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
454
+ action: "task_fork_group",
455
+ localSessionId: session.localSessionId,
456
+ ...(options.title ? { title: options.title } : {}),
457
+ }, 120_000);
458
+ const result = taskGroupControlData(response);
459
+ if (options.json) printJson(result);
460
+ else printTaskGroupResult(result, "forkgroup");
461
+ return;
462
+ }
413
463
  const sessions = filterSessions(allSessions, rest);
414
464
  if (action === "list") {
415
465
  if (rest.includes("--json")) printJson(sessions);
@@ -840,6 +890,50 @@ function ensureOk(response: ControlResponse): void {
840
890
  ));
841
891
  }
842
892
 
893
+ function requireCliGroupUser(userOpenId: string | undefined): asserts userOpenId is string {
894
+ if (userOpenId?.startsWith("ou_")) return;
895
+ throw new Error(cliText(
896
+ "The profile has no authorizing Lark user to invite. Run agentbot init again or configure FEISHU_USER_OPEN_ID.",
897
+ "当前 Profile 没有可邀请的飞书授权用户。请重新运行 agentbot init,或配置 FEISHU_USER_OPEN_ID。",
898
+ ));
899
+ }
900
+
901
+ function taskGroupControlData(response: ControlResponse): TaskGroupControlData {
902
+ ensureOk(response);
903
+ const data = response.data as Partial<TaskGroupControlData> | undefined;
904
+ if (
905
+ !data
906
+ || typeof data.sourceLocalSessionId !== "string"
907
+ || !data.group
908
+ || typeof data.group.chatId !== "string"
909
+ || typeof data.group.contextKey !== "string"
910
+ || typeof data.group.name !== "string"
911
+ || !data.task
912
+ || typeof data.task.localSessionId !== "string"
913
+ ) {
914
+ throw new Error(cliText(
915
+ "Agent Bot returned an invalid task-group result.",
916
+ "Agent Bot 返回了无效的任务群结果。",
917
+ ));
918
+ }
919
+ return data as TaskGroupControlData;
920
+ }
921
+
922
+ function printTaskGroupResult(
923
+ result: TaskGroupControlData,
924
+ action: "newgroup" | "forkgroup",
925
+ ): void {
926
+ process.stdout.write(cliText(
927
+ `${action === "forkgroup" ? "Fork group" : "Lark group"} created: ${result.group.name}\n`,
928
+ `${action === "forkgroup" ? "Fork 群" : "飞书群"}已创建:${result.group.name}\n`,
929
+ ));
930
+ process.stdout.write(`${cliText("Chat ID: ", "群 ID:")}${result.group.chatId}\n`);
931
+ process.stdout.write(`${cliText("Task ID: ", "任务 ID:")}${result.task.localSessionId}\n`);
932
+ if (result.task.remoteSessionId) {
933
+ process.stdout.write(`${cliText("Agent task ID: ", "Agent 任务 ID:")}${result.task.remoteSessionId}\n`);
934
+ }
935
+ }
936
+
843
937
  function optionValue(args: string[], name: string): string | undefined {
844
938
  const index = args.indexOf(name);
845
939
  return index >= 0 ? args[index + 1] : undefined;
@@ -35,12 +35,13 @@ const WINDOWS_SCREENSHOT_DEVELOPER_INSTRUCTIONS = [
35
35
  "Call Graphics.CopyFromScreen with those physical coordinates and validate that the saved bitmap dimensions exactly equal the selected physical capture bounds.",
36
36
  ].join(" ");
37
37
 
38
- const SESSION_REQUEST_TIMEOUT_MS = 30_000;
38
+ const SESSION_REQUEST_TIMEOUT_MS = 60_000;
39
39
  const CONTROL_REQUEST_TIMEOUT_MS = 10_000;
40
40
  const SYNC_REQUEST_TIMEOUT_MS = 5_000;
41
41
  // A timed-out fork keeps running in App Server and can create an orphan thread.
42
42
  // Wait for its response; connection closure still rejects the request.
43
43
  const FORK_REQUEST_TIMEOUT_MS = 0;
44
+ type ThreadListSortKey = "recency_at" | "updated_at";
44
45
 
45
46
  export interface AppServerClient {
46
47
  request<T = unknown>(method: string, params?: unknown, timeoutMs?: number): Promise<T>;
@@ -84,6 +85,7 @@ export class CodexRuntime implements AgentRuntime {
84
85
  private readonly unsubscribeDisconnect?: () => void;
85
86
  private readonly sessionSyncs = new Map<string, Promise<RuntimeSession>>();
86
87
  private readonly localActivityDetector?: CodexLocalActivityDetector;
88
+ private threadListSortKey: ThreadListSortKey = "recency_at";
87
89
 
88
90
  constructor(
89
91
  private readonly provider: AppServerClientProvider,
@@ -215,18 +217,18 @@ export class CodexRuntime implements AgentRuntime {
215
217
  limit?: number;
216
218
  } = {}): Promise<RemoteSessionPage> {
217
219
  const client = await this.client();
218
- const response = await client.request<ThreadListResponse>(
219
- "thread/list",
220
- {
221
- cursor: input.cursor,
222
- limit: input.limit ?? 20,
223
- sortKey: "recency_at",
224
- sortDirection: "desc",
225
- archived: false,
226
- searchTerm: input.searchTerm,
227
- },
228
- CONTROL_REQUEST_TIMEOUT_MS,
229
- );
220
+ let response: ThreadListResponse;
221
+ try {
222
+ response = await this.requestThreadList(client, input, this.threadListSortKey);
223
+ } catch (error) {
224
+ if (this.threadListSortKey !== "recency_at" || !isUnsupportedRecencySortError(error)) throw error;
225
+ this.threadListSortKey = "updated_at";
226
+ this.logger.warn(
227
+ { error },
228
+ "App Server does not support thread/list recency_at sorting; retrying with updated_at.",
229
+ );
230
+ response = await this.requestThreadList(client, input, this.threadListSortKey);
231
+ }
230
232
  const sessions = await Promise.all(response.data.map(async (thread) => {
231
233
  const listed = remoteSessionSummary(thread);
232
234
  if (listed.status === "active" || listed.lastTurnStatus === "inProgress") return listed;
@@ -250,6 +252,25 @@ export class CodexRuntime implements AgentRuntime {
250
252
  };
251
253
  }
252
254
 
255
+ private requestThreadList(
256
+ client: AppServerClient,
257
+ input: { searchTerm?: string; cursor?: string; limit?: number },
258
+ sortKey: ThreadListSortKey,
259
+ ): Promise<ThreadListResponse> {
260
+ return client.request<ThreadListResponse>(
261
+ "thread/list",
262
+ {
263
+ cursor: input.cursor,
264
+ limit: input.limit ?? 20,
265
+ sortKey,
266
+ sortDirection: "desc",
267
+ archived: false,
268
+ searchTerm: input.searchTerm,
269
+ },
270
+ CONTROL_REQUEST_TIMEOUT_MS,
271
+ );
272
+ }
273
+
253
274
  async readRemoteSession(remoteSessionId: string): Promise<RemoteSessionSummary> {
254
275
  const response = await (await this.client()).request<ThreadReadResponse>(
255
276
  "thread/read",
@@ -1021,6 +1042,18 @@ function isUnsupportedExcludeTurnsError(error: unknown): boolean {
1021
1042
  || /unknown|unrecognized|unexpected|unsupported|not supported|experimental/i.test(details);
1022
1043
  }
1023
1044
 
1045
+ function isUnsupportedRecencySortError(error: unknown): boolean {
1046
+ if (!(error instanceof AppServerRequestError) || error.method !== "thread/list") return false;
1047
+ const data = typeof error.data === "string"
1048
+ ? error.data
1049
+ : error.data === undefined
1050
+ ? ""
1051
+ : JSON.stringify(error.data);
1052
+ const details = `${error.serverMessage} ${data}`;
1053
+ return /recency_at/i.test(details)
1054
+ && /unknown|unrecognized|unexpected|unsupported|not supported|invalid/i.test(details);
1055
+ }
1056
+
1024
1057
  function remoteSessionSummary(thread: CodexThreadSnapshot): RemoteSessionSummary {
1025
1058
  const lastTurn = thread.turns?.at(-1);
1026
1059
  const lastCompletedTurn = [...(thread.turns ?? [])]
@@ -22,7 +22,7 @@ console:
22
22
 
23
23
  agents:
24
24
  codex:
25
- kind: "codex"
25
+ kind: "app-server"
26
26
  title: "Codex"
27
27
  command: "codex"
28
28
  args:
@@ -33,6 +33,16 @@ agents:
33
33
  - "stdio://"
34
34
  env: {}
35
35
 
36
+ traex:
37
+ kind: "app-server"
38
+ title: "TraeX"
39
+ command: "traex"
40
+ args:
41
+ - "app-server"
42
+ - "--listen"
43
+ - "stdio://"
44
+ env: {}
45
+
36
46
  coco:
37
47
  title: "Coco"
38
48
  command: "coco"
@@ -3,8 +3,14 @@ import { DEFAULT_LOG_PATH, DEFAULT_SQLITE_PATH } from "./paths.js";
3
3
 
4
4
  const envRecordSchema = z.record(z.string(), z.string()).default({});
5
5
 
6
+ const agentKindSchema = z.union([
7
+ z.literal("acp"),
8
+ z.literal("app-server"),
9
+ z.literal("codex").transform(() => "app-server" as const),
10
+ ]).default("acp");
11
+
6
12
  export const agentConfigSchema = z.object({
7
- kind: z.enum(["acp", "codex"]).default("acp"),
13
+ kind: agentKindSchema,
8
14
  title: z.string().min(1),
9
15
  command: z.string().min(1),
10
16
  args: z.array(z.string()).default([]),
@@ -14,7 +14,7 @@ export class ConsoleConnector {
14
14
  if (this.reader) return;
15
15
  this.logger.info("Starting local Codex test console.");
16
16
  console.log("Codex console ready. Type /help or enter a prompt.");
17
- const reader = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: "agent-bot> " });
17
+ const reader = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: "agentbot> " });
18
18
  this.reader = reader;
19
19
  reader.prompt();
20
20
  reader.on("line", (line) => {
@@ -4,27 +4,36 @@ import type { TurnPresenter } from "../presentation/OutboundRouter.js";
4
4
 
5
5
  export class ConsoleTurnPresenter implements TurnPresenter {
6
6
  private readonly contexts = new Map<string, string>();
7
+ private readonly agentLabels = new Map<string, string>();
7
8
  private readonly delivered = new Set<string>();
8
9
 
9
10
  constructor(private readonly outbound: FeishuOutbound) {}
10
11
 
11
- registerSession(sessionId: string, contextKey: string): void {
12
+ registerSession(
13
+ sessionId: string,
14
+ contextKey: string,
15
+ _taskTitle?: string,
16
+ _projectCwd?: string,
17
+ agentLabel?: string,
18
+ ): void {
12
19
  this.contexts.set(sessionId, contextKey);
20
+ if (agentLabel) this.agentLabels.set(sessionId, agentLabel);
13
21
  }
14
22
 
15
23
  updateSessionTitle(): void {}
16
24
 
17
25
  unregisterSession(sessionId: string): void {
18
26
  this.contexts.delete(sessionId);
27
+ this.agentLabels.delete(sessionId);
19
28
  }
20
29
 
21
- async startPendingTurn(_sessionId: string, contextKey: string): Promise<void> {
22
- await this.outbound.sendText(contextKey, "Connecting to Codex...");
30
+ async startPendingTurn(sessionId: string, contextKey: string): Promise<void> {
31
+ await this.outbound.sendText(contextKey, `Connecting to ${this.agentLabel(sessionId)}...`);
23
32
  }
24
33
 
25
34
  async failPendingTurn(sessionId: string, message: string): Promise<void> {
26
35
  const contextKey = this.contexts.get(sessionId);
27
- if (contextKey) await this.outbound.sendText(contextKey, `Codex failed to start: ${message}`);
36
+ if (contextKey) await this.outbound.sendText(contextKey, `${this.agentLabel(sessionId)} failed to start: ${message}`);
28
37
  }
29
38
 
30
39
  async appendSteerMessage(): Promise<void> {}
@@ -33,7 +42,7 @@ export class ConsoleTurnPresenter implements TurnPresenter {
33
42
  const contextKey = this.contexts.get(event.sessionId);
34
43
  if (!contextKey) return;
35
44
  if (event.type === "turn_started") {
36
- await this.outbound.sendText(contextKey, `Codex started (${event.turnId})`);
45
+ await this.outbound.sendText(contextKey, `${this.agentLabel(event.sessionId)} started (${event.turnId})`);
37
46
  } else if (event.type === "progress") {
38
47
  await this.outbound.sendText(contextKey, event.text);
39
48
  } else if (event.type === "tool_started") {
@@ -44,9 +53,9 @@ export class ConsoleTurnPresenter implements TurnPresenter {
44
53
  this.delivered.add(event.turnId);
45
54
  await this.outbound.sendMarkdown(contextKey, event.finalResponse || "(completed without text)");
46
55
  } else if (event.type === "turn_failed") {
47
- await this.outbound.sendText(contextKey, `Codex failed: ${event.message}`);
56
+ await this.outbound.sendText(contextKey, `${this.agentLabel(event.sessionId)} failed: ${event.message}`);
48
57
  } else if (event.type === "turn_cancelled") {
49
- await this.outbound.sendText(contextKey, "Codex stopped.");
58
+ await this.outbound.sendText(contextKey, `${this.agentLabel(event.sessionId)} stopped.`);
50
59
  }
51
60
  }
52
61
 
@@ -61,4 +70,8 @@ export class ConsoleTurnPresenter implements TurnPresenter {
61
70
  async resumeDelivery(): Promise<void> {}
62
71
 
63
72
  async flushAll(): Promise<void> {}
73
+
74
+ private agentLabel(sessionId: string): string {
75
+ return this.agentLabels.get(sessionId) ?? "Agent";
76
+ }
64
77
  }
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import { cliText } from "./cli/i18n.js";
3
+
4
+ process.stderr.write(`${cliText(
5
+ "Warning: `agent-bot` is deprecated and will be removed in a future release. Use `agentbot` instead.",
6
+ "警告:`agent-bot` 已弃用,并将在后续版本中移除。请改用 `agentbot`。",
7
+ )}\n`);
8
+
9
+ await import("./cli.js");
@@ -928,15 +928,16 @@ function renderTurnElements(
928
928
  const heading = state.status === "completed" ? "回答" : "回答生成中";
929
929
  elements.push(markdown(`**${heading}**\n${truncateText(state.assistantText, 3_000)}`));
930
930
  }
931
- if (elements.length === 0) elements.push(markdown(emptyTurnText(state.status)));
931
+ if (elements.length === 0) elements.push(markdown(emptyTurnText(state.status, state.agentLabel)));
932
932
  return elements;
933
933
  }
934
934
 
935
- function emptyTurnText(status: TurnViewStatus): string {
936
- if (status === "starting") return "正在连接 Codex…";
935
+ function emptyTurnText(status: TurnViewStatus, agentLabel?: string): string {
936
+ const label = agentLabel?.trim().replace(/[\r\n]+/g, " ") || "Agent";
937
+ if (status === "starting") return `正在连接 ${label}…`;
937
938
  if (status === "completed") return "本轮已完成。";
938
939
  if (status === "cancelled") return "本轮已停止。";
939
- return "正在等待 Codex 返回进度…";
940
+ return `正在等待 ${label} 返回进度…`;
940
941
  }
941
942
 
942
943
  function planPanel(plan: TurnViewState["plan"]): Record<string, unknown> {
@@ -64,6 +64,7 @@ export class FeishuTurnPresenter {
64
64
  private readonly sessionContexts = new Map<string, string>();
65
65
  private readonly sessionTitles = new Map<string, string>();
66
66
  private readonly sessionCwds = new Map<string, string>();
67
+ private readonly sessionAgentLabels = new Map<string, string>();
67
68
  private readonly entries = new Map<string, TurnEntry>();
68
69
  private readonly pendingEntries = new Map<string, TurnEntry>();
69
70
  private readonly renderer: CardRenderer;
@@ -77,10 +78,17 @@ export class FeishuTurnPresenter {
77
78
  this.renderer = renderer ?? new CardRenderer();
78
79
  }
79
80
 
80
- registerSession(sessionId: string, contextKey: string, taskTitle?: string, projectCwd?: string): void {
81
+ registerSession(
82
+ sessionId: string,
83
+ contextKey: string,
84
+ taskTitle?: string,
85
+ projectCwd?: string,
86
+ agentLabel?: string,
87
+ ): void {
81
88
  this.sessionContexts.set(sessionId, contextKey);
82
89
  if (taskTitle) this.sessionTitles.set(sessionId, taskTitle);
83
90
  if (projectCwd) this.sessionCwds.set(sessionId, projectCwd);
91
+ if (agentLabel) this.sessionAgentLabels.set(sessionId, agentLabel);
84
92
  }
85
93
 
86
94
  updateSessionTitle(sessionId: string, taskTitle: string): void {
@@ -97,6 +105,7 @@ export class FeishuTurnPresenter {
97
105
  this.sessionContexts.delete(sessionId);
98
106
  this.sessionTitles.delete(sessionId);
99
107
  this.sessionCwds.delete(sessionId);
108
+ this.sessionAgentLabels.delete(sessionId);
100
109
  }
101
110
 
102
111
  async startPendingTurn(
@@ -126,6 +135,7 @@ export class FeishuTurnPresenter {
126
135
  replyTarget,
127
136
  this.sessionCwds.get(sessionId),
128
137
  prompt,
138
+ this.sessionAgentLabels.get(sessionId),
129
139
  );
130
140
  const entry = { contextKey, state, initializing: Promise.resolve() } as TurnEntry;
131
141
  this.entries.set(state.turnId, entry);
@@ -179,6 +189,8 @@ export class FeishuTurnPresenter {
179
189
  this.sessionTitles.get(sessionId),
180
190
  undefined,
181
191
  this.sessionCwds.get(sessionId),
192
+ undefined,
193
+ this.sessionAgentLabels.get(sessionId),
182
194
  ),
183
195
  status: "running" as const,
184
196
  };
@@ -213,6 +225,7 @@ export class FeishuTurnPresenter {
213
225
  pending.state.replyTarget,
214
226
  pending.state.projectCwd,
215
227
  pending.state.prompt,
228
+ pending.state.agentLabel,
216
229
  ),
217
230
  event,
218
231
  );
@@ -347,6 +360,7 @@ export class FeishuTurnPresenter {
347
360
  undefined,
348
361
  this.sessionCwds.get(event.sessionId),
349
362
  undefined,
363
+ this.sessionAgentLabels.get(event.sessionId),
350
364
  );
351
365
  const state = reduceTurnEvent(
352
366
  initial,
@@ -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
- "The Lark bot is not configured. Run agent-bot init first, or use agent-bot console for local-only testing.",
11
+ "The Lark bot is not configured. Run agentbot init first, or use agentbot console for local-only testing.",
12
12
  );
13
13
  }
14
14
  return "sdk";
package/src/index.ts CHANGED
@@ -30,6 +30,7 @@ import {
30
30
  prepareCrashReportDirectory,
31
31
  resolveSupervisorDiagnosticsPaths,
32
32
  } from "./supervision/SupervisorDiagnostics.js";
33
+ import { refreshedSystemEnvironment } from "./supervision/systemEnvironment.js";
33
34
 
34
35
  const processStartedAt = new Date();
35
36
  const agentBotVersion = readPackageVersion();
@@ -69,7 +70,7 @@ if (feishuOutbound) {
69
70
  defaultAgentName,
70
71
  defaultAgentTitle: defaultAgent?.title ?? defaultAgentName,
71
72
  cwd: path.resolve(config.defaults.cwd),
72
- workspaceKind: defaultAgent?.kind === "codex" ? "projectless" : "project",
73
+ workspaceKind: defaultAgent?.kind === "app-server" ? "projectless" : "project",
73
74
  defaultUserOpenId: config.feishu.userOpenId,
74
75
  }, metadataHydrator);
75
76
  safeRestartNotifier = new SafeRestartNotifier(store, feishuOutbound, renderer, logger);
@@ -210,6 +211,27 @@ async function handleControlRequest(request: ControlRequest): Promise<ControlRes
210
211
  return { ok: true, message: await controller.controlSetTaskTitle(request.localSessionId, request.title) };
211
212
  case "task_prompt":
212
213
  return { ok: true, message: await controller.controlSendTaskPrompt(request.localSessionId, request.text) };
214
+ case "task_new_group":
215
+ return {
216
+ ok: true,
217
+ data: await controller.controlCreateTaskGroup(
218
+ request.localSessionId,
219
+ request.title,
220
+ config.feishu.userOpenId,
221
+ request.cwd,
222
+ request.projectless === true,
223
+ request.agentName,
224
+ ),
225
+ };
226
+ case "task_fork_group":
227
+ return {
228
+ ok: true,
229
+ data: await controller.controlForkTaskGroup(
230
+ request.localSessionId,
231
+ request.title,
232
+ config.feishu.userOpenId,
233
+ ),
234
+ };
213
235
  }
214
236
  }
215
237
 
@@ -236,6 +258,15 @@ function startReplacementSupervisor(restartReason: string): void {
236
258
  const supervisorEntry = fileURLToPath(new URL("./supervisor.js", import.meta.url));
237
259
  const reportDirectory = resolveSupervisorDiagnosticsPaths(config).crashReportDirectory;
238
260
  prepareCrashReportDirectory(reportDirectory);
261
+ const environmentRefresh = refreshedSystemEnvironment();
262
+ if (environmentRefresh.error) {
263
+ logger.warn({ error: environmentRefresh.error }, "Failed to refresh the Windows environment for the replacement Supervisor.");
264
+ } else if (environmentRefresh.refreshed) {
265
+ logger.info(
266
+ { pathChanged: environmentRefresh.pathChanged },
267
+ "Refreshed the Windows environment for the replacement Supervisor.",
268
+ );
269
+ }
239
270
  const supervisor = spawn(process.execPath, [
240
271
  ...nodeDiagnosticReportArguments(reportDirectory),
241
272
  supervisorEntry,
@@ -244,7 +275,7 @@ function startReplacementSupervisor(restartReason: string): void {
244
275
  detached: true,
245
276
  windowsHide: true,
246
277
  stdio: "ignore",
247
- env: replacementSupervisorEnvironment(restartReason),
278
+ env: replacementSupervisorEnvironment(restartReason, environmentRefresh.environment),
248
279
  });
249
280
  supervisor.unref();
250
281
  }
@@ -8,7 +8,13 @@ import type {
8
8
  } from "../feishu/types.js";
9
9
 
10
10
  export interface TurnPresenter {
11
- registerSession(sessionId: string, contextKey: string, taskTitle?: string, projectCwd?: string): void;
11
+ registerSession(
12
+ sessionId: string,
13
+ contextKey: string,
14
+ taskTitle?: string,
15
+ projectCwd?: string,
16
+ agentLabel?: string,
17
+ ): void;
12
18
  updateSessionTitle(sessionId: string, taskTitle: string): void;
13
19
  unregisterSession(sessionId: string): void;
14
20
  startPendingTurn(
@@ -48,11 +54,17 @@ export class OutboundRouter {
48
54
  if (routes.length === 0) throw new Error("At least one outbound route is required.");
49
55
  }
50
56
 
51
- registerSession(sessionId: string, contextKey: string, taskTitle?: string, projectCwd?: string): void {
57
+ registerSession(
58
+ sessionId: string,
59
+ contextKey: string,
60
+ taskTitle?: string,
61
+ projectCwd?: string,
62
+ agentLabel?: string,
63
+ ): void {
52
64
  const route = this.route(contextKey);
53
65
  this.sessionRoutes.set(sessionId, route);
54
66
  this.sessionContextKeys.set(sessionId, contextKey);
55
- route.presenter.registerSession(sessionId, contextKey, taskTitle, projectCwd);
67
+ route.presenter.registerSession(sessionId, contextKey, taskTitle, projectCwd, agentLabel);
56
68
  }
57
69
 
58
70
  getSessionContextKey(sessionId: string): string | undefined {
@@ -16,10 +16,12 @@ export function createTurnViewState(
16
16
  replyTarget?: MessageReplyTarget,
17
17
  projectCwd?: string,
18
18
  prompt?: string,
19
+ agentLabel?: string,
19
20
  ): TurnViewState {
20
21
  return {
21
22
  sessionId,
22
23
  turnId,
24
+ agentLabel,
23
25
  taskTitle,
24
26
  prompt,
25
27
  projectCwd,
@@ -25,6 +25,7 @@ export type TurnActivity =
25
25
  export interface TurnViewState {
26
26
  sessionId: string;
27
27
  turnId: string;
28
+ agentLabel?: string;
28
29
  taskTitle?: string;
29
30
  prompt?: string;
30
31
  projectCwd?: string;