@vellumai/assistant 0.9.0 → 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 (572) hide show
  1. package/ARCHITECTURE.md +18 -34
  2. package/bun.lock +7 -8
  3. package/docs/activation-funnel-telemetry.md +28 -22
  4. package/docs/architecture/security.md +29 -28
  5. package/docs/stt-provider-onboarding.md +3 -5
  6. package/docs/workflows-testing.md +13 -44
  7. package/docs/workflows.md +3 -5
  8. package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
  9. package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
  10. package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
  11. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
  12. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  13. package/node_modules/@vellumai/gateway-client/src/index.ts +32 -6
  14. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
  15. package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
  16. package/openapi.yaml +976 -63
  17. package/package.json +2 -1
  18. package/scripts/sync-llm-catalog.ts +6 -15
  19. package/scripts/sync-web-search-catalog.ts +3 -11
  20. package/src/__tests__/access-request-card-view.test.ts +98 -0
  21. package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
  22. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +72 -32
  23. package/src/__tests__/agent-loop-compaction-strip.test.ts +241 -0
  24. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
  25. package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
  26. package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
  27. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
  28. package/src/__tests__/app-compiler.test.ts +15 -1
  29. package/src/__tests__/app-dir-path-guard.test.ts +0 -1
  30. package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
  31. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
  32. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  33. package/src/__tests__/avatar-identity-sync.test.ts +2 -27
  34. package/src/__tests__/btw-routes.test.ts +6 -8
  35. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  36. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  37. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  38. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  39. package/src/__tests__/checker.test.ts +0 -3
  40. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  41. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  42. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  43. package/src/__tests__/config-loader-backfill.test.ts +268 -27
  44. package/src/__tests__/config-schema.test.ts +35 -0
  45. package/src/__tests__/config-watcher.test.ts +0 -18
  46. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  47. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  48. package/src/__tests__/contacts-tools.test.ts +29 -0
  49. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
  50. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  51. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  52. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  53. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  54. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  55. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  56. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  57. package/src/__tests__/conversation-title-service.test.ts +62 -0
  58. package/src/__tests__/credential-broker.test.ts +449 -1
  59. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  60. package/src/__tests__/credential-execution-tools.test.ts +0 -1
  61. package/src/__tests__/credential-prompt-route.test.ts +4 -4
  62. package/src/__tests__/credential-routes.test.ts +360 -0
  63. package/src/__tests__/credential-security-invariants.test.ts +4 -13
  64. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  65. package/src/__tests__/disk-usage.test.ts +65 -0
  66. package/src/__tests__/dynamic-page-surface.test.ts +152 -1
  67. package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
  68. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  69. package/src/__tests__/gateway-only-guard.test.ts +3 -7
  70. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  71. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  72. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  73. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  74. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  75. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  76. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  77. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  78. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  79. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  80. package/src/__tests__/identity-routes.test.ts +0 -189
  81. package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
  82. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  83. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  84. package/src/__tests__/invite-redemption-service.test.ts +4 -7
  85. package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
  86. package/src/__tests__/llm-catalog-parity.test.ts +30 -23
  87. package/src/__tests__/llm-resolver.test.ts +70 -24
  88. package/src/__tests__/llm-schema.test.ts +1 -0
  89. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  90. package/src/__tests__/mcp-health-check.test.ts +6 -7
  91. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  92. package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
  93. package/src/__tests__/onboarding-persona-write.test.ts +1 -1
  94. package/src/__tests__/path-policy.test.ts +34 -0
  95. package/src/__tests__/persona-resolver.test.ts +49 -14
  96. package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
  97. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  98. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  99. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  100. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  101. package/src/__tests__/reaction-persistence.test.ts +150 -29
  102. package/src/__tests__/registry.test.ts +2 -7
  103. package/src/__tests__/relay-server.test.ts +285 -0
  104. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  105. package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
  106. package/src/__tests__/schedule-routes.test.ts +0 -30
  107. package/src/__tests__/schedule-tools.test.ts +2 -18
  108. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  109. package/src/__tests__/skill-execute-input.test.ts +51 -1
  110. package/src/__tests__/skill-runtime-path.test.ts +2 -3
  111. package/src/__tests__/skills.test.ts +51 -0
  112. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  113. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  114. package/src/__tests__/subagent-tools.test.ts +266 -0
  115. package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
  116. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  117. package/src/__tests__/title-generate-hook.test.ts +100 -3
  118. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
  119. package/src/__tests__/token-manager.test.ts +519 -0
  120. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  121. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  122. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  123. package/src/__tests__/tool-executor.test.ts +0 -79
  124. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  125. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  126. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
  127. package/src/__tests__/trusted-contact-verification.test.ts +8 -10
  128. package/src/__tests__/twilio-routes.test.ts +81 -1
  129. package/src/__tests__/voice-invite-redemption.test.ts +2 -3
  130. package/src/__tests__/weak-open-model.test.ts +30 -0
  131. package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
  132. package/src/__tests__/workspace-greetings.test.ts +152 -0
  133. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  134. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  135. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  136. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  137. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  138. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  139. package/src/agent/loop.ts +49 -29
  140. package/src/api/README.md +6 -6
  141. package/src/api/events/tool-result.ts +6 -0
  142. package/src/api/events/workflow-completed.ts +53 -0
  143. package/src/api/events/workflow-leaf-finished.ts +38 -0
  144. package/src/api/events/workflow-leaf-started.ts +35 -0
  145. package/src/api/events/workflow-progress.ts +32 -0
  146. package/src/api/events/workflow-started.ts +31 -0
  147. package/src/api/index.ts +40 -0
  148. package/src/api/responses/conversation-message.ts +28 -4
  149. package/src/api/responses/home.ts +26 -4
  150. package/src/api/responses/workflow-journal.ts +53 -0
  151. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  152. package/src/approvals/guardian-decision-primitive.ts +26 -3
  153. package/src/approvals/guardian-request-resolvers.ts +183 -80
  154. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  155. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  156. package/src/calls/call-pointer-messages.ts +10 -4
  157. package/src/calls/channel-admission-reader.ts +104 -0
  158. package/src/calls/guardian-dispatch.ts +17 -45
  159. package/src/calls/media-stream-server.ts +84 -2
  160. package/src/calls/relay-access-wait.ts +1 -1
  161. package/src/calls/relay-server.ts +66 -0
  162. package/src/calls/relay-setup-router.ts +82 -1
  163. package/src/calls/twilio-routes.ts +17 -8
  164. package/src/calls/voice-session-bridge.ts +2 -2
  165. package/src/cli/commands/clients.ts +3 -0
  166. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  167. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  168. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  169. package/src/cli/commands/memory/index.ts +30 -0
  170. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  171. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  172. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  173. package/src/cli/commands/oauth/status.test.ts +36 -0
  174. package/src/cli/commands/oauth/status.ts +23 -3
  175. package/src/cli/commands/plugins.ts +197 -4
  176. package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
  177. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  178. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
  179. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  180. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
  181. package/src/cli/lib/diff-plugin.ts +346 -0
  182. package/src/cli/lib/inspect-plugin.ts +12 -1
  183. package/src/cli/lib/install-from-github.ts +105 -17
  184. package/src/cli/lib/merge-plugin-tree.ts +328 -0
  185. package/src/cli/lib/plugin-fingerprint.ts +14 -0
  186. package/src/cli/lib/plugin-surfaces.ts +104 -0
  187. package/src/cli/lib/upgrade-plugin.ts +298 -10
  188. package/src/cli/program.ts +2 -6
  189. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  190. package/src/config/assistant-feature-flags.ts +22 -7
  191. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  192. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  193. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  194. package/src/config/bundled-skills/subagent/SKILL.md +4 -0
  195. package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
  196. package/src/config/bundled-skills/workflows/SKILL.md +14 -8
  197. package/src/config/bundled-tool-registry.ts +2 -7
  198. package/src/config/call-site-defaults.ts +15 -2
  199. package/src/config/feature-flag-registry.json +46 -31
  200. package/src/config/inference-profile-validation.ts +26 -0
  201. package/src/config/llm-resolver.ts +3 -0
  202. package/src/config/loader.ts +4 -0
  203. package/src/config/memory-v3-gate.ts +11 -0
  204. package/src/config/profile-order.ts +28 -0
  205. package/src/config/schema.ts +8 -6
  206. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  207. package/src/config/schemas/call-site-catalog.ts +7 -0
  208. package/src/config/schemas/channels.ts +11 -0
  209. package/src/config/schemas/elevenlabs.ts +0 -1
  210. package/src/config/schemas/llm.ts +31 -0
  211. package/src/config/schemas/memory-lifecycle.ts +3 -7
  212. package/src/config/schemas/memory-v3.ts +6 -0
  213. package/src/config/schemas/platform.ts +0 -8
  214. package/src/config/schemas/services.ts +18 -0
  215. package/src/config/seed-inference-profiles.ts +109 -44
  216. package/src/config/skills.ts +21 -0
  217. package/src/config/sync-gated-profiles.ts +220 -0
  218. package/src/contacts/contact-store.ts +89 -106
  219. package/src/contacts/contacts-write.ts +5 -22
  220. package/src/contacts/types.ts +0 -1
  221. package/src/context/compactor.ts +88 -54
  222. package/src/context/strip-injections.ts +58 -10
  223. package/src/context/token-estimator.ts +1 -1
  224. package/src/credential-execution/process-manager.ts +55 -14
  225. package/src/credential-execution/prompted-credential.ts +2 -3
  226. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  227. package/src/daemon/config-watcher.ts +0 -4
  228. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  229. package/src/daemon/conversation-agent-loop.ts +114 -22
  230. package/src/daemon/conversation-history.ts +1 -1
  231. package/src/daemon/conversation-lifecycle.ts +3 -5
  232. package/src/daemon/conversation-process.ts +13 -5
  233. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  234. package/src/daemon/conversation-slash.ts +2 -23
  235. package/src/daemon/conversation-surfaces.ts +26 -0
  236. package/src/daemon/conversation-tool-setup.ts +27 -14
  237. package/src/daemon/conversation.ts +66 -14
  238. package/src/daemon/disk-pressure-policy.ts +5 -3
  239. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  240. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  241. package/src/daemon/handlers/config-a2a.ts +0 -2
  242. package/src/daemon/handlers/config-channels.ts +15 -16
  243. package/src/daemon/handlers/config-slack-channel.ts +22 -3
  244. package/src/daemon/handlers/conversations.ts +107 -0
  245. package/src/daemon/host-browser-proxy.ts +41 -0
  246. package/src/daemon/lifecycle.ts +55 -27
  247. package/src/daemon/message-provenance.ts +2 -0
  248. package/src/daemon/message-types/contacts.ts +0 -1
  249. package/src/daemon/message-types/conversations.ts +3 -3
  250. package/src/daemon/message-types/sync.ts +0 -1
  251. package/src/daemon/message-types/web-activity.ts +7 -1
  252. package/src/daemon/message-types/workflows.ts +83 -1
  253. package/src/daemon/orphan-reaper.test.ts +0 -19
  254. package/src/daemon/orphan-reaper.ts +2 -24
  255. package/src/daemon/server.ts +0 -10
  256. package/src/daemon/tool-setup-types.ts +4 -0
  257. package/src/daemon/trust-context.ts +1 -1
  258. package/src/events/tool-audit-listener.ts +2 -2
  259. package/src/home/feed-source-enrichment.test.ts +151 -0
  260. package/src/home/feed-source-enrichment.ts +176 -0
  261. package/src/home/relationship-state.ts +2 -4
  262. package/src/instrument.ts +18 -6
  263. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  264. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  265. package/src/ipc/assistant-server.ts +37 -4
  266. package/src/ipc/gateway-flag-listener.ts +18 -2
  267. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  268. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  269. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  270. package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
  271. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  272. package/src/memory/auth-fallback-events-store.ts +2 -2
  273. package/src/memory/auto-analysis-enqueue.ts +3 -5
  274. package/src/memory/bookmark-crud.ts +1 -2
  275. package/src/memory/canonical-guardian-store.ts +39 -1
  276. package/src/memory/conversation-crud.ts +9 -4
  277. package/src/memory/conversation-key-store.ts +17 -2
  278. package/src/memory/conversation-title-service.ts +64 -7
  279. package/src/memory/db-init.ts +17 -17
  280. package/src/memory/embedding-backend.ts +38 -1
  281. package/src/memory/embedding-billing-breaker.ts +96 -0
  282. package/src/memory/jobs-store.ts +25 -13
  283. package/src/memory/jobs-worker.ts +54 -1
  284. package/src/memory/lifecycle-events-store.ts +2 -2
  285. package/src/memory/memory-retrospective-constants.ts +4 -4
  286. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  287. package/src/memory/memory-retrospective-job.ts +28 -227
  288. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  289. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  290. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  291. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
  292. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  293. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  294. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  295. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  296. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  297. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  298. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  299. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  300. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  301. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
  302. package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
  303. package/src/memory/migrations/index.ts +6 -0
  304. package/src/memory/migrations/run-migrations.ts +41 -0
  305. package/src/memory/migrations/validate-migration-state.ts +1 -1
  306. package/src/memory/onboarding-events-store.ts +3 -3
  307. package/src/memory/schema/contacts.ts +0 -5
  308. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  309. package/src/memory/skill-loaded-events-store.ts +2 -2
  310. package/src/memory/tool-executed-events-store.test.ts +7 -7
  311. package/src/memory/turn-trace-store.test.ts +736 -0
  312. package/src/memory/turn-trace-store.ts +364 -0
  313. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  314. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  315. package/src/memory/v2/consolidation-job.ts +2 -2
  316. package/src/memory/v2/skill-content.ts +25 -7
  317. package/src/memory/v2/skill-store.ts +7 -1
  318. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  319. package/src/memory/v3-eval/eval-packets.ts +546 -0
  320. package/src/messaging/providers/slack/adapter.ts +1 -1
  321. package/src/messaging/providers/slack/api.ts +31 -0
  322. package/src/messaging/providers/slack/send.test.ts +114 -2
  323. package/src/messaging/providers/slack/send.ts +30 -7
  324. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  325. package/src/messaging/providers/slack/withdraw.ts +161 -0
  326. package/src/notifications/AGENTS.md +2 -0
  327. package/src/notifications/access-request-copy.ts +72 -59
  328. package/src/notifications/adapters/shared.ts +29 -0
  329. package/src/notifications/adapters/slack.ts +58 -103
  330. package/src/notifications/adapters/telegram.ts +2 -20
  331. package/src/notifications/approval-card-data.ts +333 -0
  332. package/src/notifications/broadcaster.ts +16 -3
  333. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  334. package/src/notifications/copy-composer.ts +3 -3
  335. package/src/notifications/decision-engine.ts +4 -2
  336. package/src/notifications/destination-resolver.ts +4 -6
  337. package/src/notifications/guardian-question-mode.ts +10 -0
  338. package/src/notifications/home-feed-side-effect.ts +7 -16
  339. package/src/notifications/notification-utils.ts +19 -20
  340. package/src/notifications/signal.ts +79 -43
  341. package/src/notifications/types.ts +98 -121
  342. package/src/oauth/AGENTS.md +5 -24
  343. package/src/permissions/checker.test.ts +51 -0
  344. package/src/permissions/checker.ts +185 -26
  345. package/src/permissions/ipc-risk-types.ts +24 -0
  346. package/src/permissions/question-prompter.test.ts +27 -0
  347. package/src/permissions/question-prompter.ts +4 -0
  348. package/src/platform/client.test.ts +119 -0
  349. package/src/platform/client.ts +66 -0
  350. package/src/platform/consent-cache.test.ts +267 -0
  351. package/src/platform/consent-cache.ts +174 -0
  352. package/src/plugin-api/constants.ts +1 -1
  353. package/src/plugin-api/index.ts +33 -1
  354. package/src/plugin-api/model-profiles.ts +33 -0
  355. package/src/plugin-api/types.ts +50 -2
  356. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  357. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  358. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  359. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  360. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  361. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  362. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  363. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  364. package/src/plugins/defaults/advisor/config.ts +21 -0
  365. package/src/plugins/defaults/advisor/consult.ts +93 -0
  366. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  367. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  368. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  369. package/src/plugins/defaults/advisor/package.json +14 -0
  370. package/src/plugins/defaults/advisor/steering.ts +67 -0
  371. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  372. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  373. package/src/plugins/defaults/index.ts +60 -0
  374. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  375. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  376. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  377. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  378. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  379. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  380. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
  381. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  382. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  383. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  384. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  385. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
  386. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  387. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  388. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  389. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
  390. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
  391. package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
  392. package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
  393. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
  394. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  395. package/src/prompts/persona-resolver.ts +14 -4
  396. package/src/prompts/templates/system-sections.ts +7 -2
  397. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  398. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  399. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  400. package/src/providers/atlascloud/client.ts +85 -0
  401. package/src/providers/fetch-provider-catalog.ts +85 -0
  402. package/src/providers/inference/adapter-factory.ts +3 -0
  403. package/src/providers/model-catalog.ts +58 -0
  404. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  405. package/src/providers/openai/chat-completions-provider.ts +7 -0
  406. package/src/providers/openai/responses-provider.ts +10 -0
  407. package/src/providers/provider-send-message.ts +11 -3
  408. package/src/providers/retry.ts +53 -12
  409. package/src/providers/search-provider-catalog.ts +10 -0
  410. package/src/providers/weak-open-model.ts +22 -0
  411. package/src/runtime/AGENTS.md +0 -1
  412. package/src/runtime/__tests__/agent-wake.test.ts +181 -0
  413. package/src/runtime/__tests__/client-health.test.ts +44 -0
  414. package/src/runtime/access-request-helper.ts +21 -53
  415. package/src/runtime/actor-trust-resolver.ts +59 -63
  416. package/src/runtime/agent-wake.ts +52 -0
  417. package/src/runtime/assistant-event-hub.ts +18 -4
  418. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  419. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  420. package/src/runtime/btw-sidechain.ts +3 -6
  421. package/src/runtime/capabilities.test.ts +120 -0
  422. package/src/runtime/capabilities.ts +197 -0
  423. package/src/runtime/channel-approval-types.ts +22 -45
  424. package/src/runtime/channel-invite-transports/telegram.ts +4 -4
  425. package/src/runtime/channel-retry-sweep.ts +1 -0
  426. package/src/runtime/channel-verification-service.ts +3 -3
  427. package/src/runtime/client-health.ts +26 -0
  428. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  429. package/src/runtime/effective-capabilities.test.ts +128 -0
  430. package/src/runtime/effective-capabilities.ts +84 -0
  431. package/src/runtime/guardian-reply-router.ts +106 -21
  432. package/src/runtime/invite-redemption-service.ts +9 -25
  433. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
  434. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  435. package/src/runtime/pending-interactions.ts +15 -0
  436. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  437. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  438. package/src/runtime/routes/__tests__/plugins-routes.test.ts +240 -1
  439. package/src/runtime/routes/app-routes.ts +1 -1
  440. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
  441. package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
  442. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  443. package/src/runtime/routes/btw-routes.ts +1 -27
  444. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  445. package/src/runtime/routes/client-routes.ts +10 -0
  446. package/src/runtime/routes/contact-routes.ts +31 -8
  447. package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
  448. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  449. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  450. package/src/runtime/routes/conversation-routes.ts +39 -14
  451. package/src/runtime/routes/credential-routes.ts +40 -16
  452. package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
  453. package/src/runtime/routes/events-routes.ts +1 -3
  454. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  455. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  456. package/src/runtime/routes/home-feed-routes.ts +8 -3
  457. package/src/runtime/routes/identity-routes.ts +1 -296
  458. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  459. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
  460. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  461. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  462. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  463. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  464. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  465. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  466. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  467. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  468. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  469. package/src/runtime/routes/index.ts +2 -0
  470. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  471. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  472. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  473. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  474. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  475. package/src/runtime/routes/notification-routes.ts +122 -133
  476. package/src/runtime/routes/platform-routes.ts +2 -2
  477. package/src/runtime/routes/plugins-routes.ts +202 -3
  478. package/src/runtime/routes/schedule-routes.ts +0 -22
  479. package/src/runtime/routes/secret-routes.ts +10 -0
  480. package/src/runtime/routes/surface-action-routes.ts +2 -1
  481. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  482. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  483. package/src/runtime/routes/workflow-routes.test.ts +229 -44
  484. package/src/runtime/routes/workflow-routes.ts +131 -29
  485. package/src/runtime/routes/workspace-greetings.ts +55 -0
  486. package/src/runtime/sync/resource-sync-events.ts +1 -11
  487. package/src/runtime/tool-grant-request-helper.ts +18 -16
  488. package/src/runtime/trust-context-resolver.ts +8 -5
  489. package/src/schedule/inference-profile.ts +2 -14
  490. package/src/schedule/schedule-store.ts +1 -1
  491. package/src/schedule/scheduler-types.ts +5 -1
  492. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  493. package/src/security/secret-patterns.ts +3 -0
  494. package/src/subagent/manager.ts +17 -4
  495. package/src/subagent/types.ts +6 -0
  496. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  497. package/src/telemetry/trace-collection-policy.ts +30 -0
  498. package/src/telemetry/types.ts +89 -0
  499. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  500. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  501. package/src/tools/AGENTS.md +3 -3
  502. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  503. package/src/tools/browser/browser-execution.ts +30 -19
  504. package/src/tools/document/document-tool.ts +2 -3
  505. package/src/tools/executor.ts +5 -3
  506. package/src/tools/host-terminal/host-shell.ts +5 -4
  507. package/src/tools/memory/register.ts +2 -2
  508. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  509. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  510. package/src/tools/network/web-fetch.ts +372 -1
  511. package/src/tools/network/web-search-error.ts +1 -1
  512. package/src/tools/network/web-search.ts +213 -10
  513. package/src/tools/permission-checker.ts +4 -3
  514. package/src/tools/registry.ts +20 -0
  515. package/src/tools/schedule/create.ts +7 -12
  516. package/src/tools/schedule/update.ts +4 -11
  517. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  518. package/src/tools/side-effects.ts +2 -17
  519. package/src/tools/skills/execute.ts +33 -0
  520. package/src/tools/subagent/spawn.ts +61 -12
  521. package/src/tools/terminal/shell.ts +10 -4
  522. package/src/tools/tool-approval-handler.ts +18 -13
  523. package/src/tools/tool-manifest.ts +0 -2
  524. package/src/tools/types.ts +9 -0
  525. package/src/tools/ui-surface/definitions.ts +64 -3
  526. package/src/tools/verification-control-plane-policy.ts +3 -1
  527. package/src/tools/workflows/run-workflow.test.ts +8 -18
  528. package/src/tools/workflows/run-workflow.ts +1 -0
  529. package/src/util/disk-usage.ts +78 -23
  530. package/src/util/platform.ts +10 -3
  531. package/src/watcher/telemetry.ts +2 -2
  532. package/src/workflows/capabilities.ts +2 -3
  533. package/src/workflows/engine.test.ts +175 -1
  534. package/src/workflows/engine.ts +82 -0
  535. package/src/workflows/journal-store.test.ts +70 -0
  536. package/src/workflows/journal-store.ts +18 -3
  537. package/src/workflows/run-manager.test.ts +171 -28
  538. package/src/workflows/run-manager.ts +66 -24
  539. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  540. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  541. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  542. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  543. package/src/workspace/migrations/registry.ts +8 -0
  544. package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
  545. package/src/__tests__/credential-security-e2e.test.ts +0 -362
  546. package/src/__tests__/credential-vault-unit.test.ts +0 -1528
  547. package/src/__tests__/credential-vault.test.ts +0 -1706
  548. package/src/__tests__/identity-intro-cache.test.ts +0 -315
  549. package/src/__tests__/secret-onetime-send.test.ts +0 -182
  550. package/src/cli/commands/__tests__/task.test.ts +0 -914
  551. package/src/cli/commands/task.ts +0 -771
  552. package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
  553. package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
  554. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
  555. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
  556. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
  557. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
  558. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
  559. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
  560. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
  561. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
  562. package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
  563. package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
  564. package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
  565. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
  566. package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
  567. package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
  568. package/src/memory/preloaded-apps.ts +0 -116
  569. package/src/notifications/tool-approval-copy.ts +0 -142
  570. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
  571. package/src/runtime/routes/identity-intro-cache.ts +0 -172
  572. package/src/tools/credentials/vault.ts +0 -712
@@ -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
+ }
@@ -22,7 +22,6 @@ GET handlers must be safe and side-effect-free — they must not enqueue backgro
22
22
 
23
23
  Accepted exceptions (stale-while-revalidate caches): a GET handler may kick off a bounded, fire-and-forget background refresh of a generated-content cache when no fresh cache exists, provided the handler itself stays read-only and returns immediately with cached/fallback copy, the refresh is single-flight (concurrent GETs share one regeneration), and a TTL bounds regeneration frequency. Current instances:
24
24
 
25
- - `GET /v1/identity/intro` — refreshes the generated greeting cache; the background prompt may only depend on static identity/soul context plus caller-supplied local hour/minute.
26
25
  - `GET /v1/home/feed` — refreshes the personalized home greeting and suggested-prompt caches via `revalidateHomeContentInBackground()`, which publishes `home_feed_updated` when fresh content lands so clients refetch. This is intentional: home content is generated on demand (when a user actually views Home), never at daemon startup or on a timer.
27
26
  - `GET /v1/conversation-starters` — enqueues a `generate_conversation_starters` memory job when the starter set is stale, cooldown-gated and deduped against in-flight jobs.
28
27
 
@@ -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,13 +8,17 @@
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";
15
20
  import {
16
21
  findContactByAddress,
17
- findContactByChannelExternalId,
18
22
  findGuardianForChannel,
19
23
  } from "../contacts/contact-store.js";
20
24
  import type { ContactChannel, ContactWithChannels } from "../contacts/types.js";
@@ -39,22 +43,35 @@ export type { TrustContext } from "../daemon/trust-context.js";
39
43
  * - `'trusted_contact'`: The sender is an active contact with a channel
40
44
  * (not the guardian). Trusted contacts can invoke tools but require
41
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.
42
50
  * - `'unknown'`: The sender has no contact record, no identity could be
43
- * established, or the sender is an inactive/revoked contact. Unknown
51
+ * established, or the sender is a blocked/revoked contact. Unknown
44
52
  * actors are fail-closed with no escalation path.
45
53
  */
46
- export type TrustClass = "guardian" | "trusted_contact" | "unknown";
54
+ export type TrustClass =
55
+ | "guardian"
56
+ | "trusted_contact"
57
+ | "unverified_contact"
58
+ | "unknown";
47
59
 
48
- /** Returns `true` for actors that are not fully trusted (i.e. not the guardian). */
49
- export function isUntrustedTrustClass(
50
- trustClass: TrustClass | undefined,
51
- ): boolean {
52
- return (
53
- trustClass === "trusted_contact" ||
54
- trustClass === "unknown" ||
55
- trustClass === undefined
56
- );
57
- }
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
+ };
58
75
 
59
76
  /**
60
77
  * Fully resolved trust context from the actor trust resolver.
@@ -181,19 +198,13 @@ export function resolveActorTrust(
181
198
  if (guardianResult) {
182
199
  const { contact: guardianContact, channel: guardianChannel } =
183
200
  guardianResult;
184
- const canonicalGuardianId = guardianChannel.externalUserId
185
- ? canonicalizeInboundIdentity(
186
- input.sourceChannel,
187
- guardianChannel.externalUserId,
188
- )
189
- : null;
190
201
  guardianBindingMatch = {
191
- guardianExternalUserId: guardianChannel.externalUserId ?? "",
202
+ guardianExternalUserId: guardianChannel.address,
192
203
  guardianDeliveryChatId: guardianChannel.externalChatId,
193
204
  };
194
205
  guardianPrincipalId = guardianContact.principalId ?? undefined;
195
206
  isGuardian =
196
- canonicalGuardianId != null && canonicalGuardianId === canonicalSenderId;
207
+ guardianChannel.address.toLowerCase() === canonicalSenderId.toLowerCase();
197
208
  }
198
209
 
199
210
  log.debug(
@@ -206,37 +217,18 @@ export function resolveActorTrust(
206
217
  );
207
218
 
208
219
  // --- Member lookup via contacts ---
209
- // Primary path: match by externalUserId (populated after channel verification
210
- // completes, or for channels registered via the verification upsert path).
211
- // Fallback path: match by address (covers channels registered by the inbound
212
- // name-capture flow, where address is set but externalUserId remains NULL
213
- // until the DTMF challenge succeeds). Mirrors the gateway's OR-based lookup
214
- // in ContactStore.getContactByPhoneNumber so the runtime's unverified-caller
215
- // guard fires for pre-verification channels the gateway passes through.
216
220
  let memberRecord: ActorTrustContext["memberRecord"] = null;
217
- const byExternalId = findContactByChannelExternalId(
221
+ const byAddress = findContactByAddress(
218
222
  input.sourceChannel,
219
223
  canonicalSenderId,
220
224
  );
221
- const byExternalIdChannel = byExternalId?.channels.find(
225
+ const byAddressChannel = byAddress?.channels.find(
222
226
  (ch) =>
223
227
  ch.type === input.sourceChannel &&
224
- ch.externalUserId === canonicalSenderId,
228
+ ch.address.toLowerCase() === canonicalSenderId.toLowerCase(),
225
229
  );
226
-
227
- if (byExternalId && byExternalIdChannel) {
228
- memberRecord = { contact: byExternalId, channel: byExternalIdChannel };
229
- } else {
230
- // Address fallback: catches channels where externalUserId is not yet set.
231
- const byAddress = findContactByAddress(input.sourceChannel, canonicalSenderId);
232
- const byAddressChannel = byAddress?.channels.find(
233
- (ch) =>
234
- ch.type === input.sourceChannel &&
235
- ch.address?.toLowerCase() === canonicalSenderId.toLowerCase(),
236
- );
237
- if (byAddress && byAddressChannel) {
238
- memberRecord = { contact: byAddress, channel: byAddressChannel };
239
- }
230
+ if (byAddress && byAddressChannel) {
231
+ memberRecord = { contact: byAddress, channel: byAddressChannel };
240
232
  }
241
233
 
242
234
  log.debug(
@@ -244,23 +236,16 @@ export function resolveActorTrust(
244
236
  channel: input.sourceChannel,
245
237
  canonicalSenderId,
246
238
  found: !!memberRecord,
247
- via: memberRecord?.channel.externalUserId ? "externalUserId" : memberRecord ? "address" : "none",
239
+ via: memberRecord ? "address" : "none",
248
240
  },
249
241
  "trust-resolver member lookup",
250
242
  );
251
243
 
252
244
  // Only use member metadata when the record's channel identity matches the
253
245
  // current sender to avoid misidentification in group chats.
254
- // Primary check: externalUserId (canonicalized to handle E.164 variance).
255
- // Fallback: address match for channels where externalUserId is NULL (e.g.
256
- // name-capture registrations that haven't completed DTMF verification yet).
257
- const memberMatchesSender = memberRecord?.channel.externalUserId
258
- ? canonicalizeInboundIdentity(
259
- input.sourceChannel,
260
- memberRecord.channel.externalUserId,
261
- ) === canonicalSenderId
262
- : (memberRecord?.channel.address?.toLowerCase() ===
263
- canonicalSenderId.toLowerCase());
246
+ const memberMatchesSender =
247
+ memberRecord?.channel.address.toLowerCase() ===
248
+ canonicalSenderId.toLowerCase();
264
249
 
265
250
  const memberDisplayName =
266
251
  memberMatchesSender &&
@@ -281,12 +266,23 @@ export function resolveActorTrust(
281
266
  let trustClass: TrustClass;
282
267
  if (isGuardian) {
283
268
  trustClass = "guardian";
284
- } else if (
285
- memberMatchesSender &&
286
- memberRecord &&
287
- memberRecord.channel.status === "active"
288
- ) {
289
- 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
+ }
290
286
  } else {
291
287
  trustClass = "unknown";
292
288
  }