@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,126 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+
3
+ import { formatValidationError } from '../validation-error-formatting'
4
+
5
+ describe('formatValidationError', () => {
6
+ test('parses JSON array payloads and extracts field/message', () => {
7
+ const raw = `[
8
+ {
9
+ "code": "custom",
10
+ "path": [
11
+ "toolNames"
12
+ ],
13
+ "message": "Non-empty spawnableAgents array requires the 'spawn_agents' tool. Add 'spawn_agents' to toolNames or remove spawnableAgents."
14
+ }
15
+ ]`
16
+
17
+ const result = formatValidationError(raw)
18
+
19
+ expect(result.fieldName).toBe('toolNames')
20
+ expect(result.message).toBe(
21
+ "Non-empty spawnableAgents array requires the 'spawn_agents' tool. Add 'spawn_agents' to toolNames or remove spawnableAgents.",
22
+ )
23
+ })
24
+
25
+ test('strips agent name prefix', () => {
26
+ const result = formatValidationError(
27
+ 'Agent "demo" (demo.ts): Invalid input: expected string, received number',
28
+ )
29
+
30
+ expect(result.fieldName).toBeUndefined()
31
+ expect(result.message).toBe(
32
+ 'Invalid input: expected string, received number',
33
+ )
34
+ })
35
+
36
+ test('extracts field:message pattern', () => {
37
+ const result = formatValidationError(
38
+ 'instructions: Required field is missing',
39
+ )
40
+
41
+ expect(result.fieldName).toBe('instructions')
42
+ expect(result.message).toBe('Required field is missing')
43
+ })
44
+
45
+ test('handles messages without field patterns', () => {
46
+ const result = formatValidationError(
47
+ 'Schema validation failed: Generic error',
48
+ )
49
+
50
+ expect(result.fieldName).toBeUndefined()
51
+ expect(result.message).toBe('Generic error')
52
+ })
53
+
54
+ test('handles nested path from JSON error', () => {
55
+ const raw = `[
56
+ {
57
+ "path": ["outputSchema", "properties", "summary"],
58
+ "message": "Required"
59
+ }
60
+ ]`
61
+
62
+ const result = formatValidationError(raw)
63
+
64
+ expect(result.fieldName).toBe('outputSchema.properties.summary')
65
+ expect(result.message).toBe('Required')
66
+ })
67
+
68
+ test('handles empty string input', () => {
69
+ const result = formatValidationError('')
70
+
71
+ expect(result.fieldName).toBeUndefined()
72
+ expect(result.message).toBe('Unknown validation error')
73
+ })
74
+
75
+ test('handles JSON object (not array) with error', () => {
76
+ const raw = `{"path": ["field"], "message": "Invalid value"}`
77
+
78
+ const result = formatValidationError(raw)
79
+
80
+ expect(result.fieldName).toBe('field')
81
+ expect(result.message).toBe('Invalid value')
82
+ })
83
+
84
+ test('handles JSON with empty path array', () => {
85
+ const raw = `[{"path": [], "message": "Root level error"}]`
86
+
87
+ const result = formatValidationError(raw)
88
+
89
+ expect(result.fieldName).toBeUndefined()
90
+ expect(result.message).toBe('Root level error')
91
+ })
92
+
93
+ test('handles malformed JSON gracefully', () => {
94
+ const raw = '[{"path": ["field"], invalid json'
95
+
96
+ const result = formatValidationError(raw)
97
+
98
+ expect(result.fieldName).toBeUndefined()
99
+ expect(result.message).toBe('[{"path": ["field"], invalid json')
100
+ })
101
+
102
+ test('handles colon in message part correctly', () => {
103
+ const result = formatValidationError(
104
+ 'fieldName: Error: something went wrong',
105
+ )
106
+
107
+ expect(result.fieldName).toBe('fieldName')
108
+ expect(result.message).toBe('Error: something went wrong')
109
+ })
110
+
111
+ test('handles field pattern with no message after colon', () => {
112
+ const result = formatValidationError('fieldName:')
113
+
114
+ expect(result.fieldName).toBeUndefined()
115
+ expect(result.message).toBe('fieldName:')
116
+ })
117
+
118
+ test('handles JSON with missing message property', () => {
119
+ const raw = `[{"path": ["field"], "code": "custom"}]`
120
+
121
+ const result = formatValidationError(raw)
122
+
123
+ expect(result.fieldName).toBe('field')
124
+ expect(result.message).toBe(raw)
125
+ })
126
+ })
@@ -0,0 +1,101 @@
1
+ import { AnalyticsEvent } from '@anvil/common/constants/analytics-events'
2
+ import { afterEach, describe, expect, mock, test } from 'bun:test'
3
+
4
+ import {
5
+ initAnalytics,
6
+ resetAnalyticsState,
7
+ trackEvent,
8
+ type AnalyticsDeps,
9
+ } from '../analytics'
10
+ import { deliverWindowsTerminalFailure } from '../windows-terminal-health'
11
+
12
+ import type { LogRecordInput } from '@anvil/common/schemas/logs'
13
+
14
+ const FAILED_ANALYTICS_DEPS: AnalyticsDeps = {
15
+ env: {},
16
+ isProd: true,
17
+ createClient: mock(() => {
18
+ throw new Error(
19
+ 'PostHog client should not be created without configuration',
20
+ )
21
+ }),
22
+ generateAnonymousId: () => 'analytics-anonymous-id',
23
+ }
24
+
25
+ afterEach(() => {
26
+ resetAnalyticsState()
27
+ })
28
+
29
+ describe('Windows terminal-health delivery', () => {
30
+ test('queues and drains the bounded Axiom event when analytics initialization fails', async () => {
31
+ const analyticsRecords: LogRecordInput[] = []
32
+ resetAnalyticsState({
33
+ ...FAILED_ANALYTICS_DEPS,
34
+ enqueueClientLog: (record) => analyticsRecords.push(record),
35
+ })
36
+ expect(() => initAnalytics()).toThrow(
37
+ 'NEXT_PUBLIC_POSTHOG_API_KEY or NEXT_PUBLIC_POSTHOG_HOST_URL is not set',
38
+ )
39
+ const fallbackRecords: LogRecordInput[] = []
40
+ const drainClientLogs = mock(async () => {})
41
+ const properties = {
42
+ version: '0.0.142',
43
+ platform: 'win32' as const,
44
+ stage: 'arming' as const,
45
+ failureCode: 'timeout' as const,
46
+ }
47
+
48
+ await deliverWindowsTerminalFailure(
49
+ AnalyticsEvent.TERMINAL_WATCHDOG_FAILED,
50
+ properties,
51
+ {
52
+ trackEvent,
53
+ getAnonymousId: () => 'anonymous-install-id',
54
+ enqueueClientLog: (record) => fallbackRecords.push(record),
55
+ drainClientLogs,
56
+ },
57
+ )
58
+
59
+ expect(analyticsRecords).toEqual([
60
+ {
61
+ level: 'info',
62
+ event: AnalyticsEvent.TERMINAL_WATCHDOG_FAILED,
63
+ message: AnalyticsEvent.TERMINAL_WATCHDOG_FAILED,
64
+ client_session_id: 'analytics-anonymous-id',
65
+ data: properties,
66
+ },
67
+ ])
68
+ expect(fallbackRecords).toEqual([])
69
+ expect(drainClientLogs).toHaveBeenCalledTimes(1)
70
+ })
71
+
72
+ test('preserves the analytics mirror without queuing a duplicate', async () => {
73
+ const tracked = mock(() => {})
74
+ const enqueueClientLog = mock((_record: LogRecordInput) => {})
75
+ const drainClientLogs = mock(async () => {})
76
+ const properties = {
77
+ version: '0.0.142',
78
+ platform: 'win32' as const,
79
+ stage: 'spawn' as const,
80
+ failureCode: 'enoent' as const,
81
+ }
82
+
83
+ await deliverWindowsTerminalFailure(
84
+ AnalyticsEvent.TERMINAL_BROKER_SPAWN_FAILED,
85
+ properties,
86
+ {
87
+ trackEvent: tracked,
88
+ getAnonymousId: () => 'unused',
89
+ enqueueClientLog,
90
+ drainClientLogs,
91
+ },
92
+ )
93
+
94
+ expect(tracked).toHaveBeenCalledWith(
95
+ AnalyticsEvent.TERMINAL_BROKER_SPAWN_FAILED,
96
+ properties,
97
+ )
98
+ expect(enqueueClientLog).not.toHaveBeenCalled()
99
+ expect(drainClientLogs).toHaveBeenCalledTimes(1)
100
+ })
101
+ })
@@ -0,0 +1,127 @@
1
+ import { describe, test, expect, beforeEach, afterEach } from 'bun:test'
2
+ import * as fs from 'fs'
3
+ import * as os from 'os'
4
+ import * as path from 'path'
5
+
6
+ import { writeFileAtomic, writeFileAtomicAsync } from '../write-file-atomic'
7
+
8
+ let tempDir = ''
9
+
10
+ describe('writeFileAtomic', () => {
11
+ beforeEach(() => {
12
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'anvil-atomic-'))
13
+ })
14
+
15
+ afterEach(() => {
16
+ fs.rmSync(tempDir, { recursive: true, force: true })
17
+ })
18
+
19
+ test('writes a new file', () => {
20
+ const target = path.join(tempDir, 'out.json')
21
+
22
+ writeFileAtomic(target, '{"a":1}')
23
+
24
+ expect(fs.readFileSync(target, 'utf8')).toBe('{"a":1}')
25
+ })
26
+
27
+ test('replaces an existing file', () => {
28
+ const target = path.join(tempDir, 'out.json')
29
+ fs.writeFileSync(target, 'old content')
30
+
31
+ writeFileAtomic(target, 'new content')
32
+
33
+ expect(fs.readFileSync(target, 'utf8')).toBe('new content')
34
+ })
35
+
36
+ test('leaves no temp file behind on success', () => {
37
+ const target = path.join(tempDir, 'out.json')
38
+
39
+ writeFileAtomic(target, 'data')
40
+
41
+ expect(fs.readdirSync(tempDir)).toEqual(['out.json'])
42
+ })
43
+
44
+ test('cleans up the temp file and rethrows on failure', () => {
45
+ // Renaming a file over an existing directory fails on all platforms
46
+ const target = path.join(tempDir, 'target-dir')
47
+ fs.mkdirSync(target)
48
+
49
+ expect(() => writeFileAtomic(target, 'data')).toThrow()
50
+
51
+ expect(fs.readdirSync(tempDir)).toEqual(['target-dir'])
52
+ })
53
+
54
+ test('uses a unique temp name per write (no collision between calls)', () => {
55
+ // Two writes to different targets must not share a temp path, otherwise
56
+ // concurrent sync + async writes would tear each other. The temp name
57
+ // includes a random component, so back-to-back writes never collide.
58
+ const a = path.join(tempDir, 'a.json')
59
+ const b = path.join(tempDir, 'b.json')
60
+
61
+ writeFileAtomic(a, 'a')
62
+ writeFileAtomic(b, 'b')
63
+
64
+ expect(fs.readdirSync(tempDir).sort()).toEqual(['a.json', 'b.json'])
65
+ })
66
+ })
67
+
68
+ describe('writeFileAtomicAsync', () => {
69
+ beforeEach(() => {
70
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'anvil-atomic-async-'))
71
+ })
72
+
73
+ afterEach(() => {
74
+ fs.rmSync(tempDir, { recursive: true, force: true })
75
+ })
76
+
77
+ test('writes a new file', async () => {
78
+ const target = path.join(tempDir, 'out.json')
79
+
80
+ await writeFileAtomicAsync(target, '{"a":1}')
81
+
82
+ expect(fs.readFileSync(target, 'utf8')).toBe('{"a":1}')
83
+ })
84
+
85
+ test('replaces an existing file', async () => {
86
+ const target = path.join(tempDir, 'out.json')
87
+ fs.writeFileSync(target, 'old content')
88
+
89
+ await writeFileAtomicAsync(target, 'new content')
90
+
91
+ expect(fs.readFileSync(target, 'utf8')).toBe('new content')
92
+ })
93
+
94
+ test('leaves no temp file behind on success', async () => {
95
+ const target = path.join(tempDir, 'out.json')
96
+
97
+ await writeFileAtomicAsync(target, 'data')
98
+
99
+ expect(fs.readdirSync(tempDir)).toEqual(['out.json'])
100
+ })
101
+
102
+ test('cleans up the temp file and rejects on failure', async () => {
103
+ const target = path.join(tempDir, 'target-dir')
104
+ fs.mkdirSync(target)
105
+
106
+ await expect(writeFileAtomicAsync(target, 'data')).rejects.toThrow()
107
+
108
+ expect(fs.readdirSync(tempDir)).toEqual(['target-dir'])
109
+ })
110
+
111
+ test('concurrent writes to the same file do not tear (last write wins)', async () => {
112
+ const target = path.join(tempDir, 'out.json')
113
+
114
+ await Promise.all([
115
+ writeFileAtomicAsync(target, 'first'),
116
+ writeFileAtomicAsync(target, 'second'),
117
+ writeFileAtomicAsync(target, 'third'),
118
+ ])
119
+
120
+ // Whichever rename lands last wins, but the file is always one intact
121
+ // value — never a torn mix — and no temp files remain.
122
+ expect(['first', 'second', 'third']).toContain(
123
+ fs.readFileSync(target, 'utf8'),
124
+ )
125
+ expect(fs.readdirSync(tempDir)).toEqual(['out.json'])
126
+ })
127
+ })
@@ -0,0 +1,131 @@
1
+ /** Why the active agent run is being stopped. */
2
+ export type ActiveRunStopReason =
3
+ | 'user-interrupt'
4
+ | 'logout'
5
+ | 'new-chat'
6
+ | 'history-resume'
7
+ | 'session-transition'
8
+ | 'process-exit'
9
+
10
+ type ActiveRunQueuePolicy =
11
+ | 'pause-if-pending'
12
+ | 'clear-and-block'
13
+ | 'preserve-and-block'
14
+
15
+ /**
16
+ * Every reason aborts through the same run owner, which marks the message
17
+ * interrupted and checkpoints it. Process-exit callers additionally flush
18
+ * that checkpoint synchronously. Queue behavior is the only policy that
19
+ * varies here: context changes discard old prompts, a manual interrupt pauses
20
+ * them, and exit preserves them while blocking any new dequeue.
21
+ */
22
+ export const ACTIVE_RUN_QUEUE_POLICIES = {
23
+ 'user-interrupt': 'pause-if-pending',
24
+ logout: 'clear-and-block',
25
+ 'new-chat': 'clear-and-block',
26
+ 'history-resume': 'clear-and-block',
27
+ 'session-transition': 'clear-and-block',
28
+ 'process-exit': 'preserve-and-block',
29
+ } satisfies Record<ActiveRunStopReason, ActiveRunQueuePolicy>
30
+
31
+ export type ActiveRunQueueControls = {
32
+ pauseQueueIfPending: () => void
33
+ discardQueue: () => void
34
+ setCanProcessQueue: (canProcess: boolean) => void
35
+ }
36
+
37
+ /** Apply the reason matrix to the persistent runtime's latest queue state. */
38
+ export function applyActiveRunQueuePolicy(
39
+ reason: ActiveRunStopReason,
40
+ controls: ActiveRunQueueControls,
41
+ ): void {
42
+ const policy = ACTIVE_RUN_QUEUE_POLICIES[reason]
43
+ if (policy === 'pause-if-pending') {
44
+ controls.pauseQueueIfPending()
45
+ return
46
+ }
47
+ if (policy === 'clear-and-block') {
48
+ controls.discardQueue()
49
+ return
50
+ }
51
+ if (policy === 'preserve-and-block') {
52
+ // Freebuff exit can spend up to a second releasing the session seat. Keep
53
+ // queued prompts in memory, but do not let abort cleanup dequeue a new run
54
+ // during that window.
55
+ controls.setCanProcessQueue(false)
56
+ }
57
+ }
58
+
59
+ type ActiveRun = {
60
+ ownerId: string
61
+ stop: (reason: ActiveRunStopReason) => void
62
+ }
63
+
64
+ let activeRun: ActiveRun | null = null
65
+ let runtimeStopHandler: ((reason: ActiveRunStopReason) => void) | null = null
66
+
67
+ /**
68
+ * Install cleanup owned by the persistent ChatRuntime. It runs even while no
69
+ * SDK run exists, which is important for clearing a paused queue on /logout,
70
+ * /new, or a session reset.
71
+ */
72
+ export function registerActiveRunStopHandler(
73
+ handler: (reason: ActiveRunStopReason) => void,
74
+ ): () => void {
75
+ runtimeStopHandler = handler
76
+ return () => {
77
+ if (runtimeStopHandler === handler) runtimeStopHandler = null
78
+ }
79
+ }
80
+
81
+ /** Register the sole run that may currently mutate the active chat. */
82
+ export function registerActiveRun(
83
+ ownerId: string,
84
+ stop: (reason: ActiveRunStopReason) => void,
85
+ ): void {
86
+ const previousRun = activeRun
87
+ activeRun = { ownerId, stop }
88
+ if (previousRun && previousRun.ownerId !== ownerId) {
89
+ // This should only happen if an unexpected path bypassed the chain lock.
90
+ // Stop the displaced owner after installing the new one so its late
91
+ // owner-guarded cleanup cannot unregister the replacement.
92
+ try {
93
+ previousRun.stop('user-interrupt')
94
+ } catch {
95
+ // The new owner is already installed; never orphan it because cleanup
96
+ // for the displaced run failed.
97
+ }
98
+ }
99
+ }
100
+
101
+ /** Owner-guarded so a stale run settling cannot release a newer run. */
102
+ export function clearActiveRun(ownerId: string): void {
103
+ if (activeRun?.ownerId === ownerId) {
104
+ activeRun = null
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Stop the in-flight run, if any. Ownership is released before invoking the
110
+ * callback: repeated entry points are idempotent and the stopped run cannot
111
+ * consume a later reason intended for a newer run.
112
+ */
113
+ export function stopActiveRun(reason: ActiveRunStopReason): boolean {
114
+ const run = activeRun
115
+ if (run) activeRun = null
116
+
117
+ try {
118
+ run?.stop(reason)
119
+ } catch {
120
+ // Cancellation is a best-effort boundary used during fatal process
121
+ // cleanup too; one broken run callback must not block queue/terminal
122
+ // cleanup.
123
+ }
124
+ try {
125
+ runtimeStopHandler?.(reason)
126
+ } catch {
127
+ // The run is already detached. Keep cancellation idempotent and allow the
128
+ // initiating transition or process cleanup to continue.
129
+ }
130
+ return run !== null
131
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Activity Tracker Module
3
+ *
4
+ * A singleton module that tracks user activity in the terminal.
5
+ * Used by the ads feature and activity-aware query hooks.
6
+ */
7
+
8
+ type ActivityListener = (timestamp: number) => void
9
+
10
+ const listeners = new Set<ActivityListener>()
11
+ let lastActivityTime = Date.now()
12
+
13
+ /**
14
+ * Report that the user performed an activity.
15
+ * Call this when:
16
+ * - User types in the input field
17
+ * - User moves the mouse
18
+ * - User presses keyboard shortcuts
19
+ */
20
+ export function reportActivity(): void {
21
+ lastActivityTime = Date.now()
22
+ for (const listener of listeners) {
23
+ listener(lastActivityTime)
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Get the timestamp of the last user activity.
29
+ */
30
+ export function getLastActivityTime(): number {
31
+ return lastActivityTime
32
+ }
33
+
34
+ /**
35
+ * Check if the user is currently active (within the idle threshold).
36
+ * @param idleThresholdMs - Time in ms to consider user idle (default: 30 seconds)
37
+ */
38
+ export function isUserActive(idleThresholdMs: number = 30_000): boolean {
39
+ return Date.now() - lastActivityTime < idleThresholdMs
40
+ }
41
+
42
+ /**
43
+ * Get the time in ms since the last activity.
44
+ */
45
+ export function getIdleTime(): number {
46
+ return Date.now() - lastActivityTime
47
+ }
48
+
49
+ /**
50
+ * Subscribe to activity events.
51
+ * @returns Unsubscribe function
52
+ */
53
+ export function subscribeToActivity(callback: ActivityListener): () => void {
54
+ listeners.add(callback)
55
+ return () => {
56
+ listeners.delete(callback)
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Reset the activity tracker (mainly for testing).
62
+ */
63
+ export function resetActivityTracker(): void {
64
+ lastActivityTime = Date.now()
65
+ listeners.clear()
66
+ }
@@ -0,0 +1,87 @@
1
+ import { getAgentBaseName } from './message-block-helpers'
2
+
3
+ import type {
4
+ AgentContentBlock,
5
+ TextContentBlock,
6
+ ToolContentBlock,
7
+ } from '../types/chat'
8
+
9
+ const DEFAULT_BASHER_OUTPUT_PREVIEW_MAX_LENGTH = 120
10
+ const PREVIEW_ELLIPSIS = '...'
11
+
12
+ export function truncateToSingleLinePreview(
13
+ text: string,
14
+ maxLength = DEFAULT_BASHER_OUTPUT_PREVIEW_MAX_LENGTH,
15
+ ): string | undefined {
16
+ const singleLine = text.replace(/\s+/g, ' ').trim()
17
+ if (!singleLine) {
18
+ return undefined
19
+ }
20
+
21
+ if (singleLine.length <= maxLength) {
22
+ return singleLine
23
+ }
24
+
25
+ const previewLength = Math.max(0, maxLength - PREVIEW_ELLIPSIS.length)
26
+ return `${singleLine.slice(0, previewLength).trimEnd()}${PREVIEW_ELLIPSIS}`
27
+ }
28
+
29
+ export function getAgentDisplayPrompt(
30
+ agentBlock: AgentContentBlock,
31
+ ): string | undefined {
32
+ const initialPrompt = agentBlock.initialPrompt?.trim()
33
+ if (initialPrompt) {
34
+ return initialPrompt
35
+ }
36
+
37
+ if (getAgentBaseName(agentBlock.agentType) !== 'basher') {
38
+ return undefined
39
+ }
40
+
41
+ const whatToSummarize = agentBlock.params?.what_to_summarize
42
+ return typeof whatToSummarize === 'string' && whatToSummarize.trim()
43
+ ? whatToSummarize.trim()
44
+ : undefined
45
+ }
46
+
47
+ export function getBasherFinishedOutputPreview(
48
+ agentBlock: AgentContentBlock,
49
+ maxLength = DEFAULT_BASHER_OUTPUT_PREVIEW_MAX_LENGTH,
50
+ ): string | undefined {
51
+ if (
52
+ getAgentBaseName(agentBlock.agentType) !== 'basher' ||
53
+ agentBlock.status === 'running'
54
+ ) {
55
+ return undefined
56
+ }
57
+
58
+ const blocks = agentBlock.blocks ?? []
59
+ return (
60
+ truncateToSingleLinePreview(getTextOutput(blocks), maxLength) ??
61
+ truncateToSingleLinePreview(getCommandOutput(blocks), maxLength)
62
+ )
63
+ }
64
+
65
+ function getTextOutput(
66
+ blocks: NonNullable<AgentContentBlock['blocks']>,
67
+ ): string {
68
+ return blocks
69
+ .filter(
70
+ (block): block is TextContentBlock =>
71
+ block.type === 'text' && block.textType !== 'reasoning',
72
+ )
73
+ .map((block) => block.content)
74
+ .join('\n')
75
+ }
76
+
77
+ function getCommandOutput(
78
+ blocks: NonNullable<AgentContentBlock['blocks']>,
79
+ ): string {
80
+ return blocks
81
+ .filter(
82
+ (block): block is ToolContentBlock =>
83
+ block.type === 'tool' && block.toolName === 'run_terminal_command',
84
+ )
85
+ .map((block) => block.output ?? '')
86
+ .join('\n')
87
+ }
@@ -0,0 +1,27 @@
1
+ import type { AgentContentBlock } from '../types/chat'
2
+
3
+ export interface StatusInfo {
4
+ indicator: string
5
+ label: string
6
+ color: string
7
+ text: string
8
+ }
9
+
10
+ /** Get status indicator, label, color, and formatted text based on agent status */
11
+ export function getAgentStatusInfo(
12
+ status: AgentContentBlock['status'],
13
+ theme: { primary: string; foreground: string; muted: string },
14
+ ): StatusInfo {
15
+ switch (status) {
16
+ case 'running':
17
+ return { indicator: '●', label: 'running', color: theme.primary, text: '● running' }
18
+ case 'failed':
19
+ return { indicator: '✗', label: 'failed', color: 'red', text: '✗ failed' }
20
+ case 'complete':
21
+ return { indicator: '✓', label: 'completed', color: theme.foreground, text: 'completed ✓' }
22
+ case 'cancelled':
23
+ return { indicator: '⊘', label: 'cancelled', color: 'red', text: '⊘ cancelled' }
24
+ default:
25
+ return { indicator: '○', label: 'waiting', color: theme.muted, text: '○ waiting' }
26
+ }
27
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Utilities for parsing and normalizing agent identifiers
3
+ */
4
+
5
+ /**
6
+ * Extracts the simple agent ID from a potentially qualified ID.
7
+ * Handles formats like:
8
+ * - "my-agent" -> "my-agent"
9
+ * - "publisher/my-agent" -> "my-agent"
10
+ * - "publisher/my-agent@1.0.0" -> "my-agent"
11
+ */
12
+ export function getSimpleAgentId(qualifiedId: string): string {
13
+ return qualifiedId.split('/').pop()?.split('@')[0] ?? qualifiedId
14
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Identity of streamed AI-response messages, kept in a dependency-free leaf
3
+ * module so lightweight consumers (e.g. the ads hook) can discriminate real LLM
4
+ * answers from other 'ai'-variant messages without pulling in the full
5
+ * send-message helper graph.
6
+ */
7
+
8
+ /** Id prefix identifying streamed AI response shells. */
9
+ export const AI_MESSAGE_ID_PREFIX = 'ai-'
10
+
11
+ export const generateAiMessageId = (): string =>
12
+ `${AI_MESSAGE_ID_PREFIX}${Date.now()}-${Math.random().toString(16).slice(2)}`