@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
|
@@ -1,26 +1,28 @@
|
|
|
1
|
-
import { randomBytes } from
|
|
2
|
-
import { existsSync, mkdirSync, rmSync, writeFileSync } from
|
|
3
|
-
import { tmpdir } from
|
|
4
|
-
import { join } from
|
|
5
|
-
|
|
6
|
-
import { afterEach, beforeEach, describe, expect, mock,test } from 'bun:test';
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
7
6
|
|
|
8
7
|
// ---------------------------------------------------------------------------
|
|
9
8
|
// Mocks — declared before imports that depend on platform/logger
|
|
10
9
|
// ---------------------------------------------------------------------------
|
|
11
10
|
|
|
12
|
-
const TEST_DIR = join(
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
const TEST_DIR = join(
|
|
12
|
+
tmpdir(),
|
|
13
|
+
`vellum-schema-test-${randomBytes(4).toString("hex")}`,
|
|
14
|
+
);
|
|
15
|
+
const WORKSPACE_DIR = join(TEST_DIR, "workspace");
|
|
16
|
+
const CONFIG_PATH = join(WORKSPACE_DIR, "config.json");
|
|
15
17
|
|
|
16
18
|
function ensureTestDir(): void {
|
|
17
19
|
const dirs = [
|
|
18
20
|
TEST_DIR,
|
|
19
21
|
WORKSPACE_DIR,
|
|
20
|
-
join(TEST_DIR,
|
|
21
|
-
join(TEST_DIR,
|
|
22
|
-
join(TEST_DIR,
|
|
23
|
-
join(TEST_DIR,
|
|
22
|
+
join(TEST_DIR, "data"),
|
|
23
|
+
join(TEST_DIR, "memory"),
|
|
24
|
+
join(TEST_DIR, "memory", "knowledge"),
|
|
25
|
+
join(TEST_DIR, "logs"),
|
|
24
26
|
];
|
|
25
27
|
for (const dir of dirs) {
|
|
26
28
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
@@ -29,33 +31,45 @@ function ensureTestDir(): void {
|
|
|
29
31
|
|
|
30
32
|
function makeLoggerStub(): Record<string, unknown> {
|
|
31
33
|
const stub: Record<string, unknown> = {};
|
|
32
|
-
for (const m of [
|
|
33
|
-
|
|
34
|
+
for (const m of [
|
|
35
|
+
"info",
|
|
36
|
+
"warn",
|
|
37
|
+
"error",
|
|
38
|
+
"debug",
|
|
39
|
+
"trace",
|
|
40
|
+
"fatal",
|
|
41
|
+
"silent",
|
|
42
|
+
"child",
|
|
43
|
+
]) {
|
|
44
|
+
stub[m] = m === "child" ? () => makeLoggerStub() : () => {};
|
|
34
45
|
}
|
|
35
46
|
return stub;
|
|
36
47
|
}
|
|
37
48
|
|
|
38
|
-
mock.module(
|
|
49
|
+
mock.module("../util/logger.js", () => ({
|
|
39
50
|
getLogger: () => makeLoggerStub(),
|
|
40
51
|
}));
|
|
41
52
|
|
|
42
|
-
mock.module(
|
|
53
|
+
mock.module("../util/platform.js", () => ({
|
|
43
54
|
getRootDir: () => TEST_DIR,
|
|
44
55
|
getWorkspaceDir: () => WORKSPACE_DIR,
|
|
45
56
|
getWorkspaceConfigPath: () => CONFIG_PATH,
|
|
46
|
-
getDataDir: () => join(TEST_DIR,
|
|
47
|
-
getLogPath: () => join(TEST_DIR,
|
|
57
|
+
getDataDir: () => join(TEST_DIR, "data"),
|
|
58
|
+
getLogPath: () => join(TEST_DIR, "logs", "vellum.log"),
|
|
48
59
|
ensureDataDir: () => ensureTestDir(),
|
|
49
60
|
isMacOS: () => false,
|
|
50
61
|
isLinux: () => false,
|
|
51
62
|
isWindows: () => false,
|
|
52
63
|
}));
|
|
53
64
|
|
|
54
|
-
import {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
import {
|
|
65
|
+
import {
|
|
66
|
+
buildElevenLabsVoiceSpec,
|
|
67
|
+
resolveVoiceQualityProfile,
|
|
68
|
+
} from "../calls/voice-quality.js";
|
|
69
|
+
import { invalidateConfigCache, loadConfig } from "../config/loader.js";
|
|
70
|
+
import { AssistantConfigSchema } from "../config/schema.js";
|
|
71
|
+
import { _setStorePath } from "../security/encrypted-store.js";
|
|
72
|
+
import { _setBackend } from "../security/secure-keys.js";
|
|
59
73
|
|
|
60
74
|
// ---------------------------------------------------------------------------
|
|
61
75
|
// Helpers
|
|
@@ -69,14 +83,18 @@ function writeConfig(obj: unknown): void {
|
|
|
69
83
|
// Tests: Zod schema (unit)
|
|
70
84
|
// ---------------------------------------------------------------------------
|
|
71
85
|
|
|
72
|
-
describe(
|
|
73
|
-
test(
|
|
86
|
+
describe("AssistantConfigSchema", () => {
|
|
87
|
+
test("parses empty object with full defaults", () => {
|
|
74
88
|
const result = AssistantConfigSchema.parse({});
|
|
75
|
-
expect(result.provider).toBe(
|
|
76
|
-
expect(result.model).toBe(
|
|
89
|
+
expect(result.provider).toBe("anthropic");
|
|
90
|
+
expect(result.model).toBe("claude-opus-4-6");
|
|
77
91
|
expect(result.maxTokens).toBe(16000);
|
|
78
92
|
expect(result.apiKeys).toEqual({});
|
|
79
|
-
expect(result.thinking).toEqual({
|
|
93
|
+
expect(result.thinking).toEqual({
|
|
94
|
+
enabled: false,
|
|
95
|
+
budgetTokens: 10000,
|
|
96
|
+
streamThinking: false,
|
|
97
|
+
});
|
|
80
98
|
expect(result.contextWindow).toEqual({
|
|
81
99
|
enabled: true,
|
|
82
100
|
maxInputTokens: 200000,
|
|
@@ -96,74 +114,97 @@ describe('AssistantConfigSchema', () => {
|
|
|
96
114
|
expect(result.sandbox).toEqual({
|
|
97
115
|
enabled: true,
|
|
98
116
|
});
|
|
99
|
-
expect(result.rateLimit).toEqual({
|
|
100
|
-
|
|
117
|
+
expect(result.rateLimit).toEqual({
|
|
118
|
+
maxRequestsPerMinute: 0,
|
|
119
|
+
maxTokensPerSession: 0,
|
|
120
|
+
});
|
|
121
|
+
expect(result.secretDetection).toEqual({
|
|
122
|
+
enabled: true,
|
|
123
|
+
action: "redact",
|
|
124
|
+
entropyThreshold: 4.0,
|
|
125
|
+
allowOneTimeSend: false,
|
|
126
|
+
blockIngress: true,
|
|
127
|
+
});
|
|
101
128
|
expect(result.auditLog).toEqual({ retentionDays: 0 });
|
|
102
129
|
});
|
|
103
130
|
|
|
104
|
-
test(
|
|
131
|
+
test("accepts valid complete config", () => {
|
|
105
132
|
const input = {
|
|
106
|
-
provider:
|
|
107
|
-
model:
|
|
133
|
+
provider: "openai",
|
|
134
|
+
model: "gpt-4",
|
|
108
135
|
maxTokens: 4096,
|
|
109
|
-
apiKeys: { openai:
|
|
136
|
+
apiKeys: { openai: "sk-test" },
|
|
110
137
|
thinking: { enabled: true, budgetTokens: 5000 },
|
|
111
|
-
timeouts: {
|
|
138
|
+
timeouts: {
|
|
139
|
+
shellDefaultTimeoutSec: 30,
|
|
140
|
+
shellMaxTimeoutSec: 300,
|
|
141
|
+
permissionTimeoutSec: 60,
|
|
142
|
+
},
|
|
112
143
|
sandbox: { enabled: true },
|
|
113
144
|
rateLimit: { maxRequestsPerMinute: 10, maxTokensPerSession: 100000 },
|
|
114
|
-
secretDetection: {
|
|
145
|
+
secretDetection: {
|
|
146
|
+
enabled: false,
|
|
147
|
+
action: "block" as const,
|
|
148
|
+
entropyThreshold: 5.5,
|
|
149
|
+
},
|
|
115
150
|
auditLog: { retentionDays: 30 },
|
|
116
151
|
};
|
|
117
152
|
const result = AssistantConfigSchema.parse(input);
|
|
118
|
-
expect(result.provider).toBe(
|
|
119
|
-
expect(result.model).toBe(
|
|
153
|
+
expect(result.provider).toBe("openai");
|
|
154
|
+
expect(result.model).toBe("gpt-4");
|
|
120
155
|
expect(result.maxTokens).toBe(4096);
|
|
121
156
|
expect(result.thinking.enabled).toBe(true);
|
|
122
|
-
expect(result.secretDetection.action).toBe(
|
|
157
|
+
expect(result.secretDetection.action).toBe("block");
|
|
123
158
|
});
|
|
124
159
|
|
|
125
|
-
test(
|
|
160
|
+
test("applies memory.conflicts defaults", () => {
|
|
126
161
|
const result = AssistantConfigSchema.parse({});
|
|
127
162
|
expect(result.memory.conflicts).toEqual({
|
|
128
163
|
enabled: true,
|
|
129
|
-
gateMode:
|
|
164
|
+
gateMode: "soft",
|
|
130
165
|
reaskCooldownTurns: 3,
|
|
131
166
|
resolverLlmTimeoutMs: 12000,
|
|
132
167
|
relevanceThreshold: 0.3,
|
|
133
168
|
askOnIrrelevantTurns: false,
|
|
134
|
-
conflictableKinds: [
|
|
169
|
+
conflictableKinds: [
|
|
170
|
+
"preference",
|
|
171
|
+
"profile",
|
|
172
|
+
"constraint",
|
|
173
|
+
"instruction",
|
|
174
|
+
"style",
|
|
175
|
+
],
|
|
135
176
|
});
|
|
136
177
|
});
|
|
137
178
|
|
|
138
|
-
test(
|
|
179
|
+
test("rejects invalid memory.conflicts.relevanceThreshold", () => {
|
|
139
180
|
const result = AssistantConfigSchema.safeParse({
|
|
140
181
|
memory: { conflicts: { relevanceThreshold: 2 } },
|
|
141
182
|
});
|
|
142
183
|
expect(result.success).toBe(false);
|
|
143
184
|
});
|
|
144
185
|
|
|
145
|
-
test(
|
|
186
|
+
test("rejects invalid memory.conflicts.askOnIrrelevantTurns", () => {
|
|
146
187
|
const result = AssistantConfigSchema.safeParse({
|
|
147
188
|
memory: { conflicts: { askOnIrrelevantTurns: 123 } },
|
|
148
189
|
});
|
|
149
190
|
expect(result.success).toBe(false);
|
|
150
191
|
});
|
|
151
192
|
|
|
152
|
-
test(
|
|
193
|
+
test("rejects invalid memory.conflicts.conflictableKinds entry", () => {
|
|
153
194
|
const result = AssistantConfigSchema.safeParse({
|
|
154
|
-
memory: { conflicts: { conflictableKinds: [
|
|
195
|
+
memory: { conflicts: { conflictableKinds: ["invalid_kind"] } },
|
|
155
196
|
});
|
|
156
197
|
expect(result.success).toBe(false);
|
|
157
198
|
});
|
|
158
199
|
|
|
159
|
-
test(
|
|
200
|
+
test("rejects empty memory.conflicts.conflictableKinds", () => {
|
|
160
201
|
const result = AssistantConfigSchema.safeParse({
|
|
161
202
|
memory: { conflicts: { conflictableKinds: [] } },
|
|
162
203
|
});
|
|
163
204
|
expect(result.success).toBe(false);
|
|
164
205
|
});
|
|
165
206
|
|
|
166
|
-
test(
|
|
207
|
+
test("applies memory.profile defaults", () => {
|
|
167
208
|
const result = AssistantConfigSchema.parse({});
|
|
168
209
|
expect(result.memory.profile).toEqual({
|
|
169
210
|
enabled: true,
|
|
@@ -171,14 +212,14 @@ describe('AssistantConfigSchema', () => {
|
|
|
171
212
|
});
|
|
172
213
|
});
|
|
173
214
|
|
|
174
|
-
test(
|
|
215
|
+
test("rejects invalid memory.profile.maxInjectTokens", () => {
|
|
175
216
|
const result = AssistantConfigSchema.safeParse({
|
|
176
217
|
memory: { profile: { maxInjectTokens: 0 } },
|
|
177
218
|
});
|
|
178
219
|
expect(result.success).toBe(false);
|
|
179
220
|
});
|
|
180
221
|
|
|
181
|
-
test(
|
|
222
|
+
test("applies rollout defaults for dynamic budget and entity relation features", () => {
|
|
182
223
|
const result = AssistantConfigSchema.parse({});
|
|
183
224
|
expect(result.memory.retrieval.dynamicBudget).toEqual({
|
|
184
225
|
enabled: true,
|
|
@@ -201,7 +242,7 @@ describe('AssistantConfigSchema', () => {
|
|
|
201
242
|
});
|
|
202
243
|
});
|
|
203
244
|
|
|
204
|
-
test(
|
|
245
|
+
test("applies memory.cleanup defaults", () => {
|
|
205
246
|
const result = AssistantConfigSchema.parse({});
|
|
206
247
|
expect(result.memory.cleanup).toEqual({
|
|
207
248
|
enabled: true,
|
|
@@ -212,49 +253,61 @@ describe('AssistantConfigSchema', () => {
|
|
|
212
253
|
});
|
|
213
254
|
});
|
|
214
255
|
|
|
215
|
-
test(
|
|
256
|
+
test("rejects invalid memory.cleanup.enqueueIntervalMs", () => {
|
|
216
257
|
const result = AssistantConfigSchema.safeParse({
|
|
217
258
|
memory: { cleanup: { enqueueIntervalMs: 0 } },
|
|
218
259
|
});
|
|
219
260
|
expect(result.success).toBe(false);
|
|
220
261
|
});
|
|
221
262
|
|
|
222
|
-
test(
|
|
223
|
-
const result = AssistantConfigSchema.safeParse({ provider:
|
|
263
|
+
test("rejects invalid provider", () => {
|
|
264
|
+
const result = AssistantConfigSchema.safeParse({ provider: "invalid" });
|
|
224
265
|
expect(result.success).toBe(false);
|
|
225
266
|
if (!result.success) {
|
|
226
|
-
const msgs = result.error.issues.map(i => i.message);
|
|
227
|
-
expect(msgs.some(m => m.includes(
|
|
267
|
+
const msgs = result.error.issues.map((i) => i.message);
|
|
268
|
+
expect(msgs.some((m) => m.includes("provider"))).toBe(true);
|
|
228
269
|
}
|
|
229
270
|
});
|
|
230
271
|
|
|
231
|
-
test(
|
|
272
|
+
test("rejects negative maxTokens", () => {
|
|
232
273
|
const result = AssistantConfigSchema.safeParse({ maxTokens: -100 });
|
|
233
274
|
expect(result.success).toBe(false);
|
|
234
275
|
if (!result.success) {
|
|
235
|
-
expect(
|
|
276
|
+
expect(
|
|
277
|
+
result.error.issues.some((i) => i.path.includes("maxTokens")),
|
|
278
|
+
).toBe(true);
|
|
236
279
|
}
|
|
237
280
|
});
|
|
238
281
|
|
|
239
|
-
test(
|
|
282
|
+
test("rejects non-integer maxTokens", () => {
|
|
240
283
|
const result = AssistantConfigSchema.safeParse({ maxTokens: 3.14 });
|
|
241
284
|
expect(result.success).toBe(false);
|
|
242
285
|
if (!result.success) {
|
|
243
|
-
expect(
|
|
286
|
+
expect(
|
|
287
|
+
result.error.issues.some((i) => i.path.includes("maxTokens")),
|
|
288
|
+
).toBe(true);
|
|
244
289
|
}
|
|
245
290
|
});
|
|
246
291
|
|
|
247
|
-
test(
|
|
248
|
-
const result = AssistantConfigSchema.safeParse({
|
|
292
|
+
test("rejects string maxTokens", () => {
|
|
293
|
+
const result = AssistantConfigSchema.safeParse({
|
|
294
|
+
maxTokens: "not-a-number",
|
|
295
|
+
});
|
|
249
296
|
expect(result.success).toBe(false);
|
|
250
297
|
if (!result.success) {
|
|
251
|
-
expect(
|
|
298
|
+
expect(
|
|
299
|
+
result.error.issues.some((i) => i.path.includes("maxTokens")),
|
|
300
|
+
).toBe(true);
|
|
252
301
|
}
|
|
253
302
|
});
|
|
254
303
|
|
|
255
|
-
test(
|
|
304
|
+
test("rejects invalid timeout values", () => {
|
|
256
305
|
const result = AssistantConfigSchema.safeParse({
|
|
257
|
-
timeouts: {
|
|
306
|
+
timeouts: {
|
|
307
|
+
shellDefaultTimeoutSec: -5,
|
|
308
|
+
shellMaxTimeoutSec: "bad",
|
|
309
|
+
permissionTimeoutSec: 0,
|
|
310
|
+
},
|
|
258
311
|
});
|
|
259
312
|
expect(result.success).toBe(false);
|
|
260
313
|
if (!result.success) {
|
|
@@ -262,9 +315,9 @@ describe('AssistantConfigSchema', () => {
|
|
|
262
315
|
}
|
|
263
316
|
});
|
|
264
317
|
|
|
265
|
-
test(
|
|
318
|
+
test("rejects invalid thinking config", () => {
|
|
266
319
|
const result = AssistantConfigSchema.safeParse({
|
|
267
|
-
thinking: { enabled:
|
|
320
|
+
thinking: { enabled: "yes", budgetTokens: -100 },
|
|
268
321
|
});
|
|
269
322
|
expect(result.success).toBe(false);
|
|
270
323
|
if (!result.success) {
|
|
@@ -272,7 +325,7 @@ describe('AssistantConfigSchema', () => {
|
|
|
272
325
|
}
|
|
273
326
|
});
|
|
274
327
|
|
|
275
|
-
test(
|
|
328
|
+
test("rejects contextWindow targetInputTokens >= maxInputTokens", () => {
|
|
276
329
|
const result = AssistantConfigSchema.safeParse({
|
|
277
330
|
contextWindow: { maxInputTokens: 1000, targetInputTokens: 1000 },
|
|
278
331
|
});
|
|
@@ -281,60 +334,62 @@ describe('AssistantConfigSchema', () => {
|
|
|
281
334
|
expect(
|
|
282
335
|
result.error.issues.some(
|
|
283
336
|
(issue) =>
|
|
284
|
-
issue.path.join(
|
|
285
|
-
|
|
337
|
+
issue.path.join(".") === "contextWindow.targetInputTokens" &&
|
|
338
|
+
issue.message.includes(
|
|
339
|
+
"must be less than contextWindow.maxInputTokens",
|
|
340
|
+
),
|
|
286
341
|
),
|
|
287
342
|
).toBe(true);
|
|
288
343
|
}
|
|
289
344
|
});
|
|
290
345
|
|
|
291
|
-
test(
|
|
346
|
+
test("rejects invalid secretDetection.action", () => {
|
|
292
347
|
const result = AssistantConfigSchema.safeParse({
|
|
293
|
-
secretDetection: { action:
|
|
348
|
+
secretDetection: { action: "explode" },
|
|
294
349
|
});
|
|
295
350
|
expect(result.success).toBe(false);
|
|
296
351
|
if (!result.success) {
|
|
297
|
-
const msgs = result.error.issues.map(i => i.message);
|
|
298
|
-
expect(msgs.some(m => m.includes(
|
|
352
|
+
const msgs = result.error.issues.map((i) => i.message);
|
|
353
|
+
expect(msgs.some((m) => m.includes("secretDetection.action"))).toBe(true);
|
|
299
354
|
}
|
|
300
355
|
});
|
|
301
356
|
|
|
302
|
-
test(
|
|
357
|
+
test("rejects negative secretDetection.entropyThreshold", () => {
|
|
303
358
|
const result = AssistantConfigSchema.safeParse({
|
|
304
359
|
secretDetection: { entropyThreshold: -1 },
|
|
305
360
|
});
|
|
306
361
|
expect(result.success).toBe(false);
|
|
307
362
|
});
|
|
308
363
|
|
|
309
|
-
test(
|
|
364
|
+
test("rejects negative rateLimit values", () => {
|
|
310
365
|
const result = AssistantConfigSchema.safeParse({
|
|
311
366
|
rateLimit: { maxRequestsPerMinute: -1 },
|
|
312
367
|
});
|
|
313
368
|
expect(result.success).toBe(false);
|
|
314
369
|
});
|
|
315
370
|
|
|
316
|
-
test(
|
|
371
|
+
test("rejects non-integer rateLimit values", () => {
|
|
317
372
|
const result = AssistantConfigSchema.safeParse({
|
|
318
373
|
rateLimit: { maxTokensPerSession: 3.5 },
|
|
319
374
|
});
|
|
320
375
|
expect(result.success).toBe(false);
|
|
321
376
|
});
|
|
322
377
|
|
|
323
|
-
test(
|
|
378
|
+
test("rejects negative auditLog.retentionDays", () => {
|
|
324
379
|
const result = AssistantConfigSchema.safeParse({
|
|
325
380
|
auditLog: { retentionDays: -7 },
|
|
326
381
|
});
|
|
327
382
|
expect(result.success).toBe(false);
|
|
328
383
|
});
|
|
329
384
|
|
|
330
|
-
test(
|
|
385
|
+
test("rejects non-string apiKeys values", () => {
|
|
331
386
|
const result = AssistantConfigSchema.safeParse({
|
|
332
387
|
apiKeys: { anthropic: 123 },
|
|
333
388
|
});
|
|
334
389
|
expect(result.success).toBe(false);
|
|
335
390
|
});
|
|
336
391
|
|
|
337
|
-
test(
|
|
392
|
+
test("accepts partial nested objects with defaults", () => {
|
|
338
393
|
const result = AssistantConfigSchema.parse({
|
|
339
394
|
timeouts: { shellDefaultTimeoutSec: 30 },
|
|
340
395
|
});
|
|
@@ -343,7 +398,7 @@ describe('AssistantConfigSchema', () => {
|
|
|
343
398
|
expect(result.timeouts.permissionTimeoutSec).toBe(300);
|
|
344
399
|
});
|
|
345
400
|
|
|
346
|
-
test(
|
|
401
|
+
test("accepts zero for non-negative fields", () => {
|
|
347
402
|
const result = AssistantConfigSchema.parse({
|
|
348
403
|
rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
|
|
349
404
|
auditLog: { retentionDays: 0 },
|
|
@@ -353,90 +408,106 @@ describe('AssistantConfigSchema', () => {
|
|
|
353
408
|
expect(result.auditLog.retentionDays).toBe(0);
|
|
354
409
|
});
|
|
355
410
|
|
|
356
|
-
test(
|
|
357
|
-
for (const provider of [
|
|
411
|
+
test("accepts all valid provider values", () => {
|
|
412
|
+
for (const provider of [
|
|
413
|
+
"anthropic",
|
|
414
|
+
"openai",
|
|
415
|
+
"gemini",
|
|
416
|
+
"ollama",
|
|
417
|
+
] as const) {
|
|
358
418
|
const result = AssistantConfigSchema.safeParse({ provider });
|
|
359
419
|
expect(result.success).toBe(true);
|
|
360
420
|
}
|
|
361
421
|
});
|
|
362
422
|
|
|
363
|
-
test(
|
|
364
|
-
for (const action of [
|
|
365
|
-
const result = AssistantConfigSchema.safeParse({
|
|
423
|
+
test("accepts all valid secretDetection.action values", () => {
|
|
424
|
+
for (const action of ["redact", "warn", "block"] as const) {
|
|
425
|
+
const result = AssistantConfigSchema.safeParse({
|
|
426
|
+
secretDetection: { action },
|
|
427
|
+
});
|
|
366
428
|
expect(result.success).toBe(true);
|
|
367
429
|
}
|
|
368
430
|
});
|
|
369
431
|
|
|
370
|
-
test(
|
|
432
|
+
test("provides helpful error messages", () => {
|
|
371
433
|
const result = AssistantConfigSchema.safeParse({
|
|
372
|
-
provider:
|
|
434
|
+
provider: "invalid",
|
|
373
435
|
maxTokens: -1,
|
|
374
|
-
secretDetection: { action:
|
|
436
|
+
secretDetection: { action: "explode" },
|
|
375
437
|
});
|
|
376
438
|
expect(result.success).toBe(false);
|
|
377
439
|
if (!result.success) {
|
|
378
|
-
const messages = result.error.issues.map(i => i.message);
|
|
440
|
+
const messages = result.error.issues.map((i) => i.message);
|
|
379
441
|
// Should mention the valid options
|
|
380
|
-
expect(
|
|
381
|
-
|
|
382
|
-
|
|
442
|
+
expect(
|
|
443
|
+
messages.some((m) => m.includes("anthropic") && m.includes("openai")),
|
|
444
|
+
).toBe(true);
|
|
445
|
+
expect(messages.some((m) => m.includes("positive"))).toBe(true);
|
|
446
|
+
expect(
|
|
447
|
+
messages.some(
|
|
448
|
+
(m) =>
|
|
449
|
+
m.includes("redact") && m.includes("warn") && m.includes("block"),
|
|
450
|
+
),
|
|
451
|
+
).toBe(true);
|
|
383
452
|
}
|
|
384
453
|
});
|
|
385
454
|
|
|
386
|
-
test(
|
|
455
|
+
test("sandbox with only enabled still parses", () => {
|
|
387
456
|
const result = AssistantConfigSchema.parse({ sandbox: { enabled: false } });
|
|
388
457
|
expect(result.sandbox.enabled).toBe(false);
|
|
389
458
|
});
|
|
390
459
|
|
|
391
|
-
test(
|
|
460
|
+
test("rejects unknown sandbox fields", () => {
|
|
392
461
|
const result = AssistantConfigSchema.safeParse({
|
|
393
|
-
sandbox: { backend:
|
|
462
|
+
sandbox: { backend: "docker" },
|
|
394
463
|
});
|
|
395
464
|
// Unknown keys are stripped by Zod passthrough/strip, so parse should still succeed
|
|
396
465
|
// but the unknown field should not appear in the output
|
|
397
466
|
if (result.success) {
|
|
398
|
-
expect(
|
|
467
|
+
expect(
|
|
468
|
+
(result.data.sandbox as Record<string, unknown>)["backend"],
|
|
469
|
+
).toBeUndefined();
|
|
399
470
|
}
|
|
400
471
|
});
|
|
401
472
|
|
|
402
|
-
test(
|
|
473
|
+
test("defaults permissions.mode to workspace", () => {
|
|
403
474
|
const result = AssistantConfigSchema.parse({});
|
|
404
|
-
expect(result.permissions).toEqual({ mode:
|
|
475
|
+
expect(result.permissions).toEqual({ mode: "workspace" });
|
|
405
476
|
});
|
|
406
477
|
|
|
407
|
-
test(
|
|
478
|
+
test("accepts explicit permissions.mode strict", () => {
|
|
408
479
|
const result = AssistantConfigSchema.parse({
|
|
409
|
-
permissions: { mode:
|
|
480
|
+
permissions: { mode: "strict" },
|
|
410
481
|
});
|
|
411
|
-
expect(result.permissions.mode).toBe(
|
|
482
|
+
expect(result.permissions.mode).toBe("strict");
|
|
412
483
|
});
|
|
413
484
|
|
|
414
|
-
test(
|
|
485
|
+
test("accepts explicit permissions.mode legacy", () => {
|
|
415
486
|
const result = AssistantConfigSchema.parse({
|
|
416
|
-
permissions: { mode:
|
|
487
|
+
permissions: { mode: "legacy" },
|
|
417
488
|
});
|
|
418
|
-
expect(result.permissions.mode).toBe(
|
|
489
|
+
expect(result.permissions.mode).toBe("legacy");
|
|
419
490
|
});
|
|
420
491
|
|
|
421
|
-
test(
|
|
492
|
+
test("accepts explicit permissions.mode workspace", () => {
|
|
422
493
|
const result = AssistantConfigSchema.parse({
|
|
423
|
-
permissions: { mode:
|
|
494
|
+
permissions: { mode: "workspace" },
|
|
424
495
|
});
|
|
425
|
-
expect(result.permissions.mode).toBe(
|
|
496
|
+
expect(result.permissions.mode).toBe("workspace");
|
|
426
497
|
});
|
|
427
498
|
|
|
428
|
-
test(
|
|
499
|
+
test("rejects invalid permissions.mode", () => {
|
|
429
500
|
const result = AssistantConfigSchema.safeParse({
|
|
430
|
-
permissions: { mode:
|
|
501
|
+
permissions: { mode: "permissive" },
|
|
431
502
|
});
|
|
432
503
|
expect(result.success).toBe(false);
|
|
433
504
|
if (!result.success) {
|
|
434
|
-
const msgs = result.error.issues.map(i => i.message);
|
|
435
|
-
expect(msgs.some(m => m.includes(
|
|
505
|
+
const msgs = result.error.issues.map((i) => i.message);
|
|
506
|
+
expect(msgs.some((m) => m.includes("permissions.mode"))).toBe(true);
|
|
436
507
|
}
|
|
437
508
|
});
|
|
438
509
|
|
|
439
|
-
test(
|
|
510
|
+
test("applies workspaceGit defaults including interactiveGitTimeoutMs", () => {
|
|
440
511
|
const result = AssistantConfigSchema.parse({});
|
|
441
512
|
expect(result.workspaceGit).toEqual({
|
|
442
513
|
turnCommitMaxWaitMs: 4000,
|
|
@@ -466,7 +537,7 @@ describe('AssistantConfigSchema', () => {
|
|
|
466
537
|
});
|
|
467
538
|
});
|
|
468
539
|
|
|
469
|
-
test(
|
|
540
|
+
test("accepts custom workspaceGit.interactiveGitTimeoutMs", () => {
|
|
470
541
|
const result = AssistantConfigSchema.parse({
|
|
471
542
|
workspaceGit: { interactiveGitTimeoutMs: 5000 },
|
|
472
543
|
});
|
|
@@ -475,7 +546,7 @@ describe('AssistantConfigSchema', () => {
|
|
|
475
546
|
expect(result.workspaceGit.turnCommitMaxWaitMs).toBe(4000);
|
|
476
547
|
});
|
|
477
548
|
|
|
478
|
-
test(
|
|
549
|
+
test("rejects non-positive workspaceGit.interactiveGitTimeoutMs", () => {
|
|
479
550
|
const zeroResult = AssistantConfigSchema.safeParse({
|
|
480
551
|
workspaceGit: { interactiveGitTimeoutMs: 0 },
|
|
481
552
|
});
|
|
@@ -487,23 +558,23 @@ describe('AssistantConfigSchema', () => {
|
|
|
487
558
|
expect(negativeResult.success).toBe(false);
|
|
488
559
|
});
|
|
489
560
|
|
|
490
|
-
test(
|
|
561
|
+
test("rejects non-integer workspaceGit.interactiveGitTimeoutMs", () => {
|
|
491
562
|
const result = AssistantConfigSchema.safeParse({
|
|
492
563
|
workspaceGit: { interactiveGitTimeoutMs: 3.5 },
|
|
493
564
|
});
|
|
494
565
|
expect(result.success).toBe(false);
|
|
495
566
|
});
|
|
496
567
|
|
|
497
|
-
test(
|
|
568
|
+
test("rejects non-number workspaceGit.interactiveGitTimeoutMs", () => {
|
|
498
569
|
const result = AssistantConfigSchema.safeParse({
|
|
499
|
-
workspaceGit: { interactiveGitTimeoutMs:
|
|
570
|
+
workspaceGit: { interactiveGitTimeoutMs: "fast" },
|
|
500
571
|
});
|
|
501
572
|
expect(result.success).toBe(false);
|
|
502
573
|
});
|
|
503
574
|
|
|
504
575
|
// ── commitMessageLLM config ──────────────────────────────────────────
|
|
505
576
|
|
|
506
|
-
test(
|
|
577
|
+
test("default commitMessageLLM values are correct", () => {
|
|
507
578
|
const result = AssistantConfigSchema.parse({});
|
|
508
579
|
const llm = result.workspaceGit.commitMessageLLM;
|
|
509
580
|
expect(llm.enabled).toBe(false);
|
|
@@ -517,21 +588,21 @@ describe('AssistantConfigSchema', () => {
|
|
|
517
588
|
expect(llm.minRemainingTurnBudgetMs).toBe(1000);
|
|
518
589
|
});
|
|
519
590
|
|
|
520
|
-
test(
|
|
591
|
+
test("rejects negative commitMessageLLM.timeoutMs", () => {
|
|
521
592
|
const result = AssistantConfigSchema.safeParse({
|
|
522
593
|
workspaceGit: { commitMessageLLM: { timeoutMs: -1 } },
|
|
523
594
|
});
|
|
524
595
|
expect(result.success).toBe(false);
|
|
525
596
|
});
|
|
526
597
|
|
|
527
|
-
test(
|
|
598
|
+
test("rejects commitMessageLLM.temperature > 2", () => {
|
|
528
599
|
const result = AssistantConfigSchema.safeParse({
|
|
529
600
|
workspaceGit: { commitMessageLLM: { temperature: 2.5 } },
|
|
530
601
|
});
|
|
531
602
|
expect(result.success).toBe(false);
|
|
532
603
|
});
|
|
533
604
|
|
|
534
|
-
test(
|
|
605
|
+
test("breaker settings have correct defaults", () => {
|
|
535
606
|
const result = AssistantConfigSchema.parse({});
|
|
536
607
|
const breaker = result.workspaceGit.commitMessageLLM.breaker;
|
|
537
608
|
expect(breaker.openAfterFailures).toBe(3);
|
|
@@ -539,7 +610,7 @@ describe('AssistantConfigSchema', () => {
|
|
|
539
610
|
expect(breaker.backoffMaxMs).toBe(60000);
|
|
540
611
|
});
|
|
541
612
|
|
|
542
|
-
test(
|
|
613
|
+
test("accepts valid commitMessageLLM overrides", () => {
|
|
543
614
|
const result = AssistantConfigSchema.parse({
|
|
544
615
|
workspaceGit: {
|
|
545
616
|
commitMessageLLM: {
|
|
@@ -553,19 +624,23 @@ describe('AssistantConfigSchema', () => {
|
|
|
553
624
|
expect(result.workspaceGit.commitMessageLLM.enabled).toBe(true);
|
|
554
625
|
expect(result.workspaceGit.commitMessageLLM.timeoutMs).toBe(1000);
|
|
555
626
|
expect(result.workspaceGit.commitMessageLLM.temperature).toBe(0.5);
|
|
556
|
-
expect(result.workspaceGit.commitMessageLLM.breaker.openAfterFailures).toBe(
|
|
627
|
+
expect(result.workspaceGit.commitMessageLLM.breaker.openAfterFailures).toBe(
|
|
628
|
+
5,
|
|
629
|
+
);
|
|
557
630
|
// Other breaker fields should still get defaults
|
|
558
|
-
expect(result.workspaceGit.commitMessageLLM.breaker.backoffBaseMs).toBe(
|
|
631
|
+
expect(result.workspaceGit.commitMessageLLM.breaker.backoffBaseMs).toBe(
|
|
632
|
+
2000,
|
|
633
|
+
);
|
|
559
634
|
});
|
|
560
635
|
|
|
561
|
-
test(
|
|
636
|
+
test("rejects commitMessageLLM.temperature < 0", () => {
|
|
562
637
|
const result = AssistantConfigSchema.safeParse({
|
|
563
638
|
workspaceGit: { commitMessageLLM: { temperature: -0.1 } },
|
|
564
639
|
});
|
|
565
640
|
expect(result.success).toBe(false);
|
|
566
641
|
});
|
|
567
642
|
|
|
568
|
-
test(
|
|
643
|
+
test("rejects non-integer commitMessageLLM.maxTokens", () => {
|
|
569
644
|
const result = AssistantConfigSchema.safeParse({
|
|
570
645
|
workspaceGit: { commitMessageLLM: { maxTokens: 3.5 } },
|
|
571
646
|
});
|
|
@@ -574,11 +649,11 @@ describe('AssistantConfigSchema', () => {
|
|
|
574
649
|
|
|
575
650
|
// ── Calls config ────────────────────────────────────────────────────
|
|
576
651
|
|
|
577
|
-
test(
|
|
652
|
+
test("applies calls defaults", () => {
|
|
578
653
|
const result = AssistantConfigSchema.parse({});
|
|
579
654
|
expect(result.calls).toEqual({
|
|
580
655
|
enabled: true,
|
|
581
|
-
provider:
|
|
656
|
+
provider: "twilio",
|
|
582
657
|
maxDurationSeconds: 3600,
|
|
583
658
|
userConsultTimeoutSeconds: 120,
|
|
584
659
|
ttsPlaybackDelayMs: 3000,
|
|
@@ -595,19 +670,19 @@ describe('AssistantConfigSchema', () => {
|
|
|
595
670
|
denyCategories: [],
|
|
596
671
|
},
|
|
597
672
|
voice: {
|
|
598
|
-
mode:
|
|
599
|
-
language:
|
|
600
|
-
transcriptionProvider:
|
|
673
|
+
mode: "twilio_standard",
|
|
674
|
+
language: "en-US",
|
|
675
|
+
transcriptionProvider: "Deepgram",
|
|
601
676
|
fallbackToStandardOnError: true,
|
|
602
677
|
elevenlabs: {
|
|
603
|
-
voiceId:
|
|
604
|
-
voiceModelId:
|
|
678
|
+
voiceId: "",
|
|
679
|
+
voiceModelId: "",
|
|
605
680
|
speed: 1.0,
|
|
606
681
|
stability: 0.5,
|
|
607
682
|
similarityBoost: 0.75,
|
|
608
683
|
useSpeakerBoost: true,
|
|
609
|
-
agentId:
|
|
610
|
-
apiBaseUrl:
|
|
684
|
+
agentId: "",
|
|
685
|
+
apiBaseUrl: "https://api.elevenlabs.io",
|
|
611
686
|
registerCallTimeoutMs: 5000,
|
|
612
687
|
},
|
|
613
688
|
},
|
|
@@ -622,258 +697,269 @@ describe('AssistantConfigSchema', () => {
|
|
|
622
697
|
});
|
|
623
698
|
});
|
|
624
699
|
|
|
625
|
-
test(
|
|
700
|
+
test("accepts valid calls config overrides", () => {
|
|
626
701
|
const result = AssistantConfigSchema.parse({
|
|
627
702
|
calls: {
|
|
628
703
|
enabled: false,
|
|
629
704
|
maxDurationSeconds: 1800,
|
|
630
705
|
userConsultTimeoutSeconds: 60,
|
|
631
|
-
disclosure: { enabled: false, text:
|
|
632
|
-
safety: { denyCategories: [
|
|
706
|
+
disclosure: { enabled: false, text: "Custom disclosure" },
|
|
707
|
+
safety: { denyCategories: ["spam"] },
|
|
633
708
|
},
|
|
634
709
|
});
|
|
635
710
|
expect(result.calls.enabled).toBe(false);
|
|
636
711
|
expect(result.calls.maxDurationSeconds).toBe(1800);
|
|
637
712
|
expect(result.calls.userConsultTimeoutSeconds).toBe(60);
|
|
638
713
|
expect(result.calls.disclosure.enabled).toBe(false);
|
|
639
|
-
expect(result.calls.disclosure.text).toBe(
|
|
640
|
-
expect(result.calls.safety.denyCategories).toEqual([
|
|
714
|
+
expect(result.calls.disclosure.text).toBe("Custom disclosure");
|
|
715
|
+
expect(result.calls.safety.denyCategories).toEqual(["spam"]);
|
|
641
716
|
});
|
|
642
717
|
|
|
643
|
-
test(
|
|
718
|
+
test("accepts partial calls config with defaults for missing fields", () => {
|
|
644
719
|
const result = AssistantConfigSchema.parse({
|
|
645
720
|
calls: { maxDurationSeconds: 600 },
|
|
646
721
|
});
|
|
647
722
|
expect(result.calls.enabled).toBe(true);
|
|
648
723
|
expect(result.calls.maxDurationSeconds).toBe(600);
|
|
649
724
|
expect(result.calls.userConsultTimeoutSeconds).toBe(120);
|
|
650
|
-
expect(result.calls.provider).toBe(
|
|
725
|
+
expect(result.calls.provider).toBe("twilio");
|
|
651
726
|
});
|
|
652
727
|
|
|
653
|
-
test(
|
|
728
|
+
test("rejects invalid calls.enabled", () => {
|
|
654
729
|
const result = AssistantConfigSchema.safeParse({
|
|
655
|
-
calls: { enabled:
|
|
730
|
+
calls: { enabled: "yes" },
|
|
656
731
|
});
|
|
657
732
|
expect(result.success).toBe(false);
|
|
658
733
|
});
|
|
659
734
|
|
|
660
|
-
test(
|
|
735
|
+
test("rejects invalid calls.provider", () => {
|
|
661
736
|
const result = AssistantConfigSchema.safeParse({
|
|
662
|
-
calls: { provider:
|
|
737
|
+
calls: { provider: "vonage" },
|
|
663
738
|
});
|
|
664
739
|
expect(result.success).toBe(false);
|
|
665
740
|
if (!result.success) {
|
|
666
|
-
const msgs = result.error.issues.map(i => i.message);
|
|
667
|
-
expect(msgs.some(m => m.includes(
|
|
741
|
+
const msgs = result.error.issues.map((i) => i.message);
|
|
742
|
+
expect(msgs.some((m) => m.includes("calls.provider"))).toBe(true);
|
|
668
743
|
}
|
|
669
744
|
});
|
|
670
745
|
|
|
671
|
-
test(
|
|
746
|
+
test("rejects non-positive calls.maxDurationSeconds", () => {
|
|
672
747
|
const result = AssistantConfigSchema.safeParse({
|
|
673
748
|
calls: { maxDurationSeconds: 0 },
|
|
674
749
|
});
|
|
675
750
|
expect(result.success).toBe(false);
|
|
676
751
|
});
|
|
677
752
|
|
|
678
|
-
test(
|
|
753
|
+
test("rejects non-integer calls.maxDurationSeconds", () => {
|
|
679
754
|
const result = AssistantConfigSchema.safeParse({
|
|
680
755
|
calls: { maxDurationSeconds: 3.5 },
|
|
681
756
|
});
|
|
682
757
|
expect(result.success).toBe(false);
|
|
683
758
|
});
|
|
684
759
|
|
|
685
|
-
test(
|
|
760
|
+
test("rejects non-positive calls.userConsultTimeoutSeconds", () => {
|
|
686
761
|
const result = AssistantConfigSchema.safeParse({
|
|
687
762
|
calls: { userConsultTimeoutSeconds: -1 },
|
|
688
763
|
});
|
|
689
764
|
expect(result.success).toBe(false);
|
|
690
765
|
});
|
|
691
766
|
|
|
692
|
-
test(
|
|
767
|
+
test("rejects non-boolean calls.disclosure.enabled", () => {
|
|
693
768
|
const result = AssistantConfigSchema.safeParse({
|
|
694
|
-
calls: { disclosure: { enabled:
|
|
769
|
+
calls: { disclosure: { enabled: "true" } },
|
|
695
770
|
});
|
|
696
771
|
expect(result.success).toBe(false);
|
|
697
772
|
});
|
|
698
773
|
|
|
699
|
-
test(
|
|
774
|
+
test("rejects non-string calls.disclosure.text", () => {
|
|
700
775
|
const result = AssistantConfigSchema.safeParse({
|
|
701
776
|
calls: { disclosure: { text: 123 } },
|
|
702
777
|
});
|
|
703
778
|
expect(result.success).toBe(false);
|
|
704
779
|
});
|
|
705
780
|
|
|
706
|
-
test(
|
|
781
|
+
test("rejects non-array calls.safety.denyCategories", () => {
|
|
707
782
|
const result = AssistantConfigSchema.safeParse({
|
|
708
|
-
calls: { safety: { denyCategories:
|
|
783
|
+
calls: { safety: { denyCategories: "spam" } },
|
|
709
784
|
});
|
|
710
785
|
expect(result.success).toBe(false);
|
|
711
786
|
});
|
|
712
787
|
|
|
713
788
|
// ── Calls voice config ──────────────────────────────────────────────
|
|
714
789
|
|
|
715
|
-
test(
|
|
790
|
+
test("config without calls.voice parses correctly and produces defaults", () => {
|
|
716
791
|
const result = AssistantConfigSchema.parse({});
|
|
717
|
-
expect(result.calls.voice.mode).toBe(
|
|
718
|
-
expect(result.calls.voice.language).toBe(
|
|
719
|
-
expect(result.calls.voice.transcriptionProvider).toBe(
|
|
792
|
+
expect(result.calls.voice.mode).toBe("twilio_standard");
|
|
793
|
+
expect(result.calls.voice.language).toBe("en-US");
|
|
794
|
+
expect(result.calls.voice.transcriptionProvider).toBe("Deepgram");
|
|
720
795
|
expect(result.calls.voice.fallbackToStandardOnError).toBe(true);
|
|
721
|
-
expect(result.calls.voice.elevenlabs.voiceId).toBe(
|
|
722
|
-
expect(result.calls.voice.elevenlabs.voiceModelId).toBe(
|
|
796
|
+
expect(result.calls.voice.elevenlabs.voiceId).toBe("");
|
|
797
|
+
expect(result.calls.voice.elevenlabs.voiceModelId).toBe("");
|
|
723
798
|
expect(result.calls.voice.elevenlabs.speed).toBe(1.0);
|
|
724
799
|
expect(result.calls.voice.elevenlabs.stability).toBe(0.5);
|
|
725
800
|
expect(result.calls.voice.elevenlabs.similarityBoost).toBe(0.75);
|
|
726
801
|
expect(result.calls.voice.elevenlabs.useSpeakerBoost).toBe(true);
|
|
727
|
-
expect(result.calls.voice.elevenlabs.agentId).toBe(
|
|
728
|
-
expect(result.calls.voice.elevenlabs.apiBaseUrl).toBe(
|
|
802
|
+
expect(result.calls.voice.elevenlabs.agentId).toBe("");
|
|
803
|
+
expect(result.calls.voice.elevenlabs.apiBaseUrl).toBe(
|
|
804
|
+
"https://api.elevenlabs.io",
|
|
805
|
+
);
|
|
729
806
|
expect(result.calls.voice.elevenlabs.registerCallTimeoutMs).toBe(5000);
|
|
730
807
|
});
|
|
731
808
|
|
|
732
|
-
test(
|
|
809
|
+
test("legacy style field is silently stripped by schema", () => {
|
|
733
810
|
const result = AssistantConfigSchema.parse({
|
|
734
811
|
calls: { voice: { elevenlabs: { style: 0.5 } } },
|
|
735
812
|
});
|
|
736
|
-
expect(
|
|
813
|
+
expect(
|
|
814
|
+
(result.calls.voice.elevenlabs as Record<string, unknown>).style,
|
|
815
|
+
).toBeUndefined();
|
|
737
816
|
expect(result.calls.voice.elevenlabs.speed).toBe(1.0);
|
|
738
817
|
});
|
|
739
818
|
|
|
740
|
-
test(
|
|
819
|
+
test("rejects calls.voice.elevenlabs.speed below 0.7", () => {
|
|
741
820
|
const result = AssistantConfigSchema.safeParse({
|
|
742
821
|
calls: { voice: { elevenlabs: { speed: 0.5 } } },
|
|
743
822
|
});
|
|
744
823
|
expect(result.success).toBe(false);
|
|
745
824
|
});
|
|
746
825
|
|
|
747
|
-
test(
|
|
826
|
+
test("rejects calls.voice.elevenlabs.speed above 1.2", () => {
|
|
748
827
|
const result = AssistantConfigSchema.safeParse({
|
|
749
828
|
calls: { voice: { elevenlabs: { speed: 1.5 } } },
|
|
750
829
|
});
|
|
751
830
|
expect(result.success).toBe(false);
|
|
752
831
|
});
|
|
753
832
|
|
|
754
|
-
test(
|
|
833
|
+
test("accepts valid calls.voice overrides", () => {
|
|
755
834
|
const result = AssistantConfigSchema.parse({
|
|
756
835
|
calls: {
|
|
757
836
|
voice: {
|
|
758
|
-
mode:
|
|
759
|
-
language:
|
|
760
|
-
transcriptionProvider:
|
|
837
|
+
mode: "twilio_elevenlabs_tts",
|
|
838
|
+
language: "es-ES",
|
|
839
|
+
transcriptionProvider: "Google",
|
|
761
840
|
fallbackToStandardOnError: false,
|
|
762
841
|
elevenlabs: {
|
|
763
|
-
voiceId:
|
|
842
|
+
voiceId: "abc123",
|
|
764
843
|
stability: 0.8,
|
|
765
844
|
},
|
|
766
845
|
},
|
|
767
846
|
},
|
|
768
847
|
});
|
|
769
|
-
expect(result.calls.voice.mode).toBe(
|
|
770
|
-
expect(result.calls.voice.language).toBe(
|
|
771
|
-
expect(result.calls.voice.transcriptionProvider).toBe(
|
|
848
|
+
expect(result.calls.voice.mode).toBe("twilio_elevenlabs_tts");
|
|
849
|
+
expect(result.calls.voice.language).toBe("es-ES");
|
|
850
|
+
expect(result.calls.voice.transcriptionProvider).toBe("Google");
|
|
772
851
|
expect(result.calls.voice.fallbackToStandardOnError).toBe(false);
|
|
773
|
-
expect(result.calls.voice.elevenlabs.voiceId).toBe(
|
|
852
|
+
expect(result.calls.voice.elevenlabs.voiceId).toBe("abc123");
|
|
774
853
|
expect(result.calls.voice.elevenlabs.stability).toBe(0.8);
|
|
775
854
|
// Defaults preserved for unset fields
|
|
776
|
-
expect(result.calls.voice.elevenlabs.voiceModelId).toBe(
|
|
855
|
+
expect(result.calls.voice.elevenlabs.voiceModelId).toBe("");
|
|
777
856
|
expect(result.calls.voice.elevenlabs.similarityBoost).toBe(0.75);
|
|
778
857
|
});
|
|
779
858
|
|
|
780
|
-
test(
|
|
859
|
+
test("rejects invalid calls.voice.mode", () => {
|
|
781
860
|
const result = AssistantConfigSchema.safeParse({
|
|
782
|
-
calls: { voice: { mode:
|
|
861
|
+
calls: { voice: { mode: "invalid_mode" } },
|
|
783
862
|
});
|
|
784
863
|
expect(result.success).toBe(false);
|
|
785
864
|
if (!result.success) {
|
|
786
|
-
const msgs = result.error.issues.map(i => i.message);
|
|
787
|
-
expect(msgs.some(m => m.includes(
|
|
865
|
+
const msgs = result.error.issues.map((i) => i.message);
|
|
866
|
+
expect(msgs.some((m) => m.includes("calls.voice.mode"))).toBe(true);
|
|
788
867
|
}
|
|
789
868
|
});
|
|
790
869
|
|
|
791
|
-
test(
|
|
870
|
+
test("rejects invalid calls.voice.transcriptionProvider", () => {
|
|
792
871
|
const result = AssistantConfigSchema.safeParse({
|
|
793
|
-
calls: { voice: { transcriptionProvider:
|
|
872
|
+
calls: { voice: { transcriptionProvider: "AWS" } },
|
|
794
873
|
});
|
|
795
874
|
expect(result.success).toBe(false);
|
|
796
875
|
if (!result.success) {
|
|
797
|
-
const msgs = result.error.issues.map(i => i.message);
|
|
798
|
-
expect(
|
|
876
|
+
const msgs = result.error.issues.map((i) => i.message);
|
|
877
|
+
expect(
|
|
878
|
+
msgs.some((m) => m.includes("calls.voice.transcriptionProvider")),
|
|
879
|
+
).toBe(true);
|
|
799
880
|
}
|
|
800
881
|
});
|
|
801
882
|
|
|
802
|
-
test(
|
|
883
|
+
test("rejects calls.voice.elevenlabs.stability out of range", () => {
|
|
803
884
|
const result = AssistantConfigSchema.safeParse({
|
|
804
885
|
calls: { voice: { elevenlabs: { stability: 1.5 } } },
|
|
805
886
|
});
|
|
806
887
|
expect(result.success).toBe(false);
|
|
807
888
|
});
|
|
808
889
|
|
|
809
|
-
test(
|
|
890
|
+
test("rejects calls.voice.elevenlabs.registerCallTimeoutMs below 1000", () => {
|
|
810
891
|
const result = AssistantConfigSchema.safeParse({
|
|
811
892
|
calls: { voice: { elevenlabs: { registerCallTimeoutMs: 500 } } },
|
|
812
893
|
});
|
|
813
894
|
expect(result.success).toBe(false);
|
|
814
895
|
});
|
|
815
896
|
|
|
816
|
-
test(
|
|
897
|
+
test("rejects calls.voice.elevenlabs.registerCallTimeoutMs above 15000", () => {
|
|
817
898
|
const result = AssistantConfigSchema.safeParse({
|
|
818
899
|
calls: { voice: { elevenlabs: { registerCallTimeoutMs: 20000 } } },
|
|
819
900
|
});
|
|
820
901
|
expect(result.success).toBe(false);
|
|
821
902
|
});
|
|
822
903
|
|
|
823
|
-
test(
|
|
904
|
+
test("accepts optional calls.model", () => {
|
|
824
905
|
const result = AssistantConfigSchema.parse({
|
|
825
|
-
calls: { model:
|
|
906
|
+
calls: { model: "claude-haiku-4-5-20251001" },
|
|
826
907
|
});
|
|
827
|
-
expect(result.calls.model).toBe(
|
|
908
|
+
expect(result.calls.model).toBe("claude-haiku-4-5-20251001");
|
|
828
909
|
});
|
|
829
910
|
|
|
830
|
-
test(
|
|
911
|
+
test("calls.model is undefined by default", () => {
|
|
831
912
|
const result = AssistantConfigSchema.parse({});
|
|
832
913
|
expect(result.calls.model).toBeUndefined();
|
|
833
914
|
});
|
|
834
915
|
|
|
835
916
|
// ── Caller identity config ────────────────────────────────────────
|
|
836
917
|
|
|
837
|
-
test(
|
|
918
|
+
test("applies calls.callerIdentity defaults", () => {
|
|
838
919
|
const result = AssistantConfigSchema.parse({});
|
|
839
920
|
expect(result.calls.callerIdentity).toEqual({
|
|
840
921
|
allowPerCallOverride: true,
|
|
841
922
|
});
|
|
842
923
|
});
|
|
843
924
|
|
|
844
|
-
test(
|
|
925
|
+
test("accepts valid calls.callerIdentity overrides", () => {
|
|
845
926
|
const result = AssistantConfigSchema.parse({
|
|
846
927
|
calls: {
|
|
847
928
|
callerIdentity: {
|
|
848
929
|
allowPerCallOverride: false,
|
|
849
|
-
userNumber:
|
|
930
|
+
userNumber: "+14155559999",
|
|
850
931
|
},
|
|
851
932
|
},
|
|
852
933
|
});
|
|
853
934
|
expect(result.calls.callerIdentity.allowPerCallOverride).toBe(false);
|
|
854
|
-
expect(result.calls.callerIdentity.userNumber).toBe(
|
|
935
|
+
expect(result.calls.callerIdentity.userNumber).toBe("+14155559999");
|
|
855
936
|
});
|
|
856
937
|
|
|
857
|
-
test(
|
|
938
|
+
test("legacy defaultMode field is silently stripped by schema", () => {
|
|
858
939
|
// Backward compatibility: existing configs with defaultMode should parse
|
|
859
940
|
// without error — Zod strips unrecognized keys by default.
|
|
860
941
|
const result = AssistantConfigSchema.parse({
|
|
861
942
|
calls: {
|
|
862
|
-
callerIdentity: {
|
|
943
|
+
callerIdentity: {
|
|
944
|
+
defaultMode: "user_number",
|
|
945
|
+
allowPerCallOverride: true,
|
|
946
|
+
},
|
|
863
947
|
},
|
|
864
948
|
});
|
|
865
|
-
expect(
|
|
949
|
+
expect(
|
|
950
|
+
(result.calls.callerIdentity as Record<string, unknown>).defaultMode,
|
|
951
|
+
).toBeUndefined();
|
|
866
952
|
expect(result.calls.callerIdentity.allowPerCallOverride).toBe(true);
|
|
867
953
|
});
|
|
868
954
|
|
|
869
|
-
test(
|
|
955
|
+
test("rejects non-boolean calls.callerIdentity.allowPerCallOverride", () => {
|
|
870
956
|
const result = AssistantConfigSchema.safeParse({
|
|
871
|
-
calls: { callerIdentity: { allowPerCallOverride:
|
|
957
|
+
calls: { callerIdentity: { allowPerCallOverride: "yes" } },
|
|
872
958
|
});
|
|
873
959
|
expect(result.success).toBe(false);
|
|
874
960
|
});
|
|
875
961
|
|
|
876
|
-
test(
|
|
962
|
+
test("default behavior unchanged when callerIdentity omitted", () => {
|
|
877
963
|
const result = AssistantConfigSchema.parse({
|
|
878
964
|
calls: { enabled: true },
|
|
879
965
|
});
|
|
@@ -885,115 +971,115 @@ describe('AssistantConfigSchema', () => {
|
|
|
885
971
|
// Tests: Voice quality profile resolver
|
|
886
972
|
// ---------------------------------------------------------------------------
|
|
887
973
|
|
|
888
|
-
describe(
|
|
889
|
-
test(
|
|
974
|
+
describe("resolveVoiceQualityProfile", () => {
|
|
975
|
+
test("returns correct profile for twilio_standard", () => {
|
|
890
976
|
const config = AssistantConfigSchema.parse({});
|
|
891
977
|
const profile = resolveVoiceQualityProfile(config);
|
|
892
|
-
expect(profile.mode).toBe(
|
|
893
|
-
expect(profile.ttsProvider).toBe(
|
|
894
|
-
expect(profile.voice).toBe(
|
|
895
|
-
expect(profile.transcriptionProvider).toBe(
|
|
978
|
+
expect(profile.mode).toBe("twilio_standard");
|
|
979
|
+
expect(profile.ttsProvider).toBe("Google");
|
|
980
|
+
expect(profile.voice).toBe("Google.en-US-Journey-O");
|
|
981
|
+
expect(profile.transcriptionProvider).toBe("Deepgram");
|
|
896
982
|
expect(profile.fallbackToStandardOnError).toBe(true);
|
|
897
983
|
expect(profile.validationErrors).toEqual([]);
|
|
898
984
|
});
|
|
899
985
|
|
|
900
|
-
test(
|
|
986
|
+
test("returns correct profile for twilio_elevenlabs_tts with valid voiceId", () => {
|
|
901
987
|
const config = AssistantConfigSchema.parse({
|
|
902
988
|
calls: {
|
|
903
989
|
voice: {
|
|
904
|
-
mode:
|
|
905
|
-
elevenlabs: { voiceId:
|
|
990
|
+
mode: "twilio_elevenlabs_tts",
|
|
991
|
+
elevenlabs: { voiceId: "test-voice-id" },
|
|
906
992
|
},
|
|
907
993
|
},
|
|
908
994
|
});
|
|
909
995
|
const profile = resolveVoiceQualityProfile(config);
|
|
910
|
-
expect(profile.mode).toBe(
|
|
911
|
-
expect(profile.ttsProvider).toBe(
|
|
912
|
-
expect(profile.voice).toBe(
|
|
996
|
+
expect(profile.mode).toBe("twilio_elevenlabs_tts");
|
|
997
|
+
expect(profile.ttsProvider).toBe("ElevenLabs");
|
|
998
|
+
expect(profile.voice).toBe("test-voice-id");
|
|
913
999
|
expect(profile.validationErrors).toEqual([]);
|
|
914
1000
|
});
|
|
915
1001
|
|
|
916
|
-
test(
|
|
1002
|
+
test("falls back for twilio_elevenlabs_tts with empty voiceId and fallback enabled", () => {
|
|
917
1003
|
const config = AssistantConfigSchema.parse({
|
|
918
1004
|
calls: {
|
|
919
1005
|
voice: {
|
|
920
|
-
mode:
|
|
1006
|
+
mode: "twilio_elevenlabs_tts",
|
|
921
1007
|
fallbackToStandardOnError: true,
|
|
922
|
-
elevenlabs: { voiceId:
|
|
1008
|
+
elevenlabs: { voiceId: "" },
|
|
923
1009
|
},
|
|
924
1010
|
},
|
|
925
1011
|
});
|
|
926
1012
|
const profile = resolveVoiceQualityProfile(config);
|
|
927
|
-
expect(profile.mode).toBe(
|
|
928
|
-
expect(profile.ttsProvider).toBe(
|
|
929
|
-
expect(profile.voice).toBe(
|
|
1013
|
+
expect(profile.mode).toBe("twilio_standard");
|
|
1014
|
+
expect(profile.ttsProvider).toBe("Google");
|
|
1015
|
+
expect(profile.voice).toBe("Google.en-US-Journey-O");
|
|
930
1016
|
expect(profile.validationErrors.length).toBe(1);
|
|
931
|
-
expect(profile.validationErrors[0]).toContain(
|
|
1017
|
+
expect(profile.validationErrors[0]).toContain("falling back");
|
|
932
1018
|
});
|
|
933
1019
|
|
|
934
|
-
test(
|
|
1020
|
+
test("returns errors for twilio_elevenlabs_tts with empty voiceId and fallback disabled", () => {
|
|
935
1021
|
const config = AssistantConfigSchema.parse({
|
|
936
1022
|
calls: {
|
|
937
1023
|
voice: {
|
|
938
|
-
mode:
|
|
1024
|
+
mode: "twilio_elevenlabs_tts",
|
|
939
1025
|
fallbackToStandardOnError: false,
|
|
940
|
-
elevenlabs: { voiceId:
|
|
1026
|
+
elevenlabs: { voiceId: "" },
|
|
941
1027
|
},
|
|
942
1028
|
},
|
|
943
1029
|
});
|
|
944
1030
|
const profile = resolveVoiceQualityProfile(config);
|
|
945
|
-
expect(profile.mode).toBe(
|
|
1031
|
+
expect(profile.mode).toBe("twilio_elevenlabs_tts");
|
|
946
1032
|
expect(profile.validationErrors.length).toBe(1);
|
|
947
|
-
expect(profile.validationErrors[0]).toContain(
|
|
1033
|
+
expect(profile.validationErrors[0]).toContain("voiceId is required");
|
|
948
1034
|
});
|
|
949
1035
|
|
|
950
|
-
test(
|
|
1036
|
+
test("returns correct profile for elevenlabs_agent with valid agentId", () => {
|
|
951
1037
|
const config = AssistantConfigSchema.parse({
|
|
952
1038
|
calls: {
|
|
953
1039
|
voice: {
|
|
954
|
-
mode:
|
|
955
|
-
elevenlabs: { agentId:
|
|
1040
|
+
mode: "elevenlabs_agent",
|
|
1041
|
+
elevenlabs: { agentId: "agent-123", voiceId: "v1" },
|
|
956
1042
|
},
|
|
957
1043
|
},
|
|
958
1044
|
});
|
|
959
1045
|
const profile = resolveVoiceQualityProfile(config);
|
|
960
|
-
expect(profile.mode).toBe(
|
|
961
|
-
expect(profile.ttsProvider).toBe(
|
|
962
|
-
expect(profile.voice).toBe(
|
|
963
|
-
expect(profile.agentId).toBe(
|
|
1046
|
+
expect(profile.mode).toBe("elevenlabs_agent");
|
|
1047
|
+
expect(profile.ttsProvider).toBe("ElevenLabs");
|
|
1048
|
+
expect(profile.voice).toBe("v1");
|
|
1049
|
+
expect(profile.agentId).toBe("agent-123");
|
|
964
1050
|
expect(profile.validationErrors).toEqual([]);
|
|
965
1051
|
});
|
|
966
1052
|
|
|
967
|
-
test(
|
|
1053
|
+
test("falls back for elevenlabs_agent with empty agentId and fallback enabled", () => {
|
|
968
1054
|
const config = AssistantConfigSchema.parse({
|
|
969
1055
|
calls: {
|
|
970
1056
|
voice: {
|
|
971
|
-
mode:
|
|
1057
|
+
mode: "elevenlabs_agent",
|
|
972
1058
|
fallbackToStandardOnError: true,
|
|
973
|
-
elevenlabs: { agentId:
|
|
1059
|
+
elevenlabs: { agentId: "" },
|
|
974
1060
|
},
|
|
975
1061
|
},
|
|
976
1062
|
});
|
|
977
1063
|
const profile = resolveVoiceQualityProfile(config);
|
|
978
|
-
expect(profile.mode).toBe(
|
|
1064
|
+
expect(profile.mode).toBe("twilio_standard");
|
|
979
1065
|
expect(profile.validationErrors.length).toBe(1);
|
|
980
|
-
expect(profile.validationErrors[0]).toContain(
|
|
1066
|
+
expect(profile.validationErrors[0]).toContain("agentId is empty");
|
|
981
1067
|
});
|
|
982
1068
|
|
|
983
|
-
test(
|
|
1069
|
+
test("returns errors for elevenlabs_agent with empty agentId and fallback disabled", () => {
|
|
984
1070
|
const config = AssistantConfigSchema.parse({
|
|
985
1071
|
calls: {
|
|
986
1072
|
voice: {
|
|
987
|
-
mode:
|
|
1073
|
+
mode: "elevenlabs_agent",
|
|
988
1074
|
fallbackToStandardOnError: false,
|
|
989
|
-
elevenlabs: { agentId:
|
|
1075
|
+
elevenlabs: { agentId: "" },
|
|
990
1076
|
},
|
|
991
1077
|
},
|
|
992
1078
|
});
|
|
993
1079
|
const profile = resolveVoiceQualityProfile(config);
|
|
994
|
-
expect(profile.mode).toBe(
|
|
1080
|
+
expect(profile.mode).toBe("elevenlabs_agent");
|
|
995
1081
|
expect(profile.validationErrors.length).toBe(1);
|
|
996
|
-
expect(profile.validationErrors[0]).toContain(
|
|
1082
|
+
expect(profile.validationErrors[0]).toContain("agentId is required");
|
|
997
1083
|
});
|
|
998
1084
|
});
|
|
999
1085
|
|
|
@@ -1001,51 +1087,51 @@ describe('resolveVoiceQualityProfile', () => {
|
|
|
1001
1087
|
// Tests: buildElevenLabsVoiceSpec
|
|
1002
1088
|
// ---------------------------------------------------------------------------
|
|
1003
1089
|
|
|
1004
|
-
describe(
|
|
1005
|
-
test(
|
|
1090
|
+
describe("buildElevenLabsVoiceSpec", () => {
|
|
1091
|
+
test("produces Twilio-compliant voice string: voiceId-model-speed_stability_similarity", () => {
|
|
1006
1092
|
const spec = buildElevenLabsVoiceSpec({
|
|
1007
|
-
voiceId:
|
|
1008
|
-
voiceModelId:
|
|
1093
|
+
voiceId: "abc123",
|
|
1094
|
+
voiceModelId: "turbo_v2_5",
|
|
1009
1095
|
speed: 1.0,
|
|
1010
1096
|
stability: 0.5,
|
|
1011
1097
|
similarityBoost: 0.75,
|
|
1012
1098
|
});
|
|
1013
|
-
expect(spec).toBe(
|
|
1099
|
+
expect(spec).toBe("abc123-turbo_v2_5-1_0.5_0.75");
|
|
1014
1100
|
});
|
|
1015
1101
|
|
|
1016
|
-
test(
|
|
1102
|
+
test("returns empty string when voiceId is empty", () => {
|
|
1017
1103
|
const spec = buildElevenLabsVoiceSpec({
|
|
1018
|
-
voiceId:
|
|
1019
|
-
voiceModelId:
|
|
1104
|
+
voiceId: "",
|
|
1105
|
+
voiceModelId: "turbo_v2_5",
|
|
1020
1106
|
speed: 1.0,
|
|
1021
1107
|
stability: 0.5,
|
|
1022
1108
|
similarityBoost: 0.75,
|
|
1023
1109
|
});
|
|
1024
|
-
expect(spec).toBe(
|
|
1110
|
+
expect(spec).toBe("");
|
|
1025
1111
|
});
|
|
1026
1112
|
|
|
1027
|
-
test(
|
|
1113
|
+
test("formats custom parameters correctly", () => {
|
|
1028
1114
|
const spec = buildElevenLabsVoiceSpec({
|
|
1029
|
-
voiceId:
|
|
1030
|
-
voiceModelId:
|
|
1115
|
+
voiceId: "myVoice",
|
|
1116
|
+
voiceModelId: "eleven_multilingual_v2",
|
|
1031
1117
|
speed: 0.9,
|
|
1032
1118
|
stability: 0.8,
|
|
1033
1119
|
similarityBoost: 0.9,
|
|
1034
1120
|
});
|
|
1035
|
-
expect(spec).toBe(
|
|
1121
|
+
expect(spec).toBe("myVoice-eleven_multilingual_v2-0.9_0.8_0.9");
|
|
1036
1122
|
});
|
|
1037
1123
|
|
|
1038
|
-
test(
|
|
1124
|
+
test("default config uses a bare voiceId when no model override is set", () => {
|
|
1039
1125
|
const config = AssistantConfigSchema.parse({
|
|
1040
1126
|
calls: {
|
|
1041
1127
|
voice: {
|
|
1042
|
-
mode:
|
|
1043
|
-
elevenlabs: { voiceId:
|
|
1128
|
+
mode: "twilio_elevenlabs_tts",
|
|
1129
|
+
elevenlabs: { voiceId: "test" },
|
|
1044
1130
|
},
|
|
1045
1131
|
},
|
|
1046
1132
|
});
|
|
1047
1133
|
const spec = buildElevenLabsVoiceSpec(config.calls.voice.elevenlabs);
|
|
1048
|
-
expect(spec).toBe(
|
|
1134
|
+
expect(spec).toBe("test");
|
|
1049
1135
|
});
|
|
1050
1136
|
});
|
|
1051
1137
|
|
|
@@ -1053,15 +1139,15 @@ describe('buildElevenLabsVoiceSpec', () => {
|
|
|
1053
1139
|
// Tests: loader integration (config file -> loadConfig with fallback)
|
|
1054
1140
|
// ---------------------------------------------------------------------------
|
|
1055
1141
|
|
|
1056
|
-
describe(
|
|
1142
|
+
describe("loadConfig with schema validation", () => {
|
|
1057
1143
|
beforeEach(() => {
|
|
1058
1144
|
// Keep TEST_DIR and logs in place to avoid racing async logger stream init.
|
|
1059
1145
|
ensureTestDir();
|
|
1060
1146
|
const resetPaths = [
|
|
1061
1147
|
CONFIG_PATH,
|
|
1062
|
-
join(TEST_DIR,
|
|
1063
|
-
join(TEST_DIR,
|
|
1064
|
-
join(TEST_DIR,
|
|
1148
|
+
join(TEST_DIR, "keys.enc"),
|
|
1149
|
+
join(TEST_DIR, "data"),
|
|
1150
|
+
join(TEST_DIR, "memory"),
|
|
1065
1151
|
];
|
|
1066
1152
|
for (const path of resetPaths) {
|
|
1067
1153
|
if (existsSync(path)) {
|
|
@@ -1069,8 +1155,8 @@ describe('loadConfig with schema validation', () => {
|
|
|
1069
1155
|
}
|
|
1070
1156
|
}
|
|
1071
1157
|
ensureTestDir();
|
|
1072
|
-
_setStorePath(join(TEST_DIR,
|
|
1073
|
-
_setBackend(
|
|
1158
|
+
_setStorePath(join(TEST_DIR, "keys.enc"));
|
|
1159
|
+
_setBackend("encrypted");
|
|
1074
1160
|
invalidateConfigCache();
|
|
1075
1161
|
});
|
|
1076
1162
|
|
|
@@ -1083,25 +1169,29 @@ describe('loadConfig with schema validation', () => {
|
|
|
1083
1169
|
// Intentionally do not remove TEST_DIR in afterAll.
|
|
1084
1170
|
// A late async logger flush may still target logs under this path and can
|
|
1085
1171
|
// intermittently trigger unhandled ENOENT in CI if the directory is removed.
|
|
1086
|
-
test(
|
|
1172
|
+
test("loads valid config", () => {
|
|
1087
1173
|
writeConfig({
|
|
1088
|
-
provider:
|
|
1089
|
-
model:
|
|
1174
|
+
provider: "openai",
|
|
1175
|
+
model: "gpt-4",
|
|
1090
1176
|
maxTokens: 4096,
|
|
1091
1177
|
});
|
|
1092
1178
|
const config = loadConfig();
|
|
1093
|
-
expect(config.provider).toBe(
|
|
1094
|
-
expect(config.model).toBe(
|
|
1179
|
+
expect(config.provider).toBe("openai");
|
|
1180
|
+
expect(config.model).toBe("gpt-4");
|
|
1095
1181
|
expect(config.maxTokens).toBe(4096);
|
|
1096
1182
|
});
|
|
1097
1183
|
|
|
1098
|
-
test(
|
|
1184
|
+
test("applies defaults for missing fields", () => {
|
|
1099
1185
|
writeConfig({});
|
|
1100
1186
|
const config = loadConfig();
|
|
1101
|
-
expect(config.provider).toBe(
|
|
1102
|
-
expect(config.model).toBe(
|
|
1187
|
+
expect(config.provider).toBe("anthropic");
|
|
1188
|
+
expect(config.model).toBe("claude-opus-4-6");
|
|
1103
1189
|
expect(config.maxTokens).toBe(16000);
|
|
1104
|
-
expect(config.thinking).toEqual({
|
|
1190
|
+
expect(config.thinking).toEqual({
|
|
1191
|
+
enabled: false,
|
|
1192
|
+
budgetTokens: 10000,
|
|
1193
|
+
streamThinking: false,
|
|
1194
|
+
});
|
|
1105
1195
|
expect(config.contextWindow).toEqual({
|
|
1106
1196
|
enabled: true,
|
|
1107
1197
|
maxInputTokens: 200000,
|
|
@@ -1113,21 +1203,21 @@ describe('loadConfig with schema validation', () => {
|
|
|
1113
1203
|
});
|
|
1114
1204
|
});
|
|
1115
1205
|
|
|
1116
|
-
test(
|
|
1117
|
-
writeConfig({ provider:
|
|
1206
|
+
test("falls back to default for invalid provider", () => {
|
|
1207
|
+
writeConfig({ provider: "invalid-provider" });
|
|
1118
1208
|
const config = loadConfig();
|
|
1119
|
-
expect(config.provider).toBe(
|
|
1209
|
+
expect(config.provider).toBe("anthropic");
|
|
1120
1210
|
});
|
|
1121
1211
|
|
|
1122
|
-
test(
|
|
1212
|
+
test("falls back to default for invalid maxTokens", () => {
|
|
1123
1213
|
writeConfig({ maxTokens: -100 });
|
|
1124
1214
|
const config = loadConfig();
|
|
1125
1215
|
expect(config.maxTokens).toBe(16000);
|
|
1126
1216
|
});
|
|
1127
1217
|
|
|
1128
|
-
test(
|
|
1218
|
+
test("falls back to defaults for invalid nested values", () => {
|
|
1129
1219
|
writeConfig({
|
|
1130
|
-
timeouts: { shellDefaultTimeoutSec: -5, shellMaxTimeoutSec:
|
|
1220
|
+
timeouts: { shellDefaultTimeoutSec: -5, shellMaxTimeoutSec: "bad" },
|
|
1131
1221
|
});
|
|
1132
1222
|
const config = loadConfig();
|
|
1133
1223
|
expect(config.timeouts.shellDefaultTimeoutSec).toBe(120);
|
|
@@ -1135,28 +1225,28 @@ describe('loadConfig with schema validation', () => {
|
|
|
1135
1225
|
expect(config.timeouts.permissionTimeoutSec).toBe(300);
|
|
1136
1226
|
});
|
|
1137
1227
|
|
|
1138
|
-
test(
|
|
1228
|
+
test("preserves valid fields when other fields are invalid", () => {
|
|
1139
1229
|
writeConfig({
|
|
1140
|
-
provider:
|
|
1141
|
-
model:
|
|
1230
|
+
provider: "openai",
|
|
1231
|
+
model: "gpt-4",
|
|
1142
1232
|
maxTokens: -1,
|
|
1143
1233
|
thinking: { enabled: true, budgetTokens: 5000 },
|
|
1144
1234
|
});
|
|
1145
1235
|
const config = loadConfig();
|
|
1146
|
-
expect(config.provider).toBe(
|
|
1147
|
-
expect(config.model).toBe(
|
|
1236
|
+
expect(config.provider).toBe("openai");
|
|
1237
|
+
expect(config.model).toBe("gpt-4");
|
|
1148
1238
|
expect(config.thinking.enabled).toBe(true);
|
|
1149
1239
|
expect(config.thinking.budgetTokens).toBe(5000);
|
|
1150
1240
|
expect(config.maxTokens).toBe(16000);
|
|
1151
1241
|
});
|
|
1152
1242
|
|
|
1153
|
-
test(
|
|
1243
|
+
test("handles no config file", () => {
|
|
1154
1244
|
const config = loadConfig();
|
|
1155
|
-
expect(config.provider).toBe(
|
|
1245
|
+
expect(config.provider).toBe("anthropic");
|
|
1156
1246
|
expect(config.maxTokens).toBe(16000);
|
|
1157
1247
|
});
|
|
1158
1248
|
|
|
1159
|
-
test(
|
|
1249
|
+
test("partial nested objects get defaults for missing fields", () => {
|
|
1160
1250
|
writeConfig({
|
|
1161
1251
|
timeouts: { shellDefaultTimeoutSec: 30 },
|
|
1162
1252
|
});
|
|
@@ -1166,82 +1256,86 @@ describe('loadConfig with schema validation', () => {
|
|
|
1166
1256
|
expect(config.timeouts.permissionTimeoutSec).toBe(300);
|
|
1167
1257
|
});
|
|
1168
1258
|
|
|
1169
|
-
test(
|
|
1170
|
-
writeConfig({ secretDetection: { action:
|
|
1259
|
+
test("falls back for invalid secretDetection.action", () => {
|
|
1260
|
+
writeConfig({ secretDetection: { action: "explode" } });
|
|
1171
1261
|
const config = loadConfig();
|
|
1172
|
-
expect(config.secretDetection.action).toBe(
|
|
1262
|
+
expect(config.secretDetection.action).toBe("redact");
|
|
1173
1263
|
});
|
|
1174
1264
|
|
|
1175
|
-
test(
|
|
1176
|
-
writeConfig({ sandbox: { enabled:
|
|
1265
|
+
test("falls back for invalid sandbox.enabled", () => {
|
|
1266
|
+
writeConfig({ sandbox: { enabled: "yes" } });
|
|
1177
1267
|
const config = loadConfig();
|
|
1178
1268
|
expect(config.sandbox.enabled).toBe(true);
|
|
1179
1269
|
});
|
|
1180
1270
|
|
|
1181
|
-
test(
|
|
1271
|
+
test("loads sandbox with only enabled (backward compatibility)", () => {
|
|
1182
1272
|
writeConfig({ sandbox: { enabled: false } });
|
|
1183
1273
|
const config = loadConfig();
|
|
1184
1274
|
expect(config.sandbox.enabled).toBe(false);
|
|
1185
1275
|
});
|
|
1186
1276
|
|
|
1187
|
-
test(
|
|
1188
|
-
writeConfig({ sandbox: { enabled: true, backend:
|
|
1277
|
+
test("strips unknown sandbox fields", () => {
|
|
1278
|
+
writeConfig({ sandbox: { enabled: true, backend: "docker" } });
|
|
1189
1279
|
const config = loadConfig();
|
|
1190
1280
|
expect(config.sandbox.enabled).toBe(true);
|
|
1191
|
-
expect(
|
|
1281
|
+
expect("backend" in config.sandbox).toBe(false);
|
|
1192
1282
|
});
|
|
1193
1283
|
|
|
1194
|
-
test(
|
|
1195
|
-
writeConfig({
|
|
1284
|
+
test("falls back for invalid contextWindow relationship", () => {
|
|
1285
|
+
writeConfig({
|
|
1286
|
+
contextWindow: { maxInputTokens: 1000, targetInputTokens: 1000 },
|
|
1287
|
+
});
|
|
1196
1288
|
const config = loadConfig();
|
|
1197
1289
|
expect(config.contextWindow.maxInputTokens).toBe(200000);
|
|
1198
1290
|
expect(config.contextWindow.targetInputTokens).toBe(110000);
|
|
1199
1291
|
});
|
|
1200
1292
|
|
|
1201
|
-
test(
|
|
1202
|
-
writeConfig({
|
|
1293
|
+
test("falls back for invalid rateLimit values", () => {
|
|
1294
|
+
writeConfig({
|
|
1295
|
+
rateLimit: { maxRequestsPerMinute: -1, maxTokensPerSession: 3.5 },
|
|
1296
|
+
});
|
|
1203
1297
|
const config = loadConfig();
|
|
1204
1298
|
expect(config.rateLimit.maxRequestsPerMinute).toBe(0);
|
|
1205
1299
|
expect(config.rateLimit.maxTokensPerSession).toBe(0);
|
|
1206
1300
|
});
|
|
1207
1301
|
|
|
1208
|
-
test(
|
|
1302
|
+
test("falls back for invalid auditLog.retentionDays", () => {
|
|
1209
1303
|
writeConfig({ auditLog: { retentionDays: -7 } });
|
|
1210
1304
|
const config = loadConfig();
|
|
1211
1305
|
expect(config.auditLog.retentionDays).toBe(0);
|
|
1212
1306
|
});
|
|
1213
1307
|
|
|
1214
|
-
test(
|
|
1308
|
+
test("defaults permissions.mode to workspace when not specified", () => {
|
|
1215
1309
|
writeConfig({});
|
|
1216
1310
|
const config = loadConfig();
|
|
1217
|
-
expect(config.permissions).toEqual({ mode:
|
|
1311
|
+
expect(config.permissions).toEqual({ mode: "workspace" });
|
|
1218
1312
|
});
|
|
1219
1313
|
|
|
1220
|
-
test(
|
|
1221
|
-
writeConfig({ permissions: { mode:
|
|
1314
|
+
test("loads explicit permissions.mode strict", () => {
|
|
1315
|
+
writeConfig({ permissions: { mode: "strict" } });
|
|
1222
1316
|
const config = loadConfig();
|
|
1223
|
-
expect(config.permissions.mode).toBe(
|
|
1317
|
+
expect(config.permissions.mode).toBe("strict");
|
|
1224
1318
|
});
|
|
1225
1319
|
|
|
1226
|
-
test(
|
|
1227
|
-
writeConfig({ permissions: { mode:
|
|
1320
|
+
test("falls back for invalid permissions.mode", () => {
|
|
1321
|
+
writeConfig({ permissions: { mode: "yolo" } });
|
|
1228
1322
|
const config = loadConfig();
|
|
1229
|
-
expect(config.permissions.mode).toBe(
|
|
1323
|
+
expect(config.permissions.mode).toBe("workspace");
|
|
1230
1324
|
});
|
|
1231
1325
|
|
|
1232
|
-
test(
|
|
1326
|
+
test("does not mutate default apiKeys when fallback config is overridden by env keys", () => {
|
|
1233
1327
|
const originalAnthropicApiKey = process.env.ANTHROPIC_API_KEY;
|
|
1234
1328
|
try {
|
|
1235
|
-
const testKey = [
|
|
1329
|
+
const testKey = ["test", "in", "memory", "default", "leak"].join("-");
|
|
1236
1330
|
process.env.ANTHROPIC_API_KEY = testKey;
|
|
1237
|
-
writeConfig(
|
|
1331
|
+
writeConfig("this is not a config object");
|
|
1238
1332
|
|
|
1239
1333
|
const configWithEnv = loadConfig();
|
|
1240
1334
|
expect(configWithEnv.apiKeys.anthropic).toBe(testKey);
|
|
1241
1335
|
|
|
1242
1336
|
invalidateConfigCache();
|
|
1243
1337
|
delete process.env.ANTHROPIC_API_KEY;
|
|
1244
|
-
writeConfig(
|
|
1338
|
+
writeConfig("still not a config object");
|
|
1245
1339
|
|
|
1246
1340
|
const configWithoutEnv = loadConfig();
|
|
1247
1341
|
expect(configWithoutEnv.apiKeys.anthropic).toBeUndefined();
|
|
@@ -1256,7 +1350,7 @@ describe('loadConfig with schema validation', () => {
|
|
|
1256
1350
|
|
|
1257
1351
|
// ── Calls config (loader integration) ──────────────────────────────
|
|
1258
1352
|
|
|
1259
|
-
test(
|
|
1353
|
+
test("loads calls config from file", () => {
|
|
1260
1354
|
writeConfig({
|
|
1261
1355
|
calls: { enabled: false, maxDurationSeconds: 600 },
|
|
1262
1356
|
});
|
|
@@ -1264,16 +1358,16 @@ describe('loadConfig with schema validation', () => {
|
|
|
1264
1358
|
expect(config.calls.enabled).toBe(false);
|
|
1265
1359
|
expect(config.calls.maxDurationSeconds).toBe(600);
|
|
1266
1360
|
expect(config.calls.userConsultTimeoutSeconds).toBe(120);
|
|
1267
|
-
expect(config.calls.provider).toBe(
|
|
1361
|
+
expect(config.calls.provider).toBe("twilio");
|
|
1268
1362
|
});
|
|
1269
1363
|
|
|
1270
|
-
test(
|
|
1271
|
-
writeConfig({ calls: { provider:
|
|
1364
|
+
test("falls back for invalid calls.provider", () => {
|
|
1365
|
+
writeConfig({ calls: { provider: "vonage" } });
|
|
1272
1366
|
const config = loadConfig();
|
|
1273
|
-
expect(config.calls.provider).toBe(
|
|
1367
|
+
expect(config.calls.provider).toBe("twilio");
|
|
1274
1368
|
});
|
|
1275
1369
|
|
|
1276
|
-
test(
|
|
1370
|
+
test("applies calls defaults when not specified", () => {
|
|
1277
1371
|
writeConfig({});
|
|
1278
1372
|
const config = loadConfig();
|
|
1279
1373
|
expect(config.calls.enabled).toBe(true);
|
|
@@ -1281,10 +1375,10 @@ describe('loadConfig with schema validation', () => {
|
|
|
1281
1375
|
expect(config.calls.userConsultTimeoutSeconds).toBe(120);
|
|
1282
1376
|
expect(config.calls.disclosure.enabled).toBe(true);
|
|
1283
1377
|
expect(config.calls.safety.denyCategories).toEqual([]);
|
|
1284
|
-
expect(config.calls.voice.mode).toBe(
|
|
1285
|
-
expect(config.calls.voice.language).toBe(
|
|
1286
|
-
expect(config.calls.voice.transcriptionProvider).toBe(
|
|
1287
|
-
expect(config.calls.voice.elevenlabs.voiceId).toBe(
|
|
1378
|
+
expect(config.calls.voice.mode).toBe("twilio_standard");
|
|
1379
|
+
expect(config.calls.voice.language).toBe("en-US");
|
|
1380
|
+
expect(config.calls.voice.transcriptionProvider).toBe("Deepgram");
|
|
1381
|
+
expect(config.calls.voice.elevenlabs.voiceId).toBe("");
|
|
1288
1382
|
expect(config.calls.model).toBeUndefined();
|
|
1289
1383
|
expect(config.calls.callerIdentity).toEqual({
|
|
1290
1384
|
allowPerCallOverride: true,
|
|
@@ -1296,14 +1390,14 @@ describe('loadConfig with schema validation', () => {
|
|
|
1296
1390
|
// Tests: Call entrypoint gating
|
|
1297
1391
|
// ---------------------------------------------------------------------------
|
|
1298
1392
|
|
|
1299
|
-
describe(
|
|
1393
|
+
describe("Call entrypoint gating", () => {
|
|
1300
1394
|
beforeEach(() => {
|
|
1301
1395
|
ensureTestDir();
|
|
1302
1396
|
const resetPaths = [
|
|
1303
1397
|
CONFIG_PATH,
|
|
1304
|
-
join(TEST_DIR,
|
|
1305
|
-
join(TEST_DIR,
|
|
1306
|
-
join(TEST_DIR,
|
|
1398
|
+
join(TEST_DIR, "keys.enc"),
|
|
1399
|
+
join(TEST_DIR, "data"),
|
|
1400
|
+
join(TEST_DIR, "memory"),
|
|
1307
1401
|
];
|
|
1308
1402
|
for (const path of resetPaths) {
|
|
1309
1403
|
if (existsSync(path)) {
|
|
@@ -1311,8 +1405,8 @@ describe('Call entrypoint gating', () => {
|
|
|
1311
1405
|
}
|
|
1312
1406
|
}
|
|
1313
1407
|
ensureTestDir();
|
|
1314
|
-
_setStorePath(join(TEST_DIR,
|
|
1315
|
-
_setBackend(
|
|
1408
|
+
_setStorePath(join(TEST_DIR, "keys.enc"));
|
|
1409
|
+
_setBackend("encrypted");
|
|
1316
1410
|
invalidateConfigCache();
|
|
1317
1411
|
});
|
|
1318
1412
|
|
|
@@ -1322,39 +1416,41 @@ describe('Call entrypoint gating', () => {
|
|
|
1322
1416
|
invalidateConfigCache();
|
|
1323
1417
|
});
|
|
1324
1418
|
|
|
1325
|
-
test(
|
|
1419
|
+
test("call_start tool returns error when calls.enabled is false", async () => {
|
|
1326
1420
|
writeConfig({ calls: { enabled: false } });
|
|
1327
1421
|
// Force config reload
|
|
1328
1422
|
loadConfig();
|
|
1329
1423
|
|
|
1330
|
-
const { executeCallStart: _executeCallStart } =
|
|
1424
|
+
const { executeCallStart: _executeCallStart } =
|
|
1425
|
+
await import("../tools/calls/call-start.js");
|
|
1331
1426
|
|
|
1332
1427
|
// The tool is registered via side effect. We need to test the gating logic directly.
|
|
1333
1428
|
// Since the module registers itself, we test by loading config and checking behavior.
|
|
1334
|
-
const { getConfig } = await import(
|
|
1429
|
+
const { getConfig } = await import("../config/loader.js");
|
|
1335
1430
|
const config = getConfig();
|
|
1336
1431
|
expect(config.calls.enabled).toBe(false);
|
|
1337
1432
|
});
|
|
1338
1433
|
|
|
1339
|
-
test(
|
|
1434
|
+
test("handleStartCall route returns 403 when calls.enabled is false", async () => {
|
|
1340
1435
|
writeConfig({ calls: { enabled: false } });
|
|
1341
1436
|
loadConfig();
|
|
1342
1437
|
|
|
1343
|
-
const { handleStartCall } =
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1438
|
+
const { handleStartCall } =
|
|
1439
|
+
await import("../runtime/routes/call-routes.js");
|
|
1440
|
+
const req = new Request("http://localhost/v1/calls/start", {
|
|
1441
|
+
method: "POST",
|
|
1442
|
+
headers: { "Content-Type": "application/json" },
|
|
1347
1443
|
body: JSON.stringify({
|
|
1348
|
-
phoneNumber:
|
|
1349
|
-
task:
|
|
1350
|
-
conversationId:
|
|
1444
|
+
phoneNumber: "+14155551234",
|
|
1445
|
+
task: "Test call",
|
|
1446
|
+
conversationId: "test-conv-id",
|
|
1351
1447
|
}),
|
|
1352
1448
|
});
|
|
1353
1449
|
|
|
1354
1450
|
const response = await handleStartCall(req);
|
|
1355
1451
|
expect(response.status).toBe(403);
|
|
1356
1452
|
|
|
1357
|
-
const body = await response.json() as { error: { message: string } };
|
|
1358
|
-
expect(body.error.message).toContain(
|
|
1453
|
+
const body = (await response.json()) as { error: { message: string } };
|
|
1454
|
+
expect(body.error.message).toContain("disabled");
|
|
1359
1455
|
});
|
|
1360
1456
|
});
|