import type { AgentMessage } from "../agents/runtime/index.js"; import type { SourceReplyDeliveryMode } from "../auto-reply/get-reply-options.types.js"; 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 type { DiagnosticTraceContext } from "../infra/diagnostic-trace-context.js"; import type { PluginHookBeforeAgentStartEvent, PluginHookBeforeAgentStartResult, PluginHookBeforeModelResolveEvent, PluginHookBeforeModelResolveResult, PluginHookBeforePromptBuildEvent, PluginHookBeforePromptBuildResult, } from "./hook-before-agent-start.types.js"; import type { PluginHookBeforeToolCallResult } from "./hook-before-tool-call-result.js"; import type { InputGateDecision } from "./hook-decision-types.js"; import type { PluginHookInboundClaimContext, PluginHookInboundClaimEvent, PluginHookMessageContext, PluginHookMessageReceivedEvent, PluginHookMessageSendingEvent, PluginHookMessageSendingResult, PluginHookMessageSentEvent, } from "./hook-message.types.js"; import type { PluginJsonValue } from "./host-hook-json.js"; import type { PluginAgentTurnPrepareEvent, PluginAgentTurnPrepareResult, PluginHeartbeatPromptContributionEvent, PluginHeartbeatPromptContributionResult, } from "./host-hook-turn-types.js"; export type { PluginHookBeforeAgentStartEvent, PluginHookBeforeAgentStartOverrideResult, PluginHookBeforeAgentStartResult, PluginHookBeforeModelResolveAttachment, 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 { PluginAgentTurnPrepareEvent, PluginAgentTurnPrepareResult, PluginHeartbeatPromptContributionEvent, PluginHeartbeatPromptContributionResult, } from "./host-hook-turn-types.js"; export type { PluginHookInboundClaimContext, PluginHookInboundClaimEvent, PluginHookMessageContext, PluginHookMessageReceivedEvent, PluginHookMessageSendingEvent, PluginHookMessageSendingResult, PluginHookMessageSentEvent, } from "./hook-message.types.js"; export { PluginApprovalResolutions, type PluginApprovalResolution, type PluginHookBeforeToolCallResult, } from "./hook-before-tool-call-result.js"; export type PluginHookName = | "before_model_resolve" | "agent_turn_prepare" | "before_prompt_build" | "before_agent_start" | "before_agent_reply" | "model_call_started" | "model_call_ended" | "llm_input" | "llm_output" | "before_agent_finalize" | "agent_end" | "before_compaction" | "after_compaction" | "before_reset" | "inbound_claim" | "message_received" | "message_sending" | "reply_payload_sending" | "message_sent" | "before_tool_call" | "after_tool_call" | "tool_result_persist" | "before_message_write" | "session_start" | "session_end" /** * @deprecated Core prepares thread-bound subagent bindings through channel * session-binding adapters before `subagent_spawned` fires. Use * `subagent_spawned` for post-launch observation in new plugins. */ | "subagent_spawning" | "subagent_delivery_target" | "subagent_spawned" | "subagent_ended" /** @deprecated Use gateway_stop. */ | "deactivate" | "gateway_start" | "gateway_stop" | "heartbeat_prompt_contribution" | "cron_changed" | "before_dispatch" | "reply_dispatch" | "before_install" | "before_agent_run" | "resolve_exec_env"; export const PLUGIN_HOOK_NAMES = [ "before_model_resolve", "agent_turn_prepare", "before_prompt_build", "before_agent_start", "before_agent_reply", "model_call_started", "model_call_ended", "llm_input", "llm_output", "before_agent_finalize", "agent_end", "before_compaction", "after_compaction", "before_reset", "inbound_claim", "message_received", "message_sending", "reply_payload_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", "deactivate", "gateway_start", "gateway_stop", "heartbeat_prompt_contribution", "cron_changed", "before_dispatch", "reply_dispatch", "before_install", "before_agent_run", "resolve_exec_env", ] as const satisfies readonly PluginHookName[]; type MissingPluginHookNames = Exclude; type AssertAllPluginHookNamesListed = MissingPluginHookNames extends never ? true : never; const assertAllPluginHookNamesListed: AssertAllPluginHookNamesListed = true; void assertAllPluginHookNamesListed; export type DeprecatedPluginHookName = "subagent_spawning" | "deactivate"; export type PluginHookDeprecation = { replacement: string; reason: string; removeAfter?: string; }; export const DEPRECATED_PLUGIN_HOOKS = { subagent_spawning: { replacement: "`subagent_spawned` for observation; core session bindings for routing", reason: "Core prepares thread-bound subagent bindings through channel session-binding adapters before `subagent_spawned` fires.", removeAfter: "2026-08-30", }, deactivate: { replacement: "`gateway_stop`", reason: "`deactivate` is a legacy cleanup hook alias for `gateway_stop`.", removeAfter: "2026-08-16", }, } as const satisfies Record; export const DEPRECATED_PLUGIN_HOOK_NAMES = Object.keys( DEPRECATED_PLUGIN_HOOKS, ) as DeprecatedPluginHookName[]; const deprecatedPluginHookNameSet = new Set(DEPRECATED_PLUGIN_HOOK_NAMES); export const isDeprecatedPluginHookName = ( hookName: PluginHookName, ): hookName is DeprecatedPluginHookName => deprecatedPluginHookNameSet.has(hookName); 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 = [ "agent_turn_prepare", "before_prompt_build", "before_agent_start", "heartbeat_prompt_contribution", ] 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 const CONVERSATION_HOOK_NAMES = [ "before_model_resolve", "before_agent_reply", "llm_input", "llm_output", "before_agent_finalize", "agent_end", "before_agent_run", ] as const satisfies readonly PluginHookName[]; export type ConversationHookName = (typeof CONVERSATION_HOOK_NAMES)[number]; const conversationHookNameSet = new Set(CONVERSATION_HOOK_NAMES); export const isConversationHookName = (hookName: PluginHookName): boolean => conversationHookNameSet.has(hookName); export type PluginHookAgentContext = { runId?: string; jobId?: string; trace?: DiagnosticTraceContext; agentId?: string; sessionKey?: string; sessionId?: string; workspaceDir?: string; modelProviderId?: string; modelId?: string; messageProvider?: string; /** Channel/plugin id for channel-originated runs, e.g. `discord`. */ channel?: string; /** Conversation target id for channel-originated runs. Mirrors `channelId` for compatibility. */ chatId?: string; /** Sender identity for channel-originated runs when available. */ senderId?: string; trigger?: string; channelId?: string; /** Resolved effective context-token budget after model/config/agent caps. */ contextTokenBudget?: number; /** Source that supplied the resolved context-token budget. */ contextWindowSource?: PluginHookContextWindowSource; /** Native/configured reference window when a lower cap wins. */ contextWindowReferenceTokens?: number; }; export type PluginHookContextWindowSource = | "model" | "modelsConfig" | "agentContextTokens" | "default"; 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; tools?: unknown[]; }; export type PluginHookModelCallBaseEvent = { runId: string; callId: string; sessionKey?: string; sessionId?: string; provider: string; model: string; api?: string; transport?: string; /** Resolved effective context-token budget after model/config/agent caps. */ contextTokenBudget?: number; /** Source that supplied the resolved context-token budget. */ contextWindowSource?: PluginHookContextWindowSource; /** Native/configured reference window when a lower cap wins. */ contextWindowReferenceTokens?: number; }; export type PluginHookModelCallStartedEvent = PluginHookModelCallBaseEvent; export type PluginHookModelCallEndedEvent = PluginHookModelCallBaseEvent & { durationMs: number; outcome: "completed" | "error"; errorCategory?: string; failureKind?: "aborted" | "connection_closed" | "connection_reset" | "terminated" | "timeout"; requestPayloadBytes?: number; responseStreamBytes?: number; timeToFirstByteMs?: number; upstreamRequestIdHash?: string; }; export type PluginHookLlmOutputEvent = { runId: string; sessionId: string; provider: string; model: string; /** Resolved effective context-token budget after model/config/agent caps. */ contextTokenBudget?: number; /** Source that supplied the resolved context-token budget. */ contextWindowSource?: PluginHookContextWindowSource; /** Native/configured reference window when a lower cap wins. */ contextWindowReferenceTokens?: number; /** * Fully resolved provider/model ref used for the call. * * This intentionally keeps the provider prefix so operator tooling can * distinguish e.g. openai/gpt-5.4 from codex/gpt-5.4 even when display * names collapse to just the model id. */ resolvedRef?: string; /** * Harness/backend responsible for the model loop. Kept separate from * `resolvedRef` so provider/model consumers keep a stable parse contract. */ harnessId?: string; /** The original user prompt that produced this output. */ prompt?: string; assistantTexts: string[]; lastAssistant?: unknown; usage?: { input?: number; output?: number; cacheRead?: number; cacheWrite?: number; total?: number; }; /** * Requested reasoning/think effort for this call (provider think level, e.g. * "off" | "low" | "medium" | "high"). Lets a passive footer show the mode the * user is actually running without re-deriving it. */ reasoningEffort?: string; /** Whether fast mode was active for this call. */ fastMode?: boolean; }; export type PluginHookAgentEndEvent = { runId?: string; messages: unknown[]; success: boolean; error?: string; durationMs?: number; }; export type PluginHookBeforeAgentFinalizeEvent = { runId?: string; sessionId: string; sessionKey?: string; turnId?: string; provider?: string; model?: string; cwd?: string; transcriptPath?: string; stopHookActive: boolean; lastAssistantMessage?: string; messages?: unknown[]; }; export type PluginHookBeforeAgentFinalizeResult = { /** * continue: accept normal finalization. * revise: block finalization and ask the harness for another model pass. * finalize: force finalization even if another hook requested revision. */ action?: "continue" | "revise" | "finalize"; reason?: string; retry?: { instruction: string; idempotencyKey?: string; maxAttempts?: 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; reply?: ReplyPayload; }; export type PluginHookBeforeDispatchEvent = { content: string; body?: string; channel?: string; sessionKey?: string; senderId?: string; replyToId?: string; replyToIdFull?: string; replyToBody?: string; replyToSender?: string; replyToIsQuote?: boolean; isGroup?: boolean; timestamp?: number; }; export type PluginHookBeforeDispatchContext = { channelId?: string; accountId?: string; conversationId?: string; sessionKey?: string; senderId?: string; replyToId?: string; replyToIdFull?: string; replyToBody?: string; replyToSender?: string; replyToIsQuote?: boolean; }; export type PluginHookBeforeDispatchResult = { handled: boolean; text?: string; }; export type PluginHookReplyDispatchEvent = { ctx: FinalizedMsgContext; runId?: string; sessionKey?: string; toolsAllow?: string[]; images?: Array<{ data: string; mimeType: string }>; inboundAudio: boolean; sessionTtsAuto?: TtsAutoMode; ttsChannel?: string; suppressUserDelivery?: boolean; suppressReplyLifecycle?: boolean; sourceReplyDeliveryMode?: SourceReplyDeliveryMode; shouldRouteToOriginating: boolean; originatingChannel?: string; originatingTo?: string; originatingAccountId?: string; originatingThreadId?: string | number; 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; }; /** * Per-turn execution state for the outbound reply, available to every harness * (embedded, CLI, Codex app-server) — sourced from the unified `runResult.meta` * at dispatch, not from the harness-specific `llm_output` hook. Lets a plugin * render a passive per-response footer without re-deriving run state. */ export type PluginHookReplyUsageState = { provider?: string; model?: string; /** Resolved provider/model ref actually used (keeps the provider prefix). */ resolvedRef?: string; /** Requested reasoning/think effort (e.g. "off" | "low" | "medium" | "high"). */ reasoningEffort?: string; fastMode?: boolean; /** True when a model fallback was used for this turn. */ fallbackUsed?: boolean; /** Owning agent + session for this reply. */ agentId?: string; sessionId?: string; /** Chat surface kind (e.g. "direct" | "group"). */ chatType?: string; /** Credential mode the turn ran under (e.g. "oauth" | "api_key"). */ authMode?: string; /** Session model-override source, when a non-default model was pinned. */ overrideSource?: string; /** Provider/model ref requested for the turn (vs resolvedRef actually used). */ requested?: string; /** Estimated cost of this turn in USD, when a cost table is configured. */ turnUsd?: number; /** Wall-clock duration of the turn in milliseconds. */ durationMs?: number; /** Owning agent's configured identity (name/emoji/avatar), when set. */ identity?: { name?: string; emoji?: string; avatar?: string }; compactionCount?: number; /** Effective context-token budget after model/config/agent caps. */ contextTokenBudget?: number; /** * Actual context-window occupancy at the END of the turn — the final model * call's prompt tokens, NOT the per-turn aggregate. This is the value * `context.used_tokens` / `context.pct_used` must use: the aggregate prompt * total over a multi-call tool loop overstates occupancy (often beyond the * window). Absent on harnesses that don't report it (the contract then falls * back to the aggregate prompt total, which is correct for single-call turns). */ contextUsedTokens?: number; usage?: { input?: number; output?: number; cacheRead?: number; cacheWrite?: number; total?: number; }; /** * Usage from the FINAL model call of the turn only — vs `usage`, which is the * turn aggregate summed across every tool-loop call. Lets a footer render the * last exchange's i/o + cache instead of the whole turn. Absent on harnesses * that don't report per-call usage. */ lastUsage?: { input?: number; output?: number; cacheRead?: number; cacheWrite?: number; total?: number; }; }; export type PluginHookReplyPayloadSendingEvent = { payload: PluginHookReplyPayload; kind: ReplyDispatchKind; channel?: string; sessionKey?: string; runId?: string; /** * Per-turn usage snapshot for live dispatcher delivery. Absent on durable * delivery/replay paths, and whenever no exact run correlation is available. */ usageState?: PluginHookReplyUsageState; }; export type PluginHookReplyPayload = Omit; export type PluginHookReplyPayloadSendingContext = PluginHookMessageContext; export type PluginHookReplyPayloadSendingResult = { payload?: PluginHookReplyPayload; cancel?: boolean; reason?: string; }; export type PluginHookToolKind = "code_mode_exec"; export type PluginHookToolInputKind = "javascript" | "typescript"; export type PluginHookToolContext = { agentId?: string; sessionKey?: string; sessionId?: string; runId?: string; trace?: DiagnosticTraceContext; toolName: string; /** Host-authoritative discriminator for tools that intentionally share names. */ toolKind?: PluginHookToolKind; /** Host-authoritative input/runtime family for tools whose payloads need policy distinction. */ toolInputKind?: PluginHookToolInputKind; toolCallId?: string; getSessionExtension?: (namespace: string) => PluginJsonValue | undefined; channelId?: string; }; export type PluginHookBeforeToolCallEvent = { toolName: string; params: Record; /** Host-authoritative discriminator for tools that intentionally share names. */ toolKind?: PluginHookToolKind; /** Host-authoritative input/runtime family for tools whose payloads need policy distinction. */ toolInputKind?: PluginHookToolInputKind; runId?: string; toolCallId?: string; /** * Optional best-effort destination path hints the host derived from `params` * for well-known tool envelopes (e.g. `apply_patch`). * * This is a convenience hint, not an authoritative parse result: the host's * extractor may be intentionally lenient and can return paths for malformed * or partial envelopes. Plugins may use `derivedPaths` as a fast path, but * should parse and validate `params` themselves when correctness or policy * decisions depend on the exact set of affected paths. Absent for tools the * host does not know how to derive paths for. */ derivedPaths?: readonly string[]; }; 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" | "shutdown" | "restart" | "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; }; /** * @deprecated Core prepares thread-bound subagent bindings through channel * session-binding adapters before `subagent_spawned` fires. Use * `subagent_spawned` for post-launch observation in new plugins. */ export type PluginHookSubagentSpawningEvent = PluginHookSubagentSpawnBase; /** * @deprecated Core prepares thread-bound subagent bindings through channel * session-binding adapters before `subagent_spawned` fires. Returning routing * data from `subagent_spawning` is retained only for older runtimes. */ export type PluginHookSubagentSpawningResult = | { status: "ok"; /** * @deprecated Core now resolves thread-bound spawn routing from session * bindings and channel route projection. Keep returning this only for * compatibility with older OpenClaw runtimes. */ threadBindingReady?: boolean; /** * @deprecated Use channel `resolveDeliveryTarget` plus core * `SessionBindingRecord` projection instead of returning an ad hoc * delivery route from this hook. */ deliveryOrigin?: { channel?: string; accountId?: string; to?: string; threadId?: string | number; }; } | { 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; }; /** * @deprecated Core route projection resolves subagent delivery targets from * `SessionBindingRecord` and channel `resolveDeliveryTarget`. This hook result * remains for plugin compatibility during the transition. */ export type PluginHookSubagentDeliveryTargetResult = { origin?: { channel?: string; accountId?: string; to?: string; threadId?: string | number; }; }; export type PluginHookSubagentSpawnedEvent = PluginHookSubagentSpawnBase & { runId: string; /** Fully resolved provider/model ref applied to the spawned child session. */ resolvedModel?: string; /** Provider prefix parsed from resolvedModel when the ref includes one. */ resolvedProvider?: 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; config?: OpenClawConfig; workspaceDir?: string; getCron?: () => PluginHookGatewayCronService | undefined; }; export type PluginHookGatewayStartEvent = { port: number; }; export type PluginHookGatewayStopEvent = { reason?: string; }; export type PluginHookGatewayCronRunStatus = "ok" | "error" | "skipped"; export type PluginHookGatewayCronDeliveryStatus = | "not-requested" | "delivered" | "not-delivered" | "unknown"; export type PluginHookGatewayCronJobState = { nextRunAtMs?: number; runningAtMs?: number; lastRunAtMs?: number; lastRunStatus?: PluginHookGatewayCronRunStatus; lastError?: string; lastDurationMs?: number; lastDelivered?: boolean; lastDeliveryStatus?: PluginHookGatewayCronDeliveryStatus; lastDeliveryError?: string; lastFailureNotificationDelivered?: boolean; lastFailureNotificationDeliveryStatus?: PluginHookGatewayCronDeliveryStatus; lastFailureNotificationDeliveryError?: string; }; export type PluginHookGatewayCronJob = { id: string; /** Agent id that owns this cron job. */ agentId?: string; name?: string; description?: string; enabled?: boolean; schedule?: | { kind: "cron"; expr?: string; tz?: string; staggerMs?: number; } | { kind: "at"; at?: string; } | { kind: "every"; everyMs?: number; anchorMs?: number; }; sessionTarget?: string; wakeMode?: string; payload?: { kind?: string; text?: string; }; state?: PluginHookGatewayCronJobState; createdAtMs?: number; updatedAtMs?: number; }; export type PluginHookCronChangedEvent = { action: "added" | "updated" | "removed" | "started" | "finished"; jobId: string; job?: PluginHookGatewayCronJob; /** Top-level session target for downstream routing (mirrors job.sessionTarget). */ sessionTarget?: string; /** Agent id that owns this cron job (mirrors job.agentId). */ agentId?: string; runAtMs?: number; durationMs?: number; status?: PluginHookGatewayCronRunStatus; error?: string; summary?: string; delivered?: boolean; deliveryStatus?: PluginHookGatewayCronDeliveryStatus; deliveryError?: string; sessionId?: string; sessionKey?: string; runId?: string; nextRunAtMs?: number; model?: string; provider?: string; }; export type PluginHookGatewayCronCreateInput = { name: string; description: string; enabled: boolean; schedule: { kind: string; expr: string; tz?: string; }; sessionTarget: string; wakeMode: string; payload: { kind: string; text?: string; }; }; export type PluginHookGatewayCronUpdateInput = Partial; export type PluginHookGatewayCronRemoveResult = { removed?: boolean; }; export type PluginHookGatewayCronService = { list: (opts?: { includeDisabled?: boolean }) => Promise; add: (input: PluginHookGatewayCronCreateInput) => Promise; update: (id: string, patch: PluginHookGatewayCronUpdateInput) => Promise; remove: (id: string) => Promise; }; export type PluginInstallTargetType = "skill" | "plugin"; export type PluginInstallRequestKind = | "skill-install" | "plugin-dir" | "plugin-archive" | "plugin-file" | "plugin-npm" | "plugin-git"; 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; }; // --------------------------------------------------------------------------- // before_agent_run — Lifecycle Gate Hook // --------------------------------------------------------------------------- /** Event payload for the before_agent_run gate hook. */ export type PluginHookBeforeAgentRunEvent = { /** The user's message that triggered this run. */ prompt: string; /** Loaded session history before the current prompt is submitted. */ messages: unknown[]; /** Active system prompt prepared for this run. */ systemPrompt?: string; /** Account identity when available. */ accountId?: string; /** Channel the message came from. */ channelId?: string; /** Sender identity when available. */ senderId?: string; /** Trusted sender identity bit when available. */ senderIsOwner?: boolean; }; /** Result type for before_agent_run. Returns pass/block or void (= pass). */ export type PluginHookBeforeAgentRunResult = InputGateDecision | void; export type PluginHookResolveExecEnvEvent = { sessionKey?: string; toolName: "exec"; host: "gateway" | "sandbox" | "node"; }; export type PluginHookResolveExecEnvContext = PluginHookAgentContext; export type PluginHookHandlerMap = { agent_turn_prepare: ( event: PluginAgentTurnPrepareEvent, ctx: PluginHookAgentContext, ) => Promise | PluginAgentTurnPrepareResult | void; before_model_resolve: ( event: PluginHookBeforeModelResolveEvent, ctx: PluginHookAgentContext, ) => | Promise | PluginHookBeforeModelResolveResult | void; before_prompt_build: ( event: PluginHookBeforePromptBuildEvent, ctx: PluginHookAgentContext, ) => Promise | PluginHookBeforePromptBuildResult | void; /** @deprecated Use before_model_resolve and before_prompt_build. */ before_agent_start: ( event: PluginHookBeforeAgentStartEvent, ctx: PluginHookAgentContext, ) => Promise | PluginHookBeforeAgentStartResult | void; before_agent_reply: ( event: PluginHookBeforeAgentReplyEvent, ctx: PluginHookAgentContext, ) => Promise | PluginHookBeforeAgentReplyResult | void; model_call_started: ( event: PluginHookModelCallStartedEvent, ctx: PluginHookAgentContext, ) => Promise | void; model_call_ended: ( event: PluginHookModelCallEndedEvent, ctx: PluginHookAgentContext, ) => Promise | void; llm_input: (event: PluginHookLlmInputEvent, ctx: PluginHookAgentContext) => Promise | void; llm_output: ( event: PluginHookLlmOutputEvent, ctx: PluginHookAgentContext, ) => Promise | void; before_agent_finalize: ( event: PluginHookBeforeAgentFinalizeEvent, ctx: PluginHookAgentContext, ) => | Promise | PluginHookBeforeAgentFinalizeResult | 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; reply_payload_sending: ( event: PluginHookReplyPayloadSendingEvent, ctx: PluginHookReplyPayloadSendingContext, ) => | Promise | PluginHookReplyPayloadSendingResult | 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; /** * @deprecated Core prepares thread-bound subagent bindings through channel * session-binding adapters before `subagent_spawned` fires. Use * `subagent_spawned` for post-launch observation in new plugins. */ 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; /** * Deprecated compatibility alias for gateway_stop. * * New plugins should register gateway_stop directly; the loader normalizes * deactivate registrations onto gateway_stop so cleanup handlers still run * during Gateway shutdown. * * @deprecated Use gateway_stop. */ deactivate: ( event: PluginHookGatewayStopEvent, ctx: PluginHookGatewayContext, ) => Promise | void; gateway_start: ( event: PluginHookGatewayStartEvent, ctx: PluginHookGatewayContext, ) => Promise | void; gateway_stop: ( event: PluginHookGatewayStopEvent, ctx: PluginHookGatewayContext, ) => Promise | void; heartbeat_prompt_contribution: ( event: PluginHeartbeatPromptContributionEvent, ctx: PluginHookAgentContext, ) => | Promise | PluginHeartbeatPromptContributionResult | void; cron_changed: ( event: PluginHookCronChangedEvent, ctx: PluginHookGatewayContext, ) => Promise | void; before_install: ( event: PluginHookBeforeInstallEvent, ctx: PluginHookBeforeInstallContext, ) => Promise | PluginHookBeforeInstallResult | void; before_agent_run: ( event: PluginHookBeforeAgentRunEvent, ctx: PluginHookAgentContext, ) => Promise | PluginHookBeforeAgentRunResult; resolve_exec_env: ( event: PluginHookResolveExecEnvEvent, ctx: PluginHookResolveExecEnvContext, ) => Promise | void> | Record | void; }; export type PluginHookRegistration = { pluginId: string; hookName: K; handler: PluginHookHandlerMap[K]; priority?: number; timeoutMs?: number; source: string; };