@vellumai/assistant 0.4.17 → 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 (515) hide show
  1. package/eslint.config.mjs +2 -2
  2. package/package.json +1 -1
  3. package/src/__tests__/access-request-decision.test.ts +128 -120
  4. package/src/__tests__/account-registry.test.ts +121 -110
  5. package/src/__tests__/active-skill-tools.test.ts +200 -172
  6. package/src/__tests__/actor-token-service.test.ts +341 -274
  7. package/src/__tests__/agent-loop-thinking.test.ts +28 -19
  8. package/src/__tests__/agent-loop.test.ts +798 -378
  9. package/src/__tests__/anthropic-provider.test.ts +405 -247
  10. package/src/__tests__/app-builder-tool-scripts.test.ts +97 -97
  11. package/src/__tests__/app-bundler.test.ts +112 -79
  12. package/src/__tests__/app-executors.test.ts +205 -178
  13. package/src/__tests__/app-git-history.test.ts +90 -73
  14. package/src/__tests__/app-git-service.test.ts +67 -53
  15. package/src/__tests__/app-open-proxy.test.ts +29 -25
  16. package/src/__tests__/approval-conversation-turn.test.ts +100 -81
  17. package/src/__tests__/approval-hardcoded-copy-guard.test.ts +45 -17
  18. package/src/__tests__/approval-message-composer.test.ts +119 -119
  19. package/src/__tests__/approval-primitive.test.ts +264 -233
  20. package/src/__tests__/approval-routes-http.test.ts +4 -3
  21. package/src/__tests__/asset-materialize-tool.test.ts +250 -178
  22. package/src/__tests__/asset-search-tool.test.ts +251 -191
  23. package/src/__tests__/assistant-attachment-directive.test.ts +187 -142
  24. package/src/__tests__/assistant-attachments.test.ts +254 -186
  25. package/src/__tests__/assistant-event-hub.test.ts +105 -63
  26. package/src/__tests__/assistant-event.test.ts +66 -58
  27. package/src/__tests__/assistant-events-sse-hardening.test.ts +113 -73
  28. package/src/__tests__/assistant-feature-flag-guard.test.ts +78 -52
  29. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +48 -45
  30. package/src/__tests__/assistant-feature-flags-integration.test.ts +118 -77
  31. package/src/__tests__/assistant-id-boundary-guard.test.ts +158 -104
  32. package/src/__tests__/attachments-store.test.ts +240 -183
  33. package/src/__tests__/attachments.test.ts +70 -62
  34. package/src/__tests__/audit-log-rotation.test.ts +50 -35
  35. package/src/__tests__/browser-fill-credential.test.ts +169 -101
  36. package/src/__tests__/browser-manager.test.ts +97 -75
  37. package/src/__tests__/browser-runtime-check.test.ts +16 -15
  38. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +12 -10
  39. package/src/__tests__/browser-skill-endstate.test.ts +97 -72
  40. package/src/__tests__/bundle-scanner.test.ts +47 -22
  41. package/src/__tests__/bundled-asset.test.ts +74 -47
  42. package/src/__tests__/call-constants.test.ts +19 -19
  43. package/src/__tests__/call-controller.test.ts +0 -1
  44. package/src/__tests__/call-conversation-messages.test.ts +90 -65
  45. package/src/__tests__/call-domain.test.ts +149 -121
  46. package/src/__tests__/call-pointer-message-composer.test.ts +113 -83
  47. package/src/__tests__/call-pointer-messages.test.ts +213 -154
  48. package/src/__tests__/call-pointer-no-hardcoded-copy.guard.test.ts +9 -10
  49. package/src/__tests__/call-recovery.test.ts +232 -212
  50. package/src/__tests__/call-routes-http.test.ts +0 -1
  51. package/src/__tests__/call-start-guardian-guard.test.ts +32 -30
  52. package/src/__tests__/call-state-machine.test.ts +62 -51
  53. package/src/__tests__/call-state.test.ts +89 -75
  54. package/src/__tests__/call-store.test.ts +387 -316
  55. package/src/__tests__/callback-handoff-copy.test.ts +84 -82
  56. package/src/__tests__/canonical-guardian-store.test.ts +331 -280
  57. package/src/__tests__/channel-approval-routes.test.ts +1643 -1115
  58. package/src/__tests__/channel-approval.test.ts +139 -137
  59. package/src/__tests__/channel-approvals.test.ts +0 -1
  60. package/src/__tests__/channel-delivery-store.test.ts +232 -194
  61. package/src/__tests__/channel-guardian.test.ts +5 -3
  62. package/src/__tests__/channel-invite-transport.test.ts +107 -92
  63. package/src/__tests__/channel-policy.test.ts +42 -38
  64. package/src/__tests__/channel-readiness-service.test.ts +119 -102
  65. package/src/__tests__/channel-reply-delivery.test.ts +147 -118
  66. package/src/__tests__/channel-retry-sweep.test.ts +153 -110
  67. package/src/__tests__/checker.test.ts +3309 -1850
  68. package/src/__tests__/clarification-resolver.test.ts +91 -79
  69. package/src/__tests__/classifier.test.ts +64 -54
  70. package/src/__tests__/claude-code-skill-regression.test.ts +42 -37
  71. package/src/__tests__/claude-code-tool-profiles.test.ts +31 -29
  72. package/src/__tests__/clawhub.test.ts +92 -82
  73. package/src/__tests__/cli.test.ts +30 -30
  74. package/src/__tests__/clipboard.test.ts +53 -46
  75. package/src/__tests__/commit-guarantee.test.ts +59 -52
  76. package/src/__tests__/commit-message-enrichment-service.test.ts +203 -75
  77. package/src/__tests__/compaction.benchmark.test.ts +33 -31
  78. package/src/__tests__/computer-use-session-compaction.test.ts +60 -50
  79. package/src/__tests__/computer-use-session-lifecycle.test.ts +145 -117
  80. package/src/__tests__/computer-use-session-working-dir.test.ts +62 -48
  81. package/src/__tests__/computer-use-skill-baseline.test.ts +22 -19
  82. package/src/__tests__/computer-use-skill-endstate.test.ts +45 -31
  83. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +121 -88
  84. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +65 -42
  85. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +33 -18
  86. package/src/__tests__/computer-use-tools.test.ts +121 -98
  87. package/src/__tests__/config-schema.test.ts +443 -347
  88. package/src/__tests__/config-watcher.test.ts +96 -81
  89. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +148 -133
  90. package/src/__tests__/conflict-intent-tokenization.test.ts +96 -78
  91. package/src/__tests__/conflict-policy.test.ts +151 -80
  92. package/src/__tests__/conflict-store.test.ts +203 -157
  93. package/src/__tests__/connection-policy.test.ts +89 -59
  94. package/src/__tests__/contacts-tools.test.ts +247 -178
  95. package/src/__tests__/context-memory-e2e.test.ts +306 -214
  96. package/src/__tests__/context-token-estimator.test.ts +114 -74
  97. package/src/__tests__/context-window-manager.test.ts +269 -167
  98. package/src/__tests__/contradiction-checker.test.ts +161 -135
  99. package/src/__tests__/conversation-attention-store.test.ts +350 -290
  100. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  101. package/src/__tests__/conversation-pairing.test.ts +220 -113
  102. package/src/__tests__/conversation-store.test.ts +390 -235
  103. package/src/__tests__/credential-broker-browser-fill.test.ts +325 -250
  104. package/src/__tests__/credential-broker-server-use.test.ts +283 -243
  105. package/src/__tests__/credential-broker.test.ts +128 -74
  106. package/src/__tests__/credential-host-pattern-match.test.ts +64 -44
  107. package/src/__tests__/credential-metadata-store.test.ts +360 -311
  108. package/src/__tests__/credential-policy-validate.test.ts +81 -65
  109. package/src/__tests__/credential-resolve.test.ts +212 -145
  110. package/src/__tests__/credential-security-e2e.test.ts +144 -103
  111. package/src/__tests__/credential-security-invariants.test.ts +253 -208
  112. package/src/__tests__/credential-selection.test.ts +254 -146
  113. package/src/__tests__/credential-vault-unit.test.ts +531 -341
  114. package/src/__tests__/credential-vault.test.ts +761 -484
  115. package/src/__tests__/daemon-assistant-events.test.ts +91 -66
  116. package/src/__tests__/daemon-lifecycle.test.ts +258 -190
  117. package/src/__tests__/daemon-server-session-init.test.ts +0 -1
  118. package/src/__tests__/date-context.test.ts +314 -249
  119. package/src/__tests__/db-migration-rollback.test.ts +259 -130
  120. package/src/__tests__/db-schedule-syntax-migration.test.ts +78 -41
  121. package/src/__tests__/delete-managed-skill-tool.test.ts +77 -53
  122. package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -1
  123. package/src/__tests__/dictation-mode-detection.test.ts +77 -55
  124. package/src/__tests__/dictation-profile-store.test.ts +70 -56
  125. package/src/__tests__/dictation-text-processing.test.ts +53 -35
  126. package/src/__tests__/diff.test.ts +102 -98
  127. package/src/__tests__/domain-normalize.test.ts +54 -54
  128. package/src/__tests__/domain-policy.test.ts +71 -55
  129. package/src/__tests__/dynamic-page-surface.test.ts +31 -33
  130. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +69 -69
  131. package/src/__tests__/edit-engine.test.ts +56 -56
  132. package/src/__tests__/elevenlabs-client.test.ts +117 -91
  133. package/src/__tests__/elevenlabs-config.test.ts +32 -31
  134. package/src/__tests__/email-classifier.test.ts +15 -12
  135. package/src/__tests__/email-cli.test.ts +121 -108
  136. package/src/__tests__/emit-signal-routing-intent.test.ts +76 -69
  137. package/src/__tests__/encrypted-store.test.ts +180 -154
  138. package/src/__tests__/entity-extractor.test.ts +108 -87
  139. package/src/__tests__/entity-search.test.ts +664 -258
  140. package/src/__tests__/ephemeral-permissions.test.ts +224 -188
  141. package/src/__tests__/event-bus.test.ts +81 -77
  142. package/src/__tests__/extract-email.test.ts +29 -20
  143. package/src/__tests__/file-edit-tool.test.ts +62 -44
  144. package/src/__tests__/file-ops-service.test.ts +131 -114
  145. package/src/__tests__/file-read-tool.test.ts +48 -31
  146. package/src/__tests__/file-write-tool.test.ts +43 -37
  147. package/src/__tests__/filesystem-tools.test.ts +238 -209
  148. package/src/__tests__/followup-tools.test.ts +237 -162
  149. package/src/__tests__/forbidden-legacy-symbols.test.ts +19 -20
  150. package/src/__tests__/frontmatter.test.ts +96 -81
  151. package/src/__tests__/fuzzy-match-property.test.ts +75 -81
  152. package/src/__tests__/fuzzy-match.test.ts +71 -65
  153. package/src/__tests__/gateway-client-managed-outbound.test.ts +76 -57
  154. package/src/__tests__/gateway-only-enforcement.test.ts +0 -1
  155. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  156. package/src/__tests__/gemini-image-service.test.ts +113 -100
  157. package/src/__tests__/gemini-provider.test.ts +297 -220
  158. package/src/__tests__/get-weather.test.ts +188 -114
  159. package/src/__tests__/gmail-integration.test.ts +0 -1
  160. package/src/__tests__/guardian-action-conversation-turn.test.ts +226 -171
  161. package/src/__tests__/guardian-action-copy-generator.test.ts +111 -93
  162. package/src/__tests__/guardian-action-followup-executor.test.ts +0 -1
  163. package/src/__tests__/guardian-action-followup-store.test.ts +199 -167
  164. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +297 -250
  165. package/src/__tests__/guardian-action-late-reply.test.ts +462 -316
  166. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +23 -18
  167. package/src/__tests__/guardian-action-store.test.ts +158 -109
  168. package/src/__tests__/guardian-action-sweep.test.ts +114 -100
  169. package/src/__tests__/guardian-actions-endpoint.test.ts +440 -256
  170. package/src/__tests__/guardian-control-plane-policy.test.ts +497 -331
  171. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +217 -215
  172. package/src/__tests__/guardian-dispatch.test.ts +316 -256
  173. package/src/__tests__/guardian-grant-minting.test.ts +247 -178
  174. package/src/__tests__/guardian-outbound-http.test.ts +5 -3
  175. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +99 -96
  176. package/src/__tests__/guardian-question-copy.test.ts +17 -17
  177. package/src/__tests__/guardian-question-mode.test.ts +134 -100
  178. package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
  179. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  180. package/src/__tests__/guardian-verification-intent-routing.test.ts +94 -88
  181. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
  182. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +0 -1
  183. package/src/__tests__/handle-user-message-secret-resume.test.ts +0 -1
  184. package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +92 -76
  185. package/src/__tests__/handlers-cu-observation-blob.test.ts +103 -70
  186. package/src/__tests__/handlers-ipc-blob-probe.test.ts +77 -51
  187. package/src/__tests__/handlers-slack-config.test.ts +63 -54
  188. package/src/__tests__/handlers-task-submit-slash.test.ts +18 -18
  189. package/src/__tests__/handlers-telegram-config.test.ts +662 -329
  190. package/src/__tests__/handlers-twitter-config.test.ts +525 -298
  191. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -2
  192. package/src/__tests__/headless-browser-interactions.test.ts +444 -280
  193. package/src/__tests__/headless-browser-navigate.test.ts +116 -79
  194. package/src/__tests__/headless-browser-read-tools.test.ts +123 -86
  195. package/src/__tests__/headless-browser-snapshot.test.ts +71 -56
  196. package/src/__tests__/heartbeat-service.test.ts +76 -58
  197. package/src/__tests__/history-repair-observability.test.ts +14 -14
  198. package/src/__tests__/history-repair.test.ts +171 -167
  199. package/src/__tests__/home-base-bootstrap.test.ts +30 -27
  200. package/src/__tests__/hooks-blocking.test.ts +86 -37
  201. package/src/__tests__/hooks-cli.test.ts +104 -68
  202. package/src/__tests__/hooks-config.test.ts +81 -43
  203. package/src/__tests__/hooks-discovery.test.ts +106 -96
  204. package/src/__tests__/hooks-integration.test.ts +78 -72
  205. package/src/__tests__/hooks-manager.test.ts +99 -61
  206. package/src/__tests__/hooks-runner.test.ts +94 -71
  207. package/src/__tests__/hooks-settings.test.ts +69 -64
  208. package/src/__tests__/hooks-templates.test.ts +85 -54
  209. package/src/__tests__/hooks-ts-runner.test.ts +82 -45
  210. package/src/__tests__/hooks-watch.test.ts +32 -22
  211. package/src/__tests__/host-file-edit-tool.test.ts +190 -148
  212. package/src/__tests__/host-file-read-tool.test.ts +86 -63
  213. package/src/__tests__/host-file-write-tool.test.ts +98 -64
  214. package/src/__tests__/host-shell-tool.test.ts +342 -233
  215. package/src/__tests__/inbound-invite-redemption.test.ts +0 -1
  216. package/src/__tests__/ingress-member-store.test.ts +163 -159
  217. package/src/__tests__/ingress-reconcile.test.ts +0 -1
  218. package/src/__tests__/ingress-routes-http.test.ts +441 -356
  219. package/src/__tests__/ingress-url-consistency.test.ts +125 -64
  220. package/src/__tests__/integration-status.test.ts +93 -73
  221. package/src/__tests__/intent-routing.test.ts +148 -118
  222. package/src/__tests__/invite-redemption-service.test.ts +163 -121
  223. package/src/__tests__/ipc-blob-store.test.ts +104 -91
  224. package/src/__tests__/ipc-contract-inventory.test.ts +27 -15
  225. package/src/__tests__/ipc-contract.test.ts +24 -23
  226. package/src/__tests__/ipc-protocol.test.ts +52 -46
  227. package/src/__tests__/ipc-roundtrip.benchmark.test.ts +61 -50
  228. package/src/__tests__/ipc-snapshot.test.ts +1135 -1056
  229. package/src/__tests__/ipc-validate.test.ts +240 -179
  230. package/src/__tests__/key-migration.test.ts +123 -90
  231. package/src/__tests__/keychain.test.ts +150 -123
  232. package/src/__tests__/lifecycle-docs-guard.test.ts +65 -64
  233. package/src/__tests__/llm-usage-store.test.ts +112 -87
  234. package/src/__tests__/managed-skill-lifecycle.test.ts +147 -108
  235. package/src/__tests__/managed-store.test.ts +411 -360
  236. package/src/__tests__/mcp-cli.test.ts +189 -123
  237. package/src/__tests__/mcp-health-check.test.ts +26 -21
  238. package/src/__tests__/media-generate-image.test.ts +122 -99
  239. package/src/__tests__/media-reuse-story.e2e.test.ts +282 -214
  240. package/src/__tests__/media-visibility-policy.test.ts +86 -38
  241. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +146 -100
  242. package/src/__tests__/memory-lifecycle-e2e.test.ts +385 -297
  243. package/src/__tests__/memory-query-builder.test.ts +32 -33
  244. package/src/__tests__/memory-recall-quality.test.ts +761 -407
  245. package/src/__tests__/memory-regressions.experimental.test.ts +443 -380
  246. package/src/__tests__/memory-regressions.test.ts +3725 -2642
  247. package/src/__tests__/memory-retrieval-budget.test.ts +7 -8
  248. package/src/__tests__/memory-retrieval.benchmark.test.ts +144 -109
  249. package/src/__tests__/memory-upsert-concurrency.test.ts +292 -201
  250. package/src/__tests__/messaging-send-tool.test.ts +36 -29
  251. package/src/__tests__/migration-cli-flows.test.ts +69 -53
  252. package/src/__tests__/migration-ordering.test.ts +103 -86
  253. package/src/__tests__/mime-builder.test.ts +55 -32
  254. package/src/__tests__/mock-signup-server.test.ts +384 -246
  255. package/src/__tests__/model-intents.test.ts +61 -37
  256. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +9 -12
  257. package/src/__tests__/no-is-trusted-guard.test.ts +24 -21
  258. package/src/__tests__/non-member-access-request.test.ts +3 -2
  259. package/src/__tests__/notification-broadcaster.test.ts +99 -81
  260. package/src/__tests__/notification-decision-fallback.test.ts +223 -178
  261. package/src/__tests__/notification-decision-strategy.test.ts +375 -337
  262. package/src/__tests__/notification-deep-link.test.ts +67 -61
  263. package/src/__tests__/notification-guardian-path.test.ts +248 -206
  264. package/src/__tests__/notification-routing-intent.test.ts +166 -93
  265. package/src/__tests__/notification-thread-candidate-validation.test.ts +78 -75
  266. package/src/__tests__/notification-thread-candidates.test.ts +64 -61
  267. package/src/__tests__/oauth-callback-registry.test.ts +40 -30
  268. package/src/__tests__/oauth-connect-handler.test.ts +109 -89
  269. package/src/__tests__/oauth-scope-policy.test.ts +63 -55
  270. package/src/__tests__/oauth2-gateway-transport.test.ts +252 -174
  271. package/src/__tests__/onboarding-starter-tasks.test.ts +93 -89
  272. package/src/__tests__/onboarding-template-contract.test.ts +93 -94
  273. package/src/__tests__/openai-provider.test.ts +366 -274
  274. package/src/__tests__/pairing-concurrent.test.ts +18 -12
  275. package/src/__tests__/pairing-routes.test.ts +45 -41
  276. package/src/__tests__/parallel-tool.benchmark.test.ts +108 -58
  277. package/src/__tests__/parser.test.ts +316 -226
  278. package/src/__tests__/path-classifier.test.ts +24 -25
  279. package/src/__tests__/path-policy.test.ts +187 -147
  280. package/src/__tests__/phone.test.ts +36 -36
  281. package/src/__tests__/platform-move-helper.test.ts +48 -40
  282. package/src/__tests__/platform-socket-path.test.ts +23 -24
  283. package/src/__tests__/platform-workspace-migration.test.ts +464 -414
  284. package/src/__tests__/platform.test.ts +61 -53
  285. package/src/__tests__/playbook-execution.test.ts +397 -265
  286. package/src/__tests__/playbook-tools.test.ts +267 -196
  287. package/src/__tests__/prebuilt-home-base-seed.test.ts +30 -27
  288. package/src/__tests__/pricing.test.ts +316 -136
  289. package/src/__tests__/profile-compiler.test.ts +206 -188
  290. package/src/__tests__/provider-commit-message-generator.test.ts +114 -106
  291. package/src/__tests__/provider-error-scenarios.test.ts +212 -158
  292. package/src/__tests__/provider-fail-open-selection.test.ts +51 -44
  293. package/src/__tests__/provider-registry-ollama.test.ts +13 -9
  294. package/src/__tests__/provider-streaming.benchmark.test.ts +232 -183
  295. package/src/__tests__/proxy-approval-callback.test.ts +180 -119
  296. package/src/__tests__/public-ingress-urls.test.ts +112 -94
  297. package/src/__tests__/qdrant-manager.test.ts +147 -98
  298. package/src/__tests__/ratelimit.test.ts +152 -82
  299. package/src/__tests__/recording-handler.test.ts +273 -151
  300. package/src/__tests__/recording-intent-fallback.test.ts +94 -75
  301. package/src/__tests__/recording-intent-handler.test.ts +0 -1
  302. package/src/__tests__/recording-intent.test.ts +578 -379
  303. package/src/__tests__/recording-state-machine.test.ts +530 -316
  304. package/src/__tests__/recurrence-engine-rruleset.test.ts +150 -92
  305. package/src/__tests__/recurrence-engine.test.ts +81 -41
  306. package/src/__tests__/recurrence-types.test.ts +63 -44
  307. package/src/__tests__/relay-server.test.ts +2131 -1602
  308. package/src/__tests__/reminder-store.test.ts +158 -80
  309. package/src/__tests__/reminder.test.ts +113 -109
  310. package/src/__tests__/remote-skill-policy.test.ts +96 -72
  311. package/src/__tests__/request-file-tool.test.ts +74 -67
  312. package/src/__tests__/response-tier.test.ts +131 -74
  313. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  314. package/src/__tests__/runtime-events-sse-parity.test.ts +167 -145
  315. package/src/__tests__/runtime-events-sse.test.ts +0 -1
  316. package/src/__tests__/sandbox-diagnostics.test.ts +66 -56
  317. package/src/__tests__/sandbox-host-parity.test.ts +377 -301
  318. package/src/__tests__/scaffold-managed-skill-tool.test.ts +213 -161
  319. package/src/__tests__/schedule-store.test.ts +268 -205
  320. package/src/__tests__/schedule-tools.test.ts +702 -524
  321. package/src/__tests__/scheduler-recurrence.test.ts +240 -130
  322. package/src/__tests__/scoped-approval-grants.test.ts +258 -168
  323. package/src/__tests__/scoped-grant-security-matrix.test.ts +160 -146
  324. package/src/__tests__/script-proxy-certs.test.ts +38 -35
  325. package/src/__tests__/script-proxy-connect-tunnel.test.ts +71 -46
  326. package/src/__tests__/script-proxy-decision-trace.test.ts +161 -84
  327. package/src/__tests__/script-proxy-http-forwarder.test.ts +146 -129
  328. package/src/__tests__/script-proxy-injection-runtime.test.ts +139 -113
  329. package/src/__tests__/script-proxy-mitm-handler.test.ts +226 -142
  330. package/src/__tests__/script-proxy-policy-runtime.test.ts +126 -86
  331. package/src/__tests__/script-proxy-policy.test.ts +308 -153
  332. package/src/__tests__/script-proxy-rewrite-specificity.test.ts +74 -62
  333. package/src/__tests__/script-proxy-router.test.ts +111 -77
  334. package/src/__tests__/script-proxy-session-manager.test.ts +156 -113
  335. package/src/__tests__/script-proxy-session-runtime.test.ts +28 -24
  336. package/src/__tests__/secret-allowlist.test.ts +105 -90
  337. package/src/__tests__/secret-ingress-handler.test.ts +41 -30
  338. package/src/__tests__/secret-onetime-send.test.ts +67 -50
  339. package/src/__tests__/secret-prompt-log-hygiene.test.ts +35 -31
  340. package/src/__tests__/secret-response-routing.test.ts +50 -41
  341. package/src/__tests__/secret-scanner-executor.test.ts +152 -111
  342. package/src/__tests__/secret-scanner.test.ts +495 -413
  343. package/src/__tests__/secure-keys.test.ts +132 -121
  344. package/src/__tests__/send-endpoint-busy.test.ts +0 -1
  345. package/src/__tests__/send-notification-tool.test.ts +43 -42
  346. package/src/__tests__/sensitive-output-placeholders.test.ts +72 -64
  347. package/src/__tests__/sequence-store.test.ts +335 -167
  348. package/src/__tests__/server-history-render.test.ts +341 -202
  349. package/src/__tests__/session-abort-tool-results.test.ts +133 -70
  350. package/src/__tests__/session-confirmation-signals.test.ts +252 -160
  351. package/src/__tests__/session-conflict-gate.test.ts +775 -585
  352. package/src/__tests__/session-error.test.ts +222 -191
  353. package/src/__tests__/session-evictor.test.ts +79 -62
  354. package/src/__tests__/session-init.benchmark.test.ts +170 -108
  355. package/src/__tests__/session-load-history-repair.test.ts +273 -139
  356. package/src/__tests__/session-messaging-secret-redirect.test.ts +130 -90
  357. package/src/__tests__/session-pre-run-repair.test.ts +106 -59
  358. package/src/__tests__/session-profile-injection.test.ts +198 -130
  359. package/src/__tests__/session-provider-retry-repair.test.ts +223 -141
  360. package/src/__tests__/session-queue.test.ts +624 -321
  361. package/src/__tests__/session-runtime-assembly.test.ts +425 -329
  362. package/src/__tests__/session-runtime-workspace.test.ts +69 -61
  363. package/src/__tests__/session-skill-tools.test.ts +973 -678
  364. package/src/__tests__/session-slash-known.test.ts +185 -133
  365. package/src/__tests__/session-slash-queue.test.ts +147 -81
  366. package/src/__tests__/session-slash-unknown.test.ts +135 -90
  367. package/src/__tests__/session-surfaces-task-progress.test.ts +122 -87
  368. package/src/__tests__/session-tool-setup-app-refresh.test.ts +338 -177
  369. package/src/__tests__/session-tool-setup-memory-scope.test.ts +63 -40
  370. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +60 -37
  371. package/src/__tests__/session-tool-setup-tools-disabled.test.ts +28 -26
  372. package/src/__tests__/session-undo.test.ts +43 -30
  373. package/src/__tests__/session-workspace-cache-state.test.ts +108 -67
  374. package/src/__tests__/session-workspace-injection.test.ts +245 -117
  375. package/src/__tests__/session-workspace-tool-tracking.test.ts +260 -93
  376. package/src/__tests__/shared-filesystem-errors.test.ts +47 -47
  377. package/src/__tests__/shell-credential-ref.test.ts +126 -90
  378. package/src/__tests__/shell-identity.test.ts +134 -111
  379. package/src/__tests__/shell-parser-fuzz.test.ts +263 -179
  380. package/src/__tests__/shell-parser-property.test.ts +435 -288
  381. package/src/__tests__/shell-tool-proxy-mode.test.ts +142 -70
  382. package/src/__tests__/size-guard.test.ts +42 -44
  383. package/src/__tests__/skill-feature-flags-integration.test.ts +79 -52
  384. package/src/__tests__/skill-feature-flags.test.ts +75 -47
  385. package/src/__tests__/skill-include-graph.test.ts +143 -148
  386. package/src/__tests__/skill-load-feature-flag.test.ts +94 -59
  387. package/src/__tests__/skill-load-tool.test.ts +371 -199
  388. package/src/__tests__/skill-projection-feature-flag.test.ts +131 -88
  389. package/src/__tests__/skill-projection.benchmark.test.ts +93 -65
  390. package/src/__tests__/skill-script-runner-host.test.ts +460 -250
  391. package/src/__tests__/skill-script-runner-sandbox.test.ts +168 -108
  392. package/src/__tests__/skill-script-runner.test.ts +115 -74
  393. package/src/__tests__/skill-tool-factory.test.ts +140 -96
  394. package/src/__tests__/skill-tool-manifest.test.ts +306 -210
  395. package/src/__tests__/skill-version-hash.test.ts +70 -56
  396. package/src/__tests__/skills.test.ts +0 -1
  397. package/src/__tests__/slack-channel-config.test.ts +127 -84
  398. package/src/__tests__/slack-skill.test.ts +60 -47
  399. package/src/__tests__/slash-commands-catalog.test.ts +37 -31
  400. package/src/__tests__/slash-commands-parser.test.ts +71 -64
  401. package/src/__tests__/slash-commands-resolver.test.ts +143 -107
  402. package/src/__tests__/slash-commands-rewrite.test.ts +22 -22
  403. package/src/__tests__/speaker-identification.test.ts +28 -25
  404. package/src/__tests__/starter-bundle.test.ts +27 -23
  405. package/src/__tests__/starter-task-flow.test.ts +67 -52
  406. package/src/__tests__/subagent-manager-notify.test.ts +154 -108
  407. package/src/__tests__/subagent-tools.test.ts +311 -270
  408. package/src/__tests__/subagent-types.test.ts +40 -40
  409. package/src/__tests__/surface-mutex-cleanup.test.ts +42 -30
  410. package/src/__tests__/swarm-dag-pathological.test.ts +122 -111
  411. package/src/__tests__/swarm-orchestrator.test.ts +135 -101
  412. package/src/__tests__/swarm-plan-validator.test.ts +125 -73
  413. package/src/__tests__/swarm-recursion.test.ts +58 -46
  414. package/src/__tests__/swarm-router-planner.test.ts +99 -74
  415. package/src/__tests__/swarm-session-integration.test.ts +148 -91
  416. package/src/__tests__/swarm-tool.test.ts +65 -45
  417. package/src/__tests__/swarm-worker-backend.test.ts +59 -45
  418. package/src/__tests__/swarm-worker-runner.test.ts +133 -118
  419. package/src/__tests__/system-prompt.test.ts +290 -256
  420. package/src/__tests__/task-compiler.test.ts +176 -120
  421. package/src/__tests__/task-management-tools.test.ts +561 -456
  422. package/src/__tests__/task-memory-cleanup.test.ts +627 -362
  423. package/src/__tests__/task-runner.test.ts +117 -94
  424. package/src/__tests__/task-scheduler.test.ts +113 -84
  425. package/src/__tests__/task-tools.test.ts +349 -264
  426. package/src/__tests__/terminal-sandbox.test.ts +138 -108
  427. package/src/__tests__/terminal-tools.test.ts +350 -305
  428. package/src/__tests__/thread-seed-composer.test.ts +307 -180
  429. package/src/__tests__/tool-approval-handler.test.ts +238 -137
  430. package/src/__tests__/tool-audit-listener.test.ts +69 -69
  431. package/src/__tests__/tool-domain-event-publisher.test.ts +142 -132
  432. package/src/__tests__/tool-execution-abort-cleanup.test.ts +153 -146
  433. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +136 -105
  434. package/src/__tests__/tool-executor-lifecycle-events.test.ts +355 -239
  435. package/src/__tests__/tool-executor-redaction.test.ts +112 -109
  436. package/src/__tests__/tool-executor-shell-integration.test.ts +130 -79
  437. package/src/__tests__/tool-executor.test.ts +1274 -674
  438. package/src/__tests__/tool-grant-request-escalation.test.ts +401 -283
  439. package/src/__tests__/tool-metrics-listener.test.ts +97 -85
  440. package/src/__tests__/tool-notification-listener.test.ts +42 -25
  441. package/src/__tests__/tool-permission-simulate-handler.test.ts +137 -113
  442. package/src/__tests__/tool-policy.test.ts +44 -25
  443. package/src/__tests__/tool-profiling-listener.test.ts +99 -93
  444. package/src/__tests__/tool-result-truncation.test.ts +5 -4
  445. package/src/__tests__/tool-trace-listener.test.ts +131 -111
  446. package/src/__tests__/top-level-renderer.test.ts +62 -58
  447. package/src/__tests__/top-level-scanner.test.ts +68 -64
  448. package/src/__tests__/trace-emitter.test.ts +56 -56
  449. package/src/__tests__/trust-context-guards.test.ts +65 -65
  450. package/src/__tests__/trust-store.test.ts +1239 -806
  451. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  452. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
  453. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +3 -2
  454. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -2
  455. package/src/__tests__/trusted-contact-verification.test.ts +251 -231
  456. package/src/__tests__/turn-commit.test.ts +259 -200
  457. package/src/__tests__/twilio-provider.test.ts +140 -126
  458. package/src/__tests__/twilio-rest.test.ts +22 -18
  459. package/src/__tests__/twilio-routes-elevenlabs.test.ts +0 -1
  460. package/src/__tests__/twilio-routes-twiml.test.ts +55 -55
  461. package/src/__tests__/twilio-routes.test.ts +0 -1
  462. package/src/__tests__/twitter-auth-handler.test.ts +184 -139
  463. package/src/__tests__/twitter-cli-error-shaping.test.ts +88 -73
  464. package/src/__tests__/twitter-cli-routing.test.ts +146 -99
  465. package/src/__tests__/twitter-oauth-client.test.ts +82 -65
  466. package/src/__tests__/update-bulletin-format.test.ts +69 -66
  467. package/src/__tests__/update-bulletin-state.test.ts +66 -60
  468. package/src/__tests__/update-bulletin.test.ts +150 -114
  469. package/src/__tests__/update-template-contract.test.ts +15 -10
  470. package/src/__tests__/url-safety.test.ts +288 -265
  471. package/src/__tests__/user-reference.test.ts +32 -32
  472. package/src/__tests__/view-image-tool.test.ts +118 -96
  473. package/src/__tests__/voice-invite-redemption.test.ts +111 -106
  474. package/src/__tests__/voice-quality.test.ts +117 -102
  475. package/src/__tests__/voice-scoped-grant-consumer.test.ts +204 -146
  476. package/src/__tests__/voice-session-bridge.test.ts +351 -216
  477. package/src/__tests__/weather-skill-regression.test.ts +170 -120
  478. package/src/__tests__/web-fetch.test.ts +664 -526
  479. package/src/__tests__/web-search.test.ts +379 -213
  480. package/src/__tests__/work-item-output.test.ts +90 -53
  481. package/src/__tests__/workspace-git-service.test.ts +437 -356
  482. package/src/__tests__/workspace-heartbeat-service.test.ts +125 -91
  483. package/src/__tests__/workspace-lifecycle.test.ts +98 -64
  484. package/src/__tests__/workspace-policy.test.ts +139 -71
  485. package/src/commands/__tests__/cc-command-registry.test.ts +142 -134
  486. package/src/config/__tests__/feature-flag-registry-guard.test.ts +48 -39
  487. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +25 -10
  488. package/src/config/bundled-skills/doordash/__tests__/doordash-session.test.ts +0 -1
  489. package/src/config/bundled-skills/messaging/SKILL.md +4 -3
  490. package/src/config/bundled-skills/messaging/tools/gmail-outreach-scan.ts +15 -5
  491. package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +16 -5
  492. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +34 -32
  493. package/src/config/bundled-tool-registry.ts +2 -0
  494. package/src/config/env.ts +3 -4
  495. package/src/memory/db-connection.ts +16 -10
  496. package/src/messaging/providers/gmail/adapter.ts +10 -3
  497. package/src/messaging/providers/gmail/client.ts +280 -72
  498. package/src/runtime/auth/__tests__/context.test.ts +75 -65
  499. package/src/runtime/auth/__tests__/credential-service.test.ts +137 -114
  500. package/src/runtime/auth/__tests__/guard-tests.test.ts +84 -90
  501. package/src/runtime/auth/__tests__/ipc-auth-context.test.ts +40 -40
  502. package/src/runtime/auth/__tests__/middleware.test.ts +80 -74
  503. package/src/runtime/auth/__tests__/policy.test.ts +9 -9
  504. package/src/runtime/auth/__tests__/route-policy.test.ts +76 -65
  505. package/src/runtime/auth/__tests__/scopes.test.ts +68 -60
  506. package/src/runtime/auth/__tests__/subject.test.ts +54 -54
  507. package/src/runtime/auth/__tests__/token-service.test.ts +115 -108
  508. package/src/runtime/auth/scopes.ts +3 -0
  509. package/src/runtime/auth/token-service.ts +4 -1
  510. package/src/runtime/auth/types.ts +2 -1
  511. package/src/runtime/http-server.ts +2 -1
  512. package/src/security/secure-keys.ts +103 -53
  513. package/src/tools/browser/__tests__/auth-cache.test.ts +69 -63
  514. package/src/tools/browser/__tests__/auth-detector.test.ts +218 -157
  515. package/src/tools/browser/__tests__/jit-auth.test.ts +83 -99
@@ -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
  });