@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
@@ -40,10 +40,27 @@ export interface LlmContextSection {
40
40
  language?: string;
41
41
  }
42
42
 
43
+ /**
44
+ * Structured error extracted from a rejected call's response payload.
45
+ * Mirrors the on-disk `responsePayload.error` shape produced by
46
+ * `buildProviderErrorResponsePayload`. Present only when the call failed
47
+ * before returning a response — consumers branch on its presence to
48
+ * render the call as failed.
49
+ */
50
+ export interface LlmContextError {
51
+ name?: string;
52
+ message?: string;
53
+ code?: string;
54
+ provider?: string;
55
+ statusCode?: number;
56
+ retryAfterMs?: number;
57
+ }
58
+
43
59
  export interface LlmContextNormalizationResult {
44
60
  summary?: LlmContextSummary;
45
61
  requestSections?: LlmContextSection[];
46
62
  responseSections?: LlmContextSection[];
63
+ error?: LlmContextError;
47
64
  }
48
65
 
49
66
  interface NormalizedPayloadCandidate {
@@ -55,6 +72,60 @@ interface NormalizedPayloadCandidate {
55
72
 
56
73
  export function normalizeLlmContextPayloads(
57
74
  input: LlmContextNormalizationInput,
75
+ ): LlmContextNormalizationResult {
76
+ const base = normalizeSuccessPayloads(input);
77
+ const error = normalizeProviderErrorPayload(input.responsePayload);
78
+ if (!error) {
79
+ return base;
80
+ }
81
+ // A rejected call has no response sections to render — only the request
82
+ // side normalized. Attach the structured error so the inspector can show
83
+ // a failure banner and treat the cost as $0.00 instead of silently
84
+ // falling back to "section rendering unavailable".
85
+ return { ...base, error };
86
+ }
87
+
88
+ /**
89
+ * Detect a provider/transport error stored in the response payload.
90
+ *
91
+ * Error rows are written as `{ error: { name, message, code?, provider?,
92
+ * statusCode?, retryAfterMs? } }` by `buildProviderErrorResponsePayload`.
93
+ * Successful provider responses never carry a top-level `error` object, so
94
+ * the presence of one (with at least one identifying field) is a reliable
95
+ * signal that the call failed.
96
+ */
97
+ function normalizeProviderErrorPayload(
98
+ responsePayload: unknown,
99
+ ): LlmContextError | null {
100
+ const error = asRecord(asRecord(responsePayload)?.error);
101
+ if (!error) {
102
+ return null;
103
+ }
104
+
105
+ const name = asString(error.name);
106
+ const message = asString(error.message);
107
+ const code = asString(error.code);
108
+ // Require at least one identifying field so an unrelated `error` key on a
109
+ // success payload isn't misread as a provider failure.
110
+ if (name === undefined && message === undefined && code === undefined) {
111
+ return null;
112
+ }
113
+
114
+ const normalized: LlmContextError = {};
115
+ if (name !== undefined) normalized.name = name;
116
+ if (message !== undefined) normalized.message = message;
117
+ if (code !== undefined) normalized.code = code;
118
+ const provider = asString(error.provider);
119
+ if (provider !== undefined) normalized.provider = provider;
120
+ const statusCode = asNumber(error.statusCode);
121
+ if (statusCode !== undefined) normalized.statusCode = statusCode;
122
+ const retryAfterMs = asNumber(error.retryAfterMs);
123
+ if (retryAfterMs !== undefined) normalized.retryAfterMs = retryAfterMs;
124
+ return normalized;
125
+ }
126
+
127
+ function normalizeSuccessPayloads(
128
+ input: LlmContextNormalizationInput,
58
129
  ): LlmContextNormalizationResult {
59
130
  const requestCandidates = [
60
131
  normalizeOpenAiRequestPayload(input.requestPayload),
@@ -24,7 +24,7 @@ import { getMcpAuthState } from "../../mcp/mcp-auth-state.js";
24
24
  import { deleteMcpOAuthCredentials } from "../../mcp/mcp-oauth-provider.js";
25
25
  import { getMcpToolsByServer } from "../../tools/registry.js";
26
26
  import { getLogger } from "../../util/logger.js";
27
- import { ACTOR_PRINCIPALS, GATEWAY_PRINCIPALS } from "../auth/route-policy.js";
27
+ import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
28
28
  import { BadRequestError, InternalError, NotFoundError } from "./errors.js";
29
29
  import type { RouteDefinition } from "./types.js";
30
30
 
@@ -281,6 +281,7 @@ async function handleMcpUpdate({
281
281
  maxTools,
282
282
  allowedTools,
283
283
  blockedTools,
284
+ headers,
284
285
  } = body as {
285
286
  name: string;
286
287
  enabled?: boolean;
@@ -288,6 +289,7 @@ async function handleMcpUpdate({
288
289
  maxTools?: number;
289
290
  allowedTools?: string[] | null;
290
291
  blockedTools?: string[] | null;
292
+ headers?: Record<string, string> | null;
291
293
  };
292
294
 
293
295
  const raw = loadRawConfig();
@@ -326,6 +328,19 @@ async function handleMcpUpdate({
326
328
  server.blockedTools = blockedTools;
327
329
  }
328
330
  }
331
+ if (headers !== undefined) {
332
+ const transport = server.transport as Record<string, unknown> | undefined;
333
+ if (
334
+ transport &&
335
+ (transport.type === "sse" || transport.type === "streamable-http")
336
+ ) {
337
+ if (headers === null || Object.keys(headers).length === 0) {
338
+ delete transport.headers;
339
+ } else {
340
+ transport.headers = headers;
341
+ }
342
+ }
343
+ }
329
344
 
330
345
  saveRawConfig(raw);
331
346
  triggerReload("internal_mcp_update");
@@ -342,15 +357,17 @@ async function handleMcpAdd({
342
357
  }: {
343
358
  body?: Record<string, unknown>;
344
359
  }): Promise<{ added: true }> {
345
- const { name, transportType, url, command, args, risk, disabled } = body as {
346
- name: string;
347
- transportType: string;
348
- url?: string;
349
- command?: string;
350
- args?: string[];
351
- risk?: string;
352
- disabled?: boolean;
353
- };
360
+ const { name, transportType, url, command, args, risk, disabled, headers } =
361
+ body as {
362
+ name: string;
363
+ transportType: string;
364
+ url?: string;
365
+ command?: string;
366
+ args?: string[];
367
+ risk?: string;
368
+ disabled?: boolean;
369
+ headers?: Record<string, string>;
370
+ };
354
371
 
355
372
  const riskLevel = risk ?? "high";
356
373
  if (!["low", "medium", "high"].includes(riskLevel)) {
@@ -374,7 +391,11 @@ async function handleMcpAdd({
374
391
  `--url is required for ${transportType} transport`,
375
392
  );
376
393
  }
377
- transport = { type: transportType, url };
394
+ transport = {
395
+ type: transportType,
396
+ url,
397
+ ...(headers && Object.keys(headers).length > 0 ? { headers } : {}),
398
+ };
378
399
  break;
379
400
  default:
380
401
  throw new BadRequestError(
@@ -455,8 +476,8 @@ export const ROUTES: RouteDefinition[] = [
455
476
  endpoint: "internal/mcp/auth/start",
456
477
  method: "POST",
457
478
  policy: {
458
- requiredScopes: ["internal.write"],
459
- allowedPrincipalTypes: GATEWAY_PRINCIPALS,
479
+ requiredScopes: ["settings.write"],
480
+ allowedPrincipalTypes: ACTOR_PRINCIPALS,
460
481
  },
461
482
  summary: "Start MCP OAuth flow",
462
483
  description:
@@ -470,8 +491,8 @@ export const ROUTES: RouteDefinition[] = [
470
491
  endpoint: "internal/mcp/auth/status/:serverId",
471
492
  method: "GET",
472
493
  policy: {
473
- requiredScopes: ["internal.write"],
474
- allowedPrincipalTypes: GATEWAY_PRINCIPALS,
494
+ requiredScopes: ["settings.read"],
495
+ allowedPrincipalTypes: ACTOR_PRINCIPALS,
475
496
  },
476
497
  summary: "Poll MCP OAuth flow status",
477
498
  description:
@@ -579,6 +600,7 @@ export const ROUTES: RouteDefinition[] = [
579
600
  maxTools: z.number().optional(),
580
601
  allowedTools: z.array(z.string()).nullable().optional(),
581
602
  blockedTools: z.array(z.string()).nullable().optional(),
603
+ headers: z.record(z.string(), z.string()).nullable().optional(),
582
604
  }),
583
605
  handler: handleMcpUpdate,
584
606
  },
@@ -602,6 +624,7 @@ export const ROUTES: RouteDefinition[] = [
602
624
  args: z.array(z.string()).optional(),
603
625
  risk: z.string().optional(),
604
626
  disabled: z.boolean().optional(),
627
+ headers: z.record(z.string(), z.string()).optional(),
605
628
  }),
606
629
  handler: handleMcpAdd,
607
630
  },
@@ -10,8 +10,9 @@
10
10
  import { z } from "zod";
11
11
 
12
12
  import { getDb } from "../../memory/db-connection.js";
13
- import { getMaxMigrationVersion } from "../../memory/migrations/registry.js";
13
+ import { getMaxRollbackVersion } from "../../memory/migrations/run-migrations.js";
14
14
  import { rollbackMemoryMigration } from "../../memory/migrations/validate-migration-state.js";
15
+ import { migrationSteps } from "../../memory/steps.js";
15
16
  import { getWorkspaceDir } from "../../util/platform.js";
16
17
  import { WORKSPACE_MIGRATIONS } from "../../workspace/migrations/registry.js";
17
18
  import {
@@ -43,7 +44,7 @@ async function handleRollbackMigrations({ body = {} }: RouteHandlerArgs) {
43
44
 
44
45
  if (rollbackToRegistryCeiling === true) {
45
46
  if (effectiveDbVersion === undefined)
46
- effectiveDbVersion = getMaxMigrationVersion();
47
+ effectiveDbVersion = getMaxRollbackVersion(migrationSteps);
47
48
  if (effectiveWorkspaceMigrationId === undefined)
48
49
  effectiveWorkspaceMigrationId =
49
50
  getLastWorkspaceMigrationId(WORKSPACE_MIGRATIONS) ?? undefined;
@@ -104,7 +105,7 @@ async function handleRollbackMigrations({ body = {} }: RouteHandlerArgs) {
104
105
  // Roll back DB migrations if requested.
105
106
  if (effectiveDbVersion !== undefined) {
106
107
  try {
107
- rolledBack.db = rollbackMemoryMigration(getDb(), effectiveDbVersion);
108
+ rolledBack.db = rollbackMemoryMigration(getDb(), effectiveDbVersion, migrationSteps);
108
109
  } catch (err) {
109
110
  const detail = err instanceof Error ? err.message : "Unknown error";
110
111
  throw new InternalError(`DB migration rollback failed: ${detail}`);
@@ -28,9 +28,11 @@ import {
28
28
  getDb,
29
29
  getLogsSqlite,
30
30
  getMemorySqlite,
31
+ getTelemetrySqlite,
31
32
  resetDb,
32
33
  } from "../../memory/db-connection.js";
33
34
  import { validateMigrationState } from "../../memory/migrations/validate-migration-state.js";
35
+ import { migrationSteps } from "../../memory/steps.js";
34
36
  import { credentialKey } from "../../security/credential-key.js";
35
37
  import {
36
38
  bulkSetSecureKeysAsync,
@@ -180,6 +182,7 @@ async function checkpointDbsForExport(): Promise<void> {
180
182
  for (const [label, sqlite] of [
181
183
  ["logs", getLogsSqlite()],
182
184
  ["memory", getMemorySqlite()],
185
+ ["telemetry", getTelemetrySqlite()],
183
186
  ] as const) {
184
187
  if (!sqlite) {
185
188
  log.warn(
@@ -1712,7 +1715,7 @@ function appendNewerMigrationWarningsIfAny(report: ImportCommitReport): void {
1712
1715
  return;
1713
1716
  }
1714
1717
  try {
1715
- const migrationValidation = validateMigrationState(getDb());
1718
+ const migrationValidation = validateMigrationState(getDb(), migrationSteps);
1716
1719
  if (migrationValidation.unknownCheckpoints.length > 0) {
1717
1720
  report.warnings.push(
1718
1721
  `Imported data contains ${migrationValidation.unknownCheckpoints.length} migration(s) from a newer version. Some data may not be fully compatible.`,
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Route for programmatically scheduling the onboarding "Day 2 Check-in".
3
+ *
4
+ * POST /v1/onboarding/checkin — resolve the user's Google Calendar, find the
5
+ * first open 15-minute slot tomorrow afternoon, and book the check-in event.
6
+ *
7
+ * Called by the web `/onboarding/research` flow the moment Google Calendar
8
+ * OAuth lands. Best-effort by contract: a missing/insufficient calendar
9
+ * connection returns `{ scheduled: false }` rather than an error.
10
+ */
11
+
12
+ import { z } from "zod";
13
+
14
+ import { scheduleOnboardingCheckin } from "../../onboarding/schedule-checkin.js";
15
+ import { getLogger } from "../../util/logger.js";
16
+ import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
17
+ import { BadRequestError } from "./errors.js";
18
+ import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
19
+
20
+ const log = getLogger("onboarding-checkin-routes");
21
+
22
+ function asOptionalString(value: unknown, field: string): string | undefined {
23
+ if (value === undefined || value === null) return undefined;
24
+ if (typeof value !== "string") {
25
+ throw new BadRequestError(`${field} must be a string`);
26
+ }
27
+ const trimmed = value.trim();
28
+ return trimmed.length > 0 ? trimmed : undefined;
29
+ }
30
+
31
+ async function handleScheduleOnboardingCheckin({
32
+ body = {},
33
+ }: RouteHandlerArgs) {
34
+ // The shared route adapter does not runtime-validate the body against the
35
+ // Zod requestBody (codegen-only), so guard types before use.
36
+ const userName = asOptionalString(body.userName, "userName");
37
+ const assistantName = asOptionalString(body.assistantName, "assistantName");
38
+ const timeZone = asOptionalString(body.timezone, "timezone");
39
+
40
+ const result = await scheduleOnboardingCheckin({
41
+ userName,
42
+ assistantName,
43
+ timeZone,
44
+ });
45
+
46
+ if (!result.scheduled) {
47
+ log.info({ reason: result.reason }, "Onboarding check-in not scheduled");
48
+ }
49
+ return result;
50
+ }
51
+
52
+ const RESPONSE_SCHEMA = z.object({
53
+ scheduled: z.boolean(),
54
+ reason: z.string().optional(),
55
+ eventId: z.string().optional(),
56
+ htmlLink: z.string().nullable().optional(),
57
+ start: z.string().optional(),
58
+ end: z.string().optional(),
59
+ timeZone: z.string().optional(),
60
+ });
61
+
62
+ export const ROUTES: RouteDefinition[] = [
63
+ {
64
+ operationId: "scheduleOnboardingCheckin",
65
+ endpoint: "onboarding/checkin",
66
+ method: "POST",
67
+ policy: {
68
+ requiredScopes: ["chat.write"],
69
+ allowedPrincipalTypes: ACTOR_PRINCIPALS,
70
+ },
71
+ summary: "Schedule the onboarding Day 2 check-in",
72
+ description:
73
+ "Find the first open 15-minute slot between 12pm and 5pm tomorrow " +
74
+ "(widening to 8am–8pm if booked) on the user's Google Calendar and " +
75
+ "create the Day 2 Check-in event. Best-effort: returns scheduled=false " +
76
+ "when no calendar is connected or the calendar scope wasn't granted.",
77
+ tags: ["onboarding"],
78
+ requestBody: z.object({
79
+ userName: z.string().optional(),
80
+ assistantName: z.string().optional(),
81
+ timezone: z.string().optional(),
82
+ }),
83
+ responseBody: RESPONSE_SCHEMA,
84
+ handler: handleScheduleOnboardingCheckin,
85
+ },
86
+ ];
@@ -39,6 +39,8 @@ export interface SubagentDetailResult {
39
39
  toolName?: string;
40
40
  isError?: boolean;
41
41
  messageId?: string;
42
+ toolUseId?: string;
43
+ input?: Record<string, unknown>;
42
44
  }>;
43
45
  }
44
46
 
@@ -112,6 +114,8 @@ export function parseSubagentMessages(
112
114
  type: "tool_use",
113
115
  content: JSON.stringify(input),
114
116
  toolName: name,
117
+ toolUseId: id || undefined,
118
+ input,
115
119
  });
116
120
  if (id) pendingTools.set(id, name);
117
121
  } else if (
@@ -147,6 +151,7 @@ export function parseSubagentMessages(
147
151
  content: resultContent,
148
152
  toolName: toolName ?? "unknown",
149
153
  isError,
154
+ toolUseId: toolUseId || undefined,
150
155
  });
151
156
  }
152
157
  }
@@ -9,20 +9,17 @@
9
9
  */
10
10
  import { z } from "zod";
11
11
 
12
- import type { ChannelId } from "../../channels/types.js";
13
12
  import { isHttpAuthDisabled } from "../../config/env.js";
14
- import { findGuardianForChannel } from "../../contacts/contact-store.js";
13
+ import type { TrustContext } from "../../daemon/trust-context.js";
15
14
  import { getLogger } from "../../util/logger.js";
16
- import type { TrustClass } from "../actor-trust-resolver.js";
17
- import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
18
15
  import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
19
16
  import { processGuardianDecision } from "../guardian-action-service.js";
20
- import { healGuardianBindingDrift } from "../guardian-vellum-migration.js";
21
- import { resolveLocalTrustContext } from "../local-actor-identity.js";
17
+ import { reResolveTrustOnResetDrift } from "../guardian-vellum-migration.js";
22
18
  import {
23
- resolveTrustContext,
24
- withSourceChannel,
25
- } from "../trust-context-resolver.js";
19
+ findLocalGuardianPrincipalId,
20
+ resolveActorPrincipalIdForLocalGuardian,
21
+ } from "../local-actor-identity.js";
22
+ import { resolveLocalPrincipalTrustContext } from "../local-principal-trust.js";
26
23
  import { parseCallbackData } from "./channel-route-shared.js";
27
24
  import {
28
25
  BadRequestError,
@@ -40,58 +37,51 @@ const log = getLogger("surface-action-routes");
40
37
  // ---------------------------------------------------------------------------
41
38
 
42
39
  /**
43
- * Resolve trust context from the actor principal ID and set it on the
44
- * conversation, following the same pattern as POST /v1/messages. This ensures
45
- * surface actions inherit the correct trust class (guardian vs trusted_contact)
46
- * rather than defaulting to unknown.
40
+ * Resolve trust context for the actor principal from the gateway guardian
41
+ * binding and set it on the conversation. A vellum principal is the guardian or
42
+ * nobody, so the mapper yields guardian or unknown.
47
43
  */
48
- function applyTrustContext(
44
+ async function applyTrustContext(
49
45
  conversation: {
50
- setTrustContext?(ctx: {
51
- trustClass: TrustClass;
52
- sourceChannel: ChannelId;
53
- }): void;
46
+ setTrustContext?(ctx: TrustContext): void;
54
47
  },
55
48
  actorPrincipalId: string | undefined,
56
- ): void {
49
+ ): Promise<void> {
57
50
  if (!conversation.setTrustContext) return;
58
51
 
59
52
  const sourceChannel = "vellum";
60
53
 
61
- if (actorPrincipalId) {
62
- if (isHttpAuthDisabled() && actorPrincipalId === "dev-bypass") {
63
- conversation.setTrustContext(resolveLocalTrustContext(sourceChannel));
64
- } else {
65
- const assistantId = DAEMON_INTERNAL_ASSISTANT_ID;
66
- let trustCtx = resolveTrustContext({
67
- assistantId,
68
- sourceChannel,
69
- conversationExternalId: "local",
70
- actorExternalId: actorPrincipalId,
71
- });
72
- if (trustCtx.trustClass === "unknown") {
73
- const healed = healGuardianBindingDrift(actorPrincipalId);
74
- if (healed) {
75
- trustCtx = resolveTrustContext({
76
- assistantId,
77
- sourceChannel,
78
- conversationExternalId: "local",
79
- actorExternalId: actorPrincipalId,
80
- });
81
- log.info(
82
- {
83
- actorPrincipalId,
84
- trustClass: trustCtx.trustClass,
85
- },
86
- "Trust re-resolved after guardian binding drift heal (surface action)",
87
- );
88
- }
54
+ if (!actorPrincipalId) {
55
+ conversation.setTrustContext({ trustClass: "guardian", sourceChannel });
56
+ return;
57
+ }
58
+
59
+ // Dev-bypass injects a synthetic principal that won't match the real
60
+ // guardian binding, so resolve the actual guardian principalId (gateway
61
+ // first, local store fallback) before mapping trust.
62
+ let principalId = actorPrincipalId;
63
+ if (isHttpAuthDisabled() && actorPrincipalId === "dev-bypass") {
64
+ principalId = (await findLocalGuardianPrincipalId()) ?? actorPrincipalId;
65
+ }
66
+
67
+ let trustCtx = await resolveLocalPrincipalTrustContext({
68
+ actorPrincipalId: principalId,
69
+ sourceChannel,
70
+ conversationExternalId: "local",
71
+ });
72
+ if (trustCtx.trustClass === "unknown") {
73
+ const healed = await reResolveTrustOnResetDrift(principalId, sourceChannel);
74
+ if (healed) {
75
+ trustCtx = healed;
76
+ if (healed.trustClass !== "unknown") {
77
+ log.info(
78
+ { actorPrincipalId: principalId, trustClass: trustCtx.trustClass },
79
+ "Trust re-resolved from local mirror after gateway reset drift (surface action)",
80
+ );
89
81
  }
90
- conversation.setTrustContext(withSourceChannel(sourceChannel, trustCtx));
91
82
  }
92
- } else {
93
- conversation.setTrustContext({ trustClass: "guardian", sourceChannel });
94
83
  }
84
+ conversation.setTrustContext(trustCtx);
95
85
  }
96
86
 
97
87
  // ---------------------------------------------------------------------------
@@ -129,16 +119,15 @@ async function handleSurfaceAction({
129
119
  const aprDecision = parseCallbackData(actionId, "vellum");
130
120
  if (aprDecision) {
131
121
  // Resolve the actor's guardian principal ID. In dev mode the synthetic
132
- // "dev-bypass" principal won't match the real guardian binding, so fall
133
- // back to the local guardian binding — mirrors guardian-action-routes.ts.
122
+ // "dev-bypass" principal won't match the real guardian binding, so resolve
123
+ // the local guardian principal — mirrors guardian-action-routes.ts.
134
124
  let guardianPrincipalId: string | undefined =
135
125
  headers?.["x-vellum-actor-principal-id"] ?? undefined;
136
126
  if (
137
127
  isHttpAuthDisabled() &&
138
128
  headers?.["x-vellum-actor-principal-id"] === "dev-bypass"
139
129
  ) {
140
- const binding = findGuardianForChannel("vellum");
141
- guardianPrincipalId = binding?.contact.principalId ?? undefined;
130
+ guardianPrincipalId = (await findLocalGuardianPrincipalId()) ?? undefined;
142
131
  }
143
132
 
144
133
  const result = await processGuardianDecision({
@@ -186,13 +175,20 @@ async function handleSurfaceAction({
186
175
  }
187
176
 
188
177
  const actorPrincipalId = headers?.["x-vellum-actor-principal-id"];
189
- applyTrustContext(conversation, actorPrincipalId);
178
+ await applyTrustContext(conversation, actorPrincipalId);
179
+
180
+ // Translate dev-bypass → real guardian so the surface turn's principal matches
181
+ // the SSE host-proxy client's registered principal; otherwise CU/app-control
182
+ // same-actor checks reject the turn. Real principals pass through unchanged.
183
+ const resolvedActorPrincipalId =
184
+ await resolveActorPrincipalIdForLocalGuardian(actorPrincipalId ?? undefined);
190
185
 
191
186
  try {
192
187
  const raw = await conversation.handleSurfaceAction(
193
188
  surfaceId,
194
189
  actionId,
195
190
  data,
191
+ resolvedActorPrincipalId,
196
192
  );
197
193
  const result =
198
194
  raw && typeof raw === "object" && "accepted" in raw
@@ -48,6 +48,7 @@ function makeConversationRow(
48
48
  inferenceProfileSessionId: null,
49
49
  inferenceProfileExpiresAt: null,
50
50
  lastNotifiedInferenceProfile: null,
51
+ processingStartedAt: null,
51
52
  ...overrides,
52
53
  };
53
54
  }
@@ -9,7 +9,6 @@
9
9
 
10
10
  import { parseChannelId } from "../../channels/types.js";
11
11
  import { getConfig } from "../../config/loader.js";
12
- import { findConversation } from "../../daemon/conversation-registry.js";
13
12
  import { normalizeConversationType } from "../../daemon/message-types/shared.js";
14
13
  import {
15
14
  type AttentionState,
@@ -21,6 +20,7 @@ import {
21
20
  type ConversationRow,
22
21
  getConversation,
23
22
  getDisplayMetaForConversations,
23
+ isConversationProcessing,
24
24
  } from "../../memory/conversation-crud.js";
25
25
  import type { ExternalConversationBinding } from "../../memory/external-conversation-store.js";
26
26
  import { getBindingsForConversations } from "../../memory/external-conversation-store.js";
@@ -196,9 +196,9 @@ export function serializeConversationSummary(params: {
196
196
  parentCache: Map<string, ConversationRow | null>;
197
197
  /**
198
198
  * Whether the agent loop is currently mid-turn for this conversation.
199
- * Sourced from the in-memory daemon `Conversation.isProcessing()` flag
200
- * callers resolve via `findConversation(id)?.isProcessing() ?? false`
201
- * so cold (evicted / never-loaded) rows report `false`. Plumbed in
199
+ * Resolved by `isConversationProcessing(id)`, which checks the in-memory
200
+ * daemon flag first and falls back to the persisted
201
+ * `processing_started_at` column for cold conversations. Plumbed in
202
202
  * rather than read here so the serializer stays a pure shape mapper
203
203
  * with no daemon-store coupling.
204
204
  */
@@ -286,11 +286,9 @@ export function buildConversationDetailResponse(
286
286
  attentionState: attentionStates.get(conversation.id),
287
287
  displayMeta: displayMeta.get(conversation.id),
288
288
  parentCache,
289
- // Cold (evicted / never-loaded) rows aren't in the in-memory
290
- // store, so `findConversation` returns `undefined` and they
291
- // report `isProcessing: false` — by definition they aren't
292
- // mid-turn since the agent loop only runs on resident convs.
293
- isProcessing: findConversation(conversation.id)?.isProcessing() ?? false,
289
+ // Checks in-memory flag first (hot path), falls back to the
290
+ // persisted `processing_started_at` column for cold conversations.
291
+ isProcessing: isConversationProcessing(conversation.id),
294
292
  }),
295
293
  };
296
294
  }
@@ -59,9 +59,9 @@ export type ToolGrantRequestResult =
59
59
  * Returns a result indicating whether a new request was created, an existing
60
60
  * one was deduped, or the escalation failed (no binding, missing identity).
61
61
  */
62
- export function createOrReuseToolGrantRequest(
62
+ export async function createOrReuseToolGrantRequest(
63
63
  params: ToolGrantRequestParams,
64
- ): ToolGrantRequestResult {
64
+ ): Promise<ToolGrantRequestResult> {
65
65
  const {
66
66
  assistantId,
67
67
  sourceChannel,
@@ -78,7 +78,7 @@ export function createOrReuseToolGrantRequest(
78
78
  return { failed: true, reason: "missing_identity" };
79
79
  }
80
80
 
81
- const binding = getGuardianBinding(assistantId, sourceChannel);
81
+ const binding = await getGuardianBinding(assistantId, sourceChannel);
82
82
  if (!binding) {
83
83
  log.debug(
84
84
  { sourceChannel, assistantId },