@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,34 @@
1
+ import { memo } from 'react'
2
+
3
+ import { Button } from './button'
4
+ import { useTheme } from '../hooks/use-theme'
5
+
6
+ import type { CSSProperties } from 'react'
7
+
8
+ interface CollapseButtonProps {
9
+ onClick: () => void
10
+ style?: CSSProperties
11
+ }
12
+
13
+ /**
14
+ * Reusable collapse button with '▴ collapse' text
15
+ * Styled with theme.secondary color, aligned to the right by default
16
+ */
17
+ export const CollapseButton = memo(({ onClick, style }: CollapseButtonProps) => {
18
+ const theme = useTheme()
19
+
20
+ return (
21
+ <Button
22
+ style={{
23
+ alignSelf: 'flex-end',
24
+ marginTop: 0,
25
+ ...style,
26
+ }}
27
+ onClick={onClick}
28
+ >
29
+ <text fg={theme.secondary} style={{ wrapMode: 'none' }}>
30
+ ▴ collapse
31
+ </text>
32
+ </Button>
33
+ )
34
+ })
@@ -0,0 +1,203 @@
1
+ import { TextAttributes } from '@opentui/core'
2
+ import React, { useCallback, useState } from 'react'
3
+
4
+ import { Clickable } from './clickable'
5
+ import { useTheme } from '../hooks/use-theme'
6
+ import { useTimeout } from '../hooks/use-timeout'
7
+ import { copyTextToClipboard } from '../utils/clipboard'
8
+
9
+ import type { ReactNode } from 'react'
10
+
11
+ // ============================================================================
12
+ // Exported constants and utilities for testing
13
+ // ============================================================================
14
+
15
+ /** Time in ms before the "copied" state resets */
16
+ export const COPIED_RESET_DELAY_MS = 2000
17
+
18
+ /**
19
+ * The copy-and-confirm state machine: copies `text` to the clipboard and flips
20
+ * `isCopied` true for {@link COPIED_RESET_DELAY_MS} before resetting. Lifted out
21
+ * of {@link CopyButton} so callers that render their own button (and may fire
22
+ * the copy from elsewhere, e.g. keyboard navigation) can share the same
23
+ * lifecycle instead of re-implementing it.
24
+ */
25
+ export function useCopyToClipboard(text: string): {
26
+ isCopied: boolean
27
+ copy: () => void
28
+ } {
29
+ const { setTimeout } = useTimeout()
30
+ const [isCopied, setIsCopied] = useState(false)
31
+
32
+ const copy = useCallback(() => {
33
+ void (async () => {
34
+ try {
35
+ await copyTextToClipboard(text, { suppressGlobalMessage: true })
36
+ setIsCopied(true)
37
+ setTimeout(
38
+ 'reset-copied',
39
+ () => setIsCopied(false),
40
+ COPIED_RESET_DELAY_MS,
41
+ )
42
+ } catch (_error) {
43
+ // Error is already logged and displayed by copyTextToClipboard.
44
+ }
45
+ })()
46
+ }, [text, setTimeout])
47
+
48
+ return { isCopied, copy }
49
+ }
50
+
51
+ /** Icon shown in collapsed state */
52
+ export const COPY_ICON_COLLAPSED = '⎘'
53
+ /** Text shown when hovering */
54
+ export const COPY_ICON_EXPANDED = '[⎘ copy]'
55
+ /** Text shown after copying */
56
+ export const COPY_ICON_COPIED = '[✔ copied]'
57
+
58
+ /**
59
+ * Get the text to display for the copy icon based on state.
60
+ * Exported for testing.
61
+ */
62
+ export const getCopyIconText = (
63
+ isCopied: boolean,
64
+ isHovered: boolean,
65
+ leadingSpace: boolean,
66
+ ): string => {
67
+ const space = leadingSpace ? ' ' : ''
68
+ if (isCopied) return `${space}${COPY_ICON_COPIED}`
69
+ if (isHovered) return `${space}${COPY_ICON_EXPANDED}`
70
+ return `${space}${COPY_ICON_COLLAPSED}`
71
+ }
72
+
73
+ /**
74
+ * State transition handlers for the copy button.
75
+ * Exported for testing - these are the pure logic functions
76
+ * that determine how state changes in response to user actions.
77
+ */
78
+ export const copyButtonHandlers = {
79
+ /** Handle mouse entering the button */
80
+ handleMouseOver: (isCopied: boolean): boolean => {
81
+ // Don't show hover state while showing "copied" feedback
82
+ return !isCopied
83
+ },
84
+
85
+ /** Handle mouse leaving the button - always clears hover */
86
+ handleMouseOut: (): boolean => {
87
+ return false
88
+ },
89
+
90
+ /** Handle copy action - returns new state */
91
+ handleCopy: (): { isCopied: boolean; isHovered: boolean } => {
92
+ return { isCopied: true, isHovered: false }
93
+ },
94
+ }
95
+
96
+ // ============================================================================
97
+ // Internal component
98
+ // ============================================================================
99
+
100
+ interface CopyIconProps {
101
+ isCopied: boolean
102
+ isHovered: boolean
103
+ leadingSpace: boolean
104
+ }
105
+
106
+ /**
107
+ * Internal presentational component for the copy icon.
108
+ * Displays a dimmed icon that expands on hover and changes to a checkmark when copied.
109
+ */
110
+ const CopyIcon: React.FC<CopyIconProps> = ({
111
+ isCopied,
112
+ isHovered,
113
+ leadingSpace,
114
+ }) => {
115
+ const theme = useTheme()
116
+ const text = getCopyIconText(isCopied, isHovered, leadingSpace)
117
+
118
+ if (isCopied) {
119
+ return <span fg="green">{text}</span>
120
+ }
121
+
122
+ if (isHovered) {
123
+ return <span fg={theme.foreground}>{text}</span>
124
+ }
125
+
126
+ return (
127
+ <span fg={theme.muted} attributes={TextAttributes.DIM}>
128
+ {text}
129
+ </span>
130
+ )
131
+ }
132
+
133
+ interface CopyButtonProps {
134
+ /** The text to copy to clipboard when clicked */
135
+ textToCopy: string
136
+ /** Optional content to display before the copy icon */
137
+ children?: ReactNode
138
+ /** Whether to include a leading space before the icon (default: true) */
139
+ leadingSpace?: boolean
140
+ /** Style props passed to the underlying Clickable */
141
+ style?: Record<string, unknown>
142
+ }
143
+
144
+ /**
145
+ * A clickable copy button that copies text to clipboard.
146
+ *
147
+ * Can be used standalone (just the icon) or with children (content + trailing icon).
148
+ *
149
+ * @example
150
+ * ```tsx
151
+ * // Standalone copy button
152
+ * <CopyButton textToCopy="some text" leadingSpace={false} />
153
+ *
154
+ * // With content (icon appears after children)
155
+ * <CopyButton textToCopy={content} style={{ wrapMode: 'word' }}>
156
+ * <span>Content to display</span>
157
+ * </CopyButton>
158
+ * ```
159
+ */
160
+ export const CopyButton: React.FC<CopyButtonProps> = ({
161
+ textToCopy,
162
+ children,
163
+ leadingSpace = true,
164
+ style,
165
+ }) => {
166
+ const { isCopied, copy } = useCopyToClipboard(textToCopy)
167
+ const [isHovered, setIsHovered] = useState(false)
168
+
169
+ const handleCopy = () => {
170
+ // copyButtonHandlers.handleCopy() encodes "clear hover on copy"; the copied
171
+ // flag + reset timer now live in useCopyToClipboard.
172
+ setIsHovered(copyButtonHandlers.handleCopy().isHovered)
173
+ copy()
174
+ }
175
+
176
+ const handleMouseOver = () => {
177
+ const shouldHover = copyButtonHandlers.handleMouseOver(isCopied)
178
+ if (shouldHover) {
179
+ setIsHovered(true)
180
+ }
181
+ }
182
+
183
+ const handleMouseOut = () => {
184
+ setIsHovered(copyButtonHandlers.handleMouseOut())
185
+ }
186
+
187
+ return (
188
+ <Clickable
189
+ as="text"
190
+ style={style}
191
+ onMouseDown={handleCopy}
192
+ onMouseOver={handleMouseOver}
193
+ onMouseOut={handleMouseOut}
194
+ >
195
+ {children}
196
+ <CopyIcon
197
+ isCopied={isCopied}
198
+ isHovered={isHovered}
199
+ leadingSpace={leadingSpace}
200
+ />
201
+ </Clickable>
202
+ )
203
+ }
@@ -0,0 +1,55 @@
1
+ import { useEffect, useState } from 'react'
2
+
3
+ import { useTheme } from '../hooks/use-theme'
4
+ import { formatElapsedTime } from '../utils/format-elapsed-time'
5
+
6
+ interface ElapsedTimerProps {
7
+ startTime: number | null
8
+ suffix?: string
9
+ attributes?: number
10
+ }
11
+
12
+ /**
13
+ * Self-contained timer component that updates every second.
14
+ * Only this component re-renders, not its parents.
15
+ */
16
+ export const ElapsedTimer = ({
17
+ startTime,
18
+ suffix = '',
19
+ attributes,
20
+ }: ElapsedTimerProps) => {
21
+ const theme = useTheme()
22
+ const [elapsedSeconds, setElapsedSeconds] = useState<number>(() =>
23
+ startTime ? Math.floor((Date.now() - startTime) / 1000) : 0,
24
+ )
25
+
26
+ useEffect(() => {
27
+ if (!startTime) {
28
+ setElapsedSeconds(0)
29
+ return
30
+ }
31
+
32
+ const updateElapsed = () => {
33
+ const elapsed = Math.floor((Date.now() - startTime) / 1000)
34
+ setElapsedSeconds(elapsed)
35
+ }
36
+
37
+ // Update immediately
38
+ updateElapsed()
39
+
40
+ // Then update every second
41
+ const interval = setInterval(updateElapsed, 1000)
42
+
43
+ return () => clearInterval(interval)
44
+ }, [startTime])
45
+
46
+ if (!startTime || elapsedSeconds === 0) {
47
+ return null
48
+ }
49
+
50
+ return (
51
+ <span fg={theme.secondary} attributes={attributes}>
52
+ {formatElapsedTime(elapsedSeconds)}{suffix}
53
+ </span>
54
+ )
55
+ }
@@ -0,0 +1,55 @@
1
+ import { memo, type ReactNode } from 'react'
2
+
3
+ interface ErrorBoundaryPlaceholderProps {
4
+ children: ReactNode
5
+ fallback: ReactNode
6
+ componentName?: string
7
+ }
8
+
9
+ /**
10
+ * **WARNING: This component does NOT catch render errors.**
11
+ *
12
+ * This is a placeholder/passthrough component that exists for structural purposes.
13
+ * OpenTUI's JSX types don't support React class components, which are required
14
+ * for true error boundary functionality.
15
+ *
16
+ * For actual error catching in render functions, use `withErrorFallback()` instead.
17
+ *
18
+ * @example
19
+ * // Use withErrorFallback for catching render errors:
20
+ * const safeContent = withErrorFallback(
21
+ * () => riskyRenderFunction(),
22
+ * <FallbackComponent />,
23
+ * 'MyComponent'
24
+ * )
25
+ */
26
+ export const ErrorBoundaryPlaceholder = memo(
27
+ ({ children }: ErrorBoundaryPlaceholderProps) => {
28
+ // This component does NOT catch errors - it's a passthrough.
29
+ // Use withErrorFallback() for actual error catching.
30
+ return <>{children}</>
31
+ },
32
+ )
33
+
34
+ /**
35
+ * @deprecated Use `ErrorBoundaryPlaceholder` instead. This alias exists for backward
36
+ * compatibility but the name is misleading since it doesn't actually catch errors.
37
+ */
38
+ export const ErrorBoundary = ErrorBoundaryPlaceholder
39
+
40
+ /**
41
+ * Helper to safely render content with error handling.
42
+ * Use this when you need to catch render errors in a functional context.
43
+ */
44
+ export function withErrorFallback<T>(
45
+ renderFn: () => T,
46
+ fallback: T,
47
+ componentName?: string,
48
+ ): T {
49
+ try {
50
+ return renderFn()
51
+ } catch (error) {
52
+ console.error(`[${componentName ?? 'withErrorFallback'}] Error caught:`, error)
53
+ return fallback
54
+ }
55
+ }
@@ -0,0 +1,181 @@
1
+ import React, { useCallback, useEffect } from 'react'
2
+ import { useShallow } from 'zustand/react/shallow'
3
+
4
+ import { FeedbackInputMode } from './feedback-input-mode'
5
+ import { useChatStore } from '../state/chat-store'
6
+ import { useFeedbackStore } from '../state/feedback-store'
7
+ import { showClipboardMessage } from '../utils/clipboard'
8
+ import { getApiClient } from '../utils/anvil-api'
9
+ import { buildFeedbackPayload, buildMessageContext } from '../utils/feedback-helpers'
10
+ import { resolveFeedbackSubmission } from '../utils/feedback-submission'
11
+ import { logger } from '../utils/logger'
12
+
13
+ interface FeedbackContainerProps {
14
+ inputRef: React.MutableRefObject<any>
15
+ onExitFeedback?: () => void
16
+ width: number
17
+ }
18
+
19
+ export const FeedbackContainer: React.FC<FeedbackContainerProps> = ({
20
+ inputRef,
21
+ onExitFeedback,
22
+ width,
23
+ }) => {
24
+ const {
25
+ feedbackMode,
26
+ feedbackText,
27
+ feedbackCursor,
28
+ feedbackCategory,
29
+ feedbackMessageId,
30
+ feedbackFooterMessage,
31
+ isSubmitting,
32
+ errors,
33
+ setFeedbackText,
34
+ setFeedbackCursor,
35
+ setFeedbackCategory,
36
+ } = useFeedbackStore(
37
+ useShallow((state) => ({
38
+ feedbackMode: state.feedbackMode,
39
+ feedbackText: state.feedbackText,
40
+ feedbackCursor: state.feedbackCursor,
41
+ feedbackCategory: state.feedbackCategory,
42
+ feedbackMessageId: state.feedbackMessageId,
43
+ feedbackFooterMessage: state.feedbackFooterMessage,
44
+ isSubmitting: state.isSubmitting,
45
+ errors: state.errors,
46
+ setFeedbackText: state.setFeedbackText,
47
+ setFeedbackCursor: state.setFeedbackCursor,
48
+ setFeedbackCategory: state.setFeedbackCategory,
49
+ })),
50
+ )
51
+
52
+ const { messages, agentMode, sessionCreditsUsed } = useChatStore(
53
+ useShallow((state) => ({
54
+ messages: state.messages,
55
+ agentMode: state.agentMode,
56
+ sessionCreditsUsed: state.sessionCreditsUsed,
57
+ })),
58
+ )
59
+
60
+ const handleFeedbackSubmit = useCallback(() => {
61
+ const store = useFeedbackStore.getState()
62
+ if (store.isSubmitting) return
63
+
64
+ const { clientFeedbackId } = store
65
+ if (!clientFeedbackId) return
66
+
67
+ const text = feedbackText.trim()
68
+ if (!text) {
69
+ return
70
+ }
71
+
72
+ store.setIsSubmitting(true)
73
+
74
+ const { target, recentMessages } = buildMessageContext(messages, feedbackMessageId)
75
+ const payload = buildFeedbackPayload({
76
+ text,
77
+ feedbackCategory,
78
+ feedbackMessageId,
79
+ target,
80
+ recentMessages,
81
+ agentMode,
82
+ sessionCreditsUsed,
83
+ errors,
84
+ clientFeedbackId,
85
+ })
86
+
87
+ const submittedMessageId = feedbackMessageId
88
+ const submittedCategory = feedbackCategory
89
+ const submittedClientFeedbackId = clientFeedbackId
90
+
91
+ getApiClient()
92
+ .feedback(payload)
93
+ .then((response) => {
94
+ const store = useFeedbackStore.getState()
95
+ const { isCurrentSubmission, shouldSettleSubmission } = resolveFeedbackSubmission(
96
+ store.clientFeedbackId,
97
+ submittedClientFeedbackId,
98
+ )
99
+
100
+ if (!response.ok) {
101
+ logger.warn(
102
+ { status: response.status, error: response.error },
103
+ 'Feedback API returned error',
104
+ )
105
+ if (!shouldSettleSubmission) return
106
+ store.setIsSubmitting(false)
107
+ showClipboardMessage('Feedback failed to send', { durationMs: 5000 })
108
+ return
109
+ }
110
+
111
+ if (submittedMessageId) {
112
+ store.markMessageFeedbackSubmitted(submittedMessageId, submittedCategory)
113
+ }
114
+
115
+ if (isCurrentSubmission) {
116
+ store.resetFeedbackForm()
117
+ store.closeFeedback()
118
+ store.setIsSubmitting(false)
119
+ if (onExitFeedback) onExitFeedback()
120
+ } else if (shouldSettleSubmission) {
121
+ store.setIsSubmitting(false)
122
+ }
123
+
124
+ if (shouldSettleSubmission) {
125
+ showClipboardMessage('Feedback sent!', { durationMs: 5000 })
126
+ }
127
+ })
128
+ .catch((error: unknown) => {
129
+ logger.warn({ error }, 'Failed to submit feedback to API')
130
+ const store = useFeedbackStore.getState()
131
+ if (!resolveFeedbackSubmission(store.clientFeedbackId, submittedClientFeedbackId).shouldSettleSubmission) {
132
+ return
133
+ }
134
+ store.setIsSubmitting(false)
135
+ showClipboardMessage('Feedback failed to send', { durationMs: 5000 })
136
+ })
137
+ }, [
138
+ feedbackText,
139
+ feedbackMessageId,
140
+ feedbackCategory,
141
+ errors,
142
+ messages,
143
+ agentMode,
144
+ sessionCreditsUsed,
145
+ onExitFeedback,
146
+ ])
147
+
148
+ const handleFeedbackCancel = useCallback(() => {
149
+ useFeedbackStore.getState().closeFeedback()
150
+ if (onExitFeedback) {
151
+ onExitFeedback()
152
+ }
153
+ }, [onExitFeedback])
154
+
155
+ useEffect(() => {
156
+ if (feedbackMode && inputRef.current) {
157
+ inputRef.current.focus()
158
+ }
159
+ }, [feedbackMode, inputRef])
160
+
161
+ if (!feedbackMode) {
162
+ return null
163
+ }
164
+
165
+ return (
166
+ <FeedbackInputMode
167
+ value={feedbackText}
168
+ cursor={feedbackCursor}
169
+ onChange={setFeedbackText}
170
+ onCursorChange={setFeedbackCursor}
171
+ onSubmit={handleFeedbackSubmit}
172
+ onCancel={handleFeedbackCancel}
173
+ feedbackCategory={feedbackCategory}
174
+ onCategoryChange={setFeedbackCategory}
175
+ inputRef={inputRef}
176
+ width={width}
177
+ footerMessage={feedbackFooterMessage}
178
+ isSubmitting={isSubmitting}
179
+ />
180
+ )
181
+ }
@@ -0,0 +1,89 @@
1
+ import { AnalyticsEvent } from '@anvil/common/constants/analytics-events'
2
+ import { TextAttributes } from '@opentui/core'
3
+ import React, { useRef } from 'react'
4
+
5
+ import { useHoverToggle } from './agent-mode-toggle'
6
+ import { Button } from './button'
7
+ import { useTheme } from '../hooks/use-theme'
8
+ import { logger } from '../utils/logger'
9
+
10
+ interface FeedbackIconButtonProps {
11
+ onClick: () => void
12
+ onClose: () => void
13
+ isOpen?: boolean
14
+ messageId?: string
15
+ selectedCategory?: string
16
+ hasSubmittedFeedback?: boolean
17
+ }
18
+
19
+ export const FeedbackIconButton: React.FC<FeedbackIconButtonProps> = ({
20
+ onClick,
21
+ onClose,
22
+ isOpen,
23
+ messageId,
24
+ selectedCategory,
25
+ hasSubmittedFeedback = false,
26
+ }) => {
27
+ const theme = useTheme()
28
+ const hover = useHoverToggle()
29
+ const hoveredOnceRef = useRef(false)
30
+ const handleClick = () => {
31
+ const action = isOpen ? onClose : onClick
32
+ action()
33
+ }
34
+
35
+ const handleMouseOver = () => {
36
+ if (hasSubmittedFeedback) return
37
+ hover.clearCloseTimer()
38
+ hover.scheduleOpen()
39
+ if (!hoveredOnceRef.current) {
40
+ hoveredOnceRef.current = true
41
+ logger.info(
42
+ {
43
+ eventId: AnalyticsEvent.FEEDBACK_BUTTON_HOVERED,
44
+ messageId,
45
+ source: 'cli',
46
+ },
47
+ 'Feedback button hovered',
48
+ )
49
+ }
50
+ }
51
+ const handleMouseOut = () => {
52
+ if (hasSubmittedFeedback) return
53
+ hover.scheduleClose()
54
+ }
55
+
56
+ const symbolsByCategory: Record<string, string> = {
57
+ good_result: '▲▽', // Good selected - filled up, outlined down
58
+ bad_result: '△▼', // Bad selected - outlined up, filled down
59
+ }
60
+ const textCollapsed = symbolsByCategory[selectedCategory || ''] || '△▽'
61
+ const textExpanded = '[how was this?]'
62
+
63
+ return (
64
+ <Button
65
+ style={{
66
+ flexDirection: 'row',
67
+ alignItems: 'center',
68
+ paddingLeft: 0,
69
+ paddingRight: 0,
70
+ }}
71
+ onClick={handleClick}
72
+ onMouseOver={handleMouseOver}
73
+ onMouseOut={handleMouseOut}
74
+ >
75
+ <text
76
+ style={{
77
+ wrapMode: 'none',
78
+ fg: hover.isOpen || isOpen ? theme.foreground : theme.muted,
79
+ }}
80
+ >
81
+ {hover.isOpen || isOpen ? (
82
+ textExpanded
83
+ ) : (
84
+ <span attributes={TextAttributes.DIM}>{textCollapsed}</span>
85
+ )}
86
+ </text>
87
+ </Button>
88
+ )
89
+ }