Commit Graph

92 Commits

  • 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
  • revert: remove unnecessary themeOverride params from theme functions
    The optional theme parameter was added as a workaround for tsx dev mode,
    but that's a dev-only issue. Users running the built package don't need it.
  • fix(tools.ts): sync with active tools when no saved state
    Hook was showing all tools as enabled even though only 4 were active.
    Now initializes from pi.getActiveTools() to match actual state.
  • fix(theme): add optional themeOverride param to getSettingsListTheme/getSelectListTheme
    When hooks are loaded via jiti, they get a separate module instance from
    the main app. This means the global 'theme' variable in the hook's module
    is never initialized. Adding an optional theme parameter allows hooks to
    pass the theme from ctx.ui.custom() callback.
    
    Usage in hooks:
      getSettingsListTheme(theme)  // theme from ctx.ui.custom callback
  • 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
  • feat(hooks): add tools.ts example hook for interactive tool enable/disable
    - /tools command opens SettingsList-based selector for all loaded tools
    - Space/Enter toggles individual tools between enabled/disabled
    - Changes apply immediately on toggle (like /settings)
    - Tool selection persisted to session via appendEntry()
    - State restored from current branch on session_start, session_tree, session_branch
    - Uses getBranch() to respect branch-specific tool configuration
    - Export getSettingsListTheme and getSelectListTheme for hooks to use
  • 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(plan-mode): handle non-tool steps and clean up todo text
    - Non-tool turns (analysis, explanation) now mark step complete at turn_end
    - Clean up extracted step text: remove markdown, truncate to 50 chars
    - Remove redundant action words (Use, Run, Execute, etc.)
    - Track toolsCalledThisTurn flag to distinguish tool vs non-tool turns
  • fix(plan-mode): track step completion via tool_result events
    - No longer relies on agent outputting [STEP N DONE] tags
    - Each successful tool_result marks the next uncompleted step done
    - Much more reliable than expecting LLM to follow tag format
    - Simplified execution context (no special instructions needed)
  • fix(plan-mode): use step numbers instead of random IDs
    - Steps are numbered 1, 2, 3... which is easier for agent to track
    - Agent outputs [STEP 1 DONE], [STEP 2 DONE] instead of [DONE:abc123]
    - Clearer instructions in execution context
  • fix(plan-mode): make DONE tag instruction clearer
    - Number steps and show id=xxx format
    - Clearer instruction to output [DONE:id] after each step
  • 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
  • fix(plan-mode): use context event to filter stale plan mode messages
    - Filter out old [PLAN MODE ACTIVE] and [EXECUTING PLAN] messages
    - Fresh context injected via before_agent_start with current state
    - Agent now correctly sees tools are enabled when executing
    - Reverted to ID-based tracking with [DONE:id] tags
    - Simplified execution message (no need to override old context)
  • fix(plan-mode): make execution mode clearer to agent
    - Add explicit [PLAN MODE DISABLED - EXECUTE NOW] message
    - Emphasize FULL access to all tools in execution context
    - List remaining steps in execution context
    - Prevents agent from thinking it's still restricted
  • refactor(plan-mode): use smart keyword matching instead of IDs
    - Remove ugly [DONE:id] tags - users no longer see IDs
    - Track progress via keyword matching on tool results
    - Extract significant keywords from todo text
    - Match tool name + input against todo keywords
    - Sequential preference: first uncompleted item gets bonus score
    - Much cleaner UX - progress tracked silently in background
  • 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(plan-mode): show final completed list in chat when plan finishes
    Displays all completed items with strikethrough markdown when
    all todos are done.
  • fix(plan-mode): buffer text_delta to handle split [DONE:id] patterns
    The [DONE:id] pattern may be split across multiple streaming chunks.
    Now accumulates text in a buffer and scans for complete patterns.
  • 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(plan-mode): use ID-based todo tracking with [DONE:id] tags
    - Each todo item gets a unique ID (e.g., abc123)
    - Agent marks items complete by outputting [DONE:id]
    - IDs shown in chat and in execution context
    - Agent instructed to output [DONE:id] after each step
    - Removed unreliable tool-counting heuristics
  • feat(plan-mode): show todo list in chat after planning, widget during execution
    - After agent creates plan: show todo list as a message in chat
    - During execution: show widget under Working indicator with checkboxes
    - Check off items as they complete with strikethrough
  • fix(plan-mode): fix todo extraction from assistant messages
    - AssistantMessage.content is an array, not string
    - Handle markdown bold formatting in numbered lists
    - Extract text content blocks properly
  • 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(plan-mode): add todo list extraction and progress tracking
    - Extract numbered steps from agent's plan response
    - Track progress during execution with footer indicator (📋 2/5)
    - /todos command to view current plan progress
    - State persists across sessions including todo progress
    - Agent prompted to format plans as numbered lists for tracking
  • 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
  • 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>
  • 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"
    }
  • 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
  • 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
  • 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
  • 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.
  • Use serializeConversation in handoff hook
    Properly handles all message types including tool calls, thinking,
    and tool results instead of manual text extraction.
  • 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.
  • 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
  • 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
  • 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)
  • Update custom-compaction example to use serializeConversation
    Also fix docs to show convertToLlm is needed first.