@keyou007/agent-bot 0.1.10 → 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 (121) hide show
  1. package/.env.example +1 -1
  2. package/CHANGELOG.md +25 -0
  3. package/README.md +55 -43
  4. package/README.zh.md +55 -43
  5. package/assets/agent-bot-logo.png +0 -0
  6. package/config.example.yaml +11 -1
  7. package/dist/acp/AcpSessionManager.js +78 -46
  8. package/dist/acp/AcpSessionManager.js.map +1 -1
  9. package/dist/cli/FeishuAppConfiguration.js +1 -1
  10. package/dist/cli/FeishuAppConfiguration.js.map +1 -1
  11. package/dist/cli/FeishuAppRegistration.js +2 -2
  12. package/dist/cli/FeishuAppRegistration.js.map +1 -1
  13. package/dist/cli/Initializer.js +1 -1
  14. package/dist/cli/Initializer.js.map +1 -1
  15. package/dist/cli/ServerStarter.js +7 -2
  16. package/dist/cli/ServerStarter.js.map +1 -1
  17. package/dist/cli/controlProtocol.js.map +1 -1
  18. package/dist/cli/help.js +20 -16
  19. package/dist/cli/help.js.map +1 -1
  20. package/dist/cli/taskGroupOptions.js +95 -0
  21. package/dist/cli/taskGroupOptions.js.map +1 -0
  22. package/dist/cli.js +74 -2
  23. package/dist/cli.js.map +1 -1
  24. package/dist/codex/AppServerConnection.js +15 -1
  25. package/dist/codex/AppServerConnection.js.map +1 -1
  26. package/dist/codex/CodexLocalActivityDetector.js +4 -1
  27. package/dist/codex/CodexLocalActivityDetector.js.map +1 -1
  28. package/dist/codex/CodexProcessManager.js +1 -0
  29. package/dist/codex/CodexProcessManager.js.map +1 -1
  30. package/dist/codex/CodexRuntime.js +112 -11
  31. package/dist/codex/CodexRuntime.js.map +1 -1
  32. package/dist/commands/CommandRouter.js +64 -49
  33. package/dist/commands/CommandRouter.js.map +1 -1
  34. package/dist/config/loadConfig.js +11 -1
  35. package/dist/config/loadConfig.js.map +1 -1
  36. package/dist/config/schema.js +6 -1
  37. package/dist/config/schema.js.map +1 -1
  38. package/dist/console/ConsoleConnector.js +1 -1
  39. package/dist/console/ConsoleConnector.js.map +1 -1
  40. package/dist/console/ConsoleTurnPresenter.js +15 -8
  41. package/dist/console/ConsoleTurnPresenter.js.map +1 -1
  42. package/dist/deprecated-cli.js +5 -0
  43. package/dist/deprecated-cli.js.map +1 -0
  44. package/dist/feishu/CardRenderer.js +350 -15
  45. package/dist/feishu/CardRenderer.js.map +1 -1
  46. package/dist/feishu/FeishuMessageClient.js +51 -5
  47. package/dist/feishu/FeishuMessageClient.js.map +1 -1
  48. package/dist/feishu/FeishuTurnPresenter.js +17 -8
  49. package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
  50. package/dist/feishu/transport.js +1 -1
  51. package/dist/feishu/transport.js.map +1 -1
  52. package/dist/index.js +38 -19
  53. package/dist/index.js.map +1 -1
  54. package/dist/presentation/OutboundRouter.js +2 -2
  55. package/dist/presentation/OutboundRouter.js.map +1 -1
  56. package/dist/presentation/TurnStateReducer.js +2 -1
  57. package/dist/presentation/TurnStateReducer.js.map +1 -1
  58. package/dist/proxy/ProxySessionController.js +665 -225
  59. package/dist/proxy/ProxySessionController.js.map +1 -1
  60. package/dist/runtime/AcpRuntimeAdapter.js +1 -0
  61. package/dist/runtime/AcpRuntimeAdapter.js.map +1 -1
  62. package/dist/runtime/AgentRuntimeRegistry.js +7 -4
  63. package/dist/runtime/AgentRuntimeRegistry.js.map +1 -1
  64. package/dist/runtime/createAgentRuntimeRegistry.js +22 -0
  65. package/dist/runtime/createAgentRuntimeRegistry.js.map +1 -0
  66. package/dist/startup/SessionMetadataHydrator.js +2 -2
  67. package/dist/startup/SessionMetadataHydrator.js.map +1 -1
  68. package/dist/startup/StartupNotifier.js +1 -0
  69. package/dist/startup/StartupNotifier.js.map +1 -1
  70. package/dist/state/StateStore.js +21 -15
  71. package/dist/state/StateStore.js.map +1 -1
  72. package/dist/supervision/SafeRestartScheduler.js +5 -0
  73. package/dist/supervision/SafeRestartScheduler.js.map +1 -1
  74. package/dist/supervision/systemEnvironment.js +129 -0
  75. package/dist/supervision/systemEnvironment.js.map +1 -0
  76. package/dist/supervisor.js +13 -1
  77. package/dist/supervisor.js.map +1 -1
  78. package/docs/technical-reference.md +35 -21
  79. package/docs/technical-reference.zh.md +35 -21
  80. package/npm-shrinkwrap.json +4 -3
  81. package/package.json +4 -2
  82. package/skills/agent-bot/SKILL.md +73 -30
  83. package/src/acp/AcpSessionManager.ts +73 -54
  84. package/src/cli/FeishuAppConfiguration.ts +2 -2
  85. package/src/cli/FeishuAppRegistration.ts +4 -4
  86. package/src/cli/Initializer.ts +2 -2
  87. package/src/cli/ServerStarter.ts +8 -3
  88. package/src/cli/controlProtocol.ts +21 -1
  89. package/src/cli/help.ts +20 -16
  90. package/src/cli/taskGroupOptions.ts +149 -0
  91. package/src/cli.ts +98 -4
  92. package/src/codex/AppServerConnection.ts +21 -1
  93. package/src/codex/CodexLocalActivityDetector.ts +8 -1
  94. package/src/codex/CodexProcessManager.ts +1 -0
  95. package/src/codex/CodexRuntime.ts +137 -15
  96. package/src/commands/CommandRouter.ts +71 -49
  97. package/src/commands/commandTypes.ts +6 -11
  98. package/src/config/loadConfig.ts +11 -1
  99. package/src/config/schema.ts +7 -1
  100. package/src/console/ConsoleConnector.ts +1 -1
  101. package/src/console/ConsoleTurnPresenter.ts +21 -8
  102. package/src/deprecated-cli.ts +9 -0
  103. package/src/feishu/CardRenderer.ts +423 -15
  104. package/src/feishu/FeishuMessageClient.ts +66 -3
  105. package/src/feishu/FeishuTurnPresenter.ts +22 -3
  106. package/src/feishu/transport.ts +1 -1
  107. package/src/index.ts +54 -24
  108. package/src/presentation/OutboundRouter.ts +15 -3
  109. package/src/presentation/TurnStateReducer.ts +2 -0
  110. package/src/presentation/turnViewTypes.ts +1 -0
  111. package/src/proxy/ProxySessionController.ts +870 -228
  112. package/src/runtime/AcpRuntimeAdapter.ts +1 -0
  113. package/src/runtime/AgentRuntimeRegistry.ts +7 -6
  114. package/src/runtime/createAgentRuntimeRegistry.ts +25 -0
  115. package/src/runtime/types.ts +18 -0
  116. package/src/startup/SessionMetadataHydrator.ts +2 -2
  117. package/src/startup/StartupNotifier.ts +1 -0
  118. package/src/state/StateStore.ts +29 -15
  119. package/src/supervision/SafeRestartScheduler.ts +5 -0
  120. package/src/supervision/systemEnvironment.ts +174 -0
  121. package/src/supervisor.ts +12 -1
@@ -233,8 +233,16 @@ export class FeishuMessageClient implements FeishuOutbound {
233
233
  }
234
234
 
235
235
  async updateInteractiveCard(messageId: string, card: Record<string, unknown>): Promise<void> {
236
+ const preparedCard = await this.prepareInteractiveCard(card);
237
+ await this.updateInteractiveCardNow(messageId, preparedCard);
238
+ }
239
+
240
+ private async updateInteractiveCardNow(
241
+ messageId: string,
242
+ card: Record<string, unknown>,
243
+ allowAuditFallback = true,
244
+ ): Promise<void> {
236
245
  try {
237
- const preparedCard = await this.prepareInteractiveCard(card);
238
246
  const token = await this.getTenantAccessToken();
239
247
  const response = await fetch(`https://open.feishu.cn/open-apis/im/v1/messages/${messageId}`, {
240
248
  method: "PATCH",
@@ -242,14 +250,26 @@ export class FeishuMessageClient implements FeishuOutbound {
242
250
  Authorization: `Bearer ${token}`,
243
251
  "Content-Type": "application/json; charset=utf-8",
244
252
  },
245
- body: JSON.stringify({ content: JSON.stringify(preparedCard) }),
253
+ body: JSON.stringify({ content: JSON.stringify(card) }),
246
254
  });
247
255
  const payload = (await response.json()) as SendMessageResponse;
248
256
  if (!response.ok || payload.code !== 0) {
249
257
  throw new FeishuApiError(payload.msg || response.statusText, payload.code, payload, "update", response.status);
250
258
  }
251
259
  } catch (error) {
252
- throw normalizeTransportError(error, "update");
260
+ const normalized = normalizeTransportError(error, "update");
261
+ const fallback = allowAuditFallback && normalized.isEmailAddressAuditFailure
262
+ ? sanitizeEmailAddressesInContent(card)
263
+ : undefined;
264
+ if (fallback) {
265
+ this.logger.warn(
266
+ { code: normalized.code, messageId },
267
+ "Feishu rejected a card update as email-sensitive; retrying with audit-safe text.",
268
+ );
269
+ await this.updateInteractiveCardNow(messageId, fallback, false);
270
+ return;
271
+ }
272
+ throw normalized;
253
273
  }
254
274
  }
255
275
 
@@ -278,6 +298,7 @@ export class FeishuMessageClient implements FeishuOutbound {
278
298
  msgType: string,
279
299
  content: Record<string, unknown>,
280
300
  idempotencyKey?: string,
301
+ allowAuditFallback = true,
281
302
  ): Promise<string | undefined> {
282
303
  let token: string;
283
304
  try {
@@ -317,6 +338,16 @@ export class FeishuMessageClient implements FeishuOutbound {
317
338
  lastError = error;
318
339
 
319
340
  if (!error.isRetryable || attempt === 2) {
341
+ const fallback = allowAuditFallback && error.isEmailAddressAuditFailure
342
+ ? sanitizeEmailAddressesInContent(content)
343
+ : undefined;
344
+ if (fallback) {
345
+ this.logger.warn(
346
+ { code: error.code, messageId: target.messageId, msgType },
347
+ "Feishu rejected a reply as email-sensitive; retrying with audit-safe text.",
348
+ );
349
+ return this.replyMessageNow(target, msgType, fallback, idempotencyKey, false);
350
+ }
320
351
  this.logger.error({ error, messageId: target.messageId, msgType }, "Failed to reply to Feishu message.");
321
352
  throw error;
322
353
  }
@@ -334,6 +365,7 @@ export class FeishuMessageClient implements FeishuOutbound {
334
365
  msgType: string,
335
366
  content: Record<string, unknown>,
336
367
  idempotencyKey?: string,
368
+ allowAuditFallback = true,
337
369
  ): Promise<string | undefined> {
338
370
  let token: string;
339
371
  try {
@@ -376,6 +408,16 @@ export class FeishuMessageClient implements FeishuOutbound {
376
408
  lastError = error;
377
409
 
378
410
  if (!error.isRetryable || attempt === 2) {
411
+ const fallback = allowAuditFallback && error.isEmailAddressAuditFailure
412
+ ? sanitizeEmailAddressesInContent(content)
413
+ : undefined;
414
+ if (fallback) {
415
+ this.logger.warn(
416
+ { code: error.code, contextKey, msgType },
417
+ "Feishu rejected a message as email-sensitive; retrying with audit-safe text.",
418
+ );
419
+ return this.sendMessageNow(contextKey, msgType, fallback, idempotencyKey, false);
420
+ }
379
421
  this.logger.error({ error, contextKey, msgType }, "Failed to send Feishu message.");
380
422
  throw error;
381
423
  }
@@ -573,6 +615,7 @@ export class FeishuMessageClient implements FeishuOutbound {
573
615
  export class FeishuApiError extends Error {
574
616
  readonly isRateLimit: boolean;
575
617
  readonly isRetryable: boolean;
618
+ readonly isEmailAddressAuditFailure: boolean;
576
619
 
577
620
  constructor(
578
621
  message: string,
@@ -591,6 +634,7 @@ export class FeishuApiError extends Error {
591
634
  this.name = "FeishuApiError";
592
635
  this.isRateLimit = code === 230020 || message.toLowerCase().includes("frequency limit");
593
636
  this.isRetryable = forceRetryable || this.isRateLimit || (httpStatus !== undefined && httpStatus >= 500);
637
+ this.isEmailAddressAuditFailure = code === 230028 && message.includes("EMAIL_ADDRESS");
594
638
  }
595
639
  }
596
640
 
@@ -642,6 +686,25 @@ function isRecord(value: unknown): value is Record<string, unknown> {
642
686
  return typeof value === "object" && value !== null && !Array.isArray(value);
643
687
  }
644
688
 
689
+ function sanitizeEmailAddressesInContent(content: Record<string, unknown>): Record<string, unknown> | undefined {
690
+ let changed = false;
691
+ const sanitize = (value: unknown): unknown => {
692
+ if (typeof value === "string") {
693
+ const sanitized = value.replace(
694
+ /\b([a-z0-9._%+-]+)@([a-z0-9.-]+\.[a-z]{2,})\b/gi,
695
+ "$1 [at] $2",
696
+ );
697
+ if (sanitized !== value) changed = true;
698
+ return sanitized;
699
+ }
700
+ if (Array.isArray(value)) return value.map(sanitize);
701
+ if (!isRecord(value)) return value;
702
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, sanitize(child)]));
703
+ };
704
+ const sanitized = sanitize(content) as Record<string, unknown>;
705
+ return changed ? sanitized : undefined;
706
+ }
707
+
645
708
  function cardImageLabel(image: Record<string, unknown>): string {
646
709
  const alt = image.alt;
647
710
  if (isRecord(alt) && typeof alt.content === "string" && alt.content.trim()) return alt.content;
@@ -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);
@@ -152,6 +162,7 @@ export class FeishuTurnPresenter {
152
162
  await entry.scheduler?.flush(entry.state);
153
163
  } catch (error) {
154
164
  this.options.onError?.(error);
165
+ return;
155
166
  }
156
167
  await this.syncThinkingCardReaction(entry);
157
168
  }
@@ -178,6 +189,8 @@ export class FeishuTurnPresenter {
178
189
  this.sessionTitles.get(sessionId),
179
190
  undefined,
180
191
  this.sessionCwds.get(sessionId),
192
+ undefined,
193
+ this.sessionAgentLabels.get(sessionId),
181
194
  ),
182
195
  status: "running" as const,
183
196
  };
@@ -212,6 +225,7 @@ export class FeishuTurnPresenter {
212
225
  pending.state.replyTarget,
213
226
  pending.state.projectCwd,
214
227
  pending.state.prompt,
228
+ pending.state.agentLabel,
215
229
  ),
216
230
  event,
217
231
  );
@@ -346,6 +360,7 @@ export class FeishuTurnPresenter {
346
360
  undefined,
347
361
  this.sessionCwds.get(event.sessionId),
348
362
  undefined,
363
+ this.sessionAgentLabels.get(event.sessionId),
349
364
  );
350
365
  const state = reduceTurnEvent(
351
366
  initial,
@@ -393,16 +408,20 @@ export class FeishuTurnPresenter {
393
408
  }
394
409
 
395
410
  private async finalize(entry: TurnEntry, state: TurnViewState): Promise<void> {
411
+ let terminalCardError: unknown;
396
412
  if (!entry.historySnapshot) {
397
413
  try {
398
414
  await entry.scheduler?.flush(state);
399
415
  } catch (error) {
400
416
  this.options.onError?.(error);
417
+ terminalCardError = error;
401
418
  }
402
419
  }
420
+ if (state.status === "completed" && state.finalResponse) {
421
+ await this.deliverFinal(entry.contextKey, state);
422
+ }
423
+ if (terminalCardError) throw terminalCardError;
403
424
  await this.syncThinkingCardReaction(entry);
404
- if (state.status !== "completed" || !state.finalResponse) return;
405
- await this.deliverFinal(entry.contextKey, state);
406
425
  }
407
426
 
408
427
  private async syncThinkingCardReaction(entry: TurnEntry): Promise<void> {
@@ -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
@@ -1,10 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { spawn } from "node:child_process";
3
3
  import { fileURLToPath } from "node:url";
4
- import { AcpProcessManager } from "./acp/AcpProcessManager.js";
5
- import { AcpSessionManager } from "./acp/AcpSessionManager.js";
6
- import { CodexProcessManager } from "./codex/CodexProcessManager.js";
7
- import { CodexRuntime } from "./codex/CodexRuntime.js";
8
4
  import { LocalControlServer } from "./cli/LocalControlServer.js";
9
5
  import { controlEndpoint, type ControlRequest, type ControlResponse } from "./cli/controlProtocol.js";
10
6
  import { readPackageVersion } from "./cli/packageVersion.js";
@@ -20,8 +16,7 @@ import { requireServerFeishuTransport } from "./feishu/transport.js";
20
16
  import { createLogger } from "./logging/logger.js";
21
17
  import { OutboundRouter, type OutboundRoute } from "./presentation/OutboundRouter.js";
22
18
  import { ProxySessionController } from "./proxy/ProxySessionController.js";
23
- import { AcpRuntimeAdapter } from "./runtime/AcpRuntimeAdapter.js";
24
- import { AgentRuntimeRegistry } from "./runtime/AgentRuntimeRegistry.js";
19
+ import { createAgentRuntimeRegistry } from "./runtime/createAgentRuntimeRegistry.js";
25
20
  import { StateStore } from "./state/StateStore.js";
26
21
  import { StartupNotifier } from "./startup/StartupNotifier.js";
27
22
  import { SessionMetadataHydrator } from "./startup/SessionMetadataHydrator.js";
@@ -35,6 +30,7 @@ import {
35
30
  prepareCrashReportDirectory,
36
31
  resolveSupervisorDiagnosticsPaths,
37
32
  } from "./supervision/SupervisorDiagnostics.js";
33
+ import { refreshedSystemEnvironment } from "./supervision/systemEnvironment.js";
38
34
 
39
35
  const processStartedAt = new Date();
40
36
  const agentBotVersion = readPackageVersion();
@@ -47,18 +43,7 @@ const transport = consoleOnly ? "console" : requireServerFeishuTransport(config.
47
43
  const logger = createLogger(config);
48
44
  const store = new StateStore(config.storage.sqlitePath);
49
45
 
50
- const acpProcessManager = new AcpProcessManager(logger);
51
- const acpSessionManager = new AcpSessionManager(config, acpProcessManager, logger);
52
- const acpRuntime = new AcpRuntimeAdapter(acpSessionManager);
53
- const codexAgent = Object.values(config.agents).find((agent) => agent.kind === "codex");
54
- const codexProcessManager = new CodexProcessManager(
55
- codexAgent?.command ?? "codex",
56
- codexAgent?.args ?? ["app-server", "--listen", "stdio://"],
57
- codexAgent?.env ?? {},
58
- logger,
59
- );
60
- const codexRuntime = new CodexRuntime(codexProcessManager, logger);
61
- const runtimes = new AgentRuntimeRegistry({ acp: acpRuntime, codex: codexRuntime });
46
+ const runtimes = createAgentRuntimeRegistry(config, logger);
62
47
 
63
48
  const routes: OutboundRoute[] = [];
64
49
  let feishuConnector: FeishuConnector | undefined;
@@ -85,7 +70,7 @@ if (feishuOutbound) {
85
70
  defaultAgentName,
86
71
  defaultAgentTitle: defaultAgent?.title ?? defaultAgentName,
87
72
  cwd: path.resolve(config.defaults.cwd),
88
- workspaceKind: defaultAgent?.kind === "codex" ? "projectless" : "project",
73
+ workspaceKind: defaultAgent?.kind === "app-server" ? "projectless" : "project",
89
74
  defaultUserOpenId: config.feishu.userOpenId,
90
75
  }, metadataHydrator);
91
76
  safeRestartNotifier = new SafeRestartNotifier(store, feishuOutbound, renderer, logger);
@@ -149,8 +134,24 @@ if (feishuConnector && startupNotifier) {
149
134
  }
150
135
  consoleConnector?.start();
151
136
 
152
- async function requestRestart(contextKey: string): Promise<void> {
153
- await initiateRestart("用户执行 /restart 命令", contextKey);
137
+ async function requestRestart(contextKey: string, force: boolean): Promise<void> {
138
+ if (restartRequested) {
139
+ await outbound.sendText(contextKey, "Agent Bot 已在重启中,请稍候。").catch(() => undefined);
140
+ return;
141
+ }
142
+ if (force) {
143
+ await initiateRestart("用户执行 /restart --force 命令", contextKey);
144
+ return;
145
+ }
146
+ const newlyScheduled = safeRestart.schedule("用户执行 /restart 命令");
147
+ await outbound.sendText(
148
+ contextKey,
149
+ newlyScheduled
150
+ ? "已安排安全重启。Agent Bot 会等待所有任务完成、最终结果投递完成,并保持 15 秒无新消息后重启。"
151
+ : "安全重启已在等待中,已更新重启原因。",
152
+ ).catch((error: unknown) => {
153
+ logger.warn({ error, contextKey }, "Failed to send safe restart acknowledgement.");
154
+ });
154
155
  }
155
156
 
156
157
  async function initiateRestart(reason: string, contextKey?: string): Promise<void> {
@@ -159,7 +160,7 @@ async function initiateRestart(reason: string, contextKey?: string): Promise<voi
159
160
  return;
160
161
  }
161
162
  restartRequested = true;
162
- safeRestart.cancel();
163
+ await safeRestart.cancelCurrent();
163
164
  await safeRestartNotifier?.flush();
164
165
  if (contextKey) {
165
166
  await outbound.sendText(contextKey, "Agent Bot 正在重启,恢复在线后会发送启动状态通知。").catch((error: unknown) => {
@@ -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
 
@@ -226,7 +248,6 @@ async function shutdown(exitCode: number, restartReason?: string): Promise<void>
226
248
  delay(5_000),
227
249
  ]);
228
250
  runtimes.close();
229
- acpProcessManager.stopAll();
230
251
  await controlServer?.close().catch((error: unknown) => logger.warn({ error }, "Failed to close local control endpoint."));
231
252
  store.close();
232
253
  if (restartReason) startReplacementSupervisor(restartReason);
@@ -237,6 +258,15 @@ function startReplacementSupervisor(restartReason: string): void {
237
258
  const supervisorEntry = fileURLToPath(new URL("./supervisor.js", import.meta.url));
238
259
  const reportDirectory = resolveSupervisorDiagnosticsPaths(config).crashReportDirectory;
239
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
+ }
240
270
  const supervisor = spawn(process.execPath, [
241
271
  ...nodeDiagnosticReportArguments(reportDirectory),
242
272
  supervisorEntry,
@@ -245,7 +275,7 @@ function startReplacementSupervisor(restartReason: string): void {
245
275
  detached: true,
246
276
  windowsHide: true,
247
277
  stdio: "ignore",
248
- env: replacementSupervisorEnvironment(restartReason),
278
+ env: replacementSupervisorEnvironment(restartReason, environmentRefresh.environment),
249
279
  });
250
280
  supervisor.unref();
251
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;