@vellumai/assistant 0.5.6 → 0.5.7
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/.env.example +16 -2
- package/ARCHITECTURE.md +6 -75
- package/Dockerfile +1 -1
- package/README.md +0 -2
- package/bun.lock +0 -414
- package/docs/architecture/keychain-broker.md +45 -240
- package/docs/architecture/security.md +0 -17
- package/docs/credential-execution-service.md +2 -2
- package/node_modules/@vellumai/ces-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +119 -0
- package/node_modules/@vellumai/credential-storage/package.json +1 -0
- package/node_modules/@vellumai/egress-proxy/package.json +1 -0
- package/package.json +2 -3
- package/src/__tests__/actor-token-service.test.ts +0 -114
- package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
- package/src/__tests__/browser-skill-endstate.test.ts +6 -5
- package/src/__tests__/btw-routes.test.ts +0 -39
- package/src/__tests__/call-domain.test.ts +0 -128
- package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
- package/src/__tests__/channel-approval-routes.test.ts +0 -5
- package/src/__tests__/channel-readiness-service.test.ts +1 -60
- package/src/__tests__/checker.test.ts +4 -2
- package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
- package/src/__tests__/config-schema-cmd.test.ts +0 -1
- package/src/__tests__/config-schema.test.ts +1 -1
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
- package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
- package/src/__tests__/conversation-skill-tools.test.ts +0 -54
- package/src/__tests__/conversation-title-service.test.ts +87 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +28 -14
- package/src/__tests__/credential-execution-managed-contract.test.ts +33 -18
- package/src/__tests__/credential-security-e2e.test.ts +0 -66
- package/src/__tests__/credential-security-invariants.test.ts +4 -45
- package/src/__tests__/credentials-cli.test.ts +78 -0
- package/src/__tests__/db-migration-rollback.test.ts +2015 -1
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +34 -143
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
- package/src/__tests__/guardian-routing-state.test.ts +0 -5
- package/src/__tests__/host-shell-tool.test.ts +6 -7
- package/src/__tests__/http-user-message-parity.test.ts +3 -103
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
- package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
- package/src/__tests__/intent-routing.test.ts +0 -13
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
- package/src/__tests__/keychain-broker-client.test.ts +161 -22
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
- package/src/__tests__/migration-export-http.test.ts +2 -2
- package/src/__tests__/migration-import-commit-http.test.ts +2 -2
- package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
- package/src/__tests__/migration-validate-http.test.ts +2 -2
- package/src/__tests__/non-member-access-request.test.ts +0 -5
- package/src/__tests__/notification-decision-fallback.test.ts +4 -0
- package/src/__tests__/notification-decision-identity.test.ts +4 -0
- package/src/__tests__/permission-types.test.ts +1 -0
- package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
- package/src/__tests__/qdrant-manager.test.ts +28 -2
- package/src/__tests__/registry.test.ts +0 -6
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
- package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
- package/src/__tests__/secure-keys.test.ts +83 -263
- package/src/__tests__/shell-identity.test.ts +96 -6
- package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
- package/src/__tests__/skill-feature-flags.test.ts +46 -45
- package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
- package/src/__tests__/skill-load-inline-command.test.ts +8 -12
- package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
- package/src/__tests__/skill-load-tool.test.ts +0 -2
- package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
- package/src/__tests__/skills.test.ts +0 -2
- package/src/__tests__/slack-inbound-verification.test.ts +0 -4
- package/src/__tests__/suggestion-routes.test.ts +1 -32
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
- package/src/__tests__/update-bulletin.test.ts +0 -2
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -6
- package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
- package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +218 -0
- package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
- package/src/calls/audio-store.test.ts +97 -0
- package/src/calls/audio-store.ts +205 -0
- package/src/calls/call-controller.ts +85 -7
- package/src/calls/call-domain.ts +3 -0
- package/src/calls/call-store.ts +10 -3
- package/src/calls/fish-audio-client.ts +117 -0
- package/src/calls/relay-server.ts +27 -0
- package/src/calls/twilio-routes.ts +2 -1
- package/src/calls/types.ts +1 -0
- package/src/calls/voice-ingress-preflight.ts +0 -42
- package/src/calls/voice-quality.ts +26 -5
- package/src/calls/voice-session-bridge.ts +6 -12
- package/src/cli/commands/config.ts +1 -4
- package/src/cli/commands/credentials.ts +34 -4
- package/src/cli/commands/oauth/index.ts +7 -0
- package/src/cli/commands/oauth/platform.ts +179 -0
- package/src/cli/commands/platform.ts +3 -3
- package/src/config/assistant-feature-flags.ts +186 -5
- package/src/config/bundled-skills/messaging/SKILL.md +5 -5
- package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
- package/src/config/bundled-skills/settings/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
- package/src/config/bundled-tool-registry.ts +1 -11
- package/src/config/env-registry.ts +1 -1
- package/src/config/env.ts +8 -14
- package/src/config/feature-flag-registry.json +48 -8
- package/src/config/loader.ts +98 -31
- package/src/config/schema.ts +4 -13
- package/src/config/schemas/calls.ts +13 -0
- package/src/config/schemas/fish-audio.ts +39 -0
- package/src/config/schemas/security.ts +0 -4
- package/src/config/types.ts +0 -1
- package/src/contacts/contact-store.ts +39 -0
- package/src/contacts/types.ts +2 -0
- package/src/credential-execution/approval-bridge.ts +1 -0
- package/src/credential-execution/executable-discovery.ts +28 -4
- package/src/credential-execution/feature-gates.ts +16 -0
- package/src/credential-execution/process-manager.ts +38 -0
- package/src/daemon/assistant-attachments.ts +9 -0
- package/src/daemon/config-watcher.ts +5 -0
- package/src/daemon/conversation-tool-setup.ts +0 -105
- package/src/daemon/conversation.ts +10 -1
- package/src/daemon/handlers/config-vercel.ts +92 -0
- package/src/daemon/handlers/skills.ts +2 -15
- package/src/daemon/install-symlink.ts +195 -0
- package/src/daemon/lifecycle.ts +227 -51
- package/src/daemon/message-types/conversations.ts +3 -4
- package/src/daemon/message-types/diagnostics.ts +3 -22
- package/src/daemon/message-types/messages.ts +0 -2
- package/src/daemon/message-types/upgrades.ts +8 -0
- package/src/daemon/server.ts +30 -92
- package/src/events/domain-events.ts +2 -1
- package/src/inbound/platform-callback-registration.ts +3 -3
- package/src/instrument.ts +8 -5
- package/src/memory/conversation-title-service.ts +50 -1
- package/src/memory/db-init.ts +12 -0
- package/src/memory/items-extractor.ts +15 -1
- package/src/memory/job-handlers/conversation-starters.ts +4 -1
- package/src/memory/jobs-store.ts +30 -5
- package/src/memory/jobs-worker.ts +31 -7
- package/src/memory/migrations/001-job-deferrals.ts +19 -0
- package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
- package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
- package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
- package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
- package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
- package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
- package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
- package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
- package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
- package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
- package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
- package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
- package/src/memory/migrations/116-messages-fts.ts +106 -1
- package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
- package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
- package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
- package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
- package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
- package/src/memory/migrations/141-rename-verification-table.ts +54 -0
- package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
- package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
- package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
- package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
- package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
- package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
- package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
- package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
- package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
- package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
- package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
- package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/migrations/registry.ts +90 -0
- package/src/memory/migrations/validate-migration-state.ts +137 -11
- package/src/memory/qdrant-circuit-breaker.ts +9 -0
- package/src/memory/qdrant-manager.ts +64 -7
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/contacts.ts +1 -0
- package/src/notifications/decision-engine.ts +4 -1
- package/src/oauth/connection-resolver.ts +6 -4
- package/src/permissions/checker.ts +0 -38
- package/src/permissions/shell-identity.ts +76 -22
- package/src/permissions/types.ts +4 -2
- package/src/platform/client.ts +35 -7
- package/src/prompts/persona-resolver.ts +138 -0
- package/src/prompts/system-prompt.ts +36 -4
- package/src/prompts/templates/users/default.md +1 -0
- package/src/providers/registry.ts +27 -40
- package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
- package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
- package/src/runtime/auth/external-assistant-id.ts +13 -59
- package/src/runtime/auth/route-policy.ts +15 -1
- package/src/runtime/auth/token-service.ts +43 -138
- package/src/runtime/channel-readiness-service.ts +1 -16
- package/src/runtime/http-server.ts +27 -2
- package/src/runtime/middleware/error-handler.ts +1 -9
- package/src/runtime/routes/audio-routes.ts +40 -0
- package/src/runtime/routes/btw-routes.ts +0 -17
- package/src/runtime/routes/conversation-query-routes.ts +63 -1
- package/src/runtime/routes/conversation-routes.ts +4 -44
- package/src/runtime/routes/diagnostics-routes.ts +1 -477
- package/src/runtime/routes/identity-routes.ts +18 -29
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
- package/src/runtime/routes/integrations/vercel.ts +89 -0
- package/src/runtime/routes/log-export-routes.ts +5 -0
- package/src/runtime/routes/memory-item-routes.ts +24 -6
- package/src/runtime/routes/migration-rollback-routes.ts +209 -0
- package/src/runtime/routes/migration-routes.ts +17 -1
- package/src/runtime/routes/notification-routes.ts +58 -0
- package/src/runtime/routes/schedule-routes.ts +65 -0
- package/src/runtime/routes/settings-routes.ts +41 -1
- package/src/runtime/routes/tts-routes.ts +86 -0
- package/src/runtime/routes/upgrade-broadcast-routes.ts +26 -2
- package/src/runtime/routes/workspace-commit-routes.ts +62 -0
- package/src/runtime/routes/workspace-routes.test.ts +22 -1
- package/src/runtime/routes/workspace-routes.ts +1 -1
- package/src/runtime/routes/workspace-utils.ts +86 -2
- package/src/security/ces-credential-client.ts +59 -22
- package/src/security/ces-rpc-credential-backend.ts +85 -0
- package/src/security/credential-backend.ts +12 -88
- package/src/security/keychain-broker-client.ts +10 -2
- package/src/security/secure-keys.ts +94 -113
- package/src/skills/catalog-install.ts +13 -7
- package/src/telemetry/usage-telemetry-reporter.ts +4 -2
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/executor.ts +0 -4
- package/src/tools/network/script-proxy/session-manager.ts +19 -4
- package/src/tools/network/web-fetch.ts +3 -1
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/types.ts +0 -8
- package/src/util/errors.ts +0 -12
- package/src/util/platform.ts +3 -50
- package/src/workspace/git-service.ts +5 -2
- package/src/workspace/migrations/001-avatar-rename.ts +15 -0
- package/src/workspace/migrations/003-seed-device-id.ts +17 -1
- package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
- package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
- package/src/workspace/migrations/006-services-config.ts +49 -0
- package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
- package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
- package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
- package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
- package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
- package/src/workspace/migrations/017-seed-persona-dirs.ts +95 -0
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +23 -1
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/workspace/migrations/runner.ts +106 -2
- package/src/workspace/migrations/types.ts +4 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
- package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
- package/src/__tests__/diagnostics-export.test.ts +0 -288
- package/src/__tests__/local-gateway-health.test.ts +0 -209
- package/src/__tests__/secret-ingress-handler.test.ts +0 -120
- package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
- package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
- package/src/__tests__/swarm-orchestrator.test.ts +0 -463
- package/src/__tests__/swarm-plan-validator.test.ts +0 -384
- package/src/__tests__/swarm-recursion.test.ts +0 -197
- package/src/__tests__/swarm-router-planner.test.ts +0 -234
- package/src/__tests__/swarm-tool.test.ts +0 -185
- package/src/__tests__/swarm-worker-backend.test.ts +0 -144
- package/src/__tests__/swarm-worker-runner.test.ts +0 -288
- package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
- package/src/commands/cc-command-registry.ts +0 -248
- package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
- package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
- package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
- package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
- package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
- package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
- package/src/config/schemas/swarm.ts +0 -82
- package/src/logfire.ts +0 -135
- package/src/runtime/local-gateway-health.ts +0 -275
- package/src/security/secret-ingress.ts +0 -68
- package/src/swarm/backend-claude-code.ts +0 -225
- package/src/swarm/checkpoint.ts +0 -137
- package/src/swarm/graph-utils.ts +0 -53
- package/src/swarm/index.ts +0 -55
- package/src/swarm/limits.ts +0 -66
- package/src/swarm/orchestrator.ts +0 -424
- package/src/swarm/plan-validator.ts +0 -117
- package/src/swarm/router-planner.ts +0 -162
- package/src/swarm/router-prompts.ts +0 -39
- package/src/swarm/synthesizer.ts +0 -81
- package/src/swarm/types.ts +0 -72
- package/src/swarm/worker-backend.ts +0 -131
- package/src/swarm/worker-prompts.ts +0 -80
- package/src/swarm/worker-runner.ts +0 -170
- package/src/tools/claude-code/claude-code.ts +0 -610
- package/src/tools/swarm/delegate.ts +0 -205
|
@@ -18,8 +18,6 @@ mock.module("../util/platform.js", () => ({
|
|
|
18
18
|
getDataDir: () => testDir,
|
|
19
19
|
getDbPath: () => join(testDir, "test.db"),
|
|
20
20
|
normalizeAssistantId: (id: string) => (id === "self" ? "self" : id),
|
|
21
|
-
readLockfile: () => null,
|
|
22
|
-
writeLockfile: () => {},
|
|
23
21
|
isMacOS: () => process.platform === "darwin",
|
|
24
22
|
isLinux: () => process.platform === "linux",
|
|
25
23
|
isWindows: () => process.platform === "win32",
|
|
@@ -57,8 +55,6 @@ import {
|
|
|
57
55
|
} from "../runtime/actor-token-store.js";
|
|
58
56
|
import { resetExternalAssistantIdCache } from "../runtime/auth/external-assistant-id.js";
|
|
59
57
|
import {
|
|
60
|
-
BootstrapAlreadyCompleted,
|
|
61
|
-
fetchSigningKeyFromGateway,
|
|
62
58
|
hashToken,
|
|
63
59
|
initAuthSigningKey,
|
|
64
60
|
} from "../runtime/auth/token-service.js";
|
|
@@ -732,113 +728,3 @@ describe("bootstrap private-network guard", () => {
|
|
|
732
728
|
});
|
|
733
729
|
});
|
|
734
730
|
|
|
735
|
-
// ---------------------------------------------------------------------------
|
|
736
|
-
// fetchSigningKeyFromGateway
|
|
737
|
-
// ---------------------------------------------------------------------------
|
|
738
|
-
|
|
739
|
-
describe("fetchSigningKeyFromGateway", () => {
|
|
740
|
-
const VALID_HEX_KEY = "a".repeat(64); // 64 hex chars = 32 bytes
|
|
741
|
-
const originalEnv = process.env.GATEWAY_INTERNAL_URL;
|
|
742
|
-
const originalFetch = globalThis.fetch;
|
|
743
|
-
|
|
744
|
-
beforeEach(() => {
|
|
745
|
-
process.env.GATEWAY_INTERNAL_URL = "http://gateway:7822";
|
|
746
|
-
});
|
|
747
|
-
|
|
748
|
-
afterAll(() => {
|
|
749
|
-
if (originalEnv !== undefined) {
|
|
750
|
-
process.env.GATEWAY_INTERNAL_URL = originalEnv;
|
|
751
|
-
} else {
|
|
752
|
-
delete process.env.GATEWAY_INTERNAL_URL;
|
|
753
|
-
}
|
|
754
|
-
globalThis.fetch = originalFetch;
|
|
755
|
-
});
|
|
756
|
-
|
|
757
|
-
test("returns 32-byte buffer on successful 200 response", async () => {
|
|
758
|
-
globalThis.fetch = (async () =>
|
|
759
|
-
new Response(JSON.stringify({ key: VALID_HEX_KEY }), {
|
|
760
|
-
status: 200,
|
|
761
|
-
headers: { "Content-Type": "application/json" },
|
|
762
|
-
})) as unknown as typeof fetch;
|
|
763
|
-
|
|
764
|
-
const key = await fetchSigningKeyFromGateway();
|
|
765
|
-
expect(key).toBeInstanceOf(Buffer);
|
|
766
|
-
expect(key.length).toBe(32);
|
|
767
|
-
expect(key.toString("hex")).toBe(VALID_HEX_KEY);
|
|
768
|
-
});
|
|
769
|
-
|
|
770
|
-
test("throws BootstrapAlreadyCompleted on 403 response", async () => {
|
|
771
|
-
globalThis.fetch = (async () =>
|
|
772
|
-
new Response("Forbidden", { status: 403 })) as unknown as typeof fetch;
|
|
773
|
-
|
|
774
|
-
await expect(fetchSigningKeyFromGateway()).rejects.toBeInstanceOf(
|
|
775
|
-
BootstrapAlreadyCompleted,
|
|
776
|
-
);
|
|
777
|
-
});
|
|
778
|
-
|
|
779
|
-
test("throws timeout error after max retry attempts on persistent failure", async () => {
|
|
780
|
-
// Mock Bun.sleep to avoid waiting 30s in tests
|
|
781
|
-
const origSleep = Bun.sleep;
|
|
782
|
-
Bun.sleep = (() => Promise.resolve()) as typeof Bun.sleep;
|
|
783
|
-
|
|
784
|
-
let callCount = 0;
|
|
785
|
-
globalThis.fetch = (async () => {
|
|
786
|
-
callCount++;
|
|
787
|
-
throw new Error("ECONNREFUSED");
|
|
788
|
-
}) as unknown as typeof fetch;
|
|
789
|
-
|
|
790
|
-
try {
|
|
791
|
-
await expect(fetchSigningKeyFromGateway()).rejects.toThrow(
|
|
792
|
-
"timed out waiting for gateway",
|
|
793
|
-
);
|
|
794
|
-
expect(callCount).toBe(30);
|
|
795
|
-
} finally {
|
|
796
|
-
Bun.sleep = origSleep;
|
|
797
|
-
}
|
|
798
|
-
});
|
|
799
|
-
|
|
800
|
-
test("throws when GATEWAY_INTERNAL_URL is not set", async () => {
|
|
801
|
-
delete process.env.GATEWAY_INTERNAL_URL;
|
|
802
|
-
|
|
803
|
-
await expect(fetchSigningKeyFromGateway()).rejects.toThrow(
|
|
804
|
-
"GATEWAY_INTERNAL_URL not set",
|
|
805
|
-
);
|
|
806
|
-
});
|
|
807
|
-
|
|
808
|
-
test("rejects invalid key length", async () => {
|
|
809
|
-
globalThis.fetch = (async () =>
|
|
810
|
-
new Response(JSON.stringify({ key: "aabb" }), {
|
|
811
|
-
status: 200,
|
|
812
|
-
headers: { "Content-Type": "application/json" },
|
|
813
|
-
})) as unknown as typeof fetch;
|
|
814
|
-
|
|
815
|
-
await expect(fetchSigningKeyFromGateway()).rejects.toThrow(
|
|
816
|
-
"Invalid signing key length",
|
|
817
|
-
);
|
|
818
|
-
});
|
|
819
|
-
|
|
820
|
-
test("retries on non-200/non-403 status and eventually succeeds", async () => {
|
|
821
|
-
const origSleep = Bun.sleep;
|
|
822
|
-
Bun.sleep = (() => Promise.resolve()) as typeof Bun.sleep;
|
|
823
|
-
|
|
824
|
-
let callCount = 0;
|
|
825
|
-
globalThis.fetch = (async () => {
|
|
826
|
-
callCount++;
|
|
827
|
-
if (callCount < 3) {
|
|
828
|
-
return new Response("Service Unavailable", { status: 503 });
|
|
829
|
-
}
|
|
830
|
-
return new Response(JSON.stringify({ key: VALID_HEX_KEY }), {
|
|
831
|
-
status: 200,
|
|
832
|
-
headers: { "Content-Type": "application/json" },
|
|
833
|
-
});
|
|
834
|
-
}) as unknown as typeof fetch;
|
|
835
|
-
|
|
836
|
-
try {
|
|
837
|
-
const key = await fetchSigningKeyFromGateway();
|
|
838
|
-
expect(key.length).toBe(32);
|
|
839
|
-
expect(callCount).toBe(3);
|
|
840
|
-
} finally {
|
|
841
|
-
Bun.sleep = origSleep;
|
|
842
|
-
}
|
|
843
|
-
});
|
|
844
|
-
});
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Covers:
|
|
6
6
|
* - Flag OFF blocks all exposure paths
|
|
7
7
|
* - Missing persisted value falls back to code default
|
|
8
|
-
* -
|
|
8
|
+
* - Protected feature-flags.json is the sole override mechanism
|
|
9
9
|
* - Undeclared keys default to enabled
|
|
10
10
|
*/
|
|
11
11
|
import {
|
|
@@ -101,7 +101,6 @@ mock.module("../config/loader.js", () => ({
|
|
|
101
101
|
invalidateConfigCache: () => {},
|
|
102
102
|
getNestedValue: () => undefined,
|
|
103
103
|
setNestedValue: () => {},
|
|
104
|
-
syncConfigToLockfile: () => {},
|
|
105
104
|
}));
|
|
106
105
|
|
|
107
106
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -120,7 +119,7 @@ mock.module("../tools/credentials/metadata-store.js", () => ({
|
|
|
120
119
|
}));
|
|
121
120
|
|
|
122
121
|
const { buildSystemPrompt } = await import("../prompts/system-prompt.js");
|
|
123
|
-
const { isAssistantFeatureFlagEnabled } =
|
|
122
|
+
const { isAssistantFeatureFlagEnabled, _setOverridesForTesting } =
|
|
124
123
|
await import("../config/assistant-feature-flags.js");
|
|
125
124
|
const { skillFlagKey } = await import("../config/skill-state.js");
|
|
126
125
|
|
|
@@ -130,6 +129,7 @@ const { skillFlagKey } = await import("../config/skill-state.js");
|
|
|
130
129
|
|
|
131
130
|
beforeEach(() => {
|
|
132
131
|
mkdirSync(TEST_DIR, { recursive: true });
|
|
132
|
+
_setOverridesForTesting({});
|
|
133
133
|
currentConfig = {
|
|
134
134
|
services: {
|
|
135
135
|
inference: {
|
|
@@ -148,6 +148,7 @@ beforeEach(() => {
|
|
|
148
148
|
});
|
|
149
149
|
|
|
150
150
|
afterEach(() => {
|
|
151
|
+
_setOverridesForTesting({});
|
|
151
152
|
if (existsSync(TEST_DIR)) {
|
|
152
153
|
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
153
154
|
}
|
|
@@ -198,11 +199,12 @@ describe("buildSystemPrompt assistant feature flag filtering", () => {
|
|
|
198
199
|
"browser",
|
|
199
200
|
);
|
|
200
201
|
|
|
202
|
+
_setOverridesForTesting({
|
|
203
|
+
[DECLARED_FLAG_KEY]: false,
|
|
204
|
+
"feature_flags.browser.enabled": true,
|
|
205
|
+
});
|
|
206
|
+
|
|
201
207
|
currentConfig = {
|
|
202
|
-
assistantFeatureFlagValues: {
|
|
203
|
-
[DECLARED_FLAG_KEY]: false,
|
|
204
|
-
"feature_flags.browser.enabled": true,
|
|
205
|
-
},
|
|
206
208
|
services: {
|
|
207
209
|
inference: {
|
|
208
210
|
mode: "your-own",
|
|
@@ -282,11 +284,12 @@ describe("buildSystemPrompt assistant feature flag filtering", () => {
|
|
|
282
284
|
"email-channel",
|
|
283
285
|
);
|
|
284
286
|
|
|
287
|
+
_setOverridesForTesting({
|
|
288
|
+
[DECLARED_FLAG_KEY]: false,
|
|
289
|
+
"feature_flags.email-channel.enabled": false,
|
|
290
|
+
});
|
|
291
|
+
|
|
285
292
|
currentConfig = {
|
|
286
|
-
assistantFeatureFlagValues: {
|
|
287
|
-
[DECLARED_FLAG_KEY]: false,
|
|
288
|
-
"feature_flags.email-channel.enabled": false,
|
|
289
|
-
},
|
|
290
293
|
services: {
|
|
291
294
|
inference: {
|
|
292
295
|
mode: "your-own",
|
|
@@ -311,7 +314,7 @@ describe("buildSystemPrompt assistant feature flag filtering", () => {
|
|
|
311
314
|
expect(result).not.toContain("**email-channel**");
|
|
312
315
|
});
|
|
313
316
|
|
|
314
|
-
test("
|
|
317
|
+
test("file-based overrides control visibility", () => {
|
|
315
318
|
createSkillOnDisk(
|
|
316
319
|
DECLARED_SKILL_ID,
|
|
317
320
|
"Contacts",
|
|
@@ -319,8 +322,9 @@ describe("buildSystemPrompt assistant feature flag filtering", () => {
|
|
|
319
322
|
DECLARED_FLAG_ID,
|
|
320
323
|
);
|
|
321
324
|
|
|
325
|
+
_setOverridesForTesting({ [DECLARED_FLAG_KEY]: true });
|
|
326
|
+
|
|
322
327
|
currentConfig = {
|
|
323
|
-
assistantFeatureFlagValues: { [DECLARED_FLAG_KEY]: true },
|
|
324
328
|
services: {
|
|
325
329
|
inference: {
|
|
326
330
|
mode: "your-own",
|
|
@@ -352,8 +356,9 @@ describe("buildSystemPrompt assistant feature flag filtering", () => {
|
|
|
352
356
|
"browser",
|
|
353
357
|
);
|
|
354
358
|
|
|
359
|
+
_setOverridesForTesting({ "feature_flags.browser.enabled": false });
|
|
360
|
+
|
|
355
361
|
currentConfig = {
|
|
356
|
-
assistantFeatureFlagValues: { "feature_flags.browser.enabled": false },
|
|
357
362
|
services: {
|
|
358
363
|
inference: {
|
|
359
364
|
mode: "your-own",
|
|
@@ -446,18 +451,16 @@ describe("buildSystemPrompt assistant feature flag filtering", () => {
|
|
|
446
451
|
// ---------------------------------------------------------------------------
|
|
447
452
|
|
|
448
453
|
describe("isAssistantFeatureFlagEnabled", () => {
|
|
449
|
-
test("reads from
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
} as any;
|
|
454
|
+
test("reads from file-based overrides", () => {
|
|
455
|
+
_setOverridesForTesting({ [DECLARED_FLAG_KEY]: true });
|
|
456
|
+
const config = {} as any;
|
|
453
457
|
|
|
454
458
|
expect(isAssistantFeatureFlagEnabled(DECLARED_FLAG_KEY, config)).toBe(true);
|
|
455
459
|
});
|
|
456
460
|
|
|
457
|
-
test("explicit false override in
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
} as any;
|
|
461
|
+
test("explicit false override in file-based overrides", () => {
|
|
462
|
+
_setOverridesForTesting({ [DECLARED_FLAG_KEY]: false });
|
|
463
|
+
const config = {} as any;
|
|
461
464
|
|
|
462
465
|
expect(isAssistantFeatureFlagEnabled(DECLARED_FLAG_KEY, config)).toBe(
|
|
463
466
|
false,
|
|
@@ -483,10 +486,9 @@ describe("isAssistantFeatureFlagEnabled", () => {
|
|
|
483
486
|
).toBe(true);
|
|
484
487
|
});
|
|
485
488
|
|
|
486
|
-
test("undeclared flag respects persisted
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
} as any;
|
|
489
|
+
test("undeclared flag respects persisted override", () => {
|
|
490
|
+
_setOverridesForTesting({ "feature_flags.browser.enabled": false });
|
|
491
|
+
const config = {} as any;
|
|
490
492
|
|
|
491
493
|
expect(
|
|
492
494
|
isAssistantFeatureFlagEnabled("feature_flags.browser.enabled", config),
|
|
@@ -496,9 +498,8 @@ describe("isAssistantFeatureFlagEnabled", () => {
|
|
|
496
498
|
|
|
497
499
|
describe("isAssistantFeatureFlagEnabled with skillFlagKey", () => {
|
|
498
500
|
test("resolves skill flag via canonical path", () => {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
} as any;
|
|
501
|
+
_setOverridesForTesting({ [DECLARED_FLAG_KEY]: false });
|
|
502
|
+
const config = {} as any;
|
|
502
503
|
|
|
503
504
|
expect(
|
|
504
505
|
isAssistantFeatureFlagEnabled(
|
|
@@ -7,13 +7,10 @@
|
|
|
7
7
|
import { afterAll, beforeAll, describe, expect, mock, test } from "bun:test";
|
|
8
8
|
|
|
9
9
|
mock.module("../config/loader.js", () => ({
|
|
10
|
-
getConfig: () => ({
|
|
11
|
-
assistantFeatureFlagValues: {
|
|
12
|
-
"feature_flags.browser.enabled": true,
|
|
13
|
-
},
|
|
14
|
-
}),
|
|
10
|
+
getConfig: () => ({}),
|
|
15
11
|
}));
|
|
16
12
|
|
|
13
|
+
import { _setOverridesForTesting } from "../config/assistant-feature-flags.js";
|
|
17
14
|
import {
|
|
18
15
|
projectSkillTools,
|
|
19
16
|
resetSkillToolProjection,
|
|
@@ -35,11 +32,15 @@ import {
|
|
|
35
32
|
|
|
36
33
|
afterAll(() => {
|
|
37
34
|
__resetRegistryForTesting();
|
|
35
|
+
_setOverridesForTesting({});
|
|
38
36
|
});
|
|
39
37
|
|
|
40
38
|
describe("browser skill migration end-state", () => {
|
|
41
39
|
beforeAll(async () => {
|
|
42
40
|
__resetRegistryForTesting();
|
|
41
|
+
_setOverridesForTesting({
|
|
42
|
+
"feature_flags.browser.enabled": true,
|
|
43
|
+
});
|
|
43
44
|
await initializeTools();
|
|
44
45
|
});
|
|
45
46
|
|
|
@@ -54,17 +54,6 @@ mock.module("../daemon/conversation-tool-setup.js", () => ({
|
|
|
54
54
|
buildToolDefinitions: () => MOCK_TOOLS,
|
|
55
55
|
}));
|
|
56
56
|
|
|
57
|
-
const mockCheckIngressForSecrets = mock((content: string) => ({
|
|
58
|
-
blocked: false,
|
|
59
|
-
userNotice: "",
|
|
60
|
-
detectedTypes: [] as string[],
|
|
61
|
-
normalizedContent: content,
|
|
62
|
-
}));
|
|
63
|
-
|
|
64
|
-
mock.module("../security/secret-ingress.js", () => ({
|
|
65
|
-
checkIngressForSecrets: mockCheckIngressForSecrets,
|
|
66
|
-
}));
|
|
67
|
-
|
|
68
57
|
const MOCK_SYSTEM_PROMPT = "You are a helpful assistant.";
|
|
69
58
|
const mockBuildSystemPrompt = mock(() => MOCK_SYSTEM_PROMPT);
|
|
70
59
|
|
|
@@ -236,34 +225,6 @@ describe("POST /v1/btw", () => {
|
|
|
236
225
|
expect(body.error.message).toContain("content");
|
|
237
226
|
});
|
|
238
227
|
|
|
239
|
-
test("returns 422 when content includes a blocked secret", async () => {
|
|
240
|
-
mockCheckIngressForSecrets.mockReturnValueOnce({
|
|
241
|
-
blocked: true,
|
|
242
|
-
userNotice: "Secret detected",
|
|
243
|
-
detectedTypes: ["api_key"],
|
|
244
|
-
normalizedContent: "sk-test-123",
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
const provider = makeMockProvider();
|
|
248
|
-
const session = makeMockSession(provider);
|
|
249
|
-
const res = await callHandler(
|
|
250
|
-
{ conversationKey: "key", content: "sk-test-123" },
|
|
251
|
-
{ sendMessageDeps: makeSendMessageDeps(session) },
|
|
252
|
-
);
|
|
253
|
-
|
|
254
|
-
expect(res.status).toBe(422);
|
|
255
|
-
const body = (await res.json()) as {
|
|
256
|
-
accepted: boolean;
|
|
257
|
-
error: string;
|
|
258
|
-
message: string;
|
|
259
|
-
detectedTypes: string[];
|
|
260
|
-
};
|
|
261
|
-
expect(body.accepted).toBe(false);
|
|
262
|
-
expect(body.error).toBe("secret_blocked");
|
|
263
|
-
expect(body.detectedTypes).toEqual(["api_key"]);
|
|
264
|
-
expect(provider.sendMessage).not.toHaveBeenCalled();
|
|
265
|
-
});
|
|
266
|
-
|
|
267
228
|
// -- Service unavailability (503) --
|
|
268
229
|
|
|
269
230
|
test("returns 503 when sendMessageDeps is unavailable", async () => {
|
|
@@ -40,46 +40,6 @@ let twilioInitiateCallArgs: Array<Record<string, unknown>> = [];
|
|
|
40
40
|
let mockIngressEnabled = true;
|
|
41
41
|
let mockIngressPublicBaseUrl = "https://test.example.com";
|
|
42
42
|
|
|
43
|
-
interface MockGatewayHealthResult {
|
|
44
|
-
target: string;
|
|
45
|
-
healthy: boolean;
|
|
46
|
-
localDeployment: boolean;
|
|
47
|
-
error?: string;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
interface MockEnsureLocalGatewayReadyResult extends MockGatewayHealthResult {
|
|
51
|
-
recovered: boolean;
|
|
52
|
-
recoveryAttempted: boolean;
|
|
53
|
-
recoverySkipped: boolean;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
let probeLocalGatewayHealthResults: MockGatewayHealthResult[] = [];
|
|
57
|
-
let probeLocalGatewayHealthCallCount = 0;
|
|
58
|
-
let ensureLocalGatewayReadyResult: MockEnsureLocalGatewayReadyResult;
|
|
59
|
-
let ensureLocalGatewayReadyCallCount = 0;
|
|
60
|
-
|
|
61
|
-
function makeGatewayHealthResult(
|
|
62
|
-
overrides: Partial<MockGatewayHealthResult> = {},
|
|
63
|
-
): MockGatewayHealthResult {
|
|
64
|
-
return {
|
|
65
|
-
target: "http://127.0.0.1:7830",
|
|
66
|
-
healthy: true,
|
|
67
|
-
localDeployment: true,
|
|
68
|
-
...overrides,
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function makeRecoveryResult(
|
|
73
|
-
overrides: Partial<MockEnsureLocalGatewayReadyResult> = {},
|
|
74
|
-
): MockEnsureLocalGatewayReadyResult {
|
|
75
|
-
return {
|
|
76
|
-
...makeGatewayHealthResult(),
|
|
77
|
-
recovered: false,
|
|
78
|
-
recoveryAttempted: false,
|
|
79
|
-
recoverySkipped: false,
|
|
80
|
-
...overrides,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
43
|
|
|
84
44
|
mock.module("../calls/twilio-config.js", () => ({
|
|
85
45
|
getTwilioConfig: (assistantId?: string) => ({
|
|
@@ -160,18 +120,6 @@ mock.module("../memory/conversation-title-service.js", () => ({
|
|
|
160
120
|
queueGenerateConversationTitle: () => {},
|
|
161
121
|
}));
|
|
162
122
|
|
|
163
|
-
mock.module("../runtime/local-gateway-health.js", () => ({
|
|
164
|
-
probeLocalGatewayHealth: async () => {
|
|
165
|
-
probeLocalGatewayHealthCallCount++;
|
|
166
|
-
const next =
|
|
167
|
-
probeLocalGatewayHealthResults.shift() ?? makeGatewayHealthResult();
|
|
168
|
-
return next;
|
|
169
|
-
},
|
|
170
|
-
ensureLocalGatewayReady: async () => {
|
|
171
|
-
ensureLocalGatewayReadyCallCount++;
|
|
172
|
-
return ensureLocalGatewayReadyResult;
|
|
173
|
-
},
|
|
174
|
-
}));
|
|
175
123
|
|
|
176
124
|
mock.module("../daemon/handlers/config-ingress.js", () => ({
|
|
177
125
|
computeGatewayTarget: () => "http://127.0.0.1:7830",
|
|
@@ -204,13 +152,6 @@ beforeEach(() => {
|
|
|
204
152
|
twilioInitiateCallArgs = [];
|
|
205
153
|
mockIngressEnabled = true;
|
|
206
154
|
mockIngressPublicBaseUrl = "https://test.example.com";
|
|
207
|
-
probeLocalGatewayHealthResults = [
|
|
208
|
-
makeGatewayHealthResult(),
|
|
209
|
-
makeGatewayHealthResult(),
|
|
210
|
-
];
|
|
211
|
-
probeLocalGatewayHealthCallCount = 0;
|
|
212
|
-
ensureLocalGatewayReadyResult = makeRecoveryResult();
|
|
213
|
-
ensureLocalGatewayReadyCallCount = 0;
|
|
214
155
|
});
|
|
215
156
|
|
|
216
157
|
let ensuredConvIds = new Set<string>();
|
|
@@ -446,9 +387,6 @@ describe("startCall — pointer message regression", () => {
|
|
|
446
387
|
expect(result.status).toBe(503);
|
|
447
388
|
expect(result.error).toContain("Public ingress");
|
|
448
389
|
}
|
|
449
|
-
expect(probeLocalGatewayHealthCallCount).toBe(0);
|
|
450
|
-
expect(ensureLocalGatewayReadyCallCount).toBe(0);
|
|
451
|
-
// No reconcile calls expected (reconcile triggers removed).
|
|
452
390
|
expect(twilioInitiateCallCount).toBe(0);
|
|
453
391
|
|
|
454
392
|
await new Promise((r) => setTimeout(r, 50));
|
|
@@ -459,72 +397,6 @@ describe("startCall — pointer message regression", () => {
|
|
|
459
397
|
expect(text!).toContain("failed");
|
|
460
398
|
});
|
|
461
399
|
|
|
462
|
-
test("never dials Twilio when the local callback gateway stays unhealthy", async () => {
|
|
463
|
-
const convId = "conv-domain-preflight-unhealthy";
|
|
464
|
-
ensureConversation(convId);
|
|
465
|
-
probeLocalGatewayHealthResults = [
|
|
466
|
-
makeGatewayHealthResult({
|
|
467
|
-
healthy: false,
|
|
468
|
-
error: "Gateway health check returned HTTP 503",
|
|
469
|
-
}),
|
|
470
|
-
makeGatewayHealthResult({
|
|
471
|
-
healthy: false,
|
|
472
|
-
error: "Gateway health check returned HTTP 503",
|
|
473
|
-
}),
|
|
474
|
-
];
|
|
475
|
-
ensureLocalGatewayReadyResult = makeRecoveryResult({
|
|
476
|
-
healthy: false,
|
|
477
|
-
error: "Gateway health check returned HTTP 503",
|
|
478
|
-
recovered: false,
|
|
479
|
-
recoveryAttempted: true,
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
const result = await startCall({
|
|
483
|
-
phoneNumber: "+15559876543",
|
|
484
|
-
task: "Test call",
|
|
485
|
-
conversationId: convId,
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
expect(result.ok).toBe(false);
|
|
489
|
-
if (!result.ok) {
|
|
490
|
-
expect(result.status).toBe(503);
|
|
491
|
-
expect(result.error).toContain("still unhealthy");
|
|
492
|
-
}
|
|
493
|
-
expect(probeLocalGatewayHealthCallCount).toBe(2);
|
|
494
|
-
expect(ensureLocalGatewayReadyCallCount).toBe(1);
|
|
495
|
-
// No reconcile calls expected (reconcile triggers removed).
|
|
496
|
-
expect(twilioInitiateCallCount).toBe(0);
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
test("can recover a local gateway and then place exactly one outbound Twilio call", async () => {
|
|
500
|
-
const convId = "conv-domain-preflight-recovery";
|
|
501
|
-
ensureConversation(convId);
|
|
502
|
-
probeLocalGatewayHealthResults = [
|
|
503
|
-
makeGatewayHealthResult({
|
|
504
|
-
healthy: false,
|
|
505
|
-
error: "Gateway health check returned HTTP 503",
|
|
506
|
-
}),
|
|
507
|
-
makeGatewayHealthResult(),
|
|
508
|
-
];
|
|
509
|
-
ensureLocalGatewayReadyResult = makeRecoveryResult({
|
|
510
|
-
healthy: true,
|
|
511
|
-
recovered: true,
|
|
512
|
-
recoveryAttempted: true,
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
const result = await startCall({
|
|
516
|
-
phoneNumber: "+15559876543",
|
|
517
|
-
task: "Test call",
|
|
518
|
-
conversationId: convId,
|
|
519
|
-
});
|
|
520
|
-
|
|
521
|
-
expect(result.ok).toBe(true);
|
|
522
|
-
expect(probeLocalGatewayHealthCallCount).toBe(2);
|
|
523
|
-
expect(ensureLocalGatewayReadyCallCount).toBe(1);
|
|
524
|
-
// Gateway reconcile triggers have been removed; the gateway reads
|
|
525
|
-
// credentials and config via TTL caches.
|
|
526
|
-
expect(twilioInitiateCallCount).toBe(1);
|
|
527
|
-
});
|
|
528
400
|
|
|
529
401
|
test("failed call writes a failed pointer to the initiating conversation", async () => {
|
|
530
402
|
const convId = "conv-domain-ptr-fail";
|