@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,27 +1,27 @@
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(), "call-store-test-"));
6
7
 
7
- const testDir = mkdtempSync(join(tmpdir(), 'call-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
  }));
20
19
 
21
- mock.module('../util/logger.js', () => ({
22
- getLogger: () => new Proxy({} as Record<string, unknown>, {
23
- get: () => () => {},
24
- }),
20
+ mock.module("../util/logger.js", () => ({
21
+ getLogger: () =>
22
+ new Proxy({} as Record<string, unknown>, {
23
+ get: () => () => {},
24
+ }),
25
25
  }));
26
26
 
27
27
  import {
@@ -39,15 +39,19 @@ import {
39
39
  recordCallEvent,
40
40
  releaseCallbackClaim,
41
41
  updateCallSession,
42
- } from '../calls/call-store.js';
43
- import { getDb, initializeDb, resetDb } from '../memory/db.js';
44
- import { conversations } from '../memory/schema.js';
42
+ } from "../calls/call-store.js";
43
+ import { getDb, initializeDb, resetDb } from "../memory/db.js";
44
+ import { conversations } from "../memory/schema.js";
45
45
 
46
46
  initializeDb();
47
47
 
48
48
  afterAll(() => {
49
49
  resetDb();
50
- try { rmSync(testDir, { recursive: true }); } catch { /* best effort */ }
50
+ try {
51
+ rmSync(testDir, { recursive: true });
52
+ } catch {
53
+ /* best effort */
54
+ }
51
55
  });
52
56
 
53
57
  /** Ensure a conversation row exists for the given ID so FK constraints pass. */
@@ -56,25 +60,27 @@ function ensureConversation(id: string): void {
56
60
  if (ensuredConvIds.has(id)) return;
57
61
  const db = getDb();
58
62
  const now = Date.now();
59
- db.insert(conversations).values({
60
- id,
61
- title: `Test conversation ${id}`,
62
- createdAt: now,
63
- updatedAt: now,
64
- }).run();
63
+ db.insert(conversations)
64
+ .values({
65
+ id,
66
+ title: `Test conversation ${id}`,
67
+ createdAt: now,
68
+ updatedAt: now,
69
+ })
70
+ .run();
65
71
  ensuredConvIds.add(id);
66
72
  }
67
73
 
68
74
  function resetTables() {
69
75
  const db = getDb();
70
- db.run('DELETE FROM guardian_action_deliveries');
71
- db.run('DELETE FROM guardian_action_requests');
72
- db.run('DELETE FROM call_pending_questions');
73
- db.run('DELETE FROM call_events');
74
- db.run('DELETE FROM call_sessions');
75
- db.run('DELETE FROM processed_callbacks');
76
- db.run('DELETE FROM messages');
77
- db.run('DELETE FROM conversations');
76
+ db.run("DELETE FROM guardian_action_deliveries");
77
+ db.run("DELETE FROM guardian_action_requests");
78
+ db.run("DELETE FROM call_pending_questions");
79
+ db.run("DELETE FROM call_events");
80
+ db.run("DELETE FROM call_sessions");
81
+ db.run("DELETE FROM processed_callbacks");
82
+ db.run("DELETE FROM messages");
83
+ db.run("DELETE FROM conversations");
78
84
  ensuredConvIds = new Set();
79
85
  }
80
86
 
@@ -84,272 +90,274 @@ function createTestCallSession(opts: Parameters<typeof createCallSession>[0]) {
84
90
  return createCallSession(opts);
85
91
  }
86
92
 
87
- describe('call-store', () => {
93
+ describe("call-store", () => {
88
94
  beforeEach(() => {
89
95
  resetTables();
90
96
  });
91
97
 
92
98
  // ── Call Sessions ─────────────────────────────────────────────────
93
99
 
94
- test('createCallSession creates a session with correct defaults', () => {
100
+ test("createCallSession creates a session with correct defaults", () => {
95
101
  const session = createTestCallSession({
96
- conversationId: 'conv-1',
97
- provider: 'twilio',
98
- fromNumber: '+15551234567',
99
- toNumber: '+15559876543',
100
- task: 'Book appointment',
102
+ conversationId: "conv-1",
103
+ provider: "twilio",
104
+ fromNumber: "+15551234567",
105
+ toNumber: "+15559876543",
106
+ task: "Book appointment",
101
107
  });
102
108
 
103
109
  expect(session.id).toBeDefined();
104
- expect(session.conversationId).toBe('conv-1');
105
- expect(session.provider).toBe('twilio');
106
- expect(session.fromNumber).toBe('+15551234567');
107
- expect(session.toNumber).toBe('+15559876543');
108
- expect(session.task).toBe('Book appointment');
109
- expect(session.status).toBe('initiated');
110
+ expect(session.conversationId).toBe("conv-1");
111
+ expect(session.provider).toBe("twilio");
112
+ expect(session.fromNumber).toBe("+15551234567");
113
+ expect(session.toNumber).toBe("+15559876543");
114
+ expect(session.task).toBe("Book appointment");
115
+ expect(session.status).toBe("initiated");
110
116
  expect(session.providerCallSid).toBeNull();
111
117
  expect(session.startedAt).toBeNull();
112
118
  expect(session.endedAt).toBeNull();
113
119
  expect(session.lastError).toBeNull();
114
- expect(typeof session.createdAt).toBe('number');
115
- expect(typeof session.updatedAt).toBe('number');
120
+ expect(typeof session.createdAt).toBe("number");
121
+ expect(typeof session.updatedAt).toBe("number");
116
122
  });
117
123
 
118
- test('createCallSession defaults task to null when not provided', () => {
124
+ test("createCallSession defaults task to null when not provided", () => {
119
125
  const session = createTestCallSession({
120
- conversationId: 'conv-2',
121
- provider: 'twilio',
122
- fromNumber: '+15551111111',
123
- toNumber: '+15552222222',
126
+ conversationId: "conv-2",
127
+ provider: "twilio",
128
+ fromNumber: "+15551111111",
129
+ toNumber: "+15552222222",
124
130
  });
125
131
 
126
132
  expect(session.task).toBeNull();
127
133
  });
128
134
 
129
- test('getCallSession retrieves by ID', () => {
135
+ test("getCallSession retrieves by ID", () => {
130
136
  const created = createTestCallSession({
131
- conversationId: 'conv-3',
132
- provider: 'twilio',
133
- fromNumber: '+15551111111',
134
- toNumber: '+15552222222',
137
+ conversationId: "conv-3",
138
+ provider: "twilio",
139
+ fromNumber: "+15551111111",
140
+ toNumber: "+15552222222",
135
141
  });
136
142
 
137
143
  const retrieved = getCallSession(created.id);
138
144
  expect(retrieved).not.toBeNull();
139
145
  expect(retrieved!.id).toBe(created.id);
140
- expect(retrieved!.conversationId).toBe('conv-3');
146
+ expect(retrieved!.conversationId).toBe("conv-3");
141
147
  });
142
148
 
143
- test('getCallSession returns null for missing ID', () => {
144
- const result = getCallSession('nonexistent-id');
149
+ test("getCallSession returns null for missing ID", () => {
150
+ const result = getCallSession("nonexistent-id");
145
151
  expect(result).toBeNull();
146
152
  });
147
153
 
148
- test('getCallSessionByCallSid looks up by provider call SID', () => {
154
+ test("getCallSessionByCallSid looks up by provider call SID", () => {
149
155
  const session = createTestCallSession({
150
- conversationId: 'conv-4',
151
- provider: 'twilio',
152
- fromNumber: '+15551111111',
153
- toNumber: '+15552222222',
156
+ conversationId: "conv-4",
157
+ provider: "twilio",
158
+ fromNumber: "+15551111111",
159
+ toNumber: "+15552222222",
154
160
  });
155
161
 
156
- updateCallSession(session.id, { providerCallSid: 'CA_test_sid_123' });
162
+ updateCallSession(session.id, { providerCallSid: "CA_test_sid_123" });
157
163
 
158
- const found = getCallSessionByCallSid('CA_test_sid_123');
164
+ const found = getCallSessionByCallSid("CA_test_sid_123");
159
165
  expect(found).not.toBeNull();
160
166
  expect(found!.id).toBe(session.id);
161
- expect(found!.providerCallSid).toBe('CA_test_sid_123');
167
+ expect(found!.providerCallSid).toBe("CA_test_sid_123");
162
168
  });
163
169
 
164
- test('getCallSessionByCallSid returns null for unknown SID', () => {
165
- const result = getCallSessionByCallSid('CA_unknown');
170
+ test("getCallSessionByCallSid returns null for unknown SID", () => {
171
+ const result = getCallSessionByCallSid("CA_unknown");
166
172
  expect(result).toBeNull();
167
173
  });
168
174
 
169
- test('getActiveCallSessionForConversation finds non-terminal sessions', () => {
175
+ test("getActiveCallSessionForConversation finds non-terminal sessions", () => {
170
176
  const session = createTestCallSession({
171
- conversationId: 'conv-5',
172
- provider: 'twilio',
173
- fromNumber: '+15551111111',
174
- toNumber: '+15552222222',
177
+ conversationId: "conv-5",
178
+ provider: "twilio",
179
+ fromNumber: "+15551111111",
180
+ toNumber: "+15552222222",
175
181
  });
176
182
 
177
- const active = getActiveCallSessionForConversation('conv-5');
183
+ const active = getActiveCallSessionForConversation("conv-5");
178
184
  expect(active).not.toBeNull();
179
185
  expect(active!.id).toBe(session.id);
180
186
  });
181
187
 
182
- test('getActiveCallSessionForConversation returns null when all sessions are completed', () => {
188
+ test("getActiveCallSessionForConversation returns null when all sessions are completed", () => {
183
189
  const session = createTestCallSession({
184
- conversationId: 'conv-6',
185
- provider: 'twilio',
186
- fromNumber: '+15551111111',
187
- toNumber: '+15552222222',
190
+ conversationId: "conv-6",
191
+ provider: "twilio",
192
+ fromNumber: "+15551111111",
193
+ toNumber: "+15552222222",
188
194
  });
189
195
 
190
- updateCallSession(session.id, { status: 'completed' });
196
+ updateCallSession(session.id, { status: "completed" });
191
197
 
192
- const active = getActiveCallSessionForConversation('conv-6');
198
+ const active = getActiveCallSessionForConversation("conv-6");
193
199
  expect(active).toBeNull();
194
200
  });
195
201
 
196
- test('getActiveCallSessionForConversation returns null when all sessions are failed', () => {
202
+ test("getActiveCallSessionForConversation returns null when all sessions are failed", () => {
197
203
  const session = createTestCallSession({
198
- conversationId: 'conv-7',
199
- provider: 'twilio',
200
- fromNumber: '+15551111111',
201
- toNumber: '+15552222222',
204
+ conversationId: "conv-7",
205
+ provider: "twilio",
206
+ fromNumber: "+15551111111",
207
+ toNumber: "+15552222222",
202
208
  });
203
209
 
204
- updateCallSession(session.id, { status: 'failed' });
210
+ updateCallSession(session.id, { status: "failed" });
205
211
 
206
- const active = getActiveCallSessionForConversation('conv-7');
212
+ const active = getActiveCallSessionForConversation("conv-7");
207
213
  expect(active).toBeNull();
208
214
  });
209
215
 
210
- test('getActiveCallSessionForConversation returns most recent active session', () => {
216
+ test("getActiveCallSessionForConversation returns most recent active session", () => {
211
217
  // Create two sessions for the same conversation
212
218
  const older = createTestCallSession({
213
- conversationId: 'conv-8',
214
- provider: 'twilio',
215
- fromNumber: '+15551111111',
216
- toNumber: '+15552222222',
219
+ conversationId: "conv-8",
220
+ provider: "twilio",
221
+ fromNumber: "+15551111111",
222
+ toNumber: "+15552222222",
217
223
  });
218
224
  // Mark older as completed
219
- updateCallSession(older.id, { status: 'completed' });
225
+ updateCallSession(older.id, { status: "completed" });
220
226
 
221
227
  const newer = createTestCallSession({
222
- conversationId: 'conv-8',
223
- provider: 'twilio',
224
- fromNumber: '+15551111111',
225
- toNumber: '+15553333333',
228
+ conversationId: "conv-8",
229
+ provider: "twilio",
230
+ fromNumber: "+15551111111",
231
+ toNumber: "+15553333333",
226
232
  });
227
233
 
228
- const active = getActiveCallSessionForConversation('conv-8');
234
+ const active = getActiveCallSessionForConversation("conv-8");
229
235
  expect(active).not.toBeNull();
230
236
  expect(active!.id).toBe(newer.id);
231
237
  });
232
238
 
233
- test('updateCallSession updates status, providerCallSid, and timestamps', () => {
239
+ test("updateCallSession updates status, providerCallSid, and timestamps", () => {
234
240
  const session = createTestCallSession({
235
- conversationId: 'conv-9',
236
- provider: 'twilio',
237
- fromNumber: '+15551111111',
238
- toNumber: '+15552222222',
241
+ conversationId: "conv-9",
242
+ provider: "twilio",
243
+ fromNumber: "+15551111111",
244
+ toNumber: "+15552222222",
239
245
  });
240
246
 
241
247
  const now = Date.now();
242
248
  updateCallSession(session.id, {
243
- status: 'in_progress',
244
- providerCallSid: 'CA_updated_sid',
249
+ status: "in_progress",
250
+ providerCallSid: "CA_updated_sid",
245
251
  startedAt: now,
246
252
  });
247
253
 
248
254
  const updated = getCallSession(session.id);
249
255
  expect(updated).not.toBeNull();
250
- expect(updated!.status).toBe('in_progress');
251
- expect(updated!.providerCallSid).toBe('CA_updated_sid');
256
+ expect(updated!.status).toBe("in_progress");
257
+ expect(updated!.providerCallSid).toBe("CA_updated_sid");
252
258
  expect(updated!.startedAt).toBe(now);
253
259
  // updatedAt should be updated
254
260
  expect(updated!.updatedAt).toBeGreaterThanOrEqual(session.updatedAt);
255
261
  });
256
262
 
257
- test('updateCallSession sets endedAt and lastError', () => {
263
+ test("updateCallSession sets endedAt and lastError", () => {
258
264
  const session = createTestCallSession({
259
- conversationId: 'conv-10',
260
- provider: 'twilio',
261
- fromNumber: '+15551111111',
262
- toNumber: '+15552222222',
265
+ conversationId: "conv-10",
266
+ provider: "twilio",
267
+ fromNumber: "+15551111111",
268
+ toNumber: "+15552222222",
263
269
  });
264
270
 
265
271
  const endTime = Date.now();
266
272
  updateCallSession(session.id, {
267
- status: 'failed',
273
+ status: "failed",
268
274
  endedAt: endTime,
269
- lastError: 'Network timeout',
275
+ lastError: "Network timeout",
270
276
  });
271
277
 
272
278
  const updated = getCallSession(session.id);
273
- expect(updated!.status).toBe('failed');
279
+ expect(updated!.status).toBe("failed");
274
280
  expect(updated!.endedAt).toBe(endTime);
275
- expect(updated!.lastError).toBe('Network timeout');
281
+ expect(updated!.lastError).toBe("Network timeout");
276
282
  });
277
283
 
278
284
  // ── Call Events ───────────────────────────────────────────────────
279
285
 
280
- test('recordCallEvent creates events with correct fields', () => {
286
+ test("recordCallEvent creates events with correct fields", () => {
281
287
  const session = createTestCallSession({
282
- conversationId: 'conv-11',
283
- provider: 'twilio',
284
- fromNumber: '+15551111111',
285
- toNumber: '+15552222222',
288
+ conversationId: "conv-11",
289
+ provider: "twilio",
290
+ fromNumber: "+15551111111",
291
+ toNumber: "+15552222222",
286
292
  });
287
293
 
288
- const event = recordCallEvent(session.id, 'call_started', { twilioStatus: 'initiated' });
294
+ const event = recordCallEvent(session.id, "call_started", {
295
+ twilioStatus: "initiated",
296
+ });
289
297
 
290
298
  expect(event.id).toBeDefined();
291
299
  expect(event.callSessionId).toBe(session.id);
292
- expect(event.eventType).toBe('call_started');
293
- expect(typeof event.createdAt).toBe('number');
300
+ expect(event.eventType).toBe("call_started");
301
+ expect(typeof event.createdAt).toBe("number");
294
302
  });
295
303
 
296
- test('recordCallEvent stores JSON payload', () => {
304
+ test("recordCallEvent stores JSON payload", () => {
297
305
  const session = createTestCallSession({
298
- conversationId: 'conv-12',
299
- provider: 'twilio',
300
- fromNumber: '+15551111111',
301
- toNumber: '+15552222222',
306
+ conversationId: "conv-12",
307
+ provider: "twilio",
308
+ fromNumber: "+15551111111",
309
+ toNumber: "+15552222222",
302
310
  });
303
311
 
304
- const payload = { text: 'Hello, how are you?', lang: 'en-US' };
305
- const event = recordCallEvent(session.id, 'caller_spoke', payload);
312
+ const payload = { text: "Hello, how are you?", lang: "en-US" };
313
+ const event = recordCallEvent(session.id, "caller_spoke", payload);
306
314
 
307
315
  const parsed = JSON.parse(event.payloadJson);
308
- expect(parsed.text).toBe('Hello, how are you?');
309
- expect(parsed.lang).toBe('en-US');
316
+ expect(parsed.text).toBe("Hello, how are you?");
317
+ expect(parsed.lang).toBe("en-US");
310
318
  });
311
319
 
312
- test('recordCallEvent defaults payload to empty JSON object', () => {
320
+ test("recordCallEvent defaults payload to empty JSON object", () => {
313
321
  const session = createTestCallSession({
314
- conversationId: 'conv-13',
315
- provider: 'twilio',
316
- fromNumber: '+15551111111',
317
- toNumber: '+15552222222',
322
+ conversationId: "conv-13",
323
+ provider: "twilio",
324
+ fromNumber: "+15551111111",
325
+ toNumber: "+15552222222",
318
326
  });
319
327
 
320
- const event = recordCallEvent(session.id, 'call_connected');
328
+ const event = recordCallEvent(session.id, "call_connected");
321
329
 
322
- expect(event.payloadJson).toBe('{}');
330
+ expect(event.payloadJson).toBe("{}");
323
331
  });
324
332
 
325
- test('getCallEvents retrieves events in creation order', () => {
333
+ test("getCallEvents retrieves events in creation order", () => {
326
334
  const session = createTestCallSession({
327
- conversationId: 'conv-14',
328
- provider: 'twilio',
329
- fromNumber: '+15551111111',
330
- toNumber: '+15552222222',
335
+ conversationId: "conv-14",
336
+ provider: "twilio",
337
+ fromNumber: "+15551111111",
338
+ toNumber: "+15552222222",
331
339
  });
332
340
 
333
- recordCallEvent(session.id, 'call_started');
334
- recordCallEvent(session.id, 'call_connected');
335
- recordCallEvent(session.id, 'caller_spoke', { transcript: 'Hi' });
341
+ recordCallEvent(session.id, "call_started");
342
+ recordCallEvent(session.id, "call_connected");
343
+ recordCallEvent(session.id, "caller_spoke", { transcript: "Hi" });
336
344
 
337
345
  const events = getCallEvents(session.id);
338
346
  expect(events).toHaveLength(3);
339
- expect(events[0].eventType).toBe('call_started');
340
- expect(events[1].eventType).toBe('call_connected');
341
- expect(events[2].eventType).toBe('caller_spoke');
347
+ expect(events[0].eventType).toBe("call_started");
348
+ expect(events[1].eventType).toBe("call_connected");
349
+ expect(events[2].eventType).toBe("caller_spoke");
342
350
  // Should be in ascending creation order
343
351
  expect(events[0].createdAt).toBeLessThanOrEqual(events[1].createdAt);
344
352
  expect(events[1].createdAt).toBeLessThanOrEqual(events[2].createdAt);
345
353
  });
346
354
 
347
- test('getCallEvents returns empty array for session with no events', () => {
355
+ test("getCallEvents returns empty array for session with no events", () => {
348
356
  const session = createTestCallSession({
349
- conversationId: 'conv-15',
350
- provider: 'twilio',
351
- fromNumber: '+15551111111',
352
- toNumber: '+15552222222',
357
+ conversationId: "conv-15",
358
+ provider: "twilio",
359
+ fromNumber: "+15551111111",
360
+ toNumber: "+15552222222",
353
361
  });
354
362
 
355
363
  const events = getCallEvents(session.id);
@@ -358,64 +366,67 @@ describe('call-store', () => {
358
366
 
359
367
  // ── Pending Questions ─────────────────────────────────────────────
360
368
 
361
- test('createPendingQuestion creates with status pending', () => {
369
+ test("createPendingQuestion creates with status pending", () => {
362
370
  const session = createTestCallSession({
363
- conversationId: 'conv-16',
364
- provider: 'twilio',
365
- fromNumber: '+15551111111',
366
- toNumber: '+15552222222',
371
+ conversationId: "conv-16",
372
+ provider: "twilio",
373
+ fromNumber: "+15551111111",
374
+ toNumber: "+15552222222",
367
375
  });
368
376
 
369
- const question = createPendingQuestion(session.id, 'What is your preferred date?');
377
+ const question = createPendingQuestion(
378
+ session.id,
379
+ "What is your preferred date?",
380
+ );
370
381
 
371
382
  expect(question.id).toBeDefined();
372
383
  expect(question.callSessionId).toBe(session.id);
373
- expect(question.questionText).toBe('What is your preferred date?');
374
- expect(question.status).toBe('pending');
375
- expect(typeof question.askedAt).toBe('number');
384
+ expect(question.questionText).toBe("What is your preferred date?");
385
+ expect(question.status).toBe("pending");
386
+ expect(typeof question.askedAt).toBe("number");
376
387
  expect(question.answeredAt).toBeNull();
377
388
  expect(question.answerText).toBeNull();
378
389
  });
379
390
 
380
- test('getPendingQuestion finds pending question for session', () => {
391
+ test("getPendingQuestion finds pending question for session", () => {
381
392
  const session = createTestCallSession({
382
- conversationId: 'conv-17',
383
- provider: 'twilio',
384
- fromNumber: '+15551111111',
385
- toNumber: '+15552222222',
393
+ conversationId: "conv-17",
394
+ provider: "twilio",
395
+ fromNumber: "+15551111111",
396
+ toNumber: "+15552222222",
386
397
  });
387
398
 
388
- const created = createPendingQuestion(session.id, 'What is your name?');
399
+ const created = createPendingQuestion(session.id, "What is your name?");
389
400
 
390
401
  const found = getPendingQuestion(session.id);
391
402
  expect(found).not.toBeNull();
392
403
  expect(found!.id).toBe(created.id);
393
- expect(found!.questionText).toBe('What is your name?');
394
- expect(found!.status).toBe('pending');
404
+ expect(found!.questionText).toBe("What is your name?");
405
+ expect(found!.status).toBe("pending");
395
406
  });
396
407
 
397
- test('getPendingQuestion returns null when no pending questions', () => {
408
+ test("getPendingQuestion returns null when no pending questions", () => {
398
409
  const session = createTestCallSession({
399
- conversationId: 'conv-18',
400
- provider: 'twilio',
401
- fromNumber: '+15551111111',
402
- toNumber: '+15552222222',
410
+ conversationId: "conv-18",
411
+ provider: "twilio",
412
+ fromNumber: "+15551111111",
413
+ toNumber: "+15552222222",
403
414
  });
404
415
 
405
416
  const found = getPendingQuestion(session.id);
406
417
  expect(found).toBeNull();
407
418
  });
408
419
 
409
- test('answerPendingQuestion updates status to answered', () => {
420
+ test("answerPendingQuestion updates status to answered", () => {
410
421
  const session = createTestCallSession({
411
- conversationId: 'conv-19',
412
- provider: 'twilio',
413
- fromNumber: '+15551111111',
414
- toNumber: '+15552222222',
422
+ conversationId: "conv-19",
423
+ provider: "twilio",
424
+ fromNumber: "+15551111111",
425
+ toNumber: "+15552222222",
415
426
  });
416
427
 
417
- const question = createPendingQuestion(session.id, 'What color?');
418
- answerPendingQuestion(question.id, 'Blue');
428
+ const question = createPendingQuestion(session.id, "What color?");
429
+ answerPendingQuestion(question.id, "Blue");
419
430
 
420
431
  // Should no longer appear as pending
421
432
  const pending = getPendingQuestion(session.id);
@@ -423,27 +434,30 @@ describe('call-store', () => {
423
434
 
424
435
  // Verify the record was updated by querying directly
425
436
  const db = getDb();
426
- const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
427
- const updated = raw.query('SELECT * FROM call_pending_questions WHERE id = ?').get(question.id) as {
437
+ const raw = (db as unknown as { $client: import("bun:sqlite").Database })
438
+ .$client;
439
+ const updated = raw
440
+ .query("SELECT * FROM call_pending_questions WHERE id = ?")
441
+ .get(question.id) as {
428
442
  status: string;
429
443
  answer_text: string;
430
444
  answered_at: number;
431
445
  };
432
- expect(updated.status).toBe('answered');
433
- expect(updated.answer_text).toBe('Blue');
434
- expect(typeof updated.answered_at).toBe('number');
446
+ expect(updated.status).toBe("answered");
447
+ expect(updated.answer_text).toBe("Blue");
448
+ expect(typeof updated.answered_at).toBe("number");
435
449
  });
436
450
 
437
- test('expirePendingQuestions marks all pending questions as expired', () => {
451
+ test("expirePendingQuestions marks all pending questions as expired", () => {
438
452
  const session = createTestCallSession({
439
- conversationId: 'conv-20',
440
- provider: 'twilio',
441
- fromNumber: '+15551111111',
442
- toNumber: '+15552222222',
453
+ conversationId: "conv-20",
454
+ provider: "twilio",
455
+ fromNumber: "+15551111111",
456
+ toNumber: "+15552222222",
443
457
  });
444
458
 
445
- createPendingQuestion(session.id, 'Question 1');
446
- createPendingQuestion(session.id, 'Question 2');
459
+ createPendingQuestion(session.id, "Question 1");
460
+ createPendingQuestion(session.id, "Question 2");
447
461
 
448
462
  expirePendingQuestions(session.id);
449
463
 
@@ -452,241 +466,298 @@ describe('call-store', () => {
452
466
  expect(pending).toBeNull();
453
467
 
454
468
  // Verify both were expired
455
- const raw = (getDb() as unknown as { $client: import('bun:sqlite').Database }).$client;
456
- const rows = raw.query('SELECT status FROM call_pending_questions WHERE call_session_id = ?').all(session.id) as Array<{ status: string }>;
469
+ const raw = (
470
+ getDb() as unknown as {
471
+ $client: import("bun:sqlite").Database;
472
+ }
473
+ ).$client;
474
+ const rows = raw
475
+ .query(
476
+ "SELECT status FROM call_pending_questions WHERE call_session_id = ?",
477
+ )
478
+ .all(session.id) as Array<{ status: string }>;
457
479
  expect(rows).toHaveLength(2);
458
480
  for (const row of rows) {
459
- expect(row.status).toBe('expired');
481
+ expect(row.status).toBe("expired");
460
482
  }
461
483
  });
462
484
 
463
- test('expirePendingQuestions does not affect already-answered questions', () => {
485
+ test("expirePendingQuestions does not affect already-answered questions", () => {
464
486
  const session = createTestCallSession({
465
- conversationId: 'conv-21',
466
- provider: 'twilio',
467
- fromNumber: '+15551111111',
468
- toNumber: '+15552222222',
487
+ conversationId: "conv-21",
488
+ provider: "twilio",
489
+ fromNumber: "+15551111111",
490
+ toNumber: "+15552222222",
469
491
  });
470
492
 
471
- const q1 = createPendingQuestion(session.id, 'Question 1');
472
- createPendingQuestion(session.id, 'Question 2');
493
+ const q1 = createPendingQuestion(session.id, "Question 1");
494
+ createPendingQuestion(session.id, "Question 2");
473
495
 
474
496
  // Answer q1 first
475
- answerPendingQuestion(q1.id, 'Answer 1');
497
+ answerPendingQuestion(q1.id, "Answer 1");
476
498
 
477
499
  // Then expire all pending
478
500
  expirePendingQuestions(session.id);
479
501
 
480
502
  // q1 should still be answered, not expired
481
- const raw = (getDb() as unknown as { $client: import('bun:sqlite').Database }).$client;
482
- const q1Row = raw.query('SELECT status FROM call_pending_questions WHERE id = ?').get(q1.id) as { status: string };
483
- expect(q1Row.status).toBe('answered');
503
+ const raw = (
504
+ getDb() as unknown as {
505
+ $client: import("bun:sqlite").Database;
506
+ }
507
+ ).$client;
508
+ const q1Row = raw
509
+ .query("SELECT status FROM call_pending_questions WHERE id = ?")
510
+ .get(q1.id) as { status: string };
511
+ expect(q1Row.status).toBe("answered");
484
512
  });
485
513
 
486
514
  // ── Callback Claim ──────────────────────────────────────────────
487
515
 
488
- test('claimCallback returns a claim ID on first call', () => {
516
+ test("claimCallback returns a claim ID on first call", () => {
489
517
  const session = createTestCallSession({
490
- conversationId: 'conv-22',
491
- provider: 'twilio',
492
- fromNumber: '+15551111111',
493
- toNumber: '+15552222222',
518
+ conversationId: "conv-22",
519
+ provider: "twilio",
520
+ fromNumber: "+15551111111",
521
+ toNumber: "+15552222222",
494
522
  });
495
523
 
496
- const result = claimCallback('test-dedupe-key-1', session.id);
497
- expect(result).toBeTypeOf('string');
524
+ const result = claimCallback("test-dedupe-key-1", session.id);
525
+ expect(result).toBeTypeOf("string");
498
526
  expect(result!.length).toBeGreaterThan(0);
499
527
  });
500
528
 
501
- test('claimCallback returns null on duplicate key', () => {
529
+ test("claimCallback returns null on duplicate key", () => {
502
530
  const session = createTestCallSession({
503
- conversationId: 'conv-23',
504
- provider: 'twilio',
505
- fromNumber: '+15551111111',
506
- toNumber: '+15552222222',
531
+ conversationId: "conv-23",
532
+ provider: "twilio",
533
+ fromNumber: "+15551111111",
534
+ toNumber: "+15552222222",
507
535
  });
508
536
 
509
- const first = claimCallback('test-dedupe-key-2', session.id);
510
- const second = claimCallback('test-dedupe-key-2', session.id);
537
+ const first = claimCallback("test-dedupe-key-2", session.id);
538
+ const second = claimCallback("test-dedupe-key-2", session.id);
511
539
 
512
- expect(first).toBeTypeOf('string');
540
+ expect(first).toBeTypeOf("string");
513
541
  expect(second).toBeNull();
514
542
  });
515
543
 
516
- test('releaseCallbackClaim allows re-claim', () => {
544
+ test("releaseCallbackClaim allows re-claim", () => {
517
545
  const session = createTestCallSession({
518
- conversationId: 'conv-24',
519
- provider: 'twilio',
520
- fromNumber: '+15551111111',
521
- toNumber: '+15552222222',
546
+ conversationId: "conv-24",
547
+ provider: "twilio",
548
+ fromNumber: "+15551111111",
549
+ toNumber: "+15552222222",
522
550
  });
523
551
 
524
- const first = claimCallback('test-dedupe-key-3', session.id);
525
- expect(first).toBeTypeOf('string');
552
+ const first = claimCallback("test-dedupe-key-3", session.id);
553
+ expect(first).toBeTypeOf("string");
526
554
 
527
- releaseCallbackClaim('test-dedupe-key-3', first!);
555
+ releaseCallbackClaim("test-dedupe-key-3", first!);
528
556
 
529
- const second = claimCallback('test-dedupe-key-3', session.id);
530
- expect(second).toBeTypeOf('string');
557
+ const second = claimCallback("test-dedupe-key-3", session.id);
558
+ expect(second).toBeTypeOf("string");
531
559
  });
532
560
 
533
- test('releaseCallbackClaim with wrong claimId does not release', () => {
561
+ test("releaseCallbackClaim with wrong claimId does not release", () => {
534
562
  const session = createTestCallSession({
535
- conversationId: 'conv-24b',
536
- provider: 'twilio',
537
- fromNumber: '+15551111111',
538
- toNumber: '+15552222222',
563
+ conversationId: "conv-24b",
564
+ provider: "twilio",
565
+ fromNumber: "+15551111111",
566
+ toNumber: "+15552222222",
539
567
  });
540
568
 
541
- const claimId = claimCallback('test-dedupe-key-3b', session.id);
542
- expect(claimId).toBeTypeOf('string');
569
+ const claimId = claimCallback("test-dedupe-key-3b", session.id);
570
+ expect(claimId).toBeTypeOf("string");
543
571
 
544
572
  // Attempt to release with a wrong claim ID — should be a no-op
545
- releaseCallbackClaim('test-dedupe-key-3b', 'wrong-claim-id');
573
+ releaseCallbackClaim("test-dedupe-key-3b", "wrong-claim-id");
546
574
 
547
575
  // The claim should still be held, so re-claiming should fail
548
- const second = claimCallback('test-dedupe-key-3b', session.id);
576
+ const second = claimCallback("test-dedupe-key-3b", session.id);
549
577
  expect(second).toBeNull();
550
578
  });
551
579
 
552
- test('claimCallback INSERT OR IGNORE pattern is safe for same key', () => {
580
+ test("claimCallback INSERT OR IGNORE pattern is safe for same key", () => {
553
581
  const session = createTestCallSession({
554
- conversationId: 'conv-25',
555
- provider: 'twilio',
556
- fromNumber: '+15551111111',
557
- toNumber: '+15552222222',
582
+ conversationId: "conv-25",
583
+ provider: "twilio",
584
+ fromNumber: "+15551111111",
585
+ toNumber: "+15552222222",
558
586
  });
559
587
 
560
588
  // Claim the key
561
- const first = claimCallback('test-dedupe-key-4', session.id);
562
- expect(first).toBeTypeOf('string');
589
+ const first = claimCallback("test-dedupe-key-4", session.id);
590
+ expect(first).toBeTypeOf("string");
563
591
 
564
592
  // Subsequent claims with the same key should all return null without throwing
565
- expect(claimCallback('test-dedupe-key-4', session.id)).toBeNull();
566
- expect(claimCallback('test-dedupe-key-4', session.id)).toBeNull();
593
+ expect(claimCallback("test-dedupe-key-4", session.id)).toBeNull();
594
+ expect(claimCallback("test-dedupe-key-4", session.id)).toBeNull();
567
595
 
568
596
  // Only one row should exist in the table for this key
569
- const raw = (getDb() as unknown as { $client: import('bun:sqlite').Database }).$client;
570
- const rows = raw.query('SELECT COUNT(*) as cnt FROM processed_callbacks WHERE dedupe_key = ?').get('test-dedupe-key-4') as { cnt: number };
597
+ const raw = (
598
+ getDb() as unknown as {
599
+ $client: import("bun:sqlite").Database;
600
+ }
601
+ ).$client;
602
+ const rows = raw
603
+ .query(
604
+ "SELECT COUNT(*) as cnt FROM processed_callbacks WHERE dedupe_key = ?",
605
+ )
606
+ .get("test-dedupe-key-4") as { cnt: number };
571
607
  expect(rows.cnt).toBe(1);
572
608
  });
573
609
 
574
- test('claimCallback reclaims expired orphaned claims', () => {
610
+ test("claimCallback reclaims expired orphaned claims", () => {
575
611
  const session = createTestCallSession({
576
- conversationId: 'conv-26',
577
- provider: 'twilio',
578
- fromNumber: '+15551111111',
579
- toNumber: '+15552222222',
612
+ conversationId: "conv-26",
613
+ provider: "twilio",
614
+ fromNumber: "+15551111111",
615
+ toNumber: "+15552222222",
580
616
  });
581
617
 
582
618
  // Claim the key
583
- const first = claimCallback('test-dedupe-key-expired', session.id);
584
- expect(first).toBeTypeOf('string');
619
+ const first = claimCallback("test-dedupe-key-expired", session.id);
620
+ expect(first).toBeTypeOf("string");
585
621
 
586
622
  // Simulate an orphaned claim by backdating the created_at to well past expiry
587
- const raw = (getDb() as unknown as { $client: import('bun:sqlite').Database }).$client;
623
+ const raw = (
624
+ getDb() as unknown as {
625
+ $client: import("bun:sqlite").Database;
626
+ }
627
+ ).$client;
588
628
  const oldTimestamp = Date.now() - 120_000; // 2 minutes ago, well past 60s expiry
589
- raw.query('UPDATE processed_callbacks SET created_at = ? WHERE dedupe_key = ?').run(oldTimestamp, 'test-dedupe-key-expired');
629
+ raw
630
+ .query(
631
+ "UPDATE processed_callbacks SET created_at = ? WHERE dedupe_key = ?",
632
+ )
633
+ .run(oldTimestamp, "test-dedupe-key-expired");
590
634
 
591
635
  // Reclaim should succeed because the old claim has expired
592
- const second = claimCallback('test-dedupe-key-expired', session.id);
593
- expect(second).toBeTypeOf('string');
636
+ const second = claimCallback("test-dedupe-key-expired", session.id);
637
+ expect(second).toBeTypeOf("string");
594
638
 
595
639
  // The new claim should have a different claim ID
596
640
  expect(second).not.toBe(first);
597
641
  });
598
642
 
599
- test('claimCallback does not reclaim finalized claims', () => {
643
+ test("claimCallback does not reclaim finalized claims", () => {
600
644
  const session = createTestCallSession({
601
- conversationId: 'conv-27',
602
- provider: 'twilio',
603
- fromNumber: '+15551111111',
604
- toNumber: '+15552222222',
645
+ conversationId: "conv-27",
646
+ provider: "twilio",
647
+ fromNumber: "+15551111111",
648
+ toNumber: "+15552222222",
605
649
  });
606
650
 
607
651
  // Claim and finalize
608
- const first = claimCallback('test-dedupe-key-finalized', session.id);
609
- expect(first).toBeTypeOf('string');
610
- finalizeCallbackClaim('test-dedupe-key-finalized', first!);
652
+ const first = claimCallback("test-dedupe-key-finalized", session.id);
653
+ expect(first).toBeTypeOf("string");
654
+ finalizeCallbackClaim("test-dedupe-key-finalized", first!);
611
655
 
612
656
  // Attempting to reclaim a finalized key should fail because the far-future
613
657
  // timestamp means it will never be considered expired
614
- const second = claimCallback('test-dedupe-key-finalized', session.id);
658
+ const second = claimCallback("test-dedupe-key-finalized", session.id);
615
659
  expect(second).toBeNull();
616
660
  });
617
661
 
618
- test('finalizeCallbackClaim makes claim permanent', () => {
662
+ test("finalizeCallbackClaim makes claim permanent", () => {
619
663
  const session = createTestCallSession({
620
- conversationId: 'conv-28',
621
- provider: 'twilio',
622
- fromNumber: '+15551111111',
623
- toNumber: '+15552222222',
664
+ conversationId: "conv-28",
665
+ provider: "twilio",
666
+ fromNumber: "+15551111111",
667
+ toNumber: "+15552222222",
624
668
  });
625
669
 
626
670
  // Claim and finalize
627
- const claimId = claimCallback('test-dedupe-key-permanent', session.id)!;
628
- finalizeCallbackClaim('test-dedupe-key-permanent', claimId);
671
+ const claimId = claimCallback("test-dedupe-key-permanent", session.id)!;
672
+ finalizeCallbackClaim("test-dedupe-key-permanent", claimId);
629
673
 
630
674
  // Verify the created_at is set far in the future
631
- const raw = (getDb() as unknown as { $client: import('bun:sqlite').Database }).$client;
632
- const row = raw.query('SELECT created_at FROM processed_callbacks WHERE dedupe_key = ?').get('test-dedupe-key-permanent') as { created_at: number };
675
+ const raw = (
676
+ getDb() as unknown as {
677
+ $client: import("bun:sqlite").Database;
678
+ }
679
+ ).$client;
680
+ const row = raw
681
+ .query("SELECT created_at FROM processed_callbacks WHERE dedupe_key = ?")
682
+ .get("test-dedupe-key-permanent") as { created_at: number };
633
683
  // Should be at least 50 years in the future from now
634
684
  const fiftyYearsMs = 50 * 365 * 24 * 60 * 60 * 1000;
635
685
  expect(row.created_at).toBeGreaterThan(Date.now() + fiftyYearsMs);
636
686
  });
637
687
 
638
- test('finalizeCallbackClaim with wrong claimId does not finalize', () => {
688
+ test("finalizeCallbackClaim with wrong claimId does not finalize", () => {
639
689
  const session = createTestCallSession({
640
- conversationId: 'conv-28b',
641
- provider: 'twilio',
642
- fromNumber: '+15551111111',
643
- toNumber: '+15552222222',
690
+ conversationId: "conv-28b",
691
+ provider: "twilio",
692
+ fromNumber: "+15551111111",
693
+ toNumber: "+15552222222",
644
694
  });
645
695
 
646
696
  // Claim the key
647
- const claimId = claimCallback('test-dedupe-key-permanent-b', session.id)!;
648
- expect(claimId).toBeTypeOf('string');
697
+ const claimId = claimCallback("test-dedupe-key-permanent-b", session.id)!;
698
+ expect(claimId).toBeTypeOf("string");
649
699
 
650
700
  // Try to finalize with wrong claimId — should be a no-op
651
- finalizeCallbackClaim('test-dedupe-key-permanent-b', 'wrong-claim-id');
701
+ finalizeCallbackClaim("test-dedupe-key-permanent-b", "wrong-claim-id");
652
702
 
653
703
  // Verify the created_at was NOT set to far-future (it should still be close to now)
654
- const raw = (getDb() as unknown as { $client: import('bun:sqlite').Database }).$client;
655
- const row = raw.query('SELECT created_at FROM processed_callbacks WHERE dedupe_key = ?').get('test-dedupe-key-permanent-b') as { created_at: number };
704
+ const raw = (
705
+ getDb() as unknown as {
706
+ $client: import("bun:sqlite").Database;
707
+ }
708
+ ).$client;
709
+ const row = raw
710
+ .query("SELECT created_at FROM processed_callbacks WHERE dedupe_key = ?")
711
+ .get("test-dedupe-key-permanent-b") as { created_at: number };
656
712
  const oneMinuteMs = 60 * 1000;
657
713
  expect(row.created_at).toBeLessThan(Date.now() + oneMinuteMs);
658
714
  });
659
715
 
660
- test('handler A cannot release handler B claim after reclaim', () => {
716
+ test("handler A cannot release handler B claim after reclaim", () => {
661
717
  const session = createTestCallSession({
662
- conversationId: 'conv-29',
663
- provider: 'twilio',
664
- fromNumber: '+15551111111',
665
- toNumber: '+15552222222',
718
+ conversationId: "conv-29",
719
+ provider: "twilio",
720
+ fromNumber: "+15551111111",
721
+ toNumber: "+15552222222",
666
722
  });
667
723
 
668
724
  // Handler A claims
669
- const claimA = claimCallback('test-dedupe-key-ownership', session.id)!;
670
- expect(claimA).toBeTypeOf('string');
725
+ const claimA = claimCallback("test-dedupe-key-ownership", session.id)!;
726
+ expect(claimA).toBeTypeOf("string");
671
727
 
672
728
  // Simulate handler A taking too long: backdate the claim so it expires
673
- const raw = (getDb() as unknown as { $client: import('bun:sqlite').Database }).$client;
729
+ const raw = (
730
+ getDb() as unknown as {
731
+ $client: import("bun:sqlite").Database;
732
+ }
733
+ ).$client;
674
734
  const oldTimestamp = Date.now() - 120_000;
675
- raw.query('UPDATE processed_callbacks SET created_at = ? WHERE dedupe_key = ?').run(oldTimestamp, 'test-dedupe-key-ownership');
735
+ raw
736
+ .query(
737
+ "UPDATE processed_callbacks SET created_at = ? WHERE dedupe_key = ?",
738
+ )
739
+ .run(oldTimestamp, "test-dedupe-key-ownership");
676
740
 
677
741
  // Handler B reclaims (succeeds because the old claim expired)
678
- const claimB = claimCallback('test-dedupe-key-ownership', session.id)!;
679
- expect(claimB).toBeTypeOf('string');
742
+ const claimB = claimCallback("test-dedupe-key-ownership", session.id)!;
743
+ expect(claimB).toBeTypeOf("string");
680
744
  expect(claimB).not.toBe(claimA);
681
745
 
682
746
  // Handler B finalizes
683
- finalizeCallbackClaim('test-dedupe-key-ownership', claimB);
747
+ finalizeCallbackClaim("test-dedupe-key-ownership", claimB);
684
748
 
685
749
  // Handler A tries to release using its old claimId — should be a no-op
686
- releaseCallbackClaim('test-dedupe-key-ownership', claimA);
750
+ releaseCallbackClaim("test-dedupe-key-ownership", claimA);
687
751
 
688
752
  // Verify B's finalized claim is still intact
689
- const row = raw.query('SELECT created_at, claim_id FROM processed_callbacks WHERE dedupe_key = ?').get('test-dedupe-key-ownership') as { created_at: number; claim_id: string };
753
+ const row = raw
754
+ .query(
755
+ "SELECT created_at, claim_id FROM processed_callbacks WHERE dedupe_key = ?",
756
+ )
757
+ .get("test-dedupe-key-ownership") as {
758
+ created_at: number;
759
+ claim_id: string;
760
+ };
690
761
  expect(row).not.toBeNull();
691
762
  expect(row.claim_id).toBe(claimB);
692
763
  const fiftyYearsMs = 50 * 365 * 24 * 60 * 60 * 1000;