@vellumai/assistant 0.7.0 → 0.7.1

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 (666) hide show
  1. package/ARCHITECTURE.md +6 -7
  2. package/Dockerfile +1 -0
  3. package/README.md +2 -2
  4. package/__tests__/permissions/gateway-threshold-reader.test.ts +79 -139
  5. package/bun.lock +3 -0
  6. package/docs/architecture/security.md +18 -16
  7. package/knip.json +1 -0
  8. package/node_modules/@vellumai/skill-host-contracts/__tests__/client.test.ts +1 -5
  9. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -5
  10. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +10 -16
  11. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +1 -9
  12. package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +12 -12
  13. package/node_modules/@vellumai/slack-text/bun.lock +24 -0
  14. package/node_modules/@vellumai/slack-text/package.json +18 -0
  15. package/node_modules/@vellumai/slack-text/src/index.test.ts +153 -0
  16. package/node_modules/@vellumai/slack-text/src/index.ts +235 -0
  17. package/node_modules/@vellumai/slack-text/tsconfig.json +20 -0
  18. package/openapi.yaml +294 -107
  19. package/package.json +4 -2
  20. package/scripts/generate-openapi.ts +16 -111
  21. package/src/__tests__/agent-wake-override-profile.test.ts +23 -1
  22. package/src/__tests__/anthropic-provider.test.ts +56 -13
  23. package/src/__tests__/app-conversation-ids-backfill.test.ts +278 -0
  24. package/src/__tests__/app-conversation-ids.test.ts +151 -0
  25. package/src/__tests__/approval-cascade.test.ts +0 -15
  26. package/src/__tests__/approval-routes-http.test.ts +6 -17
  27. package/src/__tests__/assistant-event-hub.test.ts +126 -77
  28. package/src/__tests__/assistant-event.test.ts +0 -5
  29. package/src/__tests__/assistant-events-sse-hardening.test.ts +37 -15
  30. package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -29
  31. package/src/__tests__/background-shell-host-bash.test.ts +34 -43
  32. package/src/__tests__/call-controller.test.ts +1 -1
  33. package/src/__tests__/call-site-routing-provider.test.ts +193 -0
  34. package/src/__tests__/channel-approval-routes.test.ts +10 -296
  35. package/src/__tests__/channel-approvals.test.ts +25 -17
  36. package/src/__tests__/channel-guardian.test.ts +100 -146
  37. package/src/__tests__/checker.test.ts +20 -34
  38. package/src/__tests__/compact-event-conversation-id-guard.test.ts +50 -0
  39. package/src/__tests__/compaction-events.test.ts +2 -0
  40. package/src/__tests__/config-schema.test.ts +6 -48
  41. package/src/__tests__/config-watcher.test.ts +12 -0
  42. package/src/__tests__/connection-policy.test.ts +1 -52
  43. package/src/__tests__/contacts-write.test.ts +2 -64
  44. package/src/__tests__/context-image-dimensions.test.ts +1 -1
  45. package/src/__tests__/context-search-memory-source.test.ts +120 -1
  46. package/src/__tests__/context-search-memory-v2-source.test.ts +383 -0
  47. package/src/__tests__/context-search-pkb-source.test.ts +49 -0
  48. package/src/__tests__/context-search-workspace-source.test.ts +9 -22
  49. package/src/__tests__/context-window-manager.test.ts +46 -0
  50. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
  51. package/src/__tests__/conversation-agent-loop-overflow.test.ts +102 -29
  52. package/src/__tests__/conversation-agent-loop.test.ts +980 -13
  53. package/src/__tests__/conversation-analysis-routes.test.ts +12 -10
  54. package/src/__tests__/conversation-attention-telegram.test.ts +11 -3
  55. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -291
  56. package/src/__tests__/conversation-history-web-search.test.ts +4 -3
  57. package/src/__tests__/conversation-inference-profile-route.test.ts +12 -23
  58. package/src/__tests__/conversation-lifecycle.test.ts +4 -4
  59. package/src/__tests__/conversation-process-callsite.test.ts +79 -2
  60. package/src/__tests__/conversation-queue.test.ts +3 -8
  61. package/src/__tests__/conversation-routes-disk-view.test.ts +1 -161
  62. package/src/__tests__/conversation-routes-guardian-reply.test.ts +0 -32
  63. package/src/__tests__/conversation-routes-slash-commands.test.ts +75 -66
  64. package/src/__tests__/conversation-runtime-assembly.test.ts +257 -3
  65. package/src/__tests__/conversation-slash-commands.test.ts +24 -4
  66. package/src/__tests__/conversation-slash-queue.test.ts +2 -0
  67. package/src/__tests__/conversation-speed-override.test.ts +0 -3
  68. package/src/__tests__/conversation-starter-routes.test.ts +79 -2
  69. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +12 -5
  70. package/src/__tests__/conversation-surfaces-standalone.test.ts +18 -14
  71. package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -2
  72. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +8 -46
  73. package/src/__tests__/conversation-usage.test.ts +253 -3
  74. package/src/__tests__/credential-execution-shell-lockdown.test.ts +0 -39
  75. package/src/__tests__/credential-health-service.test.ts +68 -0
  76. package/src/__tests__/credential-security-e2e.test.ts +4 -3
  77. package/src/__tests__/credential-security-invariants.test.ts +1 -5
  78. package/src/__tests__/credential-token-resolver.test.ts +180 -0
  79. package/src/__tests__/cu-unified-flow.test.ts +33 -16
  80. package/src/__tests__/daemon-assistant-events.test.ts +34 -21
  81. package/src/__tests__/daemon-credential-client.test.ts +4 -1
  82. package/src/__tests__/db-connection-isolation.test.ts +125 -0
  83. package/src/__tests__/db-migration-rollback.test.ts +101 -0
  84. package/src/__tests__/db-slack-compaction-watermark-migration.test.ts +169 -0
  85. package/src/__tests__/deterministic-verification-control-plane.test.ts +7 -80
  86. package/src/__tests__/document-conversations.test.ts +332 -0
  87. package/src/__tests__/embedding-managed-proxy-selection.test.ts +2 -2
  88. package/src/__tests__/emit-event-signal.test.ts +4 -6
  89. package/src/__tests__/events-client-registration.test.ts +193 -49
  90. package/src/__tests__/filing-service.test.ts +58 -7
  91. package/src/__tests__/first-greeting.test.ts +156 -150
  92. package/src/__tests__/fixtures/mock-chrome-extension.ts +108 -66
  93. package/src/__tests__/get-skill-detail-audit.test.ts +3 -8
  94. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  95. package/src/__tests__/guardian-dispatch.test.ts +1 -1
  96. package/src/__tests__/guardian-grant-minting.test.ts +7 -2
  97. package/src/__tests__/guardian-routing-invariants.test.ts +7 -2
  98. package/src/__tests__/guardian-routing-state.test.ts +1 -1
  99. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +32 -11
  100. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +2 -83
  101. package/src/__tests__/headless-browser-mode.test.ts +4 -9
  102. package/src/__tests__/headless-browser-navigate.test.ts +21 -20
  103. package/src/__tests__/heartbeat-service.test.ts +289 -7
  104. package/src/__tests__/helpers/channel-test-adapter.ts +2 -2
  105. package/src/__tests__/helpers/create-guardian-binding.ts +91 -0
  106. package/src/__tests__/host-bash-proxy.test.ts +46 -122
  107. package/src/__tests__/host-browser-e2e-cloud.test.ts +36 -497
  108. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +26 -96
  109. package/src/__tests__/host-browser-proxy.test.ts +111 -185
  110. package/src/__tests__/host-browser-routes.test.ts +45 -75
  111. package/src/__tests__/host-browser-ws-events-e2e.test.ts +26 -30
  112. package/src/__tests__/host-cu-proxy.test.ts +56 -111
  113. package/src/__tests__/host-file-proxy.test.ts +44 -98
  114. package/src/__tests__/host-file-read-tool.test.ts +42 -21
  115. package/src/__tests__/host-shell-tool.test.ts +33 -68
  116. package/src/__tests__/host-transfer-pending-interactions.test.ts +2 -18
  117. package/src/__tests__/host-transfer-proxy.test.ts +43 -53
  118. package/src/__tests__/http-user-message-parity.test.ts +0 -6
  119. package/src/__tests__/inbound-slack-persistence.test.ts +31 -0
  120. package/src/__tests__/injector-chain.test.ts +10 -5
  121. package/src/__tests__/injector-pkb-v2-silenced.test.ts +124 -0
  122. package/src/__tests__/inline-command-runner.test.ts +0 -66
  123. package/src/__tests__/inline-skill-load-permissions.test.ts +0 -2
  124. package/src/__tests__/install-skill-routing.test.ts +1 -13
  125. package/src/__tests__/llm-callsite-catalog.test.ts +34 -0
  126. package/src/__tests__/llm-catalog-parity.test.ts +90 -0
  127. package/src/__tests__/llm-context-resolution.test.ts +180 -0
  128. package/src/__tests__/llm-resolver.test.ts +80 -12
  129. package/src/__tests__/llm-usage-store.test.ts +269 -4
  130. package/src/__tests__/log-export-routes.test.ts +89 -0
  131. package/src/__tests__/managed-profile-guard.test.ts +225 -0
  132. package/src/__tests__/managed-skill-lifecycle.test.ts +0 -10
  133. package/src/__tests__/manual-token-reconciliation.test.ts +334 -0
  134. package/src/__tests__/memory-v2-static-injector.test.ts +95 -0
  135. package/src/__tests__/migration-cross-version-compatibility.test.ts +197 -291
  136. package/src/__tests__/migration-export-http.test.ts +33 -26
  137. package/src/__tests__/migration-export-streaming.test.ts +18 -10
  138. package/src/__tests__/migration-export-to-gcs.test.ts +49 -9
  139. package/src/__tests__/migration-import-commit-http.test.ts +66 -21
  140. package/src/__tests__/migration-import-from-gcs.test.ts +50 -9
  141. package/src/__tests__/migration-import-from-url.test.ts +20 -6
  142. package/src/__tests__/migration-import-preflight-http.test.ts +95 -95
  143. package/src/__tests__/migration-parity-persistence.test.ts +62 -25
  144. package/src/__tests__/migration-transport.test.ts +115 -23
  145. package/src/__tests__/migration-validate-http.test.ts +105 -80
  146. package/src/__tests__/migration-wizard.test.ts +133 -27
  147. package/src/__tests__/non-member-access-request.test.ts +1 -1
  148. package/src/__tests__/notification-guardian-path.test.ts +1 -1
  149. package/src/__tests__/oauth-store.test.ts +19 -0
  150. package/src/__tests__/platform-bash-auto-approve.test.ts +21 -12
  151. package/src/__tests__/prechat-onboarding-contract.test.ts +31 -7
  152. package/src/__tests__/pricing.test.ts +68 -4
  153. package/src/__tests__/process-message-background-slack.test.ts +331 -0
  154. package/src/__tests__/provider-managed-proxy-integration.test.ts +153 -17
  155. package/src/__tests__/provider-send-message-override-profile.test.ts +50 -0
  156. package/src/__tests__/provider-usage-tracking.test.ts +208 -0
  157. package/src/__tests__/reaction-persistence.test.ts +9 -6
  158. package/src/__tests__/rebind-secrets-screen.test.ts +53 -16
  159. package/src/__tests__/recording-handler.test.ts +64 -81
  160. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +4 -3
  161. package/src/__tests__/relay-server.test.ts +18 -13
  162. package/src/__tests__/require-fresh-approval.test.ts +13 -22
  163. package/src/__tests__/runtime-attachment-metadata.test.ts +1 -1
  164. package/src/__tests__/runtime-events-sse-parity.test.ts +3 -4
  165. package/src/__tests__/runtime-events-sse.test.ts +3 -12
  166. package/src/__tests__/search-skills-unified.test.ts +9 -15
  167. package/src/__tests__/secret-ingress-cli.test.ts +2 -5
  168. package/src/__tests__/secret-ingress-http.test.ts +0 -4
  169. package/src/__tests__/secret-onetime-send.test.ts +4 -2
  170. package/src/__tests__/secret-prompt-log-hygiene.test.ts +24 -7
  171. package/src/__tests__/secret-prompter-channel-fallback.test.ts +42 -47
  172. package/src/__tests__/secret-response-routing.test.ts +29 -15
  173. package/src/__tests__/secret-routes-managed-proxy.test.ts +5 -1
  174. package/src/__tests__/secret-scanner.test.ts +2 -545
  175. package/src/__tests__/send-endpoint-busy.test.ts +9 -24
  176. package/src/__tests__/settings-routes.test.ts +1 -1
  177. package/src/__tests__/shell-credential-ref.test.ts +0 -8
  178. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -56
  179. package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -11
  180. package/src/__tests__/skill-tool-factory.test.ts +97 -0
  181. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -30
  182. package/src/__tests__/skills-files-catalog-fallback.test.ts +11 -17
  183. package/src/__tests__/slack-inbound-verification.test.ts +1 -62
  184. package/src/__tests__/subagent-fork-notifications.test.ts +57 -47
  185. package/src/__tests__/subagent-manager-notify.test.ts +70 -70
  186. package/src/__tests__/subagent-notify-parent.test.ts +80 -83
  187. package/src/__tests__/system-prompt.test.ts +115 -13
  188. package/src/__tests__/terminal-tools.test.ts +0 -89
  189. package/src/__tests__/thread-backfill.test.ts +945 -31
  190. package/src/__tests__/tool-domain-event-publisher.test.ts +0 -36
  191. package/src/__tests__/tool-execute-pipeline.test.ts +0 -6
  192. package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -16
  193. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +9 -19
  194. package/src/__tests__/tool-executor-lifecycle-events.test.ts +4 -7
  195. package/src/__tests__/tool-executor.test.ts +12 -19
  196. package/src/__tests__/tool-metrics-listener.test.ts +0 -35
  197. package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
  198. package/src/__tests__/tool-trace-listener.test.ts +0 -17
  199. package/src/__tests__/transfer-progress-screen.test.ts +63 -26
  200. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -149
  201. package/src/__tests__/trusted-contact-multichannel.test.ts +2 -4
  202. package/src/__tests__/trusted-contact-verification.test.ts +1 -1
  203. package/src/__tests__/tts-catalog-parity.test.ts +16 -5
  204. package/src/__tests__/usage-attribution.test.ts +247 -0
  205. package/src/__tests__/usage-cli.test.ts +143 -0
  206. package/src/__tests__/usage-grouped-buckets.test.ts +155 -0
  207. package/src/__tests__/usage-routes.test.ts +150 -0
  208. package/src/__tests__/validation-results-screen.test.ts +39 -16
  209. package/src/__tests__/vbundle-pax-and-symlink.test.ts +12 -3
  210. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +49 -137
  211. package/src/__tests__/verification-control-plane-policy.test.ts +4 -7
  212. package/src/__tests__/voice-session-bridge.test.ts +5 -5
  213. package/src/__tests__/workspace-migration-062-drop-memory-v2-edges-json.test.ts +103 -0
  214. package/src/__tests__/workspace-migration-063-release-notes-dynamic-model-context.test.ts +77 -0
  215. package/src/__tests__/workspace-migration-064-unwind-main-agent-opus-seed.test.ts +225 -0
  216. package/src/__tests__/workspace-migration-memory-v2-init.test.ts +8 -30
  217. package/src/acp/index.ts +0 -15
  218. package/src/acp/session-manager.ts +37 -34
  219. package/src/agent/loop.ts +16 -1
  220. package/src/approvals/AGENTS.md +4 -0
  221. package/src/approvals/__tests__/guardian-feed-event.test.ts +10 -3
  222. package/src/approvals/guardian-request-resolvers.ts +10 -2
  223. package/src/backup/__tests__/backup-worker.test.ts +36 -8
  224. package/src/backup/__tests__/paths.test.ts +2 -2
  225. package/src/backup/__tests__/restore.test.ts +45 -28
  226. package/src/backup/backup-worker.ts +36 -2
  227. package/src/backup/paths.ts +9 -6
  228. package/src/browser-session/events.ts +0 -9
  229. package/src/calls/call-store.ts +1 -34
  230. package/src/calls/guardian-question-copy.ts +0 -108
  231. package/src/calls/relay-server.ts +0 -24
  232. package/src/calls/twilio-rest.ts +0 -38
  233. package/src/calls/twilio-routes.ts +1 -1
  234. package/src/calls/voice-session-bridge.ts +7 -38
  235. package/src/channels/types.ts +1 -36
  236. package/src/cli/commands/__tests__/cache.test.ts +152 -5
  237. package/src/cli/commands/__tests__/memory-v2.test.ts +14 -28
  238. package/src/cli/commands/__tests__/trust.test.ts +21 -387
  239. package/src/cli/commands/backup.ts +4 -4
  240. package/src/cli/commands/cache-fs.ts +8 -0
  241. package/src/cli/commands/cache.ts +153 -82
  242. package/src/cli/commands/clients.ts +63 -5
  243. package/src/cli/commands/completions.ts +3 -3
  244. package/src/cli/commands/contacts.ts +231 -76
  245. package/src/cli/commands/keys.ts +4 -1
  246. package/src/cli/commands/memory-v2.ts +24 -52
  247. package/src/cli/commands/oauth/shared.ts +2 -29
  248. package/src/cli/commands/pending.ts +102 -0
  249. package/src/cli/commands/skills.ts +77 -35
  250. package/src/cli/commands/trust.ts +70 -430
  251. package/src/cli/commands/usage.ts +25 -16
  252. package/src/cli/lib/daemon-credential-client.ts +14 -0
  253. package/src/cli/program.ts +2 -0
  254. package/src/cli.ts +0 -21
  255. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  256. package/src/config/bundled-skills/messaging/TOOLS.json +14 -4
  257. package/src/config/env-registry.ts +12 -2
  258. package/src/config/env.ts +3 -14
  259. package/src/config/feature-flag-registry.json +30 -30
  260. package/src/config/llm-callsite-catalog.ts +12 -0
  261. package/src/config/llm-context-resolution.ts +80 -0
  262. package/src/config/llm-resolver.ts +58 -22
  263. package/src/config/loader.ts +3 -3
  264. package/src/config/schema.ts +2 -158
  265. package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
  266. package/src/config/schemas/call-site-catalog.ts +271 -0
  267. package/src/config/schemas/calls.ts +5 -5
  268. package/src/config/schemas/inference.ts +1 -1
  269. package/src/config/schemas/ingress.ts +1 -1
  270. package/src/config/schemas/llm.ts +31 -3
  271. package/src/config/schemas/memory-retrieval.ts +2 -2
  272. package/src/config/schemas/memory-v2.ts +9 -0
  273. package/src/config/schemas/security.ts +1 -42
  274. package/src/config/schemas/services.ts +6 -6
  275. package/src/config/schemas/skills.ts +5 -5
  276. package/src/config/schemas/tts.ts +1 -1
  277. package/src/config/seed-inference-profiles.ts +117 -0
  278. package/src/config/skills.ts +0 -90
  279. package/src/config/types.ts +3 -6
  280. package/src/contacts/contact-store.ts +0 -17
  281. package/src/contacts/contacts-write.ts +1 -105
  282. package/src/context/window-manager.ts +44 -5
  283. package/src/credential-execution/process-manager.ts +34 -10
  284. package/src/credential-health/credential-health-service.ts +21 -16
  285. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +75 -82
  286. package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -9
  287. package/src/daemon/connection-policy.ts +1 -26
  288. package/src/daemon/conversation-agent-loop-handlers.ts +53 -4
  289. package/src/daemon/conversation-agent-loop.ts +277 -36
  290. package/src/daemon/conversation-history.ts +8 -8
  291. package/src/daemon/conversation-launch.ts +20 -135
  292. package/src/daemon/conversation-lifecycle.ts +1 -1
  293. package/src/daemon/conversation-messaging.ts +1 -0
  294. package/src/daemon/conversation-process.ts +83 -163
  295. package/src/daemon/conversation-runtime-assembly.ts +219 -76
  296. package/src/daemon/conversation-slash.ts +47 -5
  297. package/src/daemon/conversation-store.ts +7 -31
  298. package/src/daemon/conversation-surfaces.ts +22 -28
  299. package/src/daemon/conversation-tool-setup.ts +3 -33
  300. package/src/daemon/conversation-usage.ts +36 -0
  301. package/src/daemon/conversation.ts +117 -233
  302. package/src/daemon/daemon-control.ts +3 -71
  303. package/src/daemon/daemon-skill-host.ts +8 -11
  304. package/src/daemon/dictation-profile-store.ts +2 -26
  305. package/src/daemon/first-greeting.ts +44 -156
  306. package/src/daemon/handlers/config-channels.ts +12 -12
  307. package/src/daemon/handlers/config-ingress.ts +4 -165
  308. package/src/daemon/handlers/config-model.ts +1 -1
  309. package/src/daemon/handlers/config-voice.ts +0 -42
  310. package/src/daemon/handlers/conversations.ts +11 -190
  311. package/src/daemon/handlers/recording.ts +26 -158
  312. package/src/daemon/handlers/shared.ts +23 -71
  313. package/src/daemon/handlers/skills.ts +42 -93
  314. package/src/daemon/host-bash-proxy.ts +67 -45
  315. package/src/daemon/host-browser-proxy.ts +65 -27
  316. package/src/daemon/host-cu-proxy.ts +40 -39
  317. package/src/daemon/host-file-proxy.ts +58 -37
  318. package/src/daemon/host-transfer-proxy.ts +84 -46
  319. package/src/daemon/lifecycle.ts +49 -15
  320. package/src/daemon/message-types/conversations.ts +7 -0
  321. package/src/daemon/message-types/host-bash.ts +1 -0
  322. package/src/daemon/message-types/host-cu.ts +1 -0
  323. package/src/daemon/message-types/host-file.ts +1 -0
  324. package/src/daemon/message-types/host-transfer.ts +1 -0
  325. package/src/daemon/message-types/messages.ts +10 -9
  326. package/src/daemon/message-types/workspace.ts +1 -1
  327. package/src/daemon/process-message.ts +102 -239
  328. package/src/daemon/server.ts +13 -462
  329. package/src/daemon/shutdown-handlers.ts +2 -2
  330. package/src/daemon/tool-side-effects.ts +125 -107
  331. package/src/daemon/trust-context.ts +13 -0
  332. package/src/daemon/wake-target-adapter.ts +4 -9
  333. package/src/events/domain-events.ts +0 -8
  334. package/src/events/tool-audit-listener.ts +3 -1
  335. package/src/events/tool-domain-event-publisher.ts +0 -10
  336. package/src/events/tool-metrics-listener.ts +0 -17
  337. package/src/events/tool-trace-listener.ts +0 -14
  338. package/src/filing/filing-service.ts +13 -1
  339. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +6 -2
  340. package/src/heartbeat/heartbeat-service.ts +23 -5
  341. package/src/home/__tests__/feed-writer.test.ts +0 -4
  342. package/src/home/__tests__/relationship-state-writer.test.ts +30 -0
  343. package/src/home/feed-writer.ts +1 -2
  344. package/src/home/relationship-state-writer.ts +16 -3
  345. package/src/ipc/__tests__/browser-ipc.test.ts +2 -12
  346. package/src/ipc/__tests__/skill-server-bidirectional.test.ts +0 -1
  347. package/src/ipc/assistant-server.ts +3 -10
  348. package/src/ipc/routes/__tests__/memory-v2-backfill.test.ts +39 -20
  349. package/src/ipc/routes/route-adapter.ts +1 -1
  350. package/src/ipc/routes/trust-rules.test.ts +0 -95
  351. package/src/ipc/skill-ipc-types.ts +41 -0
  352. package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +13 -27
  353. package/src/ipc/skill-routes/__tests__/identity.test.ts +4 -23
  354. package/src/ipc/skill-routes/events.ts +12 -23
  355. package/src/ipc/skill-routes/identity.ts +4 -17
  356. package/src/ipc/skill-routes/index.ts +1 -1
  357. package/src/ipc/skill-server.ts +6 -39
  358. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +0 -8
  359. package/src/live-voice/protocol.ts +4 -13
  360. package/src/mcp/manager.ts +0 -5
  361. package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +55 -0
  362. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +127 -0
  363. package/src/memory/app-git-service.ts +0 -32
  364. package/src/memory/app-store.ts +154 -0
  365. package/src/memory/attachments-store.ts +6 -0
  366. package/src/memory/context-search/sources/memory-v2.ts +578 -0
  367. package/src/memory/context-search/sources/memory.ts +5 -0
  368. package/src/memory/context-search/sources/pkb.ts +10 -1
  369. package/src/memory/context-search/sources/workspace.ts +3 -2
  370. package/src/memory/conversation-crud.ts +29 -4
  371. package/src/memory/conversation-disk-view.ts +1 -5
  372. package/src/memory/conversation-starter-checkpoints.ts +63 -0
  373. package/src/memory/db-connection.ts +62 -0
  374. package/src/memory/db-init.ts +14 -0
  375. package/src/memory/embedding-backend.ts +3 -21
  376. package/src/memory/embedding-gemini.ts +0 -2
  377. package/src/memory/embedding-local.ts +6 -6
  378. package/src/memory/embedding-ollama.ts +6 -6
  379. package/src/memory/embedding-openai.ts +6 -6
  380. package/src/memory/embedding-types.ts +21 -0
  381. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +3 -7
  382. package/src/memory/graph/conversation-graph-memory.ts +35 -13
  383. package/src/memory/graph/injection.test.ts +2 -2
  384. package/src/memory/graph/injection.ts +1 -1
  385. package/src/memory/guardian-action-store.ts +0 -83
  386. package/src/memory/guardian-approvals.ts +0 -48
  387. package/src/memory/indexer.ts +1 -15
  388. package/src/memory/job-handlers/conversation-starters.ts +36 -53
  389. package/src/memory/job-utils.ts +0 -6
  390. package/src/memory/jobs-store.ts +0 -1
  391. package/src/memory/jobs-worker.ts +2 -16
  392. package/src/memory/llm-request-log-store.ts +0 -41
  393. package/src/memory/llm-usage-store.ts +129 -43
  394. package/src/memory/memory-v2-activation-log-store.ts +115 -0
  395. package/src/memory/migrations/233-document-conversations.ts +54 -0
  396. package/src/memory/migrations/234-memory-v2-activation-logs.ts +55 -0
  397. package/src/memory/migrations/235-llm-usage-attribution.ts +31 -0
  398. package/src/memory/migrations/235-slack-compaction-watermark.ts +44 -0
  399. package/src/memory/migrations/236-tool-invocations-matched-rule-id.ts +26 -0
  400. package/src/memory/migrations/__tests__/234-memory-v2-activation-logs.test.ts +182 -0
  401. package/src/memory/migrations/index.ts +14 -0
  402. package/src/memory/migrations/registry.ts +24 -0
  403. package/src/memory/raw-query.ts +2 -68
  404. package/src/memory/schema/conversations.ts +7 -0
  405. package/src/memory/schema/infrastructure.ts +25 -0
  406. package/src/memory/search/semantic.ts +5 -16
  407. package/src/memory/tool-usage-store.ts +2 -0
  408. package/src/memory/usage-buckets.ts +40 -1
  409. package/src/memory/usage-grouped-buckets.ts +127 -0
  410. package/src/memory/v2/__tests__/activation.test.ts +289 -90
  411. package/src/memory/v2/__tests__/backfill-jobs.test.ts +2 -129
  412. package/src/memory/v2/__tests__/consolidation-job.test.ts +28 -11
  413. package/src/memory/v2/__tests__/edge-index.test.ts +278 -0
  414. package/src/memory/v2/__tests__/injection.test.ts +384 -15
  415. package/src/memory/v2/__tests__/migration.test.ts +64 -36
  416. package/src/memory/v2/__tests__/page-store.test.ts +191 -8
  417. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +181 -0
  418. package/src/memory/v2/__tests__/skill-store.test.ts +115 -3
  419. package/src/memory/v2/__tests__/static-context.test.ts +153 -0
  420. package/src/memory/v2/activation.ts +168 -97
  421. package/src/memory/v2/backfill-jobs.ts +15 -100
  422. package/src/memory/v2/consolidation-job.ts +14 -12
  423. package/src/memory/v2/edge-index.ts +191 -0
  424. package/src/memory/v2/injection.ts +182 -58
  425. package/src/memory/v2/migration.ts +57 -64
  426. package/src/memory/v2/now-text.ts +2 -3
  427. package/src/memory/v2/page-store.ts +168 -31
  428. package/src/memory/v2/prompts/consolidation.ts +118 -42
  429. package/src/memory/v2/prompts/sweep.ts +3 -3
  430. package/src/memory/v2/skill-store.ts +55 -7
  431. package/src/memory/v2/static-context.ts +62 -0
  432. package/src/memory/v2/types.ts +10 -20
  433. package/src/memory/validation.ts +0 -11
  434. package/src/messaging/draft-store.ts +0 -6
  435. package/src/messaging/provider-types.ts +8 -0
  436. package/src/messaging/provider.ts +7 -0
  437. package/src/messaging/providers/gmail/client.ts +1 -121
  438. package/src/messaging/providers/outlook/client.ts +0 -73
  439. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +226 -0
  440. package/src/messaging/providers/slack/adapter.ts +122 -21
  441. package/src/messaging/providers/slack/backfill.test.ts +95 -6
  442. package/src/messaging/providers/slack/backfill.ts +89 -11
  443. package/src/messaging/providers/slack/client.ts +10 -124
  444. package/src/messaging/providers/slack/message-metadata.ts +12 -2
  445. package/src/messaging/providers/slack/render-transcript.test.ts +56 -0
  446. package/src/messaging/providers/slack/render-transcript.ts +126 -25
  447. package/src/messaging/providers/slack/types.ts +1 -0
  448. package/src/oauth/connection-resolver.test.ts +8 -0
  449. package/src/oauth/connection-resolver.ts +8 -16
  450. package/src/oauth/credential-token-resolver.ts +97 -0
  451. package/src/oauth/manual-token-connection.ts +30 -34
  452. package/src/oauth/oauth-store.ts +6 -4
  453. package/src/outbound-proxy/certs.ts +0 -7
  454. package/src/outbound-proxy/config.ts +0 -74
  455. package/src/outbound-proxy/health.ts +0 -44
  456. package/src/outbound-proxy/index.ts +0 -22
  457. package/src/permissions/approval-provenance.test.ts +184 -0
  458. package/src/permissions/approval-provenance.ts +70 -0
  459. package/src/permissions/checker.ts +4 -1
  460. package/src/permissions/gateway-threshold-reader.ts +4 -1
  461. package/src/permissions/prompter.ts +9 -2
  462. package/src/permissions/secret-prompter.ts +21 -48
  463. package/src/permissions/types.ts +33 -0
  464. package/src/permissions/workspace-policy.ts +0 -5
  465. package/src/platform/sync-identity.ts +0 -8
  466. package/src/plugins/defaults/injectors.ts +69 -2
  467. package/src/plugins/defaults/overflow-reduce.ts +3 -2
  468. package/src/plugins/types.ts +8 -0
  469. package/src/prompts/system-prompt.ts +34 -70
  470. package/src/prompts/templates/BOOTSTRAP.md +52 -6
  471. package/src/prompts/update-bulletin-job.ts +2 -0
  472. package/src/providers/__tests__/retry-callsite.test.ts +138 -1
  473. package/src/providers/anthropic/client.ts +72 -33
  474. package/src/providers/call-site-routing.ts +42 -3
  475. package/src/providers/gemini/client.ts +18 -2
  476. package/src/providers/managed-proxy/context.ts +0 -5
  477. package/src/providers/model-catalog.ts +105 -19
  478. package/src/providers/openai/chat-completions-provider.ts +6 -0
  479. package/src/providers/openai/responses-provider.ts +7 -1
  480. package/src/providers/provider-send-message.ts +45 -2
  481. package/src/providers/ratelimit.ts +7 -2
  482. package/src/providers/registry.ts +14 -9
  483. package/src/providers/retry.ts +96 -8
  484. package/src/providers/types.ts +13 -0
  485. package/src/providers/usage-tracking.ts +96 -0
  486. package/src/runtime/AGENTS.md +10 -6
  487. package/src/runtime/__tests__/agent-wake.test.ts +89 -0
  488. package/src/runtime/agent-wake.ts +39 -2
  489. package/src/runtime/assistant-event-hub.ts +541 -45
  490. package/src/runtime/assistant-event.ts +1 -6
  491. package/src/runtime/auth/context.ts +0 -9
  492. package/src/runtime/auth/middleware.ts +1 -1
  493. package/src/runtime/auth/route-policy.ts +11 -9
  494. package/src/runtime/auth/token-service.ts +0 -11
  495. package/src/runtime/channel-approvals.ts +6 -2
  496. package/src/runtime/channel-verification-service.ts +3 -5
  497. package/src/runtime/http-errors.ts +0 -34
  498. package/src/runtime/http-router.ts +6 -3
  499. package/src/runtime/http-server.ts +22 -82
  500. package/src/runtime/http-types.ts +5 -0
  501. package/src/runtime/interactive-ui.ts +0 -1
  502. package/src/runtime/middleware/auth.ts +0 -20
  503. package/src/runtime/migrations/__tests__/v1-test-helpers.ts +112 -0
  504. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +11 -4
  505. package/src/runtime/migrations/__tests__/vbundle-builder-v1-shape.test.ts +253 -0
  506. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +19 -6
  507. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +71 -27
  508. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +41 -2
  509. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +143 -79
  510. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +143 -23
  511. package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +2 -2
  512. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +371 -0
  513. package/src/runtime/migrations/migration-transport.ts +46 -13
  514. package/src/runtime/migrations/migration-wizard.ts +2 -2
  515. package/src/runtime/migrations/origin-mode.ts +40 -0
  516. package/src/runtime/migrations/vbundle-builder.ts +133 -79
  517. package/src/runtime/migrations/vbundle-import-analyzer.ts +9 -7
  518. package/src/runtime/migrations/vbundle-importer.ts +7 -7
  519. package/src/runtime/migrations/vbundle-metadata-merge.ts +1 -1
  520. package/src/runtime/migrations/vbundle-streaming-importer.ts +3 -3
  521. package/src/runtime/migrations/vbundle-streaming-validator.ts +48 -26
  522. package/src/runtime/migrations/vbundle-validator.ts +214 -41
  523. package/src/runtime/pending-interactions.ts +13 -4
  524. package/src/runtime/routes/__tests__/acp-routes.test.ts +0 -1
  525. package/src/runtime/routes/__tests__/backup-routes.test.ts +28 -19
  526. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +235 -0
  527. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +58 -0
  528. package/src/runtime/routes/__tests__/migration-export-secrets-redacted.test.ts +54 -0
  529. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +19 -6
  530. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +7 -7
  531. package/src/runtime/routes/acp-routes.test.ts +0 -3
  532. package/src/runtime/routes/acp-routes.ts +3 -7
  533. package/src/runtime/routes/app-management-routes.ts +18 -9
  534. package/src/runtime/routes/approval-routes.ts +55 -14
  535. package/src/runtime/routes/avatar-routes.ts +3 -5
  536. package/src/runtime/routes/browser-routes.ts +1 -15
  537. package/src/runtime/routes/channel-guardian-routes.ts +1 -5
  538. package/src/runtime/routes/channel-readiness-routes.ts +3 -7
  539. package/src/runtime/routes/channel-route-shared.ts +2 -28
  540. package/src/runtime/routes/client-routes.ts +45 -12
  541. package/src/runtime/routes/consolidation-routes.ts +115 -0
  542. package/src/runtime/routes/conversation-list-routes.ts +12 -29
  543. package/src/runtime/routes/conversation-management-routes.ts +14 -51
  544. package/src/runtime/routes/conversation-query-routes.ts +120 -8
  545. package/src/runtime/routes/conversation-routes.ts +44 -528
  546. package/src/runtime/routes/conversation-starter-routes.ts +19 -40
  547. package/src/runtime/routes/documents-routes.ts +53 -18
  548. package/src/runtime/routes/events-routes.ts +59 -91
  549. package/src/runtime/routes/filing-routes.ts +18 -1
  550. package/src/runtime/routes/guardian-action-routes.ts +4 -9
  551. package/src/runtime/routes/host-bash-routes.ts +3 -2
  552. package/src/runtime/routes/host-browser-routes.ts +9 -33
  553. package/src/runtime/routes/host-cu-routes.ts +6 -1
  554. package/src/runtime/routes/host-file-routes.ts +3 -2
  555. package/src/runtime/routes/host-transfer-routes.ts +11 -15
  556. package/src/runtime/routes/identity-routes.ts +78 -6
  557. package/src/runtime/routes/inbound-message-handler.ts +580 -137
  558. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -88
  559. package/src/runtime/routes/inbound-stages/background-dispatch.ts +3 -0
  560. package/src/runtime/routes/index.ts +4 -0
  561. package/src/runtime/routes/integrations/slack/channel.ts +0 -24
  562. package/src/runtime/routes/llm-call-sites-routes.ts +22 -0
  563. package/src/runtime/routes/memory-v2-routes.ts +10 -15
  564. package/src/runtime/routes/migration-routes.ts +188 -31
  565. package/src/runtime/routes/playground/guard.ts +1 -1
  566. package/src/runtime/routes/playground/index.ts +0 -2
  567. package/src/runtime/routes/recording-routes.ts +4 -24
  568. package/src/runtime/routes/rename-conversation-routes.ts +2 -6
  569. package/src/runtime/routes/schedule-routes.ts +3 -6
  570. package/src/runtime/routes/secret-routes.ts +87 -18
  571. package/src/runtime/routes/settings-routes.ts +29 -28
  572. package/src/runtime/routes/skills-routes.ts +12 -31
  573. package/src/runtime/routes/suggest-trust-rule-routes.ts +32 -1
  574. package/src/runtime/routes/task-routes.ts +6 -6
  575. package/src/runtime/routes/trust-rules-routes.ts +3 -94
  576. package/src/runtime/routes/types.ts +4 -4
  577. package/src/runtime/routes/upgrade-broadcast-routes.ts +3 -10
  578. package/src/runtime/routes/usage-routes.ts +87 -10
  579. package/src/runtime/routes/user-routes.ts +17 -31
  580. package/src/runtime/routes/work-items-routes.ts +1 -4
  581. package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -2
  582. package/src/runtime/services/analyze-conversation.ts +7 -17
  583. package/src/runtime/services/conversation-serializer.ts +2 -4
  584. package/src/runtime/verification-outbound-actions.ts +1 -1
  585. package/src/runtime/verification-rate-limiter.ts +1 -1
  586. package/src/schedule/schedule-store.ts +0 -16
  587. package/src/security/secret-scanner.ts +14 -547
  588. package/src/security/secure-keys.ts +31 -11
  589. package/src/security/token-manager.ts +7 -3
  590. package/src/signals/cancel.ts +16 -25
  591. package/src/signals/conversation-undo.ts +2 -27
  592. package/src/signals/emit-event.ts +1 -2
  593. package/src/signals/user-message.ts +108 -22
  594. package/src/skills/catalog-install.ts +1 -0
  595. package/src/skills/clawhub.ts +2 -2
  596. package/src/skills/inline-command-runner.ts +1 -7
  597. package/src/subagent/manager.ts +67 -84
  598. package/src/tasks/task-store.ts +1 -28
  599. package/src/telemetry/types.ts +6 -0
  600. package/src/telemetry/usage-telemetry-reporter.test.ts +38 -15
  601. package/src/telemetry/usage-telemetry-reporter.ts +3 -5
  602. package/src/tools/acp/spawn.test.ts +1 -2
  603. package/src/tools/acp/steer.test.ts +1 -2
  604. package/src/tools/browser/__tests__/browser-status.test.ts +44 -127
  605. package/src/tools/browser/browser-execution.ts +31 -147
  606. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +92 -68
  607. package/src/tools/browser/cdp-client/factory.ts +48 -76
  608. package/src/tools/browser/cdp-client/index.ts +1 -14
  609. package/src/tools/executor.ts +44 -31
  610. package/src/tools/host-filesystem/edit.ts +3 -2
  611. package/src/tools/host-filesystem/read.ts +3 -2
  612. package/src/tools/host-filesystem/transfer.test.ts +45 -42
  613. package/src/tools/host-filesystem/transfer.ts +4 -3
  614. package/src/tools/host-filesystem/write.ts +3 -2
  615. package/src/tools/host-terminal/host-shell.ts +4 -3
  616. package/src/tools/network/script-proxy/index.ts +1 -10
  617. package/src/tools/permission-checker.ts +66 -1
  618. package/src/tools/skills/sandbox-runner.ts +1 -6
  619. package/src/tools/skills/skill-tool-factory.ts +32 -0
  620. package/src/tools/terminal/safe-env.ts +1 -0
  621. package/src/tools/terminal/shell.ts +2 -78
  622. package/src/tools/types.ts +12 -39
  623. package/src/tts/__tests__/provider-catalog.test.ts +2 -2
  624. package/src/tts/provider-catalog.ts +1 -1
  625. package/src/usage/actors.ts +2 -1
  626. package/src/usage/attribution.ts +185 -0
  627. package/src/usage/pricing.ts +166 -0
  628. package/src/usage/types.ts +14 -0
  629. package/src/util/json.ts +13 -0
  630. package/src/util/logger.ts +3 -3
  631. package/src/util/pricing.ts +50 -3
  632. package/src/work-items/work-item-runner.ts +15 -42
  633. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +4 -3
  634. package/src/workspace/migrations/052-seed-default-inference-profiles.ts +3 -3
  635. package/src/workspace/migrations/060-memory-v2-init.ts +2 -18
  636. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +59 -0
  637. package/src/workspace/migrations/062-drop-memory-v2-edges-json.ts +27 -0
  638. package/src/workspace/migrations/063-release-notes-dynamic-model-context.ts +70 -0
  639. package/src/workspace/migrations/064-unwind-main-agent-opus-seed.ts +64 -0
  640. package/src/workspace/migrations/registry.ts +8 -0
  641. package/src/workspace/provider-commit-message-generator.ts +3 -3
  642. package/src/__tests__/sandbox-diagnostics.test.ts +0 -138
  643. package/src/__tests__/sandbox-host-parity.test.ts +0 -1024
  644. package/src/__tests__/secret-detection-handler.test.ts +0 -67
  645. package/src/__tests__/secret-scanner-executor.test.ts +0 -450
  646. package/src/__tests__/tcc-sandbox-deny.test.ts +0 -198
  647. package/src/__tests__/terminal-sandbox.test.ts +0 -374
  648. package/src/__tests__/tool-notification-listener.test.ts +0 -65
  649. package/src/context/__tests__/microcompact.test.ts +0 -805
  650. package/src/context/microcompact.ts +0 -443
  651. package/src/daemon/handlers/slack-channel-oauth-install.ts +0 -197
  652. package/src/events/tool-notification-listener.ts +0 -17
  653. package/src/ipc/routes/__tests__/memory-v2-validate.test.ts +0 -219
  654. package/src/memory/v2/__tests__/edges.test.ts +0 -435
  655. package/src/memory/v2/edges.ts +0 -217
  656. package/src/prompts/__tests__/system-prompt-memory-v2.test.ts +0 -197
  657. package/src/runtime/__tests__/chrome-extension-registry.test.ts +0 -518
  658. package/src/runtime/__tests__/client-registry.test.ts +0 -271
  659. package/src/runtime/chrome-extension-registry.ts +0 -368
  660. package/src/runtime/client-registry.ts +0 -254
  661. package/src/runtime/routes/inbound-stages/verification-intercept.ts +0 -329
  662. package/src/tools/secret-detection-handler.ts +0 -269
  663. package/src/tools/terminal/backends/native.ts +0 -327
  664. package/src/tools/terminal/backends/types.ts +0 -37
  665. package/src/tools/terminal/sandbox-diagnostics.ts +0 -87
  666. package/src/tools/terminal/sandbox.ts +0 -40
@@ -55,9 +55,15 @@ mock.module("../messaging/providers/telegram-bot/send.js", () => ({
55
55
  }));
56
56
  import { eq } from "drizzle-orm";
57
57
 
58
- import { createGuardianBinding } from "../contacts/contacts-write.js";
58
+ // Capture broadcastMessage calls so tests can inspect responses
59
+ const broadcastedMessages: unknown[] = [];
60
+ mock.module("../runtime/assistant-event-hub.js", () => ({
61
+ broadcastMessage: (msg: unknown) => {
62
+ broadcastedMessages.push(msg);
63
+ },
64
+ }));
65
+
59
66
  import { handleChannelVerificationSession } from "../daemon/handlers/config-channels.js";
60
- import type { HandlerContext } from "../daemon/handlers/shared.js";
61
67
  import type {
62
68
  ChannelVerificationSessionRequest,
63
69
  ChannelVerificationSessionResponse,
@@ -115,6 +121,7 @@ import {
115
121
  composeVerificationTelegram,
116
122
  GUARDIAN_VERIFY_TEMPLATE_KEYS,
117
123
  } from "../runtime/verification-templates.js";
124
+ import { createGuardianBinding } from "./helpers/create-guardian-binding.js";
118
125
 
119
126
  initializeDb();
120
127
 
@@ -391,7 +398,7 @@ describe("guardian service challenge validation", () => {
391
398
  }
392
399
 
393
400
  // validateAndConsumeVerification no longer creates bindings — that is
394
- // now handled by the caller (verification-intercept / relay-server).
401
+ // now handled by the gateway's verification intercepts.
395
402
  const binding = getGuardianBinding("asst-1", "phone");
396
403
  expect(binding).toBeNull();
397
404
  });
@@ -1259,32 +1266,22 @@ describe("assistant-scoped approval request lookups", () => {
1259
1266
  // ═══════════════════════════════════════════════════════════════════════════
1260
1267
 
1261
1268
  /**
1262
- * Creates a minimal mock HandlerContext that captures the response sent via ctx.send().
1269
+ * Returns a helper that reads the latest broadcastMessage call as a
1270
+ * ChannelVerificationSessionResponse. Resets the capture array first so
1271
+ * each test gets a clean slate.
1263
1272
  */
1264
- function createMockCtx(): {
1265
- ctx: HandlerContext;
1273
+ function createResponseReader(): {
1266
1274
  lastResponse: () => ChannelVerificationSessionResponse | null;
1267
1275
  } {
1268
- let captured: ChannelVerificationSessionResponse | null = null;
1269
- const ctx = {
1270
- sessions: new Map(),
1271
- sharedRequestTimestamps: [],
1272
- debounceTimers: {
1273
- schedule: () => {},
1274
- cancel: () => {},
1275
- } as unknown as HandlerContext["debounceTimers"],
1276
- suppressConfigReload: false,
1277
- setSuppressConfigReload: () => {},
1278
- updateConfigFingerprint: () => {},
1279
- send: (msg: unknown) => {
1280
- captured = msg as ChannelVerificationSessionResponse;
1281
- },
1282
- broadcast: () => {},
1283
- clearAllConversations: () => 0,
1284
- getOrCreateConversation: () => Promise.resolve({} as never),
1285
- touchConversation: () => {},
1286
- } as unknown as HandlerContext;
1287
- return { ctx, lastResponse: () => captured };
1276
+ broadcastedMessages.length = 0;
1277
+ return {
1278
+ lastResponse: () =>
1279
+ broadcastedMessages.length > 0
1280
+ ? (broadcastedMessages[
1281
+ broadcastedMessages.length - 1
1282
+ ] as ChannelVerificationSessionResponse)
1283
+ : null,
1284
+ };
1288
1285
  }
1289
1286
 
1290
1287
  describe("HTTP handler channel-aware guardian status", () => {
@@ -1293,14 +1290,14 @@ describe("HTTP handler channel-aware guardian status", () => {
1293
1290
  });
1294
1291
 
1295
1292
  test("status action for telegram returns channel and assistantId fields", async () => {
1296
- const { ctx, lastResponse } = createMockCtx();
1293
+ const { lastResponse } = createResponseReader();
1297
1294
  const msg: ChannelVerificationSessionRequest = {
1298
1295
  type: "channel_verification_session",
1299
1296
  action: "status",
1300
1297
  channel: "telegram",
1301
1298
  };
1302
1299
 
1303
- await handleChannelVerificationSession(msg, ctx);
1300
+ await handleChannelVerificationSession(msg);
1304
1301
 
1305
1302
  const resp = lastResponse();
1306
1303
  expect(resp).not.toBeNull();
@@ -1312,14 +1309,14 @@ describe("HTTP handler channel-aware guardian status", () => {
1312
1309
  });
1313
1310
 
1314
1311
  test("status action for voice returns channel: voice and assistantId: self", async () => {
1315
- const { ctx, lastResponse } = createMockCtx();
1312
+ const { lastResponse } = createResponseReader();
1316
1313
  const msg: ChannelVerificationSessionRequest = {
1317
1314
  type: "channel_verification_session",
1318
1315
  action: "status",
1319
1316
  channel: "phone",
1320
1317
  };
1321
1318
 
1322
- await handleChannelVerificationSession(msg, ctx);
1319
+ await handleChannelVerificationSession(msg);
1323
1320
 
1324
1321
  const resp = lastResponse();
1325
1322
  expect(resp).not.toBeNull();
@@ -1337,14 +1334,14 @@ describe("HTTP handler channel-aware guardian status", () => {
1337
1334
  guardianDeliveryChatId: "chat-42",
1338
1335
  });
1339
1336
 
1340
- const { ctx, lastResponse } = createMockCtx();
1337
+ const { lastResponse } = createResponseReader();
1341
1338
  const msg: ChannelVerificationSessionRequest = {
1342
1339
  type: "channel_verification_session",
1343
1340
  action: "status",
1344
1341
  channel: "telegram",
1345
1342
  };
1346
1343
 
1347
- await handleChannelVerificationSession(msg, ctx);
1344
+ await handleChannelVerificationSession(msg);
1348
1345
 
1349
1346
  const resp = lastResponse();
1350
1347
  expect(resp).not.toBeNull();
@@ -1389,14 +1386,14 @@ describe("HTTP handler channel-aware guardian status", () => {
1389
1386
  displayName: "Guardian Name",
1390
1387
  });
1391
1388
 
1392
- const { ctx, lastResponse } = createMockCtx();
1389
+ const { lastResponse } = createResponseReader();
1393
1390
  const msg: ChannelVerificationSessionRequest = {
1394
1391
  type: "channel_verification_session",
1395
1392
  action: "status",
1396
1393
  channel: "telegram",
1397
1394
  };
1398
1395
 
1399
- await handleChannelVerificationSession(msg, ctx);
1396
+ await handleChannelVerificationSession(msg);
1400
1397
 
1401
1398
  const resp = lastResponse();
1402
1399
  expect(resp).not.toBeNull();
@@ -1405,14 +1402,14 @@ describe("HTTP handler channel-aware guardian status", () => {
1405
1402
  });
1406
1403
 
1407
1404
  test("status action defaults channel to telegram when omitted", async () => {
1408
- const { ctx, lastResponse } = createMockCtx();
1405
+ const { lastResponse } = createResponseReader();
1409
1406
  const msg: ChannelVerificationSessionRequest = {
1410
1407
  type: "channel_verification_session",
1411
1408
  action: "status",
1412
1409
  // channel omitted — should default to 'telegram'
1413
1410
  };
1414
1411
 
1415
- await handleChannelVerificationSession(msg, ctx);
1412
+ await handleChannelVerificationSession(msg);
1416
1413
 
1417
1414
  const resp = lastResponse();
1418
1415
  expect(resp).not.toBeNull();
@@ -1421,7 +1418,7 @@ describe("HTTP handler channel-aware guardian status", () => {
1421
1418
  });
1422
1419
 
1423
1420
  test("status action defaults assistantId to self when omitted", async () => {
1424
- const { ctx, lastResponse } = createMockCtx();
1421
+ const { lastResponse } = createResponseReader();
1425
1422
  const msg: ChannelVerificationSessionRequest = {
1426
1423
  type: "channel_verification_session",
1427
1424
  action: "status",
@@ -1429,7 +1426,7 @@ describe("HTTP handler channel-aware guardian status", () => {
1429
1426
  // assistantId omitted — should default to 'self'
1430
1427
  };
1431
1428
 
1432
- await handleChannelVerificationSession(msg, ctx);
1429
+ await handleChannelVerificationSession(msg);
1433
1430
 
1434
1431
  const resp = lastResponse();
1435
1432
  expect(resp).not.toBeNull();
@@ -1438,14 +1435,14 @@ describe("HTTP handler channel-aware guardian status", () => {
1438
1435
  });
1439
1436
 
1440
1437
  test("status action for unbound voice does not return guardianDeliveryChatId", async () => {
1441
- const { ctx, lastResponse } = createMockCtx();
1438
+ const { lastResponse } = createResponseReader();
1442
1439
  const msg: ChannelVerificationSessionRequest = {
1443
1440
  type: "channel_verification_session",
1444
1441
  action: "status",
1445
1442
  channel: "phone",
1446
1443
  };
1447
1444
 
1448
- await handleChannelVerificationSession(msg, ctx);
1445
+ await handleChannelVerificationSession(msg);
1449
1446
 
1450
1447
  const resp = lastResponse();
1451
1448
  expect(resp).not.toBeNull();
@@ -1457,14 +1454,14 @@ describe("HTTP handler channel-aware guardian status", () => {
1457
1454
  test("status action includes hasPendingChallenge when challenge exists", async () => {
1458
1455
  createInboundVerificationSession("phone");
1459
1456
 
1460
- const { ctx, lastResponse } = createMockCtx();
1457
+ const { lastResponse } = createResponseReader();
1461
1458
  const msg: ChannelVerificationSessionRequest = {
1462
1459
  type: "channel_verification_session",
1463
1460
  action: "status",
1464
1461
  channel: "phone",
1465
1462
  };
1466
1463
 
1467
- await handleChannelVerificationSession(msg, ctx);
1464
+ await handleChannelVerificationSession(msg);
1468
1465
 
1469
1466
  const resp = lastResponse();
1470
1467
  expect(resp).not.toBeNull();
@@ -1473,14 +1470,14 @@ describe("HTTP handler channel-aware guardian status", () => {
1473
1470
  });
1474
1471
 
1475
1472
  test("status action hasPendingChallenge is false when no challenge exists", async () => {
1476
- const { ctx, lastResponse } = createMockCtx();
1473
+ const { lastResponse } = createResponseReader();
1477
1474
  const msg: ChannelVerificationSessionRequest = {
1478
1475
  type: "channel_verification_session",
1479
1476
  action: "status",
1480
1477
  channel: "phone",
1481
1478
  };
1482
1479
 
1483
- await handleChannelVerificationSession(msg, ctx);
1480
+ await handleChannelVerificationSession(msg);
1484
1481
 
1485
1482
  const resp = lastResponse();
1486
1483
  expect(resp).not.toBeNull();
@@ -1922,14 +1919,14 @@ describe("HTTP handler voice guardian verification", () => {
1922
1919
  });
1923
1920
 
1924
1921
  test("create_challenge for voice returns a high-entropy hex secret", async () => {
1925
- const { ctx, lastResponse } = createMockCtx();
1922
+ const { lastResponse } = createResponseReader();
1926
1923
  const msg: ChannelVerificationSessionRequest = {
1927
1924
  type: "channel_verification_session",
1928
1925
  action: "create_session",
1929
1926
  channel: "phone",
1930
1927
  };
1931
1928
 
1932
- await handleChannelVerificationSession(msg, ctx);
1929
+ await handleChannelVerificationSession(msg);
1933
1930
 
1934
1931
  const resp = lastResponse();
1935
1932
  expect(resp).not.toBeNull();
@@ -1942,14 +1939,14 @@ describe("HTTP handler voice guardian verification", () => {
1942
1939
  });
1943
1940
 
1944
1941
  test("status for voice reflects unbound state", async () => {
1945
- const { ctx, lastResponse } = createMockCtx();
1942
+ const { lastResponse } = createResponseReader();
1946
1943
  const msg: ChannelVerificationSessionRequest = {
1947
1944
  type: "channel_verification_session",
1948
1945
  action: "status",
1949
1946
  channel: "phone",
1950
1947
  };
1951
1948
 
1952
- await handleChannelVerificationSession(msg, ctx);
1949
+ await handleChannelVerificationSession(msg);
1953
1950
 
1954
1951
  const resp = lastResponse();
1955
1952
  expect(resp).not.toBeNull();
@@ -1967,14 +1964,14 @@ describe("HTTP handler voice guardian verification", () => {
1967
1964
  guardianDeliveryChatId: "voice-chat-1",
1968
1965
  });
1969
1966
 
1970
- const { ctx, lastResponse } = createMockCtx();
1967
+ const { lastResponse } = createResponseReader();
1971
1968
  const msg: ChannelVerificationSessionRequest = {
1972
1969
  type: "channel_verification_session",
1973
1970
  action: "status",
1974
1971
  channel: "phone",
1975
1972
  };
1976
1973
 
1977
- await handleChannelVerificationSession(msg, ctx);
1974
+ await handleChannelVerificationSession(msg);
1978
1975
 
1979
1976
  const resp = lastResponse();
1980
1977
  expect(resp).not.toBeNull();
@@ -1993,14 +1990,14 @@ describe("HTTP handler voice guardian verification", () => {
1993
1990
  guardianDeliveryChatId: "voice-chat-1",
1994
1991
  });
1995
1992
 
1996
- const { ctx, lastResponse } = createMockCtx();
1993
+ const { lastResponse } = createResponseReader();
1997
1994
  const msg: ChannelVerificationSessionRequest = {
1998
1995
  type: "channel_verification_session",
1999
1996
  action: "revoke",
2000
1997
  channel: "phone",
2001
1998
  };
2002
1999
 
2003
- await handleChannelVerificationSession(msg, ctx);
2000
+ await handleChannelVerificationSession(msg);
2004
2001
 
2005
2002
  const resp = lastResponse();
2006
2003
  expect(resp).not.toBeNull();
@@ -2025,14 +2022,14 @@ describe("HTTP handler voice guardian verification", () => {
2025
2022
  guardianDeliveryChatId: "tg-chat-1",
2026
2023
  });
2027
2024
 
2028
- const { ctx } = createMockCtx();
2025
+ broadcastedMessages.length = 0;
2029
2026
  const msg: ChannelVerificationSessionRequest = {
2030
2027
  type: "channel_verification_session",
2031
2028
  action: "revoke",
2032
2029
  channel: "phone",
2033
2030
  };
2034
2031
 
2035
- await handleChannelVerificationSession(msg, ctx);
2032
+ await handleChannelVerificationSession(msg);
2036
2033
 
2037
2034
  expect(getGuardianBinding("self", "phone")).toBeNull();
2038
2035
  expect(getGuardianBinding("self", "telegram")).not.toBeNull();
@@ -2508,7 +2505,7 @@ describe("outbound voice verification", () => {
2508
2505
  });
2509
2506
 
2510
2507
  test("start_outbound creates session with expected E.164 identity and returns code", async () => {
2511
- const { ctx, lastResponse } = createMockCtx();
2508
+ const { lastResponse } = createResponseReader();
2512
2509
  const msg: ChannelVerificationSessionRequest = {
2513
2510
  type: "channel_verification_session",
2514
2511
  action: "create_session",
@@ -2516,7 +2513,7 @@ describe("outbound voice verification", () => {
2516
2513
  destination: "+15551234567",
2517
2514
  };
2518
2515
 
2519
- await handleChannelVerificationSession(msg, ctx);
2516
+ await handleChannelVerificationSession(msg);
2520
2517
 
2521
2518
  const resp = lastResponse();
2522
2519
  expect(resp).not.toBeNull();
@@ -2544,7 +2541,7 @@ describe("outbound voice verification", () => {
2544
2541
  guardianDeliveryChatId: "voice-chat-1",
2545
2542
  });
2546
2543
 
2547
- const { ctx, lastResponse } = createMockCtx();
2544
+ const { lastResponse } = createResponseReader();
2548
2545
  const msg: ChannelVerificationSessionRequest = {
2549
2546
  type: "channel_verification_session",
2550
2547
  action: "create_session",
@@ -2553,7 +2550,7 @@ describe("outbound voice verification", () => {
2553
2550
  rebind: false,
2554
2551
  };
2555
2552
 
2556
- await handleChannelVerificationSession(msg, ctx);
2553
+ await handleChannelVerificationSession(msg);
2557
2554
 
2558
2555
  const resp = lastResponse();
2559
2556
  expect(resp).not.toBeNull();
@@ -2570,7 +2567,7 @@ describe("outbound voice verification", () => {
2570
2567
  guardianDeliveryChatId: "voice-chat-1",
2571
2568
  });
2572
2569
 
2573
- const { ctx, lastResponse } = createMockCtx();
2570
+ const { lastResponse } = createResponseReader();
2574
2571
  const msg: ChannelVerificationSessionRequest = {
2575
2572
  type: "channel_verification_session",
2576
2573
  action: "create_session",
@@ -2579,7 +2576,7 @@ describe("outbound voice verification", () => {
2579
2576
  rebind: true,
2580
2577
  };
2581
2578
 
2582
- await handleChannelVerificationSession(msg, ctx);
2579
+ await handleChannelVerificationSession(msg);
2583
2580
 
2584
2581
  const resp = lastResponse();
2585
2582
  expect(resp).not.toBeNull();
@@ -2589,7 +2586,7 @@ describe("outbound voice verification", () => {
2589
2586
 
2590
2587
  test("resend_outbound before cooldown is rejected", async () => {
2591
2588
  // Start an outbound session first
2592
- const { ctx: startCtx } = createMockCtx();
2589
+ broadcastedMessages.length = 0;
2593
2590
  await handleChannelVerificationSession(
2594
2591
  {
2595
2592
  type: "channel_verification_session",
@@ -2597,18 +2594,16 @@ describe("outbound voice verification", () => {
2597
2594
  channel: "phone",
2598
2595
  destination: "+15551234567",
2599
2596
  },
2600
- startCtx,
2601
2597
  );
2602
2598
 
2603
2599
  // Immediately try to resend (before cooldown)
2604
- const { ctx, lastResponse } = createMockCtx();
2600
+ const { lastResponse } = createResponseReader();
2605
2601
  await handleChannelVerificationSession(
2606
2602
  {
2607
2603
  type: "channel_verification_session",
2608
2604
  action: "resend_session",
2609
2605
  channel: "phone",
2610
2606
  },
2611
- ctx,
2612
2607
  );
2613
2608
 
2614
2609
  const resp = lastResponse();
@@ -2619,7 +2614,7 @@ describe("outbound voice verification", () => {
2619
2614
 
2620
2615
  test("resend_outbound after cooldown succeeds and increments sendCount", async () => {
2621
2616
  // Start an outbound session
2622
- const { ctx: startCtx, lastResponse: startResp } = createMockCtx();
2617
+ const { lastResponse: startResp } = createResponseReader();
2623
2618
  await handleChannelVerificationSession(
2624
2619
  {
2625
2620
  type: "channel_verification_session",
@@ -2627,7 +2622,6 @@ describe("outbound voice verification", () => {
2627
2622
  channel: "phone",
2628
2623
  destination: "+15551234567",
2629
2624
  },
2630
- startCtx,
2631
2625
  );
2632
2626
 
2633
2627
  const startResponse = startResp();
@@ -2644,14 +2638,13 @@ describe("outbound voice verification", () => {
2644
2638
  );
2645
2639
 
2646
2640
  // Now resend should succeed
2647
- const { ctx, lastResponse } = createMockCtx();
2641
+ const { lastResponse } = createResponseReader();
2648
2642
  await handleChannelVerificationSession(
2649
2643
  {
2650
2644
  type: "channel_verification_session",
2651
2645
  action: "resend_session",
2652
2646
  channel: "phone",
2653
2647
  },
2654
- ctx,
2655
2648
  );
2656
2649
 
2657
2650
  const resp = lastResponse();
@@ -2663,7 +2656,7 @@ describe("outbound voice verification", () => {
2663
2656
 
2664
2657
  test("resend_outbound exceeding max sends is rejected", async () => {
2665
2658
  // Start an outbound session
2666
- const { ctx: startCtx } = createMockCtx();
2659
+ broadcastedMessages.length = 0;
2667
2660
  await handleChannelVerificationSession(
2668
2661
  {
2669
2662
  type: "channel_verification_session",
@@ -2671,7 +2664,6 @@ describe("outbound voice verification", () => {
2671
2664
  channel: "phone",
2672
2665
  destination: "+15551234567",
2673
2666
  },
2674
- startCtx,
2675
2667
  );
2676
2668
 
2677
2669
  // Set the send count to MAX_SENDS_PER_SESSION and nextResendAt to the past
@@ -2685,14 +2677,13 @@ describe("outbound voice verification", () => {
2685
2677
  );
2686
2678
 
2687
2679
  // Resend should be rejected due to max sends
2688
- const { ctx, lastResponse } = createMockCtx();
2680
+ const { lastResponse } = createResponseReader();
2689
2681
  await handleChannelVerificationSession(
2690
2682
  {
2691
2683
  type: "channel_verification_session",
2692
2684
  action: "resend_session",
2693
2685
  channel: "phone",
2694
2686
  },
2695
- ctx,
2696
2687
  );
2697
2688
 
2698
2689
  const resp = lastResponse();
@@ -2703,7 +2694,7 @@ describe("outbound voice verification", () => {
2703
2694
 
2704
2695
  test("cancel_outbound revokes active session", async () => {
2705
2696
  // Start an outbound session
2706
- const { ctx: startCtx } = createMockCtx();
2697
+ broadcastedMessages.length = 0;
2707
2698
  await handleChannelVerificationSession(
2708
2699
  {
2709
2700
  type: "channel_verification_session",
@@ -2711,7 +2702,6 @@ describe("outbound voice verification", () => {
2711
2702
  channel: "phone",
2712
2703
  destination: "+15551234567",
2713
2704
  },
2714
- startCtx,
2715
2705
  );
2716
2706
 
2717
2707
  // Verify session exists
@@ -2719,14 +2709,13 @@ describe("outbound voice verification", () => {
2719
2709
  expect(sessionBefore).not.toBeNull();
2720
2710
 
2721
2711
  // Cancel it
2722
- const { ctx, lastResponse } = createMockCtx();
2712
+ const { lastResponse } = createResponseReader();
2723
2713
  await handleChannelVerificationSession(
2724
2714
  {
2725
2715
  type: "channel_verification_session",
2726
2716
  action: "cancel_session",
2727
2717
  channel: "phone",
2728
2718
  },
2729
- ctx,
2730
2719
  );
2731
2720
 
2732
2721
  const resp = lastResponse();
@@ -2789,7 +2778,7 @@ describe("outbound voice verification", () => {
2789
2778
  });
2790
2779
 
2791
2780
  test("start_outbound rejects unsupported channels", async () => {
2792
- const { ctx, lastResponse } = createMockCtx();
2781
+ const { lastResponse } = createResponseReader();
2793
2782
  await handleChannelVerificationSession(
2794
2783
  {
2795
2784
  type: "channel_verification_session",
@@ -2797,7 +2786,6 @@ describe("outbound voice verification", () => {
2797
2786
  channel: "email",
2798
2787
  destination: "user@example.com",
2799
2788
  },
2800
- ctx,
2801
2789
  );
2802
2790
 
2803
2791
  const resp = lastResponse();
@@ -2807,7 +2795,7 @@ describe("outbound voice verification", () => {
2807
2795
  });
2808
2796
 
2809
2797
  test("create_session without destination falls through to inbound challenge", async () => {
2810
- const { ctx, lastResponse } = createMockCtx();
2798
+ const { lastResponse } = createResponseReader();
2811
2799
  await handleChannelVerificationSession(
2812
2800
  {
2813
2801
  type: "channel_verification_session",
@@ -2815,7 +2803,6 @@ describe("outbound voice verification", () => {
2815
2803
  channel: "phone",
2816
2804
  // no destination — unified create_session creates an inbound challenge
2817
2805
  },
2818
- ctx,
2819
2806
  );
2820
2807
 
2821
2808
  const resp = lastResponse();
@@ -2825,7 +2812,7 @@ describe("outbound voice verification", () => {
2825
2812
  });
2826
2813
 
2827
2814
  test("start_outbound rejects unparseable phone number", async () => {
2828
- const { ctx, lastResponse } = createMockCtx();
2815
+ const { lastResponse } = createResponseReader();
2829
2816
  await handleChannelVerificationSession(
2830
2817
  {
2831
2818
  type: "channel_verification_session",
@@ -2833,7 +2820,6 @@ describe("outbound voice verification", () => {
2833
2820
  channel: "phone",
2834
2821
  destination: "not-a-phone",
2835
2822
  },
2836
- ctx,
2837
2823
  );
2838
2824
 
2839
2825
  const resp = lastResponse();
@@ -2843,7 +2829,7 @@ describe("outbound voice verification", () => {
2843
2829
  });
2844
2830
 
2845
2831
  test("start_outbound normalizes formatted phone number for voice", async () => {
2846
- const { ctx, lastResponse } = createMockCtx();
2832
+ const { lastResponse } = createResponseReader();
2847
2833
  await handleChannelVerificationSession(
2848
2834
  {
2849
2835
  type: "channel_verification_session",
@@ -2851,7 +2837,6 @@ describe("outbound voice verification", () => {
2851
2837
  channel: "phone",
2852
2838
  destination: "(555) 123-4567",
2853
2839
  },
2854
- ctx,
2855
2840
  );
2856
2841
 
2857
2842
  const resp = lastResponse();
@@ -2876,14 +2861,13 @@ describe("outbound voice verification", () => {
2876
2861
  });
2877
2862
 
2878
2863
  test("cancel_session succeeds even when no active session (idempotent)", async () => {
2879
- const { ctx, lastResponse } = createMockCtx();
2864
+ const { lastResponse } = createResponseReader();
2880
2865
  await handleChannelVerificationSession(
2881
2866
  {
2882
2867
  type: "channel_verification_session",
2883
2868
  action: "cancel_session",
2884
2869
  channel: "phone",
2885
2870
  },
2886
- ctx,
2887
2871
  );
2888
2872
 
2889
2873
  const resp = lastResponse();
@@ -2902,7 +2886,7 @@ describe("outbound Telegram verification", () => {
2902
2886
  });
2903
2887
 
2904
2888
  test("start_outbound for telegram with handle returns deep link URL, no outbound message", async () => {
2905
- const { ctx, lastResponse } = createMockCtx();
2889
+ const { lastResponse } = createResponseReader();
2906
2890
  await handleChannelVerificationSession(
2907
2891
  {
2908
2892
  type: "channel_verification_session",
@@ -2910,7 +2894,6 @@ describe("outbound Telegram verification", () => {
2910
2894
  channel: "telegram",
2911
2895
  destination: "@someuser",
2912
2896
  },
2913
- ctx,
2914
2897
  );
2915
2898
 
2916
2899
  const resp = lastResponse();
@@ -2936,7 +2919,7 @@ describe("outbound Telegram verification", () => {
2936
2919
  });
2937
2920
 
2938
2921
  test("start_outbound for telegram with handle (no @ prefix) returns deep link", async () => {
2939
- const { ctx, lastResponse } = createMockCtx();
2922
+ const { lastResponse } = createResponseReader();
2940
2923
  await handleChannelVerificationSession(
2941
2924
  {
2942
2925
  type: "channel_verification_session",
@@ -2944,7 +2927,6 @@ describe("outbound Telegram verification", () => {
2944
2927
  channel: "telegram",
2945
2928
  destination: "someuser",
2946
2929
  },
2947
- ctx,
2948
2930
  );
2949
2931
 
2950
2932
  const resp = lastResponse();
@@ -2957,7 +2939,7 @@ describe("outbound Telegram verification", () => {
2957
2939
  });
2958
2940
 
2959
2941
  test("start_outbound for telegram with known chat ID sends message, no deep link", async () => {
2960
- const { ctx, lastResponse } = createMockCtx();
2942
+ const { lastResponse } = createResponseReader();
2961
2943
  await handleChannelVerificationSession(
2962
2944
  {
2963
2945
  type: "channel_verification_session",
@@ -2965,7 +2947,6 @@ describe("outbound Telegram verification", () => {
2965
2947
  channel: "telegram",
2966
2948
  destination: "123456789",
2967
2949
  },
2968
- ctx,
2969
2950
  );
2970
2951
 
2971
2952
  const resp = lastResponse();
@@ -2997,7 +2978,7 @@ describe("outbound Telegram verification", () => {
2997
2978
  test("start_outbound for telegram without bot username fails", async () => {
2998
2979
  mockBotUsername = undefined;
2999
2980
 
3000
- const { ctx, lastResponse } = createMockCtx();
2981
+ const { lastResponse } = createResponseReader();
3001
2982
  await handleChannelVerificationSession(
3002
2983
  {
3003
2984
  type: "channel_verification_session",
@@ -3005,7 +2986,6 @@ describe("outbound Telegram verification", () => {
3005
2986
  channel: "telegram",
3006
2987
  destination: "@someuser",
3007
2988
  },
3008
- ctx,
3009
2989
  );
3010
2990
 
3011
2991
  const resp = lastResponse();
@@ -3022,7 +3002,7 @@ describe("outbound Telegram verification", () => {
3022
3002
  guardianDeliveryChatId: "chat-42",
3023
3003
  });
3024
3004
 
3025
- const { ctx, lastResponse } = createMockCtx();
3005
+ const { lastResponse } = createResponseReader();
3026
3006
  await handleChannelVerificationSession(
3027
3007
  {
3028
3008
  type: "channel_verification_session",
@@ -3031,7 +3011,6 @@ describe("outbound Telegram verification", () => {
3031
3011
  destination: "@newuser",
3032
3012
  rebind: false,
3033
3013
  },
3034
- ctx,
3035
3014
  );
3036
3015
 
3037
3016
  const resp = lastResponse();
@@ -3185,7 +3164,7 @@ describe("outbound Telegram verification", () => {
3185
3164
 
3186
3165
  test("resend_outbound for telegram works with known chat ID", async () => {
3187
3166
  // Start an outbound session with a known chat ID
3188
- const { ctx: startCtx } = createMockCtx();
3167
+ broadcastedMessages.length = 0;
3189
3168
  await handleChannelVerificationSession(
3190
3169
  {
3191
3170
  type: "channel_verification_session",
@@ -3193,7 +3172,6 @@ describe("outbound Telegram verification", () => {
3193
3172
  channel: "telegram",
3194
3173
  destination: "123456789",
3195
3174
  },
3196
- startCtx,
3197
3175
  );
3198
3176
 
3199
3177
  // Fast-forward the cooldown
@@ -3206,14 +3184,13 @@ describe("outbound Telegram verification", () => {
3206
3184
  Date.now() - 1000,
3207
3185
  );
3208
3186
 
3209
- const { ctx, lastResponse } = createMockCtx();
3187
+ const { lastResponse } = createResponseReader();
3210
3188
  await handleChannelVerificationSession(
3211
3189
  {
3212
3190
  type: "channel_verification_session",
3213
3191
  action: "resend_session",
3214
3192
  channel: "telegram",
3215
3193
  },
3216
- ctx,
3217
3194
  );
3218
3195
 
3219
3196
  const resp = lastResponse();
@@ -3229,7 +3206,7 @@ describe("outbound Telegram verification", () => {
3229
3206
 
3230
3207
  test("resend_outbound for pending_bootstrap session is rejected", async () => {
3231
3208
  // Start an outbound session with a handle (pending_bootstrap)
3232
- const { ctx: startCtx } = createMockCtx();
3209
+ broadcastedMessages.length = 0;
3233
3210
  await handleChannelVerificationSession(
3234
3211
  {
3235
3212
  type: "channel_verification_session",
@@ -3237,17 +3214,15 @@ describe("outbound Telegram verification", () => {
3237
3214
  channel: "telegram",
3238
3215
  destination: "@someuser",
3239
3216
  },
3240
- startCtx,
3241
3217
  );
3242
3218
 
3243
- const { ctx, lastResponse } = createMockCtx();
3219
+ const { lastResponse } = createResponseReader();
3244
3220
  await handleChannelVerificationSession(
3245
3221
  {
3246
3222
  type: "channel_verification_session",
3247
3223
  action: "resend_session",
3248
3224
  channel: "telegram",
3249
3225
  },
3250
- ctx,
3251
3226
  );
3252
3227
 
3253
3228
  const resp = lastResponse();
@@ -3258,7 +3233,7 @@ describe("outbound Telegram verification", () => {
3258
3233
 
3259
3234
  test("cancel_outbound for telegram revokes session", async () => {
3260
3235
  // Start an outbound session
3261
- const { ctx: startCtx } = createMockCtx();
3236
+ broadcastedMessages.length = 0;
3262
3237
  await handleChannelVerificationSession(
3263
3238
  {
3264
3239
  type: "channel_verification_session",
@@ -3266,20 +3241,18 @@ describe("outbound Telegram verification", () => {
3266
3241
  channel: "telegram",
3267
3242
  destination: "123456789",
3268
3243
  },
3269
- startCtx,
3270
3244
  );
3271
3245
 
3272
3246
  const session = serviceFindActiveSession("telegram");
3273
3247
  expect(session).not.toBeNull();
3274
3248
 
3275
- const { ctx, lastResponse } = createMockCtx();
3249
+ const { lastResponse } = createResponseReader();
3276
3250
  await handleChannelVerificationSession(
3277
3251
  {
3278
3252
  type: "channel_verification_session",
3279
3253
  action: "cancel_session",
3280
3254
  channel: "telegram",
3281
3255
  },
3282
- ctx,
3283
3256
  );
3284
3257
 
3285
3258
  const resp = lastResponse();
@@ -3320,14 +3293,13 @@ describe("outbound Telegram verification", () => {
3320
3293
  });
3321
3294
 
3322
3295
  test("create_session for telegram without destination falls through to inbound challenge", async () => {
3323
- const { ctx, lastResponse } = createMockCtx();
3296
+ const { lastResponse } = createResponseReader();
3324
3297
  await handleChannelVerificationSession(
3325
3298
  {
3326
3299
  type: "channel_verification_session",
3327
3300
  action: "create_session",
3328
3301
  channel: "telegram",
3329
3302
  },
3330
- ctx,
3331
3303
  );
3332
3304
 
3333
3305
  const resp = lastResponse();
@@ -3338,7 +3310,7 @@ describe("outbound Telegram verification", () => {
3338
3310
 
3339
3311
  test("rate limits apply to telegram outbound (per-session send cap)", async () => {
3340
3312
  // Start an outbound session with a known chat ID
3341
- const { ctx: startCtx } = createMockCtx();
3313
+ broadcastedMessages.length = 0;
3342
3314
  await handleChannelVerificationSession(
3343
3315
  {
3344
3316
  type: "channel_verification_session",
@@ -3346,7 +3318,6 @@ describe("outbound Telegram verification", () => {
3346
3318
  channel: "telegram",
3347
3319
  destination: "123456789",
3348
3320
  },
3349
- startCtx,
3350
3321
  );
3351
3322
 
3352
3323
  // Set the send count to MAX_SENDS_PER_SESSION and nextResendAt to the past
@@ -3360,14 +3331,13 @@ describe("outbound Telegram verification", () => {
3360
3331
  );
3361
3332
 
3362
3333
  // Resend should be rejected due to max sends
3363
- const { ctx, lastResponse } = createMockCtx();
3334
+ const { lastResponse } = createResponseReader();
3364
3335
  await handleChannelVerificationSession(
3365
3336
  {
3366
3337
  type: "channel_verification_session",
3367
3338
  action: "resend_session",
3368
3339
  channel: "telegram",
3369
3340
  },
3370
- ctx,
3371
3341
  );
3372
3342
 
3373
3343
  const resp = lastResponse();
@@ -3378,7 +3348,7 @@ describe("outbound Telegram verification", () => {
3378
3348
 
3379
3349
  test("rate limits apply to telegram outbound (cooldown)", async () => {
3380
3350
  // Start an outbound session with a known chat ID
3381
- const { ctx: startCtx } = createMockCtx();
3351
+ broadcastedMessages.length = 0;
3382
3352
  await handleChannelVerificationSession(
3383
3353
  {
3384
3354
  type: "channel_verification_session",
@@ -3386,18 +3356,16 @@ describe("outbound Telegram verification", () => {
3386
3356
  channel: "telegram",
3387
3357
  destination: "123456789",
3388
3358
  },
3389
- startCtx,
3390
3359
  );
3391
3360
 
3392
3361
  // Immediately try to resend (before cooldown)
3393
- const { ctx, lastResponse } = createMockCtx();
3362
+ const { lastResponse } = createResponseReader();
3394
3363
  await handleChannelVerificationSession(
3395
3364
  {
3396
3365
  type: "channel_verification_session",
3397
3366
  action: "resend_session",
3398
3367
  channel: "telegram",
3399
3368
  },
3400
- ctx,
3401
3369
  );
3402
3370
 
3403
3371
  const resp = lastResponse();
@@ -3417,7 +3385,7 @@ describe("outbound voice verification", () => {
3417
3385
  });
3418
3386
 
3419
3387
  test("start_outbound for voice creates session with 6-digit code and initiates call", async () => {
3420
- const { ctx, lastResponse } = createMockCtx();
3388
+ const { lastResponse } = createResponseReader();
3421
3389
  await handleChannelVerificationSession(
3422
3390
  {
3423
3391
  type: "channel_verification_session",
@@ -3425,7 +3393,6 @@ describe("outbound voice verification", () => {
3425
3393
  channel: "phone",
3426
3394
  destination: "+15551234567",
3427
3395
  },
3428
- ctx,
3429
3396
  );
3430
3397
 
3431
3398
  const resp = lastResponse();
@@ -3458,7 +3425,7 @@ describe("outbound voice verification", () => {
3458
3425
  });
3459
3426
 
3460
3427
  test("start_outbound for voice rejects unparseable phone number", async () => {
3461
- const { ctx, lastResponse } = createMockCtx();
3428
+ const { lastResponse } = createResponseReader();
3462
3429
  await handleChannelVerificationSession(
3463
3430
  {
3464
3431
  type: "channel_verification_session",
@@ -3466,7 +3433,6 @@ describe("outbound voice verification", () => {
3466
3433
  channel: "phone",
3467
3434
  destination: "not-a-phone",
3468
3435
  },
3469
- ctx,
3470
3436
  );
3471
3437
 
3472
3438
  const resp = lastResponse();
@@ -3476,7 +3442,7 @@ describe("outbound voice verification", () => {
3476
3442
  });
3477
3443
 
3478
3444
  test("start_outbound for voice normalizes formatted phone number", async () => {
3479
- const { ctx, lastResponse } = createMockCtx();
3445
+ const { lastResponse } = createResponseReader();
3480
3446
  await handleChannelVerificationSession(
3481
3447
  {
3482
3448
  type: "channel_verification_session",
@@ -3484,7 +3450,6 @@ describe("outbound voice verification", () => {
3484
3450
  channel: "phone",
3485
3451
  destination: "555-123-4567",
3486
3452
  },
3487
- ctx,
3488
3453
  );
3489
3454
 
3490
3455
  const resp = lastResponse();
@@ -3518,7 +3483,7 @@ describe("outbound voice verification", () => {
3518
3483
  guardianDeliveryChatId: "+15551234567",
3519
3484
  });
3520
3485
 
3521
- const { ctx, lastResponse } = createMockCtx();
3486
+ const { lastResponse } = createResponseReader();
3522
3487
  await handleChannelVerificationSession(
3523
3488
  {
3524
3489
  type: "channel_verification_session",
@@ -3527,7 +3492,6 @@ describe("outbound voice verification", () => {
3527
3492
  destination: "+15559876543",
3528
3493
  rebind: false,
3529
3494
  },
3530
- ctx,
3531
3495
  );
3532
3496
 
3533
3497
  const resp = lastResponse();
@@ -3538,7 +3502,7 @@ describe("outbound voice verification", () => {
3538
3502
 
3539
3503
  test("resend_outbound for voice initiates a new call with cooldown check", async () => {
3540
3504
  // Start an outbound session first
3541
- const { ctx: startCtx } = createMockCtx();
3505
+ broadcastedMessages.length = 0;
3542
3506
  await handleChannelVerificationSession(
3543
3507
  {
3544
3508
  type: "channel_verification_session",
@@ -3546,18 +3510,16 @@ describe("outbound voice verification", () => {
3546
3510
  channel: "phone",
3547
3511
  destination: "+15551234567",
3548
3512
  },
3549
- startCtx,
3550
3513
  );
3551
3514
 
3552
3515
  // Immediately try to resend (before cooldown)
3553
- const { ctx, lastResponse } = createMockCtx();
3516
+ const { lastResponse } = createResponseReader();
3554
3517
  await handleChannelVerificationSession(
3555
3518
  {
3556
3519
  type: "channel_verification_session",
3557
3520
  action: "resend_session",
3558
3521
  channel: "phone",
3559
3522
  },
3560
- ctx,
3561
3523
  );
3562
3524
 
3563
3525
  const resp = lastResponse();
@@ -3568,7 +3530,7 @@ describe("outbound voice verification", () => {
3568
3530
 
3569
3531
  test("cancel_outbound for voice cancels session", async () => {
3570
3532
  // Start an outbound session first
3571
- const { ctx: startCtx } = createMockCtx();
3533
+ broadcastedMessages.length = 0;
3572
3534
  await handleChannelVerificationSession(
3573
3535
  {
3574
3536
  type: "channel_verification_session",
@@ -3576,18 +3538,16 @@ describe("outbound voice verification", () => {
3576
3538
  channel: "phone",
3577
3539
  destination: "+15551234567",
3578
3540
  },
3579
- startCtx,
3580
3541
  );
3581
3542
 
3582
3543
  // Cancel the session
3583
- const { ctx, lastResponse } = createMockCtx();
3544
+ const { lastResponse } = createResponseReader();
3584
3545
  await handleChannelVerificationSession(
3585
3546
  {
3586
3547
  type: "channel_verification_session",
3587
3548
  action: "cancel_session",
3588
3549
  channel: "phone",
3589
3550
  },
3590
- ctx,
3591
3551
  );
3592
3552
 
3593
3553
  const resp = lastResponse();
@@ -3621,7 +3581,7 @@ describe("outbound voice verification", () => {
3621
3581
  .run();
3622
3582
  }
3623
3583
 
3624
- const { ctx, lastResponse } = createMockCtx();
3584
+ const { lastResponse } = createResponseReader();
3625
3585
  await handleChannelVerificationSession(
3626
3586
  {
3627
3587
  type: "channel_verification_session",
@@ -3629,7 +3589,6 @@ describe("outbound voice verification", () => {
3629
3589
  channel: "phone",
3630
3590
  destination: "+15551234567",
3631
3591
  },
3632
- ctx,
3633
3592
  );
3634
3593
 
3635
3594
  const resp = lastResponse();
@@ -3639,14 +3598,13 @@ describe("outbound voice verification", () => {
3639
3598
  });
3640
3599
 
3641
3600
  test("create_session for voice without destination falls through to inbound challenge", async () => {
3642
- const { ctx, lastResponse } = createMockCtx();
3601
+ const { lastResponse } = createResponseReader();
3643
3602
  await handleChannelVerificationSession(
3644
3603
  {
3645
3604
  type: "channel_verification_session",
3646
3605
  action: "create_session",
3647
3606
  channel: "phone",
3648
3607
  },
3649
- ctx,
3650
3608
  );
3651
3609
 
3652
3610
  const resp = lastResponse();
@@ -3674,7 +3632,7 @@ describe("M1–M4 hardening coverage", () => {
3674
3632
  // ── M2: start_outbound for voice returns secret in response ──
3675
3633
 
3676
3634
  test("start_outbound for voice response includes secret", async () => {
3677
- const { ctx, lastResponse } = createMockCtx();
3635
+ const { lastResponse } = createResponseReader();
3678
3636
  await handleChannelVerificationSession(
3679
3637
  {
3680
3638
  type: "channel_verification_session",
@@ -3682,7 +3640,6 @@ describe("M1–M4 hardening coverage", () => {
3682
3640
  channel: "phone",
3683
3641
  destination: "+15551234567",
3684
3642
  },
3685
- ctx,
3686
3643
  );
3687
3644
 
3688
3645
  const resp = lastResponse();
@@ -3697,7 +3654,7 @@ describe("M1–M4 hardening coverage", () => {
3697
3654
 
3698
3655
  test("resend_outbound for voice response includes secret", async () => {
3699
3656
  // Start a session first
3700
- const { ctx: startCtx } = createMockCtx();
3657
+ broadcastedMessages.length = 0;
3701
3658
  await handleChannelVerificationSession(
3702
3659
  {
3703
3660
  type: "channel_verification_session",
@@ -3705,7 +3662,6 @@ describe("M1–M4 hardening coverage", () => {
3705
3662
  channel: "phone",
3706
3663
  destination: "+15551234567",
3707
3664
  },
3708
- startCtx,
3709
3665
  );
3710
3666
 
3711
3667
  // Move past cooldown
@@ -3719,14 +3675,13 @@ describe("M1–M4 hardening coverage", () => {
3719
3675
  );
3720
3676
 
3721
3677
  // Resend
3722
- const { ctx, lastResponse } = createMockCtx();
3678
+ const { lastResponse } = createResponseReader();
3723
3679
  await handleChannelVerificationSession(
3724
3680
  {
3725
3681
  type: "channel_verification_session",
3726
3682
  action: "resend_session",
3727
3683
  channel: "phone",
3728
3684
  },
3729
- ctx,
3730
3685
  );
3731
3686
 
3732
3687
  const resp = lastResponse();
@@ -3740,7 +3695,7 @@ describe("M1–M4 hardening coverage", () => {
3740
3695
  // ── M2: start_outbound for Telegram bootstrap does NOT return secret ──
3741
3696
 
3742
3697
  test("start_outbound for Telegram bootstrap (handle) does NOT return secret", async () => {
3743
- const { ctx, lastResponse } = createMockCtx();
3698
+ const { lastResponse } = createResponseReader();
3744
3699
  await handleChannelVerificationSession(
3745
3700
  {
3746
3701
  type: "channel_verification_session",
@@ -3748,7 +3703,6 @@ describe("M1–M4 hardening coverage", () => {
3748
3703
  channel: "telegram",
3749
3704
  destination: "@someuser",
3750
3705
  },
3751
- ctx,
3752
3706
  );
3753
3707
 
3754
3708
  const resp = lastResponse();