import type { IdentityRegistry } from './identity.js'; import type { SubDiscussionStore } from './sub-discussion-store.js'; // Plugin-local before_prompt_build hook that injects per-(agent, channel) // guides for sub-discussion channels created via the `create-sub-discussion` // tool. Mirrors the pattern used by ClawPrompts' fabric-chat-injector // (channelId-aware injection) but with content dynamically supplied at // channel-creation time instead of read from static files via PrismFacet's // router/rule registry. // // Match logic per turn: // ctx.channelId → store.find() → sub-discussion entry // ctx.agentId → identity.findByAgentId().fabricUserId // ─ matches entry.hostUserId → inject hostGuide // ─ matches entry.guestUserIds → inject guestGuide // ─ neither → no injection // // Fail-closed on unknown agentId/channelId — we never inject "the wrong" // guide, only the right one or nothing. const _G = globalThis as Record; const DEDUP_KEY = '_fabricSubDiscussionHookDedup'; type PromptCtx = { agentId?: string; channelId?: string; messageProvider?: string; }; export function registerSubDiscussionHook( api: { on: (hook: string, handler: (...args: unknown[]) => unknown) => void; logger: { info: (m: string) => void; warn: (m: string) => void }; }, store: SubDiscussionStore, identity: IdentityRegistry, ): void { if (!(_G[DEDUP_KEY] instanceof WeakSet)) _G[DEDUP_KEY] = new WeakSet(); const dedup = _G[DEDUP_KEY] as WeakSet; api.on('before_prompt_build', async (...args: unknown[]) => { const event = args[0]; const ctx = (args[1] ?? {}) as PromptCtx; // The hook fires both for fabric-driven turns (channelId set) and // for other triggers (HF wake, exec-event, etc.) — drop those. if (typeof event === 'object' && event !== null) { if (dedup.has(event)) return undefined; dedup.add(event); } const agentId = (ctx.agentId ?? '').trim(); const channelId = (ctx.channelId ?? '').trim(); if (!agentId || !channelId) return undefined; const provider = (ctx.messageProvider ?? '').toLowerCase(); if (provider && provider !== 'fabric') return undefined; const entry = store.find(channelId); if (!entry) return undefined; const ident = identity.findByAgentId(agentId); const myUserId = (ident?.fabricUserId ?? '').trim(); if (!myUserId) { // identity registry caches fabricUserId after the first agentLogin // in inbound.ts. If it's missing here, the agent likely hasn't // completed login yet — skip rather than guess. return undefined; } if (myUserId === entry.hostUserId) { return { appendSystemContext: entry.hostGuide }; } if (entry.guestUserIds.includes(myUserId)) { return { appendSystemContext: entry.guestGuide }; } return undefined; }); }