Commit graph

619 commits

Author SHA1 Message Date
Mario Zechner
9794868b38 Implement extension discovery with package.json manifest support
Discovery rules:
1. extensions/*.ts or *.js - direct files
2. extensions/*/index.ts or index.js - subdirectory with index
3. extensions/*/package.json with pi field - load declared paths

No recursion beyond one level. Complex packages use package.json manifest.

Added PiManifest type for future theme/skill bundling support.

17 tests covering all discovery scenarios.

refs #454
2026-01-04 23:24:24 +01:00
Mario Zechner
2846c7d190 Add unified extensions system (not wired up yet)
New src/core/extensions/ directory with:
- types.ts: merged types from hooks and custom-tools
- loader.ts: single loader for extensions
- runner.ts: ExtensionRunner for event emission
- wrapper.ts: tool wrapping utilities
- index.ts: exports

Key changes from old system:
- Single ExtensionAPI with registerTool() for LLM-callable tools
- Tools use ExtensionContext (has UI access)
- No onSession callback on tools (use pi.on events instead)

refs #454
2026-01-04 22:51:11 +01:00
Mario Zechner
be330fdd9c 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
2026-01-04 22:04:50 +01:00
Nico Bailon
9c9e6822e3
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
2026-01-04 21:36:19 +01:00
Aliou Diallo
5cb7af1ddc
feat(hooks): expose setTitle to HookUIContext (#446) 2026-01-04 20:55:52 +01:00
Mario Zechner
00fa8b71c8 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.
2026-01-04 20:27:46 +01:00
Mario Zechner
31438fdf2a fix(tools): wrap ALL registry tools with hooks, not just active ones
wrappedToolRegistry was only containing activeToolsArray (4 tools).
Now wraps all tools from the registry so hooks can enable any tool.
2026-01-04 18:47:57 +01:00
Mario Zechner
2849623afc fix(tools): tool registry now contains ALL built-in tools
- createAllTools() populates registry with all 7 built-in tools
- --tools flag only sets initially active tools (default: read/bash/edit/write)
- Hooks can enable any tool from registry via setActiveTools()
- System prompt rebuilds with correct tool guidelines when tools change
- Document tsx module resolution workaround in README
2026-01-04 18:44:41 +01:00
Mario Zechner
c447e62662 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
2026-01-04 18:39:00 +01:00
Mario Zechner
6390ff87ef 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
2026-01-04 18:33:28 +01:00
Mario Zechner
e4dd21a3b2 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
2026-01-04 18:21:26 +01:00
Mario Zechner
892acedb6b 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
2026-01-04 18:13:30 +01:00
Mario Zechner
ddf8bfceee fix(hooks): deep copy messages in context event before passing to hooks
The context event handler documentation promised a deep copy but the
implementation passed the original array reference. This could cause
hooks to accidentally mutate session messages.

Uses structuredClone() for fast native deep copying.
2026-01-04 18:13:30 +01:00
Helmut Januschka
8ecb1d6c0b 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)
2026-01-04 18:13:30 +01:00
Helmut Januschka
c1d7f8d962 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
2026-01-04 18:13:29 +01:00
Helmut Januschka
277d7bd83b 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
2026-01-04 18:13:29 +01:00
Helmut Januschka
274d4a6247 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
2026-01-04 18:13:29 +01:00
Helmut Januschka
ce88ebcd68 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
2026-01-04 18:13:29 +01:00
Helmut Januschka
9b53b89bd5 fix(widgets): add max line limit and document multi-hook behavior
- Limit total widget lines to 10 to prevent viewport overflow/flicker
- Show '... (widget truncated)' when limit exceeded
- Document that multiple hooks stack widgets vertically
- Add caution about keeping widgets small
2026-01-04 18:13:29 +01:00
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
d1eea3ac4e 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
2026-01-04 18:13:28 +01:00
Helmut Januschka
dc44816051 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
2026-01-04 18:13:28 +01:00
Helmut Januschka
056f40b00b feat(coding-agent): show hook shortcuts in /hotkeys command 2026-01-04 18:13:28 +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
Armin Ronacher
5b95ccf830
Add consistent, configurable image placeholders (#442)
* Add consistent, configurable image placeholders

* Kill the placeholders
2026-01-04 18:12:55 +01:00
Aliou Diallo
0d477d39f9
fix(tui): expand paste markers when opening external editor (#444)
* fix(tui): expand paste markers when opening external editor

Add getExpandedText() method to Editor that substitutes paste markers
with actual content. Use it in Ctrl-G external editor flow so users
see full pasted content instead of [paste #N ...] placeholders.

* docs: add changelog entries for #444
2026-01-04 18:11:42 +01:00
Can Bölük
6ddfd1be13
fix(coding-agent): resolved UTF-8 corruption in bash executor output (#433)
- Fixed UTF-8 text corruption in bash executor by replacing Buffer.toString() with streaming TextDecoder.
2026-01-04 03:15:50 +01:00
Mario Zechner
5c5084481b feat(coding-agent): clipboard image paste support via Ctrl+V (fixes #419) 2026-01-04 01:05:22 +01:00
Mario Zechner
f49d2aac12 Use same header color for Available Tools as System Prompt 2026-01-03 23:05:32 +01:00
Mario Zechner
f9838bb5d4 Show first 10 lines of system prompt with click to expand for full content 2026-01-03 23:04:23 +01:00
Mario Zechner
f5e38c4007 Add click-to-expand for system prompt in HTML export 2026-01-03 23:02:30 +01:00
Mario Zechner
f090ec214a Fix missing spacer between text and tool calls in HTML export 2026-01-03 23:00:35 +01:00
Mario Zechner
e98524f1fc Fix missing spacer between text and tool calls in HTML export 2026-01-03 22:56:36 +01:00
Mario Zechner
f2b89d5ec5 Merge branch 'feat/ctrl-n-external-editor' - configurable keybindings (#405) 2026-01-03 22:51:12 +01:00
Ben Vargas
8f5466f42a
feat(coding-agent): add /quit and /exit slash commands to exit (#426)
Add new slash commands for gracefully exiting the interactive mode:
- /quit - exit the application
- /exit - alias for /quit (common CLI convention)

Both commands await shutdown() which emits session_shutdown events
to hooks and custom tools before exiting. Unlike Ctrl+C (twice) which
uses void (fire-and-forget), these commands properly wait for hooks
and tools to complete their shutdown handlers.
2026-01-03 22:38:55 +01:00
Mario Zechner
79c56475e0 Fix non-PNG images not displaying in Kitty protocol terminals
JPEG/GIF/WebP images were not rendering in terminals using the Kitty
graphics protocol (Kitty, Ghostty, WezTerm) because it requires PNG
format (f=100). Non-PNG images are now converted to PNG using sharp
before being sent to the terminal.
2026-01-03 16:58:57 +01:00
Evgeniy Skuridin
9b0ec02405
Fix version check URL typo (#423) 2026-01-03 16:55:38 +01:00
Armin Ronacher
69dc6b0788
More attempts to get an image under 5MB (#424) 2026-01-03 16:54:57 +01:00
Mario Zechner
e9cf3c1835 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
2026-01-03 15:36:54 +01:00
Helmut Januschka
f79256ac3b fix: update /hotkeys and startup hints to use configured keybindings
Both the startup instructions and /hotkeys command now display the
actual configured keybindings from keybindings.json instead of
hardcoded defaults.
2026-01-03 15:01:26 +01:00
Evgeniy Skuridin
8917a1f853
feat(coding-agent): add $ARGUMENTS syntax for slash commands (#418)
* feat(coding-agent): add $ARGUMENTS syntax for slash commands

* test(coding-agent): add tests for slash command argument substitution
2026-01-03 14:08:39 +01:00
cursive
9e3b1408f3
Copy OAuth login URL to clipboard for mobile SSH users (#415) 2026-01-03 09:43:25 +01:00
Helmut Januschka
7574fed2f2 feat: use keybindings in all selectors and lists
Update all selector/list components to use EditorKeybindingsManager:
- model-selector, session-selector, oauth-selector, user-message-selector
- hook-selector, hook-input, hook-editor, tree-selector
- select-list, settings-list, cancellable-loader

This allows users to configure selectUp, selectDown, selectConfirm,
selectCancel actions in keybindings.json
2026-01-03 08:23:56 +01:00
Helmut Januschka
8f2682578b 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"
}
2026-01-03 08:23:56 +01:00
Mario Zechner
0f81b09625 Fix edit tool diff not showing in TUI
The async diff preview computation could race with tool execution,
causing editDiffPreview to contain an error (file already modified)
while the actual tool result had the correct diff in details.diff.

Fix: prioritize result.details.diff over editDiffPreview when the
tool has executed successfully. The preview is only used before
tool execution completes.
2026-01-03 04:23:59 +01:00
Mario Zechner
746ec9eb01 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
2026-01-03 04:14:35 +01:00
Mario Zechner
97af788344 Show 'In-memory' instead of 'File: undefined' for --no-session mode 2026-01-03 02:01:30 +01:00
Mario Zechner
42b1e06ad1 feat(coding-agent): configurable double-escape action (tree vs branch)
Add doubleEscapeAction setting to choose whether double-escape with an
empty editor opens /tree (default) or /branch.

- Add setting to Settings interface and SettingsManager
- Add to /settings UI for easy toggling
- Update interactive-mode to respect the setting
- Document in README.md settings table

fixes #404
2026-01-03 01:59:08 +01:00
Mario Zechner
17b3a14bfa Fix slow /model selector by deferring OAuth token refresh
getAvailable() now uses hasAuth() which checks if auth is configured
without triggering OAuth token refresh. Refresh happens later when
the model is actually used.
2026-01-03 01:20:11 +01:00