@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
@@ -18,11 +18,16 @@
18
18
  // fusion using `dense_weight` / `sparse_weight` from `config.memory.v2`,
19
19
  // which RRF fusion would discard.
20
20
 
21
+ import { existsSync } from "node:fs";
22
+ import { mkdir, unlink, writeFile } from "node:fs/promises";
23
+ import { dirname, join } from "node:path";
24
+
21
25
  import { QdrantClient as QdrantRestClient } from "@qdrant/js-client-rest";
22
26
  import { v5 as uuidv5 } from "uuid";
23
27
 
24
28
  import { getConfig } from "../../config/loader.js";
25
29
  import { getLogger } from "../../util/logger.js";
30
+ import { getDataDir } from "../../util/platform.js";
26
31
  import type { SparseEmbedding } from "../embedding-types.js";
27
32
  import { resolveQdrantUrl } from "../qdrant-client.js";
28
33
 
@@ -86,6 +91,49 @@ let _collectionReadyPromise: Promise<{ migrated: boolean }> | null = null;
86
91
  const REQUIRED_DENSE_VECTORS = ["dense", "summary_dense"] as const;
87
92
  const REQUIRED_SPARSE_VECTORS = ["sparse", "summary_sparse"] as const;
88
93
 
94
+ /**
95
+ * Marker file written before the destructive collection-recreate path runs,
96
+ * cleared by the lifecycle hook once the reembed job has been enqueued.
97
+ *
98
+ * The sentinel exists to close a narrow data-loss window in
99
+ * `ensureConceptPageCollectionOnce`: a transient Qdrant failure between
100
+ * `deleteCollection` and `createCollection` would otherwise lose the
101
+ * "needs reembed" signal — `migrated` is reinitialized on the next call,
102
+ * any subsequent caller (e.g. an upsert) recreates the collection empty,
103
+ * and the lifecycle hook never enqueues the backfill. By persisting the
104
+ * intent on disk *before* delete, the signal survives crashes and
105
+ * intra-process retries: every later `ensureConceptPageCollection` call
106
+ * returns `migrated: true` until the lifecycle hook enqueues the reembed
107
+ * and clears the sentinel.
108
+ */
109
+ const REEMBED_SENTINEL_FILENAME = ".memory-v2-reembed-required";
110
+
111
+ function reembedSentinelPath(): string {
112
+ return join(getDataDir(), REEMBED_SENTINEL_FILENAME);
113
+ }
114
+
115
+ function reembedSentinelExists(): boolean {
116
+ return existsSync(reembedSentinelPath());
117
+ }
118
+
119
+ async function writeReembedSentinel(): Promise<void> {
120
+ const path = reembedSentinelPath();
121
+ await mkdir(dirname(path), { recursive: true });
122
+ await writeFile(path, "");
123
+ }
124
+
125
+ /**
126
+ * Remove the reembed sentinel after the lifecycle hook has enqueued the
127
+ * `memory_v2_reembed` job. Idempotent — missing-file is not an error.
128
+ */
129
+ export async function clearReembedSentinel(): Promise<void> {
130
+ try {
131
+ await unlink(reembedSentinelPath());
132
+ } catch (err) {
133
+ if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err;
134
+ }
135
+ }
136
+
89
137
  /** Lazily create a Qdrant REST client bound to the resolved URL. */
90
138
  function getClient(): QdrantRestClient {
91
139
  if (_client) return _client;
@@ -127,7 +175,11 @@ async function ensureConceptPageCollectionOnce(): Promise<{
127
175
  const vectorSize = config.memory.qdrant.vectorSize;
128
176
  const onDisk = config.memory.qdrant.onDisk;
129
177
 
130
- let migrated = false;
178
+ // A leftover sentinel means a prior call deleted the collection but never
179
+ // got to enqueue the reembed (e.g. createCollection threw, or the process
180
+ // died mid-rebuild). Carry that signal forward until the lifecycle hook
181
+ // clears it.
182
+ let migrated = reembedSentinelExists();
131
183
 
132
184
  try {
133
185
  const exists = await client.collectionExists(MEMORY_V2_COLLECTION);
@@ -156,6 +208,10 @@ async function ensureConceptPageCollectionOnce(): Promise<{
156
208
  { collection: MEMORY_V2_COLLECTION, missingNamedVectors: missing },
157
209
  "Memory v2 concept-page collection schema drift detected — deleting and recreating; embeddings will be regenerated by background reembed",
158
210
  );
211
+ // Persist the reembed intent BEFORE the destructive delete so a crash
212
+ // (or transient createCollection failure) between delete and recreate
213
+ // still triggers reembed on the next ensure call.
214
+ await writeReembedSentinel();
159
215
  await client.deleteCollection(MEMORY_V2_COLLECTION);
160
216
  migrated = true;
161
217
  // Fall through to creation below.
@@ -279,10 +335,17 @@ export async function upsertConceptPageEmbedding(params: {
279
335
  sparse: SparseEmbedding;
280
336
  summary?: { dense: number[]; sparse: SparseEmbedding };
281
337
  updatedAt: number;
338
+ /**
339
+ * Optional payload discriminator. Used to distinguish skill-seeded points
340
+ * (`kind: "skill"`) from user-authored concept pages so namespace pruning
341
+ * via {@link pruneSlugsWithPrefixExcept} can scope deletes to a single kind.
342
+ * Omitted for plain concept pages.
343
+ */
344
+ kind?: string;
282
345
  }): Promise<void> {
283
346
  await ensureConceptPageCollection();
284
347
 
285
- const { slug, dense, sparse, summary, updatedAt } = params;
348
+ const { slug, dense, sparse, summary, updatedAt, kind } = params;
286
349
  const client = getClient();
287
350
  const pointId = pointIdForSlug(slug);
288
351
 
@@ -296,6 +359,9 @@ export async function upsertConceptPageEmbedding(params: {
296
359
  vector.summary_sparse = summary.sparse;
297
360
  }
298
361
 
362
+ const payload: Record<string, unknown> = { slug, updated_at: updatedAt };
363
+ if (kind !== undefined) payload.kind = kind;
364
+
299
365
  const upsertOnce = () =>
300
366
  client.upsert(MEMORY_V2_COLLECTION, {
301
367
  wait: true,
@@ -303,7 +369,7 @@ export async function upsertConceptPageEmbedding(params: {
303
369
  {
304
370
  id: pointId,
305
371
  vector,
306
- payload: { slug, updated_at: updatedAt },
372
+ payload,
307
373
  },
308
374
  ],
309
375
  });
@@ -352,17 +418,25 @@ export async function deleteConceptPageEmbedding(slug: string): Promise<void> {
352
418
  * since skills now share the concept-page collection rather than living in a
353
419
  * dedicated one.
354
420
  *
421
+ * `kind` scopes pruning to a payload discriminator: only points whose
422
+ * `payload.kind` matches are eligible for deletion. This is critical because
423
+ * `validateSlug` permits user-authored concept pages slugged like
424
+ * `skills/foo`; without a kind filter they would collide with the skill
425
+ * namespace and be repeatedly pruned every seed run.
426
+ *
355
427
  * Idempotent: when the live `<prefix>*` slugs already match `activeSuffixes`,
356
428
  * the function performs a single scroll and no deletes.
357
429
  */
358
430
  export async function pruneSlugsWithPrefixExcept(
359
431
  prefix: string,
360
432
  activeSuffixes: readonly string[],
433
+ options: { kind?: string } = {},
361
434
  ): Promise<void> {
362
435
  await ensureConceptPageCollection();
363
436
 
364
437
  const client = getClient();
365
438
  const activeSet = new Set(activeSuffixes);
439
+ const requiredKind = options.kind;
366
440
 
367
441
  const doPrune = async (): Promise<void> => {
368
442
  const stalePointIds: Array<string | number> = [];
@@ -377,9 +451,15 @@ export async function pruneSlugsWithPrefixExcept(
377
451
  ...(offset !== undefined ? { offset } : {}),
378
452
  });
379
453
  for (const point of result.points) {
380
- const slug = (point.payload as { slug?: unknown } | null)?.slug;
454
+ const payload = point.payload as {
455
+ slug?: unknown;
456
+ kind?: unknown;
457
+ } | null;
458
+ const slug = payload?.slug;
381
459
  if (typeof slug !== "string") continue;
382
460
  if (!slug.startsWith(prefix)) continue;
461
+ if (requiredKind !== undefined && payload?.kind !== requiredKind)
462
+ continue;
383
463
  const suffix = slug.slice(prefix.length);
384
464
  if (!activeSet.has(suffix)) {
385
465
  stalePointIds.push(point.id);
@@ -435,6 +515,22 @@ export async function countConceptPagePoints(): Promise<number> {
435
515
  }
436
516
  }
437
517
 
518
+ /**
519
+ * Probe whether the v2 concept-page collection currently exists in Qdrant
520
+ * **without** triggering creation. Read-only diagnostics use this to avoid
521
+ * the side effect of bootstrapping storage just by inspecting it.
522
+ */
523
+ export async function conceptPageCollectionExists(): Promise<boolean> {
524
+ const client = getClient();
525
+ try {
526
+ const result = await client.collectionExists(MEMORY_V2_COLLECTION);
527
+ return result.exists;
528
+ } catch (err) {
529
+ if (isCollectionMissing(err)) return false;
530
+ throw err;
531
+ }
532
+ }
533
+
438
534
  /**
439
535
  * Best-effort delete of the legacy `memory_v2_skills` Qdrant collection. Skill
440
536
  * embeddings now live alongside concept pages in `memory_v2_concept_pages`
@@ -579,89 +675,6 @@ export async function hybridQueryConceptPages(
579
675
  return Array.from(merged.values());
580
676
  }
581
677
 
582
- /**
583
- * Page through the v2 concept-page collection and return up to `maxSamples`
584
- * stored dense vectors. Used by the anisotropy-fit pipeline to compute a
585
- * corpus mean + top-k principal components without re-embedding every page.
586
- *
587
- * Sparse vectors are skipped — anisotropy is a dense-embedding phenomenon, and
588
- * pulling the sparse side would just inflate the response. Payload is also
589
- * skipped because the fit doesn't need slug identity.
590
- *
591
- * Returns an empty array when the collection is empty or missing. Caller
592
- * decides what to do (typically: surface a "no vectors to fit" error).
593
- */
594
- export async function sampleConceptPageDenseVectors(
595
- maxSamples: number,
596
- ): Promise<number[][]> {
597
- if (maxSamples <= 0) return [];
598
- await ensureConceptPageCollection();
599
-
600
- const client = getClient();
601
- const out: number[][] = [];
602
- let offset: string | number | undefined = undefined;
603
- // Same pagination guard pattern as the rest of the file — bounds the loop
604
- // even if Qdrant somehow keeps handing back a non-null offset.
605
- const maxIterations = 10_000;
606
- const batchSize = Math.min(256, maxSamples);
607
-
608
- for (let i = 0; i < maxIterations; i++) {
609
- if (out.length >= maxSamples) break;
610
- const remaining = maxSamples - out.length;
611
- let result;
612
- try {
613
- result = await client.scroll(MEMORY_V2_COLLECTION, {
614
- limit: Math.min(batchSize, remaining),
615
- with_payload: false,
616
- // Fetch only the dense named vector — sparse is irrelevant for
617
- // anisotropy correction.
618
- with_vector: ["dense"],
619
- ...(offset !== undefined ? { offset } : {}),
620
- });
621
- } catch (err) {
622
- if (isCollectionMissing(err)) {
623
- _collectionReady = false;
624
- return out;
625
- }
626
- throw err;
627
- }
628
-
629
- for (const point of result.points) {
630
- const v = extractDenseVector(point.vector);
631
- if (v) out.push(v);
632
- if (out.length >= maxSamples) break;
633
- }
634
-
635
- const next = result.next_page_offset;
636
- if (next == null) break;
637
- offset = typeof next === "string" ? next : (next as number);
638
- }
639
-
640
- return out;
641
- }
642
-
643
- /**
644
- * Pull the `dense` named-vector payload out of a Qdrant point. Defensively
645
- * handles both the named-vector shape (`{ dense: [...] }`) and the legacy
646
- * unnamed-vector shape (`number[]`) so older collection layouts don't trip
647
- * the sampler. Returns `null` for shapes we don't recognise.
648
- */
649
- function extractDenseVector(vector: unknown): number[] | null {
650
- if (Array.isArray(vector)) {
651
- if (vector.every((n) => typeof n === "number")) {
652
- return vector as number[];
653
- }
654
- return null;
655
- }
656
- if (vector && typeof vector === "object") {
657
- const dense = (vector as { dense?: unknown }).dense;
658
- if (Array.isArray(dense) && dense.every((n) => typeof n === "number")) {
659
- return dense as number[];
660
- }
661
- }
662
- return null;
663
- }
664
-
665
678
  /**
666
679
  * Detect "collection not found" errors so callers can reset readiness and
667
680
  * retry after an external deletion (e.g. workspace reset).
@@ -10,8 +10,8 @@ import { readPage } from "./page-store.js";
10
10
 
11
11
  const log = getLogger("memory-v2-reranker");
12
12
 
13
- // ~512-token model context for bge-reranker-base; cap input to bound payload.
14
- const PASSAGE_CHAR_CAP = 240;
13
+ // Cap passage input to bound batched payload size and tokenization cost.
14
+ const PASSAGE_CHAR_CAP = 1500;
15
15
 
16
16
  interface CacheEntry {
17
17
  scores: Map<string, number>;
@@ -22,9 +22,16 @@ const CACHE_TTL_MS = 2 * 60 * 1000;
22
22
  const CACHE_MAX_ENTRIES = 64;
23
23
  const cache = new Map<string, CacheEntry>();
24
24
 
25
- function cacheKey(query: string, slugs: readonly string[]): string {
25
+ function cacheKey(
26
+ query: string,
27
+ slugs: readonly string[],
28
+ model: string,
29
+ dtype: string,
30
+ ): string {
26
31
  const sorted = [...slugs].sort().join("\0");
27
- return createHash("sha256").update(`${query}\0${sorted}`).digest("hex");
32
+ return createHash("sha256")
33
+ .update(`${model}\0${dtype}\0${query}\0${sorted}`)
34
+ .digest("hex");
28
35
  }
29
36
 
30
37
  function evictExpired(now: number): void {
@@ -73,6 +80,7 @@ export async function rerankCandidates(
73
80
  if (queries.length === 0) return [];
74
81
  if (candidates.length === 0) return queries.map(() => new Map());
75
82
 
83
+ const { model, dtype } = config.memory.v2.rerank;
76
84
  const now = Date.now();
77
85
  evictExpired(now);
78
86
 
@@ -84,7 +92,7 @@ export async function rerankCandidates(
84
92
  results[i] = new Map();
85
93
  continue;
86
94
  }
87
- const key = cacheKey(q, candidates);
95
+ const key = cacheKey(q, candidates, model, dtype);
88
96
  const cached = cache.get(key);
89
97
  if (cached) {
90
98
  // Refresh insertion order so frequently-hit entries survive eviction.
@@ -137,7 +145,6 @@ export async function rerankCandidates(
137
145
  }
138
146
  }
139
147
 
140
- const { model, dtype } = config.memory.v2.rerank;
141
148
  let scores: number[];
142
149
  try {
143
150
  const backend = getOrCreateRerankBackend(model, dtype);
@@ -162,7 +169,7 @@ export async function rerankCandidates(
162
169
  result.set(slugsForPassages[i], Math.max(0, Math.min(1, s)));
163
170
  }
164
171
  results[qi] = result;
165
- cache.set(cacheKey(queries[qi], candidates), {
172
+ cache.set(cacheKey(queries[qi], candidates, model, dtype), {
166
173
  scores: new Map(result),
167
174
  ts: now,
168
175
  });
@@ -0,0 +1,322 @@
1
+ /**
2
+ * Memory v2 — Sonnet router orchestration.
3
+ *
4
+ * Replaces the per-turn spreading-activation page selector with a single LLM
5
+ * call. Given the rendered page index, the most recent user/assistant turn,
6
+ * and the list of pages already injected on prior turns, the router returns a
7
+ * small set of concept-page IDs to inject for the next reply.
8
+ *
9
+ * The design mirrors `sweep-job.ts`:
10
+ * - resolve the configured provider via `getConfiguredProvider`,
11
+ * - call `provider.sendMessage` with a forced `tool_choice`,
12
+ * - validate the tool input via Zod,
13
+ * - map back to slugs and let the caller drive injection.
14
+ *
15
+ * Cache strategy. Two 1h ephemeral breakpoints carry the bulk of the
16
+ * routing cost across turns:
17
+ * 1. The last text block of the system prompt — the page index is the
18
+ * single largest input and changes only when concept pages are edited.
19
+ * Auto-applied by the Anthropic provider at the configured 1h TTL.
20
+ * 2. The first user-message block (`<now>`) — stable across most turns
21
+ * since NOW.md only changes when the model rewrites it. We set the 1h
22
+ * TTL explicitly here to match the provider-side breakpoints; the
23
+ * default 5m would force unnecessary cache re-creation.
24
+ * The Anthropic provider also auto-applies a 1h breakpoint on the last text
25
+ * block of a turn-starting user message, so the trailing uncached block does
26
+ * not need an explicit `cache_control`.
27
+ *
28
+ * This module is pure orchestration — it does not mutate activation state,
29
+ * write any files, or update the conversation. PR 10 wires it into
30
+ * `injectMemoryV2Block`; until then nothing in the daemon calls it.
31
+ */
32
+
33
+ import { z } from "zod";
34
+
35
+ import type { AssistantConfig } from "../../config/types.js";
36
+ import {
37
+ getAssistantName,
38
+ resolveUserName,
39
+ } from "../../daemon/identity-helpers.js";
40
+ import {
41
+ extractToolUse,
42
+ getConfiguredProvider,
43
+ } from "../../providers/provider-send-message.js";
44
+ import type {
45
+ ContentBlock,
46
+ Message,
47
+ ToolDefinition,
48
+ } from "../../providers/types.js";
49
+ import { getLogger } from "../../util/logger.js";
50
+ import { getPageIndex } from "./page-index.js";
51
+ import { resolveRouterPrompt } from "./prompts/router.js";
52
+ import type { EverInjectedEntry } from "./types.js";
53
+
54
+ const log = getLogger("memory-v2-router");
55
+
56
+ /**
57
+ * Reasons the router may fall short of returning a usable selection. The
58
+ * caller (PR 10) maps each reason to a fallback path; the closed string-
59
+ * literal union lets that dispatch stay exhaustive without a brittle
60
+ * free-form string match.
61
+ */
62
+ export type RouterFailureReason =
63
+ | "no_provider"
64
+ | "tool_use_missing"
65
+ | "schema_mismatch"
66
+ | "api_error"
67
+ | "empty_index";
68
+
69
+ /**
70
+ * Result of a single router call. `selectedSlugs` preserves the order the
71
+ * model returned and is already capped at `config.memory.v2.router.max_page_ids`
72
+ * with out-of-range IDs dropped.
73
+ */
74
+ export interface RouterResult {
75
+ /** Selected page slugs in the order the model returned them. */
76
+ selectedSlugs: string[];
77
+ /** `null` on success; one of the failure reasons above otherwise. */
78
+ failureReason: RouterFailureReason | null;
79
+ }
80
+
81
+ /** Tool name forced via `tool_choice`. Single shared constant so tests can match it. */
82
+ const ROUTER_TOOL_NAME = "select_pages_to_inject";
83
+
84
+ /**
85
+ * Build the tool definition handed to the provider. The JSON schema is what
86
+ * the model sees; the Zod schema below validates the response at runtime.
87
+ *
88
+ * `maxItems` mirrors the runtime `config.memory.v2.router.max_page_ids` cap
89
+ * so the model is told the same upper bound the post-call truncation
90
+ * enforces. Built per-call rather than module-scoped because the cap is
91
+ * configurable per workspace.
92
+ */
93
+ function buildRouterTool(maxPageIds: number): ToolDefinition {
94
+ return {
95
+ name: ROUTER_TOOL_NAME,
96
+ description: `Choose up to ${maxPageIds} concept page IDs to inject for the next reply. Lean toward inclusion when in doubt — missing a relevant page is a worse error than surfacing unused ones. Return [] only when nothing in the index plausibly bears on the turn.`,
97
+ input_schema: {
98
+ type: "object",
99
+ properties: {
100
+ page_ids: {
101
+ type: "array",
102
+ items: { type: "integer" },
103
+ maxItems: maxPageIds,
104
+ },
105
+ },
106
+ required: ["page_ids"],
107
+ },
108
+ };
109
+ }
110
+
111
+ const RouterResultSchema = z.object({
112
+ page_ids: z.array(z.number().int()),
113
+ });
114
+
115
+ /** Empty-result helper so call sites don't reconstruct the shape inline. */
116
+ function emptyResult(reason: RouterFailureReason | null): RouterResult {
117
+ return { selectedSlugs: [], failureReason: reason };
118
+ }
119
+
120
+ interface RunRouterParams {
121
+ workspaceDir: string;
122
+ userMessage: string;
123
+ assistantMessage: string;
124
+ /** Verbatim contents to inject into `<now>...</now>` on this turn. */
125
+ nowText: string;
126
+ /** Slugs already injected on prior turns (used to seed `<already_injected_ids>`). */
127
+ priorEverInjected: readonly EverInjectedEntry[];
128
+ config: AssistantConfig;
129
+ signal?: AbortSignal;
130
+ }
131
+
132
+ /**
133
+ * Run the router for one turn. The implementation steps (mirroring
134
+ * `sweep-job.ts` end-to-end):
135
+ *
136
+ * 1. Build the page index. If the workspace has no concept pages and no
137
+ * seeded skill entries, abstain immediately with `empty_index`.
138
+ * 2. Resolve the configured provider for the `memoryRouter` call site.
139
+ * Missing → `no_provider` so the caller can fall back to spreading
140
+ * activation or an empty injection.
141
+ * 3. Build system + user prompts. The system prompt is the rendered
142
+ * router template with the page index inlined and gets one ephemeral
143
+ * breakpoint at the end (the page-index block). The user message is
144
+ * *two* text blocks: the cached `<now>` block and the uncached
145
+ * already-injected/last-turn block.
146
+ * 4. Force `tool_choice` so the model can only emit `select_pages_to_inject`.
147
+ * 5. Parse the tool input via Zod. Anything off-shape collapses to
148
+ * `schema_mismatch`.
149
+ * 6. Map IDs to slugs through the page index, dropping IDs outside
150
+ * `[1, N]` and truncating at `max_page_ids`.
151
+ *
152
+ * Any uncaught throw inside the call (network, provider SDK error, abort)
153
+ * collapses to `api_error` and is logged at warn so callers can keep going
154
+ * without crashing the daemon. `AbortSignal.aborted` errors are *not*
155
+ * special-cased; they propagate as `api_error` because the caller treats
156
+ * "router didn't finish" the same regardless of cause.
157
+ */
158
+ export async function runRouter(
159
+ params: RunRouterParams,
160
+ ): Promise<RouterResult> {
161
+ const {
162
+ workspaceDir,
163
+ userMessage,
164
+ assistantMessage,
165
+ nowText,
166
+ priorEverInjected,
167
+ config,
168
+ signal,
169
+ } = params;
170
+
171
+ const pageIndex = await getPageIndex(workspaceDir);
172
+ if (pageIndex.entries.length === 0) {
173
+ return emptyResult("empty_index");
174
+ }
175
+
176
+ const provider = await getConfiguredProvider("memoryRouter");
177
+ if (!provider) {
178
+ log.warn("memoryRouter provider unavailable; router skipped");
179
+ return emptyResult("no_provider");
180
+ }
181
+
182
+ const systemPrompt = resolveRouterPrompt(
183
+ config.memory?.v2?.router?.router_prompt_path ?? null,
184
+ {
185
+ assistantName: getAssistantName(),
186
+ userName: resolveUserName(workspaceDir),
187
+ pageIndexBlock: pageIndex.rendered,
188
+ },
189
+ );
190
+
191
+ // Already-injected slugs that map back to a current index ID. Slugs whose
192
+ // page has been deleted since the prior turn drop out silently — the model
193
+ // only sees IDs that still resolve.
194
+ const priorIds: number[] = [];
195
+ for (const entry of priorEverInjected) {
196
+ const idx = pageIndex.bySlug.get(entry.slug);
197
+ if (idx) priorIds.push(idx.id);
198
+ }
199
+
200
+ // Cache breakpoint 2 — `<now>` is stable across most turns (NOW.md only
201
+ // changes when the model rewrites it), so the bulk of the user message
202
+ // rides the cache. We use a 1h TTL to match the system-prompt breakpoint
203
+ // and the provider's auto-applied breakpoints. The trailing block has no
204
+ // `cache_control`; the Anthropic provider auto-applies a 1h breakpoint on
205
+ // the last text block of a turn-starting user message, which covers it.
206
+ const userMsg: Message = {
207
+ role: "user",
208
+ content: [
209
+ cachedTextBlock(`<now>\n${nowText}\n</now>`),
210
+ {
211
+ type: "text",
212
+ text:
213
+ `<already_injected_ids>\n${priorIds.join(", ")}\n</already_injected_ids>\n\n` +
214
+ `<last_turn>\n[user]: ${userMessage}\n[assistant]: ${assistantMessage}\n</last_turn>`,
215
+ },
216
+ ],
217
+ };
218
+
219
+ const maxPageIds = config.memory?.v2?.router?.max_page_ids ?? 25;
220
+ const routerTool = buildRouterTool(maxPageIds);
221
+
222
+ let response;
223
+ try {
224
+ response = await provider.sendMessage(
225
+ [userMsg],
226
+ [routerTool],
227
+ systemPrompt,
228
+ {
229
+ config: {
230
+ callSite: "memoryRouter" as const,
231
+ tool_choice: { type: "tool" as const, name: ROUTER_TOOL_NAME },
232
+ },
233
+ ...(signal ? { signal } : {}),
234
+ },
235
+ );
236
+ } catch (err) {
237
+ log.warn({ err }, "Router provider call threw; treating as api_error");
238
+ return emptyResult("api_error");
239
+ }
240
+
241
+ const toolBlock = extractToolUse(response);
242
+ if (!toolBlock || toolBlock.name !== ROUTER_TOOL_NAME) {
243
+ log.warn(
244
+ { stopReason: response.stopReason },
245
+ "Router model returned no select_pages_to_inject tool_use block",
246
+ );
247
+ return emptyResult("tool_use_missing");
248
+ }
249
+
250
+ const parsed = RouterResultSchema.safeParse(toolBlock.input);
251
+ if (!parsed.success) {
252
+ log.warn(
253
+ { error: parsed.error.message },
254
+ "Router tool input did not match schema",
255
+ );
256
+ return emptyResult("schema_mismatch");
257
+ }
258
+
259
+ const N = pageIndex.entries.length;
260
+
261
+ const inRangeIds: number[] = [];
262
+ const droppedIds: number[] = [];
263
+ for (const id of parsed.data.page_ids) {
264
+ if (id >= 1 && id <= N) {
265
+ inRangeIds.push(id);
266
+ } else {
267
+ droppedIds.push(id);
268
+ }
269
+ }
270
+ if (droppedIds.length > 0) {
271
+ log.warn(
272
+ { droppedIds, indexSize: N },
273
+ "Router returned page IDs outside the valid range; dropping",
274
+ );
275
+ }
276
+
277
+ const truncated = inRangeIds.length > maxPageIds;
278
+ const finalIds = truncated ? inRangeIds.slice(0, maxPageIds) : inRangeIds;
279
+ if (truncated) {
280
+ log.warn(
281
+ { returned: inRangeIds.length, max: maxPageIds },
282
+ "Router returned more page IDs than max_page_ids; truncating",
283
+ );
284
+ }
285
+
286
+ // De-duplicate while preserving order — the index lookup alone wouldn't
287
+ // catch repeats from the model.
288
+ const seen = new Set<number>();
289
+ const selectedSlugs: string[] = [];
290
+ for (const id of finalIds) {
291
+ if (seen.has(id)) continue;
292
+ seen.add(id);
293
+ const entry = pageIndex.byId.get(id);
294
+ if (!entry) continue;
295
+ selectedSlugs.push(entry.slug);
296
+ }
297
+
298
+ return { selectedSlugs, failureReason: null };
299
+ }
300
+
301
+ /**
302
+ * Build a text content block carrying an ephemeral `cache_control`
303
+ * breakpoint with a 1h TTL. The Anthropic SDK accepts the field as an extra
304
+ * property on text blocks, but our internal `TextContent` type intentionally
305
+ * omits it (only the Anthropic provider transforms it onto the wire), so we
306
+ * reach through a `Record` cast here for the same reason `client.ts` does —
307
+ * it keeps the core types provider-agnostic. The 1h TTL matches the
308
+ * provider's auto-applied breakpoints (see `cacheTtl` in
309
+ * `providers/anthropic/client.ts`); the `<now>` block is stable across most
310
+ * turns, so default 5m would force unnecessary re-creation. The
311
+ * `extended-cache-ttl-2025-04-11` beta header is added unconditionally for
312
+ * non-Haiku models in `client.ts`, so this works without any call-site
313
+ * config.
314
+ */
315
+ function cachedTextBlock(text: string): ContentBlock {
316
+ const block: ContentBlock = { type: "text", text };
317
+ (block as unknown as Record<string, unknown>).cache_control = {
318
+ type: "ephemeral",
319
+ ttl: "1h",
320
+ };
321
+ return block;
322
+ }