@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.
- package/.env.example +1 -1
- package/CHANGELOG.md +25 -0
- package/README.md +55 -43
- package/README.zh.md +55 -43
- package/assets/agent-bot-logo.png +0 -0
- package/config.example.yaml +11 -1
- package/dist/acp/AcpSessionManager.js +78 -46
- package/dist/acp/AcpSessionManager.js.map +1 -1
- package/dist/cli/FeishuAppConfiguration.js +1 -1
- package/dist/cli/FeishuAppConfiguration.js.map +1 -1
- package/dist/cli/FeishuAppRegistration.js +2 -2
- package/dist/cli/FeishuAppRegistration.js.map +1 -1
- package/dist/cli/Initializer.js +1 -1
- package/dist/cli/Initializer.js.map +1 -1
- package/dist/cli/ServerStarter.js +7 -2
- package/dist/cli/ServerStarter.js.map +1 -1
- package/dist/cli/controlProtocol.js.map +1 -1
- package/dist/cli/help.js +20 -16
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/taskGroupOptions.js +95 -0
- package/dist/cli/taskGroupOptions.js.map +1 -0
- package/dist/cli.js +74 -2
- package/dist/cli.js.map +1 -1
- package/dist/codex/AppServerConnection.js +15 -1
- package/dist/codex/AppServerConnection.js.map +1 -1
- package/dist/codex/CodexLocalActivityDetector.js +4 -1
- package/dist/codex/CodexLocalActivityDetector.js.map +1 -1
- package/dist/codex/CodexProcessManager.js +1 -0
- package/dist/codex/CodexProcessManager.js.map +1 -1
- package/dist/codex/CodexRuntime.js +112 -11
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/commands/CommandRouter.js +64 -49
- package/dist/commands/CommandRouter.js.map +1 -1
- package/dist/config/loadConfig.js +11 -1
- package/dist/config/loadConfig.js.map +1 -1
- package/dist/config/schema.js +6 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/console/ConsoleConnector.js +1 -1
- package/dist/console/ConsoleConnector.js.map +1 -1
- package/dist/console/ConsoleTurnPresenter.js +15 -8
- package/dist/console/ConsoleTurnPresenter.js.map +1 -1
- package/dist/deprecated-cli.js +5 -0
- package/dist/deprecated-cli.js.map +1 -0
- package/dist/feishu/CardRenderer.js +350 -15
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuMessageClient.js +51 -5
- package/dist/feishu/FeishuMessageClient.js.map +1 -1
- package/dist/feishu/FeishuTurnPresenter.js +17 -8
- package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
- package/dist/feishu/transport.js +1 -1
- package/dist/feishu/transport.js.map +1 -1
- package/dist/index.js +38 -19
- package/dist/index.js.map +1 -1
- package/dist/presentation/OutboundRouter.js +2 -2
- package/dist/presentation/OutboundRouter.js.map +1 -1
- package/dist/presentation/TurnStateReducer.js +2 -1
- package/dist/presentation/TurnStateReducer.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +665 -225
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/runtime/AcpRuntimeAdapter.js +1 -0
- package/dist/runtime/AcpRuntimeAdapter.js.map +1 -1
- package/dist/runtime/AgentRuntimeRegistry.js +7 -4
- package/dist/runtime/AgentRuntimeRegistry.js.map +1 -1
- package/dist/runtime/createAgentRuntimeRegistry.js +22 -0
- package/dist/runtime/createAgentRuntimeRegistry.js.map +1 -0
- package/dist/startup/SessionMetadataHydrator.js +2 -2
- package/dist/startup/SessionMetadataHydrator.js.map +1 -1
- package/dist/startup/StartupNotifier.js +1 -0
- package/dist/startup/StartupNotifier.js.map +1 -1
- package/dist/state/StateStore.js +21 -15
- package/dist/state/StateStore.js.map +1 -1
- package/dist/supervision/SafeRestartScheduler.js +5 -0
- package/dist/supervision/SafeRestartScheduler.js.map +1 -1
- package/dist/supervision/systemEnvironment.js +129 -0
- package/dist/supervision/systemEnvironment.js.map +1 -0
- package/dist/supervisor.js +13 -1
- package/dist/supervisor.js.map +1 -1
- package/docs/technical-reference.md +35 -21
- package/docs/technical-reference.zh.md +35 -21
- package/npm-shrinkwrap.json +4 -3
- package/package.json +4 -2
- package/skills/agent-bot/SKILL.md +73 -30
- package/src/acp/AcpSessionManager.ts +73 -54
- package/src/cli/FeishuAppConfiguration.ts +2 -2
- package/src/cli/FeishuAppRegistration.ts +4 -4
- package/src/cli/Initializer.ts +2 -2
- package/src/cli/ServerStarter.ts +8 -3
- package/src/cli/controlProtocol.ts +21 -1
- package/src/cli/help.ts +20 -16
- package/src/cli/taskGroupOptions.ts +149 -0
- package/src/cli.ts +98 -4
- package/src/codex/AppServerConnection.ts +21 -1
- package/src/codex/CodexLocalActivityDetector.ts +8 -1
- package/src/codex/CodexProcessManager.ts +1 -0
- package/src/codex/CodexRuntime.ts +137 -15
- package/src/commands/CommandRouter.ts +71 -49
- package/src/commands/commandTypes.ts +6 -11
- package/src/config/loadConfig.ts +11 -1
- package/src/config/schema.ts +7 -1
- package/src/console/ConsoleConnector.ts +1 -1
- package/src/console/ConsoleTurnPresenter.ts +21 -8
- package/src/deprecated-cli.ts +9 -0
- package/src/feishu/CardRenderer.ts +423 -15
- package/src/feishu/FeishuMessageClient.ts +66 -3
- package/src/feishu/FeishuTurnPresenter.ts +22 -3
- package/src/feishu/transport.ts +1 -1
- package/src/index.ts +54 -24
- package/src/presentation/OutboundRouter.ts +15 -3
- package/src/presentation/TurnStateReducer.ts +2 -0
- package/src/presentation/turnViewTypes.ts +1 -0
- package/src/proxy/ProxySessionController.ts +870 -228
- package/src/runtime/AcpRuntimeAdapter.ts +1 -0
- package/src/runtime/AgentRuntimeRegistry.ts +7 -6
- package/src/runtime/createAgentRuntimeRegistry.ts +25 -0
- package/src/runtime/types.ts +18 -0
- package/src/startup/SessionMetadataHydrator.ts +2 -2
- package/src/startup/StartupNotifier.ts +1 -0
- package/src/state/StateStore.ts +29 -15
- package/src/supervision/SafeRestartScheduler.ts +5 -0
- package/src/supervision/systemEnvironment.ts +174 -0
- 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(
|
|
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
|
-
|
|
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(
|
|
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> {
|
package/src/feishu/transport.ts
CHANGED
|
@@ -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
|
|
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 {
|
|
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
|
|
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 === "
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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(
|
|
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,
|