@keyou007/agent-bot 0.1.12 → 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/CHANGELOG.md +9 -0
- package/README.md +23 -10
- package/README.zh.md +23 -10
- package/config.example.yaml +0 -24
- 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/Initializer.js +165 -2
- package/dist/cli/Initializer.js.map +1 -1
- package/dist/cli/help.js +4 -4
- package/dist/cli.js +194 -4
- 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 +28 -16
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/config/loadConfig.js +0 -24
- package/dist/config/loadConfig.js.map +1 -1
- package/dist/console/ConsoleConnector.js +2 -2
- package/dist/console/ConsoleConnector.js.map +1 -1
- package/dist/feishu/CardRenderer.js +8 -8
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/proxy/ProxySessionController.js +51 -51
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/runtime/AcpRuntimeAdapter.js +3 -2
- package/dist/runtime/AcpRuntimeAdapter.js.map +1 -1
- package/docs/technical-reference.md +32 -17
- package/docs/technical-reference.zh.md +32 -17
- package/npm-shrinkwrap.json +2 -2
- package/package.json +7 -3
- package/skills/agent-bot/SKILL.md +12 -6
- package/src/cli/AgentPrerequisites.ts +377 -0
- package/src/cli/DefaultAgentSelection.ts +56 -0
- package/src/cli/Initializer.ts +204 -3
- package/src/cli/help.ts +4 -4
- package/src/cli.ts +293 -5
- package/src/codex/AppServerConnection.ts +3 -3
- package/src/codex/CodexProcessManager.ts +4 -4
- package/src/codex/CodexRuntime.ts +38 -18
- package/src/config/loadConfig.ts +0 -24
- package/src/console/ConsoleConnector.ts +2 -2
- package/src/feishu/CardRenderer.ts +8 -8
- package/src/index.ts +1 -1
- package/src/proxy/ProxySessionController.ts +52 -52
- package/src/runtime/AcpRuntimeAdapter.ts +3 -1
package/src/cli/help.ts
CHANGED
|
@@ -3,13 +3,13 @@ import { cliLanguage, type CliLanguage } from "./i18n.js";
|
|
|
3
3
|
export function renderCliHelp(version: string, language: CliLanguage = cliLanguage): string {
|
|
4
4
|
if (language === "zh") {
|
|
5
5
|
return `Agent Bot ${version}
|
|
6
|
-
通过飞书使用本地 Codex
|
|
6
|
+
通过飞书使用本地 Codex、TraeX 和兼容 ACP 的 Agent。
|
|
7
7
|
|
|
8
8
|
用法:
|
|
9
9
|
agentbot [选项] <命令>
|
|
10
10
|
|
|
11
11
|
常用命令:
|
|
12
|
-
init
|
|
12
|
+
init 检查并选择 Agent,初始化飞书应用和服务
|
|
13
13
|
init --reset 重置并重新配置显式指定的 Profile
|
|
14
14
|
console 打开本地控制台
|
|
15
15
|
server start 在后台启动 Agent Bot
|
|
@@ -51,13 +51,13 @@ export function renderCliHelp(version: string, language: CliLanguage = cliLangua
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
return `Agent Bot ${version}
|
|
54
|
-
Use
|
|
54
|
+
Use Codex, TraeX, and compatible ACP agents from Lark/Feishu.
|
|
55
55
|
|
|
56
56
|
Usage:
|
|
57
57
|
agentbot [options] <command>
|
|
58
58
|
|
|
59
59
|
Common commands:
|
|
60
|
-
init
|
|
60
|
+
init Check and select an Agent; initialize the Lark app and service
|
|
61
61
|
init --reset Reset and reconfigure an explicit profile
|
|
62
62
|
console Open the local console
|
|
63
63
|
server start Start Agent Bot in the background
|
package/src/cli.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { createInterface } from "node:readline";
|
|
5
6
|
import { loadConfig, loadConfigWithoutEnvironmentMutation } from "./config/loadConfig.js";
|
|
6
7
|
import { sendControlRequest, isServerReachable } from "./cli/LocalControlClient.js";
|
|
7
8
|
import {
|
|
@@ -14,8 +15,10 @@ import {
|
|
|
14
15
|
acquireInitializationLock,
|
|
15
16
|
cleanupFeishuCredentialTemporaryFiles,
|
|
16
17
|
initializeAgentBot,
|
|
18
|
+
readConfiguredAgentSelection,
|
|
17
19
|
readFeishuCredentials,
|
|
18
20
|
shouldCreateFeishuApp,
|
|
21
|
+
writeDefaultAgent,
|
|
19
22
|
writeFeishuCredentials,
|
|
20
23
|
type InitializationResult,
|
|
21
24
|
type InitializationStatus,
|
|
@@ -38,6 +41,18 @@ import {
|
|
|
38
41
|
} from "./cli/FeishuAppConfiguration.js";
|
|
39
42
|
import { renderCliHelp } from "./cli/help.js";
|
|
40
43
|
import { cliLanguage, cliText, localizeCliErrorMessage } from "./cli/i18n.js";
|
|
44
|
+
import {
|
|
45
|
+
inspectSupportedAgent,
|
|
46
|
+
inspectSupportedAgents,
|
|
47
|
+
runSupportedAgentMaintenance,
|
|
48
|
+
type SupportedAgentInspection,
|
|
49
|
+
} from "./cli/AgentPrerequisites.js";
|
|
50
|
+
import {
|
|
51
|
+
parseMaintenanceSelection,
|
|
52
|
+
resolveDefaultAgentChoice,
|
|
53
|
+
selectableDefaultAgents,
|
|
54
|
+
type SelectableDefaultAgent,
|
|
55
|
+
} from "./cli/DefaultAgentSelection.js";
|
|
41
56
|
import { formatServerStatus, withConfiguredFeishuAppId } from "./cli/serverStatus.js";
|
|
42
57
|
import { resolveSystemSkillsRoot, SkillRegistry, type SkillRegistrationStatus } from "./cli/SkillRegistry.js";
|
|
43
58
|
import { readPackageVersion } from "./cli/packageVersion.js";
|
|
@@ -50,6 +65,7 @@ import {
|
|
|
50
65
|
type ServerStartResult,
|
|
51
66
|
} from "./cli/ServerStarter.js";
|
|
52
67
|
import { taskChatRoute } from "./cli/taskChatRoute.js";
|
|
68
|
+
import { refreshedSystemEnvironment } from "./supervision/systemEnvironment.js";
|
|
53
69
|
import {
|
|
54
70
|
parseTaskForkGroupOptions,
|
|
55
71
|
parseTaskNewGroupOptions,
|
|
@@ -112,6 +128,11 @@ async function main(input: string[]): Promise<void> {
|
|
|
112
128
|
type FeishuInitializationStatus = "created" | "existing" | "skipped";
|
|
113
129
|
|
|
114
130
|
interface InitCommandResult extends InitializationResult {
|
|
131
|
+
agents: InitializedAgent[];
|
|
132
|
+
defaultAgent: {
|
|
133
|
+
name: string;
|
|
134
|
+
status: "selected" | "existing";
|
|
135
|
+
};
|
|
115
136
|
feishu: {
|
|
116
137
|
status: FeishuInitializationStatus;
|
|
117
138
|
appId?: string;
|
|
@@ -120,6 +141,13 @@ interface InitCommandResult extends InitializationResult {
|
|
|
120
141
|
server: InitializationServerResult;
|
|
121
142
|
}
|
|
122
143
|
|
|
144
|
+
interface InitializedAgent extends SupportedAgentInspection {
|
|
145
|
+
assistance?: {
|
|
146
|
+
status: "completed" | "skipped" | "unavailable" | "failed";
|
|
147
|
+
error?: string;
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
123
151
|
async function initCommand(
|
|
124
152
|
input: string[],
|
|
125
153
|
configPath: string | undefined,
|
|
@@ -135,13 +163,15 @@ async function initCommand(
|
|
|
135
163
|
if (options.reset) await assertResetProfileServerStopped(configPath);
|
|
136
164
|
paths = initializeAgentBot({ configPath, reset: options.reset });
|
|
137
165
|
if (!options.json) printInitializationPaths(paths);
|
|
166
|
+
const agents = await initializeSupportedAgents(options.json);
|
|
167
|
+
const defaultAgent = await selectDefaultAgent(paths.config.path, agents, options.json);
|
|
138
168
|
initializationLock ??= acquireInitializationLock(paths.home.path);
|
|
139
169
|
cleanupFeishuCredentialTemporaryFiles(paths.env.path);
|
|
140
170
|
const feishu = await initializeFeishu(
|
|
141
171
|
paths,
|
|
142
172
|
options,
|
|
143
173
|
);
|
|
144
|
-
initialized = { ...paths, feishu };
|
|
174
|
+
initialized = { ...paths, agents, defaultAgent, feishu };
|
|
145
175
|
} finally {
|
|
146
176
|
initializationLock?.release();
|
|
147
177
|
}
|
|
@@ -156,6 +186,259 @@ async function initCommand(
|
|
|
156
186
|
else printInitializationServerResult(server);
|
|
157
187
|
}
|
|
158
188
|
|
|
189
|
+
async function initializeSupportedAgents(json: boolean): Promise<InitializedAgent[]> {
|
|
190
|
+
const output = json ? process.stderr : process.stdout;
|
|
191
|
+
output.write(cliText(
|
|
192
|
+
"\nAgent setup\nChecking Codex and TraeX...\n",
|
|
193
|
+
"\nAgent 设置\n正在检查 Codex 和 TraeX...\n",
|
|
194
|
+
));
|
|
195
|
+
const inspections = await inspectSupportedAgents();
|
|
196
|
+
for (const inspection of inspections) printAgentInspection(inspection, output);
|
|
197
|
+
const actionable = inspections.filter((inspection) => inspection.action);
|
|
198
|
+
if (actionable.length === 0) return inspections;
|
|
199
|
+
|
|
200
|
+
output.write(cliText("\nAvailable actions:\n", "\n可执行的操作:\n"));
|
|
201
|
+
for (const [index, inspection] of actionable.entries()) {
|
|
202
|
+
output.write(cliText(
|
|
203
|
+
` ${index + 1}. ${inspection.action?.kind === "install" ? "Install" : "Upgrade"} ${inspection.name}: ${inspection.action?.command}\n`,
|
|
204
|
+
` ${index + 1}. ${inspection.action?.kind === "install" ? "安装" : "升级"} ${inspection.name}:${inspection.action?.command}\n`,
|
|
205
|
+
));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
let selected = new Set<number>();
|
|
209
|
+
if (!process.stdin.isTTY) {
|
|
210
|
+
output.write(cliText(
|
|
211
|
+
"No interactive terminal is available. Run the commands above manually if needed.\n",
|
|
212
|
+
"当前没有交互式终端,如有需要请手动执行上面的命令。\n",
|
|
213
|
+
));
|
|
214
|
+
} else {
|
|
215
|
+
selected = new Set(await promptForAgentMaintenance(actionable.length));
|
|
216
|
+
if (selected.size === 0) {
|
|
217
|
+
output.write(cliText("No Agent changes selected.\n", "已跳过 Agent 安装和升级。\n"));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const initialized: InitializedAgent[] = [];
|
|
222
|
+
for (const inspection of inspections) {
|
|
223
|
+
if (!inspection.action) {
|
|
224
|
+
initialized.push(inspection);
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const actionIndex = actionable.indexOf(inspection);
|
|
228
|
+
if (!selected.has(actionIndex)) {
|
|
229
|
+
initialized.push({
|
|
230
|
+
...inspection,
|
|
231
|
+
assistance: { status: process.stdin.isTTY ? "skipped" : "unavailable" },
|
|
232
|
+
});
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
output.write(cliText(
|
|
237
|
+
`\nRunning ${inspection.name} ${inspection.action.kind}: ${inspection.action.command}\n`,
|
|
238
|
+
`\n正在${inspection.action.kind === "install" ? "安装" : "升级"} ${inspection.name}:${inspection.action.command}\n`,
|
|
239
|
+
));
|
|
240
|
+
const maintenance = await runSupportedAgentMaintenance(inspection.id, inspection.action.kind);
|
|
241
|
+
if (maintenance.status !== 0 || maintenance.error) {
|
|
242
|
+
const error = maintenance.error ?? cliText(
|
|
243
|
+
`Command exited with code ${maintenance.status ?? "unknown"}.`,
|
|
244
|
+
`命令退出码为 ${maintenance.status ?? "未知"}。`,
|
|
245
|
+
);
|
|
246
|
+
output.write(cliText(
|
|
247
|
+
`${inspection.name} ${inspection.action.kind} did not complete; initialization will continue.\n`,
|
|
248
|
+
`${inspection.name}${inspection.action.kind === "install" ? "安装" : "升级"}未完成,初始化将继续。\n`,
|
|
249
|
+
));
|
|
250
|
+
initialized.push({ ...inspection, assistance: { status: "failed", error } });
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
refreshCurrentProcessPath();
|
|
255
|
+
const refreshed = await inspectSupportedAgent(inspection.id);
|
|
256
|
+
if (refreshed.installedVersion) {
|
|
257
|
+
output.write(cliText(
|
|
258
|
+
`${inspection.name} is ready (${refreshed.installedVersion}).\n`,
|
|
259
|
+
`${inspection.name} 已就绪(${refreshed.installedVersion})。\n`,
|
|
260
|
+
));
|
|
261
|
+
} else {
|
|
262
|
+
output.write(cliText(
|
|
263
|
+
`${inspection.name} command completed, but the CLI is not visible in this process. Open a new terminal and run agentbot init again.\n`,
|
|
264
|
+
`${inspection.name} 命令已完成,但当前进程仍无法找到该 CLI。请打开新终端后重新运行 agentbot init。\n`,
|
|
265
|
+
));
|
|
266
|
+
}
|
|
267
|
+
initialized.push({ ...refreshed, assistance: { status: "completed" } });
|
|
268
|
+
}
|
|
269
|
+
return initialized;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function selectDefaultAgent(
|
|
273
|
+
configPath: string,
|
|
274
|
+
inspections: InitializedAgent[],
|
|
275
|
+
json: boolean,
|
|
276
|
+
): Promise<InitCommandResult["defaultAgent"]> {
|
|
277
|
+
const output = json ? process.stderr : process.stdout;
|
|
278
|
+
const configured = readConfiguredAgentSelection(configPath);
|
|
279
|
+
const choices = selectableDefaultAgents(configured.agents, inspections);
|
|
280
|
+
if (!process.stdin.isTTY) {
|
|
281
|
+
if (!configured.defaultAgent || !configured.agents.some((agent) => agent.name === configured.defaultAgent)) {
|
|
282
|
+
throw new Error(cliText(
|
|
283
|
+
"A default Agent is not configured. Run agentbot init in an interactive terminal and select one.",
|
|
284
|
+
"尚未配置默认 Agent。请在交互式终端中运行 agentbot init 并选择一个 Agent。",
|
|
285
|
+
));
|
|
286
|
+
}
|
|
287
|
+
output.write(cliText(
|
|
288
|
+
`Default Agent: ${configured.defaultAgent} (kept because no interactive terminal is available)\n`,
|
|
289
|
+
`默认 Agent:${configured.defaultAgent}(当前没有交互式终端,已保留现有设置)\n`,
|
|
290
|
+
));
|
|
291
|
+
return { name: configured.defaultAgent, status: "existing" };
|
|
292
|
+
}
|
|
293
|
+
if (choices.length === 0) {
|
|
294
|
+
throw new Error(cliText(
|
|
295
|
+
"No available configured Agent can be selected. Install Codex or TraeX, then run agentbot init again.",
|
|
296
|
+
"没有可选择的已配置 Agent。请安装 Codex 或 TraeX,然后重新运行 agentbot init。",
|
|
297
|
+
));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
output.write(cliText("\nSelect the default Agent:\n", "\n请选择默认 Agent:\n"));
|
|
301
|
+
for (const [index, choice] of choices.entries()) {
|
|
302
|
+
const version = choice.installedVersion ? ` (${choice.installedVersion})` : "";
|
|
303
|
+
const current = choice.name === configured.defaultAgent
|
|
304
|
+
? cliText(" [current]", " [当前]")
|
|
305
|
+
: "";
|
|
306
|
+
output.write(` ${index + 1}. ${choice.name} - ${choice.title}${version}${current}\n`);
|
|
307
|
+
}
|
|
308
|
+
const selected = choices[await promptForDefaultAgent(choices, configured.defaultAgent)];
|
|
309
|
+
if (!selected) throw new Error(cliText("Default Agent selection failed.", "默认 Agent 选择失败。"));
|
|
310
|
+
const updated = writeDefaultAgent(configPath, selected.name);
|
|
311
|
+
output.write(updated
|
|
312
|
+
? cliText(`Saved default Agent: ${selected.name}\n`, `已保存默认 Agent:${selected.name}\n`)
|
|
313
|
+
: cliText(`Default Agent unchanged: ${selected.name}\n`, `默认 Agent 保持不变:${selected.name}\n`));
|
|
314
|
+
return { name: selected.name, status: updated ? "selected" : "existing" };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function promptForAgentMaintenance(choiceCount: number): Promise<number[]> {
|
|
318
|
+
const readline = createInterface({ input: process.stdin, output: process.stderr });
|
|
319
|
+
return new Promise((resolve, reject) => {
|
|
320
|
+
let settled = false;
|
|
321
|
+
const finish = (selection: number[]): void => {
|
|
322
|
+
settled = true;
|
|
323
|
+
readline.close();
|
|
324
|
+
resolve(selection);
|
|
325
|
+
};
|
|
326
|
+
const ask = (): void => {
|
|
327
|
+
readline.question(cliText(
|
|
328
|
+
"Enter action numbers separated by commas, type all, or press Enter to skip: ",
|
|
329
|
+
"请输入要执行的操作编号(多个用逗号分隔),输入 all 选择全部,直接回车跳过:",
|
|
330
|
+
), (answer) => {
|
|
331
|
+
const selection = parseMaintenanceSelection(answer, choiceCount);
|
|
332
|
+
if (selection) {
|
|
333
|
+
finish(selection);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
process.stderr.write(cliText(
|
|
337
|
+
`Enter numbers from 1 to ${choiceCount}, all, or press Enter to skip.\n`,
|
|
338
|
+
`请输入 1 到 ${choiceCount} 之间的编号、all,或直接回车跳过。\n`,
|
|
339
|
+
));
|
|
340
|
+
ask();
|
|
341
|
+
});
|
|
342
|
+
};
|
|
343
|
+
listenForPromptCancellation(readline, () => {
|
|
344
|
+
settled = true;
|
|
345
|
+
reject(new Error(cliText("Initialization was cancelled.", "初始化已取消。")));
|
|
346
|
+
}, () => settled);
|
|
347
|
+
ask();
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function promptForDefaultAgent(
|
|
352
|
+
choices: SelectableDefaultAgent[],
|
|
353
|
+
currentAgent?: string,
|
|
354
|
+
): Promise<number> {
|
|
355
|
+
const readline = createInterface({ input: process.stdin, output: process.stderr });
|
|
356
|
+
return new Promise((resolve, reject) => {
|
|
357
|
+
let settled = false;
|
|
358
|
+
const finish = (index: number): void => {
|
|
359
|
+
settled = true;
|
|
360
|
+
readline.close();
|
|
361
|
+
resolve(index);
|
|
362
|
+
};
|
|
363
|
+
const ask = (): void => {
|
|
364
|
+
const currentAvailable = choices.some((choice) => choice.name === currentAgent);
|
|
365
|
+
readline.question(cliText(
|
|
366
|
+
`Enter a number or Agent name${currentAvailable ? ` [${currentAgent}]` : ""}: `,
|
|
367
|
+
`请输入编号或 Agent 标准名${currentAvailable ? ` [${currentAgent}]` : ""}:`,
|
|
368
|
+
), (answer) => {
|
|
369
|
+
const index = resolveDefaultAgentChoice(answer, choices, currentAgent);
|
|
370
|
+
if (index !== undefined) {
|
|
371
|
+
finish(index);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
process.stderr.write(cliText(
|
|
375
|
+
`Enter a number from 1 to ${choices.length} or an Agent standard name.\n`,
|
|
376
|
+
`请输入 1 到 ${choices.length} 之间的编号,或 Agent 标准名。\n`,
|
|
377
|
+
));
|
|
378
|
+
ask();
|
|
379
|
+
});
|
|
380
|
+
};
|
|
381
|
+
listenForPromptCancellation(readline, () => {
|
|
382
|
+
settled = true;
|
|
383
|
+
reject(new Error(cliText("Initialization was cancelled.", "初始化已取消。")));
|
|
384
|
+
}, () => settled);
|
|
385
|
+
ask();
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function listenForPromptCancellation(
|
|
390
|
+
readline: ReturnType<typeof createInterface>,
|
|
391
|
+
cancel: () => void,
|
|
392
|
+
isSettled: () => boolean,
|
|
393
|
+
): void {
|
|
394
|
+
readline.once("SIGINT", () => {
|
|
395
|
+
if (!isSettled()) cancel();
|
|
396
|
+
readline.close();
|
|
397
|
+
});
|
|
398
|
+
readline.once("close", () => {
|
|
399
|
+
if (!isSettled()) cancel();
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function printAgentInspection(
|
|
404
|
+
inspection: SupportedAgentInspection,
|
|
405
|
+
output: NodeJS.WriteStream,
|
|
406
|
+
): void {
|
|
407
|
+
if (inspection.state === "missing") {
|
|
408
|
+
output.write(cliText(
|
|
409
|
+
` ${inspection.name}: not installed\n`,
|
|
410
|
+
` ${inspection.name}:未安装\n`,
|
|
411
|
+
));
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
if (inspection.state === "outdated") {
|
|
415
|
+
output.write(cliText(
|
|
416
|
+
` ${inspection.name}: ${inspection.installedVersion}; update available (${inspection.latestVersion})\n`,
|
|
417
|
+
` ${inspection.name}:${inspection.installedVersion};可升级到 ${inspection.latestVersion}\n`,
|
|
418
|
+
));
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
if (inspection.latestCheckFailed) {
|
|
422
|
+
output.write(cliText(
|
|
423
|
+
` ${inspection.name}: ${inspection.installedVersion}; latest-version check unavailable\n`,
|
|
424
|
+
` ${inspection.name}:${inspection.installedVersion};无法检查最新版本\n`,
|
|
425
|
+
));
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
output.write(cliText(
|
|
429
|
+
` ${inspection.name}: ${inspection.installedVersion} (up to date)\n`,
|
|
430
|
+
` ${inspection.name}:${inspection.installedVersion}(已是最新)\n`,
|
|
431
|
+
));
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function refreshCurrentProcessPath(): void {
|
|
435
|
+
const refreshed = refreshedSystemEnvironment();
|
|
436
|
+
if (!refreshed.refreshed) return;
|
|
437
|
+
const pathEntry = Object.entries(refreshed.environment)
|
|
438
|
+
.find(([name]) => name.toLowerCase() === "path");
|
|
439
|
+
if (pathEntry?.[1]) process.env.PATH = pathEntry[1];
|
|
440
|
+
}
|
|
441
|
+
|
|
159
442
|
async function initializeFeishu(
|
|
160
443
|
paths: InitializationResult,
|
|
161
444
|
options: InitCommandOptions,
|
|
@@ -167,6 +450,9 @@ async function initializeFeishu(
|
|
|
167
450
|
...(existing.appId ? { appId: existing.appId } : {}),
|
|
168
451
|
};
|
|
169
452
|
}
|
|
453
|
+
if (!options.json) {
|
|
454
|
+
process.stdout.write(cliText("\nLark setup\n", "\n飞书设置\n"));
|
|
455
|
+
}
|
|
170
456
|
|
|
171
457
|
const controller = new AbortController();
|
|
172
458
|
const onInterrupt = (): void => controller.abort(new Error(cliText(
|
|
@@ -202,8 +488,8 @@ async function initializeFeishu(
|
|
|
202
488
|
|
|
203
489
|
if (!options.json) {
|
|
204
490
|
process.stdout.write(cliText(
|
|
205
|
-
"
|
|
206
|
-
"
|
|
491
|
+
"Checking app permissions, events, and callbacks...\n",
|
|
492
|
+
"正在检查应用权限、事件和回调...\n",
|
|
207
493
|
));
|
|
208
494
|
}
|
|
209
495
|
const manualPermissionSkip = new AbortController();
|
|
@@ -624,8 +910,8 @@ function printSkillStatus(status: SkillRegistrationStatus): void {
|
|
|
624
910
|
|
|
625
911
|
function printInitializationPaths(result: InitializationResult): void {
|
|
626
912
|
process.stdout.write(cliText(
|
|
627
|
-
"
|
|
628
|
-
"
|
|
913
|
+
"Profile setup\n",
|
|
914
|
+
"Profile 设置\n",
|
|
629
915
|
));
|
|
630
916
|
if (result.reset) {
|
|
631
917
|
process.stdout.write(`${cliText("Reset backup: ", "重置备份:")}${result.reset.backupPath}\n`);
|
|
@@ -642,6 +928,7 @@ function printInitializationResult(result: Omit<InitCommandResult, "server">): v
|
|
|
642
928
|
"\nAgent Bot initialization completed.\n",
|
|
643
929
|
"\nAgent Bot 初始化完成。\n",
|
|
644
930
|
));
|
|
931
|
+
process.stdout.write(`${cliText("Default Agent: ", "默认 Agent:")}${result.defaultAgent.name}\n`);
|
|
645
932
|
if (result.feishu.status === "created") {
|
|
646
933
|
process.stdout.write(cliText(
|
|
647
934
|
`Lark app: created and credentials saved (${result.feishu.appId})\n`,
|
|
@@ -852,6 +1139,7 @@ function feishuFeatureWarnings(missing: FeishuConfigurationChallenge["missing"])
|
|
|
852
1139
|
|
|
853
1140
|
function initializationStatusLabel(status: InitializationStatus): string {
|
|
854
1141
|
if (status === "created") return cliText("created", "已创建");
|
|
1142
|
+
if (status === "updated") return cliText("updated with missing settings", "已补全缺失配置");
|
|
855
1143
|
if (status === "reset") return cliText("reset from template", "已从模板重置");
|
|
856
1144
|
return cliText("already exists; unchanged", "已存在,未修改");
|
|
857
1145
|
}
|
|
@@ -108,7 +108,7 @@ export class AppServerConnection {
|
|
|
108
108
|
try {
|
|
109
109
|
message = JSON.parse(line) as AppServerMessage;
|
|
110
110
|
} catch (error) {
|
|
111
|
-
this.logger.warn({ line, error }, "Ignoring non-JSON
|
|
111
|
+
this.logger.warn({ line, error }, "Ignoring non-JSON App Server stdout line.");
|
|
112
112
|
return;
|
|
113
113
|
}
|
|
114
114
|
this.handleMessage(message);
|
|
@@ -128,7 +128,7 @@ export class AppServerConnection {
|
|
|
128
128
|
for (const listener of this.notificationListeners) listener(message.method, message.params);
|
|
129
129
|
return;
|
|
130
130
|
}
|
|
131
|
-
this.logger.warn({ message }, "Ignoring unknown
|
|
131
|
+
this.logger.warn({ message }, "Ignoring unknown App Server message shape.");
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
private handleResponse(message: AppServerResponse): void {
|
|
@@ -139,7 +139,7 @@ export class AppServerConnection {
|
|
|
139
139
|
responseId: message.id,
|
|
140
140
|
responseKind: "error" in message ? "error" : "result",
|
|
141
141
|
},
|
|
142
|
-
"Received
|
|
142
|
+
"Received App Server response for unknown request.",
|
|
143
143
|
);
|
|
144
144
|
return;
|
|
145
145
|
}
|
|
@@ -27,15 +27,15 @@ export class CodexProcessManager implements AppServerClientProvider {
|
|
|
27
27
|
const client = new AppServerConnection(child, this.logger.child({ component: "codex-app-server" }));
|
|
28
28
|
this.client = client;
|
|
29
29
|
readline.createInterface({ input: child.stderr, crlfDelay: Infinity }).on("line", (line) => {
|
|
30
|
-
this.logger.debug({ line }, "
|
|
30
|
+
this.logger.debug({ line }, "App Server stderr.");
|
|
31
31
|
});
|
|
32
|
-
child.once("error", (error) => this.logger.error({ error }, "
|
|
32
|
+
child.once("error", (error) => this.logger.error({ error }, "App Server process error."));
|
|
33
33
|
child.once("exit", (code, signal) => {
|
|
34
|
-
this.logger.warn({ code, signal }, "
|
|
34
|
+
this.logger.warn({ code, signal }, "App Server process exited.");
|
|
35
35
|
client.close();
|
|
36
36
|
if (this.client === client) this.client = undefined;
|
|
37
37
|
if (this.child === child) this.child = undefined;
|
|
38
|
-
const error = new Error(`
|
|
38
|
+
const error = new Error(`App Server exited (code=${code ?? "null"}, signal=${signal ?? "null"}).`);
|
|
39
39
|
for (const listener of this.disconnectListeners) listener(error);
|
|
40
40
|
});
|
|
41
41
|
await client.request("initialize", {
|
|
@@ -163,7 +163,7 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
163
163
|
if (!isUnsupportedExcludeTurnsError(error)) throw error;
|
|
164
164
|
this.logger.warn(
|
|
165
165
|
{ error },
|
|
166
|
-
"
|
|
166
|
+
"App Server does not support thread/fork excludeTurns; retrying without it.",
|
|
167
167
|
);
|
|
168
168
|
response = await client.request<ThreadResponse>("thread/fork", forkParams, FORK_REQUEST_TIMEOUT_MS);
|
|
169
169
|
}
|
|
@@ -343,7 +343,7 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
343
343
|
);
|
|
344
344
|
this.logger.info(
|
|
345
345
|
{ ...(sessionId ? { sessionId } : {}), threadId: remoteSessionId, turnId },
|
|
346
|
-
"
|
|
346
|
+
"App Server accepted the turn interrupt request.",
|
|
347
347
|
);
|
|
348
348
|
}
|
|
349
349
|
|
|
@@ -554,7 +554,7 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
554
554
|
const session = [...this.sessions.values()].find((candidate) => candidate.remoteSessionId === threadId);
|
|
555
555
|
if (session?.activeTurnId && status && status !== "active") {
|
|
556
556
|
void this.synchronizeSession(session.localSessionId).catch((error: unknown) => {
|
|
557
|
-
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.");
|
|
558
558
|
});
|
|
559
559
|
}
|
|
560
560
|
return;
|
|
@@ -566,7 +566,7 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
566
566
|
if (session.terminalTurnIds.has(mapped.turnId)) {
|
|
567
567
|
this.logger.debug(
|
|
568
568
|
{ sessionId: session.localSessionId, turnId: mapped.turnId, method },
|
|
569
|
-
"Ignoring a replayed terminal
|
|
569
|
+
"Ignoring a replayed terminal App Server turn event.",
|
|
570
570
|
);
|
|
571
571
|
return;
|
|
572
572
|
}
|
|
@@ -586,7 +586,7 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
586
586
|
notificationTurnId: mapped.turnId,
|
|
587
587
|
notificationStartedAt: mapped.startedAt,
|
|
588
588
|
},
|
|
589
|
-
"Ignoring a replayed historical
|
|
589
|
+
"Ignoring a replayed historical App Server turn start.",
|
|
590
590
|
);
|
|
591
591
|
return;
|
|
592
592
|
}
|
|
@@ -597,10 +597,10 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
597
597
|
if (!session.activeTurnId || session.activeTurnId !== mapped.turnId) {
|
|
598
598
|
this.logger.debug(
|
|
599
599
|
{ sessionId: session.localSessionId, activeTurnId: session.activeTurnId, notificationTurnId: mapped.turnId, method },
|
|
600
|
-
"Ignoring out-of-order
|
|
600
|
+
"Ignoring out-of-order App Server notification and scheduling reconciliation.",
|
|
601
601
|
);
|
|
602
602
|
void this.synchronizeSession(session.localSessionId).catch((error: unknown) => {
|
|
603
|
-
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.");
|
|
604
604
|
});
|
|
605
605
|
return;
|
|
606
606
|
}
|
|
@@ -671,9 +671,12 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
671
671
|
if (mapped.status === "cancelled") {
|
|
672
672
|
this.emit({ type: "turn_cancelled", sessionId, turnId: mapped.turnId });
|
|
673
673
|
} else if (mapped.status === "failed") {
|
|
674
|
-
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." });
|
|
675
675
|
} else {
|
|
676
|
-
const finalResponse =
|
|
676
|
+
const finalResponse = appendModelSwitchGuidance(
|
|
677
|
+
appendGeneratedImageMarkdown(session.finalText, session.generatedImagePaths),
|
|
678
|
+
session.model,
|
|
679
|
+
);
|
|
677
680
|
this.emit({
|
|
678
681
|
type: "turn_completed",
|
|
679
682
|
sessionId,
|
|
@@ -703,7 +706,7 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
703
706
|
turnId,
|
|
704
707
|
request: {
|
|
705
708
|
id: requestId,
|
|
706
|
-
title: stringValue(params.command) ?? "
|
|
709
|
+
title: stringValue(params.command) ?? "Agent approval request",
|
|
707
710
|
command: stringValue(params.command),
|
|
708
711
|
reason: stringValue(params.reason),
|
|
709
712
|
options: [
|
|
@@ -781,7 +784,7 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
781
784
|
type: "turn_failed",
|
|
782
785
|
sessionId: session.localSessionId,
|
|
783
786
|
turnId: activeTurnId,
|
|
784
|
-
message: "
|
|
787
|
+
message: "The App Server no longer reports this execution in the thread history.",
|
|
785
788
|
});
|
|
786
789
|
}
|
|
787
790
|
return;
|
|
@@ -828,7 +831,7 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
828
831
|
type: "turn_failed",
|
|
829
832
|
sessionId: session.localSessionId,
|
|
830
833
|
turnId: turn.id,
|
|
831
|
-
message: turn.error?.message ?? "
|
|
834
|
+
message: turn.error?.message ?? "App Server turn failed.",
|
|
832
835
|
});
|
|
833
836
|
return;
|
|
834
837
|
}
|
|
@@ -836,9 +839,12 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
836
839
|
...session.generatedImagePaths,
|
|
837
840
|
...extractGeneratedImagePaths(turn),
|
|
838
841
|
]);
|
|
839
|
-
const finalResponse =
|
|
840
|
-
|
|
841
|
-
|
|
842
|
+
const finalResponse = appendModelSwitchGuidance(
|
|
843
|
+
appendGeneratedImageMarkdown(
|
|
844
|
+
extractFinalResponse(turn) || session.finalText,
|
|
845
|
+
generatedImagePaths,
|
|
846
|
+
),
|
|
847
|
+
session.model,
|
|
842
848
|
);
|
|
843
849
|
session.finalText = finalResponse;
|
|
844
850
|
session.generatedImagePaths = [];
|
|
@@ -888,13 +894,13 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
888
894
|
|
|
889
895
|
private requireSession(sessionId: string): CodexSession {
|
|
890
896
|
const session = this.sessions.get(sessionId);
|
|
891
|
-
if (!session) throw new Error(`Unknown
|
|
897
|
+
if (!session) throw new Error(`Unknown App Server session: ${sessionId}`);
|
|
892
898
|
return session;
|
|
893
899
|
}
|
|
894
900
|
|
|
895
901
|
private requireActiveTurn(sessionId: string, turnId: string): CodexSession {
|
|
896
902
|
const session = this.requireSession(sessionId);
|
|
897
|
-
if (session.activeTurnId !== turnId) throw new Error(`
|
|
903
|
+
if (session.activeTurnId !== turnId) throw new Error(`App Server turn is no longer active: ${turnId}`);
|
|
898
904
|
return session;
|
|
899
905
|
}
|
|
900
906
|
|
|
@@ -917,7 +923,7 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
917
923
|
type: "turn_failed",
|
|
918
924
|
sessionId: session.localSessionId,
|
|
919
925
|
turnId,
|
|
920
|
-
message: `
|
|
926
|
+
message: `App Server disconnected: ${error.message}`,
|
|
921
927
|
});
|
|
922
928
|
}
|
|
923
929
|
for (const [requestId, pending] of this.approvals) {
|
|
@@ -978,6 +984,20 @@ function permissionParams(mode: PermissionMode): { approvalPolicy: "never" | "on
|
|
|
978
984
|
: { approvalPolicy: "on-request", sandbox: "workspace-write" };
|
|
979
985
|
}
|
|
980
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
|
+
|
|
981
1001
|
function threadLifecycleParams(cwd: string): {
|
|
982
1002
|
developerInstructions: string;
|
|
983
1003
|
} {
|
package/src/config/loadConfig.ts
CHANGED
|
@@ -43,30 +43,6 @@ agents:
|
|
|
43
43
|
- "stdio://"
|
|
44
44
|
env: {}
|
|
45
45
|
|
|
46
|
-
coco:
|
|
47
|
-
title: "Coco"
|
|
48
|
-
command: "coco"
|
|
49
|
-
args:
|
|
50
|
-
- "acp"
|
|
51
|
-
- "serve"
|
|
52
|
-
env: {}
|
|
53
|
-
|
|
54
|
-
coco-yolo:
|
|
55
|
-
title: "Coco YOLO"
|
|
56
|
-
command: "coco"
|
|
57
|
-
args:
|
|
58
|
-
- "acp"
|
|
59
|
-
- "serve"
|
|
60
|
-
- "--yolo"
|
|
61
|
-
env: {}
|
|
62
|
-
|
|
63
|
-
example:
|
|
64
|
-
title: "Example ACP Agent"
|
|
65
|
-
command: "node"
|
|
66
|
-
args:
|
|
67
|
-
- "./examples/example-acp-agent.js"
|
|
68
|
-
env: {}
|
|
69
|
-
|
|
70
46
|
defaults:
|
|
71
47
|
agent: "codex"
|
|
72
48
|
cwd: "."
|
|
@@ -12,8 +12,8 @@ export class ConsoleConnector {
|
|
|
12
12
|
|
|
13
13
|
start(): void {
|
|
14
14
|
if (this.reader) return;
|
|
15
|
-
this.logger.info("Starting local
|
|
16
|
-
console.log("
|
|
15
|
+
this.logger.info("Starting the local Agent Bot console.");
|
|
16
|
+
console.log("Agent Bot console ready. Type /help or enter a prompt.");
|
|
17
17
|
const reader = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: "agentbot> " });
|
|
18
18
|
this.reader = reader;
|
|
19
19
|
reader.prompt();
|