@vellumai/assistant 0.9.0 → 0.10.0-dev.202606192226.8ed8a6a

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 (624) hide show
  1. package/ARCHITECTURE.md +19 -35
  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 +1128 -119
  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__/agent-loop-pushes-post-hook-prompt.test.ts +306 -0
  28. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
  29. package/src/__tests__/app-compiler.test.ts +15 -1
  30. package/src/__tests__/app-dir-path-guard.test.ts +0 -1
  31. package/src/__tests__/approval-cascade.test.ts +6 -0
  32. package/src/__tests__/approval-routes-http.test.ts +128 -0
  33. package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
  34. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
  35. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  36. package/src/__tests__/avatar-identity-sync.test.ts +2 -27
  37. package/src/__tests__/btw-routes.test.ts +6 -8
  38. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  39. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  40. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  41. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  42. package/src/__tests__/checker.test.ts +0 -3
  43. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  44. package/src/__tests__/compaction-events.test.ts +6 -0
  45. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  46. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  47. package/src/__tests__/config-loader-backfill.test.ts +268 -27
  48. package/src/__tests__/config-schema.test.ts +35 -0
  49. package/src/__tests__/config-watcher.test.ts +0 -18
  50. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  51. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  52. package/src/__tests__/contacts-tools.test.ts +29 -0
  53. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
  54. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
  55. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  56. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  57. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  58. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -0
  59. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  60. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  61. package/src/__tests__/conversation-process-callsite.test.ts +6 -0
  62. package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
  63. package/src/__tests__/conversation-queue.test.ts +6 -0
  64. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  65. package/src/__tests__/conversation-slash-queue.test.ts +6 -0
  66. package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
  67. package/src/__tests__/conversation-speed-override.test.ts +6 -0
  68. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  69. package/src/__tests__/conversation-title-service.test.ts +62 -0
  70. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -0
  71. package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
  72. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
  73. package/src/__tests__/credential-broker.test.ts +449 -1
  74. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  75. package/src/__tests__/credential-execution-tools.test.ts +0 -1
  76. package/src/__tests__/credential-prompt-route.test.ts +4 -4
  77. package/src/__tests__/credential-routes.test.ts +360 -0
  78. package/src/__tests__/credential-security-invariants.test.ts +4 -13
  79. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  80. package/src/__tests__/disk-pressure-tools.test.ts +41 -1
  81. package/src/__tests__/disk-usage.test.ts +65 -0
  82. package/src/__tests__/document-tool-security.test.ts +9 -6
  83. package/src/__tests__/document-update-default-surface.test.ts +202 -0
  84. package/src/__tests__/dynamic-page-surface.test.ts +152 -1
  85. package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
  86. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  87. package/src/__tests__/gateway-only-guard.test.ts +3 -7
  88. package/src/__tests__/gemini-count-tokens.test.ts +70 -0
  89. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  90. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  91. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  92. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  93. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  94. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  95. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  96. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  97. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  98. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  99. package/src/__tests__/identity-routes.test.ts +0 -189
  100. package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
  101. package/src/__tests__/injector-disk-pressure.test.ts +10 -5
  102. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  103. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  104. package/src/__tests__/invite-redemption-service.test.ts +4 -7
  105. package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
  106. package/src/__tests__/llm-catalog-parity.test.ts +30 -23
  107. package/src/__tests__/llm-resolver.test.ts +70 -24
  108. package/src/__tests__/llm-schema.test.ts +1 -0
  109. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  110. package/src/__tests__/mcp-health-check.test.ts +6 -7
  111. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  112. package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
  113. package/src/__tests__/onboarding-persona-write.test.ts +1 -1
  114. package/src/__tests__/path-policy.test.ts +34 -0
  115. package/src/__tests__/persona-resolver.test.ts +49 -14
  116. package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
  117. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  118. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  119. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  120. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  121. package/src/__tests__/reaction-persistence.test.ts +150 -29
  122. package/src/__tests__/registry.test.ts +2 -7
  123. package/src/__tests__/relay-server.test.ts +285 -0
  124. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  125. package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
  126. package/src/__tests__/schedule-routes.test.ts +0 -30
  127. package/src/__tests__/schedule-tools.test.ts +2 -18
  128. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  129. package/src/__tests__/secret-response-routing.test.ts +35 -0
  130. package/src/__tests__/secret-routes-platform-proxy.test.ts +16 -2
  131. package/src/__tests__/skill-execute-input.test.ts +177 -1
  132. package/src/__tests__/skill-load-tool.test.ts +72 -0
  133. package/src/__tests__/skill-runtime-path.test.ts +2 -3
  134. package/src/__tests__/skills.test.ts +51 -0
  135. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  136. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  137. package/src/__tests__/subagent-tools.test.ts +266 -0
  138. package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
  139. package/src/__tests__/system-storage-cleanup-skill.test.ts +56 -0
  140. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  141. package/src/__tests__/title-generate-hook.test.ts +100 -3
  142. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
  143. package/src/__tests__/token-manager.test.ts +519 -0
  144. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  145. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  146. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  147. package/src/__tests__/tool-executor.test.ts +0 -79
  148. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  149. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  150. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
  151. package/src/__tests__/trusted-contact-verification.test.ts +8 -10
  152. package/src/__tests__/twilio-routes.test.ts +81 -1
  153. package/src/__tests__/voice-invite-redemption.test.ts +2 -3
  154. package/src/__tests__/weak-open-model.test.ts +30 -0
  155. package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
  156. package/src/__tests__/workspace-greetings.test.ts +152 -0
  157. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  158. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  159. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  160. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  161. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  162. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  163. package/src/agent/loop.ts +75 -29
  164. package/src/api/README.md +6 -6
  165. package/src/api/events/tool-result.ts +6 -0
  166. package/src/api/events/workflow-completed.ts +53 -0
  167. package/src/api/events/workflow-leaf-finished.ts +38 -0
  168. package/src/api/events/workflow-leaf-started.ts +35 -0
  169. package/src/api/events/workflow-progress.ts +32 -0
  170. package/src/api/events/workflow-started.ts +31 -0
  171. package/src/api/index.ts +40 -0
  172. package/src/api/responses/conversation-message.ts +28 -4
  173. package/src/api/responses/home.ts +26 -4
  174. package/src/api/responses/workflow-journal.ts +53 -0
  175. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  176. package/src/approvals/guardian-decision-primitive.ts +26 -3
  177. package/src/approvals/guardian-request-resolvers.ts +183 -80
  178. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  179. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  180. package/src/calls/call-pointer-messages.ts +10 -4
  181. package/src/calls/channel-admission-reader.ts +104 -0
  182. package/src/calls/guardian-dispatch.ts +17 -45
  183. package/src/calls/media-stream-server.ts +84 -2
  184. package/src/calls/relay-access-wait.ts +1 -1
  185. package/src/calls/relay-server.ts +66 -0
  186. package/src/calls/relay-setup-router.ts +82 -1
  187. package/src/calls/twilio-routes.ts +17 -8
  188. package/src/calls/voice-session-bridge.ts +2 -2
  189. package/src/cli/commands/clients.ts +3 -0
  190. package/src/cli/commands/db/status.ts +37 -3
  191. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  192. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  193. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  194. package/src/cli/commands/memory/index.ts +30 -0
  195. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  196. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  197. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  198. package/src/cli/commands/oauth/status.test.ts +36 -0
  199. package/src/cli/commands/oauth/status.ts +23 -3
  200. package/src/cli/commands/plugins.ts +400 -14
  201. package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
  202. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  203. package/src/cli/lib/__tests__/install-from-github.test.ts +31 -0
  204. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
  205. package/src/cli/lib/__tests__/plugin-pin-history.test.ts +162 -0
  206. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  207. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
  208. package/src/cli/lib/diff-plugin.ts +346 -0
  209. package/src/cli/lib/inspect-plugin.ts +12 -1
  210. package/src/cli/lib/install-from-github.ts +128 -19
  211. package/src/cli/lib/merge-plugin-tree.ts +328 -0
  212. package/src/cli/lib/plugin-fingerprint.ts +14 -0
  213. package/src/cli/lib/plugin-marketplace.ts +11 -0
  214. package/src/cli/lib/plugin-pin-history.ts +257 -0
  215. package/src/cli/lib/plugin-surfaces.ts +104 -0
  216. package/src/cli/lib/upgrade-plugin.ts +298 -10
  217. package/src/cli/program.ts +2 -6
  218. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  219. package/src/config/assistant-feature-flags.ts +22 -7
  220. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  221. package/src/config/bundled-skills/document-editor/SKILL.md +3 -3
  222. package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
  223. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  224. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  225. package/src/config/bundled-skills/subagent/SKILL.md +4 -0
  226. package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
  227. package/src/config/bundled-skills/system-storage-cleanup/SKILL.md +74 -0
  228. package/src/config/bundled-skills/workflows/SKILL.md +14 -8
  229. package/src/config/bundled-tool-registry.ts +2 -7
  230. package/src/config/call-site-defaults.ts +15 -2
  231. package/src/config/feature-flag-registry.json +46 -31
  232. package/src/config/inference-profile-validation.ts +26 -0
  233. package/src/config/llm-resolver.ts +3 -0
  234. package/src/config/loader.ts +4 -0
  235. package/src/config/memory-v3-gate.ts +11 -0
  236. package/src/config/profile-order.ts +28 -0
  237. package/src/config/schema.ts +8 -6
  238. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  239. package/src/config/schemas/call-site-catalog.ts +7 -0
  240. package/src/config/schemas/channels.ts +11 -0
  241. package/src/config/schemas/elevenlabs.ts +0 -1
  242. package/src/config/schemas/llm.ts +31 -0
  243. package/src/config/schemas/memory-lifecycle.ts +3 -7
  244. package/src/config/schemas/memory-v3.ts +6 -0
  245. package/src/config/schemas/platform.ts +0 -8
  246. package/src/config/schemas/services.ts +18 -0
  247. package/src/config/seed-inference-profiles.ts +109 -44
  248. package/src/config/skills.ts +21 -0
  249. package/src/config/sync-gated-profiles.ts +220 -0
  250. package/src/contacts/contact-store.ts +89 -106
  251. package/src/contacts/contacts-write.ts +5 -22
  252. package/src/contacts/types.ts +0 -1
  253. package/src/context/compactor.ts +88 -54
  254. package/src/context/strip-injections.ts +58 -10
  255. package/src/context/token-estimator.ts +1 -1
  256. package/src/credential-execution/process-manager.ts +55 -14
  257. package/src/credential-execution/prompted-credential.ts +2 -3
  258. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  259. package/src/daemon/__tests__/conversation-tool-setup.test.ts +44 -0
  260. package/src/daemon/config-watcher.ts +0 -4
  261. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  262. package/src/daemon/conversation-agent-loop.ts +114 -22
  263. package/src/daemon/conversation-history.ts +1 -1
  264. package/src/daemon/conversation-lifecycle.ts +3 -5
  265. package/src/daemon/conversation-process.ts +13 -5
  266. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  267. package/src/daemon/conversation-slash.ts +2 -23
  268. package/src/daemon/conversation-surfaces.ts +26 -0
  269. package/src/daemon/conversation-tool-setup.ts +53 -19
  270. package/src/daemon/conversation.ts +131 -20
  271. package/src/daemon/disk-pressure-policy.ts +5 -3
  272. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  273. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  274. package/src/daemon/handlers/config-a2a.ts +0 -2
  275. package/src/daemon/handlers/config-channels.ts +15 -16
  276. package/src/daemon/handlers/config-slack-channel.ts +22 -3
  277. package/src/daemon/handlers/conversations.ts +107 -0
  278. package/src/daemon/host-browser-proxy.ts +41 -0
  279. package/src/daemon/lifecycle.ts +55 -27
  280. package/src/daemon/memory-v2-startup.test.ts +131 -0
  281. package/src/daemon/memory-v2-startup.ts +100 -1
  282. package/src/daemon/message-provenance.ts +2 -0
  283. package/src/daemon/message-types/contacts.ts +0 -1
  284. package/src/daemon/message-types/conversations.ts +3 -3
  285. package/src/daemon/message-types/sync.ts +0 -1
  286. package/src/daemon/message-types/web-activity.ts +7 -1
  287. package/src/daemon/message-types/workflows.ts +83 -1
  288. package/src/daemon/orphan-reaper.test.ts +0 -19
  289. package/src/daemon/orphan-reaper.ts +2 -24
  290. package/src/daemon/server.ts +0 -10
  291. package/src/daemon/tool-setup-types.ts +4 -0
  292. package/src/daemon/trust-context.ts +1 -1
  293. package/src/daemon/wake-conversation-ops.ts +1 -0
  294. package/src/events/tool-audit-listener.ts +2 -2
  295. package/src/home/feed-source-enrichment.test.ts +151 -0
  296. package/src/home/feed-source-enrichment.ts +176 -0
  297. package/src/home/relationship-state.ts +2 -4
  298. package/src/instrument.ts +18 -6
  299. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  300. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  301. package/src/ipc/assistant-server.ts +37 -4
  302. package/src/ipc/gateway-flag-listener.ts +18 -2
  303. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  304. package/src/memory/__tests__/db-logs-attach.test.ts +122 -0
  305. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  306. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  307. package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
  308. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  309. package/src/memory/auth-fallback-events-store.ts +2 -2
  310. package/src/memory/auto-analysis-enqueue.ts +3 -5
  311. package/src/memory/bookmark-crud.ts +1 -2
  312. package/src/memory/canonical-guardian-store.ts +39 -1
  313. package/src/memory/conversation-crud.ts +9 -4
  314. package/src/memory/conversation-key-store.ts +17 -2
  315. package/src/memory/conversation-title-service.ts +64 -7
  316. package/src/memory/db-async-query.ts +20 -2
  317. package/src/memory/db-connection.ts +49 -5
  318. package/src/memory/db-init.ts +17 -17
  319. package/src/memory/embedding-backend.ts +38 -1
  320. package/src/memory/embedding-billing-breaker.ts +96 -0
  321. package/src/memory/jobs-store.ts +25 -13
  322. package/src/memory/jobs-worker.ts +54 -1
  323. package/src/memory/lifecycle-events-store.ts +2 -2
  324. package/src/memory/memory-retrospective-constants.ts +4 -4
  325. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  326. package/src/memory/memory-retrospective-job.ts +28 -227
  327. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  328. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  329. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  330. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
  331. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  332. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  333. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  334. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  335. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  336. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  337. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  338. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  339. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  340. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
  341. package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
  342. package/src/memory/migrations/index.ts +6 -0
  343. package/src/memory/migrations/run-migrations.ts +41 -0
  344. package/src/memory/migrations/validate-migration-state.ts +1 -1
  345. package/src/memory/onboarding-events-store.ts +3 -3
  346. package/src/memory/schema/contacts.ts +0 -5
  347. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  348. package/src/memory/skill-loaded-events-store.ts +2 -2
  349. package/src/memory/tool-executed-events-store.test.ts +7 -7
  350. package/src/memory/turn-trace-store.test.ts +736 -0
  351. package/src/memory/turn-trace-store.ts +364 -0
  352. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  353. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  354. package/src/memory/v2/consolidation-job.ts +2 -2
  355. package/src/memory/v2/skill-content.ts +25 -7
  356. package/src/memory/v2/skill-store.ts +7 -1
  357. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  358. package/src/memory/v3-eval/eval-packets.ts +546 -0
  359. package/src/messaging/providers/slack/adapter.ts +1 -1
  360. package/src/messaging/providers/slack/api.ts +31 -0
  361. package/src/messaging/providers/slack/send.test.ts +114 -2
  362. package/src/messaging/providers/slack/send.ts +30 -7
  363. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  364. package/src/messaging/providers/slack/withdraw.ts +161 -0
  365. package/src/notifications/AGENTS.md +2 -0
  366. package/src/notifications/access-request-copy.ts +72 -59
  367. package/src/notifications/adapters/shared.ts +29 -0
  368. package/src/notifications/adapters/slack.ts +58 -103
  369. package/src/notifications/adapters/telegram.ts +2 -20
  370. package/src/notifications/approval-card-data.ts +333 -0
  371. package/src/notifications/broadcaster.ts +16 -3
  372. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  373. package/src/notifications/copy-composer.ts +3 -3
  374. package/src/notifications/decision-engine.ts +4 -2
  375. package/src/notifications/destination-resolver.ts +4 -6
  376. package/src/notifications/guardian-question-mode.ts +10 -0
  377. package/src/notifications/home-feed-side-effect.ts +7 -16
  378. package/src/notifications/notification-utils.ts +19 -20
  379. package/src/notifications/signal.ts +79 -43
  380. package/src/notifications/types.ts +98 -121
  381. package/src/oauth/AGENTS.md +5 -24
  382. package/src/permissions/checker.test.ts +51 -0
  383. package/src/permissions/checker.ts +185 -26
  384. package/src/permissions/ipc-risk-types.ts +24 -0
  385. package/src/permissions/question-prompter.test.ts +27 -0
  386. package/src/permissions/question-prompter.ts +4 -0
  387. package/src/permissions/secret-prompter.ts +15 -1
  388. package/src/platform/client.test.ts +119 -0
  389. package/src/platform/client.ts +66 -0
  390. package/src/platform/consent-cache.test.ts +267 -0
  391. package/src/platform/consent-cache.ts +174 -0
  392. package/src/plugin-api/constants.ts +1 -1
  393. package/src/plugin-api/index.ts +33 -1
  394. package/src/plugin-api/model-profiles.ts +33 -0
  395. package/src/plugin-api/types.ts +50 -2
  396. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  397. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  398. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  399. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  400. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  401. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  402. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  403. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  404. package/src/plugins/defaults/advisor/config.ts +21 -0
  405. package/src/plugins/defaults/advisor/consult.ts +93 -0
  406. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  407. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  408. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  409. package/src/plugins/defaults/advisor/package.json +14 -0
  410. package/src/plugins/defaults/advisor/steering.ts +67 -0
  411. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  412. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  413. package/src/plugins/defaults/compaction/window-manager.ts +18 -0
  414. package/src/plugins/defaults/index.ts +60 -0
  415. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  416. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  417. package/src/plugins/defaults/memory-retrieval/injectors.ts +4 -4
  418. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  419. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  420. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  421. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  422. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
  423. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  424. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  425. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  426. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  427. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
  428. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  429. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  430. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  431. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
  432. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
  433. package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
  434. package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
  435. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
  436. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  437. package/src/prompts/persona-resolver.ts +14 -4
  438. package/src/prompts/templates/system-sections.ts +7 -2
  439. package/src/providers/__tests__/count-tokens-forwarding.test.ts +98 -0
  440. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  441. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  442. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  443. package/src/providers/anthropic/client.ts +230 -175
  444. package/src/providers/atlascloud/client.ts +85 -0
  445. package/src/providers/call-site-routing.ts +10 -0
  446. package/src/providers/fetch-provider-catalog.ts +85 -0
  447. package/src/providers/gemini/client.ts +43 -0
  448. package/src/providers/inference/adapter-factory.ts +3 -0
  449. package/src/providers/model-catalog.ts +58 -0
  450. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  451. package/src/providers/openai/chat-completions-provider.ts +7 -0
  452. package/src/providers/openai/responses-provider.ts +10 -0
  453. package/src/providers/provider-send-message.ts +11 -3
  454. package/src/providers/ratelimit.ts +9 -0
  455. package/src/providers/retry.ts +62 -12
  456. package/src/providers/search-provider-catalog.ts +10 -0
  457. package/src/providers/types.ts +16 -0
  458. package/src/providers/usage-tracking.ts +7 -0
  459. package/src/providers/weak-open-model.ts +22 -0
  460. package/src/runtime/AGENTS.md +0 -1
  461. package/src/runtime/__tests__/agent-wake.test.ts +227 -0
  462. package/src/runtime/__tests__/client-health.test.ts +44 -0
  463. package/src/runtime/access-request-helper.ts +21 -53
  464. package/src/runtime/actor-trust-resolver.ts +59 -63
  465. package/src/runtime/agent-wake.ts +71 -0
  466. package/src/runtime/assistant-event-hub.ts +18 -4
  467. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  468. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  469. package/src/runtime/btw-sidechain.ts +3 -6
  470. package/src/runtime/capabilities.test.ts +120 -0
  471. package/src/runtime/capabilities.ts +197 -0
  472. package/src/runtime/channel-approval-types.ts +22 -45
  473. package/src/runtime/channel-invite-transports/telegram.ts +4 -4
  474. package/src/runtime/channel-retry-sweep.ts +1 -0
  475. package/src/runtime/channel-verification-service.ts +3 -3
  476. package/src/runtime/client-health.ts +26 -0
  477. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  478. package/src/runtime/effective-capabilities.test.ts +128 -0
  479. package/src/runtime/effective-capabilities.ts +84 -0
  480. package/src/runtime/guardian-reply-router.ts +106 -21
  481. package/src/runtime/invite-redemption-service.ts +9 -25
  482. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +126 -0
  483. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  484. package/src/runtime/pending-interactions.ts +35 -0
  485. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  486. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  487. package/src/runtime/routes/__tests__/plugins-routes.test.ts +383 -1
  488. package/src/runtime/routes/app-routes.ts +1 -1
  489. package/src/runtime/routes/approval-routes.ts +40 -9
  490. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
  491. package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
  492. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  493. package/src/runtime/routes/btw-routes.ts +1 -27
  494. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  495. package/src/runtime/routes/client-routes.ts +10 -0
  496. package/src/runtime/routes/contact-routes.ts +31 -8
  497. package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
  498. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  499. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  500. package/src/runtime/routes/conversation-routes.ts +39 -14
  501. package/src/runtime/routes/credential-routes.ts +40 -16
  502. package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
  503. package/src/runtime/routes/events-routes.ts +1 -3
  504. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  505. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  506. package/src/runtime/routes/home-feed-routes.ts +8 -3
  507. package/src/runtime/routes/identity-routes.ts +1 -296
  508. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  509. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
  510. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  511. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  512. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  513. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  514. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  515. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  516. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  517. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  518. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  519. package/src/runtime/routes/index.ts +2 -0
  520. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  521. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  522. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  523. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  524. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  525. package/src/runtime/routes/notification-routes.ts +122 -133
  526. package/src/runtime/routes/platform-routes.ts +2 -2
  527. package/src/runtime/routes/plugins-routes.ts +366 -11
  528. package/src/runtime/routes/schedule-routes.ts +0 -22
  529. package/src/runtime/routes/secret-routes.ts +15 -0
  530. package/src/runtime/routes/surface-action-routes.ts +2 -1
  531. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  532. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  533. package/src/runtime/routes/workflow-routes.test.ts +229 -44
  534. package/src/runtime/routes/workflow-routes.ts +131 -29
  535. package/src/runtime/routes/workspace-greetings.ts +55 -0
  536. package/src/runtime/sync/resource-sync-events.ts +1 -11
  537. package/src/runtime/tool-grant-request-helper.ts +18 -16
  538. package/src/runtime/trust-context-resolver.ts +8 -5
  539. package/src/schedule/inference-profile.ts +2 -14
  540. package/src/schedule/schedule-store.ts +1 -1
  541. package/src/schedule/scheduler-types.ts +5 -1
  542. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  543. package/src/security/secret-patterns.ts +3 -0
  544. package/src/subagent/manager.ts +17 -4
  545. package/src/subagent/types.ts +6 -0
  546. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  547. package/src/telemetry/trace-collection-policy.ts +30 -0
  548. package/src/telemetry/types.ts +89 -0
  549. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  550. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  551. package/src/tools/AGENTS.md +3 -3
  552. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  553. package/src/tools/browser/browser-execution.ts +30 -19
  554. package/src/tools/document/document-tool.ts +30 -6
  555. package/src/tools/executor.ts +5 -3
  556. package/src/tools/host-terminal/host-shell.ts +5 -4
  557. package/src/tools/memory/register.ts +2 -2
  558. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  559. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  560. package/src/tools/network/web-fetch.ts +372 -1
  561. package/src/tools/network/web-search-error.ts +1 -1
  562. package/src/tools/network/web-search.ts +213 -10
  563. package/src/tools/permission-checker.ts +4 -3
  564. package/src/tools/registry.ts +20 -0
  565. package/src/tools/schedule/create.ts +7 -12
  566. package/src/tools/schedule/update.ts +4 -11
  567. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  568. package/src/tools/side-effects.ts +2 -17
  569. package/src/tools/skills/execute.ts +99 -2
  570. package/src/tools/skills/load.ts +43 -2
  571. package/src/tools/subagent/spawn.ts +64 -15
  572. package/src/tools/terminal/shell.ts +10 -4
  573. package/src/tools/tool-approval-handler.ts +18 -13
  574. package/src/tools/tool-manifest.ts +0 -2
  575. package/src/tools/types.ts +10 -0
  576. package/src/tools/ui-surface/definitions.ts +64 -3
  577. package/src/tools/verification-control-plane-policy.ts +3 -1
  578. package/src/tools/workflows/run-workflow.test.ts +8 -18
  579. package/src/tools/workflows/run-workflow.ts +1 -0
  580. package/src/util/disk-usage.ts +78 -23
  581. package/src/util/logs-db-path.ts +22 -0
  582. package/src/util/platform.ts +10 -3
  583. package/src/watcher/telemetry.ts +2 -2
  584. package/src/workflows/capabilities.ts +2 -3
  585. package/src/workflows/engine.test.ts +175 -1
  586. package/src/workflows/engine.ts +82 -0
  587. package/src/workflows/journal-store.test.ts +70 -0
  588. package/src/workflows/journal-store.ts +18 -3
  589. package/src/workflows/run-manager.test.ts +171 -28
  590. package/src/workflows/run-manager.ts +66 -24
  591. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  592. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  593. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  594. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  595. package/src/workspace/migrations/registry.ts +8 -0
  596. package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
  597. package/src/__tests__/credential-security-e2e.test.ts +0 -362
  598. package/src/__tests__/credential-vault-unit.test.ts +0 -1528
  599. package/src/__tests__/credential-vault.test.ts +0 -1706
  600. package/src/__tests__/identity-intro-cache.test.ts +0 -315
  601. package/src/__tests__/secret-onetime-send.test.ts +0 -182
  602. package/src/cli/commands/__tests__/task.test.ts +0 -914
  603. package/src/cli/commands/task.ts +0 -771
  604. package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
  605. package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
  606. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
  607. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
  608. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
  609. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
  610. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
  611. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
  612. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
  613. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
  614. package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
  615. package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
  616. package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
  617. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
  618. package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
  619. package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
  620. package/src/memory/preloaded-apps.ts +0 -116
  621. package/src/notifications/tool-approval-copy.ts +0 -142
  622. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
  623. package/src/runtime/routes/identity-intro-cache.ts +0 -172
  624. package/src/tools/credentials/vault.ts +0 -712
@@ -8,10 +8,10 @@ mock.module("../../util/logger.js", () => ({
8
8
  }),
9
9
  }));
10
10
 
11
- // Mutable usage-data gate, flipped per-test.
12
- let collectUsageData = true;
13
- mock.module("../../config/loader.js", () => ({
14
- getConfig: () => ({ collectUsageData }),
11
+ // Mutable consent gate, flipped per-test.
12
+ let shareAnalytics = true;
13
+ mock.module("../../platform/consent-cache.js", () => ({
14
+ getCachedShareAnalytics: () => shareAnalytics,
15
15
  }));
16
16
 
17
17
  import { getDb } from "../db-connection.js";
@@ -30,7 +30,7 @@ function resetTable(): void {
30
30
 
31
31
  describe("onboarding-events-store: recordActivationEvent", () => {
32
32
  beforeEach(() => {
33
- collectUsageData = true;
33
+ shareAnalytics = true;
34
34
  resetTable();
35
35
  });
36
36
 
@@ -66,8 +66,8 @@ describe("onboarding-events-store: recordActivationEvent", () => {
66
66
  expect(rows[0]!.stepIndex).toBe(2);
67
67
  });
68
68
 
69
- test("returns null and writes no row when collectUsageData is disabled", () => {
70
- collectUsageData = false;
69
+ test("returns null and writes no row when share_analytics is disabled", () => {
70
+ shareAnalytics = false;
71
71
  const event = recordActivationEvent({
72
72
  stepName: "activation_moment_1_complete",
73
73
  sessionId: "conv-3",
@@ -1,7 +1,7 @@
1
1
  import { and, asc, eq, gt, or } from "drizzle-orm";
2
2
  import { v4 as uuid } from "uuid";
3
3
 
4
- import { getConfig } from "../config/loader.js";
4
+ import { getCachedShareAnalytics } from "../platform/consent-cache.js";
5
5
  import { getDb } from "./db-connection.js";
6
6
  import { authFallbackEvents } from "./schema.js";
7
7
 
@@ -36,7 +36,7 @@ export function recordAuthFallbackCounts(
36
36
  windowEnd: number,
37
37
  counts: AuthFallbackCount[],
38
38
  ): number {
39
- if (!getConfig().collectUsageData) return 0;
39
+ if (!getCachedShareAnalytics()) return 0;
40
40
  if (counts.length === 0) return 0;
41
41
  const db = getDb();
42
42
  const createdAt = Date.now();
@@ -1,9 +1,7 @@
1
1
  import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
2
2
  import { getConfig } from "../config/loader.js";
3
- import {
4
- isUntrustedTrustClass,
5
- type TrustClass,
6
- } from "../runtime/actor-trust-resolver.js";
3
+ import { type TrustClass } from "../runtime/actor-trust-resolver.js";
4
+ import { resolveCapabilities } from "../runtime/capabilities.js";
7
5
  import { getLogger } from "../util/logger.js";
8
6
  import { isAutoAnalysisConversation } from "./auto-analysis-guard.js";
9
7
  import { isMemoryEnabled, upsertAutoAnalysisJob } from "./jobs-store.js";
@@ -118,7 +116,7 @@ export function enqueueAutoAnalysisOnCompaction(
118
116
  conversationId: string,
119
117
  trustClass: TrustClass | undefined,
120
118
  ): void {
121
- if (isUntrustedTrustClass(trustClass)) {
119
+ if (!resolveCapabilities(trustClass).canAccessMemory) {
122
120
  return;
123
121
  }
124
122
  try {
@@ -7,8 +7,7 @@ import { conversations, messageBookmarks, messages } from "./schema.js";
7
7
 
8
8
  /**
9
9
  * Wire-shape representation of a bookmark, joined with the bookmarked
10
- * message and its parent conversation. Mirrors
11
- * `clients/shared/Network/BookmarkSummary.swift` — dates are emitted as
10
+ * message and its parent conversation. Dates are emitted as
12
11
  * unix-millisecond integers, and the message preview is capped to keep
13
12
  * the list payload bounded.
14
13
  */
@@ -74,7 +74,7 @@ export interface CanonicalGuardianRequest {
74
74
  updatedAt: number;
75
75
  }
76
76
 
77
- interface CanonicalGuardianDelivery {
77
+ export interface CanonicalGuardianDelivery {
78
78
  id: string;
79
79
  requestId: string;
80
80
  destinationChannel: string;
@@ -674,6 +674,44 @@ export function listPendingCanonicalGuardianRequestsByDestinationChat(
674
674
  return pendingRequests;
675
675
  }
676
676
 
677
+ /**
678
+ * Find the pending canonical request whose guardian-facing delivery landed on a
679
+ * specific channel message (channel + chat + message id).
680
+ *
681
+ * This is the addressing key for emoji-reaction decisions: a reaction carries
682
+ * only the message it is attached to, so the delivered card's message id is how
683
+ * we recover which request the guardian acted on — even when several cards are
684
+ * pending in the same chat. Returns null when no delivery matches or the matched
685
+ * request is no longer pending.
686
+ */
687
+ export function getPendingCanonicalRequestByDestinationMessage(
688
+ destinationChannel: string,
689
+ destinationChatId: string,
690
+ destinationMessageId: string,
691
+ ): CanonicalGuardianRequest | null {
692
+ const db = getDb();
693
+
694
+ const delivery = db
695
+ .select()
696
+ .from(canonicalGuardianDeliveries)
697
+ .where(
698
+ and(
699
+ eq(canonicalGuardianDeliveries.destinationChannel, destinationChannel),
700
+ eq(canonicalGuardianDeliveries.destinationChatId, destinationChatId),
701
+ eq(
702
+ canonicalGuardianDeliveries.destinationMessageId,
703
+ destinationMessageId,
704
+ ),
705
+ ),
706
+ )
707
+ .get();
708
+
709
+ if (!delivery) return null;
710
+
711
+ const request = getCanonicalGuardianRequest(delivery.requestId);
712
+ return request && request.status === "pending" ? request : null;
713
+ }
714
+
677
715
  // ---------------------------------------------------------------------------
678
716
  // Conversation scope helpers
679
717
  // ---------------------------------------------------------------------------
@@ -129,7 +129,7 @@ export const messageMetadataSchema = z
129
129
  * and read gate (conversation history loading) to enforce trust-aware access.
130
130
  */
131
131
  provenanceTrustClass: z
132
- .enum(["guardian", "trusted_contact", "unknown"])
132
+ .enum(["guardian", "trusted_contact", "unverified_contact", "unknown"])
133
133
  .optional(),
134
134
  provenanceSourceChannel: channelIdSchema.optional(),
135
135
  provenanceGuardianExternalUserId: z.string().optional(),
@@ -2575,12 +2575,17 @@ export function getConversationOriginInterface(
2575
2575
  * in the given conversation, or `undefined` if none is found.
2576
2576
  *
2577
2577
  * Used by the pointer message trust resolver to detect conversations
2578
- * whose audience is a guardian or trusted_contact outside desktop-origin
2579
- * conversations.
2578
+ * whose audience is a guardian, trusted_contact, or unverified_contact
2579
+ * outside desktop-origin conversations.
2580
2580
  */
2581
2581
  export function getConversationRecentProvenanceTrustClass(
2582
2582
  conversationId: string,
2583
- ): "guardian" | "trusted_contact" | "unknown" | undefined {
2583
+ ):
2584
+ | "guardian"
2585
+ | "trusted_contact"
2586
+ | "unverified_contact"
2587
+ | "unknown"
2588
+ | undefined {
2584
2589
  const row = rawGet<{ metadata: string | null }>(
2585
2590
  `SELECT metadata FROM messages
2586
2591
  WHERE conversation_id = ? AND role = 'user' AND metadata IS NOT NULL
@@ -135,7 +135,16 @@ export function resolveConversationId(idOrKey: string): string | null {
135
135
  */
136
136
  export function getOrCreateConversation(
137
137
  conversationKey: string,
138
- opts?: { conversationType?: "standard" },
138
+ opts?: {
139
+ conversationType?: "standard";
140
+ /**
141
+ * Caller-supplied title for the conversation, used only when this call
142
+ * actually creates the row. Treated as a user-set title (`isAutoTitle = 0`)
143
+ * so the async LLM title generator's safe-overwrite check leaves it
144
+ * untouched. Ignored when the conversation already exists.
145
+ */
146
+ title?: string;
147
+ },
139
148
  ): {
140
149
  conversationId: string;
141
150
  conversationType: string;
@@ -205,13 +214,19 @@ export function getOrCreateConversation(
205
214
 
206
215
  const now = Date.now();
207
216
  const conversationId = uuid();
208
- const title = GENERATING_TITLE;
217
+ const customTitle = opts?.title?.trim();
218
+ const title = customTitle || GENERATING_TITLE;
209
219
  const memoryScopeId = "default";
210
220
 
211
221
  tx.insert(conversations)
212
222
  .values({
213
223
  id: conversationId,
214
224
  title,
225
+ // A caller-supplied title is user-set: mark it non-auto (0) so the
226
+ // async LLM title generator's `canReplaceTitle` check won't overwrite
227
+ // it. Without one, omit the column so it takes its default
228
+ // (AUTO_TITLE_LLM) and follows the auto-generated placeholder flow.
229
+ ...(customTitle ? { isAutoTitle: 0 } : {}),
215
230
  createdAt: now,
216
231
  updatedAt: now,
217
232
  totalInputTokens: 0,
@@ -166,6 +166,7 @@ export async function generateAndPersistConversationTitle(
166
166
  const fallback = deriveFallbackTitle(context) ?? UNTITLED_FALLBACK;
167
167
  updateConversationTitle(conversationId, fallback, AUTO_TITLE_DETERMINISTIC);
168
168
  publishConversationTitleChanged(conversationId, fallback);
169
+ logRetryableFallback(params, "no_provider");
169
170
  return { title: fallback, updated: true };
170
171
  }
171
172
 
@@ -206,6 +207,7 @@ export async function generateAndPersistConversationTitle(
206
207
  const fallback = deriveFallbackTitle(context) ?? UNTITLED_FALLBACK;
207
208
  updateConversationTitle(conversationId, fallback, AUTO_TITLE_DETERMINISTIC);
208
209
  publishConversationTitleChanged(conversationId, fallback);
210
+ logRetryableFallback(params, "empty_output");
209
211
  return { title: fallback, updated: true };
210
212
  }
211
213
 
@@ -230,7 +232,7 @@ export const titleMutex = new Mutex();
230
232
  /**
231
233
  * Fire-and-forget wrapper for title generation. Failures are logged
232
234
  * but do not propagate. On failure, replaces loading placeholder with
233
- * a stable fallback title so loading state is never permanent.
235
+ * a retryable fallback title so loading state is never permanent.
234
236
  *
235
237
  * Calls are serialized via {@link titleMutex} so burst conversation
236
238
  * creation does not overwhelm the LLM provider.
@@ -244,16 +246,20 @@ export function queueGenerateConversationTitle(
244
246
  })
245
247
  .catch((err) => {
246
248
  log.warn(
247
- { err, conversationId: params.conversationId },
248
- "Failed to generate conversation title (non-fatal)",
249
+ retryableFallbackLogFields(params, "generation_error", err),
250
+ "Conversation title generation used retryable fallback",
249
251
  );
250
- // Replace loading placeholder with stable fallback
252
+ // Replace loading placeholder with a retryable fallback.
251
253
  try {
252
254
  const conversation = getConversation(params.conversationId);
253
255
  if (conversation && conversation.title === GENERATING_TITLE) {
254
256
  const fallback =
255
257
  deriveFallbackTitle(params.context) ?? UNTITLED_FALLBACK;
256
- updateConversationTitle(params.conversationId, fallback);
258
+ updateConversationTitle(
259
+ params.conversationId,
260
+ fallback,
261
+ AUTO_TITLE_DETERMINISTIC,
262
+ );
257
263
  publishConversationTitleChanged(params.conversationId, fallback);
258
264
  }
259
265
  } catch {
@@ -268,6 +274,11 @@ export interface RegenerateTitleParams {
268
274
  conversationId: string;
269
275
  provider?: Provider;
270
276
  signal?: AbortSignal;
277
+ /**
278
+ * Limit regeneration to placeholder or deterministic titles. Used for retrying
279
+ * failed initial generation without racing against a successful initial title.
280
+ */
281
+ onlyIfReplaceable?: boolean;
271
282
  }
272
283
 
273
284
  /**
@@ -278,12 +289,15 @@ export interface RegenerateTitleParams {
278
289
  export async function regenerateConversationTitle(
279
290
  params: RegenerateTitleParams,
280
291
  ): Promise<{ title: string; updated: boolean }> {
281
- const { conversationId, signal } = params;
292
+ const { conversationId, onlyIfReplaceable, signal } = params;
282
293
 
283
294
  const conversation = getConversation(conversationId);
284
295
  if (!conversation || !conversation.isAutoTitle) {
285
296
  return { title: conversation?.title ?? UNTITLED_FALLBACK, updated: false };
286
297
  }
298
+ if (onlyIfReplaceable && !canReplaceTitle(conversation)) {
299
+ return { title: conversation.title ?? UNTITLED_FALLBACK, updated: false };
300
+ }
287
301
 
288
302
  const provider =
289
303
  params.provider ?? (await getConfiguredProvider("conversationTitle"));
@@ -317,7 +331,11 @@ export async function regenerateConversationTitle(
317
331
  if (title) {
318
332
  // Re-check isAutoTitle before persisting (race guard against manual rename)
319
333
  const current = getConversation(conversationId);
320
- if (!current || !current.isAutoTitle) {
334
+ if (
335
+ !current ||
336
+ !current.isAutoTitle ||
337
+ (onlyIfReplaceable && !canReplaceTitle(current))
338
+ ) {
321
339
  return { title: current?.title ?? UNTITLED_FALLBACK, updated: false };
322
340
  }
323
341
 
@@ -408,6 +426,45 @@ function buildTitlePrompt(
408
426
  return parts.join("\n");
409
427
  }
410
428
 
429
+ function titleGenerationLogFields(params: GenerateTitleParams) {
430
+ return {
431
+ conversationId: params.conversationId,
432
+ contextOrigin: params.context?.origin,
433
+ hasContext: Boolean(params.context),
434
+ userMessageLength: params.userMessage?.length ?? 0,
435
+ assistantResponseLength: params.assistantResponse?.length ?? 0,
436
+ };
437
+ }
438
+
439
+ type TitleGenerationFallbackReason =
440
+ | "no_provider"
441
+ | "empty_output"
442
+ | "generation_error";
443
+
444
+ function retryableFallbackLogFields(
445
+ params: GenerateTitleParams,
446
+ reason: TitleGenerationFallbackReason,
447
+ err?: unknown,
448
+ ): Record<string, unknown> {
449
+ const fields: Record<string, unknown> = {
450
+ ...titleGenerationLogFields(params),
451
+ reason,
452
+ fallbackSource: params.context ? "context" : "untitled",
453
+ };
454
+ if (err) fields.err = err;
455
+ return fields;
456
+ }
457
+
458
+ function logRetryableFallback(
459
+ params: GenerateTitleParams,
460
+ reason: TitleGenerationFallbackReason,
461
+ ): void {
462
+ log.warn(
463
+ retryableFallbackLogFields(params, reason),
464
+ "Conversation title generation used retryable fallback",
465
+ );
466
+ }
467
+
411
468
  const META_FAILURE_TITLES = new Set([
412
469
  "missing context",
413
470
  "no context",
@@ -61,6 +61,19 @@ export interface RunAsyncSqliteOptions {
61
61
  * the runtime pick.
62
62
  */
63
63
  forceBackend?: AsyncSqliteBackend;
64
+ /**
65
+ * Database file to run the statement against. Defaults to the main
66
+ * assistant DB (`getDbPath()`). Pass `getLogsDbPath()` to target the
67
+ * secondary append-only file directly.
68
+ *
69
+ * This only affects the `sqlite3-cli` backend, which opens the given file
70
+ * as its own `main` database — so a statement like
71
+ * `DELETE FROM llm_request_logs` runs against the right file with no ATTACH.
72
+ * The in-process fallback always runs on the daemon connection, which has
73
+ * the logs DB ATTACHed, so unqualified table names already resolve to the
74
+ * correct file regardless of this option.
75
+ */
76
+ dbPath?: string;
64
77
  }
65
78
 
66
79
  let warnedAboutFallback = false;
@@ -74,7 +87,12 @@ export async function runAsyncSqlite(
74
87
  forced === "in-process-blocking" ? undefined : findSqlite3();
75
88
 
76
89
  if (sqlite3Path && forced !== "in-process-blocking") {
77
- return runViaCli(sqlite3Path, sql, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
90
+ return runViaCli(
91
+ sqlite3Path,
92
+ sql,
93
+ options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
94
+ options.dbPath ?? getDbPath(),
95
+ );
78
96
  }
79
97
 
80
98
  if (!warnedAboutFallback) {
@@ -97,9 +115,9 @@ async function runViaCli(
97
115
  sqlite3Path: string,
98
116
  sql: string,
99
117
  timeoutMs: number,
118
+ dbPath: string,
100
119
  ): Promise<AsyncSqliteResult> {
101
120
  const startMs = Date.now();
102
- const dbPath = getDbPath();
103
121
 
104
122
  log.info(
105
123
  { sqlite3Path, dbPath, timeoutMs, sqlPreview: sql.slice(0, 80) },
@@ -5,16 +5,59 @@ import { Database } from "bun:sqlite";
5
5
 
6
6
  import { drizzle } from "drizzle-orm/bun-sqlite";
7
7
 
8
+ import { getLogsDbPath } from "../util/logs-db-path.js";
8
9
  import { ensureDataDir, getDbPath } from "../util/platform.js";
9
- import {
10
- clearStoredDb,
11
- getStoredDb,
12
- setStoredDb,
13
- } from "./db-singleton.js";
10
+ import { clearStoredDb, getStoredDb, setStoredDb } from "./db-singleton.js";
14
11
  import * as schema from "./schema.js";
15
12
 
16
13
  export type DrizzleDb = ReturnType<typeof drizzle<typeof schema>>;
17
14
 
15
+ /**
16
+ * Schema name under which the secondary append-only database file
17
+ * (`assistant-logs.db`) is ATTACHed to the main connection. Tables created in
18
+ * this schema (e.g. `logs.llm_request_logs`) live in the separate file but are
19
+ * still queryable — and joinable against main-schema tables — over the single
20
+ * daemon connection. Because no append-only table exists in the `main` schema,
21
+ * unqualified references (e.g. `llm_request_logs`) resolve to the attached copy.
22
+ */
23
+ export const LOGS_DB_SCHEMA = "logs";
24
+
25
+ /**
26
+ * ATTACH the append-only logs database to an open connection and apply its
27
+ * per-database PRAGMAs.
28
+ *
29
+ * `journal_mode` and `synchronous` are per-database settings, so they must be
30
+ * set against the attached schema explicitly — the PRAGMAs run on `main` before
31
+ * the attach do not carry over. `busy_timeout`, `foreign_keys`, `cache_size`,
32
+ * and `temp_store` are connection-wide and already configured on the connection.
33
+ *
34
+ * Best-effort: a failure here (e.g. a filesystem permission problem on the new
35
+ * file) must not take down the main database. We log and continue in line with
36
+ * the daemon's "never block startup on a subsystem failure" policy; append-only
37
+ * tables are simply unavailable until the next successful open.
38
+ *
39
+ * The logger is pulled in lazily (dynamic import) only on the error path. This
40
+ * file is intentionally kept import-light — see `db-singleton.ts` — so it does
41
+ * not take a static dependency on the logger (and transitively on pino).
42
+ */
43
+ function attachLogsDb(sqlite: Database): void {
44
+ const logsPath = getLogsDbPath();
45
+ try {
46
+ // Escape single quotes for the SQL string literal (paths can contain them).
47
+ const escaped = logsPath.replace(/'/g, "''");
48
+ sqlite.exec(`ATTACH DATABASE '${escaped}' AS ${LOGS_DB_SCHEMA}`);
49
+ sqlite.exec(`PRAGMA ${LOGS_DB_SCHEMA}.journal_mode=WAL`);
50
+ sqlite.exec(`PRAGMA ${LOGS_DB_SCHEMA}.synchronous=FULL`);
51
+ } catch (err) {
52
+ void import("../util/logger.js").then(({ getLogger }) =>
53
+ getLogger("db-connection").error(
54
+ { err, logsPath },
55
+ "Failed to ATTACH logs database; append-only log tables will be unavailable",
56
+ ),
57
+ );
58
+ }
59
+ }
60
+
18
61
  function canonicalizePathThroughExistingParent(path: string): string {
19
62
  const resolvedPath = resolve(path);
20
63
  const pendingSegments: string[] = [];
@@ -86,6 +129,7 @@ export function getDb(): DrizzleDb {
86
129
  sqlite.exec("PRAGMA foreign_keys = ON");
87
130
  sqlite.exec("PRAGMA cache_size=-256000");
88
131
  sqlite.exec("PRAGMA temp_store=MEMORY");
132
+ attachLogsDb(sqlite);
89
133
  const db = drizzle(sqlite, { schema });
90
134
  setStoredDb(db, () => sqlite.close());
91
135
  return db;
@@ -40,6 +40,7 @@ import {
40
40
  createSkillLoadedEventsTable,
41
41
  createTasksAndWorkItemsTables,
42
42
  createWatchersAndLogsTables,
43
+ dropApprovalPromptTsTrackerTable,
43
44
  migrate230AcpSessionHistory,
44
45
  migrate231RepairMemoryGraphEventDates,
45
46
  migrateA2ATasks,
@@ -68,6 +69,7 @@ import {
68
69
  migrateChannelInboundDeliveryAttempts,
69
70
  migrateChannelInteractionColumns,
70
71
  migrateContactChannelsAccessFields,
72
+ migrateContactChannelsRenormalizeAddresses,
71
73
  migrateContactChannelsTypeChatIdIndex,
72
74
  migrateContactChannelsUniqueExtUser,
73
75
  migrateContactsAssistantId,
@@ -101,6 +103,7 @@ import {
101
103
  migrateDropConflicts,
102
104
  migrateDropContactInteractionColumns,
103
105
  migrateDropEntityTables,
106
+ migrateDropExternalUserId,
104
107
  migrateDropLegacyMemberGuardianTables,
105
108
  migrateDropLoopbackPortColumn,
106
109
  migrateDropMemoryItemsTables,
@@ -194,7 +197,9 @@ import {
194
197
  migrateRenameVerificationSessionIdColumn,
195
198
  migrateRenameVerificationTable,
196
199
  migrateRenameVoiceToPhone,
200
+ migrateRewriteBalancedEconomyProfilePins,
197
201
  migrateScheduleCapabilities,
202
+ migrateScheduleDefaultNoReuseConversation,
198
203
  migrateScheduleDescription,
199
204
  migrateScheduleInferenceProfile,
200
205
  migrateScheduleOneShotRouting,
@@ -222,6 +227,7 @@ import {
222
227
  migrateUsageLlmCallCount,
223
228
  migrateVoiceInviteColumns,
224
229
  migrateVoiceInviteDisplayMetadata,
230
+ migrateWorkflowJournalLeafTokens,
225
231
  migrateWorkflowRuns,
226
232
  migrateWorkflowRunTrust,
227
233
  recoverCrashedMigrations,
@@ -229,6 +235,7 @@ import {
229
235
  runLateMigrations,
230
236
  validateMigrationState,
231
237
  } from "./migrations/index.js";
238
+ import { runMigrationSteps } from "./migrations/run-migrations.js";
232
239
 
233
240
  // ---------------------------------------------------------------------------
234
241
  // Test DB template — run migrations once, reuse across test files
@@ -518,29 +525,22 @@ export function initializeDb(): void {
518
525
  migrateBackfillOriginChannelFromBindings,
519
526
  migrateContactChannelsUniqueExtUser,
520
527
  migrateScheduleCapabilities,
528
+ migrateContactChannelsRenormalizeAddresses,
529
+ migrateScheduleDefaultNoReuseConversation,
530
+ migrateWorkflowJournalLeafTokens,
531
+ migrateDropExternalUserId,
532
+ dropApprovalPromptTsTrackerTable,
533
+ migrateRewriteBalancedEconomyProfilePins,
521
534
  ];
522
535
 
523
536
  // Run each migration step, catching and logging individual failures so one
524
537
  // broken migration doesn't prevent independent later ones from succeeding.
525
- const failures: string[] = [];
526
- for (const step of migrationSteps) {
527
- try {
528
- log.debug({ migration: step.name }, `Starting migration: ${step.name}`);
529
- step(database);
530
- log.debug({ migration: step.name }, `Migration succeeded: ${step.name}`);
531
- } catch (err) {
532
- failures.push(step.name);
533
- log.error(
534
- { err, migration: step.name },
535
- `Migration failed: ${step.name}`,
536
- );
537
- }
538
- }
538
+ const { failed } = runMigrationSteps(database, migrationSteps);
539
539
 
540
- if (failures.length > 0) {
540
+ if (failed.length > 0) {
541
541
  log.error(
542
- { failedMigrations: failures, count: failures.length },
543
- `DB initialization completed with ${failures.length} failed migration(s)`,
542
+ { failedMigrations: failed, count: failed.length },
543
+ `DB initialization completed with ${failed.length} failed migration(s)`,
544
544
  );
545
545
  }
546
546
 
@@ -7,6 +7,13 @@ import { PLATFORM_PROVIDER_META } from "../providers/platform-proxy/constants.js
7
7
  import { resolveManagedProxyContext } from "../providers/platform-proxy/context.js";
8
8
  import { getProviderKeyAsync } from "../security/secure-keys.js";
9
9
  import { getLogger } from "../util/logger.js";
10
+ import {
11
+ EmbeddingBillingBlockError,
12
+ extractHttpStatus,
13
+ isEmbeddingBillingBreakerOpen,
14
+ recordBillingBlock,
15
+ recordBillingSuccess,
16
+ } from "./embedding-billing-breaker.js";
10
17
  import { GeminiEmbeddingBackend } from "./embedding-gemini.js";
11
18
  import { OllamaEmbeddingBackend } from "./embedding-ollama.js";
12
19
  import { OpenAIEmbeddingBackend } from "./embedding-openai.js";
@@ -490,6 +497,20 @@ export async function getMemoryBackendStatus(config: AssistantConfig): Promise<{
490
497
  };
491
498
  }
492
499
 
500
+ /**
501
+ * Thrown by {@link embedWithBackend} when no embedding backend is configured or
502
+ * available. This is a PROCESS-WIDE condition (backend selection is effectively
503
+ * cached for the run), so a caller embedding many items should treat the first
504
+ * occurrence as fatal to the whole batch rather than a per-item failure — see
505
+ * `backfillAllSections`, which aborts on it instead of churning through deletes.
506
+ */
507
+ export class EmbeddingBackendUnavailableError extends Error {
508
+ constructor(message = "No memory embedding backend configured") {
509
+ super(message);
510
+ this.name = "EmbeddingBackendUnavailableError";
511
+ }
512
+ }
513
+
493
514
  export async function embedWithBackend(
494
515
  config: AssistantConfig,
495
516
  inputs: EmbeddingInput[],
@@ -499,9 +520,15 @@ export async function embedWithBackend(
499
520
  model: string;
500
521
  vectors: number[][];
501
522
  }> {
523
+ // Fail-fast when the billing breaker is open — avoids burning a network
524
+ // round-trip on every caller (embed lane jobs, activation recompute, etc.).
525
+ if (isEmbeddingBillingBreakerOpen()) {
526
+ throw new EmbeddingBillingBlockError();
527
+ }
528
+
502
529
  const selection = await selectEmbeddingBackend(config);
503
530
  if (!selection.backend) {
504
- throw new Error(
531
+ throw new EmbeddingBackendUnavailableError(
505
532
  selection.reason ?? "No memory embedding backend configured",
506
533
  );
507
534
  }
@@ -612,6 +639,10 @@ export async function embedWithBackend(
612
639
  );
613
640
  }
614
641
 
642
+ // A successful backend call proves billing is active — close the
643
+ // breaker if it was in the probe window.
644
+ recordBillingSuccess();
645
+
615
646
  if (isPrimary) {
616
647
  const merged = [...cached] as number[][];
617
648
  for (let i = 0; i < uncachedIndices.length; i++) {
@@ -626,6 +657,12 @@ export async function embedWithBackend(
626
657
  return { provider: backend.provider, model: backend.model, vectors };
627
658
  } catch (err) {
628
659
  lastErr = err;
660
+ // If ANY backend in the chain returns 402, trip the billing breaker
661
+ // immediately — fallbacks will hit the same depleted balance.
662
+ if (extractHttpStatus(err) === 402) {
663
+ recordBillingBlock();
664
+ throw err;
665
+ }
629
666
  if (backends.length > 1) {
630
667
  log.warn(
631
668
  { err, provider: backend.provider },