@keyou007/agent-bot 0.1.13-alpha.3 → 0.1.13-alpha.4
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 +5 -0
- package/dist/cli/DefaultAgentSelection.js +16 -2
- package/dist/cli/DefaultAgentSelection.js.map +1 -1
- package/dist/cli/InitializationWelcome.js +20 -42
- package/dist/cli/InitializationWelcome.js.map +1 -1
- package/dist/cli/Initializer.js +44 -0
- package/dist/cli/Initializer.js.map +1 -1
- package/dist/cli/help.js +2 -2
- package/dist/cli.js +80 -30
- package/dist/cli.js.map +1 -1
- package/dist/feishu/CardRenderer.js +15 -30
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/docs/technical-reference.md +2 -2
- package/docs/technical-reference.zh.md +2 -2
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/skills/agent-bot/SKILL.md +1 -1
- package/src/cli/DefaultAgentSelection.ts +19 -2
- package/src/cli/InitializationWelcome.ts +19 -44
- package/src/cli/Initializer.ts +67 -0
- package/src/cli/help.ts +2 -2
- package/src/cli.ts +112 -31
- package/src/feishu/CardRenderer.ts +15 -31
|
@@ -60,7 +60,7 @@ Relative `storage.sqlitePath` and `logging.path` values resolve against the dire
|
|
|
60
60
|
|
|
61
61
|
Early in initialization, `agentbot init` checks the supported Codex and TraeX CLIs in parallel and reports each installed version. Codex is compared with the latest stable `@openai/codex` package, while TraeX is compared with its Alpha channel. Missing or outdated Agents are gathered into one numbered list containing their exact install or upgrade commands. An interactive user can enter comma- or space-separated action numbers, `all`, or an empty answer to skip maintenance. Selected commands run sequentially with inherited stdio; skipped and failed actions are recorded and initialization continues. In a non-interactive terminal, commands are printed for manual use. With `--json`, progress and prompts use stderr and the final `agents` array records detection and assistance results. Codex upgrades first use `codex update` and fall back to the current npm package command when an older updater fails.
|
|
62
62
|
|
|
63
|
-
After version and maintenance checks,
|
|
63
|
+
After version and maintenance checks, a first fresh interactive `init` and every explicit `--reset` build the Profile's Agent configuration from the supported Agents that were actually detected with an installed version. Missing Codex or TraeX entries are not selectable. When multiple Agents are available, the user may select one or more by number or standard name; `all` or an empty answer selects all. Unselected Agent definitions are removed from the new configuration through a comment-preserving atomic YAML update. One selected Agent becomes the default automatically. When multiple Agents are selected, a second prompt chooses `defaults.agent` by number or standard name. Later upgrades and refreshes preserve both the configured Agent list and default without showing either selector, including custom Agents already present in the Profile. A non-interactive first initialization or reset configures every detected installed Agent and keeps the template default when selected, otherwise the first detected Agent. An existing Profile without a valid configured default fails with an instruction to set `defaults.agent` in `config.yaml`. JSON output reports the final names in `configuredAgents`, adds `configured` to each supported-Agent inspection, and returns `defaultAgent.name` plus `defaultAgent.status` (`selected` or `existing`).
|
|
64
64
|
|
|
65
65
|
`agentbot init` copies the packaged `config.example.yaml` and `.env.example` when their target files do not exist, creates the data and log directories, and restricts `.env` permissions where the platform supports POSIX modes.
|
|
66
66
|
|
|
@@ -103,7 +103,7 @@ The generated authorization links do not contain the app secret.
|
|
|
103
103
|
|
|
104
104
|
After Feishu initialization succeeds, the CLI releases the initialization lock and starts the detached supervisor through the same readiness path as `agentbot server start`. It waits up to 45 seconds for the worker to connect to Feishu and become ready. If the selected profile is already running, no second supervisor is created; instead, initialization requests a safe restart so the current installed code and refreshed configuration are loaded after active work and final delivery finish. The JSON result reports this as `server.status: "restart-scheduled"`. `--skip-feishu` skips automatic server startup, and `--json` includes the resulting `server.status` without adding non-JSON output.
|
|
105
105
|
|
|
106
|
-
After the Server starts or a safe restart is successfully scheduled, `init` sends a Card 2.0 welcome card directly to `feishu.userOpenId`. The card uploads the packaged `assets/agent-bot-logo.png`,
|
|
106
|
+
After the Server starts or a safe restart is successfully scheduled, `init` sends a Card 2.0 welcome card directly to `feishu.userOpenId`. The card uploads the packaged `assets/agent-bot-logo.png`, is always rendered in Chinese regardless of the CLI locale, and reports the installed version, default Agent, and configured Agents. When a safe restart is pending, the card says that the refreshed version takes effect after that restart instead of claiming it is already active. A Profile receipt at `<profile>/data/initialization.json` records the last successfully initialized package version so the card can distinguish first setup, version upgrade, and same-version refresh. Welcome delivery is reported in the JSON `welcome` object. A missing user Open ID or delivery failure produces an explicit warning without undoing completed configuration or stopping an already-ready Server; `--skip-feishu` records a skipped welcome.
|
|
107
107
|
|
|
108
108
|
## Feishu App Requirements
|
|
109
109
|
|
|
@@ -60,7 +60,7 @@ CLI 通过 Node.js 国际化能力读取系统 Locale。以 `zh` 开头的 Local
|
|
|
60
60
|
|
|
61
61
|
初始化开始阶段,`agentbot init` 会并行检查目前支持的 Codex 和 TraeX CLI,并显示各自已安装的版本。Codex 与最新稳定版 `@openai/codex` 比较,TraeX 与 Alpha 通道比较。未安装或版本较旧的 Agent 会汇总到一个编号列表,并显示准确的安装或升级命令。交互式用户可以输入用逗号或空格分隔的操作编号、输入 `all`,或直接回车跳过维护;所选命令按顺序执行并继承终端输入输出,跳过或失败的操作都会记录结果,初始化继续。非交互式终端只显示供手动执行的命令。使用 `--json` 时,进度与询问写入 stderr,最终 `agents` 数组记录检测和辅助执行结果。Codex 升级会先执行 `codex update`,旧版 updater 失败时回退到当前 npm 包安装命令。
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
版本与维护检查完成后,第一次全新交互式 `init` 和每次显式 `--reset` 都会根据实际检测到安装版本的受支持 Agent 生成 Profile 的 Agent 配置;仍未安装的 Codex 或 TraeX 不会进入候选列表。检测到多个 Agent 时,用户可以通过编号或标准名选择一个或多个,输入 `all` 或直接回车选择全部。未选择的 Agent 定义会通过保留注释的 YAML 原子更新从新配置中移除。只选择一个 Agent 时自动将其设为默认值;选择多个时再显示一次默认 Agent 选择,可输入编号或标准名写入 `defaults.agent`。后续升级和同版本刷新直接保留已配置 Agent 列表与默认值,不再显示这两类选择器,已有自定义 Agent 也会保留。第一次非交互式初始化或 reset 会配置所有检测到且已安装的 Agent;模板默认值在所选列表中时继续使用,否则选择第一个检测结果。已有 Profile 没有有效默认值时会失败,并提示在 `config.yaml` 中设置 `defaults.agent`。JSON 输出通过 `configuredAgents` 报告最终列表,在每个受支持 Agent 的检测结果中增加 `configured`,并通过 `defaultAgent.name` 和 `defaultAgent.status`(`selected` 或 `existing`)报告默认值。
|
|
64
64
|
|
|
65
65
|
目标文件不存在时,`agentbot init` 会复制随包提供的 `config.example.yaml` 和 `.env.example`,创建数据和日志目录,并在平台支持时把 `.env` 权限限制为 POSIX `0600`。
|
|
66
66
|
|
|
@@ -103,7 +103,7 @@ https://open.feishu.cn/app/<appId>/auth?q=im%3Amessage.group_msg&op_from=openapi
|
|
|
103
103
|
|
|
104
104
|
飞书初始化成功后,CLI 会先释放初始化锁,再通过与 `agentbot server start` 相同的就绪检查流程启动后台 Supervisor,并等待最多 45 秒,直到 Worker 连接飞书并进入就绪状态。如果当前 Profile 的服务已经运行,不会创建第二个 Supervisor,而是安排安全重启,在活动任务和最终结果投递完成后加载当前安装的代码及更新后的配置;JSON 结果将其表示为 `server.status: "restart-scheduled"`。`--skip-feishu` 会跳过自动启动;`--json` 会把结果写入 `server.status`,不会混入非 JSON 文本。
|
|
105
105
|
|
|
106
|
-
Server 成功启动或安全重启成功安排后,`init` 会直接向 `feishu.userOpenId` 发送 Card 2.0
|
|
106
|
+
Server 成功启动或安全重启成功安排后,`init` 会直接向 `feishu.userOpenId` 发送 Card 2.0 私聊欢迎卡。无论 CLI 使用什么系统语言,欢迎卡都固定显示中文;卡片会上传随包提供的 `assets/agent-bot-logo.png`,并显示当前安装版本、默认 Agent 和已配置 Agent。如果安全重启仍在等待,卡片会明确说明刷新后的版本将在重启完成后生效,而不会声称新版已经接管。`<profile>/data/initialization.json` 会记录上次成功初始化的包版本,用于区分首次初始化、版本升级和同版本配置刷新。JSON 输出通过 `welcome` 对象报告发送结果。缺少用户 Open ID 或卡片发送失败时会明确警告,但不会撤销已完成的配置,也不会停止已经就绪的 Server;`--skip-feishu` 会记录欢迎卡已跳过。
|
|
107
107
|
|
|
108
108
|
## 飞书应用要求
|
|
109
109
|
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keyou007/agent-bot",
|
|
3
|
-
"version": "0.1.13-alpha.
|
|
3
|
+
"version": "0.1.13-alpha.4",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@keyou007/agent-bot",
|
|
9
|
-
"version": "0.1.13-alpha.
|
|
9
|
+
"version": "0.1.13-alpha.4",
|
|
10
10
|
"license": "GPL-3.0-only",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@agentclientprotocol/sdk": "1.2.1",
|
package/package.json
CHANGED
|
@@ -61,7 +61,7 @@ agentbot init
|
|
|
61
61
|
|
|
62
62
|
Initialization detects the supported Codex and TraeX CLIs and reports each installed version. Missing or outdated Agents are presented in one numbered maintenance list with their exact commands. In an interactive terminal, the user may enter comma-separated action numbers, `all`, or press Enter to skip maintenance. A failed maintenance command or the absence of an interactive terminal does not block the remaining initialization. Do not select maintenance actions on the user's behalf without permission. Agent Bot uses the latest stable Codex release and the TraeX Alpha channel for these checks.
|
|
63
63
|
|
|
64
|
-
After the maintenance choice, interactive initialization
|
|
64
|
+
After the maintenance choice, a first fresh interactive initialization and every explicit `--reset` ask which detected installed Agents to configure. Missing supported Agents are omitted. The user may select one or more by number or standard name; `all` or an empty answer selects all. Relay the choices and let the user decide. Agent Bot removes unselected Agent definitions from the newly generated Profile. One selected Agent becomes the default automatically; multiple selected Agents produce a second number-or-name prompt for `defaults.agent`. Later upgrades and refreshes preserve the existing Agent list and default without either prompt, including custom configured Agents. A non-interactive first initialization or reset configures all detected installed Agents and chooses the template default when available, otherwise the first detected Agent. With `--json`, read the final list from `configuredAgents`, each supported inspection's `configured` flag, and `defaultAgent.name` plus `defaultAgent.status`.
|
|
65
65
|
|
|
66
66
|
Initialization creates the Agent Bot home, `config.yaml`, `.env`, `data/`, and `logs/` from the bundled examples. If Feishu credentials are absent, it starts Feishu one-click app registration, prints a QR code followed by its authorization URL, waits for the user to approve the request, and stores the returned app ID and secret in `.env`. It then audits the app's currently published tenant permissions, event subscriptions, and callbacks. Core and optional missing items produce separate configuration challenges. Core configuration is polled until ready, except that the manually configured all-group-message permission may be explicitly skipped with `Y`. For optional configuration, the CLI prints the QR code followed by the authorization URL and immediately waits up to five minutes for activation. The only terminal choice is also `Y`, which skips optional authorization and continues initialization.
|
|
67
67
|
|
|
@@ -12,14 +12,31 @@ export function selectableDefaultAgents(
|
|
|
12
12
|
const supported = new Map(inspections.map((inspection) => [inspection.id, inspection]));
|
|
13
13
|
return configured.flatMap((agent) => {
|
|
14
14
|
const inspection = supported.get(agent.name as SupportedAgentInspection["id"]);
|
|
15
|
-
if (
|
|
15
|
+
if (!inspection?.installedVersion) return [];
|
|
16
16
|
return [{
|
|
17
17
|
...agent,
|
|
18
|
-
|
|
18
|
+
installedVersion: inspection.installedVersion,
|
|
19
19
|
}];
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
export function resolveAgentConfigurationChoices(
|
|
24
|
+
input: string,
|
|
25
|
+
choices: SelectableDefaultAgent[],
|
|
26
|
+
): number[] | undefined {
|
|
27
|
+
const value = input.trim();
|
|
28
|
+
if (!value || /^(?:a|all)$/iu.test(value)) {
|
|
29
|
+
return choices.map((_choice, index) => index);
|
|
30
|
+
}
|
|
31
|
+
const selected = new Set<number>();
|
|
32
|
+
for (const token of value.split(/[,,\s]+/u).filter(Boolean)) {
|
|
33
|
+
const index = resolveDefaultAgentChoice(token, choices);
|
|
34
|
+
if (index === undefined) return undefined;
|
|
35
|
+
selected.add(index);
|
|
36
|
+
}
|
|
37
|
+
return selected.size > 0 ? [...selected].sort((left, right) => left - right) : undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
23
40
|
export function parseMaintenanceSelection(input: string, choiceCount: number): number[] | undefined {
|
|
24
41
|
const value = input.trim();
|
|
25
42
|
if (!value) return [];
|
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
type InitializationWelcomeKind,
|
|
11
11
|
} from "../feishu/CardRenderer.js";
|
|
12
12
|
import { FeishuMessageClient } from "../feishu/FeishuMessageClient.js";
|
|
13
|
-
import type { CliLanguage } from "./i18n.js";
|
|
14
13
|
|
|
15
14
|
export interface InitializationReceipt {
|
|
16
15
|
version: string;
|
|
@@ -24,7 +23,6 @@ export type InitializationWelcomeResult =
|
|
|
24
23
|
|
|
25
24
|
export interface SendInitializationWelcomeInput {
|
|
26
25
|
configPath?: string;
|
|
27
|
-
language: CliLanguage;
|
|
28
26
|
version: string;
|
|
29
27
|
previousVersion?: string;
|
|
30
28
|
kind: InitializationWelcomeKind;
|
|
@@ -83,7 +81,6 @@ export async function sendInitializationWelcome(
|
|
|
83
81
|
const renderer = new CardRenderer();
|
|
84
82
|
const card = renderer.renderInitializationWelcome({
|
|
85
83
|
kind: input.kind,
|
|
86
|
-
language: input.language,
|
|
87
84
|
version: input.version,
|
|
88
85
|
previousVersion: input.previousVersion,
|
|
89
86
|
activationPending: input.activationPending,
|
|
@@ -91,7 +88,7 @@ export async function sendInitializationWelcome(
|
|
|
91
88
|
defaultAgentTitle: defaultAgent.title,
|
|
92
89
|
availableAgents: Object.values(config.agents).map((agent) => agent.title),
|
|
93
90
|
logoPath: dependencies.logoPath,
|
|
94
|
-
features: welcomeFeatures(input.kind
|
|
91
|
+
features: welcomeFeatures(input.kind),
|
|
95
92
|
});
|
|
96
93
|
await dependencies.sendCard(config, `open_id:${userOpenId}`, card);
|
|
97
94
|
return { status: "sent", kind: input.kind };
|
|
@@ -103,51 +100,29 @@ function initializationReceiptPath(homePath: string): string {
|
|
|
103
100
|
|
|
104
101
|
function welcomeFeatures(
|
|
105
102
|
kind: InitializationWelcomeKind,
|
|
106
|
-
language: CliLanguage,
|
|
107
103
|
): InitializationWelcomeFeature[] {
|
|
108
104
|
if (kind === "upgrade") {
|
|
109
|
-
return
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
]
|
|
116
|
-
: [
|
|
117
|
-
{ icon: "⚙️", title: "Clearer Agent status", description: "Server status now shows each Agent's PID and version." },
|
|
118
|
-
{ icon: "🧭", title: "Find the current task", description: "The CLI can identify its invoking Codex or TraeX task." },
|
|
119
|
-
{ icon: "🗂️", title: "Better card actions", description: "Clickable Help commands and clearer Sessions pagination." },
|
|
120
|
-
{ icon: "🛡️", title: "Stronger crash recovery", description: "Turn graph links are repaired after service recovery." },
|
|
121
|
-
];
|
|
105
|
+
return [
|
|
106
|
+
{ icon: "⚙️", title: "Agent 状态更透明", description: "Server 状态会显示每个 Agent 的 PID 与版本。" },
|
|
107
|
+
{ icon: "🧭", title: "快速定位当前任务", description: "CLI 可识别正在调用它的 Codex 或 TraeX 任务。" },
|
|
108
|
+
{ icon: "🗂️", title: "卡片操作更顺手", description: "Help 命令可点击,Sessions 翻页与操作更清晰。" },
|
|
109
|
+
{ icon: "🛡️", title: "Crash 恢复更完整", description: "服务重启后会修复历史 Turn 的父子连接。" },
|
|
110
|
+
];
|
|
122
111
|
}
|
|
123
112
|
if (kind === "refresh") {
|
|
124
|
-
return
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
]
|
|
131
|
-
: [
|
|
132
|
-
{ icon: "💬", title: "Start anywhere", description: "Continue work from a private chat or group conversation." },
|
|
133
|
-
{ icon: "🧠", title: "Unified settings", description: "Switch Agent, model, thinking, and permissions in one card." },
|
|
134
|
-
{ icon: "🌿", title: "Branch tasks", description: "Use New Group or Fork Group for parallel work." },
|
|
135
|
-
{ icon: "🔄", title: "Safe recovery", description: "Tasks, progress, and service state persist across restarts." },
|
|
136
|
-
];
|
|
113
|
+
return [
|
|
114
|
+
{ icon: "💬", title: "随时开始对话", description: "私聊或群聊发送消息,即可继续当前任务。" },
|
|
115
|
+
{ icon: "🧠", title: "统一运行设置", description: "在同一张卡片中切换 Agent、模型、思考和权限。" },
|
|
116
|
+
{ icon: "🌿", title: "任务分支", description: "使用 New Group 或 Fork Group 并行处理工作。" },
|
|
117
|
+
{ icon: "🔄", title: "安全恢复", description: "任务、进度与服务状态会持久保存并恢复。" },
|
|
118
|
+
];
|
|
137
119
|
}
|
|
138
|
-
return
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
]
|
|
145
|
-
: [
|
|
146
|
-
{ icon: "💬", title: "Work from Lark", description: "Private chats, groups, and topics keep separate task context." },
|
|
147
|
-
{ icon: "🤖", title: "Connect local Agents", description: "Use Codex, TraeX, and compatible coding Agents." },
|
|
148
|
-
{ icon: "🌿", title: "Create and branch", description: "Move fresh tasks or conversation branches into new groups." },
|
|
149
|
-
{ icon: "📍", title: "See every step", description: "Follow tools, file changes, and final results in progress cards." },
|
|
150
|
-
];
|
|
120
|
+
return [
|
|
121
|
+
{ icon: "💬", title: "飞书直接对话", description: "私聊、群聊和话题分别维护自己的任务上下文。" },
|
|
122
|
+
{ icon: "🤖", title: "连接本地 Agent", description: "目前支持 Codex、TraeX 和兼容的编程 Agent。" },
|
|
123
|
+
{ icon: "🌿", title: "新建与分支", description: "把新任务或对话分支放进独立群聊并行推进。" },
|
|
124
|
+
{ icon: "📍", title: "进度始终可见", description: "通过思考卡片查看工具调用、文件变更和最终结果。" },
|
|
125
|
+
];
|
|
151
126
|
}
|
|
152
127
|
|
|
153
128
|
const defaultDependencies: InitializationWelcomeDependencies = {
|
package/src/cli/Initializer.ts
CHANGED
|
@@ -39,6 +39,10 @@ export interface InitializationOptions {
|
|
|
39
39
|
reset?: boolean;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
export function shouldConfigureAgentsDuringInitialization(status: InitializationStatus): boolean {
|
|
43
|
+
return status === "created" || status === "reset";
|
|
44
|
+
}
|
|
45
|
+
|
|
42
46
|
export interface ConfiguredAgentOption {
|
|
43
47
|
name: string;
|
|
44
48
|
title: string;
|
|
@@ -171,6 +175,69 @@ export function writeDefaultAgent(configPath: string, agentName: string): boolea
|
|
|
171
175
|
return true;
|
|
172
176
|
}
|
|
173
177
|
|
|
178
|
+
export function writeConfiguredAgentSelection(
|
|
179
|
+
configPath: string,
|
|
180
|
+
agentNames: string[],
|
|
181
|
+
defaultAgent: string,
|
|
182
|
+
): boolean {
|
|
183
|
+
const selected = new Set(agentNames.map((name) => name.trim()).filter(Boolean));
|
|
184
|
+
if (selected.size === 0) {
|
|
185
|
+
throw new Error(cliText(
|
|
186
|
+
"At least one Agent must be configured.",
|
|
187
|
+
"必须至少配置一个 Agent。",
|
|
188
|
+
));
|
|
189
|
+
}
|
|
190
|
+
if (!selected.has(defaultAgent)) {
|
|
191
|
+
throw new Error(cliText(
|
|
192
|
+
`Default Agent must be included in the configured Agents: ${defaultAgent}`,
|
|
193
|
+
`默认 Agent 必须包含在已配置 Agent 中:${defaultAgent}`,
|
|
194
|
+
));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const original = fs.readFileSync(configPath, "utf8");
|
|
198
|
+
const document = parseConfigurationDocument(original, configPath);
|
|
199
|
+
if (!isMap(document.contents)) {
|
|
200
|
+
throw new Error(cliText(
|
|
201
|
+
`Configuration file must contain a YAML mapping: ${configPath}`,
|
|
202
|
+
`配置文件必须包含 YAML 映射:${configPath}`,
|
|
203
|
+
));
|
|
204
|
+
}
|
|
205
|
+
const configuredAgents = document.contents.get("agents", true);
|
|
206
|
+
if (!isMap(configuredAgents)) {
|
|
207
|
+
throw new Error(cliText(
|
|
208
|
+
`Configuration file must contain an agents mapping: ${configPath}`,
|
|
209
|
+
`配置文件必须包含 agents 映射:${configPath}`,
|
|
210
|
+
));
|
|
211
|
+
}
|
|
212
|
+
for (const agentName of selected) {
|
|
213
|
+
if (!configuredAgents.has(agentName)) {
|
|
214
|
+
throw new Error(cliText(
|
|
215
|
+
`Cannot configure an Agent that is not defined: ${agentName}`,
|
|
216
|
+
`不能配置尚未定义的 Agent:${agentName}`,
|
|
217
|
+
));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
for (const pair of [...configuredAgents.items]) {
|
|
221
|
+
const name = yamlMappingKey(pair.key);
|
|
222
|
+
if (name && !selected.has(name)) configuredAgents.delete(name);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const defaults = document.contents.get("defaults", true);
|
|
226
|
+
if (defaults !== undefined && !isMap(defaults)) {
|
|
227
|
+
throw new Error(cliText(
|
|
228
|
+
`Configuration defaults must contain a YAML mapping: ${configPath}`,
|
|
229
|
+
`配置文件的 defaults 必须是 YAML 映射:${configPath}`,
|
|
230
|
+
));
|
|
231
|
+
}
|
|
232
|
+
if (isMap(defaults)) defaults.set("agent", defaultAgent);
|
|
233
|
+
else document.setIn(["defaults"], { agent: defaultAgent });
|
|
234
|
+
|
|
235
|
+
const updated = document.toString();
|
|
236
|
+
if (updated === original) return false;
|
|
237
|
+
writeFileAtomically(configPath, updated);
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
|
|
174
241
|
export function readFeishuCredentials(envPath: string, env: NodeJS.ProcessEnv = process.env): FeishuCredentialState {
|
|
175
242
|
const fileValues = fs.existsSync(envPath) ? parseDotEnv(fs.readFileSync(envPath, "utf8")) : {};
|
|
176
243
|
const appId = firstNonBlank(env.FEISHU_APP_ID, fileValues.FEISHU_APP_ID);
|
package/src/cli/help.ts
CHANGED
|
@@ -9,7 +9,7 @@ export function renderCliHelp(version: string, language: CliLanguage = cliLangua
|
|
|
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
|
|
@@ -63,7 +63,7 @@ Usage:
|
|
|
63
63
|
agentbot [options] <command>
|
|
64
64
|
|
|
65
65
|
Common commands:
|
|
66
|
-
init Check and
|
|
66
|
+
init Check and configure Agents; initialize the Lark app and service
|
|
67
67
|
init --reset Reset and reconfigure an explicit profile
|
|
68
68
|
console Open the local console
|
|
69
69
|
server start Start Agent Bot in the background
|
package/src/cli.ts
CHANGED
|
@@ -18,7 +18,8 @@ import {
|
|
|
18
18
|
readConfiguredAgentSelection,
|
|
19
19
|
readFeishuCredentials,
|
|
20
20
|
shouldCreateFeishuApp,
|
|
21
|
-
|
|
21
|
+
shouldConfigureAgentsDuringInitialization,
|
|
22
|
+
writeConfiguredAgentSelection,
|
|
22
23
|
writeFeishuCredentials,
|
|
23
24
|
type InitializationResult,
|
|
24
25
|
type InitializationStatus,
|
|
@@ -49,6 +50,7 @@ import {
|
|
|
49
50
|
} from "./cli/AgentPrerequisites.js";
|
|
50
51
|
import {
|
|
51
52
|
parseMaintenanceSelection,
|
|
53
|
+
resolveAgentConfigurationChoices,
|
|
52
54
|
resolveDefaultAgentChoice,
|
|
53
55
|
selectableDefaultAgents,
|
|
54
56
|
type SelectableDefaultAgent,
|
|
@@ -143,6 +145,7 @@ type FeishuInitializationStatus = "created" | "existing" | "skipped";
|
|
|
143
145
|
|
|
144
146
|
interface InitCommandResult extends InitializationResult {
|
|
145
147
|
agents: InitializedAgent[];
|
|
148
|
+
configuredAgents: string[];
|
|
146
149
|
defaultAgent: {
|
|
147
150
|
name: string;
|
|
148
151
|
status: "selected" | "existing";
|
|
@@ -158,6 +161,7 @@ interface InitCommandResult extends InitializationResult {
|
|
|
158
161
|
}
|
|
159
162
|
|
|
160
163
|
interface InitializedAgent extends SupportedAgentInspection {
|
|
164
|
+
configured?: boolean;
|
|
161
165
|
assistance?: {
|
|
162
166
|
status: "completed" | "skipped" | "unavailable" | "failed";
|
|
163
167
|
error?: string;
|
|
@@ -181,15 +185,26 @@ async function initCommand(
|
|
|
181
185
|
if (options.reset) await assertResetProfileServerStopped(configPath);
|
|
182
186
|
paths = initializeAgentBot({ configPath, reset: options.reset });
|
|
183
187
|
if (!options.json) printInitializationPaths(paths);
|
|
184
|
-
const
|
|
185
|
-
const defaultAgent = await
|
|
188
|
+
const inspectedAgents = await initializeSupportedAgents(options.json);
|
|
189
|
+
const defaultAgent = await configureAgentsAndDefault(
|
|
190
|
+
paths.config.path,
|
|
191
|
+
inspectedAgents,
|
|
192
|
+
options.json,
|
|
193
|
+
shouldConfigureAgentsDuringInitialization(paths.config.status),
|
|
194
|
+
);
|
|
195
|
+
const configuredAgents = readConfiguredAgentSelection(paths.config.path).agents.map((agent) => agent.name);
|
|
196
|
+
const configuredAgentNames = new Set(configuredAgents);
|
|
197
|
+
const agents = inspectedAgents.map((agent) => ({
|
|
198
|
+
...agent,
|
|
199
|
+
configured: configuredAgentNames.has(agent.id),
|
|
200
|
+
}));
|
|
186
201
|
initializationLock ??= acquireInitializationLock(paths.home.path);
|
|
187
202
|
cleanupFeishuCredentialTemporaryFiles(paths.env.path);
|
|
188
203
|
const feishu = await initializeFeishu(
|
|
189
204
|
paths,
|
|
190
205
|
options,
|
|
191
206
|
);
|
|
192
|
-
initialized = { ...paths, agents, defaultAgent, feishu };
|
|
207
|
+
initialized = { ...paths, agents, configuredAgents, defaultAgent, feishu };
|
|
193
208
|
} finally {
|
|
194
209
|
initializationLock?.release();
|
|
195
210
|
}
|
|
@@ -211,7 +226,6 @@ async function initCommand(
|
|
|
211
226
|
try {
|
|
212
227
|
welcome = await sendInitializationWelcome({
|
|
213
228
|
configPath,
|
|
214
|
-
language: cliLanguage,
|
|
215
229
|
version,
|
|
216
230
|
previousVersion: previousInitialization?.version,
|
|
217
231
|
kind: welcomeKind,
|
|
@@ -324,49 +338,81 @@ async function initializeSupportedAgents(json: boolean): Promise<InitializedAgen
|
|
|
324
338
|
return initialized;
|
|
325
339
|
}
|
|
326
340
|
|
|
327
|
-
async function
|
|
341
|
+
async function configureAgentsAndDefault(
|
|
328
342
|
configPath: string,
|
|
329
343
|
inspections: InitializedAgent[],
|
|
330
344
|
json: boolean,
|
|
345
|
+
configureAgents: boolean,
|
|
331
346
|
): Promise<InitCommandResult["defaultAgent"]> {
|
|
332
347
|
const output = json ? process.stderr : process.stdout;
|
|
333
348
|
const configured = readConfiguredAgentSelection(configPath);
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
349
|
+
const configuredDefaultIsValid = Boolean(
|
|
350
|
+
configured.defaultAgent
|
|
351
|
+
&& configured.agents.some((agent) => agent.name === configured.defaultAgent),
|
|
352
|
+
);
|
|
353
|
+
if (!configureAgents) {
|
|
354
|
+
if (!configuredDefaultIsValid) {
|
|
337
355
|
throw new Error(cliText(
|
|
338
|
-
"
|
|
339
|
-
"
|
|
356
|
+
"The existing Profile has no valid default Agent. Set defaults.agent in config.yaml, then run agentbot init again.",
|
|
357
|
+
"现有 Profile 没有有效的默认 Agent。请在 config.yaml 中设置 defaults.agent,然后重新运行 agentbot init。",
|
|
340
358
|
));
|
|
341
359
|
}
|
|
342
360
|
output.write(cliText(
|
|
343
|
-
`Default Agent: ${configured.defaultAgent} (
|
|
344
|
-
`默认 Agent:${configured.defaultAgent}
|
|
361
|
+
`Default Agent: ${configured.defaultAgent} (unchanged)\n`,
|
|
362
|
+
`默认 Agent:${configured.defaultAgent}(保持不变)\n`,
|
|
345
363
|
));
|
|
346
|
-
return { name: configured.defaultAgent
|
|
364
|
+
return { name: configured.defaultAgent!, status: "existing" };
|
|
347
365
|
}
|
|
366
|
+
|
|
367
|
+
const choices = selectableDefaultAgents(configured.agents, inspections);
|
|
348
368
|
if (choices.length === 0) {
|
|
349
369
|
throw new Error(cliText(
|
|
350
|
-
"No
|
|
351
|
-
"
|
|
370
|
+
"No installed supported Agent was detected. Install Codex or TraeX, then run agentbot init again.",
|
|
371
|
+
"没有检测到已安装且受支持的 Agent。请安装 Codex 或 TraeX,然后重新运行 agentbot init。",
|
|
352
372
|
));
|
|
353
373
|
}
|
|
354
374
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
const
|
|
366
|
-
output.write(
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
375
|
+
let selectedAgents = choices;
|
|
376
|
+
if (process.stdin.isTTY && choices.length > 1) {
|
|
377
|
+
output.write(cliText("\nSelect Agents to configure:\n", "\n请选择要配置的 Agent:\n"));
|
|
378
|
+
for (const [index, choice] of choices.entries()) {
|
|
379
|
+
output.write(` ${index + 1}. ${choice.name} - ${choice.title} (${choice.installedVersion})\n`);
|
|
380
|
+
}
|
|
381
|
+
const selectedIndexes = await promptForConfiguredAgents(choices);
|
|
382
|
+
selectedAgents = selectedIndexes.flatMap((index) => choices[index] ? [choices[index]!] : []);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const selectedNames = selectedAgents.map((agent) => agent.name);
|
|
386
|
+
output.write(cliText(
|
|
387
|
+
`Configured Agents: ${selectedNames.join(", ")}\n`,
|
|
388
|
+
`已配置 Agent:${selectedNames.join("、")}\n`,
|
|
389
|
+
));
|
|
390
|
+
|
|
391
|
+
let selectedDefault = selectedAgents[0]!;
|
|
392
|
+
if (selectedAgents.length > 1) {
|
|
393
|
+
if (process.stdin.isTTY) {
|
|
394
|
+
output.write(cliText("\nSelect the default Agent:\n", "\n请选择默认 Agent:\n"));
|
|
395
|
+
for (const [index, choice] of selectedAgents.entries()) {
|
|
396
|
+
const current = choice.name === configured.defaultAgent
|
|
397
|
+
? cliText(" [current]", " [当前]")
|
|
398
|
+
: "";
|
|
399
|
+
output.write(` ${index + 1}. ${choice.name} - ${choice.title} (${choice.installedVersion})${current}\n`);
|
|
400
|
+
}
|
|
401
|
+
selectedDefault = selectedAgents[
|
|
402
|
+
await promptForDefaultAgent(selectedAgents, configured.defaultAgent)
|
|
403
|
+
]!;
|
|
404
|
+
} else {
|
|
405
|
+
selectedDefault = selectedAgents.find((agent) => agent.name === configured.defaultAgent)
|
|
406
|
+
?? selectedAgents[0]!;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const updated = writeConfiguredAgentSelection(configPath, selectedNames, selectedDefault.name);
|
|
411
|
+
output.write(cliText(
|
|
412
|
+
`Default Agent: ${selectedDefault.name}${process.stdin.isTTY ? "" : " (selected automatically)"}\n`,
|
|
413
|
+
`默认 Agent:${selectedDefault.name}${process.stdin.isTTY ? "" : "(已自动选择)"}\n`,
|
|
414
|
+
));
|
|
415
|
+
return { name: selectedDefault.name, status: updated ? "selected" : "existing" };
|
|
370
416
|
}
|
|
371
417
|
|
|
372
418
|
function promptForAgentMaintenance(choiceCount: number): Promise<number[]> {
|
|
@@ -403,6 +449,40 @@ function promptForAgentMaintenance(choiceCount: number): Promise<number[]> {
|
|
|
403
449
|
});
|
|
404
450
|
}
|
|
405
451
|
|
|
452
|
+
function promptForConfiguredAgents(choices: SelectableDefaultAgent[]): Promise<number[]> {
|
|
453
|
+
const readline = createInterface({ input: process.stdin, output: process.stderr });
|
|
454
|
+
return new Promise((resolve, reject) => {
|
|
455
|
+
let settled = false;
|
|
456
|
+
const finish = (selection: number[]): void => {
|
|
457
|
+
settled = true;
|
|
458
|
+
readline.close();
|
|
459
|
+
resolve(selection);
|
|
460
|
+
};
|
|
461
|
+
const ask = (): void => {
|
|
462
|
+
readline.question(cliText(
|
|
463
|
+
"Enter numbers or Agent names separated by commas, type all, or press Enter for all: ",
|
|
464
|
+
"请输入 Agent 编号或标准名(多个用逗号分隔),输入 all 或直接回车选择全部:",
|
|
465
|
+
), (answer) => {
|
|
466
|
+
const selection = resolveAgentConfigurationChoices(answer, choices);
|
|
467
|
+
if (selection) {
|
|
468
|
+
finish(selection);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
process.stderr.write(cliText(
|
|
472
|
+
`Select at least one Agent using numbers from 1 to ${choices.length} or standard names.\n`,
|
|
473
|
+
`请使用 1 到 ${choices.length} 之间的编号或标准名,至少选择一个 Agent。\n`,
|
|
474
|
+
));
|
|
475
|
+
ask();
|
|
476
|
+
});
|
|
477
|
+
};
|
|
478
|
+
listenForPromptCancellation(readline, () => {
|
|
479
|
+
settled = true;
|
|
480
|
+
reject(new Error(cliText("Initialization was cancelled.", "初始化已取消。")));
|
|
481
|
+
}, () => settled);
|
|
482
|
+
ask();
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
406
486
|
function promptForDefaultAgent(
|
|
407
487
|
choices: SelectableDefaultAgent[],
|
|
408
488
|
currentAgent?: string,
|
|
@@ -1054,6 +1134,7 @@ function printInitializationResult(result: Omit<InitCommandResult, "server" | "w
|
|
|
1054
1134
|
"\nAgent Bot initialization completed.\n",
|
|
1055
1135
|
"\nAgent Bot 初始化完成。\n",
|
|
1056
1136
|
));
|
|
1137
|
+
process.stdout.write(`${cliText("Configured Agents: ", "已配置 Agent:")}${result.configuredAgents.join(", ")}\n`);
|
|
1057
1138
|
process.stdout.write(`${cliText("Default Agent: ", "默认 Agent:")}${result.defaultAgent.name}\n`);
|
|
1058
1139
|
if (result.feishu.status === "created") {
|
|
1059
1140
|
process.stdout.write(cliText(
|