Commit Graph

283 Commits

  • Add extensions option to createAgentSession SDK
    - Accept ExtensionFactory[] for inline extensions (merged with discovery)
    - Mark preloadedExtensions as @internal (CLI implementation detail)
    - Update sdk.md with inline extension example
    - Update CHANGELOG
  • Add customTools option back to createAgentSession SDK
    - Accepts ToolDefinition[] directly (simplified from old { path?, tool } format)
    - Tools are combined with extension-registered tools
    - Updated sdk.md documentation
    - Updated CHANGELOG
  • Merge hooks and custom-tools into unified extensions system (#454)
    Breaking changes:
    - Settings: 'hooks' and 'customTools' arrays replaced with 'extensions'
    - CLI: '--hook' and '--tool' flags replaced with '--extension' / '-e'
    - API: HookMessage renamed to CustomMessage, role 'hookMessage' to 'custom'
    - API: FileSlashCommand renamed to PromptTemplate
    - API: discoverSlashCommands() renamed to discoverPromptTemplates()
    - Directories: commands/ renamed to prompts/ for prompt templates
    
    Migration:
    - Session version bumped to 3 (auto-migrates v2 sessions)
    - Old 'hookMessage' role entries converted to 'custom'
    
    Structural changes:
    - src/core/hooks/ and src/core/custom-tools/ merged into src/core/extensions/
    - src/core/slash-commands.ts renamed to src/core/prompt-templates.ts
    - examples/hooks/ and examples/custom-tools/ merged into examples/extensions/
    - docs/hooks.md and docs/custom-tools.md merged into docs/extensions.md
    
    New test coverage:
    - test/extensions-runner.test.ts (10 tests)
    - test/extensions-discovery.test.ts (26 tests)
    - test/prompt-templates.test.ts
  • Implement extension discovery with package.json manifest support
    Discovery rules:
    1. extensions/*.ts or *.js - direct files
    2. extensions/*/index.ts or index.js - subdirectory with index
    3. extensions/*/package.json with pi field - load declared paths
    
    No recursion beyond one level. Complex packages use package.json manifest.
    
    Added PiManifest type for future theme/skill bundling support.
    
    17 tests covering all discovery scenarios.
    
    refs #454
  • Add unified extensions system (not wired up yet)
    New src/core/extensions/ directory with:
    - types.ts: merged types from hooks and custom-tools
    - loader.ts: single loader for extensions
    - runner.ts: ExtensionRunner for event emission
    - wrapper.ts: tool wrapping utilities
    - index.ts: exports
    
    Key changes from old system:
    - Single ExtensionAPI with registerTool() for LLM-callable tools
    - Tools use ExtensionContext (has UI access)
    - No onSession callback on tools (use pi.on events instead)
    
    refs #454
  • Fix event bus async error handling, clear pending messages on session switch, improve SDK docs
    - event-bus.ts: await async handlers to catch errors properly
    - agent-session.ts: clear _pendingNextTurnMessages on newSession/switchSession/branch
    - sdk.ts: make eventBus first (required) param for discoverHooks/discoverCustomTools
    - docs/sdk.md: document eventBus sharing pattern for hook/tool communication
  • feat(coding-agent): add event bus for tool/hook communication (#431)
    * feat(coding-agent): add event bus for tool/hook communication
    
    Adds pi.events API enabling custom tools and hooks to communicate via
    pub/sub. Tools can emit events, hooks can listen. Shared EventBus instance
    created per session in createAgentSession().
    
    - EventBus interface with emit() and on() methods
    - on() returns unsubscribe function
    - Threaded through hook and tool loaders
    - Documented in hooks.md and custom-tools.md
    
    * fix(coding-agent): wrap event handlers to catch errors
    
    * docs: note async handler error handling for event bus
    
    * feat(coding-agent): add sendMessage to tools, nextTurn delivery mode
    
    - Custom tools now have pi.sendMessage() for direct agent notifications
    - New deliverAs: 'nextTurn' queues messages for next user prompt
    - Fix: hooks and tools now share the same eventBus (was isolated before)
    
    * fix(coding-agent): nextTurn delivery should always queue, even when streaming
  • fix(tools): wrap ALL registry tools with hooks, not just active ones
    wrappedToolRegistry was only containing activeToolsArray (4 tools).
    Now wraps all tools from the registry so hooks can enable any tool.
  • fix(tools): tool registry now contains ALL built-in tools
    - createAllTools() populates registry with all 7 built-in tools
    - --tools flag only sets initially active tools (default: read/bash/edit/write)
    - Hooks can enable any tool from registry via setActiveTools()
    - System prompt rebuilds with correct tool guidelines when tools change
    - Document tsx module resolution workaround in README
  • fix(hooks): add stack traces to hook errors, fix tools.ts theme bug
    - HookError now includes optional stack field
    - Hook error display shows stack trace in dim color below error message
    - tools.ts: create SettingsListTheme using the theme passed to ctx.ui.custom()
      instead of using getSettingsListTheme() which depends on global theme
  • feat(hooks): add systemPromptAppend to before_agent_start, full tool registry
    - before_agent_start handlers can return systemPromptAppend to dynamically
      append text to the system prompt for that turn
    - Multiple hooks' systemPromptAppend strings are concatenated
    - Multiple hooks' messages are now all injected (not just first)
    - Tool registry now contains ALL built-in tools (read, bash, edit, write,
      grep, find, ls) regardless of --tools flag
    - --tools only sets initially active tools, hooks can enable any via
      setActiveTools()
    - System prompt automatically rebuilds when tools change, updating tool
      descriptions and guidelines
    - Add pirate.ts example hook demonstrating systemPromptAppend
    - Update hooks.md with systemPromptAppend documentation
  • fix(hooks): deep copy messages in context event before passing to hooks
    The context event handler documentation promised a deep copy but the
    implementation passed the original array reference. This could cause
    hooks to accidentally mutate session messages.
    
    Uses structuredClone() for fast native deep copying.
  • refactor: address PR feedback - merge setWidget, use KeyId for shortcuts
    1. Merge setWidget and setWidgetComponent into single overloaded method
       - Accepts either string[] or component factory function
       - Uses single Map<string, Component> internally
       - String arrays wrapped in Container with Text components
    
    2. Use KeyId type for registerShortcut instead of plain string
       - Import Key from @mariozechner/pi-tui
       - Update plan-mode example to use Key.shift('p')
       - Type-safe shortcut registration
    
    3. Fix tool API docs
       - Both built-in and custom tools can be enabled/disabled
       - Removed incorrect 'custom tools always active' statement
    
    4. Use matchesKey instead of matchShortcut (already done in rebase)
  • fix: use robust matchShortcut from TUI library
    - Add matchShortcut() function to @mariozechner/pi-tui
    - Handles Kitty protocol, legacy terminal sequences, and lock keys
    - Supports special keys (enter, tab, space, backspace, escape)
    - Replace custom implementation in interactive-mode.ts
    - Remove unused imports
  • fix: remove inline imports and debug logging
    - Convert all inline import() types to top-level imports
    - Remove debug console.error statements from plan-mode hook
  • fix(hooks): fix ContextEventResult.messages type to AgentMessage[]
    - Was incorrectly typed as Message[] which caused filtered messages to be ignored
    - Context event filter in plan-mode hook should now properly remove stale [PLAN MODE ACTIVE] messages
  • feat(hooks): add setWidgetComponent for custom TUI components
    - New ctx.ui.setWidgetComponent(key, factory) method
    - Allows custom Component to render as widget without taking focus
    - Unlike custom(), widget components render inline above editor
    - Components are disposed when cleared or replaced
    - Falls back to no-op in RPC/print modes
  • refactor(hooks): address PR feedback
    - Rename getTools/setTools to getActiveTools/setActiveTools
    - Add getAllTools to enumerate all configured tools
    - Remove text_delta event (use turn_end/agent_end instead)
    - Add shortcut conflict detection:
      - Skip shortcuts that conflict with built-in shortcuts (with warning)
      - Log warnings when hooks register same shortcut (last wins)
    - Add note about prompt cache invalidation in setActiveTools
    - Update plan-mode hook to use agent_end for [DONE:id] parsing
  • feat(hooks): add text_delta event for streaming text monitoring
    - New text_delta hook event fires for each chunk of streaming text
    - Enables real-time monitoring of agent output
    - Plan-mode hook now updates todo progress as [DONE:id] tags stream in
    - Each todo item has unique ID for reliable tracking
  • feat(hooks): add setWidget API for multi-line status displays
    - ctx.ui.setWidget(key, lines) for multi-line displays above editor
    - Widgets appear below 'Working...' indicator, above editor
    - Supports ANSI styling including strikethrough
    - Added theme.strikethrough() method
    - Plan-mode hook now shows todo list with checkboxes
    - Completed items show checked box and strikethrough text
  • feat(coding-agent): add hook API for CLI flags, shortcuts, and tool control
    Hook API additions:
    - pi.getTools() / pi.setTools(toolNames) - dynamically enable/disable tools
    - pi.registerFlag(name, options) / pi.getFlag(name) - register custom CLI flags
    - pi.registerShortcut(shortcut, options) - register keyboard shortcuts
    
    Plan mode hook (examples/hooks/plan-mode.ts):
    - /plan command or Shift+P shortcut to toggle
    - --plan CLI flag to start in plan mode
    - Read-only tools: read, bash, grep, find, ls
    - Bash restricted to non-destructive commands (blocks rm, mv, git commit, etc.)
    - Interactive prompt after each response: execute, stay, or refine
    - Shows plan indicator in footer when active
    - State persists across sessions
  • WIP: Add hook API for dynamic tool control with plan-mode hook example
    - Add pi.getTools() and pi.setTools(toolNames) to HookAPI
    - Hooks can now enable/disable tools dynamically
    - Changes take effect on next agent turn
    
    New example hook: plan-mode.ts
    - Claude Code-style read-only exploration mode
    - /plan command toggles plan mode on/off
    - Plan mode tools: read, bash, grep, find, ls
    - Edit/write tools disabled in plan mode
    - Injects context telling agent about restrictions
    - After each response, prompts to execute/stay/refine
    - State persists across sessions
  • fix(coding-agent): resolved UTF-8 corruption in bash executor output (#433)
    - Fixed UTF-8 text corruption in bash executor by replacing Buffer.toString() with streaming TextDecoder.
  • Fix slash commands and hook commands during streaming
    - Hook commands now execute immediately during streaming (they manage their own LLM interaction via pi.sendMessage())
    - File-based slash commands are expanded and queued via steer/followUp during streaming
    - prompt() accepts new streamingBehavior option ('steer' or 'followUp') for explicit queueing during streaming
    - steer() and followUp() now expand file-based slash commands and error on hook commands
    - RPC prompt command accepts optional streamingBehavior field
    - Updated docs: rpc.md, sdk.md, CHANGELOG.md
    
    fixes #420
  • feat(coding-agent): add $ARGUMENTS syntax for slash commands (#418)
    * feat(coding-agent): add $ARGUMENTS syntax for slash commands
    
    * test(coding-agent): add tests for slash command argument substitution
  • 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"
    }
  • Add shell commands without context contribution (!! prefix)
    Use !!command to execute bash commands that are shown in the TUI and
    saved to session history but excluded from LLM context, compaction
    summaries, and branch summaries.
    
    - Add excludeFromContext field to BashExecutionMessage
    - Filter excluded messages in convertToLlm()
    - Parse !! prefix in interactive mode
    - Use dim border color for excluded commands
    
    fixes #414
  • feat(coding-agent): configurable double-escape action (tree vs branch)
    Add doubleEscapeAction setting to choose whether double-escape with an
    empty editor opens /tree (default) or /branch.
    
    - Add setting to Settings interface and SettingsManager
    - Add to /settings UI for easy toggling
    - Update interactive-mode to respect the setting
    - Document in README.md settings table
    
    fixes #404
  • Fix slow /model selector by deferring OAuth token refresh
    getAvailable() now uses hasAuth() which checks if auth is configured
    without triggering OAuth token refresh. Refresh happens later when
    the model is actually used.
  • Add Vertex AI provider with ADC support
    - Implement google-vertex provider in packages/ai
    - Support ADC (Application Default Credentials) via @google/generative-ai
    - Add Gemini model catalog for Vertex AI
    - Update packages/coding-agent to handle google-vertex provider
  • Enhance provider override to support baseUrl-only mode
    Builds on #406 to support simpler proxy use case:
    - Override just baseUrl to route built-in provider through proxy
    - All built-in models preserved, no need to redefine them
    - Full replacement still works when models array is provided
  • Allow models.json to override built-in providers (#406)
    * Allow models.json to override built-in providers
    
    When a provider is defined in models.json with the same name as a
    built-in provider (e.g., 'anthropic', 'google'), the built-in models
    for that provider are completely replaced by the custom definition.
    
    This enables users to:
    - Use custom base URLs (proxies, self-hosted endpoints)
    - Define a subset of models they want available
    - Customize model configurations for built-in providers
    
    Example usage in ~/.pi/agent/models.json:
    {
      "providers": {
        "anthropic": {
          "baseUrl": "https://my-proxy.example.com/v1",
          "apiKey": "ANTHROPIC_API_KEY",
          "api": "anthropic-messages",
          "models": [...]
        }
      }
    }
    
    * Refactor model-registry for readability
    
    - Extract CustomModelsResult type and emptyCustomModelsResult helper
    - Extract loadBuiltInModels method with clear skip logic
    - Simplify loadModels with destructuring and ternary
    - Reduce repetition in error handling paths
    
    * Refactor model-registry tests for readability
    
    - Extract providerConfig() helper to hide irrelevant model fields
    - Extract writeModelsJson() helper for file writing
    - Extract getModelsForProvider() helper for filtering
    - Move modelsJsonPath to beforeEach
    
    Reduces test file from 262 to 130 lines while maintaining same coverage.
  • 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
  • feat(coding-agent): complete steer()/followUp() migration
    - Update settings-manager with steeringMode/followUpMode (migrates old queueMode)
    - Update sdk.ts to use new mode options
    - Update settings-selector UI to show both modes
    - Add Alt+Enter keybind for follow-up messages
    - Update RPC API: steer/follow_up commands, set_steering_mode/set_follow_up_mode
    - Update rpc-client with new methods
    - Delete dead code: queue-mode-selector.ts
    - Update tests for new API
    - Update mom/context.ts stubs
    - Update web-ui example
  • feat(coding-agent): update AgentSession for steer()/followUp() API
    - Rename queueMessage to steer(), add followUp()
    - Split _pendingMessages into _steeringMessages and _followUpMessages
    - Update sendHookMessage to accept deliverAs option
    - Rename hasQueuedMessages to hasPendingMessages
    - Rename queuedMessageCount to pendingMessageCount
    - Update clearQueue() return type to { steering, followUp }
    - Update UI to show steering vs follow-up messages differently
    
    WIP: settings-manager, sdk, interactive-mode, rpc-mode still need updates
  • Add guard against concurrent prompt() calls
    Agent.prompt() and Agent.continue() now throw if called while already
    streaming, preventing race conditions and corrupted state. Use
    queueMessage() to queue messages during streaming, or await the
    previous call.
    
    AgentSession.prompt() has the same guard with a message directing
    users to queueMessage().
    
    Ref #403
  • Fix edit tool failing on files with UTF-8 BOM
    Strip BOM before matching (LLM won't include invisible BOM in oldText),
    restore on write.
    
    Based on #394 by @prathamdby
  • 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.