@keyou007/agent-bot 0.1.2 → 0.1.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/.env.example +3 -1
- package/CHANGELOG.md +15 -0
- package/README.md +23 -8
- package/README.zh.md +23 -8
- package/config.example.yaml +1 -0
- package/dist/cli/FeishuAppConfiguration.js +10 -10
- package/dist/cli/FeishuAppConfiguration.js.map +1 -1
- package/dist/cli/FeishuAppRegistration.js +21 -11
- package/dist/cli/FeishuAppRegistration.js.map +1 -1
- package/dist/cli/Initializer.js +14 -8
- package/dist/cli/Initializer.js.map +1 -1
- package/dist/cli/LocalControlClient.js +2 -2
- package/dist/cli/LocalControlClient.js.map +1 -1
- package/dist/cli/LocalControlServer.js +1 -1
- package/dist/cli/LocalControlServer.js.map +1 -1
- package/dist/cli/ServerStarter.js +69 -0
- package/dist/cli/ServerStarter.js.map +1 -0
- package/dist/cli/SkillRegistry.js +4 -4
- package/dist/cli/SkillRegistry.js.map +1 -1
- package/dist/cli/help.js +5 -56
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/packageVersion.js +1 -1
- package/dist/cli/packageVersion.js.map +1 -1
- package/dist/cli/profile.js +40 -0
- package/dist/cli/profile.js.map +1 -0
- package/dist/cli/serverStatus.js +37 -0
- package/dist/cli/serverStatus.js.map +1 -0
- package/dist/cli/taskChatRoute.js +2 -2
- package/dist/cli/taskChatRoute.js.map +1 -1
- package/dist/cli.js +148 -168
- package/dist/cli.js.map +1 -1
- package/dist/config/loadConfig.js +7 -2
- package/dist/config/loadConfig.js.map +1 -1
- package/dist/config/paths.js +1 -0
- package/dist/config/paths.js.map +1 -1
- package/dist/config/schema.js +1 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/feishu/CardRenderer.js +21 -3
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuConnector.js +7 -76
- package/dist/feishu/FeishuConnector.js.map +1 -1
- package/dist/feishu/transport.js +1 -1
- package/dist/feishu/transport.js.map +1 -1
- package/dist/index.js +18 -9
- package/dist/index.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +28 -13
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/startup/StartupNotifier.js +13 -2
- package/dist/startup/StartupNotifier.js.map +1 -1
- package/dist/startup/startFeishu.js +1 -1
- package/dist/startup/startFeishu.js.map +1 -1
- package/dist/supervision/SafeRestartNotifier.js +23 -7
- package/dist/supervision/SafeRestartNotifier.js.map +1 -1
- package/dist/supervision/SafeRestartScheduler.js +20 -2
- package/dist/supervision/SafeRestartScheduler.js.map +1 -1
- package/dist/supervision/SupervisorDiagnostics.js +209 -0
- package/dist/supervision/SupervisorDiagnostics.js.map +1 -0
- package/dist/supervisor.js +47 -12
- package/dist/supervisor.js.map +1 -1
- package/docs/technical-reference.md +25 -5
- package/docs/technical-reference.zh.md +25 -5
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/skills/agent-bot/SKILL.md +24 -4
- package/src/cli/FeishuAppConfiguration.ts +10 -10
- package/src/cli/FeishuAppRegistration.ts +23 -10
- package/src/cli/Initializer.ts +14 -7
- package/src/cli/LocalControlClient.ts +2 -2
- package/src/cli/LocalControlServer.ts +1 -1
- package/src/cli/ServerStarter.ts +106 -0
- package/src/cli/SkillRegistry.ts +4 -4
- package/src/cli/help.ts +5 -65
- package/src/cli/packageVersion.ts +1 -1
- package/src/cli/profile.ts +59 -0
- package/src/cli/serverStatus.ts +39 -0
- package/src/cli/taskChatRoute.ts +2 -2
- package/src/cli.ts +160 -167
- package/src/config/loadConfig.ts +12 -2
- package/src/config/paths.ts +1 -0
- package/src/config/schema.ts +1 -0
- package/src/feishu/CardRenderer.ts +25 -3
- package/src/feishu/FeishuConnector.ts +6 -87
- package/src/feishu/transport.ts +1 -1
- package/src/index.ts +22 -9
- package/src/proxy/ProxySessionController.ts +28 -13
- package/src/startup/StartupNotifier.ts +21 -4
- package/src/startup/startFeishu.ts +1 -1
- package/src/supervision/SafeRestartNotifier.ts +22 -7
- package/src/supervision/SafeRestartScheduler.ts +27 -3
- package/src/supervision/SupervisorDiagnostics.ts +268 -0
- package/src/supervisor.ts +46 -11
package/src/cli.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import qrcode from "qrcode-terminal";
|
|
5
5
|
import { loadConfig } from "./config/loadConfig.js";
|
|
6
|
-
import { sendControlRequest, isServerReachable
|
|
6
|
+
import { sendControlRequest, isServerReachable } from "./cli/LocalControlClient.js";
|
|
7
7
|
import {
|
|
8
8
|
controlEndpoint,
|
|
9
9
|
type ControlResponse,
|
|
@@ -30,22 +30,35 @@ import {
|
|
|
30
30
|
type FeishuConfigurationChallenge,
|
|
31
31
|
} from "./cli/FeishuAppConfiguration.js";
|
|
32
32
|
import { renderCliHelp } from "./cli/help.js";
|
|
33
|
-
import {
|
|
33
|
+
import { formatServerStatus, withConfiguredFeishuAppId } from "./cli/serverStatus.js";
|
|
34
34
|
import { resolveSystemSkillsRoot, SkillRegistry, type SkillRegistrationStatus } from "./cli/SkillRegistry.js";
|
|
35
35
|
import { readPackageVersion } from "./cli/packageVersion.js";
|
|
36
|
+
import { applyExplicitProfile, parseGlobalOptions } from "./cli/profile.js";
|
|
37
|
+
import {
|
|
38
|
+
startInitializedServer,
|
|
39
|
+
startServer,
|
|
40
|
+
type InitializationServerResult,
|
|
41
|
+
type ServerStartResult,
|
|
42
|
+
} from "./cli/ServerStarter.js";
|
|
36
43
|
import { taskChatRoute } from "./cli/taskChatRoute.js";
|
|
37
44
|
import { StateStore, type SessionRecord } from "./state/StateStore.js";
|
|
45
|
+
import {
|
|
46
|
+
nodeDiagnosticReportArguments,
|
|
47
|
+
prepareCrashReportDirectory,
|
|
48
|
+
resolveSupervisorDiagnosticsPaths,
|
|
49
|
+
} from "./supervision/SupervisorDiagnostics.js";
|
|
38
50
|
|
|
39
51
|
const args = process.argv.slice(2);
|
|
40
52
|
|
|
41
53
|
void main(args).catch((error: unknown) => {
|
|
42
|
-
process.stderr.write(
|
|
54
|
+
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
43
55
|
process.exitCode = 1;
|
|
44
56
|
});
|
|
45
57
|
|
|
46
58
|
async function main(input: string[]): Promise<void> {
|
|
47
59
|
const parsed = parseGlobalOptions(input);
|
|
48
|
-
if (parsed.
|
|
60
|
+
if (parsed.profilePath) applyExplicitProfile(parsed.profilePath);
|
|
61
|
+
else if (parsed.configPath) process.env.AGENT_BOT_CONFIG = parsed.configPath;
|
|
49
62
|
const [command, ...rest] = parsed.args;
|
|
50
63
|
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
51
64
|
printHelp();
|
|
@@ -75,7 +88,7 @@ async function main(input: string[]): Promise<void> {
|
|
|
75
88
|
skillsCommand(rest);
|
|
76
89
|
return;
|
|
77
90
|
}
|
|
78
|
-
throw new Error(
|
|
91
|
+
throw new Error(`Unknown command: ${command}. Run agent-bot --help for usage.`);
|
|
79
92
|
}
|
|
80
93
|
|
|
81
94
|
type FeishuInitializationStatus = "created" | "existing" | "skipped";
|
|
@@ -86,35 +99,42 @@ interface InitCommandResult extends InitializationResult {
|
|
|
86
99
|
appId?: string;
|
|
87
100
|
configuration?: EnsureFeishuAppConfigurationResult;
|
|
88
101
|
};
|
|
102
|
+
server: InitializationServerResult;
|
|
89
103
|
}
|
|
90
104
|
|
|
91
105
|
async function initCommand(input: string[], configPath?: string): Promise<void> {
|
|
92
106
|
const supported = new Set(["--json", "--skip-feishu", "--reconfigure-feishu"]);
|
|
93
107
|
const unsupported = input.filter((value) => !supported.has(value));
|
|
94
|
-
if (unsupported.length > 0) throw new Error(`init
|
|
108
|
+
if (unsupported.length > 0) throw new Error(`Unsupported init options: ${unsupported.join(" ")}`);
|
|
95
109
|
const json = input.includes("--json");
|
|
96
110
|
const skipFeishu = input.includes("--skip-feishu");
|
|
97
111
|
const reconfigureFeishu = input.includes("--reconfigure-feishu");
|
|
98
112
|
if (skipFeishu && reconfigureFeishu) {
|
|
99
|
-
throw new Error("--skip-feishu
|
|
113
|
+
throw new Error("--skip-feishu and --reconfigure-feishu cannot be used together.");
|
|
100
114
|
}
|
|
101
115
|
|
|
102
116
|
const paths = initializeAgentBot({ configPath });
|
|
103
117
|
if (!json) printInitializationPaths(paths);
|
|
104
118
|
const initializationLock = acquireInitializationLock(paths.home.path);
|
|
105
119
|
|
|
120
|
+
let initialized: Omit<InitCommandResult, "server">;
|
|
106
121
|
try {
|
|
107
122
|
cleanupFeishuCredentialTemporaryFiles(paths.env.path);
|
|
108
123
|
const feishu = await initializeFeishu(
|
|
109
124
|
paths,
|
|
110
125
|
{ json, skipFeishu, reconfigureFeishu },
|
|
111
126
|
);
|
|
112
|
-
|
|
113
|
-
if (json) printJson(result);
|
|
114
|
-
else printInitializationResult(result);
|
|
127
|
+
initialized = { ...paths, feishu };
|
|
115
128
|
} finally {
|
|
116
129
|
initializationLock.release();
|
|
117
130
|
}
|
|
131
|
+
|
|
132
|
+
if (!json) printInitializationResult(initialized);
|
|
133
|
+
if (!json && !skipFeishu) process.stdout.write("\nStarting Agent Bot server...\n");
|
|
134
|
+
const server = await startInitializedServer({ skipFeishu, configPath });
|
|
135
|
+
const result: InitCommandResult = { ...initialized, server };
|
|
136
|
+
if (json) printJson(result);
|
|
137
|
+
else printInitializationServerResult(server);
|
|
118
138
|
}
|
|
119
139
|
|
|
120
140
|
async function initializeFeishu(
|
|
@@ -130,7 +150,7 @@ async function initializeFeishu(
|
|
|
130
150
|
}
|
|
131
151
|
|
|
132
152
|
const controller = new AbortController();
|
|
133
|
-
const onInterrupt = (): void => controller.abort(new Error("
|
|
153
|
+
const onInterrupt = (): void => controller.abort(new Error("Initialization was cancelled."));
|
|
134
154
|
process.once("SIGINT", onInterrupt);
|
|
135
155
|
process.once("SIGTERM", onInterrupt);
|
|
136
156
|
try {
|
|
@@ -140,11 +160,12 @@ async function initializeFeishu(
|
|
|
140
160
|
credentials = {
|
|
141
161
|
appId: existing.appId!,
|
|
142
162
|
appSecret: existing.appSecret!,
|
|
163
|
+
userOpenId: existing.userOpenId,
|
|
143
164
|
};
|
|
144
165
|
status = "existing";
|
|
145
166
|
} else {
|
|
146
167
|
if (existing.status === "incomplete" && !options.json) {
|
|
147
|
-
process.stdout.write("\n
|
|
168
|
+
process.stdout.write("\nIncomplete Lark credentials were found. A new bot will be created.\n");
|
|
148
169
|
}
|
|
149
170
|
credentials = await registerFeishuApp({
|
|
150
171
|
signal: controller.signal,
|
|
@@ -154,7 +175,7 @@ async function initializeFeishu(
|
|
|
154
175
|
status = "created";
|
|
155
176
|
}
|
|
156
177
|
|
|
157
|
-
if (!options.json) process.stdout.write("\n
|
|
178
|
+
if (!options.json) process.stdout.write("\nChecking Lark app permissions, events, and callbacks...\n");
|
|
158
179
|
const configuration = await ensureFeishuAppConfiguration(credentials, {
|
|
159
180
|
signal: controller.signal,
|
|
160
181
|
onVerification: (challenge) => printFeishuConfigurationVerification(challenge, options.json),
|
|
@@ -181,16 +202,16 @@ function skillsCommand(input: string[]): void {
|
|
|
181
202
|
const result = registry.install();
|
|
182
203
|
if (json) printJson(result);
|
|
183
204
|
else process.stdout.write(result.updated
|
|
184
|
-
?
|
|
185
|
-
: `Agent Bot Skill
|
|
205
|
+
? `Installed the Agent Bot Skill: ${result.status.targetPath}\n`
|
|
206
|
+
: `The Agent Bot Skill is already up to date: ${result.status.targetPath}\n`);
|
|
186
207
|
return;
|
|
187
208
|
}
|
|
188
209
|
if (action === "uninstall" || action === "unregister") {
|
|
189
210
|
const removed = registry.uninstall();
|
|
190
211
|
if (json) printJson({ removed, targetPath: registry.targetPath });
|
|
191
212
|
else process.stdout.write(removed
|
|
192
|
-
?
|
|
193
|
-
: `Agent Bot Skill
|
|
213
|
+
? `Uninstalled the Agent Bot Skill: ${registry.targetPath}\n`
|
|
214
|
+
: `The Agent Bot Skill is not installed: ${registry.targetPath}\n`);
|
|
194
215
|
return;
|
|
195
216
|
}
|
|
196
217
|
if (action === "status") {
|
|
@@ -203,13 +224,13 @@ function skillsCommand(input: string[]): void {
|
|
|
203
224
|
const paths = { sourcePath: registry.sourcePath, skillsRoot: registry.skillsRoot, targetPath: registry.targetPath };
|
|
204
225
|
if (json) printJson(paths);
|
|
205
226
|
else {
|
|
206
|
-
process.stdout.write(
|
|
207
|
-
process.stdout.write(
|
|
208
|
-
process.stdout.write(
|
|
227
|
+
process.stdout.write(`Built-in Skill: ${paths.sourcePath}\n`);
|
|
228
|
+
process.stdout.write(`System Skills directory: ${paths.skillsRoot}\n`);
|
|
229
|
+
process.stdout.write(`Installation path: ${paths.targetPath}\n`);
|
|
209
230
|
}
|
|
210
231
|
return;
|
|
211
232
|
}
|
|
212
|
-
throw new Error(
|
|
233
|
+
throw new Error(`Unknown skills command: ${action}`);
|
|
213
234
|
}
|
|
214
235
|
|
|
215
236
|
async function consoleCommand(input: string[]): Promise<void> {
|
|
@@ -217,10 +238,17 @@ async function consoleCommand(input: string[]): Promise<void> {
|
|
|
217
238
|
const endpoint = controlEndpoint(config.storage.sqlitePath);
|
|
218
239
|
const force = input.includes("--force");
|
|
219
240
|
if (!force && await isServerReachable(endpoint)) {
|
|
220
|
-
throw new Error(
|
|
241
|
+
throw new Error(
|
|
242
|
+
"The Agent Bot server is running. Stop it first to avoid concurrent task-state access, or explicitly use --force.",
|
|
243
|
+
);
|
|
221
244
|
}
|
|
222
245
|
const entry = fileURLToPath(new URL("./index.js", import.meta.url));
|
|
223
|
-
const
|
|
246
|
+
const reportDirectory = resolveSupervisorDiagnosticsPaths(config).crashReportDirectory;
|
|
247
|
+
prepareCrashReportDirectory(reportDirectory);
|
|
248
|
+
const result = spawnSync(process.execPath, [
|
|
249
|
+
...nodeDiagnosticReportArguments(reportDirectory),
|
|
250
|
+
entry,
|
|
251
|
+
], {
|
|
224
252
|
cwd: process.cwd(),
|
|
225
253
|
stdio: "inherit",
|
|
226
254
|
env: { ...process.env, AGENT_BOT_CONSOLE_ONLY: "1" },
|
|
@@ -237,57 +265,42 @@ async function serverCommand(input: string[]): Promise<void> {
|
|
|
237
265
|
try {
|
|
238
266
|
const response = await sendControlRequest(endpoint, { action: "health" }, 1_500);
|
|
239
267
|
ensureOk(response);
|
|
240
|
-
|
|
241
|
-
|
|
268
|
+
const status = withConfiguredFeishuAppId(response.data, config.feishu.appId);
|
|
269
|
+
if (rest.includes("--json")) printJson(status);
|
|
270
|
+
else process.stdout.write(formatServerStatus(status));
|
|
242
271
|
} catch {
|
|
243
|
-
|
|
244
|
-
|
|
272
|
+
const status = {
|
|
273
|
+
running: false,
|
|
274
|
+
feishuAppId: config.feishu.appId ?? null,
|
|
275
|
+
};
|
|
276
|
+
if (rest.includes("--json")) printJson(status);
|
|
277
|
+
else process.stdout.write(formatServerStatus(status));
|
|
245
278
|
process.exitCode = 3;
|
|
246
279
|
}
|
|
247
280
|
return;
|
|
248
281
|
}
|
|
249
282
|
if (action === "start") {
|
|
250
|
-
|
|
251
|
-
if (await isServerRunning(endpoint)) {
|
|
252
|
-
process.stdout.write("agent-bot server 已在运行。\n");
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
if (await isServerReachable(endpoint)) {
|
|
256
|
-
const running = await waitForServer(endpoint, 45_000);
|
|
257
|
-
if (!running) throw new Error("agent-bot server 已启动,但未能连接飞书机器人。请检查日志。");
|
|
258
|
-
process.stdout.write("agent-bot server 已启动。\n");
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
const entry = fileURLToPath(new URL("./supervisor.js", import.meta.url));
|
|
262
|
-
const child = spawn(process.execPath, [entry], {
|
|
263
|
-
cwd: process.cwd(),
|
|
264
|
-
detached: true,
|
|
265
|
-
windowsHide: true,
|
|
266
|
-
stdio: "ignore",
|
|
267
|
-
env: { ...process.env, AGENT_BOT_RESTART_REASON: "通过 agent-bot CLI 启动" },
|
|
268
|
-
});
|
|
269
|
-
child.unref();
|
|
270
|
-
const running = await waitForServer(endpoint, 45_000);
|
|
271
|
-
if (!running) throw new Error("已启动 Supervisor,但 server 未在 45 秒内连接飞书机器人。请检查日志。");
|
|
272
|
-
process.stdout.write("agent-bot server 已启动。\n");
|
|
283
|
+
printServerStartResult(await startServer(config));
|
|
273
284
|
return;
|
|
274
285
|
}
|
|
275
286
|
if (action === "stop") {
|
|
276
|
-
|
|
287
|
+
ensureOk(await sendControlRequest(endpoint, { action: "server_stop" }));
|
|
288
|
+
process.stdout.write("Agent Bot server stop requested.\n");
|
|
277
289
|
return;
|
|
278
290
|
}
|
|
279
291
|
if (action === "restart") {
|
|
280
292
|
const immediate = rest.includes("--immediate") || rest.includes("--force");
|
|
281
293
|
const reason = optionValue(rest, "--reason")
|
|
282
|
-
?? (immediate ? "通过
|
|
283
|
-
|
|
294
|
+
?? (immediate ? "通过 Agent Bot CLI 立即重启" : "通过 Agent Bot CLI 安全重启");
|
|
295
|
+
ensureOk(await sendControlRequest(endpoint, {
|
|
284
296
|
action: "server_restart",
|
|
285
297
|
mode: immediate ? "immediate" : "safe",
|
|
286
298
|
reason,
|
|
287
299
|
}));
|
|
300
|
+
process.stdout.write(immediate ? "Immediate restart requested.\n" : "Safe restart requested.\n");
|
|
288
301
|
return;
|
|
289
302
|
}
|
|
290
|
-
throw new Error(
|
|
303
|
+
throw new Error(`Unknown server command: ${action}`);
|
|
291
304
|
}
|
|
292
305
|
|
|
293
306
|
async function taskCommand(input: string[]): Promise<void> {
|
|
@@ -298,16 +311,17 @@ async function taskCommand(input: string[]): Promise<void> {
|
|
|
298
311
|
const allSessions = store.listAllSessions();
|
|
299
312
|
if (action === "prompt" || action === "send") {
|
|
300
313
|
const [reference, ...promptParts] = rest;
|
|
301
|
-
if (!reference) throw new Error(`task ${action}
|
|
314
|
+
if (!reference) throw new Error(`task ${action} requires a task number or task ID.`);
|
|
302
315
|
const text = promptParts.join(" ").trim();
|
|
303
|
-
if (!text) throw new Error(`task ${action}
|
|
316
|
+
if (!text) throw new Error(`task ${action} requires a Prompt.`);
|
|
304
317
|
const session = resolveTask(allSessions, reference);
|
|
305
318
|
const endpoint = controlEndpoint(config.storage.sqlitePath);
|
|
306
|
-
|
|
319
|
+
ensureOk(await sendControlRequest(endpoint, {
|
|
307
320
|
action: "task_prompt",
|
|
308
321
|
localSessionId: session.localSessionId,
|
|
309
322
|
text,
|
|
310
323
|
}, 60_000));
|
|
324
|
+
process.stdout.write("Prompt submitted.\n");
|
|
311
325
|
return;
|
|
312
326
|
}
|
|
313
327
|
const sessions = filterSessions(allSessions, rest);
|
|
@@ -317,7 +331,7 @@ async function taskCommand(input: string[]): Promise<void> {
|
|
|
317
331
|
return;
|
|
318
332
|
}
|
|
319
333
|
const reference = rest.find((value) => !value.startsWith("--"));
|
|
320
|
-
if (!reference) throw new Error(`task ${action}
|
|
334
|
+
if (!reference) throw new Error(`task ${action} requires a task number or task ID.`);
|
|
321
335
|
const session = resolveTask(sessions, reference);
|
|
322
336
|
if (action === "chat") {
|
|
323
337
|
const route = taskChatRoute(session);
|
|
@@ -352,7 +366,8 @@ async function taskCommand(input: string[]): Promise<void> {
|
|
|
352
366
|
}
|
|
353
367
|
const endpoint = controlEndpoint(config.storage.sqlitePath);
|
|
354
368
|
if (action === "stop") {
|
|
355
|
-
|
|
369
|
+
ensureOk(await sendControlRequest(endpoint, { action: "task_stop", localSessionId: session.localSessionId }));
|
|
370
|
+
process.stdout.write("Task stop requested.\n");
|
|
356
371
|
return;
|
|
357
372
|
}
|
|
358
373
|
if (action === "title") {
|
|
@@ -360,30 +375,21 @@ async function taskCommand(input: string[]): Promise<void> {
|
|
|
360
375
|
const titleArgs = rest.slice(referenceIndex + 1);
|
|
361
376
|
const optionIndex = titleArgs.findIndex((value) => value.startsWith("--"));
|
|
362
377
|
const title = titleArgs.slice(0, optionIndex < 0 ? undefined : optionIndex).join(" ").trim();
|
|
363
|
-
if (!title) throw new Error("task title
|
|
364
|
-
|
|
378
|
+
if (!title) throw new Error("task title requires a new title.");
|
|
379
|
+
ensureOk(await sendControlRequest(endpoint, {
|
|
365
380
|
action: "task_title",
|
|
366
381
|
localSessionId: session.localSessionId,
|
|
367
382
|
title,
|
|
368
383
|
}));
|
|
384
|
+
process.stdout.write("Task title updated.\n");
|
|
369
385
|
return;
|
|
370
386
|
}
|
|
371
|
-
throw new Error(
|
|
387
|
+
throw new Error(`Unknown task command: ${action}`);
|
|
372
388
|
} finally {
|
|
373
389
|
store.close();
|
|
374
390
|
}
|
|
375
391
|
}
|
|
376
392
|
|
|
377
|
-
function parseGlobalOptions(input: string[]): { configPath?: string; args: string[] } {
|
|
378
|
-
const args = [...input];
|
|
379
|
-
const configIndex = args.indexOf("--config");
|
|
380
|
-
if (configIndex < 0) return { args };
|
|
381
|
-
const configPath = args[configIndex + 1];
|
|
382
|
-
if (!configPath) throw new Error("--config 需要配置文件路径。");
|
|
383
|
-
args.splice(configIndex, 2);
|
|
384
|
-
return { configPath, args };
|
|
385
|
-
}
|
|
386
|
-
|
|
387
393
|
function filterSessions(sessions: SessionRecord[], args: string[]): SessionRecord[] {
|
|
388
394
|
const context = optionValue(args, "--context");
|
|
389
395
|
const status = optionValue(args, "--status");
|
|
@@ -396,7 +402,7 @@ function resolveTask(sessions: SessionRecord[], reference: string): SessionRecor
|
|
|
396
402
|
if (/^\d+$/.test(reference)) {
|
|
397
403
|
const index = Number(reference) - 1;
|
|
398
404
|
const session = sessions[index];
|
|
399
|
-
if (!session) throw new Error(
|
|
405
|
+
if (!session) throw new Error(`Task number is out of range: ${reference}`);
|
|
400
406
|
return session;
|
|
401
407
|
}
|
|
402
408
|
const exact = sessions.find((session) =>
|
|
@@ -407,93 +413,75 @@ function resolveTask(sessions: SessionRecord[], reference: string): SessionRecor
|
|
|
407
413
|
|| session.remoteSessionId?.startsWith(reference)
|
|
408
414
|
|| session.acpSessionId?.startsWith(reference));
|
|
409
415
|
if (matches.length === 1) return matches[0]!;
|
|
410
|
-
if (matches.length > 1) throw new Error(
|
|
411
|
-
throw new Error(
|
|
416
|
+
if (matches.length > 1) throw new Error(`Task ID prefix is ambiguous: ${reference}`);
|
|
417
|
+
throw new Error(`Task not found: ${reference}`);
|
|
412
418
|
}
|
|
413
419
|
|
|
414
420
|
function printTaskList(sessions: SessionRecord[]): void {
|
|
415
421
|
if (sessions.length === 0) {
|
|
416
|
-
process.stdout.write("
|
|
422
|
+
process.stdout.write("No tasks.\n");
|
|
417
423
|
return;
|
|
418
424
|
}
|
|
419
425
|
for (const [index, session] of sessions.entries()) {
|
|
420
426
|
const id = session.remoteSessionId ?? session.acpSessionId ?? session.localSessionId;
|
|
421
|
-
process.stdout.write(`${index + 1}. [${session.status}/${session.lastTurnStatus ?? "-"}] ${session.title ?? "
|
|
427
|
+
process.stdout.write(`${index + 1}. [${session.status}/${session.lastTurnStatus ?? "-"}] ${session.title ?? "Untitled task"}\n`);
|
|
422
428
|
process.stdout.write(` ${id} · ${session.contextKey} · ${session.updatedAt}\n`);
|
|
423
429
|
}
|
|
424
430
|
}
|
|
425
431
|
|
|
426
432
|
function printTaskStatus(session: SessionRecord, snapshot: unknown, remote?: TaskStatusControlData["remote"]): void {
|
|
427
|
-
process.stdout.write(
|
|
428
|
-
process.stdout.write(
|
|
429
|
-
process.stdout.write(
|
|
430
|
-
process.stdout.write(
|
|
431
|
-
process.stdout.write(
|
|
432
|
-
process.stdout.write(
|
|
433
|
-
process.stdout.write(
|
|
433
|
+
process.stdout.write(`Title: ${session.title ?? "Untitled task"}\n`);
|
|
434
|
+
process.stdout.write(`Task ID: ${session.remoteSessionId ?? session.acpSessionId ?? session.localSessionId}\n`);
|
|
435
|
+
process.stdout.write(`Local ID: ${session.localSessionId}\n`);
|
|
436
|
+
process.stdout.write(`Context: ${session.contextKey}\n`);
|
|
437
|
+
process.stdout.write(`Status: ${session.status} / ${session.lastTurnStatus ?? "-"}\n`);
|
|
438
|
+
process.stdout.write(`Directory: ${session.cwd}\n`);
|
|
439
|
+
process.stdout.write(`Last turn: ${session.lastTurnId ?? "-"}\n`);
|
|
434
440
|
let displayedFinalResponse: string | undefined;
|
|
435
441
|
if (snapshot && typeof snapshot === "object") {
|
|
436
442
|
const state = snapshot as Record<string, unknown>;
|
|
437
|
-
if (typeof state.durationMs === "number") process.stdout.write(
|
|
443
|
+
if (typeof state.durationMs === "number") process.stdout.write(`Duration: ${Math.round(state.durationMs / 1_000)}s\n`);
|
|
438
444
|
if (typeof state.totalTokens === "number") process.stdout.write(`Tokens:${state.totalTokens}\n`);
|
|
439
|
-
if (typeof state.totalToolCount === "number") process.stdout.write(
|
|
445
|
+
if (typeof state.totalToolCount === "number") process.stdout.write(`Tool calls: ${state.totalToolCount}\n`);
|
|
440
446
|
if (typeof state.finalResponse === "string" && state.finalResponse.trim()) displayedFinalResponse = state.finalResponse.trim();
|
|
441
447
|
}
|
|
442
|
-
if (!snapshot && remote?.lastTurnToolCount !== undefined) process.stdout.write(
|
|
448
|
+
if (!snapshot && remote?.lastTurnToolCount !== undefined) process.stdout.write(`Tool calls: ${remote.lastTurnToolCount}\n`);
|
|
443
449
|
displayedFinalResponse = remote?.finalResponse?.trim() || displayedFinalResponse;
|
|
444
|
-
if (displayedFinalResponse) process.stdout.write(
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
function printServerStatus(data: unknown): void {
|
|
448
|
-
const value = data && typeof data === "object" ? data as Record<string, unknown> : {};
|
|
449
|
-
const activity = value.activity && typeof value.activity === "object"
|
|
450
|
-
? value.activity as Record<string, unknown>
|
|
451
|
-
: {};
|
|
452
|
-
process.stdout.write(
|
|
453
|
-
value.ready === false
|
|
454
|
-
? "agent-bot server:启动中(正在连接飞书机器人)\n"
|
|
455
|
-
: "agent-bot server:运行中\n",
|
|
456
|
-
);
|
|
457
|
-
process.stdout.write(`PID:${value.pid ?? "-"}\n`);
|
|
458
|
-
process.stdout.write(`启动时间:${value.startedAt ?? "-"}\n`);
|
|
459
|
-
process.stdout.write(`Supervisor:${value.supervised ? "已启用" : "未启用"}\n`);
|
|
460
|
-
process.stdout.write(`运行任务:${activity.runningSessions ?? 0}\n`);
|
|
461
|
-
process.stdout.write(`待投递结果:${activity.pendingFinalDeliveries ?? 0}\n`);
|
|
462
|
-
process.stdout.write(`安全重启:${value.safeRestartScheduled ? `等待中(${value.safeRestartReason ?? "未注明原因"})` : "未安排"}\n`);
|
|
450
|
+
if (displayedFinalResponse) process.stdout.write(`Final response:\n${displayedFinalResponse}\n`);
|
|
463
451
|
}
|
|
464
452
|
|
|
465
453
|
function printSkillStatus(status: SkillRegistrationStatus): void {
|
|
466
|
-
process.stdout.write(`Agent Bot Skill
|
|
467
|
-
process.stdout.write(
|
|
468
|
-
process.stdout.write(
|
|
454
|
+
process.stdout.write(`Agent Bot Skill: ${status.registered ? "installed" : "not installed"}\n`);
|
|
455
|
+
process.stdout.write(`System directory: ${status.skillsRoot}\n`);
|
|
456
|
+
process.stdout.write(`Installation path: ${status.targetPath}\n`);
|
|
469
457
|
if (status.registered) {
|
|
470
|
-
process.stdout.write(
|
|
471
|
-
process.stdout.write(
|
|
458
|
+
process.stdout.write(`Ownership: ${status.managed ? "managed by Agent Bot" : "external directory (will not be modified or removed)"}\n`);
|
|
459
|
+
process.stdout.write(`Version: ${status.upToDate ? "up to date" : "update required"}\n`);
|
|
472
460
|
}
|
|
473
461
|
}
|
|
474
462
|
|
|
475
463
|
function printInitializationPaths(result: InitializationResult): void {
|
|
476
|
-
process.stdout.write("
|
|
477
|
-
process.stdout.write(
|
|
478
|
-
process.stdout.write(
|
|
479
|
-
process.stdout.write(
|
|
480
|
-
process.stdout.write(
|
|
481
|
-
process.stdout.write(
|
|
464
|
+
process.stdout.write("Preparing the Agent Bot user environment...\n");
|
|
465
|
+
process.stdout.write(`Home directory: ${result.home.path} (${initializationStatusLabel(result.home.status)})\n`);
|
|
466
|
+
process.stdout.write(`Config file: ${result.config.path} (${initializationStatusLabel(result.config.status)})\n`);
|
|
467
|
+
process.stdout.write(`Environment file: ${result.env.path} (${initializationStatusLabel(result.env.status)})\n`);
|
|
468
|
+
process.stdout.write(`Data directory: ${result.data.path} (${initializationStatusLabel(result.data.status)})\n`);
|
|
469
|
+
process.stdout.write(`Log directory: ${result.logs.path} (${initializationStatusLabel(result.logs.status)})\n`);
|
|
482
470
|
}
|
|
483
471
|
|
|
484
|
-
function printInitializationResult(result: InitCommandResult): void {
|
|
485
|
-
process.stdout.write("\nAgent Bot
|
|
472
|
+
function printInitializationResult(result: Omit<InitCommandResult, "server">): void {
|
|
473
|
+
process.stdout.write("\nAgent Bot initialization completed.\n");
|
|
486
474
|
if (result.feishu.status === "created") {
|
|
487
|
-
process.stdout.write(
|
|
475
|
+
process.stdout.write(`Lark app: created and credentials saved (${result.feishu.appId})\n`);
|
|
488
476
|
} else if (result.feishu.status === "existing") {
|
|
489
|
-
process.stdout.write(
|
|
477
|
+
process.stdout.write(`Lark app: already configured and unchanged (${result.feishu.appId})\n`);
|
|
490
478
|
} else {
|
|
491
|
-
process.stdout.write("
|
|
479
|
+
process.stdout.write("Lark app: skipped; use Console mode or run init again later.\n");
|
|
492
480
|
}
|
|
493
481
|
if (result.feishu.configuration?.status === "updated") {
|
|
494
482
|
const added = result.feishu.configuration.added;
|
|
495
483
|
process.stdout.write(
|
|
496
|
-
|
|
484
|
+
`Lark configuration: added ${added.scopes.length} scopes, ${added.events.length} events, and ${added.callbacks.length} callbacks\n`,
|
|
497
485
|
);
|
|
498
486
|
} else if (result.feishu.configuration?.status === "partial") {
|
|
499
487
|
const configuration = result.feishu.configuration;
|
|
@@ -501,45 +489,60 @@ function printInitializationResult(result: InitCommandResult): void {
|
|
|
501
489
|
configuration.added.scopes.length + configuration.added.events.length + configuration.added.callbacks.length;
|
|
502
490
|
if (addedCount > 0) {
|
|
503
491
|
process.stdout.write(
|
|
504
|
-
|
|
492
|
+
`Lark configuration: core capabilities are ready; added ${configuration.added.scopes.length} scopes, ${configuration.added.events.length} events, and ${configuration.added.callbacks.length} callbacks\n`,
|
|
505
493
|
);
|
|
506
494
|
} else {
|
|
507
|
-
process.stdout.write("
|
|
495
|
+
process.stdout.write("Lark configuration: core capabilities are ready\n");
|
|
508
496
|
}
|
|
509
497
|
printOptionalFeishuConfigurationWarnings(configuration.remaining);
|
|
510
498
|
} else if (result.feishu.configuration?.status === "ready") {
|
|
511
|
-
process.stdout.write("
|
|
499
|
+
process.stdout.write("Lark configuration: scopes, events, and callbacks are ready\n");
|
|
500
|
+
}
|
|
501
|
+
process.stdout.write(`Config file: ${result.config.path}\n`);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function printInitializationServerResult(result: InitCommandResult["server"]): void {
|
|
505
|
+
if (result.status === "skipped") {
|
|
506
|
+
process.stdout.write("Agent Bot server: skipped (Lark is not configured; Console mode is available)\n");
|
|
507
|
+
return;
|
|
512
508
|
}
|
|
513
|
-
|
|
514
|
-
|
|
509
|
+
printServerStartResult(result);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function printServerStartResult(result: ServerStartResult): void {
|
|
513
|
+
process.stdout.write(
|
|
514
|
+
result.status === "already-running"
|
|
515
|
+
? "Agent Bot server is already running.\n"
|
|
516
|
+
: "Agent Bot server started.\n",
|
|
517
|
+
);
|
|
515
518
|
}
|
|
516
519
|
|
|
517
520
|
function printFeishuVerification(challenge: FeishuAppRegistrationChallenge, json: boolean): void {
|
|
518
|
-
process.stderr.write("\n
|
|
521
|
+
process.stderr.write("\nScan with Lark or open the link below in a browser to create the bot app:\n\n");
|
|
519
522
|
process.stderr.write(`${challenge.verificationUrl}\n\n`);
|
|
520
523
|
if (!json) {
|
|
521
524
|
qrcode.generate(challenge.verificationUrl, { small: true }, (output) => {
|
|
522
525
|
process.stderr.write(`${output}\n`);
|
|
523
526
|
});
|
|
524
527
|
}
|
|
525
|
-
process.stderr.write(
|
|
528
|
+
process.stderr.write(`The link expires in about ${Math.ceil(challenge.expiresIn / 60)} minutes. Waiting for confirmation...\n`);
|
|
526
529
|
}
|
|
527
530
|
|
|
528
531
|
function printFeishuConfigurationVerification(
|
|
529
532
|
challenge: FeishuConfigurationChallenge,
|
|
530
533
|
json: boolean,
|
|
531
534
|
): void {
|
|
532
|
-
process.stderr.write("\n
|
|
535
|
+
process.stderr.write("\nThe Lark app is missing the following configuration:\n");
|
|
533
536
|
if (challenge.missing.scopes.length > 0) {
|
|
534
|
-
process.stderr.write(
|
|
537
|
+
process.stderr.write(`Scopes: ${challenge.missing.scopes.join(", ")}\n`);
|
|
535
538
|
}
|
|
536
539
|
if (challenge.missing.events.length > 0) {
|
|
537
|
-
process.stderr.write(
|
|
540
|
+
process.stderr.write(`Events: ${challenge.missing.events.join(", ")}\n`);
|
|
538
541
|
}
|
|
539
542
|
if (challenge.missing.callbacks.length > 0) {
|
|
540
|
-
process.stderr.write(
|
|
543
|
+
process.stderr.write(`Callbacks: ${challenge.missing.callbacks.join(", ")}\n`);
|
|
541
544
|
}
|
|
542
|
-
process.stderr.write("\n
|
|
545
|
+
process.stderr.write("\nScan with Lark or open the link below in a browser to complete the configuration:\n\n");
|
|
543
546
|
process.stderr.write(`${challenge.verificationUrl}\n\n`);
|
|
544
547
|
if (!json) {
|
|
545
548
|
qrcode.generate(challenge.verificationUrl, { small: true }, (output) => {
|
|
@@ -547,25 +550,25 @@ function printFeishuConfigurationVerification(
|
|
|
547
550
|
});
|
|
548
551
|
}
|
|
549
552
|
if (challenge.blocking) {
|
|
550
|
-
process.stderr.write("
|
|
553
|
+
process.stderr.write("Waiting for the core scopes and message event to become active...\n");
|
|
551
554
|
} else {
|
|
552
|
-
process.stderr.write("
|
|
555
|
+
process.stderr.write("These optional items do not block basic messaging. Initialization will continue; grant them later using the link.\n");
|
|
553
556
|
}
|
|
554
557
|
}
|
|
555
558
|
|
|
556
559
|
function printOptionalFeishuConfigurationWarnings(missing: FeishuConfigurationChallenge["missing"]): void {
|
|
557
|
-
process.stdout.write("
|
|
560
|
+
process.stdout.write("Warning: some optional configuration is not active, so some features may be unavailable.\n");
|
|
558
561
|
if (missing.scopes.length > 0) {
|
|
559
|
-
process.stdout.write(
|
|
562
|
+
process.stdout.write(`Inactive scopes: ${missing.scopes.join(", ")}\n`);
|
|
560
563
|
}
|
|
561
564
|
if (missing.events.length > 0) {
|
|
562
|
-
process.stdout.write(
|
|
565
|
+
process.stdout.write(`Inactive events: ${missing.events.join(", ")}\n`);
|
|
563
566
|
}
|
|
564
567
|
if (missing.callbacks.length > 0) {
|
|
565
|
-
process.stdout.write(
|
|
568
|
+
process.stdout.write(`Inactive callbacks: ${missing.callbacks.join(", ")}\n`);
|
|
566
569
|
}
|
|
567
570
|
for (const feature of optionalFeishuFeatureWarnings(missing)) {
|
|
568
|
-
process.stdout.write(
|
|
571
|
+
process.stdout.write(`Affected feature: ${feature}\n`);
|
|
569
572
|
}
|
|
570
573
|
}
|
|
571
574
|
|
|
@@ -575,41 +578,40 @@ function optionalFeishuFeatureWarnings(missing: FeishuConfigurationChallenge["mi
|
|
|
575
578
|
const callbacks = new Set(missing.callbacks);
|
|
576
579
|
const warnings: string[] = [];
|
|
577
580
|
if (scopes.has("im:chat:create")) {
|
|
578
|
-
warnings.push("
|
|
581
|
+
warnings.push("creating Lark groups with /newgroup and /forkgroup");
|
|
579
582
|
}
|
|
580
583
|
if (scopes.has("im:chat:read") || events.has("im.chat.updated_v1")) {
|
|
581
|
-
warnings.push("
|
|
584
|
+
warnings.push("synchronizing Agent Bot task titles after Lark group renames");
|
|
582
585
|
}
|
|
583
586
|
if (scopes.has("im:message.reactions:write_only")) {
|
|
584
|
-
warnings.push("
|
|
587
|
+
warnings.push("showing message-processing status with reactions");
|
|
585
588
|
}
|
|
586
589
|
if (scopes.has("im:message:readonly")) {
|
|
587
|
-
warnings.push("
|
|
590
|
+
warnings.push("reading images from user messages");
|
|
588
591
|
}
|
|
589
592
|
if (scopes.has("im:resource")) {
|
|
590
|
-
warnings.push("
|
|
593
|
+
warnings.push("uploading images, sending local images, or setting group avatars");
|
|
591
594
|
}
|
|
592
595
|
if (scopes.has("im:message:update")) {
|
|
593
|
-
warnings.push("
|
|
596
|
+
warnings.push("updating sent progress cards");
|
|
594
597
|
}
|
|
595
598
|
if (callbacks.has("card.action.trigger")) {
|
|
596
|
-
warnings.push("
|
|
599
|
+
warnings.push("card buttons and interactive actions");
|
|
597
600
|
}
|
|
598
601
|
return warnings;
|
|
599
602
|
}
|
|
600
603
|
|
|
601
604
|
function initializationStatusLabel(status: InitializationStatus): string {
|
|
602
|
-
if (status === "created") return "
|
|
603
|
-
return "
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
function printResponse(response: ControlResponse): void {
|
|
607
|
-
ensureOk(response);
|
|
608
|
-
process.stdout.write(`${response.message ?? "操作成功。"}\n`);
|
|
605
|
+
if (status === "created") return "created";
|
|
606
|
+
return "already exists; unchanged";
|
|
609
607
|
}
|
|
610
608
|
|
|
611
609
|
function ensureOk(response: ControlResponse): void {
|
|
612
|
-
if (
|
|
610
|
+
if (response.ok) return;
|
|
611
|
+
const message = response.message?.trim();
|
|
612
|
+
throw new Error(message && !/[一-龥]/u.test(message)
|
|
613
|
+
? message
|
|
614
|
+
: "Agent Bot control operation failed. Check the server logs for details.");
|
|
613
615
|
}
|
|
614
616
|
|
|
615
617
|
function optionValue(args: string[], name: string): string | undefined {
|
|
@@ -617,15 +619,6 @@ function optionValue(args: string[], name: string): string | undefined {
|
|
|
617
619
|
return index >= 0 ? args[index + 1] : undefined;
|
|
618
620
|
}
|
|
619
621
|
|
|
620
|
-
async function waitForServer(endpoint: string, timeoutMs: number): Promise<boolean> {
|
|
621
|
-
const deadline = Date.now() + timeoutMs;
|
|
622
|
-
while (Date.now() < deadline) {
|
|
623
|
-
if (await isServerRunning(endpoint)) return true;
|
|
624
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
625
|
-
}
|
|
626
|
-
return false;
|
|
627
|
-
}
|
|
628
|
-
|
|
629
622
|
function printJson(value: unknown): void {
|
|
630
623
|
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
631
624
|
}
|