Commit Graph

117 Commits

  • Fix --no-skills flag not preventing skills from loading
    The --no-skills flag set options.skills = [] in main.ts, but the
    interactive mode UI would rediscover skills anyway because it called
    loadSkills() directly instead of using the already-loaded skills.
    
    Changes:
    - Add AgentSession.skills and AgentSession.skillWarnings properties
    - discoverSkills() now returns { skills, warnings } instead of Skill[]
    - Interactive mode uses session.skills instead of calling loadSkills()
    - Update SDK docs and examples for new return type
    
    Fixes #577
  • fix(coding-agent): --no-skills flag not preventing skills from loading (#577)
    The --no-skills flag set options.skills = [] in main.ts, but the interactive mode UI would rediscover skills anyway because it called loadSkills() directly.
    
    Changes:
    - Add AgentSession.skills and AgentSession.skillWarnings properties  
    - discoverSkills() now returns { skills, warnings } instead of Skill[]
    - Interactive mode uses session.skills instead of calling loadSkills()
    
    Co-authored-by: Carlos Villela <cv@lixo.org>
  • feat(coding-agent): add tool override support via extensions
    - Add setActiveTools() to ExtensionAPI for dynamic tool management
    - Extensions can now override, wrap, or disable built-in tools
    - Add tool-override.ts example demonstrating the pattern
    - Update documentation for tool override capabilities
  • feat(coding-agent): add --no-tools flag to disable built-in tools
    Add --no-tools flag that allows starting pi without any built-in tools,
    enabling extension-only tool setups (e.g., pi-ssh-remote).
    
    - Add --no-tools flag to CLI args parsing
    - Handle --tools '' (empty string) as equivalent to no tools
    - Fix system prompt to not show READ-ONLY mode when no tools (extensions may provide write capabilities)
    - Add tests for new flag and system prompt behavior
    
    fixes #555
  • feat(coding-agent): add --no-extensions flag to disable extension discovery
    Adds --no-extensions CLI flag that skips automatic extension discovery
    while still allowing explicit -e paths. Three modes now available:
    
    1. Default: auto-discover + any -e additions
    2. --no-extensions: no extensions at all
    3. --no-extensions -e foo.js: only load explicit extensions
    
    Useful for debugging or running subagent instances without auto-discovered
    extensions.
    
    closes #524
  • refactor(coding-agent): simplify extension runtime architecture
    - Replace per-extension closures with shared ExtensionRuntime
    - Split context actions: ExtensionContextActions (required) + ExtensionCommandContextActions (optional)
    - Rename LoadedExtension to Extension, remove setter methods
    - Change runner.initialize() from options object to positional params
    - Derive hasUI from uiContext presence (no separate param)
    - Add warning when extensions override built-in tools
    - RPC and print modes now provide full command context actions
    
    BREAKING CHANGE: Extension system types and initialization API changed.
    See CHANGELOG.md for migration details.
  • fix: use defaultThinkingLevel from settings when enabledModels lacks explicit suffix (#540)
    When enabledModels is configured without thinking level suffixes (e.g.,
    'claude-opus-4-5' instead of 'claude-opus-4-5:high'), the scoped model's
    default 'off' thinking level was overriding defaultThinkingLevel from
    settings.
    
    Now thinkingLevel in ScopedModel is optional (undefined means 'not
    explicitly specified'). When passing to SDK, undefined values are filled
    with defaultThinkingLevel from settings.
  • feat(coding-agent): add ctx.ui.setEditorComponent() extension API
    - Add setEditorComponent() to ctx.ui for custom editor components
    - Add CustomEditor base class for extensions (handles app keybindings)
    - Add keybindings parameter to ctx.ui.custom() factory (breaking change)
    - Add modal-editor.ts example (vim-like modes)
    - Add rainbow-editor.ts example (animated text highlighting)
    - Update docs: extensions.md, tui.md Pattern 7
    - Clean up terminal on TUI render errors
  • fix: preserve externally-added settings when saving
    When a user edits settings.json while pi is running (e.g., adding
    enabledModels), those settings would be lost when pi saved other
    changes (e.g., changing thinking level via Shift+Tab).
    
    The fix re-reads the file before saving and merges the current file
    contents with in-memory changes, so external edits are preserved.
    
    Adds test coverage for SettingsManager.
  • feat(coding-agent): allow explicit -e flags with --no-extensions
    --no-extensions now skips discovery but still loads extensions
    specified via -e flags. This gives users three modes:
    
    1. Default: auto-discover + any -e additions
    2. --no-extensions: no extensions at all
    3. --no-extensions -e foo.js: only load foo.js, skip discovery
    
    fixes #524
  • Add --no-extensions flag to disable extension discovery
    Similar to --no-skills, this flag disables extension discovery and loading.
    Useful for debugging or when extensions are causing issues.
  • Add ExtensionAPI methods, preset example, and TUI documentation improvements
    - ExtensionAPI: setModel(), getThinkingLevel(), setThinkingLevel() methods
    - New preset.ts example with plan/implement presets for model/thinking/tools switching
    - Export all UI components from pi-coding-agent for extension use
    - docs/tui.md: Common Patterns section with copy-paste code for SelectList, BorderedLoader, SettingsList, setStatus, setWidget, setFooter
    - docs/tui.md: Key Rules section for extension UI development
    - docs/extensions.md: Exhaustive example links for all ExtensionAPI methods and events
    - System prompt now references docs/tui.md for TUI development
    
    Fixes #509, relates to #347
  • Extensions: add pi.sendUserMessage() for sending user messages
    Adds sendUserMessage() to the extension API, allowing extensions to send
    actual user messages (role: user) rather than custom messages. Unlike
    sendMessage(), this always triggers a turn and behaves as if the user
    typed the message.
    
    - Add SendUserMessageHandler type and sendUserMessage() to ExtensionAPI
    - Wire handler through loader, runner, and all modes
    - Implement via prompt() with expandPromptTemplates: false
    - Add send-user-message.ts example with /ask, /steer, /followup commands
    - Document in extensions.md
    
    fixes #483
  • Add ctx.ui.setFooter() for extensions to replace footer component
    Extensions can now replace the built-in footer with a custom component:
    - setFooter(factory) replaces with custom component
    - setFooter(undefined) restores built-in footer
    
    Includes example extension demonstrating context usage display.
    
    Closes #481
  • Merge branch 'fix/image-paste-in-wayland'
    # Conflicts:
    #	packages/coding-agent/CHANGELOG.md
  • Add blockImages setting to prevent images from being sent to LLM providers
    - Setting controls filtering at convertToLlm layer (defense-in-depth)
    - Images are always stored in session, filtered dynamically based on current setting
    - Toggle mid-session works: LLM sees/doesn't see images already in session
    - Fixed SettingsManager.save() to handle inMemory mode for all setters
    
    Closes #492
  • fix(coding-agent): improve bash tool error handling (#479)
    - Validate working directory exists before spawning to provide clear error message
    - Add spawn error handler to prevent uncaught exceptions when shell not found or cwd invalid
    - Add tests for both error scenarios
    
    Without these fixes, spawn errors (e.g., ENOENT from missing cwd or shell) would
    cause uncaught exceptions that crash the entire agent session instead of being
    returned as clean tool errors.
    
    Co-authored-by: robinwander <robinwander@users.noreply.github.com>
  • 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
  • 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)
  • 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 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
  • 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
  • fix(ai): ensure maxTokens > thinkingBudget for Claude thinking models
    Claude requires max_tokens > thinking.budget_tokens. When caller specifies
    a small maxTokens (e.g. compaction with ~13k tokens) and reasoning is enabled
    with high budget (16k tokens), the constraint was violated.
    
    Fix: In mapOptionsForApi, add thinkingBudget on top of caller's maxTokens
    (capped at model.maxTokens). If still not enough room, reduce thinkingBudget
    to leave space for output.
    
    Applied to both anthropic-messages and google-gemini-cli APIs.
    
    Also adds test utilities for OAuth credential resolution and tests for
    compaction with thinking models.
    
    fixes #413
  • 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): 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.
  • Split HookContext and HookCommandContext to prevent deadlocks
    HookContext (all events):
    - isIdle() - read-only state check
    - hasQueuedMessages() - read-only state check
    - abort() - fire-and-forget, does not wait
    
    HookCommandContext (slash commands only):
    - waitForIdle() - waits for agent to finish
    - newSession(options?) - create new session
    - branch(entryId) - branch from entry
    - navigateTree(targetId, options?) - navigate session tree
    
    Session control methods moved from HookAPI (pi.*) to HookCommandContext (ctx.*)
    because they can deadlock when called from event handlers that run inside
    the agent loop (tool_call, tool_result, context events).
  • Add session management and agent state methods to hooks API
    HookAPI additions:
    - pi.newSession(options?) - create new session with optional setup callback
    - pi.branch(entryId) - branch from a specific entry
    - pi.navigateTree(targetId, options?) - navigate the session tree
    
    HookContext additions:
    - ctx.isIdle() - check if agent is streaming
    - ctx.waitForIdle() - wait for agent to finish
    - ctx.abort() - abort current operation
    - ctx.hasQueuedMessages() - check for queued user messages
    
    These enable hooks to programmatically manage sessions (handoff, templates)
    and check agent state before showing interactive UI.
    
    Fixes #388
  • 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
  • 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
  • Hooks can render custom status (#385)
    * Add ctx.ui.setStatus(key, text) API for hooks to display status in footer
    
    - Add setStatus to HookUIContext interface
    - Implement in interactive mode (FooterComponent)
    - Implement in RPC mode (fire-and-forget)
    - Add no-op implementations for headless contexts
    - Multiple statuses displayed on single line, sorted by key
    - Supports ANSI styling (hooks handle their own colors)
    
    * Remove setStatus from changelog for now
    
    * Fix hook status API to follow TUI rules
    
    - Sanitize status text: replace newlines, tabs, carriage returns with spaces
    - Truncate combined status line to terminal width using truncateToWidth
    - Update JSDoc to document sanitization and truncation behavior
    - Remove unused createHookUIContext method
    - Add missing setStatus to test mock
    
    * Add setStatus to changelog
    
    * Use dim ellipsis for hook status truncation for consistency with footer style
    
    ---------
    
    Co-authored-by: Mario Zechner <badlogicgames@gmail.com>
  • Coalesce sequential status messages
    Rapidly changing settings no longer spams the chat log with multiple status lines.
    
    fixes #365