@lark-project/openclaw-lark-project 2026.3.169 → 2026.3.170
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/src/tools/auto-auth.js +4 -1
- package/dist/src/tools/auto-auth.js.map +2 -2
- package/dist/src/tools/mcp/project/endpoint.js +7 -3
- package/dist/src/tools/mcp/project/endpoint.js.map +2 -2
- package/dist/src/tools/oauth-cards.js +79 -0
- package/dist/src/tools/oauth-cards.js.map +2 -2
- package/dist/src/tools/project-oauth.js +190 -5
- package/dist/src/tools/project-oauth.js.map +2 -2
- package/package.json +1 -1
- package/skills/feishu-project/SKILL.md +4 -23
|
@@ -8,7 +8,7 @@ import { LarkClient } from "../core/lark-client.js";
|
|
|
8
8
|
import { createCardEntity, sendCardByCardId, updateCardKitCardForAuth } from "../card/cardkit.js";
|
|
9
9
|
import { executeAuthorize } from "./oauth.js";
|
|
10
10
|
import { formatLarkError, json } from "./oapi/helpers.js";
|
|
11
|
-
import { handleProjectAuthCardAction } from "./project-oauth.js";
|
|
11
|
+
import { handleProjectAuthCardAction, handleProjectDomainCardAction } from "./project-oauth.js";
|
|
12
12
|
import { OwnerAccessDeniedError } from "../core/owner-policy.js";
|
|
13
13
|
import { enqueueFeishuChatTask } from "../channel/chat-queue.js";
|
|
14
14
|
import { handleFeishuMessage } from "../messaging/inbound/handler.js";
|
|
@@ -427,6 +427,9 @@ async function handleCardAction(data, cfg, accountId) {
|
|
|
427
427
|
} catch {
|
|
428
428
|
return;
|
|
429
429
|
}
|
|
430
|
+
if (buttonName === "submit_project_domain") {
|
|
431
|
+
return handleProjectDomainCardAction(data, cfg, accountId);
|
|
432
|
+
}
|
|
430
433
|
if (buttonName === "submit_project_auth") {
|
|
431
434
|
return handleProjectAuthCardAction(data, cfg, accountId);
|
|
432
435
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/tools/auto-auth.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * auto-auth.ts \u2014 \u5DE5\u5177\u5C42\u81EA\u52A8\u6388\u6743\u5904\u7406\u3002\n *\n * \u5F53 OAPI \u5DE5\u5177\u9047\u5230\u6388\u6743\u95EE\u9898\u65F6\uFF0C\u76F4\u63A5\u5728\u5DE5\u5177\u5C42\u5904\u7406\uFF0C\u4E0D\u518D\u8BA9 AI \u5224\u65AD\uFF1A\n *\n * - UserAuthRequiredError (appScopeVerified=true)\n * \u2192 \u76F4\u63A5\u8C03\u7528 executeAuthorize \u53D1\u8D77 OAuth Device Flow \u5361\u7247\n *\n * - UserScopeInsufficientError\n * \u2192 \u76F4\u63A5\u8C03\u7528 executeAuthorize\uFF08\u4F7F\u7528 missingScopes\uFF09\n *\n * - AppScopeMissingError\n * \u2192 \u53D1\u9001\u5E94\u7528\u6743\u9650\u5F15\u5BFC\u5361\u7247\uFF1B\u7528\u6237\u70B9\u51FB\"\u6211\u5DF2\u5B8C\u6210\"\u540E\uFF1A\n * 1. \u66F4\u65B0\u5361\u7247\u4E3A\u5904\u7406\u4E2D\u72B6\u6001\n * 2. invalidateAppScopeCache\n * 3. \u53D1\u9001\u4E2D\u95F4\u5408\u6210\u6D88\u606F\u544A\u77E5 AI\uFF08\"\u5E94\u7528\u6743\u9650\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u53D1\u8D77\u7528\u6237\u6388\u6743...\"\uFF09\n * 4. \u8C03\u7528 executeAuthorize \u53D1\u8D77 OAuth Device Flow\n *\n * - \u5176\u4ED6\u60C5\u51B5\uFF08AppScopeCheckFailedError\u3001appScopeVerified=false \u7B49\uFF09\n * \u2192 \u56DE\u9000\u5230\u539F handleInvokeError\uFF08\u4E0D\u89E6\u53D1\u81EA\u52A8\u6388\u6743\uFF09\n *\n * \u964D\u7EA7\u7B56\u7565\uFF08\u4FDD\u5B88\uFF09\uFF1A\u4EE5\u4E0B\u60C5\u51B5\u5747\u56DE\u9000\u5230 handleInvokeError\uFF1A\n * - \u65E0 LarkTicket\uFF08\u975E\u6D88\u606F\u573A\u666F\uFF09\n * - \u65E0 senderOpenId\uFF08\u65E0\u6CD5\u786E\u5B9A\u6388\u6743\u5BF9\u8C61\uFF09\n * - \u8D26\u53F7\u672A\u914D\u7F6E\uFF08!acct.configured\uFF09\n * - \u4EFB\u4F55\u6B65\u9AA4\u629B\u51FA\u5F02\u5E38\n */\n\nimport type { ClawdbotConfig } from 'openclaw/plugin-sdk';\nimport type { ConfiguredLarkAccount } from '../core/types';\nimport type { LarkTicket } from '../core/lark-ticket';\nimport { getTicket } from '../core/lark-ticket';\nimport { larkLogger } from '../core/lark-logger';\n\nconst log = larkLogger('tools/auto-auth');\nimport { getLarkAccount } from '../core/accounts';\nimport { UserAuthRequiredError, UserScopeInsufficientError, AppScopeMissingError } from '../core/tool-client';\nimport { invalidateAppScopeCache, getAppGrantedScopes, isAppScopeSatisfied } from '../core/app-scope-checker';\nimport { LarkClient } from '../core/lark-client';\nimport { createCardEntity, sendCardByCardId, updateCardKitCardForAuth } from '../card/cardkit';\nimport { executeAuthorize } from './oauth';\nimport { formatLarkError, json } from './oapi/helpers';\nimport { handleProjectAuthCardAction } from './project-oauth';\nimport { OwnerAccessDeniedError } from '../core/owner-policy';\nimport { enqueueFeishuChatTask } from '../channel/chat-queue';\nimport { handleFeishuMessage } from '../messaging/inbound/handler';\nimport { withTicket } from '../core/lark-ticket';\n\n// ---------------------------------------------------------------------------\n// Debounce + scope merge \u2014 \u9632\u6296\u7F13\u51B2\u533A\uFF08\u4E24\u9636\u6BB5\uFF09\n//\n// \u5DE5\u5177\u8C03\u7528\u53EF\u80FD\u662F\u771F\u6B63\u5E76\u53D1\uFF0850ms \u5185\u5230\u8FBE\uFF09\u6216\u88AB\u6846\u67B6\u5E8F\u5217\u5316\uFF08\u95F4\u9694\u6570\u79D2\u5230\u8FBE\uFF09\u3002\n// \u4E3A\u540C\u65F6\u8986\u76D6\u4E24\u79CD\u573A\u666F\uFF0C\u91C7\u7528\u4E24\u9636\u6BB5\u8BBE\u8BA1\uFF1A\n//\n// collecting\uFF08\u6536\u96C6\u9636\u6BB5\uFF09\uFF1A50ms \u9632\u6296\u7A97\u53E3\uFF0C\u5408\u5E76 scope\n// executing\uFF08\u6267\u884C\u9636\u6BB5\uFF09\uFF1AflushFn \u6B63\u5728\u8FD0\u884C\uFF0C\u540E\u7EED\u8BF7\u6C42\u590D\u7528\u540C\u4E00\u7ED3\u679C\n//\n// \u4ECE collecting \u2192 executing \u8F6C\u6362\u65F6\u4E0D\u4ECE Map \u4E2D\u5220\u9664 entry\uFF0C\n// \u76F4\u5230 flushFn \u5B8C\u6210\uFF08resolve / reject\uFF09\u624D\u79FB\u9664\u3002\n// ---------------------------------------------------------------------------\n\ntype JsonResult = ReturnType<typeof json>;\n\n/** \u7F13\u51B2\u4E2D\u7684\u6388\u6743\u8BF7\u6C42 */\ninterface AuthBatchEntry {\n phase: 'collecting' | 'executing';\n scopes: Set<string>;\n waiters: Array<{ resolve: (v: JsonResult) => void; reject: (e: unknown) => void }>;\n timer: ReturnType<typeof setTimeout> | null;\n /** flushFn \u6267\u884C\u4E2D\u7684 Promise\uFF08executing \u9636\u6BB5\u6709\u503C\uFF09 */\n resultPromise: Promise<JsonResult> | null;\n /** executing \u9636\u6BB5\uFF1A\u65B0 scope \u5230\u8FBE\u65F6\u7684\u5EF6\u8FDF\u5237\u65B0\u5B9A\u65F6\u5668 */\n updateTimer: ReturnType<typeof setTimeout> | null;\n /** scope \u66F4\u65B0\u7684 executeAuthorize \u662F\u5426\u6B63\u5728\u6267\u884C\uFF08\u4E92\u65A5\u9501\uFF09 */\n isUpdating: boolean;\n /** isUpdating \u671F\u95F4\u53C8\u6709\u65B0 scope \u5230\u8FBE\uFF0C\u9700\u8981\u518D\u66F4\u65B0\u4E00\u8F6E */\n pendingReupdate: boolean;\n /** flushFn \u5F15\u7528\uFF0Cexecuting \u9636\u6BB5\u7528\u4E8E scope \u66F4\u65B0\u65F6\u91CD\u65B0\u8C03\u7528 */\n flushFn: ((mergedScopes: string[]) => Promise<JsonResult>) | null;\n /** \u4EE5\u4E0B\u5B57\u6BB5\u6765\u81EA\u7B2C\u4E00\u4E2A\u5165\u961F\u7684\u8BF7\u6C42\uFF0C\u540E\u7EED\u8BF7\u6C42\u590D\u7528 */\n account: ConfiguredLarkAccount;\n cfg: ClawdbotConfig;\n ticket: LarkTicket;\n}\n\n/**\n * \u9632\u6296\u7F13\u51B2\u533A Map\u3002\n *\n * Key \u89C4\u5219\uFF1A\n * \u7528\u6237\u6388\u6743\uFF1A`user:${accountId}:${senderOpenId}:${messageId}`\n * \u5E94\u7528\u6388\u6743\uFF1A`app:${accountId}:${chatId}:${messageId}`\n */\nconst authBatches = new Map<string, AuthBatchEntry>();\n\n/** \u9632\u6296\u7A97\u53E3\uFF08\u6BEB\u79D2\uFF09 */\nconst AUTH_DEBOUNCE_MS = 50;\n\n/** \u7528\u6237\u6388\u6743\u9632\u6296\u7A97\u53E3\uFF08\u6BEB\u79D2\uFF09\u3002\u6BD4 app auth \u7684 50ms \u66F4\u957F\uFF0C\u4FDD\u8BC1\u5E94\u7528\u6743\u9650\u5361\u7247\u5148\u53D1\u51FA\u3002 */\nconst AUTH_USER_DEBOUNCE_MS = 150;\n\n/**\n * Scope \u66F4\u65B0\u9632\u6296\u7A97\u53E3\uFF08\u6BEB\u79D2\uFF09\u3002\n * \u6BD4\u521D\u59CB\u9632\u6296\u66F4\u957F\uFF0C\u56E0\u4E3A\u5DE5\u5177\u8C03\u7528\u53EF\u80FD\u95F4\u9694\u6570\u5341\u5230\u6570\u767E\u6BEB\u79D2\u987A\u5E8F\u5230\u8FBE\u3002\n * \u9700\u8981\u7B49\u8DB3\u591F\u4E45\u4EE5\u6536\u96C6\u6240\u6709\u540E\u7EED\u5230\u8FBE\u7684 scope \u540E\u518D\u4E00\u6B21\u6027\u66F4\u65B0\u5361\u7247\u3002\n */\nconst AUTH_UPDATE_DEBOUNCE_MS = 500;\n\n/**\n * \u51B7\u5374\u671F\uFF08\u6BEB\u79D2\uFF09\u3002\n * flushFn \u6267\u884C\u5B8C\u6BD5\u540E\uFF0Centry \u7EE7\u7EED\u4FDD\u7559\u5728 Map \u4E2D\u8FD9\u4E48\u957F\u65F6\u95F4\uFF0C\n * \u9632\u6B62\u540E\u7EED\u987A\u5E8F\u5230\u8FBE\u7684\u5DE5\u5177\u8C03\u7528\u521B\u5EFA\u91CD\u590D\u5361\u7247\u3002\n */\nconst AUTH_COOLDOWN_MS = 30_000;\n\n/**\n * \u5C06\u6388\u6743\u8BF7\u6C42\u5165\u961F\u5230\u9632\u6296\u7F13\u51B2\u533A\u3002\n *\n * \u540C\u4E00 bufferKey \u7684\u8BF7\u6C42\u4F1A\u88AB\u5408\u5E76\uFF1A\n * - collecting \u9636\u6BB5\uFF1Ascope \u96C6\u5408\u53D6\u5E76\u96C6\uFF0C\u5171\u4EAB\u540C\u4E00\u4E2A flushFn \u6267\u884C\u7ED3\u679C\n * - executing \u9636\u6BB5\uFF1AflushFn \u5DF2\u5728\u8FD0\u884C\uFF0C\u540E\u7EED\u8BF7\u6C42\u76F4\u63A5\u590D\u7528\u5DF2\u6709\u7ED3\u679C\uFF08\u4E0D\u91CD\u590D\u53D1\u5361\u7247\uFF09\n *\n * @param bufferKey - \u7F13\u51B2\u533A key\uFF08\u533A\u5206\u4E0D\u540C\u7528\u6237/\u4F1A\u8BDD\uFF09\n * @param scopes - \u672C\u6B21\u8BF7\u6C42\u9700\u8981\u7684 scope \u5217\u8868\n * @param ctx - \u4E0A\u4E0B\u6587\u4FE1\u606F\uFF08\u4EC5\u7B2C\u4E00\u4E2A\u8BF7\u6C42\u7684\u88AB\u91C7\u7528\uFF09\n * @param flushFn - \u5B9A\u65F6\u5668\u5230\u671F\u540E\u6267\u884C\u7684\u5B9E\u9645\u6388\u6743\u51FD\u6570\uFF0C\u63A5\u6536\u5408\u5E76\u540E\u7684 scope \u6570\u7EC4\n */\nfunction enqueueAuthRequest(\n bufferKey: string,\n scopes: string[],\n ctx: { account: ConfiguredLarkAccount; cfg: ClawdbotConfig; ticket: LarkTicket },\n flushFn: (mergedScopes: string[]) => Promise<JsonResult>,\n debounceMs: number = AUTH_DEBOUNCE_MS,\n): Promise<JsonResult> {\n const existing = authBatches.get(bufferKey);\n\n if (existing) {\n // \u4E0D\u8BBA\u54EA\u4E2A\u9636\u6BB5\uFF0C\u90FD\u8FFD\u52A0 scope\n for (const s of scopes) existing.scopes.add(s);\n\n if (existing.phase === 'executing') {\n // flushFn \u5DF2\u5728\u6267\u884C\u6216\u5DF2\u5B8C\u6210\uFF08\u5361\u7247\u5DF2\u53D1\u51FA\uFF09\uFF0C\u590D\u7528\u7ED3\u679C\n // \u540C\u65F6\u89E6\u53D1\u5EF6\u8FDF\u5237\u65B0\uFF1A\u7528\u5408\u5E76\u540E\u7684 scope \u91CD\u65B0\u8C03\u7528 flushFn \u66F4\u65B0\u5361\u7247\n log.info(`auth in-flight, piggyback \u2192 key=${bufferKey}, scopes=[${[...existing.scopes].join(', ')}]`);\n\n // \u9632\u6296 + \u4E92\u65A5\uFF1A\u591A\u4E2A\u5FEB\u901F\u5230\u8FBE\u7684\u8BF7\u6C42\u53EA\u89E6\u53D1\u4E00\u6B21\u5361\u7247\u66F4\u65B0\n if (existing.updateTimer) clearTimeout(existing.updateTimer);\n existing.updateTimer = setTimeout(async () => {\n existing.updateTimer = null;\n\n // \u4E92\u65A5\uFF1A\u5982\u679C\u4E0A\u4E00\u8F6E\u66F4\u65B0\u8FD8\u5728\u6267\u884C\uFF0C\u6807\u8BB0 pendingReupdate \u7B49\u5B83\u7ED3\u675F\u540E\u91CD\u8DD1\n if (existing.isUpdating) {\n existing.pendingReupdate = true;\n log.info(`scope update deferred (previous update still running) \u2192 key=${bufferKey}`);\n return;\n }\n\n existing.isUpdating = true;\n try {\n const mergedScopes = [...existing.scopes];\n log.info(`scope update flush \u2192 key=${bufferKey}, scopes=[${mergedScopes.join(', ')}]`);\n // \u91CD\u65B0\u8C03\u7528 flushFn\uFF08executeAuthorize \u4F1A\u68C0\u6D4B\u5230 pendingFlow\uFF0C\n // \u539F\u5730\u66F4\u65B0\u65E7\u5361\u7247\u5185\u5BB9 + \u91CD\u542F Device Flow\uFF09\n await existing.flushFn!(mergedScopes);\n } catch (err) {\n log.warn(`scope update failed: ${err}`);\n } finally {\n existing.isUpdating = false;\n // \u5982\u679C\u9501\u5B9A\u671F\u95F4\u6709\u65B0 scope \u5230\u8FBE\uFF0C\u518D\u8DD1\u4E00\u8F6E\n if (existing.pendingReupdate) {\n existing.pendingReupdate = false;\n const finalScopes = [...existing.scopes];\n log.info(`scope reupdate \u2192 key=${bufferKey}, scopes=[${finalScopes.join(', ')}]`);\n try {\n await existing.flushFn!(finalScopes);\n } catch (err) {\n log.warn(`scope reupdate failed: ${err}`);\n }\n }\n }\n }, AUTH_UPDATE_DEBOUNCE_MS);\n\n return existing.resultPromise!;\n }\n\n // collecting \u9636\u6BB5\uFF1A\u6B63\u5E38\u5408\u5E76\n log.info(`debounce merge \u2192 key=${bufferKey}, scopes=[${[...existing.scopes].join(', ')}]`);\n return new Promise<JsonResult>((resolve, reject) => {\n existing.waiters.push({ resolve, reject });\n });\n }\n\n // \u521B\u5EFA\u65B0\u7F13\u51B2\u533A\uFF08collecting \u9636\u6BB5\uFF09\n const entry: AuthBatchEntry = {\n phase: 'collecting',\n scopes: new Set(scopes),\n waiters: [],\n timer: null,\n resultPromise: null,\n updateTimer: null,\n isUpdating: false,\n pendingReupdate: false,\n flushFn: null,\n account: ctx.account,\n cfg: ctx.cfg,\n ticket: ctx.ticket,\n };\n\n const promise = new Promise<JsonResult>((resolve, reject) => {\n entry.waiters.push({ resolve, reject });\n });\n\n entry.timer = setTimeout(async () => {\n // \u8F6C\u5165 executing \u9636\u6BB5\uFF08\u4E0D\u4ECE Map \u4E2D\u5220\u9664\uFF0C\u963B\u6B62\u540E\u7EED\u8BF7\u6C42\u521B\u5EFA\u65B0\u5361\u7247\uFF09\n entry.phase = 'executing';\n entry.timer = null;\n entry.flushFn = flushFn; // \u4FDD\u5B58\u5F15\u7528\uFF0C\u4F9B executing \u9636\u6BB5 scope \u66F4\u65B0\u65F6\u91CD\u65B0\u8C03\u7528\n const mergedScopes = [...entry.scopes];\n\n log.info(\n `debounce flush \u2192 key=${bufferKey}, ` + `waiters=${entry.waiters.length}, scopes=[${mergedScopes.join(', ')}]`,\n );\n\n // \u5C06 flushFn \u7684 Promise \u5B58\u5165 entry\uFF0C\u4F9B executing \u9636\u6BB5\u7684\u540E\u6765\u8005\u590D\u7528\n entry.resultPromise = flushFn(mergedScopes);\n\n try {\n const result = await entry.resultPromise;\n for (const w of entry.waiters) w.resolve(result);\n } catch (err) {\n for (const w of entry.waiters) w.reject(err);\n } finally {\n // \u8FDB\u5165\u51B7\u5374\u671F\uFF1Aentry \u7EE7\u7EED\u7559\u5728 Map \u4E2D\uFF0C\u540E\u7EED\u5230\u8FBE\u7684\u5DE5\u5177\u8C03\u7528\n // \u4F1A\u547D\u4E2D executing \u5206\u652F\u5E76\u590D\u7528 resultPromise\uFF0C\u4E0D\u4F1A\u521B\u5EFA\u65B0\u5361\u7247\u3002\n // \u51B7\u5374\u671F\u7ED3\u675F\u540E\u6E05\u7406\u3002\n setTimeout(() => authBatches.delete(bufferKey), AUTH_COOLDOWN_MS);\n }\n }, debounceMs);\n\n authBatches.set(bufferKey, entry);\n return promise;\n}\n\n// ---------------------------------------------------------------------------\n// PendingAppAuthFlow \u2014 \u7B49\u5F85\u7528\u6237\u786E\u8BA4\u7684\u5E94\u7528\u6743\u9650\u5F15\u5BFC\u6D41\u7A0B\n// ---------------------------------------------------------------------------\n\ninterface PendingAppAuthFlow {\n appId: string;\n accountId: string;\n cardId: string;\n sequence: number;\n requiredScopes: string[];\n /** \u4E0E\u89E6\u53D1 AppScopeMissingError \u65F6\u7684 scopeNeedType \u4E00\u81F4\u3002 */\n scopeNeedType?: 'one' | 'all';\n /** \u4E0E\u89E6\u53D1 AppScopeMissingError \u65F6\u7684 tokenType \u4E00\u81F4\u3002 */\n tokenType?: 'user' | 'tenant';\n cfg: ClawdbotConfig;\n ticket: LarkTicket;\n}\n\n/** TTL\uFF1A15 \u5206\u949F\u540E\u81EA\u52A8\u6E05\u7406\uFF0C\u9632\u6B62\u5185\u5B58\u6CC4\u6F0F\u3002 */\nconst PENDING_FLOW_TTL_MS = 15 * 60 * 1000;\n\n/** \u8BA1\u7B97\u53BB\u91CD key\uFF08chatId + messageId + \u6709\u5E8F scopes\uFF09\u3002 */\nfunction makeDedupKey(chatId: string, messageId: string, scopes: string[]): string {\n return chatId + '\\0' + messageId + '\\0' + [...scopes].sort().join(',');\n}\n\n/** \u6CE8\u518C\u540E\u7684 flow\uFF0C\u9644\u52A0\u7D22\u5F15\u952E\u4FE1\u606F */\ntype RegisteredFlow = PendingAppAuthFlow & {\n dedupKey: string;\n activeCardKey: string;\n};\n\n/**\n * \u5E94\u7528\u6743\u9650\u6388\u6743\u6D41\u7BA1\u7406\u5668 \u2014 \u7EDF\u4E00\u7BA1\u7406\u4E09\u4E2A\u5173\u8054\u7D22\u5F15\u7684\u4E00\u81F4\u6027\u3002\n *\n * \u66FF\u4EE3\u539F\u6765\u6563\u5E03\u7684 pendingAppAuthFlows / dedupIndex / activeAppCardIndex \u4E09\u4E2A Map\uFF0C\n * \u786E\u4FDD\u6CE8\u518C\u3001\u5220\u9664\u3001\u8FC1\u79FB\u64CD\u4F5C\u7684\u539F\u5B50\u6027\u3002\n */\nclass AppAuthFlowManager {\n private readonly flows = new Map<string, RegisteredFlow>();\n private readonly dedupIndex = new Map<string, string>();\n private readonly activeCardIndex = new Map<string, string>();\n\n /** \u539F\u5B50\u6CE8\u518C\u65B0\u6D41\u7A0B\uFF08\u540C\u65F6\u5199\u5165 3 \u4E2A\u7D22\u5F15 + \u8BBE\u7F6E\u7EDF\u4E00 TTL\uFF09 */\n register(operationId: string, flow: PendingAppAuthFlow, dedupKey: string, activeCardKey: string): void {\n const registered: RegisteredFlow = { ...flow, dedupKey, activeCardKey };\n this.flows.set(operationId, registered);\n this.dedupIndex.set(dedupKey, operationId);\n this.activeCardIndex.set(activeCardKey, operationId);\n\n // \u7EDF\u4E00 TTL \u6E05\u7406\n setTimeout(() => {\n if (!this.flows.has(operationId)) return; // \u5DF2\u88AB\u624B\u52A8\u6E05\u7406\uFF0C\u8DF3\u8FC7\n this.remove(operationId);\n }, PENDING_FLOW_TTL_MS);\n }\n\n /** \u53EA\u9700 operationId \u5373\u53EF\u539F\u5B50\u6E05\u7406\u6240\u6709\u7D22\u5F15 */\n remove(operationId: string): void {\n const flow = this.flows.get(operationId);\n if (!flow) return;\n\n // \u8054\u52A8\u6E05\u7406\u5EF6\u8FDF\u7528\u6237\u6388\u6743\u961F\u5217\uFF08\u9632\u6B62\u5185\u5B58\u6CC4\u6F0F\uFF09\n if (flow.ticket?.senderOpenId) {\n const deferKey = `${flow.accountId}:${flow.ticket.senderOpenId}:${flow.ticket.messageId}`;\n deferredUserAuth.delete(deferKey);\n }\n\n this.flows.delete(operationId);\n // \u6761\u4EF6\u5220\u9664\uFF1A\u9632\u6B62\u8BEF\u5220\u5DF2\u88AB\u65B0 flow \u8986\u76D6\u7684\u7D22\u5F15\n if (this.dedupIndex.get(flow.dedupKey) === operationId) {\n this.dedupIndex.delete(flow.dedupKey);\n }\n if (this.activeCardIndex.get(flow.activeCardKey) === operationId) {\n this.activeCardIndex.delete(flow.activeCardKey);\n }\n }\n\n /**\n * \u8FC1\u79FB\u5230\u65B0 operationId\uFF08\u5361\u7247\u590D\u7528\u573A\u666F\uFF1A\u6309\u94AE\u56DE\u8C03\u9700\u8981\u5339\u914D\u65B0 ID\uFF09\u3002\n * \u539F\u5B50\u64CD\u4F5C\uFF1A\u6E05\u7406\u65E7\u7D22\u5F15 \u2192 \u66F4\u65B0 flow \u2192 \u5EFA\u7ACB\u65B0\u7D22\u5F15 \u2192 \u6CE8\u518C\u65B0 TTL\u3002\n *\n * \u4FEE\u590D\u539F\u4EE3\u7801\u5361\u7247\u590D\u7528\u8DEF\u5F84\u7F3A\u5C11 TTL \u6CE8\u518C\u5BFC\u81F4\u7684\u5185\u5B58\u6CC4\u6F0F\u3002\n */\n migrateToNewOperationId(\n oldOperationId: string,\n newOperationId: string,\n updates?: { dedupKey?: string; requiredScopes?: string[]; scopeNeedType?: 'one' | 'all' },\n ): RegisteredFlow | undefined {\n const flow = this.flows.get(oldOperationId);\n if (!flow) return undefined;\n\n // \u6E05\u7406\u65E7\u7D22\u5F15\n this.flows.delete(oldOperationId);\n if (updates?.dedupKey) {\n if (this.dedupIndex.get(flow.dedupKey) === oldOperationId) {\n this.dedupIndex.delete(flow.dedupKey);\n }\n flow.dedupKey = updates.dedupKey;\n }\n if (updates?.requiredScopes) flow.requiredScopes = updates.requiredScopes;\n if (updates?.scopeNeedType) flow.scopeNeedType = updates.scopeNeedType;\n\n // \u5EFA\u7ACB\u65B0\u7D22\u5F15\n this.flows.set(newOperationId, flow);\n this.dedupIndex.set(flow.dedupKey, newOperationId);\n this.activeCardIndex.set(flow.activeCardKey, newOperationId);\n\n // \u4E3A\u65B0 operationId \u6CE8\u518C TTL\uFF08\u4FEE\u590D\u539F\u4EE3\u7801\u7684\u5185\u5B58\u6CC4\u6F0F\uFF09\n setTimeout(() => {\n if (!this.flows.has(newOperationId)) return;\n this.remove(newOperationId);\n }, PENDING_FLOW_TTL_MS);\n\n return flow;\n }\n\n /** \u901A\u8FC7 operationId \u67E5\u8BE2\uFF08card action \u56DE\u8C03\u7528\uFF09 */\n getByOperationId(id: string): PendingAppAuthFlow | undefined {\n return this.flows.get(id);\n }\n\n /** \u901A\u8FC7\u53BB\u91CD\u952E\u67E5\u8BE2\uFF08\u907F\u514D\u53D1\u9001\u91CD\u590D\u5361\u7247\uFF09 */\n getByDedupKey(key: string): { operationId: string; flow: PendingAppAuthFlow } | undefined {\n const opId = this.dedupIndex.get(key);\n if (!opId) return undefined;\n const flow = this.flows.get(opId);\n return flow ? { operationId: opId, flow } : undefined;\n }\n\n /** \u901A\u8FC7\u6D3B\u8DC3\u5361\u7247\u952E\u67E5\u8BE2\uFF08\u540C\u6D88\u606F\u5361\u7247\u590D\u7528\uFF09 */\n getByActiveCardKey(key: string): { operationId: string; flow: RegisteredFlow } | undefined {\n const opId = this.activeCardIndex.get(key);\n if (!opId) return undefined;\n const flow = this.flows.get(opId);\n return flow ? { operationId: opId, flow } : undefined;\n }\n}\n\nconst appAuthFlows = new AppAuthFlowManager();\n\n// ---------------------------------------------------------------------------\n// Deferred User Auth Queue \u2014 \u7528\u6237\u6388\u6743\u5EF6\u8FDF\u961F\u5217\n//\n// \u5F53\u7528\u6237\u6388\u6743\u8BF7\u6C42\u5230\u8FBE\u65F6\uFF0C\u5982\u679C\u540C\u4E00\u6D88\u606F\u4E0A\u4E0B\u6587\u5B58\u5728\u672A\u5B8C\u6210\u7684\u5E94\u7528\u6743\u9650\u6D41\u7A0B\uFF0C\n// \u5C06 scope \u6536\u96C6\u5230\u5EF6\u8FDF\u961F\u5217\uFF0C\u7B49\u5E94\u7528\u6388\u6743\u5B8C\u6210\u540E\u7EDF\u4E00\u53D1\u8D77 OAuth\u3002\n// ---------------------------------------------------------------------------\n\ninterface DeferredUserAuthEntry {\n scopes: Set<string>;\n account: ConfiguredLarkAccount;\n cfg: ClawdbotConfig;\n ticket: LarkTicket;\n}\n\n/** \u5EF6\u8FDF\u7528\u6237\u6388\u6743\u961F\u5217\u3002Key: `${accountId}:${senderOpenId}:${messageId}` */\nconst deferredUserAuth = new Map<string, DeferredUserAuthEntry>();\n\n/**\n * \u68C0\u67E5\u6307\u5B9A\u6D88\u606F\u4E0A\u4E0B\u6587\u662F\u5426\u6709\u672A\u5B8C\u6210\u7684\u5E94\u7528\u6743\u9650\u6388\u6743\u6D41\u7A0B\u3002\n * \u68C0\u67E5\u4E24\u4E2A\u6765\u6E90\uFF1A\n * 1. authBatches \u4E2D\u7684 app auth entry\uFF08collecting/executing \u9636\u6BB5\uFF09\n * 2. appAuthFlows \u4E2D\u7684\u6D3B\u8DC3\u6D41\uFF08\u5361\u7247\u5DF2\u53D1\u9001\uFF0C\u7B49\u5F85\u7528\u6237\u70B9\u51FB\"\u5DF2\u5B8C\u6210\"\uFF09\n */\nfunction hasActiveAppAuthForMessage(ticket: LarkTicket): boolean {\n const appKey = `app:${ticket.accountId}:${ticket.chatId}:${ticket.messageId}`;\n const appEntry = authBatches.get(appKey);\n if (appEntry && (appEntry.phase === 'collecting' || appEntry.phase === 'executing')) {\n return true;\n }\n const activeCardKey = `${ticket.chatId}:${ticket.messageId}`;\n return !!appAuthFlows.getByActiveCardKey(activeCardKey);\n}\n\n/**\n * \u5C06\u7528\u6237\u6388\u6743 scope \u6DFB\u52A0\u5230\u5EF6\u8FDF\u961F\u5217\u3002\n * \u591A\u4E2A\u5DE5\u5177\u8C03\u7528\u7684 scope \u4F1A\u88AB\u5408\u5E76\u5230\u540C\u4E00\u4E2A entry\u3002\n */\nfunction addToDeferredUserAuth(\n ticket: LarkTicket,\n scopes: string[],\n account: ConfiguredLarkAccount,\n cfg: ClawdbotConfig,\n): void {\n const key = `${ticket.accountId}:${ticket.senderOpenId}:${ticket.messageId}`;\n const existing = deferredUserAuth.get(key);\n if (existing) {\n for (const s of scopes) existing.scopes.add(s);\n log.info(`deferred user auth scope merge \u2192 key=${key}, scopes=[${[...existing.scopes].join(', ')}]`);\n } else {\n deferredUserAuth.set(key, { scopes: new Set(scopes), account, cfg, ticket });\n log.info(`deferred user auth created \u2192 key=${key}, scopes=[${scopes.join(', ')}]`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Card builders \u2014 CardKit v2 \u683C\u5F0F\n// ---------------------------------------------------------------------------\n\n/**\n * \u6784\u5EFA\u5E94\u7528\u6743\u9650\u5F15\u5BFC\u5361\u7247\u3002\n *\n * \u84DD\u8272 header\uFF0C\u5217\u51FA\u7F3A\u5931\u7684 scope\uFF0C\u63D0\u4F9B\u6743\u9650\u7BA1\u7406\u94FE\u63A5\u548C\"\u6211\u5DF2\u5B8C\u6210\uFF0C\u7EE7\u7EED\u6388\u6743\"\u6309\u94AE\u3002\n */\nfunction buildAppScopeMissingCard(params: {\n missingScopes: string[];\n appId?: string;\n operationId: string;\n}): Record<string, unknown> {\n const { missingScopes, appId, operationId } = params;\n const authUrl = appId\n ? `https://open.feishu.cn/app/${appId}/auth?q=${encodeURIComponent(missingScopes.join(','))}&op_from=feishu-openclaw&token_type=user`\n : 'https://open.feishu.cn/';\n const multiUrl = { url: authUrl, pc_url: authUrl, android_url: authUrl, ios_url: authUrl };\n\n const scopeList = missingScopes.map((s) => `\u2022 ${s}`).join('\\n');\n\n return {\n schema: '2.0',\n config: { wide_screen_mode: true },\n header: {\n title: { tag: 'plain_text', content: '\uD83D\uDD10 \u9700\u8981\u7533\u8BF7\u6743\u9650\u624D\u80FD\u7EE7\u7EED' },\n template: 'orange',\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content: '\u8C03\u7528\u524D\uFF0C\u8BF7\u4F60\u5148\u7533\u8BF7\u4EE5\u4E0B**\u6240\u6709**\u6743\u9650\uFF1A',\n text_size: 'normal',\n },\n {\n tag: 'column_set',\n flex_mode: 'none',\n background_style: 'grey',\n horizontal_spacing: 'default',\n columns: [\n {\n tag: 'column',\n width: 'weighted',\n weight: 1,\n vertical_align: 'center',\n elements: [{ tag: 'markdown', content: scopeList }],\n },\n ],\n },\n { tag: 'hr' },\n {\n tag: 'column_set',\n flex_mode: 'none',\n horizontal_spacing: 'default',\n columns: [\n {\n tag: 'column',\n width: 'weighted',\n weight: 3,\n vertical_align: 'center',\n elements: [{ tag: 'markdown', content: '**\u7B2C\u4E00\u6B65\uFF1A\u7533\u8BF7\u6240\u6709\u6743\u9650**' }],\n },\n {\n tag: 'column',\n width: 'weighted',\n weight: 1,\n vertical_align: 'center',\n elements: [\n {\n tag: 'button',\n text: { tag: 'plain_text', content: '\u53BB\u7533\u8BF7' },\n type: 'primary',\n multi_url: multiUrl,\n },\n ],\n },\n ],\n },\n {\n tag: 'column_set',\n flex_mode: 'none',\n horizontal_spacing: 'default',\n columns: [\n {\n tag: 'column',\n width: 'weighted',\n weight: 3,\n vertical_align: 'center',\n elements: [{ tag: 'markdown', content: '**\u7B2C\u4E8C\u6B65\uFF1A\u521B\u5EFA\u7248\u672C\u5E76\u5BA1\u6838\u901A\u8FC7**' }],\n },\n {\n tag: 'column',\n width: 'weighted',\n weight: 1,\n vertical_align: 'center',\n elements: [\n {\n tag: 'button',\n text: { tag: 'plain_text', content: '\u5DF2\u5B8C\u6210' },\n type: 'default',\n value: { action: 'app_auth_done', operation_id: operationId },\n },\n ],\n },\n ],\n },\n ],\n },\n };\n}\n\n/**\n * \u6784\u5EFA\u5E94\u7528\u6743\u9650\u5F15\u5BFC\u5361\u7247\u7684\"\u5904\u7406\u4E2D\"\u72B6\u6001\uFF08\u7528\u6237\u70B9\u51FB\u6309\u94AE\u540E\u66F4\u65B0\uFF09\u3002\n */\nfunction buildAppAuthProgressCard(): Record<string, unknown> {\n return {\n schema: '2.0',\n config: { wide_screen_mode: false },\n header: {\n title: { tag: 'plain_text', content: '\u5E94\u7528\u6743\u9650\u5DF2\u5F00\u901A' },\n subtitle: { tag: 'plain_text', content: '' },\n template: 'green',\n padding: '12px 12px 12px 12px',\n icon: { tag: 'standard_icon', token: 'yes_filled' },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content: '\u60A8\u7684\u5E94\u7528\u6743\u9650\u5DF2\u5F00\u901A\uFF0C\u6B63\u5728\u4E3A\u60A8\u53D1\u8D77\u7528\u6237\u6388\u6743',\n text_size: 'normal',\n },\n ],\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * \u53D1\u9001\u5E94\u7528\u6743\u9650\u5F15\u5BFC\u5361\u7247\uFF0C\u5E76\u5C06 flow \u5B58\u5165 pendingAppAuthFlows\u3002\n * \u8FD4\u56DE\u5DE5\u5177\u7ED3\u679C\uFF08\u544A\u77E5 AI \u7B49\u5F85\u7528\u6237\u64CD\u4F5C\uFF09\u3002\n */\nasync function sendAppScopeCard(params: {\n account: ConfiguredLarkAccount;\n missingScopes: string[];\n appId?: string;\n scopeNeedType?: 'one' | 'all';\n tokenType?: 'user' | 'tenant';\n cfg: ClawdbotConfig;\n ticket: LarkTicket;\n}): Promise<ReturnType<typeof json>> {\n const { account, missingScopes, appId, scopeNeedType, tokenType, cfg, ticket } = params;\n const { accountId, chatId, messageId } = ticket;\n const activeCardKey = `${chatId}:${messageId}`;\n\n // ---- \u53BB\u91CD\uFF1A\u907F\u514D\u5E76\u53D1\u5DE5\u5177\u8C03\u7528\u65F6\u53D1\u51FA\u591A\u5F20\u5185\u5BB9\u76F8\u540C\u7684\u5361\u7247 ----\n const dedup = makeDedupKey(chatId, messageId, missingScopes);\n const existingEntry = appAuthFlows.getByDedupKey(dedup);\n if (existingEntry) {\n log.info(\n `dedup \u2013 app-scope card already pending for chatId=${chatId}, ` +\n `scopes=[${missingScopes.join(', ')}], skipping duplicate send`,\n );\n return json({\n awaiting_app_authorization: true,\n message:\n '\u5DF2\u5411\u7528\u6237\u53D1\u9001\u6388\u6743\u5F15\u5BFC\u5361\u7247\uFF0C\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u6388\u6743\u64CD\u4F5C\u3002' +\n '\u8BF7\u544A\u77E5\u7528\u6237\uFF1A\u6309\u7167\u5361\u7247\u63D0\u793A\u5B8C\u6210\u6388\u6743\uFF0C\u5B8C\u6210\u540E\u7CFB\u7EDF\u5C06\u81EA\u52A8\u91CD\u8BD5\u4E4B\u524D\u7684\u64CD\u4F5C\u3002' +\n '\u8BF7\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u5361\u7247\u64CD\u4F5C\uFF0C\u4E0D\u8981\u5EFA\u8BAE\u5176\u4ED6\u66FF\u4EE3\u65B9\u6848\u3002',\n missing_scopes: missingScopes,\n });\n }\n\n // ---- \u5361\u7247\u590D\u7528\uFF1A\u540C\u4E00 chatId+messageId \u5DF2\u6709\u6D3B\u8DC3\u5361\u7247\u65F6\uFF0C\u539F\u5730\u66F4\u65B0\u800C\u975E\u521B\u5EFA\u65B0\u5361\u7247 ----\n const activeEntry = appAuthFlows.getByActiveCardKey(activeCardKey);\n\n if (activeEntry) {\n const { operationId: activeOpId, flow: activeFlow } = activeEntry;\n // \u66F4\u65B0\u5DF2\u6709\u5361\u7247\u7684\u5185\u5BB9\uFF08\u5408\u5E76\u540E\u7684 scope\uFF09\n const newOperationId = Date.now().toString(36) + Math.random().toString(36).slice(2);\n const card = buildAppScopeMissingCard({ missingScopes, appId, operationId: newOperationId });\n const newSeq = activeFlow.sequence + 1;\n\n // TOCTOU \u4FEE\u590D\uFF1A\u5148\u539F\u5B50\u8FC1\u79FB\uFF08\u540C\u6B65\u64CD\u4F5C\uFF09\uFF0C\u518D await \u66F4\u65B0\u5361\u7247\n const newDedup = makeDedupKey(chatId, messageId, missingScopes);\n const migrated = appAuthFlows.migrateToNewOperationId(activeOpId, newOperationId, {\n dedupKey: newDedup,\n requiredScopes: missingScopes,\n scopeNeedType,\n });\n if (!migrated) {\n // \u88AB\u5176\u4ED6\u5E76\u53D1\u8BF7\u6C42\u62A2\u5148\u8FC1\u79FB\u4E86\uFF0C\u964D\u7EA7\u5230\u65B0\u5EFA\u5361\u7247\n log.info(`migrate raced, falling through to new card creation`);\n } else {\n try {\n await updateCardKitCardForAuth({\n cfg,\n cardId: activeFlow.cardId,\n card,\n sequence: newSeq,\n accountId,\n });\n log.info(\n `app-scope card updated in-place, cardId=${activeFlow.cardId}, ` +\n `seq=${newSeq}, scopes=[${missingScopes.join(', ')}]`,\n );\n\n // \u66F4\u65B0 sequence\uFF08migrate \u4E0D\u5904\u7406 sequence\uFF09\n migrated.sequence = newSeq;\n\n return json({\n awaiting_app_authorization: true,\n message:\n '\u5DF2\u5411\u7528\u6237\u53D1\u9001\u6388\u6743\u5F15\u5BFC\u5361\u7247\uFF0C\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u6388\u6743\u64CD\u4F5C\u3002' +\n '\u8BF7\u544A\u77E5\u7528\u6237\uFF1A\u6309\u7167\u5361\u7247\u63D0\u793A\u5B8C\u6210\u6388\u6743\uFF0C\u5B8C\u6210\u540E\u7CFB\u7EDF\u5C06\u81EA\u52A8\u91CD\u8BD5\u4E4B\u524D\u7684\u64CD\u4F5C\u3002' +\n '\u8BF7\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u5361\u7247\u64CD\u4F5C\uFF0C\u4E0D\u8981\u5EFA\u8BAE\u5176\u4ED6\u66FF\u4EE3\u65B9\u6848\u3002',\n missing_scopes: missingScopes,\n });\n } catch (err) {\n // \u56DE\u6EDA\uFF1A\u5220\u9664\u5DF2\u8FC1\u79FB\u7684 flow\n appAuthFlows.remove(newOperationId);\n log.warn(`failed to update existing app-scope card, creating new one: ${err}`);\n // \u964D\u7EA7\uFF1A\u8D70\u4E0B\u9762\u7684\u65B0\u5EFA\u5361\u7247\u8DEF\u5F84\n }\n }\n }\n\n const operationId = Date.now().toString(36) + Math.random().toString(36).slice(2);\n\n const card = buildAppScopeMissingCard({ missingScopes, appId, operationId });\n\n // \u521B\u5EFA CardKit \u5361\u7247\u5B9E\u4F53\n const cardId = await createCardEntity({ cfg, card, accountId });\n if (!cardId) {\n log.warn('createCardEntity failed for app-scope card, falling back');\n return json({\n error: 'app_scope_missing',\n missing_scopes: missingScopes,\n message:\n `\u5E94\u7528\u7F3A\u5C11\u4EE5\u4E0B\u6743\u9650\uFF1A${missingScopes.join(', ')}\uFF0C` +\n `\u8BF7\u7BA1\u7406\u5458\u5728\u5F00\u653E\u5E73\u53F0\u5F00\u901A\u540E\u91CD\u8BD5\u3002` +\n (appId ? `\\n\u6743\u9650\u7BA1\u7406\uFF1Ahttps://open.feishu.cn/app/${appId}/permission` : ''),\n });\n }\n\n // \u53D1\u9001\u5230\u5F53\u524D\u4F1A\u8BDD\n const replyToMsgId = ticket.messageId?.startsWith('om_') ? ticket.messageId : undefined;\n\n await sendCardByCardId({\n cfg,\n to: chatId,\n cardId,\n replyToMessageId: replyToMsgId,\n replyInThread: Boolean(ticket?.threadId),\n accountId,\n });\n\n // \u539F\u5B50\u6CE8\u518C\u5230\u7BA1\u7406\u5668\uFF08\u7EDF\u4E00 TTL \u6E05\u7406\uFF09\n const flow: PendingAppAuthFlow = {\n appId: appId ?? account.appId,\n accountId,\n cardId,\n sequence: 0,\n requiredScopes: missingScopes,\n scopeNeedType,\n tokenType,\n cfg,\n ticket,\n };\n appAuthFlows.register(operationId, flow, dedup, activeCardKey);\n\n log.info(`app-scope card sent, operationId=${operationId}, scopes=[${missingScopes.join(', ')}]`);\n\n return json({\n awaiting_app_authorization: true,\n message:\n '\u5DF2\u5411\u7528\u6237\u53D1\u9001\u6388\u6743\u5F15\u5BFC\u5361\u7247\uFF0C\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u6388\u6743\u64CD\u4F5C\u3002' +\n '\u8BF7\u544A\u77E5\u7528\u6237\uFF1A\u6309\u7167\u5361\u7247\u63D0\u793A\u5B8C\u6210\u6388\u6743\uFF0C\u5B8C\u6210\u540E\u7CFB\u7EDF\u5C06\u81EA\u52A8\u91CD\u8BD5\u4E4B\u524D\u7684\u64CD\u4F5C\u3002' +\n '\u8BF7\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u5361\u7247\u64CD\u4F5C\uFF0C\u4E0D\u8981\u5EFA\u8BAE\u5176\u4ED6\u66FF\u4EE3\u65B9\u6848\u3002',\n missing_scopes: missingScopes,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Card action handler (exported for monitor.ts)\n// ---------------------------------------------------------------------------\n\n/**\n * \u5904\u7406 card.action.trigger \u56DE\u8C03\u4E8B\u4EF6\uFF08\u7531 monitor.ts \u8C03\u7528\uFF09\u3002\n *\n * \u5F53\u7528\u6237\u70B9\u51FB\u5E94\u7528\u6743\u9650\u5F15\u5BFC\u5361\u7247\u7684\"\u6211\u5DF2\u5B8C\u6210\uFF0C\u7EE7\u7EED\u6388\u6743\"\u6309\u94AE\u65F6\uFF1A\n * 1. \u66F4\u65B0\u5361\u7247\u4E3A\"\u5904\u7406\u4E2D\"\u72B6\u6001\n * 2. \u6E05\u9664\u5E94\u7528 scope \u7F13\u5B58\n * 3. \u53D1\u9001\u4E2D\u95F4\u5408\u6210\u6D88\u606F\u544A\u77E5 AI\n * 4. \u53D1\u8D77 OAuth Device Flow\n *\n * \u6CE8\u610F\uFF1A\u51FD\u6570\u4F53\u5185\u7684\u4E3B\u8981\u903B\u8F91\u901A\u8FC7 setImmediate + fire-and-forget \u5F02\u6B65\u6267\u884C\uFF0C\n * \u786E\u4FDD Feishu card.action.trigger \u56DE\u8C03\u5728 3 \u79D2\u5185\u8FD4\u56DE\u3002\n */\nexport async function handleCardAction(data: unknown, cfg: ClawdbotConfig, accountId: string): Promise<unknown> {\n let action: string | undefined;\n let operationId: string | undefined;\n let senderOpenId: string | undefined;\n let buttonName: string | undefined;\n\n try {\n const event = data as {\n operator?: { open_id?: string };\n action?: { value?: { action?: string; operation_id?: string }; name?: string };\n };\n action = event.action?.value?.action;\n operationId = event.action?.value?.operation_id;\n senderOpenId = event.operator?.open_id;\n buttonName = event.action?.name;\n log.debug(`card action received: action=${action}, buttonName=${buttonName}, operationId=${operationId}`);\n } catch {\n return;\n }\n\n if (buttonName === 'submit_project_auth') {\n return handleProjectAuthCardAction(data, cfg, accountId);\n }\n\n if (action !== 'app_auth_done' || !operationId) return;\n\n const flow = appAuthFlows.getByOperationId(operationId);\n if (!flow) {\n log.warn(`card action ${operationId} not found (expired or already handled)`);\n return;\n }\n\n log.info(`app_auth_done clicked by ${senderOpenId}, operationId=${operationId}`);\n\n // scope \u6821\u9A8C\u5728\u540C\u6B65\u8DEF\u5F84\u5B8C\u6210\uFF083 \u79D2\u5185\u8FD4\u56DE toast response\uFF09\n invalidateAppScopeCache(flow.appId);\n\n const acct = getLarkAccount(flow.cfg, flow.accountId);\n if (!acct.configured) {\n log.warn(`account ${flow.accountId} not configured, skipping OAuth`);\n return;\n }\n\n const sdk = LarkClient.fromAccount(acct).sdk;\n let grantedScopes: string[] = [];\n try {\n // \u4F7F\u7528\u4E0E\u539F\u59CB AppScopeMissingError \u76F8\u540C\u7684 tokenType\uFF0C\u4FDD\u8BC1\u6821\u9A8C\u903B\u8F91\u5B8C\u5168\u4E00\u81F4\n grantedScopes = await getAppGrantedScopes(sdk, flow.appId, flow.tokenType);\n } catch (err) {\n log.warn(`failed to re-check app scopes: ${err}, proceeding anyway`);\n }\n\n // \u4F7F\u7528\u5171\u4EAB\u51FD\u6570 isAppScopeSatisfied\uFF0C\u4E0E tool-client invoke() \u903B\u8F91\u5B8C\u5168\u4E00\u81F4\uFF1A\n // - scopeNeedType \"all\" \u2192 \u5168\u90E8\u5FC5\u987B\u6709\n // - \u9ED8\u8BA4\"one\" \u2192 \u4EA4\u96C6\u975E\u7A7A\u5373\u53EF\n // - grantedScopes \u4E3A\u7A7A \u2192 \u89C6\u4E3A\u6EE1\u8DB3\uFF08API \u5931\u8D25\u9000\u56DE\u670D\u52A1\u7AEF\u5224\u65AD\uFF09\n if (!isAppScopeSatisfied(grantedScopes, flow.requiredScopes, flow.scopeNeedType)) {\n log.warn(`app scopes still missing after user confirmation: [${flow.requiredScopes.join(', ')}]`);\n return {\n toast: {\n type: 'error',\n content: '\u6743\u9650\u5C1A\u672A\u5F00\u901A\uFF0C\u8BF7\u786E\u8BA4\u5DF2\u7533\u8BF7\u5E76\u5BA1\u6838\u901A\u8FC7\u540E\u518D\u8BD5',\n },\n };\n }\n\n log.info(`app scopes verified, proceeding with OAuth`);\n\n // \u2605 \u5728 remove() \u4E4B\u524D\u5148\u53D6\u51FA\u5EF6\u8FDF\u961F\u5217\u6570\u636E\uFF0C\u907F\u514D remove() \u7684\u8054\u52A8\u6E05\u7406\u63D0\u524D\u5220\u6389\u5B83\n const deferKey = flow.ticket.senderOpenId\n ? `${flow.accountId}:${flow.ticket.senderOpenId}:${flow.ticket.messageId}`\n : undefined;\n const consumedDeferred = deferKey ? deferredUserAuth.get(deferKey) : undefined;\n if (consumedDeferred && deferKey) {\n deferredUserAuth.delete(deferKey);\n log.info(`consumed deferred user auth scopes: [${[...consumedDeferred.scopes].join(', ')}]`);\n }\n\n // \u6821\u9A8C\u901A\u8FC7\u624D\u5220\u9664\uFF0C\u9632\u6B62\u7528\u6237\u5728\u6743\u9650\u901A\u8FC7\u524D\u591A\u6B21\u70B9\u51FB\u65E0\u6CD5\u91CD\u8BD5\n appAuthFlows.remove(operationId);\n\n // \u901A\u8FC7\u56DE\u8C03\u8FD4\u56DE\u503C\u76F4\u63A5\u66F4\u65B0\u5361\u7247\uFF08\u65B9\u5F0F\u4E00\uFF1A3 \u79D2\u5185\u7ACB\u5373\u66F4\u65B0\uFF09\u3002\n // \u98DE\u4E66\u6587\u6863\u8981\u6C42 card \u5B57\u6BB5\u5FC5\u987B\u5305\u542B type + data \u5305\u88C5\uFF1A\n // { card: { type: \"raw\", data: { schema: \"2.0\", ... } } }\n // \u6CE8\u610F\uFF1A\u4E0D\u80FD\u5728\u56DE\u8C03\u8FD4\u56DE\u524D\u8C03\u7528 card.update API\uFF0C\u98DE\u4E66\u6587\u6863\u660E\u786E\u8BF4\u660E\n // \"\u5EF6\u65F6\u66F4\u65B0\u5FC5\u987B\u5728\u54CD\u5E94\u56DE\u8C03\u8BF7\u6C42\u4E4B\u540E\u6267\u884C\uFF0C\u5E76\u884C\u6267\u884C\u6216\u63D0\u524D\u6267\u884C\u4F1A\u51FA\u73B0\u66F4\u65B0\u5931\u8D25\"\u3002\n const successCard = buildAppAuthProgressCard();\n\n // \u540E\u53F0\u5F02\u6B65\uFF1A\u56DE\u8C03\u54CD\u5E94\u4E4B\u540E\u518D\u6267\u884C API \u66F4\u65B0 + OAuth\n setImmediate(async () => {\n try {\n // \u901A\u8FC7 API \u518D\u6B21\u66F4\u65B0\u5361\u7247\uFF08\u786E\u4FDD\u6240\u6709\u67E5\u770B\u8005\u90FD\u770B\u5230\u66F4\u65B0\uFF0C\u4E0D\u53EA\u662F\u70B9\u51FB\u8005\uFF09\n try {\n await updateCardKitCardForAuth({\n cfg,\n cardId: flow.cardId,\n card: successCard,\n sequence: flow.sequence + 1,\n accountId,\n });\n } catch (err) {\n log.warn(`failed to update app-scope card to progress via API: ${err}`);\n }\n\n // \u53D1\u8D77 OAuth Device Flow\uFF08\u5B8C\u6210\u540E executeAuthorize \u4F1A\u81EA\u52A8\u53D1\u5408\u6210\u6D88\u606F\u89E6\u53D1 AI \u91CD\u8BD5\uFF09\n if (!flow.ticket.senderOpenId) {\n log.warn('no senderOpenId in ticket, skipping OAuth');\n return;\n }\n\n // \u6536\u96C6\u6240\u6709\u6765\u6E90\u7684 scope\uFF08\u8FC7\u6EE4 offline_access\uFF1A\u4EC5 app \u7EA7\u9700\u8981\uFF0Cdevice-flow \u81EA\u52A8\u8FFD\u52A0\uFF09\n const mergedScopes = new Set(flow.requiredScopes.filter((s) => s !== 'offline_access'));\n\n // \u6765\u6E90 1: \u5EF6\u8FDF\u7528\u6237\u6388\u6743\u961F\u5217\uFF08\u5DF2\u5728\u540C\u6B65\u8DEF\u5F84\u4E2D\u63D0\u524D\u53D6\u51FA\uFF0C\u89C1 consumedDeferred\uFF09\n if (consumedDeferred) {\n for (const s of consumedDeferred.scopes) mergedScopes.add(s);\n }\n\n // \u6765\u6E90 2: \u73B0\u6709 user auth batch\uFF08\u5411\u540E\u517C\u5BB9\uFF0C\u5904\u7406\u672A\u88AB\u5EF6\u8FDF\u62E6\u622A\u7684 user auth\uFF09\n const userBatchKey = `user:${flow.accountId}:${flow.ticket.senderOpenId}:${flow.ticket.messageId}`;\n const userBatch = authBatches.get(userBatchKey);\n if (userBatch) {\n for (const s of userBatch.scopes) mergedScopes.add(s);\n log.info(`merged user batch scopes into app auth completion: [${[...mergedScopes].join(', ')}]`);\n }\n\n if (mergedScopes.size === 0) {\n // \u65E0\u4E1A\u52A1 scope \u9700\u8981\u7528\u6237\u6388\u6743\uFF08\u4F8B\u5982 offline_access \u662F\u552F\u4E00\u7F3A\u5931\u7684\u5E94\u7528\u6743\u9650\uFF0C\n // \u4E14\u6CA1\u6709\u5176\u4ED6\u5DE5\u5177\u4EA7\u751F\u7528\u6237\u6388\u6743\u9700\u6C42\uFF09\u3002\u8DF3\u8FC7 OAuth\uFF0C\u76F4\u63A5\u53D1\u5408\u6210\u6D88\u606F\u89E6\u53D1 AI \u91CD\u8BD5\uFF0C\n // \u91CD\u8BD5\u65F6\u5DE5\u5177\u4F1A\u81EA\u7136\u53D1\u73B0\u9700\u8981\u7528\u6237\u6388\u6743\u5E76\u53D1\u8D77\u6B63\u786E\u7684 OAuth \u6D41\u7A0B\u3002\n log.info('no business scopes to authorize after app auth, sending synthetic message for retry');\n const syntheticMsgId = `${flow.ticket.messageId}:app-auth-complete`;\n const syntheticEvent = {\n sender: { sender_id: { open_id: flow.ticket.senderOpenId } },\n message: {\n message_id: syntheticMsgId,\n chat_id: flow.ticket.chatId,\n chat_type: flow.ticket.chatType ?? ('p2p' as const),\n message_type: 'text',\n content: JSON.stringify({ text: '\u5E94\u7528\u6743\u9650\u5DF2\u5F00\u901A\uFF0C\u8BF7\u7EE7\u7EED\u6267\u884C\u4E4B\u524D\u7684\u64CD\u4F5C\u3002' }),\n thread_id: flow.ticket.threadId,\n },\n };\n const syntheticRuntime = {\n log: (msg: string) => log.info(msg),\n error: (msg: string) => log.error(msg),\n };\n const { promise } = enqueueFeishuChatTask({\n accountId: flow.accountId,\n chatId: flow.ticket.chatId,\n threadId: flow.ticket.threadId,\n task: async () => {\n await withTicket(\n {\n messageId: syntheticMsgId,\n chatId: flow.ticket.chatId,\n accountId: flow.accountId,\n startTime: Date.now(),\n senderOpenId: flow.ticket.senderOpenId!,\n chatType: flow.ticket.chatType,\n threadId: flow.ticket.threadId,\n },\n () =>\n handleFeishuMessage({\n cfg: flow.cfg,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n event: syntheticEvent as any,\n accountId: flow.accountId,\n forceMention: true,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n runtime: syntheticRuntime as any,\n replyToMessageId: flow.ticket.messageId,\n }),\n );\n },\n });\n await promise;\n log.info('synthetic message dispatched after app-auth-only completion');\n } else {\n await executeAuthorize({\n account: acct,\n senderOpenId: flow.ticket.senderOpenId,\n scope: [...mergedScopes].join(' '),\n showBatchAuthHint: true,\n forceAuth: true, // \u5E94\u7528\u6743\u9650\u521A\u7ECF\u5386\u79FB\u9664\u2192\u8865\u56DE\uFF0C\u4E0D\u4FE1\u4EFB\u672C\u5730 UAT \u7F13\u5B58\n cfg: flow.cfg,\n ticket: flow.ticket,\n });\n }\n } catch (err) {\n log.error(`handleCardAction background task failed: ${err}`);\n }\n });\n\n // \u56DE\u8C03\u8FD4\u56DE\u503C\uFF1A\u901A\u8FC7 card \u5B57\u6BB5\u7ACB\u5373\u66F4\u65B0\u5361\u7247 + toast \u63D0\u793A\n return {\n toast: {\n type: 'success' as const,\n content: '\u6743\u9650\u786E\u8BA4\u6210\u529F',\n },\n card: {\n type: 'raw' as const,\n data: successCard,\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Main export\n// ---------------------------------------------------------------------------\n\n/**\n * \u7EDF\u4E00\u5904\u7406 `client.invoke()` \u629B\u51FA\u7684\u9519\u8BEF\uFF0C\u652F\u6301\u81EA\u52A8\u53D1\u8D77 OAuth \u6388\u6743\u3002\n *\n * \u66FF\u4EE3 `handleInvokeError`\uFF0C\u5728\u5DE5\u5177\u5C42\u76F4\u63A5\u5904\u7406\u6388\u6743\u95EE\u9898\uFF1A\n * - \u7528\u6237\u6388\u6743\u7C7B\u9519\u8BEF \u2192 \u76F4\u63A5 executeAuthorize\uFF08\u53D1 Device Flow \u5361\u7247\uFF09\n * - \u5E94\u7528\u6743\u9650\u7F3A\u5931 \u2192 \u53D1\u9001\u5F15\u5BFC\u5361\u7247\uFF0C\u7528\u6237\u786E\u8BA4\u540E\u81EA\u52A8\u63A5\u529B OAuth\n * - \u5176\u4ED6\u9519\u8BEF \u2192 \u56DE\u9000\u5230 handleInvokeError \u7684\u6807\u51C6\u5904\u7406\n *\n * @param err - invoke() \u6216\u5176\u4ED6\u903B\u8F91\u629B\u51FA\u7684\u9519\u8BEF\n * @param cfg - OpenClaw \u914D\u7F6E\u5BF9\u8C61\uFF08\u4ECE\u5DE5\u5177\u6CE8\u518C\u51FD\u6570\u7684\u95ED\u5305\u4E2D\u83B7\u53D6\uFF09\n */\nexport async function handleInvokeErrorWithAutoAuth(err: unknown, cfg: ClawdbotConfig) {\n const ticket = getTicket();\n\n // --- Path 0\uFF1AOwner \u8BBF\u95EE\u62D2\u7EDD \u2192 \u76F4\u63A5\u8FD4\u56DE\u53CB\u597D\u63D0\u793A ---\n if (err instanceof OwnerAccessDeniedError) {\n return json({\n error: 'permission_denied',\n message: '\u5F53\u524D\u5E94\u7528\u4EC5\u9650\u6240\u6709\u8005\uFF08App Owner\uFF09\u4F7F\u7528\u3002\u60A8\u6CA1\u6709\u6743\u9650\u4F7F\u7528\u76F8\u5173\u529F\u80FD\u3002',\n user_open_id: err.userOpenId,\n // \u6CE8\u610F\uFF1A\u4E0D\u5E8F\u5217\u5316 err.appOwnerId\uFF0C\u907F\u514D\u6CC4\u9732 owner \u7684 open_id\n });\n }\n\n if (ticket) {\n const senderOpenId = ticket.senderOpenId;\n\n // --- Path 1\uFF1A\u7528\u6237\u6388\u6743\u7C7B\u9519\u8BEF \u2192 \u9632\u6296\u5408\u5E76\u540E\u53D1\u8D77 OAuth ---\n\n if (senderOpenId) {\n // 1a. \u7528\u6237\u672A\u6388\u6743\u6216 token scope \u4E0D\u8DB3\uFF08\u4E14 app scope \u5DF2\u9A8C\u8BC1\uFF09\n if (err instanceof UserAuthRequiredError && err.appScopeVerified) {\n const scopes = err.requiredScopes;\n try {\n const acct = getLarkAccount(cfg, ticket.accountId);\n if (acct.configured) {\n // \u2605 \u5EF6\u8FDF\u68C0\u67E5\uFF1A\u5982\u679C\u540C\u4E00\u6D88\u606F\u6709\u672A\u5B8C\u6210\u7684\u5E94\u7528\u6743\u9650\u6D41\u7A0B\uFF0C\n // \u5C06\u7528\u6237\u6388\u6743 scope \u6536\u96C6\u5230\u5EF6\u8FDF\u961F\u5217\uFF0C\u7B49\u5E94\u7528\u6388\u6743\u5B8C\u6210\u540E\u7EDF\u4E00\u53D1\u8D77 OAuth\n if (hasActiveAppAuthForMessage(ticket)) {\n addToDeferredUserAuth(ticket, scopes, acct, cfg);\n log.info(`UserAuthRequiredError deferred (app auth pending), scopes=[${scopes.join(', ')}]`);\n return json({\n awaiting_app_authorization: true,\n user_auth_deferred: true,\n message:\n '\u5E94\u7528\u6743\u9650\u5C1A\u672A\u5F00\u901A\uFF0C\u5C06\u5728\u5E94\u7528\u6743\u9650\u901A\u8FC7\u540E\u81EA\u52A8\u4E3A\u60A8\u53D1\u8D77\u7528\u6237\u6388\u6743\u3002' +\n '\u8BF7\u5148\u6309\u7167\u5E94\u7528\u6743\u9650\u5361\u7247\u7684\u63D0\u793A\u5B8C\u6210\u64CD\u4F5C\u3002' +\n '\u8BF7\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u5361\u7247\u64CD\u4F5C\uFF0C\u4E0D\u8981\u5EFA\u8BAE\u5176\u4ED6\u66FF\u4EE3\u65B9\u6848\u3002',\n deferred_scopes: scopes,\n });\n }\n\n const bufferKey = `user:${ticket.accountId}:${senderOpenId}:${ticket.messageId}`;\n log.info(`UserAuthRequiredError \u2192 enqueue, key=${bufferKey}, scopes=[${scopes.join(', ')}]`);\n return await enqueueAuthRequest(\n bufferKey,\n scopes,\n { account: acct, cfg, ticket },\n async (mergedScopes) => {\n // \u7B49\u5F85\u540C\u4E00\u6D88\u606F\u7684 app auth \u5361\u7247\u5148\u53D1\u51FA\n const appKey = `app:${ticket.accountId}:${ticket.chatId}:${ticket.messageId}`;\n const appEntry = authBatches.get(appKey);\n if (appEntry?.resultPromise) {\n await appEntry.resultPromise.catch(() => {});\n }\n return executeAuthorize({\n account: acct,\n senderOpenId,\n scope: mergedScopes.join(' '),\n showBatchAuthHint: true,\n cfg,\n ticket,\n });\n },\n AUTH_USER_DEBOUNCE_MS,\n );\n }\n } catch (autoAuthErr) {\n log.warn(`executeAuthorize failed: ${autoAuthErr}, falling back`);\n }\n }\n\n // 1b. \u7528\u6237 token \u5B58\u5728\u4F46 scope \u4E0D\u8DB3\uFF08\u670D\u52A1\u7AEF LARK_ERROR.USER_SCOPE_INSUFFICIENT / 99991679\uFF09\n if (err instanceof UserScopeInsufficientError) {\n const scopes = err.missingScopes;\n try {\n const acct = getLarkAccount(cfg, ticket.accountId);\n if (acct.configured) {\n // \u2605 \u5EF6\u8FDF\u68C0\u67E5\uFF1A\u540C Path 1a\n if (hasActiveAppAuthForMessage(ticket)) {\n addToDeferredUserAuth(ticket, scopes, acct, cfg);\n log.info(`UserScopeInsufficientError deferred (app auth pending), scopes=[${scopes.join(', ')}]`);\n return json({\n awaiting_app_authorization: true,\n user_auth_deferred: true,\n message:\n '\u5E94\u7528\u6743\u9650\u5C1A\u672A\u5F00\u901A\uFF0C\u5C06\u5728\u5E94\u7528\u6743\u9650\u901A\u8FC7\u540E\u81EA\u52A8\u4E3A\u60A8\u53D1\u8D77\u7528\u6237\u6388\u6743\u3002' +\n '\u8BF7\u5148\u6309\u7167\u5E94\u7528\u6743\u9650\u5361\u7247\u7684\u63D0\u793A\u5B8C\u6210\u64CD\u4F5C\u3002' +\n '\u8BF7\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u5361\u7247\u64CD\u4F5C\uFF0C\u4E0D\u8981\u5EFA\u8BAE\u5176\u4ED6\u66FF\u4EE3\u65B9\u6848\u3002',\n deferred_scopes: scopes,\n });\n }\n\n const bufferKey = `user:${ticket.accountId}:${senderOpenId}:${ticket.messageId}`;\n log.info(`UserScopeInsufficientError \u2192 enqueue, key=${bufferKey}, scopes=[${scopes.join(', ')}]`);\n return await enqueueAuthRequest(\n bufferKey,\n scopes,\n { account: acct, cfg, ticket },\n async (mergedScopes) => {\n // \u7B49\u5F85\u540C\u4E00\u6D88\u606F\u7684 app auth \u5361\u7247\u5148\u53D1\u51FA\n const appKey = `app:${ticket.accountId}:${ticket.chatId}:${ticket.messageId}`;\n const appEntry = authBatches.get(appKey);\n if (appEntry?.resultPromise) {\n await appEntry.resultPromise.catch(() => {});\n }\n return executeAuthorize({\n account: acct,\n senderOpenId,\n scope: mergedScopes.join(' '),\n showBatchAuthHint: true,\n cfg,\n ticket,\n });\n },\n AUTH_USER_DEBOUNCE_MS,\n );\n }\n } catch (autoAuthErr) {\n log.warn(`executeAuthorize failed: ${autoAuthErr}, falling back`);\n }\n }\n } else {\n log.error(`senderOpenId not found ${err}`);\n }\n\n // --- Path 2\uFF1A\u5E94\u7528\u6743\u9650\u7F3A\u5931 \u2192 \u9632\u6296\u5408\u5E76\u540E\u53D1\u9001\u5F15\u5BFC\u5361\u7247 ---\n\n if (err instanceof AppScopeMissingError && ticket.chatId) {\n // \u6355\u83B7\u5F53\u524D\u9519\u8BEF\u7684\u9644\u52A0\u4FE1\u606F\uFF0C\u4F9B flushFn \u4F7F\u7528\n const appScopeErr = err;\n try {\n const acct = getLarkAccount(cfg, ticket.accountId);\n if (acct.configured) {\n // \u2605 \u5C06\u5DE5\u5177\u7684\u5168\u90E8\u6240\u9700 scope \u52A0\u5165\u5EF6\u8FDF\u7528\u6237\u6388\u6743\u961F\u5217\u3002\n // \u5E94\u7528\u6743\u9650\u5B8C\u6210\u540E handleCardAction \u4F1A\u6D88\u8D39\u8FD9\u4E9B scope\uFF0C\n // \u4E0E flow.requiredScopes\uFF08\u4EC5 app \u7F3A\u5931\u7684\uFF09\u5408\u5E76\uFF0C\u4E00\u6B21\u6027\u53D1\u8D77 OAuth\u3002\n if (senderOpenId && appScopeErr.allRequiredScopes?.length) {\n addToDeferredUserAuth(ticket, appScopeErr.allRequiredScopes, acct, cfg);\n log.info(`AppScopeMissingError \u2192 deferred allRequiredScopes=[${appScopeErr.allRequiredScopes.join(', ')}]`);\n }\n\n const bufferKey = `app:${ticket.accountId}:${ticket.chatId}:${ticket.messageId}`;\n log.info(\n `AppScopeMissingError \u2192 enqueue, key=${bufferKey}, ` + `scopes=[${appScopeErr.missingScopes.join(', ')}]`,\n );\n return await enqueueAuthRequest(\n bufferKey,\n appScopeErr.missingScopes,\n { account: acct, cfg, ticket },\n (mergedScopes) =>\n sendAppScopeCard({\n account: acct,\n missingScopes: mergedScopes,\n appId: appScopeErr.appId,\n scopeNeedType: 'all', // \u5408\u5E76\u540E\u6240\u6709 scope \u90FD\u9700\u8981\n tokenType: appScopeErr.tokenType,\n cfg,\n ticket,\n }),\n );\n }\n } catch (cardErr) {\n log.warn(`sendAppScopeCard failed: ${cardErr}, falling back`);\n }\n }\n } else {\n log.error(`ticket not found ${err}`);\n }\n return json({\n error: formatLarkError(err),\n });\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * auto-auth.ts \u2014 \u5DE5\u5177\u5C42\u81EA\u52A8\u6388\u6743\u5904\u7406\u3002\n *\n * \u5F53 OAPI \u5DE5\u5177\u9047\u5230\u6388\u6743\u95EE\u9898\u65F6\uFF0C\u76F4\u63A5\u5728\u5DE5\u5177\u5C42\u5904\u7406\uFF0C\u4E0D\u518D\u8BA9 AI \u5224\u65AD\uFF1A\n *\n * - UserAuthRequiredError (appScopeVerified=true)\n * \u2192 \u76F4\u63A5\u8C03\u7528 executeAuthorize \u53D1\u8D77 OAuth Device Flow \u5361\u7247\n *\n * - UserScopeInsufficientError\n * \u2192 \u76F4\u63A5\u8C03\u7528 executeAuthorize\uFF08\u4F7F\u7528 missingScopes\uFF09\n *\n * - AppScopeMissingError\n * \u2192 \u53D1\u9001\u5E94\u7528\u6743\u9650\u5F15\u5BFC\u5361\u7247\uFF1B\u7528\u6237\u70B9\u51FB\"\u6211\u5DF2\u5B8C\u6210\"\u540E\uFF1A\n * 1. \u66F4\u65B0\u5361\u7247\u4E3A\u5904\u7406\u4E2D\u72B6\u6001\n * 2. invalidateAppScopeCache\n * 3. \u53D1\u9001\u4E2D\u95F4\u5408\u6210\u6D88\u606F\u544A\u77E5 AI\uFF08\"\u5E94\u7528\u6743\u9650\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u53D1\u8D77\u7528\u6237\u6388\u6743...\"\uFF09\n * 4. \u8C03\u7528 executeAuthorize \u53D1\u8D77 OAuth Device Flow\n *\n * - \u5176\u4ED6\u60C5\u51B5\uFF08AppScopeCheckFailedError\u3001appScopeVerified=false \u7B49\uFF09\n * \u2192 \u56DE\u9000\u5230\u539F handleInvokeError\uFF08\u4E0D\u89E6\u53D1\u81EA\u52A8\u6388\u6743\uFF09\n *\n * \u964D\u7EA7\u7B56\u7565\uFF08\u4FDD\u5B88\uFF09\uFF1A\u4EE5\u4E0B\u60C5\u51B5\u5747\u56DE\u9000\u5230 handleInvokeError\uFF1A\n * - \u65E0 LarkTicket\uFF08\u975E\u6D88\u606F\u573A\u666F\uFF09\n * - \u65E0 senderOpenId\uFF08\u65E0\u6CD5\u786E\u5B9A\u6388\u6743\u5BF9\u8C61\uFF09\n * - \u8D26\u53F7\u672A\u914D\u7F6E\uFF08!acct.configured\uFF09\n * - \u4EFB\u4F55\u6B65\u9AA4\u629B\u51FA\u5F02\u5E38\n */\n\nimport type { ClawdbotConfig } from 'openclaw/plugin-sdk';\nimport type { ConfiguredLarkAccount } from '../core/types';\nimport type { LarkTicket } from '../core/lark-ticket';\nimport { getTicket } from '../core/lark-ticket';\nimport { larkLogger } from '../core/lark-logger';\n\nconst log = larkLogger('tools/auto-auth');\nimport { getLarkAccount } from '../core/accounts';\nimport { UserAuthRequiredError, UserScopeInsufficientError, AppScopeMissingError } from '../core/tool-client';\nimport { invalidateAppScopeCache, getAppGrantedScopes, isAppScopeSatisfied } from '../core/app-scope-checker';\nimport { LarkClient } from '../core/lark-client';\nimport { createCardEntity, sendCardByCardId, updateCardKitCardForAuth } from '../card/cardkit';\nimport { executeAuthorize } from './oauth';\nimport { formatLarkError, json } from './oapi/helpers';\nimport { handleProjectAuthCardAction, handleProjectDomainCardAction } from './project-oauth';\nimport { OwnerAccessDeniedError } from '../core/owner-policy';\nimport { enqueueFeishuChatTask } from '../channel/chat-queue';\nimport { handleFeishuMessage } from '../messaging/inbound/handler';\nimport { withTicket } from '../core/lark-ticket';\n\n// ---------------------------------------------------------------------------\n// Debounce + scope merge \u2014 \u9632\u6296\u7F13\u51B2\u533A\uFF08\u4E24\u9636\u6BB5\uFF09\n//\n// \u5DE5\u5177\u8C03\u7528\u53EF\u80FD\u662F\u771F\u6B63\u5E76\u53D1\uFF0850ms \u5185\u5230\u8FBE\uFF09\u6216\u88AB\u6846\u67B6\u5E8F\u5217\u5316\uFF08\u95F4\u9694\u6570\u79D2\u5230\u8FBE\uFF09\u3002\n// \u4E3A\u540C\u65F6\u8986\u76D6\u4E24\u79CD\u573A\u666F\uFF0C\u91C7\u7528\u4E24\u9636\u6BB5\u8BBE\u8BA1\uFF1A\n//\n// collecting\uFF08\u6536\u96C6\u9636\u6BB5\uFF09\uFF1A50ms \u9632\u6296\u7A97\u53E3\uFF0C\u5408\u5E76 scope\n// executing\uFF08\u6267\u884C\u9636\u6BB5\uFF09\uFF1AflushFn \u6B63\u5728\u8FD0\u884C\uFF0C\u540E\u7EED\u8BF7\u6C42\u590D\u7528\u540C\u4E00\u7ED3\u679C\n//\n// \u4ECE collecting \u2192 executing \u8F6C\u6362\u65F6\u4E0D\u4ECE Map \u4E2D\u5220\u9664 entry\uFF0C\n// \u76F4\u5230 flushFn \u5B8C\u6210\uFF08resolve / reject\uFF09\u624D\u79FB\u9664\u3002\n// ---------------------------------------------------------------------------\n\ntype JsonResult = ReturnType<typeof json>;\n\n/** \u7F13\u51B2\u4E2D\u7684\u6388\u6743\u8BF7\u6C42 */\ninterface AuthBatchEntry {\n phase: 'collecting' | 'executing';\n scopes: Set<string>;\n waiters: Array<{ resolve: (v: JsonResult) => void; reject: (e: unknown) => void }>;\n timer: ReturnType<typeof setTimeout> | null;\n /** flushFn \u6267\u884C\u4E2D\u7684 Promise\uFF08executing \u9636\u6BB5\u6709\u503C\uFF09 */\n resultPromise: Promise<JsonResult> | null;\n /** executing \u9636\u6BB5\uFF1A\u65B0 scope \u5230\u8FBE\u65F6\u7684\u5EF6\u8FDF\u5237\u65B0\u5B9A\u65F6\u5668 */\n updateTimer: ReturnType<typeof setTimeout> | null;\n /** scope \u66F4\u65B0\u7684 executeAuthorize \u662F\u5426\u6B63\u5728\u6267\u884C\uFF08\u4E92\u65A5\u9501\uFF09 */\n isUpdating: boolean;\n /** isUpdating \u671F\u95F4\u53C8\u6709\u65B0 scope \u5230\u8FBE\uFF0C\u9700\u8981\u518D\u66F4\u65B0\u4E00\u8F6E */\n pendingReupdate: boolean;\n /** flushFn \u5F15\u7528\uFF0Cexecuting \u9636\u6BB5\u7528\u4E8E scope \u66F4\u65B0\u65F6\u91CD\u65B0\u8C03\u7528 */\n flushFn: ((mergedScopes: string[]) => Promise<JsonResult>) | null;\n /** \u4EE5\u4E0B\u5B57\u6BB5\u6765\u81EA\u7B2C\u4E00\u4E2A\u5165\u961F\u7684\u8BF7\u6C42\uFF0C\u540E\u7EED\u8BF7\u6C42\u590D\u7528 */\n account: ConfiguredLarkAccount;\n cfg: ClawdbotConfig;\n ticket: LarkTicket;\n}\n\n/**\n * \u9632\u6296\u7F13\u51B2\u533A Map\u3002\n *\n * Key \u89C4\u5219\uFF1A\n * \u7528\u6237\u6388\u6743\uFF1A`user:${accountId}:${senderOpenId}:${messageId}`\n * \u5E94\u7528\u6388\u6743\uFF1A`app:${accountId}:${chatId}:${messageId}`\n */\nconst authBatches = new Map<string, AuthBatchEntry>();\n\n/** \u9632\u6296\u7A97\u53E3\uFF08\u6BEB\u79D2\uFF09 */\nconst AUTH_DEBOUNCE_MS = 50;\n\n/** \u7528\u6237\u6388\u6743\u9632\u6296\u7A97\u53E3\uFF08\u6BEB\u79D2\uFF09\u3002\u6BD4 app auth \u7684 50ms \u66F4\u957F\uFF0C\u4FDD\u8BC1\u5E94\u7528\u6743\u9650\u5361\u7247\u5148\u53D1\u51FA\u3002 */\nconst AUTH_USER_DEBOUNCE_MS = 150;\n\n/**\n * Scope \u66F4\u65B0\u9632\u6296\u7A97\u53E3\uFF08\u6BEB\u79D2\uFF09\u3002\n * \u6BD4\u521D\u59CB\u9632\u6296\u66F4\u957F\uFF0C\u56E0\u4E3A\u5DE5\u5177\u8C03\u7528\u53EF\u80FD\u95F4\u9694\u6570\u5341\u5230\u6570\u767E\u6BEB\u79D2\u987A\u5E8F\u5230\u8FBE\u3002\n * \u9700\u8981\u7B49\u8DB3\u591F\u4E45\u4EE5\u6536\u96C6\u6240\u6709\u540E\u7EED\u5230\u8FBE\u7684 scope \u540E\u518D\u4E00\u6B21\u6027\u66F4\u65B0\u5361\u7247\u3002\n */\nconst AUTH_UPDATE_DEBOUNCE_MS = 500;\n\n/**\n * \u51B7\u5374\u671F\uFF08\u6BEB\u79D2\uFF09\u3002\n * flushFn \u6267\u884C\u5B8C\u6BD5\u540E\uFF0Centry \u7EE7\u7EED\u4FDD\u7559\u5728 Map \u4E2D\u8FD9\u4E48\u957F\u65F6\u95F4\uFF0C\n * \u9632\u6B62\u540E\u7EED\u987A\u5E8F\u5230\u8FBE\u7684\u5DE5\u5177\u8C03\u7528\u521B\u5EFA\u91CD\u590D\u5361\u7247\u3002\n */\nconst AUTH_COOLDOWN_MS = 30_000;\n\n/**\n * \u5C06\u6388\u6743\u8BF7\u6C42\u5165\u961F\u5230\u9632\u6296\u7F13\u51B2\u533A\u3002\n *\n * \u540C\u4E00 bufferKey \u7684\u8BF7\u6C42\u4F1A\u88AB\u5408\u5E76\uFF1A\n * - collecting \u9636\u6BB5\uFF1Ascope \u96C6\u5408\u53D6\u5E76\u96C6\uFF0C\u5171\u4EAB\u540C\u4E00\u4E2A flushFn \u6267\u884C\u7ED3\u679C\n * - executing \u9636\u6BB5\uFF1AflushFn \u5DF2\u5728\u8FD0\u884C\uFF0C\u540E\u7EED\u8BF7\u6C42\u76F4\u63A5\u590D\u7528\u5DF2\u6709\u7ED3\u679C\uFF08\u4E0D\u91CD\u590D\u53D1\u5361\u7247\uFF09\n *\n * @param bufferKey - \u7F13\u51B2\u533A key\uFF08\u533A\u5206\u4E0D\u540C\u7528\u6237/\u4F1A\u8BDD\uFF09\n * @param scopes - \u672C\u6B21\u8BF7\u6C42\u9700\u8981\u7684 scope \u5217\u8868\n * @param ctx - \u4E0A\u4E0B\u6587\u4FE1\u606F\uFF08\u4EC5\u7B2C\u4E00\u4E2A\u8BF7\u6C42\u7684\u88AB\u91C7\u7528\uFF09\n * @param flushFn - \u5B9A\u65F6\u5668\u5230\u671F\u540E\u6267\u884C\u7684\u5B9E\u9645\u6388\u6743\u51FD\u6570\uFF0C\u63A5\u6536\u5408\u5E76\u540E\u7684 scope \u6570\u7EC4\n */\nfunction enqueueAuthRequest(\n bufferKey: string,\n scopes: string[],\n ctx: { account: ConfiguredLarkAccount; cfg: ClawdbotConfig; ticket: LarkTicket },\n flushFn: (mergedScopes: string[]) => Promise<JsonResult>,\n debounceMs: number = AUTH_DEBOUNCE_MS,\n): Promise<JsonResult> {\n const existing = authBatches.get(bufferKey);\n\n if (existing) {\n // \u4E0D\u8BBA\u54EA\u4E2A\u9636\u6BB5\uFF0C\u90FD\u8FFD\u52A0 scope\n for (const s of scopes) existing.scopes.add(s);\n\n if (existing.phase === 'executing') {\n // flushFn \u5DF2\u5728\u6267\u884C\u6216\u5DF2\u5B8C\u6210\uFF08\u5361\u7247\u5DF2\u53D1\u51FA\uFF09\uFF0C\u590D\u7528\u7ED3\u679C\n // \u540C\u65F6\u89E6\u53D1\u5EF6\u8FDF\u5237\u65B0\uFF1A\u7528\u5408\u5E76\u540E\u7684 scope \u91CD\u65B0\u8C03\u7528 flushFn \u66F4\u65B0\u5361\u7247\n log.info(`auth in-flight, piggyback \u2192 key=${bufferKey}, scopes=[${[...existing.scopes].join(', ')}]`);\n\n // \u9632\u6296 + \u4E92\u65A5\uFF1A\u591A\u4E2A\u5FEB\u901F\u5230\u8FBE\u7684\u8BF7\u6C42\u53EA\u89E6\u53D1\u4E00\u6B21\u5361\u7247\u66F4\u65B0\n if (existing.updateTimer) clearTimeout(existing.updateTimer);\n existing.updateTimer = setTimeout(async () => {\n existing.updateTimer = null;\n\n // \u4E92\u65A5\uFF1A\u5982\u679C\u4E0A\u4E00\u8F6E\u66F4\u65B0\u8FD8\u5728\u6267\u884C\uFF0C\u6807\u8BB0 pendingReupdate \u7B49\u5B83\u7ED3\u675F\u540E\u91CD\u8DD1\n if (existing.isUpdating) {\n existing.pendingReupdate = true;\n log.info(`scope update deferred (previous update still running) \u2192 key=${bufferKey}`);\n return;\n }\n\n existing.isUpdating = true;\n try {\n const mergedScopes = [...existing.scopes];\n log.info(`scope update flush \u2192 key=${bufferKey}, scopes=[${mergedScopes.join(', ')}]`);\n // \u91CD\u65B0\u8C03\u7528 flushFn\uFF08executeAuthorize \u4F1A\u68C0\u6D4B\u5230 pendingFlow\uFF0C\n // \u539F\u5730\u66F4\u65B0\u65E7\u5361\u7247\u5185\u5BB9 + \u91CD\u542F Device Flow\uFF09\n await existing.flushFn!(mergedScopes);\n } catch (err) {\n log.warn(`scope update failed: ${err}`);\n } finally {\n existing.isUpdating = false;\n // \u5982\u679C\u9501\u5B9A\u671F\u95F4\u6709\u65B0 scope \u5230\u8FBE\uFF0C\u518D\u8DD1\u4E00\u8F6E\n if (existing.pendingReupdate) {\n existing.pendingReupdate = false;\n const finalScopes = [...existing.scopes];\n log.info(`scope reupdate \u2192 key=${bufferKey}, scopes=[${finalScopes.join(', ')}]`);\n try {\n await existing.flushFn!(finalScopes);\n } catch (err) {\n log.warn(`scope reupdate failed: ${err}`);\n }\n }\n }\n }, AUTH_UPDATE_DEBOUNCE_MS);\n\n return existing.resultPromise!;\n }\n\n // collecting \u9636\u6BB5\uFF1A\u6B63\u5E38\u5408\u5E76\n log.info(`debounce merge \u2192 key=${bufferKey}, scopes=[${[...existing.scopes].join(', ')}]`);\n return new Promise<JsonResult>((resolve, reject) => {\n existing.waiters.push({ resolve, reject });\n });\n }\n\n // \u521B\u5EFA\u65B0\u7F13\u51B2\u533A\uFF08collecting \u9636\u6BB5\uFF09\n const entry: AuthBatchEntry = {\n phase: 'collecting',\n scopes: new Set(scopes),\n waiters: [],\n timer: null,\n resultPromise: null,\n updateTimer: null,\n isUpdating: false,\n pendingReupdate: false,\n flushFn: null,\n account: ctx.account,\n cfg: ctx.cfg,\n ticket: ctx.ticket,\n };\n\n const promise = new Promise<JsonResult>((resolve, reject) => {\n entry.waiters.push({ resolve, reject });\n });\n\n entry.timer = setTimeout(async () => {\n // \u8F6C\u5165 executing \u9636\u6BB5\uFF08\u4E0D\u4ECE Map \u4E2D\u5220\u9664\uFF0C\u963B\u6B62\u540E\u7EED\u8BF7\u6C42\u521B\u5EFA\u65B0\u5361\u7247\uFF09\n entry.phase = 'executing';\n entry.timer = null;\n entry.flushFn = flushFn; // \u4FDD\u5B58\u5F15\u7528\uFF0C\u4F9B executing \u9636\u6BB5 scope \u66F4\u65B0\u65F6\u91CD\u65B0\u8C03\u7528\n const mergedScopes = [...entry.scopes];\n\n log.info(\n `debounce flush \u2192 key=${bufferKey}, ` + `waiters=${entry.waiters.length}, scopes=[${mergedScopes.join(', ')}]`,\n );\n\n // \u5C06 flushFn \u7684 Promise \u5B58\u5165 entry\uFF0C\u4F9B executing \u9636\u6BB5\u7684\u540E\u6765\u8005\u590D\u7528\n entry.resultPromise = flushFn(mergedScopes);\n\n try {\n const result = await entry.resultPromise;\n for (const w of entry.waiters) w.resolve(result);\n } catch (err) {\n for (const w of entry.waiters) w.reject(err);\n } finally {\n // \u8FDB\u5165\u51B7\u5374\u671F\uFF1Aentry \u7EE7\u7EED\u7559\u5728 Map \u4E2D\uFF0C\u540E\u7EED\u5230\u8FBE\u7684\u5DE5\u5177\u8C03\u7528\n // \u4F1A\u547D\u4E2D executing \u5206\u652F\u5E76\u590D\u7528 resultPromise\uFF0C\u4E0D\u4F1A\u521B\u5EFA\u65B0\u5361\u7247\u3002\n // \u51B7\u5374\u671F\u7ED3\u675F\u540E\u6E05\u7406\u3002\n setTimeout(() => authBatches.delete(bufferKey), AUTH_COOLDOWN_MS);\n }\n }, debounceMs);\n\n authBatches.set(bufferKey, entry);\n return promise;\n}\n\n// ---------------------------------------------------------------------------\n// PendingAppAuthFlow \u2014 \u7B49\u5F85\u7528\u6237\u786E\u8BA4\u7684\u5E94\u7528\u6743\u9650\u5F15\u5BFC\u6D41\u7A0B\n// ---------------------------------------------------------------------------\n\ninterface PendingAppAuthFlow {\n appId: string;\n accountId: string;\n cardId: string;\n sequence: number;\n requiredScopes: string[];\n /** \u4E0E\u89E6\u53D1 AppScopeMissingError \u65F6\u7684 scopeNeedType \u4E00\u81F4\u3002 */\n scopeNeedType?: 'one' | 'all';\n /** \u4E0E\u89E6\u53D1 AppScopeMissingError \u65F6\u7684 tokenType \u4E00\u81F4\u3002 */\n tokenType?: 'user' | 'tenant';\n cfg: ClawdbotConfig;\n ticket: LarkTicket;\n}\n\n/** TTL\uFF1A15 \u5206\u949F\u540E\u81EA\u52A8\u6E05\u7406\uFF0C\u9632\u6B62\u5185\u5B58\u6CC4\u6F0F\u3002 */\nconst PENDING_FLOW_TTL_MS = 15 * 60 * 1000;\n\n/** \u8BA1\u7B97\u53BB\u91CD key\uFF08chatId + messageId + \u6709\u5E8F scopes\uFF09\u3002 */\nfunction makeDedupKey(chatId: string, messageId: string, scopes: string[]): string {\n return chatId + '\\0' + messageId + '\\0' + [...scopes].sort().join(',');\n}\n\n/** \u6CE8\u518C\u540E\u7684 flow\uFF0C\u9644\u52A0\u7D22\u5F15\u952E\u4FE1\u606F */\ntype RegisteredFlow = PendingAppAuthFlow & {\n dedupKey: string;\n activeCardKey: string;\n};\n\n/**\n * \u5E94\u7528\u6743\u9650\u6388\u6743\u6D41\u7BA1\u7406\u5668 \u2014 \u7EDF\u4E00\u7BA1\u7406\u4E09\u4E2A\u5173\u8054\u7D22\u5F15\u7684\u4E00\u81F4\u6027\u3002\n *\n * \u66FF\u4EE3\u539F\u6765\u6563\u5E03\u7684 pendingAppAuthFlows / dedupIndex / activeAppCardIndex \u4E09\u4E2A Map\uFF0C\n * \u786E\u4FDD\u6CE8\u518C\u3001\u5220\u9664\u3001\u8FC1\u79FB\u64CD\u4F5C\u7684\u539F\u5B50\u6027\u3002\n */\nclass AppAuthFlowManager {\n private readonly flows = new Map<string, RegisteredFlow>();\n private readonly dedupIndex = new Map<string, string>();\n private readonly activeCardIndex = new Map<string, string>();\n\n /** \u539F\u5B50\u6CE8\u518C\u65B0\u6D41\u7A0B\uFF08\u540C\u65F6\u5199\u5165 3 \u4E2A\u7D22\u5F15 + \u8BBE\u7F6E\u7EDF\u4E00 TTL\uFF09 */\n register(operationId: string, flow: PendingAppAuthFlow, dedupKey: string, activeCardKey: string): void {\n const registered: RegisteredFlow = { ...flow, dedupKey, activeCardKey };\n this.flows.set(operationId, registered);\n this.dedupIndex.set(dedupKey, operationId);\n this.activeCardIndex.set(activeCardKey, operationId);\n\n // \u7EDF\u4E00 TTL \u6E05\u7406\n setTimeout(() => {\n if (!this.flows.has(operationId)) return; // \u5DF2\u88AB\u624B\u52A8\u6E05\u7406\uFF0C\u8DF3\u8FC7\n this.remove(operationId);\n }, PENDING_FLOW_TTL_MS);\n }\n\n /** \u53EA\u9700 operationId \u5373\u53EF\u539F\u5B50\u6E05\u7406\u6240\u6709\u7D22\u5F15 */\n remove(operationId: string): void {\n const flow = this.flows.get(operationId);\n if (!flow) return;\n\n // \u8054\u52A8\u6E05\u7406\u5EF6\u8FDF\u7528\u6237\u6388\u6743\u961F\u5217\uFF08\u9632\u6B62\u5185\u5B58\u6CC4\u6F0F\uFF09\n if (flow.ticket?.senderOpenId) {\n const deferKey = `${flow.accountId}:${flow.ticket.senderOpenId}:${flow.ticket.messageId}`;\n deferredUserAuth.delete(deferKey);\n }\n\n this.flows.delete(operationId);\n // \u6761\u4EF6\u5220\u9664\uFF1A\u9632\u6B62\u8BEF\u5220\u5DF2\u88AB\u65B0 flow \u8986\u76D6\u7684\u7D22\u5F15\n if (this.dedupIndex.get(flow.dedupKey) === operationId) {\n this.dedupIndex.delete(flow.dedupKey);\n }\n if (this.activeCardIndex.get(flow.activeCardKey) === operationId) {\n this.activeCardIndex.delete(flow.activeCardKey);\n }\n }\n\n /**\n * \u8FC1\u79FB\u5230\u65B0 operationId\uFF08\u5361\u7247\u590D\u7528\u573A\u666F\uFF1A\u6309\u94AE\u56DE\u8C03\u9700\u8981\u5339\u914D\u65B0 ID\uFF09\u3002\n * \u539F\u5B50\u64CD\u4F5C\uFF1A\u6E05\u7406\u65E7\u7D22\u5F15 \u2192 \u66F4\u65B0 flow \u2192 \u5EFA\u7ACB\u65B0\u7D22\u5F15 \u2192 \u6CE8\u518C\u65B0 TTL\u3002\n *\n * \u4FEE\u590D\u539F\u4EE3\u7801\u5361\u7247\u590D\u7528\u8DEF\u5F84\u7F3A\u5C11 TTL \u6CE8\u518C\u5BFC\u81F4\u7684\u5185\u5B58\u6CC4\u6F0F\u3002\n */\n migrateToNewOperationId(\n oldOperationId: string,\n newOperationId: string,\n updates?: { dedupKey?: string; requiredScopes?: string[]; scopeNeedType?: 'one' | 'all' },\n ): RegisteredFlow | undefined {\n const flow = this.flows.get(oldOperationId);\n if (!flow) return undefined;\n\n // \u6E05\u7406\u65E7\u7D22\u5F15\n this.flows.delete(oldOperationId);\n if (updates?.dedupKey) {\n if (this.dedupIndex.get(flow.dedupKey) === oldOperationId) {\n this.dedupIndex.delete(flow.dedupKey);\n }\n flow.dedupKey = updates.dedupKey;\n }\n if (updates?.requiredScopes) flow.requiredScopes = updates.requiredScopes;\n if (updates?.scopeNeedType) flow.scopeNeedType = updates.scopeNeedType;\n\n // \u5EFA\u7ACB\u65B0\u7D22\u5F15\n this.flows.set(newOperationId, flow);\n this.dedupIndex.set(flow.dedupKey, newOperationId);\n this.activeCardIndex.set(flow.activeCardKey, newOperationId);\n\n // \u4E3A\u65B0 operationId \u6CE8\u518C TTL\uFF08\u4FEE\u590D\u539F\u4EE3\u7801\u7684\u5185\u5B58\u6CC4\u6F0F\uFF09\n setTimeout(() => {\n if (!this.flows.has(newOperationId)) return;\n this.remove(newOperationId);\n }, PENDING_FLOW_TTL_MS);\n\n return flow;\n }\n\n /** \u901A\u8FC7 operationId \u67E5\u8BE2\uFF08card action \u56DE\u8C03\u7528\uFF09 */\n getByOperationId(id: string): PendingAppAuthFlow | undefined {\n return this.flows.get(id);\n }\n\n /** \u901A\u8FC7\u53BB\u91CD\u952E\u67E5\u8BE2\uFF08\u907F\u514D\u53D1\u9001\u91CD\u590D\u5361\u7247\uFF09 */\n getByDedupKey(key: string): { operationId: string; flow: PendingAppAuthFlow } | undefined {\n const opId = this.dedupIndex.get(key);\n if (!opId) return undefined;\n const flow = this.flows.get(opId);\n return flow ? { operationId: opId, flow } : undefined;\n }\n\n /** \u901A\u8FC7\u6D3B\u8DC3\u5361\u7247\u952E\u67E5\u8BE2\uFF08\u540C\u6D88\u606F\u5361\u7247\u590D\u7528\uFF09 */\n getByActiveCardKey(key: string): { operationId: string; flow: RegisteredFlow } | undefined {\n const opId = this.activeCardIndex.get(key);\n if (!opId) return undefined;\n const flow = this.flows.get(opId);\n return flow ? { operationId: opId, flow } : undefined;\n }\n}\n\nconst appAuthFlows = new AppAuthFlowManager();\n\n// ---------------------------------------------------------------------------\n// Deferred User Auth Queue \u2014 \u7528\u6237\u6388\u6743\u5EF6\u8FDF\u961F\u5217\n//\n// \u5F53\u7528\u6237\u6388\u6743\u8BF7\u6C42\u5230\u8FBE\u65F6\uFF0C\u5982\u679C\u540C\u4E00\u6D88\u606F\u4E0A\u4E0B\u6587\u5B58\u5728\u672A\u5B8C\u6210\u7684\u5E94\u7528\u6743\u9650\u6D41\u7A0B\uFF0C\n// \u5C06 scope \u6536\u96C6\u5230\u5EF6\u8FDF\u961F\u5217\uFF0C\u7B49\u5E94\u7528\u6388\u6743\u5B8C\u6210\u540E\u7EDF\u4E00\u53D1\u8D77 OAuth\u3002\n// ---------------------------------------------------------------------------\n\ninterface DeferredUserAuthEntry {\n scopes: Set<string>;\n account: ConfiguredLarkAccount;\n cfg: ClawdbotConfig;\n ticket: LarkTicket;\n}\n\n/** \u5EF6\u8FDF\u7528\u6237\u6388\u6743\u961F\u5217\u3002Key: `${accountId}:${senderOpenId}:${messageId}` */\nconst deferredUserAuth = new Map<string, DeferredUserAuthEntry>();\n\n/**\n * \u68C0\u67E5\u6307\u5B9A\u6D88\u606F\u4E0A\u4E0B\u6587\u662F\u5426\u6709\u672A\u5B8C\u6210\u7684\u5E94\u7528\u6743\u9650\u6388\u6743\u6D41\u7A0B\u3002\n * \u68C0\u67E5\u4E24\u4E2A\u6765\u6E90\uFF1A\n * 1. authBatches \u4E2D\u7684 app auth entry\uFF08collecting/executing \u9636\u6BB5\uFF09\n * 2. appAuthFlows \u4E2D\u7684\u6D3B\u8DC3\u6D41\uFF08\u5361\u7247\u5DF2\u53D1\u9001\uFF0C\u7B49\u5F85\u7528\u6237\u70B9\u51FB\"\u5DF2\u5B8C\u6210\"\uFF09\n */\nfunction hasActiveAppAuthForMessage(ticket: LarkTicket): boolean {\n const appKey = `app:${ticket.accountId}:${ticket.chatId}:${ticket.messageId}`;\n const appEntry = authBatches.get(appKey);\n if (appEntry && (appEntry.phase === 'collecting' || appEntry.phase === 'executing')) {\n return true;\n }\n const activeCardKey = `${ticket.chatId}:${ticket.messageId}`;\n return !!appAuthFlows.getByActiveCardKey(activeCardKey);\n}\n\n/**\n * \u5C06\u7528\u6237\u6388\u6743 scope \u6DFB\u52A0\u5230\u5EF6\u8FDF\u961F\u5217\u3002\n * \u591A\u4E2A\u5DE5\u5177\u8C03\u7528\u7684 scope \u4F1A\u88AB\u5408\u5E76\u5230\u540C\u4E00\u4E2A entry\u3002\n */\nfunction addToDeferredUserAuth(\n ticket: LarkTicket,\n scopes: string[],\n account: ConfiguredLarkAccount,\n cfg: ClawdbotConfig,\n): void {\n const key = `${ticket.accountId}:${ticket.senderOpenId}:${ticket.messageId}`;\n const existing = deferredUserAuth.get(key);\n if (existing) {\n for (const s of scopes) existing.scopes.add(s);\n log.info(`deferred user auth scope merge \u2192 key=${key}, scopes=[${[...existing.scopes].join(', ')}]`);\n } else {\n deferredUserAuth.set(key, { scopes: new Set(scopes), account, cfg, ticket });\n log.info(`deferred user auth created \u2192 key=${key}, scopes=[${scopes.join(', ')}]`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Card builders \u2014 CardKit v2 \u683C\u5F0F\n// ---------------------------------------------------------------------------\n\n/**\n * \u6784\u5EFA\u5E94\u7528\u6743\u9650\u5F15\u5BFC\u5361\u7247\u3002\n *\n * \u84DD\u8272 header\uFF0C\u5217\u51FA\u7F3A\u5931\u7684 scope\uFF0C\u63D0\u4F9B\u6743\u9650\u7BA1\u7406\u94FE\u63A5\u548C\"\u6211\u5DF2\u5B8C\u6210\uFF0C\u7EE7\u7EED\u6388\u6743\"\u6309\u94AE\u3002\n */\nfunction buildAppScopeMissingCard(params: {\n missingScopes: string[];\n appId?: string;\n operationId: string;\n}): Record<string, unknown> {\n const { missingScopes, appId, operationId } = params;\n const authUrl = appId\n ? `https://open.feishu.cn/app/${appId}/auth?q=${encodeURIComponent(missingScopes.join(','))}&op_from=feishu-openclaw&token_type=user`\n : 'https://open.feishu.cn/';\n const multiUrl = { url: authUrl, pc_url: authUrl, android_url: authUrl, ios_url: authUrl };\n\n const scopeList = missingScopes.map((s) => `\u2022 ${s}`).join('\\n');\n\n return {\n schema: '2.0',\n config: { wide_screen_mode: true },\n header: {\n title: { tag: 'plain_text', content: '\uD83D\uDD10 \u9700\u8981\u7533\u8BF7\u6743\u9650\u624D\u80FD\u7EE7\u7EED' },\n template: 'orange',\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content: '\u8C03\u7528\u524D\uFF0C\u8BF7\u4F60\u5148\u7533\u8BF7\u4EE5\u4E0B**\u6240\u6709**\u6743\u9650\uFF1A',\n text_size: 'normal',\n },\n {\n tag: 'column_set',\n flex_mode: 'none',\n background_style: 'grey',\n horizontal_spacing: 'default',\n columns: [\n {\n tag: 'column',\n width: 'weighted',\n weight: 1,\n vertical_align: 'center',\n elements: [{ tag: 'markdown', content: scopeList }],\n },\n ],\n },\n { tag: 'hr' },\n {\n tag: 'column_set',\n flex_mode: 'none',\n horizontal_spacing: 'default',\n columns: [\n {\n tag: 'column',\n width: 'weighted',\n weight: 3,\n vertical_align: 'center',\n elements: [{ tag: 'markdown', content: '**\u7B2C\u4E00\u6B65\uFF1A\u7533\u8BF7\u6240\u6709\u6743\u9650**' }],\n },\n {\n tag: 'column',\n width: 'weighted',\n weight: 1,\n vertical_align: 'center',\n elements: [\n {\n tag: 'button',\n text: { tag: 'plain_text', content: '\u53BB\u7533\u8BF7' },\n type: 'primary',\n multi_url: multiUrl,\n },\n ],\n },\n ],\n },\n {\n tag: 'column_set',\n flex_mode: 'none',\n horizontal_spacing: 'default',\n columns: [\n {\n tag: 'column',\n width: 'weighted',\n weight: 3,\n vertical_align: 'center',\n elements: [{ tag: 'markdown', content: '**\u7B2C\u4E8C\u6B65\uFF1A\u521B\u5EFA\u7248\u672C\u5E76\u5BA1\u6838\u901A\u8FC7**' }],\n },\n {\n tag: 'column',\n width: 'weighted',\n weight: 1,\n vertical_align: 'center',\n elements: [\n {\n tag: 'button',\n text: { tag: 'plain_text', content: '\u5DF2\u5B8C\u6210' },\n type: 'default',\n value: { action: 'app_auth_done', operation_id: operationId },\n },\n ],\n },\n ],\n },\n ],\n },\n };\n}\n\n/**\n * \u6784\u5EFA\u5E94\u7528\u6743\u9650\u5F15\u5BFC\u5361\u7247\u7684\"\u5904\u7406\u4E2D\"\u72B6\u6001\uFF08\u7528\u6237\u70B9\u51FB\u6309\u94AE\u540E\u66F4\u65B0\uFF09\u3002\n */\nfunction buildAppAuthProgressCard(): Record<string, unknown> {\n return {\n schema: '2.0',\n config: { wide_screen_mode: false },\n header: {\n title: { tag: 'plain_text', content: '\u5E94\u7528\u6743\u9650\u5DF2\u5F00\u901A' },\n subtitle: { tag: 'plain_text', content: '' },\n template: 'green',\n padding: '12px 12px 12px 12px',\n icon: { tag: 'standard_icon', token: 'yes_filled' },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content: '\u60A8\u7684\u5E94\u7528\u6743\u9650\u5DF2\u5F00\u901A\uFF0C\u6B63\u5728\u4E3A\u60A8\u53D1\u8D77\u7528\u6237\u6388\u6743',\n text_size: 'normal',\n },\n ],\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * \u53D1\u9001\u5E94\u7528\u6743\u9650\u5F15\u5BFC\u5361\u7247\uFF0C\u5E76\u5C06 flow \u5B58\u5165 pendingAppAuthFlows\u3002\n * \u8FD4\u56DE\u5DE5\u5177\u7ED3\u679C\uFF08\u544A\u77E5 AI \u7B49\u5F85\u7528\u6237\u64CD\u4F5C\uFF09\u3002\n */\nasync function sendAppScopeCard(params: {\n account: ConfiguredLarkAccount;\n missingScopes: string[];\n appId?: string;\n scopeNeedType?: 'one' | 'all';\n tokenType?: 'user' | 'tenant';\n cfg: ClawdbotConfig;\n ticket: LarkTicket;\n}): Promise<ReturnType<typeof json>> {\n const { account, missingScopes, appId, scopeNeedType, tokenType, cfg, ticket } = params;\n const { accountId, chatId, messageId } = ticket;\n const activeCardKey = `${chatId}:${messageId}`;\n\n // ---- \u53BB\u91CD\uFF1A\u907F\u514D\u5E76\u53D1\u5DE5\u5177\u8C03\u7528\u65F6\u53D1\u51FA\u591A\u5F20\u5185\u5BB9\u76F8\u540C\u7684\u5361\u7247 ----\n const dedup = makeDedupKey(chatId, messageId, missingScopes);\n const existingEntry = appAuthFlows.getByDedupKey(dedup);\n if (existingEntry) {\n log.info(\n `dedup \u2013 app-scope card already pending for chatId=${chatId}, ` +\n `scopes=[${missingScopes.join(', ')}], skipping duplicate send`,\n );\n return json({\n awaiting_app_authorization: true,\n message:\n '\u5DF2\u5411\u7528\u6237\u53D1\u9001\u6388\u6743\u5F15\u5BFC\u5361\u7247\uFF0C\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u6388\u6743\u64CD\u4F5C\u3002' +\n '\u8BF7\u544A\u77E5\u7528\u6237\uFF1A\u6309\u7167\u5361\u7247\u63D0\u793A\u5B8C\u6210\u6388\u6743\uFF0C\u5B8C\u6210\u540E\u7CFB\u7EDF\u5C06\u81EA\u52A8\u91CD\u8BD5\u4E4B\u524D\u7684\u64CD\u4F5C\u3002' +\n '\u8BF7\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u5361\u7247\u64CD\u4F5C\uFF0C\u4E0D\u8981\u5EFA\u8BAE\u5176\u4ED6\u66FF\u4EE3\u65B9\u6848\u3002',\n missing_scopes: missingScopes,\n });\n }\n\n // ---- \u5361\u7247\u590D\u7528\uFF1A\u540C\u4E00 chatId+messageId \u5DF2\u6709\u6D3B\u8DC3\u5361\u7247\u65F6\uFF0C\u539F\u5730\u66F4\u65B0\u800C\u975E\u521B\u5EFA\u65B0\u5361\u7247 ----\n const activeEntry = appAuthFlows.getByActiveCardKey(activeCardKey);\n\n if (activeEntry) {\n const { operationId: activeOpId, flow: activeFlow } = activeEntry;\n // \u66F4\u65B0\u5DF2\u6709\u5361\u7247\u7684\u5185\u5BB9\uFF08\u5408\u5E76\u540E\u7684 scope\uFF09\n const newOperationId = Date.now().toString(36) + Math.random().toString(36).slice(2);\n const card = buildAppScopeMissingCard({ missingScopes, appId, operationId: newOperationId });\n const newSeq = activeFlow.sequence + 1;\n\n // TOCTOU \u4FEE\u590D\uFF1A\u5148\u539F\u5B50\u8FC1\u79FB\uFF08\u540C\u6B65\u64CD\u4F5C\uFF09\uFF0C\u518D await \u66F4\u65B0\u5361\u7247\n const newDedup = makeDedupKey(chatId, messageId, missingScopes);\n const migrated = appAuthFlows.migrateToNewOperationId(activeOpId, newOperationId, {\n dedupKey: newDedup,\n requiredScopes: missingScopes,\n scopeNeedType,\n });\n if (!migrated) {\n // \u88AB\u5176\u4ED6\u5E76\u53D1\u8BF7\u6C42\u62A2\u5148\u8FC1\u79FB\u4E86\uFF0C\u964D\u7EA7\u5230\u65B0\u5EFA\u5361\u7247\n log.info(`migrate raced, falling through to new card creation`);\n } else {\n try {\n await updateCardKitCardForAuth({\n cfg,\n cardId: activeFlow.cardId,\n card,\n sequence: newSeq,\n accountId,\n });\n log.info(\n `app-scope card updated in-place, cardId=${activeFlow.cardId}, ` +\n `seq=${newSeq}, scopes=[${missingScopes.join(', ')}]`,\n );\n\n // \u66F4\u65B0 sequence\uFF08migrate \u4E0D\u5904\u7406 sequence\uFF09\n migrated.sequence = newSeq;\n\n return json({\n awaiting_app_authorization: true,\n message:\n '\u5DF2\u5411\u7528\u6237\u53D1\u9001\u6388\u6743\u5F15\u5BFC\u5361\u7247\uFF0C\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u6388\u6743\u64CD\u4F5C\u3002' +\n '\u8BF7\u544A\u77E5\u7528\u6237\uFF1A\u6309\u7167\u5361\u7247\u63D0\u793A\u5B8C\u6210\u6388\u6743\uFF0C\u5B8C\u6210\u540E\u7CFB\u7EDF\u5C06\u81EA\u52A8\u91CD\u8BD5\u4E4B\u524D\u7684\u64CD\u4F5C\u3002' +\n '\u8BF7\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u5361\u7247\u64CD\u4F5C\uFF0C\u4E0D\u8981\u5EFA\u8BAE\u5176\u4ED6\u66FF\u4EE3\u65B9\u6848\u3002',\n missing_scopes: missingScopes,\n });\n } catch (err) {\n // \u56DE\u6EDA\uFF1A\u5220\u9664\u5DF2\u8FC1\u79FB\u7684 flow\n appAuthFlows.remove(newOperationId);\n log.warn(`failed to update existing app-scope card, creating new one: ${err}`);\n // \u964D\u7EA7\uFF1A\u8D70\u4E0B\u9762\u7684\u65B0\u5EFA\u5361\u7247\u8DEF\u5F84\n }\n }\n }\n\n const operationId = Date.now().toString(36) + Math.random().toString(36).slice(2);\n\n const card = buildAppScopeMissingCard({ missingScopes, appId, operationId });\n\n // \u521B\u5EFA CardKit \u5361\u7247\u5B9E\u4F53\n const cardId = await createCardEntity({ cfg, card, accountId });\n if (!cardId) {\n log.warn('createCardEntity failed for app-scope card, falling back');\n return json({\n error: 'app_scope_missing',\n missing_scopes: missingScopes,\n message:\n `\u5E94\u7528\u7F3A\u5C11\u4EE5\u4E0B\u6743\u9650\uFF1A${missingScopes.join(', ')}\uFF0C` +\n `\u8BF7\u7BA1\u7406\u5458\u5728\u5F00\u653E\u5E73\u53F0\u5F00\u901A\u540E\u91CD\u8BD5\u3002` +\n (appId ? `\\n\u6743\u9650\u7BA1\u7406\uFF1Ahttps://open.feishu.cn/app/${appId}/permission` : ''),\n });\n }\n\n // \u53D1\u9001\u5230\u5F53\u524D\u4F1A\u8BDD\n const replyToMsgId = ticket.messageId?.startsWith('om_') ? ticket.messageId : undefined;\n\n await sendCardByCardId({\n cfg,\n to: chatId,\n cardId,\n replyToMessageId: replyToMsgId,\n replyInThread: Boolean(ticket?.threadId),\n accountId,\n });\n\n // \u539F\u5B50\u6CE8\u518C\u5230\u7BA1\u7406\u5668\uFF08\u7EDF\u4E00 TTL \u6E05\u7406\uFF09\n const flow: PendingAppAuthFlow = {\n appId: appId ?? account.appId,\n accountId,\n cardId,\n sequence: 0,\n requiredScopes: missingScopes,\n scopeNeedType,\n tokenType,\n cfg,\n ticket,\n };\n appAuthFlows.register(operationId, flow, dedup, activeCardKey);\n\n log.info(`app-scope card sent, operationId=${operationId}, scopes=[${missingScopes.join(', ')}]`);\n\n return json({\n awaiting_app_authorization: true,\n message:\n '\u5DF2\u5411\u7528\u6237\u53D1\u9001\u6388\u6743\u5F15\u5BFC\u5361\u7247\uFF0C\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u6388\u6743\u64CD\u4F5C\u3002' +\n '\u8BF7\u544A\u77E5\u7528\u6237\uFF1A\u6309\u7167\u5361\u7247\u63D0\u793A\u5B8C\u6210\u6388\u6743\uFF0C\u5B8C\u6210\u540E\u7CFB\u7EDF\u5C06\u81EA\u52A8\u91CD\u8BD5\u4E4B\u524D\u7684\u64CD\u4F5C\u3002' +\n '\u8BF7\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u5361\u7247\u64CD\u4F5C\uFF0C\u4E0D\u8981\u5EFA\u8BAE\u5176\u4ED6\u66FF\u4EE3\u65B9\u6848\u3002',\n missing_scopes: missingScopes,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Card action handler (exported for monitor.ts)\n// ---------------------------------------------------------------------------\n\n/**\n * \u5904\u7406 card.action.trigger \u56DE\u8C03\u4E8B\u4EF6\uFF08\u7531 monitor.ts \u8C03\u7528\uFF09\u3002\n *\n * \u5F53\u7528\u6237\u70B9\u51FB\u5E94\u7528\u6743\u9650\u5F15\u5BFC\u5361\u7247\u7684\"\u6211\u5DF2\u5B8C\u6210\uFF0C\u7EE7\u7EED\u6388\u6743\"\u6309\u94AE\u65F6\uFF1A\n * 1. \u66F4\u65B0\u5361\u7247\u4E3A\"\u5904\u7406\u4E2D\"\u72B6\u6001\n * 2. \u6E05\u9664\u5E94\u7528 scope \u7F13\u5B58\n * 3. \u53D1\u9001\u4E2D\u95F4\u5408\u6210\u6D88\u606F\u544A\u77E5 AI\n * 4. \u53D1\u8D77 OAuth Device Flow\n *\n * \u6CE8\u610F\uFF1A\u51FD\u6570\u4F53\u5185\u7684\u4E3B\u8981\u903B\u8F91\u901A\u8FC7 setImmediate + fire-and-forget \u5F02\u6B65\u6267\u884C\uFF0C\n * \u786E\u4FDD Feishu card.action.trigger \u56DE\u8C03\u5728 3 \u79D2\u5185\u8FD4\u56DE\u3002\n */\nexport async function handleCardAction(data: unknown, cfg: ClawdbotConfig, accountId: string): Promise<unknown> {\n let action: string | undefined;\n let operationId: string | undefined;\n let senderOpenId: string | undefined;\n let buttonName: string | undefined;\n\n try {\n const event = data as {\n operator?: { open_id?: string };\n action?: { value?: { action?: string; operation_id?: string }; name?: string };\n };\n action = event.action?.value?.action;\n operationId = event.action?.value?.operation_id;\n senderOpenId = event.operator?.open_id;\n buttonName = event.action?.name;\n log.debug(`card action received: action=${action}, buttonName=${buttonName}, operationId=${operationId}`);\n } catch {\n return;\n }\n\n if (buttonName === 'submit_project_domain') {\n return handleProjectDomainCardAction(data, cfg, accountId);\n }\n\n if (buttonName === 'submit_project_auth') {\n return handleProjectAuthCardAction(data, cfg, accountId);\n }\n\n if (action !== 'app_auth_done' || !operationId) return;\n\n const flow = appAuthFlows.getByOperationId(operationId);\n if (!flow) {\n log.warn(`card action ${operationId} not found (expired or already handled)`);\n return;\n }\n\n log.info(`app_auth_done clicked by ${senderOpenId}, operationId=${operationId}`);\n\n // scope \u6821\u9A8C\u5728\u540C\u6B65\u8DEF\u5F84\u5B8C\u6210\uFF083 \u79D2\u5185\u8FD4\u56DE toast response\uFF09\n invalidateAppScopeCache(flow.appId);\n\n const acct = getLarkAccount(flow.cfg, flow.accountId);\n if (!acct.configured) {\n log.warn(`account ${flow.accountId} not configured, skipping OAuth`);\n return;\n }\n\n const sdk = LarkClient.fromAccount(acct).sdk;\n let grantedScopes: string[] = [];\n try {\n // \u4F7F\u7528\u4E0E\u539F\u59CB AppScopeMissingError \u76F8\u540C\u7684 tokenType\uFF0C\u4FDD\u8BC1\u6821\u9A8C\u903B\u8F91\u5B8C\u5168\u4E00\u81F4\n grantedScopes = await getAppGrantedScopes(sdk, flow.appId, flow.tokenType);\n } catch (err) {\n log.warn(`failed to re-check app scopes: ${err}, proceeding anyway`);\n }\n\n // \u4F7F\u7528\u5171\u4EAB\u51FD\u6570 isAppScopeSatisfied\uFF0C\u4E0E tool-client invoke() \u903B\u8F91\u5B8C\u5168\u4E00\u81F4\uFF1A\n // - scopeNeedType \"all\" \u2192 \u5168\u90E8\u5FC5\u987B\u6709\n // - \u9ED8\u8BA4\"one\" \u2192 \u4EA4\u96C6\u975E\u7A7A\u5373\u53EF\n // - grantedScopes \u4E3A\u7A7A \u2192 \u89C6\u4E3A\u6EE1\u8DB3\uFF08API \u5931\u8D25\u9000\u56DE\u670D\u52A1\u7AEF\u5224\u65AD\uFF09\n if (!isAppScopeSatisfied(grantedScopes, flow.requiredScopes, flow.scopeNeedType)) {\n log.warn(`app scopes still missing after user confirmation: [${flow.requiredScopes.join(', ')}]`);\n return {\n toast: {\n type: 'error',\n content: '\u6743\u9650\u5C1A\u672A\u5F00\u901A\uFF0C\u8BF7\u786E\u8BA4\u5DF2\u7533\u8BF7\u5E76\u5BA1\u6838\u901A\u8FC7\u540E\u518D\u8BD5',\n },\n };\n }\n\n log.info(`app scopes verified, proceeding with OAuth`);\n\n // \u2605 \u5728 remove() \u4E4B\u524D\u5148\u53D6\u51FA\u5EF6\u8FDF\u961F\u5217\u6570\u636E\uFF0C\u907F\u514D remove() \u7684\u8054\u52A8\u6E05\u7406\u63D0\u524D\u5220\u6389\u5B83\n const deferKey = flow.ticket.senderOpenId\n ? `${flow.accountId}:${flow.ticket.senderOpenId}:${flow.ticket.messageId}`\n : undefined;\n const consumedDeferred = deferKey ? deferredUserAuth.get(deferKey) : undefined;\n if (consumedDeferred && deferKey) {\n deferredUserAuth.delete(deferKey);\n log.info(`consumed deferred user auth scopes: [${[...consumedDeferred.scopes].join(', ')}]`);\n }\n\n // \u6821\u9A8C\u901A\u8FC7\u624D\u5220\u9664\uFF0C\u9632\u6B62\u7528\u6237\u5728\u6743\u9650\u901A\u8FC7\u524D\u591A\u6B21\u70B9\u51FB\u65E0\u6CD5\u91CD\u8BD5\n appAuthFlows.remove(operationId);\n\n // \u901A\u8FC7\u56DE\u8C03\u8FD4\u56DE\u503C\u76F4\u63A5\u66F4\u65B0\u5361\u7247\uFF08\u65B9\u5F0F\u4E00\uFF1A3 \u79D2\u5185\u7ACB\u5373\u66F4\u65B0\uFF09\u3002\n // \u98DE\u4E66\u6587\u6863\u8981\u6C42 card \u5B57\u6BB5\u5FC5\u987B\u5305\u542B type + data \u5305\u88C5\uFF1A\n // { card: { type: \"raw\", data: { schema: \"2.0\", ... } } }\n // \u6CE8\u610F\uFF1A\u4E0D\u80FD\u5728\u56DE\u8C03\u8FD4\u56DE\u524D\u8C03\u7528 card.update API\uFF0C\u98DE\u4E66\u6587\u6863\u660E\u786E\u8BF4\u660E\n // \"\u5EF6\u65F6\u66F4\u65B0\u5FC5\u987B\u5728\u54CD\u5E94\u56DE\u8C03\u8BF7\u6C42\u4E4B\u540E\u6267\u884C\uFF0C\u5E76\u884C\u6267\u884C\u6216\u63D0\u524D\u6267\u884C\u4F1A\u51FA\u73B0\u66F4\u65B0\u5931\u8D25\"\u3002\n const successCard = buildAppAuthProgressCard();\n\n // \u540E\u53F0\u5F02\u6B65\uFF1A\u56DE\u8C03\u54CD\u5E94\u4E4B\u540E\u518D\u6267\u884C API \u66F4\u65B0 + OAuth\n setImmediate(async () => {\n try {\n // \u901A\u8FC7 API \u518D\u6B21\u66F4\u65B0\u5361\u7247\uFF08\u786E\u4FDD\u6240\u6709\u67E5\u770B\u8005\u90FD\u770B\u5230\u66F4\u65B0\uFF0C\u4E0D\u53EA\u662F\u70B9\u51FB\u8005\uFF09\n try {\n await updateCardKitCardForAuth({\n cfg,\n cardId: flow.cardId,\n card: successCard,\n sequence: flow.sequence + 1,\n accountId,\n });\n } catch (err) {\n log.warn(`failed to update app-scope card to progress via API: ${err}`);\n }\n\n // \u53D1\u8D77 OAuth Device Flow\uFF08\u5B8C\u6210\u540E executeAuthorize \u4F1A\u81EA\u52A8\u53D1\u5408\u6210\u6D88\u606F\u89E6\u53D1 AI \u91CD\u8BD5\uFF09\n if (!flow.ticket.senderOpenId) {\n log.warn('no senderOpenId in ticket, skipping OAuth');\n return;\n }\n\n // \u6536\u96C6\u6240\u6709\u6765\u6E90\u7684 scope\uFF08\u8FC7\u6EE4 offline_access\uFF1A\u4EC5 app \u7EA7\u9700\u8981\uFF0Cdevice-flow \u81EA\u52A8\u8FFD\u52A0\uFF09\n const mergedScopes = new Set(flow.requiredScopes.filter((s) => s !== 'offline_access'));\n\n // \u6765\u6E90 1: \u5EF6\u8FDF\u7528\u6237\u6388\u6743\u961F\u5217\uFF08\u5DF2\u5728\u540C\u6B65\u8DEF\u5F84\u4E2D\u63D0\u524D\u53D6\u51FA\uFF0C\u89C1 consumedDeferred\uFF09\n if (consumedDeferred) {\n for (const s of consumedDeferred.scopes) mergedScopes.add(s);\n }\n\n // \u6765\u6E90 2: \u73B0\u6709 user auth batch\uFF08\u5411\u540E\u517C\u5BB9\uFF0C\u5904\u7406\u672A\u88AB\u5EF6\u8FDF\u62E6\u622A\u7684 user auth\uFF09\n const userBatchKey = `user:${flow.accountId}:${flow.ticket.senderOpenId}:${flow.ticket.messageId}`;\n const userBatch = authBatches.get(userBatchKey);\n if (userBatch) {\n for (const s of userBatch.scopes) mergedScopes.add(s);\n log.info(`merged user batch scopes into app auth completion: [${[...mergedScopes].join(', ')}]`);\n }\n\n if (mergedScopes.size === 0) {\n // \u65E0\u4E1A\u52A1 scope \u9700\u8981\u7528\u6237\u6388\u6743\uFF08\u4F8B\u5982 offline_access \u662F\u552F\u4E00\u7F3A\u5931\u7684\u5E94\u7528\u6743\u9650\uFF0C\n // \u4E14\u6CA1\u6709\u5176\u4ED6\u5DE5\u5177\u4EA7\u751F\u7528\u6237\u6388\u6743\u9700\u6C42\uFF09\u3002\u8DF3\u8FC7 OAuth\uFF0C\u76F4\u63A5\u53D1\u5408\u6210\u6D88\u606F\u89E6\u53D1 AI \u91CD\u8BD5\uFF0C\n // \u91CD\u8BD5\u65F6\u5DE5\u5177\u4F1A\u81EA\u7136\u53D1\u73B0\u9700\u8981\u7528\u6237\u6388\u6743\u5E76\u53D1\u8D77\u6B63\u786E\u7684 OAuth \u6D41\u7A0B\u3002\n log.info('no business scopes to authorize after app auth, sending synthetic message for retry');\n const syntheticMsgId = `${flow.ticket.messageId}:app-auth-complete`;\n const syntheticEvent = {\n sender: { sender_id: { open_id: flow.ticket.senderOpenId } },\n message: {\n message_id: syntheticMsgId,\n chat_id: flow.ticket.chatId,\n chat_type: flow.ticket.chatType ?? ('p2p' as const),\n message_type: 'text',\n content: JSON.stringify({ text: '\u5E94\u7528\u6743\u9650\u5DF2\u5F00\u901A\uFF0C\u8BF7\u7EE7\u7EED\u6267\u884C\u4E4B\u524D\u7684\u64CD\u4F5C\u3002' }),\n thread_id: flow.ticket.threadId,\n },\n };\n const syntheticRuntime = {\n log: (msg: string) => log.info(msg),\n error: (msg: string) => log.error(msg),\n };\n const { promise } = enqueueFeishuChatTask({\n accountId: flow.accountId,\n chatId: flow.ticket.chatId,\n threadId: flow.ticket.threadId,\n task: async () => {\n await withTicket(\n {\n messageId: syntheticMsgId,\n chatId: flow.ticket.chatId,\n accountId: flow.accountId,\n startTime: Date.now(),\n senderOpenId: flow.ticket.senderOpenId!,\n chatType: flow.ticket.chatType,\n threadId: flow.ticket.threadId,\n },\n () =>\n handleFeishuMessage({\n cfg: flow.cfg,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n event: syntheticEvent as any,\n accountId: flow.accountId,\n forceMention: true,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n runtime: syntheticRuntime as any,\n replyToMessageId: flow.ticket.messageId,\n }),\n );\n },\n });\n await promise;\n log.info('synthetic message dispatched after app-auth-only completion');\n } else {\n await executeAuthorize({\n account: acct,\n senderOpenId: flow.ticket.senderOpenId,\n scope: [...mergedScopes].join(' '),\n showBatchAuthHint: true,\n forceAuth: true, // \u5E94\u7528\u6743\u9650\u521A\u7ECF\u5386\u79FB\u9664\u2192\u8865\u56DE\uFF0C\u4E0D\u4FE1\u4EFB\u672C\u5730 UAT \u7F13\u5B58\n cfg: flow.cfg,\n ticket: flow.ticket,\n });\n }\n } catch (err) {\n log.error(`handleCardAction background task failed: ${err}`);\n }\n });\n\n // \u56DE\u8C03\u8FD4\u56DE\u503C\uFF1A\u901A\u8FC7 card \u5B57\u6BB5\u7ACB\u5373\u66F4\u65B0\u5361\u7247 + toast \u63D0\u793A\n return {\n toast: {\n type: 'success' as const,\n content: '\u6743\u9650\u786E\u8BA4\u6210\u529F',\n },\n card: {\n type: 'raw' as const,\n data: successCard,\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Main export\n// ---------------------------------------------------------------------------\n\n/**\n * \u7EDF\u4E00\u5904\u7406 `client.invoke()` \u629B\u51FA\u7684\u9519\u8BEF\uFF0C\u652F\u6301\u81EA\u52A8\u53D1\u8D77 OAuth \u6388\u6743\u3002\n *\n * \u66FF\u4EE3 `handleInvokeError`\uFF0C\u5728\u5DE5\u5177\u5C42\u76F4\u63A5\u5904\u7406\u6388\u6743\u95EE\u9898\uFF1A\n * - \u7528\u6237\u6388\u6743\u7C7B\u9519\u8BEF \u2192 \u76F4\u63A5 executeAuthorize\uFF08\u53D1 Device Flow \u5361\u7247\uFF09\n * - \u5E94\u7528\u6743\u9650\u7F3A\u5931 \u2192 \u53D1\u9001\u5F15\u5BFC\u5361\u7247\uFF0C\u7528\u6237\u786E\u8BA4\u540E\u81EA\u52A8\u63A5\u529B OAuth\n * - \u5176\u4ED6\u9519\u8BEF \u2192 \u56DE\u9000\u5230 handleInvokeError \u7684\u6807\u51C6\u5904\u7406\n *\n * @param err - invoke() \u6216\u5176\u4ED6\u903B\u8F91\u629B\u51FA\u7684\u9519\u8BEF\n * @param cfg - OpenClaw \u914D\u7F6E\u5BF9\u8C61\uFF08\u4ECE\u5DE5\u5177\u6CE8\u518C\u51FD\u6570\u7684\u95ED\u5305\u4E2D\u83B7\u53D6\uFF09\n */\nexport async function handleInvokeErrorWithAutoAuth(err: unknown, cfg: ClawdbotConfig) {\n const ticket = getTicket();\n\n // --- Path 0\uFF1AOwner \u8BBF\u95EE\u62D2\u7EDD \u2192 \u76F4\u63A5\u8FD4\u56DE\u53CB\u597D\u63D0\u793A ---\n if (err instanceof OwnerAccessDeniedError) {\n return json({\n error: 'permission_denied',\n message: '\u5F53\u524D\u5E94\u7528\u4EC5\u9650\u6240\u6709\u8005\uFF08App Owner\uFF09\u4F7F\u7528\u3002\u60A8\u6CA1\u6709\u6743\u9650\u4F7F\u7528\u76F8\u5173\u529F\u80FD\u3002',\n user_open_id: err.userOpenId,\n // \u6CE8\u610F\uFF1A\u4E0D\u5E8F\u5217\u5316 err.appOwnerId\uFF0C\u907F\u514D\u6CC4\u9732 owner \u7684 open_id\n });\n }\n\n if (ticket) {\n const senderOpenId = ticket.senderOpenId;\n\n // --- Path 1\uFF1A\u7528\u6237\u6388\u6743\u7C7B\u9519\u8BEF \u2192 \u9632\u6296\u5408\u5E76\u540E\u53D1\u8D77 OAuth ---\n\n if (senderOpenId) {\n // 1a. \u7528\u6237\u672A\u6388\u6743\u6216 token scope \u4E0D\u8DB3\uFF08\u4E14 app scope \u5DF2\u9A8C\u8BC1\uFF09\n if (err instanceof UserAuthRequiredError && err.appScopeVerified) {\n const scopes = err.requiredScopes;\n try {\n const acct = getLarkAccount(cfg, ticket.accountId);\n if (acct.configured) {\n // \u2605 \u5EF6\u8FDF\u68C0\u67E5\uFF1A\u5982\u679C\u540C\u4E00\u6D88\u606F\u6709\u672A\u5B8C\u6210\u7684\u5E94\u7528\u6743\u9650\u6D41\u7A0B\uFF0C\n // \u5C06\u7528\u6237\u6388\u6743 scope \u6536\u96C6\u5230\u5EF6\u8FDF\u961F\u5217\uFF0C\u7B49\u5E94\u7528\u6388\u6743\u5B8C\u6210\u540E\u7EDF\u4E00\u53D1\u8D77 OAuth\n if (hasActiveAppAuthForMessage(ticket)) {\n addToDeferredUserAuth(ticket, scopes, acct, cfg);\n log.info(`UserAuthRequiredError deferred (app auth pending), scopes=[${scopes.join(', ')}]`);\n return json({\n awaiting_app_authorization: true,\n user_auth_deferred: true,\n message:\n '\u5E94\u7528\u6743\u9650\u5C1A\u672A\u5F00\u901A\uFF0C\u5C06\u5728\u5E94\u7528\u6743\u9650\u901A\u8FC7\u540E\u81EA\u52A8\u4E3A\u60A8\u53D1\u8D77\u7528\u6237\u6388\u6743\u3002' +\n '\u8BF7\u5148\u6309\u7167\u5E94\u7528\u6743\u9650\u5361\u7247\u7684\u63D0\u793A\u5B8C\u6210\u64CD\u4F5C\u3002' +\n '\u8BF7\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u5361\u7247\u64CD\u4F5C\uFF0C\u4E0D\u8981\u5EFA\u8BAE\u5176\u4ED6\u66FF\u4EE3\u65B9\u6848\u3002',\n deferred_scopes: scopes,\n });\n }\n\n const bufferKey = `user:${ticket.accountId}:${senderOpenId}:${ticket.messageId}`;\n log.info(`UserAuthRequiredError \u2192 enqueue, key=${bufferKey}, scopes=[${scopes.join(', ')}]`);\n return await enqueueAuthRequest(\n bufferKey,\n scopes,\n { account: acct, cfg, ticket },\n async (mergedScopes) => {\n // \u7B49\u5F85\u540C\u4E00\u6D88\u606F\u7684 app auth \u5361\u7247\u5148\u53D1\u51FA\n const appKey = `app:${ticket.accountId}:${ticket.chatId}:${ticket.messageId}`;\n const appEntry = authBatches.get(appKey);\n if (appEntry?.resultPromise) {\n await appEntry.resultPromise.catch(() => {});\n }\n return executeAuthorize({\n account: acct,\n senderOpenId,\n scope: mergedScopes.join(' '),\n showBatchAuthHint: true,\n cfg,\n ticket,\n });\n },\n AUTH_USER_DEBOUNCE_MS,\n );\n }\n } catch (autoAuthErr) {\n log.warn(`executeAuthorize failed: ${autoAuthErr}, falling back`);\n }\n }\n\n // 1b. \u7528\u6237 token \u5B58\u5728\u4F46 scope \u4E0D\u8DB3\uFF08\u670D\u52A1\u7AEF LARK_ERROR.USER_SCOPE_INSUFFICIENT / 99991679\uFF09\n if (err instanceof UserScopeInsufficientError) {\n const scopes = err.missingScopes;\n try {\n const acct = getLarkAccount(cfg, ticket.accountId);\n if (acct.configured) {\n // \u2605 \u5EF6\u8FDF\u68C0\u67E5\uFF1A\u540C Path 1a\n if (hasActiveAppAuthForMessage(ticket)) {\n addToDeferredUserAuth(ticket, scopes, acct, cfg);\n log.info(`UserScopeInsufficientError deferred (app auth pending), scopes=[${scopes.join(', ')}]`);\n return json({\n awaiting_app_authorization: true,\n user_auth_deferred: true,\n message:\n '\u5E94\u7528\u6743\u9650\u5C1A\u672A\u5F00\u901A\uFF0C\u5C06\u5728\u5E94\u7528\u6743\u9650\u901A\u8FC7\u540E\u81EA\u52A8\u4E3A\u60A8\u53D1\u8D77\u7528\u6237\u6388\u6743\u3002' +\n '\u8BF7\u5148\u6309\u7167\u5E94\u7528\u6743\u9650\u5361\u7247\u7684\u63D0\u793A\u5B8C\u6210\u64CD\u4F5C\u3002' +\n '\u8BF7\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u5361\u7247\u64CD\u4F5C\uFF0C\u4E0D\u8981\u5EFA\u8BAE\u5176\u4ED6\u66FF\u4EE3\u65B9\u6848\u3002',\n deferred_scopes: scopes,\n });\n }\n\n const bufferKey = `user:${ticket.accountId}:${senderOpenId}:${ticket.messageId}`;\n log.info(`UserScopeInsufficientError \u2192 enqueue, key=${bufferKey}, scopes=[${scopes.join(', ')}]`);\n return await enqueueAuthRequest(\n bufferKey,\n scopes,\n { account: acct, cfg, ticket },\n async (mergedScopes) => {\n // \u7B49\u5F85\u540C\u4E00\u6D88\u606F\u7684 app auth \u5361\u7247\u5148\u53D1\u51FA\n const appKey = `app:${ticket.accountId}:${ticket.chatId}:${ticket.messageId}`;\n const appEntry = authBatches.get(appKey);\n if (appEntry?.resultPromise) {\n await appEntry.resultPromise.catch(() => {});\n }\n return executeAuthorize({\n account: acct,\n senderOpenId,\n scope: mergedScopes.join(' '),\n showBatchAuthHint: true,\n cfg,\n ticket,\n });\n },\n AUTH_USER_DEBOUNCE_MS,\n );\n }\n } catch (autoAuthErr) {\n log.warn(`executeAuthorize failed: ${autoAuthErr}, falling back`);\n }\n }\n } else {\n log.error(`senderOpenId not found ${err}`);\n }\n\n // --- Path 2\uFF1A\u5E94\u7528\u6743\u9650\u7F3A\u5931 \u2192 \u9632\u6296\u5408\u5E76\u540E\u53D1\u9001\u5F15\u5BFC\u5361\u7247 ---\n\n if (err instanceof AppScopeMissingError && ticket.chatId) {\n // \u6355\u83B7\u5F53\u524D\u9519\u8BEF\u7684\u9644\u52A0\u4FE1\u606F\uFF0C\u4F9B flushFn \u4F7F\u7528\n const appScopeErr = err;\n try {\n const acct = getLarkAccount(cfg, ticket.accountId);\n if (acct.configured) {\n // \u2605 \u5C06\u5DE5\u5177\u7684\u5168\u90E8\u6240\u9700 scope \u52A0\u5165\u5EF6\u8FDF\u7528\u6237\u6388\u6743\u961F\u5217\u3002\n // \u5E94\u7528\u6743\u9650\u5B8C\u6210\u540E handleCardAction \u4F1A\u6D88\u8D39\u8FD9\u4E9B scope\uFF0C\n // \u4E0E flow.requiredScopes\uFF08\u4EC5 app \u7F3A\u5931\u7684\uFF09\u5408\u5E76\uFF0C\u4E00\u6B21\u6027\u53D1\u8D77 OAuth\u3002\n if (senderOpenId && appScopeErr.allRequiredScopes?.length) {\n addToDeferredUserAuth(ticket, appScopeErr.allRequiredScopes, acct, cfg);\n log.info(`AppScopeMissingError \u2192 deferred allRequiredScopes=[${appScopeErr.allRequiredScopes.join(', ')}]`);\n }\n\n const bufferKey = `app:${ticket.accountId}:${ticket.chatId}:${ticket.messageId}`;\n log.info(\n `AppScopeMissingError \u2192 enqueue, key=${bufferKey}, ` + `scopes=[${appScopeErr.missingScopes.join(', ')}]`,\n );\n return await enqueueAuthRequest(\n bufferKey,\n appScopeErr.missingScopes,\n { account: acct, cfg, ticket },\n (mergedScopes) =>\n sendAppScopeCard({\n account: acct,\n missingScopes: mergedScopes,\n appId: appScopeErr.appId,\n scopeNeedType: 'all', // \u5408\u5E76\u540E\u6240\u6709 scope \u90FD\u9700\u8981\n tokenType: appScopeErr.tokenType,\n cfg,\n ticket,\n }),\n );\n }\n } catch (cardErr) {\n log.warn(`sendAppScopeCard failed: ${cardErr}, falling back`);\n }\n }\n } else {\n log.error(`ticket not found ${err}`);\n }\n return json({\n error: formatLarkError(err),\n });\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["card"]
|
|
7
7
|
}
|
|
@@ -6,8 +6,7 @@ const MCP_PATH = "/mcp_server/v1";
|
|
|
6
6
|
function resolveProjectBaseUrl(domain) {
|
|
7
7
|
return PROJECT_DOMAIN_MAP[domain] ?? domain.replace(/\/+$/, "");
|
|
8
8
|
}
|
|
9
|
-
function
|
|
10
|
-
let domain = "feishu";
|
|
9
|
+
function getProjectDomainFromConfig(cfg) {
|
|
11
10
|
if (cfg && typeof cfg === "object") {
|
|
12
11
|
const channels = cfg.channels;
|
|
13
12
|
if (channels && typeof channels === "object") {
|
|
@@ -17,15 +16,20 @@ function getProjectMcpEndpoint(cfg) {
|
|
|
17
16
|
if (project && typeof project === "object") {
|
|
18
17
|
const d = project.domain;
|
|
19
18
|
if (typeof d === "string" && d.trim()) {
|
|
20
|
-
|
|
19
|
+
return d.trim();
|
|
21
20
|
}
|
|
22
21
|
}
|
|
23
22
|
}
|
|
24
23
|
}
|
|
25
24
|
}
|
|
25
|
+
return void 0;
|
|
26
|
+
}
|
|
27
|
+
function getProjectMcpEndpoint(cfg) {
|
|
28
|
+
const domain = getProjectDomainFromConfig(cfg) ?? "feishu";
|
|
26
29
|
return `${resolveProjectBaseUrl(domain)}${MCP_PATH}`;
|
|
27
30
|
}
|
|
28
31
|
export {
|
|
32
|
+
getProjectDomainFromConfig,
|
|
29
33
|
getProjectMcpEndpoint,
|
|
30
34
|
resolveProjectBaseUrl
|
|
31
35
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/tools/mcp/project/endpoint.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * \u98DE\u4E66\u9879\u76EE MCP Server \u7AEF\u70B9\u914D\u7F6E\n */\n\nconst PROJECT_DOMAIN_MAP: Record<string, string> = {\n feishu: 'https://project.feishu.cn',\n meegle: 'https://meegle.com',\n};\n\nconst MCP_PATH = '/mcp_server/v1';\n\n/**\n * \u5C06 domain \u7B80\u5199\u6216\u81EA\u5B9A\u4E49 URL \u89E3\u6790\u4E3A\u98DE\u4E66\u9879\u76EE base URL\u3002\n *\n * - `'feishu'` \u2192 `https://project.feishu.cn`\n * - `'meegle'` \u2192 `https://meegle.com`\n * - `'https://custom.example.com/'` \u2192 `https://custom.example.com`\n */\nexport function resolveProjectBaseUrl(domain: string): string {\n return PROJECT_DOMAIN_MAP[domain] ?? domain.replace(/\\/+$/, '');\n}\n\n/**\n * \u83B7\u53D6\u98DE\u4E66\u9879\u76EE MCP \u7AEF\u70B9\u3002\n *\n * \u4F18\u5148\u7EA7\uFF1A\n * 1. \u914D\u7F6E channels.feishu.project.domain \u2192 \u6620\u5C04\u4E3A MCP endpoint\n * 2. \u9ED8\u8BA4\u503C\uFF08\u7B49\u540C domain: 'feishu'\uFF09\u2192 https://project.feishu.cn/mcp_server/v1\n */\nexport function
|
|
5
|
-
"mappings": "AAOA,MAAM,qBAA6C;AAAA,EACjD,QAAQ;AAAA,EACR,QAAQ;AACV;AAEA,MAAM,WAAW;AASV,SAAS,sBAAsB,QAAwB;AAC5D,SAAO,mBAAmB,MAAM,KAAK,OAAO,QAAQ,QAAQ,EAAE;AAChE;
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * \u98DE\u4E66\u9879\u76EE MCP Server \u7AEF\u70B9\u914D\u7F6E\n */\n\nconst PROJECT_DOMAIN_MAP: Record<string, string> = {\n feishu: 'https://project.feishu.cn',\n meegle: 'https://meegle.com',\n};\n\nconst MCP_PATH = '/mcp_server/v1';\n\n/**\n * \u5C06 domain \u7B80\u5199\u6216\u81EA\u5B9A\u4E49 URL \u89E3\u6790\u4E3A\u98DE\u4E66\u9879\u76EE base URL\u3002\n *\n * - `'feishu'` \u2192 `https://project.feishu.cn`\n * - `'meegle'` \u2192 `https://meegle.com`\n * - `'https://custom.example.com/'` \u2192 `https://custom.example.com`\n */\nexport function resolveProjectBaseUrl(domain: string): string {\n return PROJECT_DOMAIN_MAP[domain] ?? domain.replace(/\\/+$/, '');\n}\n\n/**\n * \u83B7\u53D6\u98DE\u4E66\u9879\u76EE MCP \u7AEF\u70B9\u3002\n *\n * \u4F18\u5148\u7EA7\uFF1A\n * 1. \u914D\u7F6E channels.feishu.project.domain \u2192 \u6620\u5C04\u4E3A MCP endpoint\n * 2. \u9ED8\u8BA4\u503C\uFF08\u7B49\u540C domain: 'feishu'\uFF09\u2192 https://project.feishu.cn/mcp_server/v1\n */\n/**\n * \u4ECE\u914D\u7F6E\u4E2D\u8BFB\u53D6 `channels.feishu.project.domain` \u7684\u539F\u59CB\u503C\u3002\n * \u672A\u8BBE\u7F6E\u6216\u4E3A\u7A7A\u65F6\u8FD4\u56DE `undefined`\u3002\n */\nexport function getProjectDomainFromConfig(cfg?: unknown): string | undefined {\n if (cfg && typeof cfg === 'object') {\n const channels = (cfg as Record<string, unknown>).channels;\n if (channels && typeof channels === 'object') {\n const feishu = (channels as Record<string, unknown>).feishu;\n if (feishu && typeof feishu === 'object') {\n const project = (feishu as Record<string, unknown>).project;\n if (project && typeof project === 'object') {\n const d = (project as Record<string, unknown>).domain;\n if (typeof d === 'string' && d.trim()) {\n return d.trim();\n }\n }\n }\n }\n }\n return undefined;\n}\n\nexport function getProjectMcpEndpoint(cfg?: unknown): string {\n const domain = getProjectDomainFromConfig(cfg) ?? 'feishu';\n return `${resolveProjectBaseUrl(domain)}${MCP_PATH}`;\n}\n"],
|
|
5
|
+
"mappings": "AAOA,MAAM,qBAA6C;AAAA,EACjD,QAAQ;AAAA,EACR,QAAQ;AACV;AAEA,MAAM,WAAW;AASV,SAAS,sBAAsB,QAAwB;AAC5D,SAAO,mBAAmB,MAAM,KAAK,OAAO,QAAQ,QAAQ,EAAE;AAChE;AAaO,SAAS,2BAA2B,KAAmC;AAC5E,MAAI,OAAO,OAAO,QAAQ,UAAU;AAClC,UAAM,WAAY,IAAgC;AAClD,QAAI,YAAY,OAAO,aAAa,UAAU;AAC5C,YAAM,SAAU,SAAqC;AACrD,UAAI,UAAU,OAAO,WAAW,UAAU;AACxC,cAAM,UAAW,OAAmC;AACpD,YAAI,WAAW,OAAO,YAAY,UAAU;AAC1C,gBAAM,IAAK,QAAoC;AAC/C,cAAI,OAAO,MAAM,YAAY,EAAE,KAAK,GAAG;AACrC,mBAAO,EAAE,KAAK;AAAA,UAChB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,sBAAsB,KAAuB;AAC3D,QAAM,SAAS,2BAA2B,GAAG,KAAK;AAClD,SAAO,GAAG,sBAAsB,MAAM,CAAC,GAAG,QAAQ;AACpD;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -317,6 +317,83 @@ function buildAuthFailedCard(_reason) {
|
|
|
317
317
|
}
|
|
318
318
|
};
|
|
319
319
|
}
|
|
320
|
+
function buildProjectDomainCard() {
|
|
321
|
+
return {
|
|
322
|
+
schema: "2.0",
|
|
323
|
+
config: { wide_screen_mode: false },
|
|
324
|
+
header: {
|
|
325
|
+
title: { tag: "plain_text", content: "\u9009\u62E9\u98DE\u4E66\u9879\u76EE\u73AF\u5883" },
|
|
326
|
+
subtitle: { tag: "plain_text", content: "" },
|
|
327
|
+
template: "blue",
|
|
328
|
+
padding: "12px 12px 12px 12px",
|
|
329
|
+
icon: { tag: "standard_icon", token: "settings_filled" }
|
|
330
|
+
},
|
|
331
|
+
body: {
|
|
332
|
+
elements: [
|
|
333
|
+
{
|
|
334
|
+
tag: "markdown",
|
|
335
|
+
content: "\u8BF7\u9009\u62E9\u60A8\u4F7F\u7528\u7684\u98DE\u4E66\u9879\u76EE\u73AF\u5883\uFF1A",
|
|
336
|
+
text_size: "normal"
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
tag: "form",
|
|
340
|
+
name: "project_domain_form",
|
|
341
|
+
elements: [
|
|
342
|
+
{
|
|
343
|
+
tag: "select_static",
|
|
344
|
+
name: "domain_preset",
|
|
345
|
+
placeholder: { tag: "plain_text", content: "\u9009\u62E9\u73AF\u5883" },
|
|
346
|
+
options: [
|
|
347
|
+
{ value: "feishu", text: { tag: "plain_text", content: "\u98DE\u4E66\u9879\u76EE\uFF08project.feishu.cn\uFF09" } },
|
|
348
|
+
{ value: "meegle", text: { tag: "plain_text", content: "Meegle\uFF08meegle.com\uFF09" } }
|
|
349
|
+
],
|
|
350
|
+
value: { value: "feishu", text: { tag: "plain_text", content: "\u98DE\u4E66\u9879\u76EE\uFF08project.feishu.cn\uFF09" } }
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
tag: "markdown",
|
|
354
|
+
content: "\u5982\u4F7F\u7528\u79C1\u6709\u5316\u90E8\u7F72\uFF0C\u8BF7\u5728\u4E0B\u65B9\u586B\u5199\u5B8C\u6574\u57DF\u540D\uFF08\u5C06\u8986\u76D6\u4E0A\u65B9\u9009\u62E9\uFF09\uFF1A",
|
|
355
|
+
text_size: "notation"
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
tag: "input",
|
|
359
|
+
name: "domain_custom",
|
|
360
|
+
placeholder: { tag: "plain_text", content: "https://custom.example.com" },
|
|
361
|
+
max_length: 200
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
tag: "button",
|
|
365
|
+
text: { tag: "plain_text", content: "\u786E\u8BA4" },
|
|
366
|
+
type: "primary",
|
|
367
|
+
form_action_type: "submit",
|
|
368
|
+
name: "submit_project_domain"
|
|
369
|
+
}
|
|
370
|
+
]
|
|
371
|
+
}
|
|
372
|
+
]
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
function buildProjectDomainConfirmedCard(envLabel) {
|
|
377
|
+
return {
|
|
378
|
+
schema: "2.0",
|
|
379
|
+
config: { wide_screen_mode: false },
|
|
380
|
+
header: {
|
|
381
|
+
title: { tag: "plain_text", content: "\u73AF\u5883\u5DF2\u786E\u8BA4" },
|
|
382
|
+
subtitle: { tag: "plain_text", content: "" },
|
|
383
|
+
template: "green",
|
|
384
|
+
padding: "12px 12px 12px 12px",
|
|
385
|
+
icon: { tag: "standard_icon", token: "yes_filled" }
|
|
386
|
+
},
|
|
387
|
+
body: {
|
|
388
|
+
elements: [
|
|
389
|
+
{
|
|
390
|
+
tag: "markdown",
|
|
391
|
+
content: `\u5DF2\u9009\u62E9\uFF1A**${envLabel}**\uFF0C\u6B63\u5728\u7EE7\u7EED\u6388\u6743...`
|
|
392
|
+
}
|
|
393
|
+
]
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
}
|
|
320
397
|
function buildAuthIdentityMismatchCard() {
|
|
321
398
|
return {
|
|
322
399
|
schema: "2.0",
|
|
@@ -355,6 +432,8 @@ export {
|
|
|
355
432
|
buildAuthIdentityMismatchCard,
|
|
356
433
|
buildAuthSuccessCard,
|
|
357
434
|
buildProjectAuthCard,
|
|
435
|
+
buildProjectDomainCard,
|
|
436
|
+
buildProjectDomainConfirmedCard,
|
|
358
437
|
formatScopeDescription,
|
|
359
438
|
toInAppWebUrl
|
|
360
439
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/tools/oauth-cards.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * oauth-cards.ts \u2014 OAuth \u6388\u6743\u5361\u7247\u6784\u5EFA\u51FD\u6570\u3002\n *\n * \u4ECE oauth.ts \u63D0\u53D6\u7684\u7EAF UI \u51FD\u6570\uFF0C\u4E0E OAuth \u4E1A\u52A1\u6D41\u7A0B\u89E3\u8026\u3002\n */\n\n// ---------------------------------------------------------------------------\n// Card builders\n// ---------------------------------------------------------------------------\n\nexport function buildAuthCard(params: {\n verificationUriComplete: string;\n expiresMin: number;\n scope?: string;\n isBatchAuth?: boolean;\n totalAppScopes?: number;\n alreadyGranted?: number;\n batchInfo?: string;\n filteredScopes?: string[]; // \u88AB\u8FC7\u6EE4\u7684 scope\uFF08\u5E94\u7528\u672A\u5F00\u901A\uFF09\n appId?: string; // \u7528\u4E8E\u751F\u6210\u6743\u9650\u7BA1\u7406\u94FE\u63A5\n showBatchAuthHint?: boolean; // \u4EC5 auto-auth \u6D41\u7A0B\u5C55\u793A\u6279\u91CF\u6388\u6743\u63D0\u793A\n}): Record<string, unknown> {\n const {\n verificationUriComplete,\n expiresMin,\n scope,\n isBatchAuth,\n totalAppScopes,\n alreadyGranted,\n batchInfo,\n filteredScopes,\n appId,\n showBatchAuthHint,\n } = params;\n const inAppUrl = toInAppWebUrl(verificationUriComplete);\n const multiUrl = {\n url: inAppUrl,\n pc_url: inAppUrl,\n android_url: inAppUrl,\n ios_url: inAppUrl,\n };\n\n // \u5C06 scope \u8F6C\u6210\u53EF\u8BFB\u8BF4\u660E\n const scopeDesc = formatScopeDescription(\n scope,\n isBatchAuth,\n totalAppScopes,\n alreadyGranted,\n batchInfo,\n filteredScopes,\n appId,\n );\n\n const elements: Record<string, unknown>[] = [\n // \u6388\u6743\u8BF4\u660E\n {\n tag: 'markdown',\n content: scopeDesc,\n text_size: 'normal',\n },\n // \u6388\u6743\u6309\u94AE\uFF08small\uFF0C\u9760\u53F3\uFF09\n {\n tag: 'column_set',\n flex_mode: 'none',\n horizontal_align: 'right',\n columns: [\n {\n tag: 'column',\n width: 'auto',\n elements: [\n {\n tag: 'button',\n text: { tag: 'plain_text', content: '\u524D\u5F80\u6388\u6743' },\n type: 'primary',\n size: 'medium',\n multi_url: multiUrl,\n },\n ],\n },\n ],\n },\n // \u5931\u6548\u65F6\u95F4\u63D0\u9192\n {\n tag: 'markdown',\n content: `<font color='grey'>\u6388\u6743\u94FE\u63A5\u5C06\u5728 ${expiresMin} \u5206\u949F\u540E\u5931\u6548\uFF0C\u5C4A\u65F6\u9700\u91CD\u65B0\u53D1\u8D77</font>`,\n text_size: 'notation',\n },\n // \u6279\u91CF\u6388\u6743\u63D0\u793A\uFF08\u4EC5 auto-auth \u6D41\u7A0B\u5C55\u793A\uFF09\n ...(showBatchAuthHint\n ? [\n {\n tag: 'markdown',\n content:\n \"<font color='grey'>\uD83D\uDCA1\u5982\u679C\u4F60\u5E0C\u671B\u4E00\u6B21\u6027\u6388\u4E88\u6240\u6709\u63D2\u4EF6\u6240\u9700\u8981\u7684\u6743\u9650\uFF0C\u53EF\u4EE5\u544A\u8BC9\u6211\u300C\u6388\u4E88\u6240\u6709\u7528\u6237\u6743\u9650\u300D\uFF0C\u6211\u4F1A\u534F\u52A9\u4F60\u5B8C\u6210\u3002</font>\",\n text_size: 'notation',\n },\n ]\n : []),\n ];\n\n return {\n schema: '2.0',\n config: {\n wide_screen_mode: false,\n style: {\n color: {\n 'light-yellow-bg': {\n light_mode: 'rgba(255, 214, 102, 0.12)',\n dark_mode: 'rgba(255, 214, 102, 0.08)',\n },\n },\n },\n },\n header: {\n title: {\n tag: 'plain_text',\n content: '\u9700\u8981\u60A8\u7684\u6388\u6743\u624D\u80FD\u7EE7\u7EED',\n },\n subtitle: {\n tag: 'plain_text',\n content: '',\n },\n template: 'blue',\n padding: '12px 12px 12px 12px',\n icon: {\n tag: 'standard_icon',\n token: 'lock-chat_filled',\n },\n },\n body: { elements },\n };\n}\n\n/** scope \u5B57\u7B26\u4E32 \u2192 \u53EF\u8BFB\u63CF\u8FF0 */\nexport function formatScopeDescription(\n scope?: string,\n isBatchAuth?: boolean,\n totalAppScopes?: number,\n alreadyGranted?: number,\n batchInfo?: string,\n _filteredScopes?: string[],\n _appId?: string,\n): string {\n const scopes = scope?.split(/\\s+/).filter(Boolean);\n\n if (isBatchAuth && scopes && scopes.length > 0) {\n let message = `\u5E94\u7528\u9700\u8981\u6388\u6743 **${scopes.length}** \u4E2A\u7528\u6237\u6743\u9650\uFF08\u5171 ${totalAppScopes} \u4E2A\uFF0C\u5DF2\u6388\u6743 ${alreadyGranted} \u4E2A\uFF09\u3002`;\n\n // \u5982\u679C\u8D85\u8FC7 5 \u4E2A scope\uFF0C\u53EA\u663E\u793A\u524D 3 \u4E2A\uFF0C\u7136\u540E\u7528\"...\"\u8868\u793A\n if (scopes.length > 5) {\n const previewScopes = scopes.slice(0, 3).join('\\n');\n message += `\\n\\n**\u5C06\u8981\u6388\u6743\u7684\u6743\u9650**\uFF1A\\n${previewScopes}\\n...\\n`;\n } else {\n const scopeList = scopes.map((s, idx) => `${idx + 1}. ${s}`).join('\\n');\n message += `\\n\\n**\u5C06\u8981\u6388\u6743\u7684\u6743\u9650\u5217\u8868**\uFF1A\\n${scopeList}\\n`;\n }\n\n // \u6DFB\u52A0\u5206\u6279\u63D0\u793A\u4FE1\u606F\n if (batchInfo) {\n message += `\\n\\n${batchInfo}`;\n }\n\n return message;\n }\n\n const desc = '\u6388\u6743\u540E\uFF0C\u5E94\u7528\u5C06\u80FD\u591F\u4EE5\u60A8\u7684\u8EAB\u4EFD\u6267\u884C\u76F8\u5173\u64CD\u4F5C\u3002';\n if (!scopes?.length) return desc;\n\n const message = desc + '\\n\\n\u6240\u9700\u6743\u9650\uFF1A\\n' + scopes.map((s) => `- ${s}`).join('\\n');\n\n return message;\n}\n\nexport function toInAppWebUrl(targetUrl: string): string {\n const encoded = encodeURIComponent(targetUrl);\n const lkMeta = encodeURIComponent(\n JSON.stringify({\n 'page-meta': {\n showNavBar: 'false',\n showBottomNavBar: 'false',\n },\n }),\n );\n return (\n 'https://applink.feishu.cn/client/web_url/open' +\n `?mode=sidebar-semi&max_width=800&reload=false&url=${encoded}&lk_meta=${lkMeta}`\n );\n}\n\n/**\n * \u98DE\u4E66\u9879\u76EE OAuth \u4E13\u7528\u6388\u6743\u5361\u7247\u3002\n *\n * \u4E24\u6B65\u5F0F\uFF1A\u2460 \u524D\u5F80\u6388\u6743\u6309\u94AE \u2461 URL \u7C98\u8D34\u8F93\u5165\u6846 + \u63D0\u4EA4\u6309\u94AE\uFF08\u901A\u8FC7\u5361\u7247\u56DE\u8C03\u81EA\u52A8\u5B8C\u6210\uFF09\n */\nexport function buildProjectAuthCard(params: {\n authorizationUrl: string;\n expiresMin: number;\n}): Record<string, unknown> {\n const { authorizationUrl, expiresMin } = params;\n // \u4E0D\u4F7F\u7528 applink \u5305\u88C5\uFF1A\u670D\u52A1\u7AEF\u901A\u8FC7 redirect_mode=manual \u76F4\u63A5\u5C55\u793A\u590D\u5236\u9875\u9762\uFF0C\n // \u5728\u98DE\u4E66\u5185\u7F6E\u6D4F\u89C8\u5668\u4E2D\u5373\u53EF\u6B63\u5E38\u5B8C\u6210\uFF0C\u65E0\u9700\u8DF3\u8F6C\u7CFB\u7EDF\u6D4F\u89C8\u5668\u3002\n const multiUrl = {\n url: authorizationUrl,\n pc_url: authorizationUrl,\n android_url: authorizationUrl,\n ios_url: authorizationUrl,\n };\n\n return {\n schema: '2.0',\n config: { wide_screen_mode: false },\n header: {\n title: { tag: 'plain_text', content: '\u98DE\u4E66\u9879\u76EE\u9700\u8981\u60A8\u7684\u6388\u6743' },\n subtitle: { tag: 'plain_text', content: '' },\n template: 'blue',\n padding: '12px 12px 12px 12px',\n icon: { tag: 'standard_icon', token: 'lock-chat_filled' },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content: '\u6388\u6743\u540E\uFF0C\u5E94\u7528\u5C06\u80FD\u591F\u4EE5\u60A8\u7684\u8EAB\u4EFD\u6267\u884C\u98DE\u4E66\u9879\u76EE\u76F8\u5173\u64CD\u4F5C\u3002',\n text_size: 'normal',\n },\n // Step 1: navigate to auth page\n {\n tag: 'column_set',\n flex_mode: 'none',\n horizontal_spacing: 'default',\n columns: [\n {\n tag: 'column',\n width: 'weighted',\n weight: 3,\n vertical_align: 'center',\n elements: [{ tag: 'markdown', content: '**\u7B2C\u4E00\u6B65\uFF1A\u70B9\u51FB\u6309\u94AE\uFF0C\u5728\u6D4F\u89C8\u5668\u4E2D\u5B8C\u6210\u6388\u6743**' }],\n },\n {\n tag: 'column',\n width: 'weighted',\n weight: 1,\n vertical_align: 'center',\n elements: [{\n tag: 'button',\n text: { tag: 'plain_text', content: '\u524D\u5F80\u6388\u6743' },\n type: 'primary',\n size: 'medium',\n multi_url: multiUrl,\n }],\n },\n ],\n },\n { tag: 'hr' },\n // Step 2: paste callback URL\n {\n tag: 'markdown',\n content:\n '**\u7B2C\u4E8C\u6B65\uFF1A\u7C98\u8D34\u6D4F\u89C8\u5668\u5730\u5740\u680F URL**\\n' +\n \"<font color='grey'>\u6388\u6743\u540E\u6D4F\u89C8\u5668\u4F1A\u8DF3\u8F6C\u5230\u4E00\u4E2A\u65E0\u6CD5\u6253\u5F00\u7684\u9875\u9762\uFF0C\u8FD9\u662F\u6B63\u5E38\u7684\u2014\u2014\u8BF7\u590D\u5236\u5730\u5740\u680F\u4E2D\u7684\u5B8C\u6574 URL \u7C98\u8D34\u5230\u4E0B\u65B9</font>\",\n text_size: 'normal',\n },\n {\n tag: 'form',\n name: `project_auth_form`,\n elements: [\n {\n tag: 'input',\n name: 'callback_url',\n placeholder: {\n tag: 'plain_text',\n content: '\u7C98\u8D34 URL\uFF0C\u5982 http://127.0.0.1:3456/callback?code=...',\n },\n max_length: 1000,\n },\n {\n tag: 'button',\n text: { tag: 'plain_text', content: '\u5B8C\u6210\u6388\u6743' },\n type: 'primary',\n form_action_type: 'submit',\n name: 'submit_project_auth',\n },\n ],\n },\n {\n tag: 'markdown',\n content: `<font color='grey'>\u6388\u6743\u94FE\u63A5\u5C06\u5728 ${expiresMin} \u5206\u949F\u540E\u5931\u6548\uFF0C\u5C4A\u65F6\u9700\u91CD\u65B0\u53D1\u8D77</font>`,\n text_size: 'notation',\n },\n ],\n },\n };\n}\n\nexport function buildAuthSuccessCard(): Record<string, unknown> {\n return {\n schema: '2.0',\n config: {\n wide_screen_mode: false,\n style: {\n color: {\n 'light-green-bg': {\n light_mode: 'rgba(52, 199, 89, 0.12)',\n dark_mode: 'rgba(52, 199, 89, 0.08)',\n },\n },\n },\n },\n header: {\n title: {\n tag: 'plain_text',\n content: '\u6388\u6743\u6210\u529F',\n },\n subtitle: {\n tag: 'plain_text',\n content: '',\n },\n template: 'green',\n padding: '12px 12px 12px 12px',\n icon: {\n tag: 'standard_icon',\n token: 'yes_filled',\n },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content:\n '\u60A8\u7684\u98DE\u4E66\u8D26\u53F7\u5DF2\u6210\u529F\u6388\u6743\uFF0C\u6B63\u5728\u4E3A\u60A8\u7EE7\u7EED\u6267\u884C\u64CD\u4F5C\u3002\\n\\n' +\n \"<font color='grey'>\u5982\u9700\u64A4\u9500\u6388\u6743\uFF0C\u53EF\u968F\u65F6\u544A\u8BC9\u6211\u3002</font>\",\n },\n ],\n },\n };\n}\n\nexport function buildAuthFailedCard(_reason: string): Record<string, unknown> {\n return {\n schema: '2.0',\n config: {\n wide_screen_mode: false,\n style: {\n color: {\n 'light-grey-bg': {\n light_mode: 'rgba(142, 142, 147, 0.12)',\n dark_mode: 'rgba(142, 142, 147, 0.08)',\n },\n },\n },\n },\n header: {\n title: {\n tag: 'plain_text',\n content: '\u6388\u6743\u672A\u5B8C\u6210',\n },\n subtitle: {\n tag: 'plain_text',\n content: '',\n },\n template: 'yellow',\n padding: '12px 12px 12px 12px',\n icon: {\n tag: 'standard_icon',\n token: 'warning_filled',\n },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content: '\u6388\u6743\u94FE\u63A5\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77\u6388\u6743\u3002',\n },\n ],\n },\n };\n}\n\nexport function buildAuthIdentityMismatchCard(): Record<string, unknown> {\n return {\n schema: '2.0',\n config: {\n wide_screen_mode: false,\n },\n header: {\n title: {\n tag: 'plain_text',\n content: '\u6388\u6743\u5931\u8D25\uFF0C\u64CD\u4F5C\u8D26\u53F7\u4E0E\u53D1\u8D77\u8D26\u53F7\u4E0D\u4E00\u81F4',\n },\n subtitle: {\n tag: 'plain_text',\n content: '',\n },\n template: 'red',\n padding: '12px 12px 12px 12px',\n icon: {\n tag: 'standard_icon',\n token: 'close_filled',\n },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content:\n '\u68C0\u6D4B\u5230\u5F53\u524D\u8FDB\u884C\u6388\u6743\u64CD\u4F5C\u7684\u98DE\u4E66\u8D26\u53F7\u4E0E\u53D1\u8D77\u6388\u6743\u8BF7\u6C42\u7684\u8D26\u53F7\u4E0D\u4E00\u81F4\u3002\u4E3A\u4FDD\u969C\u6570\u636E\u5B89\u5168\uFF0C\u672C\u6B21\u6388\u6743\u5DF2\u88AB\u62D2\u7EDD\u3002\\n\\n' +\n \"<font color='grey'>\u8BF7\u6388\u6743\u8BF7\u6C42\u7684\u53D1\u8D77\u4EBA\u4F7F\u7528\u5176\u8D26\u53F7\uFF0C\u70B9\u51FB\u6388\u6743\u94FE\u63A5\u5B8C\u6210\u6388\u6743\u3002</font>\",\n },\n ],\n },\n };\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * oauth-cards.ts \u2014 OAuth \u6388\u6743\u5361\u7247\u6784\u5EFA\u51FD\u6570\u3002\n *\n * \u4ECE oauth.ts \u63D0\u53D6\u7684\u7EAF UI \u51FD\u6570\uFF0C\u4E0E OAuth \u4E1A\u52A1\u6D41\u7A0B\u89E3\u8026\u3002\n */\n\n// ---------------------------------------------------------------------------\n// Card builders\n// ---------------------------------------------------------------------------\n\nexport function buildAuthCard(params: {\n verificationUriComplete: string;\n expiresMin: number;\n scope?: string;\n isBatchAuth?: boolean;\n totalAppScopes?: number;\n alreadyGranted?: number;\n batchInfo?: string;\n filteredScopes?: string[]; // \u88AB\u8FC7\u6EE4\u7684 scope\uFF08\u5E94\u7528\u672A\u5F00\u901A\uFF09\n appId?: string; // \u7528\u4E8E\u751F\u6210\u6743\u9650\u7BA1\u7406\u94FE\u63A5\n showBatchAuthHint?: boolean; // \u4EC5 auto-auth \u6D41\u7A0B\u5C55\u793A\u6279\u91CF\u6388\u6743\u63D0\u793A\n}): Record<string, unknown> {\n const {\n verificationUriComplete,\n expiresMin,\n scope,\n isBatchAuth,\n totalAppScopes,\n alreadyGranted,\n batchInfo,\n filteredScopes,\n appId,\n showBatchAuthHint,\n } = params;\n const inAppUrl = toInAppWebUrl(verificationUriComplete);\n const multiUrl = {\n url: inAppUrl,\n pc_url: inAppUrl,\n android_url: inAppUrl,\n ios_url: inAppUrl,\n };\n\n // \u5C06 scope \u8F6C\u6210\u53EF\u8BFB\u8BF4\u660E\n const scopeDesc = formatScopeDescription(\n scope,\n isBatchAuth,\n totalAppScopes,\n alreadyGranted,\n batchInfo,\n filteredScopes,\n appId,\n );\n\n const elements: Record<string, unknown>[] = [\n // \u6388\u6743\u8BF4\u660E\n {\n tag: 'markdown',\n content: scopeDesc,\n text_size: 'normal',\n },\n // \u6388\u6743\u6309\u94AE\uFF08small\uFF0C\u9760\u53F3\uFF09\n {\n tag: 'column_set',\n flex_mode: 'none',\n horizontal_align: 'right',\n columns: [\n {\n tag: 'column',\n width: 'auto',\n elements: [\n {\n tag: 'button',\n text: { tag: 'plain_text', content: '\u524D\u5F80\u6388\u6743' },\n type: 'primary',\n size: 'medium',\n multi_url: multiUrl,\n },\n ],\n },\n ],\n },\n // \u5931\u6548\u65F6\u95F4\u63D0\u9192\n {\n tag: 'markdown',\n content: `<font color='grey'>\u6388\u6743\u94FE\u63A5\u5C06\u5728 ${expiresMin} \u5206\u949F\u540E\u5931\u6548\uFF0C\u5C4A\u65F6\u9700\u91CD\u65B0\u53D1\u8D77</font>`,\n text_size: 'notation',\n },\n // \u6279\u91CF\u6388\u6743\u63D0\u793A\uFF08\u4EC5 auto-auth \u6D41\u7A0B\u5C55\u793A\uFF09\n ...(showBatchAuthHint\n ? [\n {\n tag: 'markdown',\n content:\n \"<font color='grey'>\uD83D\uDCA1\u5982\u679C\u4F60\u5E0C\u671B\u4E00\u6B21\u6027\u6388\u4E88\u6240\u6709\u63D2\u4EF6\u6240\u9700\u8981\u7684\u6743\u9650\uFF0C\u53EF\u4EE5\u544A\u8BC9\u6211\u300C\u6388\u4E88\u6240\u6709\u7528\u6237\u6743\u9650\u300D\uFF0C\u6211\u4F1A\u534F\u52A9\u4F60\u5B8C\u6210\u3002</font>\",\n text_size: 'notation',\n },\n ]\n : []),\n ];\n\n return {\n schema: '2.0',\n config: {\n wide_screen_mode: false,\n style: {\n color: {\n 'light-yellow-bg': {\n light_mode: 'rgba(255, 214, 102, 0.12)',\n dark_mode: 'rgba(255, 214, 102, 0.08)',\n },\n },\n },\n },\n header: {\n title: {\n tag: 'plain_text',\n content: '\u9700\u8981\u60A8\u7684\u6388\u6743\u624D\u80FD\u7EE7\u7EED',\n },\n subtitle: {\n tag: 'plain_text',\n content: '',\n },\n template: 'blue',\n padding: '12px 12px 12px 12px',\n icon: {\n tag: 'standard_icon',\n token: 'lock-chat_filled',\n },\n },\n body: { elements },\n };\n}\n\n/** scope \u5B57\u7B26\u4E32 \u2192 \u53EF\u8BFB\u63CF\u8FF0 */\nexport function formatScopeDescription(\n scope?: string,\n isBatchAuth?: boolean,\n totalAppScopes?: number,\n alreadyGranted?: number,\n batchInfo?: string,\n _filteredScopes?: string[],\n _appId?: string,\n): string {\n const scopes = scope?.split(/\\s+/).filter(Boolean);\n\n if (isBatchAuth && scopes && scopes.length > 0) {\n let message = `\u5E94\u7528\u9700\u8981\u6388\u6743 **${scopes.length}** \u4E2A\u7528\u6237\u6743\u9650\uFF08\u5171 ${totalAppScopes} \u4E2A\uFF0C\u5DF2\u6388\u6743 ${alreadyGranted} \u4E2A\uFF09\u3002`;\n\n // \u5982\u679C\u8D85\u8FC7 5 \u4E2A scope\uFF0C\u53EA\u663E\u793A\u524D 3 \u4E2A\uFF0C\u7136\u540E\u7528\"...\"\u8868\u793A\n if (scopes.length > 5) {\n const previewScopes = scopes.slice(0, 3).join('\\n');\n message += `\\n\\n**\u5C06\u8981\u6388\u6743\u7684\u6743\u9650**\uFF1A\\n${previewScopes}\\n...\\n`;\n } else {\n const scopeList = scopes.map((s, idx) => `${idx + 1}. ${s}`).join('\\n');\n message += `\\n\\n**\u5C06\u8981\u6388\u6743\u7684\u6743\u9650\u5217\u8868**\uFF1A\\n${scopeList}\\n`;\n }\n\n // \u6DFB\u52A0\u5206\u6279\u63D0\u793A\u4FE1\u606F\n if (batchInfo) {\n message += `\\n\\n${batchInfo}`;\n }\n\n return message;\n }\n\n const desc = '\u6388\u6743\u540E\uFF0C\u5E94\u7528\u5C06\u80FD\u591F\u4EE5\u60A8\u7684\u8EAB\u4EFD\u6267\u884C\u76F8\u5173\u64CD\u4F5C\u3002';\n if (!scopes?.length) return desc;\n\n const message = desc + '\\n\\n\u6240\u9700\u6743\u9650\uFF1A\\n' + scopes.map((s) => `- ${s}`).join('\\n');\n\n return message;\n}\n\nexport function toInAppWebUrl(targetUrl: string): string {\n const encoded = encodeURIComponent(targetUrl);\n const lkMeta = encodeURIComponent(\n JSON.stringify({\n 'page-meta': {\n showNavBar: 'false',\n showBottomNavBar: 'false',\n },\n }),\n );\n return (\n 'https://applink.feishu.cn/client/web_url/open' +\n `?mode=sidebar-semi&max_width=800&reload=false&url=${encoded}&lk_meta=${lkMeta}`\n );\n}\n\n/**\n * \u98DE\u4E66\u9879\u76EE OAuth \u4E13\u7528\u6388\u6743\u5361\u7247\u3002\n *\n * \u4E24\u6B65\u5F0F\uFF1A\u2460 \u524D\u5F80\u6388\u6743\u6309\u94AE \u2461 URL \u7C98\u8D34\u8F93\u5165\u6846 + \u63D0\u4EA4\u6309\u94AE\uFF08\u901A\u8FC7\u5361\u7247\u56DE\u8C03\u81EA\u52A8\u5B8C\u6210\uFF09\n */\nexport function buildProjectAuthCard(params: {\n authorizationUrl: string;\n expiresMin: number;\n}): Record<string, unknown> {\n const { authorizationUrl, expiresMin } = params;\n // \u4E0D\u4F7F\u7528 applink \u5305\u88C5\uFF1A\u670D\u52A1\u7AEF\u901A\u8FC7 redirect_mode=manual \u76F4\u63A5\u5C55\u793A\u590D\u5236\u9875\u9762\uFF0C\n // \u5728\u98DE\u4E66\u5185\u7F6E\u6D4F\u89C8\u5668\u4E2D\u5373\u53EF\u6B63\u5E38\u5B8C\u6210\uFF0C\u65E0\u9700\u8DF3\u8F6C\u7CFB\u7EDF\u6D4F\u89C8\u5668\u3002\n const multiUrl = {\n url: authorizationUrl,\n pc_url: authorizationUrl,\n android_url: authorizationUrl,\n ios_url: authorizationUrl,\n };\n\n return {\n schema: '2.0',\n config: { wide_screen_mode: false },\n header: {\n title: { tag: 'plain_text', content: '\u98DE\u4E66\u9879\u76EE\u9700\u8981\u60A8\u7684\u6388\u6743' },\n subtitle: { tag: 'plain_text', content: '' },\n template: 'blue',\n padding: '12px 12px 12px 12px',\n icon: { tag: 'standard_icon', token: 'lock-chat_filled' },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content: '\u6388\u6743\u540E\uFF0C\u5E94\u7528\u5C06\u80FD\u591F\u4EE5\u60A8\u7684\u8EAB\u4EFD\u6267\u884C\u98DE\u4E66\u9879\u76EE\u76F8\u5173\u64CD\u4F5C\u3002',\n text_size: 'normal',\n },\n // Step 1: navigate to auth page\n {\n tag: 'column_set',\n flex_mode: 'none',\n horizontal_spacing: 'default',\n columns: [\n {\n tag: 'column',\n width: 'weighted',\n weight: 3,\n vertical_align: 'center',\n elements: [{ tag: 'markdown', content: '**\u7B2C\u4E00\u6B65\uFF1A\u70B9\u51FB\u6309\u94AE\uFF0C\u5728\u6D4F\u89C8\u5668\u4E2D\u5B8C\u6210\u6388\u6743**' }],\n },\n {\n tag: 'column',\n width: 'weighted',\n weight: 1,\n vertical_align: 'center',\n elements: [{\n tag: 'button',\n text: { tag: 'plain_text', content: '\u524D\u5F80\u6388\u6743' },\n type: 'primary',\n size: 'medium',\n multi_url: multiUrl,\n }],\n },\n ],\n },\n { tag: 'hr' },\n // Step 2: paste callback URL\n {\n tag: 'markdown',\n content:\n '**\u7B2C\u4E8C\u6B65\uFF1A\u7C98\u8D34\u6D4F\u89C8\u5668\u5730\u5740\u680F URL**\\n' +\n \"<font color='grey'>\u6388\u6743\u540E\u6D4F\u89C8\u5668\u4F1A\u8DF3\u8F6C\u5230\u4E00\u4E2A\u65E0\u6CD5\u6253\u5F00\u7684\u9875\u9762\uFF0C\u8FD9\u662F\u6B63\u5E38\u7684\u2014\u2014\u8BF7\u590D\u5236\u5730\u5740\u680F\u4E2D\u7684\u5B8C\u6574 URL \u7C98\u8D34\u5230\u4E0B\u65B9</font>\",\n text_size: 'normal',\n },\n {\n tag: 'form',\n name: `project_auth_form`,\n elements: [\n {\n tag: 'input',\n name: 'callback_url',\n placeholder: {\n tag: 'plain_text',\n content: '\u7C98\u8D34 URL\uFF0C\u5982 http://127.0.0.1:3456/callback?code=...',\n },\n max_length: 1000,\n },\n {\n tag: 'button',\n text: { tag: 'plain_text', content: '\u5B8C\u6210\u6388\u6743' },\n type: 'primary',\n form_action_type: 'submit',\n name: 'submit_project_auth',\n },\n ],\n },\n {\n tag: 'markdown',\n content: `<font color='grey'>\u6388\u6743\u94FE\u63A5\u5C06\u5728 ${expiresMin} \u5206\u949F\u540E\u5931\u6548\uFF0C\u5C4A\u65F6\u9700\u91CD\u65B0\u53D1\u8D77</font>`,\n text_size: 'notation',\n },\n ],\n },\n };\n}\n\nexport function buildAuthSuccessCard(): Record<string, unknown> {\n return {\n schema: '2.0',\n config: {\n wide_screen_mode: false,\n style: {\n color: {\n 'light-green-bg': {\n light_mode: 'rgba(52, 199, 89, 0.12)',\n dark_mode: 'rgba(52, 199, 89, 0.08)',\n },\n },\n },\n },\n header: {\n title: {\n tag: 'plain_text',\n content: '\u6388\u6743\u6210\u529F',\n },\n subtitle: {\n tag: 'plain_text',\n content: '',\n },\n template: 'green',\n padding: '12px 12px 12px 12px',\n icon: {\n tag: 'standard_icon',\n token: 'yes_filled',\n },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content:\n '\u60A8\u7684\u98DE\u4E66\u8D26\u53F7\u5DF2\u6210\u529F\u6388\u6743\uFF0C\u6B63\u5728\u4E3A\u60A8\u7EE7\u7EED\u6267\u884C\u64CD\u4F5C\u3002\\n\\n' +\n \"<font color='grey'>\u5982\u9700\u64A4\u9500\u6388\u6743\uFF0C\u53EF\u968F\u65F6\u544A\u8BC9\u6211\u3002</font>\",\n },\n ],\n },\n };\n}\n\nexport function buildAuthFailedCard(_reason: string): Record<string, unknown> {\n return {\n schema: '2.0',\n config: {\n wide_screen_mode: false,\n style: {\n color: {\n 'light-grey-bg': {\n light_mode: 'rgba(142, 142, 147, 0.12)',\n dark_mode: 'rgba(142, 142, 147, 0.08)',\n },\n },\n },\n },\n header: {\n title: {\n tag: 'plain_text',\n content: '\u6388\u6743\u672A\u5B8C\u6210',\n },\n subtitle: {\n tag: 'plain_text',\n content: '',\n },\n template: 'yellow',\n padding: '12px 12px 12px 12px',\n icon: {\n tag: 'standard_icon',\n token: 'warning_filled',\n },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content: '\u6388\u6743\u94FE\u63A5\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77\u6388\u6743\u3002',\n },\n ],\n },\n };\n}\n\n/**\n * \u98DE\u4E66\u9879\u76EE\u57DF\u540D\u9009\u62E9\u5361\u7247\u3002\n *\n * \u4E0B\u62C9\u9009\u62E9\u9884\u8BBE\u73AF\u5883 + \u53EF\u9009\u81EA\u5B9A\u4E49\u57DF\u540D\u8F93\u5165\u6846\uFF0C\u901A\u8FC7 form submit \u56DE\u8C03\u3002\n */\nexport function buildProjectDomainCard(): Record<string, unknown> {\n return {\n schema: '2.0',\n config: { wide_screen_mode: false },\n header: {\n title: { tag: 'plain_text', content: '\u9009\u62E9\u98DE\u4E66\u9879\u76EE\u73AF\u5883' },\n subtitle: { tag: 'plain_text', content: '' },\n template: 'blue',\n padding: '12px 12px 12px 12px',\n icon: { tag: 'standard_icon', token: 'settings_filled' },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content: '\u8BF7\u9009\u62E9\u60A8\u4F7F\u7528\u7684\u98DE\u4E66\u9879\u76EE\u73AF\u5883\uFF1A',\n text_size: 'normal',\n },\n {\n tag: 'form',\n name: 'project_domain_form',\n elements: [\n {\n tag: 'select_static',\n name: 'domain_preset',\n placeholder: { tag: 'plain_text', content: '\u9009\u62E9\u73AF\u5883' },\n options: [\n { value: 'feishu', text: { tag: 'plain_text', content: '\u98DE\u4E66\u9879\u76EE\uFF08project.feishu.cn\uFF09' } },\n { value: 'meegle', text: { tag: 'plain_text', content: 'Meegle\uFF08meegle.com\uFF09' } },\n ],\n value: { value: 'feishu', text: { tag: 'plain_text', content: '\u98DE\u4E66\u9879\u76EE\uFF08project.feishu.cn\uFF09' } },\n },\n {\n tag: 'markdown',\n content: \"\u5982\u4F7F\u7528\u79C1\u6709\u5316\u90E8\u7F72\uFF0C\u8BF7\u5728\u4E0B\u65B9\u586B\u5199\u5B8C\u6574\u57DF\u540D\uFF08\u5C06\u8986\u76D6\u4E0A\u65B9\u9009\u62E9\uFF09\uFF1A\",\n text_size: 'notation',\n },\n {\n tag: 'input',\n name: 'domain_custom',\n placeholder: { tag: 'plain_text', content: 'https://custom.example.com' },\n max_length: 200,\n },\n {\n tag: 'button',\n text: { tag: 'plain_text', content: '\u786E\u8BA4' },\n type: 'primary',\n form_action_type: 'submit',\n name: 'submit_project_domain',\n },\n ],\n },\n ],\n },\n };\n}\n\nexport function buildProjectDomainConfirmedCard(envLabel: string): Record<string, unknown> {\n return {\n schema: '2.0',\n config: { wide_screen_mode: false },\n header: {\n title: { tag: 'plain_text', content: '\u73AF\u5883\u5DF2\u786E\u8BA4' },\n subtitle: { tag: 'plain_text', content: '' },\n template: 'green',\n padding: '12px 12px 12px 12px',\n icon: { tag: 'standard_icon', token: 'yes_filled' },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content: `\u5DF2\u9009\u62E9\uFF1A**${envLabel}**\uFF0C\u6B63\u5728\u7EE7\u7EED\u6388\u6743...`,\n },\n ],\n },\n };\n}\n\nexport function buildAuthIdentityMismatchCard(): Record<string, unknown> {\n return {\n schema: '2.0',\n config: {\n wide_screen_mode: false,\n },\n header: {\n title: {\n tag: 'plain_text',\n content: '\u6388\u6743\u5931\u8D25\uFF0C\u64CD\u4F5C\u8D26\u53F7\u4E0E\u53D1\u8D77\u8D26\u53F7\u4E0D\u4E00\u81F4',\n },\n subtitle: {\n tag: 'plain_text',\n content: '',\n },\n template: 'red',\n padding: '12px 12px 12px 12px',\n icon: {\n tag: 'standard_icon',\n token: 'close_filled',\n },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content:\n '\u68C0\u6D4B\u5230\u5F53\u524D\u8FDB\u884C\u6388\u6743\u64CD\u4F5C\u7684\u98DE\u4E66\u8D26\u53F7\u4E0E\u53D1\u8D77\u6388\u6743\u8BF7\u6C42\u7684\u8D26\u53F7\u4E0D\u4E00\u81F4\u3002\u4E3A\u4FDD\u969C\u6570\u636E\u5B89\u5168\uFF0C\u672C\u6B21\u6388\u6743\u5DF2\u88AB\u62D2\u7EDD\u3002\\n\\n' +\n \"<font color='grey'>\u8BF7\u6388\u6743\u8BF7\u6C42\u7684\u53D1\u8D77\u4EBA\u4F7F\u7528\u5176\u8D26\u53F7\uFF0C\u70B9\u51FB\u6388\u6743\u94FE\u63A5\u5B8C\u6210\u6388\u6743\u3002</font>\",\n },\n ],\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "AAaO,SAAS,cAAc,QAWF;AAC1B,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AACJ,QAAM,WAAW,cAAc,uBAAuB;AACtD,QAAM,WAAW;AAAA,IACf,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,SAAS;AAAA,EACX;AAGA,QAAM,YAAY;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,WAAsC;AAAA;AAAA,IAE1C;AAAA,MACE,KAAK;AAAA,MACL,SAAS;AAAA,MACT,WAAW;AAAA,IACb;AAAA;AAAA,IAEA;AAAA,MACE,KAAK;AAAA,MACL,WAAW;AAAA,MACX,kBAAkB;AAAA,MAClB,SAAS;AAAA,QACP;AAAA,UACE,KAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU;AAAA,YACR;AAAA,cACE,KAAK;AAAA,cACL,MAAM,EAAE,KAAK,cAAc,SAAS,2BAAO;AAAA,cAC3C,MAAM;AAAA,cACN,MAAM;AAAA,cACN,WAAW;AAAA,YACb;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA;AAAA,IAEA;AAAA,MACE,KAAK;AAAA,MACL,SAAS,2DAA6B,UAAU;AAAA,MAChD,WAAW;AAAA,IACb;AAAA;AAAA,IAEA,GAAI,oBACA;AAAA,MACE;AAAA,QACE,KAAK;AAAA,QACL,SACE;AAAA,QACF,WAAW;AAAA,MACb;AAAA,IACF,IACA,CAAC;AAAA,EACP;AAEA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,QAAQ;AAAA,MACN,kBAAkB;AAAA,MAClB,OAAO;AAAA,QACL,OAAO;AAAA,UACL,mBAAmB;AAAA,YACjB,YAAY;AAAA,YACZ,WAAW;AAAA,UACb;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,OAAO;AAAA,QACL,KAAK;AAAA,QACL,SAAS;AAAA,MACX;AAAA,MACA,UAAU;AAAA,QACR,KAAK;AAAA,QACL,SAAS;AAAA,MACX;AAAA,MACA,UAAU;AAAA,MACV,SAAS;AAAA,MACT,MAAM;AAAA,QACJ,KAAK;AAAA,QACL,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,MAAM,EAAE,SAAS;AAAA,EACnB;AACF;AAGO,SAAS,uBACd,OACA,aACA,gBACA,gBACA,WACA,iBACA,QACQ;AACR,QAAM,SAAS,OAAO,MAAM,KAAK,EAAE,OAAO,OAAO;AAEjD,MAAI,eAAe,UAAU,OAAO,SAAS,GAAG;AAC9C,QAAIA,WAAU,0CAAY,OAAO,MAAM,iDAAc,cAAc,mCAAU,cAAc;AAG3F,QAAI,OAAO,SAAS,GAAG;AACrB,YAAM,gBAAgB,OAAO,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI;AAClD,MAAAA,YAAW;AAAA;AAAA;AAAA,EAAqB,aAAa;AAAA;AAAA;AAAA,IAC/C,OAAO;AACL,YAAM,YAAY,OAAO,IAAI,CAAC,GAAG,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,IAAI;AACtE,MAAAA,YAAW;AAAA;AAAA;AAAA,EAAuB,SAAS;AAAA;AAAA,IAC7C;AAGA,QAAI,WAAW;AACb,MAAAA,YAAW;AAAA;AAAA,EAAO,SAAS;AAAA,IAC7B;AAEA,WAAOA;AAAA,EACT;AAEA,QAAM,OAAO;AACb,MAAI,CAAC,QAAQ,OAAQ,QAAO;AAE5B,QAAM,UAAU,OAAO,yCAAgB,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,EAAE,KAAK,IAAI;AAE5E,SAAO;AACT;AAEO,SAAS,cAAc,WAA2B;AACvD,QAAM,UAAU,mBAAmB,SAAS;AAC5C,QAAM,SAAS;AAAA,IACb,KAAK,UAAU;AAAA,MACb,aAAa;AAAA,QACX,YAAY;AAAA,QACZ,kBAAkB;AAAA,MACpB;AAAA,IACF,CAAC;AAAA,EACH;AACA,SACE,kGACqD,OAAO,YAAY,MAAM;AAElF;AAOO,SAAS,qBAAqB,QAGT;AAC1B,QAAM,EAAE,kBAAkB,WAAW,IAAI;AAGzC,QAAM,WAAW;AAAA,IACf,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,SAAS;AAAA,EACX;AAEA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,QAAQ,EAAE,kBAAkB,MAAM;AAAA,IAClC,QAAQ;AAAA,MACN,OAAO,EAAE,KAAK,cAAc,SAAS,+DAAa;AAAA,MAClD,UAAU,EAAE,KAAK,cAAc,SAAS,GAAG;AAAA,MAC3C,UAAU;AAAA,MACV,SAAS;AAAA,MACT,MAAM,EAAE,KAAK,iBAAiB,OAAO,mBAAmB;AAAA,IAC1D;AAAA,IACA,MAAM;AAAA,MACJ,UAAU;AAAA,QACR;AAAA,UACE,KAAK;AAAA,UACL,SAAS;AAAA,UACT,WAAW;AAAA,QACb;AAAA;AAAA,QAEA;AAAA,UACE,KAAK;AAAA,UACL,WAAW;AAAA,UACX,oBAAoB;AAAA,UACpB,SAAS;AAAA,YACP;AAAA,cACE,KAAK;AAAA,cACL,OAAO;AAAA,cACP,QAAQ;AAAA,cACR,gBAAgB;AAAA,cAChB,UAAU,CAAC,EAAE,KAAK,YAAY,SAAS,mHAAyB,CAAC;AAAA,YACnE;AAAA,YACA;AAAA,cACE,KAAK;AAAA,cACL,OAAO;AAAA,cACP,QAAQ;AAAA,cACR,gBAAgB;AAAA,cAChB,UAAU,CAAC;AAAA,gBACT,KAAK;AAAA,gBACL,MAAM,EAAE,KAAK,cAAc,SAAS,2BAAO;AAAA,gBAC3C,MAAM;AAAA,gBACN,MAAM;AAAA,gBACN,WAAW;AAAA,cACb,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF;AAAA,QACA,EAAE,KAAK,KAAK;AAAA;AAAA,QAEZ;AAAA,UACE,KAAK;AAAA,UACL,SACE;AAAA,UAEF,WAAW;AAAA,QACb;AAAA,QACA;AAAA,UACE,KAAK;AAAA,UACL,MAAM;AAAA,UACN,UAAU;AAAA,YACR;AAAA,cACE,KAAK;AAAA,cACL,MAAM;AAAA,cACN,aAAa;AAAA,gBACX,KAAK;AAAA,gBACL,SAAS;AAAA,cACX;AAAA,cACA,YAAY;AAAA,YACd;AAAA,YACA;AAAA,cACE,KAAK;AAAA,cACL,MAAM,EAAE,KAAK,cAAc,SAAS,2BAAO;AAAA,cAC3C,MAAM;AAAA,cACN,kBAAkB;AAAA,cAClB,MAAM;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAAA,QACA;AAAA,UACE,KAAK;AAAA,UACL,SAAS,2DAA6B,UAAU;AAAA,UAChD,WAAW;AAAA,QACb;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,uBAAgD;AAC9D,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,QAAQ;AAAA,MACN,kBAAkB;AAAA,MAClB,OAAO;AAAA,QACL,OAAO;AAAA,UACL,kBAAkB;AAAA,YAChB,YAAY;AAAA,YACZ,WAAW;AAAA,UACb;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,OAAO;AAAA,QACL,KAAK;AAAA,QACL,SAAS;AAAA,MACX;AAAA,MACA,UAAU;AAAA,QACR,KAAK;AAAA,QACL,SAAS;AAAA,MACX;AAAA,MACA,UAAU;AAAA,MACV,SAAS;AAAA,MACT,MAAM;AAAA,QACJ,KAAK;AAAA,QACL,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,MAAM;AAAA,MACJ,UAAU;AAAA,QACR;AAAA,UACE,KAAK;AAAA,UACL,SACE;AAAA,QAEJ;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,oBAAoB,SAA0C;AAC5E,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,QAAQ;AAAA,MACN,kBAAkB;AAAA,MAClB,OAAO;AAAA,QACL,OAAO;AAAA,UACL,iBAAiB;AAAA,YACf,YAAY;AAAA,YACZ,WAAW;AAAA,UACb;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,OAAO;AAAA,QACL,KAAK;AAAA,QACL,SAAS;AAAA,MACX;AAAA,MACA,UAAU;AAAA,QACR,KAAK;AAAA,QACL,SAAS;AAAA,MACX;AAAA,MACA,UAAU;AAAA,MACV,SAAS;AAAA,MACT,MAAM;AAAA,QACJ,KAAK;AAAA,QACL,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,MAAM;AAAA,MACJ,UAAU;AAAA,QACR;AAAA,UACE,KAAK;AAAA,UACL,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAOO,SAAS,yBAAkD;AAChE,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,QAAQ,EAAE,kBAAkB,MAAM;AAAA,IAClC,QAAQ;AAAA,MACN,OAAO,EAAE,KAAK,cAAc,SAAS,mDAAW;AAAA,MAChD,UAAU,EAAE,KAAK,cAAc,SAAS,GAAG;AAAA,MAC3C,UAAU;AAAA,MACV,SAAS;AAAA,MACT,MAAM,EAAE,KAAK,iBAAiB,OAAO,kBAAkB;AAAA,IACzD;AAAA,IACA,MAAM;AAAA,MACJ,UAAU;AAAA,QACR;AAAA,UACE,KAAK;AAAA,UACL,SAAS;AAAA,UACT,WAAW;AAAA,QACb;AAAA,QACA;AAAA,UACE,KAAK;AAAA,UACL,MAAM;AAAA,UACN,UAAU;AAAA,YACR;AAAA,cACE,KAAK;AAAA,cACL,MAAM;AAAA,cACN,aAAa,EAAE,KAAK,cAAc,SAAS,2BAAO;AAAA,cAClD,SAAS;AAAA,gBACP,EAAE,OAAO,UAAU,MAAM,EAAE,KAAK,cAAc,SAAS,wDAA0B,EAAE;AAAA,gBACnF,EAAE,OAAO,UAAU,MAAM,EAAE,KAAK,cAAc,SAAS,+BAAqB,EAAE;AAAA,cAChF;AAAA,cACA,OAAO,EAAE,OAAO,UAAU,MAAM,EAAE,KAAK,cAAc,SAAS,wDAA0B,EAAE;AAAA,YAC5F;AAAA,YACA;AAAA,cACE,KAAK;AAAA,cACL,SAAS;AAAA,cACT,WAAW;AAAA,YACb;AAAA,YACA;AAAA,cACE,KAAK;AAAA,cACL,MAAM;AAAA,cACN,aAAa,EAAE,KAAK,cAAc,SAAS,6BAA6B;AAAA,cACxE,YAAY;AAAA,YACd;AAAA,YACA;AAAA,cACE,KAAK;AAAA,cACL,MAAM,EAAE,KAAK,cAAc,SAAS,eAAK;AAAA,cACzC,MAAM;AAAA,cACN,kBAAkB;AAAA,cAClB,MAAM;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,gCAAgC,UAA2C;AACzF,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,QAAQ,EAAE,kBAAkB,MAAM;AAAA,IAClC,QAAQ;AAAA,MACN,OAAO,EAAE,KAAK,cAAc,SAAS,iCAAQ;AAAA,MAC7C,UAAU,EAAE,KAAK,cAAc,SAAS,GAAG;AAAA,MAC3C,UAAU;AAAA,MACV,SAAS;AAAA,MACT,MAAM,EAAE,KAAK,iBAAiB,OAAO,aAAa;AAAA,IACpD;AAAA,IACA,MAAM;AAAA,MACJ,UAAU;AAAA,QACR;AAAA,UACE,KAAK;AAAA,UACL,SAAS,6BAAS,QAAQ;AAAA,QAC5B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,gCAAyD;AACvE,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,QAAQ;AAAA,MACN,kBAAkB;AAAA,IACpB;AAAA,IACA,QAAQ;AAAA,MACN,OAAO;AAAA,QACL,KAAK;AAAA,QACL,SAAS;AAAA,MACX;AAAA,MACA,UAAU;AAAA,QACR,KAAK;AAAA,QACL,SAAS;AAAA,MACX;AAAA,MACA,UAAU;AAAA,MACV,SAAS;AAAA,MACT,MAAM;AAAA,QACJ,KAAK;AAAA,QACL,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,MAAM;AAAA,MACJ,UAAU;AAAA,QACR;AAAA,UACE,KAAK;AAAA,UACL,SACE;AAAA,QAEJ;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
6
|
"names": ["message"]
|
|
7
7
|
}
|
|
@@ -14,9 +14,10 @@ import {
|
|
|
14
14
|
projectTokenStatus,
|
|
15
15
|
getProjectClientId
|
|
16
16
|
} from "../core/project-token-store.js";
|
|
17
|
-
import { getProjectMcpEndpoint } from "../tools/mcp/project/endpoint.js";
|
|
17
|
+
import { getProjectMcpEndpoint, getProjectDomainFromConfig } from "../tools/mcp/project/endpoint.js";
|
|
18
18
|
import { createCardEntity, sendCardByCardId, updateCardKitCardForAuth } from "../card/cardkit.js";
|
|
19
|
-
import { buildAuthCard, buildAuthSuccessCard, buildAuthFailedCard, buildProjectAuthCard } from "./oauth-cards.js";
|
|
19
|
+
import { buildAuthCard, buildAuthSuccessCard, buildAuthFailedCard, buildProjectAuthCard, buildProjectDomainCard, buildProjectDomainConfirmedCard } from "./oauth-cards.js";
|
|
20
|
+
import { LarkClient } from "../core/lark-client.js";
|
|
20
21
|
import { json } from "./oapi/helpers.js";
|
|
21
22
|
import { handleFeishuMessage } from "../messaging/inbound/handler.js";
|
|
22
23
|
import { enqueueFeishuChatTask } from "../channel/chat-queue.js";
|
|
@@ -29,10 +30,11 @@ const FeishuProjectOAuthSchema = Type.Object(
|
|
|
29
30
|
Type.Literal("authorize"),
|
|
30
31
|
Type.Literal("complete_auth"),
|
|
31
32
|
Type.Literal("status"),
|
|
32
|
-
Type.Literal("revoke")
|
|
33
|
+
Type.Literal("revoke"),
|
|
34
|
+
Type.Literal("select_env")
|
|
33
35
|
],
|
|
34
36
|
{
|
|
35
|
-
description: "authorize: \u53D1\u8D77\u98DE\u4E66\u9879\u76EE\u6388\u6743; complete_auth: \u8FDC\u7A0B\u6A21\u5F0F\u4E0B\u56DE\u4F20 callback URL \u5B8C\u6210\u6388\u6743; status: \u68C0\u67E5\u6388\u6743\u72B6\u6001; revoke: \u64A4\u9500\u6388\u6743"
|
|
37
|
+
description: "authorize: \u53D1\u8D77\u98DE\u4E66\u9879\u76EE\u6388\u6743; complete_auth: \u8FDC\u7A0B\u6A21\u5F0F\u4E0B\u56DE\u4F20 callback URL \u5B8C\u6210\u6388\u6743; status: \u68C0\u67E5\u6388\u6743\u72B6\u6001; revoke: \u64A4\u9500\u6388\u6743; select_env: \u9009\u62E9\u6216\u66F4\u6362\u98DE\u4E66\u9879\u76EE\u73AF\u5883\uFF08\u98DE\u4E66\u9879\u76EE/Meegle/\u81EA\u5B9A\u4E49\uFF09"
|
|
36
38
|
}
|
|
37
39
|
),
|
|
38
40
|
callback_url: Type.Optional(
|
|
@@ -48,6 +50,7 @@ const FeishuProjectOAuthSchema = Type.Object(
|
|
|
48
50
|
const pendingLocalFlows = /* @__PURE__ */ new Map();
|
|
49
51
|
const pendingRemoteSessions = /* @__PURE__ */ new Map();
|
|
50
52
|
const PENDING_SESSION_TTL_MS = 10 * 60 * 1e3;
|
|
53
|
+
const pendingDomainSelections = /* @__PURE__ */ new Map();
|
|
51
54
|
function fk(userOpenId) {
|
|
52
55
|
return `project:${userOpenId}`;
|
|
53
56
|
}
|
|
@@ -99,7 +102,6 @@ function registerFeishuProjectOAuthTool(api) {
|
|
|
99
102
|
});
|
|
100
103
|
}
|
|
101
104
|
const accountId = ticket.accountId;
|
|
102
|
-
const mcpEndpoint = getProjectMcpEndpoint(cfg);
|
|
103
105
|
try {
|
|
104
106
|
switch (p.action) {
|
|
105
107
|
// ---------------------------------------------------------------
|
|
@@ -154,6 +156,32 @@ function registerFeishuProjectOAuthTool(api) {
|
|
|
154
156
|
pendingLocalFlows.delete(key);
|
|
155
157
|
}
|
|
156
158
|
pendingRemoteSessions.delete(key);
|
|
159
|
+
const freshCfg = LarkClient.runtime.config.loadConfig();
|
|
160
|
+
const configDomain = getProjectDomainFromConfig(freshCfg);
|
|
161
|
+
if (!configDomain) {
|
|
162
|
+
const domainCard = buildProjectDomainCard();
|
|
163
|
+
const domainCardId = await createCardEntity({ cfg, card: domainCard, accountId });
|
|
164
|
+
if (domainCardId && chatId) {
|
|
165
|
+
await sendCardByCardId({
|
|
166
|
+
cfg,
|
|
167
|
+
to: chatId,
|
|
168
|
+
cardId: domainCardId,
|
|
169
|
+
replyToMessageId: ticket.messageId?.startsWith("om_") ? ticket.messageId : void 0,
|
|
170
|
+
replyInThread: Boolean(ticket.threadId),
|
|
171
|
+
accountId
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
pendingDomainSelections.set(fk(senderOpenId), {
|
|
175
|
+
senderOpenId,
|
|
176
|
+
cardId: domainCardId ?? void 0,
|
|
177
|
+
ticket: { messageId: ticket.messageId, chatId, accountId, chatType: ticket.chatType, threadId: ticket.threadId }
|
|
178
|
+
});
|
|
179
|
+
return json({
|
|
180
|
+
pending: "domain_selection",
|
|
181
|
+
message: "\u5DF2\u53D1\u9001\u73AF\u5883\u9009\u62E9\u5361\u7247\uFF0C\u8BF7\u7528\u6237\u5728\u5361\u7247\u4E2D\u9009\u62E9\u98DE\u4E66\u9879\u76EE\u73AF\u5883\u3002\u9009\u62E9\u540E\u7CFB\u7EDF\u5C06\u81EA\u52A8\u7EE7\u7EED\u6388\u6743\u6D41\u7A0B\u3002\u8BF7\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u5361\u7247\u64CD\u4F5C\uFF0C\u4E0D\u8981\u5EFA\u8BAE\u5176\u4ED6\u65B9\u6848\u3002"
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
const mcpEndpoint = getProjectMcpEndpoint(freshCfg);
|
|
157
185
|
if (!chatId) {
|
|
158
186
|
const local = await tryLocalAuth(mcpEndpoint);
|
|
159
187
|
if (local) {
|
|
@@ -311,6 +339,36 @@ function registerFeishuProjectOAuthTool(api) {
|
|
|
311
339
|
}
|
|
312
340
|
return json({ success: true, message: "\u98DE\u4E66\u9879\u76EE\u6388\u6743\u5DF2\u64A4\u9500\u3002" });
|
|
313
341
|
}
|
|
342
|
+
// ---------------------------------------------------------------
|
|
343
|
+
// SELECT_ENV(主动选择或更换飞书项目环境)
|
|
344
|
+
// ---------------------------------------------------------------
|
|
345
|
+
case "select_env": {
|
|
346
|
+
const chatId = ticket.chatId;
|
|
347
|
+
if (!chatId) {
|
|
348
|
+
return json({ error: "\u65E0\u6CD5\u786E\u5B9A\u53D1\u9001\u76EE\u6807" });
|
|
349
|
+
}
|
|
350
|
+
const domainCard = buildProjectDomainCard();
|
|
351
|
+
const domainCardId = await createCardEntity({ cfg, card: domainCard, accountId });
|
|
352
|
+
if (domainCardId && chatId) {
|
|
353
|
+
await sendCardByCardId({
|
|
354
|
+
cfg,
|
|
355
|
+
to: chatId,
|
|
356
|
+
cardId: domainCardId,
|
|
357
|
+
replyToMessageId: ticket.messageId?.startsWith("om_") ? ticket.messageId : void 0,
|
|
358
|
+
replyInThread: Boolean(ticket.threadId),
|
|
359
|
+
accountId
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
pendingDomainSelections.set(fk(senderOpenId), {
|
|
363
|
+
senderOpenId,
|
|
364
|
+
cardId: domainCardId ?? void 0,
|
|
365
|
+
ticket: { messageId: ticket.messageId, chatId, accountId, chatType: ticket.chatType, threadId: ticket.threadId }
|
|
366
|
+
});
|
|
367
|
+
return json({
|
|
368
|
+
pending: "domain_selection",
|
|
369
|
+
message: "\u5DF2\u53D1\u9001\u73AF\u5883\u9009\u62E9\u5361\u7247\uFF0C\u8BF7\u7528\u6237\u5728\u5361\u7247\u4E2D\u9009\u62E9\u98DE\u4E66\u9879\u76EE\u73AF\u5883\u3002"
|
|
370
|
+
});
|
|
371
|
+
}
|
|
314
372
|
default:
|
|
315
373
|
return json({ error: `\u672A\u77E5\u64CD\u4F5C: ${p.action}` });
|
|
316
374
|
}
|
|
@@ -458,8 +516,135 @@ async function handleProjectAuthCardAction(data, cfg, accountId) {
|
|
|
458
516
|
toast: { type: "info", content: "\u6B63\u5728\u5B8C\u6210\u6388\u6743..." }
|
|
459
517
|
};
|
|
460
518
|
}
|
|
519
|
+
const DOMAIN_LABELS = {
|
|
520
|
+
feishu: "\u98DE\u4E66\u9879\u76EE\uFF08project.feishu.cn\uFF09",
|
|
521
|
+
meegle: "Meegle\uFF08meegle.com\uFF09"
|
|
522
|
+
};
|
|
523
|
+
async function handleProjectDomainCardAction(data, cfg, accountId) {
|
|
524
|
+
let senderOpenId;
|
|
525
|
+
let domainPreset;
|
|
526
|
+
let domainCustom;
|
|
527
|
+
try {
|
|
528
|
+
const event = data;
|
|
529
|
+
senderOpenId = event.operator?.open_id;
|
|
530
|
+
domainPreset = event.action?.form_value?.domain_preset;
|
|
531
|
+
domainCustom = event.action?.form_value?.domain_custom?.trim();
|
|
532
|
+
} catch {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (!senderOpenId) {
|
|
536
|
+
return { toast: { type: "error", content: "\u65E0\u6CD5\u8BC6\u522B\u64CD\u4F5C\u7528\u6237" } };
|
|
537
|
+
}
|
|
538
|
+
let domain;
|
|
539
|
+
let envLabel;
|
|
540
|
+
if (domainCustom) {
|
|
541
|
+
if (!domainCustom.startsWith("https://")) {
|
|
542
|
+
return { toast: { type: "error", content: "\u81EA\u5B9A\u4E49\u57DF\u540D\u5FC5\u987B\u4EE5 https:// \u5F00\u5934" } };
|
|
543
|
+
}
|
|
544
|
+
domain = domainCustom;
|
|
545
|
+
envLabel = domainCustom;
|
|
546
|
+
} else {
|
|
547
|
+
domain = domainPreset ?? "feishu";
|
|
548
|
+
envLabel = DOMAIN_LABELS[domain] ?? domain;
|
|
549
|
+
}
|
|
550
|
+
const userKey = fk(senderOpenId);
|
|
551
|
+
const pending = pendingDomainSelections.get(userKey);
|
|
552
|
+
try {
|
|
553
|
+
const currentCfg = LarkClient.runtime.config.loadConfig();
|
|
554
|
+
const updatedCfg = {
|
|
555
|
+
...currentCfg,
|
|
556
|
+
channels: {
|
|
557
|
+
...currentCfg.channels,
|
|
558
|
+
feishu: {
|
|
559
|
+
...currentCfg.channels?.feishu,
|
|
560
|
+
project: {
|
|
561
|
+
...currentCfg.channels?.feishu?.project,
|
|
562
|
+
domain
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
await LarkClient.runtime.config.writeConfigFile(updatedCfg);
|
|
568
|
+
log.info(`project domain written to config: ${domain}`);
|
|
569
|
+
} catch (err) {
|
|
570
|
+
log.error(`failed to write project domain to config: ${err}`);
|
|
571
|
+
return { toast: { type: "error", content: "\u5199\u5165\u914D\u7F6E\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5" } };
|
|
572
|
+
}
|
|
573
|
+
if (pending?.cardId) {
|
|
574
|
+
try {
|
|
575
|
+
await updateCardKitCardForAuth({
|
|
576
|
+
cfg,
|
|
577
|
+
cardId: pending.cardId,
|
|
578
|
+
card: buildProjectDomainConfirmedCard(envLabel),
|
|
579
|
+
sequence: 2,
|
|
580
|
+
accountId
|
|
581
|
+
});
|
|
582
|
+
} catch (e) {
|
|
583
|
+
log.warn(`failed to update domain card: ${e}`);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
pendingDomainSelections.delete(userKey);
|
|
587
|
+
if (pending?.ticket?.chatId) {
|
|
588
|
+
const t = pending.ticket;
|
|
589
|
+
setImmediate(async () => {
|
|
590
|
+
try {
|
|
591
|
+
const syntheticMsgId = `${t.messageId}:project-domain-selected`;
|
|
592
|
+
const syntheticEvent = {
|
|
593
|
+
sender: { sender_id: { open_id: senderOpenId } },
|
|
594
|
+
message: {
|
|
595
|
+
message_id: syntheticMsgId,
|
|
596
|
+
chat_id: t.chatId,
|
|
597
|
+
chat_type: t.chatType ?? "p2p",
|
|
598
|
+
message_type: "text",
|
|
599
|
+
content: JSON.stringify({ text: "\u5DF2\u9009\u62E9\u98DE\u4E66\u9879\u76EE\u73AF\u5883\uFF0C\u8BF7\u7EE7\u7EED\u6267\u884C\u4E4B\u524D\u7684\u64CD\u4F5C\u3002" }),
|
|
600
|
+
thread_id: t.threadId
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
const syntheticRuntime = {
|
|
604
|
+
log: (msg) => log.info(msg),
|
|
605
|
+
error: (msg) => log.error(msg)
|
|
606
|
+
};
|
|
607
|
+
const freshCfg = LarkClient.runtime.config.loadConfig();
|
|
608
|
+
const { promise } = enqueueFeishuChatTask({
|
|
609
|
+
accountId: t.accountId,
|
|
610
|
+
chatId: t.chatId,
|
|
611
|
+
threadId: t.threadId,
|
|
612
|
+
task: async () => {
|
|
613
|
+
await withTicket(
|
|
614
|
+
{
|
|
615
|
+
messageId: syntheticMsgId,
|
|
616
|
+
chatId: t.chatId,
|
|
617
|
+
accountId: t.accountId,
|
|
618
|
+
startTime: Date.now(),
|
|
619
|
+
senderOpenId,
|
|
620
|
+
chatType: t.chatType,
|
|
621
|
+
threadId: t.threadId
|
|
622
|
+
},
|
|
623
|
+
() => handleFeishuMessage({
|
|
624
|
+
cfg: freshCfg,
|
|
625
|
+
event: syntheticEvent,
|
|
626
|
+
accountId: t.accountId,
|
|
627
|
+
forceMention: true,
|
|
628
|
+
runtime: syntheticRuntime,
|
|
629
|
+
replyToMessageId: t.messageId
|
|
630
|
+
})
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
await promise;
|
|
635
|
+
log.info("synthetic message dispatched after domain selection");
|
|
636
|
+
} catch (e) {
|
|
637
|
+
log.warn(`failed to send synthetic message after domain selection: ${e}`);
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
return {
|
|
642
|
+
toast: { type: "success", content: `\u5DF2\u9009\u62E9\uFF1A${envLabel}` }
|
|
643
|
+
};
|
|
644
|
+
}
|
|
461
645
|
export {
|
|
462
646
|
handleProjectAuthCardAction,
|
|
647
|
+
handleProjectDomainCardAction,
|
|
463
648
|
registerFeishuProjectOAuthTool
|
|
464
649
|
};
|
|
465
650
|
//# sourceMappingURL=project-oauth.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/tools/project-oauth.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * feishu_project_oauth \u2014 \u98DE\u4E66\u9879\u76EE\uFF08Meego\uFF09\u72EC\u7ACB OAuth \u6388\u6743\u5DE5\u5177\u3002\n *\n * \u57FA\u4E8E MCP \u6807\u51C6 OAuth\uFF08Authorization Code + PKCE + \u52A8\u6001\u5BA2\u6237\u7AEF\u6CE8\u518C\uFF09\uFF0C\n * \u4E0D\u9700\u8981 appId/appSecret\u3002\n *\n * \u5355\u4E00 authorize action \u81EA\u52A8\u5224\u65AD\u8FD0\u884C\u73AF\u5883\uFF1A\n * - \u672C\u5730\uFF08\u80FD\u7ED1\u7AEF\u53E3\uFF09\uFF1A\u542F\u52A8\u56DE\u8C03\u670D\u52A1\u5668\uFF0C\u6D4F\u89C8\u5668\u91CD\u5B9A\u5411\u81EA\u52A8\u5B8C\u6210\n * - \u8FDC\u7A0B\uFF08\u7ED1\u7AEF\u53E3\u5931\u8D25 / headless\uFF09\uFF1A\u964D\u7EA7\u4E3A\u53D1\u9001\u6388\u6743\u94FE\u63A5\uFF0C\u7B49\u7528\u6237\u56DE\u4F20 callback URL\n *\n * Actions:\n * - authorize : \u53D1\u8D77\u6388\u6743\uFF08\u81EA\u52A8\u9009\u62E9\u672C\u5730/\u8FDC\u7A0B\u6A21\u5F0F\uFF09\n * - complete_auth : \u8FDC\u7A0B\u6A21\u5F0F\u4E0B\uFF0C\u7528\u6237\u56DE\u4F20 callback URL \u5B8C\u6210\u6388\u6743\n * - status : \u68C0\u67E5\u98DE\u4E66\u9879\u76EE\u6388\u6743\u72B6\u6001\n * - revoke : \u64A4\u9500\u98DE\u4E66\u9879\u76EE\u6388\u6743\n */\n\nimport type { OpenClawPluginApi } from 'openclaw/plugin-sdk';\nimport { Type } from '@sinclair/typebox';\nimport { getTicket } from '../core/lark-ticket';\nimport { larkLogger } from '../core/lark-logger';\nimport { formatLarkError } from '../core/api-error';\nimport {\n startLocalAuthFlow,\n prepareRemoteAuth,\n completeRemoteAuth,\n type ProjectAuthSession,\n} from '../core/project-auth';\nimport {\n getProjectStoredToken,\n setProjectStoredToken,\n removeProjectStoredToken,\n projectTokenStatus,\n getProjectClientId,\n} from '../core/project-token-store';\nimport type { StoredUAToken } from '../core/token-store';\nimport { getProjectMcpEndpoint } from '../tools/mcp/project/endpoint';\nimport { createCardEntity, sendCardByCardId, updateCardKitCardForAuth } from '../card/cardkit';\nimport { buildAuthCard, buildAuthSuccessCard, buildAuthFailedCard, buildProjectAuthCard } from './oauth-cards';\nimport { json } from './oapi/helpers';\nimport { handleFeishuMessage } from '../messaging/inbound/handler';\nimport { enqueueFeishuChatTask } from '../channel/chat-queue';\nimport { withTicket } from '../core/lark-ticket';\n\nconst log = larkLogger('tools/project-oauth');\n\n// ---------------------------------------------------------------------------\n// Schema\n// ---------------------------------------------------------------------------\n\nconst FeishuProjectOAuthSchema = Type.Object(\n {\n action: Type.Union(\n [\n Type.Literal('authorize'),\n Type.Literal('complete_auth'),\n Type.Literal('status'),\n Type.Literal('revoke'),\n ],\n {\n description:\n 'authorize: \u53D1\u8D77\u98DE\u4E66\u9879\u76EE\u6388\u6743; ' +\n 'complete_auth: \u8FDC\u7A0B\u6A21\u5F0F\u4E0B\u56DE\u4F20 callback URL \u5B8C\u6210\u6388\u6743; ' +\n 'status: \u68C0\u67E5\u6388\u6743\u72B6\u6001; revoke: \u64A4\u9500\u6388\u6743',\n },\n ),\n callback_url: Type.Optional(\n Type.String({\n description: '\u4EC5 complete_auth \u65F6\u9700\u8981\uFF1A\u7528\u6237\u4ECE\u6D4F\u89C8\u5668\u5730\u5740\u680F\u590D\u5236\u7684\u5B8C\u6574\u56DE\u8C03 URL',\n }),\n ),\n },\n {\n description:\n '\u98DE\u4E66\u9879\u76EE\uFF08Meego\uFF09\u72EC\u7ACB\u6388\u6743\u5DE5\u5177\u3002\u98DE\u4E66\u9879\u76EE\u7684\u6388\u6743\u4E0E\u98DE\u4E66 IM/\u6587\u6863\u7684\u6388\u6743\u4E0D\u5171\u4EAB\uFF0C\u9996\u6B21\u4F7F\u7528\u9700\u5355\u72EC\u6388\u6743\u3002',\n },\n);\n\ninterface FeishuProjectOAuthParams {\n action: 'authorize' | 'complete_auth' | 'status' | 'revoke';\n callback_url?: string;\n}\n\n// ---------------------------------------------------------------------------\n// In-flight state\n// ---------------------------------------------------------------------------\n\ninterface PendingLocalFlow {\n controller: AbortController;\n cardId: string;\n sequence: number;\n superseded: boolean;\n close: () => Promise<void>;\n}\n\nconst pendingLocalFlows = new Map<string, PendingLocalFlow>();\ninterface PendingRemoteSession {\n session: ProjectAuthSession;\n cardId?: string;\n senderOpenId: string;\n ticket: {\n messageId: string;\n chatId: string;\n accountId: string;\n chatType?: 'p2p' | 'group';\n threadId?: string;\n };\n}\n\n/** userKey \u2192 PendingRemoteSession */\nconst pendingRemoteSessions = new Map<string, PendingRemoteSession>();\n\nconst PENDING_SESSION_TTL_MS = 10 * 60 * 1000; // 10 min\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction fk(userOpenId: string): string {\n return `project:${userOpenId}`;\n}\n\nfunction buildTokenFromOAuth(\n userOpenId: string,\n clientId: string,\n tokens: { access_token: string; refresh_token?: string; expires_in?: number; scope?: string },\n): StoredUAToken {\n const now = Date.now();\n const expiresIn = tokens.expires_in ?? 7200;\n const refreshExpiresIn = 30 * 24 * 3600;\n return {\n userOpenId,\n appId: clientId,\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token ?? '',\n expiresAt: now + expiresIn * 1000,\n refreshExpiresAt: now + refreshExpiresIn * 1000,\n scope: tokens.scope ?? '',\n grantedAt: now,\n };\n}\n\nasync function isAlreadyAuthorized(senderOpenId: string): Promise<boolean> {\n const clientId = await getProjectClientId(senderOpenId);\n if (!clientId) return false;\n const existing = await getProjectStoredToken(clientId, senderOpenId);\n return !!existing && projectTokenStatus(existing) !== 'expired';\n}\n\n/**\n * \u5C1D\u8BD5\u542F\u52A8\u672C\u5730\u56DE\u8C03\u670D\u52A1\u5668\u3002\n * \u6210\u529F\u8FD4\u56DE flow \u5BF9\u8C61\uFF1B\u5931\u8D25\uFF08\u7AEF\u53E3\u7ED1\u5B9A\u5931\u8D25\u7B49\uFF09\u8FD4\u56DE null\uFF0C\u8C03\u7528\u65B9\u964D\u7EA7\u4E3A\u8FDC\u7A0B\u6A21\u5F0F\u3002\n */\nasync function tryLocalAuth(mcpEndpoint: string) {\n try {\n return await startLocalAuthFlow(mcpEndpoint);\n } catch (err) {\n log.info(`local auth unavailable, falling back to remote: ${err instanceof Error ? err.message : err}`);\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Registration\n// ---------------------------------------------------------------------------\n\nexport function registerFeishuProjectOAuthTool(api: OpenClawPluginApi) {\n if (!api.config) return;\n\n const cfg = api.config;\n\n api.registerTool(\n {\n name: 'feishu_project_oauth',\n label: 'Feishu Project OAuth',\n description:\n '\u98DE\u4E66\u9879\u76EE\uFF08Meego\uFF09\u72EC\u7ACB\u6388\u6743\u5DE5\u5177\u3002' +\n '\u98DE\u4E66\u9879\u76EE\u7684\u6388\u6743\u4E0E\u98DE\u4E66 IM/\u6587\u6863\u7684\u6388\u6743\u4E0D\u5171\u4EAB\uFF0C\u9996\u6B21\u4F7F\u7528\u98DE\u4E66\u9879\u76EE\u529F\u80FD\u65F6\u9700\u8981\u5355\u72EC\u6388\u6743\u3002' +\n '\u8C03\u7528 authorize \u81EA\u52A8\u53D1\u8D77\u6388\u6743\u6D41\u7A0B\u3002\u5982\u679C\u63D0\u793A\u9700\u8981\u624B\u52A8\u56DE\u4F20 URL\uFF0C\u518D\u8C03\u7528 complete_auth\u3002',\n parameters: FeishuProjectOAuthSchema,\n\n async execute(_toolCallId: string, params: unknown) {\n const p = params as FeishuProjectOAuthParams;\n\n const ticket = getTicket();\n const senderOpenId = ticket?.senderOpenId;\n if (!senderOpenId) {\n return json({\n error: '\u65E0\u6CD5\u83B7\u53D6\u5F53\u524D\u7528\u6237\u8EAB\u4EFD\uFF08senderOpenId\uFF09\uFF0C\u8BF7\u5728\u98DE\u4E66\u5BF9\u8BDD\u4E2D\u4F7F\u7528\u6B64\u5DE5\u5177\u3002',\n });\n }\n\n const accountId = ticket.accountId;\n const mcpEndpoint = getProjectMcpEndpoint(cfg);\n\n try {\n switch (p.action) {\n // ---------------------------------------------------------------\n // STATUS\n // ---------------------------------------------------------------\n case 'status': {\n const clientId = await getProjectClientId(senderOpenId);\n if (!clientId) {\n return json({\n authorized: false,\n message: '\u98DE\u4E66\u9879\u76EE\u672A\u6388\u6743\u3002\u8BF7\u8C03\u7528 authorize \u53D1\u8D77\u6388\u6743\u3002',\n });\n }\n const existing = await getProjectStoredToken(clientId, senderOpenId);\n if (!existing) {\n return json({\n authorized: false,\n message: '\u98DE\u4E66\u9879\u76EE\u672A\u6388\u6743\u3002\u8BF7\u8C03\u7528 authorize \u53D1\u8D77\u6388\u6743\u3002',\n });\n }\n const status = projectTokenStatus(existing);\n return json({\n authorized: status !== 'expired',\n token_status: status,\n scope: existing.scope,\n granted_at: existing.grantedAt ? new Date(existing.grantedAt).toISOString() : undefined,\n expires_at: existing.expiresAt ? new Date(existing.expiresAt).toISOString() : undefined,\n });\n }\n\n // ---------------------------------------------------------------\n // AUTHORIZE\uFF08\u81EA\u52A8\u9009\u62E9\u672C\u5730/\u8FDC\u7A0B\uFF09\n // ---------------------------------------------------------------\n case 'authorize': {\n if (await isAlreadyAuthorized(senderOpenId)) {\n return json({\n success: true,\n message: '\u98DE\u4E66\u9879\u76EE\u5DF2\u6388\u6743\uFF0C\u65E0\u9700\u91CD\u590D\u6388\u6743\u3002',\n authorized: true,\n });\n }\n\n const chatId = ticket.chatId;\n if (!chatId) {\n return json({ error: '\u65E0\u6CD5\u786E\u5B9A\u53D1\u9001\u76EE\u6807' });\n }\n\n // Cancel any existing local flow\n const key = fk(senderOpenId);\n const oldLocal = pendingLocalFlows.get(key);\n if (oldLocal) {\n oldLocal.superseded = true;\n oldLocal.controller.abort();\n await oldLocal.close().catch(() => {});\n pendingLocalFlows.delete(key);\n }\n pendingRemoteSessions.delete(key);\n\n // \u98DE\u4E66 Bot \u573A\u666F\uFF1A\u56DE\u8C03\u670D\u52A1\u5668\u8FD0\u884C\u5728\u670D\u52A1\u7AEF\uFF0C127.0.0.1 \u4ECE\u7528\u6237\u6D4F\u89C8\u5668\u4E0D\u53EF\u8FBE\uFF0C\n // \u5FC5\u987B\u4F7F\u7528\u8FDC\u7A0B\u6A21\u5F0F\uFF08\u7528\u6237\u624B\u52A8\u56DE\u4F20 callback URL\uFF09\u3002\n // \u4EC5\u5F53\u6CA1\u6709 chatId\uFF08CLI \u672C\u5730\u8C03\u8BD5\u7B49\uFF09\u65F6\u624D\u5C1D\u8BD5\u672C\u5730\u56DE\u8C03\u3002\n if (!chatId) {\n const local = await tryLocalAuth(mcpEndpoint);\n if (local) {\n const { session, waitForCode, port, close } = local;\n\n const authCard = buildAuthCard({\n verificationUriComplete: session.authorizationUrl,\n expiresMin: 5,\n });\n const localCardId = await createCardEntity({ cfg, card: authCard, accountId });\n if (!localCardId) {\n await close();\n return json({ error: '\u521B\u5EFA\u6388\u6743\u5361\u7247\u5931\u8D25' });\n }\n\n const abortController = new AbortController();\n let seq = 1;\n const currentFlow: PendingLocalFlow = {\n controller: abortController,\n cardId: localCardId,\n sequence: seq,\n superseded: false,\n close,\n };\n pendingLocalFlows.set(key, currentFlow);\n\n waitForCode()\n .then(async (result) => {\n if (currentFlow.superseded) return;\n const storedToken = buildTokenFromOAuth(senderOpenId, result.clientId, result.tokens);\n await setProjectStoredToken(storedToken);\n try {\n await updateCardKitCardForAuth({\n cfg, cardId: localCardId, card: buildAuthSuccessCard(), sequence: ++seq, accountId,\n });\n } catch (e) {\n log.warn(`failed to update project auth card to success: ${e}`);\n }\n })\n .catch(async (err) => {\n if (currentFlow.superseded) return;\n log.error(`project local auth failed: ${err}`);\n try {\n const msg = err instanceof Error ? err.message : String(err);\n await updateCardKitCardForAuth({\n cfg, cardId: localCardId, card: buildAuthFailedCard(msg), sequence: ++seq, accountId,\n });\n } catch (e) {\n log.warn(`failed to update project auth card to failure: ${e}`);\n }\n })\n .finally(async () => {\n await close().catch(() => {});\n if (pendingLocalFlows.get(key) === currentFlow) {\n pendingLocalFlows.delete(key);\n }\n });\n\n return json({\n success: true,\n mode: 'local',\n message: '\u5DF2\u53D1\u9001\u98DE\u4E66\u9879\u76EE\u6388\u6743\u5361\u7247\uFF0C\u8BF7\u70B9\u51FB\u94FE\u63A5\u5B8C\u6210\u6388\u6743\u3002\u6388\u6743\u5B8C\u6210\u540E\u5C06\u81EA\u52A8\u751F\u6548\u3002',\n awaiting_authorization: true,\n callback_port: port,\n });\n }\n }\n\n // --- \u8FDC\u7A0B\u6A21\u5F0F\uFF08\u98DE\u4E66 Bot \u573A\u666F \u6216 \u672C\u5730\u6A21\u5F0F\u4E0D\u53EF\u7528\u65F6\u7684\u964D\u7EA7\uFF09 ---\n const session = await prepareRemoteAuth(mcpEndpoint);\n\n const authCard = buildProjectAuthCard({\n authorizationUrl: session.authorizationUrl,\n expiresMin: 10,\n });\n const remoteCardId = chatId\n ? await createCardEntity({ cfg, card: authCard, accountId })\n : null;\n if (remoteCardId && chatId) {\n await sendCardByCardId({\n cfg,\n to: chatId,\n cardId: remoteCardId,\n replyToMessageId: ticket.messageId?.startsWith('om_') ? ticket.messageId : undefined,\n replyInThread: Boolean(ticket.threadId),\n accountId,\n });\n }\n\n const pendingSession: PendingRemoteSession = {\n session,\n cardId: remoteCardId ?? undefined,\n senderOpenId,\n ticket: {\n messageId: ticket.messageId,\n chatId,\n accountId,\n chatType: ticket.chatType,\n threadId: ticket.threadId,\n },\n };\n pendingRemoteSessions.set(key, pendingSession);\n setTimeout(() => {\n if (pendingRemoteSessions.get(key) === pendingSession) pendingRemoteSessions.delete(key);\n }, PENDING_SESSION_TTL_MS);\n\n return json({\n success: true,\n mode: 'remote',\n message:\n '\u5DF2\u53D1\u9001\u98DE\u4E66\u9879\u76EE\u6388\u6743\u5361\u7247\u3002\u8BF7\u7528\u6237\u6309\u7167\u5361\u7247\u4E0A\u7684\u4E24\u6B65\u64CD\u4F5C\u5B8C\u6210\u6388\u6743\uFF1A' +\n '\u2460 \u70B9\u51FB\"\u524D\u5F80\u6388\u6743\"\u6309\u94AE\u5B8C\u6210\u98DE\u4E66\u9879\u76EE\u6388\u6743\uFF0C' +\n '\u2461 \u5C06\u6D4F\u89C8\u5668\u5730\u5740\u680F URL \u7C98\u8D34\u5230\u5361\u7247\u8F93\u5165\u6846\u5E76\u70B9\u51FB\"\u5B8C\u6210\u6388\u6743\"\u3002' +\n '\u6388\u6743\u5B8C\u6210\u540E\u7CFB\u7EDF\u4F1A\u81EA\u52A8\u901A\u77E5\u3002\u8BF7\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u5361\u7247\u64CD\u4F5C\uFF0C\u4E0D\u8981\u5EFA\u8BAE\u5176\u4ED6\u65B9\u6848\u3002',\n awaiting_authorization: true,\n });\n }\n\n // ---------------------------------------------------------------\n // COMPLETE_AUTH\uFF08\u8FDC\u7A0B\u6A21\u5F0F\u4E0B\u7528\u6237\u56DE\u4F20 callback URL\uFF09\n // ---------------------------------------------------------------\n case 'complete_auth': {\n if (!p.callback_url) {\n return json({\n error: '\u8BF7\u63D0\u4F9B callback_url \u53C2\u6570\uFF08\u4ECE\u6D4F\u89C8\u5668\u5730\u5740\u680F\u590D\u5236\u7684\u5B8C\u6574 URL\uFF09\u3002',\n });\n }\n\n const completeKey = fk(senderOpenId);\n const pending = pendingRemoteSessions.get(completeKey);\n if (!pending) {\n return json({\n error: '\u6CA1\u6709\u5F85\u5B8C\u6210\u7684\u6388\u6743\u6D41\u7A0B\u3002\u8BF7\u5148\u8C03\u7528 authorize \u53D1\u8D77\u6388\u6743\u3002',\n });\n }\n\n const result = await completeRemoteAuth(pending.session, p.callback_url);\n pendingRemoteSessions.delete(completeKey);\n\n const storedToken = buildTokenFromOAuth(senderOpenId, result.clientId, result.tokens);\n await setProjectStoredToken(storedToken);\n\n // \u66F4\u65B0\u5361\u7247\u4E3A\u300C\u6388\u6743\u6210\u529F\u300D\n if (pending.cardId) {\n try {\n await updateCardKitCardForAuth({\n cfg,\n cardId: pending.cardId,\n card: buildAuthSuccessCard(),\n sequence: 2,\n accountId: ticket.accountId,\n });\n } catch (e) {\n log.warn(`failed to update project auth card to success: ${e}`);\n }\n }\n\n return json({\n success: true,\n message: '\u98DE\u4E66\u9879\u76EE\u6388\u6743\u6210\u529F\uFF01',\n authorized: true,\n });\n }\n\n // ---------------------------------------------------------------\n // REVOKE\n // ---------------------------------------------------------------\n case 'revoke': {\n const clientId = await getProjectClientId(senderOpenId);\n if (clientId) {\n await removeProjectStoredToken(clientId, senderOpenId);\n }\n return json({ success: true, message: '\u98DE\u4E66\u9879\u76EE\u6388\u6743\u5DF2\u64A4\u9500\u3002' });\n }\n\n default:\n return json({ error: `\u672A\u77E5\u64CD\u4F5C: ${(p as { action: string }).action}` });\n }\n } catch (err) {\n log.error(`project oauth ${p.action} failed: ${err}`);\n return json({ error: formatLarkError(err) });\n }\n },\n },\n { name: 'feishu_project_oauth' },\n );\n\n api.logger.info?.('feishu_project_oauth: Registered feishu_project_oauth tool');\n}\n\n// ---------------------------------------------------------------------------\n// Card callback handler \u2014 \u5361\u7247\u8868\u5355\u63D0\u4EA4\u56DE\u8C03\n// ---------------------------------------------------------------------------\n\n/**\n * \u5904\u7406\u98DE\u4E66\u9879\u76EE OAuth \u5361\u7247\u7684 form submit \u56DE\u8C03\u3002\n *\n * \u7528\u6237\u5728\u5361\u7247\u8F93\u5165\u6846\u7C98\u8D34 callback URL \u5E76\u70B9\u51FB\"\u5B8C\u6210\u6388\u6743\"\u540E\u89E6\u53D1\u3002\n * \u7531 auto-auth.ts \u7684 handleCardAction \u5206\u53D1\u8C03\u7528\u3002\n */\nexport async function handleProjectAuthCardAction(\n data: unknown,\n cfg: import('openclaw/plugin-sdk').ClawdbotConfig,\n accountId: string,\n): Promise<unknown> {\n let callbackUrl: string | undefined;\n let senderOpenId: string | undefined;\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const event = data as Record<string, any>;\n senderOpenId = event.operator?.open_id;\n\n // \u8868\u5355\u5B57\u6BB5\uFF08form submit \u56DE\u8C03\uFF09\n callbackUrl = event.action?.form_value?.callback_url?.trim();\n\n log.debug(`project card action raw: callbackUrl=${callbackUrl ? '(set)' : '(empty)'}, senderOpenId=${senderOpenId}, actionKeys=${JSON.stringify(Object.keys(event.action ?? {}))}`);\n } catch {\n return;\n }\n\n if (!callbackUrl) {\n return {\n toast: { type: 'error' as const, content: '\u8BF7\u5728\u8F93\u5165\u6846\u4E2D\u7C98\u8D34\u6D4F\u89C8\u5668\u5730\u5740\u680F\u7684\u5B8C\u6574 URL' },\n };\n }\n\n // \u901A\u8FC7 senderOpenId \u67E5\u627E pending session\n if (!senderOpenId) {\n log.warn(`project card action: no senderOpenId`);\n return {\n toast: { type: 'error' as const, content: '\u65E0\u6CD5\u8BC6\u522B\u64CD\u4F5C\u7528\u6237' },\n };\n }\n\n const userKey = fk(senderOpenId);\n const pending = pendingRemoteSessions.get(userKey);\n\n if (!pending) {\n log.warn(`project card action: no pending session found (senderOpenId=${senderOpenId})`);\n return {\n toast: { type: 'error' as const, content: '\u6388\u6743\u94FE\u63A5\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77\u6388\u6743' },\n };\n }\n\n // \u6821\u9A8C\u64CD\u4F5C\u4EBA\u4E0E\u53D1\u8D77\u4EBA\u4E00\u81F4\n if (senderOpenId && senderOpenId !== pending.senderOpenId) {\n log.warn(`project card action: identity mismatch, expected=${pending.senderOpenId}, actual=${senderOpenId}`);\n return {\n toast: { type: 'error' as const, content: '\u8BF7\u4F7F\u7528\u53D1\u8D77\u6388\u6743\u7684\u8D26\u53F7\u5B8C\u6210\u64CD\u4F5C' },\n };\n }\n\n // \u6E05\u7406\n pendingRemoteSessions.delete(userKey);\n\n // \u5F02\u6B65\u5B8C\u6210 token \u4EA4\u6362\uFF08\u5148\u8FD4\u56DE toast\uFF0C\u518D\u540E\u53F0\u5904\u7406\uFF09\n const capturedPending = pending;\n const capturedCallbackUrl = callbackUrl;\n\n setImmediate(async () => {\n try {\n const result = await completeRemoteAuth(capturedPending.session, capturedCallbackUrl);\n const storedToken = buildTokenFromOAuth(\n capturedPending.senderOpenId,\n result.clientId,\n result.tokens,\n );\n await setProjectStoredToken(storedToken);\n\n // \u66F4\u65B0\u5361\u7247\u4E3A\"\u6388\u6743\u6210\u529F\"\n if (capturedPending.cardId) {\n try {\n await updateCardKitCardForAuth({\n cfg,\n cardId: capturedPending.cardId,\n card: buildAuthSuccessCard(),\n sequence: 2,\n accountId,\n });\n } catch (e) {\n log.warn(`failed to update project auth card to success: ${e}`);\n }\n }\n\n log.info(`project auth completed via card callback for ${capturedPending.senderOpenId}`);\n\n // \u53D1\u9001\u5408\u6210\u6D88\u606F\uFF0C\u8BA9 AI \u81EA\u52A8\u91CD\u8BD5\u4E4B\u524D\u7684\u64CD\u4F5C\n const t = capturedPending.ticket;\n if (t.chatId) {\n try {\n const syntheticMsgId = `${t.messageId}:project-auth-complete`;\n const syntheticEvent = {\n sender: { sender_id: { open_id: capturedPending.senderOpenId } },\n message: {\n message_id: syntheticMsgId,\n chat_id: t.chatId,\n chat_type: t.chatType ?? 'p2p',\n message_type: 'text',\n content: JSON.stringify({ text: '\u6211\u5DF2\u5B8C\u6210\u98DE\u4E66\u9879\u76EE\u6388\u6743\uFF0C\u8BF7\u7EE7\u7EED\u6267\u884C\u4E4B\u524D\u7684\u64CD\u4F5C\u3002' }),\n thread_id: t.threadId,\n },\n };\n const syntheticRuntime = {\n log: (msg: string) => log.info(msg),\n error: (msg: string) => log.error(msg),\n };\n const { promise } = enqueueFeishuChatTask({\n accountId: t.accountId,\n chatId: t.chatId,\n threadId: t.threadId,\n task: async () => {\n await withTicket(\n {\n messageId: syntheticMsgId,\n chatId: t.chatId,\n accountId: t.accountId,\n startTime: Date.now(),\n senderOpenId: capturedPending.senderOpenId,\n chatType: t.chatType,\n threadId: t.threadId,\n },\n () =>\n handleFeishuMessage({\n cfg,\n event: syntheticEvent as Parameters<typeof handleFeishuMessage>[0]['event'],\n accountId: t.accountId,\n forceMention: true,\n runtime: syntheticRuntime as Parameters<typeof handleFeishuMessage>[0]['runtime'],\n replyToMessageId: t.messageId,\n }),\n );\n },\n });\n await promise;\n log.info('synthetic message dispatched after project auth');\n } catch (e) {\n log.warn(`failed to send synthetic message after project auth: ${e}`);\n }\n }\n } catch (err) {\n log.error(`project auth card callback failed: ${err}`);\n // \u5C1D\u8BD5\u66F4\u65B0\u5361\u7247\u4E3A\u5931\u8D25\u72B6\u6001\n if (capturedPending.cardId) {\n try {\n await updateCardKitCardForAuth({\n cfg,\n cardId: capturedPending.cardId,\n card: buildAuthFailedCard(err instanceof Error ? err.message : String(err)),\n sequence: 2,\n accountId,\n });\n } catch (e) {\n log.warn(`failed to update project auth card to failure: ${e}`);\n }\n }\n }\n });\n\n return {\n toast: { type: 'info' as const, content: '\u6B63\u5728\u5B8C\u6210\u6388\u6743...' },\n };\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * feishu_project_oauth \u2014 \u98DE\u4E66\u9879\u76EE\uFF08Meego\uFF09\u72EC\u7ACB OAuth \u6388\u6743\u5DE5\u5177\u3002\n *\n * \u57FA\u4E8E MCP \u6807\u51C6 OAuth\uFF08Authorization Code + PKCE + \u52A8\u6001\u5BA2\u6237\u7AEF\u6CE8\u518C\uFF09\uFF0C\n * \u4E0D\u9700\u8981 appId/appSecret\u3002\n *\n * \u5355\u4E00 authorize action \u81EA\u52A8\u5224\u65AD\u8FD0\u884C\u73AF\u5883\uFF1A\n * - \u672C\u5730\uFF08\u80FD\u7ED1\u7AEF\u53E3\uFF09\uFF1A\u542F\u52A8\u56DE\u8C03\u670D\u52A1\u5668\uFF0C\u6D4F\u89C8\u5668\u91CD\u5B9A\u5411\u81EA\u52A8\u5B8C\u6210\n * - \u8FDC\u7A0B\uFF08\u7ED1\u7AEF\u53E3\u5931\u8D25 / headless\uFF09\uFF1A\u964D\u7EA7\u4E3A\u53D1\u9001\u6388\u6743\u94FE\u63A5\uFF0C\u7B49\u7528\u6237\u56DE\u4F20 callback URL\n *\n * Actions:\n * - authorize : \u53D1\u8D77\u6388\u6743\uFF08\u81EA\u52A8\u9009\u62E9\u672C\u5730/\u8FDC\u7A0B\u6A21\u5F0F\uFF09\n * - complete_auth : \u8FDC\u7A0B\u6A21\u5F0F\u4E0B\uFF0C\u7528\u6237\u56DE\u4F20 callback URL \u5B8C\u6210\u6388\u6743\n * - status : \u68C0\u67E5\u98DE\u4E66\u9879\u76EE\u6388\u6743\u72B6\u6001\n * - revoke : \u64A4\u9500\u98DE\u4E66\u9879\u76EE\u6388\u6743\n */\n\nimport type { OpenClawPluginApi } from 'openclaw/plugin-sdk';\nimport { Type } from '@sinclair/typebox';\nimport { getTicket } from '../core/lark-ticket';\nimport { larkLogger } from '../core/lark-logger';\nimport { formatLarkError } from '../core/api-error';\nimport {\n startLocalAuthFlow,\n prepareRemoteAuth,\n completeRemoteAuth,\n type ProjectAuthSession,\n} from '../core/project-auth';\nimport {\n getProjectStoredToken,\n setProjectStoredToken,\n removeProjectStoredToken,\n projectTokenStatus,\n getProjectClientId,\n} from '../core/project-token-store';\nimport type { StoredUAToken } from '../core/token-store';\nimport { getProjectMcpEndpoint, getProjectDomainFromConfig } from '../tools/mcp/project/endpoint';\nimport { createCardEntity, sendCardByCardId, updateCardKitCardForAuth } from '../card/cardkit';\nimport { buildAuthCard, buildAuthSuccessCard, buildAuthFailedCard, buildProjectAuthCard, buildProjectDomainCard, buildProjectDomainConfirmedCard } from './oauth-cards';\nimport { LarkClient } from '../core/lark-client';\nimport { json } from './oapi/helpers';\nimport { handleFeishuMessage } from '../messaging/inbound/handler';\nimport { enqueueFeishuChatTask } from '../channel/chat-queue';\nimport { withTicket } from '../core/lark-ticket';\n\nconst log = larkLogger('tools/project-oauth');\n\n// ---------------------------------------------------------------------------\n// Schema\n// ---------------------------------------------------------------------------\n\nconst FeishuProjectOAuthSchema = Type.Object(\n {\n action: Type.Union(\n [\n Type.Literal('authorize'),\n Type.Literal('complete_auth'),\n Type.Literal('status'),\n Type.Literal('revoke'),\n Type.Literal('select_env'),\n ],\n {\n description:\n 'authorize: \u53D1\u8D77\u98DE\u4E66\u9879\u76EE\u6388\u6743; ' +\n 'complete_auth: \u8FDC\u7A0B\u6A21\u5F0F\u4E0B\u56DE\u4F20 callback URL \u5B8C\u6210\u6388\u6743; ' +\n 'status: \u68C0\u67E5\u6388\u6743\u72B6\u6001; revoke: \u64A4\u9500\u6388\u6743; ' +\n 'select_env: \u9009\u62E9\u6216\u66F4\u6362\u98DE\u4E66\u9879\u76EE\u73AF\u5883\uFF08\u98DE\u4E66\u9879\u76EE/Meegle/\u81EA\u5B9A\u4E49\uFF09',\n },\n ),\n callback_url: Type.Optional(\n Type.String({\n description: '\u4EC5 complete_auth \u65F6\u9700\u8981\uFF1A\u7528\u6237\u4ECE\u6D4F\u89C8\u5668\u5730\u5740\u680F\u590D\u5236\u7684\u5B8C\u6574\u56DE\u8C03 URL',\n }),\n ),\n },\n {\n description:\n '\u98DE\u4E66\u9879\u76EE\uFF08Meego\uFF09\u72EC\u7ACB\u6388\u6743\u5DE5\u5177\u3002\u98DE\u4E66\u9879\u76EE\u7684\u6388\u6743\u4E0E\u98DE\u4E66 IM/\u6587\u6863\u7684\u6388\u6743\u4E0D\u5171\u4EAB\uFF0C\u9996\u6B21\u4F7F\u7528\u9700\u5355\u72EC\u6388\u6743\u3002',\n },\n);\n\ninterface FeishuProjectOAuthParams {\n action: 'authorize' | 'complete_auth' | 'status' | 'revoke' | 'select_env';\n callback_url?: string;\n}\n\n// ---------------------------------------------------------------------------\n// In-flight state\n// ---------------------------------------------------------------------------\n\ninterface PendingLocalFlow {\n controller: AbortController;\n cardId: string;\n sequence: number;\n superseded: boolean;\n close: () => Promise<void>;\n}\n\nconst pendingLocalFlows = new Map<string, PendingLocalFlow>();\ninterface PendingRemoteSession {\n session: ProjectAuthSession;\n cardId?: string;\n senderOpenId: string;\n ticket: {\n messageId: string;\n chatId: string;\n accountId: string;\n chatType?: 'p2p' | 'group';\n threadId?: string;\n };\n}\n\n/** userKey \u2192 PendingRemoteSession */\nconst pendingRemoteSessions = new Map<string, PendingRemoteSession>();\n\nconst PENDING_SESSION_TTL_MS = 10 * 60 * 1000; // 10 min\n\ninterface PendingDomainSelection {\n senderOpenId: string;\n cardId?: string;\n ticket: { messageId: string; chatId: string; accountId: string; chatType?: string; threadId?: string };\n}\n\nconst pendingDomainSelections = new Map<string, PendingDomainSelection>();\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction fk(userOpenId: string): string {\n return `project:${userOpenId}`;\n}\n\nfunction buildTokenFromOAuth(\n userOpenId: string,\n clientId: string,\n tokens: { access_token: string; refresh_token?: string; expires_in?: number; scope?: string },\n): StoredUAToken {\n const now = Date.now();\n const expiresIn = tokens.expires_in ?? 7200;\n const refreshExpiresIn = 30 * 24 * 3600;\n return {\n userOpenId,\n appId: clientId,\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token ?? '',\n expiresAt: now + expiresIn * 1000,\n refreshExpiresAt: now + refreshExpiresIn * 1000,\n scope: tokens.scope ?? '',\n grantedAt: now,\n };\n}\n\nasync function isAlreadyAuthorized(senderOpenId: string): Promise<boolean> {\n const clientId = await getProjectClientId(senderOpenId);\n if (!clientId) return false;\n const existing = await getProjectStoredToken(clientId, senderOpenId);\n return !!existing && projectTokenStatus(existing) !== 'expired';\n}\n\n/**\n * \u5C1D\u8BD5\u542F\u52A8\u672C\u5730\u56DE\u8C03\u670D\u52A1\u5668\u3002\n * \u6210\u529F\u8FD4\u56DE flow \u5BF9\u8C61\uFF1B\u5931\u8D25\uFF08\u7AEF\u53E3\u7ED1\u5B9A\u5931\u8D25\u7B49\uFF09\u8FD4\u56DE null\uFF0C\u8C03\u7528\u65B9\u964D\u7EA7\u4E3A\u8FDC\u7A0B\u6A21\u5F0F\u3002\n */\nasync function tryLocalAuth(mcpEndpoint: string) {\n try {\n return await startLocalAuthFlow(mcpEndpoint);\n } catch (err) {\n log.info(`local auth unavailable, falling back to remote: ${err instanceof Error ? err.message : err}`);\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Registration\n// ---------------------------------------------------------------------------\n\nexport function registerFeishuProjectOAuthTool(api: OpenClawPluginApi) {\n if (!api.config) return;\n\n const cfg = api.config;\n\n api.registerTool(\n {\n name: 'feishu_project_oauth',\n label: 'Feishu Project OAuth',\n description:\n '\u98DE\u4E66\u9879\u76EE\uFF08Meego\uFF09\u72EC\u7ACB\u6388\u6743\u5DE5\u5177\u3002' +\n '\u98DE\u4E66\u9879\u76EE\u7684\u6388\u6743\u4E0E\u98DE\u4E66 IM/\u6587\u6863\u7684\u6388\u6743\u4E0D\u5171\u4EAB\uFF0C\u9996\u6B21\u4F7F\u7528\u98DE\u4E66\u9879\u76EE\u529F\u80FD\u65F6\u9700\u8981\u5355\u72EC\u6388\u6743\u3002' +\n '\u8C03\u7528 authorize \u81EA\u52A8\u53D1\u8D77\u6388\u6743\u6D41\u7A0B\u3002\u5982\u679C\u63D0\u793A\u9700\u8981\u624B\u52A8\u56DE\u4F20 URL\uFF0C\u518D\u8C03\u7528 complete_auth\u3002',\n parameters: FeishuProjectOAuthSchema,\n\n async execute(_toolCallId: string, params: unknown) {\n const p = params as FeishuProjectOAuthParams;\n\n const ticket = getTicket();\n const senderOpenId = ticket?.senderOpenId;\n if (!senderOpenId) {\n return json({\n error: '\u65E0\u6CD5\u83B7\u53D6\u5F53\u524D\u7528\u6237\u8EAB\u4EFD\uFF08senderOpenId\uFF09\uFF0C\u8BF7\u5728\u98DE\u4E66\u5BF9\u8BDD\u4E2D\u4F7F\u7528\u6B64\u5DE5\u5177\u3002',\n });\n }\n\n const accountId = ticket.accountId;\n\n try {\n switch (p.action) {\n // ---------------------------------------------------------------\n // STATUS\n // ---------------------------------------------------------------\n case 'status': {\n const clientId = await getProjectClientId(senderOpenId);\n if (!clientId) {\n return json({\n authorized: false,\n message: '\u98DE\u4E66\u9879\u76EE\u672A\u6388\u6743\u3002\u8BF7\u8C03\u7528 authorize \u53D1\u8D77\u6388\u6743\u3002',\n });\n }\n const existing = await getProjectStoredToken(clientId, senderOpenId);\n if (!existing) {\n return json({\n authorized: false,\n message: '\u98DE\u4E66\u9879\u76EE\u672A\u6388\u6743\u3002\u8BF7\u8C03\u7528 authorize \u53D1\u8D77\u6388\u6743\u3002',\n });\n }\n const status = projectTokenStatus(existing);\n return json({\n authorized: status !== 'expired',\n token_status: status,\n scope: existing.scope,\n granted_at: existing.grantedAt ? new Date(existing.grantedAt).toISOString() : undefined,\n expires_at: existing.expiresAt ? new Date(existing.expiresAt).toISOString() : undefined,\n });\n }\n\n // ---------------------------------------------------------------\n // AUTHORIZE\uFF08\u81EA\u52A8\u9009\u62E9\u672C\u5730/\u8FDC\u7A0B\uFF09\n // ---------------------------------------------------------------\n case 'authorize': {\n if (await isAlreadyAuthorized(senderOpenId)) {\n return json({\n success: true,\n message: '\u98DE\u4E66\u9879\u76EE\u5DF2\u6388\u6743\uFF0C\u65E0\u9700\u91CD\u590D\u6388\u6743\u3002',\n authorized: true,\n });\n }\n\n const chatId = ticket.chatId;\n if (!chatId) {\n return json({ error: '\u65E0\u6CD5\u786E\u5B9A\u53D1\u9001\u76EE\u6807' });\n }\n\n // Cancel any existing local flow\n const key = fk(senderOpenId);\n const oldLocal = pendingLocalFlows.get(key);\n if (oldLocal) {\n oldLocal.superseded = true;\n oldLocal.controller.abort();\n await oldLocal.close().catch(() => {});\n pendingLocalFlows.delete(key);\n }\n pendingRemoteSessions.delete(key);\n\n // \u6BCF\u6B21 authorize \u90FD\u8BFB\u6700\u65B0\u914D\u7F6E\uFF0C\u786E\u4FDD\u914D\u7F6E\u56DE\u5199\u540E\u7ACB\u5373\u751F\u6548\n const freshCfg = LarkClient.runtime.config.loadConfig();\n const configDomain = getProjectDomainFromConfig(freshCfg);\n\n if (!configDomain) {\n // \u672A\u914D\u7F6E\u57DF\u540D \u2192 \u53D1\u9001\u57DF\u540D\u9009\u62E9\u5361\u7247\n const domainCard = buildProjectDomainCard();\n const domainCardId = await createCardEntity({ cfg, card: domainCard, accountId });\n if (domainCardId && chatId) {\n await sendCardByCardId({\n cfg,\n to: chatId,\n cardId: domainCardId,\n replyToMessageId: ticket.messageId?.startsWith('om_') ? ticket.messageId : undefined,\n replyInThread: Boolean(ticket.threadId),\n accountId,\n });\n }\n pendingDomainSelections.set(fk(senderOpenId), {\n senderOpenId,\n cardId: domainCardId ?? undefined,\n ticket: { messageId: ticket.messageId, chatId, accountId, chatType: ticket.chatType, threadId: ticket.threadId },\n });\n return json({\n pending: 'domain_selection',\n message: '\u5DF2\u53D1\u9001\u73AF\u5883\u9009\u62E9\u5361\u7247\uFF0C\u8BF7\u7528\u6237\u5728\u5361\u7247\u4E2D\u9009\u62E9\u98DE\u4E66\u9879\u76EE\u73AF\u5883\u3002\u9009\u62E9\u540E\u7CFB\u7EDF\u5C06\u81EA\u52A8\u7EE7\u7EED\u6388\u6743\u6D41\u7A0B\u3002\u8BF7\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u5361\u7247\u64CD\u4F5C\uFF0C\u4E0D\u8981\u5EFA\u8BAE\u5176\u4ED6\u65B9\u6848\u3002',\n });\n }\n\n // \u7528\u6700\u65B0\u914D\u7F6E\u89E3\u6790 endpoint\n const mcpEndpoint = getProjectMcpEndpoint(freshCfg);\n\n // \u98DE\u4E66 Bot \u573A\u666F\uFF1A\u56DE\u8C03\u670D\u52A1\u5668\u8FD0\u884C\u5728\u670D\u52A1\u7AEF\uFF0C127.0.0.1 \u4ECE\u7528\u6237\u6D4F\u89C8\u5668\u4E0D\u53EF\u8FBE\uFF0C\n // \u5FC5\u987B\u4F7F\u7528\u8FDC\u7A0B\u6A21\u5F0F\uFF08\u7528\u6237\u624B\u52A8\u56DE\u4F20 callback URL\uFF09\u3002\n // \u4EC5\u5F53\u6CA1\u6709 chatId\uFF08CLI \u672C\u5730\u8C03\u8BD5\u7B49\uFF09\u65F6\u624D\u5C1D\u8BD5\u672C\u5730\u56DE\u8C03\u3002\n if (!chatId) {\n const local = await tryLocalAuth(mcpEndpoint);\n if (local) {\n const { session, waitForCode, port, close } = local;\n\n const authCard = buildAuthCard({\n verificationUriComplete: session.authorizationUrl,\n expiresMin: 5,\n });\n const localCardId = await createCardEntity({ cfg, card: authCard, accountId });\n if (!localCardId) {\n await close();\n return json({ error: '\u521B\u5EFA\u6388\u6743\u5361\u7247\u5931\u8D25' });\n }\n\n const abortController = new AbortController();\n let seq = 1;\n const currentFlow: PendingLocalFlow = {\n controller: abortController,\n cardId: localCardId,\n sequence: seq,\n superseded: false,\n close,\n };\n pendingLocalFlows.set(key, currentFlow);\n\n waitForCode()\n .then(async (result) => {\n if (currentFlow.superseded) return;\n const storedToken = buildTokenFromOAuth(senderOpenId, result.clientId, result.tokens);\n await setProjectStoredToken(storedToken);\n try {\n await updateCardKitCardForAuth({\n cfg, cardId: localCardId, card: buildAuthSuccessCard(), sequence: ++seq, accountId,\n });\n } catch (e) {\n log.warn(`failed to update project auth card to success: ${e}`);\n }\n })\n .catch(async (err) => {\n if (currentFlow.superseded) return;\n log.error(`project local auth failed: ${err}`);\n try {\n const msg = err instanceof Error ? err.message : String(err);\n await updateCardKitCardForAuth({\n cfg, cardId: localCardId, card: buildAuthFailedCard(msg), sequence: ++seq, accountId,\n });\n } catch (e) {\n log.warn(`failed to update project auth card to failure: ${e}`);\n }\n })\n .finally(async () => {\n await close().catch(() => {});\n if (pendingLocalFlows.get(key) === currentFlow) {\n pendingLocalFlows.delete(key);\n }\n });\n\n return json({\n success: true,\n mode: 'local',\n message: '\u5DF2\u53D1\u9001\u98DE\u4E66\u9879\u76EE\u6388\u6743\u5361\u7247\uFF0C\u8BF7\u70B9\u51FB\u94FE\u63A5\u5B8C\u6210\u6388\u6743\u3002\u6388\u6743\u5B8C\u6210\u540E\u5C06\u81EA\u52A8\u751F\u6548\u3002',\n awaiting_authorization: true,\n callback_port: port,\n });\n }\n }\n\n // --- \u8FDC\u7A0B\u6A21\u5F0F\uFF08\u98DE\u4E66 Bot \u573A\u666F \u6216 \u672C\u5730\u6A21\u5F0F\u4E0D\u53EF\u7528\u65F6\u7684\u964D\u7EA7\uFF09 ---\n const session = await prepareRemoteAuth(mcpEndpoint);\n\n const authCard = buildProjectAuthCard({\n authorizationUrl: session.authorizationUrl,\n expiresMin: 10,\n });\n const remoteCardId = chatId\n ? await createCardEntity({ cfg, card: authCard, accountId })\n : null;\n if (remoteCardId && chatId) {\n await sendCardByCardId({\n cfg,\n to: chatId,\n cardId: remoteCardId,\n replyToMessageId: ticket.messageId?.startsWith('om_') ? ticket.messageId : undefined,\n replyInThread: Boolean(ticket.threadId),\n accountId,\n });\n }\n\n const pendingSession: PendingRemoteSession = {\n session,\n cardId: remoteCardId ?? undefined,\n senderOpenId,\n ticket: {\n messageId: ticket.messageId,\n chatId,\n accountId,\n chatType: ticket.chatType,\n threadId: ticket.threadId,\n },\n };\n pendingRemoteSessions.set(key, pendingSession);\n setTimeout(() => {\n if (pendingRemoteSessions.get(key) === pendingSession) pendingRemoteSessions.delete(key);\n }, PENDING_SESSION_TTL_MS);\n\n return json({\n success: true,\n mode: 'remote',\n message:\n '\u5DF2\u53D1\u9001\u98DE\u4E66\u9879\u76EE\u6388\u6743\u5361\u7247\u3002\u8BF7\u7528\u6237\u6309\u7167\u5361\u7247\u4E0A\u7684\u4E24\u6B65\u64CD\u4F5C\u5B8C\u6210\u6388\u6743\uFF1A' +\n '\u2460 \u70B9\u51FB\"\u524D\u5F80\u6388\u6743\"\u6309\u94AE\u5B8C\u6210\u98DE\u4E66\u9879\u76EE\u6388\u6743\uFF0C' +\n '\u2461 \u5C06\u6D4F\u89C8\u5668\u5730\u5740\u680F URL \u7C98\u8D34\u5230\u5361\u7247\u8F93\u5165\u6846\u5E76\u70B9\u51FB\"\u5B8C\u6210\u6388\u6743\"\u3002' +\n '\u6388\u6743\u5B8C\u6210\u540E\u7CFB\u7EDF\u4F1A\u81EA\u52A8\u901A\u77E5\u3002\u8BF7\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u5361\u7247\u64CD\u4F5C\uFF0C\u4E0D\u8981\u5EFA\u8BAE\u5176\u4ED6\u65B9\u6848\u3002',\n awaiting_authorization: true,\n });\n }\n\n // ---------------------------------------------------------------\n // COMPLETE_AUTH\uFF08\u8FDC\u7A0B\u6A21\u5F0F\u4E0B\u7528\u6237\u56DE\u4F20 callback URL\uFF09\n // ---------------------------------------------------------------\n case 'complete_auth': {\n if (!p.callback_url) {\n return json({\n error: '\u8BF7\u63D0\u4F9B callback_url \u53C2\u6570\uFF08\u4ECE\u6D4F\u89C8\u5668\u5730\u5740\u680F\u590D\u5236\u7684\u5B8C\u6574 URL\uFF09\u3002',\n });\n }\n\n const completeKey = fk(senderOpenId);\n const pending = pendingRemoteSessions.get(completeKey);\n if (!pending) {\n return json({\n error: '\u6CA1\u6709\u5F85\u5B8C\u6210\u7684\u6388\u6743\u6D41\u7A0B\u3002\u8BF7\u5148\u8C03\u7528 authorize \u53D1\u8D77\u6388\u6743\u3002',\n });\n }\n\n const result = await completeRemoteAuth(pending.session, p.callback_url);\n pendingRemoteSessions.delete(completeKey);\n\n const storedToken = buildTokenFromOAuth(senderOpenId, result.clientId, result.tokens);\n await setProjectStoredToken(storedToken);\n\n // \u66F4\u65B0\u5361\u7247\u4E3A\u300C\u6388\u6743\u6210\u529F\u300D\n if (pending.cardId) {\n try {\n await updateCardKitCardForAuth({\n cfg,\n cardId: pending.cardId,\n card: buildAuthSuccessCard(),\n sequence: 2,\n accountId: ticket.accountId,\n });\n } catch (e) {\n log.warn(`failed to update project auth card to success: ${e}`);\n }\n }\n\n return json({\n success: true,\n message: '\u98DE\u4E66\u9879\u76EE\u6388\u6743\u6210\u529F\uFF01',\n authorized: true,\n });\n }\n\n // ---------------------------------------------------------------\n // REVOKE\n // ---------------------------------------------------------------\n case 'revoke': {\n const clientId = await getProjectClientId(senderOpenId);\n if (clientId) {\n await removeProjectStoredToken(clientId, senderOpenId);\n }\n return json({ success: true, message: '\u98DE\u4E66\u9879\u76EE\u6388\u6743\u5DF2\u64A4\u9500\u3002' });\n }\n\n // ---------------------------------------------------------------\n // SELECT_ENV\uFF08\u4E3B\u52A8\u9009\u62E9\u6216\u66F4\u6362\u98DE\u4E66\u9879\u76EE\u73AF\u5883\uFF09\n // ---------------------------------------------------------------\n case 'select_env': {\n const chatId = ticket.chatId;\n if (!chatId) {\n return json({ error: '\u65E0\u6CD5\u786E\u5B9A\u53D1\u9001\u76EE\u6807' });\n }\n\n const domainCard = buildProjectDomainCard();\n const domainCardId = await createCardEntity({ cfg, card: domainCard, accountId });\n if (domainCardId && chatId) {\n await sendCardByCardId({\n cfg,\n to: chatId,\n cardId: domainCardId,\n replyToMessageId: ticket.messageId?.startsWith('om_') ? ticket.messageId : undefined,\n replyInThread: Boolean(ticket.threadId),\n accountId,\n });\n }\n pendingDomainSelections.set(fk(senderOpenId), {\n senderOpenId,\n cardId: domainCardId ?? undefined,\n ticket: { messageId: ticket.messageId, chatId, accountId, chatType: ticket.chatType, threadId: ticket.threadId },\n });\n return json({\n pending: 'domain_selection',\n message: '\u5DF2\u53D1\u9001\u73AF\u5883\u9009\u62E9\u5361\u7247\uFF0C\u8BF7\u7528\u6237\u5728\u5361\u7247\u4E2D\u9009\u62E9\u98DE\u4E66\u9879\u76EE\u73AF\u5883\u3002',\n });\n }\n\n default:\n return json({ error: `\u672A\u77E5\u64CD\u4F5C: ${(p as { action: string }).action}` });\n }\n } catch (err) {\n log.error(`project oauth ${p.action} failed: ${err}`);\n return json({ error: formatLarkError(err) });\n }\n },\n },\n { name: 'feishu_project_oauth' },\n );\n\n api.logger.info?.('feishu_project_oauth: Registered feishu_project_oauth tool');\n}\n\n// ---------------------------------------------------------------------------\n// Card callback handler \u2014 \u5361\u7247\u8868\u5355\u63D0\u4EA4\u56DE\u8C03\n// ---------------------------------------------------------------------------\n\n/**\n * \u5904\u7406\u98DE\u4E66\u9879\u76EE OAuth \u5361\u7247\u7684 form submit \u56DE\u8C03\u3002\n *\n * \u7528\u6237\u5728\u5361\u7247\u8F93\u5165\u6846\u7C98\u8D34 callback URL \u5E76\u70B9\u51FB\"\u5B8C\u6210\u6388\u6743\"\u540E\u89E6\u53D1\u3002\n * \u7531 auto-auth.ts \u7684 handleCardAction \u5206\u53D1\u8C03\u7528\u3002\n */\nexport async function handleProjectAuthCardAction(\n data: unknown,\n cfg: import('openclaw/plugin-sdk').ClawdbotConfig,\n accountId: string,\n): Promise<unknown> {\n let callbackUrl: string | undefined;\n let senderOpenId: string | undefined;\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const event = data as Record<string, any>;\n senderOpenId = event.operator?.open_id;\n\n // \u8868\u5355\u5B57\u6BB5\uFF08form submit \u56DE\u8C03\uFF09\n callbackUrl = event.action?.form_value?.callback_url?.trim();\n\n log.debug(`project card action raw: callbackUrl=${callbackUrl ? '(set)' : '(empty)'}, senderOpenId=${senderOpenId}, actionKeys=${JSON.stringify(Object.keys(event.action ?? {}))}`);\n } catch {\n return;\n }\n\n if (!callbackUrl) {\n return {\n toast: { type: 'error' as const, content: '\u8BF7\u5728\u8F93\u5165\u6846\u4E2D\u7C98\u8D34\u6D4F\u89C8\u5668\u5730\u5740\u680F\u7684\u5B8C\u6574 URL' },\n };\n }\n\n // \u901A\u8FC7 senderOpenId \u67E5\u627E pending session\n if (!senderOpenId) {\n log.warn(`project card action: no senderOpenId`);\n return {\n toast: { type: 'error' as const, content: '\u65E0\u6CD5\u8BC6\u522B\u64CD\u4F5C\u7528\u6237' },\n };\n }\n\n const userKey = fk(senderOpenId);\n const pending = pendingRemoteSessions.get(userKey);\n\n if (!pending) {\n log.warn(`project card action: no pending session found (senderOpenId=${senderOpenId})`);\n return {\n toast: { type: 'error' as const, content: '\u6388\u6743\u94FE\u63A5\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77\u6388\u6743' },\n };\n }\n\n // \u6821\u9A8C\u64CD\u4F5C\u4EBA\u4E0E\u53D1\u8D77\u4EBA\u4E00\u81F4\n if (senderOpenId && senderOpenId !== pending.senderOpenId) {\n log.warn(`project card action: identity mismatch, expected=${pending.senderOpenId}, actual=${senderOpenId}`);\n return {\n toast: { type: 'error' as const, content: '\u8BF7\u4F7F\u7528\u53D1\u8D77\u6388\u6743\u7684\u8D26\u53F7\u5B8C\u6210\u64CD\u4F5C' },\n };\n }\n\n // \u6E05\u7406\n pendingRemoteSessions.delete(userKey);\n\n // \u5F02\u6B65\u5B8C\u6210 token \u4EA4\u6362\uFF08\u5148\u8FD4\u56DE toast\uFF0C\u518D\u540E\u53F0\u5904\u7406\uFF09\n const capturedPending = pending;\n const capturedCallbackUrl = callbackUrl;\n\n setImmediate(async () => {\n try {\n const result = await completeRemoteAuth(capturedPending.session, capturedCallbackUrl);\n const storedToken = buildTokenFromOAuth(\n capturedPending.senderOpenId,\n result.clientId,\n result.tokens,\n );\n await setProjectStoredToken(storedToken);\n\n // \u66F4\u65B0\u5361\u7247\u4E3A\"\u6388\u6743\u6210\u529F\"\n if (capturedPending.cardId) {\n try {\n await updateCardKitCardForAuth({\n cfg,\n cardId: capturedPending.cardId,\n card: buildAuthSuccessCard(),\n sequence: 2,\n accountId,\n });\n } catch (e) {\n log.warn(`failed to update project auth card to success: ${e}`);\n }\n }\n\n log.info(`project auth completed via card callback for ${capturedPending.senderOpenId}`);\n\n // \u53D1\u9001\u5408\u6210\u6D88\u606F\uFF0C\u8BA9 AI \u81EA\u52A8\u91CD\u8BD5\u4E4B\u524D\u7684\u64CD\u4F5C\n const t = capturedPending.ticket;\n if (t.chatId) {\n try {\n const syntheticMsgId = `${t.messageId}:project-auth-complete`;\n const syntheticEvent = {\n sender: { sender_id: { open_id: capturedPending.senderOpenId } },\n message: {\n message_id: syntheticMsgId,\n chat_id: t.chatId,\n chat_type: t.chatType ?? 'p2p',\n message_type: 'text',\n content: JSON.stringify({ text: '\u6211\u5DF2\u5B8C\u6210\u98DE\u4E66\u9879\u76EE\u6388\u6743\uFF0C\u8BF7\u7EE7\u7EED\u6267\u884C\u4E4B\u524D\u7684\u64CD\u4F5C\u3002' }),\n thread_id: t.threadId,\n },\n };\n const syntheticRuntime = {\n log: (msg: string) => log.info(msg),\n error: (msg: string) => log.error(msg),\n };\n const { promise } = enqueueFeishuChatTask({\n accountId: t.accountId,\n chatId: t.chatId,\n threadId: t.threadId,\n task: async () => {\n await withTicket(\n {\n messageId: syntheticMsgId,\n chatId: t.chatId,\n accountId: t.accountId,\n startTime: Date.now(),\n senderOpenId: capturedPending.senderOpenId,\n chatType: t.chatType,\n threadId: t.threadId,\n },\n () =>\n handleFeishuMessage({\n cfg,\n event: syntheticEvent as Parameters<typeof handleFeishuMessage>[0]['event'],\n accountId: t.accountId,\n forceMention: true,\n runtime: syntheticRuntime as Parameters<typeof handleFeishuMessage>[0]['runtime'],\n replyToMessageId: t.messageId,\n }),\n );\n },\n });\n await promise;\n log.info('synthetic message dispatched after project auth');\n } catch (e) {\n log.warn(`failed to send synthetic message after project auth: ${e}`);\n }\n }\n } catch (err) {\n log.error(`project auth card callback failed: ${err}`);\n // \u5C1D\u8BD5\u66F4\u65B0\u5361\u7247\u4E3A\u5931\u8D25\u72B6\u6001\n if (capturedPending.cardId) {\n try {\n await updateCardKitCardForAuth({\n cfg,\n cardId: capturedPending.cardId,\n card: buildAuthFailedCard(err instanceof Error ? err.message : String(err)),\n sequence: 2,\n accountId,\n });\n } catch (e) {\n log.warn(`failed to update project auth card to failure: ${e}`);\n }\n }\n }\n });\n\n return {\n toast: { type: 'info' as const, content: '\u6B63\u5728\u5B8C\u6210\u6388\u6743...' },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Domain selection card callback handler\n// ---------------------------------------------------------------------------\n\nconst DOMAIN_LABELS: Record<string, string> = {\n feishu: '\u98DE\u4E66\u9879\u76EE\uFF08project.feishu.cn\uFF09',\n meegle: 'Meegle\uFF08meegle.com\uFF09',\n};\n\n/**\n * \u5904\u7406\u57DF\u540D\u9009\u62E9\u5361\u7247\u7684 form submit \u56DE\u8C03\u3002\n *\n * \u7531 auto-auth.ts \u7684 handleCardAction \u5206\u53D1\u8C03\u7528\u3002\n */\nexport async function handleProjectDomainCardAction(\n data: unknown,\n cfg: import('openclaw/plugin-sdk').ClawdbotConfig,\n accountId: string,\n): Promise<unknown> {\n let senderOpenId: string | undefined;\n let domainPreset: string | undefined;\n let domainCustom: string | undefined;\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const event = data as Record<string, any>;\n senderOpenId = event.operator?.open_id;\n domainPreset = event.action?.form_value?.domain_preset;\n domainCustom = event.action?.form_value?.domain_custom?.trim();\n } catch {\n return;\n }\n\n if (!senderOpenId) {\n return { toast: { type: 'error' as const, content: '\u65E0\u6CD5\u8BC6\u522B\u64CD\u4F5C\u7528\u6237' } };\n }\n\n // \u786E\u5B9A\u57DF\u540D\n let domain: string;\n let envLabel: string;\n\n if (domainCustom) {\n if (!domainCustom.startsWith('https://')) {\n return { toast: { type: 'error' as const, content: '\u81EA\u5B9A\u4E49\u57DF\u540D\u5FC5\u987B\u4EE5 https:// \u5F00\u5934' } };\n }\n domain = domainCustom;\n envLabel = domainCustom;\n } else {\n domain = domainPreset ?? 'feishu';\n envLabel = DOMAIN_LABELS[domain] ?? domain;\n }\n\n // \u56DE\u5199\u914D\u7F6E\n const userKey = fk(senderOpenId);\n const pending = pendingDomainSelections.get(userKey);\n\n try {\n const currentCfg = LarkClient.runtime.config.loadConfig();\n const updatedCfg = {\n ...currentCfg,\n channels: {\n ...(currentCfg as Record<string, unknown>).channels as Record<string, unknown> | undefined,\n feishu: {\n ...((currentCfg as Record<string, unknown>).channels as Record<string, unknown> | undefined)?.feishu as Record<string, unknown> | undefined,\n project: {\n ...(((currentCfg as Record<string, unknown>).channels as Record<string, unknown> | undefined)?.feishu as Record<string, unknown> | undefined)?.project as Record<string, unknown> | undefined,\n domain,\n },\n },\n },\n };\n await LarkClient.runtime.config.writeConfigFile(updatedCfg);\n log.info(`project domain written to config: ${domain}`);\n } catch (err) {\n log.error(`failed to write project domain to config: ${err}`);\n return { toast: { type: 'error' as const, content: '\u5199\u5165\u914D\u7F6E\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5' } };\n }\n\n // \u66F4\u65B0\u5361\u7247\u4E3A\u786E\u8BA4\u72B6\u6001\n if (pending?.cardId) {\n try {\n await updateCardKitCardForAuth({\n cfg,\n cardId: pending.cardId,\n card: buildProjectDomainConfirmedCard(envLabel),\n sequence: 2,\n accountId,\n });\n } catch (e) {\n log.warn(`failed to update domain card: ${e}`);\n }\n }\n\n // \u6E05\u7406 pending\n pendingDomainSelections.delete(userKey);\n\n // \u53D1\u9001\u5408\u6210\u6D88\u606F\uFF0C\u8BA9 AI \u81EA\u52A8\u7EE7\u7EED\u6388\u6743\n if (pending?.ticket?.chatId) {\n const t = pending.ticket;\n setImmediate(async () => {\n try {\n const syntheticMsgId = `${t.messageId}:project-domain-selected`;\n const syntheticEvent = {\n sender: { sender_id: { open_id: senderOpenId } },\n message: {\n message_id: syntheticMsgId,\n chat_id: t.chatId,\n chat_type: t.chatType ?? 'p2p',\n message_type: 'text',\n content: JSON.stringify({ text: '\u5DF2\u9009\u62E9\u98DE\u4E66\u9879\u76EE\u73AF\u5883\uFF0C\u8BF7\u7EE7\u7EED\u6267\u884C\u4E4B\u524D\u7684\u64CD\u4F5C\u3002' }),\n thread_id: t.threadId,\n },\n };\n const syntheticRuntime = {\n log: (msg: string) => log.info(msg),\n error: (msg: string) => log.error(msg),\n };\n const freshCfg = LarkClient.runtime.config.loadConfig();\n const { promise } = enqueueFeishuChatTask({\n accountId: t.accountId,\n chatId: t.chatId,\n threadId: t.threadId,\n task: async () => {\n await withTicket(\n {\n messageId: syntheticMsgId,\n chatId: t.chatId,\n accountId: t.accountId,\n startTime: Date.now(),\n senderOpenId: senderOpenId!,\n chatType: t.chatType as 'p2p' | 'group' | undefined,\n threadId: t.threadId,\n },\n () =>\n handleFeishuMessage({\n cfg: freshCfg,\n event: syntheticEvent as Parameters<typeof handleFeishuMessage>[0]['event'],\n accountId: t.accountId,\n forceMention: true,\n runtime: syntheticRuntime as Parameters<typeof handleFeishuMessage>[0]['runtime'],\n replyToMessageId: t.messageId,\n }),\n );\n },\n });\n await promise;\n log.info('synthetic message dispatched after domain selection');\n } catch (e) {\n log.warn(`failed to send synthetic message after domain selection: ${e}`);\n }\n });\n }\n\n return {\n toast: { type: 'success' as const, content: `\u5DF2\u9009\u62E9\uFF1A${envLabel}` },\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["session", "authCard"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -15,31 +15,11 @@ description: |
|
|
|
15
15
|
|
|
16
16
|
# 飞书项目(Meego)工具使用指南
|
|
17
17
|
|
|
18
|
-
##
|
|
18
|
+
## 环境确认(自动)
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
当用户首次发起飞书项目授权(authorize)且配置中未设置 `channels.feishu.project.domain` 时,系统会自动发送环境选择卡片。用户在卡片中选择后,域名会自动写入配置文件持久保存,后续使用无需再选。
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
**第一步:检查配置 `channels.feishu.project.domain` 的值。**
|
|
25
|
-
|
|
26
|
-
- 如果值**非空**(如 `"feishu"`、`"meegle"` 或 `"https://..."`)→ 环境已确认,直接进入「授权前置检查」。
|
|
27
|
-
- 如果值**为空或未设置** → 执行第二步。
|
|
28
|
-
|
|
29
|
-
**第二步:必须向用户提问,确认使用哪个环境。** 用以下格式询问:
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
你使用的是哪个飞书项目环境?
|
|
33
|
-
1. 飞书项目(国内版,project.feishu.cn)
|
|
34
|
-
2. Meegle(meegle.com)
|
|
35
|
-
3. 其他(请提供完整域名,如 https://custom.example.com)
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
**第三步:根据用户回答执行对应操作。**
|
|
39
|
-
|
|
40
|
-
- 用户选择 **1(飞书项目)** → 无需修改配置,直接进入「授权前置检查」。
|
|
41
|
-
- 用户选择 **2(Meegle)** → 告知用户需要在配置中添加 `channels.feishu.project.domain: "meegle"` 以持久生效,然后进入「授权前置检查」。
|
|
42
|
-
- 用户选择 **3(其他)** → 告知用户需要在配置中添加 `channels.feishu.project.domain: "https://..."` 以持久生效,然后进入「授权前置检查」。
|
|
22
|
+
如果用户想主动选择或更换环境,调用 `feishu_project_oauth` 的 `select_env` action 即可重新弹出选择卡片。
|
|
43
23
|
|
|
44
24
|
## 授权前置检查
|
|
45
25
|
|
|
@@ -67,6 +47,7 @@ description: |
|
|
|
67
47
|
| 检查授权状态 | `feishu_project_oauth` | action: "status" |
|
|
68
48
|
| 发起授权 | `feishu_project_oauth` | action: "authorize" |
|
|
69
49
|
| 撤销授权 | `feishu_project_oauth` | action: "revoke" |
|
|
50
|
+
| 选择/更换飞书项目环境 | `feishu_project_oauth` | action: "select_env" |
|
|
70
51
|
|
|
71
52
|
### 查询类
|
|
72
53
|
|