@alumbwe/anvil 1.0.0 → 1.0.1

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/bin/anvil +1 -1
  2. package/dist/bundled.js +79559 -0
  3. package/dist/bundled.js.map +581 -0
  4. package/package.json +5 -2
  5. package/src/__tests__/README.md +0 -256
  6. package/src/__tests__/bash-mode.test.ts +0 -442
  7. package/src/__tests__/cli-args.test.ts +0 -181
  8. package/src/__tests__/cli-harness.test.ts +0 -52
  9. package/src/__tests__/e2e/first-time-login.test.ts +0 -173
  10. package/src/__tests__/e2e/logout-relogin-flow.test.ts +0 -110
  11. package/src/__tests__/e2e/returning-user-auth.test.ts +0 -113
  12. package/src/__tests__/e2e-cli.test.ts +0 -183
  13. package/src/__tests__/helpers/launcher-disconnect-fixture.cjs +0 -176
  14. package/src/__tests__/helpers/mock-api-client.ts +0 -64
  15. package/src/__tests__/helpers/renderer-cleanup-fixture.tsx +0 -89
  16. package/src/__tests__/helpers/terminal-watchdog-fixture.ts +0 -75
  17. package/src/__tests__/home-directory-detection.test.ts +0 -213
  18. package/src/__tests__/integration/api-integration.test.ts +0 -288
  19. package/src/__tests__/integration/credentials-storage.test.ts +0 -486
  20. package/src/__tests__/integration/local-agents.test.ts +0 -1187
  21. package/src/__tests__/integration/login-flow-instrumentation.test.ts +0 -134
  22. package/src/__tests__/integration/login-polling-working.test.ts +0 -285
  23. package/src/__tests__/integration/queue-editing.test.tsx +0 -261
  24. package/src/__tests__/integration/usage-refresh-on-completion.test.ts +0 -168
  25. package/src/__tests__/integration-tmux.test.ts +0 -188
  26. package/src/__tests__/launcher-avx2-fallback.test.ts +0 -630
  27. package/src/__tests__/launcher-disconnect.test.ts +0 -104
  28. package/src/__tests__/mocks/hover-toggle-controller.ts +0 -71
  29. package/src/__tests__/open-tui-native-bundle.test.ts +0 -378
  30. package/src/__tests__/path-completion.test.ts +0 -243
  31. package/src/__tests__/project-files-chat-id.test.ts +0 -34
  32. package/src/__tests__/release/proxy-http-get.test.ts +0 -667
  33. package/src/__tests__/release/wrapper-safety.test.ts +0 -412
  34. package/src/__tests__/renderer-cleanup.test.ts +0 -174
  35. package/src/__tests__/rerender-perf.integration.test.ts +0 -283
  36. package/src/__tests__/terminal-reset-sequences.test.ts +0 -36
  37. package/src/__tests__/terminal-watchdog.test.ts +0 -388
  38. package/src/__tests__/test-utils.ts +0 -271
  39. package/src/__tests__/tmux-poc.ts +0 -150
  40. package/src/__tests__/unit/agent-mode-toggle.test.ts +0 -137
  41. package/src/__tests__/unit/copy-button.test.ts +0 -157
  42. package/src/__tests__/unit/create-run-config.test.ts +0 -66
  43. package/src/__tests__/unit/publish-confirmation.test.ts +0 -70
  44. package/src/__tests__/unit/segmented-control.test.ts +0 -152
  45. package/src/__tests__/utils/env.test.ts +0 -169
  46. package/src/__tests__/utils/project-picker.test.ts +0 -39
  47. package/src/agents/bundled-agents.generated.d.ts +0 -14
  48. package/src/agents/bundled-agents.generated.ts +0 -3692
  49. package/src/app.tsx +0 -399
  50. package/src/chat.tsx +0 -1698
  51. package/src/cli-args.ts +0 -134
  52. package/src/commands/__tests__/bash-command.test.ts +0 -445
  53. package/src/commands/__tests__/command-args.test.ts +0 -349
  54. package/src/commands/__tests__/copy-conversation-clipboard.test.ts +0 -108
  55. package/src/commands/__tests__/copy-conversation.test.ts +0 -226
  56. package/src/commands/__tests__/diagnostics-command.test.ts +0 -35
  57. package/src/commands/__tests__/freebuff-command-aliases.test.ts +0 -56
  58. package/src/commands/__tests__/image.test.ts +0 -99
  59. package/src/commands/__tests__/init.test.ts +0 -456
  60. package/src/commands/__tests__/new-command-rotates-chat.test.ts +0 -91
  61. package/src/commands/__tests__/process-diagnostics.test.ts +0 -54
  62. package/src/commands/__tests__/prompt-builders.test.ts +0 -28
  63. package/src/commands/__tests__/router-input.test.ts +0 -298
  64. package/src/commands/ads.ts +0 -44
  65. package/src/commands/command-registry.ts +0 -726
  66. package/src/commands/copy-conversation.ts +0 -392
  67. package/src/commands/help.ts +0 -13
  68. package/src/commands/image.ts +0 -20
  69. package/src/commands/init.ts +0 -121
  70. package/src/commands/process-diagnostics.ts +0 -120
  71. package/src/commands/prompt-builders.ts +0 -100
  72. package/src/commands/publish.ts +0 -203
  73. package/src/commands/router-utils.ts +0 -80
  74. package/src/commands/router.ts +0 -487
  75. package/src/commands/usage.ts +0 -26
  76. package/src/components/__tests__/ad-banner.test.tsx +0 -83
  77. package/src/components/__tests__/chat-input-bar.test.tsx +0 -130
  78. package/src/components/__tests__/freebuff-landing-heading-row.test.tsx +0 -91
  79. package/src/components/__tests__/freebuff-model-selector.test.tsx +0 -519
  80. package/src/components/__tests__/freebuff-offer-invariants.test.ts +0 -54
  81. package/src/components/__tests__/freebuff-takeover-prompt.test.tsx +0 -212
  82. package/src/components/__tests__/grid-layout.integration.test.tsx +0 -304
  83. package/src/components/__tests__/grid-layout.test.tsx +0 -1051
  84. package/src/components/__tests__/message-block.completion.test.tsx +0 -104
  85. package/src/components/__tests__/message-block.streaming.test.tsx +0 -78
  86. package/src/components/__tests__/message-with-agents.test.tsx +0 -579
  87. package/src/components/__tests__/multiline-input.test.tsx +0 -1132
  88. package/src/components/__tests__/queue-panel.test.tsx +0 -398
  89. package/src/components/__tests__/selectable-list.test.ts +0 -232
  90. package/src/components/__tests__/status-bar.test.tsx +0 -44
  91. package/src/components/__tests__/status-indicator.test.tsx +0 -204
  92. package/src/components/__tests__/terminal-thinking-orb.test.tsx +0 -111
  93. package/src/components/__tests__/user-error-banner.test.tsx +0 -102
  94. package/src/components/ad-banner.tsx +0 -323
  95. package/src/components/agent-checklist.tsx +0 -350
  96. package/src/components/agent-mode-toggle.tsx +0 -241
  97. package/src/components/anvil-mark.tsx +0 -52
  98. package/src/components/ask-user/__tests__/multiple-choice-form.test.ts +0 -432
  99. package/src/components/ask-user/__tests__/validation.test.ts +0 -213
  100. package/src/components/ask-user/components/accordion-question.tsx +0 -153
  101. package/src/components/ask-user/components/custom-answer-input.tsx +0 -66
  102. package/src/components/ask-user/components/options-list.tsx +0 -133
  103. package/src/components/ask-user/components/question-header.tsx +0 -80
  104. package/src/components/ask-user/components/question-option.tsx +0 -89
  105. package/src/components/ask-user/constants.ts +0 -38
  106. package/src/components/ask-user/index.tsx +0 -631
  107. package/src/components/ask-user/utils/validation.ts +0 -85
  108. package/src/components/attachment-card.tsx +0 -65
  109. package/src/components/blocks/agent-block-grid.tsx +0 -58
  110. package/src/components/blocks/agent-branch-item.tsx +0 -311
  111. package/src/components/blocks/agent-branch-wrapper.tsx +0 -493
  112. package/src/components/blocks/agent-list-branch.tsx +0 -77
  113. package/src/components/blocks/ask-user-branch.tsx +0 -81
  114. package/src/components/blocks/block-helpers.ts +0 -11
  115. package/src/components/blocks/blocks-renderer.tsx +0 -289
  116. package/src/components/blocks/content-with-markdown.tsx +0 -33
  117. package/src/components/blocks/image-block.tsx +0 -128
  118. package/src/components/blocks/implementor-row.tsx +0 -429
  119. package/src/components/blocks/single-block.tsx +0 -201
  120. package/src/components/blocks/thinking-block.tsx +0 -67
  121. package/src/components/blocks/tool-block-group.tsx +0 -57
  122. package/src/components/blocks/tool-branch.tsx +0 -206
  123. package/src/components/blocks/user-content-copy.tsx +0 -157
  124. package/src/components/bottom-banner.tsx +0 -136
  125. package/src/components/build-mode-buttons.tsx +0 -110
  126. package/src/components/button.tsx +0 -66
  127. package/src/components/chat-header.tsx +0 -77
  128. package/src/components/chat-history-screen.tsx +0 -413
  129. package/src/components/chat-input-bar.tsx +0 -523
  130. package/src/components/clickable-title-box.tsx +0 -66
  131. package/src/components/clickable.tsx +0 -119
  132. package/src/components/collapse-button.tsx +0 -34
  133. package/src/components/copy-button.tsx +0 -203
  134. package/src/components/elapsed-timer.tsx +0 -55
  135. package/src/components/error-boundary.tsx +0 -55
  136. package/src/components/feedback-container.tsx +0 -181
  137. package/src/components/feedback-icon-button.tsx +0 -89
  138. package/src/components/feedback-input-mode.tsx +0 -344
  139. package/src/components/file-attachment-card.tsx +0 -98
  140. package/src/components/freebuff-active-session-summary.tsx +0 -63
  141. package/src/components/freebuff-landing-screen.tsx +0 -865
  142. package/src/components/freebuff-model-selector.tsx +0 -1087
  143. package/src/components/freebuff-referral-banner.tsx +0 -604
  144. package/src/components/freebuff-superseded-screen.tsx +0 -62
  145. package/src/components/grid-layout.tsx +0 -80
  146. package/src/components/help-banner.tsx +0 -127
  147. package/src/components/highlighted-text.tsx +0 -52
  148. package/src/components/image-card.tsx +0 -167
  149. package/src/components/image-thumbnail.tsx +0 -114
  150. package/src/components/input-cursor.tsx +0 -77
  151. package/src/components/input-mode-banner.tsx +0 -57
  152. package/src/components/load-previous-button.tsx +0 -48
  153. package/src/components/login-modal.tsx +0 -475
  154. package/src/components/message-block.tsx +0 -346
  155. package/src/components/message-footer.tsx +0 -258
  156. package/src/components/message-with-agents.tsx +0 -506
  157. package/src/components/mode-divider.tsx +0 -43
  158. package/src/components/multiline-input.tsx +0 -1241
  159. package/src/components/out-of-credits-banner.tsx +0 -160
  160. package/src/components/pending-attachments-banner.tsx +0 -116
  161. package/src/components/pending-bash-message.tsx +0 -59
  162. package/src/components/progress-bar.tsx +0 -81
  163. package/src/components/project-picker-screen.tsx +0 -485
  164. package/src/components/publish-confirmation.tsx +0 -493
  165. package/src/components/publish-container.tsx +0 -687
  166. package/src/components/queue-panel.tsx +0 -310
  167. package/src/components/raised-pill.tsx +0 -95
  168. package/src/components/renderers/plan-box.tsx +0 -59
  169. package/src/components/review-screen.tsx +0 -112
  170. package/src/components/scroll-to-bottom-button.tsx +0 -34
  171. package/src/components/segmented-control.tsx +0 -187
  172. package/src/components/selectable-list.tsx +0 -248
  173. package/src/components/selected-chips.tsx +0 -80
  174. package/src/components/separator.tsx +0 -40
  175. package/src/components/session-ended-banner.tsx +0 -226
  176. package/src/components/shimmer-text.tsx +0 -248
  177. package/src/components/status-bar.tsx +0 -313
  178. package/src/components/subscription-limit-banner.tsx +0 -206
  179. package/src/components/suggested-prompts.tsx +0 -184
  180. package/src/components/suggestion-menu.tsx +0 -215
  181. package/src/components/terminal-command-display.tsx +0 -151
  182. package/src/components/terminal-link.tsx +0 -97
  183. package/src/components/terminal-thinking-orb.tsx +0 -95
  184. package/src/components/text-attachment-card.tsx +0 -77
  185. package/src/components/thinking.tsx +0 -109
  186. package/src/components/tools/__tests__/apply-patch.test.tsx +0 -84
  187. package/src/components/tools/__tests__/code-search.test.tsx +0 -45
  188. package/src/components/tools/__tests__/gravity-index.test.ts +0 -76
  189. package/src/components/tools/__tests__/render-ui.test.tsx +0 -92
  190. package/src/components/tools/__tests__/run-terminal-command.test.ts +0 -233
  191. package/src/components/tools/apply-patch.tsx +0 -95
  192. package/src/components/tools/code-search.tsx +0 -45
  193. package/src/components/tools/composio.tsx +0 -152
  194. package/src/components/tools/diff-viewer.tsx +0 -76
  195. package/src/components/tools/glob.tsx +0 -61
  196. package/src/components/tools/gravity-index.tsx +0 -77
  197. package/src/components/tools/list-directory.tsx +0 -61
  198. package/src/components/tools/read-docs.tsx +0 -37
  199. package/src/components/tools/read-files.tsx +0 -91
  200. package/src/components/tools/read-subtree.tsx +0 -44
  201. package/src/components/tools/read-url.tsx +0 -33
  202. package/src/components/tools/registry.ts +0 -126
  203. package/src/components/tools/render-ui.tsx +0 -122
  204. package/src/components/tools/run-terminal-command.tsx +0 -79
  205. package/src/components/tools/skill.tsx +0 -29
  206. package/src/components/tools/str-replace.tsx +0 -77
  207. package/src/components/tools/suggest-followups.tsx +0 -369
  208. package/src/components/tools/task-completed.tsx +0 -16
  209. package/src/components/tools/tool-call-item.tsx +0 -273
  210. package/src/components/tools/types.ts +0 -55
  211. package/src/components/tools/web-search.tsx +0 -33
  212. package/src/components/tools/write-file.tsx +0 -16
  213. package/src/components/tools/write-todos.tsx +0 -95
  214. package/src/components/top-banner.tsx +0 -178
  215. package/src/components/usage-banner.tsx +0 -244
  216. package/src/components/user-error-banner.tsx +0 -56
  217. package/src/components/validation-error-popover.tsx +0 -192
  218. package/src/contexts/__tests__/chat-runtime-context.test.tsx +0 -149
  219. package/src/contexts/chat-runtime-context.tsx +0 -240
  220. package/src/data/slash-commands.test.ts +0 -24
  221. package/src/data/slash-commands.ts +0 -249
  222. package/src/entry.ts +0 -12
  223. package/src/hooks/__tests__/holds-live-freebuff-slot.test.ts +0 -49
  224. package/src/hooks/__tests__/session-fetch-signal.test.ts +0 -63
  225. package/src/hooks/__tests__/use-activity-query.test.ts +0 -1000
  226. package/src/hooks/__tests__/use-ask-user-bridge.test.ts +0 -176
  227. package/src/hooks/__tests__/use-auth-query.test.ts +0 -21
  228. package/src/hooks/__tests__/use-chat-input-remount.test.tsx +0 -66
  229. package/src/hooks/__tests__/use-clipboard.test.tsx +0 -73
  230. package/src/hooks/__tests__/use-connection-status.test.ts +0 -112
  231. package/src/hooks/__tests__/use-directory-browser.test.ts +0 -203
  232. package/src/hooks/__tests__/use-gravity-ad.test.ts +0 -73
  233. package/src/hooks/__tests__/use-grid-layout.test.ts +0 -349
  234. package/src/hooks/__tests__/use-input-history.test.ts +0 -699
  235. package/src/hooks/__tests__/use-message-queue-editing.test.tsx +0 -209
  236. package/src/hooks/__tests__/use-message-queue.test.tsx +0 -71
  237. package/src/hooks/__tests__/use-path-tab-completion.test.ts +0 -315
  238. package/src/hooks/__tests__/use-queue-controls.test.ts +0 -68
  239. package/src/hooks/__tests__/use-queue-ui.test.tsx +0 -83
  240. package/src/hooks/__tests__/use-searchable-list.test.ts +0 -359
  241. package/src/hooks/__tests__/use-send-message-timer.test.ts +0 -146
  242. package/src/hooks/__tests__/use-suggestion-engine-mention.test.ts +0 -361
  243. package/src/hooks/__tests__/use-suggestion-engine.test.ts +0 -485
  244. package/src/hooks/__tests__/use-terminal-focus.test.ts +0 -66
  245. package/src/hooks/__tests__/use-terminal-layout.test.ts +0 -675
  246. package/src/hooks/__tests__/use-timeout.test.ts +0 -330
  247. package/src/hooks/__tests__/use-usage-query.test.ts +0 -502
  248. package/src/hooks/__tests__/use-user-details-query.test.ts +0 -195
  249. package/src/hooks/helpers/__tests__/send-message.test.ts +0 -1918
  250. package/src/hooks/helpers/send-message.ts +0 -654
  251. package/src/hooks/stream-state.ts +0 -83
  252. package/src/hooks/use-activity-query.ts +0 -668
  253. package/src/hooks/use-agent-validation.ts +0 -74
  254. package/src/hooks/use-ask-user-bridge.ts +0 -92
  255. package/src/hooks/use-auth-query.ts +0 -253
  256. package/src/hooks/use-auth-state.ts +0 -152
  257. package/src/hooks/use-chat-input.ts +0 -105
  258. package/src/hooks/use-chat-keyboard.ts +0 -321
  259. package/src/hooks/use-chat-messages.ts +0 -254
  260. package/src/hooks/use-chat-state.ts +0 -165
  261. package/src/hooks/use-chat-streaming.ts +0 -180
  262. package/src/hooks/use-chat-ui.ts +0 -131
  263. package/src/hooks/use-clipboard.ts +0 -151
  264. package/src/hooks/use-connection-status.ts +0 -142
  265. package/src/hooks/use-directory-browser.ts +0 -88
  266. package/src/hooks/use-elapsed-time.ts +0 -116
  267. package/src/hooks/use-event.ts +0 -34
  268. package/src/hooks/use-exit-handler.ts +0 -66
  269. package/src/hooks/use-fetch-login-url.ts +0 -65
  270. package/src/hooks/use-fingerprint.ts +0 -61
  271. package/src/hooks/use-freebuff-ctrl-c-exit.ts +0 -23
  272. package/src/hooks/use-freebuff-session-progress.ts +0 -34
  273. package/src/hooks/use-freebuff-session.ts +0 -806
  274. package/src/hooks/use-freebuff-streak-query.ts +0 -61
  275. package/src/hooks/use-gravity-ad.ts +0 -656
  276. package/src/hooks/use-grid-layout.ts +0 -73
  277. package/src/hooks/use-input-history.ts +0 -173
  278. package/src/hooks/use-login-keyboard-handlers.ts +0 -75
  279. package/src/hooks/use-login-polling.ts +0 -114
  280. package/src/hooks/use-logo.tsx +0 -161
  281. package/src/hooks/use-message-queue.ts +0 -387
  282. package/src/hooks/use-now.ts +0 -20
  283. package/src/hooks/use-path-tab-completion.ts +0 -88
  284. package/src/hooks/use-publish-mutation.ts +0 -60
  285. package/src/hooks/use-queue-controls.ts +0 -68
  286. package/src/hooks/use-queue-ui.ts +0 -74
  287. package/src/hooks/use-scroll-management.ts +0 -184
  288. package/src/hooks/use-searchable-list.ts +0 -98
  289. package/src/hooks/use-send-message.ts +0 -773
  290. package/src/hooks/use-sheen-animation.tsx +0 -88
  291. package/src/hooks/use-subscription-query.ts +0 -71
  292. package/src/hooks/use-suggestion-engine.ts +0 -783
  293. package/src/hooks/use-terminal-breakpoints.ts +0 -77
  294. package/src/hooks/use-terminal-dimensions.ts +0 -46
  295. package/src/hooks/use-terminal-focus.ts +0 -29
  296. package/src/hooks/use-terminal-layout.ts +0 -189
  297. package/src/hooks/use-theme.tsx +0 -140
  298. package/src/hooks/use-timeout.ts +0 -85
  299. package/src/hooks/use-update-preference.ts +0 -66
  300. package/src/hooks/use-usage-monitor.ts +0 -55
  301. package/src/hooks/use-usage-query.ts +0 -124
  302. package/src/hooks/use-user-details-query.ts +0 -93
  303. package/src/hooks/use-why-did-you-update.ts +0 -177
  304. package/src/index.tsx +0 -441
  305. package/src/init/__tests__/init-direnv.test.ts +0 -526
  306. package/src/init/init-app.ts +0 -34
  307. package/src/init/init-direnv.ts +0 -133
  308. package/src/login/constants.ts +0 -60
  309. package/src/login/login-flow.ts +0 -204
  310. package/src/login/plain-login.ts +0 -108
  311. package/src/login/utils.ts +0 -183
  312. package/src/native/ripgrep.ts +0 -80
  313. package/src/polyfills/bun-strip-ansi.ts +0 -12
  314. package/src/pre-init/tree-sitter-wasm.ts +0 -96
  315. package/src/project-files.ts +0 -102
  316. package/src/smoke/terminal-command-broker.ts +0 -439
  317. package/src/state/__tests__/chat-store-focus.test.ts +0 -22
  318. package/src/state/__tests__/feedback-store.test.ts +0 -257
  319. package/src/state/chat-history-store.ts +0 -30
  320. package/src/state/chat-store.ts +0 -544
  321. package/src/state/feedback-store.ts +0 -160
  322. package/src/state/freebuff-model-store.ts +0 -42
  323. package/src/state/freebuff-session-store.ts +0 -53
  324. package/src/state/login-store.ts +0 -150
  325. package/src/state/message-block-store.ts +0 -149
  326. package/src/state/publish-store.ts +0 -146
  327. package/src/state/queue-panel-store.ts +0 -26
  328. package/src/state/review-store.ts +0 -24
  329. package/src/testing/env.ts +0 -46
  330. package/src/types/chat-state.ts +0 -15
  331. package/src/types/chat.ts +0 -241
  332. package/src/types/contracts/send-message.ts +0 -12
  333. package/src/types/env.ts +0 -98
  334. package/src/types/freebuff-session.ts +0 -17
  335. package/src/types/function-params.ts +0 -28
  336. package/src/types/react19-compat.d.ts +0 -19
  337. package/src/types/store.ts +0 -111
  338. package/src/types/theme-system.ts +0 -144
  339. package/src/types/utils.ts +0 -3
  340. package/src/utils/__tests__/active-run.test.ts +0 -140
  341. package/src/utils/__tests__/activity-tracker.test.ts +0 -440
  342. package/src/utils/__tests__/agent-display.test.ts +0 -139
  343. package/src/utils/__tests__/analytics-client.test.ts +0 -262
  344. package/src/utils/__tests__/anonymous-id.test.ts +0 -74
  345. package/src/utils/__tests__/anvil-api.test.ts +0 -578
  346. package/src/utils/__tests__/arrays.test.ts +0 -264
  347. package/src/utils/__tests__/bash-context-processor.test.ts +0 -38
  348. package/src/utils/__tests__/block-processor.test.ts +0 -810
  349. package/src/utils/__tests__/chat-history.test.ts +0 -203
  350. package/src/utils/__tests__/chat-input-key-intercept.test.ts +0 -40
  351. package/src/utils/__tests__/chat-meta.test.ts +0 -130
  352. package/src/utils/__tests__/clipboard-linux.test.ts +0 -359
  353. package/src/utils/__tests__/clipboard.test.ts +0 -910
  354. package/src/utils/__tests__/code-search-summary.test.ts +0 -84
  355. package/src/utils/__tests__/collapse-helpers.test.ts +0 -1109
  356. package/src/utils/__tests__/error-handling.test.ts +0 -658
  357. package/src/utils/__tests__/exit-cleanly.test.ts +0 -110
  358. package/src/utils/__tests__/feedback-helpers.test.ts +0 -444
  359. package/src/utils/__tests__/feedback-submission.test.ts +0 -26
  360. package/src/utils/__tests__/fetch-usage.test.ts +0 -355
  361. package/src/utils/__tests__/fingerprint.test.ts +0 -144
  362. package/src/utils/__tests__/fixtures/terminal-command-broker-child.ts +0 -7
  363. package/src/utils/__tests__/fixtures/terminal-command-broker-entry.ts +0 -3
  364. package/src/utils/__tests__/fixtures/terminal-command-broker-owner.ts +0 -33
  365. package/src/utils/__tests__/format-elapsed-time.test.ts +0 -103
  366. package/src/utils/__tests__/format-timeout.test.ts +0 -87
  367. package/src/utils/__tests__/freebuff-instance-owner.test.ts +0 -69
  368. package/src/utils/__tests__/freebuff-model-navigation.test.ts +0 -102
  369. package/src/utils/__tests__/freebuff-premium-reset.test.ts +0 -79
  370. package/src/utils/__tests__/freebuff-referral-cache.test.ts +0 -116
  371. package/src/utils/__tests__/freebuff-session-api.test.ts +0 -215
  372. package/src/utils/__tests__/freebuff-session-display.test.ts +0 -23
  373. package/src/utils/__tests__/freebuff-streak-line.test.ts +0 -217
  374. package/src/utils/__tests__/image-dimensions.test.ts +0 -249
  375. package/src/utils/__tests__/image-processor.test.ts +0 -209
  376. package/src/utils/__tests__/implementor-helpers.test.ts +0 -1458
  377. package/src/utils/__tests__/keyboard-actions.test.ts +0 -660
  378. package/src/utils/__tests__/layout-helpers.test.ts +0 -36
  379. package/src/utils/__tests__/lazy-response-ads.test.ts +0 -167
  380. package/src/utils/__tests__/log-shipper.test.ts +0 -53
  381. package/src/utils/__tests__/markdown-renderer.test.tsx +0 -421
  382. package/src/utils/__tests__/message-block-helpers.test.ts +0 -1263
  383. package/src/utils/__tests__/message-updater.test.ts +0 -645
  384. package/src/utils/__tests__/osc-timeout-scenarios.test.ts +0 -172
  385. package/src/utils/__tests__/pending-attachments.test.ts +0 -277
  386. package/src/utils/__tests__/polling-backoff.test.ts +0 -51
  387. package/src/utils/__tests__/queue-panel-actions.test.ts +0 -148
  388. package/src/utils/__tests__/run-state-storage.test.ts +0 -919
  389. package/src/utils/__tests__/safe-json.test.ts +0 -77
  390. package/src/utils/__tests__/sdk-event-handlers.test.ts +0 -685
  391. package/src/utils/__tests__/send-message-helpers.test.ts +0 -1898
  392. package/src/utils/__tests__/send-message-timer.test.ts +0 -64
  393. package/src/utils/__tests__/settings.test.ts +0 -99
  394. package/src/utils/__tests__/strings.test.ts +0 -192
  395. package/src/utils/__tests__/terminal-color-detection.test.ts +0 -425
  396. package/src/utils/__tests__/terminal-command-broker.test.ts +0 -528
  397. package/src/utils/__tests__/terminal-enter-detection.test.ts +0 -77
  398. package/src/utils/__tests__/terminal-io.test.ts +0 -137
  399. package/src/utils/__tests__/terminal-protocol-controller.test.ts +0 -119
  400. package/src/utils/__tests__/text-layout.test.ts +0 -82
  401. package/src/utils/__tests__/theme-platform-detection.test.ts +0 -125
  402. package/src/utils/__tests__/think-tag-parser.test.ts +0 -159
  403. package/src/utils/__tests__/trace-writer.test.ts +0 -223
  404. package/src/utils/__tests__/trim-chat-logs.test.ts +0 -78
  405. package/src/utils/__tests__/usage-banner-state.test.ts +0 -298
  406. package/src/utils/__tests__/validation-error-formatting.test.ts +0 -126
  407. package/src/utils/__tests__/windows-terminal-health.test.ts +0 -101
  408. package/src/utils/__tests__/write-file-atomic.test.ts +0 -127
  409. package/src/utils/active-run.ts +0 -131
  410. package/src/utils/activity-tracker.ts +0 -66
  411. package/src/utils/agent-display.ts +0 -87
  412. package/src/utils/agent-helpers.ts +0 -27
  413. package/src/utils/agent-id-utils.ts +0 -14
  414. package/src/utils/ai-message-id.ts +0 -12
  415. package/src/utils/analytics.ts +0 -361
  416. package/src/utils/anonymous-id.ts +0 -87
  417. package/src/utils/anvil-api.ts +0 -612
  418. package/src/utils/anvil-client.ts +0 -217
  419. package/src/utils/arrays.ts +0 -117
  420. package/src/utils/auth.ts +0 -258
  421. package/src/utils/bash-context-processor.ts +0 -47
  422. package/src/utils/bash-messages.ts +0 -108
  423. package/src/utils/block-margins.ts +0 -35
  424. package/src/utils/block-operations.ts +0 -523
  425. package/src/utils/block-processor.ts +0 -213
  426. package/src/utils/chat-history.ts +0 -253
  427. package/src/utils/chat-input-key-intercept.ts +0 -52
  428. package/src/utils/chat-meta.ts +0 -90
  429. package/src/utils/chat-scroll-accel.ts +0 -151
  430. package/src/utils/clipboard-image.ts +0 -588
  431. package/src/utils/clipboard.ts +0 -397
  432. package/src/utils/code-search-summary.ts +0 -70
  433. package/src/utils/collapse-helpers.ts +0 -256
  434. package/src/utils/config-dir.ts +0 -24
  435. package/src/utils/constants.ts +0 -190
  436. package/src/utils/create-event-handler-state.ts +0 -73
  437. package/src/utils/create-run-config.ts +0 -121
  438. package/src/utils/detect-shell.ts +0 -116
  439. package/src/utils/directory-browser.ts +0 -72
  440. package/src/utils/engagement.ts +0 -55
  441. package/src/utils/env.ts +0 -92
  442. package/src/utils/error-handling.ts +0 -255
  443. package/src/utils/error-messages.ts +0 -62
  444. package/src/utils/exit-cleanly.ts +0 -86
  445. package/src/utils/feedback-helpers.ts +0 -103
  446. package/src/utils/feedback-submission.ts +0 -22
  447. package/src/utils/fetch-usage.ts +0 -76
  448. package/src/utils/fingerprint.ts +0 -245
  449. package/src/utils/format-elapsed-time.ts +0 -33
  450. package/src/utils/format-session-units.ts +0 -6
  451. package/src/utils/format-timeout.ts +0 -28
  452. package/src/utils/format-validation-errors-for-message.ts +0 -84
  453. package/src/utils/freebuff-agent-selection.ts +0 -33
  454. package/src/utils/freebuff-instance-owner.ts +0 -66
  455. package/src/utils/freebuff-model-navigation.ts +0 -50
  456. package/src/utils/freebuff-premium-reset.ts +0 -55
  457. package/src/utils/freebuff-referral-cache.ts +0 -51
  458. package/src/utils/freebuff-session-api.ts +0 -243
  459. package/src/utils/freebuff-session-display.ts +0 -21
  460. package/src/utils/freebuff-streak-line.ts +0 -93
  461. package/src/utils/git.ts +0 -17
  462. package/src/utils/helpers.ts +0 -47
  463. package/src/utils/image-display.ts +0 -67
  464. package/src/utils/image-handler.ts +0 -325
  465. package/src/utils/image-processor.ts +0 -133
  466. package/src/utils/image-thumbnail.ts +0 -78
  467. package/src/utils/implementor-helpers.ts +0 -787
  468. package/src/utils/input-modes.ts +0 -174
  469. package/src/utils/keyboard-actions.ts +0 -392
  470. package/src/utils/keypad-keys.ts +0 -47
  471. package/src/utils/layout-helpers.ts +0 -33
  472. package/src/utils/lazy-response-ads.ts +0 -1
  473. package/src/utils/local-agent-registry.ts +0 -474
  474. package/src/utils/log-shipper.ts +0 -126
  475. package/src/utils/logger.ts +0 -327
  476. package/src/utils/markdown-renderer.tsx +0 -1090
  477. package/src/utils/math.ts +0 -3
  478. package/src/utils/message-block-helpers.ts +0 -684
  479. package/src/utils/message-history.ts +0 -143
  480. package/src/utils/message-tree-utils.ts +0 -67
  481. package/src/utils/message-updater.ts +0 -279
  482. package/src/utils/open-file.ts +0 -146
  483. package/src/utils/open-url.ts +0 -63
  484. package/src/utils/path-completion.ts +0 -111
  485. package/src/utils/path-helpers.ts +0 -38
  486. package/src/utils/pending-attachments.ts +0 -360
  487. package/src/utils/polling-backoff.ts +0 -59
  488. package/src/utils/project-picker.ts +0 -12
  489. package/src/utils/queue-panel-actions.ts +0 -70
  490. package/src/utils/recent-projects.ts +0 -156
  491. package/src/utils/renderer-cleanup.ts +0 -233
  492. package/src/utils/response-ad-positions.ts +0 -1
  493. package/src/utils/run-state-storage.ts +0 -603
  494. package/src/utils/safe-json.ts +0 -153
  495. package/src/utils/sdk-event-handlers.ts +0 -571
  496. package/src/utils/send-message-helpers.ts +0 -210
  497. package/src/utils/send-message-timer.ts +0 -110
  498. package/src/utils/settings.ts +0 -226
  499. package/src/utils/skill-registry.ts +0 -94
  500. package/src/utils/spawn-agent-matcher.ts +0 -55
  501. package/src/utils/status-indicator-state.ts +0 -107
  502. package/src/utils/stream-chunk-processor.ts +0 -57
  503. package/src/utils/strings.ts +0 -271
  504. package/src/utils/subscription.ts +0 -31
  505. package/src/utils/syntax-highlighter.tsx +0 -19
  506. package/src/utils/terminal-color-detection.ts +0 -474
  507. package/src/utils/terminal-command-broker.ts +0 -467
  508. package/src/utils/terminal-enter-detection.ts +0 -63
  509. package/src/utils/terminal-images.ts +0 -229
  510. package/src/utils/terminal-io.ts +0 -85
  511. package/src/utils/terminal-protocol-controller.ts +0 -173
  512. package/src/utils/terminal-reset-sequences.ts +0 -33
  513. package/src/utils/terminal-title.ts +0 -84
  514. package/src/utils/terminal-watchdog.ts +0 -366
  515. package/src/utils/text-layout.ts +0 -149
  516. package/src/utils/theme-config.ts +0 -141
  517. package/src/utils/theme-system.ts +0 -1206
  518. package/src/utils/think-tag-parser.ts +0 -95
  519. package/src/utils/time-format.ts +0 -21
  520. package/src/utils/trace-writer.ts +0 -162
  521. package/src/utils/ui-constants.ts +0 -69
  522. package/src/utils/update-checker.ts +0 -110
  523. package/src/utils/usage-banner-state.ts +0 -146
  524. package/src/utils/validation-error-formatting.ts +0 -86
  525. package/src/utils/validation-error-helpers.ts +0 -19
  526. package/src/utils/windows-terminal-health.ts +0 -124
  527. package/src/utils/word-wrap-utils.ts +0 -53
  528. package/src/utils/write-file-atomic.ts +0 -54
  529. 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
- }