@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
@@ -27,7 +27,7 @@ import type {
27
27
  } from "./host";
28
28
  import type { Editor } from "../components/editor";
29
29
  import { getSessionAccentAnsi, getSessionAccentHex } from "../theme/session-color";
30
- import { sanitizeStatusText } from "../chrome/shared";
30
+ import { sanitizeHookStatusText, sanitizeStatusText } from "../chrome/shared";
31
31
  import { getThemeEpoch, theme } from "../theme";
32
32
  import { type CompactionBoundaries, EMPTY_STRING_PARTS, getToolSchemaMetadataRevision } from "./context-usage";
33
33
  import {
@@ -51,9 +51,12 @@ import type {
51
51
  ComposerFactsSource,
52
52
  EffectiveStatusLineSettings,
53
53
  SegmentView,
54
+ SeparatorDef,
54
55
  StatusLineSegmentId,
55
56
  StatusLineSegmentOptions,
57
+ StatusLineSeparatorStyle,
56
58
  StatusLineSettings,
59
+ StatusResetExpiry,
57
60
  } from "./types";
58
61
 
59
62
  /** What a click on a native status segment asks zeta to open. */
@@ -81,9 +84,9 @@ const PINNED_NATIVE_PRIORITY = 1000;
81
84
  /**
82
85
  * Segments a TSP terminal shows outside the composer's facts: the model chip,
83
86
  * the context hairline and usage text (context, cost), Tern's pane header
84
- * (path, git), the tab title (session name, PR), the HUD pills (subagents),
85
- * the editor (vim) and the brand (`pi`; while focus-proxied, the viewed agent
86
- * is the composer's viewing header).
87
+ * (path), the tab title (session name, PR), the HUD pills (subagents), the
88
+ * editor (vim) and the brand (`pi`; while focus-proxied, the viewed agent is
89
+ * the composer's viewing header).
87
90
  */
88
91
  const COMPOSER_HOMED_SEGMENTS: Partial<Record<StatusLineSegmentId, true>> = {
89
92
  pi: true,
@@ -92,7 +95,6 @@ const COMPOSER_HOMED_SEGMENTS: Partial<Record<StatusLineSegmentId, true>> = {
92
95
  context_total: true,
93
96
  cost: true,
94
97
  path: true,
95
- git: true,
96
98
  pr: true,
97
99
  session_name: true,
98
100
  subagents: true,
@@ -222,12 +224,51 @@ function reportMatchesExactIdentity(report: UsageReport, identity: OAuthAccountI
222
224
  // Context-usage memo
223
225
  // ═══════════════════════════════════════════════════════════════════════════
224
226
 
227
+ /**
228
+ * Serialized length of tool-call arguments, memoized per arguments object.
229
+ * Streaming providers may grow the same object in place (at any depth), so
230
+ * each entry keeps a shape check — key/element counts plus string and number
231
+ * lengths over the whole tree — and is recomputed when it differs, without
232
+ * re-serializing the whole (possibly multi-KB edit/write) payload on every
233
+ * status-line rebuild.
234
+ */
235
+ const toolArgumentsLengthCache = new WeakMap<object, { check: number; length: number }>();
236
+
237
+ /** Past this depth the check stops descending; JSON.stringify rejects real cycles anyway. */
238
+ const SHAPE_CHECK_MAX_DEPTH = 32;
239
+
240
+ function toolArgumentsShapeCheck(value: unknown, depth = 0): number {
241
+ if (typeof value === "string") return value.length + 1;
242
+ if (typeof value === "number" || typeof value === "bigint") return String(value).length + 1;
243
+ if (value === null || typeof value !== "object" || depth >= SHAPE_CHECK_MAX_DEPTH) return 1;
244
+ let check = 1;
245
+ for (const child of Object.values(value)) check += 1 + toolArgumentsShapeCheck(child, depth + 1);
246
+ return check;
247
+ }
248
+
249
+ function toolArgumentsLength(args: unknown): number {
250
+ if (typeof args === "string") return args.length;
251
+ if (args === null || typeof args !== "object") return String(args).length;
252
+ const check = toolArgumentsShapeCheck(args);
253
+ const cached = toolArgumentsLengthCache.get(args);
254
+ if (cached !== undefined && cached.check === check) return cached.length;
255
+ let length: number;
256
+ try {
257
+ length =
258
+ JSON.stringify(args, (_key, value) => (typeof value === "bigint" ? value.toString() : value))?.length ?? 0;
259
+ } catch {
260
+ length = String(args).length;
261
+ }
262
+ toolArgumentsLengthCache.set(args, { check, length });
263
+ return length;
264
+ }
265
+
225
266
  /**
226
267
  * Cheap structural fingerprint of a message's tokenizable content. O(blocks) —
227
- * only reads string `.length` and primitives, never copies or serializes.
228
- * Detects in-place growth of the streaming tail (and other in-place mutations)
229
- * so the cached `getContextUsage()` result is recomputed when — and only when —
230
- * the numbers it depends on change.
268
+ * only reads string `.length`, primitives, and memoized tool-argument sizes;
269
+ * never copies. Detects growth of the streaming tail (in-place text appends,
270
+ * freshly parsed tool arguments) so the cached `getContextUsage()` result is
271
+ * recomputed when — and only when — the numbers it depends on change.
231
272
  */
232
273
  function messageFingerprint(msg: AgentMessage): string {
233
274
  const role = (msg as { role?: string }).role ?? "";
@@ -309,15 +350,7 @@ function messageFingerprint(msg: AgentMessage): string {
309
350
  redactedLen += b.data.length;
310
351
  } else if (b.type === "toolCall") {
311
352
  if (typeof b.name === "string") textLen += b.name.length;
312
- if (b.arguments !== undefined) {
313
- try {
314
- textLen += JSON.stringify(b.arguments, (_key, value) =>
315
- typeof value === "bigint" ? value.toString() : value,
316
- ).length;
317
- } catch {
318
- textLen += String(b.arguments).length;
319
- }
320
- }
353
+ if (b.arguments !== undefined) textLen += toolArgumentsLength(b.arguments);
321
354
  }
322
355
  }
323
356
  }
@@ -429,6 +462,25 @@ interface CachedStatusLine {
429
462
  externalInputs: StatusLineExternalInputs;
430
463
  }
431
464
 
465
+ /** Dot separator of the plain (borderless) layouts; carries no caps. */
466
+ const PLAIN_SEPARATOR: SeparatorDef = { left: "·", right: "·" };
467
+
468
+ /** A separator style's glyphs and cell widths: theme-pure, so measured once per theme. */
469
+ interface SeparatorMetrics {
470
+ themeRef: unknown;
471
+ themeEpoch: number;
472
+ style: StatusLineSeparatorStyle;
473
+ def: SeparatorDef;
474
+ leftSepWidth: number;
475
+ rightSepWidth: number;
476
+ /** Left group's closing cap (`endCaps.right`); 0 without caps. */
477
+ leftCapWidth: number;
478
+ /** Right group's opening cap (`endCaps.left`); 0 without caps. */
479
+ rightCapWidth: number;
480
+ /** Band layout's flush-left soft cap; 0 without caps. */
481
+ bandCapWidth: number;
482
+ }
483
+
432
484
  interface ActiveRepoCache {
433
485
  projectDir: string;
434
486
  activeRepo: ActiveRepoContext | null;
@@ -588,10 +640,21 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
588
640
  };
589
641
  #statusLineInputRevision = 0;
590
642
  #statusLineClockTick = 0;
643
+ /**
644
+ * Earliest wall-clock ms at which a TTL-throttled VCS cache the render path
645
+ * served goes stale (git/jj status, watcher-less branch poll, repository
646
+ * re-probe, PR activity refresh); `Infinity` when none can. The first frame
647
+ * past it expires the render memo so the getters refetch — instead of a
648
+ * fixed polling tick that rebuilt the whole bar every second.
649
+ */
650
+ #vcsStaleAt = Number.POSITIVE_INFINITY;
591
651
  /** Reused probe buffer for {@link #readStatusLineExternalInputs}; handed to the new cache entry on a miss. */
592
652
  #externalInputsProbe = new StatusLineExternalInputs();
593
653
  /** `adjustHsv` result for the gauge's threshold tint; pure in its source hex. */
594
654
  #dimmedAccentMemo: { sourceHex: string; hex: string } | undefined;
655
+ /** Separator widths for the plain and the styled (box/band) layouts; see {@link #separatorMetrics}. */
656
+ #plainSeparatorMetrics: SeparatorMetrics | undefined;
657
+ #styledSeparatorMetrics: SeparatorMetrics | undefined;
595
658
  /** Gauge boundary markers; a pure function of the session's compaction settings, model, and window. */
596
659
  #compactionBoundariesMemo:
597
660
  | {
@@ -672,7 +735,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
672
735
  * round-trips because the same {@link StatusLineSession} ref is reused.
673
736
  */
674
737
  #activeMeters: WeakMap<TSession, ActiveMeter> = new WeakMap();
675
- #planModeStatus: { enabled: boolean; paused: boolean } | null = null;
738
+ #planModeStatus: { enabled: boolean; paused: boolean; workflow?: "parallel" | "iterative" | "ultra" } | null = null;
676
739
  #loopModeStatus: SegmentContext["loopMode"] = null;
677
740
  #goalModeStatus: { enabled: boolean; paused: boolean } | null = null;
678
741
  #vibeModeStatus: { enabled: boolean } | null = null;
@@ -733,6 +796,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
733
796
  expiryHours?: number;
734
797
  expired?: boolean;
735
798
  unavailableReason?: string;
799
+ expiring?: StatusResetExpiry;
736
800
  };
737
801
  } | null = null;
738
802
  #cachedUsageContextKey: string | null = null;
@@ -745,6 +809,9 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
745
809
  #latestAppliedUsageRefreshSequence = 0;
746
810
  #codexResetSnapshots = new Map<string, CodexResetUsageSnapshot>();
747
811
  #onCodexResetFireworks: ((event: CodexResetFireworksEvent) => void) | undefined;
812
+ #onResetExpiryNotice: ((notice: string) => void) | undefined;
813
+ /** Conversations already warned about expiring saved resets: later refreshes stay quiet. */
814
+ #resetExpiryNoticedSessions = new Set<string>();
748
815
  // Context-usage memo. The status line redraws on every agent event, so the
749
816
  // hot path must not recompute context tokens unless an input changed.
750
817
  // `getContextUsage()` anchors on the last assistant's real prompt-token
@@ -815,27 +882,46 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
815
882
  #resolveRepository(cache: ActiveRepoCache): VcsRepo | null {
816
883
  if (cache.repository) return cache.repository;
817
884
  const now = Date.now();
818
- if (now - cache.repositoryCheckedAt < WATCHER_FAILURE_POLL_TTL_MS) return null;
819
- cache.repository = vcs.repo(cache.effectiveGitCwd);
820
- cache.repositoryCheckedAt = now;
885
+ if (now - cache.repositoryCheckedAt >= WATCHER_FAILURE_POLL_TTL_MS) {
886
+ cache.repository = vcs.repo(cache.effectiveGitCwd);
887
+ cache.repositoryCheckedAt = now;
888
+ }
889
+ // Still absent: re-probe on the bounded interval so a later `git init` shows up.
890
+ if (!cache.repository) this.#noteVcsStaleAt(cache.repositoryCheckedAt + WATCHER_FAILURE_POLL_TTL_MS);
821
891
  return cache.repository;
822
892
  }
823
893
 
824
894
  #resolveDisplayRepository(cache: ActiveRepoCache): VcsRepo | null {
825
895
  if (cache.displayRepository) return cache.displayRepository;
826
896
  const now = Date.now();
827
- if (now - cache.displayRepositoryCheckedAt < WATCHER_FAILURE_POLL_TTL_MS) return null;
828
- let display: VcsRepo | null;
829
- try {
830
- display = vcs.repoForDisplay(cache.effectiveGitCwd);
831
- } catch {
832
- display = null;
897
+ if (now - cache.displayRepositoryCheckedAt >= WATCHER_FAILURE_POLL_TTL_MS) {
898
+ let display: VcsRepo | null;
899
+ try {
900
+ display = vcs.repoForDisplay(cache.effectiveGitCwd);
901
+ } catch {
902
+ display = null;
903
+ }
904
+ cache.displayRepository = display ?? cache.repository;
905
+ cache.displayRepositoryCheckedAt = now;
906
+ }
907
+ if (!cache.displayRepository) {
908
+ this.#noteVcsStaleAt(cache.displayRepositoryCheckedAt + WATCHER_FAILURE_POLL_TTL_MS);
833
909
  }
834
- cache.displayRepository = display ?? cache.repository;
835
- cache.displayRepositoryCheckedAt = now;
836
910
  return cache.displayRepository;
837
911
  }
838
912
 
913
+ /** Lower {@link #vcsStaleAt} to `at` when a served VCS cache expires sooner. */
914
+ #noteVcsStaleAt(at: number): void {
915
+ if (at < this.#vcsStaleAt) this.#vcsStaleAt = at;
916
+ }
917
+
918
+ /** Expire the render memo once a served VCS cache has gone stale, so this frame refetches. */
919
+ #expireStaleVcsRender(nowMs: number): void {
920
+ if (nowMs < this.#vcsStaleAt) return;
921
+ this.#vcsStaleAt = Number.POSITIVE_INFINITY;
922
+ this.#invalidateStatusLineRenderCache();
923
+ }
924
+
839
925
  /**
840
926
  * Re-point the status line at another session (focus proxy). Invalidate: model/context/usage all derive
841
927
  * from it. `focusedAgentId` is the focused subagent id while the view is proxied, undefined for main.
@@ -1025,11 +1111,15 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
1025
1111
  return meter;
1026
1112
  }
1027
1113
 
1028
- setPlanModeStatus(status: { enabled: boolean; paused: boolean } | undefined): void {
1114
+ setPlanModeStatus(
1115
+ status: { enabled: boolean; paused: boolean; workflow?: "parallel" | "iterative" | "ultra" } | undefined,
1116
+ ): void {
1029
1117
  const next = status ?? null;
1030
1118
  if (
1031
1119
  this.#planModeStatus === next ||
1032
- (this.#planModeStatus?.enabled === next?.enabled && this.#planModeStatus?.paused === next?.paused)
1120
+ (this.#planModeStatus?.enabled === next?.enabled &&
1121
+ this.#planModeStatus?.paused === next?.paused &&
1122
+ this.#planModeStatus?.workflow === next?.workflow)
1033
1123
  ) {
1034
1124
  return;
1035
1125
  }
@@ -1129,6 +1219,11 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
1129
1219
  this.#onCodexResetFireworks = handler;
1130
1220
  }
1131
1221
 
1222
+ /** Set the callback that presents the first pool-wide saved-reset expiry warning, or clear it with `undefined`. */
1223
+ setResetExpiryNoticeHandler(handler: ((notice: string) => void) | undefined): void {
1224
+ this.#onResetExpiryNotice = handler;
1225
+ }
1226
+
1132
1227
  setHookStatus(key: string, text: string | undefined): void {
1133
1228
  if (text === undefined) {
1134
1229
  if (!this.#hookStatuses.delete(key)) return;
@@ -1198,6 +1293,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
1198
1293
  this.#stopPricingTimer();
1199
1294
  this.#clearUsageStartTimer();
1200
1295
  this.#onCodexResetFireworks = undefined;
1296
+ this.#onResetExpiryNotice = undefined;
1201
1297
  this.#codexResetSnapshots.clear();
1202
1298
  this.#retireGitWatcher();
1203
1299
  }
@@ -1236,7 +1332,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
1236
1332
  * a 40ms frame timer so the fade keeps animating after the working loader
1237
1333
  * (the usual repaint driver) has stopped.
1238
1334
  */
1239
- #brandFgAnsi(working: boolean, sessionAccentEnabled: boolean): string {
1335
+ #brandFgAnsi(working: boolean, sessionAccentEnabled: boolean, brandVisible: boolean): string {
1240
1336
  const sessionName = sessionAccentEnabled ? this.session.sessionManager?.getSessionName() : undefined;
1241
1337
  const idleHex = theme.getColorHex("dim");
1242
1338
  const workingHex =
@@ -1250,7 +1346,8 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
1250
1346
  startedAt: now,
1251
1347
  };
1252
1348
  this.#brandWorking = working;
1253
- this.#startBrandFadeTimer();
1349
+ // Only a visible `pi` segment needs fade frames; other bars stay quiet.
1350
+ if (brandVisible) this.#startBrandFadeTimer();
1254
1351
  }
1255
1352
  const hex = this.#sampleBrandHex(working ? workingHex : idleHex, now);
1256
1353
  return getSessionAccentAnsi(hex) ?? theme.getFgAnsi(working ? "accent" : "dim");
@@ -1449,7 +1546,9 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
1449
1546
  taggedRepository[GIT_VIEW] = gitRepository;
1450
1547
  }
1451
1548
  if (!gitRepository) {
1452
- if (this.#jjBranchActive || Date.now() - this.#jjBranchLastFetch < JJ_REFRESH_TTL_MS) {
1549
+ if (this.#jjBranchActive) return this.#cachedJjBranch;
1550
+ if (Date.now() - this.#jjBranchLastFetch < JJ_REFRESH_TTL_MS) {
1551
+ this.#noteVcsStaleAt(this.#jjBranchLastFetch + JJ_REFRESH_TTL_MS);
1453
1552
  return this.#cachedJjBranch;
1454
1553
  }
1455
1554
  const request: JjResolveRequest = {
@@ -1476,6 +1575,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
1476
1575
  if (this.#jjCacheGeneration === generation) this.#jjBranchLastFetch = Date.now();
1477
1576
  }
1478
1577
  if (this.#jjCacheGeneration !== generation || this.#disposed) return;
1578
+ this.#noteVcsStaleAt(this.#jjBranchLastFetch + JJ_REFRESH_TTL_MS);
1479
1579
  const changed = next !== this.#cachedJjBranch;
1480
1580
  this.#cachedJjBranch = next;
1481
1581
  if (changed) {
@@ -1489,6 +1589,9 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
1489
1589
  this.#gitWatcherUnavailable &&
1490
1590
  (this.#branchLastFetch === undefined || Date.now() - this.#branchLastFetch >= WATCHER_FAILURE_POLL_TTL_MS);
1491
1591
  if (this.#cachedBranch !== undefined && this.#cachedBranchCwd === gitCwd && !fallbackCacheExpired) {
1592
+ if (this.#gitWatcherUnavailable && this.#branchLastFetch !== undefined) {
1593
+ this.#noteVcsStaleAt(this.#branchLastFetch + WATCHER_FAILURE_POLL_TTL_MS);
1594
+ }
1492
1595
  return this.#cachedBranch;
1493
1596
  }
1494
1597
 
@@ -1541,6 +1644,9 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
1541
1644
  this.#cachedBranchRepoId = repoId;
1542
1645
  this.#cachedBranch = next;
1543
1646
  this.#branchLastFetch = Date.now();
1647
+ if (this.#gitWatcherUnavailable) {
1648
+ this.#noteVcsStaleAt(this.#branchLastFetch + WATCHER_FAILURE_POLL_TTL_MS);
1649
+ }
1544
1650
  if (prev !== next) {
1545
1651
  this.#invalidateStatusLineRenderCache();
1546
1652
  this.#onBranchChange?.();
@@ -1561,6 +1667,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
1561
1667
  this.#cachedBranchCwd = gitCwd;
1562
1668
  this.#cachedBranchRepoId = gitHeadPath;
1563
1669
  this.#branchLastFetch = Date.now();
1670
+ if (this.#gitWatcherUnavailable) this.#noteVcsStaleAt(this.#branchLastFetch + WATCHER_FAILURE_POLL_TTL_MS);
1564
1671
  if (!head) {
1565
1672
  this.#cachedBranch = null;
1566
1673
  return null;
@@ -1602,7 +1709,9 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
1602
1709
  const repository = this.#resolveRepository(activeRepoCache);
1603
1710
  if (!repository) return null;
1604
1711
  if (repository.kind() === "jj") {
1605
- if (this.#jjStatusActive || Date.now() - this.#jjStatusLastFetch < JJ_REFRESH_TTL_MS) {
1712
+ if (this.#jjStatusActive) return this.#cachedJjStatus;
1713
+ if (Date.now() - this.#jjStatusLastFetch < JJ_REFRESH_TTL_MS) {
1714
+ this.#noteVcsStaleAt(this.#jjStatusLastFetch + JJ_REFRESH_TTL_MS);
1606
1715
  return this.#cachedJjStatus;
1607
1716
  }
1608
1717
  const request: JjResolveRequest = {
@@ -1624,6 +1733,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
1624
1733
  if (this.#jjCacheGeneration === generation) this.#jjStatusLastFetch = Date.now();
1625
1734
  }
1626
1735
  if (this.#jjCacheGeneration !== generation || this.#disposed) return;
1736
+ this.#noteVcsStaleAt(this.#jjStatusLastFetch + JJ_REFRESH_TTL_MS);
1627
1737
  const prev = this.#cachedJjStatus;
1628
1738
  this.#cachedJjStatus = next;
1629
1739
  if (JSON.stringify(prev) !== JSON.stringify(next)) {
@@ -1638,6 +1748,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
1638
1748
  return this.#cachedGitStatusCwd === gitCwd ? this.#cachedGitStatus : null;
1639
1749
  }
1640
1750
  if (this.#cachedGitStatusCwd === gitCwd && Date.now() - this.#gitStatusLastFetch < GIT_STATUS_TTL_MS) {
1751
+ this.#noteVcsStaleAt(this.#gitStatusLastFetch + GIT_STATUS_TTL_MS);
1641
1752
  return this.#cachedGitStatus;
1642
1753
  }
1643
1754
 
@@ -1657,6 +1768,8 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
1657
1768
  this.#cachedGitStatusCwd = gitCwd;
1658
1769
  this.#gitStatusLastFetch = this.#gitStatusGeneration === generation ? Date.now() : 0;
1659
1770
  this.#gitStatusInFlightCwd = undefined;
1771
+ // A zeroed stamp (superseded generation) is already due: refetch next frame.
1772
+ this.#noteVcsStaleAt(this.#gitStatusLastFetch + GIT_STATUS_TTL_MS);
1660
1773
  if (!this.#disposed && JSON.stringify(prev) !== JSON.stringify(nextStatus)) {
1661
1774
  this.#invalidateStatusLineRenderCache();
1662
1775
  this.#onBranchChange?.();
@@ -1935,6 +2048,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
1935
2048
  this.#invalidateStatusLineRenderCache();
1936
2049
  this.#onBranchChange?.();
1937
2050
  }
2051
+ this.#noticeResetExpiry(session, reports);
1938
2052
  if (!resetSnapshot) return;
1939
2053
  const contextKey = this.#formatUsageContextKey(activeProvider, activeIdentity);
1940
2054
  const previous = this.#codexResetSnapshots.get(contextKey);
@@ -1944,6 +2058,33 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
1944
2058
  if (event) this.#onCodexResetFireworks?.(event);
1945
2059
  }
1946
2060
 
2061
+ #noticeResetExpiry(session: TSession, reports: unknown): void {
2062
+ // A focused subagent's view is not a conversation the user started; warn in their own.
2063
+ if (
2064
+ this.#focusedAgentId !== undefined ||
2065
+ !this.#onResetExpiryNotice ||
2066
+ !this.host.resetExpiryNotice ||
2067
+ !Array.isArray(reports)
2068
+ ) {
2069
+ return;
2070
+ }
2071
+ const sessionId = session.sessionManager.getSessionId();
2072
+ if (this.#resetExpiryNoticedSessions.has(sessionId)) return;
2073
+ // fetchUsageReports supplies normalized rows; keep only entries shaped like reports.
2074
+ const usageReports = reports.filter(
2075
+ (report): report is UsageReport =>
2076
+ !!report &&
2077
+ typeof report === "object" &&
2078
+ "provider" in report &&
2079
+ "limits" in report &&
2080
+ Array.isArray(report.limits),
2081
+ );
2082
+ const notice = this.host.resetExpiryNotice(usageReports, Date.now());
2083
+ if (!notice) return;
2084
+ this.#resetExpiryNoticedSessions.add(sessionId);
2085
+ this.#onResetExpiryNotice(notice);
2086
+ }
2087
+
1947
2088
  #observeLateUsageRefresh(session: TSession, reportsPromise: Promise<unknown>, sequence: number): void {
1948
2089
  void reportsPromise
1949
2090
  .then(reports => {
@@ -2050,6 +2191,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
2050
2191
  expiryHours?: number;
2051
2192
  expired?: boolean;
2052
2193
  unavailableReason?: string;
2194
+ expiring?: StatusResetExpiry;
2053
2195
  };
2054
2196
  } | null {
2055
2197
  if (!Array.isArray(reports)) return null;
@@ -2167,7 +2309,16 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
2167
2309
  ? resetReports.find(report => reportMatchesExactIdentity(report, context.identity))
2168
2310
  : undefined;
2169
2311
  const resetSummary = summarizeUsageResetCredits(resetReport?.resetCredits, now);
2170
- const resetExpiryMs = resetSummary?.soonestExpiry ? Date.parse(resetSummary.soonestExpiry) - now : undefined;
2312
+ const expiring =
2313
+ resetSummary && resetSummary.bankedCount > 0 && resetReport
2314
+ ? this.host.classifyResetExpiry?.(resetReport, now)
2315
+ : undefined;
2316
+ // The highlighted countdown is the warned reset's, not an earlier one that is not worth warning about.
2317
+ const resetExpiryMs = expiring
2318
+ ? expiring.expiresAtMs - now
2319
+ : resetSummary?.soonestExpiry
2320
+ ? Date.parse(resetSummary.soonestExpiry) - now
2321
+ : undefined;
2171
2322
  const resetCredits =
2172
2323
  resetSummary && resetSummary.bankedCount > 0
2173
2324
  ? {
@@ -2179,6 +2330,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
2179
2330
  : undefined,
2180
2331
  expired: resetExpiryMs !== undefined && resetExpiryMs <= 0,
2181
2332
  unavailableReason: resetSummary.unavailableReason,
2333
+ expiring,
2182
2334
  }
2183
2335
  : undefined;
2184
2336
  if (!selectedGroup) return resetCredits ? { resetCredits } : null;
@@ -2387,7 +2539,9 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
2387
2539
  const gitPr = includePr ? this.#lookupPr(activeRepoCache) : null;
2388
2540
  const compactionSpeculation = this.session.compactionSpeculation ?? "idle";
2389
2541
  this.#syncSpeculationBlink(compactionSpeculation);
2390
- const sessionAccentEnabled = this.#resolveSettings().sessionAccent !== false;
2542
+ const settings = this.#resolveSettings();
2543
+ const sessionAccentEnabled = settings.sessionAccent !== false;
2544
+ const brandVisible = settings.leftSegments.includes("pi") || settings.rightSegments.includes("pi");
2391
2545
  const turnElapsedMs = this.getTurnElapsedMs();
2392
2546
  return {
2393
2547
  session: this.session,
@@ -2426,7 +2580,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
2426
2580
  activeMs: this.getActiveMs(),
2427
2581
  turnElapsedMs,
2428
2582
  now: new Date(nowMs),
2429
- brandFgAnsi: this.#brandFgAnsi(turnElapsedMs !== null, sessionAccentEnabled),
2583
+ brandFgAnsi: this.#brandFgAnsi(turnElapsedMs !== null, sessionAccentEnabled, brandVisible),
2430
2584
  git: {
2431
2585
  branch: gitBranch,
2432
2586
  status: gitStatus,
@@ -2655,19 +2809,30 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
2655
2809
 
2656
2810
  /**
2657
2811
  * Smallest wall-clock unit that can change a visible configured segment.
2658
- * A zero tick keeps truly static bars cached indefinitely; active animation,
2659
- * clocks, countdowns, and VCS fallback polling advance only at their own
2660
- * display/probe cadence.
2812
+ * A zero tick keeps truly static bars cached indefinitely; animation, clocks,
2813
+ * and countdowns advance only at their own display cadence. VCS refreshes are
2814
+ * deadline-driven ({@link #expireStaleVcsRender}), not ticked.
2661
2815
  */
2662
2816
  #statusLineClock(nowMs: number, effectiveSettings: EffectiveStatusLineSettings): number {
2663
2817
  const leftSegments = effectiveSettings.leftSegments;
2664
2818
  const rightSegments = effectiveSettings.rightSegments;
2665
- const meter = this.#meter();
2666
- if (meter.activeStartedAt !== null || this.#brandFade !== null) {
2819
+ const turnActive = this.#meter().activeStartedAt !== null;
2820
+ // The `pi` brand is the only spinner-rate animation: its braille spinner
2821
+ // while a turn runs and its color fade across turn edges. While
2822
+ // focus-proxied it shows the viewed agent's id instead.
2823
+ if (
2824
+ (turnActive || this.#brandFade !== null) &&
2825
+ this.#focusedAgentId === undefined &&
2826
+ (leftSegments.includes("pi") || rightSegments.includes("pi"))
2827
+ ) {
2667
2828
  return Math.floor(nowMs / SPINNER_ADVANCE_MS);
2668
2829
  }
2669
2830
  const includesTime = leftSegments.includes("time") || rightSegments.includes("time");
2670
2831
  if (
2832
+ // A running turn also moves readouts that have no invalidation path of
2833
+ // their own (background-job badge, live and vibe-worker tok/s): refresh
2834
+ // them once a second instead of on the spinner tick.
2835
+ turnActive ||
2671
2836
  leftSegments.includes("time_spent") ||
2672
2837
  rightSegments.includes("time_spent") ||
2673
2838
  (this.#loopModeStatus?.limit?.kind === "duration" &&
@@ -2679,12 +2844,6 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
2679
2844
  if (includesTime) {
2680
2845
  return Math.floor(nowMs / 60_000);
2681
2846
  }
2682
- if (this.#gitEnabled() && (hasGitBackedSegment(leftSegments) || hasGitBackedSegment(rightSegments))) {
2683
- return Math.floor(nowMs / 1_000);
2684
- }
2685
- if (this.#gitEnabled() && (hasPathSegment(leftSegments) || hasPathSegment(rightSegments))) {
2686
- return Math.floor(nowMs / WATCHER_FAILURE_POLL_TTL_MS);
2687
- }
2688
2847
  return 0;
2689
2848
  }
2690
2849
 
@@ -2693,6 +2852,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
2693
2852
  const externalInputs = this.#externalInputsProbe;
2694
2853
  this.#readStatusLineExternalInputs(externalInputs);
2695
2854
  const nowMs = Date.now();
2855
+ this.#expireStaleVcsRender(nowMs);
2696
2856
  const clockTick = this.#statusLineClock(nowMs, effectiveSettings);
2697
2857
  if (clockTick !== this.#statusLineClockTick) {
2698
2858
  this.#statusLineClockTick = clockTick;
@@ -2728,6 +2888,33 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
2728
2888
  return result;
2729
2889
  }
2730
2890
 
2891
+ /**
2892
+ * Separator glyphs and cell widths for the plain or styled layouts, measured
2893
+ * once per theme (and style) instead of on every rebuild.
2894
+ */
2895
+ #separatorMetrics(plain: boolean, style: StatusLineSeparatorStyle): SeparatorMetrics {
2896
+ const themeEpoch = getThemeEpoch();
2897
+ const memo = plain ? this.#plainSeparatorMetrics : this.#styledSeparatorMetrics;
2898
+ if (memo && memo.themeRef === theme && memo.themeEpoch === themeEpoch && (plain || memo.style === style)) {
2899
+ return memo;
2900
+ }
2901
+ const def = plain ? PLAIN_SEPARATOR : getSeparator(style, theme);
2902
+ const metrics: SeparatorMetrics = {
2903
+ themeRef: theme,
2904
+ themeEpoch,
2905
+ style,
2906
+ def,
2907
+ leftSepWidth: visibleWidth(def.left),
2908
+ rightSepWidth: visibleWidth(def.right),
2909
+ leftCapWidth: def.endCaps ? visibleWidth(def.endCaps.right) : 0,
2910
+ rightCapWidth: def.endCaps ? visibleWidth(def.endCaps.left) : 0,
2911
+ bandCapWidth: def.endCaps ? visibleWidth(theme.sep.powerlineCapLeft) : 0,
2912
+ };
2913
+ if (plain) this.#plainSeparatorMetrics = metrics;
2914
+ else this.#styledSeparatorMetrics = metrics;
2915
+ return metrics;
2916
+ }
2917
+
2731
2918
  /**
2732
2919
  * Build the status bar for one of five layouts:
2733
2920
  * - `box`: powerline groups joined by the context-reactive gauge line
@@ -2764,9 +2951,8 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
2764
2951
  nowMs,
2765
2952
  previewTitle,
2766
2953
  );
2767
- const separatorDef = plain
2768
- ? { left: "·", right: "·" }
2769
- : getSeparator(effectiveSettings.separator ?? "powerline-thin", theme);
2954
+ const separators = this.#separatorMetrics(plain, effectiveSettings.separator ?? "powerline-thin");
2955
+ const separatorDef = separators.def;
2770
2956
 
2771
2957
  // `transparent` reuses the empty-string sentinel (`\x1b[49m`) so the bar
2772
2958
  // inherits the terminal's default background, matching custom themes that
@@ -2837,17 +3023,18 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
2837
3023
  const leftWidths = left.map(part => visibleWidth(part));
2838
3024
  const rightWidths = right.map(part => visibleWidth(part));
2839
3025
 
2840
- const leftSepWidth = visibleWidth(separatorDef.left);
2841
- const rightSepWidth = visibleWidth(separatorDef.right);
3026
+ const leftSepWidth = separators.leftSepWidth;
3027
+ const rightSepWidth = separators.rightSepWidth;
2842
3028
  // Transparent mode drops powerline caps (they need a bg fill to bridge),
2843
3029
  // so the width budget excludes them too.
2844
- const leftCapWidth = separatorDef.endCaps && !transparentBg ? visibleWidth(separatorDef.endCaps.right) : 0;
2845
- const rightCapWidth = separatorDef.endCaps && !transparentBg ? visibleWidth(separatorDef.endCaps.left) : 0;
3030
+ const capsVisible = separatorDef.endCaps !== undefined && !transparentBg;
3031
+ const leftCapWidth = capsVisible ? separators.leftCapWidth : 0;
3032
+ const rightCapWidth = capsVisible ? separators.rightCapWidth : 0;
2846
3033
  // The band layout opens flush against the terminal edge with a soft cap
2847
3034
  // (rust zeta's status band). Like the other caps it needs an opaque
2848
3035
  // background to bridge, and only powerline separator styles carry caps.
2849
- const bandCap = layout === "band" && separatorDef.endCaps && !transparentBg ? theme.sep.powerlineCapLeft : "";
2850
- const bandCapWidth = visibleWidth(bandCap);
3036
+ const bandCap = layout === "band" && capsVisible ? theme.sep.powerlineCapLeft : "";
3037
+ const bandCapWidth = bandCap ? separators.bandCapWidth : 0;
2851
3038
 
2852
3039
  const groupWidth = (widths: number[], capWidth: number, sepWidth: number): number => {
2853
3040
  if (widths.length === 0) return 0;
@@ -3114,29 +3301,51 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
3114
3301
  }
3115
3302
  const thresholdColor = getSessionAccentAnsi(dimmedAccent.hex) ?? usedColor;
3116
3303
 
3304
+ // Emit runs, not cells: each stretch of plain rule between markers and
3305
+ // labels is one `repeat`, and color escapes change only at run edges.
3306
+ // Precedence per cell: percent label > threshold > speculation > window label.
3307
+ const percentEnd = percentStart >= 0 ? percentStart + percentLabel.length : -1;
3308
+ const windowEnd = windowStart >= 0 ? windowStart + windowLabel.length : -1;
3117
3309
  let out = "\x1b[49m";
3118
3310
  let activeColor = "";
3119
- for (let i = 0; i < gapWidth; i++) {
3120
- let color = i < usedCount ? usedColor : unusedColor;
3121
- let glyph = horizontal;
3122
- if (percentStart >= 0 && i >= percentStart && i < percentStart + percentLabel.length) {
3311
+ let i = 0;
3312
+ while (i < gapWidth) {
3313
+ let color: string;
3314
+ let text: string;
3315
+ if (i >= percentStart && i < percentEnd) {
3316
+ const end = Math.min(percentEnd, gapWidth);
3123
3317
  color = percentOverflow ? overflowColor : usedColor;
3124
- glyph = percentLabel.charAt(i - percentStart);
3318
+ text = percentLabel.slice(i - percentStart, end - percentStart);
3319
+ i = end;
3125
3320
  } else if (i === thresholdIdx) {
3126
3321
  color = thresholdColor;
3127
- glyph = thresholdGlyph;
3322
+ text = thresholdGlyph;
3323
+ i++;
3128
3324
  } else if (i === speculationIdx) {
3129
3325
  color = speculationColor;
3130
- glyph = speculationGlyph;
3131
- } else if (windowStart >= 0 && i >= windowStart && i < windowStart + windowLabel.length) {
3132
- color = thresholdColor;
3133
- glyph = windowLabel.charAt(i - windowStart);
3326
+ text = speculationGlyph;
3327
+ i++;
3328
+ } else {
3329
+ const inWindow = i >= windowStart && i < windowEnd;
3330
+ let end = Math.min(inWindow ? windowEnd : i < usedCount ? usedCount : gapWidth, gapWidth);
3331
+ if (percentStart > i && percentStart < end) end = percentStart;
3332
+ if (thresholdIdx > i && thresholdIdx < end) end = thresholdIdx;
3333
+ if (speculationIdx > i && speculationIdx < end) end = speculationIdx;
3334
+ if (!inWindow && windowStart > i && windowStart < end) end = windowStart;
3335
+ if (inWindow) {
3336
+ color = thresholdColor;
3337
+ text = windowLabel.slice(i - windowStart, end - windowStart);
3338
+ } else {
3339
+ color = i < usedCount ? usedColor : unusedColor;
3340
+ text = horizontal.repeat(end - i);
3341
+ }
3342
+ i = end;
3134
3343
  }
3135
3344
  if (color !== activeColor) {
3136
3345
  out += color;
3137
3346
  activeColor = color;
3138
3347
  }
3139
- out += glyph;
3348
+ out += text;
3140
3349
  }
3141
3350
  return `${out}\x1b[39m`;
3142
3351
  }
@@ -3323,6 +3532,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
3323
3532
  const probe = this.#nativeInputsProbe;
3324
3533
  this.#readStatusLineExternalInputs(probe);
3325
3534
  const nowMs = Date.now();
3535
+ this.#expireStaleVcsRender(nowMs);
3326
3536
  const clockTick = this.#nativeClock(nowMs, effectiveSettings);
3327
3537
  const memo = this.#nativeMemo;
3328
3538
  if (
@@ -3380,13 +3590,14 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
3380
3590
  #buildComposerFacts(effectiveSettings: EffectiveStatusLineSettings, nowMs: number): ComposerFacts {
3381
3591
  const { leftSegments, rightSegments } = effectiveSettings;
3382
3592
  const segments = [...leftSegments, ...rightSegments];
3383
- // Tern's pane header shows the path and branch from the pane's cwd; only the PR is looked up here.
3593
+ // Tern's pane header shows the path from the pane's cwd; the branch sits by the model chip.
3594
+ const gitEnabled = this.#gitEnabled();
3384
3595
  const ctx = this.#buildSegmentContext(
3385
3596
  0,
3386
3597
  effectiveSettings.segmentOptions,
3387
3598
  false,
3388
- false,
3389
- this.#gitEnabled() && hasPrSegment(segments),
3599
+ gitEnabled && hasGitSegment(segments),
3600
+ gitEnabled && hasPrSegment(segments),
3390
3601
  nowMs,
3391
3602
  );
3392
3603
  const pr = ctx.git.pr ?? undefined;
@@ -3404,8 +3615,12 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
3404
3615
  if (!view) return;
3405
3616
  const props: TspProps<"seg"> = {
3406
3617
  role: "zeta.composer.fact",
3407
- priority: statusSegmentPriority(side, index, ids.length),
3618
+ priority:
3619
+ (PINNED_NATIVE_SEGMENTS[id] === true ? PINNED_NATIVE_PRIORITY : 0) +
3620
+ statusSegmentPriority(side, index, ids.length),
3408
3621
  };
3622
+ const action = NATIVE_SEGMENT_ACTIONS[id];
3623
+ if (action) props.actions = { click: action };
3409
3624
  facts.push(describeSeg(id, props, view, dim));
3410
3625
  });
3411
3626
  };
@@ -3509,7 +3724,8 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
3509
3724
  /**
3510
3725
  * Wall-clock granularity of the native bar. Spinners, turn timers and the
3511
3726
  * brand fade are terminal-clocked, so only wall-clock text (time, loop
3512
- * countdowns) and VCS fallback polling advance it.
3727
+ * countdowns) advances it; VCS refreshes are deadline-driven
3728
+ * ({@link #expireStaleVcsRender}).
3513
3729
  */
3514
3730
  #nativeClock(nowMs: number, effectiveSettings: EffectiveStatusLineSettings): number {
3515
3731
  const segments = [...effectiveSettings.leftSegments, ...effectiveSettings.rightSegments];
@@ -3521,8 +3737,6 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
3521
3737
  return Math.floor(nowMs / 1_000);
3522
3738
  }
3523
3739
  if (includesTime) return Math.floor(nowMs / 60_000);
3524
- if (this.#gitEnabled() && hasGitBackedSegment(segments)) return Math.floor(nowMs / 1_000);
3525
- if (this.#gitEnabled() && hasPathSegment(segments)) return Math.floor(nowMs / WATCHER_FAILURE_POLL_TTL_MS);
3526
3740
  return 0;
3527
3741
  }
3528
3742
 
@@ -3614,7 +3828,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
3614
3828
  }
3615
3829
  const showHooks = this.#settings.showHookStatus ?? true;
3616
3830
  if (showHooks && this.#sortedHookStatuses.length > 0) {
3617
- lines.push(...this.#sortedHookStatuses.map(text => truncateToWidth(sanitizeStatusText(text), width)));
3831
+ lines.push(...this.#sortedHookStatuses.map(text => truncateToWidth(sanitizeHookStatusText(text), width)));
3618
3832
  }
3619
3833
  return lines;
3620
3834
  }