@vellumai/assistant 0.4.16 → 0.4.18

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 (536) hide show
  1. package/Dockerfile +6 -6
  2. package/README.md +1 -2
  3. package/eslint.config.mjs +2 -2
  4. package/package.json +1 -1
  5. package/src/__tests__/access-request-decision.test.ts +128 -120
  6. package/src/__tests__/account-registry.test.ts +121 -110
  7. package/src/__tests__/active-skill-tools.test.ts +200 -172
  8. package/src/__tests__/actor-token-service.test.ts +341 -274
  9. package/src/__tests__/agent-loop-thinking.test.ts +28 -19
  10. package/src/__tests__/agent-loop.test.ts +798 -378
  11. package/src/__tests__/anthropic-provider.test.ts +405 -247
  12. package/src/__tests__/app-builder-tool-scripts.test.ts +97 -97
  13. package/src/__tests__/app-bundler.test.ts +112 -79
  14. package/src/__tests__/app-executors.test.ts +205 -178
  15. package/src/__tests__/app-git-history.test.ts +90 -73
  16. package/src/__tests__/app-git-service.test.ts +67 -53
  17. package/src/__tests__/app-open-proxy.test.ts +29 -25
  18. package/src/__tests__/approval-conversation-turn.test.ts +100 -81
  19. package/src/__tests__/approval-hardcoded-copy-guard.test.ts +45 -17
  20. package/src/__tests__/approval-message-composer.test.ts +119 -119
  21. package/src/__tests__/approval-primitive.test.ts +264 -233
  22. package/src/__tests__/approval-routes-http.test.ts +4 -3
  23. package/src/__tests__/asset-materialize-tool.test.ts +250 -178
  24. package/src/__tests__/asset-search-tool.test.ts +251 -191
  25. package/src/__tests__/assistant-attachment-directive.test.ts +187 -142
  26. package/src/__tests__/assistant-attachments.test.ts +254 -186
  27. package/src/__tests__/assistant-event-hub.test.ts +105 -63
  28. package/src/__tests__/assistant-event.test.ts +66 -58
  29. package/src/__tests__/assistant-events-sse-hardening.test.ts +113 -73
  30. package/src/__tests__/assistant-feature-flag-guard.test.ts +78 -52
  31. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +48 -45
  32. package/src/__tests__/assistant-feature-flags-integration.test.ts +118 -77
  33. package/src/__tests__/assistant-id-boundary-guard.test.ts +158 -104
  34. package/src/__tests__/attachments-store.test.ts +240 -183
  35. package/src/__tests__/attachments.test.ts +70 -62
  36. package/src/__tests__/audit-log-rotation.test.ts +50 -35
  37. package/src/__tests__/browser-fill-credential.test.ts +169 -101
  38. package/src/__tests__/browser-manager.test.ts +97 -75
  39. package/src/__tests__/browser-runtime-check.test.ts +16 -15
  40. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +12 -10
  41. package/src/__tests__/browser-skill-endstate.test.ts +97 -72
  42. package/src/__tests__/bundle-scanner.test.ts +47 -22
  43. package/src/__tests__/bundled-asset.test.ts +74 -47
  44. package/src/__tests__/call-constants.test.ts +19 -19
  45. package/src/__tests__/call-controller.test.ts +1073 -751
  46. package/src/__tests__/call-conversation-messages.test.ts +90 -65
  47. package/src/__tests__/call-domain.test.ts +149 -121
  48. package/src/__tests__/call-pointer-message-composer.test.ts +113 -83
  49. package/src/__tests__/call-pointer-messages.test.ts +213 -154
  50. package/src/__tests__/call-pointer-no-hardcoded-copy.guard.test.ts +9 -10
  51. package/src/__tests__/call-recovery.test.ts +232 -212
  52. package/src/__tests__/call-routes-http.test.ts +328 -279
  53. package/src/__tests__/call-start-guardian-guard.test.ts +32 -30
  54. package/src/__tests__/call-state-machine.test.ts +62 -51
  55. package/src/__tests__/call-state.test.ts +89 -75
  56. package/src/__tests__/call-store.test.ts +387 -316
  57. package/src/__tests__/callback-handoff-copy.test.ts +84 -82
  58. package/src/__tests__/canonical-guardian-store.test.ts +331 -280
  59. package/src/__tests__/channel-approval-routes.test.ts +1643 -1126
  60. package/src/__tests__/channel-approval.test.ts +139 -137
  61. package/src/__tests__/channel-approvals.test.ts +226 -182
  62. package/src/__tests__/channel-delivery-store.test.ts +232 -194
  63. package/src/__tests__/channel-guardian.test.ts +6 -3
  64. package/src/__tests__/channel-invite-transport.test.ts +107 -92
  65. package/src/__tests__/channel-policy.test.ts +42 -38
  66. package/src/__tests__/channel-readiness-service.test.ts +119 -102
  67. package/src/__tests__/channel-reply-delivery.test.ts +147 -118
  68. package/src/__tests__/channel-retry-sweep.test.ts +153 -110
  69. package/src/__tests__/checker.test.ts +3309 -1850
  70. package/src/__tests__/clarification-resolver.test.ts +91 -79
  71. package/src/__tests__/classifier.test.ts +64 -54
  72. package/src/__tests__/claude-code-skill-regression.test.ts +42 -37
  73. package/src/__tests__/claude-code-tool-profiles.test.ts +31 -29
  74. package/src/__tests__/clawhub.test.ts +92 -82
  75. package/src/__tests__/cli.test.ts +30 -30
  76. package/src/__tests__/clipboard.test.ts +53 -46
  77. package/src/__tests__/commit-guarantee.test.ts +59 -52
  78. package/src/__tests__/commit-message-enrichment-service.test.ts +203 -75
  79. package/src/__tests__/compaction.benchmark.test.ts +33 -31
  80. package/src/__tests__/computer-use-session-compaction.test.ts +60 -50
  81. package/src/__tests__/computer-use-session-lifecycle.test.ts +145 -117
  82. package/src/__tests__/computer-use-session-working-dir.test.ts +62 -48
  83. package/src/__tests__/computer-use-skill-baseline.test.ts +22 -19
  84. package/src/__tests__/computer-use-skill-endstate.test.ts +45 -31
  85. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +121 -88
  86. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +65 -42
  87. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +33 -18
  88. package/src/__tests__/computer-use-tools.test.ts +121 -98
  89. package/src/__tests__/config-schema.test.ts +443 -347
  90. package/src/__tests__/config-watcher.test.ts +96 -81
  91. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +148 -133
  92. package/src/__tests__/conflict-intent-tokenization.test.ts +96 -78
  93. package/src/__tests__/conflict-policy.test.ts +151 -80
  94. package/src/__tests__/conflict-store.test.ts +203 -157
  95. package/src/__tests__/connection-policy.test.ts +89 -59
  96. package/src/__tests__/contacts-tools.test.ts +247 -178
  97. package/src/__tests__/context-memory-e2e.test.ts +306 -214
  98. package/src/__tests__/context-token-estimator.test.ts +114 -74
  99. package/src/__tests__/context-window-manager.test.ts +269 -167
  100. package/src/__tests__/contradiction-checker.test.ts +161 -135
  101. package/src/__tests__/conversation-attention-store.test.ts +350 -290
  102. package/src/__tests__/conversation-attention-telegram.test.ts +156 -114
  103. package/src/__tests__/conversation-pairing.test.ts +220 -113
  104. package/src/__tests__/conversation-routes-guardian-reply.test.ts +164 -104
  105. package/src/__tests__/conversation-routes.test.ts +71 -41
  106. package/src/__tests__/conversation-store.test.ts +390 -235
  107. package/src/__tests__/credential-broker-browser-fill.test.ts +325 -250
  108. package/src/__tests__/credential-broker-server-use.test.ts +283 -243
  109. package/src/__tests__/credential-broker.test.ts +128 -74
  110. package/src/__tests__/credential-host-pattern-match.test.ts +64 -44
  111. package/src/__tests__/credential-metadata-store.test.ts +360 -311
  112. package/src/__tests__/credential-policy-validate.test.ts +81 -65
  113. package/src/__tests__/credential-resolve.test.ts +212 -145
  114. package/src/__tests__/credential-security-e2e.test.ts +144 -103
  115. package/src/__tests__/credential-security-invariants.test.ts +253 -208
  116. package/src/__tests__/credential-selection.test.ts +254 -146
  117. package/src/__tests__/credential-vault-unit.test.ts +531 -341
  118. package/src/__tests__/credential-vault.test.ts +761 -484
  119. package/src/__tests__/daemon-assistant-events.test.ts +91 -66
  120. package/src/__tests__/daemon-lifecycle.test.ts +258 -190
  121. package/src/__tests__/daemon-server-session-init.test.ts +257 -191
  122. package/src/__tests__/date-context.test.ts +314 -249
  123. package/src/__tests__/db-migration-rollback.test.ts +259 -130
  124. package/src/__tests__/db-schedule-syntax-migration.test.ts +78 -41
  125. package/src/__tests__/delete-managed-skill-tool.test.ts +77 -53
  126. package/src/__tests__/deterministic-verification-control-plane.test.ts +183 -135
  127. package/src/__tests__/dictation-mode-detection.test.ts +77 -55
  128. package/src/__tests__/dictation-profile-store.test.ts +70 -56
  129. package/src/__tests__/dictation-text-processing.test.ts +53 -35
  130. package/src/__tests__/diff.test.ts +102 -98
  131. package/src/__tests__/domain-normalize.test.ts +54 -54
  132. package/src/__tests__/domain-policy.test.ts +71 -55
  133. package/src/__tests__/dynamic-page-surface.test.ts +31 -33
  134. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +69 -69
  135. package/src/__tests__/edit-engine.test.ts +56 -56
  136. package/src/__tests__/elevenlabs-client.test.ts +117 -91
  137. package/src/__tests__/elevenlabs-config.test.ts +32 -31
  138. package/src/__tests__/email-classifier.test.ts +15 -12
  139. package/src/__tests__/email-cli.test.ts +121 -108
  140. package/src/__tests__/emit-signal-routing-intent.test.ts +76 -69
  141. package/src/__tests__/encrypted-store.test.ts +180 -154
  142. package/src/__tests__/entity-extractor.test.ts +108 -87
  143. package/src/__tests__/entity-search.test.ts +664 -258
  144. package/src/__tests__/ephemeral-permissions.test.ts +224 -188
  145. package/src/__tests__/event-bus.test.ts +81 -77
  146. package/src/__tests__/extract-email.test.ts +51 -0
  147. package/src/__tests__/file-edit-tool.test.ts +62 -44
  148. package/src/__tests__/file-ops-service.test.ts +131 -114
  149. package/src/__tests__/file-read-tool.test.ts +48 -31
  150. package/src/__tests__/file-write-tool.test.ts +43 -37
  151. package/src/__tests__/filesystem-tools.test.ts +238 -209
  152. package/src/__tests__/followup-tools.test.ts +237 -162
  153. package/src/__tests__/forbidden-legacy-symbols.test.ts +19 -20
  154. package/src/__tests__/frontmatter.test.ts +96 -81
  155. package/src/__tests__/fuzzy-match-property.test.ts +75 -81
  156. package/src/__tests__/fuzzy-match.test.ts +71 -65
  157. package/src/__tests__/gateway-client-managed-outbound.test.ts +76 -57
  158. package/src/__tests__/gateway-only-enforcement.test.ts +467 -369
  159. package/src/__tests__/gateway-only-guard.test.ts +54 -56
  160. package/src/__tests__/gemini-image-service.test.ts +113 -100
  161. package/src/__tests__/gemini-provider.test.ts +297 -220
  162. package/src/__tests__/get-weather.test.ts +188 -114
  163. package/src/__tests__/gmail-integration.test.ts +47 -46
  164. package/src/__tests__/guardian-action-conversation-turn.test.ts +226 -171
  165. package/src/__tests__/guardian-action-copy-generator.test.ts +111 -93
  166. package/src/__tests__/guardian-action-followup-executor.test.ts +215 -151
  167. package/src/__tests__/guardian-action-followup-store.test.ts +199 -167
  168. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +297 -250
  169. package/src/__tests__/guardian-action-late-reply.test.ts +462 -316
  170. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +23 -18
  171. package/src/__tests__/guardian-action-store.test.ts +158 -109
  172. package/src/__tests__/guardian-action-sweep.test.ts +114 -100
  173. package/src/__tests__/guardian-actions-endpoint.test.ts +440 -256
  174. package/src/__tests__/guardian-control-plane-policy.test.ts +497 -331
  175. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +217 -215
  176. package/src/__tests__/guardian-dispatch.test.ts +316 -256
  177. package/src/__tests__/guardian-grant-minting.test.ts +247 -178
  178. package/src/__tests__/guardian-outbound-http.test.ts +337 -209
  179. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +99 -96
  180. package/src/__tests__/guardian-question-copy.test.ts +17 -17
  181. package/src/__tests__/guardian-question-mode.test.ts +134 -100
  182. package/src/__tests__/guardian-routing-invariants.test.ts +679 -613
  183. package/src/__tests__/guardian-routing-state.test.ts +256 -209
  184. package/src/__tests__/guardian-verification-intent-routing.test.ts +94 -88
  185. package/src/__tests__/guardian-verification-voice-binding.test.ts +47 -41
  186. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +0 -1
  187. package/src/__tests__/handle-user-message-secret-resume.test.ts +43 -21
  188. package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +92 -76
  189. package/src/__tests__/handlers-cu-observation-blob.test.ts +103 -70
  190. package/src/__tests__/handlers-ipc-blob-probe.test.ts +77 -51
  191. package/src/__tests__/handlers-slack-config.test.ts +63 -54
  192. package/src/__tests__/handlers-task-submit-slash.test.ts +18 -18
  193. package/src/__tests__/handlers-telegram-config.test.ts +662 -329
  194. package/src/__tests__/handlers-twitter-config.test.ts +525 -298
  195. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +270 -195
  196. package/src/__tests__/headless-browser-interactions.test.ts +444 -280
  197. package/src/__tests__/headless-browser-navigate.test.ts +116 -79
  198. package/src/__tests__/headless-browser-read-tools.test.ts +123 -86
  199. package/src/__tests__/headless-browser-snapshot.test.ts +71 -56
  200. package/src/__tests__/heartbeat-service.test.ts +76 -58
  201. package/src/__tests__/history-repair-observability.test.ts +14 -14
  202. package/src/__tests__/history-repair.test.ts +171 -167
  203. package/src/__tests__/home-base-bootstrap.test.ts +30 -27
  204. package/src/__tests__/hooks-blocking.test.ts +86 -37
  205. package/src/__tests__/hooks-cli.test.ts +104 -68
  206. package/src/__tests__/hooks-config.test.ts +81 -43
  207. package/src/__tests__/hooks-discovery.test.ts +106 -96
  208. package/src/__tests__/hooks-integration.test.ts +78 -72
  209. package/src/__tests__/hooks-manager.test.ts +99 -61
  210. package/src/__tests__/hooks-runner.test.ts +94 -71
  211. package/src/__tests__/hooks-settings.test.ts +69 -64
  212. package/src/__tests__/hooks-templates.test.ts +85 -54
  213. package/src/__tests__/hooks-ts-runner.test.ts +82 -45
  214. package/src/__tests__/hooks-watch.test.ts +32 -22
  215. package/src/__tests__/host-file-edit-tool.test.ts +190 -148
  216. package/src/__tests__/host-file-read-tool.test.ts +86 -63
  217. package/src/__tests__/host-file-write-tool.test.ts +98 -64
  218. package/src/__tests__/host-shell-tool.test.ts +342 -233
  219. package/src/__tests__/inbound-invite-redemption.test.ts +194 -152
  220. package/src/__tests__/ingress-member-store.test.ts +163 -159
  221. package/src/__tests__/ingress-reconcile.test.ts +183 -142
  222. package/src/__tests__/ingress-routes-http.test.ts +441 -356
  223. package/src/__tests__/ingress-url-consistency.test.ts +125 -64
  224. package/src/__tests__/integration-status.test.ts +93 -73
  225. package/src/__tests__/intent-routing.test.ts +148 -118
  226. package/src/__tests__/invite-redemption-service.test.ts +163 -121
  227. package/src/__tests__/ipc-blob-store.test.ts +104 -91
  228. package/src/__tests__/ipc-contract-inventory.test.ts +27 -15
  229. package/src/__tests__/ipc-contract.test.ts +24 -23
  230. package/src/__tests__/ipc-protocol.test.ts +52 -46
  231. package/src/__tests__/ipc-roundtrip.benchmark.test.ts +61 -50
  232. package/src/__tests__/ipc-snapshot.test.ts +1135 -1056
  233. package/src/__tests__/ipc-validate.test.ts +240 -179
  234. package/src/__tests__/key-migration.test.ts +123 -90
  235. package/src/__tests__/keychain.test.ts +150 -123
  236. package/src/__tests__/lifecycle-docs-guard.test.ts +65 -64
  237. package/src/__tests__/llm-usage-store.test.ts +112 -87
  238. package/src/__tests__/managed-skill-lifecycle.test.ts +147 -108
  239. package/src/__tests__/managed-store.test.ts +411 -360
  240. package/src/__tests__/mcp-cli.test.ts +189 -123
  241. package/src/__tests__/mcp-health-check.test.ts +26 -21
  242. package/src/__tests__/media-generate-image.test.ts +122 -99
  243. package/src/__tests__/media-reuse-story.e2e.test.ts +282 -214
  244. package/src/__tests__/media-visibility-policy.test.ts +86 -38
  245. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +146 -100
  246. package/src/__tests__/memory-lifecycle-e2e.test.ts +385 -297
  247. package/src/__tests__/memory-query-builder.test.ts +32 -33
  248. package/src/__tests__/memory-recall-quality.test.ts +761 -407
  249. package/src/__tests__/memory-regressions.experimental.test.ts +443 -380
  250. package/src/__tests__/memory-regressions.test.ts +3725 -2642
  251. package/src/__tests__/memory-retrieval-budget.test.ts +7 -8
  252. package/src/__tests__/memory-retrieval.benchmark.test.ts +144 -109
  253. package/src/__tests__/memory-upsert-concurrency.test.ts +292 -201
  254. package/src/__tests__/messaging-send-tool.test.ts +36 -29
  255. package/src/__tests__/migration-cli-flows.test.ts +69 -53
  256. package/src/__tests__/migration-ordering.test.ts +103 -86
  257. package/src/__tests__/mime-builder.test.ts +55 -32
  258. package/src/__tests__/mock-signup-server.test.ts +384 -246
  259. package/src/__tests__/model-intents.test.ts +61 -37
  260. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +9 -12
  261. package/src/__tests__/no-is-trusted-guard.test.ts +24 -21
  262. package/src/__tests__/non-member-access-request.test.ts +294 -249
  263. package/src/__tests__/notification-broadcaster.test.ts +99 -81
  264. package/src/__tests__/notification-decision-fallback.test.ts +223 -178
  265. package/src/__tests__/notification-decision-strategy.test.ts +375 -337
  266. package/src/__tests__/notification-deep-link.test.ts +67 -61
  267. package/src/__tests__/notification-guardian-path.test.ts +248 -206
  268. package/src/__tests__/notification-routing-intent.test.ts +166 -93
  269. package/src/__tests__/notification-telegram-adapter.test.ts +60 -46
  270. package/src/__tests__/notification-thread-candidate-validation.test.ts +78 -75
  271. package/src/__tests__/notification-thread-candidates.test.ts +64 -61
  272. package/src/__tests__/oauth-callback-registry.test.ts +40 -30
  273. package/src/__tests__/oauth-connect-handler.test.ts +109 -89
  274. package/src/__tests__/oauth-scope-policy.test.ts +63 -55
  275. package/src/__tests__/oauth2-gateway-transport.test.ts +252 -174
  276. package/src/__tests__/onboarding-starter-tasks.test.ts +93 -89
  277. package/src/__tests__/onboarding-template-contract.test.ts +93 -94
  278. package/src/__tests__/openai-provider.test.ts +366 -274
  279. package/src/__tests__/pairing-concurrent.test.ts +18 -12
  280. package/src/__tests__/pairing-routes.test.ts +45 -41
  281. package/src/__tests__/parallel-tool.benchmark.test.ts +108 -58
  282. package/src/__tests__/parser.test.ts +316 -226
  283. package/src/__tests__/path-classifier.test.ts +24 -25
  284. package/src/__tests__/path-policy.test.ts +187 -147
  285. package/src/__tests__/phone.test.ts +36 -36
  286. package/src/__tests__/platform-move-helper.test.ts +48 -40
  287. package/src/__tests__/platform-socket-path.test.ts +23 -24
  288. package/src/__tests__/platform-workspace-migration.test.ts +464 -414
  289. package/src/__tests__/platform.test.ts +61 -53
  290. package/src/__tests__/playbook-execution.test.ts +397 -265
  291. package/src/__tests__/playbook-tools.test.ts +267 -196
  292. package/src/__tests__/prebuilt-home-base-seed.test.ts +30 -27
  293. package/src/__tests__/pricing.test.ts +316 -136
  294. package/src/__tests__/profile-compiler.test.ts +206 -188
  295. package/src/__tests__/provider-commit-message-generator.test.ts +114 -106
  296. package/src/__tests__/provider-error-scenarios.test.ts +212 -158
  297. package/src/__tests__/provider-fail-open-selection.test.ts +51 -44
  298. package/src/__tests__/provider-registry-ollama.test.ts +13 -9
  299. package/src/__tests__/provider-streaming.benchmark.test.ts +232 -183
  300. package/src/__tests__/proxy-approval-callback.test.ts +180 -119
  301. package/src/__tests__/public-ingress-urls.test.ts +112 -94
  302. package/src/__tests__/qdrant-manager.test.ts +147 -98
  303. package/src/__tests__/ratelimit.test.ts +152 -82
  304. package/src/__tests__/recording-handler.test.ts +273 -151
  305. package/src/__tests__/recording-intent-fallback.test.ts +94 -75
  306. package/src/__tests__/recording-intent-handler.test.ts +422 -292
  307. package/src/__tests__/recording-intent.test.ts +578 -379
  308. package/src/__tests__/recording-state-machine.test.ts +530 -316
  309. package/src/__tests__/recurrence-engine-rruleset.test.ts +150 -92
  310. package/src/__tests__/recurrence-engine.test.ts +81 -41
  311. package/src/__tests__/recurrence-types.test.ts +63 -44
  312. package/src/__tests__/relay-server.test.ts +2131 -1602
  313. package/src/__tests__/reminder-store.test.ts +158 -80
  314. package/src/__tests__/reminder.test.ts +113 -109
  315. package/src/__tests__/remote-skill-policy.test.ts +96 -72
  316. package/src/__tests__/request-file-tool.test.ts +74 -67
  317. package/src/__tests__/response-tier.test.ts +131 -74
  318. package/src/__tests__/runtime-attachment-metadata.test.ts +107 -70
  319. package/src/__tests__/runtime-events-sse-parity.test.ts +167 -145
  320. package/src/__tests__/runtime-events-sse.test.ts +67 -51
  321. package/src/__tests__/sandbox-diagnostics.test.ts +66 -56
  322. package/src/__tests__/sandbox-host-parity.test.ts +377 -301
  323. package/src/__tests__/scaffold-managed-skill-tool.test.ts +213 -161
  324. package/src/__tests__/schedule-store.test.ts +268 -205
  325. package/src/__tests__/schedule-tools.test.ts +702 -524
  326. package/src/__tests__/scheduler-recurrence.test.ts +240 -130
  327. package/src/__tests__/scoped-approval-grants.test.ts +258 -168
  328. package/src/__tests__/scoped-grant-security-matrix.test.ts +160 -146
  329. package/src/__tests__/script-proxy-certs.test.ts +38 -35
  330. package/src/__tests__/script-proxy-connect-tunnel.test.ts +71 -46
  331. package/src/__tests__/script-proxy-decision-trace.test.ts +161 -84
  332. package/src/__tests__/script-proxy-http-forwarder.test.ts +146 -129
  333. package/src/__tests__/script-proxy-injection-runtime.test.ts +139 -113
  334. package/src/__tests__/script-proxy-mitm-handler.test.ts +226 -142
  335. package/src/__tests__/script-proxy-policy-runtime.test.ts +126 -86
  336. package/src/__tests__/script-proxy-policy.test.ts +308 -153
  337. package/src/__tests__/script-proxy-rewrite-specificity.test.ts +74 -62
  338. package/src/__tests__/script-proxy-router.test.ts +111 -77
  339. package/src/__tests__/script-proxy-session-manager.test.ts +156 -113
  340. package/src/__tests__/script-proxy-session-runtime.test.ts +28 -24
  341. package/src/__tests__/secret-allowlist.test.ts +105 -90
  342. package/src/__tests__/secret-ingress-handler.test.ts +41 -30
  343. package/src/__tests__/secret-onetime-send.test.ts +67 -50
  344. package/src/__tests__/secret-prompt-log-hygiene.test.ts +35 -31
  345. package/src/__tests__/secret-response-routing.test.ts +50 -41
  346. package/src/__tests__/secret-scanner-executor.test.ts +152 -111
  347. package/src/__tests__/secret-scanner.test.ts +495 -413
  348. package/src/__tests__/secure-keys.test.ts +132 -121
  349. package/src/__tests__/send-endpoint-busy.test.ts +313 -232
  350. package/src/__tests__/send-notification-tool.test.ts +43 -42
  351. package/src/__tests__/sensitive-output-placeholders.test.ts +72 -64
  352. package/src/__tests__/sequence-store.test.ts +335 -167
  353. package/src/__tests__/server-history-render.test.ts +341 -202
  354. package/src/__tests__/session-abort-tool-results.test.ts +133 -70
  355. package/src/__tests__/session-approval-overrides.test.ts +93 -91
  356. package/src/__tests__/session-confirmation-signals.test.ts +252 -160
  357. package/src/__tests__/session-conflict-gate.test.ts +775 -585
  358. package/src/__tests__/session-error.test.ts +222 -191
  359. package/src/__tests__/session-evictor.test.ts +79 -62
  360. package/src/__tests__/session-init.benchmark.test.ts +170 -108
  361. package/src/__tests__/session-load-history-repair.test.ts +273 -139
  362. package/src/__tests__/session-messaging-secret-redirect.test.ts +130 -90
  363. package/src/__tests__/session-pre-run-repair.test.ts +106 -59
  364. package/src/__tests__/session-profile-injection.test.ts +198 -130
  365. package/src/__tests__/session-provider-retry-repair.test.ts +223 -141
  366. package/src/__tests__/session-queue.test.ts +624 -321
  367. package/src/__tests__/session-runtime-assembly.test.ts +425 -329
  368. package/src/__tests__/session-runtime-workspace.test.ts +69 -61
  369. package/src/__tests__/session-skill-tools.test.ts +973 -678
  370. package/src/__tests__/session-slash-known.test.ts +185 -133
  371. package/src/__tests__/session-slash-queue.test.ts +147 -81
  372. package/src/__tests__/session-slash-unknown.test.ts +135 -90
  373. package/src/__tests__/session-surfaces-task-progress.test.ts +122 -87
  374. package/src/__tests__/session-tool-setup-app-refresh.test.ts +338 -177
  375. package/src/__tests__/session-tool-setup-memory-scope.test.ts +63 -40
  376. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +60 -37
  377. package/src/__tests__/session-tool-setup-tools-disabled.test.ts +28 -26
  378. package/src/__tests__/session-undo.test.ts +43 -30
  379. package/src/__tests__/session-workspace-cache-state.test.ts +108 -67
  380. package/src/__tests__/session-workspace-injection.test.ts +245 -117
  381. package/src/__tests__/session-workspace-tool-tracking.test.ts +260 -93
  382. package/src/__tests__/shared-filesystem-errors.test.ts +47 -47
  383. package/src/__tests__/shell-credential-ref.test.ts +126 -90
  384. package/src/__tests__/shell-identity.test.ts +134 -111
  385. package/src/__tests__/shell-parser-fuzz.test.ts +263 -179
  386. package/src/__tests__/shell-parser-property.test.ts +435 -288
  387. package/src/__tests__/shell-tool-proxy-mode.test.ts +142 -70
  388. package/src/__tests__/size-guard.test.ts +42 -44
  389. package/src/__tests__/skill-feature-flags-integration.test.ts +79 -52
  390. package/src/__tests__/skill-feature-flags.test.ts +75 -47
  391. package/src/__tests__/skill-include-graph.test.ts +143 -148
  392. package/src/__tests__/skill-load-feature-flag.test.ts +94 -59
  393. package/src/__tests__/skill-load-tool.test.ts +371 -199
  394. package/src/__tests__/skill-projection-feature-flag.test.ts +131 -88
  395. package/src/__tests__/skill-projection.benchmark.test.ts +93 -65
  396. package/src/__tests__/skill-script-runner-host.test.ts +460 -250
  397. package/src/__tests__/skill-script-runner-sandbox.test.ts +168 -108
  398. package/src/__tests__/skill-script-runner.test.ts +115 -74
  399. package/src/__tests__/skill-tool-factory.test.ts +140 -96
  400. package/src/__tests__/skill-tool-manifest.test.ts +306 -210
  401. package/src/__tests__/skill-version-hash.test.ts +70 -56
  402. package/src/__tests__/skills.test.ts +0 -1
  403. package/src/__tests__/slack-channel-config.test.ts +127 -84
  404. package/src/__tests__/slack-skill.test.ts +60 -47
  405. package/src/__tests__/slash-commands-catalog.test.ts +37 -31
  406. package/src/__tests__/slash-commands-parser.test.ts +71 -64
  407. package/src/__tests__/slash-commands-resolver.test.ts +143 -107
  408. package/src/__tests__/slash-commands-rewrite.test.ts +22 -22
  409. package/src/__tests__/sms-messaging-provider.test.ts +74 -47
  410. package/src/__tests__/speaker-identification.test.ts +28 -25
  411. package/src/__tests__/starter-bundle.test.ts +27 -23
  412. package/src/__tests__/starter-task-flow.test.ts +67 -52
  413. package/src/__tests__/subagent-manager-notify.test.ts +154 -108
  414. package/src/__tests__/subagent-tools.test.ts +311 -270
  415. package/src/__tests__/subagent-types.test.ts +40 -40
  416. package/src/__tests__/surface-mutex-cleanup.test.ts +42 -30
  417. package/src/__tests__/swarm-dag-pathological.test.ts +122 -111
  418. package/src/__tests__/swarm-orchestrator.test.ts +135 -101
  419. package/src/__tests__/swarm-plan-validator.test.ts +125 -73
  420. package/src/__tests__/swarm-recursion.test.ts +58 -46
  421. package/src/__tests__/swarm-router-planner.test.ts +99 -74
  422. package/src/__tests__/swarm-session-integration.test.ts +148 -91
  423. package/src/__tests__/swarm-tool.test.ts +65 -45
  424. package/src/__tests__/swarm-worker-backend.test.ts +59 -45
  425. package/src/__tests__/swarm-worker-runner.test.ts +133 -118
  426. package/src/__tests__/system-prompt.test.ts +290 -256
  427. package/src/__tests__/task-compiler.test.ts +176 -120
  428. package/src/__tests__/task-management-tools.test.ts +561 -456
  429. package/src/__tests__/task-memory-cleanup.test.ts +627 -362
  430. package/src/__tests__/task-runner.test.ts +117 -94
  431. package/src/__tests__/task-scheduler.test.ts +113 -84
  432. package/src/__tests__/task-tools.test.ts +349 -264
  433. package/src/__tests__/terminal-sandbox.test.ts +138 -108
  434. package/src/__tests__/terminal-tools.test.ts +350 -305
  435. package/src/__tests__/thread-seed-composer.test.ts +307 -180
  436. package/src/__tests__/tool-approval-handler.test.ts +238 -137
  437. package/src/__tests__/tool-audit-listener.test.ts +69 -69
  438. package/src/__tests__/tool-domain-event-publisher.test.ts +142 -132
  439. package/src/__tests__/tool-execution-abort-cleanup.test.ts +153 -146
  440. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +136 -105
  441. package/src/__tests__/tool-executor-lifecycle-events.test.ts +355 -239
  442. package/src/__tests__/tool-executor-redaction.test.ts +112 -109
  443. package/src/__tests__/tool-executor-shell-integration.test.ts +130 -79
  444. package/src/__tests__/tool-executor.test.ts +1274 -674
  445. package/src/__tests__/tool-grant-request-escalation.test.ts +401 -283
  446. package/src/__tests__/tool-metrics-listener.test.ts +97 -85
  447. package/src/__tests__/tool-notification-listener.test.ts +42 -25
  448. package/src/__tests__/tool-permission-simulate-handler.test.ts +137 -113
  449. package/src/__tests__/tool-policy.test.ts +44 -25
  450. package/src/__tests__/tool-profiling-listener.test.ts +99 -93
  451. package/src/__tests__/tool-result-truncation.test.ts +5 -4
  452. package/src/__tests__/tool-trace-listener.test.ts +131 -111
  453. package/src/__tests__/top-level-renderer.test.ts +62 -58
  454. package/src/__tests__/top-level-scanner.test.ts +68 -64
  455. package/src/__tests__/trace-emitter.test.ts +56 -56
  456. package/src/__tests__/trust-context-guards.test.ts +65 -65
  457. package/src/__tests__/trust-store.test.ts +1239 -806
  458. package/src/__tests__/trusted-contact-approval-notifier.test.ts +339 -275
  459. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +484 -373
  460. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +264 -241
  461. package/src/__tests__/trusted-contact-multichannel.test.ts +182 -142
  462. package/src/__tests__/trusted-contact-verification.test.ts +251 -231
  463. package/src/__tests__/turn-commit.test.ts +259 -200
  464. package/src/__tests__/twilio-config.test.ts +49 -41
  465. package/src/__tests__/twilio-provider.test.ts +140 -126
  466. package/src/__tests__/twilio-rest.test.ts +22 -18
  467. package/src/__tests__/twilio-routes-elevenlabs.test.ts +188 -162
  468. package/src/__tests__/twilio-routes-twiml.test.ts +55 -55
  469. package/src/__tests__/twilio-routes.test.ts +389 -281
  470. package/src/__tests__/twitter-auth-handler.test.ts +184 -139
  471. package/src/__tests__/twitter-cli-error-shaping.test.ts +88 -73
  472. package/src/__tests__/twitter-cli-routing.test.ts +146 -99
  473. package/src/__tests__/twitter-oauth-client.test.ts +82 -65
  474. package/src/__tests__/update-bulletin-format.test.ts +69 -66
  475. package/src/__tests__/update-bulletin-state.test.ts +66 -60
  476. package/src/__tests__/update-bulletin.test.ts +150 -114
  477. package/src/__tests__/update-template-contract.test.ts +15 -10
  478. package/src/__tests__/url-safety.test.ts +288 -265
  479. package/src/__tests__/user-reference.test.ts +32 -32
  480. package/src/__tests__/view-image-tool.test.ts +118 -96
  481. package/src/__tests__/voice-invite-redemption.test.ts +111 -106
  482. package/src/__tests__/voice-quality.test.ts +117 -102
  483. package/src/__tests__/voice-scoped-grant-consumer.test.ts +204 -146
  484. package/src/__tests__/voice-session-bridge.test.ts +351 -216
  485. package/src/__tests__/weather-skill-regression.test.ts +170 -120
  486. package/src/__tests__/web-fetch.test.ts +664 -526
  487. package/src/__tests__/web-search.test.ts +379 -213
  488. package/src/__tests__/work-item-output.test.ts +90 -53
  489. package/src/__tests__/workspace-git-service.test.ts +437 -356
  490. package/src/__tests__/workspace-heartbeat-service.test.ts +125 -91
  491. package/src/__tests__/workspace-lifecycle.test.ts +98 -64
  492. package/src/__tests__/workspace-policy.test.ts +139 -71
  493. package/src/commands/__tests__/cc-command-registry.test.ts +142 -134
  494. package/src/config/__tests__/feature-flag-registry-guard.test.ts +48 -39
  495. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +44 -4
  496. package/src/config/bundled-skills/doordash/__tests__/doordash-session.test.ts +0 -1
  497. package/src/config/bundled-skills/messaging/SKILL.md +9 -7
  498. package/src/config/bundled-skills/messaging/tools/gmail-outreach-scan.ts +15 -5
  499. package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +16 -5
  500. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +11 -7
  501. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +34 -32
  502. package/src/config/bundled-tool-registry.ts +2 -0
  503. package/src/config/env.ts +38 -29
  504. package/src/daemon/handlers/skills.ts +18 -10
  505. package/src/daemon/ipc-contract/messages.ts +1 -0
  506. package/src/daemon/ipc-contract/surfaces.ts +7 -1
  507. package/src/daemon/session-agent-loop-handlers.ts +5 -0
  508. package/src/daemon/session-agent-loop.ts +1 -1
  509. package/src/daemon/session-process.ts +1 -1
  510. package/src/daemon/session-surfaces.ts +42 -2
  511. package/src/memory/db-connection.ts +16 -10
  512. package/src/messaging/providers/gmail/adapter.ts +10 -3
  513. package/src/messaging/providers/gmail/client.ts +280 -72
  514. package/src/runtime/auth/__tests__/context.test.ts +75 -65
  515. package/src/runtime/auth/__tests__/credential-service.test.ts +137 -114
  516. package/src/runtime/auth/__tests__/guard-tests.test.ts +84 -90
  517. package/src/runtime/auth/__tests__/ipc-auth-context.test.ts +40 -40
  518. package/src/runtime/auth/__tests__/middleware.test.ts +80 -74
  519. package/src/runtime/auth/__tests__/policy.test.ts +9 -9
  520. package/src/runtime/auth/__tests__/route-policy.test.ts +76 -65
  521. package/src/runtime/auth/__tests__/scopes.test.ts +68 -60
  522. package/src/runtime/auth/__tests__/subject.test.ts +54 -54
  523. package/src/runtime/auth/__tests__/token-service.test.ts +115 -108
  524. package/src/runtime/auth/scopes.ts +3 -0
  525. package/src/runtime/auth/token-service.ts +78 -48
  526. package/src/runtime/auth/types.ts +2 -1
  527. package/src/runtime/http-server.ts +2 -1
  528. package/src/security/secure-keys.ts +103 -53
  529. package/src/sequence/reply-matcher.ts +10 -6
  530. package/src/skills/frontmatter.ts +9 -6
  531. package/src/tools/browser/__tests__/auth-cache.test.ts +69 -63
  532. package/src/tools/browser/__tests__/auth-detector.test.ts +218 -157
  533. package/src/tools/browser/__tests__/jit-auth.test.ts +83 -99
  534. package/src/tools/ui-surface/definitions.ts +2 -1
  535. package/src/util/platform.ts +0 -12
  536. package/docs/architecture/http-token-refresh.md +0 -274
@@ -1,36 +1,36 @@
1
- import { mkdtempSync, rmSync } from 'node:fs';
2
- import { tmpdir } from 'node:os';
3
- import { join } from 'node:path';
1
+ import { mkdtempSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
4
5
 
5
- import { afterAll, beforeEach, describe, expect, mock,test } from 'bun:test';
6
+ const testDir = mkdtempSync(join(tmpdir(), "attach-store-test-"));
6
7
 
7
- const testDir = mkdtempSync(join(tmpdir(), 'attach-store-test-'));
8
-
9
- mock.module('../util/platform.js', () => ({
8
+ mock.module("../util/platform.js", () => ({
10
9
  getDataDir: () => testDir,
11
- isMacOS: () => process.platform === 'darwin',
12
- isLinux: () => process.platform === 'linux',
13
- isWindows: () => process.platform === 'win32',
14
- getSocketPath: () => join(testDir, 'test.sock'),
15
- getPidPath: () => join(testDir, 'test.pid'),
16
- getDbPath: () => join(testDir, 'test.db'),
17
- getLogPath: () => join(testDir, 'test.log'),
10
+ isMacOS: () => process.platform === "darwin",
11
+ isLinux: () => process.platform === "linux",
12
+ isWindows: () => process.platform === "win32",
13
+ getSocketPath: () => join(testDir, "test.sock"),
14
+ getPidPath: () => join(testDir, "test.pid"),
15
+ getDbPath: () => join(testDir, "test.db"),
16
+ getLogPath: () => join(testDir, "test.log"),
18
17
  ensureDataDir: () => {},
19
18
  getRootDir: () => testDir,
20
19
  }));
21
20
 
22
- mock.module('../util/logger.js', () => ({
23
- getLogger: () => new Proxy({} as Record<string, unknown>, {
24
- get: () => () => {},
25
- }),
21
+ mock.module("../util/logger.js", () => ({
22
+ getLogger: () =>
23
+ new Proxy({} as Record<string, unknown>, {
24
+ get: () => () => {},
25
+ }),
26
26
  }));
27
27
 
28
- mock.module('../config/loader.js', () => ({
28
+ mock.module("../config/loader.js", () => ({
29
29
  getConfig: () => ({
30
30
  ui: {},
31
-
32
- model: 'test',
33
- provider: 'test',
31
+
32
+ model: "test",
33
+ provider: "test",
34
34
  apiKeys: {},
35
35
  memory: { enabled: false },
36
36
  rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
@@ -49,115 +49,136 @@ import {
49
49
  MAX_UPLOAD_BYTES,
50
50
  uploadAttachment,
51
51
  validateAttachmentUpload,
52
- } from '../memory/attachments-store.js';
53
- import { addMessage,createConversation } from '../memory/conversation-store.js';
54
- import { getDb, initializeDb, resetDb } from '../memory/db.js';
52
+ } from "../memory/attachments-store.js";
53
+ import {
54
+ addMessage,
55
+ createConversation,
56
+ } from "../memory/conversation-store.js";
57
+ import { getDb, initializeDb, resetDb } from "../memory/db.js";
55
58
 
56
59
  initializeDb();
57
60
 
58
61
  afterAll(() => {
59
62
  resetDb();
60
- try { rmSync(testDir, { recursive: true }); } catch { /* best effort */ }
63
+ try {
64
+ rmSync(testDir, { recursive: true });
65
+ } catch {
66
+ /* best effort */
67
+ }
61
68
  });
62
69
 
63
70
  function resetTables() {
64
71
  const db = getDb();
65
- db.run('DELETE FROM message_attachments');
66
- db.run('DELETE FROM attachments');
67
- db.run('DELETE FROM messages');
68
- db.run('DELETE FROM conversations');
72
+ db.run("DELETE FROM message_attachments");
73
+ db.run("DELETE FROM attachments");
74
+ db.run("DELETE FROM messages");
75
+ db.run("DELETE FROM conversations");
69
76
  }
70
77
 
71
78
  // ---------------------------------------------------------------------------
72
79
  // uploadAttachment
73
80
  // ---------------------------------------------------------------------------
74
81
 
75
- describe('uploadAttachment', () => {
82
+ describe("uploadAttachment", () => {
76
83
  beforeEach(resetTables);
77
84
 
78
- test('stores attachment and returns metadata', () => {
79
- const stored = uploadAttachment('chart.png', 'image/png', 'iVBORw0K');
85
+ test("stores attachment and returns metadata", () => {
86
+ const stored = uploadAttachment("chart.png", "image/png", "iVBORw0K");
80
87
 
81
88
  expect(stored.id).toBeDefined();
82
- expect(stored.originalFilename).toBe('chart.png');
83
- expect(stored.mimeType).toBe('image/png');
84
- expect(stored.kind).toBe('image');
89
+ expect(stored.originalFilename).toBe("chart.png");
90
+ expect(stored.mimeType).toBe("image/png");
91
+ expect(stored.kind).toBe("image");
85
92
  expect(stored.sizeBytes).toBeGreaterThan(0);
86
93
  expect(stored.createdAt).toBeGreaterThan(0);
87
94
  });
88
95
 
89
- test('classifies image MIME as image kind', () => {
90
- const stored = uploadAttachment('pic.jpg', 'image/jpeg', 'AAAA');
91
- expect(stored.kind).toBe('image');
96
+ test("classifies image MIME as image kind", () => {
97
+ const stored = uploadAttachment("pic.jpg", "image/jpeg", "AAAA");
98
+ expect(stored.kind).toBe("image");
92
99
  });
93
100
 
94
- test('classifies non-image MIME as document kind', () => {
95
- const stored = uploadAttachment('doc.pdf', 'application/pdf', 'JVBER');
96
- expect(stored.kind).toBe('document');
101
+ test("classifies non-image MIME as document kind", () => {
102
+ const stored = uploadAttachment("doc.pdf", "application/pdf", "JVBER");
103
+ expect(stored.kind).toBe("document");
97
104
  });
98
105
 
99
- test('generates unique IDs for each upload', () => {
100
- const a = uploadAttachment('a.txt', 'text/plain', 'AA==');
101
- const b = uploadAttachment('b.txt', 'text/plain', 'QQ==');
106
+ test("generates unique IDs for each upload", () => {
107
+ const a = uploadAttachment("a.txt", "text/plain", "AA==");
108
+ const b = uploadAttachment("b.txt", "text/plain", "QQ==");
102
109
  expect(a.id).not.toBe(b.id);
103
110
  });
104
111
 
105
- test('computes sizeBytes from base64 correctly', () => {
112
+ test("computes sizeBytes from base64 correctly", () => {
106
113
  // "hello" = "aGVsbG8=" (8 chars, 1 pad → 5 bytes)
107
- const stored = uploadAttachment('hello.txt', 'text/plain', 'aGVsbG8=');
114
+ const stored = uploadAttachment("hello.txt", "text/plain", "aGVsbG8=");
108
115
  expect(stored.sizeBytes).toBe(5);
109
116
  });
110
117
 
111
- test('deduplicates by content hash', () => {
112
- const first = uploadAttachment('photo.png', 'image/png', 'iVBORw0KGgoAAAANSUh');
113
- const second = uploadAttachment('photo.png', 'image/png', 'iVBORw0KGgoAAAANSUh');
118
+ test("deduplicates by content hash", () => {
119
+ const first = uploadAttachment(
120
+ "photo.png",
121
+ "image/png",
122
+ "iVBORw0KGgoAAAANSUh",
123
+ );
124
+ const second = uploadAttachment(
125
+ "photo.png",
126
+ "image/png",
127
+ "iVBORw0KGgoAAAANSUh",
128
+ );
114
129
  expect(second.id).toBe(first.id);
115
130
  });
116
131
 
117
- test('deduplicates even when filenames differ', () => {
118
- const first = uploadAttachment('original.png', 'image/png', 'DUPECONTENT123');
119
- const second = uploadAttachment('renamed.png', 'image/png', 'DUPECONTENT123');
132
+ test("deduplicates even when filenames differ", () => {
133
+ const first = uploadAttachment(
134
+ "original.png",
135
+ "image/png",
136
+ "DUPECONTENT123",
137
+ );
138
+ const second = uploadAttachment(
139
+ "renamed.png",
140
+ "image/png",
141
+ "DUPECONTENT123",
142
+ );
120
143
  expect(second.id).toBe(first.id);
121
144
  });
122
145
 
123
- test('does not deduplicate different content', () => {
124
- const first = uploadAttachment('a.txt', 'text/plain', 'CONTENTA');
125
- const second = uploadAttachment('b.txt', 'text/plain', 'CONTENTB');
146
+ test("does not deduplicate different content", () => {
147
+ const first = uploadAttachment("a.txt", "text/plain", "CONTENTA");
148
+ const second = uploadAttachment("b.txt", "text/plain", "CONTENTB");
126
149
  expect(second.id).not.toBe(first.id);
127
150
  });
128
151
 
129
- test('rejects payloads exceeding MAX_UPLOAD_BYTES', () => {
152
+ test("rejects payloads exceeding MAX_UPLOAD_BYTES", () => {
130
153
  // Build a base64 string that decodes to just over the limit.
131
154
  // 4 base64 chars → 3 bytes, so we need ceil((MAX_UPLOAD_BYTES+1)/3)*4 chars.
132
155
  const oversizedLength = Math.ceil((MAX_UPLOAD_BYTES + 1) / 3) * 4;
133
- const oversizedData = 'A'.repeat(oversizedLength);
156
+ const oversizedData = "A".repeat(oversizedLength);
134
157
 
135
158
  expect(() =>
136
- uploadAttachment('huge.bin', 'application/octet-stream', oversizedData),
159
+ uploadAttachment("huge.bin", "application/octet-stream", oversizedData),
137
160
  ).toThrow(AttachmentUploadError);
138
161
  });
139
162
 
140
- test('rejects invalid base64 data', () => {
163
+ test("rejects invalid base64 data", () => {
141
164
  expect(() =>
142
- uploadAttachment('bad.txt', 'text/plain', '!!!not-base64!!!'),
165
+ uploadAttachment("bad.txt", "text/plain", "!!!not-base64!!!"),
143
166
  ).toThrow(AttachmentUploadError);
144
167
  });
145
168
 
146
- test('accepts base64 with non-standard padding/length', () => {
169
+ test("accepts base64 with non-standard padding/length", () => {
147
170
  // Lenient on length — only character set is validated
148
- expect(() =>
149
- uploadAttachment('ok.txt', 'text/plain', 'AAA'),
150
- ).not.toThrow();
171
+ expect(() => uploadAttachment("ok.txt", "text/plain", "AAA")).not.toThrow();
151
172
  });
152
173
 
153
- test('accepts payload exactly at MAX_UPLOAD_BYTES', () => {
174
+ test("accepts payload exactly at MAX_UPLOAD_BYTES", () => {
154
175
  // MAX_UPLOAD_BYTES (20 MB) is divisible by 3, so (MAX/3)*4 base64 chars
155
176
  // decodes to exactly MAX bytes with no padding.
156
177
  const exactLength = (MAX_UPLOAD_BYTES / 3) * 4;
157
- const exactData = 'A'.repeat(exactLength);
178
+ const exactData = "A".repeat(exactLength);
158
179
 
159
180
  expect(() =>
160
- uploadAttachment('exact.bin', 'application/octet-stream', exactData),
181
+ uploadAttachment("exact.bin", "application/octet-stream", exactData),
161
182
  ).not.toThrow();
162
183
  });
163
184
  });
@@ -166,24 +187,24 @@ describe('uploadAttachment', () => {
166
187
  // isValidBase64
167
188
  // ---------------------------------------------------------------------------
168
189
 
169
- describe('isValidBase64', () => {
170
- test('accepts valid base64 strings', () => {
171
- expect(isValidBase64('aGVsbG8=')).toBe(true); // "hello"
172
- expect(isValidBase64('dGVzdA==')).toBe(true); // "test"
173
- expect(isValidBase64('AAAA')).toBe(true); // no padding
174
- expect(isValidBase64('')).toBe(true); // empty
190
+ describe("isValidBase64", () => {
191
+ test("accepts valid base64 strings", () => {
192
+ expect(isValidBase64("aGVsbG8=")).toBe(true); // "hello"
193
+ expect(isValidBase64("dGVzdA==")).toBe(true); // "test"
194
+ expect(isValidBase64("AAAA")).toBe(true); // no padding
195
+ expect(isValidBase64("")).toBe(true); // empty
175
196
  });
176
197
 
177
- test('accepts strings with non-standard length (lenient)', () => {
178
- expect(isValidBase64('AAA')).toBe(true); // 3 chars, OK
179
- expect(isValidBase64('AAAAA')).toBe(true); // 5 chars, OK
198
+ test("accepts strings with non-standard length (lenient)", () => {
199
+ expect(isValidBase64("AAA")).toBe(true); // 3 chars, OK
200
+ expect(isValidBase64("AAAAA")).toBe(true); // 5 chars, OK
180
201
  });
181
202
 
182
- test('rejects strings with invalid characters', () => {
183
- expect(isValidBase64('!!!!')).toBe(false);
184
- expect(isValidBase64('abc@')).toBe(false);
185
- expect(isValidBase64('hello world')).toBe(false); // space
186
- expect(isValidBase64('data_here')).toBe(false); // underscore
203
+ test("rejects strings with invalid characters", () => {
204
+ expect(isValidBase64("!!!!")).toBe(false);
205
+ expect(isValidBase64("abc@")).toBe(false);
206
+ expect(isValidBase64("hello world")).toBe(false); // space
207
+ expect(isValidBase64("data_here")).toBe(false); // underscore
187
208
  });
188
209
  });
189
210
 
@@ -191,31 +212,31 @@ describe('isValidBase64', () => {
191
212
  // deleteAttachment
192
213
  // ---------------------------------------------------------------------------
193
214
 
194
- describe('deleteAttachment', () => {
215
+ describe("deleteAttachment", () => {
195
216
  beforeEach(resetTables);
196
217
 
197
- test('deletes existing attachment and returns deleted', () => {
198
- const stored = uploadAttachment('file.txt', 'text/plain', 'dGVzdA==');
218
+ test("deletes existing attachment and returns deleted", () => {
219
+ const stored = uploadAttachment("file.txt", "text/plain", "dGVzdA==");
199
220
  const result = deleteAttachment(stored.id);
200
- expect(result).toBe('deleted');
221
+ expect(result).toBe("deleted");
201
222
 
202
223
  const fetched = getAttachmentById(stored.id);
203
224
  expect(fetched).toBeNull();
204
225
  });
205
226
 
206
- test('returns not_found for nonexistent attachment', () => {
207
- const result = deleteAttachment('nonexistent-id');
208
- expect(result).toBe('not_found');
227
+ test("returns not_found for nonexistent attachment", () => {
228
+ const result = deleteAttachment("nonexistent-id");
229
+ expect(result).toBe("not_found");
209
230
  });
210
231
 
211
- test('returns still_referenced when messages reference the attachment', async () => {
232
+ test("returns still_referenced when messages reference the attachment", async () => {
212
233
  const conv = createConversation();
213
- const msg1 = await addMessage(conv.id, 'user', 'First upload');
214
- const msg2 = await addMessage(conv.id, 'user', 'Duplicate upload');
234
+ const msg1 = await addMessage(conv.id, "user", "First upload");
235
+ const msg2 = await addMessage(conv.id, "user", "Duplicate upload");
215
236
 
216
237
  // Dedup: both uploads return the same attachment row
217
- const first = uploadAttachment('photo.png', 'image/png', 'SHAREDCONTENT1');
218
- const second = uploadAttachment('photo.png', 'image/png', 'SHAREDCONTENT1');
238
+ const first = uploadAttachment("photo.png", "image/png", "SHAREDCONTENT1");
239
+ const second = uploadAttachment("photo.png", "image/png", "SHAREDCONTENT1");
219
240
  expect(second.id).toBe(first.id);
220
241
 
221
242
  linkAttachmentToMessage(msg1.id, first.id, 0);
@@ -223,7 +244,7 @@ describe('deleteAttachment', () => {
223
244
 
224
245
  // Delete should return still_referenced and NOT remove the attachment row
225
246
  const result = deleteAttachment(first.id);
226
- expect(result).toBe('still_referenced');
247
+ expect(result).toBe("still_referenced");
227
248
 
228
249
  // Attachment row still exists because messages reference it
229
250
  const fetched = getAttachmentById(first.id);
@@ -236,11 +257,11 @@ describe('deleteAttachment', () => {
236
257
  expect(linked2).toHaveLength(1);
237
258
  });
238
259
 
239
- test('deletes attachment when no messages reference it', () => {
240
- const stored = uploadAttachment('lonely.txt', 'text/plain', 'UNREFERENCED');
260
+ test("deletes attachment when no messages reference it", () => {
261
+ const stored = uploadAttachment("lonely.txt", "text/plain", "UNREFERENCED");
241
262
  // No linkAttachmentToMessage call — zero references
242
263
  const result = deleteAttachment(stored.id);
243
- expect(result).toBe('deleted');
264
+ expect(result).toBe("deleted");
244
265
 
245
266
  const fetched = getAttachmentById(stored.id);
246
267
  expect(fetched).toBeNull();
@@ -251,27 +272,27 @@ describe('deleteAttachment', () => {
251
272
  // getAttachmentsByIds
252
273
  // ---------------------------------------------------------------------------
253
274
 
254
- describe('getAttachmentsByIds', () => {
275
+ describe("getAttachmentsByIds", () => {
255
276
  beforeEach(resetTables);
256
277
 
257
- test('returns matching attachments with data', () => {
258
- const a = uploadAttachment('a.txt', 'text/plain', 'AAAA');
259
- const b = uploadAttachment('b.txt', 'text/plain', 'BBBB');
278
+ test("returns matching attachments with data", () => {
279
+ const a = uploadAttachment("a.txt", "text/plain", "AAAA");
280
+ const b = uploadAttachment("b.txt", "text/plain", "BBBB");
260
281
 
261
282
  const results = getAttachmentsByIds([a.id, b.id]);
262
283
  expect(results).toHaveLength(2);
263
- expect(results[0].dataBase64).toBe('AAAA');
264
- expect(results[1].dataBase64).toBe('BBBB');
284
+ expect(results[0].dataBase64).toBe("AAAA");
285
+ expect(results[1].dataBase64).toBe("BBBB");
265
286
  });
266
287
 
267
- test('returns empty array for empty IDs list', () => {
288
+ test("returns empty array for empty IDs list", () => {
268
289
  const results = getAttachmentsByIds([]);
269
290
  expect(results).toHaveLength(0);
270
291
  });
271
292
 
272
- test('skips IDs that do not exist', () => {
273
- const a = uploadAttachment('a.txt', 'text/plain', 'AAAA');
274
- const results = getAttachmentsByIds([a.id, 'nonexistent']);
293
+ test("skips IDs that do not exist", () => {
294
+ const a = uploadAttachment("a.txt", "text/plain", "AAAA");
295
+ const results = getAttachmentsByIds([a.id, "nonexistent"]);
275
296
  expect(results).toHaveLength(1);
276
297
  });
277
298
  });
@@ -280,21 +301,21 @@ describe('getAttachmentsByIds', () => {
280
301
  // getAttachmentById
281
302
  // ---------------------------------------------------------------------------
282
303
 
283
- describe('getAttachmentById', () => {
304
+ describe("getAttachmentById", () => {
284
305
  beforeEach(resetTables);
285
306
 
286
- test('returns attachment with data when found', () => {
287
- const stored = uploadAttachment('report.pdf', 'application/pdf', 'JVBER');
307
+ test("returns attachment with data when found", () => {
308
+ const stored = uploadAttachment("report.pdf", "application/pdf", "JVBER");
288
309
  const result = getAttachmentById(stored.id);
289
310
 
290
311
  expect(result).not.toBeNull();
291
312
  expect(result!.id).toBe(stored.id);
292
- expect(result!.originalFilename).toBe('report.pdf');
293
- expect(result!.dataBase64).toBe('JVBER');
313
+ expect(result!.originalFilename).toBe("report.pdf");
314
+ expect(result!.dataBase64).toBe("JVBER");
294
315
  });
295
316
 
296
- test('returns null for nonexistent ID', () => {
297
- const result = getAttachmentById('no-such-id');
317
+ test("returns null for nonexistent ID", () => {
318
+ const result = getAttachmentById("no-such-id");
298
319
  expect(result).toBeNull();
299
320
  });
300
321
  });
@@ -303,28 +324,28 @@ describe('getAttachmentById', () => {
303
324
  // linkAttachmentToMessage + getAttachmentsForMessage
304
325
  // ---------------------------------------------------------------------------
305
326
 
306
- describe('linkAttachmentToMessage + getAttachmentsForMessage', () => {
327
+ describe("linkAttachmentToMessage + getAttachmentsForMessage", () => {
307
328
  beforeEach(resetTables);
308
329
 
309
- test('links attachment and retrieves it by message', async () => {
330
+ test("links attachment and retrieves it by message", async () => {
310
331
  const conv = createConversation();
311
- const msg = await addMessage(conv.id, 'assistant', 'Here is a chart');
312
- const stored = uploadAttachment('chart.png', 'image/png', 'iVBORw0K');
332
+ const msg = await addMessage(conv.id, "assistant", "Here is a chart");
333
+ const stored = uploadAttachment("chart.png", "image/png", "iVBORw0K");
313
334
 
314
335
  linkAttachmentToMessage(msg.id, stored.id, 0);
315
336
 
316
337
  const linked = getAttachmentsForMessage(msg.id);
317
338
  expect(linked).toHaveLength(1);
318
339
  expect(linked[0].id).toBe(stored.id);
319
- expect(linked[0].originalFilename).toBe('chart.png');
320
- expect(linked[0].dataBase64).toBe('iVBORw0K');
340
+ expect(linked[0].originalFilename).toBe("chart.png");
341
+ expect(linked[0].dataBase64).toBe("iVBORw0K");
321
342
  });
322
343
 
323
- test('returns attachments in position order', async () => {
344
+ test("returns attachments in position order", async () => {
324
345
  const conv = createConversation();
325
- const msg = await addMessage(conv.id, 'assistant', 'Multiple files');
326
- const a = uploadAttachment('first.txt', 'text/plain', 'AAAA');
327
- const b = uploadAttachment('second.txt', 'text/plain', 'BBBB');
346
+ const msg = await addMessage(conv.id, "assistant", "Multiple files");
347
+ const a = uploadAttachment("first.txt", "text/plain", "AAAA");
348
+ const b = uploadAttachment("second.txt", "text/plain", "BBBB");
328
349
 
329
350
  // Link in reverse order
330
351
  linkAttachmentToMessage(msg.id, b.id, 1);
@@ -332,13 +353,13 @@ describe('linkAttachmentToMessage + getAttachmentsForMessage', () => {
332
353
 
333
354
  const linked = getAttachmentsForMessage(msg.id);
334
355
  expect(linked).toHaveLength(2);
335
- expect(linked[0].originalFilename).toBe('first.txt');
336
- expect(linked[1].originalFilename).toBe('second.txt');
356
+ expect(linked[0].originalFilename).toBe("first.txt");
357
+ expect(linked[1].originalFilename).toBe("second.txt");
337
358
  });
338
359
 
339
- test('returns empty for message with no attachments', async () => {
360
+ test("returns empty for message with no attachments", async () => {
340
361
  const conv = createConversation();
341
- const msg = await addMessage(conv.id, 'assistant', 'No attachments');
362
+ const msg = await addMessage(conv.id, "assistant", "No attachments");
342
363
 
343
364
  const linked = getAttachmentsForMessage(msg.id);
344
365
  expect(linked).toHaveLength(0);
@@ -349,20 +370,20 @@ describe('linkAttachmentToMessage + getAttachmentsForMessage', () => {
349
370
  // deleteOrphanAttachments
350
371
  // ---------------------------------------------------------------------------
351
372
 
352
- describe('deleteOrphanAttachments', () => {
373
+ describe("deleteOrphanAttachments", () => {
353
374
  beforeEach(resetTables);
354
375
 
355
- test('removes candidate attachments with no message links', () => {
356
- const stored = uploadAttachment('orphan.txt', 'text/plain', 'ZGF0YQ==');
376
+ test("removes candidate attachments with no message links", () => {
377
+ const stored = uploadAttachment("orphan.txt", "text/plain", "ZGF0YQ==");
357
378
 
358
379
  const removed = deleteOrphanAttachments([stored.id]);
359
380
  expect(removed).toBe(1);
360
381
  });
361
382
 
362
- test('preserves attachments that are still linked', async () => {
383
+ test("preserves attachments that are still linked", async () => {
363
384
  const conv = createConversation();
364
- const msg = await addMessage(conv.id, 'assistant', 'With attachment');
365
- const stored = uploadAttachment('linked.txt', 'text/plain', 'ZGF0YQ==');
385
+ const msg = await addMessage(conv.id, "assistant", "With attachment");
386
+ const stored = uploadAttachment("linked.txt", "text/plain", "ZGF0YQ==");
366
387
  linkAttachmentToMessage(msg.id, stored.id, 0);
367
388
 
368
389
  const removed = deleteOrphanAttachments([stored.id]);
@@ -372,11 +393,11 @@ describe('deleteOrphanAttachments', () => {
372
393
  expect(fetched).not.toBeNull();
373
394
  });
374
395
 
375
- test('removes only orphans when mixed candidates provided', async () => {
396
+ test("removes only orphans when mixed candidates provided", async () => {
376
397
  const conv = createConversation();
377
- const msg = await addMessage(conv.id, 'assistant', 'Mixed');
378
- const linked = uploadAttachment('linked.txt', 'text/plain', 'AAAA');
379
- const orphan = uploadAttachment('orphan.txt', 'text/plain', 'BBBB');
398
+ const msg = await addMessage(conv.id, "assistant", "Mixed");
399
+ const linked = uploadAttachment("linked.txt", "text/plain", "AAAA");
400
+ const orphan = uploadAttachment("orphan.txt", "text/plain", "BBBB");
380
401
  linkAttachmentToMessage(msg.id, linked.id, 0);
381
402
 
382
403
  const removed = deleteOrphanAttachments([linked.id, orphan.id]);
@@ -386,14 +407,14 @@ describe('deleteOrphanAttachments', () => {
386
407
  expect(remaining).not.toBeNull();
387
408
  });
388
409
 
389
- test('returns 0 when no candidates provided', () => {
410
+ test("returns 0 when no candidates provided", () => {
390
411
  const removed = deleteOrphanAttachments([]);
391
412
  expect(removed).toBe(0);
392
413
  });
393
414
 
394
- test('does not delete attachments outside the candidate set', () => {
395
- const unrelated = uploadAttachment('unrelated.txt', 'text/plain', 'AAAA');
396
- const candidate = uploadAttachment('candidate.txt', 'text/plain', 'BBBB');
415
+ test("does not delete attachments outside the candidate set", () => {
416
+ const unrelated = uploadAttachment("unrelated.txt", "text/plain", "AAAA");
417
+ const candidate = uploadAttachment("candidate.txt", "text/plain", "BBBB");
397
418
 
398
419
  const removed = deleteOrphanAttachments([candidate.id]);
399
420
  expect(removed).toBe(1);
@@ -408,71 +429,107 @@ describe('deleteOrphanAttachments', () => {
408
429
  // validateAttachmentUpload
409
430
  // ---------------------------------------------------------------------------
410
431
 
411
- describe('validateAttachmentUpload', () => {
412
- test('accepts common image MIME types', () => {
413
- expect(validateAttachmentUpload('photo.png', 'image/png').ok).toBe(true);
414
- expect(validateAttachmentUpload('pic.jpg', 'image/jpeg').ok).toBe(true);
415
- expect(validateAttachmentUpload('anim.gif', 'image/gif').ok).toBe(true);
416
- expect(validateAttachmentUpload('sticker.webp', 'image/webp').ok).toBe(true);
432
+ describe("validateAttachmentUpload", () => {
433
+ test("accepts common image MIME types", () => {
434
+ expect(validateAttachmentUpload("photo.png", "image/png").ok).toBe(true);
435
+ expect(validateAttachmentUpload("pic.jpg", "image/jpeg").ok).toBe(true);
436
+ expect(validateAttachmentUpload("anim.gif", "image/gif").ok).toBe(true);
437
+ expect(validateAttachmentUpload("sticker.webp", "image/webp").ok).toBe(
438
+ true,
439
+ );
417
440
  });
418
441
 
419
- test('accepts document MIME types', () => {
420
- expect(validateAttachmentUpload('doc.pdf', 'application/pdf').ok).toBe(true);
421
- expect(validateAttachmentUpload('notes.txt', 'text/plain').ok).toBe(true);
422
- expect(validateAttachmentUpload('data.csv', 'text/csv').ok).toBe(true);
423
- expect(validateAttachmentUpload('config.json', 'application/json').ok).toBe(true);
442
+ test("accepts document MIME types", () => {
443
+ expect(validateAttachmentUpload("doc.pdf", "application/pdf").ok).toBe(
444
+ true,
445
+ );
446
+ expect(validateAttachmentUpload("notes.txt", "text/plain").ok).toBe(true);
447
+ expect(validateAttachmentUpload("data.csv", "text/csv").ok).toBe(true);
448
+ expect(validateAttachmentUpload("config.json", "application/json").ok).toBe(
449
+ true,
450
+ );
424
451
  });
425
452
 
426
- test('accepts audio and video MIME types', () => {
427
- expect(validateAttachmentUpload('voice.ogg', 'audio/ogg').ok).toBe(true);
428
- expect(validateAttachmentUpload('song.mp3', 'audio/mpeg').ok).toBe(true);
429
- expect(validateAttachmentUpload('clip.mp4', 'video/mp4').ok).toBe(true);
453
+ test("accepts audio and video MIME types", () => {
454
+ expect(validateAttachmentUpload("voice.ogg", "audio/ogg").ok).toBe(true);
455
+ expect(validateAttachmentUpload("song.mp3", "audio/mpeg").ok).toBe(true);
456
+ expect(validateAttachmentUpload("clip.mp4", "video/mp4").ok).toBe(true);
430
457
  });
431
458
 
432
- test('accepts application/octet-stream fallback', () => {
433
- expect(validateAttachmentUpload('data.bin', 'application/octet-stream').ok).toBe(true);
459
+ test("accepts application/octet-stream fallback", () => {
460
+ expect(
461
+ validateAttachmentUpload("data.bin", "application/octet-stream").ok,
462
+ ).toBe(true);
434
463
  });
435
464
 
436
- test('rejects dangerous file extensions', () => {
437
- const exeResult = validateAttachmentUpload('malware.exe', 'application/octet-stream');
465
+ test("rejects dangerous file extensions", () => {
466
+ const exeResult = validateAttachmentUpload(
467
+ "malware.exe",
468
+ "application/octet-stream",
469
+ );
438
470
  expect(exeResult.ok).toBe(false);
439
- if (!exeResult.ok) expect(exeResult.error).toContain('.exe');
471
+ if (!exeResult.ok) expect(exeResult.error).toContain(".exe");
440
472
 
441
- const shResult = validateAttachmentUpload('script.sh', 'text/plain');
473
+ const shResult = validateAttachmentUpload("script.sh", "text/plain");
442
474
  expect(shResult.ok).toBe(false);
443
- if (!shResult.ok) expect(shResult.error).toContain('.sh');
475
+ if (!shResult.ok) expect(shResult.error).toContain(".sh");
444
476
 
445
- const isoResult = validateAttachmentUpload('disk.iso', 'application/octet-stream');
477
+ const isoResult = validateAttachmentUpload(
478
+ "disk.iso",
479
+ "application/octet-stream",
480
+ );
446
481
  expect(isoResult.ok).toBe(false);
447
- if (!isoResult.ok) expect(isoResult.error).toContain('.iso');
482
+ if (!isoResult.ok) expect(isoResult.error).toContain(".iso");
448
483
  });
449
484
 
450
- test('rejects dangerous extensions regardless of claimed MIME type', () => {
485
+ test("rejects dangerous extensions regardless of claimed MIME type", () => {
451
486
  // .exe disguised as image/png
452
- const result = validateAttachmentUpload('payload.exe', 'image/png');
487
+ const result = validateAttachmentUpload("payload.exe", "image/png");
453
488
  expect(result.ok).toBe(false);
454
489
  });
455
490
 
456
- test('extension check is case-insensitive', () => {
457
- expect(validateAttachmentUpload('PROGRAM.EXE', 'application/octet-stream').ok).toBe(false);
458
- expect(validateAttachmentUpload('script.SH', 'text/plain').ok).toBe(false);
491
+ test("extension check is case-insensitive", () => {
492
+ expect(
493
+ validateAttachmentUpload("PROGRAM.EXE", "application/octet-stream").ok,
494
+ ).toBe(false);
495
+ expect(validateAttachmentUpload("script.SH", "text/plain").ok).toBe(false);
459
496
  });
460
497
 
461
- test('rejects unsupported MIME types', () => {
462
- const result = validateAttachmentUpload('file.unknown', 'application/x-msdownload');
498
+ test("rejects unsupported MIME types", () => {
499
+ const result = validateAttachmentUpload(
500
+ "file.unknown",
501
+ "application/x-msdownload",
502
+ );
463
503
  expect(result.ok).toBe(false);
464
- if (!result.ok) expect(result.error).toContain('Unsupported MIME type');
504
+ if (!result.ok) expect(result.error).toContain("Unsupported MIME type");
465
505
  });
466
506
 
467
- test('handles filenames without extensions', () => {
507
+ test("handles filenames without extensions", () => {
468
508
  // No extension — only MIME check applies
469
- expect(validateAttachmentUpload('Makefile', 'text/plain').ok).toBe(true);
470
- expect(validateAttachmentUpload('Makefile', 'application/x-evil').ok).toBe(false);
471
- });
472
-
473
- test('rejects all dangerous extension variants', () => {
474
- for (const ext of ['bat', 'cmd', 'com', 'msi', 'dmg', 'app', 'scr', 'pif', 'vbs', 'ps1', 'jar']) {
475
- const result = validateAttachmentUpload(`file.${ext}`, 'application/octet-stream');
509
+ expect(validateAttachmentUpload("Makefile", "text/plain").ok).toBe(true);
510
+ expect(validateAttachmentUpload("Makefile", "application/x-evil").ok).toBe(
511
+ false,
512
+ );
513
+ });
514
+
515
+ test("rejects all dangerous extension variants", () => {
516
+ for (const ext of [
517
+ "bat",
518
+ "cmd",
519
+ "com",
520
+ "msi",
521
+ "dmg",
522
+ "app",
523
+ "scr",
524
+ "pif",
525
+ "vbs",
526
+ "ps1",
527
+ "jar",
528
+ ]) {
529
+ const result = validateAttachmentUpload(
530
+ `file.${ext}`,
531
+ "application/octet-stream",
532
+ );
476
533
  expect(result.ok).toBe(false);
477
534
  }
478
535
  });