@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.
- package/.env.example +1 -1
- package/CHANGELOG.md +19 -0
- package/README.md +63 -44
- package/README.zh.md +63 -44
- package/config.example.yaml +8 -22
- package/dist/cli/AgentPrerequisites.js +262 -0
- package/dist/cli/AgentPrerequisites.js.map +1 -0
- package/dist/cli/DefaultAgentSelection.js +47 -0
- package/dist/cli/DefaultAgentSelection.js.map +1 -0
- 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 +166 -3
- 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 +24 -20
- 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 +268 -6
- package/dist/cli.js.map +1 -1
- package/dist/codex/AppServerConnection.js +3 -3
- package/dist/codex/AppServerConnection.js.map +1 -1
- package/dist/codex/CodexProcessManager.js +4 -4
- package/dist/codex/CodexProcessManager.js.map +1 -1
- package/dist/codex/CodexRuntime.js +63 -25
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/config/loadConfig.js +8 -22
- 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 +3 -3
- package/dist/console/ConsoleConnector.js.map +1 -1
- package/dist/console/ConsoleTurnPresenter.js +14 -7
- 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 +13 -12
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuTurnPresenter.js +9 -5
- 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 +21 -3
- 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 +128 -72
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/runtime/AcpRuntimeAdapter.js +3 -2
- package/dist/runtime/AcpRuntimeAdapter.js.map +1 -1
- package/dist/runtime/createAgentRuntimeRegistry.js +1 -1
- package/dist/runtime/createAgentRuntimeRegistry.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 +53 -34
- package/docs/technical-reference.zh.md +53 -34
- package/npm-shrinkwrap.json +4 -3
- package/package.json +9 -4
- package/skills/agent-bot/SKILL.md +72 -33
- package/src/cli/AgentPrerequisites.ts +377 -0
- package/src/cli/DefaultAgentSelection.ts +56 -0
- package/src/cli/FeishuAppConfiguration.ts +2 -2
- package/src/cli/FeishuAppRegistration.ts +4 -4
- package/src/cli/Initializer.ts +206 -5
- package/src/cli/ServerStarter.ts +8 -3
- package/src/cli/controlProtocol.ts +21 -1
- package/src/cli/help.ts +24 -20
- package/src/cli/taskGroupOptions.ts +149 -0
- package/src/cli.ts +391 -9
- package/src/codex/AppServerConnection.ts +3 -3
- package/src/codex/CodexProcessManager.ts +4 -4
- package/src/codex/CodexRuntime.ts +84 -31
- package/src/config/loadConfig.ts +8 -22
- package/src/config/schema.ts +7 -1
- package/src/console/ConsoleConnector.ts +3 -3
- package/src/console/ConsoleTurnPresenter.ts +20 -7
- package/src/deprecated-cli.ts +9 -0
- package/src/feishu/CardRenderer.ts +13 -12
- package/src/feishu/FeishuTurnPresenter.ts +15 -1
- package/src/feishu/transport.ts +1 -1
- package/src/index.ts +34 -3
- 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 +217 -73
- package/src/runtime/AcpRuntimeAdapter.ts +3 -1
- package/src/runtime/createAgentRuntimeRegistry.ts +1 -1
- package/src/supervision/systemEnvironment.ts +174 -0
- 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 =
|
|
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
|
-
"
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 ?? "
|
|
674
|
+
this.emit({ type: "turn_failed", sessionId, turnId: mapped.turnId, message: mapped.error ?? "App Server turn failed." });
|
|
654
675
|
} else {
|
|
655
|
-
const finalResponse =
|
|
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) ?? "
|
|
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: "
|
|
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 ?? "
|
|
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 =
|
|
819
|
-
|
|
820
|
-
|
|
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
|
|
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(`
|
|
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: `
|
|
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 ?? [])]
|
package/src/config/loadConfig.ts
CHANGED
|
@@ -22,7 +22,7 @@ console:
|
|
|
22
22
|
|
|
23
23
|
agents:
|
|
24
24
|
codex:
|
|
25
|
-
kind: "
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
traex:
|
|
37
|
+
kind: "app-server"
|
|
38
|
+
title: "TraeX"
|
|
39
|
+
command: "traex"
|
|
39
40
|
args:
|
|
40
|
-
- "
|
|
41
|
-
- "
|
|
42
|
-
|
|
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:
|
package/src/config/schema.ts
CHANGED
|
@@ -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:
|
|
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
|
|
16
|
-
console.log("
|
|
17
|
-
const reader = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: "
|
|
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(
|
|
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(
|
|
22
|
-
await this.outbound.sendText(contextKey,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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 ?? "
|
|
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("
|
|
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 ?? "
|
|
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("
|
|
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 ?? "
|
|
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("
|
|
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
|
-
?
|
|
677
|
-
: "
|
|
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
|
-
|
|
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
|
|
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(
|
|
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,
|
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
|
@@ -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
|
|
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 === "
|
|
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
|
}
|