@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.
Files changed (305) hide show
  1. package/.env.example +16 -2
  2. package/ARCHITECTURE.md +6 -75
  3. package/Dockerfile +1 -1
  4. package/README.md +0 -2
  5. package/bun.lock +0 -414
  6. package/docs/architecture/keychain-broker.md +45 -240
  7. package/docs/architecture/security.md +0 -17
  8. package/docs/credential-execution-service.md +2 -2
  9. package/node_modules/@vellumai/ces-contracts/package.json +1 -0
  10. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +119 -0
  11. package/node_modules/@vellumai/credential-storage/package.json +1 -0
  12. package/node_modules/@vellumai/egress-proxy/package.json +1 -0
  13. package/package.json +2 -3
  14. package/src/__tests__/actor-token-service.test.ts +0 -114
  15. package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
  16. package/src/__tests__/browser-skill-endstate.test.ts +6 -5
  17. package/src/__tests__/btw-routes.test.ts +0 -39
  18. package/src/__tests__/call-domain.test.ts +0 -128
  19. package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
  20. package/src/__tests__/channel-approval-routes.test.ts +0 -5
  21. package/src/__tests__/channel-readiness-service.test.ts +1 -60
  22. package/src/__tests__/checker.test.ts +4 -2
  23. package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
  24. package/src/__tests__/config-schema-cmd.test.ts +0 -1
  25. package/src/__tests__/config-schema.test.ts +1 -1
  26. package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
  27. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  28. package/src/__tests__/conversation-skill-tools.test.ts +0 -54
  29. package/src/__tests__/conversation-title-service.test.ts +87 -0
  30. package/src/__tests__/credential-execution-feature-gates.test.ts +28 -14
  31. package/src/__tests__/credential-execution-managed-contract.test.ts +33 -18
  32. package/src/__tests__/credential-security-e2e.test.ts +0 -66
  33. package/src/__tests__/credential-security-invariants.test.ts +4 -45
  34. package/src/__tests__/credentials-cli.test.ts +78 -0
  35. package/src/__tests__/db-migration-rollback.test.ts +2015 -1
  36. package/src/__tests__/docker-signing-key-bootstrap.test.ts +34 -143
  37. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
  38. package/src/__tests__/guardian-routing-state.test.ts +0 -5
  39. package/src/__tests__/host-shell-tool.test.ts +6 -7
  40. package/src/__tests__/http-user-message-parity.test.ts +3 -103
  41. package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
  42. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
  43. package/src/__tests__/intent-routing.test.ts +0 -13
  44. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
  45. package/src/__tests__/keychain-broker-client.test.ts +161 -22
  46. package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
  47. package/src/__tests__/migration-export-http.test.ts +2 -2
  48. package/src/__tests__/migration-import-commit-http.test.ts +2 -2
  49. package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
  50. package/src/__tests__/migration-validate-http.test.ts +2 -2
  51. package/src/__tests__/non-member-access-request.test.ts +0 -5
  52. package/src/__tests__/notification-decision-fallback.test.ts +4 -0
  53. package/src/__tests__/notification-decision-identity.test.ts +4 -0
  54. package/src/__tests__/permission-types.test.ts +1 -0
  55. package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
  56. package/src/__tests__/qdrant-manager.test.ts +28 -2
  57. package/src/__tests__/registry.test.ts +0 -6
  58. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
  59. package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
  60. package/src/__tests__/secure-keys.test.ts +83 -263
  61. package/src/__tests__/shell-identity.test.ts +96 -6
  62. package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
  63. package/src/__tests__/skill-feature-flags.test.ts +46 -45
  64. package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
  65. package/src/__tests__/skill-load-inline-command.test.ts +8 -12
  66. package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
  67. package/src/__tests__/skill-load-tool.test.ts +0 -2
  68. package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
  69. package/src/__tests__/skills.test.ts +0 -2
  70. package/src/__tests__/slack-inbound-verification.test.ts +0 -4
  71. package/src/__tests__/suggestion-routes.test.ts +1 -32
  72. package/src/__tests__/system-prompt.test.ts +0 -1
  73. package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
  74. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
  75. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
  76. package/src/__tests__/update-bulletin.test.ts +0 -2
  77. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
  78. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -6
  79. package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
  80. package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +218 -0
  81. package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
  82. package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
  83. package/src/calls/audio-store.test.ts +97 -0
  84. package/src/calls/audio-store.ts +205 -0
  85. package/src/calls/call-controller.ts +85 -7
  86. package/src/calls/call-domain.ts +3 -0
  87. package/src/calls/call-store.ts +10 -3
  88. package/src/calls/fish-audio-client.ts +117 -0
  89. package/src/calls/relay-server.ts +27 -0
  90. package/src/calls/twilio-routes.ts +2 -1
  91. package/src/calls/types.ts +1 -0
  92. package/src/calls/voice-ingress-preflight.ts +0 -42
  93. package/src/calls/voice-quality.ts +26 -5
  94. package/src/calls/voice-session-bridge.ts +6 -12
  95. package/src/cli/commands/config.ts +1 -4
  96. package/src/cli/commands/credentials.ts +34 -4
  97. package/src/cli/commands/oauth/index.ts +7 -0
  98. package/src/cli/commands/oauth/platform.ts +179 -0
  99. package/src/cli/commands/platform.ts +3 -3
  100. package/src/config/assistant-feature-flags.ts +186 -5
  101. package/src/config/bundled-skills/messaging/SKILL.md +5 -5
  102. package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
  103. package/src/config/bundled-skills/settings/TOOLS.json +2 -2
  104. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
  105. package/src/config/bundled-tool-registry.ts +1 -11
  106. package/src/config/env-registry.ts +1 -1
  107. package/src/config/env.ts +8 -14
  108. package/src/config/feature-flag-registry.json +48 -8
  109. package/src/config/loader.ts +98 -31
  110. package/src/config/schema.ts +4 -13
  111. package/src/config/schemas/calls.ts +13 -0
  112. package/src/config/schemas/fish-audio.ts +39 -0
  113. package/src/config/schemas/security.ts +0 -4
  114. package/src/config/types.ts +0 -1
  115. package/src/contacts/contact-store.ts +39 -0
  116. package/src/contacts/types.ts +2 -0
  117. package/src/credential-execution/approval-bridge.ts +1 -0
  118. package/src/credential-execution/executable-discovery.ts +28 -4
  119. package/src/credential-execution/feature-gates.ts +16 -0
  120. package/src/credential-execution/process-manager.ts +38 -0
  121. package/src/daemon/assistant-attachments.ts +9 -0
  122. package/src/daemon/config-watcher.ts +5 -0
  123. package/src/daemon/conversation-tool-setup.ts +0 -105
  124. package/src/daemon/conversation.ts +10 -1
  125. package/src/daemon/handlers/config-vercel.ts +92 -0
  126. package/src/daemon/handlers/skills.ts +2 -15
  127. package/src/daemon/install-symlink.ts +195 -0
  128. package/src/daemon/lifecycle.ts +227 -51
  129. package/src/daemon/message-types/conversations.ts +3 -4
  130. package/src/daemon/message-types/diagnostics.ts +3 -22
  131. package/src/daemon/message-types/messages.ts +0 -2
  132. package/src/daemon/message-types/upgrades.ts +8 -0
  133. package/src/daemon/server.ts +30 -92
  134. package/src/events/domain-events.ts +2 -1
  135. package/src/inbound/platform-callback-registration.ts +3 -3
  136. package/src/instrument.ts +8 -5
  137. package/src/memory/conversation-title-service.ts +50 -1
  138. package/src/memory/db-init.ts +12 -0
  139. package/src/memory/items-extractor.ts +15 -1
  140. package/src/memory/job-handlers/conversation-starters.ts +4 -1
  141. package/src/memory/jobs-store.ts +30 -5
  142. package/src/memory/jobs-worker.ts +31 -7
  143. package/src/memory/migrations/001-job-deferrals.ts +19 -0
  144. package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
  145. package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
  146. package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
  147. package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
  148. package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
  149. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
  150. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
  151. package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
  152. package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
  153. package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
  154. package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
  155. package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
  156. package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
  157. package/src/memory/migrations/116-messages-fts.ts +106 -1
  158. package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
  159. package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
  160. package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
  161. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
  162. package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
  163. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
  164. package/src/memory/migrations/141-rename-verification-table.ts +54 -0
  165. package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
  166. package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
  167. package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
  168. package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
  169. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
  170. package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
  171. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
  172. package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
  173. package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
  174. package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
  175. package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
  176. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
  177. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
  178. package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
  179. package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
  180. package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
  181. package/src/memory/migrations/index.ts +4 -0
  182. package/src/memory/migrations/registry.ts +90 -0
  183. package/src/memory/migrations/validate-migration-state.ts +137 -11
  184. package/src/memory/qdrant-circuit-breaker.ts +9 -0
  185. package/src/memory/qdrant-manager.ts +64 -7
  186. package/src/memory/schema/calls.ts +1 -0
  187. package/src/memory/schema/contacts.ts +1 -0
  188. package/src/notifications/decision-engine.ts +4 -1
  189. package/src/oauth/connection-resolver.ts +6 -4
  190. package/src/permissions/checker.ts +0 -38
  191. package/src/permissions/shell-identity.ts +76 -22
  192. package/src/permissions/types.ts +4 -2
  193. package/src/platform/client.ts +35 -7
  194. package/src/prompts/persona-resolver.ts +138 -0
  195. package/src/prompts/system-prompt.ts +36 -4
  196. package/src/prompts/templates/users/default.md +1 -0
  197. package/src/providers/registry.ts +27 -40
  198. package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
  199. package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
  200. package/src/runtime/auth/external-assistant-id.ts +13 -59
  201. package/src/runtime/auth/route-policy.ts +15 -1
  202. package/src/runtime/auth/token-service.ts +43 -138
  203. package/src/runtime/channel-readiness-service.ts +1 -16
  204. package/src/runtime/http-server.ts +27 -2
  205. package/src/runtime/middleware/error-handler.ts +1 -9
  206. package/src/runtime/routes/audio-routes.ts +40 -0
  207. package/src/runtime/routes/btw-routes.ts +0 -17
  208. package/src/runtime/routes/conversation-query-routes.ts +63 -1
  209. package/src/runtime/routes/conversation-routes.ts +4 -44
  210. package/src/runtime/routes/diagnostics-routes.ts +1 -477
  211. package/src/runtime/routes/identity-routes.ts +18 -29
  212. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
  213. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
  214. package/src/runtime/routes/integrations/vercel.ts +89 -0
  215. package/src/runtime/routes/log-export-routes.ts +5 -0
  216. package/src/runtime/routes/memory-item-routes.ts +24 -6
  217. package/src/runtime/routes/migration-rollback-routes.ts +209 -0
  218. package/src/runtime/routes/migration-routes.ts +17 -1
  219. package/src/runtime/routes/notification-routes.ts +58 -0
  220. package/src/runtime/routes/schedule-routes.ts +65 -0
  221. package/src/runtime/routes/settings-routes.ts +41 -1
  222. package/src/runtime/routes/tts-routes.ts +86 -0
  223. package/src/runtime/routes/upgrade-broadcast-routes.ts +26 -2
  224. package/src/runtime/routes/workspace-commit-routes.ts +62 -0
  225. package/src/runtime/routes/workspace-routes.test.ts +22 -1
  226. package/src/runtime/routes/workspace-routes.ts +1 -1
  227. package/src/runtime/routes/workspace-utils.ts +86 -2
  228. package/src/security/ces-credential-client.ts +59 -22
  229. package/src/security/ces-rpc-credential-backend.ts +85 -0
  230. package/src/security/credential-backend.ts +12 -88
  231. package/src/security/keychain-broker-client.ts +10 -2
  232. package/src/security/secure-keys.ts +94 -113
  233. package/src/skills/catalog-install.ts +13 -7
  234. package/src/telemetry/usage-telemetry-reporter.ts +4 -2
  235. package/src/tools/calls/call-start.ts +1 -0
  236. package/src/tools/executor.ts +0 -4
  237. package/src/tools/network/script-proxy/session-manager.ts +19 -4
  238. package/src/tools/network/web-fetch.ts +3 -1
  239. package/src/tools/skills/execute.ts +1 -1
  240. package/src/tools/types.ts +0 -8
  241. package/src/util/errors.ts +0 -12
  242. package/src/util/platform.ts +3 -50
  243. package/src/workspace/git-service.ts +5 -2
  244. package/src/workspace/migrations/001-avatar-rename.ts +15 -0
  245. package/src/workspace/migrations/003-seed-device-id.ts +17 -1
  246. package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
  247. package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
  248. package/src/workspace/migrations/006-services-config.ts +49 -0
  249. package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
  250. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
  251. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
  252. package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
  253. package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
  254. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
  255. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
  256. package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
  257. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
  258. package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
  259. package/src/workspace/migrations/017-seed-persona-dirs.ts +95 -0
  260. package/src/workspace/migrations/migrate-to-workspace-volume.ts +23 -1
  261. package/src/workspace/migrations/registry.ts +8 -0
  262. package/src/workspace/migrations/runner.ts +106 -2
  263. package/src/workspace/migrations/types.ts +4 -0
  264. package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
  265. package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
  266. package/src/__tests__/diagnostics-export.test.ts +0 -288
  267. package/src/__tests__/local-gateway-health.test.ts +0 -209
  268. package/src/__tests__/secret-ingress-handler.test.ts +0 -120
  269. package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
  270. package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
  271. package/src/__tests__/swarm-orchestrator.test.ts +0 -463
  272. package/src/__tests__/swarm-plan-validator.test.ts +0 -384
  273. package/src/__tests__/swarm-recursion.test.ts +0 -197
  274. package/src/__tests__/swarm-router-planner.test.ts +0 -234
  275. package/src/__tests__/swarm-tool.test.ts +0 -185
  276. package/src/__tests__/swarm-worker-backend.test.ts +0 -144
  277. package/src/__tests__/swarm-worker-runner.test.ts +0 -288
  278. package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
  279. package/src/commands/cc-command-registry.ts +0 -248
  280. package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
  281. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
  282. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
  283. package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
  284. package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
  285. package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
  286. package/src/config/schemas/swarm.ts +0 -82
  287. package/src/logfire.ts +0 -135
  288. package/src/runtime/local-gateway-health.ts +0 -275
  289. package/src/security/secret-ingress.ts +0 -68
  290. package/src/swarm/backend-claude-code.ts +0 -225
  291. package/src/swarm/checkpoint.ts +0 -137
  292. package/src/swarm/graph-utils.ts +0 -53
  293. package/src/swarm/index.ts +0 -55
  294. package/src/swarm/limits.ts +0 -66
  295. package/src/swarm/orchestrator.ts +0 -424
  296. package/src/swarm/plan-validator.ts +0 -117
  297. package/src/swarm/router-planner.ts +0 -162
  298. package/src/swarm/router-prompts.ts +0 -39
  299. package/src/swarm/synthesizer.ts +0 -81
  300. package/src/swarm/types.ts +0 -72
  301. package/src/swarm/worker-backend.ts +0 -131
  302. package/src/swarm/worker-prompts.ts +0 -80
  303. package/src/swarm/worker-runner.ts +0 -170
  304. package/src/tools/claude-code/claude-code.ts +0 -610
  305. 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
- * - New assistantFeatureFlagValues is the sole override mechanism
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("assistantFeatureFlagValues overrides control visibility", () => {
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 assistantFeatureFlagValues", () => {
450
- const config = {
451
- assistantFeatureFlagValues: { [DECLARED_FLAG_KEY]: true },
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 assistantFeatureFlagValues", () => {
458
- const config = {
459
- assistantFeatureFlagValues: { [DECLARED_FLAG_KEY]: false },
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 canonical override", () => {
487
- const config = {
488
- assistantFeatureFlagValues: { "feature_flags.browser.enabled": false },
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
- const config = {
500
- assistantFeatureFlagValues: { [DECLARED_FLAG_KEY]: false },
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";