botmux 2.105.1 → 2.106.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +2 -1
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/passthrough-commands.d.ts.map +1 -1
- package/dist/core/passthrough-commands.js +1 -1
- package/dist/core/passthrough-commands.js.map +1 -1
- package/dist/daemon.d.ts +31 -8
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +750 -67
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/settings-write-applier.d.ts +5 -0
- package/dist/dashboard/settings-write-applier.d.ts.map +1 -1
- package/dist/dashboard/settings-write-applier.js +2 -2
- package/dist/dashboard/settings-write-applier.js.map +1 -1
- package/dist/dashboard/web/settings-page.d.ts.map +1 -1
- package/dist/dashboard/web/settings-page.js +13 -4
- package/dist/dashboard/web/settings-page.js.map +1 -1
- package/dist/dashboard-web/app.js +1 -1
- package/dist/dashboard-web/chunks/settings-page-R23I3IZG.js +2 -0
- package/dist/dashboard-web/style.css +11 -0
- package/dist/dashboard.js +239 -62
- package/dist/dashboard.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +1 -0
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +1 -0
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
- package/dist/im/lark/event-dispatcher.js +11 -0
- package/dist/im/lark/event-dispatcher.js.map +1 -1
- package/dist/im/lark/message-parser.d.ts +6 -0
- package/dist/im/lark/message-parser.d.ts.map +1 -1
- package/dist/im/lark/message-parser.js +14 -0
- package/dist/im/lark/message-parser.js.map +1 -1
- package/dist/services/vc-meeting-runtime-store.d.ts +4 -0
- package/dist/services/vc-meeting-runtime-store.d.ts.map +1 -1
- package/dist/services/vc-meeting-runtime-store.js +26 -0
- package/dist/services/vc-meeting-runtime-store.js.map +1 -1
- package/dist/setup/open-platform-automation.d.ts +20 -0
- package/dist/setup/open-platform-automation.d.ts.map +1 -1
- package/dist/setup/open-platform-automation.js +78 -1
- package/dist/setup/open-platform-automation.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/vc-agent/cards.d.ts +2 -0
- package/dist/vc-agent/cards.d.ts.map +1 -1
- package/dist/vc-agent/cards.js +18 -2
- package/dist/vc-agent/cards.js.map +1 -1
- package/dist/vc-agent/normalizer.d.ts.map +1 -1
- package/dist/vc-agent/normalizer.js +59 -0
- package/dist/vc-agent/normalizer.js.map +1 -1
- package/dist/vc-agent/polling-source.d.ts +28 -0
- package/dist/vc-agent/polling-source.d.ts.map +1 -1
- package/dist/vc-agent/polling-source.js +57 -5
- package/dist/vc-agent/polling-source.js.map +1 -1
- package/dist/vc-agent/types.d.ts +1 -0
- package/dist/vc-agent/types.d.ts.map +1 -1
- package/dist/worker.js +43 -6
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
- package/dist/dashboard-web/chunks/settings-page-CRDIHRNE.js +0 -2
package/dist/dashboard.js
CHANGED
|
@@ -63,8 +63,10 @@ import { startPlatformTunnelClient } from './platform/tunnel-client.js';
|
|
|
63
63
|
import { applyPlatformTeamSync, getPlatformTeamSyncRev, listPlatformTeams } from './services/platform-team-store.js';
|
|
64
64
|
import { getBotUnionId } from './services/bot-union-ids-store.js';
|
|
65
65
|
import { cleanupIdleSessions, parseIdleCleanupHours } from './dashboard/session-cleanup.js';
|
|
66
|
+
import { automateOpenPlatformSetup } from './setup/open-platform-automation.js';
|
|
67
|
+
import { VC_MEETING_FEATURE_SCOPES, VC_MEETING_REALTIME_VOICE_SCOPES } from './setup/verify-permissions.js';
|
|
68
|
+
import { checkLarkCliVersion, MIN_LARK_CLI_VERSION_FOR_VC_BOT } from './vc-agent/polling-source.js';
|
|
66
69
|
import { larkHosts } from './im/lark/lark-hosts.js';
|
|
67
|
-
import { VC_MEETING_FEATURE_SCOPES, VC_MEETING_REALTIME_VOICE_SCOPES, } from './setup/verify-permissions.js';
|
|
68
70
|
const SECRET_PATH = join(homedir(), '.botmux', '.dashboard-secret');
|
|
69
71
|
const TOKEN_PATH = join(homedir(), '.botmux', '.dashboard-token');
|
|
70
72
|
/** Per-daemon budget for the cross-daemon insight overview fan-out — bounds
|
|
@@ -261,52 +263,6 @@ function vcMeetingListenerBotOptions() {
|
|
|
261
263
|
return [];
|
|
262
264
|
}
|
|
263
265
|
}
|
|
264
|
-
async function fetchGrantedScopesForBotConfig(bot) {
|
|
265
|
-
const brand = bot.brand === 'lark' ? 'lark' : 'feishu';
|
|
266
|
-
const openApi = larkHosts(brand).openApi;
|
|
267
|
-
const ac = new AbortController();
|
|
268
|
-
const timer = setTimeout(() => ac.abort(), 10_000);
|
|
269
|
-
try {
|
|
270
|
-
const tokenRes = await fetch(`${openApi}/open-apis/auth/v3/tenant_access_token/internal`, {
|
|
271
|
-
method: 'POST',
|
|
272
|
-
headers: { 'Content-Type': 'application/json' },
|
|
273
|
-
body: JSON.stringify({ app_id: bot.larkAppId, app_secret: bot.larkAppSecret }),
|
|
274
|
-
signal: ac.signal,
|
|
275
|
-
});
|
|
276
|
-
const tokenData = await tokenRes.json();
|
|
277
|
-
if (tokenData?.code !== 0 || typeof tokenData?.tenant_access_token !== 'string') {
|
|
278
|
-
return { ok: false, error: `vcMeetingAgent_listenerBot_invalid_credentials: code=${tokenData?.code ?? '?'} msg=${tokenData?.msg ?? ''}` };
|
|
279
|
-
}
|
|
280
|
-
const infoRes = await fetch(`${openApi}/open-apis/application/v6/applications/${bot.larkAppId}?lang=zh_cn`, { headers: { Authorization: `Bearer ${tokenData.tenant_access_token}` }, signal: ac.signal });
|
|
281
|
-
const infoData = await infoRes.json();
|
|
282
|
-
if (infoData?.code === 99991672) {
|
|
283
|
-
return { ok: false, error: 'vcMeetingAgent_listenerBot_scope_check_unavailable: missing application:application:self_manage' };
|
|
284
|
-
}
|
|
285
|
-
if (infoData?.code !== 0) {
|
|
286
|
-
return { ok: false, error: `vcMeetingAgent_listenerBot_scope_check_failed: code=${infoData?.code ?? '?'} msg=${infoData?.msg ?? ''}` };
|
|
287
|
-
}
|
|
288
|
-
const scopesRaw = infoData.data?.app?.scopes
|
|
289
|
-
?? infoData.data?.application?.scopes
|
|
290
|
-
?? infoData.data?.scopes
|
|
291
|
-
?? [];
|
|
292
|
-
if (!Array.isArray(scopesRaw) || scopesRaw.length === 0) {
|
|
293
|
-
return { ok: false, error: 'vcMeetingAgent_listenerBot_scope_check_failed: empty scopes' };
|
|
294
|
-
}
|
|
295
|
-
const granted = new Set(scopesRaw.map(s => typeof s === 'string' ? s : s?.scope).filter(Boolean));
|
|
296
|
-
return { ok: true, granted };
|
|
297
|
-
}
|
|
298
|
-
catch (err) {
|
|
299
|
-
return {
|
|
300
|
-
ok: false,
|
|
301
|
-
error: ac.signal.aborted
|
|
302
|
-
? 'vcMeetingAgent_listenerBot_scope_check_timeout'
|
|
303
|
-
: `vcMeetingAgent_listenerBot_scope_check_failed: ${err?.message ?? err}`,
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
finally {
|
|
307
|
-
clearTimeout(timer);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
266
|
async function validateVcMeetingListenerBotAppId(appId) {
|
|
311
267
|
let bots;
|
|
312
268
|
try {
|
|
@@ -318,19 +274,6 @@ async function validateVcMeetingListenerBotAppId(appId) {
|
|
|
318
274
|
const bot = bots.find(b => b.larkAppId === appId);
|
|
319
275
|
if (!bot)
|
|
320
276
|
return { ok: false, error: 'vcMeetingAgent_listenerBot_unknown' };
|
|
321
|
-
const cfg = bot.vcMeetingAgent ?? {};
|
|
322
|
-
if (!cfg.larkCliProfile)
|
|
323
|
-
return { ok: false, error: 'vcMeetingAgent_listenerBot_missing_larkCliProfile' };
|
|
324
|
-
const granted = await fetchGrantedScopesForBotConfig(bot);
|
|
325
|
-
if (!granted.ok)
|
|
326
|
-
return granted;
|
|
327
|
-
const required = cfg.realtimeVoice?.enabled === true
|
|
328
|
-
? [...VC_MEETING_FEATURE_SCOPES, ...VC_MEETING_REALTIME_VOICE_SCOPES]
|
|
329
|
-
: VC_MEETING_FEATURE_SCOPES;
|
|
330
|
-
const missing = required.filter(scope => !granted.granted.has(scope.name));
|
|
331
|
-
if (missing.length > 0) {
|
|
332
|
-
return { ok: false, error: `vcMeetingAgent_listenerBot_missing_scopes: ${missing.map(s => s.name).join(',')}` };
|
|
333
|
-
}
|
|
334
277
|
return { ok: true };
|
|
335
278
|
}
|
|
336
279
|
function normalizeVcMeetingAgentRecord(raw) {
|
|
@@ -368,11 +311,220 @@ async function reloadVcMeetingBotConfigOnDaemons(appIds) {
|
|
|
368
311
|
}).catch(() => undefined);
|
|
369
312
|
}));
|
|
370
313
|
}
|
|
314
|
+
/**
|
|
315
|
+
* Fetch the set of actually granted scopes for a bot app via Feishu Open API.
|
|
316
|
+
* Used after automateOpenPlatformSetup to verify that VC meeting scopes were
|
|
317
|
+
* actually applied (not just "requested").
|
|
318
|
+
*/
|
|
319
|
+
async function fetchGrantedScopesForBot(bot) {
|
|
320
|
+
const brand = bot.brand === 'lark' ? 'lark' : 'feishu';
|
|
321
|
+
const openApi = larkHosts(brand).openApi;
|
|
322
|
+
const ac = new AbortController();
|
|
323
|
+
const timer = setTimeout(() => ac.abort(), 10_000);
|
|
324
|
+
try {
|
|
325
|
+
const tokenRes = await fetch(`${openApi}/open-apis/auth/v3/tenant_access_token/internal`, {
|
|
326
|
+
method: 'POST',
|
|
327
|
+
headers: { 'Content-Type': 'application/json' },
|
|
328
|
+
body: JSON.stringify({ app_id: bot.larkAppId, app_secret: bot.larkAppSecret }),
|
|
329
|
+
signal: ac.signal,
|
|
330
|
+
});
|
|
331
|
+
const tokenData = await tokenRes.json();
|
|
332
|
+
if (tokenData?.code !== 0 || typeof tokenData?.tenant_access_token !== 'string') {
|
|
333
|
+
return { ok: false, error: `invalid_credentials: code=${tokenData?.code ?? '?'} msg=${tokenData?.msg ?? ''}` };
|
|
334
|
+
}
|
|
335
|
+
const infoRes = await fetch(`${openApi}/open-apis/application/v6/applications/${bot.larkAppId}?lang=zh_cn`, { headers: { Authorization: `Bearer ${tokenData.tenant_access_token}` }, signal: ac.signal });
|
|
336
|
+
const infoData = await infoRes.json();
|
|
337
|
+
if (infoData?.code === 99991672) {
|
|
338
|
+
return { ok: false, error: 'missing application:application:self_manage' };
|
|
339
|
+
}
|
|
340
|
+
if (infoData?.code !== 0) {
|
|
341
|
+
return { ok: false, error: `scope_check_failed: code=${infoData?.code ?? '?'} msg=${infoData?.msg ?? ''}` };
|
|
342
|
+
}
|
|
343
|
+
const scopesRaw = infoData.data?.app?.scopes
|
|
344
|
+
?? infoData.data?.application?.scopes
|
|
345
|
+
?? infoData.data?.scopes
|
|
346
|
+
?? [];
|
|
347
|
+
const granted = new Set(scopesRaw.map((s) => typeof s === 'string' ? s : s?.scope).filter(Boolean));
|
|
348
|
+
return { ok: true, granted };
|
|
349
|
+
}
|
|
350
|
+
catch (err) {
|
|
351
|
+
return {
|
|
352
|
+
ok: false,
|
|
353
|
+
error: ac.signal.aborted ? 'timeout' : `${err?.message ?? err}`,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
finally {
|
|
357
|
+
clearTimeout(timer);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Validate that a bot has the required VC meeting scopes granted.
|
|
362
|
+
* Checks both VC_MEETING_FEATURE_SCOPES and (if realtimeVoice is enabled)
|
|
363
|
+
* VC_MEETING_REALTIME_VOICE_SCOPES.
|
|
364
|
+
*/
|
|
365
|
+
async function validateVcMeetingScopesForBot(bot) {
|
|
366
|
+
const result = await fetchGrantedScopesForBot(bot);
|
|
367
|
+
if (!result.ok)
|
|
368
|
+
return { ok: false, error: result.error };
|
|
369
|
+
const needsRealtime = bot.vcMeetingAgent?.realtimeVoice?.enabled === true;
|
|
370
|
+
const required = needsRealtime
|
|
371
|
+
? [...VC_MEETING_FEATURE_SCOPES, ...VC_MEETING_REALTIME_VOICE_SCOPES]
|
|
372
|
+
: VC_MEETING_FEATURE_SCOPES;
|
|
373
|
+
const missing = required.filter(s => !result.granted.has(s.name));
|
|
374
|
+
if (missing.length > 0) {
|
|
375
|
+
return { ok: false, error: `缺少权限: ${missing.map(s => s.name).join(', ')}` };
|
|
376
|
+
}
|
|
377
|
+
return { ok: true };
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Wait for FeishuLoginManager to produce a QR code after start().
|
|
381
|
+
* The start() method returns immediately with status='starting'; the QR code
|
|
382
|
+
* is set asynchronously in the onQrCode callback. Poll until qrDataUrl appears
|
|
383
|
+
* or we hit the timeout.
|
|
384
|
+
*/
|
|
385
|
+
async function waitForFeishuLoginQr(timeoutMs = 8_000, intervalMs = 200) {
|
|
386
|
+
const start = Date.now();
|
|
387
|
+
while (Date.now() - start < timeoutMs) {
|
|
388
|
+
const snap = feishuLogin.get();
|
|
389
|
+
if (snap?.qrDataUrl)
|
|
390
|
+
return snap.qrDataUrl;
|
|
391
|
+
// Also stop waiting if login already failed
|
|
392
|
+
if (snap?.status === 'failed' || snap?.status === 'success')
|
|
393
|
+
return null;
|
|
394
|
+
await new Promise(r => setTimeout(r, intervalMs));
|
|
395
|
+
}
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
371
398
|
async function syncVcMeetingListenerBotConfig(listenerBotAppId, previousListenerBotAppId) {
|
|
372
399
|
const nextAppId = listenerBotAppId?.trim() || null;
|
|
373
400
|
const prevAppId = previousListenerBotAppId?.trim() || null;
|
|
374
401
|
if (!nextAppId && !prevAppId)
|
|
375
402
|
return { ok: true };
|
|
403
|
+
// Require lark-cli >= MIN_LARK_CLI_VERSION_FOR_VC_BOT for VC bot meeting commands
|
|
404
|
+
// (vc +meeting-join/events/message-send --as bot). Earlier versions silently reject
|
|
405
|
+
// `--as bot` with "this command only supports: user", so the listener bot can
|
|
406
|
+
// never actually join a meeting.
|
|
407
|
+
if (nextAppId) {
|
|
408
|
+
const larkCli = checkLarkCliVersion();
|
|
409
|
+
if (!larkCli) {
|
|
410
|
+
return { ok: false, error: 'vcMeetingAgent_listenerBot_larkCli_not_found: 未检测到 lark-cli,请先安装 `npm i -g @larksuite/cli`' };
|
|
411
|
+
}
|
|
412
|
+
if (!larkCli.meetsVcBotRequirement) {
|
|
413
|
+
return {
|
|
414
|
+
ok: false,
|
|
415
|
+
error: `vcMeetingAgent_listenerBot_larkCli_too_old: 当前 lark-cli ${larkCli.version} 不支持 VC bot 入会,需要 >= ${MIN_LARK_CLI_VERSION_FOR_VC_BOT}。请运行 \`npm i -g @larksuite/cli@latest\` 升级`,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
// Best-effort auto-import VC meeting scopes via Open Platform automation.
|
|
420
|
+
// Run BEFORE writing bots.json so that hard failures (missing session, needs QR)
|
|
421
|
+
// don't leave per-bot vcMeetingAgent in a half-configured state.
|
|
422
|
+
// For `brand: 'lark'` bots the open-platform automation only supports feishu.cn;
|
|
423
|
+
// skip it silently and let the user configure manually.
|
|
424
|
+
if (nextAppId) {
|
|
425
|
+
const bots = loadBotConfigs();
|
|
426
|
+
const bot = bots.find(b => b.larkAppId === nextAppId);
|
|
427
|
+
const brand = bot?.brand === 'lark' ? 'lark' : 'feishu';
|
|
428
|
+
if (brand === 'lark') {
|
|
429
|
+
logger.info(`[vc-agent] skipping open-platform automation for lark-brand bot ${nextAppId} (feishu.cn only)`);
|
|
430
|
+
// For lark brand, still validate that required scopes exist before saving
|
|
431
|
+
if (bot) {
|
|
432
|
+
const scopeCheck = await validateVcMeetingScopesForBot(bot);
|
|
433
|
+
if (!scopeCheck.ok) {
|
|
434
|
+
return { ok: false, error: `vcMeetingAgent_listenerBot_missing_scopes: ${scopeCheck.error}` };
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
try {
|
|
440
|
+
const result = await automateOpenPlatformSetup({
|
|
441
|
+
appId: nextAppId,
|
|
442
|
+
brand,
|
|
443
|
+
maxWaitMs: 5_000,
|
|
444
|
+
onStatus: (msg) => logger.info(`[vc-agent] scope auto-import: ${msg}`),
|
|
445
|
+
});
|
|
446
|
+
if (result.ok) {
|
|
447
|
+
logger.info(`[vc-agent] auto-imported ${result.scopeCount} scopes, subscribed ${result.subscribedEventCount} events for listener bot ${nextAppId}`);
|
|
448
|
+
if (result.scopeWarning)
|
|
449
|
+
logger.warn(`[vc-agent] scope import warning: ${result.scopeWarning}`);
|
|
450
|
+
if (result.eventWarning)
|
|
451
|
+
logger.warn(`[vc-agent] event subscription warning: ${result.eventWarning}`);
|
|
452
|
+
// Post-validation: verify VC meeting scopes are actually granted after automation.
|
|
453
|
+
// The internal scope/update may silently skip some scopes (e.g. not available
|
|
454
|
+
// in this tenant). Without this check, a bot without VC scopes could be saved
|
|
455
|
+
// as global listener and silently drop all meeting events.
|
|
456
|
+
if (bot) {
|
|
457
|
+
const scopeCheck = await validateVcMeetingScopesForBot(bot);
|
|
458
|
+
if (!scopeCheck.ok) {
|
|
459
|
+
return {
|
|
460
|
+
ok: false,
|
|
461
|
+
error: `vcMeetingAgent_listenerBot_missing_scopes_after_auto: ${scopeCheck.error}。请到开放平台手动开通 VC 会议权限后重试。`,
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
// Event subscription is also critical: if all 3 event update endpoints
|
|
466
|
+
// failed (eventWarning set, subscribedEventCount === 0), the bot won't
|
|
467
|
+
// receive vc.bot.meeting_*_v1 push events → meeting invite black hole.
|
|
468
|
+
if (result.eventWarning && result.subscribedEventCount === 0) {
|
|
469
|
+
return {
|
|
470
|
+
ok: false,
|
|
471
|
+
error: `vcMeetingAgent_listenerBot_event_subscribe_failed: 事件订阅全部失败(${result.eventWarning}),bot 无法接收会议邀请事件。请到开放平台手动订阅 VC 会议事件后重试。`,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
const reason = result.reason;
|
|
477
|
+
// Session/login-related failures are hard failures — return QR so user can re-login.
|
|
478
|
+
// Without a valid Open Platform session, scope/event auto-import is impossible.
|
|
479
|
+
if (reason === 'missing_session'
|
|
480
|
+
|| reason === 'invalid_session'
|
|
481
|
+
|| reason === 'missing_csrf'
|
|
482
|
+
|| reason === 'qr_expired'
|
|
483
|
+
|| reason === 'timeout'
|
|
484
|
+
|| reason === 'login_failed') {
|
|
485
|
+
feishuLogin.start();
|
|
486
|
+
// feishuLogin.start() returns immediately with status='starting'; the QR
|
|
487
|
+
// code is set asynchronously in onQrCode. Wait briefly for it to be ready
|
|
488
|
+
// so the frontend can display it inline instead of showing an error without
|
|
489
|
+
// a scan entry.
|
|
490
|
+
const qrDataUrl = await waitForFeishuLoginQr();
|
|
491
|
+
const hint = '请用飞书扫码完成开放平台登录,登录后重新选择监听 bot 即可自动配置权限';
|
|
492
|
+
return {
|
|
493
|
+
ok: false,
|
|
494
|
+
error: `vcMeetingAgent_listenerBot_scope_auto_import_failed: ${reason}: ${hint}`,
|
|
495
|
+
feishuLoginQr: qrDataUrl ?? undefined,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
// Non-login failures (network, api_error, etc.) are best-effort — don't
|
|
499
|
+
// block the save. The user can fix scopes manually in the console.
|
|
500
|
+
logger.warn(`[vc-agent] open-platform automation failed for ${nextAppId}: ${reason}: ${result.message}`);
|
|
501
|
+
// Even on non-login automation failure, verify scopes before saving —
|
|
502
|
+
// if the bot genuinely lacks VC permissions, don't silently make it listener.
|
|
503
|
+
if (bot) {
|
|
504
|
+
const scopeCheck = await validateVcMeetingScopesForBot(bot);
|
|
505
|
+
if (!scopeCheck.ok) {
|
|
506
|
+
return {
|
|
507
|
+
ok: false,
|
|
508
|
+
error: `vcMeetingAgent_listenerBot_missing_scopes: ${scopeCheck.error}。自动化配置失败(${reason})且权限未满足,请手动开通后重试。`,
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
// Also check event subscription status — if event update failed (all 3
|
|
513
|
+
// endpoints) before the downstream api_error hit, the bot still won't
|
|
514
|
+
// receive meeting invite events → listener black hole.
|
|
515
|
+
if (result.eventWarning && (result.subscribedEventCount ?? 0) === 0) {
|
|
516
|
+
return {
|
|
517
|
+
ok: false,
|
|
518
|
+
error: `vcMeetingAgent_listenerBot_event_subscribe_failed: 事件订阅失败(${result.eventWarning}),bot 无法接收会议邀请事件。自动化配置失败(${reason}),请手动订阅 VC 会议事件后重试。`,
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
catch (err) {
|
|
524
|
+
logger.warn(`[vc-agent] open-platform automation error for ${nextAppId}: ${err?.message ?? err}`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
376
528
|
const changedAppIds = new Set();
|
|
377
529
|
try {
|
|
378
530
|
const path = requireConfigPath();
|
|
@@ -391,6 +543,23 @@ async function syncVcMeetingListenerBotConfig(listenerBotAppId, previousListener
|
|
|
391
543
|
next.dashboardManagedListener = true;
|
|
392
544
|
entryChanged = true;
|
|
393
545
|
}
|
|
546
|
+
if (!next.larkCliProfile) {
|
|
547
|
+
next.larkCliProfile = nextAppId;
|
|
548
|
+
entryChanged = true;
|
|
549
|
+
}
|
|
550
|
+
const mc = next.meetingConsumer;
|
|
551
|
+
if (!mc || typeof mc !== 'object' || Array.isArray(mc)) {
|
|
552
|
+
next.meetingConsumer = { enabled: true };
|
|
553
|
+
entryChanged = true;
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
const mcRec = mc;
|
|
557
|
+
if (mcRec.enabled !== true) {
|
|
558
|
+
mcRec.enabled = true;
|
|
559
|
+
next.meetingConsumer = mcRec;
|
|
560
|
+
entryChanged = true;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
394
563
|
if (entryChanged) {
|
|
395
564
|
compactVcMeetingAgentEntry(entry, next);
|
|
396
565
|
changed = true;
|
|
@@ -426,6 +595,7 @@ async function syncVcMeetingListenerBotConfig(listenerBotAppId, previousListener
|
|
|
426
595
|
function resolveDashboardSettings() {
|
|
427
596
|
const global = readGlobalConfig();
|
|
428
597
|
const dashboard = global.dashboard ?? {};
|
|
598
|
+
const larkCli = checkLarkCliVersion();
|
|
429
599
|
return {
|
|
430
600
|
publicReadOnly: dashboard.publicReadOnly ?? config.dashboard.publicReadOnly,
|
|
431
601
|
openTerminalInFeishu: dashboard.openTerminalInFeishu === true,
|
|
@@ -434,6 +604,9 @@ function resolveDashboardSettings() {
|
|
|
434
604
|
enabled: global.vcMeetingAgent?.enabled !== false,
|
|
435
605
|
listenerBotAppId: global.vcMeetingAgent?.listenerBotAppId ?? null,
|
|
436
606
|
listenerBotOptions: vcMeetingListenerBotOptions(),
|
|
607
|
+
larkCliVersion: larkCli?.version ?? null,
|
|
608
|
+
larkCliMeetsRequirement: larkCli?.meetsVcBotRequirement ?? false,
|
|
609
|
+
larkCliMinVersion: MIN_LARK_CLI_VERSION_FOR_VC_BOT,
|
|
437
610
|
},
|
|
438
611
|
repoPickerMode: global.repoPickerMode ?? 'all',
|
|
439
612
|
maintenance: global.maintenance ?? {},
|
|
@@ -1453,8 +1626,12 @@ const server = createServer(async (req, res) => {
|
|
|
1453
1626
|
return jsonRes(res, 400, { ok: false, error: 'bad_json' });
|
|
1454
1627
|
}
|
|
1455
1628
|
const result = await applySettingsWrite(parsed, settingsWriteApplierDeps);
|
|
1456
|
-
if (!result.ok)
|
|
1457
|
-
|
|
1629
|
+
if (!result.ok) {
|
|
1630
|
+
const body = { ok: false, error: result.error };
|
|
1631
|
+
if ('feishuLoginQr' in result && result.feishuLoginQr)
|
|
1632
|
+
body.feishuLoginQr = result.feishuLoginQr;
|
|
1633
|
+
return jsonRes(res, 400, body);
|
|
1634
|
+
}
|
|
1458
1635
|
return jsonRes(res, 200, { ok: true, settings: result.settings });
|
|
1459
1636
|
}
|
|
1460
1637
|
// ─── Version & manual update ─────────────────────────────────────────────
|