@alumbwe/anvil 1.0.0 → 1.0.2

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 (530) hide show
  1. package/bin/anvil.js +2 -0
  2. package/dist/bundled.js +79559 -0
  3. package/dist/bundled.js.map +581 -0
  4. package/package.json +7 -4
  5. package/bin/anvil +0 -2
  6. package/src/__tests__/README.md +0 -256
  7. package/src/__tests__/bash-mode.test.ts +0 -442
  8. package/src/__tests__/cli-args.test.ts +0 -181
  9. package/src/__tests__/cli-harness.test.ts +0 -52
  10. package/src/__tests__/e2e/first-time-login.test.ts +0 -173
  11. package/src/__tests__/e2e/logout-relogin-flow.test.ts +0 -110
  12. package/src/__tests__/e2e/returning-user-auth.test.ts +0 -113
  13. package/src/__tests__/e2e-cli.test.ts +0 -183
  14. package/src/__tests__/helpers/launcher-disconnect-fixture.cjs +0 -176
  15. package/src/__tests__/helpers/mock-api-client.ts +0 -64
  16. package/src/__tests__/helpers/renderer-cleanup-fixture.tsx +0 -89
  17. package/src/__tests__/helpers/terminal-watchdog-fixture.ts +0 -75
  18. package/src/__tests__/home-directory-detection.test.ts +0 -213
  19. package/src/__tests__/integration/api-integration.test.ts +0 -288
  20. package/src/__tests__/integration/credentials-storage.test.ts +0 -486
  21. package/src/__tests__/integration/local-agents.test.ts +0 -1187
  22. package/src/__tests__/integration/login-flow-instrumentation.test.ts +0 -134
  23. package/src/__tests__/integration/login-polling-working.test.ts +0 -285
  24. package/src/__tests__/integration/queue-editing.test.tsx +0 -261
  25. package/src/__tests__/integration/usage-refresh-on-completion.test.ts +0 -168
  26. package/src/__tests__/integration-tmux.test.ts +0 -188
  27. package/src/__tests__/launcher-avx2-fallback.test.ts +0 -630
  28. package/src/__tests__/launcher-disconnect.test.ts +0 -104
  29. package/src/__tests__/mocks/hover-toggle-controller.ts +0 -71
  30. package/src/__tests__/open-tui-native-bundle.test.ts +0 -378
  31. package/src/__tests__/path-completion.test.ts +0 -243
  32. package/src/__tests__/project-files-chat-id.test.ts +0 -34
  33. package/src/__tests__/release/proxy-http-get.test.ts +0 -667
  34. package/src/__tests__/release/wrapper-safety.test.ts +0 -412
  35. package/src/__tests__/renderer-cleanup.test.ts +0 -174
  36. package/src/__tests__/rerender-perf.integration.test.ts +0 -283
  37. package/src/__tests__/terminal-reset-sequences.test.ts +0 -36
  38. package/src/__tests__/terminal-watchdog.test.ts +0 -388
  39. package/src/__tests__/test-utils.ts +0 -271
  40. package/src/__tests__/tmux-poc.ts +0 -150
  41. package/src/__tests__/unit/agent-mode-toggle.test.ts +0 -137
  42. package/src/__tests__/unit/copy-button.test.ts +0 -157
  43. package/src/__tests__/unit/create-run-config.test.ts +0 -66
  44. package/src/__tests__/unit/publish-confirmation.test.ts +0 -70
  45. package/src/__tests__/unit/segmented-control.test.ts +0 -152
  46. package/src/__tests__/utils/env.test.ts +0 -169
  47. package/src/__tests__/utils/project-picker.test.ts +0 -39
  48. package/src/agents/bundled-agents.generated.d.ts +0 -14
  49. package/src/agents/bundled-agents.generated.ts +0 -3692
  50. package/src/app.tsx +0 -399
  51. package/src/chat.tsx +0 -1698
  52. package/src/cli-args.ts +0 -134
  53. package/src/commands/__tests__/bash-command.test.ts +0 -445
  54. package/src/commands/__tests__/command-args.test.ts +0 -349
  55. package/src/commands/__tests__/copy-conversation-clipboard.test.ts +0 -108
  56. package/src/commands/__tests__/copy-conversation.test.ts +0 -226
  57. package/src/commands/__tests__/diagnostics-command.test.ts +0 -35
  58. package/src/commands/__tests__/freebuff-command-aliases.test.ts +0 -56
  59. package/src/commands/__tests__/image.test.ts +0 -99
  60. package/src/commands/__tests__/init.test.ts +0 -456
  61. package/src/commands/__tests__/new-command-rotates-chat.test.ts +0 -91
  62. package/src/commands/__tests__/process-diagnostics.test.ts +0 -54
  63. package/src/commands/__tests__/prompt-builders.test.ts +0 -28
  64. package/src/commands/__tests__/router-input.test.ts +0 -298
  65. package/src/commands/ads.ts +0 -44
  66. package/src/commands/command-registry.ts +0 -726
  67. package/src/commands/copy-conversation.ts +0 -392
  68. package/src/commands/help.ts +0 -13
  69. package/src/commands/image.ts +0 -20
  70. package/src/commands/init.ts +0 -121
  71. package/src/commands/process-diagnostics.ts +0 -120
  72. package/src/commands/prompt-builders.ts +0 -100
  73. package/src/commands/publish.ts +0 -203
  74. package/src/commands/router-utils.ts +0 -80
  75. package/src/commands/router.ts +0 -487
  76. package/src/commands/usage.ts +0 -26
  77. package/src/components/__tests__/ad-banner.test.tsx +0 -83
  78. package/src/components/__tests__/chat-input-bar.test.tsx +0 -130
  79. package/src/components/__tests__/freebuff-landing-heading-row.test.tsx +0 -91
  80. package/src/components/__tests__/freebuff-model-selector.test.tsx +0 -519
  81. package/src/components/__tests__/freebuff-offer-invariants.test.ts +0 -54
  82. package/src/components/__tests__/freebuff-takeover-prompt.test.tsx +0 -212
  83. package/src/components/__tests__/grid-layout.integration.test.tsx +0 -304
  84. package/src/components/__tests__/grid-layout.test.tsx +0 -1051
  85. package/src/components/__tests__/message-block.completion.test.tsx +0 -104
  86. package/src/components/__tests__/message-block.streaming.test.tsx +0 -78
  87. package/src/components/__tests__/message-with-agents.test.tsx +0 -579
  88. package/src/components/__tests__/multiline-input.test.tsx +0 -1132
  89. package/src/components/__tests__/queue-panel.test.tsx +0 -398
  90. package/src/components/__tests__/selectable-list.test.ts +0 -232
  91. package/src/components/__tests__/status-bar.test.tsx +0 -44
  92. package/src/components/__tests__/status-indicator.test.tsx +0 -204
  93. package/src/components/__tests__/terminal-thinking-orb.test.tsx +0 -111
  94. package/src/components/__tests__/user-error-banner.test.tsx +0 -102
  95. package/src/components/ad-banner.tsx +0 -323
  96. package/src/components/agent-checklist.tsx +0 -350
  97. package/src/components/agent-mode-toggle.tsx +0 -241
  98. package/src/components/anvil-mark.tsx +0 -52
  99. package/src/components/ask-user/__tests__/multiple-choice-form.test.ts +0 -432
  100. package/src/components/ask-user/__tests__/validation.test.ts +0 -213
  101. package/src/components/ask-user/components/accordion-question.tsx +0 -153
  102. package/src/components/ask-user/components/custom-answer-input.tsx +0 -66
  103. package/src/components/ask-user/components/options-list.tsx +0 -133
  104. package/src/components/ask-user/components/question-header.tsx +0 -80
  105. package/src/components/ask-user/components/question-option.tsx +0 -89
  106. package/src/components/ask-user/constants.ts +0 -38
  107. package/src/components/ask-user/index.tsx +0 -631
  108. package/src/components/ask-user/utils/validation.ts +0 -85
  109. package/src/components/attachment-card.tsx +0 -65
  110. package/src/components/blocks/agent-block-grid.tsx +0 -58
  111. package/src/components/blocks/agent-branch-item.tsx +0 -311
  112. package/src/components/blocks/agent-branch-wrapper.tsx +0 -493
  113. package/src/components/blocks/agent-list-branch.tsx +0 -77
  114. package/src/components/blocks/ask-user-branch.tsx +0 -81
  115. package/src/components/blocks/block-helpers.ts +0 -11
  116. package/src/components/blocks/blocks-renderer.tsx +0 -289
  117. package/src/components/blocks/content-with-markdown.tsx +0 -33
  118. package/src/components/blocks/image-block.tsx +0 -128
  119. package/src/components/blocks/implementor-row.tsx +0 -429
  120. package/src/components/blocks/single-block.tsx +0 -201
  121. package/src/components/blocks/thinking-block.tsx +0 -67
  122. package/src/components/blocks/tool-block-group.tsx +0 -57
  123. package/src/components/blocks/tool-branch.tsx +0 -206
  124. package/src/components/blocks/user-content-copy.tsx +0 -157
  125. package/src/components/bottom-banner.tsx +0 -136
  126. package/src/components/build-mode-buttons.tsx +0 -110
  127. package/src/components/button.tsx +0 -66
  128. package/src/components/chat-header.tsx +0 -77
  129. package/src/components/chat-history-screen.tsx +0 -413
  130. package/src/components/chat-input-bar.tsx +0 -523
  131. package/src/components/clickable-title-box.tsx +0 -66
  132. package/src/components/clickable.tsx +0 -119
  133. package/src/components/collapse-button.tsx +0 -34
  134. package/src/components/copy-button.tsx +0 -203
  135. package/src/components/elapsed-timer.tsx +0 -55
  136. package/src/components/error-boundary.tsx +0 -55
  137. package/src/components/feedback-container.tsx +0 -181
  138. package/src/components/feedback-icon-button.tsx +0 -89
  139. package/src/components/feedback-input-mode.tsx +0 -344
  140. package/src/components/file-attachment-card.tsx +0 -98
  141. package/src/components/freebuff-active-session-summary.tsx +0 -63
  142. package/src/components/freebuff-landing-screen.tsx +0 -865
  143. package/src/components/freebuff-model-selector.tsx +0 -1087
  144. package/src/components/freebuff-referral-banner.tsx +0 -604
  145. package/src/components/freebuff-superseded-screen.tsx +0 -62
  146. package/src/components/grid-layout.tsx +0 -80
  147. package/src/components/help-banner.tsx +0 -127
  148. package/src/components/highlighted-text.tsx +0 -52
  149. package/src/components/image-card.tsx +0 -167
  150. package/src/components/image-thumbnail.tsx +0 -114
  151. package/src/components/input-cursor.tsx +0 -77
  152. package/src/components/input-mode-banner.tsx +0 -57
  153. package/src/components/load-previous-button.tsx +0 -48
  154. package/src/components/login-modal.tsx +0 -475
  155. package/src/components/message-block.tsx +0 -346
  156. package/src/components/message-footer.tsx +0 -258
  157. package/src/components/message-with-agents.tsx +0 -506
  158. package/src/components/mode-divider.tsx +0 -43
  159. package/src/components/multiline-input.tsx +0 -1241
  160. package/src/components/out-of-credits-banner.tsx +0 -160
  161. package/src/components/pending-attachments-banner.tsx +0 -116
  162. package/src/components/pending-bash-message.tsx +0 -59
  163. package/src/components/progress-bar.tsx +0 -81
  164. package/src/components/project-picker-screen.tsx +0 -485
  165. package/src/components/publish-confirmation.tsx +0 -493
  166. package/src/components/publish-container.tsx +0 -687
  167. package/src/components/queue-panel.tsx +0 -310
  168. package/src/components/raised-pill.tsx +0 -95
  169. package/src/components/renderers/plan-box.tsx +0 -59
  170. package/src/components/review-screen.tsx +0 -112
  171. package/src/components/scroll-to-bottom-button.tsx +0 -34
  172. package/src/components/segmented-control.tsx +0 -187
  173. package/src/components/selectable-list.tsx +0 -248
  174. package/src/components/selected-chips.tsx +0 -80
  175. package/src/components/separator.tsx +0 -40
  176. package/src/components/session-ended-banner.tsx +0 -226
  177. package/src/components/shimmer-text.tsx +0 -248
  178. package/src/components/status-bar.tsx +0 -313
  179. package/src/components/subscription-limit-banner.tsx +0 -206
  180. package/src/components/suggested-prompts.tsx +0 -184
  181. package/src/components/suggestion-menu.tsx +0 -215
  182. package/src/components/terminal-command-display.tsx +0 -151
  183. package/src/components/terminal-link.tsx +0 -97
  184. package/src/components/terminal-thinking-orb.tsx +0 -95
  185. package/src/components/text-attachment-card.tsx +0 -77
  186. package/src/components/thinking.tsx +0 -109
  187. package/src/components/tools/__tests__/apply-patch.test.tsx +0 -84
  188. package/src/components/tools/__tests__/code-search.test.tsx +0 -45
  189. package/src/components/tools/__tests__/gravity-index.test.ts +0 -76
  190. package/src/components/tools/__tests__/render-ui.test.tsx +0 -92
  191. package/src/components/tools/__tests__/run-terminal-command.test.ts +0 -233
  192. package/src/components/tools/apply-patch.tsx +0 -95
  193. package/src/components/tools/code-search.tsx +0 -45
  194. package/src/components/tools/composio.tsx +0 -152
  195. package/src/components/tools/diff-viewer.tsx +0 -76
  196. package/src/components/tools/glob.tsx +0 -61
  197. package/src/components/tools/gravity-index.tsx +0 -77
  198. package/src/components/tools/list-directory.tsx +0 -61
  199. package/src/components/tools/read-docs.tsx +0 -37
  200. package/src/components/tools/read-files.tsx +0 -91
  201. package/src/components/tools/read-subtree.tsx +0 -44
  202. package/src/components/tools/read-url.tsx +0 -33
  203. package/src/components/tools/registry.ts +0 -126
  204. package/src/components/tools/render-ui.tsx +0 -122
  205. package/src/components/tools/run-terminal-command.tsx +0 -79
  206. package/src/components/tools/skill.tsx +0 -29
  207. package/src/components/tools/str-replace.tsx +0 -77
  208. package/src/components/tools/suggest-followups.tsx +0 -369
  209. package/src/components/tools/task-completed.tsx +0 -16
  210. package/src/components/tools/tool-call-item.tsx +0 -273
  211. package/src/components/tools/types.ts +0 -55
  212. package/src/components/tools/web-search.tsx +0 -33
  213. package/src/components/tools/write-file.tsx +0 -16
  214. package/src/components/tools/write-todos.tsx +0 -95
  215. package/src/components/top-banner.tsx +0 -178
  216. package/src/components/usage-banner.tsx +0 -244
  217. package/src/components/user-error-banner.tsx +0 -56
  218. package/src/components/validation-error-popover.tsx +0 -192
  219. package/src/contexts/__tests__/chat-runtime-context.test.tsx +0 -149
  220. package/src/contexts/chat-runtime-context.tsx +0 -240
  221. package/src/data/slash-commands.test.ts +0 -24
  222. package/src/data/slash-commands.ts +0 -249
  223. package/src/entry.ts +0 -12
  224. package/src/hooks/__tests__/holds-live-freebuff-slot.test.ts +0 -49
  225. package/src/hooks/__tests__/session-fetch-signal.test.ts +0 -63
  226. package/src/hooks/__tests__/use-activity-query.test.ts +0 -1000
  227. package/src/hooks/__tests__/use-ask-user-bridge.test.ts +0 -176
  228. package/src/hooks/__tests__/use-auth-query.test.ts +0 -21
  229. package/src/hooks/__tests__/use-chat-input-remount.test.tsx +0 -66
  230. package/src/hooks/__tests__/use-clipboard.test.tsx +0 -73
  231. package/src/hooks/__tests__/use-connection-status.test.ts +0 -112
  232. package/src/hooks/__tests__/use-directory-browser.test.ts +0 -203
  233. package/src/hooks/__tests__/use-gravity-ad.test.ts +0 -73
  234. package/src/hooks/__tests__/use-grid-layout.test.ts +0 -349
  235. package/src/hooks/__tests__/use-input-history.test.ts +0 -699
  236. package/src/hooks/__tests__/use-message-queue-editing.test.tsx +0 -209
  237. package/src/hooks/__tests__/use-message-queue.test.tsx +0 -71
  238. package/src/hooks/__tests__/use-path-tab-completion.test.ts +0 -315
  239. package/src/hooks/__tests__/use-queue-controls.test.ts +0 -68
  240. package/src/hooks/__tests__/use-queue-ui.test.tsx +0 -83
  241. package/src/hooks/__tests__/use-searchable-list.test.ts +0 -359
  242. package/src/hooks/__tests__/use-send-message-timer.test.ts +0 -146
  243. package/src/hooks/__tests__/use-suggestion-engine-mention.test.ts +0 -361
  244. package/src/hooks/__tests__/use-suggestion-engine.test.ts +0 -485
  245. package/src/hooks/__tests__/use-terminal-focus.test.ts +0 -66
  246. package/src/hooks/__tests__/use-terminal-layout.test.ts +0 -675
  247. package/src/hooks/__tests__/use-timeout.test.ts +0 -330
  248. package/src/hooks/__tests__/use-usage-query.test.ts +0 -502
  249. package/src/hooks/__tests__/use-user-details-query.test.ts +0 -195
  250. package/src/hooks/helpers/__tests__/send-message.test.ts +0 -1918
  251. package/src/hooks/helpers/send-message.ts +0 -654
  252. package/src/hooks/stream-state.ts +0 -83
  253. package/src/hooks/use-activity-query.ts +0 -668
  254. package/src/hooks/use-agent-validation.ts +0 -74
  255. package/src/hooks/use-ask-user-bridge.ts +0 -92
  256. package/src/hooks/use-auth-query.ts +0 -253
  257. package/src/hooks/use-auth-state.ts +0 -152
  258. package/src/hooks/use-chat-input.ts +0 -105
  259. package/src/hooks/use-chat-keyboard.ts +0 -321
  260. package/src/hooks/use-chat-messages.ts +0 -254
  261. package/src/hooks/use-chat-state.ts +0 -165
  262. package/src/hooks/use-chat-streaming.ts +0 -180
  263. package/src/hooks/use-chat-ui.ts +0 -131
  264. package/src/hooks/use-clipboard.ts +0 -151
  265. package/src/hooks/use-connection-status.ts +0 -142
  266. package/src/hooks/use-directory-browser.ts +0 -88
  267. package/src/hooks/use-elapsed-time.ts +0 -116
  268. package/src/hooks/use-event.ts +0 -34
  269. package/src/hooks/use-exit-handler.ts +0 -66
  270. package/src/hooks/use-fetch-login-url.ts +0 -65
  271. package/src/hooks/use-fingerprint.ts +0 -61
  272. package/src/hooks/use-freebuff-ctrl-c-exit.ts +0 -23
  273. package/src/hooks/use-freebuff-session-progress.ts +0 -34
  274. package/src/hooks/use-freebuff-session.ts +0 -806
  275. package/src/hooks/use-freebuff-streak-query.ts +0 -61
  276. package/src/hooks/use-gravity-ad.ts +0 -656
  277. package/src/hooks/use-grid-layout.ts +0 -73
  278. package/src/hooks/use-input-history.ts +0 -173
  279. package/src/hooks/use-login-keyboard-handlers.ts +0 -75
  280. package/src/hooks/use-login-polling.ts +0 -114
  281. package/src/hooks/use-logo.tsx +0 -161
  282. package/src/hooks/use-message-queue.ts +0 -387
  283. package/src/hooks/use-now.ts +0 -20
  284. package/src/hooks/use-path-tab-completion.ts +0 -88
  285. package/src/hooks/use-publish-mutation.ts +0 -60
  286. package/src/hooks/use-queue-controls.ts +0 -68
  287. package/src/hooks/use-queue-ui.ts +0 -74
  288. package/src/hooks/use-scroll-management.ts +0 -184
  289. package/src/hooks/use-searchable-list.ts +0 -98
  290. package/src/hooks/use-send-message.ts +0 -773
  291. package/src/hooks/use-sheen-animation.tsx +0 -88
  292. package/src/hooks/use-subscription-query.ts +0 -71
  293. package/src/hooks/use-suggestion-engine.ts +0 -783
  294. package/src/hooks/use-terminal-breakpoints.ts +0 -77
  295. package/src/hooks/use-terminal-dimensions.ts +0 -46
  296. package/src/hooks/use-terminal-focus.ts +0 -29
  297. package/src/hooks/use-terminal-layout.ts +0 -189
  298. package/src/hooks/use-theme.tsx +0 -140
  299. package/src/hooks/use-timeout.ts +0 -85
  300. package/src/hooks/use-update-preference.ts +0 -66
  301. package/src/hooks/use-usage-monitor.ts +0 -55
  302. package/src/hooks/use-usage-query.ts +0 -124
  303. package/src/hooks/use-user-details-query.ts +0 -93
  304. package/src/hooks/use-why-did-you-update.ts +0 -177
  305. package/src/index.tsx +0 -441
  306. package/src/init/__tests__/init-direnv.test.ts +0 -526
  307. package/src/init/init-app.ts +0 -34
  308. package/src/init/init-direnv.ts +0 -133
  309. package/src/login/constants.ts +0 -60
  310. package/src/login/login-flow.ts +0 -204
  311. package/src/login/plain-login.ts +0 -108
  312. package/src/login/utils.ts +0 -183
  313. package/src/native/ripgrep.ts +0 -80
  314. package/src/polyfills/bun-strip-ansi.ts +0 -12
  315. package/src/pre-init/tree-sitter-wasm.ts +0 -96
  316. package/src/project-files.ts +0 -102
  317. package/src/smoke/terminal-command-broker.ts +0 -439
  318. package/src/state/__tests__/chat-store-focus.test.ts +0 -22
  319. package/src/state/__tests__/feedback-store.test.ts +0 -257
  320. package/src/state/chat-history-store.ts +0 -30
  321. package/src/state/chat-store.ts +0 -544
  322. package/src/state/feedback-store.ts +0 -160
  323. package/src/state/freebuff-model-store.ts +0 -42
  324. package/src/state/freebuff-session-store.ts +0 -53
  325. package/src/state/login-store.ts +0 -150
  326. package/src/state/message-block-store.ts +0 -149
  327. package/src/state/publish-store.ts +0 -146
  328. package/src/state/queue-panel-store.ts +0 -26
  329. package/src/state/review-store.ts +0 -24
  330. package/src/testing/env.ts +0 -46
  331. package/src/types/chat-state.ts +0 -15
  332. package/src/types/chat.ts +0 -241
  333. package/src/types/contracts/send-message.ts +0 -12
  334. package/src/types/env.ts +0 -98
  335. package/src/types/freebuff-session.ts +0 -17
  336. package/src/types/function-params.ts +0 -28
  337. package/src/types/react19-compat.d.ts +0 -19
  338. package/src/types/store.ts +0 -111
  339. package/src/types/theme-system.ts +0 -144
  340. package/src/types/utils.ts +0 -3
  341. package/src/utils/__tests__/active-run.test.ts +0 -140
  342. package/src/utils/__tests__/activity-tracker.test.ts +0 -440
  343. package/src/utils/__tests__/agent-display.test.ts +0 -139
  344. package/src/utils/__tests__/analytics-client.test.ts +0 -262
  345. package/src/utils/__tests__/anonymous-id.test.ts +0 -74
  346. package/src/utils/__tests__/anvil-api.test.ts +0 -578
  347. package/src/utils/__tests__/arrays.test.ts +0 -264
  348. package/src/utils/__tests__/bash-context-processor.test.ts +0 -38
  349. package/src/utils/__tests__/block-processor.test.ts +0 -810
  350. package/src/utils/__tests__/chat-history.test.ts +0 -203
  351. package/src/utils/__tests__/chat-input-key-intercept.test.ts +0 -40
  352. package/src/utils/__tests__/chat-meta.test.ts +0 -130
  353. package/src/utils/__tests__/clipboard-linux.test.ts +0 -359
  354. package/src/utils/__tests__/clipboard.test.ts +0 -910
  355. package/src/utils/__tests__/code-search-summary.test.ts +0 -84
  356. package/src/utils/__tests__/collapse-helpers.test.ts +0 -1109
  357. package/src/utils/__tests__/error-handling.test.ts +0 -658
  358. package/src/utils/__tests__/exit-cleanly.test.ts +0 -110
  359. package/src/utils/__tests__/feedback-helpers.test.ts +0 -444
  360. package/src/utils/__tests__/feedback-submission.test.ts +0 -26
  361. package/src/utils/__tests__/fetch-usage.test.ts +0 -355
  362. package/src/utils/__tests__/fingerprint.test.ts +0 -144
  363. package/src/utils/__tests__/fixtures/terminal-command-broker-child.ts +0 -7
  364. package/src/utils/__tests__/fixtures/terminal-command-broker-entry.ts +0 -3
  365. package/src/utils/__tests__/fixtures/terminal-command-broker-owner.ts +0 -33
  366. package/src/utils/__tests__/format-elapsed-time.test.ts +0 -103
  367. package/src/utils/__tests__/format-timeout.test.ts +0 -87
  368. package/src/utils/__tests__/freebuff-instance-owner.test.ts +0 -69
  369. package/src/utils/__tests__/freebuff-model-navigation.test.ts +0 -102
  370. package/src/utils/__tests__/freebuff-premium-reset.test.ts +0 -79
  371. package/src/utils/__tests__/freebuff-referral-cache.test.ts +0 -116
  372. package/src/utils/__tests__/freebuff-session-api.test.ts +0 -215
  373. package/src/utils/__tests__/freebuff-session-display.test.ts +0 -23
  374. package/src/utils/__tests__/freebuff-streak-line.test.ts +0 -217
  375. package/src/utils/__tests__/image-dimensions.test.ts +0 -249
  376. package/src/utils/__tests__/image-processor.test.ts +0 -209
  377. package/src/utils/__tests__/implementor-helpers.test.ts +0 -1458
  378. package/src/utils/__tests__/keyboard-actions.test.ts +0 -660
  379. package/src/utils/__tests__/layout-helpers.test.ts +0 -36
  380. package/src/utils/__tests__/lazy-response-ads.test.ts +0 -167
  381. package/src/utils/__tests__/log-shipper.test.ts +0 -53
  382. package/src/utils/__tests__/markdown-renderer.test.tsx +0 -421
  383. package/src/utils/__tests__/message-block-helpers.test.ts +0 -1263
  384. package/src/utils/__tests__/message-updater.test.ts +0 -645
  385. package/src/utils/__tests__/osc-timeout-scenarios.test.ts +0 -172
  386. package/src/utils/__tests__/pending-attachments.test.ts +0 -277
  387. package/src/utils/__tests__/polling-backoff.test.ts +0 -51
  388. package/src/utils/__tests__/queue-panel-actions.test.ts +0 -148
  389. package/src/utils/__tests__/run-state-storage.test.ts +0 -919
  390. package/src/utils/__tests__/safe-json.test.ts +0 -77
  391. package/src/utils/__tests__/sdk-event-handlers.test.ts +0 -685
  392. package/src/utils/__tests__/send-message-helpers.test.ts +0 -1898
  393. package/src/utils/__tests__/send-message-timer.test.ts +0 -64
  394. package/src/utils/__tests__/settings.test.ts +0 -99
  395. package/src/utils/__tests__/strings.test.ts +0 -192
  396. package/src/utils/__tests__/terminal-color-detection.test.ts +0 -425
  397. package/src/utils/__tests__/terminal-command-broker.test.ts +0 -528
  398. package/src/utils/__tests__/terminal-enter-detection.test.ts +0 -77
  399. package/src/utils/__tests__/terminal-io.test.ts +0 -137
  400. package/src/utils/__tests__/terminal-protocol-controller.test.ts +0 -119
  401. package/src/utils/__tests__/text-layout.test.ts +0 -82
  402. package/src/utils/__tests__/theme-platform-detection.test.ts +0 -125
  403. package/src/utils/__tests__/think-tag-parser.test.ts +0 -159
  404. package/src/utils/__tests__/trace-writer.test.ts +0 -223
  405. package/src/utils/__tests__/trim-chat-logs.test.ts +0 -78
  406. package/src/utils/__tests__/usage-banner-state.test.ts +0 -298
  407. package/src/utils/__tests__/validation-error-formatting.test.ts +0 -126
  408. package/src/utils/__tests__/windows-terminal-health.test.ts +0 -101
  409. package/src/utils/__tests__/write-file-atomic.test.ts +0 -127
  410. package/src/utils/active-run.ts +0 -131
  411. package/src/utils/activity-tracker.ts +0 -66
  412. package/src/utils/agent-display.ts +0 -87
  413. package/src/utils/agent-helpers.ts +0 -27
  414. package/src/utils/agent-id-utils.ts +0 -14
  415. package/src/utils/ai-message-id.ts +0 -12
  416. package/src/utils/analytics.ts +0 -361
  417. package/src/utils/anonymous-id.ts +0 -87
  418. package/src/utils/anvil-api.ts +0 -612
  419. package/src/utils/anvil-client.ts +0 -217
  420. package/src/utils/arrays.ts +0 -117
  421. package/src/utils/auth.ts +0 -258
  422. package/src/utils/bash-context-processor.ts +0 -47
  423. package/src/utils/bash-messages.ts +0 -108
  424. package/src/utils/block-margins.ts +0 -35
  425. package/src/utils/block-operations.ts +0 -523
  426. package/src/utils/block-processor.ts +0 -213
  427. package/src/utils/chat-history.ts +0 -253
  428. package/src/utils/chat-input-key-intercept.ts +0 -52
  429. package/src/utils/chat-meta.ts +0 -90
  430. package/src/utils/chat-scroll-accel.ts +0 -151
  431. package/src/utils/clipboard-image.ts +0 -588
  432. package/src/utils/clipboard.ts +0 -397
  433. package/src/utils/code-search-summary.ts +0 -70
  434. package/src/utils/collapse-helpers.ts +0 -256
  435. package/src/utils/config-dir.ts +0 -24
  436. package/src/utils/constants.ts +0 -190
  437. package/src/utils/create-event-handler-state.ts +0 -73
  438. package/src/utils/create-run-config.ts +0 -121
  439. package/src/utils/detect-shell.ts +0 -116
  440. package/src/utils/directory-browser.ts +0 -72
  441. package/src/utils/engagement.ts +0 -55
  442. package/src/utils/env.ts +0 -92
  443. package/src/utils/error-handling.ts +0 -255
  444. package/src/utils/error-messages.ts +0 -62
  445. package/src/utils/exit-cleanly.ts +0 -86
  446. package/src/utils/feedback-helpers.ts +0 -103
  447. package/src/utils/feedback-submission.ts +0 -22
  448. package/src/utils/fetch-usage.ts +0 -76
  449. package/src/utils/fingerprint.ts +0 -245
  450. package/src/utils/format-elapsed-time.ts +0 -33
  451. package/src/utils/format-session-units.ts +0 -6
  452. package/src/utils/format-timeout.ts +0 -28
  453. package/src/utils/format-validation-errors-for-message.ts +0 -84
  454. package/src/utils/freebuff-agent-selection.ts +0 -33
  455. package/src/utils/freebuff-instance-owner.ts +0 -66
  456. package/src/utils/freebuff-model-navigation.ts +0 -50
  457. package/src/utils/freebuff-premium-reset.ts +0 -55
  458. package/src/utils/freebuff-referral-cache.ts +0 -51
  459. package/src/utils/freebuff-session-api.ts +0 -243
  460. package/src/utils/freebuff-session-display.ts +0 -21
  461. package/src/utils/freebuff-streak-line.ts +0 -93
  462. package/src/utils/git.ts +0 -17
  463. package/src/utils/helpers.ts +0 -47
  464. package/src/utils/image-display.ts +0 -67
  465. package/src/utils/image-handler.ts +0 -325
  466. package/src/utils/image-processor.ts +0 -133
  467. package/src/utils/image-thumbnail.ts +0 -78
  468. package/src/utils/implementor-helpers.ts +0 -787
  469. package/src/utils/input-modes.ts +0 -174
  470. package/src/utils/keyboard-actions.ts +0 -392
  471. package/src/utils/keypad-keys.ts +0 -47
  472. package/src/utils/layout-helpers.ts +0 -33
  473. package/src/utils/lazy-response-ads.ts +0 -1
  474. package/src/utils/local-agent-registry.ts +0 -474
  475. package/src/utils/log-shipper.ts +0 -126
  476. package/src/utils/logger.ts +0 -327
  477. package/src/utils/markdown-renderer.tsx +0 -1090
  478. package/src/utils/math.ts +0 -3
  479. package/src/utils/message-block-helpers.ts +0 -684
  480. package/src/utils/message-history.ts +0 -143
  481. package/src/utils/message-tree-utils.ts +0 -67
  482. package/src/utils/message-updater.ts +0 -279
  483. package/src/utils/open-file.ts +0 -146
  484. package/src/utils/open-url.ts +0 -63
  485. package/src/utils/path-completion.ts +0 -111
  486. package/src/utils/path-helpers.ts +0 -38
  487. package/src/utils/pending-attachments.ts +0 -360
  488. package/src/utils/polling-backoff.ts +0 -59
  489. package/src/utils/project-picker.ts +0 -12
  490. package/src/utils/queue-panel-actions.ts +0 -70
  491. package/src/utils/recent-projects.ts +0 -156
  492. package/src/utils/renderer-cleanup.ts +0 -233
  493. package/src/utils/response-ad-positions.ts +0 -1
  494. package/src/utils/run-state-storage.ts +0 -603
  495. package/src/utils/safe-json.ts +0 -153
  496. package/src/utils/sdk-event-handlers.ts +0 -571
  497. package/src/utils/send-message-helpers.ts +0 -210
  498. package/src/utils/send-message-timer.ts +0 -110
  499. package/src/utils/settings.ts +0 -226
  500. package/src/utils/skill-registry.ts +0 -94
  501. package/src/utils/spawn-agent-matcher.ts +0 -55
  502. package/src/utils/status-indicator-state.ts +0 -107
  503. package/src/utils/stream-chunk-processor.ts +0 -57
  504. package/src/utils/strings.ts +0 -271
  505. package/src/utils/subscription.ts +0 -31
  506. package/src/utils/syntax-highlighter.tsx +0 -19
  507. package/src/utils/terminal-color-detection.ts +0 -474
  508. package/src/utils/terminal-command-broker.ts +0 -467
  509. package/src/utils/terminal-enter-detection.ts +0 -63
  510. package/src/utils/terminal-images.ts +0 -229
  511. package/src/utils/terminal-io.ts +0 -85
  512. package/src/utils/terminal-protocol-controller.ts +0 -173
  513. package/src/utils/terminal-reset-sequences.ts +0 -33
  514. package/src/utils/terminal-title.ts +0 -84
  515. package/src/utils/terminal-watchdog.ts +0 -366
  516. package/src/utils/text-layout.ts +0 -149
  517. package/src/utils/theme-config.ts +0 -141
  518. package/src/utils/theme-system.ts +0 -1206
  519. package/src/utils/think-tag-parser.ts +0 -95
  520. package/src/utils/time-format.ts +0 -21
  521. package/src/utils/trace-writer.ts +0 -162
  522. package/src/utils/ui-constants.ts +0 -69
  523. package/src/utils/update-checker.ts +0 -110
  524. package/src/utils/usage-banner-state.ts +0 -146
  525. package/src/utils/validation-error-formatting.ts +0 -86
  526. package/src/utils/validation-error-helpers.ts +0 -19
  527. package/src/utils/windows-terminal-health.ts +0 -124
  528. package/src/utils/word-wrap-utils.ts +0 -53
  529. package/src/utils/write-file-atomic.ts +0 -54
  530. package/src/utils/yield-to-event-loop.ts +0 -9
@@ -1,806 +0,0 @@
1
- import { isDirectDeepSeekMode } from '@anvil/common/constants/deepseek-direct'
2
- import {
3
- FALLBACK_FREEBUFF_MODEL_ID,
4
- getFreebuffModel,
5
- isFreebuffLimitedOfferModelId,
6
- LIMITED_FREEBUFF_MODEL_ID,
7
- resolveFreebuffModelForAccessTier,
8
- } from '@anvil/common/constants/freebuff-models'
9
- import {
10
- getLimitedModelOffers,
11
- getRateLimitsByModel,
12
- getReferralInfo,
13
- } from '@anvil/common/types/freebuff-session'
14
- import { useEffect } from 'react'
15
-
16
- import {
17
- getSelectedFreebuffModel,
18
- useFreebuffModelStore,
19
- } from '../state/freebuff-model-store'
20
- import { useChatStore } from '../state/chat-store'
21
- import { useFreebuffSessionStore } from '../state/freebuff-session-store'
22
- import { getAuthTokenDetails } from '../utils/auth'
23
- import { stopActiveRun } from '../utils/active-run'
24
- import { IS_FREEBUFF } from '../utils/constants'
25
- import {
26
- isFreebuffInstanceOwnedByDeadLocalProcess,
27
- recordFreebuffInstanceOwner,
28
- } from '../utils/freebuff-instance-owner'
29
- import { logger } from '../utils/logger'
30
- import { getSystemMessage } from '../utils/message-history'
31
- import {
32
- clearReferralCache,
33
- getCachedReferral,
34
- rememberReferral,
35
- } from '../utils/freebuff-referral-cache'
36
- import {
37
- callFreebuffSession,
38
- classifyFreebuffSessionRequestFailure,
39
- FreebuffSessionRequestError,
40
- holdsLiveFreebuffSlot,
41
- isFreebuffSessionTimeoutError,
42
- mergeCompactActiveSession,
43
- releaseFreebuffSlot,
44
- } from '../utils/freebuff-session-api'
45
- import {
46
- failedPollDelayMs,
47
- jitterPollIntervalMs,
48
- } from '../utils/polling-backoff'
49
- import { saveFreebuffModelPreference } from '../utils/settings'
50
-
51
- import type { FreebuffSessionResponse } from '../types/freebuff-session'
52
- import type {
53
- FreebuffCountryBlockReason,
54
- FreebuffIpPrivacySignal,
55
- } from '@anvil/common/types/freebuff-session'
56
-
57
- const POLL_INTERVAL_ACTIVE_MS = 30_000
58
-
59
- /** Play the terminal bell so users get an audible notification on admission. */
60
- const playAdmissionSound = () => {
61
- try {
62
- process.stdout.write('\x07')
63
- } catch {
64
- // Silent fallback — some terminals/pipes disallow writing to stdout.
65
- }
66
- }
67
-
68
- /** Picks the poll delay after a successful tick. Returns null when the state
69
- * is terminal (no further polling). */
70
- function nextDelayMs(next: FreebuffSessionResponse): number | null {
71
- const activeCadenceMs = jitterPollIntervalMs({
72
- intervalMs: POLL_INTERVAL_ACTIVE_MS,
73
- })
74
- switch (next.status) {
75
- case 'active':
76
- // Poll at the normal cadence, but ensure we land just after
77
- // `expires_at` so the transition shows up promptly instead of leaving
78
- // the countdown stuck at 0 for up to a full interval.
79
- return Math.max(
80
- 1_000,
81
- Math.min(activeCadenceMs, next.remainingMs + 1_000),
82
- )
83
- case 'ended':
84
- // Inside the grace window we keep checking so the post-grace transition
85
- // (server returns `none`, we synthesize ended-no-instanceId) is prompt.
86
- return next.instanceId ? activeCadenceMs : null
87
- case 'none':
88
- case 'superseded':
89
- case 'takeover_prompt':
90
- case 'country_blocked':
91
- case 'banned':
92
- case 'model_locked':
93
- case 'rate_limited':
94
- case 'spend_limited':
95
- case 'ip_capped':
96
- case 'model_unavailable':
97
- case 'premium_slot_taken':
98
- return null
99
- }
100
- }
101
-
102
- // --- Poll-loop control surface ---------------------------------------------
103
- //
104
- // The hook below registers a controller object here on mount; module-level
105
- // imperative functions (restart / mark superseded / mark ended / etc.) talk
106
- // to it without going through React. Non-React callers (chat-completions
107
- // gate, exit paths) hit those functions directly.
108
-
109
- /** How the next tick should behave after a forced restart.
110
- * - 'rejoin' → POST: claim/rotate a seat (used after explicit end-and-rejoin
111
- * or when the chat gate kicks us back to the queue).
112
- * - 'landing' → GET: drop to the model-picker (status 'none') so the user
113
- * reconfirms a model before rejoining. */
114
- type RestartMode = 'rejoin' | 'landing'
115
-
116
- interface PollController {
117
- /** Cancel the in-flight tick + timer and start a fresh one in `mode`. */
118
- restart: (mode: RestartMode) => Promise<void>
119
- apply: (next: FreebuffSessionResponse) => void
120
- abort: () => void
121
- }
122
-
123
- let controller: PollController | null = null
124
-
125
- /**
126
- * The model of the most recent EXPLICIT user pick (startFreebuffSession),
127
- * consumed by the first server response that follows it. Lets the
128
- * `model_locked` branch tell a deliberate pick apart from a background
129
- * rejoin/race: only the former deserves a visible explanation. Cleared on
130
- * every response so a stale pick can never annotate a later, unrelated lock.
131
- */
132
- let pendingExplicitPickModel: string | null = null
133
-
134
- /** Read the current instance id for outgoing chat requests. Defined via
135
- * `holdsLiveFreebuffSlot` so the two can't drift: an id exists exactly while
136
- * we hold a live slot (active, or `ended` inside the server-side grace
137
- * window where the row stays alive until `expires_at + grace`). */
138
- export function getFreebuffInstanceId(): string | undefined {
139
- // Direct-to-DeepSeek mode has no server-side session; hand back a stable
140
- // local id so the chat gate (which requires a live slot) lets messages
141
- // through instead of holding them for a rejoin that can never happen.
142
- if (isDirectDeepSeekMode()) return 'local-deepseek'
143
- const current = useFreebuffSessionStore.getState().session
144
- if (!current || !holdsLiveFreebuffSlot(current)) return undefined
145
- return 'instanceId' in current ? current.instanceId : undefined
146
- }
147
-
148
- /** True when the session represents a server-side slot the caller is
149
- * holding (active, or in the post-expiry grace window with a live
150
- * instance id). Chat requests are only admissible in these states — once
151
- * the slot is gone, `getFreebuffInstanceId` returns undefined and the
152
- * server rejects the request — so the message queue gates on this before
153
- * firing queued work. Same predicate gates DELETE on exit: outside these
154
- * states there is no server row to release. */
155
- function toLandingSession(
156
- current: FreebuffSessionResponse | null,
157
- ): Extract<FreebuffSessionResponse, { status: 'none' }> {
158
- const accessTier =
159
- current && 'accessTier' in current ? current.accessTier : undefined
160
- const rateLimitsByModel = getRateLimitsByModel(current)
161
- const referral = accessTier
162
- ? (getReferralInfo(current) ?? getCachedReferral(accessTier))
163
- : undefined
164
- const countryCode =
165
- current && 'countryCode' in current ? current.countryCode : undefined
166
- const countryBlockReason =
167
- current && 'countryBlockReason' in current
168
- ? current.countryBlockReason
169
- : undefined
170
- const ipPrivacySignals =
171
- current && 'ipPrivacySignals' in current
172
- ? current.ipPrivacySignals
173
- : undefined
174
- // Carried over so the picker doesn't lose the limited-offer row for the one
175
- // frame between synthesizing this state and the GET that refreshes it. The
176
- // GET is authoritative: if the wave has since been spent, the next response
177
- // simply omits the offer and the row disappears.
178
- const limitedModelOffers = getLimitedModelOffers(current)
179
-
180
- return {
181
- status: 'none',
182
- ...(accessTier ? { accessTier } : {}),
183
- ...(rateLimitsByModel ? { rateLimitsByModel } : {}),
184
- ...(referral ? { referral } : {}),
185
- ...(limitedModelOffers.length > 0 ? { limitedModelOffers } : {}),
186
- ...(countryCode ? { countryCode } : {}),
187
- ...(countryBlockReason ? { countryBlockReason } : {}),
188
- ...(ipPrivacySignals ? { ipPrivacySignals } : {}),
189
- }
190
- }
191
-
192
- interface RestartOpts {
193
- resetChat?: boolean
194
- /** DELETE the held slot before restarting so the next POST starts clean. */
195
- releaseSlot?: boolean
196
- }
197
-
198
- async function restartFreebuffSession(
199
- mode: RestartMode,
200
- opts: RestartOpts = {},
201
- ): Promise<void> {
202
- if (!IS_FREEBUFF) return
203
- // A reset changes chat ownership. Stop and checkpoint the old run before
204
- // resetting its store so late deltas cannot land in the next session.
205
- if (opts.resetChat) {
206
- stopActiveRun('session-transition')
207
- useChatStore.getState().reset()
208
- }
209
- // Halt the running poll loop before we touch local stores or DELETE the
210
- // slot. Otherwise an in-flight GET could land mid-reset and overwrite
211
- // state, or the next scheduled tick could fire between DELETE and
212
- // restart() with stale assumptions. restart() re-aborts and re-arms
213
- // below; the extra abort here is cheap.
214
- controller?.abort()
215
- if (opts.releaseSlot) await releaseFreebuffSlot()
216
- await controller?.restart(mode)
217
- }
218
-
219
- /**
220
- * Re-POST to the server (rejoining the queue / rotating the instance id).
221
- * Pass `resetChat: true` to also wipe local chat history — used when
222
- * rejoining after a session ended so the next admitted session starts fresh.
223
- */
224
- export function refreshFreebuffSession(
225
- opts: { resetChat?: boolean } = {},
226
- ): Promise<void> {
227
- return restartFreebuffSession('rejoin', { resetChat: opts.resetChat })
228
- }
229
-
230
- /**
231
- * Drop back to the pre-join landing state (model picker) instead of auto
232
- * re-queuing. Used after a session ends: the user lands on the picker so
233
- * they consciously choose a model and hit Enter to join, rather than being
234
- * silently re-queued for whatever model they last used.
235
- */
236
- export function returnToFreebuffLanding(
237
- opts: { resetChat?: boolean } = {},
238
- ): Promise<void> {
239
- return restartFreebuffSession('landing', {
240
- resetChat: opts.resetChat,
241
- releaseSlot: true,
242
- })
243
- }
244
-
245
- /** Refresh picker-only metadata (quota and queue depths) while staying on the
246
- * model selection screen. Used when a midnight-Pacific session quota reset
247
- * passes while the landing screen is open. */
248
- export function refreshFreebuffLandingMetadata(): Promise<void> {
249
- return restartFreebuffSession('landing')
250
- }
251
-
252
- /**
253
- * Start a session on `model` (admitted immediately server-side). Dual-purpose:
254
- * - First start: called from the pre-chat landing picker. The session starts
255
- * at `none` (GET-only); this is the user's explicit commitment to enter.
256
- * - Switch: called when the user picks a different model from the landing
257
- * screen. The server admits them on the new model right away.
258
- *
259
- * If the server has already admitted them on a different model, it responds
260
- * with `model_locked`; because this is a deliberate pick, the tick loop ends
261
- * that session and re-claims on the requested model (see the model_locked
262
- * branch). Background rejoins hitting the same lock revert silently instead.
263
- */
264
- export function startFreebuffSession(model: string): Promise<void> {
265
- if (!IS_FREEBUFF) return Promise.resolve()
266
- // This is the only explicit user-pick path (called from the picker on
267
- // click / Enter), so persistence belongs here — and ONLY here. Server-
268
- // driven flips (`model_locked`, `model_unavailable`, takeover) go
269
- // through `setSelectedModel` directly, which never writes to disk.
270
- const current = useFreebuffSessionStore.getState().session
271
- const accessTier =
272
- current && 'accessTier' in current ? current.accessTier : 'full'
273
- const resolved = resolveFreebuffModelForAccessTier(model, accessTier)
274
- // Remember that the next POST is a deliberate pick, so a `model_locked`
275
- // rejection explains itself in chat instead of reverting silently.
276
- pendingExplicitPickModel = resolved
277
- useFreebuffModelStore.getState().setSelectedModel(resolved)
278
- saveFreebuffModelPreference(resolved)
279
- return restartFreebuffSession('rejoin')
280
- }
281
-
282
- let takeoverInFlight: Promise<void> | null = null
283
-
284
- export function takeOverFreebuffSession(): Promise<void> {
285
- if (!IS_FREEBUFF) return Promise.resolve()
286
- if (takeoverInFlight) return takeoverInFlight
287
-
288
- const { session } = useFreebuffSessionStore.getState()
289
- if (session?.status !== 'takeover_prompt') {
290
- return Promise.resolve()
291
- }
292
-
293
- useFreebuffModelStore.getState().setSelectedModel(session.model)
294
- takeoverInFlight = restartFreebuffSession('rejoin').finally(() => {
295
- takeoverInFlight = null
296
- })
297
- return takeoverInFlight
298
- }
299
-
300
- export function markFreebuffSessionSuperseded(): void {
301
- if (!IS_FREEBUFF) return
302
- controller?.abort()
303
- controller?.apply({ status: 'superseded' })
304
- }
305
-
306
- /** Flip into the terminal `country_blocked` state from outside the poll loop.
307
- * Used when the chat-completions gate rejects on country even though the
308
- * session-level country check did not catch the request first.
309
- * Transitioning the session state here unmounts the Chat surface in favor of
310
- * the landing screen's country_blocked message, so the user can't keep typing
311
- * and sending doomed requests. */
312
- export function markFreebuffSessionCountryBlocked(params: {
313
- countryCode: string
314
- countryBlockReason?: FreebuffCountryBlockReason
315
- ipPrivacySignals?: FreebuffIpPrivacySignal[]
316
- }): void {
317
- if (!IS_FREEBUFF) return
318
- controller?.abort()
319
- controller?.apply({ status: 'country_blocked', ...params })
320
- // Best-effort DELETE so we don't hold a session row the server is already
321
- // refusing to serve at chat time.
322
- releaseFreebuffSlot().catch(() => {})
323
- }
324
-
325
- /** Flip into the local `ended` state without an instanceId (server has lost
326
- * our row). The chat surface stays mounted with the rejoin banner.
327
- * Preserves any `rateLimitsByModel` snapshot from the prior session so the
328
- * banner can show today's session count without an extra fetch. */
329
- export function markFreebuffSessionEnded(): void {
330
- if (!IS_FREEBUFF) return
331
- controller?.abort()
332
- const current = useFreebuffSessionStore.getState().session
333
- const rateLimitsByModel = getRateLimitsByModel(current)
334
- controller?.apply({
335
- status: 'ended',
336
- accessTier:
337
- current && 'accessTier' in current ? current.accessTier : undefined,
338
- rateLimitsByModel,
339
- })
340
- }
341
-
342
- interface UseFreebuffSessionResult {
343
- session: FreebuffSessionResponse | null
344
- failure: ReturnType<typeof useFreebuffSessionStore.getState>['failure']
345
- }
346
-
347
- /**
348
- * Manages the freebuff session lifecycle:
349
- * - GET on mount to probe state (no auto-join; the user picks a model in
350
- * the landing screen, which calls startFreebuffSession)
351
- * - if the probe sees an existing seat, auto-takes-over when the prior
352
- * local owner process is gone; otherwise asks before POSTing to rotate
353
- * the instance id so any other CLI on the same account is superseded
354
- * - polls GET while active to keep state fresh
355
- * - re-POSTs on explicit refresh (chat gate rejected us, user switched
356
- * models, user rejoined after ending)
357
- * - DELETE on unmount so the slot frees up for the next user
358
- * - plays a bell on admission to an active session
359
- */
360
- export function useFreebuffSession(): UseFreebuffSessionResult {
361
- const session = useFreebuffSessionStore((s) => s.session)
362
- const failure = useFreebuffSessionStore((s) => s.failure)
363
-
364
- useEffect(() => {
365
- const { setSession, setFailure } = useFreebuffSessionStore.getState()
366
-
367
- if (!IS_FREEBUFF) {
368
- // Non-freebuff (Anvil) builds never gate on a free session; leave the
369
- // store empty (app.tsx's session routing is all behind IS_FREEBUFF).
370
- setSession(null)
371
- return
372
- }
373
-
374
- if (isDirectDeepSeekMode()) {
375
- // No server-side session to poll in direct-to-DeepSeek mode. Set a
376
- // synthetic active session so the chat surface mounts normally and
377
- // getFreebuffInstanceId() satisfies the message-send gate.
378
- setSession({
379
- status: 'active',
380
- accessTier: 'full',
381
- instanceId: 'local-deepseek',
382
- model: getSelectedFreebuffModel() || FALLBACK_FREEBUFF_MODEL_ID,
383
- admittedAt: new Date().toISOString(),
384
- expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
385
- remainingMs: 24 * 60 * 60 * 1000,
386
- })
387
- setFailure(null)
388
- return
389
- }
390
-
391
- const { token } = getAuthTokenDetails()
392
- if (!token) {
393
- logger.warn(
394
- {},
395
- '[freebuff-session] No auth token; skipping free-session admission',
396
- )
397
- setFailure({
398
- type: 'other',
399
- message: 'Not authenticated',
400
- retry: null,
401
- outcomeUnknown: false,
402
- })
403
- return
404
- }
405
-
406
- let cancelled = false
407
- let abortController = new AbortController()
408
- let timer: ReturnType<typeof setTimeout> | null = null
409
- let previousStatus: FreebuffSessionResponse['status'] | null = null
410
- // A compact response for an unexpected session identity has no safe quota
411
- // snapshot to retain, so force exactly one rich poll to restore it.
412
- let needsFullActivePoll = false
413
- let restartGeneration = 0
414
- let consecutiveFailures = 0
415
- // Method for the NEXT tick. GET is read-only; POST claims/rotates a seat.
416
- // Startup is GET (probe before committing). After any POST completes we
417
- // flip back to GET. refresh() sets it to 'POST' for explicit join/rejoin;
418
- // the startup takeover branch does the same when the probe finds a seat.
419
- let nextMethod: 'GET' | 'POST' = 'GET'
420
-
421
- const apply = (next: FreebuffSessionResponse) => {
422
- rememberReferral(next)
423
- if (next.status === 'active') {
424
- useFreebuffModelStore.getState().setSelectedModel(next.model)
425
- recordFreebuffInstanceOwner(next.instanceId)
426
- } else if (next.status === 'none' && next.accessTier === 'limited') {
427
- useFreebuffModelStore
428
- .getState()
429
- .setSelectedModel(LIMITED_FREEBUFF_MODEL_ID)
430
- }
431
- setSession(next)
432
- setFailure(null)
433
- previousStatus = next.status
434
- }
435
-
436
- const clearTimer = () => {
437
- if (timer) {
438
- clearTimeout(timer)
439
- timer = null
440
- }
441
- }
442
-
443
- const schedule = (ms: number) => {
444
- if (cancelled) return
445
- clearTimer()
446
- timer = setTimeout(tick, ms)
447
- }
448
-
449
- const tick = async () => {
450
- if (cancelled) return
451
- const method = nextMethod
452
- const instanceId = getFreebuffInstanceId()
453
- const model = getSelectedFreebuffModel()
454
- const compact =
455
- method === 'GET' && previousStatus === 'active' && !needsFullActivePoll
456
- const fetchController = abortController
457
- const generation = restartGeneration
458
- try {
459
- const next = await callFreebuffSession(method, token, {
460
- signal: fetchController.signal,
461
- instanceId,
462
- model,
463
- compact,
464
- })
465
- if (
466
- cancelled ||
467
- fetchController.signal.aborted ||
468
- generation !== restartGeneration
469
- ) {
470
- return
471
- }
472
- consecutiveFailures = 0
473
- // After any successful call, default back to GET polling. The
474
- // takeover and model_locked branches below override this when they
475
- // need another POST.
476
- nextMethod = 'GET'
477
-
478
- // Consume the explicit-pick marker: it annotates exactly the first
479
- // response after a user pick, whatever that response turns out to be.
480
- const explicitPickModel = pendingExplicitPickModel
481
- pendingExplicitPickModel = null
482
-
483
- // The session is model-locked server-side: an active session on
484
- // another model rejects the switch. Two cases:
485
- // - DELIBERATE pick (the explicit-pick marker was set): honor the
486
- // click — end the locked session (usually a stale row from a
487
- // crashed CLI; DELETE is keyed on user, not instance) and
488
- // re-claim on the requested model. The marker is consume-once,
489
- // so if the retried POST races another instance back into
490
- // model_locked we take the revert branch instead of looping.
491
- // - Background rejoin racing an admission: revert the local
492
- // selection so the active session stays intact. Reverting a
493
- // deliberate pick silently made "I clicked GLM 5.2 and it
494
- // switched to DeepSeek V4 Flash" a recurring bug report
495
- // (2026-07-30): sessions live 1h even when idle, so users
496
- // constantly pick a model while a row is still active.
497
- if (next.status === 'model_locked') {
498
- if (explicitPickModel && explicitPickModel !== next.currentModel) {
499
- const current = getFreebuffModel(next.currentModel).displayName
500
- const requested = getFreebuffModel(explicitPickModel).displayName
501
- let released = false
502
- try {
503
- await callFreebuffSession('DELETE', token, {
504
- signal: fetchController.signal,
505
- })
506
- released = true
507
- } catch {
508
- // DELETE failed — fall through to the revert-with-explanation
509
- // path below rather than stranding the user mid-switch.
510
- }
511
- if (
512
- cancelled ||
513
- fetchController.signal.aborted ||
514
- generation !== restartGeneration
515
- ) {
516
- return
517
- }
518
- if (released) {
519
- useChatStore
520
- .getState()
521
- .setMessages((prev) => [
522
- ...prev,
523
- getSystemMessage(
524
- `Ended your previous session on ${current} and switched to ${requested}.`,
525
- ),
526
- ])
527
- nextMethod = 'POST'
528
- schedule(0)
529
- return
530
- }
531
- useChatStore
532
- .getState()
533
- .setMessages((prev) => [
534
- ...prev,
535
- getSystemMessage(
536
- `You're already in an active session on ${current}, and ending it failed, so the switch to ${requested} was not applied. Run /end-session, then pick ${requested}. (Sessions end on their own after 1 hour.)`,
537
- ),
538
- ])
539
- }
540
- useFreebuffModelStore.getState().setSelectedModel(next.currentModel)
541
- schedule(0)
542
- return
543
- }
544
- if (next.status === 'model_unavailable') {
545
- // Server says the requested model isn't available right now. Flip
546
- // to the always-available fallback for this run. In-memory only —
547
- // `setSelectedModel` doesn't persist, so the user's saved preference
548
- // is preserved for their next launch.
549
- //
550
- // A limited-offer model gets a sentence about it. Silence is fine for
551
- // deployment hours (the picker row says when they open), but here the
552
- // user pressed Enter on a row that was on screen a second ago and
553
- // would otherwise land on a different model with no explanation —
554
- // they lost a race for the wave's last slot.
555
- if (isFreebuffLimitedOfferModelId(next.requestedModel)) {
556
- const requested = getFreebuffModel(next.requestedModel).displayName
557
- const fallback = getFreebuffModel(
558
- FALLBACK_FREEBUFF_MODEL_ID,
559
- ).displayName
560
- useChatStore
561
- .getState()
562
- .setMessages((prev) => [
563
- ...prev,
564
- getSystemMessage(
565
- `${requested}'s trial sessions just ran out, so this session started on ${fallback} instead. Check back later — we release more in batches.`,
566
- ),
567
- ])
568
- }
569
- useFreebuffModelStore
570
- .getState()
571
- .setSelectedModel(FALLBACK_FREEBUFF_MODEL_ID)
572
- // The unavailable response came from a POST attempt. Re-POST with
573
- // the fallback model; a GET would only redisplay the old ended row
574
- // and leave the restart banner stuck in its pending state.
575
- nextMethod = 'POST'
576
- schedule(0)
577
- return
578
- }
579
-
580
- // Startup takeover: the initial probe GET saw we already hold a seat
581
- // (from a prior CLI instance). Stop here and ask before POSTing to
582
- // rotate our instance id; otherwise opening a second freebuff would
583
- // immediately supersede the first one.
584
- // `previousStatus === null` fences this to the very first tick only.
585
- // Pin the selected model to whatever the server thinks we're on so
586
- // an explicit takeover preserves our queue position instead of
587
- // switching queues.
588
- if (
589
- method === 'GET' &&
590
- previousStatus === null &&
591
- next.status === 'active'
592
- ) {
593
- useFreebuffModelStore.getState().setSelectedModel(next.model)
594
- // A fast restart after Ctrl+C can observe the old server row before
595
- // best-effort DELETE lands. If the row belongs to a dead local
596
- // process, silently do the same POST as the Take over button.
597
- if (isFreebuffInstanceOwnedByDeadLocalProcess(next.instanceId)) {
598
- nextMethod = 'POST'
599
- schedule(0)
600
- return
601
- }
602
- apply({ status: 'takeover_prompt', model: next.model })
603
- return
604
- }
605
-
606
- // Bell on admission: the user committed to a model on the landing
607
- // screen (status 'none'), which POSTs and lands them straight on an
608
- // active session (admission is immediate).
609
- if (previousStatus === 'none' && next.status === 'active') {
610
- playAdmissionSound()
611
- }
612
-
613
- // active|ended → none means we've passed the server's hard cutoff.
614
- // Synthesize a no-instanceId ended state so the chat surface stays
615
- // mounted with the Enter-to-rejoin banner instead of looping back
616
- // through the landing screen. Carry forward whichever rate-limit
617
- // snapshot we have — preferring the fresh `none` snapshot, falling
618
- // back to whatever was on the prior active/ended row — so the
619
- // banner's "N of M used today" line stays populated.
620
- if (
621
- (previousStatus === 'active' || previousStatus === 'ended') &&
622
- next.status === 'none'
623
- ) {
624
- const current = useFreebuffSessionStore.getState().session
625
- const rateLimitsByModel =
626
- next.rateLimitsByModel ?? getRateLimitsByModel(current)
627
- apply({
628
- status: 'ended',
629
- accessTier:
630
- next.accessTier ??
631
- (current && 'accessTier' in current
632
- ? current.accessTier
633
- : undefined),
634
- rateLimitsByModel,
635
- })
636
- return
637
- }
638
-
639
- if (compact && next.status === 'active') {
640
- const merged = mergeCompactActiveSession(
641
- useFreebuffSessionStore.getState().session,
642
- next,
643
- )
644
- needsFullActivePoll = merged === null
645
- apply(merged ?? next)
646
- } else {
647
- needsFullActivePoll = false
648
- apply(next)
649
- }
650
- if (needsFullActivePoll) {
651
- schedule(0)
652
- return
653
- }
654
- const delay = nextDelayMs(next)
655
- if (delay !== null) schedule(delay)
656
- } catch (err) {
657
- if (
658
- cancelled ||
659
- fetchController.signal.aborted ||
660
- generation !== restartGeneration
661
- ) {
662
- return
663
- }
664
- const msg = err instanceof Error ? err.message : String(err)
665
- consecutiveFailures++
666
- const disposition = classifyFreebuffSessionRequestFailure(method, err)
667
- const shouldRetry = disposition === 'retry'
668
- const retryAfterMs =
669
- err instanceof FreebuffSessionRequestError
670
- ? err.retryAfterMs
671
- : undefined
672
- const delayMs = shouldRetry
673
- ? failedPollDelayMs({
674
- consecutiveFailures,
675
- retryAfterMs,
676
- })
677
- : null
678
- logger.warn(
679
- { error: msg, method, consecutiveFailures, delayMs, shouldRetry },
680
- shouldRetry
681
- ? '[freebuff-session] fetch failed; backing off'
682
- : '[freebuff-session] fetch failed; automatic retry stopped',
683
- )
684
- const retry =
685
- delayMs === null
686
- ? null
687
- : {
688
- attempt: consecutiveFailures + 1,
689
- retryAtMs: Date.now() + delayMs,
690
- }
691
- const failure = {
692
- message: msg,
693
- retry,
694
- outcomeUnknown: disposition === 'unknown',
695
- }
696
- if (err instanceof FreebuffSessionRequestError) {
697
- setFailure({
698
- ...failure,
699
- type: 'http',
700
- statusCode: err.statusCode,
701
- })
702
- } else if (isFreebuffSessionTimeoutError(err)) {
703
- setFailure({ ...failure, type: 'timeout' })
704
- } else {
705
- setFailure({ ...failure, type: 'other' })
706
- }
707
- if (delayMs !== null) schedule(delayMs)
708
- }
709
- }
710
-
711
- controller = {
712
- restart: async (mode) => {
713
- const generation = ++restartGeneration
714
- clearTimer()
715
- // Abort any in-flight fetch so it can't race us and overwrite state.
716
- abortController.abort()
717
- abortController = new AbortController()
718
- // Reset previousStatus so the admission bell still fires after
719
- // a forced restart, and so the active|ended → none synthesis below
720
- // doesn't bounce a 'landing' restart straight back to 'ended'.
721
- previousStatus = null
722
- needsFullActivePoll = false
723
- consecutiveFailures = 0
724
- setFailure(null)
725
- if (mode === 'landing') {
726
- nextMethod = 'GET'
727
- // Land on the picker immediately. We can't go through the normal
728
- // tick/apply path because a server-side row that hasn't been
729
- // swept yet would trip the startup-takeover branch into an
730
- // auto-POST — the exact silent-rejoin this mode exists to
731
- // prevent. But the picker still needs live quota snapshots, so kick
732
- // off a fire-and-forget GET and extract only picker metadata from
733
- // the response, ignoring whatever status it claims. Polling resumes
734
- // when the user commits to a model via startFreebuffSession.
735
- const landingSession = toLandingSession(
736
- useFreebuffSessionStore.getState().session,
737
- )
738
- apply(landingSession)
739
- const fetchController = abortController
740
- callFreebuffSession('GET', token, {
741
- signal: fetchController.signal,
742
- })
743
- .then((response) => {
744
- if (
745
- cancelled ||
746
- fetchController.signal.aborted ||
747
- generation !== restartGeneration
748
- ) {
749
- return
750
- }
751
- if (response.status === 'none') {
752
- // Preserve cached quota/location fields only when the tier is
753
- // unchanged (or an older response omitted it). Fresh response
754
- // fields win through the following object spread.
755
- const canReuseLandingMetadata =
756
- response.accessTier === undefined ||
757
- response.accessTier === landingSession.accessTier
758
- apply({
759
- ...(canReuseLandingMetadata ? landingSession : {}),
760
- ...response,
761
- status: 'none',
762
- accessTier: response.accessTier ?? landingSession.accessTier,
763
- // A clean `none` response is authoritative for referral
764
- // state. Do not retain the cached landing value when the
765
- // server omits it (program disabled / identity removed).
766
- referral: response.referral,
767
- })
768
- }
769
- })
770
- .catch(() => {
771
- // Silent — blank hints are acceptable if the fetch fails.
772
- })
773
- return
774
- }
775
- nextMethod = 'POST'
776
- await tick()
777
- },
778
- apply,
779
- abort: () => {
780
- clearTimer()
781
- abortController.abort()
782
- },
783
- }
784
-
785
- tick()
786
-
787
- return () => {
788
- cancelled = true
789
- abortController.abort()
790
- clearTimer()
791
- const current = useFreebuffSessionStore.getState().session
792
- controller = null
793
- clearReferralCache()
794
-
795
- // Fire-and-forget DELETE. Only release if we actually held a slot so
796
- // we don't generate spurious DELETEs (e.g. HMR before POST completes).
797
- if (holdsLiveFreebuffSlot(current)) {
798
- callFreebuffSession('DELETE', token).catch(() => {})
799
- }
800
- setSession(null)
801
- setFailure(null)
802
- }
803
- }, [])
804
-
805
- return { session, failure }
806
- }