@lark-project/openclaw-lark-project 2026.3.131
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 +80 -0
- package/README.zh.md +80 -0
- package/dist/index.js +172 -0
- package/dist/index.js.map +7 -0
- package/dist/skills/feishu-bitable/SKILL.md +248 -0
- package/dist/skills/feishu-bitable/references/examples.md +813 -0
- package/dist/skills/feishu-bitable/references/field-properties.md +763 -0
- package/dist/skills/feishu-bitable/references/record-values.md +911 -0
- package/dist/skills/feishu-calendar/SKILL.md +244 -0
- package/dist/skills/feishu-channel-rules/SKILL.md +18 -0
- package/dist/skills/feishu-channel-rules/references/markdown-syntax.md +138 -0
- package/dist/skills/feishu-create-doc/SKILL.md +719 -0
- package/dist/skills/feishu-fetch-doc/SKILL.md +93 -0
- package/dist/skills/feishu-im-read/SKILL.md +163 -0
- package/dist/skills/feishu-project/SKILL.md +122 -0
- package/dist/skills/feishu-task/SKILL.md +293 -0
- package/dist/skills/feishu-troubleshoot/SKILL.md +70 -0
- package/dist/skills/feishu-update-doc/SKILL.md +285 -0
- package/dist/src/card/builder.js +293 -0
- package/dist/src/card/builder.js.map +7 -0
- package/dist/src/card/cardkit.js +126 -0
- package/dist/src/card/cardkit.js.map +7 -0
- package/dist/src/card/flush-controller.js +107 -0
- package/dist/src/card/flush-controller.js.map +7 -0
- package/dist/src/card/markdown-style.js +57 -0
- package/dist/src/card/markdown-style.js.map +7 -0
- package/dist/src/card/reply-dispatcher-types.js +39 -0
- package/dist/src/card/reply-dispatcher-types.js.map +7 -0
- package/dist/src/card/reply-dispatcher.js +245 -0
- package/dist/src/card/reply-dispatcher.js.map +7 -0
- package/dist/src/card/reply-mode.js +29 -0
- package/dist/src/card/reply-mode.js.map +7 -0
- package/dist/src/card/streaming-card-controller.js +653 -0
- package/dist/src/card/streaming-card-controller.js.map +7 -0
- package/dist/src/card/unavailable-guard.js +76 -0
- package/dist/src/card/unavailable-guard.js.map +7 -0
- package/dist/src/channel/abort-detect.js +79 -0
- package/dist/src/channel/abort-detect.js.map +7 -0
- package/dist/src/channel/chat-queue.js +50 -0
- package/dist/src/channel/chat-queue.js.map +7 -0
- package/dist/src/channel/config-adapter.js +89 -0
- package/dist/src/channel/config-adapter.js.map +7 -0
- package/dist/src/channel/directory.js +133 -0
- package/dist/src/channel/directory.js.map +7 -0
- package/dist/src/channel/event-handlers.js +175 -0
- package/dist/src/channel/event-handlers.js.map +7 -0
- package/dist/src/channel/monitor.js +108 -0
- package/dist/src/channel/monitor.js.map +7 -0
- package/dist/src/channel/onboarding-config.js +76 -0
- package/dist/src/channel/onboarding-config.js.map +7 -0
- package/dist/src/channel/onboarding-migrate.js +55 -0
- package/dist/src/channel/onboarding-migrate.js.map +7 -0
- package/dist/src/channel/onboarding.js +285 -0
- package/dist/src/channel/onboarding.js.map +7 -0
- package/dist/src/channel/plugin.js +260 -0
- package/dist/src/channel/plugin.js.map +7 -0
- package/dist/src/channel/probe.js +14 -0
- package/dist/src/channel/probe.js.map +7 -0
- package/dist/src/channel/types.js +1 -0
- package/dist/src/channel/types.js.map +7 -0
- package/dist/src/commands/auth.js +73 -0
- package/dist/src/commands/auth.js.map +7 -0
- package/dist/src/commands/diagnose.js +658 -0
- package/dist/src/commands/diagnose.js.map +7 -0
- package/dist/src/commands/doctor.js +327 -0
- package/dist/src/commands/doctor.js.map +7 -0
- package/dist/src/commands/index.js +124 -0
- package/dist/src/commands/index.js.map +7 -0
- package/dist/src/core/accounts.js +129 -0
- package/dist/src/core/accounts.js.map +7 -0
- package/dist/src/core/agent-config.js +60 -0
- package/dist/src/core/agent-config.js.map +7 -0
- package/dist/src/core/api-error.js +55 -0
- package/dist/src/core/api-error.js.map +7 -0
- package/dist/src/core/app-owner-fallback.js +17 -0
- package/dist/src/core/app-owner-fallback.js.map +7 -0
- package/dist/src/core/app-scope-checker.js +95 -0
- package/dist/src/core/app-scope-checker.js.map +7 -0
- package/dist/src/core/auth-errors.js +120 -0
- package/dist/src/core/auth-errors.js.map +7 -0
- package/dist/src/core/chat-info-cache.js +102 -0
- package/dist/src/core/chat-info-cache.js.map +7 -0
- package/dist/src/core/config-schema.js +150 -0
- package/dist/src/core/config-schema.js.map +7 -0
- package/dist/src/core/device-flow.js +174 -0
- package/dist/src/core/device-flow.js.map +7 -0
- package/dist/src/core/feishu-fetch.js +12 -0
- package/dist/src/core/feishu-fetch.js.map +7 -0
- package/dist/src/core/footer-config.js +16 -0
- package/dist/src/core/footer-config.js.map +7 -0
- package/dist/src/core/lark-client.js +322 -0
- package/dist/src/core/lark-client.js.map +7 -0
- package/dist/src/core/lark-logger.js +92 -0
- package/dist/src/core/lark-logger.js.map +7 -0
- package/dist/src/core/lark-ticket.js +18 -0
- package/dist/src/core/lark-ticket.js.map +7 -0
- package/dist/src/core/message-unavailable.js +119 -0
- package/dist/src/core/message-unavailable.js.map +7 -0
- package/dist/src/core/owner-policy.js +25 -0
- package/dist/src/core/owner-policy.js.map +7 -0
- package/dist/src/core/permission-url.js +37 -0
- package/dist/src/core/permission-url.js.map +7 -0
- package/dist/src/core/project-auth.js +177 -0
- package/dist/src/core/project-auth.js.map +7 -0
- package/dist/src/core/project-oauth-flow.js +124 -0
- package/dist/src/core/project-oauth-flow.js.map +7 -0
- package/dist/src/core/project-token-store.js +172 -0
- package/dist/src/core/project-token-store.js.map +7 -0
- package/dist/src/core/raw-request.js +45 -0
- package/dist/src/core/raw-request.js.map +7 -0
- package/dist/src/core/scope-manager.js +62 -0
- package/dist/src/core/scope-manager.js.map +7 -0
- package/dist/src/core/security-check.js +118 -0
- package/dist/src/core/security-check.js.map +7 -0
- package/dist/src/core/shutdown-hooks.js +37 -0
- package/dist/src/core/shutdown-hooks.js.map +7 -0
- package/dist/src/core/targets.js +55 -0
- package/dist/src/core/targets.js.map +7 -0
- package/dist/src/core/token-store.js +215 -0
- package/dist/src/core/token-store.js.map +7 -0
- package/dist/src/core/tool-client.js +335 -0
- package/dist/src/core/tool-client.js.map +7 -0
- package/dist/src/core/tool-scopes.js +207 -0
- package/dist/src/core/tool-scopes.js.map +7 -0
- package/dist/src/core/tools-config.js +57 -0
- package/dist/src/core/tools-config.js.map +7 -0
- package/dist/src/core/types.js +1 -0
- package/dist/src/core/types.js.map +7 -0
- package/dist/src/core/uat-client.js +124 -0
- package/dist/src/core/uat-client.js.map +7 -0
- package/dist/src/core/version.js +27 -0
- package/dist/src/core/version.js.map +7 -0
- package/dist/src/messaging/converters/audio.js +19 -0
- package/dist/src/messaging/converters/audio.js.map +7 -0
- package/dist/src/messaging/converters/calendar.js +46 -0
- package/dist/src/messaging/converters/calendar.js.map +7 -0
- package/dist/src/messaging/converters/content-converter.js +61 -0
- package/dist/src/messaging/converters/content-converter.js.map +7 -0
- package/dist/src/messaging/converters/file.js +18 -0
- package/dist/src/messaging/converters/file.js.map +7 -0
- package/dist/src/messaging/converters/folder.js +18 -0
- package/dist/src/messaging/converters/folder.js.map +7 -0
- package/dist/src/messaging/converters/hongbao.js +14 -0
- package/dist/src/messaging/converters/hongbao.js.map +7 -0
- package/dist/src/messaging/converters/image.js +16 -0
- package/dist/src/messaging/converters/image.js.map +7 -0
- package/dist/src/messaging/converters/index.js +48 -0
- package/dist/src/messaging/converters/index.js.map +7 -0
- package/dist/src/messaging/converters/interactive/card-converter.js +1040 -0
- package/dist/src/messaging/converters/interactive/card-converter.js.map +7 -0
- package/dist/src/messaging/converters/interactive/card-utils.js +36 -0
- package/dist/src/messaging/converters/interactive/card-utils.js.map +7 -0
- package/dist/src/messaging/converters/interactive/index.js +19 -0
- package/dist/src/messaging/converters/interactive/index.js.map +7 -0
- package/dist/src/messaging/converters/interactive/legacy.js +53 -0
- package/dist/src/messaging/converters/interactive/legacy.js.map +7 -0
- package/dist/src/messaging/converters/interactive/types.js +23 -0
- package/dist/src/messaging/converters/interactive/types.js.map +7 -0
- package/dist/src/messaging/converters/location.js +17 -0
- package/dist/src/messaging/converters/location.js.map +7 -0
- package/dist/src/messaging/converters/merge-forward.js +143 -0
- package/dist/src/messaging/converters/merge-forward.js.map +7 -0
- package/dist/src/messaging/converters/post.js +113 -0
- package/dist/src/messaging/converters/post.js.map +7 -0
- package/dist/src/messaging/converters/share.js +22 -0
- package/dist/src/messaging/converters/share.js.map +7 -0
- package/dist/src/messaging/converters/sticker.js +16 -0
- package/dist/src/messaging/converters/sticker.js.map +7 -0
- package/dist/src/messaging/converters/system.js +25 -0
- package/dist/src/messaging/converters/system.js.map +7 -0
- package/dist/src/messaging/converters/text.js +12 -0
- package/dist/src/messaging/converters/text.js.map +7 -0
- package/dist/src/messaging/converters/todo.js +37 -0
- package/dist/src/messaging/converters/todo.js.map +7 -0
- package/dist/src/messaging/converters/types.js +1 -0
- package/dist/src/messaging/converters/types.js.map +7 -0
- package/dist/src/messaging/converters/unknown.js +13 -0
- package/dist/src/messaging/converters/unknown.js.map +7 -0
- package/dist/src/messaging/converters/utils.js +35 -0
- package/dist/src/messaging/converters/utils.js.map +7 -0
- package/dist/src/messaging/converters/video-chat.js +21 -0
- package/dist/src/messaging/converters/video-chat.js.map +7 -0
- package/dist/src/messaging/converters/video.js +30 -0
- package/dist/src/messaging/converters/video.js.map +7 -0
- package/dist/src/messaging/converters/vote.js +24 -0
- package/dist/src/messaging/converters/vote.js.map +7 -0
- package/dist/src/messaging/inbound/dedup.js +82 -0
- package/dist/src/messaging/inbound/dedup.js.map +7 -0
- package/dist/src/messaging/inbound/dispatch-builders.js +98 -0
- package/dist/src/messaging/inbound/dispatch-builders.js.map +7 -0
- package/dist/src/messaging/inbound/dispatch-commands.js +94 -0
- package/dist/src/messaging/inbound/dispatch-commands.js.map +7 -0
- package/dist/src/messaging/inbound/dispatch-context.js +96 -0
- package/dist/src/messaging/inbound/dispatch-context.js.map +7 -0
- package/dist/src/messaging/inbound/dispatch.js +150 -0
- package/dist/src/messaging/inbound/dispatch.js.map +7 -0
- package/dist/src/messaging/inbound/enrich.js +137 -0
- package/dist/src/messaging/inbound/enrich.js.map +7 -0
- package/dist/src/messaging/inbound/gate-effects.js +28 -0
- package/dist/src/messaging/inbound/gate-effects.js.map +7 -0
- package/dist/src/messaging/inbound/gate.js +163 -0
- package/dist/src/messaging/inbound/gate.js.map +7 -0
- package/dist/src/messaging/inbound/handler.js +132 -0
- package/dist/src/messaging/inbound/handler.js.map +7 -0
- package/dist/src/messaging/inbound/media-resolver.js +70 -0
- package/dist/src/messaging/inbound/media-resolver.js.map +7 -0
- package/dist/src/messaging/inbound/mention.js +50 -0
- package/dist/src/messaging/inbound/mention.js.map +7 -0
- package/dist/src/messaging/inbound/parse-io.js +41 -0
- package/dist/src/messaging/inbound/parse-io.js.map +7 -0
- package/dist/src/messaging/inbound/parse.js +79 -0
- package/dist/src/messaging/inbound/parse.js.map +7 -0
- package/dist/src/messaging/inbound/permission.js +30 -0
- package/dist/src/messaging/inbound/permission.js.map +7 -0
- package/dist/src/messaging/inbound/policy.js +83 -0
- package/dist/src/messaging/inbound/policy.js.map +7 -0
- package/dist/src/messaging/inbound/reaction-handler.js +162 -0
- package/dist/src/messaging/inbound/reaction-handler.js.map +7 -0
- package/dist/src/messaging/inbound/user-name-cache.js +172 -0
- package/dist/src/messaging/inbound/user-name-cache.js.map +7 -0
- package/dist/src/messaging/outbound/actions.js +239 -0
- package/dist/src/messaging/outbound/actions.js.map +7 -0
- package/dist/src/messaging/outbound/chat-manage.js +74 -0
- package/dist/src/messaging/outbound/chat-manage.js.map +7 -0
- package/dist/src/messaging/outbound/deliver.js +162 -0
- package/dist/src/messaging/outbound/deliver.js.map +7 -0
- package/dist/src/messaging/outbound/fetch.js +7 -0
- package/dist/src/messaging/outbound/fetch.js.map +7 -0
- package/dist/src/messaging/outbound/forward.js +31 -0
- package/dist/src/messaging/outbound/forward.js.map +7 -0
- package/dist/src/messaging/outbound/media-url-utils.js +101 -0
- package/dist/src/messaging/outbound/media-url-utils.js.map +7 -0
- package/dist/src/messaging/outbound/media.js +463 -0
- package/dist/src/messaging/outbound/media.js.map +7 -0
- package/dist/src/messaging/outbound/outbound.js +95 -0
- package/dist/src/messaging/outbound/outbound.js.map +7 -0
- package/dist/src/messaging/outbound/reactions.js +312 -0
- package/dist/src/messaging/outbound/reactions.js.map +7 -0
- package/dist/src/messaging/outbound/send.js +194 -0
- package/dist/src/messaging/outbound/send.js.map +7 -0
- package/dist/src/messaging/outbound/typing.js +77 -0
- package/dist/src/messaging/outbound/typing.js.map +7 -0
- package/dist/src/messaging/shared/message-lookup.js +84 -0
- package/dist/src/messaging/shared/message-lookup.js.map +7 -0
- package/dist/src/messaging/types.js +1 -0
- package/dist/src/messaging/types.js.map +7 -0
- package/dist/src/tools/auto-auth.js +714 -0
- package/dist/src/tools/auto-auth.js.map +7 -0
- package/dist/src/tools/helpers.js +133 -0
- package/dist/src/tools/helpers.js.map +7 -0
- package/dist/src/tools/mcp/doc/create.js +35 -0
- package/dist/src/tools/mcp/doc/create.js.map +7 -0
- package/dist/src/tools/mcp/doc/fetch.js +33 -0
- package/dist/src/tools/mcp/doc/fetch.js.map +7 -0
- package/dist/src/tools/mcp/doc/index.js +32 -0
- package/dist/src/tools/mcp/doc/index.js.map +7 -0
- package/dist/src/tools/mcp/doc/update.js +61 -0
- package/dist/src/tools/mcp/doc/update.js.map +7 -0
- package/dist/src/tools/mcp/project/endpoint.js +25 -0
- package/dist/src/tools/mcp/project/endpoint.js.map +7 -0
- package/dist/src/tools/mcp/project/index.js +27 -0
- package/dist/src/tools/mcp/project/index.js.map +7 -0
- package/dist/src/tools/mcp/project/tools.js +579 -0
- package/dist/src/tools/mcp/project/tools.js.map +7 -0
- package/dist/src/tools/mcp/shared.js +170 -0
- package/dist/src/tools/mcp/shared.js.map +7 -0
- package/dist/src/tools/oapi/bitable/app-table-field.js +244 -0
- package/dist/src/tools/oapi/bitable/app-table-field.js.map +7 -0
- package/dist/src/tools/oapi/bitable/app-table-record.js +501 -0
- package/dist/src/tools/oapi/bitable/app-table-record.js.map +7 -0
- package/dist/src/tools/oapi/bitable/app-table-view.js +226 -0
- package/dist/src/tools/oapi/bitable/app-table-view.js.map +7 -0
- package/dist/src/tools/oapi/bitable/app-table.js +278 -0
- package/dist/src/tools/oapi/bitable/app-table.js.map +7 -0
- package/dist/src/tools/oapi/bitable/app.js +200 -0
- package/dist/src/tools/oapi/bitable/app.js.map +7 -0
- package/dist/src/tools/oapi/bitable/index.js +13 -0
- package/dist/src/tools/oapi/bitable/index.js.map +7 -0
- package/dist/src/tools/oapi/calendar/calendar.js +131 -0
- package/dist/src/tools/oapi/calendar/calendar.js.map +7 -0
- package/dist/src/tools/oapi/calendar/event-attendee.js +301 -0
- package/dist/src/tools/oapi/calendar/event-attendee.js.map +7 -0
- package/dist/src/tools/oapi/calendar/event.js +834 -0
- package/dist/src/tools/oapi/calendar/event.js.map +7 -0
- package/dist/src/tools/oapi/calendar/freebusy.js +111 -0
- package/dist/src/tools/oapi/calendar/freebusy.js.map +7 -0
- package/dist/src/tools/oapi/calendar/index.js +11 -0
- package/dist/src/tools/oapi/calendar/index.js.map +7 -0
- package/dist/src/tools/oapi/chat/chat.js +132 -0
- package/dist/src/tools/oapi/chat/chat.js.map +7 -0
- package/dist/src/tools/oapi/chat/index.js +11 -0
- package/dist/src/tools/oapi/chat/index.js.map +7 -0
- package/dist/src/tools/oapi/chat/members.js +83 -0
- package/dist/src/tools/oapi/chat/members.js.map +7 -0
- package/dist/src/tools/oapi/common/get-user.js +95 -0
- package/dist/src/tools/oapi/common/get-user.js.map +7 -0
- package/dist/src/tools/oapi/common/index.js +7 -0
- package/dist/src/tools/oapi/common/index.js.map +7 -0
- package/dist/src/tools/oapi/common/search-user.js +67 -0
- package/dist/src/tools/oapi/common/search-user.js.map +7 -0
- package/dist/src/tools/oapi/drive/doc-comments.js +310 -0
- package/dist/src/tools/oapi/drive/doc-comments.js.map +7 -0
- package/dist/src/tools/oapi/drive/doc-media.js +314 -0
- package/dist/src/tools/oapi/drive/doc-media.js.map +7 -0
- package/dist/src/tools/oapi/drive/file.js +548 -0
- package/dist/src/tools/oapi/drive/file.js.map +7 -0
- package/dist/src/tools/oapi/drive/index.js +29 -0
- package/dist/src/tools/oapi/drive/index.js.map +7 -0
- package/dist/src/tools/oapi/helpers.js +199 -0
- package/dist/src/tools/oapi/helpers.js.map +7 -0
- package/dist/src/tools/oapi/im/format-messages.js +128 -0
- package/dist/src/tools/oapi/im/format-messages.js.map +7 -0
- package/dist/src/tools/oapi/im/index.js +15 -0
- package/dist/src/tools/oapi/im/index.js.map +7 -0
- package/dist/src/tools/oapi/im/message-read.js +404 -0
- package/dist/src/tools/oapi/im/message-read.js.map +7 -0
- package/dist/src/tools/oapi/im/message.js +179 -0
- package/dist/src/tools/oapi/im/message.js.map +7 -0
- package/dist/src/tools/oapi/im/resource.js +126 -0
- package/dist/src/tools/oapi/im/resource.js.map +7 -0
- package/dist/src/tools/oapi/im/time-utils.js +169 -0
- package/dist/src/tools/oapi/im/time-utils.js.map +7 -0
- package/dist/src/tools/oapi/im/user-name-uat.js +103 -0
- package/dist/src/tools/oapi/im/user-name-uat.js.map +7 -0
- package/dist/src/tools/oapi/index.js +56 -0
- package/dist/src/tools/oapi/index.js.map +7 -0
- package/dist/src/tools/oapi/sdk-types.js +1 -0
- package/dist/src/tools/oapi/sdk-types.js.map +7 -0
- package/dist/src/tools/oapi/search/doc-search.js +215 -0
- package/dist/src/tools/oapi/search/doc-search.js.map +7 -0
- package/dist/src/tools/oapi/search/index.js +25 -0
- package/dist/src/tools/oapi/search/index.js.map +7 -0
- package/dist/src/tools/oapi/sheets/index.js +25 -0
- package/dist/src/tools/oapi/sheets/index.js.map +7 -0
- package/dist/src/tools/oapi/sheets/sheet.js +652 -0
- package/dist/src/tools/oapi/sheets/sheet.js.map +7 -0
- package/dist/src/tools/oapi/task/comment.js +151 -0
- package/dist/src/tools/oapi/task/comment.js.map +7 -0
- package/dist/src/tools/oapi/task/index.js +11 -0
- package/dist/src/tools/oapi/task/index.js.map +7 -0
- package/dist/src/tools/oapi/task/subtask.js +175 -0
- package/dist/src/tools/oapi/task/subtask.js.map +7 -0
- package/dist/src/tools/oapi/task/task.js +405 -0
- package/dist/src/tools/oapi/task/task.js.map +7 -0
- package/dist/src/tools/oapi/task/tasklist.js +366 -0
- package/dist/src/tools/oapi/task/tasklist.js.map +7 -0
- package/dist/src/tools/oapi/wiki/index.js +27 -0
- package/dist/src/tools/oapi/wiki/index.js.map +7 -0
- package/dist/src/tools/oapi/wiki/space-node.js +311 -0
- package/dist/src/tools/oapi/wiki/space-node.js.map +7 -0
- package/dist/src/tools/oapi/wiki/space.js +148 -0
- package/dist/src/tools/oapi/wiki/space.js.map +7 -0
- package/dist/src/tools/oauth-batch-auth.js +125 -0
- package/dist/src/tools/oauth-batch-auth.js.map +7 -0
- package/dist/src/tools/oauth-cards.js +269 -0
- package/dist/src/tools/oauth-cards.js.map +7 -0
- package/dist/src/tools/oauth.js +538 -0
- package/dist/src/tools/oauth.js.map +7 -0
- package/dist/src/tools/onboarding-auth.js +101 -0
- package/dist/src/tools/onboarding-auth.js.map +7 -0
- package/dist/src/tools/project-oauth.js +305 -0
- package/dist/src/tools/project-oauth.js.map +7 -0
- package/dist/src/tools/tat/im/index.js +9 -0
- package/dist/src/tools/tat/im/index.js.map +7 -0
- package/dist/src/tools/tat/im/resource.js +123 -0
- package/dist/src/tools/tat/im/resource.js.map +7 -0
- package/package.json +64 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/messaging/inbound/reaction-handler.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * Reaction event handler for the Lark/Feishu channel plugin.\n *\n * Handles `im.message.reaction.created_v1` events by building a\n * {@link MessageContext} directly and dispatching to the agent via\n * {@link dispatchToAgent}, bypassing the full 7-stage message pipeline.\n *\n * Controlled by `reactionNotifications` (default: \"own\"):\n * - `\"off\"` \u2014 reaction events are silently ignored.\n * - `\"own\"` \u2014 only reactions on the bot's own messages are dispatched.\n * - `\"all\"` \u2014 reactions on any message in the chat are dispatched.\n */\n\nimport * as crypto from 'node:crypto';\nimport type { ClawdbotConfig, RuntimeEnv, HistoryEntry } from 'openclaw/plugin-sdk';\nimport { DEFAULT_GROUP_HISTORY_LIMIT } from 'openclaw/plugin-sdk';\nimport type { FeishuReactionCreatedEvent } from '../types';\nimport type { MessageContext } from '../types';\nimport { getLarkAccount } from '../../core/accounts';\nimport { getMessageFeishu, type FeishuMessageInfo } from '../shared/message-lookup';\nimport { isThreadCapableGroup, getChatTypeFeishu } from '../../core/chat-info-cache';\nimport { resolveUserName } from './user-name-cache';\nimport { dispatchToAgent } from './dispatch';\nimport { resolveFeishuGroupConfig } from './policy';\nimport { larkLogger } from '../../core/lark-logger';\n\nconst logger = larkLogger('inbound/reaction-handler');\n\nconst REACTION_VERIFY_TIMEOUT_MS = 3_000;\n\n// ---------------------------------------------------------------------------\n// Pre-resolved reaction context\n// ---------------------------------------------------------------------------\n\nexport interface ReactionContext {\n /** Real chatId (from message API, or `p2p:${operatorOpenId}` fallback). */\n chatId: string;\n /** Resolved chat type. */\n chatType: 'p2p' | 'group';\n /** Thread ID from the fetched message, if any. */\n threadId?: string;\n /** Whether the chat is thread-capable (topic or thread-mode group). */\n threadCapable?: boolean;\n /** Fetched message info used to build the synthetic event. */\n msg: FeishuMessageInfo;\n}\n\n/**\n * Pre-resolve reaction context before enqueuing.\n *\n * Performs account config checks, safety filters, API fetch of the\n * original message, ownership verification, chat type resolution, and\n * thread-capable detection. Returns `null` when the reaction should\n * be skipped (mode off, safety filter, timeout, ownership mismatch,\n * thread-capable group with threadSession enabled).\n *\n * This function is intentionally separated so that the caller\n * (event-handlers.ts) can resolve the real chatId *before* enqueuing,\n * ensuring the reaction shares the same queue key as normal messages\n * for the same chat.\n */\nexport async function resolveReactionContext(params: {\n cfg: ClawdbotConfig;\n event: FeishuReactionCreatedEvent;\n botOpenId?: string;\n runtime?: RuntimeEnv;\n accountId?: string;\n}): Promise<ReactionContext | null> {\n const { cfg, event, botOpenId, runtime, accountId } = params;\n const log = runtime?.log ?? ((...args: unknown[]) => logger.info(args.map(String).join(' ')));\n\n const account = getLarkAccount(cfg, accountId);\n const reactionMode = account.config?.reactionNotifications ?? 'own';\n\n if (reactionMode === 'off') {\n return null;\n }\n\n const emojiType = event.reaction_type?.emoji_type;\n const messageId = event.message_id;\n const operatorOpenId = event.user_id?.open_id ?? '';\n\n if (!emojiType || !messageId || !operatorOpenId) {\n return null;\n }\n\n // ---- Safety filters (aligned with official) ----\n if (event.operator_type === 'app' || operatorOpenId === botOpenId) {\n log(`feishu[${accountId}]: ignoring app/self reaction on ${messageId}`);\n return null;\n }\n\n if (emojiType === 'Typing') {\n return null;\n }\n\n // \"own\" mode requires botOpenId to verify message ownership\n if (reactionMode === 'own' && !botOpenId) {\n log(`feishu[${accountId}]: bot open_id unavailable, skipping reaction on ${messageId}`);\n return null;\n }\n\n // ---- Fetch original message with timeout (fail-closed) ----\n const msg = await Promise.race([\n getMessageFeishu({ cfg, messageId, accountId }),\n new Promise<null>((resolve) => setTimeout(() => resolve(null), REACTION_VERIFY_TIMEOUT_MS)),\n ]).catch(() => null);\n\n if (!msg) {\n log(`feishu[${accountId}]: reacted message ${messageId} not found or timed out, skipping`);\n return null;\n }\n\n // The mget API returns app_id (cli_xxx) as sender.id for bot messages,\n // not the bot's open_id (ou_xxx). Match against the account's appId.\n const isBotMessage = msg.senderType === 'app' && msg.senderId === account.appId;\n if (reactionMode === 'own' && !isBotMessage) {\n log(\n `feishu[${accountId}]: reaction on non-bot message ${messageId}, skipping (senderId=${msg.senderId}, senderType=${msg.senderType}, botOpenId=${botOpenId}, appId=${account.appId})`,\n );\n return null;\n }\n\n // ---- Resolve effective chatId ----\n const rawChatId = event.chat_id?.trim() || msg.chatId?.trim() || '';\n const effectiveChatId = rawChatId || `p2p:${operatorOpenId}`;\n\n // ---- Resolve chat type ----\n // im.message.reaction.created_v1 does NOT include chat_id or chat_type\n // (confirmed from Feishu docs). The message GET API returns chat_id but\n // NOT chat_type. So we must determine chat_type via im.chat.get.\n //\n // Determine chat type: event payload \u2192 fetched message \u2192 im.chat.get API.\n // The first two sources are almost always empty for reaction events, so\n // getChatTypeFeishu is the primary path.\n let chatType: 'p2p' | 'group' =\n event.chat_type === 'group'\n ? 'group'\n : event.chat_type === 'p2p' || event.chat_type === 'private'\n ? 'p2p'\n : msg.chatType === 'group' || msg.chatType === 'p2p'\n ? (msg.chatType as 'p2p' | 'group')\n : 'p2p'; // tentative default, overridden below when chatId is available\n\n // When we have a real chat_id (from event or message API), query the\n // authoritative chat type via im.chat.get. This is the only reliable\n // source for reaction events.\n if (rawChatId && chatType === 'p2p' && !event.chat_type && !msg.chatType) {\n try {\n chatType = await getChatTypeFeishu({ cfg, chatId: rawChatId, accountId });\n } catch {\n // getChatTypeFeishu already logs errors and defaults to \"p2p\"\n }\n }\n\n // ---- Thread session: skip for thread-capable groups ----\n // The mget API does not return thread_id, so we cannot route the\n // synthetic event to the correct thread session. Skip reaction handling\n // only for thread-capable groups (topic / thread-mode); p2p and regular\n // groups are unaffected since they have no threads.\n let threadCapable = false;\n const threadSessionEnabled = account.config?.threadSession === true;\n if (rawChatId && chatType === 'group') {\n threadCapable = await isThreadCapableGroup({ cfg, chatId: rawChatId, accountId });\n if (threadSessionEnabled && threadCapable) {\n log(`feishu[${accountId}]: reaction on thread-capable group ${rawChatId}, skipping (threadSession enabled)`);\n return null;\n }\n }\n\n return {\n chatId: effectiveChatId,\n chatType,\n threadId: msg.threadId,\n threadCapable,\n msg,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport async function handleFeishuReaction(params: {\n cfg: ClawdbotConfig;\n event: FeishuReactionCreatedEvent;\n botOpenId?: string;\n runtime?: RuntimeEnv;\n chatHistories?: Map<string, HistoryEntry[]>;\n accountId?: string;\n /** Pre-resolved context from resolveReactionContext(). */\n preResolved: ReactionContext;\n}): Promise<void> {\n const { cfg, event, runtime, chatHistories, accountId, preResolved } = params;\n const log = runtime?.log ?? ((...args: unknown[]) => logger.info(args.map(String).join(' ')));\n const error = runtime?.error ?? ((...args: unknown[]) => logger.error(args.map(String).join(' ')));\n\n const emojiType = event.reaction_type?.emoji_type!;\n const messageId = event.message_id;\n const operatorOpenId = event.user_id?.open_id ?? '';\n\n // ---- Step A: Account resolution + accountScopedCfg ----\n const account = getLarkAccount(cfg, accountId);\n const accountFeishuCfg = account.config;\n const accountScopedCfg: ClawdbotConfig = {\n ...cfg,\n channels: { ...cfg.channels, feishu: accountFeishuCfg },\n };\n\n // ---- Step B: Build MessageContext directly ----\n const excerpt =\n preResolved.msg.content.length > 200 ? preResolved.msg.content.slice(0, 200) + '\u2026' : preResolved.msg.content;\n const syntheticText = excerpt\n ? `[reacted with ${emojiType} to message ${messageId}: \"${excerpt}\"]`\n : `[reacted with ${emojiType} to message ${messageId}]`;\n const syntheticMessageId = `${messageId}:reaction:${emojiType}:${crypto.randomUUID()}`;\n\n let ctx: MessageContext = {\n chatId: preResolved.chatId,\n messageId: syntheticMessageId,\n senderId: operatorOpenId,\n chatType: preResolved.chatType,\n content: syntheticText,\n contentType: 'text',\n resources: [],\n mentions: [],\n threadId: preResolved.threadId,\n rawMessage: {\n message_id: syntheticMessageId,\n chat_id: preResolved.chatId,\n chat_type: preResolved.chatType,\n message_type: 'text',\n content: JSON.stringify({ text: syntheticText }),\n create_time: event.action_time ?? String(Date.now()),\n thread_id: preResolved.threadId,\n },\n rawSender: {\n sender_id: {\n open_id: operatorOpenId,\n user_id: event.user_id?.user_id,\n union_id: event.user_id?.union_id,\n },\n sender_type: 'user',\n },\n };\n\n // ---- Step C: Sender name resolution ----\n const senderResult = await resolveUserName({ account, openId: operatorOpenId, log });\n if (senderResult.name) {\n ctx = { ...ctx, senderName: senderResult.name };\n }\n\n log(\n `feishu[${accountId}]: reaction \"${emojiType}\" by ${operatorOpenId} on ${messageId} (chatId=${preResolved.chatId}, chatType=${preResolved.chatType}${preResolved.threadId ? `, thread=${preResolved.threadId}` : ''}), dispatching to AI`,\n );\n logger.info(`reaction \"${emojiType}\" by ${operatorOpenId} on ${messageId} (chatType=${preResolved.chatType})`);\n\n // ---- Step D: Group config resolution ----\n const isGroup = ctx.chatType === 'group';\n const groupConfig = isGroup ? resolveFeishuGroupConfig({ cfg: accountFeishuCfg, groupId: ctx.chatId }) : undefined;\n const defaultGroupConfig = isGroup ? accountFeishuCfg?.groups?.['*'] : undefined;\n\n const historyLimit = Math.max(\n 0,\n accountFeishuCfg?.historyLimit ?? accountScopedCfg.messages?.groupChat?.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT,\n );\n\n // ---- Step E: Dispatch directly to agent ----\n try {\n await dispatchToAgent({\n ctx,\n permissionError: undefined,\n mediaPayload: {},\n quotedContent: undefined,\n account,\n accountScopedCfg,\n runtime,\n chatHistories,\n historyLimit,\n replyToMessageId: messageId,\n commandAuthorized: false,\n groupConfig,\n defaultGroupConfig,\n skipTyping: true,\n });\n } catch (err) {\n error(`feishu[${accountId}]: error dispatching reaction event: ${String(err)}`);\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { LarkClient } from "../../core/lark-client";
|
|
2
|
+
import { extractPermissionError } from "./permission";
|
|
3
|
+
const DEFAULT_MAX_SIZE = 500;
|
|
4
|
+
const DEFAULT_TTL_MS = 30 * 60 * 1e3;
|
|
5
|
+
class UserNameCache {
|
|
6
|
+
map = /* @__PURE__ */ new Map();
|
|
7
|
+
maxSize;
|
|
8
|
+
ttlMs;
|
|
9
|
+
constructor(maxSize = DEFAULT_MAX_SIZE, ttlMs = DEFAULT_TTL_MS) {
|
|
10
|
+
this.maxSize = maxSize;
|
|
11
|
+
this.ttlMs = ttlMs;
|
|
12
|
+
}
|
|
13
|
+
/** Check whether the cache holds a (possibly empty) entry for this openId. */
|
|
14
|
+
has(openId) {
|
|
15
|
+
const entry = this.map.get(openId);
|
|
16
|
+
if (!entry) return false;
|
|
17
|
+
if (entry.expireAt <= Date.now()) {
|
|
18
|
+
this.map.delete(openId);
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
/** Get a cached name (refreshes LRU position). Returns `undefined` on miss or expiry. */
|
|
24
|
+
get(openId) {
|
|
25
|
+
const entry = this.map.get(openId);
|
|
26
|
+
if (!entry) return void 0;
|
|
27
|
+
if (entry.expireAt <= Date.now()) {
|
|
28
|
+
this.map.delete(openId);
|
|
29
|
+
return void 0;
|
|
30
|
+
}
|
|
31
|
+
this.map.delete(openId);
|
|
32
|
+
this.map.set(openId, entry);
|
|
33
|
+
return entry.name;
|
|
34
|
+
}
|
|
35
|
+
/** Write a single entry (evicts oldest if over capacity). */
|
|
36
|
+
set(openId, name) {
|
|
37
|
+
this.map.delete(openId);
|
|
38
|
+
this.map.set(openId, { name, expireAt: Date.now() + this.ttlMs });
|
|
39
|
+
this.evict();
|
|
40
|
+
}
|
|
41
|
+
/** Write multiple entries at once. */
|
|
42
|
+
setMany(entries) {
|
|
43
|
+
for (const [openId, name] of entries) {
|
|
44
|
+
this.map.delete(openId);
|
|
45
|
+
this.map.set(openId, { name, expireAt: Date.now() + this.ttlMs });
|
|
46
|
+
}
|
|
47
|
+
this.evict();
|
|
48
|
+
}
|
|
49
|
+
/** Return openIds that are NOT present (or expired) in the cache. */
|
|
50
|
+
filterMissing(openIds) {
|
|
51
|
+
return openIds.filter((id) => !this.has(id));
|
|
52
|
+
}
|
|
53
|
+
/** Bulk read — returns a Map of openId→name for all hits (including empty-string names). */
|
|
54
|
+
getMany(openIds) {
|
|
55
|
+
const result = /* @__PURE__ */ new Map();
|
|
56
|
+
for (const id of openIds) {
|
|
57
|
+
if (this.has(id)) {
|
|
58
|
+
result.set(id, this.get(id) ?? "");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
/** Clear all entries. */
|
|
64
|
+
clear() {
|
|
65
|
+
this.map.clear();
|
|
66
|
+
}
|
|
67
|
+
evict() {
|
|
68
|
+
while (this.map.size > this.maxSize) {
|
|
69
|
+
const oldest = this.map.keys().next().value;
|
|
70
|
+
if (oldest !== void 0) this.map.delete(oldest);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const registry = /* @__PURE__ */ new Map();
|
|
75
|
+
function getUserNameCache(accountId) {
|
|
76
|
+
let c = registry.get(accountId);
|
|
77
|
+
if (!c) {
|
|
78
|
+
c = new UserNameCache();
|
|
79
|
+
registry.set(accountId, c);
|
|
80
|
+
}
|
|
81
|
+
return c;
|
|
82
|
+
}
|
|
83
|
+
function clearUserNameCache(accountId) {
|
|
84
|
+
if (accountId !== void 0) {
|
|
85
|
+
registry.get(accountId)?.clear();
|
|
86
|
+
registry.delete(accountId);
|
|
87
|
+
} else {
|
|
88
|
+
for (const c of registry.values()) c.clear();
|
|
89
|
+
registry.clear();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const BATCH_SIZE = 50;
|
|
93
|
+
async function batchResolveUserNames(params) {
|
|
94
|
+
const { account, openIds, log } = params;
|
|
95
|
+
if (!account.configured || openIds.length === 0) {
|
|
96
|
+
return /* @__PURE__ */ new Map();
|
|
97
|
+
}
|
|
98
|
+
const cache = getUserNameCache(account.accountId);
|
|
99
|
+
const result = cache.getMany(openIds);
|
|
100
|
+
const missing = [...new Set(cache.filterMissing(openIds))];
|
|
101
|
+
if (missing.length === 0) return result;
|
|
102
|
+
const client = LarkClient.fromAccount(account).sdk;
|
|
103
|
+
for (let i = 0; i < missing.length; i += BATCH_SIZE) {
|
|
104
|
+
const chunk = missing.slice(i, i + BATCH_SIZE);
|
|
105
|
+
try {
|
|
106
|
+
const res = await client.contact.user.batch({
|
|
107
|
+
params: {
|
|
108
|
+
user_ids: chunk,
|
|
109
|
+
user_id_type: "open_id"
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
const items = res?.data?.items ?? [];
|
|
113
|
+
const resolved = /* @__PURE__ */ new Set();
|
|
114
|
+
for (const item of items) {
|
|
115
|
+
const openId = item.open_id;
|
|
116
|
+
if (!openId) continue;
|
|
117
|
+
const name = item.name || item.display_name || item.nickname || item.en_name || "";
|
|
118
|
+
cache.set(openId, name);
|
|
119
|
+
result.set(openId, name);
|
|
120
|
+
resolved.add(openId);
|
|
121
|
+
}
|
|
122
|
+
for (const id of chunk) {
|
|
123
|
+
if (!resolved.has(id)) {
|
|
124
|
+
cache.set(id, "");
|
|
125
|
+
result.set(id, "");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} catch (err) {
|
|
129
|
+
log(`batchResolveUserNames: failed: ${String(err)}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
function createBatchResolveNames(account, log) {
|
|
135
|
+
return async (openIds) => {
|
|
136
|
+
await batchResolveUserNames({ account, openIds, log });
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
async function resolveUserName(params) {
|
|
140
|
+
const { account, openId, log } = params;
|
|
141
|
+
if (!account.configured || !openId) return {};
|
|
142
|
+
const cache = getUserNameCache(account.accountId);
|
|
143
|
+
if (cache.has(openId)) return { name: cache.get(openId) ?? "" };
|
|
144
|
+
try {
|
|
145
|
+
const client = LarkClient.fromAccount(account).sdk;
|
|
146
|
+
const res = await client.contact.user.get({
|
|
147
|
+
path: { user_id: openId },
|
|
148
|
+
params: { user_id_type: "open_id" }
|
|
149
|
+
});
|
|
150
|
+
const name = res?.data?.user?.name || res?.data?.user?.display_name || res?.data?.user?.nickname || res?.data?.user?.en_name || "";
|
|
151
|
+
cache.set(openId, name);
|
|
152
|
+
return { name: name || void 0 };
|
|
153
|
+
} catch (err) {
|
|
154
|
+
const permErr = extractPermissionError(err);
|
|
155
|
+
if (permErr) {
|
|
156
|
+
log(`feishu: permission error resolving user name: code=${permErr.code}`);
|
|
157
|
+
cache.set(openId, "");
|
|
158
|
+
return { permissionError: permErr };
|
|
159
|
+
}
|
|
160
|
+
log(`feishu: failed to resolve user name for ${openId}: ${String(err)}`);
|
|
161
|
+
return {};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
export {
|
|
165
|
+
UserNameCache,
|
|
166
|
+
batchResolveUserNames,
|
|
167
|
+
clearUserNameCache,
|
|
168
|
+
createBatchResolveNames,
|
|
169
|
+
getUserNameCache,
|
|
170
|
+
resolveUserName
|
|
171
|
+
};
|
|
172
|
+
//# sourceMappingURL=user-name-cache.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/messaging/inbound/user-name-cache.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * Account-scoped LRU cache for Feishu user display names.\n *\n * Provides:\n * - `UserNameCache` \u2014 per-account LRU Map with TTL\n * - `getUserNameCache(accountId)` \u2014 singleton registry\n * - `batchResolveUserNames()` \u2014 batch API via `contact/v3/users/batch`\n * - `resolveUserName()` \u2014 single-user fallback via `contact.user.get`\n * - `clearUserNameCache()` \u2014 teardown hook (called from LarkClient.clearCache)\n */\n\nimport type { LarkAccount } from '../../core/types';\nimport { LarkClient } from '../../core/lark-client';\nimport { extractPermissionError, type PermissionError } from './permission';\n\n// ---------------------------------------------------------------------------\n// UserNameCache\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_MAX_SIZE = 500;\nconst DEFAULT_TTL_MS = 30 * 60 * 1000; // 30 minutes\n\ninterface CacheEntry {\n name: string;\n expireAt: number;\n}\n\nexport class UserNameCache {\n private map = new Map<string, CacheEntry>();\n private maxSize: number;\n private ttlMs: number;\n\n constructor(maxSize = DEFAULT_MAX_SIZE, ttlMs = DEFAULT_TTL_MS) {\n this.maxSize = maxSize;\n this.ttlMs = ttlMs;\n }\n\n /** Check whether the cache holds a (possibly empty) entry for this openId. */\n has(openId: string): boolean {\n const entry = this.map.get(openId);\n if (!entry) return false;\n if (entry.expireAt <= Date.now()) {\n this.map.delete(openId);\n return false;\n }\n return true;\n }\n\n /** Get a cached name (refreshes LRU position). Returns `undefined` on miss or expiry. */\n get(openId: string): string | undefined {\n const entry = this.map.get(openId);\n if (!entry) return undefined;\n if (entry.expireAt <= Date.now()) {\n this.map.delete(openId);\n return undefined;\n }\n // LRU refresh: delete + re-insert to move to end\n this.map.delete(openId);\n this.map.set(openId, entry);\n return entry.name;\n }\n\n /** Write a single entry (evicts oldest if over capacity). */\n set(openId: string, name: string): void {\n this.map.delete(openId); // ensure fresh insertion order\n this.map.set(openId, { name, expireAt: Date.now() + this.ttlMs });\n this.evict();\n }\n\n /** Write multiple entries at once. */\n setMany(entries: Iterable<[string, string]>): void {\n for (const [openId, name] of entries) {\n this.map.delete(openId);\n this.map.set(openId, { name, expireAt: Date.now() + this.ttlMs });\n }\n this.evict();\n }\n\n /** Return openIds that are NOT present (or expired) in the cache. */\n filterMissing(openIds: string[]): string[] {\n return openIds.filter((id) => !this.has(id));\n }\n\n /** Bulk read \u2014 returns a Map of openId\u2192name for all hits (including empty-string names). */\n getMany(openIds: string[]): Map<string, string> {\n const result = new Map<string, string>();\n for (const id of openIds) {\n if (this.has(id)) {\n result.set(id, this.get(id) ?? '');\n }\n }\n return result;\n }\n\n /** Clear all entries. */\n clear(): void {\n this.map.clear();\n }\n\n private evict(): void {\n while (this.map.size > this.maxSize) {\n // Map iterator yields in insertion order \u2014 first key is the oldest\n const oldest = this.map.keys().next().value;\n if (oldest !== undefined) this.map.delete(oldest);\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Account-scoped singleton registry\n// ---------------------------------------------------------------------------\n\nconst registry = new Map<string, UserNameCache>();\n\n/** Get (or create) the UserNameCache for a given account. */\nexport function getUserNameCache(accountId: string): UserNameCache {\n let c = registry.get(accountId);\n if (!c) {\n c = new UserNameCache();\n registry.set(accountId, c);\n }\n return c;\n}\n\n/**\n * Clear user-name caches.\n * - With `accountId`: clear that single cache.\n * - Without: clear all caches.\n */\nexport function clearUserNameCache(accountId?: string): void {\n if (accountId !== undefined) {\n registry.get(accountId)?.clear();\n registry.delete(accountId);\n } else {\n for (const c of registry.values()) c.clear();\n registry.clear();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Batch resolve via contact/v3/users/batch\n// ---------------------------------------------------------------------------\n\n/** Max user_ids per API call (Feishu limit). */\nconst BATCH_SIZE = 50;\n\n/**\n * Batch-resolve user display names.\n *\n * 1. Check cache \u2192 collect misses\n * 2. Deduplicate\n * 3. Call `GET /open-apis/contact/v3/users/batch` in chunks of 50\n * 4. Write results back to cache\n * 5. Return full Map<openId, name> (cache hits + API results)\n *\n * Best-effort: API errors are logged but never thrown.\n */\nexport async function batchResolveUserNames(params: {\n account: LarkAccount;\n openIds: string[];\n log: (...args: unknown[]) => void;\n}): Promise<Map<string, string>> {\n const { account, openIds, log } = params;\n if (!account.configured || openIds.length === 0) {\n return new Map();\n }\n\n const cache = getUserNameCache(account.accountId);\n const result = cache.getMany(openIds);\n\n // Deduplicate missing IDs\n const missing = [...new Set(cache.filterMissing(openIds))];\n if (missing.length === 0) return result;\n\n const client = LarkClient.fromAccount(account).sdk;\n\n // Split into chunks of BATCH_SIZE and call SDK method\n for (let i = 0; i < missing.length; i += BATCH_SIZE) {\n const chunk = missing.slice(i, i + BATCH_SIZE);\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const res: any = await client.contact.user.batch({\n params: {\n user_ids: chunk,\n user_id_type: 'open_id',\n },\n });\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const items: any[] = res?.data?.items ?? [];\n const resolved = new Set<string>();\n for (const item of items) {\n const openId: string | undefined = item.open_id;\n if (!openId) continue;\n const name: string = item.name || item.display_name || item.nickname || item.en_name || '';\n cache.set(openId, name);\n result.set(openId, name);\n resolved.add(openId);\n }\n // Cache empty names for IDs the API didn't return (no permission, etc.)\n for (const id of chunk) {\n if (!resolved.has(id)) {\n cache.set(id, '');\n result.set(id, '');\n }\n }\n } catch (err) {\n log(`batchResolveUserNames: failed: ${String(err)}`);\n }\n }\n\n return result;\n}\n\n/**\n * Create a `batchResolveNames` callback for use in `ConvertContext`.\n *\n * The returned function calls `batchResolveUserNames` with the given\n * account and log function, populating the TAT user-name cache.\n */\nexport function createBatchResolveNames(\n account: LarkAccount,\n log: (...args: unknown[]) => void,\n): (openIds: string[]) => Promise<void> {\n return async (openIds) => {\n await batchResolveUserNames({ account, openIds, log });\n };\n}\n\n// ---------------------------------------------------------------------------\n// Single-user resolve (fallback)\n// ---------------------------------------------------------------------------\n\nexport interface ResolveUserNameResult {\n name?: string;\n permissionError?: PermissionError;\n}\n\n/**\n * Resolve a single user's display name.\n *\n * Checks the account-scoped cache first, then falls back to the\n * `contact.user.get` API (same as the old `resolveFeishuSenderName`).\n */\nexport async function resolveUserName(params: {\n account: LarkAccount;\n openId: string;\n log: (...args: unknown[]) => void;\n}): Promise<ResolveUserNameResult> {\n const { account, openId, log } = params;\n if (!account.configured || !openId) return {};\n\n const cache = getUserNameCache(account.accountId);\n if (cache.has(openId)) return { name: cache.get(openId) ?? '' };\n\n try {\n const client = LarkClient.fromAccount(account).sdk;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const res: any = await client.contact.user.get({\n path: { user_id: openId },\n params: { user_id_type: 'open_id' },\n });\n\n const name: string =\n res?.data?.user?.name ||\n res?.data?.user?.display_name ||\n res?.data?.user?.nickname ||\n res?.data?.user?.en_name ||\n '';\n\n // Cache even empty names to avoid repeated API calls for users\n // whose names we cannot resolve (e.g. due to permissions).\n cache.set(openId, name);\n return { name: name || undefined };\n } catch (err) {\n const permErr = extractPermissionError(err);\n if (permErr) {\n log(`feishu: permission error resolving user name: code=${permErr.code}`);\n // Cache empty name so we don't retry a known-failing openId\n cache.set(openId, '');\n return { permissionError: permErr };\n }\n log(`feishu: failed to resolve user name for ${openId}: ${String(err)}`);\n return {};\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { extractToolSend, jsonResult, readStringParam, readReactionParams } from "openclaw/plugin-sdk";
|
|
2
|
+
import { addReactionFeishu, removeReactionFeishu, listReactionsFeishu } from "./reactions";
|
|
3
|
+
import { sendTextLark, sendCardLark } from "./deliver";
|
|
4
|
+
import { uploadAndSendMediaLark } from "./media";
|
|
5
|
+
import { LarkClient } from "../../core/lark-client";
|
|
6
|
+
import { getEnabledLarkAccounts } from "../../core/accounts";
|
|
7
|
+
import { larkLogger } from "../../core/lark-logger";
|
|
8
|
+
const log = larkLogger("outbound/actions");
|
|
9
|
+
function assertLarkOk(res, context) {
|
|
10
|
+
const code = res?.code;
|
|
11
|
+
if (code !== void 0 && code !== 0) {
|
|
12
|
+
const msg = res?.msg ?? "unknown error";
|
|
13
|
+
throw new Error(`[feishu-actions] ${context}: code=${code}, msg=${msg}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const SUPPORTED_ACTIONS = /* @__PURE__ */ new Set([
|
|
17
|
+
"send",
|
|
18
|
+
"react",
|
|
19
|
+
"reactions",
|
|
20
|
+
"delete",
|
|
21
|
+
"unsend"
|
|
22
|
+
// "member-info",
|
|
23
|
+
]);
|
|
24
|
+
function parseCardParam(raw) {
|
|
25
|
+
if (raw == null) return void 0;
|
|
26
|
+
if (typeof raw === "object" && !Array.isArray(raw)) {
|
|
27
|
+
return raw;
|
|
28
|
+
}
|
|
29
|
+
if (typeof raw === "string") {
|
|
30
|
+
const trimmed = raw.trim();
|
|
31
|
+
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
|
|
32
|
+
log.warn("params.card is a string but not a JSON object, ignoring");
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const parsed = JSON.parse(trimmed);
|
|
37
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
38
|
+
log.info("params.card was a JSON string, parsed successfully");
|
|
39
|
+
return parsed;
|
|
40
|
+
}
|
|
41
|
+
log.warn("params.card JSON parsed but is not a plain object, ignoring");
|
|
42
|
+
return void 0;
|
|
43
|
+
} catch {
|
|
44
|
+
log.warn("params.card is a string but failed to JSON.parse, ignoring");
|
|
45
|
+
return void 0;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
log.warn(`params.card has unexpected type "${typeof raw}", ignoring`);
|
|
49
|
+
return void 0;
|
|
50
|
+
}
|
|
51
|
+
function readFeishuSendParams(params, toolContext) {
|
|
52
|
+
const to = readStringParam(params, "to") ?? "";
|
|
53
|
+
const text = readStringParam(params, "message", { allowEmpty: true }) ?? readStringParam(params, "text", { allowEmpty: true }) ?? "";
|
|
54
|
+
const mediaUrl = readStringParam(params, "media") ?? readStringParam(params, "path") ?? readStringParam(params, "filePath") ?? readStringParam(params, "url");
|
|
55
|
+
const fileName = readStringParam(params, "fileName") ?? readStringParam(params, "name");
|
|
56
|
+
const sameChat = !to || to === toolContext?.currentChannelId;
|
|
57
|
+
const replyInThread = sameChat && Boolean(toolContext?.currentThreadTs);
|
|
58
|
+
const replyToMessageId = readStringParam(params, "replyTo") ?? (replyInThread && toolContext?.currentMessageId ? String(toolContext.currentMessageId) : void 0);
|
|
59
|
+
const card = parseCardParam(params.card);
|
|
60
|
+
return {
|
|
61
|
+
to,
|
|
62
|
+
text,
|
|
63
|
+
mediaUrl: mediaUrl ?? void 0,
|
|
64
|
+
fileName: fileName ?? void 0,
|
|
65
|
+
replyToMessageId: replyToMessageId ?? void 0,
|
|
66
|
+
replyInThread,
|
|
67
|
+
card
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const feishuMessageActions = {
|
|
71
|
+
listActions: ({ cfg }) => {
|
|
72
|
+
const accounts = getEnabledLarkAccounts(cfg);
|
|
73
|
+
if (accounts.length === 0) return [];
|
|
74
|
+
return Array.from(SUPPORTED_ACTIONS);
|
|
75
|
+
},
|
|
76
|
+
supportsAction: ({ action }) => SUPPORTED_ACTIONS.has(action),
|
|
77
|
+
supportsButtons: ({ cfg }) => getEnabledLarkAccounts(cfg).length > 0,
|
|
78
|
+
supportsCards: ({ cfg }) => getEnabledLarkAccounts(cfg).length > 0,
|
|
79
|
+
extractToolSend: ({ args }) => extractToolSend(args, "sendMessage"),
|
|
80
|
+
handleAction: async (ctx) => {
|
|
81
|
+
const { action, params, cfg, accountId, toolContext } = ctx;
|
|
82
|
+
const aid = accountId ?? void 0;
|
|
83
|
+
log.info(`handleAction: action=${action}, accountId=${aid ?? "default"}`);
|
|
84
|
+
try {
|
|
85
|
+
switch (action) {
|
|
86
|
+
case "send":
|
|
87
|
+
return await deliverMessage(cfg, readFeishuSendParams(params, toolContext), aid, ctx.mediaLocalRoots);
|
|
88
|
+
case "react":
|
|
89
|
+
return await handleReact(cfg, params, aid);
|
|
90
|
+
case "reactions":
|
|
91
|
+
return await handleReactions(cfg, params, aid);
|
|
92
|
+
case "delete":
|
|
93
|
+
case "unsend":
|
|
94
|
+
return await handleDelete(cfg, params, aid);
|
|
95
|
+
default:
|
|
96
|
+
throw new Error(
|
|
97
|
+
`Action "${action}" is not supported for Feishu. Supported actions: ${Array.from(SUPPORTED_ACTIONS).join(", ")}.`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
} catch (err) {
|
|
101
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
102
|
+
log.error(`handleAction failed: action=${action}, error=${errMsg}`);
|
|
103
|
+
throw err;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
async function deliverMessage(cfg, sp, accountId, mediaLocalRoots) {
|
|
108
|
+
const { to, text, mediaUrl, fileName, replyToMessageId, replyInThread, card } = sp;
|
|
109
|
+
const payloadType = card ? "card" : mediaUrl ? "media" : "text";
|
|
110
|
+
const target = to || replyToMessageId || "unknown";
|
|
111
|
+
log.info(
|
|
112
|
+
`deliverMessage: type=${payloadType}, target=${target}, isReply=${Boolean(replyToMessageId)}, replyInThread=${replyInThread}, textLen=${text.trim().length}, hasMedia=${Boolean(mediaUrl)}, fileName=${fileName ?? "(none)"}`
|
|
113
|
+
);
|
|
114
|
+
if (!text.trim() && !card && !mediaUrl) {
|
|
115
|
+
log.warn("deliverMessage: no payload, rejecting");
|
|
116
|
+
throw new Error("send requires at least one of: message, card, or media.");
|
|
117
|
+
}
|
|
118
|
+
const sendCtx = { cfg, to, replyToMessageId, replyInThread, accountId };
|
|
119
|
+
if (text.trim() && (card || mediaUrl)) {
|
|
120
|
+
log.info(`deliverMessage: sending preceding text (${text.length} chars) before ${payloadType}`);
|
|
121
|
+
await sendTextLark({ ...sendCtx, text });
|
|
122
|
+
}
|
|
123
|
+
if (card) {
|
|
124
|
+
const result2 = await sendCardLark({ ...sendCtx, card });
|
|
125
|
+
log.info(`deliverMessage: card sent, messageId=${result2.messageId}`);
|
|
126
|
+
return jsonResult({ ok: true, messageId: result2.messageId, chatId: result2.chatId });
|
|
127
|
+
}
|
|
128
|
+
if (mediaUrl) {
|
|
129
|
+
return await deliverMedia(cfg, sp, accountId, mediaLocalRoots);
|
|
130
|
+
}
|
|
131
|
+
const result = await sendTextLark({ ...sendCtx, text });
|
|
132
|
+
log.info(`deliverMessage: text sent, messageId=${result.messageId}`);
|
|
133
|
+
return jsonResult({ ok: true, messageId: result.messageId, chatId: result.chatId });
|
|
134
|
+
}
|
|
135
|
+
async function deliverMedia(cfg, sp, accountId, mediaLocalRoots) {
|
|
136
|
+
const { to, mediaUrl, fileName, replyToMessageId, replyInThread } = sp;
|
|
137
|
+
log.info(`deliverMedia: url=${mediaUrl}, fileName=${fileName ?? "(auto)"}`);
|
|
138
|
+
try {
|
|
139
|
+
const result = await uploadAndSendMediaLark({
|
|
140
|
+
cfg,
|
|
141
|
+
to,
|
|
142
|
+
mediaUrl,
|
|
143
|
+
fileName,
|
|
144
|
+
replyToMessageId,
|
|
145
|
+
replyInThread,
|
|
146
|
+
accountId,
|
|
147
|
+
mediaLocalRoots
|
|
148
|
+
});
|
|
149
|
+
log.info(`deliverMedia: sent, messageId=${result.messageId}`);
|
|
150
|
+
return jsonResult({ ok: true, messageId: result.messageId, chatId: result.chatId });
|
|
151
|
+
} catch (err) {
|
|
152
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
153
|
+
log.error(`deliverMedia: upload failed for "${mediaUrl}": ${errMsg}`);
|
|
154
|
+
log.info("deliverMedia: falling back to text link");
|
|
155
|
+
const fallback = await sendTextLark({
|
|
156
|
+
cfg,
|
|
157
|
+
to,
|
|
158
|
+
text: `> ${mediaUrl}`,
|
|
159
|
+
replyToMessageId,
|
|
160
|
+
replyInThread,
|
|
161
|
+
accountId
|
|
162
|
+
});
|
|
163
|
+
return jsonResult({
|
|
164
|
+
ok: true,
|
|
165
|
+
messageId: fallback.messageId,
|
|
166
|
+
chatId: fallback.chatId,
|
|
167
|
+
warning: `Media upload failed (${errMsg}). A text link was sent instead.`
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async function handleReact(cfg, params, accountId) {
|
|
172
|
+
const messageId = readStringParam(params, "messageId", { required: true });
|
|
173
|
+
const { emoji, remove, isEmpty } = readReactionParams(params, {
|
|
174
|
+
removeErrorMessage: "Emoji is required to remove a Feishu reaction."
|
|
175
|
+
});
|
|
176
|
+
if (remove || isEmpty) {
|
|
177
|
+
log.info(`react: removing emoji=${emoji || "all"} from messageId=${messageId}`);
|
|
178
|
+
const reactions = await listReactionsFeishu({
|
|
179
|
+
cfg,
|
|
180
|
+
messageId,
|
|
181
|
+
emojiType: emoji || void 0,
|
|
182
|
+
accountId
|
|
183
|
+
});
|
|
184
|
+
const botReactions = reactions.filter((r) => r.operatorType === "app");
|
|
185
|
+
for (const r of botReactions) {
|
|
186
|
+
await removeReactionFeishu({
|
|
187
|
+
cfg,
|
|
188
|
+
messageId,
|
|
189
|
+
reactionId: r.reactionId,
|
|
190
|
+
accountId
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
log.info(`react: removed ${botReactions.length} bot reaction(s)`);
|
|
194
|
+
return jsonResult({ ok: true, removed: botReactions.length });
|
|
195
|
+
}
|
|
196
|
+
log.info(`react: adding emoji=${emoji} to messageId=${messageId}`);
|
|
197
|
+
const { reactionId } = await addReactionFeishu({
|
|
198
|
+
cfg,
|
|
199
|
+
messageId,
|
|
200
|
+
emojiType: emoji,
|
|
201
|
+
accountId
|
|
202
|
+
});
|
|
203
|
+
log.info(`react: added reactionId=${reactionId}`);
|
|
204
|
+
return jsonResult({ ok: true, reactionId });
|
|
205
|
+
}
|
|
206
|
+
async function handleReactions(cfg, params, accountId) {
|
|
207
|
+
const messageId = readStringParam(params, "messageId", { required: true });
|
|
208
|
+
const emojiType = readStringParam(params, "emoji");
|
|
209
|
+
const reactions = await listReactionsFeishu({
|
|
210
|
+
cfg,
|
|
211
|
+
messageId,
|
|
212
|
+
emojiType: emojiType || void 0,
|
|
213
|
+
accountId
|
|
214
|
+
});
|
|
215
|
+
return jsonResult({
|
|
216
|
+
ok: true,
|
|
217
|
+
reactions: reactions.map((r) => ({
|
|
218
|
+
reactionId: r.reactionId,
|
|
219
|
+
emoji: r.emojiType,
|
|
220
|
+
operatorType: r.operatorType,
|
|
221
|
+
operatorId: r.operatorId
|
|
222
|
+
}))
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
async function handleDelete(cfg, params, accountId) {
|
|
226
|
+
const messageId = readStringParam(params, "messageId", { required: true });
|
|
227
|
+
log.info(`delete: messageId=${messageId}`);
|
|
228
|
+
const client = LarkClient.fromCfg(cfg, accountId).sdk;
|
|
229
|
+
const res = await client.im.message.delete({
|
|
230
|
+
path: { message_id: messageId }
|
|
231
|
+
});
|
|
232
|
+
assertLarkOk(res, `delete message ${messageId}`);
|
|
233
|
+
log.info(`delete: done, messageId=${messageId}`);
|
|
234
|
+
return jsonResult({ ok: true, messageId, deleted: true });
|
|
235
|
+
}
|
|
236
|
+
export {
|
|
237
|
+
feishuMessageActions
|
|
238
|
+
};
|
|
239
|
+
//# sourceMappingURL=actions.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/messaging/outbound/actions.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * ChannelMessageActionAdapter for the Lark/Feishu channel plugin.\n *\n * Implements the standard message-action interface so the framework's\n * built-in `message` tool can route send, react, delete and other\n * actions to Feishu.\n *\n * The `send` action is the unified entry-point for text, card, media,\n * reply and attachment delivery \u2014 matching the Telegram/Discord pattern\n * where a single action handles all outbound message types.\n */\n\nimport type {\n ChannelMessageActionAdapter,\n ChannelMessageActionName,\n ChannelThreadingToolContext,\n OpenClawConfig,\n} from 'openclaw/plugin-sdk';\nimport { extractToolSend, jsonResult, readStringParam, readReactionParams } from 'openclaw/plugin-sdk';\n\nimport { addReactionFeishu, removeReactionFeishu, listReactionsFeishu } from './reactions';\nimport { sendTextLark, sendCardLark } from './deliver';\nimport { uploadAndSendMediaLark } from './media';\nimport { LarkClient } from '../../core/lark-client';\nimport { getEnabledLarkAccounts } from '../../core/accounts';\nimport { larkLogger } from '../../core/lark-logger';\n\nconst log = larkLogger('outbound/actions');\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Assert that a Lark SDK response has code === 0 (or no code field). */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction assertLarkOk(res: any, context: string): void {\n const code = res?.code;\n if (code !== undefined && code !== 0) {\n const msg = res?.msg ?? 'unknown error';\n throw new Error(`[feishu-actions] ${context}: code=${code}, msg=${msg}`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Supported actions\n// ---------------------------------------------------------------------------\n\nconst SUPPORTED_ACTIONS: Set<ChannelMessageActionName> = new Set([\n 'send',\n 'react',\n 'reactions',\n 'delete',\n 'unsend',\n // \"member-info\",\n]);\n\n// ---------------------------------------------------------------------------\n// Send param extraction\n// ---------------------------------------------------------------------------\n\n/** Try to resolve a card param to a plain object. Accepts objects directly or JSON strings. */\nfunction parseCardParam(raw: unknown): Record<string, unknown> | undefined {\n if (raw == null) return undefined;\n\n // Already a non-array object \u2014 use directly.\n if (typeof raw === 'object' && !Array.isArray(raw)) {\n return raw as Record<string, unknown>;\n }\n\n // String \u2014 attempt JSON.parse.\n if (typeof raw === 'string') {\n const trimmed = raw.trim();\n if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {\n log.warn('params.card is a string but not a JSON object, ignoring');\n return undefined;\n }\n try {\n const parsed: unknown = JSON.parse(trimmed);\n if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {\n log.info('params.card was a JSON string, parsed successfully');\n return parsed as Record<string, unknown>;\n }\n log.warn('params.card JSON parsed but is not a plain object, ignoring');\n return undefined;\n } catch {\n log.warn('params.card is a string but failed to JSON.parse, ignoring');\n return undefined;\n }\n }\n\n // Other types (number, boolean, etc.) \u2014 ignore with warning.\n log.warn(`params.card has unexpected type \"${typeof raw}\", ignoring`);\n return undefined;\n}\n\n/** Typed parameters extracted from a send action. */\ninterface FeishuSendParams {\n to: string;\n text: string;\n mediaUrl?: string;\n fileName?: string;\n replyToMessageId?: string;\n replyInThread: boolean;\n card?: Record<string, unknown>;\n}\n\n/**\n * Extract and normalise all send-related parameters from the raw action params.\n * When `toolContext` is provided, thread context is inherited so that replies\n * are routed to the correct thread.\n */\nfunction readFeishuSendParams(\n params: Record<string, unknown>,\n toolContext?: ChannelThreadingToolContext,\n): FeishuSendParams {\n const to = readStringParam(params, 'to') ?? '';\n\n const text =\n readStringParam(params, 'message', { allowEmpty: true }) ??\n readStringParam(params, 'text', { allowEmpty: true }) ??\n '';\n\n const mediaUrl =\n readStringParam(params, 'media') ??\n readStringParam(params, 'path') ??\n readStringParam(params, 'filePath') ??\n readStringParam(params, 'url');\n\n const fileName = readStringParam(params, 'fileName') ?? readStringParam(params, 'name');\n\n // Thread routing: when targeting the current chat (or unspecified),\n // inherit thread context from SDK toolContext.\n const sameChat = !to || to === toolContext?.currentChannelId;\n const replyInThread = sameChat && Boolean(toolContext?.currentThreadTs);\n\n const replyToMessageId =\n readStringParam(params, 'replyTo') ??\n (replyInThread && toolContext?.currentMessageId ? String(toolContext.currentMessageId) : undefined);\n\n const card = parseCardParam(params.card);\n\n return {\n to,\n text,\n mediaUrl: mediaUrl ?? undefined,\n fileName: fileName ?? undefined,\n replyToMessageId: replyToMessageId ?? undefined,\n replyInThread,\n card,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Adapter\n// ---------------------------------------------------------------------------\n\nexport const feishuMessageActions: ChannelMessageActionAdapter = {\n listActions: ({ cfg }) => {\n const accounts = getEnabledLarkAccounts(cfg);\n if (accounts.length === 0) return [];\n return Array.from(SUPPORTED_ACTIONS);\n },\n\n supportsAction: ({ action }) => SUPPORTED_ACTIONS.has(action),\n\n supportsButtons: ({ cfg }) => getEnabledLarkAccounts(cfg).length > 0,\n supportsCards: ({ cfg }) => getEnabledLarkAccounts(cfg).length > 0,\n\n extractToolSend: ({ args }) => extractToolSend(args, 'sendMessage'),\n\n handleAction: async (ctx) => {\n const { action, params, cfg, accountId, toolContext } = ctx;\n const aid = accountId ?? undefined;\n\n log.info(`handleAction: action=${action}, accountId=${aid ?? 'default'}`);\n\n try {\n switch (action) {\n case 'send':\n return await deliverMessage(cfg, readFeishuSendParams(params, toolContext), aid, ctx.mediaLocalRoots);\n case 'react':\n return await handleReact(cfg, params, aid);\n case 'reactions':\n return await handleReactions(cfg, params, aid);\n case 'delete':\n case 'unsend':\n return await handleDelete(cfg, params, aid);\n default:\n throw new Error(\n `Action \"${action}\" is not supported for Feishu. ` +\n `Supported actions: ${Array.from(SUPPORTED_ACTIONS).join(', ')}.`,\n );\n }\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`handleAction failed: action=${action}, error=${errMsg}`);\n throw err;\n }\n },\n};\n\n// ---------------------------------------------------------------------------\n// Unified message delivery\n// ---------------------------------------------------------------------------\n\n/**\n * Unified message delivery \u2014 handles text, card, and media payloads with\n * optional reply-to and thread routing.\n *\n * Supports `fileName` for named file uploads via `uploadAndSendMediaLark`.\n * On media upload failure, falls back to sending the URL as a text link.\n */\nasync function deliverMessage(\n cfg: OpenClawConfig,\n sp: FeishuSendParams,\n accountId?: string,\n mediaLocalRoots?: readonly string[],\n) {\n const { to, text, mediaUrl, fileName, replyToMessageId, replyInThread, card } = sp;\n\n const payloadType = card ? 'card' : mediaUrl ? 'media' : 'text';\n const target = to || replyToMessageId || 'unknown';\n\n log.info(\n `deliverMessage: type=${payloadType}, target=${target}, ` +\n `isReply=${Boolean(replyToMessageId)}, replyInThread=${replyInThread}, ` +\n `textLen=${text.trim().length}, hasMedia=${Boolean(mediaUrl)}, ` +\n `fileName=${fileName ?? '(none)'}`,\n );\n\n if (!text.trim() && !card && !mediaUrl) {\n log.warn('deliverMessage: no payload, rejecting');\n throw new Error('send requires at least one of: message, card, or media.');\n }\n\n const sendCtx = { cfg, to, replyToMessageId, replyInThread, accountId };\n\n // Send text first if both text and card/media are present.\n if (text.trim() && (card || mediaUrl)) {\n log.info(`deliverMessage: sending preceding text ` + `(${text.length} chars) before ${payloadType}`);\n await sendTextLark({ ...sendCtx, text });\n }\n\n // Card path.\n if (card) {\n const result = await sendCardLark({ ...sendCtx, card });\n log.info(`deliverMessage: card sent, messageId=${result.messageId}`);\n return jsonResult({ ok: true, messageId: result.messageId, chatId: result.chatId });\n }\n\n // Media path \u2014 uses uploadAndSendMediaLark directly to support fileName.\n if (mediaUrl) {\n return await deliverMedia(cfg, sp, accountId, mediaLocalRoots);\n }\n\n // Text-only path.\n const result = await sendTextLark({ ...sendCtx, text });\n log.info(`deliverMessage: text sent, messageId=${result.messageId}`);\n return jsonResult({ ok: true, messageId: result.messageId, chatId: result.chatId });\n}\n\n/**\n * Upload and send a media file with text-link fallback on failure.\n */\nasync function deliverMedia(\n cfg: OpenClawConfig,\n sp: FeishuSendParams,\n accountId?: string,\n mediaLocalRoots?: readonly string[],\n) {\n const { to, mediaUrl, fileName, replyToMessageId, replyInThread } = sp;\n\n log.info(`deliverMedia: url=${mediaUrl}, fileName=${fileName ?? '(auto)'}`);\n\n try {\n const result = await uploadAndSendMediaLark({\n cfg,\n to,\n mediaUrl,\n fileName,\n replyToMessageId,\n replyInThread,\n accountId,\n mediaLocalRoots,\n });\n log.info(`deliverMedia: sent, messageId=${result.messageId}`);\n return jsonResult({ ok: true, messageId: result.messageId, chatId: result.chatId });\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`deliverMedia: upload failed for \"${mediaUrl}\": ${errMsg}`);\n\n // Fallback: send the URL with error reason as a quote above.\n log.info('deliverMedia: falling back to text link');\n const fallback = await sendTextLark({\n cfg,\n to,\n text: `> ${mediaUrl}`,\n replyToMessageId,\n replyInThread,\n accountId,\n });\n\n return jsonResult({\n ok: true,\n messageId: fallback.messageId,\n chatId: fallback.chatId,\n warning: `Media upload failed (${errMsg}). A text link was sent instead.`,\n });\n }\n}\n\n// ---------------------------------------------------------------------------\n// Reaction handlers\n// ---------------------------------------------------------------------------\n\nasync function handleReact(cfg: OpenClawConfig, params: Record<string, unknown>, accountId?: string) {\n const messageId = readStringParam(params, 'messageId', { required: true });\n const { emoji, remove, isEmpty } = readReactionParams(params, {\n removeErrorMessage: 'Emoji is required to remove a Feishu reaction.',\n });\n\n if (remove || isEmpty) {\n log.info(`react: removing emoji=${emoji || 'all'} from messageId=${messageId}`);\n const reactions = await listReactionsFeishu({\n cfg,\n messageId,\n emojiType: emoji || undefined,\n accountId,\n });\n const botReactions = reactions.filter((r) => r.operatorType === 'app');\n for (const r of botReactions) {\n await removeReactionFeishu({\n cfg,\n messageId,\n reactionId: r.reactionId,\n accountId,\n });\n }\n log.info(`react: removed ${botReactions.length} bot reaction(s)`);\n return jsonResult({ ok: true, removed: botReactions.length });\n }\n\n log.info(`react: adding emoji=${emoji} to messageId=${messageId}`);\n const { reactionId } = await addReactionFeishu({\n cfg,\n messageId,\n emojiType: emoji,\n accountId,\n });\n log.info(`react: added reactionId=${reactionId}`);\n return jsonResult({ ok: true, reactionId });\n}\n\nasync function handleReactions(cfg: OpenClawConfig, params: Record<string, unknown>, accountId?: string) {\n const messageId = readStringParam(params, 'messageId', { required: true });\n const emojiType = readStringParam(params, 'emoji');\n\n const reactions = await listReactionsFeishu({\n cfg,\n messageId,\n emojiType: emojiType || undefined,\n accountId,\n });\n\n return jsonResult({\n ok: true,\n reactions: reactions.map((r) => ({\n reactionId: r.reactionId,\n emoji: r.emojiType,\n operatorType: r.operatorType,\n operatorId: r.operatorId,\n })),\n });\n}\n\n// ---------------------------------------------------------------------------\n// Delete\n// ---------------------------------------------------------------------------\n\nasync function handleDelete(cfg: OpenClawConfig, params: Record<string, unknown>, accountId?: string) {\n const messageId = readStringParam(params, 'messageId', { required: true });\n log.info(`delete: messageId=${messageId}`);\n const client = LarkClient.fromCfg(cfg, accountId).sdk;\n\n const res = await client.im.message.delete({\n path: { message_id: messageId },\n });\n assertLarkOk(res, `delete message ${messageId}`);\n\n log.info(`delete: done, messageId=${messageId}`);\n return jsonResult({ ok: true, messageId, deleted: true });\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["result"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { LarkClient } from "../../core/lark-client";
|
|
2
|
+
function assertLarkOk(res, context) {
|
|
3
|
+
const code = res?.code;
|
|
4
|
+
if (code !== void 0 && code !== 0) {
|
|
5
|
+
const msg = res?.msg ?? "unknown error";
|
|
6
|
+
throw new Error(`[feishu-chat-manage] ${context}: code=${code}, msg=${msg}`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
async function updateChatFeishu(params) {
|
|
10
|
+
const { cfg, chatId, name, avatar, accountId } = params;
|
|
11
|
+
const client = LarkClient.fromCfg(cfg, accountId).sdk;
|
|
12
|
+
const body = {};
|
|
13
|
+
if (name) body.name = name;
|
|
14
|
+
if (avatar) body.avatar = avatar;
|
|
15
|
+
const res = await client.im.chat.update({
|
|
16
|
+
path: { chat_id: chatId },
|
|
17
|
+
data: body
|
|
18
|
+
});
|
|
19
|
+
assertLarkOk(res, `updateChat for ${chatId}`);
|
|
20
|
+
}
|
|
21
|
+
async function addChatMembersFeishu(params) {
|
|
22
|
+
const { cfg, chatId, memberIds, accountId } = params;
|
|
23
|
+
const client = LarkClient.fromCfg(cfg, accountId).sdk;
|
|
24
|
+
const res = await client.im.v1.chatMembers.create({
|
|
25
|
+
path: { chat_id: chatId },
|
|
26
|
+
data: { id_list: memberIds },
|
|
27
|
+
params: { member_id_type: "open_id" }
|
|
28
|
+
});
|
|
29
|
+
assertLarkOk(res, `addChatMembers for ${chatId}`);
|
|
30
|
+
}
|
|
31
|
+
async function removeChatMembersFeishu(params) {
|
|
32
|
+
const { cfg, chatId, memberIds, accountId } = params;
|
|
33
|
+
const client = LarkClient.fromCfg(cfg, accountId).sdk;
|
|
34
|
+
const res = await client.im.v1.chatMembers.delete({
|
|
35
|
+
path: { chat_id: chatId },
|
|
36
|
+
data: { id_list: memberIds },
|
|
37
|
+
params: { member_id_type: "open_id" }
|
|
38
|
+
});
|
|
39
|
+
assertLarkOk(res, `removeChatMembers for ${chatId}`);
|
|
40
|
+
}
|
|
41
|
+
async function listChatMembersFeishu(params) {
|
|
42
|
+
const { cfg, chatId, accountId, pageToken } = params;
|
|
43
|
+
const client = LarkClient.fromCfg(cfg, accountId).sdk;
|
|
44
|
+
const response = await client.im.v1.chatMembers.get({
|
|
45
|
+
path: { chat_id: chatId },
|
|
46
|
+
params: {
|
|
47
|
+
member_id_type: "open_id",
|
|
48
|
+
page_size: 100,
|
|
49
|
+
...pageToken ? { page_token: pageToken } : {}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
assertLarkOk(response, `listChatMembers for ${chatId}`);
|
|
53
|
+
const members = [];
|
|
54
|
+
const items = response?.data?.items;
|
|
55
|
+
if (items && Array.isArray(items)) {
|
|
56
|
+
for (const item of items) {
|
|
57
|
+
members.push({
|
|
58
|
+
memberId: item.member_id ?? "",
|
|
59
|
+
name: item.name ?? "",
|
|
60
|
+
memberIdType: item.member_id_type ?? "open_id"
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const nextPageToken = response?.data?.page_token ?? void 0;
|
|
65
|
+
const hasMore = response?.data?.has_more === true && !!nextPageToken;
|
|
66
|
+
return { members, pageToken: nextPageToken, hasMore };
|
|
67
|
+
}
|
|
68
|
+
export {
|
|
69
|
+
addChatMembersFeishu,
|
|
70
|
+
listChatMembersFeishu,
|
|
71
|
+
removeChatMembersFeishu,
|
|
72
|
+
updateChatFeishu
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=chat-manage.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/messaging/outbound/chat-manage.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * Chat management for the Lark/Feishu channel plugin.\n *\n * Provides functions to update chat settings (name, avatar), manage\n * members (add, remove, list) using the IM Chat API.\n */\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type { OpenClawConfig } from 'openclaw/plugin-sdk';\nimport { LarkClient } from '../../core/lark-client';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface FeishuChatMember {\n /** Member ID (open_id by default). */\n memberId: string;\n /** Display name of the member. */\n name: string;\n /** ID type: \"open_id\", \"union_id\", or \"user_id\". */\n memberIdType: string;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Assert that a Lark SDK response has code === 0 (or no code field). */\nfunction assertLarkOk(res: any, context: string): void {\n const code = res?.code;\n if (code !== undefined && code !== 0) {\n const msg = res?.msg ?? 'unknown error';\n throw new Error(`[feishu-chat-manage] ${context}: code=${code}, msg=${msg}`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// updateChatFeishu\n// ---------------------------------------------------------------------------\n\n/**\n * Update chat settings such as name or avatar.\n */\nexport async function updateChatFeishu(params: {\n cfg: OpenClawConfig;\n chatId: string;\n name?: string;\n avatar?: string;\n accountId?: string;\n}): Promise<void> {\n const { cfg, chatId, name, avatar, accountId } = params;\n const client = LarkClient.fromCfg(cfg, accountId).sdk;\n\n const body: Record<string, unknown> = {};\n if (name) body.name = name;\n if (avatar) body.avatar = avatar;\n\n const res = await client.im.chat.update({\n path: { chat_id: chatId },\n data: body as any,\n });\n assertLarkOk(res, `updateChat for ${chatId}`);\n}\n\n// ---------------------------------------------------------------------------\n// addChatMembersFeishu\n// ---------------------------------------------------------------------------\n\n/**\n * Add members to a chat by their open_id list.\n */\nexport async function addChatMembersFeishu(params: {\n cfg: OpenClawConfig;\n chatId: string;\n memberIds: string[];\n accountId?: string;\n}): Promise<void> {\n const { cfg, chatId, memberIds, accountId } = params;\n const client = LarkClient.fromCfg(cfg, accountId).sdk;\n\n const res = await client.im.v1.chatMembers.create({\n path: { chat_id: chatId },\n data: { id_list: memberIds },\n params: { member_id_type: 'open_id' },\n });\n assertLarkOk(res, `addChatMembers for ${chatId}`);\n}\n\n// ---------------------------------------------------------------------------\n// removeChatMembersFeishu\n// ---------------------------------------------------------------------------\n\n/**\n * Remove members from a chat by their open_id list.\n */\nexport async function removeChatMembersFeishu(params: {\n cfg: OpenClawConfig;\n chatId: string;\n memberIds: string[];\n accountId?: string;\n}): Promise<void> {\n const { cfg, chatId, memberIds, accountId } = params;\n const client = LarkClient.fromCfg(cfg, accountId).sdk;\n\n const res = await client.im.v1.chatMembers.delete({\n path: { chat_id: chatId },\n data: { id_list: memberIds },\n params: { member_id_type: 'open_id' },\n });\n assertLarkOk(res, `removeChatMembers for ${chatId}`);\n}\n\n// ---------------------------------------------------------------------------\n// listChatMembersFeishu\n// ---------------------------------------------------------------------------\n\n/**\n * List members of a chat.\n *\n * Returns a single page (up to 100 members) to avoid unnecessary data\n * overhead for large groups. Use the returned `pageToken` to fetch\n * subsequent pages when needed.\n */\nexport async function listChatMembersFeishu(params: {\n cfg: OpenClawConfig;\n chatId: string;\n accountId?: string;\n /** Optional page token for pagination. */\n pageToken?: string;\n}): Promise<{ members: FeishuChatMember[]; pageToken?: string; hasMore: boolean }> {\n const { cfg, chatId, accountId, pageToken } = params;\n const client = LarkClient.fromCfg(cfg, accountId).sdk;\n\n const response = await client.im.v1.chatMembers.get({\n path: { chat_id: chatId },\n params: {\n member_id_type: 'open_id',\n page_size: 100,\n ...(pageToken ? { page_token: pageToken } : {}),\n },\n });\n assertLarkOk(response, `listChatMembers for ${chatId}`);\n\n const members: FeishuChatMember[] = [];\n const items = (response?.data as any)?.items;\n if (items && Array.isArray(items)) {\n for (const item of items) {\n members.push({\n memberId: item.member_id ?? '',\n name: item.name ?? '',\n memberIdType: item.member_id_type ?? 'open_id',\n });\n }\n }\n\n const nextPageToken: string | undefined = (response?.data as any)?.page_token ?? undefined;\n const hasMore = (response?.data as any)?.has_more === true && !!nextPageToken;\n\n return { members, pageToken: nextPageToken, hasMore };\n}\n"],
|
|
5
|
+
"mappings": "AAYA,SAAS,kBAAkB;AAoB3B,SAAS,aAAa,KAAU,SAAuB;AACrD,QAAM,OAAO,KAAK;AAClB,MAAI,SAAS,UAAa,SAAS,GAAG;AACpC,UAAM,MAAM,KAAK,OAAO;AACxB,UAAM,IAAI,MAAM,wBAAwB,OAAO,UAAU,IAAI,SAAS,GAAG,EAAE;AAAA,EAC7E;AACF;AASA,eAAsB,iBAAiB,QAMrB;AAChB,QAAM,EAAE,KAAK,QAAQ,MAAM,QAAQ,UAAU,IAAI;AACjD,QAAM,SAAS,WAAW,QAAQ,KAAK,SAAS,EAAE;AAElD,QAAM,OAAgC,CAAC;AACvC,MAAI,KAAM,MAAK,OAAO;AACtB,MAAI,OAAQ,MAAK,SAAS;AAE1B,QAAM,MAAM,MAAM,OAAO,GAAG,KAAK,OAAO;AAAA,IACtC,MAAM,EAAE,SAAS,OAAO;AAAA,IACxB,MAAM;AAAA,EACR,CAAC;AACD,eAAa,KAAK,kBAAkB,MAAM,EAAE;AAC9C;AASA,eAAsB,qBAAqB,QAKzB;AAChB,QAAM,EAAE,KAAK,QAAQ,WAAW,UAAU,IAAI;AAC9C,QAAM,SAAS,WAAW,QAAQ,KAAK,SAAS,EAAE;AAElD,QAAM,MAAM,MAAM,OAAO,GAAG,GAAG,YAAY,OAAO;AAAA,IAChD,MAAM,EAAE,SAAS,OAAO;AAAA,IACxB,MAAM,EAAE,SAAS,UAAU;AAAA,IAC3B,QAAQ,EAAE,gBAAgB,UAAU;AAAA,EACtC,CAAC;AACD,eAAa,KAAK,sBAAsB,MAAM,EAAE;AAClD;AASA,eAAsB,wBAAwB,QAK5B;AAChB,QAAM,EAAE,KAAK,QAAQ,WAAW,UAAU,IAAI;AAC9C,QAAM,SAAS,WAAW,QAAQ,KAAK,SAAS,EAAE;AAElD,QAAM,MAAM,MAAM,OAAO,GAAG,GAAG,YAAY,OAAO;AAAA,IAChD,MAAM,EAAE,SAAS,OAAO;AAAA,IACxB,MAAM,EAAE,SAAS,UAAU;AAAA,IAC3B,QAAQ,EAAE,gBAAgB,UAAU;AAAA,EACtC,CAAC;AACD,eAAa,KAAK,yBAAyB,MAAM,EAAE;AACrD;AAaA,eAAsB,sBAAsB,QAMuC;AACjF,QAAM,EAAE,KAAK,QAAQ,WAAW,UAAU,IAAI;AAC9C,QAAM,SAAS,WAAW,QAAQ,KAAK,SAAS,EAAE;AAElD,QAAM,WAAW,MAAM,OAAO,GAAG,GAAG,YAAY,IAAI;AAAA,IAClD,MAAM,EAAE,SAAS,OAAO;AAAA,IACxB,QAAQ;AAAA,MACN,gBAAgB;AAAA,MAChB,WAAW;AAAA,MACX,GAAI,YAAY,EAAE,YAAY,UAAU,IAAI,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AACD,eAAa,UAAU,uBAAuB,MAAM,EAAE;AAEtD,QAAM,UAA8B,CAAC;AACrC,QAAM,QAAS,UAAU,MAAc;AACvC,MAAI,SAAS,MAAM,QAAQ,KAAK,GAAG;AACjC,eAAW,QAAQ,OAAO;AACxB,cAAQ,KAAK;AAAA,QACX,UAAU,KAAK,aAAa;AAAA,QAC5B,MAAM,KAAK,QAAQ;AAAA,QACnB,cAAc,KAAK,kBAAkB;AAAA,MACvC,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,gBAAqC,UAAU,MAAc,cAAc;AACjF,QAAM,UAAW,UAAU,MAAc,aAAa,QAAQ,CAAC,CAAC;AAEhE,SAAO,EAAE,SAAS,WAAW,eAAe,QAAQ;AACtD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|