@vellumai/assistant 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (692) hide show
  1. package/AGENTS.md +11 -0
  2. package/Dockerfile +5 -4
  3. package/README.md +2 -2
  4. package/docker-entrypoint.sh +16 -0
  5. package/eslint-rules/__tests__/cli-no-daemon-internals.test.ts +420 -0
  6. package/eslint-rules/cli-no-daemon-internals.js +283 -0
  7. package/eslint.config.mjs +12 -0
  8. package/knip.json +2 -1
  9. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +10 -1
  10. package/openapi.yaml +4847 -1698
  11. package/package.json +3 -1
  12. package/scripts/generate-openapi.ts +52 -4
  13. package/scripts/sync-llm-catalog.ts +165 -0
  14. package/scripts/sync-web-search-catalog.ts +107 -0
  15. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +169 -0
  16. package/src/__tests__/agent-loop-override-profile.test.ts +26 -1
  17. package/src/__tests__/anthropic-provider.test.ts +92 -2
  18. package/src/__tests__/app-control-flow.test.ts +7 -0
  19. package/src/__tests__/assistant-events-sse-shed.test.ts +232 -0
  20. package/src/__tests__/avatar-identity-sync.test.ts +87 -0
  21. package/src/__tests__/background-workers-disk-pressure.test.ts +11 -22
  22. package/src/__tests__/btw-routes.test.ts +1 -0
  23. package/src/__tests__/call-site-routing-provider.test.ts +172 -45
  24. package/src/__tests__/cancel-resolves-conversation-key.test.ts +44 -3
  25. package/src/__tests__/channel-policy.test.ts +12 -0
  26. package/src/__tests__/checker.test.ts +89 -0
  27. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +35 -7
  28. package/src/__tests__/compact-event-conversation-id-guard.test.ts +33 -5
  29. package/src/__tests__/compaction-strip-metadata-clear.test.ts +26 -1
  30. package/src/__tests__/config-loader-backfill.test.ts +526 -102
  31. package/src/__tests__/config-loader-corrupt.test.ts +68 -0
  32. package/src/__tests__/config-loader-platform-defaults.test.ts +77 -23
  33. package/src/__tests__/config-schema-cmd.test.ts +63 -29
  34. package/src/__tests__/config-schema.test.ts +14 -3
  35. package/src/__tests__/config-set-platform-guard.test.ts +75 -152
  36. package/src/__tests__/config-set-route.test.ts +198 -0
  37. package/src/__tests__/config-watcher.test.ts +6 -0
  38. package/src/__tests__/contacts-tools.test.ts +51 -199
  39. package/src/__tests__/context-search-agent-protocol.test.ts +21 -2
  40. package/src/__tests__/context-search-agent-runner.test.ts +22 -138
  41. package/src/__tests__/context-search-conversations-source.test.ts +42 -16
  42. package/src/__tests__/context-search-fanout.test.ts +20 -157
  43. package/src/__tests__/context-search-memory-v2-source.test.ts +3 -3
  44. package/src/__tests__/context-search-types.test.ts +7 -2
  45. package/src/__tests__/context-window-manager.test.ts +389 -1
  46. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  47. package/src/__tests__/conversation-crud-inference-profile.test.ts +100 -0
  48. package/src/__tests__/conversation-error.test.ts +38 -0
  49. package/src/__tests__/conversation-fork-crud.test.ts +241 -1
  50. package/src/__tests__/conversation-inference-profile-route.test.ts +14 -14
  51. package/src/__tests__/conversation-init.benchmark.test.ts +1 -0
  52. package/src/__tests__/conversation-lifecycle.test.ts +124 -0
  53. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +100 -1
  54. package/src/__tests__/conversation-process-callsite.test.ts +21 -1
  55. package/src/__tests__/conversation-runtime-assembly.test.ts +4 -4
  56. package/src/__tests__/conversation-slash-commands.test.ts +194 -2
  57. package/src/__tests__/conversation-surfaces-app-control.test.ts +323 -3
  58. package/src/__tests__/credential-security-invariants.test.ts +5 -6
  59. package/src/__tests__/daemon-credential-client.test.ts +56 -1
  60. package/src/__tests__/db-activation-state-fk-cascade.test.ts +132 -0
  61. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +37 -0
  62. package/src/__tests__/db-memory-graph-event-date-repair.test.ts +43 -20
  63. package/src/__tests__/db-proxy-transaction.test.ts +206 -0
  64. package/src/__tests__/external-plugin-loader.test.ts +458 -0
  65. package/src/__tests__/filing-service.test.ts +23 -3
  66. package/src/__tests__/fixtures/mock-chrome-extension.ts +5 -0
  67. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  68. package/src/__tests__/graph-extraction-event-date.test.ts +34 -0
  69. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -8
  70. package/src/__tests__/heartbeat-disk-pressure.test.ts +21 -8
  71. package/src/__tests__/heartbeat-service.test.ts +50 -233
  72. package/src/__tests__/history-repair.test.ts +89 -0
  73. package/src/__tests__/host-app-control-proxy.test.ts +109 -1
  74. package/src/__tests__/host-app-control-routes.test.ts +247 -1
  75. package/src/__tests__/host-browser-proxy.test.ts +416 -20
  76. package/src/__tests__/host-browser-routes.test.ts +325 -33
  77. package/src/__tests__/host-proxy-preactivation.test.ts +211 -0
  78. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +246 -0
  79. package/src/__tests__/inference-profile-reaper.test.ts +154 -0
  80. package/src/__tests__/inference-profile-session-handler.test.ts +398 -0
  81. package/src/__tests__/inference-profile-session-ipc.test.ts +236 -0
  82. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -1
  83. package/src/__tests__/install-skill-routing.test.ts +2 -2
  84. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +15 -0
  85. package/src/__tests__/llm-callsite-catalog.test.ts +20 -1
  86. package/src/__tests__/llm-catalog-parity.test.ts +146 -0
  87. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +188 -0
  88. package/src/__tests__/llm-request-log-source-factory.test.ts +124 -0
  89. package/src/__tests__/llm-resolver.test.ts +46 -0
  90. package/src/__tests__/managed-profile-guard.test.ts +131 -2
  91. package/src/__tests__/mcp-auth-routes.test.ts +1 -0
  92. package/src/__tests__/mcp-cli.test.ts +182 -220
  93. package/src/__tests__/mcp-health-check.test.ts +56 -27
  94. package/src/__tests__/memory-jobs-worker-lanes.test.ts +18 -11
  95. package/src/__tests__/message-complete-display-id.test.ts +175 -0
  96. package/src/__tests__/notification-platform-adapter.test.ts +229 -0
  97. package/src/__tests__/oauth-cli.test.ts +38 -2009
  98. package/src/__tests__/oauth-commands-routes.test.ts +711 -0
  99. package/src/__tests__/oauth-connect-routes.test.ts +174 -11
  100. package/src/__tests__/oauth-providers-routes.test.ts +14 -10
  101. package/src/__tests__/openai-responses-cutover-guard.test.ts +33 -12
  102. package/src/__tests__/openai-responses-provider.test.ts +17 -0
  103. package/src/__tests__/plugin-bootstrap.test.ts +31 -2
  104. package/src/__tests__/plugin-route-contribution.test.ts +31 -3
  105. package/src/__tests__/plugin-tool-contribution.test.ts +31 -3
  106. package/src/__tests__/plugin-types.test.ts +13 -11
  107. package/src/__tests__/process-message-background-slack.test.ts +46 -0
  108. package/src/__tests__/profile-entry-status.test.ts +43 -0
  109. package/src/__tests__/provider-managed-proxy-integration.test.ts +12 -4
  110. package/src/__tests__/provider-registry-ollama.test.ts +12 -4
  111. package/src/__tests__/provider-send-message-override-profile.test.ts +10 -4
  112. package/src/__tests__/relay-server.test.ts +118 -0
  113. package/src/__tests__/retry-thinking-tool-choice.test.ts +15 -0
  114. package/src/__tests__/schedule-retry.test.ts +56 -4
  115. package/src/__tests__/schedule-routes.test.ts +104 -0
  116. package/src/__tests__/scheduler-disk-pressure.test.ts +0 -4
  117. package/src/__tests__/scheduler-recurrence.test.ts +87 -34
  118. package/src/__tests__/scheduler-reuse-conversation.test.ts +161 -5
  119. package/src/__tests__/scheduler-wake.test.ts +0 -63
  120. package/src/__tests__/secret-allowlist.test.ts +1 -0
  121. package/src/__tests__/secret-routes-managed-proxy.test.ts +12 -4
  122. package/src/__tests__/shell-credential-ref.test.ts +95 -3
  123. package/src/__tests__/shell-tool-proxy-mode.test.ts +14 -0
  124. package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
  125. package/src/__tests__/skill-load-tool.test.ts +2 -4
  126. package/src/__tests__/subagent-call-site-routing.test.ts +78 -16
  127. package/src/__tests__/suggestion-routes.test.ts +3 -3
  128. package/src/__tests__/sync-message-contract.test.ts +63 -0
  129. package/src/__tests__/task-scheduler.test.ts +88 -23
  130. package/src/__tests__/update-bulletin-job.test.ts +96 -193
  131. package/src/__tests__/usage-cli.test.ts +11 -73
  132. package/src/__tests__/user-plugin-loader.test.ts +145 -0
  133. package/src/__tests__/vercel-config.test.ts +168 -0
  134. package/src/__tests__/web-search-catalog-parity.test.ts +86 -0
  135. package/src/__tests__/web-search.test.ts +303 -2
  136. package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +1 -21
  137. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +58 -0
  138. package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +53 -20
  139. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +191 -0
  140. package/src/__tests__/workspace-migration-076-drop-services-inference-mode.test.ts +211 -0
  141. package/src/__tests__/workspace-migration-077-seed-memory-router-callsite.test.ts +174 -0
  142. package/src/__tests__/workspace-migration-079-home-feed-notification-only.test.ts +323 -0
  143. package/src/__tests__/workspace-migration-080-restrict-vercel-api-token-metadata.test.ts +299 -0
  144. package/src/__tests__/workspace-migration-081-backfill-bash-allowed-tools.test.ts +410 -0
  145. package/src/__tests__/workspace-migration-082-backfill-managed-profile-labels.test.ts +268 -0
  146. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +3 -3
  147. package/src/__tests__/workspace-release-notes-feature-flag-guard.test.ts +115 -0
  148. package/src/acp/__tests__/helpers/which-stub.ts +4 -2
  149. package/src/acp/resolve-agent.test.ts +25 -0
  150. package/src/acp/resolve-agent.ts +13 -2
  151. package/src/acp/session-manager.ts +14 -0
  152. package/src/approvals/guardian-request-resolvers.ts +32 -87
  153. package/src/calls/relay-server.ts +35 -0
  154. package/src/calls/relay-setup-router.ts +36 -0
  155. package/src/calls/types.ts +1 -0
  156. package/src/calls/voice-session-bridge.ts +23 -4
  157. package/src/channels/config.ts +14 -1
  158. package/src/channels/types.ts +1 -0
  159. package/src/cli/AGENTS.md +164 -4
  160. package/src/cli/__tests__/notifications.test.ts +54 -0
  161. package/src/cli/commands/__tests__/avatar.test.ts +540 -0
  162. package/src/cli/commands/__tests__/backup.test.ts +236 -776
  163. package/src/cli/commands/__tests__/cache.test.ts +1 -1
  164. package/src/cli/commands/__tests__/changelog.test.ts +593 -0
  165. package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +503 -0
  166. package/src/cli/commands/__tests__/conversations-import.test.ts +515 -0
  167. package/src/cli/commands/__tests__/domain-register.test.ts +140 -167
  168. package/src/cli/commands/__tests__/domain-status.test.ts +137 -76
  169. package/src/cli/commands/__tests__/email-attachment.test.ts +314 -337
  170. package/src/cli/commands/__tests__/email-core.test.ts +579 -0
  171. package/src/cli/commands/__tests__/image-generation.test.ts +87 -824
  172. package/src/cli/commands/__tests__/inference-send.test.ts +30 -266
  173. package/src/cli/commands/__tests__/inference-session.test.ts +423 -0
  174. package/src/cli/commands/__tests__/memory-v2.test.ts +81 -110
  175. package/src/cli/commands/__tests__/skills.test.ts +563 -0
  176. package/src/cli/commands/__tests__/status.test.ts +249 -0
  177. package/src/cli/commands/__tests__/stt.test.ts +320 -0
  178. package/src/cli/commands/__tests__/tts-synthesize.test.ts +4 -603
  179. package/src/cli/commands/__tests__/tts.test.ts +321 -0
  180. package/src/cli/commands/__tests__/webhooks.test.ts +86 -511
  181. package/src/cli/commands/attachment.ts +8 -3
  182. package/src/cli/commands/audit.ts +95 -64
  183. package/src/cli/commands/auth.ts +61 -58
  184. package/src/cli/commands/avatar.ts +276 -390
  185. package/src/cli/commands/backup.ts +409 -505
  186. package/src/cli/commands/bash.ts +9 -5
  187. package/src/cli/commands/browser.ts +28 -9
  188. package/src/cli/commands/cache.ts +9 -4
  189. package/src/cli/commands/changelog.ts +414 -0
  190. package/src/cli/commands/channel-verification-sessions.ts +238 -317
  191. package/src/cli/commands/clients.ts +8 -3
  192. package/src/cli/commands/completions.ts +9 -9
  193. package/src/cli/commands/config.ts +102 -72
  194. package/src/cli/commands/contacts.ts +575 -696
  195. package/src/cli/commands/conversations-defer.ts +17 -69
  196. package/src/cli/commands/conversations-import.ts +90 -253
  197. package/src/cli/commands/conversations.ts +346 -436
  198. package/src/cli/commands/credential-execution.ts +9 -6
  199. package/src/cli/commands/credentials.ts +456 -736
  200. package/src/cli/commands/domain.ts +128 -206
  201. package/src/cli/commands/email.ts +606 -794
  202. package/src/cli/commands/gateway.ts +8 -1
  203. package/src/cli/commands/image-generation.ts +157 -205
  204. package/src/cli/commands/inference-providers.ts +352 -0
  205. package/src/cli/commands/inference-session.ts +415 -0
  206. package/src/cli/commands/inference.ts +87 -65
  207. package/src/cli/commands/keys.ts +8 -3
  208. package/src/cli/commands/mcp.ts +103 -287
  209. package/src/cli/commands/memory-v2.ts +162 -516
  210. package/src/cli/commands/notifications.ts +33 -7
  211. package/src/cli/commands/oauth/apps.ts +292 -261
  212. package/src/cli/commands/oauth/connect.ts +176 -297
  213. package/src/cli/commands/oauth/disconnect.ts +16 -215
  214. package/src/cli/commands/oauth/index.ts +49 -45
  215. package/src/cli/commands/oauth/mode.ts +43 -199
  216. package/src/cli/commands/oauth/ping.ts +17 -125
  217. package/src/cli/commands/oauth/providers.ts +732 -921
  218. package/src/cli/commands/oauth/request.ts +60 -350
  219. package/src/cli/commands/oauth/shared.ts +11 -121
  220. package/src/cli/commands/oauth/status.ts +31 -121
  221. package/src/cli/commands/oauth/token.ts +13 -55
  222. package/src/cli/commands/pending.ts +19 -10
  223. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +133 -183
  224. package/src/cli/commands/platform/__tests__/connect.test.ts +66 -181
  225. package/src/cli/commands/platform/__tests__/disconnect.test.ts +71 -227
  226. package/src/cli/commands/platform/__tests__/status.test.ts +169 -287
  227. package/src/cli/commands/platform/connect.ts +16 -80
  228. package/src/cli/commands/platform/disconnect.ts +14 -112
  229. package/src/cli/commands/platform/index.ts +177 -246
  230. package/src/cli/commands/routes.ts +153 -336
  231. package/src/cli/commands/sequence.ts +316 -360
  232. package/src/cli/commands/skills.ts +449 -671
  233. package/src/cli/commands/status.ts +58 -37
  234. package/src/cli/commands/stt.ts +94 -262
  235. package/src/cli/commands/task.ts +14 -40
  236. package/src/cli/commands/trust.ts +8 -3
  237. package/src/cli/commands/tts.ts +162 -167
  238. package/src/cli/commands/ui.ts +35 -42
  239. package/src/cli/commands/usage.ts +188 -126
  240. package/src/cli/commands/watchers.ts +8 -3
  241. package/src/cli/commands/webhooks.ts +99 -193
  242. package/src/cli/lib/__tests__/register-command.test.ts +85 -0
  243. package/src/cli/lib/daemon-credential-client.ts +4 -5
  244. package/src/cli/lib/nested-value.ts +44 -0
  245. package/src/cli/lib/open-browser.ts +36 -0
  246. package/src/cli/lib/register-command.ts +19 -0
  247. package/src/cli/lib/time-ago.ts +34 -0
  248. package/src/cli/program.ts +2 -4
  249. package/src/cli/utils/__tests__/conversation-id.test.ts +66 -0
  250. package/src/cli/utils/__tests__/parse-duration.test.ts +49 -0
  251. package/src/cli/utils/conversation-id.ts +30 -0
  252. package/src/cli/utils/parse-duration.ts +41 -0
  253. package/src/config/acp-defaults.test.ts +5 -1
  254. package/src/config/acp-defaults.ts +11 -4
  255. package/src/config/bundled-skills/acp/TOOLS.json +2 -2
  256. package/src/config/bundled-skills/app-control/TOOLS.json +32 -0
  257. package/src/config/bundled-skills/contacts/SKILL.md +12 -45
  258. package/src/config/bundled-skills/contacts/TOOLS.json +0 -57
  259. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +0 -12
  260. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +0 -58
  261. package/src/config/bundled-tool-registry.ts +0 -2
  262. package/src/config/feature-flag-registry.json +16 -0
  263. package/src/config/llm-resolver.ts +16 -1
  264. package/src/config/loader.ts +76 -14
  265. package/src/config/raw-config-utils.ts +2 -30
  266. package/src/config/schema.ts +4 -0
  267. package/src/config/schemas/__tests__/memory-v2.test.ts +49 -0
  268. package/src/config/schemas/call-site-catalog.ts +29 -7
  269. package/src/config/schemas/llm-request-logs.ts +57 -0
  270. package/src/config/schemas/llm.ts +52 -2
  271. package/src/config/schemas/memory-retrospective.ts +48 -0
  272. package/src/config/schemas/memory-v2.ts +32 -1
  273. package/src/config/schemas/memory.ts +4 -0
  274. package/src/config/schemas/services.ts +15 -12
  275. package/src/config/seed-inference-profiles.ts +195 -134
  276. package/src/contacts/contact-store.ts +0 -61
  277. package/src/context/window-manager.ts +191 -5
  278. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +79 -0
  279. package/src/daemon/__tests__/conversation-tool-setup.test.ts +109 -4
  280. package/src/daemon/__tests__/daemon-skill-host.test.ts +10 -4
  281. package/src/daemon/approval-generators.ts +23 -29
  282. package/src/daemon/config-watcher.ts +2 -0
  283. package/src/daemon/conversation-agent-loop-handlers.ts +24 -0
  284. package/src/daemon/conversation-agent-loop.ts +127 -97
  285. package/src/daemon/conversation-error.ts +21 -0
  286. package/src/daemon/conversation-lifecycle.ts +46 -5
  287. package/src/daemon/conversation-process.ts +36 -19
  288. package/src/daemon/conversation-runtime-assembly.ts +14 -5
  289. package/src/daemon/conversation-slash.ts +175 -23
  290. package/src/daemon/conversation-store.ts +17 -10
  291. package/src/daemon/conversation-surfaces.ts +76 -12
  292. package/src/daemon/conversation-tool-setup.ts +24 -14
  293. package/src/daemon/conversation.ts +48 -9
  294. package/src/daemon/external-plugins-bootstrap.ts +18 -8
  295. package/src/daemon/guardian-action-generators.ts +7 -22
  296. package/src/daemon/handlers/config-model.ts +8 -126
  297. package/src/daemon/handlers/config-slack-channel.ts +10 -7
  298. package/src/daemon/handlers/config-vercel.ts +3 -1
  299. package/src/daemon/handlers/skills.ts +84 -5
  300. package/src/daemon/history-repair.ts +33 -6
  301. package/src/daemon/host-app-control-proxy.ts +44 -19
  302. package/src/daemon/host-bash-proxy.ts +85 -158
  303. package/src/daemon/host-browser-proxy.ts +96 -35
  304. package/src/daemon/host-proxy-base.ts +13 -1
  305. package/src/daemon/host-proxy-preactivation.ts +25 -1
  306. package/src/daemon/identity-helpers.ts +19 -0
  307. package/src/daemon/lifecycle.ts +42 -43
  308. package/src/daemon/meet-host-supervisor.ts +15 -15
  309. package/src/daemon/memory-v2-startup.ts +9 -2
  310. package/src/daemon/message-protocol.ts +6 -0
  311. package/src/daemon/message-types/bookmarks.ts +18 -0
  312. package/src/daemon/message-types/conversations.ts +12 -9
  313. package/src/daemon/message-types/messages.ts +9 -1
  314. package/src/daemon/message-types/sync.ts +60 -0
  315. package/src/daemon/pkb-reminder-builder.test.ts +54 -13
  316. package/src/daemon/pkb-reminder-builder.ts +21 -7
  317. package/src/daemon/process-message.ts +56 -23
  318. package/src/daemon/server.ts +23 -18
  319. package/src/daemon/shutdown-handlers.ts +0 -2
  320. package/src/daemon/tool-setup-types.ts +9 -0
  321. package/src/daemon/tool-side-effects.ts +6 -4
  322. package/src/daemon/wake-target-adapter.ts +11 -0
  323. package/src/export/transcript-formatter.ts +61 -2
  324. package/src/filing/filing-service.ts +40 -53
  325. package/src/heartbeat/__tests__/heartbeat-service.test.ts +359 -0
  326. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  327. package/src/heartbeat/heartbeat-service.ts +148 -127
  328. package/src/home/__tests__/feed-types.test.ts +63 -131
  329. package/src/home/__tests__/feed-writer.test.ts +77 -278
  330. package/src/home/__tests__/post-connect-feed.test.ts +9 -12
  331. package/src/home/feed-types.ts +19 -73
  332. package/src/home/feed-writer.ts +25 -156
  333. package/src/home/post-connect-feed.ts +1 -3
  334. package/src/ipc/__tests__/cli-ipc.test.ts +2 -0
  335. package/src/ipc/__tests__/email-ipc.test.ts +506 -0
  336. package/src/ipc/__tests__/exit-helper.test.ts +104 -0
  337. package/src/ipc/__tests__/streaming-client.test.ts +237 -0
  338. package/src/ipc/__tests__/streaming-framing.test.ts +142 -0
  339. package/src/ipc/assistant-server.ts +55 -6
  340. package/src/ipc/cli-client.ts +370 -50
  341. package/src/ipc/routes/db-proxy-transaction.ts +151 -0
  342. package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +60 -0
  343. package/src/ipc/skill-routes/events.ts +30 -3
  344. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +46 -0
  345. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +1 -0
  346. package/src/live-voice/live-voice-session-manager.ts +11 -4
  347. package/src/live-voice/live-voice-session.ts +14 -6
  348. package/src/memory/__tests__/bookmark-crud.test.ts +258 -0
  349. package/src/memory/__tests__/bookmark-schema.test.ts +181 -0
  350. package/src/memory/__tests__/conversation-types.test.ts +36 -0
  351. package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +130 -0
  352. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +177 -0
  353. package/src/memory/__tests__/memory-retrospective-job.test.ts +328 -0
  354. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +213 -0
  355. package/src/memory/__tests__/memory-retrospective-trigger-check.test.ts +90 -0
  356. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +69 -0
  357. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +3 -0
  358. package/src/memory/bookmark-crud.ts +179 -0
  359. package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +31 -9
  360. package/src/memory/context-search/agent-protocol.ts +5 -1
  361. package/src/memory/context-search/agent-runner.ts +60 -85
  362. package/src/memory/context-search/limits.ts +1 -4
  363. package/src/memory/context-search/search.ts +23 -113
  364. package/src/memory/context-search/sources/conversations.ts +18 -6
  365. package/src/memory/context-search/sources/memory-v2.ts +39 -14
  366. package/src/memory/context-search/sources/memory.ts +7 -0
  367. package/src/memory/context-search/sources/workspace.ts +13 -10
  368. package/src/memory/context-search/types.ts +1 -1
  369. package/src/memory/conversation-bootstrap.ts +11 -0
  370. package/src/memory/conversation-crud.ts +312 -10
  371. package/src/memory/conversation-queries.ts +9 -5
  372. package/src/memory/conversation-title-service.ts +1 -0
  373. package/src/memory/conversation-types.ts +16 -0
  374. package/src/memory/db-init.ts +14 -0
  375. package/src/memory/embedding-backend.ts +2 -1
  376. package/src/memory/embedding-runtime-manager.ts +1 -2
  377. package/src/memory/graph/__tests__/remember-description.test.ts +55 -0
  378. package/src/memory/graph/conversation-graph-memory.ts +76 -5
  379. package/src/memory/graph/extraction.ts +4 -0
  380. package/src/memory/graph/graph-memory-state-store.ts +16 -3
  381. package/src/memory/graph/tool-handlers.ts +17 -7
  382. package/src/memory/graph/tools.ts +44 -5
  383. package/src/memory/indexer.ts +17 -0
  384. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +13 -15
  385. package/src/memory/jobs/embed-concept-page.ts +45 -9
  386. package/src/memory/jobs-store.ts +51 -1
  387. package/src/memory/jobs-worker.ts +52 -3
  388. package/src/memory/llm-request-log-source-clickhouse.ts +317 -0
  389. package/src/memory/llm-request-log-source-local.ts +26 -0
  390. package/src/memory/llm-request-log-source.ts +97 -0
  391. package/src/memory/llm-request-log-store.ts +1 -1
  392. package/src/memory/memory-retrospective-constants.ts +13 -0
  393. package/src/memory/memory-retrospective-enqueue.ts +114 -0
  394. package/src/memory/memory-retrospective-job.ts +351 -0
  395. package/src/memory/memory-retrospective-startup-cleanup.ts +108 -0
  396. package/src/memory/memory-retrospective-state.ts +162 -0
  397. package/src/memory/memory-retrospective-trigger-check.ts +91 -0
  398. package/src/memory/memory-v2-activation-log-store.ts +49 -5
  399. package/src/memory/memory-v2-concept-frequency.ts +4 -0
  400. package/src/memory/message-content.ts +38 -1
  401. package/src/memory/migrations/227-add-conversation-inference-profile.ts +6 -1
  402. package/src/memory/migrations/228-rename-inference-profile-snake-case.ts +20 -7
  403. package/src/memory/migrations/229-delete-private-conversations.test.ts +70 -1
  404. package/src/memory/migrations/229-delete-private-conversations.ts +12 -0
  405. package/src/memory/migrations/231-repair-memory-graph-event-dates.ts +16 -2
  406. package/src/memory/migrations/240-conversation-inference-profile-session.ts +25 -0
  407. package/src/memory/migrations/241-activation-state-fk-cascade.ts +50 -0
  408. package/src/memory/migrations/242-message-bookmarks.ts +38 -0
  409. package/src/memory/migrations/243-provider-connections.ts +68 -0
  410. package/src/memory/migrations/244-provider-connection-status-label.ts +23 -0
  411. package/src/memory/migrations/245-memory-retrospective-state.ts +36 -0
  412. package/src/memory/migrations/246-backfill-provider-connection-label.ts +81 -0
  413. package/src/memory/migrations/__tests__/244-provider-connection-status-label.test.ts +84 -0
  414. package/src/memory/migrations/__tests__/245-memory-retrospective-state.test.ts +125 -0
  415. package/src/memory/migrations/__tests__/246-backfill-provider-connection-label.test.ts +192 -0
  416. package/src/memory/migrations/index.ts +7 -0
  417. package/src/memory/published-pages-store.ts +16 -0
  418. package/src/memory/schema/bookmarks.ts +38 -0
  419. package/src/memory/schema/conversations.ts +2 -0
  420. package/src/memory/schema/index.ts +2 -0
  421. package/src/memory/schema/inference.ts +29 -0
  422. package/src/memory/schema/memory-core.ts +9 -0
  423. package/src/memory/search/semantic.ts +1 -4
  424. package/src/memory/v2/__tests__/__snapshots__/prompts-router.test.ts.snap +27 -0
  425. package/src/memory/v2/__tests__/activation-store.test.ts +5 -5
  426. package/src/memory/v2/__tests__/activation.test.ts +11 -4
  427. package/src/memory/v2/__tests__/backfill-jobs.test.ts +38 -21
  428. package/src/memory/v2/__tests__/consolidation-job.test.ts +123 -135
  429. package/src/memory/v2/__tests__/edge-index.test.ts +1 -1
  430. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +111 -0
  431. package/src/memory/v2/__tests__/injection.test.ts +628 -10
  432. package/src/memory/v2/__tests__/migration.test.ts +7 -3
  433. package/src/memory/v2/__tests__/page-index.test.ts +277 -0
  434. package/src/memory/v2/__tests__/page-store.test.ts +14 -1
  435. package/src/memory/v2/__tests__/prompts-router.test.ts +257 -0
  436. package/src/memory/v2/__tests__/qdrant.test.ts +72 -0
  437. package/src/memory/v2/__tests__/reranker.test.ts +4 -4
  438. package/src/memory/v2/__tests__/router.test.ts +516 -0
  439. package/src/memory/v2/__tests__/sim.test.ts +45 -1
  440. package/src/memory/v2/__tests__/skill-store.test.ts +58 -3
  441. package/src/memory/v2/__tests__/static-context.test.ts +7 -22
  442. package/src/memory/v2/__tests__/sweep-job.test.ts +95 -0
  443. package/src/memory/v2/activation-store.ts +34 -5
  444. package/src/memory/v2/activation.ts +40 -27
  445. package/src/memory/v2/backfill-jobs.ts +17 -84
  446. package/src/memory/v2/consolidation-job.ts +85 -78
  447. package/src/memory/v2/frontmatter-sweep.ts +91 -0
  448. package/src/memory/v2/injection.ts +440 -109
  449. package/src/memory/v2/migration.ts +117 -20
  450. package/src/memory/v2/page-index.ts +191 -0
  451. package/src/memory/v2/page-store.ts +3 -0
  452. package/src/memory/v2/prompts/consolidation.ts +9 -7
  453. package/src/memory/v2/prompts/router.ts +192 -0
  454. package/src/memory/v2/qdrant.ts +100 -87
  455. package/src/memory/v2/reranker.ts +14 -7
  456. package/src/memory/v2/router.ts +322 -0
  457. package/src/memory/v2/sim.ts +25 -12
  458. package/src/memory/v2/skill-store.ts +118 -29
  459. package/src/memory/v2/static-context.ts +16 -9
  460. package/src/memory/v2/sweep-job.ts +122 -96
  461. package/src/memory/v2/types.ts +10 -6
  462. package/src/memory/validation.ts +13 -0
  463. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +182 -0
  464. package/src/notifications/__tests__/home-feed-side-effect.test.ts +199 -0
  465. package/src/notifications/__tests__/signal-registry.test.ts +17 -0
  466. package/src/notifications/adapters/platform.ts +171 -0
  467. package/src/notifications/conversation-pairing.ts +2 -2
  468. package/src/notifications/copy-composer.ts +15 -0
  469. package/src/notifications/destination-resolver.ts +21 -0
  470. package/src/notifications/emit-signal.ts +28 -1
  471. package/src/notifications/home-feed-side-effect.ts +111 -0
  472. package/src/notifications/signal.ts +5 -0
  473. package/src/permissions/checker.ts +12 -0
  474. package/src/permissions/ipc-risk-types.ts +2 -0
  475. package/src/plugin-api/index.ts +13 -0
  476. package/src/plugin-api/package.json +12 -0
  477. package/src/plugin-api/types.ts +62 -0
  478. package/src/plugins/defaults/injectors.ts +19 -3
  479. package/src/plugins/external-plugin-loader.ts +294 -0
  480. package/src/plugins/types.ts +46 -30
  481. package/src/plugins/user-loader.ts +64 -41
  482. package/src/proactive-artifact/job.test.ts +12 -4
  483. package/src/proactive-artifact/job.ts +4 -0
  484. package/src/proactive-artifact/trigger-state.test.ts +9 -0
  485. package/src/proactive-artifact/trigger-state.ts +4 -0
  486. package/src/prompts/__tests__/system-prompt.test.ts +105 -0
  487. package/src/prompts/system-prompt.ts +22 -1
  488. package/src/prompts/update-bulletin-job.ts +61 -73
  489. package/src/providers/__tests__/dispatch-connection-routing.test.ts +279 -0
  490. package/src/providers/__tests__/inference.test.ts +288 -0
  491. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  492. package/src/providers/__tests__/provider-secret-catalog.test.ts +6 -0
  493. package/src/providers/__tests__/retry-callsite.test.ts +14 -32
  494. package/src/providers/__tests__/satellite-connection-routing.test.ts +510 -0
  495. package/src/providers/__tests__/search-provider-catalog.test.ts +80 -0
  496. package/src/providers/anthropic/client.ts +95 -26
  497. package/src/providers/call-site-routing.ts +94 -16
  498. package/src/providers/connection-resolution.ts +163 -0
  499. package/src/providers/inference/__tests__/connections-status-label.test.ts +250 -0
  500. package/src/providers/inference/adapter-factory.ts +173 -0
  501. package/src/providers/inference/auth.ts +112 -0
  502. package/src/providers/inference/backfill.ts +196 -0
  503. package/src/providers/inference/connections.ts +356 -0
  504. package/src/providers/inference/resolve-auth.ts +65 -0
  505. package/src/providers/model-catalog.ts +104 -6
  506. package/src/providers/openai/responses-provider.ts +4 -2
  507. package/src/providers/provider-env-vars.ts +17 -7
  508. package/src/providers/provider-secret-catalog.ts +49 -30
  509. package/src/providers/provider-send-message.ts +41 -20
  510. package/src/providers/registry.ts +143 -159
  511. package/src/providers/retry.ts +18 -10
  512. package/src/providers/search-provider-catalog.ts +121 -0
  513. package/src/runtime/AGENTS.md +18 -5
  514. package/src/runtime/__tests__/background-job-runner.test.ts +357 -0
  515. package/src/runtime/__tests__/pre-first-message-gate.test.ts +82 -0
  516. package/src/runtime/actor-trust-resolver.ts +32 -10
  517. package/src/runtime/agent-wake.ts +35 -6
  518. package/src/runtime/assistant-event-hub.ts +3 -85
  519. package/src/runtime/auth/route-policy.ts +303 -8
  520. package/src/runtime/auth/same-actor.ts +2 -0
  521. package/src/runtime/background-job-runner.ts +339 -0
  522. package/src/runtime/btw-sidechain.ts +1 -0
  523. package/src/runtime/http-router.ts +36 -1
  524. package/src/runtime/http-server.ts +31 -5
  525. package/src/runtime/http-types.ts +2 -0
  526. package/src/runtime/middleware/__tests__/request-logger.test.ts +162 -0
  527. package/src/runtime/middleware/request-logger.ts +62 -1
  528. package/src/runtime/pre-first-message-gate.ts +83 -0
  529. package/src/runtime/routes/__tests__/backup-routes.test.ts +8 -1
  530. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +251 -0
  531. package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +142 -0
  532. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +315 -0
  533. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +189 -0
  534. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +15 -136
  535. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +736 -0
  536. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +4 -4
  537. package/src/runtime/routes/__tests__/stt-routes.test.ts +5 -1
  538. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +384 -0
  539. package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
  540. package/src/runtime/routes/acp-routes.ts +10 -8
  541. package/src/runtime/routes/app-management-routes.ts +228 -3
  542. package/src/runtime/routes/approval-routes.ts +0 -18
  543. package/src/runtime/routes/audit-routes.ts +43 -0
  544. package/src/runtime/routes/auth-routes.ts +72 -0
  545. package/src/runtime/routes/avatar-routes.ts +273 -20
  546. package/src/runtime/routes/backup-routes.ts +406 -2
  547. package/src/runtime/routes/bookmark-routes.ts +154 -0
  548. package/src/runtime/routes/channel-verification-routes.ts +2 -1
  549. package/src/runtime/routes/contact-routes.ts +0 -160
  550. package/src/runtime/routes/conversation-cli-routes.ts +192 -0
  551. package/src/runtime/routes/conversation-management-routes.ts +30 -43
  552. package/src/runtime/routes/conversation-query-routes.ts +334 -86
  553. package/src/runtime/routes/conversation-routes.ts +31 -10
  554. package/src/runtime/routes/conversations-import-routes.ts +229 -0
  555. package/src/runtime/routes/credential-routes.ts +540 -0
  556. package/src/runtime/routes/debug-routes.ts +2 -2
  557. package/src/runtime/routes/document-pdf-renderer.ts +5 -1
  558. package/src/runtime/routes/domain-routes.ts +167 -0
  559. package/src/runtime/routes/email-routes.ts +603 -0
  560. package/src/runtime/routes/errors.ts +2 -2
  561. package/src/runtime/routes/events-routes.ts +192 -0
  562. package/src/runtime/routes/home-feed-routes.ts +6 -78
  563. package/src/runtime/routes/host-app-control-routes.ts +44 -2
  564. package/src/runtime/routes/host-browser-routes.ts +103 -22
  565. package/src/runtime/routes/http-adapter.ts +2 -0
  566. package/src/runtime/routes/identity-routes.ts +5 -0
  567. package/src/runtime/routes/image-generation-routes.ts +99 -0
  568. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +137 -1
  569. package/src/runtime/routes/inbound-stages/background-dispatch.ts +87 -7
  570. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +156 -0
  571. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +22 -4
  572. package/src/runtime/routes/index.ts +36 -0
  573. package/src/runtime/routes/inference-profile-session-handler.ts +312 -0
  574. package/src/runtime/routes/inference-profile-session-reaper.ts +98 -0
  575. package/src/runtime/routes/inference-profile-session-routes.ts +146 -0
  576. package/src/runtime/routes/inference-provider-connection-routes.ts +317 -0
  577. package/src/runtime/routes/inference-send-routes.ts +115 -0
  578. package/src/runtime/routes/integrations/twilio.ts +1 -0
  579. package/src/runtime/routes/mcp-auth-routes.ts +283 -9
  580. package/src/runtime/routes/memory-v2-routes.ts +13 -398
  581. package/src/runtime/routes/notification-routes.ts +2 -0
  582. package/src/runtime/routes/oauth-apps.ts +112 -7
  583. package/src/runtime/routes/oauth-commands-routes.ts +1007 -0
  584. package/src/runtime/routes/oauth-connect-routes.ts +67 -5
  585. package/src/runtime/routes/oauth-providers.ts +298 -8
  586. package/src/runtime/routes/platform-routes.ts +336 -0
  587. package/src/runtime/routes/playground/inject-failures.ts +2 -1
  588. package/src/runtime/routes/playground/reset-circuit.ts +2 -1
  589. package/src/runtime/routes/playground/state.ts +2 -1
  590. package/src/runtime/routes/publish-routes.ts +221 -0
  591. package/src/runtime/routes/schedule-routes.ts +82 -0
  592. package/src/runtime/routes/sequence-routes.ts +291 -0
  593. package/src/runtime/routes/settings-routes.ts +2 -10
  594. package/src/runtime/routes/skills-routes.ts +31 -1
  595. package/src/runtime/routes/stt-routes.ts +240 -3
  596. package/src/runtime/routes/surface-action-routes.ts +43 -7
  597. package/src/runtime/routes/tts-routes.ts +67 -0
  598. package/src/runtime/routes/types.ts +32 -0
  599. package/src/runtime/routes/user-routes-cli.ts +243 -0
  600. package/src/runtime/routes/webhook-routes.ts +165 -0
  601. package/src/runtime/sync/resource-sync-events.ts +25 -0
  602. package/src/runtime/sync/sync-publisher.test.ts +105 -0
  603. package/src/runtime/sync/sync-publisher.ts +21 -0
  604. package/src/schedule/scheduler.ts +200 -123
  605. package/src/security/__tests__/provider-key-env-fallback.test.ts +12 -6
  606. package/src/security/secret-patterns.ts +3 -0
  607. package/src/sequence/engine.ts +38 -40
  608. package/src/subagent/manager.ts +20 -15
  609. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +206 -0
  610. package/src/tools/browser/browser-execution.ts +15 -4
  611. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +174 -0
  612. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +16 -13
  613. package/src/tools/browser/cdp-client/extension-cdp-client.ts +24 -1
  614. package/src/tools/browser/cdp-client/factory.ts +66 -5
  615. package/src/tools/browser/runtime-check.ts +77 -0
  616. package/src/tools/memory/register.test.ts +3 -3
  617. package/src/tools/memory/register.ts +9 -1
  618. package/src/tools/network/__tests__/web-search.test.ts +156 -0
  619. package/src/tools/network/web-search.ts +280 -37
  620. package/src/tools/permission-checker.ts +13 -5
  621. package/src/tools/subagent/spawn.ts +3 -3
  622. package/src/tools/terminal/shell.ts +44 -0
  623. package/src/usage/attribution.ts +3 -2
  624. package/src/util/pricing.ts +86 -160
  625. package/src/watcher/__tests__/engine.test.ts +301 -0
  626. package/src/watcher/constants.ts +7 -0
  627. package/src/watcher/engine.ts +90 -90
  628. package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +6 -9
  629. package/src/workspace/migrations/054-seed-recall-callsite.ts +10 -1
  630. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +28 -4
  631. package/src/workspace/migrations/069-seed-onboarding-threads.ts +8 -2
  632. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +104 -0
  633. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +93 -0
  634. package/src/workspace/migrations/074-drop-deprecated-secret-detection-keys.ts +117 -0
  635. package/src/workspace/migrations/075-memory-v2-bm25-b-default-reembed.ts +61 -0
  636. package/src/workspace/migrations/076-drop-services-inference-mode.ts +62 -0
  637. package/src/workspace/migrations/077-seed-memory-router-callsite.ts +89 -0
  638. package/src/workspace/migrations/078-release-notes-tavily-web-search.ts +66 -0
  639. package/src/workspace/migrations/079-home-feed-notification-only.ts +197 -0
  640. package/src/workspace/migrations/080-restrict-vercel-api-token-metadata.ts +182 -0
  641. package/src/workspace/migrations/081-backfill-bash-allowed-tools-for-injection-credentials.ts +160 -0
  642. package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +154 -0
  643. package/src/workspace/migrations/registry.ts +22 -0
  644. package/src/workspace/migrations/runner.ts +13 -2
  645. package/src/workspace/migrations/types.ts +13 -3
  646. package/src/workspace/provider-commit-message-generator.ts +3 -2
  647. package/src/__tests__/context-search-pkb-source.test.ts +0 -498
  648. package/src/__tests__/credentials-cli.test.ts +0 -1225
  649. package/src/__tests__/memory-admin-recall.test.ts +0 -213
  650. package/src/approvals/__tests__/guardian-feed-event.test.ts +0 -303
  651. package/src/cli/commands/__tests__/email-download.test.ts +0 -260
  652. package/src/cli/commands/__tests__/email-list.test.ts +0 -216
  653. package/src/cli/commands/__tests__/email-register.test.ts +0 -186
  654. package/src/cli/commands/__tests__/email-send.test.ts +0 -416
  655. package/src/cli/commands/__tests__/email-status.test.ts +0 -185
  656. package/src/cli/commands/__tests__/email-unregister.test.ts +0 -168
  657. package/src/cli/commands/__tests__/routes.test.ts +0 -562
  658. package/src/cli/commands/__tests__/stt-transcribe.test.ts +0 -454
  659. package/src/cli/commands/autonomy.ts +0 -365
  660. package/src/cli/commands/memory.ts +0 -424
  661. package/src/cli/commands/oauth/__tests__/connect.test.ts +0 -947
  662. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +0 -686
  663. package/src/cli/commands/oauth/__tests__/mode.test.ts +0 -632
  664. package/src/cli/commands/oauth/__tests__/ping.test.ts +0 -631
  665. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +0 -573
  666. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +0 -330
  667. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +0 -521
  668. package/src/cli/commands/oauth/__tests__/status.test.ts +0 -551
  669. package/src/cli/commands/oauth/__tests__/token.test.ts +0 -420
  670. package/src/cli/lib/daemon-avatar-client.ts +0 -37
  671. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -87
  672. package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +0 -207
  673. package/src/daemon/__tests__/conversation-feed-event.test.ts +0 -304
  674. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +0 -233
  675. package/src/home/__tests__/assistant-feed-authoring.test.ts +0 -156
  676. package/src/home/__tests__/emit-feed-event.test.ts +0 -169
  677. package/src/home/__tests__/feed-population-integration.test.ts +0 -312
  678. package/src/home/__tests__/feed-scheduler.test.ts +0 -222
  679. package/src/home/__tests__/phase5-exit-criteria.test.ts +0 -229
  680. package/src/home/__tests__/platform-gmail-digest.test.ts +0 -222
  681. package/src/home/__tests__/rollup-producer.test.ts +0 -507
  682. package/src/home/assistant-feed-authoring.ts +0 -135
  683. package/src/home/emit-feed-event.ts +0 -169
  684. package/src/home/feed-scheduler.ts +0 -281
  685. package/src/home/platform-gmail-digest.ts +0 -163
  686. package/src/home/rewrite-command-preview.ts +0 -66
  687. package/src/home/rewrite-feed-title.ts +0 -58
  688. package/src/home/rollup-producer.ts +0 -426
  689. package/src/memory/admin.ts +0 -326
  690. package/src/memory/context-search/sources/pkb.ts +0 -476
  691. package/src/memory/graph/compaction.ts +0 -299
  692. /package/src/cli/{commands → lib}/cache-fs.ts +0 -0
@@ -403,7 +403,11 @@ describe("synthesizeConceptPage", () => {
403
403
  );
404
404
  const page = await synthesizeConceptPage(cluster, null, provider);
405
405
  expect(page.slug).toBe("alice-ides");
406
- expect(page.frontmatter).toEqual({ edges: [], ref_files: [] });
406
+ expect(page.frontmatter).toEqual({
407
+ edges: [],
408
+ ref_files: [],
409
+ ref_urls: [],
410
+ });
407
411
  expect(page.body).toContain("VS Code");
408
412
  expect(page.body.endsWith("\n")).toBe(true);
409
413
  });
@@ -625,7 +629,7 @@ describe("collapseEdges", () => {
625
629
 
626
630
  describe("enqueueEmbeds", () => {
627
631
  test("enqueues one embed_concept_page job per slug", () => {
628
- expect(enqueueEmbeds(["alice", "bob", "carol"])).toBe(3);
632
+ expect(enqueueEmbeds(["alice", "bob", "carol"], database)).toBe(3);
629
633
  expect(enqueuedJobs).toEqual([
630
634
  { type: "embed_concept_page", payload: { slug: "alice" } },
631
635
  { type: "embed_concept_page", payload: { slug: "bob" } },
@@ -634,7 +638,7 @@ describe("enqueueEmbeds", () => {
634
638
  });
635
639
 
636
640
  test("empty list is a no-op", () => {
637
- expect(enqueueEmbeds([])).toBe(0);
641
+ expect(enqueueEmbeds([], database)).toBe(0);
638
642
  expect(enqueuedJobs).toEqual([]);
639
643
  });
640
644
  });
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Tests for `memory/v2/page-index.ts` — the router-prompt page index built
3
+ * from concept pages plus seeded skill entries.
4
+ *
5
+ * Tests live in temp workspaces (`mkdtemp`) and never touch `~/.vellum/`. The
6
+ * skill-store module is mocked so `listSkillEntries()` returns deterministic
7
+ * fixtures, and `page-store.js` is wrapped so we can simulate read failures
8
+ * without breaking writes.
9
+ */
10
+
11
+ import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
12
+ import { tmpdir } from "node:os";
13
+ import { join } from "node:path";
14
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
15
+
16
+ import { makeMockLogger } from "../../../__tests__/helpers/mock-logger.js";
17
+ import type { ConceptPage, SkillEntry } from "../types.js";
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Mocks — programmable skill list, programmable readPage failure set,
21
+ // recursive no-op logger. Mocks are installed BEFORE any imports of the
22
+ // module under test so the page-index module observes them at load time.
23
+ // ---------------------------------------------------------------------------
24
+
25
+ const skillState: { entries: SkillEntry[] } = { entries: [] };
26
+ const failingSlugs = new Set<string>();
27
+
28
+ mock.module("../../../util/logger.js", () => ({
29
+ getLogger: () => makeMockLogger(),
30
+ }));
31
+
32
+ mock.module("../skill-store.js", () => ({
33
+ SKILL_SLUG_PREFIX: "skills/",
34
+ listSkillEntries: () => skillState.entries,
35
+ }));
36
+
37
+ // Wrap page-store so we can simulate read failures via `failingSlugs`.
38
+ // Re-export every other binding identity-style so writes still work.
39
+ //
40
+ // Capture each real export into a local const BEFORE installing the mock —
41
+ // module namespaces hold live bindings, so a post-mock dereference of
42
+ // `realPageStore.readPage` would resolve to the mocked function and recurse
43
+ // infinitely.
44
+ const realPageStore = await import("../page-store.js");
45
+ const realReadPage = realPageStore.readPage;
46
+ mock.module("../page-store.js", () => ({
47
+ ...realPageStore,
48
+ readPage: async (workspaceDir: string, slug: string) => {
49
+ if (failingSlugs.has(slug)) {
50
+ throw new Error(`simulated read failure for ${slug}`);
51
+ }
52
+ return realReadPage(workspaceDir, slug);
53
+ },
54
+ }));
55
+
56
+ const { getPageIndex, invalidatePageIndex } = await import("../page-index.js");
57
+ const { writePage } = await import("../page-store.js");
58
+ const { invalidateEdgeIndex } = await import("../edge-index.js");
59
+
60
+ let workspaceDir: string;
61
+
62
+ beforeEach(() => {
63
+ workspaceDir = mkdtempSync(join(tmpdir(), "vellum-memory-v2-page-index-"));
64
+ skillState.entries = [];
65
+ failingSlugs.clear();
66
+ });
67
+
68
+ afterEach(() => {
69
+ invalidatePageIndex();
70
+ invalidateEdgeIndex();
71
+ if (existsSync(workspaceDir)) {
72
+ rmSync(workspaceDir, { recursive: true, force: true });
73
+ }
74
+ });
75
+
76
+ function makePage(
77
+ slug: string,
78
+ opts: { edges?: string[]; summary?: string; body?: string } = {},
79
+ ): ConceptPage {
80
+ return {
81
+ slug,
82
+ frontmatter: {
83
+ edges: opts.edges ?? [],
84
+ ref_files: [],
85
+ ref_urls: [],
86
+ ...(opts.summary !== undefined ? { summary: opts.summary } : {}),
87
+ },
88
+ body: opts.body ?? "",
89
+ };
90
+ }
91
+
92
+ // ---------------------------------------------------------------------------
93
+ // Build & cache
94
+ // ---------------------------------------------------------------------------
95
+
96
+ describe("getPageIndex", () => {
97
+ test("returns an empty index when there are no pages and no skills", async () => {
98
+ const idx = await getPageIndex(workspaceDir);
99
+ expect(idx.entries).toEqual([]);
100
+ expect(idx.bySlug.size).toBe(0);
101
+ expect(idx.byId.size).toBe(0);
102
+ expect(idx.rendered).toBe("");
103
+ });
104
+
105
+ test("caches the result so repeat calls reuse the prior build", async () => {
106
+ await writePage(workspaceDir, makePage("alice", { summary: "First" }));
107
+
108
+ const first = await getPageIndex(workspaceDir);
109
+ // Mutate disk after the first read WITHOUT going through `writePage`,
110
+ // which would invalidate the page-index cache by design. The raw
111
+ // filesystem write simulates an out-of-band file appearing — without
112
+ // the cache the second call would observe it and return a different
113
+ // object.
114
+ writeFileSync(
115
+ join(workspaceDir, "memory", "concepts", "bob.md"),
116
+ "---\nedges: []\nref_files: []\nref_urls: []\nsummary: Second\n---\n",
117
+ "utf-8",
118
+ );
119
+
120
+ const second = await getPageIndex(workspaceDir);
121
+ expect(second).toBe(first);
122
+ expect(second.entries.map((e) => e.slug)).toEqual(["alice"]);
123
+ });
124
+
125
+ test("writePage invalidates the cache so the next call sees the new page", async () => {
126
+ await writePage(workspaceDir, makePage("alice", { summary: "First" }));
127
+ const before = await getPageIndex(workspaceDir);
128
+
129
+ // `writePage` calls `invalidatePageIndex(workspaceDir)` as a side
130
+ // effect — verify that contract here so the cache-hit test above
131
+ // can't accidentally pass because writePage stopped invalidating.
132
+ await writePage(workspaceDir, makePage("bob", { summary: "Second" }));
133
+
134
+ const after = await getPageIndex(workspaceDir);
135
+ expect(after).not.toBe(before);
136
+ expect(after.entries.map((e) => e.slug)).toEqual(["alice", "bob"]);
137
+ });
138
+
139
+ test("invalidatePageIndex(workspaceDir) forces a rebuild on the next call", async () => {
140
+ await writePage(workspaceDir, makePage("alice", { summary: "First" }));
141
+ const before = await getPageIndex(workspaceDir);
142
+
143
+ invalidatePageIndex(workspaceDir);
144
+ await writePage(workspaceDir, makePage("bob", { summary: "Second" }));
145
+
146
+ const after = await getPageIndex(workspaceDir);
147
+ expect(after).not.toBe(before);
148
+ expect(after.entries.map((e) => e.slug)).toEqual(["alice", "bob"]);
149
+ });
150
+
151
+ test("invalidatePageIndex() with no arg clears any cached workspace", async () => {
152
+ await writePage(workspaceDir, makePage("alice", { summary: "First" }));
153
+ const before = await getPageIndex(workspaceDir);
154
+ invalidatePageIndex();
155
+ const after = await getPageIndex(workspaceDir);
156
+ expect(after).not.toBe(before);
157
+ });
158
+
159
+ test("sorts entries by slug ASCII deterministically across rebuilds", async () => {
160
+ await writePage(workspaceDir, makePage("zulu", { summary: "Z" }));
161
+ await writePage(workspaceDir, makePage("alpha", { summary: "A" }));
162
+ await writePage(workspaceDir, makePage("mike", { summary: "M" }));
163
+
164
+ const first = await getPageIndex(workspaceDir);
165
+ invalidatePageIndex();
166
+ const second = await getPageIndex(workspaceDir);
167
+
168
+ expect(first.entries.map((e) => e.slug)).toEqual(["alpha", "mike", "zulu"]);
169
+ expect(first.entries).toEqual(second.entries);
170
+ });
171
+
172
+ test("assigns dense 1-based IDs in slug order", async () => {
173
+ await writePage(workspaceDir, makePage("bravo", { summary: "B" }));
174
+ await writePage(workspaceDir, makePage("alpha", { summary: "A" }));
175
+ await writePage(workspaceDir, makePage("charlie", { summary: "C" }));
176
+
177
+ const idx = await getPageIndex(workspaceDir);
178
+ expect(idx.entries.map((e) => e.id)).toEqual([1, 2, 3]);
179
+ expect(idx.entries.map((e) => e.slug)).toEqual([
180
+ "alpha",
181
+ "bravo",
182
+ "charlie",
183
+ ]);
184
+ expect(idx.byId.get(1)?.slug).toBe("alpha");
185
+ expect(idx.bySlug.get("charlie")?.id).toBe(3);
186
+ });
187
+
188
+ test("drops pages whose read fails and continues the build", async () => {
189
+ await writePage(workspaceDir, makePage("alice", { summary: "Alice" }));
190
+ await writePage(workspaceDir, makePage("bob", { summary: "Bob" }));
191
+ await writePage(workspaceDir, makePage("carol", { summary: "Carol" }));
192
+
193
+ failingSlugs.add("bob");
194
+
195
+ const idx = await getPageIndex(workspaceDir);
196
+ expect(idx.entries.map((e) => e.slug)).toEqual(["alice", "carol"]);
197
+ // IDs remain dense — the dropped page does not leave a hole.
198
+ expect(idx.entries.map((e) => e.id)).toEqual([1, 2]);
199
+ });
200
+
201
+ test("integrates seeded skill entries under the skills/ slug prefix", async () => {
202
+ await writePage(workspaceDir, makePage("alice", { summary: "Alice" }));
203
+ skillState.entries = [
204
+ { id: "browser", content: "Drive a browser." },
205
+ { id: "calendar", content: "Schedule meetings." },
206
+ ];
207
+
208
+ const idx = await getPageIndex(workspaceDir);
209
+ expect(idx.entries.map((e) => e.slug)).toEqual([
210
+ "alice",
211
+ "skills/browser",
212
+ "skills/calendar",
213
+ ]);
214
+ expect(idx.bySlug.get("skills/browser")?.summary).toBe("Drive a browser.");
215
+ // Skill entries always carry an empty edge list.
216
+ expect(idx.bySlug.get("skills/browser")?.edges).toEqual([]);
217
+ });
218
+
219
+ test("resolves outgoing edges to numeric IDs and drops missing targets", async () => {
220
+ await writePage(
221
+ workspaceDir,
222
+ makePage("alice", { summary: "A", edges: ["bob", "ghost"] }),
223
+ );
224
+ await writePage(workspaceDir, makePage("bob", { summary: "B" }));
225
+
226
+ const idx = await getPageIndex(workspaceDir);
227
+ const alice = idx.bySlug.get("alice")!;
228
+ const bob = idx.bySlug.get("bob")!;
229
+ expect(alice.edges).toEqual([bob.id]);
230
+ });
231
+
232
+ test("falls back to body when frontmatter.summary is absent", async () => {
233
+ await writePage(
234
+ workspaceDir,
235
+ makePage("alice", { body: " Body fallback content. " }),
236
+ );
237
+
238
+ const idx = await getPageIndex(workspaceDir);
239
+ expect(idx.bySlug.get("alice")?.summary).toBe("Body fallback content.");
240
+ });
241
+
242
+ test("truncates summary to 200 characters", async () => {
243
+ const long = "x".repeat(500);
244
+ await writePage(workspaceDir, makePage("alice", { summary: long }));
245
+ const idx = await getPageIndex(workspaceDir);
246
+ expect(idx.bySlug.get("alice")?.summary.length).toBe(200);
247
+ });
248
+ });
249
+
250
+ // ---------------------------------------------------------------------------
251
+ // Render
252
+ // ---------------------------------------------------------------------------
253
+
254
+ describe("rendered prompt block", () => {
255
+ test("renders [id] slug — summary lines with edges parenthetical when present", async () => {
256
+ await writePage(
257
+ workspaceDir,
258
+ makePage("alice", { summary: "A page", edges: ["bob"] }),
259
+ );
260
+ await writePage(workspaceDir, makePage("bob", { summary: "B page" }));
261
+
262
+ const idx = await getPageIndex(workspaceDir);
263
+ const alice = idx.bySlug.get("alice")!;
264
+ const bob = idx.bySlug.get("bob")!;
265
+
266
+ const expected =
267
+ `[${alice.id}] alice — A page (edges: ${bob.id})\n` +
268
+ `[${bob.id}] bob — B page\n`;
269
+ expect(idx.rendered).toBe(expected);
270
+ });
271
+
272
+ test("omits the parenthetical for entries with no outgoing edges", async () => {
273
+ await writePage(workspaceDir, makePage("alice", { summary: "A page" }));
274
+ const idx = await getPageIndex(workspaceDir);
275
+ expect(idx.rendered).toBe("[1] alice — A page\n");
276
+ });
277
+ });
@@ -62,7 +62,7 @@ afterEach(() => {
62
62
  function makePage(overrides: Partial<ConceptPage> = {}): ConceptPage {
63
63
  return {
64
64
  slug: "alice-preferences",
65
- frontmatter: { edges: ["bob-handoff"], ref_files: [] },
65
+ frontmatter: { edges: ["bob-handoff"], ref_files: [], ref_urls: [] },
66
66
  body: "Alice prefers VS Code over Vim.\nShe ships at end of day.\n",
67
67
  ...overrides,
68
68
  };
@@ -238,6 +238,19 @@ describe("writePage + readPage round-trip", () => {
238
238
  expect(read!.body).toBe(body);
239
239
  });
240
240
 
241
+ test("readPage throws on unknown frontmatter keys instead of silently dropping them", async () => {
242
+ const slug = "extra-keys";
243
+ const raw =
244
+ "---\nedges: []\nref_files: []\nunknown_field: oops\n---\nbody\n";
245
+ writeFileSync(
246
+ join(workspaceDir, "memory", "concepts", `${slug}.md`),
247
+ raw,
248
+ "utf-8",
249
+ );
250
+
251
+ await expect(readPage(workspaceDir, slug)).rejects.toThrow();
252
+ });
253
+
241
254
  test("writePage overwrites an existing page", async () => {
242
255
  const page1 = makePage({ body: "first version\n" });
243
256
  await writePage(workspaceDir, page1);
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Tests for `assistant/src/memory/v2/prompts/router.ts` —
3
+ * `renderRouterPrompt` (placeholder substitution in the bundled body) and
4
+ * `resolveRouterPrompt` (file-override path with fallback).
5
+ */
6
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
7
+ import { tmpdir } from "node:os";
8
+ import { join } from "node:path";
9
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
10
+
11
+ import { renderRouterPrompt, resolveRouterPrompt } from "../prompts/router.js";
12
+
13
+ const SAMPLE_INDEX = `[1] morning-routine — coffee, walk, journal (edges: 2)
14
+ [2] journal-style — terse, dated, no fluff (edges: 1)
15
+ [3] taxes-2025 — Q1 estimate due April 15 (edges: )`;
16
+
17
+ describe("renderRouterPrompt — substitution", () => {
18
+ test("replaces all three placeholders with the supplied values", () => {
19
+ const out = renderRouterPrompt({
20
+ assistantName: "Aria",
21
+ userName: "Alice",
22
+ pageIndexBlock: SAMPLE_INDEX,
23
+ });
24
+
25
+ expect(out).not.toContain("{{ASSISTANT_NAME}}");
26
+ expect(out).not.toContain("{{USER_NAME}}");
27
+ expect(out).not.toContain("{{PAGE_INDEX}}");
28
+ expect(out).toContain("Aria");
29
+ expect(out).toContain("Alice");
30
+ expect(out).toContain(SAMPLE_INDEX);
31
+ });
32
+
33
+ test("substitutes every occurrence of the assistant name placeholder", () => {
34
+ const out = renderRouterPrompt({
35
+ assistantName: "Aria",
36
+ userName: "Alice",
37
+ pageIndexBlock: SAMPLE_INDEX,
38
+ });
39
+
40
+ // Body references the assistant name in multiple sentences; ensure none
41
+ // of them leak the raw placeholder.
42
+ const matches = out.match(/Aria/g) ?? [];
43
+ expect(matches.length).toBeGreaterThanOrEqual(2);
44
+ });
45
+ });
46
+
47
+ describe("renderRouterPrompt — neutral fallbacks", () => {
48
+ test("falls back to 'the assistant' when assistantName is null", () => {
49
+ const out = renderRouterPrompt({
50
+ assistantName: null,
51
+ userName: "Alice",
52
+ pageIndexBlock: SAMPLE_INDEX,
53
+ });
54
+
55
+ expect(out).toContain("the assistant");
56
+ expect(out).not.toContain("{{ASSISTANT_NAME}}");
57
+ });
58
+
59
+ test("falls back to 'the user' when userName is null", () => {
60
+ const out = renderRouterPrompt({
61
+ assistantName: "Aria",
62
+ userName: null,
63
+ pageIndexBlock: SAMPLE_INDEX,
64
+ });
65
+
66
+ expect(out).toContain("the user");
67
+ expect(out).not.toContain("{{USER_NAME}}");
68
+ });
69
+
70
+ test("uses both fallbacks when both names are null", () => {
71
+ const out = renderRouterPrompt({
72
+ assistantName: null,
73
+ userName: null,
74
+ pageIndexBlock: SAMPLE_INDEX,
75
+ });
76
+
77
+ expect(out).toContain("the assistant");
78
+ expect(out).toContain("the user");
79
+ });
80
+
81
+ test("falls back when names are whitespace-only strings", () => {
82
+ const out = renderRouterPrompt({
83
+ assistantName: " ",
84
+ userName: "\t\n",
85
+ pageIndexBlock: SAMPLE_INDEX,
86
+ });
87
+
88
+ expect(out).toContain("the assistant");
89
+ expect(out).toContain("the user");
90
+ });
91
+ });
92
+
93
+ describe("renderRouterPrompt — page index handling", () => {
94
+ test("substitutes an empty pageIndexBlock cleanly without double-newline artifacts", () => {
95
+ const out = renderRouterPrompt({
96
+ assistantName: "Aria",
97
+ userName: "Alice",
98
+ pageIndexBlock: "",
99
+ });
100
+
101
+ expect(out).not.toContain("{{PAGE_INDEX}}");
102
+ // The header should still be present and not followed by a stray
103
+ // triple-newline run from collapsing the empty block.
104
+ expect(out).toContain("# Concept Page Index");
105
+ expect(out).not.toMatch(/\n\n\n/);
106
+ // Output should end at the header section without trailing whitespace.
107
+ expect(out.endsWith("# Concept Page Index\n\n")).toBe(true);
108
+ });
109
+
110
+ test("preserves the page index body verbatim, including edges syntax", () => {
111
+ const out = renderRouterPrompt({
112
+ assistantName: "Aria",
113
+ userName: "Alice",
114
+ pageIndexBlock: SAMPLE_INDEX,
115
+ });
116
+
117
+ expect(out).toContain(
118
+ "[1] morning-routine — coffee, walk, journal (edges: 2)",
119
+ );
120
+ expect(out).toContain(
121
+ "[3] taxes-2025 — Q1 estimate due April 15 (edges: )",
122
+ );
123
+ });
124
+ });
125
+
126
+ describe("renderRouterPrompt — determinism & snapshot stability", () => {
127
+ test("returns the same string for the same inputs", () => {
128
+ const opts = {
129
+ assistantName: "Aria",
130
+ userName: "Alice",
131
+ pageIndexBlock: SAMPLE_INDEX,
132
+ };
133
+ expect(renderRouterPrompt(opts)).toBe(renderRouterPrompt(opts));
134
+ });
135
+
136
+ test("snapshot of fixed inputs", () => {
137
+ const out = renderRouterPrompt({
138
+ assistantName: "Aria",
139
+ userName: "Alice",
140
+ pageIndexBlock: SAMPLE_INDEX,
141
+ });
142
+
143
+ expect(out).toMatchSnapshot();
144
+ });
145
+ });
146
+
147
+ describe("renderRouterPrompt — content expectations", () => {
148
+ test("references the select_pages_to_inject tool name", () => {
149
+ const out = renderRouterPrompt({
150
+ assistantName: "Aria",
151
+ userName: "Alice",
152
+ pageIndexBlock: SAMPLE_INDEX,
153
+ });
154
+
155
+ expect(out).toContain("select_pages_to_inject");
156
+ });
157
+
158
+ test("describes the already_injected_ids and now markers", () => {
159
+ const out = renderRouterPrompt({
160
+ assistantName: "Aria",
161
+ userName: "Alice",
162
+ pageIndexBlock: SAMPLE_INDEX,
163
+ });
164
+
165
+ expect(out).toContain("<already_injected_ids>");
166
+ expect(out).toContain("<now>");
167
+ });
168
+
169
+ test("biases toward inclusion when in doubt", () => {
170
+ const out = renderRouterPrompt({
171
+ assistantName: "Aria",
172
+ userName: "Alice",
173
+ pageIndexBlock: SAMPLE_INDEX,
174
+ });
175
+
176
+ expect(out.toLowerCase()).toContain("lean toward inclusion");
177
+ expect(out.toLowerCase()).toContain("missing a relevant page");
178
+ });
179
+ });
180
+
181
+ describe("resolveRouterPrompt — override path", () => {
182
+ let tmpDir: string;
183
+
184
+ beforeEach(() => {
185
+ tmpDir = mkdtempSync(join(tmpdir(), "vellum-router-prompt-"));
186
+ });
187
+
188
+ afterEach(() => {
189
+ rmSync(tmpDir, { recursive: true, force: true });
190
+ });
191
+
192
+ const STD_OPTS = {
193
+ assistantName: "Aria",
194
+ userName: "Alice",
195
+ pageIndexBlock: SAMPLE_INDEX,
196
+ };
197
+
198
+ test("null overridePath returns the bundled prompt verbatim", () => {
199
+ expect(resolveRouterPrompt(null, STD_OPTS)).toEqual(
200
+ renderRouterPrompt(STD_OPTS),
201
+ );
202
+ });
203
+
204
+ test("loads override and substitutes placeholders", () => {
205
+ const overridePath = join(tmpDir, "router.md");
206
+ writeFileSync(
207
+ overridePath,
208
+ "Hi {{ASSISTANT_NAME}}, you are routing for {{USER_NAME}}.\n\n{{PAGE_INDEX}}",
209
+ "utf-8",
210
+ );
211
+
212
+ const out = resolveRouterPrompt(overridePath, STD_OPTS);
213
+ expect(out).toContain("Hi Aria, you are routing for Alice.");
214
+ expect(out).toContain(SAMPLE_INDEX);
215
+ expect(out).not.toContain("{{ASSISTANT_NAME}}");
216
+ expect(out).not.toContain("{{PAGE_INDEX}}");
217
+ });
218
+
219
+ test("missing override file falls back to bundled prompt", () => {
220
+ const overridePath = join(tmpDir, "does-not-exist.md");
221
+ expect(resolveRouterPrompt(overridePath, STD_OPTS)).toEqual(
222
+ renderRouterPrompt(STD_OPTS),
223
+ );
224
+ });
225
+
226
+ test("empty override file falls back to bundled prompt", () => {
227
+ const overridePath = join(tmpDir, "empty.md");
228
+ writeFileSync(overridePath, " \n\t\n", "utf-8");
229
+ expect(resolveRouterPrompt(overridePath, STD_OPTS)).toEqual(
230
+ renderRouterPrompt(STD_OPTS),
231
+ );
232
+ });
233
+
234
+ test("override that is a directory falls back to bundled prompt", () => {
235
+ // Pass the temp directory itself as the override path — lstat sees a
236
+ // directory, not a regular file, so the loader bails to bundled.
237
+ expect(resolveRouterPrompt(tmpDir, STD_OPTS)).toEqual(
238
+ renderRouterPrompt(STD_OPTS),
239
+ );
240
+ });
241
+
242
+ test("override applies neutral fallbacks for missing names", () => {
243
+ const overridePath = join(tmpDir, "neutral.md");
244
+ writeFileSync(
245
+ overridePath,
246
+ "Hi {{ASSISTANT_NAME}}, routing for {{USER_NAME}}.",
247
+ "utf-8",
248
+ );
249
+
250
+ const out = resolveRouterPrompt(overridePath, {
251
+ assistantName: null,
252
+ userName: null,
253
+ pageIndexBlock: "",
254
+ });
255
+ expect(out).toBe("Hi the assistant, routing for the user.");
256
+ });
257
+ });
@@ -1,11 +1,30 @@
1
+ import { existsSync, mkdtempSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
1
4
  import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
5
 
3
6
  import { makeMockLogger } from "../../../__tests__/helpers/mock-logger.js";
4
7
 
8
+ // Per-suite tmp data dir so the reembed sentinel never lands in the
9
+ // developer's real ~/.vellum workspace.
10
+ const TEST_DATA_DIR = mkdtempSync(join(tmpdir(), "memory-v2-qdrant-test-"));
11
+ const REEMBED_SENTINEL_PATH = join(
12
+ TEST_DATA_DIR,
13
+ ".memory-v2-reembed-required",
14
+ );
15
+
5
16
  mock.module("../../../util/logger.js", () => ({
6
17
  getLogger: () => makeMockLogger(),
7
18
  }));
8
19
 
20
+ mock.module("../../../util/platform.js", () => ({
21
+ getDataDir: () => TEST_DATA_DIR,
22
+ // Bun shares mocked modules across test files; some peer tests import
23
+ // `getWorkspaceDir` from this same module, so re-export it here to avoid
24
+ // an `undefined` if this mock is the one that wins evaluation order.
25
+ getWorkspaceDir: () => TEST_DATA_DIR,
26
+ }));
27
+
9
28
  // Stub getConfig — only the qdrant.url / vectorSize / onDisk fields matter.
10
29
  mock.module("../../../config/loader.js", () => ({
11
30
  getConfig: () => ({
@@ -184,6 +203,7 @@ const {
184
203
  deleteConceptPageEmbedding,
185
204
  hybridQueryConceptPages,
186
205
  countConceptPagePoints,
206
+ clearReembedSentinel,
187
207
  MEMORY_V2_COLLECTION,
188
208
  _resetMemoryV2QdrantForTests,
189
209
  } = await import("../qdrant.js");
@@ -209,6 +229,11 @@ function resetState(): void {
209
229
  state.countCalls = 0;
210
230
  state.upsertThrowQueue.length = 0;
211
231
  _resetMemoryV2QdrantForTests();
232
+ // Drop any sentinel a prior test left behind so the no-drift default path
233
+ // doesn't accidentally report `migrated: true`.
234
+ if (existsSync(REEMBED_SENTINEL_PATH)) {
235
+ rmSync(REEMBED_SENTINEL_PATH);
236
+ }
212
237
  }
213
238
 
214
239
  describe("memory v2 qdrant — collection lifecycle", () => {
@@ -361,6 +386,53 @@ describe("memory v2 qdrant — collection lifecycle", () => {
361
386
  expect(result).toEqual({ migrated: false });
362
387
  });
363
388
 
389
+ test("preserves the reembed signal across calls when createCollection fails after delete", async () => {
390
+ // Pre-#29823 schema triggers the destructive recreate path.
391
+ state.collectionExistsBeforeCreate = true;
392
+ state.getCollectionInfo = {
393
+ config: {
394
+ params: {
395
+ vectors: { dense: { size: 384 } },
396
+ sparse_vectors: { sparse: {} },
397
+ },
398
+ },
399
+ };
400
+ state.createCollectionThrows = new Error("Qdrant transient failure");
401
+
402
+ let firstError: unknown = null;
403
+ try {
404
+ await ensureConceptPageCollection();
405
+ } catch (err) {
406
+ firstError = err;
407
+ }
408
+ expect(firstError).not.toBeNull();
409
+ // The sentinel must outlive the failed call so the retry knows data was lost.
410
+ expect(existsSync(REEMBED_SENTINEL_PATH)).toBe(true);
411
+
412
+ // Simulate a follow-up call after the transient failure clears. The
413
+ // collection no longer exists (delete succeeded earlier) so the ensure
414
+ // path falls through to createCollection without re-entering the drift
415
+ // branch — but the sentinel must still surface as `migrated: true` so
416
+ // the lifecycle hook enqueues reembed.
417
+ state.createCollectionThrows = null;
418
+ _resetMemoryV2QdrantForTests();
419
+ const result = await ensureConceptPageCollection();
420
+
421
+ expect(result).toEqual({ migrated: true });
422
+
423
+ // Lifecycle hook clears the sentinel after enqueueing the reembed job.
424
+ await clearReembedSentinel();
425
+ expect(existsSync(REEMBED_SENTINEL_PATH)).toBe(false);
426
+ });
427
+
428
+ test("clearReembedSentinel is a no-op when no sentinel exists", async () => {
429
+ // Idempotent: missing-file does not throw, so the lifecycle hook can
430
+ // call it unconditionally without guarding.
431
+ expect(existsSync(REEMBED_SENTINEL_PATH)).toBe(false);
432
+ await clearReembedSentinel();
433
+ expect(existsSync(REEMBED_SENTINEL_PATH)).toBe(false);
434
+ });
435
+
364
436
  test("concurrent ensure during a schema rebuild only deletes/creates once", async () => {
365
437
  state.collectionExistsBeforeCreate = true;
366
438
  state.getCollectionInfo = {