@vellumai/assistant 0.8.10 → 0.8.11-staging.1

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 (400) hide show
  1. package/bun.lock +62 -1
  2. package/docs/workspace-tools.md +196 -0
  3. package/examples/plugins/echo/README.md +3 -3
  4. package/knip.json +1 -0
  5. package/openapi.yaml +460 -128
  6. package/package.json +2 -1
  7. package/scripts/build-plugin-api.ts +299 -0
  8. package/src/__tests__/agent-loop-callsite-precedence.test.ts +7 -0
  9. package/src/__tests__/agent-loop-compaction-events.test.ts +197 -0
  10. package/src/__tests__/agent-loop-exit-reason.test.ts +93 -96
  11. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +2 -0
  12. package/src/__tests__/agent-loop-output-hooks.test.ts +274 -1
  13. package/src/__tests__/agent-loop-override-profile.test.ts +3 -0
  14. package/src/__tests__/agent-loop-provider-error-recording.test.ts +4 -0
  15. package/src/__tests__/agent-loop-thinking.test.ts +4 -0
  16. package/src/__tests__/agent-loop.test.ts +578 -5
  17. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -1
  18. package/src/__tests__/approval-cascade.test.ts +1 -0
  19. package/src/__tests__/background-workers-disk-pressure.test.ts +0 -2
  20. package/src/__tests__/btw-routes.test.ts +0 -1
  21. package/src/__tests__/build-persisted-content.test.ts +75 -1
  22. package/src/__tests__/catalog-install-normalize.test.ts +141 -0
  23. package/src/__tests__/ces-startup-timeout.test.ts +60 -0
  24. package/src/__tests__/compaction-events.test.ts +1 -0
  25. package/src/__tests__/config-managed-gemini-defaults.test.ts +2 -46
  26. package/src/__tests__/context-overflow-reducer.test.ts +264 -124
  27. package/src/__tests__/context-window-manager-overflow-rung.test.ts +351 -0
  28. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  29. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +13 -5
  30. package/src/__tests__/conversation-agent-loop-overflow.test.ts +284 -455
  31. package/src/__tests__/conversation-agent-loop.test.ts +131 -551
  32. package/src/__tests__/conversation-app-control-instantiation.test.ts +13 -0
  33. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
  34. package/src/__tests__/conversation-fork-crud.test.ts +259 -0
  35. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  36. package/src/__tests__/conversation-lifecycle.test.ts +257 -1
  37. package/src/__tests__/conversation-process-callsite.test.ts +1 -0
  38. package/src/__tests__/conversation-provider-retry-repair.test.ts +38 -377
  39. package/src/__tests__/conversation-queue.test.ts +1 -39
  40. package/src/__tests__/conversation-runtime-assembly.test.ts +119 -8
  41. package/src/__tests__/conversation-skill-tools.test.ts +491 -5
  42. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  43. package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
  44. package/src/__tests__/conversation-speed-override.test.ts +1 -0
  45. package/src/__tests__/conversation-store.test.ts +74 -0
  46. package/src/__tests__/conversation-surfaces-app-control.test.ts +4 -1
  47. package/src/__tests__/conversation-tool-setup-attribution.test.ts +323 -0
  48. package/src/__tests__/conversation-tool-setup-tools-disabled.test.ts +34 -0
  49. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
  50. package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
  51. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -0
  52. package/src/__tests__/corrected-target.test.ts +93 -0
  53. package/src/__tests__/credential-execution-feature-gates.test.ts +3 -5
  54. package/src/__tests__/credential-execution-tools.test.ts +23 -11
  55. package/src/__tests__/credential-security-invariants.test.ts +6 -1
  56. package/src/__tests__/db-schedule-syntax-migration.test.ts +80 -0
  57. package/src/__tests__/device-id.test.ts +70 -1
  58. package/src/__tests__/embedding-managed-proxy-selection.test.ts +6 -40
  59. package/src/__tests__/empty-response-hook.test.ts +242 -66
  60. package/src/__tests__/external-plugin-loader.test.ts +0 -31
  61. package/src/__tests__/get-skill-detail-audit.test.ts +43 -1
  62. package/src/__tests__/guardian-routing-invariants.test.ts +91 -0
  63. package/src/__tests__/history-repair-hook.test.ts +228 -3
  64. package/src/__tests__/host-app-control-proxy.test.ts +45 -0
  65. package/src/__tests__/host-browser-proxy.test.ts +254 -9
  66. package/src/__tests__/identity-routes.test.ts +1 -0
  67. package/src/__tests__/image-recovery-hook.test.ts +387 -0
  68. package/src/__tests__/injector-chain.test.ts +5 -4
  69. package/src/__tests__/injector-v3-suppression.test.ts +373 -47
  70. package/src/__tests__/intent-routing.test.ts +7 -0
  71. package/src/__tests__/memory-retrieval-hook.test.ts +117 -15
  72. package/src/__tests__/notification-decision-strategy.test.ts +3 -3
  73. package/src/__tests__/oauth-store.test.ts +0 -85
  74. package/src/__tests__/{context-overflow-policy.test.ts → overflow-policy.test.ts} +1 -1
  75. package/src/__tests__/parallel-tool.benchmark.test.ts +4 -0
  76. package/src/__tests__/persist-unsendable-image-downscale.test.ts +29 -9
  77. package/src/__tests__/persist-unsendable-image.test.ts +4 -4
  78. package/src/__tests__/persistence-secret-redaction.test.ts +78 -0
  79. package/src/__tests__/plugin-bootstrap.test.ts +82 -73
  80. package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
  81. package/src/__tests__/plugin-types.test.ts +0 -8
  82. package/src/__tests__/provider-catalog-visibility.test.ts +1 -9
  83. package/src/__tests__/prune-old-conversations-job.test.ts +99 -0
  84. package/src/__tests__/registry.test.ts +240 -1
  85. package/src/__tests__/require-fresh-approval.test.ts +3 -0
  86. package/src/__tests__/schedule-routes.test.ts +116 -1
  87. package/src/__tests__/schedule-store.test.ts +28 -0
  88. package/src/__tests__/schedule-tools.test.ts +94 -1
  89. package/src/__tests__/server-history-render.test.ts +39 -0
  90. package/src/__tests__/skill-projection-feature-flag.test.ts +13 -0
  91. package/src/__tests__/skill-projection.benchmark.test.ts +25 -7
  92. package/src/__tests__/skills.test.ts +202 -0
  93. package/src/__tests__/slim-skill-category.test.ts +195 -0
  94. package/src/__tests__/strip-memory-injections.test.ts +33 -39
  95. package/src/__tests__/test-support/tool-invocation-seed.ts +79 -0
  96. package/src/__tests__/title-generate-hook.test.ts +9 -7
  97. package/src/__tests__/tool-audit-listener.test.ts +264 -1
  98. package/src/__tests__/tool-error-hook.test.ts +4 -3
  99. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -0
  100. package/src/__tests__/tool-executor-lifecycle-events.test.ts +273 -0
  101. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -0
  102. package/src/__tests__/tool-start-timestamp.test.ts +218 -0
  103. package/src/__tests__/tools-get-route.test.ts +202 -0
  104. package/src/__tests__/workspace-tool-loader.test.ts +319 -0
  105. package/src/__tests__/workspace-tools-watcher-flag.test.ts +70 -0
  106. package/src/agent/loop.ts +569 -319
  107. package/src/api/events/tool-result.ts +9 -0
  108. package/src/api/events/tool-use-start.ts +7 -0
  109. package/src/api/index.ts +10 -0
  110. package/src/api/responses/conversation-message.ts +135 -27
  111. package/src/api/responses/memory-v3-selection-log.ts +4 -4
  112. package/src/approvals/guardian-request-resolvers.ts +26 -0
  113. package/src/browser-session/backends/host-bridge.ts +29 -0
  114. package/src/browser-session/index.ts +1 -0
  115. package/src/browser-session/types.ts +5 -1
  116. package/src/cli/commands/__tests__/schedules.test.ts +62 -4
  117. package/src/cli/commands/__tests__/skills.test.ts +53 -0
  118. package/src/cli/commands/channel-verification-sessions.ts +6 -6
  119. package/src/cli/commands/inference-providers.ts +0 -8
  120. package/src/cli/commands/plugins.ts +2 -2
  121. package/src/cli/commands/schedules.ts +27 -4
  122. package/src/cli/commands/skills.ts +187 -146
  123. package/src/cli/commands/tools.ts +106 -0
  124. package/src/cli/lib/__tests__/install-from-github.test.ts +256 -328
  125. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +6 -2
  126. package/src/cli/lib/__tests__/plugin-details.test.ts +10 -16
  127. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +2 -2
  128. package/src/cli/lib/__tests__/search-plugins.test.ts +145 -240
  129. package/src/cli/lib/install-from-github.ts +187 -117
  130. package/src/cli/lib/plugin-catalog-cache.ts +9 -9
  131. package/src/cli/lib/plugin-details.ts +38 -68
  132. package/src/cli/lib/plugin-marketplace.ts +42 -14
  133. package/src/cli/lib/search-plugins.ts +29 -129
  134. package/src/cli/program.ts +2 -0
  135. package/src/config/bundled-skills/acp/SKILL.md +1 -0
  136. package/src/config/bundled-skills/app-builder/SKILL.md +1 -0
  137. package/src/config/bundled-skills/app-control/SKILL.md +1 -0
  138. package/src/config/bundled-skills/computer-use/SKILL.md +1 -0
  139. package/src/config/bundled-skills/contacts/SKILL.md +1 -0
  140. package/src/config/bundled-skills/document-editor/SKILL.md +1 -0
  141. package/src/config/bundled-skills/followups/SKILL.md +1 -0
  142. package/src/config/bundled-skills/image-studio/SKILL.md +1 -0
  143. package/src/config/bundled-skills/media-processing/SKILL.md +1 -0
  144. package/src/config/bundled-skills/messaging/SKILL.md +1 -0
  145. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -0
  146. package/src/config/bundled-skills/playbooks/SKILL.md +1 -0
  147. package/src/config/bundled-skills/schedule/SKILL.md +1 -0
  148. package/src/config/bundled-skills/schedule/TOOLS.json +11 -3
  149. package/src/config/bundled-skills/sequences/SKILL.md +1 -0
  150. package/src/config/bundled-skills/settings/SKILL.md +1 -0
  151. package/src/config/bundled-skills/skill-management/SKILL.md +101 -1
  152. package/src/config/bundled-skills/subagent/SKILL.md +1 -0
  153. package/src/config/bundled-skills/transcribe/SKILL.md +1 -0
  154. package/src/config/env-registry.ts +23 -0
  155. package/src/config/feature-flag-registry.json +17 -81
  156. package/src/config/loader.ts +5 -22
  157. package/src/config/schema.ts +2 -0
  158. package/src/config/schemas/__tests__/compaction-logs.test.ts +56 -0
  159. package/src/config/schemas/__tests__/memory-v2.test.ts +0 -1
  160. package/src/config/schemas/__tests__/memory-v3.test.ts +61 -1
  161. package/src/config/schemas/compaction-logs.ts +79 -0
  162. package/src/config/schemas/memory-v2.ts +0 -8
  163. package/src/config/schemas/memory-v3.ts +104 -33
  164. package/src/config/seed-inference-profiles.ts +1 -1
  165. package/src/config/skills.ts +117 -47
  166. package/src/context/compactor.ts +11 -0
  167. package/src/context/strip-injections.ts +38 -4
  168. package/src/credential-execution/feature-gates.ts +0 -21
  169. package/src/credential-execution/startup-timeout.ts +32 -4
  170. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +18 -0
  171. package/src/daemon/conversation-agent-loop-handlers.ts +140 -91
  172. package/src/daemon/conversation-agent-loop.ts +123 -658
  173. package/src/daemon/conversation-error.ts +6 -33
  174. package/src/daemon/conversation-lifecycle.ts +1 -1
  175. package/src/daemon/conversation-runtime-assembly.ts +183 -22
  176. package/src/daemon/conversation-skill-tools.ts +137 -8
  177. package/src/daemon/conversation-slash.ts +0 -14
  178. package/src/daemon/conversation-store.ts +2 -19
  179. package/src/daemon/conversation-tool-setup.ts +87 -1
  180. package/src/daemon/conversation.ts +119 -50
  181. package/src/daemon/external-plugins-bootstrap.ts +36 -95
  182. package/src/daemon/handlers/config-channels.ts +11 -2
  183. package/src/daemon/handlers/shared.ts +9 -1
  184. package/src/daemon/handlers/skills.ts +10 -4
  185. package/src/daemon/host-app-control-proxy.ts +72 -57
  186. package/src/daemon/host-browser-proxy.ts +117 -22
  187. package/src/daemon/lifecycle.ts +34 -5
  188. package/src/daemon/message-protocol.ts +0 -7
  189. package/src/daemon/message-types/schedules.ts +1 -0
  190. package/src/daemon/message-types/skills.ts +17 -0
  191. package/src/daemon/providers-setup.ts +3 -0
  192. package/src/daemon/server.ts +3 -3
  193. package/src/daemon/tool-setup-types.ts +9 -3
  194. package/src/daemon/trust-context.ts +23 -0
  195. package/src/daemon/workspace-tools-watcher.ts +324 -0
  196. package/src/events/tool-audit-listener.ts +78 -16
  197. package/src/events/tool-metrics-listener.ts +2 -5
  198. package/src/memory/__tests__/compaction-log-writer-clickhouse.test.ts +227 -0
  199. package/src/memory/__tests__/conversation-queries.test.ts +176 -0
  200. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +20 -32
  201. package/src/memory/compaction-log-writer-clickhouse.ts +418 -0
  202. package/src/memory/conversation-crud.ts +134 -3
  203. package/src/memory/conversation-queries.ts +64 -7
  204. package/src/memory/db-init.ts +14 -0
  205. package/src/memory/embedding-backend.test.ts +130 -1
  206. package/src/memory/embedding-backend.ts +79 -106
  207. package/src/memory/embedding-gemini.ts +5 -0
  208. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +12 -0
  209. package/src/memory/graph/__tests__/handle-remember-v2.test.ts +19 -0
  210. package/src/memory/graph/conversation-graph-memory.ts +36 -25
  211. package/src/memory/graph/tool-handlers.ts +3 -0
  212. package/src/memory/job-handlers/cleanup.ts +3 -1
  213. package/src/memory/jobs-store.ts +0 -28
  214. package/src/memory/jobs-worker.ts +10 -22
  215. package/src/memory/memory-marker.ts +29 -0
  216. package/src/memory/memory-retrospective-startup-cleanup.ts +1 -1
  217. package/src/memory/migrations/268-add-memory-v3-selections.ts +6 -0
  218. package/src/memory/migrations/270-schedule-description.ts +36 -0
  219. package/src/memory/migrations/275-tool-invocations-add-skill-id.test.ts +81 -0
  220. package/src/memory/migrations/275-tool-invocations-add-skill-id.ts +20 -0
  221. package/src/memory/migrations/276-tool-invocations-created-at-id-index.test.ts +68 -0
  222. package/src/memory/migrations/276-tool-invocations-created-at-id-index.ts +20 -0
  223. package/src/memory/migrations/277-add-memory-v3-ever-injected.ts +29 -0
  224. package/src/memory/migrations/278-tool-invocations-telemetry-columns.test.ts +96 -0
  225. package/src/memory/migrations/278-tool-invocations-telemetry-columns.ts +39 -0
  226. package/src/memory/migrations/279-create-skill-loaded-events.test.ts +84 -0
  227. package/src/memory/migrations/279-create-skill-loaded-events.ts +26 -0
  228. package/src/memory/migrations/280-conversations-surfaced-at.test.ts +88 -0
  229. package/src/memory/migrations/280-conversations-surfaced-at.ts +24 -0
  230. package/src/memory/migrations/index.ts +10 -0
  231. package/src/memory/migrations/registry.ts +8 -0
  232. package/src/memory/schema/conversations.ts +16 -0
  233. package/src/memory/schema/infrastructure.ts +26 -0
  234. package/src/memory/skill-loaded-events-store.test.ts +160 -0
  235. package/src/memory/skill-loaded-events-store.ts +95 -0
  236. package/src/memory/tool-executed-events-store.test.ts +219 -0
  237. package/src/memory/tool-executed-events-store.ts +102 -0
  238. package/src/memory/tool-usage-store.ts +15 -3
  239. package/src/memory/v2/__tests__/consolidation-job.test.ts +117 -12
  240. package/src/memory/v2/__tests__/consolidation-prompt-flag-gating-guard.test.ts +189 -0
  241. package/src/memory/v2/__tests__/injected-block-slugs.test.ts +90 -0
  242. package/src/memory/v2/__tests__/page-store.test.ts +33 -0
  243. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +88 -15
  244. package/src/memory/v2/activation-store.ts +50 -1
  245. package/src/memory/v2/consolidation-job.ts +113 -29
  246. package/src/memory/v2/injected-block-slugs.ts +79 -0
  247. package/src/memory/v2/injection.ts +6 -1
  248. package/src/memory/v2/prompts/consolidation.ts +414 -13
  249. package/src/memory/v2/router.ts +2 -28
  250. package/src/memory/v2/static-context.ts +1 -1
  251. package/src/memory/v2/types.ts +16 -0
  252. package/src/notifications/__tests__/copy-composer.test.ts +244 -0
  253. package/src/notifications/access-request-copy.ts +298 -0
  254. package/src/notifications/adapters/slack.ts +3 -3
  255. package/src/notifications/adapters/telegram.ts +2 -1
  256. package/src/notifications/copy-composer.ts +49 -267
  257. package/src/notifications/decision-engine.ts +16 -35
  258. package/src/notifications/home-feed-side-effect.ts +1 -6
  259. package/src/oauth/oauth-store.ts +0 -9
  260. package/src/permissions/checker.test.ts +83 -1
  261. package/src/permissions/checker.ts +25 -2
  262. package/src/permissions/gateway-threshold-reader.test.ts +182 -0
  263. package/src/permissions/gateway-threshold-reader.ts +80 -0
  264. package/src/platform/client.ts +1 -3
  265. package/src/platform/feature-gate.ts +3 -12
  266. package/src/plugin-api/constants.ts +4 -2
  267. package/src/plugin-api/index.ts +63 -11
  268. package/src/plugin-api/types.ts +236 -71
  269. package/src/plugins/defaults/compaction/compact.ts +66 -2
  270. package/src/plugins/defaults/compaction/context-overflow-reducer.ts +240 -32
  271. package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
  272. package/src/{daemon/context-overflow-policy.ts → plugins/defaults/compaction/overflow-policy.ts} +1 -1
  273. package/src/plugins/defaults/compaction/window-manager.ts +303 -1
  274. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +173 -0
  275. package/src/plugins/defaults/empty-response/hooks/stop.ts +11 -115
  276. package/src/plugins/defaults/empty-response/nudge-state-store.ts +46 -0
  277. package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +50 -0
  278. package/src/plugins/defaults/history-repair/hooks/stop.ts +22 -0
  279. package/src/plugins/defaults/history-repair/repair-state-store.ts +51 -0
  280. package/src/plugins/defaults/history-repair/terminal.ts +39 -2
  281. package/src/plugins/defaults/image-recovery/detect.ts +25 -0
  282. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +73 -0
  283. package/src/plugins/defaults/image-recovery/hooks/stop.ts +22 -0
  284. package/src/plugins/defaults/image-recovery/image-recovery-state-store.ts +48 -0
  285. package/src/plugins/defaults/image-recovery/package.json +14 -0
  286. package/src/{daemon/persist-unsendable-image.ts → plugins/defaults/image-recovery/recover.ts} +67 -14
  287. package/src/plugins/defaults/index.ts +71 -5
  288. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +76 -112
  289. package/src/plugins/defaults/memory-retrieval/hooks/{user-prompt-submit-temp.ts → user-prompt-submit.ts} +83 -74
  290. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +14 -8
  291. package/src/plugins/defaults/memory-retrieval/injectors.ts +2 -18
  292. package/src/plugins/defaults/memory-retrieval/package.json +14 -0
  293. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1157 -0
  294. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +683 -0
  295. package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +161 -140
  296. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +160 -0
  297. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +335 -316
  298. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +145 -53
  299. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +38 -1
  300. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +18 -8
  301. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +112 -71
  302. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +192 -51
  303. package/src/plugins/defaults/memory-v3-shadow/__tests__/types.test.ts +4 -16
  304. package/src/plugins/defaults/memory-v3-shadow/card.test.ts +173 -0
  305. package/src/plugins/defaults/memory-v3-shadow/card.ts +116 -0
  306. package/src/plugins/defaults/memory-v3-shadow/core-set.test.ts +104 -0
  307. package/src/plugins/defaults/memory-v3-shadow/core-set.ts +59 -0
  308. package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.test.ts +305 -0
  309. package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.ts +278 -0
  310. package/src/plugins/defaults/memory-v3-shadow/hot-set.test.ts +138 -0
  311. package/src/plugins/defaults/memory-v3-shadow/hot-set.ts +85 -0
  312. package/src/plugins/defaults/memory-v3-shadow/injector.ts +331 -24
  313. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +119 -13
  314. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +169 -114
  315. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +47 -16
  316. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +144 -66
  317. package/src/plugins/defaults/memory-v3-shadow/prune.test.ts +758 -0
  318. package/src/plugins/defaults/memory-v3-shadow/prune.ts +471 -0
  319. package/src/plugins/defaults/memory-v3-shadow/render-injection.ts +68 -16
  320. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +19 -12
  321. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +96 -43
  322. package/src/plugins/defaults/memory-v3-shadow/types.ts +34 -17
  323. package/src/plugins/defaults/title-generate/hooks/stop.ts +9 -11
  324. package/src/plugins/pipeline.ts +8 -5
  325. package/src/plugins/types.ts +5 -51
  326. package/src/providers/cache-control.ts +26 -0
  327. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +1 -2
  328. package/src/providers/model-catalog.ts +13 -1
  329. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +147 -0
  330. package/src/providers/openai/chat-completions-provider.ts +46 -0
  331. package/src/providers/openai/responses-provider.ts +45 -0
  332. package/src/providers/registry.ts +0 -8
  333. package/src/runtime/__tests__/agent-wake.test.ts +0 -1
  334. package/src/runtime/agent-wake.ts +13 -0
  335. package/src/runtime/routes/__tests__/acp-routes.test.ts +151 -0
  336. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +12 -50
  337. package/src/runtime/routes/__tests__/conversation-surface-routes.test.ts +322 -0
  338. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +0 -62
  339. package/src/runtime/routes/__tests__/plugins-routes.test.ts +31 -11
  340. package/src/runtime/routes/acp-routes.test.ts +106 -0
  341. package/src/runtime/routes/acp-routes.ts +248 -2
  342. package/src/runtime/routes/browser-tabs-routes.ts +1 -1
  343. package/src/runtime/routes/channel-verification-routes.ts +14 -5
  344. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +0 -14
  345. package/src/runtime/routes/consolidation-routes.ts +6 -82
  346. package/src/runtime/routes/conversation-list-routes.ts +6 -0
  347. package/src/runtime/routes/conversation-management-routes.ts +71 -0
  348. package/src/runtime/routes/identity-routes.ts +8 -0
  349. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
  350. package/src/runtime/routes/inference-provider-connection-routes.ts +0 -45
  351. package/src/runtime/routes/plugins-routes.ts +34 -45
  352. package/src/runtime/routes/schedule-routes.ts +43 -5
  353. package/src/runtime/routes/settings-routes.ts +140 -15
  354. package/src/runtime/routes/skills-routes.ts +18 -6
  355. package/src/runtime/services/__tests__/conversation-serializer.test.ts +140 -0
  356. package/src/runtime/services/conversation-serializer.ts +38 -1
  357. package/src/runtime/verification-outbound-actions.ts +147 -2
  358. package/src/runtime/verification-templates.ts +29 -3
  359. package/src/schedule/schedule-store.ts +19 -0
  360. package/src/skills/catalog-install.ts +77 -13
  361. package/src/tasks/task-scheduler.ts +1 -0
  362. package/src/telemetry/types.ts +66 -1
  363. package/src/telemetry/usage-telemetry-reporter.test.ts +542 -13
  364. package/src/telemetry/usage-telemetry-reporter.ts +213 -20
  365. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +49 -2
  366. package/src/tools/browser/__tests__/browser-status.test.ts +29 -5
  367. package/src/tools/browser/browser-execution.ts +27 -9
  368. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +380 -4
  369. package/src/tools/browser/cdp-client/__tests__/host-bridge-cdp-client.test.ts +107 -0
  370. package/src/tools/browser/cdp-client/__tests__/types.test.ts +6 -1
  371. package/src/tools/browser/cdp-client/factory.ts +217 -17
  372. package/src/tools/browser/cdp-client/host-bridge-cdp-client.ts +67 -0
  373. package/src/tools/browser/cdp-client/types.ts +22 -2
  374. package/src/tools/credential-execution/make-authenticated-request.ts +2 -1
  375. package/src/tools/credential-execution/manage-secure-command-tool.ts +169 -164
  376. package/src/tools/credential-execution/run-authenticated-command.ts +2 -1
  377. package/src/tools/executor.ts +39 -7
  378. package/src/tools/registry.ts +387 -5
  379. package/src/tools/schedule/create.ts +16 -0
  380. package/src/tools/schedule/list.ts +12 -4
  381. package/src/tools/schedule/update.ts +12 -0
  382. package/src/tools/skills/load.ts +11 -6
  383. package/src/tools/terminal/safe-env.ts +2 -0
  384. package/src/tools/types.ts +65 -9
  385. package/src/tools/workspace-tools/loader.ts +673 -0
  386. package/src/usage/attribution.ts +28 -0
  387. package/src/util/device-id.ts +17 -3
  388. package/src/util/platform.ts +16 -0
  389. package/tsconfig.plugin-api.json +13 -0
  390. package/src/__tests__/plugin-external-api.test.ts +0 -68
  391. package/src/__tests__/plugin-skill-contribution.test.ts +0 -355
  392. package/src/daemon/message-types/browser.ts +0 -10
  393. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
  394. package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/eval-turns.json +0 -36
  395. package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/live-turns.json +0 -37
  396. package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-eviction.test.ts +0 -106
  397. package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-skeleton.test.ts +0 -44
  398. package/src/plugins/defaults/memory-v3-shadow/working-set.ts +0 -91
  399. package/src/plugins/external-api.ts +0 -114
  400. package/src/plugins/plugin-skill-contributions.ts +0 -292
@@ -73,6 +73,52 @@ function archiveStatusClause(status: ArchiveStatusFilter) {
73
73
  }
74
74
  }
75
75
 
76
+ /**
77
+ * Raw SQL predicate for "visible in the standard (Recents) listing".
78
+ *
79
+ * Shared by the `"standard"` bucket of {@link conversationTypeClause} (list +
80
+ * count) and by every match path in {@link searchConversations} (FTS content,
81
+ * LIKE content fallback, and title LIKE) so the listing and search can never
82
+ * drift: anything the sidebar shows in Recents is also findable by search,
83
+ * and vice versa.
84
+ *
85
+ * Two arms:
86
+ * - Foreground rows: not background/scheduled/private by type, and not routed
87
+ * to the `system:background` / `system:scheduled` groups.
88
+ * - Surfaced rows (`surfaced_at IS NOT NULL`): background/scheduled rows
89
+ * explicitly promoted via the surface API. Private rows stay excluded
90
+ * unconditionally, and subagent runs are excluded from the surfaced arm so
91
+ * they can never reach the sidebar.
92
+ *
93
+ * @param alias Table name or alias qualifying the column references
94
+ * (e.g. `"c"` in the search joins).
95
+ */
96
+ function standardListingVisibilitySql(alias = "conversations"): string {
97
+ return (
98
+ `((${alias}.conversation_type NOT IN ('background', 'scheduled', 'private')` +
99
+ ` AND COALESCE(${alias}.group_id, 'system:all') NOT IN ('system:background', 'system:scheduled'))` +
100
+ ` OR ${surfacedVisibilitySql(alias)})`
101
+ );
102
+ }
103
+
104
+ /**
105
+ * Raw SQL predicate for the surfaced arm of standard-listing visibility:
106
+ * background/scheduled rows explicitly promoted via the surface API
107
+ * (`surfaced_at IS NOT NULL`), with private rows excluded unconditionally and
108
+ * subagent runs excluded so they can never reach the sidebar.
109
+ *
110
+ * Shared by {@link standardListingVisibilitySql} and
111
+ * {@link listPinnedConversations} so pinned surfaced rows stay visible
112
+ * everywhere the standard listing would show them.
113
+ */
114
+ function surfacedVisibilitySql(alias = "conversations"): string {
115
+ return (
116
+ `(${alias}.surfaced_at IS NOT NULL` +
117
+ ` AND ${alias}.conversation_type != 'private'` +
118
+ ` AND (${alias}.source IS NULL OR ${alias}.source != 'subagent'))`
119
+ );
120
+ }
121
+
76
122
  /**
77
123
  * SQL predicate selecting which bucket {@link listConversations} and
78
124
  * {@link countConversations} return, keyed by the canonical
@@ -82,7 +128,10 @@ function archiveStatusClause(status: ArchiveStatusFilter) {
82
128
  * excluding background, scheduled, and private rows. `"private"` is excluded
83
129
  * defensively because in-place snapshot restore swaps the SQLite file without
84
130
  * running migrations in-process, so legacy private rows can briefly exist
85
- * before migration cleanup deletes them.
131
+ * before migration cleanup deletes them. Background/scheduled rows with a
132
+ * non-null `surfaced_at` (explicitly promoted via the surface API) are
133
+ * included so clients can render them in the Recents grouping without a
134
+ * separate fetch.
86
135
  * - `"background"` — the background **umbrella**: background *and* scheduled
87
136
  * rows together. The back-compat bucket for the single
88
137
  * `conversationType=background` fetch that older clients (e.g. the macOS app,
@@ -101,7 +150,10 @@ function conversationTypeClause(type: ConversationType) {
101
150
  const notSubagent = sql`(${conversations.source} IS NULL OR ${conversations.source} != 'subagent')`;
102
151
  switch (type) {
103
152
  case "standard":
104
- return sql`${conversations.conversationType} NOT IN ('background', 'scheduled', 'private') AND COALESCE(group_id, 'system:all') NOT IN ('system:background', 'system:scheduled')`;
153
+ // Surfaced rows (`surfaced_at IS NOT NULL`) are promoted into the
154
+ // standard listing even when they're background/scheduled — see
155
+ // standardListingVisibilitySql for the full predicate semantics.
156
+ return sql.raw(standardListingVisibilitySql());
105
157
  case "background":
106
158
  return sql`(${conversations.conversationType} IN ('background', 'scheduled') OR group_id IN ('system:background', 'system:scheduled')) AND ${notSubagent}`;
107
159
  case "scheduled":
@@ -233,7 +285,13 @@ export function listPinnedConversations(
233
285
  .from(conversations)
234
286
  .where(
235
287
  and(
236
- sql`${conversations.conversationType} NOT IN ('background', 'scheduled', 'private')`,
288
+ // Mirror the standard listing: plain foreground rows by type, plus
289
+ // surfaced background/scheduled rows — a pinned surfaced conversation
290
+ // must stay injectable into page 1 (see surfacedVisibilitySql).
291
+ sql.raw(
292
+ `(conversations.conversation_type NOT IN ('background', 'scheduled', 'private')` +
293
+ ` OR ${surfacedVisibilitySql()})`,
294
+ ),
237
295
  sql`is_pinned = 1`,
238
296
  ...(archiveCond ? [archiveCond] : []),
239
297
  ),
@@ -417,7 +475,7 @@ export function searchConversations(
417
475
  FROM messages_fts f
418
476
  JOIN messages m ON m.id = f.message_id
419
477
  JOIN conversations c ON c.id = m.conversation_id
420
- WHERE messages_fts MATCH ? AND c.conversation_type NOT IN ('background', 'scheduled', 'private') AND COALESCE(c.group_id, 'system:all') NOT IN ('system:background', 'system:scheduled') AND c.archived_at IS NULL
478
+ WHERE messages_fts MATCH ? AND ${standardListingVisibilitySql("c")} AND c.archived_at IS NULL
421
479
  LIMIT 1000
422
480
  `,
423
481
  ftsMatch,
@@ -442,7 +500,7 @@ export function searchConversations(
442
500
  SELECT DISTINCT m.conversation_id
443
501
  FROM messages m
444
502
  JOIN conversations c ON c.id = m.conversation_id
445
- WHERE m.content LIKE ? ESCAPE '\\' AND c.conversation_type NOT IN ('background', 'scheduled', 'private') AND COALESCE(c.group_id, 'system:all') NOT IN ('system:background', 'system:scheduled') AND c.archived_at IS NULL
503
+ WHERE m.content LIKE ? ESCAPE '\\' AND ${standardListingVisibilitySql("c")} AND c.archived_at IS NULL
446
504
  LIMIT 1000
447
505
  `,
448
506
  likePattern,
@@ -456,8 +514,7 @@ export function searchConversations(
456
514
  .from(conversations)
457
515
  .where(
458
516
  and(
459
- sql`${conversations.conversationType} NOT IN ('background', 'scheduled', 'private')`,
460
- sql`COALESCE(group_id, 'system:all') NOT IN ('system:background', 'system:scheduled')`,
517
+ sql.raw(standardListingVisibilitySql()),
461
518
  sql`${conversations.title} LIKE ${titlePattern} ESCAPE '\\'`,
462
519
  sql`${conversations.archivedAt} IS NULL`,
463
520
  ),
@@ -37,6 +37,7 @@ import {
37
37
  createOnboardingEventsTable,
38
38
  createScopedApprovalGrantsTable,
39
39
  createSequenceTables,
40
+ createSkillLoadedEventsTable,
40
41
  createTasksAndWorkItemsTables,
41
42
  createWatchersAndLogsTables,
42
43
  migrate230AcpSessionHistory,
@@ -46,6 +47,7 @@ import {
46
47
  migrateActivationState,
47
48
  migrateActivationStateFkCascade,
48
49
  migrateAddConversationInferenceProfile,
50
+ migrateAddMemoryV3EverInjected,
49
51
  migrateAddMemoryV3Selections,
50
52
  migrateAddSourceTypeColumns,
51
53
  migrateAssistantContactMetadata,
@@ -77,6 +79,7 @@ import {
77
79
  migrateConversationLastNotifiedProfile,
78
80
  migrateConversationsArchivedAt,
79
81
  migrateConversationsLastMessageAt,
82
+ migrateConversationsSurfacedAt,
80
83
  migrateConversationsThreadTypeIndex,
81
84
  migrateCreateConversationGraphMemoryState,
82
85
  migrateCreateDocumentComments,
@@ -186,6 +189,7 @@ import {
186
189
  migrateRenameVerificationSessionIdColumn,
187
190
  migrateRenameVerificationTable,
188
191
  migrateRenameVoiceToPhone,
192
+ migrateScheduleDescription,
189
193
  migrateScheduleOneShotRouting,
190
194
  migrateScheduleQuietFlag,
191
195
  migrateScheduleRetryPolicy,
@@ -201,7 +205,10 @@ import {
201
205
  migrateStripIntegrationPrefixFromProviderKeys,
202
206
  migrateStripPlaceholderSentinelsFromMessages,
203
207
  migrateStripThinkingFromConsolidated,
208
+ migrateToolInvocationsCreatedAtIdIndex,
204
209
  migrateToolInvocationsMatchedRuleId,
210
+ migrateToolInvocationsSkillId,
211
+ migrateToolInvocationsTelemetryColumns,
205
212
  migrateTraceEventsCreatedAtIndex,
206
213
  migrateUsageDashboardIndexes,
207
214
  migrateUsageLlmCallCount,
@@ -478,12 +485,19 @@ export function initializeDb(): void {
478
485
  migrateLlmUsageEventsAddAssistantVersion,
479
486
  migrateAddMemoryV3Selections,
480
487
  migrateScheduleScriptTimeout,
488
+ migrateScheduleDescription,
481
489
  migrateScheduleSourceConversation,
482
490
  migrateMessagesRoleCreatedAtIndex,
483
491
  createAuthFallbackEventsTable,
484
492
  migrateAcpSessionHistoryCwd,
485
493
  migrateOnboardingEventsFunnelColumns,
486
494
  createActivationSessionsTable,
495
+ migrateToolInvocationsSkillId,
496
+ migrateToolInvocationsCreatedAtIdIndex,
497
+ migrateAddMemoryV3EverInjected,
498
+ migrateToolInvocationsTelemetryColumns,
499
+ createSkillLoadedEventsTable,
500
+ migrateConversationsSurfacedAt,
487
501
  ];
488
502
 
489
503
  // Run each migration step, catching and logging individual failures so one
@@ -1,12 +1,33 @@
1
- import { afterEach, describe, expect, mock, test } from "bun:test";
1
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
3
  import type { AssistantConfig } from "../config/types.js";
4
4
  import {
5
5
  clearEmbeddingBackendCache,
6
+ embedWithBackend,
6
7
  resetLocalEmbeddingFailureState,
7
8
  selectEmbeddingBackend,
8
9
  } from "./embedding-backend.js";
9
10
 
11
+ const getProviderKeyAsyncMock = mock(
12
+ async (_provider: string): Promise<string | undefined> => undefined,
13
+ );
14
+ mock.module("../security/secure-keys.js", () => ({
15
+ getProviderKeyAsync: getProviderKeyAsyncMock,
16
+ }));
17
+
18
+ const DISABLED_PROXY_CONTEXT = {
19
+ enabled: false,
20
+ platformBaseUrl: "",
21
+ assistantApiKey: "",
22
+ };
23
+ const resolveManagedProxyContextMock = mock(async () => DISABLED_PROXY_CONTEXT);
24
+ mock.module("../providers/platform-proxy/context.js", () => ({
25
+ resolveManagedProxyContext: resolveManagedProxyContextMock,
26
+ hasManagedProxyPrereqs: mock(async () => false),
27
+ buildManagedBaseUrl: mock(async () => undefined),
28
+ managedFallbackEnabledFor: mock(async () => false),
29
+ }));
30
+
10
31
  const LOCAL_CONFIG = {
11
32
  memory: {
12
33
  embeddings: {
@@ -73,3 +94,111 @@ describe("embedding backend cache invalidation", () => {
73
94
  expect(secondSelection.backend).toBe(firstSelection.backend);
74
95
  });
75
96
  });
97
+
98
+ const GEMINI_CONFIG = {
99
+ memory: {
100
+ embeddings: {
101
+ provider: "gemini",
102
+ geminiModel: "test-model",
103
+ },
104
+ qdrant: {
105
+ vectorSize: 3,
106
+ },
107
+ },
108
+ } as unknown as AssistantConfig;
109
+
110
+ const ENABLED_PROXY_CONTEXT = {
111
+ enabled: true,
112
+ platformBaseUrl: "https://proxy.example.com",
113
+ assistantApiKey: "stale-platform-key",
114
+ };
115
+
116
+ function jsonResponse(body: unknown, status: number): Response {
117
+ return new Response(JSON.stringify(body), {
118
+ status,
119
+ headers: { "Content-Type": "application/json" },
120
+ });
121
+ }
122
+
123
+ describe("managed-proxy Gemini fallback to direct key", () => {
124
+ const originalFetch = globalThis.fetch;
125
+ let fetchMock: ReturnType<typeof mock>;
126
+
127
+ beforeEach(() => {
128
+ clearEmbeddingBackendCache();
129
+ // Managed-proxy requests (proxy.example.com) are rejected like a platform
130
+ // proxy with a bad credential; direct Google API requests succeed.
131
+ fetchMock = mock(async (url: string | URL) => {
132
+ if (String(url).startsWith("https://proxy.example.com/")) {
133
+ return jsonResponse({ detail: "Invalid or revoked API key." }, 403);
134
+ }
135
+ return jsonResponse({ embedding: { values: [0.1, 0.2, 0.3] } }, 200);
136
+ });
137
+ globalThis.fetch = fetchMock as unknown as typeof fetch;
138
+ });
139
+
140
+ afterEach(() => {
141
+ globalThis.fetch = originalFetch;
142
+ clearEmbeddingBackendCache();
143
+ getProviderKeyAsyncMock.mockReset();
144
+ getProviderKeyAsyncMock.mockImplementation(async () => undefined);
145
+ resolveManagedProxyContextMock.mockReset();
146
+ resolveManagedProxyContextMock.mockImplementation(
147
+ async () => DISABLED_PROXY_CONTEXT,
148
+ );
149
+ });
150
+
151
+ test("falls back to the direct key when the managed-proxy primary fails", async () => {
152
+ resolveManagedProxyContextMock.mockImplementation(
153
+ async () => ENABLED_PROXY_CONTEXT,
154
+ );
155
+ getProviderKeyAsyncMock.mockImplementation(async (provider) =>
156
+ provider === "gemini" ? "direct-key" : undefined,
157
+ );
158
+
159
+ const result = await embedWithBackend(GEMINI_CONFIG, ["hello world"]);
160
+
161
+ expect(result.provider).toBe("gemini");
162
+ expect(result.vectors).toEqual([[0.1, 0.2, 0.3]]);
163
+
164
+ expect(fetchMock).toHaveBeenCalledTimes(2);
165
+ const [managedUrl, managedInit] = fetchMock.mock.calls[0] as [
166
+ string,
167
+ RequestInit,
168
+ ];
169
+ expect(managedUrl).toBe(
170
+ "https://proxy.example.com/v1/runtime-proxy/gemini/v1beta/models/test-model:embedContent",
171
+ );
172
+ expect(
173
+ (managedInit.headers as Record<string, string>)["Authorization"],
174
+ ).toBe("Bearer stale-platform-key");
175
+ const [directUrl] = fetchMock.mock.calls[1] as [string, RequestInit];
176
+ expect(directUrl).toContain("generativelanguage.googleapis.com");
177
+ expect(directUrl).toContain("key=direct-key");
178
+ });
179
+
180
+ test("surfaces the managed-proxy error when no direct key exists", async () => {
181
+ resolveManagedProxyContextMock.mockImplementation(
182
+ async () => ENABLED_PROXY_CONTEXT,
183
+ );
184
+
185
+ await expect(
186
+ embedWithBackend(GEMINI_CONFIG, ["hello world"]),
187
+ ).rejects.toThrow(/403/);
188
+ expect(fetchMock).toHaveBeenCalledTimes(1);
189
+ });
190
+
191
+ test("uses the direct key as primary when the managed proxy is disabled", async () => {
192
+ getProviderKeyAsyncMock.mockImplementation(async (provider) =>
193
+ provider === "gemini" ? "direct-key" : undefined,
194
+ );
195
+
196
+ const result = await embedWithBackend(GEMINI_CONFIG, ["another input"]);
197
+
198
+ expect(result.vectors).toEqual([[0.1, 0.2, 0.3]]);
199
+ expect(fetchMock).toHaveBeenCalledTimes(1);
200
+ const [directUrl] = fetchMock.mock.calls[0] as [string, RequestInit];
201
+ expect(directUrl).toContain("generativelanguage.googleapis.com");
202
+ expect(directUrl).toContain("key=direct-key");
203
+ });
204
+ });
@@ -1,6 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
2
 
3
- import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
4
3
  import { getOllamaBaseUrlEnv } from "../config/env.js";
5
4
  import { resolveCallSiteConfig } from "../config/llm-resolver.js";
6
5
  import type { AssistantConfig } from "../config/types.js";
@@ -267,6 +266,56 @@ function geminiCacheExtras(config: AssistantConfig): string[] {
267
266
  return extras;
268
267
  }
269
268
 
269
+ /** Build (or reuse) the direct-API Gemini backend for the given key. */
270
+ function getDirectGeminiBackend(
271
+ config: AssistantConfig,
272
+ geminiKey: string,
273
+ ): EmbeddingBackend {
274
+ return getCachedOrCreate(
275
+ "gemini",
276
+ config.memory.embeddings.geminiModel,
277
+ () =>
278
+ new GeminiEmbeddingBackend(
279
+ geminiKey,
280
+ config.memory.embeddings.geminiModel,
281
+ {
282
+ taskType: config.memory.embeddings.geminiTaskType,
283
+ dimensions: config.memory.embeddings.geminiDimensions,
284
+ },
285
+ ),
286
+ geminiCacheExtras(config),
287
+ );
288
+ }
289
+
290
+ /**
291
+ * Build (or reuse) the managed-proxy Gemini backend, or return undefined
292
+ * when the managed proxy prerequisites (platform URL + assistant API key)
293
+ * are not satisfied.
294
+ */
295
+ async function tryGetManagedGeminiBackend(
296
+ config: AssistantConfig,
297
+ ): Promise<EmbeddingBackend | undefined> {
298
+ const proxyCtx = await resolveManagedProxyContext();
299
+ const meta = PLATFORM_PROVIDER_META["gemini"];
300
+ if (!proxyCtx.enabled || !meta?.managed || !meta.proxyPath) return undefined;
301
+ const managedBaseUrl = `${proxyCtx.platformBaseUrl}${meta.proxyPath}`;
302
+ return getCachedOrCreate(
303
+ "gemini",
304
+ config.memory.embeddings.geminiModel,
305
+ () =>
306
+ new GeminiEmbeddingBackend(
307
+ proxyCtx.assistantApiKey,
308
+ config.memory.embeddings.geminiModel,
309
+ {
310
+ taskType: config.memory.embeddings.geminiTaskType,
311
+ dimensions: config.memory.embeddings.geminiDimensions ?? 3072,
312
+ managedBaseUrl,
313
+ },
314
+ ),
315
+ [...geminiCacheExtras(config), "managed"],
316
+ );
317
+ }
318
+
270
319
  export interface EmbeddingBackendSelection {
271
320
  backend: EmbeddingBackend | null;
272
321
  reason: string | null;
@@ -302,41 +351,13 @@ export async function selectEmbeddingBackend(
302
351
  };
303
352
  }
304
353
 
305
- // When the managed-gemini-embeddings-enabled flag is on AND managed proxy
306
- // prerequisites are satisfied, insert managed-proxy Gemini at the front of
307
- // the auto chain so platform assistants use Vellum-managed Gemini embeddings.
308
- if (
309
- (requested === "auto" || requested === "gemini") &&
310
- isAssistantFeatureFlagEnabled("managed-gemini-embeddings-enabled", config)
311
- ) {
312
- const proxyCtx = await resolveManagedProxyContext();
313
- if (proxyCtx.enabled) {
314
- const meta = PLATFORM_PROVIDER_META["gemini"];
315
- if (meta?.managed && meta.proxyPath) {
316
- const managedBaseUrl = `${proxyCtx.platformBaseUrl}${meta.proxyPath}`;
317
- const managedModel = config.memory.embeddings.geminiModel;
318
- const managedDimensions =
319
- config.memory.embeddings.geminiDimensions ?? 3072;
320
- const extras = geminiCacheExtras(config);
321
- return {
322
- backend: getCachedOrCreate(
323
- "gemini",
324
- managedModel,
325
- () =>
326
- new GeminiEmbeddingBackend(
327
- proxyCtx.assistantApiKey,
328
- managedModel,
329
- {
330
- taskType: config.memory.embeddings.geminiTaskType,
331
- dimensions: managedDimensions,
332
- managedBaseUrl,
333
- },
334
- ),
335
- [...extras, "managed"],
336
- ),
337
- reason: null,
338
- };
339
- }
354
+ // When managed proxy prerequisites are satisfied, insert managed-proxy Gemini
355
+ // at the front of the auto chain so platform assistants use Vellum-managed
356
+ // Gemini embeddings.
357
+ if (requested === "auto" || requested === "gemini") {
358
+ const managed = await tryGetManagedGeminiBackend(config);
359
+ if (managed) {
360
+ return { backend: managed, reason: null };
340
361
  }
341
362
  }
342
363
 
@@ -405,20 +426,7 @@ export async function selectEmbeddingBackend(
405
426
  continue;
406
427
  }
407
428
  return {
408
- backend: getCachedOrCreate(
409
- "gemini",
410
- config.memory.embeddings.geminiModel,
411
- () =>
412
- new GeminiEmbeddingBackend(
413
- geminiKey,
414
- config.memory.embeddings.geminiModel,
415
- {
416
- taskType: config.memory.embeddings.geminiTaskType,
417
- dimensions: config.memory.embeddings.geminiDimensions,
418
- },
419
- ),
420
- geminiCacheExtras(config),
421
- ),
429
+ backend: getDirectGeminiBackend(config, geminiKey),
422
430
  reason: null,
423
431
  };
424
432
  }
@@ -454,7 +462,7 @@ export async function getMemoryBackendStatus(config: AssistantConfig): Promise<{
454
462
  model: string | null;
455
463
  reason: string | null;
456
464
  }> {
457
- if (!config.memory.enabled) {
465
+ if (config.memory.enabled === false) {
458
466
  return {
459
467
  enabled: false,
460
468
  degraded: false,
@@ -511,6 +519,21 @@ export async function embedWithBackend(
511
519
  ? await selectFallbackBackends(config, selection.backend.provider)
512
520
  : [];
513
521
 
522
+ // A managed-proxy Gemini primary can fail at the proxy (e.g. a stale or
523
+ // revoked platform credential) while a valid direct Gemini key sits in the
524
+ // credential store. The provider-level chain above never includes Gemini
525
+ // when Gemini IS the primary, so without this the direct key would never
526
+ // be tried and the provider would stay down for the whole process.
527
+ if (
528
+ selection.backend instanceof GeminiEmbeddingBackend &&
529
+ selection.backend.managed
530
+ ) {
531
+ const geminiKey = await getProviderKeyAsync("gemini");
532
+ if (geminiKey) {
533
+ fallbacks.push(getDirectGeminiBackend(config, geminiKey));
534
+ }
535
+ }
536
+
514
537
  // ── Compute provider-specific vector cache extras ───────────────
515
538
  const vectorExtras =
516
539
  primaryProvider === "gemini" ? geminiCacheExtras(config) : undefined;
@@ -661,54 +684,12 @@ async function selectFallbackBackends(
661
684
  case "gemini": {
662
685
  const geminiKey = await getProviderKeyAsync("gemini");
663
686
  if (geminiKey) {
664
- backends.push(
665
- getCachedOrCreate(
666
- "gemini",
667
- config.memory.embeddings.geminiModel,
668
- () =>
669
- new GeminiEmbeddingBackend(
670
- geminiKey,
671
- config.memory.embeddings.geminiModel,
672
- {
673
- taskType: config.memory.embeddings.geminiTaskType,
674
- dimensions: config.memory.embeddings.geminiDimensions,
675
- },
676
- ),
677
- geminiCacheExtras(config),
678
- ),
679
- );
680
- } else if (
681
- isAssistantFeatureFlagEnabled(
682
- "managed-gemini-embeddings-enabled",
683
- config,
684
- )
685
- ) {
687
+ backends.push(getDirectGeminiBackend(config, geminiKey));
688
+ } else {
686
689
  // Try managed proxy Gemini as fallback when no direct key exists.
687
- const proxyCtx = await resolveManagedProxyContext();
688
- const meta = PLATFORM_PROVIDER_META["gemini"];
689
- if (proxyCtx.enabled && meta?.managed && meta.proxyPath) {
690
- const managedBaseUrl = `${proxyCtx.platformBaseUrl}${meta.proxyPath}`;
691
- const managedModel = config.memory.embeddings.geminiModel;
692
- const managedDimensions =
693
- config.memory.embeddings.geminiDimensions ?? 3072;
694
- const extras = geminiCacheExtras(config);
695
- backends.push(
696
- getCachedOrCreate(
697
- "gemini",
698
- managedModel,
699
- () =>
700
- new GeminiEmbeddingBackend(
701
- proxyCtx.assistantApiKey,
702
- managedModel,
703
- {
704
- taskType: config.memory.embeddings.geminiTaskType,
705
- dimensions: managedDimensions,
706
- managedBaseUrl,
707
- },
708
- ),
709
- [...extras, "managed"],
710
- ),
711
- );
690
+ const managed = await tryGetManagedGeminiBackend(config);
691
+ if (managed) {
692
+ backends.push(managed);
712
693
  } else {
713
694
  // Check managed cache variant first, then non-managed, so a warm
714
695
  // managed backend survives transient proxy-context blips.
@@ -724,14 +705,6 @@ async function selectFallbackBackends(
724
705
  );
725
706
  if (cached) backends.push(cached);
726
707
  }
727
- } else {
728
- // Preserve cached backend on transient credential-store failures.
729
- const cached = getCached(
730
- "gemini",
731
- config.memory.embeddings.geminiModel,
732
- geminiCacheExtras(config),
733
- );
734
- if (cached) backends.push(cached);
735
708
  }
736
709
  break;
737
710
  }
@@ -36,6 +36,11 @@ export class GeminiEmbeddingBackend implements EmbeddingBackend {
36
36
  this.managedBaseUrl = options?.managedBaseUrl;
37
37
  }
38
38
 
39
+ /** True when requests route through the managed platform proxy. */
40
+ get managed(): boolean {
41
+ return Boolean(this.managedBaseUrl);
42
+ }
43
+
39
44
  async embed(
40
45
  inputs: EmbeddingInput[],
41
46
  options?: EmbeddingRequestOptions,
@@ -173,6 +173,10 @@ const { applyNestedDefaults } = await import("../../../config/loader.js");
173
173
  const { getSqliteFrom } = await import("../../db-connection.js");
174
174
  const { migrateActivationState } =
175
175
  await import("../../migrations/232-activation-state.js");
176
+ const { migrateAddMemoryV3EverInjected } =
177
+ await import("../../migrations/277-add-memory-v3-ever-injected.js");
178
+ const { getActiveSlugs: getV3ActiveSlugs, recordInjected: recordV3Injected } =
179
+ await import("../../../plugins/defaults/memory-v3-shadow/ever-injected-store.js");
176
180
  const schema = await import("../../schema.js");
177
181
  const { _resetMemoryV2QdrantForTests } = await import("../../v2/qdrant.js");
178
182
  const { hydrate: hydrateActivationState, save: saveActivationState } =
@@ -210,6 +214,8 @@ function createTestDb(): DrizzleDb {
210
214
  )
211
215
  `);
212
216
  migrateActivationState(db);
217
+ // `onCompacted` also clears memory-v3's everInjected record.
218
+ migrateAddMemoryV3EverInjected(db);
213
219
  return db;
214
220
  }
215
221
 
@@ -589,10 +595,16 @@ describe("ConversationGraphMemory.onCompacted — v2 activation eviction", () =>
589
595
  const before = await hydrateActivationState(testDbHandle!, conversationId);
590
596
  expect(before?.everInjected.map((e) => e.slug)).toContain("alice-vscode");
591
597
 
598
+ // Seed a memory-v3 everInjected record too: the same trigger clears it
599
+ // (the frozen card blocks those slugs rode were just compacted away).
600
+ recordV3Injected(conversationId, [{ slug: "alice-vscode", bytes: 100 }]);
601
+ expect(getV3ActiveSlugs(conversationId)).toEqual(new Set(["alice-vscode"]));
602
+
592
603
  await memory.onCompacted(1);
593
604
 
594
605
  const after = await hydrateActivationState(testDbHandle!, conversationId);
595
606
  expect(after?.everInjected).toEqual([]);
607
+ expect(getV3ActiveSlugs(conversationId)).toEqual(new Set());
596
608
 
597
609
  // Turn 2 — same Qdrant relevance. With everInjected cleared the slug
598
610
  // should appear again in the injection block (re-attached on the new
@@ -60,6 +60,10 @@ const { handleRemember } = await import("../tool-handlers.js");
60
60
  const { applyNestedDefaults } = await import("../../../config/loader.js");
61
61
 
62
62
  const CONFIG = applyNestedDefaults({});
63
+ const CONFIG_MEMORY_OFF = {
64
+ ...CONFIG,
65
+ memory: { ...CONFIG.memory, enabled: false },
66
+ };
63
67
  const CONFIG_V2_OFF = {
64
68
  ...CONFIG,
65
69
  memory: { ...CONFIG.memory, v2: { ...CONFIG.memory.v2, enabled: false } },
@@ -81,6 +85,21 @@ function todaysArchiveBasename(now: Date = new Date()): string {
81
85
  }
82
86
 
83
87
  describe("handleRemember — memory.v2.enabled on", () => {
88
+ test("does not write when global memory is disabled", () => {
89
+ const result = handleRemember(
90
+ { content: "do not save this" },
91
+ "conv-memory-off",
92
+ "default",
93
+ CONFIG_MEMORY_OFF,
94
+ );
95
+
96
+ expect(result.success).toBe(false);
97
+ expect(result.message).toContain("Memory is disabled");
98
+ expect(existsSync(join(tmpWorkspace, "memory", "buffer.md"))).toBe(false);
99
+ expect(existsSync(join(tmpWorkspace, "pkb", "buffer.md"))).toBe(false);
100
+ expect(enqueueCalls).toEqual([]);
101
+ });
102
+
84
103
  test("writes to memory/buffer.md and memory/archive/<today>.md", () => {
85
104
  const result = handleRemember(
86
105
  { content: "Alice prefers VS Code over Vim" },