Commit graph

60 commits

Author SHA1 Message Date
Mario Zechner
f2b89d5ec5 Merge branch 'feat/ctrl-n-external-editor' - configurable keybindings (#405) 2026-01-03 22:51:12 +01:00
Will Hampson
d6c39655e6
docs(coding-agent): correct filename in subagent example README (#427)
The README referenced subagent.ts but the actual file is index.ts.

Co-authored-by: Will Hampson <whamp@ggl.slmail.me>
2026-01-03 22:36:58 +01:00
Helmut Januschka
8f2682578b feat: configurable keybindings for all editor and app actions
All keybindings configurable via ~/.pi/agent/keybindings.json

Editor actions:
- cursorUp, cursorDown, cursorLeft, cursorRight
- cursorWordLeft, cursorWordRight, cursorLineStart, cursorLineEnd
- deleteCharBackward, deleteCharForward, deleteWordBackward
- deleteToLineStart, deleteToLineEnd
- newLine, submit, tab
- selectUp, selectDown, selectConfirm, selectCancel

App actions:
- interrupt, clear, exit, suspend
- cycleThinkingLevel, cycleModelForward, cycleModelBackward
- selectModel, expandTools, toggleThinking, externalEditor

Also adds support for numpad Enter key (Kitty protocol codepoint 57414
and SS3 M sequence)

Example emacs-style keybindings.json:
{
  "cursorUp": ["up", "ctrl+p"],
  "cursorDown": ["down", "ctrl+n"],
  "cursorLeft": ["left", "ctrl+b"],
  "cursorRight": ["right", "ctrl+f"],
  "deleteCharForward": ["delete", "ctrl+d"],
  "cycleModelForward": "ctrl+o"
}
2026-01-03 08:23:56 +01:00
Mario Zechner
8c227052d3 fix(coding-agent): export Theme and ThemeColor for hooks to use proper types 2026-01-03 00:13:26 +01:00
Mario Zechner
65d3081d80 fix(coding-agent): fix Theme type cast in todo hook example 2026-01-03 00:13:26 +01:00
Mario Zechner
9d8230dfc6 feat(coding-agent): expose deliverAs option in hook sendMessage() API
- pi.sendMessage(msg, options?) now accepts { triggerTurn?, deliverAs? }
- deliverAs: 'steer' (default) or 'followUp' controls delivery timing
- Update all mode handlers to pass options through
- Update file-trigger example to use new API
- Update CHANGELOG
2026-01-03 00:13:26 +01:00
Mario Zechner
a980998464 Revert /todos to custom TUI component 2026-01-03 00:13:25 +01:00
Mario Zechner
cc7823eb45 Change /todos to open in external editor instead of custom TUI 2026-01-03 00:13:25 +01:00
Mario Zechner
74dc5e7c49 Add todo hook companion to todo custom tool
- /todos command displays all todos on current branch with custom UI
- Update hooks.md to clarify components must not be wrapped in Box/Container
- Cross-reference tool and hook in example READMEs
2026-01-03 00:13:25 +01:00
Mario Zechner
02175d908b Update docs for ctx.ui.editor() and handoff example
- Added ctx.ui.editor() to hooks.md and custom-tools.md
- Added ctx.ui.editor() to CHANGELOG.md
- Added handoff.ts to examples/hooks/README.md
2026-01-02 01:26:21 +01:00
Mario Zechner
d51770a63d fix(coding-agent): prevent full re-renders during write tool streaming
Move line count from header to footer to avoid changing the first line
during streaming, which was triggering full screen re-renders in the
TUI's differential rendering logic.
2026-01-02 01:11:06 +01:00
Mario Zechner
91c52de8be Use serializeConversation in handoff hook
Properly handles all message types including tool calls, thinking,
and tool results instead of manual text extraction.
2026-01-02 00:45:03 +01:00
Mario Zechner
ace0063a00 Add handoff example hook
Transfers context to a new focused session instead of compacting.
User provides a goal, hook generates a prompt with relevant context,
creates a new session with parent tracking, and loads the prompt
as a draft in the editor for review.

Inspired by Amp's handoff feature.
2026-01-02 00:42:11 +01:00
Mario Zechner
484d7e06bb Consolidate session events: remove session_before_new/session_new, add reason field to switch events
- Remove session_before_new and session_new hook events
- Add reason: 'new' | 'resume' to session_before_switch and session_switch events
- Remove 'new' reason from custom tool onSession (use 'switch' for both /new and /resume)
- Rename reset() to newSession(options?) in AgentSession
- Add NewSessionOptions with optional parentSession for lineage tracking
- Rename branchedFrom to parentSession in SessionHeader
- Rename RPC reset command to new_session with optional parentSession
- Update example hooks to use new event structure
- Update documentation and changelog

Based on discussion in #293
2026-01-01 23:31:26 +01:00
Mario Zechner
dccdf91b8c Add ctx.ui.theme getter for styling status text with theme colors
- Add theme property to HookUIContext interface
- Implement in interactive, RPC, and no-op contexts
- Add status-line.ts example hook
- Document styling with theme colors in hooks.md
2026-01-01 21:58:01 +01:00
Mario Zechner
6f7c10e323 Add setEditorText/getEditorText to hook UI context, improve custom() API
- Add setEditorText() and getEditorText() to HookUIContext for prompt generator pattern
- custom() now accepts async factories for fire-and-forget work
- Add CancellableLoader component to tui package
- Add BorderedLoader component for hooks with cancel UI
- Export HookAPI, HookContext, HookFactory from main package
- Update all examples to import from packages instead of relative paths
- Update hooks.md and custom-tools.md documentation

fixes #350
2026-01-01 00:04:56 +01:00
Mario Zechner
8e1e99ca05 Change branch() to use entryId instead of entryIndex
- AgentSession.branch(entryId: string) now takes entry ID
- SessionBeforeBranchEvent.entryId replaces entryIndex
- getUserMessagesForBranching() returns entryId
- Update RPC types and client
- Update UserMessageSelectorComponent
- Update hook examples and tests
- Update docs (hooks.md, sdk.md)
2025-12-31 13:47:34 +01:00
Mario Zechner
027d39aa33 Update custom-compaction example to use serializeConversation
Also fix docs to show convertToLlm is needed first.
2025-12-31 13:24:23 +01:00
Mario Zechner
19c4182c21 Reorder execute params: (toolCallId, params, onUpdate, ctx, signal?)
Optional signal now at the end for cleaner API
2025-12-31 12:14:28 +01:00
Mario Zechner
568150f18b Rework custom tools API with CustomToolContext
- CustomAgentTool renamed to CustomTool
- ToolAPI renamed to CustomToolAPI
- ToolContext renamed to CustomToolContext
- ToolSessionEvent renamed to CustomToolSessionEvent
- Added CustomToolContext parameter to execute() and onSession()
- CustomToolFactory now returns CustomTool<any, any> for type compatibility
- dispose() replaced with onSession({ reason: 'shutdown' })
- Added wrapCustomTool() to convert CustomTool to AgentTool
- Session exposes setToolUIContext() instead of leaking internals
- Fix ToolExecutionComponent to sync with toolOutputExpanded state
- Update all custom tool examples for new API
2025-12-31 12:05:24 +01:00
Mario Zechner
ddda8b124c refactor(coding-agent): fix compaction for branched sessions, consolidate hook context types
Compaction API:
- prepareCompaction() now takes (pathEntries, settings) only
- CompactionPreparation restructured: removed cutPoint/messagesToKeep/boundaryStart, added turnPrefixMessages/isSplitTurn/previousSummary/fileOps/settings
- compact() now takes (preparation, model, apiKey, customInstructions?, signal?)
- Fixed token overflow by using getPath() instead of getEntries()

Hook types:
- HookEventContext renamed to HookContext
- HookCommandContext removed, RegisteredCommand.handler takes (args, ctx)
- HookContext now includes model field
- SessionBeforeCompactEvent: removed previousCompactions/model, added branchEntries
- SessionBeforeTreeEvent: removed model (use ctx.model)
- HookRunner.initialize() added for modes to set up callbacks
2025-12-31 02:24:24 +01:00
Mario Zechner
d6283f99dc refactor(hooks): split session events into individual typed events
Major changes:
- Replace monolithic SessionEvent with reason discriminator with individual
  event types: session_start, session_before_switch, session_switch,
  session_before_new, session_new, session_before_branch, session_branch,
  session_before_compact, session_compact, session_shutdown
- Each event has dedicated result type (SessionBeforeSwitchResult, etc.)
- HookHandler type now allows bare return statements (void in return type)
- HookAPI.on() has proper overloads for each event with correct typing

Additional fixes:
- AgentSession now always subscribes to agent in constructor (was only
  subscribing when external subscribe() called, breaking internal handlers)
- Standardize on undefined over null throughout codebase
- HookUIContext methods return undefined instead of null
- SessionManager methods return undefined instead of null
- Simplify hook exports to 'export type * from types.js'
- Add detailed JSDoc for skipConversationRestore vs cancel
- Fix createBranchedSession to rebuild index in persist mode
- newSession() now returns the session file path

Updated all example hooks, tests, and emission sites to use new event types.
2025-12-30 22:42:22 +01:00
Mario Zechner
575c875475 Remove allowDuringStreaming flag - commands always run immediately
Hook commands now always execute immediately, even during streaming.
If a command needs to interact with the LLM, it uses pi.sendMessage()
which handles queueing automatically.

This simplifies the API and eliminates the issue of queued slash
commands being sent to the LLM instead of executing.
2025-12-30 22:42:22 +01:00
Mario Zechner
f8352bb7d7 Rename immediate -> allowDuringStreaming for hook commands 2025-12-30 22:42:22 +01:00
Mario Zechner
6ddc7418da WIP: Major cleanup - move Attachment to consumers, simplify agent API
- Removed Attachment from agent package (now in web-ui/coding-agent)
- Agent.prompt now takes (text, images?: ImageContent[])
- Removed transports from web-ui (duplicate of agent package)
- Updated coding-agent to use local message types
- Updated mom package for new agent API

Remaining: Fix AgentInterface.ts to compose UserMessageWithAttachments
2025-12-30 22:42:20 +01:00
Mario Zechner
6977bc73ca Fix snake border alignment using visibleWidth for proper ANSI length calculation 2025-12-30 22:42:20 +01:00
Mario Zechner
b47151a04f Snake: add full border frame and pause/resume with session persistence
- Full box border around title, game area, and instructions
- ESC pauses and saves state to session via pi.appendEntry()
- Resume shows 'PAUSED - press any key to continue'
- Q quits and clears saved state
- High score persists across games
2025-12-30 22:42:20 +01:00
Mario Zechner
5ae33defd3 Fix snake velocity: render cells as 2 chars wide for square aspect
Terminal cells are ~2:1 aspect ratio, so movement appeared faster
vertically. Now each game cell is 2 characters wide.
2025-12-30 22:42:19 +01:00
Mario Zechner
516c0ea8bc Improve snake game visuals
- Rounded box corners (╭╮╰╯)
- Better characters: ● head, ○ body, ◆ food, · empty
- Colored title with emoji
- Dimmed borders and help text
- Bold highlights for score and controls
2025-12-30 22:42:19 +01:00
Mario Zechner
818196d2c3 Add immediate flag to hook commands for non-queued execution
Commands with immediate: true run right away even during streaming.
Used for UI-only commands like /snake that don't interact with LLM.
2025-12-30 22:42:19 +01:00
Mario Zechner
165fb58b39 Fix snake.ts: use key helpers from pi-tui for ESC and arrow keys 2025-12-30 22:42:19 +01:00
Mario Zechner
14ad8d6228 Add ui.custom() for custom hook components with keyboard focus
- Add custom() to HookUIContext: returns { close, requestRender }
- Component receives keyboard input via handleInput()
- CustomMessageComponent default rendering now limits to 5 lines when collapsed
- Add snake.ts example hook with /snake command
2025-12-30 22:42:19 +01:00
Mario Zechner
29fec7848e Move exec to HookAPI, sessionManager/modelRegistry to HookEventContext
Breaking changes:
- HookEventContext now has sessionManager and modelRegistry (moved from SessionEventBase)
- HookAPI now has exec() method (moved from HookEventContext/HookCommandContext)
- HookRunner constructor takes sessionManager and modelRegistry as required params
- Session events no longer include sessionManager/modelRegistry fields

Hook code migration:
- event.sessionManager -> ctx.sessionManager
- event.modelRegistry -> ctx.modelRegistry
- ctx.exec() -> pi.exec()

Updated:
- src/core/hooks/types.ts - type changes
- src/core/hooks/runner.ts - constructor, createContext
- src/core/hooks/loader.ts - add exec to HookAPI
- src/core/sdk.ts - pass sessionManager/modelRegistry to HookRunner
- src/core/agent-session.ts - remove sessionManager/modelRegistry from events
- src/modes/* - remove setSessionFile calls, update events
- examples/hooks/* - update to new API
2025-12-30 22:42:19 +01:00
Mario Zechner
ba185b0571 Hook API: replace send() with sendMessage(), add appendEntry() and registerCommand()
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
2025-12-30 22:42:18 +01:00
Mario Zechner
9bba388ec5 Refactor SessionEventBase to pass sessionManager and modelRegistry
Breaking changes to hook types:
- SessionEventBase now passes sessionManager and modelRegistry directly
- before_compact: passes preparation, previousCompactions (newest first)
- before_switch: has targetSessionFile; switch: has previousSessionFile
- Removed resolveApiKey (use modelRegistry.getApiKey())
- getSessionFile() returns string | undefined for in-memory sessions

Updated:
- All session event emissions in agent-session.ts
- Hook examples (custom-compaction.ts, auto-commit-on-exit.ts, confirm-destructive.ts)
- Tests (compaction-hooks.test.ts, compaction-hooks-example.test.ts)
- export-html.ts guards for in-memory sessions
2025-12-30 22:42:18 +01:00
Mario Zechner
251fea752c Fix API key priority and compaction bugs
- getEnvApiKey: ANTHROPIC_OAUTH_TOKEN now takes precedence over ANTHROPIC_API_KEY
- findCutPoint: Stop scan-backwards loop at session header (was decrementing past it causing null preparation)
- generateSummary/generateTurnPrefixSummary: Throw on stopReason=error instead of returning empty string
- Test files: Fix API key priority order, use keepRecentTokens=1 for small test conversations
2025-12-30 22:42:17 +01:00
Mario Zechner
c58d5f20a4 Session tree structure with id/parentId linking
- Add TreeNode base type with id, parentId, timestamp
- Add *Content types for clean input/output separation
- Entry types are now TreeNode & *Content intersections
- SessionManager assigns id/parentId on save, tracks leafId
- Add migrateSessionEntries() for v1 to v2 conversion
- Migration runs on load, rewrites file
- buildSessionContext() uses tree traversal from leaf
- Compaction returns CompactionResult (content only)
- Hooks return compaction content, not full entries
- Add firstKeptEntryId to before_compact hook event
- Update mom package for tree fields
- Better error messages for compaction failures
2025-12-30 22:42:17 +01:00
Mario Zechner
911963e777 feat(coding-agent): Add --session-dir flag for custom session directory
- Add --session-dir CLI flag to specify custom session directory
- SessionManager API: second param of create(), continueRecent(), list(), open()
  changed from agentDir to sessionDir (direct directory, no cwd encoding)
- When omitted, uses default (~/.pi/agent/sessions/<encoded-cwd>/)
- --session now derives sessionDir from file's parent if --session-dir not provided
- list() validates session header before processing files
- Closes #313

Co-authored-by: scutifer <scutifer@users.noreply.github.com>
2025-12-25 20:27:41 +01:00
Mario Zechner
454ea1d36a Rename /clear to /new, update hook events to before_new/new
Closes #305 - took direct rename approach instead of alias system

Thanks @mitsuhiko for the nudge!
2025-12-25 04:15:10 +01:00
Mario Zechner
54018b6cc0 Refactor OAuth/API key handling: AuthStorage and ModelRegistry
- Add AuthStorage class for credential storage (auth.json)
- Add ModelRegistry class for model management with API key resolution
- Add discoverAuthStorage() and discoverModels() discovery functions
- Add migration from legacy oauth.json and settings.json apiKeys to auth.json
- Remove configureOAuthStorage, defaultGetApiKey, findModel, discoverAvailableModels
- Remove apiKeys from Settings type and SettingsManager methods
- Rename getOAuthPath to getAuthPath
- Update SDK, examples, docs, tests, and mom package

Fixes #296
2025-12-25 03:48:36 +01:00
Mario Zechner
030788140a WIP: Remove global state from pi-ai OAuth/API key handling
- Remove setApiKey, resolveApiKey, and global apiKeys Map from stream.ts
- Rename getApiKey to getApiKeyFromEnv (only checks env vars)
- Remove OAuth storage layer (storage.ts deleted)
- OAuth login/refresh functions now return credentials instead of saving
- getOAuthApiKey/refreshOAuthToken now take credentials as params
- Add test/oauth.ts helper for ai package tests
- Simplify root npm run check (single biome + tsgo pass)
- Remove redundant check scripts from most packages
- Add web-ui and coding-agent examples to biome/tsgo includes

coding-agent still has compile errors - needs refactoring for new API
2025-12-25 01:01:03 +01:00
Mario Zechner
705ba5d4f2 Improve compaction hooks: add signal, no timeout, SessionManager cleanup, docs 2025-12-24 13:54:05 +01:00
Mario Zechner
403faafdbe Add previousSummary to before_compact hook event 2025-12-24 12:47:12 +01:00
Mario Zechner
d9a542763a Improve before_compact hook: add messagesToKeep, replace apiKey with resolveApiKey 2025-12-24 12:41:22 +01:00
Mario Zechner
e4283294c8 Update full-compaction example to use resolveApiKey 2025-12-24 12:30:00 +01:00
Mario Zechner
5aa53ded11 Add full-compaction hook example 2025-12-24 12:25:36 +01:00
Nico Bailon
ce8a1c8eb1
Add cwd parameter to subagent example tool (#291) 2025-12-24 00:58:43 +01:00
Mario Zechner
42d7d9d9b6 Add before/after session events with cancellation support
- Merge branch event into session with before_branch/branch reasons
- Add before_switch, before_clear, shutdown reasons
- before_* events can be cancelled with { cancel: true }
- Update RPC commands to return cancelled status
- Add shutdown event on process exit
- New example hooks: confirm-destructive, dirty-repo-guard, auto-commit-on-exit

fixes #278
2025-12-22 18:18:38 +01:00
Mario Zechner
face745f3d Fix SDK tools to respect cwd option
Core tools now properly use the cwd passed to createAgentSession().
Added tool factory functions for SDK users who specify custom cwd with explicit tools.

Fixes #279
2025-12-22 16:17:55 +01:00
Mario Zechner
62c64a286b Add project-specific settings and SettingsManager factories
- SettingsManager now loads .pi/settings.json from cwd (project settings)
- Project settings merge with global settings (deep merge for objects)
- Setters only modify global settings, project settings are read-only
- Add static factories: SettingsManager.create(cwd?, agentDir?), SettingsManager.inMemory(settings?)
- Add applyOverrides() for programmatic overrides
- Replace 'settings' option with 'settingsManager' in CreateAgentSessionOptions
- Update examples to use new pattern

Incorporates PR #276 approach
2025-12-22 12:23:02 +01:00