@vellumai/assistant 0.10.5-dev.202607042025.8a04d0b → 0.10.5-staging.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 (482) hide show
  1. package/ARCHITECTURE.md +61 -64
  2. package/README.md +10 -7
  3. package/docs/stt-provider-onboarding.md +5 -4
  4. package/docs/trusted-contact-access.md +7 -7
  5. package/eslint-rules/cli-no-daemon-internals.js +0 -3
  6. package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +0 -36
  7. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +0 -22
  8. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +8 -20
  9. package/node_modules/@vellumai/gateway-client/src/index.ts +0 -27
  10. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +0 -4
  11. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +0 -6
  12. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +8 -0
  13. package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +14 -3
  14. package/openapi.yaml +42 -299
  15. package/package.json +1 -1
  16. package/src/__tests__/access-request-seed-content-blocks.test.ts +4 -60
  17. package/src/__tests__/acp-session.test.ts +1 -4
  18. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +6 -0
  19. package/src/__tests__/anthropic-provider.test.ts +19 -60
  20. package/src/__tests__/app-conversation-ids-backfill.test.ts +0 -3
  21. package/src/__tests__/app-source-watcher.test.ts +0 -11
  22. package/src/__tests__/assistant-event-hub.test.ts +0 -65
  23. package/src/__tests__/assistant-id-boundary-guard.test.ts +1 -0
  24. package/src/__tests__/assistant-stream-state.test.ts +0 -18
  25. package/src/__tests__/attachments-store.test.ts +0 -3
  26. package/src/__tests__/background-workers-disk-pressure.test.ts +26 -0
  27. package/src/__tests__/btw-routes.test.ts +3 -57
  28. package/src/__tests__/call-controller.test.ts +79 -260
  29. package/src/__tests__/call-domain.test.ts +1 -48
  30. package/src/__tests__/call-pointer-no-hardcoded-copy.guard.test.ts +6 -2
  31. package/src/__tests__/call-routes-http.test.ts +14 -15
  32. package/src/__tests__/channel-guardian.test.ts +7 -4
  33. package/src/__tests__/channel-readiness-service.test.ts +6 -0
  34. package/src/__tests__/channel-readiness-slack-remote.test.ts +4 -0
  35. package/src/__tests__/config-schema.test.ts +12 -27
  36. package/src/__tests__/contact-store-interaction-info.test.ts +70 -27
  37. package/src/__tests__/contacts-relay-reads.test.ts +23 -188
  38. package/src/__tests__/contacts-tools.test.ts +1 -42
  39. package/src/__tests__/contacts-write.test.ts +0 -181
  40. package/src/__tests__/context-search-conversations-source.test.ts +0 -4
  41. package/src/__tests__/conversation-analysis-routes.test.ts +1 -1
  42. package/src/__tests__/conversation-disk-view.test.ts +0 -1
  43. package/src/__tests__/conversation-error.test.ts +0 -26
  44. package/src/__tests__/conversation-fork-crud.test.ts +0 -3
  45. package/src/__tests__/conversation-history-web-search.test.ts +0 -7
  46. package/src/__tests__/conversation-key-store-disk-view.test.ts +0 -80
  47. package/src/__tests__/conversation-queue.test.ts +0 -156
  48. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +3 -7
  49. package/src/__tests__/conversation-routes-guardian-reply.test.ts +0 -1
  50. package/src/__tests__/conversation-routes-slash-commands.test.ts +0 -1
  51. package/src/__tests__/conversation-runtime-assembly.test.ts +16 -59
  52. package/src/__tests__/credential-prompt-route.test.ts +0 -69
  53. package/src/__tests__/credential-security-invariants.test.ts +0 -1
  54. package/src/__tests__/deterministic-verification-control-plane.test.ts +62 -1
  55. package/src/__tests__/disk-pressure-tools.test.ts +0 -6
  56. package/src/__tests__/empty-response-hook.test.ts +0 -2
  57. package/src/__tests__/empty-state-greeting-cache.test.ts +0 -9
  58. package/src/__tests__/exploration-drift-hook.test.ts +0 -1
  59. package/src/__tests__/filing-service.test.ts +584 -0
  60. package/src/__tests__/gateway-only-enforcement.test.ts +110 -15
  61. package/src/__tests__/guardian-expiry-notifier.test.ts +0 -1
  62. package/src/__tests__/guardian-question-mode.test.ts +0 -57
  63. package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -1
  64. package/src/__tests__/helpers/seed-contact-channel.ts +2 -0
  65. package/src/__tests__/history-repair-hook.test.ts +0 -3
  66. package/src/__tests__/http-conversation-lineage.test.ts +1 -6
  67. package/src/__tests__/http-user-message-parity.test.ts +0 -1
  68. package/src/__tests__/image-recovery-hook.test.ts +0 -2
  69. package/src/__tests__/inbound-invite-redemption.test.ts +341 -72
  70. package/src/__tests__/injector-chain.test.ts +0 -4
  71. package/src/__tests__/invite-redemption-service.test.ts +1180 -0
  72. package/src/__tests__/invite-routes-http.test.ts +420 -246
  73. package/src/__tests__/invite-service-ipc.test.ts +350 -0
  74. package/src/__tests__/job-handler-registry-guard.test.ts +131 -0
  75. package/src/__tests__/lexical-index-dual-write.test.ts +0 -3
  76. package/src/__tests__/list-messages-attachments.test.ts +0 -1
  77. package/src/__tests__/list-messages-background-tool-completion.test.ts +6 -6
  78. package/src/__tests__/list-messages-queued.test.ts +0 -25
  79. package/src/__tests__/managed-profile-guard.test.ts +78 -567
  80. package/src/__tests__/max-tokens-continue-hook.test.ts +0 -1
  81. package/src/__tests__/mcp-config-secret-boundary.test.ts +0 -1
  82. package/src/__tests__/media-stream-output.test.ts +19 -350
  83. package/src/__tests__/media-stream-server-integration.test.ts +169 -943
  84. package/src/__tests__/media-stream-stt-session.test.ts +1 -498
  85. package/src/__tests__/memory-jobs-worker-lanes.test.ts +2 -2
  86. package/src/__tests__/memory-retrieval-hook.test.ts +0 -1
  87. package/src/__tests__/notification-decision-fallback.test.ts +8 -104
  88. package/src/__tests__/notification-decision-strategy.test.ts +8 -57
  89. package/src/__tests__/phone.test.ts +15 -1
  90. package/src/__tests__/plugin-bootstrap.test.ts +191 -6
  91. package/src/__tests__/plugin-disabled-state.test.ts +2 -12
  92. package/src/__tests__/plugin-effective-enabled-set.test.ts +62 -41
  93. package/src/__tests__/plugin-import-boundary-guard.test.ts +2 -8
  94. package/src/__tests__/plugin-pipeline.test.ts +2 -98
  95. package/src/__tests__/plugin-types.test.ts +1 -0
  96. package/src/__tests__/pre-model-call-sanitize.test.ts +1 -1
  97. package/src/__tests__/public-ingress-urls.test.ts +48 -0
  98. package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +1 -0
  99. package/src/__tests__/relay-server.test.ts +6149 -0
  100. package/src/__tests__/schedule-routes.test.ts +0 -6
  101. package/src/__tests__/secret-ingress-http.test.ts +0 -1
  102. package/src/__tests__/secret-routes-platform-proxy.test.ts +2 -5
  103. package/src/__tests__/slack-app-setup-skill-regression.test.ts +0 -24
  104. package/src/__tests__/slack-notification-approval-card.test.ts +2 -89
  105. package/src/__tests__/slack-reaction-canonical-approval.test.ts +1 -51
  106. package/src/__tests__/stt-hints.test.ts +256 -2
  107. package/src/__tests__/surface-completion-nudge-hook.test.ts +0 -2
  108. package/src/__tests__/task-progress-nudge-hook.test.ts +0 -1
  109. package/src/__tests__/telephony-stt-routing.test.ts +329 -0
  110. package/src/__tests__/title-generate-hook.test.ts +0 -18
  111. package/src/__tests__/tool-approval-handler.test.ts +1 -87
  112. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +20 -45
  113. package/src/__tests__/tool-error-hook.test.ts +0 -1
  114. package/src/__tests__/tool-grant-request-escalation.test.ts +0 -39
  115. package/src/__tests__/tool-result-truncate-hook.test.ts +0 -1
  116. package/src/__tests__/twilio-provider.test.ts +17 -17
  117. package/src/__tests__/twilio-routes-twiml.test.ts +543 -6
  118. package/src/__tests__/twilio-routes.test.ts +387 -257
  119. package/src/__tests__/twilio-validation.test.ts +1 -1
  120. package/src/__tests__/usage-cache-backfill-migration.test.ts +0 -5
  121. package/src/__tests__/voice-ingress-preflight.test.ts +0 -61
  122. package/src/__tests__/voice-invite-redemption.test.ts +702 -0
  123. package/src/__tests__/voice-quality.test.ts +53 -21
  124. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +0 -1
  125. package/src/acp/__tests__/session-manager-persistence.test.ts +0 -2
  126. package/src/acp/__tests__/session-manager-resume.test.ts +0 -8
  127. package/src/acp/__tests__/session-manager-usage.test.ts +0 -6
  128. package/src/acp/__tests__/session-manager.test.ts +0 -6
  129. package/src/acp/agent-process.ts +0 -71
  130. package/src/acp/session-manager.ts +46 -102
  131. package/src/agent/ax-tree-compaction.test.ts +1 -4
  132. package/src/agent/loop.ts +247 -39
  133. package/src/api/events/open-panel.ts +0 -7
  134. package/src/api/index.ts +0 -8
  135. package/src/api/responses/conversation-message.ts +6 -6
  136. package/src/approvals/guardian-decision-primitive.ts +24 -74
  137. package/src/approvals/guardian-request-resolvers.ts +123 -431
  138. package/src/apps/app-store.ts +0 -1
  139. package/src/apps/shared-app-links-store.ts +0 -1
  140. package/src/calls/__tests__/relay-setup-router.test.ts +102 -126
  141. package/src/calls/call-controller.ts +50 -97
  142. package/src/calls/call-domain.ts +42 -18
  143. package/src/calls/call-pointer-messages.ts +0 -18
  144. package/src/calls/call-speech-output.ts +24 -87
  145. package/src/calls/call-store.ts +0 -4
  146. package/src/calls/call-transport.ts +36 -22
  147. package/src/calls/fish-audio-client.ts +0 -3
  148. package/src/calls/media-stream-audio-transcode.ts +2 -82
  149. package/src/calls/media-stream-output.ts +36 -142
  150. package/src/calls/media-stream-server.ts +178 -297
  151. package/src/calls/media-stream-stt-session.ts +50 -393
  152. package/src/calls/media-turn-detector.ts +6 -4
  153. package/src/calls/relay-access-wait.ts +4 -3
  154. package/src/calls/relay-server.ts +2465 -0
  155. package/src/calls/relay-setup-router.ts +43 -22
  156. package/src/calls/relay-verification.ts +24 -17
  157. package/src/calls/resolve-call-tts-provider.ts +30 -97
  158. package/src/calls/stt-hints.ts +87 -24
  159. package/src/calls/telephony-stt-routing.ts +145 -0
  160. package/src/calls/tts-call-strategy.ts +93 -35
  161. package/src/calls/twilio-provider.ts +2 -3
  162. package/src/calls/twilio-routes.ts +351 -47
  163. package/src/calls/types.ts +3 -5
  164. package/src/calls/voice-ingress-preflight.ts +0 -9
  165. package/src/calls/voice-quality.ts +64 -36
  166. package/src/calls/voice-session-bridge.ts +4 -4
  167. package/src/channels/channel-verification-sessions.ts +1 -1
  168. package/src/channels/config.ts +42 -0
  169. package/src/cli/commands/contacts.ts +2 -2
  170. package/src/cli/commands/credentials.ts +0 -17
  171. package/src/cli/commands/keys.ts +83 -86
  172. package/src/cli/commands/monitoring.ts +1 -50
  173. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +1 -1
  174. package/src/config/bundled-skills/contacts/tools/contact-search.ts +1 -1
  175. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  176. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -9
  177. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +7 -10
  178. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +1 -1
  179. package/src/config/feature-flag-registry.json +2 -2
  180. package/src/config/loader.ts +2 -0
  181. package/src/config/schemas/__tests__/memory-v3.test.ts +0 -12
  182. package/src/config/schemas/calls.ts +0 -8
  183. package/src/config/schemas/elevenlabs.ts +1 -1
  184. package/src/config/schemas/llm.ts +9 -9
  185. package/src/config/schemas/memory-v3.ts +4 -10
  186. package/src/config/schemas/tts.ts +15 -5
  187. package/src/config/seed-inference-profiles.ts +19 -30
  188. package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +128 -0
  189. package/src/contacts/__tests__/member-write-relay.test.ts +5 -101
  190. package/src/contacts/canonical-guardian-store.ts +0 -5
  191. package/src/contacts/contact-store.ts +35 -81
  192. package/src/contacts/contacts-write.ts +25 -50
  193. package/src/contacts/member-write-relay.ts +31 -74
  194. package/src/contacts/types.ts +8 -0
  195. package/src/context/compactor.ts +15 -29
  196. package/src/credential-execution/executable-discovery.ts +4 -63
  197. package/src/credential-execution/process-manager.ts +7 -13
  198. package/src/daemon/app-source-watcher.ts +0 -9
  199. package/src/daemon/conversation-agent-loop.ts +21 -51
  200. package/src/daemon/conversation-error.ts +0 -43
  201. package/src/daemon/conversation-messaging.ts +1 -1
  202. package/src/daemon/conversation-process.ts +40 -134
  203. package/src/daemon/conversation-runtime-assembly.ts +40 -25
  204. package/src/daemon/conversation-surfaces.ts +8 -162
  205. package/src/daemon/conversation-tool-setup.test.ts +1 -51
  206. package/src/daemon/conversation-tool-setup.ts +19 -59
  207. package/src/daemon/conversation.ts +7 -32
  208. package/src/daemon/external-plugins-bootstrap.ts +60 -14
  209. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +1 -1
  210. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +14 -26
  211. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +14 -27
  212. package/src/daemon/handlers/__tests__/config-channels.test.ts +5 -0
  213. package/src/daemon/handlers/config-a2a.ts +14 -7
  214. package/src/daemon/handlers/config-channels.ts +10 -2
  215. package/src/daemon/handlers/shared.ts +24 -2
  216. package/src/daemon/host-cu-proxy.ts +1 -1
  217. package/src/daemon/lifecycle.ts +26 -5
  218. package/src/daemon/message-protocol.ts +0 -2
  219. package/src/daemon/message-types/contacts.ts +10 -2
  220. package/src/daemon/trust-context.ts +0 -7
  221. package/src/daemon/wake-conversation-ops.ts +8 -7
  222. package/src/documents/document-comments-store.test.ts +1 -7
  223. package/src/documents/document-comments-store.ts +0 -8
  224. package/src/documents/document-store.ts +0 -15
  225. package/src/home/conversation-starter-checkpoints.ts +0 -1
  226. package/src/home/job-handlers/conversation-starters.ts +0 -1
  227. package/src/hooks/hook-loader.ts +5 -31
  228. package/src/hooks/registry.ts +14 -48
  229. package/src/inbound/public-ingress-urls.ts +17 -0
  230. package/src/ipc/assistant-server.ts +7 -28
  231. package/src/ipc/routes/__tests__/invite-ipc-routes.test.ts +4 -60
  232. package/src/ipc/routes/db-proxy.ts +2 -5
  233. package/src/ipc/routes/invite-ipc-routes.ts +39 -53
  234. package/src/jobs/register-job-handlers.ts +101 -0
  235. package/src/jobs/worker.ts +2 -9
  236. package/src/live-voice/__tests__/live-voice-archive.test.ts +1 -10
  237. package/src/live-voice/__tests__/live-voice-tts.test.ts +10 -10
  238. package/src/live-voice/live-voice-archive.ts +0 -5
  239. package/src/live-voice/live-voice-tts.ts +1 -1
  240. package/src/messaging/providers/slack/send.test.ts +0 -79
  241. package/src/messaging/providers/slack/send.ts +12 -48
  242. package/src/messaging/providers/whatsapp/send.ts +9 -29
  243. package/src/monitoring/resource-sampler.ts +4 -79
  244. package/src/notifications/README.md +1 -1
  245. package/src/notifications/access-request-copy.ts +23 -94
  246. package/src/notifications/adapters/slack.ts +21 -89
  247. package/src/notifications/approval-card-builder.ts +18 -33
  248. package/src/notifications/approval-card-data.ts +15 -48
  249. package/src/notifications/broadcaster.ts +16 -25
  250. package/src/notifications/decision-engine.ts +32 -131
  251. package/src/notifications/guardian-question-mode.ts +10 -58
  252. package/src/permissions/approval-policy.ts +1 -8
  253. package/src/permissions/gateway-threshold-reader.ts +6 -7
  254. package/src/permissions/secret-prompter.ts +3 -11
  255. package/src/persistence/__tests__/conversation-queries-search.test.ts +0 -4
  256. package/src/persistence/__tests__/slow-query-log.test.ts +1 -77
  257. package/src/persistence/attachments-store.ts +2 -24
  258. package/src/persistence/conversation-crud.ts +8 -73
  259. package/src/persistence/conversation-display-order-migration.ts +2 -8
  260. package/src/persistence/conversation-group-migration.test.ts +0 -8
  261. package/src/persistence/conversation-group-migration.ts +0 -9
  262. package/src/persistence/conversation-key-store.ts +0 -5
  263. package/src/persistence/conversation-queries.ts +0 -4
  264. package/src/persistence/embeddings/embedding-local.ts +0 -2
  265. package/src/persistence/group-crud.ts +1 -13
  266. package/src/persistence/invite-store.ts +481 -0
  267. package/src/persistence/job-handlers/cleanup.ts +5 -27
  268. package/src/persistence/jobs-store.ts +2 -10
  269. package/src/persistence/jobs-worker.ts +0 -90
  270. package/src/persistence/llm-usage-store.ts +0 -11
  271. package/src/persistence/memory-lifecycle-hooks.test.ts +0 -3
  272. package/src/persistence/memory-lifecycle-hooks.ts +0 -12
  273. package/src/persistence/migrations/028-call-session-mode.ts +1 -1
  274. package/src/persistence/raw-query.ts +13 -49
  275. package/src/persistence/schema/a2a.ts +0 -14
  276. package/src/persistence/schema/contacts.ts +34 -0
  277. package/src/persistence/schema/guardian.ts +0 -4
  278. package/src/persistence/slow-query-log.ts +19 -195
  279. package/src/persistence/steps.ts +0 -8
  280. package/src/persistence/subagent-store.ts +2 -5
  281. package/src/plugin-api/index.ts +1 -5
  282. package/src/plugin-api/types.ts +510 -19
  283. package/src/plugins/defaults/default-plugin-names.ts +60 -0
  284. package/src/plugins/defaults/index.ts +31 -7
  285. package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +3 -34
  286. package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +2 -2
  287. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +0 -1
  288. package/src/plugins/defaults/memory/filing-service.ts +430 -0
  289. package/src/plugins/defaults/memory/graph/bootstrap.test.ts +10 -36
  290. package/src/plugins/defaults/memory/graph/bootstrap.ts +0 -3
  291. package/src/plugins/defaults/memory/hooks/init.ts +13 -28
  292. package/src/plugins/defaults/memory/hooks/shutdown.ts +14 -0
  293. package/src/plugins/defaults/memory/job-handlers.ts +6 -14
  294. package/src/plugins/defaults/memory/memory-v2-concept-frequency.ts +0 -2
  295. package/src/plugins/defaults/memory/persistence-hooks.ts +0 -5
  296. package/src/plugins/defaults/memory/rerank-local.ts +0 -2
  297. package/src/plugins/defaults/memory/startup.ts +10 -15
  298. package/src/plugins/defaults/memory/task-memory-cleanup.ts +0 -8
  299. package/src/plugins/defaults/memory/v3/__tests__/gate-flag.test.ts +4 -17
  300. package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +2 -114
  301. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +6 -25
  302. package/src/plugins/defaults/memory/v3/gate-flag.ts +4 -9
  303. package/src/plugins/defaults/memory/v3/gate.ts +3 -3
  304. package/src/plugins/defaults/memory/v3/orchestrate.ts +0 -32
  305. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -7
  306. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +0 -5
  307. package/src/plugins/injector-registry.ts +12 -6
  308. package/src/plugins/job-handler-registry.ts +94 -0
  309. package/src/plugins/mtime-cache.ts +4 -20
  310. package/src/plugins/pipeline.ts +21 -62
  311. package/src/plugins/types.ts +23 -19
  312. package/src/prompts/templates/SOUL.md +14 -40
  313. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +1 -96
  314. package/src/providers/provider-availability.ts +4 -11
  315. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +64 -0
  316. package/src/providers/speech-to-text/__tests__/resolve.test.ts +79 -92
  317. package/src/providers/speech-to-text/deepgram-realtime.test.ts +0 -98
  318. package/src/providers/speech-to-text/deepgram-realtime.ts +3 -62
  319. package/src/providers/speech-to-text/provider-catalog.ts +89 -0
  320. package/src/providers/speech-to-text/resolve.ts +0 -55
  321. package/src/runtime/AGENTS.md +2 -0
  322. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +8 -37
  323. package/src/runtime/access-request-helper.ts +1 -15
  324. package/src/runtime/assistant-stream-state.ts +25 -80
  325. package/src/runtime/auth/__tests__/route-policy.test.ts +0 -31
  326. package/src/runtime/background-job-runner.ts +1 -9
  327. package/src/runtime/channel-approval-types.ts +2 -52
  328. package/src/runtime/channel-approvals.ts +8 -23
  329. package/src/runtime/channel-invite-transport.ts +2 -2
  330. package/src/runtime/channel-invite-transports/voice.ts +1 -1
  331. package/src/runtime/channel-readiness-service.ts +5 -3
  332. package/src/runtime/channel-verification-service.ts +8 -0
  333. package/src/runtime/guardian-action-service.ts +11 -10
  334. package/src/runtime/guardian-reply-router.ts +21 -122
  335. package/src/runtime/http-server.ts +97 -32
  336. package/src/runtime/invite-redemption-service.ts +828 -0
  337. package/src/runtime/invite-redemption-templates.ts +46 -0
  338. package/src/runtime/invite-service.ts +361 -104
  339. package/src/runtime/middleware/twilio-validation.ts +7 -5
  340. package/src/runtime/pre-first-message-gate.ts +0 -1
  341. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +35 -6
  342. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +3 -24
  343. package/src/runtime/routes/__tests__/contact-routes.test.ts +15 -80
  344. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -7
  345. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +51 -137
  346. package/src/runtime/routes/__tests__/global-search-routes.test.ts +3 -0
  347. package/src/runtime/routes/__tests__/invite-relay-routes.test.ts +30 -119
  348. package/src/runtime/routes/__tests__/monitoring-routes.test.ts +1 -26
  349. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +0 -4
  350. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +1 -1
  351. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +1 -1
  352. package/src/runtime/routes/acp-routes.ts +3 -15
  353. package/src/runtime/routes/approval-routes.ts +0 -4
  354. package/src/runtime/routes/btw-routes.ts +4 -32
  355. package/src/runtime/routes/channel-route-shared.ts +14 -18
  356. package/src/runtime/routes/channel-verification-routes.ts +3 -0
  357. package/src/runtime/routes/contact-routes.ts +117 -298
  358. package/src/runtime/routes/conversation-list-routes.ts +0 -6
  359. package/src/runtime/routes/conversation-management-routes.ts +0 -71
  360. package/src/runtime/routes/conversation-query-routes.ts +55 -222
  361. package/src/runtime/routes/conversation-routes.ts +70 -123
  362. package/src/runtime/routes/credential-prompt-routes.ts +5 -20
  363. package/src/runtime/routes/debug-routes.ts +0 -1
  364. package/src/runtime/routes/documents-routes.ts +2 -5
  365. package/src/runtime/routes/empty-state-greeting-cache.ts +10 -37
  366. package/src/runtime/routes/filing-routes.ts +27 -50
  367. package/src/runtime/routes/global-search-routes.ts +0 -1
  368. package/src/runtime/routes/inbound-message-handler.ts +3 -3
  369. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +67 -87
  370. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +507 -48
  371. package/src/runtime/routes/inference-provider-connection-routes.ts +16 -22
  372. package/src/runtime/routes/internal-twilio-routes.ts +15 -0
  373. package/src/runtime/routes/monitoring-routes.ts +0 -32
  374. package/src/runtime/routes/secret-routes.ts +9 -14
  375. package/src/runtime/routes/surface-conversation-resolver.ts +0 -1
  376. package/src/runtime/services/__tests__/analyze-conversation.test.ts +9 -54
  377. package/src/runtime/services/__tests__/conversation-serializer.test.ts +0 -19
  378. package/src/runtime/services/analyze-conversation.ts +4 -14
  379. package/src/runtime/services/conversation-serializer.ts +0 -5
  380. package/src/runtime/slack-reply-session.test.ts +0 -81
  381. package/src/runtime/slack-reply-session.ts +0 -13
  382. package/src/runtime/sync/resource-sync-events.ts +0 -10
  383. package/src/runtime/trust-verdict-consumer.ts +6 -7
  384. package/src/schedule/schedule-usage-store.ts +0 -1
  385. package/src/stt/types.ts +9 -5
  386. package/src/tools/document/document-comment-tool.test.ts +0 -4
  387. package/src/tools/terminal/safe-env.ts +0 -2
  388. package/src/tools/tool-approval-handler.ts +29 -106
  389. package/src/tools/ui-surface/definitions.ts +1 -1
  390. package/src/tts/__tests__/provider-adapters.test.ts +65 -65
  391. package/src/tts/__tests__/provider-catalog-consistency.test.ts +5 -8
  392. package/src/tts/__tests__/provider-catalog.test.ts +0 -21
  393. package/src/tts/__tests__/provider-registry.test.ts +90 -0
  394. package/src/tts/provider-catalog.ts +232 -106
  395. package/src/tts/provider-registry.ts +73 -0
  396. package/src/tts/providers/deepgram-provider.ts +0 -41
  397. package/src/tts/providers/elevenlabs-provider.ts +0 -89
  398. package/src/tts/providers/fish-audio-provider.ts +10 -61
  399. package/src/tts/providers/index.ts +44 -0
  400. package/src/tts/providers/register-builtins.ts +130 -0
  401. package/src/tts/providers/xai-provider.ts +0 -41
  402. package/src/tts/synthesize-text.ts +1 -1
  403. package/src/tts/types.ts +16 -35
  404. package/src/util/cgroup-memory.ts +7 -128
  405. package/src/util/phone.ts +8 -1
  406. package/src/util/voice-code.ts +31 -0
  407. package/src/util/worker-process.ts +1 -5
  408. package/src/workflows/journal-store.ts +1 -16
  409. package/node_modules/@vellumai/gateway-client/src/__tests__/invite-contract.test.ts +0 -233
  410. package/node_modules/@vellumai/gateway-client/src/invite-contract.ts +0 -216
  411. package/src/__tests__/add-message-preassigned-id.test.ts +0 -56
  412. package/src/__tests__/call-setup-flow-invite.test.ts +0 -568
  413. package/src/__tests__/call-setup-flow-midcall-trust.test.ts +0 -231
  414. package/src/__tests__/call-setup-flow-name-capture.test.ts +0 -754
  415. package/src/__tests__/call-setup-flow-verification.test.ts +0 -925
  416. package/src/__tests__/call-setup-flow.test.ts +0 -311
  417. package/src/__tests__/channel-setup-panel-ack.test.ts +0 -184
  418. package/src/__tests__/compactor-media-strip.test.ts +0 -177
  419. package/src/__tests__/conversation-enabledplugins-route.test.ts +0 -184
  420. package/src/__tests__/conversation-routes-hidden-queue.test.ts +0 -408
  421. package/src/__tests__/guardian-wait-controller.test.ts +0 -628
  422. package/src/__tests__/hook-broadcast.test.ts +0 -188
  423. package/src/__tests__/introduction-card-resolver.test.ts +0 -354
  424. package/src/__tests__/invite-redeemed-ipc.test.ts +0 -97
  425. package/src/__tests__/job-handler-registration-guard.test.ts +0 -106
  426. package/src/__tests__/media-stream-audio-transcode.test.ts +0 -113
  427. package/src/__tests__/telephony-credential-preflight.test.ts +0 -312
  428. package/src/__tests__/telephony-tts-guard.test.ts +0 -531
  429. package/src/__tests__/voice-approval-resolver-relay.test.ts +0 -150
  430. package/src/acp/agent-process.test.ts +0 -116
  431. package/src/acp/failure-error.test.ts +0 -109
  432. package/src/acp/failure-error.ts +0 -111
  433. package/src/acp/session-manager.test.ts +0 -312
  434. package/src/api/events/hook-event.ts +0 -53
  435. package/src/calls/__tests__/fish-audio-client.test.ts +0 -73
  436. package/src/calls/__tests__/gateway-invite-reader.test.ts +0 -170
  437. package/src/calls/call-setup-flow-types.ts +0 -76
  438. package/src/calls/call-setup-flow.ts +0 -1743
  439. package/src/calls/gateway-invite-reader.ts +0 -98
  440. package/src/calls/guardian-wait-controller.ts +0 -451
  441. package/src/calls/telephony-credential-preflight.ts +0 -159
  442. package/src/calls/telephony-tts-capability.ts +0 -186
  443. package/src/context/outbound-sanitize.ts +0 -202
  444. package/src/daemon/conversation-plugin-scope.ts +0 -38
  445. package/src/daemon/sqlite-corruption-watchdog.test.ts +0 -217
  446. package/src/daemon/sqlite-corruption-watchdog.ts +0 -155
  447. package/src/hooks/hook-broadcast.ts +0 -60
  448. package/src/hooks/hook-logger.ts +0 -28
  449. package/src/hooks/types.ts +0 -613
  450. package/src/ipc/__tests__/contacts-info-ipc.test.ts +0 -285
  451. package/src/ipc/routes/__tests__/contacts-mirror-apply-atomicity.test.ts +0 -62
  452. package/src/ipc/routes/__tests__/contacts-mirror-apply.test.ts +0 -272
  453. package/src/ipc/routes/__tests__/contacts-mirror-ipc-routes.test.ts +0 -322
  454. package/src/ipc/routes/contacts-info-ipc-routes.ts +0 -239
  455. package/src/ipc/routes/contacts-mirror-ipc-routes.ts +0 -202
  456. package/src/monitoring/__tests__/resource-sampler.test.ts +0 -84
  457. package/src/persistence/a2a-invite-store.ts +0 -205
  458. package/src/persistence/migrations/315-create-a2a-invites.ts +0 -58
  459. package/src/persistence/migrations/316-drop-contact-channels-invite-id.ts +0 -28
  460. package/src/persistence/migrations/317-canonical-guardian-requester-signals.ts +0 -24
  461. package/src/persistence/migrations/318-drop-contact-channel-telemetry.ts +0 -32
  462. package/src/persistence/migrations/__tests__/315-create-a2a-invites.test.ts +0 -161
  463. package/src/persistence/migrations/__tests__/316-drop-contact-channels-invite-id.test.ts +0 -156
  464. package/src/persistence/migrations/__tests__/318-drop-contact-channel-telemetry.test.ts +0 -134
  465. package/src/plugins/defaults/memory/__tests__/filing-jobs.test.ts +0 -123
  466. package/src/plugins/defaults/memory/__tests__/jobs-worker-pkb-schedule.test.ts +0 -282
  467. package/src/plugins/defaults/memory/filing-jobs.ts +0 -133
  468. package/src/plugins/defaults/memory/job-handler-registration.ts +0 -96
  469. package/src/plugins/defaults/memory/pkb-schedule.ts +0 -31
  470. package/src/providers/vellum-model-routing.test.ts +0 -51
  471. package/src/providers/vellum-model-routing.ts +0 -84
  472. package/src/runtime/introduction-policy.test.ts +0 -214
  473. package/src/runtime/introduction-policy.ts +0 -284
  474. package/src/telemetry/watchdog-direct-emit.test.ts +0 -109
  475. package/src/telemetry/watchdog-direct-emit.ts +0 -89
  476. package/src/tts/__tests__/provider-resolution.test.ts +0 -83
  477. package/src/tts/provider-definition.ts +0 -202
  478. package/src/util/__tests__/cgroup-memory.test.ts +0 -104
  479. package/src/util/__tests__/text-spacing.test.ts +0 -53
  480. package/src/util/__tests__/worker-memory.test.ts +0 -64
  481. package/src/util/text-spacing.ts +0 -53
  482. package/src/util/worker-memory.ts +0 -77
package/ARCHITECTURE.md CHANGED
@@ -16,10 +16,10 @@ This document owns assistant-runtime architecture details. The repo-level archit
16
16
  - Guardian/non-guardian/unverified classification is centralized in `assistant/src/runtime/trust-context-resolver.ts`.
17
17
  - The same resolver is used by:
18
18
  - `/channels/inbound` (Telegram/WhatsApp path) before run orchestration.
19
- - Inbound Twilio voice setup (media-stream `routeSetup`) to seed call-time actor context.
19
+ - Inbound Twilio voice setup (`RelayConnection.handleSetup`) to seed call-time actor context.
20
20
  - Runtime channel runs pass this as `trustContext`, and conversation runtime assembly includes actor context in the unified `<turn_context>` block (via `buildUnifiedTurnContextBlock()`) injected into provider-facing prompts.
21
21
  - Voice calls mirror the same prompt contract: `CallController` receives guardian context on setup and refreshes it immediately after successful voice challenge verification, so the first post-verification turn is grounded as `actor_role: guardian`.
22
- - Voice-specific behavior (DTMF/speech verification flow, call-setup state machine) remains voice-local; only actor-role resolution is shared.
22
+ - Voice-specific behavior (DTMF/speech verification flow, relay state machine) remains voice-local; only actor-role resolution is shared.
23
23
 
24
24
  ### Safe Storage Limits
25
25
 
@@ -429,8 +429,9 @@ External users who are not the guardian can gain access to the assistant through
429
429
  | `src/runtime/routes/access-request-decision.ts` | Guardian decision → verification session creation |
430
430
  | `src/runtime/routes/guardian-approval-interception.ts` | Routes guardian decisions (button + conversational) to access request handler |
431
431
  | `src/runtime/channel-verification-service.ts` | Verification session lifecycle, identity binding, rate limiting |
432
- | `src/runtime/routes/contact-routes.ts` | HTTP/IPC handlers for contacts, channels, and invites (invite ops relay to gateway IPC) |
433
- | `src/runtime/invite-service.ts` | Daemon-owned invite presentation (share link, guardian instruction) over gateway-minted invites |
432
+ | `src/runtime/routes/contact-routes.ts` | HTTP API handlers for contact and channel management |
433
+ | `src/runtime/routes/invite-routes.ts` | HTTP API handlers for invite management |
434
+ | `src/runtime/invite-service.ts` | Business logic for invite operations |
434
435
  | `src/contacts/contact-store.ts` | Contact read queries — lookup, search, list, and channel operations |
435
436
  | `src/memory/guardian-approvals.ts` | Approval request persistence |
436
437
  | `src/channels/channel-verification-sessions.ts` | Verification challenge persistence |
@@ -454,12 +455,11 @@ A complementary access-granting flow where the guardian proactively creates a sh
454
455
  │ • extractInboundToken(payload) → token | undefined │
455
456
  │ Registered: Telegram │ Deferred: Slack, Voice │
456
457
  ├─────────────────────────────────────────────────────────────┤
457
- │ Core Redemption Engine (gateway-native)
458
- gateway/src/verification/invite-redemption.ts
459
- Token/code validation, expiry, use-count, channel-match
460
- │ enforcement, atomic claim, gateway ACL activation │
458
+ │ Core Redemption Engine (invite-redemption-service.ts)
459
+ Channel-agnostic token validation, expiry, use-count,
460
+ channel-match enforcement, contact activation/reactivation
461
461
  │ Returns: InviteRedemptionOutcome (discriminated union) │
462
- Daemon mirrors contact info via the invite_redeemed event
462
+ Reply templates: invite-redemption-templates.ts
463
463
  └─────────────────────────────────────────────────────────────┘
464
464
  ```
465
465
 
@@ -467,42 +467,42 @@ A complementary access-granting flow where the guardian proactively creates a sh
467
467
 
468
468
  1. Guardian asks the assistant to create an invite via desktop chat.
469
469
  2. `guardian-invite-intent.ts` detects the intent and rewrites the message to force-load the `contacts` skill.
470
- 3. The skill calls the invites HTTP API; the daemon relays the mint to the gateway (`invites_create` IPC), which writes the canonical `ingress_invites` row, then the daemon's Telegram transport adapter builds a deep link: `https://t.me/<bot>?start=iv_<token>`.
470
+ 3. The skill calls the ingress HTTP API to create an invite token, then calls the Telegram transport adapter to build a deep link: `https://t.me/<bot>?start=iv_<token>`.
471
471
  4. Guardian shares the link with the invitee out-of-band.
472
472
  5. Invitee clicks the link, opening Telegram which sends `/start iv_<token>` to the bot.
473
- 6. The gateway intercepts the `/start iv_<token>` message at ingress (before any daemon forwarding) and redeems the token against its canonical `ingress_invites` row.
474
- 7. The gateway's redemption engine validates, activates the channel in the gateway ACL, and notifies the daemon (`invite_redeemed`) to mirror the contact info locally.
473
+ 6. The gateway forwards the message to `/channels/inbound`. The inbound handler calls `getInviteAdapterRegistry().get('telegram').extractInboundToken()` to parse the `iv_` token.
474
+ 7. The token is redeemed via `invite-redemption-service.ts`, which validates, activates the contact, and returns a `redeemed` outcome.
475
475
  8. A deterministic welcome message is delivered to the invitee (bypasses the LLM pipeline).
476
476
 
477
477
  **Token prefix convention:** The `iv_` prefix distinguishes invite tokens from `gv_` (guardian verification) tokens. Both use the same Telegram `/start` deep-link mechanism but are routed to different handlers.
478
478
 
479
- **Inbound intercept points:** Invite token/code extraction runs at gateway ingress for non-member senders, before ACL denial, so valid invites short-circuit the contact check. The engine handles: (a) unknown contacts — the invite creates their first channel record; (b) inactive contacts (revoked) — the invite reactivates them; blocked channels are never reactivated.
479
+ **Inbound intercept points:** Invite token extraction runs early in the inbound handler, before ACL denial, so valid invites short-circuit the contact check. Two intercept branches handle: (a) unknown contacts — the invite creates their first contact record; (b) inactive contacts (revoked/pending) — the invite reactivates them.
480
480
 
481
481
  **Channel adapter status:**
482
482
 
483
483
  | Channel | Status | Prerequisites |
484
484
  | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
485
485
  | Telegram | Shipped | Bot username resolved from credential metadata or config |
486
- | Voice | Shipped | Identity-bound voice code redemption via DTMF/speech in the call setup flow. Always-on canonical behavior with personalized friend/guardian name prompts. |
486
+ | Voice | Shipped | Identity-bound voice code redemption via DTMF/speech in the relay state machine. Always-on canonical behavior with personalized friend/guardian name prompts. |
487
487
  | Slack | Deferred | Needs DM-safe ingress — Socket Mode handles channel messages but DM-initiated invite flows need routing |
488
488
 
489
- ### Voice Invite Flow (invite_redemption)
489
+ ### Voice Invite Flow (invite_redemption_pending)
490
490
 
491
- Voice invites use a short numeric code (6 digits) instead of a URL token. The guardian creates an invite bound to the invitee's E.164 phone number; the invitee redeems it by entering the code during an inbound voice call.
491
+ Voice invites use a short numeric code (4-10 digits, default 6) instead of a URL token. The guardian creates an invite bound to the invitee's E.164 phone number; the invitee redeems it by entering the code during an inbound voice call.
492
492
 
493
493
  **Creation flow:**
494
494
 
495
495
  1. Guardian creates a voice invite via `POST /v1/contacts/invites` with `sourceChannel: "phone"` and `expectedExternalUserId` (E.164 phone).
496
- 2. The daemon relays the mint to the gateway (`invites_create` IPC), which generates a cryptographically random numeric code and stores only its SHA-256 hash on the canonical `ingress_invites` row.
496
+ 2. `invite-service.ts` generates a cryptographically random numeric code (`generateVoiceCode`), hashes it with SHA-256 (`hashVoiceCode`), and stores only the hash.
497
497
  3. The one-time plaintext `voiceCode` is returned in the creation response. The raw token is NOT returned for voice invites — redemption uses the identity-bound code flow exclusively.
498
498
  4. Guardian communicates the code to the invitee out-of-band.
499
499
 
500
- **Call-time redemption subflow (`invite_redemption`):**
500
+ **Call-time redemption subflow (`invite_redemption_pending`):**
501
501
 
502
- 1. Unknown caller dials in. The media-stream server resolves trust (gateway trust verdict first, `resolveActorTrust` fallback inside `routeSetup`). Caller is `unknown`, no pending guardian challenge.
503
- 2. `routeSetup` asks the gateway for the active voice invite bound to the caller's phone number (`get_active_voice_invite` IPC via `calls/gateway-invite-reader.ts`; fail-soft — any gateway failure falls through to the unverified path).
504
- 3. If an active, non-expired invite exists, the `CallSetupFlow` runs the `invite_redemption` sub-flow (collecting the code via DTMF or spoken digits) and prompts the caller with personalized copy: `Welcome <friend-name>. Please enter the 6-digit code that <guardian-name> provided you to verify your identity.`
505
- 4. The gateway's `redeem_voice_invite` engine validates: identity match, code hash match, expiry, use count. On success, the phone channel is activated in the gateway ACL and the call transitions to the normal call flow.
502
+ 1. Unknown caller dials in. `relay-server.ts` resolves trust via `resolveActorTrust`. Caller is `unknown`, no pending guardian challenge.
503
+ 2. The relay checks `findActiveVoiceInvites` for invites bound to the caller's phone number.
504
+ 3. If active, non-expired invites exist, the relay enters the `invite_redemption_pending` state (reuses the `verification_pending` connection state) and prompts the caller with personalized copy: `Welcome <friend-name>. Please enter the 6-digit code that <guardian-name> provided you to verify your identity.`
505
+ 4. `redeemVoiceInviteCode` validates: identity match, code hash match, expiry, use count. On success, the contact is activated and the call transitions to the normal call flow.
506
506
  5. On invalid/expired code, the caller hears deterministic failure copy: `Sorry, the code you provided is incorrect or has since expired. Please ask <guardian-name> for a new code. Goodbye.` and the call ends immediately.
507
507
 
508
508
  **Security invariants:**
@@ -516,19 +516,22 @@ Voice invites use a short numeric code (6 digits) instead of a URL token. The gu
516
516
 
517
517
  | File | Purpose |
518
518
  | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
519
- | `gateway/src/verification/invite-redemption.ts` (gateway) | Core redemption engine + gateway-ingress token/code intercept — validation, atomic claim, ACL activation, discriminated-union outcomes |
519
+ | `src/runtime/invite-redemption-service.ts` | Core redemption engine token validation, voice code redemption, contact activation, discriminated-union outcomes |
520
+ | `src/runtime/invite-redemption-templates.ts` | Deterministic reply templates for each redemption outcome |
520
521
  | `src/runtime/channel-invite-transport.ts` | Transport adapter registry with `buildShareableInvite` / `extractInboundToken` interface |
521
522
  | `src/runtime/channel-invite-transports/telegram.ts` | Telegram adapter — `t.me/<bot>?start=iv_<token>` deep links, `/start iv_<token>` extraction |
522
523
  | `src/runtime/channel-invite-transports/voice.ts` | Voice transport adapter — code-based redemption metadata |
523
524
  | `src/daemon/guardian-invite-intent.ts` | Intent detection — routes create/list/revoke requests into the contacts skill |
524
- | `src/runtime/invite-service.ts` | Daemon-owned invite presentation (share link, guardian instruction, channel handle) over gateway-minted invites |
525
- | `src/runtime/routes/contact-routes.ts` | HTTP/IPC invite handlers relay mint/list/revoke/redeem to the gateway's invite IPC routes |
526
- | `src/calls/call-setup-flow.ts` | Transport-agnostic call setup flow `invite_redemption` sub-flow (always-on canonical behavior) |
527
- | `src/calls/gateway-invite-reader.ts` | Gateway IPC read of the active voice invite for a caller identity |
525
+ | `src/runtime/invite-service.ts` | Shared business logic for invite operations (used by HTTP routes) |
526
+ | `src/runtime/routes/invite-routes.ts` | HTTP API handlers for invite management including voice invite creation and redemption |
527
+ | `src/runtime/routes/inbound-message-handler.ts` | Invite token intercept in the inbound flow (unknown-contact and inactive-contact branches) |
528
+ | `src/calls/relay-server.ts` | Voice relay state machine `invite_redemption_pending` subflow (always-on canonical behavior) |
529
+ | `src/util/voice-code.ts` | Cryptographic voice code generation and SHA-256 hashing |
530
+ | `src/persistence/invite-store.ts` | Invite persistence including `findActiveVoiceInvites` for identity-bound lookup |
528
531
 
529
532
  ### Voice Inbound Security Model (Canonical)
530
533
 
531
- The voice inbound security model determines how unknown callers are handled when they dial in. Three paths exist, evaluated in priority order by `routeSetup` (`relay-setup-router.ts`) when the media-stream session starts. All guardian decisions route through `applyCanonicalGuardianDecision` in the canonical guardian request system.
534
+ The voice inbound security model determines how unknown callers are handled when they dial in. Three paths exist, evaluated in priority order by `relay-server.ts` during the `handleSetup` phase. All guardian decisions route through `applyCanonicalGuardianDecision` in the canonical guardian request system.
532
535
 
533
536
  **Decision tree for inbound unknown callers:**
534
537
 
@@ -547,7 +550,7 @@ resolveActorTrust() → trustClass
547
550
  |
548
551
  ┌────────────────────┼──────────────────────┐
549
552
  | | |
550
- pendingChallenge? activeVoiceInvite? no invite, no challenge
553
+ pendingChallenge? activeVoiceInvites? no invite, no challenge
551
554
  | | |
552
555
  v v v
553
556
  Guardian verification Invite redemption Name capture +
@@ -556,17 +559,17 @@ resolveActorTrust() → trustClass
556
559
 
557
560
  **Path 1: Voice invite code redemption (guardian-initiated)**
558
561
 
559
- The guardian proactively creates a voice invite bound to the caller's E.164 phone number. When the unknown caller dials in and has an active, non-expired invite, the setup flow enters the `invite_redemption` sub-flow with personalized prompts using the friend's and guardian's names. This is always-on canonical behavior (no feature flag). See [Voice Invite Flow](#voice-invite-flow-invite_redemption) above.
562
+ The guardian proactively creates a voice invite bound to the caller's E.164 phone number. When the unknown caller dials in and has an active, non-expired invite, the relay enters the `invite_redemption_pending` subflow with personalized prompts using the friend's and guardian's names. This is always-on canonical behavior (no feature flag). See [Voice Invite Flow](#voice-invite-flow-invite_redemption_pending) above.
560
563
 
561
564
  **Path 2: Live in-call guardian approval (friend-initiated)**
562
565
 
563
- When no invite exists and no pending guardian challenge is active, the setup flow enters the name capture + guardian approval wait flow:
566
+ When no invite exists and no pending guardian challenge is active, the relay enters the name capture + guardian approval wait flow:
564
567
 
565
- 1. The setup flow transitions to `capturing_name` state and prompts the caller for their name with a timeout.
568
+ 1. The relay transitions to `awaiting_name` state and prompts the caller for their name with a timeout.
566
569
  2. On name capture, `notifyGuardianOfAccessRequest` creates a canonical guardian request (`kind: 'access_request'`) and notifies the guardian via the notification pipeline.
567
- 3. The setup flow hands off to a `GuardianWaitController` (`awaiting_guardian_decision`), which speaks hold messaging and heartbeat updates while polling the canonical request status.
570
+ 3. The relay transitions to `awaiting_guardian_decision` and plays hold music/messaging while polling the canonical request status.
568
571
  4. The guardian approves or denies via any channel (Telegram, desktop). All decisions route through `applyCanonicalGuardianDecision`, which dispatches to the `access_request` resolver in `guardian-request-resolvers.ts`.
569
- 5. On approval: the resolver directly activates the caller as a trusted contact (sets channel `status: 'active'`, `policy: 'allow'`), the poll detects the approved status, and the setup flow completes into the normal call flow with the caller's guardian context updated.
572
+ 5. On approval: the resolver directly activates the caller as a trusted contact (sets channel `status: 'active'`, `policy: 'allow'`), the poll detects the approved status, the relay transitions to the normal call flow with the caller's guardian context updated.
570
573
  6. On denial or timeout: the caller hears a denial message and the call ends.
571
574
 
572
575
  **Path 3: Inbound guardian verification (pending challenge)**
@@ -586,9 +589,7 @@ All guardian decisions for voice access requests flow through:
586
589
 
587
590
  | File | Purpose |
588
591
  | ---------------------------------------------- | -------------------------------------------------------------------------------------- |
589
- | `src/calls/relay-setup-router.ts` | Inbound call decision tree (`routeSetup`) |
590
- | `src/calls/call-setup-flow.ts` | Name capture, verification, and invite sub-flows over the media-stream transport |
591
- | `src/calls/guardian-wait-controller.ts` | Guardian approval wait — hold messaging, heartbeats, poll/timeout |
592
+ | `src/calls/relay-server.ts` | Inbound call decision tree, name capture, guardian approval wait polling |
592
593
  | `src/runtime/access-request-helper.ts` | Creates canonical access request and notifies guardian |
593
594
  | `src/approvals/guardian-decision-primitive.ts` | `applyCanonicalGuardianDecision` — unified decision primitive |
594
595
  | `src/approvals/guardian-request-resolvers.ts` | `access_request` resolver — voice direct activation, text-channel verification session |
@@ -605,41 +606,37 @@ Audio-to-text conversion occurs in six distinct runtime boundaries, each with it
605
606
 
606
607
  | Boundary | Runtime | Provider (current) | Adapter module | Caller |
607
608
  | -------------------------- | ----------------------------------------------------------------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
608
- | **Telephony (media-stream)** | Daemon process (Twilio Media Streams WebSocket) | Configured STT provider (via `services.stt`) | `src/calls/media-stream-stt-session.ts`, `src/providers/speech-to-text/resolve.ts`, streaming provider adapters | `src/calls/media-stream-server.ts` |
609
+ | **Telephony (hybrid)** | Twilio-native ConversationRelay or daemon media-stream (provider-conditional) | Configured STT provider (via `services.stt`) | `src/calls/telephony-stt-routing.ts` | `src/calls/twilio-routes.ts` |
609
610
  | **Daemon batch** | Daemon process (REST API to provider) | Configured STT provider (via `services.stt`) | `src/stt/daemon-batch-transcriber.ts` | `src/runtime/routes/inbound-stages/transcribe-audio.ts` |
610
611
  | **Conversation streaming** | Daemon process (WebSocket-based) | Configured STT provider (via `services.stt`) | `src/stt/stt-stream-session.ts`, `src/providers/speech-to-text/deepgram-realtime.ts`, `src/providers/speech-to-text/google-gemini-live-stream.ts`, `src/providers/speech-to-text/openai-whisper-stream.ts`, `src/providers/speech-to-text/xai-realtime.ts` | Web/Electron dictation client via gateway WS proxy |
611
612
  | **Live voice channel** | Assistant process (gateway-authenticated WebSocket) | Configured STT provider (via `services.stt`) | `src/runtime/http-server.ts`, `src/live-voice/live-voice-session-manager.ts`, `src/live-voice/live-voice-session.ts`, `src/providers/speech-to-text/resolve.ts`, streaming provider adapters | Web/Electron live voice client via `/v1/live-voice` |
612
613
  | **Client service-first** | Web/Electron via gateway → daemon | Configured STT provider (via `services.stt`) | `src/runtime/routes/stt-routes.ts` | Web/Electron dictation and voice clients |
613
614
 
614
- **Telephony boundary (media-stream):**
615
+ **Telephony boundary (hybrid routing):**
615
616
 
616
- Every phone call connects over Twilio Media Streams: the voice webhook emits `<Connect><Stream>` TwiML pointing to the gateway's media-stream proxy, and the daemon performs both STT and TTS itself — Twilio only carries mu-law audio frames. The `<Stream url="...">` encodes `callSessionId` and auth `token` as **URL path segments** (e.g. `.../media-stream/<callSessionId>/<token>`) because Twilio Media Streams does not reliably preserve query parameters across the WebSocket upgrade. The gateway extracts metadata from path segments (with query-parameter fallback for backward compatibility) and proxies raw audio frames to the daemon.
617
+ Telephony STT uses a provider-conditional hybrid model driven by `services.stt.provider`. The routing resolver (`src/calls/telephony-stt-routing.ts`) maps the configured provider to a discriminated strategy at call setup time:
617
618
 
618
- Transcription mode is selected once per session in `media-stream-stt-session.ts`:
619
+ - **`conversation-relay-native`** (Deepgram, Google) TwiML emits `<Connect><ConversationRelay>` with `transcriptionProvider` and `speechModel` attributes. Twilio handles audio ingestion and transcription natively; the daemon receives transcribed text via the relay WebSocket. The Twilio-native provider name and default speech model are read from the provider catalog entry's `telephonyRouting.twilioNativeMapping` (e.g. Deepgram maps to `provider: "Deepgram"` with `defaultSpeechModel: "nova-3"`; Google maps to `provider: "Google"` with `defaultSpeechModel: undefined`).
619
620
 
620
- - **Streaming** (default) — when `calls.voice.telephonyStreaming` is enabled and the configured provider resolves a streaming transcriber (`resolveStreamingTranscriber()`), inbound audio is decoded (mu-law PCM16, resampled 8 kHz 16 kHz) and fed to the provider's realtime adapter. Replies trigger only on utterance-boundary finals (for Deepgram, `speech_final`/`UtteranceEnd`, never mid-sentence `is_final` segments), and barge-in fires from local energy VAD, never from transcriber partials.
621
- - **Batch fallback** — otherwise the session segments turns with the energy-based `MediaTurnDetector` and transcribes each completed turn via the provider's batch API.
622
-
623
- A credential preflight (`resolveTelephonyCredentialReadiness()` in `src/calls/telephony-credential-preflight.ts`) gates every call: it requires a credentialed, telephony-capable STT provider **and** a media-stream-playable TTS provider (the configured one or a credentialed playable fallback). Inbound calls that fail the preflight receive `<Say>` setup-required copy plus `<Hangup/>` instead of a doomed stream; outbound placement fails before dialing via `preflightVoiceIngress()` with the same user-facing message.
621
+ - **`media-stream-custom`** (OpenAI Whisper) — TwiML emits `<Connect><Stream>` pointing to the gateway's media-stream proxy. The `<Stream url="...">` encodes `callSessionId` and auth `token` as **URL path segments** (e.g. `.../media-stream/<callSessionId>/<token>`) because Twilio Media Streams does not reliably preserve query parameters across the WebSocket upgrade. The gateway extracts metadata from path segments (with query-parameter fallback for backward compatibility) and proxies raw audio frames to the daemon, which transcribes server-side via the provider's batch API.
624
622
 
625
623
  Key modules:
626
624
 
627
- | Module | Purpose |
628
- | --------------------------------------------------- | -------------------------------------------------------------------------------- |
629
- | `src/calls/twilio-routes.ts` | Voice webhook handler; generates `<Connect><Stream>` TwiML, enforces the inbound credential preflight |
630
- | `src/calls/telephony-credential-preflight.ts` | Combined STT + TTS credential-readiness resolver |
631
- | `src/calls/media-stream-parser.ts` | Twilio Media Streams protocol parser |
632
- | `src/calls/media-turn-detector.ts` | Energy-based VAD turn detector for raw audio (batch mode) |
633
- | `src/calls/media-stream-stt-session.ts` | STT session streaming/batch mode selection and transcription via `services.stt` |
634
- | `src/calls/media-stream-audio-transcode.ts` | Mu-law PCM16 codecs and resampling |
635
- | `src/calls/call-transport.ts` | Transport interface decoupling CallController from wire protocol |
636
- | `src/calls/media-stream-output.ts` | Output adapter for sending TTS audio back via Media Streams |
637
- | `src/calls/media-stream-server.ts` | WebSocket server binding media-stream lifecycle to call sessions and setup flows |
638
- | `gateway/src/http/routes/twilio-media-websocket.ts` | Gateway WebSocket proxy for Media Streams frames |
625
+ | Module | Purpose |
626
+ | --------------------------------------------------- | ---------------------------------------------------------------------- |
627
+ | `src/calls/telephony-stt-routing.ts` | Maps `services.stt.provider` to a discriminated `TelephonySttStrategy` |
628
+ | `src/calls/twilio-routes.ts` | Voice webhook handler; generates provider-conditional TwiML |
629
+ | `src/calls/media-stream-parser.ts` | Twilio Media Streams protocol parser |
630
+ | `src/calls/media-turn-detector.ts` | Energy-based VAD turn detector for raw audio |
631
+ | `src/calls/media-stream-stt-session.ts` | STT session that transcribes audio turns via `services.stt` |
632
+ | `src/calls/call-transport.ts` | Transport interface decoupling CallController from wire protocol |
633
+ | `src/calls/media-stream-output.ts` | Output adapter for sending TTS audio back via Media Streams |
634
+ | `src/calls/media-stream-server.ts` | WebSocket server binding media-stream lifecycle to call sessions |
635
+ | `gateway/src/http/routes/twilio-media-websocket.ts` | Gateway WebSocket proxy for Media Streams frames |
639
636
 
640
- Guard tests in `__tests__/twilio-routes-twiml.test.ts` and `__tests__/twilio-routes.test.ts` assert that every supported STT provider yields `<Connect><Stream>` TwiML for both inbound and outbound calls.
637
+ Guard tests in `__tests__/twilio-routes-twiml.test.ts` and `__tests__/twilio-routes.test.ts` assert that TwiML generation matches the provider-conditional strategy for each supported provider.
641
638
 
642
- To add a new telephony STT provider: declare `telephonyMode` and `supportedBoundaries` on the provider's catalog entry in `provider-catalog.ts`. The telephony capability resolver (`resolveTelephonySttCapability()`) reads these fields plus credential availability from the catalog — no per-provider routing maps exist.
639
+ To add a new telephony STT provider: add a `telephonyRouting` entry to the provider's catalog entry in `provider-catalog.ts`. Set `strategyKind` to `"conversation-relay-native"` for Twilio-native providers (and include a `twilioNativeMapping` with the Twilio `provider` name and `defaultSpeechModel`), or `"media-stream-custom"` for providers that require daemon-side transcription. The routing resolver reads these fields from the catalog — no hardcoded maps to update.
643
640
 
644
641
  **Daemon batch boundary:**
645
642
 
@@ -750,7 +747,7 @@ The web client implements these flows in `clients/web/src/domains/chat/voice/`:
750
747
 
751
748
  **Cross-boundary notes:**
752
749
 
753
- - The `services.stt` config block is the single source of truth for STT provider selection across the daemon batch boundary, the conversation streaming boundary, the client service-first boundary, and the telephony boundary. The batch and streaming resolvers (`resolveBatchTranscriber()`, `resolveStreamingTranscriber()`) both read from `services.stt.provider` and resolve credentials through the same catalog; the telephony boundary's media-stream STT session uses these same resolvers — it selects streaming vs batch transcription via `resolveStreamingTranscriber()`/`resolveBatchTranscriber()` under the `calls.voice.telephonyStreaming` flag, gated by `resolveTelephonySttCapability()`, while call placement/TwiML is gated by `resolveTelephonyCredentialReadiness()`. The daemon provider catalog (`src/providers/speech-to-text/provider-catalog.ts`) is the authoritative registry of supported providers. Native clients fetch display metadata via `GET /v1/stt/providers`.
750
+ - The `services.stt` config block is the single source of truth for STT provider selection across the daemon batch boundary, the conversation streaming boundary, the client service-first boundary, and the telephony boundary. The batch and streaming resolvers (`resolveBatchTranscriber()`, `resolveStreamingTranscriber()`) both read from `services.stt.provider` and resolve credentials through the same catalog; the telephony boundary uses `resolveTelephonySttRouting()` to determine the Twilio integration strategy. The daemon provider catalog (`src/providers/speech-to-text/provider-catalog.ts`) is the authoritative registry of supported providers. Native clients fetch display metadata via `GET /v1/stt/providers`.
754
751
  - Conversation streaming does not replace the client service-first batch path. When streaming is available, it runs concurrently during recording and provides real-time partials and finals. The batch path remains the fallback for providers that do not support streaming, when streaming fails mid-session, or when streaming produces no final transcript.
755
752
  - Credential mapping is catalog-driven: `provider-secret-catalog.ts` derives STT API-key provider names from the daemon catalog via `listCredentialProviderNames()`, deduplicating against the LLM/search provider list. Adding a provider to the catalog automatically includes its credential name in `API_KEY_PROVIDERS`.
756
753
  - Terminology: "STT", "transcription", and "speech recognition" all refer to the same operation (converting audio to text).
@@ -2204,12 +2201,12 @@ The `TtsUseCase` discriminator (`"phone-call"` or `"message-playback"`) lets pro
2204
2201
 
2205
2202
  **Synthesis orchestrator (`synthesize-text.ts`):** `synthesizeText()` is the top-level entry point. It resolves the globally configured provider via the config resolver, looks up the adapter in the registry, and delegates synthesis. Provider selection is always global — per-use-case policy only gates capabilities (e.g. format checks), never overrides the chosen provider.
2206
2203
 
2207
- **Call strategy abstraction (`tts-call-strategy.ts`):** The call strategy layer determines how a TTS provider integrates with the telephony path. Instead of inferring call behavior from runtime capabilities, `resolveCallStrategy(config)` reads the provider's `callMode` from the canonical catalog and returns a `TtsCallStrategy` with the provider ID and call mode. Two modes exist:
2204
+ **Call strategy abstraction (`tts-call-strategy.ts`):** The call strategy layer determines how a TTS provider integrates with the Twilio ConversationRelay telephony path. Instead of inferring call behavior from runtime capabilities, `resolveCallStrategy(config)` reads the provider's `callMode` from the canonical catalog and returns a `TtsCallStrategy` with the provider ID and call mode. Two modes exist:
2208
2205
 
2209
- - **`native-twilio`** — the text-token path: spoken text is sent via `sendTextToken()`, which the media-stream transport re-synthesizes through daemon TTS. The profile carries a real `ttsProvider` name (e.g. `"ElevenLabs"`) and a provider-specific voice spec string built by a registered `NativeTwilioVoiceSpecBuilder`. Collapsing this mode is a documented deferred follow-up.
2210
- - **`synthesized-play`** — The assistant synthesises audio via the provider's HTTP API and streams it through the audio store / `sendPlayUrl()` path. Uses a placeholder TTS provider (`"Google"`) and an empty voice string.
2206
+ - **`native-twilio`** — Twilio handles TTS natively via ConversationRelay. The profile needs a real `ttsProvider` name (e.g. `"ElevenLabs"`) and a provider-specific voice spec string. New native providers plug in by registering a `NativeTwilioVoiceSpecBuilder` via `registerNativeTwilioVoiceSpec()` no edits to core call routing logic required.
2207
+ - **`synthesized-play`** — The assistant synthesises audio via the provider's HTTP API and streams chunks to Twilio via `play` messages. Uses a placeholder TTS provider (`"Google"`) and an empty voice string because Twilio never drives TTS itself on this path.
2211
2208
 
2212
- **Phone call integration:** Phone calls run on the media-stream transport, where the daemon synthesises speech via the configured TTS provider and transcodes it to mu-law 8 kHz frames (`media-stream-output.ts`). Each catalog entry declares `mediaStreamPlayback.outputFormat` (`pcm`, `wav`, or `none`); `resolveTelephonyTtsCapability()` (`src/calls/telephony-tts-capability.ts`) combines that field with credential availability into a playable / not-playable verdict, and the call TTS resolver falls back to a credentialed playable provider rather than producing silence. `resolveVoiceQualityProfile()` in `voice-quality.ts` uses `resolveCallStrategy()` to determine the call mode and build the voice profile.
2209
+ **Phone call integration:** `resolveVoiceQualityProfile()` in `voice-quality.ts` uses `resolveCallStrategy()` to determine the call mode, then dispatches to the appropriate path. For `native-twilio`, it looks up the registered `NativeTwilioVoiceSpec` to build the voice string. For `synthesized-play`, it uses the placeholder profile. This replaces the previous `supportsStreaming`-based branching with explicit catalog-declared modes.
2213
2210
 
2214
2211
  **Adding a new TTS provider (catalog-first checklist):**
2215
2212
 
package/README.md CHANGED
@@ -174,7 +174,7 @@ Guardian actor-role _classification_ (determining whether a sender is guardian,
174
174
 
175
175
  ### Ingress Boundary Guarantees (Gateway-Only Mode)
176
176
 
177
- The runtime operates in **gateway-only mode**: all public-facing webhook paths are blocked at the runtime level. Direct access to Twilio webhook routes (`/webhooks/twilio/voice`, `/webhooks/twilio/status`) and their legacy equivalents (`/v1/calls/twilio/*`) returns `410 GATEWAY_ONLY`. This ensures external webhook traffic can only reach the runtime through the gateway, which performs signature validation before forwarding.
177
+ The runtime operates in **gateway-only mode**: all public-facing webhook paths are blocked at the runtime level. Direct access to Twilio webhook routes (`/webhooks/twilio/voice`, `/webhooks/twilio/status`, `/webhooks/twilio/connect-action`) and their legacy equivalents (`/v1/calls/twilio/*`) returns `410 GATEWAY_ONLY`. This ensures external webhook traffic can only reach the runtime through the gateway, which performs signature validation before forwarding.
178
178
 
179
179
  Internal forwarding routes (`/v1/internal/twilio/*`) are unaffected — these accept pre-validated payloads from the gateway over the private network.
180
180
 
@@ -309,6 +309,7 @@ Guardian verification and ingress contact management are complementary but indep
309
309
  | `src/runtime/trust-context-resolver.ts` | Actor role classification: guardian / non-guardian / unverified_channel |
310
310
  | `src/runtime/routes/inbound-message-handler.ts` | Ingress ACL enforcement, verification-code intercept, escalation creation |
311
311
  | `src/contacts/contact-store.ts` | Contact + channel CRUD: `findContactChannel`, `upsertContact`, `updateChannelStatus`, `searchContacts` |
312
+ | `src/memory/invite-store.ts` | Invite lifecycle: `createInvite`, `redeemInvite` (atomically creates member record) |
312
313
  | `src/memory/channel-verification-sessions.ts` | Guardian binding types and verification challenge persistence |
313
314
  | `src/memory/guardian-approvals.ts` | Approval request persistence |
314
315
  | `src/runtime/verification-outbound-actions.ts` | Shared business logic for outbound verification (start/resend/cancel) |
@@ -382,7 +383,7 @@ External users join through **invite tokens**. There are two invite flows:
382
383
  1. **Guardian requests invite** — The guardian asks the assistant (via desktop chat) to create a Telegram invite link. The `guardian-invite-intent.ts` module detects the intent and routes the request into the `contacts` skill.
383
384
  2. **Invite creation** — The skill creates an invite token via the ingress HTTP API, looks up the Telegram bot username from the integration config endpoint, and constructs a shareable deep link: `https://t.me/<bot>?start=iv_<token>`.
384
385
  3. **Guardian shares link** — The guardian copies the deep link and shares it with the invitee through any messaging channel.
385
- 4. **Invitee redeems** — The invitee clicks the link, which opens Telegram and sends `/start iv_<token>` to the bot. The gateway intercepts the token at ingress, redeems it against its canonical invite row, activates the member channel in the gateway ACL, and notifies the daemon to mirror the contact info.
386
+ 4. **Invitee redeems** — The invitee clicks the link, which opens Telegram and sends `/start iv_<token>` to the bot. The inbound message handler extracts the token via the transport adapter, redeems it through the invite redemption service, and auto-creates an active member record.
386
387
  5. **Access granted** — The invitee receives a welcome message and all subsequent messages pass the ingress ACL.
387
388
 
388
389
  The `iv_` prefix distinguishes invite tokens from `gv_` (guardian verification) tokens, which use the same Telegram `/start` deep-link mechanism.
@@ -391,7 +392,7 @@ The `iv_` prefix distinguishes invite tokens from `gv_` (guardian verification)
391
392
 
392
393
  The invite redemption system uses a three-layer architecture:
393
394
 
394
- - **Core redemption engine** (gateway-native, `gateway/src/verification/invite-redemption.ts`) — Channel-agnostic business logic that validates tokens and codes, enforces expiry/use-count/channel-match constraints, handles member reactivation, and returns a discriminated-union `InviteRedemptionOutcome`. Deterministic reply templates map each outcome to a user-facing message without passing through the LLM.
395
+ - **Core redemption engine** (`invite-redemption-service.ts`) — Channel-agnostic business logic that validates tokens, enforces expiry/use-count/channel-match constraints, handles member reactivation, and returns a discriminated-union `InviteRedemptionOutcome`. Deterministic reply templates (`invite-redemption-templates.ts`) map each outcome to a user-facing message without passing through the LLM.
395
396
  - **Channel transport adapters** (`channel-invite-transport.ts` + `channel-invite-transports/`) — A registry of per-channel adapters that know how to build shareable links (`buildShareLink`) and extract inbound tokens (`extractInboundToken`). Adapters are implemented for Telegram, Voice, Email, WhatsApp, and Slack.
396
397
  - **Conversational orchestration** (`guardian-invite-intent.ts`) — Pattern-based intent detection that intercepts guardian invite management requests (create, list, revoke) in the session pipeline and forces immediate entry into the `contacts` skill, bypassing the normal agent loop.
397
398
 
@@ -421,14 +422,16 @@ If no guardian binding exists, escalation fails closed — the message is denied
421
422
 
422
423
  | File | Purpose |
423
424
  | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
425
+ | `src/memory/invite-store.ts` | CRUD for invite tokens with SHA-256 hashing and expiry |
424
426
  | `src/contacts/contact-store.ts` | Contact + channel CRUD with policy enforcement |
425
- | `src/runtime/routes/contact-routes.ts` | HTTP/IPC invite handlers relay mint/list/revoke/redeem to the gateway's invite IPC routes |
426
- | `src/runtime/routes/inbound-message-handler.ts` | ACL enforcement point — member lookup, policy check, escalation creation |
427
- | `gateway/src/verification/invite-redemption.ts` (gateway) | Core redemption engine — token/code validation, atomic claim, ACL activation, discriminated-union outcomes |
427
+ | `src/daemon/handlers/config-inbox.ts` | HTTP handlers for invite operations |
428
+ | `src/runtime/routes/channel-routes.ts` | ACL enforcement point — member lookup, policy check, escalation creation |
429
+ | `src/runtime/invite-redemption-service.ts` | Core redemption engine — token validation, member creation, discriminated-union outcomes |
430
+ | `src/runtime/invite-redemption-templates.ts` | Deterministic reply templates for each redemption outcome |
428
431
  | `src/runtime/channel-invite-transport.ts` | Transport adapter registry — `buildShareableInvite` / `extractInboundToken` per channel |
429
432
  | `src/runtime/channel-invite-transports/telegram.ts` | Telegram adapter — builds `t.me/<bot>?start=iv_<token>` deep links, extracts `iv_` tokens from `/start` commands |
430
433
  | `src/daemon/guardian-invite-intent.ts` | Intent detection — routes guardian invite management requests into the `contacts` skill |
431
- | `src/runtime/invite-service.ts` | Daemon-owned invite presentation (share link, guardian instruction) over gateway-minted invites |
434
+ | `src/runtime/invite-service.ts` | Shared business logic for invite and contact operations |
432
435
 
433
436
  ## Database
434
437
 
@@ -12,7 +12,8 @@ Add a new entry to the `CATALOG` map with:
12
12
  - `credentialProvider` — the credential-store key name used by `getProviderKeyAsync` to retrieve the API key. If the provider shares an API key with another service (e.g. `openai-whisper` shares the `"openai"` key, or `google-gemini` shares the `"gemini"` key), reuse that name; otherwise use the provider's own name (e.g. `"deepgram"` maps to `"deepgram"`).
13
13
  - `supportedBoundaries` — the set of `SttBoundaryId` values the provider supports. Valid values are `"daemon-batch"` (post-recording transcription) and `"daemon-streaming"` (real-time streaming transcription during conversation).
14
14
  - `conversationStreamingMode` — how the provider handles streaming transcription in conversation mode: `"realtime-ws"` (provider supports real-time streaming natively via WebSocket), `"incremental-batch"` (streaming emulated via throttled polling), or `"none"` (no streaming support). Required for all providers.
15
- - `telephonyMode` — how the provider participates in real-time telephony STT: `"realtime-ws"`, `"batch-only"`, or `"none"`. The telephony capability resolver (`resolveTelephonySttCapability()` in `src/providers/speech-to-text/resolve.ts`) reads this field plus credential availability to decide whether phone calls can run with the provider.
15
+ - `telephonyMode` — how the provider participates in real-time telephony STT: `"realtime-ws"`, `"batch-only"`, or `"none"`.
16
+ - `telephonyRouting` — telephony routing metadata that drives Twilio call setup strategy selection. Declare `strategyKind` as `"conversation-relay-native"` or `"media-stream-custom"`. For native providers, include `twilioNativeMapping` with the Twilio `provider` name and `defaultSpeechModel`.
16
17
 
17
18
  ## 2. Type-system registration
18
19
 
@@ -83,8 +84,8 @@ Clients derive shared-vs-exclusive key behavior from the catalog automatically:
83
84
 
84
85
  Before submitting the PR, verify that:
85
86
 
86
- 1. **No stale config references** — grep for any references to a separate telephony transcription config. Telephony transcription runs daemon-side over the Twilio media-stream transport (`src/calls/media-stream-stt-session.ts`), which reads `services.stt.provider` like every other boundary.
87
+ 1. **No stale config references** — grep for any references to a separate telephony transcription config. The telephony routing module (`src/calls/telephony-stt-routing.ts`) reads `services.stt.provider` and maps it to the appropriate Twilio strategy (either `conversation-relay-native` for providers Twilio supports natively, or `media-stream-custom` for server-side transcription).
87
88
 
88
- 2. **Provider catalog telephony metadata** — the new provider's catalog entry (step 1) declares `telephonyMode` and `supportedBoundaries`; these are the single source of truth for the telephony capability check (`resolveTelephonySttCapability()`) and for streaming-vs-batch mode selection in the media-stream STT session. No per-provider routing maps exist.
89
+ 2. **Provider catalog `telephonyRouting` metadata** — the new provider's catalog entry (step 1) includes a `telephonyRouting` object that is the single source of truth for strategy selection in `telephony-stt-routing.ts`. This object declares the `strategyKind` (`"conversation-relay-native"` or `"media-stream-custom"`) and, for native providers, provides a `twilioNativeMapping` with the Twilio `provider` name and `defaultSpeechModel`. The routing module contains no hardcoded provider-to-Twilio maps — it reads these values directly from the catalog.
89
90
 
90
- 3. **No duplicate wiring** — a provider should appear only once in `services.stt`. The telephony layer consumes the same provider ID; there is no second registration step for telephony.
91
+ 3. **No duplicate wiring** — a provider should appear only once in `services.stt`. The telephony routing layer consumes the same provider ID; there is no second registration step for telephony.
@@ -114,11 +114,11 @@ No member record is created. No verification session is created.
114
114
 
115
115
  ### Invites (alternative path)
116
116
 
117
- Invites are gateway-native: the gateway's `ingress_invites` table is the sole invite store, and its redemption engine (`gateway/src/verification/invite-redemption.ts`) validates the SHA-256 hashed token, atomically claims the row, and activates the channel in the gateway ACL. The daemon only mirrors the contact/channel identity locally via the `invite_redeemed` event. This path is distinct from the trusted contact flow but serves the same end state: an active member channel.
117
+ The `assistant_ingress_invites` table supports a parallel invite-based onboarding path. An invite carries a SHA-256 hashed token and can be redeemed via `redeemInvite()`, which atomically creates an active contact channel record. This path is distinct from the trusted contact flow but serves the same end state: an active `contact_channels` entry with `status: 'active'` and `policy: 'allow'`.
118
118
 
119
- | Table | Purpose in trusted contact flow |
120
- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
121
- | `ingress_invites` (gateway DB) | Not used in the guardian-mediated flow. Available as an alternative for direct invite links (e.g., guardian shares a URL instead of going through the approval + verification flow). |
119
+ | Table | Purpose in trusted contact flow |
120
+ | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
121
+ | `assistant_ingress_invites` | Not used in the guardian-mediated flow. Available as an alternative for direct invite links (e.g., guardian shares a URL instead of going through the approval + verification flow). |
122
122
 
123
123
  ### Voice In-Call Guardian Approval (friend-initiated)
124
124
 
@@ -126,10 +126,10 @@ Voice calls have a dedicated in-call guardian approval flow that differs from th
126
126
 
127
127
  **Flow:**
128
128
 
129
- 1. Unknown caller dials in. `routeSetup` (`relay-setup-router.ts`) resolves trust — caller is `unknown`, no pending challenge, no active invite.
130
- 2. The `CallSetupFlow` enters `capturing_name` state and prompts the caller for their name (with a timeout).
129
+ 1. Unknown caller dials in. `relay-server.ts` resolves trust — caller is `unknown`, no pending challenge, no active invite.
130
+ 2. Relay enters `awaiting_name` state and prompts the caller for their name (with a timeout).
131
131
  3. On name capture, `notifyGuardianOfAccessRequest` creates a canonical guardian request (`kind: 'access_request'`) and notifies the guardian.
132
- 4. The flow hands off to a `GuardianWaitController` (`awaiting_guardian_decision`), which speaks hold messaging while polling `canonical_guardian_requests` for status changes.
132
+ 4. Relay transitions to `awaiting_guardian_decision` and polls `canonical_guardian_requests` for status changes.
133
133
  5. Guardian approves or denies via any channel. All decisions route through `applyCanonicalGuardianDecision`.
134
134
  6. On approval: the `access_request` resolver directly activates the caller as a trusted contact (`upsertContactChannel` with `status: 'active'`, `policy: 'allow'`) — no verification session needed since the caller is already authenticated by their phone number.
135
135
  7. On denial or timeout: the caller hears a denial message and the call ends.
@@ -78,9 +78,6 @@ const ALLOWED_PREFIXES = {
78
78
  // Secure key storage (keys.ts) needs direct security module access —
79
79
  // by design, the secure-key helpers run in-process (not over IPC).
80
80
  "../../security/",
81
- // Canonical API-key provider list (keys.ts help text + validation) —
82
- // a static metadata catalog with no daemon state.
83
- "../../providers/provider-secret-catalog",
84
81
  // CES bridge (credential-execution.ts) speaks to the CES sidecar via
85
82
  // service-contracts RPC; daemon is not involved.
86
83
  "../../credential-execution/",
@@ -48,42 +48,6 @@ describe("contact read contracts", () => {
48
48
  expect(parsed.updatedAt).toBe(1700000000);
49
49
  });
50
50
 
51
- test("ContactReadSchema accepts null interactionCount (daemon-native gateway-telemetry outage fail-soft)", () => {
52
- const contact = {
53
- id: "c1",
54
- displayName: "Example User",
55
- role: "contact",
56
- contactType: "human",
57
- lastInteraction: null,
58
- interactionCount: null,
59
- createdAt: 1699000000,
60
- updatedAt: 1700000000,
61
- channels: [
62
- {
63
- id: "ch1",
64
- contactId: "c1",
65
- type: "imessage",
66
- address: "+15555550100",
67
- isPrimary: true,
68
- externalUserId: null,
69
- status: "active",
70
- policy: "allow",
71
- verifiedAt: null,
72
- verifiedVia: null,
73
- lastSeenAt: null,
74
- interactionCount: null,
75
- lastInteraction: null,
76
- revokedReason: null,
77
- blockedReason: null,
78
- },
79
- ],
80
- };
81
-
82
- const parsed = ContactReadSchema.parse(contact);
83
- expect(parsed.interactionCount).toBeNull();
84
- expect(parsed.channels[0].interactionCount).toBeNull();
85
- });
86
-
87
51
  test("ContactReadSchema requires createdAt/updatedAt", () => {
88
52
  const withoutTimestamps = {
89
53
  id: "c1",
@@ -29,8 +29,6 @@ const fullVerdict: TrustVerdict = {
29
29
  policy: "allow",
30
30
  verifiedAt: 1699999999,
31
31
  verifiedVia: "voice",
32
- interactionCount: 12,
33
- lastInteraction: 1699999998,
34
32
  memberDisplayName: "Member Name",
35
33
  };
36
34
 
@@ -94,26 +92,6 @@ describe("TrustVerdictSchema", () => {
94
92
  });
95
93
  });
96
94
 
97
- test("carries interaction telemetry and tolerates a null lastInteraction", () => {
98
- const parsed = TrustVerdictSchema.parse({
99
- trustClass: "trusted_contact",
100
- canonicalSenderId: "+15555550100",
101
- interactionCount: 0,
102
- lastInteraction: null,
103
- });
104
- expect(parsed.interactionCount).toBe(0);
105
- expect(parsed.lastInteraction).toBeNull();
106
- });
107
-
108
- test("leaves interaction telemetry undefined when absent", () => {
109
- const parsed = TrustVerdictSchema.parse({
110
- trustClass: "unknown",
111
- canonicalSenderId: null,
112
- });
113
- expect(parsed.interactionCount).toBeUndefined();
114
- expect(parsed.lastInteraction).toBeUndefined();
115
- });
116
-
117
95
  test("rejects an invalid trustClass", () => {
118
96
  expect(() =>
119
97
  TrustVerdictSchema.parse({
@@ -181,17 +181,6 @@ export type UpsertVerifiedChannelIpcResponse = z.infer<
181
181
  typeof UpsertVerifiedChannelIpcResponseSchema
182
182
  >;
183
183
 
184
- export const CreateContactIpcResponseSchema = z.object({
185
- contactId: z.string(),
186
- // Gateway channel id for the (channelType, address) pair, resolved from the
187
- // gateway DB (source of truth). Empty when the read-back found no row.
188
- channelId: z.string(),
189
- });
190
-
191
- export type CreateContactIpcResponse = z.infer<
192
- typeof CreateContactIpcResponseSchema
193
- >;
194
-
195
184
  export const MarkChannelRevokedIpcParamsSchema = z.object({
196
185
  contactChannelId: z.string().min(1),
197
186
  // Audit reason for the downgrade. The verification-revoke flow passes
@@ -233,7 +222,7 @@ export const ContactReadChannelSchema = z.object({
233
222
  verifiedAt: z.number().nullable(),
234
223
  verifiedVia: z.string().nullable(),
235
224
  lastSeenAt: z.number().nullable(),
236
- interactionCount: z.number().nullable(),
225
+ interactionCount: z.number(),
237
226
  lastInteraction: z.number().nullable(),
238
227
  revokedReason: z.string().nullable(),
239
228
  blockedReason: z.string().nullable(),
@@ -248,7 +237,7 @@ export const ContactReadSchema = z.object({
248
237
  notes: z.string().nullable().optional(),
249
238
  contactType: z.string().nullable().optional(),
250
239
  lastInteraction: z.number().nullable().optional(),
251
- interactionCount: z.number().nullable(),
240
+ interactionCount: z.number(),
252
241
  createdAt: z.number(),
253
242
  updatedAt: z.number(),
254
243
  channels: z.array(ContactReadChannelSchema),
@@ -270,16 +259,13 @@ export const ListContactsIpcParamsSchema = z
270
259
  .object({
271
260
  limit: z.number().optional(),
272
261
  role: z.string().optional(),
273
- // Restrict the read to these contact ids (any order). Used by the daemon to
274
- // batch-hydrate gateway-owned telemetry onto daemon-native filtered/search
275
- // results without re-implementing search in the gateway. When present,
276
- // `role`/`limit` filtering is bypassed — the id set is the filter.
277
- ids: z.array(z.string()).optional(),
278
262
  })
279
263
  .strict()
280
264
  .default({});
281
265
 
282
- export type ListContactsIpcParams = z.infer<typeof ListContactsIpcParamsSchema>;
266
+ export type ListContactsIpcParams = z.infer<
267
+ typeof ListContactsIpcParamsSchema
268
+ >;
283
269
 
284
270
  export const ListContactsIpcResponseSchema = z.object({
285
271
  ok: z.boolean(),
@@ -302,7 +288,9 @@ export const GetContactIpcResponseSchema = z.object({
302
288
  assistantMetadata: AssistantContactMetadataSchema.optional(),
303
289
  });
304
290
 
305
- export type GetContactIpcResponse = z.infer<typeof GetContactIpcResponseSchema>;
291
+ export type GetContactIpcResponse = z.infer<
292
+ typeof GetContactIpcResponseSchema
293
+ >;
306
294
 
307
295
  export const GetGuardianContactIpcParamsSchema = z
308
296
  .object({})