diff --git a/extensions/feishu/src/channel.runtime.ts b/extensions/feishu/src/channel.runtime.ts new file mode 100644 index 00000000000..8068fb350d3 --- /dev/null +++ b/extensions/feishu/src/channel.runtime.ts @@ -0,0 +1,6 @@ +export { listFeishuDirectoryGroupsLive, listFeishuDirectoryPeersLive } from "./directory.js"; +export { feishuOnboardingAdapter } from "./onboarding.js"; +export { feishuOutbound } from "./outbound.js"; +export { probeFeishu } from "./probe.js"; +export { addReactionFeishu, listReactionsFeishu, removeReactionFeishu } from "./reactions.js"; +export { sendCardFeishu, sendMessageFeishu } from "./send.js"; diff --git a/extensions/feishu/src/channel.ts b/extensions/feishu/src/channel.ts index 3baa7c916a2..17f3e5cc580 100644 --- a/extensions/feishu/src/channel.ts +++ b/extensions/feishu/src/channel.ts @@ -22,18 +22,9 @@ import { resolveDefaultFeishuAccountId, } from "./accounts.js"; import { FeishuConfigSchema } from "./config-schema.js"; -import { - listFeishuDirectoryPeers, - listFeishuDirectoryGroups, - listFeishuDirectoryPeersLive, - listFeishuDirectoryGroupsLive, -} from "./directory.js"; -import { feishuOnboardingAdapter } from "./onboarding.js"; -import { feishuOutbound } from "./outbound.js"; +import { listFeishuDirectoryPeers, listFeishuDirectoryGroups } from "./directory.static.js"; import { resolveFeishuGroupToolPolicy } from "./policy.js"; -import { probeFeishu } from "./probe.js"; -import { addReactionFeishu, listReactionsFeishu, removeReactionFeishu } from "./reactions.js"; -import { sendCardFeishu, sendMessageFeishu } from "./send.js"; +import { getFeishuRuntime } from "./runtime.js"; import { normalizeFeishuTarget, looksLikeFeishuId, formatFeishuTarget } from "./targets.js"; import type { ResolvedFeishuAccount, FeishuConfig } from "./types.js"; @@ -48,6 +39,47 @@ const meta: ChannelMeta = { order: 70, }; +async function loadFeishuChannelRuntime() { + return await import("./channel.runtime.js"); +} + +const feishuOnboarding = { + channel: "feishu", + getStatus: async (ctx) => + (await loadFeishuChannelRuntime()).feishuOnboardingAdapter.getStatus(ctx), + configure: async (ctx) => + (await loadFeishuChannelRuntime()).feishuOnboardingAdapter.configure(ctx), + dmPolicy: { + label: "Feishu", + channel: "feishu", + policyKey: "channels.feishu.dmPolicy", + allowFromKey: "channels.feishu.allowFrom", + getCurrent: (cfg) => (cfg.channels?.feishu as FeishuConfig | undefined)?.dmPolicy ?? "pairing", + setPolicy: (cfg, policy) => ({ + ...cfg, + channels: { + ...cfg.channels, + feishu: { + ...cfg.channels?.feishu, + dmPolicy: policy, + }, + }, + }), + promptAllowFrom: async (cfg, prompter) => + (await loadFeishuChannelRuntime()).feishuOnboardingAdapter.dmPolicy!.promptAllowFrom({ + cfg, + prompter, + }), + }, + disable: (cfg) => ({ + ...cfg, + channels: { + ...cfg.channels, + feishu: { ...cfg.channels?.feishu, enabled: false }, + }, + }), +} satisfies ChannelPlugin["onboarding"]; + function setFeishuNamedAccountEnabled( cfg: ClawdbotConfig, accountId: string, @@ -107,6 +139,7 @@ export const feishuPlugin: ChannelPlugin = { idLabel: "feishuUserId", normalizeAllowEntry: (entry) => entry.replace(/^(feishu|user|open_id):/i, ""), notifyApproval: async ({ cfg, id }) => { + const { sendMessageFeishu } = await loadFeishuChannelRuntime(); await sendMessageFeishu({ cfg, to: id, @@ -254,6 +287,7 @@ export const feishuPlugin: ChannelPlugin = { typeof ctx.params.replyTo === "string" ? ctx.params.replyTo.trim() || undefined : undefined; + const { sendCardFeishu } = await loadFeishuChannelRuntime(); const result = await sendCardFeishu({ cfg: ctx.cfg, to, @@ -287,6 +321,7 @@ export const feishuPlugin: ChannelPlugin = { if (!emoji) { throw new Error("Emoji is required to remove a Feishu reaction."); } + const { listReactionsFeishu, removeReactionFeishu } = await loadFeishuChannelRuntime(); const matches = await listReactionsFeishu({ cfg: ctx.cfg, messageId, @@ -321,6 +356,7 @@ export const feishuPlugin: ChannelPlugin = { "Emoji is required to add a Feishu reaction. Set clearAll=true to remove all bot reactions.", ); } + const { listReactionsFeishu, removeReactionFeishu } = await loadFeishuChannelRuntime(); const reactions = await listReactionsFeishu({ cfg: ctx.cfg, messageId, @@ -341,6 +377,7 @@ export const feishuPlugin: ChannelPlugin = { details: { ok: true, removed }, }; } + const { addReactionFeishu } = await loadFeishuChannelRuntime(); await addReactionFeishu({ cfg: ctx.cfg, messageId, @@ -361,6 +398,7 @@ export const feishuPlugin: ChannelPlugin = { if (!messageId) { throw new Error("Feishu reactions lookup requires messageId."); } + const { listReactionsFeishu } = await loadFeishuChannelRuntime(); const reactions = await listReactionsFeishu({ cfg: ctx.cfg, messageId, @@ -411,7 +449,7 @@ export const feishuPlugin: ChannelPlugin = { return setFeishuNamedAccountEnabled(cfg, accountId, true); }, }, - onboarding: feishuOnboardingAdapter, + onboarding: feishuOnboarding, messaging: { normalizeTarget: (raw) => normalizeFeishuTarget(raw) ?? undefined, targetResolver: { @@ -436,28 +474,37 @@ export const feishuPlugin: ChannelPlugin = { accountId: accountId ?? undefined, }), listPeersLive: async ({ cfg, query, limit, accountId }) => - listFeishuDirectoryPeersLive({ + (await loadFeishuChannelRuntime()).listFeishuDirectoryPeersLive({ cfg, query: query ?? undefined, limit: limit ?? undefined, accountId: accountId ?? undefined, }), listGroupsLive: async ({ cfg, query, limit, accountId }) => - listFeishuDirectoryGroupsLive({ + (await loadFeishuChannelRuntime()).listFeishuDirectoryGroupsLive({ cfg, query: query ?? undefined, limit: limit ?? undefined, accountId: accountId ?? undefined, }), }, - outbound: feishuOutbound, + outbound: { + deliveryMode: "direct", + chunker: (text, limit) => getFeishuRuntime().channel.text.chunkMarkdownText(text, limit), + chunkerMode: "markdown", + textChunkLimit: 4000, + sendText: async (params) => (await loadFeishuChannelRuntime()).feishuOutbound.sendText!(params), + sendMedia: async (params) => + (await loadFeishuChannelRuntime()).feishuOutbound.sendMedia!(params), + }, status: { defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID, { port: null }), buildChannelSummary: ({ snapshot }) => buildProbeChannelStatusSummary(snapshot, { port: snapshot.port ?? null, }), - probeAccount: async ({ account }) => await probeFeishu(account), + probeAccount: async ({ account }) => + await (await loadFeishuChannelRuntime()).probeFeishu(account), buildAccountSnapshot: ({ account, runtime, probe }) => ({ accountId: account.accountId, enabled: account.enabled, diff --git a/extensions/feishu/src/directory.static.ts b/extensions/feishu/src/directory.static.ts new file mode 100644 index 00000000000..b79e4e94f77 --- /dev/null +++ b/extensions/feishu/src/directory.static.ts @@ -0,0 +1,61 @@ +import { + listDirectoryGroupEntriesFromMapKeysAndAllowFrom, + listDirectoryUserEntriesFromAllowFromAndMapKeys, +} from "openclaw/plugin-sdk/compat"; +import type { ClawdbotConfig } from "openclaw/plugin-sdk/feishu"; +import { resolveFeishuAccount } from "./accounts.js"; +import { normalizeFeishuTarget } from "./targets.js"; + +export type FeishuDirectoryPeer = { + kind: "user"; + id: string; + name?: string; +}; + +export type FeishuDirectoryGroup = { + kind: "group"; + id: string; + name?: string; +}; + +function toFeishuDirectoryPeers(ids: string[]): FeishuDirectoryPeer[] { + return ids.map((id) => ({ kind: "user", id })); +} + +function toFeishuDirectoryGroups(ids: string[]): FeishuDirectoryGroup[] { + return ids.map((id) => ({ kind: "group", id })); +} + +export async function listFeishuDirectoryPeers(params: { + cfg: ClawdbotConfig; + query?: string; + limit?: number; + accountId?: string; +}): Promise { + const account = resolveFeishuAccount({ cfg: params.cfg, accountId: params.accountId }); + const entries = listDirectoryUserEntriesFromAllowFromAndMapKeys({ + allowFrom: account.config.allowFrom, + map: account.config.dms, + query: params.query, + limit: params.limit, + normalizeAllowFromId: (entry) => normalizeFeishuTarget(entry) ?? entry, + normalizeMapKeyId: (entry) => normalizeFeishuTarget(entry) ?? entry, + }); + return toFeishuDirectoryPeers(entries.map((entry) => entry.id)); +} + +export async function listFeishuDirectoryGroups(params: { + cfg: ClawdbotConfig; + query?: string; + limit?: number; + accountId?: string; +}): Promise { + const account = resolveFeishuAccount({ cfg: params.cfg, accountId: params.accountId }); + const entries = listDirectoryGroupEntriesFromMapKeysAndAllowFrom({ + groups: account.config.groups, + allowFrom: account.config.groupAllowFrom, + query: params.query, + limit: params.limit, + }); + return toFeishuDirectoryGroups(entries.map((entry) => entry.id)); +} diff --git a/extensions/feishu/src/directory.ts b/extensions/feishu/src/directory.ts index 4b5ca584a99..c6366990204 100644 --- a/extensions/feishu/src/directory.ts +++ b/extensions/feishu/src/directory.ts @@ -1,65 +1,14 @@ -import { - listDirectoryGroupEntriesFromMapKeysAndAllowFrom, - listDirectoryUserEntriesFromAllowFromAndMapKeys, -} from "openclaw/plugin-sdk/compat"; import type { ClawdbotConfig } from "openclaw/plugin-sdk/feishu"; import { resolveFeishuAccount } from "./accounts.js"; import { createFeishuClient } from "./client.js"; -import { normalizeFeishuTarget } from "./targets.js"; +import { + listFeishuDirectoryGroups, + listFeishuDirectoryPeers, + type FeishuDirectoryGroup, + type FeishuDirectoryPeer, +} from "./directory.static.js"; -export type FeishuDirectoryPeer = { - kind: "user"; - id: string; - name?: string; -}; - -export type FeishuDirectoryGroup = { - kind: "group"; - id: string; - name?: string; -}; - -function toFeishuDirectoryPeers(ids: string[]): FeishuDirectoryPeer[] { - return ids.map((id) => ({ kind: "user", id })); -} - -function toFeishuDirectoryGroups(ids: string[]): FeishuDirectoryGroup[] { - return ids.map((id) => ({ kind: "group", id })); -} - -export async function listFeishuDirectoryPeers(params: { - cfg: ClawdbotConfig; - query?: string; - limit?: number; - accountId?: string; -}): Promise { - const account = resolveFeishuAccount({ cfg: params.cfg, accountId: params.accountId }); - const entries = listDirectoryUserEntriesFromAllowFromAndMapKeys({ - allowFrom: account.config.allowFrom, - map: account.config.dms, - query: params.query, - limit: params.limit, - normalizeAllowFromId: (entry) => normalizeFeishuTarget(entry) ?? entry, - normalizeMapKeyId: (entry) => normalizeFeishuTarget(entry) ?? entry, - }); - return toFeishuDirectoryPeers(entries.map((entry) => entry.id)); -} - -export async function listFeishuDirectoryGroups(params: { - cfg: ClawdbotConfig; - query?: string; - limit?: number; - accountId?: string; -}): Promise { - const account = resolveFeishuAccount({ cfg: params.cfg, accountId: params.accountId }); - const entries = listDirectoryGroupEntriesFromMapKeysAndAllowFrom({ - groups: account.config.groups, - allowFrom: account.config.groupAllowFrom, - query: params.query, - limit: params.limit, - }); - return toFeishuDirectoryGroups(entries.map((entry) => entry.id)); -} +export { listFeishuDirectoryGroups, listFeishuDirectoryPeers } from "./directory.static.js"; export async function listFeishuDirectoryPeersLive(params: { cfg: ClawdbotConfig;