@lark-project/openclaw-lark-project 2026.3.176 → 2026.3.177

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.
@@ -131,13 +131,13 @@ async function setProjectStoredToken(token) {
131
131
  const key = accountKey(token.appId, token.userOpenId);
132
132
  const payload = JSON.stringify(token);
133
133
  await backend.set(PROJECT_SERVICE, key, payload);
134
- await saveClientIdIndex(token.userOpenId, token.appId);
135
- log.info(`saved project UAT for ${token.userOpenId} (at:${maskToken(token.accessToken)})`);
134
+ await saveClientIdIndex(token.userOpenId, token.appId, token.domain);
135
+ log.info(`saved project UAT for ${token.userOpenId} domain=${token.domain} (at:${maskToken(token.accessToken)})`);
136
136
  }
137
- async function removeProjectStoredToken(appId, userOpenId) {
137
+ async function removeProjectStoredToken(appId, userOpenId, domain) {
138
138
  await backend.remove(PROJECT_SERVICE, accountKey(appId, userOpenId));
139
- await removeClientIdIndex(userOpenId);
140
- log.info(`removed project UAT for ${userOpenId}`);
139
+ await removeClientIdIndex(userOpenId, domain);
140
+ log.info(`removed project UAT for ${userOpenId} domain=${domain}`);
141
141
  }
142
142
  function projectTokenStatus(token) {
143
143
  const now = Date.now();
@@ -146,19 +146,19 @@ function projectTokenStatus(token) {
146
146
  return "expired";
147
147
  }
148
148
  const CLIENT_ID_SERVICE = "openclaw-feishu-project-cid";
149
- async function getProjectClientId(userOpenId) {
149
+ async function getProjectClientId(userOpenId, domain) {
150
150
  try {
151
- return await backend.get(CLIENT_ID_SERVICE, userOpenId);
151
+ return await backend.get(CLIENT_ID_SERVICE, `${domain}:${userOpenId}`);
152
152
  } catch {
153
153
  return null;
154
154
  }
155
155
  }
156
- async function saveClientIdIndex(userOpenId, clientId) {
157
- await backend.set(CLIENT_ID_SERVICE, userOpenId, clientId);
156
+ async function saveClientIdIndex(userOpenId, clientId, domain) {
157
+ await backend.set(CLIENT_ID_SERVICE, `${domain}:${userOpenId}`, clientId);
158
158
  }
159
- async function removeClientIdIndex(userOpenId) {
159
+ async function removeClientIdIndex(userOpenId, domain) {
160
160
  try {
161
- await backend.remove(CLIENT_ID_SERVICE, userOpenId);
161
+ await backend.remove(CLIENT_ID_SERVICE, `${domain}:${userOpenId}`);
162
162
  } catch {
163
163
  }
164
164
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/core/project-token-store.ts"],
4
- "sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * \u98DE\u4E66\u9879\u76EE\uFF08Meego\uFF09\u4E13\u7528 Token \u5B58\u50A8\n *\n * \u590D\u7528 token-store \u7684 StoredUAToken \u7ED3\u6784\u548C\u8DE8\u5E73\u53F0\u540E\u7AEF\uFF0C\n * \u4F46\u4F7F\u7528\u72EC\u7ACB\u7684 service key\uFF0C\u4E0E\u98DE\u4E66 Open API \u7684 token \u5206\u5F00\u5B58\u50A8\u3002\n */\n\nimport { execFile as execFileCb } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { mkdir, unlink, readFile, writeFile, chmod } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { randomBytes, createCipheriv, createDecipheriv } from 'node:crypto';\nimport { larkLogger } from './lark-logger';\nimport { maskToken, type StoredUAToken } from './token-store';\n\nconst log = larkLogger('core/project-token-store');\nconst execFile = promisify(execFileCb);\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst PROJECT_SERVICE = 'openclaw-feishu-project-uat';\nconst REFRESH_AHEAD_MS = 5 * 60 * 1000;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction accountKey(appId: string, userOpenId: string): string {\n return `${appId}:${userOpenId}`;\n}\n\n// ---------------------------------------------------------------------------\n// Backend interface (same as token-store.ts)\n// ---------------------------------------------------------------------------\n\ninterface KeychainBackend {\n get(service: string, account: string): Promise<string | null>;\n set(service: string, account: string, data: string): Promise<void>;\n remove(service: string, account: string): Promise<void>;\n}\n\n// macOS\nconst darwinBackend: KeychainBackend = {\n async get(service, account) {\n try {\n const { stdout } = await execFile('security', ['find-generic-password', '-s', service, '-a', account, '-w']);\n return stdout.trim() || null;\n } catch {\n return null;\n }\n },\n async set(service, account, data) {\n try {\n await execFile('security', ['delete-generic-password', '-s', service, '-a', account]);\n } catch { /* Not found */ }\n await execFile('security', ['add-generic-password', '-s', service, '-a', account, '-w', data]);\n },\n async remove(service, account) {\n try {\n await execFile('security', ['delete-generic-password', '-s', service, '-a', account]);\n } catch { /* Already absent */ }\n },\n};\n\n// Linux / Windows \u2014 AES-256-GCM encrypted files\nconst MASTER_KEY_BYTES = 32;\nconst IV_BYTES = 12;\nconst TAG_BYTES = 16;\n\nfunction getStorageDir(): string {\n if (process.platform === 'win32') {\n return join(\n process.env.LOCALAPPDATA ?? join(process.env.USERPROFILE ?? homedir(), 'AppData', 'Local'),\n PROJECT_SERVICE,\n );\n }\n return join(process.env.XDG_DATA_HOME || join(homedir(), '.local', 'share'), PROJECT_SERVICE);\n}\n\nconst STORAGE_DIR = getStorageDir();\nconst MASTER_KEY_PATH = join(STORAGE_DIR, 'master.key');\n\nfunction safeFileName(account: string): string {\n return account.replace(/[^a-zA-Z0-9._-]/g, '_') + '.enc';\n}\n\nasync function ensureDir(): Promise<void> {\n await mkdir(STORAGE_DIR, { recursive: true, mode: 0o700 });\n}\n\nasync function getMasterKey(): Promise<Buffer> {\n try {\n const key = await readFile(MASTER_KEY_PATH);\n if (key.length === MASTER_KEY_BYTES) return key;\n } catch (err: unknown) {\n if (!(err instanceof Error) || (err as NodeJS.ErrnoException).code !== 'ENOENT') {\n log.warn(`failed to read project master key: ${err instanceof Error ? err.message : err}`);\n }\n }\n await ensureDir();\n const key = randomBytes(MASTER_KEY_BYTES);\n await writeFile(MASTER_KEY_PATH, key, { mode: 0o600 });\n await chmod(MASTER_KEY_PATH, 0o600);\n return key;\n}\n\nfunction encrypt(plaintext: string, key: Buffer): Buffer {\n const iv = randomBytes(IV_BYTES);\n const cipher = createCipheriv('aes-256-gcm', key, iv);\n const enc = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);\n return Buffer.concat([iv, cipher.getAuthTag(), enc]);\n}\n\nfunction decrypt(data: Buffer, key: Buffer): string | null {\n if (data.length < IV_BYTES + TAG_BYTES) return null;\n try {\n const iv = data.subarray(0, IV_BYTES);\n const tag = data.subarray(IV_BYTES, IV_BYTES + TAG_BYTES);\n const enc = data.subarray(IV_BYTES + TAG_BYTES);\n const decipher = createDecipheriv('aes-256-gcm', key, iv);\n decipher.setAuthTag(tag);\n return Buffer.concat([decipher.update(enc), decipher.final()]).toString('utf8');\n } catch {\n return null;\n }\n}\n\nconst fileBackend: KeychainBackend = {\n async get(_service, account) {\n try {\n const key = await getMasterKey();\n const data = await readFile(join(STORAGE_DIR, safeFileName(account)));\n return decrypt(data, key);\n } catch {\n return null;\n }\n },\n async set(_service, account, data) {\n const key = await getMasterKey();\n await ensureDir();\n const filePath = join(STORAGE_DIR, safeFileName(account));\n const encrypted = encrypt(data, key);\n await writeFile(filePath, encrypted, { mode: 0o600 });\n },\n async remove(_service, account) {\n try {\n await unlink(join(STORAGE_DIR, safeFileName(account)));\n } catch { /* Already absent */ }\n },\n};\n\nfunction createBackend(): KeychainBackend {\n return process.platform === 'darwin' ? darwinBackend : fileBackend;\n}\n\nconst backend = createBackend();\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport async function getProjectStoredToken(appId: string, userOpenId: string): Promise<StoredUAToken | null> {\n try {\n const json = await backend.get(PROJECT_SERVICE, accountKey(appId, userOpenId));\n if (!json) return null;\n return JSON.parse(json) as StoredUAToken;\n } catch {\n return null;\n }\n}\n\nexport async function setProjectStoredToken(token: StoredUAToken): Promise<void> {\n const key = accountKey(token.appId, token.userOpenId);\n const payload = JSON.stringify(token);\n await backend.set(PROJECT_SERVICE, key, payload);\n await saveClientIdIndex(token.userOpenId, token.appId);\n log.info(`saved project UAT for ${token.userOpenId} (at:${maskToken(token.accessToken)})`);\n}\n\nexport async function removeProjectStoredToken(appId: string, userOpenId: string): Promise<void> {\n await backend.remove(PROJECT_SERVICE, accountKey(appId, userOpenId));\n await removeClientIdIndex(userOpenId);\n log.info(`removed project UAT for ${userOpenId}`);\n}\n\nexport function projectTokenStatus(token: StoredUAToken): 'valid' | 'needs_refresh' | 'expired' {\n const now = Date.now();\n if (now < token.expiresAt - REFRESH_AHEAD_MS) return 'valid';\n if (now < token.refreshExpiresAt) return 'needs_refresh';\n return 'expired';\n}\n\n// ---------------------------------------------------------------------------\n// clientId \u7D22\u5F15 \u2014 \u4ECE userOpenId \u53CD\u67E5\u52A8\u6001\u6CE8\u518C\u7684 clientId\n// ---------------------------------------------------------------------------\n\nconst CLIENT_ID_SERVICE = 'openclaw-feishu-project-cid';\n\n/**\n * \u83B7\u53D6\u7528\u6237\u5BF9\u5E94\u7684\u98DE\u4E66\u9879\u76EE OAuth clientId\u3002\n * \u52A8\u6001\u6CE8\u518C\u7684 clientId \u5728\u6BCF\u6B21\u6CE8\u518C\u65F6\u53EF\u80FD\u4E0D\u540C\uFF0C\u6B64\u7D22\u5F15\u7528\u4E8E status/revoke \u7B49\n * \u4E0D\u77E5\u9053 clientId \u7684\u573A\u666F\u3002\n */\nexport async function getProjectClientId(userOpenId: string): Promise<string | null> {\n try {\n return await backend.get(CLIENT_ID_SERVICE, userOpenId);\n } catch {\n return null;\n }\n}\n\n/**\n * setProjectStoredToken \u7684\u589E\u5F3A\uFF1A\u540C\u65F6\u4FDD\u5B58 clientId \u7D22\u5F15\u3002\n * \u8C03\u7528\u65B9\u5728 setProjectStoredToken \u540E\u65E0\u9700\u989D\u5916\u8C03\u7528\u3002\n */\nasync function saveClientIdIndex(userOpenId: string, clientId: string): Promise<void> {\n await backend.set(CLIENT_ID_SERVICE, userOpenId, clientId);\n}\n\nasync function removeClientIdIndex(userOpenId: string): Promise<void> {\n try {\n await backend.remove(CLIENT_ID_SERVICE, userOpenId);\n } catch { /* Already absent */ }\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 * \u98DE\u4E66\u9879\u76EE\uFF08Meego\uFF09\u4E13\u7528 Token \u5B58\u50A8\n *\n * \u590D\u7528 token-store \u7684 StoredUAToken \u7ED3\u6784\u548C\u8DE8\u5E73\u53F0\u540E\u7AEF\uFF0C\n * \u4F46\u4F7F\u7528\u72EC\u7ACB\u7684 service key\uFF0C\u4E0E\u98DE\u4E66 Open API \u7684 token \u5206\u5F00\u5B58\u50A8\u3002\n */\n\nimport { execFile as execFileCb } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { mkdir, unlink, readFile, writeFile, chmod } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { randomBytes, createCipheriv, createDecipheriv } from 'node:crypto';\nimport { larkLogger } from './lark-logger';\nimport { maskToken, type StoredUAToken } from './token-store';\n\nconst log = larkLogger('core/project-token-store');\nconst execFile = promisify(execFileCb);\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst PROJECT_SERVICE = 'openclaw-feishu-project-uat';\nconst REFRESH_AHEAD_MS = 5 * 60 * 1000;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction accountKey(appId: string, userOpenId: string): string {\n return `${appId}:${userOpenId}`;\n}\n\n// ---------------------------------------------------------------------------\n// Backend interface (same as token-store.ts)\n// ---------------------------------------------------------------------------\n\ninterface KeychainBackend {\n get(service: string, account: string): Promise<string | null>;\n set(service: string, account: string, data: string): Promise<void>;\n remove(service: string, account: string): Promise<void>;\n}\n\n// macOS\nconst darwinBackend: KeychainBackend = {\n async get(service, account) {\n try {\n const { stdout } = await execFile('security', ['find-generic-password', '-s', service, '-a', account, '-w']);\n return stdout.trim() || null;\n } catch {\n return null;\n }\n },\n async set(service, account, data) {\n try {\n await execFile('security', ['delete-generic-password', '-s', service, '-a', account]);\n } catch { /* Not found */ }\n await execFile('security', ['add-generic-password', '-s', service, '-a', account, '-w', data]);\n },\n async remove(service, account) {\n try {\n await execFile('security', ['delete-generic-password', '-s', service, '-a', account]);\n } catch { /* Already absent */ }\n },\n};\n\n// Linux / Windows \u2014 AES-256-GCM encrypted files\nconst MASTER_KEY_BYTES = 32;\nconst IV_BYTES = 12;\nconst TAG_BYTES = 16;\n\nfunction getStorageDir(): string {\n if (process.platform === 'win32') {\n return join(\n process.env.LOCALAPPDATA ?? join(process.env.USERPROFILE ?? homedir(), 'AppData', 'Local'),\n PROJECT_SERVICE,\n );\n }\n return join(process.env.XDG_DATA_HOME || join(homedir(), '.local', 'share'), PROJECT_SERVICE);\n}\n\nconst STORAGE_DIR = getStorageDir();\nconst MASTER_KEY_PATH = join(STORAGE_DIR, 'master.key');\n\nfunction safeFileName(account: string): string {\n return account.replace(/[^a-zA-Z0-9._-]/g, '_') + '.enc';\n}\n\nasync function ensureDir(): Promise<void> {\n await mkdir(STORAGE_DIR, { recursive: true, mode: 0o700 });\n}\n\nasync function getMasterKey(): Promise<Buffer> {\n try {\n const key = await readFile(MASTER_KEY_PATH);\n if (key.length === MASTER_KEY_BYTES) return key;\n } catch (err: unknown) {\n if (!(err instanceof Error) || (err as NodeJS.ErrnoException).code !== 'ENOENT') {\n log.warn(`failed to read project master key: ${err instanceof Error ? err.message : err}`);\n }\n }\n await ensureDir();\n const key = randomBytes(MASTER_KEY_BYTES);\n await writeFile(MASTER_KEY_PATH, key, { mode: 0o600 });\n await chmod(MASTER_KEY_PATH, 0o600);\n return key;\n}\n\nfunction encrypt(plaintext: string, key: Buffer): Buffer {\n const iv = randomBytes(IV_BYTES);\n const cipher = createCipheriv('aes-256-gcm', key, iv);\n const enc = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);\n return Buffer.concat([iv, cipher.getAuthTag(), enc]);\n}\n\nfunction decrypt(data: Buffer, key: Buffer): string | null {\n if (data.length < IV_BYTES + TAG_BYTES) return null;\n try {\n const iv = data.subarray(0, IV_BYTES);\n const tag = data.subarray(IV_BYTES, IV_BYTES + TAG_BYTES);\n const enc = data.subarray(IV_BYTES + TAG_BYTES);\n const decipher = createDecipheriv('aes-256-gcm', key, iv);\n decipher.setAuthTag(tag);\n return Buffer.concat([decipher.update(enc), decipher.final()]).toString('utf8');\n } catch {\n return null;\n }\n}\n\nconst fileBackend: KeychainBackend = {\n async get(_service, account) {\n try {\n const key = await getMasterKey();\n const data = await readFile(join(STORAGE_DIR, safeFileName(account)));\n return decrypt(data, key);\n } catch {\n return null;\n }\n },\n async set(_service, account, data) {\n const key = await getMasterKey();\n await ensureDir();\n const filePath = join(STORAGE_DIR, safeFileName(account));\n const encrypted = encrypt(data, key);\n await writeFile(filePath, encrypted, { mode: 0o600 });\n },\n async remove(_service, account) {\n try {\n await unlink(join(STORAGE_DIR, safeFileName(account)));\n } catch { /* Already absent */ }\n },\n};\n\nfunction createBackend(): KeychainBackend {\n return process.platform === 'darwin' ? darwinBackend : fileBackend;\n}\n\nconst backend = createBackend();\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport async function getProjectStoredToken(appId: string, userOpenId: string): Promise<StoredUAToken | null> {\n try {\n const json = await backend.get(PROJECT_SERVICE, accountKey(appId, userOpenId));\n if (!json) return null;\n return JSON.parse(json) as StoredUAToken;\n } catch {\n return null;\n }\n}\n\nexport async function setProjectStoredToken(token: StoredUAToken & { domain: string }): Promise<void> {\n const key = accountKey(token.appId, token.userOpenId);\n const payload = JSON.stringify(token);\n await backend.set(PROJECT_SERVICE, key, payload);\n await saveClientIdIndex(token.userOpenId, token.appId, token.domain);\n log.info(`saved project UAT for ${token.userOpenId} domain=${token.domain} (at:${maskToken(token.accessToken)})`);\n}\n\nexport async function removeProjectStoredToken(appId: string, userOpenId: string, domain: string): Promise<void> {\n await backend.remove(PROJECT_SERVICE, accountKey(appId, userOpenId));\n await removeClientIdIndex(userOpenId, domain);\n log.info(`removed project UAT for ${userOpenId} domain=${domain}`);\n}\n\nexport function projectTokenStatus(token: StoredUAToken): 'valid' | 'needs_refresh' | 'expired' {\n const now = Date.now();\n if (now < token.expiresAt - REFRESH_AHEAD_MS) return 'valid';\n if (now < token.refreshExpiresAt) return 'needs_refresh';\n return 'expired';\n}\n\n// ---------------------------------------------------------------------------\n// clientId \u7D22\u5F15 \u2014 \u4ECE userOpenId \u53CD\u67E5\u52A8\u6001\u6CE8\u518C\u7684 clientId\n// ---------------------------------------------------------------------------\n\nconst CLIENT_ID_SERVICE = 'openclaw-feishu-project-cid';\n\n/**\n * \u83B7\u53D6\u7528\u6237\u5BF9\u5E94\u7684\u98DE\u4E66\u9879\u76EE OAuth clientId\u3002\n * \u52A8\u6001\u6CE8\u518C\u7684 clientId \u5728\u6BCF\u6B21\u6CE8\u518C\u65F6\u53EF\u80FD\u4E0D\u540C\uFF0C\u6B64\u7D22\u5F15\u7528\u4E8E status/revoke \u7B49\n * \u4E0D\u77E5\u9053 clientId \u7684\u573A\u666F\u3002\n */\nexport async function getProjectClientId(userOpenId: string, domain: string): Promise<string | null> {\n try {\n return await backend.get(CLIENT_ID_SERVICE, `${domain}:${userOpenId}`);\n } catch {\n return null;\n }\n}\n\nasync function saveClientIdIndex(userOpenId: string, clientId: string, domain: string): Promise<void> {\n await backend.set(CLIENT_ID_SERVICE, `${domain}:${userOpenId}`, clientId);\n}\n\nasync function removeClientIdIndex(userOpenId: string, domain: string): Promise<void> {\n try {\n await backend.remove(CLIENT_ID_SERVICE, `${domain}:${userOpenId}`);\n } catch { /* Already absent */ }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["key"]
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/core/token-store.ts"],
4
- "sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * UAT (User Access Token) persistent storage with cross-platform support.\n *\n * Stores OAuth token data using OS-native credential services so that tokens\n * survive process restarts without introducing plain-text local files.\n *\n * Platform backends:\n * macOS \u2013 Keychain Access via `security` CLI\n * Linux \u2013 AES-256-GCM encrypted files (XDG_DATA_HOME)\n * Windows \u2013 AES-256-GCM encrypted files (%LOCALAPPDATA%)\n *\n * Storage layout:\n * Service = \"openclaw-feishu-uat\"\n * Account = \"{appId}:{userOpenId}\"\n * Password = JSON-serialised StoredUAToken\n */\n\nimport { execFile as execFileCb } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { mkdir, unlink, readFile, writeFile, chmod } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { randomBytes, createCipheriv, createDecipheriv } from 'node:crypto';\nimport { larkLogger } from './lark-logger';\n\nconst log = larkLogger('core/token-store');\n\nconst execFile = promisify(execFileCb);\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface StoredUAToken {\n userOpenId: string;\n appId: string;\n accessToken: string;\n refreshToken: string;\n expiresAt: number; // Unix ms \u2013 access_token expiry\n refreshExpiresAt: number; // Unix ms \u2013 refresh_token expiry\n scope: string;\n grantedAt: number; // Unix ms \u2013 original grant time\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst KEYCHAIN_SERVICE = 'openclaw-feishu-uat';\n\n/** Refresh proactively when access_token expires within this window. */\nconst REFRESH_AHEAD_MS = 5 * 60 * 1000; // 5 minutes\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction accountKey(appId: string, userOpenId: string): string {\n return `${appId}:${userOpenId}`;\n}\n\n/** Mask a token for safe logging: only the last 4 chars are visible. */\nexport function maskToken(token: string): string {\n if (token.length <= 8) return '****';\n return `****${token.slice(-4)}`;\n}\n\n// ---------------------------------------------------------------------------\n// Backend interface\n// ---------------------------------------------------------------------------\n\ninterface KeychainBackend {\n get(service: string, account: string): Promise<string | null>;\n set(service: string, account: string, data: string): Promise<void>;\n remove(service: string, account: string): Promise<void>;\n}\n\n// ---------------------------------------------------------------------------\n// macOS backend \u2013 Keychain Access via `security` CLI\n// ---------------------------------------------------------------------------\n\nconst darwinBackend: KeychainBackend = {\n async get(service, account) {\n try {\n const { stdout } = await execFile('security', ['find-generic-password', '-s', service, '-a', account, '-w']);\n return stdout.trim() || null;\n } catch {\n return null;\n }\n },\n\n async set(service, account, data) {\n // Delete first \u2013 `add-generic-password` fails if the item already exists.\n try {\n await execFile('security', ['delete-generic-password', '-s', service, '-a', account]);\n } catch {\n // Not found \u2013 fine.\n }\n await execFile('security', ['add-generic-password', '-s', service, '-a', account, '-w', data]);\n },\n\n async remove(service, account) {\n try {\n await execFile('security', ['delete-generic-password', '-s', service, '-a', account]);\n } catch {\n // Already absent \u2013 fine.\n }\n },\n};\n\n// ---------------------------------------------------------------------------\n// Linux backend \u2013 AES-256-GCM encrypted files (XDG Base Directory)\n//\n// Headless Linux servers typically lack D-Bus / GNOME Keyring, so we store\n// tokens as AES-256-GCM encrypted files instead of using `secret-tool`.\n//\n// Storage path: ${XDG_DATA_HOME:-~/.local/share}/openclaw-feishu-uat/\n// ---------------------------------------------------------------------------\n\nconst LINUX_UAT_DIR = join(process.env.XDG_DATA_HOME || join(homedir(), '.local', 'share'), 'openclaw-feishu-uat');\nconst MASTER_KEY_PATH = join(LINUX_UAT_DIR, 'master.key');\nconst MASTER_KEY_BYTES = 32; // AES-256\nconst IV_BYTES = 12; // GCM recommended\nconst TAG_BYTES = 16; // GCM auth tag\n\n/** Convert account key to a filesystem-safe filename. */\nfunction linuxSafeFileName(account: string): string {\n return account.replace(/[^a-zA-Z0-9._-]/g, '_') + '.enc';\n}\n\n/** Ensure the credentials directory exists with mode 0700. */\nasync function ensureLinuxCredDir(): Promise<void> {\n await mkdir(LINUX_UAT_DIR, { recursive: true, mode: 0o700 });\n}\n\n/**\n * Load or create the 32-byte master key.\n *\n * On first run, generates a random key and writes it to disk (mode 0600).\n * On subsequent runs, reads the existing key file.\n */\nasync function getMasterKey(): Promise<Buffer> {\n try {\n const key = await readFile(MASTER_KEY_PATH);\n if (key.length === MASTER_KEY_BYTES) return key;\n log.warn('master key has unexpected length, regenerating');\n } catch (err: unknown) {\n if (!(err instanceof Error) || (err as NodeJS.ErrnoException).code !== 'ENOENT') {\n log.warn(`failed to read master key: ${err instanceof Error ? err.message : err}`);\n }\n }\n\n await ensureLinuxCredDir();\n const key = randomBytes(MASTER_KEY_BYTES);\n await writeFile(MASTER_KEY_PATH, key, { mode: 0o600 });\n await chmod(MASTER_KEY_PATH, 0o600);\n log.info('generated new master key for encrypted file storage');\n return key;\n}\n\n/** AES-256-GCM encrypt. Returns [12-byte IV][16-byte tag][ciphertext]. */\nfunction encryptData(plaintext: string, key: Buffer): Buffer {\n const iv = randomBytes(IV_BYTES);\n const cipher = createCipheriv('aes-256-gcm', key, iv);\n const enc = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);\n return Buffer.concat([iv, cipher.getAuthTag(), enc]);\n}\n\n/** AES-256-GCM decrypt. Returns plaintext or `null` on failure. */\nfunction decryptData(data: Buffer, key: Buffer): string | null {\n if (data.length < IV_BYTES + TAG_BYTES) return null;\n try {\n const iv = data.subarray(0, IV_BYTES);\n const tag = data.subarray(IV_BYTES, IV_BYTES + TAG_BYTES);\n const enc = data.subarray(IV_BYTES + TAG_BYTES);\n const decipher = createDecipheriv('aes-256-gcm', key, iv);\n decipher.setAuthTag(tag);\n return Buffer.concat([decipher.update(enc), decipher.final()]).toString('utf8');\n } catch {\n return null;\n }\n}\n\nconst linuxBackend: KeychainBackend = {\n async get(_service, account) {\n try {\n const key = await getMasterKey();\n const data = await readFile(join(LINUX_UAT_DIR, linuxSafeFileName(account)));\n return decryptData(data, key);\n } catch {\n return null;\n }\n },\n\n async set(_service, account, data) {\n const key = await getMasterKey();\n await ensureLinuxCredDir();\n const filePath = join(LINUX_UAT_DIR, linuxSafeFileName(account));\n const encrypted = encryptData(data, key);\n await writeFile(filePath, encrypted, { mode: 0o600 });\n await chmod(filePath, 0o600);\n },\n\n async remove(_service, account) {\n try {\n await unlink(join(LINUX_UAT_DIR, linuxSafeFileName(account)));\n } catch {\n // Already absent \u2013 fine.\n }\n },\n};\n\n// ---------------------------------------------------------------------------\n// Windows backend \u2013 AES-256-GCM encrypted files\n//\n// Replaces the previous DPAPI-via-PowerShell approach which was unreliable\n// (PowerShell cold-start latency, execution policy restrictions, cmd.exe\n// command-line length limits, and unavailability in containers).\n//\n// Uses the same AES-256-GCM scheme as the Linux backend with its own\n// independent storage directory and master key.\n//\n// Storage path: %LOCALAPPDATA%\\openclaw-feishu-uat\\\n// ---------------------------------------------------------------------------\n\nconst WIN32_UAT_DIR = join(\n process.env.LOCALAPPDATA ?? join(process.env.USERPROFILE ?? homedir(), 'AppData', 'Local'),\n KEYCHAIN_SERVICE,\n);\nconst WIN32_MASTER_KEY_PATH = join(WIN32_UAT_DIR, 'master.key');\n\n/** Convert account key to a filesystem-safe filename (whitelist approach). */\nfunction win32SafeFileName(account: string): string {\n return account.replace(/[^a-zA-Z0-9._-]/g, '_') + '.enc';\n}\n\nasync function ensureWin32CredDir(): Promise<void> {\n await mkdir(WIN32_UAT_DIR, { recursive: true });\n}\n\nasync function getWin32MasterKey(): Promise<Buffer> {\n try {\n const key = await readFile(WIN32_MASTER_KEY_PATH);\n if (key.length === MASTER_KEY_BYTES) return key;\n log.warn('win32 master key has unexpected length, regenerating');\n } catch (err: unknown) {\n if (!(err instanceof Error) || (err as NodeJS.ErrnoException).code !== 'ENOENT') {\n log.warn(`failed to read win32 master key: ${err instanceof Error ? err.message : err}`);\n }\n }\n\n await ensureWin32CredDir();\n const key = randomBytes(MASTER_KEY_BYTES);\n await writeFile(WIN32_MASTER_KEY_PATH, key);\n log.info('generated new master key for win32 encrypted file storage');\n return key;\n}\n\nconst win32Backend: KeychainBackend = {\n async get(_service, account) {\n try {\n const key = await getWin32MasterKey();\n const data = await readFile(join(WIN32_UAT_DIR, win32SafeFileName(account)));\n return decryptData(data, key);\n } catch {\n return null;\n }\n },\n\n async set(_service, account, data) {\n const key = await getWin32MasterKey();\n await ensureWin32CredDir();\n const filePath = join(WIN32_UAT_DIR, win32SafeFileName(account));\n const encrypted = encryptData(data, key);\n await writeFile(filePath, encrypted);\n },\n\n async remove(_service, account) {\n try {\n await unlink(join(WIN32_UAT_DIR, win32SafeFileName(account)));\n } catch {\n // Already absent \u2013 fine.\n }\n },\n};\n\n// ---------------------------------------------------------------------------\n// Platform selection\n// ---------------------------------------------------------------------------\n\nfunction createBackend(): KeychainBackend {\n switch (process.platform) {\n case 'darwin':\n return darwinBackend;\n case 'linux':\n return linuxBackend;\n case 'win32':\n return win32Backend;\n default:\n log.warn(`unsupported platform \"${process.platform}\", falling back to macOS backend`);\n return darwinBackend;\n }\n}\n\nconst backend = createBackend();\n\n// ---------------------------------------------------------------------------\n// Public API \u2013 Credential operations\n// ---------------------------------------------------------------------------\n\n/**\n * Read the stored UAT for a given (appId, userOpenId) pair.\n * Returns `null` when no entry exists or the payload is unparseable.\n */\nexport async function getStoredToken(appId: string, userOpenId: string): Promise<StoredUAToken | null> {\n try {\n const json = await backend.get(KEYCHAIN_SERVICE, accountKey(appId, userOpenId));\n if (!json) return null;\n return JSON.parse(json) as StoredUAToken;\n } catch {\n return null;\n }\n}\n\n/**\n * Persist a UAT using the platform credential store.\n *\n * Overwrites any existing entry for the same (appId, userOpenId).\n */\nexport async function setStoredToken(token: StoredUAToken): Promise<void> {\n const key = accountKey(token.appId, token.userOpenId);\n const payload = JSON.stringify(token);\n await backend.set(KEYCHAIN_SERVICE, key, payload);\n log.info(`saved UAT for ${token.userOpenId} (at:${maskToken(token.accessToken)})`);\n}\n\n/**\n * Remove a stored UAT from the credential store.\n */\nexport async function removeStoredToken(appId: string, userOpenId: string): Promise<void> {\n await backend.remove(KEYCHAIN_SERVICE, accountKey(appId, userOpenId));\n log.info(`removed UAT for ${userOpenId}`);\n}\n\n// ---------------------------------------------------------------------------\n// Token validity check\n// ---------------------------------------------------------------------------\n\n/**\n * Determine the freshness of a stored token.\n *\n * - `\"valid\"` \u2013 access_token is still good (expires > 5 min from now)\n * - `\"needs_refresh\"` \u2013 access_token expired/expiring but refresh_token is valid\n * - `\"expired\"` \u2013 both tokens are expired; re-authorization required\n */\nexport function tokenStatus(token: StoredUAToken): 'valid' | 'needs_refresh' | 'expired' {\n const now = Date.now();\n if (now < token.expiresAt - REFRESH_AHEAD_MS) {\n return 'valid';\n }\n if (now < token.refreshExpiresAt) {\n return 'needs_refresh';\n }\n return 'expired';\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 * UAT (User Access Token) persistent storage with cross-platform support.\n *\n * Stores OAuth token data using OS-native credential services so that tokens\n * survive process restarts without introducing plain-text local files.\n *\n * Platform backends:\n * macOS \u2013 Keychain Access via `security` CLI\n * Linux \u2013 AES-256-GCM encrypted files (XDG_DATA_HOME)\n * Windows \u2013 AES-256-GCM encrypted files (%LOCALAPPDATA%)\n *\n * Storage layout:\n * Service = \"openclaw-feishu-uat\"\n * Account = \"{appId}:{userOpenId}\"\n * Password = JSON-serialised StoredUAToken\n */\n\nimport { execFile as execFileCb } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { mkdir, unlink, readFile, writeFile, chmod } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { randomBytes, createCipheriv, createDecipheriv } from 'node:crypto';\nimport { larkLogger } from './lark-logger';\n\nconst log = larkLogger('core/token-store');\n\nconst execFile = promisify(execFileCb);\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface StoredUAToken {\n userOpenId: string;\n appId: string;\n accessToken: string;\n refreshToken: string;\n expiresAt: number; // Unix ms \u2013 access_token expiry\n refreshExpiresAt: number; // Unix ms \u2013 refresh_token expiry\n scope: string;\n grantedAt: number; // Unix ms \u2013 original grant time\n domain?: string; // \u98DE\u4E66\u9879\u76EE\u73AF\u5883\u6807\u8BC6\uFF08feishu/meegle/\u81EA\u5B9A\u4E49URL\uFF09\uFF0C\u98DE\u4E66 Open API token \u4E0D\u4F7F\u7528\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst KEYCHAIN_SERVICE = 'openclaw-feishu-uat';\n\n/** Refresh proactively when access_token expires within this window. */\nconst REFRESH_AHEAD_MS = 5 * 60 * 1000; // 5 minutes\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction accountKey(appId: string, userOpenId: string): string {\n return `${appId}:${userOpenId}`;\n}\n\n/** Mask a token for safe logging: only the last 4 chars are visible. */\nexport function maskToken(token: string): string {\n if (token.length <= 8) return '****';\n return `****${token.slice(-4)}`;\n}\n\n// ---------------------------------------------------------------------------\n// Backend interface\n// ---------------------------------------------------------------------------\n\ninterface KeychainBackend {\n get(service: string, account: string): Promise<string | null>;\n set(service: string, account: string, data: string): Promise<void>;\n remove(service: string, account: string): Promise<void>;\n}\n\n// ---------------------------------------------------------------------------\n// macOS backend \u2013 Keychain Access via `security` CLI\n// ---------------------------------------------------------------------------\n\nconst darwinBackend: KeychainBackend = {\n async get(service, account) {\n try {\n const { stdout } = await execFile('security', ['find-generic-password', '-s', service, '-a', account, '-w']);\n return stdout.trim() || null;\n } catch {\n return null;\n }\n },\n\n async set(service, account, data) {\n // Delete first \u2013 `add-generic-password` fails if the item already exists.\n try {\n await execFile('security', ['delete-generic-password', '-s', service, '-a', account]);\n } catch {\n // Not found \u2013 fine.\n }\n await execFile('security', ['add-generic-password', '-s', service, '-a', account, '-w', data]);\n },\n\n async remove(service, account) {\n try {\n await execFile('security', ['delete-generic-password', '-s', service, '-a', account]);\n } catch {\n // Already absent \u2013 fine.\n }\n },\n};\n\n// ---------------------------------------------------------------------------\n// Linux backend \u2013 AES-256-GCM encrypted files (XDG Base Directory)\n//\n// Headless Linux servers typically lack D-Bus / GNOME Keyring, so we store\n// tokens as AES-256-GCM encrypted files instead of using `secret-tool`.\n//\n// Storage path: ${XDG_DATA_HOME:-~/.local/share}/openclaw-feishu-uat/\n// ---------------------------------------------------------------------------\n\nconst LINUX_UAT_DIR = join(process.env.XDG_DATA_HOME || join(homedir(), '.local', 'share'), 'openclaw-feishu-uat');\nconst MASTER_KEY_PATH = join(LINUX_UAT_DIR, 'master.key');\nconst MASTER_KEY_BYTES = 32; // AES-256\nconst IV_BYTES = 12; // GCM recommended\nconst TAG_BYTES = 16; // GCM auth tag\n\n/** Convert account key to a filesystem-safe filename. */\nfunction linuxSafeFileName(account: string): string {\n return account.replace(/[^a-zA-Z0-9._-]/g, '_') + '.enc';\n}\n\n/** Ensure the credentials directory exists with mode 0700. */\nasync function ensureLinuxCredDir(): Promise<void> {\n await mkdir(LINUX_UAT_DIR, { recursive: true, mode: 0o700 });\n}\n\n/**\n * Load or create the 32-byte master key.\n *\n * On first run, generates a random key and writes it to disk (mode 0600).\n * On subsequent runs, reads the existing key file.\n */\nasync function getMasterKey(): Promise<Buffer> {\n try {\n const key = await readFile(MASTER_KEY_PATH);\n if (key.length === MASTER_KEY_BYTES) return key;\n log.warn('master key has unexpected length, regenerating');\n } catch (err: unknown) {\n if (!(err instanceof Error) || (err as NodeJS.ErrnoException).code !== 'ENOENT') {\n log.warn(`failed to read master key: ${err instanceof Error ? err.message : err}`);\n }\n }\n\n await ensureLinuxCredDir();\n const key = randomBytes(MASTER_KEY_BYTES);\n await writeFile(MASTER_KEY_PATH, key, { mode: 0o600 });\n await chmod(MASTER_KEY_PATH, 0o600);\n log.info('generated new master key for encrypted file storage');\n return key;\n}\n\n/** AES-256-GCM encrypt. Returns [12-byte IV][16-byte tag][ciphertext]. */\nfunction encryptData(plaintext: string, key: Buffer): Buffer {\n const iv = randomBytes(IV_BYTES);\n const cipher = createCipheriv('aes-256-gcm', key, iv);\n const enc = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);\n return Buffer.concat([iv, cipher.getAuthTag(), enc]);\n}\n\n/** AES-256-GCM decrypt. Returns plaintext or `null` on failure. */\nfunction decryptData(data: Buffer, key: Buffer): string | null {\n if (data.length < IV_BYTES + TAG_BYTES) return null;\n try {\n const iv = data.subarray(0, IV_BYTES);\n const tag = data.subarray(IV_BYTES, IV_BYTES + TAG_BYTES);\n const enc = data.subarray(IV_BYTES + TAG_BYTES);\n const decipher = createDecipheriv('aes-256-gcm', key, iv);\n decipher.setAuthTag(tag);\n return Buffer.concat([decipher.update(enc), decipher.final()]).toString('utf8');\n } catch {\n return null;\n }\n}\n\nconst linuxBackend: KeychainBackend = {\n async get(_service, account) {\n try {\n const key = await getMasterKey();\n const data = await readFile(join(LINUX_UAT_DIR, linuxSafeFileName(account)));\n return decryptData(data, key);\n } catch {\n return null;\n }\n },\n\n async set(_service, account, data) {\n const key = await getMasterKey();\n await ensureLinuxCredDir();\n const filePath = join(LINUX_UAT_DIR, linuxSafeFileName(account));\n const encrypted = encryptData(data, key);\n await writeFile(filePath, encrypted, { mode: 0o600 });\n await chmod(filePath, 0o600);\n },\n\n async remove(_service, account) {\n try {\n await unlink(join(LINUX_UAT_DIR, linuxSafeFileName(account)));\n } catch {\n // Already absent \u2013 fine.\n }\n },\n};\n\n// ---------------------------------------------------------------------------\n// Windows backend \u2013 AES-256-GCM encrypted files\n//\n// Replaces the previous DPAPI-via-PowerShell approach which was unreliable\n// (PowerShell cold-start latency, execution policy restrictions, cmd.exe\n// command-line length limits, and unavailability in containers).\n//\n// Uses the same AES-256-GCM scheme as the Linux backend with its own\n// independent storage directory and master key.\n//\n// Storage path: %LOCALAPPDATA%\\openclaw-feishu-uat\\\n// ---------------------------------------------------------------------------\n\nconst WIN32_UAT_DIR = join(\n process.env.LOCALAPPDATA ?? join(process.env.USERPROFILE ?? homedir(), 'AppData', 'Local'),\n KEYCHAIN_SERVICE,\n);\nconst WIN32_MASTER_KEY_PATH = join(WIN32_UAT_DIR, 'master.key');\n\n/** Convert account key to a filesystem-safe filename (whitelist approach). */\nfunction win32SafeFileName(account: string): string {\n return account.replace(/[^a-zA-Z0-9._-]/g, '_') + '.enc';\n}\n\nasync function ensureWin32CredDir(): Promise<void> {\n await mkdir(WIN32_UAT_DIR, { recursive: true });\n}\n\nasync function getWin32MasterKey(): Promise<Buffer> {\n try {\n const key = await readFile(WIN32_MASTER_KEY_PATH);\n if (key.length === MASTER_KEY_BYTES) return key;\n log.warn('win32 master key has unexpected length, regenerating');\n } catch (err: unknown) {\n if (!(err instanceof Error) || (err as NodeJS.ErrnoException).code !== 'ENOENT') {\n log.warn(`failed to read win32 master key: ${err instanceof Error ? err.message : err}`);\n }\n }\n\n await ensureWin32CredDir();\n const key = randomBytes(MASTER_KEY_BYTES);\n await writeFile(WIN32_MASTER_KEY_PATH, key);\n log.info('generated new master key for win32 encrypted file storage');\n return key;\n}\n\nconst win32Backend: KeychainBackend = {\n async get(_service, account) {\n try {\n const key = await getWin32MasterKey();\n const data = await readFile(join(WIN32_UAT_DIR, win32SafeFileName(account)));\n return decryptData(data, key);\n } catch {\n return null;\n }\n },\n\n async set(_service, account, data) {\n const key = await getWin32MasterKey();\n await ensureWin32CredDir();\n const filePath = join(WIN32_UAT_DIR, win32SafeFileName(account));\n const encrypted = encryptData(data, key);\n await writeFile(filePath, encrypted);\n },\n\n async remove(_service, account) {\n try {\n await unlink(join(WIN32_UAT_DIR, win32SafeFileName(account)));\n } catch {\n // Already absent \u2013 fine.\n }\n },\n};\n\n// ---------------------------------------------------------------------------\n// Platform selection\n// ---------------------------------------------------------------------------\n\nfunction createBackend(): KeychainBackend {\n switch (process.platform) {\n case 'darwin':\n return darwinBackend;\n case 'linux':\n return linuxBackend;\n case 'win32':\n return win32Backend;\n default:\n log.warn(`unsupported platform \"${process.platform}\", falling back to macOS backend`);\n return darwinBackend;\n }\n}\n\nconst backend = createBackend();\n\n// ---------------------------------------------------------------------------\n// Public API \u2013 Credential operations\n// ---------------------------------------------------------------------------\n\n/**\n * Read the stored UAT for a given (appId, userOpenId) pair.\n * Returns `null` when no entry exists or the payload is unparseable.\n */\nexport async function getStoredToken(appId: string, userOpenId: string): Promise<StoredUAToken | null> {\n try {\n const json = await backend.get(KEYCHAIN_SERVICE, accountKey(appId, userOpenId));\n if (!json) return null;\n return JSON.parse(json) as StoredUAToken;\n } catch {\n return null;\n }\n}\n\n/**\n * Persist a UAT using the platform credential store.\n *\n * Overwrites any existing entry for the same (appId, userOpenId).\n */\nexport async function setStoredToken(token: StoredUAToken): Promise<void> {\n const key = accountKey(token.appId, token.userOpenId);\n const payload = JSON.stringify(token);\n await backend.set(KEYCHAIN_SERVICE, key, payload);\n log.info(`saved UAT for ${token.userOpenId} (at:${maskToken(token.accessToken)})`);\n}\n\n/**\n * Remove a stored UAT from the credential store.\n */\nexport async function removeStoredToken(appId: string, userOpenId: string): Promise<void> {\n await backend.remove(KEYCHAIN_SERVICE, accountKey(appId, userOpenId));\n log.info(`removed UAT for ${userOpenId}`);\n}\n\n// ---------------------------------------------------------------------------\n// Token validity check\n// ---------------------------------------------------------------------------\n\n/**\n * Determine the freshness of a stored token.\n *\n * - `\"valid\"` \u2013 access_token is still good (expires > 5 min from now)\n * - `\"needs_refresh\"` \u2013 access_token expired/expiring but refresh_token is valid\n * - `\"expired\"` \u2013 both tokens are expired; re-authorization required\n */\nexport function tokenStatus(token: StoredUAToken): 'valid' | 'needs_refresh' | 'expired' {\n const now = Date.now();\n if (now < token.expiresAt - REFRESH_AHEAD_MS) {\n return 'valid';\n }\n if (now < token.refreshExpiresAt) {\n return 'needs_refresh';\n }\n return 'expired';\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["key"]
7
7
  }
@@ -3,8 +3,10 @@ import { isRecord, unwrapJsonRpcResult } from "../shared.js";
3
3
  import { createToolContext, formatToolResult } from "../../helpers.js";
4
4
  import { getProjectStoredToken, setProjectStoredToken, projectTokenStatus, getProjectClientId } from "../../../core/project-token-store.js";
5
5
  import { refreshProjectToken } from "../../../core/project-auth.js";
6
+ import { getProjectDomainFromConfig } from "./endpoint.js";
6
7
  import { getTicket } from "../../../core/lark-ticket.js";
7
8
  import { larkLogger } from "../../../core/lark-logger.js";
9
+ import { LarkClient } from "../../../core/lark-client.js";
8
10
  const log = larkLogger("tools/mcp/project");
9
11
  async function callProjectMcpTool(name, args, toolCallId, accessToken, endpoint) {
10
12
  const body = {
@@ -55,7 +57,14 @@ function registerProjectMcpTool(api, config) {
55
57
  error: "\u65E0\u6CD5\u83B7\u53D6\u5F53\u524D\u7528\u6237\u8EAB\u4EFD\uFF0C\u8BF7\u5728\u98DE\u4E66\u5BF9\u8BDD\u4E2D\u4F7F\u7528\u6B64\u5DE5\u5177\u3002"
56
58
  });
57
59
  }
58
- const clientId = await getProjectClientId(senderOpenId);
60
+ const currentDomain = getProjectDomainFromConfig(LarkClient.runtime.config.loadConfig());
61
+ if (!currentDomain) {
62
+ return formatToolResult({
63
+ error: "project_auth_required",
64
+ message: "\u98DE\u4E66\u9879\u76EE\u73AF\u5883\u672A\u914D\u7F6E\u3002\u8BF7\u5148\u8C03\u7528 feishu_project_oauth \u5DE5\u5177\u7684 authorize \u64CD\u4F5C\u9009\u62E9\u73AF\u5883\u5E76\u5B8C\u6210\u6388\u6743\u3002"
65
+ });
66
+ }
67
+ const clientId = await getProjectClientId(senderOpenId, currentDomain);
59
68
  if (!clientId) {
60
69
  return formatToolResult({
61
70
  error: "project_auth_required",
@@ -69,6 +78,12 @@ function registerProjectMcpTool(api, config) {
69
78
  message: "\u98DE\u4E66\u9879\u76EE\u672A\u6388\u6743\u3002\u8BF7\u5148\u8C03\u7528 feishu_project_oauth \u5DE5\u5177\u5B8C\u6210\u6388\u6743\u3002"
70
79
  });
71
80
  }
81
+ if (token.domain && currentDomain && token.domain !== currentDomain) {
82
+ return formatToolResult({
83
+ error: "project_auth_required",
84
+ message: `\u5F53\u524D\u98DE\u4E66\u9879\u76EE\u73AF\u5883\u5DF2\u5207\u6362\u4E3A\u300C${currentDomain}\u300D\uFF0C\u4E0E\u6388\u6743\u65F6\u7684\u73AF\u5883\u300C${token.domain}\u300D\u4E0D\u5339\u914D\u3002\u8BF7\u8C03\u7528 feishu_project_oauth \u5DE5\u5177\u7684 authorize \u64CD\u4F5C\u91CD\u65B0\u6388\u6743\u3002`
85
+ });
86
+ }
72
87
  const status = projectTokenStatus(token);
73
88
  if (status === "expired") {
74
89
  return formatToolResult({
@@ -85,7 +100,8 @@ function registerProjectMcpTool(api, config) {
85
100
  accessToken: refreshed.access_token,
86
101
  refreshToken: refreshed.refresh_token ?? token.refreshToken,
87
102
  expiresAt: Date.now() + (refreshed.expires_in ?? 7200) * 1e3,
88
- scope: refreshed.scope ?? token.scope
103
+ scope: refreshed.scope ?? token.scope,
104
+ domain: currentDomain
89
105
  };
90
106
  await setProjectStoredToken(token);
91
107
  toolLog.debug?.("project token refreshed");
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/tools/mcp/project/tools.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 \u5DE5\u5177\u6CE8\u518C\n *\n * \u6BCF\u4E2A\u5DE5\u5177\u4F7F\u7528\u98DE\u4E66\u9879\u76EE\u4E13\u5C5E\u7684 MCP \u7AEF\u70B9\u548C\u72EC\u7ACB\u7684 OAuth token\u3002\n * \u4E0D\u901A\u8FC7 registerMcpTool\uFF08\u4F7F\u7528\u98DE\u4E66 Open API UAT\uFF09\uFF0C\u800C\u662F\u4F7F\u7528\n * registerProjectMcpTool \u5305\u88C5\u51FD\u6570\u3002\n *\n * \u5DE5\u5177\u53C2\u6570\u7B7E\u540D\u53C2\u7167\u98DE\u4E66\u9879\u76EE MCP Server \u7684\u5B9E\u9645\u5B9A\u4E49\u3002\n */\n\nimport type { OpenClawPluginApi } from 'openclaw/plugin-sdk';\nimport type { TSchema } from '@sinclair/typebox';\nimport { Type } from '@sinclair/typebox';\nimport type { McpRpcResponse } from '../shared';\nimport { isRecord, unwrapJsonRpcResult } from '../shared';\nimport { createToolContext, formatToolResult } from '../../helpers';\nimport { getProjectStoredToken, setProjectStoredToken, projectTokenStatus, getProjectClientId } from '../../../core/project-token-store';\nimport { refreshProjectToken } from '../../../core/project-auth';\nimport { getProjectMcpEndpoint } from './endpoint';\nimport { getTicket } from '../../../core/lark-ticket';\nimport { larkLogger } from '../../../core/lark-logger';\n\nconst log = larkLogger('tools/mcp/project');\n\n// ---------------------------------------------------------------------------\n// \u98DE\u4E66\u9879\u76EE MCP \u4E13\u7528 JSON-RPC \u5BA2\u6237\u7AEF\uFF08\u4F7F\u7528\u6807\u51C6 OAuth Bearer \u8BA4\u8BC1\uFF09\n// ---------------------------------------------------------------------------\n\nasync function callProjectMcpTool(\n name: string,\n args: Record<string, unknown>,\n toolCallId: string,\n accessToken: string,\n endpoint: string,\n): Promise<unknown> {\n const body = {\n jsonrpc: '2.0',\n id: toolCallId,\n method: 'tools/call',\n params: { name, arguments: args },\n };\n\n const res = await fetch(endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${accessToken}`,\n },\n body: JSON.stringify(body),\n });\n\n const text = await res.text();\n if (!res.ok) {\n throw new Error(`MCP HTTP ${res.status} ${res.statusText}: ${text.slice(0, 4000)}`);\n }\n\n let data: McpRpcResponse;\n try {\n data = JSON.parse(text) as McpRpcResponse;\n } catch {\n throw new Error(`MCP \u8FD4\u56DE\u975E JSON\uFF1A${text.slice(0, 4000)}`);\n }\n\n if ('error' in data) {\n throw new Error(`MCP error ${data.error.code}: ${data.error.message}`);\n }\n\n return unwrapJsonRpcResult(data.result);\n}\n\n// ---------------------------------------------------------------------------\n// \u98DE\u4E66\u9879\u76EE MCP \u5DE5\u5177\u901A\u7528\u6CE8\u518C\u51FD\u6570\n// ---------------------------------------------------------------------------\n\ninterface ProjectMcpToolConfig {\n name: string;\n mcpToolName: string;\n label: string;\n description: string;\n schema: TSchema;\n endpoint: string | (() => string);\n}\n\nexport function registerProjectMcpTool(\n api: OpenClawPluginApi,\n config: ProjectMcpToolConfig,\n): void {\n const { log: toolLog } = createToolContext(api, config.name);\n\n api.registerTool(\n {\n name: config.name,\n label: config.label,\n description: config.description,\n parameters: config.schema,\n async execute(toolCallId, params) {\n const p = params as Record<string, unknown>;\n try {\n const startTime = Date.now();\n\n const ticket = getTicket();\n const senderOpenId = ticket?.senderOpenId;\n if (!senderOpenId) {\n return formatToolResult({\n error: '\u65E0\u6CD5\u83B7\u53D6\u5F53\u524D\u7528\u6237\u8EAB\u4EFD\uFF0C\u8BF7\u5728\u98DE\u4E66\u5BF9\u8BDD\u4E2D\u4F7F\u7528\u6B64\u5DE5\u5177\u3002',\n });\n }\n\n const clientId = await getProjectClientId(senderOpenId);\n if (!clientId) {\n return formatToolResult({\n error: 'project_auth_required',\n message: '\u98DE\u4E66\u9879\u76EE\u672A\u6388\u6743\u3002\u8BF7\u5148\u8C03\u7528 feishu_project_oauth \u5DE5\u5177\u5B8C\u6210\u6388\u6743\u3002',\n });\n }\n\n let token = await getProjectStoredToken(clientId, senderOpenId);\n if (!token) {\n return formatToolResult({\n error: 'project_auth_required',\n message: '\u98DE\u4E66\u9879\u76EE\u672A\u6388\u6743\u3002\u8BF7\u5148\u8C03\u7528 feishu_project_oauth \u5DE5\u5177\u5B8C\u6210\u6388\u6743\u3002',\n });\n }\n\n const status = projectTokenStatus(token);\n if (status === 'expired') {\n return formatToolResult({\n error: 'project_auth_required',\n message: '\u98DE\u4E66\u9879\u76EE\u6388\u6743\u5DF2\u8FC7\u671F\u3002\u8BF7\u8C03\u7528 feishu_project_oauth \u5DE5\u5177\u7684 authorize \u64CD\u4F5C\u91CD\u65B0\u6388\u6743\u3002',\n });\n }\n\n // Token \u5373\u5C06\u8FC7\u671F\u6216\u5DF2\u8FC7\u671F\u4F46 refresh_token \u4ECD\u6709\u6548 \u2192 \u81EA\u52A8\u5237\u65B0\n if (status === 'needs_refresh') {\n try {\n const resolvedEp = typeof config.endpoint === 'function' ? config.endpoint() : config.endpoint;\n const refreshed = await refreshProjectToken(resolvedEp, token.refreshToken, clientId);\n token = {\n ...token,\n accessToken: refreshed.access_token,\n refreshToken: refreshed.refresh_token ?? token.refreshToken,\n expiresAt: Date.now() + (refreshed.expires_in ?? 7200) * 1000,\n scope: refreshed.scope ?? token.scope,\n };\n await setProjectStoredToken(token);\n toolLog.debug?.('project token refreshed');\n } catch (refreshErr) {\n toolLog.error(`project token refresh failed: ${refreshErr}`);\n return formatToolResult({\n error: 'project_auth_required',\n message: '\u98DE\u4E66\u9879\u76EE token \u5237\u65B0\u5931\u8D25\uFF0C\u8BF7\u8C03\u7528 feishu_project_oauth \u5DE5\u5177\u7684 authorize \u64CD\u4F5C\u91CD\u65B0\u6388\u6743\u3002',\n });\n }\n }\n\n const resolvedEndpoint = typeof config.endpoint === 'function' ? config.endpoint() : config.endpoint;\n\n const result = await callProjectMcpTool(\n config.mcpToolName,\n p,\n toolCallId,\n token.accessToken,\n resolvedEndpoint,\n );\n\n const duration = Date.now() - startTime;\n toolLog.debug?.(`${config.mcpToolName} succeeded in ${duration}ms`);\n\n // MCP \u8FD4\u56DE\u503C\u5904\u7406\uFF08\u540C registerMcpTool \u903B\u8F91\uFF09\n if (isRecord(result) && Array.isArray((result as Record<string, unknown>).content)) {\n const mcpContent = (result as Record<string, unknown>).content as Array<{\n type: string;\n text: string;\n }>;\n let details: unknown = result;\n if (mcpContent.length === 1 && mcpContent[0]?.type === 'text') {\n try {\n details = JSON.parse(mcpContent[0].text);\n } catch {\n // text is not JSON\n }\n }\n return {\n content: mcpContent.map((c) => ({\n type: 'text' as const,\n text: c.text,\n })),\n details,\n };\n }\n return formatToolResult(result);\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n toolLog.error(`${config.mcpToolName} failed: ${errMsg}`);\n return formatToolResult({\n error: errMsg,\n hint: '\u5982\u679C\u662F\u6388\u6743\u95EE\u9898\uFF0C\u8BF7\u8C03\u7528 feishu_project_oauth \u5DE5\u5177\u91CD\u65B0\u6388\u6743\u3002',\n });\n }\n },\n },\n { name: config.name },\n );\n}\n\n// ---------------------------------------------------------------------------\n// \u5177\u4F53\u5DE5\u5177\u6CE8\u518C\uFF08\u53C2\u6570\u7B7E\u540D\u5339\u914D\u98DE\u4E66\u9879\u76EE MCP Server \u5B9E\u9645\u5B9A\u4E49\uFF09\n// ---------------------------------------------------------------------------\n\nexport function registerProjectTools(api: OpenClawPluginApi, endpoint: string | (() => string)) {\n let count = 0;\n\n // list_todo \u2014 \u5F85\u529E/\u5DF2\u529E/\u8D85\u671F/\u672C\u5468\u5230\u671F\n registerProjectMcpTool(api, {\n name: 'feishu_project_list_todo',\n mcpToolName: 'list_todo',\n label: 'Feishu Project: \u5F85\u529E\u5217\u8868',\n description:\n '\u83B7\u53D6\u5F53\u524D\u7528\u6237\u7684\u98DE\u4E66\u9879\u76EE\u5DE5\u4F5C\u9879\u5217\u8868\u3002\u652F\u6301\u67E5\u8BE2\u7C7B\u578B\uFF1A' +\n 'todo(\u5F85\u529E)\u3001done(\u5DF2\u529E)\u3001overdue(\u5DF2\u8D85\u671F)\u3001this_week(\u672C\u5468\u5230\u671F)\u3002',\n schema: Type.Object({\n action: Type.String({\n description: '\u67E5\u8BE2\u7C7B\u578B\u3002todo \u4E3A\u5F85\u529E, done \u4E3A\u5DF2\u529E, overdue \u4E3A\u5DF2\u8D85\u671F, this_week \u4E3A\u672C\u5468\u5230\u671F',\n }),\n asset_key: Type.Optional(\n Type.String({ description: '\u5DE5\u4F5C\u533A key\uFF0C\u683C\u5F0F\u4E3A Asset_\uFF0C\u4EC5\u5728\u63A5\u53E3\u62A5\u9519\u9700\u8981\u9009\u62E9\u65F6\u4F20\u9012' }),\n ),\n page_num: Type.Optional(\n Type.Number({ description: '\u9875\u7801\uFF0C\u4ECE 1 \u5F00\u59CB\u9012\u589E\uFF0C\u6BCF\u9875 50 \u6761' }),\n ),\n }),\n endpoint,\n });\n count++;\n\n // search_by_mql \u2014 MQL \u67E5\u8BE2\n registerProjectMcpTool(api, {\n name: 'feishu_project_search_by_mql',\n mcpToolName: 'search_by_mql',\n label: 'Feishu Project: MQL \u641C\u7D22',\n description:\n '\u4F7F\u7528 MQL \u641C\u7D22\u98DE\u4E66\u9879\u76EE\u5DE5\u4F5C\u9879\u3002MQL \u57FA\u4E8E SQL \u8BED\u6CD5\u6269\u5C55\u3002' +\n '\u4F7F\u7528\u524D\u9700\u5148\u7528 get_workitem_info \u786E\u8BA4\u7A7A\u95F4\u548C\u5DE5\u4F5C\u9879\u7C7B\u578B\u3002' +\n 'select \u540E\u4F7F\u7528\u53EF\u8BFB\u6027\u5F3A\u7684\u5B57\u6BB5\u540D\u79F0\uFF08\u5982\"\u4EFB\u52A1\u540D\u79F0\"\u800C\u975E\"name\"\uFF09\u3002',\n schema: Type.Object({\n project_key: Type.String({\n description: '\u7A7A\u95F4 projectKey\u3001simpleName \u6216\u7A7A\u95F4\u540D',\n }),\n mql: Type.Optional(\n Type.String({\n description:\n 'MQL \u8BED\u53E5\u3002\u8BED\u6CD5\uFF1Aselect `\u5B57\u6BB5\u540D` from `\u7A7A\u95F4\u540D`.`\u5DE5\u4F5C\u9879\u540D` where `\u5B57\u6BB5\u540D` = \\'\u5B57\u6BB5\u503C\\'',\n }),\n ),\n helper: Type.Optional(\n Type.String({\n description: '\u5E2E\u52A9\u5DE5\u5177\u3002\u8F93\u5165 \"helper\" \u83B7\u53D6\u6240\u6709\u547D\u4EE4\u548C\u7528\u6CD5\u793A\u4F8B',\n }),\n ),\n session_id: Type.Optional(\n Type.String({ description: 'sessionID\uFF0C\u7528\u4E8E\u5206\u9875\u67E5\u8BE2\uFF0C\u4F20\u5165\u540E\u4E0D\u89E3\u6790 mql' }),\n ),\n group_pagination_list: Type.Optional(\n Type.Array(\n Type.Object({\n group_id: Type.Optional(Type.String({ description: '\u5206\u7EC4 ID' })),\n page_num: Type.Optional(Type.Number({ description: '\u9875\u7801\uFF0C\u4ECE 1 \u5F00\u59CB' })),\n }),\n ),\n ),\n }),\n endpoint,\n });\n count++;\n\n // get_workitem_info \u2014 \u83B7\u53D6\u5DE5\u4F5C\u9879\u7C7B\u578B\u7684\u5B57\u6BB5\u4E0E\u89D2\u8272\u4FE1\u606F\n registerProjectMcpTool(api, {\n name: 'feishu_project_get_workitem_info',\n mcpToolName: 'get_workitem_info',\n label: 'Feishu Project: \u5DE5\u4F5C\u9879\u7C7B\u578B\u4FE1\u606F',\n description: '\u83B7\u53D6\u4E00\u4E2A\u5DE5\u4F5C\u9879\u7C7B\u578B\u5177\u5907\u7684\u53EF\u7528\u5B57\u6BB5\u4E0E\u89D2\u8272\u4FE1\u606F\u3002',\n schema: Type.Object({\n project_key: Type.Optional(\n Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n ),\n work_item_type: Type.String({\n description: '\u5DE5\u4F5C\u9879\u7C7B\u578B\u7684\u7CFB\u7EDF\u6807\u8BC6\u6216\u540D\u79F0\uFF0C\u5982 story\u3001\u9700\u6C42\u3001issue\u3001\u7F3A\u9677\u7B49',\n }),\n }),\n endpoint,\n });\n count++;\n\n // get_workitem_brief \u2014 \u83B7\u53D6\u5DE5\u4F5C\u9879\u6982\u51B5\n registerProjectMcpTool(api, {\n name: 'feishu_project_get_workitem_brief',\n mcpToolName: 'get_workitem_brief',\n label: 'Feishu Project: \u5DE5\u4F5C\u9879\u6982\u51B5',\n description: '\u83B7\u53D6\u4E00\u4E2A\u5DE5\u4F5C\u9879\u5B9E\u4F8B\u7684\u6982\u51B5\u4FE1\u606F\u3002',\n schema: Type.Object({\n project_key: Type.Optional(\n Type.String({ description: '\u7A7A\u95F4 projectKey \u6216 simpleName' }),\n ),\n work_item_id: Type.String({\n description: '\u5DE5\u4F5C\u9879 ID \u6216\u540D\u79F0',\n }),\n fields: Type.Optional(\n Type.Array(Type.String({ description: '\u8981\u67E5\u8BE2\u7684 field_key \u6216 field_name' })),\n ),\n }),\n endpoint,\n });\n count++;\n\n // get_workitem_field_meta \u2014 \u83B7\u53D6\u521B\u5EFA\u5DE5\u4F5C\u9879\u5143\u4FE1\u606F\n registerProjectMcpTool(api, {\n name: 'feishu_project_get_workitem_field_meta',\n mcpToolName: 'get_workitem_field_meta',\n label: 'Feishu Project: \u5B57\u6BB5\u5143\u4FE1\u606F',\n description: '\u83B7\u53D6\u521B\u5EFA\u5DE5\u4F5C\u9879\u65F6\u7684\u5B57\u6BB5\u5143\u4FE1\u606F\uFF08\u5B57\u6BB5\u540D\u3001\u7C7B\u578B\u3001\u9009\u9879\u7B49\uFF09\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n work_item_type_key: Type.String({ description: '\u5DE5\u4F5C\u9879\u7C7B\u578B key' }),\n }),\n endpoint,\n });\n count++;\n\n // create_workitem \u2014 \u521B\u5EFA\u5DE5\u4F5C\u9879\n registerProjectMcpTool(api, {\n name: 'feishu_project_create_workitem',\n mcpToolName: 'create_workitem',\n label: 'Feishu Project: \u521B\u5EFA\u5DE5\u4F5C\u9879',\n description: '\u521B\u5EFA\u4E00\u6761\u5DE5\u4F5C\u9879\u5B9E\u4F8B\u3002\u521B\u5EFA\u6210\u529F\u540E\u83B7\u5F97\u8BE6\u60C5\u9875 URL\u3002',\n schema: Type.Object({\n project_key: Type.Optional(\n Type.String({ description: '\u7A7A\u95F4 projectKey \u6216 simpleName' }),\n ),\n work_item_type: Type.String({\n description: '\u5DE5\u4F5C\u9879\u7C7B\u578B',\n }),\n fields: Type.Optional(\n Type.Array(\n Type.Object({\n field_key: Type.String({ description: '\u5B57\u6BB5 key' }),\n field_value: Type.String({\n description: '\u5B57\u6BB5\u503C\u3002\u65F6\u95F4\u7C7B\u9700\u4F20 16 \u4F4D unix \u6BEB\u79D2\u65F6\u95F4\u6233\uFF0C\u4EBA\u5458\u7C7B\u9700\u7528\u82F1\u6587\u9017\u53F7\u533A\u9694',\n }),\n }),\n ),\n ),\n }),\n endpoint,\n });\n count++;\n\n // update_field \u2014 \u4FEE\u6539\u5DE5\u4F5C\u9879\u5B57\u6BB5\n registerProjectMcpTool(api, {\n name: 'feishu_project_update_field',\n mcpToolName: 'update_field',\n label: 'Feishu Project: \u4FEE\u6539\u5B57\u6BB5',\n description: '\u4FEE\u6539\u6307\u5B9A\u5DE5\u4F5C\u9879\u5B9E\u4F8B\u7684\u5B57\u6BB5\u503C\uFF0C\u652F\u6301\u4E00\u6B21\u4FEE\u6539\u591A\u4E2A\u5B57\u6BB5\u3002',\n schema: Type.Object({\n project_key: Type.Optional(\n Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n ),\n work_item_id: Type.String({\n description: '\u5DE5\u4F5C\u9879 ID \u6216\u540D\u79F0',\n }),\n fields: Type.Optional(\n Type.Array(\n Type.Object({\n field_key: Type.String({ description: '\u5B57\u6BB5 key' }),\n field_value: Type.String({\n description: '\u5B57\u6BB5\u503C\u3002\u65F6\u95F4\u7C7B\u9700\u4F20 16 \u4F4D unix \u6BEB\u79D2\u65F6\u95F4\u6233\uFF0C\u4EBA\u5458\u7C7B\u9700\u7528\u82F1\u6587\u9017\u53F7\u533A\u9694',\n }),\n }),\n ),\n ),\n }),\n endpoint,\n });\n count++;\n\n // add_comment \u2014 \u6DFB\u52A0\u8BC4\u8BBA\n registerProjectMcpTool(api, {\n name: 'feishu_project_add_comment',\n mcpToolName: 'add_comment',\n label: 'Feishu Project: \u6DFB\u52A0\u8BC4\u8BBA',\n description: '\u5728\u6307\u5B9A\u5DE5\u4F5C\u9879\u7684\u8BC4\u8BBA/\u5907\u6CE8\u9875\u6DFB\u52A0\u4E00\u6761\u8BC4\u8BBA\uFF0C\u652F\u6301 markdown \u683C\u5F0F\u3002',\n schema: Type.Object({\n project_key: Type.Optional(\n Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n ),\n work_item_id: Type.String({\n description: '\u5DE5\u4F5C\u9879 ID \u6216\u540D\u79F0',\n }),\n work_item_type: Type.Optional(\n Type.String({ description: '\u5DE5\u4F5C\u9879\u7C7B\u578B' }),\n ),\n comment_content: Type.String({\n description: '\u8BC4\u8BBA\u5185\u5BB9\uFF0C\u652F\u6301 markdown \u683C\u5F0F',\n }),\n url: Type.Optional(\n Type.String({ description: '\u5DE5\u4F5C\u9879 URL\uFF0C\u53EF\u4ECE\u4E2D\u89E3\u6790 project_key \u7B49\u4FE1\u606F' }),\n ),\n }),\n endpoint,\n });\n count++;\n\n // finish_node \u2014 \u5B8C\u6210\u8282\u70B9/\u6D41\u8F6C\u72B6\u6001\n registerProjectMcpTool(api, {\n name: 'feishu_project_finish_node',\n mcpToolName: 'finish_node',\n label: 'Feishu Project: \u6D41\u8F6C\u72B6\u6001',\n description: '\u5B8C\u6210\u67D0\u4E2A\u8282\u70B9\uFF0C\u6D41\u8F6C\u5DE5\u4F5C\u9879\u5230\u4E0B\u4E00\u4E2A\u72B6\u6001\u3002',\n schema: Type.Object({\n project_key: Type.Optional(\n Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n ),\n work_item_id: Type.String({\n description: '\u5DE5\u4F5C\u9879 ID \u6216\u540D\u79F0',\n }),\n node_id: Type.String({\n description: '\u8981\u5B8C\u6210\u6D41\u8F6C\u7684\u8282\u70B9 ID \u6216\u8282\u70B9\u540D\u79F0',\n }),\n }),\n endpoint,\n });\n count++;\n\n // get_node_detail \u2014 \u83B7\u53D6\u8282\u70B9\u8BE6\u60C5\n registerProjectMcpTool(api, {\n name: 'feishu_project_get_node_detail',\n mcpToolName: 'get_node_detail',\n label: 'Feishu Project: \u8282\u70B9\u8BE6\u60C5',\n description: '\u83B7\u53D6\u6307\u5B9A\u8282\u70B9\u7684\u5173\u952E\u4FE1\u606F\uFF0C\u5305\u62EC\u8282\u70B9\u4FE1\u606F\u3001\u5B50\u9879\u4FE1\u606F\u3001\u81EA\u5B9A\u4E49\u5B57\u6BB5\u4FE1\u606F\u3002',\n schema: Type.Object({\n project_key: Type.Optional(\n Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n ),\n work_item_id: Type.String({ description: '\u5DE5\u4F5C\u9879 ID' }),\n node_id: Type.String({ description: '\u8282\u70B9\u540D\u79F0\u6216\u8282\u70B9 ID' }),\n node_ids: Type.Optional(\n Type.Array(Type.String({ description: '\u8282\u70B9\u540D\u79F0\u6216\u8282\u70B9 ID \u5217\u8868' })),\n ),\n }),\n endpoint,\n });\n count++;\n\n // get_transitable_statuses \u2014 \u83B7\u53D6\u53EF\u6D41\u8F6C\u72B6\u6001\n registerProjectMcpTool(api, {\n name: 'feishu_project_get_transitable_statuses',\n mcpToolName: 'get_transitable_statuses',\n label: 'Feishu Project: \u53EF\u6D41\u8F6C\u72B6\u6001',\n description: '\u83B7\u53D6\u72B6\u6001\u6D41\u5DE5\u4F5C\u9879\u7684\u53EF\u6D41\u8F6C\u72B6\u6001\u5217\u8868\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n work_item_id: Type.String({ description: '\u5DE5\u4F5C\u9879 ID' }),\n work_item_type_key: Type.String({ description: '\u5DE5\u4F5C\u9879\u7C7B\u578B key' }),\n user_key: Type.String({ description: '\u7528\u6237 userKey' }),\n }),\n endpoint,\n });\n count++;\n\n // list_workitem_types \u2014 \u83B7\u53D6\u5DE5\u4F5C\u9879\u7C7B\u578B\u5217\u8868\n registerProjectMcpTool(api, {\n name: 'feishu_project_list_workitem_types',\n mcpToolName: 'list_workitem_types',\n label: 'Feishu Project: \u5DE5\u4F5C\u9879\u7C7B\u578B\u5217\u8868',\n description: '\u83B7\u53D6\u7A7A\u95F4\u4E0B\u7684\u6240\u6709\u5DE5\u4F5C\u9879\u7C7B\u578B\u5217\u8868\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n }),\n endpoint,\n });\n count++;\n\n // search_project_info \u2014 \u67E5\u8BE2\u7A7A\u95F4\u4FE1\u606F\n registerProjectMcpTool(api, {\n name: 'feishu_project_search_project_info',\n mcpToolName: 'search_project_info',\n label: 'Feishu Project: \u67E5\u8BE2\u7A7A\u95F4',\n description: '\u67E5\u8BE2\u98DE\u4E66\u9879\u76EE\u7A7A\u95F4\u57FA\u7840\u4FE1\u606F\u3002',\n schema: Type.Object({\n project_key: Type.String({\n description: '\u7A7A\u95F4 projectKey\u3001simpleName \u6216\u7A7A\u95F4\u540D\u79F0',\n }),\n }),\n endpoint,\n });\n count++;\n\n // search_user_info \u2014 \u67E5\u8BE2\u7528\u6237\u4FE1\u606F\n registerProjectMcpTool(api, {\n name: 'feishu_project_search_user_info',\n mcpToolName: 'search_user_info',\n label: 'Feishu Project: \u67E5\u8BE2\u7528\u6237',\n description: '\u6279\u91CF\u67E5\u8BE2\u7528\u6237\u57FA\u7840\u4FE1\u606F\uFF0C\u6700\u591A 20 \u4E2A\u3002',\n schema: Type.Object({\n user_keys: Type.Array(\n Type.String({ description: 'userKey\u3001Email \u6216\u540D\u5B57' }),\n ),\n project_key: Type.Optional(\n Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n ),\n }),\n endpoint,\n });\n count++;\n\n // list_workitem_comments \u2014 \u67E5\u8BE2\u8BC4\u8BBA\u5217\u8868\n registerProjectMcpTool(api, {\n name: 'feishu_project_list_workitem_comments',\n mcpToolName: 'list_workitem_comments',\n label: 'Feishu Project: \u8BC4\u8BBA\u5217\u8868',\n description: '\u67E5\u8BE2\u5DE5\u4F5C\u9879\u8BC4\u8BBA\u5217\u8868\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n work_item_id: Type.String({ description: '\u5DE5\u4F5C\u9879 ID \u6216\u540D\u79F0' }),\n page_num: Type.Optional(Type.Number({ description: '\u9875\u7801\uFF0C\u9ED8\u8BA4 1' })),\n start_time: Type.Optional(Type.Number({ description: '\u5F00\u59CB\u65F6\u95F4\uFF0CUnix \u6BEB\u79D2\u65F6\u95F4\u6233' })),\n end_time: Type.Optional(Type.Number({ description: '\u7ED3\u675F\u65F6\u95F4\uFF0CUnix \u6BEB\u79D2\u65F6\u95F4\u6233' })),\n }),\n endpoint,\n });\n count++;\n\n // search_view_by_title \u2014 \u641C\u7D22\u89C6\u56FE\n registerProjectMcpTool(api, {\n name: 'feishu_project_search_view_by_title',\n mcpToolName: 'search_view_by_title',\n label: 'Feishu Project: \u641C\u7D22\u89C6\u56FE',\n description: '\u901A\u8FC7\u89C6\u56FE\u540D\u79F0\u6A21\u7CCA\u67E5\u8BE2\u89C6\u56FE ID\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n view_scope: Type.String({\n description: '\u5DE5\u4F5C\u9879\u7C7B\u578B\uFF1A\u9700\u6C42\u4E3A storyView\uFF0C\u7F3A\u9677\u4E3A issueView\uFF0C\u7248\u672C\u4E3A version\uFF0C\u81EA\u5B9A\u4E49\u7C7B\u578B\u4E3A\u5BF9\u5E94 key',\n }),\n key_word: Type.String({ description: '\u89C6\u56FE\u540D\u79F0\u5173\u952E\u5B57' }),\n }),\n endpoint,\n });\n count++;\n\n // get_view_detail \u2014 \u83B7\u53D6\u89C6\u56FE\u8BE6\u60C5\n registerProjectMcpTool(api, {\n name: 'feishu_project_get_view_detail',\n mcpToolName: 'get_view_detail',\n label: 'Feishu Project: \u89C6\u56FE\u8BE6\u60C5',\n description: '\u83B7\u53D6\u6307\u5B9A\u89C6\u56FE\u7684\u5DE5\u4F5C\u9879\u5217\u8868\u3002',\n schema: Type.Object({\n view_id: Type.String({ description: '\u89C6\u56FE ID' }),\n project_key: Type.Optional(Type.String({ description: '\u7A7A\u95F4 projectKey' })),\n fields: Type.Optional(\n Type.Array(Type.String({ description: '\u8981\u67E5\u8BE2\u7684 field_key \u6216 field_name' })),\n ),\n page_num: Type.Optional(Type.Number({ description: '\u5206\u9875\u9875\u7801' })),\n }),\n endpoint,\n });\n count++;\n\n // get_workitem_man_hour_records \u2014 \u5DE5\u65F6\u8BB0\u5F55\n registerProjectMcpTool(api, {\n name: 'feishu_project_get_workitem_man_hour_records',\n mcpToolName: 'get_workitem_man_hour_records',\n label: 'Feishu Project: \u5DE5\u65F6\u8BB0\u5F55',\n description: '\u83B7\u53D6\u5DE5\u4F5C\u9879\u7684\u5DE5\u65F6\u767B\u8BB0\u8BB0\u5F55\u5217\u8868\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n work_item_type_key: Type.String({ description: '\u5DE5\u4F5C\u9879\u7C7B\u578B key' }),\n work_item_id: Type.Number({ description: '\u5DE5\u4F5C\u9879\u5B9E\u4F8B ID' }),\n page_num: Type.Optional(Type.Number({ description: '\u5206\u9875\u9875\u7801\uFF0C\u6BCF\u9875\u9ED8\u8BA4 20' })),\n }),\n endpoint,\n });\n count++;\n\n // get_workitem_op_record \u2014 \u64CD\u4F5C\u8BB0\u5F55\n registerProjectMcpTool(api, {\n name: 'feishu_project_get_workitem_op_record',\n mcpToolName: 'get_workitem_op_record',\n label: 'Feishu Project: \u64CD\u4F5C\u8BB0\u5F55',\n description: '\u83B7\u53D6\u6307\u5B9A\u7A7A\u95F4\u4E0B\u4E00\u4E2A\u5DE5\u4F5C\u9879\u5B9E\u4F8B\u7684\u64CD\u4F5C\u8BB0\u5F55\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n work_item_id: Type.Number({ description: '\u5DE5\u4F5C\u9879 ID' }),\n start: Type.Optional(Type.Number({ description: '\u5F00\u59CB\u65F6\u95F4\uFF0C\u6BEB\u79D2\u7EA7\u65F6\u95F4\u6233' })),\n end: Type.Optional(Type.Number({ description: '\u7ED3\u675F\u65F6\u95F4\uFF0C\u6BEB\u79D2\u7EA7\u65F6\u95F4\u6233\uFF0C\u4E0E start \u914D\u5408\u6700\u957F 7 \u5929' })),\n operation_type: Type.Optional(\n Type.Array(Type.String({ description: '\u64CD\u4F5C\u7C7B\u578B\uFF1Amodify/create/delete/terminate/restore/complete/rollback/add/remove' })),\n ),\n start_from: Type.Optional(Type.String({ description: '\u5206\u9875\u53C2\u6570\uFF0C\u4F7F\u7528\u4E0A\u6B21\u8FD4\u56DE\u7684 start_from' })),\n }),\n endpoint,\n });\n count++;\n\n // list_schedule \u2014 \u6392\u671F\u67E5\u8BE2\n registerProjectMcpTool(api, {\n name: 'feishu_project_list_schedule',\n mcpToolName: 'list_schedule',\n label: 'Feishu Project: \u6392\u671F\u67E5\u8BE2',\n description: '\u6309\u7A7A\u95F4\u548C\u7528\u6237\u67E5\u8BE2\u6307\u5B9A\u65F6\u95F4\u8303\u56F4\u5185\u7684\u6392\u671F\u660E\u7EC6\uFF0C\u7528\u4E8E\u4EBA\u529B\u8D1F\u8377\u4E0E\u6392\u671F\u5206\u6790\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey\u3001\u540D\u79F0\u6216 simple_name' }),\n user_keys: Type.Array(\n Type.String({ description: '\u7528\u6237\u540D\u79F0\u3001\u90AE\u7BB1\u6216 userkey\uFF0C\u6700\u591A 20 \u4E2A' }),\n ),\n start_time: Type.String({ description: '\u5F00\u59CB\u65F6\u95F4\uFF0C\u683C\u5F0F 2006-01-01' }),\n end_time: Type.String({ description: '\u7ED3\u675F\u65F6\u95F4\uFF0C\u683C\u5F0F 2006-01-01\uFF0C\u6700\u5927\u4E0D\u8D85\u8FC7 3 \u4E2A\u6708' }),\n work_item_type_keys: Type.Optional(\n Type.Array(Type.String({ description: '\u5DE5\u4F5C\u9879\u7C7B\u578B key\uFF0C\u4F20 _all \u67E5\u8BE2\u6240\u6709\u7C7B\u578B' })),\n ),\n }),\n endpoint,\n });\n count++;\n\n // list_related_workitems \u2014 \u5173\u8054\u5DE5\u4F5C\u9879\n registerProjectMcpTool(api, {\n name: 'feishu_project_list_related_workitems',\n mcpToolName: 'list_related_workitems',\n label: 'Feishu Project: \u5173\u8054\u5DE5\u4F5C\u9879',\n description: '\u67E5\u8BE2\u67D0\u4E2A\u5173\u8054\u5DE5\u4F5C\u9879\u5B57\u6BB5\u6240\u5173\u8054\u7684\u5DE5\u4F5C\u9879\u5B9E\u4F8B\u7B80\u8981\u4FE1\u606F\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n work_item_type_key: Type.String({ description: '\u6E90\u5DE5\u4F5C\u9879\u7C7B\u578B' }),\n work_item_id: Type.Number({ description: '\u6E90\u5DE5\u4F5C\u9879\u5B9E\u4F8B ID' }),\n relation_work_item_type_key: Type.String({ description: '\u5173\u8054\u7684\u76EE\u6807\u5DE5\u4F5C\u9879\u7C7B\u578B' }),\n relation_key: Type.String({ description: '\u5173\u8054\u5173\u7CFB key \u6216\u5BF9\u63A5\u6807\u8BC6' }),\n relation_type: Type.Optional(Type.Number({ description: '\u5173\u8054\u6807\u8BC6\u65B9\u5F0F\uFF1A0=\u5B57\u6BB5ID\uFF0C1=\u5BF9\u63A5\u6807\u8BC6\u3002\u9ED8\u8BA4 0' })),\n page_num: Type.Optional(Type.Number({ description: '\u9875\u7801\uFF0C\u4ECE 1 \u5F00\u59CB' })),\n page_size: Type.Optional(Type.Number({ description: '\u6BCF\u9875\u6570\u91CF\uFF0C\u6700\u5927 50' })),\n }),\n endpoint,\n });\n count++;\n\n // list_workitem_relations \u2014 \u7A7A\u95F4\u5173\u8054\u5173\u7CFB\n registerProjectMcpTool(api, {\n name: 'feishu_project_list_workitem_relations',\n mcpToolName: 'list_workitem_relations',\n label: 'Feishu Project: \u5173\u8054\u5173\u7CFB\u5217\u8868',\n description: '\u67E5\u8BE2\u67D0\u4E2A\u7A7A\u95F4\u4E0B\u7684\u6240\u6709\u5DE5\u4F5C\u9879\u5173\u8054\u5173\u7CFB\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n }),\n endpoint,\n });\n count++;\n\n // list_team_members \u2014 \u56E2\u961F\u6210\u5458\n registerProjectMcpTool(api, {\n name: 'feishu_project_list_team_members',\n mcpToolName: 'list_team_members',\n label: 'Feishu Project: \u56E2\u961F\u6210\u5458',\n description: '\u67E5\u8BE2\u56E2\u961F\u4FE1\u606F\u548C\u6210\u5458\u5217\u8868\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n team_id: Type.String({ description: '\u56E2\u961F ID' }),\n page_token: Type.Optional(Type.String({ description: '\u5206\u9875 token\uFF0C\u9996\u9875\u4E0D\u4F20' })),\n }),\n endpoint,\n });\n count++;\n\n // list_charts \u2014 \u5EA6\u91CF\u56FE\u8868\n registerProjectMcpTool(api, {\n name: 'feishu_project_list_charts',\n mcpToolName: 'list_charts',\n label: 'Feishu Project: \u5EA6\u91CF\u56FE\u8868',\n description: '\u67E5\u8BE2\u6307\u5B9A\u7A7A\u95F4\u89C6\u56FE\u4E0B\u7684\u6240\u6709\u5EA6\u91CF\u56FE\u8868\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n view_id: Type.String({ description: '\u89C6\u56FE ID' }),\n page_num: Type.Optional(Type.Number({ description: '\u9875\u7801\uFF0C\u9ED8\u8BA4 1' })),\n page_size: Type.Optional(Type.Number({ description: '\u6BCF\u9875\u6761\u6570\uFF0C\u9ED8\u8BA4 50\uFF0C\u6700\u5927 200' })),\n }),\n endpoint,\n });\n count++;\n\n // create_fixed_view \u2014 \u521B\u5EFA\u56FA\u5B9A\u89C6\u56FE\n registerProjectMcpTool(api, {\n name: 'feishu_project_create_fixed_view',\n mcpToolName: 'create_fixed_view',\n label: 'Feishu Project: \u521B\u5EFA\u56FA\u5B9A\u89C6\u56FE',\n description: '\u5728\u6307\u5B9A\u7A7A\u95F4\u548C\u5DE5\u4F5C\u9879\u7C7B\u578B\u4E0B\u65B0\u589E\u4E00\u4E2A\u56FA\u5B9A\u89C6\u56FE\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n name: Type.String({ description: '\u56FA\u5B9A\u89C6\u56FE\u540D\u79F0' }),\n work_item_type_key: Type.String({ description: '\u5DE5\u4F5C\u9879\u7C7B\u578B key' }),\n work_item_id_list: Type.Array(\n Type.Number({ description: '\u5DE5\u4F5C\u9879 ID' }),\n { description: '\u9700\u8981\u6DFB\u52A0\u5165\u89C6\u56FE\u7684\u5DE5\u4F5C\u9879 ID \u5217\u8868\uFF0C\u4E0A\u9650 200 \u4E2A' },\n ),\n cooperation_mode: Type.Optional(\n Type.Number({ description: '\u534F\u4F5C\u6A21\u5F0F\uFF1A1=\u6307\u5B9A\u4EBA\u5458/\u56E2\u961F, 2=\u5168\u90E8\u7BA1\u7406\u5458, 3=\u5168\u90E8\u6210\u5458\u3002\u9ED8\u8BA4 1' }),\n ),\n }),\n endpoint,\n });\n count++;\n\n // update_fixed_view \u2014 \u66F4\u65B0\u56FA\u5B9A\u89C6\u56FE\n registerProjectMcpTool(api, {\n name: 'feishu_project_update_fixed_view',\n mcpToolName: 'update_fixed_view',\n label: 'Feishu Project: \u66F4\u65B0\u56FA\u5B9A\u89C6\u56FE',\n description: '\u66F4\u65B0\u56FA\u5B9A\u89C6\u56FE\u7684\u5DE5\u4F5C\u9879\u5217\u8868\uFF08\u65B0\u589E\u6216\u5220\u9664\uFF0C\u4E0D\u80FD\u540C\u65F6\u64CD\u4F5C\uFF09\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n view_id: Type.String({ description: '\u56FA\u5B9A\u89C6\u56FE ID' }),\n work_item_type_key: Type.String({ description: '\u5DE5\u4F5C\u9879\u7C7B\u578B key' }),\n add_work_item_ids: Type.Optional(\n Type.Array(Type.Number(), { description: '\u9700\u8981\u6DFB\u52A0\u7684\u5DE5\u4F5C\u9879 ID \u5217\u8868\uFF0C\u4E0A\u9650 200' }),\n ),\n remove_work_item_ids: Type.Optional(\n Type.Array(Type.Number(), { description: '\u9700\u8981\u5220\u9664\u7684\u5DE5\u4F5C\u9879 ID \u5217\u8868\uFF0C\u4E0A\u9650 200' }),\n ),\n }),\n endpoint,\n });\n count++;\n\n log.info(`registered ${count} project MCP tools with endpoint=${endpoint}`);\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 * \u98DE\u4E66\u9879\u76EE MCP \u5DE5\u5177\u6CE8\u518C\n *\n * \u6BCF\u4E2A\u5DE5\u5177\u4F7F\u7528\u98DE\u4E66\u9879\u76EE\u4E13\u5C5E\u7684 MCP \u7AEF\u70B9\u548C\u72EC\u7ACB\u7684 OAuth token\u3002\n * \u4E0D\u901A\u8FC7 registerMcpTool\uFF08\u4F7F\u7528\u98DE\u4E66 Open API UAT\uFF09\uFF0C\u800C\u662F\u4F7F\u7528\n * registerProjectMcpTool \u5305\u88C5\u51FD\u6570\u3002\n *\n * \u5DE5\u5177\u53C2\u6570\u7B7E\u540D\u53C2\u7167\u98DE\u4E66\u9879\u76EE MCP Server \u7684\u5B9E\u9645\u5B9A\u4E49\u3002\n */\n\nimport type { OpenClawPluginApi } from 'openclaw/plugin-sdk';\nimport type { TSchema } from '@sinclair/typebox';\nimport { Type } from '@sinclair/typebox';\nimport type { McpRpcResponse } from '../shared';\nimport { isRecord, unwrapJsonRpcResult } from '../shared';\nimport { createToolContext, formatToolResult } from '../../helpers';\nimport { getProjectStoredToken, setProjectStoredToken, projectTokenStatus, getProjectClientId } from '../../../core/project-token-store';\nimport type { StoredUAToken } from '../../../core/token-store';\nimport { refreshProjectToken } from '../../../core/project-auth';\nimport { getProjectDomainFromConfig } from './endpoint';\nimport { getTicket } from '../../../core/lark-ticket';\nimport { larkLogger } from '../../../core/lark-logger';\nimport { LarkClient } from '../../../core/lark-client';\n\nconst log = larkLogger('tools/mcp/project');\n\n// ---------------------------------------------------------------------------\n// \u98DE\u4E66\u9879\u76EE MCP \u4E13\u7528 JSON-RPC \u5BA2\u6237\u7AEF\uFF08\u4F7F\u7528\u6807\u51C6 OAuth Bearer \u8BA4\u8BC1\uFF09\n// ---------------------------------------------------------------------------\n\nasync function callProjectMcpTool(\n name: string,\n args: Record<string, unknown>,\n toolCallId: string,\n accessToken: string,\n endpoint: string,\n): Promise<unknown> {\n const body = {\n jsonrpc: '2.0',\n id: toolCallId,\n method: 'tools/call',\n params: { name, arguments: args },\n };\n\n const res = await fetch(endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${accessToken}`,\n },\n body: JSON.stringify(body),\n });\n\n const text = await res.text();\n if (!res.ok) {\n throw new Error(`MCP HTTP ${res.status} ${res.statusText}: ${text.slice(0, 4000)}`);\n }\n\n let data: McpRpcResponse;\n try {\n data = JSON.parse(text) as McpRpcResponse;\n } catch {\n throw new Error(`MCP \u8FD4\u56DE\u975E JSON\uFF1A${text.slice(0, 4000)}`);\n }\n\n if ('error' in data) {\n throw new Error(`MCP error ${data.error.code}: ${data.error.message}`);\n }\n\n return unwrapJsonRpcResult(data.result);\n}\n\n// ---------------------------------------------------------------------------\n// \u98DE\u4E66\u9879\u76EE MCP \u5DE5\u5177\u901A\u7528\u6CE8\u518C\u51FD\u6570\n// ---------------------------------------------------------------------------\n\ninterface ProjectMcpToolConfig {\n name: string;\n mcpToolName: string;\n label: string;\n description: string;\n schema: TSchema;\n endpoint: string | (() => string);\n}\n\nexport function registerProjectMcpTool(\n api: OpenClawPluginApi,\n config: ProjectMcpToolConfig,\n): void {\n const { log: toolLog } = createToolContext(api, config.name);\n\n api.registerTool(\n {\n name: config.name,\n label: config.label,\n description: config.description,\n parameters: config.schema,\n async execute(toolCallId, params) {\n const p = params as Record<string, unknown>;\n try {\n const startTime = Date.now();\n\n const ticket = getTicket();\n const senderOpenId = ticket?.senderOpenId;\n if (!senderOpenId) {\n return formatToolResult({\n error: '\u65E0\u6CD5\u83B7\u53D6\u5F53\u524D\u7528\u6237\u8EAB\u4EFD\uFF0C\u8BF7\u5728\u98DE\u4E66\u5BF9\u8BDD\u4E2D\u4F7F\u7528\u6B64\u5DE5\u5177\u3002',\n });\n }\n\n const currentDomain = getProjectDomainFromConfig(LarkClient.runtime.config.loadConfig());\n if (!currentDomain) {\n return formatToolResult({\n error: 'project_auth_required',\n message: '\u98DE\u4E66\u9879\u76EE\u73AF\u5883\u672A\u914D\u7F6E\u3002\u8BF7\u5148\u8C03\u7528 feishu_project_oauth \u5DE5\u5177\u7684 authorize \u64CD\u4F5C\u9009\u62E9\u73AF\u5883\u5E76\u5B8C\u6210\u6388\u6743\u3002',\n });\n }\n\n const clientId = await getProjectClientId(senderOpenId, currentDomain);\n if (!clientId) {\n return formatToolResult({\n error: 'project_auth_required',\n message: '\u98DE\u4E66\u9879\u76EE\u672A\u6388\u6743\u3002\u8BF7\u5148\u8C03\u7528 feishu_project_oauth \u5DE5\u5177\u5B8C\u6210\u6388\u6743\u3002',\n });\n }\n\n let token = await getProjectStoredToken(clientId, senderOpenId);\n if (!token) {\n return formatToolResult({\n error: 'project_auth_required',\n message: '\u98DE\u4E66\u9879\u76EE\u672A\u6388\u6743\u3002\u8BF7\u5148\u8C03\u7528 feishu_project_oauth \u5DE5\u5177\u5B8C\u6210\u6388\u6743\u3002',\n });\n }\n\n // \u6821\u9A8C token \u73AF\u5883\u4E0E\u5F53\u524D\u914D\u7F6E\u7684\u73AF\u5883\u662F\u5426\u5339\u914D\n if (token.domain && currentDomain && token.domain !== currentDomain) {\n return formatToolResult({\n error: 'project_auth_required',\n message: `\u5F53\u524D\u98DE\u4E66\u9879\u76EE\u73AF\u5883\u5DF2\u5207\u6362\u4E3A\u300C${currentDomain}\u300D\uFF0C\u4E0E\u6388\u6743\u65F6\u7684\u73AF\u5883\u300C${token.domain}\u300D\u4E0D\u5339\u914D\u3002\u8BF7\u8C03\u7528 feishu_project_oauth \u5DE5\u5177\u7684 authorize \u64CD\u4F5C\u91CD\u65B0\u6388\u6743\u3002`,\n });\n }\n\n const status = projectTokenStatus(token);\n if (status === 'expired') {\n return formatToolResult({\n error: 'project_auth_required',\n message: '\u98DE\u4E66\u9879\u76EE\u6388\u6743\u5DF2\u8FC7\u671F\u3002\u8BF7\u8C03\u7528 feishu_project_oauth \u5DE5\u5177\u7684 authorize \u64CD\u4F5C\u91CD\u65B0\u6388\u6743\u3002',\n });\n }\n\n // Token \u5373\u5C06\u8FC7\u671F\u6216\u5DF2\u8FC7\u671F\u4F46 refresh_token \u4ECD\u6709\u6548 \u2192 \u81EA\u52A8\u5237\u65B0\n if (status === 'needs_refresh') {\n try {\n const resolvedEp = typeof config.endpoint === 'function' ? config.endpoint() : config.endpoint;\n const refreshed = await refreshProjectToken(resolvedEp, token.refreshToken, clientId);\n token = {\n ...token,\n accessToken: refreshed.access_token,\n refreshToken: refreshed.refresh_token ?? token.refreshToken,\n expiresAt: Date.now() + (refreshed.expires_in ?? 7200) * 1000,\n scope: refreshed.scope ?? token.scope,\n domain: currentDomain,\n };\n await setProjectStoredToken(token as StoredUAToken & { domain: string });\n toolLog.debug?.('project token refreshed');\n } catch (refreshErr) {\n toolLog.error(`project token refresh failed: ${refreshErr}`);\n return formatToolResult({\n error: 'project_auth_required',\n message: '\u98DE\u4E66\u9879\u76EE token \u5237\u65B0\u5931\u8D25\uFF0C\u8BF7\u8C03\u7528 feishu_project_oauth \u5DE5\u5177\u7684 authorize \u64CD\u4F5C\u91CD\u65B0\u6388\u6743\u3002',\n });\n }\n }\n\n const resolvedEndpoint = typeof config.endpoint === 'function' ? config.endpoint() : config.endpoint;\n\n const result = await callProjectMcpTool(\n config.mcpToolName,\n p,\n toolCallId,\n token.accessToken,\n resolvedEndpoint,\n );\n\n const duration = Date.now() - startTime;\n toolLog.debug?.(`${config.mcpToolName} succeeded in ${duration}ms`);\n\n // MCP \u8FD4\u56DE\u503C\u5904\u7406\uFF08\u540C registerMcpTool \u903B\u8F91\uFF09\n if (isRecord(result) && Array.isArray((result as Record<string, unknown>).content)) {\n const mcpContent = (result as Record<string, unknown>).content as Array<{\n type: string;\n text: string;\n }>;\n let details: unknown = result;\n if (mcpContent.length === 1 && mcpContent[0]?.type === 'text') {\n try {\n details = JSON.parse(mcpContent[0].text);\n } catch {\n // text is not JSON\n }\n }\n return {\n content: mcpContent.map((c) => ({\n type: 'text' as const,\n text: c.text,\n })),\n details,\n };\n }\n return formatToolResult(result);\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n toolLog.error(`${config.mcpToolName} failed: ${errMsg}`);\n return formatToolResult({\n error: errMsg,\n hint: '\u5982\u679C\u662F\u6388\u6743\u95EE\u9898\uFF0C\u8BF7\u8C03\u7528 feishu_project_oauth \u5DE5\u5177\u91CD\u65B0\u6388\u6743\u3002',\n });\n }\n },\n },\n { name: config.name },\n );\n}\n\n// ---------------------------------------------------------------------------\n// \u5177\u4F53\u5DE5\u5177\u6CE8\u518C\uFF08\u53C2\u6570\u7B7E\u540D\u5339\u914D\u98DE\u4E66\u9879\u76EE MCP Server \u5B9E\u9645\u5B9A\u4E49\uFF09\n// ---------------------------------------------------------------------------\n\nexport function registerProjectTools(api: OpenClawPluginApi, endpoint: string | (() => string)) {\n let count = 0;\n\n // list_todo \u2014 \u5F85\u529E/\u5DF2\u529E/\u8D85\u671F/\u672C\u5468\u5230\u671F\n registerProjectMcpTool(api, {\n name: 'feishu_project_list_todo',\n mcpToolName: 'list_todo',\n label: 'Feishu Project: \u5F85\u529E\u5217\u8868',\n description:\n '\u83B7\u53D6\u5F53\u524D\u7528\u6237\u7684\u98DE\u4E66\u9879\u76EE\u5DE5\u4F5C\u9879\u5217\u8868\u3002\u652F\u6301\u67E5\u8BE2\u7C7B\u578B\uFF1A' +\n 'todo(\u5F85\u529E)\u3001done(\u5DF2\u529E)\u3001overdue(\u5DF2\u8D85\u671F)\u3001this_week(\u672C\u5468\u5230\u671F)\u3002',\n schema: Type.Object({\n action: Type.String({\n description: '\u67E5\u8BE2\u7C7B\u578B\u3002todo \u4E3A\u5F85\u529E, done \u4E3A\u5DF2\u529E, overdue \u4E3A\u5DF2\u8D85\u671F, this_week \u4E3A\u672C\u5468\u5230\u671F',\n }),\n asset_key: Type.Optional(\n Type.String({ description: '\u5DE5\u4F5C\u533A key\uFF0C\u683C\u5F0F\u4E3A Asset_\uFF0C\u4EC5\u5728\u63A5\u53E3\u62A5\u9519\u9700\u8981\u9009\u62E9\u65F6\u4F20\u9012' }),\n ),\n page_num: Type.Optional(\n Type.Number({ description: '\u9875\u7801\uFF0C\u4ECE 1 \u5F00\u59CB\u9012\u589E\uFF0C\u6BCF\u9875 50 \u6761' }),\n ),\n }),\n endpoint,\n });\n count++;\n\n // search_by_mql \u2014 MQL \u67E5\u8BE2\n registerProjectMcpTool(api, {\n name: 'feishu_project_search_by_mql',\n mcpToolName: 'search_by_mql',\n label: 'Feishu Project: MQL \u641C\u7D22',\n description:\n '\u4F7F\u7528 MQL \u641C\u7D22\u98DE\u4E66\u9879\u76EE\u5DE5\u4F5C\u9879\u3002MQL \u57FA\u4E8E SQL \u8BED\u6CD5\u6269\u5C55\u3002' +\n '\u4F7F\u7528\u524D\u9700\u5148\u7528 get_workitem_info \u786E\u8BA4\u7A7A\u95F4\u548C\u5DE5\u4F5C\u9879\u7C7B\u578B\u3002' +\n 'select \u540E\u4F7F\u7528\u53EF\u8BFB\u6027\u5F3A\u7684\u5B57\u6BB5\u540D\u79F0\uFF08\u5982\"\u4EFB\u52A1\u540D\u79F0\"\u800C\u975E\"name\"\uFF09\u3002',\n schema: Type.Object({\n project_key: Type.String({\n description: '\u7A7A\u95F4 projectKey\u3001simpleName \u6216\u7A7A\u95F4\u540D',\n }),\n mql: Type.Optional(\n Type.String({\n description:\n 'MQL \u8BED\u53E5\u3002\u8BED\u6CD5\uFF1Aselect `\u5B57\u6BB5\u540D` from `\u7A7A\u95F4\u540D`.`\u5DE5\u4F5C\u9879\u540D` where `\u5B57\u6BB5\u540D` = \\'\u5B57\u6BB5\u503C\\'',\n }),\n ),\n helper: Type.Optional(\n Type.String({\n description: '\u5E2E\u52A9\u5DE5\u5177\u3002\u8F93\u5165 \"helper\" \u83B7\u53D6\u6240\u6709\u547D\u4EE4\u548C\u7528\u6CD5\u793A\u4F8B',\n }),\n ),\n session_id: Type.Optional(\n Type.String({ description: 'sessionID\uFF0C\u7528\u4E8E\u5206\u9875\u67E5\u8BE2\uFF0C\u4F20\u5165\u540E\u4E0D\u89E3\u6790 mql' }),\n ),\n group_pagination_list: Type.Optional(\n Type.Array(\n Type.Object({\n group_id: Type.Optional(Type.String({ description: '\u5206\u7EC4 ID' })),\n page_num: Type.Optional(Type.Number({ description: '\u9875\u7801\uFF0C\u4ECE 1 \u5F00\u59CB' })),\n }),\n ),\n ),\n }),\n endpoint,\n });\n count++;\n\n // get_workitem_info \u2014 \u83B7\u53D6\u5DE5\u4F5C\u9879\u7C7B\u578B\u7684\u5B57\u6BB5\u4E0E\u89D2\u8272\u4FE1\u606F\n registerProjectMcpTool(api, {\n name: 'feishu_project_get_workitem_info',\n mcpToolName: 'get_workitem_info',\n label: 'Feishu Project: \u5DE5\u4F5C\u9879\u7C7B\u578B\u4FE1\u606F',\n description: '\u83B7\u53D6\u4E00\u4E2A\u5DE5\u4F5C\u9879\u7C7B\u578B\u5177\u5907\u7684\u53EF\u7528\u5B57\u6BB5\u4E0E\u89D2\u8272\u4FE1\u606F\u3002',\n schema: Type.Object({\n project_key: Type.Optional(\n Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n ),\n work_item_type: Type.String({\n description: '\u5DE5\u4F5C\u9879\u7C7B\u578B\u7684\u7CFB\u7EDF\u6807\u8BC6\u6216\u540D\u79F0\uFF0C\u5982 story\u3001\u9700\u6C42\u3001issue\u3001\u7F3A\u9677\u7B49',\n }),\n }),\n endpoint,\n });\n count++;\n\n // get_workitem_brief \u2014 \u83B7\u53D6\u5DE5\u4F5C\u9879\u6982\u51B5\n registerProjectMcpTool(api, {\n name: 'feishu_project_get_workitem_brief',\n mcpToolName: 'get_workitem_brief',\n label: 'Feishu Project: \u5DE5\u4F5C\u9879\u6982\u51B5',\n description: '\u83B7\u53D6\u4E00\u4E2A\u5DE5\u4F5C\u9879\u5B9E\u4F8B\u7684\u6982\u51B5\u4FE1\u606F\u3002',\n schema: Type.Object({\n project_key: Type.Optional(\n Type.String({ description: '\u7A7A\u95F4 projectKey \u6216 simpleName' }),\n ),\n work_item_id: Type.String({\n description: '\u5DE5\u4F5C\u9879 ID \u6216\u540D\u79F0',\n }),\n fields: Type.Optional(\n Type.Array(Type.String({ description: '\u8981\u67E5\u8BE2\u7684 field_key \u6216 field_name' })),\n ),\n }),\n endpoint,\n });\n count++;\n\n // get_workitem_field_meta \u2014 \u83B7\u53D6\u521B\u5EFA\u5DE5\u4F5C\u9879\u5143\u4FE1\u606F\n registerProjectMcpTool(api, {\n name: 'feishu_project_get_workitem_field_meta',\n mcpToolName: 'get_workitem_field_meta',\n label: 'Feishu Project: \u5B57\u6BB5\u5143\u4FE1\u606F',\n description: '\u83B7\u53D6\u521B\u5EFA\u5DE5\u4F5C\u9879\u65F6\u7684\u5B57\u6BB5\u5143\u4FE1\u606F\uFF08\u5B57\u6BB5\u540D\u3001\u7C7B\u578B\u3001\u9009\u9879\u7B49\uFF09\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n work_item_type_key: Type.String({ description: '\u5DE5\u4F5C\u9879\u7C7B\u578B key' }),\n }),\n endpoint,\n });\n count++;\n\n // create_workitem \u2014 \u521B\u5EFA\u5DE5\u4F5C\u9879\n registerProjectMcpTool(api, {\n name: 'feishu_project_create_workitem',\n mcpToolName: 'create_workitem',\n label: 'Feishu Project: \u521B\u5EFA\u5DE5\u4F5C\u9879',\n description: '\u521B\u5EFA\u4E00\u6761\u5DE5\u4F5C\u9879\u5B9E\u4F8B\u3002\u521B\u5EFA\u6210\u529F\u540E\u83B7\u5F97\u8BE6\u60C5\u9875 URL\u3002',\n schema: Type.Object({\n project_key: Type.Optional(\n Type.String({ description: '\u7A7A\u95F4 projectKey \u6216 simpleName' }),\n ),\n work_item_type: Type.String({\n description: '\u5DE5\u4F5C\u9879\u7C7B\u578B',\n }),\n fields: Type.Optional(\n Type.Array(\n Type.Object({\n field_key: Type.String({ description: '\u5B57\u6BB5 key' }),\n field_value: Type.String({\n description: '\u5B57\u6BB5\u503C\u3002\u65F6\u95F4\u7C7B\u9700\u4F20 16 \u4F4D unix \u6BEB\u79D2\u65F6\u95F4\u6233\uFF0C\u4EBA\u5458\u7C7B\u9700\u7528\u82F1\u6587\u9017\u53F7\u533A\u9694',\n }),\n }),\n ),\n ),\n }),\n endpoint,\n });\n count++;\n\n // update_field \u2014 \u4FEE\u6539\u5DE5\u4F5C\u9879\u5B57\u6BB5\n registerProjectMcpTool(api, {\n name: 'feishu_project_update_field',\n mcpToolName: 'update_field',\n label: 'Feishu Project: \u4FEE\u6539\u5B57\u6BB5',\n description: '\u4FEE\u6539\u6307\u5B9A\u5DE5\u4F5C\u9879\u5B9E\u4F8B\u7684\u5B57\u6BB5\u503C\uFF0C\u652F\u6301\u4E00\u6B21\u4FEE\u6539\u591A\u4E2A\u5B57\u6BB5\u3002',\n schema: Type.Object({\n project_key: Type.Optional(\n Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n ),\n work_item_id: Type.String({\n description: '\u5DE5\u4F5C\u9879 ID \u6216\u540D\u79F0',\n }),\n fields: Type.Optional(\n Type.Array(\n Type.Object({\n field_key: Type.String({ description: '\u5B57\u6BB5 key' }),\n field_value: Type.String({\n description: '\u5B57\u6BB5\u503C\u3002\u65F6\u95F4\u7C7B\u9700\u4F20 16 \u4F4D unix \u6BEB\u79D2\u65F6\u95F4\u6233\uFF0C\u4EBA\u5458\u7C7B\u9700\u7528\u82F1\u6587\u9017\u53F7\u533A\u9694',\n }),\n }),\n ),\n ),\n }),\n endpoint,\n });\n count++;\n\n // add_comment \u2014 \u6DFB\u52A0\u8BC4\u8BBA\n registerProjectMcpTool(api, {\n name: 'feishu_project_add_comment',\n mcpToolName: 'add_comment',\n label: 'Feishu Project: \u6DFB\u52A0\u8BC4\u8BBA',\n description: '\u5728\u6307\u5B9A\u5DE5\u4F5C\u9879\u7684\u8BC4\u8BBA/\u5907\u6CE8\u9875\u6DFB\u52A0\u4E00\u6761\u8BC4\u8BBA\uFF0C\u652F\u6301 markdown \u683C\u5F0F\u3002',\n schema: Type.Object({\n project_key: Type.Optional(\n Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n ),\n work_item_id: Type.String({\n description: '\u5DE5\u4F5C\u9879 ID \u6216\u540D\u79F0',\n }),\n work_item_type: Type.Optional(\n Type.String({ description: '\u5DE5\u4F5C\u9879\u7C7B\u578B' }),\n ),\n comment_content: Type.String({\n description: '\u8BC4\u8BBA\u5185\u5BB9\uFF0C\u652F\u6301 markdown \u683C\u5F0F',\n }),\n url: Type.Optional(\n Type.String({ description: '\u5DE5\u4F5C\u9879 URL\uFF0C\u53EF\u4ECE\u4E2D\u89E3\u6790 project_key \u7B49\u4FE1\u606F' }),\n ),\n }),\n endpoint,\n });\n count++;\n\n // finish_node \u2014 \u5B8C\u6210\u8282\u70B9/\u6D41\u8F6C\u72B6\u6001\n registerProjectMcpTool(api, {\n name: 'feishu_project_finish_node',\n mcpToolName: 'finish_node',\n label: 'Feishu Project: \u6D41\u8F6C\u72B6\u6001',\n description: '\u5B8C\u6210\u67D0\u4E2A\u8282\u70B9\uFF0C\u6D41\u8F6C\u5DE5\u4F5C\u9879\u5230\u4E0B\u4E00\u4E2A\u72B6\u6001\u3002',\n schema: Type.Object({\n project_key: Type.Optional(\n Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n ),\n work_item_id: Type.String({\n description: '\u5DE5\u4F5C\u9879 ID \u6216\u540D\u79F0',\n }),\n node_id: Type.String({\n description: '\u8981\u5B8C\u6210\u6D41\u8F6C\u7684\u8282\u70B9 ID \u6216\u8282\u70B9\u540D\u79F0',\n }),\n }),\n endpoint,\n });\n count++;\n\n // get_node_detail \u2014 \u83B7\u53D6\u8282\u70B9\u8BE6\u60C5\n registerProjectMcpTool(api, {\n name: 'feishu_project_get_node_detail',\n mcpToolName: 'get_node_detail',\n label: 'Feishu Project: \u8282\u70B9\u8BE6\u60C5',\n description: '\u83B7\u53D6\u6307\u5B9A\u8282\u70B9\u7684\u5173\u952E\u4FE1\u606F\uFF0C\u5305\u62EC\u8282\u70B9\u4FE1\u606F\u3001\u5B50\u9879\u4FE1\u606F\u3001\u81EA\u5B9A\u4E49\u5B57\u6BB5\u4FE1\u606F\u3002',\n schema: Type.Object({\n project_key: Type.Optional(\n Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n ),\n work_item_id: Type.String({ description: '\u5DE5\u4F5C\u9879 ID' }),\n node_id: Type.String({ description: '\u8282\u70B9\u540D\u79F0\u6216\u8282\u70B9 ID' }),\n node_ids: Type.Optional(\n Type.Array(Type.String({ description: '\u8282\u70B9\u540D\u79F0\u6216\u8282\u70B9 ID \u5217\u8868' })),\n ),\n }),\n endpoint,\n });\n count++;\n\n // get_transitable_statuses \u2014 \u83B7\u53D6\u53EF\u6D41\u8F6C\u72B6\u6001\n registerProjectMcpTool(api, {\n name: 'feishu_project_get_transitable_statuses',\n mcpToolName: 'get_transitable_statuses',\n label: 'Feishu Project: \u53EF\u6D41\u8F6C\u72B6\u6001',\n description: '\u83B7\u53D6\u72B6\u6001\u6D41\u5DE5\u4F5C\u9879\u7684\u53EF\u6D41\u8F6C\u72B6\u6001\u5217\u8868\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n work_item_id: Type.String({ description: '\u5DE5\u4F5C\u9879 ID' }),\n work_item_type_key: Type.String({ description: '\u5DE5\u4F5C\u9879\u7C7B\u578B key' }),\n user_key: Type.String({ description: '\u7528\u6237 userKey' }),\n }),\n endpoint,\n });\n count++;\n\n // list_workitem_types \u2014 \u83B7\u53D6\u5DE5\u4F5C\u9879\u7C7B\u578B\u5217\u8868\n registerProjectMcpTool(api, {\n name: 'feishu_project_list_workitem_types',\n mcpToolName: 'list_workitem_types',\n label: 'Feishu Project: \u5DE5\u4F5C\u9879\u7C7B\u578B\u5217\u8868',\n description: '\u83B7\u53D6\u7A7A\u95F4\u4E0B\u7684\u6240\u6709\u5DE5\u4F5C\u9879\u7C7B\u578B\u5217\u8868\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n }),\n endpoint,\n });\n count++;\n\n // search_project_info \u2014 \u67E5\u8BE2\u7A7A\u95F4\u4FE1\u606F\n registerProjectMcpTool(api, {\n name: 'feishu_project_search_project_info',\n mcpToolName: 'search_project_info',\n label: 'Feishu Project: \u67E5\u8BE2\u7A7A\u95F4',\n description: '\u67E5\u8BE2\u98DE\u4E66\u9879\u76EE\u7A7A\u95F4\u57FA\u7840\u4FE1\u606F\u3002',\n schema: Type.Object({\n project_key: Type.String({\n description: '\u7A7A\u95F4 projectKey\u3001simpleName \u6216\u7A7A\u95F4\u540D\u79F0',\n }),\n }),\n endpoint,\n });\n count++;\n\n // search_user_info \u2014 \u67E5\u8BE2\u7528\u6237\u4FE1\u606F\n registerProjectMcpTool(api, {\n name: 'feishu_project_search_user_info',\n mcpToolName: 'search_user_info',\n label: 'Feishu Project: \u67E5\u8BE2\u7528\u6237',\n description: '\u6279\u91CF\u67E5\u8BE2\u7528\u6237\u57FA\u7840\u4FE1\u606F\uFF0C\u6700\u591A 20 \u4E2A\u3002',\n schema: Type.Object({\n user_keys: Type.Array(\n Type.String({ description: 'userKey\u3001Email \u6216\u540D\u5B57' }),\n ),\n project_key: Type.Optional(\n Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n ),\n }),\n endpoint,\n });\n count++;\n\n // list_workitem_comments \u2014 \u67E5\u8BE2\u8BC4\u8BBA\u5217\u8868\n registerProjectMcpTool(api, {\n name: 'feishu_project_list_workitem_comments',\n mcpToolName: 'list_workitem_comments',\n label: 'Feishu Project: \u8BC4\u8BBA\u5217\u8868',\n description: '\u67E5\u8BE2\u5DE5\u4F5C\u9879\u8BC4\u8BBA\u5217\u8868\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n work_item_id: Type.String({ description: '\u5DE5\u4F5C\u9879 ID \u6216\u540D\u79F0' }),\n page_num: Type.Optional(Type.Number({ description: '\u9875\u7801\uFF0C\u9ED8\u8BA4 1' })),\n start_time: Type.Optional(Type.Number({ description: '\u5F00\u59CB\u65F6\u95F4\uFF0CUnix \u6BEB\u79D2\u65F6\u95F4\u6233' })),\n end_time: Type.Optional(Type.Number({ description: '\u7ED3\u675F\u65F6\u95F4\uFF0CUnix \u6BEB\u79D2\u65F6\u95F4\u6233' })),\n }),\n endpoint,\n });\n count++;\n\n // search_view_by_title \u2014 \u641C\u7D22\u89C6\u56FE\n registerProjectMcpTool(api, {\n name: 'feishu_project_search_view_by_title',\n mcpToolName: 'search_view_by_title',\n label: 'Feishu Project: \u641C\u7D22\u89C6\u56FE',\n description: '\u901A\u8FC7\u89C6\u56FE\u540D\u79F0\u6A21\u7CCA\u67E5\u8BE2\u89C6\u56FE ID\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n view_scope: Type.String({\n description: '\u5DE5\u4F5C\u9879\u7C7B\u578B\uFF1A\u9700\u6C42\u4E3A storyView\uFF0C\u7F3A\u9677\u4E3A issueView\uFF0C\u7248\u672C\u4E3A version\uFF0C\u81EA\u5B9A\u4E49\u7C7B\u578B\u4E3A\u5BF9\u5E94 key',\n }),\n key_word: Type.String({ description: '\u89C6\u56FE\u540D\u79F0\u5173\u952E\u5B57' }),\n }),\n endpoint,\n });\n count++;\n\n // get_view_detail \u2014 \u83B7\u53D6\u89C6\u56FE\u8BE6\u60C5\n registerProjectMcpTool(api, {\n name: 'feishu_project_get_view_detail',\n mcpToolName: 'get_view_detail',\n label: 'Feishu Project: \u89C6\u56FE\u8BE6\u60C5',\n description: '\u83B7\u53D6\u6307\u5B9A\u89C6\u56FE\u7684\u5DE5\u4F5C\u9879\u5217\u8868\u3002',\n schema: Type.Object({\n view_id: Type.String({ description: '\u89C6\u56FE ID' }),\n project_key: Type.Optional(Type.String({ description: '\u7A7A\u95F4 projectKey' })),\n fields: Type.Optional(\n Type.Array(Type.String({ description: '\u8981\u67E5\u8BE2\u7684 field_key \u6216 field_name' })),\n ),\n page_num: Type.Optional(Type.Number({ description: '\u5206\u9875\u9875\u7801' })),\n }),\n endpoint,\n });\n count++;\n\n // get_workitem_man_hour_records \u2014 \u5DE5\u65F6\u8BB0\u5F55\n registerProjectMcpTool(api, {\n name: 'feishu_project_get_workitem_man_hour_records',\n mcpToolName: 'get_workitem_man_hour_records',\n label: 'Feishu Project: \u5DE5\u65F6\u8BB0\u5F55',\n description: '\u83B7\u53D6\u5DE5\u4F5C\u9879\u7684\u5DE5\u65F6\u767B\u8BB0\u8BB0\u5F55\u5217\u8868\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n work_item_type_key: Type.String({ description: '\u5DE5\u4F5C\u9879\u7C7B\u578B key' }),\n work_item_id: Type.Number({ description: '\u5DE5\u4F5C\u9879\u5B9E\u4F8B ID' }),\n page_num: Type.Optional(Type.Number({ description: '\u5206\u9875\u9875\u7801\uFF0C\u6BCF\u9875\u9ED8\u8BA4 20' })),\n }),\n endpoint,\n });\n count++;\n\n // get_workitem_op_record \u2014 \u64CD\u4F5C\u8BB0\u5F55\n registerProjectMcpTool(api, {\n name: 'feishu_project_get_workitem_op_record',\n mcpToolName: 'get_workitem_op_record',\n label: 'Feishu Project: \u64CD\u4F5C\u8BB0\u5F55',\n description: '\u83B7\u53D6\u6307\u5B9A\u7A7A\u95F4\u4E0B\u4E00\u4E2A\u5DE5\u4F5C\u9879\u5B9E\u4F8B\u7684\u64CD\u4F5C\u8BB0\u5F55\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n work_item_id: Type.Number({ description: '\u5DE5\u4F5C\u9879 ID' }),\n start: Type.Optional(Type.Number({ description: '\u5F00\u59CB\u65F6\u95F4\uFF0C\u6BEB\u79D2\u7EA7\u65F6\u95F4\u6233' })),\n end: Type.Optional(Type.Number({ description: '\u7ED3\u675F\u65F6\u95F4\uFF0C\u6BEB\u79D2\u7EA7\u65F6\u95F4\u6233\uFF0C\u4E0E start \u914D\u5408\u6700\u957F 7 \u5929' })),\n operation_type: Type.Optional(\n Type.Array(Type.String({ description: '\u64CD\u4F5C\u7C7B\u578B\uFF1Amodify/create/delete/terminate/restore/complete/rollback/add/remove' })),\n ),\n start_from: Type.Optional(Type.String({ description: '\u5206\u9875\u53C2\u6570\uFF0C\u4F7F\u7528\u4E0A\u6B21\u8FD4\u56DE\u7684 start_from' })),\n }),\n endpoint,\n });\n count++;\n\n // list_schedule \u2014 \u6392\u671F\u67E5\u8BE2\n registerProjectMcpTool(api, {\n name: 'feishu_project_list_schedule',\n mcpToolName: 'list_schedule',\n label: 'Feishu Project: \u6392\u671F\u67E5\u8BE2',\n description: '\u6309\u7A7A\u95F4\u548C\u7528\u6237\u67E5\u8BE2\u6307\u5B9A\u65F6\u95F4\u8303\u56F4\u5185\u7684\u6392\u671F\u660E\u7EC6\uFF0C\u7528\u4E8E\u4EBA\u529B\u8D1F\u8377\u4E0E\u6392\u671F\u5206\u6790\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey\u3001\u540D\u79F0\u6216 simple_name' }),\n user_keys: Type.Array(\n Type.String({ description: '\u7528\u6237\u540D\u79F0\u3001\u90AE\u7BB1\u6216 userkey\uFF0C\u6700\u591A 20 \u4E2A' }),\n ),\n start_time: Type.String({ description: '\u5F00\u59CB\u65F6\u95F4\uFF0C\u683C\u5F0F 2006-01-01' }),\n end_time: Type.String({ description: '\u7ED3\u675F\u65F6\u95F4\uFF0C\u683C\u5F0F 2006-01-01\uFF0C\u6700\u5927\u4E0D\u8D85\u8FC7 3 \u4E2A\u6708' }),\n work_item_type_keys: Type.Optional(\n Type.Array(Type.String({ description: '\u5DE5\u4F5C\u9879\u7C7B\u578B key\uFF0C\u4F20 _all \u67E5\u8BE2\u6240\u6709\u7C7B\u578B' })),\n ),\n }),\n endpoint,\n });\n count++;\n\n // list_related_workitems \u2014 \u5173\u8054\u5DE5\u4F5C\u9879\n registerProjectMcpTool(api, {\n name: 'feishu_project_list_related_workitems',\n mcpToolName: 'list_related_workitems',\n label: 'Feishu Project: \u5173\u8054\u5DE5\u4F5C\u9879',\n description: '\u67E5\u8BE2\u67D0\u4E2A\u5173\u8054\u5DE5\u4F5C\u9879\u5B57\u6BB5\u6240\u5173\u8054\u7684\u5DE5\u4F5C\u9879\u5B9E\u4F8B\u7B80\u8981\u4FE1\u606F\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n work_item_type_key: Type.String({ description: '\u6E90\u5DE5\u4F5C\u9879\u7C7B\u578B' }),\n work_item_id: Type.Number({ description: '\u6E90\u5DE5\u4F5C\u9879\u5B9E\u4F8B ID' }),\n relation_work_item_type_key: Type.String({ description: '\u5173\u8054\u7684\u76EE\u6807\u5DE5\u4F5C\u9879\u7C7B\u578B' }),\n relation_key: Type.String({ description: '\u5173\u8054\u5173\u7CFB key \u6216\u5BF9\u63A5\u6807\u8BC6' }),\n relation_type: Type.Optional(Type.Number({ description: '\u5173\u8054\u6807\u8BC6\u65B9\u5F0F\uFF1A0=\u5B57\u6BB5ID\uFF0C1=\u5BF9\u63A5\u6807\u8BC6\u3002\u9ED8\u8BA4 0' })),\n page_num: Type.Optional(Type.Number({ description: '\u9875\u7801\uFF0C\u4ECE 1 \u5F00\u59CB' })),\n page_size: Type.Optional(Type.Number({ description: '\u6BCF\u9875\u6570\u91CF\uFF0C\u6700\u5927 50' })),\n }),\n endpoint,\n });\n count++;\n\n // list_workitem_relations \u2014 \u7A7A\u95F4\u5173\u8054\u5173\u7CFB\n registerProjectMcpTool(api, {\n name: 'feishu_project_list_workitem_relations',\n mcpToolName: 'list_workitem_relations',\n label: 'Feishu Project: \u5173\u8054\u5173\u7CFB\u5217\u8868',\n description: '\u67E5\u8BE2\u67D0\u4E2A\u7A7A\u95F4\u4E0B\u7684\u6240\u6709\u5DE5\u4F5C\u9879\u5173\u8054\u5173\u7CFB\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n }),\n endpoint,\n });\n count++;\n\n // list_team_members \u2014 \u56E2\u961F\u6210\u5458\n registerProjectMcpTool(api, {\n name: 'feishu_project_list_team_members',\n mcpToolName: 'list_team_members',\n label: 'Feishu Project: \u56E2\u961F\u6210\u5458',\n description: '\u67E5\u8BE2\u56E2\u961F\u4FE1\u606F\u548C\u6210\u5458\u5217\u8868\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n team_id: Type.String({ description: '\u56E2\u961F ID' }),\n page_token: Type.Optional(Type.String({ description: '\u5206\u9875 token\uFF0C\u9996\u9875\u4E0D\u4F20' })),\n }),\n endpoint,\n });\n count++;\n\n // list_charts \u2014 \u5EA6\u91CF\u56FE\u8868\n registerProjectMcpTool(api, {\n name: 'feishu_project_list_charts',\n mcpToolName: 'list_charts',\n label: 'Feishu Project: \u5EA6\u91CF\u56FE\u8868',\n description: '\u67E5\u8BE2\u6307\u5B9A\u7A7A\u95F4\u89C6\u56FE\u4E0B\u7684\u6240\u6709\u5EA6\u91CF\u56FE\u8868\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n view_id: Type.String({ description: '\u89C6\u56FE ID' }),\n page_num: Type.Optional(Type.Number({ description: '\u9875\u7801\uFF0C\u9ED8\u8BA4 1' })),\n page_size: Type.Optional(Type.Number({ description: '\u6BCF\u9875\u6761\u6570\uFF0C\u9ED8\u8BA4 50\uFF0C\u6700\u5927 200' })),\n }),\n endpoint,\n });\n count++;\n\n // create_fixed_view \u2014 \u521B\u5EFA\u56FA\u5B9A\u89C6\u56FE\n registerProjectMcpTool(api, {\n name: 'feishu_project_create_fixed_view',\n mcpToolName: 'create_fixed_view',\n label: 'Feishu Project: \u521B\u5EFA\u56FA\u5B9A\u89C6\u56FE',\n description: '\u5728\u6307\u5B9A\u7A7A\u95F4\u548C\u5DE5\u4F5C\u9879\u7C7B\u578B\u4E0B\u65B0\u589E\u4E00\u4E2A\u56FA\u5B9A\u89C6\u56FE\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n name: Type.String({ description: '\u56FA\u5B9A\u89C6\u56FE\u540D\u79F0' }),\n work_item_type_key: Type.String({ description: '\u5DE5\u4F5C\u9879\u7C7B\u578B key' }),\n work_item_id_list: Type.Array(\n Type.Number({ description: '\u5DE5\u4F5C\u9879 ID' }),\n { description: '\u9700\u8981\u6DFB\u52A0\u5165\u89C6\u56FE\u7684\u5DE5\u4F5C\u9879 ID \u5217\u8868\uFF0C\u4E0A\u9650 200 \u4E2A' },\n ),\n cooperation_mode: Type.Optional(\n Type.Number({ description: '\u534F\u4F5C\u6A21\u5F0F\uFF1A1=\u6307\u5B9A\u4EBA\u5458/\u56E2\u961F, 2=\u5168\u90E8\u7BA1\u7406\u5458, 3=\u5168\u90E8\u6210\u5458\u3002\u9ED8\u8BA4 1' }),\n ),\n }),\n endpoint,\n });\n count++;\n\n // update_fixed_view \u2014 \u66F4\u65B0\u56FA\u5B9A\u89C6\u56FE\n registerProjectMcpTool(api, {\n name: 'feishu_project_update_fixed_view',\n mcpToolName: 'update_fixed_view',\n label: 'Feishu Project: \u66F4\u65B0\u56FA\u5B9A\u89C6\u56FE',\n description: '\u66F4\u65B0\u56FA\u5B9A\u89C6\u56FE\u7684\u5DE5\u4F5C\u9879\u5217\u8868\uFF08\u65B0\u589E\u6216\u5220\u9664\uFF0C\u4E0D\u80FD\u540C\u65F6\u64CD\u4F5C\uFF09\u3002',\n schema: Type.Object({\n project_key: Type.String({ description: '\u7A7A\u95F4 projectKey' }),\n view_id: Type.String({ description: '\u56FA\u5B9A\u89C6\u56FE ID' }),\n work_item_type_key: Type.String({ description: '\u5DE5\u4F5C\u9879\u7C7B\u578B key' }),\n add_work_item_ids: Type.Optional(\n Type.Array(Type.Number(), { description: '\u9700\u8981\u6DFB\u52A0\u7684\u5DE5\u4F5C\u9879 ID \u5217\u8868\uFF0C\u4E0A\u9650 200' }),\n ),\n remove_work_item_ids: Type.Optional(\n Type.Array(Type.Number(), { description: '\u9700\u8981\u5220\u9664\u7684\u5DE5\u4F5C\u9879 ID \u5217\u8868\uFF0C\u4E0A\u9650 200' }),\n ),\n }),\n endpoint,\n });\n count++;\n\n log.info(`registered ${count} project MCP tools with endpoint=${endpoint}`);\n}\n"],
5
+ "mappings": "AAeA,SAAS,YAAY;AAErB,SAAS,UAAU,2BAA2B;AAC9C,SAAS,mBAAmB,wBAAwB;AACpD,SAAS,uBAAuB,uBAAuB,oBAAoB,0BAA0B;AAErG,SAAS,2BAA2B;AACpC,SAAS,kCAAkC;AAC3C,SAAS,iBAAiB;AAC1B,SAAS,kBAAkB;AAC3B,SAAS,kBAAkB;AAE3B,MAAM,MAAM,WAAW,mBAAmB;AAM1C,eAAe,mBACb,MACA,MACA,YACA,aACA,UACkB;AAClB,QAAM,OAAO;AAAA,IACX,SAAS;AAAA,IACT,IAAI;AAAA,IACJ,QAAQ;AAAA,IACR,QAAQ,EAAE,MAAM,WAAW,KAAK;AAAA,EAClC;AAEA,QAAM,MAAM,MAAM,MAAM,UAAU;AAAA,IAChC,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,iBAAiB,UAAU,WAAW;AAAA,IACxC;AAAA,IACA,MAAM,KAAK,UAAU,IAAI;AAAA,EAC3B,CAAC;AAED,QAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,MAAM,YAAY,IAAI,MAAM,IAAI,IAAI,UAAU,KAAK,KAAK,MAAM,GAAG,GAAI,CAAC,EAAE;AAAA,EACpF;AAEA,MAAI;AACJ,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,UAAM,IAAI,MAAM,oCAAgB,KAAK,MAAM,GAAG,GAAI,CAAC,EAAE;AAAA,EACvD;AAEA,MAAI,WAAW,MAAM;AACnB,UAAM,IAAI,MAAM,aAAa,KAAK,MAAM,IAAI,KAAK,KAAK,MAAM,OAAO,EAAE;AAAA,EACvE;AAEA,SAAO,oBAAoB,KAAK,MAAM;AACxC;AAeO,SAAS,uBACd,KACA,QACM;AACN,QAAM,EAAE,KAAK,QAAQ,IAAI,kBAAkB,KAAK,OAAO,IAAI;AAE3D,MAAI;AAAA,IACF;AAAA,MACE,MAAM,OAAO;AAAA,MACb,OAAO,OAAO;AAAA,MACd,aAAa,OAAO;AAAA,MACpB,YAAY,OAAO;AAAA,MACnB,MAAM,QAAQ,YAAY,QAAQ;AAChC,cAAM,IAAI;AACV,YAAI;AACF,gBAAM,YAAY,KAAK,IAAI;AAE3B,gBAAM,SAAS,UAAU;AACzB,gBAAM,eAAe,QAAQ;AAC7B,cAAI,CAAC,cAAc;AACjB,mBAAO,iBAAiB;AAAA,cACtB,OAAO;AAAA,YACT,CAAC;AAAA,UACH;AAEA,gBAAM,gBAAgB,2BAA2B,WAAW,QAAQ,OAAO,WAAW,CAAC;AACvF,cAAI,CAAC,eAAe;AAClB,mBAAO,iBAAiB;AAAA,cACtB,OAAO;AAAA,cACP,SAAS;AAAA,YACX,CAAC;AAAA,UACH;AAEA,gBAAM,WAAW,MAAM,mBAAmB,cAAc,aAAa;AACrE,cAAI,CAAC,UAAU;AACb,mBAAO,iBAAiB;AAAA,cACtB,OAAO;AAAA,cACP,SAAS;AAAA,YACX,CAAC;AAAA,UACH;AAEA,cAAI,QAAQ,MAAM,sBAAsB,UAAU,YAAY;AAC9D,cAAI,CAAC,OAAO;AACV,mBAAO,iBAAiB;AAAA,cACtB,OAAO;AAAA,cACP,SAAS;AAAA,YACX,CAAC;AAAA,UACH;AAGA,cAAI,MAAM,UAAU,iBAAiB,MAAM,WAAW,eAAe;AACnE,mBAAO,iBAAiB;AAAA,cACtB,OAAO;AAAA,cACP,SAAS,iFAAgB,aAAa,+DAAa,MAAM,MAAM;AAAA,YACjE,CAAC;AAAA,UACH;AAEA,gBAAM,SAAS,mBAAmB,KAAK;AACvC,cAAI,WAAW,WAAW;AACxB,mBAAO,iBAAiB;AAAA,cACtB,OAAO;AAAA,cACP,SAAS;AAAA,YACX,CAAC;AAAA,UACH;AAGA,cAAI,WAAW,iBAAiB;AAC9B,gBAAI;AACF,oBAAM,aAAa,OAAO,OAAO,aAAa,aAAa,OAAO,SAAS,IAAI,OAAO;AACtF,oBAAM,YAAY,MAAM,oBAAoB,YAAY,MAAM,cAAc,QAAQ;AACpF,sBAAQ;AAAA,gBACN,GAAG;AAAA,gBACH,aAAa,UAAU;AAAA,gBACvB,cAAc,UAAU,iBAAiB,MAAM;AAAA,gBAC/C,WAAW,KAAK,IAAI,KAAK,UAAU,cAAc,QAAQ;AAAA,gBACzD,OAAO,UAAU,SAAS,MAAM;AAAA,gBAChC,QAAQ;AAAA,cACV;AACA,oBAAM,sBAAsB,KAA2C;AACvE,sBAAQ,QAAQ,yBAAyB;AAAA,YAC3C,SAAS,YAAY;AACnB,sBAAQ,MAAM,iCAAiC,UAAU,EAAE;AAC3D,qBAAO,iBAAiB;AAAA,gBACtB,OAAO;AAAA,gBACP,SAAS;AAAA,cACX,CAAC;AAAA,YACH;AAAA,UACF;AAEA,gBAAM,mBAAmB,OAAO,OAAO,aAAa,aAAa,OAAO,SAAS,IAAI,OAAO;AAE5F,gBAAM,SAAS,MAAM;AAAA,YACnB,OAAO;AAAA,YACP;AAAA,YACA;AAAA,YACA,MAAM;AAAA,YACN;AAAA,UACF;AAEA,gBAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,kBAAQ,QAAQ,GAAG,OAAO,WAAW,iBAAiB,QAAQ,IAAI;AAGlE,cAAI,SAAS,MAAM,KAAK,MAAM,QAAS,OAAmC,OAAO,GAAG;AAClF,kBAAM,aAAc,OAAmC;AAIvD,gBAAI,UAAmB;AACvB,gBAAI,WAAW,WAAW,KAAK,WAAW,CAAC,GAAG,SAAS,QAAQ;AAC7D,kBAAI;AACF,0BAAU,KAAK,MAAM,WAAW,CAAC,EAAE,IAAI;AAAA,cACzC,QAAQ;AAAA,cAER;AAAA,YACF;AACA,mBAAO;AAAA,cACL,SAAS,WAAW,IAAI,CAAC,OAAO;AAAA,gBAC9B,MAAM;AAAA,gBACN,MAAM,EAAE;AAAA,cACV,EAAE;AAAA,cACF;AAAA,YACF;AAAA,UACF;AACA,iBAAO,iBAAiB,MAAM;AAAA,QAChC,SAAS,KAAK;AACZ,gBAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,kBAAQ,MAAM,GAAG,OAAO,WAAW,YAAY,MAAM,EAAE;AACvD,iBAAO,iBAAiB;AAAA,YACtB,OAAO;AAAA,YACP,MAAM;AAAA,UACR,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,IACA,EAAE,MAAM,OAAO,KAAK;AAAA,EACtB;AACF;AAMO,SAAS,qBAAqB,KAAwB,UAAmC;AAC9F,MAAI,QAAQ;AAGZ,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aACE;AAAA,IAEF,QAAQ,KAAK,OAAO;AAAA,MAClB,QAAQ,KAAK,OAAO;AAAA,QAClB,aAAa;AAAA,MACf,CAAC;AAAA,MACD,WAAW,KAAK;AAAA,QACd,KAAK,OAAO,EAAE,aAAa,4IAAmC,CAAC;AAAA,MACjE;AAAA,MACA,UAAU,KAAK;AAAA,QACb,KAAK,OAAO,EAAE,aAAa,kFAAsB,CAAC;AAAA,MACpD;AAAA,IACF,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAGA,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aACE;AAAA,IAGF,QAAQ,KAAK,OAAO;AAAA,MAClB,aAAa,KAAK,OAAO;AAAA,QACvB,aAAa;AAAA,MACf,CAAC;AAAA,MACD,KAAK,KAAK;AAAA,QACR,KAAK,OAAO;AAAA,UACV,aACE;AAAA,QACJ,CAAC;AAAA,MACH;AAAA,MACA,QAAQ,KAAK;AAAA,QACX,KAAK,OAAO;AAAA,UACV,aAAa;AAAA,QACf,CAAC;AAAA,MACH;AAAA,MACA,YAAY,KAAK;AAAA,QACf,KAAK,OAAO,EAAE,aAAa,oGAA8B,CAAC;AAAA,MAC5D;AAAA,MACA,uBAAuB,KAAK;AAAA,QAC1B,KAAK;AAAA,UACH,KAAK,OAAO;AAAA,YACV,UAAU,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,kBAAQ,CAAC,CAAC;AAAA,YAC7D,UAAU,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,0CAAY,CAAC,CAAC;AAAA,UACnE,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAGA,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,QAAQ,KAAK,OAAO;AAAA,MAClB,aAAa,KAAK;AAAA,QAChB,KAAK,OAAO,EAAE,aAAa,0BAAgB,CAAC;AAAA,MAC9C;AAAA,MACA,gBAAgB,KAAK,OAAO;AAAA,QAC1B,aAAa;AAAA,MACf,CAAC;AAAA,IACH,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAGA,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,QAAQ,KAAK,OAAO;AAAA,MAClB,aAAa,KAAK;AAAA,QAChB,KAAK,OAAO,EAAE,aAAa,4CAA6B,CAAC;AAAA,MAC3D;AAAA,MACA,cAAc,KAAK,OAAO;AAAA,QACxB,aAAa;AAAA,MACf,CAAC;AAAA,MACD,QAAQ,KAAK;AAAA,QACX,KAAK,MAAM,KAAK,OAAO,EAAE,aAAa,uDAA8B,CAAC,CAAC;AAAA,MACxE;AAAA,IACF,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAGA,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,QAAQ,KAAK,OAAO;AAAA,MAClB,aAAa,KAAK,OAAO,EAAE,aAAa,0BAAgB,CAAC;AAAA,MACzD,oBAAoB,KAAK,OAAO,EAAE,aAAa,qCAAY,CAAC;AAAA,IAC9D,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAGA,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,QAAQ,KAAK,OAAO;AAAA,MAClB,aAAa,KAAK;AAAA,QAChB,KAAK,OAAO,EAAE,aAAa,4CAA6B,CAAC;AAAA,MAC3D;AAAA,MACA,gBAAgB,KAAK,OAAO;AAAA,QAC1B,aAAa;AAAA,MACf,CAAC;AAAA,MACD,QAAQ,KAAK;AAAA,QACX,KAAK;AAAA,UACH,KAAK,OAAO;AAAA,YACV,WAAW,KAAK,OAAO,EAAE,aAAa,mBAAS,CAAC;AAAA,YAChD,aAAa,KAAK,OAAO;AAAA,cACvB,aAAa;AAAA,YACf,CAAC;AAAA,UACH,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAGA,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,QAAQ,KAAK,OAAO;AAAA,MAClB,aAAa,KAAK;AAAA,QAChB,KAAK,OAAO,EAAE,aAAa,0BAAgB,CAAC;AAAA,MAC9C;AAAA,MACA,cAAc,KAAK,OAAO;AAAA,QACxB,aAAa;AAAA,MACf,CAAC;AAAA,MACD,QAAQ,KAAK;AAAA,QACX,KAAK;AAAA,UACH,KAAK,OAAO;AAAA,YACV,WAAW,KAAK,OAAO,EAAE,aAAa,mBAAS,CAAC;AAAA,YAChD,aAAa,KAAK,OAAO;AAAA,cACvB,aAAa;AAAA,YACf,CAAC;AAAA,UACH,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAGA,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,QAAQ,KAAK,OAAO;AAAA,MAClB,aAAa,KAAK;AAAA,QAChB,KAAK,OAAO,EAAE,aAAa,0BAAgB,CAAC;AAAA,MAC9C;AAAA,MACA,cAAc,KAAK,OAAO;AAAA,QACxB,aAAa;AAAA,MACf,CAAC;AAAA,MACD,gBAAgB,KAAK;AAAA,QACnB,KAAK,OAAO,EAAE,aAAa,iCAAQ,CAAC;AAAA,MACtC;AAAA,MACA,iBAAiB,KAAK,OAAO;AAAA,QAC3B,aAAa;AAAA,MACf,CAAC;AAAA,MACD,KAAK,KAAK;AAAA,QACR,KAAK,OAAO,EAAE,aAAa,4FAAgC,CAAC;AAAA,MAC9D;AAAA,IACF,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAGA,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,QAAQ,KAAK,OAAO;AAAA,MAClB,aAAa,KAAK;AAAA,QAChB,KAAK,OAAO,EAAE,aAAa,0BAAgB,CAAC;AAAA,MAC9C;AAAA,MACA,cAAc,KAAK,OAAO;AAAA,QACxB,aAAa;AAAA,MACf,CAAC;AAAA,MACD,SAAS,KAAK,OAAO;AAAA,QACnB,aAAa;AAAA,MACf,CAAC;AAAA,IACH,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAGA,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,QAAQ,KAAK,OAAO;AAAA,MAClB,aAAa,KAAK;AAAA,QAChB,KAAK,OAAO,EAAE,aAAa,0BAAgB,CAAC;AAAA,MAC9C;AAAA,MACA,cAAc,KAAK,OAAO,EAAE,aAAa,wBAAS,CAAC;AAAA,MACnD,SAAS,KAAK,OAAO,EAAE,aAAa,gDAAa,CAAC;AAAA,MAClD,UAAU,KAAK;AAAA,QACb,KAAK,MAAM,KAAK,OAAO,EAAE,aAAa,6DAAgB,CAAC,CAAC;AAAA,MAC1D;AAAA,IACF,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAGA,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,QAAQ,KAAK,OAAO;AAAA,MAClB,aAAa,KAAK,OAAO,EAAE,aAAa,0BAAgB,CAAC;AAAA,MACzD,cAAc,KAAK,OAAO,EAAE,aAAa,wBAAS,CAAC;AAAA,MACnD,oBAAoB,KAAK,OAAO,EAAE,aAAa,qCAAY,CAAC;AAAA,MAC5D,UAAU,KAAK,OAAO,EAAE,aAAa,uBAAa,CAAC;AAAA,IACrD,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAGA,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,QAAQ,KAAK,OAAO;AAAA,MAClB,aAAa,KAAK,OAAO,EAAE,aAAa,0BAAgB,CAAC;AAAA,IAC3D,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAGA,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,QAAQ,KAAK,OAAO;AAAA,MAClB,aAAa,KAAK,OAAO;AAAA,QACvB,aAAa;AAAA,MACf,CAAC;AAAA,IACH,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAGA,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,QAAQ,KAAK,OAAO;AAAA,MAClB,WAAW,KAAK;AAAA,QACd,KAAK,OAAO,EAAE,aAAa,wCAAoB,CAAC;AAAA,MAClD;AAAA,MACA,aAAa,KAAK;AAAA,QAChB,KAAK,OAAO,EAAE,aAAa,0BAAgB,CAAC;AAAA,MAC9C;AAAA,IACF,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAGA,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,QAAQ,KAAK,OAAO;AAAA,MAClB,aAAa,KAAK,OAAO,EAAE,aAAa,0BAAgB,CAAC;AAAA,MACzD,cAAc,KAAK,OAAO,EAAE,aAAa,2CAAa,CAAC;AAAA,MACvD,UAAU,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,mCAAU,CAAC,CAAC;AAAA,MAC/D,YAAY,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,oEAAkB,CAAC,CAAC;AAAA,MACzE,UAAU,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,oEAAkB,CAAC,CAAC;AAAA,IACzE,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAGA,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,QAAQ,KAAK,OAAO;AAAA,MAClB,aAAa,KAAK,OAAO,EAAE,aAAa,0BAAgB,CAAC;AAAA,MACzD,YAAY,KAAK,OAAO;AAAA,QACtB,aAAa;AAAA,MACf,CAAC;AAAA,MACD,UAAU,KAAK,OAAO,EAAE,aAAa,6CAAU,CAAC;AAAA,IAClD,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAGA,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,QAAQ,KAAK,OAAO;AAAA,MAClB,SAAS,KAAK,OAAO,EAAE,aAAa,kBAAQ,CAAC;AAAA,MAC7C,aAAa,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,0BAAgB,CAAC,CAAC;AAAA,MACxE,QAAQ,KAAK;AAAA,QACX,KAAK,MAAM,KAAK,OAAO,EAAE,aAAa,uDAA8B,CAAC,CAAC;AAAA,MACxE;AAAA,MACA,UAAU,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,2BAAO,CAAC,CAAC;AAAA,IAC9D,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAGA,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,QAAQ,KAAK,OAAO;AAAA,MAClB,aAAa,KAAK,OAAO,EAAE,aAAa,0BAAgB,CAAC;AAAA,MACzD,oBAAoB,KAAK,OAAO,EAAE,aAAa,qCAAY,CAAC;AAAA,MAC5D,cAAc,KAAK,OAAO,EAAE,aAAa,oCAAW,CAAC;AAAA,MACrD,UAAU,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,4DAAe,CAAC,CAAC;AAAA,IACtE,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAGA,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,QAAQ,KAAK,OAAO;AAAA,MAClB,aAAa,KAAK,OAAO,EAAE,aAAa,0BAAgB,CAAC;AAAA,MACzD,cAAc,KAAK,OAAO,EAAE,aAAa,wBAAS,CAAC;AAAA,MACnD,OAAO,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,qEAAc,CAAC,CAAC;AAAA,MAChE,KAAK,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,yHAA+B,CAAC,CAAC;AAAA,MAC/E,gBAAgB,KAAK;AAAA,QACnB,KAAK,MAAM,KAAK,OAAO,EAAE,aAAa,oGAA2E,CAAC,CAAC;AAAA,MACrH;AAAA,MACA,YAAY,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,sFAA0B,CAAC,CAAC;AAAA,IACnF,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAGA,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,QAAQ,KAAK,OAAO;AAAA,MAClB,aAAa,KAAK,OAAO,EAAE,aAAa,8DAAgC,CAAC;AAAA,MACzE,WAAW,KAAK;AAAA,QACd,KAAK,OAAO,EAAE,aAAa,uFAA2B,CAAC;AAAA,MACzD;AAAA,MACA,YAAY,KAAK,OAAO,EAAE,aAAa,wDAAqB,CAAC;AAAA,MAC7D,UAAU,KAAK,OAAO,EAAE,aAAa,2GAAgC,CAAC;AAAA,MACtE,qBAAqB,KAAK;AAAA,QACxB,KAAK,MAAM,KAAK,OAAO,EAAE,aAAa,2FAA0B,CAAC,CAAC;AAAA,MACpE;AAAA,IACF,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAGA,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,QAAQ,KAAK,OAAO;AAAA,MAClB,aAAa,KAAK,OAAO,EAAE,aAAa,0BAAgB,CAAC;AAAA,MACzD,oBAAoB,KAAK,OAAO,EAAE,aAAa,uCAAS,CAAC;AAAA,MACzD,cAAc,KAAK,OAAO,EAAE,aAAa,0CAAY,CAAC;AAAA,MACtD,6BAA6B,KAAK,OAAO,EAAE,aAAa,+DAAa,CAAC;AAAA,MACtE,cAAc,KAAK,OAAO,EAAE,aAAa,8DAAiB,CAAC;AAAA,MAC3D,eAAe,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,iHAA4B,CAAC,CAAC;AAAA,MACtF,UAAU,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,0CAAY,CAAC,CAAC;AAAA,MACjE,WAAW,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,gDAAa,CAAC,CAAC;AAAA,IACrE,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAGA,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,QAAQ,KAAK,OAAO;AAAA,MAClB,aAAa,KAAK,OAAO,EAAE,aAAa,0BAAgB,CAAC;AAAA,IAC3D,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAGA,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,QAAQ,KAAK,OAAO;AAAA,MAClB,aAAa,KAAK,OAAO,EAAE,aAAa,0BAAgB,CAAC;AAAA,MACzD,SAAS,KAAK,OAAO,EAAE,aAAa,kBAAQ,CAAC;AAAA,MAC7C,YAAY,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,mDAAgB,CAAC,CAAC;AAAA,IACzE,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAGA,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,QAAQ,KAAK,OAAO;AAAA,MAClB,aAAa,KAAK,OAAO,EAAE,aAAa,0BAAgB,CAAC;AAAA,MACzD,SAAS,KAAK,OAAO,EAAE,aAAa,kBAAQ,CAAC;AAAA,MAC7C,UAAU,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,mCAAU,CAAC,CAAC;AAAA,MAC/D,WAAW,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,sEAAoB,CAAC,CAAC;AAAA,IAC5E,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAGA,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,QAAQ,KAAK,OAAO;AAAA,MAClB,aAAa,KAAK,OAAO,EAAE,aAAa,0BAAgB,CAAC;AAAA,MACzD,MAAM,KAAK,OAAO,EAAE,aAAa,uCAAS,CAAC;AAAA,MAC3C,oBAAoB,KAAK,OAAO,EAAE,aAAa,qCAAY,CAAC;AAAA,MAC5D,mBAAmB,KAAK;AAAA,QACtB,KAAK,OAAO,EAAE,aAAa,wBAAS,CAAC;AAAA,QACrC,EAAE,aAAa,kHAA6B;AAAA,MAC9C;AAAA,MACA,kBAAkB,KAAK;AAAA,QACrB,KAAK,OAAO,EAAE,aAAa,0JAAuC,CAAC;AAAA,MACrE;AAAA,IACF,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAGA,yBAAuB,KAAK;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,QAAQ,KAAK,OAAO;AAAA,MAClB,aAAa,KAAK,OAAO,EAAE,aAAa,0BAAgB,CAAC;AAAA,MACzD,SAAS,KAAK,OAAO,EAAE,aAAa,8BAAU,CAAC;AAAA,MAC/C,oBAAoB,KAAK,OAAO,EAAE,aAAa,qCAAY,CAAC;AAAA,MAC5D,mBAAmB,KAAK;AAAA,QACtB,KAAK,MAAM,KAAK,OAAO,GAAG,EAAE,aAAa,yFAAwB,CAAC;AAAA,MACpE;AAAA,MACA,sBAAsB,KAAK;AAAA,QACzB,KAAK,MAAM,KAAK,OAAO,GAAG,EAAE,aAAa,yFAAwB,CAAC;AAAA,MACpE;AAAA,IACF,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AACD;AAEA,MAAI,KAAK,cAAc,KAAK,oCAAoC,QAAQ,EAAE;AAC5E;",
6
6
  "names": []
7
7
  }
@@ -388,13 +388,32 @@ function buildProjectDomainCard() {
388
388
  function buildProjectDomainConfirmedCard(envLabel) {
389
389
  return {
390
390
  schema: "2.0",
391
- config: { wide_screen_mode: false },
391
+ config: {
392
+ wide_screen_mode: false,
393
+ style: {
394
+ color: {
395
+ "light-green-bg": {
396
+ light_mode: "rgba(52, 199, 89, 0.12)",
397
+ dark_mode: "rgba(52, 199, 89, 0.08)"
398
+ }
399
+ }
400
+ }
401
+ },
392
402
  header: {
393
- title: { tag: "plain_text", content: "\u73AF\u5883\u5DF2\u786E\u8BA4" },
394
- subtitle: { tag: "plain_text", content: "" },
403
+ title: {
404
+ tag: "plain_text",
405
+ content: "\u73AF\u5883\u5DF2\u786E\u8BA4"
406
+ },
407
+ subtitle: {
408
+ tag: "plain_text",
409
+ content: ""
410
+ },
395
411
  template: "green",
396
412
  padding: "12px 12px 12px 12px",
397
- icon: { tag: "standard_icon", token: "yes_filled" }
413
+ icon: {
414
+ tag: "standard_icon",
415
+ token: "yes_filled"
416
+ }
398
417
  },
399
418
  body: {
400
419
  elements: [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/tools/oauth-cards.ts"],
4
- "sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * oauth-cards.ts \u2014 OAuth \u6388\u6743\u5361\u7247\u6784\u5EFA\u51FD\u6570\u3002\n *\n * \u4ECE oauth.ts \u63D0\u53D6\u7684\u7EAF UI \u51FD\u6570\uFF0C\u4E0E OAuth \u4E1A\u52A1\u6D41\u7A0B\u89E3\u8026\u3002\n */\n\n// ---------------------------------------------------------------------------\n// Card builders\n// ---------------------------------------------------------------------------\n\nexport function buildAuthCard(params: {\n verificationUriComplete: string;\n expiresMin: number;\n scope?: string;\n isBatchAuth?: boolean;\n totalAppScopes?: number;\n alreadyGranted?: number;\n batchInfo?: string;\n filteredScopes?: string[]; // \u88AB\u8FC7\u6EE4\u7684 scope\uFF08\u5E94\u7528\u672A\u5F00\u901A\uFF09\n appId?: string; // \u7528\u4E8E\u751F\u6210\u6743\u9650\u7BA1\u7406\u94FE\u63A5\n showBatchAuthHint?: boolean; // \u4EC5 auto-auth \u6D41\u7A0B\u5C55\u793A\u6279\u91CF\u6388\u6743\u63D0\u793A\n}): Record<string, unknown> {\n const {\n verificationUriComplete,\n expiresMin,\n scope,\n isBatchAuth,\n totalAppScopes,\n alreadyGranted,\n batchInfo,\n filteredScopes,\n appId,\n showBatchAuthHint,\n } = params;\n const inAppUrl = toInAppWebUrl(verificationUriComplete);\n const multiUrl = {\n url: inAppUrl,\n pc_url: inAppUrl,\n android_url: inAppUrl,\n ios_url: inAppUrl,\n };\n\n // \u5C06 scope \u8F6C\u6210\u53EF\u8BFB\u8BF4\u660E\n const scopeDesc = formatScopeDescription(\n scope,\n isBatchAuth,\n totalAppScopes,\n alreadyGranted,\n batchInfo,\n filteredScopes,\n appId,\n );\n\n const elements: Record<string, unknown>[] = [\n // \u6388\u6743\u8BF4\u660E\n {\n tag: 'markdown',\n content: scopeDesc,\n text_size: 'normal',\n },\n // \u6388\u6743\u6309\u94AE\uFF08small\uFF0C\u9760\u53F3\uFF09\n {\n tag: 'column_set',\n flex_mode: 'none',\n horizontal_align: 'right',\n columns: [\n {\n tag: 'column',\n width: 'auto',\n elements: [\n {\n tag: 'button',\n text: { tag: 'plain_text', content: '\u524D\u5F80\u6388\u6743' },\n type: 'primary',\n size: 'medium',\n multi_url: multiUrl,\n },\n ],\n },\n ],\n },\n // \u5931\u6548\u65F6\u95F4\u63D0\u9192\n {\n tag: 'markdown',\n content: `<font color='grey'>\u6388\u6743\u94FE\u63A5\u5C06\u5728 ${expiresMin} \u5206\u949F\u540E\u5931\u6548\uFF0C\u5C4A\u65F6\u9700\u91CD\u65B0\u53D1\u8D77</font>`,\n text_size: 'notation',\n },\n // \u6279\u91CF\u6388\u6743\u63D0\u793A\uFF08\u4EC5 auto-auth \u6D41\u7A0B\u5C55\u793A\uFF09\n ...(showBatchAuthHint\n ? [\n {\n tag: 'markdown',\n content:\n \"<font color='grey'>\uD83D\uDCA1\u5982\u679C\u4F60\u5E0C\u671B\u4E00\u6B21\u6027\u6388\u4E88\u6240\u6709\u63D2\u4EF6\u6240\u9700\u8981\u7684\u6743\u9650\uFF0C\u53EF\u4EE5\u544A\u8BC9\u6211\u300C\u6388\u4E88\u6240\u6709\u7528\u6237\u6743\u9650\u300D\uFF0C\u6211\u4F1A\u534F\u52A9\u4F60\u5B8C\u6210\u3002</font>\",\n text_size: 'notation',\n },\n ]\n : []),\n ];\n\n return {\n schema: '2.0',\n config: {\n wide_screen_mode: false,\n style: {\n color: {\n 'light-yellow-bg': {\n light_mode: 'rgba(255, 214, 102, 0.12)',\n dark_mode: 'rgba(255, 214, 102, 0.08)',\n },\n },\n },\n },\n header: {\n title: {\n tag: 'plain_text',\n content: '\u9700\u8981\u60A8\u7684\u6388\u6743\u624D\u80FD\u7EE7\u7EED',\n },\n subtitle: {\n tag: 'plain_text',\n content: '',\n },\n template: 'blue',\n padding: '12px 12px 12px 12px',\n icon: {\n tag: 'standard_icon',\n token: 'lock-chat_filled',\n },\n },\n body: { elements },\n };\n}\n\n/** scope \u5B57\u7B26\u4E32 \u2192 \u53EF\u8BFB\u63CF\u8FF0 */\nexport function formatScopeDescription(\n scope?: string,\n isBatchAuth?: boolean,\n totalAppScopes?: number,\n alreadyGranted?: number,\n batchInfo?: string,\n _filteredScopes?: string[],\n _appId?: string,\n): string {\n const scopes = scope?.split(/\\s+/).filter(Boolean);\n\n if (isBatchAuth && scopes && scopes.length > 0) {\n let message = `\u5E94\u7528\u9700\u8981\u6388\u6743 **${scopes.length}** \u4E2A\u7528\u6237\u6743\u9650\uFF08\u5171 ${totalAppScopes} \u4E2A\uFF0C\u5DF2\u6388\u6743 ${alreadyGranted} \u4E2A\uFF09\u3002`;\n\n // \u5982\u679C\u8D85\u8FC7 5 \u4E2A scope\uFF0C\u53EA\u663E\u793A\u524D 3 \u4E2A\uFF0C\u7136\u540E\u7528\"...\"\u8868\u793A\n if (scopes.length > 5) {\n const previewScopes = scopes.slice(0, 3).join('\\n');\n message += `\\n\\n**\u5C06\u8981\u6388\u6743\u7684\u6743\u9650**\uFF1A\\n${previewScopes}\\n...\\n`;\n } else {\n const scopeList = scopes.map((s, idx) => `${idx + 1}. ${s}`).join('\\n');\n message += `\\n\\n**\u5C06\u8981\u6388\u6743\u7684\u6743\u9650\u5217\u8868**\uFF1A\\n${scopeList}\\n`;\n }\n\n // \u6DFB\u52A0\u5206\u6279\u63D0\u793A\u4FE1\u606F\n if (batchInfo) {\n message += `\\n\\n${batchInfo}`;\n }\n\n return message;\n }\n\n const desc = '\u6388\u6743\u540E\uFF0C\u5E94\u7528\u5C06\u80FD\u591F\u4EE5\u60A8\u7684\u8EAB\u4EFD\u6267\u884C\u76F8\u5173\u64CD\u4F5C\u3002';\n if (!scopes?.length) return desc;\n\n const message = desc + '\\n\\n\u6240\u9700\u6743\u9650\uFF1A\\n' + scopes.map((s) => `- ${s}`).join('\\n');\n\n return message;\n}\n\nexport function toInAppWebUrl(targetUrl: string): string {\n const encoded = encodeURIComponent(targetUrl);\n const lkMeta = encodeURIComponent(\n JSON.stringify({\n 'page-meta': {\n showNavBar: 'false',\n showBottomNavBar: 'false',\n },\n }),\n );\n return (\n 'https://applink.feishu.cn/client/web_url/open' +\n `?mode=sidebar-semi&max_width=800&reload=false&url=${encoded}&lk_meta=${lkMeta}`\n );\n}\n\n/**\n * \u98DE\u4E66\u9879\u76EE OAuth \u4E13\u7528\u6388\u6743\u5361\u7247\u3002\n *\n * \u4E24\u6B65\u5F0F\uFF1A\u2460 \u524D\u5F80\u6388\u6743\u6309\u94AE \u2461 URL \u7C98\u8D34\u8F93\u5165\u6846 + \u63D0\u4EA4\u6309\u94AE\uFF08\u901A\u8FC7\u5361\u7247\u56DE\u8C03\u81EA\u52A8\u5B8C\u6210\uFF09\n */\nexport function buildProjectAuthCard(params: {\n authorizationUrl: string;\n expiresMin: number;\n}): Record<string, unknown> {\n const { authorizationUrl, expiresMin } = params;\n // \u4E0D\u4F7F\u7528 applink \u5305\u88C5\uFF1A\u670D\u52A1\u7AEF\u901A\u8FC7 redirect_mode=manual \u76F4\u63A5\u5C55\u793A\u590D\u5236\u9875\u9762\uFF0C\n // \u5728\u98DE\u4E66\u5185\u7F6E\u6D4F\u89C8\u5668\u4E2D\u5373\u53EF\u6B63\u5E38\u5B8C\u6210\uFF0C\u65E0\u9700\u8DF3\u8F6C\u7CFB\u7EDF\u6D4F\u89C8\u5668\u3002\n const multiUrl = {\n url: authorizationUrl,\n pc_url: authorizationUrl,\n android_url: authorizationUrl,\n ios_url: authorizationUrl,\n };\n\n return {\n schema: '2.0',\n config: { wide_screen_mode: false },\n header: {\n title: { tag: 'plain_text', content: '\u98DE\u4E66\u9879\u76EE\u9700\u8981\u60A8\u7684\u6388\u6743' },\n subtitle: { tag: 'plain_text', content: '' },\n template: 'blue',\n padding: '12px 12px 12px 12px',\n icon: { tag: 'standard_icon', token: 'lock-chat_filled' },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content: '\u6388\u6743\u540E\uFF0C\u5E94\u7528\u5C06\u80FD\u591F\u4EE5\u60A8\u7684\u8EAB\u4EFD\u6267\u884C\u98DE\u4E66\u9879\u76EE\u76F8\u5173\u64CD\u4F5C\u3002',\n text_size: 'normal',\n },\n // Step 1: navigate to auth page\n {\n tag: 'column_set',\n flex_mode: 'none',\n horizontal_spacing: 'default',\n columns: [\n {\n tag: 'column',\n width: 'weighted',\n weight: 3,\n vertical_align: 'center',\n elements: [{ tag: 'markdown', content: '**\u7B2C\u4E00\u6B65\uFF1A\u70B9\u51FB\u6309\u94AE\uFF0C\u5728\u6D4F\u89C8\u5668\u4E2D\u5B8C\u6210\u6388\u6743**' }],\n },\n {\n tag: 'column',\n width: 'weighted',\n weight: 1,\n vertical_align: 'center',\n elements: [{\n tag: 'button',\n text: { tag: 'plain_text', content: '\u524D\u5F80\u6388\u6743' },\n type: 'primary',\n size: 'medium',\n multi_url: multiUrl,\n }],\n },\n ],\n },\n { tag: 'hr' },\n // Step 2: paste callback URL\n {\n tag: 'markdown',\n content:\n '**\u7B2C\u4E8C\u6B65\uFF1A\u7C98\u8D34\u6D4F\u89C8\u5668\u5730\u5740\u680F URL**\\n' +\n \"<font color='grey'>\u6388\u6743\u540E\u6D4F\u89C8\u5668\u4F1A\u8DF3\u8F6C\u5230\u4E00\u4E2A\u65E0\u6CD5\u6253\u5F00\u7684\u9875\u9762\uFF0C\u8FD9\u662F\u6B63\u5E38\u7684\u2014\u2014\u8BF7\u590D\u5236\u5730\u5740\u680F\u4E2D\u7684\u5B8C\u6574 URL \u7C98\u8D34\u5230\u4E0B\u65B9</font>\",\n text_size: 'normal',\n },\n {\n tag: 'form',\n name: `project_auth_form`,\n elements: [\n {\n tag: 'input',\n name: 'callback_url',\n placeholder: {\n tag: 'plain_text',\n content: '\u7C98\u8D34 URL\uFF0C\u5982 http://127.0.0.1:3456/callback?code=...',\n },\n max_length: 1000,\n },\n {\n tag: 'button',\n text: { tag: 'plain_text', content: '\u5B8C\u6210\u6388\u6743' },\n type: 'primary',\n form_action_type: 'submit',\n name: 'submit_project_auth',\n },\n ],\n },\n {\n tag: 'markdown',\n content: `<font color='grey'>\u6388\u6743\u94FE\u63A5\u5C06\u5728 ${expiresMin} \u5206\u949F\u540E\u5931\u6548\uFF0C\u5C4A\u65F6\u9700\u91CD\u65B0\u53D1\u8D77</font>`,\n text_size: 'notation',\n },\n ],\n },\n };\n}\n\nexport function buildAuthSuccessCard(): Record<string, unknown> {\n return {\n schema: '2.0',\n config: {\n wide_screen_mode: false,\n style: {\n color: {\n 'light-green-bg': {\n light_mode: 'rgba(52, 199, 89, 0.12)',\n dark_mode: 'rgba(52, 199, 89, 0.08)',\n },\n },\n },\n },\n header: {\n title: {\n tag: 'plain_text',\n content: '\u6388\u6743\u6210\u529F',\n },\n subtitle: {\n tag: 'plain_text',\n content: '',\n },\n template: 'green',\n padding: '12px 12px 12px 12px',\n icon: {\n tag: 'standard_icon',\n token: 'yes_filled',\n },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content:\n '\u60A8\u7684\u98DE\u4E66\u8D26\u53F7\u5DF2\u6210\u529F\u6388\u6743\uFF0C\u6B63\u5728\u4E3A\u60A8\u7EE7\u7EED\u6267\u884C\u64CD\u4F5C\u3002\\n\\n' +\n \"<font color='grey'>\u5982\u9700\u64A4\u9500\u6388\u6743\uFF0C\u53EF\u968F\u65F6\u544A\u8BC9\u6211\u3002</font>\",\n },\n ],\n },\n };\n}\n\nexport function buildAuthFailedCard(reason: string): Record<string, unknown> {\n // \u5C06\u6280\u672F\u9519\u8BEF\u8F6C\u4E3A\u7528\u6237\u53EF\u8BFB\u7684\u63D0\u793A\n let userMessage: string;\n if (reason.includes('authorization denied') || reason.includes('access_denied')) {\n userMessage = '\u6388\u6743\u88AB\u62D2\u7EDD\u6216\u5DF2\u53D6\u6D88\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77\u6388\u6743\u5E76\u786E\u8BA4\u6388\u6743\u3002';\n } else if (reason.includes('state mismatch')) {\n userMessage = '\u6388\u6743\u4F1A\u8BDD\u4E0D\u5339\u914D\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77\u6388\u6743\u3002';\n } else if (reason.includes('Token exchange failed')) {\n userMessage = '\u6388\u6743\u7801\u4EA4\u6362\u5931\u8D25\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77\u6388\u6743\u3002\u53EF\u80FD\u662F\u6388\u6743\u94FE\u63A5\u5DF2\u8FC7\u671F\u6216\u5DF2\u88AB\u4F7F\u7528\u3002';\n } else if (reason.includes('missing authorization code')) {\n userMessage = '\u56DE\u8C03 URL \u4E2D\u7F3A\u5C11\u6388\u6743\u7801\u3002\u8BF7\u786E\u4FDD\u590D\u5236\u7684\u662F\u6388\u6743\u5B8C\u6210\u540E\u6D4F\u89C8\u5668\u5730\u5740\u680F\u7684\u5B8C\u6574 URL\u3002';\n } else {\n userMessage = '\u6388\u6743\u672A\u5B8C\u6210\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77\u6388\u6743\u3002';\n }\n\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: userMessage,\n },\n ],\n },\n };\n}\n\n/**\n * \u98DE\u4E66\u9879\u76EE\u57DF\u540D\u9009\u62E9\u5361\u7247\u3002\n *\n * \u4E0B\u62C9\u9009\u62E9\u9884\u8BBE\u73AF\u5883 + \u53EF\u9009\u81EA\u5B9A\u4E49\u57DF\u540D\u8F93\u5165\u6846\uFF0C\u901A\u8FC7 form submit \u56DE\u8C03\u3002\n */\nexport function buildProjectDomainCard(): Record<string, unknown> {\n return {\n schema: '2.0',\n config: { wide_screen_mode: false },\n header: {\n title: { tag: 'plain_text', content: '\u9009\u62E9\u98DE\u4E66\u9879\u76EE\u73AF\u5883' },\n subtitle: { tag: 'plain_text', content: '' },\n template: 'blue',\n padding: '12px 12px 12px 12px',\n icon: { tag: 'standard_icon', token: 'settings_filled' },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content: '\u8BF7\u9009\u62E9\u60A8\u4F7F\u7528\u7684\u98DE\u4E66\u9879\u76EE\u73AF\u5883\uFF1A',\n text_size: 'normal',\n },\n {\n tag: 'form',\n name: 'project_domain_form',\n elements: [\n {\n tag: 'select_static',\n name: 'domain_preset',\n placeholder: { tag: 'plain_text', content: '\u9009\u62E9\u73AF\u5883' },\n options: [\n { value: 'feishu', text: { tag: 'plain_text', content: '\u98DE\u4E66\u9879\u76EE\uFF08project.feishu.cn\uFF09' } },\n { value: 'meegle', text: { tag: 'plain_text', content: 'Meegle\uFF08meegle.com\uFF09' } },\n ],\n value: { value: 'feishu', text: { tag: 'plain_text', content: '\u98DE\u4E66\u9879\u76EE\uFF08project.feishu.cn\uFF09' } },\n },\n {\n tag: 'markdown',\n content: \"\u5982\u4F7F\u7528\u79C1\u6709\u5316\u90E8\u7F72\uFF0C\u8BF7\u5728\u4E0B\u65B9\u586B\u5199\u5B8C\u6574\u57DF\u540D\uFF08\u5C06\u8986\u76D6\u4E0A\u65B9\u9009\u62E9\uFF09\uFF1A\",\n text_size: 'notation',\n },\n {\n tag: 'input',\n name: 'domain_custom',\n placeholder: { tag: 'plain_text', content: 'https://custom.example.com' },\n max_length: 200,\n },\n {\n tag: 'button',\n text: { tag: 'plain_text', content: '\u786E\u8BA4' },\n type: 'primary',\n form_action_type: 'submit',\n name: 'submit_project_domain',\n },\n ],\n },\n ],\n },\n };\n}\n\nexport function buildProjectDomainConfirmedCard(envLabel: string): Record<string, unknown> {\n return {\n schema: '2.0',\n config: { wide_screen_mode: false },\n header: {\n title: { tag: 'plain_text', content: '\u73AF\u5883\u5DF2\u786E\u8BA4' },\n subtitle: { tag: 'plain_text', content: '' },\n template: 'green',\n padding: '12px 12px 12px 12px',\n icon: { tag: 'standard_icon', token: 'yes_filled' },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content: `\u5DF2\u9009\u62E9\uFF1A**${envLabel}**\uFF0C\u6B63\u5728\u7EE7\u7EED\u6388\u6743...`,\n },\n ],\n },\n };\n}\n\nexport function buildAuthIdentityMismatchCard(): Record<string, unknown> {\n return {\n schema: '2.0',\n config: {\n wide_screen_mode: false,\n },\n header: {\n title: {\n tag: 'plain_text',\n content: '\u6388\u6743\u5931\u8D25\uFF0C\u64CD\u4F5C\u8D26\u53F7\u4E0E\u53D1\u8D77\u8D26\u53F7\u4E0D\u4E00\u81F4',\n },\n subtitle: {\n tag: 'plain_text',\n content: '',\n },\n template: 'red',\n padding: '12px 12px 12px 12px',\n icon: {\n tag: 'standard_icon',\n token: 'close_filled',\n },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content:\n '\u68C0\u6D4B\u5230\u5F53\u524D\u8FDB\u884C\u6388\u6743\u64CD\u4F5C\u7684\u98DE\u4E66\u8D26\u53F7\u4E0E\u53D1\u8D77\u6388\u6743\u8BF7\u6C42\u7684\u8D26\u53F7\u4E0D\u4E00\u81F4\u3002\u4E3A\u4FDD\u969C\u6570\u636E\u5B89\u5168\uFF0C\u672C\u6B21\u6388\u6743\u5DF2\u88AB\u62D2\u7EDD\u3002\\n\\n' +\n \"<font color='grey'>\u8BF7\u6388\u6743\u8BF7\u6C42\u7684\u53D1\u8D77\u4EBA\u4F7F\u7528\u5176\u8D26\u53F7\uFF0C\u70B9\u51FB\u6388\u6743\u94FE\u63A5\u5B8C\u6210\u6388\u6743\u3002</font>\",\n },\n ],\n },\n };\n}\n"],
5
- "mappings": "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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 // \u5C06\u6280\u672F\u9519\u8BEF\u8F6C\u4E3A\u7528\u6237\u53EF\u8BFB\u7684\u63D0\u793A\n let userMessage: string;\n if (reason.includes('authorization denied') || reason.includes('access_denied')) {\n userMessage = '\u6388\u6743\u88AB\u62D2\u7EDD\u6216\u5DF2\u53D6\u6D88\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77\u6388\u6743\u5E76\u786E\u8BA4\u6388\u6743\u3002';\n } else if (reason.includes('state mismatch')) {\n userMessage = '\u6388\u6743\u4F1A\u8BDD\u4E0D\u5339\u914D\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77\u6388\u6743\u3002';\n } else if (reason.includes('Token exchange failed')) {\n userMessage = '\u6388\u6743\u7801\u4EA4\u6362\u5931\u8D25\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77\u6388\u6743\u3002\u53EF\u80FD\u662F\u6388\u6743\u94FE\u63A5\u5DF2\u8FC7\u671F\u6216\u5DF2\u88AB\u4F7F\u7528\u3002';\n } else if (reason.includes('missing authorization code')) {\n userMessage = '\u56DE\u8C03 URL \u4E2D\u7F3A\u5C11\u6388\u6743\u7801\u3002\u8BF7\u786E\u4FDD\u590D\u5236\u7684\u662F\u6388\u6743\u5B8C\u6210\u540E\u6D4F\u89C8\u5668\u5730\u5740\u680F\u7684\u5B8C\u6574 URL\u3002';\n } else {\n userMessage = '\u6388\u6743\u672A\u5B8C\u6210\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77\u6388\u6743\u3002';\n }\n\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: userMessage,\n },\n ],\n },\n };\n}\n\n/**\n * \u98DE\u4E66\u9879\u76EE\u57DF\u540D\u9009\u62E9\u5361\u7247\u3002\n *\n * \u4E0B\u62C9\u9009\u62E9\u9884\u8BBE\u73AF\u5883 + \u53EF\u9009\u81EA\u5B9A\u4E49\u57DF\u540D\u8F93\u5165\u6846\uFF0C\u901A\u8FC7 form submit \u56DE\u8C03\u3002\n */\nexport function buildProjectDomainCard(): Record<string, unknown> {\n return {\n schema: '2.0',\n config: { wide_screen_mode: false },\n header: {\n title: { tag: 'plain_text', content: '\u9009\u62E9\u98DE\u4E66\u9879\u76EE\u73AF\u5883' },\n subtitle: { tag: 'plain_text', content: '' },\n template: 'blue',\n padding: '12px 12px 12px 12px',\n icon: { tag: 'standard_icon', token: 'settings_filled' },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content: '\u8BF7\u9009\u62E9\u60A8\u4F7F\u7528\u7684\u98DE\u4E66\u9879\u76EE\u73AF\u5883\uFF1A',\n text_size: 'normal',\n },\n {\n tag: 'form',\n name: 'project_domain_form',\n elements: [\n {\n tag: 'select_static',\n name: 'domain_preset',\n placeholder: { tag: 'plain_text', content: '\u9009\u62E9\u73AF\u5883' },\n options: [\n { value: 'feishu', text: { tag: 'plain_text', content: '\u98DE\u4E66\u9879\u76EE\uFF08project.feishu.cn\uFF09' } },\n { value: 'meegle', text: { tag: 'plain_text', content: 'Meegle\uFF08meegle.com\uFF09' } },\n ],\n value: { value: 'feishu', text: { tag: 'plain_text', content: '\u98DE\u4E66\u9879\u76EE\uFF08project.feishu.cn\uFF09' } },\n },\n {\n tag: 'markdown',\n content: \"\u5982\u4F7F\u7528\u79C1\u6709\u5316\u90E8\u7F72\uFF0C\u8BF7\u5728\u4E0B\u65B9\u586B\u5199\u5B8C\u6574\u57DF\u540D\uFF08\u5C06\u8986\u76D6\u4E0A\u65B9\u9009\u62E9\uFF09\uFF1A\",\n text_size: 'notation',\n },\n {\n tag: 'input',\n name: 'domain_custom',\n placeholder: { tag: 'plain_text', content: 'https://custom.example.com' },\n max_length: 200,\n },\n {\n tag: 'button',\n text: { tag: 'plain_text', content: '\u786E\u8BA4' },\n type: 'primary',\n form_action_type: 'submit',\n name: 'submit_project_domain',\n },\n ],\n },\n ],\n },\n };\n}\n\nexport function buildProjectDomainConfirmedCard(envLabel: string): Record<string, unknown> {\n return {\n schema: '2.0',\n config: {\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: '\u73AF\u5883\u5DF2\u786E\u8BA4',\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: `\u5DF2\u9009\u62E9\uFF1A**${envLabel}**\uFF0C\u6B63\u5728\u7EE7\u7EED\u6388\u6743...`,\n },\n ],\n },\n };\n}\n\nexport function buildAuthIdentityMismatchCard(): Record<string, unknown> {\n return {\n schema: '2.0',\n config: {\n wide_screen_mode: false,\n },\n header: {\n title: {\n tag: 'plain_text',\n content: '\u6388\u6743\u5931\u8D25\uFF0C\u64CD\u4F5C\u8D26\u53F7\u4E0E\u53D1\u8D77\u8D26\u53F7\u4E0D\u4E00\u81F4',\n },\n subtitle: {\n tag: 'plain_text',\n content: '',\n },\n template: 'red',\n padding: '12px 12px 12px 12px',\n icon: {\n tag: 'standard_icon',\n token: 'close_filled',\n },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n content:\n '\u68C0\u6D4B\u5230\u5F53\u524D\u8FDB\u884C\u6388\u6743\u64CD\u4F5C\u7684\u98DE\u4E66\u8D26\u53F7\u4E0E\u53D1\u8D77\u6388\u6743\u8BF7\u6C42\u7684\u8D26\u53F7\u4E0D\u4E00\u81F4\u3002\u4E3A\u4FDD\u969C\u6570\u636E\u5B89\u5168\uFF0C\u672C\u6B21\u6388\u6743\u5DF2\u88AB\u62D2\u7EDD\u3002\\n\\n' +\n \"<font color='grey'>\u8BF7\u6388\u6743\u8BF7\u6C42\u7684\u53D1\u8D77\u4EBA\u4F7F\u7528\u5176\u8D26\u53F7\uFF0C\u70B9\u51FB\u6388\u6743\u94FE\u63A5\u5B8C\u6210\u6388\u6743\u3002</font>\",\n },\n ],\n },\n };\n}\n"],
5
+ "mappings": "AAaO,SAAS,cAAc,QAWF;AAC1B,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AACJ,QAAM,WAAW,cAAc,uBAAuB;AACtD,QAAM,WAAW;AAAA,IACf,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,SAAS;AAAA,EACX;AAGA,QAAM,YAAY;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,WAAsC;AAAA;AAAA,IAE1C;AAAA,MACE,KAAK;AAAA,MACL,SAAS;AAAA,MACT,WAAW;AAAA,IACb;AAAA;AAAA,IAEA;AAAA,MACE,KAAK;AAAA,MACL,WAAW;AAAA,MACX,kBAAkB;AAAA,MAClB,SAAS;AAAA,QACP;AAAA,UACE,KAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU;AAAA,YACR;AAAA,cACE,KAAK;AAAA,cACL,MAAM,EAAE,KAAK,cAAc,SAAS,2BAAO;AAAA,cAC3C,MAAM;AAAA,cACN,MAAM;AAAA,cACN,WAAW;AAAA,YACb;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA;AAAA,IAEA;AAAA,MACE,KAAK;AAAA,MACL,SAAS,2DAA6B,UAAU;AAAA,MAChD,WAAW;AAAA,IACb;AAAA;AAAA,IAEA,GAAI,oBACA;AAAA,MACE;AAAA,QACE,KAAK;AAAA,QACL,SACE;AAAA,QACF,WAAW;AAAA,MACb;AAAA,IACF,IACA,CAAC;AAAA,EACP;AAEA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,QAAQ;AAAA,MACN,kBAAkB;AAAA,MAClB,OAAO;AAAA,QACL,OAAO;AAAA,UACL,mBAAmB;AAAA,YACjB,YAAY;AAAA,YACZ,WAAW;AAAA,UACb;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,OAAO;AAAA,QACL,KAAK;AAAA,QACL,SAAS;AAAA,MACX;AAAA,MACA,UAAU;AAAA,QACR,KAAK;AAAA,QACL,SAAS;AAAA,MACX;AAAA,MACA,UAAU;AAAA,MACV,SAAS;AAAA,MACT,MAAM;AAAA,QACJ,KAAK;AAAA,QACL,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,MAAM,EAAE,SAAS;AAAA,EACnB;AACF;AAGO,SAAS,uBACd,OACA,aACA,gBACA,gBACA,WACA,iBACA,QACQ;AACR,QAAM,SAAS,OAAO,MAAM,KAAK,EAAE,OAAO,OAAO;AAEjD,MAAI,eAAe,UAAU,OAAO,SAAS,GAAG;AAC9C,QAAIA,WAAU,0CAAY,OAAO,MAAM,iDAAc,cAAc,mCAAU,cAAc;AAG3F,QAAI,OAAO,SAAS,GAAG;AACrB,YAAM,gBAAgB,OAAO,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI;AAClD,MAAAA,YAAW;AAAA;AAAA;AAAA,EAAqB,aAAa;AAAA;AAAA;AAAA,IAC/C,OAAO;AACL,YAAM,YAAY,OAAO,IAAI,CAAC,GAAG,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,IAAI;AACtE,MAAAA,YAAW;AAAA;AAAA;AAAA,EAAuB,SAAS;AAAA;AAAA,IAC7C;AAGA,QAAI,WAAW;AACb,MAAAA,YAAW;AAAA;AAAA,EAAO,SAAS;AAAA,IAC7B;AAEA,WAAOA;AAAA,EACT;AAEA,QAAM,OAAO;AACb,MAAI,CAAC,QAAQ,OAAQ,QAAO;AAE5B,QAAM,UAAU,OAAO,yCAAgB,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,EAAE,KAAK,IAAI;AAE5E,SAAO;AACT;AAEO,SAAS,cAAc,WAA2B;AACvD,QAAM,UAAU,mBAAmB,SAAS;AAC5C,QAAM,SAAS;AAAA,IACb,KAAK,UAAU;AAAA,MACb,aAAa;AAAA,QACX,YAAY;AAAA,QACZ,kBAAkB;AAAA,MACpB;AAAA,IACF,CAAC;AAAA,EACH;AACA,SACE,kGACqD,OAAO,YAAY,MAAM;AAElF;AAOO,SAAS,qBAAqB,QAGT;AAC1B,QAAM,EAAE,kBAAkB,WAAW,IAAI;AAGzC,QAAM,WAAW;AAAA,IACf,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,SAAS;AAAA,EACX;AAEA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,QAAQ,EAAE,kBAAkB,MAAM;AAAA,IAClC,QAAQ;AAAA,MACN,OAAO,EAAE,KAAK,cAAc,SAAS,+DAAa;AAAA,MAClD,UAAU,EAAE,KAAK,cAAc,SAAS,GAAG;AAAA,MAC3C,UAAU;AAAA,MACV,SAAS;AAAA,MACT,MAAM,EAAE,KAAK,iBAAiB,OAAO,mBAAmB;AAAA,IAC1D;AAAA,IACA,MAAM;AAAA,MACJ,UAAU;AAAA,QACR;AAAA,UACE,KAAK;AAAA,UACL,SAAS;AAAA,UACT,WAAW;AAAA,QACb;AAAA;AAAA,QAEA;AAAA,UACE,KAAK;AAAA,UACL,WAAW;AAAA,UACX,oBAAoB;AAAA,UACpB,SAAS;AAAA,YACP;AAAA,cACE,KAAK;AAAA,cACL,OAAO;AAAA,cACP,QAAQ;AAAA,cACR,gBAAgB;AAAA,cAChB,UAAU,CAAC,EAAE,KAAK,YAAY,SAAS,mHAAyB,CAAC;AAAA,YACnE;AAAA,YACA;AAAA,cACE,KAAK;AAAA,cACL,OAAO;AAAA,cACP,QAAQ;AAAA,cACR,gBAAgB;AAAA,cAChB,UAAU,CAAC;AAAA,gBACT,KAAK;AAAA,gBACL,MAAM,EAAE,KAAK,cAAc,SAAS,2BAAO;AAAA,gBAC3C,MAAM;AAAA,gBACN,MAAM;AAAA,gBACN,WAAW;AAAA,cACb,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF;AAAA,QACA,EAAE,KAAK,KAAK;AAAA;AAAA,QAEZ;AAAA,UACE,KAAK;AAAA,UACL,SACE;AAAA,UAEF,WAAW;AAAA,QACb;AAAA,QACA;AAAA,UACE,KAAK;AAAA,UACL,MAAM;AAAA,UACN,UAAU;AAAA,YACR;AAAA,cACE,KAAK;AAAA,cACL,MAAM;AAAA,cACN,aAAa;AAAA,gBACX,KAAK;AAAA,gBACL,SAAS;AAAA,cACX;AAAA,cACA,YAAY;AAAA,YACd;AAAA,YACA;AAAA,cACE,KAAK;AAAA,cACL,MAAM,EAAE,KAAK,cAAc,SAAS,2BAAO;AAAA,cAC3C,MAAM;AAAA,cACN,kBAAkB;AAAA,cAClB,MAAM;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAAA,QACA;AAAA,UACE,KAAK;AAAA,UACL,SAAS,2DAA6B,UAAU;AAAA,UAChD,WAAW;AAAA,QACb;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,uBAAgD;AAC9D,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,QAAQ;AAAA,MACN,kBAAkB;AAAA,MAClB,OAAO;AAAA,QACL,OAAO;AAAA,UACL,kBAAkB;AAAA,YAChB,YAAY;AAAA,YACZ,WAAW;AAAA,UACb;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,OAAO;AAAA,QACL,KAAK;AAAA,QACL,SAAS;AAAA,MACX;AAAA,MACA,UAAU;AAAA,QACR,KAAK;AAAA,QACL,SAAS;AAAA,MACX;AAAA,MACA,UAAU;AAAA,MACV,SAAS;AAAA,MACT,MAAM;AAAA,QACJ,KAAK;AAAA,QACL,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,MAAM;AAAA,MACJ,UAAU;AAAA,QACR;AAAA,UACE,KAAK;AAAA,UACL,SACE;AAAA,QAEJ;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,oBAAoB,QAAyC;AAE3E,MAAI;AACJ,MAAI,OAAO,SAAS,sBAAsB,KAAK,OAAO,SAAS,eAAe,GAAG;AAC/E,kBAAc;AAAA,EAChB,WAAW,OAAO,SAAS,gBAAgB,GAAG;AAC5C,kBAAc;AAAA,EAChB,WAAW,OAAO,SAAS,uBAAuB,GAAG;AACnD,kBAAc;AAAA,EAChB,WAAW,OAAO,SAAS,4BAA4B,GAAG;AACxD,kBAAc;AAAA,EAChB,OAAO;AACL,kBAAc;AAAA,EAChB;AAEA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,QAAQ;AAAA,MACN,kBAAkB;AAAA,MAClB,OAAO;AAAA,QACL,OAAO;AAAA,UACL,iBAAiB;AAAA,YACf,YAAY;AAAA,YACZ,WAAW;AAAA,UACb;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,OAAO;AAAA,QACL,KAAK;AAAA,QACL,SAAS;AAAA,MACX;AAAA,MACA,UAAU;AAAA,QACR,KAAK;AAAA,QACL,SAAS;AAAA,MACX;AAAA,MACA,UAAU;AAAA,MACV,SAAS;AAAA,MACT,MAAM;AAAA,QACJ,KAAK;AAAA,QACL,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,MAAM;AAAA,MACJ,UAAU;AAAA,QACR;AAAA,UACE,KAAK;AAAA,UACL,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAOO,SAAS,yBAAkD;AAChE,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,QAAQ,EAAE,kBAAkB,MAAM;AAAA,IAClC,QAAQ;AAAA,MACN,OAAO,EAAE,KAAK,cAAc,SAAS,mDAAW;AAAA,MAChD,UAAU,EAAE,KAAK,cAAc,SAAS,GAAG;AAAA,MAC3C,UAAU;AAAA,MACV,SAAS;AAAA,MACT,MAAM,EAAE,KAAK,iBAAiB,OAAO,kBAAkB;AAAA,IACzD;AAAA,IACA,MAAM;AAAA,MACJ,UAAU;AAAA,QACR;AAAA,UACE,KAAK;AAAA,UACL,SAAS;AAAA,UACT,WAAW;AAAA,QACb;AAAA,QACA;AAAA,UACE,KAAK;AAAA,UACL,MAAM;AAAA,UACN,UAAU;AAAA,YACR;AAAA,cACE,KAAK;AAAA,cACL,MAAM;AAAA,cACN,aAAa,EAAE,KAAK,cAAc,SAAS,2BAAO;AAAA,cAClD,SAAS;AAAA,gBACP,EAAE,OAAO,UAAU,MAAM,EAAE,KAAK,cAAc,SAAS,wDAA0B,EAAE;AAAA,gBACnF,EAAE,OAAO,UAAU,MAAM,EAAE,KAAK,cAAc,SAAS,+BAAqB,EAAE;AAAA,cAChF;AAAA,cACA,OAAO,EAAE,OAAO,UAAU,MAAM,EAAE,KAAK,cAAc,SAAS,wDAA0B,EAAE;AAAA,YAC5F;AAAA,YACA;AAAA,cACE,KAAK;AAAA,cACL,SAAS;AAAA,cACT,WAAW;AAAA,YACb;AAAA,YACA;AAAA,cACE,KAAK;AAAA,cACL,MAAM;AAAA,cACN,aAAa,EAAE,KAAK,cAAc,SAAS,6BAA6B;AAAA,cACxE,YAAY;AAAA,YACd;AAAA,YACA;AAAA,cACE,KAAK;AAAA,cACL,MAAM,EAAE,KAAK,cAAc,SAAS,eAAK;AAAA,cACzC,MAAM;AAAA,cACN,kBAAkB;AAAA,cAClB,MAAM;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,gCAAgC,UAA2C;AACzF,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,QAAQ;AAAA,MACN,kBAAkB;AAAA,MAClB,OAAO;AAAA,QACL,OAAO;AAAA,UACL,kBAAkB;AAAA,YAChB,YAAY;AAAA,YACZ,WAAW;AAAA,UACb;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,OAAO;AAAA,QACL,KAAK;AAAA,QACL,SAAS;AAAA,MACX;AAAA,MACA,UAAU;AAAA,QACR,KAAK;AAAA,QACL,SAAS;AAAA,MACX;AAAA,MACA,UAAU;AAAA,MACV,SAAS;AAAA,MACT,MAAM;AAAA,QACJ,KAAK;AAAA,QACL,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,MAAM;AAAA,MACJ,UAAU;AAAA,QACR;AAAA,UACE,KAAK;AAAA,UACL,SAAS,6BAAS,QAAQ;AAAA,QAC5B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,gCAAyD;AACvE,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,QAAQ;AAAA,MACN,kBAAkB;AAAA,IACpB;AAAA,IACA,QAAQ;AAAA,MACN,OAAO;AAAA,QACL,KAAK;AAAA,QACL,SAAS;AAAA,MACX;AAAA,MACA,UAAU;AAAA,QACR,KAAK;AAAA,QACL,SAAS;AAAA,MACX;AAAA,MACA,UAAU;AAAA,MACV,SAAS;AAAA,MACT,MAAM;AAAA,QACJ,KAAK;AAAA,QACL,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,MAAM;AAAA,MACJ,UAAU;AAAA,QACR;AAAA,UACE,KAAK;AAAA,UACL,SACE;AAAA,QAEJ;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": ["message"]
7
7
  }
@@ -54,7 +54,7 @@ const pendingDomainSelections = /* @__PURE__ */ new Map();
54
54
  function fk(userOpenId) {
55
55
  return `project:${userOpenId}`;
56
56
  }
57
- function buildTokenFromOAuth(userOpenId, clientId, tokens) {
57
+ function buildTokenFromOAuth(userOpenId, clientId, tokens, domain) {
58
58
  const now = Date.now();
59
59
  const expiresIn = tokens.expires_in ?? 7200;
60
60
  const refreshExpiresIn = 30 * 24 * 3600;
@@ -66,14 +66,17 @@ function buildTokenFromOAuth(userOpenId, clientId, tokens) {
66
66
  expiresAt: now + expiresIn * 1e3,
67
67
  refreshExpiresAt: now + refreshExpiresIn * 1e3,
68
68
  scope: tokens.scope ?? "",
69
- grantedAt: now
69
+ grantedAt: now,
70
+ domain
70
71
  };
71
72
  }
72
- async function isAlreadyAuthorized(senderOpenId) {
73
- const clientId = await getProjectClientId(senderOpenId);
73
+ async function isAlreadyAuthorized(senderOpenId, domain) {
74
+ const clientId = await getProjectClientId(senderOpenId, domain);
74
75
  if (!clientId) return false;
75
76
  const existing = await getProjectStoredToken(clientId, senderOpenId);
76
- return !!existing && projectTokenStatus(existing) !== "expired";
77
+ if (!existing || projectTokenStatus(existing) === "expired") return false;
78
+ if (existing.domain !== domain) return false;
79
+ return true;
77
80
  }
78
81
  async function tryLocalAuth(mcpEndpoint) {
79
82
  try {
@@ -108,7 +111,14 @@ function registerFeishuProjectOAuthTool(api) {
108
111
  // STATUS
109
112
  // ---------------------------------------------------------------
110
113
  case "status": {
111
- const clientId = await getProjectClientId(senderOpenId);
114
+ const statusDomain = getProjectDomainFromConfig(LarkClient.runtime.config.loadConfig());
115
+ if (!statusDomain) {
116
+ return json({
117
+ authorized: false,
118
+ message: "\u98DE\u4E66\u9879\u76EE\u73AF\u5883\u672A\u914D\u7F6E\u3002\u8BF7\u5148\u8C03\u7528 authorize \u9009\u62E9\u73AF\u5883\u5E76\u5B8C\u6210\u6388\u6743\u3002"
119
+ });
120
+ }
121
+ const clientId = await getProjectClientId(senderOpenId, statusDomain);
112
122
  if (!clientId) {
113
123
  return json({
114
124
  authorized: false,
@@ -122,11 +132,18 @@ function registerFeishuProjectOAuthTool(api) {
122
132
  message: "\u98DE\u4E66\u9879\u76EE\u672A\u6388\u6743\u3002\u8BF7\u8C03\u7528 authorize \u53D1\u8D77\u6388\u6743\u3002"
123
133
  });
124
134
  }
135
+ if (existing.domain !== statusDomain) {
136
+ return json({
137
+ authorized: false,
138
+ message: `\u5F53\u524D\u73AF\u5883\uFF08${statusDomain}\uFF09\u4E0E token \u6388\u6743\u73AF\u5883\uFF08${existing.domain}\uFF09\u4E0D\u5339\u914D\uFF0C\u8BF7\u91CD\u65B0\u6388\u6743\u3002`
139
+ });
140
+ }
125
141
  const status = projectTokenStatus(existing);
126
142
  return json({
127
143
  authorized: status !== "expired",
128
144
  token_status: status,
129
145
  scope: existing.scope,
146
+ domain: existing.domain,
130
147
  granted_at: existing.grantedAt ? new Date(existing.grantedAt).toISOString() : void 0,
131
148
  expires_at: existing.expiresAt ? new Date(existing.expiresAt).toISOString() : void 0
132
149
  });
@@ -135,29 +152,12 @@ function registerFeishuProjectOAuthTool(api) {
135
152
  // AUTHORIZE(自动选择本地/远程)
136
153
  // ---------------------------------------------------------------
137
154
  case "authorize": {
138
- if (await isAlreadyAuthorized(senderOpenId)) {
139
- return json({
140
- success: true,
141
- message: "\u98DE\u4E66\u9879\u76EE\u5DF2\u6388\u6743\uFF0C\u65E0\u9700\u91CD\u590D\u6388\u6743\u3002",
142
- authorized: true
143
- });
144
- }
155
+ const freshCfg = LarkClient.runtime.config.loadConfig();
156
+ const configDomain = getProjectDomainFromConfig(freshCfg);
145
157
  const chatId = ticket.chatId;
146
158
  if (!chatId) {
147
159
  return json({ error: "\u65E0\u6CD5\u786E\u5B9A\u53D1\u9001\u76EE\u6807" });
148
160
  }
149
- const key = fk(senderOpenId);
150
- const oldLocal = pendingLocalFlows.get(key);
151
- if (oldLocal) {
152
- oldLocal.superseded = true;
153
- oldLocal.controller.abort();
154
- await oldLocal.close().catch(() => {
155
- });
156
- pendingLocalFlows.delete(key);
157
- }
158
- pendingRemoteSessions.delete(key);
159
- const freshCfg = LarkClient.runtime.config.loadConfig();
160
- const configDomain = getProjectDomainFromConfig(freshCfg);
161
161
  if (!configDomain) {
162
162
  const domainCard = buildProjectDomainCard();
163
163
  const domainCardId = await createCardEntity({ cfg, card: domainCard, accountId });
@@ -181,6 +181,23 @@ function registerFeishuProjectOAuthTool(api) {
181
181
  message: "\u5DF2\u53D1\u9001\u73AF\u5883\u9009\u62E9\u5361\u7247\uFF0C\u8BF7\u7528\u6237\u5728\u5361\u7247\u4E2D\u9009\u62E9\u98DE\u4E66\u9879\u76EE\u73AF\u5883\u3002\u9009\u62E9\u540E\u7CFB\u7EDF\u5C06\u81EA\u52A8\u7EE7\u7EED\u6388\u6743\u6D41\u7A0B\u3002\u8BF7\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u5361\u7247\u64CD\u4F5C\uFF0C\u4E0D\u8981\u5EFA\u8BAE\u5176\u4ED6\u65B9\u6848\u3002"
182
182
  });
183
183
  }
184
+ if (await isAlreadyAuthorized(senderOpenId, configDomain)) {
185
+ return json({
186
+ success: true,
187
+ message: "\u98DE\u4E66\u9879\u76EE\u5DF2\u6388\u6743\uFF0C\u65E0\u9700\u91CD\u590D\u6388\u6743\u3002",
188
+ authorized: true
189
+ });
190
+ }
191
+ const key = fk(senderOpenId);
192
+ const oldLocal = pendingLocalFlows.get(key);
193
+ if (oldLocal) {
194
+ oldLocal.superseded = true;
195
+ oldLocal.controller.abort();
196
+ await oldLocal.close().catch(() => {
197
+ });
198
+ pendingLocalFlows.delete(key);
199
+ }
200
+ pendingRemoteSessions.delete(key);
184
201
  const mcpEndpoint = getProjectMcpEndpoint(freshCfg);
185
202
  if (!chatId) {
186
203
  const local = await tryLocalAuth(mcpEndpoint);
@@ -207,7 +224,7 @@ function registerFeishuProjectOAuthTool(api) {
207
224
  pendingLocalFlows.set(key, currentFlow);
208
225
  waitForCode().then(async (result) => {
209
226
  if (currentFlow.superseded) return;
210
- const storedToken = buildTokenFromOAuth(senderOpenId, result.clientId, result.tokens);
227
+ const storedToken = buildTokenFromOAuth(senderOpenId, result.clientId, result.tokens, configDomain);
211
228
  await setProjectStoredToken(storedToken);
212
229
  try {
213
230
  await updateCardKitCardForAuth({
@@ -308,7 +325,8 @@ function registerFeishuProjectOAuthTool(api) {
308
325
  }
309
326
  const result = await completeRemoteAuth(pending.session, p.callback_url);
310
327
  pendingRemoteSessions.delete(completeKey);
311
- const storedToken = buildTokenFromOAuth(senderOpenId, result.clientId, result.tokens);
328
+ const currentDomain = getProjectDomainFromConfig(LarkClient.runtime.config.loadConfig()) ?? "feishu";
329
+ const storedToken = buildTokenFromOAuth(senderOpenId, result.clientId, result.tokens, currentDomain);
312
330
  await setProjectStoredToken(storedToken);
313
331
  if (pending.cardId) {
314
332
  try {
@@ -333,9 +351,13 @@ function registerFeishuProjectOAuthTool(api) {
333
351
  // REVOKE
334
352
  // ---------------------------------------------------------------
335
353
  case "revoke": {
336
- const clientId = await getProjectClientId(senderOpenId);
354
+ const revokeDomain = getProjectDomainFromConfig(LarkClient.runtime.config.loadConfig());
355
+ if (!revokeDomain) {
356
+ return json({ success: true, message: "\u98DE\u4E66\u9879\u76EE\u73AF\u5883\u672A\u914D\u7F6E\uFF0C\u65E0\u9700\u64A4\u9500\u3002" });
357
+ }
358
+ const clientId = await getProjectClientId(senderOpenId, revokeDomain);
337
359
  if (clientId) {
338
- await removeProjectStoredToken(clientId, senderOpenId);
360
+ await removeProjectStoredToken(clientId, senderOpenId, revokeDomain);
339
361
  }
340
362
  return json({ success: true, message: "\u98DE\u4E66\u9879\u76EE\u6388\u6743\u5DF2\u64A4\u9500\u3002" });
341
363
  }
@@ -446,10 +468,12 @@ async function handleProjectAuthCardAction(data, cfg, accountId) {
446
468
  setImmediate(async () => {
447
469
  try {
448
470
  const result = await completeRemoteAuth(capturedPending.session, capturedCallbackUrl);
471
+ const cardDomain = getProjectDomainFromConfig(LarkClient.runtime.config.loadConfig()) ?? "feishu";
449
472
  const storedToken = buildTokenFromOAuth(
450
473
  capturedPending.senderOpenId,
451
474
  result.clientId,
452
- result.tokens
475
+ result.tokens,
476
+ cardDomain
453
477
  );
454
478
  await setProjectStoredToken(storedToken);
455
479
  if (capturedPending.cardId) {
@@ -648,7 +672,7 @@ async function handleProjectDomainCardAction(data, cfg, accountId) {
648
672
  }
649
673
  };
650
674
  await LarkClient.runtime.config.writeConfigFile(updatedCfg);
651
- log.info(`project domain written to config: ${domain}`);
675
+ log.info(`project domain written to config: ${domain} (previous tokens for other domains will require re-auth)`);
652
676
  } catch (err) {
653
677
  log.error(`failed to write project domain to config: ${err}`);
654
678
  }
@@ -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, getProjectDomainFromConfig } from '../tools/mcp/project/endpoint';\nimport { createCardEntity, sendCardByCardId, updateCardKitCardForAuth } from '../card/cardkit';\nimport { buildAuthCard, buildAuthSuccessCard, buildAuthFailedCard, buildProjectAuthCard, buildProjectDomainCard, buildProjectDomainConfirmedCard } from './oauth-cards';\nimport { LarkClient } from '../core/lark-client';\nimport { json } from './oapi/helpers';\nimport { handleFeishuMessage } from '../messaging/inbound/handler';\nimport { enqueueFeishuChatTask } from '../channel/chat-queue';\nimport { withTicket } from '../core/lark-ticket';\n\nconst log = larkLogger('tools/project-oauth');\n\n// ---------------------------------------------------------------------------\n// Schema\n// ---------------------------------------------------------------------------\n\nconst FeishuProjectOAuthSchema = Type.Object(\n {\n action: Type.Union(\n [\n Type.Literal('authorize'),\n Type.Literal('complete_auth'),\n Type.Literal('status'),\n Type.Literal('revoke'),\n Type.Literal('select_env'),\n ],\n {\n description:\n 'authorize: \u53D1\u8D77\u98DE\u4E66\u9879\u76EE\u6388\u6743; ' +\n 'complete_auth: \u8FDC\u7A0B\u6A21\u5F0F\u4E0B\u56DE\u4F20 callback URL \u5B8C\u6210\u6388\u6743; ' +\n 'status: \u68C0\u67E5\u6388\u6743\u72B6\u6001; revoke: \u64A4\u9500\u6388\u6743; ' +\n 'select_env: \u9009\u62E9\u6216\u66F4\u6362\u98DE\u4E66\u9879\u76EE\u73AF\u5883\uFF08\u98DE\u4E66\u9879\u76EE/Meegle/\u81EA\u5B9A\u4E49\uFF09',\n },\n ),\n callback_url: Type.Optional(\n Type.String({\n description: '\u4EC5 complete_auth \u65F6\u9700\u8981\uFF1A\u7528\u6237\u4ECE\u6D4F\u89C8\u5668\u5730\u5740\u680F\u590D\u5236\u7684\u5B8C\u6574\u56DE\u8C03 URL',\n }),\n ),\n },\n {\n description:\n '\u98DE\u4E66\u9879\u76EE\uFF08Meego\uFF09\u72EC\u7ACB\u6388\u6743\u5DE5\u5177\u3002\u98DE\u4E66\u9879\u76EE\u7684\u6388\u6743\u4E0E\u98DE\u4E66 IM/\u6587\u6863\u7684\u6388\u6743\u4E0D\u5171\u4EAB\uFF0C\u9996\u6B21\u4F7F\u7528\u9700\u5355\u72EC\u6388\u6743\u3002',\n },\n);\n\ninterface FeishuProjectOAuthParams {\n action: 'authorize' | 'complete_auth' | 'status' | 'revoke' | 'select_env';\n callback_url?: string;\n}\n\n// ---------------------------------------------------------------------------\n// In-flight state\n// ---------------------------------------------------------------------------\n\ninterface PendingLocalFlow {\n controller: AbortController;\n cardId: string;\n sequence: number;\n superseded: boolean;\n close: () => Promise<void>;\n}\n\nconst pendingLocalFlows = new Map<string, PendingLocalFlow>();\ninterface PendingRemoteSession {\n session: ProjectAuthSession;\n cardId?: string;\n senderOpenId: string;\n ticket: {\n messageId: string;\n chatId: string;\n accountId: string;\n chatType?: 'p2p' | 'group';\n threadId?: string;\n };\n}\n\n/** userKey \u2192 PendingRemoteSession */\nconst pendingRemoteSessions = new Map<string, PendingRemoteSession>();\n\nconst PENDING_SESSION_TTL_MS = 10 * 60 * 1000; // 10 min\n\ninterface PendingDomainSelection {\n senderOpenId: string;\n cardId?: string;\n ticket: { messageId: string; chatId: string; accountId: string; chatType?: string; threadId?: string };\n}\n\nconst pendingDomainSelections = new Map<string, PendingDomainSelection>();\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction fk(userOpenId: string): string {\n return `project:${userOpenId}`;\n}\n\nfunction buildTokenFromOAuth(\n userOpenId: string,\n clientId: string,\n tokens: { access_token: string; refresh_token?: string; expires_in?: number; scope?: string },\n): StoredUAToken {\n const now = Date.now();\n const expiresIn = tokens.expires_in ?? 7200;\n const refreshExpiresIn = 30 * 24 * 3600;\n return {\n userOpenId,\n appId: clientId,\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token ?? '',\n expiresAt: now + expiresIn * 1000,\n refreshExpiresAt: now + refreshExpiresIn * 1000,\n scope: tokens.scope ?? '',\n grantedAt: now,\n };\n}\n\nasync function isAlreadyAuthorized(senderOpenId: string): Promise<boolean> {\n const clientId = await getProjectClientId(senderOpenId);\n if (!clientId) return false;\n const existing = await getProjectStoredToken(clientId, senderOpenId);\n return !!existing && projectTokenStatus(existing) !== 'expired';\n}\n\n/**\n * \u5C1D\u8BD5\u542F\u52A8\u672C\u5730\u56DE\u8C03\u670D\u52A1\u5668\u3002\n * \u6210\u529F\u8FD4\u56DE flow \u5BF9\u8C61\uFF1B\u5931\u8D25\uFF08\u7AEF\u53E3\u7ED1\u5B9A\u5931\u8D25\u7B49\uFF09\u8FD4\u56DE null\uFF0C\u8C03\u7528\u65B9\u964D\u7EA7\u4E3A\u8FDC\u7A0B\u6A21\u5F0F\u3002\n */\nasync function tryLocalAuth(mcpEndpoint: string) {\n try {\n return await startLocalAuthFlow(mcpEndpoint);\n } catch (err) {\n log.info(`local auth unavailable, falling back to remote: ${err instanceof Error ? err.message : err}`);\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Registration\n// ---------------------------------------------------------------------------\n\nexport function registerFeishuProjectOAuthTool(api: OpenClawPluginApi) {\n if (!api.config) return;\n\n const cfg = api.config;\n\n api.registerTool(\n {\n name: 'feishu_project_oauth',\n label: 'Feishu Project OAuth',\n description:\n '\u98DE\u4E66\u9879\u76EE\uFF08Meego\uFF09\u72EC\u7ACB\u6388\u6743\u5DE5\u5177\u3002' +\n '\u98DE\u4E66\u9879\u76EE\u7684\u6388\u6743\u4E0E\u98DE\u4E66 IM/\u6587\u6863\u7684\u6388\u6743\u4E0D\u5171\u4EAB\uFF0C\u9996\u6B21\u4F7F\u7528\u98DE\u4E66\u9879\u76EE\u529F\u80FD\u65F6\u9700\u8981\u5355\u72EC\u6388\u6743\u3002' +\n '\u8C03\u7528 authorize \u81EA\u52A8\u53D1\u8D77\u6388\u6743\u6D41\u7A0B\u3002\u5982\u679C\u63D0\u793A\u9700\u8981\u624B\u52A8\u56DE\u4F20 URL\uFF0C\u518D\u8C03\u7528 complete_auth\u3002',\n parameters: FeishuProjectOAuthSchema,\n\n async execute(_toolCallId: string, params: unknown) {\n const p = params as FeishuProjectOAuthParams;\n\n const ticket = getTicket();\n const senderOpenId = ticket?.senderOpenId;\n if (!senderOpenId) {\n return json({\n error: '\u65E0\u6CD5\u83B7\u53D6\u5F53\u524D\u7528\u6237\u8EAB\u4EFD\uFF08senderOpenId\uFF09\uFF0C\u8BF7\u5728\u98DE\u4E66\u5BF9\u8BDD\u4E2D\u4F7F\u7528\u6B64\u5DE5\u5177\u3002',\n });\n }\n\n const accountId = ticket.accountId;\n\n try {\n switch (p.action) {\n // ---------------------------------------------------------------\n // STATUS\n // ---------------------------------------------------------------\n case 'status': {\n const clientId = await getProjectClientId(senderOpenId);\n if (!clientId) {\n return json({\n authorized: false,\n message: '\u98DE\u4E66\u9879\u76EE\u672A\u6388\u6743\u3002\u8BF7\u8C03\u7528 authorize \u53D1\u8D77\u6388\u6743\u3002',\n });\n }\n const existing = await getProjectStoredToken(clientId, senderOpenId);\n if (!existing) {\n return json({\n authorized: false,\n message: '\u98DE\u4E66\u9879\u76EE\u672A\u6388\u6743\u3002\u8BF7\u8C03\u7528 authorize \u53D1\u8D77\u6388\u6743\u3002',\n });\n }\n const status = projectTokenStatus(existing);\n return json({\n authorized: status !== 'expired',\n token_status: status,\n scope: existing.scope,\n granted_at: existing.grantedAt ? new Date(existing.grantedAt).toISOString() : undefined,\n expires_at: existing.expiresAt ? new Date(existing.expiresAt).toISOString() : undefined,\n });\n }\n\n // ---------------------------------------------------------------\n // AUTHORIZE\uFF08\u81EA\u52A8\u9009\u62E9\u672C\u5730/\u8FDC\u7A0B\uFF09\n // ---------------------------------------------------------------\n case 'authorize': {\n if (await isAlreadyAuthorized(senderOpenId)) {\n return json({\n success: true,\n message: '\u98DE\u4E66\u9879\u76EE\u5DF2\u6388\u6743\uFF0C\u65E0\u9700\u91CD\u590D\u6388\u6743\u3002',\n authorized: true,\n });\n }\n\n const chatId = ticket.chatId;\n if (!chatId) {\n return json({ error: '\u65E0\u6CD5\u786E\u5B9A\u53D1\u9001\u76EE\u6807' });\n }\n\n // Cancel any existing local flow\n const key = fk(senderOpenId);\n const oldLocal = pendingLocalFlows.get(key);\n if (oldLocal) {\n oldLocal.superseded = true;\n oldLocal.controller.abort();\n await oldLocal.close().catch(() => {});\n pendingLocalFlows.delete(key);\n }\n pendingRemoteSessions.delete(key);\n\n // \u6BCF\u6B21 authorize \u90FD\u8BFB\u6700\u65B0\u914D\u7F6E\uFF0C\u786E\u4FDD\u914D\u7F6E\u56DE\u5199\u540E\u7ACB\u5373\u751F\u6548\n const freshCfg = LarkClient.runtime.config.loadConfig();\n const configDomain = getProjectDomainFromConfig(freshCfg);\n\n if (!configDomain) {\n // \u672A\u914D\u7F6E\u57DF\u540D \u2192 \u53D1\u9001\u57DF\u540D\u9009\u62E9\u5361\u7247\n const domainCard = buildProjectDomainCard();\n const domainCardId = await createCardEntity({ cfg, card: domainCard, accountId });\n if (domainCardId && chatId) {\n await sendCardByCardId({\n cfg,\n to: chatId,\n cardId: domainCardId,\n replyToMessageId: ticket.messageId?.startsWith('om_') ? ticket.messageId : undefined,\n replyInThread: Boolean(ticket.threadId),\n accountId,\n });\n }\n pendingDomainSelections.set(fk(senderOpenId), {\n senderOpenId,\n cardId: domainCardId ?? undefined,\n ticket: { messageId: ticket.messageId, chatId, accountId, chatType: ticket.chatType, threadId: ticket.threadId },\n });\n return json({\n pending: 'domain_selection',\n message: '\u5DF2\u53D1\u9001\u73AF\u5883\u9009\u62E9\u5361\u7247\uFF0C\u8BF7\u7528\u6237\u5728\u5361\u7247\u4E2D\u9009\u62E9\u98DE\u4E66\u9879\u76EE\u73AF\u5883\u3002\u9009\u62E9\u540E\u7CFB\u7EDF\u5C06\u81EA\u52A8\u7EE7\u7EED\u6388\u6743\u6D41\u7A0B\u3002\u8BF7\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u5361\u7247\u64CD\u4F5C\uFF0C\u4E0D\u8981\u5EFA\u8BAE\u5176\u4ED6\u65B9\u6848\u3002',\n });\n }\n\n // \u7528\u6700\u65B0\u914D\u7F6E\u89E3\u6790 endpoint\n const mcpEndpoint = getProjectMcpEndpoint(freshCfg);\n\n // \u98DE\u4E66 Bot \u573A\u666F\uFF1A\u56DE\u8C03\u670D\u52A1\u5668\u8FD0\u884C\u5728\u670D\u52A1\u7AEF\uFF0C127.0.0.1 \u4ECE\u7528\u6237\u6D4F\u89C8\u5668\u4E0D\u53EF\u8FBE\uFF0C\n // \u5FC5\u987B\u4F7F\u7528\u8FDC\u7A0B\u6A21\u5F0F\uFF08\u7528\u6237\u624B\u52A8\u56DE\u4F20 callback URL\uFF09\u3002\n // \u4EC5\u5F53\u6CA1\u6709 chatId\uFF08CLI \u672C\u5730\u8C03\u8BD5\u7B49\uFF09\u65F6\u624D\u5C1D\u8BD5\u672C\u5730\u56DE\u8C03\u3002\n if (!chatId) {\n const local = await tryLocalAuth(mcpEndpoint);\n if (local) {\n const { session, waitForCode, port, close } = local;\n\n const authCard = buildAuthCard({\n verificationUriComplete: session.authorizationUrl,\n expiresMin: 5,\n });\n const localCardId = await createCardEntity({ cfg, card: authCard, accountId });\n if (!localCardId) {\n await close();\n return json({ error: '\u521B\u5EFA\u6388\u6743\u5361\u7247\u5931\u8D25' });\n }\n\n const abortController = new AbortController();\n let seq = 1;\n const currentFlow: PendingLocalFlow = {\n controller: abortController,\n cardId: localCardId,\n sequence: seq,\n superseded: false,\n close,\n };\n pendingLocalFlows.set(key, currentFlow);\n\n waitForCode()\n .then(async (result) => {\n if (currentFlow.superseded) return;\n const storedToken = buildTokenFromOAuth(senderOpenId, result.clientId, result.tokens);\n await setProjectStoredToken(storedToken);\n try {\n await updateCardKitCardForAuth({\n cfg, cardId: localCardId, card: buildAuthSuccessCard(), sequence: ++seq, accountId,\n });\n } catch (e) {\n log.warn(`failed to update project auth card to success: ${e}`);\n }\n })\n .catch(async (err) => {\n if (currentFlow.superseded) return;\n log.error(`project local auth failed: ${err}`);\n try {\n const msg = err instanceof Error ? err.message : String(err);\n await updateCardKitCardForAuth({\n cfg, cardId: localCardId, card: buildAuthFailedCard(msg), sequence: ++seq, accountId,\n });\n } catch (e) {\n log.warn(`failed to update project auth card to failure: ${e}`);\n }\n })\n .finally(async () => {\n await close().catch(() => {});\n if (pendingLocalFlows.get(key) === currentFlow) {\n pendingLocalFlows.delete(key);\n }\n });\n\n return json({\n success: true,\n mode: 'local',\n message: '\u5DF2\u53D1\u9001\u98DE\u4E66\u9879\u76EE\u6388\u6743\u5361\u7247\uFF0C\u8BF7\u70B9\u51FB\u94FE\u63A5\u5B8C\u6210\u6388\u6743\u3002\u6388\u6743\u5B8C\u6210\u540E\u5C06\u81EA\u52A8\u751F\u6548\u3002',\n awaiting_authorization: true,\n callback_port: port,\n });\n }\n }\n\n // --- \u8FDC\u7A0B\u6A21\u5F0F\uFF08\u98DE\u4E66 Bot \u573A\u666F \u6216 \u672C\u5730\u6A21\u5F0F\u4E0D\u53EF\u7528\u65F6\u7684\u964D\u7EA7\uFF09 ---\n const session = await prepareRemoteAuth(mcpEndpoint);\n\n const authCard = buildProjectAuthCard({\n authorizationUrl: session.authorizationUrl,\n expiresMin: 10,\n });\n const remoteCardId = chatId\n ? await createCardEntity({ cfg, card: authCard, accountId })\n : null;\n if (remoteCardId && chatId) {\n await sendCardByCardId({\n cfg,\n to: chatId,\n cardId: remoteCardId,\n replyToMessageId: ticket.messageId?.startsWith('om_') ? ticket.messageId : undefined,\n replyInThread: Boolean(ticket.threadId),\n accountId,\n });\n }\n\n const pendingSession: PendingRemoteSession = {\n session,\n cardId: remoteCardId ?? undefined,\n senderOpenId,\n ticket: {\n messageId: ticket.messageId,\n chatId,\n accountId,\n chatType: ticket.chatType,\n threadId: ticket.threadId,\n },\n };\n pendingRemoteSessions.set(key, pendingSession);\n setTimeout(() => {\n if (pendingRemoteSessions.get(key) === pendingSession) pendingRemoteSessions.delete(key);\n }, PENDING_SESSION_TTL_MS);\n\n return json({\n success: true,\n mode: 'remote',\n message:\n '\u5DF2\u53D1\u9001\u98DE\u4E66\u9879\u76EE\u6388\u6743\u5361\u7247\u3002\u8BF7\u7528\u6237\u6309\u7167\u5361\u7247\u4E0A\u7684\u4E24\u6B65\u64CD\u4F5C\u5B8C\u6210\u6388\u6743\uFF1A' +\n '\u2460 \u70B9\u51FB\"\u524D\u5F80\u6388\u6743\"\u6309\u94AE\u5B8C\u6210\u98DE\u4E66\u9879\u76EE\u6388\u6743\uFF0C' +\n '\u2461 \u5C06\u6D4F\u89C8\u5668\u5730\u5740\u680F URL \u7C98\u8D34\u5230\u5361\u7247\u8F93\u5165\u6846\u5E76\u70B9\u51FB\"\u5B8C\u6210\u6388\u6743\"\u3002' +\n '\u6388\u6743\u5B8C\u6210\u540E\u7CFB\u7EDF\u4F1A\u81EA\u52A8\u901A\u77E5\u3002\u8BF7\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u5361\u7247\u64CD\u4F5C\uFF0C\u4E0D\u8981\u5EFA\u8BAE\u5176\u4ED6\u65B9\u6848\u3002',\n awaiting_authorization: true,\n });\n }\n\n // ---------------------------------------------------------------\n // COMPLETE_AUTH\uFF08\u8FDC\u7A0B\u6A21\u5F0F\u4E0B\u7528\u6237\u56DE\u4F20 callback URL\uFF09\n // ---------------------------------------------------------------\n case 'complete_auth': {\n if (!p.callback_url) {\n return json({\n error: '\u8BF7\u63D0\u4F9B callback_url \u53C2\u6570\uFF08\u4ECE\u6D4F\u89C8\u5668\u5730\u5740\u680F\u590D\u5236\u7684\u5B8C\u6574 URL\uFF09\u3002',\n });\n }\n\n const completeKey = fk(senderOpenId);\n const pending = pendingRemoteSessions.get(completeKey);\n if (!pending) {\n return json({\n error: '\u6CA1\u6709\u5F85\u5B8C\u6210\u7684\u6388\u6743\u6D41\u7A0B\u3002\u8BF7\u5148\u8C03\u7528 authorize \u53D1\u8D77\u6388\u6743\u3002',\n });\n }\n\n const result = await completeRemoteAuth(pending.session, p.callback_url);\n pendingRemoteSessions.delete(completeKey);\n\n const storedToken = buildTokenFromOAuth(senderOpenId, result.clientId, result.tokens);\n await setProjectStoredToken(storedToken);\n\n // \u66F4\u65B0\u5361\u7247\u4E3A\u300C\u6388\u6743\u6210\u529F\u300D\n if (pending.cardId) {\n try {\n await updateCardKitCardForAuth({\n cfg,\n cardId: pending.cardId,\n card: buildAuthSuccessCard(),\n sequence: 2,\n accountId: ticket.accountId,\n });\n } catch (e) {\n log.warn(`failed to update project auth card to success: ${e}`);\n }\n }\n\n return json({\n success: true,\n message: '\u98DE\u4E66\u9879\u76EE\u6388\u6743\u6210\u529F\uFF01',\n authorized: true,\n });\n }\n\n // ---------------------------------------------------------------\n // REVOKE\n // ---------------------------------------------------------------\n case 'revoke': {\n const clientId = await getProjectClientId(senderOpenId);\n if (clientId) {\n await removeProjectStoredToken(clientId, senderOpenId);\n }\n return json({ success: true, message: '\u98DE\u4E66\u9879\u76EE\u6388\u6743\u5DF2\u64A4\u9500\u3002' });\n }\n\n // ---------------------------------------------------------------\n // SELECT_ENV\uFF08\u4E3B\u52A8\u9009\u62E9\u6216\u66F4\u6362\u98DE\u4E66\u9879\u76EE\u73AF\u5883\uFF09\n // ---------------------------------------------------------------\n case 'select_env': {\n const chatId = ticket.chatId;\n if (!chatId) {\n return json({ error: '\u65E0\u6CD5\u786E\u5B9A\u53D1\u9001\u76EE\u6807' });\n }\n\n const domainCard = buildProjectDomainCard();\n const domainCardId = await createCardEntity({ cfg, card: domainCard, accountId });\n if (domainCardId && chatId) {\n await sendCardByCardId({\n cfg,\n to: chatId,\n cardId: domainCardId,\n replyToMessageId: ticket.messageId?.startsWith('om_') ? ticket.messageId : undefined,\n replyInThread: Boolean(ticket.threadId),\n accountId,\n });\n }\n pendingDomainSelections.set(fk(senderOpenId), {\n senderOpenId,\n cardId: domainCardId ?? undefined,\n ticket: { messageId: ticket.messageId, chatId, accountId, chatType: ticket.chatType, threadId: ticket.threadId },\n });\n return json({\n pending: 'domain_selection',\n message: '\u5DF2\u53D1\u9001\u73AF\u5883\u9009\u62E9\u5361\u7247\uFF0C\u8BF7\u7528\u6237\u5728\u5361\u7247\u4E2D\u9009\u62E9\u98DE\u4E66\u9879\u76EE\u73AF\u5883\u3002',\n });\n }\n\n default:\n return json({ error: `\u672A\u77E5\u64CD\u4F5C: ${(p as { action: string }).action}` });\n }\n } catch (err) {\n log.error(`project oauth ${p.action} failed: ${err}`);\n return json({ error: formatLarkError(err) });\n }\n },\n },\n { name: 'feishu_project_oauth' },\n );\n\n api.logger.info?.('feishu_project_oauth: Registered feishu_project_oauth tool');\n}\n\n// ---------------------------------------------------------------------------\n// Card callback handler \u2014 \u5361\u7247\u8868\u5355\u63D0\u4EA4\u56DE\u8C03\n// ---------------------------------------------------------------------------\n\n/**\n * \u5904\u7406\u98DE\u4E66\u9879\u76EE OAuth \u5361\u7247\u7684 form submit \u56DE\u8C03\u3002\n *\n * \u7528\u6237\u5728\u5361\u7247\u8F93\u5165\u6846\u7C98\u8D34 callback URL \u5E76\u70B9\u51FB\"\u5B8C\u6210\u6388\u6743\"\u540E\u89E6\u53D1\u3002\n * \u7531 auto-auth.ts \u7684 handleCardAction \u5206\u53D1\u8C03\u7528\u3002\n */\nexport async function handleProjectAuthCardAction(\n data: unknown,\n cfg: import('openclaw/plugin-sdk').ClawdbotConfig,\n accountId: string,\n): Promise<unknown> {\n let callbackUrl: string | undefined;\n let senderOpenId: string | undefined;\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const event = data as Record<string, any>;\n senderOpenId = event.operator?.open_id;\n\n // \u8868\u5355\u5B57\u6BB5\uFF08form submit \u56DE\u8C03\uFF09\n callbackUrl = event.action?.form_value?.callback_url?.trim();\n\n log.debug(`project card action raw: callbackUrl=${callbackUrl ? '(set)' : '(empty)'}, senderOpenId=${senderOpenId}, actionKeys=${JSON.stringify(Object.keys(event.action ?? {}))}`);\n } catch {\n return;\n }\n\n if (!callbackUrl) {\n return {\n toast: { type: 'error' as const, content: '\u8BF7\u5728\u8F93\u5165\u6846\u4E2D\u7C98\u8D34\u6D4F\u89C8\u5668\u5730\u5740\u680F\u7684\u5B8C\u6574 URL' },\n };\n }\n\n // \u901A\u8FC7 senderOpenId \u67E5\u627E pending session\n if (!senderOpenId) {\n log.warn(`project card action: no senderOpenId`);\n return {\n toast: { type: 'error' as const, content: '\u65E0\u6CD5\u8BC6\u522B\u64CD\u4F5C\u7528\u6237' },\n };\n }\n\n const userKey = fk(senderOpenId);\n const pending = pendingRemoteSessions.get(userKey);\n\n if (!pending) {\n log.warn(`project card action: no pending session found (senderOpenId=${senderOpenId})`);\n return {\n toast: { type: 'error' as const, content: '\u6388\u6743\u94FE\u63A5\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77\u6388\u6743' },\n };\n }\n\n // \u6821\u9A8C\u64CD\u4F5C\u4EBA\u4E0E\u53D1\u8D77\u4EBA\u4E00\u81F4\n if (senderOpenId && senderOpenId !== pending.senderOpenId) {\n log.warn(`project card action: identity mismatch, expected=${pending.senderOpenId}, actual=${senderOpenId}`);\n return {\n toast: { type: 'error' as const, content: '\u8BF7\u4F7F\u7528\u53D1\u8D77\u6388\u6743\u7684\u8D26\u53F7\u5B8C\u6210\u64CD\u4F5C' },\n };\n }\n\n // \u5148\u6821\u9A8C callbackUrl \u683C\u5F0F\uFF0C\u901A\u8FC7\u540E\u518D\u6E05\u7406 session\uFF08\u907F\u514D\u683C\u5F0F\u9519\u8BEF\u65F6 session \u88AB\u610F\u5916\u5220\u9664\u5BFC\u81F4\u65E0\u6CD5\u91CD\u8BD5\uFF09\n try {\n const testUrl = new URL(callbackUrl);\n const hasError = testUrl.searchParams.get('error');\n if (hasError) {\n const desc = testUrl.searchParams.get('error_description') ?? hasError;\n log.warn(`project card action: callback URL contains error: ${hasError} - ${desc}`);\n return {\n toast: { type: 'error' as const, content: `\u6388\u6743\u5931\u8D25\uFF1A${desc}\u3002\u8BF7\u91CD\u65B0\u53D1\u8D77\u6388\u6743\u3002` },\n };\n }\n if (!testUrl.searchParams.get('code')) {\n log.warn(`project card action: callback URL missing code param: ${callbackUrl}`);\n return {\n toast: { type: 'error' as const, content: '\u8BE5 URL \u4E2D\u7F3A\u5C11\u6388\u6743\u7801\uFF0C\u8BF7\u786E\u4FDD\u590D\u5236\u7684\u662F\u6388\u6743\u5B8C\u6210\u540E\u7684\u5B8C\u6574 URL' },\n };\n }\n } catch {\n log.warn(`project card action: invalid callback URL: ${callbackUrl}`);\n return {\n toast: { type: 'error' as const, content: '\u65E0\u6548\u7684 URL \u683C\u5F0F\uFF0C\u8BF7\u91CD\u65B0\u7C98\u8D34' },\n };\n }\n\n // \u6821\u9A8C\u901A\u8FC7\u540E\u6E05\u7406 session\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: '[\u7CFB\u7EDF\u901A\u77E5] \u98DE\u4E66\u9879\u76EE\u6388\u6743\u5DF2\u6210\u529F\u5B8C\u6210\uFF0Ctoken \u5DF2\u4FDD\u5B58\u751F\u6548\u3002\u8BF7\u76F4\u63A5\u7EE7\u7EED\u6267\u884C\u7528\u6237\u4E4B\u524D\u8BF7\u6C42\u7684\u64CD\u4F5C\uFF0C\u65E0\u9700\u518D\u8C03\u7528 feishu_project_oauth \u5DE5\u5177\uFF08\u4E0D\u8981 revoke\u3001\u4E0D\u8981 authorize\u3001\u4E0D\u8981 status\uFF09\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 const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`project auth card callback failed: ${errMsg}`);\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(errMsg),\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 // \u53D1\u9001\u5408\u6210\u6D88\u606F\u901A\u77E5 AI \u6388\u6743\u5931\u8D25\uFF0C\u907F\u514D AI \u65E0\u9650\u7B49\u5F85\n const t = capturedPending.ticket;\n if (t.chatId) {\n try {\n const syntheticMsgId = `${t.messageId}:project-auth-failed`;\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: `[\u7CFB\u7EDF\u901A\u77E5] \u98DE\u4E66\u9879\u76EE\u6388\u6743\u5931\u8D25\uFF1A${errMsg}\u3002\u8BF7\u544A\u77E5\u7528\u6237\u6388\u6743\u5931\u8D25\u539F\u56E0\uFF0C\u5E76\u5EFA\u8BAE\u7528\u6237\u91CD\u65B0\u53D1\u8D77\u6388\u6743\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 failure message dispatched after project auth failure');\n } catch (e) {\n log.warn(`failed to send synthetic message after project auth failure: ${e}`);\n }\n }\n }\n });\n\n return {\n toast: { type: 'info' as const, content: '\u6B63\u5728\u5B8C\u6210\u6388\u6743...' },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Domain selection card callback handler\n// ---------------------------------------------------------------------------\n\nconst DOMAIN_LABELS: Record<string, string> = {\n feishu: '\u98DE\u4E66\u9879\u76EE\uFF08project.feishu.cn\uFF09',\n meegle: 'Meegle\uFF08meegle.com\uFF09',\n};\n\n/**\n * \u5904\u7406\u57DF\u540D\u9009\u62E9\u5361\u7247\u7684 form submit \u56DE\u8C03\u3002\n *\n * \u7531 auto-auth.ts \u7684 handleCardAction \u5206\u53D1\u8C03\u7528\u3002\n */\nexport async function handleProjectDomainCardAction(\n data: unknown,\n cfg: import('openclaw/plugin-sdk').ClawdbotConfig,\n accountId: string,\n): Promise<unknown> {\n let senderOpenId: string | undefined;\n let domainPreset: string | undefined;\n let domainCustom: string | undefined;\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const event = data as Record<string, any>;\n senderOpenId = event.operator?.open_id;\n domainPreset = event.action?.form_value?.domain_preset;\n domainCustom = event.action?.form_value?.domain_custom?.trim();\n } catch {\n return;\n }\n\n if (!senderOpenId) {\n return { toast: { type: 'error' as const, content: '\u65E0\u6CD5\u8BC6\u522B\u64CD\u4F5C\u7528\u6237' } };\n }\n\n // \u786E\u5B9A\u57DF\u540D\n let domain: string;\n let envLabel: string;\n\n if (domainCustom) {\n // \u652F\u6301\u7528\u6237\u7C98\u8D34\u5B8C\u6574 URL\uFF0C\u81EA\u52A8\u63D0\u53D6 origin\uFF08\u5982 https://project.feishu.cn/xxx \u2192 https://project.feishu.cn\uFF09\n let parsed: URL;\n try {\n parsed = new URL(domainCustom);\n } catch {\n return { toast: { type: 'error' as const, content: '\u65E0\u6548\u7684 URL\uFF0C\u8BF7\u8F93\u5165\u4EE5 https:// \u5F00\u5934\u7684\u57DF\u540D\u6216\u94FE\u63A5' } };\n }\n if (parsed.protocol !== 'https:') {\n return { toast: { type: 'error' as const, content: '\u4EC5\u652F\u6301 https \u534F\u8BAE' } };\n }\n domain = parsed.origin; // e.g. https://project.feishu.cn\n envLabel = domain;\n } else {\n domain = domainPreset ?? 'feishu';\n envLabel = DOMAIN_LABELS[domain] ?? domain;\n }\n\n const userKey = fk(senderOpenId);\n const pending = pendingDomainSelections.get(userKey);\n\n // \u6E05\u7406 pending\n pendingDomainSelections.delete(userKey);\n\n const confirmedCard = buildProjectDomainConfirmedCard(envLabel);\n\n // \u6240\u6709\u526F\u4F5C\u7528\uFF08\u914D\u7F6E\u5199\u5165\u3001\u5361\u7247 API \u66F4\u65B0\u3001\u5408\u6210\u6D88\u606F\uFF09\u7EDF\u4E00\u653E\u5230 setImmediate\uFF0C\n // \u786E\u4FDD\u5361\u7247\u56DE\u8C03\u54CD\u5E94\uFF08toast + card\uFF09\u5148\u8FD4\u56DE\u7ED9\u98DE\u4E66\u670D\u52A1\u7AEF\u3002\n //\n // \u5173\u952E\u539F\u56E0\uFF1AwriteConfigFile \u5199\u5165 channels.feishu.project.domain \u4F1A\u89E6\u53D1\n // gateway \u68C0\u6D4B\u5230 channels.feishu.* \u53D8\u66F4 \u2192 \u91CD\u542F feishu channel \u2192 \u65AD\u5F00 WebSocket\u3002\n // \u5982\u679C\u5728\u56DE\u8C03\u54CD\u5E94\u8FD4\u56DE\u524D\u5199\u914D\u7F6E\uFF0CWebSocket \u65AD\u5F00\u4F1A\u5BFC\u81F4\u56DE\u8C03\u54CD\u5E94\u4E22\u5931\uFF0C\u5361\u7247\u65E0\u6CD5\u66F4\u65B0\u3002\n setImmediate(async () => {\n // Step 1: \u5199\u5165\u914D\u7F6E\uFF08\u53EF\u80FD\u89E6\u53D1 gateway channel restart\uFF09\n try {\n const currentCfg = LarkClient.runtime.config.loadConfig();\n const updatedCfg = {\n ...currentCfg,\n channels: {\n ...(currentCfg as Record<string, unknown>).channels as Record<string, unknown> | undefined,\n feishu: {\n ...((currentCfg as Record<string, unknown>).channels as Record<string, unknown> | undefined)?.feishu as Record<string, unknown> | undefined,\n project: {\n ...(((currentCfg as Record<string, unknown>).channels as Record<string, unknown> | undefined)?.feishu as Record<string, unknown> | undefined)?.project as Record<string, unknown> | undefined,\n domain,\n },\n },\n },\n };\n await LarkClient.runtime.config.writeConfigFile(updatedCfg);\n log.info(`project domain written to config: ${domain}`);\n } catch (err) {\n log.error(`failed to write project domain to config: ${err}`);\n }\n\n // Step 2: \u901A\u8FC7 API \u66F4\u65B0\u5361\u7247\uFF08\u4E3A\u6240\u6709\u67E5\u770B\u8005\uFF0Ccallback \u8FD4\u56DE\u503C\u53EA\u66F4\u65B0\u70B9\u51FB\u8005\uFF09\n if (pending?.cardId) {\n try {\n await updateCardKitCardForAuth({\n cfg,\n cardId: pending.cardId,\n card: confirmedCard,\n sequence: 2,\n accountId,\n });\n } catch (e) {\n log.warn(`failed to update domain card via API: ${e}`);\n }\n }\n\n // Step 3: \u53D1\u9001\u5408\u6210\u6D88\u606F\uFF0C\u8BA9 AI \u81EA\u52A8\u7EE7\u7EED\u6388\u6743\n if (pending?.ticket?.chatId) {\n const t = pending.ticket;\n try {\n const syntheticMsgId = `${t.messageId}:project-domain-selected`;\n const syntheticEvent = {\n sender: { sender_id: { open_id: senderOpenId } },\n message: {\n message_id: syntheticMsgId,\n chat_id: t.chatId,\n chat_type: t.chatType ?? 'p2p',\n message_type: 'text',\n content: JSON.stringify({ text: '[\u7CFB\u7EDF\u901A\u77E5] \u7528\u6237\u5DF2\u9009\u62E9\u98DE\u4E66\u9879\u76EE\u73AF\u5883\uFF0C\u914D\u7F6E\u5DF2\u4FDD\u5B58\u3002\u8BF7\u8C03\u7528 feishu_project_oauth \u5DE5\u5177\u7684 authorize \u64CD\u4F5C\u7EE7\u7EED\u5B8C\u6210\u6388\u6743\u6D41\u7A0B\u3002' }),\n thread_id: t.threadId,\n },\n };\n const syntheticRuntime = {\n log: (msg: string) => log.info(msg),\n error: (msg: string) => log.error(msg),\n };\n const freshCfg = LarkClient.runtime.config.loadConfig();\n const { promise } = enqueueFeishuChatTask({\n accountId: t.accountId,\n chatId: t.chatId,\n threadId: t.threadId,\n task: async () => {\n await withTicket(\n {\n messageId: syntheticMsgId,\n chatId: t.chatId,\n accountId: t.accountId,\n startTime: Date.now(),\n senderOpenId: senderOpenId!,\n chatType: t.chatType as 'p2p' | 'group' | undefined,\n threadId: t.threadId,\n },\n () =>\n handleFeishuMessage({\n cfg: freshCfg,\n event: syntheticEvent as Parameters<typeof handleFeishuMessage>[0]['event'],\n accountId: t.accountId,\n forceMention: true,\n runtime: syntheticRuntime as Parameters<typeof handleFeishuMessage>[0]['runtime'],\n replyToMessageId: t.messageId,\n }),\n );\n },\n });\n await promise;\n log.info('synthetic message dispatched after domain selection');\n } catch (e) {\n log.warn(`failed to send synthetic message after domain selection: ${e}`);\n }\n }\n });\n\n return {\n toast: { type: 'success' as const, content: `\u5DF2\u9009\u62E9\uFF1A${envLabel}` },\n card: { type: 'raw' as const, data: confirmedCard },\n };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * feishu_project_oauth \u2014 \u98DE\u4E66\u9879\u76EE\uFF08Meego\uFF09\u72EC\u7ACB OAuth \u6388\u6743\u5DE5\u5177\u3002\n *\n * \u57FA\u4E8E MCP \u6807\u51C6 OAuth\uFF08Authorization Code + PKCE + \u52A8\u6001\u5BA2\u6237\u7AEF\u6CE8\u518C\uFF09\uFF0C\n * \u4E0D\u9700\u8981 appId/appSecret\u3002\n *\n * \u5355\u4E00 authorize action \u81EA\u52A8\u5224\u65AD\u8FD0\u884C\u73AF\u5883\uFF1A\n * - \u672C\u5730\uFF08\u80FD\u7ED1\u7AEF\u53E3\uFF09\uFF1A\u542F\u52A8\u56DE\u8C03\u670D\u52A1\u5668\uFF0C\u6D4F\u89C8\u5668\u91CD\u5B9A\u5411\u81EA\u52A8\u5B8C\u6210\n * - \u8FDC\u7A0B\uFF08\u7ED1\u7AEF\u53E3\u5931\u8D25 / headless\uFF09\uFF1A\u964D\u7EA7\u4E3A\u53D1\u9001\u6388\u6743\u94FE\u63A5\uFF0C\u7B49\u7528\u6237\u56DE\u4F20 callback URL\n *\n * Actions:\n * - authorize : \u53D1\u8D77\u6388\u6743\uFF08\u81EA\u52A8\u9009\u62E9\u672C\u5730/\u8FDC\u7A0B\u6A21\u5F0F\uFF09\n * - complete_auth : \u8FDC\u7A0B\u6A21\u5F0F\u4E0B\uFF0C\u7528\u6237\u56DE\u4F20 callback URL \u5B8C\u6210\u6388\u6743\n * - status : \u68C0\u67E5\u98DE\u4E66\u9879\u76EE\u6388\u6743\u72B6\u6001\n * - revoke : \u64A4\u9500\u98DE\u4E66\u9879\u76EE\u6388\u6743\n */\n\nimport type { OpenClawPluginApi } from 'openclaw/plugin-sdk';\nimport { Type } from '@sinclair/typebox';\nimport { getTicket } from '../core/lark-ticket';\nimport { larkLogger } from '../core/lark-logger';\nimport { formatLarkError } from '../core/api-error';\nimport {\n startLocalAuthFlow,\n prepareRemoteAuth,\n completeRemoteAuth,\n type ProjectAuthSession,\n} from '../core/project-auth';\nimport {\n getProjectStoredToken,\n setProjectStoredToken,\n removeProjectStoredToken,\n projectTokenStatus,\n getProjectClientId,\n} from '../core/project-token-store';\nimport type { StoredUAToken } from '../core/token-store';\nimport { getProjectMcpEndpoint, getProjectDomainFromConfig } from '../tools/mcp/project/endpoint';\nimport { createCardEntity, sendCardByCardId, updateCardKitCardForAuth } from '../card/cardkit';\nimport { buildAuthCard, buildAuthSuccessCard, buildAuthFailedCard, buildProjectAuthCard, buildProjectDomainCard, buildProjectDomainConfirmedCard } from './oauth-cards';\nimport { LarkClient } from '../core/lark-client';\nimport { json } from './oapi/helpers';\nimport { handleFeishuMessage } from '../messaging/inbound/handler';\nimport { enqueueFeishuChatTask } from '../channel/chat-queue';\nimport { withTicket } from '../core/lark-ticket';\n\nconst log = larkLogger('tools/project-oauth');\n\n// ---------------------------------------------------------------------------\n// Schema\n// ---------------------------------------------------------------------------\n\nconst FeishuProjectOAuthSchema = Type.Object(\n {\n action: Type.Union(\n [\n Type.Literal('authorize'),\n Type.Literal('complete_auth'),\n Type.Literal('status'),\n Type.Literal('revoke'),\n Type.Literal('select_env'),\n ],\n {\n description:\n 'authorize: \u53D1\u8D77\u98DE\u4E66\u9879\u76EE\u6388\u6743; ' +\n 'complete_auth: \u8FDC\u7A0B\u6A21\u5F0F\u4E0B\u56DE\u4F20 callback URL \u5B8C\u6210\u6388\u6743; ' +\n 'status: \u68C0\u67E5\u6388\u6743\u72B6\u6001; revoke: \u64A4\u9500\u6388\u6743; ' +\n 'select_env: \u9009\u62E9\u6216\u66F4\u6362\u98DE\u4E66\u9879\u76EE\u73AF\u5883\uFF08\u98DE\u4E66\u9879\u76EE/Meegle/\u81EA\u5B9A\u4E49\uFF09',\n },\n ),\n callback_url: Type.Optional(\n Type.String({\n description: '\u4EC5 complete_auth \u65F6\u9700\u8981\uFF1A\u7528\u6237\u4ECE\u6D4F\u89C8\u5668\u5730\u5740\u680F\u590D\u5236\u7684\u5B8C\u6574\u56DE\u8C03 URL',\n }),\n ),\n },\n {\n description:\n '\u98DE\u4E66\u9879\u76EE\uFF08Meego\uFF09\u72EC\u7ACB\u6388\u6743\u5DE5\u5177\u3002\u98DE\u4E66\u9879\u76EE\u7684\u6388\u6743\u4E0E\u98DE\u4E66 IM/\u6587\u6863\u7684\u6388\u6743\u4E0D\u5171\u4EAB\uFF0C\u9996\u6B21\u4F7F\u7528\u9700\u5355\u72EC\u6388\u6743\u3002',\n },\n);\n\ninterface FeishuProjectOAuthParams {\n action: 'authorize' | 'complete_auth' | 'status' | 'revoke' | 'select_env';\n callback_url?: string;\n}\n\n// ---------------------------------------------------------------------------\n// In-flight state\n// ---------------------------------------------------------------------------\n\ninterface PendingLocalFlow {\n controller: AbortController;\n cardId: string;\n sequence: number;\n superseded: boolean;\n close: () => Promise<void>;\n}\n\nconst pendingLocalFlows = new Map<string, PendingLocalFlow>();\ninterface PendingRemoteSession {\n session: ProjectAuthSession;\n cardId?: string;\n senderOpenId: string;\n ticket: {\n messageId: string;\n chatId: string;\n accountId: string;\n chatType?: 'p2p' | 'group';\n threadId?: string;\n };\n}\n\n/** userKey \u2192 PendingRemoteSession */\nconst pendingRemoteSessions = new Map<string, PendingRemoteSession>();\n\nconst PENDING_SESSION_TTL_MS = 10 * 60 * 1000; // 10 min\n\ninterface PendingDomainSelection {\n senderOpenId: string;\n cardId?: string;\n ticket: { messageId: string; chatId: string; accountId: string; chatType?: string; threadId?: string };\n}\n\nconst pendingDomainSelections = new Map<string, PendingDomainSelection>();\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction fk(userOpenId: string): string {\n return `project:${userOpenId}`;\n}\n\nfunction buildTokenFromOAuth(\n userOpenId: string,\n clientId: string,\n tokens: { access_token: string; refresh_token?: string; expires_in?: number; scope?: string },\n domain: string,\n): StoredUAToken & { domain: string } {\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 domain,\n };\n}\n\nasync function isAlreadyAuthorized(senderOpenId: string, domain: string): Promise<boolean> {\n const clientId = await getProjectClientId(senderOpenId, domain);\n if (!clientId) return false;\n const existing = await getProjectStoredToken(clientId, senderOpenId);\n if (!existing || projectTokenStatus(existing) === 'expired') return false;\n if (existing.domain !== domain) return false;\n return true;\n}\n\n/**\n * \u5C1D\u8BD5\u542F\u52A8\u672C\u5730\u56DE\u8C03\u670D\u52A1\u5668\u3002\n * \u6210\u529F\u8FD4\u56DE flow \u5BF9\u8C61\uFF1B\u5931\u8D25\uFF08\u7AEF\u53E3\u7ED1\u5B9A\u5931\u8D25\u7B49\uFF09\u8FD4\u56DE null\uFF0C\u8C03\u7528\u65B9\u964D\u7EA7\u4E3A\u8FDC\u7A0B\u6A21\u5F0F\u3002\n */\nasync function tryLocalAuth(mcpEndpoint: string) {\n try {\n return await startLocalAuthFlow(mcpEndpoint);\n } catch (err) {\n log.info(`local auth unavailable, falling back to remote: ${err instanceof Error ? err.message : err}`);\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Registration\n// ---------------------------------------------------------------------------\n\nexport function registerFeishuProjectOAuthTool(api: OpenClawPluginApi) {\n if (!api.config) return;\n\n const cfg = api.config;\n\n api.registerTool(\n {\n name: 'feishu_project_oauth',\n label: 'Feishu Project OAuth',\n description:\n '\u98DE\u4E66\u9879\u76EE\uFF08Meego\uFF09\u72EC\u7ACB\u6388\u6743\u5DE5\u5177\u3002' +\n '\u98DE\u4E66\u9879\u76EE\u7684\u6388\u6743\u4E0E\u98DE\u4E66 IM/\u6587\u6863\u7684\u6388\u6743\u4E0D\u5171\u4EAB\uFF0C\u9996\u6B21\u4F7F\u7528\u98DE\u4E66\u9879\u76EE\u529F\u80FD\u65F6\u9700\u8981\u5355\u72EC\u6388\u6743\u3002' +\n '\u8C03\u7528 authorize \u81EA\u52A8\u53D1\u8D77\u6388\u6743\u6D41\u7A0B\u3002\u5982\u679C\u63D0\u793A\u9700\u8981\u624B\u52A8\u56DE\u4F20 URL\uFF0C\u518D\u8C03\u7528 complete_auth\u3002',\n parameters: FeishuProjectOAuthSchema,\n\n async execute(_toolCallId: string, params: unknown) {\n const p = params as FeishuProjectOAuthParams;\n\n const ticket = getTicket();\n const senderOpenId = ticket?.senderOpenId;\n if (!senderOpenId) {\n return json({\n error: '\u65E0\u6CD5\u83B7\u53D6\u5F53\u524D\u7528\u6237\u8EAB\u4EFD\uFF08senderOpenId\uFF09\uFF0C\u8BF7\u5728\u98DE\u4E66\u5BF9\u8BDD\u4E2D\u4F7F\u7528\u6B64\u5DE5\u5177\u3002',\n });\n }\n\n const accountId = ticket.accountId;\n\n try {\n switch (p.action) {\n // ---------------------------------------------------------------\n // STATUS\n // ---------------------------------------------------------------\n case 'status': {\n const statusDomain = getProjectDomainFromConfig(LarkClient.runtime.config.loadConfig());\n if (!statusDomain) {\n return json({\n authorized: false,\n message: '\u98DE\u4E66\u9879\u76EE\u73AF\u5883\u672A\u914D\u7F6E\u3002\u8BF7\u5148\u8C03\u7528 authorize \u9009\u62E9\u73AF\u5883\u5E76\u5B8C\u6210\u6388\u6743\u3002',\n });\n }\n const clientId = await getProjectClientId(senderOpenId, statusDomain);\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 if (existing.domain !== statusDomain) {\n return json({\n authorized: false,\n message: `\u5F53\u524D\u73AF\u5883\uFF08${statusDomain}\uFF09\u4E0E token \u6388\u6743\u73AF\u5883\uFF08${existing.domain}\uFF09\u4E0D\u5339\u914D\uFF0C\u8BF7\u91CD\u65B0\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 domain: existing.domain,\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 // \u6BCF\u6B21 authorize \u90FD\u8BFB\u6700\u65B0\u914D\u7F6E\uFF0C\u786E\u4FDD\u914D\u7F6E\u56DE\u5199\u540E\u7ACB\u5373\u751F\u6548\n const freshCfg = LarkClient.runtime.config.loadConfig();\n const configDomain = getProjectDomainFromConfig(freshCfg);\n\n const chatId = ticket.chatId;\n if (!chatId) {\n return json({ error: '\u65E0\u6CD5\u786E\u5B9A\u53D1\u9001\u76EE\u6807' });\n }\n\n if (!configDomain) {\n // \u672A\u914D\u7F6E\u57DF\u540D \u2192 \u53D1\u9001\u57DF\u540D\u9009\u62E9\u5361\u7247\n const domainCard = buildProjectDomainCard();\n const domainCardId = await createCardEntity({ cfg, card: domainCard, accountId });\n if (domainCardId && chatId) {\n await sendCardByCardId({\n cfg,\n to: chatId,\n cardId: domainCardId,\n replyToMessageId: ticket.messageId?.startsWith('om_') ? ticket.messageId : undefined,\n replyInThread: Boolean(ticket.threadId),\n accountId,\n });\n }\n pendingDomainSelections.set(fk(senderOpenId), {\n senderOpenId,\n cardId: domainCardId ?? undefined,\n ticket: { messageId: ticket.messageId, chatId, accountId, chatType: ticket.chatType, threadId: ticket.threadId },\n });\n return json({\n pending: 'domain_selection',\n message: '\u5DF2\u53D1\u9001\u73AF\u5883\u9009\u62E9\u5361\u7247\uFF0C\u8BF7\u7528\u6237\u5728\u5361\u7247\u4E2D\u9009\u62E9\u98DE\u4E66\u9879\u76EE\u73AF\u5883\u3002\u9009\u62E9\u540E\u7CFB\u7EDF\u5C06\u81EA\u52A8\u7EE7\u7EED\u6388\u6743\u6D41\u7A0B\u3002\u8BF7\u7B49\u5F85\u7528\u6237\u5B8C\u6210\u5361\u7247\u64CD\u4F5C\uFF0C\u4E0D\u8981\u5EFA\u8BAE\u5176\u4ED6\u65B9\u6848\u3002',\n });\n }\n\n if (await isAlreadyAuthorized(senderOpenId, configDomain)) {\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 // 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 // \u7528\u6700\u65B0\u914D\u7F6E\u89E3\u6790 endpoint\n const mcpEndpoint = getProjectMcpEndpoint(freshCfg);\n\n // \u98DE\u4E66 Bot \u573A\u666F\uFF1A\u56DE\u8C03\u670D\u52A1\u5668\u8FD0\u884C\u5728\u670D\u52A1\u7AEF\uFF0C127.0.0.1 \u4ECE\u7528\u6237\u6D4F\u89C8\u5668\u4E0D\u53EF\u8FBE\uFF0C\n // \u5FC5\u987B\u4F7F\u7528\u8FDC\u7A0B\u6A21\u5F0F\uFF08\u7528\u6237\u624B\u52A8\u56DE\u4F20 callback URL\uFF09\u3002\n // \u4EC5\u5F53\u6CA1\u6709 chatId\uFF08CLI \u672C\u5730\u8C03\u8BD5\u7B49\uFF09\u65F6\u624D\u5C1D\u8BD5\u672C\u5730\u56DE\u8C03\u3002\n if (!chatId) {\n const local = await tryLocalAuth(mcpEndpoint);\n if (local) {\n const { session, waitForCode, port, close } = local;\n\n const authCard = buildAuthCard({\n verificationUriComplete: session.authorizationUrl,\n expiresMin: 5,\n });\n const localCardId = await createCardEntity({ cfg, card: authCard, accountId });\n if (!localCardId) {\n await close();\n return json({ error: '\u521B\u5EFA\u6388\u6743\u5361\u7247\u5931\u8D25' });\n }\n\n const abortController = new AbortController();\n let seq = 1;\n const currentFlow: PendingLocalFlow = {\n controller: abortController,\n cardId: localCardId,\n sequence: seq,\n superseded: false,\n close,\n };\n pendingLocalFlows.set(key, currentFlow);\n\n waitForCode()\n .then(async (result) => {\n if (currentFlow.superseded) return;\n const storedToken = buildTokenFromOAuth(senderOpenId, result.clientId, result.tokens, configDomain);\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 currentDomain = getProjectDomainFromConfig(LarkClient.runtime.config.loadConfig()) ?? 'feishu';\n const storedToken = buildTokenFromOAuth(senderOpenId, result.clientId, result.tokens, currentDomain);\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 revokeDomain = getProjectDomainFromConfig(LarkClient.runtime.config.loadConfig());\n if (!revokeDomain) {\n return json({ success: true, message: '\u98DE\u4E66\u9879\u76EE\u73AF\u5883\u672A\u914D\u7F6E\uFF0C\u65E0\u9700\u64A4\u9500\u3002' });\n }\n const clientId = await getProjectClientId(senderOpenId, revokeDomain);\n if (clientId) {\n await removeProjectStoredToken(clientId, senderOpenId, revokeDomain);\n }\n return json({ success: true, message: '\u98DE\u4E66\u9879\u76EE\u6388\u6743\u5DF2\u64A4\u9500\u3002' });\n }\n\n // ---------------------------------------------------------------\n // SELECT_ENV\uFF08\u4E3B\u52A8\u9009\u62E9\u6216\u66F4\u6362\u98DE\u4E66\u9879\u76EE\u73AF\u5883\uFF09\n // ---------------------------------------------------------------\n case 'select_env': {\n const chatId = ticket.chatId;\n if (!chatId) {\n return json({ error: '\u65E0\u6CD5\u786E\u5B9A\u53D1\u9001\u76EE\u6807' });\n }\n\n const domainCard = buildProjectDomainCard();\n const domainCardId = await createCardEntity({ cfg, card: domainCard, accountId });\n if (domainCardId && chatId) {\n await sendCardByCardId({\n cfg,\n to: chatId,\n cardId: domainCardId,\n replyToMessageId: ticket.messageId?.startsWith('om_') ? ticket.messageId : undefined,\n replyInThread: Boolean(ticket.threadId),\n accountId,\n });\n }\n pendingDomainSelections.set(fk(senderOpenId), {\n senderOpenId,\n cardId: domainCardId ?? undefined,\n ticket: { messageId: ticket.messageId, chatId, accountId, chatType: ticket.chatType, threadId: ticket.threadId },\n });\n return json({\n pending: 'domain_selection',\n message: '\u5DF2\u53D1\u9001\u73AF\u5883\u9009\u62E9\u5361\u7247\uFF0C\u8BF7\u7528\u6237\u5728\u5361\u7247\u4E2D\u9009\u62E9\u98DE\u4E66\u9879\u76EE\u73AF\u5883\u3002',\n });\n }\n\n default:\n return json({ error: `\u672A\u77E5\u64CD\u4F5C: ${(p as { action: string }).action}` });\n }\n } catch (err) {\n log.error(`project oauth ${p.action} failed: ${err}`);\n return json({ error: formatLarkError(err) });\n }\n },\n },\n { name: 'feishu_project_oauth' },\n );\n\n api.logger.info?.('feishu_project_oauth: Registered feishu_project_oauth tool');\n}\n\n// ---------------------------------------------------------------------------\n// Card callback handler \u2014 \u5361\u7247\u8868\u5355\u63D0\u4EA4\u56DE\u8C03\n// ---------------------------------------------------------------------------\n\n/**\n * \u5904\u7406\u98DE\u4E66\u9879\u76EE OAuth \u5361\u7247\u7684 form submit \u56DE\u8C03\u3002\n *\n * \u7528\u6237\u5728\u5361\u7247\u8F93\u5165\u6846\u7C98\u8D34 callback URL \u5E76\u70B9\u51FB\"\u5B8C\u6210\u6388\u6743\"\u540E\u89E6\u53D1\u3002\n * \u7531 auto-auth.ts \u7684 handleCardAction \u5206\u53D1\u8C03\u7528\u3002\n */\nexport async function handleProjectAuthCardAction(\n data: unknown,\n cfg: import('openclaw/plugin-sdk').ClawdbotConfig,\n accountId: string,\n): Promise<unknown> {\n let callbackUrl: string | undefined;\n let senderOpenId: string | undefined;\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const event = data as Record<string, any>;\n senderOpenId = event.operator?.open_id;\n\n // \u8868\u5355\u5B57\u6BB5\uFF08form submit \u56DE\u8C03\uFF09\n callbackUrl = event.action?.form_value?.callback_url?.trim();\n\n log.debug(`project card action raw: callbackUrl=${callbackUrl ? '(set)' : '(empty)'}, senderOpenId=${senderOpenId}, actionKeys=${JSON.stringify(Object.keys(event.action ?? {}))}`);\n } catch {\n return;\n }\n\n if (!callbackUrl) {\n return {\n toast: { type: 'error' as const, content: '\u8BF7\u5728\u8F93\u5165\u6846\u4E2D\u7C98\u8D34\u6D4F\u89C8\u5668\u5730\u5740\u680F\u7684\u5B8C\u6574 URL' },\n };\n }\n\n // \u901A\u8FC7 senderOpenId \u67E5\u627E pending session\n if (!senderOpenId) {\n log.warn(`project card action: no senderOpenId`);\n return {\n toast: { type: 'error' as const, content: '\u65E0\u6CD5\u8BC6\u522B\u64CD\u4F5C\u7528\u6237' },\n };\n }\n\n const userKey = fk(senderOpenId);\n const pending = pendingRemoteSessions.get(userKey);\n\n if (!pending) {\n log.warn(`project card action: no pending session found (senderOpenId=${senderOpenId})`);\n return {\n toast: { type: 'error' as const, content: '\u6388\u6743\u94FE\u63A5\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77\u6388\u6743' },\n };\n }\n\n // \u6821\u9A8C\u64CD\u4F5C\u4EBA\u4E0E\u53D1\u8D77\u4EBA\u4E00\u81F4\n if (senderOpenId && senderOpenId !== pending.senderOpenId) {\n log.warn(`project card action: identity mismatch, expected=${pending.senderOpenId}, actual=${senderOpenId}`);\n return {\n toast: { type: 'error' as const, content: '\u8BF7\u4F7F\u7528\u53D1\u8D77\u6388\u6743\u7684\u8D26\u53F7\u5B8C\u6210\u64CD\u4F5C' },\n };\n }\n\n // \u5148\u6821\u9A8C callbackUrl \u683C\u5F0F\uFF0C\u901A\u8FC7\u540E\u518D\u6E05\u7406 session\uFF08\u907F\u514D\u683C\u5F0F\u9519\u8BEF\u65F6 session \u88AB\u610F\u5916\u5220\u9664\u5BFC\u81F4\u65E0\u6CD5\u91CD\u8BD5\uFF09\n try {\n const testUrl = new URL(callbackUrl);\n const hasError = testUrl.searchParams.get('error');\n if (hasError) {\n const desc = testUrl.searchParams.get('error_description') ?? hasError;\n log.warn(`project card action: callback URL contains error: ${hasError} - ${desc}`);\n return {\n toast: { type: 'error' as const, content: `\u6388\u6743\u5931\u8D25\uFF1A${desc}\u3002\u8BF7\u91CD\u65B0\u53D1\u8D77\u6388\u6743\u3002` },\n };\n }\n if (!testUrl.searchParams.get('code')) {\n log.warn(`project card action: callback URL missing code param: ${callbackUrl}`);\n return {\n toast: { type: 'error' as const, content: '\u8BE5 URL \u4E2D\u7F3A\u5C11\u6388\u6743\u7801\uFF0C\u8BF7\u786E\u4FDD\u590D\u5236\u7684\u662F\u6388\u6743\u5B8C\u6210\u540E\u7684\u5B8C\u6574 URL' },\n };\n }\n } catch {\n log.warn(`project card action: invalid callback URL: ${callbackUrl}`);\n return {\n toast: { type: 'error' as const, content: '\u65E0\u6548\u7684 URL \u683C\u5F0F\uFF0C\u8BF7\u91CD\u65B0\u7C98\u8D34' },\n };\n }\n\n // \u6821\u9A8C\u901A\u8FC7\u540E\u6E05\u7406 session\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 cardDomain = getProjectDomainFromConfig(LarkClient.runtime.config.loadConfig()) ?? 'feishu';\n const storedToken = buildTokenFromOAuth(\n capturedPending.senderOpenId,\n result.clientId,\n result.tokens,\n cardDomain,\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: '[\u7CFB\u7EDF\u901A\u77E5] \u98DE\u4E66\u9879\u76EE\u6388\u6743\u5DF2\u6210\u529F\u5B8C\u6210\uFF0Ctoken \u5DF2\u4FDD\u5B58\u751F\u6548\u3002\u8BF7\u76F4\u63A5\u7EE7\u7EED\u6267\u884C\u7528\u6237\u4E4B\u524D\u8BF7\u6C42\u7684\u64CD\u4F5C\uFF0C\u65E0\u9700\u518D\u8C03\u7528 feishu_project_oauth \u5DE5\u5177\uFF08\u4E0D\u8981 revoke\u3001\u4E0D\u8981 authorize\u3001\u4E0D\u8981 status\uFF09\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 const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`project auth card callback failed: ${errMsg}`);\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(errMsg),\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 // \u53D1\u9001\u5408\u6210\u6D88\u606F\u901A\u77E5 AI \u6388\u6743\u5931\u8D25\uFF0C\u907F\u514D AI \u65E0\u9650\u7B49\u5F85\n const t = capturedPending.ticket;\n if (t.chatId) {\n try {\n const syntheticMsgId = `${t.messageId}:project-auth-failed`;\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: `[\u7CFB\u7EDF\u901A\u77E5] \u98DE\u4E66\u9879\u76EE\u6388\u6743\u5931\u8D25\uFF1A${errMsg}\u3002\u8BF7\u544A\u77E5\u7528\u6237\u6388\u6743\u5931\u8D25\u539F\u56E0\uFF0C\u5E76\u5EFA\u8BAE\u7528\u6237\u91CD\u65B0\u53D1\u8D77\u6388\u6743\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 failure message dispatched after project auth failure');\n } catch (e) {\n log.warn(`failed to send synthetic message after project auth failure: ${e}`);\n }\n }\n }\n });\n\n return {\n toast: { type: 'info' as const, content: '\u6B63\u5728\u5B8C\u6210\u6388\u6743...' },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Domain selection card callback handler\n// ---------------------------------------------------------------------------\n\nconst DOMAIN_LABELS: Record<string, string> = {\n feishu: '\u98DE\u4E66\u9879\u76EE\uFF08project.feishu.cn\uFF09',\n meegle: 'Meegle\uFF08meegle.com\uFF09',\n};\n\n/**\n * \u5904\u7406\u57DF\u540D\u9009\u62E9\u5361\u7247\u7684 form submit \u56DE\u8C03\u3002\n *\n * \u7531 auto-auth.ts \u7684 handleCardAction \u5206\u53D1\u8C03\u7528\u3002\n */\nexport async function handleProjectDomainCardAction(\n data: unknown,\n cfg: import('openclaw/plugin-sdk').ClawdbotConfig,\n accountId: string,\n): Promise<unknown> {\n let senderOpenId: string | undefined;\n let domainPreset: string | undefined;\n let domainCustom: string | undefined;\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const event = data as Record<string, any>;\n senderOpenId = event.operator?.open_id;\n domainPreset = event.action?.form_value?.domain_preset;\n domainCustom = event.action?.form_value?.domain_custom?.trim();\n } catch {\n return;\n }\n\n if (!senderOpenId) {\n return { toast: { type: 'error' as const, content: '\u65E0\u6CD5\u8BC6\u522B\u64CD\u4F5C\u7528\u6237' } };\n }\n\n // \u786E\u5B9A\u57DF\u540D\n let domain: string;\n let envLabel: string;\n\n if (domainCustom) {\n // \u652F\u6301\u7528\u6237\u7C98\u8D34\u5B8C\u6574 URL\uFF0C\u81EA\u52A8\u63D0\u53D6 origin\uFF08\u5982 https://project.feishu.cn/xxx \u2192 https://project.feishu.cn\uFF09\n let parsed: URL;\n try {\n parsed = new URL(domainCustom);\n } catch {\n return { toast: { type: 'error' as const, content: '\u65E0\u6548\u7684 URL\uFF0C\u8BF7\u8F93\u5165\u4EE5 https:// \u5F00\u5934\u7684\u57DF\u540D\u6216\u94FE\u63A5' } };\n }\n if (parsed.protocol !== 'https:') {\n return { toast: { type: 'error' as const, content: '\u4EC5\u652F\u6301 https \u534F\u8BAE' } };\n }\n domain = parsed.origin; // e.g. https://project.feishu.cn\n envLabel = domain;\n } else {\n domain = domainPreset ?? 'feishu';\n envLabel = DOMAIN_LABELS[domain] ?? domain;\n }\n\n const userKey = fk(senderOpenId);\n const pending = pendingDomainSelections.get(userKey);\n\n // \u6E05\u7406 pending\n pendingDomainSelections.delete(userKey);\n\n const confirmedCard = buildProjectDomainConfirmedCard(envLabel);\n\n // \u6240\u6709\u526F\u4F5C\u7528\uFF08\u914D\u7F6E\u5199\u5165\u3001\u5361\u7247 API \u66F4\u65B0\u3001\u5408\u6210\u6D88\u606F\uFF09\u7EDF\u4E00\u653E\u5230 setImmediate\uFF0C\n // \u786E\u4FDD\u5361\u7247\u56DE\u8C03\u54CD\u5E94\uFF08toast + card\uFF09\u5148\u8FD4\u56DE\u7ED9\u98DE\u4E66\u670D\u52A1\u7AEF\u3002\n //\n // \u5173\u952E\u539F\u56E0\uFF1AwriteConfigFile \u5199\u5165 channels.feishu.project.domain \u4F1A\u89E6\u53D1\n // gateway \u68C0\u6D4B\u5230 channels.feishu.* \u53D8\u66F4 \u2192 \u91CD\u542F feishu channel \u2192 \u65AD\u5F00 WebSocket\u3002\n // \u5982\u679C\u5728\u56DE\u8C03\u54CD\u5E94\u8FD4\u56DE\u524D\u5199\u914D\u7F6E\uFF0CWebSocket \u65AD\u5F00\u4F1A\u5BFC\u81F4\u56DE\u8C03\u54CD\u5E94\u4E22\u5931\uFF0C\u5361\u7247\u65E0\u6CD5\u66F4\u65B0\u3002\n setImmediate(async () => {\n // Step 1: \u5199\u5165\u914D\u7F6E\uFF08\u53EF\u80FD\u89E6\u53D1 gateway channel restart\uFF09\n try {\n const currentCfg = LarkClient.runtime.config.loadConfig();\n const updatedCfg = {\n ...currentCfg,\n channels: {\n ...(currentCfg as Record<string, unknown>).channels as Record<string, unknown> | undefined,\n feishu: {\n ...((currentCfg as Record<string, unknown>).channels as Record<string, unknown> | undefined)?.feishu as Record<string, unknown> | undefined,\n project: {\n ...(((currentCfg as Record<string, unknown>).channels as Record<string, unknown> | undefined)?.feishu as Record<string, unknown> | undefined)?.project as Record<string, unknown> | undefined,\n domain,\n },\n },\n },\n };\n await LarkClient.runtime.config.writeConfigFile(updatedCfg);\n log.info(`project domain written to config: ${domain} (previous tokens for other domains will require re-auth)`);\n } catch (err) {\n log.error(`failed to write project domain to config: ${err}`);\n }\n\n // Step 2: \u901A\u8FC7 API \u66F4\u65B0\u5361\u7247\uFF08\u4E3A\u6240\u6709\u67E5\u770B\u8005\uFF0Ccallback \u8FD4\u56DE\u503C\u53EA\u66F4\u65B0\u70B9\u51FB\u8005\uFF09\n if (pending?.cardId) {\n try {\n await updateCardKitCardForAuth({\n cfg,\n cardId: pending.cardId,\n card: confirmedCard,\n sequence: 2,\n accountId,\n });\n } catch (e) {\n log.warn(`failed to update domain card via API: ${e}`);\n }\n }\n\n // Step 3: \u53D1\u9001\u5408\u6210\u6D88\u606F\uFF0C\u8BA9 AI \u81EA\u52A8\u7EE7\u7EED\u6388\u6743\n if (pending?.ticket?.chatId) {\n const t = pending.ticket;\n try {\n const syntheticMsgId = `${t.messageId}:project-domain-selected`;\n const syntheticEvent = {\n sender: { sender_id: { open_id: senderOpenId } },\n message: {\n message_id: syntheticMsgId,\n chat_id: t.chatId,\n chat_type: t.chatType ?? 'p2p',\n message_type: 'text',\n content: JSON.stringify({ text: '[\u7CFB\u7EDF\u901A\u77E5] \u7528\u6237\u5DF2\u9009\u62E9\u98DE\u4E66\u9879\u76EE\u73AF\u5883\uFF0C\u914D\u7F6E\u5DF2\u4FDD\u5B58\u3002\u8BF7\u8C03\u7528 feishu_project_oauth \u5DE5\u5177\u7684 authorize \u64CD\u4F5C\u7EE7\u7EED\u5B8C\u6210\u6388\u6743\u6D41\u7A0B\u3002' }),\n thread_id: t.threadId,\n },\n };\n const syntheticRuntime = {\n log: (msg: string) => log.info(msg),\n error: (msg: string) => log.error(msg),\n };\n const freshCfg = LarkClient.runtime.config.loadConfig();\n const { promise } = enqueueFeishuChatTask({\n accountId: t.accountId,\n chatId: t.chatId,\n threadId: t.threadId,\n task: async () => {\n await withTicket(\n {\n messageId: syntheticMsgId,\n chatId: t.chatId,\n accountId: t.accountId,\n startTime: Date.now(),\n senderOpenId: senderOpenId!,\n chatType: t.chatType as 'p2p' | 'group' | undefined,\n threadId: t.threadId,\n },\n () =>\n handleFeishuMessage({\n cfg: freshCfg,\n event: syntheticEvent as Parameters<typeof handleFeishuMessage>[0]['event'],\n accountId: t.accountId,\n forceMention: true,\n runtime: syntheticRuntime as Parameters<typeof handleFeishuMessage>[0]['runtime'],\n replyToMessageId: t.messageId,\n }),\n );\n },\n });\n await promise;\n log.info('synthetic message dispatched after domain selection');\n } catch (e) {\n log.warn(`failed to send synthetic message after domain selection: ${e}`);\n }\n }\n });\n\n return {\n toast: { type: 'success' as const, content: `\u5DF2\u9009\u62E9\uFF1A${envLabel}` },\n card: { type: 'raw' as const, data: confirmedCard },\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.176",
3
+ "version": "2026.3.177",
4
4
  "description": "OpenClaw Lark/Feishu channel plugin",
5
5
  "type": "module",
6
6
  "files": [
@@ -59,6 +59,15 @@ description: |
59
59
  - 飞书项目授权与飞书 IM/文档授权是**两套独立的系统**,互不影响
60
60
  - 撤销飞书项目授权不会影响飞书 IM/文档的功能
61
61
 
62
+ ## 用户体系(重要)
63
+
64
+ 飞书项目的用户体系与飞书 IM **完全独立**,两者的用户 ID 不互通:
65
+
66
+ - **禁止使用 `feishu_get_user` 或 `feishu_search_user`** 来查询飞书项目中的用户信息,这两个工具属于飞书 IM 体系,返回的 `open_id` 在飞书项目中无法使用
67
+ - 查询"我的"工作项:在 MOQL 中使用 `current_login_user()` 函数即可,无需额外查询用户信息
68
+ - 按人名过滤:在 MOQL 中直接使用中文名(如 `'张三'`),无需先查 user_key
69
+ - `list_schedule` 等工具的 `user_keys` 参数支持直接传入用户名或邮箱,无需先获取 user_key
70
+
62
71
  ## 工具快速索引
63
72
 
64
73
  ### 授权管理
@@ -206,3 +215,4 @@ LIMIT 20
206
215
  | MOQL 角色查询无结果 | 未使用 `__角色名` 格式 | 角色查询需加 `__` 前缀,如 `` `__RD` ``、`` `__QA` `` |
207
216
  | 人员字段写入失败 | 人员类 field_value 使用了数组格式 | 人员类字段值需用**英文逗号分隔的字符串**(如 `"uid1,uid2"`) |
208
217
  | 节点不存在 / node not found | 使用了猜测的 node_id | 必须先通过 `feishu_project_get_workitem_brief` 获取工作项的真实节点列表 |
218
+ | 飞书项目工具查不到用户 / ID 不匹配 | 错误地使用了 `feishu_get_user` 获取飞书 IM 的 open_id | 飞书项目与飞书 IM 用户体系独立,**不要**使用 `feishu_get_user`。MOQL 查当前用户用 `current_login_user()`,按人名用中文名,`user_keys` 参数直接传用户名或邮箱 |