@alumbwe/anvil 1.0.42 → 1.0.50

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 (238) hide show
  1. package/bin/anvil.js +18 -0
  2. package/package.json +13 -6
  3. package/src/agents/bundled-agents.generated.ts +1 -1
  4. package/src/index.tsx +27 -4
  5. package/src/login/plain-login.ts +60 -0
  6. package/src/login/supabase-auth.ts +346 -0
  7. package/src/utils/anvil-client.ts +4 -0
  8. package/src/utils/auth.ts +16 -4
  9. package/src/utils/config-dir.ts +1 -1
  10. package/vendor/@anvil/common/src/constants/deepseek-direct.ts +30 -1
  11. package/vendor/@anvil/common/src/util/project-ignore.ts +4 -3
  12. package/vendor/@anvil/sdk/dist/index.cjs +46 -10
  13. package/vendor/@anvil/sdk/dist/index.mjs +46 -10
  14. package/src/commands/__tests__/anvil-command-aliases.test.ts +0 -56
  15. package/src/commands/__tests__/bash-command.test.ts +0 -445
  16. package/src/commands/__tests__/command-args.test.ts +0 -349
  17. package/src/commands/__tests__/copy-conversation-clipboard.test.ts +0 -108
  18. package/src/commands/__tests__/copy-conversation.test.ts +0 -226
  19. package/src/commands/__tests__/diagnostics-command.test.ts +0 -35
  20. package/src/commands/__tests__/image.test.ts +0 -99
  21. package/src/commands/__tests__/init.test.ts +0 -456
  22. package/src/commands/__tests__/new-command-rotates-chat.test.ts +0 -91
  23. package/src/commands/__tests__/process-diagnostics.test.ts +0 -54
  24. package/src/commands/__tests__/prompt-builders.test.ts +0 -28
  25. package/src/commands/__tests__/router-input.test.ts +0 -298
  26. package/src/components/__tests__/ad-banner.test.tsx +0 -83
  27. package/src/components/__tests__/anvil-landing-heading-row.test.tsx +0 -91
  28. package/src/components/__tests__/anvil-model-selector.test.tsx +0 -519
  29. package/src/components/__tests__/anvil-offer-invariants.test.ts +0 -54
  30. package/src/components/__tests__/anvil-takeover-prompt.test.tsx +0 -212
  31. package/src/components/__tests__/chat-input-bar.test.tsx +0 -130
  32. package/src/components/__tests__/grid-layout.integration.test.tsx +0 -304
  33. package/src/components/__tests__/grid-layout.test.tsx +0 -1051
  34. package/src/components/__tests__/message-block.completion.test.tsx +0 -104
  35. package/src/components/__tests__/message-block.streaming.test.tsx +0 -78
  36. package/src/components/__tests__/message-with-agents.test.tsx +0 -579
  37. package/src/components/__tests__/multiline-input.test.tsx +0 -1132
  38. package/src/components/__tests__/queue-panel.test.tsx +0 -398
  39. package/src/components/__tests__/selectable-list.test.ts +0 -232
  40. package/src/components/__tests__/status-bar.test.tsx +0 -44
  41. package/src/components/__tests__/status-indicator.test.tsx +0 -204
  42. package/src/components/__tests__/terminal-thinking-orb.test.tsx +0 -111
  43. package/src/components/__tests__/user-error-banner.test.tsx +0 -102
  44. package/src/components/ask-user/__tests__/multiple-choice-form.test.ts +0 -432
  45. package/src/components/ask-user/__tests__/validation.test.ts +0 -213
  46. package/src/components/tools/__tests__/apply-patch.test.tsx +0 -84
  47. package/src/components/tools/__tests__/code-search.test.tsx +0 -45
  48. package/src/components/tools/__tests__/gravity-index.test.ts +0 -76
  49. package/src/components/tools/__tests__/render-ui.test.tsx +0 -92
  50. package/src/components/tools/__tests__/run-terminal-command.test.ts +0 -233
  51. package/src/contexts/__tests__/chat-runtime-context.test.tsx +0 -149
  52. package/src/data/slash-commands.test.ts +0 -24
  53. package/src/hooks/__tests__/holds-live-anvil-slot.test.ts +0 -49
  54. package/src/hooks/__tests__/session-fetch-signal.test.ts +0 -63
  55. package/src/hooks/__tests__/use-activity-query.test.ts +0 -1000
  56. package/src/hooks/__tests__/use-ask-user-bridge.test.ts +0 -176
  57. package/src/hooks/__tests__/use-auth-query.test.ts +0 -21
  58. package/src/hooks/__tests__/use-chat-input-remount.test.tsx +0 -66
  59. package/src/hooks/__tests__/use-clipboard.test.tsx +0 -73
  60. package/src/hooks/__tests__/use-connection-status.test.ts +0 -112
  61. package/src/hooks/__tests__/use-directory-browser.test.ts +0 -203
  62. package/src/hooks/__tests__/use-gravity-ad.test.ts +0 -73
  63. package/src/hooks/__tests__/use-grid-layout.test.ts +0 -349
  64. package/src/hooks/__tests__/use-input-history.test.ts +0 -699
  65. package/src/hooks/__tests__/use-message-queue-editing.test.tsx +0 -209
  66. package/src/hooks/__tests__/use-message-queue.test.tsx +0 -71
  67. package/src/hooks/__tests__/use-path-tab-completion.test.ts +0 -315
  68. package/src/hooks/__tests__/use-queue-controls.test.ts +0 -68
  69. package/src/hooks/__tests__/use-queue-ui.test.tsx +0 -83
  70. package/src/hooks/__tests__/use-searchable-list.test.ts +0 -359
  71. package/src/hooks/__tests__/use-send-message-timer.test.ts +0 -146
  72. package/src/hooks/__tests__/use-suggestion-engine-mention.test.ts +0 -361
  73. package/src/hooks/__tests__/use-suggestion-engine.test.ts +0 -485
  74. package/src/hooks/__tests__/use-terminal-focus.test.ts +0 -66
  75. package/src/hooks/__tests__/use-terminal-layout.test.ts +0 -675
  76. package/src/hooks/__tests__/use-timeout.test.ts +0 -330
  77. package/src/hooks/__tests__/use-usage-query.test.ts +0 -502
  78. package/src/hooks/__tests__/use-user-details-query.test.ts +0 -195
  79. package/src/hooks/helpers/__tests__/send-message.test.ts +0 -1918
  80. package/src/init/__tests__/init-direnv.test.ts +0 -526
  81. package/src/state/__tests__/chat-store-focus.test.ts +0 -22
  82. package/src/state/__tests__/feedback-store.test.ts +0 -257
  83. package/src/utils/__tests__/active-run.test.ts +0 -140
  84. package/src/utils/__tests__/activity-tracker.test.ts +0 -440
  85. package/src/utils/__tests__/agent-display.test.ts +0 -139
  86. package/src/utils/__tests__/analytics-client.test.ts +0 -262
  87. package/src/utils/__tests__/anonymous-id.test.ts +0 -74
  88. package/src/utils/__tests__/anvil-api.test.ts +0 -578
  89. package/src/utils/__tests__/anvil-instance-owner.test.ts +0 -69
  90. package/src/utils/__tests__/anvil-model-navigation.test.ts +0 -102
  91. package/src/utils/__tests__/anvil-premium-reset.test.ts +0 -79
  92. package/src/utils/__tests__/anvil-referral-cache.test.ts +0 -116
  93. package/src/utils/__tests__/anvil-session-api.test.ts +0 -215
  94. package/src/utils/__tests__/anvil-session-display.test.ts +0 -23
  95. package/src/utils/__tests__/anvil-streak-line.test.ts +0 -217
  96. package/src/utils/__tests__/arrays.test.ts +0 -264
  97. package/src/utils/__tests__/bash-context-processor.test.ts +0 -38
  98. package/src/utils/__tests__/block-processor.test.ts +0 -810
  99. package/src/utils/__tests__/chat-history.test.ts +0 -203
  100. package/src/utils/__tests__/chat-input-key-intercept.test.ts +0 -40
  101. package/src/utils/__tests__/chat-meta.test.ts +0 -130
  102. package/src/utils/__tests__/clipboard-linux.test.ts +0 -359
  103. package/src/utils/__tests__/clipboard.test.ts +0 -910
  104. package/src/utils/__tests__/code-search-summary.test.ts +0 -84
  105. package/src/utils/__tests__/collapse-helpers.test.ts +0 -1109
  106. package/src/utils/__tests__/error-handling.test.ts +0 -658
  107. package/src/utils/__tests__/exit-cleanly.test.ts +0 -110
  108. package/src/utils/__tests__/feedback-helpers.test.ts +0 -444
  109. package/src/utils/__tests__/feedback-submission.test.ts +0 -26
  110. package/src/utils/__tests__/fetch-usage.test.ts +0 -355
  111. package/src/utils/__tests__/fingerprint.test.ts +0 -144
  112. package/src/utils/__tests__/fixtures/terminal-command-broker-child.ts +0 -7
  113. package/src/utils/__tests__/fixtures/terminal-command-broker-entry.ts +0 -3
  114. package/src/utils/__tests__/fixtures/terminal-command-broker-owner.ts +0 -33
  115. package/src/utils/__tests__/format-elapsed-time.test.ts +0 -103
  116. package/src/utils/__tests__/format-timeout.test.ts +0 -87
  117. package/src/utils/__tests__/image-dimensions.test.ts +0 -249
  118. package/src/utils/__tests__/image-processor.test.ts +0 -209
  119. package/src/utils/__tests__/implementor-helpers.test.ts +0 -1458
  120. package/src/utils/__tests__/keyboard-actions.test.ts +0 -660
  121. package/src/utils/__tests__/layout-helpers.test.ts +0 -36
  122. package/src/utils/__tests__/lazy-response-ads.test.ts +0 -167
  123. package/src/utils/__tests__/log-shipper.test.ts +0 -53
  124. package/src/utils/__tests__/markdown-renderer.test.tsx +0 -421
  125. package/src/utils/__tests__/message-block-helpers.test.ts +0 -1263
  126. package/src/utils/__tests__/message-updater.test.ts +0 -645
  127. package/src/utils/__tests__/osc-timeout-scenarios.test.ts +0 -172
  128. package/src/utils/__tests__/pending-attachments.test.ts +0 -277
  129. package/src/utils/__tests__/polling-backoff.test.ts +0 -51
  130. package/src/utils/__tests__/queue-panel-actions.test.ts +0 -148
  131. package/src/utils/__tests__/run-state-storage.test.ts +0 -919
  132. package/src/utils/__tests__/safe-json.test.ts +0 -77
  133. package/src/utils/__tests__/sdk-event-handlers.test.ts +0 -685
  134. package/src/utils/__tests__/send-message-helpers.test.ts +0 -1898
  135. package/src/utils/__tests__/send-message-timer.test.ts +0 -64
  136. package/src/utils/__tests__/settings.test.ts +0 -99
  137. package/src/utils/__tests__/strings.test.ts +0 -192
  138. package/src/utils/__tests__/terminal-color-detection.test.ts +0 -425
  139. package/src/utils/__tests__/terminal-command-broker.test.ts +0 -528
  140. package/src/utils/__tests__/terminal-enter-detection.test.ts +0 -77
  141. package/src/utils/__tests__/terminal-io.test.ts +0 -137
  142. package/src/utils/__tests__/terminal-protocol-controller.test.ts +0 -119
  143. package/src/utils/__tests__/text-layout.test.ts +0 -82
  144. package/src/utils/__tests__/theme-platform-detection.test.ts +0 -125
  145. package/src/utils/__tests__/think-tag-parser.test.ts +0 -159
  146. package/src/utils/__tests__/trace-writer.test.ts +0 -223
  147. package/src/utils/__tests__/trim-chat-logs.test.ts +0 -78
  148. package/src/utils/__tests__/usage-banner-state.test.ts +0 -298
  149. package/src/utils/__tests__/validation-error-formatting.test.ts +0 -126
  150. package/src/utils/__tests__/windows-terminal-health.test.ts +0 -101
  151. package/src/utils/__tests__/write-file-atomic.test.ts +0 -127
  152. package/vendor/@anvil/common/src/__tests__/agent-validation.test.ts +0 -846
  153. package/vendor/@anvil/common/src/__tests__/anvil-model-availability.test.ts +0 -101
  154. package/vendor/@anvil/common/src/__tests__/anvil-models.test.ts +0 -1408
  155. package/vendor/@anvil/common/src/__tests__/anvil-public-data-use-copy.test.ts +0 -258
  156. package/vendor/@anvil/common/src/__tests__/anvil-referral-tiers.test.ts +0 -48
  157. package/vendor/@anvil/common/src/__tests__/anvil-spend-ceilings.test.ts +0 -282
  158. package/vendor/@anvil/common/src/__tests__/anvil-trust.test.ts +0 -526
  159. package/vendor/@anvil/common/src/__tests__/deepseek-direct.test.ts +0 -68
  160. package/vendor/@anvil/common/src/__tests__/disposable-email.test.ts +0 -132
  161. package/vendor/@anvil/common/src/__tests__/dynamic-agent-template-schema.test.ts +0 -422
  162. package/vendor/@anvil/common/src/__tests__/env-ci.test.ts +0 -167
  163. package/vendor/@anvil/common/src/__tests__/env-process.test.ts +0 -145
  164. package/vendor/@anvil/common/src/__tests__/foreign-client-shipped-agents.test.ts +0 -234
  165. package/vendor/@anvil/common/src/__tests__/foreign-client-signals.test.ts +0 -379
  166. package/vendor/@anvil/common/src/__tests__/free-agents.test.ts +0 -807
  167. package/vendor/@anvil/common/src/__tests__/gravity-capi.test.ts +0 -154
  168. package/vendor/@anvil/common/src/__tests__/handlesteps-parsing.test.ts +0 -246
  169. package/vendor/@anvil/common/src/__tests__/kimi-k3-god-only.test.ts +0 -77
  170. package/vendor/@anvil/common/src/__tests__/model-config.test.ts +0 -89
  171. package/vendor/@anvil/common/src/__tests__/project-file-tree.test.ts +0 -298
  172. package/vendor/@anvil/common/src/__tests__/provisioned-model-tiers.test.ts +0 -116
  173. package/vendor/@anvil/common/src/__tests__/reasoning-effort.test.ts +0 -167
  174. package/vendor/@anvil/common/src/__tests__/reddit-capi.test.ts +0 -132
  175. package/vendor/@anvil/common/src/__tests__/response-ad-positions.test.ts +0 -72
  176. package/vendor/@anvil/common/src/__tests__/user-state.test.ts +0 -30
  177. package/vendor/@anvil/common/src/constants/__tests__/anvil-onboarding-gate.test.ts +0 -63
  178. package/vendor/@anvil/common/src/constants/__tests__/anvil-onboarding.test.ts +0 -246
  179. package/vendor/@anvil/common/src/constants/__tests__/cf-worker-signals.test.ts +0 -106
  180. package/vendor/@anvil/common/src/testing/TESTING_PATTERNS.md +0 -351
  181. package/vendor/@anvil/common/src/testing/anvil-offer-invariants.ts +0 -169
  182. package/vendor/@anvil/common/src/testing/errors.ts +0 -33
  183. package/vendor/@anvil/common/src/testing/fixtures/agent-runtime.ts +0 -334
  184. package/vendor/@anvil/common/src/testing/impl/agent-runtime.ts +0 -6
  185. package/vendor/@anvil/common/src/testing/index.ts +0 -84
  186. package/vendor/@anvil/common/src/testing/mock-modules.ts +0 -53
  187. package/vendor/@anvil/common/src/testing/mock-types.ts +0 -123
  188. package/vendor/@anvil/common/src/testing/mocks/analytics.ts +0 -261
  189. package/vendor/@anvil/common/src/testing/mocks/child-process.ts +0 -93
  190. package/vendor/@anvil/common/src/testing/mocks/crypto.ts +0 -218
  191. package/vendor/@anvil/common/src/testing/mocks/database.ts +0 -337
  192. package/vendor/@anvil/common/src/testing/mocks/fetch.ts +0 -219
  193. package/vendor/@anvil/common/src/testing/mocks/filesystem.ts +0 -166
  194. package/vendor/@anvil/common/src/testing/mocks/index.ts +0 -101
  195. package/vendor/@anvil/common/src/testing/mocks/logger.ts +0 -135
  196. package/vendor/@anvil/common/src/testing/mocks/stream.ts +0 -313
  197. package/vendor/@anvil/common/src/testing/mocks/timers.ts +0 -132
  198. package/vendor/@anvil/common/src/testing/mocks/tree-sitter.ts +0 -127
  199. package/vendor/@anvil/common/src/testing/setup.ts +0 -282
  200. package/vendor/@anvil/common/src/tools/__tests__/compile-tool-definitions.test.ts +0 -34
  201. package/vendor/@anvil/common/src/tools/params/__tests__/coerce-to-array.test.ts +0 -213
  202. package/vendor/@anvil/common/src/tools/params/__tests__/read-docs.test.ts +0 -47
  203. package/vendor/@anvil/common/src/tools/params/__tests__/render-ui.test.ts +0 -65
  204. package/vendor/@anvil/common/src/tools/params/tool/__tests__/run-terminal-command-timeout.test.ts +0 -49
  205. package/vendor/@anvil/common/src/types/__tests__/dynamic-agent-template.test.ts +0 -20
  206. package/vendor/@anvil/common/src/util/__tests__/ad-user-agent.test.ts +0 -26
  207. package/vendor/@anvil/common/src/util/__tests__/analytics-dispatcher.test.ts +0 -122
  208. package/vendor/@anvil/common/src/util/__tests__/analytics-log.test.ts +0 -102
  209. package/vendor/@anvil/common/src/util/__tests__/analytics-sampling.test.ts +0 -143
  210. package/vendor/@anvil/common/src/util/__tests__/anvil-streak.test.ts +0 -202
  211. package/vendor/@anvil/common/src/util/__tests__/anvil-usage-summary.test.ts +0 -193
  212. package/vendor/@anvil/common/src/util/__tests__/axiom-only-log.test.ts +0 -140
  213. package/vendor/@anvil/common/src/util/__tests__/client-user-agent.test.ts +0 -148
  214. package/vendor/@anvil/common/src/util/__tests__/engagement-tracker.test.ts +0 -115
  215. package/vendor/@anvil/common/src/util/__tests__/env-file-path.test.ts +0 -57
  216. package/vendor/@anvil/common/src/util/__tests__/error-abort.test.ts +0 -774
  217. package/vendor/@anvil/common/src/util/__tests__/error-api-details.test.ts +0 -206
  218. package/vendor/@anvil/common/src/util/__tests__/file-read-limits.test.ts +0 -173
  219. package/vendor/@anvil/common/src/util/__tests__/format-code-search.test.ts +0 -60
  220. package/vendor/@anvil/common/src/util/__tests__/log-mirror.test.ts +0 -31
  221. package/vendor/@anvil/common/src/util/__tests__/messages.test.ts +0 -1252
  222. package/vendor/@anvil/common/src/util/__tests__/partial-json-delta.test.ts +0 -505
  223. package/vendor/@anvil/common/src/util/__tests__/path.test.ts +0 -22
  224. package/vendor/@anvil/common/src/util/__tests__/project-ignore.test.ts +0 -90
  225. package/vendor/@anvil/common/src/util/__tests__/promise.test.ts +0 -325
  226. package/vendor/@anvil/common/src/util/__tests__/rate-limit.test.ts +0 -28
  227. package/vendor/@anvil/common/src/util/__tests__/reddit-anvil-retention.test.ts +0 -130
  228. package/vendor/@anvil/common/src/util/__tests__/saxy.test.ts +0 -1008
  229. package/vendor/@anvil/common/src/util/__tests__/split-data.test.ts +0 -289
  230. package/vendor/@anvil/common/src/util/__tests__/string.test.ts +0 -239
  231. package/vendor/@anvil/common/src/util/__tests__/thread-title.test.ts +0 -70
  232. package/vendor/@anvil/common/src/util/__tests__/tool-result-media-order.test.ts +0 -108
  233. package/vendor/@anvil/common/src/util/__tests__/ttft-histogram.test.ts +0 -180
  234. package/vendor/@anvil/common/src/util/__tests__/with-timeout.test.ts +0 -24
  235. package/vendor/@anvil/common/src/util/__tests__/zoned-time.test.ts +0 -88
  236. package/vendor/@anvil/common/src/util/skills.test.ts +0 -44
  237. package/vendor/@anvil/sdk/dist/index.cjs.map +0 -440
  238. package/vendor/@anvil/sdk/dist/index.mjs.map +0 -440
@@ -1,526 +0,0 @@
1
- import { describe, expect, it } from 'bun:test'
2
-
3
- import {
4
- assessAnvilTrust,
5
- ANVIL_TRUST_FALLBACK_LEVEL,
6
- ANVIL_TRUST_LEVELS,
7
- ANVIL_TRUST_EARNED,
8
- ANVIL_TRUST_LIMITS,
9
- ANVIL_TRUST_THRESHOLDS,
10
- anvilTrustLimits,
11
- isAtLeastTrustLevel,
12
- toAnvilStandingInfo,
13
- type AnvilTrustSignals,
14
- } from '../constants/anvil-trust'
15
-
16
- const NOW = new Date('2026-08-11T00:00:00Z')
17
- const DAY_MS = 24 * 60 * 60 * 1000
18
-
19
- function daysAgo(days: number): Date {
20
- return new Date(NOW.getTime() - days * DAY_MS)
21
- }
22
-
23
- /** An account we know nothing about: every optional signal unknown. This is
24
- * what most pre-provenance accounts actually look like. */
25
- const UNKNOWN: AnvilTrustSignals = {
26
- accountCreatedAt: null,
27
- githubAccountCreatedAt: null,
28
- githubOldestRepoCreatedAt: null,
29
- githubPublicRepos: null,
30
- githubFollowers: null,
31
- githubTwoFactorEnabled: null,
32
- activeDays: 0,
33
- approvedBounties: 0,
34
- qualifiedReferrals: 0,
35
- hasPaid: false,
36
- signupPrivacySignals: null,
37
- signupIpSource: null,
38
- signupPrefixAccountCount: null,
39
- mailboxAccountCount: null,
40
- hasUnreversedBanEvent: false,
41
- privacyFlaggedAt: null,
42
- privacyCorroboratedAt: null,
43
- thirdPartyClientAt: null,
44
- currentRiskScore: null,
45
- }
46
-
47
- function signals(overrides: Partial<AnvilTrustSignals>) {
48
- return { ...UNKNOWN, ...overrides }
49
- }
50
-
51
- describe('level ordering', () => {
52
- it('orders least- to most-established', () => {
53
- expect(ANVIL_TRUST_LEVELS).toEqual([
54
- 'new',
55
- 'verified',
56
- 'established',
57
- 'core',
58
- ])
59
- })
60
-
61
- it('compares by position, not alphabetically', () => {
62
- expect(isAtLeastTrustLevel('core', 'new')).toBe(true)
63
- expect(isAtLeastTrustLevel('new', 'verified')).toBe(false)
64
- expect(isAtLeastTrustLevel('established', 'established')).toBe(true)
65
- })
66
- })
67
-
68
- describe('limit matrix', () => {
69
- it('reproduces the pre-existing flat limits at established/full', () => {
70
- // This is the safety property of the whole rollout: the population that
71
- // already looked like ordinary heavy users must keep exactly what it had.
72
- const full = anvilTrustLimits('full', 'established')
73
- expect(full.messagesPerDay).toBe(5_000)
74
- expect(full.messagesPer5Hours).toBe(3_000)
75
- expect(full.dailySpendUsd).toBe(50)
76
- expect(full.premiumSessionsPerDay).toBe(6)
77
-
78
- const limited = anvilTrustLimits('limited', 'established')
79
- expect(limited.messagesPerDay).toBe(3_000)
80
- expect(limited.messagesPer5Hours).toBe(2_000)
81
- })
82
-
83
- it('is monotonic in level on every axis, in both regions', () => {
84
- for (const tier of ['full', 'limited'] as const) {
85
- for (let i = 1; i < ANVIL_TRUST_LEVELS.length; i++) {
86
- const lower = ANVIL_TRUST_LIMITS[tier][ANVIL_TRUST_LEVELS[i - 1]]
87
- const higher = ANVIL_TRUST_LIMITS[tier][ANVIL_TRUST_LEVELS[i]]
88
- for (const key of Object.keys(lower) as (keyof typeof lower)[]) {
89
- expect(higher[key]).toBeGreaterThanOrEqual(lower[key])
90
- }
91
- }
92
- }
93
- })
94
-
95
- it('does not scale session-shape controls, only cost controls', () => {
96
- // Browser sessions and Desktop tabs were deliberately removed: an open
97
- // session costs nothing until it generates, and the generating is already
98
- // bounded. If either reappears here, something re-added a limit that takes
99
- // visible capability from new users and saves nothing.
100
- expect(Object.keys(ANVIL_TRUST_LIMITS.full.new).sort()).toEqual([
101
- 'dailySpendUsd',
102
- 'messagesPer5Hours',
103
- 'messagesPerDay',
104
- 'premiumSessionsPerDay',
105
- 'userMessagesPerDay',
106
- ])
107
- })
108
-
109
- it('never lets the limited tier reach a premium session', () => {
110
- // The model gate already refuses it; a non-zero number here would be a
111
- // promise the rest of the system cannot keep.
112
- for (const level of ANVIL_TRUST_LEVELS) {
113
- expect(ANVIL_TRUST_LIMITS.limited[level].premiumSessionsPerDay).toBe(0)
114
- }
115
- })
116
-
117
- it('lets a limited-region core member beat a full-region verified user', () => {
118
- // The promise the region split has to make to a real developer abroad.
119
- const limitedCore = anvilTrustLimits('limited', 'core')
120
- const fullVerified = anvilTrustLimits('full', 'verified')
121
- expect(limitedCore.messagesPerDay).toBeGreaterThan(
122
- fullVerified.messagesPerDay,
123
- )
124
- expect(limitedCore.userMessagesPerDay).toBeGreaterThan(
125
- fullVerified.userMessagesPerDay,
126
- )
127
- expect(limitedCore.dailySpendUsd).toBeGreaterThan(
128
- fullVerified.dailySpendUsd,
129
- )
130
- })
131
-
132
- it('keeps every new-account daily budget above the measured p90', () => {
133
- // Sizing anchor from free-mode-rate-limiter.ts: full-tier per-user-per-day
134
- // p90 is 837. A brand-new account doing genuinely heavy work must fit.
135
- expect(
136
- anvilTrustLimits('full', 'new').messagesPerDay,
137
- ).toBeGreaterThanOrEqual(837)
138
- })
139
- })
140
-
141
- describe('scoring', () => {
142
- it('leaves an unknown account at the floor', () => {
143
- const result = assessAnvilTrust(UNKNOWN, NOW)
144
- expect(result.level).toBe('new')
145
- expect(result.score).toBe(0)
146
- expect(result.cappedBy).toBeNull()
147
- })
148
-
149
- it('treats null signals as unknown, never as suspicious', () => {
150
- // A pre-provenance account (null everything) must score the same as one
151
- // explicitly checked and found to share nothing.
152
- const unknownProvenance = assessAnvilTrust(
153
- signals({ githubAccountCreatedAt: daysAgo(400) }),
154
- NOW,
155
- )
156
- const cleanProvenance = assessAnvilTrust(
157
- signals({
158
- githubAccountCreatedAt: daysAgo(400),
159
- signupPrefixAccountCount: 1,
160
- mailboxAccountCount: 1,
161
- }),
162
- NOW,
163
- )
164
- expect(unknownProvenance.level).toBe(cleanProvenance.level)
165
- expect(unknownProvenance.cappedBy).toBeNull()
166
- })
167
-
168
- it('reaches established on GitHub age plus ordinary account history', () => {
169
- const result = assessAnvilTrust(
170
- signals({
171
- accountCreatedAt: daysAgo(120),
172
- githubAccountCreatedAt: daysAgo(3 * 365 + 10),
173
- githubOldestRepoCreatedAt: daysAgo(400),
174
- githubPublicRepos: 12,
175
- activeDays: 40,
176
- }),
177
- NOW,
178
- )
179
- // 10 linked + 20 age + 10 repo + 5 repos + 15 acct age + 10 active
180
- expect(result.score).toBe(70)
181
- expect(result.level).toBe('established')
182
- })
183
-
184
- it('lets a brand-new limited-region account climb with earned signals alone', () => {
185
- // The route that does not require owning an aged GitHub account: this is
186
- // what the Earn page has to be able to promise.
187
- const result = assessAnvilTrust(
188
- signals({
189
- accountCreatedAt: daysAgo(10),
190
- approvedBounties: 4,
191
- qualifiedReferrals: 5,
192
- }),
193
- NOW,
194
- )
195
- // 5 acct age + 20 bounties + 15 referrals
196
- expect(result.score).toBe(40)
197
- expect(result.level).toBe('verified')
198
- })
199
-
200
- it('caps bounty and referral contributions', () => {
201
- const capped = assessAnvilTrust(
202
- signals({ approvedBounties: 50, qualifiedReferrals: 50 }),
203
- NOW,
204
- )
205
- expect(capped.score).toBe(
206
- ANVIL_TRUST_EARNED.BOUNTY_POINTS * ANVIL_TRUST_EARNED.BOUNTY_CAP +
207
- ANVIL_TRUST_EARNED.REFERRAL_POINTS *
208
- ANVIL_TRUST_EARNED.REFERRAL_CAP,
209
- )
210
- })
211
-
212
- it('lets contribution alone reach core, with no GitHub and no payment', () => {
213
- // THE property the earned caps exist for. Before they were raised this
214
- // route peaked at 70 against a threshold of 75, so `core` was reachable
215
- // only by owning an aged GitHub account or by paying — which is backwards
216
- // for a program meant to give developers in unsupported regions a way to
217
- // raise their own limits.
218
- const earned = assessAnvilTrust(
219
- signals({
220
- accountCreatedAt: daysAgo(120),
221
- activeDays: 40,
222
- approvedBounties: ANVIL_TRUST_EARNED.BOUNTY_CAP,
223
- qualifiedReferrals: ANVIL_TRUST_EARNED.REFERRAL_CAP,
224
- signupPrivacySignals: [],
225
- signupIpSource: 'cloudflare',
226
- }),
227
- NOW,
228
- )
229
- expect(earned.factors.map((f) => f.id)).not.toContain('github_linked')
230
- expect(earned.score).toBeGreaterThanOrEqual(ANVIL_TRUST_THRESHOLDS.core)
231
- expect(earned.level).toBe('core')
232
- })
233
-
234
- it('keeps paying past the point someone has proved they are real', () => {
235
- // A flat incentive is not an incentive. The tenth referral and the sixth
236
- // bounty must still be worth something, or the program stops pulling
237
- // exactly where it should pull hardest.
238
- const few = assessAnvilTrust(
239
- signals({ approvedBounties: 4, qualifiedReferrals: 5 }),
240
- NOW,
241
- )
242
- const many = assessAnvilTrust(
243
- signals({ approvedBounties: 6, qualifiedReferrals: 10 }),
244
- NOW,
245
- )
246
- expect(many.score).toBeGreaterThan(few.score)
247
- })
248
-
249
- it('never returns a score outside 0..100', () => {
250
- const maxed = assessAnvilTrust(
251
- signals({
252
- accountCreatedAt: daysAgo(1000),
253
- githubAccountCreatedAt: daysAgo(4000),
254
- githubOldestRepoCreatedAt: daysAgo(3000),
255
- githubPublicRepos: 100,
256
- githubFollowers: 500,
257
- githubTwoFactorEnabled: true,
258
- activeDays: 300,
259
- approvedBounties: 20,
260
- qualifiedReferrals: 20,
261
- hasPaid: true,
262
- signupPrivacySignals: [],
263
- signupIpSource: 'cloudflare',
264
- }),
265
- NOW,
266
- )
267
- expect(maxed.score).toBe(100)
268
- expect(maxed.level).toBe('core')
269
- })
270
-
271
- it('ignores a future-dated timestamp rather than crediting it', () => {
272
- const skewed = assessAnvilTrust(
273
- signals({
274
- githubAccountCreatedAt: new Date(NOW.getTime() + 10 * DAY_MS),
275
- }),
276
- NOW,
277
- )
278
- // Linked (10) but no age credit.
279
- expect(skewed.score).toBe(10)
280
- })
281
- })
282
-
283
- describe('caps', () => {
284
- const HIGH_SCORE: Partial<AnvilTrustSignals> = {
285
- accountCreatedAt: daysAgo(400),
286
- githubAccountCreatedAt: daysAgo(2000),
287
- githubOldestRepoCreatedAt: daysAgo(1000),
288
- githubPublicRepos: 20,
289
- githubFollowers: 50,
290
- githubTwoFactorEnabled: true,
291
- activeDays: 100,
292
- approvedBounties: 4,
293
- }
294
-
295
- it('caps a live anonymous network at verified however high the score', () => {
296
- const result = assessAnvilTrust(
297
- signals({ ...HIGH_SCORE, currentRiskScore: 90 }),
298
- NOW,
299
- )
300
- expect(result.uncappedLevel).toBe('core')
301
- expect(result.level).toBe('verified')
302
- expect(result.cappedBy).toBe('anonymous_network')
303
- })
304
-
305
- it('does not cap on a low current risk score', () => {
306
- const result = assessAnvilTrust(
307
- signals({ ...HIGH_SCORE, currentRiskScore: 10 }),
308
- NOW,
309
- )
310
- expect(result.level).toBe('core')
311
- expect(result.cappedBy).toBeNull()
312
- })
313
-
314
- it('caps a VPN signup at established, not lower', () => {
315
- const result = assessAnvilTrust(
316
- signals({ ...HIGH_SCORE, signupPrivacySignals: ['vpn'] }),
317
- NOW,
318
- )
319
- expect(result.level).toBe('established')
320
- expect(result.cappedBy).toBe('signup_privacy_egress')
321
- })
322
-
323
- it('credits a clean signup rather than capping it', () => {
324
- const result = assessAnvilTrust(
325
- signals({ ...HIGH_SCORE, signupPrivacySignals: [] }),
326
- NOW,
327
- )
328
- expect(result.cappedBy).toBeNull()
329
- expect(result.factors.some((f) => f.id === 'clean_signup')).toBe(true)
330
- })
331
-
332
- it('applies the lowest cap when several bind', () => {
333
- const result = assessAnvilTrust(
334
- signals({
335
- ...HIGH_SCORE,
336
- signupPrivacySignals: ['vpn'],
337
- mailboxAccountCount: 5,
338
- }),
339
- NOW,
340
- )
341
- expect(result.level).toBe('verified')
342
- expect(result.cappedBy).toBe('shared_mailbox')
343
- })
344
-
345
- it('leads the next steps with the cap, since points cannot clear it', () => {
346
- const result = assessAnvilTrust(
347
- signals({ ...HIGH_SCORE, currentRiskScore: 90 }),
348
- NOW,
349
- )
350
- expect(result.nextSteps[0]?.id).toBe('cap_anonymous_network')
351
- expect(result.nextSteps[0]?.label).toMatch(/VPN/i)
352
- })
353
-
354
- it('caps an account with unreversed enforcement history', () => {
355
- const result = assessAnvilTrust(
356
- signals({ ...HIGH_SCORE, hasUnreversedBanEvent: true }),
357
- NOW,
358
- )
359
- expect(result.level).toBe('verified')
360
- expect(result.cappedBy).toBe('past_enforcement')
361
- })
362
- })
363
-
364
- describe('next steps', () => {
365
- it('leads with connecting GitHub for an account that has none', () => {
366
- const result = assessAnvilTrust(UNKNOWN, NOW)
367
- expect(result.nextSteps[0]?.id).toBe('connect_github')
368
- expect(result.nextSteps[0]?.points).toBe(30)
369
- })
370
-
371
- it('stops offering steps the user has already exhausted', () => {
372
- const result = assessAnvilTrust(
373
- signals({
374
- approvedBounties: ANVIL_TRUST_EARNED.BOUNTY_CAP,
375
- qualifiedReferrals: ANVIL_TRUST_EARNED.REFERRAL_CAP,
376
- }),
377
- NOW,
378
- )
379
- expect(result.nextSteps.map((s) => s.id)).not.toContain('bounties')
380
- expect(result.nextSteps.map((s) => s.id)).not.toContain('referrals')
381
- })
382
-
383
- it('offers the remaining value, not the full value, of a partial step', () => {
384
- const result = assessAnvilTrust(signals({ approvedBounties: 2 }), NOW)
385
- const remaining =
386
- (ANVIL_TRUST_EARNED.BOUNTY_CAP - 2) *
387
- ANVIL_TRUST_EARNED.BOUNTY_POINTS
388
- expect(result.nextSteps.find((s) => s.id === 'bounties')?.points).toBe(
389
- remaining,
390
- )
391
- })
392
- })
393
-
394
- describe('wire shape', () => {
395
- it('never puts a raw limit on the wire', () => {
396
- // A published limit is a published target: the abuse pattern here is
397
- // sustained pacing just under the caps, so the caps stay server-side. This
398
- // asserts the payload rather than the component, because the payload is
399
- // what a future surface would reach for.
400
- const info = toAnvilStandingInfo(
401
- assessAnvilTrust(signals({ approvedBounties: 4 }), NOW),
402
- 'limited',
403
- )
404
- expect(info).not.toHaveProperty('limits')
405
-
406
- // Scoped to the distinctive values. Small ones (a $3 spend cap, 6 premium
407
- // sessions) collide with legitimate point values in the copy — "worth 3
408
- // points" is not a leaked limit, and asserting on them would fail for the
409
- // wrong reason.
410
- const serialized = JSON.stringify(info)
411
- const distinctive = Object.values(
412
- anvilTrustLimits('limited', info.level),
413
- ).filter((value) => value >= 100)
414
- expect(distinctive.length).toBeGreaterThan(0)
415
- for (const value of distinctive) {
416
- expect(serialized).not.toContain(String(value))
417
- }
418
- expect(info.accessTier).toBe('limited')
419
- })
420
-
421
- it('describes each axis in words', () => {
422
- const info = toAnvilStandingInfo(
423
- assessAnvilTrust(signals({ approvedBounties: 4 }), NOW),
424
- 'limited',
425
- )
426
- expect(info.highlights.map((h) => h.label)).toEqual([
427
- 'Prompts a day',
428
- 'Work per prompt',
429
- 'Premium models',
430
- ])
431
- for (const highlight of info.highlights) {
432
- expect(highlight.value).not.toMatch(/\d/)
433
- }
434
- })
435
-
436
- it('frames premium as a region fact where no level can unlock it', () => {
437
- // Every limited-row level has 0 premium sessions, so calling it a
438
- // shortfall would send the user chasing points that cannot buy it.
439
- const limited = toAnvilStandingInfo(
440
- assessAnvilTrust(signals({ approvedBounties: 4 }), NOW),
441
- 'limited',
442
- )
443
- expect(
444
- limited.highlights.find((h) => h.label === 'Premium models')?.value,
445
- ).toMatch(/region/i)
446
-
447
- const full = toAnvilStandingInfo(
448
- assessAnvilTrust(signals({ approvedBounties: 4 }), NOW),
449
- 'full',
450
- )
451
- expect(
452
- full.highlights.find((h) => h.label === 'Premium models')?.value,
453
- ).not.toMatch(/region/i)
454
- })
455
-
456
- it('reports the next threshold, and nothing beyond core', () => {
457
- const verified = toAnvilStandingInfo(
458
- assessAnvilTrust(
459
- signals({ approvedBounties: 4, activeDays: 10 }),
460
- NOW,
461
- ),
462
- 'full',
463
- )
464
- expect(verified.level).toBe('verified')
465
- expect(verified.nextLevel).toBe('established')
466
- expect(verified.nextLevelAt).toBe(ANVIL_TRUST_THRESHOLDS.established)
467
-
468
- const core = toAnvilStandingInfo(
469
- assessAnvilTrust(
470
- signals({
471
- accountCreatedAt: daysAgo(400),
472
- githubAccountCreatedAt: daysAgo(2000),
473
- githubOldestRepoCreatedAt: daysAgo(1000),
474
- githubPublicRepos: 20,
475
- githubFollowers: 50,
476
- githubTwoFactorEnabled: true,
477
- activeDays: 100,
478
- approvedBounties: 4,
479
- }),
480
- NOW,
481
- ),
482
- 'full',
483
- )
484
- expect(core.level).toBe('core')
485
- expect(core.nextLevel).toBeNull()
486
- expect(core.nextLevelAt).toBeNull()
487
- })
488
-
489
- it('explains a cap in the copy the client renders', () => {
490
- const info = toAnvilStandingInfo(
491
- assessAnvilTrust(
492
- signals({
493
- accountCreatedAt: daysAgo(400),
494
- githubAccountCreatedAt: daysAgo(2000),
495
- activeDays: 100,
496
- currentRiskScore: 99,
497
- }),
498
- NOW,
499
- ),
500
- 'full',
501
- )
502
- expect(info.cappedBy).toBe('anonymous_network')
503
- expect(info.cappedReason).toMatch(/VPN/i)
504
- })
505
-
506
- it('reports no cap when the cap sits at or above the earned level', () => {
507
- // An account scoring 'new' is not "capped at verified" — nothing bound.
508
- const info = toAnvilStandingInfo(
509
- assessAnvilTrust(signals({ currentRiskScore: 99 }), NOW),
510
- 'full',
511
- )
512
- expect(info.level).toBe('new')
513
- expect(info.cappedBy).toBeNull()
514
- })
515
- })
516
-
517
- describe('failure behaviour', () => {
518
- it('falls back to the level that reproduces the old flat limits', () => {
519
- // A broken resolver must cost us the enforcement, never the users: if this
520
- // were 'new', one degraded query would throttle the whole product.
521
- expect(ANVIL_TRUST_FALLBACK_LEVEL).toBe('established')
522
- expect(anvilTrustLimits('full', ANVIL_TRUST_FALLBACK_LEVEL)).toEqual(
523
- anvilTrustLimits('full', 'established'),
524
- )
525
- })
526
- })
@@ -1,68 +0,0 @@
1
- import { describe, expect, test } from 'bun:test'
2
-
3
- import {
4
- DEFAULT_DIRECT_DEEPSEEK_MODEL,
5
- remapModelForDirectDeepSeek,
6
- } from '../constants/deepseek-direct'
7
-
8
- describe('remapModelForDirectDeepSeek', () => {
9
- test('keeps the model name for DeepSeek lane ids', () => {
10
- expect(remapModelForDirectDeepSeek('deepseek/deepseek-v4-pro')).toBe(
11
- 'deepseek-v4-pro',
12
- )
13
- expect(remapModelForDirectDeepSeek('deepseek/deepseek-v4-flash')).toBe(
14
- 'deepseek-v4-flash',
15
- )
16
- })
17
-
18
- test('normalizes -max context tiers to their base model', () => {
19
- expect(remapModelForDirectDeepSeek('deepseek/deepseek-v4-pro-max')).toBe(
20
- 'deepseek-v4-pro',
21
- )
22
- expect(remapModelForDirectDeepSeek('deepseek/deepseek-v4-flash-max')).toBe(
23
- 'deepseek-v4-flash',
24
- )
25
- })
26
-
27
- test('maps the legacy fireworks lane to flash', () => {
28
- expect(remapModelForDirectDeepSeek('fireworks/deepseek-v4-flash')).toBe(
29
- 'deepseek-v4-flash',
30
- )
31
- })
32
-
33
- test('falls back to the default model for other providers', () => {
34
- // The default base agent requests anthropic/claude-opus-5, which DeepSeek
35
- // rejects outright ("The supported API model names are deepseek-v4-pro or
36
- // deepseek-v4-flash"). Every non-DeepSeek provider must fall back.
37
- for (const model of [
38
- 'anthropic/claude-opus-5',
39
- 'openai/gpt-5.4',
40
- 'google/gemini-3.5-flash-lite',
41
- 'minimax/minimax-m3',
42
- 'z-ai/glm-5.2',
43
- ]) {
44
- expect(remapModelForDirectDeepSeek(model)).toBe(
45
- DEFAULT_DIRECT_DEEPSEEK_MODEL,
46
- )
47
- }
48
- })
49
-
50
- test('honors the DEEPSEEK_MODEL override', () => {
51
- const previous = process.env.DEEPSEEK_MODEL
52
- process.env.DEEPSEEK_MODEL = 'deepseek-chat'
53
- try {
54
- expect(remapModelForDirectDeepSeek('anthropic/claude-opus-5')).toBe(
55
- 'deepseek-chat',
56
- )
57
- expect(remapModelForDirectDeepSeek('deepseek/deepseek-v4-pro')).toBe(
58
- 'deepseek-chat',
59
- )
60
- } finally {
61
- if (previous === undefined) {
62
- delete process.env.DEEPSEEK_MODEL
63
- } else {
64
- process.env.DEEPSEEK_MODEL = previous
65
- }
66
- }
67
- })
68
- })