@vellumai/assistant 0.10.0 → 0.10.1-dev.202606240317.ea25efe

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 (972) hide show
  1. package/ARCHITECTURE.md +36 -37
  2. package/bun.lock +3 -0
  3. package/docs/workflows.md +12 -7
  4. package/eslint-rules/cli-no-daemon-internals.js +12 -0
  5. package/node_modules/@slack/types/LICENSE +23 -0
  6. package/node_modules/@slack/types/README.md +32 -0
  7. package/node_modules/@slack/types/dist/block-kit/block-elements.d.ts +953 -0
  8. package/node_modules/@slack/types/dist/block-kit/block-elements.d.ts.map +1 -0
  9. package/node_modules/@slack/types/dist/block-kit/block-elements.js +4 -0
  10. package/node_modules/@slack/types/dist/block-kit/block-elements.js.map +1 -0
  11. package/node_modules/@slack/types/dist/block-kit/blocks.d.ts +474 -0
  12. package/node_modules/@slack/types/dist/block-kit/blocks.d.ts.map +1 -0
  13. package/node_modules/@slack/types/dist/block-kit/blocks.js +3 -0
  14. package/node_modules/@slack/types/dist/block-kit/blocks.js.map +1 -0
  15. package/node_modules/@slack/types/dist/block-kit/composition-objects.d.ts +237 -0
  16. package/node_modules/@slack/types/dist/block-kit/composition-objects.d.ts.map +1 -0
  17. package/node_modules/@slack/types/dist/block-kit/composition-objects.js +4 -0
  18. package/node_modules/@slack/types/dist/block-kit/composition-objects.js.map +1 -0
  19. package/node_modules/@slack/types/dist/block-kit/extensions.d.ts +88 -0
  20. package/node_modules/@slack/types/dist/block-kit/extensions.d.ts.map +1 -0
  21. package/node_modules/@slack/types/dist/block-kit/extensions.js +3 -0
  22. package/node_modules/@slack/types/dist/block-kit/extensions.js.map +1 -0
  23. package/node_modules/@slack/types/dist/calls.d.ts +26 -0
  24. package/node_modules/@slack/types/dist/calls.d.ts.map +1 -0
  25. package/node_modules/@slack/types/dist/calls.js +6 -0
  26. package/node_modules/@slack/types/dist/calls.js.map +1 -0
  27. package/node_modules/@slack/types/dist/chunk.d.ts +52 -0
  28. package/node_modules/@slack/types/dist/chunk.d.ts.map +1 -0
  29. package/node_modules/@slack/types/dist/chunk.js +3 -0
  30. package/node_modules/@slack/types/dist/chunk.js.map +1 -0
  31. package/node_modules/@slack/types/dist/common/bot-profile.d.ts +12 -0
  32. package/node_modules/@slack/types/dist/common/bot-profile.d.ts.map +1 -0
  33. package/node_modules/@slack/types/dist/common/bot-profile.js +3 -0
  34. package/node_modules/@slack/types/dist/common/bot-profile.js.map +1 -0
  35. package/node_modules/@slack/types/dist/common/status-emoji-display-info.d.ts +6 -0
  36. package/node_modules/@slack/types/dist/common/status-emoji-display-info.d.ts.map +1 -0
  37. package/node_modules/@slack/types/dist/common/status-emoji-display-info.js +3 -0
  38. package/node_modules/@slack/types/dist/common/status-emoji-display-info.js.map +1 -0
  39. package/node_modules/@slack/types/dist/dialog.d.ts +36 -0
  40. package/node_modules/@slack/types/dist/dialog.d.ts.map +1 -0
  41. package/node_modules/@slack/types/dist/dialog.js +3 -0
  42. package/node_modules/@slack/types/dist/dialog.js.map +1 -0
  43. package/node_modules/@slack/types/dist/events/app.d.ts +204 -0
  44. package/node_modules/@slack/types/dist/events/app.d.ts.map +1 -0
  45. package/node_modules/@slack/types/dist/events/app.js +3 -0
  46. package/node_modules/@slack/types/dist/events/app.js.map +1 -0
  47. package/node_modules/@slack/types/dist/events/assistant.d.ts +29 -0
  48. package/node_modules/@slack/types/dist/events/assistant.d.ts.map +1 -0
  49. package/node_modules/@slack/types/dist/events/assistant.js +3 -0
  50. package/node_modules/@slack/types/dist/events/assistant.js.map +1 -0
  51. package/node_modules/@slack/types/dist/events/call.d.ts +8 -0
  52. package/node_modules/@slack/types/dist/events/call.d.ts.map +1 -0
  53. package/node_modules/@slack/types/dist/events/call.js +3 -0
  54. package/node_modules/@slack/types/dist/events/call.js.map +1 -0
  55. package/node_modules/@slack/types/dist/events/channel.d.ts +85 -0
  56. package/node_modules/@slack/types/dist/events/channel.d.ts.map +1 -0
  57. package/node_modules/@slack/types/dist/events/channel.js +3 -0
  58. package/node_modules/@slack/types/dist/events/channel.js.map +1 -0
  59. package/node_modules/@slack/types/dist/events/dnd.d.ts +24 -0
  60. package/node_modules/@slack/types/dist/events/dnd.d.ts.map +1 -0
  61. package/node_modules/@slack/types/dist/events/dnd.js +3 -0
  62. package/node_modules/@slack/types/dist/events/dnd.js.map +1 -0
  63. package/node_modules/@slack/types/dist/events/email.d.ts +6 -0
  64. package/node_modules/@slack/types/dist/events/email.d.ts.map +1 -0
  65. package/node_modules/@slack/types/dist/events/email.js +3 -0
  66. package/node_modules/@slack/types/dist/events/email.js.map +1 -0
  67. package/node_modules/@slack/types/dist/events/emoji.d.ts +11 -0
  68. package/node_modules/@slack/types/dist/events/emoji.d.ts.map +1 -0
  69. package/node_modules/@slack/types/dist/events/emoji.js +3 -0
  70. package/node_modules/@slack/types/dist/events/emoji.js.map +1 -0
  71. package/node_modules/@slack/types/dist/events/entity-details-requested.d.ts +21 -0
  72. package/node_modules/@slack/types/dist/events/entity-details-requested.d.ts.map +1 -0
  73. package/node_modules/@slack/types/dist/events/entity-details-requested.js +3 -0
  74. package/node_modules/@slack/types/dist/events/entity-details-requested.js.map +1 -0
  75. package/node_modules/@slack/types/dist/events/file.d.ts +60 -0
  76. package/node_modules/@slack/types/dist/events/file.d.ts.map +1 -0
  77. package/node_modules/@slack/types/dist/events/file.js +4 -0
  78. package/node_modules/@slack/types/dist/events/file.js.map +1 -0
  79. package/node_modules/@slack/types/dist/events/function.d.ts +33 -0
  80. package/node_modules/@slack/types/dist/events/function.d.ts.map +1 -0
  81. package/node_modules/@slack/types/dist/events/function.js +3 -0
  82. package/node_modules/@slack/types/dist/events/function.js.map +1 -0
  83. package/node_modules/@slack/types/dist/events/grid-migration.d.ts +9 -0
  84. package/node_modules/@slack/types/dist/events/grid-migration.d.ts.map +1 -0
  85. package/node_modules/@slack/types/dist/events/grid-migration.js +3 -0
  86. package/node_modules/@slack/types/dist/events/grid-migration.js.map +1 -0
  87. package/node_modules/@slack/types/dist/events/group.d.ts +55 -0
  88. package/node_modules/@slack/types/dist/events/group.d.ts.map +1 -0
  89. package/node_modules/@slack/types/dist/events/group.js +3 -0
  90. package/node_modules/@slack/types/dist/events/group.js.map +1 -0
  91. package/node_modules/@slack/types/dist/events/im.d.ts +26 -0
  92. package/node_modules/@slack/types/dist/events/im.d.ts.map +1 -0
  93. package/node_modules/@slack/types/dist/events/im.js +3 -0
  94. package/node_modules/@slack/types/dist/events/im.js.map +1 -0
  95. package/node_modules/@slack/types/dist/events/index.d.ts +60 -0
  96. package/node_modules/@slack/types/dist/events/index.d.ts.map +1 -0
  97. package/node_modules/@slack/types/dist/events/index.js +43 -0
  98. package/node_modules/@slack/types/dist/events/index.js.map +1 -0
  99. package/node_modules/@slack/types/dist/events/invite.d.ts +20 -0
  100. package/node_modules/@slack/types/dist/events/invite.d.ts.map +1 -0
  101. package/node_modules/@slack/types/dist/events/invite.js +3 -0
  102. package/node_modules/@slack/types/dist/events/invite.js.map +1 -0
  103. package/node_modules/@slack/types/dist/events/link-shared.d.ts +16 -0
  104. package/node_modules/@slack/types/dist/events/link-shared.d.ts.map +1 -0
  105. package/node_modules/@slack/types/dist/events/link-shared.js +3 -0
  106. package/node_modules/@slack/types/dist/events/link-shared.js.map +1 -0
  107. package/node_modules/@slack/types/dist/events/member.d.ts +19 -0
  108. package/node_modules/@slack/types/dist/events/member.d.ts.map +1 -0
  109. package/node_modules/@slack/types/dist/events/member.js +3 -0
  110. package/node_modules/@slack/types/dist/events/member.js.map +1 -0
  111. package/node_modules/@slack/types/dist/events/message-metadata.d.ts +38 -0
  112. package/node_modules/@slack/types/dist/events/message-metadata.d.ts.map +1 -0
  113. package/node_modules/@slack/types/dist/events/message-metadata.js +3 -0
  114. package/node_modules/@slack/types/dist/events/message-metadata.js.map +1 -0
  115. package/node_modules/@slack/types/dist/events/message.d.ts +306 -0
  116. package/node_modules/@slack/types/dist/events/message.d.ts.map +1 -0
  117. package/node_modules/@slack/types/dist/events/message.js +3 -0
  118. package/node_modules/@slack/types/dist/events/message.js.map +1 -0
  119. package/node_modules/@slack/types/dist/events/pin.d.ts +60 -0
  120. package/node_modules/@slack/types/dist/events/pin.d.ts.map +1 -0
  121. package/node_modules/@slack/types/dist/events/pin.js +3 -0
  122. package/node_modules/@slack/types/dist/events/pin.js.map +1 -0
  123. package/node_modules/@slack/types/dist/events/reaction.d.ts +23 -0
  124. package/node_modules/@slack/types/dist/events/reaction.d.ts.map +1 -0
  125. package/node_modules/@slack/types/dist/events/reaction.js +3 -0
  126. package/node_modules/@slack/types/dist/events/reaction.js.map +1 -0
  127. package/node_modules/@slack/types/dist/events/shared-channel.d.ts +134 -0
  128. package/node_modules/@slack/types/dist/events/shared-channel.d.ts.map +1 -0
  129. package/node_modules/@slack/types/dist/events/shared-channel.js +3 -0
  130. package/node_modules/@slack/types/dist/events/shared-channel.js.map +1 -0
  131. package/node_modules/@slack/types/dist/events/star.d.ts +13 -0
  132. package/node_modules/@slack/types/dist/events/star.d.ts.map +1 -0
  133. package/node_modules/@slack/types/dist/events/star.js +3 -0
  134. package/node_modules/@slack/types/dist/events/star.js.map +1 -0
  135. package/node_modules/@slack/types/dist/events/steps-from-apps.d.ts +82 -0
  136. package/node_modules/@slack/types/dist/events/steps-from-apps.d.ts.map +1 -0
  137. package/node_modules/@slack/types/dist/events/steps-from-apps.js +3 -0
  138. package/node_modules/@slack/types/dist/events/steps-from-apps.js.map +1 -0
  139. package/node_modules/@slack/types/dist/events/subteam.d.ts +66 -0
  140. package/node_modules/@slack/types/dist/events/subteam.d.ts.map +1 -0
  141. package/node_modules/@slack/types/dist/events/subteam.js +3 -0
  142. package/node_modules/@slack/types/dist/events/subteam.js.map +1 -0
  143. package/node_modules/@slack/types/dist/events/team.d.ts +99 -0
  144. package/node_modules/@slack/types/dist/events/team.d.ts.map +1 -0
  145. package/node_modules/@slack/types/dist/events/team.js +3 -0
  146. package/node_modules/@slack/types/dist/events/team.js.map +1 -0
  147. package/node_modules/@slack/types/dist/events/token.d.ts +8 -0
  148. package/node_modules/@slack/types/dist/events/token.d.ts.map +1 -0
  149. package/node_modules/@slack/types/dist/events/token.js +3 -0
  150. package/node_modules/@slack/types/dist/events/token.js.map +1 -0
  151. package/node_modules/@slack/types/dist/events/user.d.ts +313 -0
  152. package/node_modules/@slack/types/dist/events/user.d.ts.map +1 -0
  153. package/node_modules/@slack/types/dist/events/user.js +3 -0
  154. package/node_modules/@slack/types/dist/events/user.js.map +1 -0
  155. package/node_modules/@slack/types/dist/index.d.ts +12 -0
  156. package/node_modules/@slack/types/dist/index.d.ts.map +1 -0
  157. package/node_modules/@slack/types/dist/index.js +28 -0
  158. package/node_modules/@slack/types/dist/index.js.map +1 -0
  159. package/node_modules/@slack/types/dist/message-attachments.d.ts +171 -0
  160. package/node_modules/@slack/types/dist/message-attachments.d.ts.map +1 -0
  161. package/node_modules/@slack/types/dist/message-attachments.js +3 -0
  162. package/node_modules/@slack/types/dist/message-attachments.js.map +1 -0
  163. package/node_modules/@slack/types/dist/message-metadata.d.ts +281 -0
  164. package/node_modules/@slack/types/dist/message-metadata.d.ts.map +1 -0
  165. package/node_modules/@slack/types/dist/message-metadata.js +27 -0
  166. package/node_modules/@slack/types/dist/message-metadata.js.map +1 -0
  167. package/node_modules/@slack/types/dist/views.d.ts +71 -0
  168. package/node_modules/@slack/types/dist/views.d.ts.map +1 -0
  169. package/node_modules/@slack/types/dist/views.js +3 -0
  170. package/node_modules/@slack/types/dist/views.js.map +1 -0
  171. package/node_modules/@slack/types/package.json +47 -0
  172. package/node_modules/@vellumai/gateway-client/bun.lock +3 -0
  173. package/node_modules/@vellumai/gateway-client/package.json +1 -0
  174. package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +69 -0
  175. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +91 -0
  176. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +96 -0
  177. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +162 -0
  178. package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +48 -0
  179. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +8 -0
  180. package/node_modules/@vellumai/gateway-client/src/index.ts +28 -0
  181. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +4 -2
  182. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +3 -2
  183. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +95 -0
  184. package/openapi.yaml +458 -18
  185. package/package.json +2 -1
  186. package/scripts/memory-inspect.ts +24 -14
  187. package/scripts/test.sh +36 -15
  188. package/src/__tests__/access-request-seed-content-blocks.test.ts +83 -103
  189. package/src/__tests__/activation-early-marking.test.ts +1 -1
  190. package/src/__tests__/actor-token-service.test.ts +39 -17
  191. package/src/__tests__/agent-loop-callsite-precedence.test.ts +1 -40
  192. package/src/__tests__/agent-loop-compaction-events.test.ts +0 -1
  193. package/src/__tests__/agent-loop-compaction-strip.test.ts +0 -1
  194. package/src/__tests__/agent-loop-exit-reason.test.ts +0 -1
  195. package/src/__tests__/agent-loop-pushes-post-hook-prompt.test.ts +306 -0
  196. package/src/__tests__/agent-loop-regrowth-guard.test.ts +0 -1
  197. package/src/__tests__/agent-loop.test.ts +3 -0
  198. package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
  199. package/src/__tests__/anthropic-provider.test.ts +210 -9
  200. package/src/__tests__/app-builder-skill-instructions.test.ts +47 -5
  201. package/src/__tests__/app-conversation-ids-backfill.test.ts +1 -1
  202. package/src/__tests__/app-source-watcher.test.ts +30 -10
  203. package/src/__tests__/approval-cascade.test.ts +6 -0
  204. package/src/__tests__/approval-interception-trust-gates.test.ts +151 -0
  205. package/src/__tests__/approval-primitive.test.ts +1 -1
  206. package/src/__tests__/approval-routes-http.test.ts +1 -1
  207. package/src/__tests__/assistant-attachments.test.ts +155 -0
  208. package/src/__tests__/assistant-event-hub-machine-name.test.ts +2 -4
  209. package/src/__tests__/assistant-events-sse-hardening.test.ts +1 -1
  210. package/src/__tests__/assistant-events-sse-shed.test.ts +1 -1
  211. package/src/__tests__/attachment-upload-trusted-source.test.ts +13 -8
  212. package/src/__tests__/attachments-store.test.ts +1 -1
  213. package/src/__tests__/audit-log-rotation.test.ts +50 -54
  214. package/src/__tests__/auth-fallback-events-store.test.ts +1 -1
  215. package/src/__tests__/auto-analysis-end-to-end.test.ts +9 -14
  216. package/src/__tests__/background-shell-bash.test.ts +4 -1
  217. package/src/__tests__/background-shell-host-bash.test.ts +17 -3
  218. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -0
  219. package/src/__tests__/call-controller.test.ts +20 -1
  220. package/src/__tests__/call-conversation-messages.test.ts +1 -1
  221. package/src/__tests__/call-domain.test.ts +1 -1
  222. package/src/__tests__/call-pointer-messages.test.ts +3 -4
  223. package/src/__tests__/call-recovery.test.ts +1 -1
  224. package/src/__tests__/call-routes-http.test.ts +1 -1
  225. package/src/__tests__/call-store.test.ts +1 -1
  226. package/src/__tests__/cancel-resolves-conversation-key.test.ts +1 -1
  227. package/src/__tests__/canonical-guardian-store.test.ts +24 -1
  228. package/src/__tests__/card-surface-data.test.ts +60 -0
  229. package/src/__tests__/channel-approval-routes.test.ts +73 -1119
  230. package/src/__tests__/channel-delivery-store.test.ts +1 -1
  231. package/src/__tests__/channel-guardian.test.ts +291 -641
  232. package/src/__tests__/channel-inbound-disk-pressure.test.ts +1 -2
  233. package/src/__tests__/channel-retry-sweep.test.ts +1 -1
  234. package/src/__tests__/compaction-events.test.ts +6 -0
  235. package/src/__tests__/compaction-trail-store.test.ts +6 -5
  236. package/src/__tests__/compaction.benchmark.test.ts +0 -1
  237. package/src/__tests__/compactor-image-manifest-trust.test.ts +1 -1
  238. package/src/__tests__/config-loader-backfill.test.ts +188 -52
  239. package/src/__tests__/config-schema.test.ts +35 -0
  240. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +1 -2
  241. package/src/__tests__/contact-store-user-file.test.ts +2 -2
  242. package/src/__tests__/contacts-relay-reads.test.ts +409 -0
  243. package/src/__tests__/contacts-tools.test.ts +4 -4
  244. package/src/__tests__/contacts-write.test.ts +1 -2
  245. package/src/__tests__/context-search-conversations-source.test.ts +1 -1
  246. package/src/__tests__/context-window-manager-compact-retry.test.ts +6 -2
  247. package/src/__tests__/context-window-manager-overflow-rung.test.ts +6 -2
  248. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
  249. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +3 -0
  250. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +3 -0
  251. package/src/__tests__/conversation-agent-loop-overflow.test.ts +3 -0
  252. package/src/__tests__/conversation-agent-loop.test.ts +7 -0
  253. package/src/__tests__/conversation-attachments.test.ts +2 -5
  254. package/src/__tests__/conversation-attention-store.test.ts +1 -1
  255. package/src/__tests__/conversation-attention-telegram.test.ts +1 -2
  256. package/src/__tests__/conversation-clear-safety.test.ts +1 -1
  257. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -0
  258. package/src/__tests__/conversation-crud-inference-profile.test.ts +1 -1
  259. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +12 -19
  260. package/src/__tests__/conversation-disk-view-integration.test.ts +1 -1
  261. package/src/__tests__/conversation-disk-view.test.ts +1 -1
  262. package/src/__tests__/conversation-fork-crud.test.ts +10 -8
  263. package/src/__tests__/conversation-fork-retrospective.test.ts +250 -0
  264. package/src/__tests__/conversation-fork-route.test.ts +1 -1
  265. package/src/__tests__/conversation-inference-profile-list.test.ts +1 -1
  266. package/src/__tests__/conversation-inference-profile-route.test.ts +1 -1
  267. package/src/__tests__/conversation-init.benchmark.test.ts +1 -1
  268. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  269. package/src/__tests__/conversation-lifecycle.test.ts +117 -0
  270. package/src/__tests__/conversation-list-source.test.ts +3 -3
  271. package/src/__tests__/conversation-process-callsite.test.ts +6 -14
  272. package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
  273. package/src/__tests__/conversation-queue.test.ts +95 -0
  274. package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
  275. package/src/__tests__/conversation-routes-guardian-reply.test.ts +12 -0
  276. package/src/__tests__/conversation-routes-slash-commands.test.ts +12 -0
  277. package/src/__tests__/conversation-runtime-assembly.test.ts +115 -12
  278. package/src/__tests__/conversation-slash-queue.test.ts +6 -0
  279. package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
  280. package/src/__tests__/conversation-speed-override.test.ts +6 -0
  281. package/src/__tests__/conversation-starter-routes.test.ts +5 -5
  282. package/src/__tests__/conversation-store.test.ts +1 -1
  283. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +4 -4
  284. package/src/__tests__/conversation-surfaces-task-progress.test.ts +352 -0
  285. package/src/__tests__/conversation-sync-tags.test.ts +1 -1
  286. package/src/__tests__/conversation-tool-setup-attribution.test.ts +47 -0
  287. package/src/__tests__/conversation-usage.test.ts +1 -1
  288. package/src/__tests__/conversation-wipe.test.ts +9 -8
  289. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -0
  290. package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
  291. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
  292. package/src/__tests__/conversations-import-system-filter.test.ts +1 -1
  293. package/src/__tests__/copy-composer-tc-templates.test.ts +17 -0
  294. package/src/__tests__/credential-security-invariants.test.ts +0 -1
  295. package/src/__tests__/db-acp-history.test.ts +2 -2
  296. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +5 -7
  297. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +6 -7
  298. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +5 -10
  299. package/src/__tests__/db-migration-rollback.test.ts +129 -39
  300. package/src/__tests__/db-proxy-transaction.test.ts +1 -1
  301. package/src/__tests__/db-schedule-syntax-migration.test.ts +0 -11
  302. package/src/__tests__/db-test-helpers.ts +36 -19
  303. package/src/__tests__/delete-propagation.test.ts +1 -1
  304. package/src/__tests__/deterministic-verification-control-plane.test.ts +28 -8
  305. package/src/__tests__/disk-pressure-guard.test.ts +41 -0
  306. package/src/__tests__/disk-pressure-tools.test.ts +41 -1
  307. package/src/__tests__/dm-backfill.test.ts +1 -1
  308. package/src/__tests__/drop-capability-card-state-migration.test.ts +0 -8
  309. package/src/__tests__/dynamic-page-surface.test.ts +0 -94
  310. package/src/__tests__/edit-propagation.test.ts +1 -1
  311. package/src/__tests__/emit-signal-routing-intent.test.ts +93 -5
  312. package/src/__tests__/empty-response-hook.test.ts +42 -0
  313. package/src/__tests__/events-client-registration.test.ts +1 -1
  314. package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
  315. package/src/__tests__/followup-tools.test.ts +1 -1
  316. package/src/__tests__/gemini-count-tokens.test.ts +70 -0
  317. package/src/__tests__/guardian-action-sweep.test.ts +9 -2
  318. package/src/__tests__/guardian-binding-drift-heal.test.ts +76 -11
  319. package/src/__tests__/guardian-card-withdrawal.test.ts +1 -1
  320. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +1 -1
  321. package/src/__tests__/guardian-dispatch.test.ts +96 -2
  322. package/src/__tests__/guardian-outbound-http.test.ts +20 -12
  323. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +1 -1
  324. package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
  325. package/src/__tests__/guardian-routing-state.test.ts +1 -2
  326. package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -1
  327. package/src/__tests__/headless-browser-mode.test.ts +2 -2
  328. package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -0
  329. package/src/__tests__/heartbeat-service.test.ts +6 -0
  330. package/src/__tests__/helpers/channel-test-adapter.ts +92 -0
  331. package/src/__tests__/host-app-control-routes.test.ts +24 -30
  332. package/src/__tests__/host-bash-routes.test.ts +31 -41
  333. package/src/__tests__/host-browser-routes.test.ts +26 -32
  334. package/src/__tests__/host-cu-routes-targeted.test.ts +25 -33
  335. package/src/__tests__/host-file-routes-targeted.test.ts +40 -52
  336. package/src/__tests__/host-transfer-routes-targeted.test.ts +31 -43
  337. package/src/__tests__/http-conversation-lineage.test.ts +1 -1
  338. package/src/__tests__/http-user-message-parity.test.ts +165 -8
  339. package/src/__tests__/image-recovery-hook.test.ts +1 -1
  340. package/src/__tests__/inbound-invite-redemption.test.ts +1 -2
  341. package/src/__tests__/inbound-trust-verdict.test.ts +254 -0
  342. package/src/__tests__/inference-profile-reaper.test.ts +1 -1
  343. package/src/__tests__/inference-profile-session-handler.test.ts +1 -1
  344. package/src/__tests__/inference-profile-session-ipc.test.ts +1 -1
  345. package/src/__tests__/injector-chain.test.ts +1 -1
  346. package/src/__tests__/injector-disk-pressure.test.ts +11 -6
  347. package/src/__tests__/internal-telemetry-routes.test.ts +1 -1
  348. package/src/__tests__/invite-redemption-service.test.ts +244 -43
  349. package/src/__tests__/invite-routes-http.test.ts +35 -186
  350. package/src/__tests__/invite-service-ipc.test.ts +287 -0
  351. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +5 -5
  352. package/src/__tests__/jobs-store-upsert-debounced.test.ts +9 -12
  353. package/src/__tests__/list-messages-attachments.test.ts +42 -1
  354. package/src/__tests__/list-messages-client-message-id.test.ts +1 -1
  355. package/src/__tests__/list-messages-hidden-metadata.test.ts +1 -1
  356. package/src/__tests__/list-messages-page-latest.test.ts +1 -1
  357. package/src/__tests__/list-messages-tool-merge.test.ts +1 -1
  358. package/src/__tests__/llm-context-normalization.test.ts +105 -0
  359. package/src/__tests__/llm-context-route-provider.test.ts +69 -4
  360. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +9 -5
  361. package/src/__tests__/llm-request-log-call-site.test.ts +6 -6
  362. package/src/__tests__/llm-request-log-turn-query.test.ts +27 -13
  363. package/src/__tests__/llm-resolver.test.ts +205 -5
  364. package/src/__tests__/llm-usage-store.test.ts +65 -1
  365. package/src/__tests__/log-export-routes.test.ts +1 -1
  366. package/src/__tests__/log-export-workspace.test.ts +3 -3
  367. package/src/__tests__/media-stream-server-integration.test.ts +127 -0
  368. package/src/__tests__/memory-jobs-worker-lanes.test.ts +5 -5
  369. package/src/__tests__/memory-recall-log-store.test.ts +1 -1
  370. package/src/__tests__/memory-upsert-concurrency.test.ts +3 -4
  371. package/src/__tests__/messages-after-tiebreaker.test.ts +1 -1
  372. package/src/__tests__/migration-import-from-url.test.ts +2 -2
  373. package/src/__tests__/mtime-cache.test.ts +375 -0
  374. package/src/__tests__/non-member-access-request.test.ts +190 -19
  375. package/src/__tests__/notification-broadcaster.test.ts +4 -0
  376. package/src/__tests__/notification-candidate-guardian-context.test.ts +203 -0
  377. package/src/__tests__/notification-decision-recipient-context.test.ts +33 -32
  378. package/src/__tests__/notification-deep-link.test.ts +4 -0
  379. package/src/__tests__/notification-guardian-path.test.ts +20 -1
  380. package/src/__tests__/notification-schedule-notify-dedup.test.ts +1 -1
  381. package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
  382. package/src/__tests__/oauth-provider-visibility.test.ts +1 -1
  383. package/src/__tests__/oauth-store.test.ts +1 -1
  384. package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
  385. package/src/__tests__/persist-unsendable-image-downscale.test.ts +1 -1
  386. package/src/__tests__/persist-unsendable-image.test.ts +1 -1
  387. package/src/__tests__/persona-resolver.test.ts +39 -1
  388. package/src/__tests__/platform-bash-auto-approve.test.ts +1 -1
  389. package/src/__tests__/playbook-execution.test.ts +1 -1
  390. package/src/__tests__/playbook-tools.test.ts +1 -1
  391. package/src/__tests__/plugin-api-model-profiles.test.ts +74 -21
  392. package/src/__tests__/plugin-bootstrap.test.ts +78 -0
  393. package/src/__tests__/provider-platform-proxy-integration.test.ts +25 -5
  394. package/src/__tests__/provider-usage-tracking.test.ts +40 -1
  395. package/src/__tests__/prune-old-conversations-job.test.ts +1 -1
  396. package/src/__tests__/reaction-persistence.test.ts +1 -1
  397. package/src/__tests__/registry.test.ts +3 -0
  398. package/src/__tests__/relay-server.test.ts +1026 -73
  399. package/src/__tests__/runtime-attachment-metadata.test.ts +9 -1
  400. package/src/__tests__/runtime-events-sse-bilingual.test.ts +7 -9
  401. package/src/__tests__/runtime-events-sse-parity.test.ts +1 -1
  402. package/src/__tests__/runtime-events-sse-reconnect.test.ts +1 -1
  403. package/src/__tests__/runtime-events-sse.test.ts +1 -1
  404. package/src/__tests__/schedule-retry.test.ts +1 -1
  405. package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -1
  406. package/src/__tests__/schedule-routes.test.ts +1 -1
  407. package/src/__tests__/schedule-store.test.ts +1 -1
  408. package/src/__tests__/schedule-tools.test.ts +1 -1
  409. package/src/__tests__/scheduler-disk-pressure.test.ts +1 -1
  410. package/src/__tests__/scheduler-recurrence.test.ts +1 -1
  411. package/src/__tests__/scheduler-reuse-conversation.test.ts +1 -1
  412. package/src/__tests__/scheduler-wake.test.ts +2 -1
  413. package/src/__tests__/scoped-approval-grants.test.ts +1 -1
  414. package/src/__tests__/scoped-grant-security-matrix.test.ts +5 -5
  415. package/src/__tests__/scrub-corrupted-image-attachments.test.ts +0 -8
  416. package/src/__tests__/secret-ingress-http.test.ts +12 -0
  417. package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -0
  418. package/src/__tests__/send-endpoint-busy.test.ts +31 -9
  419. package/src/__tests__/sequence-store.test.ts +1 -1
  420. package/src/__tests__/server-history-render.test.ts +40 -1
  421. package/src/__tests__/settings-routes.test.ts +11 -10
  422. package/src/__tests__/skill-load-tool.test.ts +72 -0
  423. package/src/__tests__/skills.test.ts +44 -0
  424. package/src/__tests__/slack-inbound-verification.test.ts +48 -5
  425. package/src/__tests__/slack-messaging-token-resolution.test.ts +13 -2
  426. package/src/__tests__/slack-reaction-canonical-approval.test.ts +1 -1
  427. package/src/__tests__/sse-actor-principal-guardian-source.test.ts +102 -0
  428. package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
  429. package/src/__tests__/stt-hints.test.ts +44 -13
  430. package/src/__tests__/subagent-detail.test.ts +27 -0
  431. package/src/__tests__/subagent-disposal.test.ts +65 -0
  432. package/src/__tests__/subagent-tool-gate-mode.test.ts +2 -73
  433. package/src/__tests__/subagent-tools.test.ts +1 -31
  434. package/src/__tests__/system-prompt.test.ts +1 -1
  435. package/src/__tests__/system-storage-cleanup-skill.test.ts +56 -0
  436. package/src/__tests__/task-compiler.test.ts +1 -1
  437. package/src/__tests__/task-management-tools.test.ts +1 -1
  438. package/src/__tests__/task-memory-cleanup.test.ts +9 -6
  439. package/src/__tests__/task-scheduler.test.ts +1 -1
  440. package/src/__tests__/thread-backfill.test.ts +1 -1
  441. package/src/__tests__/tool-approval-handler.test.ts +1 -1
  442. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +2 -0
  443. package/src/__tests__/tool-executor.test.ts +37 -1
  444. package/src/__tests__/tool-grant-request-escalation.test.ts +1 -2
  445. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +73 -1
  446. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +34 -34
  447. package/src/__tests__/trusted-contact-multichannel.test.ts +1 -2
  448. package/src/__tests__/trusted-contact-verification.test.ts +1 -1
  449. package/src/__tests__/turn-boundary-resolution.test.ts +3 -3
  450. package/src/__tests__/turn-events-store.test.ts +1 -1
  451. package/src/__tests__/twilio-routes.test.ts +98 -3
  452. package/src/__tests__/usage-cache-backfill-migration.test.ts +20 -10
  453. package/src/__tests__/usage-routes.test.ts +1 -1
  454. package/src/__tests__/user-plugin-loader.test.ts +34 -29
  455. package/src/__tests__/verification-control-plane-policy.test.ts +2 -2
  456. package/src/__tests__/voice-invite-redemption.test.ts +134 -36
  457. package/src/__tests__/voice-scoped-grant-consumer.test.ts +1 -1
  458. package/src/__tests__/voice-session-bridge.test.ts +1 -1
  459. package/src/__tests__/workspace-git-service.test.ts +114 -1
  460. package/src/__tests__/workspace-heartbeat-service.test.ts +45 -0
  461. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +1 -1
  462. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -1
  463. package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +88 -18
  464. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +6 -6
  465. package/src/__tests__/workspace-migration-109-swap-quality-profile-to-glm-5p2.test.ts +281 -0
  466. package/src/__tests__/workspace-migration-110-flip-balanced-profile-to-together.test.ts +167 -0
  467. package/src/__tests__/workspace-migrations-runner.test.ts +55 -0
  468. package/src/__tests__/workspace-tool-loader.test.ts +3 -0
  469. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +1 -1
  470. package/src/a2a/__tests__/task-store.test.ts +1 -1
  471. package/src/acp/__tests__/session-manager-persistence.test.ts +1 -1
  472. package/src/acp/__tests__/session-manager-resume.test.ts +22 -11
  473. package/src/acp/__tests__/session-manager-startup.test.ts +1 -1
  474. package/src/acp/__tests__/session-manager.test.ts +72 -1
  475. package/src/acp/index.ts +10 -0
  476. package/src/acp/session-manager.ts +35 -0
  477. package/src/agent/loop-exclusive-tool.test.ts +150 -0
  478. package/src/agent/loop.ts +101 -27
  479. package/src/api/constants/sse-replay.ts +41 -0
  480. package/src/api/events/ui-surface-show.ts +8 -3
  481. package/src/api/index.ts +7 -6
  482. package/src/api/responses/conversation-message.ts +4 -0
  483. package/src/api/responses/llm-request-log-entry.ts +25 -0
  484. package/src/api/responses/subagent-detail.ts +17 -0
  485. package/src/api/surfaces.ts +33 -0
  486. package/src/approvals/AGENTS.md +1 -2
  487. package/src/approvals/guardian-decision-primitive.ts +13 -210
  488. package/src/approvals/guardian-request-resolvers.ts +104 -58
  489. package/src/background-wake/wake-intent-hooks.test.ts +1 -1
  490. package/src/calls/__tests__/inbound-trust-reader.test.ts +110 -0
  491. package/src/calls/__tests__/relay-setup-router.test.ts +349 -65
  492. package/src/calls/guardian-dispatch.ts +10 -8
  493. package/src/calls/inbound-trust-reader.ts +56 -0
  494. package/src/calls/media-stream-server.ts +21 -0
  495. package/src/calls/relay-server.ts +231 -72
  496. package/src/calls/relay-setup-router.ts +57 -13
  497. package/src/calls/relay-verification.ts +7 -7
  498. package/src/calls/stt-hints.ts +9 -12
  499. package/src/calls/twilio-routes.ts +13 -3
  500. package/src/cli/commands/__tests__/cache.test.ts +8 -1
  501. package/src/cli/commands/cache.ts +194 -181
  502. package/src/cli/commands/contacts.ts +6 -24
  503. package/src/cli/commands/db/__tests__/repair.test.ts +15 -6
  504. package/src/cli/commands/db/__tests__/status.test.ts +7 -3
  505. package/src/cli/commands/db/status.ts +212 -33
  506. package/src/cli/commands/mcp.ts +252 -218
  507. package/src/cli/commands/memory/__tests__/memory-v3.test.ts +6 -1
  508. package/src/cli/commands/memory/__tests__/worker.test.ts +302 -0
  509. package/src/cli/commands/memory/index.ts +4 -0
  510. package/src/cli/commands/memory/memory-retrospective.ts +129 -0
  511. package/src/cli/commands/memory/memory-v3.ts +176 -4
  512. package/src/cli/commands/memory/worker.ts +175 -0
  513. package/src/cli/commands/plugins.ts +343 -14
  514. package/src/cli/lib/__tests__/install-from-github.test.ts +40 -0
  515. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +160 -1
  516. package/src/cli/lib/__tests__/plugin-pin-history.test.ts +162 -0
  517. package/src/cli/lib/__tests__/toggle-plugin.test.ts +158 -0
  518. package/src/cli/lib/install-from-github.ts +47 -6
  519. package/src/cli/lib/list-installed-plugins.ts +179 -1
  520. package/src/cli/lib/plugin-marketplace.ts +11 -0
  521. package/src/cli/lib/plugin-pin-history.ts +257 -0
  522. package/src/cli/lib/toggle-plugin.ts +146 -0
  523. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
  524. package/src/config/__tests__/sync-gated-profiles.test.ts +2 -2
  525. package/src/config/bundled-skills/app-builder/SKILL.md +15 -33
  526. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +3 -8
  527. package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +64 -37
  528. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +1 -1
  529. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +14 -72
  530. package/src/config/bundled-skills/app-builder/references/examples/README.md +1 -2
  531. package/src/config/bundled-skills/contacts/SKILL.md +7 -12
  532. package/src/config/bundled-skills/messaging/tools/shared.ts +4 -1
  533. package/src/config/bundled-skills/system-storage-cleanup/SKILL.md +74 -0
  534. package/src/config/bundled-skills/workflows/SKILL.md +4 -3
  535. package/src/config/call-site-defaults.ts +11 -2
  536. package/src/config/feature-flag-registry.json +0 -8
  537. package/src/config/llm-resolver.ts +151 -14
  538. package/src/config/loader.ts +36 -5
  539. package/src/config/profile-dispatchability.ts +11 -0
  540. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  541. package/src/config/schemas/call-site-catalog.ts +7 -0
  542. package/src/config/schemas/llm.ts +2 -0
  543. package/src/config/schemas/memory-lifecycle.ts +17 -3
  544. package/src/config/schemas/memory-v3.ts +7 -0
  545. package/src/config/schemas/memory.ts +4 -0
  546. package/src/config/schemas/timeouts.ts +32 -0
  547. package/src/config/seed-inference-profiles.ts +147 -50
  548. package/src/config/skills.ts +27 -5
  549. package/src/config/sync-gated-profiles.ts +13 -1
  550. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
  551. package/src/contacts/contact-store.ts +21 -0
  552. package/src/contacts/contacts-write.ts +3 -0
  553. package/src/contacts/guardian-delivery-reader.ts +223 -0
  554. package/src/contacts/member-status.ts +9 -0
  555. package/src/credential-health/credential-health-service.ts +1 -5
  556. package/src/daemon/__tests__/conversation-tool-setup.test.ts +44 -0
  557. package/src/daemon/app-source-watcher.ts +31 -18
  558. package/src/daemon/assistant-attachments.ts +94 -4
  559. package/src/daemon/conversation-agent-loop-handlers.ts +3 -0
  560. package/src/daemon/conversation-agent-loop.ts +18 -36
  561. package/src/daemon/conversation-process.ts +35 -16
  562. package/src/daemon/conversation-runtime-assembly.ts +91 -66
  563. package/src/daemon/conversation-surfaces.ts +273 -18
  564. package/src/daemon/conversation-tool-setup.ts +24 -64
  565. package/src/daemon/conversation.ts +149 -53
  566. package/src/daemon/disk-pressure-guard.ts +12 -2
  567. package/src/daemon/event-loop-watchdog.test.ts +85 -0
  568. package/src/daemon/event-loop-watchdog.ts +133 -0
  569. package/src/daemon/external-plugins-bootstrap.ts +26 -80
  570. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +1 -1
  571. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +1 -1
  572. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +1 -1
  573. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +1 -1
  574. package/src/daemon/handlers/__tests__/config-a2a.test.ts +1 -1
  575. package/src/daemon/handlers/config-channels.ts +41 -27
  576. package/src/daemon/handlers/conversations.ts +84 -0
  577. package/src/daemon/handlers/shared.ts +7 -0
  578. package/src/daemon/lifecycle.ts +44 -5
  579. package/src/daemon/memory-v2-startup.test.ts +72 -0
  580. package/src/daemon/memory-v2-startup.ts +87 -19
  581. package/src/daemon/message-types/inbox.ts +0 -6
  582. package/src/daemon/message-types/messages.ts +0 -4
  583. package/src/daemon/message-types/surfaces.ts +12 -11
  584. package/src/daemon/server.ts +0 -4
  585. package/src/daemon/shutdown-handlers.ts +20 -0
  586. package/src/daemon/tool-setup-types.ts +7 -5
  587. package/src/daemon/trust-context.ts +6 -0
  588. package/src/daemon/wake-conversation-ops.ts +70 -0
  589. package/src/daemon/workspace-tools-watcher.ts +7 -3
  590. package/src/documents/document-comments-store.test.ts +1 -1
  591. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +1 -1
  592. package/src/heartbeat/__tests__/heartbeat-service.test.ts +6 -0
  593. package/src/heartbeat/heartbeat-service.ts +3 -4
  594. package/src/ipc/__tests__/attachment-ipc.test.ts +1 -1
  595. package/src/ipc/__tests__/browser-ipc.test.ts +73 -2
  596. package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
  597. package/src/ipc/__tests__/watcher-ipc.test.ts +59 -39
  598. package/src/ipc/assistant-server.ts +10 -2
  599. package/src/ipc/gateway-client.ts +2 -1
  600. package/src/ipc/routes/__tests__/invite-ipc-routes.test.ts +58 -0
  601. package/src/ipc/routes/invite-ipc-routes.ts +66 -0
  602. package/src/live-voice/__tests__/live-voice-archive.test.ts +1 -1
  603. package/src/memory/__tests__/activation-session-store.test.ts +1 -1
  604. package/src/memory/__tests__/auto-analysis-guard.test.ts +1 -1
  605. package/src/memory/__tests__/conversation-group-migration.test.ts +1 -1
  606. package/src/memory/__tests__/conversation-queries.test.ts +1 -1
  607. package/src/memory/__tests__/db-async-query.test.ts +1 -1
  608. package/src/memory/__tests__/db-logs-attach.test.ts +110 -0
  609. package/src/memory/__tests__/db-maintenance.test.ts +28 -36
  610. package/src/memory/__tests__/db-memory-attach.test.ts +113 -0
  611. package/src/memory/__tests__/find-analysis-conversation.test.ts +1 -1
  612. package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +1 -1
  613. package/src/memory/__tests__/fork-message-copy.test.ts +232 -0
  614. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +3 -0
  615. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +5 -5
  616. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +8 -6
  617. package/src/memory/__tests__/memory-retrospective-job.test.ts +30 -37
  618. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +69 -66
  619. package/src/memory/__tests__/memory-retrospective-state.test.ts +1 -1
  620. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +1 -1
  621. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +1 -1
  622. package/src/memory/__tests__/onboarding-events-store.test.ts +1 -1
  623. package/src/memory/__tests__/prompt-override.test.ts +192 -0
  624. package/src/memory/__tests__/table-relocation.test.ts +129 -0
  625. package/src/memory/conversation-crud.ts +461 -152
  626. package/src/memory/db-async-query.ts +89 -5
  627. package/src/memory/db-connection.ts +101 -18
  628. package/src/memory/db-init.ts +409 -234
  629. package/src/memory/db-maintenance.ts +43 -38
  630. package/src/memory/db-singleton.ts +45 -19
  631. package/src/memory/embedding-gemini.test.ts +3 -1
  632. package/src/memory/embedding-gemini.ts +18 -2
  633. package/src/memory/fork-message-copy.ts +170 -0
  634. package/src/memory/graph/__tests__/handle-remember-v2.test.ts +92 -0
  635. package/src/memory/graph/bootstrap.test.ts +6 -3
  636. package/src/memory/graph/retriever.test.ts +12 -12
  637. package/src/memory/graph/store.test.ts +15 -25
  638. package/src/memory/graph/store.ts +23 -14
  639. package/src/memory/graph/tool-handlers.ts +34 -5
  640. package/src/memory/graph/tools.ts +5 -2
  641. package/src/memory/indexer.ts +21 -9
  642. package/src/memory/job-handlers/cleanup.ts +10 -3
  643. package/src/memory/job-handlers/embedding.test.ts +4 -4
  644. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +4 -4
  645. package/src/memory/jobs/embed-pkb-file.test.ts +7 -7
  646. package/src/memory/jobs-store.ts +36 -24
  647. package/src/memory/llm-request-log-store.ts +51 -19
  648. package/src/memory/llm-usage-store.ts +79 -21
  649. package/src/memory/memory-retrospective-job.ts +27 -19
  650. package/src/memory/memory-retrospective-startup-cleanup.ts +10 -2
  651. package/src/memory/migrations/{100-core-tables.ts → 000-core-tables.ts} +6 -10
  652. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +13 -3
  653. package/src/memory/migrations/104-core-indexes.ts +1 -1
  654. package/src/memory/migrations/126-backfill-guardian-principal-id.ts +189 -196
  655. package/src/memory/migrations/127-guardian-principal-id-not-null.ts +98 -105
  656. package/src/memory/migrations/134-contacts-notes-column.ts +66 -69
  657. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +19 -22
  658. package/src/memory/migrations/136-drop-assistant-id-columns.ts +241 -219
  659. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +204 -209
  660. package/src/memory/migrations/141-rename-verification-table.ts +45 -48
  661. package/src/memory/migrations/142-rename-verification-session-id-column.ts +16 -23
  662. package/src/memory/migrations/143-rename-guardian-verification-values.ts +23 -30
  663. package/src/memory/migrations/144-rename-voice-to-phone.ts +133 -136
  664. package/src/memory/migrations/145-drop-accounts-table.ts +4 -7
  665. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +79 -82
  666. package/src/memory/migrations/148-drop-reminders-table.ts +3 -6
  667. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +71 -78
  668. package/src/memory/migrations/157-invite-contact-id.ts +73 -76
  669. package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +44 -58
  670. package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +36 -43
  671. package/src/memory/migrations/174-rename-thread-starters-table.ts +30 -37
  672. package/src/memory/migrations/176-drop-capability-card-state.ts +17 -22
  673. package/src/memory/migrations/177-create-trace-events-table.ts +23 -28
  674. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +36 -43
  675. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +14 -21
  676. package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +17 -24
  677. package/src/memory/migrations/192-contacts-user-file-column.ts +6 -9
  678. package/src/memory/migrations/193-add-source-type-columns.ts +33 -36
  679. package/src/memory/migrations/194-memory-recall-logs.ts +34 -39
  680. package/src/memory/migrations/196-strip-integration-prefix-from-provider-keys.ts +59 -66
  681. package/src/memory/migrations/199-guardian-request-enrichment-columns.ts +41 -48
  682. package/src/memory/migrations/204-rename-memory-graph-type-values.ts +11 -18
  683. package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +76 -83
  684. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +135 -68
  685. package/src/memory/migrations/211-memory-recall-logs-query-context.ts +6 -11
  686. package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +4 -9
  687. package/src/memory/migrations/217-conversation-host-access.ts +13 -18
  688. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +86 -93
  689. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +41 -48
  690. package/src/memory/migrations/230-acp-session-history.ts +23 -28
  691. package/src/memory/migrations/231-repair-memory-graph-event-dates.ts +58 -62
  692. package/src/memory/migrations/232-activation-state.ts +11 -16
  693. package/src/memory/migrations/233-document-conversations.ts +20 -25
  694. package/src/memory/migrations/234-memory-v2-activation-logs.ts +26 -31
  695. package/src/memory/migrations/235-slack-compaction-watermark.ts +5 -10
  696. package/src/memory/migrations/236-tool-invocations-matched-rule-id.ts +6 -11
  697. package/src/memory/migrations/237-heartbeat-runs.ts +22 -27
  698. package/src/memory/migrations/239-trace-events-created-at-index.ts +4 -9
  699. package/src/memory/migrations/242-message-bookmarks.ts +17 -22
  700. package/src/memory/migrations/245-memory-retrospective-state.ts +8 -13
  701. package/src/memory/migrations/249-normalize-slack-external-content.ts +37 -41
  702. package/src/memory/migrations/251-a2a-tasks.ts +27 -32
  703. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +12 -17
  704. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +10 -15
  705. package/src/memory/migrations/256-memory-v2-injection-events.ts +70 -74
  706. package/src/memory/migrations/259-conversation-cleaned-at.ts +4 -9
  707. package/src/memory/migrations/260-rename-cleaned-at.ts +11 -16
  708. package/src/memory/migrations/261-llm-usage-add-raw-usage.ts +3 -8
  709. package/src/memory/migrations/262-memory-v3-coactivation.ts +21 -26
  710. package/src/memory/migrations/263-memory-v3-auto-edges.ts +14 -19
  711. package/src/memory/migrations/270-schedule-description.ts +7 -12
  712. package/src/memory/migrations/272-acp-session-history-cwd.ts +8 -13
  713. package/src/memory/migrations/281-memory-retrospective-remembered-log.ts +8 -13
  714. package/src/memory/migrations/297-move-llm-request-logs-to-logs-db.ts +111 -0
  715. package/src/memory/migrations/298-move-memory-jobs-to-memory-db.ts +128 -0
  716. package/src/memory/migrations/299-canonical-guardian-deliveries-conversation-index.ts +19 -0
  717. package/src/memory/migrations/__tests__/014-backfill-inbox-thread-state.test.ts +108 -0
  718. package/src/memory/migrations/__tests__/136-drop-assistant-id-columns.test.ts +82 -0
  719. package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +224 -0
  720. package/src/memory/migrations/__tests__/297-move-llm-request-logs.test.ts +180 -0
  721. package/src/memory/migrations/__tests__/run-migrations.test.ts +333 -7
  722. package/src/memory/migrations/helpers/relocation.ts +227 -0
  723. package/src/memory/migrations/registry.ts +63 -0
  724. package/src/memory/migrations/run-migrations.ts +187 -16
  725. package/src/memory/migrations/schema-introspection.ts +14 -0
  726. package/src/memory/migrations/validate-migration-state.ts +50 -145
  727. package/src/memory/prompt-override.ts +129 -0
  728. package/src/memory/raw-query.ts +47 -2
  729. package/src/memory/skill-loaded-events-store.test.ts +1 -1
  730. package/src/memory/task-memory-cleanup.ts +62 -41
  731. package/src/memory/tool-executed-events-store.test.ts +1 -1
  732. package/src/memory/turn-trace-store.test.ts +1 -1
  733. package/src/memory/v2/__tests__/backfill-jobs.test.ts +16 -15
  734. package/src/memory/v2/__tests__/cli-command-store.test.ts +25 -0
  735. package/src/memory/v2/__tests__/harness-compare.test.ts +1 -1
  736. package/src/memory/v2/__tests__/harness-oracle.test.ts +1 -1
  737. package/src/memory/v2/__tests__/harness-replay-input.test.ts +1 -1
  738. package/src/memory/v2/__tests__/skill-store.test.ts +80 -0
  739. package/src/memory/v2/__tests__/sweep-job.test.ts +2 -2
  740. package/src/memory/v2/cli-command-store.ts +75 -38
  741. package/src/memory/v2/prompts/consolidation.ts +13 -82
  742. package/src/memory/v2/prompts/router.ts +21 -93
  743. package/src/memory/v2/skill-store.ts +68 -31
  744. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +38 -0
  745. package/src/memory/v3-eval/__tests__/eval-tally.test.ts +139 -0
  746. package/src/memory/v3-eval/eval-packets.ts +197 -12
  747. package/src/memory/v3-eval/eval-tally.ts +234 -0
  748. package/src/memory/worker-control.ts +118 -0
  749. package/src/memory/worker-process.ts +72 -0
  750. package/src/messaging/provider.ts +10 -0
  751. package/src/messaging/providers/gmail/adapter.ts +1 -0
  752. package/src/messaging/providers/gmail/client.ts +13 -0
  753. package/src/messaging/providers/index.ts +1 -1
  754. package/src/messaging/providers/slack/send.test.ts +87 -39
  755. package/src/messaging/providers/slack/send.ts +84 -105
  756. package/src/notifications/README.md +9 -5
  757. package/src/notifications/__tests__/broadcaster.test.ts +16 -8
  758. package/src/notifications/__tests__/connected-channels.test.ts +114 -0
  759. package/src/notifications/__tests__/decision-engine.test.ts +78 -9
  760. package/src/notifications/__tests__/destination-resolver.test.ts +256 -0
  761. package/src/notifications/__tests__/deterministic-checks.test.ts +43 -1
  762. package/src/notifications/adapters/slack.ts +12 -10
  763. package/src/notifications/approval-card-builder.ts +81 -20
  764. package/src/notifications/approval-card-data.ts +8 -5
  765. package/src/notifications/broadcaster.ts +8 -1
  766. package/src/notifications/canonical-delivery-recorder.ts +7 -5
  767. package/src/notifications/conversation-candidates.ts +24 -59
  768. package/src/notifications/copy-composer.ts +48 -68
  769. package/src/notifications/decision-engine.ts +15 -7
  770. package/src/notifications/destination-resolver.ts +68 -24
  771. package/src/notifications/deterministic-checks.ts +19 -16
  772. package/src/notifications/emit-signal.ts +68 -15
  773. package/src/notifications/trusted-contact-payloads.ts +70 -0
  774. package/src/oauth/byo-connection.test.ts +9 -0
  775. package/src/oauth/connection-resolver.test.ts +174 -6
  776. package/src/oauth/connection-resolver.ts +132 -5
  777. package/src/oauth/oauth-store.ts +16 -3
  778. package/src/oauth/scope-utils.ts +39 -0
  779. package/src/permissions/question-prompter.test.ts +1 -1
  780. package/src/permissions/question-prompter.ts +7 -4
  781. package/src/plugin-api/index.ts +9 -4
  782. package/src/plugin-api/model-profiles.test.ts +123 -0
  783. package/src/plugin-api/model-profiles.ts +5 -1
  784. package/src/plugin-api/vision-support.test.ts +173 -0
  785. package/src/plugin-api/vision-support.ts +113 -0
  786. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +90 -0
  787. package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +106 -0
  788. package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +60 -0
  789. package/src/plugins/defaults/advisor/consult.ts +65 -6
  790. package/src/plugins/defaults/advisor/context-pack.ts +288 -0
  791. package/src/plugins/defaults/advisor/steering.ts +14 -2
  792. package/src/plugins/defaults/advisor/tools/advisor.ts +32 -5
  793. package/src/plugins/defaults/compaction/window-manager.ts +45 -64
  794. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +13 -4
  795. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +441 -0
  796. package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +57 -0
  797. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +61 -0
  798. package/src/plugins/defaults/image-fallback/package.json +14 -0
  799. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +108 -0
  800. package/src/plugins/defaults/image-fallback/src/caption-cache.ts +49 -0
  801. package/src/plugins/defaults/image-fallback/src/image-persist.ts +56 -0
  802. package/src/plugins/defaults/image-fallback/src/vision-caption.ts +120 -0
  803. package/src/plugins/defaults/index.ts +27 -0
  804. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +14 -1
  805. package/src/plugins/defaults/memory-retrieval/injectors.ts +4 -4
  806. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +134 -5
  807. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +11 -2
  808. package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +146 -0
  809. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +246 -19
  810. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +8 -1
  811. package/src/plugins/external-plugin-loader.ts +47 -6
  812. package/src/plugins/mtime-cache.ts +772 -0
  813. package/src/plugins/pipeline.ts +7 -2
  814. package/src/plugins/registry.ts +16 -5
  815. package/src/plugins/user-loader.ts +22 -76
  816. package/src/prompts/persona-resolver.ts +29 -11
  817. package/src/prompts/system-prompt.ts +1 -1
  818. package/src/prompts/templates/system-sections.ts +4 -4
  819. package/src/providers/__tests__/count-tokens-forwarding.test.ts +98 -0
  820. package/src/providers/anthropic/client.ts +290 -185
  821. package/src/providers/call-site-routing.ts +14 -0
  822. package/src/providers/gemini/client.ts +43 -0
  823. package/src/providers/inference/adapter-factory.ts +6 -0
  824. package/src/providers/inference/connections.ts +6 -1
  825. package/src/providers/model-catalog.ts +53 -0
  826. package/src/providers/openai/responses-provider.ts +5 -0
  827. package/src/providers/openrouter/client.ts +5 -0
  828. package/src/providers/platform-proxy/constants.ts +5 -0
  829. package/src/providers/provider-send-message.ts +4 -0
  830. package/src/providers/ratelimit.ts +13 -0
  831. package/src/providers/retry.ts +14 -0
  832. package/src/providers/together/client.ts +35 -0
  833. package/src/providers/types.ts +25 -0
  834. package/src/providers/usage-tracking.ts +11 -0
  835. package/src/runtime/AGENTS.md +9 -1
  836. package/src/runtime/__tests__/agent-wake.test.ts +259 -4
  837. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +181 -0
  838. package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +64 -0
  839. package/src/runtime/__tests__/local-principal-trust.test.ts +164 -0
  840. package/src/runtime/__tests__/slack-block-formatting.test.ts +39 -10
  841. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +670 -0
  842. package/src/runtime/access-request-helper.ts +19 -39
  843. package/src/runtime/actor-trust-resolver.ts +8 -16
  844. package/src/runtime/agent-wake.ts +183 -60
  845. package/src/runtime/anchored-guardian.test.ts +156 -0
  846. package/src/runtime/anchored-guardian.ts +135 -0
  847. package/src/runtime/assistant-stream-state.ts +9 -2
  848. package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +99 -0
  849. package/src/runtime/auth/require-bound-guardian.ts +21 -11
  850. package/src/runtime/channel-reply-delivery.ts +6 -3
  851. package/src/runtime/channel-verification-service.ts +24 -0
  852. package/src/runtime/guardian-decision-types.ts +3 -22
  853. package/src/runtime/guardian-vellum-migration.ts +66 -7
  854. package/src/runtime/http-server.ts +1 -15
  855. package/src/runtime/invite-redemption-service.ts +155 -6
  856. package/src/runtime/invite-service.ts +113 -62
  857. package/src/runtime/local-actor-identity.ts +76 -11
  858. package/src/runtime/local-principal-trust.ts +52 -0
  859. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +3 -0
  860. package/src/runtime/pending-interactions.ts +11 -1
  861. package/src/runtime/routes/__tests__/acp-routes.test.ts +1 -1
  862. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -1
  863. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +277 -0
  864. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +140 -0
  865. package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +26 -7
  866. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +14 -10
  867. package/src/runtime/routes/__tests__/contact-routes-update-channel-relay.test.ts +164 -0
  868. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  869. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +1 -1
  870. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +8 -8
  871. package/src/runtime/routes/__tests__/conversation-surface-routes.test.ts +1 -1
  872. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +1 -3
  873. package/src/runtime/routes/__tests__/invite-relay-routes.test.ts +240 -0
  874. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +4 -0
  875. package/src/runtime/routes/__tests__/plugins-routes.test.ts +143 -0
  876. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +1 -1
  877. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +1 -1
  878. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +163 -0
  879. package/src/runtime/routes/acp-routes-list.test.ts +4 -0
  880. package/src/runtime/routes/acp-routes.test.ts +5 -6
  881. package/src/runtime/routes/attachment-routes.ts +21 -17
  882. package/src/runtime/routes/browser-routes.ts +19 -1
  883. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +5 -9
  884. package/src/runtime/routes/channel-verification-routes.ts +13 -2
  885. package/src/runtime/routes/contact-routes.ts +275 -164
  886. package/src/runtime/routes/conversation-query-routes.ts +15 -5
  887. package/src/runtime/routes/conversation-routes.ts +80 -66
  888. package/src/runtime/routes/conversation-starter-routes.ts +7 -8
  889. package/src/runtime/routes/events-routes.ts +2 -2
  890. package/src/runtime/routes/guardian-approval-interception.ts +13 -274
  891. package/src/runtime/routes/host-app-control-routes.ts +5 -4
  892. package/src/runtime/routes/host-bash-routes.ts +5 -4
  893. package/src/runtime/routes/host-browser-routes.ts +9 -11
  894. package/src/runtime/routes/host-cu-routes.ts +5 -4
  895. package/src/runtime/routes/host-file-routes.ts +5 -4
  896. package/src/runtime/routes/host-transfer-routes.ts +6 -6
  897. package/src/runtime/routes/http-adapter.ts +1 -1
  898. package/src/runtime/routes/inbound-message-handler.ts +21 -16
  899. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +376 -0
  900. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +86 -64
  901. package/src/runtime/routes/inbound-stages/admission-policy.ts +20 -5
  902. package/src/runtime/routes/inbound-stages/background-dispatch.ts +16 -4
  903. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +21 -8
  904. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -3
  905. package/src/runtime/routes/llm-context-normalization.ts +71 -0
  906. package/src/runtime/routes/log-export-routes.ts +2 -2
  907. package/src/runtime/routes/mcp-auth-routes.ts +38 -15
  908. package/src/runtime/routes/memory-eval-routes.ts +92 -0
  909. package/src/runtime/routes/memory-item-routes.test.ts +12 -11
  910. package/src/runtime/routes/migration-routes.ts +51 -40
  911. package/src/runtime/routes/plugins-routes.ts +164 -8
  912. package/src/runtime/routes/schedule-routes.ts +1 -0
  913. package/src/runtime/routes/subagents-routes.ts +5 -0
  914. package/src/runtime/routes/surface-action-routes.ts +39 -51
  915. package/src/runtime/routes/usage-routes.ts +3 -0
  916. package/src/runtime/routes/work-items-routes.test.ts +1 -1
  917. package/src/runtime/slack-block-formatting.ts +46 -48
  918. package/src/runtime/trust-verdict-consumer.ts +210 -0
  919. package/src/schedule/scheduler.ts +6 -9
  920. package/src/signals/user-message.ts +16 -0
  921. package/src/subagent/manager.ts +9 -0
  922. package/src/telemetry/usage-telemetry-reporter.test.ts +1 -1
  923. package/src/tools/ask-question/ask-question-tool.test.ts +89 -52
  924. package/src/tools/ask-question/ask-question-tool.ts +27 -73
  925. package/src/tools/browser/__tests__/browser-status.test.ts +20 -0
  926. package/src/tools/browser/browser-execution.ts +16 -4
  927. package/src/tools/document/document-comment-tool.test.ts +1 -1
  928. package/src/tools/executor.ts +15 -3
  929. package/src/tools/host-terminal/host-shell.ts +28 -9
  930. package/src/tools/memory/register.test.ts +32 -0
  931. package/src/tools/skills/load.ts +43 -2
  932. package/src/tools/subagent/spawn.ts +4 -10
  933. package/src/tools/terminal/shell.ts +16 -5
  934. package/src/tools/tool-defaults.ts +2 -0
  935. package/src/tools/types.ts +18 -2
  936. package/src/tools/ui-surface/definitions.ts +0 -43
  937. package/src/util/fs-watcher-error.ts +36 -0
  938. package/src/util/log-redact.ts +2 -4
  939. package/src/util/logs-db-path.ts +22 -0
  940. package/src/util/memory-db-path.ts +23 -0
  941. package/src/util/platform.ts +5 -0
  942. package/src/watcher/providers/gmail.ts +7 -2
  943. package/src/workflows/engine-integration.test.ts +1 -1
  944. package/src/workflows/engine.test.ts +1 -1
  945. package/src/workflows/engine.ts +22 -0
  946. package/src/workflows/fanout-load.test.ts +1 -1
  947. package/src/workflows/journal-store.test.ts +1 -1
  948. package/src/workflows/leaf-runner.test.ts +40 -1
  949. package/src/workflows/leaf-runner.ts +26 -1
  950. package/src/workspace/git-service.ts +144 -29
  951. package/src/workspace/migrations/109-swap-quality-profile-to-glm-5p2.ts +121 -0
  952. package/src/workspace/migrations/110-flip-balanced-profile-to-together.ts +82 -0
  953. package/src/workspace/migrations/registry.ts +4 -0
  954. package/src/workspace/migrations/runner.ts +32 -2
  955. package/src/__tests__/access-request-decision.test.ts +0 -375
  956. package/src/__tests__/guardian-grant-minting.test.ts +0 -607
  957. package/src/__tests__/plugin-source-watcher.test.ts +0 -302
  958. package/src/api/events/turn-profile-auto-routed.ts +0 -28
  959. package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +0 -107
  960. package/src/daemon/plugin-source-watcher.ts +0 -278
  961. package/src/daemon/switch-inference-profile-tool.ts +0 -62
  962. package/src/memory/guardian-approvals.ts +0 -361
  963. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +0 -66
  964. package/src/memory/migrations/038-actor-token-records.ts +0 -45
  965. package/src/memory/migrations/039-actor-refresh-token-records.ts +0 -57
  966. package/src/memory/migrations/103-complex-migrations.ts +0 -23
  967. package/src/memory/migrations/113-late-migrations.ts +0 -30
  968. package/src/memory/migrations/index.ts +0 -301
  969. package/src/runtime/routes/access-request-decision.ts +0 -297
  970. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +0 -963
  971. package/src/runtime/routes/channel-guardian-routes.ts +0 -19
  972. package/src/runtime/routes/guardian-expiry-sweep.ts +0 -132
@@ -26,10 +26,6 @@ import {
26
26
  test,
27
27
  } from "bun:test";
28
28
 
29
- import { ADMISSION_FLOOR } from "@vellumai/gateway-client";
30
-
31
- import { TRUST_CLASS_RANK } from "../runtime/actor-trust-resolver.js";
32
-
33
29
  // ── Platform + logger mocks (must come before any source imports) ────
34
30
 
35
31
  // eslint-disable-next-line @typescript-eslint/no-require-imports
@@ -42,6 +38,28 @@ mock.module("../util/logger.js", () => ({
42
38
  }),
43
39
  }));
44
40
 
41
+ // The voice redemption path now claims the gateway-canonical row over IPC
42
+ // before mutating. Stub it so tests don't attempt a real socket connect; the
43
+ // claim returns consumed (updated:true) so redemption proceeds.
44
+ //
45
+ // `inviteClaimCalls` counts gateway claims so concurrency tests can assert that
46
+ // a repeated code does NOT launch a second claim. `inviteClaimGate`, when set,
47
+ // holds the claim open mid-flight so a test can drive the race window where a
48
+ // second code arrives while the first redemption is still awaiting the gateway.
49
+ let inviteClaimCalls = 0;
50
+ let inviteClaimGate: Promise<void> | null = null;
51
+ mock.module("../ipc/gateway-client.js", () => ({
52
+ ipcCall: async () => undefined,
53
+ ipcCallPersistent: async (method: string) => {
54
+ if (method === "record_invite_redemption") {
55
+ inviteClaimCalls += 1;
56
+ if (inviteClaimGate) await inviteClaimGate;
57
+ return { ok: true, updated: true, mirrored: true };
58
+ }
59
+ return undefined;
60
+ },
61
+ }));
62
+
45
63
  // ── Identity helpers mock ─────────────────────────────────────────────
46
64
 
47
65
  let mockAssistantName: string | null = "Vellum";
@@ -69,6 +87,19 @@ mock.module("../prompts/user-reference.js", () => ({
69
87
  },
70
88
  }));
71
89
 
90
+ // ── Guardian delivery reader mock ───────────────────────────────────
91
+ //
92
+ // resolveGuardianLabel primes its displayName from the gateway binding via
93
+ // getGuardianDelivery at setup. Tests drive the binding through this list.
94
+
95
+ // Tests set this to drive the guardian binding directly. When null (the
96
+ // default), the guardian-delivery-reader mock below derives the binding from
97
+ // the DB-seeded createGuardianBinding setup. Single mock registration lives
98
+ // below since `mock.module` is process-global and last-write-wins in Bun.
99
+ let mockGuardianDeliveryList:
100
+ | Array<{ channelType: string; status: string; displayName: string | null }>
101
+ | null = null;
102
+
72
103
  // ── Config mock ─────────────────────────────────────────────────────
73
104
 
74
105
  const mockConfig = {
@@ -145,37 +176,89 @@ mock.module("../calls/channel-admission-reader.js", () => ({
145
176
  },
146
177
  }));
147
178
 
148
- // Real floor semantics, mirroring relay-setup-router.test.ts. The
149
- // enforceAdmissionPolicy mock omits the exempt-channel short-circuit so the
150
- // deny path can be exercised end-to-end regardless of channel.
151
- // eslint-disable-next-line @typescript-eslint/no-require-imports
152
- const realAdmissionPolicyModule = require("../runtime/routes/inbound-stages/admission-policy.js");
153
- mock.module("../runtime/routes/inbound-stages/admission-policy.js", () => ({
154
- ...realAdmissionPolicyModule,
155
- enforceAdmissionPolicy: (input: {
156
- trustClass: string;
157
- memberStatus: string | undefined;
158
- policy: import("@vellumai/gateway-client").AdmissionPolicy;
179
+ // ── Inbound trust verdict reader mock ───────────────────────────────
180
+ //
181
+ // Mid-call re-resolution (post verification/activation) prefers the gateway
182
+ // verdict via getInboundTrustVerdict, falling back to local resolution on a
183
+ // missing/failed/unusable verdict. Tests drive the verdict through
184
+ // `mockMidCallVerdict`; null (the default) exercises the local fallback. As
185
+ // with the admission reader, delegate to the real module for sibling files
186
+ // that load later in the same worker.
187
+ let mockMidCallVerdict:
188
+ | import("@vellumai/gateway-client").TrustVerdict
189
+ | null = null;
190
+ // When set, the mid-call verdict reader blocks on this gate before returning,
191
+ // simulating a slow gateway round-trip so a test can drive a prompt into the
192
+ // re-resolution await window.
193
+ let mockMidCallVerdictGate: Promise<void> | null = null;
194
+ const realInboundTrustReaderModule = {
195
+ ...(await import("../calls/inbound-trust-reader.js")),
196
+ };
197
+ let inboundTrustMockActive = false;
198
+ mock.module("../calls/inbound-trust-reader.js", () => ({
199
+ ...realInboundTrustReaderModule,
200
+ getInboundTrustVerdict: async (input: {
201
+ channelType: import("../channels/types.js").ChannelId;
202
+ actorExternalId?: string;
159
203
  }) => {
160
- if (input.memberStatus === "blocked" || input.memberStatus === "revoked") {
161
- return {
162
- admitted: false,
163
- reason:
164
- input.memberStatus === "blocked" ? "member_blocked" : "member_revoked",
165
- shouldChallenge: false,
166
- effectivePolicy: input.policy,
167
- };
204
+ if (!inboundTrustMockActive) {
205
+ return realInboundTrustReaderModule.getInboundTrustVerdict(input);
168
206
  }
169
- const rank =
170
- (TRUST_CLASS_RANK as Record<string, number>)[input.trustClass] ?? 0;
171
- const floor = ADMISSION_FLOOR[input.policy];
172
- if (rank >= floor) return { admitted: true };
173
- return {
174
- admitted: false,
175
- reason: `admission_policy_${input.policy}`,
176
- shouldChallenge: false,
177
- effectivePolicy: input.policy,
178
- };
207
+ if (mockMidCallVerdictGate) await mockMidCallVerdictGate;
208
+ return mockMidCallVerdict;
209
+ },
210
+ }));
211
+
212
+ // ── Guardian delivery reader ────────────────────────────────────────
213
+ //
214
+ // Guardian identity now resolves via the gateway delivery reader. Derive the
215
+ // list from the DB-seeded guardian bindings so the existing createGuardianBinding
216
+ // setup keeps driving guardian resolution without per-test changes.
217
+ const realContactStoreModule = {
218
+ ...(await import("../contacts/contact-store.js")),
219
+ };
220
+ mock.module("../contacts/guardian-delivery-reader.js", () => ({
221
+ getGuardianDelivery: async () => {
222
+ // Tests that set mockGuardianDeliveryList drive the binding directly;
223
+ // otherwise derive from the DB-seeded createGuardianBinding bindings.
224
+ if (mockGuardianDeliveryList) return mockGuardianDeliveryList;
225
+ const guardians = realContactStoreModule.listGuardianChannels();
226
+ if (!guardians) return [];
227
+ return guardians.channels.map((ch) => ({
228
+ channelType: ch.type,
229
+ contactId: guardians.contact.id,
230
+ principalId: guardians.contact.principalId ?? null,
231
+ displayName: guardians.contact.displayName ?? null,
232
+ address: ch.address,
233
+ externalChatId: ch.externalChatId ?? null,
234
+ status: ch.status,
235
+ verifiedAt: ch.verifiedAt ?? null,
236
+ }));
237
+ },
238
+ guardianForChannel: (
239
+ list: Array<{ channelType: string; status: string }>,
240
+ channelType: string,
241
+ ) => list.find((g) => g.channelType === channelType && g.status === "active"),
242
+ anyGuardian: (list: unknown[]) => list[0],
243
+ }));
244
+
245
+ // ── Trust verdict consumer spy ──────────────────────────────────────
246
+ //
247
+ // Tracks whether the verdict mapper produced the final mid-call context, so a
248
+ // test can assert the local resolver was used instead (verdict not consumed).
249
+ let trustVerdictMapperUsed = false;
250
+ const realTrustVerdictConsumerModule = {
251
+ ...(await import("../runtime/trust-verdict-consumer.js")),
252
+ };
253
+ mock.module("../runtime/trust-verdict-consumer.js", () => ({
254
+ ...realTrustVerdictConsumerModule,
255
+ trustContextFromVerdict: (
256
+ ...args: Parameters<
257
+ typeof realTrustVerdictConsumerModule.trustContextFromVerdict
258
+ >
259
+ ) => {
260
+ trustVerdictMapperUsed = true;
261
+ return realTrustVerdictConsumerModule.trustContextFromVerdict(...args);
179
262
  },
180
263
  }));
181
264
 
@@ -317,17 +400,19 @@ import { generateVoiceCode, hashVoiceCode } from "../util/voice-code.js";
317
400
  import { resetDbForTesting } from "./db-test-helpers.js";
318
401
  import { createGuardianBinding } from "./helpers/create-guardian-binding.js";
319
402
 
320
- initializeDb();
403
+ await initializeDb();
321
404
 
322
405
  // Activate the channel-admission-reader stub only while this file's tests run,
323
406
  // so the registered (process-global) mock delegates to the real module for
324
407
  // sibling files that load later in the same worker.
325
408
  beforeAll(() => {
326
409
  admissionMockActive = true;
410
+ inboundTrustMockActive = true;
327
411
  });
328
412
 
329
413
  afterAll(() => {
330
414
  admissionMockActive = false;
415
+ inboundTrustMockActive = false;
331
416
  resetDbForTesting();
332
417
  });
333
418
 
@@ -489,10 +574,16 @@ describe("relay-server", () => {
489
574
  verifiedVia: "bootstrap",
490
575
  });
491
576
  activeRelayConnections.clear();
577
+ inviteClaimCalls = 0;
578
+ inviteClaimGate = null;
492
579
  mockUserReference = "my human";
493
580
  mockAssistantName = "Vellum";
581
+ mockGuardianDeliveryList = null;
494
582
  mockAdmissionPolicy = null;
495
583
  mockAdmissionGate = null;
584
+ mockMidCallVerdict = null;
585
+ mockMidCallVerdictGate = null;
586
+ trustVerdictMapperUsed = false;
496
587
  mockSendMessage.mockImplementation(createMockProviderResponse(["Hello"]));
497
588
  mockConfig.calls.verification.enabled = false;
498
589
  mockConfig.calls.verification.maxAttempts = 3;
@@ -2311,8 +2402,9 @@ describe("relay-server", () => {
2311
2402
 
2312
2403
  // ── Inbound voice invite redemption ──────────────────────────────────
2313
2404
 
2314
- test("inbound voice invite redemption: personalized welcome prompt with friend/guardian names", async () => {
2405
+ test("inbound voice invite redemption: personalized welcome prompt uses contact displayName", async () => {
2315
2406
  ensureConversation("conv-invite-welcome");
2407
+ mockUserReference = "Bob";
2316
2408
  const session = createCallSession({
2317
2409
  conversationId: "conv-invite-welcome",
2318
2410
  provider: "twilio",
@@ -2320,18 +2412,16 @@ describe("relay-server", () => {
2320
2412
  toNumber: "+15551111111",
2321
2413
  });
2322
2414
 
2323
- // Create a voice invite with friend/guardian names
2415
+ // The invitee's name is read from the bound contact's displayName.
2324
2416
  const code = generateVoiceCode(6);
2325
2417
  const codeHash = hashVoiceCode(code);
2326
2418
  createInvite({
2327
2419
  sourceChannel: "phone",
2328
- contactId: createTargetContact(),
2420
+ contactId: createTargetContact("Alice"),
2329
2421
  maxUses: 1,
2330
2422
  expectedExternalUserId: "+15558887777",
2331
2423
  voiceCodeHash: codeHash,
2332
2424
  voiceCodeDigits: 6,
2333
- friendName: "Alice",
2334
- guardianName: "Bob",
2335
2425
  });
2336
2426
 
2337
2427
  mockSendMessage.mockImplementation(
@@ -2352,7 +2442,8 @@ describe("relay-server", () => {
2352
2442
  // Should be in verification-pending state for invite redemption
2353
2443
  expect(relay.getConnectionState()).toBe("verification_pending");
2354
2444
 
2355
- // Check that the welcome prompt includes friend/guardian names
2445
+ // Welcome prompt should use the contact's first-name and the resolved
2446
+ // guardian label (not stale name flags from the invite row).
2356
2447
  const textMessages = ws.sentMessages
2357
2448
  .map((raw) => JSON.parse(raw) as { type: string; token?: string })
2358
2449
  .filter((m) => m.type === "text");
@@ -2387,6 +2478,7 @@ describe("relay-server", () => {
2387
2478
 
2388
2479
  test("inbound voice invite redemption: invalid code gets exact failure copy with guardian name and call ends", async () => {
2389
2480
  ensureConversation("conv-invite-fail");
2481
+ mockUserReference = "Dave";
2390
2482
  const session = createCallSession({
2391
2483
  conversationId: "conv-invite-fail",
2392
2484
  provider: "twilio",
@@ -2394,18 +2486,16 @@ describe("relay-server", () => {
2394
2486
  toNumber: "+15551111111",
2395
2487
  });
2396
2488
 
2397
- // Create a voice invite with friend/guardian names
2489
+ // Bound contact's displayName is used; guardian label is resolved at runtime.
2398
2490
  const code = generateVoiceCode(6);
2399
2491
  const codeHash = hashVoiceCode(code);
2400
2492
  createInvite({
2401
2493
  sourceChannel: "phone",
2402
- contactId: createTargetContact(),
2494
+ contactId: createTargetContact("Carol"),
2403
2495
  maxUses: 1,
2404
2496
  expectedExternalUserId: "+15558886666",
2405
2497
  voiceCodeHash: codeHash,
2406
2498
  voiceCodeDigits: 6,
2407
- friendName: "Carol",
2408
- guardianName: "Dave",
2409
2499
  });
2410
2500
 
2411
2501
  const { ws, relay } = createMockWs(session.id);
@@ -2426,7 +2516,11 @@ describe("relay-server", () => {
2426
2516
  await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
2427
2517
  }
2428
2518
 
2429
- // Call should be marked as failed immediately
2519
+ // Redemption is dispatched async (it now consults the gateway lifecycle
2520
+ // pre-check over IPC), so flush the microtask/timer queue before asserting.
2521
+ await new Promise((resolve) => setTimeout(resolve, 10));
2522
+
2523
+ // Call should be marked as failed
2430
2524
  const updated = getCallSession(session.id);
2431
2525
  expect(updated).not.toBeNull();
2432
2526
  expect(updated!.status).toBe("failed");
@@ -2466,6 +2560,184 @@ describe("relay-server", () => {
2466
2560
  relay.destroy();
2467
2561
  });
2468
2562
 
2563
+ test("inbound voice invite redemption: repeated code during in-flight claim is deduped (no second redemption, no spurious failure)", async () => {
2564
+ ensureConversation("conv-invite-dedup");
2565
+ const session = createCallSession({
2566
+ conversationId: "conv-invite-dedup",
2567
+ provider: "twilio",
2568
+ fromNumber: "+15558885555",
2569
+ toNumber: "+15551111111",
2570
+ });
2571
+
2572
+ const code = generateVoiceCode(6);
2573
+ const codeHash = hashVoiceCode(code);
2574
+ createInvite({
2575
+ sourceChannel: "phone",
2576
+ contactId: createTargetContact("Eve"),
2577
+ maxUses: 1,
2578
+ expectedExternalUserId: "+15558885555",
2579
+ voiceCodeHash: codeHash,
2580
+ voiceCodeDigits: 6,
2581
+ });
2582
+
2583
+ mockSendMessage.mockImplementation(
2584
+ createMockProviderResponse(["Hello, how can I help?"]),
2585
+ );
2586
+
2587
+ const { ws, relay } = createMockWs(session.id);
2588
+
2589
+ await relay.handleMessage(
2590
+ JSON.stringify({
2591
+ type: "setup",
2592
+ callSid: "CA_invite_dedup",
2593
+ from: "+15558885555",
2594
+ to: "+15551111111",
2595
+ }),
2596
+ );
2597
+
2598
+ expect(relay.getConnectionState()).toBe("verification_pending");
2599
+
2600
+ // Hold the gateway claim open so the first redemption stays in flight while
2601
+ // the caller re-speaks the SAME code (the race this guard prevents).
2602
+ let releaseClaim: () => void = () => {};
2603
+ inviteClaimGate = new Promise<void>((resolve) => {
2604
+ releaseClaim = resolve;
2605
+ });
2606
+
2607
+ // First attempt: enter the full code via DTMF — dispatched fire-and-forget.
2608
+ for (const digit of code) {
2609
+ await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
2610
+ }
2611
+ // Let the async handler reach the awaited gateway claim.
2612
+ await new Promise((resolve) => setTimeout(resolve, 0));
2613
+ expect(inviteClaimCalls).toBe(1);
2614
+
2615
+ // Second attempt with the SAME code arrives while the first is in flight.
2616
+ // It must be ignored — no second claim, no failure branch.
2617
+ for (const digit of code) {
2618
+ await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
2619
+ }
2620
+ await new Promise((resolve) => setTimeout(resolve, 0));
2621
+ expect(inviteClaimCalls).toBe(1);
2622
+
2623
+ // Now let the first redemption resolve.
2624
+ releaseClaim();
2625
+ await new Promise((resolve) => setTimeout(resolve, 10));
2626
+
2627
+ // Exactly one gateway claim ran across both attempts.
2628
+ expect(inviteClaimCalls).toBe(1);
2629
+
2630
+ // Activation succeeded — call continued, never marked failed.
2631
+ expect(relay.getConnectionState()).toBe("connected");
2632
+ const updated = getCallSession(session.id);
2633
+ expect(updated).not.toBeNull();
2634
+ expect(updated!.status).not.toBe("failed");
2635
+
2636
+ const events = getCallEvents(session.id);
2637
+ expect(
2638
+ events.some((e) => e.eventType === "invite_redemption_succeeded"),
2639
+ ).toBe(true);
2640
+ expect(events.some((e) => e.eventType === "invite_redemption_failed")).toBe(
2641
+ false,
2642
+ );
2643
+
2644
+ // No failure copy was sent for the deduped second attempt.
2645
+ const failureCopy = ws.sentMessages
2646
+ .map((raw) => JSON.parse(raw) as { type: string; token?: string })
2647
+ .filter(
2648
+ (m) =>
2649
+ m.type === "text" &&
2650
+ (m.token ?? "").includes("incorrect or has since expired"),
2651
+ );
2652
+ expect(failureCopy.length).toBe(0);
2653
+
2654
+ relay.destroy();
2655
+ });
2656
+
2657
+ test("inbound voice invite redemption: a fresh redemption after a prior attempt fully resolves still proceeds (guard cleared, no deadlock)", async () => {
2658
+ // Prior attempt: a fully-resolved redemption on its own call.
2659
+ ensureConversation("conv-invite-prior");
2660
+ const priorSession = createCallSession({
2661
+ conversationId: "conv-invite-prior",
2662
+ provider: "twilio",
2663
+ fromNumber: "+15558883333",
2664
+ toNumber: "+15551111111",
2665
+ });
2666
+ const priorCode = generateVoiceCode(6);
2667
+ createInvite({
2668
+ sourceChannel: "phone",
2669
+ contactId: createTargetContact("Ivan"),
2670
+ maxUses: 1,
2671
+ expectedExternalUserId: "+15558883333",
2672
+ voiceCodeHash: hashVoiceCode(priorCode),
2673
+ voiceCodeDigits: 6,
2674
+ });
2675
+
2676
+ mockSendMessage.mockImplementation(
2677
+ createMockProviderResponse(["Hello, how can I help?"]),
2678
+ );
2679
+
2680
+ const prior = createMockWs(priorSession.id);
2681
+ await prior.relay.handleMessage(
2682
+ JSON.stringify({
2683
+ type: "setup",
2684
+ callSid: "CA_invite_prior",
2685
+ from: "+15558883333",
2686
+ to: "+15551111111",
2687
+ }),
2688
+ );
2689
+ for (const digit of priorCode) {
2690
+ await prior.relay.handleMessage(
2691
+ JSON.stringify({ type: "dtmf", digit }),
2692
+ );
2693
+ }
2694
+ await new Promise((resolve) => setTimeout(resolve, 10));
2695
+ expect(prior.relay.getConnectionState()).toBe("connected");
2696
+ expect(inviteClaimCalls).toBe(1);
2697
+ prior.relay.destroy();
2698
+
2699
+ // Fresh attempt: a brand-new redemption proceeds (the in-flight guard from
2700
+ // the resolved attempt does not block it — it was cleared in finally).
2701
+ ensureConversation("conv-invite-fresh");
2702
+ const freshSession = createCallSession({
2703
+ conversationId: "conv-invite-fresh",
2704
+ provider: "twilio",
2705
+ fromNumber: "+15558882222",
2706
+ toNumber: "+15551111111",
2707
+ });
2708
+ const freshCode = generateVoiceCode(6);
2709
+ createInvite({
2710
+ sourceChannel: "phone",
2711
+ contactId: createTargetContact("Kim"),
2712
+ maxUses: 1,
2713
+ expectedExternalUserId: "+15558882222",
2714
+ voiceCodeHash: hashVoiceCode(freshCode),
2715
+ voiceCodeDigits: 6,
2716
+ });
2717
+
2718
+ const fresh = createMockWs(freshSession.id);
2719
+ await fresh.relay.handleMessage(
2720
+ JSON.stringify({
2721
+ type: "setup",
2722
+ callSid: "CA_invite_fresh",
2723
+ from: "+15558882222",
2724
+ to: "+15551111111",
2725
+ }),
2726
+ );
2727
+ for (const digit of freshCode) {
2728
+ await fresh.relay.handleMessage(
2729
+ JSON.stringify({ type: "dtmf", digit }),
2730
+ );
2731
+ }
2732
+ await new Promise((resolve) => setTimeout(resolve, 10));
2733
+
2734
+ // A second claim ran for the fresh attempt — guard did not deadlock it.
2735
+ expect(inviteClaimCalls).toBe(2);
2736
+ expect(fresh.relay.getConnectionState()).toBe("connected");
2737
+
2738
+ fresh.relay.destroy();
2739
+ });
2740
+
2469
2741
  test("inbound voice: unknown caller with no active invite enters name capture flow", async () => {
2470
2742
  ensureConversation("conv-invite-no-invite");
2471
2743
  const session = createCallSession({
@@ -4519,7 +4791,7 @@ describe("relay-server", () => {
4519
4791
  relay.destroy();
4520
4792
  });
4521
4793
 
4522
- test("invite redemption success: continues call with handoff copy instead of ending", async () => {
4794
+ test("invite redemption success: greeting uses bound contact's displayName first-token, not friendName", async () => {
4523
4795
  ensureConversation("conv-invite-continue");
4524
4796
  const session = createCallSession({
4525
4797
  conversationId: "conv-invite-continue",
@@ -4530,15 +4802,19 @@ describe("relay-server", () => {
4530
4802
 
4531
4803
  const code = generateVoiceCode(6);
4532
4804
  const codeHash = hashVoiceCode(code);
4805
+ // Bound contact's displayName is the source of truth — its first token
4806
+ // (here "Carolina") is what the post-redemption greeting should use.
4807
+ // The legacy friendName column carries an out-of-date free-text label
4808
+ // ("Stale Name") to prove the greeting reads from the contact, not the
4809
+ // legacy column.
4533
4810
  createInvite({
4534
4811
  sourceChannel: "phone",
4535
- contactId: createTargetContact(),
4812
+ contactId: createTargetContact("Carolina Flaherty"),
4536
4813
  maxUses: 1,
4537
4814
  expectedExternalUserId: "+15557776666",
4538
4815
  voiceCodeHash: codeHash,
4539
4816
  voiceCodeDigits: 6,
4540
- friendName: "Eve",
4541
- guardianName: "Frank",
4817
+ friendName: "Stale Name",
4542
4818
  });
4543
4819
 
4544
4820
  mockSendMessage.mockImplementation(
@@ -4569,15 +4845,18 @@ describe("relay-server", () => {
4569
4845
  // Call should remain connected
4570
4846
  expect(relay.getConnectionState()).toBe("connected");
4571
4847
 
4572
- // Handoff copy should have been sent
4848
+ // Handoff copy should use the contact's first-name, not the stale friendName
4573
4849
  const textMessages = ws.sentMessages
4574
4850
  .map((raw) => JSON.parse(raw) as { type: string; token?: string })
4575
4851
  .filter((m) => m.type === "text");
4576
4852
  expect(
4577
4853
  textMessages.some((m) =>
4578
- (m.token ?? "").includes("verified that you are Eve"),
4854
+ (m.token ?? "").includes("verified that you are Carolina"),
4579
4855
  ),
4580
4856
  ).toBe(true);
4857
+ expect(
4858
+ textMessages.every((m) => !(m.token ?? "").includes("Stale Name")),
4859
+ ).toBe(true);
4581
4860
 
4582
4861
  // No end message — call stays alive
4583
4862
  const endMessages = ws.sentMessages
@@ -4602,6 +4881,7 @@ describe("relay-server", () => {
4602
4881
  test("outbound invite prompt uses assistant introduction", async () => {
4603
4882
  ensureConversation("conv-outbound-invite-origin");
4604
4883
  ensureConversation("conv-outbound-invite");
4884
+ mockUserReference = "Hank";
4605
4885
  const session = createCallSession({
4606
4886
  conversationId: "conv-outbound-invite",
4607
4887
  provider: "twilio",
@@ -4644,20 +4924,141 @@ describe("relay-server", () => {
4644
4924
  relay.destroy();
4645
4925
  });
4646
4926
 
4927
+ test("invite redemption success: empty contact displayName triggers neutral 'Hi there' greeting", async () => {
4928
+ ensureConversation("conv-invite-blank-name");
4929
+ mockUserReference = "my human";
4930
+ mockAssistantName = "";
4931
+ const session = createCallSession({
4932
+ conversationId: "conv-invite-blank-name",
4933
+ provider: "twilio",
4934
+ fromNumber: "+15557775555",
4935
+ toNumber: "+15551111111",
4936
+ });
4937
+
4938
+ const code = generateVoiceCode(6);
4939
+ const codeHash = hashVoiceCode(code);
4940
+ // displayName is whitespace-only — greeting falls back to "Hi there"
4941
+ // rather than substituting the channel address.
4942
+ createInvite({
4943
+ sourceChannel: "phone",
4944
+ contactId: createTargetContact(" "),
4945
+ maxUses: 1,
4946
+ expectedExternalUserId: "+15557775555",
4947
+ voiceCodeHash: codeHash,
4948
+ voiceCodeDigits: 6,
4949
+ });
4950
+
4951
+ mockSendMessage.mockImplementation(
4952
+ createMockProviderResponse(["I'd be happy to help."]),
4953
+ );
4954
+
4955
+ const { ws, relay } = createMockWs(session.id);
4956
+
4957
+ await relay.handleMessage(
4958
+ JSON.stringify({
4959
+ type: "setup",
4960
+ callSid: "CA_invite_blank_name",
4961
+ from: "+15557775555",
4962
+ to: "+15551111111",
4963
+ }),
4964
+ );
4965
+
4966
+ for (const digit of code) {
4967
+ await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
4968
+ }
4969
+ await new Promise((resolve) => setTimeout(resolve, 10));
4970
+
4971
+ expect(relay.getConnectionState()).toBe("connected");
4972
+
4973
+ const textMessages = ws.sentMessages
4974
+ .map((raw) => JSON.parse(raw) as { type: string; token?: string })
4975
+ .filter((m) => m.type === "text");
4976
+ expect(
4977
+ textMessages.some((m) => (m.token ?? "").startsWith("Hi there!")),
4978
+ ).toBe(true);
4979
+ expect(
4980
+ textMessages.every(
4981
+ (m) => !(m.token ?? "").includes("+15557775555"),
4982
+ ),
4983
+ ).toBe(true);
4984
+
4985
+ relay.destroy();
4986
+ });
4987
+
4988
+ test("invite redemption success: empty contact displayName with stale friendName column still greets 'Hi there'", async () => {
4989
+ // Guards the Codex P2 on #35581 (discussion_r3453033493): when a bound
4990
+ // contact's displayName is blank and the invite row carries a stale
4991
+ // free-text friend_name, the greeting must NOT fall back to that stale
4992
+ // label. contact_id is NOT NULL, so every invite is bound — empty
4993
+ // displayName falls through to the neutral "Hi there" copy.
4994
+ ensureConversation("conv-invite-stale-friend");
4995
+ mockUserReference = "my human";
4996
+ mockAssistantName = "";
4997
+ const session = createCallSession({
4998
+ conversationId: "conv-invite-stale-friend",
4999
+ provider: "twilio",
5000
+ fromNumber: "+15557774444",
5001
+ toNumber: "+15551111111",
5002
+ });
5003
+
5004
+ const code = generateVoiceCode(6);
5005
+ const codeHash = hashVoiceCode(code);
5006
+ createInvite({
5007
+ sourceChannel: "phone",
5008
+ contactId: createTargetContact(" "),
5009
+ maxUses: 1,
5010
+ expectedExternalUserId: "+15557774444",
5011
+ voiceCodeHash: codeHash,
5012
+ voiceCodeDigits: 6,
5013
+ friendName: "Stale Legacy Name",
5014
+ });
5015
+
5016
+ mockSendMessage.mockImplementation(
5017
+ createMockProviderResponse(["I'd be happy to help."]),
5018
+ );
5019
+
5020
+ const { ws, relay } = createMockWs(session.id);
5021
+
5022
+ await relay.handleMessage(
5023
+ JSON.stringify({
5024
+ type: "setup",
5025
+ callSid: "CA_invite_stale_friend",
5026
+ from: "+15557774444",
5027
+ to: "+15551111111",
5028
+ }),
5029
+ );
5030
+
5031
+ for (const digit of code) {
5032
+ await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
5033
+ }
5034
+ await new Promise((resolve) => setTimeout(resolve, 10));
5035
+
5036
+ expect(relay.getConnectionState()).toBe("connected");
5037
+
5038
+ const textMessages = ws.sentMessages
5039
+ .map((raw) => JSON.parse(raw) as { type: string; token?: string })
5040
+ .filter((m) => m.type === "text");
5041
+ expect(
5042
+ textMessages.some((m) => (m.token ?? "").startsWith("Hi there!")),
5043
+ ).toBe(true);
5044
+ expect(
5045
+ textMessages.every(
5046
+ (m) => !(m.token ?? "").includes("Stale Legacy Name"),
5047
+ ),
5048
+ ).toBe(true);
5049
+
5050
+ relay.destroy();
5051
+ });
5052
+
4647
5053
  // ── resolveGuardianLabel resolution priority ─────────────────────────
4648
5054
 
4649
5055
  test("guardian label: guardian persona name takes precedence over Contact.displayName", async () => {
4650
5056
  mockUserReference = "Alice";
4651
5057
 
4652
- // Create a guardian binding with a different displayName
4653
- createGuardianBinding({
4654
- channel: "phone",
4655
- guardianExternalUserId: "+15559990001",
4656
- guardianDeliveryChatId: "+15559990001",
4657
- guardianPrincipalId: "+15559990001",
4658
- verifiedVia: "test",
4659
- metadataJson: JSON.stringify({ displayName: "Bob" }),
4660
- });
5058
+ // Gateway binding carries a different displayName
5059
+ mockGuardianDeliveryList = [
5060
+ { channelType: "phone", status: "active", displayName: "Bob" },
5061
+ ];
4661
5062
 
4662
5063
  ensureConversation("conv-label-user-md");
4663
5064
  const session = createCallSession({
@@ -4694,15 +5095,10 @@ describe("relay-server", () => {
4694
5095
  test("guardian label: Contact.displayName used when guardian persona name is empty", async () => {
4695
5096
  mockUserReference = "my human";
4696
5097
 
4697
- // Create a guardian binding with a displayName
4698
- createGuardianBinding({
4699
- channel: "phone",
4700
- guardianExternalUserId: "+15559990002",
4701
- guardianDeliveryChatId: "+15559990002",
4702
- guardianPrincipalId: "+15559990002",
4703
- verifiedVia: "test",
4704
- metadataJson: JSON.stringify({ displayName: "Charlie" }),
4705
- });
5098
+ // Gateway binding carries the guardian displayName
5099
+ mockGuardianDeliveryList = [
5100
+ { channelType: "phone", status: "active", displayName: "Charlie" },
5101
+ ];
4706
5102
 
4707
5103
  ensureConversation("conv-label-contact");
4708
5104
  const session = createCallSession({
@@ -4738,10 +5134,8 @@ describe("relay-server", () => {
4738
5134
  test("guardian label: DEFAULT_USER_REFERENCE used when both guardian persona name and Contact.displayName are empty", async () => {
4739
5135
  mockUserReference = "my human";
4740
5136
 
4741
- // Clear guardian binding so resolveGuardianLabel falls back to DEFAULT_USER_REFERENCE
4742
- const db = getDb();
4743
- db.run("DELETE FROM contact_channels");
4744
- db.run("DELETE FROM contacts");
5137
+ // Empty binding list so resolveGuardianLabel falls back to DEFAULT_USER_REFERENCE
5138
+ mockGuardianDeliveryList = [];
4745
5139
 
4746
5140
  ensureConversation("conv-label-default");
4747
5141
  const session = createCallSession({
@@ -5139,4 +5533,563 @@ describe("relay-server", () => {
5139
5533
  relay.destroy();
5140
5534
  });
5141
5535
  });
5536
+
5537
+ // ── Mid-call trust re-resolution from the gateway verdict ───────────
5538
+ //
5539
+ // After a verification/activation success the relay re-resolves caller trust.
5540
+ // It prefers the gateway verdict (authoritative right after the gateway
5541
+ // updated the binding) and falls back to local resolution on a missing/
5542
+ // failed/unusable verdict so a blip never drops the call.
5543
+
5544
+ function readControllerTrustClass(relay: RelayConnection): string | undefined {
5545
+ return (
5546
+ relay.getController() as unknown as {
5547
+ trustContext?: { trustClass?: string };
5548
+ }
5549
+ )?.trustContext?.trustClass;
5550
+ }
5551
+
5552
+ test("inbound guardian verification: re-resolves trust from the gateway verdict", async () => {
5553
+ ensureConversation("conv-midcall-verdict-guardian");
5554
+ const session = createCallSession({
5555
+ conversationId: "conv-midcall-verdict-guardian",
5556
+ provider: "twilio",
5557
+ fromNumber: "+15559999999",
5558
+ toNumber: "+15551111111",
5559
+ });
5560
+
5561
+ const secret = createPendingVoiceGuardianChallenge();
5562
+
5563
+ // The gateway verdict upgrades the caller to guardian post-verification.
5564
+ mockMidCallVerdict = {
5565
+ trustClass: "guardian",
5566
+ canonicalSenderId: "+15559999999",
5567
+ guardianExternalUserId: "+15559999999",
5568
+ guardianPrincipalId: "+15559999999",
5569
+ };
5570
+
5571
+ mockSendMessage.mockImplementation(
5572
+ createMockProviderResponse(["Hello, verified guardian!"]),
5573
+ );
5574
+
5575
+ const { relay } = createMockWs(session.id);
5576
+
5577
+ await relay.handleMessage(
5578
+ JSON.stringify({
5579
+ type: "setup",
5580
+ callSid: "CA_midcall_verdict_guardian",
5581
+ from: "+15559999999",
5582
+ to: "+15551111111",
5583
+ }),
5584
+ );
5585
+
5586
+ for (const digit of secret) {
5587
+ await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
5588
+ }
5589
+ await new Promise((resolve) => setTimeout(resolve, 10));
5590
+
5591
+ expect(relay.getConnectionState()).toBe("connected");
5592
+ // Controller trust reflects the gateway verdict's upgraded class.
5593
+ expect(readControllerTrustClass(relay)).toBe("guardian");
5594
+
5595
+ relay.destroy();
5596
+ });
5597
+
5598
+ test("inbound guardian verification: resolutionFailed verdict falls back to local resolution without dropping the call", async () => {
5599
+ ensureConversation("conv-midcall-verdict-failed");
5600
+ const session = createCallSession({
5601
+ conversationId: "conv-midcall-verdict-failed",
5602
+ provider: "twilio",
5603
+ fromNumber: "+15559999999",
5604
+ toNumber: "+15551111111",
5605
+ });
5606
+
5607
+ const secret = createPendingVoiceGuardianChallenge();
5608
+
5609
+ // A failed verdict must fall back to local resolution — the call stays up.
5610
+ mockMidCallVerdict = {
5611
+ trustClass: "unknown",
5612
+ canonicalSenderId: null,
5613
+ resolutionFailed: true,
5614
+ };
5615
+
5616
+ mockSendMessage.mockImplementation(
5617
+ createMockProviderResponse(["Hello, how can I help you?"]),
5618
+ );
5619
+
5620
+ const { relay } = createMockWs(session.id);
5621
+
5622
+ await relay.handleMessage(
5623
+ JSON.stringify({
5624
+ type: "setup",
5625
+ callSid: "CA_midcall_verdict_failed",
5626
+ from: "+15559999999",
5627
+ to: "+15551111111",
5628
+ }),
5629
+ );
5630
+
5631
+ for (const digit of secret) {
5632
+ await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
5633
+ }
5634
+ await new Promise((resolve) => setTimeout(resolve, 10));
5635
+
5636
+ // Fail-soft: verification still completes and the call connects.
5637
+ expect(relay.isVerificationSessionActive()).toBe(false);
5638
+ expect(relay.getConnectionState()).toBe("connected");
5639
+ expect(readControllerTrustClass(relay)).toBeDefined();
5640
+
5641
+ relay.destroy();
5642
+ });
5643
+
5644
+ test("inbound guardian verification: member-claiming but unusable verdict falls back to local resolution", async () => {
5645
+ ensureConversation("conv-midcall-verdict-unusable");
5646
+ const session = createCallSession({
5647
+ conversationId: "conv-midcall-verdict-unusable",
5648
+ provider: "twilio",
5649
+ fromNumber: "+15559999999",
5650
+ toNumber: "+15551111111",
5651
+ });
5652
+
5653
+ const secret = createPendingVoiceGuardianChallenge();
5654
+
5655
+ // Claims a member (contactId/channelId) but the ACL can't be reassembled
5656
+ // (missing status/policy) — mirrors the setup path's unusable condition.
5657
+ mockMidCallVerdict = {
5658
+ trustClass: "trusted_contact",
5659
+ canonicalSenderId: "+15559999999",
5660
+ contactId: "ct_unusable",
5661
+ channelId: "ch_unusable",
5662
+ };
5663
+
5664
+ mockSendMessage.mockImplementation(
5665
+ createMockProviderResponse(["Hello there."]),
5666
+ );
5667
+
5668
+ const { relay } = createMockWs(session.id);
5669
+
5670
+ await relay.handleMessage(
5671
+ JSON.stringify({
5672
+ type: "setup",
5673
+ callSid: "CA_midcall_verdict_unusable",
5674
+ from: "+15559999999",
5675
+ to: "+15551111111",
5676
+ }),
5677
+ );
5678
+
5679
+ for (const digit of secret) {
5680
+ await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
5681
+ }
5682
+ await new Promise((resolve) => setTimeout(resolve, 10));
5683
+
5684
+ // Fail-soft: unusable verdict does not drop the call; local fallback fires.
5685
+ expect(relay.getConnectionState()).toBe("connected");
5686
+ expect(readControllerTrustClass(relay)).toBeDefined();
5687
+
5688
+ relay.destroy();
5689
+ });
5690
+
5691
+ test("inbound guardian verification: memberless unknown verdict falls back to local resolution (just-activated invitee not downgraded)", async () => {
5692
+ ensureConversation("conv-midcall-verdict-unknown");
5693
+ const session = createCallSession({
5694
+ conversationId: "conv-midcall-verdict-unknown",
5695
+ provider: "twilio",
5696
+ fromNumber: "+15559999999",
5697
+ toNumber: "+15551111111",
5698
+ });
5699
+
5700
+ const secret = createPendingVoiceGuardianChallenge();
5701
+
5702
+ // Invite redemption writes the channel assistant-side, so right after
5703
+ // activation the gateway has no member and returns a memberless unknown
5704
+ // verdict. Mid-call re-resolution must treat it as a stale gateway view
5705
+ // and fall back to local resolution (which has the fresh channel) rather
5706
+ // than downgrade the just-activated invitee to unknown.
5707
+ mockMidCallVerdict = {
5708
+ trustClass: "unknown",
5709
+ canonicalSenderId: "+15559999999",
5710
+ };
5711
+
5712
+ mockSendMessage.mockImplementation(
5713
+ createMockProviderResponse(["Hello there."]),
5714
+ );
5715
+
5716
+ const { relay } = createMockWs(session.id);
5717
+
5718
+ await relay.handleMessage(
5719
+ JSON.stringify({
5720
+ type: "setup",
5721
+ callSid: "CA_midcall_verdict_unknown",
5722
+ from: "+15559999999",
5723
+ to: "+15551111111",
5724
+ }),
5725
+ );
5726
+
5727
+ for (const digit of secret) {
5728
+ await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
5729
+ }
5730
+ await new Promise((resolve) => setTimeout(resolve, 10));
5731
+
5732
+ expect(relay.getConnectionState()).toBe("connected");
5733
+ // Local resolver produced the final context; the unknown verdict was not
5734
+ // consumed as authoritative.
5735
+ expect(trustVerdictMapperUsed).toBe(false);
5736
+ expect(readControllerTrustClass(relay)).toBeDefined();
5737
+
5738
+ relay.destroy();
5739
+ });
5740
+
5741
+ function readControllerMemberStatus(
5742
+ relay: RelayConnection,
5743
+ ): string | undefined {
5744
+ return (
5745
+ relay.getController() as unknown as {
5746
+ trustContext?: { memberStatus?: string };
5747
+ }
5748
+ )?.trustContext?.memberStatus;
5749
+ }
5750
+
5751
+ test("inbound guardian verification: memberful blocked unknown verdict is honored (verdict path enforces blocked status)", async () => {
5752
+ ensureConversation("conv-midcall-verdict-blocked");
5753
+ const session = createCallSession({
5754
+ conversationId: "conv-midcall-verdict-blocked",
5755
+ provider: "twilio",
5756
+ fromNumber: "+15559999999",
5757
+ toNumber: "+15551111111",
5758
+ });
5759
+
5760
+ const secret = createPendingVoiceGuardianChallenge();
5761
+
5762
+ mockSendMessage.mockImplementation(
5763
+ createMockProviderResponse(["Hello there."]),
5764
+ );
5765
+
5766
+ const { relay } = createMockWs(session.id);
5767
+
5768
+ // Setup resolves locally (no verdict) so the pending guardian challenge
5769
+ // drives verification rather than denying at the door.
5770
+ await relay.handleMessage(
5771
+ JSON.stringify({
5772
+ type: "setup",
5773
+ callSid: "CA_midcall_verdict_blocked",
5774
+ from: "+15559999999",
5775
+ to: "+15551111111",
5776
+ }),
5777
+ );
5778
+
5779
+ // The gateway classifies a blocked member as trustClass "unknown" but still
5780
+ // carries contactId/channelId and the deny ACL. This memberful unknown must
5781
+ // take the verdict path on mid-call re-resolution so its blocked status is
5782
+ // enforced — not fall back to local, which could miss a stale block.
5783
+ mockMidCallVerdict = {
5784
+ trustClass: "unknown",
5785
+ canonicalSenderId: "+15559999999",
5786
+ contactId: "ct_blocked",
5787
+ channelId: "ch_blocked",
5788
+ status: "blocked",
5789
+ policy: "deny",
5790
+ };
5791
+
5792
+ for (const digit of secret) {
5793
+ await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
5794
+ }
5795
+ await new Promise((resolve) => setTimeout(resolve, 10));
5796
+
5797
+ expect(relay.getConnectionState()).toBe("connected");
5798
+ // Verdict path consumed the memberful unknown verdict; blocked status lands.
5799
+ expect(trustVerdictMapperUsed).toBe(true);
5800
+ expect(readControllerMemberStatus(relay)).toBe("blocked");
5801
+
5802
+ relay.destroy();
5803
+ });
5804
+
5805
+ test("inbound guardian verification: memberful revoked unknown verdict is honored (verdict path enforces revoked status)", async () => {
5806
+ ensureConversation("conv-midcall-verdict-revoked");
5807
+ const session = createCallSession({
5808
+ conversationId: "conv-midcall-verdict-revoked",
5809
+ provider: "twilio",
5810
+ fromNumber: "+15559999999",
5811
+ toNumber: "+15551111111",
5812
+ });
5813
+
5814
+ const secret = createPendingVoiceGuardianChallenge();
5815
+
5816
+ mockSendMessage.mockImplementation(
5817
+ createMockProviderResponse(["Hello there."]),
5818
+ );
5819
+
5820
+ const { relay } = createMockWs(session.id);
5821
+
5822
+ await relay.handleMessage(
5823
+ JSON.stringify({
5824
+ type: "setup",
5825
+ callSid: "CA_midcall_verdict_revoked",
5826
+ from: "+15559999999",
5827
+ to: "+15551111111",
5828
+ }),
5829
+ );
5830
+
5831
+ mockMidCallVerdict = {
5832
+ trustClass: "unknown",
5833
+ canonicalSenderId: "+15559999999",
5834
+ contactId: "ct_revoked",
5835
+ channelId: "ch_revoked",
5836
+ status: "revoked",
5837
+ policy: "deny",
5838
+ };
5839
+
5840
+ for (const digit of secret) {
5841
+ await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
5842
+ }
5843
+ await new Promise((resolve) => setTimeout(resolve, 10));
5844
+
5845
+ expect(relay.getConnectionState()).toBe("connected");
5846
+ expect(trustVerdictMapperUsed).toBe(true);
5847
+ expect(readControllerMemberStatus(relay)).toBe("revoked");
5848
+
5849
+ relay.destroy();
5850
+ });
5851
+
5852
+ test("a prompt arriving during the mid-call re-resolution await is deferred and runs under the upgraded trust", async () => {
5853
+ ensureConversation("conv-midcall-race");
5854
+ const session = createCallSession({
5855
+ conversationId: "conv-midcall-race",
5856
+ provider: "twilio",
5857
+ fromNumber: "+15559999999",
5858
+ toNumber: "+15551111111",
5859
+ });
5860
+
5861
+ const secret = createPendingVoiceGuardianChallenge();
5862
+
5863
+ // Capture the controller's trust class at the moment a turn actually fires,
5864
+ // so we can prove the deferred prompt did not run under the stale context.
5865
+ const trustClassAtTurn: Array<string | undefined> = [];
5866
+ mockSendMessage.mockImplementation((...args: unknown[]) => {
5867
+ trustClassAtTurn.push(readControllerTrustClass(relay));
5868
+ return createMockProviderResponse(["Hello, verified guardian!"])(
5869
+ ...(args as Parameters<ReturnType<typeof createMockProviderResponse>>),
5870
+ );
5871
+ });
5872
+
5873
+ const { relay } = createMockWs(session.id);
5874
+
5875
+ // Setup resolves locally (no verdict) so the pending challenge drives
5876
+ // verification; the gated guardian verdict is armed only for the mid-call
5877
+ // re-resolution below.
5878
+ await relay.handleMessage(
5879
+ JSON.stringify({
5880
+ type: "setup",
5881
+ callSid: "CA_midcall_race",
5882
+ from: "+15559999999",
5883
+ to: "+15551111111",
5884
+ }),
5885
+ );
5886
+
5887
+ // The gateway verdict upgrades the caller to guardian, but the round-trip is
5888
+ // slow — gate it so a prompt can land in the re-resolution await window.
5889
+ mockMidCallVerdict = {
5890
+ trustClass: "guardian",
5891
+ canonicalSenderId: "+15559999999",
5892
+ guardianExternalUserId: "+15559999999",
5893
+ guardianPrincipalId: "+15559999999",
5894
+ };
5895
+ let releaseVerdict!: () => void;
5896
+ mockMidCallVerdictGate = new Promise<void>((resolve) => {
5897
+ releaseVerdict = resolve;
5898
+ });
5899
+
5900
+ // Enter the gated re-resolution: the final DTMF digit triggers
5901
+ // handleVerificationCodeResult, which awaits the slow verdict.
5902
+ const digits = secret.split("");
5903
+ for (const digit of digits.slice(0, -1)) {
5904
+ await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
5905
+ }
5906
+ const verificationDone = relay.handleMessage(
5907
+ JSON.stringify({ type: "dtmf", digit: digits[digits.length - 1] }),
5908
+ );
5909
+ // Let the verdict await begin (connectionState is now "connected", guard set).
5910
+ await new Promise((resolve) => setTimeout(resolve, 0));
5911
+ expect(relay.getConnectionState()).toBe("connected");
5912
+ // Trust is still stale (verdict gated) — caller is not yet guardian.
5913
+ expect(readControllerTrustClass(relay)).not.toBe("guardian");
5914
+
5915
+ // Prompt arrives mid-await: it must be deferred, not processed under stale trust.
5916
+ await relay.handleMessage(
5917
+ JSON.stringify({
5918
+ type: "prompt",
5919
+ voicePrompt: "Are my appointments confirmed?",
5920
+ lang: "en-US",
5921
+ last: true,
5922
+ }),
5923
+ );
5924
+ // No turn yet — the prompt was buffered, not run under the stale context.
5925
+ expect(trustClassAtTurn).toHaveLength(0);
5926
+
5927
+ // Release the verdict; re-resolution installs the upgraded context, then the
5928
+ // deferred prompt is flushed and its turn runs under guardian trust.
5929
+ releaseVerdict();
5930
+ await verificationDone;
5931
+ await new Promise((resolve) => setTimeout(resolve, 20));
5932
+
5933
+ expect(readControllerTrustClass(relay)).toBe("guardian");
5934
+ expect(trustClassAtTurn.length).toBeGreaterThan(0);
5935
+ expect(trustClassAtTurn.every((c) => c === "guardian")).toBe(true);
5936
+
5937
+ relay.destroy();
5938
+ });
5939
+
5940
+ test("invite redemption: a prompt buffered during re-resolution runs as a real turn after activation (not dropped)", async () => {
5941
+ ensureConversation("conv-midcall-invite-flush");
5942
+ const session = createCallSession({
5943
+ conversationId: "conv-midcall-invite-flush",
5944
+ provider: "twilio",
5945
+ fromNumber: "+15558887777",
5946
+ toNumber: "+15551111111",
5947
+ });
5948
+
5949
+ const code = generateVoiceCode(6);
5950
+ createInvite({
5951
+ sourceChannel: "phone",
5952
+ contactId: createTargetContact("Alice"),
5953
+ maxUses: 1,
5954
+ expectedExternalUserId: "+15558887777",
5955
+ voiceCodeHash: hashVoiceCode(code),
5956
+ voiceCodeDigits: 6,
5957
+ });
5958
+
5959
+ const turnCountBefore = mockSendMessage.mock.calls.length;
5960
+ mockSendMessage.mockImplementation(
5961
+ createMockProviderResponse(["Sure, here you go."]),
5962
+ );
5963
+
5964
+ const { relay } = createMockWs(session.id);
5965
+
5966
+ await relay.handleMessage(
5967
+ JSON.stringify({
5968
+ type: "setup",
5969
+ callSid: "CA_midcall_invite_flush",
5970
+ from: "+15558887777",
5971
+ to: "+15551111111",
5972
+ }),
5973
+ );
5974
+ expect(relay.getConnectionState()).toBe("verification_pending");
5975
+
5976
+ // Gate the mid-call verdict so the prompt lands inside the re-resolution
5977
+ // await, after activation flips connectionState to "connected".
5978
+ let releaseVerdict!: () => void;
5979
+ mockMidCallVerdictGate = new Promise<void>((resolve) => {
5980
+ releaseVerdict = resolve;
5981
+ });
5982
+
5983
+ const digits = code.split("");
5984
+ for (const digit of digits.slice(0, -1)) {
5985
+ await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
5986
+ }
5987
+ const redemptionDone = relay.handleMessage(
5988
+ JSON.stringify({ type: "dtmf", digit: digits[digits.length - 1] }),
5989
+ );
5990
+ await new Promise((resolve) => setTimeout(resolve, 0));
5991
+ // Activation already reached the terminal state before re-resolution.
5992
+ expect(relay.getConnectionState()).toBe("connected");
5993
+
5994
+ await relay.handleMessage(
5995
+ JSON.stringify({
5996
+ type: "prompt",
5997
+ voicePrompt: "What's on my calendar today?",
5998
+ lang: "en-US",
5999
+ last: true,
6000
+ }),
6001
+ );
6002
+ // Buffered, not yet run (verdict gated).
6003
+ expect(mockSendMessage.mock.calls.length).toBe(turnCountBefore);
6004
+
6005
+ releaseVerdict();
6006
+ await redemptionDone;
6007
+ await new Promise((resolve) => setTimeout(resolve, 20));
6008
+
6009
+ // Flushed onto the real-turn path: the prompt produced an LLM turn rather
6010
+ // than being dropped by the verification-pending branch.
6011
+ expect(mockSendMessage.mock.calls.length).toBeGreaterThan(turnCountBefore);
6012
+
6013
+ relay.destroy();
6014
+ });
6015
+
6016
+ test("access-request approval: a prompt buffered during re-resolution runs as a real turn (not misrouted to wait-state)", async () => {
6017
+ ensureConversation("conv-midcall-access-flush");
6018
+ const session = createCallSession({
6019
+ conversationId: "conv-midcall-access-flush",
6020
+ provider: "twilio",
6021
+ fromNumber: "+15557770003",
6022
+ toNumber: "+15551111111",
6023
+ });
6024
+
6025
+ const turnCountBefore = mockSendMessage.mock.calls.length;
6026
+ mockSendMessage.mockImplementation(
6027
+ createMockProviderResponse(["Sure, here you go."]),
6028
+ );
6029
+
6030
+ const { relay } = createMockWs(session.id);
6031
+
6032
+ await relay.handleMessage(
6033
+ JSON.stringify({
6034
+ type: "setup",
6035
+ callSid: "CA_midcall_access_flush",
6036
+ from: "+15557770003",
6037
+ to: "+15551111111",
6038
+ }),
6039
+ );
6040
+
6041
+ await relay.handleMessage(
6042
+ JSON.stringify({
6043
+ type: "prompt",
6044
+ voicePrompt: "Bob Smith",
6045
+ lang: "en-US",
6046
+ last: true,
6047
+ }),
6048
+ );
6049
+ expect(relay.getConnectionState()).toBe("awaiting_guardian_decision");
6050
+
6051
+ // Gate the mid-call verdict so the prompt lands inside the re-resolution
6052
+ // await triggered by the approval poll.
6053
+ let releaseVerdict!: () => void;
6054
+ mockMidCallVerdictGate = new Promise<void>((resolve) => {
6055
+ releaseVerdict = resolve;
6056
+ });
6057
+
6058
+ const pending = listCanonicalGuardianRequests({
6059
+ status: "pending",
6060
+ requesterExternalUserId: "+15557770003",
6061
+ sourceChannel: "phone",
6062
+ kind: "access_request",
6063
+ });
6064
+ expect(pending.length).toBe(1);
6065
+ resolveCanonicalGuardianRequest(pending[0].id, "pending", {
6066
+ status: "approved",
6067
+ answerText: undefined,
6068
+ decidedByExternalUserId: undefined,
6069
+ });
6070
+
6071
+ // Let the poll detect approval and enter the gated re-resolution.
6072
+ await new Promise((resolve) => setTimeout(resolve, 200));
6073
+ expect(relay.getConnectionState()).toBe("connected");
6074
+
6075
+ await relay.handleMessage(
6076
+ JSON.stringify({
6077
+ type: "prompt",
6078
+ voicePrompt: "What's on my calendar today?",
6079
+ lang: "en-US",
6080
+ last: true,
6081
+ }),
6082
+ );
6083
+ // Buffered, not yet run (verdict gated).
6084
+ expect(mockSendMessage.mock.calls.length).toBe(turnCountBefore);
6085
+
6086
+ releaseVerdict();
6087
+ await new Promise((resolve) => setTimeout(resolve, 20));
6088
+
6089
+ // Flushed onto the real-turn path rather than the awaiting-guardian-decision
6090
+ // wait-state classifier: the prompt produced an LLM turn.
6091
+ expect(mockSendMessage.mock.calls.length).toBeGreaterThan(turnCountBefore);
6092
+
6093
+ relay.destroy();
6094
+ });
5142
6095
  });