/** * Extension System - Hook Types * * Hook events, handlers, and context types. * Strongly typed hook system with execution modes. */ import type { AgentMessage } from '@earendil-works/pi-agent-core'; export type { AgentMessage }; export type ExtensionHookEvent = 'before_agent_start' | 'agent_end' | 'before_compaction' | 'after_compaction' | 'message_received' | 'message_sending' | 'message_sent' | 'before_tool_call' | 'after_tool_call' | 'session_start' | 'session_end' | 'gateway_start' | 'gateway_stop' | 'context' | 'input' | 'turn_start' | 'turn_end' | 'tool_execution_start' | 'tool_execution_update' | 'tool_execution_end' | 'before_model_resolve' | 'before_prompt_build' | 'llm_input' | 'llm_output' | 'inbound_claim' | 'before_reset' | 'before_message_write' | 'subagent_spawning' | 'subagent_delivery_target' | 'subagent_ended' /** After a webchat direct stream finishes (success or error); session lock released. */ | 'webchat_turn_complete'; /** * Hook execution mode determines how handlers are processed: * - void: Fire-and-forget, parallel execution (for notifications) * - modifying: Sequential execution, results merge (for modifications) * - claiming: First handler to return handled:true wins (for exclusive handling) */ export type HookExecutionMode = 'void' | 'modifying' | 'claiming'; /** * Maps each hook event to its execution mode */ export declare const HOOK_EXECUTION_MODES: Record; export type ExtensionHookHandler = (event: unknown, context?: unknown) => unknown | Promise; export interface HookOptions { priority?: number; once?: boolean; } export interface HookAgentContext { timestamp?: Date; extensionId?: string; sessionKey?: string; agentId?: string; isMcpTool?: boolean; mcpServerId?: string; } export interface HookBeforeModelResolveEvent { prompt: string; model?: string; provider?: string; } export interface HookBeforeModelResolveResult { modelOverride?: string; providerOverride?: string; } export interface HookBeforePromptBuildEvent { prompt: string; messages?: Array<{ role: string; content: string; }>; } export interface HookBeforePromptBuildResult { prompt?: string; prependContext?: string; } export interface HookLlmInputEvent { runId: string; provider: string; model: string; prompt: string; systemPrompt?: string; messages?: Array<{ role: string; content: string; }>; temperature?: number; maxTokens?: number; } export interface HookLlmOutputEvent { runId: string; provider: string; model: string; content: string; usage?: { input?: number; output?: number; total?: number; }; finishReason?: string; } export interface HookInboundClaimEvent { channelId: string; from: string; content: string; timestamp?: Date; } export interface HookInboundClaimResult { handled: boolean; response?: string; } export interface HookBeforeResetEvent { sessionKey: string; reason?: 'user_request' | 'timeout' | 'error' | 'manual'; } export interface HookBeforeResetResult { allowReset: boolean; reason?: string; } export interface HookBeforeMessageWriteEvent { channelId: string; to: string; content: string; } export interface HookBeforeMessageWriteResult { content?: string; cancel?: boolean; reason?: string; } export interface HookTurnStartEvent { turnId: string; prompt?: string; agentId?: string; sessionKey?: string; } export interface HookTurnEndEvent { turnId: string; response?: string; error?: string; durationMs?: number; } export interface HookToolExecutionStartEvent { toolName: string; params: Record; executionId: string; } export interface HookToolExecutionUpdateEvent { toolName: string; executionId: string; progress?: number; message?: string; } export interface HookToolExecutionEndEvent { toolName: string; executionId: string; result?: unknown; error?: string; durationMs?: number; } export interface HookSubagentSpawningEvent { childSessionKey: string; requester?: { channel?: string; accountId?: string; to?: string; threadId?: string | number; }; threadRequested?: boolean; agentId?: string; label?: string; } export type HookSubagentSpawningResult = { status: 'ok'; threadBindingReady?: boolean; } | { status: 'error'; error: string; } | void; export interface HookSubagentDeliveryTargetEvent { childSessionKey: string; requesterSessionKey?: string; requesterOrigin?: { channel?: string; accountId?: string; to?: string; threadId?: string | number; }; expectsCompletionMessage?: boolean; } export type HookSubagentDeliveryTargetResult = { origin: { channel: string; accountId?: string; to?: string; threadId?: string | number; }; } | void; export interface HookSubagentEndedEvent { targetSessionKey: string; accountId?: string; } /** * Strongly typed handler map - each hook has precise event/result types */ export type HookHandlerMap = { before_agent_start: (event: BeforeAgentStartContext, ctx: HookAgentContext) => Promise | BeforeAgentStartResult | void; before_model_resolve: (event: HookBeforeModelResolveEvent, ctx: HookAgentContext) => Promise | HookBeforeModelResolveResult | void; before_prompt_build: (event: HookBeforePromptBuildEvent, ctx: HookAgentContext) => Promise | HookBeforePromptBuildResult | void; llm_input: (event: HookLlmInputEvent, ctx: HookAgentContext) => Promise | void; llm_output: (event: HookLlmOutputEvent, ctx: HookAgentContext) => Promise | void; agent_end: (event: AgentEndContext, ctx: HookAgentContext) => Promise | void; webchat_turn_complete: (event: WebchatTurnCompleteEvent, ctx: HookAgentContext) => Promise | void; before_compaction: (event: BeforeCompactionContext, ctx: HookAgentContext) => Promise | void; after_compaction: (event: AfterCompactionContext, ctx: HookAgentContext) => Promise | void; message_received: (event: MessageReceivedContext, ctx: HookAgentContext) => Promise | void; message_sending: (event: MessageSendingContext, ctx: HookAgentContext) => Promise | MessageSendingResult | void; message_sent: (event: MessageSentContext, ctx: HookAgentContext) => Promise | void; inbound_claim: (event: HookInboundClaimEvent, ctx: HookAgentContext) => Promise | HookInboundClaimResult | void; before_message_write: (event: HookBeforeMessageWriteEvent, ctx: HookAgentContext) => Promise | HookBeforeMessageWriteResult | void; before_tool_call: (event: BeforeToolCallContext, ctx: HookAgentContext) => Promise | BeforeToolCallResult | void; after_tool_call: (event: AfterToolCallContext, ctx: HookAgentContext) => Promise | void; tool_execution_start: (event: HookToolExecutionStartEvent, ctx: HookAgentContext) => Promise | void; tool_execution_update: (event: HookToolExecutionUpdateEvent, ctx: HookAgentContext) => Promise | void; tool_execution_end: (event: HookToolExecutionEndEvent, ctx: HookAgentContext) => Promise | void; subagent_spawning: (event: HookSubagentSpawningEvent, ctx: HookAgentContext) => Promise | HookSubagentSpawningResult; subagent_delivery_target: (event: HookSubagentDeliveryTargetEvent, ctx: HookAgentContext) => Promise | HookSubagentDeliveryTargetResult; subagent_ended: (event: HookSubagentEndedEvent, ctx: HookAgentContext) => Promise | void; session_start: (event: SessionStartContext, ctx: HookAgentContext) => Promise | void; session_end: (event: SessionEndContext, ctx: HookAgentContext) => Promise | void; gateway_start: (event: GatewayStartContext, ctx: HookAgentContext) => Promise | void; gateway_stop: (event: GatewayStopContext, ctx: HookAgentContext) => Promise | void; context: (event: ContextEvent, ctx: HookAgentContext) => Promise | ContextResult | void; input: (event: InputEvent, ctx: HookAgentContext) => Promise | InputResult | void; turn_start: (event: HookTurnStartEvent, ctx: HookAgentContext) => Promise | void; turn_end: (event: HookTurnEndEvent, ctx: HookAgentContext) => Promise | void; before_reset: (event: HookBeforeResetEvent, ctx: HookAgentContext) => Promise | HookBeforeResetResult | void; }; export interface HookContext { timestamp?: Date; extensionId?: string; sessionKey?: string; agentId?: string; isMcpTool?: boolean; mcpServerId?: string; } export interface BeforeAgentStartContext extends HookContext { prompt: string; messages?: unknown[]; } export interface BeforeAgentStartResult { systemPrompt?: string; prependContext?: string; } export interface AgentEndContext extends HookContext { messages: unknown[]; success: boolean; error?: string; durationMs?: number; } /** Payload for {@link ExtensionHookEvent} `webchat_turn_complete` (also passed as `event` fields + ctx.sessionKey). */ export interface WebchatTurnCompleteEvent extends HookContext { sessionKey: string; /** Channel id (e.g. `telegram:default`, `webchat`) when the turn is not webchat-only. */ channel?: string; chatId?: string; /** Raw inbound user text from the POST body (before model envelope). */ inboundUserText: string; /** Last assistant visible text after persistence (may be empty). */ assistantPlainText: string; /** User aborted the realtime run. */ aborted: boolean; /** Set when the direct stream threw before normal completion. */ streamError?: string; } export interface BeforeCompactionContext extends HookContext { messageCount: number; tokenCount?: number; } export interface AfterCompactionContext extends HookContext { messageCount: number; tokenCount?: number; compactedCount: number; } export interface MessageReceivedContext extends HookContext { channelId: string; from: string; content: string; timestamp?: Date; metadata?: Record; } export interface MessageSendingContext extends HookContext { to: string; content: string; /** Channel id (e.g. telegram) when sending through ChannelManager. */ channel?: string; metadata?: Record; } export interface MessageSendingResult { content?: string; cancel?: boolean; cancelReason?: string; } export interface MessageSentContext extends HookContext { to: string; content: string; success: boolean; error?: string; channel?: string; } export interface BeforeToolCallContext extends HookContext { toolName: string; params: Record; } export interface BeforeToolCallResult { params?: Record; block?: boolean; blockReason?: string; } export interface AfterToolCallContext extends HookContext { toolName: string; params: Record; result?: unknown; error?: string; durationMs?: number; } export interface SessionStartContext extends HookContext { sessionId: string; resumedFrom?: string; } export interface SessionEndContext extends HookContext { sessionId: string; reason?: 'completed' | 'error' | 'timeout' | 'user_request'; } export interface GatewayStartContext extends HookContext { port: number; host: string; } export interface GatewayStopContext extends HookContext { port: number; reason?: string; } export interface ContextEvent { messages: Array<{ role: string; content: string; }>; agentId?: string; sessionKey?: string; } export interface ContextResult { messages: Array<{ role: string; content: string; }>; } export interface InputEvent { text: string; images?: string[]; channelId?: string; from?: string; } export interface InputResult { action: 'continue' | 'handled' | 'blocked'; text?: string; images?: string[]; response?: string; skipAgent?: boolean; } export interface TurnEvent { turnId: string; prompt?: string; agentId?: string; sessionKey?: string; } export interface TurnResult { context?: string; skipTurn?: boolean; }