@vellumai/assistant 0.10.4 → 0.10.5-dev.202607022329.d89bf2f

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 (866) hide show
  1. package/ARCHITECTURE.md +94 -91
  2. package/Dockerfile +5 -16
  3. package/README.md +7 -10
  4. package/bun.lock +0 -4
  5. package/docs/architecture/security.md +20 -20
  6. package/docs/stt-provider-onboarding.md +4 -5
  7. package/docs/trusted-contact-access.md +7 -7
  8. package/eslint-rules/cli-no-daemon-internals.js +4 -0
  9. package/eslint.config.mjs +7 -1
  10. package/knip.json +0 -1
  11. package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +36 -0
  12. package/node_modules/@vellumai/gateway-client/src/__tests__/invite-contract.test.ts +233 -0
  13. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +40 -0
  14. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +20 -8
  15. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  16. package/node_modules/@vellumai/gateway-client/src/index.ts +33 -0
  17. package/node_modules/@vellumai/gateway-client/src/invite-contract.ts +216 -0
  18. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +91 -0
  19. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +39 -3
  20. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +0 -8
  21. package/node_modules/@vellumai/service-contracts/src/__tests__/package-boundary.test.ts +1 -5
  22. package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +3 -14
  23. package/openapi.yaml +1073 -225
  24. package/package.json +1 -4
  25. package/scripts/test.sh +35 -13
  26. package/src/__tests__/access-request-seed-content-blocks.test.ts +60 -4
  27. package/src/__tests__/acp-session.test.ts +4 -1
  28. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +0 -6
  29. package/src/__tests__/add-message-preassigned-id.test.ts +56 -0
  30. package/src/__tests__/agent-loop-set-system-prompt.test.ts +139 -0
  31. package/src/__tests__/app-conversation-ids-backfill.test.ts +3 -0
  32. package/src/__tests__/app-source-watcher.test.ts +11 -0
  33. package/src/__tests__/approval-interception-trust-gates.test.ts +148 -3
  34. package/src/__tests__/assistant-attachments.test.ts +62 -4
  35. package/src/__tests__/assistant-id-boundary-guard.test.ts +0 -1
  36. package/src/__tests__/attachments-store-heic-normalize.test.ts +230 -0
  37. package/src/__tests__/attachments-store.test.ts +3 -0
  38. package/src/__tests__/background-shell-bash.test.ts +34 -0
  39. package/src/__tests__/background-shell-host-bash.test.ts +12 -0
  40. package/src/__tests__/background-tool-registry.test.ts +97 -0
  41. package/src/__tests__/background-tool-routes.test.ts +72 -1
  42. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  43. package/src/__tests__/btw-routes.test.ts +57 -3
  44. package/src/__tests__/call-controller.test.ts +474 -84
  45. package/src/__tests__/call-domain.test.ts +48 -1
  46. package/src/__tests__/call-pointer-messages.test.ts +57 -19
  47. package/src/__tests__/call-pointer-no-hardcoded-copy.guard.test.ts +2 -6
  48. package/src/__tests__/call-routes-http.test.ts +15 -14
  49. package/src/__tests__/call-setup-flow-invite.test.ts +568 -0
  50. package/src/__tests__/call-setup-flow-midcall-trust.test.ts +231 -0
  51. package/src/__tests__/call-setup-flow-name-capture.test.ts +754 -0
  52. package/src/__tests__/call-setup-flow-verification.test.ts +925 -0
  53. package/src/__tests__/call-setup-flow.test.ts +311 -0
  54. package/src/__tests__/cancel-resolves-conversation-key.test.ts +0 -1
  55. package/src/__tests__/channel-guardian.test.ts +4 -7
  56. package/src/__tests__/channel-readiness-service.test.ts +0 -6
  57. package/src/__tests__/channel-readiness-slack-remote.test.ts +0 -4
  58. package/src/__tests__/channel-retry-sweep.test.ts +76 -64
  59. package/src/__tests__/channel-setup-panel-ack.test.ts +184 -0
  60. package/src/__tests__/clear-all-lexical.test.ts +90 -0
  61. package/src/__tests__/compactor-media-strip.test.ts +177 -0
  62. package/src/__tests__/config-loader-backfill.test.ts +5 -5
  63. package/src/__tests__/config-schema.test.ts +17 -0
  64. package/src/__tests__/contact-store-interaction-info.test.ts +27 -70
  65. package/src/__tests__/contacts-relay-reads.test.ts +188 -23
  66. package/src/__tests__/contacts-tools.test.ts +42 -1
  67. package/src/__tests__/contacts-write.test.ts +181 -0
  68. package/src/__tests__/context-search-conversations-source.test.ts +220 -81
  69. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -0
  70. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  71. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  72. package/src/__tests__/conversation-agent-loop.test.ts +39 -0
  73. package/src/__tests__/conversation-analysis-routes.test.ts +1 -1
  74. package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -0
  75. package/src/__tests__/conversation-clear-safety.test.ts +0 -1
  76. package/src/__tests__/conversation-crud-enabled-plugins.test.ts +60 -0
  77. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -1
  78. package/src/__tests__/conversation-disk-view.test.ts +1 -0
  79. package/src/__tests__/conversation-enabledplugins-route.test.ts +184 -0
  80. package/src/__tests__/conversation-error.test.ts +0 -18
  81. package/src/__tests__/conversation-fork-crud.test.ts +67 -0
  82. package/src/__tests__/conversation-history-web-search.test.ts +7 -137
  83. package/src/__tests__/conversation-initial-prompt.test.ts +110 -0
  84. package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +191 -0
  85. package/src/__tests__/conversation-process-callsite.test.ts +37 -0
  86. package/src/__tests__/conversation-queue.test.ts +149 -0
  87. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +392 -0
  88. package/src/__tests__/conversation-routes-guardian-reply.test.ts +84 -0
  89. package/src/__tests__/conversation-routes-hidden-queue.test.ts +407 -0
  90. package/src/__tests__/conversation-runtime-assembly.test.ts +36 -16
  91. package/src/__tests__/conversation-skill-tools.test.ts +73 -0
  92. package/src/__tests__/conversation-wipe.test.ts +20 -5
  93. package/src/__tests__/credential-prompt-route.test.ts +69 -0
  94. package/src/__tests__/credential-security-invariants.test.ts +1 -3
  95. package/src/__tests__/db-drop-messages-fts.test.ts +115 -0
  96. package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -62
  97. package/src/__tests__/edit-propagation.test.ts +56 -2
  98. package/src/__tests__/empty-state-greeting-cache.test.ts +9 -0
  99. package/src/__tests__/gateway-only-enforcement.test.ts +15 -110
  100. package/src/__tests__/guardian-expiry-notifier.test.ts +1 -0
  101. package/src/__tests__/guardian-question-mode.test.ts +57 -0
  102. package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -1
  103. package/src/__tests__/guardian-wait-controller.test.ts +619 -0
  104. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +33 -2
  105. package/src/__tests__/heic-fixture.ts +55 -0
  106. package/src/__tests__/helpers/seed-contact-channel.ts +0 -2
  107. package/src/__tests__/http-conversation-lineage.test.ts +6 -1
  108. package/src/__tests__/image-conversion.test.ts +170 -0
  109. package/src/__tests__/inbound-invite-redemption.test.ts +72 -341
  110. package/src/__tests__/introduction-card-resolver.test.ts +354 -0
  111. package/src/__tests__/invite-redeemed-ipc.test.ts +97 -0
  112. package/src/__tests__/invite-routes-http.test.ts +246 -420
  113. package/src/__tests__/job-handler-registration-guard.test.ts +104 -0
  114. package/src/__tests__/lexical-index-dual-write.test.ts +426 -0
  115. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +2 -2
  116. package/src/__tests__/list-messages-attachments.test.ts +159 -0
  117. package/src/__tests__/list-messages-background-tool-completion.test.ts +141 -0
  118. package/src/__tests__/list-messages-queued.test.ts +25 -0
  119. package/src/__tests__/managed-store.test.ts +129 -1
  120. package/src/__tests__/media-stream-audio-transcode.test.ts +113 -0
  121. package/src/__tests__/media-stream-output.test.ts +350 -19
  122. package/src/__tests__/media-stream-server-integration.test.ts +943 -169
  123. package/src/__tests__/media-stream-stt-session.test.ts +498 -1
  124. package/src/__tests__/memory-jobs-worker-lanes.test.ts +2 -2
  125. package/src/__tests__/model-intents.test.ts +1 -1
  126. package/src/__tests__/mtime-cache.test.ts +19 -0
  127. package/src/__tests__/non-member-access-request.test.ts +349 -3
  128. package/src/__tests__/notification-decision-fallback.test.ts +104 -8
  129. package/src/__tests__/notification-decision-strategy.test.ts +57 -8
  130. package/src/__tests__/onboarding-template-contract.test.ts +27 -43
  131. package/src/__tests__/openai-provider.test.ts +33 -0
  132. package/src/__tests__/openai-responses-provider.test.ts +40 -0
  133. package/src/__tests__/persistence-layering-guard.test.ts +29 -20
  134. package/src/__tests__/persona-resolver.test.ts +56 -2
  135. package/src/__tests__/personal-memory-auth-bypass.test.ts +62 -0
  136. package/src/__tests__/phone.test.ts +1 -15
  137. package/src/__tests__/plugin-bootstrap.test.ts +25 -196
  138. package/src/__tests__/plugin-disabled-state.test.ts +33 -0
  139. package/src/__tests__/plugin-effective-enabled-set.test.ts +227 -0
  140. package/src/__tests__/plugin-event-hub-facade.test.ts +323 -0
  141. package/src/__tests__/plugin-import-boundary-guard.test.ts +156 -42
  142. package/src/__tests__/plugin-injector-contribution.test.ts +4 -20
  143. package/src/__tests__/plugin-pipeline.test.ts +63 -1
  144. package/src/__tests__/plugin-route-contribution.test.ts +62 -115
  145. package/src/__tests__/plugin-tool-contribution.test.ts +0 -22
  146. package/src/__tests__/plugin-types.test.ts +0 -1
  147. package/src/__tests__/pointer-conversation-trust.test.ts +38 -6
  148. package/src/__tests__/pre-model-call-sanitize.test.ts +1 -1
  149. package/src/__tests__/prune-old-tool-invocations-job.test.ts +97 -0
  150. package/src/__tests__/public-ingress-urls.test.ts +0 -48
  151. package/src/__tests__/qdrant-collection-migration.test.ts +145 -2
  152. package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +0 -1
  153. package/src/__tests__/resolve-trust-class.test.ts +28 -10
  154. package/src/__tests__/scaffold-managed-skill-tool.test.ts +137 -0
  155. package/src/__tests__/schedule-retry.test.ts +38 -14
  156. package/src/__tests__/schedule-routes-workflow-validation.test.ts +3 -3
  157. package/src/__tests__/schedule-routes.test.ts +14 -6
  158. package/src/__tests__/schedule-store.test.ts +3 -1
  159. package/src/__tests__/scheduler-disk-pressure.test.ts +17 -6
  160. package/src/__tests__/scheduler-recurrence.test.ts +57 -107
  161. package/src/__tests__/scheduler-reuse-conversation.test.ts +51 -46
  162. package/src/__tests__/scheduler-wake.test.ts +13 -23
  163. package/src/__tests__/scheduler-worker-standdown.test.ts +220 -0
  164. package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
  165. package/src/__tests__/shell-observability.test.ts +1 -0
  166. package/src/__tests__/skill-boundary-guard.test.ts +3 -4
  167. package/src/__tests__/skill-load-plugin-scope.test.ts +238 -0
  168. package/src/__tests__/skill-projection-feature-flag.test.ts +2 -0
  169. package/src/__tests__/skill-script-runner-host.test.ts +53 -0
  170. package/src/__tests__/skill-script-runner-sandbox.test.ts +5 -2
  171. package/src/__tests__/skill-tool-factory.test.ts +73 -1
  172. package/src/__tests__/slack-app-setup-skill-regression.test.ts +37 -14
  173. package/src/__tests__/slack-inbound-verification.test.ts +36 -0
  174. package/src/__tests__/slack-notification-approval-card.test.ts +89 -2
  175. package/src/__tests__/slack-reaction-canonical-approval.test.ts +51 -1
  176. package/src/__tests__/stt-hints.test.ts +2 -256
  177. package/src/__tests__/subagent-allowlist-validation.test.ts +47 -0
  178. package/src/__tests__/subagent-call-site-routing.test.ts +72 -0
  179. package/src/__tests__/subagent-fork-notifications.test.ts +14 -2
  180. package/src/__tests__/subagent-persistence.test.ts +122 -0
  181. package/src/__tests__/subagent-spawn-and-await.test.ts +1 -0
  182. package/src/__tests__/subagent-terminal-message.test.ts +99 -0
  183. package/src/__tests__/subagent-tool-gate-mode.test.ts +78 -1
  184. package/src/__tests__/system-prompt.test.ts +17 -118
  185. package/src/__tests__/task-scheduler.test.ts +68 -89
  186. package/src/__tests__/telephony-credential-preflight.test.ts +312 -0
  187. package/src/__tests__/telephony-tts-guard.test.ts +538 -0
  188. package/src/__tests__/title-generate-hook.test.ts +16 -0
  189. package/src/__tests__/tool-approval-handler.test.ts +87 -1
  190. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +45 -20
  191. package/src/__tests__/tool-grant-request-escalation.test.ts +39 -0
  192. package/src/__tests__/trust-context-guards.test.ts +21 -10
  193. package/src/__tests__/trusted-contact-multichannel.test.ts +4 -0
  194. package/src/__tests__/twilio-provider.test.ts +17 -17
  195. package/src/__tests__/twilio-routes-twiml.test.ts +6 -543
  196. package/src/__tests__/twilio-routes.test.ts +257 -387
  197. package/src/__tests__/twilio-validation.test.ts +1 -1
  198. package/src/__tests__/unified-turn-context-client-os.test.ts +45 -0
  199. package/src/__tests__/usage-cache-backfill-migration.test.ts +5 -0
  200. package/src/__tests__/voice-approval-resolver-relay.test.ts +150 -0
  201. package/src/__tests__/voice-ingress-preflight.test.ts +61 -0
  202. package/src/__tests__/voice-quality.test.ts +2 -2
  203. package/src/__tests__/voice-scoped-grant-consumer.test.ts +1 -7
  204. package/src/__tests__/voice-session-bridge-processing-wait.test.ts +27 -0
  205. package/src/__tests__/voice-session-bridge.test.ts +1 -10
  206. package/src/__tests__/workspace-git-service.test.ts +1 -1
  207. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -0
  208. package/src/__tests__/workspace-migration-118-seed-now-md.test.ts +120 -0
  209. package/src/__tests__/workspace-migration-120-revise-onboarding-threads.test.ts +175 -0
  210. package/src/__tests__/workspace-migration-121-seed-default-user-guardrails.test.ts +118 -0
  211. package/src/__tests__/workspace-migration-122-relocate-default-user-boundary.test.ts +119 -0
  212. package/src/__tests__/workspace-migration-123-swap-quality-profile-to-fable.test.ts +191 -0
  213. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +235 -241
  214. package/src/acp/__tests__/prepare-agent-env.test.ts +0 -97
  215. package/src/acp/__tests__/session-manager-persistence.test.ts +2 -0
  216. package/src/acp/__tests__/session-manager-resume.test.ts +8 -0
  217. package/src/acp/__tests__/session-manager-usage.test.ts +6 -0
  218. package/src/acp/__tests__/session-manager.test.ts +6 -0
  219. package/src/acp/agent-process.test.ts +116 -0
  220. package/src/acp/agent-process.ts +71 -0
  221. package/src/acp/auto-install.ts +12 -8
  222. package/src/acp/failure-error.test.ts +109 -0
  223. package/src/acp/failure-error.ts +111 -0
  224. package/src/acp/prepare-agent-env.ts +0 -12
  225. package/src/acp/resolve-agent.test.ts +6 -45
  226. package/src/acp/resolve-agent.ts +2 -4
  227. package/src/acp/session-manager.test.ts +312 -0
  228. package/src/acp/session-manager.ts +102 -46
  229. package/src/agent/ax-tree-compaction.test.ts +4 -1
  230. package/src/agent/image-optimize.ts +17 -127
  231. package/src/agent/loop.ts +45 -192
  232. package/src/api/events/background-tool-completed.test.ts +51 -0
  233. package/src/api/events/background-tool-completed.ts +29 -0
  234. package/src/api/events/background-tool-started.test.ts +36 -0
  235. package/src/api/events/background-tool-started.ts +28 -0
  236. package/src/api/events/conversation-error.ts +0 -1
  237. package/src/api/events/open-panel.ts +33 -0
  238. package/src/api/events/subagent-status-changed.ts +4 -1
  239. package/src/api/index.ts +19 -0
  240. package/src/api/responses/conversation-message.ts +34 -0
  241. package/src/approvals/guardian-decision-primitive.ts +74 -24
  242. package/src/approvals/guardian-request-resolvers.ts +450 -122
  243. package/src/apps/app-store.ts +1 -0
  244. package/src/apps/shared-app-links-store.ts +1 -0
  245. package/src/avatar/identity-avatar.ts +3 -7
  246. package/src/background-wake/wake-intent-hooks.test.ts +1 -1
  247. package/src/calls/__tests__/fish-audio-client.test.ts +73 -0
  248. package/src/calls/__tests__/gateway-invite-reader.test.ts +170 -0
  249. package/src/calls/__tests__/relay-setup-router.test.ts +126 -102
  250. package/src/calls/call-controller.ts +134 -48
  251. package/src/calls/call-domain.ts +18 -42
  252. package/src/calls/call-pointer-messages.ts +51 -67
  253. package/src/calls/call-setup-flow-types.ts +76 -0
  254. package/src/calls/call-setup-flow.ts +1743 -0
  255. package/src/calls/call-speech-output.ts +87 -23
  256. package/src/calls/call-store.ts +4 -0
  257. package/src/calls/call-transport.ts +22 -36
  258. package/src/calls/fish-audio-client.ts +3 -0
  259. package/src/calls/gateway-invite-reader.ts +98 -0
  260. package/src/calls/guardian-wait-controller.ts +451 -0
  261. package/src/calls/media-stream-audio-transcode.ts +82 -2
  262. package/src/calls/media-stream-output.ts +142 -36
  263. package/src/calls/media-stream-server.ts +297 -178
  264. package/src/calls/media-stream-stt-session.ts +393 -50
  265. package/src/calls/media-turn-detector.ts +4 -6
  266. package/src/calls/relay-access-wait.ts +3 -4
  267. package/src/calls/relay-setup-router.ts +22 -43
  268. package/src/calls/relay-verification.ts +17 -24
  269. package/src/calls/resolve-call-tts-provider.ts +96 -29
  270. package/src/calls/stt-hints.ts +24 -87
  271. package/src/calls/telephony-credential-preflight.ts +159 -0
  272. package/src/calls/telephony-tts-capability.ts +183 -0
  273. package/src/calls/tts-call-strategy.ts +18 -20
  274. package/src/calls/twilio-provider.ts +3 -2
  275. package/src/calls/twilio-routes.ts +47 -351
  276. package/src/calls/types.ts +5 -3
  277. package/src/calls/voice-ingress-preflight.ts +9 -0
  278. package/src/calls/voice-quality.ts +13 -18
  279. package/src/calls/voice-session-bridge.ts +25 -35
  280. package/src/channels/__tests__/types.test.ts +28 -0
  281. package/src/channels/channel-verification-sessions.ts +7 -2
  282. package/src/channels/config.ts +0 -42
  283. package/src/channels/slack-thread-store.ts +12 -0
  284. package/src/channels/types.ts +23 -0
  285. package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -1
  286. package/src/cli/commands/__tests__/schedules.test.ts +83 -34
  287. package/src/cli/commands/__tests__/status.test.ts +42 -2
  288. package/src/cli/commands/contacts.ts +2 -2
  289. package/src/cli/commands/credentials.ts +17 -0
  290. package/src/cli/commands/monitoring.ts +191 -0
  291. package/src/cli/commands/schedules-worker.ts +175 -0
  292. package/src/cli/commands/schedules.ts +169 -43
  293. package/src/cli/commands/status.ts +12 -1
  294. package/src/cli/commands/tools.ts +5 -1
  295. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +1 -0
  296. package/src/cli/lib/__tests__/search-plugins.test.ts +29 -1
  297. package/src/cli/lib/__tests__/toggle-plugin.test.ts +7 -0
  298. package/src/cli/lib/search-plugins.ts +6 -0
  299. package/src/cli/lib/toggle-plugin.ts +21 -2
  300. package/src/cli/program.ts +3 -1
  301. package/src/config/__tests__/deployment-context-defaults.test.ts +23 -0
  302. package/src/config/acp-defaults.test.ts +0 -10
  303. package/src/config/acp-defaults.ts +0 -6
  304. package/src/config/bundled-skills/acp/SKILL.md +13 -45
  305. package/src/config/bundled-skills/acp/TOOLS.json +3 -3
  306. package/src/config/bundled-skills/app-builder/SKILL.md +3 -3
  307. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +9 -9
  308. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +1 -1
  309. package/src/config/bundled-skills/contacts/tools/contact-search.ts +1 -1
  310. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  311. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -9
  312. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +10 -7
  313. package/src/config/bundled-skills/skill-management/TOOLS.json +10 -0
  314. package/src/config/call-site-defaults.ts +0 -2
  315. package/src/config/feature-flag-registry.json +16 -8
  316. package/src/config/loader.ts +11 -0
  317. package/src/config/schema.ts +9 -1
  318. package/src/config/schemas/__tests__/memory-v2.test.ts +2 -2
  319. package/src/config/schemas/__tests__/memory-v3.test.ts +64 -0
  320. package/src/config/schemas/call-site-catalog.ts +0 -12
  321. package/src/config/schemas/calls.ts +8 -0
  322. package/src/config/schemas/elevenlabs.ts +1 -1
  323. package/src/config/schemas/llm.ts +0 -2
  324. package/src/config/schemas/memory-v2.ts +1 -1
  325. package/src/config/schemas/memory-v3.ts +82 -0
  326. package/src/config/schemas/monitoring.ts +61 -0
  327. package/src/config/schemas/schedules.ts +35 -0
  328. package/src/config/seed-inference-profiles.ts +2 -2
  329. package/src/config/skills-plugin-filter.test.ts +96 -0
  330. package/src/config/skills.ts +26 -0
  331. package/src/contacts/__tests__/member-write-relay.test.ts +156 -4
  332. package/src/contacts/canonical-guardian-store.ts +5 -0
  333. package/src/contacts/contact-store.ts +81 -35
  334. package/src/contacts/contacts-write.ts +50 -25
  335. package/src/contacts/member-write-relay.ts +115 -31
  336. package/src/contacts/types.ts +0 -8
  337. package/src/context/compactor.ts +29 -15
  338. package/src/context/outbound-sanitize.ts +202 -0
  339. package/src/daemon/__tests__/abort-null-controller.test.ts +167 -0
  340. package/src/daemon/__tests__/conversation-tool-setup-plugin-scope.test.ts +122 -0
  341. package/src/daemon/__tests__/embedding-reconcile.test.ts +99 -16
  342. package/src/daemon/__tests__/wake-conversation-ops-completion.test.ts +107 -0
  343. package/src/daemon/abort-watchdog.ts +7 -0
  344. package/src/daemon/app-source-watcher.ts +9 -0
  345. package/src/daemon/assistant-attachments.ts +42 -0
  346. package/src/daemon/conversation-agent-loop-handlers.ts +256 -147
  347. package/src/daemon/conversation-agent-loop.ts +61 -68
  348. package/src/daemon/conversation-error.ts +1 -15
  349. package/src/daemon/conversation-history.ts +32 -236
  350. package/src/daemon/conversation-initial-prompt.ts +77 -0
  351. package/src/daemon/conversation-lifecycle.ts +25 -1
  352. package/src/daemon/conversation-messaging.ts +2 -2
  353. package/src/daemon/conversation-plugin-scope.ts +38 -0
  354. package/src/daemon/conversation-process.ts +50 -14
  355. package/src/daemon/conversation-runtime-assembly.ts +36 -6
  356. package/src/daemon/conversation-skill-tools.ts +19 -3
  357. package/src/daemon/conversation-store.ts +3 -3
  358. package/src/daemon/conversation-surfaces.ts +171 -0
  359. package/src/daemon/conversation-tool-setup.test.ts +95 -0
  360. package/src/daemon/conversation-tool-setup.ts +116 -1
  361. package/src/daemon/conversation-turn-finalize.ts +211 -0
  362. package/src/daemon/conversation.ts +92 -1
  363. package/src/daemon/disk-pressure-policy.ts +2 -1
  364. package/src/daemon/event-loop-watchdog.ts +22 -6
  365. package/src/daemon/external-plugins-bootstrap.ts +47 -134
  366. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +1 -1
  367. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +26 -14
  368. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +27 -14
  369. package/src/daemon/handlers/__tests__/config-channels.test.ts +0 -5
  370. package/src/daemon/handlers/config-a2a.ts +7 -14
  371. package/src/daemon/handlers/config-channels.ts +2 -10
  372. package/src/daemon/handlers/conversation-history.ts +3 -1
  373. package/src/daemon/handlers/conversations.ts +0 -52
  374. package/src/daemon/handlers/shared.ts +4 -24
  375. package/src/daemon/handlers/skills.ts +4 -1
  376. package/src/daemon/host-cu-proxy.ts +1 -1
  377. package/src/daemon/lifecycle.ts +59 -472
  378. package/src/daemon/message-protocol.ts +3 -3
  379. package/src/daemon/message-provenance.ts +12 -14
  380. package/src/daemon/message-types/background-tools.ts +28 -0
  381. package/src/daemon/message-types/contacts.ts +2 -10
  382. package/src/daemon/message-types/conversations.ts +10 -0
  383. package/src/daemon/message-types/integrations.ts +2 -0
  384. package/src/daemon/message-types/surfaces.ts +1 -0
  385. package/src/daemon/message-types/sync.ts +1 -0
  386. package/src/daemon/pointer-conversation-trust.ts +23 -9
  387. package/src/daemon/pointer-turn-runner.ts +174 -0
  388. package/src/daemon/providers-setup.ts +16 -0
  389. package/src/daemon/shutdown-handlers.ts +139 -47
  390. package/src/daemon/skill-memory-refresh.ts +2 -2
  391. package/src/daemon/sqlite-corruption-watchdog.test.ts +217 -0
  392. package/src/daemon/sqlite-corruption-watchdog.ts +155 -0
  393. package/src/daemon/tool-setup-types.ts +8 -0
  394. package/src/daemon/trust-context.ts +21 -6
  395. package/src/daemon/turn-latency-tracker.test.ts +93 -0
  396. package/src/daemon/turn-latency-tracker.ts +38 -0
  397. package/src/daemon/wake-conversation-ops.ts +10 -8
  398. package/src/documents/document-comments-store.test.ts +7 -1
  399. package/src/documents/document-comments-store.ts +8 -0
  400. package/src/documents/document-store.ts +15 -0
  401. package/src/export/__tests__/transcript-formatter.test.ts +11 -0
  402. package/src/export/transcript-formatter.ts +49 -67
  403. package/src/home/__tests__/feed-writer.test.ts +247 -1
  404. package/src/home/conversation-starter-checkpoints.ts +1 -0
  405. package/src/home/feed-writer.ts +61 -0
  406. package/src/home/job-handlers/conversation-starters.ts +1 -0
  407. package/src/hooks/hook-loader.ts +12 -0
  408. package/src/hooks/registry.ts +32 -4
  409. package/src/inbound/public-ingress-urls.ts +0 -17
  410. package/src/ipc/__tests__/contacts-info-ipc.test.ts +285 -0
  411. package/src/ipc/assistant-server.ts +28 -7
  412. package/src/ipc/routes/__tests__/contacts-mirror-apply-atomicity.test.ts +62 -0
  413. package/src/ipc/routes/__tests__/contacts-mirror-apply.test.ts +272 -0
  414. package/src/ipc/routes/__tests__/contacts-mirror-ipc-routes.test.ts +322 -0
  415. package/src/ipc/routes/__tests__/invite-ipc-routes.test.ts +60 -4
  416. package/src/ipc/routes/contacts-info-ipc-routes.ts +239 -0
  417. package/src/ipc/routes/contacts-mirror-ipc-routes.ts +202 -0
  418. package/src/ipc/routes/db-proxy.ts +5 -2
  419. package/src/ipc/routes/invite-ipc-routes.ts +53 -39
  420. package/src/jobs/worker.ts +9 -2
  421. package/src/live-voice/__tests__/live-voice-archive.test.ts +10 -1
  422. package/src/live-voice/live-voice-archive.ts +5 -0
  423. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +22 -6
  424. package/src/messaging/providers/channel-transport.ts +6 -0
  425. package/src/messaging/providers/index.ts +3 -0
  426. package/src/messaging/providers/slack/api.test.ts +49 -1
  427. package/src/messaging/providers/slack/api.ts +110 -0
  428. package/src/messaging/providers/slack/send.test.ts +85 -0
  429. package/src/messaging/providers/slack/send.ts +100 -28
  430. package/src/messaging/providers/slack/transport.ts +7 -13
  431. package/src/messaging/providers/whatsapp/send.ts +29 -9
  432. package/src/monitoring/__tests__/control.test.ts +235 -0
  433. package/src/monitoring/__tests__/sample-ring-buffer.test.ts +96 -0
  434. package/src/monitoring/control.ts +105 -0
  435. package/src/monitoring/resource-sampler.ts +258 -0
  436. package/src/monitoring/sample-ring-buffer.ts +108 -0
  437. package/src/monitoring/worker.ts +94 -0
  438. package/src/notifications/README.md +1 -1
  439. package/src/notifications/access-request-copy.ts +94 -23
  440. package/src/notifications/adapters/slack.ts +89 -21
  441. package/src/notifications/approval-card-builder.ts +33 -18
  442. package/src/notifications/approval-card-data.ts +48 -15
  443. package/src/notifications/broadcaster.ts +25 -16
  444. package/src/notifications/decision-engine.ts +131 -32
  445. package/src/notifications/guardian-question-mode.ts +58 -10
  446. package/src/permissions/approval-policy.ts +8 -1
  447. package/src/permissions/checker.ts +2 -0
  448. package/src/permissions/gateway-threshold-reader.ts +7 -6
  449. package/src/permissions/ipc-risk-types.ts +1 -0
  450. package/src/permissions/secret-prompter.ts +11 -3
  451. package/src/permissions/types.ts +2 -2
  452. package/src/persistence/__tests__/conversation-queries-search.test.ts +397 -0
  453. package/src/persistence/__tests__/conversation-search-lexical.test.ts +103 -0
  454. package/src/persistence/__tests__/jobs-worker-memory-disabled.test.ts +95 -0
  455. package/src/persistence/__tests__/slow-query-log.test.ts +266 -0
  456. package/src/persistence/__tests__/slow-sync-log.test.ts +155 -0
  457. package/src/persistence/a2a-invite-store.ts +205 -0
  458. package/src/persistence/attachments-store.ts +94 -10
  459. package/src/persistence/checkpoints.ts +45 -0
  460. package/src/persistence/conversation-crud.ts +453 -260
  461. package/src/persistence/conversation-display-order-migration.ts +8 -2
  462. package/src/persistence/conversation-group-migration.test.ts +8 -0
  463. package/src/persistence/conversation-group-migration.ts +9 -0
  464. package/src/persistence/conversation-queries.ts +244 -125
  465. package/src/persistence/conversation-row-batch-delete.ts +9 -5
  466. package/src/persistence/conversation-search-lexical.ts +49 -0
  467. package/src/persistence/db-async-query.ts +42 -0
  468. package/src/persistence/db-connection.ts +13 -0
  469. package/src/persistence/delivery-channels.ts +0 -82
  470. package/src/persistence/delivery-crud.ts +27 -6
  471. package/src/persistence/embeddings/__tests__/embedding-identity.test.ts +18 -5
  472. package/src/persistence/embeddings/__tests__/messages-lexical-index.test.ts +334 -0
  473. package/src/persistence/embeddings/embedding-identity.ts +17 -5
  474. package/src/persistence/embeddings/embedding-runtime-manager.ts +31 -0
  475. package/src/persistence/embeddings/messages-lexical-index.ts +396 -0
  476. package/src/persistence/embeddings/qdrant-client.ts +81 -5
  477. package/src/persistence/group-crud.ts +13 -1
  478. package/src/persistence/job-handlers/__tests__/message-lexical-backfill.test.ts +506 -0
  479. package/src/persistence/job-handlers/__tests__/message-lexical-enqueue.test.ts +164 -0
  480. package/src/persistence/job-handlers/__tests__/message-lexical.test.ts +259 -0
  481. package/src/persistence/job-handlers/cleanup.ts +57 -9
  482. package/src/persistence/job-handlers/message-lexical-backfill.ts +187 -0
  483. package/src/persistence/job-handlers/message-lexical.ts +251 -0
  484. package/src/persistence/jobs-store.ts +102 -8
  485. package/src/persistence/jobs-worker.ts +68 -28
  486. package/src/persistence/llm-request-log-store.ts +35 -19
  487. package/src/persistence/llm-usage-store.ts +11 -0
  488. package/src/persistence/memory-lifecycle-hooks.test.ts +65 -0
  489. package/src/persistence/memory-lifecycle-hooks.ts +55 -1
  490. package/src/persistence/migrations/028-call-session-mode.ts +1 -1
  491. package/src/persistence/migrations/116-messages-fts.ts +8 -1
  492. package/src/persistence/migrations/311-create-subagents-table.ts +50 -0
  493. package/src/persistence/migrations/312-drop-inbox-conversation-state-table.ts +15 -0
  494. package/src/persistence/migrations/313-drop-messages-fts.ts +17 -0
  495. package/src/persistence/migrations/314-add-conversation-enabled-plugins.ts +25 -0
  496. package/src/persistence/migrations/315-create-a2a-invites.ts +58 -0
  497. package/src/persistence/migrations/316-drop-contact-channels-invite-id.ts +28 -0
  498. package/src/persistence/migrations/317-canonical-guardian-requester-signals.ts +24 -0
  499. package/src/persistence/migrations/318-drop-contact-channel-telemetry.ts +32 -0
  500. package/src/persistence/migrations/__tests__/315-create-a2a-invites.test.ts +161 -0
  501. package/src/persistence/migrations/__tests__/316-drop-contact-channels-invite-id.test.ts +156 -0
  502. package/src/persistence/migrations/__tests__/318-drop-contact-channel-telemetry.test.ts +134 -0
  503. package/src/persistence/migrations/__tests__/migration-prefix-guard.test.ts +99 -0
  504. package/src/persistence/raw-query.ts +50 -11
  505. package/src/persistence/schema/a2a.ts +14 -0
  506. package/src/persistence/schema/contacts.ts +0 -62
  507. package/src/persistence/schema/conversation-groups.ts +12 -0
  508. package/src/persistence/schema/conversations.ts +2 -0
  509. package/src/persistence/schema/documents.ts +65 -0
  510. package/src/persistence/schema/guardian.ts +4 -0
  511. package/src/persistence/schema/index.ts +4 -0
  512. package/src/persistence/schema/memory-injection.ts +62 -0
  513. package/src/persistence/schema/workflows.ts +55 -0
  514. package/src/persistence/slow-query-log.ts +437 -0
  515. package/src/persistence/slow-sync-log.ts +204 -0
  516. package/src/persistence/steps.ts +16 -0
  517. package/src/persistence/subagent-store.ts +125 -0
  518. package/src/persistence/worker-control.ts +45 -238
  519. package/src/plugin-api/event-hub-facade.ts +172 -0
  520. package/src/plugin-api/index.ts +9 -1
  521. package/src/plugin-api/types.ts +40 -9
  522. package/src/plugins/defaults/default-plugin-names.ts +60 -0
  523. package/src/plugins/defaults/index.ts +19 -29
  524. package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +46 -67
  525. package/src/plugins/defaults/memory/__tests__/db-async-query.test.ts +36 -1
  526. package/src/plugins/defaults/memory/__tests__/find-most-recent-retrospective-for.test.ts +2 -4
  527. package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +2 -2
  528. package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-schedule.test.ts +9 -0
  529. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +7 -4
  530. package/src/plugins/defaults/memory/config.ts +11 -0
  531. package/src/plugins/defaults/memory/context-search/agent-runner.ts +1 -1
  532. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +124 -113
  533. package/src/plugins/defaults/memory/embeddings.ts +77 -0
  534. package/src/plugins/defaults/memory/find-most-recent-retrospective-for.ts +66 -0
  535. package/src/plugins/defaults/memory/frontmatter.ts +50 -0
  536. package/src/plugins/defaults/memory/graph/bootstrap.test.ts +36 -10
  537. package/src/plugins/defaults/memory/graph/bootstrap.ts +6 -5
  538. package/src/plugins/defaults/memory/graph/consolidation.ts +3 -5
  539. package/src/plugins/defaults/memory/graph/extraction.ts +3 -6
  540. package/src/plugins/defaults/memory/graph/graph-search.ts +12 -23
  541. package/src/plugins/defaults/memory/graph/narrative.ts +3 -5
  542. package/src/plugins/defaults/memory/graph/pattern-scan.ts +3 -5
  543. package/src/plugins/defaults/memory/graph/retriever.test.ts +1 -6
  544. package/src/plugins/defaults/memory/graph/retriever.ts +5 -10
  545. package/src/plugins/defaults/memory/graph/tools.ts +1 -1
  546. package/src/plugins/defaults/memory/hooks/init.ts +31 -5
  547. package/src/plugins/defaults/memory/indexer.ts +2 -3
  548. package/src/plugins/defaults/memory/injectors.ts +2 -2
  549. package/src/plugins/defaults/memory/job-handler-registration.ts +96 -0
  550. package/src/plugins/defaults/memory/job-handlers/backfill.ts +4 -26
  551. package/src/plugins/defaults/memory/job-handlers/embedding.test.ts +6 -19
  552. package/src/plugins/defaults/memory/job-handlers/embedding.ts +9 -22
  553. package/src/plugins/defaults/memory/job-handlers/index-maintenance.ts +2 -3
  554. package/src/plugins/defaults/memory/job-handlers.ts +13 -12
  555. package/src/plugins/defaults/memory/jobs/embed-concept-page.ts +1 -1
  556. package/src/plugins/defaults/memory/llm-helpers.ts +76 -0
  557. package/src/plugins/defaults/memory/memory-retrospective-job.ts +8 -5
  558. package/src/plugins/defaults/memory/memory-retrospective-startup-cleanup.ts +2 -2
  559. package/src/plugins/defaults/memory/memory-v2-concept-frequency.ts +2 -0
  560. package/src/plugins/defaults/memory/persistence-hooks.ts +46 -1
  561. package/src/plugins/defaults/memory/pkb/pkb-index.ts +1 -5
  562. package/src/plugins/defaults/memory/pkb/pkb-search.ts +2 -2
  563. package/src/plugins/defaults/memory/routes/__tests__/memory-v2-simulate-route.test.ts +4 -6
  564. package/src/plugins/defaults/memory/routes/memory-item-routes.ts +1 -1
  565. package/src/plugins/defaults/memory/search/semantic.ts +2 -2
  566. package/src/plugins/defaults/memory/startup.ts +244 -0
  567. package/src/plugins/defaults/memory/task-memory-cleanup.ts +8 -0
  568. package/src/{daemon → plugins/defaults/memory/v2}/__tests__/memory-v2-startup.test.ts +6 -6
  569. package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +2 -13
  570. package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +4 -5
  571. package/src/plugins/defaults/memory/v2/__tests__/sweep-job.test.ts +1 -8
  572. package/src/plugins/defaults/memory/v2/activation.ts +2 -4
  573. package/src/plugins/defaults/memory/v2/cli-command-store.ts +2 -4
  574. package/src/plugins/defaults/memory/v2/frontmatter-sweep.ts +1 -1
  575. package/src/{daemon → plugins/defaults/memory/v2}/memory-v2-startup.test.ts +8 -8
  576. package/src/{daemon → plugins/defaults/memory/v2}/memory-v2-startup.ts +20 -22
  577. package/src/plugins/defaults/memory/v2/migration.ts +2 -5
  578. package/src/plugins/defaults/memory/v2/page-store.ts +1 -1
  579. package/src/plugins/defaults/memory/v2/qdrant.ts +2 -3
  580. package/src/plugins/defaults/memory/v2/router.ts +5 -5
  581. package/src/plugins/defaults/memory/v2/sim.ts +2 -4
  582. package/src/plugins/defaults/memory/v2/skill-store.ts +2 -4
  583. package/src/plugins/defaults/memory/v2/sweep-job.ts +6 -6
  584. package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +11 -9
  585. package/src/plugins/defaults/memory/v3/__tests__/dense.test.ts +107 -2
  586. package/src/plugins/defaults/memory/v3/__tests__/gate-flag.test.ts +69 -0
  587. package/src/plugins/defaults/memory/v3/__tests__/gate.test.ts +181 -0
  588. package/src/plugins/defaults/memory/v3/__tests__/live-integration.test.ts +7 -3
  589. package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +345 -8
  590. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +1 -4
  591. package/src/plugins/defaults/memory/v3/__tests__/section-needle.test.ts +64 -0
  592. package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +18 -13
  593. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +62 -1
  594. package/src/plugins/defaults/memory/v3/card.ts +1 -4
  595. package/src/plugins/defaults/memory/v3/dense.ts +60 -25
  596. package/src/plugins/defaults/memory/v3/edge.ts +1 -1
  597. package/src/plugins/defaults/memory/v3/gate-flag.ts +20 -0
  598. package/src/plugins/defaults/memory/v3/gate.ts +158 -0
  599. package/src/plugins/defaults/memory/v3/maintain-job.ts +2 -4
  600. package/src/plugins/defaults/memory/v3/orchestrate.ts +186 -25
  601. package/src/plugins/defaults/memory/v3/pool-select.test.ts +7 -9
  602. package/src/plugins/defaults/memory/v3/pool-select.ts +6 -6
  603. package/src/plugins/defaults/memory/v3/prune.ts +2 -2
  604. package/src/plugins/defaults/memory/v3/section-dense-store.ts +8 -11
  605. package/src/plugins/defaults/memory/v3/section-needle.ts +27 -7
  606. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +10 -0
  607. package/src/plugins/defaults/memory/v3-eval/eval-packets.ts +1 -4
  608. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +5 -0
  609. package/src/plugins/defaults/turn-context/injectors.ts +1 -0
  610. package/src/plugins/defaults/turn-context/unified-turn-context.ts +12 -0
  611. package/src/plugins/injector-registry.ts +13 -2
  612. package/src/plugins/mtime-cache.ts +36 -47
  613. package/src/plugins/pipeline.ts +15 -1
  614. package/src/plugins/types.ts +14 -23
  615. package/src/prompts/__tests__/system-prompt.test.ts +170 -32
  616. package/src/prompts/persona-resolver.ts +13 -9
  617. package/src/prompts/system-prompt.ts +30 -9
  618. package/src/prompts/templates/BOOTSTRAP.md +16 -119
  619. package/src/prompts/templates/SOUL.md +43 -13
  620. package/src/prompts/templates/system-sections.ts +32 -27
  621. package/src/prompts/templates/users/default.md +19 -0
  622. package/src/providers/__tests__/placeholder-sentinels.test.ts +56 -0
  623. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +96 -1
  624. package/src/providers/model-intents.ts +2 -2
  625. package/src/providers/openai/chat-completions-provider.ts +14 -1
  626. package/src/providers/openai/responses-provider.ts +18 -0
  627. package/src/providers/placeholder-sentinels.ts +18 -8
  628. package/src/providers/retry.ts +1 -0
  629. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +0 -64
  630. package/src/providers/speech-to-text/__tests__/resolve.test.ts +92 -79
  631. package/src/providers/speech-to-text/deepgram-realtime.test.ts +98 -0
  632. package/src/providers/speech-to-text/deepgram-realtime.ts +62 -3
  633. package/src/providers/speech-to-text/provider-catalog.ts +0 -89
  634. package/src/providers/speech-to-text/resolve.ts +55 -0
  635. package/src/providers/tool-progress-events.test.ts +50 -0
  636. package/src/providers/tool-progress-events.ts +99 -0
  637. package/src/providers/types.ts +1 -1
  638. package/src/providers/vellum-model-routing.test.ts +51 -0
  639. package/src/providers/vellum-model-routing.ts +84 -0
  640. package/src/runtime/AGENTS.md +5 -4
  641. package/src/runtime/__tests__/agent-wake.test.ts +158 -1
  642. package/src/runtime/__tests__/channel-verification-service.test.ts +2 -2
  643. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +37 -8
  644. package/src/runtime/access-request-helper.ts +154 -3
  645. package/src/runtime/actor-trust-resolver.ts +7 -21
  646. package/src/runtime/agent-wake.ts +41 -1
  647. package/src/runtime/assistant-event.ts +86 -12
  648. package/src/runtime/auth/__tests__/route-policy.test.ts +31 -0
  649. package/src/runtime/channel-approval-types.ts +52 -2
  650. package/src/runtime/channel-approvals.ts +23 -8
  651. package/src/runtime/channel-invite-transport.ts +2 -2
  652. package/src/runtime/channel-invite-transports/voice.ts +1 -1
  653. package/src/runtime/channel-readiness-service.ts +3 -5
  654. package/src/runtime/channel-retry-sweep.ts +18 -24
  655. package/src/runtime/channel-verification-service.ts +9 -11
  656. package/src/runtime/finalize-event-delivery.ts +30 -16
  657. package/src/runtime/guardian-action-service.ts +10 -11
  658. package/src/runtime/guardian-reply-router.ts +122 -21
  659. package/src/runtime/http-server.ts +44 -101
  660. package/src/runtime/introduction-policy.test.ts +214 -0
  661. package/src/runtime/introduction-policy.ts +284 -0
  662. package/src/runtime/invite-service.ts +104 -361
  663. package/src/runtime/local-actor-identity.test.ts +38 -0
  664. package/src/runtime/local-actor-identity.ts +14 -0
  665. package/src/runtime/middleware/__tests__/rate-limiter.test.ts +22 -0
  666. package/src/runtime/middleware/rate-limiter.ts +30 -0
  667. package/src/runtime/middleware/twilio-validation.ts +5 -7
  668. package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +28 -0
  669. package/src/runtime/migrations/origin-mode.ts +6 -6
  670. package/src/runtime/pre-first-message-gate.ts +1 -0
  671. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +6 -35
  672. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +24 -3
  673. package/src/runtime/routes/__tests__/contact-routes.test.ts +80 -15
  674. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +7 -1
  675. package/src/runtime/routes/__tests__/global-search-routes.test.ts +0 -3
  676. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +142 -1
  677. package/src/runtime/routes/__tests__/invite-relay-routes.test.ts +119 -30
  678. package/src/runtime/routes/__tests__/migration-import-allowed-hosts-env.test.ts +70 -0
  679. package/src/runtime/routes/__tests__/monitoring-routes.test.ts +194 -0
  680. package/src/runtime/routes/__tests__/plugins-routes.test.ts +755 -29
  681. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +4 -0
  682. package/src/runtime/routes/__tests__/schedule-routes-create.test.ts +70 -0
  683. package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +161 -0
  684. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +1 -1
  685. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +1 -1
  686. package/src/runtime/routes/acp-routes.ts +15 -3
  687. package/src/runtime/routes/app-management-routes.ts +4 -7
  688. package/src/runtime/routes/approval-routes.ts +4 -0
  689. package/src/runtime/routes/attachment-routes.ts +54 -3
  690. package/src/runtime/routes/avatar-routes.ts +0 -4
  691. package/src/runtime/routes/background-tool-routes.ts +34 -5
  692. package/src/runtime/routes/btw-routes.ts +32 -4
  693. package/src/runtime/routes/channel-route-shared.ts +18 -14
  694. package/src/runtime/routes/channel-verification-routes.ts +0 -3
  695. package/src/runtime/routes/contact-routes.ts +298 -117
  696. package/src/runtime/routes/conversation-list-routes.ts +6 -0
  697. package/src/runtime/routes/conversation-management-routes.ts +74 -37
  698. package/src/runtime/routes/conversation-query-routes.ts +5 -2
  699. package/src/runtime/routes/conversation-routes.ts +222 -82
  700. package/src/runtime/routes/conversations-import-routes.ts +8 -1
  701. package/src/runtime/routes/credential-prompt-routes.ts +20 -5
  702. package/src/runtime/routes/debug-routes.ts +1 -0
  703. package/src/runtime/routes/documents-routes.ts +5 -2
  704. package/src/runtime/routes/empty-state-greeting-cache.ts +37 -10
  705. package/src/runtime/routes/global-search-routes.ts +2 -1
  706. package/src/runtime/routes/guardian-approval-interception.ts +51 -0
  707. package/src/runtime/routes/home-feed-routes.ts +60 -1
  708. package/src/runtime/routes/identity-routes.ts +4 -87
  709. package/src/runtime/routes/inbound-message-handler.ts +82 -41
  710. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +430 -57
  711. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +302 -572
  712. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +76 -270
  713. package/src/runtime/routes/inbound-stages/background-dispatch.ts +21 -116
  714. package/src/runtime/routes/inbound-stages/edit-intercept.ts +9 -1
  715. package/src/runtime/routes/inbound-stages/escalation-intercept.test.ts +158 -0
  716. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +22 -1
  717. package/src/runtime/routes/index.ts +6 -0
  718. package/src/runtime/routes/inference-provider-connection-routes.ts +22 -16
  719. package/src/runtime/routes/internal-twilio-routes.ts +0 -15
  720. package/src/runtime/routes/messages-lexical-routes.ts +75 -0
  721. package/src/runtime/routes/migration-routes.ts +69 -6
  722. package/src/runtime/routes/monitoring-routes.ts +237 -0
  723. package/src/runtime/routes/plugins-routes.ts +329 -8
  724. package/src/runtime/routes/schedule-routes.ts +49 -8
  725. package/src/runtime/routes/schedule-worker-routes.ts +210 -0
  726. package/src/runtime/routes/secret-routes.ts +1 -1
  727. package/src/runtime/routes/surface-conversation-resolver.ts +1 -0
  728. package/src/runtime/services/__tests__/analyze-conversation.test.ts +54 -9
  729. package/src/runtime/services/__tests__/auto-analysis-enqueue.test.ts +30 -6
  730. package/src/runtime/services/__tests__/conversation-analyze-job.test.ts +43 -8
  731. package/src/runtime/services/__tests__/conversation-serializer.test.ts +20 -0
  732. package/src/runtime/services/analyze-conversation.ts +14 -4
  733. package/src/runtime/services/conversation-serializer.ts +5 -0
  734. package/src/runtime/slack-no-response.ts +47 -0
  735. package/src/runtime/slack-reply-session.test.ts +633 -0
  736. package/src/runtime/slack-reply-session.ts +382 -0
  737. package/src/runtime/slack-task-progress.ts +95 -0
  738. package/src/runtime/sync/resource-sync-events.ts +25 -0
  739. package/src/runtime/tool-grant-request-helper.ts +17 -1
  740. package/src/runtime/trust-class.test.ts +141 -0
  741. package/src/runtime/trust-class.ts +121 -0
  742. package/src/runtime/trust-verdict-consumer.ts +7 -6
  743. package/src/schedule/__tests__/run-script.test.ts +21 -0
  744. package/src/schedule/__tests__/worker-control.test.ts +241 -0
  745. package/src/schedule/run-script.ts +13 -6
  746. package/src/schedule/schedule-usage-store.ts +1 -0
  747. package/src/schedule/scheduler.ts +178 -127
  748. package/src/schedule/worker-control.ts +118 -0
  749. package/src/schedule/worker.ts +131 -0
  750. package/src/skills/__tests__/categories-cache.test.ts +38 -0
  751. package/src/skills/categories-cache.ts +40 -5
  752. package/src/skills/managed-store.ts +41 -6
  753. package/src/skills/validate-input.ts +47 -0
  754. package/src/stt/types.ts +5 -9
  755. package/src/subagent/manager.ts +267 -39
  756. package/src/subagent/types.ts +6 -2
  757. package/src/subagent/validate-allowlists.ts +65 -0
  758. package/src/telemetry/watchdog-direct-emit.test.ts +109 -0
  759. package/src/telemetry/watchdog-direct-emit.ts +89 -0
  760. package/src/tools/acp/list-agents.test.ts +0 -6
  761. package/src/tools/background-tool-registry.ts +61 -0
  762. package/src/tools/document/document-comment-tool.test.ts +4 -0
  763. package/src/tools/host-terminal/host-shell.test.ts +397 -0
  764. package/src/tools/host-terminal/host-shell.ts +203 -14
  765. package/src/tools/skills/find-similar-skills.test.ts +152 -1
  766. package/src/tools/skills/find-similar-skills.ts +30 -4
  767. package/src/tools/skills/load.ts +36 -0
  768. package/src/tools/skills/scaffold-managed.ts +66 -34
  769. package/src/tools/skills/skill-script-runner.ts +17 -0
  770. package/src/tools/skills/skill-tool-factory.ts +5 -1
  771. package/src/tools/terminal/safe-env.ts +3 -0
  772. package/src/tools/terminal/shell.test.ts +177 -0
  773. package/src/tools/terminal/shell.ts +98 -4
  774. package/src/tools/tool-approval-handler.ts +106 -29
  775. package/{node_modules/@vellumai/skill-host-contracts/src → src/tools}/tool-types.ts +16 -17
  776. package/src/tools/types.ts +29 -17
  777. package/src/tools/ui-surface/definitions.ts +3 -1
  778. package/src/tts/__tests__/provider-adapters.test.ts +37 -0
  779. package/src/tts/__tests__/provider-catalog.test.ts +21 -0
  780. package/src/tts/provider-catalog.ts +40 -9
  781. package/src/tts/providers/fish-audio-provider.ts +19 -10
  782. package/src/tts/types.ts +3 -4
  783. package/src/util/__tests__/process-tree.test.ts +10 -2
  784. package/src/util/__tests__/text-spacing.test.ts +53 -0
  785. package/src/util/cgroup-memory.ts +142 -0
  786. package/src/util/image-conversion.ts +281 -0
  787. package/src/util/phone.ts +1 -8
  788. package/src/util/platform.ts +33 -0
  789. package/src/util/process-tree.ts +10 -2
  790. package/src/util/text-spacing.ts +53 -0
  791. package/src/util/worker-process.ts +271 -0
  792. package/src/workflows/journal-store.ts +16 -1
  793. package/src/workflows/library.test.ts +35 -20
  794. package/src/workflows/library.ts +105 -66
  795. package/src/workspace/git-service.ts +1 -0
  796. package/src/workspace/migrations/011-backfill-installation-id.ts +83 -31
  797. package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +41 -22
  798. package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +1 -1
  799. package/src/workspace/migrations/118-seed-now-md.ts +64 -0
  800. package/src/workspace/migrations/119-strip-persisted-memory-v3-tuning-defaults.ts +169 -0
  801. package/src/workspace/migrations/120-revise-onboarding-threads.ts +47 -0
  802. package/src/workspace/migrations/121-seed-default-user-guardrails.ts +82 -0
  803. package/src/workspace/migrations/122-relocate-default-user-boundary.ts +111 -0
  804. package/src/workspace/migrations/123-swap-quality-profile-to-fable.ts +85 -0
  805. package/src/workspace/migrations/__tests__/119-strip-persisted-memory-v3-tuning-defaults.test.ts +181 -0
  806. package/src/workspace/migrations/registry.ts +12 -0
  807. package/drizzle/0000_dizzy_maggott.sql +0 -301
  808. package/drizzle/meta/0000_snapshot.json +0 -1933
  809. package/drizzle/meta/_journal.json +0 -13
  810. package/node_modules/@vellumai/skill-host-contracts/__tests__/client.test.ts +0 -887
  811. package/node_modules/@vellumai/skill-host-contracts/bun.lock +0 -24
  812. package/node_modules/@vellumai/skill-host-contracts/package.json +0 -18
  813. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -97
  814. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +0 -1349
  815. package/node_modules/@vellumai/skill-host-contracts/src/index.ts +0 -6
  816. package/node_modules/@vellumai/skill-host-contracts/src/runtime-mode.ts +0 -11
  817. package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +0 -32
  818. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +0 -333
  819. package/node_modules/@vellumai/skill-host-contracts/tsconfig.json +0 -20
  820. package/node_modules/@vellumai/skill-host-contracts/tsconfig.test.json +0 -12
  821. package/src/__tests__/invite-redemption-service.test.ts +0 -1180
  822. package/src/__tests__/invite-service-ipc.test.ts +0 -350
  823. package/src/__tests__/job-handler-registry-guard.test.ts +0 -125
  824. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +0 -203
  825. package/src/__tests__/relay-server.test.ts +0 -6153
  826. package/src/__tests__/telephony-stt-routing.test.ts +0 -329
  827. package/src/__tests__/voice-invite-redemption.test.ts +0 -702
  828. package/src/calls/relay-server.ts +0 -2463
  829. package/src/calls/telephony-stt-routing.ts +0 -145
  830. package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +0 -128
  831. package/src/daemon/__tests__/daemon-skill-host.test.ts +0 -273
  832. package/src/daemon/daemon-skill-host.ts +0 -262
  833. package/src/daemon/message-types/meet.ts +0 -143
  834. package/src/daemon/shutdown-registry.ts +0 -40
  835. package/src/ipc/__tests__/skill-server-bidirectional.test.ts +0 -253
  836. package/src/ipc/__tests__/skill-server.test.ts +0 -182
  837. package/src/ipc/skill-ipc-types.ts +0 -54
  838. package/src/ipc/skill-routes/__tests__/config.test.ts +0 -146
  839. package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +0 -448
  840. package/src/ipc/skill-routes/__tests__/identity.test.ts +0 -62
  841. package/src/ipc/skill-routes/__tests__/log.test.ts +0 -133
  842. package/src/ipc/skill-routes/__tests__/memory.test.ts +0 -203
  843. package/src/ipc/skill-routes/__tests__/platform.test.ts +0 -111
  844. package/src/ipc/skill-routes/__tests__/providers.test.ts +0 -265
  845. package/src/ipc/skill-routes/__tests__/registries.test.ts +0 -400
  846. package/src/ipc/skill-routes/config.ts +0 -47
  847. package/src/ipc/skill-routes/events.ts +0 -147
  848. package/src/ipc/skill-routes/identity.ts +0 -21
  849. package/src/ipc/skill-routes/index.ts +0 -37
  850. package/src/ipc/skill-routes/log.ts +0 -40
  851. package/src/ipc/skill-routes/memory.ts +0 -92
  852. package/src/ipc/skill-routes/platform.ts +0 -39
  853. package/src/ipc/skill-routes/providers.ts +0 -162
  854. package/src/ipc/skill-routes/registries.ts +0 -338
  855. package/src/ipc/skill-server.ts +0 -831
  856. package/src/ipc/skill-socket-path.ts +0 -20
  857. package/src/jobs/register-job-handlers.ts +0 -74
  858. package/src/persistence/invite-store.ts +0 -481
  859. package/src/plugins/job-handler-registry.ts +0 -94
  860. package/src/providers/cache-control.ts +0 -26
  861. package/src/runtime/__tests__/runtime-mode.test.ts +0 -62
  862. package/src/runtime/invite-redemption-service.ts +0 -828
  863. package/src/runtime/invite-redemption-templates.ts +0 -46
  864. package/src/runtime/runtime-mode.ts +0 -40
  865. package/src/runtime/slack-dm-text-delivery.ts +0 -177
  866. package/src/util/voice-code.ts +0 -31
@@ -24,45 +24,44 @@ mock.module("../telegram/bot-username.js", () => ({
24
24
  }));
25
25
 
26
26
  // Mock startInviteCall from call-domain — test env lacks Twilio credentials.
27
+ // Captures the last input so trigger-call tests can assert the gateway-supplied
28
+ // fields flow through to the provider call.
27
29
  let mockStartInviteCallResult:
28
30
  | { ok: true; callSid: string }
29
31
  | { ok: false; error: string; status?: number } = {
30
32
  ok: true,
31
33
  callSid: "CA_test_sid_123",
32
34
  };
35
+ let lastStartInviteCallInput: Record<string, unknown> | null = null;
33
36
  mock.module("../calls/call-domain.js", () => ({
34
- startInviteCall: async () => mockStartInviteCallResult,
37
+ startInviteCall: async (input: Record<string, unknown>) => {
38
+ lastStartInviteCallInput = input;
39
+ return mockStartInviteCallResult;
40
+ },
35
41
  }));
36
42
 
37
- // Model the gateway: the redemption claim (record_invite_redemption) and the
38
- // gateway-owned activation (upsert_verified_channel) are both relayed. The
39
- // activation write fails closed in production, so the mock must serve a
40
- // verified upsert for the legitimate-success redemption paths.
41
- const gatewayIpc = {
42
- claim: { ok: true, updated: true, mirrored: true },
43
- activationVerified: true,
44
- };
43
+ // Model the gateway `invites_redeem` IPC: the daemon redeem route is a thin
44
+ // relay (the gateway redemption engine owns validation, the atomic claim,
45
+ // and the ACL write), so the mock captures the relayed params and serves a
46
+ // scripted gateway response or a scripted relay failure.
47
+ import { IpcCallError } from "@vellumai/gateway-client/ipc-client";
48
+
49
+ const redeemRelay: {
50
+ calls: Array<Record<string, unknown> | undefined>;
51
+ result: unknown;
52
+ error: Error | null;
53
+ } = { calls: [], result: undefined, error: null };
45
54
  mock.module("../ipc/gateway-client.js", () => ({
46
55
  ipcCallPersistent: async (
47
56
  method: string,
48
57
  params?: Record<string, unknown>,
49
58
  ) => {
50
- if (method === "record_invite_redemption") return gatewayIpc.claim;
51
- if (method === "upsert_verified_channel") {
52
- if (!gatewayIpc.activationVerified) return { ok: true, verified: false };
53
- return {
54
- ok: true,
55
- verified: true,
56
- channel: {
57
- id: "gw-channel-id",
58
- contactId: (params?.contactId as string) ?? "gw-contact",
59
- type: (params?.type as string) ?? "telegram",
60
- address: (params?.address as string) ?? "gw-addr",
61
- status: "active",
62
- verifiedAt: 1,
63
- verifiedVia: (params?.verifiedVia as string) ?? "invite",
64
- },
65
- };
59
+ if (method === "invites_redeem") {
60
+ redeemRelay.calls.push(params);
61
+ if (redeemRelay.error) {
62
+ throw redeemRelay.error;
63
+ }
64
+ return redeemRelay.result;
66
65
  }
67
66
  return undefined;
68
67
  },
@@ -71,54 +70,48 @@ mock.module("../ipc/gateway-client.js", () => ({
71
70
  import { upsertContact } from "../contacts/contact-store.js";
72
71
  import { getSqlite } from "../persistence/db-connection.js";
73
72
  import { initializeDb } from "../persistence/db-init.js";
74
- import { revokeInvite } from "../persistence/invite-store.js";
75
73
  import {
76
- createIngressInvite,
74
+ composeInvitePresentation,
77
75
  triggerInviteCall,
78
76
  } from "../runtime/invite-service.js";
79
77
  import { handleRedeemInvite as _handleRedeemInvite } from "../runtime/routes/contact-routes.js";
80
78
  import { RouteError } from "../runtime/routes/errors.js";
81
79
 
82
80
  /**
83
- * The CLI/HTTP create/list/revoke/trigger route handlers relay to the gateway
84
- * (see invite-relay-routes.test.ts). The assistant-native invite logic the
85
- * gateway invokes via `invites_mint` + its native handlers is exercised here
86
- * against the assistant DB through the `invite-service` functions. Redemption
87
- * stays daemon-local and is driven through the route handler.
81
+ * Invite create/list/revoke/redeem are gateway-native (the daemon route
82
+ * handlers relay; the gateway engine is exercised in
83
+ * gateway/src/__tests__/invite-redemption-engine*.test.ts and the gateway
84
+ * handlers in contacts-control-plane-proxy.test.ts +
85
+ * ipc-invite-routes.test.ts). What stays daemon-local the redeem relay
86
+ * dispatch, the outbound call trigger, and the presentation composition
87
+ * layered onto the gateway's create payload — is exercised here.
88
88
  */
89
89
  function fakeResponse(body: unknown, status = 200) {
90
90
  return { status, json: async () => body };
91
91
  }
92
92
 
93
- async function handleCreateInvite(req: Request) {
94
- const body = (await req.json()) as Record<string, string | number>;
95
- const result = await createIngressInvite({
96
- sourceChannel: body.sourceChannel as string | undefined,
97
- note: body.note as string | undefined,
98
- maxUses: body.maxUses as number | undefined,
99
- expiresInMs: body.expiresInMs as number | undefined,
100
- expectedExternalUserId: body.expectedExternalUserId as string | undefined,
101
- contactId: body.contactId as string,
102
- });
103
- if (!result.ok) return fakeResponse({ ok: false, error: result.error }, 400);
104
- return fakeResponse({ ok: true, invite: result.data }, 201);
105
- }
106
-
107
93
  async function handleRedeemInvite(req: Request) {
108
94
  const body = (await req.json()) as Record<string, unknown>;
109
95
  try {
110
96
  const result = await _handleRedeemInvite({ body });
111
97
  return fakeResponse(result);
112
98
  } catch (err) {
113
- if (err instanceof RouteError)
99
+ if (err instanceof RouteError) {
114
100
  return fakeResponse({ ok: false, error: err.message }, err.statusCode);
101
+ }
115
102
  throw err;
116
103
  }
117
104
  }
118
105
 
119
- async function handleTriggerInviteCall(inviteId: string) {
120
- const result = await triggerInviteCall(inviteId);
121
- if (!result.ok) return fakeResponse({ ok: false, error: result.error }, 400);
106
+ async function handleTriggerInviteCall(params: {
107
+ phoneNumber?: string;
108
+ friendName?: string | null;
109
+ guardianName?: string | null;
110
+ }) {
111
+ const result = await triggerInviteCall(params);
112
+ if (!result.ok) {
113
+ return fakeResponse({ ok: false, error: result.error }, 400);
114
+ }
122
115
  return fakeResponse({ ok: true, callSid: result.data.callSid });
123
116
  }
124
117
 
@@ -130,129 +123,145 @@ function createTargetContact(displayName = "Test Contact"): string {
130
123
  }
131
124
 
132
125
  function resetTables() {
133
- getSqlite().run("DELETE FROM assistant_ingress_invites");
134
126
  getSqlite().run("DELETE FROM contact_channels");
135
127
  getSqlite().run("DELETE FROM contacts");
136
128
  }
137
129
 
130
+ function resetRedeemRelay() {
131
+ redeemRelay.calls.length = 0;
132
+ redeemRelay.result = undefined;
133
+ redeemRelay.error = null;
134
+ }
135
+
138
136
  // ---------------------------------------------------------------------------
139
- // Invite routes
137
+ // Redeem relay (gateway-native redemption behind the daemon route)
140
138
  // ---------------------------------------------------------------------------
141
139
 
142
- describe("ingress invite HTTP routes", () => {
143
- beforeEach(resetTables);
140
+ describe("invite redeem relay routes", () => {
141
+ beforeEach(() => {
142
+ resetTables();
143
+ resetRedeemRelay();
144
+ });
144
145
 
145
- test("POST /v1/contacts/invites — creates an invite", async () => {
146
- const req = new Request("http://localhost/v1/contacts/invites", {
146
+ test("POST /v1/contacts/invites/redeemtoken body relays to the gateway and returns its payload", async () => {
147
+ redeemRelay.result = {
148
+ ok: true,
149
+ invite: { id: "inv-gw-1", status: "redeemed", useCount: 1 },
150
+ type: "redeemed",
151
+ };
152
+
153
+ const req = new Request("http://localhost/v1/contacts/invites/redeem", {
147
154
  method: "POST",
148
155
  headers: { "Content-Type": "application/json" },
149
156
  body: JSON.stringify({
157
+ token: "raw-token-1",
158
+ externalUserId: "redeemer-1",
159
+ externalChatId: "chat-9",
150
160
  sourceChannel: "telegram",
151
- contactId: createTargetContact(),
152
- note: "Test invite",
153
- maxUses: 5,
161
+ displayName: "Alice Example",
162
+ username: "alice",
163
+ notAContractField: "dropped",
154
164
  }),
155
165
  });
156
166
 
157
- const res = await handleCreateInvite(req);
167
+ const res = await handleRedeemInvite(req);
158
168
  const body = (await res.json()) as Record<string, unknown>;
159
169
 
160
- expect(res.status).toBe(201);
170
+ expect(res.status).toBe(200);
161
171
  expect(body.ok).toBe(true);
162
- const invite = body.invite as Record<string, unknown>;
163
- expect(invite.sourceChannel).toBe("telegram");
164
- expect(invite.note).toBe("Test invite");
165
- expect(invite.maxUses).toBe(5);
166
- expect(invite.status).toBe("active");
167
- // Raw token should be returned on create
168
- expect(typeof invite.token).toBe("string");
169
- expect((invite.token as string).length).toBeGreaterThan(0);
172
+ expect((body.invite as Record<string, unknown>).id).toBe("inv-gw-1");
173
+ expect(body.type).toBe("redeemed");
174
+ // The relay forwards every shared-contract field — including the sender
175
+ // identity fields (displayName/username) the gateway engine stamps onto
176
+ // the new member — and nothing voice-shaped or off-contract.
177
+ expect(redeemRelay.calls).toEqual([
178
+ {
179
+ token: "raw-token-1",
180
+ sourceChannel: "telegram",
181
+ externalUserId: "redeemer-1",
182
+ externalChatId: "chat-9",
183
+ displayName: "Alice Example",
184
+ username: "alice",
185
+ },
186
+ ]);
170
187
  });
171
188
 
172
- test("POST /v1/contacts/invites — includes canonical share URL when bot username is configured", async () => {
173
- mockTelegramBotUsername = "test_invite_bot";
189
+ test("POST /v1/contacts/invites/redeemvoice body relays code + caller and returns the voice shape", async () => {
190
+ redeemRelay.result = {
191
+ ok: true,
192
+ type: "redeemed",
193
+ memberId: "ct-target",
194
+ inviteId: "inv-gw-2",
195
+ };
196
+ const code = "123456";
174
197
 
175
- try {
176
- const req = new Request("http://localhost/v1/contacts/invites", {
177
- method: "POST",
178
- headers: { "Content-Type": "application/json" },
179
- body: JSON.stringify({
180
- sourceChannel: "telegram",
181
- contactId: createTargetContact(),
182
- note: "Share link test",
183
- }),
184
- });
198
+ const req = new Request("http://localhost/v1/contacts/invites/redeem", {
199
+ method: "POST",
200
+ headers: { "Content-Type": "application/json" },
201
+ body: JSON.stringify({
202
+ callerExternalUserId: "+15551234567",
203
+ code,
204
+ assistantId: "asst-1",
205
+ }),
206
+ });
185
207
 
186
- const res = await handleCreateInvite(req);
187
- const body = (await res.json()) as Record<string, unknown>;
188
- const invite = body.invite as Record<string, unknown>;
189
- const token = invite.token as string;
190
- const share = invite.share as Record<string, unknown>;
208
+ const res = await handleRedeemInvite(req);
209
+ const body = (await res.json()) as Record<string, unknown>;
191
210
 
192
- expect(res.status).toBe(201);
193
- expect(body.ok).toBe(true);
194
- expect(typeof token).toBe("string");
195
- expect(token.length).toBeGreaterThan(0);
196
- expect(share).toBeDefined();
197
- expect(share.url).toBe(`https://t.me/test_invite_bot?start=iv_${token}`);
198
- expect(typeof share.displayText).toBe("string");
199
- } finally {
200
- mockTelegramBotUsername = undefined;
201
- }
211
+ expect(res.status).toBe(200);
212
+ expect(body).toEqual({
213
+ ok: true,
214
+ type: "redeemed",
215
+ memberId: "ct-target",
216
+ inviteId: "inv-gw-2",
217
+ });
218
+ expect(redeemRelay.calls).toEqual([
219
+ { code, callerExternalUserId: "+15551234567", assistantId: "asst-1" },
220
+ ]);
202
221
  });
203
222
 
204
- test("POST /v1/contacts/invites — missing sourceChannel returns 400", async () => {
205
- const req = new Request("http://localhost/v1/contacts/invites", {
223
+ test("POST /v1/contacts/invites/redeemgateway 400 (engine reason) surfaces as 400", async () => {
224
+ redeemRelay.error = new IpcCallError("invalid_or_expired", {
225
+ statusCode: 400,
226
+ errorCode: "BAD_REQUEST",
227
+ });
228
+
229
+ const req = new Request("http://localhost/v1/contacts/invites/redeem", {
206
230
  method: "POST",
207
231
  headers: { "Content-Type": "application/json" },
208
- body: JSON.stringify({ note: "No channel" }),
232
+ body: JSON.stringify({
233
+ callerExternalUserId: "+15551234567",
234
+ code: "000000",
235
+ }),
209
236
  });
210
237
 
211
- const res = await handleCreateInvite(req);
238
+ const res = await handleRedeemInvite(req);
212
239
  const body = (await res.json()) as { ok: boolean; error: string };
213
240
 
214
241
  expect(res.status).toBe(400);
215
242
  expect(body.ok).toBe(false);
216
- expect(body.error).toContain("sourceChannel");
243
+ expect(body.error).toBe("invalid_or_expired");
217
244
  });
218
245
 
219
- test("POST /v1/contacts/invites/redeem — redeems an invite", async () => {
220
- // Create an invite first
221
- const createRes = await handleCreateInvite(
222
- new Request("http://localhost/v1/contacts/invites", {
223
- method: "POST",
224
- headers: { "Content-Type": "application/json" },
225
- body: JSON.stringify({
226
- sourceChannel: "telegram",
227
- contactId: createTargetContact(),
228
- maxUses: 1,
229
- }),
230
- }),
231
- );
232
- const created = (await createRes.json()) as { invite: { token: string } };
246
+ test("POST /v1/contacts/invites/redeem — gateway unreachable fails CLOSED (500)", async () => {
247
+ // The gateway is the single redemption authority; a relay failure must
248
+ // surface as an error, never a locally-decided redemption.
249
+ redeemRelay.error = new IpcCallError("gateway unreachable");
233
250
 
234
251
  const req = new Request("http://localhost/v1/contacts/invites/redeem", {
235
252
  method: "POST",
236
253
  headers: { "Content-Type": "application/json" },
237
254
  body: JSON.stringify({
238
- token: created.invite.token,
239
- externalUserId: "redeemer-1",
240
- sourceChannel: "telegram",
255
+ callerExternalUserId: "+15551234567",
256
+ code: "123456",
241
257
  }),
242
258
  });
243
259
 
244
260
  const res = await handleRedeemInvite(req);
245
- const body = (await res.json()) as Record<string, unknown>;
246
-
247
- expect(res.status).toBe(200);
248
- expect(body.ok).toBe(true);
249
- const invite = body.invite as Record<string, unknown>;
250
- expect(invite.useCount).toBe(1);
251
- // Single-use invite should be fully redeemed
252
- expect(invite.status).toBe("redeemed");
261
+ expect(res.status).toBe(500);
253
262
  });
254
263
 
255
- test("POST /v1/contacts/invites/redeem — missing token returns 400", async () => {
264
+ test("POST /v1/contacts/invites/redeem — missing token returns 400 without relaying", async () => {
256
265
  const req = new Request("http://localhost/v1/contacts/invites/redeem", {
257
266
  method: "POST",
258
267
  headers: { "Content-Type": "application/json" },
@@ -262,284 +271,107 @@ describe("ingress invite HTTP routes", () => {
262
271
  const res = await handleRedeemInvite(req);
263
272
  const body = (await res.json()) as { ok: boolean; error: string };
264
273
 
274
+ // No `code` and no `token` → token path; the shared contract schema
275
+ // rejects daemon-side before any gateway round-trip.
265
276
  expect(res.status).toBe(400);
266
277
  expect(body.ok).toBe(false);
267
278
  expect(body.error).toContain("token");
279
+ expect(redeemRelay.calls.length).toBe(0);
268
280
  });
269
281
 
270
- test("POST /v1/contacts/invites/redeem — invalid token returns 400", async () => {
282
+ test("POST /v1/contacts/invites/redeem — voice code without caller identity is rejected daemon-side", async () => {
271
283
  const req = new Request("http://localhost/v1/contacts/invites/redeem", {
272
284
  method: "POST",
273
285
  headers: { "Content-Type": "application/json" },
274
- body: JSON.stringify({ token: "invalid-token" }),
286
+ body: JSON.stringify({ code: "123456" }),
275
287
  });
276
288
 
277
289
  const res = await handleRedeemInvite(req);
278
- const body = (await res.json()) as Record<string, unknown>;
290
+ const body = (await res.json()) as { ok: boolean; error: string };
279
291
 
280
292
  expect(res.status).toBe(400);
281
293
  expect(body.ok).toBe(false);
294
+ expect(body.error).toContain("callerExternalUserId");
295
+ // Rejected before any gateway round-trip.
296
+ expect(redeemRelay.calls.length).toBe(0);
282
297
  });
283
298
  });
284
299
 
285
300
  // ---------------------------------------------------------------------------
286
- // Shared logic round-trip
287
- // ---------------------------------------------------------------------------
288
-
289
- describe("ingress service shared logic", () => {
290
- beforeEach(resetTables);
291
-
292
- test("invite create + revoke round-trip through shared service", async () => {
293
- const createRes = await handleCreateInvite(
294
- new Request("http://localhost/v1/contacts/invites", {
295
- method: "POST",
296
- headers: { "Content-Type": "application/json" },
297
- body: JSON.stringify({
298
- sourceChannel: "telegram",
299
- contactId: createTargetContact(),
300
- }),
301
- }),
302
- );
303
- const created = (await createRes.json()) as {
304
- invite: { id: string; status: string };
305
- };
306
- expect(created.invite.status).toBe("active");
307
-
308
- const revoked = revokeInvite(created.invite.id);
309
- expect(revoked?.status).toBe("revoked");
310
- expect(revoked?.id).toBe(created.invite.id);
311
- });
312
- });
313
-
314
- // ---------------------------------------------------------------------------
315
- // Voice invite routes
301
+ // Presentation composition (daemon-owned layer over the gateway mint payload)
316
302
  // ---------------------------------------------------------------------------
317
303
 
318
- describe("voice invite HTTP routes", () => {
304
+ describe("composeInvitePresentation", () => {
319
305
  beforeEach(resetTables);
320
306
 
321
- test("POST /v1/contacts/invites with sourceChannel voice — creates invite with voiceCode, stores hash only", async () => {
322
- const req = new Request("http://localhost/v1/contacts/invites", {
323
- method: "POST",
324
- headers: { "Content-Type": "application/json" },
325
- body: JSON.stringify({
326
- sourceChannel: "phone",
327
- contactId: createTargetContact("Alice"),
328
- expectedExternalUserId: "+15551234567",
329
- maxUses: 3,
330
- }),
331
- });
332
-
333
- const res = await handleCreateInvite(req);
334
- const body = (await res.json()) as Record<string, unknown>;
335
-
336
- expect(res.status).toBe(201);
337
- expect(body.ok).toBe(true);
338
- const invite = body.invite as Record<string, unknown>;
339
- expect(invite.sourceChannel).toBe("phone");
340
- // Voice code should be returned (6 digits by default)
341
- expect(typeof invite.voiceCode).toBe("string");
342
- expect((invite.voiceCode as string).length).toBe(6);
343
- expect(/^\d{6}$/.test(invite.voiceCode as string)).toBe(true);
344
- // Hash should be stored
345
- expect(typeof invite.tokenHash).toBe("string");
346
- expect((invite.tokenHash as string).length).toBeGreaterThan(0);
347
- // voiceCodeDigits should be recorded
348
- expect(invite.voiceCodeDigits).toBe(6);
349
- // expectedExternalUserId should be recorded
350
- expect(invite.expectedExternalUserId).toBe("+15551234567");
351
- // friendName is mirrored from the bound contact's displayName, not a flag
352
- expect(invite.friendName).toBe("Alice");
353
- });
354
-
355
- test("voice invite creation requires expectedExternalUserId", async () => {
356
- const req = new Request("http://localhost/v1/contacts/invites", {
357
- method: "POST",
358
- headers: { "Content-Type": "application/json" },
359
- body: JSON.stringify({
360
- sourceChannel: "phone",
361
- contactId: createTargetContact(),
362
- }),
363
- });
364
-
365
- const res = await handleCreateInvite(req);
366
- const body = (await res.json()) as Record<string, unknown>;
367
-
368
- expect(res.status).toBe(400);
369
- expect(body.ok).toBe(false);
370
- expect(body.error).toContain("expectedExternalUserId");
371
- });
372
-
373
- test("voice invite creation validates E.164 format", async () => {
374
- const req = new Request("http://localhost/v1/contacts/invites", {
375
- method: "POST",
376
- headers: { "Content-Type": "application/json" },
377
- body: JSON.stringify({
378
- sourceChannel: "phone",
379
- contactId: createTargetContact(),
380
- expectedExternalUserId: "not-a-phone-number",
381
- }),
382
- });
383
-
384
- const res = await handleCreateInvite(req);
385
- const body = (await res.json()) as Record<string, unknown>;
386
-
387
- expect(res.status).toBe(400);
388
- expect(body.ok).toBe(false);
389
- expect(body.error).toContain("E.164");
390
- });
391
-
392
- test("voiceCodeDigits is always 6 — custom values are ignored", async () => {
393
- const req = new Request("http://localhost/v1/contacts/invites", {
394
- method: "POST",
395
- headers: { "Content-Type": "application/json" },
396
- body: JSON.stringify({
397
- sourceChannel: "phone",
398
- contactId: createTargetContact(),
399
- expectedExternalUserId: "+15551234567",
400
- voiceCodeDigits: 8,
401
- }),
402
- });
403
-
404
- const res = await handleCreateInvite(req);
405
- const body = (await res.json()) as Record<string, unknown>;
307
+ test("voice invites get a guardianInstruction with the contact's first name", async () => {
308
+ const contactId = createTargetContact("Carolina Example");
406
309
 
407
- expect(res.status).toBe(201);
408
- expect(body.ok).toBe(true);
409
- const invite = body.invite as Record<string, unknown>;
410
- expect((invite.voiceCode as string).length).toBe(6);
411
- expect(invite.voiceCodeDigits).toBe(6);
412
- });
413
-
414
- test("voice invites do NOT return token in response", async () => {
415
- const req = new Request("http://localhost/v1/contacts/invites", {
416
- method: "POST",
417
- headers: { "Content-Type": "application/json" },
418
- body: JSON.stringify({
310
+ const invite = await composeInvitePresentation({
311
+ contactId,
312
+ invite: {
313
+ id: "inv-voice-1",
419
314
  sourceChannel: "phone",
420
- contactId: createTargetContact(),
421
- expectedExternalUserId: "+15551234567",
422
- }),
315
+ voiceCode: "123456",
316
+ voiceCodeDigits: 6,
317
+ },
423
318
  });
424
319
 
425
- const res = await handleCreateInvite(req);
426
- const body = (await res.json()) as Record<string, unknown>;
427
-
428
- expect(res.status).toBe(201);
429
- const invite = body.invite as Record<string, unknown>;
430
- // Voice invites must not expose the raw token — callers redeem via
431
- // the identity-bound voice code flow
432
- expect(invite.token).toBeUndefined();
433
- });
434
-
435
- test("POST /v1/contacts/invites/redeem — redeems a voice invite code via unified endpoint", async () => {
436
- // Create a voice invite
437
- const createRes = await handleCreateInvite(
438
- new Request("http://localhost/v1/contacts/invites", {
439
- method: "POST",
440
- headers: { "Content-Type": "application/json" },
441
- body: JSON.stringify({
442
- sourceChannel: "phone",
443
- contactId: createTargetContact(),
444
- expectedExternalUserId: "+15551234567",
445
- maxUses: 1,
446
- }),
447
- }),
448
- );
449
- const created = (await createRes.json()) as {
450
- invite: { voiceCode: string };
451
- };
452
-
453
- // Redeem the voice code via the unified /redeem endpoint
454
- const redeemReq = new Request(
455
- "http://localhost/v1/contacts/invites/redeem",
456
- {
457
- method: "POST",
458
- headers: { "Content-Type": "application/json" },
459
- body: JSON.stringify({
460
- callerExternalUserId: "+15551234567",
461
- code: created.invite.voiceCode,
462
- }),
463
- },
320
+ expect(invite.guardianInstruction).toBe(
321
+ "Carolina will need this code when they answer. Share it with them first.",
464
322
  );
465
-
466
- const res = await handleRedeemInvite(redeemReq);
467
- const body = (await res.json()) as Record<string, unknown>;
468
-
469
- expect(res.status).toBe(200);
470
- expect(body.ok).toBe(true);
471
- expect(body.type).toBe("redeemed");
472
- expect(typeof body.memberId).toBe("string");
473
- expect(typeof body.inviteId).toBe("string");
323
+ // Voice invites never gain a share payload (no raw token exists).
324
+ expect(invite.share).toBeUndefined();
474
325
  });
475
326
 
476
- test("POST /v1/contacts/invites/redeem voice code missing fields returns 400", async () => {
477
- const req = new Request("http://localhost/v1/contacts/invites/redeem", {
478
- method: "POST",
479
- headers: { "Content-Type": "application/json" },
480
- body: JSON.stringify({ callerExternalUserId: "+15551234567" }),
327
+ test("voice invites fall back to the generic instruction when no contact name resolves", async () => {
328
+ const invite = await composeInvitePresentation({
329
+ contactId: "missing-contact",
330
+ invite: { id: "inv-voice-2", sourceChannel: "phone" },
481
331
  });
482
332
 
483
- const res = await handleRedeemInvite(req);
484
- const body = (await res.json()) as Record<string, unknown>;
485
-
486
- // No `code` and no `token` → falls through to token-based path which requires token
487
- expect(res.status).toBe(400);
488
- expect(body.ok).toBe(false);
489
- });
490
-
491
- test("POST /v1/contacts/invites/redeem — wrong voice code returns 400", async () => {
492
- // Create a voice invite
493
- await handleCreateInvite(
494
- new Request("http://localhost/v1/contacts/invites", {
495
- method: "POST",
496
- headers: { "Content-Type": "application/json" },
497
- body: JSON.stringify({
498
- sourceChannel: "phone",
499
- contactId: createTargetContact(),
500
- expectedExternalUserId: "+15551234567",
501
- maxUses: 1,
502
- }),
503
- }),
333
+ expect(invite.guardianInstruction).toBe(
334
+ "Share this code with them they'll need it when they answer the call.",
504
335
  );
336
+ });
505
337
 
506
- const req = new Request("http://localhost/v1/contacts/invites/redeem", {
507
- method: "POST",
508
- headers: { "Content-Type": "application/json" },
509
- body: JSON.stringify({
510
- callerExternalUserId: "+15551234567",
511
- code: "000000",
512
- }),
513
- });
338
+ test("telegram invites gain share URL + guardianInstruction when bot username is configured", async () => {
339
+ mockTelegramBotUsername = "test_invite_bot";
514
340
 
515
- const res = await handleRedeemInvite(req);
516
- const body = (await res.json()) as Record<string, unknown>;
341
+ try {
342
+ const rawToken = "raw-token-abc";
343
+ const invite = await composeInvitePresentation({
344
+ contactId: createTargetContact("Alice"),
345
+ invite: {
346
+ id: "inv-tg-1",
347
+ sourceChannel: "telegram",
348
+ token: rawToken,
349
+ inviteCode: "654321",
350
+ },
351
+ rawToken,
352
+ });
517
353
 
518
- expect(res.status).toBe(400);
519
- expect(body.ok).toBe(false);
354
+ const share = invite.share as Record<string, unknown>;
355
+ expect(share).toBeDefined();
356
+ expect(share.url).toBe(`https://t.me/test_invite_bot?start=iv_${rawToken}`);
357
+ expect(typeof share.displayText).toBe("string");
358
+ expect(typeof invite.guardianInstruction).toBe("string");
359
+ expect((invite.guardianInstruction as string).length).toBeGreaterThan(0);
360
+ // One-time secrets from the gateway payload pass through untouched.
361
+ expect(invite.token).toBe(rawToken);
362
+ expect(invite.inviteCode).toBe("654321");
363
+ } finally {
364
+ mockTelegramBotUsername = undefined;
365
+ }
520
366
  });
521
367
 
522
- test("voice invite creation returns guardianInstruction with the contact's first name", async () => {
523
- const req = new Request("http://localhost/v1/contacts/invites", {
524
- method: "POST",
525
- headers: { "Content-Type": "application/json" },
526
- body: JSON.stringify({
527
- sourceChannel: "phone",
528
- contactId: createTargetContact("Carolina Flaherty"),
529
- expectedExternalUserId: "+15551234567",
530
- }),
368
+ test("non-voice payloads without an inviteCode pass through unchanged", async () => {
369
+ const payload = { id: "inv-x", sourceChannel: "telegram", status: "active" };
370
+ const invite = await composeInvitePresentation({
371
+ contactId: createTargetContact(),
372
+ invite: payload,
531
373
  });
532
-
533
- const res = await handleCreateInvite(req);
534
- const body = (await res.json()) as Record<string, unknown>;
535
-
536
- expect(res.status).toBe(201);
537
- expect(body.ok).toBe(true);
538
- const invite = body.invite as Record<string, unknown>;
539
- // First-token of the contact's displayName is used in the instruction
540
- expect(invite.guardianInstruction).toBe(
541
- "Carolina will need this code when they answer. Share it with them first.",
542
- );
374
+ expect(invite).toEqual(payload);
543
375
  });
544
376
  });
545
377
 
@@ -547,86 +379,80 @@ describe("voice invite HTTP routes", () => {
547
379
  // Trigger invite call endpoint
548
380
  // ---------------------------------------------------------------------------
549
381
 
382
+ // Invite lifecycle validation (existence/active/expiry/phone) lives in the
383
+ // gateway's triggerInviteCallNative — see the gateway proxy tests. The daemon
384
+ // only performs the provider call from the gateway-supplied fields.
550
385
  describe("POST /v1/contacts/invites/:id/call", () => {
551
386
  beforeEach(() => {
552
387
  resetTables();
553
388
  mockStartInviteCallResult = { ok: true, callSid: "CA_test_sid_123" };
389
+ lastStartInviteCallInput = null;
554
390
  });
555
391
 
556
- test("triggers a call for an active phone invite", async () => {
557
- const createRes = await handleCreateInvite(
558
- new Request("http://localhost/v1/contacts/invites", {
559
- method: "POST",
560
- headers: { "Content-Type": "application/json" },
561
- body: JSON.stringify({
562
- sourceChannel: "phone",
563
- contactId: createTargetContact(),
564
- expectedExternalUserId: "+15551234567",
565
- }),
566
- }),
567
- );
568
- const created = (await createRes.json()) as { invite: { id: string } };
569
-
570
- const res = await handleTriggerInviteCall(created.invite.id);
392
+ test("places the call from the gateway-supplied fields", async () => {
393
+ const res = await handleTriggerInviteCall({
394
+ phoneNumber: "+15551234567",
395
+ friendName: "Alice",
396
+ guardianName: "Guardian Label",
397
+ });
571
398
  const body = (await res.json()) as Record<string, unknown>;
572
399
 
573
400
  expect(res.status).toBe(200);
574
401
  expect(body.ok).toBe(true);
575
402
  expect(body.callSid).toBe("CA_test_sid_123");
403
+ expect(lastStartInviteCallInput).toEqual({
404
+ phoneNumber: "+15551234567",
405
+ friendName: "Alice",
406
+ guardianName: "Guardian Label",
407
+ });
576
408
  });
577
409
 
578
- test("returns 400 for non-existent invite", async () => {
579
- const res = await handleTriggerInviteCall("nonexistent-id");
410
+ test("returns 400 when phoneNumber is missing (no local invite fallback)", async () => {
411
+ // The gateway row is the lifecycle authority and supplies the call fields;
412
+ // the daemon has no invite store to fall back on.
413
+ const res = await handleTriggerInviteCall({});
580
414
  const body = (await res.json()) as Record<string, unknown>;
581
415
 
582
416
  expect(res.status).toBe(400);
583
417
  expect(body.ok).toBe(false);
584
- expect(body.error).toBe("Invite not found");
418
+ expect(body.error).toBe("phoneNumber is required");
419
+ expect(lastStartInviteCallInput).toBeNull();
585
420
  });
586
421
 
587
- test("returns 400 for a revoked (non-active) invite", async () => {
588
- const createRes = await handleCreateInvite(
589
- new Request("http://localhost/v1/contacts/invites", {
590
- method: "POST",
591
- headers: { "Content-Type": "application/json" },
592
- body: JSON.stringify({
593
- sourceChannel: "phone",
594
- contactId: createTargetContact(),
595
- expectedExternalUserId: "+15551234567",
596
- }),
597
- }),
598
- );
599
- const created = (await createRes.json()) as { invite: { id: string } };
422
+ test("empty friendName falls through to the neutral-greeting contract", async () => {
423
+ const res = await handleTriggerInviteCall({
424
+ phoneNumber: "+15551234567",
425
+ friendName: " ",
426
+ guardianName: "Guardian Label",
427
+ });
600
428
 
601
- // Revoke the invite
602
- revokeInvite(created.invite.id);
429
+ expect(res.status).toBe(200);
430
+ expect(lastStartInviteCallInput).toMatchObject({ friendName: "" });
431
+ });
603
432
 
604
- const res = await handleTriggerInviteCall(created.invite.id);
605
- const body = (await res.json()) as Record<string, unknown>;
433
+ test("null guardianName falls back to the resolved guardian name", async () => {
434
+ const res = await handleTriggerInviteCall({
435
+ phoneNumber: "+15551234567",
436
+ friendName: "Alice",
437
+ guardianName: null,
438
+ });
606
439
 
607
- expect(res.status).toBe(400);
608
- expect(body.ok).toBe(false);
609
- expect(body.error).toBe("Invite is not active");
440
+ expect(res.status).toBe(200);
441
+ // resolveGuardianName() has no persona in this env, so the fallback
442
+ // resolves to the empty-string default rather than the null passthrough.
443
+ expect(typeof lastStartInviteCallInput?.guardianName).toBe("string");
610
444
  });
611
445
 
612
- test("returns 400 for a non-phone invite", async () => {
613
- const createRes = await handleCreateInvite(
614
- new Request("http://localhost/v1/contacts/invites", {
615
- method: "POST",
616
- headers: { "Content-Type": "application/json" },
617
- body: JSON.stringify({
618
- sourceChannel: "telegram",
619
- contactId: createTargetContact(),
620
- }),
621
- }),
622
- );
623
- const created = (await createRes.json()) as { invite: { id: string } };
446
+ test("surfaces a failed provider call as 400", async () => {
447
+ mockStartInviteCallResult = {
448
+ ok: false,
449
+ error: "phone_number must be in E.164 format",
450
+ };
624
451
 
625
- const res = await handleTriggerInviteCall(created.invite.id);
452
+ const res = await handleTriggerInviteCall({ phoneNumber: "+15551234567" });
626
453
  const body = (await res.json()) as Record<string, unknown>;
627
454
 
628
455
  expect(res.status).toBe(400);
629
- expect(body.ok).toBe(false);
630
- expect(body.error).toBe("Only phone invites support call triggering");
456
+ expect(body.error).toBe("phone_number must be in E.164 format");
631
457
  });
632
458
  });