@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.
Files changed (536) hide show
  1. package/Dockerfile +6 -6
  2. package/README.md +1 -2
  3. package/eslint.config.mjs +2 -2
  4. package/package.json +1 -1
  5. package/src/__tests__/access-request-decision.test.ts +128 -120
  6. package/src/__tests__/account-registry.test.ts +121 -110
  7. package/src/__tests__/active-skill-tools.test.ts +200 -172
  8. package/src/__tests__/actor-token-service.test.ts +341 -274
  9. package/src/__tests__/agent-loop-thinking.test.ts +28 -19
  10. package/src/__tests__/agent-loop.test.ts +798 -378
  11. package/src/__tests__/anthropic-provider.test.ts +405 -247
  12. package/src/__tests__/app-builder-tool-scripts.test.ts +97 -97
  13. package/src/__tests__/app-bundler.test.ts +112 -79
  14. package/src/__tests__/app-executors.test.ts +205 -178
  15. package/src/__tests__/app-git-history.test.ts +90 -73
  16. package/src/__tests__/app-git-service.test.ts +67 -53
  17. package/src/__tests__/app-open-proxy.test.ts +29 -25
  18. package/src/__tests__/approval-conversation-turn.test.ts +100 -81
  19. package/src/__tests__/approval-hardcoded-copy-guard.test.ts +45 -17
  20. package/src/__tests__/approval-message-composer.test.ts +119 -119
  21. package/src/__tests__/approval-primitive.test.ts +264 -233
  22. package/src/__tests__/approval-routes-http.test.ts +4 -3
  23. package/src/__tests__/asset-materialize-tool.test.ts +250 -178
  24. package/src/__tests__/asset-search-tool.test.ts +251 -191
  25. package/src/__tests__/assistant-attachment-directive.test.ts +187 -142
  26. package/src/__tests__/assistant-attachments.test.ts +254 -186
  27. package/src/__tests__/assistant-event-hub.test.ts +105 -63
  28. package/src/__tests__/assistant-event.test.ts +66 -58
  29. package/src/__tests__/assistant-events-sse-hardening.test.ts +113 -73
  30. package/src/__tests__/assistant-feature-flag-guard.test.ts +78 -52
  31. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +48 -45
  32. package/src/__tests__/assistant-feature-flags-integration.test.ts +118 -77
  33. package/src/__tests__/assistant-id-boundary-guard.test.ts +158 -104
  34. package/src/__tests__/attachments-store.test.ts +240 -183
  35. package/src/__tests__/attachments.test.ts +70 -62
  36. package/src/__tests__/audit-log-rotation.test.ts +50 -35
  37. package/src/__tests__/browser-fill-credential.test.ts +169 -101
  38. package/src/__tests__/browser-manager.test.ts +97 -75
  39. package/src/__tests__/browser-runtime-check.test.ts +16 -15
  40. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +12 -10
  41. package/src/__tests__/browser-skill-endstate.test.ts +97 -72
  42. package/src/__tests__/bundle-scanner.test.ts +47 -22
  43. package/src/__tests__/bundled-asset.test.ts +74 -47
  44. package/src/__tests__/call-constants.test.ts +19 -19
  45. package/src/__tests__/call-controller.test.ts +1073 -751
  46. package/src/__tests__/call-conversation-messages.test.ts +90 -65
  47. package/src/__tests__/call-domain.test.ts +149 -121
  48. package/src/__tests__/call-pointer-message-composer.test.ts +113 -83
  49. package/src/__tests__/call-pointer-messages.test.ts +213 -154
  50. package/src/__tests__/call-pointer-no-hardcoded-copy.guard.test.ts +9 -10
  51. package/src/__tests__/call-recovery.test.ts +232 -212
  52. package/src/__tests__/call-routes-http.test.ts +328 -279
  53. package/src/__tests__/call-start-guardian-guard.test.ts +32 -30
  54. package/src/__tests__/call-state-machine.test.ts +62 -51
  55. package/src/__tests__/call-state.test.ts +89 -75
  56. package/src/__tests__/call-store.test.ts +387 -316
  57. package/src/__tests__/callback-handoff-copy.test.ts +84 -82
  58. package/src/__tests__/canonical-guardian-store.test.ts +331 -280
  59. package/src/__tests__/channel-approval-routes.test.ts +1643 -1126
  60. package/src/__tests__/channel-approval.test.ts +139 -137
  61. package/src/__tests__/channel-approvals.test.ts +226 -182
  62. package/src/__tests__/channel-delivery-store.test.ts +232 -194
  63. package/src/__tests__/channel-guardian.test.ts +6 -3
  64. package/src/__tests__/channel-invite-transport.test.ts +107 -92
  65. package/src/__tests__/channel-policy.test.ts +42 -38
  66. package/src/__tests__/channel-readiness-service.test.ts +119 -102
  67. package/src/__tests__/channel-reply-delivery.test.ts +147 -118
  68. package/src/__tests__/channel-retry-sweep.test.ts +153 -110
  69. package/src/__tests__/checker.test.ts +3309 -1850
  70. package/src/__tests__/clarification-resolver.test.ts +91 -79
  71. package/src/__tests__/classifier.test.ts +64 -54
  72. package/src/__tests__/claude-code-skill-regression.test.ts +42 -37
  73. package/src/__tests__/claude-code-tool-profiles.test.ts +31 -29
  74. package/src/__tests__/clawhub.test.ts +92 -82
  75. package/src/__tests__/cli.test.ts +30 -30
  76. package/src/__tests__/clipboard.test.ts +53 -46
  77. package/src/__tests__/commit-guarantee.test.ts +59 -52
  78. package/src/__tests__/commit-message-enrichment-service.test.ts +203 -75
  79. package/src/__tests__/compaction.benchmark.test.ts +33 -31
  80. package/src/__tests__/computer-use-session-compaction.test.ts +60 -50
  81. package/src/__tests__/computer-use-session-lifecycle.test.ts +145 -117
  82. package/src/__tests__/computer-use-session-working-dir.test.ts +62 -48
  83. package/src/__tests__/computer-use-skill-baseline.test.ts +22 -19
  84. package/src/__tests__/computer-use-skill-endstate.test.ts +45 -31
  85. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +121 -88
  86. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +65 -42
  87. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +33 -18
  88. package/src/__tests__/computer-use-tools.test.ts +121 -98
  89. package/src/__tests__/config-schema.test.ts +443 -347
  90. package/src/__tests__/config-watcher.test.ts +96 -81
  91. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +148 -133
  92. package/src/__tests__/conflict-intent-tokenization.test.ts +96 -78
  93. package/src/__tests__/conflict-policy.test.ts +151 -80
  94. package/src/__tests__/conflict-store.test.ts +203 -157
  95. package/src/__tests__/connection-policy.test.ts +89 -59
  96. package/src/__tests__/contacts-tools.test.ts +247 -178
  97. package/src/__tests__/context-memory-e2e.test.ts +306 -214
  98. package/src/__tests__/context-token-estimator.test.ts +114 -74
  99. package/src/__tests__/context-window-manager.test.ts +269 -167
  100. package/src/__tests__/contradiction-checker.test.ts +161 -135
  101. package/src/__tests__/conversation-attention-store.test.ts +350 -290
  102. package/src/__tests__/conversation-attention-telegram.test.ts +156 -114
  103. package/src/__tests__/conversation-pairing.test.ts +220 -113
  104. package/src/__tests__/conversation-routes-guardian-reply.test.ts +164 -104
  105. package/src/__tests__/conversation-routes.test.ts +71 -41
  106. package/src/__tests__/conversation-store.test.ts +390 -235
  107. package/src/__tests__/credential-broker-browser-fill.test.ts +325 -250
  108. package/src/__tests__/credential-broker-server-use.test.ts +283 -243
  109. package/src/__tests__/credential-broker.test.ts +128 -74
  110. package/src/__tests__/credential-host-pattern-match.test.ts +64 -44
  111. package/src/__tests__/credential-metadata-store.test.ts +360 -311
  112. package/src/__tests__/credential-policy-validate.test.ts +81 -65
  113. package/src/__tests__/credential-resolve.test.ts +212 -145
  114. package/src/__tests__/credential-security-e2e.test.ts +144 -103
  115. package/src/__tests__/credential-security-invariants.test.ts +253 -208
  116. package/src/__tests__/credential-selection.test.ts +254 -146
  117. package/src/__tests__/credential-vault-unit.test.ts +531 -341
  118. package/src/__tests__/credential-vault.test.ts +761 -484
  119. package/src/__tests__/daemon-assistant-events.test.ts +91 -66
  120. package/src/__tests__/daemon-lifecycle.test.ts +258 -190
  121. package/src/__tests__/daemon-server-session-init.test.ts +257 -191
  122. package/src/__tests__/date-context.test.ts +314 -249
  123. package/src/__tests__/db-migration-rollback.test.ts +259 -130
  124. package/src/__tests__/db-schedule-syntax-migration.test.ts +78 -41
  125. package/src/__tests__/delete-managed-skill-tool.test.ts +77 -53
  126. package/src/__tests__/deterministic-verification-control-plane.test.ts +183 -135
  127. package/src/__tests__/dictation-mode-detection.test.ts +77 -55
  128. package/src/__tests__/dictation-profile-store.test.ts +70 -56
  129. package/src/__tests__/dictation-text-processing.test.ts +53 -35
  130. package/src/__tests__/diff.test.ts +102 -98
  131. package/src/__tests__/domain-normalize.test.ts +54 -54
  132. package/src/__tests__/domain-policy.test.ts +71 -55
  133. package/src/__tests__/dynamic-page-surface.test.ts +31 -33
  134. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +69 -69
  135. package/src/__tests__/edit-engine.test.ts +56 -56
  136. package/src/__tests__/elevenlabs-client.test.ts +117 -91
  137. package/src/__tests__/elevenlabs-config.test.ts +32 -31
  138. package/src/__tests__/email-classifier.test.ts +15 -12
  139. package/src/__tests__/email-cli.test.ts +121 -108
  140. package/src/__tests__/emit-signal-routing-intent.test.ts +76 -69
  141. package/src/__tests__/encrypted-store.test.ts +180 -154
  142. package/src/__tests__/entity-extractor.test.ts +108 -87
  143. package/src/__tests__/entity-search.test.ts +664 -258
  144. package/src/__tests__/ephemeral-permissions.test.ts +224 -188
  145. package/src/__tests__/event-bus.test.ts +81 -77
  146. package/src/__tests__/extract-email.test.ts +51 -0
  147. package/src/__tests__/file-edit-tool.test.ts +62 -44
  148. package/src/__tests__/file-ops-service.test.ts +131 -114
  149. package/src/__tests__/file-read-tool.test.ts +48 -31
  150. package/src/__tests__/file-write-tool.test.ts +43 -37
  151. package/src/__tests__/filesystem-tools.test.ts +238 -209
  152. package/src/__tests__/followup-tools.test.ts +237 -162
  153. package/src/__tests__/forbidden-legacy-symbols.test.ts +19 -20
  154. package/src/__tests__/frontmatter.test.ts +96 -81
  155. package/src/__tests__/fuzzy-match-property.test.ts +75 -81
  156. package/src/__tests__/fuzzy-match.test.ts +71 -65
  157. package/src/__tests__/gateway-client-managed-outbound.test.ts +76 -57
  158. package/src/__tests__/gateway-only-enforcement.test.ts +467 -369
  159. package/src/__tests__/gateway-only-guard.test.ts +54 -56
  160. package/src/__tests__/gemini-image-service.test.ts +113 -100
  161. package/src/__tests__/gemini-provider.test.ts +297 -220
  162. package/src/__tests__/get-weather.test.ts +188 -114
  163. package/src/__tests__/gmail-integration.test.ts +47 -46
  164. package/src/__tests__/guardian-action-conversation-turn.test.ts +226 -171
  165. package/src/__tests__/guardian-action-copy-generator.test.ts +111 -93
  166. package/src/__tests__/guardian-action-followup-executor.test.ts +215 -151
  167. package/src/__tests__/guardian-action-followup-store.test.ts +199 -167
  168. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +297 -250
  169. package/src/__tests__/guardian-action-late-reply.test.ts +462 -316
  170. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +23 -18
  171. package/src/__tests__/guardian-action-store.test.ts +158 -109
  172. package/src/__tests__/guardian-action-sweep.test.ts +114 -100
  173. package/src/__tests__/guardian-actions-endpoint.test.ts +440 -256
  174. package/src/__tests__/guardian-control-plane-policy.test.ts +497 -331
  175. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +217 -215
  176. package/src/__tests__/guardian-dispatch.test.ts +316 -256
  177. package/src/__tests__/guardian-grant-minting.test.ts +247 -178
  178. package/src/__tests__/guardian-outbound-http.test.ts +337 -209
  179. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +99 -96
  180. package/src/__tests__/guardian-question-copy.test.ts +17 -17
  181. package/src/__tests__/guardian-question-mode.test.ts +134 -100
  182. package/src/__tests__/guardian-routing-invariants.test.ts +679 -613
  183. package/src/__tests__/guardian-routing-state.test.ts +256 -209
  184. package/src/__tests__/guardian-verification-intent-routing.test.ts +94 -88
  185. package/src/__tests__/guardian-verification-voice-binding.test.ts +47 -41
  186. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +0 -1
  187. package/src/__tests__/handle-user-message-secret-resume.test.ts +43 -21
  188. package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +92 -76
  189. package/src/__tests__/handlers-cu-observation-blob.test.ts +103 -70
  190. package/src/__tests__/handlers-ipc-blob-probe.test.ts +77 -51
  191. package/src/__tests__/handlers-slack-config.test.ts +63 -54
  192. package/src/__tests__/handlers-task-submit-slash.test.ts +18 -18
  193. package/src/__tests__/handlers-telegram-config.test.ts +662 -329
  194. package/src/__tests__/handlers-twitter-config.test.ts +525 -298
  195. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +270 -195
  196. package/src/__tests__/headless-browser-interactions.test.ts +444 -280
  197. package/src/__tests__/headless-browser-navigate.test.ts +116 -79
  198. package/src/__tests__/headless-browser-read-tools.test.ts +123 -86
  199. package/src/__tests__/headless-browser-snapshot.test.ts +71 -56
  200. package/src/__tests__/heartbeat-service.test.ts +76 -58
  201. package/src/__tests__/history-repair-observability.test.ts +14 -14
  202. package/src/__tests__/history-repair.test.ts +171 -167
  203. package/src/__tests__/home-base-bootstrap.test.ts +30 -27
  204. package/src/__tests__/hooks-blocking.test.ts +86 -37
  205. package/src/__tests__/hooks-cli.test.ts +104 -68
  206. package/src/__tests__/hooks-config.test.ts +81 -43
  207. package/src/__tests__/hooks-discovery.test.ts +106 -96
  208. package/src/__tests__/hooks-integration.test.ts +78 -72
  209. package/src/__tests__/hooks-manager.test.ts +99 -61
  210. package/src/__tests__/hooks-runner.test.ts +94 -71
  211. package/src/__tests__/hooks-settings.test.ts +69 -64
  212. package/src/__tests__/hooks-templates.test.ts +85 -54
  213. package/src/__tests__/hooks-ts-runner.test.ts +82 -45
  214. package/src/__tests__/hooks-watch.test.ts +32 -22
  215. package/src/__tests__/host-file-edit-tool.test.ts +190 -148
  216. package/src/__tests__/host-file-read-tool.test.ts +86 -63
  217. package/src/__tests__/host-file-write-tool.test.ts +98 -64
  218. package/src/__tests__/host-shell-tool.test.ts +342 -233
  219. package/src/__tests__/inbound-invite-redemption.test.ts +194 -152
  220. package/src/__tests__/ingress-member-store.test.ts +163 -159
  221. package/src/__tests__/ingress-reconcile.test.ts +183 -142
  222. package/src/__tests__/ingress-routes-http.test.ts +441 -356
  223. package/src/__tests__/ingress-url-consistency.test.ts +125 -64
  224. package/src/__tests__/integration-status.test.ts +93 -73
  225. package/src/__tests__/intent-routing.test.ts +148 -118
  226. package/src/__tests__/invite-redemption-service.test.ts +163 -121
  227. package/src/__tests__/ipc-blob-store.test.ts +104 -91
  228. package/src/__tests__/ipc-contract-inventory.test.ts +27 -15
  229. package/src/__tests__/ipc-contract.test.ts +24 -23
  230. package/src/__tests__/ipc-protocol.test.ts +52 -46
  231. package/src/__tests__/ipc-roundtrip.benchmark.test.ts +61 -50
  232. package/src/__tests__/ipc-snapshot.test.ts +1135 -1056
  233. package/src/__tests__/ipc-validate.test.ts +240 -179
  234. package/src/__tests__/key-migration.test.ts +123 -90
  235. package/src/__tests__/keychain.test.ts +150 -123
  236. package/src/__tests__/lifecycle-docs-guard.test.ts +65 -64
  237. package/src/__tests__/llm-usage-store.test.ts +112 -87
  238. package/src/__tests__/managed-skill-lifecycle.test.ts +147 -108
  239. package/src/__tests__/managed-store.test.ts +411 -360
  240. package/src/__tests__/mcp-cli.test.ts +189 -123
  241. package/src/__tests__/mcp-health-check.test.ts +26 -21
  242. package/src/__tests__/media-generate-image.test.ts +122 -99
  243. package/src/__tests__/media-reuse-story.e2e.test.ts +282 -214
  244. package/src/__tests__/media-visibility-policy.test.ts +86 -38
  245. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +146 -100
  246. package/src/__tests__/memory-lifecycle-e2e.test.ts +385 -297
  247. package/src/__tests__/memory-query-builder.test.ts +32 -33
  248. package/src/__tests__/memory-recall-quality.test.ts +761 -407
  249. package/src/__tests__/memory-regressions.experimental.test.ts +443 -380
  250. package/src/__tests__/memory-regressions.test.ts +3725 -2642
  251. package/src/__tests__/memory-retrieval-budget.test.ts +7 -8
  252. package/src/__tests__/memory-retrieval.benchmark.test.ts +144 -109
  253. package/src/__tests__/memory-upsert-concurrency.test.ts +292 -201
  254. package/src/__tests__/messaging-send-tool.test.ts +36 -29
  255. package/src/__tests__/migration-cli-flows.test.ts +69 -53
  256. package/src/__tests__/migration-ordering.test.ts +103 -86
  257. package/src/__tests__/mime-builder.test.ts +55 -32
  258. package/src/__tests__/mock-signup-server.test.ts +384 -246
  259. package/src/__tests__/model-intents.test.ts +61 -37
  260. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +9 -12
  261. package/src/__tests__/no-is-trusted-guard.test.ts +24 -21
  262. package/src/__tests__/non-member-access-request.test.ts +294 -249
  263. package/src/__tests__/notification-broadcaster.test.ts +99 -81
  264. package/src/__tests__/notification-decision-fallback.test.ts +223 -178
  265. package/src/__tests__/notification-decision-strategy.test.ts +375 -337
  266. package/src/__tests__/notification-deep-link.test.ts +67 -61
  267. package/src/__tests__/notification-guardian-path.test.ts +248 -206
  268. package/src/__tests__/notification-routing-intent.test.ts +166 -93
  269. package/src/__tests__/notification-telegram-adapter.test.ts +60 -46
  270. package/src/__tests__/notification-thread-candidate-validation.test.ts +78 -75
  271. package/src/__tests__/notification-thread-candidates.test.ts +64 -61
  272. package/src/__tests__/oauth-callback-registry.test.ts +40 -30
  273. package/src/__tests__/oauth-connect-handler.test.ts +109 -89
  274. package/src/__tests__/oauth-scope-policy.test.ts +63 -55
  275. package/src/__tests__/oauth2-gateway-transport.test.ts +252 -174
  276. package/src/__tests__/onboarding-starter-tasks.test.ts +93 -89
  277. package/src/__tests__/onboarding-template-contract.test.ts +93 -94
  278. package/src/__tests__/openai-provider.test.ts +366 -274
  279. package/src/__tests__/pairing-concurrent.test.ts +18 -12
  280. package/src/__tests__/pairing-routes.test.ts +45 -41
  281. package/src/__tests__/parallel-tool.benchmark.test.ts +108 -58
  282. package/src/__tests__/parser.test.ts +316 -226
  283. package/src/__tests__/path-classifier.test.ts +24 -25
  284. package/src/__tests__/path-policy.test.ts +187 -147
  285. package/src/__tests__/phone.test.ts +36 -36
  286. package/src/__tests__/platform-move-helper.test.ts +48 -40
  287. package/src/__tests__/platform-socket-path.test.ts +23 -24
  288. package/src/__tests__/platform-workspace-migration.test.ts +464 -414
  289. package/src/__tests__/platform.test.ts +61 -53
  290. package/src/__tests__/playbook-execution.test.ts +397 -265
  291. package/src/__tests__/playbook-tools.test.ts +267 -196
  292. package/src/__tests__/prebuilt-home-base-seed.test.ts +30 -27
  293. package/src/__tests__/pricing.test.ts +316 -136
  294. package/src/__tests__/profile-compiler.test.ts +206 -188
  295. package/src/__tests__/provider-commit-message-generator.test.ts +114 -106
  296. package/src/__tests__/provider-error-scenarios.test.ts +212 -158
  297. package/src/__tests__/provider-fail-open-selection.test.ts +51 -44
  298. package/src/__tests__/provider-registry-ollama.test.ts +13 -9
  299. package/src/__tests__/provider-streaming.benchmark.test.ts +232 -183
  300. package/src/__tests__/proxy-approval-callback.test.ts +180 -119
  301. package/src/__tests__/public-ingress-urls.test.ts +112 -94
  302. package/src/__tests__/qdrant-manager.test.ts +147 -98
  303. package/src/__tests__/ratelimit.test.ts +152 -82
  304. package/src/__tests__/recording-handler.test.ts +273 -151
  305. package/src/__tests__/recording-intent-fallback.test.ts +94 -75
  306. package/src/__tests__/recording-intent-handler.test.ts +422 -292
  307. package/src/__tests__/recording-intent.test.ts +578 -379
  308. package/src/__tests__/recording-state-machine.test.ts +530 -316
  309. package/src/__tests__/recurrence-engine-rruleset.test.ts +150 -92
  310. package/src/__tests__/recurrence-engine.test.ts +81 -41
  311. package/src/__tests__/recurrence-types.test.ts +63 -44
  312. package/src/__tests__/relay-server.test.ts +2131 -1602
  313. package/src/__tests__/reminder-store.test.ts +158 -80
  314. package/src/__tests__/reminder.test.ts +113 -109
  315. package/src/__tests__/remote-skill-policy.test.ts +96 -72
  316. package/src/__tests__/request-file-tool.test.ts +74 -67
  317. package/src/__tests__/response-tier.test.ts +131 -74
  318. package/src/__tests__/runtime-attachment-metadata.test.ts +107 -70
  319. package/src/__tests__/runtime-events-sse-parity.test.ts +167 -145
  320. package/src/__tests__/runtime-events-sse.test.ts +67 -51
  321. package/src/__tests__/sandbox-diagnostics.test.ts +66 -56
  322. package/src/__tests__/sandbox-host-parity.test.ts +377 -301
  323. package/src/__tests__/scaffold-managed-skill-tool.test.ts +213 -161
  324. package/src/__tests__/schedule-store.test.ts +268 -205
  325. package/src/__tests__/schedule-tools.test.ts +702 -524
  326. package/src/__tests__/scheduler-recurrence.test.ts +240 -130
  327. package/src/__tests__/scoped-approval-grants.test.ts +258 -168
  328. package/src/__tests__/scoped-grant-security-matrix.test.ts +160 -146
  329. package/src/__tests__/script-proxy-certs.test.ts +38 -35
  330. package/src/__tests__/script-proxy-connect-tunnel.test.ts +71 -46
  331. package/src/__tests__/script-proxy-decision-trace.test.ts +161 -84
  332. package/src/__tests__/script-proxy-http-forwarder.test.ts +146 -129
  333. package/src/__tests__/script-proxy-injection-runtime.test.ts +139 -113
  334. package/src/__tests__/script-proxy-mitm-handler.test.ts +226 -142
  335. package/src/__tests__/script-proxy-policy-runtime.test.ts +126 -86
  336. package/src/__tests__/script-proxy-policy.test.ts +308 -153
  337. package/src/__tests__/script-proxy-rewrite-specificity.test.ts +74 -62
  338. package/src/__tests__/script-proxy-router.test.ts +111 -77
  339. package/src/__tests__/script-proxy-session-manager.test.ts +156 -113
  340. package/src/__tests__/script-proxy-session-runtime.test.ts +28 -24
  341. package/src/__tests__/secret-allowlist.test.ts +105 -90
  342. package/src/__tests__/secret-ingress-handler.test.ts +41 -30
  343. package/src/__tests__/secret-onetime-send.test.ts +67 -50
  344. package/src/__tests__/secret-prompt-log-hygiene.test.ts +35 -31
  345. package/src/__tests__/secret-response-routing.test.ts +50 -41
  346. package/src/__tests__/secret-scanner-executor.test.ts +152 -111
  347. package/src/__tests__/secret-scanner.test.ts +495 -413
  348. package/src/__tests__/secure-keys.test.ts +132 -121
  349. package/src/__tests__/send-endpoint-busy.test.ts +313 -232
  350. package/src/__tests__/send-notification-tool.test.ts +43 -42
  351. package/src/__tests__/sensitive-output-placeholders.test.ts +72 -64
  352. package/src/__tests__/sequence-store.test.ts +335 -167
  353. package/src/__tests__/server-history-render.test.ts +341 -202
  354. package/src/__tests__/session-abort-tool-results.test.ts +133 -70
  355. package/src/__tests__/session-approval-overrides.test.ts +93 -91
  356. package/src/__tests__/session-confirmation-signals.test.ts +252 -160
  357. package/src/__tests__/session-conflict-gate.test.ts +775 -585
  358. package/src/__tests__/session-error.test.ts +222 -191
  359. package/src/__tests__/session-evictor.test.ts +79 -62
  360. package/src/__tests__/session-init.benchmark.test.ts +170 -108
  361. package/src/__tests__/session-load-history-repair.test.ts +273 -139
  362. package/src/__tests__/session-messaging-secret-redirect.test.ts +130 -90
  363. package/src/__tests__/session-pre-run-repair.test.ts +106 -59
  364. package/src/__tests__/session-profile-injection.test.ts +198 -130
  365. package/src/__tests__/session-provider-retry-repair.test.ts +223 -141
  366. package/src/__tests__/session-queue.test.ts +624 -321
  367. package/src/__tests__/session-runtime-assembly.test.ts +425 -329
  368. package/src/__tests__/session-runtime-workspace.test.ts +69 -61
  369. package/src/__tests__/session-skill-tools.test.ts +973 -678
  370. package/src/__tests__/session-slash-known.test.ts +185 -133
  371. package/src/__tests__/session-slash-queue.test.ts +147 -81
  372. package/src/__tests__/session-slash-unknown.test.ts +135 -90
  373. package/src/__tests__/session-surfaces-task-progress.test.ts +122 -87
  374. package/src/__tests__/session-tool-setup-app-refresh.test.ts +338 -177
  375. package/src/__tests__/session-tool-setup-memory-scope.test.ts +63 -40
  376. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +60 -37
  377. package/src/__tests__/session-tool-setup-tools-disabled.test.ts +28 -26
  378. package/src/__tests__/session-undo.test.ts +43 -30
  379. package/src/__tests__/session-workspace-cache-state.test.ts +108 -67
  380. package/src/__tests__/session-workspace-injection.test.ts +245 -117
  381. package/src/__tests__/session-workspace-tool-tracking.test.ts +260 -93
  382. package/src/__tests__/shared-filesystem-errors.test.ts +47 -47
  383. package/src/__tests__/shell-credential-ref.test.ts +126 -90
  384. package/src/__tests__/shell-identity.test.ts +134 -111
  385. package/src/__tests__/shell-parser-fuzz.test.ts +263 -179
  386. package/src/__tests__/shell-parser-property.test.ts +435 -288
  387. package/src/__tests__/shell-tool-proxy-mode.test.ts +142 -70
  388. package/src/__tests__/size-guard.test.ts +42 -44
  389. package/src/__tests__/skill-feature-flags-integration.test.ts +79 -52
  390. package/src/__tests__/skill-feature-flags.test.ts +75 -47
  391. package/src/__tests__/skill-include-graph.test.ts +143 -148
  392. package/src/__tests__/skill-load-feature-flag.test.ts +94 -59
  393. package/src/__tests__/skill-load-tool.test.ts +371 -199
  394. package/src/__tests__/skill-projection-feature-flag.test.ts +131 -88
  395. package/src/__tests__/skill-projection.benchmark.test.ts +93 -65
  396. package/src/__tests__/skill-script-runner-host.test.ts +460 -250
  397. package/src/__tests__/skill-script-runner-sandbox.test.ts +168 -108
  398. package/src/__tests__/skill-script-runner.test.ts +115 -74
  399. package/src/__tests__/skill-tool-factory.test.ts +140 -96
  400. package/src/__tests__/skill-tool-manifest.test.ts +306 -210
  401. package/src/__tests__/skill-version-hash.test.ts +70 -56
  402. package/src/__tests__/skills.test.ts +0 -1
  403. package/src/__tests__/slack-channel-config.test.ts +127 -84
  404. package/src/__tests__/slack-skill.test.ts +60 -47
  405. package/src/__tests__/slash-commands-catalog.test.ts +37 -31
  406. package/src/__tests__/slash-commands-parser.test.ts +71 -64
  407. package/src/__tests__/slash-commands-resolver.test.ts +143 -107
  408. package/src/__tests__/slash-commands-rewrite.test.ts +22 -22
  409. package/src/__tests__/sms-messaging-provider.test.ts +74 -47
  410. package/src/__tests__/speaker-identification.test.ts +28 -25
  411. package/src/__tests__/starter-bundle.test.ts +27 -23
  412. package/src/__tests__/starter-task-flow.test.ts +67 -52
  413. package/src/__tests__/subagent-manager-notify.test.ts +154 -108
  414. package/src/__tests__/subagent-tools.test.ts +311 -270
  415. package/src/__tests__/subagent-types.test.ts +40 -40
  416. package/src/__tests__/surface-mutex-cleanup.test.ts +42 -30
  417. package/src/__tests__/swarm-dag-pathological.test.ts +122 -111
  418. package/src/__tests__/swarm-orchestrator.test.ts +135 -101
  419. package/src/__tests__/swarm-plan-validator.test.ts +125 -73
  420. package/src/__tests__/swarm-recursion.test.ts +58 -46
  421. package/src/__tests__/swarm-router-planner.test.ts +99 -74
  422. package/src/__tests__/swarm-session-integration.test.ts +148 -91
  423. package/src/__tests__/swarm-tool.test.ts +65 -45
  424. package/src/__tests__/swarm-worker-backend.test.ts +59 -45
  425. package/src/__tests__/swarm-worker-runner.test.ts +133 -118
  426. package/src/__tests__/system-prompt.test.ts +290 -256
  427. package/src/__tests__/task-compiler.test.ts +176 -120
  428. package/src/__tests__/task-management-tools.test.ts +561 -456
  429. package/src/__tests__/task-memory-cleanup.test.ts +627 -362
  430. package/src/__tests__/task-runner.test.ts +117 -94
  431. package/src/__tests__/task-scheduler.test.ts +113 -84
  432. package/src/__tests__/task-tools.test.ts +349 -264
  433. package/src/__tests__/terminal-sandbox.test.ts +138 -108
  434. package/src/__tests__/terminal-tools.test.ts +350 -305
  435. package/src/__tests__/thread-seed-composer.test.ts +307 -180
  436. package/src/__tests__/tool-approval-handler.test.ts +238 -137
  437. package/src/__tests__/tool-audit-listener.test.ts +69 -69
  438. package/src/__tests__/tool-domain-event-publisher.test.ts +142 -132
  439. package/src/__tests__/tool-execution-abort-cleanup.test.ts +153 -146
  440. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +136 -105
  441. package/src/__tests__/tool-executor-lifecycle-events.test.ts +355 -239
  442. package/src/__tests__/tool-executor-redaction.test.ts +112 -109
  443. package/src/__tests__/tool-executor-shell-integration.test.ts +130 -79
  444. package/src/__tests__/tool-executor.test.ts +1274 -674
  445. package/src/__tests__/tool-grant-request-escalation.test.ts +401 -283
  446. package/src/__tests__/tool-metrics-listener.test.ts +97 -85
  447. package/src/__tests__/tool-notification-listener.test.ts +42 -25
  448. package/src/__tests__/tool-permission-simulate-handler.test.ts +137 -113
  449. package/src/__tests__/tool-policy.test.ts +44 -25
  450. package/src/__tests__/tool-profiling-listener.test.ts +99 -93
  451. package/src/__tests__/tool-result-truncation.test.ts +5 -4
  452. package/src/__tests__/tool-trace-listener.test.ts +131 -111
  453. package/src/__tests__/top-level-renderer.test.ts +62 -58
  454. package/src/__tests__/top-level-scanner.test.ts +68 -64
  455. package/src/__tests__/trace-emitter.test.ts +56 -56
  456. package/src/__tests__/trust-context-guards.test.ts +65 -65
  457. package/src/__tests__/trust-store.test.ts +1239 -806
  458. package/src/__tests__/trusted-contact-approval-notifier.test.ts +339 -275
  459. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +484 -373
  460. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +264 -241
  461. package/src/__tests__/trusted-contact-multichannel.test.ts +182 -142
  462. package/src/__tests__/trusted-contact-verification.test.ts +251 -231
  463. package/src/__tests__/turn-commit.test.ts +259 -200
  464. package/src/__tests__/twilio-config.test.ts +49 -41
  465. package/src/__tests__/twilio-provider.test.ts +140 -126
  466. package/src/__tests__/twilio-rest.test.ts +22 -18
  467. package/src/__tests__/twilio-routes-elevenlabs.test.ts +188 -162
  468. package/src/__tests__/twilio-routes-twiml.test.ts +55 -55
  469. package/src/__tests__/twilio-routes.test.ts +389 -281
  470. package/src/__tests__/twitter-auth-handler.test.ts +184 -139
  471. package/src/__tests__/twitter-cli-error-shaping.test.ts +88 -73
  472. package/src/__tests__/twitter-cli-routing.test.ts +146 -99
  473. package/src/__tests__/twitter-oauth-client.test.ts +82 -65
  474. package/src/__tests__/update-bulletin-format.test.ts +69 -66
  475. package/src/__tests__/update-bulletin-state.test.ts +66 -60
  476. package/src/__tests__/update-bulletin.test.ts +150 -114
  477. package/src/__tests__/update-template-contract.test.ts +15 -10
  478. package/src/__tests__/url-safety.test.ts +288 -265
  479. package/src/__tests__/user-reference.test.ts +32 -32
  480. package/src/__tests__/view-image-tool.test.ts +118 -96
  481. package/src/__tests__/voice-invite-redemption.test.ts +111 -106
  482. package/src/__tests__/voice-quality.test.ts +117 -102
  483. package/src/__tests__/voice-scoped-grant-consumer.test.ts +204 -146
  484. package/src/__tests__/voice-session-bridge.test.ts +351 -216
  485. package/src/__tests__/weather-skill-regression.test.ts +170 -120
  486. package/src/__tests__/web-fetch.test.ts +664 -526
  487. package/src/__tests__/web-search.test.ts +379 -213
  488. package/src/__tests__/work-item-output.test.ts +90 -53
  489. package/src/__tests__/workspace-git-service.test.ts +437 -356
  490. package/src/__tests__/workspace-heartbeat-service.test.ts +125 -91
  491. package/src/__tests__/workspace-lifecycle.test.ts +98 -64
  492. package/src/__tests__/workspace-policy.test.ts +139 -71
  493. package/src/commands/__tests__/cc-command-registry.test.ts +142 -134
  494. package/src/config/__tests__/feature-flag-registry-guard.test.ts +48 -39
  495. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +44 -4
  496. package/src/config/bundled-skills/doordash/__tests__/doordash-session.test.ts +0 -1
  497. package/src/config/bundled-skills/messaging/SKILL.md +9 -7
  498. package/src/config/bundled-skills/messaging/tools/gmail-outreach-scan.ts +15 -5
  499. package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +16 -5
  500. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +11 -7
  501. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +34 -32
  502. package/src/config/bundled-tool-registry.ts +2 -0
  503. package/src/config/env.ts +38 -29
  504. package/src/daemon/handlers/skills.ts +18 -10
  505. package/src/daemon/ipc-contract/messages.ts +1 -0
  506. package/src/daemon/ipc-contract/surfaces.ts +7 -1
  507. package/src/daemon/session-agent-loop-handlers.ts +5 -0
  508. package/src/daemon/session-agent-loop.ts +1 -1
  509. package/src/daemon/session-process.ts +1 -1
  510. package/src/daemon/session-surfaces.ts +42 -2
  511. package/src/memory/db-connection.ts +16 -10
  512. package/src/messaging/providers/gmail/adapter.ts +10 -3
  513. package/src/messaging/providers/gmail/client.ts +280 -72
  514. package/src/runtime/auth/__tests__/context.test.ts +75 -65
  515. package/src/runtime/auth/__tests__/credential-service.test.ts +137 -114
  516. package/src/runtime/auth/__tests__/guard-tests.test.ts +84 -90
  517. package/src/runtime/auth/__tests__/ipc-auth-context.test.ts +40 -40
  518. package/src/runtime/auth/__tests__/middleware.test.ts +80 -74
  519. package/src/runtime/auth/__tests__/policy.test.ts +9 -9
  520. package/src/runtime/auth/__tests__/route-policy.test.ts +76 -65
  521. package/src/runtime/auth/__tests__/scopes.test.ts +68 -60
  522. package/src/runtime/auth/__tests__/subject.test.ts +54 -54
  523. package/src/runtime/auth/__tests__/token-service.test.ts +115 -108
  524. package/src/runtime/auth/scopes.ts +3 -0
  525. package/src/runtime/auth/token-service.ts +78 -48
  526. package/src/runtime/auth/types.ts +2 -1
  527. package/src/runtime/http-server.ts +2 -1
  528. package/src/security/secure-keys.ts +103 -53
  529. package/src/sequence/reply-matcher.ts +10 -6
  530. package/src/skills/frontmatter.ts +9 -6
  531. package/src/tools/browser/__tests__/auth-cache.test.ts +69 -63
  532. package/src/tools/browser/__tests__/auth-detector.test.ts +218 -157
  533. package/src/tools/browser/__tests__/jit-auth.test.ts +83 -99
  534. package/src/tools/ui-surface/definitions.ts +2 -1
  535. package/src/util/platform.ts +0 -12
  536. package/docs/architecture/http-token-refresh.md +0 -274
@@ -1,26 +1,28 @@
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
-
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(tmpdir(), `vellum-schema-test-${randomBytes(4).toString('hex')}`);
13
- const WORKSPACE_DIR = join(TEST_DIR, 'workspace');
14
- const CONFIG_PATH = join(WORKSPACE_DIR, 'config.json');
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, 'data'),
21
- join(TEST_DIR, 'memory'),
22
- join(TEST_DIR, 'memory', 'knowledge'),
23
- join(TEST_DIR, 'logs'),
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 ['info', 'warn', 'error', 'debug', 'trace', 'fatal', 'silent', 'child']) {
33
- stub[m] = m === 'child' ? () => makeLoggerStub() : () => {};
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('../util/logger.js', () => ({
49
+ mock.module("../util/logger.js", () => ({
39
50
  getLogger: () => makeLoggerStub(),
40
51
  }));
41
52
 
42
- mock.module('../util/platform.js', () => ({
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, 'data'),
47
- getLogPath: () => join(TEST_DIR, 'logs', 'vellum.log'),
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 { buildElevenLabsVoiceSpec, resolveVoiceQualityProfile } from '../calls/voice-quality.js';
55
- import { invalidateConfigCache,loadConfig } from '../config/loader.js';
56
- import { AssistantConfigSchema } from '../config/schema.js';
57
- import { _setStorePath } from '../security/encrypted-store.js';
58
- import { _setBackend } from '../security/secure-keys.js';
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('AssistantConfigSchema', () => {
73
- test('parses empty object with full defaults', () => {
86
+ describe("AssistantConfigSchema", () => {
87
+ test("parses empty object with full defaults", () => {
74
88
  const result = AssistantConfigSchema.parse({});
75
- expect(result.provider).toBe('anthropic');
76
- expect(result.model).toBe('claude-opus-4-6');
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({ enabled: false, budgetTokens: 10000, streamThinking: false });
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({ maxRequestsPerMinute: 0, maxTokensPerSession: 0 });
100
- expect(result.secretDetection).toEqual({ enabled: true, action: 'redact', entropyThreshold: 4.0, allowOneTimeSend: false, blockIngress: true });
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('accepts valid complete config', () => {
131
+ test("accepts valid complete config", () => {
105
132
  const input = {
106
- provider: 'openai',
107
- model: 'gpt-4',
133
+ provider: "openai",
134
+ model: "gpt-4",
108
135
  maxTokens: 4096,
109
- apiKeys: { openai: 'sk-test' },
136
+ apiKeys: { openai: "sk-test" },
110
137
  thinking: { enabled: true, budgetTokens: 5000 },
111
- timeouts: { shellDefaultTimeoutSec: 30, shellMaxTimeoutSec: 300, permissionTimeoutSec: 60 },
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: { enabled: false, action: 'block' as const, entropyThreshold: 5.5 },
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('openai');
119
- expect(result.model).toBe('gpt-4');
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('block');
157
+ expect(result.secretDetection.action).toBe("block");
123
158
  });
124
159
 
125
- test('applies memory.conflicts defaults', () => {
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: 'soft',
164
+ gateMode: "soft",
130
165
  reaskCooldownTurns: 3,
131
166
  resolverLlmTimeoutMs: 12000,
132
167
  relevanceThreshold: 0.3,
133
168
  askOnIrrelevantTurns: false,
134
- conflictableKinds: ['preference', 'profile', 'constraint', 'instruction', 'style'],
169
+ conflictableKinds: [
170
+ "preference",
171
+ "profile",
172
+ "constraint",
173
+ "instruction",
174
+ "style",
175
+ ],
135
176
  });
136
177
  });
137
178
 
138
- test('rejects invalid memory.conflicts.relevanceThreshold', () => {
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('rejects invalid memory.conflicts.askOnIrrelevantTurns', () => {
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('rejects invalid memory.conflicts.conflictableKinds entry', () => {
193
+ test("rejects invalid memory.conflicts.conflictableKinds entry", () => {
153
194
  const result = AssistantConfigSchema.safeParse({
154
- memory: { conflicts: { conflictableKinds: ['invalid_kind'] } },
195
+ memory: { conflicts: { conflictableKinds: ["invalid_kind"] } },
155
196
  });
156
197
  expect(result.success).toBe(false);
157
198
  });
158
199
 
159
- test('rejects empty memory.conflicts.conflictableKinds', () => {
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('applies memory.profile defaults', () => {
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('rejects invalid memory.profile.maxInjectTokens', () => {
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('applies rollout defaults for dynamic budget and entity relation features', () => {
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('applies memory.cleanup defaults', () => {
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('rejects invalid memory.cleanup.enqueueIntervalMs', () => {
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('rejects invalid provider', () => {
223
- const result = AssistantConfigSchema.safeParse({ provider: 'invalid' });
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('provider'))).toBe(true);
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('rejects negative maxTokens', () => {
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(result.error.issues.some(i => i.path.includes('maxTokens'))).toBe(true);
276
+ expect(
277
+ result.error.issues.some((i) => i.path.includes("maxTokens")),
278
+ ).toBe(true);
236
279
  }
237
280
  });
238
281
 
239
- test('rejects non-integer maxTokens', () => {
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(result.error.issues.some(i => i.path.includes('maxTokens'))).toBe(true);
286
+ expect(
287
+ result.error.issues.some((i) => i.path.includes("maxTokens")),
288
+ ).toBe(true);
244
289
  }
245
290
  });
246
291
 
247
- test('rejects string maxTokens', () => {
248
- const result = AssistantConfigSchema.safeParse({ maxTokens: 'not-a-number' });
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(result.error.issues.some(i => i.path.includes('maxTokens'))).toBe(true);
298
+ expect(
299
+ result.error.issues.some((i) => i.path.includes("maxTokens")),
300
+ ).toBe(true);
252
301
  }
253
302
  });
254
303
 
255
- test('rejects invalid timeout values', () => {
304
+ test("rejects invalid timeout values", () => {
256
305
  const result = AssistantConfigSchema.safeParse({
257
- timeouts: { shellDefaultTimeoutSec: -5, shellMaxTimeoutSec: 'bad', permissionTimeoutSec: 0 },
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('rejects invalid thinking config', () => {
318
+ test("rejects invalid thinking config", () => {
266
319
  const result = AssistantConfigSchema.safeParse({
267
- thinking: { enabled: 'yes', budgetTokens: -100 },
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('rejects contextWindow targetInputTokens >= maxInputTokens', () => {
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('.') === 'contextWindow.targetInputTokens'
285
- && issue.message.includes('must be less than contextWindow.maxInputTokens'),
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('rejects invalid secretDetection.action', () => {
346
+ test("rejects invalid secretDetection.action", () => {
292
347
  const result = AssistantConfigSchema.safeParse({
293
- secretDetection: { action: 'explode' },
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('secretDetection.action'))).toBe(true);
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('rejects negative secretDetection.entropyThreshold', () => {
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('rejects negative rateLimit values', () => {
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('rejects non-integer rateLimit values', () => {
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('rejects negative auditLog.retentionDays', () => {
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('rejects non-string apiKeys values', () => {
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('accepts partial nested objects with defaults', () => {
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('accepts zero for non-negative fields', () => {
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('accepts all valid provider values', () => {
357
- for (const provider of ['anthropic', 'openai', 'gemini', 'ollama'] as const) {
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('accepts all valid secretDetection.action values', () => {
364
- for (const action of ['redact', 'warn', 'block'] as const) {
365
- const result = AssistantConfigSchema.safeParse({ secretDetection: { action } });
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('provides helpful error messages', () => {
432
+ test("provides helpful error messages", () => {
371
433
  const result = AssistantConfigSchema.safeParse({
372
- provider: 'invalid',
434
+ provider: "invalid",
373
435
  maxTokens: -1,
374
- secretDetection: { action: 'explode' },
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(messages.some(m => m.includes('anthropic') && m.includes('openai'))).toBe(true);
381
- expect(messages.some(m => m.includes('positive'))).toBe(true);
382
- expect(messages.some(m => m.includes('redact') && m.includes('warn') && m.includes('block'))).toBe(true);
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('sandbox with only enabled still parses', () => {
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('rejects unknown sandbox fields', () => {
460
+ test("rejects unknown sandbox fields", () => {
392
461
  const result = AssistantConfigSchema.safeParse({
393
- sandbox: { backend: 'docker' },
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((result.data.sandbox as Record<string, unknown>)['backend']).toBeUndefined();
467
+ expect(
468
+ (result.data.sandbox as Record<string, unknown>)["backend"],
469
+ ).toBeUndefined();
399
470
  }
400
471
  });
401
472
 
402
- test('defaults permissions.mode to workspace', () => {
473
+ test("defaults permissions.mode to workspace", () => {
403
474
  const result = AssistantConfigSchema.parse({});
404
- expect(result.permissions).toEqual({ mode: 'workspace' });
475
+ expect(result.permissions).toEqual({ mode: "workspace" });
405
476
  });
406
477
 
407
- test('accepts explicit permissions.mode strict', () => {
478
+ test("accepts explicit permissions.mode strict", () => {
408
479
  const result = AssistantConfigSchema.parse({
409
- permissions: { mode: 'strict' },
480
+ permissions: { mode: "strict" },
410
481
  });
411
- expect(result.permissions.mode).toBe('strict');
482
+ expect(result.permissions.mode).toBe("strict");
412
483
  });
413
484
 
414
- test('accepts explicit permissions.mode legacy', () => {
485
+ test("accepts explicit permissions.mode legacy", () => {
415
486
  const result = AssistantConfigSchema.parse({
416
- permissions: { mode: 'legacy' },
487
+ permissions: { mode: "legacy" },
417
488
  });
418
- expect(result.permissions.mode).toBe('legacy');
489
+ expect(result.permissions.mode).toBe("legacy");
419
490
  });
420
491
 
421
- test('accepts explicit permissions.mode workspace', () => {
492
+ test("accepts explicit permissions.mode workspace", () => {
422
493
  const result = AssistantConfigSchema.parse({
423
- permissions: { mode: 'workspace' },
494
+ permissions: { mode: "workspace" },
424
495
  });
425
- expect(result.permissions.mode).toBe('workspace');
496
+ expect(result.permissions.mode).toBe("workspace");
426
497
  });
427
498
 
428
- test('rejects invalid permissions.mode', () => {
499
+ test("rejects invalid permissions.mode", () => {
429
500
  const result = AssistantConfigSchema.safeParse({
430
- permissions: { mode: 'permissive' },
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('permissions.mode'))).toBe(true);
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('applies workspaceGit defaults including interactiveGitTimeoutMs', () => {
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('accepts custom workspaceGit.interactiveGitTimeoutMs', () => {
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('rejects non-positive workspaceGit.interactiveGitTimeoutMs', () => {
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('rejects non-integer workspaceGit.interactiveGitTimeoutMs', () => {
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('rejects non-number workspaceGit.interactiveGitTimeoutMs', () => {
568
+ test("rejects non-number workspaceGit.interactiveGitTimeoutMs", () => {
498
569
  const result = AssistantConfigSchema.safeParse({
499
- workspaceGit: { interactiveGitTimeoutMs: 'fast' },
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('default commitMessageLLM values are correct', () => {
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('rejects negative commitMessageLLM.timeoutMs', () => {
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('rejects commitMessageLLM.temperature > 2', () => {
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('breaker settings have correct defaults', () => {
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('accepts valid commitMessageLLM overrides', () => {
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(5);
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(2000);
631
+ expect(result.workspaceGit.commitMessageLLM.breaker.backoffBaseMs).toBe(
632
+ 2000,
633
+ );
559
634
  });
560
635
 
561
- test('rejects commitMessageLLM.temperature < 0', () => {
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('rejects non-integer commitMessageLLM.maxTokens', () => {
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('applies calls defaults', () => {
652
+ test("applies calls defaults", () => {
578
653
  const result = AssistantConfigSchema.parse({});
579
654
  expect(result.calls).toEqual({
580
655
  enabled: true,
581
- provider: 'twilio',
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: 'twilio_standard',
599
- language: 'en-US',
600
- transcriptionProvider: 'Deepgram',
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: 'https://api.elevenlabs.io',
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('accepts valid calls config overrides', () => {
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: 'Custom disclosure' },
632
- safety: { denyCategories: ['spam'] },
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('Custom disclosure');
640
- expect(result.calls.safety.denyCategories).toEqual(['spam']);
714
+ expect(result.calls.disclosure.text).toBe("Custom disclosure");
715
+ expect(result.calls.safety.denyCategories).toEqual(["spam"]);
641
716
  });
642
717
 
643
- test('accepts partial calls config with defaults for missing fields', () => {
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('twilio');
725
+ expect(result.calls.provider).toBe("twilio");
651
726
  });
652
727
 
653
- test('rejects invalid calls.enabled', () => {
728
+ test("rejects invalid calls.enabled", () => {
654
729
  const result = AssistantConfigSchema.safeParse({
655
- calls: { enabled: 'yes' },
730
+ calls: { enabled: "yes" },
656
731
  });
657
732
  expect(result.success).toBe(false);
658
733
  });
659
734
 
660
- test('rejects invalid calls.provider', () => {
735
+ test("rejects invalid calls.provider", () => {
661
736
  const result = AssistantConfigSchema.safeParse({
662
- calls: { provider: 'vonage' },
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('calls.provider'))).toBe(true);
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('rejects non-positive calls.maxDurationSeconds', () => {
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('rejects non-integer calls.maxDurationSeconds', () => {
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('rejects non-positive calls.userConsultTimeoutSeconds', () => {
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('rejects non-boolean calls.disclosure.enabled', () => {
767
+ test("rejects non-boolean calls.disclosure.enabled", () => {
693
768
  const result = AssistantConfigSchema.safeParse({
694
- calls: { disclosure: { enabled: 'true' } },
769
+ calls: { disclosure: { enabled: "true" } },
695
770
  });
696
771
  expect(result.success).toBe(false);
697
772
  });
698
773
 
699
- test('rejects non-string calls.disclosure.text', () => {
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('rejects non-array calls.safety.denyCategories', () => {
781
+ test("rejects non-array calls.safety.denyCategories", () => {
707
782
  const result = AssistantConfigSchema.safeParse({
708
- calls: { safety: { denyCategories: 'spam' } },
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('config without calls.voice parses correctly and produces defaults', () => {
790
+ test("config without calls.voice parses correctly and produces defaults", () => {
716
791
  const result = AssistantConfigSchema.parse({});
717
- expect(result.calls.voice.mode).toBe('twilio_standard');
718
- expect(result.calls.voice.language).toBe('en-US');
719
- expect(result.calls.voice.transcriptionProvider).toBe('Deepgram');
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('https://api.elevenlabs.io');
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('legacy style field is silently stripped by schema', () => {
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((result.calls.voice.elevenlabs as Record<string, unknown>).style).toBeUndefined();
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('rejects calls.voice.elevenlabs.speed below 0.7', () => {
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('rejects calls.voice.elevenlabs.speed above 1.2', () => {
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('accepts valid calls.voice overrides', () => {
833
+ test("accepts valid calls.voice overrides", () => {
755
834
  const result = AssistantConfigSchema.parse({
756
835
  calls: {
757
836
  voice: {
758
- mode: 'twilio_elevenlabs_tts',
759
- language: 'es-ES',
760
- transcriptionProvider: 'Google',
837
+ mode: "twilio_elevenlabs_tts",
838
+ language: "es-ES",
839
+ transcriptionProvider: "Google",
761
840
  fallbackToStandardOnError: false,
762
841
  elevenlabs: {
763
- voiceId: 'abc123',
842
+ voiceId: "abc123",
764
843
  stability: 0.8,
765
844
  },
766
845
  },
767
846
  },
768
847
  });
769
- expect(result.calls.voice.mode).toBe('twilio_elevenlabs_tts');
770
- expect(result.calls.voice.language).toBe('es-ES');
771
- expect(result.calls.voice.transcriptionProvider).toBe('Google');
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('abc123');
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('rejects invalid calls.voice.mode', () => {
859
+ test("rejects invalid calls.voice.mode", () => {
781
860
  const result = AssistantConfigSchema.safeParse({
782
- calls: { voice: { mode: 'invalid_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('calls.voice.mode'))).toBe(true);
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('rejects invalid calls.voice.transcriptionProvider', () => {
870
+ test("rejects invalid calls.voice.transcriptionProvider", () => {
792
871
  const result = AssistantConfigSchema.safeParse({
793
- calls: { voice: { transcriptionProvider: 'AWS' } },
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(msgs.some(m => m.includes('calls.voice.transcriptionProvider'))).toBe(true);
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('rejects calls.voice.elevenlabs.stability out of range', () => {
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('rejects calls.voice.elevenlabs.registerCallTimeoutMs below 1000', () => {
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('rejects calls.voice.elevenlabs.registerCallTimeoutMs above 15000', () => {
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('accepts optional calls.model', () => {
904
+ test("accepts optional calls.model", () => {
824
905
  const result = AssistantConfigSchema.parse({
825
- calls: { model: 'claude-haiku-4-5-20251001' },
906
+ calls: { model: "claude-haiku-4-5-20251001" },
826
907
  });
827
- expect(result.calls.model).toBe('claude-haiku-4-5-20251001');
908
+ expect(result.calls.model).toBe("claude-haiku-4-5-20251001");
828
909
  });
829
910
 
830
- test('calls.model is undefined by default', () => {
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('applies calls.callerIdentity defaults', () => {
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('accepts valid calls.callerIdentity overrides', () => {
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: '+14155559999',
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('+14155559999');
935
+ expect(result.calls.callerIdentity.userNumber).toBe("+14155559999");
855
936
  });
856
937
 
857
- test('legacy defaultMode field is silently stripped by schema', () => {
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: { defaultMode: 'user_number', allowPerCallOverride: true },
943
+ callerIdentity: {
944
+ defaultMode: "user_number",
945
+ allowPerCallOverride: true,
946
+ },
863
947
  },
864
948
  });
865
- expect((result.calls.callerIdentity as Record<string, unknown>).defaultMode).toBeUndefined();
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('rejects non-boolean calls.callerIdentity.allowPerCallOverride', () => {
955
+ test("rejects non-boolean calls.callerIdentity.allowPerCallOverride", () => {
870
956
  const result = AssistantConfigSchema.safeParse({
871
- calls: { callerIdentity: { allowPerCallOverride: 'yes' } },
957
+ calls: { callerIdentity: { allowPerCallOverride: "yes" } },
872
958
  });
873
959
  expect(result.success).toBe(false);
874
960
  });
875
961
 
876
- test('default behavior unchanged when callerIdentity omitted', () => {
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('resolveVoiceQualityProfile', () => {
889
- test('returns correct profile for twilio_standard', () => {
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('twilio_standard');
893
- expect(profile.ttsProvider).toBe('Google');
894
- expect(profile.voice).toBe('Google.en-US-Journey-O');
895
- expect(profile.transcriptionProvider).toBe('Deepgram');
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('returns correct profile for twilio_elevenlabs_tts with valid voiceId', () => {
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: 'twilio_elevenlabs_tts',
905
- elevenlabs: { voiceId: 'test-voice-id' },
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('twilio_elevenlabs_tts');
911
- expect(profile.ttsProvider).toBe('ElevenLabs');
912
- expect(profile.voice).toBe('test-voice-id');
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('falls back for twilio_elevenlabs_tts with empty voiceId and fallback enabled', () => {
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: 'twilio_elevenlabs_tts',
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('twilio_standard');
928
- expect(profile.ttsProvider).toBe('Google');
929
- expect(profile.voice).toBe('Google.en-US-Journey-O');
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('falling back');
1017
+ expect(profile.validationErrors[0]).toContain("falling back");
932
1018
  });
933
1019
 
934
- test('returns errors for twilio_elevenlabs_tts with empty voiceId and fallback disabled', () => {
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: 'twilio_elevenlabs_tts',
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('twilio_elevenlabs_tts');
1031
+ expect(profile.mode).toBe("twilio_elevenlabs_tts");
946
1032
  expect(profile.validationErrors.length).toBe(1);
947
- expect(profile.validationErrors[0]).toContain('voiceId is required');
1033
+ expect(profile.validationErrors[0]).toContain("voiceId is required");
948
1034
  });
949
1035
 
950
- test('returns correct profile for elevenlabs_agent with valid agentId', () => {
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: 'elevenlabs_agent',
955
- elevenlabs: { agentId: 'agent-123', voiceId: 'v1' },
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('elevenlabs_agent');
961
- expect(profile.ttsProvider).toBe('ElevenLabs');
962
- expect(profile.voice).toBe('v1');
963
- expect(profile.agentId).toBe('agent-123');
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('falls back for elevenlabs_agent with empty agentId and fallback enabled', () => {
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: 'elevenlabs_agent',
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('twilio_standard');
1064
+ expect(profile.mode).toBe("twilio_standard");
979
1065
  expect(profile.validationErrors.length).toBe(1);
980
- expect(profile.validationErrors[0]).toContain('agentId is empty');
1066
+ expect(profile.validationErrors[0]).toContain("agentId is empty");
981
1067
  });
982
1068
 
983
- test('returns errors for elevenlabs_agent with empty agentId and fallback disabled', () => {
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: 'elevenlabs_agent',
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('elevenlabs_agent');
1080
+ expect(profile.mode).toBe("elevenlabs_agent");
995
1081
  expect(profile.validationErrors.length).toBe(1);
996
- expect(profile.validationErrors[0]).toContain('agentId is required');
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('buildElevenLabsVoiceSpec', () => {
1005
- test('produces Twilio-compliant voice string: voiceId-model-speed_stability_similarity', () => {
1090
+ describe("buildElevenLabsVoiceSpec", () => {
1091
+ test("produces Twilio-compliant voice string: voiceId-model-speed_stability_similarity", () => {
1006
1092
  const spec = buildElevenLabsVoiceSpec({
1007
- voiceId: 'abc123',
1008
- voiceModelId: 'turbo_v2_5',
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('abc123-turbo_v2_5-1_0.5_0.75');
1099
+ expect(spec).toBe("abc123-turbo_v2_5-1_0.5_0.75");
1014
1100
  });
1015
1101
 
1016
- test('returns empty string when voiceId is empty', () => {
1102
+ test("returns empty string when voiceId is empty", () => {
1017
1103
  const spec = buildElevenLabsVoiceSpec({
1018
- voiceId: '',
1019
- voiceModelId: 'turbo_v2_5',
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('formats custom parameters correctly', () => {
1113
+ test("formats custom parameters correctly", () => {
1028
1114
  const spec = buildElevenLabsVoiceSpec({
1029
- voiceId: 'myVoice',
1030
- voiceModelId: 'eleven_multilingual_v2',
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('myVoice-eleven_multilingual_v2-0.9_0.8_0.9');
1121
+ expect(spec).toBe("myVoice-eleven_multilingual_v2-0.9_0.8_0.9");
1036
1122
  });
1037
1123
 
1038
- test('default config uses a bare voiceId when no model override is set', () => {
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: 'twilio_elevenlabs_tts',
1043
- elevenlabs: { voiceId: 'test' },
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('test');
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('loadConfig with schema validation', () => {
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, 'keys.enc'),
1063
- join(TEST_DIR, 'data'),
1064
- join(TEST_DIR, 'memory'),
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, 'keys.enc'));
1073
- _setBackend('encrypted');
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('loads valid config', () => {
1172
+ test("loads valid config", () => {
1087
1173
  writeConfig({
1088
- provider: 'openai',
1089
- model: 'gpt-4',
1174
+ provider: "openai",
1175
+ model: "gpt-4",
1090
1176
  maxTokens: 4096,
1091
1177
  });
1092
1178
  const config = loadConfig();
1093
- expect(config.provider).toBe('openai');
1094
- expect(config.model).toBe('gpt-4');
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('applies defaults for missing fields', () => {
1184
+ test("applies defaults for missing fields", () => {
1099
1185
  writeConfig({});
1100
1186
  const config = loadConfig();
1101
- expect(config.provider).toBe('anthropic');
1102
- expect(config.model).toBe('claude-opus-4-6');
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({ enabled: false, budgetTokens: 10000, streamThinking: false });
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('falls back to default for invalid provider', () => {
1117
- writeConfig({ provider: 'invalid-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('anthropic');
1209
+ expect(config.provider).toBe("anthropic");
1120
1210
  });
1121
1211
 
1122
- test('falls back to default for invalid maxTokens', () => {
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('falls back to defaults for invalid nested values', () => {
1218
+ test("falls back to defaults for invalid nested values", () => {
1129
1219
  writeConfig({
1130
- timeouts: { shellDefaultTimeoutSec: -5, shellMaxTimeoutSec: 'bad' },
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('preserves valid fields when other fields are invalid', () => {
1228
+ test("preserves valid fields when other fields are invalid", () => {
1139
1229
  writeConfig({
1140
- provider: 'openai',
1141
- model: 'gpt-4',
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('openai');
1147
- expect(config.model).toBe('gpt-4');
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('handles no config file', () => {
1243
+ test("handles no config file", () => {
1154
1244
  const config = loadConfig();
1155
- expect(config.provider).toBe('anthropic');
1245
+ expect(config.provider).toBe("anthropic");
1156
1246
  expect(config.maxTokens).toBe(16000);
1157
1247
  });
1158
1248
 
1159
- test('partial nested objects get defaults for missing fields', () => {
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('falls back for invalid secretDetection.action', () => {
1170
- writeConfig({ secretDetection: { action: 'explode' } });
1259
+ test("falls back for invalid secretDetection.action", () => {
1260
+ writeConfig({ secretDetection: { action: "explode" } });
1171
1261
  const config = loadConfig();
1172
- expect(config.secretDetection.action).toBe('redact');
1262
+ expect(config.secretDetection.action).toBe("redact");
1173
1263
  });
1174
1264
 
1175
- test('falls back for invalid sandbox.enabled', () => {
1176
- writeConfig({ sandbox: { enabled: 'yes' } });
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('loads sandbox with only enabled (backward compatibility)', () => {
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('strips unknown sandbox fields', () => {
1188
- writeConfig({ sandbox: { enabled: true, backend: 'docker' } });
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('backend' in config.sandbox).toBe(false);
1281
+ expect("backend" in config.sandbox).toBe(false);
1192
1282
  });
1193
1283
 
1194
- test('falls back for invalid contextWindow relationship', () => {
1195
- writeConfig({ contextWindow: { maxInputTokens: 1000, targetInputTokens: 1000 } });
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('falls back for invalid rateLimit values', () => {
1202
- writeConfig({ rateLimit: { maxRequestsPerMinute: -1, maxTokensPerSession: 3.5 } });
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('falls back for invalid auditLog.retentionDays', () => {
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('defaults permissions.mode to workspace when not specified', () => {
1308
+ test("defaults permissions.mode to workspace when not specified", () => {
1215
1309
  writeConfig({});
1216
1310
  const config = loadConfig();
1217
- expect(config.permissions).toEqual({ mode: 'workspace' });
1311
+ expect(config.permissions).toEqual({ mode: "workspace" });
1218
1312
  });
1219
1313
 
1220
- test('loads explicit permissions.mode strict', () => {
1221
- writeConfig({ permissions: { mode: 'strict' } });
1314
+ test("loads explicit permissions.mode strict", () => {
1315
+ writeConfig({ permissions: { mode: "strict" } });
1222
1316
  const config = loadConfig();
1223
- expect(config.permissions.mode).toBe('strict');
1317
+ expect(config.permissions.mode).toBe("strict");
1224
1318
  });
1225
1319
 
1226
- test('falls back for invalid permissions.mode', () => {
1227
- writeConfig({ permissions: { mode: 'yolo' } });
1320
+ test("falls back for invalid permissions.mode", () => {
1321
+ writeConfig({ permissions: { mode: "yolo" } });
1228
1322
  const config = loadConfig();
1229
- expect(config.permissions.mode).toBe('workspace');
1323
+ expect(config.permissions.mode).toBe("workspace");
1230
1324
  });
1231
1325
 
1232
- test('does not mutate default apiKeys when fallback config is overridden by env keys', () => {
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 = ['test', 'in', 'memory', 'default', 'leak'].join('-');
1329
+ const testKey = ["test", "in", "memory", "default", "leak"].join("-");
1236
1330
  process.env.ANTHROPIC_API_KEY = testKey;
1237
- writeConfig('this is not a config object');
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('still not a config object');
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('loads calls config from file', () => {
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('twilio');
1361
+ expect(config.calls.provider).toBe("twilio");
1268
1362
  });
1269
1363
 
1270
- test('falls back for invalid calls.provider', () => {
1271
- writeConfig({ calls: { provider: 'vonage' } });
1364
+ test("falls back for invalid calls.provider", () => {
1365
+ writeConfig({ calls: { provider: "vonage" } });
1272
1366
  const config = loadConfig();
1273
- expect(config.calls.provider).toBe('twilio');
1367
+ expect(config.calls.provider).toBe("twilio");
1274
1368
  });
1275
1369
 
1276
- test('applies calls defaults when not specified', () => {
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('twilio_standard');
1285
- expect(config.calls.voice.language).toBe('en-US');
1286
- expect(config.calls.voice.transcriptionProvider).toBe('Deepgram');
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('Call entrypoint gating', () => {
1393
+ describe("Call entrypoint gating", () => {
1300
1394
  beforeEach(() => {
1301
1395
  ensureTestDir();
1302
1396
  const resetPaths = [
1303
1397
  CONFIG_PATH,
1304
- join(TEST_DIR, 'keys.enc'),
1305
- join(TEST_DIR, 'data'),
1306
- join(TEST_DIR, 'memory'),
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, 'keys.enc'));
1315
- _setBackend('encrypted');
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('call_start tool returns error when calls.enabled is false', async () => {
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 } = await import('../tools/calls/call-start.js');
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('../config/loader.js');
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('handleStartCall route returns 403 when calls.enabled is false', async () => {
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 } = await import('../runtime/routes/call-routes.js');
1344
- const req = new Request('http://localhost/v1/calls/start', {
1345
- method: 'POST',
1346
- headers: { 'Content-Type': 'application/json' },
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: '+14155551234',
1349
- task: 'Test call',
1350
- conversationId: 'test-conv-id',
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('disabled');
1453
+ const body = (await response.json()) as { error: { message: string } };
1454
+ expect(body.error.message).toContain("disabled");
1359
1455
  });
1360
1456
  });