Commit Graph

19 Commits

  • Cleanup: unify HookMessage naming and simplify SessionContext
    - Rename HookAppMessage to HookMessage, isHookAppMessage to isHookMessage
    - Remove entries array from SessionContext (use isHookMessage type guard instead)
    - HookMessage.content now accepts string directly (not just array)
    - Fix streamMessage type in AgentState (AppMessage, not Message)
    - Rename CustomMessageComponent to HookMessageComponent
    - Fix test hook to use pi.sendMessage
  • Add context event for non-destructive message modification before LLM calls
    - Add contextTransform option to Agent (runs before messageTransformer)
    - Deep copy messages before passing to contextTransform (modifications are ephemeral)
    - Add ContextEvent and ContextEventResult types
    - Add emitContext() to HookRunner (chains multiple handlers)
    - Wire up in sdk.ts when creating Agent with hooks
    
    Enables dynamic context pruning: hooks can modify messages sent to LLM
    without changing session data. See discussion #330.
  • Update plan: move exec to HookAPI, sessionManager/modelRegistry to contexts
    - exec() moves from HookEventContext/HookCommandContext to HookAPI
    - sessionManager/modelRegistry move from SessionEventBase to HookEventContext
    - HookCommandContext keeps sessionManager/modelRegistry (command handlers need them)
    - Both sendMessage and exec accessed via pi closure in command handlers
  • Hook commands: remove string return, use sendMessage() for prompting
    - Command handler now returns Promise<void> instead of Promise<string | undefined>
    - To trigger LLM response, use sendMessage() with triggerTurn: true
    - Simplify _tryExecuteHookCommand to return boolean
    
    Added example hook and slash command in .pi/:
    - .pi/hooks/test-command.ts - /greet command using sendMessage
    - .pi/commands/review.md - file-based /review command
  • Move hook command execution to AgentSession.prompt()
    Hook commands registered via pi.registerCommand() are now handled in
    AgentSession.prompt() alongside file-based slash commands. This:
    
    - Removes duplicate tryHandleHookCommand from interactive-mode and rpc-mode
    - All modes (interactive, RPC, print) share the same command handling logic
    - AgentSession._tryExecuteHookCommand() builds CommandContext using:
      - UI context from hookRunner (set by mode)
      - sessionManager, modelRegistry from AgentSession
      - sendMessage via sendHookMessage
      - exec via exported execCommand
    - Handler returning string uses it as prompt, undefined returns early
    
    Also:
    - Export execCommand from hooks/runner.ts
    - Add getUIContext() and getHasUI() to HookRunner
    - Make HookRunner.emitError() public for error reporting
  • 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
  • Fix CustomMessageEntry content type to match UserMessage
    content: string | (TextContent | ImageContent)[]
    
    This matches the UserMessage type from pi-ai, so content can be
    passed directly to AppMessage without conversion.
  • Add CustomMessageEntry for hook-injected messages in LLM context
    - CustomMessageEntry<T> type with customType, content, display, details
    - appendCustomMessageEntry() in SessionManager
    - buildSessionContext() includes custom_message entries as user messages
    - Exported CustomEntry and CustomMessageEntry from main index
    
    CustomEntry is for hook state (not in context).
    CustomMessageEntry is for hook-injected content (in context).
  • 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
  • Make CompactionEntry and CompactionResult generic with details field
    - CompactionEntry<T> and CompactionResult<T> now have optional details?: T
    - appendCompaction() accepts optional details parameter
    - Hooks can return compaction.details to store custom data
    - Enables structured compaction with ArtifactIndex (see #314)
    - Fix CompactionResult export location (now from compaction.ts)
    - Update plan with remaining compaction refactor items
  • Improve CustomEntry docs and make it generic
    - Add detailed doc comment explaining purpose (hook state persistence)
    - Make CustomEntry<T = unknown> generic
    - Clarify difference from CustomMessageEntry in plan
    - Update changelog
  • Use CompactionResult type for hook compaction return value
    - Import CompactionResult in hooks/types.ts
    - Replace inline type with CompactionResult for SessionEventResult.compaction
    - Add labels feature to changelog
  • Add label support for session entries
    - Add LabelEntry type with targetId and label (string | undefined)
    - Add labelsById map built on load via linear scan
    - Add getLabel(id) and appendLabelChange(targetId, label) methods
    - Add label field to SessionTreeNode, populated by getTree()
    - Update createBranchedSession to preserve labels for entries on path
    - Labels are ignored by buildSessionContext (not sent to LLM)
    - Add comprehensive tests for label functionality
  • Session tree structure with id/parentId linking
    - Add TreeNode base type with id, parentId, timestamp
    - Add *Content types for clean input/output separation
    - Entry types are now TreeNode & *Content intersections
    - SessionManager assigns id/parentId on save, tracks leafId
    - Add migrateSessionEntries() for v1 to v2 conversion
    - Migration runs on load, rewrites file
    - buildSessionContext() uses tree traversal from leaf
    - Compaction returns CompactionResult (content only)
    - Hooks return compaction content, not full entries
    - Add firstKeptEntryId to before_compact hook event
    - Update mom package for tree fields
    - Better error messages for compaction failures