@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
@@ -10,17 +10,16 @@ import {
10
10
  } from "../components/editor";
11
11
  import { addKeyAliases, canonicalKeyId, getKeybindings } from "../keybindings";
12
12
  import { type KeyId, parseKey, parseKittySequence } from "../keys";
13
- import { SpaceHoldGesture } from "../space-hold";
13
+ import { getSpaceHoldText, SpaceHoldGesture } from "../space-hold";
14
14
  import { type Component, TUI } from "../tui";
15
15
  import type { AppKeybinding } from "../app-keybindings";
16
- import { formatKeyHint } from "../key-hint-format";
16
+ import { formatTooltipKey } from "../key-hint-format";
17
17
  import { MAIN_AGENT_ID } from "../overlays/agent-hub-types";
18
18
  import { compact, keyed, node, row, span } from "../native/describe";
19
19
  import type { DescribeContext, NativeNode, NativeUiEvent } from "../native/node";
20
20
  import type { ComposerFacts, ComposerFactsSource } from "../status-line/types";
21
21
  import { allowsModelMentions, allowsSkillTokens, SKILL_TOKEN_RE } from "./skill-tokens";
22
22
  import { expandModelMentionTags, MODEL_MENTION_RE, modelMentionToken } from "./model-mention-syntax";
23
- import { imageAttachmentSource } from "./image-source";
24
23
  import { isVideoPath } from "./video";
25
24
  import {
26
25
  attachmentSgr,
@@ -41,7 +40,7 @@ import {
41
40
  } from "./composer-attachments";
42
41
  import { type MacOSSpellingFeatures, MacOSSpellingProvider } from "./macos-spelling";
43
42
  import { hasMagicKeyword, highlightMagicKeywords, magicKeywordRanges } from "./magic-keywords";
44
- import type { TspEditorDecoration } from "@linxiraos/pi-wire";
43
+ import type { TspEditorDecoration, TspText } from "@linxiraos/pi-wire";
45
44
  import { isNativeRendering } from "../native/state";
46
45
  import { isQueuedMessageList, parseQueueShorthand, QUEUE_LIST_MARKER_RE } from "./queue-input";
47
46
  import { type WordCompletionMethod, WordCompletionProvider } from "./word-completion";
@@ -50,8 +49,8 @@ import { fgOrPlain, theme } from "../theme/theme";
50
49
  /** A shell-mode draft's sigil (`!`, `!!`, `$`, `$$`) with its surrounding blanks; the mode chip stands in for it natively. */
51
50
  const SHELL_SIGIL_RE = /^\s*(?:!!?|\$\$?)[ \t]?/;
52
51
 
53
- /** The composer's TSP placeholder; the rotating ANSI hints (thinking effort, …) move into control tooltips. */
54
- const NATIVE_COMPOSER_PLACEHOLDER = "Ask zeta — / commands · @ files · ! bash";
52
+ /** The untitled session's TSP placeholder; the rotating ANSI hints (thinking effort, …) move into control tooltips. */
53
+ const NATIVE_COMPOSER_PLACEHOLDER = "What are we cooking?";
55
54
 
56
55
  /** Live composer state the TSP layout shows; the interactive host wires {@link CustomEditor.composerState}. */
57
56
  export interface ComposerNativeState {
@@ -59,10 +58,16 @@ export interface ComposerNativeState {
59
58
  readonly shell?: { readonly kind: "bash" | "python"; readonly excluded: boolean };
60
59
  /** Thinking effort word (`high`, `off`, `auto`); undefined when the model has no thinking. */
61
60
  readonly thinking?: string;
61
+ /** The thinking level draws as the model chip's icon instead of its own chip (where the terminal has `effort`). */
62
+ readonly thinkingInModel?: boolean;
63
+ /** Generation tok/s after the thinking level (live while running, else the last reading); undefined hides it. */
64
+ readonly rate?: number;
62
65
  /** A turn is running: the send keycap becomes a Stop button. */
63
66
  readonly running: boolean;
64
67
  /** The viewed subagent's lineage, outermost first and the viewed agent last; undefined on the main session. */
65
68
  readonly viewing?: readonly string[];
69
+ /** The session's title, quoted in italics as the TSP placeholder; undefined until it has one. */
70
+ readonly title?: string;
66
71
  }
67
72
 
68
73
  /** Action code prefix of the viewing header's links: `focus:<agent id>`, {@link MAIN_AGENT_ID} for the main session. */
@@ -70,6 +75,15 @@ const FOCUS_ACTION = "focus:";
70
75
 
71
76
  const IDLE_COMPOSER: ComposerNativeState = { running: false };
72
77
 
78
+ /** The composer's controls around the input, rebuilt when what they show changes; the bar adds the rate and facts. */
79
+ interface ComposerControls {
80
+ readonly focus: NativeNode | undefined;
81
+ readonly mode: NativeNode | undefined;
82
+ readonly model: NativeNode | undefined;
83
+ readonly effort: NativeNode | undefined;
84
+ readonly submit: NativeNode;
85
+ }
86
+
73
87
  /**
74
88
  * Filled steps (of four) of the effort chip's fallback meter, for terminals without the `effort`
75
89
  * kind; `auto` before it resolves has none known.
@@ -197,11 +211,17 @@ function normalizePastedPath(path: string): string {
197
211
  try {
198
212
  return url.fileURLToPath(unquoted);
199
213
  } catch {
200
- // Malformed file URL: drop through to the shell-unescape branch
201
- // so the caller can still reject it as a non-explicit path.
214
+ // Windows rejects drive-less URLs (`file:///Users/…`, forwarded from a
215
+ // macOS pasteboard or a remote session); decode them as POSIX paths.
216
+ try {
217
+ return url.fileURLToPath(unquoted, { windows: false });
218
+ } catch {
219
+ // Malformed file URL: drop through to the shell-unescape branch
220
+ // so the caller can still reject it as a non-explicit path.
221
+ }
202
222
  }
203
223
  }
204
- return unquoted.replace(SHELL_ESCAPED_PATH_CHAR_REGEX, "$1");
224
+ return (unquoted.startsWith("\\~/") ? unquoted.slice(1) : unquoted).replace(SHELL_ESCAPED_PATH_CHAR_REGEX, "$1");
205
225
  }
206
226
 
207
227
  function isExplicitPastedPath(path: string): boolean {
@@ -389,7 +409,7 @@ export function extractImagePathFromText(text: string): string | undefined {
389
409
 
390
410
  /**
391
411
  * Resolve the {@link EditorTheme} from a `CustomEditor`/`Editor` constructor
392
- * argument list, tolerating both the zeta `(theme)` and upstream-pi
412
+ * argument list, tolerating both the omp `(theme)` and upstream-pi
393
413
  * `(tui, theme, keybindings)` conventions (see {@link CustomEditor}'s
394
414
  * constructor). A real `EditorTheme` is identified structurally — it exposes a
395
415
  * `borderColor` function and a `symbols` object — so a `TUI` passed in the first
@@ -459,13 +479,13 @@ export class CustomEditor extends Editor {
459
479
  chips: ComposerChipDescriptor[];
460
480
  }
461
481
  | undefined;
462
- /** Host-wired producer of per-image `file://` links (session blob store); drives clickable
463
- * chip tokens for restored drafts (esc-esc, `/tree`, branch). */
482
+ /** Host-wired producer of per-image chip targets (a file on disk, or a session blob copy);
483
+ * drives clickable chip tokens for restored drafts (esc-esc, `/tree`, branch). */
464
484
  draftImageLinkMaterializer?: (images: readonly ImageContent[]) => Promise<(string | undefined)[] | undefined>;
465
485
 
466
486
  /**
467
487
  * The host {@link TUI}, captured when a plugin constructs this editor through
468
- * the upstream-pi `(tui, theme, keybindings)` convention. Undefined for zeta's
488
+ * the upstream-pi `(tui, theme, keybindings)` convention. Undefined for omp's
469
489
  * own `new CustomEditor(theme)` callers (they drive repaints through the
470
490
  * interactive-mode wiring instead). Plugins that call `this.tui.requestRender()`
471
491
  * in their overrides read it here (issue #4766).
@@ -473,7 +493,7 @@ export class CustomEditor extends Editor {
473
493
  tui?: TUI;
474
494
 
475
495
  /**
476
- * Accept both the zeta constructor convention — `new CustomEditor(theme)` —
496
+ * Accept both the omp constructor convention — `new CustomEditor(theme)` —
477
497
  * and the upstream-pi `Editor` convention — `new Editor(tui, theme, keybindings)`
478
498
  * — that {@link ExtensionUIContext.setEditorComponent}'s factory contract
479
499
  * advertises `(tui, theme, keybindings)`. Plugins written against upstream pi
@@ -770,7 +790,7 @@ export class CustomEditor extends Editor {
770
790
  if (!materialize || images.length === 0) return;
771
791
  const links = await materialize(images);
772
792
  if (!links || this.pendingImages !== images) return;
773
- this.pendingImageLinks = images.map((image, index) => imageAttachmentSource(image)?.path ?? links[index]);
793
+ this.pendingImageLinks = links;
774
794
  this.imageLinks = this.pendingImageLinks;
775
795
  this.#requestShimmerRepaint?.();
776
796
  }
@@ -779,10 +799,21 @@ export class CustomEditor extends Editor {
779
799
  * indivisible: a stray backspace deletes the whole token instead of corrupting it. */
780
800
  override atomicTokenPattern = COMPOSER_TOKEN_REGEX;
781
801
 
802
+ /** Atom-table revision and pattern the composer token matcher was last built for. */
803
+ #tokenPatternAtomsRevision = -1;
804
+ #tokenPattern: RegExp | undefined;
805
+
782
806
  #syncComposerTokenPattern(): void {
783
- const labels = [...this.atoms].filter(([, expansion]) => expansion.startsWith("^")).map(([label]) => label);
807
+ // Rebuild only when the atom table changed or someone replaced the pattern since the last sync.
808
+ if (this.#tokenPatternAtomsRevision === this.atomsRevision && this.#tokenPattern === this.atomicTokenPattern) {
809
+ return;
810
+ }
811
+ const labels: string[] = [];
812
+ for (const [label, expansion] of this.atoms) if (expansion.startsWith("^")) labels.push(label);
784
813
  const next = composerTokenRegex(labels);
785
814
  if (next.source !== this.atomicTokenPattern.source) this.atomicTokenPattern = next;
815
+ this.#tokenPatternAtomsRevision = this.atomsRevision;
816
+ this.#tokenPattern = this.atomicTokenPattern;
786
817
  }
787
818
 
788
819
  /** Magic-keyword shimmer cadence — drives one editor repaint every 70 ms while
@@ -800,27 +831,35 @@ export class CustomEditor extends Editor {
800
831
  * timer to request the next animation frame. Undefined when nobody is
801
832
  * listening (tests, headless callers); the timer chain still self-cleans. */
802
833
  #requestShimmerRepaint: (() => void) | undefined;
803
- #queueDecorationText: string | undefined;
834
+ /** Text revision the per-revision decoration inputs below were computed for. */
835
+ #decorationRevision = -1;
836
+ #decorationText = "";
804
837
  #decorationLines: readonly string[] = [""];
805
838
  #queueShorthandActive = false;
806
839
  #queueListActive = false;
807
840
 
808
841
  /** Decorate magic keywords, attachments, and the queue-composer header/list markers.
809
842
  * Queue shorthand reserves its first logical line as a dim `Queueing` label; sequential
810
- * item markers use the accent color so separate follow-ups remain visible while composing. */
843
+ * item markers use the accent color so separate follow-ups remain visible while composing.
844
+ * Called once per layout segment, so whole-buffer inputs are computed once per text revision. */
811
845
  override decorateText = (text: string, context: EditorTextDecorationContext): string => {
812
846
  this.#syncComposerTokenPattern();
813
- const editorText = this.getText();
847
+ if (this.#decorationRevision !== this.textRevision) {
848
+ this.#decorationRevision = this.textRevision;
849
+ const editorText = this.getText();
850
+ if (this.#decorationText !== editorText) {
851
+ this.#decorationText = editorText;
852
+ this.#decorationLines = this.getLines();
853
+ const queueBody = parseQueueShorthand(editorText);
854
+ this.#queueShorthandActive = queueBody !== undefined;
855
+ this.#queueListActive = queueBody !== undefined && isQueuedMessageList(queueBody);
856
+ }
857
+ }
858
+ // One string instance per revision: whole-buffer memos downstream hit on identity.
859
+ const editorText = this.#decorationText;
814
860
  const animated = this.focused && this.#shimmerEnabled() && hasMagicKeyword(editorText);
815
861
  const phase = animated ? (Date.now() % CustomEditor.SHIMMER_PERIOD_MS) / CustomEditor.SHIMMER_PERIOD_MS : 0;
816
862
  if (animated) this.#scheduleShimmerFrame();
817
- if (this.#queueDecorationText !== editorText) {
818
- this.#queueDecorationText = editorText;
819
- this.#decorationLines = this.getLines();
820
- const queueBody = parseQueueShorthand(editorText);
821
- this.#queueShorthandActive = queueBody !== undefined;
822
- this.#queueListActive = queueBody !== undefined && isQueuedMessageList(queueBody);
823
- }
824
863
  let sourceSearchOffset = 0;
825
864
  const locateSource = (value: string): number => {
826
865
  const offset = text.indexOf(value, sourceSearchOffset);
@@ -995,10 +1034,22 @@ export class CustomEditor extends Editor {
995
1034
  }, CustomEditor.SHIMMER_FRAME_MS);
996
1035
  this.#shimmerTimer.unref?.();
997
1036
  }
998
- /** Viewing a subagent, the draft goes to it: the placeholder names it. */
999
- override describePlaceholder = (): string => {
1000
- const agent = this.composerState().viewing?.at(-1);
1001
- return agent === undefined ? NATIVE_COMPOSER_PLACEHOLDER : `Message ${agent}`;
1037
+ /** Editing is available during bootstrap; atomic sends wait until submission is wired and enabled. */
1038
+ protected override get nativeSendable(): boolean {
1039
+ return this.onSubmit !== undefined && !this.disableSubmit;
1040
+ }
1041
+ /** The quoted-title placeholder, kept while the title stands so the editor node stays reused. */
1042
+ #titlePlaceholder: { title: string; text: TspText } | undefined;
1043
+ /** Viewing a subagent, the draft goes to it: the placeholder names it; else the session's title, quoted in italics. */
1044
+ override describePlaceholder = (): TspText => {
1045
+ const { viewing, title } = this.composerState();
1046
+ const agent = viewing?.at(-1);
1047
+ if (agent !== undefined) return `Message ${agent}`;
1048
+ if (!title) return NATIVE_COMPOSER_PLACEHOLDER;
1049
+ if (this.#titlePlaceholder?.title !== title) {
1050
+ this.#titlePlaceholder = { title, text: [{ t: `“${title}”`, s: "em" }] };
1051
+ }
1052
+ return this.#titlePlaceholder.text;
1002
1053
  };
1003
1054
  /** A shell-mode draft highlights as its language. */
1004
1055
  override describeLanguage = (): string | undefined => this.composerState().shell?.kind;
@@ -1014,8 +1065,8 @@ export class CustomEditor extends Editor {
1014
1065
  facts: ComposerFacts | undefined;
1015
1066
  chips: Component | undefined;
1016
1067
  input: NativeNode;
1017
- focus: NativeNode | undefined;
1018
- mode: NativeNode | undefined;
1068
+ rate: number | undefined;
1069
+ controls: ComposerControls;
1019
1070
  bar: NativeNode;
1020
1071
  layout: NativeEditorLayout;
1021
1072
  }
@@ -1025,7 +1076,8 @@ export class CustomEditor extends Editor {
1025
1076
  * The TSP composer: role `zeta.editor[.bash|.python]` (tone `pending` while
1026
1077
  * a turn runs) over the context hairline, the viewing header while a
1027
1078
  * subagent is focused, the attachment chips, a `line` row of the
1028
- * shell-mode chip and the input, and the `bar`: model chip, effort chip,
1079
+ * shell-mode chip and the input, and the `bar`: model chip, effort chip
1080
+ * (or the effort glyph as the model chip's icon), the generation rate,
1029
1081
  * the other status facts, usage, then send (Stop while a turn runs).
1030
1082
  * Clicks come back as `status.model`, `thinking.cycle`, `submit`,
1031
1083
  * `interrupt` and `focus:<id>` actions.
@@ -1048,27 +1100,61 @@ export class CustomEditor extends Editor {
1048
1100
  interruptKey,
1049
1101
  state.viewing?.join("\u0001"),
1050
1102
  effortGlyph,
1103
+ state.thinkingInModel,
1051
1104
  ].join("\0");
1105
+ const rate = state.rate;
1052
1106
  const chips = this.attachmentChips;
1053
1107
  const memo = this.#nativeComposer;
1054
- if (memo && memo.key === key && memo.facts === facts && memo.input === input && memo.chips === chips) {
1108
+ if (
1109
+ memo &&
1110
+ memo.key === key &&
1111
+ memo.rate === rate &&
1112
+ memo.facts === facts &&
1113
+ memo.input === input &&
1114
+ memo.chips === chips
1115
+ ) {
1055
1116
  return memo.layout;
1056
1117
  }
1057
- const { focus, mode, bar } =
1118
+ const controls =
1058
1119
  memo?.key === key && memo.facts === facts
1059
- ? memo
1120
+ ? memo.controls
1060
1121
  : this.#describeComposerControls(state, facts, thinkingKey, modelKey, interruptKey, effortGlyph);
1122
+ // The rate ticks while a turn streams: only the bar follows it.
1123
+ const bar =
1124
+ memo?.controls === controls && memo.rate === rate
1125
+ ? memo.bar
1126
+ : keyed(
1127
+ row(
1128
+ compact([
1129
+ controls.model,
1130
+ controls.effort,
1131
+ rate !== undefined &&
1132
+ node(
1133
+ "rate",
1134
+ { value: rate, unit: "tok/s", role: "zeta.composer.rate", title: "Generation rate" },
1135
+ undefined,
1136
+ "rate",
1137
+ ),
1138
+ // The status facts are the bar's flexible space; without them a spacer keeps send at the end.
1139
+ facts?.extras ?? node("row", { grow: 1 }, [], "gap"),
1140
+ facts?.usage,
1141
+ controls.submit,
1142
+ ]),
1143
+ { role: "zeta.composer.bar", gap: "sm", align: "center" },
1144
+ ),
1145
+ "bar",
1146
+ );
1061
1147
  const line = keyed(
1062
- row(compact([mode, input]), { role: "zeta.composer.line", align: "start", gap: "sm" }),
1148
+ row(compact([controls.mode, input]), { role: "zeta.composer.line", align: "start", gap: "sm" }),
1063
1149
  "line",
1064
1150
  );
1065
1151
  const layout: NativeEditorLayout = {
1066
1152
  role: shell ? `zeta.editor.${shell.kind}` : "zeta.editor",
1067
1153
  tone: state.running ? "pending" : undefined,
1068
- children: compact([facts?.context, focus, chips, line, bar]),
1154
+ children: compact([facts?.context, controls.focus, chips, line, bar]),
1069
1155
  caret: "line/input",
1070
1156
  };
1071
- this.#nativeComposer = { key, facts, chips, input, focus, mode, bar, layout };
1157
+ this.#nativeComposer = { key, facts, chips, input, rate, controls, bar, layout };
1072
1158
  return layout;
1073
1159
  };
1074
1160
 
@@ -1081,7 +1167,7 @@ export class CustomEditor extends Editor {
1081
1167
  #describeViewing(viewing: readonly string[], interruptKey: KeyId): NativeNode | undefined {
1082
1168
  const agent = viewing.at(-1);
1083
1169
  if (agent === undefined) return undefined;
1084
- const back = interruptKey === "escape" ? "esc" : formatKeyHint(interruptKey);
1170
+ const back = formatTooltipKey(interruptKey);
1085
1171
  const crumbs = viewing.slice(0, -1).map(id =>
1086
1172
  node(
1087
1173
  "text",
@@ -1130,7 +1216,7 @@ export class CustomEditor extends Editor {
1130
1216
  );
1131
1217
  }
1132
1218
 
1133
- /** The viewing header over the text, the shell-mode chip before the input, and the bar under it. */
1219
+ /** The viewing header over the text, the shell-mode chip before the input, and the bar's controls under it. */
1134
1220
  #describeComposerControls(
1135
1221
  state: ComposerNativeState,
1136
1222
  facts: ComposerFacts | undefined,
@@ -1138,9 +1224,26 @@ export class CustomEditor extends Editor {
1138
1224
  modelKey: KeyId | undefined,
1139
1225
  interruptKey: KeyId,
1140
1226
  effortGlyph: boolean,
1141
- ): { focus: NativeNode | undefined; mode: NativeNode | undefined; bar: NativeNode } {
1227
+ ): ComposerControls {
1142
1228
  const shell = state.shell;
1143
1229
  const focus = state.viewing && this.#describeViewing(state.viewing, interruptKey);
1230
+ const thinking = state.thinking;
1231
+ const thinkingHint = thinkingKey ? ` ${formatTooltipKey(thinkingKey)}` : "";
1232
+ // The level collapses into the model chip's icon; its own tooltip and click stay.
1233
+ const modelIcon =
1234
+ facts && thinking !== undefined && effortGlyph && state.thinkingInModel
1235
+ ? node(
1236
+ "effort",
1237
+ {
1238
+ level: thinking,
1239
+ role: "zeta.composer.model.effort",
1240
+ title: `Thinking effort: ${thinking}${thinkingHint}`,
1241
+ actions: { click: "thinking.cycle" },
1242
+ },
1243
+ undefined,
1244
+ "effort",
1245
+ )
1246
+ : undefined;
1144
1247
  const model =
1145
1248
  facts &&
1146
1249
  node(
@@ -1150,42 +1253,42 @@ export class CustomEditor extends Editor {
1150
1253
  gap: "xs",
1151
1254
  align: "center",
1152
1255
  tone: facts.model.tone,
1153
- title: modelKey ? `Switch model ${formatKeyHint(modelKey)}` : "Switch model",
1256
+ title: modelKey ? `Switch model ${formatTooltipKey(modelKey)}` : "Switch model",
1154
1257
  actions: { click: "status.model" },
1155
1258
  },
1156
1259
  [
1157
- node("icon", { name: "model" }, undefined, "icon"),
1260
+ modelIcon ?? node("icon", { name: "model" }, undefined, "icon"),
1158
1261
  node("text", { spans: facts.model.spans, wrap: "none" }, undefined, "name"),
1159
1262
  node("icon", { name: "chev" }, undefined, "chev"),
1160
1263
  ],
1161
1264
  "model",
1162
1265
  );
1163
- const thinking = state.thinking;
1164
1266
  const effortSteps = thinking === undefined ? undefined : EFFORT_STEPS[thinking];
1165
1267
  const effort =
1166
- thinking !== undefined &&
1167
- node(
1168
- "row",
1169
- {
1170
- role: "zeta.composer.effort",
1171
- gap: "xs",
1172
- align: "center",
1173
- title: thinkingKey ? `Thinking effort ${formatKeyHint(thinkingKey)}` : "Thinking effort",
1174
- actions: { click: "thinking.cycle" },
1175
- },
1176
- [
1177
- effortGlyph
1178
- ? node("effort", { level: thinking }, undefined, "glyph")
1179
- : node(
1180
- "meter",
1181
- { value: effortSteps === undefined ? null : effortSteps / 4, style: "blocks", steps: 4 },
1182
- undefined,
1183
- "meter",
1184
- ),
1185
- node("text", { text: thinking, wrap: "none" }, undefined, "level"),
1186
- ],
1187
- "effort",
1188
- );
1268
+ thinking !== undefined && !modelIcon
1269
+ ? node(
1270
+ "row",
1271
+ {
1272
+ role: "zeta.composer.effort",
1273
+ gap: "xs",
1274
+ align: "center",
1275
+ title: `Thinking effort${thinkingHint}`,
1276
+ actions: { click: "thinking.cycle" },
1277
+ },
1278
+ [
1279
+ effortGlyph
1280
+ ? node("effort", { level: thinking }, undefined, "glyph")
1281
+ : node(
1282
+ "meter",
1283
+ { value: effortSteps === undefined ? null : effortSteps / 4, style: "blocks", steps: 4 },
1284
+ undefined,
1285
+ "meter",
1286
+ ),
1287
+ node("text", { text: thinking, wrap: "none" }, undefined, "level"),
1288
+ ],
1289
+ "effort",
1290
+ )
1291
+ : undefined;
1189
1292
  const submit = state.running
1190
1293
  ? node(
1191
1294
  "text",
@@ -1193,7 +1296,7 @@ export class CustomEditor extends Editor {
1193
1296
  role: "zeta.composer.stop",
1194
1297
  text: "Stop",
1195
1298
  tone: "error",
1196
- title: `Stop ${interruptKey === "escape" ? "esc" : formatKeyHint(interruptKey)}`,
1299
+ title: `Stop ${formatTooltipKey(interruptKey)}`,
1197
1300
  actions: { click: "interrupt" },
1198
1301
  },
1199
1302
  undefined,
@@ -1204,22 +1307,13 @@ export class CustomEditor extends Editor {
1204
1307
  {
1205
1308
  role: "zeta.composer.send",
1206
1309
  keys: ["enter"],
1207
- title: `Send ${formatKeyHint("enter")}`,
1310
+ title: `Send ${formatTooltipKey("enter")}`,
1208
1311
  actions: { click: "submit" },
1209
1312
  },
1210
1313
  undefined,
1211
1314
  "send",
1212
1315
  );
1213
- // The status facts are the bar's flexible space; without them a spacer keeps send at the end.
1214
- const bar = keyed(
1215
- row(compact([model, effort, facts?.extras ?? node("row", { grow: 1 }, [], "gap"), facts?.usage, submit]), {
1216
- role: "zeta.composer.bar",
1217
- gap: "sm",
1218
- align: "center",
1219
- }),
1220
- "bar",
1221
- );
1222
- if (!shell) return { focus, mode: undefined, bar };
1316
+ if (!shell) return { focus, mode: undefined, model, effort, submit };
1223
1317
  const runs = shell.kind === "bash" ? "Runs in your shell" : "Runs in Python";
1224
1318
  const mode = keyed(
1225
1319
  row(
@@ -1242,16 +1336,30 @@ export class CustomEditor extends Editor {
1242
1336
  ),
1243
1337
  "mode",
1244
1338
  );
1245
- return { focus, mode, bar };
1339
+ return { focus, mode, model, effort, submit };
1246
1340
  }
1247
1341
 
1248
1342
  /**
1249
1343
  * Clicks on the composer's controls take the same paths as their keys: ⇧⇥,
1250
1344
  * ⏎ and Esc; the viewing header's links (`focus:<id>`) go to the host,
1251
1345
  * the status facts' clicks (`status.*`) to their source. Selection edits
1252
- * go to the buffer.
1346
+ * go to the buffer; `send` submits its own prompt after saving the old draft
1347
+ * for recall and waiting for in-flight clipboard work.
1253
1348
  */
1254
1349
  override handleNativeEvent(event: NativeUiEvent): void {
1350
+ if (event.type === "send") {
1351
+ // A send is its own prompt: never submit a stale draft for blank input,
1352
+ // and retain a displaced draft (including its attachments) for recall.
1353
+ if (!event.text.trim() || !this.nativeSendable) return;
1354
+ if (this.#pasteInFlight > 0) {
1355
+ this.#pendingInput.push(event);
1356
+ return;
1357
+ }
1358
+ this.clearDraftForRecall();
1359
+ this.setCollapsedText(event.text);
1360
+ this.submit();
1361
+ return;
1362
+ }
1255
1363
  if (event.type !== "action") {
1256
1364
  super.handleNativeEvent(event);
1257
1365
  return;
@@ -1301,12 +1409,17 @@ export class CustomEditor extends Editor {
1301
1409
  /** Called when the viewing header asks to view agent `id` ({@link MAIN_AGENT_ID}: the main session). */
1302
1410
  onFocusAgent?: (id: string) => void;
1303
1411
 
1304
- /** Space-bar push-to-talk; set its `handler` to enable it. It is a text-composition gesture, so it
1305
- * stays out of Vim's Normal/Visual modes (where the space bar is the `l` motion) and away from an
1306
- * open autocomplete menu. */
1412
+ #spaceHoldSnapshot: { revision: number; line: number; col: number } | undefined;
1413
+
1414
+ /** Configurable push-to-talk; set its `handler` to enable it. It is a text-composition gesture, so
1415
+ * it stays out of Vim's Normal/Visual modes (where the space bar is the `l` motion), away from an
1416
+ * open autocomplete menu, and out of a pending character jump (whose target may be the key). */
1307
1417
  readonly spaceHold = new SpaceHoldGesture(
1308
- count => this.deleteBeforeCursor(count),
1309
- () => this.vimMode === "insert" && !this.isShowingAutocomplete(),
1418
+ count => {
1419
+ this.#spaceHoldSnapshot = undefined;
1420
+ if (count > 0) this.deleteBeforeCursor(count);
1421
+ },
1422
+ () => this.vimMode === "insert" && !this.isShowingAutocomplete() && !this.isJumpPending,
1310
1423
  );
1311
1424
 
1312
1425
  /** Custom key handlers from extensions and non-built-in app actions. */
@@ -1322,9 +1435,9 @@ export class CustomEditor extends Editor {
1322
1435
  * dispatching them so a trailing `Enter` after `Cmd+V` can't submit before the image lands on
1323
1436
  * `pendingImages` (Codex PR #3602 review). */
1324
1437
  #pasteInFlight = 0;
1325
- /** Input chunks deferred behind an in-flight paste, drained in FIFO order once the paste
1326
- * count returns to zero. */
1327
- #pendingInput: string[] = [];
1438
+ /** Input chunks and explicit prompts deferred behind an in-flight paste,
1439
+ * drained in FIFO order once the paste count returns to zero. */
1440
+ #pendingInput: (string | Extract<NativeUiEvent, { type: "send" }>)[] = [];
1328
1441
  #actionKeys = new Map<ConfigurableEditorAction, KeyId[]>(
1329
1442
  Object.entries(DEFAULT_ACTION_KEYS).map(([action, keys]) => [action as ConfigurableEditorAction, [...keys]]),
1330
1443
  );
@@ -1396,10 +1509,17 @@ export class CustomEditor extends Editor {
1396
1509
  #onPasteSettled = (): void => {
1397
1510
  this.#pasteInFlight--;
1398
1511
  if (this.#pasteInFlight > 0) return;
1399
- const drained = this.#pendingInput.splice(0);
1400
- for (const chunk of drained) this.handleInput(chunk);
1512
+ this.#drainPendingInput();
1401
1513
  };
1402
1514
 
1515
+ #drainPendingInput(): void {
1516
+ const drained = this.#pendingInput.splice(0);
1517
+ for (const input of drained) {
1518
+ if (typeof input === "string") this.handleInput(input);
1519
+ else this.handleNativeEvent(input);
1520
+ }
1521
+ }
1522
+
1403
1523
  /** Track `promise` as an in-flight paste so subsequent `handleInput` calls queue behind it,
1404
1524
  * then drain the queue once it settles. Codex PR #3602 review: without this, a trailing
1405
1525
  * keystroke (Enter most painfully) in the same stdin read processes synchronously while the
@@ -1410,6 +1530,10 @@ export class CustomEditor extends Editor {
1410
1530
  void promise.then(this.#onPasteSettled, this.#onPasteSettled);
1411
1531
  }
1412
1532
 
1533
+ capturesInput(data: string): boolean {
1534
+ return this.spaceHold.shouldRoute(data);
1535
+ }
1536
+
1413
1537
  override handleInput(data: string): void {
1414
1538
  // Serialize behind any in-flight async paste so a trailing Enter / follow-up key can't
1415
1539
  // submit before the clipboard image reaches `pendingImages` (Codex PR #3602 review).
@@ -1469,14 +1593,75 @@ export class CustomEditor extends Editor {
1469
1593
  this.#collapseSkillTokens();
1470
1594
  this.#collapseModelMentions();
1471
1595
  // No async paste was started; drain the queued trailing bytes ourselves.
1472
- const drained = this.#pendingInput.splice(0);
1473
- for (const chunk of drained) this.handleInput(chunk);
1596
+ this.#drainPendingInput();
1474
1597
  return;
1475
1598
  }
1476
1599
 
1477
1600
  const parsedKey = parseKey(data);
1478
1601
  const canonical = parsedKey !== undefined ? canonicalKeyId(parsedKey) : undefined;
1479
1602
 
1603
+ // Space-hold push-to-talk runs before editor shortcuts so a reserved binding can never
1604
+ // delete, move, submit, or invoke an app action while the gesture is enabled.
1605
+ const spaceHoldText = getSpaceHoldText(data, canonical);
1606
+ const priorSnapshot = this.#spaceHoldSnapshot;
1607
+ if (priorSnapshot) {
1608
+ const cursor = this.getCursor();
1609
+ if (
1610
+ this.textRevision !== priorSnapshot.revision ||
1611
+ cursor.line !== priorSnapshot.line ||
1612
+ cursor.col !== priorSnapshot.col
1613
+ ) {
1614
+ this.spaceHold.process(undefined);
1615
+ this.#spaceHoldSnapshot = undefined;
1616
+ }
1617
+ }
1618
+
1619
+ switch (this.spaceHold.process(canonical, spaceHoldText?.length ?? 0)) {
1620
+ case "type": {
1621
+ const text = spaceHoldText!;
1622
+ const beforeCursor = this.getCursor();
1623
+ const beforeLine = this.getLines()[beforeCursor.line] ?? "";
1624
+ const beforeRevision = this.textRevision;
1625
+ this.typeCharacter(text);
1626
+ this.#collapseSkillTokens();
1627
+ this.#collapseModelMentions();
1628
+ this.#normalizeQueuePrefix(hadBareQueuePrefix);
1629
+
1630
+ const afterCursor = this.getCursor();
1631
+ const afterLine = this.getLines()[afterCursor.line] ?? "";
1632
+ const noInsertion =
1633
+ this.textRevision === beforeRevision &&
1634
+ afterCursor.line === beforeCursor.line &&
1635
+ afterCursor.col === beforeCursor.col &&
1636
+ afterLine === beforeLine;
1637
+ const literalInsertion =
1638
+ afterCursor.line === beforeCursor.line &&
1639
+ afterCursor.col === beforeCursor.col + text.length &&
1640
+ afterLine === beforeLine.slice(0, beforeCursor.col) + text + beforeLine.slice(beforeCursor.col);
1641
+ if (noInsertion) {
1642
+ this.spaceHold.recordTyped(0);
1643
+ } else if (literalInsertion) {
1644
+ this.spaceHold.recordTyped(text.length);
1645
+ } else {
1646
+ // Typing hooks rewrote the candidate (e.g. autocorrect/inline replacement).
1647
+ // Commit that edit and start a fresh cadence rather than deleting its suffix later.
1648
+ this.spaceHold.process(undefined);
1649
+ this.#spaceHoldSnapshot = undefined;
1650
+ return;
1651
+ }
1652
+ const cursor = this.getCursor();
1653
+ this.#spaceHoldSnapshot = {
1654
+ revision: this.textRevision,
1655
+ line: cursor.line,
1656
+ col: cursor.col,
1657
+ };
1658
+ return;
1659
+ }
1660
+ case "swallow":
1661
+ return;
1662
+ }
1663
+ this.#spaceHoldSnapshot = undefined;
1664
+
1480
1665
  // Left-arrow on an empty editor: surface for the agent-hub double-tap
1481
1666
  // gesture. Plain "left" only — modified arrows and any in-text cursor
1482
1667
  // movement fall through to normal handling.
@@ -1485,24 +1670,16 @@ export class CustomEditor extends Editor {
1485
1670
  return;
1486
1671
  }
1487
1672
 
1488
- // Space-hold push-to-talk: a sustained space bar starts/stops STT instead of typing spaces.
1489
- switch (this.spaceHold.process(canonical === "space")) {
1490
- case "type":
1491
- this.#forwardInput(data);
1492
- return;
1493
- case "swallow":
1494
- return;
1495
- }
1496
-
1497
1673
  // One union probe decides whether any per-action interception below can
1498
1674
  // match — plain typing then skips the ~20 per-action set lookups per key.
1499
1675
  if (
1500
1676
  canonical !== undefined &&
1501
1677
  (this.#actionMatchKeyUnion.has(canonical) || this.#customMatchKeys.has(canonical))
1502
1678
  ) {
1503
- // Intercept configured image paste (async - fires and handles result)
1679
+ // Serialize configured clipboard paste just like bracketed image paste:
1680
+ // explicit sends and subsequent keys must wait for its attachments.
1504
1681
  if (this.#matchesAction(canonical, "app.clipboard.pasteImage") && this.onPasteImage) {
1505
- void this.onPasteImage();
1682
+ this.#trackAsyncPaste(Promise.resolve(this.onPasteImage()));
1506
1683
  return;
1507
1684
  }
1508
1685