import type { AgentMessage } from "@mariozechner/pi-agent-core"; import type { ReplyPayload } from "../auto-reply/reply-payload.js"; import type { ReplyDispatchKind, ReplyDispatcher, } from "../auto-reply/reply/reply-dispatcher.types.js"; import type { FinalizedMsgContext } from "../auto-reply/templating.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import type { TtsAutoMode } from "../config/types.tts.js"; import { PLUGIN_PROMPT_MUTATION_RESULT_FIELDS, stripPromptMutationFieldsFromLegacyHookResult, } from "./hook-before-agent-start.types.js"; import type { PluginHookBeforeAgentStartEvent, PluginHookBeforeAgentStartResult, PluginHookBeforeModelResolveEvent, PluginHookBeforeModelResolveResult, PluginHookBeforePromptBuildEvent, PluginHookBeforePromptBuildResult, } from "./hook-before-agent-start.types.js"; import type { PluginHookInboundClaimContext, PluginHookInboundClaimEvent, PluginHookMessageContext, PluginHookMessageReceivedEvent, PluginHookMessageSendingEvent, PluginHookMessageSendingResult, PluginHookMessageSentEvent, } from "./hook-message.types.js"; export type { PluginHookBeforeAgentStartEvent, PluginHookBeforeAgentStartOverrideResult, PluginHookBeforeAgentStartResult, PluginHookBeforeModelResolveEvent, PluginHookBeforeModelResolveResult, PluginHookBeforePromptBuildEvent, PluginHookBeforePromptBuildResult, } from "./hook-before-agent-start.types.js"; export { PLUGIN_PROMPT_MUTATION_RESULT_FIELDS, stripPromptMutationFieldsFromLegacyHookResult, } from "./hook-before-agent-start.types.js"; export type { PluginHookInboundClaimContext, PluginHookInboundClaimEvent, PluginHookMessageContext, PluginHookMessageReceivedEvent, PluginHookMessageSendingEvent, PluginHookMessageSendingResult, PluginHookMessageSentEvent, } from "./hook-message.types.js"; export type PluginHookName = | "before_model_resolve" | "before_prompt_build" | "before_agent_start" | "before_agent_reply" | "llm_input" | "llm_output" | "agent_end" | "before_compaction" | "after_compaction" | "before_reset" | "inbound_claim" | "message_received" | "message_sending" | "message_sent" | "before_tool_call" | "after_tool_call" | "tool_result_persist" | "before_message_write" | "session_start" | "session_end" | "subagent_spawning" | "subagent_delivery_target" | "subagent_spawned" | "subagent_ended" | "gateway_start" | "gateway_stop" | "before_dispatch" | "reply_dispatch" | "before_install"; export const PLUGIN_HOOK_NAMES = [ "before_model_resolve", "before_prompt_build", "before_agent_start", "before_agent_reply", "llm_input", "llm_output", "agent_end", "before_compaction", "after_compaction", "before_reset", "inbound_claim", "message_received", "message_sending", "message_sent", "before_tool_call", "after_tool_call", "tool_result_persist", "before_message_write", "session_start", "session_end", "subagent_spawning", "subagent_delivery_target", "subagent_spawned", "subagent_ended", "gateway_start", "gateway_stop", "before_dispatch", "reply_dispatch", "before_install", ] as const satisfies readonly PluginHookName[]; type MissingPluginHookNames = Exclude; type AssertAllPluginHookNamesListed = MissingPluginHookNames extends never ? true : never; const assertAllPluginHookNamesListed: AssertAllPluginHookNamesListed = true; void assertAllPluginHookNamesListed; const pluginHookNameSet = new Set(PLUGIN_HOOK_NAMES); export const isPluginHookName = (hookName: unknown): hookName is PluginHookName => typeof hookName === "string" && pluginHookNameSet.has(hookName as PluginHookName); export const PROMPT_INJECTION_HOOK_NAMES = [ "before_prompt_build", "before_agent_start", ] as const satisfies readonly PluginHookName[]; export type PromptInjectionHookName = (typeof PROMPT_INJECTION_HOOK_NAMES)[number]; const promptInjectionHookNameSet = new Set(PROMPT_INJECTION_HOOK_NAMES); export const isPromptInjectionHookName = (hookName: PluginHookName): boolean => promptInjectionHookNameSet.has(hookName); export type PluginHookAgentContext = { runId?: string; agentId?: string; sessionKey?: string; sessionId?: string; workspaceDir?: string; modelProviderId?: string; modelId?: string; messageProvider?: string; trigger?: string; channelId?: string; }; export type PluginHookBeforeAgentReplyEvent = { cleanedBody: string; }; export type PluginHookBeforeAgentReplyResult = { handled: boolean; reply?: ReplyPayload; reason?: string; }; export type PluginHookLlmInputEvent = { runId: string; sessionId: string; provider: string; model: string; systemPrompt?: string; prompt: string; historyMessages: unknown[]; imagesCount: number; }; export type PluginHookLlmOutputEvent = { runId: string; sessionId: string; provider: string; model: string; assistantTexts: string[]; lastAssistant?: unknown; usage?: { input?: number; output?: number; cacheRead?: number; cacheWrite?: number; total?: number; }; }; export type PluginHookAgentEndEvent = { messages: unknown[]; success: boolean; error?: string; durationMs?: number; }; export type PluginHookBeforeCompactionEvent = { messageCount: number; compactingCount?: number; tokenCount?: number; messages?: unknown[]; sessionFile?: string; }; export type PluginHookBeforeResetEvent = { sessionFile?: string; messages?: unknown[]; reason?: string; }; export type PluginHookAfterCompactionEvent = { messageCount: number; tokenCount?: number; compactedCount: number; sessionFile?: string; }; export type PluginHookInboundClaimResult = { handled: boolean; }; export type PluginHookBeforeDispatchEvent = { content: string; body?: string; channel?: string; sessionKey?: string; senderId?: string; isGroup?: boolean; timestamp?: number; }; export type PluginHookBeforeDispatchContext = { channelId?: string; accountId?: string; conversationId?: string; sessionKey?: string; senderId?: string; }; export type PluginHookBeforeDispatchResult = { handled: boolean; text?: string; }; export type PluginHookReplyDispatchEvent = { ctx: FinalizedMsgContext; runId?: string; sessionKey?: string; inboundAudio: boolean; sessionTtsAuto?: TtsAutoMode; ttsChannel?: string; suppressUserDelivery?: boolean; shouldRouteToOriginating: boolean; originatingChannel?: string; originatingTo?: string; shouldSendToolSummaries: boolean; sendPolicy: "allow" | "deny"; isTailDispatch?: boolean; }; export type PluginHookReplyDispatchContext = { cfg: OpenClawConfig; dispatcher: ReplyDispatcher; abortSignal?: AbortSignal; onReplyStart?: () => Promise | void; recordProcessed: ( outcome: "completed" | "skipped" | "error", opts?: { reason?: string; error?: string; }, ) => void; markIdle: (reason: string) => void; }; export type PluginHookReplyDispatchResult = { handled: boolean; queuedFinal: boolean; counts: Record; }; export type PluginHookToolContext = { agentId?: string; sessionKey?: string; sessionId?: string; runId?: string; toolName: string; toolCallId?: string; }; export type PluginHookBeforeToolCallEvent = { toolName: string; params: Record; runId?: string; toolCallId?: string; }; export const PluginApprovalResolutions = { ALLOW_ONCE: "allow-once", ALLOW_ALWAYS: "allow-always", DENY: "deny", TIMEOUT: "timeout", CANCELLED: "cancelled", } as const; export type PluginApprovalResolution = (typeof PluginApprovalResolutions)[keyof typeof PluginApprovalResolutions]; export type PluginHookBeforeToolCallResult = { params?: Record; block?: boolean; blockReason?: string; requireApproval?: { title: string; description: string; severity?: "info" | "warning" | "critical"; timeoutMs?: number; timeoutBehavior?: "allow" | "deny"; pluginId?: string; onResolution?: (decision: PluginApprovalResolution) => Promise | void; }; }; export type PluginHookAfterToolCallEvent = { toolName: string; params: Record; runId?: string; toolCallId?: string; result?: unknown; error?: string; durationMs?: number; }; export type PluginHookToolResultPersistContext = { agentId?: string; sessionKey?: string; toolName?: string; toolCallId?: string; }; export type PluginHookToolResultPersistEvent = { toolName?: string; toolCallId?: string; message: AgentMessage; isSynthetic?: boolean; }; export type PluginHookToolResultPersistResult = { message?: AgentMessage; }; export type PluginHookBeforeMessageWriteEvent = { message: AgentMessage; sessionKey?: string; agentId?: string; }; export type PluginHookBeforeMessageWriteResult = { block?: boolean; message?: AgentMessage; }; export type PluginHookSessionContext = { agentId?: string; sessionId: string; sessionKey?: string; }; export type PluginHookSessionStartEvent = { sessionId: string; sessionKey?: string; resumedFrom?: string; }; export type PluginHookSessionEndReason = | "new" | "reset" | "idle" | "daily" | "compaction" | "deleted" | "unknown"; export type PluginHookSessionEndEvent = { sessionId: string; sessionKey?: string; messageCount: number; durationMs?: number; reason?: PluginHookSessionEndReason; sessionFile?: string; transcriptArchived?: boolean; nextSessionId?: string; nextSessionKey?: string; }; export type PluginHookSubagentContext = { runId?: string; childSessionKey?: string; requesterSessionKey?: string; }; export type PluginHookSubagentTargetKind = "subagent" | "acp"; type PluginHookSubagentSpawnBase = { childSessionKey: string; agentId: string; label?: string; mode: "run" | "session"; requester?: { channel?: string; accountId?: string; to?: string; threadId?: string | number; }; threadRequested: boolean; }; export type PluginHookSubagentSpawningEvent = PluginHookSubagentSpawnBase; export type PluginHookSubagentSpawningResult = | { status: "ok"; threadBindingReady?: boolean; } | { status: "error"; error: string; }; export type PluginHookSubagentDeliveryTargetEvent = { childSessionKey: string; requesterSessionKey: string; requesterOrigin?: { channel?: string; accountId?: string; to?: string; threadId?: string | number; }; childRunId?: string; spawnMode?: "run" | "session"; expectsCompletionMessage: boolean; }; export type PluginHookSubagentDeliveryTargetResult = { origin?: { channel?: string; accountId?: string; to?: string; threadId?: string | number; }; }; export type PluginHookSubagentSpawnedEvent = PluginHookSubagentSpawnBase & { runId: string; }; export type PluginHookSubagentEndedEvent = { targetSessionKey: string; targetKind: PluginHookSubagentTargetKind; reason: string; sendFarewell?: boolean; accountId?: string; runId?: string; endedAt?: number; outcome?: "ok" | "error" | "timeout" | "killed" | "reset" | "deleted"; error?: string; }; export type PluginHookGatewayContext = { port?: number; }; export type PluginHookGatewayStartEvent = { port: number; }; export type PluginHookGatewayStopEvent = { reason?: string; }; export type PluginInstallTargetType = "skill" | "plugin"; export type PluginInstallRequestKind = | "skill-install" | "plugin-dir" | "plugin-archive" | "plugin-file" | "plugin-npm"; export type PluginInstallSourcePathKind = "file" | "directory"; export type PluginInstallFinding = { ruleId: string; severity: "info" | "warn" | "critical"; file: string; line: number; message: string; }; export type PluginHookBeforeInstallRequest = { kind: PluginInstallRequestKind; mode: "install" | "update"; requestedSpecifier?: string; }; export type PluginHookBeforeInstallBuiltinScan = { status: "ok" | "error"; scannedFiles: number; critical: number; warn: number; info: number; findings: PluginInstallFinding[]; error?: string; }; export type PluginHookBeforeInstallSkillInstallSpec = { id?: string; kind: "brew" | "node" | "go" | "uv" | "download"; label?: string; bins?: string[]; os?: string[]; formula?: string; package?: string; module?: string; url?: string; archive?: string; extract?: boolean; stripComponents?: number; targetDir?: string; }; export type PluginHookBeforeInstallSkill = { installId: string; installSpec?: PluginHookBeforeInstallSkillInstallSpec; }; export type PluginHookBeforeInstallPlugin = { pluginId: string; contentType: "bundle" | "package" | "file"; packageName?: string; manifestId?: string; version?: string; extensions?: string[]; }; export type PluginHookBeforeInstallContext = { targetType: PluginInstallTargetType; requestKind: PluginInstallRequestKind; origin?: string; }; export type PluginHookBeforeInstallEvent = { targetType: PluginInstallTargetType; targetName: string; sourcePath: string; sourcePathKind: PluginInstallSourcePathKind; origin?: string; request: PluginHookBeforeInstallRequest; builtinScan: PluginHookBeforeInstallBuiltinScan; skill?: PluginHookBeforeInstallSkill; plugin?: PluginHookBeforeInstallPlugin; }; export type PluginHookBeforeInstallResult = { findings?: PluginInstallFinding[]; block?: boolean; blockReason?: string; }; export type PluginHookHandlerMap = { before_model_resolve: ( event: PluginHookBeforeModelResolveEvent, ctx: PluginHookAgentContext, ) => | Promise | PluginHookBeforeModelResolveResult | void; before_prompt_build: ( event: PluginHookBeforePromptBuildEvent, ctx: PluginHookAgentContext, ) => Promise | PluginHookBeforePromptBuildResult | void; before_agent_start: ( event: PluginHookBeforeAgentStartEvent, ctx: PluginHookAgentContext, ) => Promise | PluginHookBeforeAgentStartResult | void; before_agent_reply: ( event: PluginHookBeforeAgentReplyEvent, ctx: PluginHookAgentContext, ) => Promise | PluginHookBeforeAgentReplyResult | void; llm_input: (event: PluginHookLlmInputEvent, ctx: PluginHookAgentContext) => Promise | void; llm_output: ( event: PluginHookLlmOutputEvent, ctx: PluginHookAgentContext, ) => Promise | void; agent_end: (event: PluginHookAgentEndEvent, ctx: PluginHookAgentContext) => Promise | void; before_compaction: ( event: PluginHookBeforeCompactionEvent, ctx: PluginHookAgentContext, ) => Promise | void; after_compaction: ( event: PluginHookAfterCompactionEvent, ctx: PluginHookAgentContext, ) => Promise | void; before_reset: ( event: PluginHookBeforeResetEvent, ctx: PluginHookAgentContext, ) => Promise | void; inbound_claim: ( event: PluginHookInboundClaimEvent, ctx: PluginHookInboundClaimContext, ) => Promise | PluginHookInboundClaimResult | void; before_dispatch: ( event: PluginHookBeforeDispatchEvent, ctx: PluginHookBeforeDispatchContext, ) => Promise | PluginHookBeforeDispatchResult | void; reply_dispatch: ( event: PluginHookReplyDispatchEvent, ctx: PluginHookReplyDispatchContext, ) => Promise | PluginHookReplyDispatchResult | void; message_received: ( event: PluginHookMessageReceivedEvent, ctx: PluginHookMessageContext, ) => Promise | void; message_sending: ( event: PluginHookMessageSendingEvent, ctx: PluginHookMessageContext, ) => Promise | PluginHookMessageSendingResult | void; message_sent: ( event: PluginHookMessageSentEvent, ctx: PluginHookMessageContext, ) => Promise | void; before_tool_call: ( event: PluginHookBeforeToolCallEvent, ctx: PluginHookToolContext, ) => Promise | PluginHookBeforeToolCallResult | void; after_tool_call: ( event: PluginHookAfterToolCallEvent, ctx: PluginHookToolContext, ) => Promise | void; tool_result_persist: ( event: PluginHookToolResultPersistEvent, ctx: PluginHookToolResultPersistContext, ) => PluginHookToolResultPersistResult | void; before_message_write: ( event: PluginHookBeforeMessageWriteEvent, ctx: { agentId?: string; sessionKey?: string }, ) => PluginHookBeforeMessageWriteResult | void; session_start: ( event: PluginHookSessionStartEvent, ctx: PluginHookSessionContext, ) => Promise | void; session_end: ( event: PluginHookSessionEndEvent, ctx: PluginHookSessionContext, ) => Promise | void; subagent_spawning: ( event: PluginHookSubagentSpawningEvent, ctx: PluginHookSubagentContext, ) => Promise | PluginHookSubagentSpawningResult | void; subagent_delivery_target: ( event: PluginHookSubagentDeliveryTargetEvent, ctx: PluginHookSubagentContext, ) => | Promise | PluginHookSubagentDeliveryTargetResult | void; subagent_spawned: ( event: PluginHookSubagentSpawnedEvent, ctx: PluginHookSubagentContext, ) => Promise | void; subagent_ended: ( event: PluginHookSubagentEndedEvent, ctx: PluginHookSubagentContext, ) => Promise | void; gateway_start: ( event: PluginHookGatewayStartEvent, ctx: PluginHookGatewayContext, ) => Promise | void; gateway_stop: ( event: PluginHookGatewayStopEvent, ctx: PluginHookGatewayContext, ) => Promise | void; before_install: ( event: PluginHookBeforeInstallEvent, ctx: PluginHookBeforeInstallContext, ) => Promise | PluginHookBeforeInstallResult | void; }; export type PluginHookRegistration = { pluginId: string; hookName: K; handler: PluginHookHandlerMap[K]; priority?: number; source: string; };