@colinlu50/openclaw-lark-stream 2026.3.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +141 -0
- package/README.zh.md +70 -0
- package/bin/openclaw-lark.js +48 -0
- package/index.d.ts +36 -0
- package/index.js +118 -0
- package/openclaw.plugin.json +10 -0
- package/package.json +66 -0
- package/skills/feishu-bitable/SKILL.md +248 -0
- package/skills/feishu-bitable/references/examples.md +813 -0
- package/skills/feishu-bitable/references/field-properties.md +763 -0
- package/skills/feishu-bitable/references/record-values.md +911 -0
- package/skills/feishu-calendar/SKILL.md +244 -0
- package/skills/feishu-channel-rules/SKILL.md +24 -0
- package/skills/feishu-channel-rules/references/markdown-syntax.md +138 -0
- package/skills/feishu-create-doc/SKILL.md +719 -0
- package/skills/feishu-fetch-doc/SKILL.md +93 -0
- package/skills/feishu-im-read/SKILL.md +163 -0
- package/skills/feishu-task/SKILL.md +293 -0
- package/skills/feishu-troubleshoot/SKILL.md +70 -0
- package/skills/feishu-update-doc/SKILL.md +285 -0
- package/src/card/builder.d.ts +106 -0
- package/src/card/builder.js +443 -0
- package/src/card/cardkit.d.ts +90 -0
- package/src/card/cardkit.js +181 -0
- package/src/card/flush-controller.d.ts +45 -0
- package/src/card/flush-controller.js +134 -0
- package/src/card/image-resolver.d.ts +45 -0
- package/src/card/image-resolver.js +112 -0
- package/src/card/markdown-style.d.ts +16 -0
- package/src/card/markdown-style.js +97 -0
- package/src/card/reply-dispatcher-types.d.ts +120 -0
- package/src/card/reply-dispatcher-types.js +57 -0
- package/src/card/reply-dispatcher.d.ts +15 -0
- package/src/card/reply-dispatcher.js +299 -0
- package/src/card/reply-mode.d.ts +38 -0
- package/src/card/reply-mode.js +65 -0
- package/src/card/streaming-card-controller.d.ts +101 -0
- package/src/card/streaming-card-controller.js +810 -0
- package/src/card/unavailable-guard.d.ts +35 -0
- package/src/card/unavailable-guard.js +83 -0
- package/src/channel/abort-detect.d.ts +34 -0
- package/src/channel/abort-detect.js +124 -0
- package/src/channel/chat-queue.d.ts +41 -0
- package/src/channel/chat-queue.js +58 -0
- package/src/channel/config-adapter.d.ts +23 -0
- package/src/channel/config-adapter.js +101 -0
- package/src/channel/directory.d.ts +57 -0
- package/src/channel/directory.js +191 -0
- package/src/channel/event-handlers.d.ts +15 -0
- package/src/channel/event-handlers.js +221 -0
- package/src/channel/monitor.d.ts +17 -0
- package/src/channel/monitor.js +129 -0
- package/src/channel/onboarding-config.d.ts +17 -0
- package/src/channel/onboarding-config.js +88 -0
- package/src/channel/onboarding-migrate.d.ts +25 -0
- package/src/channel/onboarding-migrate.js +67 -0
- package/src/channel/onboarding.d.ts +12 -0
- package/src/channel/onboarding.js +296 -0
- package/src/channel/plugin.d.ts +13 -0
- package/src/channel/plugin.js +278 -0
- package/src/channel/probe.d.ts +14 -0
- package/src/channel/probe.js +21 -0
- package/src/channel/types.d.ts +36 -0
- package/src/channel/types.js +7 -0
- package/src/commands/auth.d.ts +21 -0
- package/src/commands/auth.js +161 -0
- package/src/commands/diagnose.d.ts +69 -0
- package/src/commands/diagnose.js +807 -0
- package/src/commands/doctor.d.ts +26 -0
- package/src/commands/doctor.js +584 -0
- package/src/commands/index.d.ts +25 -0
- package/src/commands/index.js +212 -0
- package/src/commands/locale.d.ts +7 -0
- package/src/commands/locale.js +7 -0
- package/src/core/accounts.d.ts +37 -0
- package/src/core/accounts.js +163 -0
- package/src/core/agent-config.d.ts +100 -0
- package/src/core/agent-config.js +139 -0
- package/src/core/api-error.d.ts +48 -0
- package/src/core/api-error.js +112 -0
- package/src/core/app-owner-fallback.d.ts +21 -0
- package/src/core/app-owner-fallback.js +38 -0
- package/src/core/app-scope-checker.d.ts +87 -0
- package/src/core/app-scope-checker.js +190 -0
- package/src/core/auth-errors.d.ts +144 -0
- package/src/core/auth-errors.js +154 -0
- package/src/core/chat-info-cache.d.ts +57 -0
- package/src/core/chat-info-cache.js +152 -0
- package/src/core/config-schema.d.ts +448 -0
- package/src/core/config-schema.js +200 -0
- package/src/core/device-flow.d.ts +77 -0
- package/src/core/device-flow.js +212 -0
- package/src/core/domains.d.ts +18 -0
- package/src/core/domains.js +28 -0
- package/src/core/feishu-fetch.d.ts +18 -0
- package/src/core/feishu-fetch.js +25 -0
- package/src/core/footer-config.d.ts +24 -0
- package/src/core/footer-config.js +39 -0
- package/src/core/lark-client.d.ts +108 -0
- package/src/core/lark-client.js +353 -0
- package/src/core/lark-logger.d.ts +23 -0
- package/src/core/lark-logger.js +154 -0
- package/src/core/lark-ticket.d.ts +29 -0
- package/src/core/lark-ticket.js +35 -0
- package/src/core/message-unavailable.d.ts +53 -0
- package/src/core/message-unavailable.js +130 -0
- package/src/core/owner-policy.d.ts +31 -0
- package/src/core/owner-policy.js +52 -0
- package/src/core/permission-url.d.ts +22 -0
- package/src/core/permission-url.js +72 -0
- package/src/core/raw-request.d.ts +27 -0
- package/src/core/raw-request.js +62 -0
- package/src/core/scope-manager.d.ts +168 -0
- package/src/core/scope-manager.js +213 -0
- package/src/core/security-check.d.ts +72 -0
- package/src/core/security-check.js +174 -0
- package/src/core/shutdown-hooks.d.ts +22 -0
- package/src/core/shutdown-hooks.js +56 -0
- package/src/core/targets.d.ts +60 -0
- package/src/core/targets.js +164 -0
- package/src/core/token-store.d.ts +54 -0
- package/src/core/token-store.js +314 -0
- package/src/core/tool-client.d.ts +176 -0
- package/src/core/tool-client.js +380 -0
- package/src/core/tool-scopes.d.ts +153 -0
- package/src/core/tool-scopes.js +326 -0
- package/src/core/tools-config.d.ts +55 -0
- package/src/core/tools-config.js +137 -0
- package/src/core/types.d.ts +87 -0
- package/src/core/types.js +11 -0
- package/src/core/uat-client.d.ts +46 -0
- package/src/core/uat-client.js +187 -0
- package/src/core/version.d.ts +25 -0
- package/src/core/version.js +49 -0
- package/src/messaging/converters/audio.d.ts +8 -0
- package/src/messaging/converters/audio.js +21 -0
- package/src/messaging/converters/calendar.d.ts +13 -0
- package/src/messaging/converters/calendar.js +50 -0
- package/src/messaging/converters/content-converter.d.ts +41 -0
- package/src/messaging/converters/content-converter.js +106 -0
- package/src/messaging/converters/file.d.ts +8 -0
- package/src/messaging/converters/file.js +20 -0
- package/src/messaging/converters/folder.d.ts +8 -0
- package/src/messaging/converters/folder.js +20 -0
- package/src/messaging/converters/hongbao.d.ts +8 -0
- package/src/messaging/converters/hongbao.js +16 -0
- package/src/messaging/converters/image.d.ts +8 -0
- package/src/messaging/converters/image.js +18 -0
- package/src/messaging/converters/index.d.ts +8 -0
- package/src/messaging/converters/index.js +50 -0
- package/src/messaging/converters/interactive/card-converter.d.ts +76 -0
- package/src/messaging/converters/interactive/card-converter.js +1173 -0
- package/src/messaging/converters/interactive/card-utils.d.ts +9 -0
- package/src/messaging/converters/interactive/card-utils.js +42 -0
- package/src/messaging/converters/interactive/index.d.ts +8 -0
- package/src/messaging/converters/interactive/index.js +21 -0
- package/src/messaging/converters/interactive/legacy.d.ts +11 -0
- package/src/messaging/converters/interactive/legacy.js +57 -0
- package/src/messaging/converters/interactive/types.d.ts +23 -0
- package/src/messaging/converters/interactive/types.js +24 -0
- package/src/messaging/converters/location.d.ts +8 -0
- package/src/messaging/converters/location.js +19 -0
- package/src/messaging/converters/merge-forward.d.ts +32 -0
- package/src/messaging/converters/merge-forward.js +225 -0
- package/src/messaging/converters/post.d.ts +11 -0
- package/src/messaging/converters/post.js +135 -0
- package/src/messaging/converters/share.d.ts +9 -0
- package/src/messaging/converters/share.js +23 -0
- package/src/messaging/converters/sticker.d.ts +8 -0
- package/src/messaging/converters/sticker.js +18 -0
- package/src/messaging/converters/system.d.ts +12 -0
- package/src/messaging/converters/system.js +32 -0
- package/src/messaging/converters/text.d.ts +8 -0
- package/src/messaging/converters/text.js +14 -0
- package/src/messaging/converters/todo.d.ts +8 -0
- package/src/messaging/converters/todo.js +41 -0
- package/src/messaging/converters/types.d.ts +107 -0
- package/src/messaging/converters/types.js +7 -0
- package/src/messaging/converters/unknown.d.ts +8 -0
- package/src/messaging/converters/unknown.js +16 -0
- package/src/messaging/converters/utils.d.ts +22 -0
- package/src/messaging/converters/utils.js +51 -0
- package/src/messaging/converters/video-chat.d.ts +8 -0
- package/src/messaging/converters/video-chat.js +23 -0
- package/src/messaging/converters/video.d.ts +8 -0
- package/src/messaging/converters/video.js +32 -0
- package/src/messaging/converters/vote.d.ts +8 -0
- package/src/messaging/converters/vote.js +24 -0
- package/src/messaging/inbound/dedup.d.ts +59 -0
- package/src/messaging/inbound/dedup.js +116 -0
- package/src/messaging/inbound/dispatch-builders.d.ts +84 -0
- package/src/messaging/inbound/dispatch-builders.js +152 -0
- package/src/messaging/inbound/dispatch-commands.d.ts +27 -0
- package/src/messaging/inbound/dispatch-commands.js +112 -0
- package/src/messaging/inbound/dispatch-context.d.ts +67 -0
- package/src/messaging/inbound/dispatch-context.js +136 -0
- package/src/messaging/inbound/dispatch.d.ts +47 -0
- package/src/messaging/inbound/dispatch.js +264 -0
- package/src/messaging/inbound/enrich.d.ts +102 -0
- package/src/messaging/inbound/enrich.js +227 -0
- package/src/messaging/inbound/gate-effects.d.ts +23 -0
- package/src/messaging/inbound/gate-effects.js +43 -0
- package/src/messaging/inbound/gate.d.ts +60 -0
- package/src/messaging/inbound/gate.js +233 -0
- package/src/messaging/inbound/handler.d.ts +35 -0
- package/src/messaging/inbound/handler.js +173 -0
- package/src/messaging/inbound/media-resolver.d.ts +32 -0
- package/src/messaging/inbound/media-resolver.js +87 -0
- package/src/messaging/inbound/mention.d.ts +39 -0
- package/src/messaging/inbound/mention.js +81 -0
- package/src/messaging/inbound/parse-io.d.ts +50 -0
- package/src/messaging/inbound/parse-io.js +81 -0
- package/src/messaging/inbound/parse.d.ts +28 -0
- package/src/messaging/inbound/parse.js +106 -0
- package/src/messaging/inbound/permission.d.ts +17 -0
- package/src/messaging/inbound/permission.js +40 -0
- package/src/messaging/inbound/policy.d.ts +94 -0
- package/src/messaging/inbound/policy.js +160 -0
- package/src/messaging/inbound/reaction-handler.d.ts +61 -0
- package/src/messaging/inbound/reaction-handler.js +221 -0
- package/src/messaging/inbound/user-name-cache.d.ts +82 -0
- package/src/messaging/inbound/user-name-cache.js +241 -0
- package/src/messaging/outbound/actions.d.ts +16 -0
- package/src/messaging/outbound/actions.js +309 -0
- package/src/messaging/outbound/chat-manage.d.ts +64 -0
- package/src/messaging/outbound/chat-manage.js +111 -0
- package/src/messaging/outbound/deliver.d.ts +155 -0
- package/src/messaging/outbound/deliver.js +298 -0
- package/src/messaging/outbound/fetch.d.ts +12 -0
- package/src/messaging/outbound/fetch.js +12 -0
- package/src/messaging/outbound/forward.d.ts +26 -0
- package/src/messaging/outbound/forward.js +48 -0
- package/src/messaging/outbound/media-url-utils.d.ts +29 -0
- package/src/messaging/outbound/media-url-utils.js +130 -0
- package/src/messaging/outbound/media.d.ts +260 -0
- package/src/messaging/outbound/media.js +758 -0
- package/src/messaging/outbound/outbound.d.ts +89 -0
- package/src/messaging/outbound/outbound.js +121 -0
- package/src/messaging/outbound/reactions.d.ts +124 -0
- package/src/messaging/outbound/reactions.js +378 -0
- package/src/messaging/outbound/send.d.ts +152 -0
- package/src/messaging/outbound/send.js +355 -0
- package/src/messaging/outbound/typing.d.ts +71 -0
- package/src/messaging/outbound/typing.js +179 -0
- package/src/messaging/shared/message-lookup.d.ts +54 -0
- package/src/messaging/shared/message-lookup.js +117 -0
- package/src/messaging/types.d.ts +176 -0
- package/src/messaging/types.js +10 -0
- package/src/tools/auto-auth.d.ts +56 -0
- package/src/tools/auto-auth.js +919 -0
- package/src/tools/helpers.d.ts +260 -0
- package/src/tools/helpers.js +364 -0
- package/src/tools/mcp/doc/create.d.ts +12 -0
- package/src/tools/mcp/doc/create.js +44 -0
- package/src/tools/mcp/doc/fetch.d.ts +12 -0
- package/src/tools/mcp/doc/fetch.js +36 -0
- package/src/tools/mcp/doc/index.d.ts +12 -0
- package/src/tools/mcp/doc/index.js +41 -0
- package/src/tools/mcp/doc/update.d.ts +12 -0
- package/src/tools/mcp/doc/update.js +61 -0
- package/src/tools/mcp/shared.d.ts +59 -0
- package/src/tools/mcp/shared.js +226 -0
- package/src/tools/oapi/bitable/app-table-field.d.ts +16 -0
- package/src/tools/oapi/bitable/app-table-field.js +222 -0
- package/src/tools/oapi/bitable/app-table-record.d.ts +20 -0
- package/src/tools/oapi/bitable/app-table-record.js +436 -0
- package/src/tools/oapi/bitable/app-table-view.d.ts +17 -0
- package/src/tools/oapi/bitable/app-table-view.js +195 -0
- package/src/tools/oapi/bitable/app-table.d.ts +19 -0
- package/src/tools/oapi/bitable/app-table.js +247 -0
- package/src/tools/oapi/bitable/app.d.ts +18 -0
- package/src/tools/oapi/bitable/app.js +186 -0
- package/src/tools/oapi/bitable/index.d.ts +9 -0
- package/src/tools/oapi/bitable/index.js +9 -0
- package/src/tools/oapi/calendar/calendar.d.ts +15 -0
- package/src/tools/oapi/calendar/calendar.js +122 -0
- package/src/tools/oapi/calendar/event-attendee.d.ts +16 -0
- package/src/tools/oapi/calendar/event-attendee.js +263 -0
- package/src/tools/oapi/calendar/event.d.ts +16 -0
- package/src/tools/oapi/calendar/event.js +709 -0
- package/src/tools/oapi/calendar/freebusy.d.ts +13 -0
- package/src/tools/oapi/calendar/freebusy.js +111 -0
- package/src/tools/oapi/calendar/index.d.ts +8 -0
- package/src/tools/oapi/calendar/index.js +8 -0
- package/src/tools/oapi/chat/chat.d.ts +16 -0
- package/src/tools/oapi/chat/chat.js +124 -0
- package/src/tools/oapi/chat/index.d.ts +10 -0
- package/src/tools/oapi/chat/index.js +15 -0
- package/src/tools/oapi/chat/members.d.ts +11 -0
- package/src/tools/oapi/chat/members.js +81 -0
- package/src/tools/oapi/common/get-user.d.ts +12 -0
- package/src/tools/oapi/common/get-user.js +106 -0
- package/src/tools/oapi/common/index.d.ts +6 -0
- package/src/tools/oapi/common/index.js +6 -0
- package/src/tools/oapi/common/search-user.d.ts +11 -0
- package/src/tools/oapi/common/search-user.js +73 -0
- package/src/tools/oapi/drive/doc-comments.d.ts +15 -0
- package/src/tools/oapi/drive/doc-comments.js +279 -0
- package/src/tools/oapi/drive/doc-media.d.ts +19 -0
- package/src/tools/oapi/drive/doc-media.js +335 -0
- package/src/tools/oapi/drive/file.d.ts +19 -0
- package/src/tools/oapi/drive/file.js +483 -0
- package/src/tools/oapi/drive/index.d.ts +12 -0
- package/src/tools/oapi/drive/index.js +36 -0
- package/src/tools/oapi/helpers.d.ts +182 -0
- package/src/tools/oapi/helpers.js +354 -0
- package/src/tools/oapi/im/format-messages.d.ts +50 -0
- package/src/tools/oapi/im/format-messages.js +165 -0
- package/src/tools/oapi/im/index.d.ts +10 -0
- package/src/tools/oapi/im/index.js +17 -0
- package/src/tools/oapi/im/message-read.d.ts +13 -0
- package/src/tools/oapi/im/message-read.js +411 -0
- package/src/tools/oapi/im/message.d.ts +16 -0
- package/src/tools/oapi/im/message.js +149 -0
- package/src/tools/oapi/im/resource.d.ts +13 -0
- package/src/tools/oapi/im/resource.js +150 -0
- package/src/tools/oapi/im/time-utils.d.ts +46 -0
- package/src/tools/oapi/im/time-utils.js +201 -0
- package/src/tools/oapi/im/user-name-uat.d.ts +26 -0
- package/src/tools/oapi/im/user-name-uat.js +140 -0
- package/src/tools/oapi/index.d.ts +11 -0
- package/src/tools/oapi/index.js +58 -0
- package/src/tools/oapi/sdk-types.d.ts +96 -0
- package/src/tools/oapi/sdk-types.js +12 -0
- package/src/tools/oapi/search/doc-search.d.ts +13 -0
- package/src/tools/oapi/search/doc-search.js +191 -0
- package/src/tools/oapi/search/index.d.ts +12 -0
- package/src/tools/oapi/search/index.js +33 -0
- package/src/tools/oapi/sheets/index.d.ts +12 -0
- package/src/tools/oapi/sheets/index.js +31 -0
- package/src/tools/oapi/sheets/sheet.d.ts +16 -0
- package/src/tools/oapi/sheets/sheet.js +652 -0
- package/src/tools/oapi/task/comment.d.ts +15 -0
- package/src/tools/oapi/task/comment.js +140 -0
- package/src/tools/oapi/task/index.d.ts +8 -0
- package/src/tools/oapi/task/index.js +8 -0
- package/src/tools/oapi/task/subtask.d.ts +14 -0
- package/src/tools/oapi/task/subtask.js +162 -0
- package/src/tools/oapi/task/task.d.ts +16 -0
- package/src/tools/oapi/task/task.js +344 -0
- package/src/tools/oapi/task/tasklist.d.ts +21 -0
- package/src/tools/oapi/task/tasklist.js +321 -0
- package/src/tools/oapi/wiki/index.d.ts +12 -0
- package/src/tools/oapi/wiki/index.js +34 -0
- package/src/tools/oapi/wiki/space-node.d.ts +17 -0
- package/src/tools/oapi/wiki/space-node.js +230 -0
- package/src/tools/oapi/wiki/space.d.ts +15 -0
- package/src/tools/oapi/wiki/space.js +130 -0
- package/src/tools/oauth-batch-auth.d.ts +11 -0
- package/src/tools/oauth-batch-auth.js +142 -0
- package/src/tools/oauth-cards.d.ts +39 -0
- package/src/tools/oauth-cards.js +315 -0
- package/src/tools/oauth.d.ts +47 -0
- package/src/tools/oauth.js +620 -0
- package/src/tools/onboarding-auth.d.ts +27 -0
- package/src/tools/onboarding-auth.js +130 -0
- package/src/tools/tat/im/index.d.ts +15 -0
- package/src/tools/tat/im/index.js +18 -0
- package/src/tools/tat/im/resource.d.ts +15 -0
- package/src/tools/tat/im/resource.js +157 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Directory listing for Feishu peers (users) and groups.
|
|
6
|
+
*
|
|
7
|
+
* Provides both config-based (offline) and live API directory
|
|
8
|
+
* lookups so the outbound subsystem and UI can resolve targets.
|
|
9
|
+
*/
|
|
10
|
+
import { getLarkAccount } from '../core/accounts';
|
|
11
|
+
import { LarkClient } from '../core/lark-client';
|
|
12
|
+
import { normalizeFeishuTarget } from '../core/targets';
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Shared helpers
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
/** Case-insensitive substring match on id and optional name. */
|
|
17
|
+
function matchesQuery(id, name, query) {
|
|
18
|
+
if (!query)
|
|
19
|
+
return true;
|
|
20
|
+
return id.toLowerCase().includes(query) || (name?.toLowerCase().includes(query) ?? false);
|
|
21
|
+
}
|
|
22
|
+
/** Filter items and apply optional limit. */
|
|
23
|
+
function applyLimitSlice(items, limit) {
|
|
24
|
+
return limit && limit > 0 ? items.slice(0, limit) : items;
|
|
25
|
+
}
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Config-based (offline) directory
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
/**
|
|
30
|
+
* List users known from the channel config (allowFrom + dms fields).
|
|
31
|
+
*
|
|
32
|
+
* Does not make any API calls -- useful when the bot is not yet
|
|
33
|
+
* connected or when credentials are unavailable.
|
|
34
|
+
*/
|
|
35
|
+
export async function listFeishuDirectoryPeers(params) {
|
|
36
|
+
const account = getLarkAccount(params.cfg, params.accountId);
|
|
37
|
+
const feishuCfg = account.config;
|
|
38
|
+
const q = params.query?.trim().toLowerCase() || '';
|
|
39
|
+
const ids = new Set();
|
|
40
|
+
// Collect from allowFrom entries.
|
|
41
|
+
for (const entry of feishuCfg?.allowFrom ?? []) {
|
|
42
|
+
const trimmed = String(entry).trim();
|
|
43
|
+
if (trimmed && trimmed !== '*') {
|
|
44
|
+
ids.add(trimmed);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Collect from per-user DM config keys.
|
|
48
|
+
for (const userId of Object.keys(feishuCfg?.dms ?? {})) {
|
|
49
|
+
const trimmed = userId.trim();
|
|
50
|
+
if (trimmed) {
|
|
51
|
+
ids.add(trimmed);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const peers = Array.from(ids)
|
|
55
|
+
.map((raw) => raw.trim())
|
|
56
|
+
.filter(Boolean)
|
|
57
|
+
.map((raw) => normalizeFeishuTarget(raw) ?? raw)
|
|
58
|
+
.filter((id) => matchesQuery(id, undefined, q))
|
|
59
|
+
.map((id) => ({ kind: 'user', id }));
|
|
60
|
+
return applyLimitSlice(peers, params.limit);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* List groups known from the channel config (groups + groupAllowFrom).
|
|
64
|
+
*/
|
|
65
|
+
export async function listFeishuDirectoryGroups(params) {
|
|
66
|
+
const account = getLarkAccount(params.cfg, params.accountId);
|
|
67
|
+
const feishuCfg = account.config;
|
|
68
|
+
const q = params.query?.trim().toLowerCase() || '';
|
|
69
|
+
const ids = new Set();
|
|
70
|
+
// Collect from per-group config keys.
|
|
71
|
+
for (const groupId of Object.keys(feishuCfg?.groups ?? {})) {
|
|
72
|
+
const trimmed = groupId.trim();
|
|
73
|
+
if (trimmed && trimmed !== '*') {
|
|
74
|
+
ids.add(trimmed);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Collect from groupAllowFrom entries.
|
|
78
|
+
for (const entry of feishuCfg?.groupAllowFrom ?? []) {
|
|
79
|
+
const trimmed = String(entry).trim();
|
|
80
|
+
if (trimmed && trimmed !== '*') {
|
|
81
|
+
ids.add(trimmed);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const groups = Array.from(ids)
|
|
85
|
+
.map((raw) => raw.trim())
|
|
86
|
+
.filter(Boolean)
|
|
87
|
+
.filter((id) => matchesQuery(id, undefined, q))
|
|
88
|
+
.map((id) => ({ kind: 'group', id }));
|
|
89
|
+
return applyLimitSlice(groups, params.limit);
|
|
90
|
+
}
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Live API directory
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
/**
|
|
95
|
+
* List users via the Feishu contact/v3/users API.
|
|
96
|
+
*
|
|
97
|
+
* Falls back to config-based listing when credentials are missing or
|
|
98
|
+
* the API call fails.
|
|
99
|
+
*/
|
|
100
|
+
export async function listFeishuDirectoryPeersLive(params) {
|
|
101
|
+
const account = getLarkAccount(params.cfg, params.accountId);
|
|
102
|
+
if (!account.configured) {
|
|
103
|
+
return listFeishuDirectoryPeers(params);
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const client = LarkClient.fromAccount(account).sdk;
|
|
107
|
+
const peers = [];
|
|
108
|
+
const limit = params.limit ?? 50;
|
|
109
|
+
if (limit <= 0)
|
|
110
|
+
return [];
|
|
111
|
+
const q = params.query?.trim().toLowerCase() || '';
|
|
112
|
+
let pageToken;
|
|
113
|
+
do {
|
|
114
|
+
const remaining = limit - peers.length;
|
|
115
|
+
const response = await client.contact.user.list({
|
|
116
|
+
params: {
|
|
117
|
+
page_size: Math.min(remaining, 50),
|
|
118
|
+
page_token: pageToken,
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
if (response.code !== 0 || !response.data?.items)
|
|
122
|
+
break;
|
|
123
|
+
for (const user of response.data.items) {
|
|
124
|
+
if (user.open_id && matchesQuery(user.open_id, user.name, q)) {
|
|
125
|
+
peers.push({
|
|
126
|
+
kind: 'user',
|
|
127
|
+
id: user.open_id,
|
|
128
|
+
name: user.name || undefined,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if (peers.length >= limit)
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
pageToken = response.data?.page_token;
|
|
135
|
+
} while (pageToken && peers.length < limit);
|
|
136
|
+
return peers;
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// Fallback to config-based listing on API failure.
|
|
140
|
+
return listFeishuDirectoryPeers(params);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* List groups via the Feishu im/v1/chats API.
|
|
145
|
+
*
|
|
146
|
+
* Falls back to config-based listing when credentials are missing or
|
|
147
|
+
* the API call fails.
|
|
148
|
+
*/
|
|
149
|
+
export async function listFeishuDirectoryGroupsLive(params) {
|
|
150
|
+
const account = getLarkAccount(params.cfg, params.accountId);
|
|
151
|
+
if (!account.configured) {
|
|
152
|
+
return listFeishuDirectoryGroups(params);
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
const client = LarkClient.fromAccount(account).sdk;
|
|
156
|
+
const groups = [];
|
|
157
|
+
const limit = params.limit ?? 50;
|
|
158
|
+
if (limit <= 0)
|
|
159
|
+
return [];
|
|
160
|
+
const q = params.query?.trim().toLowerCase() || '';
|
|
161
|
+
let pageToken;
|
|
162
|
+
do {
|
|
163
|
+
const remaining = limit - groups.length;
|
|
164
|
+
const response = await client.im.chat.list({
|
|
165
|
+
params: {
|
|
166
|
+
page_size: Math.min(remaining, 100),
|
|
167
|
+
page_token: pageToken,
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
if (response.code !== 0 || !response.data?.items)
|
|
171
|
+
break;
|
|
172
|
+
for (const chat of response.data.items) {
|
|
173
|
+
if (chat.chat_id && matchesQuery(chat.chat_id, chat.name, q)) {
|
|
174
|
+
groups.push({
|
|
175
|
+
kind: 'group',
|
|
176
|
+
id: chat.chat_id,
|
|
177
|
+
name: chat.name || undefined,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
if (groups.length >= limit)
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
pageToken = response.data?.page_token;
|
|
184
|
+
} while (pageToken && groups.length < limit);
|
|
185
|
+
return groups;
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// Fallback to config-based listing on API failure.
|
|
189
|
+
return listFeishuDirectoryGroups(params);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Event handlers for the Feishu WebSocket monitor.
|
|
6
|
+
*
|
|
7
|
+
* Extracted from monitor.ts to improve testability and reduce
|
|
8
|
+
* function size. Each handler receives a MonitorContext with all
|
|
9
|
+
* dependencies needed to process the event.
|
|
10
|
+
*/
|
|
11
|
+
import type { MonitorContext } from './types';
|
|
12
|
+
export declare function handleMessageEvent(ctx: MonitorContext, data: unknown): Promise<void>;
|
|
13
|
+
export declare function handleReactionEvent(ctx: MonitorContext, data: unknown): Promise<void>;
|
|
14
|
+
export declare function handleBotMembershipEvent(ctx: MonitorContext, data: unknown, action: 'added' | 'removed'): Promise<void>;
|
|
15
|
+
export declare function handleCardActionEvent(ctx: MonitorContext, data: unknown): Promise<unknown>;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Event handlers for the Feishu WebSocket monitor.
|
|
6
|
+
*
|
|
7
|
+
* Extracted from monitor.ts to improve testability and reduce
|
|
8
|
+
* function size. Each handler receives a MonitorContext with all
|
|
9
|
+
* dependencies needed to process the event.
|
|
10
|
+
*/
|
|
11
|
+
import { handleFeishuMessage } from '../messaging/inbound/handler';
|
|
12
|
+
import { handleFeishuReaction, resolveReactionContext } from '../messaging/inbound/reaction-handler';
|
|
13
|
+
import { isMessageExpired } from '../messaging/inbound/dedup';
|
|
14
|
+
import { withTicket } from '../core/lark-ticket';
|
|
15
|
+
import { larkLogger } from '../core/lark-logger';
|
|
16
|
+
import { handleCardAction } from '../tools/auto-auth';
|
|
17
|
+
import { enqueueFeishuChatTask, buildQueueKey, hasActiveTask, getActiveDispatcher } from './chat-queue';
|
|
18
|
+
import { extractRawTextFromEvent, isLikelyAbortText } from './abort-detect';
|
|
19
|
+
const elog = larkLogger('channel/event-handlers');
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Event ownership validation
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
/**
|
|
24
|
+
* Verify that the event's app_id matches the current account.
|
|
25
|
+
*
|
|
26
|
+
* Lark SDK EventDispatcher flattens the v2 envelope header (which
|
|
27
|
+
* contains `app_id`) into the handler `data` object, so `app_id` is
|
|
28
|
+
* available directly on `data`.
|
|
29
|
+
*
|
|
30
|
+
* Returns `false` (discard event) when the app_id does not match.
|
|
31
|
+
*/
|
|
32
|
+
function isEventOwnershipValid(ctx, data) {
|
|
33
|
+
const expectedAppId = ctx.lark.account.appId;
|
|
34
|
+
if (!expectedAppId)
|
|
35
|
+
return true; // appId not configured — skip check
|
|
36
|
+
const eventAppId = data.app_id;
|
|
37
|
+
if (eventAppId == null)
|
|
38
|
+
return true; // SDK did not provide app_id — defensive skip
|
|
39
|
+
if (eventAppId !== expectedAppId) {
|
|
40
|
+
elog.warn('event app_id mismatch, discarding', {
|
|
41
|
+
accountId: ctx.accountId,
|
|
42
|
+
expected: expectedAppId,
|
|
43
|
+
received: String(eventAppId),
|
|
44
|
+
});
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Message handler
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
export async function handleMessageEvent(ctx, data) {
|
|
53
|
+
if (!isEventOwnershipValid(ctx, data))
|
|
54
|
+
return;
|
|
55
|
+
const { accountId, log, error } = ctx;
|
|
56
|
+
try {
|
|
57
|
+
const event = data;
|
|
58
|
+
const msgId = event.message?.message_id ?? 'unknown';
|
|
59
|
+
const chatId = event.message?.chat_id ?? '';
|
|
60
|
+
const threadId = event.message?.thread_id || undefined;
|
|
61
|
+
// Dedup — skip duplicate messages (e.g. from WebSocket reconnects).
|
|
62
|
+
if (!ctx.messageDedup.tryRecord(msgId, accountId)) {
|
|
63
|
+
log(`feishu[${accountId}]: duplicate message ${msgId}, skipping`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
// Expiry — discard stale messages from reconnect replay.
|
|
67
|
+
if (isMessageExpired(event.message?.create_time)) {
|
|
68
|
+
log(`feishu[${accountId}]: message ${msgId} expired, discarding`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// ---- Abort fast-path ----
|
|
72
|
+
// If the message looks like an abort trigger and there is an active
|
|
73
|
+
// reply dispatcher for this chat, fire abortCard() immediately
|
|
74
|
+
// (before the message enters the serial queue) so the streaming
|
|
75
|
+
// card is terminated without waiting for the current task.
|
|
76
|
+
const abortText = extractRawTextFromEvent(event);
|
|
77
|
+
if (abortText && isLikelyAbortText(abortText)) {
|
|
78
|
+
const queueKey = buildQueueKey(accountId, chatId, threadId);
|
|
79
|
+
if (hasActiveTask(queueKey)) {
|
|
80
|
+
const active = getActiveDispatcher(queueKey);
|
|
81
|
+
if (active) {
|
|
82
|
+
log(`feishu[${accountId}]: abort fast-path triggered for chat ${chatId} (text="${abortText}")`);
|
|
83
|
+
active.abortController?.abort();
|
|
84
|
+
active.abortCard().catch((err) => {
|
|
85
|
+
error(`feishu[${accountId}]: abort fast-path abortCard failed: ${String(err)}`);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const { status } = enqueueFeishuChatTask({
|
|
91
|
+
accountId,
|
|
92
|
+
chatId,
|
|
93
|
+
threadId,
|
|
94
|
+
task: async () => {
|
|
95
|
+
try {
|
|
96
|
+
await withTicket({
|
|
97
|
+
messageId: msgId,
|
|
98
|
+
chatId,
|
|
99
|
+
accountId,
|
|
100
|
+
startTime: Date.now(),
|
|
101
|
+
senderOpenId: event.sender?.sender_id?.open_id || '',
|
|
102
|
+
chatType: event.message?.chat_type || undefined,
|
|
103
|
+
threadId,
|
|
104
|
+
}, () => handleFeishuMessage({
|
|
105
|
+
cfg: ctx.cfg,
|
|
106
|
+
event,
|
|
107
|
+
botOpenId: ctx.lark.botOpenId,
|
|
108
|
+
runtime: ctx.runtime,
|
|
109
|
+
chatHistories: ctx.chatHistories,
|
|
110
|
+
accountId,
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
error(`feishu[${accountId}]: error handling message: ${String(err)}`);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
log(`feishu[${accountId}]: message ${msgId} in chat ${chatId}${threadId ? ` thread ${threadId}` : ''} — ${status}`);
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
error(`feishu[${accountId}]: error handling message: ${String(err)}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// Reaction handler
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
export async function handleReactionEvent(ctx, data) {
|
|
128
|
+
if (!isEventOwnershipValid(ctx, data))
|
|
129
|
+
return;
|
|
130
|
+
const { accountId, log, error } = ctx;
|
|
131
|
+
try {
|
|
132
|
+
const event = data;
|
|
133
|
+
const msgId = event.message_id ?? 'unknown';
|
|
134
|
+
log(`feishu[${accountId}]: reaction event on message ${msgId}`);
|
|
135
|
+
// ---- Dedup: deterministic key based on message + emoji + operator ----
|
|
136
|
+
const emojiType = event.reaction_type?.emoji_type ?? '';
|
|
137
|
+
const operatorOpenId = event.user_id?.open_id ?? '';
|
|
138
|
+
const dedupKey = `${msgId}:reaction:${emojiType}:${operatorOpenId}`;
|
|
139
|
+
if (!ctx.messageDedup.tryRecord(dedupKey, accountId)) {
|
|
140
|
+
log(`feishu[${accountId}]: duplicate reaction ${dedupKey}, skipping`);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// ---- Expiry: discard stale reaction events ----
|
|
144
|
+
if (isMessageExpired(event.action_time)) {
|
|
145
|
+
log(`feishu[${accountId}]: reaction on ${msgId} expired, discarding`);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
// ---- Pre-resolve real chatId before enqueuing ----
|
|
149
|
+
// The API call (3s timeout) runs outside the queue so it doesn't
|
|
150
|
+
// block the serial chain, and is read-only so ordering is irrelevant.
|
|
151
|
+
const preResolved = await resolveReactionContext({
|
|
152
|
+
cfg: ctx.cfg,
|
|
153
|
+
event,
|
|
154
|
+
botOpenId: ctx.lark.botOpenId,
|
|
155
|
+
runtime: ctx.runtime,
|
|
156
|
+
accountId,
|
|
157
|
+
});
|
|
158
|
+
if (!preResolved)
|
|
159
|
+
return;
|
|
160
|
+
// ---- Enqueue with the real chatId (matches normal message queue key) ----
|
|
161
|
+
const { status } = enqueueFeishuChatTask({
|
|
162
|
+
accountId,
|
|
163
|
+
chatId: preResolved.chatId,
|
|
164
|
+
threadId: preResolved.threadId,
|
|
165
|
+
task: async () => {
|
|
166
|
+
try {
|
|
167
|
+
await withTicket({
|
|
168
|
+
messageId: msgId,
|
|
169
|
+
chatId: preResolved.chatId,
|
|
170
|
+
accountId,
|
|
171
|
+
startTime: Date.now(),
|
|
172
|
+
senderOpenId: operatorOpenId,
|
|
173
|
+
chatType: preResolved.chatType,
|
|
174
|
+
threadId: preResolved.threadId,
|
|
175
|
+
}, () => handleFeishuReaction({
|
|
176
|
+
cfg: ctx.cfg,
|
|
177
|
+
event,
|
|
178
|
+
botOpenId: ctx.lark.botOpenId,
|
|
179
|
+
runtime: ctx.runtime,
|
|
180
|
+
chatHistories: ctx.chatHistories,
|
|
181
|
+
accountId,
|
|
182
|
+
preResolved,
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
error(`feishu[${accountId}]: error handling reaction: ${String(err)}`);
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
log(`feishu[${accountId}]: reaction on ${msgId} (chatId=${preResolved.chatId}) — ${status}`);
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
error(`feishu[${accountId}]: error handling reaction event: ${String(err)}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
// Bot membership handler
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
export async function handleBotMembershipEvent(ctx, data, action) {
|
|
200
|
+
if (!isEventOwnershipValid(ctx, data))
|
|
201
|
+
return;
|
|
202
|
+
const { accountId, log, error } = ctx;
|
|
203
|
+
try {
|
|
204
|
+
const event = data;
|
|
205
|
+
log(`feishu[${accountId}]: bot ${action} ${action === 'removed' ? 'from' : 'to'} chat ${event.chat_id}`);
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
error(`feishu[${accountId}]: error handling bot ${action} event: ${String(err)}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
// Card action handler
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
export async function handleCardActionEvent(ctx, data) {
|
|
215
|
+
try {
|
|
216
|
+
return await handleCardAction(data, ctx.cfg, ctx.accountId);
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
elog.warn(`card.action.trigger handler error: ${err}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* WebSocket monitoring for the Lark/Feishu channel plugin.
|
|
6
|
+
*
|
|
7
|
+
* Manages per-account WSClient connections and routes inbound Feishu
|
|
8
|
+
* events (messages, bot membership changes, read receipts) to the
|
|
9
|
+
* appropriate handlers.
|
|
10
|
+
*/
|
|
11
|
+
import type { MonitorFeishuOpts } from './types';
|
|
12
|
+
export type { MonitorFeishuOpts } from './types';
|
|
13
|
+
/**
|
|
14
|
+
* Start monitoring for all enabled Feishu accounts (or a single
|
|
15
|
+
* account when `opts.accountId` is specified).
|
|
16
|
+
*/
|
|
17
|
+
export declare function monitorFeishuProvider(opts?: MonitorFeishuOpts): Promise<void>;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* WebSocket monitoring for the Lark/Feishu channel plugin.
|
|
6
|
+
*
|
|
7
|
+
* Manages per-account WSClient connections and routes inbound Feishu
|
|
8
|
+
* events (messages, bot membership changes, read receipts) to the
|
|
9
|
+
* appropriate handlers.
|
|
10
|
+
*/
|
|
11
|
+
import { getLarkAccount, getEnabledLarkAccounts } from '../core/accounts';
|
|
12
|
+
import { LarkClient } from '../core/lark-client';
|
|
13
|
+
import { MessageDedup } from '../messaging/inbound/dedup';
|
|
14
|
+
import { larkLogger } from '../core/lark-logger';
|
|
15
|
+
import { drainShutdownHooks } from '../core/shutdown-hooks';
|
|
16
|
+
import { handleMessageEvent, handleReactionEvent, handleBotMembershipEvent, handleCardActionEvent, } from './event-handlers';
|
|
17
|
+
const mlog = larkLogger('channel/monitor');
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Single-account monitor
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
/**
|
|
22
|
+
* Start monitoring a single Feishu account.
|
|
23
|
+
*
|
|
24
|
+
* Creates a LarkClient, probes bot identity, registers event handlers,
|
|
25
|
+
* and starts a WebSocket connection. Returns a Promise that resolves
|
|
26
|
+
* when the abort signal fires (or immediately if already aborted).
|
|
27
|
+
*/
|
|
28
|
+
async function monitorSingleAccount(params) {
|
|
29
|
+
const { account, runtime, abortSignal } = params;
|
|
30
|
+
const { accountId } = account;
|
|
31
|
+
const log = runtime?.log ?? ((...args) => mlog.info(args.map(String).join(' ')));
|
|
32
|
+
const error = runtime?.error ?? ((...args) => mlog.error(args.map(String).join(' ')));
|
|
33
|
+
// Only websocket mode is supported in the monitor path.
|
|
34
|
+
const connectionMode = account.config.connectionMode ?? 'websocket';
|
|
35
|
+
if (connectionMode !== 'websocket') {
|
|
36
|
+
log(`feishu[${accountId}]: webhook mode not implemented in monitor`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// Message dedup — filters duplicate deliveries from WebSocket reconnects.
|
|
40
|
+
const dedupCfg = account.config.dedup;
|
|
41
|
+
const messageDedup = new MessageDedup({
|
|
42
|
+
ttlMs: dedupCfg?.ttlMs,
|
|
43
|
+
maxEntries: dedupCfg?.maxEntries,
|
|
44
|
+
});
|
|
45
|
+
log(`feishu[${accountId}]: message dedup enabled (ttl=${messageDedup['ttlMs']}ms, max=${messageDedup['maxEntries']})`);
|
|
46
|
+
log(`feishu[${accountId}]: starting WebSocket connection...`);
|
|
47
|
+
// Create LarkClient instance — manages SDK client, WS, and bot identity.
|
|
48
|
+
const lark = LarkClient.fromAccount(account);
|
|
49
|
+
// Attach dedup instance so it is disposed together with the client.
|
|
50
|
+
lark.messageDedup = messageDedup;
|
|
51
|
+
/** Per-chat history maps (used for group-chat context window). */
|
|
52
|
+
const chatHistories = new Map();
|
|
53
|
+
const ctx = {
|
|
54
|
+
get cfg() {
|
|
55
|
+
return LarkClient.runtime.config.loadConfig();
|
|
56
|
+
},
|
|
57
|
+
lark,
|
|
58
|
+
accountId,
|
|
59
|
+
chatHistories,
|
|
60
|
+
messageDedup,
|
|
61
|
+
runtime,
|
|
62
|
+
log,
|
|
63
|
+
error,
|
|
64
|
+
};
|
|
65
|
+
await lark.startWS({
|
|
66
|
+
handlers: {
|
|
67
|
+
'im.message.receive_v1': (data) => handleMessageEvent(ctx, data),
|
|
68
|
+
'im.message.message_read_v1': async () => { },
|
|
69
|
+
'im.message.reaction.created_v1': (data) => handleReactionEvent(ctx, data),
|
|
70
|
+
'im.chat.member.bot.added_v1': (data) => handleBotMembershipEvent(ctx, data, 'added'),
|
|
71
|
+
'im.chat.member.bot.deleted_v1': (data) => handleBotMembershipEvent(ctx, data, 'removed'),
|
|
72
|
+
// 飞书 SDK EventDispatcher.register 不支持带返回值的处理器,此处 as any 是 SDK 类型限制的变通
|
|
73
|
+
'card.action.trigger': ((data) =>
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
75
|
+
handleCardActionEvent(ctx, data)),
|
|
76
|
+
},
|
|
77
|
+
abortSignal,
|
|
78
|
+
});
|
|
79
|
+
// startWS resolves when abortSignal fires — probe result is logged inside startWS.
|
|
80
|
+
log(`feishu[${accountId}]: bot open_id resolved: ${lark.botOpenId ?? 'unknown'}`);
|
|
81
|
+
log(`feishu[${accountId}]: WebSocket client started`);
|
|
82
|
+
mlog.info(`websocket started for account ${accountId}`);
|
|
83
|
+
}
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Public API
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
/**
|
|
88
|
+
* Start monitoring for all enabled Feishu accounts (or a single
|
|
89
|
+
* account when `opts.accountId` is specified).
|
|
90
|
+
*/
|
|
91
|
+
export async function monitorFeishuProvider(opts = {}) {
|
|
92
|
+
const cfg = opts.config;
|
|
93
|
+
if (!cfg) {
|
|
94
|
+
throw new Error('Config is required for Feishu monitor');
|
|
95
|
+
}
|
|
96
|
+
// Store the original global config so plugin commands (doctor, diagnose)
|
|
97
|
+
// can access cross-account information even when running inside an
|
|
98
|
+
// account-scoped config context.
|
|
99
|
+
LarkClient.setGlobalConfig(cfg);
|
|
100
|
+
const log = opts.runtime?.log ?? ((...args) => mlog.info(args.map(String).join(' ')));
|
|
101
|
+
// Single-account mode.
|
|
102
|
+
if (opts.accountId) {
|
|
103
|
+
const account = getLarkAccount(cfg, opts.accountId);
|
|
104
|
+
if (!account.enabled || !account.configured) {
|
|
105
|
+
throw new Error(`Feishu account "${opts.accountId}" not configured or disabled`);
|
|
106
|
+
}
|
|
107
|
+
await monitorSingleAccount({
|
|
108
|
+
cfg,
|
|
109
|
+
account,
|
|
110
|
+
runtime: opts.runtime,
|
|
111
|
+
abortSignal: opts.abortSignal,
|
|
112
|
+
});
|
|
113
|
+
await drainShutdownHooks({ log });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// Multi-account mode: start all enabled accounts in parallel.
|
|
117
|
+
const accounts = getEnabledLarkAccounts(cfg);
|
|
118
|
+
if (accounts.length === 0) {
|
|
119
|
+
throw new Error('No enabled Feishu accounts configured');
|
|
120
|
+
}
|
|
121
|
+
log(`feishu: starting ${accounts.length} account(s): ${accounts.map((a) => a.accountId).join(', ')}`);
|
|
122
|
+
await Promise.all(accounts.map((account) => monitorSingleAccount({
|
|
123
|
+
cfg,
|
|
124
|
+
account,
|
|
125
|
+
runtime: opts.runtime,
|
|
126
|
+
abortSignal: opts.abortSignal,
|
|
127
|
+
})));
|
|
128
|
+
await drainShutdownHooks({ log });
|
|
129
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Onboarding configuration mutation helpers.
|
|
6
|
+
*
|
|
7
|
+
* Pure functions that apply Feishu channel configuration changes
|
|
8
|
+
* to a ClawdbotConfig. Extracted from onboarding.ts for reuse
|
|
9
|
+
* in CLI commands and other configuration flows.
|
|
10
|
+
*/
|
|
11
|
+
import type { ClawdbotConfig, DmPolicy } from 'openclaw/plugin-sdk';
|
|
12
|
+
export declare function setFeishuDmPolicy(cfg: ClawdbotConfig, dmPolicy: DmPolicy): ClawdbotConfig;
|
|
13
|
+
export declare function setFeishuAllowFrom(cfg: ClawdbotConfig, allowFrom: string[]): ClawdbotConfig;
|
|
14
|
+
export declare function setFeishuGroupPolicy(cfg: ClawdbotConfig, groupPolicy: 'open' | 'allowlist' | 'disabled'): ClawdbotConfig;
|
|
15
|
+
export declare function setFeishuGroupAllowFrom(cfg: ClawdbotConfig, groupAllowFrom: string[]): ClawdbotConfig;
|
|
16
|
+
export declare function setFeishuGroups(cfg: ClawdbotConfig, groups: Record<string, object>): ClawdbotConfig;
|
|
17
|
+
export declare function parseAllowFromInput(raw: string): string[];
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Onboarding configuration mutation helpers.
|
|
6
|
+
*
|
|
7
|
+
* Pure functions that apply Feishu channel configuration changes
|
|
8
|
+
* to a ClawdbotConfig. Extracted from onboarding.ts for reuse
|
|
9
|
+
* in CLI commands and other configuration flows.
|
|
10
|
+
*/
|
|
11
|
+
import { addWildcardAllowFrom } from 'openclaw/plugin-sdk';
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Config mutation helpers
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
export function setFeishuDmPolicy(cfg, dmPolicy) {
|
|
16
|
+
const allowFrom = dmPolicy === 'open'
|
|
17
|
+
? addWildcardAllowFrom(cfg.channels?.feishu?.allowFrom)?.map((entry) => String(entry))
|
|
18
|
+
: undefined;
|
|
19
|
+
return {
|
|
20
|
+
...cfg,
|
|
21
|
+
channels: {
|
|
22
|
+
...cfg.channels,
|
|
23
|
+
feishu: {
|
|
24
|
+
...cfg.channels?.feishu,
|
|
25
|
+
dmPolicy,
|
|
26
|
+
...(allowFrom ? { allowFrom } : {}),
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function setFeishuAllowFrom(cfg, allowFrom) {
|
|
32
|
+
return {
|
|
33
|
+
...cfg,
|
|
34
|
+
channels: {
|
|
35
|
+
...cfg.channels,
|
|
36
|
+
feishu: {
|
|
37
|
+
...cfg.channels?.feishu,
|
|
38
|
+
allowFrom,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function setFeishuGroupPolicy(cfg, groupPolicy) {
|
|
44
|
+
return {
|
|
45
|
+
...cfg,
|
|
46
|
+
channels: {
|
|
47
|
+
...cfg.channels,
|
|
48
|
+
feishu: {
|
|
49
|
+
...cfg.channels?.feishu,
|
|
50
|
+
enabled: true,
|
|
51
|
+
groupPolicy,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function setFeishuGroupAllowFrom(cfg, groupAllowFrom) {
|
|
57
|
+
return {
|
|
58
|
+
...cfg,
|
|
59
|
+
channels: {
|
|
60
|
+
...cfg.channels,
|
|
61
|
+
feishu: {
|
|
62
|
+
...cfg.channels?.feishu,
|
|
63
|
+
groupAllowFrom,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export function setFeishuGroups(cfg, groups) {
|
|
69
|
+
return {
|
|
70
|
+
...cfg,
|
|
71
|
+
channels: {
|
|
72
|
+
...cfg.channels,
|
|
73
|
+
feishu: {
|
|
74
|
+
...cfg.channels?.feishu,
|
|
75
|
+
groups,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Input helpers
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
export function parseAllowFromInput(raw) {
|
|
84
|
+
return raw
|
|
85
|
+
.split(/[\n,;]+/g)
|
|
86
|
+
.map((entry) => entry.trim())
|
|
87
|
+
.filter(Boolean);
|
|
88
|
+
}
|