@keyou007/agent-bot 0.1.15 → 0.1.16-alpha.1
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 +10 -0
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/dist/cli/AgentPrerequisites.js +5 -0
- package/dist/cli/AgentPrerequisites.js.map +1 -1
- package/dist/cli/FeishuAppConfiguration.js +38 -0
- package/dist/cli/FeishuAppConfiguration.js.map +1 -1
- package/dist/cli/FeishuConfigurationFeatures.js +86 -0
- package/dist/cli/FeishuConfigurationFeatures.js.map +1 -0
- package/dist/cli/InitUi.js +90 -0
- package/dist/cli/InitUi.js.map +1 -0
- package/dist/cli.js +401 -271
- package/dist/cli.js.map +1 -1
- package/npm-shrinkwrap.json +61 -2
- package/package.json +6 -2
- package/src/cli/AgentPrerequisites.ts +10 -0
- package/src/cli/FeishuAppConfiguration.ts +61 -0
- package/src/cli/FeishuConfigurationFeatures.ts +110 -0
- package/src/cli/InitUi.ts +135 -0
- package/src/cli.ts +551 -403
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
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";
|
|
6
5
|
import { loadConfig, loadConfigWithoutEnvironmentMutation } from "./config/loadConfig.js";
|
|
7
6
|
import { sendControlRequest, isServerReachable } from "./cli/LocalControlClient.js";
|
|
8
7
|
import { controlEndpoint, } from "./cli/controlProtocol.js";
|
|
@@ -10,11 +9,13 @@ import { acquireInitializationLock, cleanupFeishuCredentialTemporaryFiles, initi
|
|
|
10
9
|
import { parseInitCommandOptions } from "./cli/initOptions.js";
|
|
11
10
|
import { listenForManualPermissionSkip, listenForOptionalAuthorizationSkip, } from "./cli/OptionalAuthorizationSkip.js";
|
|
12
11
|
import { registerFeishuApp, } from "./cli/FeishuAppRegistration.js";
|
|
13
|
-
import { ensureFeishuAppConfiguration, } from "./cli/FeishuAppConfiguration.js";
|
|
12
|
+
import { ensureFeishuAppConfiguration, ensureFeishuGroupMessagePermission, } from "./cli/FeishuAppConfiguration.js";
|
|
13
|
+
import { feishuAffectedFeatures, formatFeishuConfigurationFeatureIntroduction, } from "./cli/FeishuConfigurationFeatures.js";
|
|
14
14
|
import { renderCliHelp } from "./cli/help.js";
|
|
15
15
|
import { cliLanguage, cliText, localizeCliErrorMessage } from "./cli/i18n.js";
|
|
16
|
-
import { inspectSupportedAgent, inspectSupportedAgents, runSupportedAgentMaintenance, } from "./cli/AgentPrerequisites.js";
|
|
17
|
-
import {
|
|
16
|
+
import { inspectSupportedAgent, inspectSupportedAgents, runSupportedAgentMaintenance, selectAgentMaintenanceActions, } from "./cli/AgentPrerequisites.js";
|
|
17
|
+
import { selectableDefaultAgents, } from "./cli/DefaultAgentSelection.js";
|
|
18
|
+
import { InitializationPromptCancelledError, InitUi, shouldUseInteractiveInitialization, } from "./cli/InitUi.js";
|
|
18
19
|
import { formatServerStatus, withConfiguredFeishuAppId } from "./cli/serverStatus.js";
|
|
19
20
|
import { resolveSystemSkillsRoot, SkillRegistry } from "./cli/SkillRegistry.js";
|
|
20
21
|
import { readPackageVersion } from "./cli/packageVersion.js";
|
|
@@ -36,6 +37,10 @@ import { createCurrentAutostartManager } from "./cli/AutostartManager.js";
|
|
|
36
37
|
import { formatAutostartStatus, } from "./cli/autostartOutput.js";
|
|
37
38
|
const args = process.argv.slice(2);
|
|
38
39
|
void main(args).catch((error) => {
|
|
40
|
+
if (error instanceof InitializationPromptCancelledError) {
|
|
41
|
+
process.exitCode = 130;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
39
44
|
const message = error instanceof Error ? error.message : String(error);
|
|
40
45
|
process.stderr.write(`${cliText("Error: ", "错误:")}${localizeCliErrorMessage(message)}\n`);
|
|
41
46
|
process.exitCode = 1;
|
|
@@ -151,6 +156,10 @@ async function updateCommand(input) {
|
|
|
151
156
|
async function initCommand(input, configPath) {
|
|
152
157
|
const options = parseInitCommandOptions(input);
|
|
153
158
|
const version = readPackageVersion();
|
|
159
|
+
const ui = new InitUi({
|
|
160
|
+
interactive: shouldUseInteractiveInitialization(options.json),
|
|
161
|
+
});
|
|
162
|
+
ui.start(cliText(`AgentBot ${version} setup`, `AgentBot ${version} 初始化`));
|
|
154
163
|
const previousInitialization = readInitializationReceipt(agentBotHome());
|
|
155
164
|
let initializationLock = options.reset
|
|
156
165
|
? acquireInitializationLock(agentBotHome())
|
|
@@ -162,10 +171,9 @@ async function initCommand(input, configPath) {
|
|
|
162
171
|
await assertResetProfileServerStopped(configPath);
|
|
163
172
|
paths = initializeAgentBot({ configPath, reset: options.reset });
|
|
164
173
|
if (!options.json)
|
|
165
|
-
printInitializationPaths(paths);
|
|
166
|
-
const inspectedAgents = await initializeSupportedAgents(options.json);
|
|
167
|
-
const defaultAgent = await configureAgentsAndDefault(paths.config.path, inspectedAgents, options.json, shouldConfigureAgentsDuringInitialization(paths.config.status));
|
|
168
|
-
const groupMessages = await configureGroupMessageResponse(paths.config.path, options.json, shouldConfigureGroupMessagesDuringInitialization(paths.config.status));
|
|
174
|
+
printInitializationPaths(paths, ui);
|
|
175
|
+
const inspectedAgents = await initializeSupportedAgents(options.json, ui);
|
|
176
|
+
const defaultAgent = await configureAgentsAndDefault(paths.config.path, inspectedAgents, options.json, shouldConfigureAgentsDuringInitialization(paths.config.status), ui);
|
|
169
177
|
const configuredAgents = readConfiguredAgentSelection(paths.config.path).agents.map((agent) => agent.name);
|
|
170
178
|
const configuredAgentNames = new Set(configuredAgents);
|
|
171
179
|
const agents = inspectedAgents.map((agent) => ({
|
|
@@ -174,7 +182,10 @@ async function initCommand(input, configPath) {
|
|
|
174
182
|
}));
|
|
175
183
|
initializationLock ??= acquireInitializationLock(paths.home.path);
|
|
176
184
|
cleanupFeishuCredentialTemporaryFiles(paths.env.path);
|
|
177
|
-
const
|
|
185
|
+
const feishuContext = await initializeFeishu(paths, options, ui);
|
|
186
|
+
const groupMessages = await configureGroupMessageResponse(paths.config.path, options.json, shouldConfigureGroupMessagesDuringInitialization(paths.config.status), ui);
|
|
187
|
+
const feishu = await completeFeishuGroupMessageSetup(feishuContext, groupMessages.mode, ui);
|
|
188
|
+
ui.step(cliText("Installing the AgentBot Skill", "正在安装 AgentBot Skill"));
|
|
178
189
|
const skill = bundledSkillRegistry().install();
|
|
179
190
|
initialized = { ...paths, agents, configuredAgents, defaultAgent, groupMessages, feishu, skill };
|
|
180
191
|
}
|
|
@@ -182,9 +193,12 @@ async function initCommand(input, configPath) {
|
|
|
182
193
|
initializationLock?.release();
|
|
183
194
|
}
|
|
184
195
|
if (!options.json)
|
|
185
|
-
printInitializationResult(initialized);
|
|
196
|
+
printInitializationResult(initialized, ui);
|
|
186
197
|
if (!options.json && !options.skipFeishu) {
|
|
187
|
-
|
|
198
|
+
if (ui.interactive)
|
|
199
|
+
ui.step(cliText("Starting AgentBot", "正在启动 AgentBot"));
|
|
200
|
+
else
|
|
201
|
+
process.stdout.write(cliText("\nStarting Agent Bot server...\n", "\n正在启动 Agent Bot 服务...\n"));
|
|
188
202
|
}
|
|
189
203
|
const server = await startInitializedServer({ skipFeishu: options.skipFeishu, configPath });
|
|
190
204
|
const welcomeKind = resolveInitializationWelcomeKind({
|
|
@@ -224,32 +238,50 @@ async function initCommand(input, configPath) {
|
|
|
224
238
|
if (options.json)
|
|
225
239
|
printJson(result);
|
|
226
240
|
else {
|
|
227
|
-
printInitializationServerResult(server);
|
|
228
|
-
printInitializationWelcomeResult(welcome);
|
|
241
|
+
printInitializationServerResult(server, ui);
|
|
242
|
+
printInitializationWelcomeResult(welcome, ui);
|
|
243
|
+
ui.finish(cliText("AgentBot initialization completed", "AgentBot 初始化完成"));
|
|
229
244
|
}
|
|
230
245
|
}
|
|
231
|
-
async function initializeSupportedAgents(json) {
|
|
246
|
+
async function initializeSupportedAgents(json, ui) {
|
|
232
247
|
const output = json ? process.stderr : process.stdout;
|
|
233
|
-
|
|
248
|
+
if (ui.interactive)
|
|
249
|
+
ui.step(cliText("Checking installed Agents", "正在检查已安装的 Agent"));
|
|
250
|
+
else {
|
|
251
|
+
output.write(cliText("\nAgent setup\nChecking Codex and TraeX...\n", "\nAgent 设置\n正在检查 Codex 和 TraeX...\n"));
|
|
252
|
+
}
|
|
234
253
|
const inspections = await inspectSupportedAgents();
|
|
235
|
-
for (const inspection of inspections)
|
|
236
|
-
|
|
237
|
-
|
|
254
|
+
for (const inspection of inspections) {
|
|
255
|
+
if (ui.interactive)
|
|
256
|
+
printInteractiveAgentInspection(inspection, ui);
|
|
257
|
+
else
|
|
258
|
+
printAgentInspection(inspection, output);
|
|
259
|
+
}
|
|
260
|
+
const actionable = selectAgentMaintenanceActions(inspections);
|
|
238
261
|
if (actionable.length === 0)
|
|
239
262
|
return inspections;
|
|
240
|
-
output.write(cliText("\nAvailable actions:\n", "\n可执行的操作:\n"));
|
|
241
|
-
for (const [index, inspection] of actionable.entries()) {
|
|
242
|
-
output.write(cliText(` ${index + 1}. ${inspection.action?.kind === "install" ? "Install" : "Upgrade"} ${inspection.name}: ${inspection.action?.command}\n`, ` ${index + 1}. ${inspection.action?.kind === "install" ? "安装" : "升级"} ${inspection.name}:${inspection.action?.command}\n`));
|
|
243
|
-
}
|
|
244
263
|
let selected = new Set();
|
|
245
|
-
if (
|
|
246
|
-
|
|
264
|
+
if (ui.interactive) {
|
|
265
|
+
selected = new Set(await ui.multiselect({
|
|
266
|
+
message: cliText("Choose Agent maintenance actions", "请选择 Agent 安装或升级操作"),
|
|
267
|
+
options: actionable.map((inspection, index) => ({
|
|
268
|
+
value: index,
|
|
269
|
+
label: cliText(`${inspection.action?.kind === "install" ? "Install" : "Upgrade"} ${inspection.name}`, `${inspection.action?.kind === "install" ? "安装" : "升级"} ${inspection.name}`),
|
|
270
|
+
hint: inspection.action?.command,
|
|
271
|
+
})),
|
|
272
|
+
initialValues: [],
|
|
273
|
+
required: false,
|
|
274
|
+
}));
|
|
275
|
+
if (selected.size === 0) {
|
|
276
|
+
ui.info(cliText("Agent maintenance skipped", "已跳过 Agent 安装和升级"));
|
|
277
|
+
}
|
|
247
278
|
}
|
|
248
279
|
else {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
output.write(cliText("
|
|
280
|
+
output.write(cliText("\nAvailable actions:\n", "\n可执行的操作:\n"));
|
|
281
|
+
for (const [index, inspection] of actionable.entries()) {
|
|
282
|
+
output.write(cliText(` ${index + 1}. ${inspection.action?.kind === "install" ? "Install" : "Upgrade"} ${inspection.name}: ${inspection.action?.command}\n`, ` ${index + 1}. ${inspection.action?.kind === "install" ? "安装" : "升级"} ${inspection.name}:${inspection.action?.command}\n`));
|
|
252
283
|
}
|
|
284
|
+
output.write(cliText("No interactive terminal is available. Run the commands above manually if needed.\n", "当前没有交互式终端,如有需要请手动执行上面的命令。\n"));
|
|
253
285
|
}
|
|
254
286
|
const initialized = [];
|
|
255
287
|
for (const inspection of inspections) {
|
|
@@ -261,31 +293,47 @@ async function initializeSupportedAgents(json) {
|
|
|
261
293
|
if (!selected.has(actionIndex)) {
|
|
262
294
|
initialized.push({
|
|
263
295
|
...inspection,
|
|
264
|
-
assistance: { status:
|
|
296
|
+
assistance: { status: ui.interactive ? "skipped" : "unavailable" },
|
|
265
297
|
});
|
|
266
298
|
continue;
|
|
267
299
|
}
|
|
268
|
-
|
|
300
|
+
const actionMessage = cliText(`Running ${inspection.name} ${inspection.action.kind}: ${inspection.action.command}`, `正在${inspection.action.kind === "install" ? "安装" : "升级"} ${inspection.name}:${inspection.action.command}`);
|
|
301
|
+
if (ui.interactive)
|
|
302
|
+
ui.step(actionMessage);
|
|
303
|
+
else
|
|
304
|
+
output.write(`\n${actionMessage}\n`);
|
|
269
305
|
const maintenance = await runSupportedAgentMaintenance(inspection.id, inspection.action.kind);
|
|
270
306
|
if (maintenance.status !== 0 || maintenance.error) {
|
|
271
307
|
const error = maintenance.error ?? cliText(`Command exited with code ${maintenance.status ?? "unknown"}.`, `命令退出码为 ${maintenance.status ?? "未知"}。`);
|
|
272
|
-
|
|
308
|
+
const warning = cliText(`${inspection.name} ${inspection.action.kind} did not complete; initialization will continue.`, `${inspection.name}${inspection.action.kind === "install" ? "安装" : "升级"}未完成,初始化将继续。`);
|
|
309
|
+
if (ui.interactive)
|
|
310
|
+
ui.warn(warning);
|
|
311
|
+
else
|
|
312
|
+
output.write(`${warning}\n`);
|
|
273
313
|
initialized.push({ ...inspection, assistance: { status: "failed", error } });
|
|
274
314
|
continue;
|
|
275
315
|
}
|
|
276
316
|
refreshCurrentProcessPath();
|
|
277
317
|
const refreshed = await inspectSupportedAgent(inspection.id);
|
|
278
318
|
if (refreshed.installedVersion) {
|
|
279
|
-
|
|
319
|
+
const ready = cliText(`${inspection.name} is ready (${refreshed.installedVersion}).`, `${inspection.name} 已就绪(${refreshed.installedVersion})。`);
|
|
320
|
+
if (ui.interactive)
|
|
321
|
+
ui.success(ready);
|
|
322
|
+
else
|
|
323
|
+
output.write(`${ready}\n`);
|
|
280
324
|
}
|
|
281
325
|
else {
|
|
282
|
-
|
|
326
|
+
const warning = cliText(`${inspection.name} command completed, but the CLI is not visible in this process. Open a new terminal and run agentbot init again.`, `${inspection.name} 命令已完成,但当前进程仍无法找到该 CLI。请打开新终端后重新运行 agentbot init。`);
|
|
327
|
+
if (ui.interactive)
|
|
328
|
+
ui.warn(warning);
|
|
329
|
+
else
|
|
330
|
+
output.write(`${warning}\n`);
|
|
283
331
|
}
|
|
284
332
|
initialized.push({ ...refreshed, assistance: { status: "completed" } });
|
|
285
333
|
}
|
|
286
334
|
return initialized;
|
|
287
335
|
}
|
|
288
|
-
async function configureAgentsAndDefault(configPath, inspections, json, configureAgents) {
|
|
336
|
+
async function configureAgentsAndDefault(configPath, inspections, json, configureAgents, ui) {
|
|
289
337
|
const output = json ? process.stderr : process.stdout;
|
|
290
338
|
const configured = readConfiguredAgentSelection(configPath);
|
|
291
339
|
const configuredDefaultIsValid = Boolean(configured.defaultAgent
|
|
@@ -294,7 +342,11 @@ async function configureAgentsAndDefault(configPath, inspections, json, configur
|
|
|
294
342
|
if (!configuredDefaultIsValid) {
|
|
295
343
|
throw new Error(cliText("The existing Profile has no valid default Agent. Set defaults.agent in config.yaml, then run agentbot init again.", "现有 Profile 没有有效的默认 Agent。请在 config.yaml 中设置 defaults.agent,然后重新运行 agentbot init。"));
|
|
296
344
|
}
|
|
297
|
-
|
|
345
|
+
const message = cliText(`Default Agent: ${configured.defaultAgent} (unchanged)`, `默认 Agent:${configured.defaultAgent}(保持不变)`);
|
|
346
|
+
if (ui.interactive)
|
|
347
|
+
ui.info(message);
|
|
348
|
+
else
|
|
349
|
+
output.write(`${message}\n`);
|
|
298
350
|
return { name: configured.defaultAgent, status: "existing" };
|
|
299
351
|
}
|
|
300
352
|
const choices = selectableDefaultAgents(configured.agents, inspections);
|
|
@@ -302,27 +354,39 @@ async function configureAgentsAndDefault(configPath, inspections, json, configur
|
|
|
302
354
|
throw new Error(cliText("No installed supported Agent was detected. Install Codex or TraeX, then run agentbot init again.", "没有检测到已安装且受支持的 Agent。请安装 Codex 或 TraeX,然后重新运行 agentbot init。"));
|
|
303
355
|
}
|
|
304
356
|
let selectedAgents = choices;
|
|
305
|
-
if (
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
357
|
+
if (ui.interactive && choices.length > 1) {
|
|
358
|
+
const selectedIndexes = await ui.multiselect({
|
|
359
|
+
message: cliText("Choose Agents to configure", "请选择要配置的 Agent"),
|
|
360
|
+
options: choices.map((choice, index) => ({
|
|
361
|
+
value: index,
|
|
362
|
+
label: `${choice.name} - ${choice.title}`,
|
|
363
|
+
hint: choice.installedVersion,
|
|
364
|
+
})),
|
|
365
|
+
initialValues: choices.map((_, index) => index),
|
|
366
|
+
required: true,
|
|
367
|
+
});
|
|
311
368
|
selectedAgents = selectedIndexes.flatMap((index) => choices[index] ? [choices[index]] : []);
|
|
312
369
|
}
|
|
313
370
|
const selectedNames = selectedAgents.map((agent) => agent.name);
|
|
314
|
-
|
|
371
|
+
const configuredMessage = cliText(`Configured Agents: ${selectedNames.join(", ")}`, `已配置 Agent:${selectedNames.join("、")}`);
|
|
372
|
+
if (ui.interactive)
|
|
373
|
+
ui.success(configuredMessage);
|
|
374
|
+
else
|
|
375
|
+
output.write(`${configuredMessage}\n`);
|
|
315
376
|
let selectedDefault = selectedAgents[0];
|
|
316
377
|
if (selectedAgents.length > 1) {
|
|
317
|
-
if (
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
:
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
378
|
+
if (ui.interactive) {
|
|
379
|
+
const currentIndex = selectedAgents.findIndex((choice) => choice.name === configured.defaultAgent);
|
|
380
|
+
const selectedIndex = await ui.select({
|
|
381
|
+
message: cliText("Choose the default Agent", "请选择默认 Agent"),
|
|
382
|
+
options: selectedAgents.map((choice, index) => ({
|
|
383
|
+
value: index,
|
|
384
|
+
label: `${choice.name} - ${choice.title}`,
|
|
385
|
+
hint: choice.installedVersion,
|
|
386
|
+
})),
|
|
387
|
+
initialValue: currentIndex >= 0 ? currentIndex : 0,
|
|
388
|
+
});
|
|
389
|
+
selectedDefault = selectedAgents[selectedIndex];
|
|
326
390
|
}
|
|
327
391
|
else {
|
|
328
392
|
selectedDefault = selectedAgents.find((agent) => agent.name === configured.defaultAgent)
|
|
@@ -330,149 +394,66 @@ async function configureAgentsAndDefault(configPath, inspections, json, configur
|
|
|
330
394
|
}
|
|
331
395
|
}
|
|
332
396
|
const updated = writeConfiguredAgentSelection(configPath, selectedNames, selectedDefault.name);
|
|
333
|
-
|
|
397
|
+
const defaultMessage = cliText(`Default Agent: ${selectedDefault.name}${ui.interactive ? "" : " (selected automatically)"}`, `默认 Agent:${selectedDefault.name}${ui.interactive ? "" : "(已自动选择)"}`);
|
|
398
|
+
if (ui.interactive)
|
|
399
|
+
ui.success(defaultMessage);
|
|
400
|
+
else
|
|
401
|
+
output.write(`${defaultMessage}\n`);
|
|
334
402
|
return { name: selectedDefault.name, status: updated ? "selected" : "existing" };
|
|
335
403
|
}
|
|
336
|
-
async function configureGroupMessageResponse(configPath, json, configure) {
|
|
404
|
+
async function configureGroupMessageResponse(configPath, json, configure, ui) {
|
|
337
405
|
const output = json ? process.stderr : process.stdout;
|
|
338
406
|
if (!configure) {
|
|
339
|
-
|
|
407
|
+
const mode = readGroupMessageResponseMode(configPath);
|
|
408
|
+
if (ui.interactive) {
|
|
409
|
+
ui.info(cliText(`Group message response: ${mode === "all" ? "receive all group messages" : "require an explicit @ mention"} (unchanged)`, `群消息响应方式:${mode === "all" ? "接收所有群消息" : "必须明确 @ 机器人"}(保持不变)`));
|
|
410
|
+
}
|
|
411
|
+
return { mode, status: "existing" };
|
|
340
412
|
}
|
|
341
413
|
let mode = "mention-only";
|
|
342
|
-
if (
|
|
343
|
-
|
|
344
|
-
|
|
414
|
+
if (ui.interactive) {
|
|
415
|
+
mode = await ui.select({
|
|
416
|
+
message: cliText("Choose how AgentBot responds to group messages", "请选择 AgentBot 响应群消息的方式"),
|
|
417
|
+
options: [
|
|
418
|
+
{
|
|
419
|
+
value: "mention-only",
|
|
420
|
+
label: cliText("Require an explicit @ mention", "必须明确 @ 机器人"),
|
|
421
|
+
hint: cliText("Recommended; no additional manual permission is required", "推荐;不需要额外手动申请权限"),
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
value: "all",
|
|
425
|
+
label: cliText("Receive all group messages", "接收所有群消息"),
|
|
426
|
+
hint: cliText("Respond without @; requires a final manual Lark permission step", "无需 @ 即可响应;最后需要手动开通飞书权限"),
|
|
427
|
+
},
|
|
428
|
+
],
|
|
429
|
+
initialValue: "mention-only",
|
|
430
|
+
});
|
|
345
431
|
}
|
|
346
432
|
else {
|
|
347
433
|
output.write(cliText("No interactive terminal is available. Group messages will require an explicit @ mention, and the all-group-message permission will not be requested.\n", "当前没有交互式终端。群消息将要求明确 @ 机器人,并且不会申请接收所有群消息的额外权限。\n"));
|
|
348
434
|
}
|
|
349
435
|
writeGroupMessageResponseMode(configPath, mode);
|
|
350
|
-
|
|
436
|
+
const message = cliText(`Group message response: ${mode === "all" ? "receive all group messages" : "require an explicit @ mention"}`, `群消息响应方式:${mode === "all" ? "接收所有群消息" : "必须明确 @ 机器人"}`);
|
|
437
|
+
if (ui.interactive)
|
|
438
|
+
ui.success(message);
|
|
439
|
+
else
|
|
440
|
+
output.write(`${message}\n`);
|
|
351
441
|
return { mode, status: "selected" };
|
|
352
442
|
}
|
|
353
|
-
function
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
if (normalized === "" || normalized === "2") {
|
|
370
|
-
finish("mention-only");
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
process.stderr.write(cliText("Enter 1 to receive all group messages, or 2 to require an @ mention.\n", "请输入 1 接收所有群消息,或输入 2 要求明确 @ 机器人。\n"));
|
|
374
|
-
ask();
|
|
375
|
-
});
|
|
376
|
-
};
|
|
377
|
-
listenForPromptCancellation(readline, () => {
|
|
378
|
-
settled = true;
|
|
379
|
-
reject(new Error(cliText("Initialization was cancelled.", "初始化已取消。")));
|
|
380
|
-
}, () => settled);
|
|
381
|
-
ask();
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
function promptForAgentMaintenance(choiceCount) {
|
|
385
|
-
const readline = createInterface({ input: process.stdin, output: process.stderr });
|
|
386
|
-
return new Promise((resolve, reject) => {
|
|
387
|
-
let settled = false;
|
|
388
|
-
const finish = (selection) => {
|
|
389
|
-
settled = true;
|
|
390
|
-
readline.close();
|
|
391
|
-
resolve(selection);
|
|
392
|
-
};
|
|
393
|
-
const ask = () => {
|
|
394
|
-
readline.question(cliText("Enter action numbers separated by commas, type all, or press Enter to skip: ", "请输入要执行的操作编号(多个用逗号分隔),输入 all 选择全部,直接回车跳过:"), (answer) => {
|
|
395
|
-
const selection = parseMaintenanceSelection(answer, choiceCount);
|
|
396
|
-
if (selection) {
|
|
397
|
-
finish(selection);
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
process.stderr.write(cliText(`Enter numbers from 1 to ${choiceCount}, all, or press Enter to skip.\n`, `请输入 1 到 ${choiceCount} 之间的编号、all,或直接回车跳过。\n`));
|
|
401
|
-
ask();
|
|
402
|
-
});
|
|
403
|
-
};
|
|
404
|
-
listenForPromptCancellation(readline, () => {
|
|
405
|
-
settled = true;
|
|
406
|
-
reject(new Error(cliText("Initialization was cancelled.", "初始化已取消。")));
|
|
407
|
-
}, () => settled);
|
|
408
|
-
ask();
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
function promptForConfiguredAgents(choices) {
|
|
412
|
-
const readline = createInterface({ input: process.stdin, output: process.stderr });
|
|
413
|
-
return new Promise((resolve, reject) => {
|
|
414
|
-
let settled = false;
|
|
415
|
-
const finish = (selection) => {
|
|
416
|
-
settled = true;
|
|
417
|
-
readline.close();
|
|
418
|
-
resolve(selection);
|
|
419
|
-
};
|
|
420
|
-
const ask = () => {
|
|
421
|
-
readline.question(cliText("Enter numbers or Agent names separated by commas, type all, or press Enter for all: ", "请输入 Agent 编号或标准名(多个用逗号分隔),输入 all 或直接回车选择全部:"), (answer) => {
|
|
422
|
-
const selection = resolveAgentConfigurationChoices(answer, choices);
|
|
423
|
-
if (selection) {
|
|
424
|
-
finish(selection);
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
process.stderr.write(cliText(`Select at least one Agent using numbers from 1 to ${choices.length} or standard names.\n`, `请使用 1 到 ${choices.length} 之间的编号或标准名,至少选择一个 Agent。\n`));
|
|
428
|
-
ask();
|
|
429
|
-
});
|
|
430
|
-
};
|
|
431
|
-
listenForPromptCancellation(readline, () => {
|
|
432
|
-
settled = true;
|
|
433
|
-
reject(new Error(cliText("Initialization was cancelled.", "初始化已取消。")));
|
|
434
|
-
}, () => settled);
|
|
435
|
-
ask();
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
function promptForDefaultAgent(choices, currentAgent) {
|
|
439
|
-
const readline = createInterface({ input: process.stdin, output: process.stderr });
|
|
440
|
-
return new Promise((resolve, reject) => {
|
|
441
|
-
let settled = false;
|
|
442
|
-
const finish = (index) => {
|
|
443
|
-
settled = true;
|
|
444
|
-
readline.close();
|
|
445
|
-
resolve(index);
|
|
446
|
-
};
|
|
447
|
-
const ask = () => {
|
|
448
|
-
const currentAvailable = choices.some((choice) => choice.name === currentAgent);
|
|
449
|
-
readline.question(cliText(`Enter a number or Agent name${currentAvailable ? ` [${currentAgent}]` : ""}: `, `请输入编号或 Agent 标准名${currentAvailable ? ` [${currentAgent}]` : ""}:`), (answer) => {
|
|
450
|
-
const index = resolveDefaultAgentChoice(answer, choices, currentAgent);
|
|
451
|
-
if (index !== undefined) {
|
|
452
|
-
finish(index);
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
process.stderr.write(cliText(`Enter a number from 1 to ${choices.length} or an Agent standard name.\n`, `请输入 1 到 ${choices.length} 之间的编号,或 Agent 标准名。\n`));
|
|
456
|
-
ask();
|
|
457
|
-
});
|
|
458
|
-
};
|
|
459
|
-
listenForPromptCancellation(readline, () => {
|
|
460
|
-
settled = true;
|
|
461
|
-
reject(new Error(cliText("Initialization was cancelled.", "初始化已取消。")));
|
|
462
|
-
}, () => settled);
|
|
463
|
-
ask();
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
function listenForPromptCancellation(readline, cancel, isSettled) {
|
|
467
|
-
readline.once("SIGINT", () => {
|
|
468
|
-
if (!isSettled())
|
|
469
|
-
cancel();
|
|
470
|
-
readline.close();
|
|
471
|
-
});
|
|
472
|
-
readline.once("close", () => {
|
|
473
|
-
if (!isSettled())
|
|
474
|
-
cancel();
|
|
475
|
-
});
|
|
443
|
+
function printInteractiveAgentInspection(inspection, ui) {
|
|
444
|
+
if (inspection.state === "missing") {
|
|
445
|
+
ui.info(cliText(`${inspection.name}: not installed`, `${inspection.name}:未安装`));
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
if (inspection.state === "outdated") {
|
|
449
|
+
ui.warn(cliText(`${inspection.name}: ${inspection.installedVersion}; update available (${inspection.latestVersion})`, `${inspection.name}:${inspection.installedVersion};可升级到 ${inspection.latestVersion}`));
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (inspection.latestCheckFailed) {
|
|
453
|
+
ui.warn(cliText(`${inspection.name}: ${inspection.installedVersion}; latest-version check unavailable`, `${inspection.name}:${inspection.installedVersion};无法检查最新版本`));
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
ui.success(cliText(`${inspection.name}: ${inspection.installedVersion} (up to date)`, `${inspection.name}:${inspection.installedVersion}(已是最新)`));
|
|
476
457
|
}
|
|
477
458
|
function printAgentInspection(inspection, output) {
|
|
478
459
|
if (inspection.state === "missing") {
|
|
@@ -498,25 +479,24 @@ function refreshCurrentProcessPath() {
|
|
|
498
479
|
if (pathEntry?.[1])
|
|
499
480
|
process.env.PATH = pathEntry[1];
|
|
500
481
|
}
|
|
501
|
-
async function initializeFeishu(paths, options,
|
|
482
|
+
async function initializeFeishu(paths, options, ui) {
|
|
502
483
|
const existing = readFeishuCredentials(paths.env.path);
|
|
503
484
|
if (options.skipFeishu) {
|
|
504
485
|
return {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
486
|
+
result: {
|
|
487
|
+
status: "skipped",
|
|
488
|
+
...(existing.appId ? { appId: existing.appId } : {}),
|
|
489
|
+
...(existing.appId && existing.appSecret
|
|
490
|
+
? { userOpenIdStatus: existing.userOpenId ? "configured" : "pending" }
|
|
491
|
+
: {}),
|
|
492
|
+
},
|
|
510
493
|
};
|
|
511
494
|
}
|
|
512
|
-
if (
|
|
495
|
+
if (ui.interactive)
|
|
496
|
+
ui.step(cliText("Creating or connecting the Lark bot", "正在创建或连接飞书机器人"));
|
|
497
|
+
else if (!options.json)
|
|
513
498
|
process.stdout.write(cliText("\nLark setup\n", "\n飞书设置\n"));
|
|
514
|
-
|
|
515
|
-
const controller = new AbortController();
|
|
516
|
-
const onInterrupt = () => controller.abort(new Error(cliText("Initialization was cancelled.", "初始化已取消。")));
|
|
517
|
-
process.once("SIGINT", onInterrupt);
|
|
518
|
-
process.once("SIGTERM", onInterrupt);
|
|
519
|
-
try {
|
|
499
|
+
return withInitializationCancellation(async (signal) => {
|
|
520
500
|
let credentials;
|
|
521
501
|
let status;
|
|
522
502
|
if (!shouldCreateFeishuApp(existing, options.reconfigureFeishu)) {
|
|
@@ -526,31 +506,85 @@ async function initializeFeishu(paths, options, respondToAllGroupMessages) {
|
|
|
526
506
|
userOpenId: existing.userOpenId,
|
|
527
507
|
};
|
|
528
508
|
status = "existing";
|
|
509
|
+
if (ui.interactive) {
|
|
510
|
+
ui.success(cliText(`Using the existing Lark bot (${credentials.appId})`, `正在使用已有飞书机器人(${credentials.appId})`));
|
|
511
|
+
}
|
|
529
512
|
}
|
|
530
513
|
else {
|
|
531
514
|
if (existing.status === "incomplete" && !options.json) {
|
|
532
|
-
|
|
515
|
+
const message = cliText("Incomplete Lark credentials were found. A new bot will be created.", "发现不完整的飞书凭据,将创建一个新机器人。");
|
|
516
|
+
if (ui.interactive)
|
|
517
|
+
ui.warn(message);
|
|
518
|
+
else
|
|
519
|
+
process.stdout.write(`\n${message}\n`);
|
|
533
520
|
}
|
|
534
521
|
credentials = await registerFeishuApp({
|
|
535
|
-
signal
|
|
536
|
-
onVerification: (challenge) => printFeishuVerification(challenge),
|
|
522
|
+
signal,
|
|
523
|
+
onVerification: (challenge) => printFeishuVerification(challenge, ui),
|
|
537
524
|
});
|
|
538
525
|
writeFeishuCredentials(paths.env.path, credentials);
|
|
539
526
|
status = "created";
|
|
527
|
+
if (ui.interactive) {
|
|
528
|
+
ui.success(cliText(`Lark bot created (${credentials.appId})`, `飞书机器人已创建(${credentials.appId})`));
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
const checking = cliText("Checking one-click Lark permissions, events, and callbacks", "正在检查可一键授权的飞书权限、事件和回调");
|
|
532
|
+
if (ui.interactive)
|
|
533
|
+
ui.step(checking);
|
|
534
|
+
else if (!options.json)
|
|
535
|
+
process.stdout.write(`${checking}...\n`);
|
|
536
|
+
const configuration = await runFeishuConfigurationInteraction(signal, ui, (interaction) => ensureFeishuAppConfiguration(credentials, {
|
|
537
|
+
...interaction,
|
|
538
|
+
respondToAllGroupMessages: false,
|
|
539
|
+
}));
|
|
540
|
+
if (ui.interactive) {
|
|
541
|
+
if (configuration.status === "partial") {
|
|
542
|
+
ui.warn(cliText("Some optional one-click Lark configuration was skipped or is not active yet", "部分可选的一键飞书配置已跳过或尚未生效"));
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
ui.success(cliText("One-click Lark permissions, events, and callbacks are ready", "可一键授权的飞书权限、事件和回调已就绪"));
|
|
546
|
+
}
|
|
540
547
|
}
|
|
541
|
-
|
|
542
|
-
|
|
548
|
+
return {
|
|
549
|
+
credentials,
|
|
550
|
+
result: {
|
|
551
|
+
status,
|
|
552
|
+
appId: credentials.appId,
|
|
553
|
+
userOpenIdStatus: credentials.userOpenId ? "configured" : "pending",
|
|
554
|
+
configuration,
|
|
555
|
+
},
|
|
556
|
+
};
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
async function completeFeishuGroupMessageSetup(context, mode, ui) {
|
|
560
|
+
if (mode !== "all" || !context.credentials)
|
|
561
|
+
return context.result;
|
|
562
|
+
ui.step(cliText("Configuring permission for group messages without @ mentions", "正在配置群消息免 @ 权限"));
|
|
563
|
+
const groupConfiguration = await withInitializationCancellation((signal) => runFeishuConfigurationInteraction(signal, ui, (interaction) => ensureFeishuGroupMessagePermission(context.credentials, interaction)));
|
|
564
|
+
if (ui.interactive) {
|
|
565
|
+
if (groupConfiguration.status === "partial") {
|
|
566
|
+
ui.warn(cliText("The all-group-message permission is not active; group messages still require an @ mention", "接收所有群消息权限尚未生效;群消息仍需 @ 机器人"));
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
ui.success(cliText("Group messages can be received without @ mentions", "已可接收未 @ 机器人的群消息"));
|
|
543
570
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
571
|
+
}
|
|
572
|
+
return {
|
|
573
|
+
...context.result,
|
|
574
|
+
configuration: mergeFeishuConfigurationResults(context.result.configuration, groupConfiguration),
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
async function runFeishuConfigurationInteraction(signal, ui, operation) {
|
|
578
|
+
const manualPermissionSkip = new AbortController();
|
|
579
|
+
const optionalSkip = new AbortController();
|
|
580
|
+
let skipListener;
|
|
581
|
+
try {
|
|
582
|
+
return await operation({
|
|
583
|
+
signal,
|
|
550
584
|
manualPermissionSkipSignal: manualPermissionSkip.signal,
|
|
551
585
|
optionalSkipSignal: optionalSkip.signal,
|
|
552
586
|
onVerification: (challenge) => {
|
|
553
|
-
printFeishuConfigurationVerification(challenge);
|
|
587
|
+
printFeishuConfigurationVerification(challenge, ui);
|
|
554
588
|
if (challenge.kind === "manual_scope" && challenge.blocking) {
|
|
555
589
|
skipListener?.close();
|
|
556
590
|
skipListener = listenForManualPermissionSkip(() => manualPermissionSkip.abort());
|
|
@@ -560,19 +594,46 @@ async function initializeFeishu(paths, options, respondToAllGroupMessages) {
|
|
|
560
594
|
skipListener = listenForOptionalAuthorizationSkip(() => optionalSkip.abort());
|
|
561
595
|
}
|
|
562
596
|
},
|
|
563
|
-
})
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
finally {
|
|
600
|
+
skipListener?.close();
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
async function withInitializationCancellation(operation) {
|
|
604
|
+
const controller = new AbortController();
|
|
605
|
+
const onInterrupt = () => controller.abort(new Error(cliText("Initialization was cancelled.", "初始化已取消。")));
|
|
606
|
+
process.once("SIGINT", onInterrupt);
|
|
607
|
+
process.once("SIGTERM", onInterrupt);
|
|
608
|
+
try {
|
|
609
|
+
return await operation(controller.signal);
|
|
570
610
|
}
|
|
571
611
|
finally {
|
|
572
612
|
process.removeListener("SIGINT", onInterrupt);
|
|
573
613
|
process.removeListener("SIGTERM", onInterrupt);
|
|
574
614
|
}
|
|
575
615
|
}
|
|
616
|
+
function mergeFeishuConfigurationResults(first, second) {
|
|
617
|
+
if (!first)
|
|
618
|
+
return second;
|
|
619
|
+
const added = mergeFeishuConfigurationSets(first.added, second.added);
|
|
620
|
+
const remaining = mergeFeishuConfigurationSets(first.remaining, second.remaining);
|
|
621
|
+
const addedCount = added.scopes.length + added.events.length + added.callbacks.length;
|
|
622
|
+
const remainingCount = remaining.scopes.length + remaining.events.length + remaining.callbacks.length;
|
|
623
|
+
return {
|
|
624
|
+
status: remainingCount > 0 ? "partial" : addedCount > 0 ? "updated" : "ready",
|
|
625
|
+
configuration: second.configuration,
|
|
626
|
+
added,
|
|
627
|
+
remaining,
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
function mergeFeishuConfigurationSets(first, second) {
|
|
631
|
+
return {
|
|
632
|
+
scopes: [...new Set([...first.scopes, ...second.scopes])],
|
|
633
|
+
events: [...new Set([...first.events, ...second.events])],
|
|
634
|
+
callbacks: [...new Set([...first.callbacks, ...second.callbacks])],
|
|
635
|
+
};
|
|
636
|
+
}
|
|
576
637
|
async function assertResetProfileServerStopped(configPath) {
|
|
577
638
|
const endpoints = new Set([controlEndpoint(defaultSqlitePath())]);
|
|
578
639
|
const selectedConfigPath = configPath ?? process.env.AGENT_BOT_CONFIG;
|
|
@@ -1448,7 +1509,21 @@ function printSkillStatus(status) {
|
|
|
1448
1509
|
: cliText("update required", "需要更新")}\n`);
|
|
1449
1510
|
}
|
|
1450
1511
|
}
|
|
1451
|
-
function printInitializationPaths(result) {
|
|
1512
|
+
function printInitializationPaths(result, ui) {
|
|
1513
|
+
if (ui.interactive) {
|
|
1514
|
+
const lines = [
|
|
1515
|
+
...(result.reset
|
|
1516
|
+
? [`${cliText("Reset backup", "重置备份")}: ${result.reset.backupPath}`]
|
|
1517
|
+
: []),
|
|
1518
|
+
`${cliText("Home directory", "主目录")}: ${result.home.path} (${initializationStatusLabel(result.home.status)})`,
|
|
1519
|
+
`${cliText("Config file", "配置文件")}: ${result.config.path} (${initializationStatusLabel(result.config.status)})`,
|
|
1520
|
+
`${cliText("Environment file", "环境文件")}: ${result.env.path} (${initializationStatusLabel(result.env.status)})`,
|
|
1521
|
+
`${cliText("Data directory", "数据目录")}: ${result.data.path} (${initializationStatusLabel(result.data.status)})`,
|
|
1522
|
+
`${cliText("Log directory", "日志目录")}: ${result.logs.path} (${initializationStatusLabel(result.logs.status)})`,
|
|
1523
|
+
];
|
|
1524
|
+
ui.note(cliText("Profile", "Profile"), lines.join("\n"));
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1452
1527
|
process.stdout.write(cliText("Profile setup\n", "Profile 设置\n"));
|
|
1453
1528
|
if (result.reset) {
|
|
1454
1529
|
process.stdout.write(`${cliText("Reset backup: ", "重置备份:")}${result.reset.backupPath}\n`);
|
|
@@ -1459,7 +1534,38 @@ function printInitializationPaths(result) {
|
|
|
1459
1534
|
process.stdout.write(`${cliText("Data directory: ", "数据目录:")}${result.data.path} (${initializationStatusLabel(result.data.status)})\n`);
|
|
1460
1535
|
process.stdout.write(`${cliText("Log directory: ", "日志目录:")}${result.logs.path} (${initializationStatusLabel(result.logs.status)})\n`);
|
|
1461
1536
|
}
|
|
1462
|
-
function printInitializationResult(result) {
|
|
1537
|
+
function printInitializationResult(result, ui) {
|
|
1538
|
+
if (ui.interactive) {
|
|
1539
|
+
const feishuStatus = result.feishu.status === "created"
|
|
1540
|
+
? cliText(`created (${result.feishu.appId})`, `已创建(${result.feishu.appId})`)
|
|
1541
|
+
: result.feishu.status === "existing"
|
|
1542
|
+
? cliText(`existing (${result.feishu.appId})`, `已配置(${result.feishu.appId})`)
|
|
1543
|
+
: cliText("skipped", "已跳过");
|
|
1544
|
+
const configuration = result.feishu.configuration;
|
|
1545
|
+
const configurationStatus = configuration?.status === "partial"
|
|
1546
|
+
? cliText("partially configured", "部分配置未完成")
|
|
1547
|
+
: configuration
|
|
1548
|
+
? cliText("ready", "已就绪")
|
|
1549
|
+
: cliText("not checked", "未检查");
|
|
1550
|
+
ui.note(cliText("Initialization summary", "初始化摘要"), [
|
|
1551
|
+
`${cliText("Configured Agents", "已配置 Agent")}: ${result.configuredAgents.join(", ")}`,
|
|
1552
|
+
`${cliText("Default Agent", "默认 Agent")}: ${result.defaultAgent.name}`,
|
|
1553
|
+
`${cliText("Group messages", "群消息")}: ${result.groupMessages.mode === "all"
|
|
1554
|
+
? cliText("receive all", "接收所有消息")
|
|
1555
|
+
: cliText("require @ mention", "需要 @ 机器人")}`,
|
|
1556
|
+
`${cliText("Lark bot", "飞书机器人")}: ${feishuStatus}`,
|
|
1557
|
+
`${cliText("Lark configuration", "飞书配置")}: ${configurationStatus}`,
|
|
1558
|
+
`${cliText("AgentBot Skill", "AgentBot Skill")}: ${result.skill.status.targetPath}`,
|
|
1559
|
+
`${cliText("Config file", "配置文件")}: ${result.config.path}`,
|
|
1560
|
+
].join("\n"));
|
|
1561
|
+
if (configuration?.status === "partial") {
|
|
1562
|
+
ui.warn(cliText("Initialization continued with some unavailable Lark features.", "部分飞书功能尚不可用,但初始化已继续。"));
|
|
1563
|
+
for (const feature of feishuAffectedFeatures(configuration.remaining)) {
|
|
1564
|
+
ui.warn(`${cliText("Affected feature", "受影响功能")}: ${feature}`);
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1463
1569
|
process.stdout.write(cliText("\nAgent Bot initialization completed.\n", "\nAgent Bot 初始化完成。\n"));
|
|
1464
1570
|
process.stdout.write(`${cliText("Configured Agents: ", "已配置 Agent:")}${result.configuredAgents.join(", ")}\n`);
|
|
1465
1571
|
process.stdout.write(`${cliText("Default Agent: ", "默认 Agent:")}${result.defaultAgent.name}\n`);
|
|
@@ -1499,28 +1605,55 @@ function printInitializationResult(result) {
|
|
|
1499
1605
|
}
|
|
1500
1606
|
process.stdout.write(`${cliText("Config file: ", "配置文件:")}${result.config.path}\n`);
|
|
1501
1607
|
}
|
|
1502
|
-
function printInitializationServerResult(result) {
|
|
1608
|
+
function printInitializationServerResult(result, ui) {
|
|
1503
1609
|
if (result.status === "skipped") {
|
|
1504
|
-
|
|
1610
|
+
const message = cliText("Agent Bot server: skipped (Lark is not configured; Console mode is available)", "Agent Bot 服务:已跳过(未配置飞书;可使用 Console 模式)");
|
|
1611
|
+
if (ui.interactive)
|
|
1612
|
+
ui.warn(message);
|
|
1613
|
+
else
|
|
1614
|
+
process.stdout.write(`${message}\n`);
|
|
1505
1615
|
return;
|
|
1506
1616
|
}
|
|
1507
1617
|
if (result.status === "restart-scheduled") {
|
|
1508
|
-
|
|
1618
|
+
const message = cliText("Agent Bot server: safe restart scheduled to load the current version.", "Agent Bot 服务:已安排安全重启,以加载当前版本。");
|
|
1619
|
+
if (ui.interactive)
|
|
1620
|
+
ui.success(message);
|
|
1621
|
+
else
|
|
1622
|
+
process.stdout.write(`${message}\n`);
|
|
1509
1623
|
return;
|
|
1510
1624
|
}
|
|
1511
|
-
|
|
1625
|
+
if (ui.interactive) {
|
|
1626
|
+
ui.success(result.status === "already-running"
|
|
1627
|
+
? cliText("Agent Bot server is already running.", "Agent Bot 服务已在运行。")
|
|
1628
|
+
: cliText("Agent Bot server started.", "Agent Bot 服务已启动。"));
|
|
1629
|
+
}
|
|
1630
|
+
else {
|
|
1631
|
+
printServerStartResult(result);
|
|
1632
|
+
}
|
|
1512
1633
|
}
|
|
1513
|
-
function printInitializationWelcomeResult(result) {
|
|
1634
|
+
function printInitializationWelcomeResult(result, ui) {
|
|
1514
1635
|
if (result.status === "sent") {
|
|
1515
|
-
|
|
1636
|
+
const message = cliText("Welcome card: sent to your Lark private chat.", "欢迎卡片:已发送到你的飞书私聊。");
|
|
1637
|
+
if (ui.interactive)
|
|
1638
|
+
ui.success(message);
|
|
1639
|
+
else
|
|
1640
|
+
process.stdout.write(`${message}\n`);
|
|
1516
1641
|
return;
|
|
1517
1642
|
}
|
|
1518
1643
|
if (result.status === "failed") {
|
|
1519
|
-
|
|
1644
|
+
const message = cliText(`The private welcome card could not be sent: ${result.message}`, `无法发送私聊欢迎卡片:${result.message}`);
|
|
1645
|
+
if (ui.interactive)
|
|
1646
|
+
ui.warn(message);
|
|
1647
|
+
else
|
|
1648
|
+
process.stderr.write(`${cliText("Warning: ", "警告:")}${message}\n`);
|
|
1520
1649
|
return;
|
|
1521
1650
|
}
|
|
1522
1651
|
if (result.reason === "missing-user-open-id") {
|
|
1523
|
-
|
|
1652
|
+
const message = cliText("The private welcome card was skipped because FEISHU_USER_OPEN_ID is not available. Send the bot a private message, then run agentbot init again.", "尚未获得 FEISHU_USER_OPEN_ID,无法发送私聊欢迎卡片。请先私聊机器人,再次运行 agentbot init。");
|
|
1653
|
+
if (ui.interactive)
|
|
1654
|
+
ui.warn(message);
|
|
1655
|
+
else
|
|
1656
|
+
process.stderr.write(`${cliText("Warning: ", "警告:")}${message}\n`);
|
|
1524
1657
|
}
|
|
1525
1658
|
}
|
|
1526
1659
|
function printServerStartResult(result) {
|
|
@@ -1528,24 +1661,43 @@ function printServerStartResult(result) {
|
|
|
1528
1661
|
? cliText("Agent Bot server is already running.\n", "Agent Bot 服务已在运行。\n")
|
|
1529
1662
|
: cliText("Agent Bot server started.\n", "Agent Bot 服务已启动。\n"));
|
|
1530
1663
|
}
|
|
1531
|
-
function printFeishuVerification(challenge) {
|
|
1664
|
+
function printFeishuVerification(challenge, ui) {
|
|
1665
|
+
if (ui.interactive) {
|
|
1666
|
+
ui.note(cliText("Create Lark bot", "创建飞书机器人"), cliText("Open the link below and confirm creation. AgentBot will save the App ID and App Secret after Lark returns them.", "请打开下方链接并确认创建。飞书返回 App ID 和 App Secret 后,AgentBot 会自动保存。"));
|
|
1667
|
+
}
|
|
1532
1668
|
printVerificationLink({
|
|
1533
1669
|
verificationUrl: challenge.verificationUrl,
|
|
1534
1670
|
});
|
|
1535
|
-
|
|
1671
|
+
const waiting = cliText(`The link expires in about ${Math.ceil(challenge.expiresIn / 60)} minutes. Waiting for confirmation...`, `链接将在约 ${Math.ceil(challenge.expiresIn / 60)} 分钟后过期,正在等待确认...`);
|
|
1672
|
+
if (ui.interactive)
|
|
1673
|
+
ui.info(waiting);
|
|
1674
|
+
else
|
|
1675
|
+
process.stderr.write(`${waiting}\n`);
|
|
1536
1676
|
}
|
|
1537
|
-
function printFeishuConfigurationVerification(challenge) {
|
|
1538
|
-
|
|
1539
|
-
? cliText("
|
|
1540
|
-
: cliText("
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1677
|
+
function printFeishuConfigurationVerification(challenge, ui) {
|
|
1678
|
+
const title = challenge.kind === "manual_scope"
|
|
1679
|
+
? cliText("Manual Lark permission", "手动开通飞书权限")
|
|
1680
|
+
: cliText("Lark authorization required", "需要飞书授权");
|
|
1681
|
+
const details = [
|
|
1682
|
+
...(challenge.missing.scopes.length > 0
|
|
1683
|
+
? [`${cliText("Scopes", "权限")}: ${challenge.missing.scopes.join(", ")}`]
|
|
1684
|
+
: []),
|
|
1685
|
+
...(challenge.missing.events.length > 0
|
|
1686
|
+
? [`${cliText("Events", "事件")}: ${challenge.missing.events.join(", ")}`]
|
|
1687
|
+
: []),
|
|
1688
|
+
...(challenge.missing.callbacks.length > 0
|
|
1689
|
+
? [`${cliText("Callbacks", "回调")}: ${challenge.missing.callbacks.join(", ")}`]
|
|
1690
|
+
: []),
|
|
1691
|
+
formatFeishuConfigurationFeatureIntroduction(challenge.missing).trim(),
|
|
1692
|
+
].filter(Boolean).join("\n");
|
|
1693
|
+
if (ui.interactive) {
|
|
1694
|
+
ui.note(title, details);
|
|
1546
1695
|
}
|
|
1547
|
-
|
|
1548
|
-
process.stderr.write(
|
|
1696
|
+
else {
|
|
1697
|
+
process.stderr.write(challenge.kind === "manual_scope"
|
|
1698
|
+
? cliText("\nThe following Lark permission must be added manually in Developer Console:\n", "\n必须在飞书开发者后台手动添加以下权限:\n")
|
|
1699
|
+
: cliText("\nThe Lark app is missing the following configuration:\n", "\n飞书应用缺少以下配置:\n"));
|
|
1700
|
+
process.stderr.write(`${details}\n`);
|
|
1549
1701
|
}
|
|
1550
1702
|
printVerificationLink({
|
|
1551
1703
|
verificationUrl: challenge.verificationUrl,
|
|
@@ -1554,17 +1706,26 @@ function printFeishuConfigurationVerification(challenge) {
|
|
|
1554
1706
|
: undefined,
|
|
1555
1707
|
});
|
|
1556
1708
|
if (challenge.kind === "manual_scope") {
|
|
1557
|
-
|
|
1709
|
+
const guidance = cliText("Add the filtered permission, publish the app version, and complete tenant approval if required.", "请添加已筛选的权限、发布应用版本,并在需要时完成租户管理员审批。");
|
|
1710
|
+
if (ui.interactive)
|
|
1711
|
+
ui.info(guidance);
|
|
1712
|
+
else
|
|
1713
|
+
process.stderr.write(`${guidance}\n`);
|
|
1558
1714
|
}
|
|
1715
|
+
let waiting;
|
|
1559
1716
|
if (challenge.kind === "manual_scope") {
|
|
1560
|
-
|
|
1717
|
+
waiting = cliText("Waiting up to 5 minutes for this permission to appear in the published app version...", "将等待最多 5 分钟,直到该权限出现在已发布的应用版本中...");
|
|
1561
1718
|
}
|
|
1562
1719
|
else if (challenge.blocking) {
|
|
1563
|
-
|
|
1720
|
+
waiting = cliText("Waiting for the core scopes and message event to become active...", "正在等待核心权限和消息事件生效...");
|
|
1564
1721
|
}
|
|
1565
1722
|
else {
|
|
1566
|
-
|
|
1723
|
+
waiting = cliText("Waiting up to 5 minutes for these optional items to become active...", "将等待最多 5 分钟,直到这些可选配置生效...");
|
|
1567
1724
|
}
|
|
1725
|
+
if (ui.interactive)
|
|
1726
|
+
ui.info(waiting);
|
|
1727
|
+
else
|
|
1728
|
+
process.stderr.write(`${waiting}\n`);
|
|
1568
1729
|
}
|
|
1569
1730
|
function printFeishuConfigurationWarnings(missing) {
|
|
1570
1731
|
process.stdout.write(cliText("Warning: some Lark configuration is not active, so some features may be unavailable.\n", "警告:部分飞书配置尚未生效,某些功能可能不可用。\n"));
|
|
@@ -1577,41 +1738,10 @@ function printFeishuConfigurationWarnings(missing) {
|
|
|
1577
1738
|
if (missing.callbacks.length > 0) {
|
|
1578
1739
|
process.stdout.write(`${cliText("Inactive callbacks: ", "未生效回调:")}${missing.callbacks.join(", ")}\n`);
|
|
1579
1740
|
}
|
|
1580
|
-
for (const feature of
|
|
1741
|
+
for (const feature of feishuAffectedFeatures(missing)) {
|
|
1581
1742
|
process.stdout.write(`${cliText("Affected feature: ", "受影响功能:")}${feature}\n`);
|
|
1582
1743
|
}
|
|
1583
1744
|
}
|
|
1584
|
-
function feishuFeatureWarnings(missing) {
|
|
1585
|
-
const scopes = new Set(missing.scopes);
|
|
1586
|
-
const events = new Set(missing.events);
|
|
1587
|
-
const callbacks = new Set(missing.callbacks);
|
|
1588
|
-
const warnings = [];
|
|
1589
|
-
if (scopes.has("im:message.group_msg")) {
|
|
1590
|
-
warnings.push(cliText("responding to ordinary group messages that do not mention the bot", "响应未 @ 机器人的普通群消息"));
|
|
1591
|
-
}
|
|
1592
|
-
if (scopes.has("im:chat:create")) {
|
|
1593
|
-
warnings.push(cliText("creating Lark groups with /newgroup and /forkgroup", "使用 /newgroup 和 /forkgroup 创建飞书群"));
|
|
1594
|
-
}
|
|
1595
|
-
if (scopes.has("im:chat:read") || events.has("im.chat.updated_v1")) {
|
|
1596
|
-
warnings.push(cliText("synchronizing Agent Bot task titles after Lark group renames", "飞书群重命名后同步 Agent Bot 任务标题"));
|
|
1597
|
-
}
|
|
1598
|
-
if (scopes.has("im:message.reactions:write_only")) {
|
|
1599
|
-
warnings.push(cliText("showing message-processing status with reactions", "使用 Reaction 显示消息处理状态"));
|
|
1600
|
-
}
|
|
1601
|
-
if (scopes.has("im:message:readonly")) {
|
|
1602
|
-
warnings.push(cliText("reading images from user messages", "读取用户消息中的图片"));
|
|
1603
|
-
}
|
|
1604
|
-
if (scopes.has("im:resource")) {
|
|
1605
|
-
warnings.push(cliText("uploading images, sending local images, or setting group avatars", "上传图片、发送本地图片或设置群头像"));
|
|
1606
|
-
}
|
|
1607
|
-
if (scopes.has("im:message:update")) {
|
|
1608
|
-
warnings.push(cliText("updating sent progress cards", "更新已发送的进度卡片"));
|
|
1609
|
-
}
|
|
1610
|
-
if (callbacks.has("card.action.trigger")) {
|
|
1611
|
-
warnings.push(cliText("card buttons and interactive actions", "卡片按钮和交互操作"));
|
|
1612
|
-
}
|
|
1613
|
-
return warnings;
|
|
1614
|
-
}
|
|
1615
1745
|
function initializationStatusLabel(status) {
|
|
1616
1746
|
if (status === "created")
|
|
1617
1747
|
return cliText("created", "已创建");
|