@astralyn/pi 0.84.0 → 0.84.2

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 (176) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/dist/cli/args.js +3 -3
  3. package/dist/cli/args.js.map +1 -1
  4. package/dist/cli/auth-check.d.ts +19 -0
  5. package/dist/cli/auth-check.d.ts.map +1 -0
  6. package/dist/cli/auth-check.js +50 -0
  7. package/dist/cli/auth-check.js.map +1 -0
  8. package/dist/cli/auth-command.d.ts +24 -0
  9. package/dist/cli/auth-command.d.ts.map +1 -0
  10. package/dist/cli/auth-command.js +104 -0
  11. package/dist/cli/auth-command.js.map +1 -0
  12. package/dist/cli/credential-print.d.ts +4 -14
  13. package/dist/cli/credential-print.d.ts.map +1 -1
  14. package/dist/cli/credential-print.js +38 -79
  15. package/dist/cli/credential-print.js.map +1 -1
  16. package/dist/core/auth-storage.d.ts +10 -0
  17. package/dist/core/auth-storage.d.ts.map +1 -1
  18. package/dist/core/auth-storage.js +76 -1
  19. package/dist/core/auth-storage.js.map +1 -1
  20. package/dist/core/extensions/types.d.ts +5 -0
  21. package/dist/core/extensions/types.d.ts.map +1 -1
  22. package/dist/core/extensions/types.js.map +1 -1
  23. package/dist/core/keybindings.d.ts +8 -0
  24. package/dist/core/keybindings.d.ts.map +1 -1
  25. package/dist/core/model-resolver.d.ts.map +1 -1
  26. package/dist/core/model-resolver.js +1 -0
  27. package/dist/core/model-resolver.js.map +1 -1
  28. package/dist/core/model-runtime.d.ts +2 -0
  29. package/dist/core/model-runtime.d.ts.map +1 -1
  30. package/dist/core/model-runtime.js +3 -1
  31. package/dist/core/model-runtime.js.map +1 -1
  32. package/dist/core/tools/bash.d.ts +4 -0
  33. package/dist/core/tools/bash.d.ts.map +1 -1
  34. package/dist/core/tools/bash.js +6 -4
  35. package/dist/core/tools/bash.js.map +1 -1
  36. package/dist/core/tools/edit.d.ts +4 -0
  37. package/dist/core/tools/edit.d.ts.map +1 -1
  38. package/dist/core/tools/edit.js +11 -7
  39. package/dist/core/tools/edit.js.map +1 -1
  40. package/dist/core/tools/find.d.ts +4 -0
  41. package/dist/core/tools/find.d.ts.map +1 -1
  42. package/dist/core/tools/find.js +5 -1
  43. package/dist/core/tools/find.js.map +1 -1
  44. package/dist/core/tools/grep.d.ts +4 -0
  45. package/dist/core/tools/grep.d.ts.map +1 -1
  46. package/dist/core/tools/grep.js +5 -1
  47. package/dist/core/tools/grep.js.map +1 -1
  48. package/dist/core/tools/ls.d.ts +4 -0
  49. package/dist/core/tools/ls.d.ts.map +1 -1
  50. package/dist/core/tools/ls.js +5 -1
  51. package/dist/core/tools/ls.js.map +1 -1
  52. package/dist/core/tools/read.d.ts +4 -0
  53. package/dist/core/tools/read.d.ts.map +1 -1
  54. package/dist/core/tools/read.js +6 -2
  55. package/dist/core/tools/read.js.map +1 -1
  56. package/dist/core/tools/write.d.ts +4 -0
  57. package/dist/core/tools/write.d.ts.map +1 -1
  58. package/dist/core/tools/write.js +6 -2
  59. package/dist/core/tools/write.js.map +1 -1
  60. package/dist/extensions/index.d.ts.map +1 -1
  61. package/dist/extensions/index.js +0 -4
  62. package/dist/extensions/index.js.map +1 -1
  63. package/dist/extensions/todo/schema.d.ts +1 -1
  64. package/dist/extensions/todo/state.js +5 -5
  65. package/dist/extensions/todo/state.js.map +1 -1
  66. package/dist/main.d.ts.map +1 -1
  67. package/dist/main.js +57 -21
  68. package/dist/main.js.map +1 -1
  69. package/dist/modes/interactive/interactive-mode.d.ts +3 -4
  70. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  71. package/dist/modes/interactive/interactive-mode.js +48 -41
  72. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  73. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  74. package/dist/modes/interactive/theme/theme.js +11 -3
  75. package/dist/modes/interactive/theme/theme.js.map +1 -1
  76. package/dist/server/create-harness.d.ts +26 -0
  77. package/dist/server/create-harness.d.ts.map +1 -0
  78. package/dist/server/create-harness.js +104 -0
  79. package/dist/server/create-harness.js.map +1 -0
  80. package/dist/utils/coalescing-terminal.d.ts +19 -4
  81. package/dist/utils/coalescing-terminal.d.ts.map +1 -1
  82. package/dist/utils/coalescing-terminal.js +33 -5
  83. package/dist/utils/coalescing-terminal.js.map +1 -1
  84. package/docs/bundled/README.md +0 -2
  85. package/docs/docs.json +0 -8
  86. package/docs/extensions.md +3 -2
  87. package/docs/keybindings.md +9 -5
  88. package/docs/providers.md +8 -1
  89. package/docs/usage.md +0 -1
  90. package/examples/extensions/README.md +2 -0
  91. package/examples/extensions/custom-provider-anthropic/package-lock.json +5 -5
  92. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  93. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  94. package/examples/extensions/gondolin/package-lock.json +2 -2
  95. package/examples/extensions/gondolin/package.json +1 -1
  96. package/examples/extensions/plan-mode/README.md +66 -0
  97. package/examples/extensions/plan-mode/index.ts +390 -0
  98. package/examples/extensions/plan-mode/utils.ts +168 -0
  99. package/examples/extensions/sandbox/package-lock.json +3 -3
  100. package/examples/extensions/sandbox/package.json +1 -1
  101. package/examples/extensions/subagent/README.md +175 -0
  102. package/examples/extensions/subagent/agents/planner.md +37 -0
  103. package/examples/extensions/subagent/agents/reviewer.md +35 -0
  104. package/examples/extensions/subagent/agents/scout.md +50 -0
  105. package/examples/extensions/subagent/agents/worker.md +24 -0
  106. package/examples/extensions/subagent/agents.ts +126 -0
  107. package/examples/extensions/subagent/index.ts +1009 -0
  108. package/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
  109. package/examples/extensions/subagent/prompts/implement.md +10 -0
  110. package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
  111. package/examples/extensions/with-deps/package-lock.json +4 -4
  112. package/examples/extensions/with-deps/package.json +1 -1
  113. package/npm-shrinkwrap.json +26 -26
  114. package/package.json +6 -6
  115. package/dist/extensions/biu/index.d.ts +0 -19
  116. package/dist/extensions/biu/index.d.ts.map +0 -1
  117. package/dist/extensions/biu/index.js +0 -350
  118. package/dist/extensions/biu/index.js.map +0 -1
  119. package/dist/extensions/biu/prompts.d.ts +0 -26
  120. package/dist/extensions/biu/prompts.d.ts.map +0 -1
  121. package/dist/extensions/biu/prompts.js +0 -260
  122. package/dist/extensions/biu/prompts.js.map +0 -1
  123. package/dist/extensions/biu/workspace.d.ts +0 -122
  124. package/dist/extensions/biu/workspace.d.ts.map +0 -1
  125. package/dist/extensions/biu/workspace.js +0 -404
  126. package/dist/extensions/biu/workspace.js.map +0 -1
  127. package/dist/extensions/rewind/anchor.d.ts +0 -29
  128. package/dist/extensions/rewind/anchor.d.ts.map +0 -1
  129. package/dist/extensions/rewind/anchor.js +0 -35
  130. package/dist/extensions/rewind/anchor.js.map +0 -1
  131. package/dist/extensions/rewind/config.d.ts +0 -16
  132. package/dist/extensions/rewind/config.d.ts.map +0 -1
  133. package/dist/extensions/rewind/config.js +0 -69
  134. package/dist/extensions/rewind/config.js.map +0 -1
  135. package/dist/extensions/rewind/engine.d.ts +0 -93
  136. package/dist/extensions/rewind/engine.d.ts.map +0 -1
  137. package/dist/extensions/rewind/engine.js +0 -778
  138. package/dist/extensions/rewind/engine.js.map +0 -1
  139. package/dist/extensions/rewind/gc.d.ts +0 -35
  140. package/dist/extensions/rewind/gc.d.ts.map +0 -1
  141. package/dist/extensions/rewind/gc.js +0 -169
  142. package/dist/extensions/rewind/gc.js.map +0 -1
  143. package/dist/extensions/rewind/index.d.ts +0 -3
  144. package/dist/extensions/rewind/index.d.ts.map +0 -1
  145. package/dist/extensions/rewind/index.js +0 -287
  146. package/dist/extensions/rewind/index.js.map +0 -1
  147. package/dist/extensions/rewind/menu.d.ts +0 -13
  148. package/dist/extensions/rewind/menu.d.ts.map +0 -1
  149. package/dist/extensions/rewind/menu.js +0 -123
  150. package/dist/extensions/rewind/menu.js.map +0 -1
  151. package/dist/extensions/rewind/paths.d.ts +0 -4
  152. package/dist/extensions/rewind/paths.d.ts.map +0 -1
  153. package/dist/extensions/rewind/paths.js +0 -15
  154. package/dist/extensions/rewind/paths.js.map +0 -1
  155. package/dist/extensions/rewind/restore.d.ts +0 -54
  156. package/dist/extensions/rewind/restore.d.ts.map +0 -1
  157. package/dist/extensions/rewind/restore.js +0 -94
  158. package/dist/extensions/rewind/restore.js.map +0 -1
  159. package/dist/extensions/rewind/snapshot.d.ts +0 -45
  160. package/dist/extensions/rewind/snapshot.d.ts.map +0 -1
  161. package/dist/extensions/rewind/snapshot.js +0 -21
  162. package/dist/extensions/rewind/snapshot.js.map +0 -1
  163. package/dist/extensions/rewind/storage.d.ts +0 -12
  164. package/dist/extensions/rewind/storage.d.ts.map +0 -1
  165. package/dist/extensions/rewind/storage.js +0 -33
  166. package/dist/extensions/rewind/storage.js.map +0 -1
  167. package/dist/extensions/rewind/text.d.ts +0 -7
  168. package/dist/extensions/rewind/text.d.ts.map +0 -1
  169. package/dist/extensions/rewind/text.js +0 -19
  170. package/dist/extensions/rewind/text.js.map +0 -1
  171. package/dist/extensions/rewind/tool-path.d.ts +0 -3
  172. package/dist/extensions/rewind/tool-path.d.ts.map +0 -1
  173. package/dist/extensions/rewind/tool-path.js +0 -9
  174. package/dist/extensions/rewind/tool-path.js.map +0 -1
  175. package/docs/bundled/extensions/biu.md +0 -63
  176. package/docs/bundled/extensions/rewind.md +0 -86
@@ -24,10 +24,12 @@ import { ProcessTerminal } from "@earendil-works/pi-tui";
24
24
  * Windows Terminal — yanks a scrolled-up reader to the top of the buffer on
25
25
  * every reflow (upstream #6502, #5576, #6050; rejected upstream fix #4204).
26
26
  *
27
- * Preservation is therefore windowed, not permanent: the host enables it via
28
- * setScrollbackPreservation while an agent run is mutating the transcript
29
- * (and keeps it on until the user's next input after the run settles, since
30
- * they may still be scrolled up reading), and leaves it off otherwise. An
27
+ * Preservation is therefore windowed, not permanent: the host marks agent
28
+ * runs via setAgentRunActive, which turns preservation on at run start and
29
+ * keeps it on until the user's next input after the run settles, since they
30
+ * may still be scrolled up reading. The input half of the window is observed
31
+ * directly on this terminal's own input path, so it needs no TUI listener
32
+ * and survives renderer switches (which reuse this terminal) untouched. An
31
33
  * idle-session full redraw — Ctrl+O toggles, mode switches — passes through
32
34
  * with upstream's wipe-and-replay, whose result is byte-perfect: scrollback
33
35
  * becomes the clean new transcript. Mid-run user toggles can arm
@@ -63,6 +65,7 @@ export declare class CoalescingTerminal extends ProcessTerminal {
63
65
  private viewportTopProvider;
64
66
  private preserve;
65
67
  private passArmed;
68
+ private agentRunActive;
66
69
  private flushOnExit;
67
70
  /**
68
71
  * Enable or disable the scrollback-preserving rewrite. Preservation is
@@ -72,8 +75,20 @@ export declare class CoalescingTerminal extends ProcessTerminal {
72
75
  * input after it settles. Outside that window every full redraw passes
73
76
  * through with upstream's wipe-and-replay, whose result is byte-perfect:
74
77
  * scrollback becomes the clean new transcript. Default: disabled.
78
+ * setAgentRunActive drives this for the normal agent-run window; this
79
+ * primitive remains for direct control in tests.
75
80
  */
76
81
  setScrollbackPreservation(enabled: boolean): void;
82
+ /**
83
+ * Open or close the agent-run half of the preservation window. Opening
84
+ * (agent_start) turns preservation on. Closing (agent_settled) leaves
85
+ * preservation on: trailing reflow frames may land after the run while
86
+ * the reader is still scrolled up. The window then closes on the user's
87
+ * next input, observed in start()'s input wrapper — once the user acts
88
+ * they are back at the bottom, and upstream's clean wipe-and-replay wins
89
+ * again until the next run.
90
+ */
91
+ setAgentRunActive(active: boolean): void;
77
92
  /**
78
93
  * Arm a one-shot exemption for an explicit user action (Ctrl+O tool
79
94
  * expansion, thinking visibility) so its own full redraw keeps the
@@ -1 +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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,qBAAa,kBAAmB,SAAQ,eAAe;IACtD,OAAO,CAAC,OAAO,CAAM;IACrB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,mBAAmB,CAAyC;IACpE,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAsB;IAEzC;;;;;;;;OAQG;IACH,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAIjD;;;;;;;;OAQG;IACH,kBAAkB,IAAI,IAAI;IAI1B;;;;;;;;OAQG;IACH,sBAAsB,CAAC,QAAQ,EAAE,MAAM,MAAM,GAAG,SAAS,GAAG,IAAI;;IAWvD,KAAK,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAYlE,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAUzB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAQ3B,UAAU,IAAI,IAAI;IAIlB,UAAU,IAAI,IAAI;IAIlB,SAAS,IAAI,IAAI;IAIjB,eAAe,IAAI,IAAI;IAIvB,WAAW,IAAI,IAAI;IAInB,IAAI,IAAI,IAAI;IAQrB;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IA2E1B,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,KAAK;CASb"}
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AACH,qBAAa,kBAAmB,SAAQ,eAAe;IACtD,OAAO,CAAC,OAAO,CAAM;IACrB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,mBAAmB,CAAyC;IACpE,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,WAAW,CAAsB;IAEzC;;;;;;;;;;OAUG;IACH,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAIjD;;;;;;;;OAQG;IACH,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IAKxC;;;;;;;;OAQG;IACH,kBAAkB,IAAI,IAAI;IAI1B;;;;;;;;OAQG;IACH,sBAAsB,CAAC,QAAQ,EAAE,MAAM,MAAM,GAAG,SAAS,GAAG,IAAI;;IAWvD,KAAK,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAoBlE,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAUzB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAQ3B,UAAU,IAAI,IAAI;IAIlB,UAAU,IAAI,IAAI;IAIlB,SAAS,IAAI,IAAI;IAIjB,eAAe,IAAI,IAAI;IAIvB,WAAW,IAAI,IAAI;IAInB,IAAI,IAAI,IAAI;IAQrB;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IA2E1B,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,KAAK;CASb"}
@@ -30,10 +30,12 @@ const KITTY_APC_PREFIX = "\x1b_G";
30
30
  * Windows Terminal — yanks a scrolled-up reader to the top of the buffer on
31
31
  * every reflow (upstream #6502, #5576, #6050; rejected upstream fix #4204).
32
32
  *
33
- * Preservation is therefore windowed, not permanent: the host enables it via
34
- * setScrollbackPreservation while an agent run is mutating the transcript
35
- * (and keeps it on until the user's next input after the run settles, since
36
- * they may still be scrolled up reading), and leaves it off otherwise. An
33
+ * Preservation is therefore windowed, not permanent: the host marks agent
34
+ * runs via setAgentRunActive, which turns preservation on at run start and
35
+ * keeps it on until the user's next input after the run settles, since they
36
+ * may still be scrolled up reading. The input half of the window is observed
37
+ * directly on this terminal's own input path, so it needs no TUI listener
38
+ * and survives renderer switches (which reuse this terminal) untouched. An
37
39
  * idle-session full redraw — Ctrl+O toggles, mode switches — passes through
38
40
  * with upstream's wipe-and-replay, whose result is byte-perfect: scrollback
39
41
  * becomes the clean new transcript. Mid-run user toggles can arm
@@ -70,10 +72,26 @@ export class CoalescingTerminal extends ProcessTerminal {
70
72
  * input after it settles. Outside that window every full redraw passes
71
73
  * through with upstream's wipe-and-replay, whose result is byte-perfect:
72
74
  * scrollback becomes the clean new transcript. Default: disabled.
75
+ * setAgentRunActive drives this for the normal agent-run window; this
76
+ * primitive remains for direct control in tests.
73
77
  */
74
78
  setScrollbackPreservation(enabled) {
75
79
  this.preserve = enabled;
76
80
  }
81
+ /**
82
+ * Open or close the agent-run half of the preservation window. Opening
83
+ * (agent_start) turns preservation on. Closing (agent_settled) leaves
84
+ * preservation on: trailing reflow frames may land after the run while
85
+ * the reader is still scrolled up. The window then closes on the user's
86
+ * next input, observed in start()'s input wrapper — once the user acts
87
+ * they are back at the bottom, and upstream's clean wipe-and-replay wins
88
+ * again until the next run.
89
+ */
90
+ setAgentRunActive(active) {
91
+ this.agentRunActive = active;
92
+ if (active)
93
+ this.preserve = true;
94
+ }
77
95
  /**
78
96
  * Arm a one-shot exemption for an explicit user action (Ctrl+O tool
79
97
  * expansion, thinking visibility) so its own full redraw keeps the
@@ -104,6 +122,7 @@ export class CoalescingTerminal extends ProcessTerminal {
104
122
  this.flushQueued = false;
105
123
  this.preserve = false;
106
124
  this.passArmed = false;
125
+ this.agentRunActive = false;
107
126
  this.flushOnExit = () => this.flush();
108
127
  // A synchronous process.exit() before the queued microtask runs would
109
128
  // otherwise drop the final frame.
@@ -118,7 +137,16 @@ export class CoalescingTerminal extends ProcessTerminal {
118
137
  // across repeated start/stop cycles.
119
138
  process.removeListener("exit", this.flushOnExit);
120
139
  process.on("exit", this.flushOnExit);
121
- super.start(onInput, onResize);
140
+ // User input outside an active run closes the preservation window
141
+ // (see setAgentRunActive). Observing input here instead of on a TUI
142
+ // input listener keeps the window logic in one place and renderer
143
+ // switches — which stop and restart this shared terminal — rewrap
144
+ // the new handler automatically.
145
+ super.start((data) => {
146
+ if (!this.agentRunActive)
147
+ this.preserve = false;
148
+ onInput(data);
149
+ }, onResize);
122
150
  }
123
151
  write(data) {
124
152
  if (data.length === 0)
@@ -1 +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. During streaming, content-driven triggers (a line changing\n * above the viewport during markdown reflow) fire this routinely, which — in\n * terminals that clamp the scroll offset when scrollback vanishes, notably\n * Windows Terminal — yanks a scrolled-up reader to the top of the buffer on\n * every reflow (upstream #6502, #5576, #6050; rejected upstream fix #4204).\n *\n * Preservation is therefore windowed, not permanent: the host enables it via\n * setScrollbackPreservation while an agent run is mutating the transcript\n * (and keeps it on until the user's next input after the run settles, since\n * they may still be scrolled up reading), and leaves it off otherwise. An\n * idle-session full redraw — Ctrl+O toggles, mode switches — passes through\n * with upstream's wipe-and-replay, whose result is byte-perfect: scrollback\n * becomes the clean new transcript. Mid-run user toggles can arm\n * passNextFullRedraw for the same clean replay on their own frame.\n *\n * While preservation is active, a matching frame is rewritten to repaint\n * without the wipe. With setViewportTopProvider wired, the rewrite knows\n * which transcript line the visible screen starts at, so a frame that grew\n * the transcript repaints the on-screen rows with the new content at those\n * same positions and then scrolls the extra lines in at the bottom row.\n * Each bottom-row newline pushes the top row — already repainted with the\n * correct new content — into scrollback, exactly like ordinary streaming\n * output, so scrollback ends up byte-equivalent to the upstream full replay\n * without ever clearing it. Without the provider, or when the transcript\n * shrank (lines cannot be pulled back out of scrollback), the rewrite falls\n * back to overwriting the visible screen in place with the bottom\n * viewport-height rows, at the cost of a stale seam above the screen. ED 2\n * is avoided entirely in rewritten frames because conhost/Windows Terminal\n * implement it by scrolling the screen contents into scrollback rather than\n * erasing in place.\n *\n * Width- and height-change redraws keep the upstream wipe because re-wrapped\n * content genuinely invalidates old scrollback, and frames containing kitty\n * graphics pass through untouched. A frame that does not match the expected\n * shape is passed through unchanged, so if upstream reshapes its render\n * 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 lastWriteRows: number | undefined;\n\tprivate viewportTopProvider: (() => number | undefined) | undefined;\n\tprivate preserve = false;\n\tprivate passArmed = false;\n\tprivate flushOnExit = () => this.flush();\n\n\t/**\n\t * Enable or disable the scrollback-preserving rewrite. Preservation is\n\t * only worth its imperfections (stale renderings above the screen, seams\n\t * on shrink) while the transcript is mutating on its own and the reader\n\t * may be scrolled up — i.e. during an agent run and until the user's next\n\t * input after it settles. Outside that window every full redraw passes\n\t * through with upstream's wipe-and-replay, whose result is byte-perfect:\n\t * scrollback becomes the clean new transcript. Default: disabled.\n\t */\n\tsetScrollbackPreservation(enabled: boolean): void {\n\t\tthis.preserve = enabled;\n\t}\n\n\t/**\n\t * Arm a one-shot exemption for an explicit user action (Ctrl+O tool\n\t * expansion, thinking visibility) so its own full redraw keeps the\n\t * upstream wipe-and-replay even while preservation is active mid-run.\n\t * The user just pressed a key, so they are interacting at the bottom;\n\t * a clean replay beats preservation's stale-seam trade-offs there. The\n\t * flag is cleared by the next write so it cannot leak onto a later\n\t * streaming redraw.\n\t */\n\tpassNextFullRedraw(): void {\n\t\tthis.passArmed = true;\n\t}\n\n\t/**\n\t * Wire the renderer's notion of which transcript line the visible screen\n\t * currently starts at (pi-tui's `previousViewportTop`). Full-redraw frames\n\t * that grew the transcript then scroll the new above-screen lines into\n\t * scrollback instead of dropping them. The provider is read while the\n\t * frame is being written, i.e. before the renderer updates its own\n\t * bookkeeping for that frame; returning undefined falls back to the\n\t * bottom-anchored in-place repaint.\n\t */\n\tsetViewportTopProvider(provider: () => number | undefined): void {\n\t\tthis.viewportTopProvider = provider;\n\t}\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 start(onInput: (data: string) => void, onResize: () => void): void {\n\t\t// stop() removes the exit flush, and renderer switches stop and start\n\t\t// the shared terminal each time; restore it so a synchronous\n\t\t// process.exit() during this lifecycle still flushes pending frames.\n\t\t// remove-then-add keeps exactly one registration even when start()\n\t\t// runs right after construction (constructor already registered) or\n\t\t// across repeated start/stop cycles.\n\t\tprocess.removeListener(\"exit\", this.flushOnExit);\n\t\tprocess.on(\"exit\", this.flushOnExit);\n\t\tsuper.start(onInput, onResize);\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 rows = this.rows;\n\t\tconst widthChanged = this.lastWriteColumns !== undefined && this.lastWriteColumns !== columns;\n\t\tconst heightChanged = this.lastWriteRows !== undefined && this.lastWriteRows !== rows;\n\t\tthis.lastWriteColumns = columns;\n\t\tthis.lastWriteRows = rows;\n\t\t// One-shot: the very next write is the armed toggle's own frame (or a\n\t\t// differential frame when the toggle stayed inside the viewport, in\n\t\t// which case no exemption is needed and the flag must not linger).\n\t\tconst passAllowed = this.passArmed;\n\t\tthis.passArmed = false;\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// Outside the preservation window (idle sessions, pre-session UI) and\n\t\t// for armed user toggles, upstream's wipe-and-replay is byte-perfect;\n\t\t// let it through.\n\t\tif (!this.preserve || passAllowed) {\n\t\t\treturn data;\n\t\t}\n\t\t// Re-wrapped/re-flowed content invalidates what scrollback holds and\n\t\t// where the screen sits in it; keep the wipe.\n\t\tif (widthChanged || heightChanged) {\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// ED 2 (`2J`) is deliberately avoided in both rewrites below: conhost/\n\t\t// Windows Terminal implement it by scrolling the current screen into\n\t\t// scrollback (cls compatibility), so without the `3J` wipe each redraw\n\t\t// would stack another screenful of duplicates into history.\n\t\tconst lines = body.split(\"\\r\\n\");\n\t\tconst newViewportTop = Math.max(0, lines.length - rows);\n\t\tconst viewportTop = this.readViewportTop();\n\t\tif (viewportTop !== undefined && viewportTop <= newViewportTop) {\n\t\t\t// The transcript grew (or held steady): repaint the rows currently\n\t\t\t// on screen with the new content at those same transcript\n\t\t\t// positions, then scroll the remaining lines in at the bottom row.\n\t\t\t// Each bottom-row newline pushes the already-repainted top row into\n\t\t\t// scrollback, so scrollback receives exactly the new lines the\n\t\t\t// upstream replay would have put there — no wipe, no loss.\n\t\t\tconst onScreen = lines.slice(viewportTop, viewportTop + rows);\n\t\t\tconst scrolled = lines.slice(viewportTop + rows);\n\t\t\tlet repaint = onScreen.map((line) => `\\x1b[2K${line}`).join(\"\\r\\n\");\n\t\t\tfor (const line of scrolled) {\n\t\t\t\trepaint += `\\r\\n\\x1b[2K${line}`;\n\t\t\t}\n\t\t\treturn `${SYNC_START}\\x1b[H${repaint}\\x1b[J${SYNC_END}`;\n\t\t}\n\t\t// Fallback (no provider, or the transcript shrank — lines cannot be\n\t\t// pulled back out of scrollback): repaint only the bottom\n\t\t// viewport-height rows, overwriting the screen in place: home,\n\t\t// erase-and-rewrite each row, erase whatever remains below. Writing the\n\t\t// full transcript after a screen clear would push a duplicate copy of\n\t\t// everything above the viewport into scrollback; the truncated\n\t\t// overwrite produces the exact same final screen and cursor position as\n\t\t// the full replay (rows beyond the screen would have scrolled out\n\t\t// anyway).\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\t// The provider reaches into the renderer's bookkeeping; treat anything\n\t// unexpected as \"unknown\" and fall back rather than corrupt the screen.\n\tprivate readViewportTop(): number | undefined {\n\t\tif (!this.viewportTopProvider) return undefined;\n\t\ttry {\n\t\t\tconst value = this.viewportTopProvider();\n\t\t\treturn typeof value === \"number\" && Number.isInteger(value) && value >= 0 ? value : undefined;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\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"]}
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. During streaming, content-driven triggers (a line changing\n * above the viewport during markdown reflow) fire this routinely, which — in\n * terminals that clamp the scroll offset when scrollback vanishes, notably\n * Windows Terminal — yanks a scrolled-up reader to the top of the buffer on\n * every reflow (upstream #6502, #5576, #6050; rejected upstream fix #4204).\n *\n * Preservation is therefore windowed, not permanent: the host marks agent\n * runs via setAgentRunActive, which turns preservation on at run start and\n * keeps it on until the user's next input after the run settles, since they\n * may still be scrolled up reading. The input half of the window is observed\n * directly on this terminal's own input path, so it needs no TUI listener\n * and survives renderer switches (which reuse this terminal) untouched. An\n * idle-session full redraw — Ctrl+O toggles, mode switches — passes through\n * with upstream's wipe-and-replay, whose result is byte-perfect: scrollback\n * becomes the clean new transcript. Mid-run user toggles can arm\n * passNextFullRedraw for the same clean replay on their own frame.\n *\n * While preservation is active, a matching frame is rewritten to repaint\n * without the wipe. With setViewportTopProvider wired, the rewrite knows\n * which transcript line the visible screen starts at, so a frame that grew\n * the transcript repaints the on-screen rows with the new content at those\n * same positions and then scrolls the extra lines in at the bottom row.\n * Each bottom-row newline pushes the top row — already repainted with the\n * correct new content — into scrollback, exactly like ordinary streaming\n * output, so scrollback ends up byte-equivalent to the upstream full replay\n * without ever clearing it. Without the provider, or when the transcript\n * shrank (lines cannot be pulled back out of scrollback), the rewrite falls\n * back to overwriting the visible screen in place with the bottom\n * viewport-height rows, at the cost of a stale seam above the screen. ED 2\n * is avoided entirely in rewritten frames because conhost/Windows Terminal\n * implement it by scrolling the screen contents into scrollback rather than\n * erasing in place.\n *\n * Width- and height-change redraws keep the upstream wipe because re-wrapped\n * content genuinely invalidates old scrollback, and frames containing kitty\n * graphics pass through untouched. A frame that does not match the expected\n * shape is passed through unchanged, so if upstream reshapes its render\n * 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 lastWriteRows: number | undefined;\n\tprivate viewportTopProvider: (() => number | undefined) | undefined;\n\tprivate preserve = false;\n\tprivate passArmed = false;\n\tprivate agentRunActive = false;\n\tprivate flushOnExit = () => this.flush();\n\n\t/**\n\t * Enable or disable the scrollback-preserving rewrite. Preservation is\n\t * only worth its imperfections (stale renderings above the screen, seams\n\t * on shrink) while the transcript is mutating on its own and the reader\n\t * may be scrolled up — i.e. during an agent run and until the user's next\n\t * input after it settles. Outside that window every full redraw passes\n\t * through with upstream's wipe-and-replay, whose result is byte-perfect:\n\t * scrollback becomes the clean new transcript. Default: disabled.\n\t * setAgentRunActive drives this for the normal agent-run window; this\n\t * primitive remains for direct control in tests.\n\t */\n\tsetScrollbackPreservation(enabled: boolean): void {\n\t\tthis.preserve = enabled;\n\t}\n\n\t/**\n\t * Open or close the agent-run half of the preservation window. Opening\n\t * (agent_start) turns preservation on. Closing (agent_settled) leaves\n\t * preservation on: trailing reflow frames may land after the run while\n\t * the reader is still scrolled up. The window then closes on the user's\n\t * next input, observed in start()'s input wrapper — once the user acts\n\t * they are back at the bottom, and upstream's clean wipe-and-replay wins\n\t * again until the next run.\n\t */\n\tsetAgentRunActive(active: boolean): void {\n\t\tthis.agentRunActive = active;\n\t\tif (active) this.preserve = true;\n\t}\n\n\t/**\n\t * Arm a one-shot exemption for an explicit user action (Ctrl+O tool\n\t * expansion, thinking visibility) so its own full redraw keeps the\n\t * upstream wipe-and-replay even while preservation is active mid-run.\n\t * The user just pressed a key, so they are interacting at the bottom;\n\t * a clean replay beats preservation's stale-seam trade-offs there. The\n\t * flag is cleared by the next write so it cannot leak onto a later\n\t * streaming redraw.\n\t */\n\tpassNextFullRedraw(): void {\n\t\tthis.passArmed = true;\n\t}\n\n\t/**\n\t * Wire the renderer's notion of which transcript line the visible screen\n\t * currently starts at (pi-tui's `previousViewportTop`). Full-redraw frames\n\t * that grew the transcript then scroll the new above-screen lines into\n\t * scrollback instead of dropping them. The provider is read while the\n\t * frame is being written, i.e. before the renderer updates its own\n\t * bookkeeping for that frame; returning undefined falls back to the\n\t * bottom-anchored in-place repaint.\n\t */\n\tsetViewportTopProvider(provider: () => number | undefined): void {\n\t\tthis.viewportTopProvider = provider;\n\t}\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 start(onInput: (data: string) => void, onResize: () => void): void {\n\t\t// stop() removes the exit flush, and renderer switches stop and start\n\t\t// the shared terminal each time; restore it so a synchronous\n\t\t// process.exit() during this lifecycle still flushes pending frames.\n\t\t// remove-then-add keeps exactly one registration even when start()\n\t\t// runs right after construction (constructor already registered) or\n\t\t// across repeated start/stop cycles.\n\t\tprocess.removeListener(\"exit\", this.flushOnExit);\n\t\tprocess.on(\"exit\", this.flushOnExit);\n\t\t// User input outside an active run closes the preservation window\n\t\t// (see setAgentRunActive). Observing input here instead of on a TUI\n\t\t// input listener keeps the window logic in one place and renderer\n\t\t// switches — which stop and restart this shared terminal — rewrap\n\t\t// the new handler automatically.\n\t\tsuper.start((data) => {\n\t\t\tif (!this.agentRunActive) this.preserve = false;\n\t\t\tonInput(data);\n\t\t}, onResize);\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 rows = this.rows;\n\t\tconst widthChanged = this.lastWriteColumns !== undefined && this.lastWriteColumns !== columns;\n\t\tconst heightChanged = this.lastWriteRows !== undefined && this.lastWriteRows !== rows;\n\t\tthis.lastWriteColumns = columns;\n\t\tthis.lastWriteRows = rows;\n\t\t// One-shot: the very next write is the armed toggle's own frame (or a\n\t\t// differential frame when the toggle stayed inside the viewport, in\n\t\t// which case no exemption is needed and the flag must not linger).\n\t\tconst passAllowed = this.passArmed;\n\t\tthis.passArmed = false;\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// Outside the preservation window (idle sessions, pre-session UI) and\n\t\t// for armed user toggles, upstream's wipe-and-replay is byte-perfect;\n\t\t// let it through.\n\t\tif (!this.preserve || passAllowed) {\n\t\t\treturn data;\n\t\t}\n\t\t// Re-wrapped/re-flowed content invalidates what scrollback holds and\n\t\t// where the screen sits in it; keep the wipe.\n\t\tif (widthChanged || heightChanged) {\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// ED 2 (`2J`) is deliberately avoided in both rewrites below: conhost/\n\t\t// Windows Terminal implement it by scrolling the current screen into\n\t\t// scrollback (cls compatibility), so without the `3J` wipe each redraw\n\t\t// would stack another screenful of duplicates into history.\n\t\tconst lines = body.split(\"\\r\\n\");\n\t\tconst newViewportTop = Math.max(0, lines.length - rows);\n\t\tconst viewportTop = this.readViewportTop();\n\t\tif (viewportTop !== undefined && viewportTop <= newViewportTop) {\n\t\t\t// The transcript grew (or held steady): repaint the rows currently\n\t\t\t// on screen with the new content at those same transcript\n\t\t\t// positions, then scroll the remaining lines in at the bottom row.\n\t\t\t// Each bottom-row newline pushes the already-repainted top row into\n\t\t\t// scrollback, so scrollback receives exactly the new lines the\n\t\t\t// upstream replay would have put there — no wipe, no loss.\n\t\t\tconst onScreen = lines.slice(viewportTop, viewportTop + rows);\n\t\t\tconst scrolled = lines.slice(viewportTop + rows);\n\t\t\tlet repaint = onScreen.map((line) => `\\x1b[2K${line}`).join(\"\\r\\n\");\n\t\t\tfor (const line of scrolled) {\n\t\t\t\trepaint += `\\r\\n\\x1b[2K${line}`;\n\t\t\t}\n\t\t\treturn `${SYNC_START}\\x1b[H${repaint}\\x1b[J${SYNC_END}`;\n\t\t}\n\t\t// Fallback (no provider, or the transcript shrank — lines cannot be\n\t\t// pulled back out of scrollback): repaint only the bottom\n\t\t// viewport-height rows, overwriting the screen in place: home,\n\t\t// erase-and-rewrite each row, erase whatever remains below. Writing the\n\t\t// full transcript after a screen clear would push a duplicate copy of\n\t\t// everything above the viewport into scrollback; the truncated\n\t\t// overwrite produces the exact same final screen and cursor position as\n\t\t// the full replay (rows beyond the screen would have scrolled out\n\t\t// anyway).\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\t// The provider reaches into the renderer's bookkeeping; treat anything\n\t// unexpected as \"unknown\" and fall back rather than corrupt the screen.\n\tprivate readViewportTop(): number | undefined {\n\t\tif (!this.viewportTopProvider) return undefined;\n\t\ttry {\n\t\t\tconst value = this.viewportTopProvider();\n\t\t\treturn typeof value === \"number\" && Number.isInteger(value) && value >= 0 ? value : undefined;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\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"]}
@@ -9,10 +9,8 @@ These hidden built-ins use the same public Extension API available to external e
9
9
  | Feature | Tool or command | Purpose |
10
10
  |---|---|---|
11
11
  | [`llama.cpp`](../llama-cpp.md) | `/llama` | Manage models served by the local llama.cpp router |
12
- | [Biu](extensions/biu.md) | `/biu`, `biu://` paths | Carry a project cycle through plan, optional decomposition, execution, and archive |
13
12
  | [DeepWiki](extensions/deepwiki.md) | `deepwiki` | Query indexed public GitHub repository documentation |
14
13
  | [Question](extensions/question.md) | `question` | Ask structured questions through native interactive UI |
15
- | [Rewind](extensions/rewind.md) | `/rewind`, `/tree` lifecycle | Create and restore project snapshots around session operations |
16
14
  | [Router](extensions/router.md) | `/router` | Configure and probe Codex-style routing endpoints |
17
15
  | [Statusline](extensions/statusline.md) | Footer status | Show concise extension-managed activity state |
18
16
  | [Subagent](extensions/subagent.md) | `subagent`, `/agents` | Delegate bounded work to isolated child Pi sessions |
package/docs/docs.json CHANGED
@@ -56,10 +56,6 @@
56
56
  "title": "Overview",
57
57
  "path": "bundled/README.md"
58
58
  },
59
- {
60
- "title": "Biu",
61
- "path": "bundled/extensions/biu.md"
62
- },
63
59
  {
64
60
  "title": "DeepWiki",
65
61
  "path": "bundled/extensions/deepwiki.md"
@@ -68,10 +64,6 @@
68
64
  "title": "Question",
69
65
  "path": "bundled/extensions/question.md"
70
66
  },
71
- {
72
- "title": "Rewind",
73
- "path": "bundled/extensions/rewind.md"
74
- },
75
67
  {
76
68
  "title": "Router",
77
69
  "path": "bundled/extensions/router.md"
@@ -778,7 +778,8 @@ Behavior guarantees:
778
778
  - Mutations to `event.input` affect the actual tool execution
779
779
  - Later `tool_call` handlers see mutations made by earlier handlers
780
780
  - No re-validation is performed after your mutation
781
- - Return values from `tool_call` only control blocking via `{ block: true, reason?: string }`
781
+ - Return values from `tool_call` control blocking via `{ block: true, reason?: string, terminate?: boolean }`
782
+ - `terminate` only applies to a blocked call; the agent stops early only when every finalized result in the batch is terminating
782
783
 
783
784
  ```typescript
784
785
  import { isToolCallEventType } from "@astralyn/pi";
@@ -794,7 +795,7 @@ pi.on("tool_call", async (event, ctx) => {
794
795
  event.input.command = `source ~/.profile\n${event.input.command}`;
795
796
 
796
797
  if (event.input.command.includes("rm -rf")) {
797
- return { block: true, reason: "Dangerous command" };
798
+ return { block: true, reason: "Dangerous command", terminate: true };
798
799
  }
799
800
  }
800
801
 
@@ -10,7 +10,7 @@ After editing `keybindings.json`, run `/reload` in pi to apply the changes witho
10
10
 
11
11
  ## Key Format
12
12
 
13
- `modifier+key` where modifiers are `ctrl`, `shift`, `alt` (combinable) and keys are:
13
+ `modifier+key` where modifiers are `ctrl`, `shift`, `alt`, `super` (combinable) and keys are:
14
14
 
15
15
  - **Letters:** `a-z`
16
16
  - **Digits:** `0-9`
@@ -18,7 +18,9 @@ After editing `keybindings.json`, run `/reload` in pi to apply the changes witho
18
18
  - **Function:** `f1`-`f12`
19
19
  - **Symbols:** `` ` ``, `-`, `=`, `[`, `]`, `\`, `;`, `'`, `,`, `.`, `/`, `!`, `@`, `#`, `$`, `%`, `^`, `&`, `*`, `(`, `)`, `_`, `+`, `|`, `~`, `{`, `}`, `:`, `<`, `>`, `?`
20
20
 
21
- Modifier combinations: `ctrl+shift+x`, `alt+ctrl+x`, `ctrl+shift+alt+x`, `ctrl+1`, etc.
21
+ Modifier combinations: `ctrl+shift+x`, `alt+ctrl+x`, `ctrl+shift+alt+x`, `super+k`, `ctrl+super+k`, `ctrl+1`, etc.
22
+
23
+ `super` bindings require a terminal that reports the modifier separately, typically through the Kitty keyboard protocol. They may not work in terminals without that support.
22
24
 
23
25
  ## All Actions
24
26
 
@@ -95,12 +97,14 @@ Fullscreen transcript bindings take precedence over editor bindings. The default
95
97
  | `pageUp`, `pageDown` | Editor | Transcript |
96
98
  | `ctrl+pageUp`, `ctrl+pageDown` | Editor | Editor |
97
99
 
98
- This routing remains configurable through the ordinary action bindings. For example, `"tui.altScreen.pageUp": "ctrl+pageUp"` makes `pageUp` control the editor and `ctrl+pageUp` control the transcript in fullscreen mode. Setting `"tui.altScreen.pageUp": []` disables that transcript shortcut entirely. User bindings replace the defaults for that action.
100
+ This routing remains configurable through the ordinary action bindings. For example, `"tui.altScreen.pageUp": "ctrl+pageUp"` makes `pageUp` control the editor and `ctrl+pageUp` control the transcript in fullscreen mode. Bind `tui.altScreen.halfPageUp` and `tui.altScreen.halfPageDown` for smaller transcript steps while keeping the full-page bindings. Setting `"tui.altScreen.pageUp": []` disables that transcript shortcut entirely. User bindings replace the defaults for that action.
99
101
 
100
102
  | Keybinding id | Default | Description |
101
103
  |--------|---------|-------------|
102
104
  | `tui.altScreen.pageUp` | `pageUp` | Scroll the transcript up by one page |
103
105
  | `tui.altScreen.pageDown` | `pageDown` | Scroll the transcript down by one page |
106
+ | `tui.altScreen.halfPageUp` | *(none)* | Scroll the transcript up by half a page |
107
+ | `tui.altScreen.halfPageDown` | *(none)* | Scroll the transcript down by half a page |
104
108
  | `tui.altScreen.previousPrompt` | `ctrl+shift+up` | Jump to the previous marked message |
105
109
  | `tui.altScreen.nextPrompt` | `ctrl+shift+down` | Jump to the next marked message |
106
110
  | `tui.altScreen.top` | `home` | Scroll to the beginning of the transcript |
@@ -111,11 +115,11 @@ This routing remains configurable through the ordinary action bindings. For exam
111
115
  | Keybinding id | Default | Description |
112
116
  |--------|---------|-------------|
113
117
  | `app.interrupt` | `escape` | Cancel / abort |
114
- | `app.clear` | `ctrl+c` | Clear editor |
118
+ | `app.clear` | `ctrl+c` | Clear editor (first) / exit (second) |
115
119
  | `app.exit` | `ctrl+d` | Exit (when editor empty) |
116
120
  | `app.suspend` | `ctrl+z` (none on Windows) | Suspend to background |
117
121
  | `app.editor.external` | `ctrl+g` | Open in external editor (`externalEditor`, `$VISUAL`, `$EDITOR`, Notepad on Windows, or `nano` elsewhere) |
118
- | `app.clipboard.pasteImage` | `ctrl+v` (`alt+v` on Windows) | Paste image from clipboard |
122
+ | `app.clipboard.pasteImage` | `ctrl+v` (`alt+v` on Windows) | Paste image or text from clipboard |
119
123
 
120
124
  ### Sessions
121
125
 
package/docs/providers.md CHANGED
@@ -96,7 +96,8 @@ pi
96
96
  | Kimi For Coding | `KIMI_API_KEY` | `kimi-coding` |
97
97
  | MiniMax | `MINIMAX_API_KEY` | `minimax` |
98
98
  | MiniMax (China) | `MINIMAX_CN_API_KEY` | `minimax-cn` |
99
- | Qwen Token Plan | `QWEN_TOKEN_PLAN_API_KEY` | `qwen-token-plan` |
99
+ | Qwen Token Plan (existing catalog) | `QWEN_TOKEN_PLAN_API_KEY` | `qwen-token-plan` |
100
+ | Qwen Token Plan (Individual) | `QWEN_TOKEN_PLAN_API_KEY` | `qwen-token-plan-individual` |
100
101
  | Qwen Token Plan (China) | `QWEN_TOKEN_PLAN_CN_API_KEY` | `qwen-token-plan-cn` |
101
102
  | Xiaomi MiMo | `XIAOMI_API_KEY` | `xiaomi` |
102
103
  | Xiaomi MiMo Token Plan (China) | `XIAOMI_TOKEN_PLAN_CN_API_KEY` | `xiaomi-token-plan-cn` |
@@ -121,6 +122,7 @@ Store credentials in `~/.pi/agent/auth.json`:
121
122
  "opencode-go": { "type": "api_key", "key": "..." },
122
123
  "together": { "type": "api_key", "key": "..." },
123
124
  "qwen-token-plan": { "type": "api_key", "key": "sk-sp-..." },
125
+ "qwen-token-plan-individual": { "type": "api_key", "key": "sk-sp-..." },
124
126
  "qwen-token-plan-cn": { "type": "api_key", "key": "sk-sp-..." },
125
127
  "xiaomi": { "type": "api_key", "key": "..." },
126
128
  "xiaomi-token-plan-cn": { "type": "api_key", "key": "..." },
@@ -129,6 +131,11 @@ Store credentials in `~/.pi/agent/auth.json`:
129
131
  }
130
132
  ```
131
133
 
134
+ `qwen-token-plan-individual` uses the same international endpoint and `QWEN_TOKEN_PLAN_API_KEY` as
135
+ `qwen-token-plan`, but limits the picker to the models documented for Individual subscriptions. The existing
136
+ provider keeps its broader catalog for backward compatibility. When using `auth.json`, store the
137
+ credential under the provider you select; an environment variable is shared by both international providers.
138
+
132
139
  The file is created with `0600` permissions (user read/write only). Auth file credentials take priority over environment variables.
133
140
 
134
141
  API key credentials can also include provider-scoped environment values. These values are used before process environment variables when resolving the credential key, provider/model headers, and provider configuration such as Cloudflare account IDs, Azure OpenAI settings, Vertex project/location, Bedrock settings, `PI_CACHE_RETENTION`, and `HTTP_PROXY`/`HTTPS_PROXY`.
package/docs/usage.md CHANGED
@@ -37,7 +37,6 @@ Type `/` in the editor to open command completion. Extensions can register custo
37
37
  | Command | Description |
38
38
  |---------|-------------|
39
39
  | `/login`, `/logout` | Manage OAuth or API-key credentials |
40
- | [`/biu`](bundled/extensions/biu.md) | Toggle the TUI-only Biu workflow; inside the mode, open its menu |
41
40
  | [`/llama`](llama-cpp.md) | Download, load, and unload llama.cpp router models |
42
41
  | `/model` | Switch models |
43
42
  | `/scoped-models` | Enable/disable models for Ctrl+P cycling |
@@ -43,12 +43,14 @@ cp permission-gate.ts ~/.pi/agent/extensions/
43
43
  | `minimal-mode.ts` | Override built-in tool rendering for minimal display (only tool calls, no output in collapsed mode) |
44
44
  | `truncated-tool.ts` | Wraps ripgrep with proper output truncation (50KB/2000 lines) |
45
45
  | `ssh.ts` | Delegate all tools to a remote machine via SSH using pluggable operations |
46
+ | `subagent/` | Delegate tasks to specialized subagents with isolated context windows |
46
47
 
47
48
  ### Commands & UI
48
49
 
49
50
  | Extension | Description |
50
51
  |-----------|-------------|
51
52
  | `preset.ts` | Named presets for model, thinking level, tools, and instructions via `--preset` flag and `/preset` command |
53
+ | `plan-mode/` | Claude Code-style plan mode for read-only exploration with `/plan` command and step tracking |
52
54
  | `tools.ts` | Interactive `/tools` command to enable/disable tools with session persistence |
53
55
  | `handoff.ts` | Transfer context to a new focused session via `/handoff <goal>` |
54
56
  | `commands.ts` | Lists available slash commands, optionally filtered by source |
@@ -1,14 +1,14 @@
1
1
  {
2
- "name": "pi-extension-custom-provider-anthropic",
3
- "version": "0.82.0",
2
+ "name": "pi-extension-custom-provider",
3
+ "version": "0.84.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
- "name": "pi-extension-custom-provider-anthropic",
9
- "version": "0.82.0",
8
+ "name": "pi-extension-custom-provider",
9
+ "version": "0.84.1",
10
10
  "dependencies": {
11
- "@anthropic-ai/sdk": "0.52.0"
11
+ "@anthropic-ai/sdk": "^0.52.0"
12
12
  }
13
13
  },
14
14
  "node_modules/@anthropic-ai/sdk": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-anthropic",
3
3
  "private": true,
4
- "version": "0.82.0",
4
+ "version": "0.84.1",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-gitlab-duo",
3
3
  "private": true,
4
- "version": "0.82.0",
4
+ "version": "0.84.1",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-gondolin",
3
- "version": "0.82.0",
3
+ "version": "0.84.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-gondolin",
9
- "version": "0.82.0",
9
+ "version": "0.84.1",
10
10
  "dependencies": {
11
11
  "@earendil-works/gondolin": "0.12.0"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-gondolin",
3
3
  "private": true,
4
- "version": "0.82.0",
4
+ "version": "0.84.1",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -0,0 +1,66 @@
1
+ # Plan Mode Extension
2
+
3
+ Read-only exploration mode for safe code analysis.
4
+
5
+ ## Features
6
+
7
+ - **Built-in write tools disabled**: Disables edit/write while preserving other active tools
8
+ - **Bash allowlist**: Only read-only bash commands are allowed
9
+ - **Plan extraction**: Extracts numbered steps from `Plan:` sections
10
+ - **Progress tracking**: Widget shows completion status during execution
11
+ - **[DONE:n] markers**: Explicit step completion tracking
12
+ - **Session persistence**: State survives session resume
13
+
14
+ ## Commands
15
+
16
+ - `/plan` - Toggle plan mode
17
+ - `/todos` - Show current plan progress
18
+ - `Ctrl+Alt+P` - Toggle plan mode (shortcut)
19
+
20
+ ## Usage
21
+
22
+ 1. Enable plan mode with `/plan` or `--plan` flag
23
+ 2. Ask the agent to analyze code and create a plan
24
+ 3. The agent should output a numbered plan under a `Plan:` header:
25
+
26
+ ```
27
+ Plan:
28
+ 1. First step description
29
+ 2. Second step description
30
+ 3. Third step description
31
+ ```
32
+
33
+ 4. Choose "Execute the plan" when prompted
34
+ 5. During execution, the agent marks steps complete with `[DONE:n]` tags
35
+ 6. Progress widget shows completion status
36
+
37
+ ## How It Works
38
+
39
+ ### Plan Mode (Read-Only)
40
+ - Built-in edit/write tools disabled
41
+ - Other active tools remain available
42
+ - Bash commands filtered through allowlist
43
+ - Agent creates a plan without making changes
44
+
45
+ ### Execution Mode
46
+ - Full tool access restored
47
+ - Agent executes steps in order
48
+ - `[DONE:n]` markers track completion
49
+ - Widget shows progress
50
+
51
+ ### Command Allowlist
52
+
53
+ Safe commands (allowed):
54
+ - File inspection: `cat`, `head`, `tail`, `less`, `more`
55
+ - Search: `grep`, `find`, `rg`, `fd`
56
+ - Directory: `ls`, `pwd`, `tree`
57
+ - Git read: `git status`, `git log`, `git diff`, `git branch`
58
+ - Package info: `npm list`, `npm outdated`, `yarn info`
59
+ - System info: `uname`, `whoami`, `date`, `uptime`
60
+
61
+ Blocked commands:
62
+ - File modification: `rm`, `mv`, `cp`, `mkdir`, `touch`
63
+ - Git write: `git add`, `git commit`, `git push`
64
+ - Package install: `npm install`, `yarn add`, `pip install`
65
+ - System: `sudo`, `kill`, `reboot`
66
+ - Editors: `vim`, `nano`, `code`