// agent = openclaw channel account. // Config shape: // channels.fabric.centerApiBase = "http://localhost:7001/api" (shared) // channels.fabric.accounts. = { fabricApiKey, centerApiBase? } // Each account id IS the openclaw agentId that owns that Fabric identity. export type FabricAccountConfig = { fabricApiKey?: string; centerApiBase?: string; enabled?: boolean; allowFrom?: string[]; dmPolicy?: string; }; export type FabricChannelConfig = { centerApiBase?: string; // Shared secret matching the guild's FABRIC_BACKEND_GUILD_COMMANDS_SYNC_KEY // (Guild C-2). Required by the channel config schema; sourced from config // only — never from the environment. commandsSyncKey?: string; // Coalesce an agent turn that OpenClaw split into multiple deliveries // (text → thinking/tool → text => N sendText calls) into ONE Fabric // message. The flush boundary is the deterministic `agent_end` hook (not // a timer). Default true; set false for raw per-segment posting. coalesce?: boolean; accounts?: Record; defaultAccount?: string; } & FabricAccountConfig; type Cfg = { channels?: { fabric?: FabricChannelConfig }; [k: string]: unknown }; export type ResolvedFabricAccount = { accountId: string; enabled: boolean; centerApiBase: string; fabricApiKey: string; allowFrom: string[]; dmPolicy: string | undefined; }; const DEFAULT_CENTER = 'http://localhost:7001/api'; function section(cfg: Cfg): FabricChannelConfig { return cfg.channels?.fabric ?? {}; } // The commands-sync shared secret (channel-level only). Empty string when // unconfigured — callers decide how to handle (slash-command sync is then // rejected by the guild). export function resolveCommandsSyncKey(cfg: Cfg): string { return (section(cfg).commandsSyncKey ?? '').trim(); } // Whether to coalesce a split agent turn into one Fabric message // (channel-level). Default true. export function resolveCoalesce(cfg: Cfg): boolean { return (cfg.channels?.fabric ?? {}).coalesce !== false; } export function listFabricAccountIds(cfg: Cfg): string[] { const accts = section(cfg).accounts ?? {}; const ids = Object.keys(accts); return ids.length ? ids : ['default']; } export function resolveDefaultFabricAccountId(cfg: Cfg): string { const s = section(cfg); if (s.defaultAccount) return s.defaultAccount; const ids = listFabricAccountIds(cfg); return ids[0] ?? 'default'; } export function resolveFabricAccount(cfg: Cfg, accountId?: string | null): ResolvedFabricAccount { const s = section(cfg); const id = accountId ?? resolveDefaultFabricAccountId(cfg); const acc = s.accounts?.[id] ?? {}; const fabricApiKey = (acc.fabricApiKey ?? '').trim(); const centerApiBase = (acc.centerApiBase ?? s.centerApiBase ?? DEFAULT_CENTER).trim(); return { accountId: id, enabled: acc.enabled !== false && s.enabled !== false, centerApiBase, fabricApiKey, allowFrom: acc.allowFrom ?? s.allowFrom ?? [], dmPolicy: acc.dmPolicy ?? s.dmPolicy, }; } export function listEnabledFabricAccounts(cfg: Cfg): ResolvedFabricAccount[] { return listFabricAccountIds(cfg) .map((id) => resolveFabricAccount(cfg, id)) .filter((a) => a.enabled && a.fabricApiKey); }