Commit graph

23 commits

Author SHA1 Message Date
Helmut Januschka
4ecf3f9422 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
2026-01-04 18:13:29 +01:00
Helmut Januschka
c956a726ed 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
2026-01-04 18:13:28 +01:00
Helmut Januschka
059292ead1 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
2026-01-04 18:13:28 +01:00
Mario Zechner
c10d99d2de fix: export type guard functions at runtime, fixes #397 2026-01-02 10:37:28 +01:00
Mario Zechner
0d9fddec1e 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).
2026-01-02 00:24:58 +01:00
Mario Zechner
08fab16e2d Add ReadonlySessionManager and refactor branch summarization
- Add ReadonlySessionManager interface to session-manager.ts
- Re-export from hooks/index.ts
- Add collectEntriesForBranchSummary() to extract entries for summarization
- Don't stop at compaction boundaries (include their summaries as context)
- Add token budget support to prepareBranchEntries()
- Walk entries newest-to-oldest to prioritize recent context
- Use options object for generateBranchSummary()
- Handle compaction entries as context summaries
- Export new types: CollectEntriesResult, GenerateBranchSummaryOptions
2025-12-30 22:42:23 +01:00
Mario Zechner
d6283f99dc 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.
2025-12-30 22:42:22 +01:00
Mario Zechner
38d65dfe59 Add ReadonlySessionManager type for hooks
Hooks now receive ReadonlySessionManager in contexts, which only
exposes read methods. Writes must go through pi.sendMessage() or
pi.appendEntry().
2025-12-30 22:42:22 +01:00
Mario Zechner
57146de202 Implement before_agent_start hook event
- Add BeforeAgentStartEvent and BeforeAgentStartEventResult types
- Add emitBeforeAgentStart to HookRunner
- Call in AgentSession.prompt() before agent.prompt()
- Hook can return a message to inject into context (persisted + visible)
- Add test hook demonstrating custom message rendering and before_agent_start
2025-12-30 22:42:22 +01:00
Mario Zechner
204d27581b 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
2025-12-30 22:42:20 +01:00
Mario Zechner
77fe3f1a13 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.
2025-12-30 22:42:20 +01:00
Mario Zechner
14ad8d6228 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
2025-12-30 22:42:19 +01:00
Mario Zechner
a8866d7a83 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
2025-12-30 22:42:19 +01:00
Mario Zechner
09e7e9196c Update plan: HookCommandContext without sendMessage (use pi closure) 2025-12-30 22:42:19 +01:00
Mario Zechner
c8d9382aaa 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
2025-12-30 22:42:18 +01:00
Mario Zechner
ba185b0571 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
2025-12-30 22:42:18 +01:00
Mario Zechner
11a7845ceb 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.
2025-12-30 22:42:18 +01:00
Mario Zechner
42d7d9d9b6 Add before/after session events with cancellation support
- Merge branch event into session with before_branch/branch reasons
- Add before_switch, before_clear, shutdown reasons
- before_* events can be cancelled with { cancel: true }
- Update RPC commands to return cancelled status
- Add shutdown event on process exit
- New example hooks: confirm-destructive, dirty-repo-guard, auto-commit-on-exit

fixes #278
2025-12-22 18:18:38 +01:00
Mario Zechner
d353e5e219 Add type guards for tool_result event narrowing
- Export isBashToolResult, isReadToolResult, etc. type guards
- Update hooks.md with type guard usage examples
- Document custom tool handling in hooks.md
2025-12-19 00:48:03 +01:00
Mario Zechner
3d9bad8fb6 Expose full tool result content and details in hook tool_result event
Breaking change: ToolResultEvent now exposes content and typed details
instead of just a result string. Hook handlers returning { result: ... }
must change to { content: [...] }.

- ToolResultEvent is now a discriminated union based on toolName
- Each built-in tool has typed details (BashToolDetails, etc.)
- Export tool details types and TruncationResult
- Update hooks.md documentation

Closes #233
2025-12-19 00:42:08 +01:00
Mario Zechner
e7097d911a Custom tools with session lifecycle, examples for hooks and tools
- Custom tools: TypeScript modules that extend pi with new tools
  - Custom TUI rendering via renderCall/renderResult
  - User interaction via pi.ui (select, confirm, input, notify)
  - Session lifecycle via onSession callback for state reconstruction
  - Examples: todo.ts, question.ts, hello.ts

- Hook examples: permission-gate, git-checkpoint, protected-paths

- Session lifecycle centralized in AgentSession
  - Works across all modes (interactive, print, RPC)
  - Unified session event for hooks (replaces session_start/session_switch)

- Box component added to pi-tui

- Examples bundled in npm and binary releases

Fixes #190
2025-12-17 16:03:23 +01:00
Mario Zechner
7c553acd1e Add hooks system with pi.send() for external message injection
- Hook discovery from ~/.pi/agent/hooks/, .pi/hooks/, --hook flag
- Events: session_start, session_switch, agent_start/end, turn_start/end, tool_call, tool_result, branch
- tool_call can block execution, tool_result can modify results
- pi.send(text, attachments?) to inject messages from external sources
- UI primitives: ctx.ui.select/confirm/input/notify
- Context: ctx.exec(), ctx.cwd, ctx.sessionFile, ctx.hasUI
- Docs shipped with npm package and binary builds
- System prompt references docs folder
2025-12-10 00:50:30 +01:00
Mario Zechner
04d59f31ea feat(coding-agent): implement hooks system
- Add hooks infrastructure in core/hooks/ (loader, runner, types)
- HookUIContext interface with mode-specific implementations
- Interactive mode: TUI-based selector/input/confirm dialogs
- RPC mode: JSON protocol for hook UI requests/responses
- Print mode: no-op UI context (hooks run but can't prompt)
- AgentSession.branch() now async, returns { selectedText, skipped }
- Settings: hooks[] and hookTimeout configuration
- Export hook types from package for hook authors

Based on PR #147 proposal, adapted for new architecture.
2025-12-09 22:17:12 +01:00