@vellumai/assistant 0.9.1-staging.1 → 0.10.0-staging.2

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 (433) hide show
  1. package/docs/activation-funnel-telemetry.md +24 -18
  2. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
  3. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  4. package/node_modules/@vellumai/gateway-client/src/index.ts +15 -0
  5. package/openapi.yaml +852 -15
  6. package/package.json +1 -1
  7. package/src/__tests__/access-request-card-view.test.ts +98 -0
  8. package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
  9. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +59 -7
  10. package/src/__tests__/agent-loop-compaction-strip.test.ts +17 -16
  11. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
  12. package/src/__tests__/app-compiler.test.ts +15 -1
  13. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  14. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  15. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  16. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  17. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  18. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  19. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  20. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  21. package/src/__tests__/config-loader-backfill.test.ts +174 -30
  22. package/src/__tests__/config-schema.test.ts +35 -0
  23. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  24. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  25. package/src/__tests__/contacts-tools.test.ts +29 -0
  26. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  27. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  28. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  29. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  30. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  31. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  32. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  33. package/src/__tests__/conversation-title-service.test.ts +62 -0
  34. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  35. package/src/__tests__/credential-prompt-route.test.ts +1 -0
  36. package/src/__tests__/credential-security-invariants.test.ts +2 -0
  37. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  38. package/src/__tests__/disk-usage.test.ts +65 -0
  39. package/src/__tests__/dynamic-page-surface.test.ts +51 -0
  40. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  41. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  42. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  43. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  44. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  45. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  46. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  47. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  48. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  49. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  50. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  51. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  52. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  53. package/src/__tests__/invite-redemption-service.test.ts +0 -3
  54. package/src/__tests__/llm-catalog-parity.test.ts +30 -1
  55. package/src/__tests__/llm-resolver.test.ts +21 -0
  56. package/src/__tests__/llm-schema.test.ts +1 -0
  57. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  58. package/src/__tests__/mcp-health-check.test.ts +6 -7
  59. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  60. package/src/__tests__/path-policy.test.ts +34 -0
  61. package/src/__tests__/persona-resolver.test.ts +38 -0
  62. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  63. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  64. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  65. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  66. package/src/__tests__/reaction-persistence.test.ts +150 -29
  67. package/src/__tests__/relay-server.test.ts +285 -0
  68. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  69. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  70. package/src/__tests__/skill-execute-input.test.ts +5 -0
  71. package/src/__tests__/skills.test.ts +51 -0
  72. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  73. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  74. package/src/__tests__/subagent-tools.test.ts +150 -0
  75. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  76. package/src/__tests__/title-generate-hook.test.ts +100 -3
  77. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  78. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  79. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  80. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  81. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  82. package/src/__tests__/trusted-contact-verification.test.ts +2 -4
  83. package/src/__tests__/twilio-routes.test.ts +81 -1
  84. package/src/__tests__/voice-invite-redemption.test.ts +0 -1
  85. package/src/__tests__/weak-open-model.test.ts +30 -0
  86. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  87. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  88. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  89. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  90. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  91. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  92. package/src/agent/loop.ts +33 -33
  93. package/src/api/events/tool-result.ts +6 -0
  94. package/src/api/events/workflow-completed.ts +53 -0
  95. package/src/api/events/workflow-leaf-finished.ts +38 -0
  96. package/src/api/events/workflow-leaf-started.ts +35 -0
  97. package/src/api/events/workflow-progress.ts +32 -0
  98. package/src/api/events/workflow-started.ts +31 -0
  99. package/src/api/index.ts +40 -0
  100. package/src/api/responses/conversation-message.ts +26 -0
  101. package/src/api/responses/home.ts +26 -0
  102. package/src/api/responses/workflow-journal.ts +53 -0
  103. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  104. package/src/approvals/guardian-decision-primitive.ts +26 -3
  105. package/src/approvals/guardian-request-resolvers.ts +181 -78
  106. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  107. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  108. package/src/calls/call-pointer-messages.ts +10 -4
  109. package/src/calls/channel-admission-reader.ts +104 -0
  110. package/src/calls/guardian-dispatch.ts +17 -45
  111. package/src/calls/media-stream-server.ts +84 -2
  112. package/src/calls/relay-server.ts +66 -0
  113. package/src/calls/relay-setup-router.ts +82 -1
  114. package/src/calls/twilio-routes.ts +17 -8
  115. package/src/cli/commands/clients.ts +3 -0
  116. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  117. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  118. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  119. package/src/cli/commands/memory/index.ts +30 -0
  120. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  121. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  122. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  123. package/src/cli/commands/oauth/status.test.ts +36 -0
  124. package/src/cli/commands/oauth/status.ts +23 -3
  125. package/src/cli/commands/plugins.ts +57 -5
  126. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  127. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +134 -4
  128. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  129. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +53 -11
  130. package/src/cli/lib/inspect-plugin.ts +12 -1
  131. package/src/cli/lib/merge-plugin-tree.ts +149 -49
  132. package/src/cli/lib/plugin-surfaces.ts +104 -0
  133. package/src/cli/lib/upgrade-plugin.ts +64 -36
  134. package/src/cli/program.ts +2 -4
  135. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  136. package/src/config/assistant-feature-flags.ts +22 -7
  137. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  138. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  139. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  140. package/src/config/bundled-skills/workflows/SKILL.md +14 -7
  141. package/src/config/call-site-defaults.ts +3 -0
  142. package/src/config/feature-flag-registry.json +49 -18
  143. package/src/config/llm-resolver.ts +3 -0
  144. package/src/config/memory-v3-gate.ts +11 -0
  145. package/src/config/schema.ts +8 -6
  146. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  147. package/src/config/schemas/call-site-catalog.ts +7 -0
  148. package/src/config/schemas/channels.ts +11 -0
  149. package/src/config/schemas/llm.ts +31 -0
  150. package/src/config/schemas/memory-lifecycle.ts +3 -7
  151. package/src/config/schemas/memory-v3.ts +6 -0
  152. package/src/config/schemas/services.ts +18 -0
  153. package/src/config/seed-inference-profiles.ts +94 -34
  154. package/src/config/skills.ts +21 -0
  155. package/src/config/sync-gated-profiles.ts +220 -0
  156. package/src/contacts/contact-store.ts +2 -10
  157. package/src/contacts/contacts-write.ts +1 -2
  158. package/src/contacts/types.ts +0 -1
  159. package/src/context/compactor.ts +86 -52
  160. package/src/context/strip-injections.ts +58 -10
  161. package/src/context/token-estimator.ts +1 -1
  162. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  163. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  164. package/src/daemon/conversation-agent-loop.ts +100 -19
  165. package/src/daemon/conversation-history.ts +1 -1
  166. package/src/daemon/conversation-lifecycle.ts +3 -5
  167. package/src/daemon/conversation-process.ts +13 -5
  168. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  169. package/src/daemon/conversation-surfaces.ts +26 -0
  170. package/src/daemon/conversation-tool-setup.ts +16 -11
  171. package/src/daemon/conversation.ts +64 -14
  172. package/src/daemon/disk-pressure-policy.ts +5 -3
  173. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  174. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  175. package/src/daemon/handlers/config-a2a.ts +0 -2
  176. package/src/daemon/handlers/config-channels.ts +5 -10
  177. package/src/daemon/handlers/config-slack-channel.ts +20 -0
  178. package/src/daemon/handlers/conversations.ts +107 -0
  179. package/src/daemon/host-browser-proxy.ts +41 -0
  180. package/src/daemon/lifecycle.ts +55 -20
  181. package/src/daemon/message-provenance.ts +2 -0
  182. package/src/daemon/message-types/contacts.ts +0 -1
  183. package/src/daemon/message-types/web-activity.ts +7 -1
  184. package/src/daemon/message-types/workflows.ts +83 -1
  185. package/src/daemon/tool-setup-types.ts +4 -0
  186. package/src/daemon/trust-context.ts +1 -1
  187. package/src/events/tool-audit-listener.ts +2 -2
  188. package/src/home/feed-source-enrichment.test.ts +151 -0
  189. package/src/home/feed-source-enrichment.ts +176 -0
  190. package/src/instrument.ts +18 -6
  191. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  192. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  193. package/src/ipc/assistant-server.ts +37 -4
  194. package/src/ipc/gateway-flag-listener.ts +18 -2
  195. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  196. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  197. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  198. package/src/memory/__tests__/memory-retrospective-job.test.ts +34 -0
  199. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  200. package/src/memory/auth-fallback-events-store.ts +2 -2
  201. package/src/memory/auto-analysis-enqueue.ts +3 -5
  202. package/src/memory/canonical-guardian-store.ts +39 -1
  203. package/src/memory/conversation-crud.ts +9 -4
  204. package/src/memory/conversation-key-store.ts +17 -2
  205. package/src/memory/conversation-title-service.ts +64 -7
  206. package/src/memory/db-init.ts +10 -0
  207. package/src/memory/embedding-backend.ts +15 -1
  208. package/src/memory/jobs-worker.ts +2 -1
  209. package/src/memory/lifecycle-events-store.ts +2 -2
  210. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  211. package/src/memory/memory-retrospective-job.ts +9 -0
  212. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  213. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  214. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  215. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +10 -0
  216. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  217. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  218. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  219. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  220. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  221. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  222. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  223. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  224. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  225. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +30 -0
  226. package/src/memory/migrations/index.ts +5 -0
  227. package/src/memory/onboarding-events-store.ts +3 -3
  228. package/src/memory/schema/contacts.ts +0 -1
  229. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  230. package/src/memory/skill-loaded-events-store.ts +2 -2
  231. package/src/memory/tool-executed-events-store.test.ts +7 -7
  232. package/src/memory/turn-trace-store.test.ts +736 -0
  233. package/src/memory/turn-trace-store.ts +364 -0
  234. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  235. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  236. package/src/memory/v2/consolidation-job.ts +2 -2
  237. package/src/memory/v2/skill-content.ts +25 -7
  238. package/src/memory/v2/skill-store.ts +7 -1
  239. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  240. package/src/memory/v3-eval/eval-packets.ts +546 -0
  241. package/src/messaging/providers/slack/api.ts +31 -0
  242. package/src/messaging/providers/slack/send.test.ts +114 -2
  243. package/src/messaging/providers/slack/send.ts +30 -7
  244. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  245. package/src/messaging/providers/slack/withdraw.ts +161 -0
  246. package/src/notifications/AGENTS.md +2 -0
  247. package/src/notifications/access-request-copy.ts +72 -59
  248. package/src/notifications/adapters/slack.ts +55 -73
  249. package/src/notifications/approval-card-data.ts +333 -0
  250. package/src/notifications/broadcaster.ts +6 -2
  251. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  252. package/src/notifications/copy-composer.ts +3 -3
  253. package/src/notifications/decision-engine.ts +4 -2
  254. package/src/notifications/destination-resolver.ts +4 -6
  255. package/src/notifications/guardian-question-mode.ts +10 -0
  256. package/src/notifications/home-feed-side-effect.ts +3 -13
  257. package/src/notifications/notification-utils.ts +2 -1
  258. package/src/notifications/signal.ts +79 -43
  259. package/src/notifications/types.ts +98 -128
  260. package/src/permissions/checker.test.ts +51 -0
  261. package/src/permissions/checker.ts +185 -26
  262. package/src/permissions/ipc-risk-types.ts +24 -0
  263. package/src/permissions/question-prompter.test.ts +27 -0
  264. package/src/permissions/question-prompter.ts +4 -0
  265. package/src/platform/client.test.ts +119 -0
  266. package/src/platform/client.ts +66 -0
  267. package/src/platform/consent-cache.test.ts +267 -0
  268. package/src/platform/consent-cache.ts +174 -0
  269. package/src/plugin-api/index.ts +27 -0
  270. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  271. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  272. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  273. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  274. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  275. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  276. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  277. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  278. package/src/plugins/defaults/advisor/config.ts +21 -0
  279. package/src/plugins/defaults/advisor/consult.ts +93 -0
  280. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  281. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  282. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  283. package/src/plugins/defaults/advisor/package.json +14 -0
  284. package/src/plugins/defaults/advisor/steering.ts +67 -0
  285. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  286. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  287. package/src/plugins/defaults/index.ts +35 -0
  288. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  289. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  290. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  291. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  292. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  293. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  294. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +75 -7
  295. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  296. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  297. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  298. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  299. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +37 -4
  300. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  301. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  302. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  303. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -12
  304. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  305. package/src/prompts/persona-resolver.ts +12 -2
  306. package/src/prompts/templates/system-sections.ts +7 -2
  307. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  308. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  309. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  310. package/src/providers/atlascloud/client.ts +85 -0
  311. package/src/providers/fetch-provider-catalog.ts +85 -0
  312. package/src/providers/inference/adapter-factory.ts +3 -0
  313. package/src/providers/model-catalog.ts +58 -0
  314. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  315. package/src/providers/openai/chat-completions-provider.ts +7 -0
  316. package/src/providers/openai/responses-provider.ts +10 -0
  317. package/src/providers/provider-send-message.ts +11 -3
  318. package/src/providers/retry.ts +53 -12
  319. package/src/providers/search-provider-catalog.ts +10 -0
  320. package/src/providers/weak-open-model.ts +22 -0
  321. package/src/runtime/__tests__/agent-wake.test.ts +181 -0
  322. package/src/runtime/__tests__/client-health.test.ts +44 -0
  323. package/src/runtime/access-request-helper.ts +21 -53
  324. package/src/runtime/actor-trust-resolver.ts +49 -21
  325. package/src/runtime/agent-wake.ts +52 -0
  326. package/src/runtime/assistant-event-hub.ts +18 -4
  327. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  328. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  329. package/src/runtime/capabilities.test.ts +120 -0
  330. package/src/runtime/capabilities.ts +197 -0
  331. package/src/runtime/channel-approval-types.ts +5 -1
  332. package/src/runtime/channel-retry-sweep.ts +1 -0
  333. package/src/runtime/channel-verification-service.ts +1 -2
  334. package/src/runtime/client-health.ts +26 -0
  335. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  336. package/src/runtime/effective-capabilities.test.ts +128 -0
  337. package/src/runtime/effective-capabilities.ts +84 -0
  338. package/src/runtime/guardian-reply-router.ts +106 -21
  339. package/src/runtime/invite-redemption-service.ts +6 -22
  340. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
  341. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  342. package/src/runtime/pending-interactions.ts +15 -0
  343. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  344. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  345. package/src/runtime/routes/__tests__/plugins-routes.test.ts +35 -13
  346. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  347. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  348. package/src/runtime/routes/client-routes.ts +10 -0
  349. package/src/runtime/routes/contact-routes.ts +31 -8
  350. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  351. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  352. package/src/runtime/routes/conversation-routes.ts +37 -12
  353. package/src/runtime/routes/events-routes.ts +1 -3
  354. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  355. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  356. package/src/runtime/routes/home-feed-routes.ts +8 -3
  357. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  358. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +88 -6
  359. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  360. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  361. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  362. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  363. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  364. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  365. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  366. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  367. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  368. package/src/runtime/routes/index.ts +2 -0
  369. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  370. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  371. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  372. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  373. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  374. package/src/runtime/routes/notification-routes.ts +122 -133
  375. package/src/runtime/routes/platform-routes.ts +2 -2
  376. package/src/runtime/routes/plugins-routes.ts +40 -7
  377. package/src/runtime/routes/secret-routes.ts +10 -0
  378. package/src/runtime/routes/surface-action-routes.ts +2 -1
  379. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  380. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  381. package/src/runtime/routes/workflow-routes.test.ts +225 -1
  382. package/src/runtime/routes/workflow-routes.ts +131 -1
  383. package/src/runtime/tool-grant-request-helper.ts +18 -16
  384. package/src/runtime/trust-context-resolver.ts +8 -5
  385. package/src/schedule/schedule-store.ts +1 -1
  386. package/src/schedule/scheduler-types.ts +5 -1
  387. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  388. package/src/security/secret-patterns.ts +3 -0
  389. package/src/subagent/manager.ts +11 -4
  390. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  391. package/src/telemetry/trace-collection-policy.ts +30 -0
  392. package/src/telemetry/types.ts +89 -0
  393. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  394. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  395. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  396. package/src/tools/browser/browser-execution.ts +29 -18
  397. package/src/tools/document/document-tool.ts +2 -3
  398. package/src/tools/executor.ts +5 -3
  399. package/src/tools/host-terminal/host-shell.ts +5 -4
  400. package/src/tools/memory/register.ts +2 -2
  401. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  402. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  403. package/src/tools/network/web-fetch.ts +372 -1
  404. package/src/tools/network/web-search.ts +213 -10
  405. package/src/tools/permission-checker.ts +3 -2
  406. package/src/tools/registry.ts +20 -0
  407. package/src/tools/schedule/create.ts +4 -3
  408. package/src/tools/schedule/update.ts +2 -1
  409. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  410. package/src/tools/skills/execute.ts +1 -2
  411. package/src/tools/subagent/spawn.ts +37 -13
  412. package/src/tools/terminal/shell.ts +10 -4
  413. package/src/tools/tool-approval-handler.ts +17 -10
  414. package/src/tools/types.ts +9 -0
  415. package/src/tools/ui-surface/definitions.ts +25 -2
  416. package/src/tools/verification-control-plane-policy.ts +3 -1
  417. package/src/tools/workflows/run-workflow.ts +1 -0
  418. package/src/util/disk-usage.ts +78 -23
  419. package/src/util/platform.ts +8 -1
  420. package/src/watcher/telemetry.ts +2 -2
  421. package/src/workflows/engine.test.ts +175 -1
  422. package/src/workflows/engine.ts +82 -0
  423. package/src/workflows/journal-store.test.ts +70 -0
  424. package/src/workflows/journal-store.ts +18 -3
  425. package/src/workflows/run-manager.test.ts +171 -3
  426. package/src/workflows/run-manager.ts +64 -0
  427. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  428. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  429. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  430. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  431. package/src/workspace/migrations/registry.ts +8 -0
  432. package/src/notifications/tool-approval-copy.ts +0 -142
  433. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
@@ -105,6 +105,16 @@ export const SEARCH_PROVIDER_CATALOG: readonly SearchProviderCatalogEntry[] = [
105
105
  fallbackOrder: 3,
106
106
  privacyPolicyUrl: "https://tavily.com/privacy",
107
107
  },
108
+ {
109
+ id: "firecrawl",
110
+ displayName: "Firecrawl",
111
+ kind: "byok",
112
+ apiKeyPrefix: "fc-...",
113
+ envVar: "FIRECRAWL_API_KEY",
114
+ secretKey: "firecrawl",
115
+ fallbackOrder: 4,
116
+ privacyPolicyUrl: "https://www.firecrawl.dev/privacy-policy",
117
+ },
108
118
  ];
109
119
 
110
120
  /** Provider ids accepted by the web-search config schema. */
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Family-level classification of "weak open models" — open-weight models
3
+ * (Kimi, DeepSeek, MiniMax, GLM) that disregard static instructions and have
4
+ * capability gaps that capable models (Claude, GPT) do not. Used by harness
5
+ * levers that coach or redirect these models without touching capable-model
6
+ * behavior: the task-progress-nudge plugin and the empty-dynamic_page surface
7
+ * redirect.
8
+ *
9
+ * Family-level matching spans provider naming conventions: OpenRouter
10
+ * `moonshotai/kimi-k2.6`, `deepseek/deepseek-chat`, `minimax/minimax-m3`;
11
+ * Fireworks `accounts/fireworks/models/minimax-m3`, `kimi-k2p6`. Extend the
12
+ * pattern as other open models show the same gaps.
13
+ *
14
+ * Distinct from exploration-drift's narrower `LOOP_PRONE_MODEL_PATTERN`, which
15
+ * targets specific loop-prone versions rather than the whole capability family.
16
+ */
17
+ export const WEAK_OPEN_MODEL_PATTERN = /kimi|deepseek|minimax|glm/i;
18
+
19
+ /** True when `model` is a weak open model (see {@link WEAK_OPEN_MODEL_PATTERN}). */
20
+ export function isWeakOpenModel(model: string | null | undefined): boolean {
21
+ return typeof model === "string" && WEAK_OPEN_MODEL_PATTERN.test(model);
22
+ }
@@ -244,6 +244,55 @@ const recordRequestLogCalls: Array<{
244
244
  provider?: string;
245
245
  callSite?: string;
246
246
  }> = [];
247
+ const recordUsageCalls: Array<{
248
+ conversationId: string;
249
+ inputTokens: number;
250
+ outputTokens: number;
251
+ model: string;
252
+ actor: string;
253
+ cacheCreationInputTokens: number;
254
+ cacheReadInputTokens: number;
255
+ callSite: unknown;
256
+ overrideProfile: unknown;
257
+ forceOverrideProfile: unknown;
258
+ selectionSeed: unknown;
259
+ }> = [];
260
+ mock.module("../../daemon/conversation-usage.js", () => ({
261
+ recordUsage: (
262
+ ctx: { conversationId: string },
263
+ inputTokens: number,
264
+ outputTokens: number,
265
+ model: string,
266
+ _onEvent: unknown,
267
+ actor: string,
268
+ _requestId: unknown,
269
+ cacheCreationInputTokens = 0,
270
+ cacheReadInputTokens = 0,
271
+ _rawResponse?: unknown,
272
+ _llmCallCount?: number,
273
+ _contextWindow?: unknown,
274
+ attribution?: {
275
+ callSite?: unknown;
276
+ overrideProfile?: unknown;
277
+ forceOverrideProfile?: unknown;
278
+ selectionSeed?: unknown;
279
+ },
280
+ ) => {
281
+ recordUsageCalls.push({
282
+ conversationId: ctx.conversationId,
283
+ inputTokens,
284
+ outputTokens,
285
+ model,
286
+ actor,
287
+ cacheCreationInputTokens,
288
+ cacheReadInputTokens,
289
+ callSite: attribution?.callSite ?? null,
290
+ overrideProfile: attribution?.overrideProfile ?? null,
291
+ forceOverrideProfile: attribution?.forceOverrideProfile,
292
+ selectionSeed: attribution?.selectionSeed,
293
+ });
294
+ },
295
+ }));
247
296
  mock.module("../../memory/llm-request-log-store.js", () => ({
248
297
  recordRequestLog: (
249
298
  conversationId: string,
@@ -464,6 +513,7 @@ beforeEach(() => {
464
513
  __resetWakeChainForTests();
465
514
  wakeConvRegistry.clear();
466
515
  recordRequestLogCalls.length = 0;
516
+ recordUsageCalls.length = 0;
467
517
  mockGetOrCreateConversationCalls.length = 0;
468
518
  mockResolverTarget = null;
469
519
  mockGetConversationOverrideProfile = () => undefined;
@@ -1896,6 +1946,137 @@ describe("wakeAgentForOpportunity", () => {
1896
1946
  expect(recordRequestLogCalls[0]?.callSite).toBe("memoryRetrospective");
1897
1947
  });
1898
1948
 
1949
+ test("wake records LLM usage to the cost ledger, attributed to its call site", async () => {
1950
+ const usageEvent: AgentEvent = {
1951
+ type: "usage",
1952
+ inputTokens: 100,
1953
+ outputTokens: 5,
1954
+ model: "test-model",
1955
+ actualProvider: "test-provider",
1956
+ providerDurationMs: 10,
1957
+ cacheCreationInputTokens: 7,
1958
+ cacheReadInputTokens: 11,
1959
+ rawRequest: { request: "retrospective wake" },
1960
+ rawResponse: { response: "real reply" },
1961
+ };
1962
+ const conversation = makeWakeConversation({
1963
+ baseline: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
1964
+ scriptedEvents: [usageEvent],
1965
+ scriptedAssistant: {
1966
+ role: "assistant",
1967
+ content: [{ type: "text", text: "real reply" }],
1968
+ },
1969
+ });
1970
+
1971
+ await wakeAgentForOpportunity(
1972
+ {
1973
+ conversationId: conversation.conversationId,
1974
+ hint: "do reply",
1975
+ source: "unit-test",
1976
+ callSite: "memoryRetrospective",
1977
+ },
1978
+ { resolveTarget: async () => conversation },
1979
+ );
1980
+
1981
+ // A wake-driven LLM call records a usage row attributed to its
1982
+ // conversation, so its cost reaches the ledger.
1983
+ expect(recordUsageCalls).toHaveLength(1);
1984
+ expect(recordUsageCalls[0]).toMatchObject({
1985
+ conversationId: conversation.conversationId,
1986
+ inputTokens: 100,
1987
+ outputTokens: 5,
1988
+ model: "test-model",
1989
+ actor: "main_agent",
1990
+ cacheCreationInputTokens: 7,
1991
+ cacheReadInputTokens: 11,
1992
+ callSite: "memoryRetrospective",
1993
+ // Seed the dispatch path used for mix-arm resolution.
1994
+ selectionSeed: conversation.conversationId,
1995
+ });
1996
+ });
1997
+
1998
+ test("forced-profile wake records usage under the forced profile, not the call site", async () => {
1999
+ const usageEvent: AgentEvent = {
2000
+ type: "usage",
2001
+ inputTokens: 100,
2002
+ outputTokens: 5,
2003
+ model: "test-model",
2004
+ actualProvider: "test-provider",
2005
+ providerDurationMs: 10,
2006
+ rawRequest: { request: "forced wake" },
2007
+ rawResponse: { response: "real reply" },
2008
+ };
2009
+ const conversation = makeWakeConversation({
2010
+ baseline: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
2011
+ scriptedEvents: [usageEvent],
2012
+ scriptedAssistant: {
2013
+ role: "assistant",
2014
+ content: [{ type: "text", text: "real reply" }],
2015
+ },
2016
+ });
2017
+
2018
+ await wakeAgentForOpportunity(
2019
+ {
2020
+ conversationId: conversation.conversationId,
2021
+ hint: "do reply",
2022
+ source: "unit-test",
2023
+ callSite: "memoryRetrospective",
2024
+ // Stand-in for the source conversation's profile, floated above the
2025
+ // call-site profile (fork retrospectives). `recordUsage` is mocked, so
2026
+ // this value is only threaded through and asserted — not resolved.
2027
+ forceOverrideProfile: "source-profile",
2028
+ },
2029
+ { resolveTarget: async () => conversation },
2030
+ );
2031
+
2032
+ expect(recordUsageCalls).toHaveLength(1);
2033
+ expect(recordUsageCalls[0]).toMatchObject({
2034
+ overrideProfile: "source-profile",
2035
+ forceOverrideProfile: true,
2036
+ selectionSeed: conversation.conversationId,
2037
+ });
2038
+ });
2039
+
2040
+ test("silent no-op wake still records usage even though its request log is dropped", async () => {
2041
+ const usageEvent: AgentEvent = {
2042
+ type: "usage",
2043
+ inputTokens: 100,
2044
+ outputTokens: 5,
2045
+ model: "test-model",
2046
+ actualProvider: "test-provider",
2047
+ providerDurationMs: 10,
2048
+ rawRequest: { request: "no-op wake" },
2049
+ rawResponse: { response: "no output" },
2050
+ };
2051
+ const conversation = makeWakeConversation({
2052
+ baseline: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
2053
+ scriptedEvents: [usageEvent],
2054
+ // Empty assistant text → silent no-op, so the request log is dropped.
2055
+ scriptedAssistant: {
2056
+ role: "assistant",
2057
+ content: [{ type: "text", text: "" }],
2058
+ },
2059
+ });
2060
+
2061
+ await wakeAgentForOpportunity(
2062
+ {
2063
+ conversationId: conversation.conversationId,
2064
+ hint: "consider doing nothing",
2065
+ source: "unit-test",
2066
+ },
2067
+ { resolveTarget: async () => conversation },
2068
+ );
2069
+
2070
+ // Silent wake drops its request log, but the call still cost money.
2071
+ expect(recordRequestLogCalls).toHaveLength(0);
2072
+ expect(recordUsageCalls).toHaveLength(1);
2073
+ expect(recordUsageCalls[0]).toMatchObject({
2074
+ conversationId: conversation.conversationId,
2075
+ inputTokens: 100,
2076
+ outputTokens: 5,
2077
+ });
2078
+ });
2079
+
1899
2080
  test("non-serializable usage payload does not abort the wake", async () => {
1900
2081
  // Circular reference in rawRequest — JSON.stringify throws on this.
1901
2082
  // Serialization must happen inside persistLog's try/catch so the
@@ -0,0 +1,44 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { isClientDegraded } from "../client-health.js";
4
+
5
+ const HEARTBEAT_MS = 7_000;
6
+
7
+ describe("isClientDegraded", () => {
8
+ test("fresh connection is not degraded", () => {
9
+ const now = new Date(1_000_000);
10
+ const lastActiveAt = now; // just connected
11
+ expect(isClientDegraded(lastActiveAt, now, HEARTBEAT_MS)).toBe(false);
12
+ });
13
+
14
+ test("actively heartbeating connection is not degraded", () => {
15
+ const now = new Date(1_000_000);
16
+ const lastActiveAt = new Date(now.getTime() - 2_000); // heartbeat 2s ago
17
+ expect(isClientDegraded(lastActiveAt, now, HEARTBEAT_MS)).toBe(false);
18
+ });
19
+
20
+ test("never-heartbeating connection that has gone stale is degraded", () => {
21
+ const now = new Date(1_000_000);
22
+ const lastActiveAt = new Date(now.getTime() - 30_000); // 30s since any activity
23
+ expect(isClientDegraded(lastActiveAt, now, HEARTBEAT_MS)).toBe(true);
24
+ });
25
+
26
+ test("heartbeated then froze is degraded (stale relative to now, not connectedAt)", () => {
27
+ const now = new Date(1_000_000);
28
+ // Connected long ago, heartbeated for a while, then stopped an hour ago.
29
+ const lastActiveAt = new Date(now.getTime() - 3_600_000);
30
+ expect(isClientDegraded(lastActiveAt, now, HEARTBEAT_MS)).toBe(true);
31
+ });
32
+
33
+ test("boundary: just under 2 heartbeat intervals stale is not degraded", () => {
34
+ const now = new Date(1_000_000);
35
+ const lastActiveAt = new Date(now.getTime() - (2 * HEARTBEAT_MS - 1));
36
+ expect(isClientDegraded(lastActiveAt, now, HEARTBEAT_MS)).toBe(false);
37
+ });
38
+
39
+ test("boundary: just over 2 heartbeat intervals stale is degraded", () => {
40
+ const now = new Date(1_000_000);
41
+ const lastActiveAt = new Date(now.getTime() - (2 * HEARTBEAT_MS + 1));
42
+ expect(isClientDegraded(lastActiveAt, now, HEARTBEAT_MS)).toBe(true);
43
+ });
44
+ });
@@ -15,33 +15,20 @@ import type { ChannelId } from "../channels/types.js";
15
15
  import { findGuardianForChannel } from "../contacts/contact-store.js";
16
16
  import type { ChannelStatus } from "../contacts/types.js";
17
17
  import {
18
- createCanonicalGuardianDelivery,
19
18
  createCanonicalGuardianRequest,
20
19
  listCanonicalGuardianRequests,
21
- updateCanonicalGuardianDelivery,
22
20
  } from "../memory/canonical-guardian-store.js";
21
+ import {
22
+ recordApprovalCardDelivery,
23
+ recordGuardianRequestDeliveries,
24
+ } from "../notifications/canonical-delivery-recorder.js";
23
25
  import { emitNotificationSignal } from "../notifications/emit-signal.js";
24
- import type {
25
- GuardianResolutionSource,
26
- NotificationSourceChannel,
27
- } from "../notifications/signal.js";
28
- import type { NotificationDeliveryResult } from "../notifications/types.js";
26
+ import type { GuardianResolutionSource } from "../notifications/signal.js";
29
27
  import { getLogger } from "../util/logger.js";
30
28
  import { GUARDIAN_APPROVAL_TTL_MS } from "./routes/channel-route-shared.js";
31
29
 
32
30
  const log = getLogger("access-request-helper");
33
31
 
34
- function applyDeliveryStatus(
35
- deliveryId: string,
36
- result: NotificationDeliveryResult,
37
- ): void {
38
- if (result.status === "sent") {
39
- updateCanonicalGuardianDelivery(deliveryId, { status: "sent" });
40
- return;
41
- }
42
- updateCanonicalGuardianDelivery(deliveryId, { status: "failed" });
43
- }
44
-
45
32
  // ---------------------------------------------------------------------------
46
33
  // Types
47
34
  // ---------------------------------------------------------------------------
@@ -127,7 +114,7 @@ export function notifyGuardianOfAccessRequest(
127
114
  sourceGuardian &&
128
115
  sourceGuardian.contact.principalId === assistantGuardianPrincipalId
129
116
  ) {
130
- guardianExternalUserId = sourceGuardian.channel.externalUserId;
117
+ guardianExternalUserId = sourceGuardian.channel.address;
131
118
  guardianPrincipalId = sourceGuardian.contact.principalId;
132
119
  guardianBindingChannel = sourceGuardian.channel.type;
133
120
  guardianResolutionSource = "source-channel-contact";
@@ -136,8 +123,7 @@ export function notifyGuardianOfAccessRequest(
136
123
  // Access requests always require a principal. If source-channel resolution
137
124
  // did not match the assistant anchor, use the anchored vellum identity.
138
125
  if (!guardianPrincipalId && vellumGuardian) {
139
- guardianExternalUserId =
140
- vellumGuardian.channel.externalUserId ?? guardianExternalUserId;
126
+ guardianExternalUserId = vellumGuardian.channel.address;
141
127
  guardianPrincipalId = assistantGuardianPrincipalId ?? null;
142
128
  guardianBindingChannel = guardianBindingChannel ?? "vellum";
143
129
  guardianResolutionSource = "vellum-anchor";
@@ -200,7 +186,7 @@ export function notifyGuardianOfAccessRequest(
200
186
  expiresAt: Date.now() + GUARDIAN_APPROVAL_TTL_MS,
201
187
  });
202
188
 
203
- let vellumDeliveryId: string | null = null;
189
+ let vellumDeliveryId: string | undefined;
204
190
  // When the access request originates from a text channel with
205
191
  // notification delivery support (Slack, Telegram) and the guardian was
206
192
  // resolved via a verified same-channel contact, route the notification
@@ -219,7 +205,7 @@ export function notifyGuardianOfAccessRequest(
219
205
 
220
206
  void emitNotificationSignal({
221
207
  sourceEventName: "ingress.access_request",
222
- sourceChannel: sourceChannel as NotificationSourceChannel,
208
+ sourceChannel,
223
209
  sourceContextId: `access-req-${sourceChannel}-${actorExternalId}`,
224
210
  requiresConversation: true,
225
211
  ...(sameChannelOnly ? { routingIntent: "single_channel" as const } : {}),
@@ -250,44 +236,26 @@ export function notifyGuardianOfAccessRequest(
250
236
  onConversationCreated: (info) => {
251
237
  if (info.sourceEventName !== "ingress.access_request" || vellumDeliveryId)
252
238
  return;
253
- const delivery = createCanonicalGuardianDelivery({
239
+ vellumDeliveryId = recordApprovalCardDelivery({
254
240
  requestId: canonicalRequest.id,
255
- destinationChannel: "vellum",
256
- destinationConversationId: info.conversationId,
257
- });
258
- vellumDeliveryId = delivery.id;
241
+ channel: "vellum",
242
+ conversationId: info.conversationId,
243
+ })?.id;
259
244
  },
260
245
  })
261
246
  .then((signalResult) => {
262
- for (const result of signalResult.deliveryResults) {
263
- if (result.channel === "vellum") {
264
- if (!vellumDeliveryId) {
265
- const delivery = createCanonicalGuardianDelivery({
266
- requestId: canonicalRequest.id,
267
- destinationChannel: "vellum",
268
- destinationConversationId: result.conversationId,
269
- });
270
- vellumDeliveryId = delivery.id;
271
- }
272
- applyDeliveryStatus(vellumDeliveryId, result);
273
- continue;
274
- }
275
-
276
- const delivery = createCanonicalGuardianDelivery({
277
- requestId: canonicalRequest.id,
278
- destinationChannel: result.channel,
279
- destinationChatId:
280
- result.destination.length > 0 ? result.destination : undefined,
281
- });
282
- applyDeliveryStatus(delivery.id, result);
283
- }
247
+ vellumDeliveryId = recordGuardianRequestDeliveries({
248
+ requestId: canonicalRequest.id,
249
+ deliveryResults: signalResult.deliveryResults,
250
+ vellumDeliveryId,
251
+ });
284
252
 
285
253
  if (!vellumDeliveryId && !sameChannelOnly) {
286
- const fallback = createCanonicalGuardianDelivery({
254
+ recordApprovalCardDelivery({
287
255
  requestId: canonicalRequest.id,
288
- destinationChannel: "vellum",
256
+ channel: "vellum",
257
+ status: "failed",
289
258
  });
290
- updateCanonicalGuardianDelivery(fallback.id, { status: "failed" });
291
259
  log.warn(
292
260
  { requestId: canonicalRequest.id, reason: signalResult.reason },
293
261
  "Notification pipeline did not produce a vellum delivery result for access request",
@@ -8,7 +8,12 @@
8
8
  * Trust classifications:
9
9
  * - `guardian`: sender matches the guardian contact's channel for this channel type.
10
10
  * - `trusted_contact`: sender is an active contact channel (not the guardian).
11
- * - `unknown`: sender has no matching contact or no identity could be established.
11
+ * - `unverified_contact`: sender matches a contact channel that is pending or
12
+ * unverified — known to the guardian but not yet through verification.
13
+ * Treated identically to `trusted_contact` downstream; the distinction only
14
+ * matters at the admission floor (see channel admission policy).
15
+ * - `unknown`: sender has no matching contact, no identity could be
16
+ * established, or the contact's channel is blocked/revoked.
12
17
  */
13
18
 
14
19
  import type { ChannelId } from "../channels/types.js";
@@ -38,22 +43,35 @@ export type { TrustContext } from "../daemon/trust-context.js";
38
43
  * - `'trusted_contact'`: The sender is an active contact with a channel
39
44
  * (not the guardian). Trusted contacts can invoke tools but require
40
45
  * guardian approval for sensitive operations.
46
+ * - `'unverified_contact'`: The sender matches a contact channel whose
47
+ * status is `pending` or `unverified` — known to the guardian but not yet
48
+ * verified. Treated identically to `trusted_contact` for every downstream
49
+ * capability/tool/approval decision; the distinction is admission-only.
41
50
  * - `'unknown'`: The sender has no contact record, no identity could be
42
- * established, or the sender is an inactive/revoked contact. Unknown
51
+ * established, or the sender is a blocked/revoked contact. Unknown
43
52
  * actors are fail-closed with no escalation path.
44
53
  */
45
- export type TrustClass = "guardian" | "trusted_contact" | "unknown";
54
+ export type TrustClass =
55
+ | "guardian"
56
+ | "trusted_contact"
57
+ | "unverified_contact"
58
+ | "unknown";
46
59
 
47
- /** Returns `true` for actors that are not fully trusted (i.e. not the guardian). */
48
- export function isUntrustedTrustClass(
49
- trustClass: TrustClass | undefined,
50
- ): boolean {
51
- return (
52
- trustClass === "trusted_contact" ||
53
- trustClass === "unknown" ||
54
- trustClass === undefined
55
- );
56
- }
60
+ /**
61
+ * Trust-class ordinal used by the per-channel admission policy floor check.
62
+ * Higher rank = more trusted. Blocked/revoked never reach classification —
63
+ * their effective rank is 0 and is enforced by the inbound ACL stage's
64
+ * member-status short-circuit, not via this table.
65
+ *
66
+ * See `wave-b-plan.md` §2.4. Paired with `ADMISSION_FLOOR` from
67
+ * `@vellumai/gateway-client` — both tables move together.
68
+ */
69
+ export const TRUST_CLASS_RANK: Record<TrustClass, number> = {
70
+ guardian: 4,
71
+ trusted_contact: 3,
72
+ unverified_contact: 2,
73
+ unknown: 1,
74
+ };
57
75
 
58
76
  /**
59
77
  * Fully resolved trust context from the actor trust resolver.
@@ -181,8 +199,7 @@ export function resolveActorTrust(
181
199
  const { contact: guardianContact, channel: guardianChannel } =
182
200
  guardianResult;
183
201
  guardianBindingMatch = {
184
- guardianExternalUserId:
185
- guardianChannel.externalUserId ?? guardianChannel.address ?? "",
202
+ guardianExternalUserId: guardianChannel.address,
186
203
  guardianDeliveryChatId: guardianChannel.externalChatId,
187
204
  };
188
205
  guardianPrincipalId = guardianContact.principalId ?? undefined;
@@ -249,12 +266,23 @@ export function resolveActorTrust(
249
266
  let trustClass: TrustClass;
250
267
  if (isGuardian) {
251
268
  trustClass = "guardian";
252
- } else if (
253
- memberMatchesSender &&
254
- memberRecord &&
255
- memberRecord.channel.status === "active"
256
- ) {
257
- trustClass = "trusted_contact";
269
+ } else if (memberMatchesSender && memberRecord) {
270
+ const status = memberRecord.channel.status;
271
+ if (status === "active") {
272
+ trustClass = "trusted_contact";
273
+ } else if (status === "unverified" || status === "pending") {
274
+ // Pre-verification / awaiting-verification contacts get their own
275
+ // admission tier. Treated identically to trusted_contact for ALL
276
+ // downstream capability/tool/approval decisions; the distinction
277
+ // only matters at the channel admission floor.
278
+ trustClass = "unverified_contact";
279
+ } else {
280
+ // status === "blocked" or "revoked" → unknown. acl-enforcement
281
+ // re-checks resolvedMember.channel.status and emits the appropriate
282
+ // member_blocked / member_revoked reasons, so hard-deny semantics
283
+ // for these statuses are preserved end-to-end.
284
+ trustClass = "unknown";
285
+ }
258
286
  } else {
259
287
  trustClass = "unknown";
260
288
  }
@@ -58,6 +58,7 @@ import { resolveEffectiveContextWindow } from "../config/llm-context-resolution.
58
58
  import { getConfig } from "../config/loader.js";
59
59
  import type { LLMCallSite } from "../config/schemas/llm.js";
60
60
  import type { Conversation } from "../daemon/conversation.js";
61
+ import { recordUsage } from "../daemon/conversation-usage.js";
61
62
  import { getDiskPressureStatus } from "../daemon/disk-pressure-guard.js";
62
63
  import {
63
64
  classifyDiskPressureTurnPolicy,
@@ -195,6 +196,14 @@ export interface WakeOptions {
195
196
  * retrospectives whose conversation title already says "(Retrospective)").
196
197
  */
197
198
  suppressWakeSurface?: boolean;
199
+ /**
200
+ * Run the wake's turn as non-interactive (no client present). Threads to the
201
+ * agent loop's `isNonInteractive`, which gates the `<non_interactive_context>`
202
+ * and `<background_turn>` injectors. Fork-based memory retrospectives set this
203
+ * to the source conversation's background-turn state so the fork reproduces
204
+ * the source's injected turn block (prompt-cache prefix parity). Default false.
205
+ */
206
+ isNonInteractive?: boolean;
198
207
  /**
199
208
  * Optional exact tool allowlist for this wake. Used by internal maintenance
200
209
  * jobs that need the assistant's judgment but must not execute arbitrary
@@ -587,6 +596,7 @@ export async function wakeAgentForOpportunity(
587
596
  opts.forceOverrideProfile ??
588
597
  getConversationOverrideProfile(conversationId);
589
598
  const callSite = opts.callSite ?? "mainAgent";
599
+ const isNonInteractive = opts.isNonInteractive ?? false;
590
600
  const config = getConfig();
591
601
  const effectiveContextWindow = resolveEffectiveContextWindow({
592
602
  llm: config.llm,
@@ -784,6 +794,47 @@ export async function wakeAgentForOpportunity(
784
794
  if (event.type === "compaction_completed") {
785
795
  recordCompactionEndBestEffort(conversationId, event);
786
796
  }
797
+ // Normal user turns record usage via the `dispatchAgentEvent` event handler)
798
+ // Wakes run their own onEvent and bypass it, so record here.
799
+ if (event.type === "usage") {
800
+ try {
801
+ recordUsage(
802
+ {
803
+ conversationId,
804
+ providerName: event.actualProvider ?? conversation.provider.name,
805
+ usageStats: conversation.usageStats,
806
+ },
807
+ event.inputTokens,
808
+ event.outputTokens,
809
+ event.model,
810
+ () => {},
811
+ "main_agent",
812
+ `wake:${source}`,
813
+ event.cacheCreationInputTokens ?? 0,
814
+ event.cacheReadInputTokens ?? 0,
815
+ event.rawResponse,
816
+ 1,
817
+ undefined,
818
+ // Mirror the profile state the request actually ran under:
819
+ // `forceOverrideProfile` floats the override above the call-site
820
+ // profile (fork retrospectives with matchConversationProfile), and
821
+ // the conversation-id seed resolves the same mix arm the dispatch
822
+ // path chose. Without these, attribution credits the call-site
823
+ // profile/arm instead of the one that ran.
824
+ {
825
+ callSite,
826
+ overrideProfile: overrideProfile ?? null,
827
+ forceOverrideProfile,
828
+ selectionSeed: conversationId,
829
+ },
830
+ );
831
+ } catch (err) {
832
+ log.warn(
833
+ { conversationId, source, err },
834
+ "agent-wake: usage recording failed (non-fatal)",
835
+ );
836
+ }
837
+ }
787
838
  // Replicates the recordRequestLog side-effect in `handleUsage` because
788
839
  // wakes own their own onEvent and never reach `dispatchAgentEvent`.
789
840
  // Defer persistence while buffering — see `pendingLogs` above.
@@ -1080,6 +1131,7 @@ export async function wakeAgentForOpportunity(
1080
1131
  trust: wakeTrust,
1081
1132
  overrideProfile,
1082
1133
  forceOverrideProfile,
1134
+ isNonInteractive,
1083
1135
  // The wake's compaction lives in the pre-run gate above
1084
1136
  // (`conversation.maybeCompact()`), never in the loop: the in-loop
1085
1137
  // budget gate and overflow-recovery ladder stay disabled because
@@ -40,6 +40,7 @@ export function capabilityForMessageType(
40
40
  return HOST_PREFIX_TO_CAPABILITY[stem];
41
41
  }
42
42
  import { appendEventToStream } from "../signals/event-stream.js";
43
+ import { IntegrityError } from "../util/errors.js";
43
44
  import { getLogger } from "../util/logger.js";
44
45
  import type { AssistantEvent } from "./assistant-event.js";
45
46
  import { buildAssistantEvent } from "./assistant-event.js";
@@ -757,9 +758,22 @@ async function createCanonicalRequestForConfirmation(
757
758
  });
758
759
  }
759
760
  } catch (err) {
760
- log.debug(
761
- { err, conversationId },
762
- "Failed to create canonical request from broadcast",
763
- );
761
+ if (err instanceof IntegrityError) {
762
+ // The confirmation could not be promoted to a canonical guardian request
763
+ // (e.g. its trust context resolved no guardianPrincipalId). Channel
764
+ // guardian decisions — reactions, buttons, and text — all route through
765
+ // the canonical pipeline, so without this record none of them can resolve
766
+ // the confirmation. Surface it rather than swallowing: for a guardian's
767
+ // own confirmation a bound principal should always be present.
768
+ log.warn(
769
+ { err, conversationId, requestId: msg.requestId },
770
+ "Could not create canonical guardian request for confirmation; channel guardian decisions will not work for it",
771
+ );
772
+ } else {
773
+ log.debug(
774
+ { err, conversationId },
775
+ "Failed to create canonical request from broadcast",
776
+ );
777
+ }
764
778
  }
765
779
  }
@@ -210,6 +210,18 @@ describe("ROUTES policy declarations", () => {
210
210
  expect(route!.policy!.requiredScopes).toContain("chat.write");
211
211
  });
212
212
 
213
+ test("platform/status is readable by browser actors", async () => {
214
+ const { ROUTES } = await import("../../routes/index.js");
215
+ const route = ROUTES.find(
216
+ (r) => r.endpoint === "platform/status" && r.method === "GET",
217
+ );
218
+ expect(route).toBeDefined();
219
+ expect(route!.policy).not.toBeNull();
220
+ expect(route!.policy!.allowedPrincipalTypes).toContain("actor");
221
+ expect(route!.policy!.allowedPrincipalTypes).toContain("local");
222
+ expect(route!.policy!.requiredScopes).toContain("settings.read");
223
+ });
224
+
213
225
  test("confirm declares an approval-write policy", async () => {
214
226
  const { ROUTES } = await import("../../routes/index.js");
215
227
  const route = ROUTES.find((r) => r.endpoint === "confirm");
@@ -27,10 +27,7 @@ export function requireBoundGuardian(
27
27
  // No guardian yet — in pre-bootstrap state, allow through
28
28
  return null;
29
29
  }
30
- if (
31
- (guardianResult.channel.externalUserId ??
32
- guardianResult.contact.principalId) !== authContext.actorPrincipalId
33
- ) {
30
+ if (guardianResult.channel.address !== authContext.actorPrincipalId) {
34
31
  return httpError(
35
32
  "FORBIDDEN",
36
33
  "Actor is not the bound guardian for this channel",