@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
@@ -161,23 +161,22 @@ async function runSeedV2CliCommandEntries(generation: number): Promise<void> {
161
161
  seeds.push({ id: name, description, content });
162
162
  }
163
163
 
164
- // Sparse (BM25/TF) encoding is computed locally; only the dense vectors
165
- // require `embedWithBackend`, which is unconfigured during the cold-start
166
- // window before a managed-proxy embedding credential is provisioned. A
167
- // dense-embed failure is non-fatal to the in-memory cache: the v3 needle
168
- // finder lane reads CLI capabilities from `entries` / the page index, NOT
169
- // from Qdrant, so the cache is populated from the local Commander tree
170
- // regardless of backend state and commands stay discoverable from first
171
- // boot. Only the dense Qdrant upsert is skipped; the managed-credential
172
- // reseed and the v3 maintain pass backfill the dense vectors on recovery.
173
164
  const nextEntries = new Map<string, CliCommandEntry>();
174
165
  let denseVectors: number[][] = [];
175
- let denseAvailable = false;
176
- let denseError: unknown = null;
177
166
  let encodeSparse: (
178
167
  input: string,
179
168
  ) => ReturnType<typeof generateSparseEmbedding> = generateSparseEmbedding;
180
169
  if (seeds.length > 0) {
170
+ const embedded = await embedWithBackend(
171
+ config,
172
+ seeds.map((s) => s.content),
173
+ );
174
+ denseVectors = await Promise.all(
175
+ embedded.vectors.map((v) =>
176
+ applyCorrectionIfCalibrated(v, embedded.provider, embedded.model),
177
+ ),
178
+ );
179
+
181
180
  // CLI commands share the concept-page Qdrant collection, so the sparse
182
181
  // vector must use the same stemmed BM25 encoding as the concept-page
183
182
  // documents. Fall back to the legacy TF encoder only during the cold-
@@ -191,24 +190,6 @@ async function runSeedV2CliCommandEntries(generation: number): Promise<void> {
191
190
  b: config.memory.v2.bm25_b,
192
191
  })
193
192
  : generateSparseEmbedding(input);
194
- try {
195
- const embedded = await embedWithBackend(
196
- config,
197
- seeds.map((s) => s.content),
198
- );
199
- denseVectors = await Promise.all(
200
- embedded.vectors.map((v) =>
201
- applyCorrectionIfCalibrated(v, embedded.provider, embedded.model),
202
- ),
203
- );
204
- denseAvailable = true;
205
- } catch (err) {
206
- denseError = err;
207
- log.warn(
208
- { err },
209
- "Embedding backend unavailable — seeding CLI-command cache without dense Qdrant vectors; the needle lane surfaces commands from the cache and the dense lane backfills when the backend recovers",
210
- );
211
- }
212
193
  }
213
194
 
214
195
  if (generation !== requestedSeedGeneration) {
@@ -220,17 +201,7 @@ async function runSeedV2CliCommandEntries(generation: number): Promise<void> {
220
201
  return;
221
202
  }
222
203
 
223
- // Populate the in-memory cache (and therefore the page index / needle lane)
224
- // from the local Commander tree regardless of dense availability.
225
- for (const seed of seeds) {
226
- nextEntries.set(seed.id, seed);
227
- }
228
-
229
- // Write the dense+sparse Qdrant points only when dense vectors were
230
- // produced. In the degraded (backend-unavailable) path we skip the upsert
231
- // so we never write half-formed points; the dense lane backfills on
232
- // recovery.
233
- if (seeds.length > 0 && denseAvailable) {
204
+ if (seeds.length > 0) {
234
205
  const now = Date.now();
235
206
  await Promise.all(
236
207
  seeds.map((seed, i) =>
@@ -243,48 +214,40 @@ async function runSeedV2CliCommandEntries(generation: number): Promise<void> {
243
214
  }),
244
215
  ),
245
216
  );
217
+ for (const seed of seeds) {
218
+ nextEntries.set(seed.id, seed);
219
+ }
246
220
  }
247
221
 
248
- // The CLI tree is always available (no remote catalog), so pruning to clear
249
- // stale rows runs whenever we wrote the current set this run OR there was
250
- // nothing to embed (empty tree). The cold-start degraded path (commands
251
- // present but dense embedding unavailable) skips the prune so we never
252
- // reconcile the collection against a set we did not persist. Run the legacy
253
- // `kind` backfill once per process so pre-discriminator rows become prunable.
254
- if (denseAvailable || seeds.length === 0) {
255
- const knownIds = new Set(seeds.map((s) => s.id));
256
- if (!legacyKindBackfillDone) {
257
- try {
258
- await backfillKindOnPointsWithPrefix(
259
- CLI_COMMAND_SLUG_PREFIX,
260
- CLI_COMMAND_PAYLOAD_KIND,
261
- knownIds,
262
- );
263
- legacyKindBackfillDone = true;
264
- } catch (err) {
265
- log.warn(
266
- { err },
267
- "Failed to backfill kind on legacy CLI-command points — pruning may leave orphans this run",
268
- );
269
- }
222
+ // The CLI tree is always available (no remote catalog), so pruning is
223
+ // unconditional. Run the legacy `kind` backfill once per process so
224
+ // pre-discriminator rows become prunable.
225
+ const knownIds = new Set(seeds.map((s) => s.id));
226
+ if (!legacyKindBackfillDone) {
227
+ try {
228
+ await backfillKindOnPointsWithPrefix(
229
+ CLI_COMMAND_SLUG_PREFIX,
230
+ CLI_COMMAND_PAYLOAD_KIND,
231
+ knownIds,
232
+ );
233
+ legacyKindBackfillDone = true;
234
+ } catch (err) {
235
+ log.warn(
236
+ { err },
237
+ "Failed to backfill kind on legacy CLI-command points — pruning may leave orphans this run",
238
+ );
270
239
  }
271
- await pruneSlugsWithPrefixExcept(
272
- CLI_COMMAND_SLUG_PREFIX,
273
- seeds.map((s) => s.id),
274
- { kind: CLI_COMMAND_PAYLOAD_KIND },
275
- );
276
240
  }
241
+ await pruneSlugsWithPrefixExcept(
242
+ CLI_COMMAND_SLUG_PREFIX,
243
+ seeds.map((s) => s.id),
244
+ { kind: CLI_COMMAND_PAYLOAD_KIND },
245
+ );
277
246
 
278
- // Atomically replace the cache from the freshly enumerated commands. Drop
279
- // the page-index cache so the next router invocation observes the new
280
- // command set.
247
+ // Atomically replace the cache only after every step above succeeds.
281
248
  entries = nextEntries;
282
249
  invalidatePageIndex();
283
-
284
- // Surface a dense-embed failure to `throwOnError` callers so the existing
285
- // retry + maintain machinery backfills the dense lane. The in-memory cache
286
- // is already updated above, so the needle lane is fixed regardless.
287
- lastSeedError = denseError;
250
+ lastSeedError = null;
288
251
  } catch (err) {
289
252
  lastSeedError = err;
290
253
  log.warn({ err }, "Failed to seed v2 CLI-command entries");
@@ -18,9 +18,12 @@
18
18
  * the convention established for the sweep prompt.
19
19
  */
20
20
 
21
+ import { lstatSync, readFileSync } from "node:fs";
22
+ import { homedir } from "node:os";
23
+ import { isAbsolute, join } from "node:path";
24
+
21
25
  import { getLogger } from "../../../util/logger.js";
22
26
  import { getWorkspaceDir } from "../../../util/platform.js";
23
- import { loadPromptOverride } from "../../prompt-override.js";
24
27
 
25
28
  const log = getLogger("memory-v2-consolidate-prompt");
26
29
 
@@ -58,6 +61,14 @@ export const CORE_PAGES_CONSOLIDATION_SECTION = `## 10. Review \`memory/core-pag
58
61
 
59
62
  `;
60
63
 
64
+ /**
65
+ * Upper bound for the override file. Real consolidation prompts are kilobytes;
66
+ * 1 MiB is generous headroom while preventing a `settings.write` principal from
67
+ * pointing the field at a multi-gigabyte file (or `/dev/zero`-like stream that
68
+ * `lstat` can't size cap on its own) and exfiltrating it through the wake hint.
69
+ */
70
+ const MAX_PROMPT_BYTES = 1 * 1024 * 1024;
71
+
61
72
  /**
62
73
  * Consolidation prompt — live-mode only. The agent runs as itself (full
63
74
  * SOUL.md + IDENTITY.md + persona + memory autoloads) with the standard
@@ -826,32 +837,90 @@ export function renderConsolidationPrompt(
826
837
  /**
827
838
  * Load the consolidation prompt template, optionally overridden from the file
828
839
  * referenced by `memory.v2.consolidation_prompt_path`, then substitute
829
- * `{{CUTOFF}}`. File loading (path resolution, size guard, and the permissive
830
- * fall-back to the bundled prompt on a missing/unreadable/empty/oversized
831
- * override) is handled by the shared {@link loadPromptOverride}.
840
+ * `{{CUTOFF}}`. Path-resolution rules are documented on the schema field.
832
841
  *
833
842
  * Override files get the same placeholder substitutions as the bundled
834
843
  * template: `{{CUTOFF}}` always, and `{{CORE_PAGES_SECTION}}` per the same
835
844
  * flag gate — so a prompt copied from the bundled source never leaks a raw
836
845
  * placeholder, and a customized prompt can opt into the managed section.
846
+ *
847
+ * Failure handling is intentionally permissive — missing file, read error, or
848
+ * empty/whitespace-only body all log a warning and fall back to the bundled
849
+ * prompt. Consolidation must never break because of a bad override: the
850
+ * daemon's startup philosophy is "log and recover."
837
851
  */
838
852
  export function resolveConsolidationPrompt(
839
853
  overridePath: string | null,
840
854
  cutoff: string,
841
855
  options: ConsolidationPromptOptions,
842
856
  ): string {
843
- const override = loadPromptOverride({
844
- overridePath,
845
- workspaceDir: getWorkspaceDir(),
846
- log,
847
- label: "consolidation prompt",
848
- });
849
- if (override === null) return renderConsolidationPrompt(cutoff, options);
850
-
851
- return override
857
+ if (overridePath === null) return renderConsolidationPrompt(cutoff, options);
858
+
859
+ const resolvedPath = resolveOverridePath(overridePath);
860
+ let contents: string;
861
+ try {
862
+ const stat = lstatSync(resolvedPath);
863
+ if (!stat.isFile()) {
864
+ log.warn(
865
+ {
866
+ configuredPath: overridePath,
867
+ resolvedPath,
868
+ reason: "not_regular_file",
869
+ fallback: "bundled",
870
+ },
871
+ "consolidation prompt override is not a regular file; using bundled prompt",
872
+ );
873
+ return renderConsolidationPrompt(cutoff, options);
874
+ }
875
+ if (stat.size > MAX_PROMPT_BYTES) {
876
+ log.warn(
877
+ {
878
+ configuredPath: overridePath,
879
+ resolvedPath,
880
+ size: stat.size,
881
+ limit: MAX_PROMPT_BYTES,
882
+ reason: "oversized_override",
883
+ fallback: "bundled",
884
+ },
885
+ "consolidation prompt override exceeds size limit; using bundled prompt",
886
+ );
887
+ return renderConsolidationPrompt(cutoff, options);
888
+ }
889
+ contents = readFileSync(resolvedPath, "utf-8");
890
+ } catch (err) {
891
+ const code = (err as NodeJS.ErrnoException).code;
892
+ log.warn(
893
+ { configuredPath: overridePath, resolvedPath, code, fallback: "bundled" },
894
+ "consolidation prompt override unreadable; using bundled prompt",
895
+ );
896
+ return renderConsolidationPrompt(cutoff, options);
897
+ }
898
+
899
+ if (contents.trim().length === 0) {
900
+ log.warn(
901
+ {
902
+ configuredPath: overridePath,
903
+ resolvedPath,
904
+ reason: "empty_override",
905
+ fallback: "bundled",
906
+ },
907
+ "consolidation prompt override is empty; using bundled prompt",
908
+ );
909
+ return renderConsolidationPrompt(cutoff, options);
910
+ }
911
+
912
+ return contents
852
913
  .replaceAll(CUTOFF_PLACEHOLDER, cutoff)
853
914
  .replaceAll(
854
915
  CORE_PAGES_PLACEHOLDER,
855
916
  options.includeCorePagesSection ? CORE_PAGES_CONSOLIDATION_SECTION : "",
856
917
  );
857
918
  }
919
+
920
+ function resolveOverridePath(overridePath: string): string {
921
+ if (overridePath.startsWith("~/")) {
922
+ return join(homedir(), overridePath.slice(2));
923
+ }
924
+ if (isAbsolute(overridePath)) return overridePath;
925
+ return join(getWorkspaceDir(), overridePath);
926
+ }
@@ -22,14 +22,22 @@
22
22
  * same placeholder substitution applies to overrides.
23
23
  */
24
24
 
25
+ import { lstatSync, readFileSync } from "node:fs";
26
+ import { homedir } from "node:os";
27
+ import { isAbsolute, join } from "node:path";
28
+
25
29
  import { getLogger } from "../../../util/logger.js";
26
- import {
27
- loadPromptOverride,
28
- MAX_PROMPT_OVERRIDE_BYTES,
29
- } from "../../prompt-override.js";
30
30
 
31
31
  const log = getLogger("memory-v2-router-prompt");
32
32
 
33
+ /**
34
+ * Hard upper bound on the override file size. The bundled prompt is well
35
+ * under 4 KiB; 1 MiB is generous-enough for any reasonable hand-edit while
36
+ * still preventing pathological inputs from being slurped into memory on
37
+ * every router call.
38
+ */
39
+ const MAX_PROMPT_BYTES = 1 * 1024 * 1024;
40
+
33
41
  /** Sentinel substituted with the assistant's display name at runtime. */
34
42
  const ASSISTANT_NAME_PLACEHOLDER = "{{ASSISTANT_NAME}}";
35
43
 
@@ -94,12 +102,14 @@ export function renderRouterPrompt(opts: RenderRouterPromptOpts): string {
94
102
  /**
95
103
  * Load the router prompt template, optionally overridden from the file
96
104
  * referenced by `memory.v2.router.router_prompt_path`, then substitute the
97
- * standard placeholders. File loading (path resolution, size guard, and the
98
- * permissive fall-back to the bundled prompt on a missing/unreadable/empty/
99
- * oversized override) is handled by the shared {@link loadPromptOverride}.
105
+ * standard placeholders. Path-resolution rules mirror the consolidation
106
+ * prompt override: absolute paths used as-is, leading `~/` expanded to home,
107
+ * relative paths resolved under `workspaceDir`.
100
108
  *
101
- * An `inlineOverride` (e.g. the simulator playground) takes precedence over the
102
- * configured file path; same placeholder substitution and size guard apply.
109
+ * Failure handling is intentionally permissive missing file, read error,
110
+ * oversized file, or empty/whitespace-only body all log a warning and fall
111
+ * back to the bundled prompt. Router selection must never break because of
112
+ * a bad override.
103
113
  */
104
114
  export function resolveRouterPrompt(
105
115
  overridePath: string | null,
@@ -107,15 +117,17 @@ export function resolveRouterPrompt(
107
117
  opts: RenderRouterPromptOpts,
108
118
  inlineOverride?: string | null,
109
119
  ): string {
110
- // Inline override takes precedence over the configured file path and the
111
- // bundled prompt. Empty/whitespace bodies fall through to file/bundled
112
- // resolution so a "cleared" textarea is treated as no override.
120
+ // Inline override (e.g. simulator playground) takes precedence over the
121
+ // configured file path and the bundled prompt. Same placeholder
122
+ // substitution + size guard as the file-path branch; empty/whitespace
123
+ // bodies fall through to file/bundled resolution so a "cleared" textarea
124
+ // is treated as no override.
113
125
  if (inlineOverride !== undefined && inlineOverride !== null) {
114
- if (inlineOverride.length > MAX_PROMPT_OVERRIDE_BYTES) {
126
+ if (inlineOverride.length > MAX_PROMPT_BYTES) {
115
127
  log.warn(
116
128
  {
117
129
  size: inlineOverride.length,
118
- limit: MAX_PROMPT_OVERRIDE_BYTES,
130
+ limit: MAX_PROMPT_BYTES,
119
131
  reason: "oversized_inline_override",
120
132
  fallback: "path_or_bundled",
121
133
  },
@@ -126,13 +138,62 @@ export function resolveRouterPrompt(
126
138
  }
127
139
  }
128
140
 
129
- const override = loadPromptOverride({
130
- overridePath,
131
- workspaceDir,
132
- log,
133
- label: "router prompt",
134
- });
135
- return substitutePlaceholders(override ?? ROUTER_PROMPT, opts);
141
+ if (overridePath === null) return renderRouterPrompt(opts);
142
+
143
+ const resolvedPath = resolveOverridePath(overridePath, workspaceDir);
144
+ let contents: string;
145
+ try {
146
+ const stat = lstatSync(resolvedPath);
147
+ if (!stat.isFile()) {
148
+ log.warn(
149
+ {
150
+ configuredPath: overridePath,
151
+ resolvedPath,
152
+ reason: "not_regular_file",
153
+ fallback: "bundled",
154
+ },
155
+ "router prompt override is not a regular file; using bundled prompt",
156
+ );
157
+ return renderRouterPrompt(opts);
158
+ }
159
+ if (stat.size > MAX_PROMPT_BYTES) {
160
+ log.warn(
161
+ {
162
+ configuredPath: overridePath,
163
+ resolvedPath,
164
+ size: stat.size,
165
+ limit: MAX_PROMPT_BYTES,
166
+ reason: "oversized_override",
167
+ fallback: "bundled",
168
+ },
169
+ "router prompt override exceeds size limit; using bundled prompt",
170
+ );
171
+ return renderRouterPrompt(opts);
172
+ }
173
+ contents = readFileSync(resolvedPath, "utf-8");
174
+ } catch (err) {
175
+ const code = (err as NodeJS.ErrnoException).code;
176
+ log.warn(
177
+ { configuredPath: overridePath, resolvedPath, code, fallback: "bundled" },
178
+ "router prompt override unreadable; using bundled prompt",
179
+ );
180
+ return renderRouterPrompt(opts);
181
+ }
182
+
183
+ if (contents.trim().length === 0) {
184
+ log.warn(
185
+ {
186
+ configuredPath: overridePath,
187
+ resolvedPath,
188
+ reason: "empty_override",
189
+ fallback: "bundled",
190
+ },
191
+ "router prompt override is empty; using bundled prompt",
192
+ );
193
+ return renderRouterPrompt(opts);
194
+ }
195
+
196
+ return substitutePlaceholders(contents, opts);
136
197
  }
137
198
 
138
199
  function substitutePlaceholders(
@@ -146,3 +207,14 @@ function substitutePlaceholders(
146
207
  .replaceAll(USER_NAME_PLACEHOLDER, () => user)
147
208
  .replaceAll(PAGE_INDEX_PLACEHOLDER, () => opts.pageIndexBlock);
148
209
  }
210
+
211
+ function resolveOverridePath(
212
+ overridePath: string,
213
+ workspaceDir: string,
214
+ ): string {
215
+ if (overridePath.startsWith("~/")) {
216
+ return join(homedir(), overridePath.slice(2));
217
+ }
218
+ if (isAbsolute(overridePath)) return overridePath;
219
+ return join(workspaceDir, overridePath);
220
+ }
@@ -16,11 +16,8 @@
16
16
  //
17
17
  // Skill entries are kept in a small in-process cache so the render path can
18
18
  // fetch a `SkillEntry` synchronously by id without round-tripping to Qdrant.
19
- // The cache is replaced atomically from the local catalog at the end of a seed
20
- // run — including when the dense embedding backend is unavailable, in which
21
- // case only the dense Qdrant write is deferred so the cache (and the v3 needle
22
- // lane it feeds) still reflects the current skills. An unexpected error in
23
- // another step leaves the prior cache intact (skills are best-effort).
19
+ // The cache is replaced atomically at the end of a successful seed run; on
20
+ // error the prior cache stays intact (skills are best-effort).
24
21
 
25
22
  import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
26
23
  import { getConfig } from "../../config/loader.js";
@@ -231,26 +228,26 @@ async function runSeedV2SkillEntries(generation: number): Promise<void> {
231
228
  );
232
229
  }
233
230
 
234
- // Build the dense + sparse vectors for the Qdrant write. Sparse (BM25/TF)
235
- // encoding is computed locally and needs no backend; only the dense vectors
236
- // require `embedWithBackend`, which is unconfigured during the cold-start
237
- // window before a managed-proxy embedding credential is provisioned.
238
- //
239
- // A dense-embed failure is non-fatal to the in-memory cache: the v3 needle
240
- // finder lane and always-candidate skill pinning read skills from `entries`
241
- // / the page index, NOT from Qdrant, so the cache is populated from the
242
- // local catalog regardless of backend state and skills stay discoverable
243
- // from first boot. Only the dense Qdrant upsert is skipped; the managed-
244
- // credential reseed (`maybeReseedCapabilitiesAfterManagedCredential`) and
245
- // the v3 maintain pass backfill the dense vectors once the backend recovers.
231
+ // Embed all content strings in one batched call when there is anything to
232
+ // embed. Skipping the call when `seeds` is empty avoids throwing on an
233
+ // unavailable embedding backend in the all-disabled case, so pruning and
234
+ // cache replacement still run and clear stale state.
246
235
  const nextEntries = new Map<string, SkillEntry>();
247
236
  let denseVectors: number[][] = [];
248
- let denseAvailable = false;
249
- let denseError: unknown = null;
250
237
  let encodeSparse: (
251
238
  input: string,
252
239
  ) => ReturnType<typeof generateSparseEmbedding> = generateSparseEmbedding;
253
240
  if (seeds.length > 0) {
241
+ const embedded = await embedWithBackend(
242
+ config,
243
+ seeds.map((s) => s.content),
244
+ );
245
+ denseVectors = await Promise.all(
246
+ embedded.vectors.map((v) =>
247
+ applyCorrectionIfCalibrated(v, embedded.provider, embedded.model),
248
+ ),
249
+ );
250
+
254
251
  // Skills share the concept-page Qdrant collection, so the sparse vector
255
252
  // must use the same stemmed BM25 encoding the concept-page documents
256
253
  // carry — otherwise the stemmed BM25 query vectors used by callers (see
@@ -266,24 +263,6 @@ async function runSeedV2SkillEntries(generation: number): Promise<void> {
266
263
  b: config.memory.v2.bm25_b,
267
264
  })
268
265
  : generateSparseEmbedding(input);
269
- try {
270
- const embedded = await embedWithBackend(
271
- config,
272
- seeds.map((s) => s.content),
273
- );
274
- denseVectors = await Promise.all(
275
- embedded.vectors.map((v) =>
276
- applyCorrectionIfCalibrated(v, embedded.provider, embedded.model),
277
- ),
278
- );
279
- denseAvailable = true;
280
- } catch (err) {
281
- denseError = err;
282
- log.warn(
283
- { err },
284
- "Embedding backend unavailable — seeding skill cache without dense Qdrant vectors; the needle lane surfaces skills from the cache and the dense lane backfills when the backend recovers",
285
- );
286
- }
287
266
  }
288
267
 
289
268
  if (generation !== requestedSeedGeneration) {
@@ -295,17 +274,7 @@ async function runSeedV2SkillEntries(generation: number): Promise<void> {
295
274
  return;
296
275
  }
297
276
 
298
- // Populate the in-memory cache (and therefore the page index / needle lane)
299
- // from the local catalog regardless of dense availability.
300
- for (const seed of seeds) {
301
- nextEntries.set(seed.id, seed);
302
- }
303
-
304
- // Write Qdrant points only when dense vectors were produced. In the
305
- // degraded (backend-unavailable) path we skip Qdrant mutation entirely —
306
- // both the upsert and the prune below — so we never write half-formed
307
- // points or reconcile the collection against a set we did not persist.
308
- if (seeds.length > 0 && denseAvailable) {
277
+ if (seeds.length > 0) {
309
278
  const now = Date.now();
310
279
  await Promise.all(
311
280
  seeds.map((seed, i) =>
@@ -318,15 +287,16 @@ async function runSeedV2SkillEntries(generation: number): Promise<void> {
318
287
  }),
319
288
  ),
320
289
  );
290
+ for (const seed of seeds) {
291
+ nextEntries.set(seed.id, seed);
292
+ }
321
293
  }
322
294
 
323
- // Prune stale skill slugs. Skip when the catalog is unavailable (empty array
324
- // from network failure or cold cache we cannot enumerate which uninstalled
325
- // catalog skills should exist) OR when dense vectors were not written this
326
- // run (don't reconcile Qdrant against points we did not refresh). The
327
- // `seeds.length === 0` branch still prunes under an available catalog so the
328
- // all-disabled case clears stale rows.
329
- if (catalogAvailable && (denseAvailable || seeds.length === 0)) {
295
+ // Prune stale skill slugs. When the catalog is unavailable (empty array
296
+ // from network failure or cold cache), we cannot enumerate which
297
+ // uninstalled catalog skills should exist, so skip pruning entirely to
298
+ // avoid aggressively removing previously-seeded catalog skill embeddings.
299
+ if (catalogAvailable) {
330
300
  // Tag legacy skill points missing `payload.kind` before pruning so the
331
301
  // kind-scoped prune can see them. Once-per-process; the backfill is
332
302
  // idempotent (server-side `is_empty` filter), so a partial failure
@@ -351,26 +321,19 @@ async function runSeedV2SkillEntries(generation: number): Promise<void> {
351
321
  seeds.map((s) => s.id),
352
322
  { kind: SKILL_PAYLOAD_KIND },
353
323
  );
354
- } else if (!catalogAvailable) {
324
+ } else {
355
325
  log.info(
356
326
  "Catalog unavailable — skipping skill pruning to preserve prior catalog embeddings",
357
327
  );
358
328
  }
359
329
 
360
- // Atomically replace the cache from the freshly enumerated skills. The local
361
- // resolution (`resolveSkillStates`) is authoritative, so a skill the config
362
- // just disabled or removed drops out here even when the remote catalog is
363
- // unavailable. Drop the page-index cache so the next router invocation
364
- // observes the new skill set (skill entries share the unified concept-page
365
- // collection and surface in the same index).
330
+ // Atomically replace the cache only after every step above succeeds.
366
331
  entries = nextEntries;
332
+ // Drop the page-index cache so the next router invocation observes the
333
+ // freshly seeded skill set (skill entries share the unified concept-page
334
+ // collection and surface in the same index).
367
335
  invalidatePageIndex();
368
-
369
- // Surface a dense-embed failure to `throwOnError` callers (the managed-
370
- // credential reseed and the operator reembed route) so the existing retry +
371
- // maintain machinery backfills the dense lane. The in-memory cache is
372
- // already updated above, so the needle lane is fixed regardless.
373
- lastSeedError = denseError;
336
+ lastSeedError = null;
374
337
  } catch (err) {
375
338
  lastSeedError = err;
376
339
  log.warn({ err }, "Failed to seed v2 skill entries");
@@ -27,22 +27,14 @@ mock.module("../copy-composer.js", () => ({
27
27
  composeFallbackCopy: () => composeFallbackReturn,
28
28
  }));
29
29
 
30
- // Stub only getGuardianDelivery; keep the real selectors so this mock is
31
- // harmless if it leaks into destination-resolver.test.ts under a shared run.
32
- const realGuardianReader = await import(
33
- "../../contacts/guardian-delivery-reader.js"
34
- );
35
- mock.module("../../contacts/guardian-delivery-reader.js", () => ({
36
- ...realGuardianReader,
37
- getGuardianDelivery: async () => null,
38
- }));
39
-
40
- // Use the real destination-resolver (DB-free via the local-read stub below)
41
- // so this mock does not leak into destination-resolver.test.ts under a shared
42
- // bun-test invocation. With no guardian, the resolver still yields a vellum
43
- // destination, which is all these tests exercise.
44
- mock.module("../../contacts/contact-store.js", () => ({
45
- findGuardianForChannel: () => null,
30
+ mock.module("../destination-resolver.js", () => ({
31
+ resolveDestinations: (channels: string[]) => {
32
+ const map = new Map<string, ChannelDestination>();
33
+ for (const ch of channels) {
34
+ map.set(ch, { channel: ch as ChannelDestination["channel"] });
35
+ }
36
+ return map;
37
+ },
46
38
  }));
47
39
 
48
40
  mock.module("../conversation-pairing.js", () => ({