@keyou007/agent-bot 0.1.11 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +1 -1
- package/CHANGELOG.md +10 -0
- package/README.md +41 -35
- package/README.zh.md +41 -35
- package/config.example.yaml +11 -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/CodexRuntime.js +35 -9
- package/dist/codex/CodexRuntime.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 +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 +5 -4
- 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 +20 -2
- 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 +78 -22
- package/dist/proxy/ProxySessionController.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 +21 -17
- package/docs/technical-reference.zh.md +21 -17
- package/npm-shrinkwrap.json +4 -3
- package/package.json +3 -2
- package/skills/agent-bot/SKILL.md +61 -28
- 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/CodexRuntime.ts +46 -13
- 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 +20 -7
- package/src/deprecated-cli.ts +9 -0
- package/src/feishu/CardRenderer.ts +5 -4
- package/src/feishu/FeishuTurnPresenter.ts +15 -1
- package/src/feishu/transport.ts +1 -1
- package/src/index.ts +33 -2
- 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 +166 -22
- package/src/runtime/createAgentRuntimeRegistry.ts +1 -1
- package/src/supervision/systemEnvironment.ts +174 -0
- package/src/supervisor.ts +12 -1
package/src/cli.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { sendControlRequest, isServerReachable } from "./cli/LocalControlClient.
|
|
|
7
7
|
import {
|
|
8
8
|
controlEndpoint,
|
|
9
9
|
type ControlResponse,
|
|
10
|
+
type TaskGroupControlData,
|
|
10
11
|
type TaskStatusControlData,
|
|
11
12
|
} from "./cli/controlProtocol.js";
|
|
12
13
|
import {
|
|
@@ -49,6 +50,10 @@ import {
|
|
|
49
50
|
type ServerStartResult,
|
|
50
51
|
} from "./cli/ServerStarter.js";
|
|
51
52
|
import { taskChatRoute } from "./cli/taskChatRoute.js";
|
|
53
|
+
import {
|
|
54
|
+
parseTaskForkGroupOptions,
|
|
55
|
+
parseTaskNewGroupOptions,
|
|
56
|
+
} from "./cli/taskGroupOptions.js";
|
|
52
57
|
import { StateStore, type SessionRecord } from "./state/StateStore.js";
|
|
53
58
|
import {
|
|
54
59
|
nodeDiagnosticReportArguments,
|
|
@@ -99,8 +104,8 @@ async function main(input: string[]): Promise<void> {
|
|
|
99
104
|
return;
|
|
100
105
|
}
|
|
101
106
|
throw new Error(cliText(
|
|
102
|
-
`Unknown command: ${command}. Run
|
|
103
|
-
`未知命令:${command}。运行
|
|
107
|
+
`Unknown command: ${command}. Run agentbot --help for usage.`,
|
|
108
|
+
`未知命令:${command}。运行 agentbot --help 查看用法。`,
|
|
104
109
|
));
|
|
105
110
|
}
|
|
106
111
|
|
|
@@ -246,8 +251,8 @@ async function assertResetProfileServerStopped(configPath?: string): Promise<voi
|
|
|
246
251
|
if (await isServerReachable(endpoint)) {
|
|
247
252
|
throw new Error(
|
|
248
253
|
cliText(
|
|
249
|
-
"Cannot reset a running profile. Stop it with
|
|
250
|
-
"无法重置正在运行的 Profile。请先执行
|
|
254
|
+
"Cannot reset a running profile. Stop it with agentbot --profile <directory> server stop, then try again.",
|
|
255
|
+
"无法重置正在运行的 Profile。请先执行 agentbot --profile <目录> server stop,然后重试。",
|
|
251
256
|
),
|
|
252
257
|
);
|
|
253
258
|
}
|
|
@@ -410,6 +415,51 @@ async function taskCommand(input: string[]): Promise<void> {
|
|
|
410
415
|
process.stdout.write(cliText("Prompt submitted.\n", "提示词已提交。\n"));
|
|
411
416
|
return;
|
|
412
417
|
}
|
|
418
|
+
if (action === "newgroup") {
|
|
419
|
+
const options = parseTaskNewGroupOptions(rest);
|
|
420
|
+
const session = resolveTask(allSessions, options.reference);
|
|
421
|
+
const targetAgentName = options.agentName ?? session.agentName;
|
|
422
|
+
const targetAgent = config.agents[targetAgentName];
|
|
423
|
+
if (!targetAgent) {
|
|
424
|
+
throw new Error(cliText(
|
|
425
|
+
`Unknown Agent standard name: ${targetAgentName}.`,
|
|
426
|
+
`未知的 Agent 标准名:${targetAgentName}。`,
|
|
427
|
+
));
|
|
428
|
+
}
|
|
429
|
+
if (options.projectless && targetAgent.kind !== "app-server") {
|
|
430
|
+
throw new Error(cliText(
|
|
431
|
+
"task newgroup --nodir is only available for App Server agents.",
|
|
432
|
+
"task newgroup --nodir 仅适用于 App Server Agent。",
|
|
433
|
+
));
|
|
434
|
+
}
|
|
435
|
+
requireCliGroupUser(config.feishu.userOpenId);
|
|
436
|
+
const response = await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
|
|
437
|
+
action: "task_new_group",
|
|
438
|
+
localSessionId: session.localSessionId,
|
|
439
|
+
...(options.title ? { title: options.title } : {}),
|
|
440
|
+
...(options.cwd ? { cwd: options.cwd } : {}),
|
|
441
|
+
...(options.agentName ? { agentName: options.agentName } : {}),
|
|
442
|
+
...(options.projectless ? { projectless: true } : {}),
|
|
443
|
+
}, 120_000);
|
|
444
|
+
const result = taskGroupControlData(response);
|
|
445
|
+
if (options.json) printJson(result);
|
|
446
|
+
else printTaskGroupResult(result, "newgroup");
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (action === "forkgroup") {
|
|
450
|
+
const options = parseTaskForkGroupOptions(rest);
|
|
451
|
+
const session = resolveTask(allSessions, options.reference);
|
|
452
|
+
requireCliGroupUser(config.feishu.userOpenId);
|
|
453
|
+
const response = await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
|
|
454
|
+
action: "task_fork_group",
|
|
455
|
+
localSessionId: session.localSessionId,
|
|
456
|
+
...(options.title ? { title: options.title } : {}),
|
|
457
|
+
}, 120_000);
|
|
458
|
+
const result = taskGroupControlData(response);
|
|
459
|
+
if (options.json) printJson(result);
|
|
460
|
+
else printTaskGroupResult(result, "forkgroup");
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
413
463
|
const sessions = filterSessions(allSessions, rest);
|
|
414
464
|
if (action === "list") {
|
|
415
465
|
if (rest.includes("--json")) printJson(sessions);
|
|
@@ -840,6 +890,50 @@ function ensureOk(response: ControlResponse): void {
|
|
|
840
890
|
));
|
|
841
891
|
}
|
|
842
892
|
|
|
893
|
+
function requireCliGroupUser(userOpenId: string | undefined): asserts userOpenId is string {
|
|
894
|
+
if (userOpenId?.startsWith("ou_")) return;
|
|
895
|
+
throw new Error(cliText(
|
|
896
|
+
"The profile has no authorizing Lark user to invite. Run agentbot init again or configure FEISHU_USER_OPEN_ID.",
|
|
897
|
+
"当前 Profile 没有可邀请的飞书授权用户。请重新运行 agentbot init,或配置 FEISHU_USER_OPEN_ID。",
|
|
898
|
+
));
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
function taskGroupControlData(response: ControlResponse): TaskGroupControlData {
|
|
902
|
+
ensureOk(response);
|
|
903
|
+
const data = response.data as Partial<TaskGroupControlData> | undefined;
|
|
904
|
+
if (
|
|
905
|
+
!data
|
|
906
|
+
|| typeof data.sourceLocalSessionId !== "string"
|
|
907
|
+
|| !data.group
|
|
908
|
+
|| typeof data.group.chatId !== "string"
|
|
909
|
+
|| typeof data.group.contextKey !== "string"
|
|
910
|
+
|| typeof data.group.name !== "string"
|
|
911
|
+
|| !data.task
|
|
912
|
+
|| typeof data.task.localSessionId !== "string"
|
|
913
|
+
) {
|
|
914
|
+
throw new Error(cliText(
|
|
915
|
+
"Agent Bot returned an invalid task-group result.",
|
|
916
|
+
"Agent Bot 返回了无效的任务群结果。",
|
|
917
|
+
));
|
|
918
|
+
}
|
|
919
|
+
return data as TaskGroupControlData;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
function printTaskGroupResult(
|
|
923
|
+
result: TaskGroupControlData,
|
|
924
|
+
action: "newgroup" | "forkgroup",
|
|
925
|
+
): void {
|
|
926
|
+
process.stdout.write(cliText(
|
|
927
|
+
`${action === "forkgroup" ? "Fork group" : "Lark group"} created: ${result.group.name}\n`,
|
|
928
|
+
`${action === "forkgroup" ? "Fork 群" : "飞书群"}已创建:${result.group.name}\n`,
|
|
929
|
+
));
|
|
930
|
+
process.stdout.write(`${cliText("Chat ID: ", "群 ID:")}${result.group.chatId}\n`);
|
|
931
|
+
process.stdout.write(`${cliText("Task ID: ", "任务 ID:")}${result.task.localSessionId}\n`);
|
|
932
|
+
if (result.task.remoteSessionId) {
|
|
933
|
+
process.stdout.write(`${cliText("Agent task ID: ", "Agent 任务 ID:")}${result.task.remoteSessionId}\n`);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
843
937
|
function optionValue(args: string[], name: string): string | undefined {
|
|
844
938
|
const index = args.indexOf(name);
|
|
845
939
|
return index >= 0 ? args[index + 1] : undefined;
|
|
@@ -35,12 +35,13 @@ const WINDOWS_SCREENSHOT_DEVELOPER_INSTRUCTIONS = [
|
|
|
35
35
|
"Call Graphics.CopyFromScreen with those physical coordinates and validate that the saved bitmap dimensions exactly equal the selected physical capture bounds.",
|
|
36
36
|
].join(" ");
|
|
37
37
|
|
|
38
|
-
const SESSION_REQUEST_TIMEOUT_MS =
|
|
38
|
+
const SESSION_REQUEST_TIMEOUT_MS = 60_000;
|
|
39
39
|
const CONTROL_REQUEST_TIMEOUT_MS = 10_000;
|
|
40
40
|
const SYNC_REQUEST_TIMEOUT_MS = 5_000;
|
|
41
41
|
// A timed-out fork keeps running in App Server and can create an orphan thread.
|
|
42
42
|
// Wait for its response; connection closure still rejects the request.
|
|
43
43
|
const FORK_REQUEST_TIMEOUT_MS = 0;
|
|
44
|
+
type ThreadListSortKey = "recency_at" | "updated_at";
|
|
44
45
|
|
|
45
46
|
export interface AppServerClient {
|
|
46
47
|
request<T = unknown>(method: string, params?: unknown, timeoutMs?: number): Promise<T>;
|
|
@@ -84,6 +85,7 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
84
85
|
private readonly unsubscribeDisconnect?: () => void;
|
|
85
86
|
private readonly sessionSyncs = new Map<string, Promise<RuntimeSession>>();
|
|
86
87
|
private readonly localActivityDetector?: CodexLocalActivityDetector;
|
|
88
|
+
private threadListSortKey: ThreadListSortKey = "recency_at";
|
|
87
89
|
|
|
88
90
|
constructor(
|
|
89
91
|
private readonly provider: AppServerClientProvider,
|
|
@@ -215,18 +217,18 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
215
217
|
limit?: number;
|
|
216
218
|
} = {}): Promise<RemoteSessionPage> {
|
|
217
219
|
const client = await this.client();
|
|
218
|
-
|
|
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",
|
|
@@ -1021,6 +1042,18 @@ function isUnsupportedExcludeTurnsError(error: unknown): boolean {
|
|
|
1021
1042
|
|| /unknown|unrecognized|unexpected|unsupported|not supported|experimental/i.test(details);
|
|
1022
1043
|
}
|
|
1023
1044
|
|
|
1045
|
+
function isUnsupportedRecencySortError(error: unknown): boolean {
|
|
1046
|
+
if (!(error instanceof AppServerRequestError) || error.method !== "thread/list") return false;
|
|
1047
|
+
const data = typeof error.data === "string"
|
|
1048
|
+
? error.data
|
|
1049
|
+
: error.data === undefined
|
|
1050
|
+
? ""
|
|
1051
|
+
: JSON.stringify(error.data);
|
|
1052
|
+
const details = `${error.serverMessage} ${data}`;
|
|
1053
|
+
return /recency_at/i.test(details)
|
|
1054
|
+
&& /unknown|unrecognized|unexpected|unsupported|not supported|invalid/i.test(details);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1024
1057
|
function remoteSessionSummary(thread: CodexThreadSnapshot): RemoteSessionSummary {
|
|
1025
1058
|
const lastTurn = thread.turns?.at(-1);
|
|
1026
1059
|
const lastCompletedTurn = [...(thread.turns ?? [])]
|
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,6 +33,16 @@ agents:
|
|
|
33
33
|
- "stdio://"
|
|
34
34
|
env: {}
|
|
35
35
|
|
|
36
|
+
traex:
|
|
37
|
+
kind: "app-server"
|
|
38
|
+
title: "TraeX"
|
|
39
|
+
command: "traex"
|
|
40
|
+
args:
|
|
41
|
+
- "app-server"
|
|
42
|
+
- "--listen"
|
|
43
|
+
- "stdio://"
|
|
44
|
+
env: {}
|
|
45
|
+
|
|
36
46
|
coco:
|
|
37
47
|
title: "Coco"
|
|
38
48
|
command: "coco"
|
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([]),
|
|
@@ -14,7 +14,7 @@ export class ConsoleConnector {
|
|
|
14
14
|
if (this.reader) return;
|
|
15
15
|
this.logger.info("Starting local Codex test console.");
|
|
16
16
|
console.log("Codex console ready. Type /help or enter a prompt.");
|
|
17
|
-
const reader = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: "
|
|
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");
|
|
@@ -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();
|
|
@@ -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
|
}
|
|
@@ -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,
|