- Extract execCommand to src/core/exec.ts, shared by hooks and custom-tools
- Rename CustomMessageRenderer -> HookMessageRenderer
- Rename registerCustomMessageRenderer -> registerMessageRenderer
- Renderer now receives HookMessage instead of CustomMessageEntry
- CustomMessageComponent now has setExpanded() and responds to Ctrl+E toggle
- Re-export ExecOptions/ExecResult from exec.ts for backward compatibility
Breaking changes to Hook API:
- pi.send(text, attachments?) replaced with pi.sendMessage(message, triggerTurn?)
- Creates CustomMessageEntry instead of user messages
- Properly handles queuing during streaming via agent loop
- Supports optional turn triggering when idle
- New pi.appendEntry(customType, data?) for hook state persistence
- New pi.registerCommand(name, options) for custom slash commands
- Handler types renamed: SendHandler -> SendMessageHandler, new AppendEntryHandler
Implementation:
- AgentSession.sendHookMessage() handles all three cases:
- Streaming: queues message with _hookData marker, agent loop processes it
- Not streaming + triggerTurn: appends to state/session, calls agent.continue()
- Not streaming + no trigger: appends to state/session only
- message_end handler routes based on _hookData presence to correct persistence
- HookRunner gains getRegisteredCommands() and getCommand() methods
New types: HookMessage<T>, RegisteredCommand, CommandContext
- Add renderCustomMessage to HookAPI for registering custom renderers
- Add CustomMessageRenderer type and CustomMessageRenderOptions
- Store customMessageRenderers in LoadedHook
- Add getCustomMessageRenderer(customType) to HookRunner
- SessionContext.entries now aligned with messages (same length, corresponding indices)
TUI can now correlate messages with their source entries to identify
custom_message entries and use hook-provided renderers.
- Use default params instead of resolvedAgentDir pattern in loaders
- Remove unused DiscoverAndLoadHooksOptions interface
- Remove redundant github-copilot special casing in getAvailableModels