@mariozechner/pi-coding-agent 0.30.1 → 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.
Files changed (301) hide show
  1. package/CHANGELOG.md +251 -2
  2. package/README.md +105 -84
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +5 -1
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/cli/file-processor.d.ts +3 -3
  7. package/dist/cli/file-processor.d.ts.map +1 -1
  8. package/dist/cli/file-processor.js +7 -10
  9. package/dist/cli/file-processor.js.map +1 -1
  10. package/dist/config.d.ts +9 -0
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +18 -0
  13. package/dist/config.js.map +1 -1
  14. package/dist/core/agent-session.d.ts +73 -34
  15. package/dist/core/agent-session.d.ts.map +1 -1
  16. package/dist/core/agent-session.js +464 -210
  17. package/dist/core/agent-session.js.map +1 -1
  18. package/dist/core/auth-storage.d.ts +2 -7
  19. package/dist/core/auth-storage.d.ts.map +1 -1
  20. package/dist/core/auth-storage.js +4 -52
  21. package/dist/core/auth-storage.js.map +1 -1
  22. package/dist/core/bash-executor.d.ts +2 -2
  23. package/dist/core/bash-executor.d.ts.map +1 -1
  24. package/dist/core/bash-executor.js +2 -2
  25. package/dist/core/bash-executor.js.map +1 -1
  26. package/dist/core/compaction/branch-summarization.d.ts +84 -0
  27. package/dist/core/compaction/branch-summarization.d.ts.map +1 -0
  28. package/dist/core/compaction/branch-summarization.js +233 -0
  29. package/dist/core/compaction/branch-summarization.js.map +1 -0
  30. package/dist/core/{compaction.d.ts → compaction/compaction.d.ts} +38 -19
  31. package/dist/core/compaction/compaction.d.ts.map +1 -0
  32. package/dist/core/compaction/compaction.js +558 -0
  33. package/dist/core/compaction/compaction.js.map +1 -0
  34. package/dist/core/compaction/index.d.ts +7 -0
  35. package/dist/core/compaction/index.d.ts.map +1 -0
  36. package/dist/core/compaction/index.js +7 -0
  37. package/dist/core/compaction/index.js.map +1 -0
  38. package/dist/core/compaction/utils.d.ts +35 -0
  39. package/dist/core/compaction/utils.d.ts.map +1 -0
  40. package/dist/core/compaction/utils.js +138 -0
  41. package/dist/core/compaction/utils.js.map +1 -0
  42. package/dist/core/custom-tools/index.d.ts +2 -1
  43. package/dist/core/custom-tools/index.d.ts.map +1 -1
  44. package/dist/core/custom-tools/index.js +1 -0
  45. package/dist/core/custom-tools/index.js.map +1 -1
  46. package/dist/core/custom-tools/loader.d.ts.map +1 -1
  47. package/dist/core/custom-tools/loader.js +13 -80
  48. package/dist/core/custom-tools/loader.js.map +1 -1
  49. package/dist/core/custom-tools/types.d.ts +84 -59
  50. package/dist/core/custom-tools/types.d.ts.map +1 -1
  51. package/dist/core/custom-tools/types.js.map +1 -1
  52. package/dist/core/custom-tools/wrapper.d.ts +15 -0
  53. package/dist/core/custom-tools/wrapper.d.ts.map +1 -0
  54. package/dist/core/custom-tools/wrapper.js +23 -0
  55. package/dist/core/custom-tools/wrapper.js.map +1 -0
  56. package/dist/core/exec.d.ts +29 -0
  57. package/dist/core/exec.d.ts.map +1 -0
  58. package/dist/core/exec.js +71 -0
  59. package/dist/core/exec.js.map +1 -0
  60. package/dist/core/export-html/index.d.ts +17 -0
  61. package/dist/core/export-html/index.d.ts.map +1 -0
  62. package/dist/core/export-html/index.js +171 -0
  63. package/dist/core/export-html/index.js.map +1 -0
  64. package/dist/core/export-html/template.css +781 -0
  65. package/dist/core/export-html/template.html +54 -0
  66. package/dist/core/export-html/template.js +1185 -0
  67. package/dist/core/export-html/vendor/highlight.min.js +1213 -0
  68. package/dist/core/export-html/vendor/marked.min.js +6 -0
  69. package/dist/core/hooks/index.d.ts +4 -4
  70. package/dist/core/hooks/index.d.ts.map +1 -1
  71. package/dist/core/hooks/index.js +3 -3
  72. package/dist/core/hooks/index.js.map +1 -1
  73. package/dist/core/hooks/loader.d.ts +40 -5
  74. package/dist/core/hooks/loader.d.ts.map +1 -1
  75. package/dist/core/hooks/loader.js +43 -10
  76. package/dist/core/hooks/loader.js.map +1 -1
  77. package/dist/core/hooks/runner.d.ts +94 -18
  78. package/dist/core/hooks/runner.d.ts.map +1 -1
  79. package/dist/core/hooks/runner.js +199 -120
  80. package/dist/core/hooks/runner.js.map +1 -1
  81. package/dist/core/hooks/tool-wrapper.d.ts +1 -1
  82. package/dist/core/hooks/tool-wrapper.d.ts.map +1 -1
  83. package/dist/core/hooks/tool-wrapper.js +36 -19
  84. package/dist/core/hooks/tool-wrapper.js.map +1 -1
  85. package/dist/core/hooks/types.d.ts +407 -96
  86. package/dist/core/hooks/types.d.ts.map +1 -1
  87. package/dist/core/hooks/types.js.map +1 -1
  88. package/dist/core/index.d.ts +4 -3
  89. package/dist/core/index.d.ts.map +1 -1
  90. package/dist/core/index.js.map +1 -1
  91. package/dist/core/messages.d.ts +44 -12
  92. package/dist/core/messages.d.ts.map +1 -1
  93. package/dist/core/messages.js +82 -34
  94. package/dist/core/messages.js.map +1 -1
  95. package/dist/core/model-registry.d.ts +5 -5
  96. package/dist/core/model-registry.d.ts.map +1 -1
  97. package/dist/core/model-registry.js +7 -7
  98. package/dist/core/model-registry.js.map +1 -1
  99. package/dist/core/model-resolver.d.ts +7 -7
  100. package/dist/core/model-resolver.d.ts.map +1 -1
  101. package/dist/core/model-resolver.js +45 -14
  102. package/dist/core/model-resolver.js.map +1 -1
  103. package/dist/core/sdk.d.ts +7 -10
  104. package/dist/core/sdk.d.ts.map +1 -1
  105. package/dist/core/sdk.js +88 -32
  106. package/dist/core/sdk.js.map +1 -1
  107. package/dist/core/session-manager.d.ts +202 -36
  108. package/dist/core/session-manager.d.ts.map +1 -1
  109. package/dist/core/session-manager.js +565 -133
  110. package/dist/core/session-manager.js.map +1 -1
  111. package/dist/core/settings-manager.d.ts +9 -3
  112. package/dist/core/settings-manager.d.ts.map +1 -1
  113. package/dist/core/settings-manager.js +13 -12
  114. package/dist/core/settings-manager.js.map +1 -1
  115. package/dist/core/system-prompt.d.ts.map +1 -1
  116. package/dist/core/system-prompt.js +6 -3
  117. package/dist/core/system-prompt.js.map +1 -1
  118. package/dist/core/tools/bash.d.ts +1 -1
  119. package/dist/core/tools/bash.d.ts.map +1 -1
  120. package/dist/core/tools/bash.js.map +1 -1
  121. package/dist/core/tools/edit-diff.d.ts +33 -0
  122. package/dist/core/tools/edit-diff.d.ts.map +1 -0
  123. package/dist/core/tools/edit-diff.js +171 -0
  124. package/dist/core/tools/edit-diff.js.map +1 -0
  125. package/dist/core/tools/edit.d.ts +7 -1
  126. package/dist/core/tools/edit.d.ts.map +1 -1
  127. package/dist/core/tools/edit.js +20 -95
  128. package/dist/core/tools/edit.js.map +1 -1
  129. package/dist/core/tools/find.d.ts +1 -1
  130. package/dist/core/tools/find.d.ts.map +1 -1
  131. package/dist/core/tools/find.js.map +1 -1
  132. package/dist/core/tools/grep.d.ts +1 -1
  133. package/dist/core/tools/grep.d.ts.map +1 -1
  134. package/dist/core/tools/grep.js.map +1 -1
  135. package/dist/core/tools/index.d.ts +1 -1
  136. package/dist/core/tools/index.d.ts.map +1 -1
  137. package/dist/core/tools/index.js.map +1 -1
  138. package/dist/core/tools/ls.d.ts +1 -1
  139. package/dist/core/tools/ls.d.ts.map +1 -1
  140. package/dist/core/tools/ls.js.map +1 -1
  141. package/dist/core/tools/read.d.ts +1 -1
  142. package/dist/core/tools/read.d.ts.map +1 -1
  143. package/dist/core/tools/read.js.map +1 -1
  144. package/dist/core/tools/write.d.ts +1 -1
  145. package/dist/core/tools/write.d.ts.map +1 -1
  146. package/dist/core/tools/write.js.map +1 -1
  147. package/dist/index.d.ts +8 -7
  148. package/dist/index.d.ts.map +1 -1
  149. package/dist/index.js +5 -5
  150. package/dist/index.js.map +1 -1
  151. package/dist/main.d.ts.map +1 -1
  152. package/dist/main.js +25 -25
  153. package/dist/main.js.map +1 -1
  154. package/dist/migrations.d.ts +28 -0
  155. package/dist/migrations.d.ts.map +1 -0
  156. package/dist/migrations.js +125 -0
  157. package/dist/migrations.js.map +1 -0
  158. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  159. package/dist/modes/interactive/components/assistant-message.js +3 -4
  160. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  161. package/dist/modes/interactive/components/bash-execution.d.ts +1 -1
  162. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  163. package/dist/modes/interactive/components/bash-execution.js +6 -2
  164. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  165. package/dist/modes/interactive/components/bordered-loader.d.ts +12 -0
  166. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -0
  167. package/dist/modes/interactive/components/bordered-loader.js +30 -0
  168. package/dist/modes/interactive/components/bordered-loader.js.map +1 -0
  169. package/dist/modes/interactive/components/branch-summary-message.d.ts +14 -0
  170. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -0
  171. package/dist/modes/interactive/components/branch-summary-message.js +35 -0
  172. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -0
  173. package/dist/modes/interactive/components/compaction-summary-message.d.ts +14 -0
  174. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -0
  175. package/dist/modes/interactive/components/compaction-summary-message.js +36 -0
  176. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -0
  177. package/dist/modes/interactive/components/dynamic-border.d.ts +5 -1
  178. package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
  179. package/dist/modes/interactive/components/dynamic-border.js +5 -1
  180. package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
  181. package/dist/modes/interactive/components/footer.d.ts +12 -6
  182. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  183. package/dist/modes/interactive/components/footer.js +57 -25
  184. package/dist/modes/interactive/components/footer.js.map +1 -1
  185. package/dist/modes/interactive/components/hook-editor.d.ts +15 -0
  186. package/dist/modes/interactive/components/hook-editor.d.ts.map +1 -0
  187. package/dist/modes/interactive/components/hook-editor.js +95 -0
  188. package/dist/modes/interactive/components/hook-editor.js.map +1 -0
  189. package/dist/modes/interactive/components/hook-message.d.ts +18 -0
  190. package/dist/modes/interactive/components/hook-message.d.ts.map +1 -0
  191. package/dist/modes/interactive/components/hook-message.js +80 -0
  192. package/dist/modes/interactive/components/hook-message.js.map +1 -0
  193. package/dist/modes/interactive/components/model-selector.d.ts +3 -3
  194. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  195. package/dist/modes/interactive/components/model-selector.js +1 -1
  196. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  197. package/dist/modes/interactive/components/tool-execution.d.ts +15 -2
  198. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  199. package/dist/modes/interactive/components/tool-execution.js +70 -21
  200. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  201. package/dist/modes/interactive/components/tree-selector.d.ts +52 -0
  202. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -0
  203. package/dist/modes/interactive/components/tree-selector.js +745 -0
  204. package/dist/modes/interactive/components/tree-selector.js.map +1 -0
  205. package/dist/modes/interactive/components/user-message-selector.d.ts +3 -3
  206. package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
  207. package/dist/modes/interactive/components/user-message-selector.js +1 -1
  208. package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
  209. package/dist/modes/interactive/components/user-message.d.ts +1 -1
  210. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  211. package/dist/modes/interactive/components/user-message.js +2 -5
  212. package/dist/modes/interactive/components/user-message.js.map +1 -1
  213. package/dist/modes/interactive/interactive-mode.d.ts +29 -12
  214. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  215. package/dist/modes/interactive/interactive-mode.js +589 -208
  216. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  217. package/dist/modes/interactive/theme/dark.json +13 -1
  218. package/dist/modes/interactive/theme/light.json +13 -1
  219. package/dist/modes/interactive/theme/theme-schema.json +34 -0
  220. package/dist/modes/interactive/theme/theme.d.ts +20 -2
  221. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  222. package/dist/modes/interactive/theme/theme.js +135 -2
  223. package/dist/modes/interactive/theme/theme.js.map +1 -1
  224. package/dist/modes/print-mode.d.ts +3 -3
  225. package/dist/modes/print-mode.d.ts.map +1 -1
  226. package/dist/modes/print-mode.js +26 -20
  227. package/dist/modes/print-mode.js.map +1 -1
  228. package/dist/modes/rpc/rpc-client.d.ts +13 -10
  229. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  230. package/dist/modes/rpc/rpc-client.js +11 -10
  231. package/dist/modes/rpc/rpc-client.js.map +1 -1
  232. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  233. package/dist/modes/rpc/rpc-mode.js +88 -35
  234. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  235. package/dist/modes/rpc/rpc-types.d.ts +30 -11
  236. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  237. package/dist/modes/rpc/rpc-types.js.map +1 -1
  238. package/dist/utils/shell.d.ts +4 -2
  239. package/dist/utils/shell.d.ts.map +1 -1
  240. package/dist/utils/shell.js +36 -7
  241. package/dist/utils/shell.js.map +1 -1
  242. package/dist/utils/tools-manager.d.ts +1 -1
  243. package/dist/utils/tools-manager.d.ts.map +1 -1
  244. package/dist/utils/tools-manager.js +2 -2
  245. package/dist/utils/tools-manager.js.map +1 -1
  246. package/docs/compaction.md +388 -0
  247. package/docs/custom-tools.md +146 -43
  248. package/docs/extension-loading.md +1004 -0
  249. package/docs/hooks.md +562 -596
  250. package/docs/rpc.md +33 -19
  251. package/docs/sdk.md +93 -21
  252. package/docs/session-tree-plan.md +441 -0
  253. package/docs/session.md +172 -21
  254. package/docs/skills.md +2 -0
  255. package/docs/theme.md +31 -2
  256. package/docs/tree.md +197 -0
  257. package/docs/tui.md +343 -0
  258. package/examples/README.md +1 -9
  259. package/examples/custom-tools/hello/index.ts +4 -3
  260. package/examples/custom-tools/question/index.ts +4 -4
  261. package/examples/custom-tools/subagent/index.ts +7 -6
  262. package/examples/custom-tools/todo/index.ts +11 -5
  263. package/examples/hooks/README.md +29 -71
  264. package/examples/hooks/auto-commit-on-exit.ts +8 -9
  265. package/examples/hooks/confirm-destructive.ts +29 -30
  266. package/examples/hooks/custom-compaction.ts +20 -21
  267. package/examples/hooks/dirty-repo-guard.ts +41 -40
  268. package/examples/hooks/file-trigger.ts +10 -5
  269. package/examples/hooks/git-checkpoint.ts +16 -12
  270. package/examples/hooks/handoff.ts +150 -0
  271. package/examples/hooks/permission-gate.ts +1 -1
  272. package/examples/hooks/protected-paths.ts +1 -1
  273. package/examples/hooks/qna.ts +119 -0
  274. package/examples/hooks/snake.ts +343 -0
  275. package/examples/hooks/status-line.ts +40 -0
  276. package/examples/sdk/01-minimal.ts +1 -1
  277. package/examples/sdk/02-custom-model.ts +1 -1
  278. package/examples/sdk/03-custom-prompt.ts +1 -1
  279. package/examples/sdk/04-skills.ts +1 -1
  280. package/examples/sdk/05-tools.ts +4 -4
  281. package/examples/sdk/06-hooks.ts +1 -1
  282. package/examples/sdk/07-context-files.ts +1 -1
  283. package/examples/sdk/08-slash-commands.ts +6 -1
  284. package/examples/sdk/09-api-keys-and-oauth.ts +1 -1
  285. package/examples/sdk/10-settings.ts +1 -1
  286. package/examples/sdk/11-sessions.ts +1 -1
  287. package/examples/sdk/12-full-control.ts +4 -7
  288. package/package.json +6 -6
  289. package/dist/core/compaction.d.ts.map +0 -1
  290. package/dist/core/compaction.js +0 -412
  291. package/dist/core/compaction.js.map +0 -1
  292. package/dist/core/export-html.d.ts +0 -23
  293. package/dist/core/export-html.d.ts.map +0 -1
  294. package/dist/core/export-html.js +0 -1185
  295. package/dist/core/export-html.js.map +0 -1
  296. package/dist/modes/interactive/components/compaction.d.ts +0 -15
  297. package/dist/modes/interactive/components/compaction.d.ts.map +0 -1
  298. package/dist/modes/interactive/components/compaction.js +0 -41
  299. package/dist/modes/interactive/components/compaction.js.map +0 -1
  300. package/docs/hooks-v2.md +0 -385
  301. package/docs/session-tree.md +0 -452
@@ -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.**