@bubblebrain-ai/bubble 0.0.52 → 0.0.53

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 (481) 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/node_modules/@bubblebrain-ai/pi-tui/LICENSE +21 -0
  213. package/node_modules/@bubblebrain-ai/pi-tui/README.md +854 -0
  214. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/alt-screen-search.d.ts +26 -0
  215. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/alt-screen-search.d.ts.map +1 -0
  216. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/alt-screen-search.js +121 -0
  217. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/alt-screen-search.js.map +1 -0
  218. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/autocomplete.d.ts +86 -0
  219. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/autocomplete.d.ts.map +1 -0
  220. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/autocomplete.js +649 -0
  221. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/autocomplete.js.map +1 -0
  222. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/alt-screen-flash.d.ts +13 -0
  223. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/alt-screen-flash.d.ts.map +1 -0
  224. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/alt-screen-flash.js +36 -0
  225. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/alt-screen-flash.js.map +1 -0
  226. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/box.d.ts +22 -0
  227. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/box.d.ts.map +1 -0
  228. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/box.js +99 -0
  229. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/box.js.map +1 -0
  230. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/cancellable-loader.d.ts +22 -0
  231. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/cancellable-loader.d.ts.map +1 -0
  232. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/cancellable-loader.js +36 -0
  233. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/cancellable-loader.js.map +1 -0
  234. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/editor.d.ts +330 -0
  235. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/editor.d.ts.map +1 -0
  236. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/editor.js +2344 -0
  237. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/editor.js.map +1 -0
  238. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/h-stack.d.ts +7 -0
  239. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/h-stack.d.ts.map +1 -0
  240. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/h-stack.js +43 -0
  241. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/h-stack.js.map +1 -0
  242. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/image.d.ts +28 -0
  243. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/image.d.ts.map +1 -0
  244. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/image.js +82 -0
  245. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/image.js.map +1 -0
  246. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/input.d.ts +48 -0
  247. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/input.d.ts.map +1 -0
  248. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/input.js +390 -0
  249. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/input.js.map +1 -0
  250. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/loader.d.ts +31 -0
  251. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/loader.d.ts.map +1 -0
  252. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/loader.js +67 -0
  253. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/loader.js.map +1 -0
  254. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/markdown.d.ts +103 -0
  255. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/markdown.d.ts.map +1 -0
  256. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/markdown.js +797 -0
  257. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/markdown.js.map +1 -0
  258. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/scroll-view.d.ts +56 -0
  259. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/scroll-view.d.ts.map +1 -0
  260. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/scroll-view.js +179 -0
  261. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/scroll-view.js.map +1 -0
  262. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/select-list.d.ts +53 -0
  263. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/select-list.d.ts.map +1 -0
  264. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/select-list.js +175 -0
  265. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/select-list.js.map +1 -0
  266. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/settings-list.d.ts +50 -0
  267. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/settings-list.d.ts.map +1 -0
  268. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/settings-list.js +174 -0
  269. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/settings-list.js.map +1 -0
  270. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/spacer.d.ts +12 -0
  271. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/spacer.d.ts.map +1 -0
  272. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/spacer.js +22 -0
  273. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/spacer.js.map +1 -0
  274. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/stack.d.ts +32 -0
  275. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/stack.d.ts.map +1 -0
  276. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/stack.js +109 -0
  277. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/stack.js.map +1 -0
  278. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/text.d.ts +19 -0
  279. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/text.d.ts.map +1 -0
  280. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/text.js +81 -0
  281. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/text.js.map +1 -0
  282. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/truncated-text.d.ts +13 -0
  283. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/truncated-text.d.ts.map +1 -0
  284. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/truncated-text.js +48 -0
  285. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/truncated-text.js.map +1 -0
  286. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/v-stack.d.ts +8 -0
  287. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/v-stack.d.ts.map +1 -0
  288. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/v-stack.js +26 -0
  289. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/v-stack.js.map +1 -0
  290. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/editor-component.d.ts +39 -0
  291. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/editor-component.d.ts.map +1 -0
  292. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/editor-component.js +2 -0
  293. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/editor-component.js.map +1 -0
  294. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/fuzzy.d.ts +16 -0
  295. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/fuzzy.d.ts.map +1 -0
  296. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/fuzzy.js +110 -0
  297. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/fuzzy.js.map +1 -0
  298. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/index.d.ts +31 -0
  299. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/index.d.ts.map +1 -0
  300. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/index.js +42 -0
  301. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/index.js.map +1 -0
  302. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keybindings.d.ts +273 -0
  303. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keybindings.d.ts.map +1 -0
  304. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keybindings.js +231 -0
  305. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keybindings.js.map +1 -0
  306. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keys.d.ts +184 -0
  307. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keys.d.ts.map +1 -0
  308. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keys.js +1174 -0
  309. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keys.js.map +1 -0
  310. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/kill-ring.d.ts +28 -0
  311. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/kill-ring.d.ts.map +1 -0
  312. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/kill-ring.js +46 -0
  313. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/kill-ring.js.map +1 -0
  314. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/latex.d.ts +10 -0
  315. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/latex.d.ts.map +1 -0
  316. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/latex.js +1268 -0
  317. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/latex.js.map +1 -0
  318. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout-node.d.ts +40 -0
  319. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout-node.d.ts.map +1 -0
  320. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout-node.js +6 -0
  321. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout-node.js.map +1 -0
  322. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout.d.ts +42 -0
  323. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout.d.ts.map +1 -0
  324. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout.js +335 -0
  325. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout.js.map +1 -0
  326. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/native-modifiers.d.ts +3 -0
  327. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/native-modifiers.d.ts.map +1 -0
  328. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/native-modifiers.js +60 -0
  329. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/native-modifiers.js.map +1 -0
  330. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/stdin-buffer.d.ts +56 -0
  331. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/stdin-buffer.d.ts.map +1 -0
  332. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/stdin-buffer.js +363 -0
  333. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/stdin-buffer.js.map +1 -0
  334. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-colors.d.ts +10 -0
  335. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-colors.d.ts.map +1 -0
  336. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-colors.js +59 -0
  337. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-colors.js.map +1 -0
  338. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-image.d.ts +116 -0
  339. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-image.d.ts.map +1 -0
  340. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-image.js +518 -0
  341. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-image.js.map +1 -0
  342. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal.d.ts +142 -0
  343. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal.d.ts.map +1 -0
  344. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal.js +544 -0
  345. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal.js.map +1 -0
  346. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/testing.d.ts +11 -0
  347. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/testing.d.ts.map +1 -0
  348. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/testing.js +11 -0
  349. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/testing.js.map +1 -0
  350. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.d.ts +140 -0
  351. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.d.ts.map +1 -0
  352. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.js +1334 -0
  353. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.js.map +1 -0
  354. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-main-screen.d.ts +39 -0
  355. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-main-screen.d.ts.map +1 -0
  356. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-main-screen.js +536 -0
  357. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-main-screen.js.map +1 -0
  358. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.d.ts +338 -0
  359. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.d.ts.map +1 -0
  360. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.js +1016 -0
  361. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.js.map +1 -0
  362. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/undo-stack.d.ts +17 -0
  363. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/undo-stack.d.ts.map +1 -0
  364. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/undo-stack.js +27 -0
  365. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/undo-stack.js.map +1 -0
  366. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/utils.d.ts +96 -0
  367. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/utils.d.ts.map +1 -0
  368. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/utils.js +1193 -0
  369. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/utils.js.map +1 -0
  370. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/word-navigation.d.ts +25 -0
  371. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/word-navigation.d.ts.map +1 -0
  372. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/word-navigation.js +96 -0
  373. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/word-navigation.js.map +1 -0
  374. package/node_modules/@bubblebrain-ai/pi-tui/dist/test/virtual-terminal.d.ts +73 -0
  375. package/node_modules/@bubblebrain-ai/pi-tui/dist/test/virtual-terminal.d.ts.map +1 -0
  376. package/node_modules/@bubblebrain-ai/pi-tui/dist/test/virtual-terminal.js +188 -0
  377. package/node_modules/@bubblebrain-ai/pi-tui/dist/test/virtual-terminal.js.map +1 -0
  378. package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/README.md +20 -0
  379. package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/build.sh +62 -0
  380. package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-modifiers.node +0 -0
  381. package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
  382. package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/src/darwin-modifiers.c +70 -0
  383. package/node_modules/@bubblebrain-ai/pi-tui/native/win32/README.md +22 -0
  384. package/node_modules/@bubblebrain-ai/pi-tui/native/win32/build.mjs +229 -0
  385. package/node_modules/@bubblebrain-ai/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  386. package/node_modules/@bubblebrain-ai/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  387. package/node_modules/@bubblebrain-ai/pi-tui/native/win32/src/win32-console-mode.c +119 -0
  388. package/node_modules/@bubblebrain-ai/pi-tui/package.json +66 -0
  389. package/node_modules/get-east-asian-width/index.d.ts +60 -0
  390. package/node_modules/get-east-asian-width/index.js +30 -0
  391. package/node_modules/get-east-asian-width/license +9 -0
  392. package/node_modules/get-east-asian-width/lookup-data.js +21 -0
  393. package/node_modules/get-east-asian-width/lookup.js +138 -0
  394. package/node_modules/get-east-asian-width/package.json +71 -0
  395. package/node_modules/get-east-asian-width/readme.md +65 -0
  396. package/node_modules/get-east-asian-width/utilities.js +24 -0
  397. package/package.json +23 -10
  398. package/packages/pi-tui/node_modules/marked/LICENSE +44 -0
  399. package/packages/pi-tui/node_modules/marked/README.md +115 -0
  400. package/packages/pi-tui/node_modules/marked/bin/main.js +284 -0
  401. package/packages/pi-tui/node_modules/marked/bin/marked.js +16 -0
  402. package/packages/pi-tui/node_modules/marked/lib/marked.d.ts +760 -0
  403. package/packages/pi-tui/node_modules/marked/lib/marked.esm.js +77 -0
  404. package/packages/pi-tui/node_modules/marked/lib/marked.esm.js.map +7 -0
  405. package/packages/pi-tui/node_modules/marked/lib/marked.umd.js +79 -0
  406. package/packages/pi-tui/node_modules/marked/lib/marked.umd.js.map +7 -0
  407. package/packages/pi-tui/node_modules/marked/man/marked.1 +113 -0
  408. package/packages/pi-tui/node_modules/marked/man/marked.1.md +93 -0
  409. package/packages/pi-tui/node_modules/marked/package.json +103 -0
  410. package/dist/tui-ink/app.d.ts +0 -76
  411. package/dist/tui-ink/app.js +0 -2377
  412. package/dist/tui-ink/approval/approval-dialog.d.ts +0 -13
  413. package/dist/tui-ink/approval/approval-dialog.js +0 -184
  414. package/dist/tui-ink/approval/diff-view.d.ts +0 -7
  415. package/dist/tui-ink/approval/diff-view.js +0 -44
  416. package/dist/tui-ink/approval/select.d.ts +0 -35
  417. package/dist/tui-ink/approval/select.js +0 -98
  418. package/dist/tui-ink/code-highlight.d.ts +0 -8
  419. package/dist/tui-ink/code-highlight.js +0 -121
  420. package/dist/tui-ink/command-palette.d.ts +0 -32
  421. package/dist/tui-ink/command-palette.js +0 -158
  422. package/dist/tui-ink/compaction-progress.d.ts +0 -19
  423. package/dist/tui-ink/compaction-progress.js +0 -74
  424. package/dist/tui-ink/detect-theme.d.ts +0 -1
  425. package/dist/tui-ink/detect-theme.js +0 -1
  426. package/dist/tui-ink/feedback-dialog.d.ts +0 -19
  427. package/dist/tui-ink/feedback-dialog.js +0 -133
  428. package/dist/tui-ink/feishu-setup-picker.d.ts +0 -5
  429. package/dist/tui-ink/feishu-setup-picker.js +0 -271
  430. package/dist/tui-ink/footer.d.ts +0 -26
  431. package/dist/tui-ink/footer.js +0 -51
  432. package/dist/tui-ink/input-box.d.ts +0 -189
  433. package/dist/tui-ink/input-box.js +0 -1279
  434. package/dist/tui-ink/input-history.d.ts +0 -1
  435. package/dist/tui-ink/input-history.js +0 -1
  436. package/dist/tui-ink/markdown.d.ts +0 -103
  437. package/dist/tui-ink/markdown.js +0 -747
  438. package/dist/tui-ink/message-list.d.ts +0 -49
  439. package/dist/tui-ink/message-list.js +0 -841
  440. package/dist/tui-ink/model-picker.d.ts +0 -143
  441. package/dist/tui-ink/model-picker.js +0 -966
  442. package/dist/tui-ink/plan-confirm.d.ts +0 -7
  443. package/dist/tui-ink/plan-confirm.js +0 -115
  444. package/dist/tui-ink/question-dialog.d.ts +0 -8
  445. package/dist/tui-ink/question-dialog.js +0 -135
  446. package/dist/tui-ink/rewind-picker.d.ts +0 -8
  447. package/dist/tui-ink/rewind-picker.js +0 -102
  448. package/dist/tui-ink/run-session-picker.js +0 -22
  449. package/dist/tui-ink/run.d.ts +0 -62
  450. package/dist/tui-ink/run.js +0 -103
  451. package/dist/tui-ink/session-picker.d.ts +0 -10
  452. package/dist/tui-ink/session-picker.js +0 -113
  453. package/dist/tui-ink/stats-panel.d.ts +0 -11
  454. package/dist/tui-ink/stats-panel.js +0 -65
  455. package/dist/tui-ink/subagent-inspector.d.ts +0 -21
  456. package/dist/tui-ink/subagent-inspector.js +0 -205
  457. package/dist/tui-ink/use-terminal-size.d.ts +0 -4
  458. package/dist/tui-ink/use-terminal-size.js +0 -48
  459. package/dist/tui-ink/waiting-indicator.d.ts +0 -12
  460. package/dist/tui-ink/waiting-indicator.js +0 -129
  461. package/dist/tui-ink/welcome.d.ts +0 -25
  462. package/dist/tui-ink/welcome.js +0 -88
  463. package/dist/tui-ink/wordmark.d.ts +0 -8
  464. package/dist/tui-ink/wordmark.js +0 -103
  465. /package/dist/{tui-ink → tui/model}/composer-buffer.d.ts +0 -0
  466. /package/dist/{tui-ink → tui/model}/display-history.js +0 -0
  467. /package/dist/{tui-ink → tui/model}/input-queue.js +0 -0
  468. /package/dist/{tui-ink → tui/model}/key-events.d.ts +0 -0
  469. /package/dist/{tui-ink → tui/model}/key-events.js +0 -0
  470. /package/dist/{tui-ink → tui/model}/paste-operation-tracker.d.ts +0 -0
  471. /package/dist/{tui-ink → tui/model}/paste-operation-tracker.js +0 -0
  472. /package/dist/{tui-ink → tui/model}/recent-activity.d.ts +0 -0
  473. /package/dist/{tui-ink → tui/model}/submit-dedupe.js +0 -0
  474. /package/dist/{tui-ink → tui/model}/sync-output.d.ts +0 -0
  475. /package/dist/{tui-ink → tui/model}/sync-output.js +0 -0
  476. /package/dist/{tui-ink → tui/model}/terminal-env.d.ts +0 -0
  477. /package/dist/{tui-ink → tui/model}/terminal-env.js +0 -0
  478. /package/dist/{tui-ink → tui/model}/terminal-mouse.d.ts +0 -0
  479. /package/dist/{tui-ink → tui/model}/terminal-mouse.js +0 -0
  480. /package/dist/{tui-ink → tui/model}/width.d.ts +0 -0
  481. /package/dist/{tui-ink → tui/model}/width.js +0 -0
@@ -0,0 +1,1035 @@
1
+ import { SessionManager } from "../../session.js";
2
+ import { tokenUsageTotal } from "../../goal/usage.js";
3
+ import { ControllerState } from "./state.js";
4
+ import { buildAssistantMessage, createRunState, reduceAgentEvent, reduceRunFinish, STREAMING_FLUSH_INTERVAL_MS, } from "./agent-event-reducer.js";
5
+ import { beginSteer, createInputQueueState, drainLeftoverSteers, purgeForSessionSwitch, } from "./input-queue-machine.js";
6
+ import { AgentRunInputQueue } from "../../agent/input-controller.js";
7
+ import { OverlayRequestController } from "./overlay-controller.js";
8
+ import { SessionTransitionController } from "./session-transition.js";
9
+ import { moveStatusMessageToEnd, nextDisplayMessageKey, setUserInputStatus, snapshotDisplayParts, } from "../model/display-history.js";
10
+ import { accumulateLiveSubagentUpdate, collectSubagentGroups, mergeSubagentSnapshotsIntoMessages, pruneSettledLiveSubagentTools, } from "../model/subagent-view.js";
11
+ import { reconstructDisplayMessages } from "../model/display-reconstruct.js";
12
+ import { buildImageContentParts, buildImageContentPartsFromDisplayText } from "../model/image-paste.js";
13
+ import { displayImagesFromPayload, formatImageUserDisplayText } from "../image-display.js";
14
+ import { GoalRuntimeController } from "./goal-runtime-controller.js";
15
+ import { TaskRuntimeController } from "./task-runtime-controller.js";
16
+ import { formatInternalContextBlock } from "../../agent/internal-reminder-sanitizer.js";
17
+ import { executeRewind } from "../../rewind.js";
18
+ function isSubmitPayload(value) {
19
+ if (!value || typeof value !== "object")
20
+ return false;
21
+ const payload = value;
22
+ return typeof payload.text === "string" && Array.isArray(payload.images);
23
+ }
24
+ export class BubbleTuiController {
25
+ deps;
26
+ state = new ControllerState();
27
+ listeners = new Set();
28
+ overlays;
29
+ queue = createInputQueueState();
30
+ sessionTransition;
31
+ startedAtMs;
32
+ goalRuntime;
33
+ taskRuntime;
34
+ pendingTaskAnnouncements = new Map();
35
+ sessionManager;
36
+ transcript = [];
37
+ runActive = false;
38
+ runState = null;
39
+ /** Background subagent updates can outlive the provider turn that launched
40
+ * them. Keep their latest synthetic tool snapshot in the live trace. */
41
+ liveSubagentTools = new Map();
42
+ /** Provider-turn accumulators for true child-session inspection. */
43
+ childRuns = new Map();
44
+ activeInputController = null;
45
+ activeAbortController = null;
46
+ commandActivity = null;
47
+ /** False after a provider turn commits, so settled and live rows never coexist. */
48
+ liveStreamVisible = false;
49
+ backgroundTaskUnsubscribe;
50
+ disposed = false;
51
+ constructor(deps) {
52
+ this.deps = deps;
53
+ this.sessionManager = deps.sessionManager;
54
+ this.transcript = [
55
+ ...reconstructDisplayMessages([...deps.agent.messages]),
56
+ ...restoredTaskLifecycleMessages(this.sessionManager),
57
+ ];
58
+ this.overlays = new OverlayRequestController();
59
+ this.startedAtMs = Date.now();
60
+ this.sessionTransition = new SessionTransitionController({
61
+ host: deps.ports.sessionHost,
62
+ state: this.state,
63
+ overlays: this.overlays,
64
+ queue: this.queue,
65
+ agent: {
66
+ getMessages: () => deps.agent.messages,
67
+ setSessionID: (file) => deps.agent.setSessionID(file),
68
+ },
69
+ bumpExternalGeneration: () => {
70
+ this.externalGeneration += 1;
71
+ },
72
+ clearLiveSubagentTools: () => {
73
+ this.liveSubagentTools.clear();
74
+ this.childRuns.clear();
75
+ this.liveSubagentVersion += 1;
76
+ },
77
+ commit: (manager, transcript) => {
78
+ this.sessionManager = manager;
79
+ this.deps.ports.flush.cancelFlush();
80
+ this.transcript = [...transcript, ...restoredTaskLifecycleMessages(manager)];
81
+ this.queue.queued.length = 0;
82
+ this.liveStreamVisible = false;
83
+ this.runState = null;
84
+ this.activeInputController = null;
85
+ this.activeAbortController = null;
86
+ this.commandActivity = null;
87
+ this.state.touch();
88
+ },
89
+ });
90
+ this.overlays.onChange(() => this.state.touch());
91
+ if (deps.goalStore) {
92
+ this.goalRuntime = new GoalRuntimeController({
93
+ store: deps.goalStore,
94
+ getSessionManager: () => this.sessionManager,
95
+ isRunActive: () => this.isBusy(),
96
+ queuedInputs: () => this.queue.queued.length,
97
+ isDisposed: () => this.disposed,
98
+ startRun: (input, cwd) => {
99
+ void this.runTurn(input, cwd, { goalRun: true });
100
+ },
101
+ appendMessage: (role, content) => {
102
+ this.appendDisplayMessage({
103
+ key: nextDisplayMessageKey(role === "user" ? "user" : role === "error" ? "error" : "notice"),
104
+ role,
105
+ content,
106
+ syntheticKind: role === "assistant" ? "ui_notice" : undefined,
107
+ });
108
+ },
109
+ onStateChanged: () => {
110
+ this.state.touch();
111
+ this.notify();
112
+ },
113
+ });
114
+ }
115
+ if (deps.processManager) {
116
+ this.taskRuntime = new TaskRuntimeController({
117
+ processManager: deps.processManager,
118
+ getSessionFile: () => this.sessionManager.getSessionFile(),
119
+ appendMarker: (file, marker, payload) => this.appendTaskMarker(file, marker, payload),
120
+ announceCompletion: (task) => this.announceTaskCompletion(task),
121
+ submitTaskWake: (summary) => {
122
+ const cwd = this.sessionManager.getMetadata().cwd ?? deps.workspaceCwd ?? process.cwd();
123
+ void this.runTurn(formatInternalContextBlock("background-task", summary), cwd);
124
+ },
125
+ tasksAutoResume: deps.tasksAutoResume !== false,
126
+ gates: () => ({
127
+ turnRunning: this.isBusy(),
128
+ queuedInputs: this.queue.queued.length,
129
+ exiting: this.disposed,
130
+ goalActive: this.goalRuntime?.snapshot()?.status === "active",
131
+ }),
132
+ });
133
+ this.taskRuntime.start();
134
+ }
135
+ this.backgroundTaskUnsubscribe = (deps.processManager
136
+ ? deps.processManager.onChange(() => {
137
+ this.state.touch();
138
+ this.notify();
139
+ })
140
+ : deps.agent.backgroundTasks?.subscribe?.(() => {
141
+ this.state.touch();
142
+ this.notify();
143
+ }));
144
+ }
145
+ externalGeneration = 0;
146
+ liveSubagentVersion = 0;
147
+ subscribe(listener) {
148
+ this.listeners.add(listener);
149
+ return () => this.listeners.delete(listener);
150
+ }
151
+ getSnapshotVersion() {
152
+ return this.state.version;
153
+ }
154
+ getTranscript() {
155
+ return this.transcript;
156
+ }
157
+ getSessionManager() {
158
+ return this.sessionManager;
159
+ }
160
+ getGoalIndicator() {
161
+ return this.goalRuntime?.indicatorLine();
162
+ }
163
+ handleGoalCommand(input, cwd) {
164
+ if (!this.goalRuntime) {
165
+ this.appendDisplayMessage({
166
+ key: nextDisplayMessageKey("error"),
167
+ role: "error",
168
+ content: "Goals are not available in this session.",
169
+ });
170
+ return;
171
+ }
172
+ this.goalRuntime.handleCommand(input, cwd);
173
+ }
174
+ getSubagentGroups() {
175
+ const fromTrace = collectSubagentGroups(this.transcript, [...this.liveSubagentTools.values()]);
176
+ const claimed = new Set(fromTrace.flatMap((group) => group.members.map((member) => member.subAgentId).filter(Boolean)));
177
+ const direct = this.deps.agent.listSubAgents()
178
+ .filter((snapshot) => !claimed.has(snapshot.agentId))
179
+ .map((snapshot) => ({
180
+ id: `single:${snapshot.agentId}`,
181
+ runId: snapshot.runId,
182
+ kind: "single",
183
+ label: snapshot.nickname,
184
+ members: [{
185
+ subAgentId: snapshot.agentId,
186
+ agentName: snapshot.agentName,
187
+ nickname: snapshot.nickname,
188
+ status: snapshot.status,
189
+ category: snapshot.category,
190
+ phase: snapshot.phase,
191
+ route: snapshot.route,
192
+ profileSource: snapshot.profileSource,
193
+ task: snapshot.task,
194
+ summary: snapshot.summary,
195
+ toolNotes: snapshot.toolNotes,
196
+ error: snapshot.error,
197
+ usage: snapshot.usage,
198
+ createdAt: snapshot.createdAt,
199
+ updatedAt: snapshot.updatedAt,
200
+ }],
201
+ }));
202
+ return [...fromTrace, ...direct];
203
+ }
204
+ getWorkflows() {
205
+ return this.deps.agent.listWorkflows();
206
+ }
207
+ getBackgroundTasks() {
208
+ return this.deps.processManager?.listTasks(this.sessionManager.getSessionFile())
209
+ ?? this.deps.agent.backgroundTasks?.list()
210
+ ?? [];
211
+ }
212
+ getChildTranscript(agentId) {
213
+ return reconstructDisplayMessages(this.deps.agent.getSubAgentMessages(agentId));
214
+ }
215
+ getChildStreamingTail(agentId) {
216
+ const child = this.childRuns.get(agentId);
217
+ if (!child?.visible)
218
+ return null;
219
+ const acc = child.state.accumulator;
220
+ return {
221
+ content: acc.content,
222
+ reasoning: acc.reasoning,
223
+ tools: acc.toolCalls.map((tool) => ({
224
+ ...tool,
225
+ args: { ...tool.args },
226
+ metadata: tool.metadata ? { ...tool.metadata } : undefined,
227
+ })),
228
+ parts: snapshotDisplayParts(acc.parts),
229
+ };
230
+ }
231
+ stopSubagent(agentId) {
232
+ void this.deps.agent.closeSubAgent(agentId);
233
+ }
234
+ stopWorkflow(runId) {
235
+ this.deps.agent.closeWorkflow(runId);
236
+ }
237
+ stopBackgroundTask(id) {
238
+ if (this.deps.processManager)
239
+ void this.deps.processManager.killTask(id);
240
+ else
241
+ void this.deps.agent.backgroundTasks?.kill?.(id);
242
+ }
243
+ getBackgroundTaskOutput(id) {
244
+ return this.deps.processManager?.taskOutputTail(id) ?? this.deps.agent.backgroundTasks?.outputTail(id) ?? "";
245
+ }
246
+ /** Called by the host after graceful task reaping on TUI exit. */
247
+ persistFinalTaskMarkers() {
248
+ this.taskRuntime?.persistTerminalSnapshot();
249
+ }
250
+ appendTaskMarker(file, marker, payload) {
251
+ try {
252
+ const manager = this.sessionManager.getSessionFile() === file
253
+ ? this.sessionManager
254
+ : new SessionManager(file);
255
+ manager.appendMarker(marker, payload);
256
+ }
257
+ catch {
258
+ // Lifecycle persistence is best-effort; it must never break the child.
259
+ }
260
+ }
261
+ announceTaskCompletion(task) {
262
+ // Switching back to the owner session reconstructs terminal task rows
263
+ // from its persisted marker before held completions are released. Treat
264
+ // that row as the same lifecycle event instead of appending a duplicate.
265
+ if (this.transcript.some((message) => message.toolCalls?.some((tool) => tool.metadata?.taskId === task.id && tool.metadata?.taskLifecycle !== undefined))) {
266
+ return;
267
+ }
268
+ if (this.runActive) {
269
+ this.pendingTaskAnnouncements.set(task.id, task);
270
+ this.state.touch();
271
+ this.notify();
272
+ return;
273
+ }
274
+ this.appendDisplayMessage(taskLifecycleDisplayMessage(task, this.deps.processManager?.taskOutputTail(task.id, 12_000)));
275
+ }
276
+ /** Move the newest live Bash Execute into the unified task manager. */
277
+ promoteActiveBash() {
278
+ const channel = this.deps.promotionChannel;
279
+ if (!channel || !this.runState)
280
+ return undefined;
281
+ const tool = [...this.runState.accumulator.toolCalls]
282
+ .reverse()
283
+ .find((candidate) => candidate.name === "bash"
284
+ && candidate.status === "running"
285
+ && channel.hasPromotable(candidate.id));
286
+ return tool ? channel.requestPromotion(tool.id) : undefined;
287
+ }
288
+ /** Drop every transcript row (/clear). */
289
+ clearTranscript() {
290
+ this.transcript = [];
291
+ this.liveSubagentTools.clear();
292
+ this.childRuns.clear();
293
+ this.state.touch();
294
+ this.notify();
295
+ }
296
+ /** Re-project the visible transcript after a non-controller command rewrites Agent history. */
297
+ rebuildTranscriptFromAgent() {
298
+ this.state.withTransaction(() => {
299
+ this.transcript = [
300
+ ...reconstructDisplayMessages([...this.deps.agent.messages]),
301
+ ...restoredTaskLifecycleMessages(this.sessionManager),
302
+ ];
303
+ this.liveSubagentTools.clear();
304
+ this.childRuns.clear();
305
+ this.liveSubagentVersion += 1;
306
+ this.state.touch();
307
+ });
308
+ this.notify();
309
+ }
310
+ /**
311
+ * Rewind persistence and every renderer-facing mirror as one observable
312
+ * transition. File restoration can take time, but no partial transcript is
313
+ * published while it runs.
314
+ */
315
+ async rewindToTurn(targetId, scope) {
316
+ if (this.runActive)
317
+ throw new Error("Cancel the active turn before rewinding.");
318
+ const result = await executeRewind(this.sessionManager, this.deps.agent, targetId, scope);
319
+ if (scope !== "code") {
320
+ this.state.withTransaction(() => {
321
+ purgeForSessionSwitch(this.queue);
322
+ this.queue.queued.length = 0;
323
+ this.transcript = [
324
+ ...reconstructDisplayMessages([...this.deps.agent.messages]),
325
+ ...restoredTaskLifecycleMessages(this.sessionManager),
326
+ ];
327
+ this.liveSubagentTools.clear();
328
+ this.childRuns.clear();
329
+ this.liveSubagentVersion += 1;
330
+ this.liveStreamVisible = false;
331
+ this.runState = null;
332
+ this.state.touch();
333
+ });
334
+ this.notify();
335
+ }
336
+ return result;
337
+ }
338
+ /** Host-side row injection (user echo, notices) — single append point. */
339
+ appendDisplayMessage(message) {
340
+ this.transcript = [...this.transcript, message];
341
+ this.state.touch();
342
+ this.notify();
343
+ }
344
+ isRunning() {
345
+ return this.runActive;
346
+ }
347
+ /** A slash command that owns the activity lane without masquerading as an
348
+ * Agent turn. This keeps streaming/steering state separate while still
349
+ * giving Escape and Ctrl+C one cancellation boundary. */
350
+ beginCommandActivity(kind) {
351
+ if (this.runActive || this.commandActivity) {
352
+ throw new Error("Another operation is already running.");
353
+ }
354
+ const abortController = new AbortController();
355
+ this.commandActivity = {
356
+ kind,
357
+ status: "running",
358
+ startedAt: this.deps.ports.clock.now(),
359
+ abortController,
360
+ };
361
+ this.state.touch();
362
+ this.notify();
363
+ return abortController.signal;
364
+ }
365
+ finishCommandActivity(kind, event) {
366
+ if (this.commandActivity?.kind !== kind)
367
+ return;
368
+ this.state.withTransaction(() => {
369
+ if (event)
370
+ this.transcript = [...this.transcript, event];
371
+ this.commandActivity = null;
372
+ this.state.touch();
373
+ });
374
+ this.notify();
375
+ this.taskRuntime?.onIdle();
376
+ }
377
+ getCommandActivity() {
378
+ const activity = this.commandActivity;
379
+ if (!activity)
380
+ return null;
381
+ return {
382
+ kind: activity.kind,
383
+ status: activity.status,
384
+ startedAt: activity.startedAt,
385
+ };
386
+ }
387
+ isBusy() {
388
+ return this.runActive || this.commandActivity !== null;
389
+ }
390
+ /** Queue composer input while a command owns the pane. Grok keeps the
391
+ * composer available during Compact; the message starts after the command
392
+ * reaches its terminal event instead of racing the history rewrite. */
393
+ queueAfterCommand(input) {
394
+ if (!this.commandActivity)
395
+ return false;
396
+ this.queueInput(input);
397
+ return true;
398
+ }
399
+ /** Queue a complete composer payload for the next provider turn. */
400
+ queueInput(input) {
401
+ const payload = typeof input === "string" ? { text: input, images: [] } : input;
402
+ const displayKey = nextDisplayMessageKey("queued");
403
+ this.queue.queued.push({
404
+ payload,
405
+ displayKey,
406
+ sessionFile: this.sessionManager.getSessionFile(),
407
+ });
408
+ this.transcript = [...this.transcript, {
409
+ key: displayKey,
410
+ role: "user",
411
+ content: this.submitDisplayText(payload),
412
+ images: displayImagesFromPayload(payload),
413
+ inputStatus: "queued",
414
+ }];
415
+ this.state.touch();
416
+ this.notify();
417
+ }
418
+ async drainQueuedInputs(cwd, agentOptions = {}) {
419
+ if (this.isBusy())
420
+ return;
421
+ const next = this.queue.queued.shift();
422
+ if (!next || this.disposed)
423
+ return;
424
+ if (next.displayKey) {
425
+ this.transcript = moveStatusMessageToEnd(this.transcript, next.displayKey);
426
+ }
427
+ this.state.touch();
428
+ this.notify();
429
+ await this.runTurn(next.payload, cwd, agentOptions);
430
+ }
431
+ pendingSteerCount() {
432
+ return this.queue.pendingSteers.size;
433
+ }
434
+ queuedInputCount() {
435
+ return this.queue.queued.length;
436
+ }
437
+ submitDisplayText(payload) {
438
+ return formatImageUserDisplayText(payload.displayText ?? payload.text, payload.images.length, payload.imageDisplayStart);
439
+ }
440
+ submitAgentInput(payload) {
441
+ if (payload.images.length === 0)
442
+ return payload.text;
443
+ return payload.displayText
444
+ ? buildImageContentPartsFromDisplayText(payload.displayText, payload.text, payload.images, payload.imageDisplayStart)
445
+ : buildImageContentParts(payload.text, payload.images);
446
+ }
447
+ /** Add input to the current Agent run without creating a second run that
448
+ * would overwrite the shared streaming accumulator. */
449
+ steer(content) {
450
+ if (!this.runActive || !this.activeInputController)
451
+ return false;
452
+ const input = this.activeInputController.enqueue(content);
453
+ const displayKey = nextDisplayMessageKey("steer");
454
+ beginSteer(this.queue, {
455
+ id: input.id,
456
+ content,
457
+ displayKey,
458
+ sessionFile: this.sessionManager.getSessionFile(),
459
+ });
460
+ this.transcript = [...this.transcript, {
461
+ key: displayKey,
462
+ clientId: input.id,
463
+ role: "user",
464
+ content,
465
+ inputStatus: "pending_steer",
466
+ }];
467
+ this.state.touch();
468
+ this.notify();
469
+ return true;
470
+ }
471
+ cancelActiveRun(reason = "Interrupted by user") {
472
+ if (this.activeAbortController && !this.activeAbortController.signal.aborted) {
473
+ this.activeAbortController.abort(Object.assign(new Error(reason), { name: "AgentAbortError" }));
474
+ return true;
475
+ }
476
+ if (this.commandActivity && !this.commandActivity.abortController.signal.aborted) {
477
+ this.commandActivity.status = "cancelling";
478
+ this.commandActivity.abortController.abort(Object.assign(new Error(reason), { name: "AgentAbortError" }));
479
+ this.state.touch();
480
+ this.notify();
481
+ return true;
482
+ }
483
+ return false;
484
+ }
485
+ /**
486
+ * Live streaming tail for the render loop: the in-flight accumulator's
487
+ * content/reasoning/tool summary. Renderers draw it in the live region
488
+ * (never committed to scrollback) until the turn commits.
489
+ */
490
+ getStreamingTail() {
491
+ if (!this.runActive || !this.runState || !this.liveStreamVisible)
492
+ return null;
493
+ const acc = this.runState.accumulator;
494
+ const currentIds = new Set(acc.toolCalls.map((tool) => tool.id));
495
+ const carriedTools = [...this.liveSubagentTools.values()].filter((tool) => !currentIds.has(tool.id));
496
+ const allTools = [...acc.toolCalls, ...carriedTools];
497
+ const parts = snapshotDisplayParts(acc.parts);
498
+ if (carriedTools.length > 0) {
499
+ parts.push({
500
+ type: "tools",
501
+ toolCalls: carriedTools.map((tool) => ({
502
+ ...tool,
503
+ args: { ...tool.args },
504
+ metadata: tool.metadata ? { ...tool.metadata } : undefined,
505
+ })),
506
+ });
507
+ }
508
+ return {
509
+ content: acc.content,
510
+ // Ink kept the current provider turn's reasoning visible even after
511
+ // answer/tool bytes arrived. Never hide it based on another live field.
512
+ reasoning: acc.reasoning,
513
+ // Return a render-safe snapshot: reducer events continue mutating the
514
+ // accumulator in place while the TUI may still hold the previous tail.
515
+ tools: allTools.map((tool) => ({
516
+ ...tool,
517
+ args: { ...tool.args },
518
+ metadata: tool.metadata ? { ...tool.metadata } : undefined,
519
+ })),
520
+ // Parts are the canonical commentary/tool/commentary timeline. Without
521
+ // them a live renderer necessarily moves every tool before all text.
522
+ parts,
523
+ // Phase is provider-turn local, matching Ink's clearAssistantStream().
524
+ // A tool in an earlier committed turn must not suppress fresh Thinking.
525
+ phase: allTools.length > 0 ? "working" : "thinking",
526
+ };
527
+ }
528
+ pendingOverlayCount() {
529
+ return this.overlays.pendingCount();
530
+ }
531
+ /**
532
+ * Drive one agent run: reduce the event stream, apply effects, and finish.
533
+ * Mirrors runAgentInput (app.tsx:1438-1958) minus the rendering.
534
+ */
535
+ async runTurn(input, cwd, options) {
536
+ if (this.disposed)
537
+ throw new Error("controller disposed");
538
+ if (this.commandActivity) {
539
+ throw new Error("A command is already running.");
540
+ }
541
+ if (this.runActive) {
542
+ if (typeof input === "string")
543
+ this.steer(input);
544
+ else if (isSubmitPayload(input)) {
545
+ if (input.images.length === 0)
546
+ this.steer(input.text);
547
+ else
548
+ this.queueInput(input);
549
+ }
550
+ return;
551
+ }
552
+ const agentInput = isSubmitPayload(input) ? this.submitAgentInput(input) : input;
553
+ this.runActive = true;
554
+ this.liveStreamVisible = true;
555
+ this.runState = createRunState(Date.now());
556
+ const inputController = new AgentRunInputQueue(`run-${this.runState.accumulator.runId}`);
557
+ const abortController = new AbortController();
558
+ const goalStatusAtStart = this.goalRuntime?.snapshot()?.status;
559
+ const goalRun = options?.goalRun === true;
560
+ const agentOptions = { ...options };
561
+ delete agentOptions.goalRun;
562
+ if (isSubmitPayload(input) && input.images.length > 0 && input.displayText) {
563
+ agentOptions.userMessageUi = {
564
+ displayText: input.displayText,
565
+ ...(input.imageDisplayStart !== undefined
566
+ ? { imageDisplayStart: input.imageDisplayStart }
567
+ : {}),
568
+ };
569
+ }
570
+ let runUsageTokens = 0;
571
+ let runUsageReported = false;
572
+ let taskResultsForNextTurn = this.taskRuntime?.captureCurrentUndeliveredIds() ?? [];
573
+ this.activeInputController = inputController;
574
+ this.activeAbortController = abortController;
575
+ const upstreamAbort = () => abortController.abort(options?.abortSignal?.reason);
576
+ if (options?.abortSignal?.aborted)
577
+ upstreamAbort();
578
+ else
579
+ options?.abortSignal?.addEventListener("abort", upstreamAbort, { once: true });
580
+ pruneSettledLiveSubagentTools(this.liveSubagentTools);
581
+ this.state.touch();
582
+ this.notify();
583
+ const ctx = {
584
+ external: false,
585
+ isCurrentRun: () => !this.disposed,
586
+ now: () => this.deps.ports.clock.now(),
587
+ runStartedAt: this.deps.ports.clock.now(),
588
+ pendingSteers: this.queue.pendingSteers,
589
+ };
590
+ let runError;
591
+ let cancelled = false;
592
+ try {
593
+ for await (const event of this.deps.agent.run(agentInput, cwd, {
594
+ ...agentOptions,
595
+ abortSignal: abortController.signal,
596
+ inputController,
597
+ })) {
598
+ if (event.type === "turn_start") {
599
+ this.taskRuntime?.markResultsDelivered(taskResultsForNextTurn);
600
+ taskResultsForNextTurn = [];
601
+ }
602
+ if (event.type === "turn_end" && event.usage) {
603
+ runUsageReported = true;
604
+ runUsageTokens += tokenUsageTotal(event.usage);
605
+ }
606
+ const { state, effects } = reduceAgentEvent(this.runState, event, ctx);
607
+ this.runState = state;
608
+ const subagentMetadata = event.type === "tool_update"
609
+ ? event.update.metadata
610
+ : event.type === "tool_end"
611
+ ? event.result.metadata
612
+ : undefined;
613
+ if (subagentMetadata?.kind === "subagent") {
614
+ this.transcript = mergeSubagentSnapshotsIntoMessages(this.transcript, subagentMetadata);
615
+ }
616
+ if (event.type === "tool_update"
617
+ && effects.some((effect) => effect.kind === "live-subagent-changed")) {
618
+ accumulateLiveSubagentUpdate(this.liveSubagentTools, {
619
+ id: event.id,
620
+ name: event.name,
621
+ metadata: event.update.metadata,
622
+ });
623
+ }
624
+ if (event.type === "tool_update" && event.update.childEvent) {
625
+ this.reduceChildEvent(event.update.subAgentId, event.update.childEvent);
626
+ }
627
+ if (event.type === "input_rejected") {
628
+ const steer = this.queue.pendingSteers.get(event.id);
629
+ if (steer) {
630
+ this.queue.pendingSteers.delete(event.id);
631
+ this.queue.queued.push({
632
+ payload: { text: event.content, images: [] },
633
+ displayKey: steer.displayKey,
634
+ sessionFile: steer.sessionFile ?? this.sessionManager.getSessionFile(),
635
+ });
636
+ this.transcript = this.transcript.map((message) => (message.key === steer.displayKey ? setUserInputStatus(message, "queued") : message));
637
+ }
638
+ }
639
+ else if (event.type === "input_applied") {
640
+ this.queue.pendingSteers.delete(event.id);
641
+ }
642
+ // Visibility is an event-boundary concern, not a generic consequence
643
+ // of clearing the accumulator. A provider turn_start/retry must keep
644
+ // the empty waiting spinner mounted; only turn_end hides the live tail
645
+ // in the same transaction that commits its settled replacement.
646
+ if (event.type === "turn_end") {
647
+ if (event.willContinue) {
648
+ taskResultsForNextTurn = this.taskRuntime?.captureCurrentUndeliveredIds() ?? [];
649
+ }
650
+ this.liveStreamVisible = !!event.willContinue;
651
+ }
652
+ else if (event.type === "turn_start"
653
+ || event.type === "text_delta"
654
+ || event.type === "reasoning_delta"
655
+ || event.type === "tool_call_start"
656
+ || event.type === "tool_start") {
657
+ this.liveStreamVisible = true;
658
+ }
659
+ if (event.type === "turn_end") {
660
+ // Commit the provider turn and replace its accumulator in the same
661
+ // observable frame. For a continuation, keep an empty waiting tail;
662
+ // for a final boundary, keep it hidden. Clearing the accumulator at
663
+ // BOTH boundaries is important: Agent still performs cleanup after
664
+ // final turn_end, and an error there must not commit the settled
665
+ // answer a second time from the stale accumulator.
666
+ this.state.withTransaction(() => {
667
+ for (const effect of effects)
668
+ this.applyEffectMutation(effect);
669
+ this.runState = createRunState(this.runState?.accumulator.runId ?? Date.now());
670
+ this.liveStreamVisible = !!event.willContinue;
671
+ this.state.touch();
672
+ });
673
+ this.notify();
674
+ }
675
+ else {
676
+ this.applyEffects(effects);
677
+ }
678
+ const hasDirty = Object.values(this.runState.dirty).some(Boolean);
679
+ // Text/reasoning deltas have no immediate effect notification. Batch
680
+ // them behind one coalesced 40ms paint. Tool events already notified
681
+ // through their effects, so acknowledging their dirty bits here avoids
682
+ // a redundant delayed repaint. Dirty means "paint pending", not
683
+ // "this run has ever changed", and must never remain latched forever.
684
+ if (hasDirty && (event.type === "text_delta" || event.type === "reasoning_delta")) {
685
+ this.deps.ports.flush.scheduleFlush(STREAMING_FLUSH_INTERVAL_MS, () => {
686
+ if (this.disposed || !this.runActive)
687
+ return;
688
+ this.state.touch();
689
+ this.notify();
690
+ });
691
+ }
692
+ if (hasDirty) {
693
+ this.runState = {
694
+ ...this.runState,
695
+ dirty: { content: false, reasoning: false, parts: false, tools: false },
696
+ };
697
+ }
698
+ }
699
+ }
700
+ catch (error) {
701
+ runError = error;
702
+ cancelled = this.isAbortLike(error);
703
+ // Legacy catch (app.tsx:1855-1861): commit the partial answer before
704
+ // surfacing the interrupt/error so streamed content is not lost.
705
+ const partial = this.runState ? buildAssistantMessage(this.runState) : null;
706
+ // The partial commit, error effect, and live-tail removal are one frame.
707
+ // Publishing the partial first used to expose {settled, live:true}, then
708
+ // finally removed the tail in a second frame (visible duplication).
709
+ this.state.withTransaction(() => {
710
+ if (partial)
711
+ this.transcript = [...this.transcript, partial];
712
+ this.liveStreamVisible = false;
713
+ this.applyEffectMutation({ kind: "run-error", error });
714
+ this.state.touch();
715
+ });
716
+ this.notify();
717
+ }
718
+ finally {
719
+ const leftoverSteers = inputController.clear();
720
+ // The signal is the source of truth. A provider may observe cancellation
721
+ // and end its iterator without throwing, but the user's pending steers
722
+ // must still be dropped exactly as they are in the Ink implementation.
723
+ cancelled ||= abortController.signal.aborted;
724
+ for (const leftover of leftoverSteers) {
725
+ const steer = this.queue.pendingSteers.get(leftover.id);
726
+ if (!steer)
727
+ continue;
728
+ this.queue.pendingSteers.delete(leftover.id);
729
+ if (cancelled) {
730
+ this.transcript = this.transcript.filter((message) => message.key !== steer.displayKey);
731
+ }
732
+ else {
733
+ this.queue.queued.push({
734
+ payload: { text: leftover.content, images: [] },
735
+ displayKey: steer.displayKey,
736
+ sessionFile: steer.sessionFile ?? this.sessionManager.getSessionFile(),
737
+ });
738
+ this.transcript = this.transcript.map((message) => (message.key === steer.displayKey ? setUserInputStatus(message, "queued") : message));
739
+ }
740
+ }
741
+ const finish = reduceRunFinish(this.runState ?? createRunState(0), {
742
+ cancelled,
743
+ errored: runError != null && !cancelled,
744
+ // Leftovers were already applied above while their display metadata
745
+ // was still available. Do not ask the reducer to emit a second drain.
746
+ leftoverSteers: [],
747
+ ownsCurrentGeneration: true,
748
+ });
749
+ this.state.withTransaction(() => {
750
+ for (const effect of finish.effects)
751
+ this.applyEffectMutation(effect);
752
+ this.deps.ports.flush.cancelFlush();
753
+ this.runActive = false;
754
+ this.liveStreamVisible = false;
755
+ this.runState = null;
756
+ this.activeInputController = null;
757
+ this.activeAbortController = null;
758
+ if (this.pendingTaskAnnouncements.size > 0) {
759
+ this.transcript = [
760
+ ...this.transcript,
761
+ ...[...this.pendingTaskAnnouncements.values()].map((task) => taskLifecycleDisplayMessage(task, this.deps.processManager?.taskOutputTail(task.id, 12_000))),
762
+ ];
763
+ this.pendingTaskAnnouncements.clear();
764
+ }
765
+ this.state.touch();
766
+ });
767
+ this.notify();
768
+ options?.abortSignal?.removeEventListener("abort", upstreamAbort);
769
+ }
770
+ this.goalRuntime?.afterRun({
771
+ goalRun,
772
+ goalStatusAtStart,
773
+ cancelled,
774
+ errored: runError != null && !cancelled,
775
+ usageTokens: runUsageTokens,
776
+ usageReported: runUsageReported,
777
+ }, cwd);
778
+ this.taskRuntime?.onIdle();
779
+ await this.drainQueuedInputs(cwd, agentOptions);
780
+ }
781
+ /** Apply one reducer transition and publish exactly one observable snapshot. */
782
+ applyEffects(effects) {
783
+ if (effects.length === 0)
784
+ return;
785
+ this.state.withTransaction(() => {
786
+ for (const effect of effects)
787
+ this.applyEffectMutation(effect);
788
+ });
789
+ this.notify();
790
+ }
791
+ /** Mutate for one effect; callers own the surrounding transaction + notify. */
792
+ applyEffectMutation(effect) {
793
+ this.deps.onEffect?.(effect);
794
+ switch (effect.kind) {
795
+ case "stream-cleared":
796
+ this.deps.ports.flush.cancelFlush();
797
+ break;
798
+ case "assistant-committed": {
799
+ const message = this.runState ? buildAssistantMessage(this.runState, effect.taskElapsedMs) : null;
800
+ if (message)
801
+ this.transcript = [...this.transcript, message];
802
+ break;
803
+ }
804
+ case "transcript-append":
805
+ this.transcript = [...this.transcript, effect.message];
806
+ break;
807
+ case "notice":
808
+ this.transcript = [...this.transcript, {
809
+ key: `notice-${this.transcript.length}`,
810
+ role: effect.role === "error" ? "error" : "assistant",
811
+ content: effect.text,
812
+ syntheticKind: effect.role === "error" ? undefined : "ui_notice",
813
+ }];
814
+ break;
815
+ case "queue-updated":
816
+ void effect.pending;
817
+ break;
818
+ case "steer-applied":
819
+ this.transcript = moveStatusMessageToEnd(this.transcript, effect.displayKey);
820
+ break;
821
+ case "steer-requeued":
822
+ // The event loop records the rejected content and queued badge.
823
+ break;
824
+ case "run-error": {
825
+ const message = effect.error instanceof Error ? effect.error.message : String(effect.error);
826
+ if (this.isAbortLike(effect.error)) {
827
+ this.transcript = [...this.transcript, {
828
+ key: nextDisplayMessageKey("interrupt"),
829
+ role: "assistant",
830
+ content: message || "Interrupted by user",
831
+ syntheticKind: "ui_interrupt",
832
+ }];
833
+ }
834
+ else {
835
+ this.transcript = [...this.transcript, {
836
+ key: nextDisplayMessageKey("error"),
837
+ role: "error",
838
+ content: message || "Agent run failed",
839
+ }];
840
+ }
841
+ break;
842
+ }
843
+ default:
844
+ break;
845
+ }
846
+ this.state.touch();
847
+ }
848
+ notify() {
849
+ for (const listener of this.listeners)
850
+ listener(this.state.version);
851
+ }
852
+ reduceChildEvent(agentId, event) {
853
+ const current = this.childRuns.get(agentId) ?? {
854
+ state: createRunState(Date.now()),
855
+ visible: true,
856
+ updatedAt: Date.now(),
857
+ };
858
+ const reduced = reduceAgentEvent(current.state, event, {
859
+ external: false,
860
+ isCurrentRun: () => !this.disposed,
861
+ now: () => this.deps.ports.clock.now(),
862
+ runStartedAt: current.state.accumulator.runId,
863
+ pendingSteers: new Map(),
864
+ });
865
+ let state = reduced.state;
866
+ let visible = current.visible;
867
+ if (event.type === "turn_start")
868
+ visible = true;
869
+ if (event.type === "turn_end") {
870
+ state = createRunState(current.state.accumulator.runId);
871
+ visible = !!event.willContinue;
872
+ }
873
+ this.childRuns.set(agentId, { state, visible, updatedAt: Date.now() });
874
+ }
875
+ isAbortLike(error) {
876
+ return error instanceof Error && (error.name === "AbortError" || error.name === "AgentAbortError");
877
+ }
878
+ /** Drain leftover steers at run end (cancelled drops, normal requeues). */
879
+ drainLeftovers(leftovers, cancelled) {
880
+ drainLeftoverSteers(this.queue, leftovers, { cancelled });
881
+ this.state.touch();
882
+ }
883
+ /** Atomic session switch through the two-phase transaction. */
884
+ switchSession(plan) {
885
+ if (this.isBusy())
886
+ return { ok: false, error: "Stop the current run before switching sessions." };
887
+ const outcome = this.sessionTransition.switchTo(plan);
888
+ if (outcome.ok && outcome.manager) {
889
+ this.goalRuntime?.loadCurrentSession();
890
+ this.taskRuntime?.releaseHeldCompletions(outcome.manager.getSessionFile());
891
+ this.notify();
892
+ }
893
+ return outcome.ok ? { ok: true } : { ok: false, error: outcome.error };
894
+ }
895
+ /** Start an empty native session using the same atomic lifecycle as resume. */
896
+ createFreshSession(cwd, notice) {
897
+ if (this.isBusy())
898
+ return { ok: false, error: "Stop the current run before starting a new session." };
899
+ const outcome = this.sessionTransition.createFresh(cwd, notice);
900
+ if (outcome.ok && outcome.manager) {
901
+ this.goalRuntime?.loadCurrentSession();
902
+ this.taskRuntime?.releaseHeldCompletions(outcome.manager.getSessionFile());
903
+ this.notify();
904
+ }
905
+ return outcome.ok ? { ok: true } : { ok: false, error: outcome.error };
906
+ }
907
+ /**
908
+ * Shutdown: settle overlays, cancel any run, and report the exit summary.
909
+ * Idempotent — a second call is a no-op.
910
+ */
911
+ shutdown(reason) {
912
+ if (this.disposed) {
913
+ return { reason, wallMs: Date.now() - this.startedAtMs };
914
+ }
915
+ this.disposed = true;
916
+ this.cancelActiveRun(reason);
917
+ this.goalRuntime?.dispose();
918
+ this.taskRuntime?.dispose();
919
+ this.overlays.dispose();
920
+ this.backgroundTaskUnsubscribe?.();
921
+ this.deps.ports.flush.cancelFlush();
922
+ return { reason, wallMs: Date.now() - this.startedAtMs };
923
+ }
924
+ }
925
+ function taskLifecycleDisplayMessage(task, output) {
926
+ const status = task.status === "killed"
927
+ ? "cancelled"
928
+ : task.status === "failed"
929
+ ? "failed"
930
+ : "completed";
931
+ const tool = {
932
+ id: `task-lifecycle:${task.id}:${task.endedAt ?? Date.now()}`,
933
+ name: "bash",
934
+ args: {
935
+ command: task.command,
936
+ ...(task.description ? { description: task.description } : {}),
937
+ },
938
+ status,
939
+ isError: task.status === "failed",
940
+ result: output?.trim() ? output : undefined,
941
+ startedAt: task.startedAt,
942
+ metadata: {
943
+ kind: "shell",
944
+ background: true,
945
+ taskId: task.id,
946
+ taskLifecycle: task.status,
947
+ exitCode: task.exitCode ?? null,
948
+ endedAt: task.endedAt,
949
+ outputLines: task.outputLines,
950
+ },
951
+ };
952
+ return {
953
+ key: nextDisplayMessageKey("task"),
954
+ role: "assistant",
955
+ content: "",
956
+ toolCalls: [tool],
957
+ parts: [{ type: "tools", toolCalls: [tool] }],
958
+ };
959
+ }
960
+ function restoredTaskLifecycleMessages(manager) {
961
+ let entries;
962
+ try {
963
+ entries = manager.getEntries();
964
+ }
965
+ catch {
966
+ return [];
967
+ }
968
+ let startIndex = 0;
969
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
970
+ const entry = entries[index];
971
+ if (entry?.type === "marker" && entry.kind === "conversation_clear") {
972
+ startIndex = index + 1;
973
+ break;
974
+ }
975
+ }
976
+ const starts = new Map();
977
+ const completed = new Map();
978
+ for (let index = startIndex; index < entries.length; index += 1) {
979
+ const entry = entries[index];
980
+ if (entry?.type !== "marker" || !entry.value)
981
+ continue;
982
+ let payload;
983
+ try {
984
+ payload = JSON.parse(entry.value);
985
+ }
986
+ catch {
987
+ continue;
988
+ }
989
+ const id = typeof payload.id === "string" ? payload.id : undefined;
990
+ if (!id)
991
+ continue;
992
+ if (entry.kind === "task_started") {
993
+ starts.set(id, payload);
994
+ continue;
995
+ }
996
+ if (entry.kind !== "task_finished" && entry.kind !== "task_killed")
997
+ continue;
998
+ const start = starts.get(id) ?? {};
999
+ const status = entry.kind === "task_killed"
1000
+ ? "killed"
1001
+ : payload.status === "failed"
1002
+ ? "failed"
1003
+ : "completed";
1004
+ const startedAt = numeric(payload.startedAt) ?? numeric(start.startedAt) ?? entry.timestamp;
1005
+ const endedAt = numeric(payload.endedAt) ?? entry.timestamp;
1006
+ completed.set(id, {
1007
+ order: index,
1008
+ task: {
1009
+ kind: "task",
1010
+ id,
1011
+ command: stringValue(payload.command) ?? stringValue(start.command) ?? id,
1012
+ description: stringValue(payload.description) ?? stringValue(start.description),
1013
+ cwd: manager.getMetadata().cwd ?? process.cwd(),
1014
+ pid: numeric(start.pid),
1015
+ status,
1016
+ exitCode: typeof payload.exitCode === "number" ? payload.exitCode : null,
1017
+ startedAt,
1018
+ endedAt,
1019
+ outputTruncated: false,
1020
+ outputLines: numeric(payload.outputLines) ?? 0,
1021
+ ownerSessionId: manager.getSessionFile(),
1022
+ },
1023
+ output: stringValue(payload.output),
1024
+ });
1025
+ }
1026
+ return [...completed.values()]
1027
+ .sort((left, right) => left.order - right.order)
1028
+ .map(({ task, output }) => taskLifecycleDisplayMessage(task, output));
1029
+ }
1030
+ function numeric(value) {
1031
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
1032
+ }
1033
+ function stringValue(value) {
1034
+ return typeof value === "string" && value.length > 0 ? value : undefined;
1035
+ }