@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
@@ -41,6 +41,7 @@ import {
41
41
  } from "bun:test";
42
42
 
43
43
  import { drizzle } from "drizzle-orm/bun-sqlite";
44
+ import { z } from "zod";
44
45
 
45
46
  import { makeMockLogger } from "../../../__tests__/helpers/mock-logger.js";
46
47
  import type { AssistantConfig } from "../../../config/types.js";
@@ -153,6 +154,11 @@ mock.module("../skill-store.js", () => ({
153
154
  isSkillSlug: (slug: string) => slug.startsWith("skills/"),
154
155
  SKILL_SLUG_PREFIX: "skills/",
155
156
  skillSlugFor: (id: string) => `skills/${id}`,
157
+ // PR 4 added `listSkillEntries`; `page-index.ts` (transitively imported
158
+ // via `page-store.ts` and `skill-store.ts`) consumes it at module-init
159
+ // time. Tests stage skill content via `skillState.entries`; expose them
160
+ // here so the page-index loader sees a consistent view.
161
+ listSkillEntries: () => Array.from(skillState.entries.values()),
156
162
  }));
157
163
 
158
164
  // ---------------------------------------------------------------------------
@@ -179,6 +185,93 @@ mock.module("../../memory-v2-activation-log-store.js", () => ({
179
185
  },
180
186
  }));
181
187
 
188
+ // ---------------------------------------------------------------------------
189
+ // Page-store mock — pass-through with optional per-slug failure injection
190
+ // ---------------------------------------------------------------------------
191
+ //
192
+ // Most tests want the real `readPage` (it walks the temp workspace seeded in
193
+ // `beforeAll`). The error-isolation tests stage a slug whose `readPage` call
194
+ // must throw — typically a Zod validation error mimicking the real-world
195
+ // "unrecognized frontmatter key" failure that motivated this work. Tests
196
+ // stage entries via `pageStoreState.failingSlugs` and reset in `resetState`.
197
+ //
198
+ // Bun's `mock.module` mutates the module's exports object in place, so
199
+ // `realPageStore.readPage` AFTER the mock applies would resolve to the mock
200
+ // itself — calling it would recurse. We capture the original function value
201
+ // (not a property lookup) before installing the mock so the pass-through
202
+ // path has a real reference to the underlying implementation.
203
+
204
+ const realPageStoreModule = await import("../page-store.js");
205
+ const realReadPage = realPageStoreModule.readPage;
206
+ const pageStoreState = {
207
+ failingSlugs: new Map<string, Error>(),
208
+ };
209
+ mock.module("../page-store.js", () => ({
210
+ ...realPageStoreModule,
211
+ readPage: async (workspaceDir: string, slug: string) => {
212
+ const err = pageStoreState.failingSlugs.get(slug);
213
+ if (err) throw err;
214
+ return realReadPage(workspaceDir, slug);
215
+ },
216
+ }));
217
+
218
+ // ---------------------------------------------------------------------------
219
+ // Router mock — programmable per-call result
220
+ // ---------------------------------------------------------------------------
221
+ //
222
+ // PR 10 wires `runRouter` into `injectMemoryV2Block` behind the
223
+ // `memory.v2.router.enabled` flag. The activation-mode tests above never
224
+ // flip the flag, so the default mock returns a no-op result and the router
225
+ // branch is never exercised. Router-mode tests set `routerState.nextResult`
226
+ // to stage a deterministic outcome before each call.
227
+
228
+ interface RouterResultStub {
229
+ selectedSlugs: string[];
230
+ failureReason: string | null;
231
+ }
232
+
233
+ const routerState = {
234
+ nextResult: null as RouterResultStub | null,
235
+ callCount: 0,
236
+ };
237
+
238
+ mock.module("../router.js", () => ({
239
+ runRouter: async () => {
240
+ routerState.callCount++;
241
+ return (
242
+ routerState.nextResult ?? {
243
+ selectedSlugs: [],
244
+ failureReason: null,
245
+ }
246
+ );
247
+ },
248
+ }));
249
+
250
+ // ---------------------------------------------------------------------------
251
+ // Activation-store mock — pass-through with optional `save` failure injection
252
+ // ---------------------------------------------------------------------------
253
+ //
254
+ // One regression test forces `save` to throw to exercise the
255
+ // `injectMemoryV2Block` outer try/finally — telemetry must still be flushed
256
+ // (with `mode: "errored"`) and the error must propagate. Default behavior
257
+ // delegates to the real activation-store so the rest of the suite stays
258
+ // untouched. Same pre-mock function-capture trick as `readPage` above.
259
+
260
+ const realActivationStoreModule = await import("../activation-store.js");
261
+ const realSave = realActivationStoreModule.save;
262
+ const activationStoreState = {
263
+ saveShouldThrow: false,
264
+ };
265
+ mock.module("../activation-store.js", () => ({
266
+ ...realActivationStoreModule,
267
+ save: async (...args: Parameters<typeof realSave>) => {
268
+ if (activationStoreState.saveShouldThrow) {
269
+ throw new Error("simulated activation-store save failure");
270
+ }
271
+ return realSave(...args);
272
+ },
273
+ }));
274
+
182
275
  // ---------------------------------------------------------------------------
183
276
  // Workspace + DB setup
184
277
  // ---------------------------------------------------------------------------
@@ -299,8 +392,10 @@ function makeConfig(
299
392
  epsilon: number;
300
393
  dense_weight: number;
301
394
  sparse_weight: number;
395
+ router: { enabled: boolean; max_page_ids?: number };
302
396
  }> = {},
303
397
  ): AssistantConfig {
398
+ const { router, ...rest } = overrides;
304
399
  return {
305
400
  memory: {
306
401
  v2: {
@@ -314,7 +409,8 @@ function makeConfig(
314
409
  epsilon: 0.01,
315
410
  dense_weight: 1.0,
316
411
  sparse_weight: 0.0,
317
- ...overrides,
412
+ router: { enabled: false, max_page_ids: 25, ...(router ?? {}) },
413
+ ...rest,
318
414
  },
319
415
  },
320
416
  } as unknown as AssistantConfig;
@@ -390,6 +486,10 @@ function resetState(): void {
390
486
  skillState.entries.clear();
391
487
  telemetryState.recordCalls.length = 0;
392
488
  telemetryState.recordShouldThrow = false;
489
+ pageStoreState.failingSlugs.clear();
490
+ activationStoreState.saveShouldThrow = false;
491
+ routerState.nextResult = null;
492
+ routerState.callCount = 0;
393
493
  // The qdrant module caches its client; the cached client may be a
394
494
  // MockQdrantClient instance from a sibling test file. Reset to force a
395
495
  // fresh `new QdrantClient()` against this file's mock.
@@ -712,11 +812,13 @@ describe("injectMemoryV2Block", () => {
712
812
  });
713
813
 
714
814
  test("persists sparse state — only slugs above epsilon survive", async () => {
715
- // Carol scores high; alice/bob essentially zero. After saving, only
716
- // carol should appear in the persisted state map.
815
+ // Carol scores high; alice essentially zero. After saving, only carol
816
+ // should appear in the persisted state map. denseScore is the raw
817
+ // Qdrant cosine in [-1, 1]; alice uses -1 so the post `(x+1)/2`
818
+ // unit-mapping pins her fused score to 0 — below epsilon.
717
819
  stageTurn([
718
820
  { slug: "carol-jazz", denseScore: 1.0 },
719
- { slug: "alice-vscode", denseScore: 0.0 },
821
+ { slug: "alice-vscode", denseScore: -1.0 },
720
822
  ]);
721
823
  await injectMemoryV2Block({
722
824
  database: db,
@@ -907,9 +1009,10 @@ describe("injectMemoryV2Block", () => {
907
1009
 
908
1010
  test("skill slugs whose entry is missing from the cache are dropped silently", async () => {
909
1011
  // The skill ranks into top-K but the in-process cache no longer knows
910
- // its content (skill uninstalled mid-run). The render path drops it
911
- // without surfacing it as a `missingSlugs` page-missing event that
912
- // status is reserved for on-disk concept pages, not catalog-derived
1012
+ // its content (skill uninstalled mid-run, or a startup race where the
1013
+ // Qdrant row landed before the skill cache was seeded). The render path
1014
+ // drops it without surfacing it as a `missingSlugs` page-missing event —
1015
+ // that status is reserved for on-disk concept pages, not catalog-derived
913
1016
  // skill entries.
914
1017
  stageTurn([{ slug: "skills/missing-skill", denseScore: 0.9 }]);
915
1018
  // No `stageSkills` call — cache stays empty.
@@ -925,10 +1028,16 @@ describe("injectMemoryV2Block", () => {
925
1028
  config: makeConfig(),
926
1029
  });
927
1030
 
928
- // `toInject` still records the slug (it ranked into top-K) but the
929
- // block collapses to null because the only entry was a cache miss.
930
- expect(result.toInject).toEqual(["skills/missing-skill"]);
1031
+ // The skill is excluded from `toInject` (and `everInjected`) so future
1032
+ // per-turn runs re-attempt the attach once the cache is populated.
1033
+ // `block` collapses to null because the only candidate was a cache miss.
1034
+ expect(result.toInject).toEqual([]);
931
1035
  expect(result.block).toBeNull();
1036
+
1037
+ // Persisted `everInjected` must not record the missing skill — that
1038
+ // would block retry on a later turn until compaction-driven eviction.
1039
+ const persisted = await hydrate(db, "conv-1");
1040
+ expect(persisted!.everInjected).toEqual([]);
932
1041
  });
933
1042
 
934
1043
  test("returns null when both concept pages and skills are empty", async () => {
@@ -1287,4 +1396,513 @@ describe("injectMemoryV2Block", () => {
1287
1396
  { slug: "alice-vscode", turn: 1 },
1288
1397
  ]);
1289
1398
  });
1399
+
1400
+ // ---------------------------------------------------------------------------
1401
+ // Per-page error isolation + on-throw telemetry
1402
+ // ---------------------------------------------------------------------------
1403
+
1404
+ test("one slug's page-read failing isolates the error — other slugs still render and the corrupt slug records `status: corrupt`", async () => {
1405
+ // Two slugs rank into top-K together. Carol's page reads cleanly; alice's
1406
+ // `readPage` throws a ZodError mimicking the real "unrecognized
1407
+ // frontmatter key" failure that motivated this work. Before the fix, the
1408
+ // bare `Promise.all` rejected and the entire turn lost its block AND its
1409
+ // activation log row. With per-page isolation, carol still renders and
1410
+ // the activation log row marks alice as `corrupt` (telemetry remains
1411
+ // observable for triage).
1412
+ const zodErr = z.object({ x: z.string() }).safeParse({ x: 1 }).error!;
1413
+ pageStoreState.failingSlugs.set("alice-vscode", zodErr);
1414
+ stageTurn([
1415
+ { slug: "alice-vscode", denseScore: 0.95 },
1416
+ { slug: "carol-jazz", denseScore: 0.9 },
1417
+ ]);
1418
+
1419
+ const result = await injectMemoryV2Block({
1420
+ database: db,
1421
+ conversationId: "conv-1",
1422
+ currentTurn: 1,
1423
+ userMessage: "music and editors",
1424
+ assistantMessage: "",
1425
+ nowText: "Now",
1426
+ messageId: "msg-1",
1427
+ config: makeConfig(),
1428
+ });
1429
+
1430
+ // (a) Block is non-null and contains content from the OTHER slug; alice
1431
+ // is dropped from the rendered block but does not poison the batch.
1432
+ expect(result.block).not.toBeNull();
1433
+ expect(result.block).toContain("# memory/concepts/carol-jazz.md");
1434
+ expect(result.block).not.toContain("# memory/concepts/alice-vscode.md");
1435
+
1436
+ // (b) Activation log row exists with carol `injected` and alice
1437
+ // `corrupt`. Status `corrupt` is reserved for read-time throws and is
1438
+ // distinct from `page_missing` (which is null-return / file vanished).
1439
+ expect(telemetryState.recordCalls.length).toBe(1);
1440
+ const row = telemetryState.recordCalls[0] as {
1441
+ mode: string;
1442
+ concepts: Array<{ slug: string; status: string }>;
1443
+ };
1444
+ expect(row.mode).toBe("per-turn");
1445
+ const byslug = new Map(row.concepts.map((c) => [c.slug, c]));
1446
+ expect(byslug.get("alice-vscode")!.status).toBe("corrupt");
1447
+ expect(byslug.get("carol-jazz")!.status).toBe("injected");
1448
+
1449
+ // (c) Both slugs land in `toInject` and `everInjected` — same handling
1450
+ // as `page_missing` (see the phantom-slug test): the slug was attempted
1451
+ // this turn, telemetry records the outcome, and we don't keep re-trying
1452
+ // a stale Qdrant / edge-index entry on every subsequent turn.
1453
+ expect(new Set(result.toInject)).toEqual(
1454
+ new Set(["alice-vscode", "carol-jazz"]),
1455
+ );
1456
+ const persisted = await hydrate(db, "conv-1");
1457
+ const everInjectedSlugs = persisted!.everInjected.map((e) => e.slug);
1458
+ expect(new Set(everInjectedSlugs)).toEqual(
1459
+ new Set(["alice-vscode", "carol-jazz"]),
1460
+ );
1461
+ });
1462
+
1463
+ test("a throw before renderInjectionBlock still flushes telemetry as `mode: errored` and re-throws", async () => {
1464
+ // The activation-state save throws — the most realistic non-render
1465
+ // failure mode (transient SQLite write error mid-injection). The
1466
+ // `injectMemoryV2Block` outer try/finally must (a) flush an activation
1467
+ // log row tagged `mode: "errored"` so silent failures stay observable
1468
+ // in the database, and (b) re-throw so callers (e.g. `prepareMemory`'s
1469
+ // outer catch) see the original error and can degrade gracefully.
1470
+ activationStoreState.saveShouldThrow = true;
1471
+ stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
1472
+
1473
+ let threw: unknown = undefined;
1474
+ try {
1475
+ await injectMemoryV2Block({
1476
+ database: db,
1477
+ conversationId: "conv-1",
1478
+ currentTurn: 1,
1479
+ userMessage: "Alice's editor",
1480
+ assistantMessage: "",
1481
+ nowText: "Now",
1482
+ messageId: "msg-1",
1483
+ config: makeConfig(),
1484
+ });
1485
+ } catch (err) {
1486
+ threw = err;
1487
+ }
1488
+
1489
+ // The original error propagates to the caller.
1490
+ expect(threw).toBeInstanceOf(Error);
1491
+ expect((threw as Error).message).toContain(
1492
+ "simulated activation-store save failure",
1493
+ );
1494
+
1495
+ // A telemetry row was still written, tagged `errored`. `concepts` is
1496
+ // empty because the throw fired before the row-builder ran — that's
1497
+ // expected and documented as part of the contract.
1498
+ expect(telemetryState.recordCalls.length).toBe(1);
1499
+ const row = telemetryState.recordCalls[0] as {
1500
+ mode: string;
1501
+ conversationId: string;
1502
+ turn: number;
1503
+ concepts: unknown[];
1504
+ };
1505
+ expect(row.mode).toBe("errored");
1506
+ expect(row.conversationId).toBe("conv-1");
1507
+ expect(row.turn).toBe(1);
1508
+ expect(row.concepts).toEqual([]);
1509
+ });
1510
+
1511
+ test("activation pipeline routes through `finalizeInjection` — telemetry shape and config snapshot match the contract", async () => {
1512
+ // Pure-refactor regression check: `injectMemoryV2Block` now delegates the
1513
+ // tail (state save + render + telemetry finalization + log write) to a
1514
+ // private `finalizeInjection` helper. This test asserts the helper is
1515
+ // exercised by verifying `recordMemoryV2ActivationLog` is called with the
1516
+ // same arg shape as before — same conversationId/turn/mode, same config
1517
+ // snapshot, and a fully populated concept row whose status was finalized
1518
+ // to `"injected"` on the freshly-attached slug.
1519
+ stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
1520
+
1521
+ const result = await injectMemoryV2Block({
1522
+ database: db,
1523
+ conversationId: "conv-finalize",
1524
+ currentTurn: 7,
1525
+ userMessage: "Alice's editor",
1526
+ assistantMessage: "",
1527
+ nowText: "Now",
1528
+ messageId: "msg-finalize",
1529
+ config: makeConfig(),
1530
+ });
1531
+
1532
+ // The helper rendered + persisted just like the original tail did.
1533
+ expect(result.block).toContain("alice-vscode");
1534
+ expect(result.toInject).toEqual(["alice-vscode"]);
1535
+
1536
+ expect(telemetryState.recordCalls.length).toBe(1);
1537
+ const row = telemetryState.recordCalls[0] as {
1538
+ conversationId: string;
1539
+ turn: number;
1540
+ mode: string;
1541
+ concepts: Array<{
1542
+ slug: string;
1543
+ status: string;
1544
+ finalActivation: number;
1545
+ }>;
1546
+ config: {
1547
+ d: number;
1548
+ c_user: number;
1549
+ c_assistant: number;
1550
+ c_now: number;
1551
+ k: number;
1552
+ hops: number;
1553
+ top_k: number;
1554
+ epsilon: number;
1555
+ };
1556
+ };
1557
+ expect(row.conversationId).toBe("conv-finalize");
1558
+ expect(row.turn).toBe(7);
1559
+ expect(row.mode).toBe("per-turn");
1560
+ // Config snapshot must include all eight tunables — proves the helper is
1561
+ // pulling from `config.memory.v2` rather than synthesizing a partial.
1562
+ expect(Object.keys(row.config).sort()).toEqual(
1563
+ [
1564
+ "c_assistant",
1565
+ "c_now",
1566
+ "c_user",
1567
+ "d",
1568
+ "epsilon",
1569
+ "hops",
1570
+ "k",
1571
+ "top_k",
1572
+ ].sort(),
1573
+ );
1574
+ // Status finalization ran inside the helper — alice was selected and
1575
+ // rendered, so its row reads `injected`.
1576
+ const aliceRow = row.concepts.find((c) => c.slug === "alice-vscode");
1577
+ expect(aliceRow?.status).toBe("injected");
1578
+ });
1579
+
1580
+ // ---------------------------------------------------------------------------
1581
+ // Router mode (flag-gated)
1582
+ // ---------------------------------------------------------------------------
1583
+
1584
+ describe("router mode", () => {
1585
+ test("flag-on: router-selected slugs render and append to everInjected", async () => {
1586
+ // Router picks alice. The activation pipeline never runs — we don't
1587
+ // stage any qdrant responses here, and that's intentional. The
1588
+ // candidate set comes straight from the router's `selectedSlugs`.
1589
+ routerState.nextResult = {
1590
+ selectedSlugs: ["alice-vscode"],
1591
+ failureReason: null,
1592
+ };
1593
+
1594
+ const result = await injectMemoryV2Block({
1595
+ database: db,
1596
+ conversationId: "conv-router-1",
1597
+ currentTurn: 1,
1598
+ userMessage: "Tell me about Alice",
1599
+ assistantMessage: "",
1600
+ nowText: "Now",
1601
+ messageId: "msg-1",
1602
+ config: makeConfig({ router: { enabled: true } }),
1603
+ });
1604
+
1605
+ expect(routerState.callCount).toBe(1);
1606
+ expect(result.toInject).toEqual(["alice-vscode"]);
1607
+ expect(result.block).not.toBeNull();
1608
+ expect(result.block).toContain("# memory/concepts/alice-vscode.md");
1609
+
1610
+ const persisted = await hydrate(db, "conv-router-1");
1611
+ expect(persisted!.everInjected).toEqual([
1612
+ { slug: "alice-vscode", turn: 1 },
1613
+ ]);
1614
+ // Router mode persists an empty sparse activation map — the router
1615
+ // does not compute spreading-activation scores.
1616
+ expect(persisted!.state).toEqual({});
1617
+
1618
+ // Telemetry: success rows get `mode: "router"` and `source: "router"`,
1619
+ // with all activation fields zeroed.
1620
+ expect(telemetryState.recordCalls.length).toBe(1);
1621
+ const row = telemetryState.recordCalls[0] as {
1622
+ mode: string;
1623
+ concepts: Array<{
1624
+ slug: string;
1625
+ source: string;
1626
+ status: string;
1627
+ finalActivation: number;
1628
+ ownActivation: number;
1629
+ }>;
1630
+ };
1631
+ expect(row.mode).toBe("router");
1632
+ const aliceRow = row.concepts.find((c) => c.slug === "alice-vscode");
1633
+ expect(aliceRow).toBeDefined();
1634
+ expect(aliceRow!.source).toBe("router");
1635
+ expect(aliceRow!.status).toBe("injected");
1636
+ expect(aliceRow!.finalActivation).toBe(0);
1637
+ expect(aliceRow!.ownActivation).toBe(0);
1638
+ });
1639
+
1640
+ test("flag-on: router failure logs warn, writes mode:`errored` telemetry, returns null block", async () => {
1641
+ routerState.nextResult = {
1642
+ selectedSlugs: [],
1643
+ failureReason: "api_error",
1644
+ };
1645
+
1646
+ const result = await injectMemoryV2Block({
1647
+ database: db,
1648
+ conversationId: "conv-router-fail",
1649
+ currentTurn: 3,
1650
+ userMessage: "anything",
1651
+ assistantMessage: "ok",
1652
+ nowText: "Now",
1653
+ messageId: "msg-fail",
1654
+ config: makeConfig({ router: { enabled: true } }),
1655
+ });
1656
+
1657
+ expect(result.block).toBeNull();
1658
+ expect(result.toInject).toEqual([]);
1659
+
1660
+ // Stub state still advanced.
1661
+ const persisted = await hydrate(db, "conv-router-fail");
1662
+ expect(persisted).not.toBeNull();
1663
+ expect(persisted!.currentTurn).toBe(3);
1664
+ expect(persisted!.messageId).toBe("msg-fail");
1665
+ expect(persisted!.state).toEqual({});
1666
+ expect(persisted!.everInjected).toEqual([]);
1667
+
1668
+ // Single telemetry row with `mode: "errored"` (not `"router"`).
1669
+ expect(telemetryState.recordCalls.length).toBe(1);
1670
+ const row = telemetryState.recordCalls[0] as {
1671
+ mode: string;
1672
+ conversationId: string;
1673
+ turn: number;
1674
+ concepts: unknown[];
1675
+ };
1676
+ expect(row.mode).toBe("errored");
1677
+ expect(row.conversationId).toBe("conv-router-fail");
1678
+ expect(row.turn).toBe(3);
1679
+ expect(row.concepts).toEqual([]);
1680
+ });
1681
+
1682
+ test("flag-on: router abstention (empty selectedSlugs, no failure) writes mode:`router` row with no injected pages", async () => {
1683
+ routerState.nextResult = {
1684
+ selectedSlugs: [],
1685
+ failureReason: null,
1686
+ };
1687
+
1688
+ const result = await injectMemoryV2Block({
1689
+ database: db,
1690
+ conversationId: "conv-router-abstain",
1691
+ currentTurn: 1,
1692
+ userMessage: "small talk",
1693
+ assistantMessage: "",
1694
+ nowText: "Now",
1695
+ messageId: "msg-abstain",
1696
+ config: makeConfig({ router: { enabled: true } }),
1697
+ });
1698
+
1699
+ expect(result.block).toBeNull();
1700
+ expect(result.toInject).toEqual([]);
1701
+
1702
+ // No prior everInjected to dedup against, so toInject is empty and
1703
+ // nothing renders. State still advanced.
1704
+ const persisted = await hydrate(db, "conv-router-abstain");
1705
+ expect(persisted!.everInjected).toEqual([]);
1706
+ expect(persisted!.currentTurn).toBe(1);
1707
+
1708
+ // Telemetry: `mode: "router"` row with zero injected pages.
1709
+ expect(telemetryState.recordCalls.length).toBe(1);
1710
+ const row = telemetryState.recordCalls[0] as {
1711
+ mode: string;
1712
+ concepts: Array<{ slug: string; status: string }>;
1713
+ };
1714
+ expect(row.mode).toBe("router");
1715
+ const injectedCount = row.concepts.filter(
1716
+ (c) => c.status === "injected",
1717
+ ).length;
1718
+ expect(injectedCount).toBe(0);
1719
+ });
1720
+
1721
+ test("flag-on: router-selected slug whose page is missing on disk records `page_missing` and is NOT added to everInjected", async () => {
1722
+ routerState.nextResult = {
1723
+ selectedSlugs: ["phantom-router-slug"],
1724
+ failureReason: null,
1725
+ };
1726
+
1727
+ const result = await injectMemoryV2Block({
1728
+ database: db,
1729
+ conversationId: "conv-router-missing",
1730
+ currentTurn: 1,
1731
+ userMessage: "phantom",
1732
+ assistantMessage: "",
1733
+ nowText: "Now",
1734
+ messageId: "msg-missing",
1735
+ config: makeConfig({ router: { enabled: true } }),
1736
+ });
1737
+
1738
+ // No backing page → block collapses to null.
1739
+ expect(result.block).toBeNull();
1740
+ // toInject mirrors `newlyInjected` from `finalizeInjection` — the
1741
+ // missing slug still flowed through `slugsToRender` so it's recorded
1742
+ // here (matching the activation-mode phantom-slug contract).
1743
+ expect(result.toInject).toEqual(["phantom-router-slug"]);
1744
+
1745
+ // Activation-mode parity: the phantom slug DOES land in everInjected
1746
+ // so we don't infinite-retry it. (This matches the behavior the
1747
+ // existing `returns null block when toInject slugs all reference
1748
+ // missing pages` test asserts for activation mode.)
1749
+ const persisted = await hydrate(db, "conv-router-missing");
1750
+ expect(persisted!.everInjected).toEqual([
1751
+ { slug: "phantom-router-slug", turn: 1 },
1752
+ ]);
1753
+
1754
+ // Telemetry: `status: "page_missing"` for the phantom slug.
1755
+ expect(telemetryState.recordCalls.length).toBe(1);
1756
+ const row = telemetryState.recordCalls[0] as {
1757
+ mode: string;
1758
+ concepts: Array<{ slug: string; status: string; source: string }>;
1759
+ };
1760
+ expect(row.mode).toBe("router");
1761
+ const phantom = row.concepts.find(
1762
+ (c) => c.slug === "phantom-router-slug",
1763
+ );
1764
+ expect(phantom).toBeDefined();
1765
+ expect(phantom!.status).toBe("page_missing");
1766
+ expect(phantom!.source).toBe("router");
1767
+ });
1768
+
1769
+ test("flag-on: router re-picking a prior-everInjected slug does NOT re-render it; non-overlapping picks render and append to everInjected", async () => {
1770
+ // Turn 1: router picks alice. Standard append.
1771
+ routerState.nextResult = {
1772
+ selectedSlugs: ["alice-vscode"],
1773
+ failureReason: null,
1774
+ };
1775
+ const turn1 = await injectMemoryV2Block({
1776
+ database: db,
1777
+ conversationId: "conv-router-dedup",
1778
+ currentTurn: 1,
1779
+ userMessage: "Tell me about Alice",
1780
+ assistantMessage: "",
1781
+ nowText: "Now",
1782
+ messageId: "msg-1",
1783
+ config: makeConfig({ router: { enabled: true } }),
1784
+ });
1785
+ expect(turn1.toInject).toEqual(["alice-vscode"]);
1786
+
1787
+ // Turn 2: router re-picks alice (the "re-anchor" prompt branch) AND
1788
+ // adds bob. The block must NOT contain alice's body — her cached
1789
+ // attachment from turn 1 is still on the prior user message — but
1790
+ // must contain bob's.
1791
+ telemetryState.recordCalls.length = 0;
1792
+ routerState.nextResult = {
1793
+ selectedSlugs: ["alice-vscode", "bob-coffee"],
1794
+ failureReason: null,
1795
+ };
1796
+ const turn2 = await injectMemoryV2Block({
1797
+ database: db,
1798
+ conversationId: "conv-router-dedup",
1799
+ currentTurn: 2,
1800
+ userMessage: "And Bob?",
1801
+ assistantMessage: "Sure",
1802
+ nowText: "Now",
1803
+ messageId: "msg-2",
1804
+ config: makeConfig({ router: { enabled: true } }),
1805
+ });
1806
+
1807
+ // Re-picked alice was deduped; only bob is freshly injected.
1808
+ expect(turn2.toInject).toEqual(["bob-coffee"]);
1809
+ expect(turn2.block).not.toBeNull();
1810
+ expect(turn2.block).toContain("# memory/concepts/bob-coffee.md");
1811
+ expect(turn2.block).toContain("Bob takes his coffee");
1812
+ expect(turn2.block).not.toContain("VS Code");
1813
+ expect(turn2.block).not.toContain("# memory/concepts/alice-vscode.md");
1814
+
1815
+ // everInjected only gained bob — alice was already there.
1816
+ const persisted = await hydrate(db, "conv-router-dedup");
1817
+ expect(persisted!.everInjected).toEqual([
1818
+ { slug: "alice-vscode", turn: 1 },
1819
+ { slug: "bob-coffee", turn: 2 },
1820
+ ]);
1821
+ });
1822
+
1823
+ test("flag-on: telemetry distinguishes `source: router` (router picks) from `source: carry_over` (prior-everInjected slugs the router did not re-pick)", async () => {
1824
+ // Turn 1: seed everInjected with alice.
1825
+ routerState.nextResult = {
1826
+ selectedSlugs: ["alice-vscode"],
1827
+ failureReason: null,
1828
+ };
1829
+ await injectMemoryV2Block({
1830
+ database: db,
1831
+ conversationId: "conv-router-source",
1832
+ currentTurn: 1,
1833
+ userMessage: "Alice",
1834
+ assistantMessage: "",
1835
+ nowText: "Now",
1836
+ messageId: "msg-1",
1837
+ config: makeConfig({ router: { enabled: true } }),
1838
+ });
1839
+ telemetryState.recordCalls.length = 0;
1840
+
1841
+ // Turn 2: router picks bob only. alice is still in everInjected but
1842
+ // not re-picked — her telemetry row must read `source: carry_over`,
1843
+ // not `source: router`.
1844
+ routerState.nextResult = {
1845
+ selectedSlugs: ["bob-coffee"],
1846
+ failureReason: null,
1847
+ };
1848
+ await injectMemoryV2Block({
1849
+ database: db,
1850
+ conversationId: "conv-router-source",
1851
+ currentTurn: 2,
1852
+ userMessage: "Bob",
1853
+ assistantMessage: "",
1854
+ nowText: "Now",
1855
+ messageId: "msg-2",
1856
+ config: makeConfig({ router: { enabled: true } }),
1857
+ });
1858
+
1859
+ expect(telemetryState.recordCalls.length).toBe(1);
1860
+ const row = telemetryState.recordCalls[0] as {
1861
+ mode: string;
1862
+ concepts: Array<{ slug: string; source: string; status: string }>;
1863
+ };
1864
+ expect(row.mode).toBe("router");
1865
+ const aliceRow = row.concepts.find((c) => c.slug === "alice-vscode");
1866
+ const bobRow = row.concepts.find((c) => c.slug === "bob-coffee");
1867
+ expect(aliceRow).toBeDefined();
1868
+ expect(bobRow).toBeDefined();
1869
+ expect(aliceRow!.source).toBe("carry_over");
1870
+ expect(aliceRow!.status).toBe("in_context");
1871
+ expect(bobRow!.source).toBe("router");
1872
+ expect(bobRow!.status).toBe("injected");
1873
+ });
1874
+
1875
+ test("flag-off (default): activation pipeline still runs unchanged", async () => {
1876
+ // Regression check — with the router flag explicitly off (the
1877
+ // production default), `runRouter` must never be called and the
1878
+ // activation pipeline drives the selection just like before.
1879
+ stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
1880
+ routerState.nextResult = {
1881
+ selectedSlugs: ["should-not-be-used"],
1882
+ failureReason: null,
1883
+ };
1884
+
1885
+ const result = await injectMemoryV2Block({
1886
+ database: db,
1887
+ conversationId: "conv-flag-off",
1888
+ currentTurn: 1,
1889
+ userMessage: "Alice's editor",
1890
+ assistantMessage: "",
1891
+ nowText: "Now",
1892
+ messageId: "msg-1",
1893
+ config: makeConfig({ router: { enabled: false } }),
1894
+ });
1895
+
1896
+ // Router was not called.
1897
+ expect(routerState.callCount).toBe(0);
1898
+ // Activation pipeline produced its normal result.
1899
+ expect(result.toInject).toEqual(["alice-vscode"]);
1900
+ expect(result.block).toContain("# memory/concepts/alice-vscode.md");
1901
+
1902
+ // Telemetry row carries the activation mode, not router.
1903
+ expect(telemetryState.recordCalls.length).toBe(1);
1904
+ const row = telemetryState.recordCalls[0] as { mode: string };
1905
+ expect(row.mode).toBe("per-turn");
1906
+ });
1907
+ });
1290
1908
  });