@vellumai/assistant 0.4.16 → 0.4.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +6 -6
- package/README.md +1 -2
- 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 +1073 -751
- 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 +328 -279
- 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 -1126
- package/src/__tests__/channel-approval.test.ts +139 -137
- package/src/__tests__/channel-approvals.test.ts +226 -182
- package/src/__tests__/channel-delivery-store.test.ts +232 -194
- package/src/__tests__/channel-guardian.test.ts +6 -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 +156 -114
- package/src/__tests__/conversation-pairing.test.ts +220 -113
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +164 -104
- package/src/__tests__/conversation-routes.test.ts +71 -41
- 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 +257 -191
- 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 +183 -135
- 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 +51 -0
- 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 +467 -369
- package/src/__tests__/gateway-only-guard.test.ts +54 -56
- 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 +47 -46
- 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 +215 -151
- 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 +337 -209
- 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 +679 -613
- package/src/__tests__/guardian-routing-state.test.ts +256 -209
- package/src/__tests__/guardian-verification-intent-routing.test.ts +94 -88
- package/src/__tests__/guardian-verification-voice-binding.test.ts +47 -41
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +0 -1
- package/src/__tests__/handle-user-message-secret-resume.test.ts +43 -21
- 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 +270 -195
- 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 +194 -152
- package/src/__tests__/ingress-member-store.test.ts +163 -159
- package/src/__tests__/ingress-reconcile.test.ts +183 -142
- 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 +294 -249
- 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-telegram-adapter.test.ts +60 -46
- 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 +422 -292
- 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 +107 -70
- package/src/__tests__/runtime-events-sse-parity.test.ts +167 -145
- package/src/__tests__/runtime-events-sse.test.ts +67 -51
- 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 +313 -232
- 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-approval-overrides.test.ts +93 -91
- 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__/sms-messaging-provider.test.ts +74 -47
- 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 +339 -275
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +484 -373
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +264 -241
- package/src/__tests__/trusted-contact-multichannel.test.ts +182 -142
- package/src/__tests__/trusted-contact-verification.test.ts +251 -231
- package/src/__tests__/turn-commit.test.ts +259 -200
- package/src/__tests__/twilio-config.test.ts +49 -41
- 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 +188 -162
- package/src/__tests__/twilio-routes-twiml.test.ts +55 -55
- package/src/__tests__/twilio-routes.test.ts +389 -281
- 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 +44 -4
- package/src/config/bundled-skills/doordash/__tests__/doordash-session.test.ts +0 -1
- package/src/config/bundled-skills/messaging/SKILL.md +9 -7
- 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/messaging/tools/messaging-reply.ts +11 -7
- 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 +38 -29
- package/src/daemon/handlers/skills.ts +18 -10
- package/src/daemon/ipc-contract/messages.ts +1 -0
- package/src/daemon/ipc-contract/surfaces.ts +7 -1
- package/src/daemon/session-agent-loop-handlers.ts +5 -0
- package/src/daemon/session-agent-loop.ts +1 -1
- package/src/daemon/session-process.ts +1 -1
- package/src/daemon/session-surfaces.ts +42 -2
- 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 +78 -48
- 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/sequence/reply-matcher.ts +10 -6
- package/src/skills/frontmatter.ts +9 -6
- 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
- package/src/tools/ui-surface/definitions.ts +2 -1
- package/src/util/platform.ts +0 -12
- package/docs/architecture/http-token-refresh.md +0 -274
|
@@ -5,52 +5,56 @@
|
|
|
5
5
|
* POST /v1/calls/:id/cancel, and POST /v1/calls/:id/answer
|
|
6
6
|
* through RuntimeHttpServer.
|
|
7
7
|
*/
|
|
8
|
-
import { mkdtempSync, realpathSync,rmSync } from
|
|
9
|
-
import { tmpdir } from
|
|
10
|
-
import { join } from
|
|
8
|
+
import { mkdtempSync, realpathSync, rmSync } from "node:fs";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
mock.module("../config/env.js", () => ({ isHttpAuthDisabled: () => true }));
|
|
13
14
|
|
|
14
|
-
const testDir = realpathSync(
|
|
15
|
+
const testDir = realpathSync(
|
|
16
|
+
mkdtempSync(join(tmpdir(), "call-routes-http-test-")),
|
|
17
|
+
);
|
|
15
18
|
|
|
16
|
-
mock.module(
|
|
19
|
+
mock.module("../util/platform.js", () => ({
|
|
17
20
|
getRootDir: () => testDir,
|
|
18
21
|
getDataDir: () => testDir,
|
|
19
|
-
isMacOS: () => process.platform ===
|
|
20
|
-
isLinux: () => process.platform ===
|
|
21
|
-
isWindows: () => process.platform ===
|
|
22
|
-
getSocketPath: () => join(testDir,
|
|
23
|
-
getPidPath: () => join(testDir,
|
|
24
|
-
getDbPath: () => join(testDir,
|
|
25
|
-
getLogPath: () => join(testDir,
|
|
22
|
+
isMacOS: () => process.platform === "darwin",
|
|
23
|
+
isLinux: () => process.platform === "linux",
|
|
24
|
+
isWindows: () => process.platform === "win32",
|
|
25
|
+
getSocketPath: () => join(testDir, "test.sock"),
|
|
26
|
+
getPidPath: () => join(testDir, "test.pid"),
|
|
27
|
+
getDbPath: () => join(testDir, "test.db"),
|
|
28
|
+
getLogPath: () => join(testDir, "test.log"),
|
|
26
29
|
ensureDataDir: () => {},
|
|
27
30
|
readHttpToken: () => null,
|
|
28
31
|
}));
|
|
29
32
|
|
|
30
|
-
mock.module(
|
|
31
|
-
getLogger: () =>
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
mock.module("../util/logger.js", () => ({
|
|
34
|
+
getLogger: () =>
|
|
35
|
+
new Proxy({} as Record<string, unknown>, {
|
|
36
|
+
get: () => () => {},
|
|
37
|
+
}),
|
|
34
38
|
}));
|
|
35
39
|
|
|
36
40
|
const mockCallsConfig = {
|
|
37
41
|
enabled: true,
|
|
38
|
-
provider:
|
|
42
|
+
provider: "twilio",
|
|
39
43
|
maxDurationSeconds: 3600,
|
|
40
44
|
userConsultTimeoutSeconds: 120,
|
|
41
|
-
disclosure: { enabled: false, text:
|
|
45
|
+
disclosure: { enabled: false, text: "" },
|
|
42
46
|
safety: { denyCategories: [] },
|
|
43
47
|
callerIdentity: {
|
|
44
48
|
allowPerCallOverride: true,
|
|
45
49
|
},
|
|
46
50
|
};
|
|
47
51
|
|
|
48
|
-
mock.module(
|
|
52
|
+
mock.module("../config/loader.js", () => ({
|
|
49
53
|
getConfig: () => ({
|
|
50
54
|
ui: {},
|
|
51
|
-
|
|
52
|
-
model:
|
|
53
|
-
provider:
|
|
55
|
+
|
|
56
|
+
model: "test",
|
|
57
|
+
provider: "test",
|
|
54
58
|
apiKeys: {},
|
|
55
59
|
memory: { enabled: false },
|
|
56
60
|
rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
|
|
@@ -58,8 +62,8 @@ mock.module('../config/loader.js', () => ({
|
|
|
58
62
|
calls: mockCallsConfig,
|
|
59
63
|
}),
|
|
60
64
|
loadConfig: () => ({
|
|
61
|
-
model:
|
|
62
|
-
provider:
|
|
65
|
+
model: "test",
|
|
66
|
+
provider: "test",
|
|
63
67
|
apiKeys: {},
|
|
64
68
|
memory: { enabled: false },
|
|
65
69
|
rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
|
|
@@ -67,34 +71,42 @@ mock.module('../config/loader.js', () => ({
|
|
|
67
71
|
calls: mockCallsConfig,
|
|
68
72
|
ingress: {
|
|
69
73
|
enabled: true,
|
|
70
|
-
publicBaseUrl:
|
|
74
|
+
publicBaseUrl: "https://test.example.com",
|
|
71
75
|
},
|
|
72
76
|
}),
|
|
73
77
|
}));
|
|
74
78
|
|
|
75
79
|
// Mock Twilio provider to avoid real API calls
|
|
76
|
-
mock.module(
|
|
80
|
+
mock.module("../calls/twilio-provider.js", () => ({
|
|
77
81
|
TwilioConversationRelayProvider: class {
|
|
78
|
-
static getAuthToken() {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
static getAuthToken() {
|
|
83
|
+
return "mock-auth-token";
|
|
84
|
+
}
|
|
85
|
+
static verifyWebhookSignature() {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
async initiateCall() {
|
|
89
|
+
return { callSid: "CA_mock_sid_123" };
|
|
90
|
+
}
|
|
91
|
+
async endCall() {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
82
94
|
},
|
|
83
95
|
}));
|
|
84
96
|
|
|
85
97
|
// Mock Twilio config
|
|
86
|
-
mock.module(
|
|
98
|
+
mock.module("../calls/twilio-config.js", () => ({
|
|
87
99
|
getTwilioConfig: (assistantId?: string) => ({
|
|
88
|
-
accountSid:
|
|
89
|
-
authToken:
|
|
90
|
-
phoneNumber: assistantId ===
|
|
91
|
-
webhookBaseUrl:
|
|
92
|
-
wssBaseUrl:
|
|
100
|
+
accountSid: "AC_test",
|
|
101
|
+
authToken: "test_token",
|
|
102
|
+
phoneNumber: assistantId === "asst-alpha" ? "+15550009999" : "+15550001111",
|
|
103
|
+
webhookBaseUrl: "https://test.example.com",
|
|
104
|
+
wssBaseUrl: "wss://test.example.com",
|
|
93
105
|
}),
|
|
94
106
|
}));
|
|
95
107
|
|
|
96
108
|
// Mock secure keys
|
|
97
|
-
mock.module(
|
|
109
|
+
mock.module("../security/secure-keys.js", () => ({
|
|
98
110
|
getSecureKey: () => null,
|
|
99
111
|
}));
|
|
100
112
|
|
|
@@ -102,12 +114,12 @@ import {
|
|
|
102
114
|
createCallSession,
|
|
103
115
|
createPendingQuestion,
|
|
104
116
|
updateCallSession,
|
|
105
|
-
} from
|
|
106
|
-
import { getDb, initializeDb, resetDb } from
|
|
107
|
-
import { conversations } from
|
|
108
|
-
import { RuntimeHttpServer } from
|
|
117
|
+
} from "../calls/call-store.js";
|
|
118
|
+
import { getDb, initializeDb, resetDb } from "../memory/db.js";
|
|
119
|
+
import { conversations } from "../memory/schema.js";
|
|
120
|
+
import { RuntimeHttpServer } from "../runtime/http-server.js";
|
|
109
121
|
|
|
110
|
-
import
|
|
122
|
+
import "../calls/call-state.js";
|
|
111
123
|
|
|
112
124
|
initializeDb();
|
|
113
125
|
|
|
@@ -115,7 +127,7 @@ initializeDb();
|
|
|
115
127
|
// Helpers
|
|
116
128
|
// ---------------------------------------------------------------------------
|
|
117
129
|
|
|
118
|
-
const TEST_TOKEN =
|
|
130
|
+
const TEST_TOKEN = "test-bearer-token-calls";
|
|
119
131
|
const AUTH_HEADERS = { Authorization: `Bearer ${TEST_TOKEN}` };
|
|
120
132
|
|
|
121
133
|
let ensuredConvIds = new Set<string>();
|
|
@@ -124,25 +136,27 @@ function ensureConversation(id: string): void {
|
|
|
124
136
|
if (ensuredConvIds.has(id)) return;
|
|
125
137
|
const db = getDb();
|
|
126
138
|
const now = Date.now();
|
|
127
|
-
db.insert(conversations)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
139
|
+
db.insert(conversations)
|
|
140
|
+
.values({
|
|
141
|
+
id,
|
|
142
|
+
title: `Test conversation ${id}`,
|
|
143
|
+
createdAt: now,
|
|
144
|
+
updatedAt: now,
|
|
145
|
+
})
|
|
146
|
+
.run();
|
|
133
147
|
ensuredConvIds.add(id);
|
|
134
148
|
}
|
|
135
149
|
|
|
136
150
|
function resetTables() {
|
|
137
151
|
const db = getDb();
|
|
138
|
-
db.run(
|
|
139
|
-
db.run(
|
|
140
|
-
db.run(
|
|
141
|
-
db.run(
|
|
142
|
-
db.run(
|
|
143
|
-
db.run(
|
|
144
|
-
db.run(
|
|
145
|
-
db.run(
|
|
152
|
+
db.run("DELETE FROM guardian_action_deliveries");
|
|
153
|
+
db.run("DELETE FROM guardian_action_requests");
|
|
154
|
+
db.run("DELETE FROM call_pending_questions");
|
|
155
|
+
db.run("DELETE FROM call_events");
|
|
156
|
+
db.run("DELETE FROM call_sessions");
|
|
157
|
+
db.run("DELETE FROM tool_invocations");
|
|
158
|
+
db.run("DELETE FROM messages");
|
|
159
|
+
db.run("DELETE FROM conversations");
|
|
146
160
|
ensuredConvIds = new Set();
|
|
147
161
|
}
|
|
148
162
|
|
|
@@ -150,7 +164,7 @@ function resetTables() {
|
|
|
150
164
|
// Tests
|
|
151
165
|
// ---------------------------------------------------------------------------
|
|
152
166
|
|
|
153
|
-
describe(
|
|
167
|
+
describe("runtime call routes — HTTP layer", () => {
|
|
154
168
|
let server: RuntimeHttpServer;
|
|
155
169
|
let port: number;
|
|
156
170
|
|
|
@@ -160,7 +174,11 @@ describe('runtime call routes — HTTP layer', () => {
|
|
|
160
174
|
|
|
161
175
|
afterAll(() => {
|
|
162
176
|
resetDb();
|
|
163
|
-
try {
|
|
177
|
+
try {
|
|
178
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
179
|
+
} catch {
|
|
180
|
+
/* best effort */
|
|
181
|
+
}
|
|
164
182
|
});
|
|
165
183
|
|
|
166
184
|
async function startServer(): Promise<void> {
|
|
@@ -173,29 +191,29 @@ describe('runtime call routes — HTTP layer', () => {
|
|
|
173
191
|
await server?.stop();
|
|
174
192
|
}
|
|
175
193
|
|
|
176
|
-
function callsUrl(path =
|
|
194
|
+
function callsUrl(path = ""): string {
|
|
177
195
|
return `http://127.0.0.1:${port}/v1/calls${path}`;
|
|
178
196
|
}
|
|
179
197
|
|
|
180
198
|
// ── POST /v1/calls/start ────────────────────────────────────────────
|
|
181
199
|
|
|
182
|
-
test(
|
|
200
|
+
test("POST /v1/calls/start returns 201 with call session", async () => {
|
|
183
201
|
await startServer();
|
|
184
|
-
ensureConversation(
|
|
202
|
+
ensureConversation("conv-start-1");
|
|
185
203
|
|
|
186
|
-
const res = await fetch(callsUrl(
|
|
187
|
-
method:
|
|
188
|
-
headers: {
|
|
204
|
+
const res = await fetch(callsUrl("/start"), {
|
|
205
|
+
method: "POST",
|
|
206
|
+
headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
|
|
189
207
|
body: JSON.stringify({
|
|
190
|
-
phoneNumber:
|
|
191
|
-
task:
|
|
192
|
-
conversationId:
|
|
208
|
+
phoneNumber: "+15559998888",
|
|
209
|
+
task: "Book a table for two",
|
|
210
|
+
conversationId: "conv-start-1",
|
|
193
211
|
}),
|
|
194
212
|
});
|
|
195
213
|
|
|
196
214
|
expect(res.status).toBe(201);
|
|
197
215
|
|
|
198
|
-
const body = await res.json() as {
|
|
216
|
+
const body = (await res.json()) as {
|
|
199
217
|
callSessionId: string;
|
|
200
218
|
callSid: string;
|
|
201
219
|
status: string;
|
|
@@ -204,111 +222,119 @@ describe('runtime call routes — HTTP layer', () => {
|
|
|
204
222
|
};
|
|
205
223
|
|
|
206
224
|
expect(body.callSessionId).toBeDefined();
|
|
207
|
-
expect(body.callSid).toBe(
|
|
208
|
-
expect(body.status).toBe(
|
|
209
|
-
expect(body.toNumber).toBe(
|
|
210
|
-
expect(body.fromNumber).toBe(
|
|
225
|
+
expect(body.callSid).toBe("CA_mock_sid_123");
|
|
226
|
+
expect(body.status).toBe("initiated");
|
|
227
|
+
expect(body.toNumber).toBe("+15559998888");
|
|
228
|
+
expect(body.fromNumber).toBe("+15550001111");
|
|
211
229
|
|
|
212
230
|
await stopServer();
|
|
213
231
|
});
|
|
214
232
|
|
|
215
|
-
test(
|
|
233
|
+
test("POST /v1/calls/start returns 400 when conversationId missing", async () => {
|
|
216
234
|
await startServer();
|
|
217
235
|
|
|
218
|
-
const res = await fetch(callsUrl(
|
|
219
|
-
method:
|
|
220
|
-
headers: {
|
|
236
|
+
const res = await fetch(callsUrl("/start"), {
|
|
237
|
+
method: "POST",
|
|
238
|
+
headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
|
|
221
239
|
body: JSON.stringify({
|
|
222
|
-
phoneNumber:
|
|
223
|
-
task:
|
|
240
|
+
phoneNumber: "+15559998888",
|
|
241
|
+
task: "Book a table",
|
|
224
242
|
}),
|
|
225
243
|
});
|
|
226
244
|
|
|
227
245
|
expect(res.status).toBe(400);
|
|
228
|
-
const body = await res.json() as {
|
|
229
|
-
|
|
246
|
+
const body = (await res.json()) as {
|
|
247
|
+
error: { message: string; code?: string };
|
|
248
|
+
};
|
|
249
|
+
expect(body.error.message).toContain("conversationId");
|
|
230
250
|
|
|
231
251
|
await stopServer();
|
|
232
252
|
});
|
|
233
253
|
|
|
234
|
-
test(
|
|
254
|
+
test("POST /v1/calls/start returns 400 for invalid phone number", async () => {
|
|
235
255
|
await startServer();
|
|
236
|
-
ensureConversation(
|
|
256
|
+
ensureConversation("conv-start-2");
|
|
237
257
|
|
|
238
|
-
const res = await fetch(callsUrl(
|
|
239
|
-
method:
|
|
240
|
-
headers: {
|
|
258
|
+
const res = await fetch(callsUrl("/start"), {
|
|
259
|
+
method: "POST",
|
|
260
|
+
headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
|
|
241
261
|
body: JSON.stringify({
|
|
242
|
-
phoneNumber:
|
|
243
|
-
task:
|
|
244
|
-
conversationId:
|
|
262
|
+
phoneNumber: "not-a-number",
|
|
263
|
+
task: "Book a table",
|
|
264
|
+
conversationId: "conv-start-2",
|
|
245
265
|
}),
|
|
246
266
|
});
|
|
247
267
|
|
|
248
268
|
expect(res.status).toBe(400);
|
|
249
|
-
const body = await res.json() as {
|
|
250
|
-
|
|
269
|
+
const body = (await res.json()) as {
|
|
270
|
+
error: { message: string; code?: string };
|
|
271
|
+
};
|
|
272
|
+
expect(body.error.message).toContain("E.164");
|
|
251
273
|
|
|
252
274
|
await stopServer();
|
|
253
275
|
});
|
|
254
276
|
|
|
255
|
-
test(
|
|
277
|
+
test("POST /v1/calls/start returns 400 for malformed JSON", async () => {
|
|
256
278
|
await startServer();
|
|
257
279
|
|
|
258
|
-
const res = await fetch(callsUrl(
|
|
259
|
-
method:
|
|
260
|
-
headers: {
|
|
261
|
-
body:
|
|
280
|
+
const res = await fetch(callsUrl("/start"), {
|
|
281
|
+
method: "POST",
|
|
282
|
+
headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
|
|
283
|
+
body: "not-json{{",
|
|
262
284
|
});
|
|
263
285
|
|
|
264
286
|
expect(res.status).toBe(400);
|
|
265
|
-
const body = await res.json() as {
|
|
266
|
-
|
|
287
|
+
const body = (await res.json()) as {
|
|
288
|
+
error: { message: string; code?: string };
|
|
289
|
+
};
|
|
290
|
+
expect(body.error.message).toContain("Invalid JSON");
|
|
267
291
|
|
|
268
292
|
await stopServer();
|
|
269
293
|
});
|
|
270
294
|
|
|
271
|
-
test(
|
|
295
|
+
test("POST /v1/calls/start with callerIdentityMode user_number is accepted", async () => {
|
|
272
296
|
await startServer();
|
|
273
|
-
ensureConversation(
|
|
297
|
+
ensureConversation("conv-start-identity-1");
|
|
274
298
|
|
|
275
|
-
const res = await fetch(callsUrl(
|
|
276
|
-
method:
|
|
277
|
-
headers: {
|
|
299
|
+
const res = await fetch(callsUrl("/start"), {
|
|
300
|
+
method: "POST",
|
|
301
|
+
headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
|
|
278
302
|
body: JSON.stringify({
|
|
279
|
-
phoneNumber:
|
|
280
|
-
task:
|
|
281
|
-
conversationId:
|
|
282
|
-
callerIdentityMode:
|
|
303
|
+
phoneNumber: "+15559998888",
|
|
304
|
+
task: "Book a table for two",
|
|
305
|
+
conversationId: "conv-start-identity-1",
|
|
306
|
+
callerIdentityMode: "user_number",
|
|
283
307
|
}),
|
|
284
308
|
});
|
|
285
309
|
|
|
286
310
|
// user_number mode requires a configured user phone number;
|
|
287
311
|
// since we haven't set one, this should return a 400 explaining why
|
|
288
312
|
expect(res.status).toBe(400);
|
|
289
|
-
const body = await res.json() as {
|
|
290
|
-
|
|
313
|
+
const body = (await res.json()) as {
|
|
314
|
+
error: { message: string; code?: string };
|
|
315
|
+
};
|
|
316
|
+
expect(body.error.message).toContain("user_number");
|
|
291
317
|
|
|
292
318
|
await stopServer();
|
|
293
319
|
});
|
|
294
320
|
|
|
295
|
-
test(
|
|
321
|
+
test("POST /v1/calls/start without callerIdentityMode defaults to assistant_number", async () => {
|
|
296
322
|
await startServer();
|
|
297
|
-
ensureConversation(
|
|
323
|
+
ensureConversation("conv-start-identity-2");
|
|
298
324
|
|
|
299
|
-
const res = await fetch(callsUrl(
|
|
300
|
-
method:
|
|
301
|
-
headers: {
|
|
325
|
+
const res = await fetch(callsUrl("/start"), {
|
|
326
|
+
method: "POST",
|
|
327
|
+
headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
|
|
302
328
|
body: JSON.stringify({
|
|
303
|
-
phoneNumber:
|
|
304
|
-
task:
|
|
305
|
-
conversationId:
|
|
329
|
+
phoneNumber: "+15559998888",
|
|
330
|
+
task: "Book a table for two",
|
|
331
|
+
conversationId: "conv-start-identity-2",
|
|
306
332
|
}),
|
|
307
333
|
});
|
|
308
334
|
|
|
309
335
|
expect(res.status).toBe(201);
|
|
310
336
|
|
|
311
|
-
const body = await res.json() as {
|
|
337
|
+
const body = (await res.json()) as {
|
|
312
338
|
callSessionId: string;
|
|
313
339
|
callSid: string;
|
|
314
340
|
status: string;
|
|
@@ -318,50 +344,52 @@ describe('runtime call routes — HTTP layer', () => {
|
|
|
318
344
|
};
|
|
319
345
|
|
|
320
346
|
expect(body.callSessionId).toBeDefined();
|
|
321
|
-
expect(body.callSid).toBe(
|
|
322
|
-
expect(body.fromNumber).toBe(
|
|
323
|
-
expect(body.callerIdentityMode).toBe(
|
|
347
|
+
expect(body.callSid).toBe("CA_mock_sid_123");
|
|
348
|
+
expect(body.fromNumber).toBe("+15550001111");
|
|
349
|
+
expect(body.callerIdentityMode).toBe("assistant_number");
|
|
324
350
|
|
|
325
351
|
await stopServer();
|
|
326
352
|
});
|
|
327
353
|
|
|
328
|
-
test(
|
|
354
|
+
test("POST /v1/calls/start returns 400 for invalid callerIdentityMode", async () => {
|
|
329
355
|
await startServer();
|
|
330
|
-
ensureConversation(
|
|
356
|
+
ensureConversation("conv-start-identity-bogus");
|
|
331
357
|
|
|
332
|
-
const res = await fetch(callsUrl(
|
|
333
|
-
method:
|
|
334
|
-
headers: {
|
|
358
|
+
const res = await fetch(callsUrl("/start"), {
|
|
359
|
+
method: "POST",
|
|
360
|
+
headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
|
|
335
361
|
body: JSON.stringify({
|
|
336
|
-
phoneNumber:
|
|
337
|
-
task:
|
|
338
|
-
conversationId:
|
|
339
|
-
callerIdentityMode:
|
|
362
|
+
phoneNumber: "+15559998888",
|
|
363
|
+
task: "Book a table for two",
|
|
364
|
+
conversationId: "conv-start-identity-bogus",
|
|
365
|
+
callerIdentityMode: "bogus",
|
|
340
366
|
}),
|
|
341
367
|
});
|
|
342
368
|
|
|
343
369
|
expect(res.status).toBe(400);
|
|
344
|
-
const body = await res.json() as {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
expect(body.error.message).toContain(
|
|
348
|
-
expect(body.error.message).toContain(
|
|
370
|
+
const body = (await res.json()) as {
|
|
371
|
+
error: { message: string; code?: string };
|
|
372
|
+
};
|
|
373
|
+
expect(body.error.message).toContain("Invalid callerIdentityMode");
|
|
374
|
+
expect(body.error.message).toContain("bogus");
|
|
375
|
+
expect(body.error.message).toContain("assistant_number");
|
|
376
|
+
expect(body.error.message).toContain("user_number");
|
|
349
377
|
|
|
350
378
|
await stopServer();
|
|
351
379
|
});
|
|
352
380
|
|
|
353
381
|
// ── GET /v1/calls/:id ───────────────────────────────────────────────
|
|
354
382
|
|
|
355
|
-
test(
|
|
383
|
+
test("GET /v1/calls/:id returns call status", async () => {
|
|
356
384
|
await startServer();
|
|
357
|
-
ensureConversation(
|
|
385
|
+
ensureConversation("conv-get-1");
|
|
358
386
|
|
|
359
387
|
const session = createCallSession({
|
|
360
|
-
conversationId:
|
|
361
|
-
provider:
|
|
362
|
-
fromNumber:
|
|
363
|
-
toNumber:
|
|
364
|
-
task:
|
|
388
|
+
conversationId: "conv-get-1",
|
|
389
|
+
provider: "twilio",
|
|
390
|
+
fromNumber: "+15550001111",
|
|
391
|
+
toNumber: "+15559998888",
|
|
392
|
+
task: "Test task",
|
|
365
393
|
});
|
|
366
394
|
|
|
367
395
|
const res = await fetch(callsUrl(`/${session.id}`), {
|
|
@@ -370,7 +398,7 @@ describe('runtime call routes — HTTP layer', () => {
|
|
|
370
398
|
|
|
371
399
|
expect(res.status).toBe(200);
|
|
372
400
|
|
|
373
|
-
const body = await res.json() as {
|
|
401
|
+
const body = (await res.json()) as {
|
|
374
402
|
callSessionId: string;
|
|
375
403
|
status: string;
|
|
376
404
|
toNumber: string;
|
|
@@ -380,19 +408,19 @@ describe('runtime call routes — HTTP layer', () => {
|
|
|
380
408
|
};
|
|
381
409
|
|
|
382
410
|
expect(body.callSessionId).toBe(session.id);
|
|
383
|
-
expect(body.status).toBe(
|
|
384
|
-
expect(body.toNumber).toBe(
|
|
385
|
-
expect(body.fromNumber).toBe(
|
|
386
|
-
expect(body.task).toBe(
|
|
411
|
+
expect(body.status).toBe("initiated");
|
|
412
|
+
expect(body.toNumber).toBe("+15559998888");
|
|
413
|
+
expect(body.fromNumber).toBe("+15550001111");
|
|
414
|
+
expect(body.task).toBe("Test task");
|
|
387
415
|
expect(body.pendingQuestion).toBeNull();
|
|
388
416
|
|
|
389
417
|
await stopServer();
|
|
390
418
|
});
|
|
391
419
|
|
|
392
|
-
test(
|
|
420
|
+
test("GET /v1/calls/:id returns 404 for unknown session", async () => {
|
|
393
421
|
await startServer();
|
|
394
422
|
|
|
395
|
-
const res = await fetch(callsUrl(
|
|
423
|
+
const res = await fetch(callsUrl("/nonexistent-id"), {
|
|
396
424
|
headers: AUTH_HEADERS,
|
|
397
425
|
});
|
|
398
426
|
|
|
@@ -403,48 +431,51 @@ describe('runtime call routes — HTTP layer', () => {
|
|
|
403
431
|
|
|
404
432
|
// ── POST /v1/calls/:id/cancel ──────────────────────────────────────
|
|
405
433
|
|
|
406
|
-
test(
|
|
434
|
+
test("POST /v1/calls/:id/cancel transitions to cancelled", async () => {
|
|
407
435
|
await startServer();
|
|
408
|
-
ensureConversation(
|
|
436
|
+
ensureConversation("conv-cancel-1");
|
|
409
437
|
|
|
410
438
|
const session = createCallSession({
|
|
411
|
-
conversationId:
|
|
412
|
-
provider:
|
|
413
|
-
fromNumber:
|
|
414
|
-
toNumber:
|
|
439
|
+
conversationId: "conv-cancel-1",
|
|
440
|
+
provider: "twilio",
|
|
441
|
+
fromNumber: "+15550001111",
|
|
442
|
+
toNumber: "+15559998888",
|
|
415
443
|
});
|
|
416
444
|
|
|
417
445
|
const res = await fetch(callsUrl(`/${session.id}/cancel`), {
|
|
418
|
-
method:
|
|
419
|
-
headers: {
|
|
420
|
-
body: JSON.stringify({ reason:
|
|
446
|
+
method: "POST",
|
|
447
|
+
headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
|
|
448
|
+
body: JSON.stringify({ reason: "User requested" }),
|
|
421
449
|
});
|
|
422
450
|
|
|
423
451
|
expect(res.status).toBe(200);
|
|
424
452
|
|
|
425
|
-
const body = await res.json() as {
|
|
453
|
+
const body = (await res.json()) as {
|
|
454
|
+
callSessionId: string;
|
|
455
|
+
status: string;
|
|
456
|
+
};
|
|
426
457
|
expect(body.callSessionId).toBe(session.id);
|
|
427
|
-
expect(body.status).toBe(
|
|
458
|
+
expect(body.status).toBe("cancelled");
|
|
428
459
|
|
|
429
460
|
await stopServer();
|
|
430
461
|
});
|
|
431
462
|
|
|
432
|
-
test(
|
|
463
|
+
test("POST /v1/calls/:id/cancel returns 409 for already-ended call", async () => {
|
|
433
464
|
await startServer();
|
|
434
|
-
ensureConversation(
|
|
465
|
+
ensureConversation("conv-cancel-2");
|
|
435
466
|
|
|
436
467
|
const session = createCallSession({
|
|
437
|
-
conversationId:
|
|
438
|
-
provider:
|
|
439
|
-
fromNumber:
|
|
440
|
-
toNumber:
|
|
468
|
+
conversationId: "conv-cancel-2",
|
|
469
|
+
provider: "twilio",
|
|
470
|
+
fromNumber: "+15550001111",
|
|
471
|
+
toNumber: "+15559998888",
|
|
441
472
|
});
|
|
442
473
|
|
|
443
|
-
updateCallSession(session.id, { status:
|
|
474
|
+
updateCallSession(session.id, { status: "completed", endedAt: Date.now() });
|
|
444
475
|
|
|
445
476
|
const res = await fetch(callsUrl(`/${session.id}/cancel`), {
|
|
446
|
-
method:
|
|
447
|
-
headers: {
|
|
477
|
+
method: "POST",
|
|
478
|
+
headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
|
|
448
479
|
body: JSON.stringify({}),
|
|
449
480
|
});
|
|
450
481
|
|
|
@@ -453,12 +484,12 @@ describe('runtime call routes — HTTP layer', () => {
|
|
|
453
484
|
await stopServer();
|
|
454
485
|
});
|
|
455
486
|
|
|
456
|
-
test(
|
|
487
|
+
test("POST /v1/calls/:id/cancel returns 404 for unknown session", async () => {
|
|
457
488
|
await startServer();
|
|
458
489
|
|
|
459
|
-
const res = await fetch(callsUrl(
|
|
460
|
-
method:
|
|
461
|
-
headers: {
|
|
490
|
+
const res = await fetch(callsUrl("/nonexistent-id/cancel"), {
|
|
491
|
+
method: "POST",
|
|
492
|
+
headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
|
|
462
493
|
body: JSON.stringify({}),
|
|
463
494
|
});
|
|
464
495
|
|
|
@@ -469,69 +500,73 @@ describe('runtime call routes — HTTP layer', () => {
|
|
|
469
500
|
|
|
470
501
|
// ── POST /v1/calls/:id/answer ──────────────────────────────────────
|
|
471
502
|
|
|
472
|
-
test(
|
|
503
|
+
test("POST /v1/calls/:id/answer returns 400 for malformed JSON", async () => {
|
|
473
504
|
await startServer();
|
|
474
|
-
ensureConversation(
|
|
505
|
+
ensureConversation("conv-answer-badjson");
|
|
475
506
|
|
|
476
507
|
const session = createCallSession({
|
|
477
|
-
conversationId:
|
|
478
|
-
provider:
|
|
479
|
-
fromNumber:
|
|
480
|
-
toNumber:
|
|
508
|
+
conversationId: "conv-answer-badjson",
|
|
509
|
+
provider: "twilio",
|
|
510
|
+
fromNumber: "+15550001111",
|
|
511
|
+
toNumber: "+15559998888",
|
|
481
512
|
});
|
|
482
513
|
|
|
483
514
|
const res = await fetch(callsUrl(`/${session.id}/answer`), {
|
|
484
|
-
method:
|
|
485
|
-
headers: {
|
|
486
|
-
body:
|
|
515
|
+
method: "POST",
|
|
516
|
+
headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
|
|
517
|
+
body: "not-json{{",
|
|
487
518
|
});
|
|
488
519
|
|
|
489
520
|
expect(res.status).toBe(400);
|
|
490
|
-
const body = await res.json() as {
|
|
491
|
-
|
|
521
|
+
const body = (await res.json()) as {
|
|
522
|
+
error: { message: string; code?: string };
|
|
523
|
+
};
|
|
524
|
+
expect(body.error.message).toContain("Invalid JSON");
|
|
492
525
|
|
|
493
526
|
await stopServer();
|
|
494
527
|
});
|
|
495
528
|
|
|
496
|
-
test(
|
|
529
|
+
test("POST /v1/calls/:id/answer returns 404 when no pending question", async () => {
|
|
497
530
|
await startServer();
|
|
498
|
-
ensureConversation(
|
|
531
|
+
ensureConversation("conv-answer-1");
|
|
499
532
|
|
|
500
533
|
const session = createCallSession({
|
|
501
|
-
conversationId:
|
|
502
|
-
provider:
|
|
503
|
-
fromNumber:
|
|
504
|
-
toNumber:
|
|
534
|
+
conversationId: "conv-answer-1",
|
|
535
|
+
provider: "twilio",
|
|
536
|
+
fromNumber: "+15550001111",
|
|
537
|
+
toNumber: "+15559998888",
|
|
505
538
|
});
|
|
506
539
|
|
|
507
540
|
const res = await fetch(callsUrl(`/${session.id}/answer`), {
|
|
508
|
-
method:
|
|
509
|
-
headers: {
|
|
510
|
-
body: JSON.stringify({ answer:
|
|
541
|
+
method: "POST",
|
|
542
|
+
headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
|
|
543
|
+
body: JSON.stringify({ answer: "Yes, please" }),
|
|
511
544
|
});
|
|
512
545
|
|
|
513
546
|
expect(res.status).toBe(409);
|
|
514
|
-
const body = await res.json() as {
|
|
515
|
-
|
|
547
|
+
const body = (await res.json()) as {
|
|
548
|
+
error: { message: string; code?: string };
|
|
549
|
+
};
|
|
550
|
+
expect(body.error.message).toContain("No active controller");
|
|
516
551
|
|
|
517
552
|
await stopServer();
|
|
518
553
|
});
|
|
519
554
|
|
|
520
|
-
test(
|
|
555
|
+
test("POST /v1/calls/:id/answer returns 400 when answer is empty", async () => {
|
|
521
556
|
await startServer();
|
|
522
|
-
ensureConversation(
|
|
557
|
+
ensureConversation("conv-answer-2");
|
|
523
558
|
|
|
524
559
|
const session = createCallSession({
|
|
525
|
-
conversationId:
|
|
526
|
-
provider:
|
|
527
|
-
fromNumber:
|
|
528
|
-
toNumber:
|
|
560
|
+
conversationId: "conv-answer-2",
|
|
561
|
+
provider: "twilio",
|
|
562
|
+
fromNumber: "+15550001111",
|
|
563
|
+
toNumber: "+15559998888",
|
|
529
564
|
});
|
|
530
565
|
|
|
531
566
|
const res = await fetch(callsUrl(`/${session.id}/answer`), {
|
|
532
|
-
method:
|
|
533
|
-
headers: {
|
|
534
|
-
body: JSON.stringify({ answer:
|
|
567
|
+
method: "POST",
|
|
568
|
+
headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
|
|
569
|
+
body: JSON.stringify({ answer: "" }),
|
|
535
570
|
});
|
|
536
571
|
|
|
537
572
|
expect(res.status).toBe(400);
|
|
@@ -539,169 +574,183 @@ describe('runtime call routes — HTTP layer', () => {
|
|
|
539
574
|
await stopServer();
|
|
540
575
|
});
|
|
541
576
|
|
|
542
|
-
test(
|
|
577
|
+
test("POST /v1/calls/:id/answer returns 409 when no orchestrator", async () => {
|
|
543
578
|
await startServer();
|
|
544
|
-
ensureConversation(
|
|
579
|
+
ensureConversation("conv-answer-3");
|
|
545
580
|
|
|
546
581
|
const session = createCallSession({
|
|
547
|
-
conversationId:
|
|
548
|
-
provider:
|
|
549
|
-
fromNumber:
|
|
550
|
-
toNumber:
|
|
582
|
+
conversationId: "conv-answer-3",
|
|
583
|
+
provider: "twilio",
|
|
584
|
+
fromNumber: "+15550001111",
|
|
585
|
+
toNumber: "+15559998888",
|
|
551
586
|
});
|
|
552
587
|
|
|
553
588
|
// Create a pending question but no orchestrator
|
|
554
|
-
createPendingQuestion(session.id,
|
|
589
|
+
createPendingQuestion(session.id, "What date do you prefer?");
|
|
555
590
|
|
|
556
591
|
const res = await fetch(callsUrl(`/${session.id}/answer`), {
|
|
557
|
-
method:
|
|
558
|
-
headers: {
|
|
559
|
-
body: JSON.stringify({ answer:
|
|
592
|
+
method: "POST",
|
|
593
|
+
headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
|
|
594
|
+
body: JSON.stringify({ answer: "Tomorrow" }),
|
|
560
595
|
});
|
|
561
596
|
|
|
562
597
|
expect(res.status).toBe(409);
|
|
563
|
-
const body = await res.json() as {
|
|
564
|
-
|
|
598
|
+
const body = (await res.json()) as {
|
|
599
|
+
error: { message: string; code?: string };
|
|
600
|
+
};
|
|
601
|
+
expect(body.error.message).toContain("No active controller");
|
|
565
602
|
|
|
566
603
|
await stopServer();
|
|
567
604
|
});
|
|
568
605
|
|
|
569
606
|
// ── POST /v1/calls/:id/instruction ────────────────────────────────
|
|
570
607
|
|
|
571
|
-
test(
|
|
608
|
+
test("POST /v1/calls/:id/instruction returns 400 for malformed JSON", async () => {
|
|
572
609
|
await startServer();
|
|
573
|
-
ensureConversation(
|
|
610
|
+
ensureConversation("conv-instr-badjson");
|
|
574
611
|
|
|
575
612
|
const session = createCallSession({
|
|
576
|
-
conversationId:
|
|
577
|
-
provider:
|
|
578
|
-
fromNumber:
|
|
579
|
-
toNumber:
|
|
613
|
+
conversationId: "conv-instr-badjson",
|
|
614
|
+
provider: "twilio",
|
|
615
|
+
fromNumber: "+15550001111",
|
|
616
|
+
toNumber: "+15559998888",
|
|
580
617
|
});
|
|
581
618
|
|
|
582
619
|
const res = await fetch(callsUrl(`/${session.id}/instruction`), {
|
|
583
|
-
method:
|
|
584
|
-
headers: {
|
|
585
|
-
body:
|
|
620
|
+
method: "POST",
|
|
621
|
+
headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
|
|
622
|
+
body: "not-json{{",
|
|
586
623
|
});
|
|
587
624
|
|
|
588
625
|
expect(res.status).toBe(400);
|
|
589
|
-
const body = await res.json() as {
|
|
590
|
-
|
|
626
|
+
const body = (await res.json()) as {
|
|
627
|
+
error: { message: string; code?: string };
|
|
628
|
+
};
|
|
629
|
+
expect(body.error.message).toContain("Invalid JSON");
|
|
591
630
|
|
|
592
631
|
await stopServer();
|
|
593
632
|
});
|
|
594
633
|
|
|
595
|
-
test(
|
|
634
|
+
test("POST /v1/calls/:id/instruction returns 400 when instruction is empty", async () => {
|
|
596
635
|
await startServer();
|
|
597
|
-
ensureConversation(
|
|
636
|
+
ensureConversation("conv-instr-empty");
|
|
598
637
|
|
|
599
638
|
const session = createCallSession({
|
|
600
|
-
conversationId:
|
|
601
|
-
provider:
|
|
602
|
-
fromNumber:
|
|
603
|
-
toNumber:
|
|
639
|
+
conversationId: "conv-instr-empty",
|
|
640
|
+
provider: "twilio",
|
|
641
|
+
fromNumber: "+15550001111",
|
|
642
|
+
toNumber: "+15559998888",
|
|
604
643
|
});
|
|
605
644
|
|
|
606
645
|
const res = await fetch(callsUrl(`/${session.id}/instruction`), {
|
|
607
|
-
method:
|
|
608
|
-
headers: {
|
|
609
|
-
body: JSON.stringify({ instruction:
|
|
646
|
+
method: "POST",
|
|
647
|
+
headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
|
|
648
|
+
body: JSON.stringify({ instruction: "" }),
|
|
610
649
|
});
|
|
611
650
|
|
|
612
651
|
expect(res.status).toBe(400);
|
|
613
|
-
const body = await res.json() as {
|
|
614
|
-
|
|
652
|
+
const body = (await res.json()) as {
|
|
653
|
+
error: { message: string; code?: string };
|
|
654
|
+
};
|
|
655
|
+
expect(body.error.message).toContain("instructionText");
|
|
615
656
|
|
|
616
657
|
await stopServer();
|
|
617
658
|
});
|
|
618
659
|
|
|
619
|
-
test(
|
|
660
|
+
test("POST /v1/calls/:id/instruction returns 400 when instruction field is missing", async () => {
|
|
620
661
|
await startServer();
|
|
621
|
-
ensureConversation(
|
|
662
|
+
ensureConversation("conv-instr-missing");
|
|
622
663
|
|
|
623
664
|
const session = createCallSession({
|
|
624
|
-
conversationId:
|
|
625
|
-
provider:
|
|
626
|
-
fromNumber:
|
|
627
|
-
toNumber:
|
|
665
|
+
conversationId: "conv-instr-missing",
|
|
666
|
+
provider: "twilio",
|
|
667
|
+
fromNumber: "+15550001111",
|
|
668
|
+
toNumber: "+15559998888",
|
|
628
669
|
});
|
|
629
670
|
|
|
630
671
|
const res = await fetch(callsUrl(`/${session.id}/instruction`), {
|
|
631
|
-
method:
|
|
632
|
-
headers: {
|
|
672
|
+
method: "POST",
|
|
673
|
+
headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
|
|
633
674
|
body: JSON.stringify({}),
|
|
634
675
|
});
|
|
635
676
|
|
|
636
677
|
expect(res.status).toBe(400);
|
|
637
|
-
const body = await res.json() as {
|
|
638
|
-
|
|
678
|
+
const body = (await res.json()) as {
|
|
679
|
+
error: { message: string; code?: string };
|
|
680
|
+
};
|
|
681
|
+
expect(body.error.message).toContain("instructionText");
|
|
639
682
|
|
|
640
683
|
await stopServer();
|
|
641
684
|
});
|
|
642
685
|
|
|
643
|
-
test(
|
|
686
|
+
test("POST /v1/calls/:id/instruction returns 404 for unknown session", async () => {
|
|
644
687
|
await startServer();
|
|
645
688
|
|
|
646
|
-
const res = await fetch(callsUrl(
|
|
647
|
-
method:
|
|
648
|
-
headers: {
|
|
649
|
-
body: JSON.stringify({ instruction:
|
|
689
|
+
const res = await fetch(callsUrl("/nonexistent-id/instruction"), {
|
|
690
|
+
method: "POST",
|
|
691
|
+
headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
|
|
692
|
+
body: JSON.stringify({ instruction: "Speed things up" }),
|
|
650
693
|
});
|
|
651
694
|
|
|
652
695
|
expect(res.status).toBe(404);
|
|
653
|
-
const body = await res.json() as {
|
|
654
|
-
|
|
696
|
+
const body = (await res.json()) as {
|
|
697
|
+
error: { message: string; code?: string };
|
|
698
|
+
};
|
|
699
|
+
expect(body.error.message).toContain("No call session found");
|
|
655
700
|
|
|
656
701
|
await stopServer();
|
|
657
702
|
});
|
|
658
703
|
|
|
659
|
-
test(
|
|
704
|
+
test("POST /v1/calls/:id/instruction returns 409 for ended call", async () => {
|
|
660
705
|
await startServer();
|
|
661
|
-
ensureConversation(
|
|
706
|
+
ensureConversation("conv-instr-ended");
|
|
662
707
|
|
|
663
708
|
const session = createCallSession({
|
|
664
|
-
conversationId:
|
|
665
|
-
provider:
|
|
666
|
-
fromNumber:
|
|
667
|
-
toNumber:
|
|
709
|
+
conversationId: "conv-instr-ended",
|
|
710
|
+
provider: "twilio",
|
|
711
|
+
fromNumber: "+15550001111",
|
|
712
|
+
toNumber: "+15559998888",
|
|
668
713
|
});
|
|
669
714
|
|
|
670
|
-
updateCallSession(session.id, { status:
|
|
715
|
+
updateCallSession(session.id, { status: "completed", endedAt: Date.now() });
|
|
671
716
|
|
|
672
717
|
const res = await fetch(callsUrl(`/${session.id}/instruction`), {
|
|
673
|
-
method:
|
|
674
|
-
headers: {
|
|
675
|
-
body: JSON.stringify({ instruction:
|
|
718
|
+
method: "POST",
|
|
719
|
+
headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
|
|
720
|
+
body: JSON.stringify({ instruction: "Speed things up" }),
|
|
676
721
|
});
|
|
677
722
|
|
|
678
723
|
expect(res.status).toBe(409);
|
|
679
|
-
const body = await res.json() as {
|
|
680
|
-
|
|
724
|
+
const body = (await res.json()) as {
|
|
725
|
+
error: { message: string; code?: string };
|
|
726
|
+
};
|
|
727
|
+
expect(body.error.message).toContain("not active");
|
|
681
728
|
|
|
682
729
|
await stopServer();
|
|
683
730
|
});
|
|
684
731
|
|
|
685
|
-
test(
|
|
732
|
+
test("POST /v1/calls/:id/instruction returns 409 when no orchestrator", async () => {
|
|
686
733
|
await startServer();
|
|
687
|
-
ensureConversation(
|
|
734
|
+
ensureConversation("conv-instr-no-orch");
|
|
688
735
|
|
|
689
736
|
const session = createCallSession({
|
|
690
|
-
conversationId:
|
|
691
|
-
provider:
|
|
692
|
-
fromNumber:
|
|
693
|
-
toNumber:
|
|
737
|
+
conversationId: "conv-instr-no-orch",
|
|
738
|
+
provider: "twilio",
|
|
739
|
+
fromNumber: "+15550001111",
|
|
740
|
+
toNumber: "+15559998888",
|
|
694
741
|
});
|
|
695
742
|
|
|
696
743
|
const res = await fetch(callsUrl(`/${session.id}/instruction`), {
|
|
697
|
-
method:
|
|
698
|
-
headers: {
|
|
699
|
-
body: JSON.stringify({ instruction:
|
|
744
|
+
method: "POST",
|
|
745
|
+
headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
|
|
746
|
+
body: JSON.stringify({ instruction: "Speed things up" }),
|
|
700
747
|
});
|
|
701
748
|
|
|
702
749
|
expect(res.status).toBe(409);
|
|
703
|
-
const body = await res.json() as {
|
|
704
|
-
|
|
750
|
+
const body = (await res.json()) as {
|
|
751
|
+
error: { message: string; code?: string };
|
|
752
|
+
};
|
|
753
|
+
expect(body.error.message).toContain("No active controller");
|
|
705
754
|
|
|
706
755
|
await stopServer();
|
|
707
756
|
});
|