@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,137 @@
|
|
|
1
|
+
import { PERMISSION_ERROR_COOLDOWN_MS, permissionErrorNotifiedAt } from "./permission";
|
|
2
|
+
import { resolveUserName } from "./user-name-cache";
|
|
3
|
+
import { downloadResources, buildFeishuMediaPayload } from "./media-resolver";
|
|
4
|
+
import { getMessageFeishu } from "../outbound/fetch";
|
|
5
|
+
import { getUserNameCache, batchResolveUserNames } from "./user-name-cache";
|
|
6
|
+
async function resolveSenderInfo(params) {
|
|
7
|
+
const { account, log } = params;
|
|
8
|
+
let ctx = params.ctx;
|
|
9
|
+
if (ctx.rawSender?.sender_type !== "user") {
|
|
10
|
+
log(`sender_type is "${ctx.rawSender?.sender_type}", skipping name resolution`);
|
|
11
|
+
return { ctx };
|
|
12
|
+
}
|
|
13
|
+
const senderResult = await resolveUserName({
|
|
14
|
+
account,
|
|
15
|
+
openId: ctx.senderId,
|
|
16
|
+
log
|
|
17
|
+
});
|
|
18
|
+
if (senderResult.name) {
|
|
19
|
+
ctx = { ...ctx, senderName: senderResult.name };
|
|
20
|
+
log(`sender resolved: ${senderResult.name}`);
|
|
21
|
+
} else if (senderResult.permissionError) {
|
|
22
|
+
log(`sender resolve failed: permission error code=${senderResult.permissionError.code}`);
|
|
23
|
+
}
|
|
24
|
+
let permissionError;
|
|
25
|
+
if (senderResult.permissionError) {
|
|
26
|
+
const appKey = account.appId ?? "default";
|
|
27
|
+
const now = Date.now();
|
|
28
|
+
const lastNotified = permissionErrorNotifiedAt.get(appKey) ?? 0;
|
|
29
|
+
if (now - lastNotified > PERMISSION_ERROR_COOLDOWN_MS) {
|
|
30
|
+
permissionErrorNotifiedAt.set(appKey, now);
|
|
31
|
+
permissionError = senderResult.permissionError;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { ctx, permissionError };
|
|
35
|
+
}
|
|
36
|
+
async function prefetchUserNames(params) {
|
|
37
|
+
const { ctx, account, log } = params;
|
|
38
|
+
if (!account.configured) return;
|
|
39
|
+
const cache = getUserNameCache(account.accountId);
|
|
40
|
+
for (const m of ctx.mentions) {
|
|
41
|
+
if (!m.isBot && m.openId && m.name) {
|
|
42
|
+
cache.set(m.openId, m.name);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const openIds = /* @__PURE__ */ new Set();
|
|
46
|
+
if (ctx.senderId) openIds.add(ctx.senderId);
|
|
47
|
+
for (const m of ctx.mentions) {
|
|
48
|
+
if (!m.isBot && m.openId) openIds.add(m.openId);
|
|
49
|
+
}
|
|
50
|
+
const toResolve = cache.filterMissing([...openIds]);
|
|
51
|
+
if (toResolve.length > 0) {
|
|
52
|
+
await batchResolveUserNames({ account, openIds: toResolve, log });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function resolveMedia(params) {
|
|
56
|
+
const { ctx, accountScopedCfg, account, log } = params;
|
|
57
|
+
const accountFeishuCfg = account.config;
|
|
58
|
+
const mediaMaxBytes = (accountFeishuCfg?.mediaMaxMb ?? 30) * 1024 * 1024;
|
|
59
|
+
const mediaList = await downloadResources({
|
|
60
|
+
cfg: accountScopedCfg,
|
|
61
|
+
messageId: ctx.messageId,
|
|
62
|
+
resources: ctx.resources,
|
|
63
|
+
maxBytes: mediaMaxBytes,
|
|
64
|
+
log,
|
|
65
|
+
accountId: account.accountId
|
|
66
|
+
});
|
|
67
|
+
if (mediaList.length > 0) {
|
|
68
|
+
log(`media resolved: ${mediaList.length} attachment(s)`);
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
payload: buildFeishuMediaPayload(mediaList),
|
|
72
|
+
mediaList
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function substituteMediaPaths(content, mediaList) {
|
|
76
|
+
let result = content;
|
|
77
|
+
for (const media of mediaList) {
|
|
78
|
+
const { fileKey, path, resourceType } = media;
|
|
79
|
+
switch (resourceType) {
|
|
80
|
+
case "image":
|
|
81
|
+
result = result.replace(``, path);
|
|
82
|
+
break;
|
|
83
|
+
case "sticker":
|
|
84
|
+
result = result.replace(`<sticker key="${fileKey}"/>`, path);
|
|
85
|
+
break;
|
|
86
|
+
case "audio": {
|
|
87
|
+
const audioRe = new RegExp(`<audio key="${escapeRegExp(fileKey)}"[^/]*/>`);
|
|
88
|
+
result = result.replace(audioRe, `[Audio: ${path}]`);
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
case "file": {
|
|
92
|
+
const fileRe = new RegExp(`<file key="${escapeRegExp(fileKey)}"[^/]*/>`);
|
|
93
|
+
result = result.replace(fileRe, `[File: ${path}]`);
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case "video": {
|
|
97
|
+
const videoRe = new RegExp(`<video key="${escapeRegExp(fileKey)}"[^/]*/>`);
|
|
98
|
+
result = result.replace(videoRe, `[Video: ${path}]`);
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
function escapeRegExp(s) {
|
|
106
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
107
|
+
}
|
|
108
|
+
async function resolveQuotedContent(params) {
|
|
109
|
+
const { ctx, accountScopedCfg, account, log } = params;
|
|
110
|
+
if (!ctx.parentId) return void 0;
|
|
111
|
+
try {
|
|
112
|
+
const quotedMsg = await getMessageFeishu({
|
|
113
|
+
cfg: accountScopedCfg,
|
|
114
|
+
messageId: ctx.parentId,
|
|
115
|
+
accountId: account.accountId,
|
|
116
|
+
expandForward: true
|
|
117
|
+
});
|
|
118
|
+
if (!quotedMsg) return void 0;
|
|
119
|
+
log(`feishu[${account.accountId}]: fetched quoted message: ${quotedMsg.content?.slice(0, 100)}`);
|
|
120
|
+
const prefix = `[message_id=${ctx.parentId}]`;
|
|
121
|
+
if (quotedMsg.senderName) {
|
|
122
|
+
return `${prefix} ${quotedMsg.senderName}: ${quotedMsg.content}`;
|
|
123
|
+
}
|
|
124
|
+
return `${prefix} ${quotedMsg.content}`;
|
|
125
|
+
} catch (err) {
|
|
126
|
+
log(`feishu[${account.accountId}]: failed to fetch quoted message: ${String(err)}`);
|
|
127
|
+
return void 0;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
export {
|
|
131
|
+
prefetchUserNames,
|
|
132
|
+
resolveMedia,
|
|
133
|
+
resolveQuotedContent,
|
|
134
|
+
resolveSenderInfo,
|
|
135
|
+
substituteMediaPaths
|
|
136
|
+
};
|
|
137
|
+
//# sourceMappingURL=enrich.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/messaging/inbound/enrich.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * Context enrichment for inbound Feishu messages.\n *\n * Enrichment phases:\n *\n * - **resolveSenderInfo** (lightweight, before gate) \u2014 resolves sender\n * display name and tracks permission errors.\n * - **prefetchUserNames** (after gate, before content resolution) \u2014 batch\n * pre-warm the account-scoped user-name cache for the sender and all\n * non-bot mentions so that downstream merge_forward expansion and\n * quoted-message formatting can read names synchronously.\n * - **resolveMedia** (after gate) \u2014 downloads binary media attachments\n * using ResourceDescriptors from the converter phase.\n * - **resolveQuotedContent** (after gate) \u2014 fetches the replied-to\n * message text for context.\n *\n * Note: merge_forward expansion for the primary message is now handled\n * at parse time in {@link parseMessageEvent}. Quoted merge_forward\n * messages are still expanded here via {@link resolveQuotedContent}.\n */\n\nimport type { ClawdbotConfig } from 'openclaw/plugin-sdk';\nimport type { MessageContext, FeishuMediaInfo } from '../types';\nimport type { LarkAccount } from '../../core/types';\nimport type { PermissionError } from './permission';\nimport { PERMISSION_ERROR_COOLDOWN_MS, permissionErrorNotifiedAt } from './permission';\nimport { resolveUserName } from './user-name-cache';\nimport { downloadResources, buildFeishuMediaPayload } from './media-resolver';\nimport { getMessageFeishu } from '../outbound/fetch';\nimport { getUserNameCache, batchResolveUserNames } from './user-name-cache';\n\n// ---------------------------------------------------------------------------\n// Phase 1: Sender info (lightweight, before gate)\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the sender display name and track permission errors.\n *\n * This must run before the gate check because per-group sender\n * allowlists may match on senderName.\n */\nexport async function resolveSenderInfo(params: {\n ctx: MessageContext;\n account: LarkAccount;\n log: (...args: unknown[]) => void;\n}): Promise<{ ctx: MessageContext; permissionError?: PermissionError }> {\n const { account, log } = params;\n let ctx = params.ctx;\n\n // Only resolve display name for real users \u2014 the contact API\n // does not return results for app/bot accounts.\n if (ctx.rawSender?.sender_type !== 'user') {\n log(`sender_type is \"${ctx.rawSender?.sender_type}\", skipping name resolution`);\n return { ctx };\n }\n\n // Resolve sender display name (best-effort)\n const senderResult = await resolveUserName({\n account,\n openId: ctx.senderId,\n log,\n });\n if (senderResult.name) {\n ctx = { ...ctx, senderName: senderResult.name };\n log(`sender resolved: ${senderResult.name}`);\n } else if (senderResult.permissionError) {\n log(`sender resolve failed: permission error code=${senderResult.permissionError.code}`);\n }\n\n // Track permission errors (with cooldown)\n let permissionError: PermissionError | undefined;\n if (senderResult.permissionError) {\n const appKey = account.appId ?? 'default';\n const now = Date.now();\n const lastNotified = permissionErrorNotifiedAt.get(appKey) ?? 0;\n\n if (now - lastNotified > PERMISSION_ERROR_COOLDOWN_MS) {\n permissionErrorNotifiedAt.set(appKey, now);\n permissionError = senderResult.permissionError;\n }\n }\n\n return { ctx, permissionError };\n}\n\n// ---------------------------------------------------------------------------\n// Phase 1.5: Batch pre-warm user name cache (after gate)\n// ---------------------------------------------------------------------------\n\n/**\n * Batch-prefetch user display names for the sender and all non-bot\n * mentions. Mention names that are already known from the event payload\n * are written into the cache for free.\n */\nexport async function prefetchUserNames(params: {\n ctx: MessageContext;\n account: LarkAccount;\n log: (...args: unknown[]) => void;\n}): Promise<void> {\n const { ctx, account, log } = params;\n if (!account.configured) return;\n\n const cache = getUserNameCache(account.accountId);\n\n // Seed cache with mention names already present in the event payload\n for (const m of ctx.mentions) {\n if (!m.isBot && m.openId && m.name) {\n cache.set(m.openId, m.name);\n }\n }\n\n // Collect all openIds we care about\n const openIds = new Set<string>();\n if (ctx.senderId) openIds.add(ctx.senderId);\n for (const m of ctx.mentions) {\n if (!m.isBot && m.openId) openIds.add(m.openId);\n }\n\n // Batch-resolve any that are still missing\n const toResolve = cache.filterMissing([...openIds]);\n if (toResolve.length > 0) {\n await batchResolveUserNames({ account, openIds: toResolve, log });\n }\n}\n\n// ---------------------------------------------------------------------------\n// Phase 2a: Media attachments (binary downloads)\n// ---------------------------------------------------------------------------\n\n/** Result of media resolution: envelope payload + per-file mapping. */\nexport interface ResolveMediaResult {\n payload: Record<string, unknown>;\n mediaList: FeishuMediaInfo[];\n}\n\n/**\n * Download and save binary media attachments (images, files, audio,\n * video, stickers) from the inbound message.\n *\n * Uses ResourceDescriptors extracted by content converters during the\n * parse phase \u2014 no re-parsing of rawMessage.content needed.\n *\n * Returns a payload object whose keys (`MediaPath`, `MediaType`, \u2026)\n * are spread directly into the agent envelope, plus the raw mediaList\n * for content substitution.\n */\nexport async function resolveMedia(params: {\n ctx: MessageContext;\n /** account \u7EA7\u522B\u7684 ClawdbotConfig\uFF08channels.feishu \u5DF2\u66FF\u6362\u4E3A per-account \u5408\u5E76\u540E\u7684\u914D\u7F6E\uFF09 */\n accountScopedCfg: ClawdbotConfig;\n account: LarkAccount;\n log: (...args: unknown[]) => void;\n}): Promise<ResolveMediaResult> {\n const { ctx, accountScopedCfg, account, log } = params;\n const accountFeishuCfg = account.config;\n\n const mediaMaxBytes = (accountFeishuCfg?.mediaMaxMb ?? 30) * 1024 * 1024;\n\n const mediaList = await downloadResources({\n cfg: accountScopedCfg,\n messageId: ctx.messageId,\n resources: ctx.resources,\n maxBytes: mediaMaxBytes,\n log,\n accountId: account.accountId,\n });\n\n if (mediaList.length > 0) {\n log(`media resolved: ${mediaList.length} attachment(s)`);\n }\n\n return {\n payload: buildFeishuMediaPayload(mediaList),\n mediaList,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Media content substitution\n// ---------------------------------------------------------------------------\n\n/**\n * Replace Feishu file-key references in message content with actual\n * local file paths after download.\n *\n * This is critical for:\n * - **Images / stickers**: The SDK's `detectAndLoadPromptImages` scans\n * the prompt text for local file paths with image extensions.\n * - **Audio / video / files**: Gives the AI meaningful context about\n * what was received (the SDK reads these via `MediaPath` directly,\n * but the text body should still reflect the actual attachments).\n */\nexport function substituteMediaPaths(content: string, mediaList: FeishuMediaInfo[]): string {\n let result = content;\n for (const media of mediaList) {\n const { fileKey, path, resourceType } = media;\n switch (resourceType) {\n case 'image':\n //  \u2192 local path (SDK detects image extensions)\n result = result.replace(``, path);\n break;\n case 'sticker':\n // <sticker key=\"xxx\"/> \u2192 local path (treated like image)\n result = result.replace(`<sticker key=\"${fileKey}\"/>`, path);\n break;\n case 'audio': {\n // <audio key=\"xxx\" .../> \u2192 [Audio: /path/to/audio.opus ...]\n const audioRe = new RegExp(`<audio key=\"${escapeRegExp(fileKey)}\"[^/]*/>`);\n result = result.replace(audioRe, `[Audio: ${path}]`);\n break;\n }\n case 'file': {\n // <file key=\"xxx\" .../> \u2192 [File: /path/to/doc.pdf]\n const fileRe = new RegExp(`<file key=\"${escapeRegExp(fileKey)}\"[^/]*/>`);\n result = result.replace(fileRe, `[File: ${path}]`);\n break;\n }\n case 'video': {\n // <video key=\"xxx\" .../> \u2192 [Video: /path/to/video.mp4]\n const videoRe = new RegExp(`<video key=\"${escapeRegExp(fileKey)}\"[^/]*/>`);\n result = result.replace(videoRe, `[Video: ${path}]`);\n break;\n }\n }\n }\n return result;\n}\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n// ---------------------------------------------------------------------------\n// Phase 2b: Quoted / replied-to message (text context)\n// ---------------------------------------------------------------------------\n\n/**\n * Fetch the text content of the message that the user replied to.\n *\n * If the quoted message is itself a merge_forward, its sub-messages are\n * fetched and formatted as a single text block.\n *\n * Returns `\"senderName: content\"` when the sender name is available so\n * the AI knows who originally wrote the quoted message.\n */\nexport async function resolveQuotedContent(params: {\n ctx: MessageContext;\n /** account \u7EA7\u522B\u7684 ClawdbotConfig\uFF08channels.feishu \u5DF2\u66FF\u6362\u4E3A per-account \u5408\u5E76\u540E\u7684\u914D\u7F6E\uFF09 */\n accountScopedCfg: ClawdbotConfig;\n account: LarkAccount;\n log: (...args: unknown[]) => void;\n}): Promise<string | undefined> {\n const { ctx, accountScopedCfg, account, log } = params;\n\n if (!ctx.parentId) return undefined;\n\n try {\n const quotedMsg = await getMessageFeishu({\n cfg: accountScopedCfg,\n messageId: ctx.parentId,\n accountId: account.accountId,\n expandForward: true,\n });\n if (!quotedMsg) return undefined;\n\n log(`feishu[${account.accountId}]: fetched quoted message: ${quotedMsg.content?.slice(0, 100)}`);\n\n // Build quoted text with message_id prefix so AI can correlate\n // file_key / image_key with the source message for resource download.\n const prefix = `[message_id=${ctx.parentId}]`;\n if (quotedMsg.senderName) {\n return `${prefix} ${quotedMsg.senderName}: ${quotedMsg.content}`;\n }\n return `${prefix} ${quotedMsg.content}`;\n } catch (err) {\n log(`feishu[${account.accountId}]: failed to fetch quoted message: ${String(err)}`);\n return undefined;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { LarkClient } from "../../core/lark-client";
|
|
2
|
+
import { sendMessageFeishu } from "../outbound/send";
|
|
3
|
+
async function sendPairingReply(params) {
|
|
4
|
+
const { senderId, chatId, accountId, accountScopedCfg } = params;
|
|
5
|
+
const core = LarkClient.runtime;
|
|
6
|
+
const { code } = await core.channel.pairing.upsertPairingRequest({
|
|
7
|
+
channel: "feishu",
|
|
8
|
+
id: senderId,
|
|
9
|
+
accountId
|
|
10
|
+
});
|
|
11
|
+
const pairingReply = core.channel.pairing.buildPairingReply({
|
|
12
|
+
channel: "feishu",
|
|
13
|
+
idLine: senderId,
|
|
14
|
+
code
|
|
15
|
+
});
|
|
16
|
+
if (accountScopedCfg) {
|
|
17
|
+
await sendMessageFeishu({
|
|
18
|
+
cfg: accountScopedCfg,
|
|
19
|
+
to: chatId,
|
|
20
|
+
text: pairingReply,
|
|
21
|
+
accountId
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
sendPairingReply
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=gate-effects.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/messaging/inbound/gate-effects.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * Side-effect functions for the inbound message gate.\n *\n * Extracted from gate.ts to separate pure policy decisions from I/O\n * operations (pairing request creation, message sending).\n */\n\nimport type { ClawdbotConfig } from 'openclaw/plugin-sdk';\nimport { LarkClient } from '../../core/lark-client';\nimport { sendMessageFeishu } from '../outbound/send';\n\n// ---------------------------------------------------------------------------\n// Pairing reply\n// ---------------------------------------------------------------------------\n\n/**\n * Create a pairing request and send a pairing reply message to the user.\n *\n * This is the side-effect portion of the DM pairing gate: the pure\n * policy decision (whether to pair) is made in gate.ts, and this\n * function executes the resulting I/O.\n */\nexport async function sendPairingReply(params: {\n senderId: string;\n chatId: string;\n accountId: string;\n accountScopedCfg?: ClawdbotConfig;\n}): Promise<void> {\n const { senderId, chatId, accountId, accountScopedCfg } = params;\n const core = LarkClient.runtime;\n\n const { code } = await core.channel.pairing.upsertPairingRequest({\n channel: 'feishu',\n id: senderId,\n accountId,\n });\n\n const pairingReply = core.channel.pairing.buildPairingReply({\n channel: 'feishu',\n idLine: senderId,\n code,\n });\n\n if (accountScopedCfg) {\n await sendMessageFeishu({\n cfg: accountScopedCfg,\n to: chatId,\n text: pairingReply,\n accountId,\n });\n }\n}\n"],
|
|
5
|
+
"mappings": "AAWA,SAAS,kBAAkB;AAC3B,SAAS,yBAAyB;AAalC,eAAsB,iBAAiB,QAKrB;AAChB,QAAM,EAAE,UAAU,QAAQ,WAAW,iBAAiB,IAAI;AAC1D,QAAM,OAAO,WAAW;AAExB,QAAM,EAAE,KAAK,IAAI,MAAM,KAAK,QAAQ,QAAQ,qBAAqB;AAAA,IAC/D,SAAS;AAAA,IACT,IAAI;AAAA,IACJ;AAAA,EACF,CAAC;AAED,QAAM,eAAe,KAAK,QAAQ,QAAQ,kBAAkB;AAAA,IAC1D,SAAS;AAAA,IACT,QAAQ;AAAA,IACR;AAAA,EACF,CAAC;AAED,MAAI,kBAAkB;AACpB,UAAM,kBAAkB;AAAA,MACtB,KAAK;AAAA,MACL,IAAI;AAAA,MACJ,MAAM;AAAA,MACN;AAAA,IACF,CAAC;AAAA,EACH;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { LarkClient } from "../../core/lark-client";
|
|
2
|
+
import {
|
|
3
|
+
resolveFeishuGroupConfig,
|
|
4
|
+
resolveFeishuAllowlistMatch,
|
|
5
|
+
isFeishuGroupAllowed,
|
|
6
|
+
splitLegacyGroupAllowFrom,
|
|
7
|
+
resolveGroupSenderPolicyContext
|
|
8
|
+
} from "./policy";
|
|
9
|
+
import { mentionedBot } from "./mention";
|
|
10
|
+
import { sendPairingReply } from "./gate-effects";
|
|
11
|
+
let legacyGroupAllowFromWarned = false;
|
|
12
|
+
async function readAllowFromStore(accountId) {
|
|
13
|
+
const core = LarkClient.runtime;
|
|
14
|
+
return await core.channel.pairing.readAllowFromStore({
|
|
15
|
+
channel: "feishu",
|
|
16
|
+
accountId
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
async function checkMessageGate(params) {
|
|
20
|
+
const { ctx, accountFeishuCfg, account, accountScopedCfg, log } = params;
|
|
21
|
+
const isGroup = ctx.chatType === "group";
|
|
22
|
+
if (isGroup) {
|
|
23
|
+
return checkGroupGate({ ctx, accountFeishuCfg, account, accountScopedCfg, log });
|
|
24
|
+
}
|
|
25
|
+
return checkDmGate({ ctx, accountFeishuCfg, account, accountScopedCfg, log });
|
|
26
|
+
}
|
|
27
|
+
function checkGroupGate(params) {
|
|
28
|
+
const { ctx, accountFeishuCfg, account, accountScopedCfg, log } = params;
|
|
29
|
+
const core = LarkClient.runtime;
|
|
30
|
+
const rawGroupAllowFrom = accountFeishuCfg?.groupAllowFrom ?? [];
|
|
31
|
+
const { legacyChatIds, senderAllowFrom: senderGroupAllowFrom } = splitLegacyGroupAllowFrom(rawGroupAllowFrom);
|
|
32
|
+
if (legacyChatIds.length > 0 && !legacyGroupAllowFromWarned) {
|
|
33
|
+
legacyGroupAllowFromWarned = true;
|
|
34
|
+
log(
|
|
35
|
+
`feishu[${account.accountId}]: \u26A0\uFE0F groupAllowFrom contains chat_id entries (${legacyChatIds.join(", ")}). groupAllowFrom is for SENDER filtering (open_ids like ou_xxx). Please move chat_ids to "groups" config instead:
|
|
36
|
+
channels.feishu.groups: {
|
|
37
|
+
` + legacyChatIds.map((id) => ` "${id}": {},`).join("\n") + `
|
|
38
|
+
}`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
const groupAccess = core.channel.groups.resolveGroupPolicy({
|
|
42
|
+
cfg: accountScopedCfg ?? {},
|
|
43
|
+
channel: "feishu",
|
|
44
|
+
groupId: ctx.chatId,
|
|
45
|
+
accountId: account.accountId,
|
|
46
|
+
groupIdCaseInsensitive: true,
|
|
47
|
+
hasGroupAllowFrom: senderGroupAllowFrom.length > 0
|
|
48
|
+
});
|
|
49
|
+
let legacyGroupAdmit = false;
|
|
50
|
+
if (!groupAccess.allowed) {
|
|
51
|
+
const chatIdLower = ctx.chatId.toLowerCase();
|
|
52
|
+
const legacyMatch = legacyChatIds.some((id) => String(id).toLowerCase() === chatIdLower);
|
|
53
|
+
if (!legacyMatch) {
|
|
54
|
+
log(`feishu[${account.accountId}]: group ${ctx.chatId} blocked by group-level policy`);
|
|
55
|
+
return { allowed: false, reason: "group_not_allowed" };
|
|
56
|
+
}
|
|
57
|
+
legacyGroupAdmit = true;
|
|
58
|
+
}
|
|
59
|
+
const groupConfig = resolveFeishuGroupConfig({
|
|
60
|
+
cfg: accountFeishuCfg,
|
|
61
|
+
groupId: ctx.chatId
|
|
62
|
+
});
|
|
63
|
+
const defaultConfig = accountFeishuCfg?.groups?.["*"];
|
|
64
|
+
const enabled = groupConfig?.enabled ?? defaultConfig?.enabled;
|
|
65
|
+
if (enabled === false) {
|
|
66
|
+
log(`feishu[${account.accountId}]: group ${ctx.chatId} disabled by per-group config`);
|
|
67
|
+
return { allowed: false, reason: "group_disabled" };
|
|
68
|
+
}
|
|
69
|
+
const hasExplicitSenderConfig = senderGroupAllowFrom.length > 0 || (groupConfig?.allowFrom ?? []).length > 0 || groupConfig?.groupPolicy != null;
|
|
70
|
+
if (!(legacyGroupAdmit && !hasExplicitSenderConfig)) {
|
|
71
|
+
const { senderPolicy, senderAllowFrom } = resolveGroupSenderPolicyContext({
|
|
72
|
+
groupConfig,
|
|
73
|
+
defaultConfig,
|
|
74
|
+
accountFeishuCfg,
|
|
75
|
+
senderGroupAllowFrom
|
|
76
|
+
});
|
|
77
|
+
const senderAllowed = isFeishuGroupAllowed({
|
|
78
|
+
groupPolicy: senderPolicy,
|
|
79
|
+
allowFrom: senderAllowFrom,
|
|
80
|
+
senderId: ctx.senderId,
|
|
81
|
+
senderName: ctx.senderName
|
|
82
|
+
});
|
|
83
|
+
if (!senderAllowed) {
|
|
84
|
+
log(`feishu[${account.accountId}]: sender ${ctx.senderId} not allowed in group ${ctx.chatId}`);
|
|
85
|
+
return { allowed: false, reason: "sender_not_allowed" };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const requireMention = core.channel.groups.resolveRequireMention({
|
|
89
|
+
cfg: accountScopedCfg ?? {},
|
|
90
|
+
channel: "feishu",
|
|
91
|
+
groupId: ctx.chatId,
|
|
92
|
+
accountId: account.accountId,
|
|
93
|
+
groupIdCaseInsensitive: true,
|
|
94
|
+
requireMentionOverride: accountFeishuCfg?.requireMention
|
|
95
|
+
});
|
|
96
|
+
if (requireMention && !mentionedBot(ctx)) {
|
|
97
|
+
log(`feishu[${account.accountId}]: message in group ${ctx.chatId} did not mention bot, recording to history`);
|
|
98
|
+
return {
|
|
99
|
+
allowed: false,
|
|
100
|
+
reason: "no_mention",
|
|
101
|
+
historyEntry: {
|
|
102
|
+
sender: ctx.senderId,
|
|
103
|
+
body: `${ctx.senderName ?? ctx.senderId}: ${ctx.content}`,
|
|
104
|
+
timestamp: Date.now(),
|
|
105
|
+
messageId: ctx.messageId
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return { allowed: true };
|
|
110
|
+
}
|
|
111
|
+
async function checkDmGate(params) {
|
|
112
|
+
const { ctx, accountFeishuCfg, account, accountScopedCfg, log } = params;
|
|
113
|
+
const dmPolicy = accountFeishuCfg?.dmPolicy ?? "pairing";
|
|
114
|
+
const configAllowFrom = accountFeishuCfg?.allowFrom ?? [];
|
|
115
|
+
if (dmPolicy === "disabled") {
|
|
116
|
+
log(`feishu[${account.accountId}]: DM disabled by policy, rejecting sender ${ctx.senderId}`);
|
|
117
|
+
return { allowed: false, reason: "dm_disabled" };
|
|
118
|
+
}
|
|
119
|
+
if (dmPolicy === "open") {
|
|
120
|
+
return { allowed: true };
|
|
121
|
+
}
|
|
122
|
+
if (dmPolicy === "allowlist") {
|
|
123
|
+
const storeAllowFrom2 = await readAllowFromStore(account.accountId).catch(() => []);
|
|
124
|
+
const combinedAllowFrom2 = [...configAllowFrom, ...storeAllowFrom2];
|
|
125
|
+
const match2 = resolveFeishuAllowlistMatch({
|
|
126
|
+
allowFrom: combinedAllowFrom2,
|
|
127
|
+
senderId: ctx.senderId,
|
|
128
|
+
senderName: ctx.senderName
|
|
129
|
+
});
|
|
130
|
+
if (!match2.allowed) {
|
|
131
|
+
log(`feishu[${account.accountId}]: sender ${ctx.senderId} not in DM allowlist`);
|
|
132
|
+
return { allowed: false, reason: "dm_not_allowed" };
|
|
133
|
+
}
|
|
134
|
+
return { allowed: true };
|
|
135
|
+
}
|
|
136
|
+
const storeAllowFrom = await readAllowFromStore(account.accountId).catch(() => []);
|
|
137
|
+
const combinedAllowFrom = [...configAllowFrom, ...storeAllowFrom];
|
|
138
|
+
const match = resolveFeishuAllowlistMatch({
|
|
139
|
+
allowFrom: combinedAllowFrom,
|
|
140
|
+
senderId: ctx.senderId,
|
|
141
|
+
senderName: ctx.senderName
|
|
142
|
+
});
|
|
143
|
+
if (match.allowed) {
|
|
144
|
+
return { allowed: true };
|
|
145
|
+
}
|
|
146
|
+
log(`feishu[${account.accountId}]: sender ${ctx.senderId} not paired, creating pairing request`);
|
|
147
|
+
try {
|
|
148
|
+
await sendPairingReply({
|
|
149
|
+
senderId: ctx.senderId,
|
|
150
|
+
chatId: ctx.chatId,
|
|
151
|
+
accountId: account.accountId,
|
|
152
|
+
accountScopedCfg
|
|
153
|
+
});
|
|
154
|
+
} catch (err) {
|
|
155
|
+
log(`feishu[${account.accountId}]: failed to create pairing request for ${ctx.senderId}: ${String(err)}`);
|
|
156
|
+
}
|
|
157
|
+
return { allowed: false, reason: "pairing_pending" };
|
|
158
|
+
}
|
|
159
|
+
export {
|
|
160
|
+
checkMessageGate,
|
|
161
|
+
readAllowFromStore as readFeishuAllowFromStore
|
|
162
|
+
};
|
|
163
|
+
//# sourceMappingURL=gate.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/messaging/inbound/gate.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * Policy gate for inbound Feishu messages.\n *\n * Determines whether a parsed message should be processed or rejected\n * based on group/DM access policies, sender allowlists, and mention\n * requirements.\n *\n * Group access follows the same two-layer model as Telegram:\n *\n * Layer 1 \u2013 Which GROUPS are allowed (SDK `resolveGroupPolicy`):\n * - No `groups` configured + `groupPolicy: \"open\"` \u2192 any group passes\n * - `groupPolicy: \"allowlist\"` or `groups` configured \u2192 acts as allowlist\n * (explicit group IDs or `\"*\"` wildcard)\n * - `groupPolicy: \"disabled\"` \u2192 all groups blocked\n *\n * Layer 2 \u2013 Which SENDERS are allowed within a group:\n * - Per-group `groupPolicy` overrides global for sender filtering\n * - `groupAllowFrom` (global) + per-group `allowFrom` are merged\n * - `\"open\"` \u2192 any sender; `\"allowlist\"` \u2192 check merged list;\n * `\"disabled\"` \u2192 block all senders\n */\n\nimport type { ClawdbotConfig, HistoryEntry } from 'openclaw/plugin-sdk';\nimport type { MessageContext } from '../types';\nimport type { FeishuConfig } from '../../core/types';\nimport type { LarkAccount } from '../../core/types';\nimport { LarkClient } from '../../core/lark-client';\nimport {\n resolveFeishuGroupConfig,\n resolveFeishuAllowlistMatch,\n isFeishuGroupAllowed,\n splitLegacyGroupAllowFrom,\n resolveGroupSenderPolicyContext,\n} from './policy';\nimport { mentionedBot } from './mention';\nimport { sendPairingReply } from './gate-effects';\n\n/** Prevent spamming the legacy groupAllowFrom migration warning. */\nlet legacyGroupAllowFromWarned = false;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Read the pairing allowFrom store for the Feishu channel via the SDK runtime.\n */\nasync function readAllowFromStore(accountId: string): Promise<string[]> {\n const core = LarkClient.runtime;\n return await core.channel.pairing.readAllowFromStore({\n channel: 'feishu',\n accountId,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface GateResult {\n allowed: boolean;\n reason?: string;\n /** When a group message is rejected due to missing bot mention, the\n * caller should record this entry into the chat history map. */\n historyEntry?: HistoryEntry;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Read the pairing allowFrom store for the Feishu channel.\n *\n * Exported so that handler.ts can provide it as a closure to the SDK's\n * `resolveSenderCommandAuthorization` helper.\n */\nexport { readAllowFromStore as readFeishuAllowFromStore };\n\n/**\n * Check whether an inbound message passes all access-control gates.\n *\n * The DM gate is async because it may read from the pairing store\n * and send pairing request messages.\n */\nexport async function checkMessageGate(params: {\n ctx: MessageContext;\n accountFeishuCfg?: FeishuConfig;\n account: LarkAccount;\n /** account \u7EA7\u522B\u7684 ClawdbotConfig\uFF08channels.feishu \u5DF2\u66FF\u6362\u4E3A per-account \u5408\u5E76\u540E\u7684\u914D\u7F6E\uFF09 */\n accountScopedCfg?: ClawdbotConfig;\n log: (...args: unknown[]) => void;\n}): Promise<GateResult> {\n const { ctx, accountFeishuCfg, account, accountScopedCfg, log } = params;\n const isGroup = ctx.chatType === 'group';\n\n if (isGroup) {\n return checkGroupGate({ ctx, accountFeishuCfg, account, accountScopedCfg, log });\n }\n\n return checkDmGate({ ctx, accountFeishuCfg, account, accountScopedCfg, log });\n}\n\n// ---------------------------------------------------------------------------\n// Internal: group gate\n// ---------------------------------------------------------------------------\n\nfunction checkGroupGate(params: {\n ctx: MessageContext;\n accountFeishuCfg?: FeishuConfig;\n account: LarkAccount;\n accountScopedCfg?: ClawdbotConfig;\n log: (...args: unknown[]) => void;\n}): GateResult {\n const { ctx, accountFeishuCfg, account, accountScopedCfg, log } = params;\n const core = LarkClient.runtime;\n\n // ---- Legacy compat: groupAllowFrom with chat_id entries ----\n // Older Feishu configs used groupAllowFrom with chat_ids (oc_xxx) to\n // control which groups are allowed. The correct semantic (aligned with\n // Telegram) is sender_ids. Detect and split so both layers still work.\n const rawGroupAllowFrom = accountFeishuCfg?.groupAllowFrom ?? [];\n const { legacyChatIds, senderAllowFrom: senderGroupAllowFrom } = splitLegacyGroupAllowFrom(rawGroupAllowFrom);\n\n if (legacyChatIds.length > 0 && !legacyGroupAllowFromWarned) {\n legacyGroupAllowFromWarned = true;\n log(\n `feishu[${account.accountId}]: \u26A0\uFE0F groupAllowFrom contains chat_id entries ` +\n `(${legacyChatIds.join(', ')}). groupAllowFrom is for SENDER filtering ` +\n `(open_ids like ou_xxx). Please move chat_ids to \"groups\" config instead:\\n` +\n ` channels.feishu.groups: {\\n` +\n legacyChatIds.map((id) => ` \"${id}\": {},`).join('\\n') +\n `\\n }`,\n );\n }\n\n // ---- Layer 1: Group-level access (SDK) ----\n // The SDK reads `channels.feishu.groups` as an allowlist of group IDs.\n // - No groups configured + groupPolicy \"open\" \u2192 any group passes\n // - groupPolicy \"allowlist\" (or groups configured) \u2192 only listed groups pass\n // - groupPolicy \"disabled\" \u2192 all groups blocked\n const groupAccess = core.channel.groups.resolveGroupPolicy({\n cfg: accountScopedCfg ?? {},\n channel: 'feishu',\n groupId: ctx.chatId,\n accountId: account.accountId,\n groupIdCaseInsensitive: true,\n hasGroupAllowFrom: senderGroupAllowFrom.length > 0,\n });\n\n // Legacy compat: if SDK rejects the group but the chat_id is in the\n // old-style groupAllowFrom, allow it (backward compatibility).\n // Track whether this group was admitted via legacy path so we can skip\n // sender filtering below (old semantic: chat_id in groupAllowFrom meant\n // \"allow this group for any sender\").\n let legacyGroupAdmit = false;\n if (!groupAccess.allowed) {\n const chatIdLower = ctx.chatId.toLowerCase();\n const legacyMatch = legacyChatIds.some((id) => String(id).toLowerCase() === chatIdLower);\n if (!legacyMatch) {\n log(`feishu[${account.accountId}]: group ${ctx.chatId} blocked by group-level policy`);\n return { allowed: false, reason: 'group_not_allowed' };\n }\n legacyGroupAdmit = true;\n }\n\n // ---- Per-group config (Feishu-specific fields) ----\n const groupConfig = resolveFeishuGroupConfig({\n cfg: accountFeishuCfg,\n groupId: ctx.chatId,\n });\n const defaultConfig = accountFeishuCfg?.groups?.['*'];\n\n // Per-group enabled flag\n const enabled = groupConfig?.enabled ?? defaultConfig?.enabled;\n if (enabled === false) {\n log(`feishu[${account.accountId}]: group ${ctx.chatId} disabled by per-group config`);\n return { allowed: false, reason: 'group_disabled' };\n }\n\n // ---- Layer 2: Sender-level access ----\n // Per-group groupPolicy overrides the global groupPolicy for sender filtering.\n // senderGroupAllowFrom (global, oc_ entries excluded) + per-group allowFrom.\n //\n // Legacy compat: when a group was admitted via old-style chat_id in\n // groupAllowFrom AND there is no explicit per-group sender config,\n // skip sender filtering (old semantic = \"group allowed, any sender\").\n const hasExplicitSenderConfig =\n senderGroupAllowFrom.length > 0 || (groupConfig?.allowFrom ?? []).length > 0 || groupConfig?.groupPolicy != null;\n\n if (!(legacyGroupAdmit && !hasExplicitSenderConfig)) {\n const { senderPolicy, senderAllowFrom } = resolveGroupSenderPolicyContext({\n groupConfig,\n defaultConfig,\n accountFeishuCfg,\n senderGroupAllowFrom,\n });\n\n const senderAllowed = isFeishuGroupAllowed({\n groupPolicy: senderPolicy,\n allowFrom: senderAllowFrom,\n senderId: ctx.senderId,\n senderName: ctx.senderName,\n });\n\n if (!senderAllowed) {\n log(`feishu[${account.accountId}]: sender ${ctx.senderId} not allowed in group ${ctx.chatId}`);\n return { allowed: false, reason: 'sender_not_allowed' };\n }\n }\n\n // ---- Mention requirement (SDK) ----\n // SDK precedence: per-group > default (\"*\") > requireMentionOverride > true\n const requireMention = core.channel.groups.resolveRequireMention({\n cfg: accountScopedCfg ?? {},\n channel: 'feishu',\n groupId: ctx.chatId,\n accountId: account.accountId,\n groupIdCaseInsensitive: true,\n requireMentionOverride: accountFeishuCfg?.requireMention,\n });\n\n if (requireMention && !mentionedBot(ctx)) {\n log(`feishu[${account.accountId}]: message in group ${ctx.chatId} did not mention bot, recording to history`);\n\n return {\n allowed: false,\n reason: 'no_mention',\n historyEntry: {\n sender: ctx.senderId,\n body: `${ctx.senderName ?? ctx.senderId}: ${ctx.content}`,\n timestamp: Date.now(),\n messageId: ctx.messageId,\n },\n };\n }\n\n return { allowed: true };\n}\n\n// ---------------------------------------------------------------------------\n// Internal: DM gate\n// ---------------------------------------------------------------------------\n\nasync function checkDmGate(params: {\n ctx: MessageContext;\n accountFeishuCfg?: FeishuConfig;\n account: LarkAccount;\n accountScopedCfg?: ClawdbotConfig;\n log: (...args: unknown[]) => void;\n}): Promise<GateResult> {\n const { ctx, accountFeishuCfg, account, accountScopedCfg, log } = params;\n\n const dmPolicy = accountFeishuCfg?.dmPolicy ?? 'pairing';\n const configAllowFrom = accountFeishuCfg?.allowFrom ?? [];\n\n if (dmPolicy === 'disabled') {\n log(`feishu[${account.accountId}]: DM disabled by policy, rejecting sender ${ctx.senderId}`);\n return { allowed: false, reason: 'dm_disabled' };\n }\n\n if (dmPolicy === 'open') {\n return { allowed: true };\n }\n\n if (dmPolicy === 'allowlist') {\n const storeAllowFrom = await readAllowFromStore(account.accountId).catch(() => [] as string[]);\n const combinedAllowFrom = [...configAllowFrom, ...storeAllowFrom];\n\n const match = resolveFeishuAllowlistMatch({\n allowFrom: combinedAllowFrom,\n senderId: ctx.senderId,\n senderName: ctx.senderName,\n });\n if (!match.allowed) {\n log(`feishu[${account.accountId}]: sender ${ctx.senderId} not in DM allowlist`);\n return { allowed: false, reason: 'dm_not_allowed' };\n }\n return { allowed: true };\n }\n\n // dmPolicy === \"pairing\"\n const storeAllowFrom = await readAllowFromStore(account.accountId).catch(() => [] as string[]);\n const combinedAllowFrom = [...configAllowFrom, ...storeAllowFrom];\n\n const match = resolveFeishuAllowlistMatch({\n allowFrom: combinedAllowFrom,\n senderId: ctx.senderId,\n senderName: ctx.senderName,\n });\n\n if (match.allowed) {\n return { allowed: true };\n }\n\n // Sender not yet paired \u2014 create a pairing request and notify them\n log(`feishu[${account.accountId}]: sender ${ctx.senderId} not paired, creating pairing request`);\n try {\n await sendPairingReply({\n senderId: ctx.senderId,\n chatId: ctx.chatId,\n accountId: account.accountId,\n accountScopedCfg,\n });\n } catch (err) {\n log(`feishu[${account.accountId}]: failed to create pairing request for ${ctx.senderId}: ${String(err)}`);\n }\n\n return { allowed: false, reason: 'pairing_pending' };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["storeAllowFrom", "combinedAllowFrom", "match"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import {
|
|
2
|
+
recordPendingHistoryEntryIfEnabled,
|
|
3
|
+
DEFAULT_GROUP_HISTORY_LIMIT,
|
|
4
|
+
resolveSenderCommandAuthorization,
|
|
5
|
+
isNormalizedSenderAllowed
|
|
6
|
+
} from "openclaw/plugin-sdk";
|
|
7
|
+
import { getLarkAccount } from "../../core/accounts";
|
|
8
|
+
import { LarkClient } from "../../core/lark-client";
|
|
9
|
+
import { larkLogger } from "../../core/lark-logger";
|
|
10
|
+
import { ticketElapsed } from "../../core/lark-ticket";
|
|
11
|
+
import { parseMessageEvent } from "./parse";
|
|
12
|
+
import {
|
|
13
|
+
resolveSenderInfo,
|
|
14
|
+
prefetchUserNames,
|
|
15
|
+
resolveMedia,
|
|
16
|
+
resolveQuotedContent,
|
|
17
|
+
substituteMediaPaths
|
|
18
|
+
} from "./enrich";
|
|
19
|
+
import { checkMessageGate, readFeishuAllowFromStore } from "./gate";
|
|
20
|
+
import { dispatchToAgent } from "./dispatch";
|
|
21
|
+
import { resolveFeishuGroupConfig, splitLegacyGroupAllowFrom } from "./policy";
|
|
22
|
+
import { threadScopedKey } from "../../channel/chat-queue";
|
|
23
|
+
const logger = larkLogger("inbound/handler");
|
|
24
|
+
async function handleFeishuMessage(params) {
|
|
25
|
+
const { cfg, event, botOpenId, runtime, chatHistories, accountId, replyToMessageId, forceMention, skipTyping } = params;
|
|
26
|
+
const account = getLarkAccount(cfg, accountId);
|
|
27
|
+
const accountFeishuCfg = account.config;
|
|
28
|
+
const accountScopedCfg = {
|
|
29
|
+
...cfg,
|
|
30
|
+
channels: { ...cfg.channels, feishu: accountFeishuCfg }
|
|
31
|
+
};
|
|
32
|
+
const log = runtime?.log ?? ((...args) => logger.info(args.map(String).join(" ")));
|
|
33
|
+
const error = runtime?.error ?? ((...args) => logger.error(args.map(String).join(" ")));
|
|
34
|
+
let ctx = await parseMessageEvent(event, botOpenId, {
|
|
35
|
+
cfg: accountScopedCfg,
|
|
36
|
+
accountId: account.accountId
|
|
37
|
+
});
|
|
38
|
+
const { ctx: enrichedCtx, permissionError } = await resolveSenderInfo({
|
|
39
|
+
ctx,
|
|
40
|
+
account,
|
|
41
|
+
log
|
|
42
|
+
});
|
|
43
|
+
ctx = enrichedCtx;
|
|
44
|
+
log(`feishu[${account.accountId}]: received message from ${ctx.senderId} in ${ctx.chatId} (${ctx.chatType})`);
|
|
45
|
+
logger.info(`received from ${ctx.senderId} in ${ctx.chatId} (${ctx.chatType})`);
|
|
46
|
+
const historyLimit = Math.max(
|
|
47
|
+
0,
|
|
48
|
+
accountFeishuCfg?.historyLimit ?? accountScopedCfg.messages?.groupChat?.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT
|
|
49
|
+
);
|
|
50
|
+
const gate = forceMention ? { allowed: true } : await checkMessageGate({ ctx, accountFeishuCfg, account, accountScopedCfg, log });
|
|
51
|
+
if (!gate.allowed) {
|
|
52
|
+
if (gate.reason === "no_mention") {
|
|
53
|
+
logger.info(`rejected: no bot mention in group ${ctx.chatId}`);
|
|
54
|
+
}
|
|
55
|
+
if (gate.historyEntry && chatHistories) {
|
|
56
|
+
const historyKey = threadScopedKey(ctx.chatId, ctx.threadId);
|
|
57
|
+
recordPendingHistoryEntryIfEnabled({
|
|
58
|
+
historyMap: chatHistories,
|
|
59
|
+
historyKey,
|
|
60
|
+
limit: historyLimit,
|
|
61
|
+
entry: gate.historyEntry
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
await prefetchUserNames({ ctx, account, log });
|
|
67
|
+
const enrichParams = { ctx, accountScopedCfg, account, log };
|
|
68
|
+
const [mediaResult, quotedContent] = await Promise.all([
|
|
69
|
+
resolveMedia(enrichParams),
|
|
70
|
+
resolveQuotedContent(enrichParams)
|
|
71
|
+
]);
|
|
72
|
+
if (mediaResult.mediaList.length > 0) {
|
|
73
|
+
ctx = {
|
|
74
|
+
...ctx,
|
|
75
|
+
content: substituteMediaPaths(ctx.content, mediaResult.mediaList)
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const core = LarkClient.runtime;
|
|
79
|
+
const isGroup = ctx.chatType === "group";
|
|
80
|
+
const dmPolicy = accountFeishuCfg?.dmPolicy ?? "pairing";
|
|
81
|
+
const groupConfig = isGroup ? resolveFeishuGroupConfig({ cfg: accountFeishuCfg, groupId: ctx.chatId }) : void 0;
|
|
82
|
+
const defaultGroupConfig = isGroup ? accountFeishuCfg?.groups?.["*"] : void 0;
|
|
83
|
+
const configuredGroupAllowFrom = (() => {
|
|
84
|
+
if (!isGroup) return void 0;
|
|
85
|
+
const { senderAllowFrom } = splitLegacyGroupAllowFrom(accountFeishuCfg?.groupAllowFrom ?? []);
|
|
86
|
+
const senderGroupAllowFrom = senderAllowFrom;
|
|
87
|
+
const perGroupAllowFrom = (groupConfig?.allowFrom ?? []).map(String);
|
|
88
|
+
const defaultSenderAllowFrom = !groupConfig && defaultGroupConfig?.allowFrom ? defaultGroupConfig.allowFrom.map(String) : [];
|
|
89
|
+
const combined = [...senderGroupAllowFrom, ...perGroupAllowFrom, ...defaultSenderAllowFrom];
|
|
90
|
+
if (combined.length > 0) return combined;
|
|
91
|
+
const explicitSenderPolicy = groupConfig?.groupPolicy ?? defaultGroupConfig?.groupPolicy ?? accountFeishuCfg?.groupPolicy;
|
|
92
|
+
return explicitSenderPolicy === "open" ? ["*"] : [];
|
|
93
|
+
})();
|
|
94
|
+
const { commandAuthorized } = await resolveSenderCommandAuthorization({
|
|
95
|
+
rawBody: ctx.content,
|
|
96
|
+
cfg: accountScopedCfg,
|
|
97
|
+
isGroup,
|
|
98
|
+
dmPolicy,
|
|
99
|
+
configuredAllowFrom: (accountFeishuCfg?.allowFrom ?? []).map(String),
|
|
100
|
+
configuredGroupAllowFrom,
|
|
101
|
+
senderId: ctx.senderId,
|
|
102
|
+
isSenderAllowed: (senderId, allowFrom) => isNormalizedSenderAllowed({ senderId, allowFrom }),
|
|
103
|
+
readAllowFromStore: () => readFeishuAllowFromStore(account.accountId),
|
|
104
|
+
shouldComputeCommandAuthorized: core.channel.commands.shouldComputeCommandAuthorized,
|
|
105
|
+
resolveCommandAuthorizedFromAuthorizers: core.channel.commands.resolveCommandAuthorizedFromAuthorizers
|
|
106
|
+
});
|
|
107
|
+
try {
|
|
108
|
+
await dispatchToAgent({
|
|
109
|
+
ctx,
|
|
110
|
+
permissionError,
|
|
111
|
+
mediaPayload: mediaResult.payload,
|
|
112
|
+
quotedContent,
|
|
113
|
+
account,
|
|
114
|
+
accountScopedCfg,
|
|
115
|
+
runtime,
|
|
116
|
+
chatHistories,
|
|
117
|
+
historyLimit,
|
|
118
|
+
replyToMessageId,
|
|
119
|
+
commandAuthorized,
|
|
120
|
+
groupConfig,
|
|
121
|
+
defaultGroupConfig,
|
|
122
|
+
skipTyping
|
|
123
|
+
});
|
|
124
|
+
} catch (err) {
|
|
125
|
+
error(`feishu[${account.accountId}]: failed to dispatch message: ${String(err)}`);
|
|
126
|
+
logger.error(`dispatch failed: ${String(err)} (elapsed=${ticketElapsed()}ms)`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export {
|
|
130
|
+
handleFeishuMessage
|
|
131
|
+
};
|
|
132
|
+
//# sourceMappingURL=handler.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/messaging/inbound/handler.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * Inbound message handling pipeline for the Lark/Feishu channel plugin.\n *\n * Orchestrates a seven-stage pipeline:\n * 1. Account resolution\n * 2. Event parsing \u2192 parse.ts (merge_forward expanded in-place)\n * 3. Sender enrichment \u2192 enrich.ts (lightweight, before gate)\n * 4. Policy gate \u2192 gate.ts\n * 5. User name prefetch \u2192 enrich.ts (batch cache warm-up)\n * 6. Content resolution \u2192 enrich.ts (media / quote, parallel)\n * 7. Agent dispatch \u2192 dispatch.ts\n */\n\nimport type { ClawdbotConfig, RuntimeEnv } from 'openclaw/plugin-sdk';\nimport {\n recordPendingHistoryEntryIfEnabled,\n DEFAULT_GROUP_HISTORY_LIMIT,\n resolveSenderCommandAuthorization,\n isNormalizedSenderAllowed,\n type HistoryEntry,\n} from 'openclaw/plugin-sdk';\nimport type { FeishuMessageEvent } from '../types';\nimport { getLarkAccount } from '../../core/accounts';\nimport { LarkClient } from '../../core/lark-client';\nimport { larkLogger } from '../../core/lark-logger';\nimport { ticketElapsed } from '../../core/lark-ticket';\nimport { parseMessageEvent } from './parse';\nimport {\n resolveSenderInfo,\n prefetchUserNames,\n resolveMedia,\n resolveQuotedContent,\n substituteMediaPaths,\n} from './enrich';\nimport { checkMessageGate, readFeishuAllowFromStore, type GateResult } from './gate';\nimport { dispatchToAgent } from './dispatch';\nimport { resolveFeishuGroupConfig, splitLegacyGroupAllowFrom } from './policy';\nimport { threadScopedKey } from '../../channel/chat-queue';\n\nconst logger = larkLogger('inbound/handler');\n\n// ---------------------------------------------------------------------------\n// Public: handle inbound message\n// ---------------------------------------------------------------------------\n\nexport async function handleFeishuMessage(params: {\n cfg: ClawdbotConfig;\n event: FeishuMessageEvent;\n botOpenId?: string;\n runtime?: RuntimeEnv;\n chatHistories?: Map<string, HistoryEntry[]>;\n accountId?: string;\n /** Override the message ID used for reply threading (typing indicators,\n * card replies, etc.). Useful for synthetic messages whose message_id\n * is not a real Feishu message ID. */\n replyToMessageId?: string;\n /** When true, skip the policy gate (mention requirement, allowlist).\n * Used for synthetic messages that are not real user messages. */\n forceMention?: boolean;\n /** When true, skip the typing indicator for this dispatch (e.g. reactions). */\n skipTyping?: boolean;\n}): Promise<void> {\n const { cfg, event, botOpenId, runtime, chatHistories, accountId, replyToMessageId, forceMention, skipTyping } =\n params;\n\n // 1. Account resolution\n const account = getLarkAccount(cfg, accountId);\n const accountFeishuCfg = account.config;\n\n // \u2605 \u591A\u8D26\u53F7\u914D\u7F6E\u9694\u79BB\uFF1A\u6784\u9020 account \u7EA7\u522B\u7684 ClawdbotConfig\n //\n // \u5728\u591A\u8D26\u53F7\u573A\u666F\u4E0B\uFF0C\u6BCF\u4E2A account \u53EF\u4EE5\u72EC\u7ACB\u914D\u7F6E groupPolicy / requireMention\n // \u7B49\u7B56\u7565\u3002\u4F46 SDK \u7684 resolveGroupPolicy / resolveRequireMention \u7B49\u51FD\u6570\u4ECE\n // cfg.channels.feishu \u8BFB\u53D6\u914D\u7F6E\uFF0C\u800C cfg \u662F\u9876\u5C42\u5168\u5C40\u914D\u7F6E\uFF0C\u4E0D\u5305\u542B per-account\n // \u7684\u8986\u76D6\u503C\u3002\n //\n // \u8FD9\u91CC\u5C06 cfg.channels.feishu \u66FF\u6362\u4E3A\u7ECF\u8FC7 getLarkAccount() \u5408\u5E76\u540E\u7684\n // accountFeishuCfg\uFF08= base config + account override\uFF09\uFF0C\u786E\u4FDD\u4E0B\u6E38\u6240\u6709 SDK \u8C03\u7528\n // \u90FD\u80FD\u6B63\u786E\u8BFB\u53D6\u5F53\u524D account \u7684\u914D\u7F6E\u3002\n const accountScopedCfg: ClawdbotConfig = {\n ...cfg,\n channels: { ...cfg.channels, feishu: accountFeishuCfg },\n };\n\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 // 2. Parse event \u2192 MessageContext (merge_forward expanded in-place)\n let ctx = await parseMessageEvent(event, botOpenId, {\n cfg: accountScopedCfg,\n accountId: account.accountId,\n });\n\n // 3. Enrich (lightweight): sender name + permission error tracking\n const { ctx: enrichedCtx, permissionError } = await resolveSenderInfo({\n ctx,\n account,\n log,\n });\n ctx = enrichedCtx;\n\n log(`feishu[${account.accountId}]: received message from ${ctx.senderId} in ${ctx.chatId} (${ctx.chatType})`);\n logger.info(`received from ${ctx.senderId} in ${ctx.chatId} (${ctx.chatType})`);\n\n const historyLimit = Math.max(\n 0,\n accountFeishuCfg?.historyLimit ?? accountScopedCfg.messages?.groupChat?.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT,\n );\n\n // 4. Gate: policy / access-control checks (skipped for synthetic messages)\n const gate = forceMention\n ? ({ allowed: true } as GateResult)\n : await checkMessageGate({ ctx, accountFeishuCfg, account, accountScopedCfg, log });\n if (!gate.allowed) {\n if (gate.reason === 'no_mention') {\n logger.info(`rejected: no bot mention in group ${ctx.chatId}`);\n }\n // Record history entry if the gate produced one (group no-mention case)\n if (gate.historyEntry && chatHistories) {\n const historyKey = threadScopedKey(ctx.chatId, ctx.threadId);\n recordPendingHistoryEntryIfEnabled({\n historyMap: chatHistories,\n historyKey,\n limit: historyLimit,\n entry: gate.historyEntry,\n });\n }\n return;\n }\n\n // 5. Batch pre-warm user name cache (sender + mentions)\n await prefetchUserNames({ ctx, account, log });\n\n // 6. Enrich (heavyweight, after gate \u2014 parallel where possible)\n const enrichParams = { ctx, accountScopedCfg, account, log };\n const [mediaResult, quotedContent] = await Promise.all([\n resolveMedia(enrichParams),\n resolveQuotedContent(enrichParams),\n ]);\n\n // 6b. Replace Feishu file-key placeholders in content with local\n // file paths so the SDK can detect images for native vision and\n // the AI receives meaningful file references.\n if (mediaResult.mediaList.length > 0) {\n ctx = {\n ...ctx,\n content: substituteMediaPaths(ctx.content, mediaResult.mediaList),\n };\n }\n\n // 7. Compute commandAuthorized via SDK access group command gating\n const core = LarkClient.runtime;\n const isGroup = ctx.chatType === 'group';\n const dmPolicy = accountFeishuCfg?.dmPolicy ?? 'pairing';\n\n // Resolve per-group config early \u2014 shared by both command authorization\n // and dispatch (step 8).\n const groupConfig = isGroup ? resolveFeishuGroupConfig({ cfg: accountFeishuCfg, groupId: ctx.chatId }) : undefined;\n const defaultGroupConfig = isGroup ? accountFeishuCfg?.groups?.['*'] : undefined;\n\n // Build the sender allowlist for command authorization in group context.\n // Excludes legacy oc_xxx chat-id entries (group admission, not sender identity).\n //\n // When the explicit group sender policy is \"open\", pass [\"*\"] to align\n // command authorization with chat access (if you can chat, you can run\n // commands). When no policy is configured (undefined fallback), default to\n // allowlist behaviour \u2014 only users in accountFeishuCfg.allowFrom (owner list) or\n // an explicit groupAllowFrom/per-group allowFrom can run commands.\n const configuredGroupAllowFrom = (() => {\n if (!isGroup) return undefined;\n // Exclude legacy oc_xxx chat-id entries from groupAllowFrom (sender filter only).\n const { senderAllowFrom } = splitLegacyGroupAllowFrom(accountFeishuCfg?.groupAllowFrom ?? []);\n const senderGroupAllowFrom = senderAllowFrom;\n const perGroupAllowFrom = (groupConfig?.allowFrom ?? []).map(String);\n const defaultSenderAllowFrom =\n !groupConfig && defaultGroupConfig?.allowFrom ? defaultGroupConfig.allowFrom.map(String) : [];\n const combined = [...senderGroupAllowFrom, ...perGroupAllowFrom, ...defaultSenderAllowFrom];\n if (combined.length > 0) return combined;\n // No allowFrom list configured \u2014 check if sender policy is explicitly \"open\".\n // Do NOT fall back to \"open\" as a default: unset policy \u2192 allowlist behaviour.\n const explicitSenderPolicy =\n groupConfig?.groupPolicy ?? defaultGroupConfig?.groupPolicy ?? accountFeishuCfg?.groupPolicy;\n return explicitSenderPolicy === 'open' ? ['*'] : [];\n })();\n\n const { commandAuthorized } = await resolveSenderCommandAuthorization({\n rawBody: ctx.content,\n cfg: accountScopedCfg,\n isGroup,\n dmPolicy,\n configuredAllowFrom: (accountFeishuCfg?.allowFrom ?? []).map(String),\n configuredGroupAllowFrom,\n senderId: ctx.senderId,\n isSenderAllowed: (senderId, allowFrom) => isNormalizedSenderAllowed({ senderId, allowFrom }),\n readAllowFromStore: () => readFeishuAllowFromStore(account.accountId),\n shouldComputeCommandAuthorized: core.channel.commands.shouldComputeCommandAuthorized,\n resolveCommandAuthorizedFromAuthorizers: core.channel.commands.resolveCommandAuthorizedFromAuthorizers,\n });\n\n // 8. Dispatch to agent\n // groupConfig and defaultGroupConfig are already resolved above.\n\n try {\n await dispatchToAgent({\n ctx,\n permissionError,\n mediaPayload: mediaResult.payload,\n quotedContent,\n account,\n accountScopedCfg,\n runtime,\n chatHistories,\n historyLimit,\n replyToMessageId,\n commandAuthorized,\n groupConfig,\n defaultGroupConfig,\n skipTyping,\n });\n } catch (err) {\n error(`feishu[${account.accountId}]: failed to dispatch message: ${String(err)}`);\n logger.error(`dispatch failed: ${String(err)} (elapsed=${ticketElapsed()}ms)`);\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|