botmux 3.18.7 → 3.18.8
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/.runtime-build-id +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.d.ts +57 -0
- package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.js +200 -21
- package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
- package/dist/adapters/cli/pi-turn-boundary-extension.d.ts +66 -0
- package/dist/adapters/cli/pi-turn-boundary-extension.d.ts.map +1 -0
- package/dist/adapters/cli/pi-turn-boundary-extension.js +106 -0
- package/dist/adapters/cli/pi-turn-boundary-extension.js.map +1 -0
- package/dist/adapters/cli/pi.d.ts +29 -0
- package/dist/adapters/cli/pi.d.ts.map +1 -1
- package/dist/adapters/cli/pi.js +61 -10
- package/dist/adapters/cli/pi.js.map +1 -1
- package/dist/bot-registry.d.ts +9 -0
- package/dist/bot-registry.d.ts.map +1 -1
- package/dist/bot-registry.js +5 -0
- package/dist/bot-registry.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +8 -1
- package/dist/cli.js.map +1 -1
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
- package/dist/core/dashboard-ipc-server.js +24 -0
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +134 -17
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +5 -3
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/bot-payload.d.ts +2 -0
- package/dist/dashboard/bot-payload.d.ts.map +1 -1
- package/dist/dashboard/bot-payload.js +2 -0
- package/dist/dashboard/bot-payload.js.map +1 -1
- package/dist/dashboard/groups-matrix-snapshot.d.ts +27 -0
- package/dist/dashboard/groups-matrix-snapshot.d.ts.map +1 -1
- package/dist/dashboard/groups-matrix-snapshot.js +16 -0
- package/dist/dashboard/groups-matrix-snapshot.js.map +1 -1
- package/dist/dashboard/web/bot-defaults-page.d.ts +4 -0
- package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults-page.js +52 -9
- package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
- package/dist/dashboard/web/bot-defaults.d.ts +29 -0
- package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults.js +45 -1
- package/dist/dashboard/web/bot-defaults.js.map +1 -1
- package/dist/dashboard/web/bot-onboarding.d.ts.map +1 -1
- package/dist/dashboard/web/bot-onboarding.js +5 -0
- package/dist/dashboard/web/bot-onboarding.js.map +1 -1
- package/dist/dashboard/web/dashboard-components.d.ts +29 -0
- package/dist/dashboard/web/dashboard-components.d.ts.map +1 -1
- package/dist/dashboard/web/dashboard-components.js +96 -4
- package/dist/dashboard/web/dashboard-components.js.map +1 -1
- package/dist/dashboard/web/groups-api.d.ts +11 -0
- package/dist/dashboard/web/groups-api.d.ts.map +1 -1
- package/dist/dashboard/web/groups-api.js +51 -0
- package/dist/dashboard/web/groups-api.js.map +1 -1
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +10 -0
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/overview.d.ts.map +1 -1
- package/dist/dashboard/web/overview.js +11 -2
- package/dist/dashboard/web/overview.js.map +1 -1
- package/dist/dashboard/web/ui.d.ts.map +1 -1
- package/dist/dashboard/web/ui.js +12 -2
- package/dist/dashboard/web/ui.js.map +1 -1
- package/dist/dashboard-web/app.js +1 -1
- package/dist/dashboard-web/chunks/{agent-workbench-dock-page-6LC422N6.js → agent-workbench-dock-page-FAL2YGY5.js} +1 -1
- package/dist/dashboard-web/chunks/{agent-workbench-page-RSAWY7O2.js → agent-workbench-page-G65IOPLY.js} +1 -1
- package/dist/dashboard-web/chunks/bot-defaults-page-3BNFNZEO.js +1 -0
- package/dist/dashboard-web/chunks/chunk-2XHWGUTB.js +10 -0
- package/dist/dashboard-web/chunks/{chunk-R3LJUCEO.js → chunk-3OJQKYM3.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-K232GT3M.js → chunk-IRFGJSPL.js} +5 -5
- package/dist/dashboard-web/chunks/{chunk-IA5KMD5O.js → chunk-IYVXEFKU.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-YJA3KY3J.js → chunk-MYLVEXYF.js} +1 -1
- package/dist/dashboard-web/chunks/chunk-QALRQEKD.js +1 -0
- package/dist/dashboard-web/chunks/{connectors-page-DJCTHSFB.js → connectors-page-K6VZNYUQ.js} +1 -1
- package/dist/dashboard-web/chunks/{customization-page-VQJAEDZY.js → customization-page-FRP6RFOI.js} +1 -1
- package/dist/dashboard-web/chunks/{feedback-page-O7SZRKNM.js → feedback-page-4X7N5J33.js} +1 -1
- package/dist/dashboard-web/chunks/{groups-page-7PONUYHE.js → groups-page-RFQVUO6K.js} +1 -1
- package/dist/dashboard-web/chunks/{insights-page-J6DAAJ6D.js → insights-page-XWH77GX7.js} +1 -1
- package/dist/dashboard-web/chunks/{monitor-room-F3QNEW7N.js → monitor-room-WFU3745R.js} +1 -1
- package/dist/dashboard-web/chunks/{monitoring-page-JHDD2VS2.js → monitoring-page-3NYPZEQT.js} +1 -1
- package/dist/dashboard-web/chunks/{office-page-MMTN4IKD.js → office-page-4I3X6PTF.js} +1 -1
- package/dist/dashboard-web/chunks/overview-page-OQWLVTKT.js +1 -0
- package/dist/dashboard-web/chunks/{roles-page-FNI4MGX6.js → roles-page-RKUGNQOW.js} +1 -1
- package/dist/dashboard-web/chunks/{schedules-page-6PUU63KS.js → schedules-page-SAJKYRZ6.js} +1 -1
- package/dist/dashboard-web/chunks/{sessions-page-O4N72EXT.js → sessions-page-IU6VNSX4.js} +1 -1
- package/dist/dashboard-web/chunks/{settings-page-G7B3STUF.js → settings-page-WTIPX7BQ.js} +1 -1
- package/dist/dashboard-web/chunks/{skills-page-CEWKQOIG.js → skills-page-MKG3HJIU.js} +1 -1
- package/dist/dashboard-web/chunks/{team-federation-page-NUP4VK4H.js → team-federation-page-7SMZJWPT.js} +1 -1
- package/dist/dashboard-web/chunks/{v3-page-M4XF6D5Z.js → v3-page-JMCFLY4Y.js} +1 -1
- package/dist/dashboard-web/chunks/{whiteboards-page-L755NZX6.js → whiteboards-page-5YTWY5EB.js} +1 -1
- package/dist/dashboard-web/style.css +65 -3
- package/dist/dashboard.js +50 -4
- package/dist/dashboard.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +4 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +4 -1
- 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 +141 -38
- package/dist/im/lark/event-dispatcher.js.map +1 -1
- package/dist/services/card-prefs-store.d.ts +2 -0
- package/dist/services/card-prefs-store.d.ts.map +1 -1
- package/dist/services/card-prefs-store.js +9 -1
- package/dist/services/card-prefs-store.js.map +1 -1
- package/dist/services/pi-transcript.d.ts +21 -1
- package/dist/services/pi-transcript.d.ts.map +1 -1
- package/dist/services/pi-transcript.js +188 -8
- package/dist/services/pi-transcript.js.map +1 -1
- package/dist/services/session-store.d.ts.map +1 -1
- package/dist/services/session-store.js +495 -11
- package/dist/services/session-store.js.map +1 -1
- package/dist/services/under-review-notify-store.d.ts +18 -0
- package/dist/services/under-review-notify-store.d.ts.map +1 -0
- package/dist/services/under-review-notify-store.js +67 -0
- package/dist/services/under-review-notify-store.js.map +1 -0
- package/dist/setup/open-platform-automation.d.ts +210 -2
- package/dist/setup/open-platform-automation.d.ts.map +1 -1
- package/dist/setup/open-platform-automation.js +538 -44
- package/dist/setup/open-platform-automation.js.map +1 -1
- package/dist/setup/open-platform-outcome.d.ts.map +1 -1
- package/dist/setup/open-platform-outcome.js +5 -0
- package/dist/setup/open-platform-outcome.js.map +1 -1
- package/dist/workflows/events/payloads.d.ts +6 -6
- package/dist/workflows/events/schema.d.ts +12 -12
- package/package.json +9 -7
- package/dist/dashboard-web/chunks/bot-defaults-page-67RAWCSQ.js +0 -1
- package/dist/dashboard-web/chunks/chunk-MMLTCXHP.js +0 -10
- package/dist/dashboard-web/chunks/chunk-NYY66DNC.js +0 -1
- package/dist/dashboard-web/chunks/overview-page-IQ2UJAJP.js +0 -1
|
@@ -23,9 +23,10 @@ import { isTeamGroupChat } from '../../services/team-groups-store.js';
|
|
|
23
23
|
import { isPlatformTeamBot, isPlatformHallChat, isPlatformTeamMember } from '../../services/platform-team-store.js';
|
|
24
24
|
import { getBotUnionId, recordBotUnionId, recordBotUnionIdFromMentions } from '../../services/bot-union-ids-store.js';
|
|
25
25
|
import { getDocSubscription, putDocSubscription, removeDocSubscription, listAllDocSubscriptions } from '../../services/doc-subs-store.js';
|
|
26
|
+
import { wasPendingReviewNotified, markPendingReviewNotified } from '../../services/under-review-notify-store.js';
|
|
26
27
|
import { getDocComment, isBotAuthoredReply, hasBotSentinel, commentTriggerAllowed, BOT_REPLY_SENTINEL } from './doc-comment.js';
|
|
27
28
|
import { BOTMUX_REQUIRED_SCOPES, DOC_FEATURE_SCOPES, DOC_WATCH_SCOPES, DOC_COMMENT_EVENT, VC_MEETING_BOT_EVENTS, VC_MEETING_FEATURE_SCOPES, VC_MEETING_REALTIME_VOICE_SCOPES, buildEventSubDeepLink, buildScopeDeepLink, } from '../../setup/verify-permissions.js';
|
|
28
|
-
import { automateOpenPlatformSetup, probeVcMeetingEventSubscription, readDefaultScopeManifest, filterScopeManifest } from '../../setup/open-platform-automation.js';
|
|
29
|
+
import { automateOpenPlatformSetup, probeVcMeetingEventSubscription, readDefaultScopeManifest, filterScopeManifest, inspectUnderReviewConfigHints } from '../../setup/open-platform-automation.js';
|
|
29
30
|
import { larkHosts, normalizeBrand, sdkDomain } from './lark-hosts.js';
|
|
30
31
|
import { tryHandleGrantCommand } from './grant-command.js';
|
|
31
32
|
import { tryHandleInviteCommand } from './invite-command.js';
|
|
@@ -184,36 +185,28 @@ const SELF_MANAGE_SCOPE = 'application:application:self_manage';
|
|
|
184
185
|
function getAdminOpenId(bot) {
|
|
185
186
|
return bot.resolvedAllowedUsers.find(u => u.startsWith('ou_'));
|
|
186
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* 给管理员发一条提醒。**返回是否真的发出去了。**
|
|
190
|
+
*
|
|
191
|
+
* 为什么要返回值:多数调用点是 best-effort、不关心成败(所以这里吞掉异常而不是抛,
|
|
192
|
+
* 避免一次 DM 失败中断整条自检流程)。但「按待审版本节流」那处必须知道成败 ——
|
|
193
|
+
* 先记「已通知」再发的话,一次网络失败就把那个版本永久节流掉,人再也收不到提醒,
|
|
194
|
+
* 且是零信号的静默丢失。所以由这里如实回报,让**只有关心的那一处**去判断。
|
|
195
|
+
*/
|
|
187
196
|
async function dmAdmin(larkAppId, adminOpenId, content, contextTag) {
|
|
188
197
|
try {
|
|
189
198
|
await sendUserMessage(larkAppId, adminOpenId, content, 'text');
|
|
190
199
|
logger.info(`[${larkAppId}] notified admin ${adminOpenId.substring(0, 12)} about ${contextTag}`);
|
|
200
|
+
return true;
|
|
191
201
|
}
|
|
192
202
|
catch (err) {
|
|
193
203
|
logger.warn(`[${larkAppId}] failed to DM admin about ${contextTag}: ${err?.message ?? err}`);
|
|
204
|
+
return false;
|
|
194
205
|
}
|
|
195
206
|
}
|
|
196
|
-
/**
|
|
197
|
-
* Try to auto-fix missing scopes using the same Open Platform web-session
|
|
198
|
-
* automation that powers `botmux setup`. If ~/.botmux/feishu-session.json holds
|
|
199
|
-
* a valid cached session, this adds all botmux-required scopes and publishes
|
|
200
|
-
* a new app version — no user interaction needed.
|
|
201
|
-
*
|
|
202
|
-
* Returns `true` if scopes were successfully applied (caller should stop),
|
|
203
|
-
* `false` to fall through to the manual DM warning.
|
|
204
|
-
*
|
|
205
|
-
* `opts.disableQrLogin` — when the only thing missing is a non-critical scope,
|
|
206
|
-
* we still try to grab it (the automation imports the full manifest), but must
|
|
207
|
-
* NOT pop a second QR code if the cached session is gone. Passing this makes a
|
|
208
|
-
* missing/expired session fail cleanly (reason `invalid_session`) instead of
|
|
209
|
-
* prompting a login.
|
|
210
|
-
* `opts.silent` — suppress the admin success DM (used for the opt-in / optional
|
|
211
|
-
* path so a bot that never asked for the feature isn't pinged; the log line is
|
|
212
|
-
* enough). Failures are always silent here regardless.
|
|
213
|
-
*/
|
|
214
207
|
async function tryAutoFixScopes(larkAppId, bot, brand, missingCritical, missingOptional, opts) {
|
|
215
208
|
if (brand !== 'feishu')
|
|
216
|
-
return
|
|
209
|
+
return { kind: 'failed' };
|
|
217
210
|
try {
|
|
218
211
|
const totalMissing = missingCritical.length + missingOptional.length;
|
|
219
212
|
logger.info(`[${larkAppId}] attempting auto-fix for ${totalMissing} missing scopes via Open Platform...`);
|
|
@@ -271,15 +264,28 @@ async function tryAutoFixScopes(larkAppId, bot, brand, missingCritical, missingO
|
|
|
271
264
|
: result.privilegeRangeWarning
|
|
272
265
|
? `, 权限数据范围未能自动配置(${result.privilegeRangeWarning})`
|
|
273
266
|
: '';
|
|
274
|
-
//
|
|
275
|
-
//
|
|
267
|
+
// 三种结局互斥,判定顺序不能反(#1044 的 publishSkipped 必须最先判):
|
|
268
|
+
// ① publishSkipped —— 无变更、**根本没建版本**,报 `version n/a published` 会误导
|
|
269
|
+
// ② versionWarning —— 建/提交了但回读仍是草稿。「version X published」曾是**谎报**:
|
|
270
|
+
// `publish/commit` 回 code=0 而版本仍停在未提交草稿态,日志照样写 published,
|
|
271
|
+
// 于是那个草稿一路卡死后续每一次自愈(`app_version/create` 撞 code=10043)。
|
|
272
|
+
// ③ 正常发布
|
|
276
273
|
const versionDetail = result.publishSkipped
|
|
277
274
|
? '无变更,已跳过发版'
|
|
278
|
-
:
|
|
275
|
+
: result.versionWarning
|
|
276
|
+
? `version ${result.versionId ?? 'n/a'} NOT published(${result.versionWarning})`
|
|
277
|
+
: `version ${result.versionId ?? 'n/a'} ${result.versionReused ? '(复用既有草稿) ' : ''}published`;
|
|
278
|
+
// 「秒过」与「要人审」是两种截然不同的结局,日志必须分开——否则运维看到
|
|
279
|
+
// published 会以为权限已经生效,而实际上可能还在等某个人点同意。
|
|
280
|
+
const approvalDetail = result.approvalAutoPassed === true
|
|
281
|
+
? '(审批流自动通过,已即时生效)'
|
|
282
|
+
: result.approvalHumanApprovers?.length
|
|
283
|
+
? `(需人工审批:${result.approvalHumanApprovers.join('、')})`
|
|
284
|
+
: '';
|
|
279
285
|
const summary = `[${larkAppId}] auto-fix ${autoFixEffective ? 'succeeded' : 'could NOT apply the missing scopes'}: ${scopeDetail}${privilegeRangeDetail}, ` +
|
|
280
|
-
`${versionDetail}, ` +
|
|
286
|
+
`${versionDetail}${approvalDetail}, ` +
|
|
281
287
|
`${result.subscribedEventCount} events subscribed`;
|
|
282
|
-
if (autoFixEffective)
|
|
288
|
+
if (autoFixEffective && !result.versionWarning)
|
|
283
289
|
logger.info(summary);
|
|
284
290
|
else
|
|
285
291
|
logger.warn(summary);
|
|
@@ -287,7 +293,7 @@ async function tryAutoFixScopes(larkAppId, bot, brand, missingCritical, missingO
|
|
|
287
293
|
// never enabled the feature must not be pinged just because a non-critical
|
|
288
294
|
// scope was topped up in the background). The log line above is the record.
|
|
289
295
|
if (opts?.silent)
|
|
290
|
-
return
|
|
296
|
+
return { kind: 'fixed' };
|
|
291
297
|
// Notify admin that auto-fix worked — even if im:message was missing before,
|
|
292
298
|
// the newly published version should now have it.
|
|
293
299
|
const adminOpenId = getAdminOpenId(bot);
|
|
@@ -296,14 +302,30 @@ async function tryAutoFixScopes(larkAppId, bot, brand, missingCritical, missingO
|
|
|
296
302
|
const missingList = fixedList.map(s => `• ${s.desc} (\`${s.name}\`)`).join('\n');
|
|
297
303
|
// 标题跟着实际结果走:一项都没落地时说「已自动修复」是谎报,管理员会以为
|
|
298
304
|
// 不用管,而这条路径的下游(缺 critical scope)恰恰是最需要人工介入的。
|
|
299
|
-
|
|
305
|
+
// 同理「新版本已发布」也不能写死:scope 写进了清单但版本没提交,权限就**不会
|
|
306
|
+
// 生效**,而管理员读到「已发布」只会干等。versionWarning 有值时直接给出
|
|
307
|
+
// 手动收尾的动作。
|
|
308
|
+
//
|
|
309
|
+
// 三种结局要分清,因为「用户接下来该做什么」完全不同:
|
|
310
|
+
// ① 秒过(审批流全自动通过)→ 已经生效了,什么都不用做
|
|
311
|
+
// ② 要人审 → 已提交,但在等具体某个人;告诉他等谁,免得重复点或干等
|
|
312
|
+
// ③ 判不出来 → 保持中性措辞,不猜
|
|
313
|
+
const versionLine = result.versionWarning
|
|
314
|
+
? `⚠️ 但新版本**没有成功提交发布**(${result.versionWarning})。权限要等版本发布后才生效。`
|
|
315
|
+
: result.approvalAutoPassed === true
|
|
316
|
+
? '新版本已提交,审批流**自动通过**,权限已即时生效。'
|
|
317
|
+
: result.approvalHumanApprovers?.length
|
|
318
|
+
? `新版本已提交,但需要**人工审批**(审批人:${result.approvalHumanApprovers.join('、')}),`
|
|
319
|
+
+ '通过后权限才生效——不用重复提交,等审批即可。'
|
|
320
|
+
: '新版本已发布。';
|
|
321
|
+
await dmAdmin(larkAppId, adminOpenId, (autoFixEffective && !result.versionWarning
|
|
300
322
|
? `✅ botmux 已自动为机器人 "${bot.botName ?? larkAppId}" 修复了缺失的权限:\n\n`
|
|
301
|
-
: `⚠️ botmux 尝试自动为机器人 "${bot.botName ?? larkAppId}"
|
|
323
|
+
: `⚠️ botmux 尝试自动为机器人 "${bot.botName ?? larkAppId}" 补齐以下权限,但没能全部落地,需要你手动收尾:\n\n`) +
|
|
302
324
|
`${missingList}\n\n` +
|
|
303
|
-
`${scopeDetail}
|
|
325
|
+
`${scopeDetail},${versionLine}\n` +
|
|
304
326
|
`权限变更可能需要 1-2 分钟生效。如仍有问题执行 \`botmux restart\`。`, autoFixEffective ? `auto-fixed ${fixedList.length} scopes` : `auto-fix could not apply ${fixedList.length} scopes`);
|
|
305
327
|
}
|
|
306
|
-
return
|
|
328
|
+
return { kind: 'fixed' };
|
|
307
329
|
}
|
|
308
330
|
// Auto-fix failed — log reason. Critical path falls through to a manual
|
|
309
331
|
// deep-link DM; the silent optional-scope top-up path does NOT DM (it just
|
|
@@ -319,15 +341,32 @@ async function tryAutoFixScopes(larkAppId, bot, brand, missingCritical, missingO
|
|
|
319
341
|
missing_csrf: '开放平台页面未返回 CSRF token',
|
|
320
342
|
scope_mapping_failed: '权限映射失败',
|
|
321
343
|
api_error: '开放平台 API 错误',
|
|
344
|
+
app_under_review: '应用正在飞书审核中(配置被开放平台写锁,审批通过后自动恢复)',
|
|
322
345
|
};
|
|
323
|
-
|
|
346
|
+
// ⚠️ 泛化标签和具体消息**两个都要打**。这里曾写成
|
|
347
|
+
// `reasons[result.reason] ?? result.message`——`api_error` 在表里有值,`??`
|
|
348
|
+
// 永远短路,于是 `result.message` 一次都进不了日志。线上真实后果:一天 5 次自愈
|
|
349
|
+
// 失败只留下 5 行「开放平台 API 错误」,而被吞掉的那句正是唯一有用的诊断
|
|
350
|
+
// (`code=10043 版本已创建,请刷新`),排查只能靠重放整条链路才拿到。
|
|
351
|
+
// 泛化标签帮人快速归类,具体消息才说得清到底哪儿坏——别再让前者盖掉后者。
|
|
352
|
+
const reasonLabel = reasons[result.reason] ?? result.reason;
|
|
353
|
+
// 审核中不是「修不了」,是「现在不能改、审批通过就好」。日志级别降为 info 并显式
|
|
354
|
+
// 说明不会落回深链 DM——把权限链接推给管理员是**错误的建议**:审核期间开放平台
|
|
355
|
+
// 连 scope/update 都拒(实测 code=10046),点了也写不进去。
|
|
356
|
+
if (result.reason === 'app_under_review') {
|
|
357
|
+
logger.info(`[${larkAppId}] auto-fix deferred: ${reasonLabel}` +
|
|
358
|
+
`${result.message ? ` — ${result.message}` : ''}`);
|
|
359
|
+
return { kind: 'under_review', inReviewVersionId: result.inReviewVersionId };
|
|
360
|
+
}
|
|
361
|
+
logger.warn(`[${larkAppId}] auto-fix not possible (${result.reason}: ${reasonLabel}` +
|
|
362
|
+
`${result.message ? ` — ${result.message}` : ''}). ` +
|
|
324
363
|
(opts?.silent ? 'Leaving opt-in scope ungranted (no DM).' : 'Falling back to manual deep-link DM.'));
|
|
325
|
-
return
|
|
364
|
+
return { kind: 'failed' };
|
|
326
365
|
}
|
|
327
366
|
catch (err) {
|
|
328
367
|
logger.warn(`[${larkAppId}] auto-fix error: ${err?.message ?? err}` +
|
|
329
368
|
(opts?.silent ? ' — leaving opt-in scope ungranted (no DM)' : ' — falling back to manual DM'));
|
|
330
|
-
return
|
|
369
|
+
return { kind: 'failed' };
|
|
331
370
|
}
|
|
332
371
|
}
|
|
333
372
|
export async function checkRequiredScopes(larkAppId) {
|
|
@@ -360,7 +399,9 @@ export async function checkRequiredScopes(larkAppId) {
|
|
|
360
399
|
if (brand === 'feishu') {
|
|
361
400
|
const requiredNow = BOTMUX_REQUIRED_SCOPES.map(s => ({ name: s.name, desc: s.desc }));
|
|
362
401
|
const fixed = await tryAutoFixScopes(larkAppId, bot, brand, requiredNow, []);
|
|
363
|
-
|
|
402
|
+
// 审核中同样直接返回:这条路径下游是让管理员去点 self_manage 深链,而审核期间
|
|
403
|
+
// 写锁生效,点了也开不了——等审批通过下次重启自检即可。
|
|
404
|
+
if (fixed.kind !== 'failed')
|
|
364
405
|
return;
|
|
365
406
|
}
|
|
366
407
|
const selfManageAuthUrl = buildScopeDeepLink(bot.config.larkAppId, SELF_MANAGE_SCOPE, brand);
|
|
@@ -512,10 +553,14 @@ export async function checkRequiredScopes(larkAppId) {
|
|
|
512
553
|
// before (no API call / no prompt / no nag).
|
|
513
554
|
if (missingOptional.length > 0 && brand === 'feishu') {
|
|
514
555
|
const toppedUp = await tryAutoFixScopes(larkAppId, bot, brand, [], missingOptional, { disableQrLogin: true, silent: true, grantedScopeNames: grantedScopeBuckets });
|
|
515
|
-
|
|
556
|
+
// 只有真的补上了才说「auto-topped-up」:审核中什么都没写进去(开放平台
|
|
557
|
+
// 连 scope/update 都拒),说补上了就是谎报。审核中静默跳过,等审批通过。
|
|
558
|
+
if (toppedUp.kind === 'fixed') {
|
|
516
559
|
logger.info(`[${larkAppId}] auto-topped-up ${missingOptional.length} optional scope(s): ${missingOptional.map(s => s.name).join('、')}`);
|
|
517
560
|
return;
|
|
518
561
|
}
|
|
562
|
+
if (toppedUp.kind === 'under_review')
|
|
563
|
+
return;
|
|
519
564
|
logger.debug(`[${larkAppId}] optional scope(s) missing (${missingOptional.map(s => s.name).join('、')}); no cached web session to auto-apply — leaving to opt-in feature owner`);
|
|
520
565
|
}
|
|
521
566
|
logger.info(`[${larkAppId}] all critical scopes granted (${BOTMUX_REQUIRED_SCOPES.filter(s => s.critical).length} checked)`);
|
|
@@ -526,8 +571,49 @@ export async function checkRequiredScopes(larkAppId) {
|
|
|
526
571
|
// directly add missing scopes and publish a new version without user interaction.
|
|
527
572
|
// Falls through to manual DM warning if session is missing/expired.
|
|
528
573
|
const autoFixed = await tryAutoFixScopes(larkAppId, bot, brand, missingCritical, missingOptional, { grantedScopeNames: grantedScopeBuckets });
|
|
529
|
-
if (autoFixed)
|
|
574
|
+
if (autoFixed.kind === 'fixed')
|
|
530
575
|
return;
|
|
576
|
+
// 审核中:**不推权限深链**(审核期间连 scope/update 都被拒,点了写不进去),
|
|
577
|
+
// 也**不说「等审批通过就好」**——触发审批通常意味着有配置不合规,那一版不会自己
|
|
578
|
+
// 通过(模板建的应用出生带「数据范围=全部」,正撞租户「非必要不申请全员数据」的
|
|
579
|
+
// 加签规则)。所以这里只做一件事:**如实告诉管理员卡在哪、为什么卡、怎么修**。
|
|
580
|
+
if (autoFixed.kind === 'under_review') {
|
|
581
|
+
const summary = missingCritical.map(s => `${s.name} (${s.desc})`).join('、');
|
|
582
|
+
logger.warn(`[${larkAppId}] 缺少 ${missingCritical.length} 项必需权限(${summary}),` +
|
|
583
|
+
'但应用有一个版本卡在飞书审核中、配置暂时不可写;需人工修配置后撤回重提,botmux 无法自动解决。');
|
|
584
|
+
const reviewAdmin = getAdminOpenId(bot);
|
|
585
|
+
// 🔴 同一个待审版本只打扰一次。不做节流的话,卡在审核中是个持续数天的状态
|
|
586
|
+
//(线上实测 18 天 / 23 天),而本函数每次 daemon 启动都跑一遍 ⟹ 卡 N 天就发 N 条
|
|
587
|
+
// 一模一样的 DM,等于把这次要消灭的刷屏换个文案请回来。
|
|
588
|
+
// key 用待审版本 id:对齐「事情有没有变化」而非「过了多久」——人撤回重提了新版本,
|
|
589
|
+
// 该说;同一版还卡着,不必再说。拿不到 id 时**刻意不节流**(见 store 注释)。
|
|
590
|
+
const alreadyNotified = wasPendingReviewNotified(config.session.dataDir, larkAppId, autoFixed.inReviewVersionId);
|
|
591
|
+
if (reviewAdmin && !alreadyNotified) {
|
|
592
|
+
const rangeHint = await inspectUnderReviewConfigHints(bot.config.larkAppId, brand);
|
|
593
|
+
const delivered = await dmAdmin(larkAppId, reviewAdmin, `⏳ 机器人 "${bot.botName ?? larkAppId}" 还缺 ${missingCritical.length} 项必需权限,`
|
|
594
|
+
+ `而它有一个版本**卡在飞书审核中**,开放平台锁定了配置写入,botmux 补不了:\n\n`
|
|
595
|
+
+ `${missingCritical.map(s => `• ${s.desc} (\`${s.name}\`)`).join('\n')}\n\n`
|
|
596
|
+
+ `**为什么会进审核**:触发企业审批通常说明有配置不合规,最常见是权限的「数据范围」`
|
|
597
|
+
+ `没配、默认成「全部/全员」,撞上租户「非必要不申请全员数据,如申请全员范围需提供`
|
|
598
|
+
+ `充分理由、视情况加签至 CEO-2」的规则。${rangeHint}\n\n`
|
|
599
|
+
+ `**怎么修**(这一版不会自己通过,需要人工):\n`
|
|
600
|
+
+ `1. 开放平台 →「版本管理与发布」→ 点进审核中那一版的**版本详情**,看审批详情里被卡的项\n`
|
|
601
|
+
+ `2. 修掉不合规项(如把权限的数据范围从「全部」改成「与应用的可用范围一致」)\n`
|
|
602
|
+
+ `3. 在版本详情页点 **Withdraw** 撤回该版本\n`
|
|
603
|
+
+ `4. 执行 \`botmux restart\` —— botmux 会自动补齐权限并重新提交\n\n`
|
|
604
|
+
+ `⚠️ 直接撤回重提**不管用**:不改配置的话同一条规则会再拦一次。\n`
|
|
605
|
+
+ `⚠️ 此时去权限管理页手动点开通也**无效**:审核期间飞书连「申请权限」都拒绝。`, `app under review, ${missingCritical.length} scopes deferred`);
|
|
606
|
+
// ⚠️ **发送成功才记账**。反了的话一次网络失败就把这个 versionId 永久节流掉,
|
|
607
|
+
// 管理员再也收不到提醒,而且是零信号的静默丢失,比刷屏更难发现。
|
|
608
|
+
if (delivered) {
|
|
609
|
+
markPendingReviewNotified(config.session.dataDir, larkAppId, autoFixed.inReviewVersionId);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
else if (alreadyNotified) {
|
|
613
|
+
logger.info(`[${larkAppId}] 待审版本 ${autoFixed.inReviewVersionId} 已通知过管理员,本次不重复打扰`);
|
|
614
|
+
}
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
531
617
|
// Log + DM consolidated message listing all missing critical scopes.
|
|
532
618
|
const summaryLine = missingCritical.map(s => `${s.name} (${s.desc})`).join('、');
|
|
533
619
|
logger.error(`[${larkAppId}] 缺少 ${missingCritical.length} 项必需权限:${summaryLine}。` +
|
|
@@ -602,11 +688,28 @@ export async function ensureVcMeetingEventsSubscribed(larkAppId) {
|
|
|
602
688
|
}
|
|
603
689
|
// Automation failed (session expired mid-run / api error). Log; DM the admin
|
|
604
690
|
// once so a bot that genuinely lacks the subscription gets a human nudge.
|
|
605
|
-
|
|
691
|
+
//
|
|
692
|
+
// 「应用正在审核中」要单独说:审核期间开放平台锁配置写入,刷新登录态**没有用**
|
|
693
|
+
// (不是 session 问题),等审批通过重启即可。给错的操作建议比不给更糟——用户会
|
|
694
|
+
// 反复 `botmux setup` 却始终不见好。
|
|
695
|
+
const underReview = !result.ok && result.reason === 'app_under_review';
|
|
696
|
+
if (underReview) {
|
|
697
|
+
logger.info(`[${larkAppId}] VC event auto-subscribe deferred (app under review): ${result.message}`);
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
logger.warn(`[${larkAppId}] VC event auto-subscribe failed (${result.reason}): ${result.message}`);
|
|
701
|
+
}
|
|
606
702
|
const adminOpenId = getAdminOpenId(bot);
|
|
607
703
|
if (adminOpenId) {
|
|
608
704
|
await dmAdmin(larkAppId, adminOpenId, `⚠️ botmux 想在启动时自动为机器人 "${bot.botName ?? larkAppId}" 订阅视频会议事件(vc.bot.meeting_*),以便它被拉进会时能自动进会,但自动配置失败:${result.message}\n\n` +
|
|
609
|
-
|
|
705
|
+
(underReview
|
|
706
|
+
// ⚠️ 别说「等审批通过就好」:触发审批通常意味着有配置不合规,那一版**不会自己
|
|
707
|
+
// 通过**,说「等」等于让人干等。也别说「刷新登录态」——审核锁住的是配置写入,
|
|
708
|
+
// 不是 session 问题。
|
|
709
|
+
? '该应用有一个版本**卡在飞书审核中**,配置写入被锁(刷新登录态无效)。触发审批通常说明有配置不合规'
|
|
710
|
+
+ '(最常见:权限的「数据范围」默认成「全部/全员」)。需人工处理:开放平台 →「版本管理与发布」→ 该版本详情 → '
|
|
711
|
+
+ '看审批详情修掉不合规项 → 点 **Withdraw** 撤回 → 执行 `botmux restart` 重试。不改配置直接撤回重提会被同一条规则再拦。'
|
|
712
|
+
: '请运行 `botmux setup` 刷新飞书开放平台登录态后重启 daemon,botmux 会自动重试。'), underReview ? 'vc event auto-subscribe deferred (under review)' : 'vc event auto-subscribe failed');
|
|
610
713
|
}
|
|
611
714
|
}
|
|
612
715
|
catch (err) {
|