Files
Dirigent/plugin/hooks/before-prompt-build.ts

129 lines
4.9 KiB
TypeScript

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<string, DecisionRecord>;
sessionInjected: Set<string>;
policyState: { channelPolicies: Record<string, unknown> };
DECISION_TTL_MS: number;
ensurePolicyStateLoaded: (api: OpenClawPluginApi, config: DirigentConfig) => void;
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,
shouldDebugLog,
buildEndMarkerInstruction,
buildSchedulingIdentifierInstruction,
buildAgentIdentity,
} = deps;
api.on("before_prompt_build", async (event, ctx) => {
const key = ctx.sessionKey;
if (!key) return;
const live = baseConfig 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<string, unknown>).prompt as string) || "";
const derived = deriveDecisionInputFromPrompt({
prompt,
messageProvider: ctx.messageProvider,
sessionKey: key,
ctx: ctx as Record<string, unknown>,
event: event as Record<string, unknown>,
});
const decision = evaluateDecision({
config: live,
channel: derived.channel,
channelId: derived.channelId,
channelPolicies: policyState.channelPolicies as Record<string, any>,
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<string, unknown>).sender_id ?? "")} ` +
`convSender=${String((derived.conv as Record<string, unknown>).sender ?? "")} ` +
`convChannelId=${String((derived.conv as Record<string, unknown>).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<string, unknown>).prompt as string) || "";
const derived = deriveDecisionInputFromPrompt({
prompt,
messageProvider: ctx.messageProvider,
sessionKey: key,
ctx: ctx as Record<string, unknown>,
event: event as Record<string, unknown>,
});
const policy = resolvePolicy(live, derived.channelId, policyState.channelPolicies as Record<string, any>);
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 = `\n\nYour agent identity: ${idStr}.`;
}
}
const schedulingInstruction = isGroupChat ? buildSchedulingIdentifierInstruction(schedulingId) : "";
(event as Record<string, unknown>).prompt = `${prompt}\n\n${instruction}${identity}${schedulingInstruction}`;
sessionInjected.add(key);
});
}