@bubblebrain-ai/bubble 0.0.52 → 0.0.54

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 (483) hide show
  1. package/README.md +4 -12
  2. package/dist/agent/routing-catalog.js +3 -2
  3. package/dist/agent/subagent/router.js +5 -0
  4. package/dist/agent/subagent/runtime.d.ts +1 -0
  5. package/dist/agent/subagent/runtime.js +13 -1
  6. package/dist/agent/subagent-control.d.ts +4 -0
  7. package/dist/agent/subagent-control.js +1 -0
  8. package/dist/agent/subagent-store.js +2 -0
  9. package/dist/agent/workflow/control.d.ts +2 -0
  10. package/dist/agent/workflow/runs.d.ts +1 -0
  11. package/dist/agent/workflow/runs.js +3 -0
  12. package/dist/agent.d.ts +11 -1
  13. package/dist/agent.js +158 -57
  14. package/dist/bin.js +2 -44
  15. package/dist/config.d.ts +14 -1
  16. package/dist/config.js +45 -4
  17. package/dist/context/budget.d.ts +7 -1
  18. package/dist/context/budget.js +25 -2
  19. package/dist/context/compact-llm.d.ts +1 -1
  20. package/dist/context/compact-llm.js +5 -2
  21. package/dist/context/overflow.js +2 -0
  22. package/dist/context/projector.d.ts +8 -0
  23. package/dist/context/projector.js +55 -6
  24. package/dist/context/tool-output-truncate.d.ts +17 -1
  25. package/dist/context/tool-output-truncate.js +171 -49
  26. package/dist/feishu/agent-host/run-driver.js +3 -0
  27. package/dist/feishu/serve.js +2 -0
  28. package/dist/goal/command.d.ts +1 -1
  29. package/dist/goal/command.js +7 -2
  30. package/dist/main.d.ts +1 -1
  31. package/dist/main.js +38 -8
  32. package/dist/memory/db.js +0 -7
  33. package/dist/model-catalog.js +25 -6
  34. package/dist/model-config.js +3 -2
  35. package/dist/model-pricing.d.ts +24 -5
  36. package/dist/model-pricing.js +0 -0
  37. package/dist/model-selection.js +13 -4
  38. package/dist/network/chatgpt-transport.js +0 -34
  39. package/dist/network/errors.d.ts +19 -0
  40. package/dist/network/errors.js +24 -0
  41. package/dist/network/provider-transport.d.ts +0 -4
  42. package/dist/network/provider-transport.js +3 -35
  43. package/dist/network/retry.d.ts +6 -1
  44. package/dist/network/retry.js +9 -0
  45. package/dist/provider-model-policy.d.ts +16 -0
  46. package/dist/provider-model-policy.js +33 -0
  47. package/dist/provider-registry.d.ts +1 -1
  48. package/dist/provider-registry.js +73 -10
  49. package/dist/provider-transform.js +32 -4
  50. package/dist/provider.d.ts +6 -2
  51. package/dist/provider.js +95 -6
  52. package/dist/question/controller.js +5 -4
  53. package/dist/question/normalize.d.ts +6 -0
  54. package/dist/question/normalize.js +11 -0
  55. package/dist/rewind.d.ts +20 -0
  56. package/dist/rewind.js +29 -0
  57. package/dist/sdk/index.d.ts +1 -1
  58. package/dist/sdk/index.js +11 -3
  59. package/dist/session.d.ts +7 -0
  60. package/dist/session.js +20 -0
  61. package/dist/skills/discovery.js +18 -0
  62. package/dist/skills/registry.d.ts +12 -2
  63. package/dist/skills/registry.js +40 -5
  64. package/dist/skills/types.d.ts +2 -0
  65. package/dist/slash-commands/commands.js +120 -81
  66. package/dist/slash-commands/registry.d.ts +2 -1
  67. package/dist/slash-commands/registry.js +3 -0
  68. package/dist/slash-commands/types.d.ts +22 -4
  69. package/dist/stats/usage.d.ts +3 -1
  70. package/dist/stats/usage.js +22 -13
  71. package/dist/tasks/manager.d.ts +4 -2
  72. package/dist/tasks/manager.js +34 -9
  73. package/dist/tasks/promotion.d.ts +2 -2
  74. package/dist/tasks/promotion.js +2 -2
  75. package/dist/tools/agent-lifecycle.js +4 -0
  76. package/dist/tools/bash.d.ts +3 -1
  77. package/dist/tools/bash.js +13 -2
  78. package/dist/tools/grep.d.ts +5 -1
  79. package/dist/tools/grep.js +318 -69
  80. package/dist/tools/index.d.ts +3 -1
  81. package/dist/tools/index.js +1 -0
  82. package/dist/tools/question.js +5 -4
  83. package/dist/tools/read.js +40 -21
  84. package/dist/tui/animation-clock.d.ts +14 -0
  85. package/dist/tui/animation-clock.js +34 -0
  86. package/dist/tui/app.d.ts +176 -0
  87. package/dist/tui/app.js +1647 -0
  88. package/dist/tui/components/approval-dialog.d.ts +43 -0
  89. package/dist/tui/components/approval-dialog.js +247 -0
  90. package/dist/tui/components/bottom-sheet.d.ts +9 -0
  91. package/dist/tui/components/bottom-sheet.js +32 -0
  92. package/dist/tui/components/context-info.d.ts +54 -0
  93. package/dist/tui/components/context-info.js +388 -0
  94. package/dist/tui/components/feedback-dialog.d.ts +56 -0
  95. package/dist/tui/components/feedback-dialog.js +262 -0
  96. package/dist/tui/components/feishu-setup-dialog.d.ts +65 -0
  97. package/dist/tui/components/feishu-setup-dialog.js +332 -0
  98. package/dist/tui/components/image-preview.d.ts +32 -0
  99. package/dist/tui/components/image-preview.js +130 -0
  100. package/dist/tui/components/plan-dialog.d.ts +34 -0
  101. package/dist/tui/components/plan-dialog.js +196 -0
  102. package/dist/tui/components/provider-key-input.d.ts +18 -0
  103. package/dist/tui/components/provider-key-input.js +49 -0
  104. package/dist/tui/components/question-dialog.d.ts +25 -0
  105. package/dist/tui/components/question-dialog.js +263 -0
  106. package/dist/tui/components/responsive-transcript.d.ts +31 -0
  107. package/dist/tui/components/responsive-transcript.js +77 -0
  108. package/dist/tui/components/rewind-picker.d.ts +62 -0
  109. package/dist/tui/components/rewind-picker.js +317 -0
  110. package/dist/tui/components/session-picker.d.ts +49 -0
  111. package/dist/tui/components/session-picker.js +303 -0
  112. package/dist/tui/components/skills-panel.d.ts +59 -0
  113. package/dist/tui/components/skills-panel.js +568 -0
  114. package/dist/tui/components/stats-panel.d.ts +40 -0
  115. package/dist/tui/components/stats-panel.js +301 -0
  116. package/dist/tui/components/streaming-message.d.ts +69 -0
  117. package/dist/tui/components/streaming-message.js +393 -0
  118. package/dist/tui/components/subagent-inspector.d.ts +28 -0
  119. package/dist/tui/components/subagent-inspector.js +127 -0
  120. package/dist/tui/components/task-inspector.d.ts +26 -0
  121. package/dist/tui/components/task-inspector.js +70 -0
  122. package/dist/tui/components/tasks-pane.d.ts +88 -0
  123. package/dist/tui/components/tasks-pane.js +416 -0
  124. package/dist/tui/components/transcript.d.ts +96 -0
  125. package/dist/tui/components/transcript.js +771 -0
  126. package/dist/tui/components/welcome.d.ts +20 -0
  127. package/dist/tui/components/welcome.js +133 -0
  128. package/dist/tui/components/workflow-inspector.d.ts +30 -0
  129. package/dist/tui/components/workflow-inspector.js +136 -0
  130. package/dist/tui/composer-autocomplete.d.ts +61 -0
  131. package/dist/tui/composer-autocomplete.js +383 -0
  132. package/dist/tui/composer-style.d.ts +7 -0
  133. package/dist/tui/composer-style.js +29 -0
  134. package/dist/tui/controller/agent-event-reducer.d.ts +75 -0
  135. package/dist/tui/controller/agent-event-reducer.js +238 -0
  136. package/dist/tui/controller/composer-controller.d.ts +95 -0
  137. package/dist/tui/controller/composer-controller.js +502 -0
  138. package/dist/tui/controller/controller.d.ts +194 -0
  139. package/dist/tui/controller/controller.js +1035 -0
  140. package/dist/tui/controller/effects.d.ts +71 -0
  141. package/dist/tui/controller/effects.js +1 -0
  142. package/dist/tui/controller/goal-runtime-controller.d.ts +47 -0
  143. package/dist/tui/controller/goal-runtime-controller.js +202 -0
  144. package/dist/tui/controller/input-queue-machine.d.ts +71 -0
  145. package/dist/tui/controller/input-queue-machine.js +121 -0
  146. package/dist/tui/controller/intents.d.ts +56 -0
  147. package/dist/tui/controller/intents.js +6 -0
  148. package/dist/tui/controller/overlay-controller.d.ts +64 -0
  149. package/dist/tui/controller/overlay-controller.js +129 -0
  150. package/dist/tui/controller/ports.d.ts +57 -0
  151. package/dist/tui/controller/ports.js +9 -0
  152. package/dist/tui/controller/session-transition.d.ts +54 -0
  153. package/dist/tui/controller/session-transition.js +68 -0
  154. package/dist/tui/controller/state.d.ts +14 -0
  155. package/dist/tui/controller/state.js +36 -0
  156. package/dist/tui/controller/task-runtime-controller.d.ts +66 -0
  157. package/dist/tui/controller/task-runtime-controller.js +158 -0
  158. package/dist/tui/footer.d.ts +37 -0
  159. package/dist/tui/footer.js +80 -0
  160. package/dist/{tui-ink → tui/formatting}/edit-diff.d.ts +1 -1
  161. package/dist/{tui-ink → tui/formatting}/edit-diff.js +1 -1
  162. package/dist/{tui-ink/app-helpers.d.ts → tui/formatting/summary.d.ts} +4 -4
  163. package/dist/{tui-ink/app-helpers.js → tui/formatting/summary.js} +2 -2
  164. package/dist/tui/fullscreen.d.ts +48 -0
  165. package/dist/tui/fullscreen.js +255 -0
  166. package/dist/tui/image-display.d.ts +4 -0
  167. package/dist/tui/image-display.js +30 -12
  168. package/dist/tui/markdown-style.d.ts +5 -0
  169. package/dist/tui/markdown-style.js +23 -0
  170. package/dist/{tui-ink → tui/model}/composer-buffer.js +1 -1
  171. package/dist/tui/model/composer-types.d.ts +17 -0
  172. package/dist/tui/model/composer-types.js +1 -0
  173. package/dist/{tui-ink → tui/model}/display-history.d.ts +7 -1
  174. package/dist/{tui-ink → tui/model}/display-reconstruct.d.ts +1 -1
  175. package/dist/{tui-ink → tui/model}/display-reconstruct.js +54 -6
  176. package/dist/{tui-ink → tui/model}/file-mentions.d.ts +2 -0
  177. package/dist/{tui-ink → tui/model}/file-mentions.js +4 -0
  178. package/dist/tui/model/image-attachment.d.ts +15 -0
  179. package/dist/tui/model/image-attachment.js +1 -0
  180. package/dist/{tui-ink → tui/model}/image-paste.d.ts +7 -1
  181. package/dist/{tui-ink → tui/model}/image-paste.js +46 -0
  182. package/dist/tui/model/input-history.d.ts +1 -0
  183. package/dist/tui/model/input-history.js +1 -0
  184. package/dist/{tui-ink → tui/model}/input-queue.d.ts +1 -1
  185. package/dist/{tui-ink → tui/model}/recent-activity.js +1 -1
  186. package/dist/{tui-ink → tui/model}/subagent-view.d.ts +17 -2
  187. package/dist/{tui-ink → tui/model}/subagent-view.js +125 -4
  188. package/dist/{tui-ink → tui/model}/submit-dedupe.d.ts +1 -1
  189. package/dist/tui/model/theme-style.d.ts +10 -0
  190. package/dist/tui/model/theme-style.js +79 -0
  191. package/dist/{tui-ink → tui/model}/theme.d.ts +11 -23
  192. package/dist/{tui-ink → tui/model}/theme.js +23 -38
  193. package/dist/{tui-ink → tui/model}/trace-groups.d.ts +2 -0
  194. package/dist/{tui-ink → tui/model}/trace-groups.js +91 -30
  195. package/dist/tui/model/trace-interaction.d.ts +63 -0
  196. package/dist/tui/model/trace-interaction.js +87 -0
  197. package/dist/tui/model-picker-data.d.ts +1 -2
  198. package/dist/tui/model-picker-data.js +8 -1
  199. package/dist/tui/model-switch.js +2 -0
  200. package/dist/{tui-ink → tui}/run-session-picker.d.ts +2 -3
  201. package/dist/tui/run-session-picker.js +64 -0
  202. package/dist/tui/run.d.ts +72 -0
  203. package/dist/tui/run.js +139 -0
  204. package/dist/tui/terminal/multiplexer.d.ts +5 -0
  205. package/dist/tui/terminal/multiplexer.js +14 -0
  206. package/dist/tui/testing/fake-agent.d.ts +24 -0
  207. package/dist/tui/testing/fake-agent.js +22 -0
  208. package/dist/tui/testing/spy-host.d.ts +21 -0
  209. package/dist/tui/testing/spy-host.js +55 -0
  210. package/dist/tui/trace-groups.js +27 -6
  211. package/dist/types.d.ts +7 -0
  212. package/dist/update/index.d.ts +1 -0
  213. package/dist/update/index.js +22 -17
  214. package/node_modules/@bubblebrain-ai/pi-tui/LICENSE +21 -0
  215. package/node_modules/@bubblebrain-ai/pi-tui/README.md +854 -0
  216. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/alt-screen-search.d.ts +26 -0
  217. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/alt-screen-search.d.ts.map +1 -0
  218. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/alt-screen-search.js +121 -0
  219. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/alt-screen-search.js.map +1 -0
  220. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/autocomplete.d.ts +86 -0
  221. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/autocomplete.d.ts.map +1 -0
  222. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/autocomplete.js +649 -0
  223. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/autocomplete.js.map +1 -0
  224. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/alt-screen-flash.d.ts +13 -0
  225. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/alt-screen-flash.d.ts.map +1 -0
  226. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/alt-screen-flash.js +36 -0
  227. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/alt-screen-flash.js.map +1 -0
  228. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/box.d.ts +22 -0
  229. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/box.d.ts.map +1 -0
  230. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/box.js +99 -0
  231. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/box.js.map +1 -0
  232. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/cancellable-loader.d.ts +22 -0
  233. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/cancellable-loader.d.ts.map +1 -0
  234. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/cancellable-loader.js +36 -0
  235. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/cancellable-loader.js.map +1 -0
  236. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/editor.d.ts +330 -0
  237. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/editor.d.ts.map +1 -0
  238. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/editor.js +2344 -0
  239. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/editor.js.map +1 -0
  240. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/h-stack.d.ts +7 -0
  241. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/h-stack.d.ts.map +1 -0
  242. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/h-stack.js +43 -0
  243. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/h-stack.js.map +1 -0
  244. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/image.d.ts +28 -0
  245. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/image.d.ts.map +1 -0
  246. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/image.js +82 -0
  247. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/image.js.map +1 -0
  248. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/input.d.ts +48 -0
  249. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/input.d.ts.map +1 -0
  250. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/input.js +390 -0
  251. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/input.js.map +1 -0
  252. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/loader.d.ts +31 -0
  253. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/loader.d.ts.map +1 -0
  254. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/loader.js +67 -0
  255. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/loader.js.map +1 -0
  256. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/markdown.d.ts +103 -0
  257. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/markdown.d.ts.map +1 -0
  258. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/markdown.js +797 -0
  259. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/markdown.js.map +1 -0
  260. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/scroll-view.d.ts +56 -0
  261. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/scroll-view.d.ts.map +1 -0
  262. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/scroll-view.js +179 -0
  263. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/scroll-view.js.map +1 -0
  264. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/select-list.d.ts +53 -0
  265. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/select-list.d.ts.map +1 -0
  266. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/select-list.js +175 -0
  267. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/select-list.js.map +1 -0
  268. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/settings-list.d.ts +50 -0
  269. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/settings-list.d.ts.map +1 -0
  270. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/settings-list.js +174 -0
  271. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/settings-list.js.map +1 -0
  272. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/spacer.d.ts +12 -0
  273. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/spacer.d.ts.map +1 -0
  274. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/spacer.js +22 -0
  275. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/spacer.js.map +1 -0
  276. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/stack.d.ts +32 -0
  277. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/stack.d.ts.map +1 -0
  278. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/stack.js +109 -0
  279. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/stack.js.map +1 -0
  280. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/text.d.ts +19 -0
  281. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/text.d.ts.map +1 -0
  282. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/text.js +81 -0
  283. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/text.js.map +1 -0
  284. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/truncated-text.d.ts +13 -0
  285. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/truncated-text.d.ts.map +1 -0
  286. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/truncated-text.js +48 -0
  287. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/truncated-text.js.map +1 -0
  288. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/v-stack.d.ts +8 -0
  289. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/v-stack.d.ts.map +1 -0
  290. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/v-stack.js +26 -0
  291. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/v-stack.js.map +1 -0
  292. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/editor-component.d.ts +39 -0
  293. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/editor-component.d.ts.map +1 -0
  294. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/editor-component.js +2 -0
  295. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/editor-component.js.map +1 -0
  296. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/fuzzy.d.ts +16 -0
  297. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/fuzzy.d.ts.map +1 -0
  298. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/fuzzy.js +110 -0
  299. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/fuzzy.js.map +1 -0
  300. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/index.d.ts +31 -0
  301. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/index.d.ts.map +1 -0
  302. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/index.js +42 -0
  303. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/index.js.map +1 -0
  304. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keybindings.d.ts +273 -0
  305. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keybindings.d.ts.map +1 -0
  306. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keybindings.js +231 -0
  307. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keybindings.js.map +1 -0
  308. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keys.d.ts +184 -0
  309. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keys.d.ts.map +1 -0
  310. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keys.js +1174 -0
  311. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keys.js.map +1 -0
  312. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/kill-ring.d.ts +28 -0
  313. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/kill-ring.d.ts.map +1 -0
  314. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/kill-ring.js +46 -0
  315. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/kill-ring.js.map +1 -0
  316. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/latex.d.ts +10 -0
  317. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/latex.d.ts.map +1 -0
  318. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/latex.js +1268 -0
  319. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/latex.js.map +1 -0
  320. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout-node.d.ts +40 -0
  321. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout-node.d.ts.map +1 -0
  322. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout-node.js +6 -0
  323. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout-node.js.map +1 -0
  324. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout.d.ts +42 -0
  325. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout.d.ts.map +1 -0
  326. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout.js +335 -0
  327. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout.js.map +1 -0
  328. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/native-modifiers.d.ts +3 -0
  329. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/native-modifiers.d.ts.map +1 -0
  330. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/native-modifiers.js +60 -0
  331. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/native-modifiers.js.map +1 -0
  332. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/stdin-buffer.d.ts +56 -0
  333. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/stdin-buffer.d.ts.map +1 -0
  334. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/stdin-buffer.js +363 -0
  335. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/stdin-buffer.js.map +1 -0
  336. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-colors.d.ts +10 -0
  337. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-colors.d.ts.map +1 -0
  338. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-colors.js +59 -0
  339. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-colors.js.map +1 -0
  340. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-image.d.ts +116 -0
  341. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-image.d.ts.map +1 -0
  342. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-image.js +518 -0
  343. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-image.js.map +1 -0
  344. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal.d.ts +142 -0
  345. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal.d.ts.map +1 -0
  346. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal.js +544 -0
  347. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal.js.map +1 -0
  348. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/testing.d.ts +11 -0
  349. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/testing.d.ts.map +1 -0
  350. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/testing.js +11 -0
  351. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/testing.js.map +1 -0
  352. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.d.ts +140 -0
  353. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.d.ts.map +1 -0
  354. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.js +1334 -0
  355. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.js.map +1 -0
  356. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-main-screen.d.ts +39 -0
  357. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-main-screen.d.ts.map +1 -0
  358. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-main-screen.js +536 -0
  359. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-main-screen.js.map +1 -0
  360. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.d.ts +338 -0
  361. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.d.ts.map +1 -0
  362. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.js +1016 -0
  363. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.js.map +1 -0
  364. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/undo-stack.d.ts +17 -0
  365. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/undo-stack.d.ts.map +1 -0
  366. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/undo-stack.js +27 -0
  367. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/undo-stack.js.map +1 -0
  368. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/utils.d.ts +96 -0
  369. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/utils.d.ts.map +1 -0
  370. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/utils.js +1193 -0
  371. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/utils.js.map +1 -0
  372. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/word-navigation.d.ts +25 -0
  373. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/word-navigation.d.ts.map +1 -0
  374. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/word-navigation.js +96 -0
  375. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/word-navigation.js.map +1 -0
  376. package/node_modules/@bubblebrain-ai/pi-tui/dist/test/virtual-terminal.d.ts +73 -0
  377. package/node_modules/@bubblebrain-ai/pi-tui/dist/test/virtual-terminal.d.ts.map +1 -0
  378. package/node_modules/@bubblebrain-ai/pi-tui/dist/test/virtual-terminal.js +188 -0
  379. package/node_modules/@bubblebrain-ai/pi-tui/dist/test/virtual-terminal.js.map +1 -0
  380. package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/README.md +20 -0
  381. package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/build.sh +62 -0
  382. package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-modifiers.node +0 -0
  383. package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
  384. package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/src/darwin-modifiers.c +70 -0
  385. package/node_modules/@bubblebrain-ai/pi-tui/native/win32/README.md +22 -0
  386. package/node_modules/@bubblebrain-ai/pi-tui/native/win32/build.mjs +229 -0
  387. package/node_modules/@bubblebrain-ai/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  388. package/node_modules/@bubblebrain-ai/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  389. package/node_modules/@bubblebrain-ai/pi-tui/native/win32/src/win32-console-mode.c +119 -0
  390. package/node_modules/@bubblebrain-ai/pi-tui/package.json +66 -0
  391. package/node_modules/get-east-asian-width/index.d.ts +60 -0
  392. package/node_modules/get-east-asian-width/index.js +30 -0
  393. package/node_modules/get-east-asian-width/license +9 -0
  394. package/node_modules/get-east-asian-width/lookup-data.js +21 -0
  395. package/node_modules/get-east-asian-width/lookup.js +138 -0
  396. package/node_modules/get-east-asian-width/package.json +71 -0
  397. package/node_modules/get-east-asian-width/readme.md +65 -0
  398. package/node_modules/get-east-asian-width/utilities.js +24 -0
  399. package/node_modules/marked/LICENSE +44 -0
  400. package/node_modules/marked/README.md +115 -0
  401. package/node_modules/marked/bin/main.js +284 -0
  402. package/node_modules/marked/bin/marked.js +16 -0
  403. package/node_modules/marked/lib/marked.d.ts +760 -0
  404. package/node_modules/marked/lib/marked.esm.js +77 -0
  405. package/node_modules/marked/lib/marked.esm.js.map +7 -0
  406. package/node_modules/marked/lib/marked.umd.js +79 -0
  407. package/node_modules/marked/lib/marked.umd.js.map +7 -0
  408. package/node_modules/marked/man/marked.1 +113 -0
  409. package/node_modules/marked/man/marked.1.md +93 -0
  410. package/node_modules/marked/package.json +103 -0
  411. package/package.json +25 -10
  412. package/dist/tui-ink/app.d.ts +0 -76
  413. package/dist/tui-ink/app.js +0 -2377
  414. package/dist/tui-ink/approval/approval-dialog.d.ts +0 -13
  415. package/dist/tui-ink/approval/approval-dialog.js +0 -184
  416. package/dist/tui-ink/approval/diff-view.d.ts +0 -7
  417. package/dist/tui-ink/approval/diff-view.js +0 -44
  418. package/dist/tui-ink/approval/select.d.ts +0 -35
  419. package/dist/tui-ink/approval/select.js +0 -98
  420. package/dist/tui-ink/code-highlight.d.ts +0 -8
  421. package/dist/tui-ink/code-highlight.js +0 -121
  422. package/dist/tui-ink/command-palette.d.ts +0 -32
  423. package/dist/tui-ink/command-palette.js +0 -158
  424. package/dist/tui-ink/compaction-progress.d.ts +0 -19
  425. package/dist/tui-ink/compaction-progress.js +0 -74
  426. package/dist/tui-ink/detect-theme.d.ts +0 -1
  427. package/dist/tui-ink/detect-theme.js +0 -1
  428. package/dist/tui-ink/feedback-dialog.d.ts +0 -19
  429. package/dist/tui-ink/feedback-dialog.js +0 -133
  430. package/dist/tui-ink/feishu-setup-picker.d.ts +0 -5
  431. package/dist/tui-ink/feishu-setup-picker.js +0 -271
  432. package/dist/tui-ink/footer.d.ts +0 -26
  433. package/dist/tui-ink/footer.js +0 -51
  434. package/dist/tui-ink/input-box.d.ts +0 -189
  435. package/dist/tui-ink/input-box.js +0 -1279
  436. package/dist/tui-ink/input-history.d.ts +0 -1
  437. package/dist/tui-ink/input-history.js +0 -1
  438. package/dist/tui-ink/markdown.d.ts +0 -103
  439. package/dist/tui-ink/markdown.js +0 -747
  440. package/dist/tui-ink/message-list.d.ts +0 -49
  441. package/dist/tui-ink/message-list.js +0 -841
  442. package/dist/tui-ink/model-picker.d.ts +0 -143
  443. package/dist/tui-ink/model-picker.js +0 -966
  444. package/dist/tui-ink/plan-confirm.d.ts +0 -7
  445. package/dist/tui-ink/plan-confirm.js +0 -115
  446. package/dist/tui-ink/question-dialog.d.ts +0 -8
  447. package/dist/tui-ink/question-dialog.js +0 -135
  448. package/dist/tui-ink/rewind-picker.d.ts +0 -8
  449. package/dist/tui-ink/rewind-picker.js +0 -102
  450. package/dist/tui-ink/run-session-picker.js +0 -22
  451. package/dist/tui-ink/run.d.ts +0 -62
  452. package/dist/tui-ink/run.js +0 -103
  453. package/dist/tui-ink/session-picker.d.ts +0 -10
  454. package/dist/tui-ink/session-picker.js +0 -113
  455. package/dist/tui-ink/stats-panel.d.ts +0 -11
  456. package/dist/tui-ink/stats-panel.js +0 -65
  457. package/dist/tui-ink/subagent-inspector.d.ts +0 -21
  458. package/dist/tui-ink/subagent-inspector.js +0 -205
  459. package/dist/tui-ink/use-terminal-size.d.ts +0 -4
  460. package/dist/tui-ink/use-terminal-size.js +0 -48
  461. package/dist/tui-ink/waiting-indicator.d.ts +0 -12
  462. package/dist/tui-ink/waiting-indicator.js +0 -129
  463. package/dist/tui-ink/welcome.d.ts +0 -25
  464. package/dist/tui-ink/welcome.js +0 -88
  465. package/dist/tui-ink/wordmark.d.ts +0 -8
  466. package/dist/tui-ink/wordmark.js +0 -103
  467. /package/dist/{tui-ink → tui/model}/composer-buffer.d.ts +0 -0
  468. /package/dist/{tui-ink → tui/model}/display-history.js +0 -0
  469. /package/dist/{tui-ink → tui/model}/input-queue.js +0 -0
  470. /package/dist/{tui-ink → tui/model}/key-events.d.ts +0 -0
  471. /package/dist/{tui-ink → tui/model}/key-events.js +0 -0
  472. /package/dist/{tui-ink → tui/model}/paste-operation-tracker.d.ts +0 -0
  473. /package/dist/{tui-ink → tui/model}/paste-operation-tracker.js +0 -0
  474. /package/dist/{tui-ink → tui/model}/recent-activity.d.ts +0 -0
  475. /package/dist/{tui-ink → tui/model}/submit-dedupe.js +0 -0
  476. /package/dist/{tui-ink → tui/model}/sync-output.d.ts +0 -0
  477. /package/dist/{tui-ink → tui/model}/sync-output.js +0 -0
  478. /package/dist/{tui-ink → tui/model}/terminal-env.d.ts +0 -0
  479. /package/dist/{tui-ink → tui/model}/terminal-env.js +0 -0
  480. /package/dist/{tui-ink → tui/model}/terminal-mouse.d.ts +0 -0
  481. /package/dist/{tui-ink → tui/model}/terminal-mouse.js +0 -0
  482. /package/dist/{tui-ink → tui/model}/width.d.ts +0 -0
  483. /package/dist/{tui-ink → tui/model}/width.js +0 -0
@@ -1,841 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import React from "react";
3
- import { Box, Static, Text, measureElement } from "ink";
4
- import { useTheme } from "./theme.js";
5
- import { highlightCode, inferLang } from "./code-highlight.js";
6
- import { MarkdownContent, StreamingMarkdown } from "./markdown.js";
7
- import { visualWidth, graphemeWidth, ambiguousIsWide } from "./width.js";
8
- import { userInputStatusBadgeLabel, } from "./display-history.js";
9
- import { buildTraceGroups, executeCommandBlock, formatTracePath, shouldInlineExecuteCommand, traceGroupLabel, } from "./trace-groups.js";
10
- import { EDIT_COLLAPSED_DIFF_LINES, formatEditSuccessSummary, getEditDiffDetails } from "./edit-diff.js";
11
- import { latestSubagentNote, sortSubagents, subagentDescriptor, subagentLabel, subagentStatusColor, subagentSummary, } from "./subagent-view.js";
12
- import { sanitizeInternalReminderBlocks } from "../agent/internal-reminder-sanitizer.js";
13
- import { stripFileBlocks } from "../context/compaction-files.js";
14
- import { splitImageDisplayContent } from "../tui/image-display.js";
15
- const EXECUTE_COMMAND_BLOCK_MAX_LINES = 4;
16
- /**
17
- * Every transcript row — each committed <Static> item and the DynamicClamp
18
- * live region — wraps its content in paddingX={1}: one column of inset on
19
- * EACH side. Any wrap budget derived from terminalColumns inside a row must
20
- * net out these two columns. Forgetting them hands the pre-wrapper a budget
21
- * two columns wider than the real room, and a dense CJK line that packs flush
22
- * against that budget renders past the terminal edge — the terminal then
23
- * hard-wraps the last glyph onto a stray row at column 0.
24
- */
25
- const TRANSCRIPT_ROW_PADDING = 2;
26
- export function MessageList({ messages, streamingContent, streamingReasoning, streamingTools, streamingParts, terminalColumns, showThinking = false, verboseTrace, pendingApproval, nowTick, welcomeBanner, staticGeneration = 0, paddingX = 1, maxStreamRows, }) {
27
- const theme = useTheme();
28
- const hasStreaming = !!(streamingContent ||
29
- streamingReasoning ||
30
- streamingTools.length > 0 ||
31
- streamingParts.length > 0);
32
- const regularMessages = messages.filter((message) => !message.inputStatus);
33
- const pendingSteerMessages = messages.filter((message) => message.inputStatus === "pending_steer");
34
- const queuedInputMessages = messages.filter((message) => message.inputStatus === "queued");
35
- // While the transcript has no settled rows, the banner stays OUT of
36
- // <Static> and renders in the live region instead, so it repaints on every
37
- // commit — a /model switch on the fresh launch screen updates the banner in
38
- // place. It is committed to scrollback together with the first settled row
39
- // (same render commit, so the hand-off is seamless) and frozen from then on.
40
- const bannerCommitted = regularMessages.length > 0;
41
- const staticItems = [];
42
- if (welcomeBanner && bannerCommitted) {
43
- staticItems.push({ kind: "welcome", key: "welcome" });
44
- }
45
- const lastMessageIndex = regularMessages.length - 1;
46
- for (let i = 0; i < regularMessages.length; i++) {
47
- const msg = regularMessages[i];
48
- staticItems.push({
49
- kind: "message",
50
- key: msg.key ?? `message-${i}`,
51
- message: msg,
52
- showExpandHint: !hasStreaming && i === lastMessageIndex,
53
- separateFromPrevious: msg.role === "user" && regularMessages[i - 1]?.role === "user",
54
- });
55
- }
56
- const hasDynamic = hasStreaming || pendingSteerMessages.length > 0 || queuedInputMessages.length > 0;
57
- // The live region must never grow taller than the viewport: a frame taller
58
- // than the pane breaks Ink's in-place redraw under tmux (the cursor-up clear
59
- // can't reach scrolled-off rows), leaving large blank gaps + stray glyphs.
60
- // Clip it to maxStreamRows and pin to the bottom so the user sees the latest
61
- // output (tail view); the full turn lands in <Static> scrollback on commit.
62
- const clampDynamic = typeof maxStreamRows === "number" && maxStreamRows > 0;
63
- return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [_jsx(Static, { items: staticItems, children: (item) => {
64
- if (item.kind === "welcome") {
65
- return (_jsx(Box, { flexDirection: "column", paddingX: paddingX, backgroundColor: theme.background, children: welcomeBanner }, item.key));
66
- }
67
- return (_jsx(Box, { flexDirection: "column", paddingX: paddingX, backgroundColor: theme.background, children: _jsx(MessageItem, { message: item.message, terminalColumns: terminalColumns, showThinking: showThinking, verboseTrace: verboseTrace, showExpandHint: item.showExpandHint, separateFromPrevious: item.separateFromPrevious }) }, item.key));
68
- } }, `transcript-${staticGeneration}`), welcomeBanner && !bannerCommitted && (_jsx(Box, { flexDirection: "column", paddingX: paddingX, backgroundColor: theme.background, children: welcomeBanner })), hasDynamic && (_jsxs(DynamicClamp, { maxRows: clampDynamic ? maxStreamRows : undefined, paddingX: paddingX, backgroundColor: theme.background, children: [hasStreaming && (_jsx(StreamingMessage, { content: streamingContent, reasoning: streamingReasoning, tools: streamingTools, parts: streamingParts, terminalColumns: terminalColumns, showThinking: showThinking, verboseTrace: verboseTrace, pendingApproval: pendingApproval, nowTick: nowTick })), pendingSteerMessages.length > 0 && (_jsx(PendingInputMessagesBlock, { messages: pendingSteerMessages, terminalColumns: terminalColumns, title: "Messages to steer at next model call", hint: "applies before the next provider request", bulletColor: theme.warning })), queuedInputMessages.length > 0 && (_jsx(PendingInputMessagesBlock, { messages: queuedInputMessages, terminalColumns: terminalColumns, title: "Messages queued for next turn", hint: "runs after the current answer", bulletColor: theme.muted }))] }))] }));
69
- }
70
- /**
71
- * Bounds the live (in-progress turn) region to at most `maxRows` rows, pinned
72
- * to the bottom so the user always sees the latest output (tail view). A live
73
- * frame taller than the terminal pane breaks Ink's in-place redraw under tmux
74
- * (the cursor-up clear can't reach rows that scrolled off), leaving large blank
75
- * gaps and stray glyphs. We measure the natural content height and only clip
76
- * when it actually exceeds `maxRows`, so short turns keep their natural height
77
- * (no reserved-space gap). The full turn still lands in <Static> scrollback the
78
- * moment it commits, so nothing is lost — only the live preview is windowed.
79
- */
80
- function DynamicClamp({ maxRows, paddingX, backgroundColor, children, }) {
81
- const innerRef = React.useRef(null);
82
- const [offset, setOffset] = React.useState(0);
83
- const [clipHeight, setClipHeight] = React.useState(undefined);
84
- // Re-measure after every commit (streaming changes height with each token).
85
- // useLayoutEffect runs before Ink flushes the frame to the terminal, so the
86
- // clamp is applied in the same paint — avoiding a one-frame overflow flash
87
- // under tmux when a turn first grows past the pane. setState bails out via
88
- // Object.is when nothing moved, so the steady state does not loop.
89
- React.useLayoutEffect(() => {
90
- if (!maxRows || !innerRef.current) {
91
- if (offset !== 0)
92
- setOffset(0);
93
- if (clipHeight !== undefined)
94
- setClipHeight(undefined);
95
- return;
96
- }
97
- const height = measureElement(innerRef.current).height;
98
- if (height > maxRows) {
99
- const nextOffset = -(height - maxRows);
100
- if (nextOffset !== offset)
101
- setOffset(nextOffset);
102
- if (clipHeight !== maxRows)
103
- setClipHeight(maxRows);
104
- }
105
- else {
106
- if (offset !== 0)
107
- setOffset(0);
108
- if (clipHeight !== undefined)
109
- setClipHeight(undefined);
110
- }
111
- });
112
- return (_jsx(Box, { flexDirection: "column", flexShrink: 0, paddingX: paddingX, backgroundColor: backgroundColor, ...(clipHeight !== undefined ? { height: clipHeight, overflowY: "hidden" } : {}), children: _jsx(Box, { ref: innerRef, flexDirection: "column", flexShrink: 0, marginTop: offset, children: children }) }));
113
- }
114
- // Memoized: with no <Static> region, every transcript row re-renders on each
115
- // state change unless its props are referentially stable. Message objects are
116
- // append-only (compaction reuses already-compacted instances), keys are
117
- // stable, and nowTick is only threaded to the last row, so memo hits for all
118
- // settled history rows.
119
- const MessageItem = React.memo(function MessageItem({ message, terminalColumns, showThinking, verboseTrace, showExpandHint, separateFromPrevious, nowTick, }) {
120
- const theme = useTheme();
121
- if (message.role === "user") {
122
- return (_jsx(UserMessageBlock, { content: message.content, terminalColumns: terminalColumns, inputStatus: message.inputStatus, separateFromPrevious: separateFromPrevious }));
123
- }
124
- if (message.role === "error") {
125
- return (_jsx(Box, { marginBottom: 1, flexDirection: "column", children: _jsxs(Text, { color: theme.error, children: ["Error: ", message.content] }) }));
126
- }
127
- if (message.syntheticKind === "ui_compact_summary") {
128
- return _jsx(CompactionSummaryBlock, { message: message });
129
- }
130
- if (message.syntheticKind === "ui_interrupt") {
131
- return (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: theme.error, children: "\u23F9 " }), _jsx(Text, { color: theme.muted, dimColor: true, children: message.content || "Interrupted by user" })] }));
132
- }
133
- // One-line status notices (model switch etc.): accent-colored so they read
134
- // as UI feedback, not assistant prose.
135
- if (message.syntheticKind === "ui_notice") {
136
- return (_jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsxs(Text, { color: theme.accent, children: ["\u2726 ", message.content] }) }));
137
- }
138
- const visibleReasoning = sanitizeInternalReminderBlocks(message.reasoning ?? "").trim();
139
- // Same defense as reasoning: strip any internal reminder markup the model
140
- // echoed back into its visible answer so it never reaches the transcript.
141
- const visibleContent = sanitizeInternalReminderBlocks(message.content ?? "");
142
- // Decide visibility by what will ACTUALLY render below, not by raw array
143
- // lengths. A turn whose only text part is an echoed <bubble_internal_*>
144
- // reminder sanitizes to empty (TimelineText returns null), but the wrapper's
145
- // marginTop/marginBottom would still emit a blank band — and long sessions
146
- // inject more reminders, so consecutive empty turns stack into a large gap
147
- // after the tool rows. Mirror the render: parts path → MessageParts; non-parts
148
- // path → toolCalls/visibleContent; verbose adds a TurnDigest from toolCalls.
149
- const hasParts = (message.parts?.length ?? 0) > 0;
150
- const hasVisibleParts = hasParts &&
151
- (message.parts ?? []).some((part) => part.type === "tools"
152
- ? part.toolCalls.length > 0
153
- : sanitizeInternalReminderBlocks(part.content).trim() !== "");
154
- const toolCallCount = message.toolCalls?.length ?? 0;
155
- const hasVisibleAssistantContent = (hasParts ? hasVisibleParts : (!!visibleContent.trim() || toolCallCount > 0)) ||
156
- // Reasoning now always renders (collapsed by default), so its presence
157
- // alone makes a turn visible.
158
- !!visibleReasoning ||
159
- // A finalized turn carries taskElapsedMs and renders a TaskDurationLine even
160
- // when its text/parts are empty — mirror that so the duration isn't dropped.
161
- // (The verbose TurnDigest needs no term: whenever it renders, the same
162
- // toolCalls already make the turn visible via the parts/non-parts branch.)
163
- message.taskElapsedMs !== undefined;
164
- if (!hasVisibleAssistantContent)
165
- return null;
166
- return (_jsxs(Box, { marginTop: 1, marginBottom: 1, flexDirection: "column", children: [visibleReasoning && (_jsx(ReasoningTraceBlock, { reasoning: visibleReasoning, expanded: showThinking || verboseTrace })), message.parts && message.parts.length > 0 ? (_jsx(MessageParts, { parts: message.parts, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: undefined, showExpandHint: showExpandHint, nowTick: nowTick })) : (_jsxs(_Fragment, { children: [message.toolCalls && (_jsx(ToolsPart, { toolCalls: message.toolCalls, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: undefined, showExpandHint: showExpandHint, nowTick: nowTick })), visibleContent.trim() && (
167
- // Thread the real terminal width down (minus the row's paddingX
168
- // inset); without it, width-aware blocks like tables fall back to
169
- // a stale/default useTerminalSize and mis-budget their columns.
170
- _jsx(MarkdownContent, { content: visibleContent, maxWidth: Math.max(20, terminalColumns - 4) }))] })), verboseTrace && message.toolCalls && message.toolCalls.length > 0 && (_jsx(TurnDigest, { toolCalls: message.toolCalls })), message.taskElapsedMs !== undefined && (_jsx(TaskDurationLine, { elapsedMs: message.taskElapsedMs }))] }));
171
- });
172
- function StreamingMessage({ content, reasoning, tools, parts, terminalColumns, showThinking, verboseTrace, pendingApproval, nowTick, }) {
173
- const deferredContent = React.useDeferredValue(content);
174
- const deferredReasoning = React.useDeferredValue(reasoning);
175
- const deferredParts = React.useDeferredValue(parts);
176
- const visibleReasoning = sanitizeInternalReminderBlocks(deferredReasoning).trim();
177
- const visibleParts = deferredParts.length > 0
178
- ? deferredParts
179
- : fallbackStreamingParts(deferredContent, tools);
180
- return (_jsxs(Box, { flexDirection: "column", children: [visibleReasoning && (_jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(ReasoningTraceBlock, { reasoning: visibleReasoning, expanded: showThinking || verboseTrace, streaming: true }) })), visibleParts.length > 0 && (
181
- // marginTop=1 matches the committed MessageItem layout exactly, so the
182
- // gap under the user message is identical while streaming and after the
183
- // turn commits — no spacing jump at finalize time. (The old marginTop=0
184
- // was a flicker mitigation for the main-screen <Static> renderer; the
185
- // alt-screen viewport repaints frames atomically, so it's obsolete.)
186
- _jsx(Box, { marginTop: 1, marginBottom: 1, flexDirection: "column", children: _jsx(MessageParts, { parts: visibleParts, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: pendingApproval, showExpandHint: true, nowTick: nowTick, showActivity: true, streaming: true }) }))] }));
187
- }
188
- function MessageParts({ parts, terminalColumns, verboseTrace, pendingApproval, showExpandHint, nowTick, showActivity = false, streaming = false, }) {
189
- const lastToolsPartIndex = findLastToolsPartIndex(parts);
190
- const lastTextPartIndex = findLastTextPartIndex(parts);
191
- return (_jsx(Box, { flexDirection: "column", children: parts.map((part, idx) => {
192
- if (part.type === "text") {
193
- return (_jsx(TimelineText, { content: part.content, compactTop: idx === 0, terminalColumns: terminalColumns, streaming: streaming && idx === lastTextPartIndex }, `text-${idx}`));
194
- }
195
- return (_jsx(ToolsPart, { toolCalls: part.toolCalls, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: pendingApproval, showExpandHint: showExpandHint && idx === lastToolsPartIndex, compactTop: idx === 0, nowTick: nowTick, showActivity: showActivity && idx === lastToolsPartIndex }, `tools-${idx}`));
196
- }) }));
197
- }
198
- function findLastTextPartIndex(parts) {
199
- for (let i = parts.length - 1; i >= 0; i--) {
200
- if (parts[i]?.type === "text")
201
- return i;
202
- }
203
- return -1;
204
- }
205
- function TimelineText({ content, compactTop, terminalColumns, streaming = false, }) {
206
- const theme = useTheme();
207
- // Strip any internal reminder/context markup the model echoed back into its
208
- // visible text — the reasoning path already does this. Without it, a model
209
- // that parrots a <bubble_internal_*> block (e.g. an injected system reminder)
210
- // leaks it straight into the transcript. The streaming sanitizer also holds a
211
- // half-typed block until it closes, so partial markup never flashes.
212
- const visible = sanitizeInternalReminderBlocks(content);
213
- if (!visible.trim())
214
- return null;
215
- // Timeline gutter = marginLeft (2) + "● " marker. The ● (U+25CF) is itself
216
- // an ambiguous-width glyph, so the marker is 3 cells on a narrow terminal but
217
- // 4 on an ambiguous-wide one — Ink lays it out as 3 either way (it measures
218
- // ●=1), so on a wide terminal the first line of every message gets shoved 1
219
- // cell right and would overflow. Reserve that extra cell up front so the
220
- // pre-wrap never packs a line the terminal then hard-wraps.
221
- const gutter = ambiguousIsWide() ? 6 : 5;
222
- const available = terminalColumns
223
- ? Math.max(20, terminalColumns - gutter - TRANSCRIPT_ROW_PADDING)
224
- : undefined;
225
- const trimmed = visible.trim();
226
- return (_jsxs(Box, { marginLeft: 2, marginTop: compactTop ? 0 : 1, children: [_jsx(Text, { color: theme.agent, children: "\u25CF " }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: streaming ? (_jsx(StreamingMarkdown, { content: trimmed, maxWidth: available, flushTop: true })) : (_jsx(MarkdownContent, { content: trimmed, maxWidth: available, flushTop: true })) })] }));
227
- }
228
- function ToolsPart({ toolCalls, terminalColumns, verboseTrace, pendingApproval, showExpandHint, compactTop = false, nowTick, showActivity = false, }) {
229
- if (toolCalls.length === 0)
230
- return null;
231
- if (!verboseTrace) {
232
- return (_jsx(TraceGroupList, { toolCalls: toolCalls, terminalColumns: terminalColumns, pendingApproval: pendingApproval, nowTick: nowTick, compactTop: compactTop, showActivity: showActivity }));
233
- }
234
- const lastIdx = toolCalls.length - 1;
235
- return (_jsx(Box, { flexDirection: "column", children: toolCalls.map((tc, idx) => {
236
- const isWaitingApproval = isToolPending(tc) && !!pendingApproval && approvalMatchesTool(pendingApproval, tc);
237
- return (_jsx(ToolCallDisplay, { toolCall: tc, isStreaming: isToolPending(tc), verbose: verboseTrace, terminalColumns: terminalColumns, showExpandHint: showExpandHint && idx === lastIdx, waitingApproval: isWaitingApproval, compactTop: idx === 0 && compactTop, nowTick: nowTick }, tc.id));
238
- }) }));
239
- }
240
- function fallbackStreamingParts(content, tools) {
241
- const parts = [];
242
- if (tools.length > 0)
243
- parts.push({ type: "tools", toolCalls: tools });
244
- if (content)
245
- parts.push({ type: "text", content });
246
- return parts;
247
- }
248
- function findLastToolsPartIndex(parts) {
249
- for (let i = parts.length - 1; i >= 0; i--) {
250
- if (parts[i]?.type === "tools")
251
- return i;
252
- }
253
- return -1;
254
- }
255
- function TraceGroupList({ toolCalls, terminalColumns, pendingApproval, nowTick, compactTop = false, showActivity = false, }) {
256
- const groups = React.useMemo(() => buildTraceGroups(toolCalls), [toolCalls]);
257
- const activeGroup = showActivity ? findActiveTraceGroup(groups, pendingApproval) : undefined;
258
- if (groups.length === 0)
259
- return null;
260
- return (_jsxs(Box, { flexDirection: "column", children: [activeGroup && (_jsx(TraceActivityLine, { group: activeGroup, pendingApproval: pendingApproval, nowTick: nowTick, terminalColumns: terminalColumns })), groups.map((group, idx) => (_jsx(TraceGroupBlock, { group: group, terminalColumns: terminalColumns, pendingApproval: pendingApproval, compactTop: idx === 0 && compactTop, nowTick: nowTick }, group.raw.map((tool) => tool.id).join(":"))))] }));
261
- }
262
- function TraceActivityLine({ group, pendingApproval, nowTick, terminalColumns, }) {
263
- const theme = useTheme();
264
- const waiting = isTraceGroupWaitingForApproval(group, pendingApproval);
265
- void nowTick;
266
- const labelWidth = Math.max(20, terminalColumns - 26);
267
- const label = truncateVisual(traceGroupLabel(group), labelWidth);
268
- return (_jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: waiting ? theme.warning : theme.tracePending, children: "\u25CF " }), _jsxs(Text, { color: theme.traceDetail, children: [waiting ? "Waiting for approval" : "Working on", " "] }), _jsx(Text, { color: theme.traceAction, children: label })] }));
269
- }
270
- function TraceGroupBlock({ group, terminalColumns, pendingApproval, compactTop, nowTick, }) {
271
- const theme = useTheme();
272
- const waiting = isTraceGroupWaitingForApproval(group, pendingApproval);
273
- const status = traceGroupStatus(group, waiting, theme, nowTick);
274
- const editTool = group.kind === "edit" && group.raw.length === 1 ? group.raw[0] : undefined;
275
- const editDetails = editTool && !group.pending && !group.hasError ? getEditDiffDetails(editTool) : null;
276
- if (editTool && editDetails) {
277
- return (_jsx(EditTraceBlock, { tool: editTool, details: editDetails, terminalColumns: terminalColumns, compactTop: compactTop, status: status }));
278
- }
279
- const allErrored = group.hasError && group.errorCount >= group.raw.length && !group.pending;
280
- const titleColor = allErrored ? theme.error : theme.traceAction;
281
- const detailColor = allErrored ? theme.error : theme.traceDetail;
282
- const commandWidth = Math.max(14, terminalColumns - group.title.length - 20);
283
- const detailWidth = Math.max(20, terminalColumns - 8);
284
- const detailLines = group.previewLines.length > 0 ? group.previewLines : group.items;
285
- // Finished, error-free Execute rows collapse to a one-line summary: the
286
- // multi-line command body and stdout preview only earn space while running
287
- // (you want to see what's executing) or on failure (you need the error).
288
- // Ctrl+O (verbose trace) shows everything.
289
- const collapsedExecute = group.kind === "execute" && !group.pending && !waiting && !group.hasError;
290
- // A model-provided bash description owns the header slot; the command then
291
- // always renders as a block below. Without one, single-line commands that
292
- // fit stay inline; anything longer becomes a wrapped block preserving the
293
- // command's own line structure — commands are never clipped mid-line.
294
- // Collapsed rows always inline the (truncated) command instead of a block.
295
- const showDescription = group.kind === "execute" && !!group.description;
296
- const inlineCommand = !showDescription && group.command
297
- ? (group.kind === "execute"
298
- ? (collapsedExecute || shouldInlineExecuteCommand(group, commandWidth)
299
- ? truncateVisual(group.command.replace(/\s+/g, " "), commandWidth)
300
- : undefined)
301
- : (visualWidth(group.command) <= commandWidth ? group.command : undefined))
302
- : undefined;
303
- const commandBlock = group.command && !inlineCommand && !collapsedExecute
304
- ? (group.kind === "execute"
305
- ? executeCommandBlock(group, EXECUTE_COMMAND_BLOCK_MAX_LINES)
306
- : { lines: [group.command], omitted: 0 })
307
- : null;
308
- const collapsedOutputLines = group.previewLines.length + group.omitted;
309
- return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: compactTop ? 0 : 1, children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, color: titleColor, children: group.title }), showDescription ? (_jsxs(Text, { color: theme.traceDetail, children: [" ", truncateVisual(group.description, commandWidth)] })) : inlineCommand ? (_jsxs(Text, { color: theme.traceCommand, children: [" ", inlineCommand] })) : !group.command && group.count !== undefined && group.noun ? (_jsxs(Text, { color: theme.traceCount, children: [" ", group.count, " ", group.noun] })) : null, status && _jsxs(Text, { color: status.color, children: [" ", status.text] })] }), commandBlock && commandBlock.lines.length > 0 && (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [commandBlock.lines.flatMap((line, idx) => wrapByVisualWidth(line || " ", Math.max(10, detailWidth - 2)).map((seg, segIdx) => (_jsx(Text, { color: theme.traceCommand, children: seg }, `cmd-${idx}-${segIdx}`)))), commandBlock.omitted > 0 && (_jsxs(Text, { color: theme.traceDetail, children: ["\u2026 ", commandBlock.omitted, " more line", commandBlock.omitted === 1 ? "" : "s"] }))] })), collapsedExecute ? (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: theme.traceDetail, children: ["\u23BF ", collapsedOutputLines > 0
310
- ? `${collapsedOutputLines} line${collapsedOutputLines === 1 ? "" : "s"} output · Ctrl+O to view`
311
- : "no output"] }) })) : detailLines.length > 0 && (_jsx(Box, { flexDirection: "column", marginLeft: 2, children: detailLines.map((line, index) => (_jsxs(Box, { marginLeft: index === 0 ? 0 : 2, children: [index === 0 && _jsx(Text, { color: theme.traceDetail, children: "\u21B3 " }), _jsx(Text, { color: detailColor, children: truncateVisual(line, detailWidth - (index === 0 ? 2 : 0)) })] }, index))) })), group.errorLines.length > 0 && (_jsx(Box, { flexDirection: "column", marginLeft: 2, children: group.errorLines.map((line, index) => (_jsxs(Box, { marginLeft: index === 0 ? 0 : 2, children: [index === 0 && _jsx(Text, { color: theme.traceDetail, children: "\u21B3 " }), _jsx(Text, { color: theme.error, children: truncateVisual(line, detailWidth - (index === 0 ? 2 : 0)) })] }, `error-${index}`))) })), group.omitted > 0 && !collapsedExecute && (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: theme.traceDetail, children: ["... ", group.omitted, " more, Ctrl+O to view"] }) }))] }));
312
- }
313
- function EditTraceBlock({ tool, details, terminalColumns, compactTop, status, }) {
314
- const theme = useTheme();
315
- const path = formatTracePath(details.path ?? tool.args.path ?? "");
316
- const pathWidth = Math.max(14, terminalColumns - 12);
317
- return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: compactTop ? 0 : 1, children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, color: theme.traceAction, children: "Edit" }), path && _jsxs(Text, { color: theme.traceCommand, children: [" ", truncateVisual(path, pathWidth)] }), status && _jsxs(Text, { color: status.color, children: [" ", status.text] })] }), _jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: theme.traceDetail, children: "\u23BF " }), _jsx(Text, { color: theme.success, children: formatEditSuccessSummary(details) })] }), _jsx(DiffBlock, { diff: details.diff, terminalColumns: terminalColumns, maxLines: EDIT_COLLAPSED_DIFF_LINES, verbose: false, showExpandHint: true })] }));
318
- }
319
- function traceGroupStatus(group, waitingApproval, theme, nowTick) {
320
- if (waitingApproval)
321
- return { text: "waiting for approval", color: theme.warning };
322
- if (group.pending) {
323
- void nowTick;
324
- return { text: "running", color: theme.tracePending };
325
- }
326
- if (group.hasError) {
327
- const count = group.errorCount || 1;
328
- return { text: count === 1 ? "1 error" : `${count} errors`, color: theme.error };
329
- }
330
- return null;
331
- }
332
- function findActiveTraceGroup(groups, pendingApproval) {
333
- for (let i = groups.length - 1; i >= 0; i--) {
334
- const group = groups[i];
335
- if (isTraceGroupWaitingForApproval(group, pendingApproval) || group.pending) {
336
- return group;
337
- }
338
- }
339
- return undefined;
340
- }
341
- function isTraceGroupWaitingForApproval(group, pendingApproval) {
342
- return !!pendingApproval && group.raw.some((tool) => isToolPending(tool) && approvalMatchesTool(pendingApproval, tool));
343
- }
344
- function approvalMatchesTool(hint, tc) {
345
- if (hint.toolName !== tc.name)
346
- return false;
347
- if (hint.toolName === "bash") {
348
- return !hint.command || hint.command === tc.args.command;
349
- }
350
- return !hint.path || hint.path === tc.args.path;
351
- }
352
- function isToolPending(tool) {
353
- return tool.result === undefined;
354
- }
355
- /** Live rolling window size — enough to read the model's train of thought. */
356
- const REASONING_STREAM_WINDOW = 5;
357
- /** Lines kept when a finished turn's reasoning is collapsed. */
358
- const REASONING_COLLAPSED_LINES = 2;
359
- /**
360
- * Reasoning trace with three states, because a reasoning model can spend
361
- * minutes here (qwen3.8-max at high effort drafts entire files in thought):
362
- * hiding it looks like a hang, printing all of it buries the transcript.
363
- *
364
- * streaming + compact rolling last N lines, so there is always motion
365
- * settled + compact opening lines + "(ctrl+o to expand)"
366
- * expanded everything (Ctrl+T thinking, or Ctrl+O verbose)
367
- */
368
- function ReasoningTraceBlock({ reasoning, expanded = false, streaming = false, }) {
369
- const theme = useTheme();
370
- const lines = React.useMemo(() => reasoning.split("\n").filter((l) => l.trim() !== ""), [reasoning]);
371
- if (lines.length === 0)
372
- return null;
373
- const visible = expanded
374
- ? lines
375
- : streaming
376
- ? lines.slice(-REASONING_STREAM_WINDOW)
377
- : lines.slice(0, REASONING_COLLAPSED_LINES);
378
- const hidden = lines.length - visible.length;
379
- return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [_jsxs(Text, { color: theme.thinkingDim, dimColor: true, children: ["\u273B ", streaming && !expanded ? "Thinking…" : "Thinking"] }), visible.map((line, i) => (_jsx(Text, { color: theme.thinkingDim, dimColor: true, italic: true, children: line }, i))), hidden > 0 && (_jsx(Text, { color: theme.thinkingDim, dimColor: true, children: streaming ? "…" : "… (ctrl+o to expand)" }))] }));
380
- }
381
- function CompactionSummaryBlock({ message }) {
382
- const theme = useTheme();
383
- const rawStatus = message.content.replace(/^✓\s*/, "").trim();
384
- const status = rawStatus.replace(/^Compaction complete\s*(?:·\s*)?/i, "").trim() || "Session compacted";
385
- // Same defense as every other visible-text path: strip any internal reminder
386
- // markup before rendering, so a summary that echoed it never reaches the
387
- // transcript. Belt-and-suspenders — the summarizer is fed sanitized history,
388
- // but the summary is model-generated and also re-injected as context.
389
- // File-tracking blocks (<read-files>/<modified-files>) are model-facing
390
- // bookkeeping welded onto the summary — never render them raw either.
391
- const summary = stripFileBlocks(sanitizeInternalReminderBlocks(message.compactionSummary ?? "")).trim() || undefined;
392
- return (_jsxs(Box, { marginTop: 1, marginBottom: 1, paddingX: 1, flexDirection: "column", borderStyle: "round", borderColor: theme.borderActive, children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: theme.success, bold: true, children: "\u2713 " }), _jsx(Text, { color: theme.accent, bold: true, children: "Compaction checkpoint" }), _jsxs(Text, { color: theme.muted, children: [" \u00B7 ", status] })] }), summary && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: theme.muted, dimColor: true, children: "Preserved context summary" }), _jsx(Box, { paddingLeft: 2, flexDirection: "column", children: _jsx(MarkdownContent, { content: summary }) })] }))] }));
393
- }
394
- function UserMessageBlock({ content, terminalColumns, inputStatus, separateFromPrevious = false, }) {
395
- const theme = useTheme();
396
- const badge = userInputStatusBadgeLabel(inputStatus);
397
- // Defense in depth: pure internal-block rows are dropped upstream
398
- // (isInternalBlockOnlyContent), but a block with trailing text — or a
399
- // truncated block — would slip that anchored filter. Never render markup.
400
- const visibleContent = sanitizeInternalReminderBlocks(content);
401
- // Preserve the message's existing text inset after removing the decorative
402
- // rail: the former two rail columns become ordinary background padding.
403
- const leftPaddingWidth = 3;
404
- const rightPaddingWidth = 1;
405
- const horizontalRoom = Math.max(20, terminalColumns - 2);
406
- const bubbleTextWidth = Math.max(1, horizontalRoom - leftPaddingWidth - rightPaddingWidth);
407
- const { bodyLines, referenceLines } = splitImageDisplayContent(visibleContent);
408
- const wrappedLines = bodyLines
409
- .flatMap((line) => wrapByVisualWidth(line, bubbleTextWidth));
410
- const attachmentReferenceIndent = " ".repeat(leftPaddingWidth);
411
- return (_jsxs(Box, { flexDirection: "column", marginTop: separateFromPrevious ? 1 : 0, children: [badge && (_jsxs(Box, { children: [_jsx(Text, { bold: true, color: inputStatus === "pending_steer" ? theme.warning : theme.muted, children: ` ${badge} ` }), _jsx(Text, { color: theme.dim, children: inputStatus === "pending_steer" ? "applies at the next model call" : "runs after this turn" })] })), wrappedLines.map((line, index) => (_jsx(Box, { children: _jsx(Text, { backgroundColor: theme.userMessageBg, color: theme.userMessageText, children: `${" ".repeat(leftPaddingWidth)}${padVisual(line || " ", bubbleTextWidth)}${" ".repeat(rightPaddingWidth)}` }) }, index))), referenceLines.map((line, index) => (_jsx(Box, { children: _jsx(Text, { color: theme.muted, children: `${attachmentReferenceIndent}${line}` }) }, `attachment-${index}`)))] }));
412
- }
413
- function PendingInputMessagesBlock({ messages, terminalColumns, title, hint, bulletColor, }) {
414
- const theme = useTheme();
415
- // Inset per row: marginLeft (2) + "↳ " (2) + the transcript row padding.
416
- const contentWidth = Math.max(20, terminalColumns - 4 - TRANSCRIPT_ROW_PADDING);
417
- return (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: bulletColor, children: "\u2022 " }), _jsxs(Text, { bold: true, color: theme.inputText, children: [title, " "] }), _jsxs(Text, { color: theme.dim, children: ["(", hint, ")"] })] }), messages.flatMap((message, messageIndex) => {
418
- const { bodyLines, referenceLines } = splitImageDisplayContent(message.content || " ");
419
- const wrappedBody = bodyLines.flatMap((line) => wrapByVisualWidth(line || " ", contentWidth));
420
- const bodyRows = wrappedBody.map((line, lineIndex) => (_jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: theme.dim, children: lineIndex === 0 ? "↳ " : " " }), _jsx(Text, { color: theme.inputText, children: line })] }, `body-${message.key ?? messageIndex}-${lineIndex}`)));
421
- const attachmentRows = referenceLines.map((line, lineIndex) => (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: theme.dim, children: [" ", line] }) }, `attachment-${message.key ?? messageIndex}-${lineIndex}`)));
422
- return [...bodyRows, ...attachmentRows];
423
- })] }));
424
- }
425
- const TOOL_DISPLAY_NAMES = {
426
- read: "Read",
427
- write: "Write",
428
- edit: "Edit",
429
- bash: "Bash",
430
- grep: "Grep",
431
- glob: "Glob",
432
- web_fetch: "WebFetch",
433
- web_search: "WebSearch",
434
- };
435
- const TOOL_GLYPHS = {
436
- read: "⏺",
437
- write: "✎",
438
- edit: "✎",
439
- bash: "▶",
440
- grep: "⌕",
441
- glob: "⌕",
442
- web_fetch: "⇲",
443
- web_search: "⌕",
444
- task: "↳",
445
- skill: "★",
446
- };
447
- function displayToolName(name) {
448
- if (TOOL_DISPLAY_NAMES[name])
449
- return TOOL_DISPLAY_NAMES[name];
450
- return name.charAt(0).toUpperCase() + name.slice(1);
451
- }
452
- function toolGlyph(name) {
453
- return TOOL_GLYPHS[name] ?? "●";
454
- }
455
- function getToolHeader(toolCall) {
456
- const args = toolCall.args || {};
457
- const trunc = (s, n = 50) => (s.length > n ? s.slice(0, n) + "..." : s);
458
- switch (toolCall.name) {
459
- case "read":
460
- case "write":
461
- case "edit":
462
- return args.path ? trunc(String(args.path), 60) : undefined;
463
- case "bash":
464
- return args.command ? trunc(String(args.command).replace(/\n/g, " "), 60) : undefined;
465
- case "grep":
466
- return args.pattern ? trunc(String(args.pattern), 60) : undefined;
467
- case "glob":
468
- return args.pattern ? trunc(String(args.pattern), 60) : undefined;
469
- case "web_fetch":
470
- return args.url ? trunc(String(args.url), 60) : undefined;
471
- case "web_search":
472
- return args.query ? trunc(String(args.query), 60) : undefined;
473
- default:
474
- return undefined;
475
- }
476
- }
477
- function summarizeToolResult(tc) {
478
- if (tc.result === undefined)
479
- return "pending";
480
- const raw = tc.result.replace(/\r\n/g, "\n");
481
- if (tc.isError) {
482
- const firstLine = raw.split("\n").find((l) => l.trim() !== "") || "Error";
483
- return firstLine.length > 80 ? firstLine.slice(0, 80) + "..." : firstLine;
484
- }
485
- const nonEmpty = raw.split("\n").filter((l) => l.trim() !== "");
486
- const lineCount = nonEmpty.length;
487
- const p = (n, singular, plural) => `${n} ${n === 1 ? singular : plural}`;
488
- switch (tc.name) {
489
- case "read":
490
- return p(lineCount, "line", "lines");
491
- case "write": {
492
- const firstLine = raw.split("\n")[0] || "";
493
- if (firstLine.startsWith("Wrote ") || firstLine.startsWith("Updated ")) {
494
- return firstLine;
495
- }
496
- return "Wrote file";
497
- }
498
- case "edit": {
499
- return formatEditSuccessSummary(getEditDiffDetails(tc));
500
- }
501
- case "bash":
502
- return lineCount > 0 ? `${p(lineCount, "line", "lines")} output` : "Done";
503
- case "grep":
504
- return `Found ${p(lineCount, "match", "matches")}`;
505
- case "glob":
506
- return `Found ${p(lineCount, "file", "files")}`;
507
- case "web_search":
508
- return `${p(lineCount, "result", "results")}`;
509
- case "web_fetch":
510
- return p(lineCount, "line", "lines");
511
- default:
512
- return lineCount > 0 ? p(lineCount, "line", "lines") : "Done";
513
- }
514
- }
515
- function subagentsFrom(toolCall) {
516
- const raw = toolCall.metadata?.subagents;
517
- if (!Array.isArray(raw))
518
- return [];
519
- return raw.filter((item) => typeof item === "object" && item !== null);
520
- }
521
- const COLLAPSED_PREVIEW_LINES = 10;
522
- const EXPANDED_PREVIEW_LINES = 50;
523
- function ToolCallDisplay({ toolCall, isStreaming, verbose, terminalColumns, showExpandHint = false, waitingApproval = false, compactTop = false, nowTick, }) {
524
- const theme = useTheme();
525
- if (toolCall.metadata?.kind === "subagent") {
526
- return (_jsx(SubagentToolDisplay, { toolCall: toolCall, verbose: verbose, terminalColumns: terminalColumns, compactTop: compactTop }));
527
- }
528
- // Show raw output immediately, then upgrade to highlighted ANSI when shiki
529
- // resolves. Avoids a noticeable "flash" where the line jumps from empty/raw
530
- // to colorized after a tick.
531
- const initialPreview = React.useMemo(() => {
532
- if (toolCall.result === undefined || toolCall.isError)
533
- return null;
534
- return toolCall.result.replace(/\r\n/g, "\n");
535
- }, [toolCall.result, toolCall.isError]);
536
- const [highlighted, setHighlighted] = React.useState(initialPreview);
537
- const header = getToolHeader(toolCall);
538
- const maxLines = verbose ? EXPANDED_PREVIEW_LINES : COLLAPSED_PREVIEW_LINES;
539
- React.useEffect(() => {
540
- let cancelled = false;
541
- if (toolCall.result === undefined || toolCall.isError) {
542
- setHighlighted(null);
543
- return;
544
- }
545
- const raw = toolCall.result.replace(/\r\n/g, "\n");
546
- let lang = "text";
547
- if (toolCall.name === "read")
548
- lang = inferLang(toolCall.args.path);
549
- else if (toolCall.name === "bash")
550
- lang = "shell";
551
- // Always seed with raw so the user sees content immediately.
552
- setHighlighted(raw);
553
- if (lang === "text") {
554
- return;
555
- }
556
- highlightCode(raw, lang)
557
- .then((out) => {
558
- if (!cancelled)
559
- setHighlighted(out);
560
- })
561
- .catch(() => {
562
- if (!cancelled)
563
- setHighlighted(raw);
564
- });
565
- return () => {
566
- cancelled = true;
567
- };
568
- }, [toolCall.result, toolCall.name, toolCall.args.path, toolCall.isError]);
569
- const glyph = toolGlyph(toolCall.name);
570
- const bulletColor = toolCall.isError
571
- ? theme.error
572
- : waitingApproval
573
- ? theme.warning
574
- : isStreaming
575
- ? theme.toolPending
576
- : theme.user;
577
- const name = displayToolName(toolCall.name);
578
- // Compose summary: pending tools stay compact; waiting-for-approval gets an
579
- // explicit badge so the trail survives the dialog closing.
580
- let summary;
581
- let summaryColor = theme.muted;
582
- if (waitingApproval) {
583
- summary = "⏸ waiting for approval";
584
- summaryColor = theme.warning;
585
- }
586
- else if (isToolPending(toolCall) && toolCall.startedAt) {
587
- void nowTick;
588
- summary = "running";
589
- summaryColor = theme.toolPending;
590
- }
591
- else {
592
- summary = summarizeToolResult(toolCall);
593
- if (toolCall.isError)
594
- summaryColor = theme.error;
595
- else if (toolCall.name === "edit" && toolCall.result !== undefined)
596
- summaryColor = theme.success;
597
- }
598
- const editDetails = getEditDiffDetails(toolCall);
599
- const isEditDiff = editDetails !== null && toolCall.result !== undefined;
600
- // Only show the file preview once the tool actually executed. During the
601
- // streaming-args phase, args.content is incomplete and re-rendering the
602
- // entire body per delta both looks chaotic and breaks on partial escapes.
603
- const isWritePreview = toolCall.name === "write" && !toolCall.isError && toolCall.result !== undefined;
604
- return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: compactTop ? 0 : 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: bulletColor, children: [glyph, " "] }), _jsx(Text, { bold: true, color: theme.toolName, children: name }), header && _jsxs(Text, { color: theme.muted, children: ["(", header, ")"] })] }), _jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: summaryColor, children: ["\u23BF ", summary] }) }), toolCall.isError && toolCall.result && (_jsx(Box, { marginLeft: 4, flexDirection: "column", children: toolCall.result.replace(/\r\n/g, "\n").split("\n").slice(0, 6).map((line, i) => (_jsx(Text, { color: theme.error, children: line }, i))) })), isEditDiff && (_jsx(DiffBlock, { diff: editDetails.diff, terminalColumns: terminalColumns, maxLines: maxLines, verbose: verbose, showExpandHint: showExpandHint })), isWritePreview && (_jsx(WritePreview, { content: String(toolCall.args.content || ""), maxLines: maxLines, verbose: verbose, showExpandHint: showExpandHint })), !toolCall.isError && !isEditDiff && !isWritePreview && highlighted && (_jsx(OutputPreview, { text: highlighted, maxLines: maxLines, verbose: verbose, showExpandHint: showExpandHint }))] }));
605
- }
606
- function SubagentToolDisplay({ toolCall, verbose, terminalColumns, compactTop, }) {
607
- const theme = useTheme();
608
- const subagents = subagentsFrom(toolCall);
609
- const hasError = toolCall.isError || subagents.some((subagent) => (subagent.status === "failed" || subagent.status === "blocked" || subagent.status === "cancelled"));
610
- const bulletColor = hasError ? theme.error : isToolPending(toolCall) ? theme.toolPending : theme.user;
611
- const detailWidth = Math.max(24, terminalColumns - 10);
612
- const rows = verbose ? sortSubagents(subagents) : sortSubagents(subagents).slice(0, 4);
613
- const omitted = Math.max(0, subagents.length - rows.length);
614
- return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: compactTop ? 0 : 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: bulletColor, children: "\u21B3 " }), _jsx(Text, { bold: true, color: theme.toolName, children: "Subagents" }), subagents.length > 0 && _jsxs(Text, { color: theme.muted, children: [" ", subagentSummary(subagents)] })] }), rows.length > 0 && (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [rows.map((subagent, index) => {
615
- const status = subagent.status ?? "running";
616
- const label = padVisual(truncateVisual(subagentLabel(subagent), 10), 10);
617
- const descriptorWidth = verbose ? 42 : 32;
618
- const descriptor = padVisual(truncateVisual(subagentDescriptor(subagent), descriptorWidth), descriptorWidth);
619
- const note = truncateVisual(latestSubagentNote(subagent), Math.max(12, detailWidth - 16 - descriptorWidth - 10));
620
- return (_jsxs(Box, { children: [_jsx(Text, { color: subagentStatusColor(status, theme), children: label }), _jsxs(Text, { color: theme.traceAction, children: [" ", descriptor] }), _jsxs(Text, { color: subagentStatusColor(status, theme), children: [" ", padVisual(status, 9)] }), note && _jsxs(Text, { color: subagent.error ? theme.error : theme.traceDetail, children: [" ", note] })] }, subagent.subAgentId ?? `${subagentLabel(subagent)}-${index}`));
621
- }), omitted > 0 && (_jsxs(Text, { color: theme.muted, children: ["... ", omitted, " more \u00B7 Ctrl+O to expand \u00B7 \u2193 then Enter to inspect traces"] }))] })), subagents.length === 0 && toolCall.result && (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: hasError ? theme.error : theme.muted, children: summarizeToolResult(toolCall) }) }))] }));
622
- }
623
- function TruncationHint({ remaining, verbose, showExpandHint, }) {
624
- const theme = useTheme();
625
- if (remaining <= 0)
626
- return null;
627
- const noun = `line${remaining === 1 ? "" : "s"}`;
628
- if (verbose) {
629
- return (_jsxs(Text, { color: theme.muted, children: ["... (", remaining, " more ", noun, ")"] }));
630
- }
631
- return (_jsxs(Text, { color: theme.muted, children: ["\u2026 +", remaining, " ", noun, showExpandHint ? " (ctrl+o to expand)" : ""] }));
632
- }
633
- function OutputPreview({ text, maxLines, verbose, showExpandHint, }) {
634
- const theme = useTheme();
635
- const lines = text.split("\n");
636
- const shown = lines.slice(0, maxLines);
637
- const remaining = Math.max(0, lines.length - maxLines);
638
- if (shown.length === 0 || (shown.length === 1 && shown[0] === ""))
639
- return null;
640
- return (_jsxs(Box, { flexDirection: "column", marginLeft: 4, children: [shown.map((line, i) => (_jsxs(Box, { children: [_jsx(Text, { color: theme.muted, children: "\u2502 " }), _jsx(Text, { children: line })] }, i))), _jsx(TruncationHint, { remaining: remaining, verbose: verbose, showExpandHint: showExpandHint })] }));
641
- }
642
- function WritePreview({ content, maxLines, verbose, showExpandHint, }) {
643
- const theme = useTheme();
644
- const lines = content.split("\n");
645
- const shown = lines.slice(0, maxLines);
646
- const remaining = Math.max(0, lines.length - maxLines);
647
- const numWidth = Math.max(2, String(lines.length).length);
648
- return (_jsxs(Box, { flexDirection: "column", marginLeft: 4, children: [shown.map((line, i) => (_jsxs(Box, { children: [_jsxs(Text, { color: theme.muted, children: [String(i + 1).padStart(numWidth, " "), " "] }), _jsx(Text, { children: line })] }, i))), _jsx(TruncationHint, { remaining: remaining, verbose: verbose, showExpandHint: showExpandHint })] }));
649
- }
650
- function parseDiffLines(body) {
651
- const result = [];
652
- let oldNum = 0;
653
- let newNum = 0;
654
- const rawLines = body.split("\n");
655
- if (rawLines[rawLines.length - 1] === "")
656
- rawLines.pop();
657
- for (const raw of rawLines) {
658
- if (raw.startsWith("+++") ||
659
- raw.startsWith("---") ||
660
- raw.startsWith("Index:") ||
661
- raw.startsWith("==="))
662
- continue;
663
- if (raw.startsWith("@@")) {
664
- const m = raw.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
665
- if (m) {
666
- oldNum = parseInt(m[1], 10);
667
- newNum = parseInt(m[2], 10);
668
- }
669
- continue;
670
- }
671
- if (raw.startsWith("+")) {
672
- result.push({ type: "add", num: newNum, content: raw.slice(1) });
673
- newNum++;
674
- }
675
- else if (raw.startsWith("-")) {
676
- result.push({ type: "remove", num: oldNum, content: raw.slice(1) });
677
- oldNum++;
678
- }
679
- else {
680
- const content = raw.startsWith(" ") ? raw.slice(1) : raw;
681
- result.push({ type: "context", num: newNum, content });
682
- oldNum++;
683
- newNum++;
684
- }
685
- }
686
- return result;
687
- }
688
- function DiffBlock({ diff, terminalColumns, maxLines, verbose, showExpandHint, }) {
689
- const theme = useTheme();
690
- const lines = parseDiffLines(diff);
691
- const shown = lines.slice(0, maxLines);
692
- const remaining = Math.max(0, lines.length - maxLines);
693
- const maxNum = lines.reduce((acc, l) => Math.max(acc, l.num), 0);
694
- const numWidth = Math.max(2, String(maxNum).length);
695
- const leftMargin = 2;
696
- const prefixWidth = numWidth + 4; // " NUM ± "
697
- // Reserve the full left-margin chain from terminal edge to diff content:
698
- // app padding (1) + ToolCallDisplay marginLeft (2) + DiffBlock marginLeft (2)
699
- // + right padding (1) + 1-col safety = 7. Without this, each row overflows
700
- // by 1 column, the terminal auto-wraps, and every line renders with a blank
701
- // row beneath it.
702
- const bandWidth = Math.max(10, terminalColumns - 7);
703
- const contentWidth = Math.max(1, bandWidth - prefixWidth);
704
- const blankPrefix = " ".repeat(prefixWidth);
705
- return (_jsxs(Box, { flexDirection: "column", marginLeft: leftMargin, children: [shown.flatMap((line, i) => {
706
- const bg = line.type === "add"
707
- ? theme.diffAdd
708
- : line.type === "remove"
709
- ? theme.diffRemove
710
- : undefined;
711
- const sign = line.type === "add" ? "+" : line.type === "remove" ? "-" : " ";
712
- const numStr = String(line.num).padStart(numWidth, " ");
713
- // Soft-wrap long lines at the terminal-derived content width so narrow
714
- // splits still show the full content. Continuation rows reuse the same
715
- // background but blank out the gutter (no line number, no +/-) so a
716
- // reader can tell at a glance which rows belong to the same logical
717
- // diff line.
718
- const segments = wrapByVisualWidth(line.content, contentWidth);
719
- return segments.map((segment, segIdx) => {
720
- const padded = padVisual(segment, contentWidth);
721
- const prefix = segIdx === 0 ? ` ${numStr} ${sign} ` : blankPrefix;
722
- return (_jsx(Text, { backgroundColor: bg, color: theme.userMessageText, children: `${prefix}${padded}` }, `${i}-${segIdx}`));
723
- });
724
- }), _jsx(TruncationHint, { remaining: remaining, verbose: verbose, showExpandHint: showExpandHint })] }));
725
- }
726
- /**
727
- * "Edited 3 files (+42 -8) — a.ts, b.ts" digest below the assistant turn.
728
- * Surfaces only when there is at least one file-mutating tool call.
729
- */
730
- function TurnDigest({ toolCalls }) {
731
- const theme = useTheme();
732
- const digest = React.useMemo(() => buildDigest(toolCalls), [toolCalls]);
733
- if (!digest)
734
- return null;
735
- return (_jsx(Box, { marginLeft: 2, marginTop: 1, children: _jsx(Text, { color: theme.muted, dimColor: true, children: digest }) }));
736
- }
737
- function TaskDurationLine({ elapsedMs }) {
738
- const theme = useTheme();
739
- return (_jsx(Box, { marginLeft: 2, marginTop: 1, children: _jsxs(Text, { color: theme.muted, dimColor: true, children: ["Task duration: ", formatDuration(elapsedMs)] }) }));
740
- }
741
- function buildDigest(toolCalls) {
742
- const paths = new Set();
743
- let added = 0;
744
- let removed = 0;
745
- let writes = 0;
746
- let edits = 0;
747
- for (const tc of toolCalls) {
748
- if (tc.isError || !tc.result)
749
- continue;
750
- if (tc.name === "edit") {
751
- const details = getEditDiffDetails(tc);
752
- if (details) {
753
- added += details.added;
754
- removed += details.removed;
755
- }
756
- if (tc.args.path)
757
- paths.add(String(tc.args.path));
758
- edits += 1;
759
- }
760
- else if (tc.name === "write") {
761
- if (tc.args.path)
762
- paths.add(String(tc.args.path));
763
- writes += 1;
764
- const content = String(tc.args.content ?? "");
765
- if (content)
766
- added += content.split("\n").length;
767
- }
768
- }
769
- const total = edits + writes;
770
- if (total === 0 || paths.size === 0)
771
- return null;
772
- const verb = edits > 0 && writes === 0 ? "Edited" : writes > 0 && edits === 0 ? "Wrote" : "Touched";
773
- const pathList = Array.from(paths);
774
- const shownPaths = pathList.slice(0, 4).map((p) => p.split("/").pop() || p);
775
- const extra = pathList.length - shownPaths.length;
776
- const pathDisplay = shownPaths.join(", ") + (extra > 0 ? `, +${extra} more` : "");
777
- const stats = added || removed
778
- ? ` (${added ? `+${added}` : ""}${added && removed ? " " : ""}${removed ? `-${removed}` : ""})`
779
- : "";
780
- return `↳ ${verb} ${paths.size} file${paths.size === 1 ? "" : "s"}${stats} — ${pathDisplay}`;
781
- }
782
- function formatDuration(ms) {
783
- if (!Number.isFinite(ms) || ms <= 0)
784
- return "0s";
785
- if (ms < 1000)
786
- return `${Math.max(1, Math.round(ms))}ms`;
787
- const seconds = ms / 1000;
788
- if (seconds < 10)
789
- return `${seconds.toFixed(1)}s`;
790
- if (seconds < 60)
791
- return `${Math.round(seconds)}s`;
792
- let minutes = Math.floor(seconds / 60);
793
- let remSec = Math.round(seconds - minutes * 60);
794
- if (remSec >= 60) {
795
- minutes += Math.floor(remSec / 60);
796
- remSec %= 60;
797
- }
798
- return remSec === 0 ? `${minutes}m` : `${minutes}m ${remSec}s`;
799
- }
800
- function truncateVisual(str, maxWidth) {
801
- if (maxWidth <= 0)
802
- return "";
803
- let out = "";
804
- let width = 0;
805
- for (const char of str) {
806
- const w = graphemeWidth(char);
807
- if (width + w > maxWidth)
808
- break;
809
- out += char;
810
- width += w;
811
- }
812
- return out;
813
- }
814
- function padVisual(str, width) {
815
- const currentWidth = visualWidth(str);
816
- return str + " ".repeat(Math.max(0, width - currentWidth));
817
- }
818
- function wrapByVisualWidth(line, maxWidth) {
819
- if (maxWidth <= 0)
820
- return [line];
821
- if (line === "")
822
- return [""];
823
- const result = [];
824
- let current = "";
825
- let currentWidth = 0;
826
- for (const char of line) {
827
- const w = graphemeWidth(char);
828
- if (currentWidth + w > maxWidth) {
829
- result.push(current);
830
- current = char;
831
- currentWidth = w;
832
- }
833
- else {
834
- current += char;
835
- currentWidth += w;
836
- }
837
- }
838
- if (current !== "" || result.length === 0)
839
- result.push(current);
840
- return result;
841
- }