@lark-project/openclaw-lark-project 2026.3.163 → 2026.3.164
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/core/project-auth.js +2 -1
- package/dist/src/core/project-auth.js.map +2 -2
- package/dist/src/core/project-oauth-flow.js +5 -0
- package/dist/src/core/project-oauth-flow.js.map +2 -2
- package/dist/src/tools/auto-auth.js +4 -1
- package/dist/src/tools/auto-auth.js.map +2 -2
- package/dist/src/tools/oauth-cards.js +8 -3
- package/dist/src/tools/oauth-cards.js.map +2 -2
- package/package.json +1 -1
|
@@ -141,7 +141,8 @@ async function prepareRemoteAuth(mcpEndpoint) {
|
|
|
141
141
|
clientId: client.client_id,
|
|
142
142
|
redirectUri,
|
|
143
143
|
codeChallenge: pkce.codeChallenge,
|
|
144
|
-
state
|
|
144
|
+
state,
|
|
145
|
+
extraParams: { redirect_mode: "manual" }
|
|
145
146
|
});
|
|
146
147
|
return { metadata, client, pkce, state, redirectUri, authorizationUrl };
|
|
147
148
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/core/project-auth.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * \u98DE\u4E66\u9879\u76EE\uFF08Meego\uFF09\u72EC\u7ACB OAuth \u7F16\u6392\n *\n * \u57FA\u4E8E MCP \u6807\u51C6 OAuth\uFF08Authorization Code + PKCE + \u52A8\u6001\u5BA2\u6237\u7AEF\u6CE8\u518C\uFF09\uFF0C\n * \u4E0D\u4F9D\u8D56\u98DE\u4E66\u5F00\u653E\u5E73\u53F0 appId/appSecret\uFF0C\u4E5F\u4E0D\u8D70 Device Flow\u3002\n *\n * \u652F\u6301\u4E24\u79CD\u8FD0\u884C\u6A21\u5F0F\uFF1A\n * - \u672C\u5730\u6A21\u5F0F\uFF08\u6709\u6D4F\u89C8\u5668\uFF09\uFF1A\u542F\u52A8 127.0.0.1 \u56DE\u8C03\u670D\u52A1\u5668\uFF0C\u81EA\u52A8\u5B8C\u6210\u6388\u6743\n * - \u8FDC\u7A0B\u6A21\u5F0F\uFF08\u65E0\u6D4F\u89C8\u5668\uFF09\uFF1A\u751F\u6210\u6388\u6743\u94FE\u63A5\uFF0C\u7528\u6237\u5728\u5916\u90E8\u6D4F\u89C8\u5668\u5B8C\u6210\u540E\u56DE\u4F20 URL\n */\n\nimport http from 'node:http';\nimport { randomUUID } from 'node:crypto';\nimport { larkLogger } from './lark-logger';\nimport {\n discoverOAuthMetadata,\n registerOAuthClient,\n generatePKCE,\n buildAuthorizationUrl,\n exchangeCodeForTokens,\n extractCodeFromCallbackUrl,\n refreshAccessToken,\n type OAuthMetadata,\n type OAuthClientInfo,\n type OAuthTokens,\n type PKCEPair,\n} from './project-oauth-flow';\nimport { getProjectMcpEndpoint } from '../tools/mcp/project/endpoint';\n\nconst log = larkLogger('core/project-auth');\n\nconst CALLBACK_HOST = '127.0.0.1';\nconst CALLBACK_PATH = '/callback';\n\n// ---------------------------------------------------------------------------\n// Pending authorization session \u2014 \u5171\u4EAB\u72B6\u6001\uFF0C\u4F9B\u672C\u5730\u56DE\u8C03\u548C\u8FDC\u7A0B\u624B\u52A8\u4E24\u79CD\u6A21\u5F0F\u4F7F\u7528\n// ---------------------------------------------------------------------------\n\nexport interface ProjectAuthSession {\n metadata: OAuthMetadata;\n client: OAuthClientInfo;\n pkce: PKCEPair;\n state: string;\n redirectUri: string;\n authorizationUrl: string;\n}\n\n// ---------------------------------------------------------------------------\n// \u516C\u5171\u6B65\u9AA4\uFF1A\u53D1\u73B0 + \u6CE8\u518C + PKCE + \u6784\u5EFA\u6388\u6743 URL\n// ---------------------------------------------------------------------------\n\nexport async function prepareAuthSession(\n mcpEndpoint?: string,\n redirectUri?: string,\n): Promise<ProjectAuthSession> {\n const endpoint = mcpEndpoint ?? getProjectMcpEndpoint();\n\n log.info(`discovering OAuth metadata from ${endpoint}`);\n const metadata = await discoverOAuthMetadata(endpoint);\n\n const callbackUri = redirectUri ?? `http://${CALLBACK_HOST}:0${CALLBACK_PATH}`;\n\n log.info('registering OAuth client via dynamic registration');\n const client = await registerOAuthClient(\n metadata.registration_endpoint,\n callbackUri,\n );\n\n const pkce = generatePKCE();\n const state = randomUUID();\n\n const authorizationUrl = buildAuthorizationUrl({\n authorizationEndpoint: metadata.authorization_endpoint,\n clientId: client.client_id,\n redirectUri: callbackUri,\n codeChallenge: pkce.codeChallenge,\n state,\n });\n\n return { metadata, client, pkce, state, redirectUri: callbackUri, authorizationUrl };\n}\n\n// ---------------------------------------------------------------------------\n// \u6A21\u5F0F 1\uFF1A\u672C\u5730\u56DE\u8C03\uFF08\u6709\u6D4F\u89C8\u5668\uFF09\n// ---------------------------------------------------------------------------\n\nexport interface LocalAuthResult {\n tokens: OAuthTokens;\n clientId: string;\n}\n\n/**\n * \u542F\u52A8\u672C\u5730 HTTP \u56DE\u8C03\u670D\u52A1\u5668\uFF0C\u7B49\u5F85\u6D4F\u89C8\u5668\u91CD\u5B9A\u5411\u5E26\u56DE authorization code\u3002\n *\n * \u6D41\u7A0B\uFF1A\n * 1. prepareAuthSession\uFF08\u52A8\u6001\u6CE8\u518C\u65F6\u7528\u5B9E\u9645\u7AEF\u53E3\u7684 redirect_uri\uFF09\n * 2. \u542F\u52A8 127.0.0.1 \u56DE\u8C03\u670D\u52A1\u5668\n * 3. \u6253\u5F00\u6D4F\u89C8\u5668\uFF08\u8C03\u7528\u65B9\u8D1F\u8D23\uFF09\n * 4. \u7B49\u5F85\u56DE\u8C03 \u2192 \u63D0\u53D6 code \u2192 \u6362 token\n * 5. \u5173\u95ED\u670D\u52A1\u5668\n */\nexport async function startLocalAuthFlow(\n mcpEndpoint?: string,\n signal?: AbortSignal,\n): Promise<{\n session: ProjectAuthSession;\n waitForCode: () => Promise<LocalAuthResult>;\n port: number;\n close: () => Promise<void>;\n}> {\n const endpoint = mcpEndpoint ?? getProjectMcpEndpoint();\n\n log.info(`discovering OAuth metadata from ${endpoint}`);\n const metadata = await discoverOAuthMetadata(endpoint);\n\n // \u5148\u542F\u52A8\u670D\u52A1\u5668\u62FF\u5230\u5B9E\u9645\u7AEF\u53E3\uFF0C\u518D\u7528\u8BE5\u7AEF\u53E3\u6CE8\u518C\u5BA2\u6237\u7AEF\n const server = http.createServer();\n const port = await new Promise<number>((resolve, reject) => {\n server.listen(0, CALLBACK_HOST, () => {\n const addr = server.address();\n if (addr && typeof addr === 'object') {\n resolve(addr.port);\n } else {\n reject(new Error('Failed to determine callback port'));\n }\n });\n server.once('error', reject);\n });\n\n const redirectUri = `http://${CALLBACK_HOST}:${port}${CALLBACK_PATH}`;\n\n const client = await registerOAuthClient(\n metadata.registration_endpoint,\n redirectUri,\n );\n\n const pkce = generatePKCE();\n const state = randomUUID();\n\n const authorizationUrl = buildAuthorizationUrl({\n authorizationEndpoint: metadata.authorization_endpoint,\n clientId: client.client_id,\n redirectUri,\n codeChallenge: pkce.codeChallenge,\n state,\n });\n\n const session: ProjectAuthSession = {\n metadata,\n client,\n pkce,\n state,\n redirectUri,\n authorizationUrl,\n };\n\n // \u7B49\u5F85\u56DE\u8C03\u7684 Promise\n const waitForCode = (): Promise<LocalAuthResult> =>\n new Promise<LocalAuthResult>((resolve, reject) => {\n const onAbort = () => {\n reject(new Error('Authorization cancelled'));\n cleanup();\n };\n signal?.addEventListener('abort', onAbort, { once: true });\n\n const cleanup = () => {\n signal?.removeEventListener('abort', onAbort);\n };\n\n server.on('request', async (req, res) => {\n try {\n const reqUrl = new URL(req.url ?? '', `http://${CALLBACK_HOST}:${port}`);\n if (reqUrl.pathname !== CALLBACK_PATH) {\n res.statusCode = 404;\n res.end('Not found');\n return;\n }\n\n const { code, state: receivedState } = extractCodeFromCallbackUrl(reqUrl.toString());\n\n if (receivedState && receivedState !== session.state) {\n res.statusCode = 400;\n res.setHeader('Content-Type', 'text/html');\n res.end('<html><body><h1>Authorization failed</h1><p>Invalid state</p></body></html>');\n reject(new Error('OAuth state mismatch'));\n cleanup();\n return;\n }\n\n res.statusCode = 200;\n res.setHeader('Content-Type', 'text/html');\n res.end('<html><body><h1>Authorization successful</h1><p>You can close this tab.</p></body></html>');\n\n const tokens = await exchangeCodeForTokens({\n tokenEndpoint: session.metadata.token_endpoint,\n code,\n codeVerifier: session.pkce.codeVerifier,\n clientId: session.client.client_id,\n redirectUri: session.redirectUri,\n });\n\n resolve({ tokens, clientId: session.client.client_id });\n cleanup();\n } catch (err) {\n res.statusCode = 500;\n res.end('Internal error');\n reject(err);\n cleanup();\n }\n });\n });\n\n const close = async () => {\n await new Promise<void>((resolve) => {\n server.close(() => resolve());\n });\n };\n\n return { session, waitForCode, port, close };\n}\n\n// ---------------------------------------------------------------------------\n// \u6A21\u5F0F 2\uFF1A\u8FDC\u7A0B\u624B\u52A8\uFF08\u65E0\u6D4F\u89C8\u5668\uFF09\n// ---------------------------------------------------------------------------\n\n/**\n * \u4E3A\u8FDC\u7A0B\uFF08\u65E0\u6D4F\u89C8\u5668\uFF09\u573A\u666F\u51C6\u5907\u6388\u6743\u3002\n *\n * \u8FD4\u56DE session \u548C authorizationUrl\uFF0C\u8C03\u7528\u65B9\u5C06 URL \u53D1\u9001\u7ED9\u7528\u6237\u3002\n * \u7528\u6237\u5728\u672C\u5730\u6D4F\u89C8\u5668\u5B8C\u6210\u6388\u6743\u540E\uFF0C\u6D4F\u89C8\u5668\u91CD\u5B9A\u5411\u5230 127.0.0.1\uFF08\u4E0D\u53EF\u8FBE\uFF09\uFF0C\n * \u7528\u6237\u4ECE\u5730\u5740\u680F\u590D\u5236\u5B8C\u6574 URL \u56DE\u4F20\u3002\u8C03\u7528\u65B9\u7528 completeRemoteAuth \u5B8C\u6210\u6362 token\u3002\n */\nexport async function prepareRemoteAuth(\n mcpEndpoint?: string,\n): Promise<ProjectAuthSession> {\n const endpoint = mcpEndpoint ?? getProjectMcpEndpoint();\n const metadata = await discoverOAuthMetadata(endpoint);\n\n // \u8FDC\u7A0B\u6A21\u5F0F\u4F7F\u7528\u56FA\u5B9A\u7AEF\u53E3\u7684 redirect_uri\uFF08\u7528\u6237\u9700\u8981\u624B\u52A8\u590D\u5236 URL\uFF09\n const redirectUri = `http://${CALLBACK_HOST}:3456${CALLBACK_PATH}`;\n\n const client = await registerOAuthClient(\n metadata.registration_endpoint,\n redirectUri,\n );\n\n const pkce = generatePKCE();\n const state = randomUUID();\n\n const authorizationUrl = buildAuthorizationUrl({\n authorizationEndpoint: metadata.authorization_endpoint,\n clientId: client.client_id,\n redirectUri,\n codeChallenge: pkce.codeChallenge,\n state,\n });\n\n return { metadata, client, pkce, state, redirectUri, authorizationUrl };\n}\n\n/**\n * \u7528\u6237\u624B\u52A8\u56DE\u4F20 callback URL \u540E\uFF0C\u63D0\u53D6 code \u5E76\u6362 token\u3002\n */\nexport async function completeRemoteAuth(\n session: ProjectAuthSession,\n callbackUrl: string,\n): Promise<LocalAuthResult> {\n const { code, state: receivedState } = extractCodeFromCallbackUrl(callbackUrl);\n\n if (receivedState && receivedState !== session.state) {\n throw new Error('OAuth state mismatch \u2014 \u8BF7\u91CD\u65B0\u53D1\u8D77\u6388\u6743');\n }\n\n const tokens = await exchangeCodeForTokens({\n tokenEndpoint: session.metadata.token_endpoint,\n code,\n codeVerifier: session.pkce.codeVerifier,\n clientId: session.client.client_id,\n redirectUri: session.redirectUri,\n });\n\n return { tokens, clientId: session.client.client_id };\n}\n\n// ---------------------------------------------------------------------------\n// Token \u5237\u65B0\uFF08\u4E24\u79CD\u6A21\u5F0F\u5171\u7528\uFF09\n// ---------------------------------------------------------------------------\n\nexport async function refreshProjectToken(\n mcpEndpoint: string,\n refreshToken: string,\n clientId: string,\n): Promise<OAuthTokens> {\n const metadata = await discoverOAuthMetadata(mcpEndpoint);\n return refreshAccessToken({\n tokenEndpoint: metadata.token_endpoint,\n refreshToken,\n clientId,\n });\n}\n"],
|
|
5
|
-
"mappings": "AAcA,OAAO,UAAU;AACjB,SAAS,kBAAkB;AAC3B,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AACP,SAAS,6BAA6B;AAEtC,MAAM,MAAM,WAAW,mBAAmB;AAE1C,MAAM,gBAAgB;AACtB,MAAM,gBAAgB;AAmBtB,eAAsB,mBACpB,aACA,aAC6B;AAC7B,QAAM,WAAW,eAAe,sBAAsB;AAEtD,MAAI,KAAK,mCAAmC,QAAQ,EAAE;AACtD,QAAM,WAAW,MAAM,sBAAsB,QAAQ;AAErD,QAAM,cAAc,eAAe,UAAU,aAAa,KAAK,aAAa;AAE5E,MAAI,KAAK,mDAAmD;AAC5D,QAAM,SAAS,MAAM;AAAA,IACnB,SAAS;AAAA,IACT;AAAA,EACF;AAEA,QAAM,OAAO,aAAa;AAC1B,QAAM,QAAQ,WAAW;AAEzB,QAAM,mBAAmB,sBAAsB;AAAA,IAC7C,uBAAuB,SAAS;AAAA,IAChC,UAAU,OAAO;AAAA,IACjB,aAAa;AAAA,IACb,eAAe,KAAK;AAAA,IACpB;AAAA,EACF,CAAC;AAED,SAAO,EAAE,UAAU,QAAQ,MAAM,OAAO,aAAa,aAAa,iBAAiB;AACrF;AAqBA,eAAsB,mBACpB,aACA,QAMC;AACD,QAAM,WAAW,eAAe,sBAAsB;AAEtD,MAAI,KAAK,mCAAmC,QAAQ,EAAE;AACtD,QAAM,WAAW,MAAM,sBAAsB,QAAQ;AAGrD,QAAM,SAAS,KAAK,aAAa;AACjC,QAAM,OAAO,MAAM,IAAI,QAAgB,CAAC,SAAS,WAAW;AAC1D,WAAO,OAAO,GAAG,eAAe,MAAM;AACpC,YAAM,OAAO,OAAO,QAAQ;AAC5B,UAAI,QAAQ,OAAO,SAAS,UAAU;AACpC,gBAAQ,KAAK,IAAI;AAAA,MACnB,OAAO;AACL,eAAO,IAAI,MAAM,mCAAmC,CAAC;AAAA,MACvD;AAAA,IACF,CAAC;AACD,WAAO,KAAK,SAAS,MAAM;AAAA,EAC7B,CAAC;AAED,QAAM,cAAc,UAAU,aAAa,IAAI,IAAI,GAAG,aAAa;AAEnE,QAAM,SAAS,MAAM;AAAA,IACnB,SAAS;AAAA,IACT;AAAA,EACF;AAEA,QAAM,OAAO,aAAa;AAC1B,QAAM,QAAQ,WAAW;AAEzB,QAAM,mBAAmB,sBAAsB;AAAA,IAC7C,uBAAuB,SAAS;AAAA,IAChC,UAAU,OAAO;AAAA,IACjB;AAAA,IACA,eAAe,KAAK;AAAA,IACpB;AAAA,EACF,CAAC;AAED,QAAM,UAA8B;AAAA,IAClC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAGA,QAAM,cAAc,MAClB,IAAI,QAAyB,CAAC,SAAS,WAAW;AAChD,UAAM,UAAU,MAAM;AACpB,aAAO,IAAI,MAAM,yBAAyB,CAAC;AAC3C,cAAQ;AAAA,IACV;AACA,YAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AAEzD,UAAM,UAAU,MAAM;AACpB,cAAQ,oBAAoB,SAAS,OAAO;AAAA,IAC9C;AAEA,WAAO,GAAG,WAAW,OAAO,KAAK,QAAQ;AACvC,UAAI;AACF,cAAM,SAAS,IAAI,IAAI,IAAI,OAAO,IAAI,UAAU,aAAa,IAAI,IAAI,EAAE;AACvE,YAAI,OAAO,aAAa,eAAe;AACrC,cAAI,aAAa;AACjB,cAAI,IAAI,WAAW;AACnB;AAAA,QACF;AAEA,cAAM,EAAE,MAAM,OAAO,cAAc,IAAI,2BAA2B,OAAO,SAAS,CAAC;AAEnF,YAAI,iBAAiB,kBAAkB,QAAQ,OAAO;AACpD,cAAI,aAAa;AACjB,cAAI,UAAU,gBAAgB,WAAW;AACzC,cAAI,IAAI,6EAA6E;AACrF,iBAAO,IAAI,MAAM,sBAAsB,CAAC;AACxC,kBAAQ;AACR;AAAA,QACF;AAEA,YAAI,aAAa;AACjB,YAAI,UAAU,gBAAgB,WAAW;AACzC,YAAI,IAAI,2FAA2F;AAEnG,cAAM,SAAS,MAAM,sBAAsB;AAAA,UACzC,eAAe,QAAQ,SAAS;AAAA,UAChC;AAAA,UACA,cAAc,QAAQ,KAAK;AAAA,UAC3B,UAAU,QAAQ,OAAO;AAAA,UACzB,aAAa,QAAQ;AAAA,QACvB,CAAC;AAED,gBAAQ,EAAE,QAAQ,UAAU,QAAQ,OAAO,UAAU,CAAC;AACtD,gBAAQ;AAAA,MACV,SAAS,KAAK;AACZ,YAAI,aAAa;AACjB,YAAI,IAAI,gBAAgB;AACxB,eAAO,GAAG;AACV,gBAAQ;AAAA,MACV;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AAEH,QAAM,QAAQ,YAAY;AACxB,UAAM,IAAI,QAAc,CAAC,YAAY;AACnC,aAAO,MAAM,MAAM,QAAQ,CAAC;AAAA,IAC9B,CAAC;AAAA,EACH;AAEA,SAAO,EAAE,SAAS,aAAa,MAAM,MAAM;AAC7C;AAaA,eAAsB,kBACpB,aAC6B;AAC7B,QAAM,WAAW,eAAe,sBAAsB;AACtD,QAAM,WAAW,MAAM,sBAAsB,QAAQ;AAGrD,QAAM,cAAc,UAAU,aAAa,QAAQ,aAAa;AAEhE,QAAM,SAAS,MAAM;AAAA,IACnB,SAAS;AAAA,IACT;AAAA,EACF;AAEA,QAAM,OAAO,aAAa;AAC1B,QAAM,QAAQ,WAAW;AAEzB,QAAM,mBAAmB,sBAAsB;AAAA,IAC7C,uBAAuB,SAAS;AAAA,IAChC,UAAU,OAAO;AAAA,IACjB;AAAA,IACA,eAAe,KAAK;AAAA,IACpB;AAAA,
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * \u98DE\u4E66\u9879\u76EE\uFF08Meego\uFF09\u72EC\u7ACB OAuth \u7F16\u6392\n *\n * \u57FA\u4E8E MCP \u6807\u51C6 OAuth\uFF08Authorization Code + PKCE + \u52A8\u6001\u5BA2\u6237\u7AEF\u6CE8\u518C\uFF09\uFF0C\n * \u4E0D\u4F9D\u8D56\u98DE\u4E66\u5F00\u653E\u5E73\u53F0 appId/appSecret\uFF0C\u4E5F\u4E0D\u8D70 Device Flow\u3002\n *\n * \u652F\u6301\u4E24\u79CD\u8FD0\u884C\u6A21\u5F0F\uFF1A\n * - \u672C\u5730\u6A21\u5F0F\uFF08\u6709\u6D4F\u89C8\u5668\uFF09\uFF1A\u542F\u52A8 127.0.0.1 \u56DE\u8C03\u670D\u52A1\u5668\uFF0C\u81EA\u52A8\u5B8C\u6210\u6388\u6743\n * - \u8FDC\u7A0B\u6A21\u5F0F\uFF08\u65E0\u6D4F\u89C8\u5668\uFF09\uFF1A\u751F\u6210\u6388\u6743\u94FE\u63A5\uFF0C\u7528\u6237\u5728\u5916\u90E8\u6D4F\u89C8\u5668\u5B8C\u6210\u540E\u56DE\u4F20 URL\n */\n\nimport http from 'node:http';\nimport { randomUUID } from 'node:crypto';\nimport { larkLogger } from './lark-logger';\nimport {\n discoverOAuthMetadata,\n registerOAuthClient,\n generatePKCE,\n buildAuthorizationUrl,\n exchangeCodeForTokens,\n extractCodeFromCallbackUrl,\n refreshAccessToken,\n type OAuthMetadata,\n type OAuthClientInfo,\n type OAuthTokens,\n type PKCEPair,\n} from './project-oauth-flow';\nimport { getProjectMcpEndpoint } from '../tools/mcp/project/endpoint';\n\nconst log = larkLogger('core/project-auth');\n\nconst CALLBACK_HOST = '127.0.0.1';\nconst CALLBACK_PATH = '/callback';\n\n// ---------------------------------------------------------------------------\n// Pending authorization session \u2014 \u5171\u4EAB\u72B6\u6001\uFF0C\u4F9B\u672C\u5730\u56DE\u8C03\u548C\u8FDC\u7A0B\u624B\u52A8\u4E24\u79CD\u6A21\u5F0F\u4F7F\u7528\n// ---------------------------------------------------------------------------\n\nexport interface ProjectAuthSession {\n metadata: OAuthMetadata;\n client: OAuthClientInfo;\n pkce: PKCEPair;\n state: string;\n redirectUri: string;\n authorizationUrl: string;\n}\n\n// ---------------------------------------------------------------------------\n// \u516C\u5171\u6B65\u9AA4\uFF1A\u53D1\u73B0 + \u6CE8\u518C + PKCE + \u6784\u5EFA\u6388\u6743 URL\n// ---------------------------------------------------------------------------\n\nexport async function prepareAuthSession(\n mcpEndpoint?: string,\n redirectUri?: string,\n): Promise<ProjectAuthSession> {\n const endpoint = mcpEndpoint ?? getProjectMcpEndpoint();\n\n log.info(`discovering OAuth metadata from ${endpoint}`);\n const metadata = await discoverOAuthMetadata(endpoint);\n\n const callbackUri = redirectUri ?? `http://${CALLBACK_HOST}:0${CALLBACK_PATH}`;\n\n log.info('registering OAuth client via dynamic registration');\n const client = await registerOAuthClient(\n metadata.registration_endpoint,\n callbackUri,\n );\n\n const pkce = generatePKCE();\n const state = randomUUID();\n\n const authorizationUrl = buildAuthorizationUrl({\n authorizationEndpoint: metadata.authorization_endpoint,\n clientId: client.client_id,\n redirectUri: callbackUri,\n codeChallenge: pkce.codeChallenge,\n state,\n });\n\n return { metadata, client, pkce, state, redirectUri: callbackUri, authorizationUrl };\n}\n\n// ---------------------------------------------------------------------------\n// \u6A21\u5F0F 1\uFF1A\u672C\u5730\u56DE\u8C03\uFF08\u6709\u6D4F\u89C8\u5668\uFF09\n// ---------------------------------------------------------------------------\n\nexport interface LocalAuthResult {\n tokens: OAuthTokens;\n clientId: string;\n}\n\n/**\n * \u542F\u52A8\u672C\u5730 HTTP \u56DE\u8C03\u670D\u52A1\u5668\uFF0C\u7B49\u5F85\u6D4F\u89C8\u5668\u91CD\u5B9A\u5411\u5E26\u56DE authorization code\u3002\n *\n * \u6D41\u7A0B\uFF1A\n * 1. prepareAuthSession\uFF08\u52A8\u6001\u6CE8\u518C\u65F6\u7528\u5B9E\u9645\u7AEF\u53E3\u7684 redirect_uri\uFF09\n * 2. \u542F\u52A8 127.0.0.1 \u56DE\u8C03\u670D\u52A1\u5668\n * 3. \u6253\u5F00\u6D4F\u89C8\u5668\uFF08\u8C03\u7528\u65B9\u8D1F\u8D23\uFF09\n * 4. \u7B49\u5F85\u56DE\u8C03 \u2192 \u63D0\u53D6 code \u2192 \u6362 token\n * 5. \u5173\u95ED\u670D\u52A1\u5668\n */\nexport async function startLocalAuthFlow(\n mcpEndpoint?: string,\n signal?: AbortSignal,\n): Promise<{\n session: ProjectAuthSession;\n waitForCode: () => Promise<LocalAuthResult>;\n port: number;\n close: () => Promise<void>;\n}> {\n const endpoint = mcpEndpoint ?? getProjectMcpEndpoint();\n\n log.info(`discovering OAuth metadata from ${endpoint}`);\n const metadata = await discoverOAuthMetadata(endpoint);\n\n // \u5148\u542F\u52A8\u670D\u52A1\u5668\u62FF\u5230\u5B9E\u9645\u7AEF\u53E3\uFF0C\u518D\u7528\u8BE5\u7AEF\u53E3\u6CE8\u518C\u5BA2\u6237\u7AEF\n const server = http.createServer();\n const port = await new Promise<number>((resolve, reject) => {\n server.listen(0, CALLBACK_HOST, () => {\n const addr = server.address();\n if (addr && typeof addr === 'object') {\n resolve(addr.port);\n } else {\n reject(new Error('Failed to determine callback port'));\n }\n });\n server.once('error', reject);\n });\n\n const redirectUri = `http://${CALLBACK_HOST}:${port}${CALLBACK_PATH}`;\n\n const client = await registerOAuthClient(\n metadata.registration_endpoint,\n redirectUri,\n );\n\n const pkce = generatePKCE();\n const state = randomUUID();\n\n const authorizationUrl = buildAuthorizationUrl({\n authorizationEndpoint: metadata.authorization_endpoint,\n clientId: client.client_id,\n redirectUri,\n codeChallenge: pkce.codeChallenge,\n state,\n });\n\n const session: ProjectAuthSession = {\n metadata,\n client,\n pkce,\n state,\n redirectUri,\n authorizationUrl,\n };\n\n // \u7B49\u5F85\u56DE\u8C03\u7684 Promise\n const waitForCode = (): Promise<LocalAuthResult> =>\n new Promise<LocalAuthResult>((resolve, reject) => {\n const onAbort = () => {\n reject(new Error('Authorization cancelled'));\n cleanup();\n };\n signal?.addEventListener('abort', onAbort, { once: true });\n\n const cleanup = () => {\n signal?.removeEventListener('abort', onAbort);\n };\n\n server.on('request', async (req, res) => {\n try {\n const reqUrl = new URL(req.url ?? '', `http://${CALLBACK_HOST}:${port}`);\n if (reqUrl.pathname !== CALLBACK_PATH) {\n res.statusCode = 404;\n res.end('Not found');\n return;\n }\n\n const { code, state: receivedState } = extractCodeFromCallbackUrl(reqUrl.toString());\n\n if (receivedState && receivedState !== session.state) {\n res.statusCode = 400;\n res.setHeader('Content-Type', 'text/html');\n res.end('<html><body><h1>Authorization failed</h1><p>Invalid state</p></body></html>');\n reject(new Error('OAuth state mismatch'));\n cleanup();\n return;\n }\n\n res.statusCode = 200;\n res.setHeader('Content-Type', 'text/html');\n res.end('<html><body><h1>Authorization successful</h1><p>You can close this tab.</p></body></html>');\n\n const tokens = await exchangeCodeForTokens({\n tokenEndpoint: session.metadata.token_endpoint,\n code,\n codeVerifier: session.pkce.codeVerifier,\n clientId: session.client.client_id,\n redirectUri: session.redirectUri,\n });\n\n resolve({ tokens, clientId: session.client.client_id });\n cleanup();\n } catch (err) {\n res.statusCode = 500;\n res.end('Internal error');\n reject(err);\n cleanup();\n }\n });\n });\n\n const close = async () => {\n await new Promise<void>((resolve) => {\n server.close(() => resolve());\n });\n };\n\n return { session, waitForCode, port, close };\n}\n\n// ---------------------------------------------------------------------------\n// \u6A21\u5F0F 2\uFF1A\u8FDC\u7A0B\u624B\u52A8\uFF08\u65E0\u6D4F\u89C8\u5668\uFF09\n// ---------------------------------------------------------------------------\n\n/**\n * \u4E3A\u8FDC\u7A0B\uFF08\u65E0\u6D4F\u89C8\u5668\uFF09\u573A\u666F\u51C6\u5907\u6388\u6743\u3002\n *\n * \u8FD4\u56DE session \u548C authorizationUrl\uFF0C\u8C03\u7528\u65B9\u5C06 URL \u53D1\u9001\u7ED9\u7528\u6237\u3002\n * \u7528\u6237\u5728\u672C\u5730\u6D4F\u89C8\u5668\u5B8C\u6210\u6388\u6743\u540E\uFF0C\u6D4F\u89C8\u5668\u91CD\u5B9A\u5411\u5230 127.0.0.1\uFF08\u4E0D\u53EF\u8FBE\uFF09\uFF0C\n * \u7528\u6237\u4ECE\u5730\u5740\u680F\u590D\u5236\u5B8C\u6574 URL \u56DE\u4F20\u3002\u8C03\u7528\u65B9\u7528 completeRemoteAuth \u5B8C\u6210\u6362 token\u3002\n */\nexport async function prepareRemoteAuth(\n mcpEndpoint?: string,\n): Promise<ProjectAuthSession> {\n const endpoint = mcpEndpoint ?? getProjectMcpEndpoint();\n const metadata = await discoverOAuthMetadata(endpoint);\n\n // \u8FDC\u7A0B\u6A21\u5F0F\u4F7F\u7528\u56FA\u5B9A\u7AEF\u53E3\u7684 redirect_uri\uFF08\u7528\u6237\u9700\u8981\u624B\u52A8\u590D\u5236 URL\uFF09\n const redirectUri = `http://${CALLBACK_HOST}:3456${CALLBACK_PATH}`;\n\n const client = await registerOAuthClient(\n metadata.registration_endpoint,\n redirectUri,\n );\n\n const pkce = generatePKCE();\n const state = randomUUID();\n\n const authorizationUrl = buildAuthorizationUrl({\n authorizationEndpoint: metadata.authorization_endpoint,\n clientId: client.client_id,\n redirectUri,\n codeChallenge: pkce.codeChallenge,\n state,\n extraParams: { redirect_mode: 'manual' },\n });\n\n return { metadata, client, pkce, state, redirectUri, authorizationUrl };\n}\n\n/**\n * \u7528\u6237\u624B\u52A8\u56DE\u4F20 callback URL \u540E\uFF0C\u63D0\u53D6 code \u5E76\u6362 token\u3002\n */\nexport async function completeRemoteAuth(\n session: ProjectAuthSession,\n callbackUrl: string,\n): Promise<LocalAuthResult> {\n const { code, state: receivedState } = extractCodeFromCallbackUrl(callbackUrl);\n\n if (receivedState && receivedState !== session.state) {\n throw new Error('OAuth state mismatch \u2014 \u8BF7\u91CD\u65B0\u53D1\u8D77\u6388\u6743');\n }\n\n const tokens = await exchangeCodeForTokens({\n tokenEndpoint: session.metadata.token_endpoint,\n code,\n codeVerifier: session.pkce.codeVerifier,\n clientId: session.client.client_id,\n redirectUri: session.redirectUri,\n });\n\n return { tokens, clientId: session.client.client_id };\n}\n\n// ---------------------------------------------------------------------------\n// Token \u5237\u65B0\uFF08\u4E24\u79CD\u6A21\u5F0F\u5171\u7528\uFF09\n// ---------------------------------------------------------------------------\n\nexport async function refreshProjectToken(\n mcpEndpoint: string,\n refreshToken: string,\n clientId: string,\n): Promise<OAuthTokens> {\n const metadata = await discoverOAuthMetadata(mcpEndpoint);\n return refreshAccessToken({\n tokenEndpoint: metadata.token_endpoint,\n refreshToken,\n clientId,\n });\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -57,6 +57,11 @@ function buildAuthorizationUrl(params) {
|
|
|
57
57
|
if (params.scope) {
|
|
58
58
|
url.searchParams.set("scope", params.scope);
|
|
59
59
|
}
|
|
60
|
+
if (params.extraParams) {
|
|
61
|
+
for (const [key, value] of Object.entries(params.extraParams)) {
|
|
62
|
+
url.searchParams.set(key, value);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
60
65
|
return url.toString();
|
|
61
66
|
}
|
|
62
67
|
async function exchangeCodeForTokens(params) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/core/project-oauth-flow.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 OAuth \u539F\u8BED\n *\n * \u5B9E\u73B0\u6807\u51C6 MCP OAuth \u6D41\u7A0B\uFF1A\n * 1. \u53D1\u73B0 OAuth \u7AEF\u70B9 (RFC 8414)\n * 2. \u52A8\u6001\u5BA2\u6237\u7AEF\u6CE8\u518C (RFC 7591)\n * 3. PKCE (RFC 7636)\n * 4. Authorization Code \u6362 token\n * 5. Refresh token\n *\n * \u4E0D\u4F9D\u8D56\u4EFB\u4F55\u7B2C\u4E09\u65B9 OAuth \u5E93\uFF0C\u4EC5\u4F7F\u7528 Node 18+ \u5185\u7F6E fetch + crypto\u3002\n */\n\nimport { randomBytes, createHash } from 'node:crypto';\nimport { larkLogger } from './lark-logger';\n\nconst log = larkLogger('core/project-oauth-flow');\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface OAuthMetadata {\n issuer: string;\n authorization_endpoint: string;\n token_endpoint: string;\n registration_endpoint: string;\n response_types_supported: string[];\n grant_types_supported: string[];\n code_challenge_methods_supported: string[];\n scopes_supported: string[];\n}\n\nexport interface OAuthClientInfo {\n client_id: string;\n client_secret?: string;\n redirect_uris: string[];\n client_name: string;\n}\n\nexport interface PKCEPair {\n codeVerifier: string;\n codeChallenge: string;\n}\n\nexport interface OAuthTokens {\n access_token: string;\n refresh_token?: string;\n token_type: string;\n expires_in?: number;\n scope?: string;\n}\n\n// ---------------------------------------------------------------------------\n// 1. \u53D1\u73B0 OAuth \u7AEF\u70B9\n// ---------------------------------------------------------------------------\n\nexport async function discoverOAuthMetadata(serverUrl: string): Promise<OAuthMetadata> {\n const base = serverUrl.replace(/\\/+$/, '');\n // MCP \u89C4\u8303\uFF1A\u5148\u5C1D\u8BD5 path-aware\uFF0C\u518D\u56DE\u9000 root\n const origin = new URL(base).origin;\n const candidates = [\n `${base}/.well-known/oauth-authorization-server`,\n `${origin}/.well-known/oauth-authorization-server`,\n ];\n\n for (const url of candidates) {\n const res = await fetch(url);\n if (res.ok) {\n const data = (await res.json()) as OAuthMetadata;\n log.info(`OAuth metadata discovered from ${url}`);\n return data;\n }\n }\n\n throw new Error(\n `Failed to discover OAuth metadata from ${serverUrl} (tried ${candidates.join(', ')})`,\n );\n}\n\n// ---------------------------------------------------------------------------\n// 2. \u52A8\u6001\u5BA2\u6237\u7AEF\u6CE8\u518C\n// ---------------------------------------------------------------------------\n\nexport async function registerOAuthClient(\n registrationEndpoint: string,\n redirectUri: string,\n clientName?: string,\n): Promise<OAuthClientInfo> {\n const body = {\n client_name: clientName ?? 'openclaw-feishu-project',\n redirect_uris: [redirectUri],\n grant_types: ['authorization_code', 'refresh_token'],\n response_types: ['code'],\n token_endpoint_auth_method: 'none',\n };\n\n const res = await fetch(registrationEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n\n const text = await res.text();\n if (!res.ok) {\n throw new Error(`OAuth client registration failed: HTTP ${res.status} \u2013 ${text.slice(0, 500)}`);\n }\n\n const data = JSON.parse(text) as OAuthClientInfo;\n log.info(`OAuth client registered: client_id=${data.client_id}`);\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// 3. PKCE\n// ---------------------------------------------------------------------------\n\nexport function generatePKCE(): PKCEPair {\n const codeVerifier = randomBytes(32).toString('base64url');\n const codeChallenge = createHash('sha256').update(codeVerifier).digest('base64url');\n return { codeVerifier, codeChallenge };\n}\n\n// ---------------------------------------------------------------------------\n// 4. \u6784\u5EFA\u6388\u6743 URL\n// ---------------------------------------------------------------------------\n\nexport function buildAuthorizationUrl(params: {\n authorizationEndpoint: string;\n clientId: string;\n redirectUri: string;\n codeChallenge: string;\n state: string;\n scope?: string;\n}): string {\n const url = new URL(params.authorizationEndpoint);\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('client_id', params.clientId);\n url.searchParams.set('redirect_uri', params.redirectUri);\n url.searchParams.set('code_challenge', params.codeChallenge);\n url.searchParams.set('code_challenge_method', 'S256');\n url.searchParams.set('state', params.state);\n if (params.scope) {\n url.searchParams.set('scope', params.scope);\n }\n return url.toString();\n}\n\n// ---------------------------------------------------------------------------\n// 5. Authorization Code \u2192 Token\n// ---------------------------------------------------------------------------\n\nexport async function exchangeCodeForTokens(params: {\n tokenEndpoint: string;\n code: string;\n codeVerifier: string;\n clientId: string;\n redirectUri: string;\n}): Promise<OAuthTokens> {\n const body = new URLSearchParams({\n grant_type: 'authorization_code',\n code: params.code,\n redirect_uri: params.redirectUri,\n client_id: params.clientId,\n code_verifier: params.codeVerifier,\n });\n\n const res = await fetch(params.tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n const text = await res.text();\n if (!res.ok) {\n throw new Error(`Token exchange failed: HTTP ${res.status} \u2013 ${text.slice(0, 500)}`);\n }\n\n const data = JSON.parse(text) as OAuthTokens;\n log.info('Authorization code exchanged for tokens');\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// 6. Refresh Token\n// ---------------------------------------------------------------------------\n\nexport async function refreshAccessToken(params: {\n tokenEndpoint: string;\n refreshToken: string;\n clientId: string;\n}): Promise<OAuthTokens> {\n const body = new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: params.refreshToken,\n client_id: params.clientId,\n });\n\n const res = await fetch(params.tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n const text = await res.text();\n if (!res.ok) {\n throw new Error(`Token refresh failed: HTTP ${res.status} \u2013 ${text.slice(0, 500)}`);\n }\n\n const data = JSON.parse(text) as OAuthTokens;\n log.info('Access token refreshed');\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// 7. \u4ECE\u56DE\u8C03 URL \u4E2D\u63D0\u53D6 code \u548C state\n// ---------------------------------------------------------------------------\n\nexport function extractCodeFromCallbackUrl(\n callbackUrl: string,\n): { code: string; state: string | null } {\n const url = new URL(callbackUrl);\n const code = url.searchParams.get('code');\n const error = url.searchParams.get('error');\n if (error) {\n const desc = url.searchParams.get('error_description') ?? error;\n throw new Error(`OAuth authorization denied: ${desc}`);\n }\n if (!code) {\n throw new Error('Callback URL missing authorization code');\n }\n return { code, state: url.searchParams.get('state') };\n}\n"],
|
|
5
|
-
"mappings": "AAgBA,SAAS,aAAa,kBAAkB;AACxC,SAAS,kBAAkB;AAE3B,MAAM,MAAM,WAAW,yBAAyB;AAyChD,eAAsB,sBAAsB,WAA2C;AACrF,QAAM,OAAO,UAAU,QAAQ,QAAQ,EAAE;AAEzC,QAAM,SAAS,IAAI,IAAI,IAAI,EAAE;AAC7B,QAAM,aAAa;AAAA,IACjB,GAAG,IAAI;AAAA,IACP,GAAG,MAAM;AAAA,EACX;AAEA,aAAW,OAAO,YAAY;AAC5B,UAAM,MAAM,MAAM,MAAM,GAAG;AAC3B,QAAI,IAAI,IAAI;AACV,YAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,UAAI,KAAK,kCAAkC,GAAG,EAAE;AAChD,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,IAAI;AAAA,IACR,0CAA0C,SAAS,WAAW,WAAW,KAAK,IAAI,CAAC;AAAA,EACrF;AACF;AAMA,eAAsB,oBACpB,sBACA,aACA,YAC0B;AAC1B,QAAM,OAAO;AAAA,IACX,aAAa,cAAc;AAAA,IAC3B,eAAe,CAAC,WAAW;AAAA,IAC3B,aAAa,CAAC,sBAAsB,eAAe;AAAA,IACnD,gBAAgB,CAAC,MAAM;AAAA,IACvB,4BAA4B;AAAA,EAC9B;AAEA,QAAM,MAAM,MAAM,MAAM,sBAAsB;AAAA,IAC5C,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM,KAAK,UAAU,IAAI;AAAA,EAC3B,CAAC;AAED,QAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,MAAM,0CAA0C,IAAI,MAAM,WAAM,KAAK,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,EAChG;AAEA,QAAM,OAAO,KAAK,MAAM,IAAI;AAC5B,MAAI,KAAK,sCAAsC,KAAK,SAAS,EAAE;AAC/D,SAAO;AACT;AAMO,SAAS,eAAyB;AACvC,QAAM,eAAe,YAAY,EAAE,EAAE,SAAS,WAAW;AACzD,QAAM,gBAAgB,WAAW,QAAQ,EAAE,OAAO,YAAY,EAAE,OAAO,WAAW;AAClF,SAAO,EAAE,cAAc,cAAc;AACvC;AAMO,SAAS,sBAAsB,
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * \u98DE\u4E66\u9879\u76EE MCP OAuth \u539F\u8BED\n *\n * \u5B9E\u73B0\u6807\u51C6 MCP OAuth \u6D41\u7A0B\uFF1A\n * 1. \u53D1\u73B0 OAuth \u7AEF\u70B9 (RFC 8414)\n * 2. \u52A8\u6001\u5BA2\u6237\u7AEF\u6CE8\u518C (RFC 7591)\n * 3. PKCE (RFC 7636)\n * 4. Authorization Code \u6362 token\n * 5. Refresh token\n *\n * \u4E0D\u4F9D\u8D56\u4EFB\u4F55\u7B2C\u4E09\u65B9 OAuth \u5E93\uFF0C\u4EC5\u4F7F\u7528 Node 18+ \u5185\u7F6E fetch + crypto\u3002\n */\n\nimport { randomBytes, createHash } from 'node:crypto';\nimport { larkLogger } from './lark-logger';\n\nconst log = larkLogger('core/project-oauth-flow');\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface OAuthMetadata {\n issuer: string;\n authorization_endpoint: string;\n token_endpoint: string;\n registration_endpoint: string;\n response_types_supported: string[];\n grant_types_supported: string[];\n code_challenge_methods_supported: string[];\n scopes_supported: string[];\n}\n\nexport interface OAuthClientInfo {\n client_id: string;\n client_secret?: string;\n redirect_uris: string[];\n client_name: string;\n}\n\nexport interface PKCEPair {\n codeVerifier: string;\n codeChallenge: string;\n}\n\nexport interface OAuthTokens {\n access_token: string;\n refresh_token?: string;\n token_type: string;\n expires_in?: number;\n scope?: string;\n}\n\n// ---------------------------------------------------------------------------\n// 1. \u53D1\u73B0 OAuth \u7AEF\u70B9\n// ---------------------------------------------------------------------------\n\nexport async function discoverOAuthMetadata(serverUrl: string): Promise<OAuthMetadata> {\n const base = serverUrl.replace(/\\/+$/, '');\n // MCP \u89C4\u8303\uFF1A\u5148\u5C1D\u8BD5 path-aware\uFF0C\u518D\u56DE\u9000 root\n const origin = new URL(base).origin;\n const candidates = [\n `${base}/.well-known/oauth-authorization-server`,\n `${origin}/.well-known/oauth-authorization-server`,\n ];\n\n for (const url of candidates) {\n const res = await fetch(url);\n if (res.ok) {\n const data = (await res.json()) as OAuthMetadata;\n log.info(`OAuth metadata discovered from ${url}`);\n return data;\n }\n }\n\n throw new Error(\n `Failed to discover OAuth metadata from ${serverUrl} (tried ${candidates.join(', ')})`,\n );\n}\n\n// ---------------------------------------------------------------------------\n// 2. \u52A8\u6001\u5BA2\u6237\u7AEF\u6CE8\u518C\n// ---------------------------------------------------------------------------\n\nexport async function registerOAuthClient(\n registrationEndpoint: string,\n redirectUri: string,\n clientName?: string,\n): Promise<OAuthClientInfo> {\n const body = {\n client_name: clientName ?? 'openclaw-feishu-project',\n redirect_uris: [redirectUri],\n grant_types: ['authorization_code', 'refresh_token'],\n response_types: ['code'],\n token_endpoint_auth_method: 'none',\n };\n\n const res = await fetch(registrationEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n\n const text = await res.text();\n if (!res.ok) {\n throw new Error(`OAuth client registration failed: HTTP ${res.status} \u2013 ${text.slice(0, 500)}`);\n }\n\n const data = JSON.parse(text) as OAuthClientInfo;\n log.info(`OAuth client registered: client_id=${data.client_id}`);\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// 3. PKCE\n// ---------------------------------------------------------------------------\n\nexport function generatePKCE(): PKCEPair {\n const codeVerifier = randomBytes(32).toString('base64url');\n const codeChallenge = createHash('sha256').update(codeVerifier).digest('base64url');\n return { codeVerifier, codeChallenge };\n}\n\n// ---------------------------------------------------------------------------\n// 4. \u6784\u5EFA\u6388\u6743 URL\n// ---------------------------------------------------------------------------\n\nexport function buildAuthorizationUrl(params: {\n authorizationEndpoint: string;\n clientId: string;\n redirectUri: string;\n codeChallenge: string;\n state: string;\n scope?: string;\n extraParams?: Record<string, string>;\n}): string {\n const url = new URL(params.authorizationEndpoint);\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('client_id', params.clientId);\n url.searchParams.set('redirect_uri', params.redirectUri);\n url.searchParams.set('code_challenge', params.codeChallenge);\n url.searchParams.set('code_challenge_method', 'S256');\n url.searchParams.set('state', params.state);\n if (params.scope) {\n url.searchParams.set('scope', params.scope);\n }\n if (params.extraParams) {\n for (const [key, value] of Object.entries(params.extraParams)) {\n url.searchParams.set(key, value);\n }\n }\n return url.toString();\n}\n\n// ---------------------------------------------------------------------------\n// 5. Authorization Code \u2192 Token\n// ---------------------------------------------------------------------------\n\nexport async function exchangeCodeForTokens(params: {\n tokenEndpoint: string;\n code: string;\n codeVerifier: string;\n clientId: string;\n redirectUri: string;\n}): Promise<OAuthTokens> {\n const body = new URLSearchParams({\n grant_type: 'authorization_code',\n code: params.code,\n redirect_uri: params.redirectUri,\n client_id: params.clientId,\n code_verifier: params.codeVerifier,\n });\n\n const res = await fetch(params.tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n const text = await res.text();\n if (!res.ok) {\n throw new Error(`Token exchange failed: HTTP ${res.status} \u2013 ${text.slice(0, 500)}`);\n }\n\n const data = JSON.parse(text) as OAuthTokens;\n log.info('Authorization code exchanged for tokens');\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// 6. Refresh Token\n// ---------------------------------------------------------------------------\n\nexport async function refreshAccessToken(params: {\n tokenEndpoint: string;\n refreshToken: string;\n clientId: string;\n}): Promise<OAuthTokens> {\n const body = new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: params.refreshToken,\n client_id: params.clientId,\n });\n\n const res = await fetch(params.tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n const text = await res.text();\n if (!res.ok) {\n throw new Error(`Token refresh failed: HTTP ${res.status} \u2013 ${text.slice(0, 500)}`);\n }\n\n const data = JSON.parse(text) as OAuthTokens;\n log.info('Access token refreshed');\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// 7. \u4ECE\u56DE\u8C03 URL \u4E2D\u63D0\u53D6 code \u548C state\n// ---------------------------------------------------------------------------\n\nexport function extractCodeFromCallbackUrl(\n callbackUrl: string,\n): { code: string; state: string | null } {\n const url = new URL(callbackUrl);\n const code = url.searchParams.get('code');\n const error = url.searchParams.get('error');\n if (error) {\n const desc = url.searchParams.get('error_description') ?? error;\n throw new Error(`OAuth authorization denied: ${desc}`);\n }\n if (!code) {\n throw new Error('Callback URL missing authorization code');\n }\n return { code, state: url.searchParams.get('state') };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -416,15 +416,18 @@ async function handleCardAction(data, cfg, accountId) {
|
|
|
416
416
|
let action;
|
|
417
417
|
let operationId;
|
|
418
418
|
let senderOpenId;
|
|
419
|
+
let buttonName;
|
|
419
420
|
try {
|
|
420
421
|
const event = data;
|
|
421
422
|
action = event.action?.value?.action;
|
|
422
423
|
operationId = event.action?.value?.operation_id;
|
|
423
424
|
senderOpenId = event.operator?.open_id;
|
|
425
|
+
buttonName = event.action?.name;
|
|
426
|
+
log.debug(`card action received: action=${action}, buttonName=${buttonName}, operationId=${operationId}`);
|
|
424
427
|
} catch {
|
|
425
428
|
return;
|
|
426
429
|
}
|
|
427
|
-
if (action === "project_auth_complete") {
|
|
430
|
+
if (action === "project_auth_complete" || buttonName === "submit_project_auth") {
|
|
428
431
|
return handleProjectAuthCardAction(data, cfg, accountId);
|
|
429
432
|
}
|
|
430
433
|
if (action !== "app_auth_done" || !operationId) return;
|
|
@@ -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\n try {\n const event = data as {\n operator?: { open_id?: string };\n action?: { value?: { action?: string; operation_id?: string } };\n };\n action = event.action?.value?.action;\n operationId = event.action?.value?.operation_id;\n senderOpenId = event.operator?.open_id;\n } catch {\n return;\n }\n\n if (action === 'project_auth_complete') {\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 } 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 // \u8868\u5355\u63D0\u4EA4\u6309\u94AE\u7684 value \u53EF\u80FD\u4E0D\u88AB\u98DE\u4E66\u4F20\u9012\uFF0C\u901A\u8FC7 button name \u540E\u5907\u8BC6\u522B\n if (action === 'project_auth_complete' || 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
|
}
|
|
@@ -134,6 +134,10 @@ ${batchInfo}`;
|
|
|
134
134
|
const message = desc + "\n\n\u6240\u9700\u6743\u9650\uFF1A\n" + scopes.map((s) => `- ${s}`).join("\n");
|
|
135
135
|
return message;
|
|
136
136
|
}
|
|
137
|
+
function toSystemBrowserUrl(targetUrl) {
|
|
138
|
+
const encoded = encodeURIComponent(targetUrl);
|
|
139
|
+
return `https://applink.feishu.cn/client/web_url/open?mode=browser&url=${encoded}`;
|
|
140
|
+
}
|
|
137
141
|
function toInAppWebUrl(targetUrl) {
|
|
138
142
|
const encoded = encodeURIComponent(targetUrl);
|
|
139
143
|
const lkMeta = encodeURIComponent(
|
|
@@ -148,8 +152,8 @@ function toInAppWebUrl(targetUrl) {
|
|
|
148
152
|
}
|
|
149
153
|
function buildProjectAuthCard(params) {
|
|
150
154
|
const { authorizationUrl, operationId, expiresMin } = params;
|
|
151
|
-
const
|
|
152
|
-
const multiUrl = { url:
|
|
155
|
+
const browserUrl = toSystemBrowserUrl(authorizationUrl);
|
|
156
|
+
const multiUrl = { url: browserUrl, pc_url: browserUrl, android_url: browserUrl, ios_url: browserUrl };
|
|
153
157
|
return {
|
|
154
158
|
schema: "2.0",
|
|
155
159
|
config: { wide_screen_mode: false },
|
|
@@ -353,6 +357,7 @@ export {
|
|
|
353
357
|
buildAuthSuccessCard,
|
|
354
358
|
buildProjectAuthCard,
|
|
355
359
|
formatScopeDescription,
|
|
356
|
-
toInAppWebUrl
|
|
360
|
+
toInAppWebUrl,
|
|
361
|
+
toSystemBrowserUrl
|
|
357
362
|
};
|
|
358
363
|
//# sourceMappingURL=oauth-cards.js.map
|
|
@@ -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 operationId: string;\n expiresMin: number;\n}): Record<string, unknown> {\n const { authorizationUrl, operationId, expiresMin } = params;\n const inAppUrl = toInAppWebUrl(authorizationUrl);\n const multiUrl = { url: inAppUrl, pc_url: inAppUrl, android_url: inAppUrl, ios_url: inAppUrl };\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 value: { action: 'project_auth_complete', operation_id: operationId },\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 toSystemBrowserUrl(targetUrl: string): string {\n const encoded = encodeURIComponent(targetUrl);\n return `https://applink.feishu.cn/client/web_url/open?mode=browser&url=${encoded}`;\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 operationId: string;\n expiresMin: number;\n}): Record<string, unknown> {\n const { authorizationUrl, operationId, expiresMin } = params;\n const browserUrl = toSystemBrowserUrl(authorizationUrl);\n const multiUrl = { url: browserUrl, pc_url: browserUrl, android_url: browserUrl, ios_url: browserUrl };\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 value: { action: 'project_auth_complete', operation_id: operationId },\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": "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,mBAAmB,WAA2B;AAC5D,QAAM,UAAU,mBAAmB,SAAS;AAC5C,SAAO,kEAAkE,OAAO;AAClF;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,QAIT;AAC1B,QAAM,EAAE,kBAAkB,aAAa,WAAW,IAAI;AACtD,QAAM,aAAa,mBAAmB,gBAAgB;AACtD,QAAM,WAAW,EAAE,KAAK,YAAY,QAAQ,YAAY,aAAa,YAAY,SAAS,WAAW;AAErG,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,cACN,OAAO,EAAE,QAAQ,yBAAyB,cAAc,YAAY;AAAA,YACtE;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;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
|
}
|