@lark-project/openclaw-lark-project 2026.3.166 → 2026.3.168

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.
@@ -13,6 +13,10 @@ const ReplyModeSchema = z.union([
13
13
  ]).optional();
14
14
  const ChunkModeEnum = z.enum(["newline", "paragraph", "none"]);
15
15
  const DomainSchema = z.union([z.literal("feishu"), z.literal("lark"), z.string().regex(/^https:\/\//)]).optional();
16
+ const ProjectDomainSchema = z.union([z.literal("feishu"), z.literal("meegle"), z.string().regex(/^https:\/\//)]).optional();
17
+ const FeishuProjectConfigSchema = z.object({
18
+ domain: ProjectDomainSchema
19
+ }).optional();
16
20
  const AllowFromSchema = z.union([z.string(), z.array(z.string())]).optional().transform((v) => {
17
21
  if (v === void 0 || v === null) return void 0;
18
22
  return Array.isArray(v) ? v : [v];
@@ -117,7 +121,8 @@ const FeishuAccountConfigSchema = z.object({
117
121
  dedup: DedupSchema,
118
122
  reactionNotifications: ReactionNotificationModeSchema,
119
123
  threadSession: z.boolean().optional(),
120
- uat: UATConfigSchema
124
+ uat: UATConfigSchema,
125
+ project: FeishuProjectConfigSchema
121
126
  });
122
127
  const FeishuConfigSchema = FeishuAccountConfigSchema.extend({
123
128
  accounts: z.record(z.string(), FeishuAccountConfigSchema).optional()
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/core/config-schema.ts"],
4
- "sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * Zod-based configuration schema for the OpenClaw Lark/Feishu channel plugin.\n *\n * Provides runtime validation, sensible defaults, and cross-field refinements\n * so that every consuming module can rely on well-typed configuration objects.\n */\n\nimport { z, toJSONSchema } from 'zod';\n\nexport { z };\n\n// ---------------------------------------------------------------------------\n// Shared micro-schemas\n// ---------------------------------------------------------------------------\n\nconst DmPolicyEnum = z.enum(['open', 'pairing', 'allowlist', 'disabled']);\nconst GroupPolicyEnum = z.enum(['open', 'allowlist', 'disabled']);\nconst ConnectionModeEnum = z.enum(['websocket', 'webhook']);\nconst ReplyModeValue = z.enum(['auto', 'static', 'streaming']);\nconst ReplyModeSchema = z\n .union([\n ReplyModeValue,\n z.object({\n default: ReplyModeValue.optional(),\n group: ReplyModeValue.optional(),\n direct: ReplyModeValue.optional(),\n }),\n ])\n .optional();\nconst ChunkModeEnum = z.enum(['newline', 'paragraph', 'none']);\n\nconst DomainSchema = z.union([z.literal('feishu'), z.literal('lark'), z.string().regex(/^https:\\/\\//)]).optional();\n\nconst AllowFromSchema = z\n .union([z.string(), z.array(z.string())])\n .optional()\n .transform((v) => {\n if (v === undefined || v === null) return undefined;\n return Array.isArray(v) ? v : [v];\n });\n\nconst ToolPolicySchema = z\n .object({\n allow: z.array(z.string()).optional(),\n deny: z.array(z.string()).optional(),\n })\n .optional();\n\nconst FeishuToolsFlagSchema = z\n .object({\n doc: z.boolean().optional(),\n wiki: z.boolean().optional(),\n drive: z.boolean().optional(),\n perm: z.boolean().optional(),\n scopes: z.boolean().optional(),\n })\n .optional();\n\nconst FeishuFooterSchema = z\n .object({\n status: z.boolean().optional(),\n elapsed: z.boolean().optional(),\n })\n .optional();\n\nconst BlockStreamingCoalesceSchema = z\n .object({\n minChars: z.number().optional(),\n maxChars: z.number().optional(),\n idleMs: z.number().optional(),\n })\n .optional();\n\nconst MarkdownConfigSchema = z\n .object({\n tables: z.enum(['off', 'bullets', 'code']).optional(),\n })\n .optional();\n\nconst HeartbeatSchema = z\n .object({\n every: z.string().optional(),\n activeHours: z\n .object({\n start: z.string().optional(),\n end: z.string().optional(),\n timezone: z.string().optional(),\n })\n .optional(),\n target: z.string().optional(),\n to: z.string().optional(),\n prompt: z.string().optional(),\n accountId: z.string().optional(),\n })\n .optional();\n\nconst CapabilitiesSchema = z\n .object({\n image: z.boolean().optional(),\n audio: z.boolean().optional(),\n video: z.boolean().optional(),\n })\n .optional();\n\nconst DedupSchema = z\n .object({\n ttlMs: z.number().optional(), // default 43200000 (12h)\n maxEntries: z.number().optional(), // default 5000\n })\n .optional();\n\nconst ReactionNotificationModeSchema = z.enum(['off', 'own', 'all']).optional();\n\nexport const UATConfigSchema = z\n .object({\n enabled: z.boolean().optional(),\n allowedScopes: z.array(z.string()).optional(),\n blockedScopes: z.array(z.string()).optional(),\n })\n .optional();\n\nconst DmConfigSchema = z\n .object({\n historyLimit: z.number().optional(),\n })\n .optional();\n\n// ---------------------------------------------------------------------------\n// Group schema\n// ---------------------------------------------------------------------------\n\nexport const FeishuGroupSchema = z.object({\n groupPolicy: GroupPolicyEnum.optional(),\n requireMention: z.boolean().optional(),\n tools: ToolPolicySchema,\n skills: z.array(z.string()).optional(),\n enabled: z.boolean().optional(),\n allowFrom: AllowFromSchema,\n systemPrompt: z.string().optional(),\n});\n\n// ---------------------------------------------------------------------------\n// Account config schema (same shape as top-level minus `accounts`)\n// ---------------------------------------------------------------------------\n\nexport const FeishuAccountConfigSchema = z.object({\n appId: z.string().optional(),\n appSecret: z.string().optional(),\n encryptKey: z.string().optional(),\n verificationToken: z.string().optional(),\n name: z.string().optional(),\n enabled: z.boolean().optional(),\n domain: DomainSchema,\n connectionMode: ConnectionModeEnum.optional(),\n webhookPath: z.string().optional(),\n webhookPort: z.number().optional(),\n dmPolicy: DmPolicyEnum.optional(),\n allowFrom: AllowFromSchema,\n groupPolicy: GroupPolicyEnum.optional(),\n groupAllowFrom: AllowFromSchema,\n requireMention: z.boolean().optional(),\n groups: z.record(z.string(), FeishuGroupSchema).optional(),\n historyLimit: z.number().optional(),\n dmHistoryLimit: z.number().optional(),\n dms: DmConfigSchema,\n textChunkLimit: z.number().optional(),\n chunkMode: ChunkModeEnum.optional(),\n blockStreamingCoalesce: BlockStreamingCoalesceSchema,\n mediaMaxMb: z.number().optional(),\n heartbeat: HeartbeatSchema,\n replyMode: ReplyModeSchema,\n streaming: z.boolean().optional(),\n blockStreaming: z.boolean().optional(),\n tools: FeishuToolsFlagSchema,\n footer: FeishuFooterSchema,\n markdown: MarkdownConfigSchema,\n configWrites: z.boolean().optional(),\n capabilities: CapabilitiesSchema,\n dedup: DedupSchema,\n reactionNotifications: ReactionNotificationModeSchema,\n threadSession: z.boolean().optional(),\n uat: UATConfigSchema,\n});\n\n// ---------------------------------------------------------------------------\n// Top-level Feishu config schema\n// ---------------------------------------------------------------------------\n\nexport const FeishuConfigSchema = FeishuAccountConfigSchema.extend({\n accounts: z.record(z.string(), FeishuAccountConfigSchema).optional(),\n}).superRefine((data, ctx) => {\n // When dmPolicy is \"open\", allowFrom must contain the wildcard \"*\".\n if (data.dmPolicy === 'open') {\n const list = data.allowFrom;\n const hasWildcard = Array.isArray(list) && list.includes('*');\n\n if (!hasWildcard) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['allowFrom'],\n message: 'When dmPolicy is \"open\", allowFrom must include \"*\" to permit all senders.',\n });\n }\n }\n});\n\n// ---------------------------------------------------------------------------\n// Auto-generated JSON Schema (single source of truth)\n// ---------------------------------------------------------------------------\n\n/**\n * JSON Schema derived from FeishuConfigSchema.\n *\n * - `io: \"input\"` exposes the input type for `.transform()` schemas (e.g. AllowFromSchema).\n * - `unrepresentable: \"any\"` degrades `.superRefine()` constraints to `{}`.\n * - `target: \"draft-07\"` matches the plugin system's expected JSON Schema version.\n */\nexport const FEISHU_CONFIG_JSON_SCHEMA: Record<string, unknown> = toJSONSchema(FeishuConfigSchema, {\n target: 'draft-07',\n io: 'input',\n unrepresentable: 'any',\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 * Zod-based configuration schema for the OpenClaw Lark/Feishu channel plugin.\n *\n * Provides runtime validation, sensible defaults, and cross-field refinements\n * so that every consuming module can rely on well-typed configuration objects.\n */\n\nimport { z, toJSONSchema } from 'zod';\n\nexport { z };\n\n// ---------------------------------------------------------------------------\n// Shared micro-schemas\n// ---------------------------------------------------------------------------\n\nconst DmPolicyEnum = z.enum(['open', 'pairing', 'allowlist', 'disabled']);\nconst GroupPolicyEnum = z.enum(['open', 'allowlist', 'disabled']);\nconst ConnectionModeEnum = z.enum(['websocket', 'webhook']);\nconst ReplyModeValue = z.enum(['auto', 'static', 'streaming']);\nconst ReplyModeSchema = z\n .union([\n ReplyModeValue,\n z.object({\n default: ReplyModeValue.optional(),\n group: ReplyModeValue.optional(),\n direct: ReplyModeValue.optional(),\n }),\n ])\n .optional();\nconst ChunkModeEnum = z.enum(['newline', 'paragraph', 'none']);\n\nconst DomainSchema = z.union([z.literal('feishu'), z.literal('lark'), z.string().regex(/^https:\\/\\//)]).optional();\n\nconst ProjectDomainSchema = z\n .union([z.literal('feishu'), z.literal('meegle'), z.string().regex(/^https:\\/\\//)])\n .optional();\n\nconst FeishuProjectConfigSchema = z\n .object({\n domain: ProjectDomainSchema,\n })\n .optional();\n\nconst AllowFromSchema = z\n .union([z.string(), z.array(z.string())])\n .optional()\n .transform((v) => {\n if (v === undefined || v === null) return undefined;\n return Array.isArray(v) ? v : [v];\n });\n\nconst ToolPolicySchema = z\n .object({\n allow: z.array(z.string()).optional(),\n deny: z.array(z.string()).optional(),\n })\n .optional();\n\nconst FeishuToolsFlagSchema = z\n .object({\n doc: z.boolean().optional(),\n wiki: z.boolean().optional(),\n drive: z.boolean().optional(),\n perm: z.boolean().optional(),\n scopes: z.boolean().optional(),\n })\n .optional();\n\nconst FeishuFooterSchema = z\n .object({\n status: z.boolean().optional(),\n elapsed: z.boolean().optional(),\n })\n .optional();\n\nconst BlockStreamingCoalesceSchema = z\n .object({\n minChars: z.number().optional(),\n maxChars: z.number().optional(),\n idleMs: z.number().optional(),\n })\n .optional();\n\nconst MarkdownConfigSchema = z\n .object({\n tables: z.enum(['off', 'bullets', 'code']).optional(),\n })\n .optional();\n\nconst HeartbeatSchema = z\n .object({\n every: z.string().optional(),\n activeHours: z\n .object({\n start: z.string().optional(),\n end: z.string().optional(),\n timezone: z.string().optional(),\n })\n .optional(),\n target: z.string().optional(),\n to: z.string().optional(),\n prompt: z.string().optional(),\n accountId: z.string().optional(),\n })\n .optional();\n\nconst CapabilitiesSchema = z\n .object({\n image: z.boolean().optional(),\n audio: z.boolean().optional(),\n video: z.boolean().optional(),\n })\n .optional();\n\nconst DedupSchema = z\n .object({\n ttlMs: z.number().optional(), // default 43200000 (12h)\n maxEntries: z.number().optional(), // default 5000\n })\n .optional();\n\nconst ReactionNotificationModeSchema = z.enum(['off', 'own', 'all']).optional();\n\nexport const UATConfigSchema = z\n .object({\n enabled: z.boolean().optional(),\n allowedScopes: z.array(z.string()).optional(),\n blockedScopes: z.array(z.string()).optional(),\n })\n .optional();\n\nconst DmConfigSchema = z\n .object({\n historyLimit: z.number().optional(),\n })\n .optional();\n\n// ---------------------------------------------------------------------------\n// Group schema\n// ---------------------------------------------------------------------------\n\nexport const FeishuGroupSchema = z.object({\n groupPolicy: GroupPolicyEnum.optional(),\n requireMention: z.boolean().optional(),\n tools: ToolPolicySchema,\n skills: z.array(z.string()).optional(),\n enabled: z.boolean().optional(),\n allowFrom: AllowFromSchema,\n systemPrompt: z.string().optional(),\n});\n\n// ---------------------------------------------------------------------------\n// Account config schema (same shape as top-level minus `accounts`)\n// ---------------------------------------------------------------------------\n\nexport const FeishuAccountConfigSchema = z.object({\n appId: z.string().optional(),\n appSecret: z.string().optional(),\n encryptKey: z.string().optional(),\n verificationToken: z.string().optional(),\n name: z.string().optional(),\n enabled: z.boolean().optional(),\n domain: DomainSchema,\n connectionMode: ConnectionModeEnum.optional(),\n webhookPath: z.string().optional(),\n webhookPort: z.number().optional(),\n dmPolicy: DmPolicyEnum.optional(),\n allowFrom: AllowFromSchema,\n groupPolicy: GroupPolicyEnum.optional(),\n groupAllowFrom: AllowFromSchema,\n requireMention: z.boolean().optional(),\n groups: z.record(z.string(), FeishuGroupSchema).optional(),\n historyLimit: z.number().optional(),\n dmHistoryLimit: z.number().optional(),\n dms: DmConfigSchema,\n textChunkLimit: z.number().optional(),\n chunkMode: ChunkModeEnum.optional(),\n blockStreamingCoalesce: BlockStreamingCoalesceSchema,\n mediaMaxMb: z.number().optional(),\n heartbeat: HeartbeatSchema,\n replyMode: ReplyModeSchema,\n streaming: z.boolean().optional(),\n blockStreaming: z.boolean().optional(),\n tools: FeishuToolsFlagSchema,\n footer: FeishuFooterSchema,\n markdown: MarkdownConfigSchema,\n configWrites: z.boolean().optional(),\n capabilities: CapabilitiesSchema,\n dedup: DedupSchema,\n reactionNotifications: ReactionNotificationModeSchema,\n threadSession: z.boolean().optional(),\n uat: UATConfigSchema,\n project: FeishuProjectConfigSchema,\n});\n\n// ---------------------------------------------------------------------------\n// Top-level Feishu config schema\n// ---------------------------------------------------------------------------\n\nexport const FeishuConfigSchema = FeishuAccountConfigSchema.extend({\n accounts: z.record(z.string(), FeishuAccountConfigSchema).optional(),\n}).superRefine((data, ctx) => {\n // When dmPolicy is \"open\", allowFrom must contain the wildcard \"*\".\n if (data.dmPolicy === 'open') {\n const list = data.allowFrom;\n const hasWildcard = Array.isArray(list) && list.includes('*');\n\n if (!hasWildcard) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['allowFrom'],\n message: 'When dmPolicy is \"open\", allowFrom must include \"*\" to permit all senders.',\n });\n }\n }\n});\n\n// ---------------------------------------------------------------------------\n// Auto-generated JSON Schema (single source of truth)\n// ---------------------------------------------------------------------------\n\n/**\n * JSON Schema derived from FeishuConfigSchema.\n *\n * - `io: \"input\"` exposes the input type for `.transform()` schemas (e.g. AllowFromSchema).\n * - `unrepresentable: \"any\"` degrades `.superRefine()` constraints to `{}`.\n * - `target: \"draft-07\"` matches the plugin system's expected JSON Schema version.\n */\nexport const FEISHU_CONFIG_JSON_SCHEMA: Record<string, unknown> = toJSONSchema(FeishuConfigSchema, {\n target: 'draft-07',\n io: 'input',\n unrepresentable: 'any',\n});\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -30,7 +30,7 @@ function resolveOAuthEndpoints(brand) {
30
30
  }
31
31
  async function requestDeviceAuthorization(params) {
32
32
  const { appId, appSecret, brand } = params;
33
- const endpoints = params.endpoints || resolveOAuthEndpoints(brand);
33
+ const endpoints = resolveOAuthEndpoints(brand);
34
34
  let scope = params.scope ?? "";
35
35
  if (!scope.includes("offline_access")) {
36
36
  scope = scope ? `${scope} offline_access` : "offline_access";
@@ -92,7 +92,7 @@ async function pollDeviceToken(params) {
92
92
  const MAX_POLL_ATTEMPTS = 200;
93
93
  const { appId, appSecret, brand, deviceCode, expiresIn, signal } = params;
94
94
  let interval = params.interval;
95
- const endpoints = params.endpoints || resolveOAuthEndpoints(brand);
95
+ const endpoints = resolveOAuthEndpoints(brand);
96
96
  const deadline = Date.now() + expiresIn * 1e3;
97
97
  let attempts = 0;
98
98
  while (Date.now() < deadline && attempts < MAX_POLL_ATTEMPTS) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/core/device-flow.ts"],
4
- "sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * OAuth 2.0 Device Authorization Grant (RFC 8628) for Lark/Feishu.\n *\n * Two-step flow:\n * 1. `requestDeviceAuthorization` \u2013 obtains device_code + user_code.\n * 2. `pollDeviceToken` \u2013 polls the token endpoint until the user authorises,\n * rejects, or the code expires.\n *\n * All HTTP calls use the built-in `fetch` (Node 18+). The Lark SDK is not\n * used here because these OAuth endpoints are outside the SDK's scope.\n */\n\nimport type { LarkBrand } from './types';\nimport { larkLogger } from './lark-logger';\n\nconst log = larkLogger('core/device-flow');\nimport { feishuFetch } from './feishu-fetch';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface DeviceAuthResponse {\n deviceCode: string;\n userCode: string;\n verificationUri: string;\n verificationUriComplete: string;\n expiresIn: number; // seconds\n interval: number; // recommended polling interval (seconds)\n}\n\nexport interface DeviceFlowTokenData {\n accessToken: string;\n refreshToken: string;\n expiresIn: number; // seconds\n refreshExpiresIn: number; // seconds\n scope: string;\n}\n\nexport type DeviceFlowResult =\n | { ok: true; token: DeviceFlowTokenData }\n | { ok: false; error: DeviceFlowError; message: string };\n\nexport type DeviceFlowError = 'authorization_pending' | 'slow_down' | 'access_denied' | 'expired_token';\n\n// ---------------------------------------------------------------------------\n// Endpoint resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the two OAuth endpoint URLs based on the configured brand.\n */\nexport function resolveOAuthEndpoints(brand: LarkBrand): {\n deviceAuthorization: string;\n token: string;\n} {\n if (!brand || brand === 'feishu') {\n return {\n deviceAuthorization: 'https://accounts.feishu.cn/oauth/v1/device_authorization',\n token: 'https://open.feishu.cn/open-apis/authen/v2/oauth/token',\n };\n }\n if (brand === 'lark') {\n return {\n deviceAuthorization: 'https://accounts.larksuite.com/oauth/v1/device_authorization',\n token: 'https://open.larksuite.com/open-apis/authen/v2/oauth/token',\n };\n }\n // Custom domain \u2013 derive paths by convention.\n // Smart derivation: open.X \u2192 accounts.X for the device authorization endpoint.\n const base = brand.replace(/\\/+$/, '');\n let accountsBase = base;\n try {\n const parsed = new URL(base);\n if (parsed.hostname.startsWith('open.')) {\n accountsBase = `${parsed.protocol}//${parsed.hostname.replace(/^open\\./, 'accounts.')}`;\n }\n } catch {\n /* fallback to base */\n }\n\n return {\n deviceAuthorization: `${accountsBase}/oauth/v1/device_authorization`,\n token: `${base}/open-apis/authen/v2/oauth/token`,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Step 1 \u2013 Device Authorization Request\n// ---------------------------------------------------------------------------\n\n/**\n * Request a device authorisation code from the Feishu OAuth server.\n *\n * Uses Confidential Client authentication (HTTP Basic with appId:appSecret).\n * The `offline_access` scope is automatically appended so that the token\n * response includes a refresh_token.\n */\nexport async function requestDeviceAuthorization(params: {\n appId: string;\n appSecret: string;\n brand: LarkBrand;\n scope?: string;\n endpoints?: { deviceAuthorization: string; token: string };\n}): Promise<DeviceAuthResponse> {\n const { appId, appSecret, brand } = params;\n const endpoints = params.endpoints || resolveOAuthEndpoints(brand);\n\n // Ensure offline_access is always requested.\n let scope = params.scope ?? '';\n if (!scope.includes('offline_access')) {\n scope = scope ? `${scope} offline_access` : 'offline_access';\n }\n\n const basicAuth = Buffer.from(`${appId}:${appSecret}`).toString('base64');\n\n const body = new URLSearchParams();\n body.set('client_id', appId);\n body.set('scope', scope);\n\n log.info(\n `requesting device authorization (scope=\"${scope}\") url=${endpoints.deviceAuthorization} token_url=${endpoints.token}`,\n );\n\n const resp = await feishuFetch(endpoints.deviceAuthorization, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Authorization: `Basic ${basicAuth}`,\n },\n body: body.toString(),\n });\n\n const text = await resp.text();\n log.info(`response status=${resp.status} body=${text.slice(0, 500)}`);\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(text) as Record<string, unknown>;\n } catch {\n throw new Error(`Device authorization failed: HTTP ${resp.status} \u2013 ${text.slice(0, 200)}`);\n }\n\n if (!resp.ok || data.error) {\n const msg = (data.error_description as string) ?? (data.error as string) ?? 'Unknown error';\n throw new Error(`Device authorization failed: ${msg}`);\n }\n\n const expiresIn = (data.expires_in as number) ?? 240;\n const interval = (data.interval as number) ?? 5;\n log.info(`device_code obtained, expires_in=${expiresIn}s (${Math.round(expiresIn / 60)}min), interval=${interval}s`);\n\n return {\n deviceCode: data.device_code as string,\n userCode: data.user_code as string,\n verificationUri: data.verification_uri as string,\n verificationUriComplete: (data.verification_uri_complete as string) ?? (data.verification_uri as string),\n expiresIn,\n interval,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Step 2 \u2013 Poll Token Endpoint\n// ---------------------------------------------------------------------------\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener(\n 'abort',\n () => {\n clearTimeout(timer);\n reject(new DOMException('Aborted', 'AbortError'));\n },\n { once: true },\n );\n });\n}\n\n/**\n * Poll the token endpoint until the user authorises, rejects, or the code\n * expires.\n *\n * Handles `authorization_pending` (keep polling), `slow_down` (back off by\n * +5 s), `access_denied` and `expired_token` (terminal errors).\n *\n * Pass an `AbortSignal` to cancel polling from the outside.\n */\nexport async function pollDeviceToken(params: {\n appId: string;\n appSecret: string;\n brand: LarkBrand;\n deviceCode: string;\n interval: number;\n expiresIn: number;\n signal?: AbortSignal;\n endpoints?: { deviceAuthorization: string; token: string };\n}): Promise<DeviceFlowResult> {\n const MAX_POLL_INTERVAL = 60; // slow_down \u6700\u5927\u95F4\u9694 60 \u79D2\n const MAX_POLL_ATTEMPTS = 200; // \u5B89\u5168\u4E0A\u9650\uFF08\u8FDC\u8D85\u8BBE\u5907\u7801\u6709\u6548\u671F\uFF09\n\n const { appId, appSecret, brand, deviceCode, expiresIn, signal } = params;\n let interval = params.interval;\n const endpoints = params.endpoints || resolveOAuthEndpoints(brand);\n const deadline = Date.now() + expiresIn * 1000;\n let attempts = 0;\n\n while (Date.now() < deadline && attempts < MAX_POLL_ATTEMPTS) {\n attempts++;\n if (signal?.aborted) {\n return { ok: false, error: 'expired_token', message: 'Polling was cancelled' };\n }\n\n await sleep(interval * 1000, signal);\n\n let data: Record<string, unknown>;\n try {\n const resp = await feishuFetch(endpoints.token, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n device_code: deviceCode,\n client_id: appId,\n client_secret: appSecret,\n }).toString(),\n });\n data = (await resp.json()) as Record<string, unknown>;\n } catch (err) {\n log.warn(`poll network error: ${err}`);\n interval = Math.min(interval + 1, MAX_POLL_INTERVAL);\n continue;\n }\n\n const error = data.error as string | undefined;\n\n if (!error && data.access_token) {\n log.info('token obtained successfully');\n const refreshToken = (data.refresh_token as string) ?? '';\n const expiresIn = (data.expires_in as number) ?? 7200;\n let refreshExpiresIn = (data.refresh_token_expires_in as number) ?? 604800;\n if (!refreshToken) {\n log.warn('no refresh_token in response, token will not be refreshable');\n refreshExpiresIn = expiresIn;\n }\n return {\n ok: true,\n token: {\n accessToken: data.access_token as string,\n refreshToken,\n expiresIn,\n refreshExpiresIn,\n scope: (data.scope as string) ?? '',\n },\n };\n }\n\n if (error === 'authorization_pending') {\n log.debug('authorization_pending, retrying...');\n continue;\n }\n\n if (error === 'slow_down') {\n interval = Math.min(interval + 5, MAX_POLL_INTERVAL);\n log.info(`slow_down, interval increased to ${interval}s`);\n continue;\n }\n\n if (error === 'access_denied') {\n log.info('user denied authorization');\n return { ok: false, error: 'access_denied', message: '\u7528\u6237\u62D2\u7EDD\u4E86\u6388\u6743' };\n }\n\n if (error === 'expired_token' || error === 'invalid_grant') {\n log.info(`device code expired/invalid (error=${error})`);\n return { ok: false, error: 'expired_token', message: '\u6388\u6743\u7801\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77' };\n }\n\n // Unknown error \u2013 treat as terminal.\n const desc = (data.error_description as string) ?? error ?? 'Unknown error';\n log.warn(`unexpected error: error=${error}, desc=${desc}`);\n return { ok: false, error: 'expired_token', message: desc };\n }\n\n if (attempts >= MAX_POLL_ATTEMPTS) {\n log.warn(`max poll attempts (${MAX_POLL_ATTEMPTS}) reached`);\n }\n return { ok: false, error: 'expired_token', message: '\u6388\u6743\u8D85\u65F6\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77' };\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 2.0 Device Authorization Grant (RFC 8628) for Lark/Feishu.\n *\n * Two-step flow:\n * 1. `requestDeviceAuthorization` \u2013 obtains device_code + user_code.\n * 2. `pollDeviceToken` \u2013 polls the token endpoint until the user authorises,\n * rejects, or the code expires.\n *\n * All HTTP calls use the built-in `fetch` (Node 18+). The Lark SDK is not\n * used here because these OAuth endpoints are outside the SDK's scope.\n */\n\nimport type { LarkBrand } from './types';\nimport { larkLogger } from './lark-logger';\n\nconst log = larkLogger('core/device-flow');\nimport { feishuFetch } from './feishu-fetch';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface DeviceAuthResponse {\n deviceCode: string;\n userCode: string;\n verificationUri: string;\n verificationUriComplete: string;\n expiresIn: number; // seconds\n interval: number; // recommended polling interval (seconds)\n}\n\nexport interface DeviceFlowTokenData {\n accessToken: string;\n refreshToken: string;\n expiresIn: number; // seconds\n refreshExpiresIn: number; // seconds\n scope: string;\n}\n\nexport type DeviceFlowResult =\n | { ok: true; token: DeviceFlowTokenData }\n | { ok: false; error: DeviceFlowError; message: string };\n\nexport type DeviceFlowError = 'authorization_pending' | 'slow_down' | 'access_denied' | 'expired_token';\n\n// ---------------------------------------------------------------------------\n// Endpoint resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the two OAuth endpoint URLs based on the configured brand.\n */\nexport function resolveOAuthEndpoints(brand: LarkBrand): {\n deviceAuthorization: string;\n token: string;\n} {\n if (!brand || brand === 'feishu') {\n return {\n deviceAuthorization: 'https://accounts.feishu.cn/oauth/v1/device_authorization',\n token: 'https://open.feishu.cn/open-apis/authen/v2/oauth/token',\n };\n }\n if (brand === 'lark') {\n return {\n deviceAuthorization: 'https://accounts.larksuite.com/oauth/v1/device_authorization',\n token: 'https://open.larksuite.com/open-apis/authen/v2/oauth/token',\n };\n }\n // Custom domain \u2013 derive paths by convention.\n // Smart derivation: open.X \u2192 accounts.X for the device authorization endpoint.\n const base = brand.replace(/\\/+$/, '');\n let accountsBase = base;\n try {\n const parsed = new URL(base);\n if (parsed.hostname.startsWith('open.')) {\n accountsBase = `${parsed.protocol}//${parsed.hostname.replace(/^open\\./, 'accounts.')}`;\n }\n } catch {\n /* fallback to base */\n }\n\n return {\n deviceAuthorization: `${accountsBase}/oauth/v1/device_authorization`,\n token: `${base}/open-apis/authen/v2/oauth/token`,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Step 1 \u2013 Device Authorization Request\n// ---------------------------------------------------------------------------\n\n/**\n * Request a device authorisation code from the Feishu OAuth server.\n *\n * Uses Confidential Client authentication (HTTP Basic with appId:appSecret).\n * The `offline_access` scope is automatically appended so that the token\n * response includes a refresh_token.\n */\nexport async function requestDeviceAuthorization(params: {\n appId: string;\n appSecret: string;\n brand: LarkBrand;\n scope?: string;\n}): Promise<DeviceAuthResponse> {\n const { appId, appSecret, brand } = params;\n const endpoints = resolveOAuthEndpoints(brand);\n\n // Ensure offline_access is always requested.\n let scope = params.scope ?? '';\n if (!scope.includes('offline_access')) {\n scope = scope ? `${scope} offline_access` : 'offline_access';\n }\n\n const basicAuth = Buffer.from(`${appId}:${appSecret}`).toString('base64');\n\n const body = new URLSearchParams();\n body.set('client_id', appId);\n body.set('scope', scope);\n\n log.info(\n `requesting device authorization (scope=\"${scope}\") url=${endpoints.deviceAuthorization} token_url=${endpoints.token}`,\n );\n\n const resp = await feishuFetch(endpoints.deviceAuthorization, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Authorization: `Basic ${basicAuth}`,\n },\n body: body.toString(),\n });\n\n const text = await resp.text();\n log.info(`response status=${resp.status} body=${text.slice(0, 500)}`);\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(text) as Record<string, unknown>;\n } catch {\n throw new Error(`Device authorization failed: HTTP ${resp.status} \u2013 ${text.slice(0, 200)}`);\n }\n\n if (!resp.ok || data.error) {\n const msg = (data.error_description as string) ?? (data.error as string) ?? 'Unknown error';\n throw new Error(`Device authorization failed: ${msg}`);\n }\n\n const expiresIn = (data.expires_in as number) ?? 240;\n const interval = (data.interval as number) ?? 5;\n log.info(`device_code obtained, expires_in=${expiresIn}s (${Math.round(expiresIn / 60)}min), interval=${interval}s`);\n\n return {\n deviceCode: data.device_code as string,\n userCode: data.user_code as string,\n verificationUri: data.verification_uri as string,\n verificationUriComplete: (data.verification_uri_complete as string) ?? (data.verification_uri as string),\n expiresIn,\n interval,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Step 2 \u2013 Poll Token Endpoint\n// ---------------------------------------------------------------------------\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener(\n 'abort',\n () => {\n clearTimeout(timer);\n reject(new DOMException('Aborted', 'AbortError'));\n },\n { once: true },\n );\n });\n}\n\n/**\n * Poll the token endpoint until the user authorises, rejects, or the code\n * expires.\n *\n * Handles `authorization_pending` (keep polling), `slow_down` (back off by\n * +5 s), `access_denied` and `expired_token` (terminal errors).\n *\n * Pass an `AbortSignal` to cancel polling from the outside.\n */\nexport async function pollDeviceToken(params: {\n appId: string;\n appSecret: string;\n brand: LarkBrand;\n deviceCode: string;\n interval: number;\n expiresIn: number;\n signal?: AbortSignal;\n}): Promise<DeviceFlowResult> {\n const MAX_POLL_INTERVAL = 60; // slow_down \u6700\u5927\u95F4\u9694 60 \u79D2\n const MAX_POLL_ATTEMPTS = 200; // \u5B89\u5168\u4E0A\u9650\uFF08\u8FDC\u8D85\u8BBE\u5907\u7801\u6709\u6548\u671F\uFF09\n\n const { appId, appSecret, brand, deviceCode, expiresIn, signal } = params;\n let interval = params.interval;\n const endpoints = resolveOAuthEndpoints(brand);\n const deadline = Date.now() + expiresIn * 1000;\n let attempts = 0;\n\n while (Date.now() < deadline && attempts < MAX_POLL_ATTEMPTS) {\n attempts++;\n if (signal?.aborted) {\n return { ok: false, error: 'expired_token', message: 'Polling was cancelled' };\n }\n\n await sleep(interval * 1000, signal);\n\n let data: Record<string, unknown>;\n try {\n const resp = await feishuFetch(endpoints.token, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n device_code: deviceCode,\n client_id: appId,\n client_secret: appSecret,\n }).toString(),\n });\n data = (await resp.json()) as Record<string, unknown>;\n } catch (err) {\n log.warn(`poll network error: ${err}`);\n interval = Math.min(interval + 1, MAX_POLL_INTERVAL);\n continue;\n }\n\n const error = data.error as string | undefined;\n\n if (!error && data.access_token) {\n log.info('token obtained successfully');\n const refreshToken = (data.refresh_token as string) ?? '';\n const expiresIn = (data.expires_in as number) ?? 7200;\n let refreshExpiresIn = (data.refresh_token_expires_in as number) ?? 604800;\n if (!refreshToken) {\n log.warn('no refresh_token in response, token will not be refreshable');\n refreshExpiresIn = expiresIn;\n }\n return {\n ok: true,\n token: {\n accessToken: data.access_token as string,\n refreshToken,\n expiresIn,\n refreshExpiresIn,\n scope: (data.scope as string) ?? '',\n },\n };\n }\n\n if (error === 'authorization_pending') {\n log.debug('authorization_pending, retrying...');\n continue;\n }\n\n if (error === 'slow_down') {\n interval = Math.min(interval + 5, MAX_POLL_INTERVAL);\n log.info(`slow_down, interval increased to ${interval}s`);\n continue;\n }\n\n if (error === 'access_denied') {\n log.info('user denied authorization');\n return { ok: false, error: 'access_denied', message: '\u7528\u6237\u62D2\u7EDD\u4E86\u6388\u6743' };\n }\n\n if (error === 'expired_token' || error === 'invalid_grant') {\n log.info(`device code expired/invalid (error=${error})`);\n return { ok: false, error: 'expired_token', message: '\u6388\u6743\u7801\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77' };\n }\n\n // Unknown error \u2013 treat as terminal.\n const desc = (data.error_description as string) ?? error ?? 'Unknown error';\n log.warn(`unexpected error: error=${error}, desc=${desc}`);\n return { ok: false, error: 'expired_token', message: desc };\n }\n\n if (attempts >= MAX_POLL_ATTEMPTS) {\n log.warn(`max poll attempts (${MAX_POLL_ATTEMPTS}) reached`);\n }\n return { ok: false, error: 'expired_token', message: '\u6388\u6743\u8D85\u65F6\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77' };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["expiresIn"]
7
7
  }
@@ -427,7 +427,7 @@ async function handleCardAction(data, cfg, accountId) {
427
427
  } catch {
428
428
  return;
429
429
  }
430
- if (action === "project_auth_complete" || buttonName === "submit_project_auth") {
430
+ if (buttonName === "submit_project_auth") {
431
431
  return handleProjectAuthCardAction(data, cfg, accountId);
432
432
  }
433
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 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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",
4
+ "sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * auto-auth.ts \u2014 \u5DE5\u5177\u5C42\u81EA\u52A8\u6388\u6743\u5904\u7406\u3002\n *\n * \u5F53 OAPI \u5DE5\u5177\u9047\u5230\u6388\u6743\u95EE\u9898\u65F6\uFF0C\u76F4\u63A5\u5728\u5DE5\u5177\u5C42\u5904\u7406\uFF0C\u4E0D\u518D\u8BA9 AI \u5224\u65AD\uFF1A\n *\n * - UserAuthRequiredError (appScopeVerified=true)\n * \u2192 \u76F4\u63A5\u8C03\u7528 executeAuthorize \u53D1\u8D77 OAuth Device Flow \u5361\u7247\n *\n * - UserScopeInsufficientError\n * \u2192 \u76F4\u63A5\u8C03\u7528 executeAuthorize\uFF08\u4F7F\u7528 missingScopes\uFF09\n *\n * - AppScopeMissingError\n * \u2192 \u53D1\u9001\u5E94\u7528\u6743\u9650\u5F15\u5BFC\u5361\u7247\uFF1B\u7528\u6237\u70B9\u51FB\"\u6211\u5DF2\u5B8C\u6210\"\u540E\uFF1A\n * 1. \u66F4\u65B0\u5361\u7247\u4E3A\u5904\u7406\u4E2D\u72B6\u6001\n * 2. invalidateAppScopeCache\n * 3. \u53D1\u9001\u4E2D\u95F4\u5408\u6210\u6D88\u606F\u544A\u77E5 AI\uFF08\"\u5E94\u7528\u6743\u9650\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u53D1\u8D77\u7528\u6237\u6388\u6743...\"\uFF09\n * 4. \u8C03\u7528 executeAuthorize \u53D1\u8D77 OAuth Device Flow\n *\n * - \u5176\u4ED6\u60C5\u51B5\uFF08AppScopeCheckFailedError\u3001appScopeVerified=false \u7B49\uFF09\n * \u2192 \u56DE\u9000\u5230\u539F handleInvokeError\uFF08\u4E0D\u89E6\u53D1\u81EA\u52A8\u6388\u6743\uFF09\n *\n * \u964D\u7EA7\u7B56\u7565\uFF08\u4FDD\u5B88\uFF09\uFF1A\u4EE5\u4E0B\u60C5\u51B5\u5747\u56DE\u9000\u5230 handleInvokeError\uFF1A\n * - \u65E0 LarkTicket\uFF08\u975E\u6D88\u606F\u573A\u666F\uFF09\n * - \u65E0 senderOpenId\uFF08\u65E0\u6CD5\u786E\u5B9A\u6388\u6743\u5BF9\u8C61\uFF09\n * - \u8D26\u53F7\u672A\u914D\u7F6E\uFF08!acct.configured\uFF09\n * - \u4EFB\u4F55\u6B65\u9AA4\u629B\u51FA\u5F02\u5E38\n */\n\nimport type { ClawdbotConfig } from 'openclaw/plugin-sdk';\nimport type { ConfiguredLarkAccount } from '../core/types';\nimport type { LarkTicket } from '../core/lark-ticket';\nimport { getTicket } from '../core/lark-ticket';\nimport { larkLogger } from '../core/lark-logger';\n\nconst log = larkLogger('tools/auto-auth');\nimport { getLarkAccount } from '../core/accounts';\nimport { UserAuthRequiredError, UserScopeInsufficientError, AppScopeMissingError } from '../core/tool-client';\nimport { invalidateAppScopeCache, getAppGrantedScopes, isAppScopeSatisfied } from '../core/app-scope-checker';\nimport { LarkClient } from '../core/lark-client';\nimport { createCardEntity, sendCardByCardId, updateCardKitCardForAuth } from '../card/cardkit';\nimport { executeAuthorize } from './oauth';\nimport { formatLarkError, json } from './oapi/helpers';\nimport { handleProjectAuthCardAction } from './project-oauth';\nimport { OwnerAccessDeniedError } from '../core/owner-policy';\nimport { enqueueFeishuChatTask } from '../channel/chat-queue';\nimport { handleFeishuMessage } from '../messaging/inbound/handler';\nimport { withTicket } from '../core/lark-ticket';\n\n// ---------------------------------------------------------------------------\n// Debounce + scope merge \u2014 \u9632\u6296\u7F13\u51B2\u533A\uFF08\u4E24\u9636\u6BB5\uFF09\n//\n// \u5DE5\u5177\u8C03\u7528\u53EF\u80FD\u662F\u771F\u6B63\u5E76\u53D1\uFF0850ms \u5185\u5230\u8FBE\uFF09\u6216\u88AB\u6846\u67B6\u5E8F\u5217\u5316\uFF08\u95F4\u9694\u6570\u79D2\u5230\u8FBE\uFF09\u3002\n// \u4E3A\u540C\u65F6\u8986\u76D6\u4E24\u79CD\u573A\u666F\uFF0C\u91C7\u7528\u4E24\u9636\u6BB5\u8BBE\u8BA1\uFF1A\n//\n// collecting\uFF08\u6536\u96C6\u9636\u6BB5\uFF09\uFF1A50ms \u9632\u6296\u7A97\u53E3\uFF0C\u5408\u5E76 scope\n// executing\uFF08\u6267\u884C\u9636\u6BB5\uFF09\uFF1AflushFn \u6B63\u5728\u8FD0\u884C\uFF0C\u540E\u7EED\u8BF7\u6C42\u590D\u7528\u540C\u4E00\u7ED3\u679C\n//\n// \u4ECE collecting \u2192 executing \u8F6C\u6362\u65F6\u4E0D\u4ECE Map \u4E2D\u5220\u9664 entry\uFF0C\n// \u76F4\u5230 flushFn \u5B8C\u6210\uFF08resolve / reject\uFF09\u624D\u79FB\u9664\u3002\n// ---------------------------------------------------------------------------\n\ntype JsonResult = ReturnType<typeof json>;\n\n/** \u7F13\u51B2\u4E2D\u7684\u6388\u6743\u8BF7\u6C42 */\ninterface AuthBatchEntry {\n phase: 'collecting' | 'executing';\n scopes: Set<string>;\n waiters: Array<{ resolve: (v: JsonResult) => void; reject: (e: unknown) => void }>;\n timer: ReturnType<typeof setTimeout> | null;\n /** flushFn \u6267\u884C\u4E2D\u7684 Promise\uFF08executing \u9636\u6BB5\u6709\u503C\uFF09 */\n resultPromise: Promise<JsonResult> | null;\n /** executing \u9636\u6BB5\uFF1A\u65B0 scope \u5230\u8FBE\u65F6\u7684\u5EF6\u8FDF\u5237\u65B0\u5B9A\u65F6\u5668 */\n updateTimer: ReturnType<typeof setTimeout> | null;\n /** scope \u66F4\u65B0\u7684 executeAuthorize \u662F\u5426\u6B63\u5728\u6267\u884C\uFF08\u4E92\u65A5\u9501\uFF09 */\n isUpdating: boolean;\n /** isUpdating \u671F\u95F4\u53C8\u6709\u65B0 scope \u5230\u8FBE\uFF0C\u9700\u8981\u518D\u66F4\u65B0\u4E00\u8F6E */\n pendingReupdate: boolean;\n /** flushFn \u5F15\u7528\uFF0Cexecuting \u9636\u6BB5\u7528\u4E8E scope \u66F4\u65B0\u65F6\u91CD\u65B0\u8C03\u7528 */\n flushFn: ((mergedScopes: string[]) => Promise<JsonResult>) | null;\n /** \u4EE5\u4E0B\u5B57\u6BB5\u6765\u81EA\u7B2C\u4E00\u4E2A\u5165\u961F\u7684\u8BF7\u6C42\uFF0C\u540E\u7EED\u8BF7\u6C42\u590D\u7528 */\n account: ConfiguredLarkAccount;\n cfg: ClawdbotConfig;\n ticket: LarkTicket;\n}\n\n/**\n * \u9632\u6296\u7F13\u51B2\u533A Map\u3002\n *\n * Key \u89C4\u5219\uFF1A\n * \u7528\u6237\u6388\u6743\uFF1A`user:${accountId}:${senderOpenId}:${messageId}`\n * \u5E94\u7528\u6388\u6743\uFF1A`app:${accountId}:${chatId}:${messageId}`\n */\nconst authBatches = new Map<string, AuthBatchEntry>();\n\n/** \u9632\u6296\u7A97\u53E3\uFF08\u6BEB\u79D2\uFF09 */\nconst AUTH_DEBOUNCE_MS = 50;\n\n/** \u7528\u6237\u6388\u6743\u9632\u6296\u7A97\u53E3\uFF08\u6BEB\u79D2\uFF09\u3002\u6BD4 app auth \u7684 50ms \u66F4\u957F\uFF0C\u4FDD\u8BC1\u5E94\u7528\u6743\u9650\u5361\u7247\u5148\u53D1\u51FA\u3002 */\nconst AUTH_USER_DEBOUNCE_MS = 150;\n\n/**\n * Scope \u66F4\u65B0\u9632\u6296\u7A97\u53E3\uFF08\u6BEB\u79D2\uFF09\u3002\n * \u6BD4\u521D\u59CB\u9632\u6296\u66F4\u957F\uFF0C\u56E0\u4E3A\u5DE5\u5177\u8C03\u7528\u53EF\u80FD\u95F4\u9694\u6570\u5341\u5230\u6570\u767E\u6BEB\u79D2\u987A\u5E8F\u5230\u8FBE\u3002\n * \u9700\u8981\u7B49\u8DB3\u591F\u4E45\u4EE5\u6536\u96C6\u6240\u6709\u540E\u7EED\u5230\u8FBE\u7684 scope \u540E\u518D\u4E00\u6B21\u6027\u66F4\u65B0\u5361\u7247\u3002\n */\nconst AUTH_UPDATE_DEBOUNCE_MS = 500;\n\n/**\n * \u51B7\u5374\u671F\uFF08\u6BEB\u79D2\uFF09\u3002\n * flushFn \u6267\u884C\u5B8C\u6BD5\u540E\uFF0Centry \u7EE7\u7EED\u4FDD\u7559\u5728 Map \u4E2D\u8FD9\u4E48\u957F\u65F6\u95F4\uFF0C\n * \u9632\u6B62\u540E\u7EED\u987A\u5E8F\u5230\u8FBE\u7684\u5DE5\u5177\u8C03\u7528\u521B\u5EFA\u91CD\u590D\u5361\u7247\u3002\n */\nconst AUTH_COOLDOWN_MS = 30_000;\n\n/**\n * \u5C06\u6388\u6743\u8BF7\u6C42\u5165\u961F\u5230\u9632\u6296\u7F13\u51B2\u533A\u3002\n *\n * \u540C\u4E00 bufferKey \u7684\u8BF7\u6C42\u4F1A\u88AB\u5408\u5E76\uFF1A\n * - collecting \u9636\u6BB5\uFF1Ascope \u96C6\u5408\u53D6\u5E76\u96C6\uFF0C\u5171\u4EAB\u540C\u4E00\u4E2A flushFn \u6267\u884C\u7ED3\u679C\n * - executing \u9636\u6BB5\uFF1AflushFn \u5DF2\u5728\u8FD0\u884C\uFF0C\u540E\u7EED\u8BF7\u6C42\u76F4\u63A5\u590D\u7528\u5DF2\u6709\u7ED3\u679C\uFF08\u4E0D\u91CD\u590D\u53D1\u5361\u7247\uFF09\n *\n * @param bufferKey - \u7F13\u51B2\u533A key\uFF08\u533A\u5206\u4E0D\u540C\u7528\u6237/\u4F1A\u8BDD\uFF09\n * @param scopes - \u672C\u6B21\u8BF7\u6C42\u9700\u8981\u7684 scope \u5217\u8868\n * @param ctx - \u4E0A\u4E0B\u6587\u4FE1\u606F\uFF08\u4EC5\u7B2C\u4E00\u4E2A\u8BF7\u6C42\u7684\u88AB\u91C7\u7528\uFF09\n * @param flushFn - \u5B9A\u65F6\u5668\u5230\u671F\u540E\u6267\u884C\u7684\u5B9E\u9645\u6388\u6743\u51FD\u6570\uFF0C\u63A5\u6536\u5408\u5E76\u540E\u7684 scope \u6570\u7EC4\n */\nfunction enqueueAuthRequest(\n bufferKey: string,\n scopes: string[],\n ctx: { account: ConfiguredLarkAccount; cfg: ClawdbotConfig; ticket: LarkTicket },\n flushFn: (mergedScopes: string[]) => Promise<JsonResult>,\n debounceMs: number = AUTH_DEBOUNCE_MS,\n): Promise<JsonResult> {\n const existing = authBatches.get(bufferKey);\n\n if (existing) {\n // \u4E0D\u8BBA\u54EA\u4E2A\u9636\u6BB5\uFF0C\u90FD\u8FFD\u52A0 scope\n for (const s of scopes) existing.scopes.add(s);\n\n if (existing.phase === 'executing') {\n // flushFn \u5DF2\u5728\u6267\u884C\u6216\u5DF2\u5B8C\u6210\uFF08\u5361\u7247\u5DF2\u53D1\u51FA\uFF09\uFF0C\u590D\u7528\u7ED3\u679C\n // \u540C\u65F6\u89E6\u53D1\u5EF6\u8FDF\u5237\u65B0\uFF1A\u7528\u5408\u5E76\u540E\u7684 scope \u91CD\u65B0\u8C03\u7528 flushFn \u66F4\u65B0\u5361\u7247\n log.info(`auth in-flight, piggyback \u2192 key=${bufferKey}, scopes=[${[...existing.scopes].join(', ')}]`);\n\n // \u9632\u6296 + \u4E92\u65A5\uFF1A\u591A\u4E2A\u5FEB\u901F\u5230\u8FBE\u7684\u8BF7\u6C42\u53EA\u89E6\u53D1\u4E00\u6B21\u5361\u7247\u66F4\u65B0\n if (existing.updateTimer) clearTimeout(existing.updateTimer);\n existing.updateTimer = setTimeout(async () => {\n existing.updateTimer = null;\n\n // \u4E92\u65A5\uFF1A\u5982\u679C\u4E0A\u4E00\u8F6E\u66F4\u65B0\u8FD8\u5728\u6267\u884C\uFF0C\u6807\u8BB0 pendingReupdate \u7B49\u5B83\u7ED3\u675F\u540E\u91CD\u8DD1\n if (existing.isUpdating) {\n existing.pendingReupdate = true;\n log.info(`scope update deferred (previous update still running) \u2192 key=${bufferKey}`);\n return;\n }\n\n existing.isUpdating = true;\n try {\n const mergedScopes = [...existing.scopes];\n log.info(`scope update flush \u2192 key=${bufferKey}, scopes=[${mergedScopes.join(', ')}]`);\n // \u91CD\u65B0\u8C03\u7528 flushFn\uFF08executeAuthorize \u4F1A\u68C0\u6D4B\u5230 pendingFlow\uFF0C\n // \u539F\u5730\u66F4\u65B0\u65E7\u5361\u7247\u5185\u5BB9 + \u91CD\u542F Device Flow\uFF09\n await existing.flushFn!(mergedScopes);\n } catch (err) {\n log.warn(`scope update failed: ${err}`);\n } finally {\n existing.isUpdating = false;\n // \u5982\u679C\u9501\u5B9A\u671F\u95F4\u6709\u65B0 scope \u5230\u8FBE\uFF0C\u518D\u8DD1\u4E00\u8F6E\n if (existing.pendingReupdate) {\n existing.pendingReupdate = false;\n const finalScopes = [...existing.scopes];\n log.info(`scope reupdate \u2192 key=${bufferKey}, scopes=[${finalScopes.join(', ')}]`);\n try {\n await existing.flushFn!(finalScopes);\n } catch (err) {\n log.warn(`scope reupdate failed: ${err}`);\n }\n }\n }\n }, AUTH_UPDATE_DEBOUNCE_MS);\n\n return existing.resultPromise!;\n }\n\n // collecting \u9636\u6BB5\uFF1A\u6B63\u5E38\u5408\u5E76\n log.info(`debounce merge \u2192 key=${bufferKey}, scopes=[${[...existing.scopes].join(', ')}]`);\n return new Promise<JsonResult>((resolve, reject) => {\n existing.waiters.push({ resolve, reject });\n });\n }\n\n // \u521B\u5EFA\u65B0\u7F13\u51B2\u533A\uFF08collecting \u9636\u6BB5\uFF09\n const entry: AuthBatchEntry = {\n phase: 'collecting',\n scopes: new Set(scopes),\n waiters: [],\n timer: null,\n resultPromise: null,\n updateTimer: null,\n isUpdating: false,\n pendingReupdate: false,\n flushFn: null,\n account: ctx.account,\n cfg: ctx.cfg,\n ticket: ctx.ticket,\n };\n\n const promise = new Promise<JsonResult>((resolve, reject) => {\n entry.waiters.push({ resolve, reject });\n });\n\n entry.timer = setTimeout(async () => {\n // \u8F6C\u5165 executing \u9636\u6BB5\uFF08\u4E0D\u4ECE Map \u4E2D\u5220\u9664\uFF0C\u963B\u6B62\u540E\u7EED\u8BF7\u6C42\u521B\u5EFA\u65B0\u5361\u7247\uFF09\n entry.phase = 'executing';\n entry.timer = null;\n entry.flushFn = flushFn; // \u4FDD\u5B58\u5F15\u7528\uFF0C\u4F9B executing \u9636\u6BB5 scope \u66F4\u65B0\u65F6\u91CD\u65B0\u8C03\u7528\n const mergedScopes = [...entry.scopes];\n\n log.info(\n `debounce flush \u2192 key=${bufferKey}, ` + `waiters=${entry.waiters.length}, scopes=[${mergedScopes.join(', ')}]`,\n );\n\n // \u5C06 flushFn \u7684 Promise \u5B58\u5165 entry\uFF0C\u4F9B executing \u9636\u6BB5\u7684\u540E\u6765\u8005\u590D\u7528\n entry.resultPromise = flushFn(mergedScopes);\n\n try {\n const result = await entry.resultPromise;\n for (const w of entry.waiters) w.resolve(result);\n } catch (err) {\n for (const w of entry.waiters) w.reject(err);\n } finally {\n // \u8FDB\u5165\u51B7\u5374\u671F\uFF1Aentry \u7EE7\u7EED\u7559\u5728 Map \u4E2D\uFF0C\u540E\u7EED\u5230\u8FBE\u7684\u5DE5\u5177\u8C03\u7528\n // \u4F1A\u547D\u4E2D executing \u5206\u652F\u5E76\u590D\u7528 resultPromise\uFF0C\u4E0D\u4F1A\u521B\u5EFA\u65B0\u5361\u7247\u3002\n // \u51B7\u5374\u671F\u7ED3\u675F\u540E\u6E05\u7406\u3002\n setTimeout(() => authBatches.delete(bufferKey), AUTH_COOLDOWN_MS);\n }\n }, debounceMs);\n\n authBatches.set(bufferKey, entry);\n return promise;\n}\n\n// ---------------------------------------------------------------------------\n// PendingAppAuthFlow \u2014 \u7B49\u5F85\u7528\u6237\u786E\u8BA4\u7684\u5E94\u7528\u6743\u9650\u5F15\u5BFC\u6D41\u7A0B\n// ---------------------------------------------------------------------------\n\ninterface PendingAppAuthFlow {\n appId: string;\n accountId: string;\n cardId: string;\n sequence: number;\n requiredScopes: string[];\n /** \u4E0E\u89E6\u53D1 AppScopeMissingError \u65F6\u7684 scopeNeedType \u4E00\u81F4\u3002 */\n scopeNeedType?: 'one' | 'all';\n /** \u4E0E\u89E6\u53D1 AppScopeMissingError \u65F6\u7684 tokenType \u4E00\u81F4\u3002 */\n tokenType?: 'user' | 'tenant';\n cfg: ClawdbotConfig;\n ticket: LarkTicket;\n}\n\n/** TTL\uFF1A15 \u5206\u949F\u540E\u81EA\u52A8\u6E05\u7406\uFF0C\u9632\u6B62\u5185\u5B58\u6CC4\u6F0F\u3002 */\nconst PENDING_FLOW_TTL_MS = 15 * 60 * 1000;\n\n/** \u8BA1\u7B97\u53BB\u91CD key\uFF08chatId + messageId + \u6709\u5E8F scopes\uFF09\u3002 */\nfunction makeDedupKey(chatId: string, messageId: string, scopes: string[]): string {\n return chatId + '\\0' + messageId + '\\0' + [...scopes].sort().join(',');\n}\n\n/** \u6CE8\u518C\u540E\u7684 flow\uFF0C\u9644\u52A0\u7D22\u5F15\u952E\u4FE1\u606F */\ntype RegisteredFlow = PendingAppAuthFlow & {\n dedupKey: string;\n activeCardKey: string;\n};\n\n/**\n * \u5E94\u7528\u6743\u9650\u6388\u6743\u6D41\u7BA1\u7406\u5668 \u2014 \u7EDF\u4E00\u7BA1\u7406\u4E09\u4E2A\u5173\u8054\u7D22\u5F15\u7684\u4E00\u81F4\u6027\u3002\n *\n * \u66FF\u4EE3\u539F\u6765\u6563\u5E03\u7684 pendingAppAuthFlows / dedupIndex / activeAppCardIndex \u4E09\u4E2A Map\uFF0C\n * \u786E\u4FDD\u6CE8\u518C\u3001\u5220\u9664\u3001\u8FC1\u79FB\u64CD\u4F5C\u7684\u539F\u5B50\u6027\u3002\n */\nclass AppAuthFlowManager {\n private readonly flows = new Map<string, RegisteredFlow>();\n private readonly dedupIndex = new Map<string, string>();\n private readonly activeCardIndex = new Map<string, string>();\n\n /** \u539F\u5B50\u6CE8\u518C\u65B0\u6D41\u7A0B\uFF08\u540C\u65F6\u5199\u5165 3 \u4E2A\u7D22\u5F15 + \u8BBE\u7F6E\u7EDF\u4E00 TTL\uFF09 */\n register(operationId: string, flow: PendingAppAuthFlow, dedupKey: string, activeCardKey: string): void {\n const registered: RegisteredFlow = { ...flow, dedupKey, activeCardKey };\n this.flows.set(operationId, registered);\n this.dedupIndex.set(dedupKey, operationId);\n this.activeCardIndex.set(activeCardKey, operationId);\n\n // \u7EDF\u4E00 TTL \u6E05\u7406\n setTimeout(() => {\n if (!this.flows.has(operationId)) return; // \u5DF2\u88AB\u624B\u52A8\u6E05\u7406\uFF0C\u8DF3\u8FC7\n this.remove(operationId);\n }, PENDING_FLOW_TTL_MS);\n }\n\n /** \u53EA\u9700 operationId \u5373\u53EF\u539F\u5B50\u6E05\u7406\u6240\u6709\u7D22\u5F15 */\n remove(operationId: string): void {\n const flow = this.flows.get(operationId);\n if (!flow) return;\n\n // \u8054\u52A8\u6E05\u7406\u5EF6\u8FDF\u7528\u6237\u6388\u6743\u961F\u5217\uFF08\u9632\u6B62\u5185\u5B58\u6CC4\u6F0F\uFF09\n if (flow.ticket?.senderOpenId) {\n const deferKey = `${flow.accountId}:${flow.ticket.senderOpenId}:${flow.ticket.messageId}`;\n deferredUserAuth.delete(deferKey);\n }\n\n this.flows.delete(operationId);\n // \u6761\u4EF6\u5220\u9664\uFF1A\u9632\u6B62\u8BEF\u5220\u5DF2\u88AB\u65B0 flow \u8986\u76D6\u7684\u7D22\u5F15\n if (this.dedupIndex.get(flow.dedupKey) === operationId) {\n this.dedupIndex.delete(flow.dedupKey);\n }\n if (this.activeCardIndex.get(flow.activeCardKey) === operationId) {\n this.activeCardIndex.delete(flow.activeCardKey);\n }\n }\n\n /**\n * \u8FC1\u79FB\u5230\u65B0 operationId\uFF08\u5361\u7247\u590D\u7528\u573A\u666F\uFF1A\u6309\u94AE\u56DE\u8C03\u9700\u8981\u5339\u914D\u65B0 ID\uFF09\u3002\n * \u539F\u5B50\u64CD\u4F5C\uFF1A\u6E05\u7406\u65E7\u7D22\u5F15 \u2192 \u66F4\u65B0 flow \u2192 \u5EFA\u7ACB\u65B0\u7D22\u5F15 \u2192 \u6CE8\u518C\u65B0 TTL\u3002\n *\n * \u4FEE\u590D\u539F\u4EE3\u7801\u5361\u7247\u590D\u7528\u8DEF\u5F84\u7F3A\u5C11 TTL \u6CE8\u518C\u5BFC\u81F4\u7684\u5185\u5B58\u6CC4\u6F0F\u3002\n */\n migrateToNewOperationId(\n oldOperationId: string,\n newOperationId: string,\n updates?: { dedupKey?: string; requiredScopes?: string[]; scopeNeedType?: 'one' | 'all' },\n ): RegisteredFlow | undefined {\n const flow = this.flows.get(oldOperationId);\n if (!flow) return undefined;\n\n // \u6E05\u7406\u65E7\u7D22\u5F15\n this.flows.delete(oldOperationId);\n if (updates?.dedupKey) {\n if (this.dedupIndex.get(flow.dedupKey) === oldOperationId) {\n this.dedupIndex.delete(flow.dedupKey);\n }\n flow.dedupKey = updates.dedupKey;\n }\n if (updates?.requiredScopes) flow.requiredScopes = updates.requiredScopes;\n if (updates?.scopeNeedType) flow.scopeNeedType = updates.scopeNeedType;\n\n // \u5EFA\u7ACB\u65B0\u7D22\u5F15\n this.flows.set(newOperationId, flow);\n this.dedupIndex.set(flow.dedupKey, newOperationId);\n this.activeCardIndex.set(flow.activeCardKey, newOperationId);\n\n // \u4E3A\u65B0 operationId \u6CE8\u518C TTL\uFF08\u4FEE\u590D\u539F\u4EE3\u7801\u7684\u5185\u5B58\u6CC4\u6F0F\uFF09\n setTimeout(() => {\n if (!this.flows.has(newOperationId)) return;\n this.remove(newOperationId);\n }, PENDING_FLOW_TTL_MS);\n\n return flow;\n }\n\n /** \u901A\u8FC7 operationId \u67E5\u8BE2\uFF08card action \u56DE\u8C03\u7528\uFF09 */\n getByOperationId(id: string): PendingAppAuthFlow | undefined {\n return this.flows.get(id);\n }\n\n /** \u901A\u8FC7\u53BB\u91CD\u952E\u67E5\u8BE2\uFF08\u907F\u514D\u53D1\u9001\u91CD\u590D\u5361\u7247\uFF09 */\n getByDedupKey(key: string): { operationId: string; flow: PendingAppAuthFlow } | undefined {\n const opId = this.dedupIndex.get(key);\n if (!opId) return undefined;\n const flow = this.flows.get(opId);\n return flow ? { operationId: opId, flow } : undefined;\n }\n\n /** \u901A\u8FC7\u6D3B\u8DC3\u5361\u7247\u952E\u67E5\u8BE2\uFF08\u540C\u6D88\u606F\u5361\u7247\u590D\u7528\uFF09 */\n getByActiveCardKey(key: string): { operationId: string; flow: RegisteredFlow } | undefined {\n const opId = this.activeCardIndex.get(key);\n if (!opId) return undefined;\n const flow = this.flows.get(opId);\n return flow ? { operationId: opId, flow } : undefined;\n }\n}\n\nconst appAuthFlows = new AppAuthFlowManager();\n\n// ---------------------------------------------------------------------------\n// Deferred User Auth Queue \u2014 \u7528\u6237\u6388\u6743\u5EF6\u8FDF\u961F\u5217\n//\n// \u5F53\u7528\u6237\u6388\u6743\u8BF7\u6C42\u5230\u8FBE\u65F6\uFF0C\u5982\u679C\u540C\u4E00\u6D88\u606F\u4E0A\u4E0B\u6587\u5B58\u5728\u672A\u5B8C\u6210\u7684\u5E94\u7528\u6743\u9650\u6D41\u7A0B\uFF0C\n// \u5C06 scope \u6536\u96C6\u5230\u5EF6\u8FDF\u961F\u5217\uFF0C\u7B49\u5E94\u7528\u6388\u6743\u5B8C\u6210\u540E\u7EDF\u4E00\u53D1\u8D77 OAuth\u3002\n// ---------------------------------------------------------------------------\n\ninterface DeferredUserAuthEntry {\n scopes: Set<string>;\n account: ConfiguredLarkAccount;\n cfg: ClawdbotConfig;\n ticket: LarkTicket;\n}\n\n/** \u5EF6\u8FDF\u7528\u6237\u6388\u6743\u961F\u5217\u3002Key: `${accountId}:${senderOpenId}:${messageId}` */\nconst deferredUserAuth = new Map<string, DeferredUserAuthEntry>();\n\n/**\n * \u68C0\u67E5\u6307\u5B9A\u6D88\u606F\u4E0A\u4E0B\u6587\u662F\u5426\u6709\u672A\u5B8C\u6210\u7684\u5E94\u7528\u6743\u9650\u6388\u6743\u6D41\u7A0B\u3002\n * \u68C0\u67E5\u4E24\u4E2A\u6765\u6E90\uFF1A\n * 1. authBatches \u4E2D\u7684 app auth entry\uFF08collecting/executing \u9636\u6BB5\uFF09\n * 2. appAuthFlows \u4E2D\u7684\u6D3B\u8DC3\u6D41\uFF08\u5361\u7247\u5DF2\u53D1\u9001\uFF0C\u7B49\u5F85\u7528\u6237\u70B9\u51FB\"\u5DF2\u5B8C\u6210\"\uFF09\n */\nfunction hasActiveAppAuthForMessage(ticket: LarkTicket): boolean {\n const appKey = `app:${ticket.accountId}:${ticket.chatId}:${ticket.messageId}`;\n const appEntry = authBatches.get(appKey);\n if (appEntry && (appEntry.phase === 'collecting' || appEntry.phase === 'executing')) {\n return true;\n }\n const activeCardKey = `${ticket.chatId}:${ticket.messageId}`;\n return !!appAuthFlows.getByActiveCardKey(activeCardKey);\n}\n\n/**\n * \u5C06\u7528\u6237\u6388\u6743 scope \u6DFB\u52A0\u5230\u5EF6\u8FDF\u961F\u5217\u3002\n * \u591A\u4E2A\u5DE5\u5177\u8C03\u7528\u7684 scope \u4F1A\u88AB\u5408\u5E76\u5230\u540C\u4E00\u4E2A entry\u3002\n */\nfunction addToDeferredUserAuth(\n ticket: LarkTicket,\n scopes: string[],\n account: ConfiguredLarkAccount,\n cfg: ClawdbotConfig,\n): void {\n const key = `${ticket.accountId}:${ticket.senderOpenId}:${ticket.messageId}`;\n const existing = deferredUserAuth.get(key);\n if (existing) {\n for (const s of scopes) existing.scopes.add(s);\n log.info(`deferred user auth scope merge \u2192 key=${key}, scopes=[${[...existing.scopes].join(', ')}]`);\n } else {\n deferredUserAuth.set(key, { scopes: new Set(scopes), account, cfg, ticket });\n log.info(`deferred user auth created \u2192 key=${key}, scopes=[${scopes.join(', ')}]`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Card builders \u2014 CardKit v2 \u683C\u5F0F\n// ---------------------------------------------------------------------------\n\n/**\n * \u6784\u5EFA\u5E94\u7528\u6743\u9650\u5F15\u5BFC\u5361\u7247\u3002\n *\n * \u84DD\u8272 header\uFF0C\u5217\u51FA\u7F3A\u5931\u7684 scope\uFF0C\u63D0\u4F9B\u6743\u9650\u7BA1\u7406\u94FE\u63A5\u548C\"\u6211\u5DF2\u5B8C\u6210\uFF0C\u7EE7\u7EED\u6388\u6743\"\u6309\u94AE\u3002\n */\nfunction buildAppScopeMissingCard(params: {\n missingScopes: string[];\n appId?: string;\n operationId: string;\n}): Record<string, unknown> {\n const { missingScopes, appId, operationId } = params;\n const authUrl = appId\n ? `https://open.feishu.cn/app/${appId}/auth?q=${encodeURIComponent(missingScopes.join(','))}&op_from=feishu-openclaw&token_type=user`\n : 'https://open.feishu.cn/';\n const multiUrl = { url: authUrl, pc_url: authUrl, android_url: authUrl, ios_url: authUrl };\n\n const scopeList = missingScopes.map((s) => `\u2022 ${s}`).join('\\n');\n\n return {\n schema: '2.0',\n config: { wide_screen_mode: true },\n header: {\n title: { tag: 'plain_text', content: '\uD83D\uDD10 \u9700\u8981\u7533\u8BF7\u6743\u9650\u624D\u80FD\u7EE7\u7EED' },\n template: 'orange',\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content: '\u8C03\u7528\u524D\uFF0C\u8BF7\u4F60\u5148\u7533\u8BF7\u4EE5\u4E0B**\u6240\u6709**\u6743\u9650\uFF1A',\n text_size: 'normal',\n },\n {\n tag: 'column_set',\n flex_mode: 'none',\n background_style: 'grey',\n horizontal_spacing: 'default',\n columns: [\n {\n tag: 'column',\n width: 'weighted',\n weight: 1,\n vertical_align: 'center',\n elements: [{ tag: 'markdown', content: scopeList }],\n },\n ],\n },\n { tag: 'hr' },\n {\n tag: 'column_set',\n flex_mode: 'none',\n horizontal_spacing: 'default',\n columns: [\n {\n tag: 'column',\n width: 'weighted',\n weight: 3,\n vertical_align: 'center',\n elements: [{ tag: 'markdown', content: '**\u7B2C\u4E00\u6B65\uFF1A\u7533\u8BF7\u6240\u6709\u6743\u9650**' }],\n },\n {\n tag: 'column',\n width: 'weighted',\n weight: 1,\n vertical_align: 'center',\n elements: [\n {\n tag: 'button',\n text: { tag: 'plain_text', content: '\u53BB\u7533\u8BF7' },\n type: 'primary',\n multi_url: multiUrl,\n },\n ],\n },\n ],\n },\n {\n tag: 'column_set',\n flex_mode: 'none',\n horizontal_spacing: 'default',\n columns: [\n {\n tag: 'column',\n width: 'weighted',\n weight: 3,\n vertical_align: 'center',\n elements: [{ tag: 'markdown', content: '**\u7B2C\u4E8C\u6B65\uFF1A\u521B\u5EFA\u7248\u672C\u5E76\u5BA1\u6838\u901A\u8FC7**' }],\n },\n {\n tag: 'column',\n width: 'weighted',\n weight: 1,\n vertical_align: 'center',\n elements: [\n {\n tag: 'button',\n text: { tag: 'plain_text', content: '\u5DF2\u5B8C\u6210' },\n type: 'default',\n value: { action: 'app_auth_done', operation_id: operationId },\n },\n ],\n },\n ],\n },\n ],\n },\n };\n}\n\n/**\n * \u6784\u5EFA\u5E94\u7528\u6743\u9650\u5F15\u5BFC\u5361\u7247\u7684\"\u5904\u7406\u4E2D\"\u72B6\u6001\uFF08\u7528\u6237\u70B9\u51FB\u6309\u94AE\u540E\u66F4\u65B0\uFF09\u3002\n */\nfunction buildAppAuthProgressCard(): Record<string, unknown> {\n return {\n schema: '2.0',\n config: { wide_screen_mode: false },\n header: {\n title: { tag: 'plain_text', content: '\u5E94\u7528\u6743\u9650\u5DF2\u5F00\u901A' },\n subtitle: { tag: 'plain_text', content: '' },\n template: 'green',\n padding: '12px 12px 12px 12px',\n icon: { tag: 'standard_icon', token: 'yes_filled' },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content: '\u60A8\u7684\u5E94\u7528\u6743\u9650\u5DF2\u5F00\u901A\uFF0C\u6B63\u5728\u4E3A\u60A8\u53D1\u8D77\u7528\u6237\u6388\u6743',\n text_size: 'normal',\n },\n ],\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * \u53D1\u9001\u5E94\u7528\u6743\u9650\u5F15\u5BFC\u5361\u7247\uFF0C\u5E76\u5C06 flow \u5B58\u5165 pendingAppAuthFlows\u3002\n * \u8FD4\u56DE\u5DE5\u5177\u7ED3\u679C\uFF08\u544A\u77E5 AI \u7B49\u5F85\u7528\u6237\u64CD\u4F5C\uFF09\u3002\n */\nasync function sendAppScopeCard(params: {\n account: ConfiguredLarkAccount;\n missingScopes: string[];\n appId?: string;\n scopeNeedType?: 'one' | 'all';\n tokenType?: 'user' | 'tenant';\n cfg: ClawdbotConfig;\n ticket: LarkTicket;\n}): Promise<ReturnType<typeof json>> {\n const { account, missingScopes, appId, scopeNeedType, tokenType, cfg, ticket } = params;\n const { accountId, chatId, messageId } = ticket;\n const activeCardKey = `${chatId}:${messageId}`;\n\n // ---- \u53BB\u91CD\uFF1A\u907F\u514D\u5E76\u53D1\u5DE5\u5177\u8C03\u7528\u65F6\u53D1\u51FA\u591A\u5F20\u5185\u5BB9\u76F8\u540C\u7684\u5361\u7247 ----\n const dedup = makeDedupKey(chatId, messageId, missingScopes);\n const existingEntry = appAuthFlows.getByDedupKey(dedup);\n if (existingEntry) {\n log.info(\n `dedup \u2013 app-scope card already pending for chatId=${chatId}, ` +\n `scopes=[${missingScopes.join(', ')}], skipping duplicate send`,\n );\n return json({\n awaiting_app_authorization: true,\n message:\n '\u5DF2\u5411\u7528\u6237\u53D1\u9001\u6388\u6743\u5F15\u5BFC\u5361\u7247\uFF0C\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u6388\u6743\u64CD\u4F5C\u3002' +\n '\u8BF7\u544A\u77E5\u7528\u6237\uFF1A\u6309\u7167\u5361\u7247\u63D0\u793A\u5B8C\u6210\u6388\u6743\uFF0C\u5B8C\u6210\u540E\u7CFB\u7EDF\u5C06\u81EA\u52A8\u91CD\u8BD5\u4E4B\u524D\u7684\u64CD\u4F5C\u3002' +\n '\u8BF7\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u5361\u7247\u64CD\u4F5C\uFF0C\u4E0D\u8981\u5EFA\u8BAE\u5176\u4ED6\u66FF\u4EE3\u65B9\u6848\u3002',\n missing_scopes: missingScopes,\n });\n }\n\n // ---- \u5361\u7247\u590D\u7528\uFF1A\u540C\u4E00 chatId+messageId \u5DF2\u6709\u6D3B\u8DC3\u5361\u7247\u65F6\uFF0C\u539F\u5730\u66F4\u65B0\u800C\u975E\u521B\u5EFA\u65B0\u5361\u7247 ----\n const activeEntry = appAuthFlows.getByActiveCardKey(activeCardKey);\n\n if (activeEntry) {\n const { operationId: activeOpId, flow: activeFlow } = activeEntry;\n // \u66F4\u65B0\u5DF2\u6709\u5361\u7247\u7684\u5185\u5BB9\uFF08\u5408\u5E76\u540E\u7684 scope\uFF09\n const newOperationId = Date.now().toString(36) + Math.random().toString(36).slice(2);\n const card = buildAppScopeMissingCard({ missingScopes, appId, operationId: newOperationId });\n const newSeq = activeFlow.sequence + 1;\n\n // TOCTOU \u4FEE\u590D\uFF1A\u5148\u539F\u5B50\u8FC1\u79FB\uFF08\u540C\u6B65\u64CD\u4F5C\uFF09\uFF0C\u518D await \u66F4\u65B0\u5361\u7247\n const newDedup = makeDedupKey(chatId, messageId, missingScopes);\n const migrated = appAuthFlows.migrateToNewOperationId(activeOpId, newOperationId, {\n dedupKey: newDedup,\n requiredScopes: missingScopes,\n scopeNeedType,\n });\n if (!migrated) {\n // \u88AB\u5176\u4ED6\u5E76\u53D1\u8BF7\u6C42\u62A2\u5148\u8FC1\u79FB\u4E86\uFF0C\u964D\u7EA7\u5230\u65B0\u5EFA\u5361\u7247\n log.info(`migrate raced, falling through to new card creation`);\n } else {\n try {\n await updateCardKitCardForAuth({\n cfg,\n cardId: activeFlow.cardId,\n card,\n sequence: newSeq,\n accountId,\n });\n log.info(\n `app-scope card updated in-place, cardId=${activeFlow.cardId}, ` +\n `seq=${newSeq}, scopes=[${missingScopes.join(', ')}]`,\n );\n\n // \u66F4\u65B0 sequence\uFF08migrate \u4E0D\u5904\u7406 sequence\uFF09\n migrated.sequence = newSeq;\n\n return json({\n awaiting_app_authorization: true,\n message:\n '\u5DF2\u5411\u7528\u6237\u53D1\u9001\u6388\u6743\u5F15\u5BFC\u5361\u7247\uFF0C\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u6388\u6743\u64CD\u4F5C\u3002' +\n '\u8BF7\u544A\u77E5\u7528\u6237\uFF1A\u6309\u7167\u5361\u7247\u63D0\u793A\u5B8C\u6210\u6388\u6743\uFF0C\u5B8C\u6210\u540E\u7CFB\u7EDF\u5C06\u81EA\u52A8\u91CD\u8BD5\u4E4B\u524D\u7684\u64CD\u4F5C\u3002' +\n '\u8BF7\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u5361\u7247\u64CD\u4F5C\uFF0C\u4E0D\u8981\u5EFA\u8BAE\u5176\u4ED6\u66FF\u4EE3\u65B9\u6848\u3002',\n missing_scopes: missingScopes,\n });\n } catch (err) {\n // \u56DE\u6EDA\uFF1A\u5220\u9664\u5DF2\u8FC1\u79FB\u7684 flow\n appAuthFlows.remove(newOperationId);\n log.warn(`failed to update existing app-scope card, creating new one: ${err}`);\n // \u964D\u7EA7\uFF1A\u8D70\u4E0B\u9762\u7684\u65B0\u5EFA\u5361\u7247\u8DEF\u5F84\n }\n }\n }\n\n const operationId = Date.now().toString(36) + Math.random().toString(36).slice(2);\n\n const card = buildAppScopeMissingCard({ missingScopes, appId, operationId });\n\n // \u521B\u5EFA CardKit \u5361\u7247\u5B9E\u4F53\n const cardId = await createCardEntity({ cfg, card, accountId });\n if (!cardId) {\n log.warn('createCardEntity failed for app-scope card, falling back');\n return json({\n error: 'app_scope_missing',\n missing_scopes: missingScopes,\n message:\n `\u5E94\u7528\u7F3A\u5C11\u4EE5\u4E0B\u6743\u9650\uFF1A${missingScopes.join(', ')}\uFF0C` +\n `\u8BF7\u7BA1\u7406\u5458\u5728\u5F00\u653E\u5E73\u53F0\u5F00\u901A\u540E\u91CD\u8BD5\u3002` +\n (appId ? `\\n\u6743\u9650\u7BA1\u7406\uFF1Ahttps://open.feishu.cn/app/${appId}/permission` : ''),\n });\n }\n\n // \u53D1\u9001\u5230\u5F53\u524D\u4F1A\u8BDD\n const replyToMsgId = ticket.messageId?.startsWith('om_') ? ticket.messageId : undefined;\n\n await sendCardByCardId({\n cfg,\n to: chatId,\n cardId,\n replyToMessageId: replyToMsgId,\n replyInThread: Boolean(ticket?.threadId),\n accountId,\n });\n\n // \u539F\u5B50\u6CE8\u518C\u5230\u7BA1\u7406\u5668\uFF08\u7EDF\u4E00 TTL \u6E05\u7406\uFF09\n const flow: PendingAppAuthFlow = {\n appId: appId ?? account.appId,\n accountId,\n cardId,\n sequence: 0,\n requiredScopes: missingScopes,\n scopeNeedType,\n tokenType,\n cfg,\n ticket,\n };\n appAuthFlows.register(operationId, flow, dedup, activeCardKey);\n\n log.info(`app-scope card sent, operationId=${operationId}, scopes=[${missingScopes.join(', ')}]`);\n\n return json({\n awaiting_app_authorization: true,\n message:\n '\u5DF2\u5411\u7528\u6237\u53D1\u9001\u6388\u6743\u5F15\u5BFC\u5361\u7247\uFF0C\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u6388\u6743\u64CD\u4F5C\u3002' +\n '\u8BF7\u544A\u77E5\u7528\u6237\uFF1A\u6309\u7167\u5361\u7247\u63D0\u793A\u5B8C\u6210\u6388\u6743\uFF0C\u5B8C\u6210\u540E\u7CFB\u7EDF\u5C06\u81EA\u52A8\u91CD\u8BD5\u4E4B\u524D\u7684\u64CD\u4F5C\u3002' +\n '\u8BF7\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u5361\u7247\u64CD\u4F5C\uFF0C\u4E0D\u8981\u5EFA\u8BAE\u5176\u4ED6\u66FF\u4EE3\u65B9\u6848\u3002',\n missing_scopes: missingScopes,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Card action handler (exported for monitor.ts)\n// ---------------------------------------------------------------------------\n\n/**\n * \u5904\u7406 card.action.trigger \u56DE\u8C03\u4E8B\u4EF6\uFF08\u7531 monitor.ts \u8C03\u7528\uFF09\u3002\n *\n * \u5F53\u7528\u6237\u70B9\u51FB\u5E94\u7528\u6743\u9650\u5F15\u5BFC\u5361\u7247\u7684\"\u6211\u5DF2\u5B8C\u6210\uFF0C\u7EE7\u7EED\u6388\u6743\"\u6309\u94AE\u65F6\uFF1A\n * 1. \u66F4\u65B0\u5361\u7247\u4E3A\"\u5904\u7406\u4E2D\"\u72B6\u6001\n * 2. \u6E05\u9664\u5E94\u7528 scope \u7F13\u5B58\n * 3. \u53D1\u9001\u4E2D\u95F4\u5408\u6210\u6D88\u606F\u544A\u77E5 AI\n * 4. \u53D1\u8D77 OAuth Device Flow\n *\n * \u6CE8\u610F\uFF1A\u51FD\u6570\u4F53\u5185\u7684\u4E3B\u8981\u903B\u8F91\u901A\u8FC7 setImmediate + fire-and-forget \u5F02\u6B65\u6267\u884C\uFF0C\n * \u786E\u4FDD Feishu card.action.trigger \u56DE\u8C03\u5728 3 \u79D2\u5185\u8FD4\u56DE\u3002\n */\nexport async function handleCardAction(data: unknown, cfg: ClawdbotConfig, accountId: string): Promise<unknown> {\n let action: string | undefined;\n let operationId: string | undefined;\n let senderOpenId: string | undefined;\n let buttonName: string | undefined;\n\n try {\n const event = data as {\n operator?: { open_id?: string };\n action?: { value?: { action?: string; operation_id?: string }; name?: string };\n };\n action = event.action?.value?.action;\n operationId = event.action?.value?.operation_id;\n senderOpenId = event.operator?.open_id;\n buttonName = event.action?.name;\n log.debug(`card action received: action=${action}, buttonName=${buttonName}, operationId=${operationId}`);\n } catch {\n return;\n }\n\n if (buttonName === 'submit_project_auth') {\n return handleProjectAuthCardAction(data, cfg, accountId);\n }\n\n if (action !== 'app_auth_done' || !operationId) return;\n\n const flow = appAuthFlows.getByOperationId(operationId);\n if (!flow) {\n log.warn(`card action ${operationId} not found (expired or already handled)`);\n return;\n }\n\n log.info(`app_auth_done clicked by ${senderOpenId}, operationId=${operationId}`);\n\n // scope \u6821\u9A8C\u5728\u540C\u6B65\u8DEF\u5F84\u5B8C\u6210\uFF083 \u79D2\u5185\u8FD4\u56DE toast response\uFF09\n invalidateAppScopeCache(flow.appId);\n\n const acct = getLarkAccount(flow.cfg, flow.accountId);\n if (!acct.configured) {\n log.warn(`account ${flow.accountId} not configured, skipping OAuth`);\n return;\n }\n\n const sdk = LarkClient.fromAccount(acct).sdk;\n let grantedScopes: string[] = [];\n try {\n // \u4F7F\u7528\u4E0E\u539F\u59CB AppScopeMissingError \u76F8\u540C\u7684 tokenType\uFF0C\u4FDD\u8BC1\u6821\u9A8C\u903B\u8F91\u5B8C\u5168\u4E00\u81F4\n grantedScopes = await getAppGrantedScopes(sdk, flow.appId, flow.tokenType);\n } catch (err) {\n log.warn(`failed to re-check app scopes: ${err}, proceeding anyway`);\n }\n\n // \u4F7F\u7528\u5171\u4EAB\u51FD\u6570 isAppScopeSatisfied\uFF0C\u4E0E tool-client invoke() \u903B\u8F91\u5B8C\u5168\u4E00\u81F4\uFF1A\n // - scopeNeedType \"all\" \u2192 \u5168\u90E8\u5FC5\u987B\u6709\n // - \u9ED8\u8BA4\"one\" \u2192 \u4EA4\u96C6\u975E\u7A7A\u5373\u53EF\n // - grantedScopes \u4E3A\u7A7A \u2192 \u89C6\u4E3A\u6EE1\u8DB3\uFF08API \u5931\u8D25\u9000\u56DE\u670D\u52A1\u7AEF\u5224\u65AD\uFF09\n if (!isAppScopeSatisfied(grantedScopes, flow.requiredScopes, flow.scopeNeedType)) {\n log.warn(`app scopes still missing after user confirmation: [${flow.requiredScopes.join(', ')}]`);\n return {\n toast: {\n type: 'error',\n content: '\u6743\u9650\u5C1A\u672A\u5F00\u901A\uFF0C\u8BF7\u786E\u8BA4\u5DF2\u7533\u8BF7\u5E76\u5BA1\u6838\u901A\u8FC7\u540E\u518D\u8BD5',\n },\n };\n }\n\n log.info(`app scopes verified, proceeding with OAuth`);\n\n // \u2605 \u5728 remove() \u4E4B\u524D\u5148\u53D6\u51FA\u5EF6\u8FDF\u961F\u5217\u6570\u636E\uFF0C\u907F\u514D remove() \u7684\u8054\u52A8\u6E05\u7406\u63D0\u524D\u5220\u6389\u5B83\n const deferKey = flow.ticket.senderOpenId\n ? `${flow.accountId}:${flow.ticket.senderOpenId}:${flow.ticket.messageId}`\n : undefined;\n const consumedDeferred = deferKey ? deferredUserAuth.get(deferKey) : undefined;\n if (consumedDeferred && deferKey) {\n deferredUserAuth.delete(deferKey);\n log.info(`consumed deferred user auth scopes: [${[...consumedDeferred.scopes].join(', ')}]`);\n }\n\n // \u6821\u9A8C\u901A\u8FC7\u624D\u5220\u9664\uFF0C\u9632\u6B62\u7528\u6237\u5728\u6743\u9650\u901A\u8FC7\u524D\u591A\u6B21\u70B9\u51FB\u65E0\u6CD5\u91CD\u8BD5\n appAuthFlows.remove(operationId);\n\n // \u901A\u8FC7\u56DE\u8C03\u8FD4\u56DE\u503C\u76F4\u63A5\u66F4\u65B0\u5361\u7247\uFF08\u65B9\u5F0F\u4E00\uFF1A3 \u79D2\u5185\u7ACB\u5373\u66F4\u65B0\uFF09\u3002\n // \u98DE\u4E66\u6587\u6863\u8981\u6C42 card \u5B57\u6BB5\u5FC5\u987B\u5305\u542B type + data \u5305\u88C5\uFF1A\n // { card: { type: \"raw\", data: { schema: \"2.0\", ... } } }\n // \u6CE8\u610F\uFF1A\u4E0D\u80FD\u5728\u56DE\u8C03\u8FD4\u56DE\u524D\u8C03\u7528 card.update API\uFF0C\u98DE\u4E66\u6587\u6863\u660E\u786E\u8BF4\u660E\n // \"\u5EF6\u65F6\u66F4\u65B0\u5FC5\u987B\u5728\u54CD\u5E94\u56DE\u8C03\u8BF7\u6C42\u4E4B\u540E\u6267\u884C\uFF0C\u5E76\u884C\u6267\u884C\u6216\u63D0\u524D\u6267\u884C\u4F1A\u51FA\u73B0\u66F4\u65B0\u5931\u8D25\"\u3002\n const successCard = buildAppAuthProgressCard();\n\n // \u540E\u53F0\u5F02\u6B65\uFF1A\u56DE\u8C03\u54CD\u5E94\u4E4B\u540E\u518D\u6267\u884C API \u66F4\u65B0 + OAuth\n setImmediate(async () => {\n try {\n // \u901A\u8FC7 API \u518D\u6B21\u66F4\u65B0\u5361\u7247\uFF08\u786E\u4FDD\u6240\u6709\u67E5\u770B\u8005\u90FD\u770B\u5230\u66F4\u65B0\uFF0C\u4E0D\u53EA\u662F\u70B9\u51FB\u8005\uFF09\n try {\n await updateCardKitCardForAuth({\n cfg,\n cardId: flow.cardId,\n card: successCard,\n sequence: flow.sequence + 1,\n accountId,\n });\n } catch (err) {\n log.warn(`failed to update app-scope card to progress via API: ${err}`);\n }\n\n // \u53D1\u8D77 OAuth Device Flow\uFF08\u5B8C\u6210\u540E executeAuthorize \u4F1A\u81EA\u52A8\u53D1\u5408\u6210\u6D88\u606F\u89E6\u53D1 AI \u91CD\u8BD5\uFF09\n if (!flow.ticket.senderOpenId) {\n log.warn('no senderOpenId in ticket, skipping OAuth');\n return;\n }\n\n // \u6536\u96C6\u6240\u6709\u6765\u6E90\u7684 scope\uFF08\u8FC7\u6EE4 offline_access\uFF1A\u4EC5 app \u7EA7\u9700\u8981\uFF0Cdevice-flow \u81EA\u52A8\u8FFD\u52A0\uFF09\n const mergedScopes = new Set(flow.requiredScopes.filter((s) => s !== 'offline_access'));\n\n // \u6765\u6E90 1: \u5EF6\u8FDF\u7528\u6237\u6388\u6743\u961F\u5217\uFF08\u5DF2\u5728\u540C\u6B65\u8DEF\u5F84\u4E2D\u63D0\u524D\u53D6\u51FA\uFF0C\u89C1 consumedDeferred\uFF09\n if (consumedDeferred) {\n for (const s of consumedDeferred.scopes) mergedScopes.add(s);\n }\n\n // \u6765\u6E90 2: \u73B0\u6709 user auth batch\uFF08\u5411\u540E\u517C\u5BB9\uFF0C\u5904\u7406\u672A\u88AB\u5EF6\u8FDF\u62E6\u622A\u7684 user auth\uFF09\n const userBatchKey = `user:${flow.accountId}:${flow.ticket.senderOpenId}:${flow.ticket.messageId}`;\n const userBatch = authBatches.get(userBatchKey);\n if (userBatch) {\n for (const s of userBatch.scopes) mergedScopes.add(s);\n log.info(`merged user batch scopes into app auth completion: [${[...mergedScopes].join(', ')}]`);\n }\n\n if (mergedScopes.size === 0) {\n // \u65E0\u4E1A\u52A1 scope \u9700\u8981\u7528\u6237\u6388\u6743\uFF08\u4F8B\u5982 offline_access \u662F\u552F\u4E00\u7F3A\u5931\u7684\u5E94\u7528\u6743\u9650\uFF0C\n // \u4E14\u6CA1\u6709\u5176\u4ED6\u5DE5\u5177\u4EA7\u751F\u7528\u6237\u6388\u6743\u9700\u6C42\uFF09\u3002\u8DF3\u8FC7 OAuth\uFF0C\u76F4\u63A5\u53D1\u5408\u6210\u6D88\u606F\u89E6\u53D1 AI \u91CD\u8BD5\uFF0C\n // \u91CD\u8BD5\u65F6\u5DE5\u5177\u4F1A\u81EA\u7136\u53D1\u73B0\u9700\u8981\u7528\u6237\u6388\u6743\u5E76\u53D1\u8D77\u6B63\u786E\u7684 OAuth \u6D41\u7A0B\u3002\n log.info('no business scopes to authorize after app auth, sending synthetic message for retry');\n const syntheticMsgId = `${flow.ticket.messageId}:app-auth-complete`;\n const syntheticEvent = {\n sender: { sender_id: { open_id: flow.ticket.senderOpenId } },\n message: {\n message_id: syntheticMsgId,\n chat_id: flow.ticket.chatId,\n chat_type: flow.ticket.chatType ?? ('p2p' as const),\n message_type: 'text',\n content: JSON.stringify({ text: '\u5E94\u7528\u6743\u9650\u5DF2\u5F00\u901A\uFF0C\u8BF7\u7EE7\u7EED\u6267\u884C\u4E4B\u524D\u7684\u64CD\u4F5C\u3002' }),\n thread_id: flow.ticket.threadId,\n },\n };\n const syntheticRuntime = {\n log: (msg: string) => log.info(msg),\n error: (msg: string) => log.error(msg),\n };\n const { promise } = enqueueFeishuChatTask({\n accountId: flow.accountId,\n chatId: flow.ticket.chatId,\n threadId: flow.ticket.threadId,\n task: async () => {\n await withTicket(\n {\n messageId: syntheticMsgId,\n chatId: flow.ticket.chatId,\n accountId: flow.accountId,\n startTime: Date.now(),\n senderOpenId: flow.ticket.senderOpenId!,\n chatType: flow.ticket.chatType,\n threadId: flow.ticket.threadId,\n },\n () =>\n handleFeishuMessage({\n cfg: flow.cfg,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n event: syntheticEvent as any,\n accountId: flow.accountId,\n forceMention: true,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n runtime: syntheticRuntime as any,\n replyToMessageId: flow.ticket.messageId,\n }),\n );\n },\n });\n await promise;\n log.info('synthetic message dispatched after app-auth-only completion');\n } else {\n await executeAuthorize({\n account: acct,\n senderOpenId: flow.ticket.senderOpenId,\n scope: [...mergedScopes].join(' '),\n showBatchAuthHint: true,\n forceAuth: true, // \u5E94\u7528\u6743\u9650\u521A\u7ECF\u5386\u79FB\u9664\u2192\u8865\u56DE\uFF0C\u4E0D\u4FE1\u4EFB\u672C\u5730 UAT \u7F13\u5B58\n cfg: flow.cfg,\n ticket: flow.ticket,\n });\n }\n } catch (err) {\n log.error(`handleCardAction background task failed: ${err}`);\n }\n });\n\n // \u56DE\u8C03\u8FD4\u56DE\u503C\uFF1A\u901A\u8FC7 card \u5B57\u6BB5\u7ACB\u5373\u66F4\u65B0\u5361\u7247 + toast \u63D0\u793A\n return {\n toast: {\n type: 'success' as const,\n content: '\u6743\u9650\u786E\u8BA4\u6210\u529F',\n },\n card: {\n type: 'raw' as const,\n data: successCard,\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Main export\n// ---------------------------------------------------------------------------\n\n/**\n * \u7EDF\u4E00\u5904\u7406 `client.invoke()` \u629B\u51FA\u7684\u9519\u8BEF\uFF0C\u652F\u6301\u81EA\u52A8\u53D1\u8D77 OAuth \u6388\u6743\u3002\n *\n * \u66FF\u4EE3 `handleInvokeError`\uFF0C\u5728\u5DE5\u5177\u5C42\u76F4\u63A5\u5904\u7406\u6388\u6743\u95EE\u9898\uFF1A\n * - \u7528\u6237\u6388\u6743\u7C7B\u9519\u8BEF \u2192 \u76F4\u63A5 executeAuthorize\uFF08\u53D1 Device Flow \u5361\u7247\uFF09\n * - \u5E94\u7528\u6743\u9650\u7F3A\u5931 \u2192 \u53D1\u9001\u5F15\u5BFC\u5361\u7247\uFF0C\u7528\u6237\u786E\u8BA4\u540E\u81EA\u52A8\u63A5\u529B OAuth\n * - \u5176\u4ED6\u9519\u8BEF \u2192 \u56DE\u9000\u5230 handleInvokeError \u7684\u6807\u51C6\u5904\u7406\n *\n * @param err - invoke() \u6216\u5176\u4ED6\u903B\u8F91\u629B\u51FA\u7684\u9519\u8BEF\n * @param cfg - OpenClaw \u914D\u7F6E\u5BF9\u8C61\uFF08\u4ECE\u5DE5\u5177\u6CE8\u518C\u51FD\u6570\u7684\u95ED\u5305\u4E2D\u83B7\u53D6\uFF09\n */\nexport async function handleInvokeErrorWithAutoAuth(err: unknown, cfg: ClawdbotConfig) {\n const ticket = getTicket();\n\n // --- Path 0\uFF1AOwner \u8BBF\u95EE\u62D2\u7EDD \u2192 \u76F4\u63A5\u8FD4\u56DE\u53CB\u597D\u63D0\u793A ---\n if (err instanceof OwnerAccessDeniedError) {\n return json({\n error: 'permission_denied',\n message: '\u5F53\u524D\u5E94\u7528\u4EC5\u9650\u6240\u6709\u8005\uFF08App Owner\uFF09\u4F7F\u7528\u3002\u60A8\u6CA1\u6709\u6743\u9650\u4F7F\u7528\u76F8\u5173\u529F\u80FD\u3002',\n user_open_id: err.userOpenId,\n // \u6CE8\u610F\uFF1A\u4E0D\u5E8F\u5217\u5316 err.appOwnerId\uFF0C\u907F\u514D\u6CC4\u9732 owner \u7684 open_id\n });\n }\n\n if (ticket) {\n const senderOpenId = ticket.senderOpenId;\n\n // --- Path 1\uFF1A\u7528\u6237\u6388\u6743\u7C7B\u9519\u8BEF \u2192 \u9632\u6296\u5408\u5E76\u540E\u53D1\u8D77 OAuth ---\n\n if (senderOpenId) {\n // 1a. \u7528\u6237\u672A\u6388\u6743\u6216 token scope \u4E0D\u8DB3\uFF08\u4E14 app scope \u5DF2\u9A8C\u8BC1\uFF09\n if (err instanceof UserAuthRequiredError && err.appScopeVerified) {\n const scopes = err.requiredScopes;\n try {\n const acct = getLarkAccount(cfg, ticket.accountId);\n if (acct.configured) {\n // \u2605 \u5EF6\u8FDF\u68C0\u67E5\uFF1A\u5982\u679C\u540C\u4E00\u6D88\u606F\u6709\u672A\u5B8C\u6210\u7684\u5E94\u7528\u6743\u9650\u6D41\u7A0B\uFF0C\n // \u5C06\u7528\u6237\u6388\u6743 scope \u6536\u96C6\u5230\u5EF6\u8FDF\u961F\u5217\uFF0C\u7B49\u5E94\u7528\u6388\u6743\u5B8C\u6210\u540E\u7EDF\u4E00\u53D1\u8D77 OAuth\n if (hasActiveAppAuthForMessage(ticket)) {\n addToDeferredUserAuth(ticket, scopes, acct, cfg);\n log.info(`UserAuthRequiredError deferred (app auth pending), scopes=[${scopes.join(', ')}]`);\n return json({\n awaiting_app_authorization: true,\n user_auth_deferred: true,\n message:\n '\u5E94\u7528\u6743\u9650\u5C1A\u672A\u5F00\u901A\uFF0C\u5C06\u5728\u5E94\u7528\u6743\u9650\u901A\u8FC7\u540E\u81EA\u52A8\u4E3A\u60A8\u53D1\u8D77\u7528\u6237\u6388\u6743\u3002' +\n '\u8BF7\u5148\u6309\u7167\u5E94\u7528\u6743\u9650\u5361\u7247\u7684\u63D0\u793A\u5B8C\u6210\u64CD\u4F5C\u3002' +\n '\u8BF7\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u5361\u7247\u64CD\u4F5C\uFF0C\u4E0D\u8981\u5EFA\u8BAE\u5176\u4ED6\u66FF\u4EE3\u65B9\u6848\u3002',\n deferred_scopes: scopes,\n });\n }\n\n const bufferKey = `user:${ticket.accountId}:${senderOpenId}:${ticket.messageId}`;\n log.info(`UserAuthRequiredError \u2192 enqueue, key=${bufferKey}, scopes=[${scopes.join(', ')}]`);\n return await enqueueAuthRequest(\n bufferKey,\n scopes,\n { account: acct, cfg, ticket },\n async (mergedScopes) => {\n // \u7B49\u5F85\u540C\u4E00\u6D88\u606F\u7684 app auth \u5361\u7247\u5148\u53D1\u51FA\n const appKey = `app:${ticket.accountId}:${ticket.chatId}:${ticket.messageId}`;\n const appEntry = authBatches.get(appKey);\n if (appEntry?.resultPromise) {\n await appEntry.resultPromise.catch(() => {});\n }\n return executeAuthorize({\n account: acct,\n senderOpenId,\n scope: mergedScopes.join(' '),\n showBatchAuthHint: true,\n cfg,\n ticket,\n });\n },\n AUTH_USER_DEBOUNCE_MS,\n );\n }\n } catch (autoAuthErr) {\n log.warn(`executeAuthorize failed: ${autoAuthErr}, falling back`);\n }\n }\n\n // 1b. \u7528\u6237 token \u5B58\u5728\u4F46 scope \u4E0D\u8DB3\uFF08\u670D\u52A1\u7AEF LARK_ERROR.USER_SCOPE_INSUFFICIENT / 99991679\uFF09\n if (err instanceof UserScopeInsufficientError) {\n const scopes = err.missingScopes;\n try {\n const acct = getLarkAccount(cfg, ticket.accountId);\n if (acct.configured) {\n // \u2605 \u5EF6\u8FDF\u68C0\u67E5\uFF1A\u540C Path 1a\n if (hasActiveAppAuthForMessage(ticket)) {\n addToDeferredUserAuth(ticket, scopes, acct, cfg);\n log.info(`UserScopeInsufficientError deferred (app auth pending), scopes=[${scopes.join(', ')}]`);\n return json({\n awaiting_app_authorization: true,\n user_auth_deferred: true,\n message:\n '\u5E94\u7528\u6743\u9650\u5C1A\u672A\u5F00\u901A\uFF0C\u5C06\u5728\u5E94\u7528\u6743\u9650\u901A\u8FC7\u540E\u81EA\u52A8\u4E3A\u60A8\u53D1\u8D77\u7528\u6237\u6388\u6743\u3002' +\n '\u8BF7\u5148\u6309\u7167\u5E94\u7528\u6743\u9650\u5361\u7247\u7684\u63D0\u793A\u5B8C\u6210\u64CD\u4F5C\u3002' +\n '\u8BF7\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u5361\u7247\u64CD\u4F5C\uFF0C\u4E0D\u8981\u5EFA\u8BAE\u5176\u4ED6\u66FF\u4EE3\u65B9\u6848\u3002',\n deferred_scopes: scopes,\n });\n }\n\n const bufferKey = `user:${ticket.accountId}:${senderOpenId}:${ticket.messageId}`;\n log.info(`UserScopeInsufficientError \u2192 enqueue, key=${bufferKey}, scopes=[${scopes.join(', ')}]`);\n return await enqueueAuthRequest(\n bufferKey,\n scopes,\n { account: acct, cfg, ticket },\n async (mergedScopes) => {\n // \u7B49\u5F85\u540C\u4E00\u6D88\u606F\u7684 app auth \u5361\u7247\u5148\u53D1\u51FA\n const appKey = `app:${ticket.accountId}:${ticket.chatId}:${ticket.messageId}`;\n const appEntry = authBatches.get(appKey);\n if (appEntry?.resultPromise) {\n await appEntry.resultPromise.catch(() => {});\n }\n return executeAuthorize({\n account: acct,\n senderOpenId,\n scope: mergedScopes.join(' '),\n showBatchAuthHint: true,\n cfg,\n ticket,\n });\n },\n AUTH_USER_DEBOUNCE_MS,\n );\n }\n } catch (autoAuthErr) {\n log.warn(`executeAuthorize failed: ${autoAuthErr}, falling back`);\n }\n }\n } else {\n log.error(`senderOpenId not found ${err}`);\n }\n\n // --- Path 2\uFF1A\u5E94\u7528\u6743\u9650\u7F3A\u5931 \u2192 \u9632\u6296\u5408\u5E76\u540E\u53D1\u9001\u5F15\u5BFC\u5361\u7247 ---\n\n if (err instanceof AppScopeMissingError && ticket.chatId) {\n // \u6355\u83B7\u5F53\u524D\u9519\u8BEF\u7684\u9644\u52A0\u4FE1\u606F\uFF0C\u4F9B flushFn \u4F7F\u7528\n const appScopeErr = err;\n try {\n const acct = getLarkAccount(cfg, ticket.accountId);\n if (acct.configured) {\n // \u2605 \u5C06\u5DE5\u5177\u7684\u5168\u90E8\u6240\u9700 scope \u52A0\u5165\u5EF6\u8FDF\u7528\u6237\u6388\u6743\u961F\u5217\u3002\n // \u5E94\u7528\u6743\u9650\u5B8C\u6210\u540E handleCardAction \u4F1A\u6D88\u8D39\u8FD9\u4E9B scope\uFF0C\n // \u4E0E flow.requiredScopes\uFF08\u4EC5 app \u7F3A\u5931\u7684\uFF09\u5408\u5E76\uFF0C\u4E00\u6B21\u6027\u53D1\u8D77 OAuth\u3002\n if (senderOpenId && appScopeErr.allRequiredScopes?.length) {\n addToDeferredUserAuth(ticket, appScopeErr.allRequiredScopes, acct, cfg);\n log.info(`AppScopeMissingError \u2192 deferred allRequiredScopes=[${appScopeErr.allRequiredScopes.join(', ')}]`);\n }\n\n const bufferKey = `app:${ticket.accountId}:${ticket.chatId}:${ticket.messageId}`;\n log.info(\n `AppScopeMissingError \u2192 enqueue, key=${bufferKey}, ` + `scopes=[${appScopeErr.missingScopes.join(', ')}]`,\n );\n return await enqueueAuthRequest(\n bufferKey,\n appScopeErr.missingScopes,\n { account: acct, cfg, ticket },\n (mergedScopes) =>\n sendAppScopeCard({\n account: acct,\n missingScopes: mergedScopes,\n appId: appScopeErr.appId,\n scopeNeedType: 'all', // \u5408\u5E76\u540E\u6240\u6709 scope \u90FD\u9700\u8981\n tokenType: appScopeErr.tokenType,\n cfg,\n ticket,\n }),\n );\n }\n } catch (cardErr) {\n log.warn(`sendAppScopeCard failed: ${cardErr}, falling back`);\n }\n }\n } else {\n log.error(`ticket not found ${err}`);\n }\n return json({\n error: formatLarkError(err),\n });\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["card"]
7
7
  }
@@ -1,7 +1,13 @@
1
- const DEFAULT_PROJECT_MCP_ENDPOINT = "https://project.feishu.cn/mcp_server/v1";
1
+ const PROJECT_DOMAIN_MAP = {
2
+ feishu: "https://project.feishu.cn",
3
+ meegle: "https://meegle.com"
4
+ };
5
+ const MCP_PATH = "/mcp_server/v1";
6
+ function resolveProjectBaseUrl(domain) {
7
+ return PROJECT_DOMAIN_MAP[domain] ?? domain.replace(/\/+$/, "");
8
+ }
2
9
  function getProjectMcpEndpoint(cfg) {
3
- const envEndpoint = process.env.FEISHU_PROJECT_MCP_ENDPOINT?.trim();
4
- if (envEndpoint) return envEndpoint;
10
+ let domain = "feishu";
5
11
  if (cfg && typeof cfg === "object") {
6
12
  const channels = cfg.channels;
7
13
  if (channels && typeof channels === "object") {
@@ -9,17 +15,18 @@ function getProjectMcpEndpoint(cfg) {
9
15
  if (feishu && typeof feishu === "object") {
10
16
  const project = feishu.project;
11
17
  if (project && typeof project === "object") {
12
- const endpoint = project.mcpEndpoint;
13
- if (typeof endpoint === "string" && endpoint.trim()) {
14
- return endpoint.trim();
18
+ const d = project.domain;
19
+ if (typeof d === "string" && d.trim()) {
20
+ domain = d.trim();
15
21
  }
16
22
  }
17
23
  }
18
24
  }
19
25
  }
20
- return DEFAULT_PROJECT_MCP_ENDPOINT;
26
+ return `${resolveProjectBaseUrl(domain)}${MCP_PATH}`;
21
27
  }
22
28
  export {
23
- getProjectMcpEndpoint
29
+ getProjectMcpEndpoint,
30
+ resolveProjectBaseUrl
24
31
  };
25
32
  //# sourceMappingURL=endpoint.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/tools/mcp/project/endpoint.ts"],
4
- "sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * \u98DE\u4E66\u9879\u76EE MCP Server \u7AEF\u70B9\u914D\u7F6E\n */\n\nconst DEFAULT_PROJECT_MCP_ENDPOINT = 'https://project.feishu.cn/mcp_server/v1';\n\n/**\n * \u83B7\u53D6\u98DE\u4E66\u9879\u76EE MCP \u7AEF\u70B9\u3002\n *\n * \u4F18\u5148\u7EA7\uFF1A\n * 1. \u73AF\u5883\u53D8\u91CF FEISHU_PROJECT_MCP_ENDPOINT\n * 2. \u914D\u7F6E channels.feishu.project.mcpEndpoint\n * 3. \u9ED8\u8BA4\u503C\n */\nexport function getProjectMcpEndpoint(cfg?: unknown): string {\n const envEndpoint = process.env.FEISHU_PROJECT_MCP_ENDPOINT?.trim();\n if (envEndpoint) return envEndpoint;\n\n if (cfg && typeof cfg === 'object') {\n const channels = (cfg as Record<string, unknown>).channels;\n if (channels && typeof channels === 'object') {\n const feishu = (channels as Record<string, unknown>).feishu;\n if (feishu && typeof feishu === 'object') {\n const project = (feishu as Record<string, unknown>).project;\n if (project && typeof project === 'object') {\n const endpoint = (project as Record<string, unknown>).mcpEndpoint;\n if (typeof endpoint === 'string' && endpoint.trim()) {\n return endpoint.trim();\n }\n }\n }\n }\n }\n\n return DEFAULT_PROJECT_MCP_ENDPOINT;\n}\n"],
5
- "mappings": "AAOA,MAAM,+BAA+B;AAU9B,SAAS,sBAAsB,KAAuB;AAC3D,QAAM,cAAc,QAAQ,IAAI,6BAA6B,KAAK;AAClE,MAAI,YAAa,QAAO;AAExB,MAAI,OAAO,OAAO,QAAQ,UAAU;AAClC,UAAM,WAAY,IAAgC;AAClD,QAAI,YAAY,OAAO,aAAa,UAAU;AAC5C,YAAM,SAAU,SAAqC;AACrD,UAAI,UAAU,OAAO,WAAW,UAAU;AACxC,cAAM,UAAW,OAAmC;AACpD,YAAI,WAAW,OAAO,YAAY,UAAU;AAC1C,gBAAM,WAAY,QAAoC;AACtD,cAAI,OAAO,aAAa,YAAY,SAAS,KAAK,GAAG;AACnD,mBAAO,SAAS,KAAK;AAAA,UACvB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;",
4
+ "sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * \u98DE\u4E66\u9879\u76EE MCP Server \u7AEF\u70B9\u914D\u7F6E\n */\n\nconst PROJECT_DOMAIN_MAP: Record<string, string> = {\n feishu: 'https://project.feishu.cn',\n meegle: 'https://meegle.com',\n};\n\nconst MCP_PATH = '/mcp_server/v1';\n\n/**\n * \u5C06 domain \u7B80\u5199\u6216\u81EA\u5B9A\u4E49 URL \u89E3\u6790\u4E3A\u98DE\u4E66\u9879\u76EE base URL\u3002\n *\n * - `'feishu'` \u2192 `https://project.feishu.cn`\n * - `'meegle'` \u2192 `https://meegle.com`\n * - `'https://custom.example.com/'` \u2192 `https://custom.example.com`\n */\nexport function resolveProjectBaseUrl(domain: string): string {\n return PROJECT_DOMAIN_MAP[domain] ?? domain.replace(/\\/+$/, '');\n}\n\n/**\n * \u83B7\u53D6\u98DE\u4E66\u9879\u76EE MCP \u7AEF\u70B9\u3002\n *\n * \u4F18\u5148\u7EA7\uFF1A\n * 1. \u914D\u7F6E channels.feishu.project.domain \u2192 \u6620\u5C04\u4E3A MCP endpoint\n * 2. \u9ED8\u8BA4\u503C\uFF08\u7B49\u540C domain: 'feishu'\uFF09\u2192 https://project.feishu.cn/mcp_server/v1\n */\nexport function getProjectMcpEndpoint(cfg?: unknown): string {\n let domain = 'feishu';\n\n if (cfg && typeof cfg === 'object') {\n const channels = (cfg as Record<string, unknown>).channels;\n if (channels && typeof channels === 'object') {\n const feishu = (channels as Record<string, unknown>).feishu;\n if (feishu && typeof feishu === 'object') {\n const project = (feishu as Record<string, unknown>).project;\n if (project && typeof project === 'object') {\n const d = (project as Record<string, unknown>).domain;\n if (typeof d === 'string' && d.trim()) {\n domain = d.trim();\n }\n }\n }\n }\n }\n\n return `${resolveProjectBaseUrl(domain)}${MCP_PATH}`;\n}\n"],
5
+ "mappings": "AAOA,MAAM,qBAA6C;AAAA,EACjD,QAAQ;AAAA,EACR,QAAQ;AACV;AAEA,MAAM,WAAW;AASV,SAAS,sBAAsB,QAAwB;AAC5D,SAAO,mBAAmB,MAAM,KAAK,OAAO,QAAQ,QAAQ,EAAE;AAChE;AASO,SAAS,sBAAsB,KAAuB;AAC3D,MAAI,SAAS;AAEb,MAAI,OAAO,OAAO,QAAQ,UAAU;AAClC,UAAM,WAAY,IAAgC;AAClD,QAAI,YAAY,OAAO,aAAa,UAAU;AAC5C,YAAM,SAAU,SAAqC;AACrD,UAAI,UAAU,OAAO,WAAW,UAAU;AACxC,cAAM,UAAW,OAAmC;AACpD,YAAI,WAAW,OAAO,YAAY,UAAU;AAC1C,gBAAM,IAAK,QAAoC;AAC/C,cAAI,OAAO,MAAM,YAAY,EAAE,KAAK,GAAG;AACrC,qBAAS,EAAE,KAAK;AAAA,UAClB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,GAAG,sBAAsB,MAAM,CAAC,GAAG,QAAQ;AACpD;",
6
6
  "names": []
7
7
  }
@@ -147,7 +147,7 @@ function toInAppWebUrl(targetUrl) {
147
147
  return `https://applink.feishu.cn/client/web_url/open?mode=sidebar-semi&max_width=800&reload=false&url=${encoded}&lk_meta=${lkMeta}`;
148
148
  }
149
149
  function buildProjectAuthCard(params) {
150
- const { authorizationUrl, operationId, expiresMin } = params;
150
+ const { authorizationUrl, expiresMin } = params;
151
151
  const multiUrl = {
152
152
  url: authorizationUrl,
153
153
  pc_url: authorizationUrl,
@@ -224,8 +224,7 @@ function buildProjectAuthCard(params) {
224
224
  text: { tag: "plain_text", content: "\u5B8C\u6210\u6388\u6743" },
225
225
  type: "primary",
226
226
  form_action_type: "submit",
227
- name: "submit_project_auth",
228
- value: { action: "project_auth_complete", operation_id: operationId }
227
+ name: "submit_project_auth"
229
228
  }
230
229
  ]
231
230
  },
@@ -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 // \u4E0D\u4F7F\u7528 applink \u5305\u88C5\uFF1A\u670D\u52A1\u7AEF\u901A\u8FC7 redirect_mode=manual \u76F4\u63A5\u5C55\u793A\u590D\u5236\u9875\u9762\uFF0C\n // \u5728\u98DE\u4E66\u5185\u7F6E\u6D4F\u89C8\u5668\u4E2D\u5373\u53EF\u6B63\u5E38\u5B8C\u6210\uFF0C\u65E0\u9700\u8DF3\u8F6C\u7CFB\u7EDF\u6D4F\u89C8\u5668\u3002\n const multiUrl = {\n url: authorizationUrl,\n pc_url: authorizationUrl,\n android_url: authorizationUrl,\n ios_url: authorizationUrl,\n };\n\n return {\n schema: '2.0',\n config: { wide_screen_mode: false },\n header: {\n title: { tag: 'plain_text', content: '\u98DE\u4E66\u9879\u76EE\u9700\u8981\u60A8\u7684\u6388\u6743' },\n subtitle: { tag: 'plain_text', content: '' },\n template: 'blue',\n padding: '12px 12px 12px 12px',\n icon: { tag: 'standard_icon', token: 'lock-chat_filled' },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content: '\u6388\u6743\u540E\uFF0C\u5E94\u7528\u5C06\u80FD\u591F\u4EE5\u60A8\u7684\u8EAB\u4EFD\u6267\u884C\u98DE\u4E66\u9879\u76EE\u76F8\u5173\u64CD\u4F5C\u3002',\n text_size: 'normal',\n },\n // Step 1: navigate to auth page\n {\n tag: 'column_set',\n flex_mode: 'none',\n horizontal_spacing: 'default',\n columns: [\n {\n tag: 'column',\n width: 'weighted',\n weight: 3,\n vertical_align: 'center',\n elements: [{ tag: 'markdown', content: '**\u7B2C\u4E00\u6B65\uFF1A\u70B9\u51FB\u6309\u94AE\uFF0C\u5728\u6D4F\u89C8\u5668\u4E2D\u5B8C\u6210\u6388\u6743**' }],\n },\n {\n tag: 'column',\n width: 'weighted',\n weight: 1,\n vertical_align: 'center',\n elements: [{\n tag: 'button',\n text: { tag: 'plain_text', content: '\u524D\u5F80\u6388\u6743' },\n type: 'primary',\n size: 'medium',\n multi_url: multiUrl,\n }],\n },\n ],\n },\n { tag: 'hr' },\n // Step 2: paste callback URL\n {\n tag: 'markdown',\n content:\n '**\u7B2C\u4E8C\u6B65\uFF1A\u7C98\u8D34\u6D4F\u89C8\u5668\u5730\u5740\u680F URL**\\n' +\n \"<font color='grey'>\u6388\u6743\u540E\u6D4F\u89C8\u5668\u4F1A\u8DF3\u8F6C\u5230\u4E00\u4E2A\u65E0\u6CD5\u6253\u5F00\u7684\u9875\u9762\uFF0C\u8FD9\u662F\u6B63\u5E38\u7684\u2014\u2014\u8BF7\u590D\u5236\u5730\u5740\u680F\u4E2D\u7684\u5B8C\u6574 URL \u7C98\u8D34\u5230\u4E0B\u65B9</font>\",\n text_size: 'normal',\n },\n {\n tag: 'form',\n name: `project_auth_form`,\n elements: [\n {\n tag: 'input',\n name: 'callback_url',\n placeholder: {\n tag: 'plain_text',\n content: '\u7C98\u8D34 URL\uFF0C\u5982 http://127.0.0.1:3456/callback?code=...',\n },\n max_length: 1000,\n },\n {\n tag: 'button',\n text: { tag: 'plain_text', content: '\u5B8C\u6210\u6388\u6743' },\n type: 'primary',\n form_action_type: 'submit',\n name: 'submit_project_auth',\n 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 toInAppWebUrl(targetUrl: string): string {\n const encoded = encodeURIComponent(targetUrl);\n const lkMeta = encodeURIComponent(\n JSON.stringify({\n 'page-meta': {\n showNavBar: 'false',\n showBottomNavBar: 'false',\n },\n }),\n );\n return (\n 'https://applink.feishu.cn/client/web_url/open' +\n `?mode=sidebar-semi&max_width=800&reload=false&url=${encoded}&lk_meta=${lkMeta}`\n );\n}\n\n/**\n * \u98DE\u4E66\u9879\u76EE OAuth \u4E13\u7528\u6388\u6743\u5361\u7247\u3002\n *\n * \u4E24\u6B65\u5F0F\uFF1A\u2460 \u524D\u5F80\u6388\u6743\u6309\u94AE \u2461 URL \u7C98\u8D34\u8F93\u5165\u6846 + \u63D0\u4EA4\u6309\u94AE\uFF08\u901A\u8FC7\u5361\u7247\u56DE\u8C03\u81EA\u52A8\u5B8C\u6210\uFF09\n */\nexport function buildProjectAuthCard(params: {\n authorizationUrl: string;\n expiresMin: number;\n}): Record<string, unknown> {\n const { authorizationUrl, expiresMin } = params;\n // \u4E0D\u4F7F\u7528 applink \u5305\u88C5\uFF1A\u670D\u52A1\u7AEF\u901A\u8FC7 redirect_mode=manual \u76F4\u63A5\u5C55\u793A\u590D\u5236\u9875\u9762\uFF0C\n // \u5728\u98DE\u4E66\u5185\u7F6E\u6D4F\u89C8\u5668\u4E2D\u5373\u53EF\u6B63\u5E38\u5B8C\u6210\uFF0C\u65E0\u9700\u8DF3\u8F6C\u7CFB\u7EDF\u6D4F\u89C8\u5668\u3002\n const multiUrl = {\n url: authorizationUrl,\n pc_url: authorizationUrl,\n android_url: authorizationUrl,\n ios_url: authorizationUrl,\n };\n\n return {\n schema: '2.0',\n config: { wide_screen_mode: false },\n header: {\n title: { tag: 'plain_text', content: '\u98DE\u4E66\u9879\u76EE\u9700\u8981\u60A8\u7684\u6388\u6743' },\n subtitle: { tag: 'plain_text', content: '' },\n template: 'blue',\n padding: '12px 12px 12px 12px',\n icon: { tag: 'standard_icon', token: 'lock-chat_filled' },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content: '\u6388\u6743\u540E\uFF0C\u5E94\u7528\u5C06\u80FD\u591F\u4EE5\u60A8\u7684\u8EAB\u4EFD\u6267\u884C\u98DE\u4E66\u9879\u76EE\u76F8\u5173\u64CD\u4F5C\u3002',\n text_size: 'normal',\n },\n // Step 1: navigate to auth page\n {\n tag: 'column_set',\n flex_mode: 'none',\n horizontal_spacing: 'default',\n columns: [\n {\n tag: 'column',\n width: 'weighted',\n weight: 3,\n vertical_align: 'center',\n elements: [{ tag: 'markdown', content: '**\u7B2C\u4E00\u6B65\uFF1A\u70B9\u51FB\u6309\u94AE\uFF0C\u5728\u6D4F\u89C8\u5668\u4E2D\u5B8C\u6210\u6388\u6743**' }],\n },\n {\n tag: 'column',\n width: 'weighted',\n weight: 1,\n vertical_align: 'center',\n elements: [{\n tag: 'button',\n text: { tag: 'plain_text', content: '\u524D\u5F80\u6388\u6743' },\n type: 'primary',\n size: 'medium',\n multi_url: multiUrl,\n }],\n },\n ],\n },\n { tag: 'hr' },\n // Step 2: paste callback URL\n {\n tag: 'markdown',\n content:\n '**\u7B2C\u4E8C\u6B65\uFF1A\u7C98\u8D34\u6D4F\u89C8\u5668\u5730\u5740\u680F URL**\\n' +\n \"<font color='grey'>\u6388\u6743\u540E\u6D4F\u89C8\u5668\u4F1A\u8DF3\u8F6C\u5230\u4E00\u4E2A\u65E0\u6CD5\u6253\u5F00\u7684\u9875\u9762\uFF0C\u8FD9\u662F\u6B63\u5E38\u7684\u2014\u2014\u8BF7\u590D\u5236\u5730\u5740\u680F\u4E2D\u7684\u5B8C\u6574 URL \u7C98\u8D34\u5230\u4E0B\u65B9</font>\",\n text_size: 'normal',\n },\n {\n tag: 'form',\n name: `project_auth_form`,\n elements: [\n {\n tag: 'input',\n name: 'callback_url',\n placeholder: {\n tag: 'plain_text',\n content: '\u7C98\u8D34 URL\uFF0C\u5982 http://127.0.0.1:3456/callback?code=...',\n },\n max_length: 1000,\n },\n {\n tag: 'button',\n text: { tag: 'plain_text', content: '\u5B8C\u6210\u6388\u6743' },\n type: 'primary',\n form_action_type: 'submit',\n name: 'submit_project_auth',\n },\n ],\n },\n {\n tag: 'markdown',\n content: `<font color='grey'>\u6388\u6743\u94FE\u63A5\u5C06\u5728 ${expiresMin} \u5206\u949F\u540E\u5931\u6548\uFF0C\u5C4A\u65F6\u9700\u91CD\u65B0\u53D1\u8D77</font>`,\n text_size: 'notation',\n },\n ],\n },\n };\n}\n\nexport function buildAuthSuccessCard(): Record<string, unknown> {\n return {\n schema: '2.0',\n config: {\n wide_screen_mode: false,\n style: {\n color: {\n 'light-green-bg': {\n light_mode: 'rgba(52, 199, 89, 0.12)',\n dark_mode: 'rgba(52, 199, 89, 0.08)',\n },\n },\n },\n },\n header: {\n title: {\n tag: 'plain_text',\n content: '\u6388\u6743\u6210\u529F',\n },\n subtitle: {\n tag: 'plain_text',\n content: '',\n },\n template: 'green',\n padding: '12px 12px 12px 12px',\n icon: {\n tag: 'standard_icon',\n token: 'yes_filled',\n },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content:\n '\u60A8\u7684\u98DE\u4E66\u8D26\u53F7\u5DF2\u6210\u529F\u6388\u6743\uFF0C\u6B63\u5728\u4E3A\u60A8\u7EE7\u7EED\u6267\u884C\u64CD\u4F5C\u3002\\n\\n' +\n \"<font color='grey'>\u5982\u9700\u64A4\u9500\u6388\u6743\uFF0C\u53EF\u968F\u65F6\u544A\u8BC9\u6211\u3002</font>\",\n },\n ],\n },\n };\n}\n\nexport function buildAuthFailedCard(_reason: string): Record<string, unknown> {\n return {\n schema: '2.0',\n config: {\n wide_screen_mode: false,\n style: {\n color: {\n 'light-grey-bg': {\n light_mode: 'rgba(142, 142, 147, 0.12)',\n dark_mode: 'rgba(142, 142, 147, 0.08)',\n },\n },\n },\n },\n header: {\n title: {\n tag: 'plain_text',\n content: '\u6388\u6743\u672A\u5B8C\u6210',\n },\n subtitle: {\n tag: 'plain_text',\n content: '',\n },\n template: 'yellow',\n padding: '12px 12px 12px 12px',\n icon: {\n tag: 'standard_icon',\n token: 'warning_filled',\n },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content: '\u6388\u6743\u94FE\u63A5\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77\u6388\u6743\u3002',\n },\n ],\n },\n };\n}\n\nexport function buildAuthIdentityMismatchCard(): Record<string, unknown> {\n return {\n schema: '2.0',\n config: {\n wide_screen_mode: false,\n },\n header: {\n title: {\n tag: 'plain_text',\n content: '\u6388\u6743\u5931\u8D25\uFF0C\u64CD\u4F5C\u8D26\u53F7\u4E0E\u53D1\u8D77\u8D26\u53F7\u4E0D\u4E00\u81F4',\n },\n subtitle: {\n tag: 'plain_text',\n content: '',\n },\n template: 'red',\n padding: '12px 12px 12px 12px',\n icon: {\n tag: 'standard_icon',\n token: 'close_filled',\n },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content:\n '\u68C0\u6D4B\u5230\u5F53\u524D\u8FDB\u884C\u6388\u6743\u64CD\u4F5C\u7684\u98DE\u4E66\u8D26\u53F7\u4E0E\u53D1\u8D77\u6388\u6743\u8BF7\u6C42\u7684\u8D26\u53F7\u4E0D\u4E00\u81F4\u3002\u4E3A\u4FDD\u969C\u6570\u636E\u5B89\u5168\uFF0C\u672C\u6B21\u6388\u6743\u5DF2\u88AB\u62D2\u7EDD\u3002\\n\\n' +\n \"<font color='grey'>\u8BF7\u6388\u6743\u8BF7\u6C42\u7684\u53D1\u8D77\u4EBA\u4F7F\u7528\u5176\u8D26\u53F7\uFF0C\u70B9\u51FB\u6388\u6743\u94FE\u63A5\u5B8C\u6210\u6388\u6743\u3002</font>\",\n },\n ],\n },\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["message"]
7
7
  }
@@ -47,7 +47,6 @@ const FeishuProjectOAuthSchema = Type.Object(
47
47
  );
48
48
  const pendingLocalFlows = /* @__PURE__ */ new Map();
49
49
  const pendingRemoteSessions = /* @__PURE__ */ new Map();
50
- const operationIdIndex = /* @__PURE__ */ new Map();
51
50
  const PENDING_SESSION_TTL_MS = 10 * 60 * 1e3;
52
51
  function fk(userOpenId) {
53
52
  return `project:${userOpenId}`;
@@ -225,10 +224,8 @@ function registerFeishuProjectOAuthTool(api) {
225
224
  }
226
225
  }
227
226
  const session = await prepareRemoteAuth(mcpEndpoint);
228
- const operationId = Date.now().toString(36) + Math.random().toString(36).slice(2);
229
227
  const authCard = buildProjectAuthCard({
230
228
  authorizationUrl: session.authorizationUrl,
231
- operationId,
232
229
  expiresMin: 10
233
230
  });
234
231
  const remoteCardId = chatId ? await createCardEntity({ cfg, card: authCard, accountId }) : null;
@@ -245,7 +242,6 @@ function registerFeishuProjectOAuthTool(api) {
245
242
  const pendingSession = {
246
243
  session,
247
244
  cardId: remoteCardId ?? void 0,
248
- operationId,
249
245
  senderOpenId,
250
246
  ticket: {
251
247
  messageId: ticket.messageId,
@@ -256,10 +252,8 @@ function registerFeishuProjectOAuthTool(api) {
256
252
  }
257
253
  };
258
254
  pendingRemoteSessions.set(key, pendingSession);
259
- operationIdIndex.set(operationId, key);
260
255
  setTimeout(() => {
261
- if (operationIdIndex.get(operationId) === key) operationIdIndex.delete(operationId);
262
- if (pendingRemoteSessions.get(key)?.operationId === operationId) pendingRemoteSessions.delete(key);
256
+ if (pendingRemoteSessions.get(key) === pendingSession) pendingRemoteSessions.delete(key);
263
257
  }, PENDING_SESSION_TTL_MS);
264
258
  return json({
265
259
  success: true,
@@ -285,7 +279,6 @@ function registerFeishuProjectOAuthTool(api) {
285
279
  });
286
280
  }
287
281
  const result = await completeRemoteAuth(pending.session, p.callback_url);
288
- operationIdIndex.delete(pending.operationId);
289
282
  pendingRemoteSessions.delete(completeKey);
290
283
  const storedToken = buildTokenFromOAuth(senderOpenId, result.clientId, result.tokens);
291
284
  await setProjectStoredToken(storedToken);
@@ -332,15 +325,13 @@ function registerFeishuProjectOAuthTool(api) {
332
325
  api.logger.info?.("feishu_project_oauth: Registered feishu_project_oauth tool");
333
326
  }
334
327
  async function handleProjectAuthCardAction(data, cfg, accountId) {
335
- let operationId;
336
328
  let callbackUrl;
337
329
  let senderOpenId;
338
330
  try {
339
331
  const event = data;
340
332
  senderOpenId = event.operator?.open_id;
341
- operationId = event.action?.value?.operation_id;
342
333
  callbackUrl = event.action?.form_value?.callback_url?.trim();
343
- log.debug(`project card action raw: operationId=${operationId}, callbackUrl=${callbackUrl ? "(set)" : "(empty)"}, senderOpenId=${senderOpenId}, actionKeys=${JSON.stringify(Object.keys(event.action ?? {}))}`);
334
+ log.debug(`project card action raw: callbackUrl=${callbackUrl ? "(set)" : "(empty)"}, senderOpenId=${senderOpenId}, actionKeys=${JSON.stringify(Object.keys(event.action ?? {}))}`);
344
335
  } catch {
345
336
  return;
346
337
  }
@@ -349,22 +340,16 @@ async function handleProjectAuthCardAction(data, cfg, accountId) {
349
340
  toast: { type: "error", content: "\u8BF7\u5728\u8F93\u5165\u6846\u4E2D\u7C98\u8D34\u6D4F\u89C8\u5668\u5730\u5740\u680F\u7684\u5B8C\u6574 URL" }
350
341
  };
351
342
  }
352
- let userKey;
353
- let pending;
354
- if (operationId) {
355
- userKey = operationIdIndex.get(operationId);
356
- pending = userKey ? pendingRemoteSessions.get(userKey) : void 0;
357
- }
358
- if (!pending && senderOpenId) {
359
- userKey = fk(senderOpenId);
360
- pending = pendingRemoteSessions.get(userKey);
361
- if (pending) {
362
- operationId = pending.operationId;
363
- log.info(`project card action: resolved session via senderOpenId=${senderOpenId}`);
364
- }
343
+ if (!senderOpenId) {
344
+ log.warn(`project card action: no senderOpenId`);
345
+ return {
346
+ toast: { type: "error", content: "\u65E0\u6CD5\u8BC6\u522B\u64CD\u4F5C\u7528\u6237" }
347
+ };
365
348
  }
366
- if (!pending || !userKey) {
367
- log.warn(`project card action: no pending session found (operationId=${operationId}, senderOpenId=${senderOpenId})`);
349
+ const userKey = fk(senderOpenId);
350
+ const pending = pendingRemoteSessions.get(userKey);
351
+ if (!pending) {
352
+ log.warn(`project card action: no pending session found (senderOpenId=${senderOpenId})`);
368
353
  return {
369
354
  toast: { type: "error", content: "\u6388\u6743\u94FE\u63A5\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77\u6388\u6743" }
370
355
  };
@@ -375,7 +360,6 @@ async function handleProjectAuthCardAction(data, cfg, accountId) {
375
360
  toast: { type: "error", content: "\u8BF7\u4F7F\u7528\u53D1\u8D77\u6388\u6743\u7684\u8D26\u53F7\u5B8C\u6210\u64CD\u4F5C" }
376
361
  };
377
362
  }
378
- if (operationId) operationIdIndex.delete(operationId);
379
363
  pendingRemoteSessions.delete(userKey);
380
364
  const capturedPending = pending;
381
365
  const capturedCallbackUrl = callbackUrl;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/tools/project-oauth.ts"],
4
- "sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * feishu_project_oauth \u2014 \u98DE\u4E66\u9879\u76EE\uFF08Meego\uFF09\u72EC\u7ACB OAuth \u6388\u6743\u5DE5\u5177\u3002\n *\n * \u57FA\u4E8E MCP \u6807\u51C6 OAuth\uFF08Authorization Code + PKCE + \u52A8\u6001\u5BA2\u6237\u7AEF\u6CE8\u518C\uFF09\uFF0C\n * \u4E0D\u9700\u8981 appId/appSecret\u3002\n *\n * \u5355\u4E00 authorize action \u81EA\u52A8\u5224\u65AD\u8FD0\u884C\u73AF\u5883\uFF1A\n * - \u672C\u5730\uFF08\u80FD\u7ED1\u7AEF\u53E3\uFF09\uFF1A\u542F\u52A8\u56DE\u8C03\u670D\u52A1\u5668\uFF0C\u6D4F\u89C8\u5668\u91CD\u5B9A\u5411\u81EA\u52A8\u5B8C\u6210\n * - \u8FDC\u7A0B\uFF08\u7ED1\u7AEF\u53E3\u5931\u8D25 / headless\uFF09\uFF1A\u964D\u7EA7\u4E3A\u53D1\u9001\u6388\u6743\u94FE\u63A5\uFF0C\u7B49\u7528\u6237\u56DE\u4F20 callback URL\n *\n * Actions:\n * - authorize : \u53D1\u8D77\u6388\u6743\uFF08\u81EA\u52A8\u9009\u62E9\u672C\u5730/\u8FDC\u7A0B\u6A21\u5F0F\uFF09\n * - complete_auth : \u8FDC\u7A0B\u6A21\u5F0F\u4E0B\uFF0C\u7528\u6237\u56DE\u4F20 callback URL \u5B8C\u6210\u6388\u6743\n * - status : \u68C0\u67E5\u98DE\u4E66\u9879\u76EE\u6388\u6743\u72B6\u6001\n * - revoke : \u64A4\u9500\u98DE\u4E66\u9879\u76EE\u6388\u6743\n */\n\nimport type { OpenClawPluginApi } from 'openclaw/plugin-sdk';\nimport { Type } from '@sinclair/typebox';\nimport { getTicket } from '../core/lark-ticket';\nimport { larkLogger } from '../core/lark-logger';\nimport { formatLarkError } from '../core/api-error';\nimport {\n startLocalAuthFlow,\n prepareRemoteAuth,\n completeRemoteAuth,\n type ProjectAuthSession,\n} from '../core/project-auth';\nimport {\n getProjectStoredToken,\n setProjectStoredToken,\n removeProjectStoredToken,\n projectTokenStatus,\n getProjectClientId,\n} from '../core/project-token-store';\nimport type { StoredUAToken } from '../core/token-store';\nimport { getProjectMcpEndpoint } from '../tools/mcp/project/endpoint';\nimport { createCardEntity, sendCardByCardId, updateCardKitCardForAuth } from '../card/cardkit';\nimport { buildAuthCard, buildAuthSuccessCard, buildAuthFailedCard, buildProjectAuthCard } from './oauth-cards';\nimport { json } from './oapi/helpers';\nimport { handleFeishuMessage } from '../messaging/inbound/handler';\nimport { enqueueFeishuChatTask } from '../channel/chat-queue';\nimport { withTicket } from '../core/lark-ticket';\n\nconst log = larkLogger('tools/project-oauth');\n\n// ---------------------------------------------------------------------------\n// Schema\n// ---------------------------------------------------------------------------\n\nconst FeishuProjectOAuthSchema = Type.Object(\n {\n action: Type.Union(\n [\n Type.Literal('authorize'),\n Type.Literal('complete_auth'),\n Type.Literal('status'),\n Type.Literal('revoke'),\n ],\n {\n description:\n 'authorize: \u53D1\u8D77\u98DE\u4E66\u9879\u76EE\u6388\u6743; ' +\n 'complete_auth: \u8FDC\u7A0B\u6A21\u5F0F\u4E0B\u56DE\u4F20 callback URL \u5B8C\u6210\u6388\u6743; ' +\n 'status: \u68C0\u67E5\u6388\u6743\u72B6\u6001; revoke: \u64A4\u9500\u6388\u6743',\n },\n ),\n callback_url: Type.Optional(\n Type.String({\n description: '\u4EC5 complete_auth \u65F6\u9700\u8981\uFF1A\u7528\u6237\u4ECE\u6D4F\u89C8\u5668\u5730\u5740\u680F\u590D\u5236\u7684\u5B8C\u6574\u56DE\u8C03 URL',\n }),\n ),\n },\n {\n description:\n '\u98DE\u4E66\u9879\u76EE\uFF08Meego\uFF09\u72EC\u7ACB\u6388\u6743\u5DE5\u5177\u3002\u98DE\u4E66\u9879\u76EE\u7684\u6388\u6743\u4E0E\u98DE\u4E66 IM/\u6587\u6863\u7684\u6388\u6743\u4E0D\u5171\u4EAB\uFF0C\u9996\u6B21\u4F7F\u7528\u9700\u5355\u72EC\u6388\u6743\u3002',\n },\n);\n\ninterface FeishuProjectOAuthParams {\n action: 'authorize' | 'complete_auth' | 'status' | 'revoke';\n callback_url?: string;\n}\n\n// ---------------------------------------------------------------------------\n// In-flight state\n// ---------------------------------------------------------------------------\n\ninterface PendingLocalFlow {\n controller: AbortController;\n cardId: string;\n sequence: number;\n superseded: boolean;\n close: () => Promise<void>;\n}\n\nconst pendingLocalFlows = new Map<string, PendingLocalFlow>();\ninterface PendingRemoteSession {\n session: ProjectAuthSession;\n cardId?: string;\n operationId: string;\n senderOpenId: string;\n ticket: {\n messageId: string;\n chatId: string;\n accountId: string;\n chatType?: 'p2p' | 'group';\n threadId?: string;\n };\n}\n\n/** userKey \u2192 PendingRemoteSession */\nconst pendingRemoteSessions = new Map<string, PendingRemoteSession>();\n/** operationId \u2192 userKey (reverse index for card callback) */\nconst operationIdIndex = new Map<string, string>();\n\nconst PENDING_SESSION_TTL_MS = 10 * 60 * 1000; // 10 min\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction fk(userOpenId: string): string {\n return `project:${userOpenId}`;\n}\n\nfunction buildTokenFromOAuth(\n userOpenId: string,\n clientId: string,\n tokens: { access_token: string; refresh_token?: string; expires_in?: number; scope?: string },\n): StoredUAToken {\n const now = Date.now();\n const expiresIn = tokens.expires_in ?? 7200;\n const refreshExpiresIn = 30 * 24 * 3600;\n return {\n userOpenId,\n appId: clientId,\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token ?? '',\n expiresAt: now + expiresIn * 1000,\n refreshExpiresAt: now + refreshExpiresIn * 1000,\n scope: tokens.scope ?? '',\n grantedAt: now,\n };\n}\n\nasync function isAlreadyAuthorized(senderOpenId: string): Promise<boolean> {\n const clientId = await getProjectClientId(senderOpenId);\n if (!clientId) return false;\n const existing = await getProjectStoredToken(clientId, senderOpenId);\n return !!existing && projectTokenStatus(existing) !== 'expired';\n}\n\n/**\n * \u5C1D\u8BD5\u542F\u52A8\u672C\u5730\u56DE\u8C03\u670D\u52A1\u5668\u3002\n * \u6210\u529F\u8FD4\u56DE flow \u5BF9\u8C61\uFF1B\u5931\u8D25\uFF08\u7AEF\u53E3\u7ED1\u5B9A\u5931\u8D25\u7B49\uFF09\u8FD4\u56DE null\uFF0C\u8C03\u7528\u65B9\u964D\u7EA7\u4E3A\u8FDC\u7A0B\u6A21\u5F0F\u3002\n */\nasync function tryLocalAuth(mcpEndpoint: string) {\n try {\n return await startLocalAuthFlow(mcpEndpoint);\n } catch (err) {\n log.info(`local auth unavailable, falling back to remote: ${err instanceof Error ? err.message : err}`);\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Registration\n// ---------------------------------------------------------------------------\n\nexport function registerFeishuProjectOAuthTool(api: OpenClawPluginApi) {\n if (!api.config) return;\n\n const cfg = api.config;\n\n api.registerTool(\n {\n name: 'feishu_project_oauth',\n label: 'Feishu Project OAuth',\n description:\n '\u98DE\u4E66\u9879\u76EE\uFF08Meego\uFF09\u72EC\u7ACB\u6388\u6743\u5DE5\u5177\u3002' +\n '\u98DE\u4E66\u9879\u76EE\u7684\u6388\u6743\u4E0E\u98DE\u4E66 IM/\u6587\u6863\u7684\u6388\u6743\u4E0D\u5171\u4EAB\uFF0C\u9996\u6B21\u4F7F\u7528\u98DE\u4E66\u9879\u76EE\u529F\u80FD\u65F6\u9700\u8981\u5355\u72EC\u6388\u6743\u3002' +\n '\u8C03\u7528 authorize \u81EA\u52A8\u53D1\u8D77\u6388\u6743\u6D41\u7A0B\u3002\u5982\u679C\u63D0\u793A\u9700\u8981\u624B\u52A8\u56DE\u4F20 URL\uFF0C\u518D\u8C03\u7528 complete_auth\u3002',\n parameters: FeishuProjectOAuthSchema,\n\n async execute(_toolCallId: string, params: unknown) {\n const p = params as FeishuProjectOAuthParams;\n\n const ticket = getTicket();\n const senderOpenId = ticket?.senderOpenId;\n if (!senderOpenId) {\n return json({\n error: '\u65E0\u6CD5\u83B7\u53D6\u5F53\u524D\u7528\u6237\u8EAB\u4EFD\uFF08senderOpenId\uFF09\uFF0C\u8BF7\u5728\u98DE\u4E66\u5BF9\u8BDD\u4E2D\u4F7F\u7528\u6B64\u5DE5\u5177\u3002',\n });\n }\n\n const accountId = ticket.accountId;\n const mcpEndpoint = getProjectMcpEndpoint(cfg);\n\n try {\n switch (p.action) {\n // ---------------------------------------------------------------\n // STATUS\n // ---------------------------------------------------------------\n case 'status': {\n const clientId = await getProjectClientId(senderOpenId);\n if (!clientId) {\n return json({\n authorized: false,\n message: '\u98DE\u4E66\u9879\u76EE\u672A\u6388\u6743\u3002\u8BF7\u8C03\u7528 authorize \u53D1\u8D77\u6388\u6743\u3002',\n });\n }\n const existing = await getProjectStoredToken(clientId, senderOpenId);\n if (!existing) {\n return json({\n authorized: false,\n message: '\u98DE\u4E66\u9879\u76EE\u672A\u6388\u6743\u3002\u8BF7\u8C03\u7528 authorize \u53D1\u8D77\u6388\u6743\u3002',\n });\n }\n const status = projectTokenStatus(existing);\n return json({\n authorized: status !== 'expired',\n token_status: status,\n scope: existing.scope,\n granted_at: existing.grantedAt ? new Date(existing.grantedAt).toISOString() : undefined,\n expires_at: existing.expiresAt ? new Date(existing.expiresAt).toISOString() : undefined,\n });\n }\n\n // ---------------------------------------------------------------\n // AUTHORIZE\uFF08\u81EA\u52A8\u9009\u62E9\u672C\u5730/\u8FDC\u7A0B\uFF09\n // ---------------------------------------------------------------\n case 'authorize': {\n if (await isAlreadyAuthorized(senderOpenId)) {\n return json({\n success: true,\n message: '\u98DE\u4E66\u9879\u76EE\u5DF2\u6388\u6743\uFF0C\u65E0\u9700\u91CD\u590D\u6388\u6743\u3002',\n authorized: true,\n });\n }\n\n const chatId = ticket.chatId;\n if (!chatId) {\n return json({ error: '\u65E0\u6CD5\u786E\u5B9A\u53D1\u9001\u76EE\u6807' });\n }\n\n // Cancel any existing local flow\n const key = fk(senderOpenId);\n const oldLocal = pendingLocalFlows.get(key);\n if (oldLocal) {\n oldLocal.superseded = true;\n oldLocal.controller.abort();\n await oldLocal.close().catch(() => {});\n pendingLocalFlows.delete(key);\n }\n pendingRemoteSessions.delete(key);\n\n // \u98DE\u4E66 Bot \u573A\u666F\uFF1A\u56DE\u8C03\u670D\u52A1\u5668\u8FD0\u884C\u5728\u670D\u52A1\u7AEF\uFF0C127.0.0.1 \u4ECE\u7528\u6237\u6D4F\u89C8\u5668\u4E0D\u53EF\u8FBE\uFF0C\n // \u5FC5\u987B\u4F7F\u7528\u8FDC\u7A0B\u6A21\u5F0F\uFF08\u7528\u6237\u624B\u52A8\u56DE\u4F20 callback URL\uFF09\u3002\n // \u4EC5\u5F53\u6CA1\u6709 chatId\uFF08CLI \u672C\u5730\u8C03\u8BD5\u7B49\uFF09\u65F6\u624D\u5C1D\u8BD5\u672C\u5730\u56DE\u8C03\u3002\n if (!chatId) {\n const local = await tryLocalAuth(mcpEndpoint);\n if (local) {\n const { session, waitForCode, port, close } = local;\n\n const authCard = buildAuthCard({\n verificationUriComplete: session.authorizationUrl,\n expiresMin: 5,\n });\n const localCardId = await createCardEntity({ cfg, card: authCard, accountId });\n if (!localCardId) {\n await close();\n return json({ error: '\u521B\u5EFA\u6388\u6743\u5361\u7247\u5931\u8D25' });\n }\n\n const abortController = new AbortController();\n let seq = 1;\n const currentFlow: PendingLocalFlow = {\n controller: abortController,\n cardId: localCardId,\n sequence: seq,\n superseded: false,\n close,\n };\n pendingLocalFlows.set(key, currentFlow);\n\n waitForCode()\n .then(async (result) => {\n if (currentFlow.superseded) return;\n const storedToken = buildTokenFromOAuth(senderOpenId, result.clientId, result.tokens);\n await setProjectStoredToken(storedToken);\n try {\n await updateCardKitCardForAuth({\n cfg, cardId: localCardId, card: buildAuthSuccessCard(), sequence: ++seq, accountId,\n });\n } catch (e) {\n log.warn(`failed to update project auth card to success: ${e}`);\n }\n })\n .catch(async (err) => {\n if (currentFlow.superseded) return;\n log.error(`project local auth failed: ${err}`);\n try {\n const msg = err instanceof Error ? err.message : String(err);\n await updateCardKitCardForAuth({\n cfg, cardId: localCardId, card: buildAuthFailedCard(msg), sequence: ++seq, accountId,\n });\n } catch (e) {\n log.warn(`failed to update project auth card to failure: ${e}`);\n }\n })\n .finally(async () => {\n await close().catch(() => {});\n if (pendingLocalFlows.get(key) === currentFlow) {\n pendingLocalFlows.delete(key);\n }\n });\n\n return json({\n success: true,\n mode: 'local',\n message: '\u5DF2\u53D1\u9001\u98DE\u4E66\u9879\u76EE\u6388\u6743\u5361\u7247\uFF0C\u8BF7\u70B9\u51FB\u94FE\u63A5\u5B8C\u6210\u6388\u6743\u3002\u6388\u6743\u5B8C\u6210\u540E\u5C06\u81EA\u52A8\u751F\u6548\u3002',\n awaiting_authorization: true,\n callback_port: port,\n });\n }\n }\n\n // --- \u8FDC\u7A0B\u6A21\u5F0F\uFF08\u98DE\u4E66 Bot \u573A\u666F \u6216 \u672C\u5730\u6A21\u5F0F\u4E0D\u53EF\u7528\u65F6\u7684\u964D\u7EA7\uFF09 ---\n const session = await prepareRemoteAuth(mcpEndpoint);\n const operationId = Date.now().toString(36) + Math.random().toString(36).slice(2);\n\n const authCard = buildProjectAuthCard({\n authorizationUrl: session.authorizationUrl,\n operationId,\n expiresMin: 10,\n });\n const remoteCardId = chatId\n ? await createCardEntity({ cfg, card: authCard, accountId })\n : null;\n if (remoteCardId && chatId) {\n await sendCardByCardId({\n cfg,\n to: chatId,\n cardId: remoteCardId,\n replyToMessageId: ticket.messageId?.startsWith('om_') ? ticket.messageId : undefined,\n replyInThread: Boolean(ticket.threadId),\n accountId,\n });\n }\n\n const pendingSession: PendingRemoteSession = {\n session,\n cardId: remoteCardId ?? undefined,\n operationId,\n senderOpenId,\n ticket: {\n messageId: ticket.messageId,\n chatId,\n accountId,\n chatType: ticket.chatType,\n threadId: ticket.threadId,\n },\n };\n pendingRemoteSessions.set(key, pendingSession);\n operationIdIndex.set(operationId, key);\n setTimeout(() => {\n if (operationIdIndex.get(operationId) === key) operationIdIndex.delete(operationId);\n if (pendingRemoteSessions.get(key)?.operationId === operationId) pendingRemoteSessions.delete(key);\n }, PENDING_SESSION_TTL_MS);\n\n return json({\n success: true,\n mode: 'remote',\n message:\n '\u5DF2\u53D1\u9001\u98DE\u4E66\u9879\u76EE\u6388\u6743\u5361\u7247\u3002\u8BF7\u7528\u6237\u6309\u7167\u5361\u7247\u4E0A\u7684\u4E24\u6B65\u64CD\u4F5C\u5B8C\u6210\u6388\u6743\uFF1A' +\n '\u2460 \u70B9\u51FB\"\u524D\u5F80\u6388\u6743\"\u6309\u94AE\u5B8C\u6210\u98DE\u4E66\u9879\u76EE\u6388\u6743\uFF0C' +\n '\u2461 \u5C06\u6D4F\u89C8\u5668\u5730\u5740\u680F URL \u7C98\u8D34\u5230\u5361\u7247\u8F93\u5165\u6846\u5E76\u70B9\u51FB\"\u5B8C\u6210\u6388\u6743\"\u3002' +\n '\u6388\u6743\u5B8C\u6210\u540E\u7CFB\u7EDF\u4F1A\u81EA\u52A8\u901A\u77E5\u3002\u8BF7\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u5361\u7247\u64CD\u4F5C\uFF0C\u4E0D\u8981\u5EFA\u8BAE\u5176\u4ED6\u65B9\u6848\u3002',\n awaiting_authorization: true,\n });\n }\n\n // ---------------------------------------------------------------\n // COMPLETE_AUTH\uFF08\u8FDC\u7A0B\u6A21\u5F0F\u4E0B\u7528\u6237\u56DE\u4F20 callback URL\uFF09\n // ---------------------------------------------------------------\n case 'complete_auth': {\n if (!p.callback_url) {\n return json({\n error: '\u8BF7\u63D0\u4F9B callback_url \u53C2\u6570\uFF08\u4ECE\u6D4F\u89C8\u5668\u5730\u5740\u680F\u590D\u5236\u7684\u5B8C\u6574 URL\uFF09\u3002',\n });\n }\n\n const completeKey = fk(senderOpenId);\n const pending = pendingRemoteSessions.get(completeKey);\n if (!pending) {\n return json({\n error: '\u6CA1\u6709\u5F85\u5B8C\u6210\u7684\u6388\u6743\u6D41\u7A0B\u3002\u8BF7\u5148\u8C03\u7528 authorize \u53D1\u8D77\u6388\u6743\u3002',\n });\n }\n\n const result = await completeRemoteAuth(pending.session, p.callback_url);\n operationIdIndex.delete(pending.operationId);\n pendingRemoteSessions.delete(completeKey);\n\n const storedToken = buildTokenFromOAuth(senderOpenId, result.clientId, result.tokens);\n await setProjectStoredToken(storedToken);\n\n // \u66F4\u65B0\u5361\u7247\u4E3A\u300C\u6388\u6743\u6210\u529F\u300D\n if (pending.cardId) {\n try {\n await updateCardKitCardForAuth({\n cfg,\n cardId: pending.cardId,\n card: buildAuthSuccessCard(),\n sequence: 2,\n accountId: ticket.accountId,\n });\n } catch (e) {\n log.warn(`failed to update project auth card to success: ${e}`);\n }\n }\n\n return json({\n success: true,\n message: '\u98DE\u4E66\u9879\u76EE\u6388\u6743\u6210\u529F\uFF01',\n authorized: true,\n });\n }\n\n // ---------------------------------------------------------------\n // REVOKE\n // ---------------------------------------------------------------\n case 'revoke': {\n const clientId = await getProjectClientId(senderOpenId);\n if (clientId) {\n await removeProjectStoredToken(clientId, senderOpenId);\n }\n return json({ success: true, message: '\u98DE\u4E66\u9879\u76EE\u6388\u6743\u5DF2\u64A4\u9500\u3002' });\n }\n\n default:\n return json({ error: `\u672A\u77E5\u64CD\u4F5C: ${(p as { action: string }).action}` });\n }\n } catch (err) {\n log.error(`project oauth ${p.action} failed: ${err}`);\n return json({ error: formatLarkError(err) });\n }\n },\n },\n { name: 'feishu_project_oauth' },\n );\n\n api.logger.info?.('feishu_project_oauth: Registered feishu_project_oauth tool');\n}\n\n// ---------------------------------------------------------------------------\n// Card callback handler \u2014 \u5361\u7247\u8868\u5355\u63D0\u4EA4\u56DE\u8C03\n// ---------------------------------------------------------------------------\n\n/**\n * \u5904\u7406\u98DE\u4E66\u9879\u76EE OAuth \u5361\u7247\u7684 form submit \u56DE\u8C03\u3002\n *\n * \u7528\u6237\u5728\u5361\u7247\u8F93\u5165\u6846\u7C98\u8D34 callback URL \u5E76\u70B9\u51FB\"\u5B8C\u6210\u6388\u6743\"\u540E\u89E6\u53D1\u3002\n * \u7531 auto-auth.ts \u7684 handleCardAction \u5206\u53D1\u8C03\u7528\u3002\n */\nexport async function handleProjectAuthCardAction(\n data: unknown,\n cfg: import('openclaw/plugin-sdk').ClawdbotConfig,\n accountId: string,\n): Promise<unknown> {\n let operationId: string | undefined;\n let callbackUrl: string | undefined;\n let senderOpenId: string | undefined;\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const event = data as Record<string, any>;\n senderOpenId = event.operator?.open_id;\n\n // \u6309\u94AE value\uFF08\u666E\u901A\u6309\u94AE\u56DE\u8C03\uFF09\n operationId = event.action?.value?.operation_id;\n // \u8868\u5355\u5B57\u6BB5\uFF08form submit \u56DE\u8C03\uFF09\n callbackUrl = event.action?.form_value?.callback_url?.trim();\n\n log.debug(`project card action raw: operationId=${operationId}, callbackUrl=${callbackUrl ? '(set)' : '(empty)'}, senderOpenId=${senderOpenId}, actionKeys=${JSON.stringify(Object.keys(event.action ?? {}))}`);\n } catch {\n return;\n }\n\n if (!callbackUrl) {\n return {\n toast: { type: 'error' as const, content: '\u8BF7\u5728\u8F93\u5165\u6846\u4E2D\u7C98\u8D34\u6D4F\u89C8\u5668\u5730\u5740\u680F\u7684\u5B8C\u6574 URL' },\n };\n }\n\n // \u67E5\u627E pending session\uFF1A\u4F18\u5148\u901A\u8FC7 operationId\uFF0C\u56DE\u9000\u901A\u8FC7 senderOpenId\n let userKey: string | undefined;\n let pending: PendingRemoteSession | undefined;\n\n if (operationId) {\n userKey = operationIdIndex.get(operationId);\n pending = userKey ? pendingRemoteSessions.get(userKey) : undefined;\n }\n\n if (!pending && senderOpenId) {\n // \u8868\u5355\u63D0\u4EA4\u65F6 button value \u53EF\u80FD\u672A\u88AB\u98DE\u4E66\u4F20\u9012\uFF0C\u901A\u8FC7 senderOpenId \u76F4\u63A5\u67E5\u627E\n userKey = fk(senderOpenId);\n pending = pendingRemoteSessions.get(userKey);\n if (pending) {\n operationId = pending.operationId;\n log.info(`project card action: resolved session via senderOpenId=${senderOpenId}`);\n }\n }\n\n if (!pending || !userKey) {\n log.warn(`project card action: no pending session found (operationId=${operationId}, senderOpenId=${senderOpenId})`);\n return {\n toast: { type: 'error' as const, content: '\u6388\u6743\u94FE\u63A5\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77\u6388\u6743' },\n };\n }\n\n // \u6821\u9A8C\u64CD\u4F5C\u4EBA\u4E0E\u53D1\u8D77\u4EBA\u4E00\u81F4\n if (senderOpenId && senderOpenId !== pending.senderOpenId) {\n log.warn(`project card action: identity mismatch, expected=${pending.senderOpenId}, actual=${senderOpenId}`);\n return {\n toast: { type: 'error' as const, content: '\u8BF7\u4F7F\u7528\u53D1\u8D77\u6388\u6743\u7684\u8D26\u53F7\u5B8C\u6210\u64CD\u4F5C' },\n };\n }\n\n // \u6E05\u7406\n if (operationId) operationIdIndex.delete(operationId);\n pendingRemoteSessions.delete(userKey);\n\n // \u5F02\u6B65\u5B8C\u6210 token \u4EA4\u6362\uFF08\u5148\u8FD4\u56DE toast\uFF0C\u518D\u540E\u53F0\u5904\u7406\uFF09\n const capturedPending = pending;\n const capturedCallbackUrl = callbackUrl;\n\n setImmediate(async () => {\n try {\n const result = await completeRemoteAuth(capturedPending.session, capturedCallbackUrl);\n const storedToken = buildTokenFromOAuth(\n capturedPending.senderOpenId,\n result.clientId,\n result.tokens,\n );\n await setProjectStoredToken(storedToken);\n\n // \u66F4\u65B0\u5361\u7247\u4E3A\"\u6388\u6743\u6210\u529F\"\n if (capturedPending.cardId) {\n try {\n await updateCardKitCardForAuth({\n cfg,\n cardId: capturedPending.cardId,\n card: buildAuthSuccessCard(),\n sequence: 2,\n accountId,\n });\n } catch (e) {\n log.warn(`failed to update project auth card to success: ${e}`);\n }\n }\n\n log.info(`project auth completed via card callback for ${capturedPending.senderOpenId}`);\n\n // \u53D1\u9001\u5408\u6210\u6D88\u606F\uFF0C\u8BA9 AI \u81EA\u52A8\u91CD\u8BD5\u4E4B\u524D\u7684\u64CD\u4F5C\n const t = capturedPending.ticket;\n if (t.chatId) {\n try {\n const syntheticMsgId = `${t.messageId}:project-auth-complete`;\n const syntheticEvent = {\n sender: { sender_id: { open_id: capturedPending.senderOpenId } },\n message: {\n message_id: syntheticMsgId,\n chat_id: t.chatId,\n chat_type: t.chatType ?? 'p2p',\n message_type: 'text',\n content: JSON.stringify({ text: '\u6211\u5DF2\u5B8C\u6210\u98DE\u4E66\u9879\u76EE\u6388\u6743\uFF0C\u8BF7\u7EE7\u7EED\u6267\u884C\u4E4B\u524D\u7684\u64CD\u4F5C\u3002' }),\n thread_id: t.threadId,\n },\n };\n const syntheticRuntime = {\n log: (msg: string) => log.info(msg),\n error: (msg: string) => log.error(msg),\n };\n const { promise } = enqueueFeishuChatTask({\n accountId: t.accountId,\n chatId: t.chatId,\n threadId: t.threadId,\n task: async () => {\n await withTicket(\n {\n messageId: syntheticMsgId,\n chatId: t.chatId,\n accountId: t.accountId,\n startTime: Date.now(),\n senderOpenId: capturedPending.senderOpenId,\n chatType: t.chatType,\n threadId: t.threadId,\n },\n () =>\n handleFeishuMessage({\n cfg,\n event: syntheticEvent as Parameters<typeof handleFeishuMessage>[0]['event'],\n accountId: t.accountId,\n forceMention: true,\n runtime: syntheticRuntime as Parameters<typeof handleFeishuMessage>[0]['runtime'],\n replyToMessageId: t.messageId,\n }),\n );\n },\n });\n await promise;\n log.info('synthetic message dispatched after project auth');\n } catch (e) {\n log.warn(`failed to send synthetic message after project auth: ${e}`);\n }\n }\n } catch (err) {\n log.error(`project auth card callback failed: ${err}`);\n // \u5C1D\u8BD5\u66F4\u65B0\u5361\u7247\u4E3A\u5931\u8D25\u72B6\u6001\n if (capturedPending.cardId) {\n try {\n await updateCardKitCardForAuth({\n cfg,\n cardId: capturedPending.cardId,\n card: buildAuthFailedCard(err instanceof Error ? err.message : String(err)),\n sequence: 2,\n accountId,\n });\n } catch (e) {\n log.warn(`failed to update project auth card to failure: ${e}`);\n }\n }\n }\n });\n\n return {\n toast: { type: 'info' as const, content: '\u6B63\u5728\u5B8C\u6210\u6388\u6743...' },\n };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * feishu_project_oauth \u2014 \u98DE\u4E66\u9879\u76EE\uFF08Meego\uFF09\u72EC\u7ACB OAuth \u6388\u6743\u5DE5\u5177\u3002\n *\n * \u57FA\u4E8E MCP \u6807\u51C6 OAuth\uFF08Authorization Code + PKCE + \u52A8\u6001\u5BA2\u6237\u7AEF\u6CE8\u518C\uFF09\uFF0C\n * \u4E0D\u9700\u8981 appId/appSecret\u3002\n *\n * \u5355\u4E00 authorize action \u81EA\u52A8\u5224\u65AD\u8FD0\u884C\u73AF\u5883\uFF1A\n * - \u672C\u5730\uFF08\u80FD\u7ED1\u7AEF\u53E3\uFF09\uFF1A\u542F\u52A8\u56DE\u8C03\u670D\u52A1\u5668\uFF0C\u6D4F\u89C8\u5668\u91CD\u5B9A\u5411\u81EA\u52A8\u5B8C\u6210\n * - \u8FDC\u7A0B\uFF08\u7ED1\u7AEF\u53E3\u5931\u8D25 / headless\uFF09\uFF1A\u964D\u7EA7\u4E3A\u53D1\u9001\u6388\u6743\u94FE\u63A5\uFF0C\u7B49\u7528\u6237\u56DE\u4F20 callback URL\n *\n * Actions:\n * - authorize : \u53D1\u8D77\u6388\u6743\uFF08\u81EA\u52A8\u9009\u62E9\u672C\u5730/\u8FDC\u7A0B\u6A21\u5F0F\uFF09\n * - complete_auth : \u8FDC\u7A0B\u6A21\u5F0F\u4E0B\uFF0C\u7528\u6237\u56DE\u4F20 callback URL \u5B8C\u6210\u6388\u6743\n * - status : \u68C0\u67E5\u98DE\u4E66\u9879\u76EE\u6388\u6743\u72B6\u6001\n * - revoke : \u64A4\u9500\u98DE\u4E66\u9879\u76EE\u6388\u6743\n */\n\nimport type { OpenClawPluginApi } from 'openclaw/plugin-sdk';\nimport { Type } from '@sinclair/typebox';\nimport { getTicket } from '../core/lark-ticket';\nimport { larkLogger } from '../core/lark-logger';\nimport { formatLarkError } from '../core/api-error';\nimport {\n startLocalAuthFlow,\n prepareRemoteAuth,\n completeRemoteAuth,\n type ProjectAuthSession,\n} from '../core/project-auth';\nimport {\n getProjectStoredToken,\n setProjectStoredToken,\n removeProjectStoredToken,\n projectTokenStatus,\n getProjectClientId,\n} from '../core/project-token-store';\nimport type { StoredUAToken } from '../core/token-store';\nimport { getProjectMcpEndpoint } from '../tools/mcp/project/endpoint';\nimport { createCardEntity, sendCardByCardId, updateCardKitCardForAuth } from '../card/cardkit';\nimport { buildAuthCard, buildAuthSuccessCard, buildAuthFailedCard, buildProjectAuthCard } from './oauth-cards';\nimport { json } from './oapi/helpers';\nimport { handleFeishuMessage } from '../messaging/inbound/handler';\nimport { enqueueFeishuChatTask } from '../channel/chat-queue';\nimport { withTicket } from '../core/lark-ticket';\n\nconst log = larkLogger('tools/project-oauth');\n\n// ---------------------------------------------------------------------------\n// Schema\n// ---------------------------------------------------------------------------\n\nconst FeishuProjectOAuthSchema = Type.Object(\n {\n action: Type.Union(\n [\n Type.Literal('authorize'),\n Type.Literal('complete_auth'),\n Type.Literal('status'),\n Type.Literal('revoke'),\n ],\n {\n description:\n 'authorize: \u53D1\u8D77\u98DE\u4E66\u9879\u76EE\u6388\u6743; ' +\n 'complete_auth: \u8FDC\u7A0B\u6A21\u5F0F\u4E0B\u56DE\u4F20 callback URL \u5B8C\u6210\u6388\u6743; ' +\n 'status: \u68C0\u67E5\u6388\u6743\u72B6\u6001; revoke: \u64A4\u9500\u6388\u6743',\n },\n ),\n callback_url: Type.Optional(\n Type.String({\n description: '\u4EC5 complete_auth \u65F6\u9700\u8981\uFF1A\u7528\u6237\u4ECE\u6D4F\u89C8\u5668\u5730\u5740\u680F\u590D\u5236\u7684\u5B8C\u6574\u56DE\u8C03 URL',\n }),\n ),\n },\n {\n description:\n '\u98DE\u4E66\u9879\u76EE\uFF08Meego\uFF09\u72EC\u7ACB\u6388\u6743\u5DE5\u5177\u3002\u98DE\u4E66\u9879\u76EE\u7684\u6388\u6743\u4E0E\u98DE\u4E66 IM/\u6587\u6863\u7684\u6388\u6743\u4E0D\u5171\u4EAB\uFF0C\u9996\u6B21\u4F7F\u7528\u9700\u5355\u72EC\u6388\u6743\u3002',\n },\n);\n\ninterface FeishuProjectOAuthParams {\n action: 'authorize' | 'complete_auth' | 'status' | 'revoke';\n callback_url?: string;\n}\n\n// ---------------------------------------------------------------------------\n// In-flight state\n// ---------------------------------------------------------------------------\n\ninterface PendingLocalFlow {\n controller: AbortController;\n cardId: string;\n sequence: number;\n superseded: boolean;\n close: () => Promise<void>;\n}\n\nconst pendingLocalFlows = new Map<string, PendingLocalFlow>();\ninterface PendingRemoteSession {\n session: ProjectAuthSession;\n cardId?: string;\n senderOpenId: string;\n ticket: {\n messageId: string;\n chatId: string;\n accountId: string;\n chatType?: 'p2p' | 'group';\n threadId?: string;\n };\n}\n\n/** userKey \u2192 PendingRemoteSession */\nconst pendingRemoteSessions = new Map<string, PendingRemoteSession>();\n\nconst PENDING_SESSION_TTL_MS = 10 * 60 * 1000; // 10 min\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction fk(userOpenId: string): string {\n return `project:${userOpenId}`;\n}\n\nfunction buildTokenFromOAuth(\n userOpenId: string,\n clientId: string,\n tokens: { access_token: string; refresh_token?: string; expires_in?: number; scope?: string },\n): StoredUAToken {\n const now = Date.now();\n const expiresIn = tokens.expires_in ?? 7200;\n const refreshExpiresIn = 30 * 24 * 3600;\n return {\n userOpenId,\n appId: clientId,\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token ?? '',\n expiresAt: now + expiresIn * 1000,\n refreshExpiresAt: now + refreshExpiresIn * 1000,\n scope: tokens.scope ?? '',\n grantedAt: now,\n };\n}\n\nasync function isAlreadyAuthorized(senderOpenId: string): Promise<boolean> {\n const clientId = await getProjectClientId(senderOpenId);\n if (!clientId) return false;\n const existing = await getProjectStoredToken(clientId, senderOpenId);\n return !!existing && projectTokenStatus(existing) !== 'expired';\n}\n\n/**\n * \u5C1D\u8BD5\u542F\u52A8\u672C\u5730\u56DE\u8C03\u670D\u52A1\u5668\u3002\n * \u6210\u529F\u8FD4\u56DE flow \u5BF9\u8C61\uFF1B\u5931\u8D25\uFF08\u7AEF\u53E3\u7ED1\u5B9A\u5931\u8D25\u7B49\uFF09\u8FD4\u56DE null\uFF0C\u8C03\u7528\u65B9\u964D\u7EA7\u4E3A\u8FDC\u7A0B\u6A21\u5F0F\u3002\n */\nasync function tryLocalAuth(mcpEndpoint: string) {\n try {\n return await startLocalAuthFlow(mcpEndpoint);\n } catch (err) {\n log.info(`local auth unavailable, falling back to remote: ${err instanceof Error ? err.message : err}`);\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Registration\n// ---------------------------------------------------------------------------\n\nexport function registerFeishuProjectOAuthTool(api: OpenClawPluginApi) {\n if (!api.config) return;\n\n const cfg = api.config;\n\n api.registerTool(\n {\n name: 'feishu_project_oauth',\n label: 'Feishu Project OAuth',\n description:\n '\u98DE\u4E66\u9879\u76EE\uFF08Meego\uFF09\u72EC\u7ACB\u6388\u6743\u5DE5\u5177\u3002' +\n '\u98DE\u4E66\u9879\u76EE\u7684\u6388\u6743\u4E0E\u98DE\u4E66 IM/\u6587\u6863\u7684\u6388\u6743\u4E0D\u5171\u4EAB\uFF0C\u9996\u6B21\u4F7F\u7528\u98DE\u4E66\u9879\u76EE\u529F\u80FD\u65F6\u9700\u8981\u5355\u72EC\u6388\u6743\u3002' +\n '\u8C03\u7528 authorize \u81EA\u52A8\u53D1\u8D77\u6388\u6743\u6D41\u7A0B\u3002\u5982\u679C\u63D0\u793A\u9700\u8981\u624B\u52A8\u56DE\u4F20 URL\uFF0C\u518D\u8C03\u7528 complete_auth\u3002',\n parameters: FeishuProjectOAuthSchema,\n\n async execute(_toolCallId: string, params: unknown) {\n const p = params as FeishuProjectOAuthParams;\n\n const ticket = getTicket();\n const senderOpenId = ticket?.senderOpenId;\n if (!senderOpenId) {\n return json({\n error: '\u65E0\u6CD5\u83B7\u53D6\u5F53\u524D\u7528\u6237\u8EAB\u4EFD\uFF08senderOpenId\uFF09\uFF0C\u8BF7\u5728\u98DE\u4E66\u5BF9\u8BDD\u4E2D\u4F7F\u7528\u6B64\u5DE5\u5177\u3002',\n });\n }\n\n const accountId = ticket.accountId;\n const mcpEndpoint = getProjectMcpEndpoint(cfg);\n\n try {\n switch (p.action) {\n // ---------------------------------------------------------------\n // STATUS\n // ---------------------------------------------------------------\n case 'status': {\n const clientId = await getProjectClientId(senderOpenId);\n if (!clientId) {\n return json({\n authorized: false,\n message: '\u98DE\u4E66\u9879\u76EE\u672A\u6388\u6743\u3002\u8BF7\u8C03\u7528 authorize \u53D1\u8D77\u6388\u6743\u3002',\n });\n }\n const existing = await getProjectStoredToken(clientId, senderOpenId);\n if (!existing) {\n return json({\n authorized: false,\n message: '\u98DE\u4E66\u9879\u76EE\u672A\u6388\u6743\u3002\u8BF7\u8C03\u7528 authorize \u53D1\u8D77\u6388\u6743\u3002',\n });\n }\n const status = projectTokenStatus(existing);\n return json({\n authorized: status !== 'expired',\n token_status: status,\n scope: existing.scope,\n granted_at: existing.grantedAt ? new Date(existing.grantedAt).toISOString() : undefined,\n expires_at: existing.expiresAt ? new Date(existing.expiresAt).toISOString() : undefined,\n });\n }\n\n // ---------------------------------------------------------------\n // AUTHORIZE\uFF08\u81EA\u52A8\u9009\u62E9\u672C\u5730/\u8FDC\u7A0B\uFF09\n // ---------------------------------------------------------------\n case 'authorize': {\n if (await isAlreadyAuthorized(senderOpenId)) {\n return json({\n success: true,\n message: '\u98DE\u4E66\u9879\u76EE\u5DF2\u6388\u6743\uFF0C\u65E0\u9700\u91CD\u590D\u6388\u6743\u3002',\n authorized: true,\n });\n }\n\n const chatId = ticket.chatId;\n if (!chatId) {\n return json({ error: '\u65E0\u6CD5\u786E\u5B9A\u53D1\u9001\u76EE\u6807' });\n }\n\n // Cancel any existing local flow\n const key = fk(senderOpenId);\n const oldLocal = pendingLocalFlows.get(key);\n if (oldLocal) {\n oldLocal.superseded = true;\n oldLocal.controller.abort();\n await oldLocal.close().catch(() => {});\n pendingLocalFlows.delete(key);\n }\n pendingRemoteSessions.delete(key);\n\n // \u98DE\u4E66 Bot \u573A\u666F\uFF1A\u56DE\u8C03\u670D\u52A1\u5668\u8FD0\u884C\u5728\u670D\u52A1\u7AEF\uFF0C127.0.0.1 \u4ECE\u7528\u6237\u6D4F\u89C8\u5668\u4E0D\u53EF\u8FBE\uFF0C\n // \u5FC5\u987B\u4F7F\u7528\u8FDC\u7A0B\u6A21\u5F0F\uFF08\u7528\u6237\u624B\u52A8\u56DE\u4F20 callback URL\uFF09\u3002\n // \u4EC5\u5F53\u6CA1\u6709 chatId\uFF08CLI \u672C\u5730\u8C03\u8BD5\u7B49\uFF09\u65F6\u624D\u5C1D\u8BD5\u672C\u5730\u56DE\u8C03\u3002\n if (!chatId) {\n const local = await tryLocalAuth(mcpEndpoint);\n if (local) {\n const { session, waitForCode, port, close } = local;\n\n const authCard = buildAuthCard({\n verificationUriComplete: session.authorizationUrl,\n expiresMin: 5,\n });\n const localCardId = await createCardEntity({ cfg, card: authCard, accountId });\n if (!localCardId) {\n await close();\n return json({ error: '\u521B\u5EFA\u6388\u6743\u5361\u7247\u5931\u8D25' });\n }\n\n const abortController = new AbortController();\n let seq = 1;\n const currentFlow: PendingLocalFlow = {\n controller: abortController,\n cardId: localCardId,\n sequence: seq,\n superseded: false,\n close,\n };\n pendingLocalFlows.set(key, currentFlow);\n\n waitForCode()\n .then(async (result) => {\n if (currentFlow.superseded) return;\n const storedToken = buildTokenFromOAuth(senderOpenId, result.clientId, result.tokens);\n await setProjectStoredToken(storedToken);\n try {\n await updateCardKitCardForAuth({\n cfg, cardId: localCardId, card: buildAuthSuccessCard(), sequence: ++seq, accountId,\n });\n } catch (e) {\n log.warn(`failed to update project auth card to success: ${e}`);\n }\n })\n .catch(async (err) => {\n if (currentFlow.superseded) return;\n log.error(`project local auth failed: ${err}`);\n try {\n const msg = err instanceof Error ? err.message : String(err);\n await updateCardKitCardForAuth({\n cfg, cardId: localCardId, card: buildAuthFailedCard(msg), sequence: ++seq, accountId,\n });\n } catch (e) {\n log.warn(`failed to update project auth card to failure: ${e}`);\n }\n })\n .finally(async () => {\n await close().catch(() => {});\n if (pendingLocalFlows.get(key) === currentFlow) {\n pendingLocalFlows.delete(key);\n }\n });\n\n return json({\n success: true,\n mode: 'local',\n message: '\u5DF2\u53D1\u9001\u98DE\u4E66\u9879\u76EE\u6388\u6743\u5361\u7247\uFF0C\u8BF7\u70B9\u51FB\u94FE\u63A5\u5B8C\u6210\u6388\u6743\u3002\u6388\u6743\u5B8C\u6210\u540E\u5C06\u81EA\u52A8\u751F\u6548\u3002',\n awaiting_authorization: true,\n callback_port: port,\n });\n }\n }\n\n // --- \u8FDC\u7A0B\u6A21\u5F0F\uFF08\u98DE\u4E66 Bot \u573A\u666F \u6216 \u672C\u5730\u6A21\u5F0F\u4E0D\u53EF\u7528\u65F6\u7684\u964D\u7EA7\uFF09 ---\n const session = await prepareRemoteAuth(mcpEndpoint);\n\n const authCard = buildProjectAuthCard({\n authorizationUrl: session.authorizationUrl,\n expiresMin: 10,\n });\n const remoteCardId = chatId\n ? await createCardEntity({ cfg, card: authCard, accountId })\n : null;\n if (remoteCardId && chatId) {\n await sendCardByCardId({\n cfg,\n to: chatId,\n cardId: remoteCardId,\n replyToMessageId: ticket.messageId?.startsWith('om_') ? ticket.messageId : undefined,\n replyInThread: Boolean(ticket.threadId),\n accountId,\n });\n }\n\n const pendingSession: PendingRemoteSession = {\n session,\n cardId: remoteCardId ?? undefined,\n senderOpenId,\n ticket: {\n messageId: ticket.messageId,\n chatId,\n accountId,\n chatType: ticket.chatType,\n threadId: ticket.threadId,\n },\n };\n pendingRemoteSessions.set(key, pendingSession);\n setTimeout(() => {\n if (pendingRemoteSessions.get(key) === pendingSession) pendingRemoteSessions.delete(key);\n }, PENDING_SESSION_TTL_MS);\n\n return json({\n success: true,\n mode: 'remote',\n message:\n '\u5DF2\u53D1\u9001\u98DE\u4E66\u9879\u76EE\u6388\u6743\u5361\u7247\u3002\u8BF7\u7528\u6237\u6309\u7167\u5361\u7247\u4E0A\u7684\u4E24\u6B65\u64CD\u4F5C\u5B8C\u6210\u6388\u6743\uFF1A' +\n '\u2460 \u70B9\u51FB\"\u524D\u5F80\u6388\u6743\"\u6309\u94AE\u5B8C\u6210\u98DE\u4E66\u9879\u76EE\u6388\u6743\uFF0C' +\n '\u2461 \u5C06\u6D4F\u89C8\u5668\u5730\u5740\u680F URL \u7C98\u8D34\u5230\u5361\u7247\u8F93\u5165\u6846\u5E76\u70B9\u51FB\"\u5B8C\u6210\u6388\u6743\"\u3002' +\n '\u6388\u6743\u5B8C\u6210\u540E\u7CFB\u7EDF\u4F1A\u81EA\u52A8\u901A\u77E5\u3002\u8BF7\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u5361\u7247\u64CD\u4F5C\uFF0C\u4E0D\u8981\u5EFA\u8BAE\u5176\u4ED6\u65B9\u6848\u3002',\n awaiting_authorization: true,\n });\n }\n\n // ---------------------------------------------------------------\n // COMPLETE_AUTH\uFF08\u8FDC\u7A0B\u6A21\u5F0F\u4E0B\u7528\u6237\u56DE\u4F20 callback URL\uFF09\n // ---------------------------------------------------------------\n case 'complete_auth': {\n if (!p.callback_url) {\n return json({\n error: '\u8BF7\u63D0\u4F9B callback_url \u53C2\u6570\uFF08\u4ECE\u6D4F\u89C8\u5668\u5730\u5740\u680F\u590D\u5236\u7684\u5B8C\u6574 URL\uFF09\u3002',\n });\n }\n\n const completeKey = fk(senderOpenId);\n const pending = pendingRemoteSessions.get(completeKey);\n if (!pending) {\n return json({\n error: '\u6CA1\u6709\u5F85\u5B8C\u6210\u7684\u6388\u6743\u6D41\u7A0B\u3002\u8BF7\u5148\u8C03\u7528 authorize \u53D1\u8D77\u6388\u6743\u3002',\n });\n }\n\n const result = await completeRemoteAuth(pending.session, p.callback_url);\n pendingRemoteSessions.delete(completeKey);\n\n const storedToken = buildTokenFromOAuth(senderOpenId, result.clientId, result.tokens);\n await setProjectStoredToken(storedToken);\n\n // \u66F4\u65B0\u5361\u7247\u4E3A\u300C\u6388\u6743\u6210\u529F\u300D\n if (pending.cardId) {\n try {\n await updateCardKitCardForAuth({\n cfg,\n cardId: pending.cardId,\n card: buildAuthSuccessCard(),\n sequence: 2,\n accountId: ticket.accountId,\n });\n } catch (e) {\n log.warn(`failed to update project auth card to success: ${e}`);\n }\n }\n\n return json({\n success: true,\n message: '\u98DE\u4E66\u9879\u76EE\u6388\u6743\u6210\u529F\uFF01',\n authorized: true,\n });\n }\n\n // ---------------------------------------------------------------\n // REVOKE\n // ---------------------------------------------------------------\n case 'revoke': {\n const clientId = await getProjectClientId(senderOpenId);\n if (clientId) {\n await removeProjectStoredToken(clientId, senderOpenId);\n }\n return json({ success: true, message: '\u98DE\u4E66\u9879\u76EE\u6388\u6743\u5DF2\u64A4\u9500\u3002' });\n }\n\n default:\n return json({ error: `\u672A\u77E5\u64CD\u4F5C: ${(p as { action: string }).action}` });\n }\n } catch (err) {\n log.error(`project oauth ${p.action} failed: ${err}`);\n return json({ error: formatLarkError(err) });\n }\n },\n },\n { name: 'feishu_project_oauth' },\n );\n\n api.logger.info?.('feishu_project_oauth: Registered feishu_project_oauth tool');\n}\n\n// ---------------------------------------------------------------------------\n// Card callback handler \u2014 \u5361\u7247\u8868\u5355\u63D0\u4EA4\u56DE\u8C03\n// ---------------------------------------------------------------------------\n\n/**\n * \u5904\u7406\u98DE\u4E66\u9879\u76EE OAuth \u5361\u7247\u7684 form submit \u56DE\u8C03\u3002\n *\n * \u7528\u6237\u5728\u5361\u7247\u8F93\u5165\u6846\u7C98\u8D34 callback URL \u5E76\u70B9\u51FB\"\u5B8C\u6210\u6388\u6743\"\u540E\u89E6\u53D1\u3002\n * \u7531 auto-auth.ts \u7684 handleCardAction \u5206\u53D1\u8C03\u7528\u3002\n */\nexport async function handleProjectAuthCardAction(\n data: unknown,\n cfg: import('openclaw/plugin-sdk').ClawdbotConfig,\n accountId: string,\n): Promise<unknown> {\n let callbackUrl: string | undefined;\n let senderOpenId: string | undefined;\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const event = data as Record<string, any>;\n senderOpenId = event.operator?.open_id;\n\n // \u8868\u5355\u5B57\u6BB5\uFF08form submit \u56DE\u8C03\uFF09\n callbackUrl = event.action?.form_value?.callback_url?.trim();\n\n log.debug(`project card action raw: callbackUrl=${callbackUrl ? '(set)' : '(empty)'}, senderOpenId=${senderOpenId}, actionKeys=${JSON.stringify(Object.keys(event.action ?? {}))}`);\n } catch {\n return;\n }\n\n if (!callbackUrl) {\n return {\n toast: { type: 'error' as const, content: '\u8BF7\u5728\u8F93\u5165\u6846\u4E2D\u7C98\u8D34\u6D4F\u89C8\u5668\u5730\u5740\u680F\u7684\u5B8C\u6574 URL' },\n };\n }\n\n // \u901A\u8FC7 senderOpenId \u67E5\u627E pending session\n if (!senderOpenId) {\n log.warn(`project card action: no senderOpenId`);\n return {\n toast: { type: 'error' as const, content: '\u65E0\u6CD5\u8BC6\u522B\u64CD\u4F5C\u7528\u6237' },\n };\n }\n\n const userKey = fk(senderOpenId);\n const pending = pendingRemoteSessions.get(userKey);\n\n if (!pending) {\n log.warn(`project card action: no pending session found (senderOpenId=${senderOpenId})`);\n return {\n toast: { type: 'error' as const, content: '\u6388\u6743\u94FE\u63A5\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77\u6388\u6743' },\n };\n }\n\n // \u6821\u9A8C\u64CD\u4F5C\u4EBA\u4E0E\u53D1\u8D77\u4EBA\u4E00\u81F4\n if (senderOpenId && senderOpenId !== pending.senderOpenId) {\n log.warn(`project card action: identity mismatch, expected=${pending.senderOpenId}, actual=${senderOpenId}`);\n return {\n toast: { type: 'error' as const, content: '\u8BF7\u4F7F\u7528\u53D1\u8D77\u6388\u6743\u7684\u8D26\u53F7\u5B8C\u6210\u64CD\u4F5C' },\n };\n }\n\n // \u6E05\u7406\n pendingRemoteSessions.delete(userKey);\n\n // \u5F02\u6B65\u5B8C\u6210 token \u4EA4\u6362\uFF08\u5148\u8FD4\u56DE toast\uFF0C\u518D\u540E\u53F0\u5904\u7406\uFF09\n const capturedPending = pending;\n const capturedCallbackUrl = callbackUrl;\n\n setImmediate(async () => {\n try {\n const result = await completeRemoteAuth(capturedPending.session, capturedCallbackUrl);\n const storedToken = buildTokenFromOAuth(\n capturedPending.senderOpenId,\n result.clientId,\n result.tokens,\n );\n await setProjectStoredToken(storedToken);\n\n // \u66F4\u65B0\u5361\u7247\u4E3A\"\u6388\u6743\u6210\u529F\"\n if (capturedPending.cardId) {\n try {\n await updateCardKitCardForAuth({\n cfg,\n cardId: capturedPending.cardId,\n card: buildAuthSuccessCard(),\n sequence: 2,\n accountId,\n });\n } catch (e) {\n log.warn(`failed to update project auth card to success: ${e}`);\n }\n }\n\n log.info(`project auth completed via card callback for ${capturedPending.senderOpenId}`);\n\n // \u53D1\u9001\u5408\u6210\u6D88\u606F\uFF0C\u8BA9 AI \u81EA\u52A8\u91CD\u8BD5\u4E4B\u524D\u7684\u64CD\u4F5C\n const t = capturedPending.ticket;\n if (t.chatId) {\n try {\n const syntheticMsgId = `${t.messageId}:project-auth-complete`;\n const syntheticEvent = {\n sender: { sender_id: { open_id: capturedPending.senderOpenId } },\n message: {\n message_id: syntheticMsgId,\n chat_id: t.chatId,\n chat_type: t.chatType ?? 'p2p',\n message_type: 'text',\n content: JSON.stringify({ text: '\u6211\u5DF2\u5B8C\u6210\u98DE\u4E66\u9879\u76EE\u6388\u6743\uFF0C\u8BF7\u7EE7\u7EED\u6267\u884C\u4E4B\u524D\u7684\u64CD\u4F5C\u3002' }),\n thread_id: t.threadId,\n },\n };\n const syntheticRuntime = {\n log: (msg: string) => log.info(msg),\n error: (msg: string) => log.error(msg),\n };\n const { promise } = enqueueFeishuChatTask({\n accountId: t.accountId,\n chatId: t.chatId,\n threadId: t.threadId,\n task: async () => {\n await withTicket(\n {\n messageId: syntheticMsgId,\n chatId: t.chatId,\n accountId: t.accountId,\n startTime: Date.now(),\n senderOpenId: capturedPending.senderOpenId,\n chatType: t.chatType,\n threadId: t.threadId,\n },\n () =>\n handleFeishuMessage({\n cfg,\n event: syntheticEvent as Parameters<typeof handleFeishuMessage>[0]['event'],\n accountId: t.accountId,\n forceMention: true,\n runtime: syntheticRuntime as Parameters<typeof handleFeishuMessage>[0]['runtime'],\n replyToMessageId: t.messageId,\n }),\n );\n },\n });\n await promise;\n log.info('synthetic message dispatched after project auth');\n } catch (e) {\n log.warn(`failed to send synthetic message after project auth: ${e}`);\n }\n }\n } catch (err) {\n log.error(`project auth card callback failed: ${err}`);\n // \u5C1D\u8BD5\u66F4\u65B0\u5361\u7247\u4E3A\u5931\u8D25\u72B6\u6001\n if (capturedPending.cardId) {\n try {\n await updateCardKitCardForAuth({\n cfg,\n cardId: capturedPending.cardId,\n card: buildAuthFailedCard(err instanceof Error ? err.message : String(err)),\n sequence: 2,\n accountId,\n });\n } catch (e) {\n log.warn(`failed to update project auth card to failure: ${e}`);\n }\n }\n }\n });\n\n return {\n toast: { type: 'info' as const, content: '\u6B63\u5728\u5B8C\u6210\u6388\u6743...' },\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["session", "authCard"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-project/openclaw-lark-project",
3
- "version": "2026.3.166",
3
+ "version": "2026.3.168",
4
4
  "description": "OpenClaw Lark/Feishu channel plugin",
5
5
  "type": "module",
6
6
  "files": [
@@ -15,6 +15,20 @@ description: |
15
15
 
16
16
  # 飞书项目(Meego)工具使用指南
17
17
 
18
+ ## 环境确认
19
+
20
+ 首次使用飞书项目能力、发起授权之前,检查配置中 `channels.feishu.project.domain` 是否已设置。
21
+
22
+ **如果已配置**:直接使用配置值,跳过询问。
23
+
24
+ **如果未配置(值为空)**:询问用户所使用的飞书项目环境,提供以下选项:
25
+
26
+ 1. **飞书项目**(国内 SaaS,project.feishu.cn)— 选此项无需额外配置,直接继续
27
+ 2. **Meegle**(meegle.com)— 需要在配置中设置 `channels.feishu.project.domain: "meegle"`
28
+ 3. **自定义域名** — 需要设置为完整 URL,例如 `channels.feishu.project.domain: "https://custom.example.com"`
29
+
30
+ 用户确认后,如果选择了非默认环境,提示用户将 `project.domain` 写入配置文件以避免每次询问。
31
+
18
32
  ## 授权前置检查
19
33
 
20
34
  飞书项目的 OAuth 授权与飞书 IM/文档的授权**完全独立**,不共享 token。首次使用飞书项目功能时,必须先完成独立授权。