@keyou007/agent-bot 0.1.9 → 0.1.11
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 +23 -0
- package/README.md +22 -10
- package/README.zh.md +22 -10
- package/assets/agent-bot-logo.png +0 -0
- package/dist/acp/AcpSessionManager.js +78 -46
- package/dist/acp/AcpSessionManager.js.map +1 -1
- package/dist/cli/FeishuAppConfiguration.js +84 -22
- package/dist/cli/FeishuAppConfiguration.js.map +1 -1
- package/dist/cli/FeishuAppRegistration.js +12 -9
- package/dist/cli/FeishuAppRegistration.js.map +1 -1
- package/dist/cli/Initializer.js +7 -6
- package/dist/cli/Initializer.js.map +1 -1
- package/dist/cli/LocalControlClient.js +3 -2
- package/dist/cli/LocalControlClient.js.map +1 -1
- package/dist/cli/LocalControlServer.js +2 -1
- package/dist/cli/LocalControlServer.js.map +1 -1
- package/dist/cli/OptionalAuthorizationSkip.js +27 -6
- package/dist/cli/OptionalAuthorizationSkip.js.map +1 -1
- package/dist/cli/ServerStarter.js +12 -3
- package/dist/cli/ServerStarter.js.map +1 -1
- package/dist/cli/SkillRegistry.js +5 -4
- package/dist/cli/SkillRegistry.js.map +1 -1
- package/dist/cli/VerificationOutput.js +2 -1
- package/dist/cli/VerificationOutput.js.map +1 -1
- package/dist/cli/help.js +48 -1
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/i18n.js +27 -0
- package/dist/cli/i18n.js.map +1 -0
- package/dist/cli/initOptions.js +5 -4
- package/dist/cli/initOptions.js.map +1 -1
- package/dist/cli/packageVersion.js +2 -1
- package/dist/cli/packageVersion.js.map +1 -1
- package/dist/cli/profile.js +4 -3
- package/dist/cli/profile.js.map +1 -1
- package/dist/cli/serverStatus.js +15 -14
- package/dist/cli/serverStatus.js.map +1 -1
- package/dist/cli/taskChatRoute.js +3 -2
- package/dist/cli/taskChatRoute.js.map +1 -1
- package/dist/cli.js +165 -107
- package/dist/cli.js.map +1 -1
- package/dist/codex/AppServerConnection.js +15 -1
- package/dist/codex/AppServerConnection.js.map +1 -1
- package/dist/codex/CodexLocalActivityDetector.js +4 -1
- package/dist/codex/CodexLocalActivityDetector.js.map +1 -1
- package/dist/codex/CodexProcessManager.js +1 -0
- package/dist/codex/CodexProcessManager.js.map +1 -1
- package/dist/codex/CodexRuntime.js +77 -2
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/commands/CommandRouter.js +108 -47
- package/dist/commands/CommandRouter.js.map +1 -1
- package/dist/console/ConsoleTurnPresenter.js +1 -1
- package/dist/console/ConsoleTurnPresenter.js.map +1 -1
- package/dist/feishu/CardRenderer.js +345 -11
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuMessageClient.js +51 -5
- package/dist/feishu/FeishuMessageClient.js.map +1 -1
- package/dist/feishu/FeishuTurnPresenter.js +8 -3
- package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
- package/dist/index.js +18 -17
- package/dist/index.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +595 -205
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/runtime/AcpRuntimeAdapter.js +1 -0
- package/dist/runtime/AcpRuntimeAdapter.js.map +1 -1
- package/dist/runtime/AgentRuntimeRegistry.js +7 -4
- package/dist/runtime/AgentRuntimeRegistry.js.map +1 -1
- package/dist/runtime/createAgentRuntimeRegistry.js +22 -0
- package/dist/runtime/createAgentRuntimeRegistry.js.map +1 -0
- package/dist/startup/SessionMetadataHydrator.js +2 -2
- package/dist/startup/SessionMetadataHydrator.js.map +1 -1
- package/dist/startup/StartupNotifier.js +1 -0
- package/dist/startup/StartupNotifier.js.map +1 -1
- package/dist/state/StateStore.js +21 -15
- package/dist/state/StateStore.js.map +1 -1
- package/dist/supervision/SafeRestartScheduler.js +5 -0
- package/dist/supervision/SafeRestartScheduler.js.map +1 -1
- package/docs/technical-reference.md +27 -6
- package/docs/technical-reference.zh.md +27 -6
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
- package/skills/agent-bot/SKILL.md +19 -9
- package/src/acp/AcpSessionManager.ts +73 -54
- package/src/cli/FeishuAppConfiguration.ts +130 -18
- package/src/cli/FeishuAppRegistration.ts +24 -9
- package/src/cli/Initializer.ts +25 -6
- package/src/cli/LocalControlClient.ts +9 -2
- package/src/cli/LocalControlServer.ts +2 -1
- package/src/cli/OptionalAuthorizationSkip.ts +51 -6
- package/src/cli/ServerStarter.ts +20 -3
- package/src/cli/SkillRegistry.ts +17 -4
- package/src/cli/VerificationOutput.ts +4 -1
- package/src/cli/help.ts +50 -1
- package/src/cli/i18n.ts +38 -0
- package/src/cli/initOptions.ts +17 -4
- package/src/cli/packageVersion.ts +5 -1
- package/src/cli/profile.ts +13 -3
- package/src/cli/serverStatus.ts +19 -14
- package/src/cli/taskChatRoute.ts +9 -2
- package/src/cli.ts +290 -103
- package/src/codex/AppServerConnection.ts +21 -1
- package/src/codex/CodexLocalActivityDetector.ts +8 -1
- package/src/codex/CodexProcessManager.ts +1 -0
- package/src/codex/CodexRuntime.ts +91 -2
- package/src/commands/CommandRouter.ts +123 -46
- package/src/commands/commandTypes.ts +6 -11
- package/src/console/ConsoleTurnPresenter.ts +1 -1
- package/src/feishu/CardRenderer.ts +418 -11
- package/src/feishu/FeishuMessageClient.ts +66 -3
- package/src/feishu/FeishuTurnPresenter.ts +7 -2
- package/src/index.ts +21 -22
- package/src/proxy/ProxySessionController.ts +712 -208
- package/src/runtime/AcpRuntimeAdapter.ts +1 -0
- package/src/runtime/AgentRuntimeRegistry.ts +7 -6
- package/src/runtime/createAgentRuntimeRegistry.ts +25 -0
- package/src/runtime/types.ts +18 -0
- package/src/startup/SessionMetadataHydrator.ts +2 -2
- package/src/startup/StartupNotifier.ts +1 -0
- package/src/state/StateStore.ts +29 -15
- package/src/supervision/SafeRestartScheduler.ts +5 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { gzipSync } from "node:zlib";
|
|
2
2
|
import type { FeishuAppCredentials } from "./FeishuAppRegistration.js";
|
|
3
|
+
import { cliText } from "./i18n.js";
|
|
3
4
|
|
|
4
5
|
const FEISHU_OPEN_BASE_URL = "https://open.feishu.cn";
|
|
5
6
|
const TENANT_TOKEN_PATH = "/open-apis/auth/v3/tenant_access_token/internal";
|
|
@@ -71,6 +72,7 @@ const CORE_FEISHU_SCOPES = new Set([
|
|
|
71
72
|
"im:message:send_as_bot",
|
|
72
73
|
]);
|
|
73
74
|
const CORE_FEISHU_EVENTS = new Set<string>(["im.message.receive_v1"]);
|
|
75
|
+
const MANUAL_FEISHU_SCOPES = new Set(["im:message.group_msg"]);
|
|
74
76
|
|
|
75
77
|
export interface FeishuAppConfiguration {
|
|
76
78
|
scopes: string[];
|
|
@@ -85,6 +87,7 @@ export interface MissingFeishuAppConfiguration {
|
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
export interface FeishuConfigurationChallenge {
|
|
90
|
+
kind: "launcher" | "manual_scope";
|
|
88
91
|
verificationUrl: string;
|
|
89
92
|
missing: MissingFeishuAppConfiguration;
|
|
90
93
|
blocking: boolean;
|
|
@@ -94,6 +97,7 @@ export interface EnsureFeishuAppConfigurationOptions {
|
|
|
94
97
|
fetch?: typeof globalThis.fetch;
|
|
95
98
|
sleep?: (milliseconds: number) => Promise<void>;
|
|
96
99
|
signal?: AbortSignal;
|
|
100
|
+
manualPermissionSkipSignal?: AbortSignal;
|
|
97
101
|
optionalSkipSignal?: AbortSignal;
|
|
98
102
|
pollIntervalMs?: number;
|
|
99
103
|
timeoutMs?: number;
|
|
@@ -157,15 +161,27 @@ export async function ensureFeishuAppConfiguration(
|
|
|
157
161
|
|
|
158
162
|
let remainingMs = timeoutMs;
|
|
159
163
|
let lastError: unknown;
|
|
164
|
+
let refreshImmediately = options.manualPermissionSkipSignal?.aborted === true;
|
|
160
165
|
while (remainingMs > 0) {
|
|
161
166
|
const waitMs = Math.min(pollIntervalMs, remainingMs);
|
|
162
|
-
|
|
163
|
-
|
|
167
|
+
if (!refreshImmediately) {
|
|
168
|
+
const waitResult = options.manualPermissionSkipSignal?.aborted
|
|
169
|
+
? await sleepWithAbort(sleep, waitMs, options.signal).then(() => "elapsed" as const)
|
|
170
|
+
: await sleepWithOptionalSkip(
|
|
171
|
+
sleep,
|
|
172
|
+
waitMs,
|
|
173
|
+
options.signal,
|
|
174
|
+
options.manualPermissionSkipSignal,
|
|
175
|
+
);
|
|
176
|
+
if (waitResult === "elapsed") remainingMs -= waitMs;
|
|
177
|
+
}
|
|
178
|
+
refreshImmediately = false;
|
|
164
179
|
|
|
165
180
|
try {
|
|
166
181
|
configuration = await readFeishuAppConfiguration(credentials, fetchImpl, options.signal);
|
|
167
182
|
const remaining = missingFeishuAppConfiguration(configuration);
|
|
168
|
-
|
|
183
|
+
const unresolvedCore = coreMissingFeishuAppConfiguration(remaining);
|
|
184
|
+
if (!hasBlockingCoreConfiguration(unresolvedCore, options.manualPermissionSkipSignal)) {
|
|
169
185
|
return completeOptionalFeishuConfiguration(
|
|
170
186
|
credentials,
|
|
171
187
|
configuration,
|
|
@@ -175,17 +191,40 @@ export async function ensureFeishuAppConfiguration(
|
|
|
175
191
|
pollIntervalMs,
|
|
176
192
|
optionalTimeoutMs,
|
|
177
193
|
options,
|
|
194
|
+
skippedManualScopes(coreMissing, options.manualPermissionSkipSignal),
|
|
178
195
|
);
|
|
179
196
|
}
|
|
180
197
|
lastError = undefined;
|
|
181
198
|
} catch (error) {
|
|
182
199
|
if (options.signal?.aborted) throw abortError(options.signal);
|
|
200
|
+
const knownRemaining = missingFeishuAppConfiguration(configuration);
|
|
201
|
+
if (!hasBlockingCoreConfiguration(
|
|
202
|
+
coreMissingFeishuAppConfiguration(knownRemaining),
|
|
203
|
+
options.manualPermissionSkipSignal,
|
|
204
|
+
)) {
|
|
205
|
+
return completeOptionalFeishuConfiguration(
|
|
206
|
+
credentials,
|
|
207
|
+
configuration,
|
|
208
|
+
missing,
|
|
209
|
+
fetchImpl,
|
|
210
|
+
sleep,
|
|
211
|
+
pollIntervalMs,
|
|
212
|
+
optionalTimeoutMs,
|
|
213
|
+
options,
|
|
214
|
+
skippedManualScopes(coreMissing, options.manualPermissionSkipSignal),
|
|
215
|
+
);
|
|
216
|
+
}
|
|
183
217
|
lastError = error;
|
|
184
218
|
}
|
|
185
219
|
}
|
|
186
220
|
|
|
187
|
-
const detail = lastError instanceof Error
|
|
188
|
-
|
|
221
|
+
const detail = lastError instanceof Error
|
|
222
|
+
? cliText(` Last check failed: ${lastError.message}`, ` 上次检查失败:${lastError.message}`)
|
|
223
|
+
: "";
|
|
224
|
+
throw new Error(cliText(
|
|
225
|
+
`Timed out waiting for the core Lark scopes and message event. Run agent-bot init again.${detail}`,
|
|
226
|
+
`等待飞书核心权限和消息事件超时。请重新运行 agent-bot init。${detail}`,
|
|
227
|
+
));
|
|
189
228
|
}
|
|
190
229
|
|
|
191
230
|
async function requestMissingFeishuConfiguration(
|
|
@@ -194,8 +233,28 @@ async function requestMissingFeishuConfiguration(
|
|
|
194
233
|
blocking: boolean,
|
|
195
234
|
onVerification: EnsureFeishuAppConfigurationOptions["onVerification"],
|
|
196
235
|
): Promise<void> {
|
|
197
|
-
const
|
|
198
|
-
|
|
236
|
+
const launcherMissing: MissingFeishuAppConfiguration = {
|
|
237
|
+
scopes: missing.scopes.filter((scope) => !MANUAL_FEISHU_SCOPES.has(scope)),
|
|
238
|
+
events: missing.events,
|
|
239
|
+
callbacks: missing.callbacks,
|
|
240
|
+
};
|
|
241
|
+
if (hasMissingConfiguration(launcherMissing)) {
|
|
242
|
+
await onVerification?.({
|
|
243
|
+
kind: "launcher",
|
|
244
|
+
verificationUrl: buildFeishuConfigurationUrl(appId, launcherMissing),
|
|
245
|
+
missing: launcherMissing,
|
|
246
|
+
blocking,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
for (const scope of missing.scopes.filter((value) => MANUAL_FEISHU_SCOPES.has(value))) {
|
|
251
|
+
await onVerification?.({
|
|
252
|
+
kind: "manual_scope",
|
|
253
|
+
verificationUrl: buildFeishuPermissionManagementUrl(appId, scope),
|
|
254
|
+
missing: { scopes: [scope], events: [], callbacks: [] },
|
|
255
|
+
blocking,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
199
258
|
}
|
|
200
259
|
|
|
201
260
|
async function completeOptionalFeishuConfiguration(
|
|
@@ -207,13 +266,15 @@ async function completeOptionalFeishuConfiguration(
|
|
|
207
266
|
pollIntervalMs: number,
|
|
208
267
|
timeoutMs: number,
|
|
209
268
|
options: EnsureFeishuAppConfigurationOptions,
|
|
269
|
+
ignoredScopes: ReadonlySet<string> = new Set(),
|
|
210
270
|
): Promise<EnsureFeishuAppConfigurationResult> {
|
|
211
271
|
let configuration = initialConfiguration;
|
|
212
272
|
let remaining = missingFeishuAppConfiguration(configuration);
|
|
213
|
-
|
|
273
|
+
const requested = withoutIgnoredScopes(remaining, ignoredScopes);
|
|
274
|
+
if (hasMissingConfiguration(requested)) {
|
|
214
275
|
await requestMissingFeishuConfiguration(
|
|
215
276
|
credentials.appId,
|
|
216
|
-
|
|
277
|
+
requested,
|
|
217
278
|
false,
|
|
218
279
|
options.onVerification,
|
|
219
280
|
);
|
|
@@ -227,6 +288,7 @@ async function completeOptionalFeishuConfiguration(
|
|
|
227
288
|
timeoutMs,
|
|
228
289
|
options.signal,
|
|
229
290
|
options.optionalSkipSignal,
|
|
291
|
+
ignoredScopes,
|
|
230
292
|
);
|
|
231
293
|
configuration = completed.configuration;
|
|
232
294
|
remaining = completed.remaining;
|
|
@@ -249,6 +311,7 @@ async function waitForOptionalFeishuConfiguration(
|
|
|
249
311
|
timeoutMs: number,
|
|
250
312
|
signal?: AbortSignal,
|
|
251
313
|
optionalSkipSignal?: AbortSignal,
|
|
314
|
+
ignoredScopes: ReadonlySet<string> = new Set(),
|
|
252
315
|
): Promise<{
|
|
253
316
|
configuration: FeishuAppConfiguration;
|
|
254
317
|
remaining: MissingFeishuAppConfiguration;
|
|
@@ -256,7 +319,7 @@ async function waitForOptionalFeishuConfiguration(
|
|
|
256
319
|
let configuration = initialConfiguration;
|
|
257
320
|
let remaining = missingFeishuAppConfiguration(configuration);
|
|
258
321
|
let remainingMs = timeoutMs;
|
|
259
|
-
while (remainingMs > 0 && hasMissingConfiguration(remaining)) {
|
|
322
|
+
while (remainingMs > 0 && hasMissingConfiguration(withoutIgnoredScopes(remaining, ignoredScopes))) {
|
|
260
323
|
if (optionalSkipSignal?.aborted) break;
|
|
261
324
|
const waitMs = Math.min(pollIntervalMs, remainingMs);
|
|
262
325
|
const waitResult = await sleepWithOptionalSkip(
|
|
@@ -346,6 +409,14 @@ export function buildFeishuConfigurationUrl(appId: string, missing: MissingFeish
|
|
|
346
409
|
return url.toString();
|
|
347
410
|
}
|
|
348
411
|
|
|
412
|
+
export function buildFeishuPermissionManagementUrl(appId: string, scope: string): string {
|
|
413
|
+
const url = new URL(`/app/${encodeURIComponent(appId)}/auth`, FEISHU_OPEN_BASE_URL);
|
|
414
|
+
url.searchParams.set("q", scope);
|
|
415
|
+
url.searchParams.set("op_from", "openapi");
|
|
416
|
+
url.searchParams.set("token_type", "tenant");
|
|
417
|
+
return url.toString();
|
|
418
|
+
}
|
|
419
|
+
|
|
349
420
|
async function readFeishuAppConfiguration(
|
|
350
421
|
credentials: FeishuAppCredentials,
|
|
351
422
|
fetchImpl: typeof globalThis.fetch,
|
|
@@ -360,14 +431,14 @@ async function readFeishuAppConfiguration(
|
|
|
360
431
|
`${FEISHU_OPEN_BASE_URL}${APPLICATION_PATH_PREFIX}/${appId}?lang=zh_cn`,
|
|
361
432
|
{ headers },
|
|
362
433
|
signal,
|
|
363
|
-
"Read Lark app configuration",
|
|
434
|
+
cliText("Read Lark app configuration", "读取飞书应用配置"),
|
|
364
435
|
),
|
|
365
436
|
fetchJson(
|
|
366
437
|
fetchImpl,
|
|
367
438
|
`${FEISHU_OPEN_BASE_URL}${APPLICATION_PATH_PREFIX}/${appId}/app_versions?lang=zh_cn&page_size=2`,
|
|
368
439
|
{ headers },
|
|
369
440
|
signal,
|
|
370
|
-
"Read Lark app versions",
|
|
441
|
+
cliText("Read Lark app versions", "读取飞书应用版本"),
|
|
371
442
|
),
|
|
372
443
|
]);
|
|
373
444
|
|
|
@@ -415,10 +486,13 @@ async function getTenantAccessToken(
|
|
|
415
486
|
}),
|
|
416
487
|
},
|
|
417
488
|
signal,
|
|
418
|
-
"Get the Lark tenant_access_token",
|
|
489
|
+
cliText("Get the Lark tenant_access_token", "获取飞书 tenant_access_token"),
|
|
419
490
|
);
|
|
420
491
|
const token = readString(payload, "tenant_access_token");
|
|
421
|
-
if (!token) throw new Error(
|
|
492
|
+
if (!token) throw new Error(cliText(
|
|
493
|
+
"The Lark tenant_access_token response is missing the access token.",
|
|
494
|
+
"飞书 tenant_access_token 响应中缺少访问令牌。",
|
|
495
|
+
));
|
|
422
496
|
return token;
|
|
423
497
|
}
|
|
424
498
|
|
|
@@ -439,12 +513,18 @@ async function fetchJson(
|
|
|
439
513
|
const response = await fetchImpl(url, { ...init, signal: controller.signal });
|
|
440
514
|
const payload = (await response.json().catch(() => undefined)) as unknown;
|
|
441
515
|
if (!isJsonObject(payload)) {
|
|
442
|
-
throw new Error(
|
|
516
|
+
throw new Error(cliText(
|
|
517
|
+
`${operation} returned an invalid response (HTTP ${response.status}).`,
|
|
518
|
+
`${operation}返回了无效响应(HTTP ${response.status})。`,
|
|
519
|
+
));
|
|
443
520
|
}
|
|
444
521
|
const code = readNumber(payload, "code");
|
|
445
522
|
if (!response.ok || code !== 0) {
|
|
446
523
|
throw new FeishuConfigurationApiError(
|
|
447
|
-
|
|
524
|
+
cliText(
|
|
525
|
+
`${operation} failed: ${readString(payload, "msg") || response.statusText || `HTTP ${response.status}`}`,
|
|
526
|
+
`${operation}失败:${readString(payload, "msg") || response.statusText || `HTTP ${response.status}`}`,
|
|
527
|
+
),
|
|
448
528
|
code,
|
|
449
529
|
response.status,
|
|
450
530
|
);
|
|
@@ -452,7 +532,10 @@ async function fetchJson(
|
|
|
452
532
|
return payload;
|
|
453
533
|
} catch (error) {
|
|
454
534
|
if (parentSignal?.aborted) throw abortError(parentSignal);
|
|
455
|
-
if (controller.signal.aborted) throw new Error(
|
|
535
|
+
if (controller.signal.aborted) throw new Error(cliText(
|
|
536
|
+
`${operation} timed out.`,
|
|
537
|
+
`${operation}超时。`,
|
|
538
|
+
));
|
|
456
539
|
throw error;
|
|
457
540
|
} finally {
|
|
458
541
|
clearTimeout(timeout);
|
|
@@ -484,6 +567,33 @@ function hasMissingConfiguration(missing: MissingFeishuAppConfiguration): boolea
|
|
|
484
567
|
return missing.scopes.length > 0 || missing.events.length > 0 || missing.callbacks.length > 0;
|
|
485
568
|
}
|
|
486
569
|
|
|
570
|
+
function hasBlockingCoreConfiguration(
|
|
571
|
+
missing: MissingFeishuAppConfiguration,
|
|
572
|
+
manualPermissionSkipSignal?: AbortSignal,
|
|
573
|
+
): boolean {
|
|
574
|
+
if (!manualPermissionSkipSignal?.aborted) return hasMissingConfiguration(missing);
|
|
575
|
+
return hasMissingConfiguration(withoutIgnoredScopes(missing, MANUAL_FEISHU_SCOPES));
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function skippedManualScopes(
|
|
579
|
+
initialCoreMissing: MissingFeishuAppConfiguration,
|
|
580
|
+
manualPermissionSkipSignal?: AbortSignal,
|
|
581
|
+
): ReadonlySet<string> {
|
|
582
|
+
if (!manualPermissionSkipSignal?.aborted) return new Set();
|
|
583
|
+
return new Set(initialCoreMissing.scopes.filter((scope) => MANUAL_FEISHU_SCOPES.has(scope)));
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function withoutIgnoredScopes(
|
|
587
|
+
missing: MissingFeishuAppConfiguration,
|
|
588
|
+
ignoredScopes: ReadonlySet<string>,
|
|
589
|
+
): MissingFeishuAppConfiguration {
|
|
590
|
+
return {
|
|
591
|
+
scopes: missing.scopes.filter((scope) => !ignoredScopes.has(scope)),
|
|
592
|
+
events: missing.events,
|
|
593
|
+
callbacks: missing.callbacks,
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
|
|
487
597
|
function coreMissingFeishuAppConfiguration(missing: MissingFeishuAppConfiguration): MissingFeishuAppConfiguration {
|
|
488
598
|
return {
|
|
489
599
|
scopes: missing.scopes.filter((scope) => CORE_FEISHU_SCOPES.has(scope)),
|
|
@@ -554,7 +664,9 @@ function positiveInteger(value: number | undefined): number | undefined {
|
|
|
554
664
|
}
|
|
555
665
|
|
|
556
666
|
function abortError(signal: AbortSignal): Error {
|
|
557
|
-
return signal.reason instanceof Error
|
|
667
|
+
return signal.reason instanceof Error
|
|
668
|
+
? signal.reason
|
|
669
|
+
: new Error(cliText("Lark app configuration was cancelled.", "飞书应用配置已取消。"));
|
|
558
670
|
}
|
|
559
671
|
|
|
560
672
|
function delay(milliseconds: number): Promise<void> {
|
|
@@ -41,12 +41,15 @@ export async function registerFeishuApp(options: FeishuAppRegistrationOptions =
|
|
|
41
41
|
},
|
|
42
42
|
options.signal,
|
|
43
43
|
);
|
|
44
|
-
throwRegistrationError(begin, "Could not start Lark app creation");
|
|
44
|
+
throwRegistrationError(begin, cliText("Could not start Lark app creation", "无法开始创建飞书应用"));
|
|
45
45
|
|
|
46
46
|
const deviceCode = readString(begin, "device_code");
|
|
47
47
|
const userCode = readString(begin, "user_code");
|
|
48
48
|
if (!deviceCode || !userCode) {
|
|
49
|
-
throw new Error(
|
|
49
|
+
throw new Error(cliText(
|
|
50
|
+
"The Lark app creation response is missing device_code or user_code.",
|
|
51
|
+
"飞书应用创建响应中缺少 device_code 或 user_code。",
|
|
52
|
+
));
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
const expiresIn =
|
|
@@ -113,16 +116,22 @@ export async function registerFeishuApp(options: FeishuAppRegistrationOptions =
|
|
|
113
116
|
intervalSeconds = Math.min(intervalSeconds + 5, MAX_POLL_INTERVAL_SECONDS);
|
|
114
117
|
continue;
|
|
115
118
|
case "access_denied":
|
|
116
|
-
throw new Error("Lark app creation was cancelled.");
|
|
119
|
+
throw new Error(cliText("Lark app creation was cancelled.", "飞书应用创建已取消。"));
|
|
117
120
|
case "expired_token":
|
|
118
121
|
case "invalid_grant":
|
|
119
|
-
throw new Error(
|
|
122
|
+
throw new Error(cliText(
|
|
123
|
+
"The Lark app creation link expired. Run agent-bot init again.",
|
|
124
|
+
"飞书应用创建链接已过期,请重新运行 agent-bot init。",
|
|
125
|
+
));
|
|
120
126
|
default:
|
|
121
|
-
throwRegistrationError(payload, "Lark app creation failed");
|
|
127
|
+
throwRegistrationError(payload, cliText("Lark app creation failed", "飞书应用创建失败"));
|
|
122
128
|
}
|
|
123
129
|
}
|
|
124
130
|
|
|
125
|
-
throw new Error(
|
|
131
|
+
throw new Error(cliText(
|
|
132
|
+
"Timed out waiting for Lark app creation. Run agent-bot init again.",
|
|
133
|
+
"等待飞书应用创建超时,请重新运行 agent-bot init。",
|
|
134
|
+
));
|
|
126
135
|
}
|
|
127
136
|
|
|
128
137
|
function buildVerificationUrl(userCode: string): string {
|
|
@@ -152,7 +161,10 @@ async function postRegistration(
|
|
|
152
161
|
});
|
|
153
162
|
const payload = (await response.json().catch(() => undefined)) as unknown;
|
|
154
163
|
if (!isRecord(payload)) {
|
|
155
|
-
throw new Error(
|
|
164
|
+
throw new Error(cliText(
|
|
165
|
+
`The Lark app registration endpoint returned an invalid response (HTTP ${response.status}).`,
|
|
166
|
+
`飞书应用注册接口返回了无效响应(HTTP ${response.status})。`,
|
|
167
|
+
));
|
|
156
168
|
}
|
|
157
169
|
return payload;
|
|
158
170
|
} finally {
|
|
@@ -165,7 +177,7 @@ function throwRegistrationError(payload: RegistrationPayload, prefix: string): v
|
|
|
165
177
|
const code = readString(payload, "error");
|
|
166
178
|
if (!code) return;
|
|
167
179
|
const description = readString(payload, "error_description") || code;
|
|
168
|
-
throw new Error(`${prefix}: ${description}`);
|
|
180
|
+
throw new Error(`${prefix}${cliText(": ", ":")}${description}`);
|
|
169
181
|
}
|
|
170
182
|
|
|
171
183
|
function readString(value: RegistrationPayload, key: string): string {
|
|
@@ -189,7 +201,9 @@ function isRecord(value: unknown): value is RegistrationPayload {
|
|
|
189
201
|
}
|
|
190
202
|
|
|
191
203
|
function abortError(signal: AbortSignal): Error {
|
|
192
|
-
return signal.reason instanceof Error
|
|
204
|
+
return signal.reason instanceof Error
|
|
205
|
+
? signal.reason
|
|
206
|
+
: new Error(cliText("Lark app creation was cancelled.", "飞书应用创建已取消。"));
|
|
193
207
|
}
|
|
194
208
|
|
|
195
209
|
function delay(milliseconds: number): Promise<void> {
|
|
@@ -218,3 +232,4 @@ async function sleepWithAbort(
|
|
|
218
232
|
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
219
233
|
}
|
|
220
234
|
}
|
|
235
|
+
import { cliText } from "./i18n.js";
|
package/src/cli/Initializer.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { cliText } from "./i18n.js";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
6
7
|
import { parse as parseDotEnv } from "dotenv";
|
|
7
8
|
import {
|
|
@@ -136,7 +137,10 @@ export function writeFeishuCredentials(envPath: string, credentials: FeishuCrede
|
|
|
136
137
|
|| persisted.appSecret !== credentials.appSecret
|
|
137
138
|
|| persisted.userOpenId !== credentials.userOpenId
|
|
138
139
|
) {
|
|
139
|
-
throw new Error(
|
|
140
|
+
throw new Error(cliText(
|
|
141
|
+
"Lark credential verification failed after writing the environment file.",
|
|
142
|
+
"写入环境文件后,飞书凭据校验失败。",
|
|
143
|
+
));
|
|
140
144
|
}
|
|
141
145
|
} finally {
|
|
142
146
|
if (temporaryFile !== undefined) fs.closeSync(temporaryFile);
|
|
@@ -183,13 +187,19 @@ export function acquireInitializationLock(lockDirectory: string): Initialization
|
|
|
183
187
|
if (created) fs.rmSync(lockPath, { force: true });
|
|
184
188
|
if (!isFileExistsError(error)) throw error;
|
|
185
189
|
if (!isStaleInitializationLock(lockPath)) {
|
|
186
|
-
throw new Error(
|
|
190
|
+
throw new Error(cliText(
|
|
191
|
+
`Another agent-bot init process is running (lock file: ${lockPath}).`,
|
|
192
|
+
`另一个 agent-bot init 进程正在运行(锁文件:${lockPath})。`,
|
|
193
|
+
));
|
|
187
194
|
}
|
|
188
195
|
fs.rmSync(lockPath, { force: true });
|
|
189
196
|
}
|
|
190
197
|
}
|
|
191
198
|
|
|
192
|
-
throw new Error(
|
|
199
|
+
throw new Error(cliText(
|
|
200
|
+
`Could not acquire the initialization lock: ${lockPath}`,
|
|
201
|
+
`无法获取初始化锁:${lockPath}`,
|
|
202
|
+
));
|
|
193
203
|
}
|
|
194
204
|
|
|
195
205
|
export function cleanupFeishuCredentialTemporaryFiles(envPath: string): number {
|
|
@@ -209,7 +219,10 @@ export function cleanupFeishuCredentialTemporaryFiles(envPath: string): number {
|
|
|
209
219
|
|
|
210
220
|
function assertTemplate(templatePath: string): void {
|
|
211
221
|
if (!fs.statSync(templatePath, { throwIfNoEntry: false })?.isFile()) {
|
|
212
|
-
throw new Error(
|
|
222
|
+
throw new Error(cliText(
|
|
223
|
+
`Initialization template does not exist: ${templatePath}`,
|
|
224
|
+
`初始化模板不存在:${templatePath}`,
|
|
225
|
+
));
|
|
213
226
|
}
|
|
214
227
|
}
|
|
215
228
|
|
|
@@ -243,7 +256,10 @@ function resetProfileContents(
|
|
|
243
256
|
|| path.dirname(path.resolve(configPath)) !== resolvedHome
|
|
244
257
|
|| path.dirname(path.resolve(envPath)) !== resolvedHome
|
|
245
258
|
) {
|
|
246
|
-
throw new Error(
|
|
259
|
+
throw new Error(cliText(
|
|
260
|
+
"--reset only supports a profile-owned config.yaml and .env.",
|
|
261
|
+
"--reset 仅支持由 Profile 管理的 config.yaml 和 .env。",
|
|
262
|
+
));
|
|
247
263
|
}
|
|
248
264
|
const targets = [
|
|
249
265
|
{ name: "config.yaml", sourcePath: configPath },
|
|
@@ -304,7 +320,10 @@ function upsertDotEnvValue(contents: string, key: string, value: string): string
|
|
|
304
320
|
|
|
305
321
|
function assertDotEnvValue(key: string, value: string): void {
|
|
306
322
|
if (!value.trim() || /[\r\n]/.test(value)) {
|
|
307
|
-
throw new Error(
|
|
323
|
+
throw new Error(cliText(
|
|
324
|
+
`${key} is not a valid single-line environment variable value.`,
|
|
325
|
+
`${key} 不是有效的单行环境变量值。`,
|
|
326
|
+
));
|
|
308
327
|
}
|
|
309
328
|
}
|
|
310
329
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import net from "node:net";
|
|
2
2
|
import type { ControlRequest, ControlResponse } from "./controlProtocol.js";
|
|
3
|
+
import { cliText } from "./i18n.js";
|
|
3
4
|
|
|
4
5
|
export async function sendControlRequest(
|
|
5
6
|
endpoint: string,
|
|
@@ -17,7 +18,10 @@ export async function sendControlRequest(
|
|
|
17
18
|
socket.destroy();
|
|
18
19
|
operation();
|
|
19
20
|
};
|
|
20
|
-
const timer = setTimeout(() => finish(() => reject(new Error(
|
|
21
|
+
const timer = setTimeout(() => finish(() => reject(new Error(cliText(
|
|
22
|
+
"Timed out connecting to the Agent Bot control endpoint.",
|
|
23
|
+
"连接 Agent Bot 控制端点超时。",
|
|
24
|
+
)))), timeoutMs);
|
|
21
25
|
socket.setEncoding("utf8");
|
|
22
26
|
socket.once("connect", () => socket.write(`${JSON.stringify(request)}\n`));
|
|
23
27
|
socket.on("data", (chunk: string) => {
|
|
@@ -33,7 +37,10 @@ export async function sendControlRequest(
|
|
|
33
37
|
});
|
|
34
38
|
socket.once("error", (error) => finish(() => reject(error)));
|
|
35
39
|
socket.once("end", () => {
|
|
36
|
-
if (!settled) finish(() => reject(new Error(
|
|
40
|
+
if (!settled) finish(() => reject(new Error(cliText(
|
|
41
|
+
"The Agent Bot control endpoint returned no response.",
|
|
42
|
+
"Agent Bot 控制端点未返回响应。",
|
|
43
|
+
))));
|
|
37
44
|
});
|
|
38
45
|
});
|
|
39
46
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import net from "node:net";
|
|
3
3
|
import type { ControlRequest, ControlResponse } from "./controlProtocol.js";
|
|
4
|
+
import { cliText } from "./i18n.js";
|
|
4
5
|
|
|
5
6
|
export type ControlRequestHandler = (request: ControlRequest) => Promise<ControlResponse>;
|
|
6
7
|
|
|
@@ -61,7 +62,7 @@ export class LocalControlServer {
|
|
|
61
62
|
function parseRequest(line: string): ControlRequest {
|
|
62
63
|
const value = JSON.parse(line) as unknown;
|
|
63
64
|
if (!value || typeof value !== "object" || !("action" in value) || typeof value.action !== "string") {
|
|
64
|
-
throw new Error("Invalid local control request.");
|
|
65
|
+
throw new Error(cliText("Invalid local control request.", "无效的本地控制请求。"));
|
|
65
66
|
}
|
|
66
67
|
return value as ControlRequest;
|
|
67
68
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createInterface, type Interface } from "node:readline";
|
|
2
|
+
import { cliText } from "./i18n.js";
|
|
2
3
|
|
|
3
4
|
export interface OptionalAuthorizationSkipListener {
|
|
4
5
|
close(): void;
|
|
@@ -6,6 +7,45 @@ export interface OptionalAuthorizationSkipListener {
|
|
|
6
7
|
|
|
7
8
|
export function listenForOptionalAuthorizationSkip(
|
|
8
9
|
onSkip: () => void,
|
|
10
|
+
): OptionalAuthorizationSkipListener {
|
|
11
|
+
return listenForAuthorizationSkip(onSkip, {
|
|
12
|
+
prompt: cliText(
|
|
13
|
+
"Skip optional authorization? Enter Y and press Enter to skip.\n",
|
|
14
|
+
"是否跳过可选授权?输入 Y 并按回车键跳过。\n",
|
|
15
|
+
),
|
|
16
|
+
retry: cliText(
|
|
17
|
+
"Enter Y to skip optional authorization, or keep waiting.\n",
|
|
18
|
+
"输入 Y 跳过可选授权,或继续等待。\n",
|
|
19
|
+
),
|
|
20
|
+
skipped: cliText(
|
|
21
|
+
"Optional authorization skipped. Initialization will continue.\n",
|
|
22
|
+
"已跳过可选授权,初始化将继续。\n",
|
|
23
|
+
),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function listenForManualPermissionSkip(
|
|
28
|
+
onSkip: () => void,
|
|
29
|
+
): OptionalAuthorizationSkipListener {
|
|
30
|
+
return listenForAuthorizationSkip(onSkip, {
|
|
31
|
+
prompt: cliText(
|
|
32
|
+
"Skip waiting for this permission? Enter Y and press Enter to skip.\n",
|
|
33
|
+
"是否跳过等待此权限?输入 Y 并按回车键跳过。\n",
|
|
34
|
+
),
|
|
35
|
+
retry: cliText(
|
|
36
|
+
"Enter Y to skip this permission, or keep waiting.\n",
|
|
37
|
+
"输入 Y 跳过此权限,或继续等待。\n",
|
|
38
|
+
),
|
|
39
|
+
skipped: cliText(
|
|
40
|
+
"Permission wait skipped. Initialization will continue with limited group-message functionality.\n",
|
|
41
|
+
"已跳过权限等待,初始化将继续,但群消息功能受限。\n",
|
|
42
|
+
),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function listenForAuthorizationSkip(
|
|
47
|
+
onSkip: () => void,
|
|
48
|
+
messages: { prompt: string; retry: string; skipped: string },
|
|
9
49
|
): OptionalAuthorizationSkipListener {
|
|
10
50
|
if (!process.stdin.isTTY) return { close: () => undefined };
|
|
11
51
|
|
|
@@ -13,19 +53,24 @@ export function listenForOptionalAuthorizationSkip(
|
|
|
13
53
|
input: process.stdin,
|
|
14
54
|
output: process.stderr,
|
|
15
55
|
});
|
|
56
|
+
let closed = false;
|
|
57
|
+
const close = (): void => {
|
|
58
|
+
if (closed) return;
|
|
59
|
+
closed = true;
|
|
60
|
+
closeReadline(readline, onLine);
|
|
61
|
+
};
|
|
16
62
|
const onLine = (value: string): void => {
|
|
17
63
|
if (!isOptionalAuthorizationSkip(value)) {
|
|
18
|
-
process.stderr.write(
|
|
64
|
+
process.stderr.write(messages.retry);
|
|
19
65
|
return;
|
|
20
66
|
}
|
|
21
|
-
process.stderr.write(
|
|
67
|
+
process.stderr.write(messages.skipped);
|
|
68
|
+
close();
|
|
22
69
|
onSkip();
|
|
23
70
|
};
|
|
24
71
|
readline.on("line", onLine);
|
|
25
|
-
process.stderr.write(
|
|
26
|
-
return {
|
|
27
|
-
close: () => closeReadline(readline, onLine),
|
|
28
|
-
};
|
|
72
|
+
process.stderr.write(messages.prompt);
|
|
73
|
+
return { close };
|
|
29
74
|
}
|
|
30
75
|
|
|
31
76
|
export function isOptionalAuthorizationSkip(value: string): boolean {
|
package/src/cli/ServerStarter.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
resolveSupervisorDiagnosticsPaths,
|
|
10
10
|
} from "../supervision/SupervisorDiagnostics.js";
|
|
11
11
|
import { controlEndpoint } from "./controlProtocol.js";
|
|
12
|
+
import { cliText } from "./i18n.js";
|
|
12
13
|
import { isServerReachable, isServerRunning } from "./LocalControlClient.js";
|
|
13
14
|
|
|
14
15
|
export interface ServerStartResult {
|
|
@@ -47,7 +48,17 @@ export async function startServer(
|
|
|
47
48
|
config: AppConfig,
|
|
48
49
|
dependencies: ServerStarterDependencies = defaultDependencies,
|
|
49
50
|
): Promise<ServerStartResult> {
|
|
50
|
-
|
|
51
|
+
try {
|
|
52
|
+
requireServerFeishuTransport(config.feishu);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (error instanceof Error && /Lark bot is not configured/.test(error.message)) {
|
|
55
|
+
throw new Error(cliText(
|
|
56
|
+
"The Lark bot is not configured. Run agent-bot init first, or use agent-bot console for local-only testing.",
|
|
57
|
+
"尚未配置飞书机器人。请先运行 agent-bot init,或使用 agent-bot console 进行纯本地测试。",
|
|
58
|
+
));
|
|
59
|
+
}
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
51
62
|
const endpoint = controlEndpoint(config.storage.sqlitePath);
|
|
52
63
|
|
|
53
64
|
if (await dependencies.isRunning(endpoint)) {
|
|
@@ -56,7 +67,10 @@ export async function startServer(
|
|
|
56
67
|
if (await dependencies.isReachable(endpoint)) {
|
|
57
68
|
const running = await dependencies.waitUntilRunning(endpoint, SERVER_START_TIMEOUT_MS);
|
|
58
69
|
if (!running) {
|
|
59
|
-
throw new Error(
|
|
70
|
+
throw new Error(cliText(
|
|
71
|
+
"The Agent Bot server started but could not connect to Lark. Check the logs.",
|
|
72
|
+
"Agent Bot 服务已启动,但无法连接飞书。请检查日志。",
|
|
73
|
+
));
|
|
60
74
|
}
|
|
61
75
|
return { status: "started" };
|
|
62
76
|
}
|
|
@@ -64,7 +78,10 @@ export async function startServer(
|
|
|
64
78
|
dependencies.spawnSupervisor(config);
|
|
65
79
|
const running = await dependencies.waitUntilRunning(endpoint, SERVER_START_TIMEOUT_MS);
|
|
66
80
|
if (!running) {
|
|
67
|
-
throw new Error(
|
|
81
|
+
throw new Error(cliText(
|
|
82
|
+
"The Supervisor started, but the server did not connect to Lark within 45 seconds. Check the logs.",
|
|
83
|
+
"Supervisor 已启动,但服务未能在 45 秒内连接飞书。请检查日志。",
|
|
84
|
+
));
|
|
68
85
|
}
|
|
69
86
|
return { status: "started" };
|
|
70
87
|
}
|