@oh-my-pi/pi-tui 18.13.3 → 18.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/CHANGELOG.md +17 -41
  2. package/README.md +34 -34
  3. package/dist/types/app-keybindings.d.ts +15 -0
  4. package/dist/types/apps/session-picker.d.ts +1 -0
  5. package/dist/types/charts/chart-plan.d.ts +9 -7
  6. package/dist/types/chat/assistant-message.d.ts +14 -2
  7. package/dist/types/chat/extension-types.d.ts +31 -0
  8. package/dist/types/chat/reply-write.d.ts +48 -0
  9. package/dist/types/chat/table-chart.d.ts +4 -3
  10. package/dist/types/chat/tool-execution.d.ts +0 -1
  11. package/dist/types/chat/transcript-outline.d.ts +7 -0
  12. package/dist/types/chat/transcript-render-helpers.d.ts +2 -0
  13. package/dist/types/chrome/index.d.ts +1 -0
  14. package/dist/types/chrome/transcript-notice.d.ts +35 -0
  15. package/dist/types/chrome/transcript-status.d.ts +20 -8
  16. package/dist/types/components/editor.d.ts +6 -0
  17. package/dist/types/components/markdown.d.ts +7 -0
  18. package/dist/types/native/blobs.d.ts +2 -1
  19. package/dist/types/native/picker.d.ts +3 -3
  20. package/dist/types/overlays/login-dialog.d.ts +5 -1
  21. package/dist/types/overlays/model-browser.d.ts +10 -1
  22. package/dist/types/overlays/oauth-selector.d.ts +23 -0
  23. package/dist/types/overlays/rewind-selector.d.ts +1 -1
  24. package/dist/types/overlays/session-selector.d.ts +9 -0
  25. package/dist/types/prompt/composer.d.ts +41 -4
  26. package/dist/types/prompt/interactive-context-helpers.d.ts +10 -3
  27. package/dist/types/prompt/welcome.d.ts +6 -2
  28. package/dist/types/render/extension-links.d.ts +65 -0
  29. package/dist/types/render/hyperlink.d.ts +25 -7
  30. package/dist/types/render/index.d.ts +1 -0
  31. package/dist/types/setup/scenes/model.d.ts +1 -1
  32. package/dist/types/setup/scenes/outro.d.ts +5 -4
  33. package/dist/types/setup/scenes/sign-in.d.ts +13 -8
  34. package/dist/types/setup/scenes/splash.d.ts +4 -4
  35. package/dist/types/setup/scenes/types.d.ts +16 -1
  36. package/dist/types/setup/startup-splash.d.ts +1 -1
  37. package/dist/types/setup/wizard-overlay.d.ts +11 -7
  38. package/dist/types/setup/wizard.d.ts +5 -1
  39. package/dist/types/status-line/component.d.ts +1 -1
  40. package/dist/types/status-line/context-usage.d.ts +28 -6
  41. package/dist/types/status-line/host.d.ts +6 -2
  42. package/dist/types/tools/bash.d.ts +2 -0
  43. package/dist/types/tools/default-renderer.d.ts +6 -4
  44. package/dist/types/tools/irc.d.ts +6 -0
  45. package/dist/types/tools/json-tree.d.ts +23 -4
  46. package/dist/types/tools/lsp.d.ts +2 -0
  47. package/dist/types/tools/mcp.d.ts +6 -5
  48. package/dist/types/tools/native-view.d.ts +1 -1
  49. package/dist/types/tools/renderer.d.ts +2 -0
  50. package/dist/types/tools/streaming-output.d.ts +11 -0
  51. package/dist/types/tools/task.d.ts +9 -1
  52. package/dist/types/tools/url-scheme-host.d.ts +17 -0
  53. package/dist/types/tools/yield.d.ts +1 -1
  54. package/dist/types/tui.d.ts +3 -1
  55. package/package.json +9 -14
  56. package/src/app-keybindings.ts +15 -0
  57. package/src/apps/session-picker.ts +2 -0
  58. package/src/charts/chart-plan.ts +20 -50
  59. package/src/chat/assistant-message.ts +134 -18
  60. package/src/chat/background-tan-message.ts +4 -0
  61. package/src/chat/chat-transcript-builder.ts +58 -28
  62. package/src/chat/compaction-summary-message.ts +2 -1
  63. package/src/chat/extension-types.ts +34 -0
  64. package/src/chat/reply-write.ts +127 -0
  65. package/src/chat/table-chart.ts +10 -4
  66. package/src/chat/tool-execution.ts +13 -10
  67. package/src/chat/transcript-outline.ts +27 -0
  68. package/src/chat/transcript-render-helpers.ts +66 -24
  69. package/src/chrome/index.ts +1 -0
  70. package/src/chrome/transcript-notice.ts +83 -0
  71. package/src/chrome/transcript-status.ts +29 -29
  72. package/src/components/editor.ts +7 -0
  73. package/src/components/markdown.ts +116 -6
  74. package/src/hotkeys-markdown.ts +1 -0
  75. package/src/native/backend.ts +3 -2
  76. package/src/native/blobs.ts +3 -2
  77. package/src/native/picker.ts +3 -3
  78. package/src/overlays/advisor-config.ts +1 -10
  79. package/src/overlays/login-dialog.ts +31 -5
  80. package/src/overlays/model-browser.ts +37 -0
  81. package/src/overlays/model-hub.ts +1 -7
  82. package/src/overlays/model-picker.ts +1 -10
  83. package/src/overlays/oauth-selector.ts +44 -6
  84. package/src/overlays/rewind-selector.ts +29 -12
  85. package/src/overlays/session-selector.ts +113 -16
  86. package/src/prompt/composer.ts +213 -5
  87. package/src/prompt/emoji-autocomplete.ts +0 -2
  88. package/src/prompt/interactive-context-helpers.ts +29 -4
  89. package/src/prompt/welcome.ts +31 -45
  90. package/src/render/extension-links.ts +214 -0
  91. package/src/render/hyperlink.ts +65 -9
  92. package/src/render/index.ts +1 -0
  93. package/src/setup/scenes/composer.ts +2 -0
  94. package/src/setup/scenes/glyph.ts +2 -0
  95. package/src/setup/scenes/model.ts +192 -29
  96. package/src/setup/scenes/outro.ts +30 -17
  97. package/src/setup/scenes/sign-in.ts +196 -400
  98. package/src/setup/scenes/splash.ts +25 -21
  99. package/src/setup/scenes/theme.ts +2 -0
  100. package/src/setup/scenes/types.ts +18 -1
  101. package/src/setup/startup-splash.ts +8 -7
  102. package/src/setup/wizard-overlay.ts +164 -33
  103. package/src/setup/wizard.ts +11 -2
  104. package/src/status-line/component.ts +4 -10
  105. package/src/status-line/context-usage.ts +72 -21
  106. package/src/status-line/host.ts +6 -2
  107. package/src/theme/defaults.json +1 -0
  108. package/src/theme/loader.ts +1 -1
  109. package/src/theme/theme.ts +2 -10
  110. package/src/tools/bash.ts +2 -0
  111. package/src/tools/default-renderer.ts +9 -6
  112. package/src/tools/eval.ts +14 -4
  113. package/src/tools/irc.ts +6 -0
  114. package/src/tools/json-tree.ts +131 -41
  115. package/src/tools/lsp.ts +36 -15
  116. package/src/tools/mcp.ts +20 -12
  117. package/src/tools/native-view.ts +1 -1
  118. package/src/tools/renderer.ts +2 -0
  119. package/src/tools/streaming-output.ts +41 -0
  120. package/src/tools/task.ts +59 -17
  121. package/src/tools/url-scheme-host.ts +21 -0
  122. package/src/tools/yield.ts +18 -9
  123. package/src/tui.ts +36 -13
  124. package/THIRD-PARTY-NOTICES.txt +0 -22901
  125. package/dist/types/theme/defaults/index.d.ts +0 -10289
  126. package/src/theme/defaults/alabaster.json +0 -103
  127. package/src/theme/defaults/amethyst.json +0 -106
  128. package/src/theme/defaults/anthracite.json +0 -103
  129. package/src/theme/defaults/basalt.json +0 -101
  130. package/src/theme/defaults/birch.json +0 -105
  131. package/src/theme/defaults/dark-abyss.json +0 -101
  132. package/src/theme/defaults/dark-arctic.json +0 -114
  133. package/src/theme/defaults/dark-aurora.json +0 -105
  134. package/src/theme/defaults/dark-catppuccin.json +0 -117
  135. package/src/theme/defaults/dark-cavern.json +0 -101
  136. package/src/theme/defaults/dark-celestial.json +0 -116
  137. package/src/theme/defaults/dark-copper.json +0 -105
  138. package/src/theme/defaults/dark-cosmos.json +0 -100
  139. package/src/theme/defaults/dark-cyberpunk.json +0 -112
  140. package/src/theme/defaults/dark-dracula.json +0 -108
  141. package/src/theme/defaults/dark-eclipse.json +0 -101
  142. package/src/theme/defaults/dark-ember.json +0 -105
  143. package/src/theme/defaults/dark-equinox.json +0 -100
  144. package/src/theme/defaults/dark-forest.json +0 -106
  145. package/src/theme/defaults/dark-github.json +0 -115
  146. package/src/theme/defaults/dark-gruvbox.json +0 -122
  147. package/src/theme/defaults/dark-lavender.json +0 -105
  148. package/src/theme/defaults/dark-lunar.json +0 -99
  149. package/src/theme/defaults/dark-midnight.json +0 -105
  150. package/src/theme/defaults/dark-monochrome.json +0 -104
  151. package/src/theme/defaults/dark-monokai.json +0 -108
  152. package/src/theme/defaults/dark-nebula.json +0 -100
  153. package/src/theme/defaults/dark-neon-noir.json +0 -110
  154. package/src/theme/defaults/dark-nord.json +0 -108
  155. package/src/theme/defaults/dark-ocean.json +0 -111
  156. package/src/theme/defaults/dark-one.json +0 -110
  157. package/src/theme/defaults/dark-poimandres.json +0 -125
  158. package/src/theme/defaults/dark-rainforest.json +0 -101
  159. package/src/theme/defaults/dark-reef.json +0 -101
  160. package/src/theme/defaults/dark-retro.json +0 -102
  161. package/src/theme/defaults/dark-rose-pine.json +0 -106
  162. package/src/theme/defaults/dark-sakura.json +0 -105
  163. package/src/theme/defaults/dark-slate.json +0 -105
  164. package/src/theme/defaults/dark-solarized.json +0 -107
  165. package/src/theme/defaults/dark-solstice.json +0 -100
  166. package/src/theme/defaults/dark-starfall.json +0 -101
  167. package/src/theme/defaults/dark-sunset.json +0 -109
  168. package/src/theme/defaults/dark-swamp.json +0 -100
  169. package/src/theme/defaults/dark-synthwave.json +0 -113
  170. package/src/theme/defaults/dark-taiga.json +0 -101
  171. package/src/theme/defaults/dark-terminal.json +0 -105
  172. package/src/theme/defaults/dark-tokyo-night.json +0 -111
  173. package/src/theme/defaults/dark-tundra.json +0 -101
  174. package/src/theme/defaults/dark-twilight.json +0 -101
  175. package/src/theme/defaults/dark-volcanic.json +0 -101
  176. package/src/theme/defaults/graphite.json +0 -102
  177. package/src/theme/defaults/index.ts +0 -203
  178. package/src/theme/defaults/light-arctic.json +0 -117
  179. package/src/theme/defaults/light-aurora-day.json +0 -101
  180. package/src/theme/defaults/light-canyon.json +0 -101
  181. package/src/theme/defaults/light-catppuccin.json +0 -116
  182. package/src/theme/defaults/light-cirrus.json +0 -100
  183. package/src/theme/defaults/light-coral.json +0 -105
  184. package/src/theme/defaults/light-cyberpunk.json +0 -106
  185. package/src/theme/defaults/light-dawn.json +0 -100
  186. package/src/theme/defaults/light-dunes.json +0 -101
  187. package/src/theme/defaults/light-eucalyptus.json +0 -105
  188. package/src/theme/defaults/light-forest.json +0 -110
  189. package/src/theme/defaults/light-frost.json +0 -105
  190. package/src/theme/defaults/light-github.json +0 -125
  191. package/src/theme/defaults/light-glacier.json +0 -101
  192. package/src/theme/defaults/light-gruvbox.json +0 -118
  193. package/src/theme/defaults/light-haze.json +0 -100
  194. package/src/theme/defaults/light-honeycomb.json +0 -105
  195. package/src/theme/defaults/light-lagoon.json +0 -101
  196. package/src/theme/defaults/light-lavender.json +0 -105
  197. package/src/theme/defaults/light-meadow.json +0 -101
  198. package/src/theme/defaults/light-mint.json +0 -105
  199. package/src/theme/defaults/light-monochrome.json +0 -111
  200. package/src/theme/defaults/light-ocean.json +0 -109
  201. package/src/theme/defaults/light-one.json +0 -109
  202. package/src/theme/defaults/light-opal.json +0 -101
  203. package/src/theme/defaults/light-orchard.json +0 -101
  204. package/src/theme/defaults/light-paper.json +0 -105
  205. package/src/theme/defaults/light-poimandres.json +0 -125
  206. package/src/theme/defaults/light-prism.json +0 -100
  207. package/src/theme/defaults/light-retro.json +0 -108
  208. package/src/theme/defaults/light-sand.json +0 -105
  209. package/src/theme/defaults/light-savanna.json +0 -101
  210. package/src/theme/defaults/light-solarized.json +0 -112
  211. package/src/theme/defaults/light-soleil.json +0 -100
  212. package/src/theme/defaults/light-sunset.json +0 -109
  213. package/src/theme/defaults/light-synthwave.json +0 -108
  214. package/src/theme/defaults/light-tokyo-night.json +0 -121
  215. package/src/theme/defaults/light-wetland.json +0 -101
  216. package/src/theme/defaults/light-zenith.json +0 -99
  217. package/src/theme/defaults/limestone.json +0 -104
  218. package/src/theme/defaults/mahogany.json +0 -107
  219. package/src/theme/defaults/marble.json +0 -103
  220. package/src/theme/defaults/obsidian.json +0 -101
  221. package/src/theme/defaults/onyx.json +0 -101
  222. package/src/theme/defaults/pearl.json +0 -103
  223. package/src/theme/defaults/porcelain.json +0 -101
  224. package/src/theme/defaults/quartz.json +0 -106
  225. package/src/theme/defaults/sandstone.json +0 -105
  226. package/src/theme/defaults/titanium.json +0 -100
package/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.14.0] - 2026-10-29
6
+
7
+ ### Breaking Changes
8
+
9
+ - Moved `PINNED_HUD_TOGGLE_ID` to the coding agent's task output manager so reserving subagent IDs no longer loads the interactive editor.
10
+ - Updated `StatusLineHost.resetExpiryNotice` to receive the session and manage warning deduplication; custom status-line components must use the new signature.
11
+ - Removed `ToolExecutionHandle.setToolActivityVisible`; tool-activity visibility is now controlled by transcript components, while reply blocks remain visible by default.
12
+ - Removed the `@oh-my-pi/pi-tui/theme/defaults` export. Bundled palettes are now available through `getBuiltinThemes()` from `theme/defaults.json`.
13
+
14
+ ### Fixed
15
+
16
+ - Fixed table-chart direction detection so measures are not incorrectly marked as better based on header words; single-measure tables are now sent for judging when local charting is appropriate.
17
+ - Fixed LSP diagnostics that incorrectly showed “No issues” before a language server had reported; such files are now shown as unverified. ([#17293](https://github.com/can1357/oh-my-pi/pull/17293) by [@JustasMasiulis](https://github.com/JustasMasiulis))
18
+ - Fixed Esc-Esc rewind in Tern selecting interrupted turns with no visible output instead of the last displayed turn.
19
+ - Fixed streamed code blocks losing lines that scrolled off screen before the closing fence arrived; completed lines now remain available in scrollback. ([#17333](https://github.com/can1357/oh-my-pi/pull/17333) by [@gzkGit777](https://github.com/gzkGit777))
20
+
5
21
  ## [18.13.3] - 2026-10-28
6
22
 
7
23
  ### Added
@@ -2458,44 +2474,4 @@
2458
2474
  - Fixed a notification chip (or any injected block) rendering on top of an actively streaming tool render on ED3-risk terminals (Ghostty/kitty/Alacritty/iTerm2). While a foreground tool streams, its header's elapsed-time counter ticks every frame; once output scrolls the header above the viewport top, each tick is an offscreen edit that — because the eager scrollback-rebuild opt-in is gated off on these terminals — repaints the viewport in place and advances the rendered line count without committing the new overflow to native history. `#scrollbackHighWater` then lagged the logical viewport top, so a later content shrink whose changes landed in the visible region slipped past the shrink-across-boundary guard and reached the differential emitter, which is anchored to `#maxLinesRendered - height`: it rewrote only the suffix, dropped the newly exposed top row, and left a blank at the bottom, drifting every row below the edit one line up so it painted over the rows above. Such shrinks now re-anchor the bottom of the viewport with a non-destructive repaint, and the foreground-streaming shrink-across-boundary case repaints the live tail instead of padding and pinning the pre-shrink viewport.
2459
2475
  - Fixed a terminal resize during foreground-tool streaming on an unknown-viewport / ED3-risk host (Ghostty/kitty/Alacritty/iTerm2/WSL) leaving native scrollback permanently out of sync, so scrolling back after the turn showed missing rows. A pure geometry resize (no content change) takes the in-place viewport-repaint path, which — unlike a content-bearing resize that rebuilds via the geometry branch — never flagged native history. Because the prompt-submit checkpoint (`refreshNativeScrollbackIfDirty`) only rebuilds when scrollback is marked dirty on these hosts, the discrepancy was never reconciled. Overflowing geometry repaints whose viewport is not known to be at the bottom now mark scrollback dirty so the next checkpoint rebuilds an exact copy of the transcript.
2460
2476
 
2461
- ## [15.8.2] - 2026-06-03
2462
-
2463
- ### Added
2464
-
2465
- - Added `PI_NO_SYNC_OUTPUT=1` to disable DEC 2026 synchronized-output wrappers for terminals whose implementation is buggy or visually worse, while keeping the renderer's autowrap guards active during paints ([#1765](https://github.com/can1357/oh-my-pi/issues/1765)).
2466
-
2467
- ### Fixed
2468
-
2469
- - Fixed terminal resizes that land in the same render frame as streamed output splicing a phantom blank row into native scrollback and offsetting every later row by one. A height shrink (or width change carrying an append) with content overflowing the viewport fell through to the differential emitter, whose scroll math is anchored to the pre-resize viewport top and hardware-cursor row — both invalidated by the terminal's own resize reflow. Geometry-changed frames now rebuild native history when the viewport is at (or possibly at) the bottom, and defer non-destructively for a reader confirmed scrolled into history.
2470
- - Fixed Ghostty/kitty/Alacritty-style ED3-risk terminals freezing the prompt after a deferred shrink; focused keyboard input now uses the same explicit user-input viewport opt-in as autocomplete and can repaint immediately instead of waiting for a resize.
2471
- - Deferred eager live scrollback rebuilds under WSL fronted by Windows Terminal (`WT_SESSION` present in a Linux environment) so foreground streaming no longer emits ED3 (`CSI 3 J`) and yanks a reader scrolled into Windows Terminal's host scrollback; deferred rewrites still reconcile at the next prompt-submit checkpoint ([#1610](https://github.com/can1357/oh-my-pi/issues/1610)).
2472
- - Fixed tmux (and screen/zellij) pane history gaining a complete duplicate copy of the transcript every time a deferred offscreen edit was followed by another render. Multiplexer panes never receive a destructive scrollback clear, so the dirty-scrollback rebuild path only appended the full transcript on top of preserved pane history — repeatedly. Live frames inside multiplexers now keep repainting the viewport and leave history reconciliation to explicit checkpoints, which also removes the O(transcript) write amplification per frame.
2473
- - Fixed tmux pane viewports corrupting and pane history duplicating when a resize coincides with rendering: a resize racing a streamed append reached the stale-anchor diff emitters (phantom rows in the pane), a forced render racing a resize replayed the whole transcript into preserved pane history, and the prompt-submit checkpoint did the same after any deferred offscreen edit. Geometry-changed frames inside multiplexers now repaint the viewport in place, and forced-render geometry replays plus checkpoint replays are disabled there — tmux reflows its own pane grid and its history cannot be cleared, only duplicated.
2474
- - Fixed terminal resize events whose dimensions net out unchanged by render time (rapid SIGWINCH round trips during a window drag, coalesced into one 16ms frame) being invisible to the renderer. The terminal reflows its buffer on every resize event — rows move between the viewport and scrollback and can be evicted at the scrollback cap — so diffing against the pre-resize screen splices blank phantom rows into the viewport. The renderer now tracks the resize event itself, not just the dimension delta, and routes such frames through the geometry-change repaint/rebuild paths.
2475
- - Fixed Termux terminal resizes (screen rotation or software-keyboard toggles) displacing or hiding output after the viewport height changed. Content-bearing resizes were routed to the differential emitter, whose scroll math is anchored to the pre-resize viewport, so appended rows landed too low; pure height changes were treated as no-ops, exposing blank rows that later appends could fill without growing native scrollback. Termux resizes now repaint or rebuild at the new geometry like every other non-multiplexer terminal.
2476
- - Fixed the turn-end teardown frame freezing on ED3-risk terminals (Ghostty/kitty/Alacritty/iTerm2): disabling eager scrollback rebuild now takes effect only after the in-flight frame is classified, so the loader/status removal still paints instead of deferring and leaving a stale spinner until the next keystroke.
2477
- - Fixed non-WT ConPTY terminals on Windows (Tabby, Hyper, VS Code, conhost) clearing scrollback and yanking the viewport to the top whenever streaming output or a prompt-submit rebuild arrived while the user was scrolled up. The kernel32 viewport probe describes the ConPTY pseudo-console buffer — which is pinned to the visible grid, invisible to host-UI scrollback — so it reported "at bottom" no matter where the user had scrolled, and the [#1635](https://github.com/can1357/oh-my-pi/issues/1635) fix only distrusted it under `WT_SESSION`, which Tabby and other ConPTY hosts never set. The probe is now removed entirely: every Windows host is treated as viewport-unobservable, live mutations defer destructive rebuilds (no `\x1b[3J`, no viewport movement), and native scrollback reconciles at the prompt-submit checkpoint where the Enter keystroke has already pinned the host viewport to the bottom ([#1746](https://github.com/can1357/oh-my-pi/issues/1746)).
2478
- - Fixed emoji-presentation symbols (a default-text symbol followed by variation-selector-16 `U+FE0F`, e.g. `⚠️`, `ℹ️`, `❤️`, keycaps) measuring as 1 cell instead of 2 in the native width engine on macOS. The native scanner now keeps `UnicodeWidthStr` as the source of truth for multi-codepoint graphemes and applies only the local macOS Hangul Compatibility Jamo character-width delta, preserving VS16/keycap sequence widths without reintroducing jamo cursor drift.
2479
- - Deferred eager live scrollback rebuilds on macOS Terminal.app and iTerm2 so assistant/tool streaming no longer emits ED3 (`CSI 3 J`) while their native viewport position is unobservable, preserving readers scrolled into terminal history ([#1300](https://github.com/can1357/oh-my-pi/issues/1300)).
2480
- - Fixed width-shrink reflow leaving old-width rows in native history so later appends no longer undercount scrollback growth or duplicate wrapped content.
2481
- - Fixed hiding overlays after terminal reflow so stale dialog rows are scrubbed from native scrollback on non-multiplexer terminals.
2482
-
2483
- ### Removed
2484
-
2485
- - Removed `shouldTrustNativeViewportProbe` and `ProcessTerminal`'s kernel32 `GetConsoleScreenBufferInfo` viewport probe. No Windows environment can answer "is the user's viewport at the bottom" truthfully — under ConPTY (every modern host) the pseudo-console buffer is pinned to the visible grid so the probe always read "at bottom", and under legacy conhost the window tracks the output cursor rather than the buffer tail so it always read "scrolled up" — so the probe and its trust gate are gone; `ProcessTerminal` no longer implements the optional `Terminal.isNativeViewportAtBottom`.
2486
-
2487
- ## [15.8.1] - 2026-06-02
2488
-
2489
- ### Fixed
2490
-
2491
- - Deferred eager live scrollback rebuilds on VTE terminals so GNOME-style Linux terminals do not flash or erase readable scrollback during streaming ([#1719](https://github.com/can1357/oh-my-pi/issues/1719)).
2492
-
2493
- ## [15.8.0] - 2026-06-02
2494
-
2495
- ### Fixed
2496
-
2497
- - Deferred eager live scrollback rebuilds on POSIX terminals where xterm ED3 (`CSI 3 J`, erase saved lines) can disturb scrolled-up readers during streaming, while keeping direct user-input and checkpoint rebuilds explicit ([#1682](https://github.com/can1357/oh-my-pi/issues/1682)).
2498
- - Fixed TUI shutdown placing the parent shell prompt one row below short rendered content instead of directly on the next line ([#1620](https://github.com/can1357/oh-my-pi/issues/1620)).
2499
- - Stopped painting inline color swatches for 4-digit hex runs in Markdown rendering. The `#RGBA` CSS form collides with hashline `#TAG` snapshot tags (4 hex digits, e.g. `#6C5E`), which were sprouting spurious RGB swatches in prose and codespans. Only `#RGB`, `#RRGGBB`, and `#RRGGBBAA` qualify now.
2500
-
2501
- Older entries are archived in [packages/tui/CHANGELOG.md@3deb6751027b](https://github.com/can1357/oh-my-pi/blob/3deb6751027b11b10ac14b2a5c07303817276505/packages/tui/CHANGELOG.md).
2477
+ Older entries are archived in [packages/tui/CHANGELOG.md@6693ddbfd58c](https://github.com/can1357/oh-my-pi/blob/6693ddbfd58ce9edcdc0dc3fe98a8882c9bae834/packages/tui/CHANGELOG.md).
package/README.md CHANGED
@@ -28,7 +28,7 @@ const tui = new TUI(terminal);
28
28
  tui.addChild(new Text("Welcome to my app!"));
29
29
 
30
30
  const editor = new Editor(editorTheme);
31
- editor.onSubmit = (text) => {
31
+ editor.onSubmit = text => {
32
32
  console.log("Submitted:", text);
33
33
  tui.addChild(new Text(`You said: ${text}`));
34
34
  };
@@ -106,12 +106,12 @@ interface Component {
106
106
  }
107
107
  ```
108
108
 
109
- | Method | Description |
110
- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
109
+ | Method | Description |
110
+ | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
111
111
  | `render(width)` | Returns an array of strings, one per line. Each line **must not exceed `width`** or the TUI will error. Use `truncateToWidth()` or manual wrapping to ensure this. The result is component-owned and immutable to callers; return the same array reference when unchanged (enables renderer memoization) and a new array when content changed. |
112
- | `handleInput?(data)` | Called when the component has focus and receives keyboard input. The `data` string contains raw terminal input (may include ANSI escape sequences). |
113
- | `invalidate?()` | Called to clear any cached render state. Components should re-render from scratch on the next `render()` call. |
114
- | `releaseRenderCaches?()` | Drops only derived render caches. The next `render()` must return the same rows without eager rebuilds, renderer callbacks, image conversions, or child replacement. |
112
+ | `handleInput?(data)` | Called when the component has focus and receives keyboard input. The `data` string contains raw terminal input (may include ANSI escape sequences). |
113
+ | `invalidate?()` | Called to clear any cached render state. Components should re-render from scratch on the next `render()` call. |
114
+ | `releaseRenderCaches?()` | Drops only derived render caches. The next `render()` must return the same rows without eager rebuilds, renderer callbacks, image conversions, or child replacement. |
115
115
 
116
116
  ## Built-in Components
117
117
 
@@ -119,20 +119,20 @@ interface Component {
119
119
 
120
120
  Build screens from persistent components and update their data, selection, expansion, or size through setters. Layouts own child bounds and mouse-coordinate translation; controllers retain domain workflows and asynchronous operations. Propagate `invalidate()` after theme changes and `dispose()` when removing an owned component tree.
121
121
 
122
- | Family | Components | Import |
123
- | --- | --- | --- |
124
- | Layout | `Stack`, `Row`, `SplitPane` | package root |
125
- | Panels | `OverlayPanel`, `PanelRows`, `PanelDivider` | `/chrome` |
126
- | Menus | `SelectList`, `MenuSelection` | package root |
127
- | Forms | `Form`, `FormField`, `TextFormField`, `SelectFormField`, `SettingsFormField` | package root |
128
- | Wizard steps | `WizardStep` | package root |
129
- | Viewports | `ScrollView`, including child rendering, follow-tail, and keyed range anchoring | package root |
130
- | Trees | `TreeView` | package root |
131
- | Disclosure | `Disclosure`, with lazy summary/detail children | package root |
132
- | Messages | `FramedMessageComponent`, `MessageNoticeComponent`, `MessageDividerComponent` | `/chrome` |
133
- | Tool output | `ToolCard`, `framedToolCard`, `plainToolCard`, `OutputPane` | `/render` |
134
- | Transcripts | `TranscriptBrowser` | package root |
135
- | Data | `MetricRow`, `ProgressBar`, `Table`, `KeyValueList`, `Section` | package root |
122
+ | Family | Components | Import |
123
+ | ------------ | ------------------------------------------------------------------------------- | ------------ |
124
+ | Layout | `Stack`, `Row`, `SplitPane` | package root |
125
+ | Panels | `OverlayPanel`, `PanelRows`, `PanelDivider` | `/chrome` |
126
+ | Menus | `SelectList`, `MenuSelection` | package root |
127
+ | Forms | `Form`, `FormField`, `TextFormField`, `SelectFormField`, `SettingsFormField` | package root |
128
+ | Wizard steps | `WizardStep` | package root |
129
+ | Viewports | `ScrollView`, including child rendering, follow-tail, and keyed range anchoring | package root |
130
+ | Trees | `TreeView` | package root |
131
+ | Disclosure | `Disclosure`, with lazy summary/detail children | package root |
132
+ | Messages | `FramedMessageComponent`, `MessageNoticeComponent`, `MessageDividerComponent` | `/chrome` |
133
+ | Tool output | `ToolCard`, `framedToolCard`, `plainToolCard`, `OutputPane` | `/render` |
134
+ | Transcripts | `TranscriptBrowser` | package root |
135
+ | Data | `MetricRow`, `ProgressBar`, `Table`, `KeyValueList`, `Section` | package root |
136
136
 
137
137
  ```typescript
138
138
  import { Disclosure, SplitPane, Text } from "@oh-my-pi/pi-tui";
@@ -176,10 +176,10 @@ Container that applies padding and background color to all children.
176
176
  const box = new Box(
177
177
  1, // paddingX (default: 1)
178
178
  1, // paddingY (default: 1)
179
- (text) => chalk.bgGray(text), // optional background function
179
+ text => chalk.bgGray(text), // optional background function
180
180
  );
181
181
  box.addChild(new Text("Content"));
182
- box.setBgFn((text) => chalk.bgBlue(text)); // Change background dynamically
182
+ box.setBgFn(text => chalk.bgBlue(text)); // Change background dynamically
183
183
  ```
184
184
 
185
185
  ### Text
@@ -191,10 +191,10 @@ const text = new Text(
191
191
  "Hello World", // text content
192
192
  1, // paddingX (default: 1)
193
193
  1, // paddingY (default: 1)
194
- (text) => chalk.bgGray(text), // optional background function
194
+ text => chalk.bgGray(text), // optional background function
195
195
  );
196
196
  text.setText("Updated text");
197
- text.setCustomBgFn((text) => chalk.bgBlue(text));
197
+ text.setCustomBgFn(text => chalk.bgBlue(text));
198
198
  ```
199
199
 
200
200
  ### TruncatedText
@@ -215,7 +215,7 @@ Single-line text input with horizontal scrolling.
215
215
 
216
216
  ```typescript
217
217
  const input = new Input();
218
- input.onSubmit = (value) => console.log(value);
218
+ input.onSubmit = value => console.log(value);
219
219
  input.setValue("initial");
220
220
  input.getValue();
221
221
  ```
@@ -285,11 +285,11 @@ interface EditorTheme {
285
285
  }
286
286
 
287
287
  const editor = new Editor(theme);
288
- editor.onSubmit = (text) => console.log(text);
289
- editor.onChange = (text) => console.log("Changed:", text);
288
+ editor.onSubmit = text => console.log(text);
289
+ editor.onChange = text => console.log("Changed:", text);
290
290
  editor.disableSubmit = true; // Disable submit temporarily
291
291
  editor.setAutocompleteProvider(provider);
292
- editor.borderColor = (s) => chalk.blue(s); // Change border dynamically
292
+ editor.borderColor = s => chalk.blue(s); // Change border dynamically
293
293
  ```
294
294
 
295
295
  **Features:**
@@ -371,8 +371,8 @@ Animated loading spinner.
371
371
  ```typescript
372
372
  const loader = new Loader(
373
373
  tui, // TUI instance for render updates
374
- (s) => chalk.cyan(s), // spinner color function
375
- (s) => chalk.gray(s), // message color function
374
+ s => chalk.cyan(s), // spinner color function
375
+ s => chalk.gray(s), // message color function
376
376
  "Loading...", // message (default: "Loading...")
377
377
  );
378
378
  loader.start();
@@ -387,8 +387,8 @@ Extends Loader with Escape key handling and an AbortSignal for cancelling async
387
387
  ```typescript
388
388
  const loader = new CancellableLoader(
389
389
  tui, // TUI instance for render updates
390
- (s) => chalk.cyan(s), // spinner color function
391
- (s) => chalk.gray(s), // message color function
390
+ s => chalk.cyan(s), // spinner color function
391
+ s => chalk.gray(s), // message color function
392
392
  "Working...", // message
393
393
  );
394
394
  loader.onAbort = () => done(null); // Called when user presses Escape
@@ -430,9 +430,9 @@ const list = new SelectList(
430
430
  theme, // SelectListTheme
431
431
  );
432
432
 
433
- list.onSelect = (item) => console.log("Selected:", item);
433
+ list.onSelect = item => console.log("Selected:", item);
434
434
  list.onCancel = () => console.log("Cancelled");
435
- list.onSelectionChange = (item) => console.log("Highlighted:", item);
435
+ list.onSelectionChange = item => console.log("Highlighted:", item);
436
436
  list.setFilter("opt"); // Filter items
437
437
  ```
438
438
 
@@ -39,10 +39,13 @@ interface AppKeybindings {
39
39
  "app.session.rename": true;
40
40
  "app.session.delete": true;
41
41
  "app.session.deleteNoninvasive": true;
42
+ "app.session.pin": true;
43
+ "app.session.unpin": true;
42
44
  "app.tree.previousBranch": true;
43
45
  "app.tree.nextBranch": true;
44
46
  "app.plan.toggle": true;
45
47
  "app.history.search": true;
48
+ "app.links.hint": true;
46
49
  "app.stt.pushToTalk": true;
47
50
  "app.stt.toggle": true;
48
51
  "app.live.toggle": true;
@@ -329,6 +332,14 @@ export declare const KEYBINDINGS: {
329
332
  readonly defaultKeys: "ctrl+backspace";
330
333
  readonly description: "Delete session (non-invasive)";
331
334
  };
335
+ readonly "app.session.pin": {
336
+ readonly defaultKeys: "alt+k";
337
+ readonly description: "Pin the selected session in the resume picker";
338
+ };
339
+ readonly "app.session.unpin": {
340
+ readonly defaultKeys: "alt+shift+k";
341
+ readonly description: "Unpin the selected session in the resume picker";
342
+ };
332
343
  readonly "app.tree.previousBranch": {
333
344
  readonly defaultKeys: [];
334
345
  readonly description: "Jump to the previous branch in /tree";
@@ -345,6 +356,10 @@ export declare const KEYBINDINGS: {
345
356
  readonly defaultKeys: "ctrl+r";
346
357
  readonly description: "Search history";
347
358
  };
359
+ readonly "app.links.hint": {
360
+ readonly defaultKeys: "alt+o";
361
+ readonly description: "Pick an extension link on screen by its hint tag";
362
+ };
348
363
  readonly "app.stt.pushToTalk": {
349
364
  readonly defaultKeys: "space";
350
365
  readonly description: "Hold to record speech-to-text";
@@ -6,6 +6,7 @@ export interface SessionPickerHost<T extends SessionSelectorEntry = SessionSelec
6
6
  deleteSession?(session: T): Promise<boolean>;
7
7
  /** Rename the session and resolve with the title as stored, or null when it did not happen. */
8
8
  renameSession?(session: T, title: string): Promise<string | null>;
9
+ setSessionPinned?(session: T, pinned: boolean): Promise<void>;
9
10
  loadAllSessions?(): Promise<T[]>;
10
11
  }
11
12
  /** Presentation and capability controls for the standalone session picker. */
@@ -419,9 +419,10 @@ export type ChartAnnotation = {
419
419
  export declare function planChart(table: TableAnalysis): ChartPlan | undefined;
420
420
  /**
421
421
  * Whether a model should pick `table`'s chart (`tui.autoGraph: smart`):
422
- * multi-series tables, and tables whose rows may be named by a number the
423
- * `guess` plots (`Thread | State`, `Finding | Current`), where only meaning
424
- * tells a name from a measure.
422
+ * multi-series tables, tables whose rows may be named by a number the
423
+ * `guess` plots (`Thread | State`, `Finding | Current`), and any other chart
424
+ * the guess would draw — only meaning tells a measure from page references,
425
+ * step numbers or figures quoted in prose, which the judge charts as none.
425
426
  */
426
427
  export declare function shouldJudge(table: TableAnalysis, guess: ChartPlan | undefined): boolean;
427
428
  /** Measure columns before `guess`'s text label column: numbers that may only name their rows. */
@@ -446,10 +447,11 @@ export declare function tableTakeaways(table: TableAnalysis, plan: ChartPlan, co
446
447
  */
447
448
  export declare function drawnNames(spec: ChartSpec): Set<string>;
448
449
  /**
449
- * The local guess at what `plan`'s rows and measures mean: header keywords
450
- * for each measure's {@link Polarity} (resources like tokens or memory only
451
- * beside a baseline), rivals from the label's header, focus from bolded or
452
- * `(current)` row names, and a status column whose cells vary.
450
+ * The local guess at what `plan`'s rows and measures mean: each measure's
451
+ * {@link Polarity} as far as its unit says ({@link dimPolarity}; what a name
452
+ * means — `Memory` as a capacity or a cost — is the judge's call), rivals from
453
+ * the label's header, focus from bolded or `(current)` row names, and a status
454
+ * column whose cells vary.
453
455
  */
454
456
  export declare function guessMeaning(table: TableAnalysis, plan: ChartPlan): Pick<ChartPlan, "polarity" | "rowPolarity" | "rivals" | "focus" | "status">;
455
457
  /** Whether `plan`'s rows may each be a metric of their own: a `change`, a transposed table, or mixed-unit columns. */
@@ -1,7 +1,7 @@
1
1
  import type { AssistantMessage, ImageContent } from "@oh-my-pi/pi-ai";
2
2
  import { type Component, Container } from "../tui.js";
3
3
  import { type ImageBudget } from "../components/image.js";
4
- import type { AssistantThinkingRenderer } from "./extension-types.js";
4
+ import type { AssistantTextRenderer, AssistantTextRendering, AssistantThinkingRenderer } from "./extension-types.js";
5
5
  import type { DescribeContext, NativeNode, NativeUiEvent } from "../native/node.js";
6
6
  import { type CacheInvalidation } from "./cache-invalidation-marker.js";
7
7
  import { type ServedModelMismatch } from "./served-model-marker.js";
@@ -38,7 +38,7 @@ export declare class AssistantMessageComponent extends Container {
38
38
  * for lack of a target is re-rendered with its reaction stripped.
39
39
  */
40
40
  pickReactionTarget(transcript: readonly Component[]): void;
41
- constructor(message?: AssistantMessage, hideThinkingBlock?: boolean, onImageUpdate?: () => void, thinkingRenderers?: readonly AssistantThinkingRenderer[], imageBudget?: ImageBudget, proseOnlyThinking?: boolean, linkTargets?: ReadonlyMap<string, string>, expandThinkingBlocks?: boolean);
41
+ constructor(message?: AssistantMessage, hideThinkingBlock?: boolean, onImageUpdate?: () => void, thinkingRenderers?: readonly AssistantThinkingRenderer[], imageBudget?: ImageBudget, proseOnlyThinking?: boolean, linkTargets?: ReadonlyMap<string, string>, expandThinkingBlocks?: boolean, textRendering?: AssistantTextRendering);
42
42
  /**
43
43
  * Show or clear the trailing cache-invalidation divider. Set at `message_end`
44
44
  * (live) or during rebuild, once the turn's usage is known and compared
@@ -122,7 +122,19 @@ export declare class AssistantMessageComponent extends Container {
122
122
  renderTranscriptBlockEmergencyRow(width: number): string | undefined;
123
123
  getTranscriptBlockVersion(): number;
124
124
  markTranscriptBlockFinalized(): void;
125
+ /**
126
+ * Swap the extension text renderers (the setting enabling them changed) and redraw with them.
127
+ * Enabling them also drops rows already published as stable, since a renderer may rewrite them;
128
+ * callers MUST pair this with a scrollback-clearing display reset, as for
129
+ * {@link resetTranscriptStableRows}.
130
+ */
131
+ setTextRenderers(renderers: readonly AssistantTextRenderer[]): void;
125
132
  applyRetryRecovery(retryRecovery: AssistantMessage["retryRecovery"]): void;
133
+ /**
134
+ * Draw the current message again when it is the reply sent at `timestamp`, re-running the
135
+ * extension text renderers; returns whether it was. The caller schedules the frame.
136
+ */
137
+ rerenderMessage(timestamp: number): boolean;
126
138
  messagePersistenceKey(): string | undefined;
127
139
  /** Toggle rendering for assistant-native and tool-result images. */
128
140
  setImagesVisible(visible: boolean): void;
@@ -1,3 +1,4 @@
1
+ import type { AssistantMessage } from "@oh-my-pi/pi-ai";
1
2
  import type { Component, TUI } from "../tui.js";
2
3
  import type { Theme } from "../theme/theme.js";
3
4
  import type { CustomMessage, HookMessage } from "./messages.js";
@@ -17,6 +18,36 @@ export interface AssistantThinkingRenderContext {
17
18
  requestRender(): void;
18
19
  }
19
20
  export type AssistantThinkingRenderer = (context: AssistantThinkingRenderContext, theme: Theme) => Component | undefined;
21
+ export interface AssistantTextRenderContext {
22
+ /**
23
+ * The message being drawn; `message.timestamp` identifies the reply for `ctx.ui.rerenderMessage()`.
24
+ * After a tool call this is only the segment drawn there, so its `content` does not hold the whole reply.
25
+ * A deep copy: editing it, at any depth, cannot change the stored reply.
26
+ */
27
+ message: AssistantMessage;
28
+ /**
29
+ * The text block's index in the full reply's `content`, also when the reply is drawn in segments
30
+ * around tool calls; not an index into `message.content` for a segment. It counts the content as
31
+ * currently stored: a transcript rebuild that drops tool calls left without a result shifts later
32
+ * blocks down, so do not persist it as a permanent key.
33
+ */
34
+ contentIndex: number;
35
+ /** The block's markdown as drawn so far, after any earlier renderer. */
36
+ text: string;
37
+ /** True while the block may still grow; false for its final render, a block sealed at a tool call, and later redraws. */
38
+ streaming: boolean;
39
+ }
40
+ /**
41
+ * Display-only transform of one assistant text block: returns the markdown to draw, or `undefined`
42
+ * to draw `context.text` unchanged. The session, exports and model context keep the original text.
43
+ */
44
+ export type AssistantTextRenderer = (context: AssistantTextRenderContext) => string | undefined;
45
+ /** Extension text renderers for one assistant component, and where its content starts in the full reply. */
46
+ export interface AssistantTextRendering {
47
+ renderers: readonly AssistantTextRenderer[];
48
+ /** Index in the full reply's `content` of this component's first block; a segment after a tool call starts past it. */
49
+ contentOffset: number;
50
+ }
20
51
  export interface HookMessageRenderOptions {
21
52
  /** Whether the view is expanded */
22
53
  expanded: boolean;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Replies: text the agent sends to a scheme whose writes are `reply`-scoped (`out://`)
3
+ * is its mid-turn message to the user, drawn as assistant prose so it can answer
4
+ * without ending its turn.
5
+ * - A `write` call to a reply URL draws as {@link ReplyWriteComponent}, streaming as the
6
+ * call's arguments arrive, instead of a tool card.
7
+ * - Text a tool's commands write there (`printf … > out://` in bash) arrives in the
8
+ * tool's result `details.replies` ({@link toolResultReplies}); transcripts draw each
9
+ * one as a prose block after the call ({@link replyDisplayMessage}).
10
+ */
11
+ import type { AssistantMessage } from "@oh-my-pi/pi-ai";
12
+ import type { ImageBudget } from "../components/image.js";
13
+ import { AssistantMessageComponent } from "./assistant-message.js";
14
+ import type { ToolExecutionHandle, ToolResultFrame } from "./tool-execution.js";
15
+ /**
16
+ * Whether a `toolName` call writes to a reply URL. `undefined` while streamed args cannot tell yet:
17
+ * no path so far, or a path that could still grow into a reply URL (`ou`, `out:/`). Callers holding
18
+ * final args treat `undefined` as `false`.
19
+ */
20
+ export declare function isReplyWrite(toolName: string, args: unknown): boolean | undefined;
21
+ /** Display options of a reply block; the rest of its presentation follows the assistant prose defaults. */
22
+ export interface ReplyWriteOptions {
23
+ /** Repaint request for figures and images that finish loading after a render. */
24
+ readonly requestRender?: () => void;
25
+ readonly imageBudget?: ImageBudget;
26
+ /** Resolved local destinations of Markdown links, as for assistant text. */
27
+ readonly linkTargets?: ReadonlyMap<string, string>;
28
+ }
29
+ /** Replies a tool result carries in `details.replies` (text its commands wrote to a reply URL), oldest first. */
30
+ export declare function toolResultReplies(details: unknown): readonly string[];
31
+ /** Assistant message drawing reply text. No provider turn backs it, so its identity fields stay empty. */
32
+ export declare function replyDisplayMessage(text: string, errorMessage?: string): AssistantMessage;
33
+ /**
34
+ * A reply write ({@link isReplyWrite}) drawn as assistant prose. The call's lifecycle arrives
35
+ * through {@link ToolExecutionHandle}: streamed args grow the text, final args settle it, and
36
+ * the result seals the block, a failure showing its error under the text. It is a reply, not
37
+ * tool activity, so hiding tool activity leaves it on screen.
38
+ */
39
+ export declare class ReplyWriteComponent extends AssistantMessageComponent implements ToolExecutionHandle {
40
+ #private;
41
+ constructor(options?: ReplyWriteOptions);
42
+ updateArgs(args: unknown): void;
43
+ setArgsComplete(): void;
44
+ setExecutionStarted(): void;
45
+ updateResult(result: ToolResultFrame, isPartial?: boolean): void;
46
+ parkAsBackground(): void;
47
+ seal(): void;
48
+ }
@@ -19,10 +19,11 @@ export interface TableChartRequest {
19
19
  readonly guess: ChartPlan;
20
20
  }
21
21
  /**
22
- * Picks the chart for a multi-series table under `smart`: a plan, or `null`
23
- * for no chart. A rejection falls back to the local guess.
22
+ * Picks the chart for a table under `smart`: a plan, or `null` for no chart.
23
+ * A rejection falls back to the local guess; `undefined` (no judge to ask
24
+ * right now) keeps the guess at once, exactly as `always` would draw it.
24
25
  */
25
- export type TableChartPlanner = (request: TableChartRequest) => Promise<ChartPlan | null>;
26
+ export type TableChartPlanner = (request: TableChartRequest) => Promise<ChartPlan | null> | undefined;
26
27
  /**
27
28
  * The chart under a table: its SVG, colored with figure tokens (`var(--accent)`, …)
28
29
  * still to resolve; none; or a smart pick on its way (the promise settles once the cache holds it).
@@ -35,7 +35,6 @@ export interface ToolExecutionHandle extends Component {
35
35
  setArgsComplete(toolCallId?: string): void;
36
36
  setExecutionStarted(toolCallId?: string): void;
37
37
  setExpanded(expanded: boolean): void;
38
- setToolActivityVisible(visible: boolean): void;
39
38
  /** Mark the call parked: it returned, but stays tracked for async job frames. */
40
39
  parkAsBackground(): void;
41
40
  /** Seal the block as final history and stop its animations. */
@@ -9,6 +9,7 @@ import { type TranscriptEntryLike as TranscriptEntry } from "./transcript-entry.
9
9
  import type { SessionMessageEntryLike as SessionMessageEntry } from "./transcript-entry.js";
10
10
  import { type ThemeColor } from "../theme/index.js";
11
11
  import type { ChatTranscriptBuilder } from "./chat-transcript-builder.js";
12
+ import type { DescribeContext } from "../native/node.js";
12
13
  /** One selectable transcript item: a message entry plus its rendered block range. */
13
14
  export interface OutlineTarget {
14
15
  /** Entry the selection resolves to; extended over trailing componentless tool results. */
@@ -63,6 +64,12 @@ export declare class OutlineRowCache {
63
64
  export declare function appendOutlineEntries(builder: ChatTranscriptBuilder, entries: TranscriptEntry[]): OutlineTarget[];
64
65
  /** Per-target "renders at least one non-blank row" flags at the given rows. */
65
66
  export declare function outlineVisibility(childRows: readonly (readonly string[])[], targets: readonly OutlineTarget[]): boolean[];
67
+ /**
68
+ * Per-target "describes something that shows" flags: {@link outlineVisibility}
69
+ * for a TSP page, where blocks describe nodes instead of rendering rows. An
70
+ * empty `col`/`row` (an interrupted turn with no output) shows nothing.
71
+ */
72
+ export declare function describedVisibility(blocks: readonly Component[], targets: readonly OutlineTarget[], cx: DescribeContext): boolean[];
66
73
  /** Dotted horizontal rule with rounded corners, spanning the outline width. */
67
74
  export declare function outlineRule(left: string, right: string, innerWidth: number, color?: ThemeColor, caption?: string): string;
68
75
  /** Wrap pre-rendered rows in the dotted outline (rules above/below, `┆` sides). */
@@ -55,6 +55,8 @@ export declare function assistantHasVisibleContent(message: AssistantAgentMessag
55
55
  export declare function splitAssistantMessageToolTimeline(message: AssistantAgentMessage): {
56
56
  beforeTools: AssistantAgentMessage;
57
57
  afterToolCalls: ReadonlyMap<string, AssistantAgentMessage>;
58
+ /** Index in `message.content` of each after-tool segment's first block, by tool-call id. */
59
+ contentOffsets: ReadonlyMap<string, number>;
58
60
  hasToolCalls: boolean;
59
61
  lastToolCallId?: string;
60
62
  };
@@ -16,5 +16,6 @@ export * from "./shared.js";
16
16
  export * from "./streaming-panel.js";
17
17
  export * from "./tool-activity.js";
18
18
  export * from "./transcript-container.js";
19
+ export * from "./transcript-notice.js";
19
20
  export * from "./transcript-status.js";
20
21
  export * from "./visual-truncate.js";
@@ -0,0 +1,35 @@
1
+ import type { TspText } from "@oh-my-pi/pi-wire";
2
+ import type { NativeNode } from "../native/node.js";
3
+ import type { Component } from "../tui.js";
4
+ import { TranscriptBlock } from "./transcript-container.js";
5
+ /** How loud an inline transcript notice is; a native terminal styles it as `omp.notice.<tone>`. */
6
+ export type NoticeTone = "info" | "success" | "warning" | "error";
7
+ /** Optional parts of a {@link noticeRow}. */
8
+ export interface NoticeRowOptions {
9
+ /** Icon name in place of the tone's own (`read` for a file read, `download` for an update). */
10
+ readonly icon?: string;
11
+ readonly key?: string;
12
+ }
13
+ /**
14
+ * One inline transcript notice for a native terminal (`omp.notice.<tone>`): a
15
+ * leading icon beside the wrapped message. Warnings, errors, background-job
16
+ * completions and prompt file reads all draw as this row, so they read alike.
17
+ * The tone colors the icon (an info notice keeps it quiet); the message spans
18
+ * carry emphasis only (`path`, `mono`, `dim`), never the tone itself.
19
+ */
20
+ export declare function noticeRow(tone: NoticeTone, message: TspText, options?: NoticeRowOptions): NativeNode;
21
+ /**
22
+ * A transcript block that a native terminal draws as one {@link noticeRow}
23
+ * while a text terminal draws `ansi`.
24
+ */
25
+ export declare class TranscriptNotice extends TranscriptBlock {
26
+ #private;
27
+ constructor(native: NativeNode, ansi: readonly Component[]);
28
+ /**
29
+ * `showWarning` / `showError`: a blank row and the `Warning: …` / `Error: …`
30
+ * line in its color in a text terminal; natively the tone's icon and the
31
+ * message, the label left to the icon.
32
+ */
33
+ static line(tone: "warning" | "error", message: string): TranscriptNotice;
34
+ describe(): NativeNode;
35
+ }
@@ -1,24 +1,36 @@
1
+ import type { TspSpan } from "@oh-my-pi/pi-wire";
1
2
  import type { NativeNode } from "../native/node.js";
2
3
  import { type ThemeColor } from "../theme/index.js";
3
4
  import { TranscriptBlock } from "./transcript-container.js";
4
- /** One themed run of a status row. */
5
+ import { type NoticeTone } from "./transcript-notice.js";
6
+ /** One themed run of a status row's terminal line. */
5
7
  export interface TranscriptStatusPart {
6
8
  readonly text: string;
7
9
  readonly color: ThemeColor;
8
- /** Glyph art (tree connectors) drawn only in rows; native terminals draw their own structure. */
9
- readonly rowsOnly?: boolean;
10
10
  }
11
- /** One compact transcript status row assembled from themed parts, joined by single spaces. */
11
+ /** A status row in a native terminal: its {@link noticeRow}'s tone, icon and message. */
12
+ export interface TranscriptStatusNative {
13
+ readonly tone: NoticeTone;
14
+ /** Icon name in place of the tone's own. */
15
+ readonly icon?: string;
16
+ readonly message: readonly TspSpan[];
17
+ }
18
+ /**
19
+ * One compact transcript status row: the terminal line's themed parts (joined
20
+ * by single spaces, glyphs and brackets included) and the same row natively.
21
+ */
12
22
  export interface TranscriptStatusRow {
13
23
  readonly parts: readonly (TranscriptStatusPart | undefined)[];
14
24
  readonly indent?: number;
25
+ readonly native: TranscriptStatusNative;
15
26
  }
16
- /** Shared compact status-pill block used by notices and background activity. */
27
+ /**
28
+ * Shared compact status block for background activity and prompt file reads:
29
+ * one themed line per row in a text terminal, one notice row per row natively
30
+ * (`omp.status-block`).
31
+ */
17
32
  export declare class TranscriptStatusBlock extends TranscriptBlock {
18
33
  #private;
19
34
  constructor(rows: readonly TranscriptStatusRow[]);
20
- /** Append a supplemental line such as an artifact warning. */
21
- addLine(parts: readonly (TranscriptStatusPart | undefined)[], indent?: number): void;
22
- /** One wrapped text line per status row. */
23
35
  describe(): NativeNode;
24
36
  }
@@ -100,6 +100,12 @@ export declare class Editor implements Component, Focusable {
100
100
  describeLanguage: (() => string | undefined) | undefined;
101
101
  /** Label the TSP field to disable host-side editing while another input owner handles raw keys. */
102
102
  nativeKeyPassthrough: boolean;
103
+ /**
104
+ * Ask the TSP terminal to keep what is typed into this field and draw it at
105
+ * the caret until this clears, then send it: set while startup cannot read
106
+ * input yet (the prompt composer's deferred-input start).
107
+ */
108
+ nativeTypeahead: boolean;
103
109
  /**
104
110
  * TSP chrome around the `editor` node (the prompt composer's chips, mode
105
111
  * chip, send/stop): the root role, the children with `input` placed where
@@ -10,6 +10,13 @@ import type { Component } from "../tui.js";
10
10
  * byte-identical to rendering `text`.
11
11
  */
12
12
  export declare function streamingMarkdownSource(text: string): string;
13
+ /**
14
+ * Whether `text` ends inside a code fence the source never closed. The
15
+ * renderer still draws such a fence's closing border, so a caller publishing a
16
+ * settled source prefix must drop that fabricated row: it is not a prefix of
17
+ * the live block, which reaches the border only once the closer arrives.
18
+ */
19
+ export declare function endsInsideOpenFence(text: string): boolean;
13
20
  /**
14
21
  * Streaming `md` source for a native terminal, minus a still-growing table row:
15
22
  * the unterminated last line when a GFM table (a run of pipe lines holding a
@@ -29,7 +29,8 @@ export declare function base64ImageNode(data: string, mimeType: string, p?: Omit
29
29
  /**
30
30
  * Props that make an `image` node open the file at `filePath` (as a `file://` URL, the TSP
31
31
  * open-target form): a click zooms the image in the terminal, and the file opens from its
32
- * context menu. Empty without a file.
32
+ * context menu. Always uses a local file URL because the open action runs locally,
33
+ * even when remote file handlers are configured. Empty without a file.
33
34
  */
34
35
  export declare function openableImageProps(filePath: string | undefined): Pick<TspProps<"image">, "href" | "actions">;
35
36
  /**