135 lines
5.1 KiB
TypeScript
135 lines
5.1 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;
|
|
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<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 = 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 };
|
|
});
|
|
}
|