Commit Graph

132 Commits

  • feat(coding-agent): package deduplication and collision detection
    - Package deduplication: same package in global+project, project wins
    - Collision detection for skills, prompts, and themes with ResourceCollision type
    - PathMetadata tracking with parent directory lookup for file paths
    - Display improvements: section headers, sorted groups, accent colors for packages
    - pi list shows full paths below package names
    - Extension loader discovers files in directories without index.ts
    - In-memory SettingsManager properly tracks project settings
    
    fixes #645
  • feat(coding-agent): make skill invocation messages collapsible
    - Add ParsedSkillBlock interface and parseSkillBlock() function
    - Change skill expansion to use XML-style <skill> tags
    - Add SkillInvocationMessageComponent for collapsible display
    - Collapsed: single line with skill name and expand hint
    - User message rendered separately after skill block
    
    Fixes #894
  • fix(coding-agent): simplify extension error listener to single instance
    There's only ever one bindings instance per session, so the Set/Array
    approach was unnecessary. Changed from Set<ExtensionErrorListener> to
    optional single listener.
  • fix(coding-agent): add 'terminated' to retryable error patterns
    Codex API can send 'terminated' error mid-stream, which should be
    retried like other transient server errors.
  • feat(coding-agent): ResourceLoader, package management, and /reload command (#645)
    - Add ResourceLoader interface and DefaultResourceLoader implementation
    - Add PackageManager for npm/git extension sources with install/remove/update
    - Add session.reload() and session.bindExtensions() APIs
    - Add /reload command in interactive mode
    - Add CLI flags: --skill, --theme, --prompt-template, --no-themes, --no-prompt-templates
    - Add pi install/remove/update commands for extension management
    - Refactor settings.json to use arrays for skills, prompts, themes
    - Remove legacy SkillsSettings source flags and filters
    - Update SDK examples and documentation for ResourceLoader pattern
    - Add theme registration and loadThemeFromPath for dynamic themes
    - Add getShellEnv to include bin dir in PATH for bash commands
  • fix(coding-agent): prevent crash on OAuth authentication failure (#849)
    Detect OAuth authentication failures (expired credentials, offline) and provide helpful error message instead of crashing with generic 'No API key found' error.
    
    Co-authored-by: Mario Zechner <badlogicgames@gmail.com>
  • fix(coding-agent): handle auto-compaction failures gracefully
    When auto-compaction fails (e.g., quota exceeded), emit the error via
    the auto_compaction_end event instead of throwing. The UI now displays
    the error message, allowing users to take action (switch models, wait
    for quota reset, etc.) instead of crashing.
    
    fixes #792
  • fix(ai): filter empty error assistant messages in transformMessages
    When 429/500 errors occur during tool execution, empty assistant messages
    with stopReason='error' get persisted. These break the tool_use -> tool_result
    chain for Claude/Gemini APIs.
    
    Added centralized filtering in transformMessages to skip assistant messages
    with empty content and no tool calls. Provider-level filters remain for
    defense-in-depth.
  • Move skill command handling to AgentSession, update docs
    - Skill commands (/skill:name) now expanded in AgentSession instead of
      interactive mode, enabling them in RPC and print modes
    - Input event can now intercept /skill:name before expansion
    - Updated extensions.md with clearer input event docs and processing order
    - Updated rpc.md: hook -> extension terminology, added skill expansion mentions
    - Added PR attribution to changelog entries for #761
  • feat(coding-agent): add input event for extension input interception (#761)
    * feat(coding-agent): add input event for extension input interception
    
    Extensions can now intercept, transform, or handle user input before the
    agent processes it. Three result types: continue (pass through), transform
    (modify text/images), handled (respond without LLM). Handlers chain
    transforms and short-circuit on handled. Source field identifies origin.
    
    * fix: make source public, use if/else over ternary
    
    * fix: remove response field, extension handles own UI
  • feat(coding-agent): Custom tool export rendering in export (#702)
    * coding-agent: add ANSI-to-HTML converter for export
    
    * coding-agent: add getToolDefinition method to ExtensionRunner
    
    * coding-agent: add tool HTML renderer factory for custom tools
    
    * coding-agent: add custom tool pre-rendering to HTML export
    
    * coding-agent: render pre-rendered custom tools in HTML export
    
    * coding-agent: integrate tool renderer in exportToHtml
  • Improve Google Cloud Code Assist error handling (#665)
    * Improve Cloud Code Assist error messages
    
    - Extract just the message from verbose JSON error responses
    - Extract cause from generic 'fetch failed' errors for better diagnostics
    
    * Make 'other side closed' network error retryable
    
    * Make 'other side closed' network error retryable
  • Change getAllTools() to return ToolInfo[] instead of string[]
    Breaking change: pi.getAllTools() now returns Array<{ name, description }>
    instead of string[]. Extensions needing just names can use .map(t => t.name).
    
    Removes redundant getToolInfo() method added in original PR.
    
    Fixes #647
  • Rename /branch command to /fork
    - RPC: branch -> fork, get_branch_messages -> get_fork_messages
    - SDK: branch() -> fork(), getBranchMessages() -> getForkMessages()
    - AgentSession: branch() -> fork(), getUserMessagesForBranching() -> getUserMessagesForForking()
    - Extension events: session_before_branch -> session_before_fork, session_branch -> session_fork
    - Settings: doubleEscapeAction 'branch' -> 'fork'
    
    fixes #641
  • Add /models command for enabling/disabling Ctrl+P model cycling
    - New /models command with toggle UI for each available model
    - Changes persist to enabledModels in settings.json
    - Updates take effect immediately for Ctrl+P cycling
  • feat(coding-agent): add model_select extension hook
    Fires when model changes via setModel(), cycleModel(), or session restore.
    Includes source field ("set" | "cycle" | "restore") and previous model.
  • 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
  • feat(coding-agent): add user_bash event and theme API extensions
    - user_bash event for intercepting ! and !! commands (#528)
    - Extensions can return { operations } or { result } to redirect/replace
    - executeBashWithOperations() for custom BashOperations execution
    - session.recordBashResult() for extensions handling bash themselves
    - Theme API: getAllThemes(), getTheme(), setTheme() on ctx.ui
    - mac-system-theme.ts example: sync with macOS dark/light mode
    - Updated ssh.ts to use user_bash event
  • Allow extensions to modify system prompt in before_agent_start
    - Add systemPrompt to BeforeAgentStartEvent so extensions can see current prompt
    - Change systemPromptAppend to systemPrompt in BeforeAgentStartEventResult for full replacement
    - Extensions can now chain modifications (each sees the result of previous)
    - Update ssh.ts to replace local cwd with remote cwd in system prompt
    - Update pirate.ts, claude-rules.ts, preset.ts to use new API
    
    fixes #575
  • fix: show retry attempt count when aborting during retry
    When aborting a retry attempt, surface the retry-aware abort message
    in the assistant output and tool results instead of a generic "Aborted".
    
    - Set errorMessage for aborted streaming messages
    - Render abort message without forcing a leading newline when no content
  • fix: ESC key not interrupting during Working... state
    Three related fixes:
    
    1. google-gemini-cli: Handle abort signal in stream reading loop
       - Add abort event listener to cancel reader immediately when signal fires
       - Fix AbortError detection in retry catch block (fetch throws AbortError,
         not our custom message)
       - Swallow reader.cancel() rejection to avoid unhandled promise
    
    2. agent-session: Fix retry attempt counter showing 0 on cancel
       - abortRetry() was resetting _retryAttempt before the catch block could
         read it for the error message
    
    3. interactive-mode: Restore main escape handler on agent_start
       - When auto-retry starts, onEscape is replaced with retry-specific handler
       - auto_retry_end (which restores it) fires on turn_end, after streaming begins
       - Now restore immediately on agent_start if retry handler is still active
    
    Amended: suppress reader.cancel() rejection on abort.
  • 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
  • feat(ai,agent,coding-agent): add sessionId for provider session-based caching
    - Add sessionId to StreamOptions for providers that support session-based caching
    - OpenAI Codex provider uses sessionId for prompt_cache_key and routing headers
    - Agent class now accepts and forwards sessionId to stream functions
    - coding-agent passes session ID from SessionManager and updates on session changes
    - Update ai package README with table of contents, OpenAI Codex OAuth docs, and env vars table
    - Increase Codex instructions cache TTL from 15 minutes to 24 hours
    - Add tests for sessionId forwarding in ai and agent packages
  • fix: clean up Codex thinking level handling
    - Remove per-thinking-level model variants (gpt-5.2-codex-high, etc.)
    - Remove thinkingLevels from Model type
    - Provider clamps reasoning effort internally
    - Omit reasoning field when thinking is off
    
    fixes #472
  • 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
  • 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
  • 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
  • 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
  • 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 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
  • 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): 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
  • Add agent state methods to CustomToolContext and fix abort signature
    CustomToolContext now has:
    - isIdle() - check if agent is streaming
    - hasQueuedMessages() - check if user has queued messages
    - abort() - abort current operation (fire-and-forget)
    
    Changed abort() signature from Promise<void> to void in both
    HookContext and CustomToolContext. The abort is fire-and-forget:
    it calls session.abort() without awaiting, so the abort signal
    is set immediately while waitForIdle() runs in the background.
    
    Fixes #388
  • 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
  • WIP: Rewrite export-html with tree sidebar, client-side rendering
    - Add tree sidebar with search and filter (Default/All/Labels)
    - Client-side markdown/syntax highlighting via vendored marked.js + highlight.js
    - Base64 encode session data to avoid escaping issues
    - Reuse theme.ts color tokens via getResolvedThemeColors()
    - Sticky sidebar, responsive mobile layout with overlay
    - Click tree node to scroll to message
    - Keyboard shortcuts: Esc to reset, Ctrl/Cmd+F to search
  • 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)
  • 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