@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
@@ -1,1180 +0,0 @@
1
- import { beforeEach, describe, expect, mock, test } from "bun:test";
2
-
3
- mock.module("../util/logger.js", () => ({
4
- getLogger: () =>
5
- new Proxy({} as Record<string, unknown>, {
6
- get: () => () => {},
7
- }),
8
- }));
9
-
10
- // Wrap the real contacts-write so a test can force the best-effort local mirror
11
- // to fail (simulating a gateway-verified activation whose local DB row failed),
12
- // while normal setup keeps the real implementation. Capture the concrete real
13
- // function BEFORE registering the mock so the wrapper never recurses into
14
- // itself via the (now live) module namespace.
15
- const contactsWriteState = { mirrorThrows: false };
16
- const realContactsWrite = await import("../contacts/contacts-write.js");
17
- const realUpsertContactChannel = realContactsWrite.upsertContactChannel;
18
- mock.module("../contacts/contacts-write.js", () => ({
19
- ...realContactsWrite,
20
- upsertContactChannel: (
21
- params: Parameters<typeof realUpsertContactChannel>[0],
22
- ) => {
23
- if (contactsWriteState.mirrorThrows) {
24
- throw new Error("local mirror exploded");
25
- }
26
- return realUpsertContactChannel(params);
27
- },
28
- }));
29
-
30
- // Mock the gateway IPC bridge used by the redemption service for the
31
- // authoritative pre-mutation claim (record_invite_redemption). Tests drive the
32
- // claim result via `gatewayIpc`.
33
- const gatewayIpc = {
34
- claim: { ok: true, updated: true, mirrored: true } as {
35
- ok: boolean;
36
- updated: boolean;
37
- mirrored: boolean;
38
- },
39
- claimThrows: false,
40
- // When set, contacts_get_rich throws (gateway read unreachable) so the
41
- // gate-status fallback must fail open.
42
- richThrows: false,
43
- // When set, overrides the contacts_get_rich response (e.g. a gateway row
44
- // under a divergent UUID for the same (type,address)).
45
- richOverride: null as ((contactId: string | undefined) => unknown) | null,
46
- // Drives the upsert_verified_channel relay verdict. When false the gateway
47
- // refuses the actor (blocked/revoked) and the activation is refused.
48
- activationVerified: true,
49
- // Gateway channel returned on a verified activation, surfaced as the memberId
50
- // when the local mirror produces no row.
51
- activationChannelId: "gw-channel-id" as string | undefined,
52
- calls: [] as { method: string; params?: Record<string, unknown> }[],
53
- };
54
-
55
- mock.module("../ipc/gateway-client.js", () => ({
56
- ipcCallPersistent: async (
57
- method: string,
58
- params?: Record<string, unknown>,
59
- ) => {
60
- gatewayIpc.calls.push({ method, params });
61
- if (method === "contacts_get_rich") {
62
- if (gatewayIpc.richThrows) throw new Error("gateway read unreachable");
63
- if (gatewayIpc.richOverride) {
64
- return gatewayIpc.richOverride(params?.contactId as string);
65
- }
66
- return richContactForId(params?.contactId as string);
67
- }
68
- if (method === "record_invite_redemption") {
69
- if (gatewayIpc.claimThrows) throw new Error("gateway unreachable");
70
- onGatewayClaim?.();
71
- return gatewayIpc.claim;
72
- }
73
- if (method === "upsert_verified_channel") {
74
- if (!gatewayIpc.activationVerified) {
75
- return { ok: true, verified: false };
76
- }
77
- return {
78
- ok: true,
79
- verified: true,
80
- channel: gatewayIpc.activationChannelId
81
- ? {
82
- id: gatewayIpc.activationChannelId,
83
- contactId: (params?.contactId as string) ?? "gw-contact",
84
- type: (params?.type as string) ?? "telegram",
85
- address: (params?.address as string) ?? "gw-addr",
86
- status: "active",
87
- verifiedAt: 1,
88
- verifiedVia: (params?.verifiedVia as string) ?? "invite",
89
- }
90
- : undefined,
91
- };
92
- }
93
- return undefined;
94
- },
95
- }));
96
-
97
- // Serves contacts_get_rich (the gateway ACL read backing the gate-status
98
- // fallback) from the seeded local contact, so gate resolution sources status
99
- // from the gateway path rather than the local channel column.
100
- function richContactForId(contactId: string | undefined) {
101
- if (!contactId) return undefined;
102
- const contact = getContact(contactId);
103
- if (!contact) return undefined;
104
- // The gateway owns the ACL; tests seed it into the gateway ACL store. Source
105
- // role/status/policy from there to build the gateway-rich response the
106
- // production read parses (never the Phase-B-dropped assistant ACL columns).
107
- return {
108
- ok: true,
109
- contact: {
110
- id: contact.id,
111
- displayName: contact.displayName,
112
- role: gatewayContactRole(contact.id) ?? "contact",
113
- interactionCount: contact.interactionCount,
114
- createdAt: contact.createdAt,
115
- updatedAt: contact.updatedAt,
116
- channels: contact.channels.map((c) => {
117
- const acl = gatewayChannelAcl(c.id);
118
- return {
119
- id: c.id,
120
- contactId: c.contactId,
121
- type: c.type,
122
- address: c.address,
123
- isPrimary: c.isPrimary,
124
- externalUserId: c.externalChatId,
125
- status: acl.status,
126
- policy: acl.policy,
127
- verifiedAt: acl.verifiedAt,
128
- verifiedVia: null,
129
- lastSeenAt: null,
130
- interactionCount: 0,
131
- lastInteraction: null,
132
- revokedReason: null,
133
- blockedReason: null,
134
- };
135
- }),
136
- },
137
- };
138
- }
139
-
140
- /** Read a channel's seeded ACL view from the gateway ACL store. */
141
- function gatewayChannelAcl(channelId: string): {
142
- status: string;
143
- policy: string;
144
- verifiedAt: number | null;
145
- } {
146
- const row = gatewayAclByChannelId(channelId);
147
- return {
148
- status: row?.status ?? "unverified",
149
- policy: row?.policy ?? "allow",
150
- verifiedAt: row?.verifiedAt ?? null,
151
- };
152
- }
153
-
154
- /** The seeded gateway role for any of a contact's channels. */
155
- function gatewayContactRole(contactId: string): string | undefined {
156
- return gatewayAclRows().find((r) => r.contactId === contactId)?.role;
157
- }
158
-
159
- // Lets a test inject a side-effect into the gateway claim — runs after the
160
- // service's pre-validation but before the assistant use-bump, so it can race a
161
- // revoke into the window that makes `recordInviteUse` return false.
162
- let onGatewayClaim: (() => void) | null = null;
163
-
164
- function resetGatewayIpc() {
165
- gatewayIpc.claim = { ok: true, updated: true, mirrored: true };
166
- gatewayIpc.claimThrows = false;
167
- gatewayIpc.richThrows = false;
168
- gatewayIpc.richOverride = null;
169
- gatewayIpc.activationVerified = true;
170
- gatewayIpc.activationChannelId = "gw-channel-id";
171
- gatewayIpc.calls = [];
172
- onGatewayClaim = null;
173
- }
174
-
175
- import type { TrustVerdict } from "@vellumai/gateway-client";
176
-
177
- import {
178
- findContactChannel,
179
- getContact,
180
- upsertContact,
181
- } from "../contacts/contact-store.js";
182
- import { getSqlite } from "../persistence/db-connection.js";
183
- import { initializeDb } from "../persistence/db-init.js";
184
- import {
185
- createInvite,
186
- revokeInvite as revokeStoreFn,
187
- } from "../persistence/invite-store.js";
188
- import {
189
- type InviteRedemptionOutcome,
190
- redeemInvite,
191
- redeemInviteByCode,
192
- resolveMemberGateStatus,
193
- } from "../runtime/invite-redemption-service.js";
194
- import { hashVoiceCode } from "../util/voice-code.js";
195
- import {
196
- gatewayAclByChannelId,
197
- gatewayAclRows,
198
- resetGatewayAclStore,
199
- } from "./helpers/gateway-acl-store.js";
200
- import { seedContactChannel } from "./helpers/seed-contact-channel.js";
201
-
202
- await initializeDb();
203
-
204
- function resetTables() {
205
- getSqlite().run("DELETE FROM assistant_ingress_invites");
206
- getSqlite().run("DELETE FROM contact_channels");
207
- getSqlite().run("DELETE FROM contacts");
208
- resetGatewayAclStore();
209
- }
210
-
211
- /** Create a throwaway contact and return its ID, for use as the invite's contactId. */
212
- function createTargetContact(displayName = "Target Contact"): string {
213
- return upsertContact({ displayName, role: "contact" }).id;
214
- }
215
-
216
- /** Read a contact's seeded gateway role (gateway-owned, not a local column). */
217
- function localContactRole(contactId: string): string | undefined {
218
- return gatewayContactRole(contactId);
219
- }
220
-
221
- describe("invite-redemption-service", () => {
222
- beforeEach(() => {
223
- resetTables();
224
- resetGatewayIpc();
225
- contactsWriteState.mirrorThrows = false;
226
- });
227
-
228
- test("redeems a valid invite and returns typed outcome", async () => {
229
- const targetContactId = createTargetContact();
230
- const { rawToken, invite } = createInvite({
231
- sourceChannel: "telegram",
232
- contactId: targetContactId,
233
- maxUses: 1,
234
- });
235
-
236
- const outcome = await redeemInvite({
237
- rawToken,
238
- sourceChannel: "telegram",
239
- externalUserId: "user-1",
240
- });
241
-
242
- expect(outcome.ok).toBe(true);
243
- expect(outcome).toEqual({
244
- ok: true,
245
- type: "redeemed",
246
- memberId: expect.any(String),
247
- inviteId: invite.id,
248
- });
249
-
250
- // The activation is written to the gateway via upsert_verified_channel.
251
- expect(
252
- gatewayIpc.calls.some((c) => c.method === "upsert_verified_channel"),
253
- ).toBe(true);
254
- });
255
-
256
- test("marks channel as verified via invite on redemption", async () => {
257
- const targetContactId = createTargetContact();
258
- const { rawToken } = createInvite({
259
- sourceChannel: "telegram",
260
- contactId: targetContactId,
261
- maxUses: 1,
262
- });
263
-
264
- const outcome = await redeemInvite({
265
- rawToken,
266
- sourceChannel: "telegram",
267
- externalUserId: "user-1",
268
- });
269
-
270
- expect(outcome.ok).toBe(true);
271
-
272
- // The gateway owns the verified ACL verdict (relayed via
273
- // upsert_verified_channel); the local mirror persists identity only.
274
- const result = findContactChannel({
275
- channelType: "telegram",
276
- address: "user-1",
277
- });
278
- expect(result).not.toBeNull();
279
- expect(
280
- gatewayIpc.calls.some((c) => c.method === "upsert_verified_channel"),
281
- ).toBe(true);
282
- });
283
-
284
- test("marks channel as verified via invite on 6-digit code redemption", async () => {
285
- const targetContactId = createTargetContact();
286
- const inviteCode = "123456";
287
- createInvite({
288
- sourceChannel: "telegram",
289
- contactId: targetContactId,
290
- maxUses: 1,
291
- inviteCodeHash: hashVoiceCode(inviteCode),
292
- });
293
-
294
- const outcome = await redeemInviteByCode({
295
- code: inviteCode,
296
- sourceChannel: "telegram",
297
- externalUserId: "code-user-1",
298
- });
299
-
300
- expect(outcome.ok).toBe(true);
301
-
302
- // The gateway owns the verified ACL verdict; the local mirror is identity-only.
303
- const result = findContactChannel({
304
- channelType: "telegram",
305
- address: "code-user-1",
306
- });
307
- expect(result).not.toBeNull();
308
- expect(
309
- gatewayIpc.calls.some((c) => c.method === "upsert_verified_channel"),
310
- ).toBe(true);
311
- });
312
-
313
- test("returns invalid_token for a bogus token", async () => {
314
- const outcome = await redeemInvite({
315
- rawToken: "totally-bogus-token",
316
- sourceChannel: "telegram",
317
- externalUserId: "user-1",
318
- });
319
-
320
- expect(outcome).toEqual({ ok: false, reason: "invalid_token" });
321
- });
322
-
323
- test("returns expired for an expired invite", async () => {
324
- const targetContactId = createTargetContact();
325
- // Create an invite that expired 1 ms ago
326
- const { rawToken } = createInvite({
327
- sourceChannel: "telegram",
328
- contactId: targetContactId,
329
- maxUses: 1,
330
- expiresInMs: -1,
331
- });
332
-
333
- const outcome = await redeemInvite({
334
- rawToken,
335
- sourceChannel: "telegram",
336
- externalUserId: "user-1",
337
- });
338
-
339
- expect(outcome).toEqual({ ok: false, reason: "expired" });
340
- });
341
-
342
- test("returns revoked for a revoked invite", async () => {
343
- const targetContactId = createTargetContact();
344
- const { rawToken, invite } = createInvite({
345
- sourceChannel: "telegram",
346
- contactId: targetContactId,
347
- maxUses: 1,
348
- });
349
- revokeStoreFn(invite.id);
350
-
351
- const outcome = await redeemInvite({
352
- rawToken,
353
- sourceChannel: "telegram",
354
- externalUserId: "user-1",
355
- });
356
-
357
- expect(outcome).toEqual({ ok: false, reason: "revoked" });
358
- });
359
-
360
- test("returns max_uses_reached when invite is fully consumed", async () => {
361
- const targetContactId = createTargetContact();
362
- const { rawToken } = createInvite({
363
- sourceChannel: "telegram",
364
- contactId: targetContactId,
365
- maxUses: 1,
366
- });
367
-
368
- // First redemption should succeed
369
- const first = await redeemInvite({
370
- rawToken,
371
- sourceChannel: "telegram",
372
- externalUserId: "user-1",
373
- });
374
- expect(first.ok).toBe(true);
375
-
376
- // Second attempt should fail — the invite is now fully redeemed
377
- const second = await redeemInvite({
378
- rawToken,
379
- sourceChannel: "telegram",
380
- externalUserId: "user-2",
381
- });
382
-
383
- expect(second).toEqual({ ok: false, reason: "max_uses_reached" });
384
- });
385
-
386
- test("returns channel_mismatch when redeeming on wrong channel", async () => {
387
- const targetContactId = createTargetContact();
388
- const { rawToken } = createInvite({
389
- sourceChannel: "telegram",
390
- contactId: targetContactId,
391
- maxUses: 1,
392
- });
393
-
394
- const outcome = await redeemInvite({
395
- rawToken,
396
- sourceChannel: "phone",
397
- externalUserId: "user-1",
398
- });
399
-
400
- expect(outcome).toEqual({ ok: false, reason: "channel_mismatch" });
401
- });
402
-
403
- test("returns missing_identity when no externalUserId or externalChatId", async () => {
404
- const targetContactId = createTargetContact();
405
- const { rawToken } = createInvite({
406
- sourceChannel: "telegram",
407
- contactId: targetContactId,
408
- maxUses: 1,
409
- });
410
-
411
- const outcome = await redeemInvite({
412
- rawToken,
413
- sourceChannel: "telegram",
414
- });
415
-
416
- expect(outcome).toEqual({ ok: false, reason: "missing_identity" });
417
- });
418
-
419
- test("returns already_member when user is already an active member", async () => {
420
- // Pre-create an active member and find their contact
421
- const member = seedContactChannel({
422
- sourceChannel: "telegram",
423
- externalUserId: "existing-user",
424
- status: "active",
425
- });
426
-
427
- // Create an invite targeting the same contact that owns the channel
428
- const { rawToken } = createInvite({
429
- sourceChannel: "telegram",
430
- contactId: member.contactId,
431
- maxUses: 5,
432
- });
433
-
434
- const outcome = await redeemInvite({
435
- rawToken,
436
- sourceChannel: "telegram",
437
- externalUserId: "existing-user",
438
- });
439
-
440
- expect(outcome.ok).toBe(true);
441
- expect(
442
- (outcome as Extract<InviteRedemptionOutcome, { type: "already_member" }>)
443
- .type,
444
- ).toBe("already_member");
445
- expect(
446
- (outcome as Extract<InviteRedemptionOutcome, { type: "already_member" }>)
447
- .memberId,
448
- ).toEqual(expect.any(String));
449
- });
450
-
451
- test("returns invalid_token for a blocked member to avoid leaking membership status", async () => {
452
- // Pre-create a blocked member and find their contact
453
- const member = seedContactChannel({
454
- sourceChannel: "telegram",
455
- externalUserId: "blocked-user",
456
- status: "blocked",
457
- });
458
-
459
- // Create an invite targeting the same contact that owns the channel
460
- const { rawToken } = createInvite({
461
- sourceChannel: "telegram",
462
- contactId: member.contactId,
463
- maxUses: 5,
464
- });
465
-
466
- const outcome = await redeemInvite({
467
- rawToken,
468
- sourceChannel: "telegram",
469
- externalUserId: "blocked-user",
470
- });
471
-
472
- expect(outcome).toEqual({ ok: false, reason: "invalid_token" });
473
- });
474
-
475
- test("matches an active member by (type,address) when the gateway row has a divergent uuid", async () => {
476
- const member = seedContactChannel({
477
- sourceChannel: "telegram",
478
- externalUserId: "divergent-user",
479
- status: "active",
480
- });
481
-
482
- // The gateway row for the same (type,address) carries a DIFFERENT id, as a
483
- // reconcile divergence would produce. Matching by id alone would miss it.
484
- gatewayIpc.richOverride = () => ({
485
- ok: true,
486
- contact: {
487
- id: member.contactId,
488
- displayName: "divergent-user",
489
- role: "contact",
490
- interactionCount: 0,
491
- createdAt: 1,
492
- updatedAt: 1,
493
- channels: [
494
- {
495
- id: "gateway-divergent-uuid",
496
- contactId: member.contactId,
497
- type: "telegram",
498
- address: "divergent-user",
499
- isPrimary: false,
500
- externalUserId: null,
501
- status: "active",
502
- policy: "allow",
503
- verifiedAt: 1,
504
- verifiedVia: "invite",
505
- lastSeenAt: null,
506
- interactionCount: 0,
507
- lastInteraction: null,
508
- revokedReason: null,
509
- blockedReason: null,
510
- },
511
- ],
512
- },
513
- });
514
-
515
- const { rawToken } = createInvite({
516
- sourceChannel: "telegram",
517
- contactId: member.contactId,
518
- maxUses: 5,
519
- });
520
-
521
- const outcome = await redeemInvite({
522
- rawToken,
523
- sourceChannel: "telegram",
524
- externalUserId: "divergent-user",
525
- });
526
-
527
- expect(outcome.ok).toBe(true);
528
- expect((outcome as { type: string }).type).toBe("already_member");
529
- });
530
-
531
- test("blocks via the (type,address) match when the gateway row has a divergent uuid", async () => {
532
- const member = seedContactChannel({
533
- sourceChannel: "telegram",
534
- externalUserId: "divergent-blocked",
535
- status: "blocked",
536
- });
537
-
538
- gatewayIpc.richOverride = () => ({
539
- ok: true,
540
- contact: {
541
- id: member.contactId,
542
- displayName: "divergent-blocked",
543
- role: "contact",
544
- interactionCount: 0,
545
- createdAt: 1,
546
- updatedAt: 1,
547
- channels: [
548
- {
549
- id: "gateway-divergent-blocked-uuid",
550
- contactId: member.contactId,
551
- type: "telegram",
552
- // Case-divergent address must still match (COLLATE NOCASE).
553
- address: "DIVERGENT-BLOCKED",
554
- isPrimary: false,
555
- externalUserId: null,
556
- status: "blocked",
557
- policy: "deny",
558
- verifiedAt: null,
559
- verifiedVia: null,
560
- lastSeenAt: null,
561
- interactionCount: 0,
562
- lastInteraction: null,
563
- revokedReason: null,
564
- blockedReason: "guardian blocked",
565
- },
566
- ],
567
- },
568
- });
569
-
570
- const { rawToken } = createInvite({
571
- sourceChannel: "telegram",
572
- contactId: member.contactId,
573
- maxUses: 5,
574
- });
575
-
576
- const outcome = await redeemInvite({
577
- rawToken,
578
- sourceChannel: "telegram",
579
- externalUserId: "divergent-blocked",
580
- });
581
-
582
- expect(outcome).toEqual({ ok: false, reason: "invalid_token" });
583
- });
584
-
585
- test("fails open (no throw) when the gateway gate-status read is unreachable", async () => {
586
- // No verdict member and an unreachable gateway read must degrade to the
587
- // fail-open path: redemption still resolves rather than throwing.
588
- const member = seedContactChannel({
589
- sourceChannel: "telegram",
590
- externalUserId: "readfail-user",
591
- status: "revoked",
592
- });
593
-
594
- gatewayIpc.richThrows = true;
595
-
596
- const { rawToken } = createInvite({
597
- sourceChannel: "telegram",
598
- contactId: member.contactId,
599
- maxUses: 1,
600
- });
601
-
602
- const outcome = await redeemInvite({
603
- rawToken,
604
- sourceChannel: "telegram",
605
- externalUserId: "readfail-user",
606
- });
607
-
608
- expect(outcome.ok).toBe(true);
609
- });
610
-
611
- test("binds redeemer to the invite's target contact, not the guardian", async () => {
612
- // Pre-create a guardian contact with a revoked telegram channel
613
- const guardianSeed = seedContactChannel({
614
- sourceChannel: "telegram",
615
- externalUserId: "guardian-tg-id",
616
- displayName: "Guardian",
617
- role: "guardian",
618
- status: "revoked",
619
- });
620
-
621
- // Create a separate target contact "Mom"
622
- const momContact = upsertContact({
623
- displayName: "Mom",
624
- role: "contact",
625
- });
626
-
627
- // Create an invite targeting Mom's contact
628
- const { rawToken } = createInvite({
629
- sourceChannel: "telegram",
630
- contactId: momContact.id,
631
- maxUses: 5,
632
- });
633
-
634
- // Redeem using the guardian's Telegram identity
635
- const outcome = await redeemInvite({
636
- rawToken,
637
- sourceChannel: "telegram",
638
- externalUserId: "guardian-tg-id",
639
- });
640
-
641
- // Should succeed — redeemer's channel is bound to Mom
642
- expect(outcome.ok).toBe(true);
643
- expect((outcome as { type: string }).type).toBe("redeemed");
644
-
645
- // Gateway-first: the activation relays the target contactId so the gateway
646
- // binds the channel to Mom (not the guardian) — the binding is not lost.
647
- const upsert = gatewayIpc.calls.find(
648
- (c) => c.method === "upsert_verified_channel",
649
- );
650
- expect(upsert).toBeDefined();
651
- expect(upsert!.params).toMatchObject({
652
- type: "telegram",
653
- address: "guardian-tg-id",
654
- contactId: momContact.id,
655
- });
656
-
657
- // Verify the redeemer's Telegram ID is now bound to Mom's contact
658
- const result = findContactChannel({
659
- channelType: "telegram",
660
- address: "guardian-tg-id",
661
- });
662
- expect(result).not.toBeNull();
663
- expect(result!.contact.id).toBe(momContact.id);
664
-
665
- // Verify the original guardian contact was NOT modified
666
- const guardian = getContact(guardianSeed.contactId);
667
- expect(guardian).not.toBeNull();
668
- expect(localContactRole(guardianSeed.contactId)).toBe("guardian");
669
- });
670
-
671
- test("downgrades guardian to contact when redeeming invite targeting own contact", async () => {
672
- // Create a guardian contact with a revoked channel
673
- const guardianSeed = seedContactChannel({
674
- sourceChannel: "telegram",
675
- externalUserId: "guardian-own-id",
676
- displayName: "Guardian",
677
- role: "guardian",
678
- status: "revoked",
679
- });
680
-
681
- // Create invite targeting the guardian's own contact
682
- const { rawToken } = createInvite({
683
- sourceChannel: "telegram",
684
- contactId: guardianSeed.contactId,
685
- maxUses: 5,
686
- });
687
-
688
- const outcome = await redeemInvite({
689
- rawToken,
690
- sourceChannel: "telegram",
691
- externalUserId: "guardian-own-id",
692
- });
693
-
694
- expect(outcome.ok).toBe(true);
695
-
696
- // The role downgrade is gateway-owned; redemption relays the activation for
697
- // the guardian's own contact rather than mutating the local role.
698
- expect(
699
- gatewayIpc.calls.some((c) => c.method === "upsert_verified_channel"),
700
- ).toBe(true);
701
- const updated = getContact(guardianSeed.contactId);
702
- expect(updated).not.toBeNull();
703
- });
704
-
705
- test("binds redeemer to the invite's target contact via 6-digit code, not the guardian", async () => {
706
- // Pre-create a guardian contact with a revoked telegram channel
707
- const guardianSeed = seedContactChannel({
708
- sourceChannel: "telegram",
709
- externalUserId: "guardian-code-id",
710
- displayName: "Guardian",
711
- role: "guardian",
712
- status: "revoked",
713
- });
714
-
715
- // Create a separate target contact "Mom"
716
- const momContact = upsertContact({
717
- displayName: "Mom",
718
- role: "contact",
719
- });
720
-
721
- // Create an invite targeting Mom's contact with a 6-digit code
722
- const code = "123456";
723
- const inviteCodeHash = hashVoiceCode(code);
724
- createInvite({
725
- sourceChannel: "telegram",
726
- contactId: momContact.id,
727
- maxUses: 5,
728
- inviteCodeHash,
729
- });
730
-
731
- // Redeem using the guardian's Telegram identity
732
- const outcome = await redeemInviteByCode({
733
- code,
734
- sourceChannel: "telegram",
735
- externalUserId: "guardian-code-id",
736
- });
737
-
738
- // Should succeed — redeemer's channel is bound to Mom
739
- expect(outcome.ok).toBe(true);
740
- expect((outcome as { type: string }).type).toBe("redeemed");
741
-
742
- // Verify the redeemer's Telegram ID is now bound to Mom's contact
743
- const result = findContactChannel({
744
- channelType: "telegram",
745
- address: "guardian-code-id",
746
- });
747
- expect(result).not.toBeNull();
748
- expect(result!.contact.id).toBe(momContact.id);
749
-
750
- // Verify the original guardian contact was NOT modified
751
- const guardian = getContact(guardianSeed.contactId);
752
- expect(guardian).not.toBeNull();
753
- expect(localContactRole(guardianSeed.contactId)).toBe("guardian");
754
- });
755
-
756
- test("does not return already_member for a revoked member", async () => {
757
- const targetContactId = createTargetContact();
758
- const { rawToken } = createInvite({
759
- sourceChannel: "telegram",
760
- contactId: targetContactId,
761
- maxUses: 5,
762
- });
763
-
764
- // Pre-create a revoked member
765
- seedContactChannel({
766
- sourceChannel: "telegram",
767
- externalUserId: "revoked-user",
768
- status: "revoked",
769
- });
770
-
771
- const outcome = await redeemInvite({
772
- rawToken,
773
- sourceChannel: "telegram",
774
- externalUserId: "revoked-user",
775
- });
776
-
777
- // Should redeem, not return already_member
778
- expect(outcome.ok).toBe(true);
779
- expect(
780
- (outcome as Extract<InviteRedemptionOutcome, { type: "redeemed" }>).type,
781
- ).toBe("redeemed");
782
- });
783
-
784
- test("raw token is not present in the outcome object", async () => {
785
- const targetContactId = createTargetContact();
786
- const { rawToken } = createInvite({
787
- sourceChannel: "telegram",
788
- contactId: targetContactId,
789
- maxUses: 1,
790
- });
791
-
792
- const outcome = await redeemInvite({
793
- rawToken,
794
- sourceChannel: "telegram",
795
- externalUserId: "user-1",
796
- });
797
-
798
- // Verify the raw token does not appear anywhere in the serialized outcome
799
- const serialized = JSON.stringify(outcome);
800
- expect(serialized).not.toContain(rawToken);
801
- });
802
-
803
- test("channel enforcement blocks cross-channel redemption (voice invite via slack)", async () => {
804
- const targetContactId = createTargetContact();
805
- const { rawToken } = createInvite({
806
- sourceChannel: "phone",
807
- contactId: targetContactId,
808
- maxUses: 1,
809
- });
810
-
811
- const outcome = await redeemInvite({
812
- rawToken,
813
- sourceChannel: "slack",
814
- externalUserId: "user-1",
815
- });
816
-
817
- expect(outcome).toEqual({ ok: false, reason: "channel_mismatch" });
818
- });
819
-
820
- test("returns invalid_token for an active member with a bogus token (no membership probing)", async () => {
821
- // Pre-create an active member
822
- seedContactChannel({
823
- sourceChannel: "telegram",
824
- externalUserId: "probed-user",
825
- status: "active",
826
- });
827
-
828
- // Attempt to redeem with a bogus token — must NOT leak membership status
829
- const outcome = await redeemInvite({
830
- rawToken: "completely-bogus-token",
831
- sourceChannel: "telegram",
832
- externalUserId: "probed-user",
833
- });
834
-
835
- expect(outcome).toEqual({ ok: false, reason: "invalid_token" });
836
- });
837
-
838
- test("returns expired for an active member with an expired invite token", async () => {
839
- const targetContactId = createTargetContact();
840
- // Create an expired invite
841
- const { rawToken } = createInvite({
842
- sourceChannel: "telegram",
843
- contactId: targetContactId,
844
- maxUses: 5,
845
- expiresInMs: -1,
846
- });
847
-
848
- // Pre-create an active member
849
- seedContactChannel({
850
- sourceChannel: "telegram",
851
- externalUserId: "expired-token-user",
852
- status: "active",
853
- });
854
-
855
- // Expired token must return expired, not already_member
856
- const outcome = await redeemInvite({
857
- rawToken,
858
- sourceChannel: "telegram",
859
- externalUserId: "expired-token-user",
860
- });
861
-
862
- expect(outcome).toEqual({ ok: false, reason: "expired" });
863
- });
864
-
865
- test("returns channel_mismatch for an active member with a valid token for a different channel", async () => {
866
- const targetContactId = createTargetContact();
867
- // Create an invite for voice
868
- const { rawToken } = createInvite({
869
- sourceChannel: "phone",
870
- contactId: targetContactId,
871
- maxUses: 5,
872
- });
873
-
874
- // Pre-create an active member on telegram
875
- seedContactChannel({
876
- sourceChannel: "telegram",
877
- externalUserId: "cross-channel-user",
878
- status: "active",
879
- });
880
-
881
- // Valid token for wrong channel must return channel_mismatch, not already_member
882
- const outcome = await redeemInvite({
883
- rawToken,
884
- sourceChannel: "telegram",
885
- externalUserId: "cross-channel-user",
886
- });
887
-
888
- expect(outcome).toEqual({ ok: false, reason: "channel_mismatch" });
889
- });
890
-
891
- // ── Gateway lifecycle pre-check + redemption mirror ────────────────────
892
-
893
- test("rejects redemption when the gateway claim is not consumable (no assistant mutation)", async () => {
894
- const targetContactId = createTargetContact();
895
- const { rawToken } = createInvite({
896
- sourceChannel: "telegram",
897
- contactId: targetContactId,
898
- maxUses: 1,
899
- });
900
-
901
- // Gateway row exists but was NOT consumable (revoked/exhausted/raced).
902
- gatewayIpc.claim = { ok: true, updated: false, mirrored: true };
903
-
904
- const outcome = await redeemInvite({
905
- rawToken,
906
- sourceChannel: "telegram",
907
- externalUserId: "gw-revoked-user",
908
- });
909
-
910
- expect(outcome).toEqual({ ok: false, reason: "invalid_token" });
911
-
912
- // No member was created — the assistant DB was never mutated.
913
- expect(
914
- findContactChannel({
915
- channelType: "telegram",
916
- address: "gw-revoked-user",
917
- }),
918
- ).toBeNull();
919
- });
920
-
921
- test("does not activate the member when recordInviteUse loses the race (returns false)", async () => {
922
- const targetContactId = createTargetContact();
923
- const { rawToken, invite } = createInvite({
924
- sourceChannel: "telegram",
925
- contactId: targetContactId,
926
- maxUses: 1,
927
- });
928
-
929
- // Legacy gateway row so the claim proceeds past the gateway gate. Revoke
930
- // the assistant invite during the claim — after pre-validation, before the
931
- // use-bump — so `recordInviteUse` sees a non-active row and returns false.
932
- gatewayIpc.claim = { ok: true, updated: false, mirrored: false };
933
- onGatewayClaim = () => {
934
- revokeStoreFn(invite.id);
935
- };
936
-
937
- const outcome = await redeemInvite({
938
- rawToken,
939
- sourceChannel: "telegram",
940
- externalUserId: "raced-user",
941
- });
942
-
943
- expect(outcome).toEqual({ ok: false, reason: "invalid_token" });
944
-
945
- // The member was NOT activated: no gateway upsert and no active channel.
946
- expect(
947
- gatewayIpc.calls.some((c) => c.method === "upsert_verified_channel"),
948
- ).toBe(false);
949
- expect(
950
- findContactChannel({ channelType: "telegram", address: "raced-user" }),
951
- ).toBeNull();
952
- });
953
-
954
- test("proceeds and mutates when the gateway claim is consumed (updated:true)", async () => {
955
- const targetContactId = createTargetContact();
956
- const { rawToken, invite } = createInvite({
957
- sourceChannel: "telegram",
958
- contactId: targetContactId,
959
- maxUses: 1,
960
- });
961
-
962
- gatewayIpc.claim = { ok: true, updated: true, mirrored: true };
963
-
964
- const outcome = await redeemInvite({
965
- rawToken,
966
- sourceChannel: "telegram",
967
- externalUserId: "gw-active-user",
968
- });
969
-
970
- expect(outcome.ok).toBe(true);
971
-
972
- // The claim was performed against the resolved invite id.
973
- const claim = gatewayIpc.calls.find(
974
- (c) => c.method === "record_invite_redemption",
975
- );
976
- expect(claim).toBeDefined();
977
- expect(claim!.params).toMatchObject({ inviteId: invite.id });
978
-
979
- // The assistant DB was mutated.
980
- expect(
981
- findContactChannel({
982
- channelType: "telegram",
983
- address: "gw-active-user",
984
- }),
985
- ).not.toBeNull();
986
- });
987
-
988
- test("proceeds for a legacy invite the gateway has never seen (mirrored:false)", async () => {
989
- const targetContactId = createTargetContact();
990
- const { rawToken } = createInvite({
991
- sourceChannel: "telegram",
992
- contactId: targetContactId,
993
- maxUses: 1,
994
- });
995
-
996
- gatewayIpc.claim = { ok: true, updated: false, mirrored: false };
997
-
998
- const outcome = await redeemInvite({
999
- rawToken,
1000
- sourceChannel: "telegram",
1001
- externalUserId: "legacy-user",
1002
- });
1003
-
1004
- expect(outcome.ok).toBe(true);
1005
- expect(
1006
- findContactChannel({ channelType: "telegram", address: "legacy-user" }),
1007
- ).not.toBeNull();
1008
- });
1009
-
1010
- test("fails open when the gateway claim throws (assistant-side checks still apply)", async () => {
1011
- const targetContactId = createTargetContact();
1012
- const { rawToken } = createInvite({
1013
- sourceChannel: "telegram",
1014
- contactId: targetContactId,
1015
- maxUses: 1,
1016
- });
1017
-
1018
- gatewayIpc.claimThrows = true;
1019
-
1020
- const outcome = await redeemInvite({
1021
- rawToken,
1022
- sourceChannel: "telegram",
1023
- externalUserId: "failopen-user",
1024
- });
1025
-
1026
- expect(outcome.ok).toBe(true);
1027
- expect(
1028
- findContactChannel({ channelType: "telegram", address: "failopen-user" }),
1029
- ).not.toBeNull();
1030
- });
1031
-
1032
- test("does not claim the gateway row for an already-active member", async () => {
1033
- const targetContactId = createTargetContact();
1034
- const { rawToken } = createInvite({
1035
- sourceChannel: "telegram",
1036
- contactId: targetContactId,
1037
- maxUses: 1,
1038
- });
1039
-
1040
- // Seed an already-active member bound to the invite's target contact.
1041
- seedContactChannel({
1042
- sourceChannel: "telegram",
1043
- externalUserId: "already-member-user",
1044
- role: "contact",
1045
- status: "active",
1046
- policy: "allow",
1047
- contactId: targetContactId,
1048
- });
1049
-
1050
- const outcome = await redeemInvite({
1051
- rawToken,
1052
- sourceChannel: "telegram",
1053
- externalUserId: "already-member-user",
1054
- });
1055
-
1056
- expect(outcome.ok).toBe(true);
1057
- expect((outcome as { type: string }).type).toBe("already_member");
1058
-
1059
- // No gateway claim was made — no use must be consumed for a non-redemption.
1060
- expect(
1061
- gatewayIpc.calls.some((c) => c.method === "record_invite_redemption"),
1062
- ).toBe(false);
1063
- });
1064
-
1065
- test("rejects 6-digit code redemption when the gateway claim is not consumable", async () => {
1066
- const targetContactId = createTargetContact();
1067
- const code = "654321";
1068
- createInvite({
1069
- sourceChannel: "telegram",
1070
- contactId: targetContactId,
1071
- maxUses: 1,
1072
- inviteCodeHash: hashVoiceCode(code),
1073
- });
1074
-
1075
- gatewayIpc.claim = { ok: true, updated: false, mirrored: true };
1076
-
1077
- const outcome = await redeemInviteByCode({
1078
- code,
1079
- sourceChannel: "telegram",
1080
- externalUserId: "gw-revoked-code-user",
1081
- });
1082
-
1083
- expect(outcome).toEqual({ ok: false, reason: "invalid_token" });
1084
- expect(
1085
- findContactChannel({
1086
- channelType: "telegram",
1087
- address: "gw-revoked-code-user",
1088
- }),
1089
- ).toBeNull();
1090
- });
1091
-
1092
- test("returns invalid_token (no throw) when the gateway refuses the activation", async () => {
1093
- const targetContactId = createTargetContact();
1094
- const { rawToken } = createInvite({
1095
- sourceChannel: "telegram",
1096
- contactId: targetContactId,
1097
- maxUses: 1,
1098
- });
1099
-
1100
- // Gateway claim consumes the row, then refuses the channel activation
1101
- // (blocked/revoked actor). The committed use is acceptable for this rare
1102
- // blocked-backstop path; the outcome must be the branch failure, not a 500.
1103
- gatewayIpc.activationVerified = false;
1104
-
1105
- const outcome = await redeemInvite({
1106
- rawToken,
1107
- sourceChannel: "telegram",
1108
- externalUserId: "refused-user",
1109
- });
1110
-
1111
- expect(outcome).toEqual({ ok: false, reason: "invalid_token" });
1112
- });
1113
-
1114
- test("redeems with the gateway channel id when the gateway verifies but the local mirror fails", async () => {
1115
- const targetContactId = createTargetContact();
1116
- const { rawToken, invite } = createInvite({
1117
- sourceChannel: "telegram",
1118
- contactId: targetContactId,
1119
- maxUses: 1,
1120
- });
1121
-
1122
- // Gateway verifies and returns its channel; the best-effort local mirror
1123
- // throws. The activation must still stand using the gateway channel id.
1124
- gatewayIpc.activationChannelId = "gw-verified-channel";
1125
- contactsWriteState.mirrorThrows = true;
1126
-
1127
- const outcome = await redeemInvite({
1128
- rawToken,
1129
- sourceChannel: "telegram",
1130
- externalUserId: "mirrorless-user",
1131
- });
1132
-
1133
- expect(outcome).toEqual({
1134
- ok: true,
1135
- type: "redeemed",
1136
- memberId: "gw-verified-channel",
1137
- inviteId: invite.id,
1138
- });
1139
- });
1140
- });
1141
-
1142
- describe("resolveMemberGateStatus", () => {
1143
- const memberlessVerdict: TrustVerdict = {
1144
- trustClass: "unverified_contact",
1145
- canonicalSenderId: "telegram:blocked-user",
1146
- };
1147
- const memberVerdict: TrustVerdict = {
1148
- trustClass: "trusted_contact",
1149
- canonicalSenderId: "telegram:active-user",
1150
- contactId: "contact-1",
1151
- channelId: "channel-1",
1152
- type: "telegram",
1153
- address: "active-user",
1154
- status: "active",
1155
- policy: "allow",
1156
- };
1157
-
1158
- test("uses the verdict member status when the verdict resolves a member", async () => {
1159
- expect(await resolveMemberGateStatus(memberVerdict, "blocked")).toBe(
1160
- "active",
1161
- );
1162
- });
1163
-
1164
- test("falls back to local status when a non-null verdict carries no member", async () => {
1165
- // A previously blocked contact with a valid invite must stay blocked even
1166
- // when the verdict is non-null but memberless (externalChatId-only match /
1167
- // resolutionFailed), so it can't bypass the gate.
1168
- expect(await resolveMemberGateStatus(memberlessVerdict, "blocked")).toBe(
1169
- "blocked",
1170
- );
1171
- });
1172
-
1173
- test("falls back to local status when the verdict is null", async () => {
1174
- expect(await resolveMemberGateStatus(null, "blocked")).toBe("blocked");
1175
- });
1176
-
1177
- test("returns null when neither verdict member nor local status is present", async () => {
1178
- expect(await resolveMemberGateStatus(memberlessVerdict, null)).toBeNull();
1179
- });
1180
- });