@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,429 @@
1
+ import { TextAttributes } from '@opentui/core'
2
+ import React, { memo, useCallback, useMemo, useState } from 'react'
3
+
4
+ /** Horizontal padding inside implementor cards (left + right) */
5
+ const CARD_HORIZONTAL_PADDING = 4
6
+ /** Fixed width for the +/- bar visualization */
7
+ const STATS_BAR_WIDTH = 5
8
+ /** Minimum inner content width */
9
+ const MIN_INNER_WIDTH = 10
10
+
11
+ /** Labels for proposal cards when no file changes exist */
12
+ const EMPTY_STATE_LABELS = {
13
+ running: 'generating...',
14
+ complete: 'no changes',
15
+ failed: 'failed',
16
+ cancelled: 'cancelled',
17
+ } as const
18
+
19
+ import { useGridLayout } from '../../hooks/use-grid-layout'
20
+ import { useTheme } from '../../hooks/use-theme'
21
+ import { getAgentStatusInfo } from '../../utils/agent-helpers'
22
+ import {
23
+ buildActivityTimeline,
24
+ getImplementorDisplayName,
25
+ getImplementorIndex,
26
+ getFileStatsFromBlocks,
27
+ truncateWithEllipsis,
28
+ type FileStats,
29
+ } from '../../utils/implementor-helpers'
30
+ import { getRelativePath } from '../../utils/path-helpers'
31
+ import { PROPOSAL_BORDER_CHARS } from '../../utils/ui-constants'
32
+ import { Button } from '../button'
33
+ import { CollapseButton } from '../collapse-button'
34
+ import { DiffViewer } from '../tools/diff-viewer'
35
+
36
+ import type { AgentContentBlock, ContentBlock } from '../../types/chat'
37
+
38
+ interface ImplementorGroupProps {
39
+ implementors: AgentContentBlock[]
40
+ siblingBlocks: ContentBlock[]
41
+ availableWidth: number
42
+ }
43
+
44
+ export const ImplementorGroup = memo(
45
+ ({ implementors, siblingBlocks, availableWidth }: ImplementorGroupProps) => {
46
+ const { columnWidth: cardWidth, columnGroups } = useGridLayout(
47
+ implementors,
48
+ availableWidth,
49
+ )
50
+
51
+ return (
52
+ <box
53
+ style={{
54
+ flexDirection: 'column',
55
+ gap: 1,
56
+ width: '100%',
57
+ marginTop: 1,
58
+ }}
59
+ >
60
+ {/* Masonry layout: columns side by side, cards stack vertically in each */}
61
+ <box
62
+ style={{
63
+ flexDirection: 'row',
64
+ gap: 1,
65
+ width: '100%',
66
+ alignItems: 'flex-start',
67
+ }}
68
+ >
69
+ {columnGroups.map((columnItems, colIdx) => {
70
+ // Use first agent's ID as stable column key
71
+ const columnKey = columnItems[0]?.agentId ?? `col-${colIdx}`
72
+ return (
73
+ <box
74
+ key={columnKey}
75
+ style={{
76
+ flexDirection: 'column',
77
+ gap: 0,
78
+ flexGrow: 1,
79
+ flexShrink: 1,
80
+ flexBasis: 0,
81
+ minWidth: 0,
82
+ }}
83
+ >
84
+ {columnItems.map((agentBlock) => {
85
+ const implementorIndex = getImplementorIndex(
86
+ agentBlock,
87
+ siblingBlocks,
88
+ )
89
+
90
+ return (
91
+ <ImplementorCard
92
+ key={agentBlock.agentId}
93
+ agentBlock={agentBlock}
94
+ implementorIndex={implementorIndex}
95
+ cardWidth={cardWidth}
96
+ />
97
+ )
98
+ })}
99
+ </box>
100
+ )
101
+ })}
102
+ </box>
103
+ </box>
104
+ )
105
+ },
106
+ )
107
+
108
+ interface ImplementorCardProps {
109
+ agentBlock: AgentContentBlock
110
+ implementorIndex?: number
111
+ cardWidth: number
112
+ }
113
+
114
+ const ImplementorCard = memo(
115
+ ({ agentBlock, implementorIndex, cardWidth }: ImplementorCardProps) => {
116
+ const theme = useTheme()
117
+ const [selectedFile, setSelectedFile] = useState<string | null>(null)
118
+
119
+ const isComplete = agentBlock.status === 'complete'
120
+
121
+ const displayName = getImplementorDisplayName(
122
+ agentBlock.agentType,
123
+ implementorIndex,
124
+ )
125
+
126
+ // Get file stats for compact view
127
+ const fileStats = useMemo(
128
+ () => getFileStatsFromBlocks(agentBlock.blocks),
129
+ [agentBlock.blocks],
130
+ )
131
+
132
+ // Build timeline to extract diffs
133
+ const timeline = useMemo(
134
+ () => buildActivityTimeline(agentBlock.blocks),
135
+ [agentBlock.blocks],
136
+ )
137
+
138
+ // Build map of file path -> diff for inline display
139
+ const fileDiffs = useMemo(() => {
140
+ const diffs = new Map<string, string>()
141
+ for (const item of timeline) {
142
+ if (item.type === 'edit' && item.diff) {
143
+ diffs.set(item.content, item.diff)
144
+ }
145
+ }
146
+ return diffs
147
+ }, [timeline])
148
+
149
+ // Get status info from helper
150
+ const {
151
+ indicator: statusIndicator,
152
+ label: statusLabel,
153
+ color: statusColor,
154
+ } = getAgentStatusInfo(agentBlock.status, theme)
155
+ // Format: "● running" when streaming, "completed ✓" when done (checkmark at end)
156
+ const statusText =
157
+ statusIndicator === '✓'
158
+ ? `${statusLabel} ${statusIndicator}`
159
+ : `${statusIndicator} ${statusLabel}`
160
+
161
+ // Use cardWidth for internal truncation calculations (approximate internal space)
162
+ const innerWidth = Math.max(
163
+ MIN_INNER_WIDTH,
164
+ cardWidth - CARD_HORIZONTAL_PADDING,
165
+ )
166
+
167
+ // Toggle file selection - clicking same file deselects it
168
+ const handleFileSelect = useCallback((filePath: string) => {
169
+ setSelectedFile((prev) => (prev === filePath ? null : filePath))
170
+ }, [])
171
+
172
+ return (
173
+ <box
174
+ border
175
+ borderStyle="single"
176
+ customBorderChars={PROPOSAL_BORDER_CHARS}
177
+ borderColor={isComplete ? theme.muted : theme.primary}
178
+ style={{
179
+ flexDirection: 'column',
180
+ flexGrow: 1,
181
+ flexShrink: 1,
182
+ minWidth: 0,
183
+ paddingLeft: 1,
184
+ paddingRight: 1,
185
+ paddingTop: 0,
186
+ paddingBottom: 0,
187
+ }}
188
+ >
189
+ {/* Header: Model name + Status */}
190
+ <box
191
+ style={{
192
+ flexDirection: 'row',
193
+ alignItems: 'center',
194
+ gap: 1,
195
+ width: '100%',
196
+ }}
197
+ >
198
+ <text
199
+ fg={theme.foreground}
200
+ attributes={TextAttributes.BOLD}
201
+ style={{ wrapMode: 'none' }}
202
+ >
203
+ {displayName}
204
+ </text>
205
+ <text
206
+ fg={statusColor}
207
+ attributes={TextAttributes.DIM}
208
+ style={{ wrapMode: 'none' }}
209
+ >
210
+ {statusText}
211
+ </text>
212
+ </box>
213
+
214
+ {/* Prompt provided to this proposal */}
215
+ {agentBlock.initialPrompt && (
216
+ <box style={{ marginTop: 1, width: '100%' }}>
217
+ <text fg={theme.muted} attributes={TextAttributes.ITALIC}>
218
+ {agentBlock.initialPrompt}
219
+ </text>
220
+ </box>
221
+ )}
222
+
223
+ {/* File stats - click file name to view diff inline */}
224
+ {fileStats.length > 0 && (
225
+ <CompactFileStats
226
+ fileStats={fileStats}
227
+ availableWidth={innerWidth}
228
+ selectedFile={selectedFile}
229
+ onSelectFile={handleFileSelect}
230
+ fileDiffs={fileDiffs}
231
+ />
232
+ )}
233
+
234
+ {/* Show status-appropriate message when no file changes */}
235
+ {fileStats.length === 0 && (
236
+ <text
237
+ fg={theme.muted}
238
+ attributes={TextAttributes.ITALIC}
239
+ style={{ marginTop: 1 }}
240
+ >
241
+ {EMPTY_STATE_LABELS[agentBlock.status]}
242
+ </text>
243
+ )}
244
+ </box>
245
+ )
246
+ },
247
+ )
248
+
249
+ interface CompactFileStatsProps {
250
+ fileStats: FileStats[]
251
+ availableWidth: number
252
+ selectedFile: string | null
253
+ onSelectFile: (filePath: string) => void
254
+ /** Map of file path to diff content */
255
+ fileDiffs: Map<string, string>
256
+ }
257
+
258
+ const CompactFileStats = memo(
259
+ ({
260
+ fileStats,
261
+ availableWidth,
262
+ selectedFile,
263
+ onSelectFile,
264
+ fileDiffs,
265
+ }: CompactFileStatsProps) => {
266
+ const theme = useTheme()
267
+
268
+ // Fixed bar width - keeps layout simple and predictable
269
+ const maxBarWidth = STATS_BAR_WIDTH
270
+
271
+ // Calculate max string widths for alignment (so all bars meet at center axis)
272
+ // Always include +0/-0 in width calculation since we always show them
273
+ const maxAddedStrWidth = Math.max(
274
+ ...fileStats.map((f) => `+${f.stats.linesAdded}`.length),
275
+ 2, // Minimum "+0"
276
+ )
277
+ const maxRemovedStrWidth = Math.max(
278
+ ...fileStats.map((f) => `-${f.stats.linesRemoved}`.length),
279
+ 2, // Minimum "-0"
280
+ )
281
+
282
+ return (
283
+ <box style={{ flexDirection: 'column', marginTop: 1 }}>
284
+ {fileStats.map((file, idx) => (
285
+ <CompactFileRow
286
+ key={`${file.path}-${idx}`}
287
+ file={file}
288
+ availableWidth={availableWidth}
289
+ maxBarWidth={maxBarWidth}
290
+ maxAddedStrWidth={maxAddedStrWidth}
291
+ maxRemovedStrWidth={maxRemovedStrWidth}
292
+ isSelected={selectedFile === file.path}
293
+ onSelect={() => onSelectFile(file.path)}
294
+ diff={fileDiffs.get(file.path)}
295
+ />
296
+ ))}
297
+ </box>
298
+ )
299
+ },
300
+ )
301
+
302
+ interface CompactFileRowProps {
303
+ file: FileStats
304
+ availableWidth: number
305
+ maxBarWidth: number
306
+ maxAddedStrWidth: number
307
+ maxRemovedStrWidth: number
308
+ isSelected: boolean
309
+ onSelect: () => void
310
+ diff?: string
311
+ }
312
+
313
+ const CompactFileRow = memo(
314
+ ({
315
+ file,
316
+ availableWidth,
317
+ maxBarWidth,
318
+ maxAddedStrWidth,
319
+ maxRemovedStrWidth,
320
+ isSelected,
321
+ onSelect,
322
+ diff,
323
+ }: CompactFileRowProps) => {
324
+ const theme = useTheme()
325
+ const [isHovered, setIsHovered] = useState(false)
326
+
327
+ // Format numbers - always show counts, including +0 and -0
328
+ const addedStr = `+${file.stats.linesAdded}`
329
+ const removedStr = `-${file.stats.linesRemoved}`
330
+
331
+ // Full-width colored sections with numbers inside:
332
+ // - Added section: green bar extending to center with +N in white (right-aligned)
333
+ // - Removed section: red bar extending from center with -N in white (left-aligned)
334
+ const addedSectionWidth = maxBarWidth + maxAddedStrWidth
335
+ const removedSectionWidth = maxBarWidth + maxRemovedStrWidth
336
+
337
+ // +N right-aligned within the green section with 1 space padding before the center edge
338
+ const addedContent = (addedStr + ' ').padStart(addedSectionWidth)
339
+ // -N left-aligned within the red section with 1 space padding after the center edge
340
+ const removedContent = (' ' + removedStr).padEnd(removedSectionWidth)
341
+
342
+ // Calculate available width for file path
343
+ // Layout: changeType(1) + spaces(2) + filePath + spaces(2) + bars
344
+ // Total bar section width: 2*maxBarWidth + maxAddedStrWidth + maxRemovedStrWidth (no center gap)
345
+ const barWidth = 2 * maxBarWidth + maxAddedStrWidth + maxRemovedStrWidth
346
+ const fixedWidth = 1 + 2 + 2 + barWidth
347
+ const maxFilePathWidth = Math.max(10, availableWidth - fixedWidth)
348
+
349
+ // Get and truncate file path
350
+ const relativePath = getRelativePath(file.path)
351
+ const displayPath = truncateWithEllipsis(relativePath, maxFilePathWidth)
352
+
353
+ return (
354
+ <box style={{ flexDirection: 'column' }}>
355
+ {/* File row */}
356
+ <box style={{ flexDirection: 'row', alignItems: 'center' }}>
357
+ {/* Change type: fixed */}
358
+ <text fg={theme.muted} style={{ flexShrink: 0 }}>
359
+ {file.changeType}
360
+ </text>
361
+ <text style={{ flexShrink: 0 }}> </text>
362
+
363
+ {/* File path: clickable with underline on hover, flexes to push bars right */}
364
+ <Button
365
+ onClick={onSelect}
366
+ onMouseOver={() => setIsHovered(true)}
367
+ onMouseOut={() => setIsHovered(false)}
368
+ style={{
369
+ paddingLeft: 0,
370
+ paddingRight: 0,
371
+ flexGrow: 1,
372
+ flexShrink: 1,
373
+ flexBasis: 0,
374
+ minWidth: 0,
375
+ }}
376
+ >
377
+ <text
378
+ fg={theme.foreground}
379
+ attributes={
380
+ isHovered || isSelected ? TextAttributes.UNDERLINE : undefined
381
+ }
382
+ style={{
383
+ wrapMode: 'none',
384
+ }}
385
+ >
386
+ {displayPath}
387
+ </text>
388
+ </Button>
389
+ <text style={{ flexShrink: 0 }}> </text>
390
+
391
+ {/* Bar visualization: full-width bars meeting at center with numbers inside */}
392
+ <text style={{ flexShrink: 0, wrapMode: 'none' }}>
393
+ {/* Added section: muted gray-green bar with +N inside */}
394
+ <span fg={theme.foreground} bg="#3A5A3A">
395
+ {addedContent}
396
+ </span>
397
+ {/* Removed section: muted gray-red bar with -N inside */}
398
+ <span fg={theme.foreground} bg="#5A3A3A">
399
+ {removedContent}
400
+ </span>
401
+ </text>
402
+ </box>
403
+
404
+ {/* Inline diff viewer when selected - aligns with card content (full width) */}
405
+ {isSelected && diff && (
406
+ <box style={{ flexDirection: 'column', marginTop: 1, width: '100%' }}>
407
+ <box
408
+ style={{
409
+ flexDirection: 'column',
410
+ width: '100%',
411
+ paddingLeft: 1,
412
+ paddingRight: 1,
413
+ paddingTop: 1,
414
+ paddingBottom: 1,
415
+ backgroundColor: theme.surface,
416
+ }}
417
+ >
418
+ <DiffViewer diffText={diff} />
419
+ </box>
420
+ <CollapseButton onClick={onSelect} />
421
+ </box>
422
+ )}
423
+ </box>
424
+ )
425
+ },
426
+ )
427
+
428
+ // Keep the old exports for backward compatibility during transition
429
+ export { ImplementorCard as ImplementorRow }
@@ -0,0 +1,201 @@
1
+ import { TextAttributes } from '@opentui/core'
2
+ import React, { memo, type ReactNode } from 'react'
3
+
4
+ import { AgentBranchWrapper } from './agent-branch-wrapper'
5
+ import { AgentListBranch } from './agent-list-branch'
6
+ import { AskUserBranch } from './ask-user-branch'
7
+ import { trimNewlines, isReasoningTextBlock } from './block-helpers'
8
+ import { ContentWithMarkdown } from './content-with-markdown'
9
+ import { ImageBlock } from './image-block'
10
+ import { UserBlockTextWithInlineCopy } from './user-content-copy'
11
+ import { useTheme } from '../../hooks/use-theme'
12
+ import { PlanBox } from '../renderers/plan-box'
13
+
14
+ import type {
15
+ ContentBlock,
16
+ TextContentBlock,
17
+ ImageContentBlock,
18
+ } from '../../types/chat'
19
+ import type { MarkdownPalette } from '../../utils/markdown-renderer'
20
+
21
+ interface SingleBlockProps {
22
+ block: ContentBlock
23
+ idx: number
24
+ messageId: string
25
+ blocks?: ContentBlock[]
26
+ isLoading: boolean
27
+ isComplete?: boolean
28
+ isUser: boolean
29
+ textColor: string
30
+ availableWidth: number
31
+ markdownPalette: MarkdownPalette
32
+ onToggleCollapsed: (id: string) => void
33
+ onBuildFast: () => void
34
+ onBuildMax: () => void
35
+ onBuildLite: () => void
36
+ isLastMessage?: boolean
37
+ contentToCopy?: string
38
+ }
39
+
40
+ export const SingleBlock = memo(
41
+ ({
42
+ block,
43
+ idx,
44
+ messageId,
45
+ blocks,
46
+ isLoading,
47
+ isComplete,
48
+ isUser,
49
+ textColor,
50
+ availableWidth,
51
+ markdownPalette,
52
+ onToggleCollapsed,
53
+ onBuildFast,
54
+ onBuildMax,
55
+ onBuildLite,
56
+ isLastMessage,
57
+ contentToCopy,
58
+ }: SingleBlockProps): ReactNode => {
59
+ const theme = useTheme()
60
+ const codeBlockWidth = Math.max(10, availableWidth - 8)
61
+
62
+ switch (block.type) {
63
+ case 'text': {
64
+ if (isReasoningTextBlock(block)) {
65
+ return null
66
+ }
67
+ const textBlock = block as TextContentBlock
68
+ const isStreamingText = isLoading || !isComplete
69
+ const filteredContent = isStreamingText
70
+ ? trimNewlines(textBlock.content)
71
+ : textBlock.content.trim()
72
+ if (!filteredContent) {
73
+ return null
74
+ }
75
+ const renderKey = `${messageId}-text-${idx}`
76
+ const explicitColor = textBlock.color
77
+ const blockTextColor = explicitColor ?? textColor
78
+
79
+ if (contentToCopy) {
80
+ return (
81
+ <UserBlockTextWithInlineCopy
82
+ key={renderKey}
83
+ content={filteredContent}
84
+ contentToCopy={contentToCopy}
85
+ isStreaming={isStreamingText}
86
+ textColor={blockTextColor}
87
+ codeBlockWidth={codeBlockWidth}
88
+ palette={markdownPalette}
89
+ marginTop={0}
90
+ marginBottom={0}
91
+ />
92
+ )
93
+ }
94
+
95
+ return (
96
+ <text
97
+ key={renderKey}
98
+ style={{
99
+ wrapMode: 'word',
100
+ fg: blockTextColor,
101
+ }}
102
+ attributes={isUser ? TextAttributes.ITALIC : undefined}
103
+ >
104
+ <ContentWithMarkdown
105
+ content={filteredContent}
106
+ isStreaming={isStreamingText}
107
+ codeBlockWidth={codeBlockWidth}
108
+ palette={markdownPalette}
109
+ />
110
+ </text>
111
+ )
112
+ }
113
+
114
+ case 'plan': {
115
+ return (
116
+ <box key={`${messageId}-plan-${idx}`} style={{ width: '100%' }}>
117
+ <PlanBox
118
+ planContent={block.content}
119
+ availableWidth={availableWidth}
120
+ markdownPalette={markdownPalette}
121
+ onBuildFast={onBuildFast}
122
+ onBuildMax={onBuildMax}
123
+ onBuildLite={onBuildLite}
124
+ />
125
+ </box>
126
+ )
127
+ }
128
+
129
+ case 'html': {
130
+ return (
131
+ <box
132
+ key={`${messageId}-html-${idx}`}
133
+ style={{
134
+ flexDirection: 'column',
135
+ gap: 0,
136
+ width: '100%',
137
+ }}
138
+ >
139
+ {block.render({ textColor, theme })}
140
+ </box>
141
+ )
142
+ }
143
+
144
+ case 'tool': {
145
+ return null
146
+ }
147
+
148
+ case 'ask-user': {
149
+ return (
150
+ <AskUserBranch
151
+ key={`${messageId}-ask-user-${idx}`}
152
+ block={block}
153
+ availableWidth={availableWidth}
154
+ />
155
+ )
156
+ }
157
+
158
+ case 'image': {
159
+ return (
160
+ <ImageBlock
161
+ key={`${messageId}-image-${idx}`}
162
+ block={block as ImageContentBlock}
163
+ availableWidth={availableWidth}
164
+ />
165
+ )
166
+ }
167
+
168
+ case 'agent': {
169
+ return (
170
+ <AgentBranchWrapper
171
+ key={`${messageId}-agent-${block.agentId}`}
172
+ agentBlock={block}
173
+ keyPrefix={`${messageId}-agent-${block.agentId}`}
174
+ availableWidth={availableWidth}
175
+ markdownPalette={markdownPalette}
176
+ onToggleCollapsed={onToggleCollapsed}
177
+ onBuildFast={onBuildFast}
178
+ onBuildMax={onBuildMax}
179
+ onBuildLite={onBuildLite}
180
+ siblingBlocks={blocks}
181
+ isLastMessage={isLastMessage}
182
+ />
183
+ )
184
+ }
185
+
186
+ case 'agent-list': {
187
+ return (
188
+ <AgentListBranch
189
+ key={`${messageId}-agent-list-${block.id}`}
190
+ agentListBlock={block}
191
+ keyPrefix={`${messageId}-agent-list-${block.id}`}
192
+ onToggleCollapsed={onToggleCollapsed}
193
+ />
194
+ )
195
+ }
196
+
197
+ default:
198
+ return null
199
+ }
200
+ },
201
+ )
@@ -0,0 +1,67 @@
1
+ import { memo, useCallback } from 'react'
2
+
3
+ import { Thinking } from '../thinking'
4
+
5
+ import type { ContentBlock } from '../../types/chat'
6
+
7
+ // Nested thinking blocks need more offset to account for the subagent's border and padding
8
+ const WIDTH_OFFSET = 6
9
+ const NESTED_WIDTH_OFFSET = 10
10
+
11
+ interface ThinkingBlockProps {
12
+ blocks: Extract<ContentBlock, { type: 'text' }>[]
13
+ onToggleCollapsed: (id: string) => void
14
+ availableWidth: number
15
+ isNested: boolean
16
+ /** Whether the parent message is complete (used to hide native reasoning blocks) */
17
+ isMessageComplete: boolean
18
+ }
19
+
20
+ export const ThinkingBlock = memo(
21
+ ({
22
+ blocks,
23
+ onToggleCollapsed,
24
+ availableWidth,
25
+ isNested,
26
+ isMessageComplete,
27
+ }: ThinkingBlockProps) => {
28
+ const firstBlock = blocks[0]
29
+ const thinkingId = firstBlock?.thinkingId
30
+ const combinedContent = blocks
31
+ .map((b) => b.content)
32
+ .join('')
33
+ .trim()
34
+
35
+ const thinkingCollapseState = firstBlock?.thinkingCollapseState ?? 'preview'
36
+ const offset = isNested ? NESTED_WIDTH_OFFSET : WIDTH_OFFSET
37
+ const availWidth = Math.max(10, availableWidth - offset)
38
+
39
+ const handleToggle = useCallback(() => {
40
+ if (thinkingId) {
41
+ onToggleCollapsed(thinkingId)
42
+ }
43
+ }, [onToggleCollapsed, thinkingId])
44
+
45
+ // thinkingOpen === false means explicitly closed (with </think> tag or message completion)
46
+ // Otherwise (true or undefined), completion is determined by message completion
47
+ const isThinkingComplete =
48
+ firstBlock?.thinkingOpen === false || isMessageComplete
49
+
50
+ // Hide if no content or no thinkingId (but NOT when thinking is complete)
51
+ if (!combinedContent || !thinkingId) {
52
+ return null
53
+ }
54
+
55
+ return (
56
+ <box>
57
+ <Thinking
58
+ content={combinedContent}
59
+ thinkingCollapseState={thinkingCollapseState}
60
+ isThinkingComplete={isThinkingComplete}
61
+ onToggle={handleToggle}
62
+ availableWidth={availWidth}
63
+ />
64
+ </box>
65
+ )
66
+ },
67
+ )