@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,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Outbound mention normalizer for Feishu post messages. Rewrites <at>
|
|
6
|
+
* tag variants and resolves "@Name" to the canonical
|
|
7
|
+
* <at user_id="ou_xxx">Name</at> form expected by the Feishu API.
|
|
8
|
+
*/
|
|
9
|
+
import type { LarkAccount } from '../../core/types';
|
|
10
|
+
import type { PrincipalKind } from '../inbound/user-name-cache-store';
|
|
11
|
+
/**
|
|
12
|
+
* Rewrites <at> tag attribute and quote variants to the canonical
|
|
13
|
+
* `<at user_id="ou_xxx">` form. Idempotent; pure string transform.
|
|
14
|
+
*
|
|
15
|
+
* Recognized variants: `id=`, `open_id=`, `user_id=`; double-quoted,
|
|
16
|
+
* single-quoted, or unquoted; `id=all` aligned to `user_id="all"` with
|
|
17
|
+
* "Everyone" name fill. `<person>` picker tags are left untouched.
|
|
18
|
+
*/
|
|
19
|
+
export declare function normalizeOutboundMentionsTagPass(text: string): string;
|
|
20
|
+
export type LogFn = (...args: unknown[]) => void;
|
|
21
|
+
export interface NormalizeContext {
|
|
22
|
+
/** Chat where the message will be sent; keys lazy chat-member fetches. */
|
|
23
|
+
chatId: string;
|
|
24
|
+
account: LarkAccount;
|
|
25
|
+
/** Optional sink for prefetch errors during normalization. */
|
|
26
|
+
log?: LogFn;
|
|
27
|
+
}
|
|
28
|
+
export interface SentinelEntry {
|
|
29
|
+
name: string;
|
|
30
|
+
reason: 'not_found' | 'ambiguous';
|
|
31
|
+
candidates?: Array<{
|
|
32
|
+
openId: string;
|
|
33
|
+
kind?: PrincipalKind;
|
|
34
|
+
}>;
|
|
35
|
+
}
|
|
36
|
+
export interface NormalizeResult {
|
|
37
|
+
normalizedText: string;
|
|
38
|
+
sentinels: SentinelEntry[];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Normalizes outbound text for Feishu: rewrites <at> tag variants and
|
|
42
|
+
* resolves plain "@Name" against the per-account name cache.
|
|
43
|
+
*
|
|
44
|
+
* On cache miss, fetches the chat's bot list and retries; if still
|
|
45
|
+
* unresolved, fetches the chat's member list and retries. Names that
|
|
46
|
+
* match multiple cache entries become ambiguous sentinels for next-turn
|
|
47
|
+
* disambiguation; remaining misses are dropped without a sentinel to
|
|
48
|
+
* avoid false positives on `@` followed by non-name CJK runs.
|
|
49
|
+
*/
|
|
50
|
+
export declare function normalizeOutboundMentions(text: string, ctx: NormalizeContext): Promise<NormalizeResult>;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*
|
|
6
|
+
* Outbound mention normalizer for Feishu post messages. Rewrites <at>
|
|
7
|
+
* tag variants and resolves "@Name" to the canonical
|
|
8
|
+
* <at user_id="ou_xxx">Name</at> form expected by the Feishu API.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.normalizeOutboundMentionsTagPass = normalizeOutboundMentionsTagPass;
|
|
12
|
+
exports.normalizeOutboundMentions = normalizeOutboundMentions;
|
|
13
|
+
const user_name_cache_1 = require("../inbound/user-name-cache.js");
|
|
14
|
+
/**
|
|
15
|
+
* Rewrites <at> tag attribute and quote variants to the canonical
|
|
16
|
+
* `<at user_id="ou_xxx">` form. Idempotent; pure string transform.
|
|
17
|
+
*
|
|
18
|
+
* Recognized variants: `id=`, `open_id=`, `user_id=`; double-quoted,
|
|
19
|
+
* single-quoted, or unquoted; `id=all` aligned to `user_id="all"` with
|
|
20
|
+
* "Everyone" name fill. `<person>` picker tags are left untouched.
|
|
21
|
+
*/
|
|
22
|
+
function normalizeOutboundMentionsTagPass(text) {
|
|
23
|
+
let out = text;
|
|
24
|
+
// <at id=all|user_id="all"|...></at> → <at user_id="all">Everyone</at>.
|
|
25
|
+
// Match an explicit closing tag so already-canonical input stays
|
|
26
|
+
// idempotent: a bare opening tag would match the optional-close form
|
|
27
|
+
// and corrupt the trailing "Everyone</at>" into a double close.
|
|
28
|
+
out = out.replace(/<at\s+(?:id|user_id|open_id)\s*=\s*["']?all["']?\s*>\s*<\/at>/gi, '<at user_id="all">Everyone</at>');
|
|
29
|
+
// Generic <at attr=ou_xxx> → <at user_id="ou_xxx">.
|
|
30
|
+
out = out.replace(/<at\s+(?:id|open_id|user_id)\s*=\s*["']?(ou_[A-Za-z0-9_-]+)["']?\s*>/gi, '<at user_id="$1">');
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
const noopLog = () => { };
|
|
34
|
+
/** Spans excluded from the @Name scan: code, canonical tags, emails, URLs. */
|
|
35
|
+
const MASK_PATTERNS = [
|
|
36
|
+
/```[\s\S]*?```/g,
|
|
37
|
+
/`[^`\n]*`/g,
|
|
38
|
+
/<at\s+user_id="[^"]+">[^<]*<\/at>/g,
|
|
39
|
+
/<person\s+[^>]*>[^<]*<\/person>/g,
|
|
40
|
+
/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g,
|
|
41
|
+
/\b(?:https?|ftp|mailto):\/\/[^\s)\]<>]+/g,
|
|
42
|
+
];
|
|
43
|
+
// CJK range U+4E00–U+9FA5 covers ideographs so multibyte names match.
|
|
44
|
+
const CANDIDATE_RE = /@([A-Za-z0-9\u4e00-\u9fa5_]+(?:[.-][A-Za-z0-9\u4e00-\u9fa5_]+)*)/g;
|
|
45
|
+
const FALLBACK_PREFETCHES = [user_name_cache_1.prefetchChatBots, user_name_cache_1.prefetchChatMembers];
|
|
46
|
+
/**
|
|
47
|
+
* Normalizes outbound text for Feishu: rewrites <at> tag variants and
|
|
48
|
+
* resolves plain "@Name" against the per-account name cache.
|
|
49
|
+
*
|
|
50
|
+
* On cache miss, fetches the chat's bot list and retries; if still
|
|
51
|
+
* unresolved, fetches the chat's member list and retries. Names that
|
|
52
|
+
* match multiple cache entries become ambiguous sentinels for next-turn
|
|
53
|
+
* disambiguation; remaining misses are dropped without a sentinel to
|
|
54
|
+
* avoid false positives on `@` followed by non-name CJK runs.
|
|
55
|
+
*/
|
|
56
|
+
async function normalizeOutboundMentions(text, ctx) {
|
|
57
|
+
let out = normalizeOutboundMentionsTagPass(text);
|
|
58
|
+
// Drop redundant `@` immediately preceding a canonical <at> tag.
|
|
59
|
+
out = out.replace(/@(<at\s+user_id="ou_[A-Za-z0-9_-]+">[^<]*<\/at>)/g, '$1');
|
|
60
|
+
const inMask = buildMaskPredicate(out);
|
|
61
|
+
const sentinels = [];
|
|
62
|
+
const replacements = [];
|
|
63
|
+
// First sweep: resolve from cache; defer misses for lazy fetch.
|
|
64
|
+
let pending = [];
|
|
65
|
+
for (const m of out.matchAll(CANDIDATE_RE)) {
|
|
66
|
+
const start = m.index;
|
|
67
|
+
if (inMask(start))
|
|
68
|
+
continue;
|
|
69
|
+
const end = start + m[0].length;
|
|
70
|
+
const r = resolveCandidate(m[1], ctx);
|
|
71
|
+
if (r.kind === 'resolved') {
|
|
72
|
+
replacements.push(makeReplacement(start, end, r));
|
|
73
|
+
}
|
|
74
|
+
else if (r.entry.reason === 'ambiguous') {
|
|
75
|
+
sentinels.push(r.entry);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
pending.push({ start, end, name: m[1] });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Retry through each fallback in turn; remaining misses are dropped.
|
|
82
|
+
for (const prefetch of FALLBACK_PREFETCHES) {
|
|
83
|
+
if (pending.length === 0)
|
|
84
|
+
break;
|
|
85
|
+
pending = await retryWithPrefetch(pending, prefetch, ctx, replacements, sentinels);
|
|
86
|
+
}
|
|
87
|
+
return { normalizedText: applyReplacements(out, replacements), sentinels };
|
|
88
|
+
}
|
|
89
|
+
function buildMaskPredicate(text) {
|
|
90
|
+
const masks = [];
|
|
91
|
+
for (const re of MASK_PATTERNS) {
|
|
92
|
+
for (const m of text.matchAll(re)) {
|
|
93
|
+
masks.push([m.index, m.index + m[0].length]);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return (idx) => masks.some(([s, e]) => idx >= s && idx < e);
|
|
97
|
+
}
|
|
98
|
+
function makeReplacement(start, end, r) {
|
|
99
|
+
return { start, end, text: `<at user_id="${r.openId}">${r.displayName}</at>` };
|
|
100
|
+
}
|
|
101
|
+
async function retryWithPrefetch(pending, prefetch, ctx, replacements, sentinels) {
|
|
102
|
+
await prefetch(ctx.account, ctx.chatId, ctx.log ?? noopLog);
|
|
103
|
+
const stillMissing = [];
|
|
104
|
+
for (const p of pending) {
|
|
105
|
+
const r = resolveCandidate(p.name, ctx);
|
|
106
|
+
if (r.kind === 'resolved') {
|
|
107
|
+
replacements.push(makeReplacement(p.start, p.end, r));
|
|
108
|
+
}
|
|
109
|
+
else if (r.entry.reason === 'ambiguous') {
|
|
110
|
+
sentinels.push(r.entry);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
stillMissing.push(p);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return stillMissing;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Applies non-overlapping replacements in a single left-to-right pass.
|
|
120
|
+
* Builds an array of literal chunks then joins it once — O(n + total
|
|
121
|
+
* replacement length), no string-concat quadratic behavior.
|
|
122
|
+
*/
|
|
123
|
+
function applyReplacements(text, replacements) {
|
|
124
|
+
if (replacements.length === 0)
|
|
125
|
+
return text;
|
|
126
|
+
const sorted = [...replacements].sort((a, b) => a.start - b.start);
|
|
127
|
+
const out = [];
|
|
128
|
+
let cursor = 0;
|
|
129
|
+
for (const r of sorted) {
|
|
130
|
+
if (r.start < cursor)
|
|
131
|
+
continue; // drop overlapping replacements defensively
|
|
132
|
+
out.push(text.slice(cursor, r.start), r.text);
|
|
133
|
+
cursor = r.end;
|
|
134
|
+
}
|
|
135
|
+
out.push(text.slice(cursor));
|
|
136
|
+
return out.join('');
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Aliases for "mention everyone" that may appear as plain text instead
|
|
140
|
+
* of the canonical `<at user_id="all">` tag. Match is case-insensitive.
|
|
141
|
+
*/
|
|
142
|
+
const ALL_ALIASES = new Set(['all', 'everyone', '所有人']);
|
|
143
|
+
function resolveCandidate(name, ctx) {
|
|
144
|
+
// Literal @-everyone aliases map to the canonical Feishu @all tag,
|
|
145
|
+
// bypassing the per-account name cache. The display name "Everyone"
|
|
146
|
+
// matches what the tag-level normalizer fills in for empty <at> bodies.
|
|
147
|
+
if (ALL_ALIASES.has(name.toLowerCase())) {
|
|
148
|
+
return { kind: 'resolved', openId: 'all', displayName: 'Everyone' };
|
|
149
|
+
}
|
|
150
|
+
const cache = (0, user_name_cache_1.getUserNameCache)(ctx.account.accountId);
|
|
151
|
+
const matches = cache.lookupByName(name);
|
|
152
|
+
if (matches.length === 1) {
|
|
153
|
+
return { kind: 'resolved', openId: matches[0].openId, displayName: matches[0].name };
|
|
154
|
+
}
|
|
155
|
+
if (matches.length > 1) {
|
|
156
|
+
return {
|
|
157
|
+
kind: 'sentinel',
|
|
158
|
+
entry: {
|
|
159
|
+
name,
|
|
160
|
+
reason: 'ambiguous',
|
|
161
|
+
candidates: matches.map((m) => ({ openId: m.openId, kind: m.kind })),
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return { kind: 'sentinel', entry: { name, reason: 'not_found' } };
|
|
166
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Outbound mention normalization + bot-peer @-mention enforcement.
|
|
6
|
+
*
|
|
7
|
+
* Why this exists: in Feishu groups, a bot only receives a message when
|
|
8
|
+
* something explicitly @-mentions its open_id with a structured `<at>`
|
|
9
|
+
* element. LLM outputs come in many "natural" @-shapes (markdown, angle
|
|
10
|
+
* brackets, template syntax) but none of them satisfy the delivery rule —
|
|
11
|
+
* only `<at user_id="ou_xxx">Name</at>` does. This module:
|
|
12
|
+
*
|
|
13
|
+
* 1. `normalizeOutboundMentions` — rewrites the six common LLM shapes
|
|
14
|
+
* into the standard `<at>` element when the name resolves via the
|
|
15
|
+
* per-chat mention-registry. Unknown names are left as plain text
|
|
16
|
+
* (a recipient who's never spoken in this chat can't be @-mentioned).
|
|
17
|
+
*
|
|
18
|
+
* 2. `ensureMention` — in bot→bot group scenarios, the agent reply must
|
|
19
|
+
* include an explicit @ of the peer bot or the peer never receives
|
|
20
|
+
* it. When the LLM forgets to add one, prepend it as a safety net.
|
|
21
|
+
*
|
|
22
|
+
* Both are idempotent: existing standard `<at>` elements are preserved,
|
|
23
|
+
* and `ensureMention` is a no-op when the peer is already mentioned.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Rewrite LLM-emitted mention shapes into the standard `<at user_id="…">`
|
|
27
|
+
* element used by Feishu for guaranteed delivery. Unknown names — those
|
|
28
|
+
* whose display name isn't in the per-chat registry — are left intact as
|
|
29
|
+
* plain text so the agent's wording survives even when delivery cannot
|
|
30
|
+
* fire.
|
|
31
|
+
*/
|
|
32
|
+
export declare function normalizeOutboundMentions(text: string, chatId: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Ensure the reply explicitly @-mentions the given peer.
|
|
35
|
+
*
|
|
36
|
+
* Designed for bot↔bot group flows: when the peer is another bot, Feishu
|
|
37
|
+
* won't deliver the message unless an `<at user_id="ou_peer">` element is
|
|
38
|
+
* present somewhere in the text. If the LLM already mentioned the peer,
|
|
39
|
+
* no-op; otherwise, prepend a standard `<at>` so the peer wakes up.
|
|
40
|
+
*/
|
|
41
|
+
export declare function ensureMention(text: string, peerOpenId: string, peerName: string): string;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*
|
|
6
|
+
* Outbound mention normalization + bot-peer @-mention enforcement.
|
|
7
|
+
*
|
|
8
|
+
* Why this exists: in Feishu groups, a bot only receives a message when
|
|
9
|
+
* something explicitly @-mentions its open_id with a structured `<at>`
|
|
10
|
+
* element. LLM outputs come in many "natural" @-shapes (markdown, angle
|
|
11
|
+
* brackets, template syntax) but none of them satisfy the delivery rule —
|
|
12
|
+
* only `<at user_id="ou_xxx">Name</at>` does. This module:
|
|
13
|
+
*
|
|
14
|
+
* 1. `normalizeOutboundMentions` — rewrites the six common LLM shapes
|
|
15
|
+
* into the standard `<at>` element when the name resolves via the
|
|
16
|
+
* per-chat mention-registry. Unknown names are left as plain text
|
|
17
|
+
* (a recipient who's never spoken in this chat can't be @-mentioned).
|
|
18
|
+
*
|
|
19
|
+
* 2. `ensureMention` — in bot→bot group scenarios, the agent reply must
|
|
20
|
+
* include an explicit @ of the peer bot or the peer never receives
|
|
21
|
+
* it. When the LLM forgets to add one, prepend it as a safety net.
|
|
22
|
+
*
|
|
23
|
+
* Both are idempotent: existing standard `<at>` elements are preserved,
|
|
24
|
+
* and `ensureMention` is a no-op when the peer is already mentioned.
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.normalizeOutboundMentions = normalizeOutboundMentions;
|
|
28
|
+
exports.ensureMention = ensureMention;
|
|
29
|
+
const mention_registry_1 = require("../inbound/mention-registry.js");
|
|
30
|
+
const STANDARD_AT_RE = /<at\s+user_id="[^"]*">[^<]*<\/at>/g;
|
|
31
|
+
// LLM-produced mention shapes (in priority order — first match wins).
|
|
32
|
+
// Each pattern captures the display name; the runner consults the registry
|
|
33
|
+
// for an openId before rewriting, and leaves the original text untouched
|
|
34
|
+
// when the name doesn't resolve.
|
|
35
|
+
//
|
|
36
|
+
// Plain `@Name` is intentionally last because the more decorated variants
|
|
37
|
+
// embed an `@` and we want them to claim ownership of their match first.
|
|
38
|
+
const VARIANT_PATTERNS = [
|
|
39
|
+
{ re: /@\[([^\]\n]+)\]/g, group: 1 }, // @[Name]
|
|
40
|
+
{ re: /@<([^>\n]+)>/g, group: 1 }, // @<Name>
|
|
41
|
+
{ re: /<@([^>\n]+)>/g, group: 1 }, // <@Name>
|
|
42
|
+
{ re: /<at>\s*([^<\n]+?)\s*<\/at>/g, group: 1 }, // <at>Name</at>
|
|
43
|
+
{ re: /\{\{\s*([^}\n]+?)\s*\}\}/g, group: 1 }, // {{Name}}
|
|
44
|
+
// Plain @Name — single token, ASCII/CJK/digit/underscore. Won't match
|
|
45
|
+
// mid-word (preceded by a word char), so email addresses are safe.
|
|
46
|
+
{ re: /(^|[^\w@])@([A-Za-z0-9_一-鿿][\w.一-鿿-]{0,30})/g, group: 2 },
|
|
47
|
+
];
|
|
48
|
+
/**
|
|
49
|
+
* Rewrite LLM-emitted mention shapes into the standard `<at user_id="…">`
|
|
50
|
+
* element used by Feishu for guaranteed delivery. Unknown names — those
|
|
51
|
+
* whose display name isn't in the per-chat registry — are left intact as
|
|
52
|
+
* plain text so the agent's wording survives even when delivery cannot
|
|
53
|
+
* fire.
|
|
54
|
+
*/
|
|
55
|
+
function normalizeOutboundMentions(text, chatId) {
|
|
56
|
+
if (!text || !chatId)
|
|
57
|
+
return text;
|
|
58
|
+
// Step 1: mask out already-standard <at> elements so the variant patterns
|
|
59
|
+
// below never re-match them.
|
|
60
|
+
const protectedTokens = [];
|
|
61
|
+
let masked = text.replace(STANDARD_AT_RE, (m) => {
|
|
62
|
+
const idx = protectedTokens.push(m) - 1;
|
|
63
|
+
return `\x00P${idx}\x00`;
|
|
64
|
+
});
|
|
65
|
+
// Step 2: apply variant patterns in declared priority.
|
|
66
|
+
for (const { re, group } of VARIANT_PATTERNS) {
|
|
67
|
+
masked = masked.replace(re, (...args) => {
|
|
68
|
+
const match = args[0];
|
|
69
|
+
const groups = args.slice(1, args.length - 2);
|
|
70
|
+
const name = (groups[group - 1] ?? '').trim();
|
|
71
|
+
if (!name)
|
|
72
|
+
return match;
|
|
73
|
+
const openId = (0, mention_registry_1.lookupByName)(chatId, name);
|
|
74
|
+
if (!openId)
|
|
75
|
+
return match;
|
|
76
|
+
const standardAt = `<at user_id="${openId}">${name}</at>`;
|
|
77
|
+
// For the plain-@Name pattern we captured a leading word boundary in
|
|
78
|
+
// group 1 — preserve it. Other patterns matched the @-shape exactly.
|
|
79
|
+
if (group === 2) {
|
|
80
|
+
const leading = groups[0] ?? '';
|
|
81
|
+
return `${leading}${standardAt}`;
|
|
82
|
+
}
|
|
83
|
+
return standardAt;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
// Step 3: restore protected tokens. The \x00 sentinels are intentional
|
|
87
|
+
// private-use markers that never appear in real Feishu text.
|
|
88
|
+
// eslint-disable-next-line no-control-regex
|
|
89
|
+
return masked.replace(/\x00P(\d+)\x00/g, (_, idx) => protectedTokens[Number(idx)]);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Ensure the reply explicitly @-mentions the given peer.
|
|
93
|
+
*
|
|
94
|
+
* Designed for bot↔bot group flows: when the peer is another bot, Feishu
|
|
95
|
+
* won't deliver the message unless an `<at user_id="ou_peer">` element is
|
|
96
|
+
* present somewhere in the text. If the LLM already mentioned the peer,
|
|
97
|
+
* no-op; otherwise, prepend a standard `<at>` so the peer wakes up.
|
|
98
|
+
*/
|
|
99
|
+
function ensureMention(text, peerOpenId, peerName) {
|
|
100
|
+
if (!peerOpenId)
|
|
101
|
+
return text;
|
|
102
|
+
// Already-mentioned check — match any existing <at user_id="ou_peer">
|
|
103
|
+
// element regardless of the rendered name to preserve idempotency.
|
|
104
|
+
const escaped = peerOpenId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
105
|
+
const existingRe = new RegExp(`<at\\s+user_id="${escaped}">[^<]*<\\/at>`);
|
|
106
|
+
if (existingRe.test(text))
|
|
107
|
+
return text;
|
|
108
|
+
const standardAt = `<at user_id="${peerOpenId}">${peerName || peerOpenId}</at>`;
|
|
109
|
+
if (!text || !text.trim())
|
|
110
|
+
return standardAt;
|
|
111
|
+
return `${standardAt} ${text}`;
|
|
112
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { ChannelOutboundAdapter } from 'openclaw/plugin-sdk/channel-send-result';
|
|
2
|
+
/**
|
|
3
|
+
* Channel-specific payload for Feishu, carried in `ReplyPayload.channelData.feishu`.
|
|
4
|
+
*
|
|
5
|
+
* Callers (skills, tools, programmatic code) populate this structure to send
|
|
6
|
+
* Feishu-native content that the standard text/media path cannot express.
|
|
7
|
+
*
|
|
8
|
+
* Both card v1 (Message Card) and v2 (CardKit) formats are supported.
|
|
9
|
+
* The Feishu server distinguishes the version by the presence of `schema: "2.0"`.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* // --- v1 Message Card (default) ---
|
|
14
|
+
* const v1Reply: ReplyPayload = {
|
|
15
|
+
* channelData: {
|
|
16
|
+
* feishu: {
|
|
17
|
+
* card: {
|
|
18
|
+
* config: { wide_screen_mode: true },
|
|
19
|
+
* header: {
|
|
20
|
+
* title: { tag: "plain_text", content: "Task Created" },
|
|
21
|
+
* template: "green",
|
|
22
|
+
* },
|
|
23
|
+
* elements: [
|
|
24
|
+
* { tag: "div", text: { tag: "lark_md", content: "**Title:** Fix login bug" } },
|
|
25
|
+
* { tag: "action", actions: [
|
|
26
|
+
* { tag: "button", text: { tag: "plain_text", content: "View" }, type: "primary", url: "https://..." },
|
|
27
|
+
* ]},
|
|
28
|
+
* ],
|
|
29
|
+
* },
|
|
30
|
+
* },
|
|
31
|
+
* },
|
|
32
|
+
* };
|
|
33
|
+
*
|
|
34
|
+
* // --- v2 CardKit ---
|
|
35
|
+
* const v2Reply: ReplyPayload = {
|
|
36
|
+
* channelData: {
|
|
37
|
+
* feishu: {
|
|
38
|
+
* card: {
|
|
39
|
+
* schema: "2.0",
|
|
40
|
+
* config: { wide_screen_mode: true },
|
|
41
|
+
* header: {
|
|
42
|
+
* title: { tag: "plain_text", content: "Task Created" },
|
|
43
|
+
* template: "green",
|
|
44
|
+
* },
|
|
45
|
+
* body: {
|
|
46
|
+
* elements: [
|
|
47
|
+
* { tag: "markdown", content: "**Title:** Fix login bug" },
|
|
48
|
+
* ],
|
|
49
|
+
* },
|
|
50
|
+
* },
|
|
51
|
+
* },
|
|
52
|
+
* },
|
|
53
|
+
* };
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export interface FeishuChannelData {
|
|
57
|
+
/**
|
|
58
|
+
* A complete Feishu interactive card JSON object (v1 or v2).
|
|
59
|
+
*
|
|
60
|
+
* The card is sent as-is via `msg_type: "interactive"`. The Feishu server
|
|
61
|
+
* uses the presence of `schema: "2.0"` to determine the card version.
|
|
62
|
+
*
|
|
63
|
+
* **v1 (Message Card)** — default when no `schema` field is present.
|
|
64
|
+
* Top-level fields: `config`, `header`, `elements`.
|
|
65
|
+
* Element tags: `div`, `action`, `button`, `button_group`, `note`,
|
|
66
|
+
* `img`, `hr`, `column_set`, `markdown` (limited), `lark_md` (in div.text).
|
|
67
|
+
*
|
|
68
|
+
* **v2 (CardKit)** — activated by `schema: "2.0"`.
|
|
69
|
+
* Top-level fields: `schema`, `config`, `header`, `body.elements`.
|
|
70
|
+
* Element tags: `markdown`, `plain_text`, `hr`, `collapsible_panel`,
|
|
71
|
+
* `column_set`, `table`, `image`, `button`, `select_static`, `overflow`.
|
|
72
|
+
* Not supported in v2: `action`, `button_group`, `note`, `div` + `lark_md`.
|
|
73
|
+
*
|
|
74
|
+
* @see https://open.larkoffice.com/document/feishu-cards/card-json-v2-structure (v2)
|
|
75
|
+
* @see https://open.feishu.cn/document/uAjLw4CM/ukzMukzMukzM/feishu-cards/card-components (v1)
|
|
76
|
+
*/
|
|
77
|
+
card?: Record<string, unknown>;
|
|
78
|
+
}
|
|
79
|
+
export declare const feishuOutbound: ChannelOutboundAdapter;
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.feishuOutbound = void 0;
|
|
5
|
+
const lark_client_1 = require("../../core/lark-client.js");
|
|
6
|
+
const lark_logger_1 = require("../../core/lark-logger.js");
|
|
7
|
+
const targets_1 = require("../../core/targets.js");
|
|
8
|
+
const comment_target_1 = require("../../core/comment-target.js");
|
|
9
|
+
const synthetic_target_1 = require("../../core/synthetic-target.js");
|
|
10
|
+
const deliver_1 = require("./deliver.js");
|
|
11
|
+
const outbound_mention_1 = require("./outbound-mention.js");
|
|
12
|
+
const bot_peer_context_1 = require("./bot-peer-context.js");
|
|
13
|
+
/**
|
|
14
|
+
* Apply the outbound mention safety net for the current send:
|
|
15
|
+
* - rewrite LLM-emitted @-shapes into Feishu's standard <at> element
|
|
16
|
+
* - when the dispatch layer marked the current reply as bot→bot in a
|
|
17
|
+
* group (via `runWithBotPeerContext`), guarantee the peer bot is
|
|
18
|
+
* explicitly @-mentioned so Feishu delivers the message at all.
|
|
19
|
+
*/
|
|
20
|
+
function applyOutboundMentions(text, chatId) {
|
|
21
|
+
const normalized = (0, outbound_mention_1.normalizeOutboundMentions)(text, chatId);
|
|
22
|
+
const peer = (0, bot_peer_context_1.currentBotPeerContext)();
|
|
23
|
+
// De-dupe the peer @ across chunks: once it has appeared on one chunk
|
|
24
|
+
// (model-written or injected), later chunks of the same dispatch skip it.
|
|
25
|
+
// Mirrors normalizeFeishuOutboundText in send.ts so both outbound paths
|
|
26
|
+
// behave the same and a long multi-chunk reply doesn't @ the peer repeatedly.
|
|
27
|
+
if (!peer || peer.mentioned)
|
|
28
|
+
return normalized;
|
|
29
|
+
const out = (0, outbound_mention_1.ensureMention)(normalized, peer.peerOpenId, peer.peerName);
|
|
30
|
+
if (out.includes(`user_id="${peer.peerOpenId}"`)) {
|
|
31
|
+
peer.mentioned = true;
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
const log = (0, lark_logger_1.larkLogger)('outbound/outbound');
|
|
36
|
+
/**
|
|
37
|
+
* Map adapter-level parameters to internal send context.
|
|
38
|
+
*
|
|
39
|
+
* Mirrors the pattern used by Telegram (`resolveTelegramSendContext`) and
|
|
40
|
+
* Slack (`sendSlackOutboundMessage`) to centralise parameter mapping.
|
|
41
|
+
*/
|
|
42
|
+
function resolveFeishuSendContext(params) {
|
|
43
|
+
const routeTarget = (0, targets_1.parseFeishuRouteTarget)(params.to);
|
|
44
|
+
const explicitThreadId = params.threadId != null && String(params.threadId).trim() !== '' ? String(params.threadId).trim() : undefined;
|
|
45
|
+
const explicitReplyToId = params.replyToId?.trim() || undefined;
|
|
46
|
+
const replyToMessageId = explicitReplyToId ?? routeTarget.replyToMessageId;
|
|
47
|
+
const replyInThread = Boolean(explicitThreadId ?? routeTarget.threadId);
|
|
48
|
+
if (!explicitReplyToId && routeTarget.replyToMessageId) {
|
|
49
|
+
log.info('resolved reply target from encoded originating route');
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
cfg: params.cfg,
|
|
53
|
+
to: routeTarget.target,
|
|
54
|
+
replyToMessageId,
|
|
55
|
+
replyInThread,
|
|
56
|
+
threadId: explicitThreadId,
|
|
57
|
+
accountId: params.accountId ?? undefined,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Adapter
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
exports.feishuOutbound = {
|
|
64
|
+
deliveryMode: 'direct',
|
|
65
|
+
chunker: (text, limit) => lark_client_1.LarkClient.runtime.channel.text.chunkMarkdownText(text, limit),
|
|
66
|
+
chunkerMode: 'markdown',
|
|
67
|
+
textChunkLimit: 15000,
|
|
68
|
+
sendText: async ({ cfg, to, text, accountId, replyToId, threadId }) => {
|
|
69
|
+
log.info(`sendText: target=${to}, textLength=${text.length}`);
|
|
70
|
+
// Synthetic targets (e.g. VC meeting-invited) have no real IM peer —
|
|
71
|
+
// drop the send silently so the agent pipeline stays uniform without
|
|
72
|
+
// producing unsolicited DMs. See core/synthetic-target.ts.
|
|
73
|
+
if ((0, synthetic_target_1.isSyntheticTarget)(to)) {
|
|
74
|
+
log.debug(`sendText: synthetic target ${to}, dropping outbound IM send`);
|
|
75
|
+
return { channel: 'feishu', messageId: '', chatId: to };
|
|
76
|
+
}
|
|
77
|
+
// Comment thread routing — route replies through Drive comment API
|
|
78
|
+
if ((0, comment_target_1.isCommentTarget)(to)) {
|
|
79
|
+
log.info(`sendText: detected comment target, routing through Drive comment API`);
|
|
80
|
+
const result = await (0, deliver_1.sendCommentReplyLark)({ cfg, to, text, accountId: accountId ?? undefined });
|
|
81
|
+
return { channel: 'feishu', ...result };
|
|
82
|
+
}
|
|
83
|
+
const ctx = resolveFeishuSendContext({ cfg, to, accountId, replyToId, threadId });
|
|
84
|
+
const finalText = applyOutboundMentions(text, ctx.to);
|
|
85
|
+
const result = await (0, deliver_1.sendTextLark)({ ...ctx, to: ctx.to, text: finalText });
|
|
86
|
+
return { channel: 'feishu', ...result };
|
|
87
|
+
},
|
|
88
|
+
sendMedia: async ({ cfg, to, text, mediaUrl, mediaLocalRoots, accountId, replyToId, threadId }) => {
|
|
89
|
+
log.info(`sendMedia: target=${to}, ` + `hasText=${Boolean(text?.trim())}, mediaUrl=${mediaUrl ?? '(none)'}`);
|
|
90
|
+
// Synthetic targets — drop silently (see sendText for rationale).
|
|
91
|
+
if ((0, synthetic_target_1.isSyntheticTarget)(to)) {
|
|
92
|
+
log.debug(`sendMedia: synthetic target ${to}, dropping outbound IM send`);
|
|
93
|
+
return { channel: 'feishu', messageId: '', chatId: to };
|
|
94
|
+
}
|
|
95
|
+
// Comment thread routing — send text (with media URL appended) via Drive comment API
|
|
96
|
+
if ((0, comment_target_1.isCommentTarget)(to)) {
|
|
97
|
+
log.info(`sendMedia: detected comment target, routing through Drive comment API`);
|
|
98
|
+
const parts = [];
|
|
99
|
+
if (text?.trim())
|
|
100
|
+
parts.push(text.trim());
|
|
101
|
+
if (mediaUrl)
|
|
102
|
+
parts.push(`📎 ${mediaUrl}`);
|
|
103
|
+
const combinedText = parts.join('\n') || '(media)';
|
|
104
|
+
const result = await (0, deliver_1.sendCommentReplyLark)({ cfg, to, text: combinedText, accountId: accountId ?? undefined });
|
|
105
|
+
return { channel: 'feishu', ...result };
|
|
106
|
+
}
|
|
107
|
+
const ctx = resolveFeishuSendContext({ cfg, to, accountId, replyToId, threadId });
|
|
108
|
+
const normalizedCaption = text ? applyOutboundMentions(text, ctx.to) : text;
|
|
109
|
+
// Feishu media messages do not support inline captions — send text first.
|
|
110
|
+
// Capture the result so the no-mediaUrl path can return it without re-sending.
|
|
111
|
+
let captionResult;
|
|
112
|
+
if (normalizedCaption?.trim()) {
|
|
113
|
+
captionResult = await (0, deliver_1.sendTextLark)({ ...ctx, to: ctx.to, text: normalizedCaption });
|
|
114
|
+
}
|
|
115
|
+
// No mediaUrl — text-only flow.
|
|
116
|
+
if (!mediaUrl) {
|
|
117
|
+
log.info('sendMedia: no mediaUrl provided, falling back to text-only');
|
|
118
|
+
if (captionResult) {
|
|
119
|
+
// Caption was already sent above; return that result.
|
|
120
|
+
return { channel: 'feishu', ...captionResult };
|
|
121
|
+
}
|
|
122
|
+
// No caption text — send empty/raw text to satisfy the contract.
|
|
123
|
+
const result = await (0, deliver_1.sendTextLark)({ ...ctx, to: ctx.to, text: normalizedCaption ?? '' });
|
|
124
|
+
return { channel: 'feishu', ...result };
|
|
125
|
+
}
|
|
126
|
+
const result = await (0, deliver_1.sendMediaLark)({ ...ctx, to: ctx.to, mediaUrl, mediaLocalRoots });
|
|
127
|
+
return {
|
|
128
|
+
channel: 'feishu',
|
|
129
|
+
messageId: result.messageId,
|
|
130
|
+
chatId: result.chatId,
|
|
131
|
+
...(result.warning ? { meta: { warnings: [result.warning] } } : {}),
|
|
132
|
+
};
|
|
133
|
+
},
|
|
134
|
+
sendPayload: async ({ cfg, to, payload, mediaLocalRoots, accountId, replyToId, threadId }) => {
|
|
135
|
+
// Synthetic targets — drop silently (see sendText for rationale).
|
|
136
|
+
if ((0, synthetic_target_1.isSyntheticTarget)(to)) {
|
|
137
|
+
log.debug(`sendPayload: synthetic target ${to}, dropping outbound IM send`);
|
|
138
|
+
return { channel: 'feishu', messageId: '', chatId: to };
|
|
139
|
+
}
|
|
140
|
+
const ctx = resolveFeishuSendContext({ cfg, to, accountId, replyToId, threadId });
|
|
141
|
+
// --- channelData.feishu: card message support ---
|
|
142
|
+
const feishuData = payload.channelData?.feishu;
|
|
143
|
+
// --- Resolve text + media from payload ---
|
|
144
|
+
const text = payload.text ?? '';
|
|
145
|
+
const mediaUrls = payload.mediaUrls?.length ? payload.mediaUrls : payload.mediaUrl ? [payload.mediaUrl] : [];
|
|
146
|
+
log.info(`sendPayload: target=${to}, ` +
|
|
147
|
+
`textLength=${text.length}, mediaCount=${mediaUrls.length}, ` +
|
|
148
|
+
`hasCard=${Boolean(feishuData?.card)}`);
|
|
149
|
+
// --- channelData.feishu.card: card message path ---
|
|
150
|
+
// Feishu card messages are standalone (msg_type="interactive"), so
|
|
151
|
+
// text and media must be sent as separate messages around the card.
|
|
152
|
+
if (feishuData?.card) {
|
|
153
|
+
if (text.trim()) {
|
|
154
|
+
await (0, deliver_1.sendTextLark)({ ...ctx, to: ctx.to, text });
|
|
155
|
+
}
|
|
156
|
+
const cardResult = await (0, deliver_1.sendCardLark)({ ...ctx, to: ctx.to, card: feishuData.card });
|
|
157
|
+
const warnings = [];
|
|
158
|
+
for (const mediaUrl of mediaUrls) {
|
|
159
|
+
const mediaResult = await (0, deliver_1.sendMediaLark)({ ...ctx, to: ctx.to, mediaUrl, mediaLocalRoots });
|
|
160
|
+
if (mediaResult.warning) {
|
|
161
|
+
warnings.push(mediaResult.warning);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
channel: 'feishu',
|
|
166
|
+
messageId: cardResult.messageId,
|
|
167
|
+
chatId: cardResult.chatId,
|
|
168
|
+
...(warnings.length > 0 ? { meta: { warnings } } : {}),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
// --- Standard text + media orchestration (no card) ---
|
|
172
|
+
// No media: text-only
|
|
173
|
+
if (mediaUrls.length === 0) {
|
|
174
|
+
const result = await (0, deliver_1.sendTextLark)({ ...ctx, to: ctx.to, text });
|
|
175
|
+
return { channel: 'feishu', ...result };
|
|
176
|
+
}
|
|
177
|
+
// Has media: send leading text, then loop media URLs
|
|
178
|
+
if (text.trim()) {
|
|
179
|
+
await (0, deliver_1.sendTextLark)({ ...ctx, to: ctx.to, text });
|
|
180
|
+
}
|
|
181
|
+
const warnings = [];
|
|
182
|
+
let lastResult;
|
|
183
|
+
for (const mediaUrl of mediaUrls) {
|
|
184
|
+
lastResult = await (0, deliver_1.sendMediaLark)({ ...ctx, to: ctx.to, mediaUrl, mediaLocalRoots });
|
|
185
|
+
if (lastResult.warning) {
|
|
186
|
+
warnings.push(lastResult.warning);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
channel: 'feishu',
|
|
191
|
+
...(lastResult ?? { messageId: '', chatId: '' }),
|
|
192
|
+
...(warnings.length > 0 ? { meta: { warnings } } : {}),
|
|
193
|
+
};
|
|
194
|
+
},
|
|
195
|
+
};
|