@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,828 +0,0 @@
1
- /**
2
- * Typed invite redemption engine.
3
- *
4
- * Wraps the low-level invite store primitives with channel-scoped enforcement
5
- * and a discriminated-union outcome type so callers can handle every case
6
- * deterministically. The raw token is accepted as input but is never logged,
7
- * persisted, or returned in the outcome.
8
- */
9
-
10
- import {
11
- getInboundTrustVerdict,
12
- getPhoneCallerVerdict,
13
- } from "../calls/inbound-trust-reader.js";
14
- import type { ChannelId } from "../channels/types.js";
15
- import { findContactChannel, getContact } from "../contacts/contact-store.js";
16
- import { gatewayContactChannelState } from "../contacts/gateway-channel-read.js";
17
- import { activateMemberChannel } from "../contacts/member-write-relay.js";
18
- import type { ChannelStatus, ContactChannel } from "../contacts/types.js";
19
- import { ipcCallPersistent } from "../ipc/gateway-client.js";
20
- import {
21
- findActiveVoiceInvites,
22
- findByInviteCodeHash,
23
- findByTokenHash,
24
- hashToken,
25
- markInviteExpired,
26
- recordInviteUse,
27
- } from "../persistence/invite-store.js";
28
- import { canonicalizeInboundIdentity } from "../util/canonicalize-identity.js";
29
- import { getLogger } from "../util/logger.js";
30
- import { hashVoiceCode } from "../util/voice-code.js";
31
- import { verdictMemberFromVerdict } from "./trust-verdict-consumer.js";
32
-
33
- const log = getLogger("invite-redemption-service");
34
-
35
- /**
36
- * Resolve the sender's existing member status for the already_member/blocked
37
- * gate from the gateway trust verdict. Falls back to the gateway-sourced channel
38
- * status when the verdict is absent or carries no resolvable member status (e.g.
39
- * an externalChatId-only match or a resolutionFailed verdict), so a blocked
40
- * contact can't bypass the gate.
41
- */
42
- export async function resolveMemberGateStatus(
43
- verdict: Awaited<ReturnType<typeof getInboundTrustVerdict>>,
44
- fallbackStatus: ChannelStatus | null,
45
- ): Promise<ChannelStatus | null> {
46
- const memberStatus = verdict
47
- ? verdictMemberFromVerdict(verdict)?.status
48
- : null;
49
- return memberStatus ?? fallbackStatus;
50
- }
51
-
52
- /**
53
- * Gateway-sourced status for an existing local channel, used as the gate-status
54
- * fallback when the verdict resolves no member. The local row is only located by
55
- * identity; its status is read from the gateway (ACL source of truth), never the
56
- * local column.
57
- */
58
- async function gatewayFallbackStatus(
59
- channel: Pick<ContactChannel, "contactId" | "type" | "address"> | null,
60
- ): Promise<ChannelStatus | null> {
61
- if (!channel) return null;
62
- const state = await gatewayContactChannelState(channel);
63
- return (state?.status as ChannelStatus | undefined) ?? null;
64
- }
65
-
66
- // ---------------------------------------------------------------------------
67
- // Gateway lifecycle bridge (shared by all redemption paths)
68
- // ---------------------------------------------------------------------------
69
- //
70
- // The assistant is the authority on token/code → invite resolution (it holds
71
- // the hashes and caller scoping). Once it has resolved the EXACT invite and
72
- // passed its own validation, it CLAIMS the gateway-canonical row — BY ID, which
73
- // is unambiguous — as the AUTHORITATIVE lifecycle gate BEFORE mutating its own
74
- // DB. `record_invite_redemption` atomically checks status="active" and consumes
75
- // the row, so there is no check-then-act window: a guardian revoke or a racing
76
- // redemption that consumes the gateway row first makes this claim fail, and the
77
- // assistant never mutates. This keeps the gateway invite row the lifecycle
78
- // source of truth across every redemption path (token + 6-digit channel
79
- // intercepts, voice relay, HTTP).
80
-
81
- /**
82
- * Atomically CLAIM the gateway-canonical invite row for `inviteId`.
83
- *
84
- * This is the single authoritative gateway gate, performed BEFORE the assistant
85
- * mutation. Returns a decision the caller maps to behavior:
86
- * - "claimed": the gateway row existed and was consumed (active → redeemed) →
87
- * proceed to the assistant mutation.
88
- * - "rejected": the gateway row EXISTS but was NOT consumable
89
- * (revoked / exhausted / expired / already redeemed in a race) → REJECT the
90
- * redemption WITHOUT mutating the assistant DB.
91
- * - "legacy": no gateway row exists (pre-migration assistant-only invite) →
92
- * proceed (the assistant stays authoritative). Logged at warn.
93
- * - "unavailable": the gateway IPC threw (gateway unreachable). FAIL-OPEN —
94
- * proceed; the assistant-side checks still apply. Logged loudly.
95
- */
96
- async function claimGatewayRedemption(
97
- inviteId: string,
98
- redeemedBy: {
99
- redeemedByExternalUserId?: string | null;
100
- redeemedByExternalChatId?: string | null;
101
- },
102
- ): Promise<"claimed" | "rejected" | "legacy" | "unavailable"> {
103
- try {
104
- const res = (await ipcCallPersistent("record_invite_redemption", {
105
- inviteId,
106
- redeemedByExternalUserId: redeemedBy.redeemedByExternalUserId ?? null,
107
- redeemedByExternalChatId: redeemedBy.redeemedByExternalChatId ?? null,
108
- })) as { ok?: boolean; updated?: boolean; mirrored?: boolean } | null;
109
-
110
- if (!res || typeof res !== "object") {
111
- log.warn(
112
- { inviteId, res },
113
- "record_invite_redemption: gateway returned malformed response — failing open",
114
- );
115
- return "unavailable";
116
- }
117
- if (res.mirrored === false) {
118
- log.warn(
119
- { inviteId },
120
- "record_invite_redemption: no gateway invite row — treating as legacy assistant-only invite",
121
- );
122
- return "legacy";
123
- }
124
- // mirrored === true: a gateway row exists. `updated` reflects whether the
125
- // atomic status="active" claim consumed it.
126
- return res.updated ? "claimed" : "rejected";
127
- } catch (err) {
128
- // FAIL-OPEN: availability > strict gating. The assistant-side validation
129
- // (status/expiry/use-count/channel) still applies; we just couldn't claim
130
- // the gateway-canonical row.
131
- log.error(
132
- { err, inviteId },
133
- "record_invite_redemption: gateway claim unavailable — failing open (assistant-side checks still apply)",
134
- );
135
- return "unavailable";
136
- }
137
- }
138
-
139
- // ---------------------------------------------------------------------------
140
- // Outcome type
141
- // ---------------------------------------------------------------------------
142
-
143
- export type InviteRedemptionOutcome =
144
- | { ok: true; type: "redeemed"; memberId: string; inviteId: string }
145
- | { ok: true; type: "already_member"; memberId: string }
146
- | {
147
- ok: false;
148
- reason:
149
- | "invalid_token"
150
- | "expired"
151
- | "revoked"
152
- | "max_uses_reached"
153
- | "channel_mismatch"
154
- | "missing_identity";
155
- };
156
-
157
- // Generic failure reasons for voice redemption — intentionally vague to avoid
158
- // leaking information about which invites exist or which identity is bound.
159
- export type VoiceRedemptionOutcome =
160
- | { ok: true; type: "redeemed"; memberId: string; inviteId: string }
161
- | { ok: true; type: "already_member"; memberId: string }
162
- | { ok: false; reason: "invalid_or_expired" };
163
-
164
- // ---------------------------------------------------------------------------
165
- // Error-string to typed-reason mapping
166
- // ---------------------------------------------------------------------------
167
-
168
- const STORE_ERROR_TO_REASON: Record<
169
- string,
170
- (InviteRedemptionOutcome & { ok: false }) | undefined
171
- > = {
172
- invite_not_found: { ok: false, reason: "invalid_token" },
173
- invite_expired: { ok: false, reason: "expired" },
174
- invite_revoked: { ok: false, reason: "revoked" },
175
- invite_redeemed: { ok: false, reason: "max_uses_reached" },
176
- invite_max_uses_reached: { ok: false, reason: "max_uses_reached" },
177
- invite_channel_mismatch: { ok: false, reason: "channel_mismatch" },
178
- };
179
-
180
- // ---------------------------------------------------------------------------
181
- // redeemInvite
182
- // ---------------------------------------------------------------------------
183
-
184
- export async function redeemInvite(params: {
185
- rawToken: string;
186
- sourceChannel: string;
187
- externalUserId?: string;
188
- externalChatId?: string;
189
- displayName?: string;
190
- username?: string;
191
- assistantId?: string;
192
- }): Promise<InviteRedemptionOutcome> {
193
- const {
194
- rawToken,
195
- sourceChannel,
196
- externalUserId,
197
- externalChatId,
198
- displayName,
199
- username,
200
- } = params;
201
-
202
- if (!externalUserId && !externalChatId) {
203
- return { ok: false, reason: "missing_identity" };
204
- }
205
-
206
- // Validate the invite token before any membership checks to prevent
207
- // membership-status probing with arbitrary tokens.
208
- const tokenHash = hashToken(rawToken);
209
- const invite = findByTokenHash(tokenHash);
210
-
211
- if (!invite) {
212
- return { ok: false, reason: "invalid_token" };
213
- }
214
-
215
- if (invite.status !== "active") {
216
- const mapped = STORE_ERROR_TO_REASON[`invite_${invite.status}`];
217
- if (mapped) return mapped;
218
- return { ok: false, reason: "invalid_token" };
219
- }
220
-
221
- if (invite.expiresAt <= Date.now()) {
222
- markInviteExpired(invite.id);
223
- return { ok: false, reason: "expired" };
224
- }
225
-
226
- if (invite.useCount >= invite.maxUses) {
227
- return { ok: false, reason: "max_uses_reached" };
228
- }
229
-
230
- // Enforce channel match: the token must belong to the channel the caller
231
- // is redeeming from.
232
- if (sourceChannel !== invite.sourceChannel) {
233
- return { ok: false, reason: "channel_mismatch" };
234
- }
235
-
236
- // Token is valid — now safe to check existing membership without leaking
237
- // membership status to callers with bogus tokens.
238
- const canonicalUserId = externalUserId
239
- ? (canonicalizeInboundIdentity(
240
- sourceChannel as ChannelId,
241
- externalUserId,
242
- ) ?? externalUserId)
243
- : undefined;
244
- const contactResult = findContactChannel({
245
- channelType: sourceChannel,
246
- address: canonicalUserId,
247
- externalChatId: externalChatId,
248
- });
249
- const existingChannel = contactResult?.channel ?? null;
250
- const existingContact = contactResult?.contact ?? null;
251
-
252
- // If the invite targets a specific contact and the sender's existing channel
253
- // belongs to a different contact, ignore the existing match — the invite
254
- // should bind the sender's identity to the target contact, not the existing one.
255
- const targetMismatch =
256
- existingContact && existingContact.id !== invite.contactId;
257
-
258
- const gateStatus = await resolveMemberGateStatus(
259
- await getInboundTrustVerdict({
260
- channelType: sourceChannel as ChannelId,
261
- actorExternalId: canonicalUserId,
262
- }),
263
- await gatewayFallbackStatus(existingChannel),
264
- );
265
-
266
- if (existingChannel && gateStatus === "active" && !targetMismatch) {
267
- return { ok: true, type: "already_member", memberId: existingChannel.id };
268
- }
269
-
270
- // Blocked members cannot bypass the guardian's explicit block via invite
271
- // links. Return the same generic failure as an invalid token to avoid
272
- // leaking membership status to the caller.
273
- if (existingChannel && gateStatus === "blocked") {
274
- return { ok: false, reason: "invalid_token" };
275
- }
276
-
277
- // Authoritative gateway claim (by id — caller-scoped resolution already
278
- // picked the exact invite). Consume the gateway-canonical row BEFORE mutating
279
- // the assistant so a concurrent revoke/redemption that consumes it first
280
- // rejects here, with no assistant mutation to roll back. No use is consumed
281
- // for the already_member/blocked early returns above.
282
- if (
283
- (await claimGatewayRedemption(invite.id, {
284
- redeemedByExternalUserId: externalUserId,
285
- redeemedByExternalChatId: externalChatId,
286
- })) === "rejected"
287
- ) {
288
- return { ok: false, reason: "invalid_token" };
289
- }
290
-
291
- // Inactive member reactivation: when the user already has a member record in
292
- // a non-active state (revoked/pending), reactivate it gateway-first then
293
- // consume an invite use. The fresh-member path below mirrors this.
294
- if (existingChannel && !targetMismatch) {
295
- const canonicalMemberId = existingChannel.address;
296
- const canonicalCallerId = externalUserId
297
- ? canonicalizeInboundIdentity(sourceChannel as ChannelId, externalUserId)
298
- : null;
299
- const memberMatchesSender =
300
- !!canonicalCallerId && canonicalMemberId === canonicalCallerId;
301
- const preservedDisplayName =
302
- memberMatchesSender && existingContact?.displayName?.trim().length
303
- ? existingContact.displayName
304
- : displayName;
305
-
306
- // Consume the assistant invite use BEFORE activating the member, so a
307
- // concurrent revoke/exhaustion (recordInviteUse returns false) leaves no
308
- // active member behind.
309
- try {
310
- if (
311
- !recordInviteUse({
312
- inviteId: invite.id,
313
- externalUserId,
314
- externalChatId,
315
- })
316
- ) {
317
- // Invite revoked/expired between pre-validation and write.
318
- return { ok: false, reason: "invalid_token" };
319
- }
320
- } catch (err) {
321
- // Rare: the gateway claim already consumed the row but the assistant
322
- // mutation failed — a recoverable wasted gateway use; no cross-process
323
- // rollback is attempted.
324
- log.error(
325
- { err, inviteId: invite.id },
326
- "redeemInvite: assistant mutation failed AFTER gateway claim consumed the row (wasted use)",
327
- );
328
- throw err;
329
- }
330
-
331
- // Gateway-first: activate the member channel on the authoritative gateway
332
- // before the assistant DB; the local mirror is best-effort.
333
- const reactivated = await activateMemberChannel({
334
- sourceChannel,
335
- externalUserId,
336
- externalChatId,
337
- // Reactivation should not overwrite a guardian-managed nickname.
338
- displayName: preservedDisplayName,
339
- username,
340
- policy: "allow",
341
- inviteId: invite.id,
342
- verifiedAt: Date.now(),
343
- verifiedVia: "invite",
344
- contactId: invite.contactId,
345
- });
346
-
347
- if (reactivated.status === "refused") {
348
- return { ok: false, reason: "invalid_token" };
349
- }
350
-
351
- return {
352
- ok: true,
353
- type: "redeemed",
354
- memberId: reactivated.memberId,
355
- inviteId: invite.id,
356
- };
357
- }
358
-
359
- // Fresh member creation: upsert into contacts tables and consume an invite
360
- // use atomically, mirroring the reactivation path above.
361
- // When the invite targets a specific contact (targetMismatch path), preserve
362
- // the target contact's guardian-assigned display name if it has one.
363
- let freshDisplayName = displayName;
364
- if (invite.contactId) {
365
- const targetContact = getContact(invite.contactId);
366
- if (targetContact?.displayName?.trim().length) {
367
- freshDisplayName = targetContact.displayName;
368
- }
369
- }
370
-
371
- // Consume the assistant invite use BEFORE activating the member, so a
372
- // concurrent revoke/exhaustion (recordInviteUse returns false) leaves no
373
- // active member behind.
374
- try {
375
- if (
376
- !recordInviteUse({ inviteId: invite.id, externalUserId, externalChatId })
377
- ) {
378
- // Invite revoked/expired between pre-validation and write.
379
- return { ok: false, reason: "invalid_token" };
380
- }
381
- } catch (err) {
382
- // Rare: gateway claim succeeded but the assistant mutation failed — a
383
- // recoverable wasted gateway use; no cross-process rollback attempted.
384
- log.error(
385
- { err, inviteId: invite.id },
386
- "redeemInvite: assistant mutation failed AFTER gateway claim consumed the row (wasted use)",
387
- );
388
- throw err;
389
- }
390
-
391
- // Gateway-first: activate the member channel on the authoritative gateway
392
- // before the assistant DB; the local mirror is best-effort.
393
- const freshResult = await activateMemberChannel({
394
- sourceChannel,
395
- externalUserId,
396
- externalChatId,
397
- displayName: freshDisplayName,
398
- username,
399
- policy: "allow",
400
- inviteId: invite.id,
401
- verifiedAt: Date.now(),
402
- verifiedVia: "invite",
403
- contactId: invite.contactId,
404
- });
405
-
406
- if (freshResult.status === "refused") {
407
- return { ok: false, reason: "invalid_token" };
408
- }
409
-
410
- return {
411
- ok: true,
412
- type: "redeemed",
413
- memberId: freshResult.memberId,
414
- inviteId: invite.id,
415
- };
416
- }
417
-
418
- // ---------------------------------------------------------------------------
419
- // redeemVoiceInviteCode
420
- // ---------------------------------------------------------------------------
421
-
422
- /**
423
- * Redeem a voice invite code for a caller identified by their E.164 phone number.
424
- *
425
- * Unlike token-based redemption, voice redemption:
426
- * 1. Filters only active voice invites bound to the caller's identity
427
- * (expectedExternalUserId must match callerExternalUserId).
428
- * 2. Validates the short numeric code by hashing it and comparing to the
429
- * stored voiceCodeHash.
430
- * 3. Enforces expiry and use limits.
431
- * 4. On success: upserts/reactivates a member with status 'active', policy 'allow'.
432
- * 5. Consumes one invite use atomically (increment useCount).
433
- *
434
- * Failure responses are intentionally generic ("invalid_or_expired") to prevent
435
- * oracle attacks that could reveal which invites exist or which phone numbers
436
- * are bound.
437
- */
438
- export async function redeemVoiceInviteCode(params: {
439
- assistantId?: string;
440
- callerExternalUserId: string;
441
- sourceChannel: "phone";
442
- code: string;
443
- }): Promise<VoiceRedemptionOutcome> {
444
- const { callerExternalUserId, code } = params;
445
-
446
- if (!callerExternalUserId) {
447
- return { ok: false, reason: "invalid_or_expired" };
448
- }
449
-
450
- // Find all active voice invites bound to the caller's phone number
451
- const candidates = findActiveVoiceInvites({
452
- expectedExternalUserId: callerExternalUserId,
453
- });
454
-
455
- if (candidates.length === 0) {
456
- return { ok: false, reason: "invalid_or_expired" };
457
- }
458
-
459
- const codeHash = hashVoiceCode(code);
460
- const now = Date.now();
461
-
462
- // Search for a matching invite: code hash match, not expired, uses remaining
463
- const invite = candidates.find((inv) => {
464
- if (inv.voiceCodeHash !== codeHash) return false;
465
- if (inv.expiresAt <= now) return false;
466
- if (inv.useCount >= inv.maxUses) return false;
467
- return true;
468
- });
469
-
470
- if (!invite) {
471
- // Mark any expired candidates while we're here
472
- for (const inv of candidates) {
473
- if (inv.expiresAt <= now && inv.status === "active") {
474
- markInviteExpired(inv.id);
475
- }
476
- }
477
- return { ok: false, reason: "invalid_or_expired" };
478
- }
479
-
480
- // Channel enforcement: voice invites can only be redeemed on the voice channel
481
- if (invite.sourceChannel !== "phone") {
482
- return { ok: false, reason: "invalid_or_expired" };
483
- }
484
-
485
- // Check for existing membership
486
- const canonicalCallerId =
487
- canonicalizeInboundIdentity("phone" as ChannelId, callerExternalUserId) ??
488
- callerExternalUserId;
489
- const voiceContactResult = findContactChannel({
490
- channelType: "phone",
491
- address: canonicalCallerId,
492
- });
493
- const existingVoiceChannel = voiceContactResult?.channel ?? null;
494
- const voiceContact = voiceContactResult?.contact ?? null;
495
-
496
- // If the invite targets a specific contact and the sender's existing channel
497
- // belongs to a different contact, ignore the existing match — the invite
498
- // should bind the sender's identity to the target contact, not the existing one.
499
- const targetMismatch = voiceContact && voiceContact.id !== invite.contactId;
500
-
501
- const gateStatus = await resolveMemberGateStatus(
502
- await getPhoneCallerVerdict(canonicalCallerId),
503
- await gatewayFallbackStatus(existingVoiceChannel),
504
- );
505
-
506
- if (existingVoiceChannel && gateStatus === "active" && !targetMismatch) {
507
- return {
508
- ok: true,
509
- type: "already_member",
510
- memberId: existingVoiceChannel.id,
511
- };
512
- }
513
-
514
- // Blocked members cannot bypass the guardian's explicit block
515
- if (existingVoiceChannel && gateStatus === "blocked") {
516
- return { ok: false, reason: "invalid_or_expired" };
517
- }
518
-
519
- // Authoritative gateway claim (by id). The candidate was already resolved
520
- // caller-scoped (expectedExternalUserId == caller), so the id is unambiguous
521
- // even when two phone invites share a 6-digit code — this closes the
522
- // colliding-code gap that a code-hash-only gateway gate left open. Consuming
523
- // the gateway row here, before the assistant mutation, closes the TOCTOU
524
- // window: a concurrent revoke/redemption rejects with no assistant mutation.
525
- if (
526
- (await claimGatewayRedemption(invite.id, {
527
- redeemedByExternalUserId: callerExternalUserId,
528
- })) === "rejected"
529
- ) {
530
- return { ok: false, reason: "invalid_or_expired" };
531
- }
532
-
533
- // When the invite targets a specific contact, preserve the target contact's
534
- // guardian-assigned display name if it has one.
535
- let preservedDisplayName = voiceContact?.displayName?.trim().length
536
- ? voiceContact.displayName
537
- : (invite.friendName ?? undefined);
538
- if (invite.contactId) {
539
- const targetContact = getContact(invite.contactId);
540
- if (targetContact?.displayName?.trim().length) {
541
- preservedDisplayName = targetContact.displayName;
542
- }
543
- }
544
-
545
- // Consume the assistant invite use BEFORE activating the member, so a
546
- // concurrent revoke/exhaustion (recordInviteUse returns false) leaves no
547
- // active member behind.
548
- try {
549
- if (
550
- !recordInviteUse({
551
- inviteId: invite.id,
552
- externalUserId: callerExternalUserId,
553
- })
554
- ) {
555
- // Invite revoked/expired between pre-validation and write.
556
- return { ok: false, reason: "invalid_or_expired" };
557
- }
558
- } catch (err) {
559
- // Rare: gateway claim succeeded but the assistant mutation failed — a
560
- // recoverable wasted gateway use; no cross-process rollback attempted.
561
- log.error(
562
- { err, inviteId: invite.id },
563
- "redeemVoiceInviteCode: assistant mutation failed AFTER gateway claim consumed the row (wasted use)",
564
- );
565
- throw err;
566
- }
567
-
568
- // Gateway-first: activate the member channel on the authoritative gateway
569
- // before the assistant DB; the local mirror is best-effort.
570
- const writeResult = await activateMemberChannel({
571
- sourceChannel: "phone",
572
- externalUserId: callerExternalUserId,
573
- externalChatId: callerExternalUserId,
574
- displayName: preservedDisplayName,
575
- policy: "allow",
576
- inviteId: invite.id,
577
- verifiedAt: Date.now(),
578
- verifiedVia: "invite",
579
- contactId: invite.contactId,
580
- });
581
-
582
- if (writeResult.status === "refused") {
583
- return { ok: false, reason: "invalid_or_expired" };
584
- }
585
-
586
- return {
587
- ok: true,
588
- type: "redeemed",
589
- memberId: writeResult.memberId,
590
- inviteId: invite.id,
591
- };
592
- }
593
-
594
- // ---------------------------------------------------------------------------
595
- // redeemInviteByCode
596
- // ---------------------------------------------------------------------------
597
-
598
- /**
599
- * Redeem an invite using a 6-digit invite code (channel-agnostic).
600
- *
601
- * Unlike token-based redemption which uses deep links, code redemption works
602
- * by intercepting bare 6-digit messages on channels with codeRedemptionEnabled.
603
- * The code is hashed and looked up via `findByInviteCodeHash`.
604
- *
605
- * Validation: active status, not expired, uses remaining, channel match.
606
- * On success: upserts/reactivates a member with status 'active', policy 'allow'.
607
- */
608
- export async function redeemInviteByCode(params: {
609
- code: string;
610
- sourceChannel: string;
611
- externalUserId?: string;
612
- externalChatId?: string;
613
- displayName?: string;
614
- username?: string;
615
- assistantId?: string;
616
- }): Promise<InviteRedemptionOutcome> {
617
- const {
618
- code,
619
- sourceChannel,
620
- externalUserId,
621
- externalChatId,
622
- displayName,
623
- username,
624
- } = params;
625
-
626
- if (!externalUserId && !externalChatId) {
627
- return { ok: false, reason: "missing_identity" };
628
- }
629
-
630
- const codeHash = hashVoiceCode(code);
631
- const invite = findByInviteCodeHash(codeHash, sourceChannel);
632
-
633
- if (!invite) {
634
- return { ok: false, reason: "invalid_token" };
635
- }
636
-
637
- if (invite.status !== "active") {
638
- const mapped = STORE_ERROR_TO_REASON[`invite_${invite.status}`];
639
- if (mapped) return mapped;
640
- return { ok: false, reason: "invalid_token" };
641
- }
642
-
643
- if (invite.expiresAt <= Date.now()) {
644
- markInviteExpired(invite.id);
645
- return { ok: false, reason: "expired" };
646
- }
647
-
648
- if (invite.useCount >= invite.maxUses) {
649
- return { ok: false, reason: "max_uses_reached" };
650
- }
651
-
652
- // Code is valid — now safe to check existing membership without leaking
653
- // membership status to callers with bogus codes.
654
- const canonicalUserId = externalUserId
655
- ? (canonicalizeInboundIdentity(
656
- sourceChannel as ChannelId,
657
- externalUserId,
658
- ) ?? externalUserId)
659
- : undefined;
660
- const contactResult = findContactChannel({
661
- channelType: sourceChannel,
662
- address: canonicalUserId,
663
- externalChatId: externalChatId,
664
- });
665
- const existingChannel = contactResult?.channel ?? null;
666
- const existingContact = contactResult?.contact ?? null;
667
-
668
- // If the invite targets a specific contact and the sender's existing channel
669
- // belongs to a different contact, ignore the existing match — the invite
670
- // should bind the sender's identity to the target contact, not the existing one.
671
- const targetMismatch =
672
- existingContact && existingContact.id !== invite.contactId;
673
-
674
- const gateStatus = await resolveMemberGateStatus(
675
- await getInboundTrustVerdict({
676
- channelType: sourceChannel as ChannelId,
677
- actorExternalId: canonicalUserId,
678
- }),
679
- await gatewayFallbackStatus(existingChannel),
680
- );
681
-
682
- if (existingChannel && gateStatus === "active" && !targetMismatch) {
683
- return { ok: true, type: "already_member", memberId: existingChannel.id };
684
- }
685
-
686
- // Blocked members cannot bypass the guardian's explicit block via invite
687
- // codes. Return the same generic failure as an invalid token to avoid
688
- // leaking membership status to the caller.
689
- if (existingChannel && gateStatus === "blocked") {
690
- return { ok: false, reason: "invalid_token" };
691
- }
692
-
693
- // Authoritative gateway claim (by id). Consume the gateway-canonical row
694
- // before mutating the assistant, so a concurrent revoke/redemption that
695
- // consumes it first rejects here with no assistant mutation to roll back. No
696
- // use is consumed for the already_member/blocked early returns above.
697
- if (
698
- (await claimGatewayRedemption(invite.id, {
699
- redeemedByExternalUserId: externalUserId,
700
- redeemedByExternalChatId: externalChatId,
701
- })) === "rejected"
702
- ) {
703
- return { ok: false, reason: "invalid_token" };
704
- }
705
-
706
- // Inactive member reactivation: reactivate gateway-first, then consume an
707
- // invite use.
708
- if (existingChannel && !targetMismatch) {
709
- const canonicalMemberId = existingChannel.address;
710
- const canonicalCallerId = externalUserId
711
- ? canonicalizeInboundIdentity(sourceChannel as ChannelId, externalUserId)
712
- : null;
713
- const memberMatchesSender =
714
- !!canonicalCallerId && canonicalMemberId === canonicalCallerId;
715
- const preservedDisplayName =
716
- memberMatchesSender && existingContact?.displayName?.trim().length
717
- ? existingContact.displayName
718
- : displayName;
719
-
720
- // Consume the assistant invite use BEFORE activating the member, so a
721
- // concurrent revoke/exhaustion (recordInviteUse returns false) leaves no
722
- // active member behind.
723
- try {
724
- if (
725
- !recordInviteUse({
726
- inviteId: invite.id,
727
- externalUserId,
728
- externalChatId,
729
- })
730
- ) {
731
- // Invite revoked/expired between pre-validation and write.
732
- return { ok: false, reason: "invalid_token" };
733
- }
734
- } catch (err) {
735
- // Rare: gateway claim succeeded but the assistant mutation failed — a
736
- // recoverable wasted gateway use; no cross-process rollback attempted.
737
- log.error(
738
- { err, inviteId: invite.id },
739
- "redeemInviteByCode: assistant mutation failed AFTER gateway claim consumed the row (wasted use)",
740
- );
741
- throw err;
742
- }
743
-
744
- // Gateway-first: activate the member channel on the authoritative gateway
745
- // before the assistant DB; the local mirror is best-effort.
746
- const reactivated = await activateMemberChannel({
747
- sourceChannel,
748
- externalUserId,
749
- externalChatId,
750
- displayName: preservedDisplayName,
751
- username,
752
- policy: "allow",
753
- inviteId: invite.id,
754
- verifiedAt: Date.now(),
755
- verifiedVia: "invite",
756
- contactId: invite.contactId,
757
- });
758
-
759
- if (reactivated.status === "refused") {
760
- return { ok: false, reason: "invalid_token" };
761
- }
762
-
763
- return {
764
- ok: true,
765
- type: "redeemed",
766
- memberId: reactivated.memberId,
767
- inviteId: invite.id,
768
- };
769
- }
770
-
771
- // Fresh member creation: upsert into contacts tables and consume an invite
772
- // use atomically.
773
- // When the invite targets a specific contact (targetMismatch path), preserve
774
- // the target contact's guardian-assigned display name if it has one.
775
- let freshDisplayName = displayName;
776
- if (invite.contactId) {
777
- const targetContact = getContact(invite.contactId);
778
- if (targetContact?.displayName?.trim().length) {
779
- freshDisplayName = targetContact.displayName;
780
- }
781
- }
782
-
783
- // Consume the assistant invite use BEFORE activating the member, so a
784
- // concurrent revoke/exhaustion (recordInviteUse returns false) leaves no
785
- // active member behind.
786
- try {
787
- if (
788
- !recordInviteUse({ inviteId: invite.id, externalUserId, externalChatId })
789
- ) {
790
- // Invite revoked/expired between pre-validation and write.
791
- return { ok: false, reason: "invalid_token" };
792
- }
793
- } catch (err) {
794
- // Rare: gateway claim succeeded but the assistant mutation failed — a
795
- // recoverable wasted gateway use; no cross-process rollback attempted.
796
- log.error(
797
- { err, inviteId: invite.id },
798
- "redeemInviteByCode: assistant mutation failed AFTER gateway claim consumed the row (wasted use)",
799
- );
800
- throw err;
801
- }
802
-
803
- // Gateway-first: activate the member channel on the authoritative gateway
804
- // before the assistant DB; the local mirror is best-effort.
805
- const freshResult = await activateMemberChannel({
806
- sourceChannel,
807
- externalUserId,
808
- externalChatId,
809
- displayName: freshDisplayName,
810
- username,
811
- policy: "allow",
812
- inviteId: invite.id,
813
- verifiedAt: Date.now(),
814
- verifiedVia: "invite",
815
- contactId: invite.contactId,
816
- });
817
-
818
- if (freshResult.status === "refused") {
819
- return { ok: false, reason: "invalid_token" };
820
- }
821
-
822
- return {
823
- ok: true,
824
- type: "redeemed",
825
- memberId: freshResult.memberId,
826
- inviteId: invite.id,
827
- };
828
- }