import { createChatChannelPlugin, createChannelPluginBase, } from 'openclaw/plugin-sdk/channel-core'; import type { OpenClawConfig } from 'openclaw/plugin-sdk/channel-core'; export type ResolvedFabricAccount = { accountId: string | null; centerApiBase: string; allowFrom: string[]; dmPolicy: string | undefined; }; export function resolveFabricAccount( cfg: OpenClawConfig, accountId?: string | null, ): ResolvedFabricAccount { const section = (cfg.channels as Record)?.['fabric']; const centerApiBase: string | undefined = section?.centerApiBase; if (!centerApiBase) throw new Error('fabric: channels.fabric.centerApiBase is required'); return { accountId: accountId ?? null, centerApiBase, allowFrom: section?.allowFrom ?? [], dmPolicy: section?.dmSecurity, }; } // Outbound is wired by the entry (it needs the identity registry + client to // post as the right agent). Channel-turn visible replies go through the // inbound adapter's delivery callback; this object owns config/security only. export function buildFabricChannelPlugin( sendText: (params: { accountId?: string | null; to: string; text: string }) => Promise<{ messageId?: string }>, ) { return createChatChannelPlugin({ base: createChannelPluginBase({ id: 'fabric', setup: { resolveAccount: resolveFabricAccount, inspectAccount(cfg, accountId) { const section = (cfg.channels as Record)?.['fabric']; const ok = Boolean(section?.centerApiBase); return { enabled: ok, configured: ok, tokenStatus: ok ? 'available' : 'missing' }; }, }, }), security: { dm: { channelKey: 'fabric', resolvePolicy: (a) => a.dmPolicy, resolveAllowFrom: (a) => a.allowFrom, defaultPolicy: 'allowlist', }, }, // Fabric replies thread by being posted into the same channel. threading: { topLevelReplyToMode: 'channel' }, outbound: { attachedResults: { sendText: async (params) => sendText(params), }, }, }); }