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 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→HookContextHookCommandContextis now a new interface extendingHookContextwith session control methods
Event changes:
- The monolithic
sessionevent 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_switchandsession_switchevents now includereason: "new" | "resume"to distinguish between/newand/resume- New
session_before_treeandsession_treeevents for/treenavigation (hook can provide custom branch summary) - New
before_agent_startevent: inject messages before the agent loop starts - New
contextevent: modify messages non-destructively before each LLM call - Session entries are no longer passed in events. Use
ctx.sessionManager.getEntries()orctx.sessionManager.getBranch()instead
API changes:
pi.send(text, attachments?)→pi.sendMessage(message, triggerTurn?)(createsCustomMessageEntry)- New
pi.appendEntry(customType, data?)for hook state persistence (not in LLM context) - New
pi.registerCommand(name, options)for custom slash commands (handler receivesHookCommandContext) - 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.themegetter for styling text with theme colors ctx.exec()moved topi.exec()ctx.sessionFile→ctx.sessionManager.getSessionFile()- New
ctx.modelRegistryandctx.modelfor API key resolution
HookCommandContext (slash commands only):
ctx.waitForIdle()- wait for agent to finish streamingctx.newSession(options?)- create new sessions with optional setup callbackctx.branch(entryId)- branch from a specific entryctx.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:
hookTimeoutsetting (hooks no longer have timeouts; use Ctrl+C to abort)resolveApiKeyparameter (usectx.modelRegistry.getApiKey(model))
See docs/hooks.md and 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→CustomToolToolAPI→CustomToolAPIToolContext→CustomToolContextToolSessionEvent→CustomToolSessionEvent
Execute signature changed:
// 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 streamingctx.hasQueuedMessages()- check if user has queued messages (skip interactive prompts)ctx.abort()- abort current operation (fire-and-forget)
Session event changes:
CustomToolSessionEventnow only hasreasonandpreviousSessionFile- Session entries are no longer in the event. Use
ctx.sessionManager.getBranch()orctx.sessionManager.getEntries()to reconstruct state - Reasons:
"start" | "switch" | "branch" | "tree" | "shutdown"(no separate"new"reason;/newtriggers"switch") dispose()method removed. UseonSessionwithreason: "shutdown"for cleanup
See docs/custom-tools.md and examples/custom-tools/ for the current API.
SDK Migration
Type changes:
CustomAgentTool→CustomToolAppMessage→AgentMessagesessionFilereturnsstring | undefined(wasstring | null)modelreturnsModel | undefined(wasModel | null)Attachmenttype removed. UseImageContentfrom@mariozechner/pi-aiinstead. 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 optionalparentSessionfor lineage trackingnewSession()andswitchSession()now returnPromise<boolean>(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 optionalparentSessionfor lineage trackingcreateBranchedSessionFromEntries(entries, index)→createBranchedSession(leafId)SessionHeader.branchedFrom→SessionHeader.parentSessionsaveCompaction(entry)→appendCompaction(summary, firstKeptEntryId, tokensBefore, details?)getEntries()now excludes the session header (usegetHeader()separately)getSessionFile()returnsstring | 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.
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→convertToLlmSessionContextaliasLoadedSessionremoved
See docs/sdk.md and examples/sdk/ for the current API.
RPC Migration
Session commands:
resetcommand →new_sessioncommand with optionalparentSessionfield
Branching commands:
branchcommand:entryIndex→entryIdget_branch_messagesresponse:entryIndex→entryId
Type changes:
- Messages are now
AgentMessage(wasAppMessage) promptcommand:attachmentsfield replaced withimagesfield usingImageContentformat
Compaction events:
auto_compaction_startnow includesreasonfield ("threshold"or"overflow")auto_compaction_endnow includeswillRetryfieldcompactresponse includes fullCompactionResult(summary,firstKeptEntryId,tokensBefore,details)
See 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:
readFilesandmodifiedFilesarrays indetails, 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.
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
lto 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 componentscustomMessageBg: background for hook-injected messages (CustomMessageEntry)customMessageText: text color for hook messagescustomMessageLabel: label color for hook messages (the[customType]prefix)
Total color count increased from 46 to 50. See docs/theme.md for the full color list and copy values from the built-in dark/light themes.
Settings:
enabledModels: allowlist models insettings.json(same format as--modelsCLI)
Added
ctx.ui.setStatus(key, text)for hooks to display persistent status text in the footer (#385 by @prateekmedia)ctx.ui.themegetter for styling status text and other output with theme colors/sharecommand to upload session as a secret GitHub gist and get a shareable URL via shittycodingagent.ai (#380)- HTML export now includes a tree visualization sidebar for navigating session branches (#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
exportsection in theme JSON (#387 by @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. thinkingTexttheme token: Configurable color for thinking block text. (#366 by @paulbettner)
Changed
- Entry IDs: Session entries now use short 8-character hex IDs instead of full UUIDs
- API key priority:
ANTHROPIC_OAUTH_TOKENnow takes precedence overANTHROPIC_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
<style>that could break DOM rendering - Crash when displaying bash output containing Unicode format characters like U+0600-U+0604 (#372 by @HACKE-RC)
- Footer shows full session stats: Token usage and cost now include all messages, not just those after compaction. (#322)
- Status messages spam chat log: Rapidly changing settings (e.g., thinking level via Shift+Tab) would add multiple status lines. Sequential status updates now coalesce into a single line. (#365 by @paulbettner)
- Toggling thinking blocks during streaming shows nothing: Pressing Ctrl+T while streaming would hide the current message until streaming completed.
- Resuming session resets thinking level to off: Initial model and thinking level were not saved to session file, causing
--resume/--continueto default tooff. (#342 by @aliou) - Hook
tool_resultevent ignores errors from custom tools: Thetool_resulthook event was never emitted when tools threw errors, and always hadisError: falsefor successful executions. Now emits the event with correctisErrorvalue in both success and error cases. (#374 by @nicobailon) - Edit tool fails on Windows due to CRLF line endings: Files with CRLF line endings now match correctly when LLMs send LF-only text. Line endings are normalized before matching and restored to original style on write. (#355 by @Pratham-Dubey)
- Edit tool fails on files with UTF-8 BOM: Files with UTF-8 BOM marker could cause "text not found" errors since the LLM doesn't include the invisible BOM character. BOM is now stripped before matching and restored on write. (#394 by @prathamdby)
- Use bash instead of sh on Unix: Fixed shell commands using
/bin/shinstead of/bin/bashon Unix systems. (#328 by @dnouri) - OAuth login URL clickable: Made OAuth login URLs clickable in terminal. (#349 by @Cursivez)
- Improved error messages: Better error messages when
apiKeyormodelare missing. (#346 by @ronyrus) - Session file validation:
findMostRecentSession()now validates session headers before returning, preventing non-session JSONL files from being loaded - Compaction error handling:
generateSummary()andgenerateTurnPrefixSummary()now throw on LLM errors instead of returning empty strings - Compaction with branched sessions: Fixed compaction incorrectly including entries from abandoned branches, causing token overflow errors. Compaction now uses
sessionManager.getPath()to work only on the current branch path, eliminating 80+ lines of duplicate entry collection logic betweenprepareCompaction()andcompact() - enabledModels glob patterns:
--modelsandenabledModelsnow support glob patterns likegithub-copilot/*or*sonnet*. Previously, patterns were only matched literally or via substring search. (#337)