@keyou007/agent-bot 0.1.11 → 0.1.13-alpha.0

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 (97) hide show
  1. package/.env.example +1 -1
  2. package/CHANGELOG.md +19 -0
  3. package/README.md +63 -44
  4. package/README.zh.md +63 -44
  5. package/config.example.yaml +8 -22
  6. package/dist/cli/AgentPrerequisites.js +262 -0
  7. package/dist/cli/AgentPrerequisites.js.map +1 -0
  8. package/dist/cli/DefaultAgentSelection.js +47 -0
  9. package/dist/cli/DefaultAgentSelection.js.map +1 -0
  10. package/dist/cli/FeishuAppConfiguration.js +1 -1
  11. package/dist/cli/FeishuAppConfiguration.js.map +1 -1
  12. package/dist/cli/FeishuAppRegistration.js +2 -2
  13. package/dist/cli/FeishuAppRegistration.js.map +1 -1
  14. package/dist/cli/Initializer.js +166 -3
  15. package/dist/cli/Initializer.js.map +1 -1
  16. package/dist/cli/ServerStarter.js +7 -2
  17. package/dist/cli/ServerStarter.js.map +1 -1
  18. package/dist/cli/controlProtocol.js.map +1 -1
  19. package/dist/cli/help.js +24 -20
  20. package/dist/cli/help.js.map +1 -1
  21. package/dist/cli/taskGroupOptions.js +95 -0
  22. package/dist/cli/taskGroupOptions.js.map +1 -0
  23. package/dist/cli.js +268 -6
  24. package/dist/cli.js.map +1 -1
  25. package/dist/codex/AppServerConnection.js +3 -3
  26. package/dist/codex/AppServerConnection.js.map +1 -1
  27. package/dist/codex/CodexProcessManager.js +4 -4
  28. package/dist/codex/CodexProcessManager.js.map +1 -1
  29. package/dist/codex/CodexRuntime.js +63 -25
  30. package/dist/codex/CodexRuntime.js.map +1 -1
  31. package/dist/config/loadConfig.js +8 -22
  32. package/dist/config/loadConfig.js.map +1 -1
  33. package/dist/config/schema.js +6 -1
  34. package/dist/config/schema.js.map +1 -1
  35. package/dist/console/ConsoleConnector.js +3 -3
  36. package/dist/console/ConsoleConnector.js.map +1 -1
  37. package/dist/console/ConsoleTurnPresenter.js +14 -7
  38. package/dist/console/ConsoleTurnPresenter.js.map +1 -1
  39. package/dist/deprecated-cli.js +5 -0
  40. package/dist/deprecated-cli.js.map +1 -0
  41. package/dist/feishu/CardRenderer.js +13 -12
  42. package/dist/feishu/CardRenderer.js.map +1 -1
  43. package/dist/feishu/FeishuTurnPresenter.js +9 -5
  44. package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
  45. package/dist/feishu/transport.js +1 -1
  46. package/dist/feishu/transport.js.map +1 -1
  47. package/dist/index.js +21 -3
  48. package/dist/index.js.map +1 -1
  49. package/dist/presentation/OutboundRouter.js +2 -2
  50. package/dist/presentation/OutboundRouter.js.map +1 -1
  51. package/dist/presentation/TurnStateReducer.js +2 -1
  52. package/dist/presentation/TurnStateReducer.js.map +1 -1
  53. package/dist/proxy/ProxySessionController.js +128 -72
  54. package/dist/proxy/ProxySessionController.js.map +1 -1
  55. package/dist/runtime/AcpRuntimeAdapter.js +3 -2
  56. package/dist/runtime/AcpRuntimeAdapter.js.map +1 -1
  57. package/dist/runtime/createAgentRuntimeRegistry.js +1 -1
  58. package/dist/runtime/createAgentRuntimeRegistry.js.map +1 -1
  59. package/dist/supervision/systemEnvironment.js +129 -0
  60. package/dist/supervision/systemEnvironment.js.map +1 -0
  61. package/dist/supervisor.js +13 -1
  62. package/dist/supervisor.js.map +1 -1
  63. package/docs/technical-reference.md +53 -34
  64. package/docs/technical-reference.zh.md +53 -34
  65. package/npm-shrinkwrap.json +4 -3
  66. package/package.json +9 -4
  67. package/skills/agent-bot/SKILL.md +72 -33
  68. package/src/cli/AgentPrerequisites.ts +377 -0
  69. package/src/cli/DefaultAgentSelection.ts +56 -0
  70. package/src/cli/FeishuAppConfiguration.ts +2 -2
  71. package/src/cli/FeishuAppRegistration.ts +4 -4
  72. package/src/cli/Initializer.ts +206 -5
  73. package/src/cli/ServerStarter.ts +8 -3
  74. package/src/cli/controlProtocol.ts +21 -1
  75. package/src/cli/help.ts +24 -20
  76. package/src/cli/taskGroupOptions.ts +149 -0
  77. package/src/cli.ts +391 -9
  78. package/src/codex/AppServerConnection.ts +3 -3
  79. package/src/codex/CodexProcessManager.ts +4 -4
  80. package/src/codex/CodexRuntime.ts +84 -31
  81. package/src/config/loadConfig.ts +8 -22
  82. package/src/config/schema.ts +7 -1
  83. package/src/console/ConsoleConnector.ts +3 -3
  84. package/src/console/ConsoleTurnPresenter.ts +20 -7
  85. package/src/deprecated-cli.ts +9 -0
  86. package/src/feishu/CardRenderer.ts +13 -12
  87. package/src/feishu/FeishuTurnPresenter.ts +15 -1
  88. package/src/feishu/transport.ts +1 -1
  89. package/src/index.ts +34 -3
  90. package/src/presentation/OutboundRouter.ts +15 -3
  91. package/src/presentation/TurnStateReducer.ts +2 -0
  92. package/src/presentation/turnViewTypes.ts +1 -0
  93. package/src/proxy/ProxySessionController.ts +217 -73
  94. package/src/runtime/AcpRuntimeAdapter.ts +3 -1
  95. package/src/runtime/createAgentRuntimeRegistry.ts +1 -1
  96. package/src/supervision/systemEnvironment.ts +174 -0
  97. package/src/supervisor.ts +12 -1
@@ -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,
@@ -161,7 +163,7 @@ export class CodexRuntime implements AgentRuntime {
161
163
  if (!isUnsupportedExcludeTurnsError(error)) throw error;
162
164
  this.logger.warn(
163
165
  { error },
164
- "Codex App Server does not support thread/fork excludeTurns; retrying without it.",
166
+ "App Server does not support thread/fork excludeTurns; retrying without it.",
165
167
  );
166
168
  response = await client.request<ThreadResponse>("thread/fork", forkParams, FORK_REQUEST_TIMEOUT_MS);
167
169
  }
@@ -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",
@@ -322,7 +343,7 @@ export class CodexRuntime implements AgentRuntime {
322
343
  );
323
344
  this.logger.info(
324
345
  { ...(sessionId ? { sessionId } : {}), threadId: remoteSessionId, turnId },
325
- "Codex accepted the turn interrupt request.",
346
+ "App Server accepted the turn interrupt request.",
326
347
  );
327
348
  }
328
349
 
@@ -533,7 +554,7 @@ export class CodexRuntime implements AgentRuntime {
533
554
  const session = [...this.sessions.values()].find((candidate) => candidate.remoteSessionId === threadId);
534
555
  if (session?.activeTurnId && status && status !== "active") {
535
556
  void this.synchronizeSession(session.localSessionId).catch((error: unknown) => {
536
- this.logger.warn({ error, sessionId: session.localSessionId, status }, "Failed to reconcile Codex thread status.");
557
+ this.logger.warn({ error, sessionId: session.localSessionId, status }, "Failed to reconcile App Server thread status.");
537
558
  });
538
559
  }
539
560
  return;
@@ -545,7 +566,7 @@ export class CodexRuntime implements AgentRuntime {
545
566
  if (session.terminalTurnIds.has(mapped.turnId)) {
546
567
  this.logger.debug(
547
568
  { sessionId: session.localSessionId, turnId: mapped.turnId, method },
548
- "Ignoring a replayed terminal Codex turn event.",
569
+ "Ignoring a replayed terminal App Server turn event.",
549
570
  );
550
571
  return;
551
572
  }
@@ -565,7 +586,7 @@ export class CodexRuntime implements AgentRuntime {
565
586
  notificationTurnId: mapped.turnId,
566
587
  notificationStartedAt: mapped.startedAt,
567
588
  },
568
- "Ignoring a replayed historical Codex turn start.",
589
+ "Ignoring a replayed historical App Server turn start.",
569
590
  );
570
591
  return;
571
592
  }
@@ -576,10 +597,10 @@ export class CodexRuntime implements AgentRuntime {
576
597
  if (!session.activeTurnId || session.activeTurnId !== mapped.turnId) {
577
598
  this.logger.debug(
578
599
  { sessionId: session.localSessionId, activeTurnId: session.activeTurnId, notificationTurnId: mapped.turnId, method },
579
- "Ignoring out-of-order Codex notification and scheduling reconciliation.",
600
+ "Ignoring out-of-order App Server notification and scheduling reconciliation.",
580
601
  );
581
602
  void this.synchronizeSession(session.localSessionId).catch((error: unknown) => {
582
- this.logger.warn({ error, sessionId: session.localSessionId }, "Failed to reconcile out-of-order Codex notification.");
603
+ this.logger.warn({ error, sessionId: session.localSessionId }, "Failed to reconcile out-of-order App Server notification.");
583
604
  });
584
605
  return;
585
606
  }
@@ -650,9 +671,12 @@ export class CodexRuntime implements AgentRuntime {
650
671
  if (mapped.status === "cancelled") {
651
672
  this.emit({ type: "turn_cancelled", sessionId, turnId: mapped.turnId });
652
673
  } else if (mapped.status === "failed") {
653
- this.emit({ type: "turn_failed", sessionId, turnId: mapped.turnId, message: mapped.error ?? "Codex turn failed." });
674
+ this.emit({ type: "turn_failed", sessionId, turnId: mapped.turnId, message: mapped.error ?? "App Server turn failed." });
654
675
  } else {
655
- const finalResponse = appendGeneratedImageMarkdown(session.finalText, session.generatedImagePaths);
676
+ const finalResponse = appendModelSwitchGuidance(
677
+ appendGeneratedImageMarkdown(session.finalText, session.generatedImagePaths),
678
+ session.model,
679
+ );
656
680
  this.emit({
657
681
  type: "turn_completed",
658
682
  sessionId,
@@ -682,7 +706,7 @@ export class CodexRuntime implements AgentRuntime {
682
706
  turnId,
683
707
  request: {
684
708
  id: requestId,
685
- title: stringValue(params.command) ?? "Codex approval request",
709
+ title: stringValue(params.command) ?? "Agent approval request",
686
710
  command: stringValue(params.command),
687
711
  reason: stringValue(params.reason),
688
712
  options: [
@@ -760,7 +784,7 @@ export class CodexRuntime implements AgentRuntime {
760
784
  type: "turn_failed",
761
785
  sessionId: session.localSessionId,
762
786
  turnId: activeTurnId,
763
- message: "Codex no longer reports this execution in the thread history.",
787
+ message: "The App Server no longer reports this execution in the thread history.",
764
788
  });
765
789
  }
766
790
  return;
@@ -807,7 +831,7 @@ export class CodexRuntime implements AgentRuntime {
807
831
  type: "turn_failed",
808
832
  sessionId: session.localSessionId,
809
833
  turnId: turn.id,
810
- message: turn.error?.message ?? "Codex turn failed.",
834
+ message: turn.error?.message ?? "App Server turn failed.",
811
835
  });
812
836
  return;
813
837
  }
@@ -815,9 +839,12 @@ export class CodexRuntime implements AgentRuntime {
815
839
  ...session.generatedImagePaths,
816
840
  ...extractGeneratedImagePaths(turn),
817
841
  ]);
818
- const finalResponse = appendGeneratedImageMarkdown(
819
- extractFinalResponse(turn) || session.finalText,
820
- generatedImagePaths,
842
+ const finalResponse = appendModelSwitchGuidance(
843
+ appendGeneratedImageMarkdown(
844
+ extractFinalResponse(turn) || session.finalText,
845
+ generatedImagePaths,
846
+ ),
847
+ session.model,
821
848
  );
822
849
  session.finalText = finalResponse;
823
850
  session.generatedImagePaths = [];
@@ -867,13 +894,13 @@ export class CodexRuntime implements AgentRuntime {
867
894
 
868
895
  private requireSession(sessionId: string): CodexSession {
869
896
  const session = this.sessions.get(sessionId);
870
- if (!session) throw new Error(`Unknown Codex session: ${sessionId}`);
897
+ if (!session) throw new Error(`Unknown App Server session: ${sessionId}`);
871
898
  return session;
872
899
  }
873
900
 
874
901
  private requireActiveTurn(sessionId: string, turnId: string): CodexSession {
875
902
  const session = this.requireSession(sessionId);
876
- if (session.activeTurnId !== turnId) throw new Error(`Codex turn is no longer active: ${turnId}`);
903
+ if (session.activeTurnId !== turnId) throw new Error(`App Server turn is no longer active: ${turnId}`);
877
904
  return session;
878
905
  }
879
906
 
@@ -896,7 +923,7 @@ export class CodexRuntime implements AgentRuntime {
896
923
  type: "turn_failed",
897
924
  sessionId: session.localSessionId,
898
925
  turnId,
899
- message: `Codex App Server disconnected: ${error.message}`,
926
+ message: `App Server disconnected: ${error.message}`,
900
927
  });
901
928
  }
902
929
  for (const [requestId, pending] of this.approvals) {
@@ -957,6 +984,20 @@ function permissionParams(mode: PermissionMode): { approvalPolicy: "never" | "on
957
984
  : { approvalPolicy: "on-request", sandbox: "workspace-write" };
958
985
  }
959
986
 
987
+ const MODEL_SWITCH_GUIDANCE = "请发送 `/model` 切换到其他模型后重试。";
988
+
989
+ function appendModelSwitchGuidance(response: string, model?: string): string {
990
+ if (!hasKnownCodeModeFailure(response) || response.includes(MODEL_SWITCH_GUIDANCE)) return response;
991
+ const modelLabel = model?.trim() ? ` \`${model.trim()}\`` : "";
992
+ const guidance = `> 当前模型${modelLabel} 的本地工具执行通道异常。${MODEL_SWITCH_GUIDANCE}`;
993
+ return response.trimEnd() ? `${response.trimEnd()}\n\n${guidance}` : guidance;
994
+ }
995
+
996
+ function hasKnownCodeModeFailure(response: string): boolean {
997
+ return /code-mode host closed its stdout/i.test(response)
998
+ || /exec expects an object containing raw JavaScript in [`'"]?input[`'"]?/i.test(response);
999
+ }
1000
+
960
1001
  function threadLifecycleParams(cwd: string): {
961
1002
  developerInstructions: string;
962
1003
  } {
@@ -1021,6 +1062,18 @@ function isUnsupportedExcludeTurnsError(error: unknown): boolean {
1021
1062
  || /unknown|unrecognized|unexpected|unsupported|not supported|experimental/i.test(details);
1022
1063
  }
1023
1064
 
1065
+ function isUnsupportedRecencySortError(error: unknown): boolean {
1066
+ if (!(error instanceof AppServerRequestError) || error.method !== "thread/list") return false;
1067
+ const data = typeof error.data === "string"
1068
+ ? error.data
1069
+ : error.data === undefined
1070
+ ? ""
1071
+ : JSON.stringify(error.data);
1072
+ const details = `${error.serverMessage} ${data}`;
1073
+ return /recency_at/i.test(details)
1074
+ && /unknown|unrecognized|unexpected|unsupported|not supported|invalid/i.test(details);
1075
+ }
1076
+
1024
1077
  function remoteSessionSummary(thread: CodexThreadSnapshot): RemoteSessionSummary {
1025
1078
  const lastTurn = thread.turns?.at(-1);
1026
1079
  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,28 +33,14 @@ agents:
33
33
  - "stdio://"
34
34
  env: {}
35
35
 
36
- coco:
37
- title: "Coco"
38
- command: "coco"
36
+ traex:
37
+ kind: "app-server"
38
+ title: "TraeX"
39
+ command: "traex"
39
40
  args:
40
- - "acp"
41
- - "serve"
42
- env: {}
43
-
44
- coco-yolo:
45
- title: "Coco YOLO"
46
- command: "coco"
47
- args:
48
- - "acp"
49
- - "serve"
50
- - "--yolo"
51
- env: {}
52
-
53
- example:
54
- title: "Example ACP Agent"
55
- command: "node"
56
- args:
57
- - "./examples/example-acp-agent.js"
41
+ - "app-server"
42
+ - "--listen"
43
+ - "stdio://"
58
44
  env: {}
59
45
 
60
46
  defaults:
@@ -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([]),
@@ -12,9 +12,9 @@ export class ConsoleConnector {
12
12
 
13
13
  start(): void {
14
14
  if (this.reader) return;
15
- this.logger.info("Starting local Codex test console.");
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> " });
15
+ this.logger.info("Starting the local Agent Bot console.");
16
+ console.log("Agent Bot console ready. Type /help or enter a prompt.");
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");
@@ -163,7 +163,7 @@ export class CardRenderer {
163
163
  : `**默认 Agent**:${inlineCode(view.currentAgent)}`,
164
164
  ...(view.runtimeSettingsAvailable
165
165
  ? [
166
- `**Provider / 模型**:${inlineCode(view.currentProvider ?? "Codex 默认")} / ${inlineCode(view.currentModel ?? "默认")}`,
166
+ `**Provider / 模型**:${inlineCode(view.currentProvider ?? "Agent 默认")} / ${inlineCode(view.currentModel ?? "默认")}`,
167
167
  `**思考强度 / 权限**:${inlineCode(view.currentEffort ?? "自动")} / ${inlineCode(permissionModeLabel(view.currentPermissionMode))}`,
168
168
  ]
169
169
  : []),
@@ -190,7 +190,7 @@ export class CardRenderer {
190
190
  if (!view.providerSupported) {
191
191
  elements.push(markdown("当前运行时不支持 Provider 切换;其他设置仍可通过上方 tab 修改。"));
192
192
  } else if (view.providers.length === 0) {
193
- elements.push(markdown("Codex 配置中没有可用的 Provider。"));
193
+ elements.push(markdown("当前 Agent 配置中没有可用的 Provider。"));
194
194
  } else {
195
195
  elements.push(...view.providers.map((provider) => settingsOptionRow({
196
196
  label: [
@@ -270,14 +270,14 @@ export class CardRenderer {
270
270
  renderProviderSelector(view: ProviderSelectorCardView): Record<string, unknown> {
271
271
  const elements: Record<string, unknown>[] = [
272
272
  markdown([
273
- `**当前 Provider**:${inlineCode(view.currentProvider ?? "Codex 默认")}`,
273
+ `**当前 Provider**:${inlineCode(view.currentProvider ?? "Agent 默认")}`,
274
274
  `**模型 / 思考强度 / 权限**:${inlineCode(view.currentModel ?? "默认")} / ${inlineCode(view.reasoningEffort ?? "自动")} / ${inlineCode(permissionModeLabel(view.permissionMode))}`,
275
275
  ...(view.notice ? [view.notice] : []),
276
276
  ].join("\n")),
277
277
  { tag: "hr" },
278
278
  ];
279
279
  if (view.providers.length === 0) {
280
- elements.push(markdown("Codex 配置中没有可用的 Provider。"));
280
+ elements.push(markdown("当前 Agent 配置中没有可用的 Provider。"));
281
281
  } else {
282
282
  elements.push(...view.providers.map((provider) => ({
283
283
  tag: "column_set",
@@ -635,14 +635,14 @@ export class CardRenderer {
635
635
  ? [
636
636
  `**当前任务**:${inlineCode(view.currentTask.title ?? view.currentTask.id)}`,
637
637
  workspaceLine,
638
- `**Provider / 模型 / 思考强度 / 权限**:${inlineCode(view.currentTask.modelProvider ?? "Codex 默认")} / ${inlineCode(view.currentTask.model ?? "默认")} / ${inlineCode(view.currentTask.reasoningEffort ?? "自动")} / ${inlineCode(permissionModeLabel(view.currentTask.permissionMode))}`,
638
+ `**Provider / 模型 / 思考强度 / 权限**:${inlineCode(view.currentTask.modelProvider ?? "Agent 默认")} / ${inlineCode(view.currentTask.model ?? "默认")} / ${inlineCode(view.currentTask.reasoningEffort ?? "自动")} / ${inlineCode(permissionModeLabel(view.currentTask.permissionMode))}`,
639
639
  `**任务状态 / Agent**:${persistedTaskStatus(view.currentTask.sessionStatus, view.currentTask.lastTurnStatus)} / ${inlineCode(view.currentTask.agentName)}`,
640
640
  `**任务 ID**:${inlineCode(view.currentTask.id)}`,
641
641
  ]
642
642
  : [
643
643
  "**当前任务**:无,下一条普通消息会创建新任务",
644
644
  workspaceLine,
645
- `**Provider / 模型 / 思考强度 / 权限**:${inlineCode("Codex 默认")} / ${inlineCode("默认")} / ${inlineCode("自动")} / ${inlineCode(permissionModeLabel())}`,
645
+ `**Provider / 模型 / 思考强度 / 权限**:${inlineCode("Agent 默认")} / ${inlineCode("默认")} / ${inlineCode("自动")} / ${inlineCode(permissionModeLabel())}`,
646
646
  `**默认 Agent**:${view.defaultAgentTitle} (${inlineCode(view.defaultAgentName)})`,
647
647
  ];
648
648
  lines.push(
@@ -673,8 +673,8 @@ export class CardRenderer {
673
673
 
674
674
  renderTurnDetails(state: TurnViewState): Record<string, unknown> {
675
675
  const title = state.taskTitle
676
- ? `Codex 执行详情:${truncateText(state.taskTitle.replace(/\s+/g, " ").trim(), 60)}`
677
- : "Codex 执行详情";
676
+ ? `任务执行详情:${truncateText(state.taskTitle.replace(/\s+/g, " ").trim(), 60)}`
677
+ : "任务执行详情";
678
678
  return turnCard(title, "blue", renderTurnElements(state, "always"), renderTurnSubtitle(state));
679
679
  }
680
680
 
@@ -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();
@@ -58,7 +59,7 @@ if (feishuOutbound) {
58
59
  const presenter = new FeishuTurnPresenter(feishuOutbound, store, renderer, {
59
60
  normalIntervalMs: 2_000,
60
61
  criticalGapMs: 500,
61
- onError: (error) => logger.warn({ error }, "Failed to update Codex progress card."),
62
+ onError: (error) => logger.warn({ error }, "Failed to update Agent progress card."),
62
63
  });
63
64
  routes.push({ matches: (contextKey) => !contextKey.startsWith("console:"), outbound: feishuOutbound, presenter });
64
65
  const defaultAgentName = config.defaults.agent!;
@@ -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
  }