@vellumai/assistant 0.10.2-dev.202606250318.5e7cfb0 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (430) hide show
  1. package/bun.lock +0 -20
  2. package/docs/workspace-tools.md +33 -42
  3. package/eslint-rules/cli-no-daemon-internals.js +0 -6
  4. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +0 -31
  5. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +0 -44
  6. package/node_modules/@vellumai/gateway-client/src/index.ts +0 -14
  7. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +0 -17
  8. package/node_modules/@vellumai/service-contracts/package.json +0 -1
  9. package/node_modules/@vellumai/service-contracts/src/index.ts +0 -1
  10. package/openapi.yaml +0 -155
  11. package/package.json +1 -4
  12. package/scripts/test.sh +15 -36
  13. package/src/__tests__/actor-token-service.test.ts +14 -36
  14. package/src/__tests__/agent-loop-override-profile.test.ts +0 -1
  15. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -2
  16. package/src/__tests__/agent-wake-override-profile.test.ts +0 -2
  17. package/src/__tests__/annotate-activity-metadata.test.ts +0 -2
  18. package/src/__tests__/annotate-risk-options.test.ts +0 -2
  19. package/src/__tests__/approval-cascade.test.ts +0 -2
  20. package/src/__tests__/assistant-attachments.test.ts +0 -42
  21. package/src/__tests__/background-workers-disk-pressure.test.ts +0 -2
  22. package/src/__tests__/btw-routes.test.ts +0 -2
  23. package/src/__tests__/build-persisted-content.test.ts +0 -2
  24. package/src/__tests__/call-controller.test.ts +0 -19
  25. package/src/__tests__/channel-guardian.test.ts +58 -94
  26. package/src/__tests__/channel-reply-delivery.test.ts +0 -2
  27. package/src/__tests__/compaction-events.test.ts +0 -2
  28. package/src/__tests__/compaction.benchmark.test.ts +0 -2
  29. package/src/__tests__/compactor-call-site-logging.test.ts +0 -2
  30. package/src/__tests__/compactor-low-watermark-cut.test.ts +0 -2
  31. package/src/__tests__/compactor-preserved-tail-count.test.ts +0 -2
  32. package/src/__tests__/compactor-summary-call-truncation.test.ts +0 -2
  33. package/src/__tests__/compactor-web-search-strip.test.ts +0 -2
  34. package/src/__tests__/config-loader-backfill.test.ts +10 -123
  35. package/src/__tests__/config-schema.test.ts +0 -1
  36. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +29 -31
  37. package/src/__tests__/contacts-relay-reads.test.ts +15 -13
  38. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -2
  39. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +0 -2
  40. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +0 -2
  41. package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
  42. package/src/__tests__/conversation-agent-loop.test.ts +0 -134
  43. package/src/__tests__/conversation-analysis-routes.test.ts +0 -2
  44. package/src/__tests__/conversation-app-control-lifecycle.test.ts +0 -2
  45. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -2
  46. package/src/__tests__/conversation-history-web-search.test.ts +0 -2
  47. package/src/__tests__/conversation-load-history-repair.test.ts +0 -2
  48. package/src/__tests__/conversation-load-history-stripped.test.ts +0 -2
  49. package/src/__tests__/conversation-pairing.test.ts +0 -2
  50. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +0 -2
  51. package/src/__tests__/conversation-process-callsite.test.ts +0 -2
  52. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -2
  53. package/src/__tests__/conversation-queue.test.ts +0 -91
  54. package/src/__tests__/conversation-routes-guardian-reply.test.ts +0 -14
  55. package/src/__tests__/conversation-routes-slash-commands.test.ts +0 -14
  56. package/src/__tests__/conversation-slash-queue.test.ts +0 -2
  57. package/src/__tests__/conversation-slash-unknown.test.ts +0 -2
  58. package/src/__tests__/conversation-speed-override.test.ts +0 -2
  59. package/src/__tests__/conversation-surfaces-task-progress.test.ts +0 -29
  60. package/src/__tests__/conversation-title-service.test.ts +0 -2
  61. package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -47
  62. package/src/__tests__/conversation-usage.test.ts +0 -2
  63. package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -2
  64. package/src/__tests__/conversation-workspace-injection.test.ts +0 -2
  65. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -2
  66. package/src/__tests__/credential-security-invariants.test.ts +1 -1
  67. package/src/__tests__/db-migration-rollback.test.ts +171 -205
  68. package/src/__tests__/db-test-helpers.ts +4 -5
  69. package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -4
  70. package/src/__tests__/disk-pressure-guard.test.ts +0 -41
  71. package/src/__tests__/dm-persistence.test.ts +0 -2
  72. package/src/__tests__/emit-signal-routing-intent.test.ts +5 -10
  73. package/src/__tests__/events-dev-bypass-actor.test.ts +1 -7
  74. package/src/__tests__/exploration-drift-hook.test.ts +2 -3
  75. package/src/__tests__/filing-service.test.ts +0 -2
  76. package/src/__tests__/guardian-binding-drift-heal.test.ts +10 -75
  77. package/src/__tests__/guardian-dispatch.test.ts +1 -95
  78. package/src/__tests__/guardian-outbound-http.test.ts +0 -13
  79. package/src/__tests__/heartbeat-disk-pressure.test.ts +0 -2
  80. package/src/__tests__/heartbeat-service.test.ts +0 -2
  81. package/src/__tests__/helpers/channel-test-adapter.ts +7 -1
  82. package/src/__tests__/host-app-control-routes.test.ts +30 -24
  83. package/src/__tests__/host-bash-routes.test.ts +41 -31
  84. package/src/__tests__/host-browser-routes.test.ts +32 -26
  85. package/src/__tests__/host-cu-routes-targeted.test.ts +33 -25
  86. package/src/__tests__/host-file-routes-targeted.test.ts +52 -40
  87. package/src/__tests__/host-transfer-routes-targeted.test.ts +43 -31
  88. package/src/__tests__/http-user-message-parity.test.ts +8 -290
  89. package/src/__tests__/inbound-invite-redemption.test.ts +0 -28
  90. package/src/__tests__/inbound-slack-persistence.test.ts +0 -2
  91. package/src/__tests__/invite-redemption-service.test.ts +0 -198
  92. package/src/__tests__/llm-context-normalization.test.ts +0 -105
  93. package/src/__tests__/llm-request-log-error-payload.test.ts +9 -71
  94. package/src/__tests__/llm-usage-store.test.ts +0 -25
  95. package/src/__tests__/mcp-health-check.test.ts +1 -2
  96. package/src/__tests__/media-stream-server-integration.test.ts +0 -127
  97. package/src/__tests__/memory-retrieval-hook.test.ts +0 -2
  98. package/src/__tests__/messaging-send-tool.test.ts +0 -2
  99. package/src/__tests__/migration-import-from-url.test.ts +2 -2
  100. package/src/__tests__/mtime-cache.test.ts +5 -146
  101. package/src/__tests__/native-web-search.test.ts +0 -2
  102. package/src/__tests__/non-member-access-request.test.ts +17 -189
  103. package/src/__tests__/notification-broadcaster.test.ts +0 -4
  104. package/src/__tests__/notification-decision-recipient-context.test.ts +32 -33
  105. package/src/__tests__/notification-deep-link.test.ts +0 -6
  106. package/src/__tests__/notification-guardian-path.test.ts +0 -19
  107. package/src/__tests__/openai-provider.test.ts +12 -22
  108. package/src/__tests__/openai-responses-provider.test.ts +2 -12
  109. package/src/__tests__/outbound-slack-persistence.test.ts +0 -2
  110. package/src/__tests__/pending-interactions-resolved-event.test.ts +4 -7
  111. package/src/__tests__/persistence-secret-redaction.test.ts +0 -2
  112. package/src/__tests__/plugin-bootstrap.test.ts +73 -3
  113. package/src/__tests__/plugin-route-contribution.test.ts +17 -4
  114. package/src/__tests__/plugin-tool-contribution.test.ts +18 -3
  115. package/src/__tests__/plugin-types.test.ts +2 -0
  116. package/src/__tests__/process-message-background-slack.test.ts +0 -2
  117. package/src/__tests__/process-message-display-content.test.ts +0 -2
  118. package/src/__tests__/provider-error-scenarios.test.ts +4 -5
  119. package/src/__tests__/provider-usage-tracking.test.ts +0 -39
  120. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +0 -2
  121. package/src/__tests__/registry.test.ts +1 -4
  122. package/src/__tests__/relay-server.test.ts +25 -694
  123. package/src/__tests__/runtime-attachment-metadata.test.ts +1 -0
  124. package/src/__tests__/secret-ingress-http.test.ts +0 -14
  125. package/src/__tests__/send-endpoint-busy.test.ts +8 -30
  126. package/src/__tests__/skills.test.ts +0 -44
  127. package/src/__tests__/slack-inbound-verification.test.ts +2 -47
  128. package/src/__tests__/stt-hints.test.ts +13 -44
  129. package/src/__tests__/subagent-detail.test.ts +0 -27
  130. package/src/__tests__/subagent-disposal.test.ts +0 -65
  131. package/src/__tests__/subagent-notify-parent.test.ts +0 -2
  132. package/src/__tests__/subagent-role-registry.test.ts +2 -7
  133. package/src/__tests__/subagent-spawn-tool-fork.test.ts +0 -2
  134. package/src/__tests__/subagent-tools.test.ts +0 -2
  135. package/src/__tests__/suggestion-routes.test.ts +0 -2
  136. package/src/__tests__/title-generate-hook.test.ts +0 -2
  137. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -2
  138. package/src/__tests__/tool-executor.test.ts +11 -16
  139. package/src/__tests__/tool-preview-lifecycle.test.ts +0 -2
  140. package/src/__tests__/tool-result-metadata-plumbing.test.ts +0 -2
  141. package/src/__tests__/tool-start-timestamp.test.ts +0 -2
  142. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +10 -10
  143. package/src/__tests__/twilio-routes.test.ts +0 -96
  144. package/src/__tests__/ui-file-upload-surface.test.ts +0 -86
  145. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  146. package/src/__tests__/voice-invite-redemption.test.ts +0 -33
  147. package/src/__tests__/web-search-backend-failure.test.ts +0 -2
  148. package/src/__tests__/workspace-migration-remove-hooks.test.ts +35 -14
  149. package/src/__tests__/workspace-tool-loader.test.ts +2 -195
  150. package/src/__tests__/workspace-tools-watcher-flag.test.ts +70 -0
  151. package/src/agent/loop.ts +0 -56
  152. package/src/api/index.ts +1 -19
  153. package/src/api/responses/llm-request-log-entry.ts +0 -29
  154. package/src/api/responses/subagent-detail.ts +0 -17
  155. package/src/api/surfaces.ts +3 -39
  156. package/src/approvals/guardian-request-resolvers.ts +11 -1
  157. package/src/calls/__tests__/relay-setup-router.test.ts +4 -262
  158. package/src/calls/call-domain.ts +3 -3
  159. package/src/calls/guardian-dispatch.ts +8 -10
  160. package/src/calls/inbound-trust-reader.ts +1 -17
  161. package/src/calls/media-stream-server.ts +0 -21
  162. package/src/calls/relay-server.ts +50 -167
  163. package/src/calls/relay-setup-router.ts +7 -37
  164. package/src/calls/relay-verification.ts +4 -4
  165. package/src/calls/stt-hints.ts +12 -9
  166. package/src/calls/twilio-routes.ts +4 -14
  167. package/src/channels/types.ts +20 -10
  168. package/src/cli/commands/__tests__/cache.test.ts +1 -8
  169. package/src/cli/commands/cache.ts +181 -194
  170. package/src/cli/commands/db/__tests__/repair.test.ts +5 -6
  171. package/src/cli/commands/db/status.ts +1 -37
  172. package/src/cli/commands/mcp.ts +218 -252
  173. package/src/cli/commands/memory/index.ts +0 -2
  174. package/src/cli/commands/plugins.ts +3 -75
  175. package/src/cli/lib/__tests__/install-from-github.test.ts +0 -102
  176. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +1 -160
  177. package/src/cli/lib/list-installed-plugins.ts +1 -179
  178. package/src/config/__tests__/sync-gated-profiles.test.ts +3 -11
  179. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +17 -27
  180. package/src/config/bundled-skills/contacts/tools/contact-search.ts +3 -13
  181. package/src/config/bundled-skills/subagent/SKILL.md +1 -1
  182. package/src/config/bundled-skills/subagent/TOOLS.json +1 -1
  183. package/src/config/feature-flag-registry.json +13 -5
  184. package/src/config/loader.ts +5 -38
  185. package/src/config/schemas/__tests__/memory-v3.test.ts +0 -1
  186. package/src/config/schemas/memory-lifecycle.ts +0 -12
  187. package/src/config/schemas/memory-v3.ts +0 -7
  188. package/src/config/schemas/memory.ts +0 -4
  189. package/src/config/schemas/timeouts.ts +0 -8
  190. package/src/config/seed-inference-profiles.ts +11 -21
  191. package/src/config/skills.ts +5 -27
  192. package/src/config/sync-gated-profiles.ts +13 -12
  193. package/src/contacts/contacts-write.ts +0 -3
  194. package/src/daemon/assistant-attachments.ts +4 -27
  195. package/src/daemon/conversation-agent-loop.ts +0 -28
  196. package/src/daemon/conversation-process.ts +16 -35
  197. package/src/daemon/conversation-surfaces.ts +38 -111
  198. package/src/daemon/conversation-tool-setup.ts +16 -50
  199. package/src/daemon/conversation.ts +1 -13
  200. package/src/daemon/disk-pressure-guard.ts +2 -12
  201. package/src/daemon/event-loop-watchdog.ts +1 -28
  202. package/src/daemon/external-plugins-bootstrap.ts +34 -4
  203. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -25
  204. package/src/daemon/handlers/config-a2a.ts +14 -6
  205. package/src/daemon/handlers/config-channels.ts +22 -78
  206. package/src/daemon/handlers/conversations.ts +0 -77
  207. package/src/daemon/lifecycle.ts +0 -4
  208. package/src/daemon/mcp-reload-service.ts +0 -10
  209. package/src/daemon/memory-v2-startup.test.ts +0 -72
  210. package/src/daemon/memory-v2-startup.ts +19 -87
  211. package/src/daemon/message-types/conversations.ts +0 -2
  212. package/src/daemon/message-types/surfaces.ts +12 -12
  213. package/src/daemon/server.ts +4 -0
  214. package/src/daemon/shutdown-handlers.ts +0 -20
  215. package/src/daemon/tool-setup-types.ts +0 -9
  216. package/src/daemon/workspace-tools-watcher.ts +328 -0
  217. package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
  218. package/src/ipc/assistant-server.ts +2 -2
  219. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
  220. package/src/mcp/client.ts +1 -15
  221. package/src/mcp/mcp-auth-orchestrator.ts +1 -6
  222. package/src/mcp/mcp-oauth-provider.ts +8 -19
  223. package/src/memory/__tests__/memory-retrospective-job.test.ts +0 -8
  224. package/src/memory/conversation-crud.ts +0 -38
  225. package/src/memory/db-connection.ts +3 -22
  226. package/src/memory/db-init.ts +502 -36
  227. package/src/memory/db-singleton.ts +4 -6
  228. package/src/memory/jobs-worker.ts +0 -58
  229. package/src/memory/llm-request-log-store.ts +1 -26
  230. package/src/memory/llm-usage-store.ts +20 -48
  231. package/src/memory/memory-retrospective-job.ts +8 -9
  232. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +56 -130
  233. package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
  234. package/src/memory/migrations/registry.ts +573 -0
  235. package/src/memory/migrations/run-migrations.ts +6 -90
  236. package/src/memory/migrations/validate-migration-state.ts +66 -101
  237. package/src/memory/schema/conversations.ts +0 -9
  238. package/src/memory/schema/infrastructure.ts +0 -20
  239. package/src/memory/v2/__tests__/cli-command-store.test.ts +0 -25
  240. package/src/memory/v2/__tests__/skill-store.test.ts +0 -80
  241. package/src/memory/v2/cli-command-store.ts +38 -75
  242. package/src/memory/v2/prompts/consolidation.ts +82 -13
  243. package/src/memory/v2/prompts/router.ts +93 -21
  244. package/src/memory/v2/skill-store.ts +31 -68
  245. package/src/notifications/__tests__/broadcaster.test.ts +8 -16
  246. package/src/notifications/__tests__/decision-engine.test.ts +9 -78
  247. package/src/notifications/broadcaster.ts +1 -8
  248. package/src/notifications/decision-engine.ts +7 -15
  249. package/src/notifications/destination-resolver.ts +24 -68
  250. package/src/notifications/emit-signal.ts +14 -39
  251. package/src/permissions/question-prompter.test.ts +1 -1
  252. package/src/permissions/question-prompter.ts +4 -7
  253. package/src/plugin-api/index.ts +6 -6
  254. package/src/plugin-api/types.ts +5 -3
  255. package/src/plugin-api/vision-support.test.ts +4 -28
  256. package/src/plugin-api/vision-support.ts +31 -66
  257. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -161
  258. package/src/plugins/defaults/advisor/consult.ts +6 -110
  259. package/src/plugins/defaults/advisor/steering.ts +2 -14
  260. package/src/plugins/defaults/advisor/tools/advisor.ts +5 -32
  261. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +1 -2
  262. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +7 -47
  263. package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +11 -10
  264. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +20 -12
  265. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +11 -42
  266. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +3 -33
  267. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +4 -48
  268. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +8 -4
  269. package/src/plugins/defaults/memory-v3-shadow/injector.ts +15 -43
  270. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +2 -11
  271. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +13 -77
  272. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +11 -12
  273. package/src/plugins/mtime-cache.ts +291 -76
  274. package/src/plugins/pipeline.ts +13 -111
  275. package/src/plugins/types.ts +2 -0
  276. package/src/providers/anthropic/client.ts +0 -5
  277. package/src/providers/call-site-routing.ts +0 -4
  278. package/src/providers/model-catalog.ts +0 -16
  279. package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
  280. package/src/providers/openai/chat-completions-provider.ts +83 -37
  281. package/src/providers/openai/responses-provider.ts +46 -50
  282. package/src/providers/openrouter/client.ts +0 -5
  283. package/src/providers/provider-send-message.ts +0 -4
  284. package/src/providers/ratelimit.ts +0 -4
  285. package/src/providers/retry.ts +0 -4
  286. package/src/providers/types.ts +0 -9
  287. package/src/providers/usage-tracking.ts +0 -4
  288. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +3 -335
  289. package/src/runtime/access-request-helper.ts +39 -19
  290. package/src/runtime/actor-trust-resolver.ts +2 -2
  291. package/src/runtime/assistant-event-hub.ts +1 -1
  292. package/src/runtime/assistant-stream-state.ts +2 -9
  293. package/src/runtime/auth/require-bound-guardian.ts +11 -21
  294. package/src/runtime/channel-verification-service.ts +31 -56
  295. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
  296. package/src/runtime/guardian-vellum-migration.ts +7 -66
  297. package/src/runtime/invite-redemption-service.ts +187 -198
  298. package/src/runtime/local-actor-identity.ts +11 -76
  299. package/src/runtime/pending-interactions.ts +1 -11
  300. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +5 -56
  301. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  302. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +0 -187
  303. package/src/runtime/routes/browser-routes.ts +1 -1
  304. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +5 -13
  305. package/src/runtime/routes/channel-verification-routes.ts +3 -3
  306. package/src/runtime/routes/contact-routes.ts +32 -8
  307. package/src/runtime/routes/conversation-cli-routes.ts +5 -4
  308. package/src/runtime/routes/conversation-list-routes.ts +7 -4
  309. package/src/runtime/routes/conversation-query-routes.ts +0 -72
  310. package/src/runtime/routes/conversation-routes.ts +85 -84
  311. package/src/runtime/routes/events-routes.ts +2 -2
  312. package/src/runtime/routes/global-search-routes.ts +1 -3
  313. package/src/runtime/routes/guardian-action-routes.ts +5 -4
  314. package/src/runtime/routes/host-app-control-routes.ts +4 -5
  315. package/src/runtime/routes/host-bash-routes.ts +4 -5
  316. package/src/runtime/routes/host-browser-routes.ts +11 -9
  317. package/src/runtime/routes/host-cu-routes.ts +4 -5
  318. package/src/runtime/routes/host-file-routes.ts +4 -5
  319. package/src/runtime/routes/host-transfer-routes.ts +6 -6
  320. package/src/runtime/routes/http-adapter.ts +1 -1
  321. package/src/runtime/routes/identity-routes.ts +2 -3
  322. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  323. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +5 -97
  324. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +49 -61
  325. package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -16
  326. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
  327. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +8 -21
  328. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +3 -14
  329. package/src/runtime/routes/index.ts +0 -2
  330. package/src/runtime/routes/llm-context-normalization.ts +0 -83
  331. package/src/runtime/routes/mcp-auth-routes.ts +19 -171
  332. package/src/runtime/routes/migration-rollback-routes.ts +3 -4
  333. package/src/runtime/routes/migration-routes.ts +1 -4
  334. package/src/runtime/routes/subagents-routes.ts +0 -5
  335. package/src/runtime/routes/surface-action-routes.ts +56 -42
  336. package/src/runtime/services/__tests__/conversation-serializer.test.ts +0 -1
  337. package/src/runtime/services/conversation-serializer.ts +9 -7
  338. package/src/runtime/tool-grant-request-helper.ts +3 -3
  339. package/src/runtime/trust-verdict-consumer.ts +9 -85
  340. package/src/runtime/verification-outbound-actions.ts +18 -18
  341. package/src/signals/user-message.ts +0 -16
  342. package/src/subagent/manager.ts +0 -9
  343. package/src/subagent/types.ts +3 -3
  344. package/src/telemetry/types.ts +1 -34
  345. package/src/telemetry/usage-telemetry-reporter.test.ts +2 -3
  346. package/src/telemetry/usage-telemetry-reporter.ts +3 -87
  347. package/src/tools/ask-question/ask-question-tool.test.ts +0 -29
  348. package/src/tools/ask-question/ask-question-tool.ts +0 -13
  349. package/src/tools/executor.ts +4 -4
  350. package/src/tools/registry.ts +0 -18
  351. package/src/tools/shared/filesystem/path-policy.ts +5 -12
  352. package/src/tools/tool-approval-handler.ts +1 -1
  353. package/src/tools/tool-defaults.ts +2 -9
  354. package/src/tools/tool-manifest.ts +0 -3
  355. package/src/tools/types.ts +2 -17
  356. package/src/tools/workspace-tools/loader.ts +244 -348
  357. package/src/util/errors.ts +1 -26
  358. package/src/util/platform.ts +0 -5
  359. package/src/workflows/library.test.ts +0 -140
  360. package/src/workflows/library.ts +28 -82
  361. package/src/workspace/migrations/017-seed-persona-dirs.ts +34 -3
  362. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +24 -3
  363. package/src/workspace/migrations/048-remove-workspace-hooks.ts +66 -14
  364. package/src/workspace/migrations/registry.ts +0 -2
  365. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +0 -91
  366. package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +0 -48
  367. package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +0 -28
  368. package/node_modules/@vellumai/service-contracts/src/channels.ts +0 -41
  369. package/src/__tests__/code-search-tool.test.ts +0 -585
  370. package/src/__tests__/guardian-expiry-notifier.test.ts +0 -282
  371. package/src/__tests__/mcp-config-secret-boundary.test.ts +0 -390
  372. package/src/__tests__/plugin-pipeline.test.ts +0 -96
  373. package/src/__tests__/sse-actor-principal-guardian-source.test.ts +0 -102
  374. package/src/__tests__/steer-on-enqueue-question.test.ts +0 -181
  375. package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +0 -208
  376. package/src/agent/loop-exclusive-tool.test.ts +0 -150
  377. package/src/api/constants/sse-replay.ts +0 -41
  378. package/src/api/events/conversation-notice.ts +0 -26
  379. package/src/approvals/guardian-channel-delivery.ts +0 -30
  380. package/src/approvals/guardian-expiry-notifier.ts +0 -148
  381. package/src/cli/commands/memory/__tests__/worker.test.ts +0 -302
  382. package/src/cli/commands/memory/worker.ts +0 -175
  383. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +0 -143
  384. package/src/config/prune-seeded-callsite-defaults.ts +0 -110
  385. package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +0 -129
  386. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +0 -312
  387. package/src/contacts/__tests__/member-write-relay.test.ts +0 -202
  388. package/src/contacts/guardian-delivery-reader.ts +0 -223
  389. package/src/contacts/member-write-relay.ts +0 -189
  390. package/src/daemon/conversation-notices.ts +0 -60
  391. package/src/daemon/handlers/__tests__/config-channels.test.ts +0 -225
  392. package/src/hooks/hook-loader.ts +0 -341
  393. package/src/mcp/mcp-header-store.ts +0 -134
  394. package/src/memory/__tests__/301-create-watchdog-events.test.ts +0 -110
  395. package/src/memory/__tests__/prompt-override.test.ts +0 -192
  396. package/src/memory/__tests__/watchdog-events-store.test.ts +0 -161
  397. package/src/memory/migrations/300-add-processing-started-at.ts +0 -30
  398. package/src/memory/migrations/301-create-watchdog-events.ts +0 -45
  399. package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +0 -224
  400. package/src/memory/prompt-override.ts +0 -129
  401. package/src/memory/steps.ts +0 -573
  402. package/src/memory/watchdog-events-store.ts +0 -87
  403. package/src/memory/worker-control.ts +0 -118
  404. package/src/memory/worker-process.ts +0 -72
  405. package/src/notifications/__tests__/connected-channels.test.ts +0 -114
  406. package/src/notifications/__tests__/destination-resolver.test.ts +0 -256
  407. package/src/onboarding/checkin-event.test.ts +0 -222
  408. package/src/onboarding/checkin-event.ts +0 -321
  409. package/src/onboarding/schedule-checkin.ts +0 -190
  410. package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +0 -106
  411. package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +0 -60
  412. package/src/plugins/defaults/advisor/context-pack.ts +0 -288
  413. package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +0 -146
  414. package/src/plugins/surface-import.ts +0 -121
  415. package/src/providers/openai/__tests__/api-error-normalization.test.ts +0 -321
  416. package/src/providers/openai/api-error-normalization.ts +0 -270
  417. package/src/runtime/__tests__/channel-verification-service.test.ts +0 -133
  418. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +0 -181
  419. package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +0 -66
  420. package/src/runtime/__tests__/local-principal-trust.test.ts +0 -164
  421. package/src/runtime/anchored-guardian.test.ts +0 -156
  422. package/src/runtime/anchored-guardian.ts +0 -135
  423. package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +0 -99
  424. package/src/runtime/local-principal-trust.ts +0 -52
  425. package/src/runtime/routes/__tests__/contact-routes.test.ts +0 -212
  426. package/src/runtime/routes/__tests__/global-search-routes.test.ts +0 -93
  427. package/src/runtime/routes/onboarding-checkin-routes.ts +0 -86
  428. package/src/tools/filesystem/search.ts +0 -543
  429. package/src/util/telemetry-db-path.ts +0 -24
  430. package/src/workspace/migrations/111-prune-seeded-callsite-defaults.ts +0 -134
@@ -12,11 +12,8 @@ import type {
12
12
 
13
13
  // ─── Mocks ──────────────────────────────────────────────────────────────────
14
14
 
15
- // Control doesSupportVision from the test: by profile key for the
16
- // user-prompt-submit path (ModelProfileInfo) and by model id for the
17
- // post-tool-use path (bare string).
15
+ // Control doesSupportVision per-profile from the test.
18
16
  let visionProfiles: Set<string>;
19
- let visionModels: Set<string>;
20
17
  let mockProfiles: ModelProfileInfo[];
21
18
  let sendMessageResponse = {
22
19
  content: [{ type: "text", text: "A red chart showing Q3 revenue." }],
@@ -33,10 +30,8 @@ const fakeProvider = {
33
30
  // Mock @vellumai/plugin-api — only the runtime handles the plugin imports.
34
31
  // `extractAllText` stays real (imported from the relative path, not plugin-api).
35
32
  mock.module("@vellumai/plugin-api", () => ({
36
- doesSupportVision: (arg: ModelProfileInfo | string) =>
37
- typeof arg === "string"
38
- ? visionModels.has(arg)
39
- : visionProfiles.has(arg.key),
33
+ doesSupportVision: (profile: ModelProfileInfo) =>
34
+ visionProfiles.has(profile.key),
40
35
  getModelProfiles: () => mockProfiles,
41
36
  getConfiguredProvider: async () => (providerResolves ? fakeProvider : null),
42
37
  }));
@@ -141,9 +136,6 @@ function makeToolCtx(
141
136
 
142
137
  beforeEach(() => {
143
138
  visionProfiles = new Set<string>(["vision-profile"]);
144
- // "text-only-model" (the default post-tool-use ctx.model) is absent, so it
145
- // reads as text-only; a vision model id is added per-test.
146
- visionModels = new Set<string>();
147
139
  mockProfiles = [
148
140
  profile("text-only", { label: "Text Only", isActive: true }),
149
141
  profile("vision-profile", { label: "Vision" }),
@@ -263,10 +255,7 @@ describe("image-fallback user-prompt-submit hook", () => {
263
255
  };
264
256
  // Override the mock to track calls.
265
257
  mock.module("@vellumai/plugin-api", () => ({
266
- doesSupportVision: (arg: ModelProfileInfo | string) =>
267
- typeof arg === "string"
268
- ? visionModels.has(arg)
269
- : visionProfiles.has(arg.key),
258
+ doesSupportVision: (p: ModelProfileInfo) => visionProfiles.has(p.key),
270
259
  getModelProfiles: () => mockProfiles,
271
260
  getConfiguredProvider: async () => trackingProvider,
272
261
  }));
@@ -284,10 +273,7 @@ describe("image-fallback user-prompt-submit hook", () => {
284
273
 
285
274
  // Restore the original mock for other tests.
286
275
  mock.module("@vellumai/plugin-api", () => ({
287
- doesSupportVision: (arg: ModelProfileInfo | string) =>
288
- typeof arg === "string"
289
- ? visionModels.has(arg)
290
- : visionProfiles.has(arg.key),
276
+ doesSupportVision: (p: ModelProfileInfo) => visionProfiles.has(p.key),
291
277
  getModelProfiles: () => mockProfiles,
292
278
  getConfiguredProvider: async () =>
293
279
  providerResolves ? fakeProvider : null,
@@ -360,10 +346,9 @@ describe("image-fallback post-tool-use hook", () => {
360
346
  );
361
347
  });
362
348
 
363
- test("is a no-op when the model that ran supports vision", async () => {
364
- visionModels = new Set(["vision-model"]);
349
+ test("is a no-op when the active model supports vision", async () => {
350
+ visionProfiles = new Set(["text-only"]); // active profile supports vision
365
351
  const ctx = makeToolCtx({
366
- model: "vision-model",
367
352
  toolResponse: toolResult([imageBlock("shot1")]),
368
353
  });
369
354
  await postToolUse(ctx);
@@ -413,29 +398,4 @@ describe("image-fallback post-tool-use hook", () => {
413
398
  const text = (ctx.toolResponse.contentBlocks![0] as { text: string }).text;
414
399
  expect(text).not.toContain("saved to");
415
400
  });
416
-
417
- test("is a no-op when contentBlocks carry no image", async () => {
418
- const textBlock = { type: "text" as const, text: "just text" };
419
- const ctx = makeToolCtx({ toolResponse: toolResult([textBlock]) });
420
- await postToolUse(ctx);
421
- expect(ctx.toolResponse.contentBlocks![0]).toEqual(textBlock);
422
- });
423
-
424
- test("gates on ctx.model, not the workspace active profile", async () => {
425
- // The active profile is vision-capable, but the model that actually ran
426
- // (ctx.model) is text-only — the model that ran must win, so the image is
427
- // captioned.
428
- mockProfiles = [
429
- profile("vision-active", { isActive: true }),
430
- profile("vision-profile", {}),
431
- ];
432
- visionProfiles = new Set(["vision-active", "vision-profile"]);
433
- visionModels = new Set<string>(); // "text-only-model" is text-only
434
- const ctx = makeToolCtx({
435
- model: "text-only-model",
436
- toolResponse: toolResult([imageBlock("shot1")]),
437
- });
438
- await postToolUse(ctx);
439
- expect(ctx.toolResponse.contentBlocks![0].type).toBe("text");
440
- });
441
401
  });
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Default `post-tool-use` hook: when the turn's model is text-only, captions
2
+ * Default `post-tool-use` hook: when the active model is text-only, captions
3
3
  * the image blocks a tool returns (e.g. a `browser_screenshot`) and
4
4
  * substitutes the caption as a text block so the result stays sendable to a
5
5
  * provider that would otherwise reject the raw image.
@@ -9,14 +9,15 @@
9
9
  * rather than the top-level message content the `user-prompt-submit` hook
10
10
  * handles. Both share {@link captionImageBlocks}.
11
11
  *
12
- * Capability is read straight off `ctx.model` — the provider-reported model id
13
- * for the turn that issued this tool call — so the decision tracks the model
14
- * that actually ran, including a text-only override. The substitution is in
15
- * place, so the persisted/displayed tool result carries the caption too.
12
+ * The active model is resolved from the workspace's active profile — the
13
+ * post-tool-use context carries the running model, and the active profile is
14
+ * what the loop is executing this turn. If that profile supports vision, the
15
+ * hook is a no-op and the image reaches the model untouched.
16
16
  */
17
17
 
18
18
  import {
19
19
  doesSupportVision,
20
+ getModelProfiles,
20
21
  type PluginHookFn,
21
22
  type PostToolUseContext,
22
23
  } from "@vellumai/plugin-api";
@@ -25,13 +26,13 @@ import { captionImageBlocks } from "../src/caption-blocks.js";
25
26
  import { findVisionProfile } from "../src/vision-caption.js";
26
27
 
27
28
  const postToolUse: PluginHookFn<PostToolUseContext> = async (ctx) => {
28
- // Cheapest gate first: bail unless the tool actually returned an image,
29
- // before touching the model catalog or resolving a vision profile.
30
29
  const blocks = ctx.toolResponse.contentBlocks;
31
- if (blocks == null || !blocks.some((b) => b.type === "image")) return;
30
+ if (blocks == null || blocks.length === 0) return;
32
31
 
33
- // If the model that ran already supports vision, leave the image in place.
34
- if (doesSupportVision(ctx.model)) return;
32
+ // If the active model already supports vision, leave the image in place.
33
+ const activeProfile = getModelProfiles().find((p) => p.isActive);
34
+ if (activeProfile == null) return;
35
+ if (doesSupportVision(activeProfile)) return;
35
36
 
36
37
  // Find a vision-capable profile for captioning.
37
38
  const visionProfileKey = findVisionProfile();
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Default `user-prompt-submit` hook: when the turn's model is text-only,
2
+ * Default `user-prompt-submit` hook: when the active model is text-only,
3
3
  * captions image blocks via a vision-capable profile and substitutes the
4
4
  * caption as a text block so the model can still reason about the image's
5
5
  * content.
@@ -7,14 +7,13 @@
7
7
  * The hook runs once per user turn, after the assistant assembles
8
8
  * `latestMessages` and before they flow into `agentLoop.run()`. It:
9
9
  *
10
- * 1. Checks whether the turn's model needs image→text fallback via
11
- * {@link needsImageFallback} (resolving the turn's `modelProfileKey`, or the
12
- * workspace's active profile when the key is `null`). If the model handles
13
- * images, the hook is a no-op.
10
+ * 1. Resolves the active profile from `modelProfileKey` (or the workspace's
11
+ * active profile when the key is `null`) and checks `doesSupportVision`.
12
+ * If the model already handles images, the hook is a no-op.
14
13
  * 2. Finds a vision-capable profile for captioning via `findVisionProfile`.
15
14
  * If none exists, images are replaced with a fail-open placeholder so the
16
15
  * model at least knows an image was present.
17
- * 3. Replaces each image block with a `[Image …]` text caption via
16
+ * 3. Replaces each `ImageContent` block with a `[Image …]` text caption via
18
17
  * {@link captionImageBlocks} (which also persists the original and caches
19
18
  * captions across turns).
20
19
  *
@@ -23,19 +22,28 @@
23
22
  */
24
23
 
25
24
  import {
25
+ doesSupportVision,
26
+ getModelProfiles,
26
27
  type PluginHookFn,
27
28
  type UserPromptSubmitContext,
28
29
  } from "@vellumai/plugin-api";
29
30
 
30
- import {
31
- captionImageBlocks,
32
- needsImageFallback,
33
- } from "../src/caption-blocks.js";
31
+ import { captionImageBlocks } from "../src/caption-blocks.js";
34
32
  import { findVisionProfile } from "../src/vision-caption.js";
35
33
 
36
34
  const userPromptSubmit: PluginHookFn<UserPromptSubmitContext> = async (ctx) => {
37
- // If the turn's model already supports vision, nothing to do.
38
- if (!needsImageFallback(ctx.modelProfileKey)) return;
35
+ // Resolve the active profile from modelProfileKey, falling back to the
36
+ // workspace's active profile when the key is null (profile unchanged since
37
+ // the last notified turn).
38
+ const profiles = getModelProfiles();
39
+ const activeProfile =
40
+ ctx.modelProfileKey != null
41
+ ? profiles.find((p) => p.key === ctx.modelProfileKey)
42
+ : profiles.find((p) => p.isActive);
43
+ if (activeProfile == null) return;
44
+
45
+ // If the active model already supports vision, nothing to do.
46
+ if (doesSupportVision(activeProfile)) return;
39
47
 
40
48
  // Find a vision-capable profile for captioning.
41
49
  const visionProfileKey = findVisionProfile();
@@ -1,62 +1,31 @@
1
1
  /**
2
2
  * Shared image→text substitution for the image-fallback plugin's hooks.
3
3
  *
4
- * Two hooks replace `image` content blocks with a text caption when the turn's
4
+ * Two hooks replace `image` content blocks with a text caption when the active
5
5
  * model can't process images: `user-prompt-submit` handles user-attached
6
6
  * images, and `post-tool-use` handles images a tool returns (e.g. a browser
7
- * screenshot). This module holds what they share — deciding whether a profile
8
- * needs the fallback ({@link needsImageFallback}) and the per-block
9
- * substitution ({@link captionImageBlocks}): persist the original image to a
10
- * known location, caption it via a vision-capable profile, and swap in a
11
- * `[Image …]` text block.
7
+ * screenshot). This module holds the per-block substitution they share —
8
+ * persist the original image to a known location, caption it via a
9
+ * vision-capable profile, and swap in a `[Image …]` text block.
12
10
  *
13
- * The substitution mutates the blocks in place, so the caption replaces the
14
- * image everywhere the block is referenced (the provider-bound history and the
15
- * persisted/displayed copy alike) a text-only turn does not keep the raw
16
- * image around.
17
- *
18
- * The caption text states up front that the model can't view images and the
19
- * image was auto-described to text, so the model treats the block as a derived
20
- * description rather than a verbatim transcript.
11
+ * The caption text states up front that the active model can't view images and
12
+ * the image was auto-described to text, so the model treats the block as a
13
+ * derived description rather than a verbatim transcript.
21
14
  *
22
15
  * Fail-open is the dominant error mode: a captioning failure leaves a
23
16
  * placeholder text block rather than the raw image (which a text-only provider
24
17
  * would reject) or nothing (which would lose information).
25
18
  */
26
19
 
27
- import {
28
- type ContentBlock,
29
- doesSupportVision,
30
- getModelProfiles,
31
- type ImageContent,
32
- type PluginLogger,
20
+ import type {
21
+ ContentBlock,
22
+ ImageContent,
23
+ PluginLogger,
33
24
  } from "@vellumai/plugin-api";
34
25
 
35
26
  import { persistImage } from "./image-persist.js";
36
27
  import { captionImage } from "./vision-caption.js";
37
28
 
38
- /**
39
- * Whether the profile a turn runs needs image→text fallback (i.e. it can't
40
- * process images itself).
41
- *
42
- * Used by `user-prompt-submit`, whose context carries the profile key rather
43
- * than the resolved model id: prefer the turn's `modelProfileKey` — which
44
- * carries a text-only override even when the workspace's active profile is
45
- * vision-capable — and fall back to the active profile only when the key is
46
- * `null` (profile unchanged since the last notified turn). Returns `false` when
47
- * no profile resolves or the resolved model already supports vision, in which
48
- * case the image reaches the model untouched.
49
- */
50
- export function needsImageFallback(modelProfileKey: string | null): boolean {
51
- const profiles = getModelProfiles();
52
- const activeProfile =
53
- modelProfileKey != null
54
- ? profiles.find((p) => p.key === modelProfileKey)
55
- : profiles.find((p) => p.isActive);
56
- if (activeProfile == null) return false;
57
- return !doesSupportVision(activeProfile);
58
- }
59
-
60
29
  /**
61
30
  * Replace every `image` block in `blocks` (in place) with a text caption so a
62
31
  * text-only model can still reason about the image's content. Returns the
@@ -67,17 +67,13 @@ let pruneConfig: {
67
67
  maxResidentBytes: number;
68
68
  targetResidentBytes: number;
69
69
  } | null = null;
70
- /** Canned orchestrate result per turnIndex; `null` simulates an ordinary miss. */
71
- let turnResults = new Map<number, OrchestrateResult | null | Error>();
70
+ /** Canned orchestrate result per turnIndex; `null` simulates a failed turn. */
71
+ let turnResults = new Map<number, OrchestrateResult | null>();
72
72
  const observeTurnSpy = mock(
73
73
  async (
74
74
  _conversationId: string,
75
75
  turnIndex: number,
76
- ): Promise<OrchestrateResult | null> => {
77
- const value = turnResults.get(turnIndex) ?? null;
78
- if (value instanceof Error) throw value;
79
- return value;
80
- },
76
+ ): Promise<OrchestrateResult | null> => turnResults.get(turnIndex) ?? null,
81
77
  );
82
78
 
83
79
  const logCalls: Array<{ data: unknown; msg: string }> = [];
@@ -203,9 +199,6 @@ const {
203
199
  } = await import("../ever-injected-store.js");
204
200
  const { V3_CARDS_INJECTION_HEADER } = await import("../render-injection.js");
205
201
  const { flushPruneValveForTests } = await import("../prune.js");
206
- const { drainConversationNotices, resetConversationNoticesForTests } =
207
- await import("../../../../daemon/conversation-notices.js");
208
- const { MemoryV3RetrievalUnavailableError } = await import("../pool-select.js");
209
202
 
210
203
  // ─── helpers ────────────────────────────────────────────────────────────────
211
204
 
@@ -320,7 +313,6 @@ beforeEach(async () => {
320
313
  logCalls.length = 0;
321
314
  testDb = makeDb();
322
315
  resetMemoryV3InjectorStateForTests();
323
- resetConversationNoticesForTests();
324
316
  });
325
317
 
326
318
  afterAll(async () => {
@@ -343,28 +335,6 @@ describe("memoryV3Injector — frozen net-new cards", () => {
343
335
  expect(getActiveSlugs("conv-1")).toEqual(new Set());
344
336
  });
345
337
 
346
- test("live retrieval failure queues a degraded-memory notice", async () => {
347
- liveEnabled = true;
348
- turnResults.set(
349
- 0,
350
- new MemoryV3RetrievalUnavailableError("selector unavailable"),
351
- );
352
-
353
- await expect(produceCardsWithoutCommit("conv-1", 0)).resolves.toBeNull();
354
-
355
- expect(drainConversationNotices("conv-1")).toEqual([
356
- {
357
- type: "conversation_notice",
358
- conversationId: "conv-1",
359
- source: "memory_v3",
360
- code: "UNKNOWN",
361
- userMessage:
362
- "Memory is temporarily unavailable, so this response may not use your saved memories. You can retry in a moment.",
363
- errorCategory: "memory_v3_degraded",
364
- },
365
- ]);
366
- });
367
-
368
338
  test("turn 1 renders cards; turn 2 re-selecting the same pages renders ZERO new cards", async () => {
369
339
  liveEnabled = true;
370
340
  turnResults.set(0, result(["page-a", "page-b"]));
@@ -26,7 +26,6 @@
26
26
  * The provider is stubbed so no network calls fire; mirrors selector.test.ts.
27
27
  */
28
28
 
29
- import { createRequire } from "node:module";
30
29
  import { beforeEach, describe, expect, mock, test } from "bun:test";
31
30
 
32
31
  import type {
@@ -36,7 +35,6 @@ import type {
36
35
  SendMessageOptions,
37
36
  ToolUseContent,
38
37
  } from "../../../../providers/types.js";
39
- import { ProviderError } from "../../../../util/errors.js";
40
38
  import type { MemoryRoutingTurn } from "../types.js";
41
39
 
42
40
  // ---------------------------------------------------------------------------
@@ -45,11 +43,6 @@ import type { MemoryRoutingTurn } from "../types.js";
45
43
  // ---------------------------------------------------------------------------
46
44
 
47
45
  let providerStub: Provider | null = null;
48
- const registryReal = {
49
- ...(createRequire(import.meta.url)(
50
- "../../../../providers/registry.js",
51
- ) as Record<string, unknown>),
52
- };
53
46
 
54
47
  interface ProviderCall {
55
48
  messages: Message[];
@@ -64,12 +57,6 @@ mock.module("../../../../providers/provider-send-message.js", () => ({
64
57
  response.content.find((b): b is ToolUseContent => b.type === "tool_use"),
65
58
  }));
66
59
 
67
- mock.module("../../../../providers/registry.js", () => ({
68
- ...registryReal,
69
- getProviderRoutingSource: (providerName: string) =>
70
- providerName === "managed" ? "managed-proxy" : "user-key",
71
- }));
72
-
73
60
  mock.module("../../../../util/logger.js", () => ({
74
61
  getLogger: () => ({
75
62
  warn: (...args: unknown[]) => warnCalls.push({ args }),
@@ -266,9 +253,10 @@ describe("selectPool — id mapping", () => {
266
253
  });
267
254
 
268
255
  // ---------------------------------------------------------------------------
269
- // selectPool — infrastructure failures THROW. A deliberate empty selection and
270
- // an empty pool (covered above) still return normally; only a genuine infra
271
- // failure throws so callers can log it distinctly from an empty selection.
256
+ // selectPool — infrastructure failures THROW (no silent degradation). A
257
+ // deliberate empty selection and an empty pool (covered above) still return
258
+ // normally; only a genuine infra failure throws so the LIVE injector can
259
+ // hard-fail the turn instead of shipping it with no memory.
272
260
  // ---------------------------------------------------------------------------
273
261
 
274
262
  describe("selectPool — infrastructure failures throw", () => {
@@ -383,38 +371,6 @@ describe("selectPool — infrastructure failures throw", () => {
383
371
  ]);
384
372
  });
385
373
 
386
- test("managed provider 402 attaches a non-terminal credits notice", async () => {
387
- providerStub = {
388
- name: "managed",
389
- sendMessage: async (messages, options) => {
390
- providerCalls.push({ messages, options });
391
- throw new ProviderError(
392
- "Together AI API error (402): 402 status code (no body)",
393
- "managed",
394
- 402,
395
- );
396
- },
397
- };
398
- let caught: unknown;
399
- try {
400
- await selectPool(makePool(), makeTurn("x"));
401
- } catch (err) {
402
- caught = err;
403
- }
404
-
405
- expect(caught).toBeInstanceOf(MemoryV3RetrievalUnavailableError);
406
- const notice = (
407
- caught as InstanceType<typeof MemoryV3RetrievalUnavailableError>
408
- ).conversationNotice;
409
- expect(notice).toEqual({
410
- source: "memory_v3",
411
- code: "PROVIDER_BILLING",
412
- userMessage:
413
- "You've run out of credits. Add funds to continue using the assistant.",
414
- errorCategory: "credits_exhausted",
415
- });
416
- });
417
-
418
374
  test("provider throw redacts sensitive message details in diagnostics", async () => {
419
375
  const providerSecret = ["sk-proj-", "a".repeat(40)].join("");
420
376
  const message = `provider rejected Authorization: Bearer ${providerSecret}`;
@@ -808,7 +808,7 @@ describe("memory-v3 shadow plugin", () => {
808
808
  });
809
809
  });
810
810
 
811
- describe("memory-v3 infrastructure-failure handling", () => {
811
+ describe("memory-v3 infrastructure-failure handling (hard-fail vs swallow)", () => {
812
812
  const throwInfra = () =>
813
813
  orchestrateSpy.mockImplementationOnce(async () => {
814
814
  throw new MemoryV3RetrievalUnavailableError(
@@ -816,12 +816,14 @@ describe("memory-v3 infrastructure-failure handling", () => {
816
816
  );
817
817
  });
818
818
 
819
- test("LIVE injector logs and degrades to no v3 block on an infra failure", async () => {
819
+ test("LIVE injector HARD-FAILS the turn on an infra failure (no silent memory loss)", async () => {
820
820
  liveEnabled = true;
821
821
  shadowEnabled = false;
822
822
  throwInfra();
823
823
 
824
- expect(await produce("conv-infra-live", 0)).toBeNull();
824
+ await expect(produce("conv-infra-live", 0)).rejects.toThrow(
825
+ MemoryV3RetrievalUnavailableError,
826
+ );
825
827
  });
826
828
 
827
829
  test("SHADOW injector swallows an infra failure (v2 fallback) — no throw, no block", async () => {
@@ -830,7 +832,7 @@ describe("memory-v3 infrastructure-failure handling", () => {
830
832
  throwInfra();
831
833
 
832
834
  // Shadow mode: v2 retrieval still ran this turn, so the v3 injector returns
833
- // null.
835
+ // null rather than failing the turn.
834
836
  expect(await produce("conv-infra-shadow", 0)).toBeNull();
835
837
  });
836
838
 
@@ -851,6 +853,8 @@ describe("memory-v3 infrastructure-failure handling", () => {
851
853
  throw new Error("some unexpected non-infra bug");
852
854
  });
853
855
 
856
+ // Only INFRA failures hard-fail; any other error stays non-fatal so a bug
857
+ // in one lane can't take every turn down.
854
858
  expect(await produce("conv-nonfatal-live", 0)).toBeNull();
855
859
  });
856
860
  });
@@ -63,10 +63,6 @@
63
63
  import { isAssistantFeatureFlagEnabled } from "../../../config/assistant-feature-flags.js";
64
64
  import { getConfig } from "../../../config/loader.js";
65
65
  import { isMemoryV3Live } from "../../../config/memory-v3-gate.js";
66
- import {
67
- type PendingConversationNotice,
68
- queueConversationNotice,
69
- } from "../../../daemon/conversation-notices.js";
70
66
  import { isPersonalMemoryAllowed } from "../../../daemon/trust-context.js";
71
67
  import {
72
68
  wrapMemoryBlock,
@@ -124,26 +120,6 @@ function lruSet<V>(map: Map<string, V>, key: string, value: V): void {
124
120
  map.set(key, value);
125
121
  }
126
122
 
127
- function queueMemoryV3ConversationNotice(
128
- err: MemoryV3RetrievalUnavailableError,
129
- ctx: TurnContext,
130
- live: boolean,
131
- ): void {
132
- if (!live) return;
133
- const notice: PendingConversationNotice = err.conversationNotice ?? {
134
- source: "memory_v3",
135
- code: "UNKNOWN",
136
- userMessage:
137
- "Memory is temporarily unavailable, so this response may not use your saved memories. You can retry in a moment.",
138
- errorCategory: "memory_v3_degraded",
139
- };
140
- queueConversationNotice(
141
- ctx.conversationId,
142
- `memory_v3:${ctx.turnIndex}:${notice.errorCategory ?? notice.code}`,
143
- notice,
144
- );
145
- }
146
-
147
123
  // ─── shared per-turn orchestration memo ─────────────────────────────────────
148
124
 
149
125
  interface ObservedTurn {
@@ -269,16 +245,16 @@ export const memoryV3Injector: Injector = {
269
245
  try {
270
246
  observed = await observeTurnOnce(ctx.conversationId, ctx.turnIndex);
271
247
  } catch (err) {
272
- if (err instanceof MemoryV3RetrievalUnavailableError) {
273
- queueMemoryV3ConversationNotice(err, ctx, live);
274
- log.error(
275
- {
276
- err: err.message,
277
- conversationId: ctx.conversationId,
278
- mode: live ? "live" : "shadow",
279
- },
280
- "memory-v3 selection failed; skipping v3 memory for this turn",
281
- );
248
+ // A memory-v3 INFRASTRUCTURE failure (the selector lost its provider —
249
+ // e.g. a transient CES credential blip). Under `memory-v3-live` the
250
+ // user-prompt-submit hook already skipped v2 retrieval, so swallowing
251
+ // here would ship the turn with NO memory at all — exactly the silent
252
+ // degradation we want to eliminate. Hard-fail the turn instead (a clean,
253
+ // retryable error). In shadow mode v2 still ran this turn, so fall back
254
+ // to it (return null). Non-infra errors are already swallowed inside
255
+ // observeTurn; anything else reaching here stays non-fatal.
256
+ if (live && err instanceof MemoryV3RetrievalUnavailableError) {
257
+ throw err;
282
258
  }
283
259
  return null;
284
260
  }
@@ -429,16 +405,12 @@ export const memoryV3SpotlightInjector: Injector = {
429
405
  placement: "after-memory-prefix",
430
406
  };
431
407
  } catch (err) {
408
+ // Live-only injector: an infra failure must hard-fail the turn. The cards
409
+ // injector (ordered ahead of this one) normally throws first, so this
410
+ // path is defensive — it keeps the behavior correct if the cards injector
411
+ // is ever disabled or reordered.
432
412
  if (err instanceof MemoryV3RetrievalUnavailableError) {
433
- queueMemoryV3ConversationNotice(err, ctx, true);
434
- log.error(
435
- {
436
- err: err.message,
437
- conversationId: ctx.conversationId,
438
- },
439
- "memory-v3 spotlight selection failed; skipping spotlight",
440
- );
441
- return null;
413
+ throw err;
442
414
  }
443
415
  log.warn(
444
416
  {
@@ -119,10 +119,6 @@ export interface OrchestrateDeps {
119
119
  /** Hard cap on total learned-lane surfaced articles; `0` disables the pass
120
120
  * (canonical value: `memory.v3.learnedEdges.cap`). */
121
121
  learnedCap?: number;
122
- /** The selector's system prompt. Omitted → the selector's bundled default.
123
- * The live caller resolves `memory.v3.selectorPromptPath` (workspace-relative
124
- * file override) via `resolveSelectorPrompt` and threads the result here. */
125
- selectorPrompt?: string;
126
122
  }
127
123
 
128
124
  /** A finder-lane candidate: the slug, the descriptor that justified it, and
@@ -382,13 +378,8 @@ export async function orchestrate(
382
378
 
383
379
  // Step 3: a SINGLE forced-tool select over the cache-ordered pool. The
384
380
  // selections come back slug-deduped (pinned flags ORed) — `selectPool`'s
385
- // contract. `selectorPrompt` is the (optionally overridden) instruction
386
- // scaffold; `undefined` falls through to the bundled default.
387
- const selections = await selectPool(
388
- { stable, finder: finderTail },
389
- turn,
390
- deps.selectorPrompt,
391
- );
381
+ // contract.
382
+ const selections = await selectPool({ stable, finder: finderTail }, turn);
392
383
 
393
384
  return {
394
385
  selections,