@larksuite/openclaw-lark 2025.7.15-beta.0
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 +76 -0
- package/bin/openclaw-lark.js +39 -0
- package/index.d.ts +36 -0
- package/index.js +188 -0
- package/openclaw.plugin.json +64 -0
- package/package.json +63 -0
- package/secret-contract-api.d.ts +15 -0
- package/secret-contract-api.js +78 -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 +242 -0
- package/skills/feishu-channel-rules/SKILL.md +18 -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 +340 -0
- package/skills/feishu-troubleshoot/SKILL.md +70 -0
- package/skills/feishu-update-doc/SKILL.md +285 -0
- package/src/card/builder.d.ts +152 -0
- package/src/card/builder.js +794 -0
- package/src/card/card-error.d.ts +91 -0
- package/src/card/card-error.js +206 -0
- package/src/card/cardkit.d.ts +90 -0
- package/src/card/cardkit.js +203 -0
- package/src/card/flush-controller.d.ts +45 -0
- package/src/card/flush-controller.js +138 -0
- package/src/card/image-resolver.d.ts +45 -0
- package/src/card/image-resolver.js +116 -0
- package/src/card/markdown-style.d.ts +16 -0
- package/src/card/markdown-style.js +106 -0
- package/src/card/reasoning-utils.d.ts +14 -0
- package/src/card/reasoning-utils.js +64 -0
- package/src/card/reply-dispatcher-types.d.ts +132 -0
- package/src/card/reply-dispatcher-types.js +61 -0
- package/src/card/reply-dispatcher.d.ts +15 -0
- package/src/card/reply-dispatcher.js +447 -0
- package/src/card/reply-mode.d.ts +41 -0
- package/src/card/reply-mode.js +70 -0
- package/src/card/streaming-card-controller.d.ts +118 -0
- package/src/card/streaming-card-controller.js +1045 -0
- package/src/card/tool-use-config.d.ts +26 -0
- package/src/card/tool-use-config.js +76 -0
- package/src/card/tool-use-display.d.ts +37 -0
- package/src/card/tool-use-display.js +476 -0
- package/src/card/tool-use-trace-store.d.ts +51 -0
- package/src/card/tool-use-trace-store.js +271 -0
- package/src/card/unavailable-guard.d.ts +35 -0
- package/src/card/unavailable-guard.js +87 -0
- package/src/channel/abort-detect.d.ts +47 -0
- package/src/channel/abort-detect.js +219 -0
- package/src/channel/chat-queue.d.ts +41 -0
- package/src/channel/chat-queue.js +68 -0
- package/src/channel/config-adapter.d.ts +23 -0
- package/src/channel/config-adapter.js +107 -0
- package/src/channel/directory.d.ts +57 -0
- package/src/channel/directory.js +197 -0
- package/src/channel/event-handlers.d.ts +17 -0
- package/src/channel/event-handlers.js +380 -0
- package/src/channel/interactive-dispatch.d.ts +59 -0
- package/src/channel/interactive-dispatch.js +195 -0
- package/src/channel/monitor.d.ts +17 -0
- package/src/channel/monitor.js +140 -0
- package/src/channel/onboarding-config.d.ts +18 -0
- package/src/channel/onboarding-config.js +96 -0
- package/src/channel/onboarding-migrate.d.ts +25 -0
- package/src/channel/onboarding-migrate.js +70 -0
- package/src/channel/onboarding.d.ts +12 -0
- package/src/channel/onboarding.js +300 -0
- package/src/channel/plugin.d.ts +13 -0
- package/src/channel/plugin.js +310 -0
- package/src/channel/probe.d.ts +14 -0
- package/src/channel/probe.js +24 -0
- package/src/channel/types.d.ts +37 -0
- package/src/channel/types.js +8 -0
- package/src/commands/auth.d.ts +21 -0
- package/src/commands/auth.js +165 -0
- package/src/commands/diagnose.d.ts +69 -0
- package/src/commands/diagnose.js +848 -0
- package/src/commands/doctor.d.ts +27 -0
- package/src/commands/doctor.js +588 -0
- package/src/commands/index.d.ts +25 -0
- package/src/commands/index.js +219 -0
- package/src/commands/locale.d.ts +7 -0
- package/src/commands/locale.js +8 -0
- package/src/core/accounts.d.ts +51 -0
- package/src/core/accounts.js +219 -0
- package/src/core/agent-config.d.ts +100 -0
- package/src/core/agent-config.js +148 -0
- package/src/core/api-error.d.ts +48 -0
- package/src/core/api-error.js +117 -0
- package/src/core/app-owner-fallback.d.ts +22 -0
- package/src/core/app-owner-fallback.js +39 -0
- package/src/core/app-scope-checker.d.ts +87 -0
- package/src/core/app-scope-checker.js +198 -0
- package/src/core/auth-errors.d.ts +144 -0
- package/src/core/auth-errors.js +160 -0
- package/src/core/card-action-operator.d.ts +33 -0
- package/src/core/card-action-operator.js +30 -0
- package/src/core/chat-info-cache.d.ts +67 -0
- package/src/core/chat-info-cache.js +165 -0
- package/src/core/comment-target.d.ts +65 -0
- package/src/core/comment-target.js +100 -0
- package/src/core/config-schema.d.ts +490 -0
- package/src/core/config-schema.js +223 -0
- package/src/core/device-flow.d.ts +77 -0
- package/src/core/device-flow.js +217 -0
- package/src/core/domains.d.ts +18 -0
- package/src/core/domains.js +34 -0
- package/src/core/feishu-fetch.d.ts +18 -0
- package/src/core/feishu-fetch.js +28 -0
- package/src/core/footer-config.d.ts +24 -0
- package/src/core/footer-config.js +51 -0
- package/src/core/lark-client.d.ts +125 -0
- package/src/core/lark-client.js +468 -0
- package/src/core/lark-logger.d.ts +23 -0
- package/src/core/lark-logger.js +160 -0
- package/src/core/lark-ticket.d.ts +29 -0
- package/src/core/lark-ticket.js +40 -0
- package/src/core/message-unavailable.d.ts +53 -0
- package/src/core/message-unavailable.js +142 -0
- package/src/core/owner-policy.d.ts +32 -0
- package/src/core/owner-policy.js +55 -0
- package/src/core/permission-url.d.ts +22 -0
- package/src/core/permission-url.js +76 -0
- package/src/core/raw-request.d.ts +27 -0
- package/src/core/raw-request.js +90 -0
- package/src/core/runtime-store.d.ts +13 -0
- package/src/core/runtime-store.js +29 -0
- package/src/core/scope-manager.d.ts +168 -0
- package/src/core/scope-manager.js +224 -0
- package/src/core/sdk-compat.d.ts +20 -0
- package/src/core/sdk-compat.js +28 -0
- package/src/core/security-check.d.ts +72 -0
- package/src/core/security-check.js +184 -0
- package/src/core/shutdown-hooks.d.ts +22 -0
- package/src/core/shutdown-hooks.js +61 -0
- package/src/core/synthetic-target.d.ts +33 -0
- package/src/core/synthetic-target.js +40 -0
- package/src/core/targets.d.ts +60 -0
- package/src/core/targets.js +173 -0
- package/src/core/token-store.d.ts +54 -0
- package/src/core/token-store.js +326 -0
- package/src/core/tool-client.d.ts +176 -0
- package/src/core/tool-client.js +426 -0
- package/src/core/tool-scopes.d.ts +156 -0
- package/src/core/tool-scopes.js +344 -0
- package/src/core/tools-config.d.ts +55 -0
- package/src/core/tools-config.js +143 -0
- package/src/core/types.d.ts +91 -0
- package/src/core/types.js +12 -0
- package/src/core/uat-client.d.ts +46 -0
- package/src/core/uat-client.js +192 -0
- package/src/core/version.d.ts +31 -0
- package/src/core/version.js +69 -0
- package/src/messaging/converters/audio.d.ts +8 -0
- package/src/messaging/converters/audio.js +24 -0
- package/src/messaging/converters/calendar.d.ts +13 -0
- package/src/messaging/converters/calendar.js +56 -0
- package/src/messaging/converters/content-converter-helpers.d.ts +30 -0
- package/src/messaging/converters/content-converter-helpers.js +82 -0
- package/src/messaging/converters/content-converter.d.ts +24 -0
- package/src/messaging/converters/content-converter.js +40 -0
- package/src/messaging/converters/file.d.ts +8 -0
- package/src/messaging/converters/file.js +24 -0
- package/src/messaging/converters/folder.d.ts +8 -0
- package/src/messaging/converters/folder.js +24 -0
- package/src/messaging/converters/hongbao.d.ts +8 -0
- package/src/messaging/converters/hongbao.js +20 -0
- package/src/messaging/converters/image.d.ts +8 -0
- package/src/messaging/converters/image.js +22 -0
- package/src/messaging/converters/index.d.ts +8 -0
- package/src/messaging/converters/index.js +53 -0
- package/src/messaging/converters/interactive/card-converter.d.ts +76 -0
- package/src/messaging/converters/interactive/card-converter.js +1177 -0
- package/src/messaging/converters/interactive/card-utils.d.ts +9 -0
- package/src/messaging/converters/interactive/card-utils.js +47 -0
- package/src/messaging/converters/interactive/index.d.ts +8 -0
- package/src/messaging/converters/interactive/index.js +25 -0
- package/src/messaging/converters/interactive/legacy.d.ts +11 -0
- package/src/messaging/converters/interactive/legacy.js +60 -0
- package/src/messaging/converters/interactive/types.d.ts +23 -0
- package/src/messaging/converters/interactive/types.js +27 -0
- package/src/messaging/converters/location.d.ts +8 -0
- package/src/messaging/converters/location.js +23 -0
- package/src/messaging/converters/merge-forward.d.ts +32 -0
- package/src/messaging/converters/merge-forward.js +235 -0
- package/src/messaging/converters/post.d.ts +11 -0
- package/src/messaging/converters/post.js +235 -0
- package/src/messaging/converters/share.d.ts +9 -0
- package/src/messaging/converters/share.js +28 -0
- package/src/messaging/converters/sticker.d.ts +8 -0
- package/src/messaging/converters/sticker.js +22 -0
- package/src/messaging/converters/system.d.ts +12 -0
- package/src/messaging/converters/system.js +36 -0
- package/src/messaging/converters/text.d.ts +8 -0
- package/src/messaging/converters/text.js +18 -0
- package/src/messaging/converters/todo.d.ts +8 -0
- package/src/messaging/converters/todo.js +45 -0
- package/src/messaging/converters/types.d.ts +114 -0
- package/src/messaging/converters/types.js +8 -0
- package/src/messaging/converters/unknown.d.ts +8 -0
- package/src/messaging/converters/unknown.js +20 -0
- package/src/messaging/converters/utils.d.ts +22 -0
- package/src/messaging/converters/utils.js +57 -0
- package/src/messaging/converters/video-chat.d.ts +8 -0
- package/src/messaging/converters/video-chat.js +31 -0
- package/src/messaging/converters/video.d.ts +8 -0
- package/src/messaging/converters/video.js +35 -0
- package/src/messaging/converters/vote.d.ts +8 -0
- package/src/messaging/converters/vote.js +28 -0
- package/src/messaging/inbound/bot-content.d.ts +84 -0
- package/src/messaging/inbound/bot-content.js +117 -0
- package/src/messaging/inbound/bot-loop-guard.d.ts +48 -0
- package/src/messaging/inbound/bot-loop-guard.js +89 -0
- package/src/messaging/inbound/comment-context.d.ts +82 -0
- package/src/messaging/inbound/comment-context.js +353 -0
- package/src/messaging/inbound/comment-handler.d.ts +30 -0
- package/src/messaging/inbound/comment-handler.js +269 -0
- package/src/messaging/inbound/dedup.d.ts +59 -0
- package/src/messaging/inbound/dedup.js +121 -0
- package/src/messaging/inbound/dispatch-builders.d.ts +104 -0
- package/src/messaging/inbound/dispatch-builders.js +241 -0
- package/src/messaging/inbound/dispatch-commands.d.ts +22 -0
- package/src/messaging/inbound/dispatch-commands.js +131 -0
- package/src/messaging/inbound/dispatch-context.d.ts +67 -0
- package/src/messaging/inbound/dispatch-context.js +153 -0
- package/src/messaging/inbound/dispatch.d.ts +50 -0
- package/src/messaging/inbound/dispatch.js +477 -0
- package/src/messaging/inbound/enrich.d.ts +102 -0
- package/src/messaging/inbound/enrich.js +236 -0
- package/src/messaging/inbound/gate-effects.d.ts +23 -0
- package/src/messaging/inbound/gate-effects.js +46 -0
- package/src/messaging/inbound/gate.d.ts +91 -0
- package/src/messaging/inbound/gate.js +342 -0
- package/src/messaging/inbound/handler-registry.d.ts +25 -0
- package/src/messaging/inbound/handler-registry.js +23 -0
- package/src/messaging/inbound/handler.d.ts +37 -0
- package/src/messaging/inbound/handler.js +286 -0
- package/src/messaging/inbound/media-resolver.d.ts +32 -0
- package/src/messaging/inbound/media-resolver.js +91 -0
- package/src/messaging/inbound/mention-registry.d.ts +59 -0
- package/src/messaging/inbound/mention-registry.js +115 -0
- package/src/messaging/inbound/mention.d.ts +48 -0
- package/src/messaging/inbound/mention.js +102 -0
- package/src/messaging/inbound/parse-io.d.ts +50 -0
- package/src/messaging/inbound/parse-io.js +86 -0
- package/src/messaging/inbound/parse.d.ts +28 -0
- package/src/messaging/inbound/parse.js +128 -0
- package/src/messaging/inbound/permission.d.ts +17 -0
- package/src/messaging/inbound/permission.js +44 -0
- package/src/messaging/inbound/policy.d.ts +95 -0
- package/src/messaging/inbound/policy.js +168 -0
- package/src/messaging/inbound/reaction-handler.d.ts +62 -0
- package/src/messaging/inbound/reaction-handler.js +259 -0
- package/src/messaging/inbound/sentinel-store.d.ts +37 -0
- package/src/messaging/inbound/sentinel-store.js +98 -0
- package/src/messaging/inbound/synthetic-message.d.ts +26 -0
- package/src/messaging/inbound/synthetic-message.js +59 -0
- package/src/messaging/inbound/user-name-cache-store.d.ts +62 -0
- package/src/messaging/inbound/user-name-cache-store.js +236 -0
- package/src/messaging/inbound/user-name-cache.d.ts +78 -0
- package/src/messaging/inbound/user-name-cache.js +306 -0
- package/src/messaging/inbound/vc-meeting-invited-handler.d.ts +20 -0
- package/src/messaging/inbound/vc-meeting-invited-handler.js +236 -0
- package/src/messaging/inbound/vc-sender.d.ts +41 -0
- package/src/messaging/inbound/vc-sender.js +53 -0
- package/src/messaging/outbound/actions.d.ts +16 -0
- package/src/messaging/outbound/actions.js +329 -0
- package/src/messaging/outbound/bot-peer-context.d.ts +42 -0
- package/src/messaging/outbound/bot-peer-context.js +40 -0
- package/src/messaging/outbound/chat-manage.d.ts +64 -0
- package/src/messaging/outbound/chat-manage.js +117 -0
- package/src/messaging/outbound/deliver.d.ts +186 -0
- package/src/messaging/outbound/deliver.js +410 -0
- package/src/messaging/outbound/fetch.d.ts +12 -0
- package/src/messaging/outbound/fetch.js +17 -0
- package/src/messaging/outbound/forward.d.ts +26 -0
- package/src/messaging/outbound/forward.js +51 -0
- package/src/messaging/outbound/media-url-utils.d.ts +29 -0
- package/src/messaging/outbound/media-url-utils.js +172 -0
- package/src/messaging/outbound/media.d.ts +260 -0
- package/src/messaging/outbound/media.js +865 -0
- package/src/messaging/outbound/normalize-mentions.d.ts +50 -0
- package/src/messaging/outbound/normalize-mentions.js +166 -0
- package/src/messaging/outbound/outbound-mention.d.ts +41 -0
- package/src/messaging/outbound/outbound-mention.js +112 -0
- package/src/messaging/outbound/outbound.d.ts +79 -0
- package/src/messaging/outbound/outbound.js +195 -0
- package/src/messaging/outbound/reactions.d.ts +124 -0
- package/src/messaging/outbound/reactions.js +384 -0
- package/src/messaging/outbound/send.d.ts +153 -0
- package/src/messaging/outbound/send.js +417 -0
- package/src/messaging/outbound/typing.d.ts +60 -0
- package/src/messaging/outbound/typing.js +139 -0
- package/src/messaging/shared/message-lookup.d.ts +54 -0
- package/src/messaging/shared/message-lookup.js +120 -0
- package/src/messaging/types.d.ts +316 -0
- package/src/messaging/types.js +11 -0
- package/src/tools/ask-user-question.d.ts +32 -0
- package/src/tools/ask-user-question.js +922 -0
- package/src/tools/auto-auth.d.ts +57 -0
- package/src/tools/auto-auth.js +904 -0
- package/src/tools/helpers.d.ts +264 -0
- package/src/tools/helpers.js +389 -0
- package/src/tools/mcp/doc/create.d.ts +12 -0
- package/src/tools/mcp/doc/create.js +47 -0
- package/src/tools/mcp/doc/fetch.d.ts +12 -0
- package/src/tools/mcp/doc/fetch.js +39 -0
- package/src/tools/mcp/doc/index.d.ts +12 -0
- package/src/tools/mcp/doc/index.js +50 -0
- package/src/tools/mcp/doc/update.d.ts +12 -0
- package/src/tools/mcp/doc/update.js +64 -0
- package/src/tools/mcp/shared.d.ts +59 -0
- package/src/tools/mcp/shared.js +239 -0
- package/src/tools/oapi/bitable/app-table-field.d.ts +16 -0
- package/src/tools/oapi/bitable/app-table-field.js +225 -0
- package/src/tools/oapi/bitable/app-table-record.d.ts +20 -0
- package/src/tools/oapi/bitable/app-table-record.js +439 -0
- package/src/tools/oapi/bitable/app-table-view.d.ts +16 -0
- package/src/tools/oapi/bitable/app-table-view.js +172 -0
- package/src/tools/oapi/bitable/app-table.d.ts +17 -0
- package/src/tools/oapi/bitable/app-table.js +195 -0
- package/src/tools/oapi/bitable/app.d.ts +18 -0
- package/src/tools/oapi/bitable/app.js +189 -0
- package/src/tools/oapi/bitable/index.d.ts +9 -0
- package/src/tools/oapi/bitable/index.js +17 -0
- package/src/tools/oapi/calendar/calendar.d.ts +15 -0
- package/src/tools/oapi/calendar/calendar.js +125 -0
- package/src/tools/oapi/calendar/event-attendee.d.ts +14 -0
- package/src/tools/oapi/calendar/event-attendee.js +161 -0
- package/src/tools/oapi/calendar/event.d.ts +16 -0
- package/src/tools/oapi/calendar/event.js +712 -0
- package/src/tools/oapi/calendar/freebusy.d.ts +13 -0
- package/src/tools/oapi/calendar/freebusy.js +114 -0
- package/src/tools/oapi/calendar/index.d.ts +8 -0
- package/src/tools/oapi/calendar/index.js +15 -0
- package/src/tools/oapi/chat/chat.d.ts +16 -0
- package/src/tools/oapi/chat/chat.js +127 -0
- package/src/tools/oapi/chat/index.d.ts +10 -0
- package/src/tools/oapi/chat/index.js +23 -0
- package/src/tools/oapi/chat/members.d.ts +11 -0
- package/src/tools/oapi/chat/members.js +84 -0
- package/src/tools/oapi/common/get-user.d.ts +12 -0
- package/src/tools/oapi/common/get-user.js +109 -0
- package/src/tools/oapi/common/index.d.ts +6 -0
- package/src/tools/oapi/common/index.js +11 -0
- package/src/tools/oapi/common/search-user.d.ts +11 -0
- package/src/tools/oapi/common/search-user.js +76 -0
- package/src/tools/oapi/drive/doc-comments.d.ts +15 -0
- package/src/tools/oapi/drive/doc-comments.js +351 -0
- package/src/tools/oapi/drive/doc-media.d.ts +19 -0
- package/src/tools/oapi/drive/doc-media.js +371 -0
- package/src/tools/oapi/drive/file.d.ts +19 -0
- package/src/tools/oapi/drive/file.js +519 -0
- package/src/tools/oapi/drive/index.d.ts +12 -0
- package/src/tools/oapi/drive/index.js +45 -0
- package/src/tools/oapi/helpers.d.ts +183 -0
- package/src/tools/oapi/helpers.js +385 -0
- package/src/tools/oapi/im/format-messages.d.ts +50 -0
- package/src/tools/oapi/im/format-messages.js +169 -0
- package/src/tools/oapi/im/index.d.ts +10 -0
- package/src/tools/oapi/im/index.js +25 -0
- package/src/tools/oapi/im/message-read.d.ts +13 -0
- package/src/tools/oapi/im/message-read.js +419 -0
- package/src/tools/oapi/im/message.d.ts +16 -0
- package/src/tools/oapi/im/message.js +152 -0
- package/src/tools/oapi/im/resource.d.ts +13 -0
- package/src/tools/oapi/im/resource.js +186 -0
- package/src/tools/oapi/im/time-utils.d.ts +46 -0
- package/src/tools/oapi/im/time-utils.js +212 -0
- package/src/tools/oapi/im/user-name-uat.d.ts +26 -0
- package/src/tools/oapi/im/user-name-uat.js +143 -0
- package/src/tools/oapi/index.d.ts +11 -0
- package/src/tools/oapi/index.js +64 -0
- package/src/tools/oapi/sdk-types.d.ts +96 -0
- package/src/tools/oapi/sdk-types.js +13 -0
- package/src/tools/oapi/search/doc-search.d.ts +13 -0
- package/src/tools/oapi/search/doc-search.js +194 -0
- package/src/tools/oapi/search/index.d.ts +12 -0
- package/src/tools/oapi/search/index.js +37 -0
- package/src/tools/oapi/sheets/index.d.ts +12 -0
- package/src/tools/oapi/sheets/index.js +35 -0
- package/src/tools/oapi/sheets/sheet.d.ts +16 -0
- package/src/tools/oapi/sheets/sheet.js +688 -0
- package/src/tools/oapi/task/attachment.d.ts +18 -0
- package/src/tools/oapi/task/attachment.js +107 -0
- package/src/tools/oapi/task/comment.d.ts +15 -0
- package/src/tools/oapi/task/comment.js +149 -0
- package/src/tools/oapi/task/index.d.ts +11 -0
- package/src/tools/oapi/task/index.js +21 -0
- package/src/tools/oapi/task/section.d.ts +17 -0
- package/src/tools/oapi/task/section.js +293 -0
- package/src/tools/oapi/task/subtask.d.ts +14 -0
- package/src/tools/oapi/task/subtask.js +171 -0
- package/src/tools/oapi/task/task.d.ts +19 -0
- package/src/tools/oapi/task/task.js +512 -0
- package/src/tools/oapi/task/task_agent.d.ts +14 -0
- package/src/tools/oapi/task/task_agent.js +108 -0
- package/src/tools/oapi/task/tasklist.d.ts +19 -0
- package/src/tools/oapi/task/tasklist.js +276 -0
- package/src/tools/oapi/wiki/index.d.ts +12 -0
- package/src/tools/oapi/wiki/index.js +42 -0
- package/src/tools/oapi/wiki/space-node.d.ts +17 -0
- package/src/tools/oapi/wiki/space-node.js +233 -0
- package/src/tools/oapi/wiki/space.d.ts +15 -0
- package/src/tools/oapi/wiki/space.js +133 -0
- package/src/tools/oauth-batch-auth.d.ts +11 -0
- package/src/tools/oauth-batch-auth.js +150 -0
- package/src/tools/oauth-cards.d.ts +39 -0
- package/src/tools/oauth-cards.js +324 -0
- package/src/tools/oauth.d.ts +47 -0
- package/src/tools/oauth.js +592 -0
- package/src/tools/onboarding-auth.d.ts +27 -0
- package/src/tools/onboarding-auth.js +133 -0
- package/src/tools/tat/im/index.d.ts +15 -0
- package/src/tools/tat/im/index.js +22 -0
- package/src/tools/tat/im/resource.d.ts +15 -0
- package/src/tools/tat/im/resource.js +192 -0
- package/tsdown.config.d.ts +2 -0
- package/tsdown.config.js +25 -0
- package/vitest.config.d.ts +2 -0
- package/vitest.config.js +12 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* feishu_oauth tool — User OAuth authorisation management.
|
|
6
|
+
*
|
|
7
|
+
* Actions:
|
|
8
|
+
* - authorize : Initiate Device Flow, send auth card, poll for token.
|
|
9
|
+
* - status : Check whether the current user has a valid UAT.
|
|
10
|
+
* - revoke : Remove the current user's stored UAT.
|
|
11
|
+
*
|
|
12
|
+
* Security:
|
|
13
|
+
* - **Does not** accept a `user_open_id` parameter. The target user is
|
|
14
|
+
* always the message sender, obtained from the LarkTicket.
|
|
15
|
+
* - Token values are never included in the return payload (AI cannot see
|
|
16
|
+
* them).
|
|
17
|
+
*/
|
|
18
|
+
import type { ClawdbotConfig, OpenClawPluginApi } from 'openclaw/plugin-sdk';
|
|
19
|
+
import type { ConfiguredLarkAccount } from '../core/types';
|
|
20
|
+
import type { LarkTicket } from '../core/lark-ticket';
|
|
21
|
+
export declare function registerFeishuOAuthTool(api: OpenClawPluginApi): void;
|
|
22
|
+
export interface ExecuteAuthorizeParams {
|
|
23
|
+
account: ConfiguredLarkAccount;
|
|
24
|
+
senderOpenId: string;
|
|
25
|
+
scope: string;
|
|
26
|
+
isBatchAuth?: boolean;
|
|
27
|
+
totalAppScopes?: number;
|
|
28
|
+
alreadyGranted?: number;
|
|
29
|
+
batchInfo?: string;
|
|
30
|
+
skipSyntheticMessage?: boolean;
|
|
31
|
+
showBatchAuthHint?: boolean;
|
|
32
|
+
forceAuth?: boolean;
|
|
33
|
+
onAuthComplete?: () => void | Promise<void>;
|
|
34
|
+
cfg: ClawdbotConfig;
|
|
35
|
+
ticket: LarkTicket | undefined;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 执行 OAuth 授权流程(Device Flow)
|
|
39
|
+
* 可被 feishu_oauth 和 feishu_oauth_batch_auth 共享调用
|
|
40
|
+
*/
|
|
41
|
+
export declare function executeAuthorize(params: ExecuteAuthorizeParams): Promise<{
|
|
42
|
+
content: Array<{
|
|
43
|
+
type: 'text';
|
|
44
|
+
text: string;
|
|
45
|
+
}>;
|
|
46
|
+
details: unknown;
|
|
47
|
+
}>;
|
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*
|
|
6
|
+
* feishu_oauth tool — User OAuth authorisation management.
|
|
7
|
+
*
|
|
8
|
+
* Actions:
|
|
9
|
+
* - authorize : Initiate Device Flow, send auth card, poll for token.
|
|
10
|
+
* - status : Check whether the current user has a valid UAT.
|
|
11
|
+
* - revoke : Remove the current user's stored UAT.
|
|
12
|
+
*
|
|
13
|
+
* Security:
|
|
14
|
+
* - **Does not** accept a `user_open_id` parameter. The target user is
|
|
15
|
+
* always the message sender, obtained from the LarkTicket.
|
|
16
|
+
* - Token values are never included in the return payload (AI cannot see
|
|
17
|
+
* them).
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.registerFeishuOAuthTool = registerFeishuOAuthTool;
|
|
21
|
+
exports.executeAuthorize = executeAuthorize;
|
|
22
|
+
const typebox_1 = require("@sinclair/typebox");
|
|
23
|
+
const accounts_1 = require("../core/accounts.js");
|
|
24
|
+
const owner_policy_1 = require("../core/owner-policy.js");
|
|
25
|
+
const lark_client_1 = require("../core/lark-client.js");
|
|
26
|
+
const app_scope_checker_1 = require("../core/app-scope-checker.js");
|
|
27
|
+
const lark_ticket_1 = require("../core/lark-ticket.js");
|
|
28
|
+
const lark_logger_1 = require("../core/lark-logger.js");
|
|
29
|
+
const log = (0, lark_logger_1.larkLogger)('tools/oauth');
|
|
30
|
+
const api_error_1 = require("../core/api-error.js");
|
|
31
|
+
const device_flow_1 = require("../core/device-flow.js");
|
|
32
|
+
const token_store_1 = require("../core/token-store.js");
|
|
33
|
+
const uat_client_1 = require("../core/uat-client.js");
|
|
34
|
+
const cardkit_1 = require("../card/cardkit.js");
|
|
35
|
+
const synthetic_message_1 = require("../messaging/inbound/synthetic-message.js");
|
|
36
|
+
const oauth_cards_1 = require("./oauth-cards.js");
|
|
37
|
+
const helpers_1 = require("./helpers.js");
|
|
38
|
+
const json = helpers_1.formatToolResult;
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Schema
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
const FeishuOAuthSchema = typebox_1.Type.Object({
|
|
43
|
+
action: typebox_1.Type.Union([
|
|
44
|
+
// Type.Literal("authorize"), // 已由 auto-auth 自动处理,不再对外暴露
|
|
45
|
+
typebox_1.Type.Literal('revoke'),
|
|
46
|
+
], {
|
|
47
|
+
description: 'revoke: 撤销当前用户已保存的授权凭据',
|
|
48
|
+
}),
|
|
49
|
+
}, {
|
|
50
|
+
description: '飞书用户撤销授权工具。' +
|
|
51
|
+
'仅在用户明确说"撤销授权"、"取消授权"、"退出登录"、"清除授权"时调用。' +
|
|
52
|
+
'【严禁调用场景】用户说"重新授权"、"发起授权"、"重新发起"、"授权失败"、"授权过期"时,绝对不要调用此工具,授权流程由系统自动处理。',
|
|
53
|
+
});
|
|
54
|
+
const pendingFlows = new Map();
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Identity verification after Device Flow
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
/**
|
|
59
|
+
* 使用刚获取的 UAT 调用 /authen/v1/user_info,
|
|
60
|
+
* 验证实际完成 OAuth 授权的用户 open_id 是否与预期的 senderOpenId 一致。
|
|
61
|
+
*
|
|
62
|
+
* 防止群聊中其他用户点击授权链接后,错误的 UAT 被绑定到 owner 的身份。
|
|
63
|
+
*/
|
|
64
|
+
async function verifyTokenIdentity(brand, accessToken, expectedOpenId) {
|
|
65
|
+
const domain = brand === 'lark' ? 'https://open.larksuite.com' : 'https://open.feishu.cn';
|
|
66
|
+
const url = `${domain}/open-apis/authen/v1/user_info`;
|
|
67
|
+
try {
|
|
68
|
+
const res = await fetch(url, {
|
|
69
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
70
|
+
});
|
|
71
|
+
const data = (await res.json());
|
|
72
|
+
if (data.code !== 0) {
|
|
73
|
+
log.warn(`user_info API error: code=${data.code}, msg=${data.msg}`);
|
|
74
|
+
return { valid: false };
|
|
75
|
+
}
|
|
76
|
+
const actualOpenId = data.data?.open_id;
|
|
77
|
+
if (!actualOpenId) {
|
|
78
|
+
log.warn('user_info API returned no open_id');
|
|
79
|
+
return { valid: false };
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
valid: actualOpenId === expectedOpenId,
|
|
83
|
+
actualOpenId,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
log.warn(`identity verification request failed: ${err}`);
|
|
88
|
+
return { valid: false };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Registration
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
function registerFeishuOAuthTool(api) {
|
|
95
|
+
if (!api.config)
|
|
96
|
+
return;
|
|
97
|
+
const cfg = api.config;
|
|
98
|
+
(0, helpers_1.registerTool)(api, {
|
|
99
|
+
name: 'feishu_oauth',
|
|
100
|
+
label: 'Feishu OAuth',
|
|
101
|
+
description: '飞书用户撤销授权工具。' +
|
|
102
|
+
'仅在用户明确说"撤销授权"、"取消授权"、"退出登录"、"清除授权"时调用 revoke。' +
|
|
103
|
+
'【严禁调用场景】用户说"重新授权"、"发起授权"、"重新发起"、"授权失败"、"授权过期"时,绝对不要调用此工具,授权流程由系统自动处理,无需人工干预。' +
|
|
104
|
+
'不需要传入 user_open_id,系统自动从消息上下文获取当前用户。',
|
|
105
|
+
parameters: FeishuOAuthSchema,
|
|
106
|
+
async execute(_toolCallId, params) {
|
|
107
|
+
const p = params;
|
|
108
|
+
// Resolve identity from trace context (set in monitor.ts).
|
|
109
|
+
const ticket = (0, lark_ticket_1.getTicket)();
|
|
110
|
+
const senderOpenId = ticket?.senderOpenId;
|
|
111
|
+
if (!senderOpenId) {
|
|
112
|
+
return json({
|
|
113
|
+
error: '无法获取当前用户身份(senderOpenId),请在飞书对话中使用此工具。',
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
// Use the accountId from LarkTicket to resolve the correct account
|
|
117
|
+
// (important for multi-account setups like prod + boe).
|
|
118
|
+
const acct = (0, accounts_1.getLarkAccount)(cfg, ticket.accountId);
|
|
119
|
+
if (!acct.configured) {
|
|
120
|
+
return json({
|
|
121
|
+
error: `账号 ${ticket.accountId} 缺少 appId 或 appSecret 配置`,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
const account = acct; // Now we know it's ConfiguredLarkAccount
|
|
125
|
+
try {
|
|
126
|
+
switch (p.action) {
|
|
127
|
+
// ---------------------------------------------------------------
|
|
128
|
+
// AUTHORIZE — 已由 auto-auth 自动处理,此分支不再对外暴露
|
|
129
|
+
// ---------------------------------------------------------------
|
|
130
|
+
// case "authorize": {
|
|
131
|
+
// return await executeAuthorize({
|
|
132
|
+
// account,
|
|
133
|
+
// senderOpenId,
|
|
134
|
+
// scope: p.scope || "",
|
|
135
|
+
// isBatchAuth: false,
|
|
136
|
+
// cfg,
|
|
137
|
+
// ticket,
|
|
138
|
+
// });
|
|
139
|
+
// }
|
|
140
|
+
// ---------------------------------------------------------------
|
|
141
|
+
// STATUS
|
|
142
|
+
// ---------------------------------------------------------------
|
|
143
|
+
// case "status": {
|
|
144
|
+
// const status = await getUATStatus(account.appId, senderOpenId);
|
|
145
|
+
// return json({
|
|
146
|
+
// authorized: status.authorized,
|
|
147
|
+
// scope: status.scope,
|
|
148
|
+
// token_status: status.tokenStatus,
|
|
149
|
+
// granted_at: status.grantedAt
|
|
150
|
+
// ? new Date(status.grantedAt).toISOString()
|
|
151
|
+
// : undefined,
|
|
152
|
+
// expires_at: status.expiresAt
|
|
153
|
+
// ? new Date(status.expiresAt).toISOString()
|
|
154
|
+
// : undefined,
|
|
155
|
+
// });
|
|
156
|
+
// }
|
|
157
|
+
// ---------------------------------------------------------------
|
|
158
|
+
// REVOKE
|
|
159
|
+
// ---------------------------------------------------------------
|
|
160
|
+
case 'revoke': {
|
|
161
|
+
await (0, uat_client_1.revokeUAT)(account.appId, senderOpenId);
|
|
162
|
+
return json({ success: true, message: '用户授权已撤销。' });
|
|
163
|
+
}
|
|
164
|
+
default:
|
|
165
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
166
|
+
return json({ error: `未知操作: ${p.action}` });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
log.error(`${p.action} failed: ${err}`);
|
|
171
|
+
return json({ error: (0, api_error_1.formatLarkError)(err) });
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
}, { name: 'feishu_oauth' });
|
|
175
|
+
api.logger.debug?.('feishu_oauth: Registered feishu_oauth tool');
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* 执行 OAuth 授权流程(Device Flow)
|
|
179
|
+
* 可被 feishu_oauth 和 feishu_oauth_batch_auth 共享调用
|
|
180
|
+
*/
|
|
181
|
+
async function executeAuthorize(params) {
|
|
182
|
+
const { account, senderOpenId, scope, isBatchAuth, totalAppScopes, alreadyGranted, batchInfo, skipSyntheticMessage, showBatchAuthHint, forceAuth, onAuthComplete, cfg, ticket, } = params;
|
|
183
|
+
const { appId, appSecret, brand, accountId } = account;
|
|
184
|
+
// 0. Check if the user is the app owner (fail-close: 安全优先).
|
|
185
|
+
const sdk = lark_client_1.LarkClient.fromAccount(account).sdk;
|
|
186
|
+
try {
|
|
187
|
+
await (0, owner_policy_1.assertOwnerAccessStrict)(account, sdk, senderOpenId);
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
if (err instanceof owner_policy_1.OwnerAccessDeniedError) {
|
|
191
|
+
log.warn(`non-owner user ${senderOpenId} attempted to authorize`);
|
|
192
|
+
return json({
|
|
193
|
+
error: 'permission_denied',
|
|
194
|
+
message: '当前应用仅限所有者(App Owner)使用。您没有权限发起授权,无法使用相关功能。',
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
throw err;
|
|
198
|
+
}
|
|
199
|
+
// effectiveScope:可变 scope 变量,后续可能因 pendingFlow 合并而扩大
|
|
200
|
+
let effectiveScope = scope;
|
|
201
|
+
// 1. Check if user already authorised + scope coverage.
|
|
202
|
+
// forceAuth=true 时跳过缓存检查,直接发起新 Device Flow。
|
|
203
|
+
// 用于 AppScopeMissing 场景:应用权限刚被移除再补回,本地 UAT 缓存的 scope 状态不可信。
|
|
204
|
+
const existing = forceAuth ? null : await (0, token_store_1.getStoredToken)(appId, senderOpenId);
|
|
205
|
+
if (existing && (0, token_store_1.tokenStatus)(existing) !== 'expired') {
|
|
206
|
+
// 如果请求了特定 scope,检查是否已覆盖
|
|
207
|
+
if (effectiveScope) {
|
|
208
|
+
const requestedScopes = effectiveScope.split(/\s+/).filter(Boolean);
|
|
209
|
+
const grantedScopes = new Set((existing.scope ?? '').split(/\s+/).filter(Boolean));
|
|
210
|
+
const missingScopes = requestedScopes.filter((s) => !grantedScopes.has(s));
|
|
211
|
+
if (missingScopes.length > 0) {
|
|
212
|
+
// scope 不足 → 继续走 Device Flow(飞书 OAuth 是增量授权)
|
|
213
|
+
log.info(`existing token missing scopes [${missingScopes.join(', ')}], starting incremental auth`);
|
|
214
|
+
// 不 revoke 旧 token,直接用缺失的 scope 发起新 Device Flow
|
|
215
|
+
// 飞书会累积授权,新 token 包含旧 + 新 scope
|
|
216
|
+
// 继续执行下面的 Device Flow 逻辑
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
if (onAuthComplete) {
|
|
220
|
+
try {
|
|
221
|
+
await onAuthComplete();
|
|
222
|
+
}
|
|
223
|
+
catch (e) {
|
|
224
|
+
log.warn(`onAuthComplete failed: ${e}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return json({
|
|
228
|
+
success: true,
|
|
229
|
+
message: '用户已授权,scope 已覆盖。',
|
|
230
|
+
authorized: true,
|
|
231
|
+
scope: existing.scope,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
if (onAuthComplete) {
|
|
237
|
+
try {
|
|
238
|
+
await onAuthComplete();
|
|
239
|
+
}
|
|
240
|
+
catch (e) {
|
|
241
|
+
log.warn(`onAuthComplete failed: ${e}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return json({
|
|
245
|
+
success: true,
|
|
246
|
+
message: '用户已授权,无需重复授权。',
|
|
247
|
+
authorized: true,
|
|
248
|
+
scope: existing.scope,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// 2. Guard against duplicate in-flight flows for this user.
|
|
253
|
+
const flowKey = `${appId}:${senderOpenId}`;
|
|
254
|
+
let reuseCardId;
|
|
255
|
+
let reuseSeq = 0;
|
|
256
|
+
if (pendingFlows.has(flowKey)) {
|
|
257
|
+
const oldFlow = pendingFlows.get(flowKey);
|
|
258
|
+
const currentMessageId = ticket?.messageId ?? '';
|
|
259
|
+
if (oldFlow.messageId === currentMessageId) {
|
|
260
|
+
// 同一轮工具调用(messageId 相同)→ 复用旧卡片
|
|
261
|
+
oldFlow.superseded = true;
|
|
262
|
+
oldFlow.controller.abort();
|
|
263
|
+
reuseCardId = oldFlow.cardId;
|
|
264
|
+
reuseSeq = oldFlow.sequence;
|
|
265
|
+
pendingFlows.delete(flowKey);
|
|
266
|
+
// scope 合并:将旧 flow 的 scope 与新请求合并
|
|
267
|
+
if (oldFlow.scope) {
|
|
268
|
+
const oldScopes = oldFlow.scope.split(/\s+/).filter(Boolean);
|
|
269
|
+
const newScopes = effectiveScope?.split(/\s+/).filter(Boolean) ?? [];
|
|
270
|
+
const merged = new Set([...oldScopes, ...newScopes]);
|
|
271
|
+
effectiveScope = [...merged].join(' ');
|
|
272
|
+
log.info(`scope merge on reuse: [${[...merged].join(', ')}]`);
|
|
273
|
+
}
|
|
274
|
+
log.info(`same message, replacing flow for user=${senderOpenId}, app=${appId}, reusing cardId=${reuseCardId}`);
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
// 新对话(messageId 不同)→ 取消旧流 + 旧卡片标记"授权未完成" + 创建新卡片
|
|
278
|
+
oldFlow.superseded = true;
|
|
279
|
+
oldFlow.controller.abort();
|
|
280
|
+
pendingFlows.delete(flowKey);
|
|
281
|
+
log.info(`new message, cancelling old flow for user=${senderOpenId}, app=${appId}, old cardId=${oldFlow.cardId}`);
|
|
282
|
+
// 标记旧卡片为"授权未完成"
|
|
283
|
+
try {
|
|
284
|
+
await (0, cardkit_1.updateCardKitCardForAuth)({
|
|
285
|
+
cfg,
|
|
286
|
+
cardId: oldFlow.cardId,
|
|
287
|
+
card: (0, oauth_cards_1.buildAuthFailedCard)('新的授权请求已发起'),
|
|
288
|
+
sequence: oldFlow.sequence + 1,
|
|
289
|
+
accountId,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
catch (e) {
|
|
293
|
+
log.warn(`failed to update old card to expired: ${e}`);
|
|
294
|
+
}
|
|
295
|
+
// reuseCardId 保持 undefined,后续会创建新卡片
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// 2.5 应用 scope 预检:过滤掉应用未开通的 scope
|
|
299
|
+
let filteredScope = effectiveScope;
|
|
300
|
+
let unavailableScopes = [];
|
|
301
|
+
if (effectiveScope) {
|
|
302
|
+
try {
|
|
303
|
+
const sdk = lark_client_1.LarkClient.fromAccount(account).sdk;
|
|
304
|
+
const requestedScopes = effectiveScope.split(/\s+/).filter(Boolean);
|
|
305
|
+
const appScopes = await (0, app_scope_checker_1.getAppGrantedScopes)(sdk, appId, 'user');
|
|
306
|
+
const availableScopes = requestedScopes.filter((s) => appScopes.includes(s));
|
|
307
|
+
unavailableScopes = requestedScopes.filter((s) => !appScopes.includes(s));
|
|
308
|
+
if (unavailableScopes.length > 0) {
|
|
309
|
+
log.info(`app has not granted scopes [${unavailableScopes.join(', ')}], filtering them out`);
|
|
310
|
+
if (availableScopes.length === 0) {
|
|
311
|
+
// 所有 scope 都未开通,直接返回错误
|
|
312
|
+
const openDomain = brand === 'lark' ? 'https://open.larksuite.com' : 'https://open.feishu.cn';
|
|
313
|
+
const permissionUrl = `${openDomain}/app/${appId}/permission`;
|
|
314
|
+
return json({
|
|
315
|
+
error: 'app_scopes_not_granted',
|
|
316
|
+
message: `应用未开通任何请求的用户权限,无法发起授权。请先在开放平台开通以下权限:\n${unavailableScopes.map((s) => `- ${s}`).join('\n')}\n\n权限管理地址:${permissionUrl}`,
|
|
317
|
+
unavailable_scopes: unavailableScopes,
|
|
318
|
+
app_permission_url: permissionUrl,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
// 部分 scope 未开通,只授权已开通的 scope
|
|
322
|
+
filteredScope = availableScopes.join(' ');
|
|
323
|
+
log.info(`proceeding with available scopes [${availableScopes.join(', ')}]`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
catch (err) {
|
|
327
|
+
// 如果 scope 检查失败,记录日志但继续执行(降级处理)
|
|
328
|
+
log.warn(`failed to check app scopes, proceeding anyway: ${err}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// 3. Request device authorisation.
|
|
332
|
+
const deviceAuth = await (0, device_flow_1.requestDeviceAuthorization)({
|
|
333
|
+
appId,
|
|
334
|
+
appSecret,
|
|
335
|
+
brand,
|
|
336
|
+
scope: filteredScope,
|
|
337
|
+
});
|
|
338
|
+
// 4. Build and send authorisation card.
|
|
339
|
+
const authCard = (0, oauth_cards_1.buildAuthCard)({
|
|
340
|
+
verificationUriComplete: deviceAuth.verificationUriComplete,
|
|
341
|
+
expiresMin: Math.round(deviceAuth.expiresIn / 60),
|
|
342
|
+
scope: filteredScope, // 使用过滤后的 scope
|
|
343
|
+
isBatchAuth,
|
|
344
|
+
totalAppScopes,
|
|
345
|
+
alreadyGranted,
|
|
346
|
+
batchInfo,
|
|
347
|
+
filteredScopes: unavailableScopes.length > 0 ? unavailableScopes : undefined,
|
|
348
|
+
appId,
|
|
349
|
+
showBatchAuthHint,
|
|
350
|
+
brand,
|
|
351
|
+
});
|
|
352
|
+
let cardId;
|
|
353
|
+
let seq;
|
|
354
|
+
const chatId = ticket?.chatId;
|
|
355
|
+
if (!chatId || !ticket) {
|
|
356
|
+
return json({ error: '无法确定发送目标' });
|
|
357
|
+
}
|
|
358
|
+
if (reuseCardId) {
|
|
359
|
+
// 复用旧卡片:原地更新内容(scope + 授权链接),不创建新卡片
|
|
360
|
+
const newSeq = reuseSeq + 1;
|
|
361
|
+
try {
|
|
362
|
+
await (0, cardkit_1.updateCardKitCardForAuth)({
|
|
363
|
+
cfg,
|
|
364
|
+
cardId: reuseCardId,
|
|
365
|
+
card: authCard,
|
|
366
|
+
sequence: newSeq,
|
|
367
|
+
accountId,
|
|
368
|
+
});
|
|
369
|
+
log.info(`updated existing card ${reuseCardId} with merged scopes, seq=${newSeq}`);
|
|
370
|
+
}
|
|
371
|
+
catch (err) {
|
|
372
|
+
log.warn(`failed to update existing card, creating new one: ${err}`);
|
|
373
|
+
// 降级:创建新卡片
|
|
374
|
+
const newCardId = await (0, cardkit_1.createCardEntity)({ cfg, card: authCard, accountId });
|
|
375
|
+
if (!newCardId)
|
|
376
|
+
return json({ error: '创建授权卡片失败' });
|
|
377
|
+
if (chatId) {
|
|
378
|
+
await (0, cardkit_1.sendCardByCardId)({
|
|
379
|
+
cfg,
|
|
380
|
+
to: chatId,
|
|
381
|
+
cardId: newCardId,
|
|
382
|
+
replyToMessageId: ticket?.messageId?.startsWith('om_') ? ticket.messageId : undefined,
|
|
383
|
+
replyInThread: Boolean(ticket?.threadId),
|
|
384
|
+
accountId,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
cardId = newCardId;
|
|
388
|
+
seq = 1;
|
|
389
|
+
reuseCardId = undefined;
|
|
390
|
+
}
|
|
391
|
+
if (reuseCardId) {
|
|
392
|
+
cardId = reuseCardId;
|
|
393
|
+
seq = newSeq;
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
cardId = cardId;
|
|
397
|
+
seq = seq;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
// 首次创建卡片
|
|
402
|
+
const newCardId = await (0, cardkit_1.createCardEntity)({ cfg, card: authCard, accountId });
|
|
403
|
+
if (!newCardId) {
|
|
404
|
+
return json({ error: '创建授权卡片失败' });
|
|
405
|
+
}
|
|
406
|
+
await (0, cardkit_1.sendCardByCardId)({
|
|
407
|
+
cfg,
|
|
408
|
+
to: chatId,
|
|
409
|
+
cardId: newCardId,
|
|
410
|
+
replyToMessageId: ticket?.messageId?.startsWith('om_') ? ticket.messageId : undefined,
|
|
411
|
+
replyInThread: Boolean(ticket?.threadId),
|
|
412
|
+
accountId,
|
|
413
|
+
});
|
|
414
|
+
cardId = newCardId;
|
|
415
|
+
seq = 1;
|
|
416
|
+
}
|
|
417
|
+
// 7. Start background polling.
|
|
418
|
+
const abortController = new AbortController();
|
|
419
|
+
const currentFlow = {
|
|
420
|
+
controller: abortController,
|
|
421
|
+
cardId,
|
|
422
|
+
sequence: seq,
|
|
423
|
+
messageId: ticket?.messageId ?? '',
|
|
424
|
+
superseded: false,
|
|
425
|
+
scope: effectiveScope,
|
|
426
|
+
};
|
|
427
|
+
pendingFlows.set(flowKey, currentFlow);
|
|
428
|
+
let pendingFlowDelete = false;
|
|
429
|
+
// Fire-and-forget – polling happens asynchronously.
|
|
430
|
+
(0, device_flow_1.pollDeviceToken)({
|
|
431
|
+
appId,
|
|
432
|
+
appSecret,
|
|
433
|
+
brand,
|
|
434
|
+
deviceCode: deviceAuth.deviceCode,
|
|
435
|
+
interval: deviceAuth.interval,
|
|
436
|
+
expiresIn: deviceAuth.expiresIn,
|
|
437
|
+
signal: abortController.signal,
|
|
438
|
+
})
|
|
439
|
+
.then(async (result) => {
|
|
440
|
+
// 被新流替换后,跳过所有卡片更新,避免覆盖新流的卡片内容
|
|
441
|
+
if (currentFlow.superseded) {
|
|
442
|
+
log.info(`flow superseded, skipping card update for cardId=${cardId}`);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (result.ok) {
|
|
446
|
+
// ===== 身份校验:验证实际授权用户与发起人一致 =====
|
|
447
|
+
const identity = await verifyTokenIdentity(brand, result.token.accessToken, senderOpenId);
|
|
448
|
+
if (!identity.valid) {
|
|
449
|
+
log.warn(`identity mismatch! expected=${senderOpenId}, ` +
|
|
450
|
+
`actual=${identity.actualOpenId ?? 'unknown'}, cardId=${cardId}`);
|
|
451
|
+
try {
|
|
452
|
+
await (0, cardkit_1.updateCardKitCardForAuth)({
|
|
453
|
+
cfg,
|
|
454
|
+
cardId,
|
|
455
|
+
card: (0, oauth_cards_1.buildAuthIdentityMismatchCard)(brand),
|
|
456
|
+
sequence: ++seq,
|
|
457
|
+
accountId,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
catch (e) {
|
|
461
|
+
log.warn(`failed to update card for identity mismatch: ${e}`);
|
|
462
|
+
}
|
|
463
|
+
pendingFlows.delete(flowKey);
|
|
464
|
+
pendingFlowDelete = true;
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
// ===== 身份校验通过,继续保存 token =====
|
|
468
|
+
// Save token to Keychain.
|
|
469
|
+
const now = Date.now();
|
|
470
|
+
const storedToken = {
|
|
471
|
+
userOpenId: senderOpenId,
|
|
472
|
+
appId,
|
|
473
|
+
accessToken: result.token.accessToken,
|
|
474
|
+
refreshToken: result.token.refreshToken,
|
|
475
|
+
expiresAt: now + result.token.expiresIn * 1000,
|
|
476
|
+
refreshExpiresAt: now + result.token.refreshExpiresIn * 1000,
|
|
477
|
+
scope: result.token.scope,
|
|
478
|
+
grantedAt: now,
|
|
479
|
+
};
|
|
480
|
+
await (0, token_store_1.setStoredToken)(storedToken);
|
|
481
|
+
// 1. Update card → success immediately so user sees
|
|
482
|
+
// visual confirmation right away.
|
|
483
|
+
try {
|
|
484
|
+
await (0, cardkit_1.updateCardKitCardForAuth)({
|
|
485
|
+
cfg,
|
|
486
|
+
cardId,
|
|
487
|
+
card: (0, oauth_cards_1.buildAuthSuccessCard)(brand),
|
|
488
|
+
sequence: ++seq,
|
|
489
|
+
accountId,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
catch (e) {
|
|
493
|
+
log.warn(`failed to update card to success: ${e}`);
|
|
494
|
+
}
|
|
495
|
+
// 删除 pending flow
|
|
496
|
+
pendingFlows.delete(flowKey);
|
|
497
|
+
pendingFlowDelete = true;
|
|
498
|
+
// 2. Send synthetic message to notify AI that auth is
|
|
499
|
+
// complete, so it can automatically retry the operation.
|
|
500
|
+
// Skip when called from onboarding (no AI context to retry).
|
|
501
|
+
// 调用 onAuthComplete 回调(用于 onboarding 批量授权链式触发)
|
|
502
|
+
if (onAuthComplete) {
|
|
503
|
+
try {
|
|
504
|
+
await onAuthComplete();
|
|
505
|
+
}
|
|
506
|
+
catch (e) {
|
|
507
|
+
log.warn(`onAuthComplete failed: ${e}`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (skipSyntheticMessage) {
|
|
511
|
+
log.info('skipSyntheticMessage=true, skipping synthetic message');
|
|
512
|
+
}
|
|
513
|
+
else
|
|
514
|
+
try {
|
|
515
|
+
const syntheticMsgId = `${ticket.messageId}:auth-complete`;
|
|
516
|
+
// Provide a minimal runtime so reply-dispatcher
|
|
517
|
+
// does not crash on `params.runtime.log?.()`.
|
|
518
|
+
const syntheticRuntime = {
|
|
519
|
+
log: (msg) => log.info(msg),
|
|
520
|
+
error: (msg) => log.error(msg),
|
|
521
|
+
};
|
|
522
|
+
const status = await (0, synthetic_message_1.dispatchSyntheticTextMessage)({
|
|
523
|
+
cfg,
|
|
524
|
+
accountId,
|
|
525
|
+
chatId,
|
|
526
|
+
senderOpenId,
|
|
527
|
+
text: '我已完成飞书账号授权,请继续执行之前的操作。',
|
|
528
|
+
syntheticMessageId: syntheticMsgId,
|
|
529
|
+
replyToMessageId: ticket.messageId,
|
|
530
|
+
chatType: ticket.chatType,
|
|
531
|
+
threadId: ticket.threadId,
|
|
532
|
+
runtime: syntheticRuntime,
|
|
533
|
+
});
|
|
534
|
+
log.info(`synthetic message queued (${status})`);
|
|
535
|
+
log.info('synthetic message dispatched after successful auth');
|
|
536
|
+
}
|
|
537
|
+
catch (e) {
|
|
538
|
+
log.warn(`failed to send synthetic message after auth: ${e}`);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
// Update card → failure.
|
|
543
|
+
try {
|
|
544
|
+
await (0, cardkit_1.updateCardKitCardForAuth)({
|
|
545
|
+
cfg,
|
|
546
|
+
cardId,
|
|
547
|
+
card: (0, oauth_cards_1.buildAuthFailedCard)(result.message),
|
|
548
|
+
sequence: ++seq,
|
|
549
|
+
accountId,
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
catch (e) {
|
|
553
|
+
log.warn(`failed to update card to failure: ${e}`);
|
|
554
|
+
}
|
|
555
|
+
// 删除 pending flow
|
|
556
|
+
pendingFlows.delete(flowKey);
|
|
557
|
+
pendingFlowDelete = true;
|
|
558
|
+
}
|
|
559
|
+
})
|
|
560
|
+
.catch((err) => {
|
|
561
|
+
log.error(`polling error: ${err}`);
|
|
562
|
+
})
|
|
563
|
+
.finally(() => {
|
|
564
|
+
if (!pendingFlowDelete) {
|
|
565
|
+
// 只在当前 flow 仍是注册的那个时才删除,避免旧流误删新流的 entry
|
|
566
|
+
if (pendingFlows.get(flowKey) === currentFlow) {
|
|
567
|
+
pendingFlows.delete(flowKey);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
const scopeCount = filteredScope.split(/\s+/).filter(Boolean).length;
|
|
572
|
+
let message = isBatchAuth
|
|
573
|
+
? `已发送批量授权请求卡片,共需授权 ${scopeCount} 个权限。请在卡片中完成授权。`
|
|
574
|
+
: '已发送授权请求卡片,请用户在卡片中点击链接完成授权。授权完成后请重新执行之前的操作。';
|
|
575
|
+
if (batchInfo) {
|
|
576
|
+
message += batchInfo;
|
|
577
|
+
}
|
|
578
|
+
// 如果有被过滤的 scope,添加提示信息
|
|
579
|
+
if (unavailableScopes.length > 0) {
|
|
580
|
+
const openDomain = brand === 'lark' ? 'https://open.larksuite.com' : 'https://open.feishu.cn';
|
|
581
|
+
const permissionUrl = `${openDomain}/app/${appId}/permission`;
|
|
582
|
+
message += `\n\n⚠️ **注意**:以下权限因应用未开通而被跳过,如需使用请先在开放平台开通:\n${unavailableScopes.map((s) => `- ${s}`).join('\n')}\n\n权限管理地址:${permissionUrl}`;
|
|
583
|
+
}
|
|
584
|
+
const openDomainForResult = brand === 'lark' ? 'https://open.larksuite.com' : 'https://open.feishu.cn';
|
|
585
|
+
return json({
|
|
586
|
+
success: true,
|
|
587
|
+
message,
|
|
588
|
+
awaiting_authorization: true,
|
|
589
|
+
filtered_scopes: unavailableScopes.length > 0 ? unavailableScopes : undefined,
|
|
590
|
+
app_permission_url: unavailableScopes.length > 0 ? `${openDomainForResult}/app/${appId}/permission` : undefined,
|
|
591
|
+
});
|
|
592
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Onboarding 预授权模块。
|
|
6
|
+
*
|
|
7
|
+
* 配对后自动发起 OAuth Device Flow,引导应用 owner 完成用户授权。
|
|
8
|
+
* 仅当配对用户 === 应用 owner 时触发。
|
|
9
|
+
*
|
|
10
|
+
* 飞书限制:单次 OAuth 最多 50 个 scope。
|
|
11
|
+
* 超过 50 个时自动分批处理,每批授权完成后自动发起下一批(链式触发)。
|
|
12
|
+
*/
|
|
13
|
+
import type { ClawdbotConfig } from 'openclaw/plugin-sdk';
|
|
14
|
+
/**
|
|
15
|
+
* 配对后触发 onboarding OAuth 授权。
|
|
16
|
+
*
|
|
17
|
+
* 流程:
|
|
18
|
+
* 1. 检查 userOpenId === 应用 owner,不匹配则静默跳过
|
|
19
|
+
* 2. 读取 onboarding-scopes.json 中的 user scope 列表
|
|
20
|
+
* 3. 分批处理(每批最多 50 个),第一批直接发起 OAuth Device Flow
|
|
21
|
+
* 4. 每批授权完成后通过 onAuthComplete 回调自动发起下一批
|
|
22
|
+
*/
|
|
23
|
+
export declare function triggerOnboarding(params: {
|
|
24
|
+
cfg: ClawdbotConfig;
|
|
25
|
+
userOpenId: string;
|
|
26
|
+
accountId: string;
|
|
27
|
+
}): Promise<void>;
|