@oh-my-pi/pi-coding-agent 1.337.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1228 -0
- package/README.md +1041 -0
- package/docs/compaction.md +403 -0
- package/docs/custom-tools.md +541 -0
- package/docs/extension-loading.md +1004 -0
- package/docs/hooks.md +867 -0
- package/docs/rpc.md +1040 -0
- package/docs/sdk.md +994 -0
- package/docs/session-tree-plan.md +441 -0
- package/docs/session.md +240 -0
- package/docs/skills.md +290 -0
- package/docs/theme.md +637 -0
- package/docs/tree.md +197 -0
- package/docs/tui.md +341 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +124 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/custom-tools/question/index.ts +84 -0
- package/examples/custom-tools/subagent/README.md +172 -0
- package/examples/custom-tools/subagent/agents/planner.md +37 -0
- package/examples/custom-tools/subagent/agents/reviewer.md +35 -0
- package/examples/custom-tools/subagent/agents/scout.md +50 -0
- package/examples/custom-tools/subagent/agents/worker.md +24 -0
- package/examples/custom-tools/subagent/agents.ts +156 -0
- package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
- package/examples/custom-tools/subagent/commands/implement.md +10 -0
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
- package/examples/custom-tools/subagent/index.ts +1002 -0
- package/examples/custom-tools/todo/index.ts +212 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +49 -0
- package/examples/hooks/confirm-destructive.ts +59 -0
- package/examples/hooks/custom-compaction.ts +116 -0
- package/examples/hooks/dirty-repo-guard.ts +52 -0
- package/examples/hooks/file-trigger.ts +41 -0
- package/examples/hooks/git-checkpoint.ts +53 -0
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +34 -0
- package/examples/hooks/protected-paths.ts +30 -0
- package/examples/hooks/qna.ts +119 -0
- package/examples/hooks/snake.ts +343 -0
- package/examples/hooks/status-line.ts +40 -0
- package/examples/sdk/01-minimal.ts +22 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +44 -0
- package/examples/sdk/04-skills.ts +44 -0
- package/examples/sdk/05-tools.ts +90 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +36 -0
- package/examples/sdk/08-slash-commands.ts +42 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
- package/examples/sdk/10-settings.ts +38 -0
- package/examples/sdk/11-sessions.ts +48 -0
- package/examples/sdk/12-full-control.ts +95 -0
- package/examples/sdk/README.md +154 -0
- package/package.json +81 -0
- package/src/cli/args.ts +246 -0
- package/src/cli/file-processor.ts +72 -0
- package/src/cli/list-models.ts +104 -0
- package/src/cli/plugin-cli.ts +650 -0
- package/src/cli/session-picker.ts +41 -0
- package/src/cli.ts +10 -0
- package/src/commands/init.md +20 -0
- package/src/config.ts +159 -0
- package/src/core/agent-session.ts +1900 -0
- package/src/core/auth-storage.ts +236 -0
- package/src/core/bash-executor.ts +196 -0
- package/src/core/compaction/branch-summarization.ts +343 -0
- package/src/core/compaction/compaction.ts +742 -0
- package/src/core/compaction/index.ts +7 -0
- package/src/core/compaction/utils.ts +154 -0
- package/src/core/custom-tools/index.ts +21 -0
- package/src/core/custom-tools/loader.ts +248 -0
- package/src/core/custom-tools/types.ts +169 -0
- package/src/core/custom-tools/wrapper.ts +28 -0
- package/src/core/exec.ts +129 -0
- package/src/core/export-html/index.ts +211 -0
- package/src/core/export-html/template.css +781 -0
- package/src/core/export-html/template.html +54 -0
- package/src/core/export-html/template.js +1185 -0
- package/src/core/export-html/vendor/highlight.min.js +1213 -0
- package/src/core/export-html/vendor/marked.min.js +6 -0
- package/src/core/hooks/index.ts +16 -0
- package/src/core/hooks/loader.ts +312 -0
- package/src/core/hooks/runner.ts +434 -0
- package/src/core/hooks/tool-wrapper.ts +99 -0
- package/src/core/hooks/types.ts +773 -0
- package/src/core/index.ts +52 -0
- package/src/core/mcp/client.ts +158 -0
- package/src/core/mcp/config.ts +154 -0
- package/src/core/mcp/index.ts +45 -0
- package/src/core/mcp/loader.ts +68 -0
- package/src/core/mcp/manager.ts +181 -0
- package/src/core/mcp/tool-bridge.ts +148 -0
- package/src/core/mcp/transports/http.ts +316 -0
- package/src/core/mcp/transports/index.ts +6 -0
- package/src/core/mcp/transports/stdio.ts +252 -0
- package/src/core/mcp/types.ts +220 -0
- package/src/core/messages.ts +189 -0
- package/src/core/model-registry.ts +317 -0
- package/src/core/model-resolver.ts +393 -0
- package/src/core/plugins/doctor.ts +59 -0
- package/src/core/plugins/index.ts +38 -0
- package/src/core/plugins/installer.ts +189 -0
- package/src/core/plugins/loader.ts +338 -0
- package/src/core/plugins/manager.ts +672 -0
- package/src/core/plugins/parser.ts +105 -0
- package/src/core/plugins/paths.ts +32 -0
- package/src/core/plugins/types.ts +190 -0
- package/src/core/sdk.ts +760 -0
- package/src/core/session-manager.ts +1128 -0
- package/src/core/settings-manager.ts +443 -0
- package/src/core/skills.ts +437 -0
- package/src/core/slash-commands.ts +248 -0
- package/src/core/system-prompt.ts +439 -0
- package/src/core/timings.ts +25 -0
- package/src/core/tools/ask.ts +211 -0
- package/src/core/tools/bash-interceptor.ts +120 -0
- package/src/core/tools/bash.ts +250 -0
- package/src/core/tools/context.ts +32 -0
- package/src/core/tools/edit-diff.ts +475 -0
- package/src/core/tools/edit.ts +208 -0
- package/src/core/tools/exa/company.ts +59 -0
- package/src/core/tools/exa/index.ts +64 -0
- package/src/core/tools/exa/linkedin.ts +59 -0
- package/src/core/tools/exa/logger.ts +56 -0
- package/src/core/tools/exa/mcp-client.ts +368 -0
- package/src/core/tools/exa/render.ts +196 -0
- package/src/core/tools/exa/researcher.ts +90 -0
- package/src/core/tools/exa/search.ts +337 -0
- package/src/core/tools/exa/types.ts +168 -0
- package/src/core/tools/exa/websets.ts +248 -0
- package/src/core/tools/find.ts +261 -0
- package/src/core/tools/grep.ts +555 -0
- package/src/core/tools/index.ts +202 -0
- package/src/core/tools/ls.ts +140 -0
- package/src/core/tools/lsp/client.ts +605 -0
- package/src/core/tools/lsp/config.ts +147 -0
- package/src/core/tools/lsp/edits.ts +101 -0
- package/src/core/tools/lsp/index.ts +804 -0
- package/src/core/tools/lsp/render.ts +447 -0
- package/src/core/tools/lsp/rust-analyzer.ts +145 -0
- package/src/core/tools/lsp/types.ts +463 -0
- package/src/core/tools/lsp/utils.ts +486 -0
- package/src/core/tools/notebook.ts +229 -0
- package/src/core/tools/path-utils.ts +61 -0
- package/src/core/tools/read.ts +240 -0
- package/src/core/tools/renderers.ts +540 -0
- package/src/core/tools/task/agents.ts +153 -0
- package/src/core/tools/task/artifacts.ts +114 -0
- package/src/core/tools/task/bundled-agents/browser.md +71 -0
- package/src/core/tools/task/bundled-agents/explore.md +82 -0
- package/src/core/tools/task/bundled-agents/plan.md +54 -0
- package/src/core/tools/task/bundled-agents/reviewer.md +59 -0
- package/src/core/tools/task/bundled-agents/task.md +53 -0
- package/src/core/tools/task/bundled-commands/architect-plan.md +10 -0
- package/src/core/tools/task/bundled-commands/implement-with-critic.md +11 -0
- package/src/core/tools/task/bundled-commands/implement.md +11 -0
- package/src/core/tools/task/commands.ts +213 -0
- package/src/core/tools/task/discovery.ts +208 -0
- package/src/core/tools/task/executor.ts +367 -0
- package/src/core/tools/task/index.ts +388 -0
- package/src/core/tools/task/model-resolver.ts +115 -0
- package/src/core/tools/task/parallel.ts +38 -0
- package/src/core/tools/task/render.ts +232 -0
- package/src/core/tools/task/types.ts +99 -0
- package/src/core/tools/truncate.ts +265 -0
- package/src/core/tools/web-fetch.ts +2370 -0
- package/src/core/tools/web-search/auth.ts +193 -0
- package/src/core/tools/web-search/index.ts +537 -0
- package/src/core/tools/web-search/providers/anthropic.ts +198 -0
- package/src/core/tools/web-search/providers/exa.ts +302 -0
- package/src/core/tools/web-search/providers/perplexity.ts +195 -0
- package/src/core/tools/web-search/render.ts +182 -0
- package/src/core/tools/web-search/types.ts +180 -0
- package/src/core/tools/write.ts +99 -0
- package/src/index.ts +176 -0
- package/src/main.ts +464 -0
- package/src/migrations.ts +135 -0
- package/src/modes/index.ts +43 -0
- package/src/modes/interactive/components/armin.ts +382 -0
- package/src/modes/interactive/components/assistant-message.ts +86 -0
- package/src/modes/interactive/components/bash-execution.ts +196 -0
- package/src/modes/interactive/components/bordered-loader.ts +41 -0
- package/src/modes/interactive/components/branch-summary-message.ts +42 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
- package/src/modes/interactive/components/custom-editor.ts +122 -0
- package/src/modes/interactive/components/diff.ts +147 -0
- package/src/modes/interactive/components/dynamic-border.ts +25 -0
- package/src/modes/interactive/components/footer.ts +381 -0
- package/src/modes/interactive/components/hook-editor.ts +117 -0
- package/src/modes/interactive/components/hook-input.ts +64 -0
- package/src/modes/interactive/components/hook-message.ts +96 -0
- package/src/modes/interactive/components/hook-selector.ts +91 -0
- package/src/modes/interactive/components/model-selector.ts +247 -0
- package/src/modes/interactive/components/oauth-selector.ts +120 -0
- package/src/modes/interactive/components/plugin-settings.ts +479 -0
- package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
- package/src/modes/interactive/components/session-selector.ts +204 -0
- package/src/modes/interactive/components/settings-selector.ts +453 -0
- package/src/modes/interactive/components/show-images-selector.ts +45 -0
- package/src/modes/interactive/components/theme-selector.ts +62 -0
- package/src/modes/interactive/components/thinking-selector.ts +64 -0
- package/src/modes/interactive/components/tool-execution.ts +675 -0
- package/src/modes/interactive/components/tree-selector.ts +866 -0
- package/src/modes/interactive/components/user-message-selector.ts +159 -0
- package/src/modes/interactive/components/user-message.ts +18 -0
- package/src/modes/interactive/components/visual-truncate.ts +50 -0
- package/src/modes/interactive/components/welcome.ts +183 -0
- package/src/modes/interactive/interactive-mode.ts +2516 -0
- package/src/modes/interactive/theme/dark.json +101 -0
- package/src/modes/interactive/theme/light.json +98 -0
- package/src/modes/interactive/theme/theme-schema.json +308 -0
- package/src/modes/interactive/theme/theme.ts +998 -0
- package/src/modes/print-mode.ts +128 -0
- package/src/modes/rpc/rpc-client.ts +527 -0
- package/src/modes/rpc/rpc-mode.ts +483 -0
- package/src/modes/rpc/rpc-types.ts +203 -0
- package/src/utils/changelog.ts +99 -0
- package/src/utils/clipboard.ts +265 -0
- package/src/utils/fuzzy.ts +108 -0
- package/src/utils/mime.ts +30 -0
- package/src/utils/shell.ts +276 -0
- package/src/utils/tools-manager.ts +274 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
# Session Tree Implementation Plan
|
|
2
|
+
|
|
3
|
+
Reference: [session-tree.md](./session-tree.md)
|
|
4
|
+
|
|
5
|
+
## Phase 1: SessionManager Core ✅
|
|
6
|
+
|
|
7
|
+
- [x] Update entry types with `id`, `parentId` fields (using SessionEntryBase)
|
|
8
|
+
- [x] Add `version` field to `SessionHeader`
|
|
9
|
+
- [x] Change `CompactionEntry.firstKeptEntryIndex` → `firstKeptEntryId`
|
|
10
|
+
- [x] Add `BranchSummaryEntry` type
|
|
11
|
+
- [x] Add `CustomEntry` type for hooks
|
|
12
|
+
- [x] Add `byId: Map<string, SessionEntry>` index
|
|
13
|
+
- [x] Add `leafId: string` tracking
|
|
14
|
+
- [x] Implement `getPath(fromId?)` tree traversal
|
|
15
|
+
- [x] Implement `getTree()` returning `SessionTreeNode[]`
|
|
16
|
+
- [x] Implement `getEntry(id)` lookup
|
|
17
|
+
- [x] Implement `getLeafUuid()` and `getLeafEntry()` helpers
|
|
18
|
+
- [x] Update `_buildIndex()` to populate `byId` map
|
|
19
|
+
- [x] Rename `saveXXX()` to `appendXXX()` (returns id, advances leaf)
|
|
20
|
+
- [x] Add `appendCustomEntry(customType, data)` for hooks
|
|
21
|
+
- [x] Update `buildSessionContext()` to use `getPath()` traversal
|
|
22
|
+
|
|
23
|
+
## Phase 2: Migration ✅
|
|
24
|
+
|
|
25
|
+
- [x] Add `CURRENT_SESSION_VERSION = 2` constant
|
|
26
|
+
- [x] Implement `migrateV1ToV2()` with extensible migration chain
|
|
27
|
+
- [x] Update `setSessionFile()` to detect version and migrate
|
|
28
|
+
- [x] Implement `_rewriteFile()` for post-migration persistence
|
|
29
|
+
- [x] Handle `firstKeptEntryIndex` → `firstKeptEntryId` conversion in migration
|
|
30
|
+
|
|
31
|
+
## Phase 3: Branching ✅
|
|
32
|
+
|
|
33
|
+
- [x] Implement `branch(id)` - switch leaf pointer
|
|
34
|
+
- [x] Implement `branchWithSummary(id, summary)` - create summary entry
|
|
35
|
+
- [x] Implement `createBranchedSession(leafId)` - extract path to new file
|
|
36
|
+
- [x] Update `AgentSession.branch()` to use new API
|
|
37
|
+
|
|
38
|
+
## Phase 4: Compaction Integration ✅
|
|
39
|
+
|
|
40
|
+
- [x] Update `compaction.ts` to work with IDs
|
|
41
|
+
- [x] Update `prepareCompaction()` to return `firstKeptEntryId`
|
|
42
|
+
- [x] Update `compact()` to return `CompactionResult` with `firstKeptEntryId`
|
|
43
|
+
- [x] Update `AgentSession` compaction methods
|
|
44
|
+
- [x] Add `firstKeptEntryId` to `before_compact` hook event
|
|
45
|
+
|
|
46
|
+
## Phase 5: Testing ✅
|
|
47
|
+
|
|
48
|
+
- [x] `migration.test.ts` - v1 to v2 migration, idempotency
|
|
49
|
+
- [x] `build-context.test.ts` - context building with tree structure, compaction, branches
|
|
50
|
+
- [x] `tree-traversal.test.ts` - append operations, getPath, getTree, branching
|
|
51
|
+
- [x] `file-operations.test.ts` - loadEntriesFromFile, findMostRecentSession
|
|
52
|
+
- [x] `save-entry.test.ts` - custom entry integration
|
|
53
|
+
- [x] Update existing compaction tests for new types
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Remaining Work
|
|
58
|
+
|
|
59
|
+
### Compaction Refactor
|
|
60
|
+
|
|
61
|
+
- [x] Use `CompactionResult` type for hook return value
|
|
62
|
+
- [x] Make `CompactionEntry<T>` generic with optional `details?: T` field for hook-specific data
|
|
63
|
+
- [x] Make `CompactionResult<T>` generic to match
|
|
64
|
+
- [x] Update `SessionEventBase` to pass `sessionManager` and `modelRegistry` instead of derived fields
|
|
65
|
+
- [x] Update `before_compact` event:
|
|
66
|
+
- Pass `preparation: CompactionPreparation` instead of individual fields
|
|
67
|
+
- Pass `previousCompactions: CompactionEntry[]` (newest first) instead of `previousSummary?: string`
|
|
68
|
+
- Keep: `customInstructions`, `model`, `signal`
|
|
69
|
+
- Drop: `resolveApiKey` (use `modelRegistry.getApiKey()`), `cutPoint`, `entries`
|
|
70
|
+
- [x] Update hook example `custom-compaction.ts` to use new API
|
|
71
|
+
- [x] Update `getSessionFile()` to return `string | undefined` for in-memory sessions
|
|
72
|
+
- [x] Update `before_switch` to have `targetSessionFile`, `switch` to have `previousSessionFile`
|
|
73
|
+
|
|
74
|
+
Reference: [#314](https://github.com/badlogic/pi-mono/pull/314) - Structured compaction with anchored iterative summarization needs `details` field to store `ArtifactIndex` and version markers.
|
|
75
|
+
|
|
76
|
+
### Branch Summary Design ✅
|
|
77
|
+
|
|
78
|
+
Current type:
|
|
79
|
+
```typescript
|
|
80
|
+
export interface BranchSummaryEntry extends SessionEntryBase {
|
|
81
|
+
type: "branch_summary";
|
|
82
|
+
summary: string;
|
|
83
|
+
fromId: string; // References the abandoned leaf
|
|
84
|
+
fromHook?: boolean; // Whether summary was generated by a hook
|
|
85
|
+
details?: unknown; // File tracking: { readFiles, modifiedFiles }
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
- [x] `fromId` field references the abandoned leaf
|
|
90
|
+
- [x] `fromHook` field distinguishes pi-generated vs hook-generated summaries
|
|
91
|
+
- [x] `details` field for file tracking
|
|
92
|
+
- [x] Branch summarizer implemented with structured output format
|
|
93
|
+
- [x] Uses serialization approach (same as compaction) to prevent model confusion
|
|
94
|
+
- [x] Tests for `branchWithSummary()` flow
|
|
95
|
+
|
|
96
|
+
### Entry Labels ✅
|
|
97
|
+
|
|
98
|
+
- [x] Add `LabelEntry` type with `targetId` and `label` fields
|
|
99
|
+
- [x] Add `labelsById: Map<string, string>` private field
|
|
100
|
+
- [x] Build labels map in `_buildIndex()` via linear scan
|
|
101
|
+
- [x] Add `getLabel(id)` method
|
|
102
|
+
- [x] Add `appendLabelChange(targetId, label)` method (undefined clears)
|
|
103
|
+
- [x] Update `createBranchedSession()` to filter out LabelEntry and recreate from resolved map
|
|
104
|
+
- [x] `buildSessionContext()` already ignores LabelEntry (only handles message types)
|
|
105
|
+
- [x] Add `label?: string` to `SessionTreeNode`, populated by `getTree()`
|
|
106
|
+
- [x] Display labels in UI (tree-selector shows labels)
|
|
107
|
+
- [x] `/label` command (implemented in tree-selector)
|
|
108
|
+
|
|
109
|
+
### CustomMessageEntry<T>
|
|
110
|
+
|
|
111
|
+
Hook-injected messages that participate in LLM context. Unlike `CustomEntry<T>` (for hook state only), these are sent to the model.
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
export interface CustomMessageEntry<T = unknown> extends SessionEntryBase {
|
|
115
|
+
type: "custom_message";
|
|
116
|
+
customType: string; // Hook identifier
|
|
117
|
+
content: string | (TextContent | ImageContent)[]; // Message content (same as UserMessage)
|
|
118
|
+
details?: T; // Hook-specific data for state reconstruction on reload
|
|
119
|
+
display: boolean; // Whether to display in TUI
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Behavior:
|
|
124
|
+
- [x] Type definition matching plan
|
|
125
|
+
- [x] `appendCustomMessageEntry(customType, content, display, details?)` in SessionManager
|
|
126
|
+
- [x] `buildSessionContext()` includes custom_message entries as user messages
|
|
127
|
+
- [x] Exported from main index
|
|
128
|
+
- [x] TUI rendering:
|
|
129
|
+
- `display: false` - hidden entirely
|
|
130
|
+
- `display: true` - rendered with purple styling (customMessageBg, customMessageText, customMessageLabel theme colors)
|
|
131
|
+
- [x] `registerCustomMessageRenderer(customType, renderer)` in HookAPI for custom renderers
|
|
132
|
+
- [x] Renderer returns inner Component, TUI wraps in styled Box
|
|
133
|
+
|
|
134
|
+
### Hook API Changes ✅
|
|
135
|
+
|
|
136
|
+
**Renamed:**
|
|
137
|
+
- `renderCustomMessage()` → `registerCustomMessageRenderer()`
|
|
138
|
+
|
|
139
|
+
**New: `sendMessage()` ✅**
|
|
140
|
+
|
|
141
|
+
Replaces `send()`. Always creates CustomMessageEntry, never user messages.
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
type HookMessage<T = unknown> = Pick<CustomMessageEntry<T>, 'customType' | 'content' | 'display' | 'details'>;
|
|
145
|
+
|
|
146
|
+
sendMessage(message: HookMessage, triggerTurn?: boolean): void;
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Implementation:
|
|
150
|
+
- Uses agent's queue mechanism with `_hookData` marker on AppMessage
|
|
151
|
+
- `message_end` handler routes based on marker presence
|
|
152
|
+
- `AgentSession.sendHookMessage()` handles three cases:
|
|
153
|
+
- Streaming: queues via `agent.queueMessage()`, loop processes and emits `message_end`
|
|
154
|
+
- Not streaming + triggerTurn: direct append + `agent.continue()`
|
|
155
|
+
- Not streaming + no trigger: direct append only
|
|
156
|
+
- TUI updates via event (streaming) or explicit rebuild (non-streaming)
|
|
157
|
+
|
|
158
|
+
**New: `appendEntry()` ✅**
|
|
159
|
+
|
|
160
|
+
For hook state persistence (NOT in LLM context):
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
appendEntry(customType: string, data?: unknown): void;
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Calls `sessionManager.appendCustomEntry()` directly.
|
|
167
|
+
|
|
168
|
+
**New: `registerCommand()` (types ✅, wiring TODO)**
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
// HookAPI (the `pi` object) - utilities available to all hooks:
|
|
172
|
+
interface HookAPI {
|
|
173
|
+
sendMessage(message: HookMessage, triggerTurn?: boolean): void;
|
|
174
|
+
appendEntry(customType: string, data?: unknown): void;
|
|
175
|
+
registerCommand(name: string, options: RegisteredCommand): void;
|
|
176
|
+
registerCustomMessageRenderer(customType: string, renderer: CustomMessageRenderer): void;
|
|
177
|
+
exec(command: string, args: string[], options?: ExecOptions): Promise<ExecResult>;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// HookEventContext - passed to event handlers, has stable context:
|
|
181
|
+
interface HookEventContext {
|
|
182
|
+
ui: HookUIContext;
|
|
183
|
+
hasUI: boolean;
|
|
184
|
+
cwd: string;
|
|
185
|
+
sessionManager: SessionManager;
|
|
186
|
+
modelRegistry: ModelRegistry;
|
|
187
|
+
}
|
|
188
|
+
// Note: exec moved to HookAPI, sessionManager/modelRegistry moved from SessionEventBase
|
|
189
|
+
|
|
190
|
+
// HookCommandContext - passed to command handlers:
|
|
191
|
+
interface HookCommandContext {
|
|
192
|
+
args: string; // Everything after /commandname
|
|
193
|
+
ui: HookUIContext;
|
|
194
|
+
hasUI: boolean;
|
|
195
|
+
cwd: string;
|
|
196
|
+
sessionManager: SessionManager;
|
|
197
|
+
modelRegistry: ModelRegistry;
|
|
198
|
+
}
|
|
199
|
+
// Note: exec and sendMessage accessed via `pi` closure
|
|
200
|
+
|
|
201
|
+
registerCommand(name: string, options: {
|
|
202
|
+
description?: string;
|
|
203
|
+
handler: (ctx: HookCommandContext) => Promise<void>;
|
|
204
|
+
}): void;
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Handler return:
|
|
208
|
+
- `void` - command completed (use `sendMessage()` with `triggerTurn: true` to prompt LLM)
|
|
209
|
+
|
|
210
|
+
Wiring (all in AgentSession.prompt()):
|
|
211
|
+
- [x] Add hook commands to autocomplete in interactive-mode
|
|
212
|
+
- [x] `_tryExecuteHookCommand()` in AgentSession handles command execution
|
|
213
|
+
- [x] Build HookCommandContext with ui (from hookRunner), exec, sessionManager, etc.
|
|
214
|
+
- [x] If handler returns string, use as prompt text
|
|
215
|
+
- [x] If handler returns undefined, return early (no LLM call)
|
|
216
|
+
- [x] Works for all modes (interactive, RPC, print) via shared AgentSession
|
|
217
|
+
|
|
218
|
+
**New: `ui.custom()` ✅**
|
|
219
|
+
|
|
220
|
+
For arbitrary hook UI with keyboard focus:
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
interface HookUIContext {
|
|
224
|
+
// ... existing: select, confirm, input, notify
|
|
225
|
+
|
|
226
|
+
/** Show custom component with keyboard focus. Call done() when finished. */
|
|
227
|
+
custom(component: Component, done: () => void): void;
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
See also: `CustomEntry<T>` for storing hook state that does NOT participate in context.
|
|
232
|
+
|
|
233
|
+
**New: `context` event ✅**
|
|
234
|
+
|
|
235
|
+
Fires before messages are sent to the LLM, allowing hooks to modify context non-destructively.
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
interface ContextEvent {
|
|
239
|
+
type: "context";
|
|
240
|
+
/** Messages that will be sent to the LLM */
|
|
241
|
+
messages: Message[];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
interface ContextEventResult {
|
|
245
|
+
/** Modified messages to send instead */
|
|
246
|
+
messages?: Message[];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// In HookAPI:
|
|
250
|
+
on(event: "context", handler: HookHandler<ContextEvent, ContextEventResult | void>): void;
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Example use case: **Dynamic Context Pruning** ([discussion #330](https://github.com/badlogic/pi-mono/discussions/330))
|
|
254
|
+
|
|
255
|
+
Non-destructive pruning of tool results to reduce context size:
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
export default function(pi: HookAPI) {
|
|
259
|
+
// Register /prune command
|
|
260
|
+
pi.registerCommand("prune", {
|
|
261
|
+
description: "Mark tool results for pruning",
|
|
262
|
+
handler: async (ctx) => {
|
|
263
|
+
// Show UI to select which tool results to prune
|
|
264
|
+
// Append custom entry recording pruning decisions:
|
|
265
|
+
// { toolResultId, strategy: "summary" | "truncate" | "remove" }
|
|
266
|
+
pi.appendEntry("tool-result-pruning", { ... });
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// Intercept context before LLM call
|
|
271
|
+
pi.on("context", async (event, ctx) => {
|
|
272
|
+
// Find all pruning entries in session
|
|
273
|
+
const entries = ctx.sessionManager.getEntries();
|
|
274
|
+
const pruningRules = entries
|
|
275
|
+
.filter(e => e.type === "custom" && e.customType === "tool-result-pruning")
|
|
276
|
+
.map(e => e.data);
|
|
277
|
+
|
|
278
|
+
// Apply pruning rules to messages
|
|
279
|
+
const prunedMessages = applyPruning(event.messages, pruningRules);
|
|
280
|
+
return { messages: prunedMessages };
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Benefits:
|
|
286
|
+
- Original tool results stay intact in session
|
|
287
|
+
- Pruning is stored as custom entries, survives session reload
|
|
288
|
+
- Works with branching (pruning entries are part of the tree)
|
|
289
|
+
- Trade-off: cache busting on first submission after pruning
|
|
290
|
+
|
|
291
|
+
### Investigate: `context` event vs `before_agent_start` ✅
|
|
292
|
+
|
|
293
|
+
References:
|
|
294
|
+
- [#324](https://github.com/badlogic/pi-mono/issues/324) - `before_agent_start` proposal
|
|
295
|
+
- [#330](https://github.com/badlogic/pi-mono/discussions/330) - Dynamic Context Pruning (why `context` was added)
|
|
296
|
+
|
|
297
|
+
**Current `context` event:**
|
|
298
|
+
- Fires before each LLM call within the agent loop
|
|
299
|
+
- Receives `AgentMessage[]` (deep copy, safe to modify)
|
|
300
|
+
- Returns `Message[]` (inconsistent with input type)
|
|
301
|
+
- Modifications are transient (not persisted to session)
|
|
302
|
+
- No TUI visibility of what was changed
|
|
303
|
+
- Use case: non-destructive pruning, dynamic context manipulation
|
|
304
|
+
|
|
305
|
+
**Type inconsistency:** Event receives `AgentMessage[]` but result returns `Message[]`:
|
|
306
|
+
```typescript
|
|
307
|
+
interface ContextEvent {
|
|
308
|
+
messages: AgentMessage[]; // Input
|
|
309
|
+
}
|
|
310
|
+
interface ContextEventResult {
|
|
311
|
+
messages?: Message[]; // Output - different type!
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Questions:
|
|
316
|
+
- [ ] Should input/output both be `Message[]` (LLM format)?
|
|
317
|
+
- [ ] Or both be `AgentMessage[]` with conversion happening after?
|
|
318
|
+
- [ ] Where does `AgentMessage[]` → `Message[]` conversion currently happen?
|
|
319
|
+
|
|
320
|
+
**Proposed `before_agent_start` event:**
|
|
321
|
+
- Fires once when user submits a prompt, before `agent_start`
|
|
322
|
+
- Allows hooks to inject additional content that gets **persisted** to session
|
|
323
|
+
- Injected content is visible in TUI (observability)
|
|
324
|
+
- Does not bust prompt cache (appended after user message, not modifying system prompt)
|
|
325
|
+
|
|
326
|
+
**Key difference:**
|
|
327
|
+
| Aspect | `context` | `before_agent_start` |
|
|
328
|
+
|--------|-----------|---------------------|
|
|
329
|
+
| When | Before each LLM call | Once per user prompt |
|
|
330
|
+
| Persisted | No | Yes (as SystemMessage) |
|
|
331
|
+
| TUI visible | No | Yes (collapsible) |
|
|
332
|
+
| Cache impact | Can bust cache | Append-only, cache-safe |
|
|
333
|
+
| Use case | Transient manipulation | Persistent context injection |
|
|
334
|
+
|
|
335
|
+
**Implementation (completed):**
|
|
336
|
+
- Reuses `HookMessage` type (no new message type needed)
|
|
337
|
+
- Handler returns `{ message: Pick<HookMessage, "customType" | "content" | "display" | "details"> }`
|
|
338
|
+
- Message is appended to agent state AND persisted to session before `agent.prompt()` is called
|
|
339
|
+
- Renders using existing `HookMessageComponent` (or custom renderer if registered)
|
|
340
|
+
- [ ] How does it interact with compaction? (treated like user messages?)
|
|
341
|
+
- [ ] Can hook return multiple messages or just one?
|
|
342
|
+
|
|
343
|
+
**Implementation sketch:**
|
|
344
|
+
```typescript
|
|
345
|
+
interface BeforeAgentStartEvent {
|
|
346
|
+
type: "before_agent_start";
|
|
347
|
+
userMessage: UserMessage; // The prompt user just submitted
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
interface BeforeAgentStartResult {
|
|
351
|
+
/** Additional context to inject (persisted as SystemMessage) */
|
|
352
|
+
inject?: {
|
|
353
|
+
label: string; // Shown in collapsed TUI state
|
|
354
|
+
content: string | (TextContent | ImageContent)[];
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### HTML Export
|
|
360
|
+
|
|
361
|
+
- [ ] Add collapsible sidebar showing full tree structure
|
|
362
|
+
- [ ] Allow selecting any node in tree to view that path
|
|
363
|
+
- [ ] Add "reset to session leaf" button
|
|
364
|
+
- [ ] Render full path (no compaction resolution needed)
|
|
365
|
+
- [ ] Responsive: collapse sidebar on mobile
|
|
366
|
+
|
|
367
|
+
### UI Commands ✅
|
|
368
|
+
|
|
369
|
+
- [x] `/branch` - Creates new session file from current path (uses `createBranchedSession()`)
|
|
370
|
+
- [x] `/tree` - In-session tree navigation via tree-selector component
|
|
371
|
+
- Shows full tree structure with labels
|
|
372
|
+
- Navigate between branches (moves leaf pointer)
|
|
373
|
+
- Shows current position
|
|
374
|
+
- Generates branch summaries when switching branches
|
|
375
|
+
|
|
376
|
+
### Tree Selector Improvements ✅
|
|
377
|
+
|
|
378
|
+
- [x] Active line highlight using `selectedBg` theme color
|
|
379
|
+
- [x] Filter modes via `^O` (forward) / `Shift+^O` (backward):
|
|
380
|
+
- `default`: hides label/custom entries
|
|
381
|
+
- `no-tools`: default minus tool results
|
|
382
|
+
- `user-only`: just user messages
|
|
383
|
+
- `labeled-only`: just labeled entries
|
|
384
|
+
- `all`: everything
|
|
385
|
+
|
|
386
|
+
### Documentation
|
|
387
|
+
|
|
388
|
+
Review and update all docs:
|
|
389
|
+
|
|
390
|
+
- [ ] `docs/hooks.md` - Major update for hook API:
|
|
391
|
+
- `pi.send()` → `pi.sendMessage()` with new signature
|
|
392
|
+
- New `pi.appendEntry()` for state persistence
|
|
393
|
+
- New `pi.registerCommand()` for custom slash commands
|
|
394
|
+
- New `pi.registerCustomMessageRenderer()` for custom TUI rendering
|
|
395
|
+
- `HookCommandContext` interface and handler patterns
|
|
396
|
+
- `HookMessage<T>` type
|
|
397
|
+
- Updated event signatures (`SessionEventBase`, `before_compact`, etc.)
|
|
398
|
+
- [ ] `docs/hooks-v2.md` - Review/merge or remove if obsolete
|
|
399
|
+
- [ ] `docs/sdk.md` - Update for:
|
|
400
|
+
- `HookMessage` and `isHookMessage()`
|
|
401
|
+
- `Agent.prompt(AppMessage)` overload
|
|
402
|
+
- Session v2 tree structure
|
|
403
|
+
- SessionManager API changes
|
|
404
|
+
- [ ] `docs/session.md` - Update for v2 tree structure, new entry types
|
|
405
|
+
- [ ] `docs/custom-tools.md` - Check if hook changes affect custom tools
|
|
406
|
+
- [ ] `docs/rpc.md` - Check if hook commands work in RPC mode
|
|
407
|
+
- [ ] `docs/skills.md` - Review for any hook-related updates
|
|
408
|
+
- [ ] `docs/extension-loading.md` - Review
|
|
409
|
+
- [x] `docs/theme.md` - Added selectedBg, customMessageBg/Text/Label color tokens (50 total)
|
|
410
|
+
- [ ] `README.md` - Update hook examples if any
|
|
411
|
+
|
|
412
|
+
### Examples
|
|
413
|
+
|
|
414
|
+
Review and update examples:
|
|
415
|
+
|
|
416
|
+
- [ ] `examples/hooks/` - Update existing, add new examples:
|
|
417
|
+
- [ ] Review `custom-compaction.ts` for new API
|
|
418
|
+
- [ ] Add `registerCommand()` example
|
|
419
|
+
- [ ] Add `sendMessage()` example
|
|
420
|
+
- [ ] Add `registerCustomMessageRenderer()` example
|
|
421
|
+
- [ ] `examples/sdk/` - Update for new session/hook APIs
|
|
422
|
+
- [ ] `examples/custom-tools/` - Review for compatibility
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
## Before Release
|
|
427
|
+
|
|
428
|
+
- [ ] Run full automated test suite: `npm test`
|
|
429
|
+
- [ ] Manual testing of tree navigation and branch summarization
|
|
430
|
+
- [ ] Verify compaction with file tracking works correctly
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
## Notes
|
|
435
|
+
|
|
436
|
+
- All append methods return the new entry's ID
|
|
437
|
+
- Migration rewrites file on first load if version < CURRENT_VERSION
|
|
438
|
+
- Existing sessions become linear chains after migration (parentId = previous entry)
|
|
439
|
+
- Tree features available immediately after migration
|
|
440
|
+
- SessionHeader does NOT have id/parentId (it's metadata, not part of tree)
|
|
441
|
+
- Session is append-only: entries cannot be modified or deleted, only branching changes the leaf pointer
|
package/docs/session.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# Session File Format
|
|
2
|
+
|
|
3
|
+
Sessions are stored as JSONL (JSON Lines) files. Each line is a JSON object with a `type` field. Session entries form a tree structure via `id`/`parentId` fields, enabling in-place branching without creating new files.
|
|
4
|
+
|
|
5
|
+
## File Location
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
~/.pi/agent/sessions/--<path>--/<timestamp>_<uuid>.jsonl
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Where `<path>` is the working directory with `/` replaced by `-`.
|
|
12
|
+
|
|
13
|
+
## Session Version
|
|
14
|
+
|
|
15
|
+
Sessions have a version field in the header:
|
|
16
|
+
|
|
17
|
+
- **Version 1**: Linear entry sequence (legacy, auto-migrated on load)
|
|
18
|
+
- **Version 2**: Tree structure with `id`/`parentId` linking
|
|
19
|
+
|
|
20
|
+
Existing v1 sessions are automatically migrated to v2 when loaded.
|
|
21
|
+
|
|
22
|
+
## Type Definitions
|
|
23
|
+
|
|
24
|
+
- [`src/core/session-manager.ts`](../src/core/session-manager.ts) - Session entry types
|
|
25
|
+
- [`packages/agent/src/types.ts`](../../agent/src/types.ts) - `AgentMessage`, `Attachment`, `ThinkingLevel`
|
|
26
|
+
- [`packages/ai/src/types.ts`](../../ai/src/types.ts) - `UserMessage`, `AssistantMessage`, `ToolResultMessage`, `Usage`, `ToolCall`
|
|
27
|
+
|
|
28
|
+
## Entry Base
|
|
29
|
+
|
|
30
|
+
All entries (except `SessionHeader`) extend `SessionEntryBase`:
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
interface SessionEntryBase {
|
|
34
|
+
type: string;
|
|
35
|
+
id: string; // 8-char hex ID
|
|
36
|
+
parentId: string | null; // Parent entry ID (null for first entry)
|
|
37
|
+
timestamp: string; // ISO timestamp
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Entry Types
|
|
42
|
+
|
|
43
|
+
### SessionHeader
|
|
44
|
+
|
|
45
|
+
First line of the file. Metadata only, not part of the tree (no `id`/`parentId`).
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{"type":"session","version":2,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project"}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
For sessions with a parent (created via `/branch` or `newSession({ parentSession })`):
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{"type":"session","version":2,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project","parentSession":"/path/to/original/session.jsonl"}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### SessionMessageEntry
|
|
58
|
+
|
|
59
|
+
A message in the conversation. The `message` field contains an `AgentMessage`.
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{"type":"message","id":"a1b2c3d4","parentId":"prev1234","timestamp":"2024-12-03T14:00:01.000Z","message":{"role":"user","content":"Hello"}}
|
|
63
|
+
{"type":"message","id":"b2c3d4e5","parentId":"a1b2c3d4","timestamp":"2024-12-03T14:00:02.000Z","message":{"role":"assistant","content":[{"type":"text","text":"Hi!"}],"provider":"anthropic","model":"claude-sonnet-4-5","usage":{...},"stopReason":"stop"}}
|
|
64
|
+
{"type":"message","id":"c3d4e5f6","parentId":"b2c3d4e5","timestamp":"2024-12-03T14:00:03.000Z","message":{"role":"toolResult","toolCallId":"call_123","toolName":"bash","content":[{"type":"text","text":"output"}],"isError":false}}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### ModelChangeEntry
|
|
68
|
+
|
|
69
|
+
Emitted when the user switches models mid-session.
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{"type":"model_change","id":"d4e5f6g7","parentId":"c3d4e5f6","timestamp":"2024-12-03T14:05:00.000Z","provider":"openai","modelId":"gpt-4o"}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### ThinkingLevelChangeEntry
|
|
76
|
+
|
|
77
|
+
Emitted when the user changes the thinking/reasoning level.
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{"type":"thinking_level_change","id":"e5f6g7h8","parentId":"d4e5f6g7","timestamp":"2024-12-03T14:06:00.000Z","thinkingLevel":"high"}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### CompactionEntry
|
|
84
|
+
|
|
85
|
+
Created when context is compacted. Stores a summary of earlier messages.
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{"type":"compaction","id":"f6g7h8i9","parentId":"e5f6g7h8","timestamp":"2024-12-03T14:10:00.000Z","summary":"User discussed X, Y, Z...","firstKeptEntryId":"c3d4e5f6","tokensBefore":50000}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Optional fields:
|
|
92
|
+
- `details`: Compaction-implementation specific data (e.g., file operations for default implementation, or custom data for custom hook implementations)
|
|
93
|
+
- `fromHook`: `true` if generated by a hook, `false`/`undefined` if pi-generated
|
|
94
|
+
|
|
95
|
+
### BranchSummaryEntry
|
|
96
|
+
|
|
97
|
+
Created when switching branches via `/tree` with an LLM generated summary of the left branch up to the common ancestor. Captures context from the abandoned path.
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{"type":"branch_summary","id":"g7h8i9j0","parentId":"a1b2c3d4","timestamp":"2024-12-03T14:15:00.000Z","fromId":"f6g7h8i9","summary":"Branch explored approach A..."}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Optional fields:
|
|
104
|
+
- `details`: File tracking data (`{ readFiles: string[], modifiedFiles: string[] }`) for default implementation, arbitrary for custom implementation
|
|
105
|
+
- `fromHook`: `true` if generated by a hook
|
|
106
|
+
|
|
107
|
+
### CustomEntry
|
|
108
|
+
|
|
109
|
+
Hook state persistence. Does NOT participate in LLM context.
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{"type":"custom","id":"h8i9j0k1","parentId":"g7h8i9j0","timestamp":"2024-12-03T14:20:00.000Z","customType":"my-hook","data":{"count":42}}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Use `customType` to identify your hook's entries on reload.
|
|
116
|
+
|
|
117
|
+
### CustomMessageEntry
|
|
118
|
+
|
|
119
|
+
Hook-injected messages that DO participate in LLM context.
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{"type":"custom_message","id":"i9j0k1l2","parentId":"h8i9j0k1","timestamp":"2024-12-03T14:25:00.000Z","customType":"my-hook","content":"Injected context...","display":true}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Fields:
|
|
126
|
+
- `content`: String or `(TextContent | ImageContent)[]` (same as UserMessage)
|
|
127
|
+
- `display`: `true` = show in TUI with purple styling, `false` = hidden
|
|
128
|
+
- `details`: Optional hook-specific metadata (not sent to LLM)
|
|
129
|
+
|
|
130
|
+
### LabelEntry
|
|
131
|
+
|
|
132
|
+
User-defined bookmark/marker on an entry.
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{"type":"label","id":"j0k1l2m3","parentId":"i9j0k1l2","timestamp":"2024-12-03T14:30:00.000Z","targetId":"a1b2c3d4","label":"checkpoint-1"}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Set `label` to `undefined` to clear a label.
|
|
139
|
+
|
|
140
|
+
## Tree Structure
|
|
141
|
+
|
|
142
|
+
Entries form a tree:
|
|
143
|
+
- First entry has `parentId: null`
|
|
144
|
+
- Each subsequent entry points to its parent via `parentId`
|
|
145
|
+
- Branching creates new children from an earlier entry
|
|
146
|
+
- The "leaf" is the current position in the tree
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
[user msg] ─── [assistant] ─── [user msg] ─── [assistant] ─┬─ [user msg] ← current leaf
|
|
150
|
+
│
|
|
151
|
+
└─ [branch_summary] ─── [user msg] ← alternate branch
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Context Building
|
|
155
|
+
|
|
156
|
+
`buildSessionContext()` walks from the current leaf to the root, producing the message list for the LLM:
|
|
157
|
+
|
|
158
|
+
1. Collects all entries on the path
|
|
159
|
+
2. Extracts current model and thinking level settings
|
|
160
|
+
3. If a `CompactionEntry` is on the path:
|
|
161
|
+
- Emits the summary first
|
|
162
|
+
- Then messages from `firstKeptEntryId` to compaction
|
|
163
|
+
- Then messages after compaction
|
|
164
|
+
4. Converts `BranchSummaryEntry` and `CustomMessageEntry` to appropriate message formats
|
|
165
|
+
|
|
166
|
+
## Parsing Example
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
import { readFileSync } from "fs";
|
|
170
|
+
|
|
171
|
+
const lines = readFileSync("session.jsonl", "utf8").trim().split("\n");
|
|
172
|
+
|
|
173
|
+
for (const line of lines) {
|
|
174
|
+
const entry = JSON.parse(line);
|
|
175
|
+
|
|
176
|
+
switch (entry.type) {
|
|
177
|
+
case "session":
|
|
178
|
+
console.log(`Session v${entry.version ?? 1}: ${entry.id}`);
|
|
179
|
+
break;
|
|
180
|
+
case "message":
|
|
181
|
+
console.log(`[${entry.id}] ${entry.message.role}: ${JSON.stringify(entry.message.content)}`);
|
|
182
|
+
break;
|
|
183
|
+
case "compaction":
|
|
184
|
+
console.log(`[${entry.id}] Compaction: ${entry.tokensBefore} tokens summarized`);
|
|
185
|
+
break;
|
|
186
|
+
case "branch_summary":
|
|
187
|
+
console.log(`[${entry.id}] Branch from ${entry.fromId}`);
|
|
188
|
+
break;
|
|
189
|
+
case "custom":
|
|
190
|
+
console.log(`[${entry.id}] Custom (${entry.customType}): ${JSON.stringify(entry.data)}`);
|
|
191
|
+
break;
|
|
192
|
+
case "custom_message":
|
|
193
|
+
console.log(`[${entry.id}] Hook message (${entry.customType}): ${entry.content}`);
|
|
194
|
+
break;
|
|
195
|
+
case "label":
|
|
196
|
+
console.log(`[${entry.id}] Label "${entry.label}" on ${entry.targetId}`);
|
|
197
|
+
break;
|
|
198
|
+
case "model_change":
|
|
199
|
+
console.log(`[${entry.id}] Model: ${entry.provider}/${entry.modelId}`);
|
|
200
|
+
break;
|
|
201
|
+
case "thinking_level_change":
|
|
202
|
+
console.log(`[${entry.id}] Thinking: ${entry.thinkingLevel}`);
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## SessionManager API
|
|
209
|
+
|
|
210
|
+
Key methods for working with sessions programmatically:
|
|
211
|
+
|
|
212
|
+
### Creation
|
|
213
|
+
- `SessionManager.create(cwd, sessionDir?)` - New session
|
|
214
|
+
- `SessionManager.open(path, sessionDir?)` - Open existing
|
|
215
|
+
- `SessionManager.continueRecent(cwd, sessionDir?)` - Continue most recent or create new
|
|
216
|
+
- `SessionManager.inMemory(cwd?)` - No file persistence
|
|
217
|
+
|
|
218
|
+
### Appending (all return entry ID)
|
|
219
|
+
- `appendMessage(message)` - Add message
|
|
220
|
+
- `appendThinkingLevelChange(level)` - Record thinking change
|
|
221
|
+
- `appendModelChange(provider, modelId)` - Record model change
|
|
222
|
+
- `appendCompaction(summary, firstKeptEntryId, tokensBefore, details?, fromHook?)` - Add compaction
|
|
223
|
+
- `appendCustomEntry(customType, data?)` - Hook state (not in context)
|
|
224
|
+
- `appendCustomMessageEntry(customType, content, display, details?)` - Hook message (in context)
|
|
225
|
+
- `appendLabelChange(targetId, label)` - Set/clear label
|
|
226
|
+
|
|
227
|
+
### Tree Navigation
|
|
228
|
+
- `getLeafId()` - Current position
|
|
229
|
+
- `getEntry(id)` - Get entry by ID
|
|
230
|
+
- `getPath(fromId?)` - Walk from entry to root
|
|
231
|
+
- `getTree()` - Get full tree structure
|
|
232
|
+
- `getChildren(parentId)` - Get direct children
|
|
233
|
+
- `getLabel(id)` - Get label for entry
|
|
234
|
+
- `branch(entryId)` - Move leaf to earlier entry
|
|
235
|
+
- `branchWithSummary(entryId, summary, details?, fromHook?)` - Branch with context summary
|
|
236
|
+
|
|
237
|
+
### Context
|
|
238
|
+
- `buildSessionContext()` - Get messages for LLM
|
|
239
|
+
- `getEntries()` - All entries (excluding header)
|
|
240
|
+
- `getHeader()` - Session metadata
|