@vellumai/assistant 0.10.2 → 0.10.3-dev.202606252046.9075fd5

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 (546) hide show
  1. package/bun.lock +20 -0
  2. package/docs/workspace-tools.md +42 -33
  3. package/eslint-rules/cli-no-daemon-internals.js +6 -0
  4. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +91 -0
  5. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +31 -0
  6. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +44 -0
  7. package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +48 -0
  8. package/node_modules/@vellumai/gateway-client/src/index.ts +14 -0
  9. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +17 -0
  10. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  11. package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +28 -0
  12. package/node_modules/@vellumai/service-contracts/src/channels.ts +41 -0
  13. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  14. package/openapi.yaml +196 -56
  15. package/package.json +4 -1
  16. package/scripts/test.sh +36 -15
  17. package/src/__tests__/actor-token-service.test.ts +36 -14
  18. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +83 -31
  19. package/src/__tests__/agent-loop-override-profile.test.ts +1 -0
  20. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  21. package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
  22. package/src/__tests__/annotate-activity-metadata.test.ts +2 -0
  23. package/src/__tests__/annotate-risk-options.test.ts +2 -0
  24. package/src/__tests__/approval-cascade.test.ts +2 -0
  25. package/src/__tests__/assistant-attachments.test.ts +42 -0
  26. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -0
  27. package/src/__tests__/btw-routes.test.ts +2 -0
  28. package/src/__tests__/build-persisted-content.test.ts +2 -0
  29. package/src/__tests__/call-controller.test.ts +19 -0
  30. package/src/__tests__/channel-approval-routes.test.ts +21 -26
  31. package/src/__tests__/channel-delivery-store.test.ts +28 -0
  32. package/src/__tests__/channel-guardian.test.ts +164 -78
  33. package/src/__tests__/channel-inbound-disk-pressure.test.ts +11 -19
  34. package/src/__tests__/channel-reply-delivery.test.ts +2 -0
  35. package/src/__tests__/code-search-tool.test.ts +585 -0
  36. package/src/__tests__/compaction-events.test.ts +2 -0
  37. package/src/__tests__/compaction-ledger-store.test.ts +128 -0
  38. package/src/__tests__/compaction.benchmark.test.ts +2 -0
  39. package/src/__tests__/compactor-call-site-logging.test.ts +2 -0
  40. package/src/__tests__/compactor-low-watermark-cut.test.ts +2 -0
  41. package/src/__tests__/compactor-preserved-tail-count.test.ts +2 -0
  42. package/src/__tests__/compactor-summary-call-truncation.test.ts +2 -0
  43. package/src/__tests__/compactor-web-search-strip.test.ts +2 -0
  44. package/src/__tests__/config-loader-backfill.test.ts +123 -10
  45. package/src/__tests__/config-schema.test.ts +1 -0
  46. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +31 -29
  47. package/src/__tests__/consult-deadline.test.ts +60 -0
  48. package/src/__tests__/contact-store-interaction-info.test.ts +156 -0
  49. package/src/__tests__/contact-store-user-file.test.ts +7 -10
  50. package/src/__tests__/contacts-relay-reads.test.ts +19 -24
  51. package/src/__tests__/contacts-write.test.ts +0 -2
  52. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  53. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +2 -0
  54. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
  55. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
  56. package/src/__tests__/conversation-agent-loop.test.ts +134 -0
  57. package/src/__tests__/conversation-analysis-routes.test.ts +2 -0
  58. package/src/__tests__/conversation-app-control-lifecycle.test.ts +2 -0
  59. package/src/__tests__/conversation-attention-telegram.test.ts +9 -11
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -0
  61. package/src/__tests__/conversation-fork-crud.test.ts +354 -24
  62. package/src/__tests__/conversation-history-web-search.test.ts +2 -0
  63. package/src/__tests__/conversation-load-history-repair.test.ts +2 -0
  64. package/src/__tests__/conversation-load-history-stripped.test.ts +2 -0
  65. package/src/__tests__/conversation-pairing.test.ts +2 -0
  66. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
  67. package/src/__tests__/conversation-process-callsite.test.ts +2 -0
  68. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  69. package/src/__tests__/conversation-queue.test.ts +91 -0
  70. package/src/__tests__/conversation-routes-guardian-reply.test.ts +14 -0
  71. package/src/__tests__/conversation-routes-slash-commands.test.ts +14 -0
  72. package/src/__tests__/conversation-slash-queue.test.ts +2 -0
  73. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  74. package/src/__tests__/conversation-speed-override.test.ts +2 -0
  75. package/src/__tests__/conversation-surfaces-task-progress.test.ts +29 -0
  76. package/src/__tests__/conversation-title-service.test.ts +2 -0
  77. package/src/__tests__/conversation-tool-setup-attribution.test.ts +47 -0
  78. package/src/__tests__/conversation-usage.test.ts +2 -0
  79. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -0
  80. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  81. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  82. package/src/__tests__/credential-security-invariants.test.ts +1 -1
  83. package/src/__tests__/db-compaction-events-migration.test.ts +129 -0
  84. package/src/__tests__/db-migration-rollback.test.ts +205 -171
  85. package/src/__tests__/db-test-helpers.ts +5 -4
  86. package/src/__tests__/delete-propagation.test.ts +5 -3
  87. package/src/__tests__/deterministic-verification-control-plane.test.ts +4 -2
  88. package/src/__tests__/disk-pressure-guard.test.ts +41 -0
  89. package/src/__tests__/dm-backfill.test.ts +6 -4
  90. package/src/__tests__/dm-persistence.test.ts +2 -0
  91. package/src/__tests__/emit-signal-routing-intent.test.ts +7 -6
  92. package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
  93. package/src/__tests__/exploration-drift-hook.test.ts +3 -2
  94. package/src/__tests__/filing-service.test.ts +2 -0
  95. package/src/__tests__/guardian-binding-drift-heal.test.ts +104 -19
  96. package/src/__tests__/guardian-dispatch.test.ts +140 -1
  97. package/src/__tests__/guardian-expiry-notifier.test.ts +282 -0
  98. package/src/__tests__/guardian-outbound-http.test.ts +13 -0
  99. package/src/__tests__/guardian-routing-state.test.ts +6 -10
  100. package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
  101. package/src/__tests__/heartbeat-service.test.ts +2 -0
  102. package/src/__tests__/helpers/channel-test-adapter.ts +46 -19
  103. package/src/__tests__/helpers/create-guardian-binding.ts +15 -23
  104. package/src/__tests__/helpers/mock-logger.ts +1 -0
  105. package/src/__tests__/helpers/seed-contact-channel.ts +96 -0
  106. package/src/__tests__/host-app-control-routes.test.ts +24 -30
  107. package/src/__tests__/host-bash-routes.test.ts +31 -41
  108. package/src/__tests__/host-browser-routes.test.ts +26 -32
  109. package/src/__tests__/host-cu-routes-targeted.test.ts +25 -33
  110. package/src/__tests__/host-file-routes-targeted.test.ts +40 -52
  111. package/src/__tests__/host-transfer-routes-targeted.test.ts +31 -43
  112. package/src/__tests__/http-user-message-parity.test.ts +290 -8
  113. package/src/__tests__/inbound-invite-redemption.test.ts +115 -10
  114. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  115. package/src/__tests__/invite-redemption-service.test.ts +471 -53
  116. package/src/__tests__/invite-routes-http.test.ts +34 -0
  117. package/src/__tests__/invite-service-ipc.test.ts +65 -2
  118. package/src/__tests__/llm-context-normalization.test.ts +105 -0
  119. package/src/__tests__/llm-request-log-error-payload.test.ts +71 -9
  120. package/src/__tests__/llm-usage-store.test.ts +25 -0
  121. package/src/__tests__/mcp-config-secret-boundary.test.ts +392 -0
  122. package/src/__tests__/mcp-health-check.test.ts +2 -1
  123. package/src/__tests__/media-stream-server-integration.test.ts +127 -0
  124. package/src/__tests__/memory-retrieval-hook.test.ts +2 -0
  125. package/src/__tests__/messaging-send-tool.test.ts +2 -0
  126. package/src/__tests__/migration-import-from-url.test.ts +2 -2
  127. package/src/__tests__/mtime-cache.test.ts +275 -5
  128. package/src/__tests__/native-web-search.test.ts +2 -0
  129. package/src/__tests__/non-member-access-request.test.ts +191 -17
  130. package/src/__tests__/notification-broadcaster.test.ts +4 -0
  131. package/src/__tests__/notification-decision-recipient-context.test.ts +33 -32
  132. package/src/__tests__/notification-deep-link.test.ts +6 -0
  133. package/src/__tests__/notification-guardian-path.test.ts +19 -0
  134. package/src/__tests__/onboarding-persona-write.test.ts +52 -22
  135. package/src/__tests__/openai-provider.test.ts +22 -12
  136. package/src/__tests__/openai-responses-provider.test.ts +12 -2
  137. package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
  138. package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
  139. package/src/__tests__/persist-onboarding-artifacts.test.ts +1 -0
  140. package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
  141. package/src/__tests__/persona-resolver.test.ts +75 -45
  142. package/src/__tests__/plugin-bootstrap.test.ts +21 -83
  143. package/src/__tests__/plugin-disabled-state.test.ts +190 -0
  144. package/src/__tests__/plugin-pipeline.test.ts +96 -0
  145. package/src/__tests__/plugin-route-contribution.test.ts +6 -19
  146. package/src/__tests__/plugin-tool-contribution.test.ts +5 -20
  147. package/src/__tests__/plugin-types.test.ts +2 -4
  148. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  149. package/src/__tests__/process-message-display-content.test.ts +2 -0
  150. package/src/__tests__/provider-error-scenarios.test.ts +5 -4
  151. package/src/__tests__/provider-usage-tracking.test.ts +39 -0
  152. package/src/__tests__/reaction-intercept-cold-cache-warm.test.ts +135 -0
  153. package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +158 -0
  154. package/src/__tests__/reaction-persistence.test.ts +51 -4
  155. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +2 -0
  156. package/src/__tests__/registry.test.ts +4 -1
  157. package/src/__tests__/relay-server.test.ts +758 -32
  158. package/src/__tests__/runtime-attachment-metadata.test.ts +9 -12
  159. package/src/__tests__/secret-ingress-http.test.ts +14 -0
  160. package/src/__tests__/send-endpoint-busy.test.ts +30 -8
  161. package/src/__tests__/settings-routes.test.ts +32 -0
  162. package/src/__tests__/skills.test.ts +44 -0
  163. package/src/__tests__/slack-inbound-verification.test.ts +47 -2
  164. package/src/__tests__/sse-actor-principal-guardian-source.test.ts +79 -0
  165. package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
  166. package/src/__tests__/stt-hints.test.ts +49 -15
  167. package/src/__tests__/subagent-detail.test.ts +27 -0
  168. package/src/__tests__/subagent-disposal.test.ts +65 -0
  169. package/src/__tests__/subagent-fork-prompt-role.test.ts +195 -0
  170. package/src/__tests__/subagent-fork-spawn.test.ts +6 -7
  171. package/src/__tests__/subagent-notify-parent.test.ts +2 -0
  172. package/src/__tests__/subagent-role-registry.test.ts +24 -6
  173. package/src/__tests__/subagent-spawn-and-await.test.ts +546 -0
  174. package/src/__tests__/subagent-spawn-tool-fork.test.ts +2 -0
  175. package/src/__tests__/subagent-tools.test.ts +398 -1
  176. package/src/__tests__/suggestion-routes.test.ts +2 -0
  177. package/src/__tests__/thread-backfill.test.ts +3 -3
  178. package/src/__tests__/title-generate-hook.test.ts +2 -0
  179. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
  180. package/src/__tests__/tool-executor.test.ts +16 -11
  181. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
  182. package/src/__tests__/tool-result-metadata-plumbing.test.ts +2 -0
  183. package/src/__tests__/tool-start-timestamp.test.ts +2 -0
  184. package/src/__tests__/trusted-contact-approval-notifier.test.ts +37 -51
  185. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +12 -12
  186. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -7
  187. package/src/__tests__/trusted-contact-multichannel.test.ts +16 -7
  188. package/src/__tests__/trusted-contact-verification.test.ts +79 -54
  189. package/src/__tests__/twilio-routes.test.ts +96 -0
  190. package/src/__tests__/ui-file-upload-surface.test.ts +86 -0
  191. package/src/__tests__/verification-control-plane-policy.test.ts +2 -0
  192. package/src/__tests__/voice-guardian-cold-cache-warm.test.ts +137 -0
  193. package/src/__tests__/voice-invite-redemption.test.ts +216 -20
  194. package/src/__tests__/web-search-backend-failure.test.ts +2 -0
  195. package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +3 -3
  196. package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +208 -0
  197. package/src/__tests__/workspace-migration-112-remove-advisor-callsite-override.test.ts +170 -0
  198. package/src/__tests__/workspace-migration-drop-user-md.test.ts +196 -238
  199. package/src/__tests__/workspace-migration-remove-hooks.test.ts +14 -35
  200. package/src/__tests__/workspace-tool-loader.test.ts +195 -2
  201. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +35 -47
  202. package/src/agent/loop-exclusive-tool.test.ts +154 -0
  203. package/src/agent/loop-native-web-search.test.ts +200 -0
  204. package/src/agent/loop.ts +164 -1
  205. package/src/api/constants/sse-replay.ts +41 -0
  206. package/src/api/events/conversation-notice.ts +26 -0
  207. package/src/api/index.ts +19 -1
  208. package/src/api/responses/llm-request-log-entry.ts +29 -0
  209. package/src/api/responses/subagent-detail.ts +17 -0
  210. package/src/api/surfaces.ts +39 -3
  211. package/src/approvals/guardian-channel-delivery.ts +30 -0
  212. package/src/approvals/guardian-expiry-notifier.ts +148 -0
  213. package/src/approvals/guardian-request-resolvers.ts +17 -15
  214. package/src/calls/__tests__/relay-setup-router.test.ts +267 -17
  215. package/src/calls/call-domain.ts +3 -3
  216. package/src/calls/guardian-dispatch.ts +14 -9
  217. package/src/calls/inbound-trust-reader.ts +24 -2
  218. package/src/calls/media-stream-server.ts +21 -0
  219. package/src/calls/relay-access-wait.ts +6 -6
  220. package/src/calls/relay-server.ts +188 -51
  221. package/src/calls/relay-setup-router.ts +47 -17
  222. package/src/calls/relay-verification.ts +4 -4
  223. package/src/calls/stt-hints.ts +9 -12
  224. package/src/calls/twilio-routes.ts +14 -4
  225. package/src/channels/types.ts +10 -20
  226. package/src/cli/commands/__tests__/cache.test.ts +8 -1
  227. package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
  228. package/src/cli/commands/cache.ts +194 -181
  229. package/src/cli/commands/contacts.ts +10 -7
  230. package/src/cli/commands/db/__tests__/repair.test.ts +6 -5
  231. package/src/cli/commands/db/status.ts +37 -1
  232. package/src/cli/commands/mcp.ts +252 -218
  233. package/src/cli/commands/memory/__tests__/worker.test.ts +432 -0
  234. package/src/cli/commands/memory/index.ts +2 -0
  235. package/src/cli/commands/memory/worker.ts +242 -0
  236. package/src/cli/commands/plugins.ts +125 -13
  237. package/src/cli/lib/__tests__/install-from-github.test.ts +102 -0
  238. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +160 -1
  239. package/src/cli/lib/__tests__/publish-plugin.test.ts +404 -0
  240. package/src/cli/lib/list-installed-plugins.ts +179 -1
  241. package/src/cli/lib/publish-plugin.ts +633 -0
  242. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
  243. package/src/config/__tests__/sync-gated-profiles.test.ts +16 -10
  244. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +27 -17
  245. package/src/config/bundled-skills/contacts/tools/contact-search.ts +13 -3
  246. package/src/config/bundled-skills/subagent/SKILL.md +17 -2
  247. package/src/config/bundled-skills/subagent/TOOLS.json +5 -4
  248. package/src/config/call-site-defaults.ts +0 -6
  249. package/src/config/feature-flag-registry.json +5 -13
  250. package/src/config/llm-resolver.ts +0 -3
  251. package/src/config/loader.ts +38 -5
  252. package/src/config/prune-seeded-callsite-defaults.ts +110 -0
  253. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  254. package/src/config/schemas/call-site-catalog.ts +0 -7
  255. package/src/config/schemas/heartbeat.ts +2 -5
  256. package/src/config/schemas/llm.ts +3 -12
  257. package/src/config/schemas/memory-lifecycle.ts +12 -0
  258. package/src/config/schemas/memory-v3.ts +7 -0
  259. package/src/config/schemas/memory.ts +4 -0
  260. package/src/config/schemas/timeouts.ts +8 -0
  261. package/src/config/seed-inference-profiles.ts +30 -34
  262. package/src/config/skills.ts +27 -5
  263. package/src/config/sync-gated-profiles.ts +12 -16
  264. package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +128 -0
  265. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
  266. package/src/contacts/__tests__/member-write-relay.test.ts +226 -0
  267. package/src/contacts/contact-store.ts +27 -237
  268. package/src/contacts/contacts-write.ts +18 -55
  269. package/src/contacts/gateway-channel-read.ts +51 -0
  270. package/src/contacts/guardian-delivery-reader.ts +223 -0
  271. package/src/contacts/member-write-relay.ts +183 -0
  272. package/src/contacts/types.ts +3 -15
  273. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +35 -0
  274. package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -44
  275. package/src/daemon/assistant-attachments.ts +27 -4
  276. package/src/daemon/conversation-agent-loop.ts +28 -0
  277. package/src/daemon/conversation-notices.ts +60 -0
  278. package/src/daemon/conversation-process.ts +35 -16
  279. package/src/daemon/conversation-surfaces.ts +111 -38
  280. package/src/daemon/conversation-tool-setup.ts +71 -30
  281. package/src/daemon/conversation.ts +23 -1
  282. package/src/daemon/disk-pressure-guard.ts +12 -2
  283. package/src/daemon/event-loop-watchdog.ts +28 -1
  284. package/src/daemon/external-plugins-bootstrap.ts +17 -41
  285. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -1
  286. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -2
  287. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +25 -2
  288. package/src/daemon/handlers/__tests__/config-channels.test.ts +220 -0
  289. package/src/daemon/handlers/config-a2a.ts +6 -14
  290. package/src/daemon/handlers/config-channels.ts +67 -26
  291. package/src/daemon/handlers/conversations.ts +77 -0
  292. package/src/daemon/lifecycle.ts +16 -0
  293. package/src/daemon/mcp-reload-service.ts +10 -0
  294. package/src/daemon/memory-v2-startup.test.ts +72 -0
  295. package/src/daemon/memory-v2-startup.ts +87 -19
  296. package/src/daemon/message-types/conversations.ts +2 -0
  297. package/src/daemon/message-types/surfaces.ts +12 -12
  298. package/src/daemon/server.ts +0 -4
  299. package/src/daemon/shutdown-handlers.ts +20 -0
  300. package/src/daemon/tool-setup-types.ts +9 -0
  301. package/src/heartbeat/heartbeat-service.ts +5 -0
  302. package/src/home/relationship-state-writer.ts +5 -0
  303. package/src/hooks/hook-loader.ts +341 -0
  304. package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
  305. package/src/ipc/assistant-server.ts +2 -2
  306. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -0
  307. package/src/mcp/client.ts +15 -1
  308. package/src/mcp/mcp-auth-orchestrator.ts +6 -1
  309. package/src/mcp/mcp-header-store.ts +134 -0
  310. package/src/mcp/mcp-oauth-provider.ts +19 -8
  311. package/src/memory/__tests__/301-create-watchdog-events.test.ts +110 -0
  312. package/src/memory/__tests__/memory-retrospective-job.test.ts +8 -0
  313. package/src/memory/__tests__/prompt-override.test.ts +192 -0
  314. package/src/memory/__tests__/watchdog-events-store.test.ts +161 -0
  315. package/src/memory/compaction-ledger-store.ts +107 -0
  316. package/src/memory/conversation-crud.ts +129 -61
  317. package/src/memory/db-connection.ts +22 -3
  318. package/src/memory/db-init.ts +37 -503
  319. package/src/memory/db-singleton.ts +6 -4
  320. package/src/memory/jobs-worker.ts +104 -0
  321. package/src/memory/llm-request-log-store.ts +26 -1
  322. package/src/memory/llm-usage-store.ts +48 -20
  323. package/src/memory/memory-retrospective-job.ts +14 -8
  324. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +152 -56
  325. package/src/memory/migrations/300-add-processing-started-at.ts +30 -0
  326. package/src/memory/migrations/301-create-watchdog-events.ts +45 -0
  327. package/src/memory/migrations/302-create-compaction-events.ts +107 -0
  328. package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +297 -0
  329. package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
  330. package/src/memory/migrations/run-migrations.ts +106 -10
  331. package/src/memory/migrations/validate-migration-state.ts +105 -67
  332. package/src/memory/prompt-override.ts +129 -0
  333. package/src/memory/schema/contacts.ts +6 -2
  334. package/src/memory/schema/conversations.ts +37 -0
  335. package/src/memory/schema/infrastructure.ts +20 -0
  336. package/src/memory/steps.ts +1294 -0
  337. package/src/memory/v2/__tests__/cli-command-store.test.ts +25 -0
  338. package/src/memory/v2/__tests__/skill-store.test.ts +80 -0
  339. package/src/memory/v2/cli-command-store.ts +75 -38
  340. package/src/memory/v2/prompts/consolidation.ts +13 -82
  341. package/src/memory/v2/prompts/router.ts +21 -93
  342. package/src/memory/v2/skill-store.ts +68 -31
  343. package/src/memory/watchdog-events-store.ts +87 -0
  344. package/src/memory/worker-control.ts +204 -0
  345. package/src/memory/worker-process.ts +89 -0
  346. package/src/messaging/channel-binding-metadata.ts +31 -0
  347. package/src/messaging/provider-types.ts +8 -0
  348. package/src/messaging/providers/slack/binding-metadata.ts +60 -0
  349. package/src/notifications/__tests__/broadcaster.test.ts +8 -8
  350. package/src/notifications/__tests__/connected-channels.test.ts +86 -0
  351. package/src/notifications/__tests__/decision-engine.test.ts +78 -9
  352. package/src/notifications/__tests__/destination-resolver.test.ts +151 -0
  353. package/src/notifications/broadcaster.ts +8 -1
  354. package/src/notifications/decision-engine.ts +15 -7
  355. package/src/notifications/destination-resolver.ts +53 -25
  356. package/src/notifications/emit-signal.ts +34 -15
  357. package/src/onboarding/checkin-event.test.ts +222 -0
  358. package/src/onboarding/checkin-event.ts +321 -0
  359. package/src/onboarding/schedule-checkin.ts +190 -0
  360. package/src/permissions/question-prompter.test.ts +1 -1
  361. package/src/permissions/question-prompter.ts +7 -4
  362. package/src/plugin-api/index.ts +12 -12
  363. package/src/plugin-api/types.ts +12 -14
  364. package/src/plugin-api/vision-support.test.ts +28 -4
  365. package/src/plugin-api/vision-support.ts +66 -31
  366. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +2 -2
  367. package/src/plugins/defaults/empty-response/hooks/stop.ts +2 -2
  368. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +4 -3
  369. package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +2 -2
  370. package/src/plugins/defaults/history-repair/hooks/stop.ts +2 -2
  371. package/src/plugins/defaults/history-repair/hooks/user-prompt-submit.ts +2 -2
  372. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +47 -7
  373. package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +12 -13
  374. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +14 -22
  375. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +42 -11
  376. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +2 -2
  377. package/src/plugins/defaults/image-recovery/hooks/stop.ts +2 -2
  378. package/src/plugins/defaults/index.ts +0 -35
  379. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +2 -2
  380. package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +2 -2
  381. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +2 -2
  382. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  383. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +33 -3
  384. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +48 -4
  385. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +4 -8
  386. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +2 -2
  387. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +2 -2
  388. package/src/plugins/defaults/memory-v3-shadow/injector.ts +43 -15
  389. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +11 -2
  390. package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +146 -0
  391. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +77 -13
  392. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +12 -11
  393. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +2 -2
  394. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +2 -2
  395. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -2
  396. package/src/plugins/defaults/title-generate/hooks/stop.ts +2 -2
  397. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +2 -2
  398. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +2 -2
  399. package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +2 -2
  400. package/src/plugins/disabled-state.ts +31 -0
  401. package/src/plugins/external-plugin-loader.ts +2 -2
  402. package/src/plugins/mtime-cache.ts +186 -330
  403. package/src/plugins/pipeline.ts +111 -13
  404. package/src/plugins/registry.ts +59 -16
  405. package/src/plugins/surface-import.ts +121 -0
  406. package/src/plugins/types.ts +7 -9
  407. package/src/prompts/persona-resolver.ts +43 -11
  408. package/src/providers/anthropic/client.ts +5 -0
  409. package/src/providers/call-site-routing.ts +45 -0
  410. package/src/providers/model-catalog.ts +16 -0
  411. package/src/providers/openai/__tests__/api-error-normalization.test.ts +321 -0
  412. package/src/providers/openai/api-error-normalization.ts +270 -0
  413. package/src/providers/openai/chat-completions-provider.ts +37 -83
  414. package/src/providers/openai/responses-provider.ts +50 -46
  415. package/src/providers/openrouter/client.ts +5 -0
  416. package/src/providers/provider-send-message.ts +10 -0
  417. package/src/providers/ratelimit.ts +10 -0
  418. package/src/providers/registry.ts +1 -1
  419. package/src/providers/retry.ts +10 -0
  420. package/src/providers/types.ts +22 -0
  421. package/src/providers/usage-tracking.ts +10 -0
  422. package/src/runtime/__tests__/channel-verification-service.test.ts +133 -0
  423. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +184 -0
  424. package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +66 -0
  425. package/src/runtime/__tests__/local-principal-trust.test.ts +162 -0
  426. package/src/runtime/__tests__/member-verdict-cache.test.ts +119 -0
  427. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +402 -123
  428. package/src/runtime/access-request-helper.ts +18 -39
  429. package/src/runtime/actor-trust-resolver.ts +46 -19
  430. package/src/runtime/anchored-guardian.test.ts +109 -0
  431. package/src/runtime/anchored-guardian.ts +86 -0
  432. package/src/runtime/assistant-event-hub.ts +1 -1
  433. package/src/runtime/assistant-stream-state.ts +9 -2
  434. package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +99 -0
  435. package/src/runtime/auth/require-bound-guardian.ts +21 -11
  436. package/src/runtime/channel-verification-service.ts +56 -31
  437. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
  438. package/src/runtime/guardian-vellum-migration.ts +69 -8
  439. package/src/runtime/invite-redemption-service.ts +213 -187
  440. package/src/runtime/local-actor-identity.test.ts +108 -0
  441. package/src/runtime/local-actor-identity.ts +85 -13
  442. package/src/runtime/local-principal-trust.ts +52 -0
  443. package/src/runtime/member-verdict-cache.ts +0 -0
  444. package/src/runtime/pending-interactions.ts +11 -1
  445. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +56 -5
  446. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  447. package/src/runtime/routes/__tests__/contact-routes.test.ts +305 -0
  448. package/src/runtime/routes/__tests__/global-search-routes.test.ts +92 -0
  449. package/src/runtime/routes/__tests__/http-adapter-actor-header.test.ts +129 -0
  450. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +188 -0
  451. package/src/runtime/routes/browser-routes.ts +1 -1
  452. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +13 -5
  453. package/src/runtime/routes/channel-verification-routes.ts +3 -3
  454. package/src/runtime/routes/contact-routes.ts +48 -57
  455. package/src/runtime/routes/conversation-cli-routes.ts +4 -5
  456. package/src/runtime/routes/conversation-list-routes.ts +4 -7
  457. package/src/runtime/routes/conversation-query-routes.ts +72 -0
  458. package/src/runtime/routes/conversation-routes.ts +84 -85
  459. package/src/runtime/routes/events-routes.ts +2 -2
  460. package/src/runtime/routes/global-search-routes.ts +3 -1
  461. package/src/runtime/routes/guardian-action-routes.ts +4 -5
  462. package/src/runtime/routes/host-app-control-routes.ts +5 -4
  463. package/src/runtime/routes/host-bash-routes.ts +5 -4
  464. package/src/runtime/routes/host-browser-routes.ts +9 -11
  465. package/src/runtime/routes/host-cu-routes.ts +5 -4
  466. package/src/runtime/routes/host-file-routes.ts +5 -4
  467. package/src/runtime/routes/host-transfer-routes.ts +6 -6
  468. package/src/runtime/routes/http-adapter.ts +16 -1
  469. package/src/runtime/routes/identity-routes.ts +3 -2
  470. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  471. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +97 -5
  472. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +61 -59
  473. package/src/runtime/routes/inbound-stages/background-dispatch.ts +13 -5
  474. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
  475. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +21 -8
  476. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -3
  477. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +19 -0
  478. package/src/runtime/routes/index.ts +2 -0
  479. package/src/runtime/routes/llm-context-normalization.ts +83 -0
  480. package/src/runtime/routes/mcp-auth-routes.ts +171 -19
  481. package/src/runtime/routes/migration-rollback-routes.ts +4 -3
  482. package/src/runtime/routes/migration-routes.ts +4 -1
  483. package/src/runtime/routes/onboarding-checkin-routes.ts +86 -0
  484. package/src/runtime/routes/settings-routes.ts +8 -3
  485. package/src/runtime/routes/subagents-routes.ts +5 -0
  486. package/src/runtime/routes/surface-action-routes.ts +42 -56
  487. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  488. package/src/runtime/services/conversation-serializer.ts +13 -58
  489. package/src/runtime/tool-grant-request-helper.ts +3 -3
  490. package/src/runtime/trust-verdict-consumer.ts +111 -40
  491. package/src/runtime/verification-outbound-actions.ts +18 -18
  492. package/src/signals/user-message.ts +16 -0
  493. package/src/subagent/__tests__/consult-prompt.test.ts +35 -0
  494. package/src/{plugins/defaults/advisor/__tests__/transcript.test.ts → subagent/__tests__/consult-transcript.test.ts} +47 -10
  495. package/src/{plugins/defaults/advisor/steering.ts → subagent/consult-prompt.ts} +22 -32
  496. package/src/{plugins/defaults/advisor/transcript.ts → subagent/consult-transcript.ts} +18 -8
  497. package/src/subagent/index.ts +1 -1
  498. package/src/subagent/manager.ts +254 -33
  499. package/src/subagent/types.ts +11 -4
  500. package/src/telemetry/types.ts +34 -1
  501. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  502. package/src/telemetry/usage-telemetry-reporter.ts +87 -3
  503. package/src/tools/ask-question/ask-question-tool.test.ts +29 -0
  504. package/src/tools/ask-question/ask-question-tool.ts +13 -0
  505. package/src/tools/executor.ts +4 -4
  506. package/src/tools/filesystem/search.ts +543 -0
  507. package/src/tools/registry.ts +38 -0
  508. package/src/tools/shared/filesystem/path-policy.ts +12 -5
  509. package/src/tools/subagent/consult-deadline.ts +49 -0
  510. package/src/tools/subagent/spawn.ts +234 -5
  511. package/src/tools/tool-approval-handler.ts +1 -1
  512. package/src/tools/tool-defaults.ts +9 -2
  513. package/src/tools/tool-manifest.ts +3 -0
  514. package/src/tools/types.ts +131 -25
  515. package/src/tools/workspace-tools/loader.ts +348 -244
  516. package/src/util/errors.ts +26 -1
  517. package/src/util/logger.ts +9 -0
  518. package/src/util/platform.ts +19 -0
  519. package/src/util/telemetry-db-path.ts +24 -0
  520. package/src/workflows/library.test.ts +140 -0
  521. package/src/workflows/library.ts +82 -28
  522. package/src/workspace/migrations/017-seed-persona-dirs.ts +3 -34
  523. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +3 -24
  524. package/src/workspace/migrations/031-drop-user-md.ts +232 -148
  525. package/src/workspace/migrations/048-remove-workspace-hooks.ts +14 -66
  526. package/src/workspace/migrations/111-prune-seeded-callsite-defaults.ts +134 -0
  527. package/src/workspace/migrations/112-remove-advisor-callsite-override.ts +64 -0
  528. package/src/workspace/migrations/registry.ts +4 -0
  529. package/src/__tests__/workspace-tools-watcher-flag.test.ts +0 -70
  530. package/src/daemon/workspace-tools-watcher.ts +0 -328
  531. package/src/memory/migrations/registry.ts +0 -573
  532. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +0 -56
  533. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +0 -43
  534. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +0 -137
  535. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -153
  536. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +0 -138
  537. package/src/plugins/defaults/advisor/advisor-gate.ts +0 -29
  538. package/src/plugins/defaults/advisor/advisor-state-store.ts +0 -94
  539. package/src/plugins/defaults/advisor/config.ts +0 -21
  540. package/src/plugins/defaults/advisor/consult.ts +0 -93
  541. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +0 -34
  542. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +0 -30
  543. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +0 -19
  544. package/src/plugins/defaults/advisor/package.json +0 -14
  545. package/src/plugins/defaults/advisor/tools/advisor.ts +0 -65
  546. package/src/providers/openai/__tests__/api-error-detail.test.ts +0 -120
@@ -808,7 +808,7 @@ describe("memory-v3 shadow plugin", () => {
808
808
  });
809
809
  });
810
810
 
811
- describe("memory-v3 infrastructure-failure handling (hard-fail vs swallow)", () => {
811
+ describe("memory-v3 infrastructure-failure handling", () => {
812
812
  const throwInfra = () =>
813
813
  orchestrateSpy.mockImplementationOnce(async () => {
814
814
  throw new MemoryV3RetrievalUnavailableError(
@@ -816,14 +816,12 @@ describe("memory-v3 infrastructure-failure handling (hard-fail vs swallow)", ()
816
816
  );
817
817
  });
818
818
 
819
- test("LIVE injector HARD-FAILS the turn on an infra failure (no silent memory loss)", async () => {
819
+ test("LIVE injector logs and degrades to no v3 block on an infra failure", async () => {
820
820
  liveEnabled = true;
821
821
  shadowEnabled = false;
822
822
  throwInfra();
823
823
 
824
- await expect(produce("conv-infra-live", 0)).rejects.toThrow(
825
- MemoryV3RetrievalUnavailableError,
826
- );
824
+ expect(await produce("conv-infra-live", 0)).toBeNull();
827
825
  });
828
826
 
829
827
  test("SHADOW injector swallows an infra failure (v2 fallback) — no throw, no block", async () => {
@@ -832,7 +830,7 @@ describe("memory-v3 infrastructure-failure handling (hard-fail vs swallow)", ()
832
830
  throwInfra();
833
831
 
834
832
  // Shadow mode: v2 retrieval still ran this turn, so the v3 injector returns
835
- // null rather than failing the turn.
833
+ // null.
836
834
  expect(await produce("conv-infra-shadow", 0)).toBeNull();
837
835
  });
838
836
 
@@ -853,8 +851,6 @@ describe("memory-v3 infrastructure-failure handling (hard-fail vs swallow)", ()
853
851
  throw new Error("some unexpected non-infra bug");
854
852
  });
855
853
 
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.
858
854
  expect(await produce("conv-nonfatal-live", 0)).toBeNull();
859
855
  });
860
856
  });
@@ -7,8 +7,8 @@
7
7
  * `../injector.js` and re-apply it here. Until then this hook does nothing.
8
8
  */
9
9
 
10
- import type { PluginHookFn } from "@vellumai/plugin-api";
10
+ import type { HookFunction } from "@vellumai/plugin-api";
11
11
 
12
- const postCompact: PluginHookFn = async () => {};
12
+ const postCompact: HookFunction = async () => {};
13
13
 
14
14
  export default postCompact;
@@ -10,10 +10,10 @@
10
10
  */
11
11
 
12
12
  import type {
13
- PluginHookFn,
13
+ HookFunction,
14
14
  UserPromptSubmitContext,
15
15
  } from "@vellumai/plugin-api";
16
16
 
17
- const userPromptSubmit: PluginHookFn<UserPromptSubmitContext> = async () => {};
17
+ const userPromptSubmit: HookFunction<UserPromptSubmitContext> = async () => {};
18
18
 
19
19
  export default userPromptSubmit;
@@ -63,6 +63,10 @@
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";
66
70
  import { isPersonalMemoryAllowed } from "../../../daemon/trust-context.js";
67
71
  import {
68
72
  wrapMemoryBlock,
@@ -120,6 +124,26 @@ function lruSet<V>(map: Map<string, V>, key: string, value: V): void {
120
124
  map.set(key, value);
121
125
  }
122
126
 
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
+
123
147
  // ─── shared per-turn orchestration memo ─────────────────────────────────────
124
148
 
125
149
  interface ObservedTurn {
@@ -245,16 +269,16 @@ export const memoryV3Injector: Injector = {
245
269
  try {
246
270
  observed = await observeTurnOnce(ctx.conversationId, ctx.turnIndex);
247
271
  } catch (err) {
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;
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
+ );
258
282
  }
259
283
  return null;
260
284
  }
@@ -405,12 +429,16 @@ export const memoryV3SpotlightInjector: Injector = {
405
429
  placement: "after-memory-prefix",
406
430
  };
407
431
  } 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.
412
432
  if (err instanceof MemoryV3RetrievalUnavailableError) {
413
- throw err;
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;
414
442
  }
415
443
  log.warn(
416
444
  {
@@ -119,6 +119,10 @@ 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;
122
126
  }
123
127
 
124
128
  /** A finder-lane candidate: the slug, the descriptor that justified it, and
@@ -378,8 +382,13 @@ export async function orchestrate(
378
382
 
379
383
  // Step 3: a SINGLE forced-tool select over the cache-ordered pool. The
380
384
  // selections come back slug-deduped (pinned flags ORed) — `selectPool`'s
381
- // contract.
382
- const selections = await selectPool({ stable, finder: finderTail }, turn);
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
+ );
383
392
 
384
393
  return {
385
394
  selections,
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Tests for `pool-select.ts` `selectPool` — focused on error SURFACING:
3
+ * - a provider call that THROWS on every attempt surfaces the underlying
4
+ * provider error (e.g. an upstream HTTP 4xx) in the thrown
5
+ * `MemoryV3RetrievalUnavailableError` message, rather than the generic
6
+ * "no usable selection" string that hid it;
7
+ * - a 200 response carrying no usable `tool_use` still throws the generic
8
+ * "no usable selection" message;
9
+ * - happy paths preserved: explicit ids → selection, omitted ids → keepAll,
10
+ * empty pool → [].
11
+ *
12
+ * `mock.module` is process-global and leaks into sibling files in a directory
13
+ * run, so the provider-send-message stub DELEGATES to the real implementation
14
+ * (keeping the real `extractToolUse`) unless this test is actively running
15
+ * (`selectMockActive`) — mirrors `prune.test.ts` / `ever-injected-store.test.ts`.
16
+ */
17
+
18
+ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
19
+
20
+ import type {
21
+ ContentBlock,
22
+ Provider,
23
+ ProviderResponse,
24
+ } from "../../../providers/types.js";
25
+ import type { MemoryRoutingTurn, Slug } from "./types.js";
26
+
27
+ const realProviderSend = {
28
+ ...(await import("../../../providers/provider-send-message.js")),
29
+ };
30
+
31
+ let selectMockActive = false;
32
+ let sendMessageImpl: (() => Promise<ProviderResponse>) | null = null;
33
+
34
+ const mockProvider = {
35
+ name: "mock-memory-v3-selector",
36
+ async sendMessage(): Promise<ProviderResponse> {
37
+ if (!sendMessageImpl) throw new Error("sendMessageImpl not configured");
38
+ return sendMessageImpl();
39
+ },
40
+ } as unknown as Provider;
41
+
42
+ mock.module("../../../providers/provider-send-message.js", () => ({
43
+ ...realProviderSend,
44
+ getConfiguredProvider: (
45
+ ...args: Parameters<typeof realProviderSend.getConfiguredProvider>
46
+ ) =>
47
+ selectMockActive
48
+ ? Promise.resolve(mockProvider)
49
+ : realProviderSend.getConfiguredProvider(...args),
50
+ }));
51
+
52
+ const { MemoryV3RetrievalUnavailableError, selectPool } =
53
+ await import("./pool-select.js");
54
+
55
+ function response(content: ContentBlock[]): ProviderResponse {
56
+ return {
57
+ content,
58
+ model: "mock",
59
+ usage: { inputTokens: 0, outputTokens: 0 },
60
+ } as ProviderResponse;
61
+ }
62
+
63
+ const turn: MemoryRoutingTurn = {
64
+ conversationId: "conv-1",
65
+ turnNumber: 0,
66
+ currentMessage: "echo something back",
67
+ recentContext: "",
68
+ };
69
+
70
+ const pool = {
71
+ stable: [],
72
+ finder: [{ slug: "page-a" as Slug, descriptor: "a descriptor" }],
73
+ };
74
+
75
+ describe("selectPool", () => {
76
+ beforeEach(() => {
77
+ selectMockActive = true;
78
+ sendMessageImpl = null;
79
+ });
80
+ afterAll(() => {
81
+ selectMockActive = false;
82
+ });
83
+
84
+ test("a provider throw surfaces the underlying error in the thrown message", async () => {
85
+ const upstream = "Together AI API error (400): 400 status code (no body)";
86
+ sendMessageImpl = async () => {
87
+ throw new Error(upstream);
88
+ };
89
+ let caught: unknown;
90
+ try {
91
+ await selectPool(pool, turn);
92
+ } catch (err) {
93
+ caught = err;
94
+ }
95
+ expect(caught).toBeInstanceOf(MemoryV3RetrievalUnavailableError);
96
+ expect((caught as Error).message).toContain(
97
+ "provider call failed after retries",
98
+ );
99
+ // The real upstream error is no longer hidden behind the generic message.
100
+ expect((caught as Error).message).toContain(upstream);
101
+ });
102
+
103
+ test("a 200 with no usable tool_use throws the generic message", async () => {
104
+ sendMessageImpl = async () =>
105
+ response([{ type: "text", text: "I cannot call the tool." }]);
106
+ let caught: unknown;
107
+ try {
108
+ await selectPool(pool, turn);
109
+ } catch (err) {
110
+ caught = err;
111
+ }
112
+ expect(caught).toBeInstanceOf(MemoryV3RetrievalUnavailableError);
113
+ expect((caught as Error).message).toBe(
114
+ "memory-v3 pool selector returned no usable selection after retries",
115
+ );
116
+ });
117
+
118
+ test("explicit ids select the matching candidates", async () => {
119
+ sendMessageImpl = async () =>
120
+ response([
121
+ {
122
+ type: "tool_use",
123
+ id: "call-1",
124
+ name: "select_pages",
125
+ input: { ids: [1] },
126
+ },
127
+ ]);
128
+ expect(await selectPool(pool, turn)).toEqual([
129
+ { slug: "page-a", pinned: false },
130
+ ]);
131
+ });
132
+
133
+ test("omitted ids keep all candidates (recall-safe)", async () => {
134
+ sendMessageImpl = async () =>
135
+ response([
136
+ { type: "tool_use", id: "call-1", name: "select_pages", input: {} },
137
+ ]);
138
+ expect(await selectPool(pool, turn)).toEqual([
139
+ { slug: "page-a", pinned: false },
140
+ ]);
141
+ });
142
+
143
+ test("an empty candidate pool returns no selections", async () => {
144
+ expect(await selectPool({ stable: [], finder: [] }, turn)).toEqual([]);
145
+ });
146
+ });
@@ -41,15 +41,16 @@
41
41
  * - infrastructure failure (selector provider unavailable — e.g. a transient
42
42
  * CES credential blip drops the API key — or no usable `tool_use` / schema
43
43
  * mismatch surviving the short re-prompt retry) → throw
44
- * {@link MemoryV3RetrievalUnavailableError}. There is NO deterministic-lane
45
- * fallback: the LIVE injector propagates this to hard-fail the turn
46
- * (retryable) rather than silently shipping it with no `<memory>` block,
47
- * while the shadow/observation path swallows it and lets v2 retrieval serve
48
- * the turn.
44
+ * {@link MemoryV3RetrievalUnavailableError}. The live injector treats this
45
+ * as a logged memory miss for the turn; shadow/observation callers swallow
46
+ * it so v2 retrieval can serve the turn.
49
47
  */
50
48
 
51
49
  import { z } from "zod";
52
50
 
51
+ import { classifyConversationError } from "../../../daemon/conversation-error.js";
52
+ import type { PendingConversationNotice } from "../../../daemon/conversation-notices.js";
53
+ import { loadPromptOverride } from "../../../memory/prompt-override.js";
53
54
  import { cachedTextBlock } from "../../../providers/cache-control.js";
54
55
  import {
55
56
  extractToolUse,
@@ -76,17 +77,43 @@ const log = getLogger("memory-v3-pool-select");
76
77
  * re-prompt retry. Deliberately DISTINCT from a deliberate empty selection
77
78
  * (`ids: []`) and an empty candidate pool, both of which return normally.
78
79
  *
79
- * The LIVE memory-v3 injector propagates this to hard-fail the turn (a clean,
80
- * retryable failure) rather than silently shipping with no memory; the
80
+ * The live memory-v3 injector logs this as a memory miss for the turn; the
81
81
  * shadow/observation path catches and swallows it.
82
82
  */
83
83
  export class MemoryV3RetrievalUnavailableError extends Error {
84
- constructor(message: string) {
85
- super(message);
84
+ readonly conversationNotice?: PendingConversationNotice;
85
+
86
+ constructor(
87
+ message: string,
88
+ options?: {
89
+ cause?: unknown;
90
+ conversationNotice?: PendingConversationNotice;
91
+ },
92
+ ) {
93
+ super(
94
+ message,
95
+ options?.cause === undefined ? undefined : { cause: options.cause },
96
+ );
86
97
  this.name = "MemoryV3RetrievalUnavailableError";
98
+ this.conversationNotice = options?.conversationNotice;
87
99
  }
88
100
  }
89
101
 
102
+ function providerBillingNoticeFromError(
103
+ error: unknown,
104
+ ): PendingConversationNotice | undefined {
105
+ const classified = classifyConversationError(error, {
106
+ phase: "agent_loop",
107
+ });
108
+ if (classified.code !== "PROVIDER_BILLING") return undefined;
109
+ return {
110
+ source: "memory_v3",
111
+ code: classified.code,
112
+ userMessage: classified.userMessage,
113
+ errorCategory: classified.errorCategory,
114
+ };
115
+ }
116
+
90
117
  /** A dynamic-tail (finder) candidate: the slug plus the descriptor that
91
118
  * justifies it — a matched section for a needle/dense hit, or a curated link
92
119
  * description for an edge page. Rendered as a one-line snippet, prefixed
@@ -201,6 +228,28 @@ A page can be relevant because of the current situation — the date or the live
201
228
 
202
229
  If the conversation is centrally ABOUT a page (rather than only peripherally relevant to it), mark that page as pinned. Call \`select_pages\` with the chosen IDs. Omit \`ids\` only as a recall-safe fallback when you cannot judge the pool (keeps every candidate); return \`[]\` when candidates are present but none are relevant.`;
203
230
 
231
+ /**
232
+ * Resolve the selector system prompt: the file at `overridePath` when it is set
233
+ * and usable, otherwise the bundled {@link SYSTEM_PROMPT}. Path resolution and
234
+ * fallback follow the shared override loader (workspace-relative; a missing,
235
+ * empty, oversized, or unreadable file degrades to the bundled prompt with a
236
+ * warning). The selector prompt takes no placeholders — the candidate pool is
237
+ * the user message — so an override file is used verbatim.
238
+ */
239
+ export function resolveSelectorPrompt(
240
+ overridePath: string | null,
241
+ workspaceDir: string,
242
+ ): string {
243
+ return (
244
+ loadPromptOverride({
245
+ overridePath,
246
+ workspaceDir,
247
+ log,
248
+ label: "memory-v3 selector prompt",
249
+ }) ?? SYSTEM_PROMPT
250
+ );
251
+ }
252
+
204
253
  /** Collapse a descriptor to one line and cap its length for a finder line. */
205
254
  function renderSnippet(descriptor: string): string {
206
255
  return truncate(descriptor.replace(/\s+/g, " ").trim(), SNIPPET_MAX_CHARS);
@@ -315,10 +364,15 @@ function dedupeBySlug(
315
364
  * relevant" signal); an explicit `[]` keeps none; an infrastructure failure
316
365
  * (after a short re-prompt retry) keeps none, degrading to the deterministic
317
366
  * recall lanes the orchestrator unions in.
367
+ *
368
+ * `systemPrompt` is the selector's instruction scaffold; it defaults to the
369
+ * bundled {@link SYSTEM_PROMPT} and is overridable via `memory.v3.selectorPromptPath`
370
+ * (resolved by {@link resolveSelectorPrompt} at the call site).
318
371
  */
319
372
  export async function selectPool(
320
373
  pool: SelectorPool,
321
374
  turn: MemoryRoutingTurn,
375
+ systemPrompt: string = SYSTEM_PROMPT,
322
376
  ): Promise<SelectedPage[]> {
323
377
  // The concatenated numbering: ids 1…m are the stable-prefix cards, ids
324
378
  // m+1… are the finder lines.
@@ -340,7 +394,7 @@ export async function selectPool(
340
394
  stableCount: pool.stable.length,
341
395
  finderCount: pool.finder.length,
342
396
  },
343
- "pool selector provider unavailable — failing the turn rather than dropping memory",
397
+ "pool selector provider unavailable",
344
398
  );
345
399
  throw new MemoryV3RetrievalUnavailableError(
346
400
  "memory-v3 pool selector provider unavailable",
@@ -394,6 +448,12 @@ export async function selectPool(
394
448
  // (no usable tool_use, or tool input that fails the schema) re-prompts before
395
449
  // we give up. `null` from an attempt means "unusable, retry"; the provider
396
450
  // layer already backs off transient throws, so this loop adds no delay.
451
+ //
452
+ // `lastError` captures the most recent attempt's thrown provider error —
453
+ // `retryForResult` swallows attempt throws, so without this an infrastructure
454
+ // failure (e.g. an upstream HTTP 4xx/5xx) is indistinguishable from a 200 that
455
+ // carried no usable tool_use. It is cleared on every attempt that reaches a
456
+ // response, so it reflects the LAST attempt's failure mode.
397
457
  let lastError: unknown = null;
398
458
  const parsed = await retryForResult(async () => {
399
459
  attempt += 1;
@@ -401,7 +461,7 @@ export async function selectPool(
401
461
  try {
402
462
  response = await provider.sendMessage([userMsg], {
403
463
  tools: [SELECT_PAGES_TOOL],
404
- systemPrompt: SYSTEM_PROMPT,
464
+ systemPrompt,
405
465
  config: {
406
466
  callSite: MEMORY_V3_SELECT_CALL_SITE,
407
467
  tool_choice: { type: "tool" as const, name: SELECT_PAGES_TOOL_NAME },
@@ -476,10 +536,14 @@ export async function selectPool(
476
536
  providerName: provider.name,
477
537
  failures,
478
538
  },
479
- "pool selector provider call failed after retries — failing the turn rather than dropping memory",
539
+ "pool selector provider call failed after retries",
480
540
  );
481
541
  throw new MemoryV3RetrievalUnavailableError(
482
542
  `memory-v3 pool selector provider call failed after retries: ${redactedDetail}`,
543
+ {
544
+ cause: lastError,
545
+ conversationNotice: providerBillingNoticeFromError(lastError),
546
+ },
483
547
  );
484
548
  }
485
549
  log.warn(
@@ -491,7 +555,7 @@ export async function selectPool(
491
555
  providerName: provider.name,
492
556
  failures,
493
557
  },
494
- "pool selector returned no usable tool_use after retries — failing the turn rather than dropping memory",
558
+ "pool selector returned no usable tool_use after retries",
495
559
  );
496
560
  throw new MemoryV3RetrievalUnavailableError(
497
561
  "memory-v3 pool selector returned no usable selection after retries",
@@ -56,7 +56,10 @@ import { computeHotSet } from "./hot-set.js";
56
56
  import { computeLearnedEdgeGraph } from "./learned-edges.js";
57
57
  import type { OrchestrateResult } from "./orchestrate.js";
58
58
  import { orchestrate } from "./orchestrate.js";
59
- import { MemoryV3RetrievalUnavailableError } from "./pool-select.js";
59
+ import {
60
+ MemoryV3RetrievalUnavailableError,
61
+ resolveSelectorPrompt,
62
+ } from "./pool-select.js";
60
63
  import { ensureSectionCollection } from "./section-dense-store.js";
61
64
  import type { SectionNeedle } from "./section-needle.js";
62
65
  import { buildSectionNeedle } from "./section-needle.js";
@@ -576,6 +579,10 @@ export async function observeTurn(
576
579
  learnedGraph: lanes.learnedGraph,
577
580
  learnedPerSeed: v3.learnedEdges.perSeed,
578
581
  learnedCap: v3.learnedEdges.cap,
582
+ selectorPrompt: resolveSelectorPrompt(
583
+ v3.selectorPromptPath,
584
+ getWorkspaceDir(),
585
+ ),
579
586
  });
580
587
 
581
588
  // A zero-selection turn over a non-trivial pool is unusual enough to be
@@ -598,13 +605,9 @@ export async function observeTurn(
598
605
  writeSelections(conversationId, turnIndex, rows);
599
606
  return result;
600
607
  } catch (err) {
601
- // An INFRASTRUCTURE failure (the selector lost its provider — e.g. a
602
- // transient CES credential blip) must NOT be silently swallowed: re-throw
603
- // so the LIVE injector hard-fails the turn (a clean, retryable failure)
604
- // rather than shipping it with no `<memory>` block. The shadow/observation
605
- // callers (the injector in shadow mode, runShadowObservation) catch this
606
- // and swallow it, so observation never fails a turn. Other (non-infra)
607
- // errors stay non-fatal and degrade to no v3 block, as before.
608
+ // Infrastructure failures are surfaced to callers that want distinct
609
+ // logging from ordinary orchestration misses. Observation callers swallow
610
+ // them so memory-v3 never fails the turn.
608
611
  if (err instanceof MemoryV3RetrievalUnavailableError) {
609
612
  throw err;
610
613
  }
@@ -634,8 +637,6 @@ export async function runShadowObservation(
634
637
  try {
635
638
  await observeTurn(conversationId, turnIndex);
636
639
  } catch {
637
- // Shadow observation is fire-and-forget and must NEVER fail a turn.
638
- // `observeTurn` now re-throws infra failures so the LIVE injector can
639
- // hard-fail on them; here (observation only) we swallow them.
640
+ // Shadow observation is fire-and-forget and must never fail a turn.
640
641
  }
641
642
  }
@@ -45,7 +45,7 @@
45
45
  * the `post-model-call` chain — later hooks see (and may override) its decision.
46
46
  */
47
47
 
48
- import type { PluginHookFn, PostModelCallContext } from "@vellumai/plugin-api";
48
+ import type { HookFunction, PostModelCallContext } from "@vellumai/plugin-api";
49
49
 
50
50
  import type { ContentBlock, Message } from "../../../../providers/types.js";
51
51
  import {
@@ -246,7 +246,7 @@ function hasDanglingProgressSurface(messages: ReadonlyArray<Message>): boolean {
246
246
  return false;
247
247
  }
248
248
 
249
- const postModelCall: PluginHookFn<PostModelCallContext> = async (ctx) => {
249
+ const postModelCall: HookFunction<PostModelCallContext> = async (ctx) => {
250
250
  // A provider rejection carries no turn content to assess (a recovery hook
251
251
  // owns the rejection).
252
252
  if (ctx.error) return;
@@ -11,11 +11,11 @@
11
11
  * refused).
12
12
  */
13
13
 
14
- import type { PluginHookFn, StopContext } from "@vellumai/plugin-api";
14
+ import type { HookFunction, StopContext } from "@vellumai/plugin-api";
15
15
 
16
16
  import { clearSurfaceCompletionNudged } from "../nudge-state-store.js";
17
17
 
18
- const stop: PluginHookFn<StopContext> = async (ctx) => {
18
+ const stop: HookFunction<StopContext> = async (ctx) => {
19
19
  clearSurfaceCompletionNudged(ctx.conversationId);
20
20
  };
21
21
 
@@ -45,7 +45,7 @@
45
45
  * below it (a new turn restarts counting low).
46
46
  */
47
47
 
48
- import type { PluginHookFn, PostToolUseContext } from "@vellumai/plugin-api";
48
+ import type { HookFunction, PostToolUseContext } from "@vellumai/plugin-api";
49
49
 
50
50
  import type { ContentBlock, Message } from "../../../../providers/types.js";
51
51
  import { isWeakOpenModel } from "../../../../providers/weak-open-model.js";
@@ -134,7 +134,7 @@ function scanTurn(messages: ReadonlyArray<Message>): {
134
134
  return { rounds, taskProgressShown };
135
135
  }
136
136
 
137
- const postToolUse: PluginHookFn<PostToolUseContext> = async (ctx) => {
137
+ const postToolUse: HookFunction<PostToolUseContext> = async (ctx) => {
138
138
  if (!isWeakOpenModel(ctx.model)) return;
139
139
 
140
140
  const { rounds, taskProgressShown } = scanTurn(ctx.messages);
@@ -18,7 +18,7 @@
18
18
  * stateless and means a mid-run array rewrite (compaction) can't invalidate it.
19
19
  */
20
20
 
21
- import type { PluginHookFn, StopContext } from "@vellumai/plugin-api";
21
+ import type { HookFunction, StopContext } from "@vellumai/plugin-api";
22
22
 
23
23
  import { getConfig } from "../../../../config/loader.js";
24
24
  import { getConversation } from "../../../../memory/conversation-crud.js";
@@ -65,7 +65,7 @@ function shouldRetryFallbackTitle(conversation: {
65
65
  );
66
66
  }
67
67
 
68
- const stop: PluginHookFn<StopContext> = async (ctx) => {
68
+ const stop: HookFunction<StopContext> = async (ctx) => {
69
69
  // Re-title only at a genuine successful turn end (the model returned a reply
70
70
  // with no tool calls). Any other terminal — a provider rejection, abort, or
71
71
  // an output-limit cutoff — produced no new topic to re-title from.
@@ -11,14 +11,14 @@
11
11
  */
12
12
 
13
13
  import type {
14
- PluginHookFn,
14
+ HookFunction,
15
15
  UserPromptSubmitContext,
16
16
  } from "@vellumai/plugin-api";
17
17
 
18
18
  import { getConversation } from "../../../../memory/conversation-crud.js";
19
19
  import { queueGenerateConversationTitle } from "../../../../memory/conversation-title-service.js";
20
20
 
21
- const userPromptSubmit: PluginHookFn<UserPromptSubmitContext> = async (ctx) => {
21
+ const userPromptSubmit: HookFunction<UserPromptSubmitContext> = async (ctx) => {
22
22
  // System conversations (background/scheduled) carry a deterministic title
23
23
  // from bootstrap. Their own job prompts arrive as non-interactive turns and
24
24
  // must not spend an LLM call on a title nobody reads — only a genuine user
@@ -22,7 +22,7 @@
22
22
  * result for the tool resets its streak.
23
23
  */
24
24
 
25
- import type { PluginHookFn, PostToolUseContext } from "@vellumai/plugin-api";
25
+ import type { HookFunction, PostToolUseContext } from "@vellumai/plugin-api";
26
26
 
27
27
  import type { Message } from "../../../../providers/types.js";
28
28
 
@@ -85,7 +85,7 @@ function priorConsecutiveErrors(
85
85
  return streak;
86
86
  }
87
87
 
88
- const postToolUse: PluginHookFn<PostToolUseContext> = async (ctx) => {
88
+ const postToolUse: HookFunction<PostToolUseContext> = async (ctx) => {
89
89
  if (ctx.toolResponse.is_error !== true) return;
90
90
 
91
91
  const namesById = toolNamesById(ctx.messages);