import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; import { evaluateDecision, resolvePolicy, type Decision, type DirigentConfig } from "../rules.js"; import { deriveDecisionInputFromPrompt } from "../decision-input.js"; type DebugConfig = { enableDebugLogs?: boolean; debugLogChannelIds?: string[]; }; type DecisionRecord = { decision: Decision; createdAt: number; needsRestore?: boolean; }; type BeforePromptBuildDeps = { api: OpenClawPluginApi; baseConfig: DirigentConfig; sessionDecision: Map; sessionInjected: Set; policyState: { channelPolicies: Record }; DECISION_TTL_MS: number; ensurePolicyStateLoaded: (api: OpenClawPluginApi, config: DirigentConfig) => void; getLivePluginConfig: (api: OpenClawPluginApi, fallback: DirigentConfig) => DirigentConfig; shouldDebugLog: (config: DirigentConfig & DebugConfig, channelId?: string) => boolean; buildEndMarkerInstruction: (endSymbols: string[], isGroupChat: boolean, schedulingIdentifier: string, waitIdentifier: string) => string; buildSchedulingIdentifierInstruction: (schedulingIdentifier: string) => string; buildAgentIdentity: (api: OpenClawPluginApi, agentId: string) => string; }; export function registerBeforePromptBuildHook(deps: BeforePromptBuildDeps): void { const { api, baseConfig, sessionDecision, sessionInjected, policyState, DECISION_TTL_MS, ensurePolicyStateLoaded, getLivePluginConfig, shouldDebugLog, buildEndMarkerInstruction, buildSchedulingIdentifierInstruction, buildAgentIdentity, } = deps; api.on("before_prompt_build", async (event, ctx) => { const key = ctx.sessionKey; if (!key) return; const live = getLivePluginConfig(api, baseConfig as DirigentConfig) as DirigentConfig & DebugConfig; ensurePolicyStateLoaded(api, live); let rec = sessionDecision.get(key); if (!rec || Date.now() - rec.createdAt > DECISION_TTL_MS) { if (rec) sessionDecision.delete(key); const prompt = ((event as Record).prompt as string) || ""; const derived = deriveDecisionInputFromPrompt({ prompt, messageProvider: ctx.messageProvider, sessionKey: key, ctx: ctx as Record, event: event as Record, }); const decision = evaluateDecision({ config: live, channel: derived.channel, channelId: derived.channelId, channelPolicies: policyState.channelPolicies as Record, senderId: derived.senderId, content: derived.content, }); rec = { decision, createdAt: Date.now() }; if (shouldDebugLog(live, derived.channelId)) { api.logger.info( `dirigent: debug before_prompt_build recompute session=${key} ` + `channel=${derived.channel} channelId=${derived.channelId ?? ""} senderId=${derived.senderId ?? ""} ` + `convSenderId=${String((derived.conv as Record).sender_id ?? "")} ` + `convSender=${String((derived.conv as Record).sender ?? "")} ` + `convChannelId=${String((derived.conv as Record).channel_id ?? "")} ` + `decision=${decision.reason} shouldNoReply=${decision.shouldUseNoReply} shouldInject=${decision.shouldInjectEndMarkerPrompt}`, ); } } sessionDecision.delete(key); if (sessionInjected.has(key)) { if (shouldDebugLog(live, undefined)) { api.logger.info(`dirigent: debug before_prompt_build session=${key} inject skipped (already injected)`); } return; } if (!rec.decision.shouldInjectEndMarkerPrompt) { if (shouldDebugLog(live, undefined)) { api.logger.info(`dirigent: debug before_prompt_build session=${key} inject=false reason=${rec.decision.reason}`); } return; } const prompt = ((event as Record).prompt as string) || ""; const derived = deriveDecisionInputFromPrompt({ prompt, messageProvider: ctx.messageProvider, sessionKey: key, ctx: ctx as Record, event: event as Record, }); const policy = resolvePolicy(live, derived.channelId, policyState.channelPolicies as Record); const isGroupChat = derived.conv.is_group_chat === true || derived.conv.is_group_chat === "true"; const schedulingId = live.schedulingIdentifier || "➡️"; const waitId = live.waitIdentifier || "👤"; const instruction = buildEndMarkerInstruction(policy.endSymbols, isGroupChat, schedulingId, waitId); let identity = ""; if (isGroupChat && ctx.agentId) { const idStr = buildAgentIdentity(api, ctx.agentId); if (idStr) identity = idStr + "\n\n"; } let schedulingInstruction = ""; if (isGroupChat) { schedulingInstruction = buildSchedulingIdentifierInstruction(schedulingId); } sessionInjected.add(key); api.logger.info(`dirigent: prepend end marker instruction for session=${key}, reason=${rec.decision.reason} isGroupChat=${isGroupChat}`); return { prependContext: identity + instruction + schedulingInstruction }; }); }