@mariozechner/pi-coding-agent 0.31.1 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +56 -5
  3. package/dist/cli/file-processor.d.ts +5 -1
  4. package/dist/cli/file-processor.d.ts.map +1 -1
  5. package/dist/cli/file-processor.js +28 -8
  6. package/dist/cli/file-processor.js.map +1 -1
  7. package/dist/core/agent-session.d.ts +41 -16
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +90 -41
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/auth-storage.d.ts +6 -1
  12. package/dist/core/auth-storage.d.ts.map +1 -1
  13. package/dist/core/auth-storage.js +16 -1
  14. package/dist/core/auth-storage.js.map +1 -1
  15. package/dist/core/custom-tools/types.d.ts +1 -1
  16. package/dist/core/custom-tools/types.d.ts.map +1 -1
  17. package/dist/core/custom-tools/types.js.map +1 -1
  18. package/dist/core/hooks/loader.d.ts +4 -1
  19. package/dist/core/hooks/loader.d.ts.map +1 -1
  20. package/dist/core/hooks/loader.js +2 -2
  21. package/dist/core/hooks/loader.js.map +1 -1
  22. package/dist/core/hooks/runner.d.ts +2 -2
  23. package/dist/core/hooks/runner.d.ts.map +1 -1
  24. package/dist/core/hooks/runner.js +3 -3
  25. package/dist/core/hooks/runner.js.map +1 -1
  26. package/dist/core/hooks/types.d.ts +10 -4
  27. package/dist/core/hooks/types.d.ts.map +1 -1
  28. package/dist/core/hooks/types.js.map +1 -1
  29. package/dist/core/model-registry.d.ts +5 -2
  30. package/dist/core/model-registry.d.ts.map +1 -1
  31. package/dist/core/model-registry.js +85 -49
  32. package/dist/core/model-registry.js.map +1 -1
  33. package/dist/core/model-resolver.d.ts.map +1 -1
  34. package/dist/core/model-resolver.js +1 -0
  35. package/dist/core/model-resolver.js.map +1 -1
  36. package/dist/core/sdk.d.ts.map +1 -1
  37. package/dist/core/sdk.js +9 -6
  38. package/dist/core/sdk.js.map +1 -1
  39. package/dist/core/settings-manager.d.ts +17 -3
  40. package/dist/core/settings-manager.d.ts.map +1 -1
  41. package/dist/core/settings-manager.js +41 -6
  42. package/dist/core/settings-manager.js.map +1 -1
  43. package/dist/core/tools/index.d.ts +9 -4
  44. package/dist/core/tools/index.d.ts.map +1 -1
  45. package/dist/core/tools/index.js +6 -6
  46. package/dist/core/tools/index.js.map +1 -1
  47. package/dist/core/tools/read.d.ts +5 -1
  48. package/dist/core/tools/read.d.ts.map +1 -1
  49. package/dist/core/tools/read.js +22 -5
  50. package/dist/core/tools/read.js.map +1 -1
  51. package/dist/index.d.ts +3 -3
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +2 -2
  54. package/dist/index.js.map +1 -1
  55. package/dist/main.d.ts.map +1 -1
  56. package/dist/main.js +5 -5
  57. package/dist/main.js.map +1 -1
  58. package/dist/modes/interactive/components/custom-editor.d.ts +1 -0
  59. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  60. package/dist/modes/interactive/components/custom-editor.js +7 -1
  61. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  62. package/dist/modes/interactive/components/hook-editor.d.ts.map +1 -1
  63. package/dist/modes/interactive/components/hook-editor.js +3 -3
  64. package/dist/modes/interactive/components/hook-editor.js.map +1 -1
  65. package/dist/modes/interactive/components/hook-input.d.ts.map +1 -1
  66. package/dist/modes/interactive/components/hook-input.js +3 -3
  67. package/dist/modes/interactive/components/hook-input.js.map +1 -1
  68. package/dist/modes/interactive/components/hook-selector.d.ts.map +1 -1
  69. package/dist/modes/interactive/components/hook-selector.js +3 -3
  70. package/dist/modes/interactive/components/hook-selector.js.map +1 -1
  71. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  72. package/dist/modes/interactive/components/model-selector.js +3 -3
  73. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  74. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  75. package/dist/modes/interactive/components/oauth-selector.js +3 -3
  76. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  77. package/dist/modes/interactive/components/settings-selector.d.ts +8 -2
  78. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  79. package/dist/modes/interactive/components/settings-selector.js +37 -6
  80. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  81. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  82. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  83. package/dist/modes/interactive/interactive-mode.js +66 -19
  84. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  85. package/dist/modes/print-mode.d.ts.map +1 -1
  86. package/dist/modes/print-mode.js +3 -3
  87. package/dist/modes/print-mode.js.map +1 -1
  88. package/dist/modes/rpc/rpc-client.d.ts +12 -4
  89. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  90. package/dist/modes/rpc/rpc-client.js +18 -6
  91. package/dist/modes/rpc/rpc-client.js.map +1 -1
  92. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  93. package/dist/modes/rpc/rpc-mode.js +21 -12
  94. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  95. package/dist/modes/rpc/rpc-types.d.ts +25 -6
  96. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  97. package/dist/modes/rpc/rpc-types.js.map +1 -1
  98. package/dist/utils/image-resize.d.ts +29 -0
  99. package/dist/utils/image-resize.d.ts.map +1 -0
  100. package/dist/utils/image-resize.js +111 -0
  101. package/dist/utils/image-resize.js.map +1 -0
  102. package/docs/hooks.md +16 -9
  103. package/examples/README.md +6 -0
  104. package/examples/custom-tools/README.md +2 -0
  105. package/examples/hooks/README.md +1 -0
  106. package/examples/hooks/file-trigger.ts +1 -1
  107. package/examples/hooks/todo/index.ts +134 -0
  108. package/package.json +6 -5
  109. package/dist/modes/interactive/components/queue-mode-selector.d.ts +0 -10
  110. package/dist/modes/interactive/components/queue-mode-selector.d.ts.map +0 -1
  111. package/dist/modes/interactive/components/queue-mode-selector.js +0 -42
  112. package/dist/modes/interactive/components/queue-mode-selector.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"runner.js","sourceRoot":"","sources":["../../../src/core/hooks/runner.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook runner - executes hooks and manages their lifecycle.\n */\n\nimport type { AgentMessage } from \"@mariozechner/pi-agent-core\";\nimport type { Model } from \"@mariozechner/pi-ai\";\nimport { theme } from \"../../modes/interactive/theme/theme.js\";\nimport type { ModelRegistry } from \"../model-registry.js\";\nimport type { SessionManager } from \"../session-manager.js\";\nimport type {\n\tAppendEntryHandler,\n\tBranchHandler,\n\tLoadedHook,\n\tNavigateTreeHandler,\n\tNewSessionHandler,\n\tSendMessageHandler,\n} from \"./loader.js\";\nimport type {\n\tBeforeAgentStartEvent,\n\tBeforeAgentStartEventResult,\n\tContextEvent,\n\tContextEventResult,\n\tHookCommandContext,\n\tHookContext,\n\tHookError,\n\tHookEvent,\n\tHookMessageRenderer,\n\tHookUIContext,\n\tRegisteredCommand,\n\tSessionBeforeCompactResult,\n\tSessionBeforeTreeResult,\n\tToolCallEvent,\n\tToolCallEventResult,\n\tToolResultEventResult,\n} from \"./types.js\";\n\n/**\n * Listener for hook errors.\n */\nexport type HookErrorListener = (error: HookError) => void;\n\n// Re-export execCommand for backward compatibility\nexport { execCommand } from \"../exec.js\";\n\n/** No-op UI context used when no UI is available */\nconst noOpUIContext: HookUIContext = {\n\tselect: async () => undefined,\n\tconfirm: async () => false,\n\tinput: async () => undefined,\n\tnotify: () => {},\n\tsetStatus: () => {},\n\tcustom: async () => undefined as never,\n\tsetEditorText: () => {},\n\tgetEditorText: () => \"\",\n\teditor: async () => undefined,\n\tget theme() {\n\t\treturn theme;\n\t},\n};\n\n/**\n * HookRunner executes hooks and manages event emission.\n */\nexport class HookRunner {\n\tprivate hooks: LoadedHook[];\n\tprivate uiContext: HookUIContext;\n\tprivate hasUI: boolean;\n\tprivate cwd: string;\n\tprivate sessionManager: SessionManager;\n\tprivate modelRegistry: ModelRegistry;\n\tprivate errorListeners: Set<HookErrorListener> = new Set();\n\tprivate getModel: () => Model<any> | undefined = () => undefined;\n\tprivate isIdleFn: () => boolean = () => true;\n\tprivate waitForIdleFn: () => Promise<void> = async () => {};\n\tprivate abortFn: () => void = () => {};\n\tprivate hasQueuedMessagesFn: () => boolean = () => false;\n\tprivate newSessionHandler: NewSessionHandler = async () => ({ cancelled: false });\n\tprivate branchHandler: BranchHandler = async () => ({ cancelled: false });\n\tprivate navigateTreeHandler: NavigateTreeHandler = async () => ({ cancelled: false });\n\n\tconstructor(hooks: LoadedHook[], cwd: string, sessionManager: SessionManager, modelRegistry: ModelRegistry) {\n\t\tthis.hooks = hooks;\n\t\tthis.uiContext = noOpUIContext;\n\t\tthis.hasUI = false;\n\t\tthis.cwd = cwd;\n\t\tthis.sessionManager = sessionManager;\n\t\tthis.modelRegistry = modelRegistry;\n\t}\n\n\t/**\n\t * Initialize HookRunner with all required context.\n\t * Modes call this once the agent session is fully set up.\n\t */\n\tinitialize(options: {\n\t\t/** Function to get the current model */\n\t\tgetModel: () => Model<any> | undefined;\n\t\t/** Handler for hooks to send messages */\n\t\tsendMessageHandler: SendMessageHandler;\n\t\t/** Handler for hooks to append entries */\n\t\tappendEntryHandler: AppendEntryHandler;\n\t\t/** Handler for creating new sessions (for HookCommandContext) */\n\t\tnewSessionHandler?: NewSessionHandler;\n\t\t/** Handler for branching sessions (for HookCommandContext) */\n\t\tbranchHandler?: BranchHandler;\n\t\t/** Handler for navigating session tree (for HookCommandContext) */\n\t\tnavigateTreeHandler?: NavigateTreeHandler;\n\t\t/** Function to check if agent is idle */\n\t\tisIdle?: () => boolean;\n\t\t/** Function to wait for agent to be idle */\n\t\twaitForIdle?: () => Promise<void>;\n\t\t/** Function to abort current operation (fire-and-forget) */\n\t\tabort?: () => void;\n\t\t/** Function to check if there are queued messages */\n\t\thasQueuedMessages?: () => boolean;\n\t\t/** UI context for interactive prompts */\n\t\tuiContext?: HookUIContext;\n\t\t/** Whether UI is available */\n\t\thasUI?: boolean;\n\t}): void {\n\t\tthis.getModel = options.getModel;\n\t\tthis.isIdleFn = options.isIdle ?? (() => true);\n\t\tthis.waitForIdleFn = options.waitForIdle ?? (async () => {});\n\t\tthis.abortFn = options.abort ?? (() => {});\n\t\tthis.hasQueuedMessagesFn = options.hasQueuedMessages ?? (() => false);\n\t\t// Store session handlers for HookCommandContext\n\t\tif (options.newSessionHandler) {\n\t\t\tthis.newSessionHandler = options.newSessionHandler;\n\t\t}\n\t\tif (options.branchHandler) {\n\t\t\tthis.branchHandler = options.branchHandler;\n\t\t}\n\t\tif (options.navigateTreeHandler) {\n\t\t\tthis.navigateTreeHandler = options.navigateTreeHandler;\n\t\t}\n\t\t// Set per-hook handlers for pi.sendMessage() and pi.appendEntry()\n\t\tfor (const hook of this.hooks) {\n\t\t\thook.setSendMessageHandler(options.sendMessageHandler);\n\t\t\thook.setAppendEntryHandler(options.appendEntryHandler);\n\t\t}\n\t\tthis.uiContext = options.uiContext ?? noOpUIContext;\n\t\tthis.hasUI = options.hasUI ?? false;\n\t}\n\n\t/**\n\t * Get the UI context (set by mode).\n\t */\n\tgetUIContext(): HookUIContext | null {\n\t\treturn this.uiContext;\n\t}\n\n\t/**\n\t * Get whether UI is available.\n\t */\n\tgetHasUI(): boolean {\n\t\treturn this.hasUI;\n\t}\n\n\t/**\n\t * Get the paths of all loaded hooks.\n\t */\n\tgetHookPaths(): string[] {\n\t\treturn this.hooks.map((h) => h.path);\n\t}\n\n\t/**\n\t * Subscribe to hook errors.\n\t * @returns Unsubscribe function\n\t */\n\tonError(listener: HookErrorListener): () => void {\n\t\tthis.errorListeners.add(listener);\n\t\treturn () => this.errorListeners.delete(listener);\n\t}\n\n\t/**\n\t * Emit an error to all listeners.\n\t */\n\t/**\n\t * Emit an error to all error listeners.\n\t */\n\temitError(error: HookError): void {\n\t\tfor (const listener of this.errorListeners) {\n\t\t\tlistener(error);\n\t\t}\n\t}\n\n\t/**\n\t * Check if any hooks have handlers for the given event type.\n\t */\n\thasHandlers(eventType: string): boolean {\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(eventType);\n\t\t\tif (handlers && handlers.length > 0) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Get a message renderer for the given customType.\n\t * Returns the first renderer found across all hooks, or undefined if none.\n\t */\n\tgetMessageRenderer(customType: string): HookMessageRenderer | undefined {\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst renderer = hook.messageRenderers.get(customType);\n\t\t\tif (renderer) {\n\t\t\t\treturn renderer;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Get all registered commands from all hooks.\n\t */\n\tgetRegisteredCommands(): RegisteredCommand[] {\n\t\tconst commands: RegisteredCommand[] = [];\n\t\tfor (const hook of this.hooks) {\n\t\t\tfor (const command of hook.commands.values()) {\n\t\t\t\tcommands.push(command);\n\t\t\t}\n\t\t}\n\t\treturn commands;\n\t}\n\n\t/**\n\t * Get a registered command by name.\n\t * Returns the first command found across all hooks, or undefined if none.\n\t */\n\tgetCommand(name: string): RegisteredCommand | undefined {\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst command = hook.commands.get(name);\n\t\t\tif (command) {\n\t\t\t\treturn command;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Create the event context for handlers.\n\t */\n\tprivate createContext(): HookContext {\n\t\treturn {\n\t\t\tui: this.uiContext,\n\t\t\thasUI: this.hasUI,\n\t\t\tcwd: this.cwd,\n\t\t\tsessionManager: this.sessionManager,\n\t\t\tmodelRegistry: this.modelRegistry,\n\t\t\tmodel: this.getModel(),\n\t\t\tisIdle: () => this.isIdleFn(),\n\t\t\tabort: () => this.abortFn(),\n\t\t\thasQueuedMessages: () => this.hasQueuedMessagesFn(),\n\t\t};\n\t}\n\n\t/**\n\t * Create the command context for slash command handlers.\n\t * Extends HookContext with session control methods that are only safe in commands.\n\t */\n\tcreateCommandContext(): HookCommandContext {\n\t\treturn {\n\t\t\t...this.createContext(),\n\t\t\twaitForIdle: () => this.waitForIdleFn(),\n\t\t\tnewSession: (options) => this.newSessionHandler(options),\n\t\t\tbranch: (entryId) => this.branchHandler(entryId),\n\t\t\tnavigateTree: (targetId, options) => this.navigateTreeHandler(targetId, options),\n\t\t};\n\t}\n\n\t/**\n\t * Check if event type is a session \"before_*\" event that can be cancelled.\n\t */\n\tprivate isSessionBeforeEvent(\n\t\ttype: string,\n\t): type is \"session_before_switch\" | \"session_before_branch\" | \"session_before_compact\" | \"session_before_tree\" {\n\t\treturn (\n\t\t\ttype === \"session_before_switch\" ||\n\t\t\ttype === \"session_before_branch\" ||\n\t\t\ttype === \"session_before_compact\" ||\n\t\t\ttype === \"session_before_tree\"\n\t\t);\n\t}\n\n\t/**\n\t * Emit an event to all hooks.\n\t * Returns the result from session before_* / tool_result events (if any handler returns one).\n\t */\n\tasync emit(\n\t\tevent: HookEvent,\n\t): Promise<SessionBeforeCompactResult | SessionBeforeTreeResult | ToolResultEventResult | undefined> {\n\t\tconst ctx = this.createContext();\n\t\tlet result: SessionBeforeCompactResult | SessionBeforeTreeResult | ToolResultEventResult | undefined;\n\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(event.type);\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\t\t// For session before_* events, capture the result (for cancellation)\n\t\t\t\t\tif (this.isSessionBeforeEvent(event.type) && handlerResult) {\n\t\t\t\t\t\tresult = handlerResult as SessionBeforeCompactResult | SessionBeforeTreeResult;\n\t\t\t\t\t\t// If cancelled, stop processing further hooks\n\t\t\t\t\t\tif (result.cancel) {\n\t\t\t\t\t\t\treturn result;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// For tool_result events, capture the result\n\t\t\t\t\tif (event.type === \"tool_result\" && handlerResult) {\n\t\t\t\t\t\tresult = handlerResult as ToolResultEventResult;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\thookPath: hook.path,\n\t\t\t\t\t\tevent: event.type,\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Emit a tool_call event to all hooks.\n\t * No timeout - user prompts can take as long as needed.\n\t * Errors are thrown (not swallowed) so caller can block on failure.\n\t */\n\tasync emitToolCall(event: ToolCallEvent): Promise<ToolCallEventResult | undefined> {\n\t\tconst ctx = this.createContext();\n\t\tlet result: ToolCallEventResult | undefined;\n\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(\"tool_call\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\t// No timeout - let user take their time\n\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\tif (handlerResult) {\n\t\t\t\t\tresult = handlerResult as ToolCallEventResult;\n\t\t\t\t\t// If blocked, stop processing further hooks\n\t\t\t\t\tif (result.block) {\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Emit a context event to all hooks.\n\t * Handlers are chained - each gets the previous handler's output (if any).\n\t * Returns the final modified messages, or the original if no modifications.\n\t *\n\t * Note: Messages are already deep-copied by the caller (pi-ai preprocessor).\n\t */\n\tasync emitContext(messages: AgentMessage[]): Promise<AgentMessage[]> {\n\t\tconst ctx = this.createContext();\n\t\tlet currentMessages = messages;\n\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(\"context\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst event: ContextEvent = { type: \"context\", messages: currentMessages };\n\t\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\t\tif (handlerResult && (handlerResult as ContextEventResult).messages) {\n\t\t\t\t\t\tcurrentMessages = (handlerResult as ContextEventResult).messages!;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\thookPath: hook.path,\n\t\t\t\t\t\tevent: \"context\",\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn currentMessages;\n\t}\n\n\t/**\n\t * Emit before_agent_start event to all hooks.\n\t * Returns the first message to inject (if any handler returns one).\n\t */\n\tasync emitBeforeAgentStart(\n\t\tprompt: string,\n\t\timages?: import(\"@mariozechner/pi-ai\").ImageContent[],\n\t): Promise<BeforeAgentStartEventResult | undefined> {\n\t\tconst ctx = this.createContext();\n\t\tlet result: BeforeAgentStartEventResult | undefined;\n\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(\"before_agent_start\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst event: BeforeAgentStartEvent = { type: \"before_agent_start\", prompt, images };\n\t\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\t\t// Take the first message returned\n\t\t\t\t\tif (handlerResult && (handlerResult as BeforeAgentStartEventResult).message && !result) {\n\t\t\t\t\t\tresult = handlerResult as BeforeAgentStartEventResult;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\thookPath: hook.path,\n\t\t\t\t\t\tevent: \"before_agent_start\",\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n}\n"]}
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../../../src/core/hooks/runner.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook runner - executes hooks and manages their lifecycle.\n */\n\nimport type { AgentMessage } from \"@mariozechner/pi-agent-core\";\nimport type { Model } from \"@mariozechner/pi-ai\";\nimport { theme } from \"../../modes/interactive/theme/theme.js\";\nimport type { ModelRegistry } from \"../model-registry.js\";\nimport type { SessionManager } from \"../session-manager.js\";\nimport type {\n\tAppendEntryHandler,\n\tBranchHandler,\n\tLoadedHook,\n\tNavigateTreeHandler,\n\tNewSessionHandler,\n\tSendMessageHandler,\n} from \"./loader.js\";\nimport type {\n\tBeforeAgentStartEvent,\n\tBeforeAgentStartEventResult,\n\tContextEvent,\n\tContextEventResult,\n\tHookCommandContext,\n\tHookContext,\n\tHookError,\n\tHookEvent,\n\tHookMessageRenderer,\n\tHookUIContext,\n\tRegisteredCommand,\n\tSessionBeforeCompactResult,\n\tSessionBeforeTreeResult,\n\tToolCallEvent,\n\tToolCallEventResult,\n\tToolResultEventResult,\n} from \"./types.js\";\n\n/**\n * Listener for hook errors.\n */\nexport type HookErrorListener = (error: HookError) => void;\n\n// Re-export execCommand for backward compatibility\nexport { execCommand } from \"../exec.js\";\n\n/** No-op UI context used when no UI is available */\nconst noOpUIContext: HookUIContext = {\n\tselect: async () => undefined,\n\tconfirm: async () => false,\n\tinput: async () => undefined,\n\tnotify: () => {},\n\tsetStatus: () => {},\n\tcustom: async () => undefined as never,\n\tsetEditorText: () => {},\n\tgetEditorText: () => \"\",\n\teditor: async () => undefined,\n\tget theme() {\n\t\treturn theme;\n\t},\n};\n\n/**\n * HookRunner executes hooks and manages event emission.\n */\nexport class HookRunner {\n\tprivate hooks: LoadedHook[];\n\tprivate uiContext: HookUIContext;\n\tprivate hasUI: boolean;\n\tprivate cwd: string;\n\tprivate sessionManager: SessionManager;\n\tprivate modelRegistry: ModelRegistry;\n\tprivate errorListeners: Set<HookErrorListener> = new Set();\n\tprivate getModel: () => Model<any> | undefined = () => undefined;\n\tprivate isIdleFn: () => boolean = () => true;\n\tprivate waitForIdleFn: () => Promise<void> = async () => {};\n\tprivate abortFn: () => void = () => {};\n\tprivate hasPendingMessagesFn: () => boolean = () => false;\n\tprivate newSessionHandler: NewSessionHandler = async () => ({ cancelled: false });\n\tprivate branchHandler: BranchHandler = async () => ({ cancelled: false });\n\tprivate navigateTreeHandler: NavigateTreeHandler = async () => ({ cancelled: false });\n\n\tconstructor(hooks: LoadedHook[], cwd: string, sessionManager: SessionManager, modelRegistry: ModelRegistry) {\n\t\tthis.hooks = hooks;\n\t\tthis.uiContext = noOpUIContext;\n\t\tthis.hasUI = false;\n\t\tthis.cwd = cwd;\n\t\tthis.sessionManager = sessionManager;\n\t\tthis.modelRegistry = modelRegistry;\n\t}\n\n\t/**\n\t * Initialize HookRunner with all required context.\n\t * Modes call this once the agent session is fully set up.\n\t */\n\tinitialize(options: {\n\t\t/** Function to get the current model */\n\t\tgetModel: () => Model<any> | undefined;\n\t\t/** Handler for hooks to send messages */\n\t\tsendMessageHandler: SendMessageHandler;\n\t\t/** Handler for hooks to append entries */\n\t\tappendEntryHandler: AppendEntryHandler;\n\t\t/** Handler for creating new sessions (for HookCommandContext) */\n\t\tnewSessionHandler?: NewSessionHandler;\n\t\t/** Handler for branching sessions (for HookCommandContext) */\n\t\tbranchHandler?: BranchHandler;\n\t\t/** Handler for navigating session tree (for HookCommandContext) */\n\t\tnavigateTreeHandler?: NavigateTreeHandler;\n\t\t/** Function to check if agent is idle */\n\t\tisIdle?: () => boolean;\n\t\t/** Function to wait for agent to be idle */\n\t\twaitForIdle?: () => Promise<void>;\n\t\t/** Function to abort current operation (fire-and-forget) */\n\t\tabort?: () => void;\n\t\t/** Function to check if there are queued messages */\n\t\thasPendingMessages?: () => boolean;\n\t\t/** UI context for interactive prompts */\n\t\tuiContext?: HookUIContext;\n\t\t/** Whether UI is available */\n\t\thasUI?: boolean;\n\t}): void {\n\t\tthis.getModel = options.getModel;\n\t\tthis.isIdleFn = options.isIdle ?? (() => true);\n\t\tthis.waitForIdleFn = options.waitForIdle ?? (async () => {});\n\t\tthis.abortFn = options.abort ?? (() => {});\n\t\tthis.hasPendingMessagesFn = options.hasPendingMessages ?? (() => false);\n\t\t// Store session handlers for HookCommandContext\n\t\tif (options.newSessionHandler) {\n\t\t\tthis.newSessionHandler = options.newSessionHandler;\n\t\t}\n\t\tif (options.branchHandler) {\n\t\t\tthis.branchHandler = options.branchHandler;\n\t\t}\n\t\tif (options.navigateTreeHandler) {\n\t\t\tthis.navigateTreeHandler = options.navigateTreeHandler;\n\t\t}\n\t\t// Set per-hook handlers for pi.sendMessage() and pi.appendEntry()\n\t\tfor (const hook of this.hooks) {\n\t\t\thook.setSendMessageHandler(options.sendMessageHandler);\n\t\t\thook.setAppendEntryHandler(options.appendEntryHandler);\n\t\t}\n\t\tthis.uiContext = options.uiContext ?? noOpUIContext;\n\t\tthis.hasUI = options.hasUI ?? false;\n\t}\n\n\t/**\n\t * Get the UI context (set by mode).\n\t */\n\tgetUIContext(): HookUIContext | null {\n\t\treturn this.uiContext;\n\t}\n\n\t/**\n\t * Get whether UI is available.\n\t */\n\tgetHasUI(): boolean {\n\t\treturn this.hasUI;\n\t}\n\n\t/**\n\t * Get the paths of all loaded hooks.\n\t */\n\tgetHookPaths(): string[] {\n\t\treturn this.hooks.map((h) => h.path);\n\t}\n\n\t/**\n\t * Subscribe to hook errors.\n\t * @returns Unsubscribe function\n\t */\n\tonError(listener: HookErrorListener): () => void {\n\t\tthis.errorListeners.add(listener);\n\t\treturn () => this.errorListeners.delete(listener);\n\t}\n\n\t/**\n\t * Emit an error to all listeners.\n\t */\n\t/**\n\t * Emit an error to all error listeners.\n\t */\n\temitError(error: HookError): void {\n\t\tfor (const listener of this.errorListeners) {\n\t\t\tlistener(error);\n\t\t}\n\t}\n\n\t/**\n\t * Check if any hooks have handlers for the given event type.\n\t */\n\thasHandlers(eventType: string): boolean {\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(eventType);\n\t\t\tif (handlers && handlers.length > 0) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Get a message renderer for the given customType.\n\t * Returns the first renderer found across all hooks, or undefined if none.\n\t */\n\tgetMessageRenderer(customType: string): HookMessageRenderer | undefined {\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst renderer = hook.messageRenderers.get(customType);\n\t\t\tif (renderer) {\n\t\t\t\treturn renderer;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Get all registered commands from all hooks.\n\t */\n\tgetRegisteredCommands(): RegisteredCommand[] {\n\t\tconst commands: RegisteredCommand[] = [];\n\t\tfor (const hook of this.hooks) {\n\t\t\tfor (const command of hook.commands.values()) {\n\t\t\t\tcommands.push(command);\n\t\t\t}\n\t\t}\n\t\treturn commands;\n\t}\n\n\t/**\n\t * Get a registered command by name.\n\t * Returns the first command found across all hooks, or undefined if none.\n\t */\n\tgetCommand(name: string): RegisteredCommand | undefined {\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst command = hook.commands.get(name);\n\t\t\tif (command) {\n\t\t\t\treturn command;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Create the event context for handlers.\n\t */\n\tprivate createContext(): HookContext {\n\t\treturn {\n\t\t\tui: this.uiContext,\n\t\t\thasUI: this.hasUI,\n\t\t\tcwd: this.cwd,\n\t\t\tsessionManager: this.sessionManager,\n\t\t\tmodelRegistry: this.modelRegistry,\n\t\t\tmodel: this.getModel(),\n\t\t\tisIdle: () => this.isIdleFn(),\n\t\t\tabort: () => this.abortFn(),\n\t\t\thasPendingMessages: () => this.hasPendingMessagesFn(),\n\t\t};\n\t}\n\n\t/**\n\t * Create the command context for slash command handlers.\n\t * Extends HookContext with session control methods that are only safe in commands.\n\t */\n\tcreateCommandContext(): HookCommandContext {\n\t\treturn {\n\t\t\t...this.createContext(),\n\t\t\twaitForIdle: () => this.waitForIdleFn(),\n\t\t\tnewSession: (options) => this.newSessionHandler(options),\n\t\t\tbranch: (entryId) => this.branchHandler(entryId),\n\t\t\tnavigateTree: (targetId, options) => this.navigateTreeHandler(targetId, options),\n\t\t};\n\t}\n\n\t/**\n\t * Check if event type is a session \"before_*\" event that can be cancelled.\n\t */\n\tprivate isSessionBeforeEvent(\n\t\ttype: string,\n\t): type is \"session_before_switch\" | \"session_before_branch\" | \"session_before_compact\" | \"session_before_tree\" {\n\t\treturn (\n\t\t\ttype === \"session_before_switch\" ||\n\t\t\ttype === \"session_before_branch\" ||\n\t\t\ttype === \"session_before_compact\" ||\n\t\t\ttype === \"session_before_tree\"\n\t\t);\n\t}\n\n\t/**\n\t * Emit an event to all hooks.\n\t * Returns the result from session before_* / tool_result events (if any handler returns one).\n\t */\n\tasync emit(\n\t\tevent: HookEvent,\n\t): Promise<SessionBeforeCompactResult | SessionBeforeTreeResult | ToolResultEventResult | undefined> {\n\t\tconst ctx = this.createContext();\n\t\tlet result: SessionBeforeCompactResult | SessionBeforeTreeResult | ToolResultEventResult | undefined;\n\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(event.type);\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\t\t// For session before_* events, capture the result (for cancellation)\n\t\t\t\t\tif (this.isSessionBeforeEvent(event.type) && handlerResult) {\n\t\t\t\t\t\tresult = handlerResult as SessionBeforeCompactResult | SessionBeforeTreeResult;\n\t\t\t\t\t\t// If cancelled, stop processing further hooks\n\t\t\t\t\t\tif (result.cancel) {\n\t\t\t\t\t\t\treturn result;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// For tool_result events, capture the result\n\t\t\t\t\tif (event.type === \"tool_result\" && handlerResult) {\n\t\t\t\t\t\tresult = handlerResult as ToolResultEventResult;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\thookPath: hook.path,\n\t\t\t\t\t\tevent: event.type,\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Emit a tool_call event to all hooks.\n\t * No timeout - user prompts can take as long as needed.\n\t * Errors are thrown (not swallowed) so caller can block on failure.\n\t */\n\tasync emitToolCall(event: ToolCallEvent): Promise<ToolCallEventResult | undefined> {\n\t\tconst ctx = this.createContext();\n\t\tlet result: ToolCallEventResult | undefined;\n\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(\"tool_call\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\t// No timeout - let user take their time\n\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\tif (handlerResult) {\n\t\t\t\t\tresult = handlerResult as ToolCallEventResult;\n\t\t\t\t\t// If blocked, stop processing further hooks\n\t\t\t\t\tif (result.block) {\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Emit a context event to all hooks.\n\t * Handlers are chained - each gets the previous handler's output (if any).\n\t * Returns the final modified messages, or the original if no modifications.\n\t *\n\t * Note: Messages are already deep-copied by the caller (pi-ai preprocessor).\n\t */\n\tasync emitContext(messages: AgentMessage[]): Promise<AgentMessage[]> {\n\t\tconst ctx = this.createContext();\n\t\tlet currentMessages = messages;\n\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(\"context\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst event: ContextEvent = { type: \"context\", messages: currentMessages };\n\t\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\t\tif (handlerResult && (handlerResult as ContextEventResult).messages) {\n\t\t\t\t\t\tcurrentMessages = (handlerResult as ContextEventResult).messages!;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\thookPath: hook.path,\n\t\t\t\t\t\tevent: \"context\",\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn currentMessages;\n\t}\n\n\t/**\n\t * Emit before_agent_start event to all hooks.\n\t * Returns the first message to inject (if any handler returns one).\n\t */\n\tasync emitBeforeAgentStart(\n\t\tprompt: string,\n\t\timages?: import(\"@mariozechner/pi-ai\").ImageContent[],\n\t): Promise<BeforeAgentStartEventResult | undefined> {\n\t\tconst ctx = this.createContext();\n\t\tlet result: BeforeAgentStartEventResult | undefined;\n\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(\"before_agent_start\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst event: BeforeAgentStartEvent = { type: \"before_agent_start\", prompt, images };\n\t\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\t\t// Take the first message returned\n\t\t\t\t\tif (handlerResult && (handlerResult as BeforeAgentStartEventResult).message && !result) {\n\t\t\t\t\t\tresult = handlerResult as BeforeAgentStartEventResult;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\thookPath: hook.path,\n\t\t\t\t\t\tevent: \"before_agent_start\",\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n}\n"]}
@@ -132,7 +132,7 @@ export interface HookContext {
132
132
  /** Abort the current agent operation (fire-and-forget, does not wait) */
133
133
  abort(): void;
134
134
  /** Whether there are queued messages waiting to be processed */
135
- hasQueuedMessages(): boolean;
135
+ hasPendingMessages(): boolean;
136
136
  }
137
137
  /**
138
138
  * Extended context for slash command handlers.
@@ -556,10 +556,16 @@ export interface HookAPI {
556
556
  * @param message.content - Message content (string or TextContent/ImageContent array)
557
557
  * @param message.display - Whether to show in TUI (true = styled display, false = hidden)
558
558
  * @param message.details - Optional hook-specific metadata (not sent to LLM)
559
- * @param triggerTurn - If true and agent is idle, triggers a new LLM turn. Default: false.
560
- * If agent is streaming, message is queued and triggerTurn is ignored.
559
+ * @param options.triggerTurn - If true and agent is idle, triggers a new LLM turn. Default: false.
560
+ * If agent is streaming, message is queued and triggerTurn is ignored.
561
+ * @param options.deliverAs - How to deliver when agent is streaming. Default: "steer".
562
+ * - "steer": Interrupt mid-run, delivered after current tool execution.
563
+ * - "followUp": Wait until agent finishes all work before delivery.
561
564
  */
562
- sendMessage<T = unknown>(message: Pick<HookMessage<T>, "customType" | "content" | "display" | "details">, triggerTurn?: boolean): void;
565
+ sendMessage<T = unknown>(message: Pick<HookMessage<T>, "customType" | "content" | "display" | "details">, options?: {
566
+ triggerTurn?: boolean;
567
+ deliverAs?: "steer" | "followUp";
568
+ }): void;
563
569
  /**
564
570
  * Append a custom entry to the session for hook state persistence.
565
571
  * Creates a CustomEntry that does NOT participate in LLM context.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/types.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook system types.\n *\n * Hooks are TypeScript modules that can subscribe to agent lifecycle events\n * and interact with the user via UI primitives.\n */\n\nimport type { AgentMessage } from \"@mariozechner/pi-agent-core\";\nimport type { ImageContent, Message, Model, TextContent, ToolResultMessage } from \"@mariozechner/pi-ai\";\nimport type { Component, TUI } from \"@mariozechner/pi-tui\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.js\";\nimport type { CompactionPreparation, CompactionResult } from \"../compaction/index.js\";\nimport type { ExecOptions, ExecResult } from \"../exec.js\";\nimport type { HookMessage } from \"../messages.js\";\nimport type { ModelRegistry } from \"../model-registry.js\";\nimport type {\n\tBranchSummaryEntry,\n\tCompactionEntry,\n\tReadonlySessionManager,\n\tSessionEntry,\n\tSessionManager,\n} from \"../session-manager.js\";\n\nimport type { EditToolDetails } from \"../tools/edit.js\";\nimport type {\n\tBashToolDetails,\n\tFindToolDetails,\n\tGrepToolDetails,\n\tLsToolDetails,\n\tReadToolDetails,\n} from \"../tools/index.js\";\n\n// Re-export for backward compatibility\nexport type { ExecOptions, ExecResult } from \"../exec.js\";\n\n/**\n * UI context for hooks to request interactive UI from the harness.\n * Each mode (interactive, RPC, print) provides its own implementation.\n */\nexport interface HookUIContext {\n\t/**\n\t * Show a selector and return the user's choice.\n\t * @param title - Title to display\n\t * @param options - Array of string options\n\t * @returns Selected option string, or null if cancelled\n\t */\n\tselect(title: string, options: string[]): Promise<string | undefined>;\n\n\t/**\n\t * Show a confirmation dialog.\n\t * @returns true if confirmed, false if cancelled\n\t */\n\tconfirm(title: string, message: string): Promise<boolean>;\n\n\t/**\n\t * Show a text input dialog.\n\t * @returns User input, or undefined if cancelled\n\t */\n\tinput(title: string, placeholder?: string): Promise<string | undefined>;\n\n\t/**\n\t * Show a notification to the user.\n\t */\n\tnotify(message: string, type?: \"info\" | \"warning\" | \"error\"): void;\n\n\t/**\n\t * Set status text in the footer/status bar.\n\t * Pass undefined as text to clear the status for this key.\n\t * Text can include ANSI escape codes for styling.\n\t * Note: Newlines, tabs, and carriage returns are replaced with spaces.\n\t * The combined status line is truncated to terminal width.\n\t * @param key - Unique key to identify this status (e.g., hook name)\n\t * @param text - Status text to display, or undefined to clear\n\t */\n\tsetStatus(key: string, text: string | undefined): void;\n\n\t/**\n\t * Show a custom component with keyboard focus.\n\t * The factory receives TUI, theme, and a done() callback to close the component.\n\t * Can be async for fire-and-forget work (don't await the work, just start it).\n\t *\n\t * @param factory - Function that creates the component. Call done() when finished.\n\t * @returns Promise that resolves with the value passed to done()\n\t *\n\t * @example\n\t * // Sync factory\n\t * const result = await ctx.ui.custom((tui, theme, done) => {\n\t * const component = new MyComponent(tui, theme);\n\t * component.onFinish = (value) => done(value);\n\t * return component;\n\t * });\n\t *\n\t * // Async factory with fire-and-forget work\n\t * const result = await ctx.ui.custom(async (tui, theme, done) => {\n\t * const loader = new CancellableLoader(tui, theme.fg(\"accent\"), theme.fg(\"muted\"), \"Working...\");\n\t * loader.onAbort = () => done(null);\n\t * doWork(loader.signal).then(done); // Don't await - fire and forget\n\t * return loader;\n\t * });\n\t */\n\tcustom<T>(\n\t\tfactory: (\n\t\t\ttui: TUI,\n\t\t\ttheme: Theme,\n\t\t\tdone: (result: T) => void,\n\t\t) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,\n\t): Promise<T>;\n\n\t/**\n\t * Set the text in the core input editor.\n\t * Use this to pre-fill the input box with generated content (e.g., prompt templates, extracted questions).\n\t * @param text - Text to set in the editor\n\t */\n\tsetEditorText(text: string): void;\n\n\t/**\n\t * Get the current text from the core input editor.\n\t * @returns Current editor text\n\t */\n\tgetEditorText(): string;\n\n\t/**\n\t * Show a multi-line editor for text editing.\n\t * Supports Ctrl+G to open external editor ($VISUAL or $EDITOR).\n\t * @param title - Title describing what is being edited\n\t * @param prefill - Optional initial text\n\t * @returns Edited text, or undefined if cancelled (Escape)\n\t */\n\teditor(title: string, prefill?: string): Promise<string | undefined>;\n\n\t/**\n\t * Get the current theme for styling text with ANSI codes.\n\t * Use theme.fg() and theme.bg() to style status text.\n\t *\n\t * @example\n\t * const theme = ctx.ui.theme;\n\t * ctx.ui.setStatus(\"my-hook\", theme.fg(\"success\", \"✓\") + \" Ready\");\n\t */\n\treadonly theme: Theme;\n}\n\n/**\n * Context passed to hook event handlers.\n * For command handlers, see HookCommandContext which extends this with session control methods.\n */\nexport interface HookContext {\n\t/** UI methods for user interaction */\n\tui: HookUIContext;\n\t/** Whether UI is available (false in print mode) */\n\thasUI: boolean;\n\t/** Current working directory */\n\tcwd: string;\n\t/** Session manager (read-only) - use pi.sendMessage()/pi.appendEntry() for writes */\n\tsessionManager: ReadonlySessionManager;\n\t/** Model registry - use for API key resolution and model retrieval */\n\tmodelRegistry: ModelRegistry;\n\t/** Current model (may be undefined if no model is selected yet) */\n\tmodel: Model<any> | undefined;\n\t/** Whether the agent is idle (not streaming) */\n\tisIdle(): boolean;\n\t/** Abort the current agent operation (fire-and-forget, does not wait) */\n\tabort(): void;\n\t/** Whether there are queued messages waiting to be processed */\n\thasQueuedMessages(): boolean;\n}\n\n/**\n * Extended context for slash command handlers.\n * Includes session control methods that are only safe in user-initiated commands.\n *\n * These methods are not available in event handlers because they can cause\n * deadlocks when called from within the agent loop (e.g., tool_call, context events).\n */\nexport interface HookCommandContext extends HookContext {\n\t/** Wait for the agent to finish streaming */\n\twaitForIdle(): Promise<void>;\n\n\t/**\n\t * Start a new session, optionally with a setup callback to initialize it.\n\t * The setup callback receives a writable SessionManager for the new session.\n\t *\n\t * @param options.parentSession - Path to parent session for lineage tracking\n\t * @param options.setup - Async callback to initialize the new session (e.g., append messages)\n\t * @returns Object with `cancelled: true` if a hook cancelled the new session\n\t *\n\t * @example\n\t * // Handoff: summarize current session and start fresh with context\n\t * await ctx.newSession({\n\t * parentSession: ctx.sessionManager.getSessionFile(),\n\t * setup: async (sm) => {\n\t * sm.appendMessage({ role: \"user\", content: [{ type: \"text\", text: summary }] });\n\t * }\n\t * });\n\t */\n\tnewSession(options?: {\n\t\tparentSession?: string;\n\t\tsetup?: (sessionManager: SessionManager) => Promise<void>;\n\t}): Promise<{ cancelled: boolean }>;\n\n\t/**\n\t * Branch from a specific entry, creating a new session file.\n\t *\n\t * @param entryId - ID of the entry to branch from\n\t * @returns Object with `cancelled: true` if a hook cancelled the branch\n\t */\n\tbranch(entryId: string): Promise<{ cancelled: boolean }>;\n\n\t/**\n\t * Navigate to a different point in the session tree (in-place).\n\t *\n\t * @param targetId - ID of the entry to navigate to\n\t * @param options.summarize - Whether to summarize the abandoned branch\n\t * @returns Object with `cancelled: true` if a hook cancelled the navigation\n\t */\n\tnavigateTree(targetId: string, options?: { summarize?: boolean }): Promise<{ cancelled: boolean }>;\n}\n\n// ============================================================================\n// Session Events\n// ============================================================================\n\n/** Fired on initial session load */\nexport interface SessionStartEvent {\n\ttype: \"session_start\";\n}\n\n/** Fired before switching to another session (can be cancelled) */\nexport interface SessionBeforeSwitchEvent {\n\ttype: \"session_before_switch\";\n\t/** Reason for the switch */\n\treason: \"new\" | \"resume\";\n\t/** Session file we're switching to (only for \"resume\") */\n\ttargetSessionFile?: string;\n}\n\n/** Fired after switching to another session */\nexport interface SessionSwitchEvent {\n\ttype: \"session_switch\";\n\t/** Reason for the switch */\n\treason: \"new\" | \"resume\";\n\t/** Session file we came from */\n\tpreviousSessionFile: string | undefined;\n}\n\n/** Fired before branching a session (can be cancelled) */\nexport interface SessionBeforeBranchEvent {\n\ttype: \"session_before_branch\";\n\t/** ID of the entry to branch from */\n\tentryId: string;\n}\n\n/** Fired after branching a session */\nexport interface SessionBranchEvent {\n\ttype: \"session_branch\";\n\tpreviousSessionFile: string | undefined;\n}\n\n/** Fired before context compaction (can be cancelled or customized) */\nexport interface SessionBeforeCompactEvent {\n\ttype: \"session_before_compact\";\n\t/** Compaction preparation with messages to summarize, file ops, previous summary, etc. */\n\tpreparation: CompactionPreparation;\n\t/** Branch entries (root to current leaf). Use to inspect custom state or previous compactions. */\n\tbranchEntries: SessionEntry[];\n\t/** Optional user-provided instructions for the summary */\n\tcustomInstructions?: string;\n\t/** Abort signal - hooks should pass this to LLM calls and check it periodically */\n\tsignal: AbortSignal;\n}\n\n/** Fired after context compaction */\nexport interface SessionCompactEvent {\n\ttype: \"session_compact\";\n\tcompactionEntry: CompactionEntry;\n\t/** Whether the compaction entry was provided by a hook */\n\tfromHook: boolean;\n}\n\n/** Fired on process exit (SIGINT/SIGTERM) */\nexport interface SessionShutdownEvent {\n\ttype: \"session_shutdown\";\n}\n\n/** Preparation data for tree navigation (used by session_before_tree event) */\nexport interface TreePreparation {\n\t/** Node being switched to */\n\ttargetId: string;\n\t/** Current active leaf (being abandoned), null if no current position */\n\toldLeafId: string | null;\n\t/** Common ancestor of target and old leaf, null if no common ancestor */\n\tcommonAncestorId: string | null;\n\t/** Entries to summarize (old leaf back to common ancestor or compaction) */\n\tentriesToSummarize: SessionEntry[];\n\t/** Whether user chose to summarize */\n\tuserWantsSummary: boolean;\n}\n\n/** Fired before navigating to a different node in the session tree (can be cancelled) */\nexport interface SessionBeforeTreeEvent {\n\ttype: \"session_before_tree\";\n\t/** Preparation data for the navigation */\n\tpreparation: TreePreparation;\n\t/** Abort signal - honors Escape during summarization (model available via ctx.model) */\n\tsignal: AbortSignal;\n}\n\n/** Fired after navigating to a different node in the session tree */\nexport interface SessionTreeEvent {\n\ttype: \"session_tree\";\n\t/** The new active leaf, null if navigated to before first entry */\n\tnewLeafId: string | null;\n\t/** Previous active leaf, null if there was no position */\n\toldLeafId: string | null;\n\t/** Branch summary entry if one was created */\n\tsummaryEntry?: BranchSummaryEntry;\n\t/** Whether summary came from hook */\n\tfromHook?: boolean;\n}\n\n/** Union of all session event types */\nexport type SessionEvent =\n\t| SessionStartEvent\n\t| SessionBeforeSwitchEvent\n\t| SessionSwitchEvent\n\t| SessionBeforeBranchEvent\n\t| SessionBranchEvent\n\t| SessionBeforeCompactEvent\n\t| SessionCompactEvent\n\t| SessionShutdownEvent\n\t| SessionBeforeTreeEvent\n\t| SessionTreeEvent;\n\n/**\n * Event data for context event.\n * Fired before each LLM call, allowing hooks to modify context non-destructively.\n * Original session messages are NOT modified - only the messages sent to the LLM are affected.\n */\nexport interface ContextEvent {\n\ttype: \"context\";\n\t/** Messages about to be sent to the LLM (deep copy, safe to modify) */\n\tmessages: AgentMessage[];\n}\n\n/**\n * Event data for before_agent_start event.\n * Fired after user submits a prompt but before the agent loop starts.\n * Allows hooks to inject context that will be persisted and visible in TUI.\n */\nexport interface BeforeAgentStartEvent {\n\ttype: \"before_agent_start\";\n\t/** The user's prompt text */\n\tprompt: string;\n\t/** Any images attached to the prompt */\n\timages?: ImageContent[];\n}\n\n/**\n * Event data for agent_start event.\n * Fired when an agent loop starts (once per user prompt).\n */\nexport interface AgentStartEvent {\n\ttype: \"agent_start\";\n}\n\n/**\n * Event data for agent_end event.\n */\nexport interface AgentEndEvent {\n\ttype: \"agent_end\";\n\tmessages: AgentMessage[];\n}\n\n/**\n * Event data for turn_start event.\n */\nexport interface TurnStartEvent {\n\ttype: \"turn_start\";\n\tturnIndex: number;\n\ttimestamp: number;\n}\n\n/**\n * Event data for turn_end event.\n */\nexport interface TurnEndEvent {\n\ttype: \"turn_end\";\n\tturnIndex: number;\n\tmessage: AgentMessage;\n\ttoolResults: ToolResultMessage[];\n}\n\n/**\n * Event data for tool_call event.\n * Fired before a tool is executed. Hooks can block execution.\n */\nexport interface ToolCallEvent {\n\ttype: \"tool_call\";\n\t/** Tool name (e.g., \"bash\", \"edit\", \"write\") */\n\ttoolName: string;\n\t/** Tool call ID */\n\ttoolCallId: string;\n\t/** Tool input parameters */\n\tinput: Record<string, unknown>;\n}\n\n/**\n * Base interface for tool_result events.\n */\ninterface ToolResultEventBase {\n\ttype: \"tool_result\";\n\t/** Tool call ID */\n\ttoolCallId: string;\n\t/** Tool input parameters */\n\tinput: Record<string, unknown>;\n\t/** Full content array (text and images) */\n\tcontent: (TextContent | ImageContent)[];\n\t/** Whether the tool execution was an error */\n\tisError: boolean;\n}\n\n/** Tool result event for bash tool */\nexport interface BashToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"bash\";\n\tdetails: BashToolDetails | undefined;\n}\n\n/** Tool result event for read tool */\nexport interface ReadToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"read\";\n\tdetails: ReadToolDetails | undefined;\n}\n\n/** Tool result event for edit tool */\nexport interface EditToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"edit\";\n\tdetails: EditToolDetails | undefined;\n}\n\n/** Tool result event for write tool */\nexport interface WriteToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"write\";\n\tdetails: undefined;\n}\n\n/** Tool result event for grep tool */\nexport interface GrepToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"grep\";\n\tdetails: GrepToolDetails | undefined;\n}\n\n/** Tool result event for find tool */\nexport interface FindToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"find\";\n\tdetails: FindToolDetails | undefined;\n}\n\n/** Tool result event for ls tool */\nexport interface LsToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"ls\";\n\tdetails: LsToolDetails | undefined;\n}\n\n/** Tool result event for custom/unknown tools */\nexport interface CustomToolResultEvent extends ToolResultEventBase {\n\ttoolName: string;\n\tdetails: unknown;\n}\n\n/**\n * Event data for tool_result event.\n * Fired after a tool is executed. Hooks can modify the result.\n * Use toolName to discriminate and get typed details.\n */\nexport type ToolResultEvent =\n\t| BashToolResultEvent\n\t| ReadToolResultEvent\n\t| EditToolResultEvent\n\t| WriteToolResultEvent\n\t| GrepToolResultEvent\n\t| FindToolResultEvent\n\t| LsToolResultEvent\n\t| CustomToolResultEvent;\n\n// Type guards for narrowing ToolResultEvent to specific tool types\nexport function isBashToolResult(e: ToolResultEvent): e is BashToolResultEvent {\n\treturn e.toolName === \"bash\";\n}\nexport function isReadToolResult(e: ToolResultEvent): e is ReadToolResultEvent {\n\treturn e.toolName === \"read\";\n}\nexport function isEditToolResult(e: ToolResultEvent): e is EditToolResultEvent {\n\treturn e.toolName === \"edit\";\n}\nexport function isWriteToolResult(e: ToolResultEvent): e is WriteToolResultEvent {\n\treturn e.toolName === \"write\";\n}\nexport function isGrepToolResult(e: ToolResultEvent): e is GrepToolResultEvent {\n\treturn e.toolName === \"grep\";\n}\nexport function isFindToolResult(e: ToolResultEvent): e is FindToolResultEvent {\n\treturn e.toolName === \"find\";\n}\nexport function isLsToolResult(e: ToolResultEvent): e is LsToolResultEvent {\n\treturn e.toolName === \"ls\";\n}\n\n/**\n * Union of all hook event types.\n */\nexport type HookEvent =\n\t| SessionEvent\n\t| ContextEvent\n\t| BeforeAgentStartEvent\n\t| AgentStartEvent\n\t| AgentEndEvent\n\t| TurnStartEvent\n\t| TurnEndEvent\n\t| ToolCallEvent\n\t| ToolResultEvent;\n\n// ============================================================================\n// Event Results\n// ============================================================================\n\n/**\n * Return type for context event handlers.\n * Allows hooks to modify messages before they're sent to the LLM.\n */\nexport interface ContextEventResult {\n\t/** Modified messages to send instead of the original */\n\tmessages?: Message[];\n}\n\n/**\n * Return type for tool_call event handlers.\n * Allows hooks to block tool execution.\n */\nexport interface ToolCallEventResult {\n\t/** If true, block the tool from executing */\n\tblock?: boolean;\n\t/** Reason for blocking (returned to LLM as error) */\n\treason?: string;\n}\n\n/**\n * Return type for tool_result event handlers.\n * Allows hooks to modify tool results.\n */\nexport interface ToolResultEventResult {\n\t/** Replacement content array (text and images) */\n\tcontent?: (TextContent | ImageContent)[];\n\t/** Replacement details */\n\tdetails?: unknown;\n\t/** Override isError flag */\n\tisError?: boolean;\n}\n\n/**\n * Return type for before_agent_start event handlers.\n * Allows hooks to inject context before the agent runs.\n */\nexport interface BeforeAgentStartEventResult {\n\t/** Message to inject into context (persisted to session, visible in TUI) */\n\tmessage?: Pick<HookMessage, \"customType\" | \"content\" | \"display\" | \"details\">;\n}\n\n/** Return type for session_before_switch handlers */\nexport interface SessionBeforeSwitchResult {\n\t/** If true, cancel the switch */\n\tcancel?: boolean;\n}\n\n/** Return type for session_before_branch handlers */\nexport interface SessionBeforeBranchResult {\n\t/**\n\t * If true, abort the branch entirely. No new session file is created,\n\t * conversation stays unchanged.\n\t */\n\tcancel?: boolean;\n\t/**\n\t * If true, the branch proceeds (new session file created, session state updated)\n\t * but the in-memory conversation is NOT rewound to the branch point.\n\t *\n\t * Use case: git-checkpoint hook that restores code state separately.\n\t * The hook handles state restoration itself, so it doesn't want the\n\t * agent's conversation to be rewound (which would lose recent context).\n\t *\n\t * - `cancel: true` → nothing happens, user stays in current session\n\t * - `skipConversationRestore: true` → branch happens, but messages stay as-is\n\t * - neither → branch happens AND messages rewind to branch point (default)\n\t */\n\tskipConversationRestore?: boolean;\n}\n\n/** Return type for session_before_compact handlers */\nexport interface SessionBeforeCompactResult {\n\t/** If true, cancel the compaction */\n\tcancel?: boolean;\n\t/** Custom compaction result - SessionManager adds id/parentId */\n\tcompaction?: CompactionResult;\n}\n\n/** Return type for session_before_tree handlers */\nexport interface SessionBeforeTreeResult {\n\t/** If true, cancel the navigation entirely */\n\tcancel?: boolean;\n\t/**\n\t * Custom summary (skips default summarizer).\n\t * Only used if preparation.userWantsSummary is true.\n\t */\n\tsummary?: {\n\t\tsummary: string;\n\t\tdetails?: unknown;\n\t};\n}\n\n// ============================================================================\n// Hook API\n// ============================================================================\n\n/**\n * Handler function type for each event.\n * Handlers can return R, undefined, or void (bare return statements).\n */\n// biome-ignore lint/suspicious/noConfusingVoidType: void allows bare return statements in handlers\nexport type HookHandler<E, R = undefined> = (event: E, ctx: HookContext) => Promise<R | void> | R | void;\n\nexport interface HookMessageRenderOptions {\n\t/** Whether the view is expanded */\n\texpanded: boolean;\n}\n\n/**\n * Renderer for hook messages.\n * Hooks register these to provide custom TUI rendering for their message types.\n */\nexport type HookMessageRenderer<T = unknown> = (\n\tmessage: HookMessage<T>,\n\toptions: HookMessageRenderOptions,\n\ttheme: Theme,\n) => Component | undefined;\n\n/**\n * Command registration options.\n */\nexport interface RegisteredCommand {\n\tname: string;\n\tdescription?: string;\n\n\thandler: (args: string, ctx: HookCommandContext) => Promise<void>;\n}\n\n/**\n * HookAPI passed to hook factory functions.\n * Hooks use pi.on() to subscribe to events and pi.sendMessage() to inject messages.\n */\nexport interface HookAPI {\n\t// Session events\n\ton(event: \"session_start\", handler: HookHandler<SessionStartEvent>): void;\n\ton(event: \"session_before_switch\", handler: HookHandler<SessionBeforeSwitchEvent, SessionBeforeSwitchResult>): void;\n\ton(event: \"session_switch\", handler: HookHandler<SessionSwitchEvent>): void;\n\ton(event: \"session_before_branch\", handler: HookHandler<SessionBeforeBranchEvent, SessionBeforeBranchResult>): void;\n\ton(event: \"session_branch\", handler: HookHandler<SessionBranchEvent>): void;\n\ton(\n\t\tevent: \"session_before_compact\",\n\t\thandler: HookHandler<SessionBeforeCompactEvent, SessionBeforeCompactResult>,\n\t): void;\n\ton(event: \"session_compact\", handler: HookHandler<SessionCompactEvent>): void;\n\ton(event: \"session_shutdown\", handler: HookHandler<SessionShutdownEvent>): void;\n\ton(event: \"session_before_tree\", handler: HookHandler<SessionBeforeTreeEvent, SessionBeforeTreeResult>): void;\n\ton(event: \"session_tree\", handler: HookHandler<SessionTreeEvent>): void;\n\n\t// Context and agent events\n\ton(event: \"context\", handler: HookHandler<ContextEvent, ContextEventResult>): void;\n\ton(event: \"before_agent_start\", handler: HookHandler<BeforeAgentStartEvent, BeforeAgentStartEventResult>): void;\n\ton(event: \"agent_start\", handler: HookHandler<AgentStartEvent>): void;\n\ton(event: \"agent_end\", handler: HookHandler<AgentEndEvent>): void;\n\ton(event: \"turn_start\", handler: HookHandler<TurnStartEvent>): void;\n\ton(event: \"turn_end\", handler: HookHandler<TurnEndEvent>): void;\n\ton(event: \"tool_call\", handler: HookHandler<ToolCallEvent, ToolCallEventResult>): void;\n\ton(event: \"tool_result\", handler: HookHandler<ToolResultEvent, ToolResultEventResult>): void;\n\n\t/**\n\t * Send a custom message to the session. Creates a CustomMessageEntry that\n\t * participates in LLM context and can be displayed in the TUI.\n\t *\n\t * Use this when you want the LLM to see the message content.\n\t * For hook state that should NOT be sent to the LLM, use appendEntry() instead.\n\t *\n\t * @param message - The message to send\n\t * @param message.customType - Identifier for your hook (used for filtering on reload)\n\t * @param message.content - Message content (string or TextContent/ImageContent array)\n\t * @param message.display - Whether to show in TUI (true = styled display, false = hidden)\n\t * @param message.details - Optional hook-specific metadata (not sent to LLM)\n\t * @param triggerTurn - If true and agent is idle, triggers a new LLM turn. Default: false.\n\t * If agent is streaming, message is queued and triggerTurn is ignored.\n\t */\n\tsendMessage<T = unknown>(\n\t\tmessage: Pick<HookMessage<T>, \"customType\" | \"content\" | \"display\" | \"details\">,\n\t\ttriggerTurn?: boolean,\n\t): void;\n\n\t/**\n\t * Append a custom entry to the session for hook state persistence.\n\t * Creates a CustomEntry that does NOT participate in LLM context.\n\t *\n\t * Use this to store hook-specific data that should persist across session reloads\n\t * but should NOT be sent to the LLM. On reload, scan session entries for your\n\t * customType to reconstruct hook state.\n\t *\n\t * For messages that SHOULD be sent to the LLM, use sendMessage() instead.\n\t *\n\t * @param customType - Identifier for your hook (used for filtering on reload)\n\t * @param data - Hook-specific data to persist (must be JSON-serializable)\n\t *\n\t * @example\n\t * // Store permission state\n\t * pi.appendEntry(\"permissions\", { level: \"full\", grantedAt: Date.now() });\n\t *\n\t * // On reload, reconstruct state from entries\n\t * pi.on(\"session\", async (event, ctx) => {\n\t * if (event.reason === \"start\") {\n\t * const entries = event.sessionManager.getEntries();\n\t * const myEntries = entries.filter(e => e.type === \"custom\" && e.customType === \"permissions\");\n\t * // Reconstruct state from myEntries...\n\t * }\n\t * });\n\t */\n\tappendEntry<T = unknown>(customType: string, data?: T): void;\n\n\t/**\n\t * Register a custom renderer for CustomMessageEntry with a specific customType.\n\t * The renderer is called when rendering the entry in the TUI.\n\t * Return nothing to use the default renderer.\n\t */\n\tregisterMessageRenderer<T = unknown>(customType: string, renderer: HookMessageRenderer<T>): void;\n\n\t/**\n\t * Register a custom slash command.\n\t * Handler receives HookCommandContext with session control methods.\n\t */\n\tregisterCommand(name: string, options: { description?: string; handler: RegisteredCommand[\"handler\"] }): void;\n\n\t/**\n\t * Execute a shell command and return stdout/stderr/code.\n\t * Supports timeout and abort signal.\n\t */\n\texec(command: string, args: string[], options?: ExecOptions): Promise<ExecResult>;\n}\n\n/**\n * Hook factory function type.\n * Hooks export a default function that receives the HookAPI.\n */\nexport type HookFactory = (pi: HookAPI) => void;\n\n// ============================================================================\n// Errors\n// ============================================================================\n\n/**\n * Error emitted when a hook fails.\n */\nexport interface HookError {\n\thookPath: string;\n\tevent: string;\n\terror: string;\n}\n"]}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/types.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook system types.\n *\n * Hooks are TypeScript modules that can subscribe to agent lifecycle events\n * and interact with the user via UI primitives.\n */\n\nimport type { AgentMessage } from \"@mariozechner/pi-agent-core\";\nimport type { ImageContent, Message, Model, TextContent, ToolResultMessage } from \"@mariozechner/pi-ai\";\nimport type { Component, TUI } from \"@mariozechner/pi-tui\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.js\";\nimport type { CompactionPreparation, CompactionResult } from \"../compaction/index.js\";\nimport type { ExecOptions, ExecResult } from \"../exec.js\";\nimport type { HookMessage } from \"../messages.js\";\nimport type { ModelRegistry } from \"../model-registry.js\";\nimport type {\n\tBranchSummaryEntry,\n\tCompactionEntry,\n\tReadonlySessionManager,\n\tSessionEntry,\n\tSessionManager,\n} from \"../session-manager.js\";\n\nimport type { EditToolDetails } from \"../tools/edit.js\";\nimport type {\n\tBashToolDetails,\n\tFindToolDetails,\n\tGrepToolDetails,\n\tLsToolDetails,\n\tReadToolDetails,\n} from \"../tools/index.js\";\n\n// Re-export for backward compatibility\nexport type { ExecOptions, ExecResult } from \"../exec.js\";\n\n/**\n * UI context for hooks to request interactive UI from the harness.\n * Each mode (interactive, RPC, print) provides its own implementation.\n */\nexport interface HookUIContext {\n\t/**\n\t * Show a selector and return the user's choice.\n\t * @param title - Title to display\n\t * @param options - Array of string options\n\t * @returns Selected option string, or null if cancelled\n\t */\n\tselect(title: string, options: string[]): Promise<string | undefined>;\n\n\t/**\n\t * Show a confirmation dialog.\n\t * @returns true if confirmed, false if cancelled\n\t */\n\tconfirm(title: string, message: string): Promise<boolean>;\n\n\t/**\n\t * Show a text input dialog.\n\t * @returns User input, or undefined if cancelled\n\t */\n\tinput(title: string, placeholder?: string): Promise<string | undefined>;\n\n\t/**\n\t * Show a notification to the user.\n\t */\n\tnotify(message: string, type?: \"info\" | \"warning\" | \"error\"): void;\n\n\t/**\n\t * Set status text in the footer/status bar.\n\t * Pass undefined as text to clear the status for this key.\n\t * Text can include ANSI escape codes for styling.\n\t * Note: Newlines, tabs, and carriage returns are replaced with spaces.\n\t * The combined status line is truncated to terminal width.\n\t * @param key - Unique key to identify this status (e.g., hook name)\n\t * @param text - Status text to display, or undefined to clear\n\t */\n\tsetStatus(key: string, text: string | undefined): void;\n\n\t/**\n\t * Show a custom component with keyboard focus.\n\t * The factory receives TUI, theme, and a done() callback to close the component.\n\t * Can be async for fire-and-forget work (don't await the work, just start it).\n\t *\n\t * @param factory - Function that creates the component. Call done() when finished.\n\t * @returns Promise that resolves with the value passed to done()\n\t *\n\t * @example\n\t * // Sync factory\n\t * const result = await ctx.ui.custom((tui, theme, done) => {\n\t * const component = new MyComponent(tui, theme);\n\t * component.onFinish = (value) => done(value);\n\t * return component;\n\t * });\n\t *\n\t * // Async factory with fire-and-forget work\n\t * const result = await ctx.ui.custom(async (tui, theme, done) => {\n\t * const loader = new CancellableLoader(tui, theme.fg(\"accent\"), theme.fg(\"muted\"), \"Working...\");\n\t * loader.onAbort = () => done(null);\n\t * doWork(loader.signal).then(done); // Don't await - fire and forget\n\t * return loader;\n\t * });\n\t */\n\tcustom<T>(\n\t\tfactory: (\n\t\t\ttui: TUI,\n\t\t\ttheme: Theme,\n\t\t\tdone: (result: T) => void,\n\t\t) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,\n\t): Promise<T>;\n\n\t/**\n\t * Set the text in the core input editor.\n\t * Use this to pre-fill the input box with generated content (e.g., prompt templates, extracted questions).\n\t * @param text - Text to set in the editor\n\t */\n\tsetEditorText(text: string): void;\n\n\t/**\n\t * Get the current text from the core input editor.\n\t * @returns Current editor text\n\t */\n\tgetEditorText(): string;\n\n\t/**\n\t * Show a multi-line editor for text editing.\n\t * Supports Ctrl+G to open external editor ($VISUAL or $EDITOR).\n\t * @param title - Title describing what is being edited\n\t * @param prefill - Optional initial text\n\t * @returns Edited text, or undefined if cancelled (Escape)\n\t */\n\teditor(title: string, prefill?: string): Promise<string | undefined>;\n\n\t/**\n\t * Get the current theme for styling text with ANSI codes.\n\t * Use theme.fg() and theme.bg() to style status text.\n\t *\n\t * @example\n\t * const theme = ctx.ui.theme;\n\t * ctx.ui.setStatus(\"my-hook\", theme.fg(\"success\", \"✓\") + \" Ready\");\n\t */\n\treadonly theme: Theme;\n}\n\n/**\n * Context passed to hook event handlers.\n * For command handlers, see HookCommandContext which extends this with session control methods.\n */\nexport interface HookContext {\n\t/** UI methods for user interaction */\n\tui: HookUIContext;\n\t/** Whether UI is available (false in print mode) */\n\thasUI: boolean;\n\t/** Current working directory */\n\tcwd: string;\n\t/** Session manager (read-only) - use pi.sendMessage()/pi.appendEntry() for writes */\n\tsessionManager: ReadonlySessionManager;\n\t/** Model registry - use for API key resolution and model retrieval */\n\tmodelRegistry: ModelRegistry;\n\t/** Current model (may be undefined if no model is selected yet) */\n\tmodel: Model<any> | undefined;\n\t/** Whether the agent is idle (not streaming) */\n\tisIdle(): boolean;\n\t/** Abort the current agent operation (fire-and-forget, does not wait) */\n\tabort(): void;\n\t/** Whether there are queued messages waiting to be processed */\n\thasPendingMessages(): boolean;\n}\n\n/**\n * Extended context for slash command handlers.\n * Includes session control methods that are only safe in user-initiated commands.\n *\n * These methods are not available in event handlers because they can cause\n * deadlocks when called from within the agent loop (e.g., tool_call, context events).\n */\nexport interface HookCommandContext extends HookContext {\n\t/** Wait for the agent to finish streaming */\n\twaitForIdle(): Promise<void>;\n\n\t/**\n\t * Start a new session, optionally with a setup callback to initialize it.\n\t * The setup callback receives a writable SessionManager for the new session.\n\t *\n\t * @param options.parentSession - Path to parent session for lineage tracking\n\t * @param options.setup - Async callback to initialize the new session (e.g., append messages)\n\t * @returns Object with `cancelled: true` if a hook cancelled the new session\n\t *\n\t * @example\n\t * // Handoff: summarize current session and start fresh with context\n\t * await ctx.newSession({\n\t * parentSession: ctx.sessionManager.getSessionFile(),\n\t * setup: async (sm) => {\n\t * sm.appendMessage({ role: \"user\", content: [{ type: \"text\", text: summary }] });\n\t * }\n\t * });\n\t */\n\tnewSession(options?: {\n\t\tparentSession?: string;\n\t\tsetup?: (sessionManager: SessionManager) => Promise<void>;\n\t}): Promise<{ cancelled: boolean }>;\n\n\t/**\n\t * Branch from a specific entry, creating a new session file.\n\t *\n\t * @param entryId - ID of the entry to branch from\n\t * @returns Object with `cancelled: true` if a hook cancelled the branch\n\t */\n\tbranch(entryId: string): Promise<{ cancelled: boolean }>;\n\n\t/**\n\t * Navigate to a different point in the session tree (in-place).\n\t *\n\t * @param targetId - ID of the entry to navigate to\n\t * @param options.summarize - Whether to summarize the abandoned branch\n\t * @returns Object with `cancelled: true` if a hook cancelled the navigation\n\t */\n\tnavigateTree(targetId: string, options?: { summarize?: boolean }): Promise<{ cancelled: boolean }>;\n}\n\n// ============================================================================\n// Session Events\n// ============================================================================\n\n/** Fired on initial session load */\nexport interface SessionStartEvent {\n\ttype: \"session_start\";\n}\n\n/** Fired before switching to another session (can be cancelled) */\nexport interface SessionBeforeSwitchEvent {\n\ttype: \"session_before_switch\";\n\t/** Reason for the switch */\n\treason: \"new\" | \"resume\";\n\t/** Session file we're switching to (only for \"resume\") */\n\ttargetSessionFile?: string;\n}\n\n/** Fired after switching to another session */\nexport interface SessionSwitchEvent {\n\ttype: \"session_switch\";\n\t/** Reason for the switch */\n\treason: \"new\" | \"resume\";\n\t/** Session file we came from */\n\tpreviousSessionFile: string | undefined;\n}\n\n/** Fired before branching a session (can be cancelled) */\nexport interface SessionBeforeBranchEvent {\n\ttype: \"session_before_branch\";\n\t/** ID of the entry to branch from */\n\tentryId: string;\n}\n\n/** Fired after branching a session */\nexport interface SessionBranchEvent {\n\ttype: \"session_branch\";\n\tpreviousSessionFile: string | undefined;\n}\n\n/** Fired before context compaction (can be cancelled or customized) */\nexport interface SessionBeforeCompactEvent {\n\ttype: \"session_before_compact\";\n\t/** Compaction preparation with messages to summarize, file ops, previous summary, etc. */\n\tpreparation: CompactionPreparation;\n\t/** Branch entries (root to current leaf). Use to inspect custom state or previous compactions. */\n\tbranchEntries: SessionEntry[];\n\t/** Optional user-provided instructions for the summary */\n\tcustomInstructions?: string;\n\t/** Abort signal - hooks should pass this to LLM calls and check it periodically */\n\tsignal: AbortSignal;\n}\n\n/** Fired after context compaction */\nexport interface SessionCompactEvent {\n\ttype: \"session_compact\";\n\tcompactionEntry: CompactionEntry;\n\t/** Whether the compaction entry was provided by a hook */\n\tfromHook: boolean;\n}\n\n/** Fired on process exit (SIGINT/SIGTERM) */\nexport interface SessionShutdownEvent {\n\ttype: \"session_shutdown\";\n}\n\n/** Preparation data for tree navigation (used by session_before_tree event) */\nexport interface TreePreparation {\n\t/** Node being switched to */\n\ttargetId: string;\n\t/** Current active leaf (being abandoned), null if no current position */\n\toldLeafId: string | null;\n\t/** Common ancestor of target and old leaf, null if no common ancestor */\n\tcommonAncestorId: string | null;\n\t/** Entries to summarize (old leaf back to common ancestor or compaction) */\n\tentriesToSummarize: SessionEntry[];\n\t/** Whether user chose to summarize */\n\tuserWantsSummary: boolean;\n}\n\n/** Fired before navigating to a different node in the session tree (can be cancelled) */\nexport interface SessionBeforeTreeEvent {\n\ttype: \"session_before_tree\";\n\t/** Preparation data for the navigation */\n\tpreparation: TreePreparation;\n\t/** Abort signal - honors Escape during summarization (model available via ctx.model) */\n\tsignal: AbortSignal;\n}\n\n/** Fired after navigating to a different node in the session tree */\nexport interface SessionTreeEvent {\n\ttype: \"session_tree\";\n\t/** The new active leaf, null if navigated to before first entry */\n\tnewLeafId: string | null;\n\t/** Previous active leaf, null if there was no position */\n\toldLeafId: string | null;\n\t/** Branch summary entry if one was created */\n\tsummaryEntry?: BranchSummaryEntry;\n\t/** Whether summary came from hook */\n\tfromHook?: boolean;\n}\n\n/** Union of all session event types */\nexport type SessionEvent =\n\t| SessionStartEvent\n\t| SessionBeforeSwitchEvent\n\t| SessionSwitchEvent\n\t| SessionBeforeBranchEvent\n\t| SessionBranchEvent\n\t| SessionBeforeCompactEvent\n\t| SessionCompactEvent\n\t| SessionShutdownEvent\n\t| SessionBeforeTreeEvent\n\t| SessionTreeEvent;\n\n/**\n * Event data for context event.\n * Fired before each LLM call, allowing hooks to modify context non-destructively.\n * Original session messages are NOT modified - only the messages sent to the LLM are affected.\n */\nexport interface ContextEvent {\n\ttype: \"context\";\n\t/** Messages about to be sent to the LLM (deep copy, safe to modify) */\n\tmessages: AgentMessage[];\n}\n\n/**\n * Event data for before_agent_start event.\n * Fired after user submits a prompt but before the agent loop starts.\n * Allows hooks to inject context that will be persisted and visible in TUI.\n */\nexport interface BeforeAgentStartEvent {\n\ttype: \"before_agent_start\";\n\t/** The user's prompt text */\n\tprompt: string;\n\t/** Any images attached to the prompt */\n\timages?: ImageContent[];\n}\n\n/**\n * Event data for agent_start event.\n * Fired when an agent loop starts (once per user prompt).\n */\nexport interface AgentStartEvent {\n\ttype: \"agent_start\";\n}\n\n/**\n * Event data for agent_end event.\n */\nexport interface AgentEndEvent {\n\ttype: \"agent_end\";\n\tmessages: AgentMessage[];\n}\n\n/**\n * Event data for turn_start event.\n */\nexport interface TurnStartEvent {\n\ttype: \"turn_start\";\n\tturnIndex: number;\n\ttimestamp: number;\n}\n\n/**\n * Event data for turn_end event.\n */\nexport interface TurnEndEvent {\n\ttype: \"turn_end\";\n\tturnIndex: number;\n\tmessage: AgentMessage;\n\ttoolResults: ToolResultMessage[];\n}\n\n/**\n * Event data for tool_call event.\n * Fired before a tool is executed. Hooks can block execution.\n */\nexport interface ToolCallEvent {\n\ttype: \"tool_call\";\n\t/** Tool name (e.g., \"bash\", \"edit\", \"write\") */\n\ttoolName: string;\n\t/** Tool call ID */\n\ttoolCallId: string;\n\t/** Tool input parameters */\n\tinput: Record<string, unknown>;\n}\n\n/**\n * Base interface for tool_result events.\n */\ninterface ToolResultEventBase {\n\ttype: \"tool_result\";\n\t/** Tool call ID */\n\ttoolCallId: string;\n\t/** Tool input parameters */\n\tinput: Record<string, unknown>;\n\t/** Full content array (text and images) */\n\tcontent: (TextContent | ImageContent)[];\n\t/** Whether the tool execution was an error */\n\tisError: boolean;\n}\n\n/** Tool result event for bash tool */\nexport interface BashToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"bash\";\n\tdetails: BashToolDetails | undefined;\n}\n\n/** Tool result event for read tool */\nexport interface ReadToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"read\";\n\tdetails: ReadToolDetails | undefined;\n}\n\n/** Tool result event for edit tool */\nexport interface EditToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"edit\";\n\tdetails: EditToolDetails | undefined;\n}\n\n/** Tool result event for write tool */\nexport interface WriteToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"write\";\n\tdetails: undefined;\n}\n\n/** Tool result event for grep tool */\nexport interface GrepToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"grep\";\n\tdetails: GrepToolDetails | undefined;\n}\n\n/** Tool result event for find tool */\nexport interface FindToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"find\";\n\tdetails: FindToolDetails | undefined;\n}\n\n/** Tool result event for ls tool */\nexport interface LsToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"ls\";\n\tdetails: LsToolDetails | undefined;\n}\n\n/** Tool result event for custom/unknown tools */\nexport interface CustomToolResultEvent extends ToolResultEventBase {\n\ttoolName: string;\n\tdetails: unknown;\n}\n\n/**\n * Event data for tool_result event.\n * Fired after a tool is executed. Hooks can modify the result.\n * Use toolName to discriminate and get typed details.\n */\nexport type ToolResultEvent =\n\t| BashToolResultEvent\n\t| ReadToolResultEvent\n\t| EditToolResultEvent\n\t| WriteToolResultEvent\n\t| GrepToolResultEvent\n\t| FindToolResultEvent\n\t| LsToolResultEvent\n\t| CustomToolResultEvent;\n\n// Type guards for narrowing ToolResultEvent to specific tool types\nexport function isBashToolResult(e: ToolResultEvent): e is BashToolResultEvent {\n\treturn e.toolName === \"bash\";\n}\nexport function isReadToolResult(e: ToolResultEvent): e is ReadToolResultEvent {\n\treturn e.toolName === \"read\";\n}\nexport function isEditToolResult(e: ToolResultEvent): e is EditToolResultEvent {\n\treturn e.toolName === \"edit\";\n}\nexport function isWriteToolResult(e: ToolResultEvent): e is WriteToolResultEvent {\n\treturn e.toolName === \"write\";\n}\nexport function isGrepToolResult(e: ToolResultEvent): e is GrepToolResultEvent {\n\treturn e.toolName === \"grep\";\n}\nexport function isFindToolResult(e: ToolResultEvent): e is FindToolResultEvent {\n\treturn e.toolName === \"find\";\n}\nexport function isLsToolResult(e: ToolResultEvent): e is LsToolResultEvent {\n\treturn e.toolName === \"ls\";\n}\n\n/**\n * Union of all hook event types.\n */\nexport type HookEvent =\n\t| SessionEvent\n\t| ContextEvent\n\t| BeforeAgentStartEvent\n\t| AgentStartEvent\n\t| AgentEndEvent\n\t| TurnStartEvent\n\t| TurnEndEvent\n\t| ToolCallEvent\n\t| ToolResultEvent;\n\n// ============================================================================\n// Event Results\n// ============================================================================\n\n/**\n * Return type for context event handlers.\n * Allows hooks to modify messages before they're sent to the LLM.\n */\nexport interface ContextEventResult {\n\t/** Modified messages to send instead of the original */\n\tmessages?: Message[];\n}\n\n/**\n * Return type for tool_call event handlers.\n * Allows hooks to block tool execution.\n */\nexport interface ToolCallEventResult {\n\t/** If true, block the tool from executing */\n\tblock?: boolean;\n\t/** Reason for blocking (returned to LLM as error) */\n\treason?: string;\n}\n\n/**\n * Return type for tool_result event handlers.\n * Allows hooks to modify tool results.\n */\nexport interface ToolResultEventResult {\n\t/** Replacement content array (text and images) */\n\tcontent?: (TextContent | ImageContent)[];\n\t/** Replacement details */\n\tdetails?: unknown;\n\t/** Override isError flag */\n\tisError?: boolean;\n}\n\n/**\n * Return type for before_agent_start event handlers.\n * Allows hooks to inject context before the agent runs.\n */\nexport interface BeforeAgentStartEventResult {\n\t/** Message to inject into context (persisted to session, visible in TUI) */\n\tmessage?: Pick<HookMessage, \"customType\" | \"content\" | \"display\" | \"details\">;\n}\n\n/** Return type for session_before_switch handlers */\nexport interface SessionBeforeSwitchResult {\n\t/** If true, cancel the switch */\n\tcancel?: boolean;\n}\n\n/** Return type for session_before_branch handlers */\nexport interface SessionBeforeBranchResult {\n\t/**\n\t * If true, abort the branch entirely. No new session file is created,\n\t * conversation stays unchanged.\n\t */\n\tcancel?: boolean;\n\t/**\n\t * If true, the branch proceeds (new session file created, session state updated)\n\t * but the in-memory conversation is NOT rewound to the branch point.\n\t *\n\t * Use case: git-checkpoint hook that restores code state separately.\n\t * The hook handles state restoration itself, so it doesn't want the\n\t * agent's conversation to be rewound (which would lose recent context).\n\t *\n\t * - `cancel: true` → nothing happens, user stays in current session\n\t * - `skipConversationRestore: true` → branch happens, but messages stay as-is\n\t * - neither → branch happens AND messages rewind to branch point (default)\n\t */\n\tskipConversationRestore?: boolean;\n}\n\n/** Return type for session_before_compact handlers */\nexport interface SessionBeforeCompactResult {\n\t/** If true, cancel the compaction */\n\tcancel?: boolean;\n\t/** Custom compaction result - SessionManager adds id/parentId */\n\tcompaction?: CompactionResult;\n}\n\n/** Return type for session_before_tree handlers */\nexport interface SessionBeforeTreeResult {\n\t/** If true, cancel the navigation entirely */\n\tcancel?: boolean;\n\t/**\n\t * Custom summary (skips default summarizer).\n\t * Only used if preparation.userWantsSummary is true.\n\t */\n\tsummary?: {\n\t\tsummary: string;\n\t\tdetails?: unknown;\n\t};\n}\n\n// ============================================================================\n// Hook API\n// ============================================================================\n\n/**\n * Handler function type for each event.\n * Handlers can return R, undefined, or void (bare return statements).\n */\n// biome-ignore lint/suspicious/noConfusingVoidType: void allows bare return statements in handlers\nexport type HookHandler<E, R = undefined> = (event: E, ctx: HookContext) => Promise<R | void> | R | void;\n\nexport interface HookMessageRenderOptions {\n\t/** Whether the view is expanded */\n\texpanded: boolean;\n}\n\n/**\n * Renderer for hook messages.\n * Hooks register these to provide custom TUI rendering for their message types.\n */\nexport type HookMessageRenderer<T = unknown> = (\n\tmessage: HookMessage<T>,\n\toptions: HookMessageRenderOptions,\n\ttheme: Theme,\n) => Component | undefined;\n\n/**\n * Command registration options.\n */\nexport interface RegisteredCommand {\n\tname: string;\n\tdescription?: string;\n\n\thandler: (args: string, ctx: HookCommandContext) => Promise<void>;\n}\n\n/**\n * HookAPI passed to hook factory functions.\n * Hooks use pi.on() to subscribe to events and pi.sendMessage() to inject messages.\n */\nexport interface HookAPI {\n\t// Session events\n\ton(event: \"session_start\", handler: HookHandler<SessionStartEvent>): void;\n\ton(event: \"session_before_switch\", handler: HookHandler<SessionBeforeSwitchEvent, SessionBeforeSwitchResult>): void;\n\ton(event: \"session_switch\", handler: HookHandler<SessionSwitchEvent>): void;\n\ton(event: \"session_before_branch\", handler: HookHandler<SessionBeforeBranchEvent, SessionBeforeBranchResult>): void;\n\ton(event: \"session_branch\", handler: HookHandler<SessionBranchEvent>): void;\n\ton(\n\t\tevent: \"session_before_compact\",\n\t\thandler: HookHandler<SessionBeforeCompactEvent, SessionBeforeCompactResult>,\n\t): void;\n\ton(event: \"session_compact\", handler: HookHandler<SessionCompactEvent>): void;\n\ton(event: \"session_shutdown\", handler: HookHandler<SessionShutdownEvent>): void;\n\ton(event: \"session_before_tree\", handler: HookHandler<SessionBeforeTreeEvent, SessionBeforeTreeResult>): void;\n\ton(event: \"session_tree\", handler: HookHandler<SessionTreeEvent>): void;\n\n\t// Context and agent events\n\ton(event: \"context\", handler: HookHandler<ContextEvent, ContextEventResult>): void;\n\ton(event: \"before_agent_start\", handler: HookHandler<BeforeAgentStartEvent, BeforeAgentStartEventResult>): void;\n\ton(event: \"agent_start\", handler: HookHandler<AgentStartEvent>): void;\n\ton(event: \"agent_end\", handler: HookHandler<AgentEndEvent>): void;\n\ton(event: \"turn_start\", handler: HookHandler<TurnStartEvent>): void;\n\ton(event: \"turn_end\", handler: HookHandler<TurnEndEvent>): void;\n\ton(event: \"tool_call\", handler: HookHandler<ToolCallEvent, ToolCallEventResult>): void;\n\ton(event: \"tool_result\", handler: HookHandler<ToolResultEvent, ToolResultEventResult>): void;\n\n\t/**\n\t * Send a custom message to the session. Creates a CustomMessageEntry that\n\t * participates in LLM context and can be displayed in the TUI.\n\t *\n\t * Use this when you want the LLM to see the message content.\n\t * For hook state that should NOT be sent to the LLM, use appendEntry() instead.\n\t *\n\t * @param message - The message to send\n\t * @param message.customType - Identifier for your hook (used for filtering on reload)\n\t * @param message.content - Message content (string or TextContent/ImageContent array)\n\t * @param message.display - Whether to show in TUI (true = styled display, false = hidden)\n\t * @param message.details - Optional hook-specific metadata (not sent to LLM)\n\t * @param options.triggerTurn - If true and agent is idle, triggers a new LLM turn. Default: false.\n\t * If agent is streaming, message is queued and triggerTurn is ignored.\n\t * @param options.deliverAs - How to deliver when agent is streaming. Default: \"steer\".\n\t * - \"steer\": Interrupt mid-run, delivered after current tool execution.\n\t * - \"followUp\": Wait until agent finishes all work before delivery.\n\t */\n\tsendMessage<T = unknown>(\n\t\tmessage: Pick<HookMessage<T>, \"customType\" | \"content\" | \"display\" | \"details\">,\n\t\toptions?: { triggerTurn?: boolean; deliverAs?: \"steer\" | \"followUp\" },\n\t): void;\n\n\t/**\n\t * Append a custom entry to the session for hook state persistence.\n\t * Creates a CustomEntry that does NOT participate in LLM context.\n\t *\n\t * Use this to store hook-specific data that should persist across session reloads\n\t * but should NOT be sent to the LLM. On reload, scan session entries for your\n\t * customType to reconstruct hook state.\n\t *\n\t * For messages that SHOULD be sent to the LLM, use sendMessage() instead.\n\t *\n\t * @param customType - Identifier for your hook (used for filtering on reload)\n\t * @param data - Hook-specific data to persist (must be JSON-serializable)\n\t *\n\t * @example\n\t * // Store permission state\n\t * pi.appendEntry(\"permissions\", { level: \"full\", grantedAt: Date.now() });\n\t *\n\t * // On reload, reconstruct state from entries\n\t * pi.on(\"session\", async (event, ctx) => {\n\t * if (event.reason === \"start\") {\n\t * const entries = event.sessionManager.getEntries();\n\t * const myEntries = entries.filter(e => e.type === \"custom\" && e.customType === \"permissions\");\n\t * // Reconstruct state from myEntries...\n\t * }\n\t * });\n\t */\n\tappendEntry<T = unknown>(customType: string, data?: T): void;\n\n\t/**\n\t * Register a custom renderer for CustomMessageEntry with a specific customType.\n\t * The renderer is called when rendering the entry in the TUI.\n\t * Return nothing to use the default renderer.\n\t */\n\tregisterMessageRenderer<T = unknown>(customType: string, renderer: HookMessageRenderer<T>): void;\n\n\t/**\n\t * Register a custom slash command.\n\t * Handler receives HookCommandContext with session control methods.\n\t */\n\tregisterCommand(name: string, options: { description?: string; handler: RegisteredCommand[\"handler\"] }): void;\n\n\t/**\n\t * Execute a shell command and return stdout/stderr/code.\n\t * Supports timeout and abort signal.\n\t */\n\texec(command: string, args: string[], options?: ExecOptions): Promise<ExecResult>;\n}\n\n/**\n * Hook factory function type.\n * Hooks export a default function that receives the HookAPI.\n */\nexport type HookFactory = (pi: HookAPI) => void;\n\n// ============================================================================\n// Errors\n// ============================================================================\n\n/**\n * Error emitted when a hook fails.\n */\nexport interface HookError {\n\thookPath: string;\n\tevent: string;\n\terror: string;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/hooks/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA8dH,mEAAmE;AACnE,MAAM,UAAU,gBAAgB,CAAC,CAAkB,EAA4B;IAC9E,OAAO,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC;AAAA,CAC7B;AACD,MAAM,UAAU,gBAAgB,CAAC,CAAkB,EAA4B;IAC9E,OAAO,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC;AAAA,CAC7B;AACD,MAAM,UAAU,gBAAgB,CAAC,CAAkB,EAA4B;IAC9E,OAAO,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC;AAAA,CAC7B;AACD,MAAM,UAAU,iBAAiB,CAAC,CAAkB,EAA6B;IAChF,OAAO,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC;AAAA,CAC9B;AACD,MAAM,UAAU,gBAAgB,CAAC,CAAkB,EAA4B;IAC9E,OAAO,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC;AAAA,CAC7B;AACD,MAAM,UAAU,gBAAgB,CAAC,CAAkB,EAA4B;IAC9E,OAAO,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC;AAAA,CAC7B;AACD,MAAM,UAAU,cAAc,CAAC,CAAkB,EAA0B;IAC1E,OAAO,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC;AAAA,CAC3B","sourcesContent":["/**\n * Hook system types.\n *\n * Hooks are TypeScript modules that can subscribe to agent lifecycle events\n * and interact with the user via UI primitives.\n */\n\nimport type { AgentMessage } from \"@mariozechner/pi-agent-core\";\nimport type { ImageContent, Message, Model, TextContent, ToolResultMessage } from \"@mariozechner/pi-ai\";\nimport type { Component, TUI } from \"@mariozechner/pi-tui\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.js\";\nimport type { CompactionPreparation, CompactionResult } from \"../compaction/index.js\";\nimport type { ExecOptions, ExecResult } from \"../exec.js\";\nimport type { HookMessage } from \"../messages.js\";\nimport type { ModelRegistry } from \"../model-registry.js\";\nimport type {\n\tBranchSummaryEntry,\n\tCompactionEntry,\n\tReadonlySessionManager,\n\tSessionEntry,\n\tSessionManager,\n} from \"../session-manager.js\";\n\nimport type { EditToolDetails } from \"../tools/edit.js\";\nimport type {\n\tBashToolDetails,\n\tFindToolDetails,\n\tGrepToolDetails,\n\tLsToolDetails,\n\tReadToolDetails,\n} from \"../tools/index.js\";\n\n// Re-export for backward compatibility\nexport type { ExecOptions, ExecResult } from \"../exec.js\";\n\n/**\n * UI context for hooks to request interactive UI from the harness.\n * Each mode (interactive, RPC, print) provides its own implementation.\n */\nexport interface HookUIContext {\n\t/**\n\t * Show a selector and return the user's choice.\n\t * @param title - Title to display\n\t * @param options - Array of string options\n\t * @returns Selected option string, or null if cancelled\n\t */\n\tselect(title: string, options: string[]): Promise<string | undefined>;\n\n\t/**\n\t * Show a confirmation dialog.\n\t * @returns true if confirmed, false if cancelled\n\t */\n\tconfirm(title: string, message: string): Promise<boolean>;\n\n\t/**\n\t * Show a text input dialog.\n\t * @returns User input, or undefined if cancelled\n\t */\n\tinput(title: string, placeholder?: string): Promise<string | undefined>;\n\n\t/**\n\t * Show a notification to the user.\n\t */\n\tnotify(message: string, type?: \"info\" | \"warning\" | \"error\"): void;\n\n\t/**\n\t * Set status text in the footer/status bar.\n\t * Pass undefined as text to clear the status for this key.\n\t * Text can include ANSI escape codes for styling.\n\t * Note: Newlines, tabs, and carriage returns are replaced with spaces.\n\t * The combined status line is truncated to terminal width.\n\t * @param key - Unique key to identify this status (e.g., hook name)\n\t * @param text - Status text to display, or undefined to clear\n\t */\n\tsetStatus(key: string, text: string | undefined): void;\n\n\t/**\n\t * Show a custom component with keyboard focus.\n\t * The factory receives TUI, theme, and a done() callback to close the component.\n\t * Can be async for fire-and-forget work (don't await the work, just start it).\n\t *\n\t * @param factory - Function that creates the component. Call done() when finished.\n\t * @returns Promise that resolves with the value passed to done()\n\t *\n\t * @example\n\t * // Sync factory\n\t * const result = await ctx.ui.custom((tui, theme, done) => {\n\t * const component = new MyComponent(tui, theme);\n\t * component.onFinish = (value) => done(value);\n\t * return component;\n\t * });\n\t *\n\t * // Async factory with fire-and-forget work\n\t * const result = await ctx.ui.custom(async (tui, theme, done) => {\n\t * const loader = new CancellableLoader(tui, theme.fg(\"accent\"), theme.fg(\"muted\"), \"Working...\");\n\t * loader.onAbort = () => done(null);\n\t * doWork(loader.signal).then(done); // Don't await - fire and forget\n\t * return loader;\n\t * });\n\t */\n\tcustom<T>(\n\t\tfactory: (\n\t\t\ttui: TUI,\n\t\t\ttheme: Theme,\n\t\t\tdone: (result: T) => void,\n\t\t) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,\n\t): Promise<T>;\n\n\t/**\n\t * Set the text in the core input editor.\n\t * Use this to pre-fill the input box with generated content (e.g., prompt templates, extracted questions).\n\t * @param text - Text to set in the editor\n\t */\n\tsetEditorText(text: string): void;\n\n\t/**\n\t * Get the current text from the core input editor.\n\t * @returns Current editor text\n\t */\n\tgetEditorText(): string;\n\n\t/**\n\t * Show a multi-line editor for text editing.\n\t * Supports Ctrl+G to open external editor ($VISUAL or $EDITOR).\n\t * @param title - Title describing what is being edited\n\t * @param prefill - Optional initial text\n\t * @returns Edited text, or undefined if cancelled (Escape)\n\t */\n\teditor(title: string, prefill?: string): Promise<string | undefined>;\n\n\t/**\n\t * Get the current theme for styling text with ANSI codes.\n\t * Use theme.fg() and theme.bg() to style status text.\n\t *\n\t * @example\n\t * const theme = ctx.ui.theme;\n\t * ctx.ui.setStatus(\"my-hook\", theme.fg(\"success\", \"✓\") + \" Ready\");\n\t */\n\treadonly theme: Theme;\n}\n\n/**\n * Context passed to hook event handlers.\n * For command handlers, see HookCommandContext which extends this with session control methods.\n */\nexport interface HookContext {\n\t/** UI methods for user interaction */\n\tui: HookUIContext;\n\t/** Whether UI is available (false in print mode) */\n\thasUI: boolean;\n\t/** Current working directory */\n\tcwd: string;\n\t/** Session manager (read-only) - use pi.sendMessage()/pi.appendEntry() for writes */\n\tsessionManager: ReadonlySessionManager;\n\t/** Model registry - use for API key resolution and model retrieval */\n\tmodelRegistry: ModelRegistry;\n\t/** Current model (may be undefined if no model is selected yet) */\n\tmodel: Model<any> | undefined;\n\t/** Whether the agent is idle (not streaming) */\n\tisIdle(): boolean;\n\t/** Abort the current agent operation (fire-and-forget, does not wait) */\n\tabort(): void;\n\t/** Whether there are queued messages waiting to be processed */\n\thasQueuedMessages(): boolean;\n}\n\n/**\n * Extended context for slash command handlers.\n * Includes session control methods that are only safe in user-initiated commands.\n *\n * These methods are not available in event handlers because they can cause\n * deadlocks when called from within the agent loop (e.g., tool_call, context events).\n */\nexport interface HookCommandContext extends HookContext {\n\t/** Wait for the agent to finish streaming */\n\twaitForIdle(): Promise<void>;\n\n\t/**\n\t * Start a new session, optionally with a setup callback to initialize it.\n\t * The setup callback receives a writable SessionManager for the new session.\n\t *\n\t * @param options.parentSession - Path to parent session for lineage tracking\n\t * @param options.setup - Async callback to initialize the new session (e.g., append messages)\n\t * @returns Object with `cancelled: true` if a hook cancelled the new session\n\t *\n\t * @example\n\t * // Handoff: summarize current session and start fresh with context\n\t * await ctx.newSession({\n\t * parentSession: ctx.sessionManager.getSessionFile(),\n\t * setup: async (sm) => {\n\t * sm.appendMessage({ role: \"user\", content: [{ type: \"text\", text: summary }] });\n\t * }\n\t * });\n\t */\n\tnewSession(options?: {\n\t\tparentSession?: string;\n\t\tsetup?: (sessionManager: SessionManager) => Promise<void>;\n\t}): Promise<{ cancelled: boolean }>;\n\n\t/**\n\t * Branch from a specific entry, creating a new session file.\n\t *\n\t * @param entryId - ID of the entry to branch from\n\t * @returns Object with `cancelled: true` if a hook cancelled the branch\n\t */\n\tbranch(entryId: string): Promise<{ cancelled: boolean }>;\n\n\t/**\n\t * Navigate to a different point in the session tree (in-place).\n\t *\n\t * @param targetId - ID of the entry to navigate to\n\t * @param options.summarize - Whether to summarize the abandoned branch\n\t * @returns Object with `cancelled: true` if a hook cancelled the navigation\n\t */\n\tnavigateTree(targetId: string, options?: { summarize?: boolean }): Promise<{ cancelled: boolean }>;\n}\n\n// ============================================================================\n// Session Events\n// ============================================================================\n\n/** Fired on initial session load */\nexport interface SessionStartEvent {\n\ttype: \"session_start\";\n}\n\n/** Fired before switching to another session (can be cancelled) */\nexport interface SessionBeforeSwitchEvent {\n\ttype: \"session_before_switch\";\n\t/** Reason for the switch */\n\treason: \"new\" | \"resume\";\n\t/** Session file we're switching to (only for \"resume\") */\n\ttargetSessionFile?: string;\n}\n\n/** Fired after switching to another session */\nexport interface SessionSwitchEvent {\n\ttype: \"session_switch\";\n\t/** Reason for the switch */\n\treason: \"new\" | \"resume\";\n\t/** Session file we came from */\n\tpreviousSessionFile: string | undefined;\n}\n\n/** Fired before branching a session (can be cancelled) */\nexport interface SessionBeforeBranchEvent {\n\ttype: \"session_before_branch\";\n\t/** ID of the entry to branch from */\n\tentryId: string;\n}\n\n/** Fired after branching a session */\nexport interface SessionBranchEvent {\n\ttype: \"session_branch\";\n\tpreviousSessionFile: string | undefined;\n}\n\n/** Fired before context compaction (can be cancelled or customized) */\nexport interface SessionBeforeCompactEvent {\n\ttype: \"session_before_compact\";\n\t/** Compaction preparation with messages to summarize, file ops, previous summary, etc. */\n\tpreparation: CompactionPreparation;\n\t/** Branch entries (root to current leaf). Use to inspect custom state or previous compactions. */\n\tbranchEntries: SessionEntry[];\n\t/** Optional user-provided instructions for the summary */\n\tcustomInstructions?: string;\n\t/** Abort signal - hooks should pass this to LLM calls and check it periodically */\n\tsignal: AbortSignal;\n}\n\n/** Fired after context compaction */\nexport interface SessionCompactEvent {\n\ttype: \"session_compact\";\n\tcompactionEntry: CompactionEntry;\n\t/** Whether the compaction entry was provided by a hook */\n\tfromHook: boolean;\n}\n\n/** Fired on process exit (SIGINT/SIGTERM) */\nexport interface SessionShutdownEvent {\n\ttype: \"session_shutdown\";\n}\n\n/** Preparation data for tree navigation (used by session_before_tree event) */\nexport interface TreePreparation {\n\t/** Node being switched to */\n\ttargetId: string;\n\t/** Current active leaf (being abandoned), null if no current position */\n\toldLeafId: string | null;\n\t/** Common ancestor of target and old leaf, null if no common ancestor */\n\tcommonAncestorId: string | null;\n\t/** Entries to summarize (old leaf back to common ancestor or compaction) */\n\tentriesToSummarize: SessionEntry[];\n\t/** Whether user chose to summarize */\n\tuserWantsSummary: boolean;\n}\n\n/** Fired before navigating to a different node in the session tree (can be cancelled) */\nexport interface SessionBeforeTreeEvent {\n\ttype: \"session_before_tree\";\n\t/** Preparation data for the navigation */\n\tpreparation: TreePreparation;\n\t/** Abort signal - honors Escape during summarization (model available via ctx.model) */\n\tsignal: AbortSignal;\n}\n\n/** Fired after navigating to a different node in the session tree */\nexport interface SessionTreeEvent {\n\ttype: \"session_tree\";\n\t/** The new active leaf, null if navigated to before first entry */\n\tnewLeafId: string | null;\n\t/** Previous active leaf, null if there was no position */\n\toldLeafId: string | null;\n\t/** Branch summary entry if one was created */\n\tsummaryEntry?: BranchSummaryEntry;\n\t/** Whether summary came from hook */\n\tfromHook?: boolean;\n}\n\n/** Union of all session event types */\nexport type SessionEvent =\n\t| SessionStartEvent\n\t| SessionBeforeSwitchEvent\n\t| SessionSwitchEvent\n\t| SessionBeforeBranchEvent\n\t| SessionBranchEvent\n\t| SessionBeforeCompactEvent\n\t| SessionCompactEvent\n\t| SessionShutdownEvent\n\t| SessionBeforeTreeEvent\n\t| SessionTreeEvent;\n\n/**\n * Event data for context event.\n * Fired before each LLM call, allowing hooks to modify context non-destructively.\n * Original session messages are NOT modified - only the messages sent to the LLM are affected.\n */\nexport interface ContextEvent {\n\ttype: \"context\";\n\t/** Messages about to be sent to the LLM (deep copy, safe to modify) */\n\tmessages: AgentMessage[];\n}\n\n/**\n * Event data for before_agent_start event.\n * Fired after user submits a prompt but before the agent loop starts.\n * Allows hooks to inject context that will be persisted and visible in TUI.\n */\nexport interface BeforeAgentStartEvent {\n\ttype: \"before_agent_start\";\n\t/** The user's prompt text */\n\tprompt: string;\n\t/** Any images attached to the prompt */\n\timages?: ImageContent[];\n}\n\n/**\n * Event data for agent_start event.\n * Fired when an agent loop starts (once per user prompt).\n */\nexport interface AgentStartEvent {\n\ttype: \"agent_start\";\n}\n\n/**\n * Event data for agent_end event.\n */\nexport interface AgentEndEvent {\n\ttype: \"agent_end\";\n\tmessages: AgentMessage[];\n}\n\n/**\n * Event data for turn_start event.\n */\nexport interface TurnStartEvent {\n\ttype: \"turn_start\";\n\tturnIndex: number;\n\ttimestamp: number;\n}\n\n/**\n * Event data for turn_end event.\n */\nexport interface TurnEndEvent {\n\ttype: \"turn_end\";\n\tturnIndex: number;\n\tmessage: AgentMessage;\n\ttoolResults: ToolResultMessage[];\n}\n\n/**\n * Event data for tool_call event.\n * Fired before a tool is executed. Hooks can block execution.\n */\nexport interface ToolCallEvent {\n\ttype: \"tool_call\";\n\t/** Tool name (e.g., \"bash\", \"edit\", \"write\") */\n\ttoolName: string;\n\t/** Tool call ID */\n\ttoolCallId: string;\n\t/** Tool input parameters */\n\tinput: Record<string, unknown>;\n}\n\n/**\n * Base interface for tool_result events.\n */\ninterface ToolResultEventBase {\n\ttype: \"tool_result\";\n\t/** Tool call ID */\n\ttoolCallId: string;\n\t/** Tool input parameters */\n\tinput: Record<string, unknown>;\n\t/** Full content array (text and images) */\n\tcontent: (TextContent | ImageContent)[];\n\t/** Whether the tool execution was an error */\n\tisError: boolean;\n}\n\n/** Tool result event for bash tool */\nexport interface BashToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"bash\";\n\tdetails: BashToolDetails | undefined;\n}\n\n/** Tool result event for read tool */\nexport interface ReadToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"read\";\n\tdetails: ReadToolDetails | undefined;\n}\n\n/** Tool result event for edit tool */\nexport interface EditToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"edit\";\n\tdetails: EditToolDetails | undefined;\n}\n\n/** Tool result event for write tool */\nexport interface WriteToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"write\";\n\tdetails: undefined;\n}\n\n/** Tool result event for grep tool */\nexport interface GrepToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"grep\";\n\tdetails: GrepToolDetails | undefined;\n}\n\n/** Tool result event for find tool */\nexport interface FindToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"find\";\n\tdetails: FindToolDetails | undefined;\n}\n\n/** Tool result event for ls tool */\nexport interface LsToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"ls\";\n\tdetails: LsToolDetails | undefined;\n}\n\n/** Tool result event for custom/unknown tools */\nexport interface CustomToolResultEvent extends ToolResultEventBase {\n\ttoolName: string;\n\tdetails: unknown;\n}\n\n/**\n * Event data for tool_result event.\n * Fired after a tool is executed. Hooks can modify the result.\n * Use toolName to discriminate and get typed details.\n */\nexport type ToolResultEvent =\n\t| BashToolResultEvent\n\t| ReadToolResultEvent\n\t| EditToolResultEvent\n\t| WriteToolResultEvent\n\t| GrepToolResultEvent\n\t| FindToolResultEvent\n\t| LsToolResultEvent\n\t| CustomToolResultEvent;\n\n// Type guards for narrowing ToolResultEvent to specific tool types\nexport function isBashToolResult(e: ToolResultEvent): e is BashToolResultEvent {\n\treturn e.toolName === \"bash\";\n}\nexport function isReadToolResult(e: ToolResultEvent): e is ReadToolResultEvent {\n\treturn e.toolName === \"read\";\n}\nexport function isEditToolResult(e: ToolResultEvent): e is EditToolResultEvent {\n\treturn e.toolName === \"edit\";\n}\nexport function isWriteToolResult(e: ToolResultEvent): e is WriteToolResultEvent {\n\treturn e.toolName === \"write\";\n}\nexport function isGrepToolResult(e: ToolResultEvent): e is GrepToolResultEvent {\n\treturn e.toolName === \"grep\";\n}\nexport function isFindToolResult(e: ToolResultEvent): e is FindToolResultEvent {\n\treturn e.toolName === \"find\";\n}\nexport function isLsToolResult(e: ToolResultEvent): e is LsToolResultEvent {\n\treturn e.toolName === \"ls\";\n}\n\n/**\n * Union of all hook event types.\n */\nexport type HookEvent =\n\t| SessionEvent\n\t| ContextEvent\n\t| BeforeAgentStartEvent\n\t| AgentStartEvent\n\t| AgentEndEvent\n\t| TurnStartEvent\n\t| TurnEndEvent\n\t| ToolCallEvent\n\t| ToolResultEvent;\n\n// ============================================================================\n// Event Results\n// ============================================================================\n\n/**\n * Return type for context event handlers.\n * Allows hooks to modify messages before they're sent to the LLM.\n */\nexport interface ContextEventResult {\n\t/** Modified messages to send instead of the original */\n\tmessages?: Message[];\n}\n\n/**\n * Return type for tool_call event handlers.\n * Allows hooks to block tool execution.\n */\nexport interface ToolCallEventResult {\n\t/** If true, block the tool from executing */\n\tblock?: boolean;\n\t/** Reason for blocking (returned to LLM as error) */\n\treason?: string;\n}\n\n/**\n * Return type for tool_result event handlers.\n * Allows hooks to modify tool results.\n */\nexport interface ToolResultEventResult {\n\t/** Replacement content array (text and images) */\n\tcontent?: (TextContent | ImageContent)[];\n\t/** Replacement details */\n\tdetails?: unknown;\n\t/** Override isError flag */\n\tisError?: boolean;\n}\n\n/**\n * Return type for before_agent_start event handlers.\n * Allows hooks to inject context before the agent runs.\n */\nexport interface BeforeAgentStartEventResult {\n\t/** Message to inject into context (persisted to session, visible in TUI) */\n\tmessage?: Pick<HookMessage, \"customType\" | \"content\" | \"display\" | \"details\">;\n}\n\n/** Return type for session_before_switch handlers */\nexport interface SessionBeforeSwitchResult {\n\t/** If true, cancel the switch */\n\tcancel?: boolean;\n}\n\n/** Return type for session_before_branch handlers */\nexport interface SessionBeforeBranchResult {\n\t/**\n\t * If true, abort the branch entirely. No new session file is created,\n\t * conversation stays unchanged.\n\t */\n\tcancel?: boolean;\n\t/**\n\t * If true, the branch proceeds (new session file created, session state updated)\n\t * but the in-memory conversation is NOT rewound to the branch point.\n\t *\n\t * Use case: git-checkpoint hook that restores code state separately.\n\t * The hook handles state restoration itself, so it doesn't want the\n\t * agent's conversation to be rewound (which would lose recent context).\n\t *\n\t * - `cancel: true` → nothing happens, user stays in current session\n\t * - `skipConversationRestore: true` → branch happens, but messages stay as-is\n\t * - neither → branch happens AND messages rewind to branch point (default)\n\t */\n\tskipConversationRestore?: boolean;\n}\n\n/** Return type for session_before_compact handlers */\nexport interface SessionBeforeCompactResult {\n\t/** If true, cancel the compaction */\n\tcancel?: boolean;\n\t/** Custom compaction result - SessionManager adds id/parentId */\n\tcompaction?: CompactionResult;\n}\n\n/** Return type for session_before_tree handlers */\nexport interface SessionBeforeTreeResult {\n\t/** If true, cancel the navigation entirely */\n\tcancel?: boolean;\n\t/**\n\t * Custom summary (skips default summarizer).\n\t * Only used if preparation.userWantsSummary is true.\n\t */\n\tsummary?: {\n\t\tsummary: string;\n\t\tdetails?: unknown;\n\t};\n}\n\n// ============================================================================\n// Hook API\n// ============================================================================\n\n/**\n * Handler function type for each event.\n * Handlers can return R, undefined, or void (bare return statements).\n */\n// biome-ignore lint/suspicious/noConfusingVoidType: void allows bare return statements in handlers\nexport type HookHandler<E, R = undefined> = (event: E, ctx: HookContext) => Promise<R | void> | R | void;\n\nexport interface HookMessageRenderOptions {\n\t/** Whether the view is expanded */\n\texpanded: boolean;\n}\n\n/**\n * Renderer for hook messages.\n * Hooks register these to provide custom TUI rendering for their message types.\n */\nexport type HookMessageRenderer<T = unknown> = (\n\tmessage: HookMessage<T>,\n\toptions: HookMessageRenderOptions,\n\ttheme: Theme,\n) => Component | undefined;\n\n/**\n * Command registration options.\n */\nexport interface RegisteredCommand {\n\tname: string;\n\tdescription?: string;\n\n\thandler: (args: string, ctx: HookCommandContext) => Promise<void>;\n}\n\n/**\n * HookAPI passed to hook factory functions.\n * Hooks use pi.on() to subscribe to events and pi.sendMessage() to inject messages.\n */\nexport interface HookAPI {\n\t// Session events\n\ton(event: \"session_start\", handler: HookHandler<SessionStartEvent>): void;\n\ton(event: \"session_before_switch\", handler: HookHandler<SessionBeforeSwitchEvent, SessionBeforeSwitchResult>): void;\n\ton(event: \"session_switch\", handler: HookHandler<SessionSwitchEvent>): void;\n\ton(event: \"session_before_branch\", handler: HookHandler<SessionBeforeBranchEvent, SessionBeforeBranchResult>): void;\n\ton(event: \"session_branch\", handler: HookHandler<SessionBranchEvent>): void;\n\ton(\n\t\tevent: \"session_before_compact\",\n\t\thandler: HookHandler<SessionBeforeCompactEvent, SessionBeforeCompactResult>,\n\t): void;\n\ton(event: \"session_compact\", handler: HookHandler<SessionCompactEvent>): void;\n\ton(event: \"session_shutdown\", handler: HookHandler<SessionShutdownEvent>): void;\n\ton(event: \"session_before_tree\", handler: HookHandler<SessionBeforeTreeEvent, SessionBeforeTreeResult>): void;\n\ton(event: \"session_tree\", handler: HookHandler<SessionTreeEvent>): void;\n\n\t// Context and agent events\n\ton(event: \"context\", handler: HookHandler<ContextEvent, ContextEventResult>): void;\n\ton(event: \"before_agent_start\", handler: HookHandler<BeforeAgentStartEvent, BeforeAgentStartEventResult>): void;\n\ton(event: \"agent_start\", handler: HookHandler<AgentStartEvent>): void;\n\ton(event: \"agent_end\", handler: HookHandler<AgentEndEvent>): void;\n\ton(event: \"turn_start\", handler: HookHandler<TurnStartEvent>): void;\n\ton(event: \"turn_end\", handler: HookHandler<TurnEndEvent>): void;\n\ton(event: \"tool_call\", handler: HookHandler<ToolCallEvent, ToolCallEventResult>): void;\n\ton(event: \"tool_result\", handler: HookHandler<ToolResultEvent, ToolResultEventResult>): void;\n\n\t/**\n\t * Send a custom message to the session. Creates a CustomMessageEntry that\n\t * participates in LLM context and can be displayed in the TUI.\n\t *\n\t * Use this when you want the LLM to see the message content.\n\t * For hook state that should NOT be sent to the LLM, use appendEntry() instead.\n\t *\n\t * @param message - The message to send\n\t * @param message.customType - Identifier for your hook (used for filtering on reload)\n\t * @param message.content - Message content (string or TextContent/ImageContent array)\n\t * @param message.display - Whether to show in TUI (true = styled display, false = hidden)\n\t * @param message.details - Optional hook-specific metadata (not sent to LLM)\n\t * @param triggerTurn - If true and agent is idle, triggers a new LLM turn. Default: false.\n\t * If agent is streaming, message is queued and triggerTurn is ignored.\n\t */\n\tsendMessage<T = unknown>(\n\t\tmessage: Pick<HookMessage<T>, \"customType\" | \"content\" | \"display\" | \"details\">,\n\t\ttriggerTurn?: boolean,\n\t): void;\n\n\t/**\n\t * Append a custom entry to the session for hook state persistence.\n\t * Creates a CustomEntry that does NOT participate in LLM context.\n\t *\n\t * Use this to store hook-specific data that should persist across session reloads\n\t * but should NOT be sent to the LLM. On reload, scan session entries for your\n\t * customType to reconstruct hook state.\n\t *\n\t * For messages that SHOULD be sent to the LLM, use sendMessage() instead.\n\t *\n\t * @param customType - Identifier for your hook (used for filtering on reload)\n\t * @param data - Hook-specific data to persist (must be JSON-serializable)\n\t *\n\t * @example\n\t * // Store permission state\n\t * pi.appendEntry(\"permissions\", { level: \"full\", grantedAt: Date.now() });\n\t *\n\t * // On reload, reconstruct state from entries\n\t * pi.on(\"session\", async (event, ctx) => {\n\t * if (event.reason === \"start\") {\n\t * const entries = event.sessionManager.getEntries();\n\t * const myEntries = entries.filter(e => e.type === \"custom\" && e.customType === \"permissions\");\n\t * // Reconstruct state from myEntries...\n\t * }\n\t * });\n\t */\n\tappendEntry<T = unknown>(customType: string, data?: T): void;\n\n\t/**\n\t * Register a custom renderer for CustomMessageEntry with a specific customType.\n\t * The renderer is called when rendering the entry in the TUI.\n\t * Return nothing to use the default renderer.\n\t */\n\tregisterMessageRenderer<T = unknown>(customType: string, renderer: HookMessageRenderer<T>): void;\n\n\t/**\n\t * Register a custom slash command.\n\t * Handler receives HookCommandContext with session control methods.\n\t */\n\tregisterCommand(name: string, options: { description?: string; handler: RegisteredCommand[\"handler\"] }): void;\n\n\t/**\n\t * Execute a shell command and return stdout/stderr/code.\n\t * Supports timeout and abort signal.\n\t */\n\texec(command: string, args: string[], options?: ExecOptions): Promise<ExecResult>;\n}\n\n/**\n * Hook factory function type.\n * Hooks export a default function that receives the HookAPI.\n */\nexport type HookFactory = (pi: HookAPI) => void;\n\n// ============================================================================\n// Errors\n// ============================================================================\n\n/**\n * Error emitted when a hook fails.\n */\nexport interface HookError {\n\thookPath: string;\n\tevent: string;\n\terror: string;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/hooks/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA8dH,mEAAmE;AACnE,MAAM,UAAU,gBAAgB,CAAC,CAAkB,EAA4B;IAC9E,OAAO,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC;AAAA,CAC7B;AACD,MAAM,UAAU,gBAAgB,CAAC,CAAkB,EAA4B;IAC9E,OAAO,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC;AAAA,CAC7B;AACD,MAAM,UAAU,gBAAgB,CAAC,CAAkB,EAA4B;IAC9E,OAAO,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC;AAAA,CAC7B;AACD,MAAM,UAAU,iBAAiB,CAAC,CAAkB,EAA6B;IAChF,OAAO,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC;AAAA,CAC9B;AACD,MAAM,UAAU,gBAAgB,CAAC,CAAkB,EAA4B;IAC9E,OAAO,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC;AAAA,CAC7B;AACD,MAAM,UAAU,gBAAgB,CAAC,CAAkB,EAA4B;IAC9E,OAAO,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC;AAAA,CAC7B;AACD,MAAM,UAAU,cAAc,CAAC,CAAkB,EAA0B;IAC1E,OAAO,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC;AAAA,CAC3B","sourcesContent":["/**\n * Hook system types.\n *\n * Hooks are TypeScript modules that can subscribe to agent lifecycle events\n * and interact with the user via UI primitives.\n */\n\nimport type { AgentMessage } from \"@mariozechner/pi-agent-core\";\nimport type { ImageContent, Message, Model, TextContent, ToolResultMessage } from \"@mariozechner/pi-ai\";\nimport type { Component, TUI } from \"@mariozechner/pi-tui\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.js\";\nimport type { CompactionPreparation, CompactionResult } from \"../compaction/index.js\";\nimport type { ExecOptions, ExecResult } from \"../exec.js\";\nimport type { HookMessage } from \"../messages.js\";\nimport type { ModelRegistry } from \"../model-registry.js\";\nimport type {\n\tBranchSummaryEntry,\n\tCompactionEntry,\n\tReadonlySessionManager,\n\tSessionEntry,\n\tSessionManager,\n} from \"../session-manager.js\";\n\nimport type { EditToolDetails } from \"../tools/edit.js\";\nimport type {\n\tBashToolDetails,\n\tFindToolDetails,\n\tGrepToolDetails,\n\tLsToolDetails,\n\tReadToolDetails,\n} from \"../tools/index.js\";\n\n// Re-export for backward compatibility\nexport type { ExecOptions, ExecResult } from \"../exec.js\";\n\n/**\n * UI context for hooks to request interactive UI from the harness.\n * Each mode (interactive, RPC, print) provides its own implementation.\n */\nexport interface HookUIContext {\n\t/**\n\t * Show a selector and return the user's choice.\n\t * @param title - Title to display\n\t * @param options - Array of string options\n\t * @returns Selected option string, or null if cancelled\n\t */\n\tselect(title: string, options: string[]): Promise<string | undefined>;\n\n\t/**\n\t * Show a confirmation dialog.\n\t * @returns true if confirmed, false if cancelled\n\t */\n\tconfirm(title: string, message: string): Promise<boolean>;\n\n\t/**\n\t * Show a text input dialog.\n\t * @returns User input, or undefined if cancelled\n\t */\n\tinput(title: string, placeholder?: string): Promise<string | undefined>;\n\n\t/**\n\t * Show a notification to the user.\n\t */\n\tnotify(message: string, type?: \"info\" | \"warning\" | \"error\"): void;\n\n\t/**\n\t * Set status text in the footer/status bar.\n\t * Pass undefined as text to clear the status for this key.\n\t * Text can include ANSI escape codes for styling.\n\t * Note: Newlines, tabs, and carriage returns are replaced with spaces.\n\t * The combined status line is truncated to terminal width.\n\t * @param key - Unique key to identify this status (e.g., hook name)\n\t * @param text - Status text to display, or undefined to clear\n\t */\n\tsetStatus(key: string, text: string | undefined): void;\n\n\t/**\n\t * Show a custom component with keyboard focus.\n\t * The factory receives TUI, theme, and a done() callback to close the component.\n\t * Can be async for fire-and-forget work (don't await the work, just start it).\n\t *\n\t * @param factory - Function that creates the component. Call done() when finished.\n\t * @returns Promise that resolves with the value passed to done()\n\t *\n\t * @example\n\t * // Sync factory\n\t * const result = await ctx.ui.custom((tui, theme, done) => {\n\t * const component = new MyComponent(tui, theme);\n\t * component.onFinish = (value) => done(value);\n\t * return component;\n\t * });\n\t *\n\t * // Async factory with fire-and-forget work\n\t * const result = await ctx.ui.custom(async (tui, theme, done) => {\n\t * const loader = new CancellableLoader(tui, theme.fg(\"accent\"), theme.fg(\"muted\"), \"Working...\");\n\t * loader.onAbort = () => done(null);\n\t * doWork(loader.signal).then(done); // Don't await - fire and forget\n\t * return loader;\n\t * });\n\t */\n\tcustom<T>(\n\t\tfactory: (\n\t\t\ttui: TUI,\n\t\t\ttheme: Theme,\n\t\t\tdone: (result: T) => void,\n\t\t) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,\n\t): Promise<T>;\n\n\t/**\n\t * Set the text in the core input editor.\n\t * Use this to pre-fill the input box with generated content (e.g., prompt templates, extracted questions).\n\t * @param text - Text to set in the editor\n\t */\n\tsetEditorText(text: string): void;\n\n\t/**\n\t * Get the current text from the core input editor.\n\t * @returns Current editor text\n\t */\n\tgetEditorText(): string;\n\n\t/**\n\t * Show a multi-line editor for text editing.\n\t * Supports Ctrl+G to open external editor ($VISUAL or $EDITOR).\n\t * @param title - Title describing what is being edited\n\t * @param prefill - Optional initial text\n\t * @returns Edited text, or undefined if cancelled (Escape)\n\t */\n\teditor(title: string, prefill?: string): Promise<string | undefined>;\n\n\t/**\n\t * Get the current theme for styling text with ANSI codes.\n\t * Use theme.fg() and theme.bg() to style status text.\n\t *\n\t * @example\n\t * const theme = ctx.ui.theme;\n\t * ctx.ui.setStatus(\"my-hook\", theme.fg(\"success\", \"✓\") + \" Ready\");\n\t */\n\treadonly theme: Theme;\n}\n\n/**\n * Context passed to hook event handlers.\n * For command handlers, see HookCommandContext which extends this with session control methods.\n */\nexport interface HookContext {\n\t/** UI methods for user interaction */\n\tui: HookUIContext;\n\t/** Whether UI is available (false in print mode) */\n\thasUI: boolean;\n\t/** Current working directory */\n\tcwd: string;\n\t/** Session manager (read-only) - use pi.sendMessage()/pi.appendEntry() for writes */\n\tsessionManager: ReadonlySessionManager;\n\t/** Model registry - use for API key resolution and model retrieval */\n\tmodelRegistry: ModelRegistry;\n\t/** Current model (may be undefined if no model is selected yet) */\n\tmodel: Model<any> | undefined;\n\t/** Whether the agent is idle (not streaming) */\n\tisIdle(): boolean;\n\t/** Abort the current agent operation (fire-and-forget, does not wait) */\n\tabort(): void;\n\t/** Whether there are queued messages waiting to be processed */\n\thasPendingMessages(): boolean;\n}\n\n/**\n * Extended context for slash command handlers.\n * Includes session control methods that are only safe in user-initiated commands.\n *\n * These methods are not available in event handlers because they can cause\n * deadlocks when called from within the agent loop (e.g., tool_call, context events).\n */\nexport interface HookCommandContext extends HookContext {\n\t/** Wait for the agent to finish streaming */\n\twaitForIdle(): Promise<void>;\n\n\t/**\n\t * Start a new session, optionally with a setup callback to initialize it.\n\t * The setup callback receives a writable SessionManager for the new session.\n\t *\n\t * @param options.parentSession - Path to parent session for lineage tracking\n\t * @param options.setup - Async callback to initialize the new session (e.g., append messages)\n\t * @returns Object with `cancelled: true` if a hook cancelled the new session\n\t *\n\t * @example\n\t * // Handoff: summarize current session and start fresh with context\n\t * await ctx.newSession({\n\t * parentSession: ctx.sessionManager.getSessionFile(),\n\t * setup: async (sm) => {\n\t * sm.appendMessage({ role: \"user\", content: [{ type: \"text\", text: summary }] });\n\t * }\n\t * });\n\t */\n\tnewSession(options?: {\n\t\tparentSession?: string;\n\t\tsetup?: (sessionManager: SessionManager) => Promise<void>;\n\t}): Promise<{ cancelled: boolean }>;\n\n\t/**\n\t * Branch from a specific entry, creating a new session file.\n\t *\n\t * @param entryId - ID of the entry to branch from\n\t * @returns Object with `cancelled: true` if a hook cancelled the branch\n\t */\n\tbranch(entryId: string): Promise<{ cancelled: boolean }>;\n\n\t/**\n\t * Navigate to a different point in the session tree (in-place).\n\t *\n\t * @param targetId - ID of the entry to navigate to\n\t * @param options.summarize - Whether to summarize the abandoned branch\n\t * @returns Object with `cancelled: true` if a hook cancelled the navigation\n\t */\n\tnavigateTree(targetId: string, options?: { summarize?: boolean }): Promise<{ cancelled: boolean }>;\n}\n\n// ============================================================================\n// Session Events\n// ============================================================================\n\n/** Fired on initial session load */\nexport interface SessionStartEvent {\n\ttype: \"session_start\";\n}\n\n/** Fired before switching to another session (can be cancelled) */\nexport interface SessionBeforeSwitchEvent {\n\ttype: \"session_before_switch\";\n\t/** Reason for the switch */\n\treason: \"new\" | \"resume\";\n\t/** Session file we're switching to (only for \"resume\") */\n\ttargetSessionFile?: string;\n}\n\n/** Fired after switching to another session */\nexport interface SessionSwitchEvent {\n\ttype: \"session_switch\";\n\t/** Reason for the switch */\n\treason: \"new\" | \"resume\";\n\t/** Session file we came from */\n\tpreviousSessionFile: string | undefined;\n}\n\n/** Fired before branching a session (can be cancelled) */\nexport interface SessionBeforeBranchEvent {\n\ttype: \"session_before_branch\";\n\t/** ID of the entry to branch from */\n\tentryId: string;\n}\n\n/** Fired after branching a session */\nexport interface SessionBranchEvent {\n\ttype: \"session_branch\";\n\tpreviousSessionFile: string | undefined;\n}\n\n/** Fired before context compaction (can be cancelled or customized) */\nexport interface SessionBeforeCompactEvent {\n\ttype: \"session_before_compact\";\n\t/** Compaction preparation with messages to summarize, file ops, previous summary, etc. */\n\tpreparation: CompactionPreparation;\n\t/** Branch entries (root to current leaf). Use to inspect custom state or previous compactions. */\n\tbranchEntries: SessionEntry[];\n\t/** Optional user-provided instructions for the summary */\n\tcustomInstructions?: string;\n\t/** Abort signal - hooks should pass this to LLM calls and check it periodically */\n\tsignal: AbortSignal;\n}\n\n/** Fired after context compaction */\nexport interface SessionCompactEvent {\n\ttype: \"session_compact\";\n\tcompactionEntry: CompactionEntry;\n\t/** Whether the compaction entry was provided by a hook */\n\tfromHook: boolean;\n}\n\n/** Fired on process exit (SIGINT/SIGTERM) */\nexport interface SessionShutdownEvent {\n\ttype: \"session_shutdown\";\n}\n\n/** Preparation data for tree navigation (used by session_before_tree event) */\nexport interface TreePreparation {\n\t/** Node being switched to */\n\ttargetId: string;\n\t/** Current active leaf (being abandoned), null if no current position */\n\toldLeafId: string | null;\n\t/** Common ancestor of target and old leaf, null if no common ancestor */\n\tcommonAncestorId: string | null;\n\t/** Entries to summarize (old leaf back to common ancestor or compaction) */\n\tentriesToSummarize: SessionEntry[];\n\t/** Whether user chose to summarize */\n\tuserWantsSummary: boolean;\n}\n\n/** Fired before navigating to a different node in the session tree (can be cancelled) */\nexport interface SessionBeforeTreeEvent {\n\ttype: \"session_before_tree\";\n\t/** Preparation data for the navigation */\n\tpreparation: TreePreparation;\n\t/** Abort signal - honors Escape during summarization (model available via ctx.model) */\n\tsignal: AbortSignal;\n}\n\n/** Fired after navigating to a different node in the session tree */\nexport interface SessionTreeEvent {\n\ttype: \"session_tree\";\n\t/** The new active leaf, null if navigated to before first entry */\n\tnewLeafId: string | null;\n\t/** Previous active leaf, null if there was no position */\n\toldLeafId: string | null;\n\t/** Branch summary entry if one was created */\n\tsummaryEntry?: BranchSummaryEntry;\n\t/** Whether summary came from hook */\n\tfromHook?: boolean;\n}\n\n/** Union of all session event types */\nexport type SessionEvent =\n\t| SessionStartEvent\n\t| SessionBeforeSwitchEvent\n\t| SessionSwitchEvent\n\t| SessionBeforeBranchEvent\n\t| SessionBranchEvent\n\t| SessionBeforeCompactEvent\n\t| SessionCompactEvent\n\t| SessionShutdownEvent\n\t| SessionBeforeTreeEvent\n\t| SessionTreeEvent;\n\n/**\n * Event data for context event.\n * Fired before each LLM call, allowing hooks to modify context non-destructively.\n * Original session messages are NOT modified - only the messages sent to the LLM are affected.\n */\nexport interface ContextEvent {\n\ttype: \"context\";\n\t/** Messages about to be sent to the LLM (deep copy, safe to modify) */\n\tmessages: AgentMessage[];\n}\n\n/**\n * Event data for before_agent_start event.\n * Fired after user submits a prompt but before the agent loop starts.\n * Allows hooks to inject context that will be persisted and visible in TUI.\n */\nexport interface BeforeAgentStartEvent {\n\ttype: \"before_agent_start\";\n\t/** The user's prompt text */\n\tprompt: string;\n\t/** Any images attached to the prompt */\n\timages?: ImageContent[];\n}\n\n/**\n * Event data for agent_start event.\n * Fired when an agent loop starts (once per user prompt).\n */\nexport interface AgentStartEvent {\n\ttype: \"agent_start\";\n}\n\n/**\n * Event data for agent_end event.\n */\nexport interface AgentEndEvent {\n\ttype: \"agent_end\";\n\tmessages: AgentMessage[];\n}\n\n/**\n * Event data for turn_start event.\n */\nexport interface TurnStartEvent {\n\ttype: \"turn_start\";\n\tturnIndex: number;\n\ttimestamp: number;\n}\n\n/**\n * Event data for turn_end event.\n */\nexport interface TurnEndEvent {\n\ttype: \"turn_end\";\n\tturnIndex: number;\n\tmessage: AgentMessage;\n\ttoolResults: ToolResultMessage[];\n}\n\n/**\n * Event data for tool_call event.\n * Fired before a tool is executed. Hooks can block execution.\n */\nexport interface ToolCallEvent {\n\ttype: \"tool_call\";\n\t/** Tool name (e.g., \"bash\", \"edit\", \"write\") */\n\ttoolName: string;\n\t/** Tool call ID */\n\ttoolCallId: string;\n\t/** Tool input parameters */\n\tinput: Record<string, unknown>;\n}\n\n/**\n * Base interface for tool_result events.\n */\ninterface ToolResultEventBase {\n\ttype: \"tool_result\";\n\t/** Tool call ID */\n\ttoolCallId: string;\n\t/** Tool input parameters */\n\tinput: Record<string, unknown>;\n\t/** Full content array (text and images) */\n\tcontent: (TextContent | ImageContent)[];\n\t/** Whether the tool execution was an error */\n\tisError: boolean;\n}\n\n/** Tool result event for bash tool */\nexport interface BashToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"bash\";\n\tdetails: BashToolDetails | undefined;\n}\n\n/** Tool result event for read tool */\nexport interface ReadToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"read\";\n\tdetails: ReadToolDetails | undefined;\n}\n\n/** Tool result event for edit tool */\nexport interface EditToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"edit\";\n\tdetails: EditToolDetails | undefined;\n}\n\n/** Tool result event for write tool */\nexport interface WriteToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"write\";\n\tdetails: undefined;\n}\n\n/** Tool result event for grep tool */\nexport interface GrepToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"grep\";\n\tdetails: GrepToolDetails | undefined;\n}\n\n/** Tool result event for find tool */\nexport interface FindToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"find\";\n\tdetails: FindToolDetails | undefined;\n}\n\n/** Tool result event for ls tool */\nexport interface LsToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"ls\";\n\tdetails: LsToolDetails | undefined;\n}\n\n/** Tool result event for custom/unknown tools */\nexport interface CustomToolResultEvent extends ToolResultEventBase {\n\ttoolName: string;\n\tdetails: unknown;\n}\n\n/**\n * Event data for tool_result event.\n * Fired after a tool is executed. Hooks can modify the result.\n * Use toolName to discriminate and get typed details.\n */\nexport type ToolResultEvent =\n\t| BashToolResultEvent\n\t| ReadToolResultEvent\n\t| EditToolResultEvent\n\t| WriteToolResultEvent\n\t| GrepToolResultEvent\n\t| FindToolResultEvent\n\t| LsToolResultEvent\n\t| CustomToolResultEvent;\n\n// Type guards for narrowing ToolResultEvent to specific tool types\nexport function isBashToolResult(e: ToolResultEvent): e is BashToolResultEvent {\n\treturn e.toolName === \"bash\";\n}\nexport function isReadToolResult(e: ToolResultEvent): e is ReadToolResultEvent {\n\treturn e.toolName === \"read\";\n}\nexport function isEditToolResult(e: ToolResultEvent): e is EditToolResultEvent {\n\treturn e.toolName === \"edit\";\n}\nexport function isWriteToolResult(e: ToolResultEvent): e is WriteToolResultEvent {\n\treturn e.toolName === \"write\";\n}\nexport function isGrepToolResult(e: ToolResultEvent): e is GrepToolResultEvent {\n\treturn e.toolName === \"grep\";\n}\nexport function isFindToolResult(e: ToolResultEvent): e is FindToolResultEvent {\n\treturn e.toolName === \"find\";\n}\nexport function isLsToolResult(e: ToolResultEvent): e is LsToolResultEvent {\n\treturn e.toolName === \"ls\";\n}\n\n/**\n * Union of all hook event types.\n */\nexport type HookEvent =\n\t| SessionEvent\n\t| ContextEvent\n\t| BeforeAgentStartEvent\n\t| AgentStartEvent\n\t| AgentEndEvent\n\t| TurnStartEvent\n\t| TurnEndEvent\n\t| ToolCallEvent\n\t| ToolResultEvent;\n\n// ============================================================================\n// Event Results\n// ============================================================================\n\n/**\n * Return type for context event handlers.\n * Allows hooks to modify messages before they're sent to the LLM.\n */\nexport interface ContextEventResult {\n\t/** Modified messages to send instead of the original */\n\tmessages?: Message[];\n}\n\n/**\n * Return type for tool_call event handlers.\n * Allows hooks to block tool execution.\n */\nexport interface ToolCallEventResult {\n\t/** If true, block the tool from executing */\n\tblock?: boolean;\n\t/** Reason for blocking (returned to LLM as error) */\n\treason?: string;\n}\n\n/**\n * Return type for tool_result event handlers.\n * Allows hooks to modify tool results.\n */\nexport interface ToolResultEventResult {\n\t/** Replacement content array (text and images) */\n\tcontent?: (TextContent | ImageContent)[];\n\t/** Replacement details */\n\tdetails?: unknown;\n\t/** Override isError flag */\n\tisError?: boolean;\n}\n\n/**\n * Return type for before_agent_start event handlers.\n * Allows hooks to inject context before the agent runs.\n */\nexport interface BeforeAgentStartEventResult {\n\t/** Message to inject into context (persisted to session, visible in TUI) */\n\tmessage?: Pick<HookMessage, \"customType\" | \"content\" | \"display\" | \"details\">;\n}\n\n/** Return type for session_before_switch handlers */\nexport interface SessionBeforeSwitchResult {\n\t/** If true, cancel the switch */\n\tcancel?: boolean;\n}\n\n/** Return type for session_before_branch handlers */\nexport interface SessionBeforeBranchResult {\n\t/**\n\t * If true, abort the branch entirely. No new session file is created,\n\t * conversation stays unchanged.\n\t */\n\tcancel?: boolean;\n\t/**\n\t * If true, the branch proceeds (new session file created, session state updated)\n\t * but the in-memory conversation is NOT rewound to the branch point.\n\t *\n\t * Use case: git-checkpoint hook that restores code state separately.\n\t * The hook handles state restoration itself, so it doesn't want the\n\t * agent's conversation to be rewound (which would lose recent context).\n\t *\n\t * - `cancel: true` → nothing happens, user stays in current session\n\t * - `skipConversationRestore: true` → branch happens, but messages stay as-is\n\t * - neither → branch happens AND messages rewind to branch point (default)\n\t */\n\tskipConversationRestore?: boolean;\n}\n\n/** Return type for session_before_compact handlers */\nexport interface SessionBeforeCompactResult {\n\t/** If true, cancel the compaction */\n\tcancel?: boolean;\n\t/** Custom compaction result - SessionManager adds id/parentId */\n\tcompaction?: CompactionResult;\n}\n\n/** Return type for session_before_tree handlers */\nexport interface SessionBeforeTreeResult {\n\t/** If true, cancel the navigation entirely */\n\tcancel?: boolean;\n\t/**\n\t * Custom summary (skips default summarizer).\n\t * Only used if preparation.userWantsSummary is true.\n\t */\n\tsummary?: {\n\t\tsummary: string;\n\t\tdetails?: unknown;\n\t};\n}\n\n// ============================================================================\n// Hook API\n// ============================================================================\n\n/**\n * Handler function type for each event.\n * Handlers can return R, undefined, or void (bare return statements).\n */\n// biome-ignore lint/suspicious/noConfusingVoidType: void allows bare return statements in handlers\nexport type HookHandler<E, R = undefined> = (event: E, ctx: HookContext) => Promise<R | void> | R | void;\n\nexport interface HookMessageRenderOptions {\n\t/** Whether the view is expanded */\n\texpanded: boolean;\n}\n\n/**\n * Renderer for hook messages.\n * Hooks register these to provide custom TUI rendering for their message types.\n */\nexport type HookMessageRenderer<T = unknown> = (\n\tmessage: HookMessage<T>,\n\toptions: HookMessageRenderOptions,\n\ttheme: Theme,\n) => Component | undefined;\n\n/**\n * Command registration options.\n */\nexport interface RegisteredCommand {\n\tname: string;\n\tdescription?: string;\n\n\thandler: (args: string, ctx: HookCommandContext) => Promise<void>;\n}\n\n/**\n * HookAPI passed to hook factory functions.\n * Hooks use pi.on() to subscribe to events and pi.sendMessage() to inject messages.\n */\nexport interface HookAPI {\n\t// Session events\n\ton(event: \"session_start\", handler: HookHandler<SessionStartEvent>): void;\n\ton(event: \"session_before_switch\", handler: HookHandler<SessionBeforeSwitchEvent, SessionBeforeSwitchResult>): void;\n\ton(event: \"session_switch\", handler: HookHandler<SessionSwitchEvent>): void;\n\ton(event: \"session_before_branch\", handler: HookHandler<SessionBeforeBranchEvent, SessionBeforeBranchResult>): void;\n\ton(event: \"session_branch\", handler: HookHandler<SessionBranchEvent>): void;\n\ton(\n\t\tevent: \"session_before_compact\",\n\t\thandler: HookHandler<SessionBeforeCompactEvent, SessionBeforeCompactResult>,\n\t): void;\n\ton(event: \"session_compact\", handler: HookHandler<SessionCompactEvent>): void;\n\ton(event: \"session_shutdown\", handler: HookHandler<SessionShutdownEvent>): void;\n\ton(event: \"session_before_tree\", handler: HookHandler<SessionBeforeTreeEvent, SessionBeforeTreeResult>): void;\n\ton(event: \"session_tree\", handler: HookHandler<SessionTreeEvent>): void;\n\n\t// Context and agent events\n\ton(event: \"context\", handler: HookHandler<ContextEvent, ContextEventResult>): void;\n\ton(event: \"before_agent_start\", handler: HookHandler<BeforeAgentStartEvent, BeforeAgentStartEventResult>): void;\n\ton(event: \"agent_start\", handler: HookHandler<AgentStartEvent>): void;\n\ton(event: \"agent_end\", handler: HookHandler<AgentEndEvent>): void;\n\ton(event: \"turn_start\", handler: HookHandler<TurnStartEvent>): void;\n\ton(event: \"turn_end\", handler: HookHandler<TurnEndEvent>): void;\n\ton(event: \"tool_call\", handler: HookHandler<ToolCallEvent, ToolCallEventResult>): void;\n\ton(event: \"tool_result\", handler: HookHandler<ToolResultEvent, ToolResultEventResult>): void;\n\n\t/**\n\t * Send a custom message to the session. Creates a CustomMessageEntry that\n\t * participates in LLM context and can be displayed in the TUI.\n\t *\n\t * Use this when you want the LLM to see the message content.\n\t * For hook state that should NOT be sent to the LLM, use appendEntry() instead.\n\t *\n\t * @param message - The message to send\n\t * @param message.customType - Identifier for your hook (used for filtering on reload)\n\t * @param message.content - Message content (string or TextContent/ImageContent array)\n\t * @param message.display - Whether to show in TUI (true = styled display, false = hidden)\n\t * @param message.details - Optional hook-specific metadata (not sent to LLM)\n\t * @param options.triggerTurn - If true and agent is idle, triggers a new LLM turn. Default: false.\n\t * If agent is streaming, message is queued and triggerTurn is ignored.\n\t * @param options.deliverAs - How to deliver when agent is streaming. Default: \"steer\".\n\t * - \"steer\": Interrupt mid-run, delivered after current tool execution.\n\t * - \"followUp\": Wait until agent finishes all work before delivery.\n\t */\n\tsendMessage<T = unknown>(\n\t\tmessage: Pick<HookMessage<T>, \"customType\" | \"content\" | \"display\" | \"details\">,\n\t\toptions?: { triggerTurn?: boolean; deliverAs?: \"steer\" | \"followUp\" },\n\t): void;\n\n\t/**\n\t * Append a custom entry to the session for hook state persistence.\n\t * Creates a CustomEntry that does NOT participate in LLM context.\n\t *\n\t * Use this to store hook-specific data that should persist across session reloads\n\t * but should NOT be sent to the LLM. On reload, scan session entries for your\n\t * customType to reconstruct hook state.\n\t *\n\t * For messages that SHOULD be sent to the LLM, use sendMessage() instead.\n\t *\n\t * @param customType - Identifier for your hook (used for filtering on reload)\n\t * @param data - Hook-specific data to persist (must be JSON-serializable)\n\t *\n\t * @example\n\t * // Store permission state\n\t * pi.appendEntry(\"permissions\", { level: \"full\", grantedAt: Date.now() });\n\t *\n\t * // On reload, reconstruct state from entries\n\t * pi.on(\"session\", async (event, ctx) => {\n\t * if (event.reason === \"start\") {\n\t * const entries = event.sessionManager.getEntries();\n\t * const myEntries = entries.filter(e => e.type === \"custom\" && e.customType === \"permissions\");\n\t * // Reconstruct state from myEntries...\n\t * }\n\t * });\n\t */\n\tappendEntry<T = unknown>(customType: string, data?: T): void;\n\n\t/**\n\t * Register a custom renderer for CustomMessageEntry with a specific customType.\n\t * The renderer is called when rendering the entry in the TUI.\n\t * Return nothing to use the default renderer.\n\t */\n\tregisterMessageRenderer<T = unknown>(customType: string, renderer: HookMessageRenderer<T>): void;\n\n\t/**\n\t * Register a custom slash command.\n\t * Handler receives HookCommandContext with session control methods.\n\t */\n\tregisterCommand(name: string, options: { description?: string; handler: RegisteredCommand[\"handler\"] }): void;\n\n\t/**\n\t * Execute a shell command and return stdout/stderr/code.\n\t * Supports timeout and abort signal.\n\t */\n\texec(command: string, args: string[], options?: ExecOptions): Promise<ExecResult>;\n}\n\n/**\n * Hook factory function type.\n * Hooks export a default function that receives the HookAPI.\n */\nexport type HookFactory = (pi: HookAPI) => void;\n\n// ============================================================================\n// Errors\n// ============================================================================\n\n/**\n * Error emitted when a hook fails.\n */\nexport interface HookError {\n\thookPath: string;\n\tevent: string;\n\terror: string;\n}\n"]}
@@ -22,6 +22,8 @@ export declare class ModelRegistry {
22
22
  */
23
23
  getError(): string | undefined;
24
24
  private loadModels;
25
+ /** Load built-in models, skipping replaced providers and applying overrides */
26
+ private loadBuiltInModels;
25
27
  private loadCustomModels;
26
28
  private validateConfig;
27
29
  private parseModels;
@@ -31,9 +33,10 @@ export declare class ModelRegistry {
31
33
  */
32
34
  getAll(): Model<Api>[];
33
35
  /**
34
- * Get only models that have valid API keys available.
36
+ * Get only models that have auth configured.
37
+ * This is a fast check that doesn't refresh OAuth tokens.
35
38
  */
36
- getAvailable(): Promise<Model<Api>[]>;
39
+ getAvailable(): Model<Api>[];
37
40
  /**
38
41
  * Find a model by provider and ID.
39
42
  */
@@ -1 +1 @@
1
- {"version":3,"file":"model-registry.d.ts","sourceRoot":"","sources":["../../src/core/model-registry.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACN,KAAK,GAAG,EAKR,KAAK,KAAK,EAEV,MAAM,qBAAqB,CAAC;AAI7B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAsErD;;GAEG;AACH,qBAAa,aAAa;IAMxB,QAAQ,CAAC,WAAW,EAAE,WAAW;IACjC,OAAO,CAAC,cAAc;IANvB,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,qBAAqB,CAAkC;IAC/D,OAAO,CAAC,SAAS,CAAiC;IAElD,YACU,WAAW,EAAE,WAAW,EACzB,cAAc,GAAE,MAAM,GAAG,SAAqB,EAatD;IAED;;OAEG;IACH,OAAO,IAAI,IAAI,CAId;IAED;;OAEG;IACH,QAAQ,IAAI,MAAM,GAAG,SAAS,CAE7B;IAED,OAAO,CAAC,UAAU;IAmClB,OAAO,CAAC,gBAAgB;IAyCxB,OAAO,CAAC,cAAc;IAuBtB,OAAO,CAAC,WAAW;IA6CnB;;;OAGG;IACH,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,EAAE,CAErB;IAED;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAS1C;IAED;;OAEG;IACH,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,SAAS,CAE9D;IAED;;OAEG;IACG,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAE9D;IAED;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO,CAGvC;CACD","sourcesContent":["/**\n * Model registry - manages built-in and custom models, provides API key resolution.\n */\n\nimport {\n\ttype Api,\n\tgetGitHubCopilotBaseUrl,\n\tgetModels,\n\tgetProviders,\n\ttype KnownProvider,\n\ttype Model,\n\tnormalizeDomain,\n} from \"@mariozechner/pi-ai\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport AjvModule from \"ajv\";\nimport { existsSync, readFileSync } from \"fs\";\nimport type { AuthStorage } from \"./auth-storage.js\";\n\nconst Ajv = (AjvModule as any).default || AjvModule;\n\n// Schema for OpenAI compatibility settings\nconst OpenAICompatSchema = Type.Object({\n\tsupportsStore: Type.Optional(Type.Boolean()),\n\tsupportsDeveloperRole: Type.Optional(Type.Boolean()),\n\tsupportsReasoningEffort: Type.Optional(Type.Boolean()),\n\tmaxTokensField: Type.Optional(Type.Union([Type.Literal(\"max_completion_tokens\"), Type.Literal(\"max_tokens\")])),\n});\n\n// Schema for custom model definition\nconst ModelDefinitionSchema = Type.Object({\n\tid: Type.String({ minLength: 1 }),\n\tname: Type.String({ minLength: 1 }),\n\tapi: Type.Optional(\n\t\tType.Union([\n\t\t\tType.Literal(\"openai-completions\"),\n\t\t\tType.Literal(\"openai-responses\"),\n\t\t\tType.Literal(\"anthropic-messages\"),\n\t\t\tType.Literal(\"google-generative-ai\"),\n\t\t]),\n\t),\n\treasoning: Type.Boolean(),\n\tinput: Type.Array(Type.Union([Type.Literal(\"text\"), Type.Literal(\"image\")])),\n\tcost: Type.Object({\n\t\tinput: Type.Number(),\n\t\toutput: Type.Number(),\n\t\tcacheRead: Type.Number(),\n\t\tcacheWrite: Type.Number(),\n\t}),\n\tcontextWindow: Type.Number(),\n\tmaxTokens: Type.Number(),\n\theaders: Type.Optional(Type.Record(Type.String(), Type.String())),\n\tcompat: Type.Optional(OpenAICompatSchema),\n});\n\nconst ProviderConfigSchema = Type.Object({\n\tbaseUrl: Type.String({ minLength: 1 }),\n\tapiKey: Type.String({ minLength: 1 }),\n\tapi: Type.Optional(\n\t\tType.Union([\n\t\t\tType.Literal(\"openai-completions\"),\n\t\t\tType.Literal(\"openai-responses\"),\n\t\t\tType.Literal(\"anthropic-messages\"),\n\t\t\tType.Literal(\"google-generative-ai\"),\n\t\t]),\n\t),\n\theaders: Type.Optional(Type.Record(Type.String(), Type.String())),\n\tauthHeader: Type.Optional(Type.Boolean()),\n\tmodels: Type.Array(ModelDefinitionSchema),\n});\n\nconst ModelsConfigSchema = Type.Object({\n\tproviders: Type.Record(Type.String(), ProviderConfigSchema),\n});\n\ntype ModelsConfig = Static<typeof ModelsConfigSchema>;\n\n/**\n * Resolve an API key config value to an actual key.\n * Checks environment variable first, then treats as literal.\n */\nfunction resolveApiKeyConfig(keyConfig: string): string | undefined {\n\tconst envValue = process.env[keyConfig];\n\tif (envValue) return envValue;\n\treturn keyConfig;\n}\n\n/**\n * Model registry - loads and manages models, resolves API keys via AuthStorage.\n */\nexport class ModelRegistry {\n\tprivate models: Model<Api>[] = [];\n\tprivate customProviderApiKeys: Map<string, string> = new Map();\n\tprivate loadError: string | undefined = undefined;\n\n\tconstructor(\n\t\treadonly authStorage: AuthStorage,\n\t\tprivate modelsJsonPath: string | undefined = undefined,\n\t) {\n\t\t// Set up fallback resolver for custom provider API keys\n\t\tthis.authStorage.setFallbackResolver((provider) => {\n\t\t\tconst keyConfig = this.customProviderApiKeys.get(provider);\n\t\t\tif (keyConfig) {\n\t\t\t\treturn resolveApiKeyConfig(keyConfig);\n\t\t\t}\n\t\t\treturn undefined;\n\t\t});\n\n\t\t// Load models\n\t\tthis.loadModels();\n\t}\n\n\t/**\n\t * Reload models from disk (built-in + custom from models.json).\n\t */\n\trefresh(): void {\n\t\tthis.customProviderApiKeys.clear();\n\t\tthis.loadError = undefined;\n\t\tthis.loadModels();\n\t}\n\n\t/**\n\t * Get any error from loading models.json (undefined if no error).\n\t */\n\tgetError(): string | undefined {\n\t\treturn this.loadError;\n\t}\n\n\tprivate loadModels(): void {\n\t\t// Load built-in models\n\t\tconst builtInModels: Model<Api>[] = [];\n\t\tfor (const provider of getProviders()) {\n\t\t\tconst providerModels = getModels(provider as KnownProvider);\n\t\t\tbuiltInModels.push(...(providerModels as Model<Api>[]));\n\t\t}\n\n\t\t// Load custom models from models.json (if path provided)\n\t\tlet customModels: Model<Api>[] = [];\n\t\tif (this.modelsJsonPath) {\n\t\t\tconst result = this.loadCustomModels(this.modelsJsonPath);\n\t\t\tif (result.error) {\n\t\t\t\tthis.loadError = result.error;\n\t\t\t\t// Keep built-in models even if custom models failed to load\n\t\t\t} else {\n\t\t\t\tcustomModels = result.models;\n\t\t\t}\n\t\t}\n\n\t\tconst combined = [...builtInModels, ...customModels];\n\n\t\t// Update github-copilot base URL based on OAuth credentials\n\t\tconst copilotCred = this.authStorage.get(\"github-copilot\");\n\t\tif (copilotCred?.type === \"oauth\") {\n\t\t\tconst domain = copilotCred.enterpriseUrl\n\t\t\t\t? (normalizeDomain(copilotCred.enterpriseUrl) ?? undefined)\n\t\t\t\t: undefined;\n\t\t\tconst baseUrl = getGitHubCopilotBaseUrl(copilotCred.access, domain);\n\t\t\tthis.models = combined.map((m) => (m.provider === \"github-copilot\" ? { ...m, baseUrl } : m));\n\t\t} else {\n\t\t\tthis.models = combined;\n\t\t}\n\t}\n\n\tprivate loadCustomModels(modelsJsonPath: string): { models: Model<Api>[]; error: string | undefined } {\n\t\tif (!existsSync(modelsJsonPath)) {\n\t\t\treturn { models: [], error: undefined };\n\t\t}\n\n\t\ttry {\n\t\t\tconst content = readFileSync(modelsJsonPath, \"utf-8\");\n\t\t\tconst config: ModelsConfig = JSON.parse(content);\n\n\t\t\t// Validate schema\n\t\t\tconst ajv = new Ajv();\n\t\t\tconst validate = ajv.compile(ModelsConfigSchema);\n\t\t\tif (!validate(config)) {\n\t\t\t\tconst errors =\n\t\t\t\t\tvalidate.errors?.map((e: any) => ` - ${e.instancePath || \"root\"}: ${e.message}`).join(\"\\n\") ||\n\t\t\t\t\t\"Unknown schema error\";\n\t\t\t\treturn {\n\t\t\t\t\tmodels: [],\n\t\t\t\t\terror: `Invalid models.json schema:\\n${errors}\\n\\nFile: ${modelsJsonPath}`,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Additional validation\n\t\t\tthis.validateConfig(config);\n\n\t\t\t// Parse models\n\t\t\treturn { models: this.parseModels(config), error: undefined };\n\t\t} catch (error) {\n\t\t\tif (error instanceof SyntaxError) {\n\t\t\t\treturn {\n\t\t\t\t\tmodels: [],\n\t\t\t\t\terror: `Failed to parse models.json: ${error.message}\\n\\nFile: ${modelsJsonPath}`,\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tmodels: [],\n\t\t\t\terror: `Failed to load models.json: ${error instanceof Error ? error.message : error}\\n\\nFile: ${modelsJsonPath}`,\n\t\t\t};\n\t\t}\n\t}\n\n\tprivate validateConfig(config: ModelsConfig): void {\n\t\tfor (const [providerName, providerConfig] of Object.entries(config.providers)) {\n\t\t\tconst hasProviderApi = !!providerConfig.api;\n\n\t\t\tfor (const modelDef of providerConfig.models) {\n\t\t\t\tconst hasModelApi = !!modelDef.api;\n\n\t\t\t\tif (!hasProviderApi && !hasModelApi) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Provider ${providerName}, model ${modelDef.id}: no \"api\" specified. Set at provider or model level.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (!modelDef.id) throw new Error(`Provider ${providerName}: model missing \"id\"`);\n\t\t\t\tif (!modelDef.name) throw new Error(`Provider ${providerName}: model missing \"name\"`);\n\t\t\t\tif (modelDef.contextWindow <= 0)\n\t\t\t\t\tthrow new Error(`Provider ${providerName}, model ${modelDef.id}: invalid contextWindow`);\n\t\t\t\tif (modelDef.maxTokens <= 0)\n\t\t\t\t\tthrow new Error(`Provider ${providerName}, model ${modelDef.id}: invalid maxTokens`);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate parseModels(config: ModelsConfig): Model<Api>[] {\n\t\tconst models: Model<Api>[] = [];\n\n\t\tfor (const [providerName, providerConfig] of Object.entries(config.providers)) {\n\t\t\t// Store API key config for fallback resolver\n\t\t\tthis.customProviderApiKeys.set(providerName, providerConfig.apiKey);\n\n\t\t\tfor (const modelDef of providerConfig.models) {\n\t\t\t\tconst api = modelDef.api || providerConfig.api;\n\t\t\t\tif (!api) continue;\n\n\t\t\t\t// Merge headers: provider headers are base, model headers override\n\t\t\t\tlet headers =\n\t\t\t\t\tproviderConfig.headers || modelDef.headers\n\t\t\t\t\t\t? { ...providerConfig.headers, ...modelDef.headers }\n\t\t\t\t\t\t: undefined;\n\n\t\t\t\t// If authHeader is true, add Authorization header with resolved API key\n\t\t\t\tif (providerConfig.authHeader) {\n\t\t\t\t\tconst resolvedKey = resolveApiKeyConfig(providerConfig.apiKey);\n\t\t\t\t\tif (resolvedKey) {\n\t\t\t\t\t\theaders = { ...headers, Authorization: `Bearer ${resolvedKey}` };\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelDef.id,\n\t\t\t\t\tname: modelDef.name,\n\t\t\t\t\tapi: api as Api,\n\t\t\t\t\tprovider: providerName,\n\t\t\t\t\tbaseUrl: providerConfig.baseUrl,\n\t\t\t\t\treasoning: modelDef.reasoning,\n\t\t\t\t\tinput: modelDef.input as (\"text\" | \"image\")[],\n\t\t\t\t\tcost: modelDef.cost,\n\t\t\t\t\tcontextWindow: modelDef.contextWindow,\n\t\t\t\t\tmaxTokens: modelDef.maxTokens,\n\t\t\t\t\theaders,\n\t\t\t\t\tcompat: modelDef.compat,\n\t\t\t\t} as Model<Api>);\n\t\t\t}\n\t\t}\n\n\t\treturn models;\n\t}\n\n\t/**\n\t * Get all models (built-in + custom).\n\t * If models.json had errors, returns only built-in models.\n\t */\n\tgetAll(): Model<Api>[] {\n\t\treturn this.models;\n\t}\n\n\t/**\n\t * Get only models that have valid API keys available.\n\t */\n\tasync getAvailable(): Promise<Model<Api>[]> {\n\t\tconst available: Model<Api>[] = [];\n\t\tfor (const model of this.models) {\n\t\t\tconst apiKey = await this.authStorage.getApiKey(model.provider);\n\t\t\tif (apiKey) {\n\t\t\t\tavailable.push(model);\n\t\t\t}\n\t\t}\n\t\treturn available;\n\t}\n\n\t/**\n\t * Find a model by provider and ID.\n\t */\n\tfind(provider: string, modelId: string): Model<Api> | undefined {\n\t\treturn this.models.find((m) => m.provider === provider && m.id === modelId) ?? undefined;\n\t}\n\n\t/**\n\t * Get API key for a model.\n\t */\n\tasync getApiKey(model: Model<Api>): Promise<string | undefined> {\n\t\treturn this.authStorage.getApiKey(model.provider);\n\t}\n\n\t/**\n\t * Check if a model is using OAuth credentials (subscription).\n\t */\n\tisUsingOAuth(model: Model<Api>): boolean {\n\t\tconst cred = this.authStorage.get(model.provider);\n\t\treturn cred?.type === \"oauth\";\n\t}\n}\n"]}
1
+ {"version":3,"file":"model-registry.d.ts","sourceRoot":"","sources":["../../src/core/model-registry.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACN,KAAK,GAAG,EAKR,KAAK,KAAK,EAEV,MAAM,qBAAqB,CAAC;AAI7B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AA2FrD;;GAEG;AACH,qBAAa,aAAa;IAMxB,QAAQ,CAAC,WAAW,EAAE,WAAW;IACjC,OAAO,CAAC,cAAc;IANvB,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,qBAAqB,CAAkC;IAC/D,OAAO,CAAC,SAAS,CAAiC;IAElD,YACU,WAAW,EAAE,WAAW,EACzB,cAAc,GAAE,MAAM,GAAG,SAAqB,EAatD;IAED;;OAEG;IACH,OAAO,IAAI,IAAI,CAId;IAED;;OAEG;IACH,QAAQ,IAAI,MAAM,GAAG,SAAS,CAE7B;IAED,OAAO,CAAC,UAAU;IA8BlB,+EAA+E;IAC/E,OAAO,CAAC,iBAAiB;IAiBzB,OAAO,CAAC,gBAAgB;IAuDxB,OAAO,CAAC,cAAc;IAyCtB,OAAO,CAAC,WAAW;IAmDnB;;;OAGG;IACH,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,EAAE,CAErB;IAED;;;OAGG;IACH,YAAY,IAAI,KAAK,CAAC,GAAG,CAAC,EAAE,CAE3B;IAED;;OAEG;IACH,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,SAAS,CAE9D;IAED;;OAEG;IACG,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAE9D;IAED;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO,CAGvC;CACD","sourcesContent":["/**\n * Model registry - manages built-in and custom models, provides API key resolution.\n */\n\nimport {\n\ttype Api,\n\tgetGitHubCopilotBaseUrl,\n\tgetModels,\n\tgetProviders,\n\ttype KnownProvider,\n\ttype Model,\n\tnormalizeDomain,\n} from \"@mariozechner/pi-ai\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport AjvModule from \"ajv\";\nimport { existsSync, readFileSync } from \"fs\";\nimport type { AuthStorage } from \"./auth-storage.js\";\n\nconst Ajv = (AjvModule as any).default || AjvModule;\n\n// Schema for OpenAI compatibility settings\nconst OpenAICompatSchema = Type.Object({\n\tsupportsStore: Type.Optional(Type.Boolean()),\n\tsupportsDeveloperRole: Type.Optional(Type.Boolean()),\n\tsupportsReasoningEffort: Type.Optional(Type.Boolean()),\n\tmaxTokensField: Type.Optional(Type.Union([Type.Literal(\"max_completion_tokens\"), Type.Literal(\"max_tokens\")])),\n});\n\n// Schema for custom model definition\nconst ModelDefinitionSchema = Type.Object({\n\tid: Type.String({ minLength: 1 }),\n\tname: Type.String({ minLength: 1 }),\n\tapi: Type.Optional(\n\t\tType.Union([\n\t\t\tType.Literal(\"openai-completions\"),\n\t\t\tType.Literal(\"openai-responses\"),\n\t\t\tType.Literal(\"anthropic-messages\"),\n\t\t\tType.Literal(\"google-generative-ai\"),\n\t\t]),\n\t),\n\treasoning: Type.Boolean(),\n\tinput: Type.Array(Type.Union([Type.Literal(\"text\"), Type.Literal(\"image\")])),\n\tcost: Type.Object({\n\t\tinput: Type.Number(),\n\t\toutput: Type.Number(),\n\t\tcacheRead: Type.Number(),\n\t\tcacheWrite: Type.Number(),\n\t}),\n\tcontextWindow: Type.Number(),\n\tmaxTokens: Type.Number(),\n\theaders: Type.Optional(Type.Record(Type.String(), Type.String())),\n\tcompat: Type.Optional(OpenAICompatSchema),\n});\n\nconst ProviderConfigSchema = Type.Object({\n\tbaseUrl: Type.Optional(Type.String({ minLength: 1 })),\n\tapiKey: Type.Optional(Type.String({ minLength: 1 })),\n\tapi: Type.Optional(\n\t\tType.Union([\n\t\t\tType.Literal(\"openai-completions\"),\n\t\t\tType.Literal(\"openai-responses\"),\n\t\t\tType.Literal(\"anthropic-messages\"),\n\t\t\tType.Literal(\"google-generative-ai\"),\n\t\t]),\n\t),\n\theaders: Type.Optional(Type.Record(Type.String(), Type.String())),\n\tauthHeader: Type.Optional(Type.Boolean()),\n\tmodels: Type.Optional(Type.Array(ModelDefinitionSchema)),\n});\n\nconst ModelsConfigSchema = Type.Object({\n\tproviders: Type.Record(Type.String(), ProviderConfigSchema),\n});\n\ntype ModelsConfig = Static<typeof ModelsConfigSchema>;\n\n/** Provider override config (baseUrl, headers, apiKey) without custom models */\ninterface ProviderOverride {\n\tbaseUrl?: string;\n\theaders?: Record<string, string>;\n\tapiKey?: string;\n}\n\n/** Result of loading custom models from models.json */\ninterface CustomModelsResult {\n\tmodels: Model<Api>[];\n\t/** Providers with custom models (full replacement) */\n\treplacedProviders: Set<string>;\n\t/** Providers with only baseUrl/headers override (no custom models) */\n\toverrides: Map<string, ProviderOverride>;\n\terror: string | undefined;\n}\n\nfunction emptyCustomModelsResult(error?: string): CustomModelsResult {\n\treturn { models: [], replacedProviders: new Set(), overrides: new Map(), error };\n}\n\n/**\n * Resolve an API key config value to an actual key.\n * Checks environment variable first, then treats as literal.\n */\nfunction resolveApiKeyConfig(keyConfig: string): string | undefined {\n\tconst envValue = process.env[keyConfig];\n\tif (envValue) return envValue;\n\treturn keyConfig;\n}\n\n/**\n * Model registry - loads and manages models, resolves API keys via AuthStorage.\n */\nexport class ModelRegistry {\n\tprivate models: Model<Api>[] = [];\n\tprivate customProviderApiKeys: Map<string, string> = new Map();\n\tprivate loadError: string | undefined = undefined;\n\n\tconstructor(\n\t\treadonly authStorage: AuthStorage,\n\t\tprivate modelsJsonPath: string | undefined = undefined,\n\t) {\n\t\t// Set up fallback resolver for custom provider API keys\n\t\tthis.authStorage.setFallbackResolver((provider) => {\n\t\t\tconst keyConfig = this.customProviderApiKeys.get(provider);\n\t\t\tif (keyConfig) {\n\t\t\t\treturn resolveApiKeyConfig(keyConfig);\n\t\t\t}\n\t\t\treturn undefined;\n\t\t});\n\n\t\t// Load models\n\t\tthis.loadModels();\n\t}\n\n\t/**\n\t * Reload models from disk (built-in + custom from models.json).\n\t */\n\trefresh(): void {\n\t\tthis.customProviderApiKeys.clear();\n\t\tthis.loadError = undefined;\n\t\tthis.loadModels();\n\t}\n\n\t/**\n\t * Get any error from loading models.json (undefined if no error).\n\t */\n\tgetError(): string | undefined {\n\t\treturn this.loadError;\n\t}\n\n\tprivate loadModels(): void {\n\t\t// Load custom models from models.json first (to know which providers to skip/override)\n\t\tconst {\n\t\t\tmodels: customModels,\n\t\t\treplacedProviders,\n\t\t\toverrides,\n\t\t\terror,\n\t\t} = this.modelsJsonPath ? this.loadCustomModels(this.modelsJsonPath) : emptyCustomModelsResult();\n\n\t\tif (error) {\n\t\t\tthis.loadError = error;\n\t\t\t// Keep built-in models even if custom models failed to load\n\t\t}\n\n\t\tconst builtInModels = this.loadBuiltInModels(replacedProviders, overrides);\n\t\tconst combined = [...builtInModels, ...customModels];\n\n\t\t// Update github-copilot base URL based on OAuth credentials\n\t\tconst copilotCred = this.authStorage.get(\"github-copilot\");\n\t\tif (copilotCred?.type === \"oauth\") {\n\t\t\tconst domain = copilotCred.enterpriseUrl\n\t\t\t\t? (normalizeDomain(copilotCred.enterpriseUrl) ?? undefined)\n\t\t\t\t: undefined;\n\t\t\tconst baseUrl = getGitHubCopilotBaseUrl(copilotCred.access, domain);\n\t\t\tthis.models = combined.map((m) => (m.provider === \"github-copilot\" ? { ...m, baseUrl } : m));\n\t\t} else {\n\t\t\tthis.models = combined;\n\t\t}\n\t}\n\n\t/** Load built-in models, skipping replaced providers and applying overrides */\n\tprivate loadBuiltInModels(replacedProviders: Set<string>, overrides: Map<string, ProviderOverride>): Model<Api>[] {\n\t\treturn getProviders()\n\t\t\t.filter((provider) => !replacedProviders.has(provider))\n\t\t\t.flatMap((provider) => {\n\t\t\t\tconst models = getModels(provider as KnownProvider) as Model<Api>[];\n\t\t\t\tconst override = overrides.get(provider);\n\t\t\t\tif (!override) return models;\n\n\t\t\t\t// Apply baseUrl/headers override to all models of this provider\n\t\t\t\treturn models.map((m) => ({\n\t\t\t\t\t...m,\n\t\t\t\t\tbaseUrl: override.baseUrl ?? m.baseUrl,\n\t\t\t\t\theaders: override.headers ? { ...m.headers, ...override.headers } : m.headers,\n\t\t\t\t}));\n\t\t\t});\n\t}\n\n\tprivate loadCustomModels(modelsJsonPath: string): CustomModelsResult {\n\t\tif (!existsSync(modelsJsonPath)) {\n\t\t\treturn emptyCustomModelsResult();\n\t\t}\n\n\t\ttry {\n\t\t\tconst content = readFileSync(modelsJsonPath, \"utf-8\");\n\t\t\tconst config: ModelsConfig = JSON.parse(content);\n\n\t\t\t// Validate schema\n\t\t\tconst ajv = new Ajv();\n\t\t\tconst validate = ajv.compile(ModelsConfigSchema);\n\t\t\tif (!validate(config)) {\n\t\t\t\tconst errors =\n\t\t\t\t\tvalidate.errors?.map((e: any) => ` - ${e.instancePath || \"root\"}: ${e.message}`).join(\"\\n\") ||\n\t\t\t\t\t\"Unknown schema error\";\n\t\t\t\treturn emptyCustomModelsResult(`Invalid models.json schema:\\n${errors}\\n\\nFile: ${modelsJsonPath}`);\n\t\t\t}\n\n\t\t\t// Additional validation\n\t\t\tthis.validateConfig(config);\n\n\t\t\t// Separate providers into \"full replacement\" (has models) vs \"override-only\" (no models)\n\t\t\tconst replacedProviders = new Set<string>();\n\t\t\tconst overrides = new Map<string, ProviderOverride>();\n\n\t\t\tfor (const [providerName, providerConfig] of Object.entries(config.providers)) {\n\t\t\t\tif (providerConfig.models && providerConfig.models.length > 0) {\n\t\t\t\t\t// Has custom models -> full replacement\n\t\t\t\t\treplacedProviders.add(providerName);\n\t\t\t\t} else {\n\t\t\t\t\t// No models -> just override baseUrl/headers on built-in\n\t\t\t\t\toverrides.set(providerName, {\n\t\t\t\t\t\tbaseUrl: providerConfig.baseUrl,\n\t\t\t\t\t\theaders: providerConfig.headers,\n\t\t\t\t\t\tapiKey: providerConfig.apiKey,\n\t\t\t\t\t});\n\t\t\t\t\t// Store API key for fallback resolver\n\t\t\t\t\tif (providerConfig.apiKey) {\n\t\t\t\t\t\tthis.customProviderApiKeys.set(providerName, providerConfig.apiKey);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn { models: this.parseModels(config), replacedProviders, overrides, error: undefined };\n\t\t} catch (error) {\n\t\t\tif (error instanceof SyntaxError) {\n\t\t\t\treturn emptyCustomModelsResult(`Failed to parse models.json: ${error.message}\\n\\nFile: ${modelsJsonPath}`);\n\t\t\t}\n\t\t\treturn emptyCustomModelsResult(\n\t\t\t\t`Failed to load models.json: ${error instanceof Error ? error.message : error}\\n\\nFile: ${modelsJsonPath}`,\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate validateConfig(config: ModelsConfig): void {\n\t\tfor (const [providerName, providerConfig] of Object.entries(config.providers)) {\n\t\t\tconst hasProviderApi = !!providerConfig.api;\n\t\t\tconst models = providerConfig.models ?? [];\n\n\t\t\tif (models.length === 0) {\n\t\t\t\t// Override-only config: just needs baseUrl (to override built-in)\n\t\t\t\tif (!providerConfig.baseUrl) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Provider ${providerName}: must specify either \"baseUrl\" (for override) or \"models\" (for replacement).`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Full replacement: needs baseUrl and apiKey\n\t\t\t\tif (!providerConfig.baseUrl) {\n\t\t\t\t\tthrow new Error(`Provider ${providerName}: \"baseUrl\" is required when defining custom models.`);\n\t\t\t\t}\n\t\t\t\tif (!providerConfig.apiKey) {\n\t\t\t\t\tthrow new Error(`Provider ${providerName}: \"apiKey\" is required when defining custom models.`);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (const modelDef of models) {\n\t\t\t\tconst hasModelApi = !!modelDef.api;\n\n\t\t\t\tif (!hasProviderApi && !hasModelApi) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Provider ${providerName}, model ${modelDef.id}: no \"api\" specified. Set at provider or model level.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (!modelDef.id) throw new Error(`Provider ${providerName}: model missing \"id\"`);\n\t\t\t\tif (!modelDef.name) throw new Error(`Provider ${providerName}: model missing \"name\"`);\n\t\t\t\tif (modelDef.contextWindow <= 0)\n\t\t\t\t\tthrow new Error(`Provider ${providerName}, model ${modelDef.id}: invalid contextWindow`);\n\t\t\t\tif (modelDef.maxTokens <= 0)\n\t\t\t\t\tthrow new Error(`Provider ${providerName}, model ${modelDef.id}: invalid maxTokens`);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate parseModels(config: ModelsConfig): Model<Api>[] {\n\t\tconst models: Model<Api>[] = [];\n\n\t\tfor (const [providerName, providerConfig] of Object.entries(config.providers)) {\n\t\t\tconst modelDefs = providerConfig.models ?? [];\n\t\t\tif (modelDefs.length === 0) continue; // Override-only, no custom models\n\n\t\t\t// Store API key config for fallback resolver\n\t\t\tif (providerConfig.apiKey) {\n\t\t\t\tthis.customProviderApiKeys.set(providerName, providerConfig.apiKey);\n\t\t\t}\n\n\t\t\tfor (const modelDef of modelDefs) {\n\t\t\t\tconst api = modelDef.api || providerConfig.api;\n\t\t\t\tif (!api) continue;\n\n\t\t\t\t// Merge headers: provider headers are base, model headers override\n\t\t\t\tlet headers =\n\t\t\t\t\tproviderConfig.headers || modelDef.headers\n\t\t\t\t\t\t? { ...providerConfig.headers, ...modelDef.headers }\n\t\t\t\t\t\t: undefined;\n\n\t\t\t\t// If authHeader is true, add Authorization header with resolved API key\n\t\t\t\tif (providerConfig.authHeader && providerConfig.apiKey) {\n\t\t\t\t\tconst resolvedKey = resolveApiKeyConfig(providerConfig.apiKey);\n\t\t\t\t\tif (resolvedKey) {\n\t\t\t\t\t\theaders = { ...headers, Authorization: `Bearer ${resolvedKey}` };\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// baseUrl is validated to exist for providers with models\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelDef.id,\n\t\t\t\t\tname: modelDef.name,\n\t\t\t\t\tapi: api as Api,\n\t\t\t\t\tprovider: providerName,\n\t\t\t\t\tbaseUrl: providerConfig.baseUrl!,\n\t\t\t\t\treasoning: modelDef.reasoning,\n\t\t\t\t\tinput: modelDef.input as (\"text\" | \"image\")[],\n\t\t\t\t\tcost: modelDef.cost,\n\t\t\t\t\tcontextWindow: modelDef.contextWindow,\n\t\t\t\t\tmaxTokens: modelDef.maxTokens,\n\t\t\t\t\theaders,\n\t\t\t\t\tcompat: modelDef.compat,\n\t\t\t\t} as Model<Api>);\n\t\t\t}\n\t\t}\n\n\t\treturn models;\n\t}\n\n\t/**\n\t * Get all models (built-in + custom).\n\t * If models.json had errors, returns only built-in models.\n\t */\n\tgetAll(): Model<Api>[] {\n\t\treturn this.models;\n\t}\n\n\t/**\n\t * Get only models that have auth configured.\n\t * This is a fast check that doesn't refresh OAuth tokens.\n\t */\n\tgetAvailable(): Model<Api>[] {\n\t\treturn this.models.filter((m) => this.authStorage.hasAuth(m.provider));\n\t}\n\n\t/**\n\t * Find a model by provider and ID.\n\t */\n\tfind(provider: string, modelId: string): Model<Api> | undefined {\n\t\treturn this.models.find((m) => m.provider === provider && m.id === modelId) ?? undefined;\n\t}\n\n\t/**\n\t * Get API key for a model.\n\t */\n\tasync getApiKey(model: Model<Api>): Promise<string | undefined> {\n\t\treturn this.authStorage.getApiKey(model.provider);\n\t}\n\n\t/**\n\t * Check if a model is using OAuth credentials (subscription).\n\t */\n\tisUsingOAuth(model: Model<Api>): boolean {\n\t\tconst cred = this.authStorage.get(model.provider);\n\t\treturn cred?.type === \"oauth\";\n\t}\n}\n"]}