@mariozechner/pi-coding-agent 0.30.2 → 0.31.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 +244 -1
- package/README.md +105 -84
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +5 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/file-processor.d.ts +3 -3
- package/dist/cli/file-processor.d.ts.map +1 -1
- package/dist/cli/file-processor.js +7 -10
- package/dist/cli/file-processor.js.map +1 -1
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +18 -0
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +73 -34
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +464 -210
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +2 -2
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +2 -2
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/bash-executor.d.ts +2 -2
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +2 -2
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +84 -0
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -0
- package/dist/core/compaction/branch-summarization.js +233 -0
- package/dist/core/compaction/branch-summarization.js.map +1 -0
- package/dist/core/{compaction.d.ts → compaction/compaction.d.ts} +38 -19
- package/dist/core/compaction/compaction.d.ts.map +1 -0
- package/dist/core/compaction/compaction.js +558 -0
- package/dist/core/compaction/compaction.js.map +1 -0
- package/dist/core/compaction/index.d.ts +7 -0
- package/dist/core/compaction/index.d.ts.map +1 -0
- package/dist/core/compaction/index.js +7 -0
- package/dist/core/compaction/index.js.map +1 -0
- package/dist/core/compaction/utils.d.ts +35 -0
- package/dist/core/compaction/utils.d.ts.map +1 -0
- package/dist/core/compaction/utils.js +138 -0
- package/dist/core/compaction/utils.js.map +1 -0
- package/dist/core/custom-tools/index.d.ts +2 -1
- package/dist/core/custom-tools/index.d.ts.map +1 -1
- package/dist/core/custom-tools/index.js +1 -0
- package/dist/core/custom-tools/index.js.map +1 -1
- package/dist/core/custom-tools/loader.d.ts.map +1 -1
- package/dist/core/custom-tools/loader.js +13 -80
- package/dist/core/custom-tools/loader.js.map +1 -1
- package/dist/core/custom-tools/types.d.ts +84 -59
- package/dist/core/custom-tools/types.d.ts.map +1 -1
- package/dist/core/custom-tools/types.js.map +1 -1
- package/dist/core/custom-tools/wrapper.d.ts +15 -0
- package/dist/core/custom-tools/wrapper.d.ts.map +1 -0
- package/dist/core/custom-tools/wrapper.js +23 -0
- package/dist/core/custom-tools/wrapper.js.map +1 -0
- package/dist/core/exec.d.ts +29 -0
- package/dist/core/exec.d.ts.map +1 -0
- package/dist/core/exec.js +71 -0
- package/dist/core/exec.js.map +1 -0
- package/dist/core/export-html/index.d.ts +17 -0
- package/dist/core/export-html/index.d.ts.map +1 -0
- package/dist/core/export-html/index.js +171 -0
- package/dist/core/export-html/index.js.map +1 -0
- package/dist/core/export-html/template.css +781 -0
- package/dist/core/export-html/template.html +54 -0
- package/dist/core/export-html/template.js +1185 -0
- package/dist/core/export-html/vendor/highlight.min.js +1213 -0
- package/dist/core/export-html/vendor/marked.min.js +6 -0
- package/dist/core/hooks/index.d.ts +4 -4
- package/dist/core/hooks/index.d.ts.map +1 -1
- package/dist/core/hooks/index.js +3 -3
- package/dist/core/hooks/index.js.map +1 -1
- package/dist/core/hooks/loader.d.ts +40 -5
- package/dist/core/hooks/loader.d.ts.map +1 -1
- package/dist/core/hooks/loader.js +43 -10
- package/dist/core/hooks/loader.js.map +1 -1
- package/dist/core/hooks/runner.d.ts +94 -18
- package/dist/core/hooks/runner.d.ts.map +1 -1
- package/dist/core/hooks/runner.js +199 -120
- package/dist/core/hooks/runner.js.map +1 -1
- package/dist/core/hooks/tool-wrapper.d.ts +1 -1
- package/dist/core/hooks/tool-wrapper.d.ts.map +1 -1
- package/dist/core/hooks/tool-wrapper.js +36 -19
- package/dist/core/hooks/tool-wrapper.js.map +1 -1
- package/dist/core/hooks/types.d.ts +407 -96
- package/dist/core/hooks/types.d.ts.map +1 -1
- package/dist/core/hooks/types.js.map +1 -1
- package/dist/core/index.d.ts +4 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/messages.d.ts +44 -12
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +82 -34
- package/dist/core/messages.js.map +1 -1
- package/dist/core/model-registry.d.ts +5 -5
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +7 -7
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts +7 -7
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +45 -14
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/sdk.d.ts +7 -10
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +88 -32
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +202 -36
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +565 -133
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +9 -3
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +13 -12
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +6 -3
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts +33 -0
- package/dist/core/tools/edit-diff.d.ts.map +1 -0
- package/dist/core/tools/edit-diff.js +171 -0
- package/dist/core/tools/edit-diff.js.map +1 -0
- package/dist/core/tools/edit.d.ts +7 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +20 -95
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/read.d.ts +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/write.d.ts +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js.map +1 -1
- package/dist/index.d.ts +8 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +22 -21
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +3 -4
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +6 -2
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.d.ts +12 -0
- package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -0
- package/dist/modes/interactive/components/bordered-loader.js +30 -0
- package/dist/modes/interactive/components/bordered-loader.js.map +1 -0
- package/dist/modes/interactive/components/branch-summary-message.d.ts +14 -0
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/branch-summary-message.js +35 -0
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -0
- package/dist/modes/interactive/components/compaction-summary-message.d.ts +14 -0
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/compaction-summary-message.js +36 -0
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -0
- package/dist/modes/interactive/components/dynamic-border.d.ts +5 -1
- package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
- package/dist/modes/interactive/components/dynamic-border.js +5 -1
- package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +12 -6
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +57 -25
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/hook-editor.d.ts +15 -0
- package/dist/modes/interactive/components/hook-editor.d.ts.map +1 -0
- package/dist/modes/interactive/components/hook-editor.js +95 -0
- package/dist/modes/interactive/components/hook-editor.js.map +1 -0
- package/dist/modes/interactive/components/hook-message.d.ts +18 -0
- package/dist/modes/interactive/components/hook-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/hook-message.js +80 -0
- package/dist/modes/interactive/components/hook-message.js.map +1 -0
- package/dist/modes/interactive/components/model-selector.d.ts +3 -3
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +1 -1
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +15 -2
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +70 -21
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts +52 -0
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/tree-selector.js +745 -0
- package/dist/modes/interactive/components/tree-selector.js.map +1 -0
- package/dist/modes/interactive/components/user-message-selector.d.ts +3 -3
- package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.js +1 -1
- package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts +1 -1
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +2 -5
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +29 -12
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +589 -208
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/dark.json +13 -1
- package/dist/modes/interactive/theme/light.json +13 -1
- package/dist/modes/interactive/theme/theme-schema.json +34 -0
- package/dist/modes/interactive/theme/theme.d.ts +20 -2
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +135 -2
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/print-mode.d.ts +3 -3
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +26 -20
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +13 -10
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +11 -10
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +88 -35
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +30 -11
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/utils/shell.d.ts +4 -2
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +36 -7
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/tools-manager.d.ts +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +2 -2
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/compaction.md +388 -0
- package/docs/custom-tools.md +146 -43
- package/docs/extension-loading.md +1004 -0
- package/docs/hooks.md +562 -596
- package/docs/rpc.md +33 -19
- package/docs/sdk.md +93 -21
- package/docs/session-tree-plan.md +441 -0
- package/docs/session.md +172 -21
- package/docs/skills.md +2 -0
- package/docs/theme.md +31 -2
- package/docs/tree.md +197 -0
- package/docs/tui.md +343 -0
- package/examples/README.md +1 -9
- package/examples/custom-tools/hello/index.ts +4 -3
- package/examples/custom-tools/question/index.ts +4 -4
- package/examples/custom-tools/subagent/index.ts +7 -6
- package/examples/custom-tools/todo/index.ts +11 -5
- package/examples/hooks/README.md +29 -71
- package/examples/hooks/auto-commit-on-exit.ts +8 -9
- package/examples/hooks/confirm-destructive.ts +29 -30
- package/examples/hooks/custom-compaction.ts +20 -21
- package/examples/hooks/dirty-repo-guard.ts +41 -40
- package/examples/hooks/file-trigger.ts +10 -5
- package/examples/hooks/git-checkpoint.ts +16 -12
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +1 -1
- package/examples/hooks/protected-paths.ts +1 -1
- 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 +1 -1
- package/examples/sdk/02-custom-model.ts +1 -1
- package/examples/sdk/03-custom-prompt.ts +1 -1
- package/examples/sdk/04-skills.ts +1 -1
- package/examples/sdk/05-tools.ts +4 -4
- package/examples/sdk/06-hooks.ts +1 -1
- package/examples/sdk/07-context-files.ts +1 -1
- package/examples/sdk/08-slash-commands.ts +6 -1
- package/examples/sdk/09-api-keys-and-oauth.ts +1 -1
- package/examples/sdk/10-settings.ts +1 -1
- package/examples/sdk/11-sessions.ts +1 -1
- package/examples/sdk/12-full-control.ts +4 -7
- package/package.json +6 -6
- package/dist/core/compaction.d.ts.map +0 -1
- package/dist/core/compaction.js +0 -412
- package/dist/core/compaction.js.map +0 -1
- package/dist/core/export-html.d.ts +0 -23
- package/dist/core/export-html.d.ts.map +0 -1
- package/dist/core/export-html.js +0 -1185
- package/dist/core/export-html.js.map +0 -1
- package/dist/modes/interactive/components/compaction.d.ts +0 -15
- package/dist/modes/interactive/components/compaction.d.ts.map +0 -1
- package/dist/modes/interactive/components/compaction.js +0 -41
- package/dist/modes/interactive/components/compaction.js.map +0 -1
- package/docs/hooks-v2.md +0 -385
- package/docs/session-tree.md +0 -452
package/docs/session-tree.md
DELETED
|
@@ -1,452 +0,0 @@
|
|
|
1
|
-
# Session Tree Format
|
|
2
|
-
|
|
3
|
-
Analysis of switching from linear JSONL to tree-based session storage.
|
|
4
|
-
|
|
5
|
-
## Current Format (Linear)
|
|
6
|
-
|
|
7
|
-
```jsonl
|
|
8
|
-
{"type":"session","id":"...","timestamp":"...","cwd":"..."}
|
|
9
|
-
{"type":"message","timestamp":"...","message":{"role":"user",...}}
|
|
10
|
-
{"type":"message","timestamp":"...","message":{"role":"assistant",...}}
|
|
11
|
-
{"type":"compaction","timestamp":"...","summary":"...","firstKeptEntryIndex":2,"tokensBefore":50000}
|
|
12
|
-
{"type":"message","timestamp":"...","message":{"role":"user",...}}
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Context is built by scanning linearly, applying compaction ranges.
|
|
16
|
-
|
|
17
|
-
## Proposed Format (Tree)
|
|
18
|
-
|
|
19
|
-
Each entry has a `uuid` and `parentUuid` field (null for root). Session header includes `version` for future migrations:
|
|
20
|
-
|
|
21
|
-
```jsonl
|
|
22
|
-
{"type":"session","version":2,"uuid":"a1b2c3","parentUuid":null,"id":"...","cwd":"..."}
|
|
23
|
-
{"type":"message","uuid":"d4e5f6","parentUuid":"a1b2c3","message":{"role":"user",...}}
|
|
24
|
-
{"type":"message","uuid":"g7h8i9","parentUuid":"d4e5f6","message":{"role":"assistant",...}}
|
|
25
|
-
{"type":"message","uuid":"j0k1l2","parentUuid":"g7h8i9","message":{"role":"user",...}}
|
|
26
|
-
{"type":"message","uuid":"m3n4o5","parentUuid":"j0k1l2","message":{"role":"assistant",...}}
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
Version history:
|
|
30
|
-
- **v1** (implicit): Linear format, no uuid/parentUuid
|
|
31
|
-
- **v2**: Tree format with uuid/parentUuid
|
|
32
|
-
|
|
33
|
-
The **last entry** is always the current leaf. Context = walk from leaf to root via `parentUuid`.
|
|
34
|
-
|
|
35
|
-
Using UUIDs (like Claude Code does) instead of indices because:
|
|
36
|
-
- No remapping needed when branching to new file
|
|
37
|
-
- Robust to entry deletion/reordering
|
|
38
|
-
- Orphan references are detectable
|
|
39
|
-
- ~30 extra bytes per entry is negligible for text-heavy sessions
|
|
40
|
-
|
|
41
|
-
### Branching
|
|
42
|
-
|
|
43
|
-
Branch from entry `g7h8i9` (after first assistant response):
|
|
44
|
-
|
|
45
|
-
```jsonl
|
|
46
|
-
... entries unchanged ...
|
|
47
|
-
{"type":"message","uuid":"p6q7r8","parentUuid":"g7h8i9","message":{"role":"user",...}}
|
|
48
|
-
{"type":"message","uuid":"s9t0u1","parentUuid":"p6q7r8","message":{"role":"assistant",...}}
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
Walking s9t0u1→p6q7r8→g7h8i9→d4e5f6→a1b2c3 gives the branched context.
|
|
52
|
-
|
|
53
|
-
The old path (j0k1l2, m3n4o5) remains in the file but is not in the current context.
|
|
54
|
-
|
|
55
|
-
### Visual
|
|
56
|
-
|
|
57
|
-
```
|
|
58
|
-
[a1b2:session]
|
|
59
|
-
│
|
|
60
|
-
[d4e5:user "hello"]
|
|
61
|
-
│
|
|
62
|
-
[g7h8:assistant "hi"]
|
|
63
|
-
│
|
|
64
|
-
┌────┴────┐
|
|
65
|
-
│ │
|
|
66
|
-
[j0k1:user A] [p6q7:user B] ← branch point
|
|
67
|
-
│ │
|
|
68
|
-
[m3n4:asst A] [s9t0:asst B] ← current leaf
|
|
69
|
-
│
|
|
70
|
-
(old path)
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## Context Building
|
|
74
|
-
|
|
75
|
-
```typescript
|
|
76
|
-
function buildContext(entries: SessionEntry[]): AppMessage[] {
|
|
77
|
-
// Build UUID -> entry map
|
|
78
|
-
const byUuid = new Map(entries.map(e => [e.uuid, e]));
|
|
79
|
-
|
|
80
|
-
// Start from last entry (current leaf)
|
|
81
|
-
let current: SessionEntry | undefined = entries[entries.length - 1];
|
|
82
|
-
|
|
83
|
-
// Walk to root, collecting messages
|
|
84
|
-
const path: SessionEntry[] = [];
|
|
85
|
-
while (current) {
|
|
86
|
-
path.unshift(current);
|
|
87
|
-
current = current.parentUuid ? byUuid.get(current.parentUuid) : undefined;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Extract messages, apply compaction summaries
|
|
91
|
-
return pathToMessages(path);
|
|
92
|
-
}
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
Complexity: O(n) to build map, O(depth) to walk. Total O(n), but walk is fast.
|
|
96
|
-
|
|
97
|
-
## Consequences for Stacking
|
|
98
|
-
|
|
99
|
-
### Current Approach (hooks-v2.md)
|
|
100
|
-
|
|
101
|
-
Stacking uses `stack_pop` entries with complex range overlap rules:
|
|
102
|
-
|
|
103
|
-
```typescript
|
|
104
|
-
interface StackPopEntry {
|
|
105
|
-
type: "stack_pop";
|
|
106
|
-
backToIndex: number;
|
|
107
|
-
summary: string;
|
|
108
|
-
prePopSummary?: string;
|
|
109
|
-
}
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
Context building requires tracking ranges, IDs, "later wins" logic.
|
|
113
|
-
|
|
114
|
-
### Tree Approach
|
|
115
|
-
|
|
116
|
-
Stacking becomes trivial branching:
|
|
117
|
-
|
|
118
|
-
```jsonl
|
|
119
|
-
... conversation entries ...
|
|
120
|
-
{"type":"stack_summary","uuid":"x1y2z3","parentUuid":"g7h8i9","summary":"Work done after this point"}
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
To "pop" to entry `g7h8i9`:
|
|
124
|
-
1. Generate summary of entries after `g7h8i9`
|
|
125
|
-
2. Append summary entry with `parentUuid: "g7h8i9"`
|
|
126
|
-
|
|
127
|
-
Context walk follows parentUuid chain. Abandoned entries are not traversed.
|
|
128
|
-
|
|
129
|
-
**No range tracking. No overlap rules. No "later wins" logic.**
|
|
130
|
-
|
|
131
|
-
### Multiple Pops
|
|
132
|
-
|
|
133
|
-
```
|
|
134
|
-
[a]─[b]─[c]─[d]─[e]─[f]─[g]─[h]
|
|
135
|
-
│
|
|
136
|
-
└─[i:summary]─[j]─[k]─[l]
|
|
137
|
-
│
|
|
138
|
-
└─[m:summary]─[n:current]
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
Each pop just creates a new branch. Context: n→m→k→j→i→c→b→a.
|
|
142
|
-
|
|
143
|
-
## Consequences for Compaction
|
|
144
|
-
|
|
145
|
-
### Current Approach
|
|
146
|
-
|
|
147
|
-
Compaction stores `firstKeptEntryIndex` (an index) and requires careful handling when stacking crosses compaction boundaries.
|
|
148
|
-
|
|
149
|
-
### Tree Approach
|
|
150
|
-
|
|
151
|
-
Compaction is just another entry in the linear chain, not a branch. Only change: `firstKeptEntryIndex` → `firstKeptEntryUuid`.
|
|
152
|
-
|
|
153
|
-
```
|
|
154
|
-
root → m1 → m2 → m3 → m4 → m5 → m6 → m7 → m8 → m9 → m10 → compaction
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
```jsonl
|
|
158
|
-
{"type":"compaction","uuid":"c1","parentUuid":"m10","summary":"...","firstKeptEntryUuid":"m6","tokensBefore":50000}
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
Context building:
|
|
162
|
-
1. Walk from leaf (compaction) to root
|
|
163
|
-
2. See compaction entry → note `firstKeptEntryUuid: "m6"`
|
|
164
|
-
3. Continue walking: m10, m9, m8, m7, m6 ← stop here
|
|
165
|
-
4. Everything before m6 is replaced by summary
|
|
166
|
-
5. Result: `[summary, m6, m7, m8, m9, m10]`
|
|
167
|
-
|
|
168
|
-
**Tree is for branching (stacking, alternative paths). Compaction is just a marker in the linear chain.**
|
|
169
|
-
|
|
170
|
-
### Compaction + Stacking
|
|
171
|
-
|
|
172
|
-
Stacking creates a branch, compaction is inline on each branch:
|
|
173
|
-
|
|
174
|
-
```
|
|
175
|
-
[root]─[m1]─[m2]─[m3]─[m4]─[m5]─[compaction1]─[m6]─[m7]─[m8]
|
|
176
|
-
│
|
|
177
|
-
└─[stack_summary]─[m9]─[m10]─[compaction2]─[m11:current]
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
Each branch has its own compaction history. Context walks the current branch only.
|
|
181
|
-
|
|
182
|
-
## Consequences for API
|
|
183
|
-
|
|
184
|
-
### SessionManager Changes
|
|
185
|
-
|
|
186
|
-
```typescript
|
|
187
|
-
interface SessionEntry {
|
|
188
|
-
type: string;
|
|
189
|
-
uuid: string; // NEW: unique identifier
|
|
190
|
-
parentUuid: string | null; // NEW: null for root
|
|
191
|
-
timestamp?: string;
|
|
192
|
-
// ... type-specific fields
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
class SessionManager {
|
|
196
|
-
// NEW: Get current leaf entry
|
|
197
|
-
getCurrentLeaf(): SessionEntry;
|
|
198
|
-
|
|
199
|
-
// NEW: Walk from entry to root
|
|
200
|
-
getPath(fromUuid?: string): SessionEntry[];
|
|
201
|
-
|
|
202
|
-
// NEW: Get entry by UUID
|
|
203
|
-
getEntry(uuid: string): SessionEntry | undefined;
|
|
204
|
-
|
|
205
|
-
// CHANGED: Uses tree walk instead of linear scan
|
|
206
|
-
buildSessionContext(): SessionContext;
|
|
207
|
-
|
|
208
|
-
// NEW: Create branch point
|
|
209
|
-
branch(parentUuid: string): string; // returns new entry's uuid
|
|
210
|
-
|
|
211
|
-
// NEW: Create branch with summary of abandoned subtree
|
|
212
|
-
branchWithSummary(parentUuid: string, summary: string): string;
|
|
213
|
-
|
|
214
|
-
// CHANGED: Simpler, just creates summary node
|
|
215
|
-
saveCompaction(entry: CompactionEntry): void;
|
|
216
|
-
|
|
217
|
-
// CHANGED: Now requires parentUuid (uses current leaf if omitted)
|
|
218
|
-
saveMessage(message: AppMessage, parentUuid?: string): void;
|
|
219
|
-
saveEntry(entry: SessionEntry): void;
|
|
220
|
-
}
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
### AgentSession Changes
|
|
224
|
-
|
|
225
|
-
```typescript
|
|
226
|
-
class AgentSession {
|
|
227
|
-
// CHANGED: Uses tree-based branching
|
|
228
|
-
async branch(entryUuid: string): Promise<BranchResult>;
|
|
229
|
-
|
|
230
|
-
// NEW: Branch in current session (no new file)
|
|
231
|
-
async branchInPlace(entryUuid: string, options?: {
|
|
232
|
-
summarize?: boolean; // Generate summary of abandoned subtree
|
|
233
|
-
}): Promise<void>;
|
|
234
|
-
|
|
235
|
-
// NEW: Get tree structure for visualization
|
|
236
|
-
getSessionTree(): SessionTree;
|
|
237
|
-
|
|
238
|
-
// CHANGED: Simpler implementation
|
|
239
|
-
async compact(): Promise<CompactionResult>;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
interface BranchResult {
|
|
243
|
-
selectedText: string;
|
|
244
|
-
cancelled: boolean;
|
|
245
|
-
newSessionFile?: string; // If branching to new file
|
|
246
|
-
inPlace: boolean; // If branched in current file
|
|
247
|
-
}
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
### Hook API Changes
|
|
251
|
-
|
|
252
|
-
```typescript
|
|
253
|
-
interface HookEventContext {
|
|
254
|
-
// NEW: Tree-aware entry access
|
|
255
|
-
entries: readonly SessionEntry[];
|
|
256
|
-
currentPath: readonly SessionEntry[]; // Entries from root to current leaf
|
|
257
|
-
|
|
258
|
-
// NEW: Branch without creating new file
|
|
259
|
-
branchInPlace(parentUuid: string, summary?: string): Promise<void>;
|
|
260
|
-
|
|
261
|
-
// Existing
|
|
262
|
-
saveEntry(entry: SessionEntry): Promise<void>;
|
|
263
|
-
rebuildContext(): Promise<void>;
|
|
264
|
-
}
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
## New Features Enabled
|
|
268
|
-
|
|
269
|
-
### 1. In-Place Branching
|
|
270
|
-
|
|
271
|
-
Currently, `/branch` always creates a new session file. With tree format:
|
|
272
|
-
|
|
273
|
-
```
|
|
274
|
-
/branch → Create new session file (current behavior)
|
|
275
|
-
/branch-here → Branch in current file, optionally with summary
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
Use case: Quick "let me try something else" without file proliferation.
|
|
279
|
-
|
|
280
|
-
### 2. Branch History Navigation
|
|
281
|
-
|
|
282
|
-
```
|
|
283
|
-
/branches → List all branches in current session
|
|
284
|
-
/switch <uuid> → Switch to branch at entry
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
The session file contains full history. UI can visualize the tree.
|
|
288
|
-
|
|
289
|
-
### 3. Simpler Stacking
|
|
290
|
-
|
|
291
|
-
No hooks needed for basic stacking:
|
|
292
|
-
|
|
293
|
-
```
|
|
294
|
-
/pop → Branch to previous user message with auto-summary
|
|
295
|
-
/pop <uuid> → Branch to specific entry with auto-summary
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
Core functionality, not hook-dependent.
|
|
299
|
-
|
|
300
|
-
### 4. Subtree Export
|
|
301
|
-
|
|
302
|
-
```
|
|
303
|
-
/export-branch <uuid> → Export just the subtree from entry
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
Useful for sharing specific conversation paths. No index remapping needed since UUIDs are stable.
|
|
307
|
-
|
|
308
|
-
### 5. Merge/Cherry-pick (Future)
|
|
309
|
-
|
|
310
|
-
With tree structure, could support:
|
|
311
|
-
|
|
312
|
-
```
|
|
313
|
-
/cherry-pick <uuid> → Copy entry's message to current branch
|
|
314
|
-
/merge <uuid> → Merge branch into current
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
## Migration
|
|
318
|
-
|
|
319
|
-
### Strategy: Migrate on Load + Rewrite
|
|
320
|
-
|
|
321
|
-
When loading a session, check if migration is needed. If so, migrate in memory and rewrite the file. This is transparent to users and only happens once per session file.
|
|
322
|
-
|
|
323
|
-
```typescript
|
|
324
|
-
const CURRENT_VERSION = 2;
|
|
325
|
-
|
|
326
|
-
function loadSession(path: string): SessionEntry[] {
|
|
327
|
-
const content = readFileSync(path, 'utf8');
|
|
328
|
-
const entries = parseEntries(content);
|
|
329
|
-
|
|
330
|
-
const header = entries.find(e => e.type === 'session');
|
|
331
|
-
const version = header?.version ?? 1;
|
|
332
|
-
|
|
333
|
-
if (version < CURRENT_VERSION) {
|
|
334
|
-
migrateEntries(entries, version);
|
|
335
|
-
writeFileSync(path, entries.map(e => JSON.stringify(e)).join('\n') + '\n');
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
return entries;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
function migrateEntries(entries: SessionEntry[], fromVersion: number): void {
|
|
342
|
-
if (fromVersion < 2) {
|
|
343
|
-
// v1 → v2: Add uuid/parentUuid, convert firstKeptEntryIndex
|
|
344
|
-
const uuids: string[] = [];
|
|
345
|
-
|
|
346
|
-
for (let i = 0; i < entries.length; i++) {
|
|
347
|
-
const entry = entries[i];
|
|
348
|
-
const uuid = generateUuid();
|
|
349
|
-
uuids.push(uuid);
|
|
350
|
-
|
|
351
|
-
entry.uuid = uuid;
|
|
352
|
-
entry.parentUuid = i === 0 ? null : uuids[i - 1];
|
|
353
|
-
|
|
354
|
-
// Update session header version
|
|
355
|
-
if (entry.type === 'session') {
|
|
356
|
-
entry.version = CURRENT_VERSION;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Convert compaction index to UUID
|
|
360
|
-
if (entry.type === 'compaction' && 'firstKeptEntryIndex' in entry) {
|
|
361
|
-
entry.firstKeptEntryUuid = uuids[entry.firstKeptEntryIndex];
|
|
362
|
-
delete entry.firstKeptEntryIndex;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// Future migrations: if (fromVersion < 3) { ... }
|
|
368
|
-
}
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
### What Gets Migrated
|
|
372
|
-
|
|
373
|
-
| v1 Field | v2 Field |
|
|
374
|
-
|----------|----------|
|
|
375
|
-
| (none) | `uuid` (generated) |
|
|
376
|
-
| (none) | `parentUuid` (previous entry's uuid, null for root) |
|
|
377
|
-
| (none on session) | `version: 2` |
|
|
378
|
-
| `firstKeptEntryIndex` | `firstKeptEntryUuid` |
|
|
379
|
-
|
|
380
|
-
Migrated sessions work exactly as before (linear path). Tree features become available.
|
|
381
|
-
|
|
382
|
-
### API Compatibility
|
|
383
|
-
|
|
384
|
-
- `buildSessionContext()` returns same structure
|
|
385
|
-
- `branch()` still works, just uses UUIDs
|
|
386
|
-
- Existing hooks continue to work
|
|
387
|
-
- Old sessions auto-migrate on first load
|
|
388
|
-
|
|
389
|
-
## Complexity Analysis
|
|
390
|
-
|
|
391
|
-
| Operation | Linear | Tree |
|
|
392
|
-
|-----------|--------|------|
|
|
393
|
-
| Append message | O(1) | O(1) |
|
|
394
|
-
| Build context | O(n) | O(n) map + O(depth) walk |
|
|
395
|
-
| Branch to new file | O(n) copy | O(path) copy, no remapping |
|
|
396
|
-
| Find entry by UUID | O(n) | O(1) with map |
|
|
397
|
-
| Compaction | O(n) | O(depth) |
|
|
398
|
-
|
|
399
|
-
Tree with UUIDs is comparable or better. The UUID map can be cached.
|
|
400
|
-
|
|
401
|
-
## File Size
|
|
402
|
-
|
|
403
|
-
Tree format adds ~50 bytes per entry (`"uuid":"...","parentUuid":"..."`, 36 chars each). For 1000-entry session: ~50KB overhead. Negligible for text-heavy sessions.
|
|
404
|
-
|
|
405
|
-
Abandoned branches remain in file but don't affect context building performance.
|
|
406
|
-
|
|
407
|
-
## Example: Full Session with Branching
|
|
408
|
-
|
|
409
|
-
```jsonl
|
|
410
|
-
{"type":"session","version":2,"uuid":"ses1","parentUuid":null,"id":"abc","cwd":"/project"}
|
|
411
|
-
{"type":"message","uuid":"m1","parentUuid":"ses1","message":{"role":"user","content":"Build a CLI"}}
|
|
412
|
-
{"type":"message","uuid":"m2","parentUuid":"m1","message":{"role":"assistant","content":"I'll create..."}}
|
|
413
|
-
{"type":"message","uuid":"m3","parentUuid":"m2","message":{"role":"user","content":"Add --verbose flag"}}
|
|
414
|
-
{"type":"message","uuid":"m4","parentUuid":"m3","message":{"role":"assistant","content":"Here's the flag..."}}
|
|
415
|
-
{"type":"message","uuid":"m5","parentUuid":"m4","message":{"role":"user","content":"Actually use Python"}}
|
|
416
|
-
{"type":"message","uuid":"m6","parentUuid":"m5","message":{"role":"assistant","content":"Converting to Python..."}}
|
|
417
|
-
{"type":"branch_summary","uuid":"bs1","parentUuid":"m2","summary":"Attempted Node.js CLI with --verbose flag"}
|
|
418
|
-
{"type":"message","uuid":"m7","parentUuid":"bs1","message":{"role":"user","content":"Use Rust instead"}}
|
|
419
|
-
{"type":"message","uuid":"m8","parentUuid":"m7","message":{"role":"assistant","content":"Creating Rust CLI..."}}
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
Context path: m8→m7→bs1→m2→m1→ses1
|
|
423
|
-
|
|
424
|
-
Result:
|
|
425
|
-
1. User: "Build a CLI"
|
|
426
|
-
2. Assistant: "I'll create..."
|
|
427
|
-
3. Summary: "Attempted Node.js CLI with --verbose flag"
|
|
428
|
-
4. User: "Use Rust instead"
|
|
429
|
-
5. Assistant: "Creating Rust CLI..."
|
|
430
|
-
|
|
431
|
-
Entries m3-m6 (the Node.js/Python path) are preserved but not in context.
|
|
432
|
-
|
|
433
|
-
## Prior Art
|
|
434
|
-
|
|
435
|
-
Claude Code uses the same approach:
|
|
436
|
-
- `uuid` field on each entry
|
|
437
|
-
- `parentUuid` links to parent (null for root)
|
|
438
|
-
- `leafUuid` in summary entries to track conversation endpoints
|
|
439
|
-
- Separate files for sidechains (`isSidechain: true`)
|
|
440
|
-
|
|
441
|
-
## Recommendation
|
|
442
|
-
|
|
443
|
-
The tree format with UUIDs:
|
|
444
|
-
- Simplifies stacking (no range overlap logic)
|
|
445
|
-
- Simplifies compaction (no boundary crossing)
|
|
446
|
-
- Enables in-place branching
|
|
447
|
-
- Enables branch visualization/navigation
|
|
448
|
-
- No index remapping on branch-to-file
|
|
449
|
-
- Maintains backward compatibility
|
|
450
|
-
- Validated by Claude Code's implementation
|
|
451
|
-
|
|
452
|
-
**Recommend implementing for v2 of hooks/session system.**
|