@colinlu50/openclaw-lark-stream 2026.3.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +141 -0
- package/README.zh.md +70 -0
- package/bin/openclaw-lark.js +48 -0
- package/index.d.ts +36 -0
- package/index.js +118 -0
- package/openclaw.plugin.json +10 -0
- package/package.json +66 -0
- package/skills/feishu-bitable/SKILL.md +248 -0
- package/skills/feishu-bitable/references/examples.md +813 -0
- package/skills/feishu-bitable/references/field-properties.md +763 -0
- package/skills/feishu-bitable/references/record-values.md +911 -0
- package/skills/feishu-calendar/SKILL.md +244 -0
- package/skills/feishu-channel-rules/SKILL.md +24 -0
- package/skills/feishu-channel-rules/references/markdown-syntax.md +138 -0
- package/skills/feishu-create-doc/SKILL.md +719 -0
- package/skills/feishu-fetch-doc/SKILL.md +93 -0
- package/skills/feishu-im-read/SKILL.md +163 -0
- package/skills/feishu-task/SKILL.md +293 -0
- package/skills/feishu-troubleshoot/SKILL.md +70 -0
- package/skills/feishu-update-doc/SKILL.md +285 -0
- package/src/card/builder.d.ts +106 -0
- package/src/card/builder.js +443 -0
- package/src/card/cardkit.d.ts +90 -0
- package/src/card/cardkit.js +181 -0
- package/src/card/flush-controller.d.ts +45 -0
- package/src/card/flush-controller.js +134 -0
- package/src/card/image-resolver.d.ts +45 -0
- package/src/card/image-resolver.js +112 -0
- package/src/card/markdown-style.d.ts +16 -0
- package/src/card/markdown-style.js +97 -0
- package/src/card/reply-dispatcher-types.d.ts +120 -0
- package/src/card/reply-dispatcher-types.js +57 -0
- package/src/card/reply-dispatcher.d.ts +15 -0
- package/src/card/reply-dispatcher.js +299 -0
- package/src/card/reply-mode.d.ts +38 -0
- package/src/card/reply-mode.js +65 -0
- package/src/card/streaming-card-controller.d.ts +101 -0
- package/src/card/streaming-card-controller.js +810 -0
- package/src/card/unavailable-guard.d.ts +35 -0
- package/src/card/unavailable-guard.js +83 -0
- package/src/channel/abort-detect.d.ts +34 -0
- package/src/channel/abort-detect.js +124 -0
- package/src/channel/chat-queue.d.ts +41 -0
- package/src/channel/chat-queue.js +58 -0
- package/src/channel/config-adapter.d.ts +23 -0
- package/src/channel/config-adapter.js +101 -0
- package/src/channel/directory.d.ts +57 -0
- package/src/channel/directory.js +191 -0
- package/src/channel/event-handlers.d.ts +15 -0
- package/src/channel/event-handlers.js +221 -0
- package/src/channel/monitor.d.ts +17 -0
- package/src/channel/monitor.js +129 -0
- package/src/channel/onboarding-config.d.ts +17 -0
- package/src/channel/onboarding-config.js +88 -0
- package/src/channel/onboarding-migrate.d.ts +25 -0
- package/src/channel/onboarding-migrate.js +67 -0
- package/src/channel/onboarding.d.ts +12 -0
- package/src/channel/onboarding.js +296 -0
- package/src/channel/plugin.d.ts +13 -0
- package/src/channel/plugin.js +278 -0
- package/src/channel/probe.d.ts +14 -0
- package/src/channel/probe.js +21 -0
- package/src/channel/types.d.ts +36 -0
- package/src/channel/types.js +7 -0
- package/src/commands/auth.d.ts +21 -0
- package/src/commands/auth.js +161 -0
- package/src/commands/diagnose.d.ts +69 -0
- package/src/commands/diagnose.js +807 -0
- package/src/commands/doctor.d.ts +26 -0
- package/src/commands/doctor.js +584 -0
- package/src/commands/index.d.ts +25 -0
- package/src/commands/index.js +212 -0
- package/src/commands/locale.d.ts +7 -0
- package/src/commands/locale.js +7 -0
- package/src/core/accounts.d.ts +37 -0
- package/src/core/accounts.js +163 -0
- package/src/core/agent-config.d.ts +100 -0
- package/src/core/agent-config.js +139 -0
- package/src/core/api-error.d.ts +48 -0
- package/src/core/api-error.js +112 -0
- package/src/core/app-owner-fallback.d.ts +21 -0
- package/src/core/app-owner-fallback.js +38 -0
- package/src/core/app-scope-checker.d.ts +87 -0
- package/src/core/app-scope-checker.js +190 -0
- package/src/core/auth-errors.d.ts +144 -0
- package/src/core/auth-errors.js +154 -0
- package/src/core/chat-info-cache.d.ts +57 -0
- package/src/core/chat-info-cache.js +152 -0
- package/src/core/config-schema.d.ts +448 -0
- package/src/core/config-schema.js +200 -0
- package/src/core/device-flow.d.ts +77 -0
- package/src/core/device-flow.js +212 -0
- package/src/core/domains.d.ts +18 -0
- package/src/core/domains.js +28 -0
- package/src/core/feishu-fetch.d.ts +18 -0
- package/src/core/feishu-fetch.js +25 -0
- package/src/core/footer-config.d.ts +24 -0
- package/src/core/footer-config.js +39 -0
- package/src/core/lark-client.d.ts +108 -0
- package/src/core/lark-client.js +353 -0
- package/src/core/lark-logger.d.ts +23 -0
- package/src/core/lark-logger.js +154 -0
- package/src/core/lark-ticket.d.ts +29 -0
- package/src/core/lark-ticket.js +35 -0
- package/src/core/message-unavailable.d.ts +53 -0
- package/src/core/message-unavailable.js +130 -0
- package/src/core/owner-policy.d.ts +31 -0
- package/src/core/owner-policy.js +52 -0
- package/src/core/permission-url.d.ts +22 -0
- package/src/core/permission-url.js +72 -0
- package/src/core/raw-request.d.ts +27 -0
- package/src/core/raw-request.js +62 -0
- package/src/core/scope-manager.d.ts +168 -0
- package/src/core/scope-manager.js +213 -0
- package/src/core/security-check.d.ts +72 -0
- package/src/core/security-check.js +174 -0
- package/src/core/shutdown-hooks.d.ts +22 -0
- package/src/core/shutdown-hooks.js +56 -0
- package/src/core/targets.d.ts +60 -0
- package/src/core/targets.js +164 -0
- package/src/core/token-store.d.ts +54 -0
- package/src/core/token-store.js +314 -0
- package/src/core/tool-client.d.ts +176 -0
- package/src/core/tool-client.js +380 -0
- package/src/core/tool-scopes.d.ts +153 -0
- package/src/core/tool-scopes.js +326 -0
- package/src/core/tools-config.d.ts +55 -0
- package/src/core/tools-config.js +137 -0
- package/src/core/types.d.ts +87 -0
- package/src/core/types.js +11 -0
- package/src/core/uat-client.d.ts +46 -0
- package/src/core/uat-client.js +187 -0
- package/src/core/version.d.ts +25 -0
- package/src/core/version.js +49 -0
- package/src/messaging/converters/audio.d.ts +8 -0
- package/src/messaging/converters/audio.js +21 -0
- package/src/messaging/converters/calendar.d.ts +13 -0
- package/src/messaging/converters/calendar.js +50 -0
- package/src/messaging/converters/content-converter.d.ts +41 -0
- package/src/messaging/converters/content-converter.js +106 -0
- package/src/messaging/converters/file.d.ts +8 -0
- package/src/messaging/converters/file.js +20 -0
- package/src/messaging/converters/folder.d.ts +8 -0
- package/src/messaging/converters/folder.js +20 -0
- package/src/messaging/converters/hongbao.d.ts +8 -0
- package/src/messaging/converters/hongbao.js +16 -0
- package/src/messaging/converters/image.d.ts +8 -0
- package/src/messaging/converters/image.js +18 -0
- package/src/messaging/converters/index.d.ts +8 -0
- package/src/messaging/converters/index.js +50 -0
- package/src/messaging/converters/interactive/card-converter.d.ts +76 -0
- package/src/messaging/converters/interactive/card-converter.js +1173 -0
- package/src/messaging/converters/interactive/card-utils.d.ts +9 -0
- package/src/messaging/converters/interactive/card-utils.js +42 -0
- package/src/messaging/converters/interactive/index.d.ts +8 -0
- package/src/messaging/converters/interactive/index.js +21 -0
- package/src/messaging/converters/interactive/legacy.d.ts +11 -0
- package/src/messaging/converters/interactive/legacy.js +57 -0
- package/src/messaging/converters/interactive/types.d.ts +23 -0
- package/src/messaging/converters/interactive/types.js +24 -0
- package/src/messaging/converters/location.d.ts +8 -0
- package/src/messaging/converters/location.js +19 -0
- package/src/messaging/converters/merge-forward.d.ts +32 -0
- package/src/messaging/converters/merge-forward.js +225 -0
- package/src/messaging/converters/post.d.ts +11 -0
- package/src/messaging/converters/post.js +135 -0
- package/src/messaging/converters/share.d.ts +9 -0
- package/src/messaging/converters/share.js +23 -0
- package/src/messaging/converters/sticker.d.ts +8 -0
- package/src/messaging/converters/sticker.js +18 -0
- package/src/messaging/converters/system.d.ts +12 -0
- package/src/messaging/converters/system.js +32 -0
- package/src/messaging/converters/text.d.ts +8 -0
- package/src/messaging/converters/text.js +14 -0
- package/src/messaging/converters/todo.d.ts +8 -0
- package/src/messaging/converters/todo.js +41 -0
- package/src/messaging/converters/types.d.ts +107 -0
- package/src/messaging/converters/types.js +7 -0
- package/src/messaging/converters/unknown.d.ts +8 -0
- package/src/messaging/converters/unknown.js +16 -0
- package/src/messaging/converters/utils.d.ts +22 -0
- package/src/messaging/converters/utils.js +51 -0
- package/src/messaging/converters/video-chat.d.ts +8 -0
- package/src/messaging/converters/video-chat.js +23 -0
- package/src/messaging/converters/video.d.ts +8 -0
- package/src/messaging/converters/video.js +32 -0
- package/src/messaging/converters/vote.d.ts +8 -0
- package/src/messaging/converters/vote.js +24 -0
- package/src/messaging/inbound/dedup.d.ts +59 -0
- package/src/messaging/inbound/dedup.js +116 -0
- package/src/messaging/inbound/dispatch-builders.d.ts +84 -0
- package/src/messaging/inbound/dispatch-builders.js +152 -0
- package/src/messaging/inbound/dispatch-commands.d.ts +27 -0
- package/src/messaging/inbound/dispatch-commands.js +112 -0
- package/src/messaging/inbound/dispatch-context.d.ts +67 -0
- package/src/messaging/inbound/dispatch-context.js +136 -0
- package/src/messaging/inbound/dispatch.d.ts +47 -0
- package/src/messaging/inbound/dispatch.js +264 -0
- package/src/messaging/inbound/enrich.d.ts +102 -0
- package/src/messaging/inbound/enrich.js +227 -0
- package/src/messaging/inbound/gate-effects.d.ts +23 -0
- package/src/messaging/inbound/gate-effects.js +43 -0
- package/src/messaging/inbound/gate.d.ts +60 -0
- package/src/messaging/inbound/gate.js +233 -0
- package/src/messaging/inbound/handler.d.ts +35 -0
- package/src/messaging/inbound/handler.js +173 -0
- package/src/messaging/inbound/media-resolver.d.ts +32 -0
- package/src/messaging/inbound/media-resolver.js +87 -0
- package/src/messaging/inbound/mention.d.ts +39 -0
- package/src/messaging/inbound/mention.js +81 -0
- package/src/messaging/inbound/parse-io.d.ts +50 -0
- package/src/messaging/inbound/parse-io.js +81 -0
- package/src/messaging/inbound/parse.d.ts +28 -0
- package/src/messaging/inbound/parse.js +106 -0
- package/src/messaging/inbound/permission.d.ts +17 -0
- package/src/messaging/inbound/permission.js +40 -0
- package/src/messaging/inbound/policy.d.ts +94 -0
- package/src/messaging/inbound/policy.js +160 -0
- package/src/messaging/inbound/reaction-handler.d.ts +61 -0
- package/src/messaging/inbound/reaction-handler.js +221 -0
- package/src/messaging/inbound/user-name-cache.d.ts +82 -0
- package/src/messaging/inbound/user-name-cache.js +241 -0
- package/src/messaging/outbound/actions.d.ts +16 -0
- package/src/messaging/outbound/actions.js +309 -0
- package/src/messaging/outbound/chat-manage.d.ts +64 -0
- package/src/messaging/outbound/chat-manage.js +111 -0
- package/src/messaging/outbound/deliver.d.ts +155 -0
- package/src/messaging/outbound/deliver.js +298 -0
- package/src/messaging/outbound/fetch.d.ts +12 -0
- package/src/messaging/outbound/fetch.js +12 -0
- package/src/messaging/outbound/forward.d.ts +26 -0
- package/src/messaging/outbound/forward.js +48 -0
- package/src/messaging/outbound/media-url-utils.d.ts +29 -0
- package/src/messaging/outbound/media-url-utils.js +130 -0
- package/src/messaging/outbound/media.d.ts +260 -0
- package/src/messaging/outbound/media.js +758 -0
- package/src/messaging/outbound/outbound.d.ts +89 -0
- package/src/messaging/outbound/outbound.js +121 -0
- package/src/messaging/outbound/reactions.d.ts +124 -0
- package/src/messaging/outbound/reactions.js +378 -0
- package/src/messaging/outbound/send.d.ts +152 -0
- package/src/messaging/outbound/send.js +355 -0
- package/src/messaging/outbound/typing.d.ts +71 -0
- package/src/messaging/outbound/typing.js +179 -0
- package/src/messaging/shared/message-lookup.d.ts +54 -0
- package/src/messaging/shared/message-lookup.js +117 -0
- package/src/messaging/types.d.ts +176 -0
- package/src/messaging/types.js +10 -0
- package/src/tools/auto-auth.d.ts +56 -0
- package/src/tools/auto-auth.js +919 -0
- package/src/tools/helpers.d.ts +260 -0
- package/src/tools/helpers.js +364 -0
- package/src/tools/mcp/doc/create.d.ts +12 -0
- package/src/tools/mcp/doc/create.js +44 -0
- package/src/tools/mcp/doc/fetch.d.ts +12 -0
- package/src/tools/mcp/doc/fetch.js +36 -0
- package/src/tools/mcp/doc/index.d.ts +12 -0
- package/src/tools/mcp/doc/index.js +41 -0
- package/src/tools/mcp/doc/update.d.ts +12 -0
- package/src/tools/mcp/doc/update.js +61 -0
- package/src/tools/mcp/shared.d.ts +59 -0
- package/src/tools/mcp/shared.js +226 -0
- package/src/tools/oapi/bitable/app-table-field.d.ts +16 -0
- package/src/tools/oapi/bitable/app-table-field.js +222 -0
- package/src/tools/oapi/bitable/app-table-record.d.ts +20 -0
- package/src/tools/oapi/bitable/app-table-record.js +436 -0
- package/src/tools/oapi/bitable/app-table-view.d.ts +17 -0
- package/src/tools/oapi/bitable/app-table-view.js +195 -0
- package/src/tools/oapi/bitable/app-table.d.ts +19 -0
- package/src/tools/oapi/bitable/app-table.js +247 -0
- package/src/tools/oapi/bitable/app.d.ts +18 -0
- package/src/tools/oapi/bitable/app.js +186 -0
- package/src/tools/oapi/bitable/index.d.ts +9 -0
- package/src/tools/oapi/bitable/index.js +9 -0
- package/src/tools/oapi/calendar/calendar.d.ts +15 -0
- package/src/tools/oapi/calendar/calendar.js +122 -0
- package/src/tools/oapi/calendar/event-attendee.d.ts +16 -0
- package/src/tools/oapi/calendar/event-attendee.js +263 -0
- package/src/tools/oapi/calendar/event.d.ts +16 -0
- package/src/tools/oapi/calendar/event.js +709 -0
- package/src/tools/oapi/calendar/freebusy.d.ts +13 -0
- package/src/tools/oapi/calendar/freebusy.js +111 -0
- package/src/tools/oapi/calendar/index.d.ts +8 -0
- package/src/tools/oapi/calendar/index.js +8 -0
- package/src/tools/oapi/chat/chat.d.ts +16 -0
- package/src/tools/oapi/chat/chat.js +124 -0
- package/src/tools/oapi/chat/index.d.ts +10 -0
- package/src/tools/oapi/chat/index.js +15 -0
- package/src/tools/oapi/chat/members.d.ts +11 -0
- package/src/tools/oapi/chat/members.js +81 -0
- package/src/tools/oapi/common/get-user.d.ts +12 -0
- package/src/tools/oapi/common/get-user.js +106 -0
- package/src/tools/oapi/common/index.d.ts +6 -0
- package/src/tools/oapi/common/index.js +6 -0
- package/src/tools/oapi/common/search-user.d.ts +11 -0
- package/src/tools/oapi/common/search-user.js +73 -0
- package/src/tools/oapi/drive/doc-comments.d.ts +15 -0
- package/src/tools/oapi/drive/doc-comments.js +279 -0
- package/src/tools/oapi/drive/doc-media.d.ts +19 -0
- package/src/tools/oapi/drive/doc-media.js +335 -0
- package/src/tools/oapi/drive/file.d.ts +19 -0
- package/src/tools/oapi/drive/file.js +483 -0
- package/src/tools/oapi/drive/index.d.ts +12 -0
- package/src/tools/oapi/drive/index.js +36 -0
- package/src/tools/oapi/helpers.d.ts +182 -0
- package/src/tools/oapi/helpers.js +354 -0
- package/src/tools/oapi/im/format-messages.d.ts +50 -0
- package/src/tools/oapi/im/format-messages.js +165 -0
- package/src/tools/oapi/im/index.d.ts +10 -0
- package/src/tools/oapi/im/index.js +17 -0
- package/src/tools/oapi/im/message-read.d.ts +13 -0
- package/src/tools/oapi/im/message-read.js +411 -0
- package/src/tools/oapi/im/message.d.ts +16 -0
- package/src/tools/oapi/im/message.js +149 -0
- package/src/tools/oapi/im/resource.d.ts +13 -0
- package/src/tools/oapi/im/resource.js +150 -0
- package/src/tools/oapi/im/time-utils.d.ts +46 -0
- package/src/tools/oapi/im/time-utils.js +201 -0
- package/src/tools/oapi/im/user-name-uat.d.ts +26 -0
- package/src/tools/oapi/im/user-name-uat.js +140 -0
- package/src/tools/oapi/index.d.ts +11 -0
- package/src/tools/oapi/index.js +58 -0
- package/src/tools/oapi/sdk-types.d.ts +96 -0
- package/src/tools/oapi/sdk-types.js +12 -0
- package/src/tools/oapi/search/doc-search.d.ts +13 -0
- package/src/tools/oapi/search/doc-search.js +191 -0
- package/src/tools/oapi/search/index.d.ts +12 -0
- package/src/tools/oapi/search/index.js +33 -0
- package/src/tools/oapi/sheets/index.d.ts +12 -0
- package/src/tools/oapi/sheets/index.js +31 -0
- package/src/tools/oapi/sheets/sheet.d.ts +16 -0
- package/src/tools/oapi/sheets/sheet.js +652 -0
- package/src/tools/oapi/task/comment.d.ts +15 -0
- package/src/tools/oapi/task/comment.js +140 -0
- package/src/tools/oapi/task/index.d.ts +8 -0
- package/src/tools/oapi/task/index.js +8 -0
- package/src/tools/oapi/task/subtask.d.ts +14 -0
- package/src/tools/oapi/task/subtask.js +162 -0
- package/src/tools/oapi/task/task.d.ts +16 -0
- package/src/tools/oapi/task/task.js +344 -0
- package/src/tools/oapi/task/tasklist.d.ts +21 -0
- package/src/tools/oapi/task/tasklist.js +321 -0
- package/src/tools/oapi/wiki/index.d.ts +12 -0
- package/src/tools/oapi/wiki/index.js +34 -0
- package/src/tools/oapi/wiki/space-node.d.ts +17 -0
- package/src/tools/oapi/wiki/space-node.js +230 -0
- package/src/tools/oapi/wiki/space.d.ts +15 -0
- package/src/tools/oapi/wiki/space.js +130 -0
- package/src/tools/oauth-batch-auth.d.ts +11 -0
- package/src/tools/oauth-batch-auth.js +142 -0
- package/src/tools/oauth-cards.d.ts +39 -0
- package/src/tools/oauth-cards.js +315 -0
- package/src/tools/oauth.d.ts +47 -0
- package/src/tools/oauth.js +620 -0
- package/src/tools/onboarding-auth.d.ts +27 -0
- package/src/tools/onboarding-auth.js +130 -0
- package/src/tools/tat/im/index.d.ts +15 -0
- package/src/tools/tat/im/index.js +18 -0
- package/src/tools/tat/im/resource.d.ts +15 -0
- package/src/tools/tat/im/resource.js +157 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Shared Lark API error handling utilities.
|
|
6
|
+
*
|
|
7
|
+
* Provides unified error handling for two distinct error paths:
|
|
8
|
+
*
|
|
9
|
+
* 1. **Response-level errors** — The SDK returns a response object with a
|
|
10
|
+
* non-zero `code`. Handled by {@link assertLarkOk}.
|
|
11
|
+
*
|
|
12
|
+
* 2. **Thrown exceptions** — The SDK throws an Axios-style error (HTTP 4xx)
|
|
13
|
+
* whose properties include the Feishu error `code` and `msg`.
|
|
14
|
+
* Handled by {@link formatLarkError}.
|
|
15
|
+
*
|
|
16
|
+
* Both paths intercept well-known codes (e.g. LARK_ERROR.APP_SCOPE_MISSING (99991672) — missing API scopes)
|
|
17
|
+
* and produce user-friendly messages with actionable authorization links.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* 从 Lark SDK 抛错对象中提取飞书 API code。
|
|
21
|
+
*
|
|
22
|
+
* 支持三种常见结构:
|
|
23
|
+
* - `{ code }` — SDK 直接挂载
|
|
24
|
+
* - `{ data: { code } }` — 响应体嵌套
|
|
25
|
+
* - `{ response: { data: { code } } }` — Axios 风格
|
|
26
|
+
*/
|
|
27
|
+
export declare function extractLarkApiCode(err: unknown): number | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Assert that a Lark SDK response is successful (code === 0).
|
|
30
|
+
*
|
|
31
|
+
* For permission errors (code LARK_ERROR.APP_SCOPE_MISSING (99991672)), the thrown error includes the
|
|
32
|
+
* required scope names and a direct authorization URL so the AI can
|
|
33
|
+
* present it to the end user.
|
|
34
|
+
*/
|
|
35
|
+
export declare function assertLarkOk(res: {
|
|
36
|
+
code?: number;
|
|
37
|
+
msg?: string;
|
|
38
|
+
}): void;
|
|
39
|
+
/**
|
|
40
|
+
* Extract a meaningful error message from a thrown Lark SDK / Axios error.
|
|
41
|
+
*
|
|
42
|
+
* The Lark SDK throws Axios errors whose object carries Feishu-specific
|
|
43
|
+
* fields (`code`, `msg`) alongside the standard `message`. For permission
|
|
44
|
+
* errors (LARK_ERROR.APP_SCOPE_MISSING (99991672)) we format a user-friendly string with scopes + auth URL.
|
|
45
|
+
* For all other errors we try `err.msg` first (the Feishu detail) and fall
|
|
46
|
+
* back to `err.message` (the generic Axios text).
|
|
47
|
+
*/
|
|
48
|
+
export declare function formatLarkError(err: unknown): string;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Shared Lark API error handling utilities.
|
|
6
|
+
*
|
|
7
|
+
* Provides unified error handling for two distinct error paths:
|
|
8
|
+
*
|
|
9
|
+
* 1. **Response-level errors** — The SDK returns a response object with a
|
|
10
|
+
* non-zero `code`. Handled by {@link assertLarkOk}.
|
|
11
|
+
*
|
|
12
|
+
* 2. **Thrown exceptions** — The SDK throws an Axios-style error (HTTP 4xx)
|
|
13
|
+
* whose properties include the Feishu error `code` and `msg`.
|
|
14
|
+
* Handled by {@link formatLarkError}.
|
|
15
|
+
*
|
|
16
|
+
* Both paths intercept well-known codes (e.g. LARK_ERROR.APP_SCOPE_MISSING (99991672) — missing API scopes)
|
|
17
|
+
* and produce user-friendly messages with actionable authorization links.
|
|
18
|
+
*/
|
|
19
|
+
import { extractPermissionGrantUrl, extractPermissionScopes } from './permission-url';
|
|
20
|
+
import { LARK_ERROR } from './auth-errors';
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Helpers
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
/**
|
|
25
|
+
* Given a Feishu error code and msg, format a user-friendly permission
|
|
26
|
+
* error string if the code is LARK_ERROR.APP_SCOPE_MISSING (99991672). Returns `null` for other codes.
|
|
27
|
+
*/
|
|
28
|
+
function formatPermissionError(code, msg) {
|
|
29
|
+
if (code !== LARK_ERROR.APP_SCOPE_MISSING)
|
|
30
|
+
return null;
|
|
31
|
+
const authUrl = extractPermissionGrantUrl(msg);
|
|
32
|
+
const scopes = extractPermissionScopes(msg);
|
|
33
|
+
return `权限不足:应用缺少 [${scopes}] 权限。\n` + `请管理员点击以下链接申请并开通权限:\n${authUrl}`;
|
|
34
|
+
}
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Code extraction
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
function coerceCode(value) {
|
|
39
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
if (typeof value === 'string') {
|
|
43
|
+
const parsed = Number(value);
|
|
44
|
+
if (Number.isFinite(parsed))
|
|
45
|
+
return parsed;
|
|
46
|
+
}
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 从 Lark SDK 抛错对象中提取飞书 API code。
|
|
51
|
+
*
|
|
52
|
+
* 支持三种常见结构:
|
|
53
|
+
* - `{ code }` — SDK 直接挂载
|
|
54
|
+
* - `{ data: { code } }` — 响应体嵌套
|
|
55
|
+
* - `{ response: { data: { code } } }` — Axios 风格
|
|
56
|
+
*/
|
|
57
|
+
export function extractLarkApiCode(err) {
|
|
58
|
+
if (!err || typeof err !== 'object')
|
|
59
|
+
return undefined;
|
|
60
|
+
const e = err;
|
|
61
|
+
return coerceCode(e.code) ?? coerceCode(e.data?.code) ?? coerceCode(e.response?.data?.code);
|
|
62
|
+
}
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Public API
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
/**
|
|
67
|
+
* Assert that a Lark SDK response is successful (code === 0).
|
|
68
|
+
*
|
|
69
|
+
* For permission errors (code LARK_ERROR.APP_SCOPE_MISSING (99991672)), the thrown error includes the
|
|
70
|
+
* required scope names and a direct authorization URL so the AI can
|
|
71
|
+
* present it to the end user.
|
|
72
|
+
*/
|
|
73
|
+
export function assertLarkOk(res) {
|
|
74
|
+
if (!res.code || res.code === 0)
|
|
75
|
+
return;
|
|
76
|
+
const permMsg = formatPermissionError(res.code, res.msg ?? '');
|
|
77
|
+
if (permMsg)
|
|
78
|
+
throw new Error(permMsg);
|
|
79
|
+
throw new Error(res.msg ?? `Feishu API error (code: ${res.code})`);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Extract a meaningful error message from a thrown Lark SDK / Axios error.
|
|
83
|
+
*
|
|
84
|
+
* The Lark SDK throws Axios errors whose object carries Feishu-specific
|
|
85
|
+
* fields (`code`, `msg`) alongside the standard `message`. For permission
|
|
86
|
+
* errors (LARK_ERROR.APP_SCOPE_MISSING (99991672)) we format a user-friendly string with scopes + auth URL.
|
|
87
|
+
* For all other errors we try `err.msg` first (the Feishu detail) and fall
|
|
88
|
+
* back to `err.message` (the generic Axios text).
|
|
89
|
+
*/
|
|
90
|
+
export function formatLarkError(err) {
|
|
91
|
+
if (!err || typeof err !== 'object') {
|
|
92
|
+
return String(err);
|
|
93
|
+
}
|
|
94
|
+
const e = err;
|
|
95
|
+
// Path 1: Lark SDK merges Feishu fields onto the thrown error object.
|
|
96
|
+
if (typeof e.code === 'number' && e.msg) {
|
|
97
|
+
const permMsg = formatPermissionError(e.code, e.msg);
|
|
98
|
+
if (permMsg)
|
|
99
|
+
return permMsg;
|
|
100
|
+
return e.msg;
|
|
101
|
+
}
|
|
102
|
+
// Path 2: Standard Axios error — dig into response.data.
|
|
103
|
+
const data = e.response?.data;
|
|
104
|
+
if (data && typeof data.code === 'number' && data.msg) {
|
|
105
|
+
const permMsg = formatPermissionError(data.code, data.msg);
|
|
106
|
+
if (permMsg)
|
|
107
|
+
return permMsg;
|
|
108
|
+
return data.msg;
|
|
109
|
+
}
|
|
110
|
+
// Fallback.
|
|
111
|
+
return e.message ?? String(err);
|
|
112
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* 应用所有者查询 — 复用 app-scope-checker 的 API 调用和统一 owner 定义。
|
|
6
|
+
*
|
|
7
|
+
* 所有 owner 判定统一使用 {@link getAppInfo} 返回的 `effectiveOwnerOpenId`。
|
|
8
|
+
* 不维护独立缓存,完全依赖 app-scope-checker 的 30s 缓存。
|
|
9
|
+
*/
|
|
10
|
+
import type { ConfiguredLarkAccount } from './types';
|
|
11
|
+
/**
|
|
12
|
+
* 获取应用的 effectiveOwnerOpenId。
|
|
13
|
+
*
|
|
14
|
+
* 复用 app-scope-checker 的 API 调用、缓存和统一 owner 定义(effectiveOwnerOpenId)。
|
|
15
|
+
* 查询失败时返回 undefined(fail-open)。
|
|
16
|
+
*
|
|
17
|
+
* @param account - 已配置的飞书账号信息
|
|
18
|
+
* @param sdk - 飞书 SDK 实例(必须已初始化 TAT)
|
|
19
|
+
* @returns 应用所有者的 open_id,如果查询失败则返回 undefined
|
|
20
|
+
*/
|
|
21
|
+
export declare function getAppOwnerFallback(account: ConfiguredLarkAccount, sdk: any): Promise<string | undefined>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* 应用所有者查询 — 复用 app-scope-checker 的 API 调用和统一 owner 定义。
|
|
6
|
+
*
|
|
7
|
+
* 所有 owner 判定统一使用 {@link getAppInfo} 返回的 `effectiveOwnerOpenId`。
|
|
8
|
+
* 不维护独立缓存,完全依赖 app-scope-checker 的 30s 缓存。
|
|
9
|
+
*/
|
|
10
|
+
import { getAppInfo } from './app-scope-checker';
|
|
11
|
+
import { larkLogger } from './lark-logger';
|
|
12
|
+
const log = larkLogger('core/app-owner-fallback');
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Public API
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
/**
|
|
17
|
+
* 获取应用的 effectiveOwnerOpenId。
|
|
18
|
+
*
|
|
19
|
+
* 复用 app-scope-checker 的 API 调用、缓存和统一 owner 定义(effectiveOwnerOpenId)。
|
|
20
|
+
* 查询失败时返回 undefined(fail-open)。
|
|
21
|
+
*
|
|
22
|
+
* @param account - 已配置的飞书账号信息
|
|
23
|
+
* @param sdk - 飞书 SDK 实例(必须已初始化 TAT)
|
|
24
|
+
* @returns 应用所有者的 open_id,如果查询失败则返回 undefined
|
|
25
|
+
*/
|
|
26
|
+
export async function getAppOwnerFallback(account,
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
28
|
+
sdk) {
|
|
29
|
+
const { appId } = account;
|
|
30
|
+
try {
|
|
31
|
+
const appInfo = await getAppInfo(sdk, appId);
|
|
32
|
+
return appInfo.effectiveOwnerOpenId;
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
log.warn(`failed to get owner for ${appId}: ${err instanceof Error ? err.message : err}`);
|
|
36
|
+
return undefined; // fail-open: 获取失败不阻塞业务
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* App Scope Checker — 查询应用已开通的 scope 列表。
|
|
6
|
+
*
|
|
7
|
+
* 通过 `GET /open-apis/application/v6/applications/:app_id` (TAT) 获取
|
|
8
|
+
* 应用信息,从 `app.scopes` 中提取已开通的 scope 字符串列表。
|
|
9
|
+
*
|
|
10
|
+
* 结果带 30 秒内存缓存,避免每次 invoke() 都调远程 API。
|
|
11
|
+
* scope 检查失败后可调 {@link invalidateAppScopeCache} 清缓存重查。
|
|
12
|
+
*/
|
|
13
|
+
import type * as Lark from '@larksuiteoapi/node-sdk';
|
|
14
|
+
export interface AppInfo {
|
|
15
|
+
appId: string;
|
|
16
|
+
creatorId?: string;
|
|
17
|
+
ownerOpenId?: string;
|
|
18
|
+
ownerType?: number;
|
|
19
|
+
/**
|
|
20
|
+
* 统一的 owner 判定结果。所有需要判定"谁是应用 owner"的场景都应使用此字段。
|
|
21
|
+
*
|
|
22
|
+
* 规则:owner_type=2(企业内成员)时取 owner_id,否则回退 creator_id。
|
|
23
|
+
* 兼容 owner.owner_type 和 owner.type 两种字段名。
|
|
24
|
+
*/
|
|
25
|
+
effectiveOwnerOpenId?: string;
|
|
26
|
+
scopes: Array<{
|
|
27
|
+
scope: string;
|
|
28
|
+
token_types?: string[];
|
|
29
|
+
}>;
|
|
30
|
+
}
|
|
31
|
+
/** 清除指定 appId 的缓存。 */
|
|
32
|
+
export declare function invalidateAppScopeCache(appId: string): void;
|
|
33
|
+
/**
|
|
34
|
+
* 获取应用已开通的 scope 列表。
|
|
35
|
+
*
|
|
36
|
+
* 需要应用自身有 `application:application:self_manage` 权限。
|
|
37
|
+
* `appId` 可传 `"me"` 查自己。
|
|
38
|
+
*
|
|
39
|
+
* @param sdk - Lark SDK 实例
|
|
40
|
+
* @param appId - 应用 ID
|
|
41
|
+
* @param tokenType - token 类型,用于过滤只支持特定 token 类型的 scope
|
|
42
|
+
* @returns scope 字符串数组,如 `["calendar:calendar", "task:task:write"]`
|
|
43
|
+
*/
|
|
44
|
+
export declare function getAppGrantedScopes(sdk: Lark.Client, appId: string, tokenType?: 'user' | 'tenant'): Promise<string[]>;
|
|
45
|
+
/**
|
|
46
|
+
* 获取应用信息,包括 owner 信息。
|
|
47
|
+
*
|
|
48
|
+
* 复用 getAppGrantedScopes 的 API 调用和缓存。
|
|
49
|
+
* 如果缓存中已有数据且未过期,直接从缓存提取。
|
|
50
|
+
*
|
|
51
|
+
* @param sdk - Lark SDK 实例
|
|
52
|
+
* @param appId - 应用 ID(可传 "me")
|
|
53
|
+
*/
|
|
54
|
+
export declare function getAppInfo(sdk: Lark.Client, appId: string): Promise<AppInfo>;
|
|
55
|
+
/**
|
|
56
|
+
* 计算 APP 已有 ∩ OAPI 需要 的交集。
|
|
57
|
+
*
|
|
58
|
+
* 用于传给 OAuth 的 scope 参数 — 只请求 APP 已开通且 API 需要的 scope。
|
|
59
|
+
*
|
|
60
|
+
* @param appGranted - 应用已开通的 scope 列表
|
|
61
|
+
* @param apiRequired - OAPI 要求的 scope 列表
|
|
62
|
+
* @returns 交集 scope 列表
|
|
63
|
+
*/
|
|
64
|
+
export declare function intersectScopes(appGranted: string[], apiRequired: string[]): string[];
|
|
65
|
+
/**
|
|
66
|
+
* 计算 OAPI 需要但 APP 未开通的 scope(差集)。
|
|
67
|
+
*
|
|
68
|
+
* 用于 AppScopeMissingError 的 missingScopes。
|
|
69
|
+
*
|
|
70
|
+
* @param appGranted - 应用已开通的 scope 列表
|
|
71
|
+
* @param apiRequired - OAPI 要求的 scope 列表
|
|
72
|
+
* @returns 缺失的 scope 列表
|
|
73
|
+
*/
|
|
74
|
+
export declare function missingScopes(appGranted: string[], apiRequired: string[]): string[];
|
|
75
|
+
/**
|
|
76
|
+
* 校验应用已开通的 scope 是否满足要求。
|
|
77
|
+
*
|
|
78
|
+
* 与 tool-client.ts invoke() 的 scope 校验逻辑完全一致,作为唯一真值来源:
|
|
79
|
+
* - `scopeNeedType === "all"`: appScopes 必须包含 requiredScopes 的全部项
|
|
80
|
+
* - 其他(默认 "one"): appScopes 与 requiredScopes 的交集非空即可
|
|
81
|
+
* - appScopes 为空: 视为满足(API 查询失败,退回服务端判断)
|
|
82
|
+
*
|
|
83
|
+
* @param appScopes - 应用已开通的 scope 列表(由 getAppGrantedScopes 返回)
|
|
84
|
+
* @param requiredScopes - 需要的 scope 列表
|
|
85
|
+
* @param scopeNeedType - "all" 表示全部必须,undefined/"one" 表示任一即可
|
|
86
|
+
*/
|
|
87
|
+
export declare function isAppScopeSatisfied(appScopes: string[], requiredScopes: string[], scopeNeedType?: 'one' | 'all'): boolean;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* App Scope Checker — 查询应用已开通的 scope 列表。
|
|
6
|
+
*
|
|
7
|
+
* 通过 `GET /open-apis/application/v6/applications/:app_id` (TAT) 获取
|
|
8
|
+
* 应用信息,从 `app.scopes` 中提取已开通的 scope 字符串列表。
|
|
9
|
+
*
|
|
10
|
+
* 结果带 30 秒内存缓存,避免每次 invoke() 都调远程 API。
|
|
11
|
+
* scope 检查失败后可调 {@link invalidateAppScopeCache} 清缓存重查。
|
|
12
|
+
*/
|
|
13
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
14
|
+
import { larkLogger } from './lark-logger';
|
|
15
|
+
const log = larkLogger('core/app-scope-checker');
|
|
16
|
+
import { AppScopeCheckFailedError } from './auth-errors';
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Cache
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
const cache = new Map();
|
|
21
|
+
const CACHE_TTL_MS = 30 * 1000; // 30 秒
|
|
22
|
+
/** 清除指定 appId 的缓存。 */
|
|
23
|
+
export function invalidateAppScopeCache(appId) {
|
|
24
|
+
cache.delete(appId);
|
|
25
|
+
}
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Fetch
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
/**
|
|
30
|
+
* 获取应用已开通的 scope 列表。
|
|
31
|
+
*
|
|
32
|
+
* 需要应用自身有 `application:application:self_manage` 权限。
|
|
33
|
+
* `appId` 可传 `"me"` 查自己。
|
|
34
|
+
*
|
|
35
|
+
* @param sdk - Lark SDK 实例
|
|
36
|
+
* @param appId - 应用 ID
|
|
37
|
+
* @param tokenType - token 类型,用于过滤只支持特定 token 类型的 scope
|
|
38
|
+
* @returns scope 字符串数组,如 `["calendar:calendar", "task:task:write"]`
|
|
39
|
+
*/
|
|
40
|
+
export async function getAppGrantedScopes(sdk, appId, tokenType) {
|
|
41
|
+
// 1. 检查缓存
|
|
42
|
+
const cached = cache.get(appId);
|
|
43
|
+
if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
|
|
44
|
+
// 从缓存中过滤出支持当前 token 类型的 scope
|
|
45
|
+
return cached.rawScopes
|
|
46
|
+
.filter((s) => {
|
|
47
|
+
if (tokenType && s.token_types && Array.isArray(s.token_types)) {
|
|
48
|
+
return s.token_types.includes(tokenType);
|
|
49
|
+
}
|
|
50
|
+
return true;
|
|
51
|
+
})
|
|
52
|
+
.map((s) => s.scope);
|
|
53
|
+
}
|
|
54
|
+
// 2. 调用 API
|
|
55
|
+
try {
|
|
56
|
+
const res = await sdk.request({
|
|
57
|
+
method: 'GET',
|
|
58
|
+
url: `/open-apis/application/v6/applications/${appId}`,
|
|
59
|
+
params: { lang: 'zh_cn' },
|
|
60
|
+
});
|
|
61
|
+
if (res.code !== 0) {
|
|
62
|
+
// 任何 API 错误都认为是应用缺少 application:application:self_manage 权限
|
|
63
|
+
throw new AppScopeCheckFailedError(appId);
|
|
64
|
+
}
|
|
65
|
+
// 响应结构: res.data.app.scopes → [{ scope: "xxx", description, level, token_types?: string[] }]
|
|
66
|
+
// 或者从 app_version 中获取 scopes
|
|
67
|
+
const app = res.data?.app ?? res.app ?? res.data;
|
|
68
|
+
const rawScopes = app?.scopes ?? app?.online_version?.scopes ?? [];
|
|
69
|
+
// 提取并验证 scope 字符串
|
|
70
|
+
const validScopes = rawScopes
|
|
71
|
+
.filter((s) => typeof s.scope === 'string' && s.scope.length > 0)
|
|
72
|
+
.map((s) => ({ scope: s.scope, token_types: s.token_types }));
|
|
73
|
+
// 3. 写缓存(缓存完整数据,包含 token_types 和原始 app 对象)
|
|
74
|
+
cache.set(appId, { rawScopes: validScopes, rawApp: app, fetchedAt: Date.now() });
|
|
75
|
+
log.info(`fetched ${validScopes.length} scopes for app ${appId}`);
|
|
76
|
+
// 4. 根据 tokenType 过滤
|
|
77
|
+
const scopes = validScopes
|
|
78
|
+
.filter((s) => {
|
|
79
|
+
if (tokenType && s.token_types && Array.isArray(s.token_types)) {
|
|
80
|
+
return s.token_types.includes(tokenType);
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
})
|
|
84
|
+
.map((s) => s.scope);
|
|
85
|
+
log.info(`returning ${scopes.length} scopes${tokenType ? ` for ${tokenType} token` : ''}`);
|
|
86
|
+
return scopes;
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
// 如果是 AppScopeCheckFailedError,重新抛出(不吞掉)
|
|
90
|
+
if (err instanceof AppScopeCheckFailedError) {
|
|
91
|
+
throw err;
|
|
92
|
+
}
|
|
93
|
+
// 检查是否是权限相关的 HTTP 错误(400/403)
|
|
94
|
+
// axios/SDK 异常对象通常包含 response.status 或 status 字段
|
|
95
|
+
const statusCode = err?.response?.status || err?.status || err?.statusCode;
|
|
96
|
+
const isPermissionError = statusCode === 400 ||
|
|
97
|
+
statusCode === 403 ||
|
|
98
|
+
(err instanceof Error && (err.message.includes('status code 400') || err.message.includes('status code 403')));
|
|
99
|
+
if (isPermissionError) {
|
|
100
|
+
throw new AppScopeCheckFailedError(appId);
|
|
101
|
+
}
|
|
102
|
+
log.warn(`failed to fetch scopes for ${appId}: ${err instanceof Error ? err.message : err}`);
|
|
103
|
+
// 其他查询失败不阻塞调用,返回空数组(后续 API 调用如果缺 scope 会被服务端拒绝)
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// App info
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
/**
|
|
111
|
+
* 获取应用信息,包括 owner 信息。
|
|
112
|
+
*
|
|
113
|
+
* 复用 getAppGrantedScopes 的 API 调用和缓存。
|
|
114
|
+
* 如果缓存中已有数据且未过期,直接从缓存提取。
|
|
115
|
+
*
|
|
116
|
+
* @param sdk - Lark SDK 实例
|
|
117
|
+
* @param appId - 应用 ID(可传 "me")
|
|
118
|
+
*/
|
|
119
|
+
export async function getAppInfo(sdk, appId) {
|
|
120
|
+
// 先确保缓存已填充(调一次 getAppGrantedScopes 来触发 API + 缓存)
|
|
121
|
+
await getAppGrantedScopes(sdk, appId);
|
|
122
|
+
const cached = cache.get(appId);
|
|
123
|
+
const rawApp = cached?.rawApp;
|
|
124
|
+
// 提取 owner 信息
|
|
125
|
+
const owner = rawApp?.owner;
|
|
126
|
+
const creatorId = rawApp?.creator_id;
|
|
127
|
+
// 统一 owner 定义:type=2(企业内成员)用 owner_id,否则回退 creator_id
|
|
128
|
+
// 兼容两种字段名(owner_type 和 type)
|
|
129
|
+
const ownerTypeValue = owner?.owner_type ?? owner?.type;
|
|
130
|
+
const effectiveOwnerOpenId = ownerTypeValue === 2 && owner?.owner_id ? owner.owner_id : (creatorId ?? owner?.owner_id);
|
|
131
|
+
return {
|
|
132
|
+
appId,
|
|
133
|
+
creatorId,
|
|
134
|
+
ownerOpenId: owner?.owner_id,
|
|
135
|
+
ownerType: owner?.owner_type,
|
|
136
|
+
effectiveOwnerOpenId,
|
|
137
|
+
scopes: cached?.rawScopes ?? [],
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// Scope intersection
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
/**
|
|
144
|
+
* 计算 APP 已有 ∩ OAPI 需要 的交集。
|
|
145
|
+
*
|
|
146
|
+
* 用于传给 OAuth 的 scope 参数 — 只请求 APP 已开通且 API 需要的 scope。
|
|
147
|
+
*
|
|
148
|
+
* @param appGranted - 应用已开通的 scope 列表
|
|
149
|
+
* @param apiRequired - OAPI 要求的 scope 列表
|
|
150
|
+
* @returns 交集 scope 列表
|
|
151
|
+
*/
|
|
152
|
+
export function intersectScopes(appGranted, apiRequired) {
|
|
153
|
+
const grantedSet = new Set(appGranted);
|
|
154
|
+
return apiRequired.filter((s) => grantedSet.has(s));
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* 计算 OAPI 需要但 APP 未开通的 scope(差集)。
|
|
158
|
+
*
|
|
159
|
+
* 用于 AppScopeMissingError 的 missingScopes。
|
|
160
|
+
*
|
|
161
|
+
* @param appGranted - 应用已开通的 scope 列表
|
|
162
|
+
* @param apiRequired - OAPI 要求的 scope 列表
|
|
163
|
+
* @returns 缺失的 scope 列表
|
|
164
|
+
*/
|
|
165
|
+
export function missingScopes(appGranted, apiRequired) {
|
|
166
|
+
const grantedSet = new Set(appGranted);
|
|
167
|
+
return apiRequired.filter((s) => !grantedSet.has(s));
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* 校验应用已开通的 scope 是否满足要求。
|
|
171
|
+
*
|
|
172
|
+
* 与 tool-client.ts invoke() 的 scope 校验逻辑完全一致,作为唯一真值来源:
|
|
173
|
+
* - `scopeNeedType === "all"`: appScopes 必须包含 requiredScopes 的全部项
|
|
174
|
+
* - 其他(默认 "one"): appScopes 与 requiredScopes 的交集非空即可
|
|
175
|
+
* - appScopes 为空: 视为满足(API 查询失败,退回服务端判断)
|
|
176
|
+
*
|
|
177
|
+
* @param appScopes - 应用已开通的 scope 列表(由 getAppGrantedScopes 返回)
|
|
178
|
+
* @param requiredScopes - 需要的 scope 列表
|
|
179
|
+
* @param scopeNeedType - "all" 表示全部必须,undefined/"one" 表示任一即可
|
|
180
|
+
*/
|
|
181
|
+
export function isAppScopeSatisfied(appScopes, requiredScopes, scopeNeedType) {
|
|
182
|
+
if (appScopes.length === 0)
|
|
183
|
+
return true; // API 查询失败 → 退回服务端判断
|
|
184
|
+
if (requiredScopes.length === 0)
|
|
185
|
+
return true;
|
|
186
|
+
if (scopeNeedType === 'all') {
|
|
187
|
+
return missingScopes(appScopes, requiredScopes).length === 0;
|
|
188
|
+
}
|
|
189
|
+
return intersectScopes(appScopes, requiredScopes).length > 0;
|
|
190
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* auth-errors.ts — 统一错误类型定义。
|
|
6
|
+
*
|
|
7
|
+
* 所有与认证/授权/scope 相关的错误类型集中在此文件,
|
|
8
|
+
* 解除 tool-client ↔ app-scope-checker 循环依赖。
|
|
9
|
+
*
|
|
10
|
+
* 其他模块应直接 import 此文件,或通过 tool-client / uat-client 的 re-export 使用。
|
|
11
|
+
*/
|
|
12
|
+
/** 飞书 OAPI 错误码常量,替代各处硬编码的 magic number。 */
|
|
13
|
+
export declare const LARK_ERROR: {
|
|
14
|
+
/** 应用 scope 不足(租户维度) */
|
|
15
|
+
readonly APP_SCOPE_MISSING: 99991672;
|
|
16
|
+
/** 用户 token scope 不足 */
|
|
17
|
+
readonly USER_SCOPE_INSUFFICIENT: 99991679;
|
|
18
|
+
/** access_token 无效 */
|
|
19
|
+
readonly TOKEN_INVALID: 99991668;
|
|
20
|
+
/** access_token 已过期 */
|
|
21
|
+
readonly TOKEN_EXPIRED: 99991677;
|
|
22
|
+
/** refresh_token 本身无效(格式非法或来自 v1 API) */
|
|
23
|
+
readonly REFRESH_TOKEN_INVALID: 20026;
|
|
24
|
+
/** refresh_token 已过期(超过 365 天) */
|
|
25
|
+
readonly REFRESH_TOKEN_EXPIRED: 20037;
|
|
26
|
+
/** refresh_token 已被吊销 */
|
|
27
|
+
readonly REFRESH_TOKEN_REVOKED: 20064;
|
|
28
|
+
/** refresh_token 已被使用(单次消费,rotation 场景) */
|
|
29
|
+
readonly REFRESH_TOKEN_ALREADY_USED: 20073;
|
|
30
|
+
/** refresh token 端点服务端内部错误,可重试 */
|
|
31
|
+
readonly REFRESH_SERVER_ERROR: 20050;
|
|
32
|
+
/** 消息已被撤回 */
|
|
33
|
+
readonly MESSAGE_RECALLED: 230011;
|
|
34
|
+
/** 消息已被删除 */
|
|
35
|
+
readonly MESSAGE_DELETED: 231003;
|
|
36
|
+
};
|
|
37
|
+
/** refresh token 端点可重试的错误码集合(服务端瞬时故障)。遇到后重试一次,仍失败则清 token。 */
|
|
38
|
+
export declare const REFRESH_TOKEN_RETRYABLE: ReadonlySet<number>;
|
|
39
|
+
/** 消息终止错误码集合(撤回/删除),遇到后应停止对该消息的后续操作。 */
|
|
40
|
+
export declare const MESSAGE_TERMINAL_CODES: ReadonlySet<number>;
|
|
41
|
+
/** access_token 失效相关的错误码集合,遇到后可尝试刷新重试。 */
|
|
42
|
+
export declare const TOKEN_RETRY_CODES: ReadonlySet<number>;
|
|
43
|
+
/** invoke() 错误共享的 scope 信息。 */
|
|
44
|
+
export interface ScopeErrorInfo {
|
|
45
|
+
apiName: string;
|
|
46
|
+
scopes: string[];
|
|
47
|
+
/** 应用 scope 是否已验证通过。false 表示 app scope 检查失败,scope 信息可能不准确。 */
|
|
48
|
+
appScopeVerified?: boolean;
|
|
49
|
+
/** 应用 ID,用于生成开放平台权限管理链接。 */
|
|
50
|
+
appId?: string;
|
|
51
|
+
}
|
|
52
|
+
/** OAuth 授权提示信息,与 handleInvokeError 返回的结构一致。 */
|
|
53
|
+
export interface AuthHint {
|
|
54
|
+
error: string;
|
|
55
|
+
api: string;
|
|
56
|
+
required_scope: string;
|
|
57
|
+
user_open_id: string;
|
|
58
|
+
message: string;
|
|
59
|
+
next_tool_call: {
|
|
60
|
+
tool: 'feishu_oauth';
|
|
61
|
+
params: {
|
|
62
|
+
action: 'authorize';
|
|
63
|
+
scope: string;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** tryInvoke 返回值的判别联合体。 */
|
|
68
|
+
export type TryInvokeResult<T> = {
|
|
69
|
+
ok: true;
|
|
70
|
+
data: T;
|
|
71
|
+
} | {
|
|
72
|
+
ok: false;
|
|
73
|
+
error: string;
|
|
74
|
+
authHint: AuthHint;
|
|
75
|
+
} | {
|
|
76
|
+
ok: false;
|
|
77
|
+
error: string;
|
|
78
|
+
authHint?: undefined;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Thrown when no valid UAT exists and the user needs to (re-)authorise.
|
|
82
|
+
* Callers should catch this and trigger the OAuth flow.
|
|
83
|
+
*/
|
|
84
|
+
export declare class NeedAuthorizationError extends Error {
|
|
85
|
+
readonly userOpenId: string;
|
|
86
|
+
constructor(userOpenId: string);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 应用缺少 application:application:self_manage 权限,无法查询应用权限配置。
|
|
90
|
+
*
|
|
91
|
+
* 需要管理员在飞书开放平台开通 application:application:self_manage 权限。
|
|
92
|
+
*/
|
|
93
|
+
export declare class AppScopeCheckFailedError extends Error {
|
|
94
|
+
/** 应用 ID,用于生成开放平台权限管理链接。 */
|
|
95
|
+
readonly appId?: string;
|
|
96
|
+
constructor(appId?: string);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 应用未开通 OAPI 所需 scope。
|
|
100
|
+
*
|
|
101
|
+
* 需要管理员在飞书开放平台开通权限。
|
|
102
|
+
*/
|
|
103
|
+
export declare class AppScopeMissingError extends Error {
|
|
104
|
+
readonly apiName: string;
|
|
105
|
+
/** OAPI 需要但 APP 未开通的 scope 列表。 */
|
|
106
|
+
readonly missingScopes: string[];
|
|
107
|
+
/** 工具的全部所需 scope(含已开通的),用于应用权限完成后一次性发起用户授权。 */
|
|
108
|
+
readonly allRequiredScopes?: string[];
|
|
109
|
+
/** 应用 ID,用于生成开放平台权限管理链接。 */
|
|
110
|
+
readonly appId?: string;
|
|
111
|
+
readonly scopeNeedType?: 'one' | 'all';
|
|
112
|
+
/** 触发此错误时使用的 token 类型,用于保持 card action 二次校验一致。 */
|
|
113
|
+
readonly tokenType?: 'user' | 'tenant';
|
|
114
|
+
constructor(info: ScopeErrorInfo, scopeNeedType?: 'one' | 'all', tokenType?: 'user' | 'tenant', allRequiredScopes?: string[]);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* 用户未授权或 scope 不足,需要发起 OAuth 授权。
|
|
118
|
+
*
|
|
119
|
+
* `requiredScopes` 为 APP∩OAPI 的有效 scope,可直接传给
|
|
120
|
+
* `feishu_oauth authorize --scope`。
|
|
121
|
+
*/
|
|
122
|
+
export declare class UserAuthRequiredError extends Error {
|
|
123
|
+
readonly userOpenId: string;
|
|
124
|
+
readonly apiName: string;
|
|
125
|
+
/** APP∩OAPI 交集 scope,传给 OAuth authorize。 */
|
|
126
|
+
readonly requiredScopes: string[];
|
|
127
|
+
/** 应用 scope 是否已验证通过。false 时 requiredScopes 可能不准确。 */
|
|
128
|
+
readonly appScopeVerified: boolean;
|
|
129
|
+
/** 应用 ID,用于生成开放平台权限管理链接。 */
|
|
130
|
+
readonly appId?: string;
|
|
131
|
+
constructor(userOpenId: string, info: ScopeErrorInfo);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 服务端报 99991679 — 用户 token 的 scope 不足。
|
|
135
|
+
*
|
|
136
|
+
* 需要增量授权:用缺失的 scope 发起新 Device Flow。
|
|
137
|
+
*/
|
|
138
|
+
export declare class UserScopeInsufficientError extends Error {
|
|
139
|
+
readonly userOpenId: string;
|
|
140
|
+
readonly apiName: string;
|
|
141
|
+
/** 缺失的 scope 列表。 */
|
|
142
|
+
readonly missingScopes: string[];
|
|
143
|
+
constructor(userOpenId: string, info: ScopeErrorInfo);
|
|
144
|
+
}
|