@astralyn/pi 0.82.2 → 0.82.4

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 (172) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +70 -634
  3. package/dist/cli/config-selector.d.ts.map +1 -1
  4. package/dist/cli/config-selector.js +3 -2
  5. package/dist/cli/config-selector.js.map +1 -1
  6. package/dist/cli/startup-ui.d.ts.map +1 -1
  7. package/dist/cli/startup-ui.js +3 -2
  8. package/dist/cli/startup-ui.js.map +1 -1
  9. package/dist/core/agent-session.d.ts +2 -0
  10. package/dist/core/agent-session.d.ts.map +1 -1
  11. package/dist/core/agent-session.js +31 -12
  12. package/dist/core/agent-session.js.map +1 -1
  13. package/dist/core/extensions/index.d.ts +1 -1
  14. package/dist/core/extensions/index.d.ts.map +1 -1
  15. package/dist/core/extensions/index.js.map +1 -1
  16. package/dist/core/extensions/types.d.ts +11 -0
  17. package/dist/core/extensions/types.d.ts.map +1 -1
  18. package/dist/core/extensions/types.js.map +1 -1
  19. package/dist/core/model-resolver.d.ts +1 -0
  20. package/dist/core/model-resolver.d.ts.map +1 -1
  21. package/dist/core/model-resolver.js +13 -3
  22. package/dist/core/model-resolver.js.map +1 -1
  23. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  24. package/dist/core/remote-catalog-provider.js +19 -1
  25. package/dist/core/remote-catalog-provider.js.map +1 -1
  26. package/dist/core/resource-loader.d.ts.map +1 -1
  27. package/dist/core/resource-loader.js +3 -0
  28. package/dist/core/resource-loader.js.map +1 -1
  29. package/dist/extensions/index.d.ts.map +1 -1
  30. package/dist/extensions/index.js +2 -0
  31. package/dist/extensions/index.js.map +1 -1
  32. package/dist/extensions/llama/provider.d.ts.map +1 -1
  33. package/dist/extensions/llama/provider.js +6 -0
  34. package/dist/extensions/llama/provider.js.map +1 -1
  35. package/dist/extensions/plan/constants.d.ts +31 -0
  36. package/dist/extensions/plan/constants.d.ts.map +1 -0
  37. package/dist/extensions/plan/constants.js +57 -0
  38. package/dist/extensions/plan/constants.js.map +1 -0
  39. package/dist/extensions/plan/index.d.ts +21 -0
  40. package/dist/extensions/plan/index.d.ts.map +1 -0
  41. package/dist/extensions/plan/index.js +291 -0
  42. package/dist/extensions/plan/index.js.map +1 -0
  43. package/dist/extensions/plan/schema.d.ts +15 -0
  44. package/dist/extensions/plan/schema.d.ts.map +1 -0
  45. package/dist/extensions/plan/schema.js +13 -0
  46. package/dist/extensions/plan/schema.js.map +1 -0
  47. package/dist/extensions/plan/state.d.ts +29 -0
  48. package/dist/extensions/plan/state.d.ts.map +1 -0
  49. package/dist/extensions/plan/state.js +74 -0
  50. package/dist/extensions/plan/state.js.map +1 -0
  51. package/dist/extensions/plan/storage.d.ts +18 -0
  52. package/dist/extensions/plan/storage.d.ts.map +1 -0
  53. package/dist/extensions/plan/storage.js +58 -0
  54. package/dist/extensions/plan/storage.js.map +1 -0
  55. package/dist/extensions/rewind/anchor.d.ts +29 -0
  56. package/dist/extensions/rewind/anchor.d.ts.map +1 -0
  57. package/dist/extensions/rewind/anchor.js +35 -0
  58. package/dist/extensions/rewind/anchor.js.map +1 -0
  59. package/dist/extensions/rewind/engine.d.ts +2 -6
  60. package/dist/extensions/rewind/engine.d.ts.map +1 -1
  61. package/dist/extensions/rewind/engine.js +41 -10
  62. package/dist/extensions/rewind/engine.js.map +1 -1
  63. package/dist/extensions/rewind/index.d.ts.map +1 -1
  64. package/dist/extensions/rewind/index.js +55 -21
  65. package/dist/extensions/rewind/index.js.map +1 -1
  66. package/dist/extensions/rewind/menu.d.ts.map +1 -1
  67. package/dist/extensions/rewind/menu.js +7 -2
  68. package/dist/extensions/rewind/menu.js.map +1 -1
  69. package/dist/extensions/rewind/restore.d.ts +26 -9
  70. package/dist/extensions/rewind/restore.d.ts.map +1 -1
  71. package/dist/extensions/rewind/restore.js +58 -16
  72. package/dist/extensions/rewind/restore.js.map +1 -1
  73. package/dist/extensions/subagent/activity.d.ts +1 -0
  74. package/dist/extensions/subagent/activity.d.ts.map +1 -1
  75. package/dist/extensions/subagent/activity.js +20 -4
  76. package/dist/extensions/subagent/activity.js.map +1 -1
  77. package/dist/extensions/subagent/agents.d.ts.map +1 -1
  78. package/dist/extensions/subagent/agents.js +2 -13
  79. package/dist/extensions/subagent/agents.js.map +1 -1
  80. package/dist/extensions/subagent/constants.d.ts +4 -3
  81. package/dist/extensions/subagent/constants.d.ts.map +1 -1
  82. package/dist/extensions/subagent/constants.js +9 -3
  83. package/dist/extensions/subagent/constants.js.map +1 -1
  84. package/dist/extensions/subagent/index.d.ts.map +1 -1
  85. package/dist/extensions/subagent/index.js +12 -5
  86. package/dist/extensions/subagent/index.js.map +1 -1
  87. package/dist/extensions/subagent/render.d.ts.map +1 -1
  88. package/dist/extensions/subagent/render.js +166 -41
  89. package/dist/extensions/subagent/render.js.map +1 -1
  90. package/dist/extensions/subagent/resolve.d.ts +2 -5
  91. package/dist/extensions/subagent/resolve.d.ts.map +1 -1
  92. package/dist/extensions/subagent/resolve.js +8 -13
  93. package/dist/extensions/subagent/resolve.js.map +1 -1
  94. package/dist/extensions/subagent/runner.d.ts +3 -1
  95. package/dist/extensions/subagent/runner.d.ts.map +1 -1
  96. package/dist/extensions/subagent/runner.js +111 -66
  97. package/dist/extensions/subagent/runner.js.map +1 -1
  98. package/dist/extensions/subagent/schema.d.ts +0 -6
  99. package/dist/extensions/subagent/schema.d.ts.map +1 -1
  100. package/dist/extensions/subagent/schema.js +2 -3
  101. package/dist/extensions/subagent/schema.js.map +1 -1
  102. package/dist/extensions/subagent/sdk-runner.d.ts.map +1 -1
  103. package/dist/extensions/subagent/sdk-runner.js +32 -16
  104. package/dist/extensions/subagent/sdk-runner.js.map +1 -1
  105. package/dist/extensions/subagent/types.d.ts +3 -2
  106. package/dist/extensions/subagent/types.d.ts.map +1 -1
  107. package/dist/extensions/subagent/types.js.map +1 -1
  108. package/dist/modes/interactive/components/custom-message.d.ts +3 -1
  109. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  110. package/dist/modes/interactive/components/custom-message.js +10 -2
  111. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  112. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  113. package/dist/modes/interactive/components/scoped-models-selector.js +22 -13
  114. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  115. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  116. package/dist/modes/interactive/interactive-mode.js +31 -18
  117. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  118. package/dist/utils/coalescing-terminal.d.ts +62 -0
  119. package/dist/utils/coalescing-terminal.d.ts.map +1 -0
  120. package/dist/utils/coalescing-terminal.js +151 -0
  121. package/dist/utils/coalescing-terminal.js.map +1 -0
  122. package/docs/bundled/README.md +38 -0
  123. package/docs/bundled/extensions/README.md +19 -0
  124. package/docs/bundled/extensions/plan.md +31 -0
  125. package/docs/{distribution → bundled}/extensions/question.md +0 -11
  126. package/docs/{distribution → bundled}/extensions/rewind.md +19 -18
  127. package/docs/{distribution → bundled}/extensions/router.md +1 -14
  128. package/docs/{distribution → bundled}/extensions/statusline.md +1 -2
  129. package/docs/{distribution → bundled}/extensions/subagent.md +15 -22
  130. package/docs/bundled/extensions/todo.md +28 -0
  131. package/docs/{distribution → bundled}/themes.md +4 -11
  132. package/docs/{distribution → bundled}/tool-presentation.md +4 -10
  133. package/docs/compaction.md +18 -14
  134. package/docs/custom-provider.md +8 -8
  135. package/docs/docs.json +61 -9
  136. package/docs/extensions.md +14 -13
  137. package/docs/index.md +12 -7
  138. package/docs/json.md +4 -4
  139. package/docs/providers.md +1 -1
  140. package/docs/rpc.md +3 -9
  141. package/docs/session-format.md +5 -5
  142. package/docs/skills.md +1 -1
  143. package/docs/themes.md +4 -8
  144. package/docs/tui.md +1 -1
  145. package/examples/extensions/README.md +0 -1
  146. package/examples/extensions/custom-provider-anthropic/index.ts +4 -4
  147. package/examples/extensions/custom-provider-gitlab-duo/index.ts +2 -2
  148. package/examples/extensions/dynamic-resources/dynamic.json +1 -1
  149. package/examples/extensions/gondolin/index.ts +4 -6
  150. package/examples/extensions/message-renderer.ts +2 -2
  151. package/examples/extensions/overlay-qa-tests.ts +2 -2
  152. package/examples/extensions/project-trust.ts +2 -2
  153. package/examples/sdk/README.md +38 -44
  154. package/npm-shrinkwrap.json +2 -2
  155. package/package.json +6 -2
  156. package/docs/development.md +0 -68
  157. package/docs/distribution/README.md +0 -34
  158. package/docs/distribution/architecture.md +0 -102
  159. package/docs/distribution/extensions/README.md +0 -17
  160. package/docs/distribution/maintenance.md +0 -121
  161. package/docs/distribution/release.md +0 -85
  162. package/examples/extensions/subagent/README.md +0 -175
  163. package/examples/extensions/subagent/agents/planner.md +0 -37
  164. package/examples/extensions/subagent/agents/reviewer.md +0 -35
  165. package/examples/extensions/subagent/agents/scout.md +0 -50
  166. package/examples/extensions/subagent/agents/worker.md +0 -24
  167. package/examples/extensions/subagent/agents.ts +0 -126
  168. package/examples/extensions/subagent/index.ts +0 -1009
  169. package/examples/extensions/subagent/prompts/implement-and-review.md +0 -10
  170. package/examples/extensions/subagent/prompts/implement.md +0 -10
  171. package/examples/extensions/subagent/prompts/scout-and-plan.md +0 -9
  172. /package/docs/{distribution → bundled}/extensions/deepwiki.md +0 -0
@@ -0,0 +1,62 @@
1
+ import { ProcessTerminal } from "@earendil-works/pi-tui";
2
+ /**
3
+ * ProcessTerminal that (1) coalesces every write issued within one synchronous
4
+ * task into a single stdout write and (2) keeps content-driven full redraws
5
+ * from destroying the terminal's scrollback.
6
+ *
7
+ * Coalescing: pi-tui emits each render pass as several separate stdout
8
+ * writes: the frame wrapped in synchronized output (CSI ?2026h/l), then a
9
+ * relative cursor move that parks the hardware cursor at the focused
10
+ * component's caret for IME composition, then a cursor visibility toggle.
11
+ * Terminals that anchor the IME candidate window to the hardware cursor
12
+ * (Windows Terminal and other ConPTY hosts, WezTerm) can process the frame
13
+ * write before the reposition write arrives, briefly leaving the cursor at
14
+ * the end of the last repainted line. With an IME active this makes the
15
+ * candidate window jump between the caret and the right edge of the input
16
+ * line on every keystroke. Merging the writes makes each frame atomic, so the
17
+ * only cursor position a terminal can observe is the final one.
18
+ *
19
+ * Scrollback preservation: pi-tui's fullRender(clear) opens with
20
+ * `2J H 3J`, wiping the terminal's scrollback, and then replays the entire
21
+ * transcript. Content-driven triggers (a line changing above the viewport
22
+ * during streaming markdown reflow, large content shrinks) fire this
23
+ * routinely, which erases pre-session shell history and — in terminals that
24
+ * clamp the scroll offset when scrollback vanishes, notably Windows
25
+ * Terminal — yanks a scrolled-up viewport to the top of the buffer
26
+ * (upstream #6502, #5576, #6050; rejected upstream fix #4204). Frames
27
+ * matching that shape are rewritten to overwrite the visible screen in place
28
+ * (home, erase-and-rewrite each row, erase below) with only the bottom
29
+ * viewport-height rows, which leaves scrollback (and the reader's scroll
30
+ * position) untouched while producing a byte-identical final screen state.
31
+ * ED 2 is avoided entirely because conhost/Windows Terminal implement it by
32
+ * scrolling the screen contents into scrollback rather than erasing in
33
+ * place. Width-change redraws keep the upstream wipe because
34
+ * re-wrapped content genuinely invalidates old scrollback, and frames
35
+ * containing kitty graphics pass through untouched. A frame that does not
36
+ * match the expected shape is passed through unchanged, so if upstream
37
+ * reshapes its render output the wrapper degrades back to stock behavior.
38
+ */
39
+ export declare class CoalescingTerminal extends ProcessTerminal {
40
+ private pending;
41
+ private flushQueued;
42
+ private lastWriteColumns;
43
+ private flushOnExit;
44
+ constructor();
45
+ write(data: string): void;
46
+ moveBy(lines: number): void;
47
+ hideCursor(): void;
48
+ showCursor(): void;
49
+ clearLine(): void;
50
+ clearFromCursor(): void;
51
+ clearScreen(): void;
52
+ stop(): void;
53
+ /**
54
+ * Rewrite a full-redraw frame (`?2026h 2J H 3J <lines> ?2026l`) so it
55
+ * repaints the screen without wiping scrollback. Anything that does not
56
+ * match this exact shape — including width-change redraws and frames
57
+ * touching kitty images — passes through unchanged.
58
+ */
59
+ private preserveScrollback;
60
+ private flush;
61
+ }
62
+ //# sourceMappingURL=coalescing-terminal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coalescing-terminal.d.ts","sourceRoot":"","sources":["../../src/utils/coalescing-terminal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AASzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,qBAAa,kBAAmB,SAAQ,eAAe;IACtD,OAAO,CAAC,OAAO,CAAM;IACrB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,WAAW,CAAsB;IAEzC,cAKC;IAEQ,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAMjC;IAIQ,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAMnC;IAEQ,UAAU,IAAI,IAAI,CAE1B;IAEQ,UAAU,IAAI,IAAI,CAE1B;IAEQ,SAAS,IAAI,IAAI,CAEzB;IAEQ,eAAe,IAAI,IAAI,CAE/B;IAEQ,WAAW,IAAI,IAAI,CAE3B;IAEQ,IAAI,IAAI,IAAI,CAMpB;IAED;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IAwC1B,OAAO,CAAC,KAAK;CASb","sourcesContent":["import { ProcessTerminal } from \"@earendil-works/pi-tui\";\n\nconst SYNC_START = \"\\x1b[?2026h\";\nconst SYNC_END = \"\\x1b[?2026l\";\n/** Clear screen, home, clear scrollback — pi-tui's fullRender(clear) preamble. */\nconst CLEAR_WITH_SCROLLBACK = \"\\x1b[2J\\x1b[H\\x1b[3J\";\n/** Kitty graphics APC prefix; frames touching images interleave cursor moves. */\nconst KITTY_APC_PREFIX = \"\\x1b_G\";\n\n/**\n * ProcessTerminal that (1) coalesces every write issued within one synchronous\n * task into a single stdout write and (2) keeps content-driven full redraws\n * from destroying the terminal's scrollback.\n *\n * Coalescing: pi-tui emits each render pass as several separate stdout\n * writes: the frame wrapped in synchronized output (CSI ?2026h/l), then a\n * relative cursor move that parks the hardware cursor at the focused\n * component's caret for IME composition, then a cursor visibility toggle.\n * Terminals that anchor the IME candidate window to the hardware cursor\n * (Windows Terminal and other ConPTY hosts, WezTerm) can process the frame\n * write before the reposition write arrives, briefly leaving the cursor at\n * the end of the last repainted line. With an IME active this makes the\n * candidate window jump between the caret and the right edge of the input\n * line on every keystroke. Merging the writes makes each frame atomic, so the\n * only cursor position a terminal can observe is the final one.\n *\n * Scrollback preservation: pi-tui's fullRender(clear) opens with\n * `2J H 3J`, wiping the terminal's scrollback, and then replays the entire\n * transcript. Content-driven triggers (a line changing above the viewport\n * during streaming markdown reflow, large content shrinks) fire this\n * routinely, which erases pre-session shell history and — in terminals that\n * clamp the scroll offset when scrollback vanishes, notably Windows\n * Terminal — yanks a scrolled-up viewport to the top of the buffer\n * (upstream #6502, #5576, #6050; rejected upstream fix #4204). Frames\n * matching that shape are rewritten to overwrite the visible screen in place\n * (home, erase-and-rewrite each row, erase below) with only the bottom\n * viewport-height rows, which leaves scrollback (and the reader's scroll\n * position) untouched while producing a byte-identical final screen state.\n * ED 2 is avoided entirely because conhost/Windows Terminal implement it by\n * scrolling the screen contents into scrollback rather than erasing in\n * place. Width-change redraws keep the upstream wipe because\n * re-wrapped content genuinely invalidates old scrollback, and frames\n * containing kitty graphics pass through untouched. A frame that does not\n * match the expected shape is passed through unchanged, so if upstream\n * reshapes its render output the wrapper degrades back to stock behavior.\n */\nexport class CoalescingTerminal extends ProcessTerminal {\n\tprivate pending = \"\";\n\tprivate flushQueued = false;\n\tprivate lastWriteColumns: number | undefined;\n\tprivate flushOnExit = () => this.flush();\n\n\tconstructor() {\n\t\tsuper();\n\t\t// A synchronous process.exit() before the queued microtask runs would\n\t\t// otherwise drop the final frame.\n\t\tprocess.on(\"exit\", this.flushOnExit);\n\t}\n\n\toverride write(data: string): void {\n\t\tif (data.length === 0) return;\n\t\tthis.pending += this.preserveScrollback(data);\n\t\tif (this.flushQueued) return;\n\t\tthis.flushQueued = true;\n\t\tqueueMicrotask(() => this.flush());\n\t}\n\n\t// Route every escape-sequence helper through the coalescing buffer so a\n\t// caller mixing helpers with write() keeps its output ordered.\n\toverride moveBy(lines: number): void {\n\t\tif (lines > 0) {\n\t\t\tthis.write(`\\x1b[${lines}B`);\n\t\t} else if (lines < 0) {\n\t\t\tthis.write(`\\x1b[${-lines}A`);\n\t\t}\n\t}\n\n\toverride hideCursor(): void {\n\t\tthis.write(\"\\x1b[?25l\");\n\t}\n\n\toverride showCursor(): void {\n\t\tthis.write(\"\\x1b[?25h\");\n\t}\n\n\toverride clearLine(): void {\n\t\tthis.write(\"\\x1b[K\");\n\t}\n\n\toverride clearFromCursor(): void {\n\t\tthis.write(\"\\x1b[J\");\n\t}\n\n\toverride clearScreen(): void {\n\t\tthis.write(\"\\x1b[2J\\x1b[H\");\n\t}\n\n\toverride stop(): void {\n\t\t// ProcessTerminal.stop() writes its teardown sequences directly to\n\t\t// stdout, so pending frame data must go out first.\n\t\tthis.flush();\n\t\tprocess.removeListener(\"exit\", this.flushOnExit);\n\t\tsuper.stop();\n\t}\n\n\t/**\n\t * Rewrite a full-redraw frame (`?2026h 2J H 3J <lines> ?2026l`) so it\n\t * repaints the screen without wiping scrollback. Anything that does not\n\t * match this exact shape — including width-change redraws and frames\n\t * touching kitty images — passes through unchanged.\n\t */\n\tprivate preserveScrollback(data: string): string {\n\t\tconst columns = this.columns;\n\t\tconst widthChanged = this.lastWriteColumns !== undefined && this.lastWriteColumns !== columns;\n\t\tthis.lastWriteColumns = columns;\n\n\t\tconst prefix = SYNC_START + CLEAR_WITH_SCROLLBACK;\n\t\tif (!data.startsWith(prefix) || !data.endsWith(SYNC_END)) {\n\t\t\treturn data;\n\t\t}\n\t\t// Re-wrapped content invalidates what scrollback holds; keep the wipe.\n\t\tif (widthChanged) {\n\t\t\treturn data;\n\t\t}\n\t\tif (data.includes(KITTY_APC_PREFIX)) {\n\t\t\treturn data;\n\t\t}\n\t\tconst body = data.slice(prefix.length, data.length - SYNC_END.length);\n\t\t// A second clear inside one write means the frame shape changed\n\t\t// upstream; bail out rather than guess.\n\t\tif (body.includes(CLEAR_WITH_SCROLLBACK)) {\n\t\t\treturn data;\n\t\t}\n\t\t// Repaint only the bottom viewport-height rows, overwriting the screen\n\t\t// in place: home, erase-and-rewrite each row, erase whatever remains\n\t\t// below. Writing the full transcript after a screen clear would push a\n\t\t// duplicate copy of everything above the viewport into scrollback; the\n\t\t// truncated overwrite produces the exact same final screen and cursor\n\t\t// position as the full replay (rows beyond the screen would have\n\t\t// scrolled out anyway). ED 2 (`2J`) is deliberately avoided even for\n\t\t// the visible screen: conhost/Windows Terminal implement it by\n\t\t// scrolling the current screen into scrollback (cls compatibility), so\n\t\t// without the `3J` wipe each redraw would stack another screenful of\n\t\t// duplicates into history.\n\t\tconst lines = body.split(\"\\r\\n\");\n\t\tconst rows = this.rows;\n\t\tconst kept = lines.length > rows ? lines.slice(lines.length - rows) : lines;\n\t\tconst repaint = kept.map((line) => `\\x1b[2K${line}`).join(\"\\r\\n\");\n\t\treturn `${SYNC_START}\\x1b[H${repaint}\\x1b[J${SYNC_END}`;\n\t}\n\n\tprivate flush(): void {\n\t\tthis.flushQueued = false;\n\t\tif (this.pending.length === 0) {\n\t\t\treturn;\n\t\t}\n\t\tconst data = this.pending;\n\t\tthis.pending = \"\";\n\t\tsuper.write(data);\n\t}\n}\n"]}
@@ -0,0 +1,151 @@
1
+ import { ProcessTerminal } from "@earendil-works/pi-tui";
2
+ const SYNC_START = "\x1b[?2026h";
3
+ const SYNC_END = "\x1b[?2026l";
4
+ /** Clear screen, home, clear scrollback — pi-tui's fullRender(clear) preamble. */
5
+ const CLEAR_WITH_SCROLLBACK = "\x1b[2J\x1b[H\x1b[3J";
6
+ /** Kitty graphics APC prefix; frames touching images interleave cursor moves. */
7
+ const KITTY_APC_PREFIX = "\x1b_G";
8
+ /**
9
+ * ProcessTerminal that (1) coalesces every write issued within one synchronous
10
+ * task into a single stdout write and (2) keeps content-driven full redraws
11
+ * from destroying the terminal's scrollback.
12
+ *
13
+ * Coalescing: pi-tui emits each render pass as several separate stdout
14
+ * writes: the frame wrapped in synchronized output (CSI ?2026h/l), then a
15
+ * relative cursor move that parks the hardware cursor at the focused
16
+ * component's caret for IME composition, then a cursor visibility toggle.
17
+ * Terminals that anchor the IME candidate window to the hardware cursor
18
+ * (Windows Terminal and other ConPTY hosts, WezTerm) can process the frame
19
+ * write before the reposition write arrives, briefly leaving the cursor at
20
+ * the end of the last repainted line. With an IME active this makes the
21
+ * candidate window jump between the caret and the right edge of the input
22
+ * line on every keystroke. Merging the writes makes each frame atomic, so the
23
+ * only cursor position a terminal can observe is the final one.
24
+ *
25
+ * Scrollback preservation: pi-tui's fullRender(clear) opens with
26
+ * `2J H 3J`, wiping the terminal's scrollback, and then replays the entire
27
+ * transcript. Content-driven triggers (a line changing above the viewport
28
+ * during streaming markdown reflow, large content shrinks) fire this
29
+ * routinely, which erases pre-session shell history and — in terminals that
30
+ * clamp the scroll offset when scrollback vanishes, notably Windows
31
+ * Terminal — yanks a scrolled-up viewport to the top of the buffer
32
+ * (upstream #6502, #5576, #6050; rejected upstream fix #4204). Frames
33
+ * matching that shape are rewritten to overwrite the visible screen in place
34
+ * (home, erase-and-rewrite each row, erase below) with only the bottom
35
+ * viewport-height rows, which leaves scrollback (and the reader's scroll
36
+ * position) untouched while producing a byte-identical final screen state.
37
+ * ED 2 is avoided entirely because conhost/Windows Terminal implement it by
38
+ * scrolling the screen contents into scrollback rather than erasing in
39
+ * place. Width-change redraws keep the upstream wipe because
40
+ * re-wrapped content genuinely invalidates old scrollback, and frames
41
+ * containing kitty graphics pass through untouched. A frame that does not
42
+ * match the expected shape is passed through unchanged, so if upstream
43
+ * reshapes its render output the wrapper degrades back to stock behavior.
44
+ */
45
+ export class CoalescingTerminal extends ProcessTerminal {
46
+ pending = "";
47
+ flushQueued = false;
48
+ lastWriteColumns;
49
+ flushOnExit = () => this.flush();
50
+ constructor() {
51
+ super();
52
+ // A synchronous process.exit() before the queued microtask runs would
53
+ // otherwise drop the final frame.
54
+ process.on("exit", this.flushOnExit);
55
+ }
56
+ write(data) {
57
+ if (data.length === 0)
58
+ return;
59
+ this.pending += this.preserveScrollback(data);
60
+ if (this.flushQueued)
61
+ return;
62
+ this.flushQueued = true;
63
+ queueMicrotask(() => this.flush());
64
+ }
65
+ // Route every escape-sequence helper through the coalescing buffer so a
66
+ // caller mixing helpers with write() keeps its output ordered.
67
+ moveBy(lines) {
68
+ if (lines > 0) {
69
+ this.write(`\x1b[${lines}B`);
70
+ }
71
+ else if (lines < 0) {
72
+ this.write(`\x1b[${-lines}A`);
73
+ }
74
+ }
75
+ hideCursor() {
76
+ this.write("\x1b[?25l");
77
+ }
78
+ showCursor() {
79
+ this.write("\x1b[?25h");
80
+ }
81
+ clearLine() {
82
+ this.write("\x1b[K");
83
+ }
84
+ clearFromCursor() {
85
+ this.write("\x1b[J");
86
+ }
87
+ clearScreen() {
88
+ this.write("\x1b[2J\x1b[H");
89
+ }
90
+ stop() {
91
+ // ProcessTerminal.stop() writes its teardown sequences directly to
92
+ // stdout, so pending frame data must go out first.
93
+ this.flush();
94
+ process.removeListener("exit", this.flushOnExit);
95
+ super.stop();
96
+ }
97
+ /**
98
+ * Rewrite a full-redraw frame (`?2026h 2J H 3J <lines> ?2026l`) so it
99
+ * repaints the screen without wiping scrollback. Anything that does not
100
+ * match this exact shape — including width-change redraws and frames
101
+ * touching kitty images — passes through unchanged.
102
+ */
103
+ preserveScrollback(data) {
104
+ const columns = this.columns;
105
+ const widthChanged = this.lastWriteColumns !== undefined && this.lastWriteColumns !== columns;
106
+ this.lastWriteColumns = columns;
107
+ const prefix = SYNC_START + CLEAR_WITH_SCROLLBACK;
108
+ if (!data.startsWith(prefix) || !data.endsWith(SYNC_END)) {
109
+ return data;
110
+ }
111
+ // Re-wrapped content invalidates what scrollback holds; keep the wipe.
112
+ if (widthChanged) {
113
+ return data;
114
+ }
115
+ if (data.includes(KITTY_APC_PREFIX)) {
116
+ return data;
117
+ }
118
+ const body = data.slice(prefix.length, data.length - SYNC_END.length);
119
+ // A second clear inside one write means the frame shape changed
120
+ // upstream; bail out rather than guess.
121
+ if (body.includes(CLEAR_WITH_SCROLLBACK)) {
122
+ return data;
123
+ }
124
+ // Repaint only the bottom viewport-height rows, overwriting the screen
125
+ // in place: home, erase-and-rewrite each row, erase whatever remains
126
+ // below. Writing the full transcript after a screen clear would push a
127
+ // duplicate copy of everything above the viewport into scrollback; the
128
+ // truncated overwrite produces the exact same final screen and cursor
129
+ // position as the full replay (rows beyond the screen would have
130
+ // scrolled out anyway). ED 2 (`2J`) is deliberately avoided even for
131
+ // the visible screen: conhost/Windows Terminal implement it by
132
+ // scrolling the current screen into scrollback (cls compatibility), so
133
+ // without the `3J` wipe each redraw would stack another screenful of
134
+ // duplicates into history.
135
+ const lines = body.split("\r\n");
136
+ const rows = this.rows;
137
+ const kept = lines.length > rows ? lines.slice(lines.length - rows) : lines;
138
+ const repaint = kept.map((line) => `\x1b[2K${line}`).join("\r\n");
139
+ return `${SYNC_START}\x1b[H${repaint}\x1b[J${SYNC_END}`;
140
+ }
141
+ flush() {
142
+ this.flushQueued = false;
143
+ if (this.pending.length === 0) {
144
+ return;
145
+ }
146
+ const data = this.pending;
147
+ this.pending = "";
148
+ super.write(data);
149
+ }
150
+ }
151
+ //# sourceMappingURL=coalescing-terminal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coalescing-terminal.js","sourceRoot":"","sources":["../../src/utils/coalescing-terminal.ts"],"names":[],"mappings":"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","sourcesContent":["import { ProcessTerminal } from \"@earendil-works/pi-tui\";\n\nconst SYNC_START = \"\\x1b[?2026h\";\nconst SYNC_END = \"\\x1b[?2026l\";\n/** Clear screen, home, clear scrollback — pi-tui's fullRender(clear) preamble. */\nconst CLEAR_WITH_SCROLLBACK = \"\\x1b[2J\\x1b[H\\x1b[3J\";\n/** Kitty graphics APC prefix; frames touching images interleave cursor moves. */\nconst KITTY_APC_PREFIX = \"\\x1b_G\";\n\n/**\n * ProcessTerminal that (1) coalesces every write issued within one synchronous\n * task into a single stdout write and (2) keeps content-driven full redraws\n * from destroying the terminal's scrollback.\n *\n * Coalescing: pi-tui emits each render pass as several separate stdout\n * writes: the frame wrapped in synchronized output (CSI ?2026h/l), then a\n * relative cursor move that parks the hardware cursor at the focused\n * component's caret for IME composition, then a cursor visibility toggle.\n * Terminals that anchor the IME candidate window to the hardware cursor\n * (Windows Terminal and other ConPTY hosts, WezTerm) can process the frame\n * write before the reposition write arrives, briefly leaving the cursor at\n * the end of the last repainted line. With an IME active this makes the\n * candidate window jump between the caret and the right edge of the input\n * line on every keystroke. Merging the writes makes each frame atomic, so the\n * only cursor position a terminal can observe is the final one.\n *\n * Scrollback preservation: pi-tui's fullRender(clear) opens with\n * `2J H 3J`, wiping the terminal's scrollback, and then replays the entire\n * transcript. Content-driven triggers (a line changing above the viewport\n * during streaming markdown reflow, large content shrinks) fire this\n * routinely, which erases pre-session shell history and — in terminals that\n * clamp the scroll offset when scrollback vanishes, notably Windows\n * Terminal — yanks a scrolled-up viewport to the top of the buffer\n * (upstream #6502, #5576, #6050; rejected upstream fix #4204). Frames\n * matching that shape are rewritten to overwrite the visible screen in place\n * (home, erase-and-rewrite each row, erase below) with only the bottom\n * viewport-height rows, which leaves scrollback (and the reader's scroll\n * position) untouched while producing a byte-identical final screen state.\n * ED 2 is avoided entirely because conhost/Windows Terminal implement it by\n * scrolling the screen contents into scrollback rather than erasing in\n * place. Width-change redraws keep the upstream wipe because\n * re-wrapped content genuinely invalidates old scrollback, and frames\n * containing kitty graphics pass through untouched. A frame that does not\n * match the expected shape is passed through unchanged, so if upstream\n * reshapes its render output the wrapper degrades back to stock behavior.\n */\nexport class CoalescingTerminal extends ProcessTerminal {\n\tprivate pending = \"\";\n\tprivate flushQueued = false;\n\tprivate lastWriteColumns: number | undefined;\n\tprivate flushOnExit = () => this.flush();\n\n\tconstructor() {\n\t\tsuper();\n\t\t// A synchronous process.exit() before the queued microtask runs would\n\t\t// otherwise drop the final frame.\n\t\tprocess.on(\"exit\", this.flushOnExit);\n\t}\n\n\toverride write(data: string): void {\n\t\tif (data.length === 0) return;\n\t\tthis.pending += this.preserveScrollback(data);\n\t\tif (this.flushQueued) return;\n\t\tthis.flushQueued = true;\n\t\tqueueMicrotask(() => this.flush());\n\t}\n\n\t// Route every escape-sequence helper through the coalescing buffer so a\n\t// caller mixing helpers with write() keeps its output ordered.\n\toverride moveBy(lines: number): void {\n\t\tif (lines > 0) {\n\t\t\tthis.write(`\\x1b[${lines}B`);\n\t\t} else if (lines < 0) {\n\t\t\tthis.write(`\\x1b[${-lines}A`);\n\t\t}\n\t}\n\n\toverride hideCursor(): void {\n\t\tthis.write(\"\\x1b[?25l\");\n\t}\n\n\toverride showCursor(): void {\n\t\tthis.write(\"\\x1b[?25h\");\n\t}\n\n\toverride clearLine(): void {\n\t\tthis.write(\"\\x1b[K\");\n\t}\n\n\toverride clearFromCursor(): void {\n\t\tthis.write(\"\\x1b[J\");\n\t}\n\n\toverride clearScreen(): void {\n\t\tthis.write(\"\\x1b[2J\\x1b[H\");\n\t}\n\n\toverride stop(): void {\n\t\t// ProcessTerminal.stop() writes its teardown sequences directly to\n\t\t// stdout, so pending frame data must go out first.\n\t\tthis.flush();\n\t\tprocess.removeListener(\"exit\", this.flushOnExit);\n\t\tsuper.stop();\n\t}\n\n\t/**\n\t * Rewrite a full-redraw frame (`?2026h 2J H 3J <lines> ?2026l`) so it\n\t * repaints the screen without wiping scrollback. Anything that does not\n\t * match this exact shape — including width-change redraws and frames\n\t * touching kitty images — passes through unchanged.\n\t */\n\tprivate preserveScrollback(data: string): string {\n\t\tconst columns = this.columns;\n\t\tconst widthChanged = this.lastWriteColumns !== undefined && this.lastWriteColumns !== columns;\n\t\tthis.lastWriteColumns = columns;\n\n\t\tconst prefix = SYNC_START + CLEAR_WITH_SCROLLBACK;\n\t\tif (!data.startsWith(prefix) || !data.endsWith(SYNC_END)) {\n\t\t\treturn data;\n\t\t}\n\t\t// Re-wrapped content invalidates what scrollback holds; keep the wipe.\n\t\tif (widthChanged) {\n\t\t\treturn data;\n\t\t}\n\t\tif (data.includes(KITTY_APC_PREFIX)) {\n\t\t\treturn data;\n\t\t}\n\t\tconst body = data.slice(prefix.length, data.length - SYNC_END.length);\n\t\t// A second clear inside one write means the frame shape changed\n\t\t// upstream; bail out rather than guess.\n\t\tif (body.includes(CLEAR_WITH_SCROLLBACK)) {\n\t\t\treturn data;\n\t\t}\n\t\t// Repaint only the bottom viewport-height rows, overwriting the screen\n\t\t// in place: home, erase-and-rewrite each row, erase whatever remains\n\t\t// below. Writing the full transcript after a screen clear would push a\n\t\t// duplicate copy of everything above the viewport into scrollback; the\n\t\t// truncated overwrite produces the exact same final screen and cursor\n\t\t// position as the full replay (rows beyond the screen would have\n\t\t// scrolled out anyway). ED 2 (`2J`) is deliberately avoided even for\n\t\t// the visible screen: conhost/Windows Terminal implement it by\n\t\t// scrolling the current screen into scrollback (cls compatibility), so\n\t\t// without the `3J` wipe each redraw would stack another screenful of\n\t\t// duplicates into history.\n\t\tconst lines = body.split(\"\\r\\n\");\n\t\tconst rows = this.rows;\n\t\tconst kept = lines.length > rows ? lines.slice(lines.length - rows) : lines;\n\t\tconst repaint = kept.map((line) => `\\x1b[2K${line}`).join(\"\\r\\n\");\n\t\treturn `${SYNC_START}\\x1b[H${repaint}\\x1b[J${SYNC_END}`;\n\t}\n\n\tprivate flush(): void {\n\t\tthis.flushQueued = false;\n\t\tif (this.pending.length === 0) {\n\t\t\treturn;\n\t\t}\n\t\tconst data = this.pending;\n\t\tthis.pending = \"\";\n\t\tsuper.write(data);\n\t}\n}\n"]}
@@ -0,0 +1,38 @@
1
+ # Bundled features
2
+
3
+ The installed `@astralyn/pi` package includes a small set of workflows and presentation choices on top of its coding-agent behavior. Upstream Pi release tags are synchronization input, not a separate user documentation set.
4
+
5
+ ## Extensions
6
+
7
+ The following extensions are loaded as hidden built-ins. They use the same public Extension API available to external extensions.
8
+
9
+ | Extension | Tool or command | Purpose |
10
+ |---|---|---|
11
+ | `llama.cpp` | `/llama` | Manage models served by the local llama.cpp router |
12
+ | `deepwiki` | `deepwiki` | Query indexed public GitHub repository documentation |
13
+ | `question` | `question` | Ask structured questions through native interactive UI |
14
+ | `rewind` | `/rewind` | Create and restore project snapshots around session lifecycle operations |
15
+ | `router` | `/router` | Configure and probe Codex-style routing endpoints |
16
+ | `statusline` | Footer status | Show concise extension-managed activity state |
17
+ | `subagent` | `subagent`, `/agents` | Delegate bounded work to isolated child Pi sessions |
18
+ | `todo` | `todo`, `/todos` | Track multi-step work with dependencies and status |
19
+
20
+ See the [bundled extension catalog](extensions/README.md) for configuration and behavior details.
21
+
22
+ ## Context safety
23
+
24
+ Long tool loops are checked after a completed tool batch and before another provider request. When the active context crosses the configured auto-compaction threshold, Pi compacts and rebuilds the context before continuing the same run. Cancellation, compaction failure, an unavailable cut point, or retained context that remains unsafe stops before another provider request.
25
+
26
+ See [Compaction](../compaction.md).
27
+
28
+ ## Tool presentation
29
+
30
+ Native tool calls use a consistent `●` call and `│` result shell with bounded collapsed output. Built-in semantic renderers remain responsible for paths, diffs, syntax highlighting, command output, and images.
31
+
32
+ See [Native tool presentation](tool-presentation.md).
33
+
34
+ ## Themes
35
+
36
+ The package includes `ice-cream-dark` and `ice-cream-light` alongside the standard `dark` and `light` themes.
37
+
38
+ See [Bundled themes](themes.md) and [Theme authoring](../themes.md).
@@ -0,0 +1,19 @@
1
+ # Bundled extensions
2
+
3
+ These are extensions shipped in the `@astralyn/pi` npm package. For the public API used to write external extensions, see the [Extension API documentation](../../extensions.md).
4
+
5
+ They are registered as hidden built-in extensions and use the same public Extension API available to external extensions. Their implementation details are internal to the package and are not required for installation or configuration.
6
+
7
+ | Extension | Tool or command | Documentation |
8
+ |---|---|---|
9
+ | `llama.cpp` | `/llama` | [llama.cpp](../../llama-cpp.md) |
10
+ | `deepwiki` | `deepwiki` | [deepwiki.md](deepwiki.md) |
11
+ | `plan` | `exit_plan`, `/plan` | [plan.md](plan.md) |
12
+ | `question` | `question` | [question.md](question.md) |
13
+ | `todo` | `todo`, `/todos` | [todo.md](todo.md) |
14
+ | `rewind` | `/rewind`, `/tree` lifecycle | [rewind.md](rewind.md) |
15
+ | `router` | `/router` | [router.md](router.md) |
16
+ | `statusline` | Footer | [statusline.md](statusline.md) |
17
+ | `subagent` | `subagent`, `/agents` | [subagent.md](subagent.md) |
18
+
19
+ The catalog documents installed-package behavior and configuration; it does not require access to the package's internal source.
@@ -0,0 +1,31 @@
1
+ # plan — read-only plan mode
2
+
3
+ Adds `/plan` for a Claude-Code-style planning phase: the model explores read-only, interviews the user, and leaves the mode through a user-approved `exit_plan` tool call that saves the plan to disk and optionally compacts the context before execution starts.
4
+
5
+ ## Behavior
6
+
7
+ - `/plan` (or the `--plan` CLI flag) toggles plan mode. On entry the built-in `edit`, `write`, and `bash` tools and the bundled `subagent` tool are deactivated — `subagent` because child sessions do not inherit the restricted tool set. Read-only built-ins and other extension tools (`question`, `todo`, `deepwiki`, …) stay available. A `tool_call` guard also blocks the four tools for requests already in flight when the mode toggles.
8
+ - While planning, the system prompt gains a plan-mode block: explore before asking, interview one decision at a time with the `question` tool (with a recommendation and trade-offs), skip the interview for small clear tasks, then present the full plan as response text before calling `exit_plan`.
9
+ - `exit_plan(title, plan, revises?)` opens the approval menu:
10
+ - **Start executing (keep full context)** — saves the plan, restores tools, and execution continues in the same run.
11
+ - **Compact context, then execute** — saves the plan, ends the run (`terminate`), compacts once the run has settled, and only then restores tools; execution restarts via a kickoff message that embeds the plan text, so continuation never depends on the compaction summary. If compaction fails, tools are restored and execution proceeds with full context.
12
+ - **Keep planning** — optionally collects feedback (multi-line editor in the TUI, input dialog in RPC) and returns it to the model; plan mode stays active. `Esc` and dialog timeouts map here, never to an executing option.
13
+ - Both executing options save the plan file. If the session has no name yet, the plan title becomes the session name.
14
+ - `/plan` while planning exits directly without a menu or a file — the plan text is still in context. During a pending compaction the toggle is refused until the compaction settles.
15
+ - Plan files live under `<agentDir>/plans/<sessionId>/NN-<slug>.md` (`~/.pi/agent/plans/…` by default, `PI_CODING_AGENT_DIR` respected) with `title`, `created`, `cwd`, `session`, and optional `revises` frontmatter. Numbering is append-only; revisions are new files that point back via `revises`.
16
+ - To revise a plan mid-execution, re-enter with `/plan`: the system prompt lists the branch's saved plans so the model reads the latest one, accounts for completed work, and produces a revision.
17
+
18
+ ## Limits
19
+
20
+ - The kickoff message embeds at most 24 000 characters of plan markdown; longer plans are truncated with a pointer to the full file.
21
+ - In `json`/`print` modes `exit_plan` cannot ask, so it saves the plan and exits with context kept (write access is restored without a menu).
22
+ - Approving with **Compact context, then execute** and then interrupting before compaction completes (quit, resume, branch switch) degrades to the keep-context exit on the next session start.
23
+
24
+ ## Implementation notes
25
+
26
+ - **Conversation-backed state.** Every transition appends a `plan-mode` custom entry (never sent to the LLM); lifecycle handlers replay the current branch tail → head, so `/reload`, `/tree`, resume, and fork restore the mode. Fork and clone copy entries, so the recorded plan-file paths follow the new session even though it gets a fresh session id — the `<sessionId>` directory is only the initial storage location, not the index.
27
+ - **State is keyed per session id** (todo pattern): resume and `/tree` can switch sessions within one process, and handlers re-point the active bucket before touching state.
28
+ - **Tool restore is an inverse operation, not a snapshot.** Entry records which tools were actually removed; exit re-adds exactly those, so tools other extensions activate during planning are not revoked and tools the user had disabled are not resurrected.
29
+ - **No write access is ever needed for the plan itself** — the plan text travels as an `exit_plan` parameter and the extension writes the file.
30
+ - **The compact-then-execute window is closed by construction**: plan mode (and its tool restrictions) persists until compaction completes; the run is terminated by the tool result and the compaction starts on `agent_settled`, which fires once per fully settled run.
31
+ - The `exit_plan` result text authoritatively announces mode changes because the per-run system prompt override is not rebuilt mid-run.
@@ -46,14 +46,3 @@ The transcript uses a private `renderCall` / `renderResult` only to replace raw
46
46
  - `validateQuestions` enforces uniqueness, reserved-label rejection, and text budgets beyond what the JSON schema can express.
47
47
  - Dialog navigation follows Pi's injected select/input keybindings where applicable; custom actions such as Space-to-toggle remain explicit in the footer.
48
48
 
49
- **Files:**
50
-
51
- - `constants.ts` — tool identity and model-facing prompt copy
52
- - `index.ts` — tool registration and execution
53
- - `dialog.ts` — interactive dialog, review, and discussion flow
54
- - `schema.ts` — params and semantic validation
55
- - `state.ts` — per-question dialog state helpers and answer ordering
56
- - `results.ts` — bounded model-result builders
57
- - `render.ts` — compact transcript summaries within Pi's native tool frame
58
- - `limits.ts` — UI and model-output budgets
59
- - `types.ts` — shared interfaces and the `OTHER_OPTION` sentinel
@@ -13,7 +13,11 @@ Per turn: `before_agent_start` opens a snapshot frame (re-recording every tracke
13
13
  file at its turn-start state, reusing the latest backup when unchanged);
14
14
  `tool_call(edit|write)` backs up each newly edited file *before* it lands;
15
15
  `agent_settled` persists the frame to the session JSONL as a `pi-rewind-snapshot`
16
- custom entry **only when files changed**. Using `agent_settled` (not `agent_end`)
16
+ custom entry **only when files changed**. The frame anchors to the **first** user
17
+ entry the run appended — the message whose start the frame recorded; steering and
18
+ follow-up messages consumed inside the same run append later user entries and do
19
+ not steal the anchor (a run that appends no user entry, e.g. custom-triggered,
20
+ records no frame). Using `agent_settled` (not `agent_end`)
17
21
  keeps auto-retry, overflow compaction-retry, and queued follow-ups in one logical
18
22
  turn — `agent_end` can fire while Pi still continues. Requires **Pi ≥ 0.80.4**
19
23
  (when `agent_settled` was added); older hosts never fire that event, so frames
@@ -24,18 +28,25 @@ rebuilt from them on `session_start`.
24
28
  `edit` and `write` tools. Files written by `bash` (redirects, codegen, `mv`) or
25
29
  edited by hand outside Pi are **not** tracked — same boundary as Claude Code's
26
30
  file-history. Rewind undoes *Pi's edits*, not arbitrary filesystem state.
27
- - **Time-travel is via `/tree`.** Navigating to a node whose turn changed files
28
- prompts to restore them, listing the affected files (cwd-relative, up to 8
29
- then *"+N more"*) under *"Restore 3 files to this point? (+120 / −40)"*
30
- when coarse line stats are available; choosing yes restores the work tree to
31
- that turn's start state. Nodes with no file changes navigate silently. Only
32
- files that actually differ are rewritten. Line totals are a bag-of-lines
31
+ - **Time-travel is via `/tree`.** Navigating to a node whose recorded state
32
+ differs from the work tree prompts to restore, listing the affected files
33
+ (cwd-relative, up to 8 then *"+N more"*) under *"Restore 3 files to this
34
+ point? (+120 / −40)"* when coarse line stats are available. The restore
35
+ target mirrors `/tree`'s own semantics: a **user message** target (whose turn
36
+ is removed and returned to the editor) restores to the state *before* that
37
+ turn ran; any **other** target (whose turn stays in the conversation)
38
+ restores to the state *after* that turn — the state recorded when the next
39
+ run began. When nothing was recorded after the target (e.g. the last turn's
40
+ assistant message), navigation is silent rather than rolling back edits the
41
+ conversation still contains. Nodes with no file changes navigate silently.
42
+ Only files that actually differ are rewritten. Line totals are a bag-of-lines
33
43
  estimate (capped at 1 MB per file), not a full Myers diff.
34
44
  - **`/rewind` is a settings + storage menu**, not a restore picker:
35
45
  - toggle rewind on/off,
36
46
  - set the auto-clean retention window,
37
47
  - inspect storage and prune (clean aged + orphaned / remove orphaned / remove
38
- all except current).
48
+ all except current — the latter also keeps backups written to in the last
49
+ 10 minutes, which may belong to sessions running in another Pi process).
39
50
  - **New files** created by `write` are tracked with a "did not exist" marker, so
40
51
  rewinding deletes them.
41
52
  - **Resume/fork** hard-links the prior session's backup blobs into the new
@@ -71,15 +82,5 @@ rebuilt from them on `session_start`.
71
82
  - **Config is memory-cached.** `config.json` is re-read on `session_start` and updated in memory when `/rewind` saves.
72
83
  - **Restore path is single-scan.** The `/tree` confirm pass caches changed absolute paths; IO is concurrency-capped (16).
73
84
 
74
- **Files:**
75
-
76
- - `index.ts` — lifecycle hooks (`tool_call`, `before_agent_start`, `agent_settled`, `session_start`, `session_before_tree`/`session_tree`, `session_shutdown`) and the `/rewind` command
77
- - `engine.ts` — the file-history backup engine (track / snapshot / apply / resume-migrate), ported from Claude Code's file-history
78
- - `snapshot.ts` — persisted snapshot data shapes (pure)
79
- - `config.ts` — load/save `rewind/config.json`
80
- - `gc.ts` — age + orphan storage reclamation; storage inventory for the menu
81
- - `menu.ts` — the `/rewind` settings + storage menu
82
- - `restore.ts` — `/tree`-target → snapshot matching and restore
83
- - `paths.ts`, `text.ts`, `tool-path.ts` — private helpers owned by this plugin
84
85
 
85
86
  Architecture informed by oh-my-pi (GPL-3.0) and Claude Code's file-history; independent implementation.
@@ -4,7 +4,7 @@
4
4
 
5
5
  Configuration lives at `~/.pi/agent/router.json`.
6
6
 
7
- Providers are registered at extension load via `pi.registerProvider` (config form + `streamSimple`). They do **not** go through `models.json`. This follows Pi's documented custom-provider path ([providers.md](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/providers.md), [custom-provider.md](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/custom-provider.md)): the stream wraps the built-in `openAIResponsesApi()` from `@earendil-works/pi-ai/compat` (same approach as the GitLab Duo example), then reshapes the request for Codex-style relays.
7
+ Providers are registered at extension load via `pi.registerProvider` (config form + `streamSimple`). They do **not** go through `models.json`. This follows Pi's documented custom-provider path ([providers.md](../../providers.md), [custom-provider.md](../../custom-provider.md)): the stream wraps the built-in `openAIResponsesApi()` from `@earendil-works/pi-ai/compat` (same approach as the GitLab Duo example), then reshapes the request for Codex-style relays.
8
8
 
9
9
  ---
10
10
 
@@ -123,17 +123,4 @@ Session affinity headers use hyphenated form (`session-id`, `x-client-request-id
123
123
 
124
124
  **Multi-turn tool calls.** On a fixed relay + fixed model, tool-call ids preserve the Responses form and replay like Codex. Pi only rewrites ids across model boundaries. Staying on one model for long tool + reasoning sessions avoids id sanitization.
125
125
 
126
- **Files:**
127
126
 
128
- | File | Role |
129
- |:-:|:-:|
130
- | `index.ts` | Async factory: load config, register providers, handle `/router` commands |
131
- | `store.ts` | `router.json` read / write |
132
- | `register.ts` | `registerProvider` / unregister |
133
- | `stream.ts` | Wraps `openAIResponsesApi` + payload reshape |
134
- | `probe.ts` | `GET …/models` catalog fetch |
135
- | `presets.ts` | 272k defaults + thinking-level map helpers |
136
- | `dialog.ts` | Selectors, multi-select, thinking editor (dirty Esc warning) |
137
- | `ui.ts` | Command flows; auto-save on relay mutations |
138
- | `constants.ts` | Command name, defaults, `router-codex` API tag |
139
- | `types.ts` | Config types |
@@ -95,7 +95,7 @@ may be removed manually.
95
95
 
96
96
  ## Limits
97
97
 
98
- - Auto-enables only in TUI mode on `session_start`; RPC and print modes show the upstream footer.
98
+ - Auto-enables only in TUI mode on `session_start`; RPC and print modes have no TUI footer.
99
99
  - The native `(auto)` compaction marker is not reproduced because Pi does not expose that state to extensions.
100
100
 
101
101
  ## Implementation notes
@@ -107,4 +107,3 @@ Footer paint is hot (every TUI render). The extension caches:
107
107
 
108
108
  Both caches clear on footer `invalidate()`.
109
109
 
110
- **Files:** `index.ts` — footer lifecycle, formatting, usage aggregation, and responsive two-line left/right layout.
@@ -8,7 +8,8 @@ Exactly one mode is required per call:
8
8
 
9
9
  - **Single**: `agent`, `description`, and `prompt`.
10
10
  - **Parallel**: `tasks`, an array of independent task objects.
11
- - **Chain**: `chain`, an array of sequential task objects. `{previous}` in a later prompt is replaced by the previous worker's bounded final response.
11
+
12
+ There is deliberately no chain mode: sequential work is driven by the parent, which reads each report before writing the next briefing.
12
13
 
13
14
  Every task object has:
14
15
 
@@ -17,11 +18,9 @@ agent? Agent profile name; defaults to general
17
18
  description Short UI label
18
19
  prompt Self-contained worker briefing
19
20
  cwd? Relative directory inside the parent working directory
20
- model? Temporary provider/model override
21
- thinking? Temporary thinking-level override
22
21
  ```
23
22
 
24
- Parallel and chain batches allow at most eight tasks. A session-scoped gate runs at most three workers concurrently, including sibling `subagent` calls from the same parent session.
23
+ Parallel batches allow at most eight tasks. A session-scoped gate runs at most five workers concurrently, including sibling `subagent` calls from the same parent session.
25
24
 
26
25
  ## Agent profiles
27
26
 
@@ -50,26 +49,26 @@ Project definitions are loaded only after Pi trusts the project. Invalid Markdow
50
49
 
51
50
  Built-in profiles:
52
51
 
53
- - `general`: read, bash, edit, write, grep, find, and ls.
54
- - `explorer`: read-only `read`, `grep`, `find`, and `ls`, with low thinking by default.
52
+ - `general`: read, bash, edit, and write.
53
+ - `explorer`: read-only `read`, `grep`, `find`, and `ls`.
55
54
 
56
55
  A profile may only allow Pi built-in tools. Child sessions load no extensions, skills, or prompt templates, so a worker cannot recursively call `subagent` or inherit unrelated extension capabilities. Project context files such as `AGENTS.md` remain available.
57
56
 
58
57
  ## Model and thinking selection
59
58
 
60
- The model/thinking resolution order is:
59
+ The model/thinking resolution has exactly two layers:
61
60
 
62
61
  ```text
63
- per-call tool fields
64
- > saved /agents profile override
65
- > profile Markdown frontmatter
66
- > current parent session
62
+ saved /agents profile override > current parent session
67
63
  ```
68
64
 
65
+ Callers cannot pick models per call, and profile Markdown `model`/`thinking`
66
+ frontmatter is intentionally ignored: agent files travel across machines, so a
67
+ pinned model rarely exists in the reader's environment.
68
+
69
69
  Run `/agents` to choose a profile and set either field to:
70
70
 
71
- - **inherit**: force the active parent-session value;
72
- - **agent default**: clear the saved override and use Markdown/default inheritance;
71
+ - **inherit**: follow the parent session's current value;
73
72
  - a configured `provider/model` or explicit supported thinking level.
74
73
 
75
74
  Overrides are user-owned and saved atomically in:
@@ -84,8 +83,9 @@ They apply only to future subagent runs and never change the parent session's `/
84
83
 
85
84
  - The parent tool call waits until every worker reaches a terminal state.
86
85
  - Parent abort, `/reload`, `/new`, `/resume`, `/fork`, and session shutdown abort active child sessions. Queued workers do not survive the call.
87
- - Parallel worker failures do not cancel other independent workers. A failed or aborted chain step skips its remaining steps.
88
- - Progress is rendered in Pi's native tool transcript. The collapsed view shows status, recent activity, usage, and the configured expand hint; `Ctrl+O` shows bounded activities and final Markdown reports.
86
+ - Parallel worker failures do not cancel other independent workers.
87
+ - Transient provider errors auto-retry: the worker session retries retryable stream errors with backoff (visible as `Retrying (n/m) in Xs…`), and a failure that produced no work (for example a preflight auth throw) is rerun at the task level up to two more times. Runs with partial work behind them are never rerun.
88
+ - Progress is rendered in Pi's native tool transcript. The collapsed view shows an intent line (for example `Verifying changes`), the current tool marked with `›`, live elapsed time, usage with a `ctx:` context watermark, and the configured expand hint. The call-level status dot belongs to the tool shell; run and activity rows only use `›`, `○`, `✓`, `×`, and `■`. `Ctrl+O` shows bounded activities and final Markdown reports; expanded batches start with a numbered contents list whose numbers match the `──` section headers.
89
89
  - Full child transcripts are not stored separately. Bounded run details remain in the parent tool result, so completed calls restore naturally in the parent session tree.
90
90
  - Nested usage is returned with the tool result and included in parent session accounting.
91
91
 
@@ -93,10 +93,3 @@ They apply only to future subagent runs and never change the parent session's `/
93
93
 
94
94
  There are no background agents, persistent worker IDs, unread state, fleet panel, statusline widget, send/stop/resume control plane, completion notifications, swarm/coordinator, worktree isolation, nested agents, MCP, hooks, or agent memory.
95
95
 
96
- **Files:** `src/extensions/subagent/`
97
-
98
- - `index.ts` — registration, parent provider synchronization, `/agents`, shutdown cleanup
99
- - `agents.ts` / `settings.ts` / `resolve.ts` — profile discovery, persistent overrides, task validation
100
- - `sdk-runner.ts` / `runner.ts` — isolated sessions, progress, abort, concurrency, modes
101
- - `activity.ts` / `render.ts` — bounded evidence, usage, native transcript rendering
102
- - `schema.ts` / `types.ts` / `constants.ts` — public tool contract and data model
@@ -0,0 +1,28 @@
1
+ # todo — task list overlay
2
+
3
+ Adds a `todo` tool plus `/todos` for multi-step work, with a live overlay above the editor.
4
+
5
+ ## Behavior
6
+
7
+ - Actions: `create`, `update`, `list`, `get`, `delete`, `clear`. Statuses: `pending`, `in_progress`, `completed`, `deleted`. `list` accepts an optional `status` filter and `includeDeleted`; `status: "deleted"` includes tombstones directly.
8
+ - The live overlay renders above the editor and hides itself when there are no visible tasks.
9
+ - Creation always starts a task as `pending`; `blockedBy` dependencies are supported for sequencing, and missing dependencies, deleted dependencies, self-dependencies, and cycles are rejected. A task cannot move to `in_progress` or `completed` until every `blockedBy` dependency is `completed`.
10
+ - Deletion — via `delete` or `update` with `status: "deleted"` — leaves an immutable tombstone for branch history, removes that task from every dependent `blockedBy` list, and therefore releases dependents. `clear` empties the list without reusing ids.
11
+ - Newly completed tasks remain visible for 30 seconds, then drop from the overlay; historical completions loaded by `/reload` or `/tree` stay hidden so live work remains prominent.
12
+ - **Exactly one `in_progress`.** Moving a task to `in_progress` auto-demotes any other `in_progress` tasks to `pending`; the tool result lists demoted ids so the model sees the side effect.
13
+ - **Verification soft nudge.** When a completion leaves the list fully done with 3+ completed tasks and no subject/description matching `verif|test|check|review`, the tool result appends a short NOTE (text only — `details` schema unchanged).
14
+
15
+ ## Limits
16
+
17
+ - `list` output is bounded at 50 items by default, with a trailing notice when truncated.
18
+ - The overlay body is capped at 10 rows; its truncation summary includes hidden status counts.
19
+ - Newly completed tasks remain visible for 30 seconds then drop from the overlay.
20
+
21
+ ## Implementation notes
22
+
23
+ - **Conversation-backed state.** Every tool result stores a full snapshot in `details`; lifecycle handlers replay the current branch on `/reload`, compaction, and session-tree navigation. There is no separate disk database. Compaction-safe by design: `sessionManager.getBranch()` returns the full branch history — only `buildSessionContext` summarizes for the LLM. Replay walks the branch **tail → head** and stops at the latest todo `toolResult`.
24
+ - **State is keyed per session id.** Resume and `/tree` can switch sessions within one process; `execute` and the lifecycle handlers re-point the active bucket before touching state.
25
+ - **Status transitions are gated.** `completed` can be reopened to `in_progress` or `pending`, but a `deleted` tombstone is terminal. Single-active is enforced on demote (not hard-reject) so the model does not need a retry loop.
26
+ - **Tool execution is sequential** (`executionMode: "sequential"`) so parallel tool calls cannot race on in-memory state. Validation failures **throw** so Pi marks `isError: true` and the branch replays the last good snapshot.
27
+ - **Overlay paint is pure + width-cached.** Completion visibility bookkeeping runs in `update()` plus a disposable timeout, never during `render`. Same terminal width reuses the last line array.
28
+
@@ -1,8 +1,8 @@
1
1
  # Bundled theme design
2
2
 
3
- This document describes the themes shipped by the distribution. For theme discovery, JSON format, and custom theme authoring, see the [Theme API documentation](../themes.md).
3
+ This document describes the themes shipped in the `@astralyn/pi` package. For theme discovery, JSON format, and custom theme authoring, see the [Theme API documentation](../themes.md).
4
4
 
5
- The Fork bundles two Pi themes using the native theme schema and loader:
5
+ The package bundles two Pi themes using the native theme schema and loader:
6
6
 
7
7
  | Theme | Use |
8
8
  |---|---|
@@ -46,13 +46,6 @@ The palette keeps an ice-cream / sea-salt identity: cool cyan-blue accents with
46
46
 
47
47
  Bundled dialogs, overlays, and the statusline use semantic theme helpers such as `theme.fg(...)` and `theme.bg(...)`. They do not hard-code ANSI colors or hex values.
48
48
 
49
- ## Theme files
49
+ ## Theme assets
50
50
 
51
- The source assets are:
52
-
53
- ```text
54
- src/modes/interactive/theme/ice-cream-dark.json
55
- src/modes/interactive/theme/ice-cream-light.json
56
- ```
57
-
58
- The normal package build copies all theme JSON assets into the runtime package. The themes use the schema at `src/modes/interactive/theme/theme-schema.json`.
51
+ The built-in theme names are stable user-facing identifiers. In an installed package, the JSON assets are available under `dist/modes/interactive/theme/` if you need to inspect them; normal use does not require accessing those internal files.