@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.
- package/eslint.config.mjs +2 -2
- package/package.json +1 -1
- package/src/__tests__/access-request-decision.test.ts +128 -120
- package/src/__tests__/account-registry.test.ts +121 -110
- package/src/__tests__/active-skill-tools.test.ts +200 -172
- package/src/__tests__/actor-token-service.test.ts +341 -274
- package/src/__tests__/agent-loop-thinking.test.ts +28 -19
- package/src/__tests__/agent-loop.test.ts +798 -378
- package/src/__tests__/anthropic-provider.test.ts +405 -247
- package/src/__tests__/app-builder-tool-scripts.test.ts +97 -97
- package/src/__tests__/app-bundler.test.ts +112 -79
- package/src/__tests__/app-executors.test.ts +205 -178
- package/src/__tests__/app-git-history.test.ts +90 -73
- package/src/__tests__/app-git-service.test.ts +67 -53
- package/src/__tests__/app-open-proxy.test.ts +29 -25
- package/src/__tests__/approval-conversation-turn.test.ts +100 -81
- package/src/__tests__/approval-hardcoded-copy-guard.test.ts +45 -17
- package/src/__tests__/approval-message-composer.test.ts +119 -119
- package/src/__tests__/approval-primitive.test.ts +264 -233
- package/src/__tests__/approval-routes-http.test.ts +4 -3
- package/src/__tests__/asset-materialize-tool.test.ts +250 -178
- package/src/__tests__/asset-search-tool.test.ts +251 -191
- package/src/__tests__/assistant-attachment-directive.test.ts +187 -142
- package/src/__tests__/assistant-attachments.test.ts +254 -186
- package/src/__tests__/assistant-event-hub.test.ts +105 -63
- package/src/__tests__/assistant-event.test.ts +66 -58
- package/src/__tests__/assistant-events-sse-hardening.test.ts +113 -73
- package/src/__tests__/assistant-feature-flag-guard.test.ts +78 -52
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +48 -45
- package/src/__tests__/assistant-feature-flags-integration.test.ts +118 -77
- package/src/__tests__/assistant-id-boundary-guard.test.ts +158 -104
- package/src/__tests__/attachments-store.test.ts +240 -183
- package/src/__tests__/attachments.test.ts +70 -62
- package/src/__tests__/audit-log-rotation.test.ts +50 -35
- package/src/__tests__/browser-fill-credential.test.ts +169 -101
- package/src/__tests__/browser-manager.test.ts +97 -75
- package/src/__tests__/browser-runtime-check.test.ts +16 -15
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +12 -10
- package/src/__tests__/browser-skill-endstate.test.ts +97 -72
- package/src/__tests__/bundle-scanner.test.ts +47 -22
- package/src/__tests__/bundled-asset.test.ts +74 -47
- package/src/__tests__/call-constants.test.ts +19 -19
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/call-conversation-messages.test.ts +90 -65
- package/src/__tests__/call-domain.test.ts +149 -121
- package/src/__tests__/call-pointer-message-composer.test.ts +113 -83
- package/src/__tests__/call-pointer-messages.test.ts +213 -154
- package/src/__tests__/call-pointer-no-hardcoded-copy.guard.test.ts +9 -10
- package/src/__tests__/call-recovery.test.ts +232 -212
- package/src/__tests__/call-routes-http.test.ts +0 -1
- package/src/__tests__/call-start-guardian-guard.test.ts +32 -30
- package/src/__tests__/call-state-machine.test.ts +62 -51
- package/src/__tests__/call-state.test.ts +89 -75
- package/src/__tests__/call-store.test.ts +387 -316
- package/src/__tests__/callback-handoff-copy.test.ts +84 -82
- package/src/__tests__/canonical-guardian-store.test.ts +331 -280
- package/src/__tests__/channel-approval-routes.test.ts +1643 -1115
- package/src/__tests__/channel-approval.test.ts +139 -137
- package/src/__tests__/channel-approvals.test.ts +0 -1
- package/src/__tests__/channel-delivery-store.test.ts +232 -194
- package/src/__tests__/channel-guardian.test.ts +5 -3
- package/src/__tests__/channel-invite-transport.test.ts +107 -92
- package/src/__tests__/channel-policy.test.ts +42 -38
- package/src/__tests__/channel-readiness-service.test.ts +119 -102
- package/src/__tests__/channel-reply-delivery.test.ts +147 -118
- package/src/__tests__/channel-retry-sweep.test.ts +153 -110
- package/src/__tests__/checker.test.ts +3309 -1850
- package/src/__tests__/clarification-resolver.test.ts +91 -79
- package/src/__tests__/classifier.test.ts +64 -54
- package/src/__tests__/claude-code-skill-regression.test.ts +42 -37
- package/src/__tests__/claude-code-tool-profiles.test.ts +31 -29
- package/src/__tests__/clawhub.test.ts +92 -82
- package/src/__tests__/cli.test.ts +30 -30
- package/src/__tests__/clipboard.test.ts +53 -46
- package/src/__tests__/commit-guarantee.test.ts +59 -52
- package/src/__tests__/commit-message-enrichment-service.test.ts +203 -75
- package/src/__tests__/compaction.benchmark.test.ts +33 -31
- package/src/__tests__/computer-use-session-compaction.test.ts +60 -50
- package/src/__tests__/computer-use-session-lifecycle.test.ts +145 -117
- package/src/__tests__/computer-use-session-working-dir.test.ts +62 -48
- package/src/__tests__/computer-use-skill-baseline.test.ts +22 -19
- package/src/__tests__/computer-use-skill-endstate.test.ts +45 -31
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +121 -88
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +65 -42
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +33 -18
- package/src/__tests__/computer-use-tools.test.ts +121 -98
- package/src/__tests__/config-schema.test.ts +443 -347
- package/src/__tests__/config-watcher.test.ts +96 -81
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +148 -133
- package/src/__tests__/conflict-intent-tokenization.test.ts +96 -78
- package/src/__tests__/conflict-policy.test.ts +151 -80
- package/src/__tests__/conflict-store.test.ts +203 -157
- package/src/__tests__/connection-policy.test.ts +89 -59
- package/src/__tests__/contacts-tools.test.ts +247 -178
- package/src/__tests__/context-memory-e2e.test.ts +306 -214
- package/src/__tests__/context-token-estimator.test.ts +114 -74
- package/src/__tests__/context-window-manager.test.ts +269 -167
- package/src/__tests__/contradiction-checker.test.ts +161 -135
- package/src/__tests__/conversation-attention-store.test.ts +350 -290
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-pairing.test.ts +220 -113
- package/src/__tests__/conversation-store.test.ts +390 -235
- package/src/__tests__/credential-broker-browser-fill.test.ts +325 -250
- package/src/__tests__/credential-broker-server-use.test.ts +283 -243
- package/src/__tests__/credential-broker.test.ts +128 -74
- package/src/__tests__/credential-host-pattern-match.test.ts +64 -44
- package/src/__tests__/credential-metadata-store.test.ts +360 -311
- package/src/__tests__/credential-policy-validate.test.ts +81 -65
- package/src/__tests__/credential-resolve.test.ts +212 -145
- package/src/__tests__/credential-security-e2e.test.ts +144 -103
- package/src/__tests__/credential-security-invariants.test.ts +253 -208
- package/src/__tests__/credential-selection.test.ts +254 -146
- package/src/__tests__/credential-vault-unit.test.ts +531 -341
- package/src/__tests__/credential-vault.test.ts +761 -484
- package/src/__tests__/daemon-assistant-events.test.ts +91 -66
- package/src/__tests__/daemon-lifecycle.test.ts +258 -190
- package/src/__tests__/daemon-server-session-init.test.ts +0 -1
- package/src/__tests__/date-context.test.ts +314 -249
- package/src/__tests__/db-migration-rollback.test.ts +259 -130
- package/src/__tests__/db-schedule-syntax-migration.test.ts +78 -41
- package/src/__tests__/delete-managed-skill-tool.test.ts +77 -53
- package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -1
- package/src/__tests__/dictation-mode-detection.test.ts +77 -55
- package/src/__tests__/dictation-profile-store.test.ts +70 -56
- package/src/__tests__/dictation-text-processing.test.ts +53 -35
- package/src/__tests__/diff.test.ts +102 -98
- package/src/__tests__/domain-normalize.test.ts +54 -54
- package/src/__tests__/domain-policy.test.ts +71 -55
- package/src/__tests__/dynamic-page-surface.test.ts +31 -33
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +69 -69
- package/src/__tests__/edit-engine.test.ts +56 -56
- package/src/__tests__/elevenlabs-client.test.ts +117 -91
- package/src/__tests__/elevenlabs-config.test.ts +32 -31
- package/src/__tests__/email-classifier.test.ts +15 -12
- package/src/__tests__/email-cli.test.ts +121 -108
- package/src/__tests__/emit-signal-routing-intent.test.ts +76 -69
- package/src/__tests__/encrypted-store.test.ts +180 -154
- package/src/__tests__/entity-extractor.test.ts +108 -87
- package/src/__tests__/entity-search.test.ts +664 -258
- package/src/__tests__/ephemeral-permissions.test.ts +224 -188
- package/src/__tests__/event-bus.test.ts +81 -77
- package/src/__tests__/extract-email.test.ts +29 -20
- package/src/__tests__/file-edit-tool.test.ts +62 -44
- package/src/__tests__/file-ops-service.test.ts +131 -114
- package/src/__tests__/file-read-tool.test.ts +48 -31
- package/src/__tests__/file-write-tool.test.ts +43 -37
- package/src/__tests__/filesystem-tools.test.ts +238 -209
- package/src/__tests__/followup-tools.test.ts +237 -162
- package/src/__tests__/forbidden-legacy-symbols.test.ts +19 -20
- package/src/__tests__/frontmatter.test.ts +96 -81
- package/src/__tests__/fuzzy-match-property.test.ts +75 -81
- package/src/__tests__/fuzzy-match.test.ts +71 -65
- package/src/__tests__/gateway-client-managed-outbound.test.ts +76 -57
- package/src/__tests__/gateway-only-enforcement.test.ts +0 -1
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/gemini-image-service.test.ts +113 -100
- package/src/__tests__/gemini-provider.test.ts +297 -220
- package/src/__tests__/get-weather.test.ts +188 -114
- package/src/__tests__/gmail-integration.test.ts +0 -1
- package/src/__tests__/guardian-action-conversation-turn.test.ts +226 -171
- package/src/__tests__/guardian-action-copy-generator.test.ts +111 -93
- package/src/__tests__/guardian-action-followup-executor.test.ts +0 -1
- package/src/__tests__/guardian-action-followup-store.test.ts +199 -167
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +297 -250
- package/src/__tests__/guardian-action-late-reply.test.ts +462 -316
- package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +23 -18
- package/src/__tests__/guardian-action-store.test.ts +158 -109
- package/src/__tests__/guardian-action-sweep.test.ts +114 -100
- package/src/__tests__/guardian-actions-endpoint.test.ts +440 -256
- package/src/__tests__/guardian-control-plane-policy.test.ts +497 -331
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +217 -215
- package/src/__tests__/guardian-dispatch.test.ts +316 -256
- package/src/__tests__/guardian-grant-minting.test.ts +247 -178
- package/src/__tests__/guardian-outbound-http.test.ts +5 -3
- package/src/__tests__/guardian-principal-id-roundtrip.test.ts +99 -96
- package/src/__tests__/guardian-question-copy.test.ts +17 -17
- package/src/__tests__/guardian-question-mode.test.ts +134 -100
- package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
- package/src/__tests__/guardian-routing-state.test.ts +0 -1
- package/src/__tests__/guardian-verification-intent-routing.test.ts +94 -88
- package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +0 -1
- package/src/__tests__/handle-user-message-secret-resume.test.ts +0 -1
- package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +92 -76
- package/src/__tests__/handlers-cu-observation-blob.test.ts +103 -70
- package/src/__tests__/handlers-ipc-blob-probe.test.ts +77 -51
- package/src/__tests__/handlers-slack-config.test.ts +63 -54
- package/src/__tests__/handlers-task-submit-slash.test.ts +18 -18
- package/src/__tests__/handlers-telegram-config.test.ts +662 -329
- package/src/__tests__/handlers-twitter-config.test.ts +525 -298
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -2
- package/src/__tests__/headless-browser-interactions.test.ts +444 -280
- package/src/__tests__/headless-browser-navigate.test.ts +116 -79
- package/src/__tests__/headless-browser-read-tools.test.ts +123 -86
- package/src/__tests__/headless-browser-snapshot.test.ts +71 -56
- package/src/__tests__/heartbeat-service.test.ts +76 -58
- package/src/__tests__/history-repair-observability.test.ts +14 -14
- package/src/__tests__/history-repair.test.ts +171 -167
- package/src/__tests__/home-base-bootstrap.test.ts +30 -27
- package/src/__tests__/hooks-blocking.test.ts +86 -37
- package/src/__tests__/hooks-cli.test.ts +104 -68
- package/src/__tests__/hooks-config.test.ts +81 -43
- package/src/__tests__/hooks-discovery.test.ts +106 -96
- package/src/__tests__/hooks-integration.test.ts +78 -72
- package/src/__tests__/hooks-manager.test.ts +99 -61
- package/src/__tests__/hooks-runner.test.ts +94 -71
- package/src/__tests__/hooks-settings.test.ts +69 -64
- package/src/__tests__/hooks-templates.test.ts +85 -54
- package/src/__tests__/hooks-ts-runner.test.ts +82 -45
- package/src/__tests__/hooks-watch.test.ts +32 -22
- package/src/__tests__/host-file-edit-tool.test.ts +190 -148
- package/src/__tests__/host-file-read-tool.test.ts +86 -63
- package/src/__tests__/host-file-write-tool.test.ts +98 -64
- package/src/__tests__/host-shell-tool.test.ts +342 -233
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -1
- package/src/__tests__/ingress-member-store.test.ts +163 -159
- package/src/__tests__/ingress-reconcile.test.ts +0 -1
- package/src/__tests__/ingress-routes-http.test.ts +441 -356
- package/src/__tests__/ingress-url-consistency.test.ts +125 -64
- package/src/__tests__/integration-status.test.ts +93 -73
- package/src/__tests__/intent-routing.test.ts +148 -118
- package/src/__tests__/invite-redemption-service.test.ts +163 -121
- package/src/__tests__/ipc-blob-store.test.ts +104 -91
- package/src/__tests__/ipc-contract-inventory.test.ts +27 -15
- package/src/__tests__/ipc-contract.test.ts +24 -23
- package/src/__tests__/ipc-protocol.test.ts +52 -46
- package/src/__tests__/ipc-roundtrip.benchmark.test.ts +61 -50
- package/src/__tests__/ipc-snapshot.test.ts +1135 -1056
- package/src/__tests__/ipc-validate.test.ts +240 -179
- package/src/__tests__/key-migration.test.ts +123 -90
- package/src/__tests__/keychain.test.ts +150 -123
- package/src/__tests__/lifecycle-docs-guard.test.ts +65 -64
- package/src/__tests__/llm-usage-store.test.ts +112 -87
- package/src/__tests__/managed-skill-lifecycle.test.ts +147 -108
- package/src/__tests__/managed-store.test.ts +411 -360
- package/src/__tests__/mcp-cli.test.ts +189 -123
- package/src/__tests__/mcp-health-check.test.ts +26 -21
- package/src/__tests__/media-generate-image.test.ts +122 -99
- package/src/__tests__/media-reuse-story.e2e.test.ts +282 -214
- package/src/__tests__/media-visibility-policy.test.ts +86 -38
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +146 -100
- package/src/__tests__/memory-lifecycle-e2e.test.ts +385 -297
- package/src/__tests__/memory-query-builder.test.ts +32 -33
- package/src/__tests__/memory-recall-quality.test.ts +761 -407
- package/src/__tests__/memory-regressions.experimental.test.ts +443 -380
- package/src/__tests__/memory-regressions.test.ts +3725 -2642
- package/src/__tests__/memory-retrieval-budget.test.ts +7 -8
- package/src/__tests__/memory-retrieval.benchmark.test.ts +144 -109
- package/src/__tests__/memory-upsert-concurrency.test.ts +292 -201
- package/src/__tests__/messaging-send-tool.test.ts +36 -29
- package/src/__tests__/migration-cli-flows.test.ts +69 -53
- package/src/__tests__/migration-ordering.test.ts +103 -86
- package/src/__tests__/mime-builder.test.ts +55 -32
- package/src/__tests__/mock-signup-server.test.ts +384 -246
- package/src/__tests__/model-intents.test.ts +61 -37
- package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +9 -12
- package/src/__tests__/no-is-trusted-guard.test.ts +24 -21
- package/src/__tests__/non-member-access-request.test.ts +3 -2
- package/src/__tests__/notification-broadcaster.test.ts +99 -81
- package/src/__tests__/notification-decision-fallback.test.ts +223 -178
- package/src/__tests__/notification-decision-strategy.test.ts +375 -337
- package/src/__tests__/notification-deep-link.test.ts +67 -61
- package/src/__tests__/notification-guardian-path.test.ts +248 -206
- package/src/__tests__/notification-routing-intent.test.ts +166 -93
- package/src/__tests__/notification-thread-candidate-validation.test.ts +78 -75
- package/src/__tests__/notification-thread-candidates.test.ts +64 -61
- package/src/__tests__/oauth-callback-registry.test.ts +40 -30
- package/src/__tests__/oauth-connect-handler.test.ts +109 -89
- package/src/__tests__/oauth-scope-policy.test.ts +63 -55
- package/src/__tests__/oauth2-gateway-transport.test.ts +252 -174
- package/src/__tests__/onboarding-starter-tasks.test.ts +93 -89
- package/src/__tests__/onboarding-template-contract.test.ts +93 -94
- package/src/__tests__/openai-provider.test.ts +366 -274
- package/src/__tests__/pairing-concurrent.test.ts +18 -12
- package/src/__tests__/pairing-routes.test.ts +45 -41
- package/src/__tests__/parallel-tool.benchmark.test.ts +108 -58
- package/src/__tests__/parser.test.ts +316 -226
- package/src/__tests__/path-classifier.test.ts +24 -25
- package/src/__tests__/path-policy.test.ts +187 -147
- package/src/__tests__/phone.test.ts +36 -36
- package/src/__tests__/platform-move-helper.test.ts +48 -40
- package/src/__tests__/platform-socket-path.test.ts +23 -24
- package/src/__tests__/platform-workspace-migration.test.ts +464 -414
- package/src/__tests__/platform.test.ts +61 -53
- package/src/__tests__/playbook-execution.test.ts +397 -265
- package/src/__tests__/playbook-tools.test.ts +267 -196
- package/src/__tests__/prebuilt-home-base-seed.test.ts +30 -27
- package/src/__tests__/pricing.test.ts +316 -136
- package/src/__tests__/profile-compiler.test.ts +206 -188
- package/src/__tests__/provider-commit-message-generator.test.ts +114 -106
- package/src/__tests__/provider-error-scenarios.test.ts +212 -158
- package/src/__tests__/provider-fail-open-selection.test.ts +51 -44
- package/src/__tests__/provider-registry-ollama.test.ts +13 -9
- package/src/__tests__/provider-streaming.benchmark.test.ts +232 -183
- package/src/__tests__/proxy-approval-callback.test.ts +180 -119
- package/src/__tests__/public-ingress-urls.test.ts +112 -94
- package/src/__tests__/qdrant-manager.test.ts +147 -98
- package/src/__tests__/ratelimit.test.ts +152 -82
- package/src/__tests__/recording-handler.test.ts +273 -151
- package/src/__tests__/recording-intent-fallback.test.ts +94 -75
- package/src/__tests__/recording-intent-handler.test.ts +0 -1
- package/src/__tests__/recording-intent.test.ts +578 -379
- package/src/__tests__/recording-state-machine.test.ts +530 -316
- package/src/__tests__/recurrence-engine-rruleset.test.ts +150 -92
- package/src/__tests__/recurrence-engine.test.ts +81 -41
- package/src/__tests__/recurrence-types.test.ts +63 -44
- package/src/__tests__/relay-server.test.ts +2131 -1602
- package/src/__tests__/reminder-store.test.ts +158 -80
- package/src/__tests__/reminder.test.ts +113 -109
- package/src/__tests__/remote-skill-policy.test.ts +96 -72
- package/src/__tests__/request-file-tool.test.ts +74 -67
- package/src/__tests__/response-tier.test.ts +131 -74
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
- package/src/__tests__/runtime-events-sse-parity.test.ts +167 -145
- package/src/__tests__/runtime-events-sse.test.ts +0 -1
- package/src/__tests__/sandbox-diagnostics.test.ts +66 -56
- package/src/__tests__/sandbox-host-parity.test.ts +377 -301
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +213 -161
- package/src/__tests__/schedule-store.test.ts +268 -205
- package/src/__tests__/schedule-tools.test.ts +702 -524
- package/src/__tests__/scheduler-recurrence.test.ts +240 -130
- package/src/__tests__/scoped-approval-grants.test.ts +258 -168
- package/src/__tests__/scoped-grant-security-matrix.test.ts +160 -146
- package/src/__tests__/script-proxy-certs.test.ts +38 -35
- package/src/__tests__/script-proxy-connect-tunnel.test.ts +71 -46
- package/src/__tests__/script-proxy-decision-trace.test.ts +161 -84
- package/src/__tests__/script-proxy-http-forwarder.test.ts +146 -129
- package/src/__tests__/script-proxy-injection-runtime.test.ts +139 -113
- package/src/__tests__/script-proxy-mitm-handler.test.ts +226 -142
- package/src/__tests__/script-proxy-policy-runtime.test.ts +126 -86
- package/src/__tests__/script-proxy-policy.test.ts +308 -153
- package/src/__tests__/script-proxy-rewrite-specificity.test.ts +74 -62
- package/src/__tests__/script-proxy-router.test.ts +111 -77
- package/src/__tests__/script-proxy-session-manager.test.ts +156 -113
- package/src/__tests__/script-proxy-session-runtime.test.ts +28 -24
- package/src/__tests__/secret-allowlist.test.ts +105 -90
- package/src/__tests__/secret-ingress-handler.test.ts +41 -30
- package/src/__tests__/secret-onetime-send.test.ts +67 -50
- package/src/__tests__/secret-prompt-log-hygiene.test.ts +35 -31
- package/src/__tests__/secret-response-routing.test.ts +50 -41
- package/src/__tests__/secret-scanner-executor.test.ts +152 -111
- package/src/__tests__/secret-scanner.test.ts +495 -413
- package/src/__tests__/secure-keys.test.ts +132 -121
- package/src/__tests__/send-endpoint-busy.test.ts +0 -1
- package/src/__tests__/send-notification-tool.test.ts +43 -42
- package/src/__tests__/sensitive-output-placeholders.test.ts +72 -64
- package/src/__tests__/sequence-store.test.ts +335 -167
- package/src/__tests__/server-history-render.test.ts +341 -202
- package/src/__tests__/session-abort-tool-results.test.ts +133 -70
- package/src/__tests__/session-confirmation-signals.test.ts +252 -160
- package/src/__tests__/session-conflict-gate.test.ts +775 -585
- package/src/__tests__/session-error.test.ts +222 -191
- package/src/__tests__/session-evictor.test.ts +79 -62
- package/src/__tests__/session-init.benchmark.test.ts +170 -108
- package/src/__tests__/session-load-history-repair.test.ts +273 -139
- package/src/__tests__/session-messaging-secret-redirect.test.ts +130 -90
- package/src/__tests__/session-pre-run-repair.test.ts +106 -59
- package/src/__tests__/session-profile-injection.test.ts +198 -130
- package/src/__tests__/session-provider-retry-repair.test.ts +223 -141
- package/src/__tests__/session-queue.test.ts +624 -321
- package/src/__tests__/session-runtime-assembly.test.ts +425 -329
- package/src/__tests__/session-runtime-workspace.test.ts +69 -61
- package/src/__tests__/session-skill-tools.test.ts +973 -678
- package/src/__tests__/session-slash-known.test.ts +185 -133
- package/src/__tests__/session-slash-queue.test.ts +147 -81
- package/src/__tests__/session-slash-unknown.test.ts +135 -90
- package/src/__tests__/session-surfaces-task-progress.test.ts +122 -87
- package/src/__tests__/session-tool-setup-app-refresh.test.ts +338 -177
- package/src/__tests__/session-tool-setup-memory-scope.test.ts +63 -40
- package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +60 -37
- package/src/__tests__/session-tool-setup-tools-disabled.test.ts +28 -26
- package/src/__tests__/session-undo.test.ts +43 -30
- package/src/__tests__/session-workspace-cache-state.test.ts +108 -67
- package/src/__tests__/session-workspace-injection.test.ts +245 -117
- package/src/__tests__/session-workspace-tool-tracking.test.ts +260 -93
- package/src/__tests__/shared-filesystem-errors.test.ts +47 -47
- package/src/__tests__/shell-credential-ref.test.ts +126 -90
- package/src/__tests__/shell-identity.test.ts +134 -111
- package/src/__tests__/shell-parser-fuzz.test.ts +263 -179
- package/src/__tests__/shell-parser-property.test.ts +435 -288
- package/src/__tests__/shell-tool-proxy-mode.test.ts +142 -70
- package/src/__tests__/size-guard.test.ts +42 -44
- package/src/__tests__/skill-feature-flags-integration.test.ts +79 -52
- package/src/__tests__/skill-feature-flags.test.ts +75 -47
- package/src/__tests__/skill-include-graph.test.ts +143 -148
- package/src/__tests__/skill-load-feature-flag.test.ts +94 -59
- package/src/__tests__/skill-load-tool.test.ts +371 -199
- package/src/__tests__/skill-projection-feature-flag.test.ts +131 -88
- package/src/__tests__/skill-projection.benchmark.test.ts +93 -65
- package/src/__tests__/skill-script-runner-host.test.ts +460 -250
- package/src/__tests__/skill-script-runner-sandbox.test.ts +168 -108
- package/src/__tests__/skill-script-runner.test.ts +115 -74
- package/src/__tests__/skill-tool-factory.test.ts +140 -96
- package/src/__tests__/skill-tool-manifest.test.ts +306 -210
- package/src/__tests__/skill-version-hash.test.ts +70 -56
- package/src/__tests__/skills.test.ts +0 -1
- package/src/__tests__/slack-channel-config.test.ts +127 -84
- package/src/__tests__/slack-skill.test.ts +60 -47
- package/src/__tests__/slash-commands-catalog.test.ts +37 -31
- package/src/__tests__/slash-commands-parser.test.ts +71 -64
- package/src/__tests__/slash-commands-resolver.test.ts +143 -107
- package/src/__tests__/slash-commands-rewrite.test.ts +22 -22
- package/src/__tests__/speaker-identification.test.ts +28 -25
- package/src/__tests__/starter-bundle.test.ts +27 -23
- package/src/__tests__/starter-task-flow.test.ts +67 -52
- package/src/__tests__/subagent-manager-notify.test.ts +154 -108
- package/src/__tests__/subagent-tools.test.ts +311 -270
- package/src/__tests__/subagent-types.test.ts +40 -40
- package/src/__tests__/surface-mutex-cleanup.test.ts +42 -30
- package/src/__tests__/swarm-dag-pathological.test.ts +122 -111
- package/src/__tests__/swarm-orchestrator.test.ts +135 -101
- package/src/__tests__/swarm-plan-validator.test.ts +125 -73
- package/src/__tests__/swarm-recursion.test.ts +58 -46
- package/src/__tests__/swarm-router-planner.test.ts +99 -74
- package/src/__tests__/swarm-session-integration.test.ts +148 -91
- package/src/__tests__/swarm-tool.test.ts +65 -45
- package/src/__tests__/swarm-worker-backend.test.ts +59 -45
- package/src/__tests__/swarm-worker-runner.test.ts +133 -118
- package/src/__tests__/system-prompt.test.ts +290 -256
- package/src/__tests__/task-compiler.test.ts +176 -120
- package/src/__tests__/task-management-tools.test.ts +561 -456
- package/src/__tests__/task-memory-cleanup.test.ts +627 -362
- package/src/__tests__/task-runner.test.ts +117 -94
- package/src/__tests__/task-scheduler.test.ts +113 -84
- package/src/__tests__/task-tools.test.ts +349 -264
- package/src/__tests__/terminal-sandbox.test.ts +138 -108
- package/src/__tests__/terminal-tools.test.ts +350 -305
- package/src/__tests__/thread-seed-composer.test.ts +307 -180
- package/src/__tests__/tool-approval-handler.test.ts +238 -137
- package/src/__tests__/tool-audit-listener.test.ts +69 -69
- package/src/__tests__/tool-domain-event-publisher.test.ts +142 -132
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +153 -146
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +136 -105
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +355 -239
- package/src/__tests__/tool-executor-redaction.test.ts +112 -109
- package/src/__tests__/tool-executor-shell-integration.test.ts +130 -79
- package/src/__tests__/tool-executor.test.ts +1274 -674
- package/src/__tests__/tool-grant-request-escalation.test.ts +401 -283
- package/src/__tests__/tool-metrics-listener.test.ts +97 -85
- package/src/__tests__/tool-notification-listener.test.ts +42 -25
- package/src/__tests__/tool-permission-simulate-handler.test.ts +137 -113
- package/src/__tests__/tool-policy.test.ts +44 -25
- package/src/__tests__/tool-profiling-listener.test.ts +99 -93
- package/src/__tests__/tool-result-truncation.test.ts +5 -4
- package/src/__tests__/tool-trace-listener.test.ts +131 -111
- package/src/__tests__/top-level-renderer.test.ts +62 -58
- package/src/__tests__/top-level-scanner.test.ts +68 -64
- package/src/__tests__/trace-emitter.test.ts +56 -56
- package/src/__tests__/trust-context-guards.test.ts +65 -65
- package/src/__tests__/trust-store.test.ts +1239 -806
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +3 -2
- package/src/__tests__/trusted-contact-multichannel.test.ts +3 -2
- package/src/__tests__/trusted-contact-verification.test.ts +251 -231
- package/src/__tests__/turn-commit.test.ts +259 -200
- package/src/__tests__/twilio-provider.test.ts +140 -126
- package/src/__tests__/twilio-rest.test.ts +22 -18
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +0 -1
- package/src/__tests__/twilio-routes-twiml.test.ts +55 -55
- package/src/__tests__/twilio-routes.test.ts +0 -1
- package/src/__tests__/twitter-auth-handler.test.ts +184 -139
- package/src/__tests__/twitter-cli-error-shaping.test.ts +88 -73
- package/src/__tests__/twitter-cli-routing.test.ts +146 -99
- package/src/__tests__/twitter-oauth-client.test.ts +82 -65
- package/src/__tests__/update-bulletin-format.test.ts +69 -66
- package/src/__tests__/update-bulletin-state.test.ts +66 -60
- package/src/__tests__/update-bulletin.test.ts +150 -114
- package/src/__tests__/update-template-contract.test.ts +15 -10
- package/src/__tests__/url-safety.test.ts +288 -265
- package/src/__tests__/user-reference.test.ts +32 -32
- package/src/__tests__/view-image-tool.test.ts +118 -96
- package/src/__tests__/voice-invite-redemption.test.ts +111 -106
- package/src/__tests__/voice-quality.test.ts +117 -102
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +204 -146
- package/src/__tests__/voice-session-bridge.test.ts +351 -216
- package/src/__tests__/weather-skill-regression.test.ts +170 -120
- package/src/__tests__/web-fetch.test.ts +664 -526
- package/src/__tests__/web-search.test.ts +379 -213
- package/src/__tests__/work-item-output.test.ts +90 -53
- package/src/__tests__/workspace-git-service.test.ts +437 -356
- package/src/__tests__/workspace-heartbeat-service.test.ts +125 -91
- package/src/__tests__/workspace-lifecycle.test.ts +98 -64
- package/src/__tests__/workspace-policy.test.ts +139 -71
- package/src/commands/__tests__/cc-command-registry.test.ts +142 -134
- package/src/config/__tests__/feature-flag-registry-guard.test.ts +48 -39
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +25 -10
- package/src/config/bundled-skills/doordash/__tests__/doordash-session.test.ts +0 -1
- package/src/config/bundled-skills/messaging/SKILL.md +4 -3
- package/src/config/bundled-skills/messaging/tools/gmail-outreach-scan.ts +15 -5
- package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +16 -5
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +34 -32
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/env.ts +3 -4
- package/src/memory/db-connection.ts +16 -10
- package/src/messaging/providers/gmail/adapter.ts +10 -3
- package/src/messaging/providers/gmail/client.ts +280 -72
- package/src/runtime/auth/__tests__/context.test.ts +75 -65
- package/src/runtime/auth/__tests__/credential-service.test.ts +137 -114
- package/src/runtime/auth/__tests__/guard-tests.test.ts +84 -90
- package/src/runtime/auth/__tests__/ipc-auth-context.test.ts +40 -40
- package/src/runtime/auth/__tests__/middleware.test.ts +80 -74
- package/src/runtime/auth/__tests__/policy.test.ts +9 -9
- package/src/runtime/auth/__tests__/route-policy.test.ts +76 -65
- package/src/runtime/auth/__tests__/scopes.test.ts +68 -60
- package/src/runtime/auth/__tests__/subject.test.ts +54 -54
- package/src/runtime/auth/__tests__/token-service.test.ts +115 -108
- package/src/runtime/auth/scopes.ts +3 -0
- package/src/runtime/auth/token-service.ts +4 -1
- package/src/runtime/auth/types.ts +2 -1
- package/src/runtime/http-server.ts +2 -1
- package/src/security/secure-keys.ts +103 -53
- package/src/tools/browser/__tests__/auth-cache.test.ts +69 -63
- package/src/tools/browser/__tests__/auth-detector.test.ts +218 -157
- package/src/tools/browser/__tests__/jit-auth.test.ts +83 -99
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
import { mkdtempSync, rmSync } from
|
|
2
|
-
import { tmpdir } from
|
|
3
|
-
import { join } from
|
|
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
|
-
|
|
6
|
+
const testDir = mkdtempSync(join(tmpdir(), "call-store-test-"));
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
mock.module('../util/platform.js', () => ({
|
|
8
|
+
mock.module("../util/platform.js", () => ({
|
|
10
9
|
getDataDir: () => testDir,
|
|
11
|
-
isMacOS: () => process.platform ===
|
|
12
|
-
isLinux: () => process.platform ===
|
|
13
|
-
isWindows: () => process.platform ===
|
|
14
|
-
getSocketPath: () => join(testDir,
|
|
15
|
-
getPidPath: () => join(testDir,
|
|
16
|
-
getDbPath: () => join(testDir,
|
|
17
|
-
getLogPath: () => join(testDir,
|
|
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(
|
|
22
|
-
getLogger: () =>
|
|
23
|
-
|
|
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
|
|
43
|
-
import { getDb, initializeDb, resetDb } from
|
|
44
|
-
import { conversations } from
|
|
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 {
|
|
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)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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(
|
|
71
|
-
db.run(
|
|
72
|
-
db.run(
|
|
73
|
-
db.run(
|
|
74
|
-
db.run(
|
|
75
|
-
db.run(
|
|
76
|
-
db.run(
|
|
77
|
-
db.run(
|
|
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(
|
|
93
|
+
describe("call-store", () => {
|
|
88
94
|
beforeEach(() => {
|
|
89
95
|
resetTables();
|
|
90
96
|
});
|
|
91
97
|
|
|
92
98
|
// ── Call Sessions ─────────────────────────────────────────────────
|
|
93
99
|
|
|
94
|
-
test(
|
|
100
|
+
test("createCallSession creates a session with correct defaults", () => {
|
|
95
101
|
const session = createTestCallSession({
|
|
96
|
-
conversationId:
|
|
97
|
-
provider:
|
|
98
|
-
fromNumber:
|
|
99
|
-
toNumber:
|
|
100
|
-
task:
|
|
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(
|
|
105
|
-
expect(session.provider).toBe(
|
|
106
|
-
expect(session.fromNumber).toBe(
|
|
107
|
-
expect(session.toNumber).toBe(
|
|
108
|
-
expect(session.task).toBe(
|
|
109
|
-
expect(session.status).toBe(
|
|
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(
|
|
115
|
-
expect(typeof session.updatedAt).toBe(
|
|
120
|
+
expect(typeof session.createdAt).toBe("number");
|
|
121
|
+
expect(typeof session.updatedAt).toBe("number");
|
|
116
122
|
});
|
|
117
123
|
|
|
118
|
-
test(
|
|
124
|
+
test("createCallSession defaults task to null when not provided", () => {
|
|
119
125
|
const session = createTestCallSession({
|
|
120
|
-
conversationId:
|
|
121
|
-
provider:
|
|
122
|
-
fromNumber:
|
|
123
|
-
toNumber:
|
|
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(
|
|
135
|
+
test("getCallSession retrieves by ID", () => {
|
|
130
136
|
const created = createTestCallSession({
|
|
131
|
-
conversationId:
|
|
132
|
-
provider:
|
|
133
|
-
fromNumber:
|
|
134
|
-
toNumber:
|
|
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(
|
|
146
|
+
expect(retrieved!.conversationId).toBe("conv-3");
|
|
141
147
|
});
|
|
142
148
|
|
|
143
|
-
test(
|
|
144
|
-
const result = getCallSession(
|
|
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(
|
|
154
|
+
test("getCallSessionByCallSid looks up by provider call SID", () => {
|
|
149
155
|
const session = createTestCallSession({
|
|
150
|
-
conversationId:
|
|
151
|
-
provider:
|
|
152
|
-
fromNumber:
|
|
153
|
-
toNumber:
|
|
156
|
+
conversationId: "conv-4",
|
|
157
|
+
provider: "twilio",
|
|
158
|
+
fromNumber: "+15551111111",
|
|
159
|
+
toNumber: "+15552222222",
|
|
154
160
|
});
|
|
155
161
|
|
|
156
|
-
updateCallSession(session.id, { providerCallSid:
|
|
162
|
+
updateCallSession(session.id, { providerCallSid: "CA_test_sid_123" });
|
|
157
163
|
|
|
158
|
-
const found = getCallSessionByCallSid(
|
|
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(
|
|
167
|
+
expect(found!.providerCallSid).toBe("CA_test_sid_123");
|
|
162
168
|
});
|
|
163
169
|
|
|
164
|
-
test(
|
|
165
|
-
const result = getCallSessionByCallSid(
|
|
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(
|
|
175
|
+
test("getActiveCallSessionForConversation finds non-terminal sessions", () => {
|
|
170
176
|
const session = createTestCallSession({
|
|
171
|
-
conversationId:
|
|
172
|
-
provider:
|
|
173
|
-
fromNumber:
|
|
174
|
-
toNumber:
|
|
177
|
+
conversationId: "conv-5",
|
|
178
|
+
provider: "twilio",
|
|
179
|
+
fromNumber: "+15551111111",
|
|
180
|
+
toNumber: "+15552222222",
|
|
175
181
|
});
|
|
176
182
|
|
|
177
|
-
const active = getActiveCallSessionForConversation(
|
|
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(
|
|
188
|
+
test("getActiveCallSessionForConversation returns null when all sessions are completed", () => {
|
|
183
189
|
const session = createTestCallSession({
|
|
184
|
-
conversationId:
|
|
185
|
-
provider:
|
|
186
|
-
fromNumber:
|
|
187
|
-
toNumber:
|
|
190
|
+
conversationId: "conv-6",
|
|
191
|
+
provider: "twilio",
|
|
192
|
+
fromNumber: "+15551111111",
|
|
193
|
+
toNumber: "+15552222222",
|
|
188
194
|
});
|
|
189
195
|
|
|
190
|
-
updateCallSession(session.id, { status:
|
|
196
|
+
updateCallSession(session.id, { status: "completed" });
|
|
191
197
|
|
|
192
|
-
const active = getActiveCallSessionForConversation(
|
|
198
|
+
const active = getActiveCallSessionForConversation("conv-6");
|
|
193
199
|
expect(active).toBeNull();
|
|
194
200
|
});
|
|
195
201
|
|
|
196
|
-
test(
|
|
202
|
+
test("getActiveCallSessionForConversation returns null when all sessions are failed", () => {
|
|
197
203
|
const session = createTestCallSession({
|
|
198
|
-
conversationId:
|
|
199
|
-
provider:
|
|
200
|
-
fromNumber:
|
|
201
|
-
toNumber:
|
|
204
|
+
conversationId: "conv-7",
|
|
205
|
+
provider: "twilio",
|
|
206
|
+
fromNumber: "+15551111111",
|
|
207
|
+
toNumber: "+15552222222",
|
|
202
208
|
});
|
|
203
209
|
|
|
204
|
-
updateCallSession(session.id, { status:
|
|
210
|
+
updateCallSession(session.id, { status: "failed" });
|
|
205
211
|
|
|
206
|
-
const active = getActiveCallSessionForConversation(
|
|
212
|
+
const active = getActiveCallSessionForConversation("conv-7");
|
|
207
213
|
expect(active).toBeNull();
|
|
208
214
|
});
|
|
209
215
|
|
|
210
|
-
test(
|
|
216
|
+
test("getActiveCallSessionForConversation returns most recent active session", () => {
|
|
211
217
|
// Create two sessions for the same conversation
|
|
212
218
|
const older = createTestCallSession({
|
|
213
|
-
conversationId:
|
|
214
|
-
provider:
|
|
215
|
-
fromNumber:
|
|
216
|
-
toNumber:
|
|
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:
|
|
225
|
+
updateCallSession(older.id, { status: "completed" });
|
|
220
226
|
|
|
221
227
|
const newer = createTestCallSession({
|
|
222
|
-
conversationId:
|
|
223
|
-
provider:
|
|
224
|
-
fromNumber:
|
|
225
|
-
toNumber:
|
|
228
|
+
conversationId: "conv-8",
|
|
229
|
+
provider: "twilio",
|
|
230
|
+
fromNumber: "+15551111111",
|
|
231
|
+
toNumber: "+15553333333",
|
|
226
232
|
});
|
|
227
233
|
|
|
228
|
-
const active = getActiveCallSessionForConversation(
|
|
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(
|
|
239
|
+
test("updateCallSession updates status, providerCallSid, and timestamps", () => {
|
|
234
240
|
const session = createTestCallSession({
|
|
235
|
-
conversationId:
|
|
236
|
-
provider:
|
|
237
|
-
fromNumber:
|
|
238
|
-
toNumber:
|
|
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:
|
|
244
|
-
providerCallSid:
|
|
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(
|
|
251
|
-
expect(updated!.providerCallSid).toBe(
|
|
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(
|
|
263
|
+
test("updateCallSession sets endedAt and lastError", () => {
|
|
258
264
|
const session = createTestCallSession({
|
|
259
|
-
conversationId:
|
|
260
|
-
provider:
|
|
261
|
-
fromNumber:
|
|
262
|
-
toNumber:
|
|
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:
|
|
273
|
+
status: "failed",
|
|
268
274
|
endedAt: endTime,
|
|
269
|
-
lastError:
|
|
275
|
+
lastError: "Network timeout",
|
|
270
276
|
});
|
|
271
277
|
|
|
272
278
|
const updated = getCallSession(session.id);
|
|
273
|
-
expect(updated!.status).toBe(
|
|
279
|
+
expect(updated!.status).toBe("failed");
|
|
274
280
|
expect(updated!.endedAt).toBe(endTime);
|
|
275
|
-
expect(updated!.lastError).toBe(
|
|
281
|
+
expect(updated!.lastError).toBe("Network timeout");
|
|
276
282
|
});
|
|
277
283
|
|
|
278
284
|
// ── Call Events ───────────────────────────────────────────────────
|
|
279
285
|
|
|
280
|
-
test(
|
|
286
|
+
test("recordCallEvent creates events with correct fields", () => {
|
|
281
287
|
const session = createTestCallSession({
|
|
282
|
-
conversationId:
|
|
283
|
-
provider:
|
|
284
|
-
fromNumber:
|
|
285
|
-
toNumber:
|
|
288
|
+
conversationId: "conv-11",
|
|
289
|
+
provider: "twilio",
|
|
290
|
+
fromNumber: "+15551111111",
|
|
291
|
+
toNumber: "+15552222222",
|
|
286
292
|
});
|
|
287
293
|
|
|
288
|
-
const event = recordCallEvent(session.id,
|
|
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(
|
|
293
|
-
expect(typeof event.createdAt).toBe(
|
|
300
|
+
expect(event.eventType).toBe("call_started");
|
|
301
|
+
expect(typeof event.createdAt).toBe("number");
|
|
294
302
|
});
|
|
295
303
|
|
|
296
|
-
test(
|
|
304
|
+
test("recordCallEvent stores JSON payload", () => {
|
|
297
305
|
const session = createTestCallSession({
|
|
298
|
-
conversationId:
|
|
299
|
-
provider:
|
|
300
|
-
fromNumber:
|
|
301
|
-
toNumber:
|
|
306
|
+
conversationId: "conv-12",
|
|
307
|
+
provider: "twilio",
|
|
308
|
+
fromNumber: "+15551111111",
|
|
309
|
+
toNumber: "+15552222222",
|
|
302
310
|
});
|
|
303
311
|
|
|
304
|
-
const payload = { text:
|
|
305
|
-
const event = recordCallEvent(session.id,
|
|
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(
|
|
309
|
-
expect(parsed.lang).toBe(
|
|
316
|
+
expect(parsed.text).toBe("Hello, how are you?");
|
|
317
|
+
expect(parsed.lang).toBe("en-US");
|
|
310
318
|
});
|
|
311
319
|
|
|
312
|
-
test(
|
|
320
|
+
test("recordCallEvent defaults payload to empty JSON object", () => {
|
|
313
321
|
const session = createTestCallSession({
|
|
314
|
-
conversationId:
|
|
315
|
-
provider:
|
|
316
|
-
fromNumber:
|
|
317
|
-
toNumber:
|
|
322
|
+
conversationId: "conv-13",
|
|
323
|
+
provider: "twilio",
|
|
324
|
+
fromNumber: "+15551111111",
|
|
325
|
+
toNumber: "+15552222222",
|
|
318
326
|
});
|
|
319
327
|
|
|
320
|
-
const event = recordCallEvent(session.id,
|
|
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(
|
|
333
|
+
test("getCallEvents retrieves events in creation order", () => {
|
|
326
334
|
const session = createTestCallSession({
|
|
327
|
-
conversationId:
|
|
328
|
-
provider:
|
|
329
|
-
fromNumber:
|
|
330
|
-
toNumber:
|
|
335
|
+
conversationId: "conv-14",
|
|
336
|
+
provider: "twilio",
|
|
337
|
+
fromNumber: "+15551111111",
|
|
338
|
+
toNumber: "+15552222222",
|
|
331
339
|
});
|
|
332
340
|
|
|
333
|
-
recordCallEvent(session.id,
|
|
334
|
-
recordCallEvent(session.id,
|
|
335
|
-
recordCallEvent(session.id,
|
|
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(
|
|
340
|
-
expect(events[1].eventType).toBe(
|
|
341
|
-
expect(events[2].eventType).toBe(
|
|
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(
|
|
355
|
+
test("getCallEvents returns empty array for session with no events", () => {
|
|
348
356
|
const session = createTestCallSession({
|
|
349
|
-
conversationId:
|
|
350
|
-
provider:
|
|
351
|
-
fromNumber:
|
|
352
|
-
toNumber:
|
|
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(
|
|
369
|
+
test("createPendingQuestion creates with status pending", () => {
|
|
362
370
|
const session = createTestCallSession({
|
|
363
|
-
conversationId:
|
|
364
|
-
provider:
|
|
365
|
-
fromNumber:
|
|
366
|
-
toNumber:
|
|
371
|
+
conversationId: "conv-16",
|
|
372
|
+
provider: "twilio",
|
|
373
|
+
fromNumber: "+15551111111",
|
|
374
|
+
toNumber: "+15552222222",
|
|
367
375
|
});
|
|
368
376
|
|
|
369
|
-
const question = createPendingQuestion(
|
|
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(
|
|
374
|
-
expect(question.status).toBe(
|
|
375
|
-
expect(typeof question.askedAt).toBe(
|
|
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(
|
|
391
|
+
test("getPendingQuestion finds pending question for session", () => {
|
|
381
392
|
const session = createTestCallSession({
|
|
382
|
-
conversationId:
|
|
383
|
-
provider:
|
|
384
|
-
fromNumber:
|
|
385
|
-
toNumber:
|
|
393
|
+
conversationId: "conv-17",
|
|
394
|
+
provider: "twilio",
|
|
395
|
+
fromNumber: "+15551111111",
|
|
396
|
+
toNumber: "+15552222222",
|
|
386
397
|
});
|
|
387
398
|
|
|
388
|
-
const created = createPendingQuestion(session.id,
|
|
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(
|
|
394
|
-
expect(found!.status).toBe(
|
|
404
|
+
expect(found!.questionText).toBe("What is your name?");
|
|
405
|
+
expect(found!.status).toBe("pending");
|
|
395
406
|
});
|
|
396
407
|
|
|
397
|
-
test(
|
|
408
|
+
test("getPendingQuestion returns null when no pending questions", () => {
|
|
398
409
|
const session = createTestCallSession({
|
|
399
|
-
conversationId:
|
|
400
|
-
provider:
|
|
401
|
-
fromNumber:
|
|
402
|
-
toNumber:
|
|
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(
|
|
420
|
+
test("answerPendingQuestion updates status to answered", () => {
|
|
410
421
|
const session = createTestCallSession({
|
|
411
|
-
conversationId:
|
|
412
|
-
provider:
|
|
413
|
-
fromNumber:
|
|
414
|
-
toNumber:
|
|
422
|
+
conversationId: "conv-19",
|
|
423
|
+
provider: "twilio",
|
|
424
|
+
fromNumber: "+15551111111",
|
|
425
|
+
toNumber: "+15552222222",
|
|
415
426
|
});
|
|
416
427
|
|
|
417
|
-
const question = createPendingQuestion(session.id,
|
|
418
|
-
answerPendingQuestion(question.id,
|
|
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(
|
|
427
|
-
|
|
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(
|
|
433
|
-
expect(updated.answer_text).toBe(
|
|
434
|
-
expect(typeof updated.answered_at).toBe(
|
|
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(
|
|
451
|
+
test("expirePendingQuestions marks all pending questions as expired", () => {
|
|
438
452
|
const session = createTestCallSession({
|
|
439
|
-
conversationId:
|
|
440
|
-
provider:
|
|
441
|
-
fromNumber:
|
|
442
|
-
toNumber:
|
|
453
|
+
conversationId: "conv-20",
|
|
454
|
+
provider: "twilio",
|
|
455
|
+
fromNumber: "+15551111111",
|
|
456
|
+
toNumber: "+15552222222",
|
|
443
457
|
});
|
|
444
458
|
|
|
445
|
-
createPendingQuestion(session.id,
|
|
446
|
-
createPendingQuestion(session.id,
|
|
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 = (
|
|
456
|
-
|
|
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(
|
|
481
|
+
expect(row.status).toBe("expired");
|
|
460
482
|
}
|
|
461
483
|
});
|
|
462
484
|
|
|
463
|
-
test(
|
|
485
|
+
test("expirePendingQuestions does not affect already-answered questions", () => {
|
|
464
486
|
const session = createTestCallSession({
|
|
465
|
-
conversationId:
|
|
466
|
-
provider:
|
|
467
|
-
fromNumber:
|
|
468
|
-
toNumber:
|
|
487
|
+
conversationId: "conv-21",
|
|
488
|
+
provider: "twilio",
|
|
489
|
+
fromNumber: "+15551111111",
|
|
490
|
+
toNumber: "+15552222222",
|
|
469
491
|
});
|
|
470
492
|
|
|
471
|
-
const q1 = createPendingQuestion(session.id,
|
|
472
|
-
createPendingQuestion(session.id,
|
|
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,
|
|
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 = (
|
|
482
|
-
|
|
483
|
-
|
|
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(
|
|
516
|
+
test("claimCallback returns a claim ID on first call", () => {
|
|
489
517
|
const session = createTestCallSession({
|
|
490
|
-
conversationId:
|
|
491
|
-
provider:
|
|
492
|
-
fromNumber:
|
|
493
|
-
toNumber:
|
|
518
|
+
conversationId: "conv-22",
|
|
519
|
+
provider: "twilio",
|
|
520
|
+
fromNumber: "+15551111111",
|
|
521
|
+
toNumber: "+15552222222",
|
|
494
522
|
});
|
|
495
523
|
|
|
496
|
-
const result = claimCallback(
|
|
497
|
-
expect(result).toBeTypeOf(
|
|
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(
|
|
529
|
+
test("claimCallback returns null on duplicate key", () => {
|
|
502
530
|
const session = createTestCallSession({
|
|
503
|
-
conversationId:
|
|
504
|
-
provider:
|
|
505
|
-
fromNumber:
|
|
506
|
-
toNumber:
|
|
531
|
+
conversationId: "conv-23",
|
|
532
|
+
provider: "twilio",
|
|
533
|
+
fromNumber: "+15551111111",
|
|
534
|
+
toNumber: "+15552222222",
|
|
507
535
|
});
|
|
508
536
|
|
|
509
|
-
const first = claimCallback(
|
|
510
|
-
const second = claimCallback(
|
|
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(
|
|
540
|
+
expect(first).toBeTypeOf("string");
|
|
513
541
|
expect(second).toBeNull();
|
|
514
542
|
});
|
|
515
543
|
|
|
516
|
-
test(
|
|
544
|
+
test("releaseCallbackClaim allows re-claim", () => {
|
|
517
545
|
const session = createTestCallSession({
|
|
518
|
-
conversationId:
|
|
519
|
-
provider:
|
|
520
|
-
fromNumber:
|
|
521
|
-
toNumber:
|
|
546
|
+
conversationId: "conv-24",
|
|
547
|
+
provider: "twilio",
|
|
548
|
+
fromNumber: "+15551111111",
|
|
549
|
+
toNumber: "+15552222222",
|
|
522
550
|
});
|
|
523
551
|
|
|
524
|
-
const first = claimCallback(
|
|
525
|
-
expect(first).toBeTypeOf(
|
|
552
|
+
const first = claimCallback("test-dedupe-key-3", session.id);
|
|
553
|
+
expect(first).toBeTypeOf("string");
|
|
526
554
|
|
|
527
|
-
releaseCallbackClaim(
|
|
555
|
+
releaseCallbackClaim("test-dedupe-key-3", first!);
|
|
528
556
|
|
|
529
|
-
const second = claimCallback(
|
|
530
|
-
expect(second).toBeTypeOf(
|
|
557
|
+
const second = claimCallback("test-dedupe-key-3", session.id);
|
|
558
|
+
expect(second).toBeTypeOf("string");
|
|
531
559
|
});
|
|
532
560
|
|
|
533
|
-
test(
|
|
561
|
+
test("releaseCallbackClaim with wrong claimId does not release", () => {
|
|
534
562
|
const session = createTestCallSession({
|
|
535
|
-
conversationId:
|
|
536
|
-
provider:
|
|
537
|
-
fromNumber:
|
|
538
|
-
toNumber:
|
|
563
|
+
conversationId: "conv-24b",
|
|
564
|
+
provider: "twilio",
|
|
565
|
+
fromNumber: "+15551111111",
|
|
566
|
+
toNumber: "+15552222222",
|
|
539
567
|
});
|
|
540
568
|
|
|
541
|
-
const claimId = claimCallback(
|
|
542
|
-
expect(claimId).toBeTypeOf(
|
|
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(
|
|
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(
|
|
576
|
+
const second = claimCallback("test-dedupe-key-3b", session.id);
|
|
549
577
|
expect(second).toBeNull();
|
|
550
578
|
});
|
|
551
579
|
|
|
552
|
-
test(
|
|
580
|
+
test("claimCallback INSERT OR IGNORE pattern is safe for same key", () => {
|
|
553
581
|
const session = createTestCallSession({
|
|
554
|
-
conversationId:
|
|
555
|
-
provider:
|
|
556
|
-
fromNumber:
|
|
557
|
-
toNumber:
|
|
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(
|
|
562
|
-
expect(first).toBeTypeOf(
|
|
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(
|
|
566
|
-
expect(claimCallback(
|
|
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 = (
|
|
570
|
-
|
|
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(
|
|
610
|
+
test("claimCallback reclaims expired orphaned claims", () => {
|
|
575
611
|
const session = createTestCallSession({
|
|
576
|
-
conversationId:
|
|
577
|
-
provider:
|
|
578
|
-
fromNumber:
|
|
579
|
-
toNumber:
|
|
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(
|
|
584
|
-
expect(first).toBeTypeOf(
|
|
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 = (
|
|
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
|
|
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(
|
|
593
|
-
expect(second).toBeTypeOf(
|
|
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(
|
|
643
|
+
test("claimCallback does not reclaim finalized claims", () => {
|
|
600
644
|
const session = createTestCallSession({
|
|
601
|
-
conversationId:
|
|
602
|
-
provider:
|
|
603
|
-
fromNumber:
|
|
604
|
-
toNumber:
|
|
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(
|
|
609
|
-
expect(first).toBeTypeOf(
|
|
610
|
-
finalizeCallbackClaim(
|
|
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(
|
|
658
|
+
const second = claimCallback("test-dedupe-key-finalized", session.id);
|
|
615
659
|
expect(second).toBeNull();
|
|
616
660
|
});
|
|
617
661
|
|
|
618
|
-
test(
|
|
662
|
+
test("finalizeCallbackClaim makes claim permanent", () => {
|
|
619
663
|
const session = createTestCallSession({
|
|
620
|
-
conversationId:
|
|
621
|
-
provider:
|
|
622
|
-
fromNumber:
|
|
623
|
-
toNumber:
|
|
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(
|
|
628
|
-
finalizeCallbackClaim(
|
|
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 = (
|
|
632
|
-
|
|
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(
|
|
688
|
+
test("finalizeCallbackClaim with wrong claimId does not finalize", () => {
|
|
639
689
|
const session = createTestCallSession({
|
|
640
|
-
conversationId:
|
|
641
|
-
provider:
|
|
642
|
-
fromNumber:
|
|
643
|
-
toNumber:
|
|
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(
|
|
648
|
-
expect(claimId).toBeTypeOf(
|
|
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(
|
|
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 = (
|
|
655
|
-
|
|
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(
|
|
716
|
+
test("handler A cannot release handler B claim after reclaim", () => {
|
|
661
717
|
const session = createTestCallSession({
|
|
662
|
-
conversationId:
|
|
663
|
-
provider:
|
|
664
|
-
fromNumber:
|
|
665
|
-
toNumber:
|
|
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(
|
|
670
|
-
expect(claimA).toBeTypeOf(
|
|
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 = (
|
|
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
|
|
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(
|
|
679
|
-
expect(claimB).toBeTypeOf(
|
|
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(
|
|
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(
|
|
750
|
+
releaseCallbackClaim("test-dedupe-key-ownership", claimA);
|
|
687
751
|
|
|
688
752
|
// Verify B's finalized claim is still intact
|
|
689
|
-
const row = raw
|
|
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;
|