@alumbwe/anvil 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (529) hide show
  1. package/README.md +84 -0
  2. package/bin/anvil +2 -0
  3. package/bin/tree-sitter.wasm +0 -0
  4. package/package.json +67 -0
  5. package/src/__tests__/README.md +256 -0
  6. package/src/__tests__/bash-mode.test.ts +442 -0
  7. package/src/__tests__/cli-args.test.ts +181 -0
  8. package/src/__tests__/cli-harness.test.ts +52 -0
  9. package/src/__tests__/e2e/first-time-login.test.ts +173 -0
  10. package/src/__tests__/e2e/logout-relogin-flow.test.ts +110 -0
  11. package/src/__tests__/e2e/returning-user-auth.test.ts +113 -0
  12. package/src/__tests__/e2e-cli.test.ts +183 -0
  13. package/src/__tests__/helpers/launcher-disconnect-fixture.cjs +176 -0
  14. package/src/__tests__/helpers/mock-api-client.ts +64 -0
  15. package/src/__tests__/helpers/renderer-cleanup-fixture.tsx +89 -0
  16. package/src/__tests__/helpers/terminal-watchdog-fixture.ts +75 -0
  17. package/src/__tests__/home-directory-detection.test.ts +213 -0
  18. package/src/__tests__/integration/api-integration.test.ts +288 -0
  19. package/src/__tests__/integration/credentials-storage.test.ts +486 -0
  20. package/src/__tests__/integration/local-agents.test.ts +1187 -0
  21. package/src/__tests__/integration/login-flow-instrumentation.test.ts +134 -0
  22. package/src/__tests__/integration/login-polling-working.test.ts +285 -0
  23. package/src/__tests__/integration/queue-editing.test.tsx +261 -0
  24. package/src/__tests__/integration/usage-refresh-on-completion.test.ts +168 -0
  25. package/src/__tests__/integration-tmux.test.ts +188 -0
  26. package/src/__tests__/launcher-avx2-fallback.test.ts +630 -0
  27. package/src/__tests__/launcher-disconnect.test.ts +104 -0
  28. package/src/__tests__/mocks/hover-toggle-controller.ts +71 -0
  29. package/src/__tests__/open-tui-native-bundle.test.ts +378 -0
  30. package/src/__tests__/path-completion.test.ts +243 -0
  31. package/src/__tests__/project-files-chat-id.test.ts +34 -0
  32. package/src/__tests__/release/proxy-http-get.test.ts +667 -0
  33. package/src/__tests__/release/wrapper-safety.test.ts +412 -0
  34. package/src/__tests__/renderer-cleanup.test.ts +174 -0
  35. package/src/__tests__/rerender-perf.integration.test.ts +283 -0
  36. package/src/__tests__/terminal-reset-sequences.test.ts +36 -0
  37. package/src/__tests__/terminal-watchdog.test.ts +388 -0
  38. package/src/__tests__/test-utils.ts +271 -0
  39. package/src/__tests__/tmux-poc.ts +150 -0
  40. package/src/__tests__/unit/agent-mode-toggle.test.ts +137 -0
  41. package/src/__tests__/unit/copy-button.test.ts +157 -0
  42. package/src/__tests__/unit/create-run-config.test.ts +66 -0
  43. package/src/__tests__/unit/publish-confirmation.test.ts +70 -0
  44. package/src/__tests__/unit/segmented-control.test.ts +152 -0
  45. package/src/__tests__/utils/env.test.ts +169 -0
  46. package/src/__tests__/utils/project-picker.test.ts +39 -0
  47. package/src/agents/bundled-agents.generated.d.ts +14 -0
  48. package/src/agents/bundled-agents.generated.ts +3692 -0
  49. package/src/app.tsx +399 -0
  50. package/src/chat.tsx +1698 -0
  51. package/src/cli-args.ts +134 -0
  52. package/src/commands/__tests__/bash-command.test.ts +445 -0
  53. package/src/commands/__tests__/command-args.test.ts +349 -0
  54. package/src/commands/__tests__/copy-conversation-clipboard.test.ts +108 -0
  55. package/src/commands/__tests__/copy-conversation.test.ts +226 -0
  56. package/src/commands/__tests__/diagnostics-command.test.ts +35 -0
  57. package/src/commands/__tests__/freebuff-command-aliases.test.ts +56 -0
  58. package/src/commands/__tests__/image.test.ts +99 -0
  59. package/src/commands/__tests__/init.test.ts +456 -0
  60. package/src/commands/__tests__/new-command-rotates-chat.test.ts +91 -0
  61. package/src/commands/__tests__/process-diagnostics.test.ts +54 -0
  62. package/src/commands/__tests__/prompt-builders.test.ts +28 -0
  63. package/src/commands/__tests__/router-input.test.ts +298 -0
  64. package/src/commands/ads.ts +44 -0
  65. package/src/commands/command-registry.ts +726 -0
  66. package/src/commands/copy-conversation.ts +392 -0
  67. package/src/commands/help.ts +13 -0
  68. package/src/commands/image.ts +20 -0
  69. package/src/commands/init.ts +121 -0
  70. package/src/commands/process-diagnostics.ts +120 -0
  71. package/src/commands/prompt-builders.ts +100 -0
  72. package/src/commands/publish.ts +203 -0
  73. package/src/commands/router-utils.ts +80 -0
  74. package/src/commands/router.ts +487 -0
  75. package/src/commands/usage.ts +26 -0
  76. package/src/components/__tests__/ad-banner.test.tsx +83 -0
  77. package/src/components/__tests__/chat-input-bar.test.tsx +130 -0
  78. package/src/components/__tests__/freebuff-landing-heading-row.test.tsx +91 -0
  79. package/src/components/__tests__/freebuff-model-selector.test.tsx +519 -0
  80. package/src/components/__tests__/freebuff-offer-invariants.test.ts +54 -0
  81. package/src/components/__tests__/freebuff-takeover-prompt.test.tsx +212 -0
  82. package/src/components/__tests__/grid-layout.integration.test.tsx +304 -0
  83. package/src/components/__tests__/grid-layout.test.tsx +1051 -0
  84. package/src/components/__tests__/message-block.completion.test.tsx +104 -0
  85. package/src/components/__tests__/message-block.streaming.test.tsx +78 -0
  86. package/src/components/__tests__/message-with-agents.test.tsx +579 -0
  87. package/src/components/__tests__/multiline-input.test.tsx +1132 -0
  88. package/src/components/__tests__/queue-panel.test.tsx +398 -0
  89. package/src/components/__tests__/selectable-list.test.ts +232 -0
  90. package/src/components/__tests__/status-bar.test.tsx +44 -0
  91. package/src/components/__tests__/status-indicator.test.tsx +204 -0
  92. package/src/components/__tests__/terminal-thinking-orb.test.tsx +111 -0
  93. package/src/components/__tests__/user-error-banner.test.tsx +102 -0
  94. package/src/components/ad-banner.tsx +323 -0
  95. package/src/components/agent-checklist.tsx +350 -0
  96. package/src/components/agent-mode-toggle.tsx +241 -0
  97. package/src/components/anvil-mark.tsx +52 -0
  98. package/src/components/ask-user/__tests__/multiple-choice-form.test.ts +432 -0
  99. package/src/components/ask-user/__tests__/validation.test.ts +213 -0
  100. package/src/components/ask-user/components/accordion-question.tsx +153 -0
  101. package/src/components/ask-user/components/custom-answer-input.tsx +66 -0
  102. package/src/components/ask-user/components/options-list.tsx +133 -0
  103. package/src/components/ask-user/components/question-header.tsx +80 -0
  104. package/src/components/ask-user/components/question-option.tsx +89 -0
  105. package/src/components/ask-user/constants.ts +38 -0
  106. package/src/components/ask-user/index.tsx +631 -0
  107. package/src/components/ask-user/utils/validation.ts +85 -0
  108. package/src/components/attachment-card.tsx +65 -0
  109. package/src/components/blocks/agent-block-grid.tsx +58 -0
  110. package/src/components/blocks/agent-branch-item.tsx +311 -0
  111. package/src/components/blocks/agent-branch-wrapper.tsx +493 -0
  112. package/src/components/blocks/agent-list-branch.tsx +77 -0
  113. package/src/components/blocks/ask-user-branch.tsx +81 -0
  114. package/src/components/blocks/block-helpers.ts +11 -0
  115. package/src/components/blocks/blocks-renderer.tsx +289 -0
  116. package/src/components/blocks/content-with-markdown.tsx +33 -0
  117. package/src/components/blocks/image-block.tsx +128 -0
  118. package/src/components/blocks/implementor-row.tsx +429 -0
  119. package/src/components/blocks/single-block.tsx +201 -0
  120. package/src/components/blocks/thinking-block.tsx +67 -0
  121. package/src/components/blocks/tool-block-group.tsx +57 -0
  122. package/src/components/blocks/tool-branch.tsx +206 -0
  123. package/src/components/blocks/user-content-copy.tsx +157 -0
  124. package/src/components/bottom-banner.tsx +136 -0
  125. package/src/components/build-mode-buttons.tsx +110 -0
  126. package/src/components/button.tsx +66 -0
  127. package/src/components/chat-header.tsx +77 -0
  128. package/src/components/chat-history-screen.tsx +413 -0
  129. package/src/components/chat-input-bar.tsx +523 -0
  130. package/src/components/clickable-title-box.tsx +66 -0
  131. package/src/components/clickable.tsx +119 -0
  132. package/src/components/collapse-button.tsx +34 -0
  133. package/src/components/copy-button.tsx +203 -0
  134. package/src/components/elapsed-timer.tsx +55 -0
  135. package/src/components/error-boundary.tsx +55 -0
  136. package/src/components/feedback-container.tsx +181 -0
  137. package/src/components/feedback-icon-button.tsx +89 -0
  138. package/src/components/feedback-input-mode.tsx +344 -0
  139. package/src/components/file-attachment-card.tsx +98 -0
  140. package/src/components/freebuff-active-session-summary.tsx +63 -0
  141. package/src/components/freebuff-landing-screen.tsx +865 -0
  142. package/src/components/freebuff-model-selector.tsx +1087 -0
  143. package/src/components/freebuff-referral-banner.tsx +604 -0
  144. package/src/components/freebuff-superseded-screen.tsx +62 -0
  145. package/src/components/grid-layout.tsx +80 -0
  146. package/src/components/help-banner.tsx +127 -0
  147. package/src/components/highlighted-text.tsx +52 -0
  148. package/src/components/image-card.tsx +167 -0
  149. package/src/components/image-thumbnail.tsx +114 -0
  150. package/src/components/input-cursor.tsx +77 -0
  151. package/src/components/input-mode-banner.tsx +57 -0
  152. package/src/components/load-previous-button.tsx +48 -0
  153. package/src/components/login-modal.tsx +475 -0
  154. package/src/components/message-block.tsx +346 -0
  155. package/src/components/message-footer.tsx +258 -0
  156. package/src/components/message-with-agents.tsx +506 -0
  157. package/src/components/mode-divider.tsx +43 -0
  158. package/src/components/multiline-input.tsx +1241 -0
  159. package/src/components/out-of-credits-banner.tsx +160 -0
  160. package/src/components/pending-attachments-banner.tsx +116 -0
  161. package/src/components/pending-bash-message.tsx +59 -0
  162. package/src/components/progress-bar.tsx +81 -0
  163. package/src/components/project-picker-screen.tsx +485 -0
  164. package/src/components/publish-confirmation.tsx +493 -0
  165. package/src/components/publish-container.tsx +687 -0
  166. package/src/components/queue-panel.tsx +310 -0
  167. package/src/components/raised-pill.tsx +95 -0
  168. package/src/components/renderers/plan-box.tsx +59 -0
  169. package/src/components/review-screen.tsx +112 -0
  170. package/src/components/scroll-to-bottom-button.tsx +34 -0
  171. package/src/components/segmented-control.tsx +187 -0
  172. package/src/components/selectable-list.tsx +248 -0
  173. package/src/components/selected-chips.tsx +80 -0
  174. package/src/components/separator.tsx +40 -0
  175. package/src/components/session-ended-banner.tsx +226 -0
  176. package/src/components/shimmer-text.tsx +248 -0
  177. package/src/components/status-bar.tsx +313 -0
  178. package/src/components/subscription-limit-banner.tsx +206 -0
  179. package/src/components/suggested-prompts.tsx +184 -0
  180. package/src/components/suggestion-menu.tsx +215 -0
  181. package/src/components/terminal-command-display.tsx +151 -0
  182. package/src/components/terminal-link.tsx +97 -0
  183. package/src/components/terminal-thinking-orb.tsx +95 -0
  184. package/src/components/text-attachment-card.tsx +77 -0
  185. package/src/components/thinking.tsx +109 -0
  186. package/src/components/tools/__tests__/apply-patch.test.tsx +84 -0
  187. package/src/components/tools/__tests__/code-search.test.tsx +45 -0
  188. package/src/components/tools/__tests__/gravity-index.test.ts +76 -0
  189. package/src/components/tools/__tests__/render-ui.test.tsx +92 -0
  190. package/src/components/tools/__tests__/run-terminal-command.test.ts +233 -0
  191. package/src/components/tools/apply-patch.tsx +95 -0
  192. package/src/components/tools/code-search.tsx +45 -0
  193. package/src/components/tools/composio.tsx +152 -0
  194. package/src/components/tools/diff-viewer.tsx +76 -0
  195. package/src/components/tools/glob.tsx +61 -0
  196. package/src/components/tools/gravity-index.tsx +77 -0
  197. package/src/components/tools/list-directory.tsx +61 -0
  198. package/src/components/tools/read-docs.tsx +37 -0
  199. package/src/components/tools/read-files.tsx +91 -0
  200. package/src/components/tools/read-subtree.tsx +44 -0
  201. package/src/components/tools/read-url.tsx +33 -0
  202. package/src/components/tools/registry.ts +126 -0
  203. package/src/components/tools/render-ui.tsx +122 -0
  204. package/src/components/tools/run-terminal-command.tsx +79 -0
  205. package/src/components/tools/skill.tsx +29 -0
  206. package/src/components/tools/str-replace.tsx +77 -0
  207. package/src/components/tools/suggest-followups.tsx +369 -0
  208. package/src/components/tools/task-completed.tsx +16 -0
  209. package/src/components/tools/tool-call-item.tsx +273 -0
  210. package/src/components/tools/types.ts +55 -0
  211. package/src/components/tools/web-search.tsx +33 -0
  212. package/src/components/tools/write-file.tsx +16 -0
  213. package/src/components/tools/write-todos.tsx +95 -0
  214. package/src/components/top-banner.tsx +178 -0
  215. package/src/components/usage-banner.tsx +244 -0
  216. package/src/components/user-error-banner.tsx +56 -0
  217. package/src/components/validation-error-popover.tsx +192 -0
  218. package/src/contexts/__tests__/chat-runtime-context.test.tsx +149 -0
  219. package/src/contexts/chat-runtime-context.tsx +240 -0
  220. package/src/data/slash-commands.test.ts +24 -0
  221. package/src/data/slash-commands.ts +249 -0
  222. package/src/entry.ts +12 -0
  223. package/src/hooks/__tests__/holds-live-freebuff-slot.test.ts +49 -0
  224. package/src/hooks/__tests__/session-fetch-signal.test.ts +63 -0
  225. package/src/hooks/__tests__/use-activity-query.test.ts +1000 -0
  226. package/src/hooks/__tests__/use-ask-user-bridge.test.ts +176 -0
  227. package/src/hooks/__tests__/use-auth-query.test.ts +21 -0
  228. package/src/hooks/__tests__/use-chat-input-remount.test.tsx +66 -0
  229. package/src/hooks/__tests__/use-clipboard.test.tsx +73 -0
  230. package/src/hooks/__tests__/use-connection-status.test.ts +112 -0
  231. package/src/hooks/__tests__/use-directory-browser.test.ts +203 -0
  232. package/src/hooks/__tests__/use-gravity-ad.test.ts +73 -0
  233. package/src/hooks/__tests__/use-grid-layout.test.ts +349 -0
  234. package/src/hooks/__tests__/use-input-history.test.ts +699 -0
  235. package/src/hooks/__tests__/use-message-queue-editing.test.tsx +209 -0
  236. package/src/hooks/__tests__/use-message-queue.test.tsx +71 -0
  237. package/src/hooks/__tests__/use-path-tab-completion.test.ts +315 -0
  238. package/src/hooks/__tests__/use-queue-controls.test.ts +68 -0
  239. package/src/hooks/__tests__/use-queue-ui.test.tsx +83 -0
  240. package/src/hooks/__tests__/use-searchable-list.test.ts +359 -0
  241. package/src/hooks/__tests__/use-send-message-timer.test.ts +146 -0
  242. package/src/hooks/__tests__/use-suggestion-engine-mention.test.ts +361 -0
  243. package/src/hooks/__tests__/use-suggestion-engine.test.ts +485 -0
  244. package/src/hooks/__tests__/use-terminal-focus.test.ts +66 -0
  245. package/src/hooks/__tests__/use-terminal-layout.test.ts +675 -0
  246. package/src/hooks/__tests__/use-timeout.test.ts +330 -0
  247. package/src/hooks/__tests__/use-usage-query.test.ts +502 -0
  248. package/src/hooks/__tests__/use-user-details-query.test.ts +195 -0
  249. package/src/hooks/helpers/__tests__/send-message.test.ts +1918 -0
  250. package/src/hooks/helpers/send-message.ts +654 -0
  251. package/src/hooks/stream-state.ts +83 -0
  252. package/src/hooks/use-activity-query.ts +668 -0
  253. package/src/hooks/use-agent-validation.ts +74 -0
  254. package/src/hooks/use-ask-user-bridge.ts +92 -0
  255. package/src/hooks/use-auth-query.ts +253 -0
  256. package/src/hooks/use-auth-state.ts +152 -0
  257. package/src/hooks/use-chat-input.ts +105 -0
  258. package/src/hooks/use-chat-keyboard.ts +321 -0
  259. package/src/hooks/use-chat-messages.ts +254 -0
  260. package/src/hooks/use-chat-state.ts +165 -0
  261. package/src/hooks/use-chat-streaming.ts +180 -0
  262. package/src/hooks/use-chat-ui.ts +131 -0
  263. package/src/hooks/use-clipboard.ts +151 -0
  264. package/src/hooks/use-connection-status.ts +142 -0
  265. package/src/hooks/use-directory-browser.ts +88 -0
  266. package/src/hooks/use-elapsed-time.ts +116 -0
  267. package/src/hooks/use-event.ts +34 -0
  268. package/src/hooks/use-exit-handler.ts +66 -0
  269. package/src/hooks/use-fetch-login-url.ts +65 -0
  270. package/src/hooks/use-fingerprint.ts +61 -0
  271. package/src/hooks/use-freebuff-ctrl-c-exit.ts +23 -0
  272. package/src/hooks/use-freebuff-session-progress.ts +34 -0
  273. package/src/hooks/use-freebuff-session.ts +806 -0
  274. package/src/hooks/use-freebuff-streak-query.ts +61 -0
  275. package/src/hooks/use-gravity-ad.ts +656 -0
  276. package/src/hooks/use-grid-layout.ts +73 -0
  277. package/src/hooks/use-input-history.ts +173 -0
  278. package/src/hooks/use-login-keyboard-handlers.ts +75 -0
  279. package/src/hooks/use-login-polling.ts +114 -0
  280. package/src/hooks/use-logo.tsx +161 -0
  281. package/src/hooks/use-message-queue.ts +387 -0
  282. package/src/hooks/use-now.ts +20 -0
  283. package/src/hooks/use-path-tab-completion.ts +88 -0
  284. package/src/hooks/use-publish-mutation.ts +60 -0
  285. package/src/hooks/use-queue-controls.ts +68 -0
  286. package/src/hooks/use-queue-ui.ts +74 -0
  287. package/src/hooks/use-scroll-management.ts +184 -0
  288. package/src/hooks/use-searchable-list.ts +98 -0
  289. package/src/hooks/use-send-message.ts +773 -0
  290. package/src/hooks/use-sheen-animation.tsx +88 -0
  291. package/src/hooks/use-subscription-query.ts +71 -0
  292. package/src/hooks/use-suggestion-engine.ts +783 -0
  293. package/src/hooks/use-terminal-breakpoints.ts +77 -0
  294. package/src/hooks/use-terminal-dimensions.ts +46 -0
  295. package/src/hooks/use-terminal-focus.ts +29 -0
  296. package/src/hooks/use-terminal-layout.ts +189 -0
  297. package/src/hooks/use-theme.tsx +140 -0
  298. package/src/hooks/use-timeout.ts +85 -0
  299. package/src/hooks/use-update-preference.ts +66 -0
  300. package/src/hooks/use-usage-monitor.ts +55 -0
  301. package/src/hooks/use-usage-query.ts +124 -0
  302. package/src/hooks/use-user-details-query.ts +93 -0
  303. package/src/hooks/use-why-did-you-update.ts +177 -0
  304. package/src/index.tsx +441 -0
  305. package/src/init/__tests__/init-direnv.test.ts +526 -0
  306. package/src/init/init-app.ts +34 -0
  307. package/src/init/init-direnv.ts +133 -0
  308. package/src/login/constants.ts +60 -0
  309. package/src/login/login-flow.ts +204 -0
  310. package/src/login/plain-login.ts +108 -0
  311. package/src/login/utils.ts +183 -0
  312. package/src/native/ripgrep.ts +80 -0
  313. package/src/polyfills/bun-strip-ansi.ts +12 -0
  314. package/src/pre-init/tree-sitter-wasm.ts +96 -0
  315. package/src/project-files.ts +102 -0
  316. package/src/smoke/terminal-command-broker.ts +439 -0
  317. package/src/state/__tests__/chat-store-focus.test.ts +22 -0
  318. package/src/state/__tests__/feedback-store.test.ts +257 -0
  319. package/src/state/chat-history-store.ts +30 -0
  320. package/src/state/chat-store.ts +544 -0
  321. package/src/state/feedback-store.ts +160 -0
  322. package/src/state/freebuff-model-store.ts +42 -0
  323. package/src/state/freebuff-session-store.ts +53 -0
  324. package/src/state/login-store.ts +150 -0
  325. package/src/state/message-block-store.ts +149 -0
  326. package/src/state/publish-store.ts +146 -0
  327. package/src/state/queue-panel-store.ts +26 -0
  328. package/src/state/review-store.ts +24 -0
  329. package/src/testing/env.ts +46 -0
  330. package/src/types/chat-state.ts +15 -0
  331. package/src/types/chat.ts +241 -0
  332. package/src/types/contracts/send-message.ts +12 -0
  333. package/src/types/env.ts +98 -0
  334. package/src/types/freebuff-session.ts +17 -0
  335. package/src/types/function-params.ts +28 -0
  336. package/src/types/react19-compat.d.ts +19 -0
  337. package/src/types/store.ts +111 -0
  338. package/src/types/theme-system.ts +144 -0
  339. package/src/types/utils.ts +3 -0
  340. package/src/utils/__tests__/active-run.test.ts +140 -0
  341. package/src/utils/__tests__/activity-tracker.test.ts +440 -0
  342. package/src/utils/__tests__/agent-display.test.ts +139 -0
  343. package/src/utils/__tests__/analytics-client.test.ts +262 -0
  344. package/src/utils/__tests__/anonymous-id.test.ts +74 -0
  345. package/src/utils/__tests__/anvil-api.test.ts +578 -0
  346. package/src/utils/__tests__/arrays.test.ts +264 -0
  347. package/src/utils/__tests__/bash-context-processor.test.ts +38 -0
  348. package/src/utils/__tests__/block-processor.test.ts +810 -0
  349. package/src/utils/__tests__/chat-history.test.ts +203 -0
  350. package/src/utils/__tests__/chat-input-key-intercept.test.ts +40 -0
  351. package/src/utils/__tests__/chat-meta.test.ts +130 -0
  352. package/src/utils/__tests__/clipboard-linux.test.ts +359 -0
  353. package/src/utils/__tests__/clipboard.test.ts +910 -0
  354. package/src/utils/__tests__/code-search-summary.test.ts +84 -0
  355. package/src/utils/__tests__/collapse-helpers.test.ts +1109 -0
  356. package/src/utils/__tests__/error-handling.test.ts +658 -0
  357. package/src/utils/__tests__/exit-cleanly.test.ts +110 -0
  358. package/src/utils/__tests__/feedback-helpers.test.ts +444 -0
  359. package/src/utils/__tests__/feedback-submission.test.ts +26 -0
  360. package/src/utils/__tests__/fetch-usage.test.ts +355 -0
  361. package/src/utils/__tests__/fingerprint.test.ts +144 -0
  362. package/src/utils/__tests__/fixtures/terminal-command-broker-child.ts +7 -0
  363. package/src/utils/__tests__/fixtures/terminal-command-broker-entry.ts +3 -0
  364. package/src/utils/__tests__/fixtures/terminal-command-broker-owner.ts +33 -0
  365. package/src/utils/__tests__/format-elapsed-time.test.ts +103 -0
  366. package/src/utils/__tests__/format-timeout.test.ts +87 -0
  367. package/src/utils/__tests__/freebuff-instance-owner.test.ts +69 -0
  368. package/src/utils/__tests__/freebuff-model-navigation.test.ts +102 -0
  369. package/src/utils/__tests__/freebuff-premium-reset.test.ts +79 -0
  370. package/src/utils/__tests__/freebuff-referral-cache.test.ts +116 -0
  371. package/src/utils/__tests__/freebuff-session-api.test.ts +215 -0
  372. package/src/utils/__tests__/freebuff-session-display.test.ts +23 -0
  373. package/src/utils/__tests__/freebuff-streak-line.test.ts +217 -0
  374. package/src/utils/__tests__/image-dimensions.test.ts +249 -0
  375. package/src/utils/__tests__/image-processor.test.ts +209 -0
  376. package/src/utils/__tests__/implementor-helpers.test.ts +1458 -0
  377. package/src/utils/__tests__/keyboard-actions.test.ts +660 -0
  378. package/src/utils/__tests__/layout-helpers.test.ts +36 -0
  379. package/src/utils/__tests__/lazy-response-ads.test.ts +167 -0
  380. package/src/utils/__tests__/log-shipper.test.ts +53 -0
  381. package/src/utils/__tests__/markdown-renderer.test.tsx +421 -0
  382. package/src/utils/__tests__/message-block-helpers.test.ts +1263 -0
  383. package/src/utils/__tests__/message-updater.test.ts +645 -0
  384. package/src/utils/__tests__/osc-timeout-scenarios.test.ts +172 -0
  385. package/src/utils/__tests__/pending-attachments.test.ts +277 -0
  386. package/src/utils/__tests__/polling-backoff.test.ts +51 -0
  387. package/src/utils/__tests__/queue-panel-actions.test.ts +148 -0
  388. package/src/utils/__tests__/run-state-storage.test.ts +919 -0
  389. package/src/utils/__tests__/safe-json.test.ts +77 -0
  390. package/src/utils/__tests__/sdk-event-handlers.test.ts +685 -0
  391. package/src/utils/__tests__/send-message-helpers.test.ts +1898 -0
  392. package/src/utils/__tests__/send-message-timer.test.ts +64 -0
  393. package/src/utils/__tests__/settings.test.ts +99 -0
  394. package/src/utils/__tests__/strings.test.ts +192 -0
  395. package/src/utils/__tests__/terminal-color-detection.test.ts +425 -0
  396. package/src/utils/__tests__/terminal-command-broker.test.ts +528 -0
  397. package/src/utils/__tests__/terminal-enter-detection.test.ts +77 -0
  398. package/src/utils/__tests__/terminal-io.test.ts +137 -0
  399. package/src/utils/__tests__/terminal-protocol-controller.test.ts +119 -0
  400. package/src/utils/__tests__/text-layout.test.ts +82 -0
  401. package/src/utils/__tests__/theme-platform-detection.test.ts +125 -0
  402. package/src/utils/__tests__/think-tag-parser.test.ts +159 -0
  403. package/src/utils/__tests__/trace-writer.test.ts +223 -0
  404. package/src/utils/__tests__/trim-chat-logs.test.ts +78 -0
  405. package/src/utils/__tests__/usage-banner-state.test.ts +298 -0
  406. package/src/utils/__tests__/validation-error-formatting.test.ts +126 -0
  407. package/src/utils/__tests__/windows-terminal-health.test.ts +101 -0
  408. package/src/utils/__tests__/write-file-atomic.test.ts +127 -0
  409. package/src/utils/active-run.ts +131 -0
  410. package/src/utils/activity-tracker.ts +66 -0
  411. package/src/utils/agent-display.ts +87 -0
  412. package/src/utils/agent-helpers.ts +27 -0
  413. package/src/utils/agent-id-utils.ts +14 -0
  414. package/src/utils/ai-message-id.ts +12 -0
  415. package/src/utils/analytics.ts +361 -0
  416. package/src/utils/anonymous-id.ts +87 -0
  417. package/src/utils/anvil-api.ts +612 -0
  418. package/src/utils/anvil-client.ts +217 -0
  419. package/src/utils/arrays.ts +117 -0
  420. package/src/utils/auth.ts +258 -0
  421. package/src/utils/bash-context-processor.ts +47 -0
  422. package/src/utils/bash-messages.ts +108 -0
  423. package/src/utils/block-margins.ts +35 -0
  424. package/src/utils/block-operations.ts +523 -0
  425. package/src/utils/block-processor.ts +213 -0
  426. package/src/utils/chat-history.ts +253 -0
  427. package/src/utils/chat-input-key-intercept.ts +52 -0
  428. package/src/utils/chat-meta.ts +90 -0
  429. package/src/utils/chat-scroll-accel.ts +151 -0
  430. package/src/utils/clipboard-image.ts +588 -0
  431. package/src/utils/clipboard.ts +397 -0
  432. package/src/utils/code-search-summary.ts +70 -0
  433. package/src/utils/collapse-helpers.ts +256 -0
  434. package/src/utils/config-dir.ts +24 -0
  435. package/src/utils/constants.ts +190 -0
  436. package/src/utils/create-event-handler-state.ts +73 -0
  437. package/src/utils/create-run-config.ts +121 -0
  438. package/src/utils/detect-shell.ts +116 -0
  439. package/src/utils/directory-browser.ts +72 -0
  440. package/src/utils/engagement.ts +55 -0
  441. package/src/utils/env.ts +92 -0
  442. package/src/utils/error-handling.ts +255 -0
  443. package/src/utils/error-messages.ts +62 -0
  444. package/src/utils/exit-cleanly.ts +86 -0
  445. package/src/utils/feedback-helpers.ts +103 -0
  446. package/src/utils/feedback-submission.ts +22 -0
  447. package/src/utils/fetch-usage.ts +76 -0
  448. package/src/utils/fingerprint.ts +245 -0
  449. package/src/utils/format-elapsed-time.ts +33 -0
  450. package/src/utils/format-session-units.ts +6 -0
  451. package/src/utils/format-timeout.ts +28 -0
  452. package/src/utils/format-validation-errors-for-message.ts +84 -0
  453. package/src/utils/freebuff-agent-selection.ts +33 -0
  454. package/src/utils/freebuff-instance-owner.ts +66 -0
  455. package/src/utils/freebuff-model-navigation.ts +50 -0
  456. package/src/utils/freebuff-premium-reset.ts +55 -0
  457. package/src/utils/freebuff-referral-cache.ts +51 -0
  458. package/src/utils/freebuff-session-api.ts +243 -0
  459. package/src/utils/freebuff-session-display.ts +21 -0
  460. package/src/utils/freebuff-streak-line.ts +93 -0
  461. package/src/utils/git.ts +17 -0
  462. package/src/utils/helpers.ts +47 -0
  463. package/src/utils/image-display.ts +67 -0
  464. package/src/utils/image-handler.ts +325 -0
  465. package/src/utils/image-processor.ts +133 -0
  466. package/src/utils/image-thumbnail.ts +78 -0
  467. package/src/utils/implementor-helpers.ts +787 -0
  468. package/src/utils/input-modes.ts +174 -0
  469. package/src/utils/keyboard-actions.ts +392 -0
  470. package/src/utils/keypad-keys.ts +47 -0
  471. package/src/utils/layout-helpers.ts +33 -0
  472. package/src/utils/lazy-response-ads.ts +1 -0
  473. package/src/utils/local-agent-registry.ts +474 -0
  474. package/src/utils/log-shipper.ts +126 -0
  475. package/src/utils/logger.ts +327 -0
  476. package/src/utils/markdown-renderer.tsx +1090 -0
  477. package/src/utils/math.ts +3 -0
  478. package/src/utils/message-block-helpers.ts +684 -0
  479. package/src/utils/message-history.ts +143 -0
  480. package/src/utils/message-tree-utils.ts +67 -0
  481. package/src/utils/message-updater.ts +279 -0
  482. package/src/utils/open-file.ts +146 -0
  483. package/src/utils/open-url.ts +63 -0
  484. package/src/utils/path-completion.ts +111 -0
  485. package/src/utils/path-helpers.ts +38 -0
  486. package/src/utils/pending-attachments.ts +360 -0
  487. package/src/utils/polling-backoff.ts +59 -0
  488. package/src/utils/project-picker.ts +12 -0
  489. package/src/utils/queue-panel-actions.ts +70 -0
  490. package/src/utils/recent-projects.ts +156 -0
  491. package/src/utils/renderer-cleanup.ts +233 -0
  492. package/src/utils/response-ad-positions.ts +1 -0
  493. package/src/utils/run-state-storage.ts +603 -0
  494. package/src/utils/safe-json.ts +153 -0
  495. package/src/utils/sdk-event-handlers.ts +571 -0
  496. package/src/utils/send-message-helpers.ts +210 -0
  497. package/src/utils/send-message-timer.ts +110 -0
  498. package/src/utils/settings.ts +226 -0
  499. package/src/utils/skill-registry.ts +94 -0
  500. package/src/utils/spawn-agent-matcher.ts +55 -0
  501. package/src/utils/status-indicator-state.ts +107 -0
  502. package/src/utils/stream-chunk-processor.ts +57 -0
  503. package/src/utils/strings.ts +271 -0
  504. package/src/utils/subscription.ts +31 -0
  505. package/src/utils/syntax-highlighter.tsx +19 -0
  506. package/src/utils/terminal-color-detection.ts +474 -0
  507. package/src/utils/terminal-command-broker.ts +467 -0
  508. package/src/utils/terminal-enter-detection.ts +63 -0
  509. package/src/utils/terminal-images.ts +229 -0
  510. package/src/utils/terminal-io.ts +85 -0
  511. package/src/utils/terminal-protocol-controller.ts +173 -0
  512. package/src/utils/terminal-reset-sequences.ts +33 -0
  513. package/src/utils/terminal-title.ts +84 -0
  514. package/src/utils/terminal-watchdog.ts +366 -0
  515. package/src/utils/text-layout.ts +149 -0
  516. package/src/utils/theme-config.ts +141 -0
  517. package/src/utils/theme-system.ts +1206 -0
  518. package/src/utils/think-tag-parser.ts +95 -0
  519. package/src/utils/time-format.ts +21 -0
  520. package/src/utils/trace-writer.ts +162 -0
  521. package/src/utils/ui-constants.ts +69 -0
  522. package/src/utils/update-checker.ts +110 -0
  523. package/src/utils/usage-banner-state.ts +146 -0
  524. package/src/utils/validation-error-formatting.ts +86 -0
  525. package/src/utils/validation-error-helpers.ts +19 -0
  526. package/src/utils/windows-terminal-health.ts +124 -0
  527. package/src/utils/word-wrap-utils.ts +53 -0
  528. package/src/utils/write-file-atomic.ts +54 -0
  529. package/src/utils/yield-to-event-loop.ts +9 -0
@@ -0,0 +1,108 @@
1
+ import { formatTimestamp } from './helpers'
2
+
3
+ import type { PendingBashMessage } from '../types/store'
4
+ import type { ChatMessage, ContentBlock } from '../types/chat'
5
+ import type { ToolResultOutput } from '@anvil/common/types/messages/content-part'
6
+
7
+ export function createRunTerminalToolResult(params: {
8
+ command: string
9
+ cwd: string
10
+ stdout: string | null
11
+ stderr: string | null
12
+ exitCode: number
13
+ errorMessage?: string
14
+ }): ToolResultOutput[] {
15
+ const { command, cwd, stdout, stderr, exitCode, errorMessage } = params
16
+ if (errorMessage) {
17
+ return [
18
+ {
19
+ type: 'json' as const,
20
+ value: { command, startingCwd: cwd, errorMessage },
21
+ },
22
+ ]
23
+ }
24
+ return [
25
+ {
26
+ type: 'json' as const,
27
+ value: {
28
+ command,
29
+ startingCwd: cwd,
30
+ stdout: stdout || null,
31
+ stderr: stderr || null,
32
+ exitCode,
33
+ },
34
+ },
35
+ ]
36
+ }
37
+
38
+ export function buildBashHistoryMessages(params: {
39
+ command: string
40
+ cwd: string
41
+ toolCallId?: string
42
+ output?: string
43
+ isComplete?: boolean
44
+ }): {
45
+ assistantMessage: ChatMessage
46
+ toolCallId: string
47
+ } {
48
+ const { command, cwd, output = '...', isComplete = false } = params
49
+ const toolCallId = params.toolCallId ?? crypto.randomUUID()
50
+
51
+ const toolBlock: ContentBlock = {
52
+ type: 'tool',
53
+ toolName: 'run_terminal_command',
54
+ toolCallId,
55
+ input: { command },
56
+ output,
57
+ }
58
+
59
+ const assistantMessage: ChatMessage = {
60
+ id: `bash-result-${toolCallId}`,
61
+ variant: 'ai',
62
+ content: '',
63
+ blocks: [toolBlock],
64
+ timestamp: formatTimestamp(),
65
+ isComplete,
66
+ metadata: { bashCwd: cwd },
67
+ }
68
+
69
+ return { assistantMessage, toolCallId }
70
+ }
71
+
72
+ /**
73
+ * Format pending bash messages as context to prepend to the user's prompt.
74
+ * This provides the LLM with information about commands the user ran manually.
75
+ * Only includes completed (non-running) commands.
76
+ */
77
+ export function formatBashContextForPrompt(
78
+ pendingBashMessages: PendingBashMessage[],
79
+ ): string {
80
+ // Only include completed commands
81
+ const completedCommands = pendingBashMessages.filter((msg) => !msg.isRunning)
82
+
83
+ if (completedCommands.length === 0) {
84
+ return ''
85
+ }
86
+
87
+ const commandsContext = completedCommands
88
+ .map((bash) => {
89
+ const cwd = bash.cwd || process.cwd()
90
+ let result = `Command: ${bash.command}\nDirectory: ${cwd}\nExit code: ${bash.exitCode}`
91
+ if (bash.stdout) {
92
+ result += `\nStdout:\n${bash.stdout}`
93
+ }
94
+ if (bash.stderr) {
95
+ result += `\nStderr:\n${bash.stderr}`
96
+ }
97
+ return result
98
+ })
99
+ .join('\n\n---\n\n')
100
+
101
+ return `<user_terminal_commands>
102
+ The user ran the following terminal command(s) before this message:
103
+
104
+ ${commandsContext}
105
+ </user_terminal_commands>
106
+
107
+ `
108
+ }
@@ -0,0 +1,35 @@
1
+ import type { ContentBlock, TextContentBlock } from '../types/chat'
2
+
3
+ /**
4
+ * Margin calculation result for a content block.
5
+ */
6
+ export interface BlockMargins {
7
+ marginTop: number
8
+ marginBottom: number
9
+ }
10
+
11
+ /** Extracts margins for a text block, suppressing top margin after tool/agent blocks. */
12
+ export function extractTextBlockMargins(
13
+ block: TextContentBlock,
14
+ prevBlock: ContentBlock | null,
15
+ ): BlockMargins {
16
+ const prevBlockSuppressesMargin =
17
+ prevBlock !== null &&
18
+ (prevBlock.type === 'tool' || prevBlock.type === 'agent')
19
+
20
+ const marginTop = prevBlockSuppressesMargin ? 0 : (block.marginTop ?? 0)
21
+ const marginBottom = block.marginBottom ?? 0
22
+
23
+ return { marginTop, marginBottom }
24
+ }
25
+
26
+ /** Extracts margins for an HTML block using explicit values without context adjustments. */
27
+ export function extractHtmlBlockMargins(block: {
28
+ marginTop?: number
29
+ marginBottom?: number
30
+ }): BlockMargins {
31
+ return {
32
+ marginTop: block.marginTop ?? 0,
33
+ marginBottom: block.marginBottom ?? 0,
34
+ }
35
+ }
@@ -0,0 +1,523 @@
1
+ import { updateBlocksRecursively } from './message-block-helpers'
2
+ import {
3
+ parseThinkTags,
4
+ getPartialTagLength,
5
+ THINK_OPEN_TAG,
6
+ THINK_CLOSE_TAG,
7
+ } from './think-tag-parser'
8
+
9
+ import type {
10
+ ContentBlock,
11
+ ToolContentBlock,
12
+ TextContentBlock,
13
+ } from '../types/chat'
14
+
15
+ let thinkingIdCounter = 0
16
+ const generateThinkingId = (): string => {
17
+ thinkingIdCounter++
18
+ return `thinking-${thinkingIdCounter}`
19
+ }
20
+
21
+ type AgentTextUpdate =
22
+ | { type: 'text'; mode: 'append'; content: string; textType: 'text' | 'reasoning' }
23
+ | { type: 'text'; mode: 'replace'; content: string }
24
+
25
+ const updateAgentText = (
26
+ blocks: ContentBlock[],
27
+ agentId: string,
28
+ update: AgentTextUpdate,
29
+ ) => {
30
+ return updateBlocksRecursively(blocks, agentId, (block) => {
31
+ if (block.type !== 'agent') {
32
+ return block
33
+ }
34
+
35
+ const agentBlocks = block.blocks ? [...block.blocks] : []
36
+ const text = update.content ?? ''
37
+
38
+ if (update.mode === 'replace') {
39
+ const updatedBlocks = [...agentBlocks]
40
+ let replaced = false
41
+
42
+ for (let i = updatedBlocks.length - 1; i >= 0; i--) {
43
+ const entry = updatedBlocks[i]
44
+ if (entry.type === 'text') {
45
+ replaced = true
46
+ if (entry.content === text && block.content === text) {
47
+ return block
48
+ }
49
+ updatedBlocks[i] = { ...entry, content: text }
50
+ break
51
+ }
52
+ }
53
+
54
+ if (!replaced) {
55
+ updatedBlocks.push({ type: 'text', content: text })
56
+ }
57
+
58
+ return {
59
+ ...block,
60
+ content: text,
61
+ blocks: updatedBlocks,
62
+ }
63
+ }
64
+
65
+ if (!text) {
66
+ return block
67
+ }
68
+
69
+ // Handle native reasoning chunks for agent blocks
70
+ if (update.textType === 'reasoning') {
71
+ const updatedAgentBlocks = appendNativeReasoningToBlocks(agentBlocks, text)
72
+ const updatedContent = (block.content ?? '') + text
73
+ return {
74
+ ...block,
75
+ content: updatedContent,
76
+ blocks: updatedAgentBlocks,
77
+ }
78
+ }
79
+
80
+ // For regular text: first close any open native reasoning block, then use think tag parsing
81
+ const blocksWithClosedReasoning = closeNativeReasoningBlock(agentBlocks)
82
+ const updatedAgentBlocks = appendTextWithThinkParsingToBlocks(
83
+ blocksWithClosedReasoning,
84
+ text,
85
+ )
86
+ const updatedContent = (block.content ?? '') + text
87
+ return {
88
+ ...block,
89
+ content: updatedContent,
90
+ blocks: updatedAgentBlocks,
91
+ }
92
+ })
93
+ }
94
+
95
+ /**
96
+ * Check if a text block represents an open (unclosed) thinking block.
97
+ */
98
+ const isOpenThinkingBlock = (block: ContentBlock | undefined): boolean => {
99
+ if (!block || block.type !== 'text') {
100
+ return false
101
+ }
102
+ return block.textType === 'reasoning' && block.thinkingOpen === true
103
+ }
104
+
105
+ /**
106
+ * Creates a new reasoning (thinking) text block.
107
+ */
108
+ const createReasoningBlock = (
109
+ content: string,
110
+ thinkingOpen: boolean,
111
+ thinkingId: string,
112
+ ): TextContentBlock => ({
113
+ type: 'text',
114
+ content,
115
+ textType: 'reasoning',
116
+ thinkingCollapseState: 'preview',
117
+ thinkingOpen,
118
+ thinkingId,
119
+ })
120
+
121
+ /**
122
+ * Creates a new regular text block.
123
+ */
124
+ const createTextBlock = (content: string): TextContentBlock => ({
125
+ type: 'text',
126
+ content,
127
+ textType: 'text',
128
+ })
129
+
130
+ /**
131
+ * Shared logic for appending text with think tag parsing.
132
+ * Used by both root stream and agent blocks.
133
+ */
134
+ const appendTextWithThinkParsingToBlocks = (
135
+ blocks: ContentBlock[],
136
+ text: string,
137
+ ): ContentBlock[] => {
138
+ if (!text) {
139
+ return blocks
140
+ }
141
+
142
+ const nextBlocks = [...blocks]
143
+ const lastBlock = nextBlocks[nextBlocks.length - 1]
144
+ const wasInsideThinking = isOpenThinkingBlock(lastBlock)
145
+
146
+ let textToParse = text
147
+ let lastBlockContent = ''
148
+
149
+ if (wasInsideThinking && lastBlock?.type === 'text') {
150
+ lastBlockContent = lastBlock.content
151
+
152
+ const partialLen = getPartialTagLength(lastBlockContent)
153
+ if (partialLen > 0) {
154
+ const potentialTag = lastBlockContent.slice(-partialLen) + text
155
+ if (potentialTag.startsWith(THINK_CLOSE_TAG)) {
156
+ const newLastContent = lastBlockContent.slice(0, -partialLen)
157
+ textToParse = lastBlockContent.slice(-partialLen) + text
158
+
159
+ if (newLastContent) {
160
+ nextBlocks[nextBlocks.length - 1] = {
161
+ ...lastBlock,
162
+ content: newLastContent,
163
+ }
164
+ } else {
165
+ nextBlocks.pop()
166
+ }
167
+ }
168
+ }
169
+ } else if (
170
+ !wasInsideThinking &&
171
+ lastBlock?.type === 'text' &&
172
+ lastBlock.textType === 'text'
173
+ ) {
174
+ lastBlockContent = lastBlock.content
175
+ const partialLen = getPartialTagLength(lastBlockContent)
176
+ if (partialLen > 0) {
177
+ const potentialTag = lastBlockContent.slice(-partialLen) + text
178
+ if (potentialTag.startsWith(THINK_OPEN_TAG)) {
179
+ const newLastContent = lastBlockContent.slice(0, -partialLen)
180
+ textToParse = lastBlockContent.slice(-partialLen) + text
181
+
182
+ if (newLastContent) {
183
+ nextBlocks[nextBlocks.length - 1] = {
184
+ ...lastBlock,
185
+ content: newLastContent,
186
+ }
187
+ } else {
188
+ nextBlocks.pop()
189
+ }
190
+ }
191
+ }
192
+ }
193
+
194
+ const currentLastBlock = nextBlocks[nextBlocks.length - 1]
195
+ const insideThinking = isOpenThinkingBlock(currentLastBlock)
196
+
197
+ if (insideThinking && !textToParse.includes('<')) {
198
+ if (currentLastBlock?.type === 'text') {
199
+ nextBlocks[nextBlocks.length - 1] = {
200
+ ...currentLastBlock,
201
+ content: currentLastBlock.content + textToParse,
202
+ }
203
+ return nextBlocks
204
+ }
205
+ }
206
+
207
+ if (!insideThinking && !textToParse.includes('<')) {
208
+ if (
209
+ currentLastBlock?.type === 'text' &&
210
+ currentLastBlock.textType === 'text'
211
+ ) {
212
+ nextBlocks[nextBlocks.length - 1] = {
213
+ ...currentLastBlock,
214
+ content: currentLastBlock.content + textToParse,
215
+ }
216
+ return nextBlocks
217
+ }
218
+ return [...nextBlocks, createTextBlock(textToParse)]
219
+ }
220
+
221
+ const fullText = insideThinking ? THINK_OPEN_TAG + textToParse : textToParse
222
+
223
+ const segments = parseThinkTags(fullText)
224
+
225
+ let segmentStartIdx = 0
226
+ if (
227
+ insideThinking &&
228
+ segments.length > 0 &&
229
+ segments[0].type === 'thinking'
230
+ ) {
231
+ const firstSegment = segments[0]
232
+ if (currentLastBlock?.type === 'text') {
233
+ const hasMoreSegments = segments.length > 1
234
+ const thinkingOpen =
235
+ !hasMoreSegments && !textToParse.includes(THINK_CLOSE_TAG)
236
+
237
+ nextBlocks[nextBlocks.length - 1] = {
238
+ ...currentLastBlock,
239
+ content: currentLastBlock.content + firstSegment.content,
240
+ thinkingOpen,
241
+ }
242
+ }
243
+ segmentStartIdx = 1
244
+ } else if (insideThinking && textToParse.includes(THINK_CLOSE_TAG)) {
245
+ // Handle case where we're inside thinking and receive </think> with no content
246
+ // (e.g., just "</think>" or "</think>text"). In this case parseThinkTags returns
247
+ // empty or starts with text, but we still need to close the thinking block.
248
+ if (currentLastBlock?.type === 'text') {
249
+ nextBlocks[nextBlocks.length - 1] = {
250
+ ...currentLastBlock,
251
+ thinkingOpen: false,
252
+ }
253
+ }
254
+ }
255
+
256
+ for (let i = segmentStartIdx; i < segments.length; i++) {
257
+ const segment = segments[i]
258
+ const isLastSegment = i === segments.length - 1
259
+
260
+ if (segment.type === 'thinking') {
261
+ const thinkingOpen =
262
+ isLastSegment && !textToParse.endsWith(THINK_CLOSE_TAG)
263
+ if (thinkingOpen) {
264
+ nextBlocks.push(createReasoningBlock(segment.content, thinkingOpen, generateThinkingId()))
265
+ }
266
+ } else {
267
+ const prevBlock = nextBlocks[nextBlocks.length - 1]
268
+ if (
269
+ prevBlock?.type === 'text' &&
270
+ prevBlock.textType === 'text' &&
271
+ !prevBlock.thinkingOpen
272
+ ) {
273
+ nextBlocks[nextBlocks.length - 1] = {
274
+ ...prevBlock,
275
+ content: prevBlock.content + segment.content,
276
+ }
277
+ } else {
278
+ nextBlocks.push(createTextBlock(segment.content))
279
+ }
280
+ }
281
+ }
282
+
283
+ return nextBlocks
284
+ }
285
+
286
+ /**
287
+ * Appends native reasoning content to blocks array (for agent blocks).
288
+ * Similar to how appendTextToRootStream handles reasoning for root.
289
+ */
290
+ const appendNativeReasoningToBlocks = (
291
+ blocks: ContentBlock[],
292
+ text: string,
293
+ ): ContentBlock[] => {
294
+ if (!text) {
295
+ return blocks
296
+ }
297
+
298
+ const nextBlocks = [...blocks]
299
+ const lastBlock = nextBlocks[nextBlocks.length - 1]
300
+
301
+ // If last block is already an open native reasoning block, append to it
302
+ // Only append if it's a native reasoning block (thinkingOpen === undefined),
303
+ // not a closed one or a <think> tag block
304
+ if (isNativeReasoningBlock(lastBlock) && lastBlock.type === 'text') {
305
+ const updatedBlock: ContentBlock = {
306
+ ...lastBlock,
307
+ content: lastBlock.content + text,
308
+ }
309
+ nextBlocks[nextBlocks.length - 1] = updatedBlock
310
+ return nextBlocks
311
+ }
312
+
313
+ // Create a new native reasoning block
314
+ const newBlock: ContentBlock = {
315
+ type: 'text',
316
+ content: text,
317
+ textType: 'reasoning',
318
+ thinkingCollapseState: 'preview',
319
+ thinkingId: generateThinkingId(),
320
+ }
321
+
322
+ return [...nextBlocks, newBlock]
323
+ }
324
+
325
+ /**
326
+ * Checks if a block is a native reasoning block (not from <think> tags).
327
+ * Native reasoning blocks have textType === 'reasoning' but thinkingOpen === undefined.
328
+ */
329
+ export const isNativeReasoningBlock = (block: ContentBlock | undefined): boolean => {
330
+ if (!block || block.type !== 'text') {
331
+ return false
332
+ }
333
+ return block.textType === 'reasoning' && block.thinkingOpen === undefined
334
+ }
335
+
336
+ /**
337
+ * Closes native reasoning blocks within a specific agent's blocks.
338
+ * Used when a tool call happens for a subagent.
339
+ */
340
+ export const closeNativeReasoningInAgent = (
341
+ blocks: ContentBlock[],
342
+ agentId: string,
343
+ ): ContentBlock[] => {
344
+ return updateBlocksRecursively(blocks, agentId, (block) => {
345
+ if (block.type !== 'agent') {
346
+ return block
347
+ }
348
+ const closedBlocks = block.blocks ? closeNativeReasoningBlock(block.blocks) : undefined
349
+ if (closedBlocks && closedBlocks !== block.blocks) {
350
+ return { ...block, blocks: closedBlocks }
351
+ }
352
+ return block
353
+ })
354
+ }
355
+
356
+ /**
357
+ * Marks the last native reasoning block as complete by setting thinkingOpen: false.
358
+ * This triggers the UI to collapse the thinking block.
359
+ *
360
+ * Note: We search backwards through all blocks because agent/tool blocks may have
361
+ * been added after the reasoning block but before text output starts.
362
+ */
363
+ export const closeNativeReasoningBlock = (
364
+ blocks: ContentBlock[],
365
+ ): ContentBlock[] => {
366
+ // Find the last native reasoning block (not just the last block)
367
+ let lastReasoningIndex = -1
368
+ for (let i = blocks.length - 1; i >= 0; i--) {
369
+ if (isNativeReasoningBlock(blocks[i])) {
370
+ lastReasoningIndex = i
371
+ break
372
+ }
373
+ }
374
+
375
+ if (lastReasoningIndex === -1) {
376
+ return blocks
377
+ }
378
+
379
+ const reasoningBlock = blocks[lastReasoningIndex]
380
+ if (reasoningBlock.type !== 'text') {
381
+ return blocks
382
+ }
383
+
384
+ const nextBlocks = [...blocks]
385
+ nextBlocks[lastReasoningIndex] = {
386
+ ...reasoningBlock,
387
+ thinkingOpen: false,
388
+ }
389
+ return nextBlocks
390
+ }
391
+
392
+ export const appendTextToRootStream = (
393
+ blocks: ContentBlock[],
394
+ delta: { type: 'text' | 'reasoning'; text: string },
395
+ ) => {
396
+ if (!delta.text) {
397
+ return blocks
398
+ }
399
+
400
+ // For reasoning type (from native reasoning_chunk events), use original behavior
401
+ if (delta.type === 'reasoning') {
402
+ const nextBlocks = [...blocks]
403
+ const lastBlock = nextBlocks[nextBlocks.length - 1]
404
+
405
+ if (
406
+ lastBlock &&
407
+ lastBlock.type === 'text' &&
408
+ lastBlock.textType === 'reasoning'
409
+ ) {
410
+ const updatedBlock: ContentBlock = {
411
+ ...lastBlock,
412
+ content: lastBlock.content + delta.text,
413
+ }
414
+ nextBlocks[nextBlocks.length - 1] = updatedBlock
415
+ return nextBlocks
416
+ }
417
+
418
+ const newBlock: ContentBlock = {
419
+ type: 'text',
420
+ content: delta.text,
421
+ textType: 'reasoning',
422
+ thinkingCollapseState: 'preview',
423
+ thinkingId: generateThinkingId(),
424
+ }
425
+
426
+ return [...nextBlocks, newBlock]
427
+ }
428
+
429
+ // For text type: first close any open native reasoning block, then parse for <think> tags
430
+ const blocksWithClosedReasoning = closeNativeReasoningBlock(blocks)
431
+ return appendTextWithThinkParsingToBlocks(blocksWithClosedReasoning, delta.text)
432
+ }
433
+
434
+ export const appendTextToAgentBlock = (
435
+ blocks: ContentBlock[],
436
+ agentId: string,
437
+ text: string,
438
+ textType: 'text' | 'reasoning' = 'text',
439
+ ) =>
440
+ updateAgentText(blocks, agentId, {
441
+ type: 'text',
442
+ mode: 'append',
443
+ content: text,
444
+ textType,
445
+ })
446
+
447
+ export const replaceTextInAgentBlock = (
448
+ blocks: ContentBlock[],
449
+ agentId: string,
450
+ text: string,
451
+ ) =>
452
+ updateAgentText(blocks, agentId, {
453
+ type: 'text',
454
+ mode: 'replace',
455
+ content: text,
456
+ })
457
+
458
+ export const appendToolToAgentBlock = (
459
+ blocks: ContentBlock[],
460
+ agentId: string,
461
+ toolBlock: ToolContentBlock,
462
+ ) =>
463
+ updateBlocksRecursively(blocks, agentId, (block) => {
464
+ if (block.type !== 'agent') {
465
+ return block
466
+ }
467
+ // Close any open native reasoning blocks before adding the tool
468
+ const agentBlocks = block.blocks ? closeNativeReasoningBlock([...block.blocks]) : []
469
+ return { ...block, blocks: [...agentBlocks, toolBlock] }
470
+ })
471
+
472
+ export const markAgentComplete = (blocks: ContentBlock[], agentId: string) =>
473
+ updateBlocksRecursively(blocks, agentId, (block) => {
474
+ if (block.type !== 'agent') {
475
+ return block
476
+ }
477
+ // Close any open native reasoning blocks when the agent completes
478
+ const closedBlocks = block.blocks ? closeNativeReasoningBlock(block.blocks) : undefined
479
+ return {
480
+ ...block,
481
+ status: 'complete' as const,
482
+ ...(closedBlocks && { blocks: closedBlocks }),
483
+ }
484
+ })
485
+
486
+ /**
487
+ * Recursively marks all agent blocks with status 'running' as 'cancelled'.
488
+ * Used when the user interrupts a response to indicate subagents were stopped.
489
+ * Also closes any open native reasoning blocks so they don't appear "streaming".
490
+ */
491
+ export const markRunningAgentsAsCancelled = (
492
+ blocks: ContentBlock[],
493
+ ): ContentBlock[] => {
494
+ return blocks.map((block) => {
495
+ if (block.type !== 'agent') {
496
+ return block
497
+ }
498
+
499
+ // First recursively process nested agents, then close any reasoning blocks
500
+ let updatedBlocks = block.blocks
501
+ ? markRunningAgentsAsCancelled(block.blocks)
502
+ : undefined
503
+
504
+ // Close any open native reasoning blocks in this agent
505
+ if (updatedBlocks) {
506
+ updatedBlocks = closeNativeReasoningBlock(updatedBlocks)
507
+ }
508
+
509
+ if (block.status === 'running') {
510
+ return {
511
+ ...block,
512
+ status: 'cancelled' as const,
513
+ ...(updatedBlocks && { blocks: updatedBlocks }),
514
+ }
515
+ }
516
+
517
+ if (updatedBlocks && updatedBlocks !== block.blocks) {
518
+ return { ...block, blocks: updatedBlocks }
519
+ }
520
+
521
+ return block
522
+ })
523
+ }