@linxiraos/pi-tui 1.1.26 → 1.2.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 (423) hide show
  1. package/CHANGELOG.md +51 -491
  2. package/README.md +13 -7
  3. package/dist/types/active-terminal.d.ts +30 -0
  4. package/dist/types/app-keybindings.d.ts +7 -2
  5. package/dist/types/apps/cleanse-board.d.ts +1 -1
  6. package/dist/types/apps/debug/log-viewer.d.ts +4 -0
  7. package/dist/types/apps/debug/raw-sse-buffer.d.ts +2 -0
  8. package/dist/types/apps/git/git-tui.d.ts +5 -4
  9. package/dist/types/apps/git/help.d.ts +3 -1
  10. package/dist/types/apps/if-bench-board.d.ts +2 -2
  11. package/dist/types/apps/setup-model-picker.d.ts +1 -1
  12. package/dist/types/autocomplete.d.ts +5 -2
  13. package/dist/types/charts/chart-plan.d.ts +674 -0
  14. package/dist/types/charts/chart-svg.d.ts +59 -0
  15. package/dist/types/charts/table-data.d.ts +175 -0
  16. package/dist/types/chat/assistant-message.d.ts +11 -1
  17. package/dist/types/chat/chat-transcript-builder.d.ts +3 -0
  18. package/dist/types/chat/compaction-summary-message.d.ts +1 -0
  19. package/dist/types/chat/execution-shared.d.ts +6 -5
  20. package/dist/types/chat/fence-figure.d.ts +42 -0
  21. package/dist/types/chat/figure-markdown.d.ts +42 -0
  22. package/dist/types/chat/messages.d.ts +1 -1
  23. package/dist/types/chat/svg-figure.d.ts +33 -0
  24. package/dist/types/chat/svg-source.d.ts +49 -0
  25. package/dist/types/chat/table-chart.d.ts +89 -0
  26. package/dist/types/chat/tool-execution.d.ts +2 -0
  27. package/dist/types/chat/user-message.d.ts +17 -4
  28. package/dist/types/chrome/shared.d.ts +9 -0
  29. package/dist/types/chrome/status-notice.d.ts +15 -3
  30. package/dist/types/chrome/transcript-container.d.ts +6 -0
  31. package/dist/types/components/box.d.ts +1 -0
  32. package/dist/types/components/disclosure.d.ts +1 -0
  33. package/dist/types/components/editor.d.ts +23 -5
  34. package/dist/types/components/form.d.ts +1 -0
  35. package/dist/types/components/image.d.ts +26 -44
  36. package/dist/types/components/input.d.ts +2 -1
  37. package/dist/types/components/key-value-list.d.ts +1 -0
  38. package/dist/types/components/layout/row.d.ts +1 -0
  39. package/dist/types/components/layout/stack.d.ts +1 -0
  40. package/dist/types/components/loader.d.ts +5 -6
  41. package/dist/types/components/markdown.d.ts +47 -5
  42. package/dist/types/components/scroll-view.d.ts +1 -0
  43. package/dist/types/components/section.d.ts +1 -0
  44. package/dist/types/components/table.d.ts +1 -0
  45. package/dist/types/components/text.d.ts +1 -0
  46. package/dist/types/components/tree-view.d.ts +7 -0
  47. package/dist/types/components/truncated-text.d.ts +1 -0
  48. package/dist/types/fuzzy.d.ts +29 -1
  49. package/dist/types/key-hint-format.d.ts +9 -1
  50. package/dist/types/keybinding-matchers.d.ts +3 -0
  51. package/dist/types/keybindings.d.ts +1 -1
  52. package/dist/types/kitty-graphics.d.ts +2 -2
  53. package/dist/types/loop-watchdog.d.ts +8 -7
  54. package/dist/types/multiplexers/cmux.d.ts +16 -0
  55. package/dist/types/multiplexers/herdr.d.ts +26 -0
  56. package/dist/types/multiplexers/notify.d.ts +13 -0
  57. package/dist/types/multiplexers/orca.d.ts +12 -0
  58. package/dist/types/multiplexers/screen.d.ts +9 -0
  59. package/dist/types/multiplexers/tmux.d.ts +29 -0
  60. package/dist/types/multiplexers/types.d.ts +100 -0
  61. package/dist/types/multiplexers/wmux.d.ts +12 -0
  62. package/dist/types/multiplexers/zellij.d.ts +12 -0
  63. package/dist/types/native/apply.d.ts +4 -3
  64. package/dist/types/native/backend.d.ts +5 -4
  65. package/dist/types/native/blobs.d.ts +7 -3
  66. package/dist/types/native/encode.d.ts +8 -6
  67. package/dist/types/native/node.d.ts +36 -5
  68. package/dist/types/native/overlay.d.ts +8 -3
  69. package/dist/types/native/settle.d.ts +4 -3
  70. package/dist/types/overlays/agent-hub-projection.d.ts +6 -5
  71. package/dist/types/overlays/agent-hub-types.d.ts +9 -0
  72. package/dist/types/overlays/agent-hub.d.ts +3 -0
  73. package/dist/types/overlays/agent-transcript-viewer.d.ts +12 -2
  74. package/dist/types/overlays/agents-hub.d.ts +9 -0
  75. package/dist/types/overlays/annotation-overlay.d.ts +19 -2
  76. package/dist/types/overlays/annotation-types.d.ts +2 -0
  77. package/dist/types/overlays/ask-dialog.d.ts +5 -2
  78. package/dist/types/overlays/btw-history-panel.d.ts +13 -1
  79. package/dist/types/overlays/copy-selector.d.ts +1 -0
  80. package/dist/types/overlays/history-search.d.ts +10 -0
  81. package/dist/types/overlays/hook-selector.d.ts +9 -4
  82. package/dist/types/overlays/hub-frame.d.ts +4 -1
  83. package/dist/types/overlays/jobs-panel.d.ts +1 -7
  84. package/dist/types/overlays/model-browser.d.ts +54 -7
  85. package/dist/types/overlays/model-hub.d.ts +37 -0
  86. package/dist/types/overlays/oauth-selector.d.ts +0 -1
  87. package/dist/types/overlays/plan-review-overlay.d.ts +3 -2
  88. package/dist/types/overlays/report-panel.d.ts +68 -0
  89. package/dist/types/overlays/rewind-selector.d.ts +1 -0
  90. package/dist/types/overlays/session-observer-registry.d.ts +2 -0
  91. package/dist/types/overlays/session-selector.d.ts +6 -2
  92. package/dist/types/overlays/settings-defs.d.ts +1 -0
  93. package/dist/types/overlays/thinking-selector.d.ts +2 -2
  94. package/dist/types/overlays/usage-dashboard.d.ts +12 -8
  95. package/dist/types/overlays/usage-display.d.ts +3 -1
  96. package/dist/types/prompt/composer-cache.d.ts +0 -16
  97. package/dist/types/prompt/composer.d.ts +18 -3
  98. package/dist/types/prompt/custom-editor.d.ts +25 -13
  99. package/dist/types/prompt/image-source.d.ts +2 -1
  100. package/dist/types/prompt/interactive-context-helpers.d.ts +5 -1
  101. package/dist/types/prompt/model-mention-autocomplete.d.ts +1 -1
  102. package/dist/types/prompt/welcome.d.ts +7 -2
  103. package/dist/types/render/composite.d.ts +2 -0
  104. package/dist/types/render/output-pane.d.ts +1 -0
  105. package/dist/types/render/render-utils.d.ts +1 -0
  106. package/dist/types/render/tool-card.d.ts +6 -0
  107. package/dist/types/render/width-aware-text.d.ts +2 -0
  108. package/dist/types/setup/scenes/types.d.ts +1 -0
  109. package/dist/types/space-hold.d.ts +40 -25
  110. package/dist/types/status-line/component.d.ts +3 -0
  111. package/dist/types/status-line/context-usage.d.ts +7 -5
  112. package/dist/types/status-line/host.d.ts +4 -1
  113. package/dist/types/status-line/segments.d.ts +2 -0
  114. package/dist/types/status-line/types.d.ts +14 -3
  115. package/dist/types/terminal-capabilities.d.ts +41 -36
  116. package/dist/types/terminal-multiplexer.d.ts +127 -14
  117. package/dist/types/terminal.d.ts +23 -16
  118. package/dist/types/theme/active-symbols.d.ts +1 -1
  119. package/dist/types/theme/schema.d.ts +11 -2
  120. package/dist/types/theme/symbols.d.ts +1 -1
  121. package/dist/types/theme/theme-class.d.ts +7 -0
  122. package/dist/types/theme/theme.d.ts +1 -1
  123. package/dist/types/theme/tui-adapters.d.ts +3 -0
  124. package/dist/types/thinking.d.ts +1 -1
  125. package/dist/types/tmux.d.ts +11 -4
  126. package/dist/types/tools/bash.d.ts +7 -2
  127. package/dist/types/tools/daemon.d.ts +1 -1
  128. package/dist/types/tools/eval.d.ts +22 -3
  129. package/dist/types/tools/glob.d.ts +4 -0
  130. package/dist/types/tools/native-view.d.ts +47 -0
  131. package/dist/types/tools/output-meta.d.ts +11 -1
  132. package/dist/types/tools/renderer.d.ts +37 -2
  133. package/dist/types/tools/streaming-output.d.ts +20 -1
  134. package/dist/types/tools/task.d.ts +2 -3
  135. package/dist/types/tools/write.d.ts +9 -2
  136. package/dist/types/tools/yield.d.ts +22 -0
  137. package/dist/types/tui.d.ts +36 -0
  138. package/dist/types/utils.d.ts +6 -0
  139. package/dist/types/vim.d.ts +19 -5
  140. package/dist/types/windows-input-mode.d.ts +33 -1
  141. package/package.json +13 -9
  142. package/src/active-terminal.d.ts +30 -0
  143. package/src/active-terminal.ts +80 -0
  144. package/src/app-keybindings.d.ts +7 -2
  145. package/src/app-keybindings.ts +7 -2
  146. package/src/apps/autoresearch-dashboard.ts +7 -3
  147. package/src/apps/cleanse-board.ts +2 -2
  148. package/src/apps/cleanse-picker.ts +1 -1
  149. package/src/apps/debug/log-viewer.ts +188 -72
  150. package/src/apps/debug/raw-sse-buffer.ts +8 -0
  151. package/src/apps/debug/raw-sse.ts +39 -5
  152. package/src/apps/git/diff-pane.ts +96 -23
  153. package/src/apps/git/git-tui.ts +23 -16
  154. package/src/apps/git/help.ts +29 -20
  155. package/src/apps/git/sidebar.ts +8 -3
  156. package/src/apps/if-bench-board.ts +2 -2
  157. package/src/apps/ps-top.ts +2 -2
  158. package/src/apps/setup-model-picker.ts +1 -1
  159. package/src/autocomplete.d.ts +5 -2
  160. package/src/autocomplete.ts +67 -43
  161. package/src/bracketed-paste.ts +10 -3
  162. package/src/charts/chart-plan.d.ts +674 -0
  163. package/src/charts/chart-plan.ts +5081 -0
  164. package/src/charts/chart-svg.d.ts +59 -0
  165. package/src/charts/chart-svg.ts +4657 -0
  166. package/src/charts/table-data.d.ts +175 -0
  167. package/src/charts/table-data.ts +1478 -0
  168. package/src/chat/advisor-message.ts +11 -0
  169. package/src/chat/assistant-message.d.ts +11 -1
  170. package/src/chat/assistant-message.ts +245 -54
  171. package/src/chat/chat-transcript-builder.d.ts +3 -0
  172. package/src/chat/chat-transcript-builder.ts +9 -3
  173. package/src/chat/compaction-summary-message.d.ts +1 -0
  174. package/src/chat/compaction-summary-message.ts +8 -0
  175. package/src/chat/execution-shared.d.ts +6 -5
  176. package/src/chat/execution-shared.ts +42 -25
  177. package/src/chat/fence-figure.d.ts +42 -0
  178. package/src/chat/fence-figure.ts +80 -0
  179. package/src/chat/figure-markdown.d.ts +42 -0
  180. package/src/chat/figure-markdown.ts +174 -0
  181. package/src/chat/messages.d.ts +1 -1
  182. package/src/chat/messages.ts +1 -1
  183. package/src/chat/read-tool-group.ts +6 -4
  184. package/src/chat/skill-message.ts +6 -0
  185. package/src/chat/svg-figure.d.ts +33 -0
  186. package/src/chat/svg-figure.ts +399 -0
  187. package/src/chat/svg-source.d.ts +49 -0
  188. package/src/chat/svg-source.ts +188 -0
  189. package/src/chat/table-chart.d.ts +89 -0
  190. package/src/chat/table-chart.ts +294 -0
  191. package/src/chat/tool-execution.d.ts +2 -0
  192. package/src/chat/tool-execution.ts +86 -7
  193. package/src/chat/user-message.d.ts +17 -4
  194. package/src/chat/user-message.ts +71 -8
  195. package/src/chrome/shared.ts +53 -0
  196. package/src/chrome/status-notice.ts +25 -7
  197. package/src/chrome/transcript-container.d.ts +6 -0
  198. package/src/chrome/transcript-container.ts +162 -19
  199. package/src/components/box.d.ts +1 -0
  200. package/src/components/box.ts +5 -0
  201. package/src/components/disclosure.d.ts +1 -0
  202. package/src/components/disclosure.ts +8 -0
  203. package/src/components/editor.d.ts +23 -5
  204. package/src/components/editor.ts +316 -47
  205. package/src/components/form.d.ts +1 -0
  206. package/src/components/form.ts +8 -0
  207. package/src/components/image.d.ts +26 -44
  208. package/src/components/image.ts +115 -152
  209. package/src/components/input.d.ts +2 -1
  210. package/src/components/input.ts +17 -7
  211. package/src/components/key-value-list.d.ts +1 -0
  212. package/src/components/key-value-list.ts +4 -0
  213. package/src/components/layout/row.d.ts +1 -0
  214. package/src/components/layout/row.ts +7 -0
  215. package/src/components/layout/stack.d.ts +1 -0
  216. package/src/components/layout/stack.ts +7 -0
  217. package/src/components/loader.d.ts +5 -6
  218. package/src/components/loader.ts +10 -17
  219. package/src/components/markdown.d.ts +47 -5
  220. package/src/components/markdown.ts +786 -303
  221. package/src/components/scroll-view.d.ts +1 -0
  222. package/src/components/scroll-view.ts +5 -0
  223. package/src/components/section.d.ts +1 -0
  224. package/src/components/section.ts +5 -0
  225. package/src/components/settings-list.ts +2 -0
  226. package/src/components/table.d.ts +1 -0
  227. package/src/components/table.ts +4 -0
  228. package/src/components/text.d.ts +1 -0
  229. package/src/components/text.ts +7 -0
  230. package/src/components/tree-view.d.ts +7 -0
  231. package/src/components/tree-view.ts +38 -8
  232. package/src/components/truncated-text.d.ts +1 -0
  233. package/src/components/truncated-text.ts +5 -0
  234. package/src/desktop-notify.ts +3 -2
  235. package/src/fuzzy.d.ts +29 -1
  236. package/src/fuzzy.ts +115 -30
  237. package/src/hotkeys-markdown.ts +9 -2
  238. package/src/key-hint-format.d.ts +9 -1
  239. package/src/key-hint-format.ts +26 -11
  240. package/src/keybinding-matchers.d.ts +3 -0
  241. package/src/keybinding-matchers.ts +6 -2
  242. package/src/keybindings.d.ts +1 -1
  243. package/src/keybindings.ts +1 -1
  244. package/src/kitty-graphics.d.ts +2 -2
  245. package/src/kitty-graphics.ts +11 -5
  246. package/src/loop-watchdog.d.ts +8 -7
  247. package/src/loop-watchdog.ts +18 -13
  248. package/src/multiplexers/cmux.d.ts +16 -0
  249. package/src/multiplexers/cmux.ts +29 -0
  250. package/src/multiplexers/herdr.d.ts +26 -0
  251. package/src/multiplexers/herdr.ts +64 -0
  252. package/src/multiplexers/notify.d.ts +13 -0
  253. package/src/multiplexers/notify.ts +24 -0
  254. package/src/multiplexers/orca.d.ts +12 -0
  255. package/src/multiplexers/orca.ts +16 -0
  256. package/src/multiplexers/screen.d.ts +9 -0
  257. package/src/multiplexers/screen.ts +15 -0
  258. package/src/multiplexers/tmux.d.ts +29 -0
  259. package/src/multiplexers/tmux.ts +40 -0
  260. package/src/multiplexers/types.d.ts +100 -0
  261. package/src/multiplexers/types.ts +110 -0
  262. package/src/multiplexers/wmux.d.ts +12 -0
  263. package/src/multiplexers/wmux.ts +16 -0
  264. package/src/multiplexers/zellij.d.ts +12 -0
  265. package/src/multiplexers/zellij.ts +24 -0
  266. package/src/native/apply.d.ts +4 -3
  267. package/src/native/apply.ts +4 -3
  268. package/src/native/backend.d.ts +5 -4
  269. package/src/native/backend.ts +199 -22
  270. package/src/native/blobs.d.ts +7 -3
  271. package/src/native/blobs.ts +47 -12
  272. package/src/native/encode.d.ts +8 -6
  273. package/src/native/encode.ts +76 -34
  274. package/src/native/node.d.ts +36 -5
  275. package/src/native/node.ts +31 -5
  276. package/src/native/overlay.d.ts +8 -3
  277. package/src/native/overlay.ts +17 -4
  278. package/src/native/reconcile.ts +16 -4
  279. package/src/native/settle.d.ts +4 -3
  280. package/src/native/settle.ts +4 -3
  281. package/src/overlays/agent-hub-projection.d.ts +6 -5
  282. package/src/overlays/agent-hub-projection.ts +124 -13
  283. package/src/overlays/agent-hub-types.d.ts +9 -0
  284. package/src/overlays/agent-hub-types.ts +6 -0
  285. package/src/overlays/agent-hub.ts +48 -29
  286. package/src/overlays/agent-transcript-viewer.ts +93 -23
  287. package/src/overlays/agents-hub.ts +28 -8
  288. package/src/overlays/annotation-overlay.ts +293 -90
  289. package/src/overlays/annotation-types.ts +2 -0
  290. package/src/overlays/ask-dialog.ts +10 -17
  291. package/src/overlays/btw-history-panel.ts +136 -39
  292. package/src/overlays/copy-selector.d.ts +1 -0
  293. package/src/overlays/copy-selector.ts +32 -8
  294. package/src/overlays/extensions/extension-dashboard.ts +4 -4
  295. package/src/overlays/extensions/state-manager.ts +52 -12
  296. package/src/overlays/history-search.ts +100 -7
  297. package/src/overlays/hook-selector.ts +12 -5
  298. package/src/overlays/hub-frame.ts +19 -1
  299. package/src/overlays/jobs-panel.ts +33 -52
  300. package/src/overlays/model-browser.ts +367 -122
  301. package/src/overlays/model-hub.ts +435 -59
  302. package/src/overlays/move-overlay.ts +28 -14
  303. package/src/overlays/oauth-selector.ts +54 -70
  304. package/src/overlays/pause-screen.ts +2 -2
  305. package/src/overlays/plan-review-overlay.ts +208 -70
  306. package/src/overlays/plugin-settings.ts +3 -3
  307. package/src/overlays/report-panel.ts +191 -0
  308. package/src/overlays/rewind-selector.ts +88 -17
  309. package/src/overlays/session-observer-registry.d.ts +2 -0
  310. package/src/overlays/session-observer-registry.ts +10 -2
  311. package/src/overlays/session-selector.ts +64 -26
  312. package/src/overlays/settings-defs.ts +5 -4
  313. package/src/overlays/settings-selector.ts +33 -8
  314. package/src/overlays/snapcompact-shape-preview.ts +2 -3
  315. package/src/overlays/stats-notice.ts +1 -1
  316. package/src/overlays/thinking-selector.ts +11 -10
  317. package/src/overlays/tree-selector.ts +41 -20
  318. package/src/overlays/usage-dashboard.ts +117 -58
  319. package/src/overlays/usage-display.ts +10 -4
  320. package/src/overlays/usage-row.ts +22 -8
  321. package/src/prompt/attachment-chips.ts +15 -11
  322. package/src/prompt/composer-attachments.ts +6 -4
  323. package/src/prompt/composer-cache.ts +7 -86
  324. package/src/prompt/composer.ts +37 -26
  325. package/src/prompt/custom-editor.ts +282 -105
  326. package/src/prompt/image-source.ts +2 -1
  327. package/src/prompt/interactive-context-helpers.ts +32 -18
  328. package/src/prompt/macos-spelling.ts +7 -0
  329. package/src/prompt/magic-keywords.ts +11 -2
  330. package/src/prompt/model-mention-autocomplete.ts +14 -14
  331. package/src/prompt/queued-messages.ts +6 -4
  332. package/src/prompt/tips.txt +13 -9
  333. package/src/prompt/welcome.ts +64 -46
  334. package/src/prompt/word-completion.ts +38 -5
  335. package/src/render/composite.d.ts +2 -0
  336. package/src/render/composite.ts +64 -0
  337. package/src/render/output-pane.d.ts +1 -0
  338. package/src/render/output-pane.ts +6 -0
  339. package/src/render/render-utils.d.ts +1 -0
  340. package/src/render/render-utils.ts +16 -1
  341. package/src/render/tool-card.d.ts +6 -0
  342. package/src/render/tool-card.ts +15 -0
  343. package/src/render/width-aware-text.d.ts +2 -0
  344. package/src/render/width-aware-text.ts +5 -0
  345. package/src/setup/scenes/model.ts +2 -1
  346. package/src/setup/scenes/types.ts +1 -0
  347. package/src/space-hold.d.ts +40 -25
  348. package/src/space-hold.ts +156 -52
  349. package/src/status-line/component.ts +292 -78
  350. package/src/status-line/context-usage.ts +21 -46
  351. package/src/status-line/host.ts +4 -1
  352. package/src/status-line/segments.ts +40 -22
  353. package/src/status-line/types.ts +15 -3
  354. package/src/terminal-capabilities.d.ts +41 -36
  355. package/src/terminal-capabilities.ts +137 -213
  356. package/src/terminal-multiplexer.d.ts +127 -14
  357. package/src/terminal-multiplexer.ts +112 -33
  358. package/src/terminal.d.ts +23 -16
  359. package/src/terminal.ts +203 -99
  360. package/src/theme/active-symbols.d.ts +1 -1
  361. package/src/theme/active-symbols.ts +1 -1
  362. package/src/theme/schema-validation.ts +1 -0
  363. package/src/theme/schema.d.ts +11 -2
  364. package/src/theme/schema.ts +14 -1
  365. package/src/theme/session-color.ts +1 -7
  366. package/src/theme/symbols.d.ts +1 -1
  367. package/src/theme/symbols.ts +11 -4
  368. package/src/theme/theme-class.d.ts +7 -0
  369. package/src/theme/theme-class.ts +26 -1
  370. package/src/theme/theme-schema.json +5 -1
  371. package/src/theme/theme.d.ts +1 -1
  372. package/src/theme/theme.ts +8 -4
  373. package/src/theme/tui-adapters.d.ts +3 -0
  374. package/src/theme/tui-adapters.ts +68 -0
  375. package/src/thinking.d.ts +1 -1
  376. package/src/thinking.ts +1 -1
  377. package/src/tmux.d.ts +11 -4
  378. package/src/tmux.ts +47 -11
  379. package/src/tools/ast-edit.ts +59 -35
  380. package/src/tools/ast-grep.ts +35 -16
  381. package/src/tools/bash.d.ts +7 -2
  382. package/src/tools/bash.ts +116 -47
  383. package/src/tools/daemon.d.ts +1 -1
  384. package/src/tools/daemon.ts +1 -1
  385. package/src/tools/default-renderer.ts +35 -19
  386. package/src/tools/edit.ts +142 -120
  387. package/src/tools/eval-format/index.ts +28 -2
  388. package/src/tools/eval-format/javascript.ts +7 -5
  389. package/src/tools/eval.d.ts +22 -3
  390. package/src/tools/eval.ts +364 -208
  391. package/src/tools/fetch.ts +5 -6
  392. package/src/tools/glob.d.ts +4 -0
  393. package/src/tools/glob.ts +33 -65
  394. package/src/tools/grep.ts +81 -48
  395. package/src/tools/index.ts +2 -0
  396. package/src/tools/lsp.ts +81 -59
  397. package/src/tools/mcp.ts +111 -68
  398. package/src/tools/native-view.d.ts +47 -0
  399. package/src/tools/native-view.ts +102 -1
  400. package/src/tools/output-meta.d.ts +11 -1
  401. package/src/tools/output-meta.ts +23 -3
  402. package/src/tools/read.ts +7 -5
  403. package/src/tools/renderer.d.ts +37 -2
  404. package/src/tools/renderer.ts +42 -2
  405. package/src/tools/streaming-output.d.ts +20 -1
  406. package/src/tools/streaming-output.ts +154 -50
  407. package/src/tools/task.d.ts +2 -3
  408. package/src/tools/task.ts +115 -62
  409. package/src/tools/todo.ts +119 -30
  410. package/src/tools/wait.ts +34 -24
  411. package/src/tools/web-search.ts +1 -1
  412. package/src/tools/write.d.ts +9 -2
  413. package/src/tools/write.ts +147 -95
  414. package/src/tools/yield.d.ts +22 -0
  415. package/src/tools/yield.ts +225 -0
  416. package/src/tui.d.ts +36 -0
  417. package/src/tui.ts +666 -217
  418. package/src/utils.d.ts +6 -0
  419. package/src/utils.ts +19 -0
  420. package/src/vim.d.ts +19 -5
  421. package/src/vim.ts +333 -18
  422. package/src/windows-input-mode.d.ts +33 -1
  423. package/src/windows-input-mode.ts +212 -6
@@ -1,5 +1,6 @@
1
1
  import { encodeSixel } from "@linxiraos/pi-natives";
2
2
  import { $env, isBunTestRuntime, isTerminalHeadless, isWsl } from "@linxiraos/pi-utils/env";
3
+ import { writeTerminalSequence } from "./active-terminal";
3
4
  import { sendDesktopNotification, shouldDeliverDesktopNotification } from "./desktop-notify";
4
5
  import {
5
6
  detectKittyUnicodePlaceholdersSupport,
@@ -9,12 +10,17 @@ import {
9
10
  renderKittyPlaceholderLines,
10
11
  setKittyGraphics,
11
12
  } from "./kitty-graphics";
12
- import { isInsideHerdr, isInsideTerminalMultiplexer } from "./terminal-multiplexer";
13
- import { isInsideTmux, resolveTmuxClientTerminalName, wrapTmuxPassthrough, wrapTmuxPassthroughIfNeeded } from "./tmux";
13
+ import {
14
+ isInsideTerminalMultiplexer,
15
+ routeTerminalMultiplexerNotification,
16
+ terminalMultiplexerForTerm,
17
+ terminalMultiplexerSessions,
18
+ } from "./terminal-multiplexer";
19
+ import { resolveTmuxClientTerminalName, wrapTmuxPassthroughIfNeeded } from "./tmux";
14
20
  import type { HangulCompatibilityJamoWidth } from "./utils";
15
21
 
16
22
  export * from "./terminal-multiplexer";
17
- export { isInsideTmux, wrapTmuxPassthrough } from "./tmux";
23
+ export { wrapTmuxPassthrough } from "./tmux";
18
24
 
19
25
  export enum ImageProtocol {
20
26
  Kitty = "\x1b_G",
@@ -40,95 +46,10 @@ export type TerminalId =
40
46
  | "otty"
41
47
  | "rio"
42
48
  | "tern"
49
+ | "monstar"
43
50
  | "base"
44
51
  | "trueColor";
45
52
 
46
- const CMUX_NOTIFICATION_TITLE = "zeta";
47
- const CMUX_SURFACE_ID_PATTERN = /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/iu;
48
-
49
- /** Title and body for an out-of-band multiplexer notification (cmux, Herdr). */
50
- function notificationTitleAndBody(message: string | TerminalNotification): { title: string; body: string } {
51
- if (typeof message === "string") return { title: CMUX_NOTIFICATION_TITLE, body: message };
52
- return { title: message.title?.trim() || CMUX_NOTIFICATION_TITLE, body: message.body ?? "" };
53
- }
54
-
55
- /**
56
- * Route a notification through cmux when the process belongs to a concrete
57
- * surface. Workspace/socket state alone is not enough: only the injected
58
- * surface UUID identifies the pane that should receive the notification.
59
- * Returns whether cmux owns delivery so the caller can preserve every existing
60
- * terminal fallback unchanged when no valid surface is present.
61
- */
62
- function sendCmuxNotification(message: string | TerminalNotification, env: NodeJS.ProcessEnv = Bun.env): boolean {
63
- const surfaceId = env.CMUX_SURFACE_ID?.trim();
64
- if (!surfaceId || !CMUX_SURFACE_ID_PATTERN.test(surfaceId)) return false;
65
-
66
- const { title, body } = notificationTitleAndBody(message);
67
- try {
68
- const child = Bun.spawn({
69
- cmd: ["cmux", "notify", "--surface", surfaceId, "--title", title, "--body", body],
70
- stdin: "ignore",
71
- stdout: "ignore",
72
- stderr: "ignore",
73
- });
74
- child.unref();
75
- } catch {
76
- // A missing cmux binary leaves delivery to the existing terminal fallback.
77
- return false;
78
- }
79
- return true;
80
- }
81
-
82
- const HERDR_PANE_ID_PATTERN = /^[0-9A-Za-z:_-]{1,64}$/u;
83
- /**
84
- * `herdr notification show` takes the title as its first positional and reads
85
- * exactly these three values there as a help request; it has no `--`
86
- * terminator. Any other text, including one starting with `-`, is a title.
87
- */
88
- const HERDR_USAGE_TOKENS = new Set(["help", "--help", "-h"]);
89
-
90
- /**
91
- * Route a notification through Herdr when the process runs inside one of its
92
- * panes. Herdr multiplexes panes like tmux but swallows bare OSC 9 / OSC 99 and
93
- * has no DCS passthrough envelope, and its bell relay does not flag a
94
- * backgrounded tab — so without this branch a backgrounded pane gets no signal
95
- * at all that the agent finished or is waiting for input.
96
- *
97
- * `sound` maps the notification kind onto what Herdr offers: a question waiting
98
- * on the user and a turn that stopped with an error both need the human and
99
- * ring `request`, a settled turn rings `done`, anything else stays
100
- * silent. Returns whether Herdr owns delivery, so every existing terminal
101
- * fallback is preserved when the pane id is absent or the binary is missing.
102
- */
103
- function sendHerdrNotification(message: string | TerminalNotification, env: NodeJS.ProcessEnv = Bun.env): boolean {
104
- // Pane-only detection, like `isInsideHerdr`: an env-sanitizing launcher can
105
- // drop HERDR_ENV and keep the pane identity, and that pane can still be
106
- // backgrounded. The pane id itself is what the CLI needs, so it stays required.
107
- if (!isInsideHerdr(env)) return false;
108
- const paneId = env.HERDR_PANE_ID?.trim();
109
- if (!paneId || !HERDR_PANE_ID_PATTERN.test(paneId)) return false;
110
-
111
- const parsed = notificationTitleAndBody(message);
112
- const title = HERDR_USAGE_TOKENS.has(parsed.title) ? CMUX_NOTIFICATION_TITLE : parsed.title;
113
- const body = parsed.body;
114
- const kinds = typeof message === "string" ? [] : [message.type ?? []].flat();
115
- const sound =
116
- kinds.includes("ask") || kinds.includes("error") ? "request" : kinds.includes("completion") ? "done" : "none";
117
- try {
118
- const child = Bun.spawn({
119
- cmd: ["herdr", "notification", "show", title, "--body", body, "--sound", sound],
120
- stdin: "ignore",
121
- stdout: "ignore",
122
- stderr: "ignore",
123
- });
124
- child.unref();
125
- } catch {
126
- // A missing herdr binary leaves delivery to the existing terminal fallback.
127
- return false;
128
- }
129
- return true;
130
- }
131
-
132
53
  const IMAGE_MARKER_SCAN_LIMIT = 512;
133
54
  const SIXEL_MARKER_SCAN_LIMIT = 128;
134
55
  const KITTY_PLACEHOLDER_HIGH_SURROGATE = KITTY_PLACEHOLDER.charCodeAt(0);
@@ -234,34 +155,17 @@ export class TerminalInfo {
234
155
 
235
156
  sendNotification(message: string | TerminalNotification): void {
236
157
  if (isNotificationSuppressed() || isTerminalHeadless()) return;
237
- // Innermost surface first. A Herdr pane launched inside a cmux surface
238
- // inherits both `HERDR_PANE_ID` and the outer `CMUX_SURFACE_ID`; routing to
239
- // cmux there would flag the containing surface and leave the backgrounded
240
- // Herdr pane — the one actually waiting — without a sound or a marker.
241
- if (sendHerdrNotification(message)) return;
242
- if (sendCmuxNotification(message)) return;
243
158
  const formatted = this.formatNotification(message);
244
- // Under tmux, terminals whose notify protocol is OSC 9 / OSC 99 would
245
- // otherwise lose the notification entirely: tmux does not forward bare
246
- // OSC 9/99 to the outer terminal, and the bare sequence does not flag
247
- // tmux's own `monitor-bell` / `monitor-activity`. Wrap the OSC in tmux's
248
- // DCS passthrough envelope so users with `allow-passthrough on` still
249
- // get the desktop toast, then append a BEL so `monitor-bell` flags the
250
- // pane/window for everyone else — the only signal a backgrounded pane
251
- // has that the agent finished or is waiting for input. `Bell` protocol
252
- // already self-flags via tmux's bell monitoring, so leave it alone.
253
- if (this.notifyProtocol !== NotifyProtocol.Bell && isInsideTmux()) {
254
- process.stdout.write(`${wrapTmuxPassthrough(formatted)}\x07`);
255
- return;
256
- }
257
- // Zellij drops OSC 9/99 and has no DCS passthrough envelope, but raises its
258
- // `[!]` bell flag on a bare BEL — the same backgrounded-pane signal tmux
259
- // users get. So follow the (Zellij-swallowed) OSC with a plain BEL.
260
- if (this.notifyProtocol !== NotifyProtocol.Bell && isInsideZellij()) {
261
- process.stdout.write(`${formatted}\x07`);
262
- return;
263
- }
264
- process.stdout.write(formatted);
159
+ // Multiplexers that swallow or rewrite the terminal's notification get it
160
+ // first, innermost target first; the terminal sequence is the fallback.
161
+ const routed = routeTerminalMultiplexerNotification({
162
+ message,
163
+ env: Bun.env,
164
+ sequence: this.notifyProtocol === NotifyProtocol.Bell ? null : formatted,
165
+ write: writeTerminalSequence,
166
+ });
167
+ if (routed) return;
168
+ writeTerminalSequence(formatted);
265
169
  // VTE-family terminals (Ptyxis, GNOME Terminal, Tilix, …) plus Alacritty
266
170
  // and bare xterm-on-Wayland have no in-band escape that surfaces an
267
171
  // arbitrary desktop toast (#3685). When the chosen `notifyProtocol` is
@@ -274,15 +178,6 @@ export class TerminalInfo {
274
178
  }
275
179
  }
276
180
 
277
- /**
278
- * Whether the agent process is running inside a Zellij session. Read fresh on
279
- * each call (like {@link isInsideTmux}) so a session attached/detached mid-run
280
- * is observed and tests can toggle `Bun.env.ZELLIJ` per case.
281
- */
282
- export function isInsideZellij(env: NodeJS.ProcessEnv = Bun.env): boolean {
283
- return Boolean(env.ZELLIJ);
284
- }
285
-
286
181
  /**
287
182
  * Whether the agent process runs in an SSH session, so the terminal emulator
288
183
  * is remote and host-local input/keyboard modes cannot be assumed.
@@ -379,12 +274,12 @@ function advertisesSynchronizedOutput(termFeatures: string | undefined): boolean
379
274
  * 2. Positive `TERM_FEATURES` advertisement (`Sy`) — survives SSH/mux wrapping.
380
275
  * 3. Windows Terminal (1.24+) via `WT_SESSION`, on native win32 and the
381
276
  * WSL/SSH-fronted host alike.
382
- * 4. Herdr panes. Herdr is otherwise treated as a multiplexer so leaked
383
- * kitty/ghostty identities cannot enable placeholder graphics, but its
384
- * pane VTE is libghostty and already suppresses compositing while DEC 2026
385
- * is set. Leaving sync off lets CUP-diff paints and split write(2) chunks
386
- * composite as dirty-row patches — the live viewport tears, with the top
387
- * frozen while only the bottom refreshes.
277
+ * 4. A multiplexer session whose pane VTE honors DEC 2026 regardless of its
278
+ * DECRQM reply (Herdr's libghostty pane). Such a multiplexer still owns
279
+ * the grid, so leaked kitty/ghostty identities cannot enable placeholder
280
+ * graphics, but leaving sync off lets CUP-diff paints and split write(2)
281
+ * chunks composite as dirty-row patches — the live viewport tears, with
282
+ * the top frozen while only the bottom refreshes.
388
283
  * 5. Known direct terminals with confirmed support. SSH does *not* disable —
389
284
  * DEC 2026 passes through SSH when the outer terminal honors it.
390
285
  * 6. Everything else starts off, including risky multiplexers; the runtime
@@ -400,7 +295,7 @@ export function shouldEnableSynchronizedOutputByDefault(
400
295
 
401
296
  if (advertisesSynchronizedOutput(env.TERM_FEATURES)) return true;
402
297
  if (env.WT_SESSION && (!env.TERM_PROGRAM || env.TERM_PROGRAM.toLowerCase() === "windows_terminal")) return true;
403
- if (isInsideHerdr(env)) return true;
298
+ if (terminalMultiplexerSessions(env).some(multiplexer => multiplexer.honorsSynchronizedOutput)) return true;
404
299
 
405
300
  // Risky multiplexers start off even when an inner terminal id leaks through:
406
301
  // older tmux/screen synchronized-output handling is flaky and a mux may not
@@ -413,6 +308,7 @@ export function shouldEnableSynchronizedOutputByDefault(
413
308
  switch (terminalId) {
414
309
  case "kitty":
415
310
  case "ghostty":
311
+ case "monstar":
416
312
  case "wezterm":
417
313
  case "iterm2":
418
314
  case "alacritty":
@@ -478,6 +374,7 @@ export function detectStyledUnderlineSupport(terminalId: TerminalId, env: NodeJS
478
374
  switch (terminalId) {
479
375
  case "kitty":
480
376
  case "ghostty":
377
+ case "monstar":
481
378
  case "wezterm":
482
379
  return true;
483
380
  case "iterm2": {
@@ -517,48 +414,35 @@ export function hyperlinksUserOverride(env: NodeJS.ProcessEnv = Bun.env): boolea
517
414
  return null;
518
415
  }
519
416
 
520
- /**
521
- * Parse tmux's self-reported version from `TERM_PROGRAM_VERSION`. tmux sets
522
- * `TERM_PROGRAM=tmux` and `TERM_PROGRAM_VERSION=<version>` automatically since
523
- * 3.2a; older releases (or any path that does not surface the version) yield
524
- * `null` and the caller treats tmux conservatively.
525
- */
526
- function parseTmuxVersionFromEnv(env: NodeJS.ProcessEnv): { major: number; minor: number } | null {
527
- if (env.TERM_PROGRAM?.toLowerCase() !== "tmux") return null;
528
- return parseMajorMinorVersion(env.TERM_PROGRAM_VERSION);
529
- }
530
-
531
417
  /**
532
418
  * Whether OSC 8 hyperlinks should be enabled by default.
533
419
  *
534
420
  * Policy (highest precedence first):
535
421
  * 1. Explicit user override (`PI_NO_HYPERLINKS=1` off, `PI_FORCE_HYPERLINKS=1`
536
422
  * on). Opt-out wins ties.
537
- * 2. Herdr pane with no nested screen/tmux: on. Herdr hides the outer
538
- * terminal (`TERM=xterm-256color`, no `TERM_PROGRAM`), but it renders
539
- * OSC 8 in its own grid and opens links itself on Ctrl+click, so the
540
- * outer terminal's support does not matter.
423
+ * 2. Every multiplexer session with a hyperlink policy renders OSC 8 itself
424
+ * (`render`, e.g. Herdr): on. Herdr hides the outer terminal
425
+ * (`TERM=xterm-256color`, no `TERM_PROGRAM`), so the outer terminal's
426
+ * support does not matter.
541
427
  * 2b. Zeta workbench pane with no nested screen/tmux: on, for the same
542
428
  * reason — the workbench's terminal simulator renders OSC 8 itself and
543
429
  * opens links on Ctrl+click (`ZETA_WORKBENCH=1` is injected into every
544
430
  * pane PTY; the outer terminal is invisible from inside).
545
431
  * 3. Static terminal capability — terminals whose {@link TerminalInfo} marks
546
432
  * `hyperlinks: false` (e.g. `base`) stay off unless the user forced on.
547
- * 4. GNU screen's explicit session marker (`STY`) always off, even if tmux is
548
- * also present: a screen layer anywhere in the path cannot forward OSC 8.
549
- * 5. tmux session (`TMUX` set): enabled when tmux self-reports >= 3.4 via
550
- * `TERM_PROGRAM_VERSION` (tmux 3.4 stores OSC 8 as a cell attribute and
551
- * forwards it to outer terminals whose `terminal-features` include
552
- * `hyperlinks`). Older or unknown versions stay off; on outer terminals
553
- * without the feature configured, tmux silently drops the sequence —
554
- * identical to today. Checked before the screen-family TERM heuristic
555
- * because tmux's historical `default-terminal` is `screen-256color`, so
556
- * `TERM=screen*` inside a tmux session must NOT short-circuit to off.
557
- * 6. screen-family TERM without `TMUX` always off: screen never gained OSC 8
558
- * support.
559
- * 7. tmux-family TERM without `TMUX` env — unusual (e.g. inspection scripts);
560
- * no version available, so off.
561
- * 8. Otherwise honor the static terminal capability.
433
+ * 4. A session that never forwards OSC 8 (`drop`, e.g. GNU screen's `STY`)
434
+ * always off, even when a forwarding multiplexer is also present.
435
+ * 5. Forwarding sessions (`forward`, e.g. `TMUX`): enabled when every one
436
+ * self-reports at least its forwarding version via `TERM_PROGRAM` /
437
+ * `TERM_PROGRAM_VERSION` (tmux 3.4). Older or unknown versions stay off;
438
+ * on outer terminals without the feature configured, tmux silently drops
439
+ * the sequence. Checked before the TERM heuristic because tmux's
440
+ * historical `default-terminal` is `screen-256color`, so `TERM=screen*`
441
+ * inside a tmux session must NOT short-circuit to off.
442
+ * 6. TERM naming a multiplexer whose session markers were stripped: off,
443
+ * since no session proves the multiplexer forwards OSC 8 (screen never
444
+ * does; tmux has no version to check).
445
+ * 7. Otherwise honor the static terminal capability.
562
446
  */
563
447
  export function shouldEnableHyperlinksByDefault(
564
448
  env: NodeJS.ProcessEnv = Bun.env,
@@ -567,29 +451,29 @@ export function shouldEnableHyperlinksByDefault(
567
451
  const override = hyperlinksUserOverride(env);
568
452
  if (override !== null) return override;
569
453
 
570
- if (isInsideHerdr(env) && !env.STY && !env.TMUX) return true;
454
+ const policies = terminalMultiplexerSessions(env).flatMap(multiplexer =>
455
+ multiplexer.hyperlinks === undefined ? [] : [multiplexer.hyperlinks],
456
+ );
457
+ if (policies.length > 0 && policies.every(policy => policy === "render")) return true;
571
458
 
572
459
  if (isInsideZetaWorkbench(env) && !env.STY && !env.TMUX) return true;
573
460
 
574
461
  if (!getTerminalInfo(terminalId).hyperlinks) return false;
575
462
 
576
- // STY is GNU screen's explicit session marker. It vetoes tmux enabling when
577
- // multiplexers are nested because screen cannot forward OSC 8 anywhere in the
578
- // path.
579
- if (env.STY) return false;
580
-
581
- // tmux check before TERM heuristics: TMUX is the authoritative current-session
582
- // signal and supersedes TERM, which may be `screen-256color` under tmux's
583
- // historical default-terminal setting.
584
- if (env.TMUX) {
585
- const version = parseTmuxVersionFromEnv(env);
586
- if (!version) return false;
587
- return version.major > 3 || (version.major === 3 && version.minor >= 4);
463
+ if (policies.includes("drop")) return false;
464
+
465
+ const forwarders = policies.flatMap(policy => (typeof policy === "object" ? [policy.forward] : []));
466
+ if (forwarders.length > 0) {
467
+ const termProgram = env.TERM_PROGRAM?.toLowerCase();
468
+ return forwarders.every(forward => {
469
+ if (termProgram !== forward.termProgram) return false;
470
+ const version = parseMajorMinorVersion(env.TERM_PROGRAM_VERSION);
471
+ if (!version) return false;
472
+ return version.major > forward.major || (version.major === forward.major && version.minor >= forward.minor);
473
+ });
588
474
  }
589
475
 
590
- const term = env.TERM?.toLowerCase() ?? "";
591
- if (term.startsWith("screen")) return false;
592
- if (term.startsWith("tmux")) return false;
476
+ if (terminalMultiplexerForTerm(env)) return false;
593
477
 
594
478
  return true;
595
479
  }
@@ -663,10 +547,14 @@ export function resolveImageProtocol(
663
547
  if (imageProtocol !== null && isPaseoEmbedder(env)) {
664
548
  return null;
665
549
  }
666
- // Herdr owns the pane grid but does not expose whether the attached client
667
- // enabled its experimental Kitty renderer. Outer-terminal identity variables
668
- // can leak into the pane, so only the explicit protocol override is safe.
669
- if (imageProtocol !== null && isInsideHerdr(env)) {
550
+ // Some multiplexers own the pane grid without exposing whether the attached
551
+ // client renders graphics (Herdr's experimental Kitty renderer). Outer-
552
+ // terminal identity variables can leak into the pane, so only the explicit
553
+ // protocol override is safe.
554
+ if (
555
+ imageProtocol !== null &&
556
+ terminalMultiplexerSessions(env).some(multiplexer => multiplexer.imagesRequireOverride)
557
+ ) {
670
558
  return null;
671
559
  }
672
560
  return imageProtocol;
@@ -729,6 +617,19 @@ const KNOWN_TERMINALS = Object.freeze({
729
617
  // renders natively (Tern Surface Protocol) is decided by the `hello`
730
618
  // handshake alone, never by this identity.
731
619
  tern: new TerminalInfo("tern", ImageProtocol.Kitty, true, true, NotifyProtocol.Osc99),
620
+ // Monstar (rockorager/monstar) is a Wayland terminal built on libghostty. It
621
+ // sets TERM=monstar and answers XTVERSION with `monstar <version>`. It
622
+ // documents Kitty graphics with Unicode placeholders, OSC 8 hyperlinks, and
623
+ // synchronized output. libghostty reports Hangul Jamo as 2 cells, and the
624
+ // Monstar renderer draws curly underlines in the SGR 58 underline color, so
625
+ // the id-keyed allowlists treat Monstar like Ghostty.
626
+ // Monstar clears OSC 9;4 progress after 15 s without an update, so it also
627
+ // gets the Ghostty progress keepalive. The Ghostty initial image delay stays
628
+ // Ghostty-only: it works around a Ghostty app startup race, not libghostty.
629
+ // Monstar turns OSC 9 into a D-Bus notification with a default action;
630
+ // activating it focuses the Monstar window. The BEL path uses omp's own
631
+ // `notify-send` fallback instead, which cannot focus a window.
632
+ monstar: new TerminalInfo("monstar", ImageProtocol.Kitty, true, true, NotifyProtocol.Osc9, false, false, false, 2),
732
633
  });
733
634
 
734
635
  /** Resolve terminal identity from environment markers used by common emulators. */
@@ -750,6 +651,7 @@ export function detectTerminalId(env: NodeJS.ProcessEnv = Bun.env): TerminalId {
750
651
  if (caseEq(program, "otty")) return "otty";
751
652
  if (caseEq(program, "rio")) return "rio";
752
653
  if (caseEq(program, "tern")) return "tern";
654
+ if (caseEq(program, "monstar")) return "monstar";
753
655
  return null;
754
656
  }
755
657
 
@@ -781,6 +683,7 @@ export function detectTerminalId(env: NodeJS.ProcessEnv = Bun.env): TerminalId {
781
683
  if (clientProgramId) return clientProgramId;
782
684
 
783
685
  if (TERM?.toLowerCase().includes("ghostty")) return "ghostty";
686
+ if (TERM && caseEq(TERM, "monstar")) return "monstar";
784
687
 
785
688
  if (COLORTERM) {
786
689
  if (caseEq(COLORTERM, "truecolor") || caseEq(COLORTERM, "24bit")) return "trueColor";
@@ -931,6 +834,13 @@ export interface ImageRenderOptions {
931
834
  placementId?: number;
932
835
  /** When true (Kitty + {@link imageId}), also return the one-time transmit sequence. */
933
836
  includeTransmit?: boolean;
837
+ /**
838
+ * SIXEL sequence for the target pixel size renderImage computes, so the
839
+ * caller can encode off the JS thread: answer `undefined` while the encode
840
+ * is pending (the image's rows stay reserved) and `null` once it failed.
841
+ * Without a provider, renderImage encodes synchronously.
842
+ */
843
+ sixel?: (widthPx: number, heightPx: number) => string | null | undefined;
934
844
  }
935
845
 
936
846
  // Default cell dimensions - updated by TUI when terminal responds to query
@@ -1024,8 +934,7 @@ export function encodeKittyPlacement(options: {
1024
934
  /**
1025
935
  * Exact shape of the direct-placement line {@link Image} emits as its block's
1026
936
  * last row: optional `ESC 7` + `CUU(rows-1)` prefix, the {@link encodeKittyPlacement}
1027
- * APC, optional `ESC 8` suffix. tmux-passthrough-wrapped lines deliberately do
1028
- * not match (passthrough placements stay untouched).
937
+ * APC, optional `ESC 8` suffix. The parser unwraps tmux passthrough first.
1029
938
  */
1030
939
  const KITTY_DIRECT_PLACEMENT_LINE =
1031
940
  /^(?:\x1b7(?:\x1b\[(\d+)A)?)?\x1b_Ga=p,q=2,C=1,i=(\d+)(?:,p=(\d+))?(?:,c=(\d+))?(?:,r=(\d+))?\x1b\\(?:\x1b8)?$/;
@@ -1040,10 +949,15 @@ export interface ParsedKittyPlacementLine {
1040
949
  /**
1041
950
  * Parse a frame line that consists solely of a Kitty direct placement (the
1042
951
  * last line of an {@link Image} block). Returns null for anything else —
1043
- * placeholder grids, tmux-wrapped placements, sixel/iTerm2 payloads — so
952
+ * placeholder grids, sixel/iTerm2 payloads — so
1044
953
  * callers fall back to writing the line verbatim.
1045
954
  */
1046
955
  export function parseKittyDirectPlacementLine(line: string): ParsedKittyPlacementLine | null {
956
+ if (line.includes("\x1bPtmux;")) {
957
+ line = line.replace(/\x1bPtmux;((?:[^\x1b]|\x1b\x1b)*)\x1b\\/g, (_match, payload: string) =>
958
+ payload.replaceAll("\x1b\x1b", "\x1b"),
959
+ );
960
+ }
1047
961
  const m = KITTY_DIRECT_PLACEMENT_LINE.exec(line);
1048
962
  if (!m) return null;
1049
963
  const columns = m[4] !== undefined ? Number(m[4]) : 0;
@@ -1088,9 +1002,7 @@ export function encodeKittyPlacementLine(options: {
1088
1002
  const srcY = Math.floor((options.imageHeightPx * hiddenRows) / options.rows);
1089
1003
  params.push(`y=${srcY}`, `h=${Math.max(1, options.imageHeightPx - srcY)}`);
1090
1004
  }
1091
- // No tmux passthrough: inside tmux the component's own line arrives
1092
- // wrapped, never parses, and never reaches this rewrite.
1093
- const apc = `\x1b_G${params.join(",")}\x1b\\`;
1005
+ const apc = wrapTmuxPassthroughIfNeeded(`\x1b_G${params.join(",")}\x1b\\`);
1094
1006
  const cuu = visibleRows - 1;
1095
1007
  return cuu > 0 ? `\x1b7\x1b[${cuu}A${apc}\x1b8` : apc;
1096
1008
  }
@@ -1117,8 +1029,8 @@ export function encodeKittyDeleteAllImages(): string {
1117
1029
  /**
1118
1030
  * Delete a single placement of an image (`d=i`, lowercase): removes its cells
1119
1031
  * and registry entry but keeps the transmitted data, so a later `a=p` under a
1120
- * fresh placement id needs no retransmit. Used to clear stale placement-epoch
1121
- * entries after a destructive history clear.
1032
+ * fresh placement id needs no retransmit. The frame writer uses this before
1033
+ * replacing a live image without deleting its archived copies.
1122
1034
  */
1123
1035
  export function encodeKittyDeletePlacement(imageId: number, placementId: number): string {
1124
1036
  return wrapTmuxPassthroughIfNeeded(`\x1b_Ga=d,d=i,i=${imageId},p=${placementId},q=2\x1b\\`);
@@ -1331,6 +1243,19 @@ export function getImageDimensions(base64Data: string, mimeType: string): ImageD
1331
1243
  return null;
1332
1244
  }
1333
1245
 
1246
+ /**
1247
+ * SIXEL sequence for `base64Data` at the given pixel size, encoded on the JS
1248
+ * thread; `null` when the encode fails. For output that cannot wait for an
1249
+ * off-thread encode.
1250
+ */
1251
+ export function encodeSixelNow(base64Data: string, widthPx: number, heightPx: number): string | null {
1252
+ try {
1253
+ return encodeSixel(new Uint8Array(Buffer.from(base64Data, "base64")), widthPx, heightPx);
1254
+ } catch {
1255
+ return null;
1256
+ }
1257
+ }
1258
+
1334
1259
  export function renderImage(
1335
1260
  base64Data: string,
1336
1261
  imageDimensions: ImageDimensions,
@@ -1384,29 +1309,28 @@ export function renderImage(
1384
1309
  }
1385
1310
 
1386
1311
  if (TERMINAL.imageProtocol === ImageProtocol.Sixel) {
1387
- try {
1388
- // SIXEL encodes in 6-pixel vertical bands. A height that is not a
1389
- // multiple of 6 is padded with transparent rows, but the terminal
1390
- // still allocates cell rows for the padded height. When the padded
1391
- // height crosses a cell boundary the terminal uses one more row
1392
- // than fit.rows, so the next line of content overwrites the bottom
1393
- // of the image — a visible slice stripped from the image. Round the
1394
- // encode height DOWN to the largest multiple of 6 that fits within
1395
- // the requested row budget, so the band boundary aligns without
1396
- // padding and the reserved row count never exceeds fit.rows. Scale
1397
- // the width by the same ratio so resize_exact preserves the aspect
1398
- // ratio instead of squashing the image vertically.
1399
- const rawHeightPx = Math.max(1, fit.rows * cellDims.heightPx);
1400
- const targetHeightPx = Math.max(6, Math.floor(rawHeightPx / 6) * 6);
1401
- const heightScale = targetHeightPx / rawHeightPx;
1402
- const targetWidthPx = Math.max(1, Math.round(fit.columns * cellDims.widthPx * heightScale));
1403
- const rows = Math.max(1, Math.ceil(targetHeightPx / cellDims.heightPx));
1404
- const decoded = new Uint8Array(Buffer.from(base64Data, "base64"));
1405
- const sequence = encodeSixel(decoded, targetWidthPx, targetHeightPx);
1406
- return { sequence, rows };
1407
- } catch {
1408
- return null;
1409
- }
1312
+ // SIXEL encodes in 6-pixel vertical bands. A height that is not a
1313
+ // multiple of 6 is padded with transparent rows, but the terminal
1314
+ // still allocates cell rows for the padded height. When the padded
1315
+ // height crosses a cell boundary the terminal uses one more row
1316
+ // than fit.rows, so the next line of content overwrites the bottom
1317
+ // of the image — a visible slice stripped from the image. Round the
1318
+ // encode height DOWN to the largest multiple of 6 that fits within
1319
+ // the requested row budget, so the band boundary aligns without
1320
+ // padding and the reserved row count never exceeds fit.rows. Scale
1321
+ // the width by the same ratio so resize_exact preserves the aspect
1322
+ // ratio instead of squashing the image vertically.
1323
+ const rawHeightPx = Math.max(1, fit.rows * cellDims.heightPx);
1324
+ const targetHeightPx = Math.max(6, Math.floor(rawHeightPx / 6) * 6);
1325
+ const heightScale = targetHeightPx / rawHeightPx;
1326
+ const targetWidthPx = Math.max(1, Math.round(fit.columns * cellDims.widthPx * heightScale));
1327
+ const rows = Math.max(1, Math.ceil(targetHeightPx / cellDims.heightPx));
1328
+ const sequence = options.sixel
1329
+ ? options.sixel(targetWidthPx, targetHeightPx)
1330
+ : encodeSixelNow(base64Data, targetWidthPx, targetHeightPx);
1331
+ if (sequence === null) return null;
1332
+ // Undefined while the provider's encode is pending: the rows stay reserved.
1333
+ return { sequence, rows };
1410
1334
  }
1411
1335
  if (TERMINAL.imageProtocol === ImageProtocol.Iterm2) {
1412
1336
  const sequence = encodeITerm2(base64Data, {