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