Commit graph

466 commits

Author SHA1 Message Date
Mario Zechner
30cd723411 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
2025-12-30 22:42:18 +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
d43a5e47a1 Update CHANGELOG with CustomMessageEntry and hook rendering API 2025-12-30 22:42:18 +01:00
Mario Zechner
ba11622d0c Wire up hook custom message renderers to TUI
- CustomMessageComponent accepts optional CustomMessageRenderer
- If hook provides a renderer, call it and use returned Component inside Box
- Falls back to default rendering (label + Markdown) if no renderer or null returned
- renderSessionContext gets renderer from hookRunner and passes to component
2025-12-30 22:42:18 +01:00
Mario Zechner
7b94ddf36b Add TUI rendering for CustomMessageEntry
- Add CustomMessageComponent with purple-tinted styling
- Add theme colors: customMessageBg, customMessageText, customMessageLabel
- Rename renderMessages to renderSessionContext taking SessionContext directly
- renderInitialMessages now gets context from sessionManager
- Skip rendering for display: false entries
2025-12-30 22:42:18 +01:00
Mario Zechner
beb804cda0 Fix: compaction now handles branch_summary and custom_message entries
- Add getMessageFromEntry helper to extract AppMessage from any context-producing entry
- Update findValidCutPoints to treat branch_summary/custom_message as valid cut points
- Update findTurnStartIndex to recognize branch_summary/custom_message as turn starters
- Update all message extraction loops to use getMessageFromEntry
2025-12-30 22:42:18 +01:00
Mario Zechner
754f55e3f6 Fix: handle branch_summary in kept messages before compaction 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
3ecaaa5937 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.
2025-12-30 22:42:18 +01:00
Mario Zechner
9da36e5ac6 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).
2025-12-30 22:42:18 +01:00
Mario Zechner
9bba388ec5 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
2025-12-30 22:42:18 +01:00
Mario Zechner
d96375b5e5 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
2025-12-30 22:42:18 +01:00
Mario Zechner
efb1036d8e 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
2025-12-30 22:42:18 +01:00
Mario Zechner
e841942377 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
2025-12-30 22:42:18 +01:00
Mario Zechner
9e68a59fed 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
2025-12-30 22:42:18 +01:00
Mario Zechner
6f94e24629 Session tree: simplify types, add branching API, comprehensive tests
Types:
- SessionEntryBase with type field, extended by all entry types
- CustomEntry for hooks (type: 'custom', customType, data)
- Remove XXXContent types and TreeNode (redundant)

API:
- Rename saveXXX to appendXXX with JSDoc explaining tree semantics
- Rename branchInPlace to branch() with better docs
- Add createBranchedSession(leafId) replacing index-based version
- Add getTree() returning SessionTreeNode[] for tree traversal
- Add appendCustomEntry(customType, data) for hooks

Tests:
- tree-traversal.test.ts: 28 tests covering append, getPath, getTree,
  branch, branchWithSummary, createBranchedSession
- save-entry.test.ts: custom entry integration

Docs:
- Class-level JSDoc explaining append-only tree model
- Method docs explaining leaf advancement and branching
- CHANGELOG.md entry for all changes
2025-12-30 22:42:18 +01:00
Mario Zechner
beb70f126d Refactor session manager: migration chain, validation, tests
- Add migrateV1ToV2/migrateToCurrentVersion for extensible migrations
- createSummaryMessage now takes timestamp from entry
- loadEntriesFromFile validates session header
- findMostRecentSession only returns valid session files (reads first 512 bytes)
- Remove ConversationEntry alias
- Fix mom context.ts TreeNode type

Tests:
- migration.test.ts: v1 migration, idempotency
- build-context.test.ts: 14 tests covering trivial, compaction, branches
- file-operations.test.ts: loadEntriesFromFile, findMostRecentSession
2025-12-30 22:42:18 +01:00
Mario Zechner
95312e00bb Use short 8-char IDs for session entries
- Replace custom uuidv4() with native crypto.randomUUID()
- Entry IDs use first 8 hex chars with collision checking
- Session IDs stay full UUIDs (used in filenames)
- ~0.01 collisions per 10k entries, retry handles it
2025-12-30 22:42:17 +01:00
Mario Zechner
77595b97f9 Fix --session flag to use provided filename
When --session path was provided for a non-existent file, _initNewSession() was overwriting the path with an auto-generated one. Now it only generates a filename if sessionFile wasn't already set.
2025-12-30 22:42:17 +01:00
Mario Zechner
9478a3c1f5 Fix SessionEntry type to exclude SessionHeader
- SessionEntry now only contains conversation entries (messages, compaction, etc.)
- SessionHeader is separate, not part of SessionEntry
- FileEntry = SessionHeader | SessionEntry (for file storage)
- getEntries() filters out header, returns SessionEntry[]
- Added getHeader() for accessing session metadata
- Updated compaction and tests to not expect header in entries
- Updated mom package to use FileEntry for internal storage
2025-12-30 22:42:17 +01:00
Mario Zechner
251fea752c 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
2025-12-30 22:42:17 +01:00
Mario Zechner
c58d5f20a4 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
2025-12-30 22:42:17 +01:00
Pratham Dubey
8c43a9fbc8 Fix edit tool failing on Windows due to CRLF line endings
Normalize line endings to LF before matching, restore original style on write.
Files with CRLF now match when LLMs send LF-only oldText.

Fixes #355
2025-12-30 02:32:16 +05:30
Mario Zechner
65fb9116fb
Merge pull request #346 from ronyrus/main
Improve error message when `apiKey` or `model` are absent
2025-12-29 13:38:21 +01:00
Mario Zechner
8ced1131c0
Merge pull request #349 from Cursivez/fix/clickable-oauth-login-link
fix: Make OAuth login URL clickable in terminal
2025-12-29 13:34:33 +01:00
Armin Ronacher
e30c4e30cb Fix /copy after aborts 2025-12-29 11:54:45 +01:00
Cursivez
d42a3d71fe fix: make OAuth login URL clickable in terminal
Use OSC 8 hyperlink escape sequence to show 'Click here to login'
as a clickable link instead of displaying the raw URL which spans
multiple lines and is hard to click in terminals (especially WSL).
2025-12-29 13:45:11 +08:00
Rony Kelner
47b11bf798 Improve error message when apiKey or model are absent 2025-12-29 10:39:04 +09:00
Duncan Ogilvie
9e6282e3dd Add enabledModels to settings.json 2025-12-27 13:53:21 +01:00
Daniel Nouri
8fc0610a53 fix: Use bash instead of sh on Unix systems
The bash tool is named "bash" and described as executing bash commands,
but was using sh on Unix. On many distros (Ubuntu, Debian, Alpine, etc.),
/bin/sh is a POSIX-only shell that doesn't support bash syntax like [[ ]],
arrays, or here-strings. This caused the LLM to write bash syntax that
failed, wasting tokens on rewrites.

Now prefers /bin/bash on Unix, falling back to sh only if bash isn't found.
2025-12-26 23:12:57 +01:00
Mario Zechner
cb6310e159 Add automatic session migration for v0.30.0 bug
- Create migrations.ts with consolidated migrations
- Move auth migration from AuthStorage.migrateLegacy() to migrations.ts
- Add migrateSessionsFromAgentRoot() to fix misplaced sessions
- Sessions in ~/.pi/agent/*.jsonl are auto-migrated on startup

fixes #320
2025-12-26 03:24:49 +01:00
Mario Zechner
9dcb0bdba4 Release v0.30.1
fixes #320
2025-12-26 03:13:37 +01:00
Mario Zechner
fbfdeb2c2e Fix custom system prompts missing context, skills, date/time, cwd
fixes #321
2025-12-26 03:06:10 +01:00
Kao Félix
a7efe3d4c1 fix: use consistent model comparison including provider 2025-12-25 22:10:08 +01:00
Mario Zechner
911963e777 feat(coding-agent): Add --session-dir flag for custom session directory
- Add --session-dir CLI flag to specify custom session directory
- SessionManager API: second param of create(), continueRecent(), list(), open()
  changed from agentDir to sessionDir (direct directory, no cwd encoding)
- When omitted, uses default (~/.pi/agent/sessions/<encoded-cwd>/)
- --session now derives sessionDir from file's parent if --session-dir not provided
- list() validates session header before processing files
- Closes #313

Co-authored-by: scutifer <scutifer@users.noreply.github.com>
2025-12-25 20:27:41 +01:00
Mario Zechner
4edfff41a7
Merge pull request #315 from mitsuhiko/model-switcher
Reverse model switching and binding for dialog
2025-12-25 18:33:42 +01:00
Mario Zechner
3f0ffc6064 Merge branch 'fix-311' 2025-12-25 18:08:59 +01:00
Mario Zechner
66d19ac9c8 Fix jiti alias for typebox subpath imports in custom tools/hooks
fixes #311
2025-12-25 18:07:42 +01:00
Armin Ronacher
b576a527c7 Reverse model switching and binding for dialog 2025-12-25 18:07:36 +01:00
Mario Zechner
f8d3b0e34d Auto-load SYSTEM.md as custom system prompt
Closes #309
2025-12-25 18:04:22 +01:00
Mario Zechner
b4f7a957c4
Add /settings command with unified settings menu (#312)
* Add /settings command with unified settings menu

- Add SettingsList component to tui package with support for:
  - Inline value cycling (Enter/Space toggles)
  - Submenus for complex selections
  - Selection preservation when returning from submenu

- Add /settings slash command consolidating:
  - Auto-compact (toggle)
  - Show images (toggle)
  - Queue mode (cycle)
  - Hide thinking (toggle)
  - Collapse changelog (toggle)
  - Thinking level (submenu)
  - Theme (submenu with preview)

- Update AGENTS.md to clarify no inline imports rule

Fixes #310

* Add /settings to README slash commands table

* Remove old settings slash commands, consolidate into /settings

- Remove /thinking, /queue, /theme, /autocompact, /show-images commands
- Remove unused selector methods and imports
- Update README references to use /settings
2025-12-25 15:39:42 +01:00
Mario Zechner
c53b22db43 Add /settings command with unified settings menu
- Add SettingsList component to tui package with support for:
  - Inline value cycling (Enter/Space toggles)
  - Submenus for complex selections
  - Selection preservation when returning from submenu

- Add /settings slash command consolidating:
  - Auto-compact (toggle)
  - Show images (toggle)
  - Queue mode (cycle)
  - Hide thinking (toggle)
  - Collapse changelog (toggle)
  - Thinking level (submenu)
  - Theme (submenu with preview)

- Update AGENTS.md to clarify no inline imports rule

Fixes #310
2025-12-25 15:35:06 +01:00
Mario Zechner
454ea1d36a Rename /clear to /new, update hook events to before_new/new
Closes #305 - took direct rename approach instead of alias system

Thanks @mitsuhiko for the nudge!
2025-12-25 04:15:10 +01:00
Mario Zechner
1059d39d54 Add removeRuntimeApiKey to AuthStorage 2025-12-25 03:57:20 +01:00
Mario Zechner
54018b6cc0 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
2025-12-25 03:48:36 +01:00
Mario Zechner
bf022d2581 WIP: Add auth-storage.ts for credential management
- AuthStorage class for reading/writing auth.json
- Supports both api_key and oauth credential types
- getApiKey() priority: auth.json api_key > auth.json oauth > env var
2025-12-25 01:23:45 +01:00
Mario Zechner
1c31d91c83 WIP: Rename model-config.ts to models-json.ts
- loadCustomModels now takes file path instead of agentDir
2025-12-25 01:15:17 +01:00
Armin Ronacher
0c4d60f8c8 Skip over exact duplicate skills 2025-12-24 22:45:19 +01:00
Mario Zechner
ac5f4a77cc Fix model selector not showing models with settings.json API keys
Fixes #295
2025-12-24 21:23:44 +01:00