@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
package/src/terminal.ts CHANGED
@@ -3,9 +3,11 @@ import * as fs from "node:fs";
3
3
  import { TtyWriter } from "@linxiraos/pi-natives";
4
4
  import { $env, isBunTestRuntime, isTerminalHeadless, isWsl } from "@linxiraos/pi-utils/env";
5
5
  import * as logger from "@linxiraos/pi-utils/logger";
6
+ import { popLoopPhase, pushLoopPhase } from "@linxiraos/pi-utils/loop-phase";
6
7
  import * as postmortem from "@linxiraos/pi-utils/postmortem";
7
8
  import { restoreTerminalStderr, suppressTerminalStderr } from "@linxiraos/pi-utils/stderr-guard";
8
9
  import { TSP_VERSION } from "@linxiraos/pi-wire";
10
+ import { getActiveTerminal, registerStdoutErrorHandler, setActiveTerminal } from "./active-terminal";
9
11
  import {
10
12
  encodeBundledGlyphRegistrations,
11
13
  encodeGlyphCoverageQuery,
@@ -26,15 +28,22 @@ import {
26
28
  setTerminalGlyphProtocol,
27
29
  TERMINAL,
28
30
  } from "./terminal-capabilities";
29
- import { isInsideTmux, wrapTmuxPassthrough } from "./tmux";
31
+ import { tmuxMultiplexer } from "./multiplexers/tmux";
32
+ import type { TerminalMultiplexerBackgroundColorCache } from "./multiplexers/types";
33
+ import { terminalMultiplexerSessions } from "./terminal-multiplexer";
34
+ import { setTmuxPaneApcTitles, tmuxSessionSpeaksTsp } from "./tmux";
30
35
  import { setHangulCompatibilityJamoWidth } from "./utils";
31
36
  import { translateWindowsAltGrSequence } from "./windows-altgr";
32
- import { Win32InputModeDecoder } from "./windows-input-mode";
37
+ import { Win32InputModeDecoder, Win32PasteMarkerNormalizer } from "./windows-input-mode";
38
+
39
+ export { writeTerminalSequence, writeThroughActiveTerminal } from "./active-terminal";
33
40
 
34
41
  const TERMINAL_PROGRESS_KEEPALIVE_MS = 1000;
35
42
  const TERMINAL_PROGRESS_ACTIVE_SEQUENCE = "\x1b]9;4;3\x07";
36
43
  const TERMINAL_PROGRESS_CLEAR_SEQUENCE = "\x1b]9;4;0;\x07";
37
44
  const WINDOWS_TERMINAL_OSC11_POLL_MS = 30_000;
45
+ /** How long a relayed ESC record waits for the rest of its sequence before acting as Escape (matches StdinBuffer). */
46
+ const WIN32_RELAYED_ESCAPE_TIMEOUT_MS = 75;
38
47
 
39
48
  /**
40
49
  * Terminal → program TSP messages through a Windows ConPTY: its input parser
@@ -43,6 +52,9 @@ const WINDOWS_TERMINAL_OSC11_POLL_MS = 30_000;
43
52
  */
44
53
  const TSP_OSC_PREFIX = `\x1b]877;${TSP_PREFIX.slice(2)}`;
45
54
 
55
+ /** How long a TSP hello waits for its reply inside tmux, which answers DA1 itself. */
56
+ const TSP_TMUX_HELLO_TIMEOUT_MS = 1000;
57
+
46
58
  /**
47
59
  * The TSP message a reassembled input string holds, in APC form, or
48
60
  * `undefined` while its terminator has not arrived. An OSC 877 message may end
@@ -90,6 +102,9 @@ function shouldPollWindowsTerminalAppearance(env: NodeJS.ProcessEnv = Bun.env):
90
102
  */
91
103
  const MAX_CONPTY_WRITE_CHUNK_BYTES = 16 * 1024;
92
104
 
105
+ /** An OSC 52 clipboard write: selection, base64 payload, BEL or ST terminator. */
106
+ const OSC52_CLIPBOARD_WRITE = /\x1b\]52;([^;\x07\x1b]*);([^\x07\x1b]*)(\x07|\x1b\\)/g;
107
+
93
108
  /**
94
109
  * Split `data` into chunks whose encoded UTF-8 byte length is no greater than
95
110
  * `maxChunkBytes`, preferring a line boundary (`\n`) as the cut point so
@@ -280,8 +295,6 @@ export class StdoutStallWatchdog {
280
295
  * Minimal terminal interface for TUI
281
296
  */
282
297
 
283
- // Track active terminal for emergency cleanup on crash
284
- let activeTerminal: ProcessTerminal | null = null;
285
298
  // Track if a terminal was ever started (for emergency restore logic)
286
299
  let terminalEverStarted = false;
287
300
  // Whether the alternate screen buffer is currently active (mirrors the TUI's
@@ -306,38 +319,6 @@ function registerPostmortemTerminalRestore(): void {
306
319
  export function setAltScreenActive(active: boolean): void {
307
320
  altScreenActive = active;
308
321
  }
309
- /**
310
- * Route an out-of-band escape sequence (e.g. an OSC title update) through the
311
- * active terminal's output path. While a TUI owns stdout, frame paints go
312
- * through the off-thread write pump and can split across multiple write(2)
313
- * calls; a direct main-thread `process.stdout.write` can land between two of
314
- * them — mid escape sequence — and the host terminal then prints the payload
315
- * as literal text at the cursor position. Returns false when no terminal has
316
- * started, in which case the caller owns stdout and may write directly.
317
- */
318
- export function writeThroughActiveTerminal(data: string): boolean {
319
- if (!activeTerminal) return false;
320
- activeTerminal.write(data);
321
- return true;
322
- }
323
-
324
- const stdoutErrorHandlers = new Set<(err: Error) => void>();
325
- let stdoutErrorListenerInstalled = false;
326
-
327
- function onStdoutError(err: Error): void {
328
- for (const handler of stdoutErrorHandlers) handler(err);
329
- }
330
-
331
- function registerStdoutErrorHandler(handler: (err: Error) => void): () => void {
332
- stdoutErrorHandlers.add(handler);
333
- if (!stdoutErrorListenerInstalled) {
334
- process.stdout.on("error", onStdoutError);
335
- stdoutErrorListenerInstalled = true;
336
- }
337
- return () => {
338
- stdoutErrorHandlers.delete(handler);
339
- };
340
- }
341
322
 
342
323
  const STD_INPUT_HANDLE = -10;
343
324
  const ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200;
@@ -423,7 +404,7 @@ export function emergencyTerminalRestore(): void {
423
404
  // Crash paths must surface subsequent stderr (fatal reports) on the
424
405
  // real terminal; no-op when the stderr guard is inactive.
425
406
  restoreTerminalStderr();
426
- const terminal = activeTerminal;
407
+ const terminal = getActiveTerminal();
427
408
  if (terminal) {
428
409
  // Keyboard enhancement state is screen-local: pop the alt-screen
429
410
  // frame before leaving it, then let stop() pop zeta's main-screen frame.
@@ -527,7 +508,12 @@ export const CURSOR_SHAPE_CODES: Record<CursorShape, number> = {
527
508
  bar: 6,
528
509
  };
529
510
  export interface Terminal {
530
- // Start the terminal with input, resize, and host-disconnect handlers.
511
+ /**
512
+ * Start the terminal with input, resize, and host-disconnect handlers.
513
+ * Unless input is deferred, this sends capability probes; a terminal that
514
+ * cannot parse one may leave concealed bytes on the cursor row, so the
515
+ * caller repaints after starting.
516
+ */
531
517
  start(
532
518
  onInput: (data: string) => void,
533
519
  onResize: () => void,
@@ -539,8 +525,10 @@ export interface Terminal {
539
525
  * Take ownership of stdin after a `deferInput` start: enable raw mode,
540
526
  * attach input handlers, and run the capability probes start() skipped.
541
527
  * Bytes the user typed in cooked mode meanwhile are replayed through
542
- * `onInput`. No-op when input was never deferred. Optional so custom
543
- * Terminals built against older pi-tui versions keep working.
528
+ * `onInput`. No-op when input was never deferred. Like {@link start}, the
529
+ * probes may leave concealed bytes on the cursor row, so the caller
530
+ * repaints afterwards. Optional so custom Terminals built against older
531
+ * pi-tui versions keep working.
544
532
  */
545
533
  enableInput?(): void;
546
534
 
@@ -672,6 +660,12 @@ export interface Terminal {
672
660
  * `status` is the DECRPM value when the terminal answered DECRQM.
673
661
  */
674
662
  onPrivateModeReport?(callback: PrivateModeReportHandler): void;
663
+ /**
664
+ * Report whether DA1 advertises SIXEL (attribute 4), replaying the latest
665
+ * advertisement to late subscribers. A missing attribute does not rule out
666
+ * support discovered through another graphics query.
667
+ */
668
+ onSixelSupport?(callback: (supported: boolean) => void): void;
675
669
  /**
676
670
  * Register a callback fired once the startup Glyph Protocol handshake
677
671
  * resolves (see {@link GlyphProtocolReportHandler}). A subscriber that
@@ -733,9 +727,18 @@ function parseOsc99KeyValues(section: string): Map<string, string> {
733
727
  }
734
728
  return values;
735
729
  }
730
+ /**
731
+ * Brackets the startup capability probes. A terminal that cannot parse a probe
732
+ * prints it as text at the cursor (Apple Terminal drops `ESC _`/`ESC \` and
733
+ * prints APC payloads, and abandons DECRQM at its `$`, printing the final `p`).
734
+ * DECSC, autowrap off, and SGR 8 (conceal) keep that text invisible and on the
735
+ * cursor row, so it can neither wrap nor scroll the screen; DECRC puts the
736
+ * cursor back. The screen owner repaints the row afterwards (see
737
+ * {@link Terminal.start} / {@link Terminal.enableInput}).
738
+ */
739
+ const PROBE_GUARD_BEGIN = "\x1b7\x1b[?7l\x1b[8m";
740
+ const PROBE_GUARD_END = "\x1b[28m\x1b[?7h\x1b8";
736
741
  const XTERM_SCROLL_TO_BOTTOM_MODES = [1010, 1011] as const;
737
- type Osc11QueryRoute = "direct" | "tmux";
738
- const TMUX_OSC11_CACHE_REFRESH_DELAY_MS = 100;
739
742
 
740
743
  function isXtermScrollToBottomMode(mode: number): boolean {
741
744
  return mode === 1010 || mode === 1011;
@@ -786,6 +789,9 @@ export class ProcessTerminal implements Terminal {
786
789
  // Windows console fallback when kitty is unavailable: key records arrive as
787
790
  // win32-input-mode sequences and are decoded before reaching the handler.
788
791
  #win32InputDecoder?: Win32InputModeDecoder;
792
+ #win32PasteNormalizer?: Win32PasteMarkerNormalizer;
793
+ // Releases a relayed lone ESC the decoder held for a sequence tail that never came.
794
+ #win32PendingFlushTimer?: Timer;
789
795
  #stdinBuffer?: StdinBuffer;
790
796
  #stdinDataHandler?: (data: string) => void;
791
797
  #disconnectHandler?: () => void;
@@ -853,10 +859,10 @@ export class ProcessTerminal implements Terminal {
853
859
  #appearance: TerminalAppearance | undefined;
854
860
  #osc11Pending = false;
855
861
  #osc11ActiveToken?: TerminalAppearanceRequestToken;
856
- #osc11QueuedQuery?: { route: Osc11QueryRoute; token?: TerminalAppearanceRequestToken };
862
+ #osc11QueuedQuery?: { cache?: TerminalMultiplexerBackgroundColorCache; token?: TerminalAppearanceRequestToken };
857
863
  #nextAppearanceRequestToken = 1;
858
864
  #osc11ResponseBuffer = "";
859
- #osc11TmuxRefreshTimer?: Timer;
865
+ #osc11CacheRefreshTimer?: Timer;
860
866
  #osc99PendingId: string | undefined;
861
867
  #osc99ResponseBuffer = "";
862
868
  #osc99Capabilities = new Map<string, string>();
@@ -868,9 +874,15 @@ export class ProcessTerminal implements Terminal {
868
874
  #glyphProtocolReplyBuffer = "";
869
875
  #glyphProtocolResult: boolean | undefined;
870
876
  #glyphProtocolCallbacks: GlyphProtocolReportHandler[] = [];
877
+ #sixelSupport: boolean | undefined;
878
+ #sixelSupportCallbacks: Array<(supported: boolean) => void> = [];
871
879
  #tspPending = false;
872
880
  #tspResult: TspHello | null | undefined;
873
881
  #tspCallbacks: TspHelloHandler[] = [];
882
+ /** Hello deadline inside tmux, where no DA1 sentinel can end the probe. */
883
+ #tspTimeout: NodeJS.Timeout | undefined;
884
+ /** This tmux pane's APC titles are off for TSP and must be restored. */
885
+ #tspTmuxPaneGated = false;
874
886
  #tspReplyBuffer = "";
875
887
  #privateCsiResponseBuffer = "";
876
888
  #da1SentinelOwners: Da1SentinelOwner[] = [];
@@ -886,10 +898,9 @@ export class ProcessTerminal implements Terminal {
886
898
  #mode2031DebounceTimer?: Timer;
887
899
  #windowsTerminalAppearancePollTimer?: Timer;
888
900
  #progressActive = false;
889
- // Ghostty expires OSC 9;4 state without a heartbeat. Persistent hosts such
901
+ // Ghostty and Monstar expire OSC 9;4 state without a heartbeat. Persistent hosts such
890
902
  // as Windows Terminal restart their indeterminate animation on every write.
891
- readonly #keepProgressAlive = TERMINAL.id === "ghostty";
892
- #bracketedPasteRefreshTimer?: Timer;
903
+ readonly #keepProgressAlive = TERMINAL.id === "ghostty" || TERMINAL.id === "monstar";
893
904
  #progressTimer?: Timer;
894
905
 
895
906
  constructor(options?: ProcessTerminalOptions) {
@@ -955,10 +966,11 @@ export class ProcessTerminal implements Terminal {
955
966
 
956
967
  /**
957
968
  * Re-query the terminal background through the startup DA1-sentinel FIFO,
958
- * pending/queued gating, parsing, dedup, and appearance callbacks. Inside
959
- * tmux, only this explicit path first passes an OSC 11 query to the outer
960
- * terminal, waits briefly for tmux to consume the response into its cache,
961
- * then reads that cache with a direct query. The outer query deliberately has
969
+ * pending/queued gating, parsing, dedup, and appearance callbacks. Inside a
970
+ * multiplexer that answers OSC 11 from a cache (tmux), only this explicit
971
+ * path first passes an OSC 11 query to the outer terminal, waits briefly for
972
+ * the multiplexer to consume the response into its cache, then reads that
973
+ * cache with a direct query. The outer query deliberately has
962
974
  * no DA1 sentinel: multiplexers can decode a fragmented DA1 response as a key
963
975
  * sequence and leak the remaining bytes into the editor. Startup and Mode 2031
964
976
  * probes remain direct. Suppressed while inactive, headless, or after teardown.
@@ -969,7 +981,10 @@ export class ProcessTerminal implements Terminal {
969
981
  if (token >= this.#nextAppearanceRequestToken) {
970
982
  this.#nextAppearanceRequestToken = token + 1;
971
983
  }
972
- this.#queryBackgroundColor(isInsideTmux() ? "tmux" : "direct", token);
984
+ const cache = terminalMultiplexerSessions().find(
985
+ multiplexer => multiplexer.backgroundColorCache,
986
+ )?.backgroundColorCache;
987
+ this.#queryBackgroundColor(cache, token);
973
988
  return token;
974
989
  }
975
990
 
@@ -984,6 +999,11 @@ export class ProcessTerminal implements Terminal {
984
999
  if (this.#glyphProtocolResult !== undefined) callback(this.#glyphProtocolResult);
985
1000
  }
986
1001
 
1002
+ onSixelSupport(callback: (supported: boolean) => void): void {
1003
+ this.#sixelSupportCallbacks.push(callback);
1004
+ if (this.#sixelSupport !== undefined) callback(this.#sixelSupport);
1005
+ }
1006
+
987
1007
  onTspHello(callback: TspHelloHandler): void {
988
1008
  this.#tspCallbacks.push(callback);
989
1009
  if (this.#tspResult !== undefined) callback(this.#tspResult);
@@ -1025,8 +1045,9 @@ export class ProcessTerminal implements Terminal {
1025
1045
  if (this.#headless) return;
1026
1046
  registerPostmortemTerminalRestore();
1027
1047
 
1028
- // Register for emergency cleanup
1029
- activeTerminal = this;
1048
+ // Own stdout: out-of-band writers route through this terminal, and the
1049
+ // emergency restore finds it on crash.
1050
+ setActiveTerminal(this);
1030
1051
  terminalEverStarted = true;
1031
1052
  // Own the blocking write(2) on a pump thread (unix TTYs only). A stale
1032
1053
  // prebuilt natives module without the export falls back to direct writes.
@@ -1122,6 +1143,8 @@ export class ProcessTerminal implements Terminal {
1122
1143
  // events that lose modifier information. Must run after setRawMode(true)
1123
1144
  // since that resets console mode flags.
1124
1145
  this.#enableWindowsVTInput();
1146
+ // Every response-eliciting probe goes out inside the leak guard.
1147
+ this.#safeWrite(PROBE_GUARD_BEGIN);
1125
1148
  // Query and enable Kitty keyboard protocol
1126
1149
  // The query handler intercepts input temporarily, then installs the user's handler
1127
1150
  // See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
@@ -1184,6 +1207,7 @@ export class ProcessTerminal implements Terminal {
1184
1207
  for (const mode of XTERM_SCROLL_TO_BOTTOM_MODES) {
1185
1208
  this.#queryPrivateMode(mode);
1186
1209
  }
1210
+ this.#safeWrite(PROBE_GUARD_END);
1187
1211
  }
1188
1212
 
1189
1213
  /**
@@ -1264,6 +1288,7 @@ export class ProcessTerminal implements Terminal {
1264
1288
 
1265
1289
  // DA1 (Primary Device Attributes) response: \x1b[?...c
1266
1290
  const da1ResponsePattern = /^\x1b\[\?[\d;]*c$/;
1291
+ const da1SixelAttributePattern = /;4(?:;|c$)/u;
1267
1292
 
1268
1293
  // Private CSI partial: \x1b[?<digits/semicolons>... — incomplete probe response
1269
1294
  // that the StdinBuffer flushed before the terminator arrived (split across
@@ -1407,6 +1432,19 @@ export class ProcessTerminal implements Terminal {
1407
1432
  // outstanding sentinel — a reply that arrives after the FIFO drains (slow
1408
1433
  // SSH/PTY links) must never reach the composer as literal text (#8542).
1409
1434
  if (da1ResponsePattern.test(sequence)) {
1435
+ // Publish graphics support without forwarding DA1 bytes to application
1436
+ // input or changing ownership of the existing probe sentinel.
1437
+ const supportsSixel = da1SixelAttributePattern.test(sequence);
1438
+ if (supportsSixel !== this.#sixelSupport) {
1439
+ this.#sixelSupport = supportsSixel;
1440
+ for (const callback of this.#sixelSupportCallbacks) {
1441
+ try {
1442
+ callback(supportsSixel);
1443
+ } catch {
1444
+ // Capability subscribers must not interrupt sentinel handling.
1445
+ }
1446
+ }
1447
+ }
1410
1448
  const owner = this.#da1SentinelOwners.shift();
1411
1449
  if (!owner) {
1412
1450
  // Late/unowned reply: nothing to resolve, just drop the bytes.
@@ -1429,7 +1467,7 @@ export class ProcessTerminal implements Terminal {
1429
1467
  ) {
1430
1468
  const query = this.#osc11QueuedQuery;
1431
1469
  this.#osc11QueuedQuery = undefined;
1432
- this.#startOsc11Query(query.route, query.token);
1470
+ this.#startOsc11Query(query.cache, query.token);
1433
1471
  }
1434
1472
  break;
1435
1473
  }
@@ -1597,9 +1635,18 @@ export class ProcessTerminal implements Terminal {
1597
1635
  return;
1598
1636
  }
1599
1637
  if (this.#inputHandler) {
1600
- const win32Keys = this.#win32InputDecoder?.decode(sequence);
1601
- if (win32Keys !== undefined) {
1638
+ const decoder = this.#win32InputDecoder;
1639
+ const win32Keys = decoder?.decode(sequence);
1640
+ if (decoder && win32Keys !== undefined) {
1641
+ clearTimeout(this.#win32PendingFlushTimer);
1642
+ this.#win32PendingFlushTimer = undefined;
1602
1643
  for (const key of win32Keys) this.#inputHandler(key);
1644
+ if (decoder.hasPendingSequence) {
1645
+ this.#win32PendingFlushTimer = setTimeout(
1646
+ () => this.#releaseWin32PendingSequence(),
1647
+ WIN32_RELAYED_ESCAPE_TIMEOUT_MS,
1648
+ );
1649
+ }
1603
1650
  return;
1604
1651
  }
1605
1652
  // Windows console hosts drop AltGr text under kitty (AltGr+F → `CSI 102;3u`);
@@ -1626,7 +1673,10 @@ export class ProcessTerminal implements Terminal {
1626
1673
 
1627
1674
  // Handler that pipes stdin data through the buffer
1628
1675
  this.#stdinDataHandler = (data: string) => {
1629
- this.#stdinBuffer!.process(data);
1676
+ // Recover modes reset by the host before the user's next paste.
1677
+ if (data && this.#active && this.#privateModeSupport.get(2004)) this.#safeWrite("\x1b[?2004h");
1678
+ if (this.#win32PasteNormalizer) this.#win32PasteNormalizer.process(data);
1679
+ else this.#stdinBuffer!.process(data);
1630
1680
  };
1631
1681
  }
1632
1682
 
@@ -1635,36 +1685,39 @@ export class ProcessTerminal implements Terminal {
1635
1685
  * DA1 avoids indefinite hangs: if DA1 response arrives before OSC 11,
1636
1686
  * the terminal does not support OSC 11.
1637
1687
  */
1638
- #queryBackgroundColor(route: Osc11QueryRoute = "direct", token?: TerminalAppearanceRequestToken): void {
1688
+ #queryBackgroundColor(
1689
+ cache?: TerminalMultiplexerBackgroundColorCache,
1690
+ token?: TerminalAppearanceRequestToken,
1691
+ ): void {
1639
1692
  if (this.#dead) return;
1640
1693
  // Queue if an OSC 11 query is in flight or its DA1 sentinel hasn't been
1641
1694
  // consumed yet. Starting a new query while a DA1 is outstanding would
1642
1695
  // increment the sentinel counter, and the old DA1 arrival would then
1643
1696
  // prematurely clear the new query's pending state. Preserve a requested
1644
- // tmux passthrough route when coalescing direct and explicit queries, and
1645
- // retain the latest explicit request identity across automatic queries.
1697
+ // cache refresh when coalescing direct and explicit queries, and retain
1698
+ // the latest explicit request identity across automatic queries.
1646
1699
  if (this.#osc11Pending || this.#da1SentinelOwners.some(o => o.kind === "osc11")) {
1647
1700
  const queued = this.#osc11QueuedQuery;
1648
- this.#osc11QueuedQuery = {
1649
- route: queued?.route === "tmux" || route === "tmux" ? "tmux" : "direct",
1650
- token: token ?? queued?.token,
1651
- };
1701
+ this.#osc11QueuedQuery = { cache: queued?.cache ?? cache, token: token ?? queued?.token };
1652
1702
  return;
1653
1703
  }
1654
- this.#startOsc11Query(route, token);
1704
+ this.#startOsc11Query(cache, token);
1655
1705
  }
1656
1706
 
1657
- #startOsc11Query(route: Osc11QueryRoute, token?: TerminalAppearanceRequestToken): void {
1707
+ #startOsc11Query(
1708
+ cache: TerminalMultiplexerBackgroundColorCache | undefined,
1709
+ token?: TerminalAppearanceRequestToken,
1710
+ ): void {
1658
1711
  this.#osc11Pending = true;
1659
1712
  this.#osc11ActiveToken = token;
1660
1713
  this.#osc11ResponseBuffer = "";
1661
- if (route === "tmux") {
1662
- this.#safeWrite(wrapTmuxPassthrough("\x1b]11;?\x07"));
1663
- this.#osc11TmuxRefreshTimer = setTimeout(() => {
1664
- this.#osc11TmuxRefreshTimer = undefined;
1714
+ if (cache) {
1715
+ this.#safeWrite(cache.passthrough("\x1b]11;?\x07"));
1716
+ this.#osc11CacheRefreshTimer = setTimeout(() => {
1717
+ this.#osc11CacheRefreshTimer = undefined;
1665
1718
  if (this.#dead || !this.#osc11Pending) return;
1666
1719
  this.#startDirectOsc11Query();
1667
- }, TMUX_OSC11_CACHE_REFRESH_DELAY_MS);
1720
+ }, cache.settleMs);
1668
1721
  return;
1669
1722
  }
1670
1723
  this.#startDirectOsc11Query();
@@ -1787,6 +1840,9 @@ export class ProcessTerminal implements Terminal {
1787
1840
  #shouldQueryTspSupport(): boolean {
1788
1841
  const override = $env.PI_TUI_NATIVE;
1789
1842
  if (override === "0") return false;
1843
+ // tmux makes APC bodies the pane title unless the hosting terminal asked
1844
+ // for TSP through `@tern-tsp`: no override may force the probe there.
1845
+ if (tmuxMultiplexer.isInside($env)) return tmuxSessionSpeaksTsp($env);
1790
1846
  if (override === "1") return true;
1791
1847
  // Multiplexers swallow APC, so the reply could never arrive.
1792
1848
  if (isInsideTerminalMultiplexer($env)) return false;
@@ -1804,16 +1860,37 @@ export class ProcessTerminal implements Terminal {
1804
1860
  return;
1805
1861
  }
1806
1862
  this.#tspPending = true;
1863
+ if (tmuxMultiplexer.isInside($env)) {
1864
+ // tmux answers DA1 itself, ahead of any reply: wait for the reply instead.
1865
+ this.#tspTmuxPaneGated = true;
1866
+ setTmuxPaneApcTitles(false, $env);
1867
+ this.#tspTimeout = setTimeout(() => {
1868
+ this.#tspTimeout = undefined;
1869
+ this.#resolveTspSupport(null);
1870
+ }, TSP_TMUX_HELLO_TIMEOUT_MS);
1871
+ this.#safeWrite(encodeTspHelloQuery());
1872
+ return;
1873
+ }
1807
1874
  this.#da1SentinelOwners.push({ kind: "tsp" });
1808
1875
  this.#safeWrite(`${encodeTspHelloQuery()}\x1b[c`);
1809
1876
  }
1810
1877
 
1878
+ /** Hand APC titles back to tmux once this pane stops speaking TSP. */
1879
+ #endTspTmuxPane(): void {
1880
+ clearTimeout(this.#tspTimeout);
1881
+ this.#tspTimeout = undefined;
1882
+ if (!this.#tspTmuxPaneGated) return;
1883
+ this.#tspTmuxPaneGated = false;
1884
+ setTmuxPaneApcTitles(true, $env);
1885
+ }
1886
+
1811
1887
  #handleTspMessage(sequence: string): void {
1812
1888
  const message = parseTspMessage(sequence);
1813
- if (message?.verb === "r") {
1814
- if (message.reply.r === "hello") this.#resolveTspSupport(message.reply);
1889
+ if (message?.verb === "r" && message.reply.r === "hello") {
1890
+ this.#resolveTspSupport(message.reply);
1815
1891
  return;
1816
1892
  }
1893
+ // Events and every other reply (`blobs`) go to the native backend.
1817
1894
  this.#inputHandler?.(sequence);
1818
1895
  }
1819
1896
 
@@ -1822,6 +1899,12 @@ export class ProcessTerminal implements Terminal {
1822
1899
  this.#tspPending = false;
1823
1900
  const result = hello !== null && hello.v === TSP_VERSION ? hello : null;
1824
1901
  if (hello !== null && result === null) logger.warn("TSP: unsupported protocol version", { v: hello.v });
1902
+ if (result === null) {
1903
+ this.#endTspTmuxPane();
1904
+ } else {
1905
+ clearTimeout(this.#tspTimeout);
1906
+ this.#tspTimeout = undefined;
1907
+ }
1825
1908
  this.#tspResult = result;
1826
1909
  for (const cb of this.#tspCallbacks) {
1827
1910
  try {
@@ -1892,6 +1975,12 @@ export class ProcessTerminal implements Terminal {
1892
1975
  // and the mode splits arrow keys into Escape plus literal text (#14034).
1893
1976
  this.#safeWrite("\x1b[?9001h");
1894
1977
  this.#win32InputDecoder = new Win32InputModeDecoder();
1978
+ // The normalizer's expiry already spent the ESC wait; release the decoder's hold
1979
+ // at once instead of starting a second window.
1980
+ this.#win32PasteNormalizer = new Win32PasteMarkerNormalizer(
1981
+ data => this.#stdinBuffer?.process(data),
1982
+ () => this.#releaseWin32PendingSequence(),
1983
+ );
1895
1984
  return;
1896
1985
  }
1897
1986
  // A remote terminal with no identifying env may not understand the request.
@@ -1900,8 +1989,20 @@ export class ProcessTerminal implements Terminal {
1900
1989
  this.#modifyOtherKeysActive = true;
1901
1990
  }
1902
1991
 
1992
+ /** Deliver relayed VT text the decoder is holding (a lone ESC becomes Escape). */
1993
+ #releaseWin32PendingSequence(): void {
1994
+ clearTimeout(this.#win32PendingFlushTimer);
1995
+ this.#win32PendingFlushTimer = undefined;
1996
+ const pending = this.#win32InputDecoder?.flushPendingSequence() ?? [];
1997
+ for (const key of pending) this.#inputHandler?.(key);
1998
+ }
1999
+
1903
2000
  #disableWin32InputMode(): void {
1904
2001
  if (!this.#win32InputDecoder) return;
2002
+ this.#win32PasteNormalizer?.flush();
2003
+ this.#win32PasteNormalizer = undefined;
2004
+ // Held input still belongs to the user (e.g. Escape before a late kitty reply).
2005
+ this.#releaseWin32PendingSequence();
1905
2006
  this.#safeWrite("\x1b[?9001l");
1906
2007
  this.#win32InputDecoder = undefined;
1907
2008
  }
@@ -1975,12 +2076,6 @@ export class ProcessTerminal implements Terminal {
1975
2076
  // fallback resolves unsupported).
1976
2077
  if (mode === 2004 && supported) {
1977
2078
  this.#stdinBuffer?.setRawPasteStallProbe(this.#isLoopStalled);
1978
- // A terminal can reset this mode after the initial probe (for example,
1979
- // iTerm2's Terminal State toggle). Keep the mode asserted while we own
1980
- // the TTY, since a stalled loop now replays unmarked bursts as keys.
1981
- this.#bracketedPasteRefreshTimer ??= setInterval(() => {
1982
- if (this.#active && !this.#dead) this.#safeWrite("\x1b[?2004h");
1983
- }, 1000);
1984
2079
  }
1985
2080
  }
1986
2081
 
@@ -2114,14 +2209,10 @@ export class ProcessTerminal implements Terminal {
2114
2209
  // Suppress observer/timer callbacks before any teardown can yield or throw.
2115
2210
  this.#active = false;
2116
2211
  this.#inputDeferred = false;
2117
- if (this.#bracketedPasteRefreshTimer) {
2118
- clearInterval(this.#bracketedPasteRefreshTimer);
2119
- this.#bracketedPasteRefreshTimer = undefined;
2120
- }
2121
2212
  if (this.#headless) return;
2122
- // Unregister from emergency cleanup
2123
- if (activeTerminal === this) {
2124
- activeTerminal = null;
2213
+ // Release stdout ownership (out-of-band writers and emergency cleanup)
2214
+ if (getActiveTerminal() === this) {
2215
+ setActiveTerminal(null);
2125
2216
  }
2126
2217
 
2127
2218
  // Release terminal ownership of fd 2 first so external programs,
@@ -2179,9 +2270,9 @@ export class ProcessTerminal implements Terminal {
2179
2270
  clearTimeout(this.#mode2031DebounceTimer);
2180
2271
  this.#mode2031DebounceTimer = undefined;
2181
2272
  }
2182
- if (this.#osc11TmuxRefreshTimer) {
2183
- clearTimeout(this.#osc11TmuxRefreshTimer);
2184
- this.#osc11TmuxRefreshTimer = undefined;
2273
+ if (this.#osc11CacheRefreshTimer) {
2274
+ clearTimeout(this.#osc11CacheRefreshTimer);
2275
+ this.#osc11CacheRefreshTimer = undefined;
2185
2276
  }
2186
2277
  this.#appearanceCallbacks = [];
2187
2278
  this.#appearanceReportCallbacks = [];
@@ -2198,8 +2289,11 @@ export class ProcessTerminal implements Terminal {
2198
2289
  this.#glyphProtocolResult = undefined;
2199
2290
  this.#glyphProtocolReplyBuffer = "";
2200
2291
  this.#glyphProtocolCallbacks = [];
2292
+ this.#sixelSupport = undefined;
2293
+ this.#sixelSupportCallbacks = [];
2201
2294
  setTerminalGlyphProtocol(false);
2202
2295
  this.#tspPending = false;
2296
+ this.#endTspTmuxPane();
2203
2297
  this.#tspResult = undefined;
2204
2298
  this.#tspReplyBuffer = "";
2205
2299
  this.#tspCallbacks = [];
@@ -2289,10 +2383,6 @@ export class ProcessTerminal implements Terminal {
2289
2383
  #markTerminalDisconnected(reason: string, err?: unknown): void {
2290
2384
  if (this.#dead) return;
2291
2385
  this.#dead = true;
2292
- if (this.#bracketedPasteRefreshTimer) {
2293
- clearInterval(this.#bracketedPasteRefreshTimer);
2294
- this.#bracketedPasteRefreshTimer = undefined;
2295
- }
2296
2386
  this.#disarmStdoutStallWatchdog();
2297
2387
  logger.warn("terminal disconnected; stopping interactive rendering", { reason, err });
2298
2388
 
@@ -2321,10 +2411,19 @@ export class ProcessTerminal implements Terminal {
2321
2411
  }
2322
2412
 
2323
2413
  write(data: string): void {
2414
+ // Hosts can reset bracketed paste (e.g. iTerm2's Terminal State toggle).
2415
+ // Reassert it with existing output, never on a timer while idle.
2416
+ if (data && this.#active && this.#privateModeSupport.get(2004)) data = `\x1b[?2004h${data}`;
2324
2417
  this.#safeWrite(data);
2325
2418
  if (this.#writeLogPath) {
2326
2419
  try {
2327
- fs.appendFileSync(this.#writeLogPath, data, { encoding: "utf8" });
2420
+ // Keep clipboard contents out of the debug log: record the payload's length only.
2421
+ const logged = data.replace(
2422
+ OSC52_CLIPBOARD_WRITE,
2423
+ (_seq, selection: string, payload: string, end: string) =>
2424
+ `\x1b]52;${selection};<${payload.length} bytes>${end}`,
2425
+ );
2426
+ fs.appendFileSync(this.#writeLogPath, logged, { encoding: "utf8" });
2328
2427
  } catch {
2329
2428
  // Ignore logging errors
2330
2429
  }
@@ -2364,11 +2463,14 @@ export class ProcessTerminal implements Terminal {
2364
2463
  this.#trackStdoutBacklog(pending);
2365
2464
  return;
2366
2465
  }
2367
- // A console-sharing child process may have flipped the console codepage
2368
- // away from UTF-8; repair it before any bytes hit WriteFile so no frame
2369
- // is ever translated through an OEM codepage. See ensureWindowsConsoleUtf8.
2370
- if (process.platform === "win32") ensureWindowsConsoleUtf8();
2466
+ // Without the pump, writes (and the codepage guard's console calls) block
2467
+ // the event loop until the terminal drains; label them for the watchdog.
2468
+ pushLoopPhase("ui.terminal-write");
2371
2469
  try {
2470
+ // A console-sharing child process may have flipped the console codepage
2471
+ // away from UTF-8; repair it before any bytes hit WriteFile so no frame
2472
+ // is ever translated through an OEM codepage. See ensureWindowsConsoleUtf8.
2473
+ if (process.platform === "win32") ensureWindowsConsoleUtf8();
2372
2474
  // Windows ConPTY drops viewport tracking when a single write exceeds
2373
2475
  // ~32-64 KB: the host UI's scroll position stays parked at wherever
2374
2476
  // the write began, even though every byte landed in scrollback. Split
@@ -2397,6 +2499,8 @@ export class ProcessTerminal implements Terminal {
2397
2499
  this.#trackStdoutBacklog(process.stdout.writableLength ?? 0);
2398
2500
  } catch (err) {
2399
2501
  this.#markTerminalDisconnected("stdout failed", err);
2502
+ } finally {
2503
+ popLoopPhase();
2400
2504
  }
2401
2505
  }
2402
2506
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Active theme's symbol lookup without importing `./theme` (which pulls the
3
3
  * native addon through `@linxiraos/pi-natives`). Key-hint formatting runs on
4
- * CLI paths that must stay addon-free (`omp --version`, help), so it reads the
4
+ * CLI paths that must stay addon-free (`zetacode --version`, help), so it reads the
5
5
  * active theme through this mirror; `./theme` publishes every assignment here.
6
6
  */
7
7
  import { type SymbolKey } from "./symbols";
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Active theme's symbol lookup without importing `./theme` (which pulls the
3
3
  * native addon through `@linxiraos/pi-natives`). Key-hint formatting runs on
4
- * CLI paths that must stay addon-free (`omp --version`, help), so it reads the
4
+ * CLI paths that must stay addon-free (`zetacode --version`, help), so it reads the
5
5
  * active theme through this mirror; `./theme` publishes every assignment here.
6
6
  */
7
7
  import { SYMBOL_PRESETS, type SymbolKey } from "./symbols";
@@ -13,6 +13,7 @@ const themeColorsSchema = type({
13
13
  muted: "string | number",
14
14
  dim: "string | number",
15
15
  text: "string | number",
16
+ "assistantMessageText?": "string | number",
16
17
  thinkingText: "string | number",
17
18
  selectedBg: "string | number",
18
19
  userMessageBg: "string | number",