# Changelog ## [Unreleased] ### Breaking Changes - **Queue API replaced with steer/followUp**: The `queueMessage()` method has been split into two methods with different delivery semantics ([#403](https://github.com/badlogic/pi-mono/issues/403)): - `steer(text)`: Interrupts the agent mid-run (Enter while streaming). Delivered after current tool execution. - `followUp(text)`: Waits until the agent finishes (Alt+Enter while streaming). Delivered only when agent stops. - **Settings renamed**: `queueMode` setting renamed to `steeringMode`. Added new `followUpMode` setting. Old settings.json files are migrated automatically. - **AgentSession methods renamed**: - `queueMessage()` → `steer()` and `followUp()` - `queueMode` getter → `steeringMode` and `followUpMode` getters - `setQueueMode()` → `setSteeringMode()` and `setFollowUpMode()` - `queuedMessageCount` → `pendingMessageCount` - `getQueuedMessages()` → `getSteeringMessages()` and `getFollowUpMessages()` - `clearQueue()` now returns `{ steering: string[], followUp: string[] }` - `hasQueuedMessages()` → `hasPendingMessages()` - **Hook API signature changed**: `pi.sendMessage()` second parameter changed from `triggerTurn?: boolean` to `options?: { triggerTurn?, deliverAs? }`. Use `deliverAs: "followUp"` for follow-up delivery. Affects both hooks and internal `sendHookMessage()` method. - **RPC API changes**: - `queue_message` command → `steer` and `follow_up` commands - `set_queue_mode` command → `set_steering_mode` and `set_follow_up_mode` commands - `RpcSessionState.queueMode` → `steeringMode` and `followUpMode` - **Settings UI**: "Queue mode" setting split into "Steering mode" and "Follow-up mode" ### Added - Vertex AI provider (`google-vertex`): access Gemini models via Google Cloud Vertex AI using Application Default Credentials ([#300](https://github.com/badlogic/pi-mono/pull/300) by [@default-anton](https://github.com/default-anton)) - Built-in provider overrides in `models.json`: override just `baseUrl` to route a built-in provider through a proxy while keeping all its models, or define `models` to fully replace the provider ([#406](https://github.com/badlogic/pi-mono/pull/406) by [@yevhen](https://github.com/yevhen)) - Automatic image resizing: images larger than 2000x2000 are resized for better model compatibility. Original dimensions are injected into the prompt. Controlled via `/settings` or `images.autoResize` in settings.json. ([#402](https://github.com/badlogic/pi-mono/pull/402) by [@mitsuhiko](https://github.com/mitsuhiko)) - Alt+Enter keybind to queue follow-up messages while agent is streaming - `Theme` and `ThemeColor` types now exported for hooks using `ctx.ui.custom()` - Terminal window title now displays "pi - dirname" to identify which project session you're in ([#407](https://github.com/badlogic/pi-mono/pull/407) by [@kaofelix](https://github.com/kaofelix)) ### Changed - Editor component now uses word wrapping instead of character-level wrapping for better readability ([#382](https://github.com/badlogic/pi-mono/pull/382) by [@nickseelert](https://github.com/nickseelert)) ### Fixed - Shift+Space, Shift+Backspace, and Shift+Delete now work correctly in Kitty-protocol terminals (Kitty, WezTerm, etc.) instead of being silently ignored ([#411](https://github.com/badlogic/pi-mono/pull/411) by [@nathyong](https://github.com/nathyong)) - `AgentSession.prompt()` now throws if called while the agent is already streaming, preventing race conditions. Use `steer()` or `followUp()` to queue messages during streaming. - Ctrl+C now works like Escape in selector components, so mashing Ctrl+C will eventually close the program ([#400](https://github.com/badlogic/pi-mono/pull/400) by [@mitsuhiko](https://github.com/mitsuhiko)) ## [0.31.1] - 2026-01-02 ### Fixed - Model selector no longer allows negative index when pressing arrow keys before models finish loading ([#398](https://github.com/badlogic/pi-mono/pull/398) by [@mitsuhiko](https://github.com/mitsuhiko)) - Type guard functions (`isBashToolResult`, etc.) now exported at runtime, not just in type declarations ([#397](https://github.com/badlogic/pi-mono/issues/397)) ## [0.31.0] - 2026-01-02 This release introduces session trees for in-place branching, major API changes to hooks and custom tools, and structured compaction with file tracking. ### Session Tree Sessions now use a tree structure with `id`/`parentId` fields. This enables in-place branching: navigate to any previous point with `/tree`, continue from there, and switch between branches while preserving all history in a single file. **Existing sessions are automatically migrated** (v1 → v2) on first load. No manual action required. New entry types: `BranchSummaryEntry` (context from abandoned branches), `CustomEntry` (hook state), `CustomMessageEntry` (hook-injected messages), `LabelEntry` (bookmarks). See [docs/session.md](docs/session.md) for the file format and `SessionManager` API. ### Hooks Migration The hooks API has been restructured with more granular events and better session access. **Type renames:** - `HookEventContext` → `HookContext` - `HookCommandContext` is now a new interface extending `HookContext` with session control methods **Event changes:** - The monolithic `session` event is now split into granular events: `session_start`, `session_before_switch`, `session_switch`, `session_before_branch`, `session_branch`, `session_before_compact`, `session_compact`, `session_shutdown` - `session_before_switch` and `session_switch` events now include `reason: "new" | "resume"` to distinguish between `/new` and `/resume` - New `session_before_tree` and `session_tree` events for `/tree` navigation (hook can provide custom branch summary) - New `before_agent_start` event: inject messages before the agent loop starts - New `context` event: modify messages non-destructively before each LLM call - Session entries are no longer passed in events. Use `ctx.sessionManager.getEntries()` or `ctx.sessionManager.getBranch()` instead **API changes:** - `pi.send(text, attachments?)` → `pi.sendMessage(message, triggerTurn?)` (creates `CustomMessageEntry`) - New `pi.appendEntry(customType, data?)` for hook state persistence (not in LLM context) - New `pi.registerCommand(name, options)` for custom slash commands (handler receives `HookCommandContext`) - New `pi.registerMessageRenderer(customType, renderer)` for custom TUI rendering - New `ctx.isIdle()`, `ctx.abort()`, `ctx.hasQueuedMessages()` for agent state (available in all events) - New `ctx.ui.editor(title, prefill?)` for multi-line text editing with Ctrl+G external editor support - New `ctx.ui.custom(component)` for full TUI component rendering with keyboard focus - New `ctx.ui.setStatus(key, text)` for persistent status text in footer (multiple hooks can set their own) - New `ctx.ui.theme` getter for styling text with theme colors - `ctx.exec()` moved to `pi.exec()` - `ctx.sessionFile` → `ctx.sessionManager.getSessionFile()` - New `ctx.modelRegistry` and `ctx.model` for API key resolution **HookCommandContext (slash commands only):** - `ctx.waitForIdle()` - wait for agent to finish streaming - `ctx.newSession(options?)` - create new sessions with optional setup callback - `ctx.branch(entryId)` - branch from a specific entry - `ctx.navigateTree(targetId, options?)` - navigate the session tree These methods are only on `HookCommandContext` (not `HookContext`) because they can deadlock if called from event handlers that run inside the agent loop. **Removed:** - `hookTimeout` setting (hooks no longer have timeouts; use Ctrl+C to abort) - `resolveApiKey` parameter (use `ctx.modelRegistry.getApiKey(model)`) See [docs/hooks.md](docs/hooks.md) and [examples/hooks/](examples/hooks/) for the current API. ### Custom Tools Migration The custom tools API has been restructured to mirror the hooks pattern with a context object. **Type renames:** - `CustomAgentTool` → `CustomTool` - `ToolAPI` → `CustomToolAPI` - `ToolContext` → `CustomToolContext` - `ToolSessionEvent` → `CustomToolSessionEvent` **Execute signature changed:** ```typescript // Before (v0.30.2) execute(toolCallId, params, signal, onUpdate) // After execute(toolCallId, params, onUpdate, ctx, signal?) ``` The new `ctx: CustomToolContext` provides `sessionManager`, `modelRegistry`, `model`, and agent state methods: - `ctx.isIdle()` - check if agent is streaming - `ctx.hasQueuedMessages()` - check if user has queued messages (skip interactive prompts) - `ctx.abort()` - abort current operation (fire-and-forget) **Session event changes:** - `CustomToolSessionEvent` now only has `reason` and `previousSessionFile` - Session entries are no longer in the event. Use `ctx.sessionManager.getBranch()` or `ctx.sessionManager.getEntries()` to reconstruct state - Reasons: `"start" | "switch" | "branch" | "tree" | "shutdown"` (no separate `"new"` reason; `/new` triggers `"switch"`) - `dispose()` method removed. Use `onSession` with `reason: "shutdown"` for cleanup See [docs/custom-tools.md](docs/custom-tools.md) and [examples/custom-tools/](examples/custom-tools/) for the current API. ### SDK Migration **Type changes:** - `CustomAgentTool` → `CustomTool` - `AppMessage` → `AgentMessage` - `sessionFile` returns `string | undefined` (was `string | null`) - `model` returns `Model | undefined` (was `Model | null`) - `Attachment` type removed. Use `ImageContent` from `@mariozechner/pi-ai` instead. Add images directly to message content arrays. **AgentSession API:** - `branch(entryIndex: number)` → `branch(entryId: string)` - `getUserMessagesForBranching()` returns `{ entryId, text }` instead of `{ entryIndex, text }` - `reset()` → `newSession(options?)` where options has optional `parentSession` for lineage tracking - `newSession()` and `switchSession()` now return `Promise` (false if cancelled by hook) - New `navigateTree(targetId, options?)` for in-place tree navigation **Hook integration:** - New `sendHookMessage(message, triggerTurn?)` for hook message injection **SessionManager API:** - Method renames: `saveXXX()` → `appendXXX()` (e.g., `appendMessage`, `appendCompaction`) - `branchInPlace()` → `branch()` - `reset()` → `newSession(options?)` with optional `parentSession` for lineage tracking - `createBranchedSessionFromEntries(entries, index)` → `createBranchedSession(leafId)` - `SessionHeader.branchedFrom` → `SessionHeader.parentSession` - `saveCompaction(entry)` → `appendCompaction(summary, firstKeptEntryId, tokensBefore, details?)` - `getEntries()` now excludes the session header (use `getHeader()` separately) - `getSessionFile()` returns `string | undefined` (undefined for in-memory sessions) - New tree methods: `getTree()`, `getBranch()`, `getLeafId()`, `getLeafEntry()`, `getEntry()`, `getChildren()`, `getLabel()` - New append methods: `appendCustomEntry()`, `appendCustomMessageEntry()`, `appendLabelChange()` - New branch methods: `branch(entryId)`, `branchWithSummary()` **ModelRegistry (new):** `ModelRegistry` is a new class that manages model discovery and API key resolution. It combines built-in models with custom models from `models.json` and resolves API keys via `AuthStorage`. ```typescript import { discoverAuthStorage, discoverModels } from "@mariozechner/pi-coding-agent"; const authStorage = discoverAuthStorage(); // ~/.pi/agent/auth.json const modelRegistry = discoverModels(authStorage); // + ~/.pi/agent/models.json // Get all models (built-in + custom) const allModels = modelRegistry.getAll(); // Get only models with valid API keys const available = await modelRegistry.getAvailable(); // Find specific model const model = modelRegistry.find("anthropic", "claude-sonnet-4-20250514"); // Get API key for a model const apiKey = await modelRegistry.getApiKey(model); ``` This replaces the old `resolveApiKey` callback pattern. Hooks and custom tools access it via `ctx.modelRegistry`. **Renamed exports:** - `messageTransformer` → `convertToLlm` - `SessionContext` alias `LoadedSession` removed See [docs/sdk.md](docs/sdk.md) and [examples/sdk/](examples/sdk/) for the current API. ### RPC Migration **Session commands:** - `reset` command → `new_session` command with optional `parentSession` field **Branching commands:** - `branch` command: `entryIndex` → `entryId` - `get_branch_messages` response: `entryIndex` → `entryId` **Type changes:** - Messages are now `AgentMessage` (was `AppMessage`) - `prompt` command: `attachments` field replaced with `images` field using `ImageContent` format **Compaction events:** - `auto_compaction_start` now includes `reason` field (`"threshold"` or `"overflow"`) - `auto_compaction_end` now includes `willRetry` field - `compact` response includes full `CompactionResult` (`summary`, `firstKeptEntryId`, `tokensBefore`, `details`) See [docs/rpc.md](docs/rpc.md) for the current protocol. ### Structured Compaction Compaction and branch summarization now use a structured output format: - Clear sections: Goal, Progress, Key Information, File Operations - File tracking: `readFiles` and `modifiedFiles` arrays in `details`, accumulated across compactions - Conversations are serialized to text before summarization to prevent the model from "continuing" them The `before_compact` and `before_tree` hook events allow custom compaction implementations. See [docs/compaction.md](docs/compaction.md). ### Interactive Mode **`/tree` command:** - Navigate the full session tree in-place - Search by typing, page with ←/→ - Filter modes (Ctrl+O): default → no-tools → user-only → labeled-only → all - Press `l` to label entries as bookmarks - Selecting a branch switches context and optionally injects a summary of the abandoned branch **Entry labels:** - Bookmark any entry via `/tree` → select → `l` - Labels appear in tree view and persist as `LabelEntry` **Theme changes (breaking for custom themes):** Custom themes must add these new color tokens or they will fail to load: - `selectedBg`: background for selected/highlighted items in tree selector and other components - `customMessageBg`: background for hook-injected messages (`CustomMessageEntry`) - `customMessageText`: text color for hook messages - `customMessageLabel`: label color for hook messages (the `[customType]` prefix) Total color count increased from 46 to 50. See [docs/theme.md](docs/theme.md) for the full color list and copy values from the built-in dark/light themes. **Settings:** - `enabledModels`: allowlist models in `settings.json` (same format as `--models` CLI) ### Added - `ctx.ui.setStatus(key, text)` for hooks to display persistent status text in the footer ([#385](https://github.com/badlogic/pi-mono/pull/385) by [@prateekmedia](https://github.com/prateekmedia)) - `ctx.ui.theme` getter for styling status text and other output with theme colors - `/share` command to upload session as a secret GitHub gist and get a shareable URL via shittycodingagent.ai ([#380](https://github.com/badlogic/pi-mono/issues/380)) - HTML export now includes a tree visualization sidebar for navigating session branches ([#375](https://github.com/badlogic/pi-mono/issues/375)) - HTML export supports keyboard shortcuts: Ctrl+T to toggle thinking blocks, Ctrl+O to toggle tool outputs - HTML export supports theme-configurable background colors via optional `export` section in theme JSON ([#387](https://github.com/badlogic/pi-mono/pull/387) by [@mitsuhiko](https://github.com/mitsuhiko)) - HTML export syntax highlighting now uses theme colors and matches TUI rendering - **Snake game example hook**: Demonstrates `ui.custom()`, `registerCommand()`, and session persistence. See [examples/hooks/snake.ts](examples/hooks/snake.ts). - **`thinkingText` theme token**: Configurable color for thinking block text. ([#366](https://github.com/badlogic/pi-mono/pull/366) by [@paulbettner](https://github.com/paulbettner)) ### Changed - **Entry IDs**: Session entries now use short 8-character hex IDs instead of full UUIDs - **API key priority**: `ANTHROPIC_OAUTH_TOKEN` now takes precedence over `ANTHROPIC_API_KEY` - HTML export template split into separate files (template.html, template.css, template.js) for easier maintenance ### Fixed - HTML export now properly sanitizes user messages containing HTML tags like `