@vellumai/assistant 0.10.1 → 0.10.2-dev.202606241651.2d2b40d

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 (367) hide show
  1. package/docs/workspace-tools.md +42 -33
  2. package/eslint-rules/cli-no-daemon-internals.js +6 -0
  3. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +91 -0
  4. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +31 -0
  5. package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +48 -0
  6. package/node_modules/@vellumai/gateway-client/src/index.ts +14 -0
  7. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +17 -0
  8. package/openapi.yaml +74 -1
  9. package/package.json +1 -1
  10. package/scripts/test.sh +36 -15
  11. package/src/__tests__/actor-token-service.test.ts +36 -14
  12. package/src/__tests__/agent-loop-override-profile.test.ts +1 -0
  13. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  14. package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
  15. package/src/__tests__/annotate-activity-metadata.test.ts +2 -0
  16. package/src/__tests__/annotate-risk-options.test.ts +2 -0
  17. package/src/__tests__/approval-cascade.test.ts +2 -0
  18. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -0
  19. package/src/__tests__/btw-routes.test.ts +2 -0
  20. package/src/__tests__/build-persisted-content.test.ts +2 -0
  21. package/src/__tests__/call-controller.test.ts +19 -0
  22. package/src/__tests__/channel-guardian.test.ts +94 -58
  23. package/src/__tests__/channel-reply-delivery.test.ts +2 -0
  24. package/src/__tests__/compaction-events.test.ts +2 -0
  25. package/src/__tests__/compaction.benchmark.test.ts +2 -0
  26. package/src/__tests__/compactor-call-site-logging.test.ts +2 -0
  27. package/src/__tests__/compactor-low-watermark-cut.test.ts +2 -0
  28. package/src/__tests__/compactor-preserved-tail-count.test.ts +2 -0
  29. package/src/__tests__/compactor-summary-call-truncation.test.ts +2 -0
  30. package/src/__tests__/compactor-web-search-strip.test.ts +2 -0
  31. package/src/__tests__/computer-use-tools.test.ts +13 -0
  32. package/src/__tests__/config-loader-backfill.test.ts +5 -1
  33. package/src/__tests__/config-schema.test.ts +1 -0
  34. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +31 -29
  35. package/src/__tests__/contacts-relay-reads.test.ts +13 -15
  36. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  37. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +2 -0
  38. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
  39. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
  40. package/src/__tests__/conversation-agent-loop.test.ts +7 -0
  41. package/src/__tests__/conversation-analysis-routes.test.ts +2 -0
  42. package/src/__tests__/conversation-app-control-lifecycle.test.ts +2 -0
  43. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -0
  44. package/src/__tests__/conversation-history-web-search.test.ts +2 -0
  45. package/src/__tests__/conversation-load-history-repair.test.ts +2 -0
  46. package/src/__tests__/conversation-load-history-stripped.test.ts +2 -0
  47. package/src/__tests__/conversation-pairing.test.ts +2 -0
  48. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
  49. package/src/__tests__/conversation-process-callsite.test.ts +2 -0
  50. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  51. package/src/__tests__/conversation-queue.test.ts +91 -0
  52. package/src/__tests__/conversation-routes-guardian-reply.test.ts +14 -0
  53. package/src/__tests__/conversation-routes-slash-commands.test.ts +14 -0
  54. package/src/__tests__/conversation-slash-queue.test.ts +2 -0
  55. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  56. package/src/__tests__/conversation-speed-override.test.ts +2 -0
  57. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +65 -0
  58. package/src/__tests__/conversation-title-service.test.ts +2 -0
  59. package/src/__tests__/conversation-tool-setup-attribution.test.ts +47 -0
  60. package/src/__tests__/conversation-usage.test.ts +2 -0
  61. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -0
  62. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  63. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  64. package/src/__tests__/credential-security-invariants.test.ts +0 -1
  65. package/src/__tests__/db-migration-rollback.test.ts +205 -171
  66. package/src/__tests__/db-test-helpers.ts +5 -4
  67. package/src/__tests__/deterministic-verification-control-plane.test.ts +4 -2
  68. package/src/__tests__/disk-pressure-guard.test.ts +41 -0
  69. package/src/__tests__/dm-persistence.test.ts +2 -0
  70. package/src/__tests__/emit-signal-routing-intent.test.ts +10 -5
  71. package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
  72. package/src/__tests__/filing-service.test.ts +2 -0
  73. package/src/__tests__/guardian-binding-drift-heal.test.ts +75 -10
  74. package/src/__tests__/guardian-dispatch.test.ts +95 -1
  75. package/src/__tests__/guardian-outbound-http.test.ts +13 -0
  76. package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
  77. package/src/__tests__/heartbeat-service.test.ts +2 -0
  78. package/src/__tests__/helpers/channel-test-adapter.ts +1 -7
  79. package/src/__tests__/host-app-control-routes.test.ts +24 -30
  80. package/src/__tests__/host-bash-routes.test.ts +31 -41
  81. package/src/__tests__/host-browser-routes.test.ts +26 -32
  82. package/src/__tests__/host-cu-proxy.test.ts +299 -0
  83. package/src/__tests__/host-cu-routes-targeted.test.ts +25 -33
  84. package/src/__tests__/host-file-routes-targeted.test.ts +40 -52
  85. package/src/__tests__/host-transfer-routes-targeted.test.ts +31 -43
  86. package/src/__tests__/http-user-message-parity.test.ts +167 -8
  87. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  88. package/src/__tests__/invite-redemption-service.test.ts +43 -0
  89. package/src/__tests__/llm-context-normalization.test.ts +105 -0
  90. package/src/__tests__/llm-usage-store.test.ts +25 -0
  91. package/src/__tests__/media-stream-server-integration.test.ts +127 -0
  92. package/src/__tests__/memory-retrieval-hook.test.ts +2 -0
  93. package/src/__tests__/messaging-send-tool.test.ts +2 -0
  94. package/src/__tests__/migration-import-from-url.test.ts +2 -2
  95. package/src/__tests__/native-web-search.test.ts +2 -0
  96. package/src/__tests__/non-member-access-request.test.ts +189 -17
  97. package/src/__tests__/notification-broadcaster.test.ts +4 -0
  98. package/src/__tests__/notification-decision-recipient-context.test.ts +33 -32
  99. package/src/__tests__/notification-deep-link.test.ts +6 -0
  100. package/src/__tests__/notification-guardian-path.test.ts +19 -0
  101. package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
  102. package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
  103. package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
  104. package/src/__tests__/plugin-bootstrap.test.ts +3 -73
  105. package/src/__tests__/plugin-route-contribution.test.ts +4 -17
  106. package/src/__tests__/plugin-tool-contribution.test.ts +3 -18
  107. package/src/__tests__/plugin-types.test.ts +0 -2
  108. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  109. package/src/__tests__/process-message-display-content.test.ts +2 -0
  110. package/src/__tests__/provider-usage-tracking.test.ts +39 -0
  111. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +2 -0
  112. package/src/__tests__/registry.test.ts +3 -0
  113. package/src/__tests__/relay-server.test.ts +694 -25
  114. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  115. package/src/__tests__/secret-ingress-http.test.ts +14 -0
  116. package/src/__tests__/send-endpoint-busy.test.ts +30 -8
  117. package/src/__tests__/skills.test.ts +44 -0
  118. package/src/__tests__/slack-inbound-verification.test.ts +47 -2
  119. package/src/__tests__/sse-actor-principal-guardian-source.test.ts +102 -0
  120. package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
  121. package/src/__tests__/stt-hints.test.ts +44 -13
  122. package/src/__tests__/subagent-detail.test.ts +27 -0
  123. package/src/__tests__/subagent-disposal.test.ts +65 -0
  124. package/src/__tests__/subagent-notify-parent.test.ts +2 -0
  125. package/src/__tests__/subagent-spawn-tool-fork.test.ts +2 -0
  126. package/src/__tests__/subagent-tools.test.ts +2 -0
  127. package/src/__tests__/suggestion-routes.test.ts +2 -0
  128. package/src/__tests__/title-generate-hook.test.ts +2 -0
  129. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
  130. package/src/__tests__/tool-executor.test.ts +16 -11
  131. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
  132. package/src/__tests__/tool-result-metadata-plumbing.test.ts +2 -0
  133. package/src/__tests__/tool-start-timestamp.test.ts +2 -0
  134. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +10 -10
  135. package/src/__tests__/twilio-routes.test.ts +96 -0
  136. package/src/__tests__/verification-control-plane-policy.test.ts +2 -0
  137. package/src/__tests__/web-search-backend-failure.test.ts +2 -0
  138. package/src/__tests__/workspace-tool-loader.test.ts +195 -2
  139. package/src/agent/loop-exclusive-tool.test.ts +150 -0
  140. package/src/agent/loop.ts +56 -0
  141. package/src/api/constants/sse-replay.ts +41 -0
  142. package/src/api/index.ts +6 -0
  143. package/src/api/responses/llm-request-log-entry.ts +25 -0
  144. package/src/api/responses/subagent-detail.ts +17 -0
  145. package/src/calls/__tests__/relay-setup-router.test.ts +262 -4
  146. package/src/calls/call-domain.ts +3 -3
  147. package/src/calls/guardian-dispatch.ts +10 -8
  148. package/src/calls/inbound-trust-reader.ts +17 -1
  149. package/src/calls/media-stream-server.ts +21 -0
  150. package/src/calls/relay-server.ts +167 -50
  151. package/src/calls/relay-setup-router.ts +37 -7
  152. package/src/calls/relay-verification.ts +4 -4
  153. package/src/calls/stt-hints.ts +9 -12
  154. package/src/calls/twilio-routes.ts +14 -4
  155. package/src/cli/commands/__tests__/cache.test.ts +8 -1
  156. package/src/cli/commands/cache.ts +194 -181
  157. package/src/cli/commands/db/__tests__/repair.test.ts +6 -5
  158. package/src/cli/commands/db/status.ts +37 -1
  159. package/src/cli/commands/mcp.ts +252 -218
  160. package/src/cli/commands/memory/__tests__/worker.test.ts +302 -0
  161. package/src/cli/commands/memory/index.ts +2 -0
  162. package/src/cli/commands/memory/worker.ts +175 -0
  163. package/src/cli/commands/plugins.ts +75 -3
  164. package/src/cli/lib/__tests__/install-from-github.test.ts +102 -0
  165. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +160 -1
  166. package/src/cli/lib/list-installed-plugins.ts +179 -1
  167. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
  168. package/src/config/bundled-skills/computer-use/TOOLS.json +6 -1
  169. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +27 -17
  170. package/src/config/bundled-skills/contacts/tools/contact-search.ts +13 -3
  171. package/src/config/feature-flag-registry.json +0 -8
  172. package/src/config/loader.ts +36 -5
  173. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  174. package/src/config/schemas/memory-lifecycle.ts +12 -0
  175. package/src/config/schemas/memory-v3.ts +7 -0
  176. package/src/config/schemas/memory.ts +4 -0
  177. package/src/config/schemas/timeouts.ts +8 -0
  178. package/src/config/seed-inference-profiles.ts +14 -5
  179. package/src/config/skills.ts +27 -5
  180. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
  181. package/src/contacts/contacts-write.ts +3 -0
  182. package/src/contacts/guardian-delivery-reader.ts +223 -0
  183. package/src/daemon/conversation-agent-loop.ts +9 -0
  184. package/src/daemon/conversation-process.ts +39 -17
  185. package/src/daemon/conversation-surfaces.ts +8 -0
  186. package/src/daemon/conversation-tool-setup.ts +49 -16
  187. package/src/daemon/conversation.ts +21 -2
  188. package/src/daemon/disk-pressure-guard.ts +12 -2
  189. package/src/daemon/event-loop-watchdog.ts +28 -1
  190. package/src/daemon/external-plugins-bootstrap.ts +4 -34
  191. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +25 -0
  192. package/src/daemon/handlers/__tests__/config-channels.test.ts +225 -0
  193. package/src/daemon/handlers/config-a2a.ts +6 -14
  194. package/src/daemon/handlers/config-channels.ts +78 -22
  195. package/src/daemon/handlers/conversations.ts +77 -0
  196. package/src/daemon/host-cu-proxy.ts +102 -11
  197. package/src/daemon/lifecycle.ts +4 -0
  198. package/src/daemon/memory-v2-startup.test.ts +72 -0
  199. package/src/daemon/memory-v2-startup.ts +87 -19
  200. package/src/daemon/server.ts +0 -4
  201. package/src/daemon/shutdown-handlers.ts +20 -0
  202. package/src/daemon/tool-setup-types.ts +9 -0
  203. package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
  204. package/src/ipc/assistant-server.ts +2 -2
  205. package/src/memory/__tests__/301-create-watchdog-events.test.ts +110 -0
  206. package/src/memory/__tests__/memory-retrospective-job.test.ts +8 -0
  207. package/src/memory/__tests__/prompt-override.test.ts +192 -0
  208. package/src/memory/__tests__/watchdog-events-store.test.ts +161 -0
  209. package/src/memory/conversation-crud.ts +38 -0
  210. package/src/memory/db-connection.ts +22 -3
  211. package/src/memory/db-init.ts +36 -502
  212. package/src/memory/db-singleton.ts +6 -4
  213. package/src/memory/jobs-worker.ts +58 -0
  214. package/src/memory/llm-usage-store.ts +48 -20
  215. package/src/memory/memory-retrospective-job.ts +9 -8
  216. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +13 -3
  217. package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -27
  218. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +130 -56
  219. package/src/memory/migrations/300-add-processing-started-at.ts +30 -0
  220. package/src/memory/migrations/301-create-watchdog-events.ts +45 -0
  221. package/src/memory/migrations/__tests__/014-backfill-inbox-thread-state.test.ts +108 -0
  222. package/src/memory/migrations/__tests__/136-drop-assistant-id-columns.test.ts +82 -0
  223. package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +224 -0
  224. package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
  225. package/src/memory/migrations/run-migrations.ts +90 -6
  226. package/src/memory/migrations/schema-introspection.ts +14 -0
  227. package/src/memory/migrations/validate-migration-state.ts +101 -66
  228. package/src/memory/prompt-override.ts +129 -0
  229. package/src/memory/schema/conversations.ts +9 -0
  230. package/src/memory/schema/infrastructure.ts +20 -0
  231. package/src/memory/steps.ts +573 -0
  232. package/src/memory/v2/__tests__/cli-command-store.test.ts +25 -0
  233. package/src/memory/v2/__tests__/skill-store.test.ts +80 -0
  234. package/src/memory/v2/cli-command-store.ts +75 -38
  235. package/src/memory/v2/prompts/consolidation.ts +13 -82
  236. package/src/memory/v2/prompts/router.ts +21 -93
  237. package/src/memory/v2/skill-store.ts +68 -31
  238. package/src/memory/watchdog-events-store.ts +87 -0
  239. package/src/memory/worker-control.ts +118 -0
  240. package/src/memory/worker-process.ts +72 -0
  241. package/src/notifications/__tests__/broadcaster.test.ts +16 -8
  242. package/src/notifications/__tests__/connected-channels.test.ts +114 -0
  243. package/src/notifications/__tests__/decision-engine.test.ts +78 -9
  244. package/src/notifications/__tests__/destination-resolver.test.ts +256 -0
  245. package/src/notifications/broadcaster.ts +8 -1
  246. package/src/notifications/decision-engine.ts +15 -7
  247. package/src/notifications/destination-resolver.ts +68 -24
  248. package/src/notifications/emit-signal.ts +39 -14
  249. package/src/onboarding/checkin-event.test.ts +220 -0
  250. package/src/onboarding/checkin-event.ts +321 -0
  251. package/src/onboarding/schedule-checkin.ts +190 -0
  252. package/src/permissions/question-prompter.test.ts +1 -1
  253. package/src/permissions/question-prompter.ts +7 -4
  254. package/src/plugin-api/index.ts +6 -6
  255. package/src/plugin-api/types.ts +3 -5
  256. package/src/plugin-api/vision-support.test.ts +28 -4
  257. package/src/plugin-api/vision-support.ts +66 -31
  258. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +161 -0
  259. package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +106 -0
  260. package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +60 -0
  261. package/src/plugins/defaults/advisor/consult.ts +110 -6
  262. package/src/plugins/defaults/advisor/context-pack.ts +288 -0
  263. package/src/plugins/defaults/advisor/steering.ts +14 -2
  264. package/src/plugins/defaults/advisor/tools/advisor.ts +32 -5
  265. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +47 -7
  266. package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +10 -11
  267. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +12 -20
  268. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +42 -11
  269. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +11 -2
  270. package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +146 -0
  271. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +29 -1
  272. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +8 -1
  273. package/src/plugins/mtime-cache.ts +7 -2
  274. package/src/plugins/types.ts +0 -2
  275. package/src/providers/anthropic/client.ts +5 -0
  276. package/src/providers/call-site-routing.ts +4 -0
  277. package/src/providers/model-catalog.ts +16 -0
  278. package/src/providers/openai/responses-provider.ts +5 -0
  279. package/src/providers/openrouter/client.ts +5 -0
  280. package/src/providers/provider-send-message.ts +4 -0
  281. package/src/providers/ratelimit.ts +4 -0
  282. package/src/providers/retry.ts +4 -0
  283. package/src/providers/types.ts +9 -0
  284. package/src/providers/usage-tracking.ts +4 -0
  285. package/src/runtime/__tests__/channel-verification-service.test.ts +133 -0
  286. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +181 -0
  287. package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +66 -0
  288. package/src/runtime/__tests__/local-principal-trust.test.ts +164 -0
  289. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +335 -3
  290. package/src/runtime/access-request-helper.ts +19 -39
  291. package/src/runtime/actor-trust-resolver.ts +2 -2
  292. package/src/runtime/anchored-guardian.test.ts +156 -0
  293. package/src/runtime/anchored-guardian.ts +135 -0
  294. package/src/runtime/assistant-event-hub.ts +1 -1
  295. package/src/runtime/assistant-stream-state.ts +9 -2
  296. package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +99 -0
  297. package/src/runtime/auth/require-bound-guardian.ts +21 -11
  298. package/src/runtime/channel-verification-service.ts +56 -31
  299. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
  300. package/src/runtime/guardian-vellum-migration.ts +66 -7
  301. package/src/runtime/invite-redemption-service.ts +50 -18
  302. package/src/runtime/local-actor-identity.ts +76 -11
  303. package/src/runtime/local-principal-trust.ts +52 -0
  304. package/src/runtime/pending-interactions.ts +11 -1
  305. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +56 -5
  306. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  307. package/src/runtime/routes/__tests__/contact-routes.test.ts +212 -0
  308. package/src/runtime/routes/__tests__/global-search-routes.test.ts +93 -0
  309. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +215 -1
  310. package/src/runtime/routes/browser-routes.ts +1 -1
  311. package/src/runtime/routes/channel-verification-routes.ts +3 -3
  312. package/src/runtime/routes/contact-routes.ts +8 -32
  313. package/src/runtime/routes/conversation-cli-routes.ts +4 -5
  314. package/src/runtime/routes/conversation-list-routes.ts +4 -7
  315. package/src/runtime/routes/conversation-routes.ts +74 -81
  316. package/src/runtime/routes/events-routes.ts +2 -2
  317. package/src/runtime/routes/global-search-routes.ts +3 -1
  318. package/src/runtime/routes/guardian-action-routes.ts +4 -5
  319. package/src/runtime/routes/host-app-control-routes.ts +5 -4
  320. package/src/runtime/routes/host-bash-routes.ts +5 -4
  321. package/src/runtime/routes/host-browser-routes.ts +9 -11
  322. package/src/runtime/routes/host-cu-routes.ts +5 -4
  323. package/src/runtime/routes/host-file-routes.ts +5 -4
  324. package/src/runtime/routes/host-transfer-routes.ts +6 -6
  325. package/src/runtime/routes/http-adapter.ts +1 -1
  326. package/src/runtime/routes/identity-routes.ts +3 -2
  327. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  328. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +97 -5
  329. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +61 -49
  330. package/src/runtime/routes/inbound-stages/background-dispatch.ts +16 -4
  331. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
  332. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +21 -8
  333. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -3
  334. package/src/runtime/routes/index.ts +2 -0
  335. package/src/runtime/routes/llm-context-normalization.ts +71 -0
  336. package/src/runtime/routes/mcp-auth-routes.ts +38 -15
  337. package/src/runtime/routes/migration-rollback-routes.ts +4 -3
  338. package/src/runtime/routes/migration-routes.ts +4 -1
  339. package/src/runtime/routes/onboarding-checkin-routes.ts +86 -0
  340. package/src/runtime/routes/subagents-routes.ts +5 -0
  341. package/src/runtime/routes/surface-action-routes.ts +51 -55
  342. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  343. package/src/runtime/services/conversation-serializer.ts +7 -9
  344. package/src/runtime/tool-grant-request-helper.ts +3 -3
  345. package/src/runtime/trust-verdict-consumer.ts +85 -9
  346. package/src/runtime/verification-outbound-actions.ts +18 -18
  347. package/src/signals/user-message.ts +16 -0
  348. package/src/subagent/manager.ts +9 -0
  349. package/src/telemetry/types.ts +34 -1
  350. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  351. package/src/telemetry/usage-telemetry-reporter.ts +87 -3
  352. package/src/tools/ask-question/ask-question-tool.test.ts +29 -0
  353. package/src/tools/ask-question/ask-question-tool.ts +13 -0
  354. package/src/tools/computer-use/definitions.ts +8 -2
  355. package/src/tools/executor.ts +4 -4
  356. package/src/tools/registry.ts +18 -0
  357. package/src/tools/tool-approval-handler.ts +1 -1
  358. package/src/tools/tool-defaults.ts +9 -2
  359. package/src/tools/types.ts +17 -2
  360. package/src/tools/workspace-tools/loader.ts +348 -244
  361. package/src/util/platform.ts +5 -0
  362. package/src/util/telemetry-db-path.ts +24 -0
  363. package/src/workspace/migrations/017-seed-persona-dirs.ts +3 -34
  364. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +3 -24
  365. package/src/__tests__/workspace-tools-watcher-flag.test.ts +0 -70
  366. package/src/daemon/workspace-tools-watcher.ts +0 -328
  367. package/src/memory/migrations/registry.ts +0 -573
@@ -29,6 +29,17 @@ mock.module("../config/env.js", () => ({
29
29
  checkUnrecognizedEnvVars: () => {},
30
30
  }));
31
31
 
32
+ // No gateway in tests: force the reader to miss so resolution exercises the
33
+ // local-store bootstrap fallback deterministically.
34
+ let fakeGuardianDelivery: { principalId?: string | null } | null = null;
35
+ mock.module("../contacts/guardian-delivery-reader.js", () => ({
36
+ getGuardianDelivery: async () =>
37
+ fakeGuardianDelivery ? [fakeGuardianDelivery] : null,
38
+ guardianForChannel: (list: { principalId?: string | null }[]) => list[0],
39
+ invalidateGuardianDeliveryCache: () => {},
40
+ onContactChange: () => {},
41
+ }));
42
+
32
43
  import { getDb } from "../memory/db-connection.js";
33
44
  import { initializeDb } from "../memory/db-init.js";
34
45
  import { resetExternalAssistantIdCache } from "../runtime/auth/external-assistant-id.js";
@@ -51,6 +62,7 @@ await initializeDb();
51
62
  beforeEach(async () => {
52
63
  initAuthSigningKey(TEST_KEY);
53
64
  resetExternalAssistantIdCache();
65
+ fakeGuardianDelivery = null;
54
66
  resetDbForTesting();
55
67
  await initializeDb();
56
68
  });
@@ -60,8 +72,8 @@ beforeEach(async () => {
60
72
  // ---------------------------------------------------------------------------
61
73
 
62
74
  describe("resolveLocalTrustContext", () => {
63
- test("falls back to minimal trust context when no vellum binding exists", () => {
64
- const ctx = resolveLocalTrustContext();
75
+ test("falls back to minimal trust context when no vellum binding exists", async () => {
76
+ const ctx = await resolveLocalTrustContext();
65
77
  expect(ctx.sourceChannel).toBe("vellum");
66
78
  });
67
79
  });
@@ -71,38 +83,48 @@ describe("resolveLocalTrustContext", () => {
71
83
  // ---------------------------------------------------------------------------
72
84
 
73
85
  describe("resolveLocalAuthContext", () => {
74
- test("returns AuthContext with local principal type", () => {
75
- const ctx = resolveLocalAuthContext("session-123");
86
+ test("returns AuthContext with local principal type", async () => {
87
+ const ctx = await resolveLocalAuthContext("session-123");
76
88
  expect(ctx.principalType).toBe("local");
77
89
  });
78
90
 
79
- test("subject follows local:self:<conversationId> pattern", () => {
80
- const ctx = resolveLocalAuthContext("session-abc");
91
+ test("subject follows local:self:<conversationId> pattern", async () => {
92
+ const ctx = await resolveLocalAuthContext("session-abc");
81
93
  expect(ctx.subject).toBe("local:self:session-abc");
82
94
  });
83
95
 
84
- test("assistantId is always self", () => {
85
- const ctx = resolveLocalAuthContext("session-123");
96
+ test("assistantId is always self", async () => {
97
+ const ctx = await resolveLocalAuthContext("session-123");
86
98
  expect(ctx.assistantId).toBe("self");
87
99
  });
88
100
 
89
- test("uses local_v1 scope profile with local.all scope", () => {
90
- const ctx = resolveLocalAuthContext("session-123");
101
+ test("uses local_v1 scope profile with local.all scope", async () => {
102
+ const ctx = await resolveLocalAuthContext("session-123");
91
103
  expect(ctx.scopeProfile).toBe("local_v1");
92
104
  expect(ctx.scopes.has("local.all")).toBe(true);
93
105
  });
94
106
 
95
- test("actorPrincipalId is undefined when no vellum binding exists", () => {
107
+ test("actorPrincipalId is undefined when no vellum binding exists", async () => {
96
108
  const db = getDb();
97
109
  db.run("DELETE FROM contact_channels");
98
110
  db.run("DELETE FROM contacts");
99
111
 
100
- const ctx = resolveLocalAuthContext("session-123");
112
+ const ctx = await resolveLocalAuthContext("session-123");
101
113
  expect(ctx.actorPrincipalId).toBeUndefined();
102
114
  });
103
115
 
104
- test("conversationId matches the provided argument", () => {
105
- const ctx = resolveLocalAuthContext("my-session");
116
+ test("resolves the guardian principal from the gateway when available", async () => {
117
+ const db = getDb();
118
+ db.run("DELETE FROM contact_channels");
119
+ db.run("DELETE FROM contacts");
120
+ fakeGuardianDelivery = { principalId: "gateway-guardian-id" };
121
+
122
+ const ctx = await resolveLocalAuthContext("session-123");
123
+ expect(ctx.actorPrincipalId).toBe("gateway-guardian-id");
124
+ });
125
+
126
+ test("conversationId matches the provided argument", async () => {
127
+ const ctx = await resolveLocalAuthContext("my-session");
106
128
  expect(ctx.conversationId).toBe("my-session");
107
129
  });
108
130
  });
@@ -384,6 +384,7 @@ describe("SubagentManager.spawn — overrideProfile inheritance", () => {
384
384
  // inheritance chain breaks at the second nesting level.
385
385
 
386
386
  mock.module("../memory/conversation-crud.js", () => ({
387
+ setConversationProcessingStartedAt: () => {},
387
388
  // Always return undefined for the row read so the test fails fast unless
388
389
  // executeSubagentSpawn reads from context.overrideProfile first.
389
390
  getConversationOverrideProfile: () => undefined,
@@ -21,6 +21,8 @@ import type {
21
21
  import type { Message } from "../providers/types.js";
22
22
 
23
23
  mock.module("../memory/conversation-crud.js", () => ({
24
+ setConversationProcessingStartedAt: () => {},
25
+ isConversationProcessing: () => false,
24
26
  getConversationOverrideProfile: () => undefined,
25
27
  }));
26
28
 
@@ -31,6 +31,8 @@ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
31
31
  let mockOverrideProfile: string | undefined = undefined;
32
32
 
33
33
  mock.module("../memory/conversation-crud.js", () => ({
34
+ setConversationProcessingStartedAt: () => {},
35
+ isConversationProcessing: () => false,
34
36
  getConversationOverrideProfile: (_id: string) => mockOverrideProfile,
35
37
  reserveMessage: mock(async () => ({ id: "msg-reserve" })),
36
38
  }));
@@ -55,6 +55,8 @@ let mockedRowContent = "";
55
55
  const updates: Array<{ id: string; content: string }> = [];
56
56
 
57
57
  mock.module("../memory/conversation-crud.js", () => ({
58
+ setConversationProcessingStartedAt: () => {},
59
+ isConversationProcessing: () => false,
58
60
  addMessage: () => ({ id: "mock-msg-id" }),
59
61
  getMessageById: (id: string) =>
60
62
  mockedRowContent ? { id, content: mockedRowContent } : null,
@@ -50,6 +50,8 @@ let mockedRowContent = "";
50
50
  const updates: Array<{ id: string; content: string }> = [];
51
51
 
52
52
  mock.module("../memory/conversation-crud.js", () => ({
53
+ setConversationProcessingStartedAt: () => {},
54
+ isConversationProcessing: () => false,
53
55
  addMessage: () => ({ id: "mock-msg-id" }),
54
56
  getMessageById: (id: string) =>
55
57
  mockedRowContent ? { id, content: mockedRowContent } : null,
@@ -121,6 +121,8 @@ mock.module("../security/secret-allowlist.js", () => ({
121
121
  }));
122
122
 
123
123
  mock.module("../memory/conversation-crud.js", () => ({
124
+ setConversationProcessingStartedAt: () => {},
125
+ isConversationProcessing: () => false,
124
126
  setConversationOriginChannelIfUnset: () => {},
125
127
  updateConversationContextWindow: () => {},
126
128
  deleteMessageById: () => {},
@@ -100,6 +100,8 @@ mock.module("../daemon/process-message.js", () => ({
100
100
 
101
101
  const createdConversations: Array<{ conversationType: string }> = [];
102
102
  mock.module("../memory/conversation-crud.js", () => ({
103
+ setConversationProcessingStartedAt: () => {},
104
+ isConversationProcessing: () => false,
103
105
  addMessage: mock(() => ({ id: "msg-1" })),
104
106
  archiveConversation: mock(() => true),
105
107
  batchSetDisplayOrders: mock(() => {}),
@@ -43,6 +43,8 @@ mock.module("../memory/conversation-key-store.js", () => ({
43
43
  const mockAddMessage = mock(() => {});
44
44
 
45
45
  mock.module("../memory/conversation-crud.js", () => ({
46
+ setConversationProcessingStartedAt: () => {},
47
+ isConversationProcessing: () => false,
46
48
  addMessage: mockAddMessage,
47
49
  reserveMessage: mock(async () => ({ id: "msg-reserve" })),
48
50
  }));
@@ -41,6 +41,8 @@ mock.module("../config/loader.js", () => ({
41
41
  }));
42
42
 
43
43
  mock.module("../memory/conversation-crud.js", () => ({
44
+ setConversationProcessingStartedAt: () => {},
45
+ isConversationProcessing: () => false,
44
46
  addMessage: () => ({ id: "mock-msg-id" }),
45
47
  getMessageById: () => null,
46
48
  updateMessageContent: () => {},
@@ -168,6 +168,25 @@ mock.module("../notifications/emit-signal.js", () => ({
168
168
  }),
169
169
  }));
170
170
 
171
+ // Guardian principalId is resolved from the gateway binding reader. Mirror the
172
+ // vellum binding seeded by resetTables so guardian dispatch can resolve it.
173
+ mock.module("../contacts/guardian-delivery-reader.js", () => ({
174
+ getGuardianDelivery: async () => [
175
+ {
176
+ channelType: "vellum",
177
+ contactId: "guardian-vellum",
178
+ principalId: "test-principal-id",
179
+ address: "local",
180
+ status: "active",
181
+ },
182
+ ],
183
+ guardianForChannel: (
184
+ list: Array<{ channelType: string; status: string }>,
185
+ channelType: string,
186
+ ) => list.find((g) => g.channelType === channelType && g.status === "active"),
187
+ anyGuardian: (list: unknown[]) => list[0],
188
+ }));
189
+
171
190
  mock.module("../calls/voice-session-bridge.js", () => {
172
191
  mockStartVoiceTurn = mock(createMockVoiceTurn(["Hello", " there"]));
173
192
  return {
@@ -83,6 +83,42 @@ mock.module("../ipc/gateway-client.js", () => ({
83
83
  }),
84
84
  }));
85
85
 
86
+ // Guardian-delivery reader mock — the inbound challenge guard reads guardian
87
+ // existence from the gateway. Derive the list from the local binding state so
88
+ // the gateway-backed presence guard mirrors the DB the rest of the test sets up.
89
+ const resolveGuardianList = async (input?: { channelTypes?: string[] }) => {
90
+ const { findGuardianForChannel } = await import(
91
+ "../contacts/contact-store.js"
92
+ );
93
+ const channels = input?.channelTypes ?? [];
94
+ return channels
95
+ .map((channelType) => {
96
+ const found = findGuardianForChannel(channelType);
97
+ if (!found) return null;
98
+ return {
99
+ channelType,
100
+ contactId: found.contact.id,
101
+ principalId: found.contact.principalId ?? null,
102
+ displayName: found.contact.displayName ?? null,
103
+ address: found.channel.address,
104
+ externalChatId: found.channel.externalChatId ?? null,
105
+ status: "active",
106
+ verifiedAt: found.channel.verifiedAt ?? null,
107
+ };
108
+ })
109
+ .filter((g) => g !== null);
110
+ };
111
+
112
+ mock.module("../contacts/guardian-delivery-reader.js", () => ({
113
+ getGuardianDelivery: resolveGuardianList,
114
+ getGuardianDeliveryFresh: resolveGuardianList,
115
+ guardianForChannel: (
116
+ list: Array<{ channelType: string; status: string }>,
117
+ channelType: string,
118
+ ) =>
119
+ list.find((g) => g.channelType === channelType && g.status === "active"),
120
+ }));
121
+
86
122
  import { handleChannelVerificationSession } from "../daemon/handlers/config-channels.js";
87
123
  import type {
88
124
  ChannelVerificationSessionRequest,
@@ -319,12 +355,12 @@ describe("guardian service challenge validation", () => {
319
355
  }
320
356
  });
321
357
 
322
- test("validateAndConsumeVerification does not create a guardian binding (caller responsibility)", () => {
358
+ test("validateAndConsumeVerification does not create a guardian binding (caller responsibility)", async () => {
323
359
  const { secret } = createInboundVerificationSession("telegram");
324
360
 
325
361
  validateAndConsumeVerification("telegram", secret, "user-42", "chat-42");
326
362
 
327
- const binding = getGuardianBinding("asst-1", "telegram");
363
+ const binding = await getGuardianBinding("asst-1", "telegram");
328
364
  expect(binding).toBeNull();
329
365
  });
330
366
 
@@ -396,7 +432,7 @@ describe("guardian service challenge validation", () => {
396
432
  expect(result2.success).toBe(false);
397
433
  });
398
434
 
399
- test("validateAndConsumeVerification succeeds with voice channel", () => {
435
+ test("validateAndConsumeVerification succeeds with voice channel", async () => {
400
436
  const { secret } = createInboundVerificationSession("phone");
401
437
 
402
438
  const result = validateAndConsumeVerification(
@@ -413,7 +449,7 @@ describe("guardian service challenge validation", () => {
413
449
 
414
450
  // validateAndConsumeVerification no longer creates bindings — that is
415
451
  // now handled by the gateway's verification intercepts.
416
- const binding = getGuardianBinding("asst-1", "phone");
452
+ const binding = await getGuardianBinding("asst-1", "phone");
417
453
  expect(binding).toBeNull();
418
454
  });
419
455
 
@@ -449,7 +485,7 @@ describe("guardian service challenge validation", () => {
449
485
  expect(telegramResult.success).toBe(true);
450
486
  });
451
487
 
452
- test("validateAndConsumeVerification succeeds even with existing binding (conflict check is caller responsibility)", () => {
488
+ test("validateAndConsumeVerification succeeds even with existing binding (conflict check is caller responsibility)", async () => {
453
489
  // Create initial guardian binding
454
490
  createGuardianBinding({
455
491
  channel: "telegram",
@@ -458,7 +494,7 @@ describe("guardian service challenge validation", () => {
458
494
  guardianDeliveryChatId: "old-chat",
459
495
  });
460
496
 
461
- const oldBinding = getGuardianBinding("asst-1", "telegram");
497
+ const oldBinding = await getGuardianBinding("asst-1", "telegram");
462
498
  expect(oldBinding).not.toBeNull();
463
499
  expect(oldBinding!.guardianExternalUserId).toBe("old-user");
464
500
 
@@ -473,7 +509,7 @@ describe("guardian service challenge validation", () => {
473
509
  // Challenge validation succeeds — the caller decides how to handle binding conflicts
474
510
  expect(result.success).toBe(true);
475
511
 
476
- const binding = getGuardianBinding("asst-1", "telegram");
512
+ const binding = await getGuardianBinding("asst-1", "telegram");
477
513
  expect(binding).not.toBeNull();
478
514
  expect(binding!.guardianExternalUserId).toBe("old-user");
479
515
  });
@@ -488,7 +524,7 @@ describe("guardian identity check", () => {
488
524
  resetTables();
489
525
  });
490
526
 
491
- test("isGuardian returns true for matching user", () => {
527
+ test("isGuardian returns true for matching user", async () => {
492
528
  createGuardianBinding({
493
529
  channel: "telegram",
494
530
  guardianExternalUserId: "user-42",
@@ -496,10 +532,10 @@ describe("guardian identity check", () => {
496
532
  guardianDeliveryChatId: "chat-42",
497
533
  });
498
534
 
499
- expect(isGuardian("asst-1", "telegram", "user-42")).toBe(true);
535
+ expect(await isGuardian("asst-1", "telegram", "user-42")).toBe(true);
500
536
  });
501
537
 
502
- test("isGuardian returns false for non-matching user", () => {
538
+ test("isGuardian returns false for non-matching user", async () => {
503
539
  createGuardianBinding({
504
540
  channel: "telegram",
505
541
  guardianExternalUserId: "user-42",
@@ -507,14 +543,14 @@ describe("guardian identity check", () => {
507
543
  guardianDeliveryChatId: "chat-42",
508
544
  });
509
545
 
510
- expect(isGuardian("asst-1", "telegram", "user-99")).toBe(false);
546
+ expect(await isGuardian("asst-1", "telegram", "user-99")).toBe(false);
511
547
  });
512
548
 
513
- test("isGuardian returns false when no binding exists", () => {
514
- expect(isGuardian("asst-1", "telegram", "user-42")).toBe(false);
549
+ test("isGuardian returns false when no binding exists", async () => {
550
+ expect(await isGuardian("asst-1", "telegram", "user-42")).toBe(false);
515
551
  });
516
552
 
517
- test("isGuardian returns false after binding is revoked", () => {
553
+ test("isGuardian returns false after binding is revoked", async () => {
518
554
  createGuardianBinding({
519
555
  channel: "telegram",
520
556
  guardianExternalUserId: "user-42",
@@ -524,10 +560,10 @@ describe("guardian identity check", () => {
524
560
 
525
561
  serviceRevokeBinding("asst-1", "telegram");
526
562
 
527
- expect(isGuardian("asst-1", "telegram", "user-42")).toBe(false);
563
+ expect(await isGuardian("asst-1", "telegram", "user-42")).toBe(false);
528
564
  });
529
565
 
530
- test("getGuardianBinding returns the active binding", () => {
566
+ test("getGuardianBinding returns the active binding", async () => {
531
567
  createGuardianBinding({
532
568
  channel: "telegram",
533
569
  guardianExternalUserId: "user-42",
@@ -535,17 +571,17 @@ describe("guardian identity check", () => {
535
571
  guardianDeliveryChatId: "chat-42",
536
572
  });
537
573
 
538
- const binding = getGuardianBinding("asst-1", "telegram");
574
+ const binding = await getGuardianBinding("asst-1", "telegram");
539
575
  expect(binding).not.toBeNull();
540
576
  expect(binding!.guardianExternalUserId).toBe("user-42");
541
577
  });
542
578
 
543
- test("getGuardianBinding returns null when no binding exists", () => {
544
- const binding = getGuardianBinding("asst-1", "telegram");
579
+ test("getGuardianBinding returns null when no binding exists", async () => {
580
+ const binding = await getGuardianBinding("asst-1", "telegram");
545
581
  expect(binding).toBeNull();
546
582
  });
547
583
 
548
- test("isGuardian works for voice channel", () => {
584
+ test("isGuardian works for voice channel", async () => {
549
585
  createGuardianBinding({
550
586
  channel: "phone",
551
587
  guardianExternalUserId: "phone-user-1",
@@ -553,13 +589,13 @@ describe("guardian identity check", () => {
553
589
  guardianDeliveryChatId: "voice-chat-1",
554
590
  });
555
591
 
556
- expect(isGuardian("asst-1", "phone", "phone-user-1")).toBe(true);
557
- expect(isGuardian("asst-1", "phone", "phone-user-2")).toBe(false);
592
+ expect(await isGuardian("asst-1", "phone", "phone-user-1")).toBe(true);
593
+ expect(await isGuardian("asst-1", "phone", "phone-user-2")).toBe(false);
558
594
  // Telegram guardian should not match voice channel
559
- expect(isGuardian("asst-1", "telegram", "phone-user-1")).toBe(false);
595
+ expect(await isGuardian("asst-1", "telegram", "phone-user-1")).toBe(false);
560
596
  });
561
597
 
562
- test("serviceRevokeBinding revokes the active binding", () => {
598
+ test("serviceRevokeBinding revokes the active binding", async () => {
563
599
  createGuardianBinding({
564
600
  channel: "telegram",
565
601
  guardianExternalUserId: "user-42",
@@ -569,7 +605,7 @@ describe("guardian identity check", () => {
569
605
 
570
606
  const result = serviceRevokeBinding("asst-1", "telegram");
571
607
  expect(result).toBe(true);
572
- expect(getGuardianBinding("asst-1", "telegram")).toBeNull();
608
+ expect(await getGuardianBinding("asst-1", "telegram")).toBeNull();
573
609
  });
574
610
  });
575
611
 
@@ -860,7 +896,7 @@ describe("channel-scoped guardian resolution", () => {
860
896
  resetTables();
861
897
  });
862
898
 
863
- test("isGuardian resolves independently per channel", () => {
899
+ test("isGuardian resolves independently per channel", async () => {
864
900
  // Create guardian binding on telegram
865
901
  createGuardianBinding({
866
902
  channel: "telegram",
@@ -877,15 +913,15 @@ describe("channel-scoped guardian resolution", () => {
877
913
  });
878
914
 
879
915
  // user-alpha is guardian for telegram but not voice
880
- expect(isGuardian("self", "telegram", "user-alpha")).toBe(true);
881
- expect(isGuardian("self", "phone", "user-alpha")).toBe(false);
916
+ expect(await isGuardian("self", "telegram", "user-alpha")).toBe(true);
917
+ expect(await isGuardian("self", "phone", "user-alpha")).toBe(false);
882
918
 
883
919
  // user-beta is guardian for voice but not telegram
884
- expect(isGuardian("self", "phone", "user-beta")).toBe(true);
885
- expect(isGuardian("self", "telegram", "user-beta")).toBe(false);
920
+ expect(await isGuardian("self", "phone", "user-beta")).toBe(true);
921
+ expect(await isGuardian("self", "telegram", "user-beta")).toBe(false);
886
922
  });
887
923
 
888
- test("getGuardianBinding returns different bindings for different channels", () => {
924
+ test("getGuardianBinding returns different bindings for different channels", async () => {
889
925
  createGuardianBinding({
890
926
  channel: "telegram",
891
927
  guardianExternalUserId: "user-alpha",
@@ -899,8 +935,8 @@ describe("channel-scoped guardian resolution", () => {
899
935
  guardianDeliveryChatId: "chat-beta",
900
936
  });
901
937
 
902
- const bindingTelegram = getGuardianBinding("self", "telegram");
903
- const bindingVoice = getGuardianBinding("self", "phone");
938
+ const bindingTelegram = await getGuardianBinding("self", "telegram");
939
+ const bindingVoice = await getGuardianBinding("self", "phone");
904
940
 
905
941
  expect(bindingTelegram).not.toBeNull();
906
942
  expect(bindingVoice).not.toBeNull();
@@ -908,7 +944,7 @@ describe("channel-scoped guardian resolution", () => {
908
944
  expect(bindingVoice!.guardianExternalUserId).toBe("user-beta");
909
945
  });
910
946
 
911
- test("revoking binding for one channel does not affect another", () => {
947
+ test("revoking binding for one channel does not affect another", async () => {
912
948
  createGuardianBinding({
913
949
  channel: "telegram",
914
950
  guardianExternalUserId: "user-alpha",
@@ -924,11 +960,11 @@ describe("channel-scoped guardian resolution", () => {
924
960
 
925
961
  serviceRevokeBinding("self", "telegram");
926
962
 
927
- expect(getGuardianBinding("self", "telegram")).toBeNull();
928
- expect(getGuardianBinding("self", "phone")).not.toBeNull();
963
+ expect(await getGuardianBinding("self", "telegram")).toBeNull();
964
+ expect(await getGuardianBinding("self", "phone")).not.toBeNull();
929
965
  });
930
966
 
931
- test("validateAndConsumeVerification scoped to channel", () => {
967
+ test("validateAndConsumeVerification scoped to channel", async () => {
932
968
  // Create challenge on telegram
933
969
  const { secret: secretTelegram } =
934
970
  createInboundVerificationSession("telegram");
@@ -961,8 +997,8 @@ describe("channel-scoped guardian resolution", () => {
961
997
  );
962
998
  expect(resultVoice.success).toBe(true);
963
999
 
964
- const bindingTelegram = getGuardianBinding("self", "telegram");
965
- const bindingVoice = getGuardianBinding("self", "phone");
1000
+ const bindingTelegram = await getGuardianBinding("self", "telegram");
1001
+ const bindingVoice = await getGuardianBinding("self", "phone");
966
1002
  expect(bindingTelegram).toBeNull();
967
1003
  expect(bindingVoice).toBeNull();
968
1004
  });
@@ -1277,7 +1313,7 @@ describe("voice guardian challenge validation", () => {
1277
1313
  }
1278
1314
  });
1279
1315
 
1280
- test("validateAndConsumeVerification does not create a guardian binding for voice (caller responsibility)", () => {
1316
+ test("validateAndConsumeVerification does not create a guardian binding for voice (caller responsibility)", async () => {
1281
1317
  const { secret } = createInboundVerificationSession("phone");
1282
1318
 
1283
1319
  validateAndConsumeVerification(
@@ -1287,7 +1323,7 @@ describe("voice guardian challenge validation", () => {
1287
1323
  "voice-chat-1",
1288
1324
  );
1289
1325
 
1290
- const binding = getGuardianBinding("asst-1", "phone");
1326
+ const binding = await getGuardianBinding("asst-1", "phone");
1291
1327
  expect(binding).toBeNull();
1292
1328
  });
1293
1329
 
@@ -1361,7 +1397,7 @@ describe("voice guardian challenge validation", () => {
1361
1397
  expect(result2.success).toBe(false);
1362
1398
  });
1363
1399
 
1364
- test("validateAndConsumeVerification succeeds even with existing voice binding (conflict check is caller responsibility)", () => {
1400
+ test("validateAndConsumeVerification succeeds even with existing voice binding (conflict check is caller responsibility)", async () => {
1365
1401
  createGuardianBinding({
1366
1402
  channel: "phone",
1367
1403
  guardianExternalUserId: "old-voice-user",
@@ -1369,7 +1405,7 @@ describe("voice guardian challenge validation", () => {
1369
1405
  guardianDeliveryChatId: "old-voice-chat",
1370
1406
  });
1371
1407
 
1372
- const oldBinding = getGuardianBinding("asst-1", "phone");
1408
+ const oldBinding = await getGuardianBinding("asst-1", "phone");
1373
1409
  expect(oldBinding).not.toBeNull();
1374
1410
  expect(oldBinding!.guardianExternalUserId).toBe("old-voice-user");
1375
1411
 
@@ -1385,7 +1421,7 @@ describe("voice guardian challenge validation", () => {
1385
1421
  expect(result.success).toBe(true);
1386
1422
 
1387
1423
  // The original binding is untouched (no side effects)
1388
- const binding = getGuardianBinding("asst-1", "phone");
1424
+ const binding = await getGuardianBinding("asst-1", "phone");
1389
1425
  expect(binding).not.toBeNull();
1390
1426
  expect(binding!.guardianExternalUserId).toBe("old-voice-user");
1391
1427
  });
@@ -1400,7 +1436,7 @@ describe("voice guardian identity and revocation", () => {
1400
1436
  resetTables();
1401
1437
  });
1402
1438
 
1403
- test("isGuardian works for voice channel", () => {
1439
+ test("isGuardian works for voice channel", async () => {
1404
1440
  createGuardianBinding({
1405
1441
  channel: "phone",
1406
1442
  guardianExternalUserId: "voice-user-1",
@@ -1408,13 +1444,13 @@ describe("voice guardian identity and revocation", () => {
1408
1444
  guardianDeliveryChatId: "voice-chat-1",
1409
1445
  });
1410
1446
 
1411
- expect(isGuardian("asst-1", "phone", "voice-user-1")).toBe(true);
1412
- expect(isGuardian("asst-1", "phone", "voice-user-2")).toBe(false);
1447
+ expect(await isGuardian("asst-1", "phone", "voice-user-1")).toBe(true);
1448
+ expect(await isGuardian("asst-1", "phone", "voice-user-2")).toBe(false);
1413
1449
  // Voice guardian should not match telegram channel
1414
- expect(isGuardian("asst-1", "telegram", "voice-user-1")).toBe(false);
1450
+ expect(await isGuardian("asst-1", "telegram", "voice-user-1")).toBe(false);
1415
1451
  });
1416
1452
 
1417
- test("getGuardianBinding returns voice binding", () => {
1453
+ test("getGuardianBinding returns voice binding", async () => {
1418
1454
  createGuardianBinding({
1419
1455
  channel: "phone",
1420
1456
  guardianExternalUserId: "voice-user-1",
@@ -1422,13 +1458,13 @@ describe("voice guardian identity and revocation", () => {
1422
1458
  guardianDeliveryChatId: "voice-chat-1",
1423
1459
  });
1424
1460
 
1425
- const binding = getGuardianBinding("asst-1", "phone");
1461
+ const binding = await getGuardianBinding("asst-1", "phone");
1426
1462
  expect(binding).not.toBeNull();
1427
1463
  expect(binding!.channel).toBe("phone");
1428
1464
  expect(binding!.guardianExternalUserId).toBe("voice-user-1");
1429
1465
  });
1430
1466
 
1431
- test("revokeBinding clears active voice guardian binding", () => {
1467
+ test("revokeBinding clears active voice guardian binding", async () => {
1432
1468
  createGuardianBinding({
1433
1469
  channel: "phone",
1434
1470
  guardianExternalUserId: "voice-user-1",
@@ -1438,10 +1474,10 @@ describe("voice guardian identity and revocation", () => {
1438
1474
 
1439
1475
  const result = serviceRevokeBinding("asst-1", "phone");
1440
1476
  expect(result).toBe(true);
1441
- expect(getGuardianBinding("asst-1", "phone")).toBeNull();
1477
+ expect(await getGuardianBinding("asst-1", "phone")).toBeNull();
1442
1478
  });
1443
1479
 
1444
- test("revokeBinding for voice does not affect telegram binding", () => {
1480
+ test("revokeBinding for voice does not affect telegram binding", async () => {
1445
1481
  createGuardianBinding({
1446
1482
  channel: "phone",
1447
1483
  guardianExternalUserId: "voice-user-1",
@@ -1457,8 +1493,8 @@ describe("voice guardian identity and revocation", () => {
1457
1493
 
1458
1494
  serviceRevokeBinding("asst-1", "phone");
1459
1495
 
1460
- expect(getGuardianBinding("asst-1", "phone")).toBeNull();
1461
- expect(getGuardianBinding("asst-1", "telegram")).not.toBeNull();
1496
+ expect(await getGuardianBinding("asst-1", "phone")).toBeNull();
1497
+ expect(await getGuardianBinding("asst-1", "telegram")).not.toBeNull();
1462
1498
  });
1463
1499
  });
1464
1500
 
@@ -1716,7 +1752,7 @@ describe("HTTP handler voice guardian verification", () => {
1716
1752
  expect(resp!.bound).toBe(false);
1717
1753
 
1718
1754
  // Verify binding is actually revoked
1719
- expect(getGuardianBinding("self", "phone")).toBeNull();
1755
+ expect(await getGuardianBinding("self", "phone")).toBeNull();
1720
1756
  });
1721
1757
 
1722
1758
  test("revoke for voice does not affect telegram binding", async () => {
@@ -1742,8 +1778,8 @@ describe("HTTP handler voice guardian verification", () => {
1742
1778
 
1743
1779
  await handleChannelVerificationSession(msg);
1744
1780
 
1745
- expect(getGuardianBinding("self", "phone")).toBeNull();
1746
- expect(getGuardianBinding("self", "telegram")).not.toBeNull();
1781
+ expect(await getGuardianBinding("self", "phone")).toBeNull();
1782
+ expect(await getGuardianBinding("self", "telegram")).not.toBeNull();
1747
1783
  });
1748
1784
  });
1749
1785
 
@@ -81,6 +81,8 @@ mock.module("../runtime/gateway-client.js", () => ({
81
81
  }));
82
82
 
83
83
  mock.module("../memory/conversation-crud.js", () => ({
84
+ setConversationProcessingStartedAt: () => {},
85
+ isConversationProcessing: () => false,
84
86
  setConversationOriginChannelIfUnset: () => {},
85
87
  updateConversationContextWindow: () => {},
86
88
  deleteMessageById: () => {},
@@ -125,6 +125,8 @@ mock.module("../security/secret-allowlist.js", () => ({
125
125
  }));
126
126
 
127
127
  mock.module("../memory/conversation-crud.js", () => ({
128
+ setConversationProcessingStartedAt: () => {},
129
+ isConversationProcessing: () => false,
128
130
  setConversationOriginChannelIfUnset: () => {},
129
131
  updateConversationContextWindow: () => {},
130
132
  setConversationHistoryStrippedAt: () => {},
@@ -17,6 +17,8 @@ mock.module("../util/logger.js", () => ({
17
17
  // The compactor reads the conversation's image attachments from the DB to
18
18
  // build its manifest; with no images these return empty.
19
19
  mock.module("../memory/conversation-crud.js", () => ({
20
+ setConversationProcessingStartedAt: () => {},
21
+ isConversationProcessing: () => false,
20
22
  getMessages: () => [],
21
23
  }));
22
24
  mock.module("../memory/attachments-store.js", () => ({
@@ -30,6 +30,8 @@ mock.module("../util/logger.js", () => ({
30
30
  }));
31
31
 
32
32
  mock.module("../memory/conversation-crud.js", () => ({
33
+ setConversationProcessingStartedAt: () => {},
34
+ isConversationProcessing: () => false,
33
35
  getMessages: () => [],
34
36
  reserveMessage: mock(async () => ({ id: "msg-reserve" })),
35
37
  }));