Commit Graph

27 Commits

  • 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
  • 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
  • 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>
  • 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
  • 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
  • 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
  • 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.
  • 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
  • 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
  • Refactor: shared exec utility, rename CustomMessageRenderer to HookMessageRenderer
    - Extract execCommand to src/core/exec.ts, shared by hooks and custom-tools
    - Rename CustomMessageRenderer -> HookMessageRenderer
    - Rename registerCustomMessageRenderer -> registerMessageRenderer
    - Renderer now receives HookMessage instead of CustomMessageEntry
    - CustomMessageComponent now has setExpanded() and responds to Ctrl+E toggle
    - Re-export ExecOptions/ExecResult from exec.ts for backward compatibility
  • Fix tests for sessionManager/modelRegistry on context
    - compaction-hooks-example.test.ts: get sessionManager/modelRegistry from ctx
    - compaction-hooks.test.ts:
      - Pass sessionManager/modelRegistry to HookRunner constructor
      - Remove setSessionFile call
      - Update tests to use session.sessionManager instead of event.sessionManager
  • 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
  • Add CustomMessageEntry rendering infrastructure
    - Add renderCustomMessage to HookAPI for registering custom renderers
    - Add CustomMessageRenderer type and CustomMessageRenderOptions
    - Store customMessageRenderers in LoadedHook
    - Add getCustomMessageRenderer(customType) to HookRunner
    - SessionContext.entries now aligned with messages (same length, corresponding indices)
    
    TUI can now correlate messages with their source entries to identify
    custom_message entries and use hook-provided renderers.
  • 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
  • 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
  • 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
  • Add before_compact hook event (closes #281) (#285)
    * Add before_compact hook event (closes #281)
    
    * Add compact hook event and documentation
    
    - Add compact event that fires after compaction completes
    - Update hooks.md with lifecycle diagram, field docs, and example
    - Add CHANGELOG entry
    - Add comprehensive test coverage (10 tests) for before_compact and compact events
    - Tests cover: event emission, cancellation, custom entry, error handling, multiple hooks