@vellumai/assistant 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (991) hide show
  1. package/AGENTS.md +11 -0
  2. package/ARCHITECTURE.md +2 -7
  3. package/Dockerfile +80 -5
  4. package/README.md +2 -2
  5. package/bun.lock +11 -1
  6. package/docker-entrypoint.sh +21 -0
  7. package/docker-init-apt-root.sh +94 -0
  8. package/docker-kata-apt-env.sh +39 -0
  9. package/docs/plugins.md +88 -47
  10. package/docs/skills.md +9 -7
  11. package/eslint-rules/__tests__/cli-no-daemon-internals.test.ts +420 -0
  12. package/eslint-rules/cli-no-daemon-internals.js +283 -0
  13. package/eslint.config.mjs +12 -0
  14. package/examples/plugins/echo/README.md +27 -27
  15. package/examples/plugins/echo/package.json +3 -0
  16. package/examples/plugins/echo/register.ts +31 -31
  17. package/knip.json +2 -1
  18. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +10 -1
  19. package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
  20. package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
  21. package/openapi.yaml +4462 -991
  22. package/package.json +5 -1
  23. package/scripts/generate-openapi.ts +135 -14
  24. package/scripts/sync-llm-catalog.ts +165 -0
  25. package/scripts/sync-web-search-catalog.ts +129 -0
  26. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +169 -0
  27. package/src/__tests__/agent-image-optimize.test.ts +11 -3
  28. package/src/__tests__/agent-loop-override-profile.test.ts +26 -1
  29. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  30. package/src/__tests__/anthropic-provider.test.ts +137 -2
  31. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  32. package/src/__tests__/app-control-flow.test.ts +7 -0
  33. package/src/__tests__/app-executors.test.ts +220 -4
  34. package/src/__tests__/assistant-events-sse-shed.test.ts +232 -0
  35. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  36. package/src/__tests__/avatar-identity-sync.test.ts +87 -0
  37. package/src/__tests__/background-workers-disk-pressure.test.ts +11 -22
  38. package/src/__tests__/btw-routes.test.ts +1 -0
  39. package/src/__tests__/bundled-asset.test.ts +6 -6
  40. package/src/__tests__/call-site-routing-provider.test.ts +172 -45
  41. package/src/__tests__/cancel-resolves-conversation-key.test.ts +44 -3
  42. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  43. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  44. package/src/__tests__/channel-policy.test.ts +12 -0
  45. package/src/__tests__/checker.test.ts +89 -0
  46. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  47. package/src/__tests__/clawhub.test.ts +75 -16
  48. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +35 -7
  49. package/src/__tests__/compact-event-conversation-id-guard.test.ts +33 -5
  50. package/src/__tests__/compaction-strip-metadata-clear.test.ts +26 -1
  51. package/src/__tests__/compactor-tail-resolution.test.ts +41 -0
  52. package/src/__tests__/config-loader-backfill.test.ts +526 -102
  53. package/src/__tests__/config-loader-corrupt.test.ts +68 -0
  54. package/src/__tests__/config-loader-platform-defaults.test.ts +77 -23
  55. package/src/__tests__/config-schema-cmd.test.ts +63 -29
  56. package/src/__tests__/config-schema.test.ts +35 -3
  57. package/src/__tests__/config-set-platform-guard.test.ts +75 -152
  58. package/src/__tests__/config-set-route.test.ts +278 -0
  59. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  60. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  61. package/src/__tests__/config-watcher.test.ts +6 -0
  62. package/src/__tests__/contacts-tools.test.ts +51 -199
  63. package/src/__tests__/context-search-agent-protocol.test.ts +21 -2
  64. package/src/__tests__/context-search-agent-runner.test.ts +22 -138
  65. package/src/__tests__/context-search-conversations-source.test.ts +159 -18
  66. package/src/__tests__/context-search-fanout.test.ts +20 -157
  67. package/src/__tests__/context-search-memory-v2-source.test.ts +3 -4
  68. package/src/__tests__/context-search-types.test.ts +7 -2
  69. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  70. package/src/__tests__/context-token-estimator.test.ts +1 -0
  71. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  72. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  73. package/src/__tests__/conversation-agent-loop-overflow.test.ts +93 -92
  74. package/src/__tests__/conversation-agent-loop.test.ts +2 -0
  75. package/src/__tests__/conversation-crud-inference-profile.test.ts +100 -0
  76. package/src/__tests__/conversation-error.test.ts +80 -3
  77. package/src/__tests__/conversation-fork-crud.test.ts +323 -1
  78. package/src/__tests__/conversation-inference-profile-route.test.ts +54 -18
  79. package/src/__tests__/conversation-init.benchmark.test.ts +1 -0
  80. package/src/__tests__/conversation-lifecycle.test.ts +297 -0
  81. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  82. package/src/__tests__/conversation-pairing.test.ts +54 -0
  83. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +100 -1
  84. package/src/__tests__/conversation-process-callsite.test.ts +25 -2
  85. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  86. package/src/__tests__/conversation-queue.test.ts +4 -1
  87. package/src/__tests__/conversation-runtime-assembly.test.ts +80 -13
  88. package/src/__tests__/conversation-slash-commands.test.ts +194 -2
  89. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  90. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  91. package/src/__tests__/conversation-surfaces-app-control.test.ts +323 -3
  92. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  93. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  94. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  95. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  96. package/src/__tests__/credential-security-invariants.test.ts +8 -8
  97. package/src/__tests__/daemon-credential-client.test.ts +56 -1
  98. package/src/__tests__/db-activation-state-fk-cascade.test.ts +132 -0
  99. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +37 -0
  100. package/src/__tests__/db-memory-graph-event-date-repair.test.ts +43 -20
  101. package/src/__tests__/db-proxy-transaction.test.ts +206 -0
  102. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  103. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  104. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  105. package/src/__tests__/dm-backfill.test.ts +121 -10
  106. package/src/__tests__/document-tool-security.test.ts +258 -0
  107. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  108. package/src/__tests__/edit-propagation.test.ts +33 -0
  109. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  110. package/src/__tests__/external-plugin-loader.test.ts +482 -0
  111. package/src/__tests__/filing-service.test.ts +163 -3
  112. package/src/__tests__/fixtures/mock-chrome-extension.ts +5 -0
  113. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  114. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  115. package/src/__tests__/graph-extraction-event-date.test.ts +34 -0
  116. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +42 -69
  117. package/src/__tests__/heartbeat-disk-pressure.test.ts +21 -8
  118. package/src/__tests__/heartbeat-service.test.ts +50 -233
  119. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  120. package/src/__tests__/helpers/wait-for.ts +21 -0
  121. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  122. package/src/__tests__/history-repair.test.ts +162 -0
  123. package/src/__tests__/host-app-control-proxy.test.ts +365 -1
  124. package/src/__tests__/host-app-control-routes.test.ts +247 -1
  125. package/src/__tests__/host-browser-proxy.test.ts +416 -20
  126. package/src/__tests__/host-browser-routes.test.ts +325 -33
  127. package/src/__tests__/host-proxy-preactivation.test.ts +211 -0
  128. package/src/__tests__/image-credentials.test.ts +1 -1
  129. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  130. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +246 -0
  131. package/src/__tests__/inference-profile-reaper.test.ts +156 -0
  132. package/src/__tests__/inference-profile-session-handler.test.ts +410 -0
  133. package/src/__tests__/inference-profile-session-ipc.test.ts +248 -0
  134. package/src/__tests__/injector-chain.test.ts +10 -8
  135. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -1
  136. package/src/__tests__/install-skill-routing.test.ts +157 -39
  137. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +107 -3
  138. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  139. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  140. package/src/__tests__/llm-callsite-catalog.test.ts +20 -1
  141. package/src/__tests__/llm-catalog-parity.test.ts +190 -2
  142. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +222 -0
  143. package/src/__tests__/llm-request-log-source-factory.test.ts +100 -0
  144. package/src/__tests__/llm-resolver.test.ts +46 -0
  145. package/src/__tests__/llm-usage-store.test.ts +114 -0
  146. package/src/__tests__/managed-profile-guard.test.ts +145 -14
  147. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  148. package/src/__tests__/managed-store.test.ts +84 -192
  149. package/src/__tests__/mcp-auth-routes.test.ts +1 -0
  150. package/src/__tests__/mcp-cli.test.ts +182 -220
  151. package/src/__tests__/mcp-health-check.test.ts +56 -27
  152. package/src/__tests__/media-generate-image.test.ts +1 -1
  153. package/src/__tests__/memory-jobs-worker-lanes.test.ts +18 -11
  154. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  155. package/src/__tests__/message-complete-display-id.test.ts +175 -0
  156. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  157. package/src/__tests__/notification-platform-adapter.test.ts +229 -0
  158. package/src/__tests__/oauth-cli.test.ts +38 -2009
  159. package/src/__tests__/oauth-commands-routes.test.ts +863 -0
  160. package/src/__tests__/oauth-connect-routes.test.ts +174 -11
  161. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  162. package/src/__tests__/oauth-providers-routes.test.ts +14 -10
  163. package/src/__tests__/openai-provider.test.ts +24 -0
  164. package/src/__tests__/openai-responses-cutover-guard.test.ts +48 -19
  165. package/src/__tests__/openai-responses-provider.test.ts +17 -0
  166. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  167. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  168. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +1 -1
  169. package/src/__tests__/platform.test.ts +2 -0
  170. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  171. package/src/__tests__/plugin-bootstrap.test.ts +41 -38
  172. package/src/__tests__/plugin-external-api.test.ts +68 -0
  173. package/src/__tests__/plugin-registry.test.ts +0 -77
  174. package/src/__tests__/plugin-route-contribution.test.ts +31 -4
  175. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  176. package/src/__tests__/plugin-tool-contribution.test.ts +47 -18
  177. package/src/__tests__/plugin-types.test.ts +15 -23
  178. package/src/__tests__/process-message-background-slack.test.ts +53 -0
  179. package/src/__tests__/process-message-display-content.test.ts +421 -0
  180. package/src/__tests__/profile-entry-status.test.ts +43 -0
  181. package/src/__tests__/provider-catalog-visibility.test.ts +142 -0
  182. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  183. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +20 -12
  184. package/src/__tests__/provider-registry-ollama.test.ts +12 -4
  185. package/src/__tests__/provider-send-message-override-profile.test.ts +10 -4
  186. package/src/__tests__/relay-server.test.ts +118 -0
  187. package/src/__tests__/retry-thinking-tool-choice.test.ts +15 -0
  188. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  189. package/src/__tests__/schedule-retry.test.ts +56 -4
  190. package/src/__tests__/schedule-routes.test.ts +151 -0
  191. package/src/__tests__/schedule-store.test.ts +94 -0
  192. package/src/__tests__/scheduler-disk-pressure.test.ts +0 -4
  193. package/src/__tests__/scheduler-recurrence.test.ts +87 -34
  194. package/src/__tests__/scheduler-reuse-conversation.test.ts +208 -5
  195. package/src/__tests__/scheduler-wake.test.ts +0 -63
  196. package/src/__tests__/schema-transforms.test.ts +20 -0
  197. package/src/__tests__/search-skills-unified.test.ts +0 -5
  198. package/src/__tests__/secret-allowlist.test.ts +1 -0
  199. package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +12 -4
  200. package/src/__tests__/server-history-render.test.ts +43 -0
  201. package/src/__tests__/shell-credential-ref.test.ts +95 -3
  202. package/src/__tests__/shell-tool-proxy-mode.test.ts +14 -0
  203. package/src/__tests__/skill-load-feature-flag.test.ts +1 -12
  204. package/src/__tests__/skill-load-tool.test.ts +29 -93
  205. package/src/__tests__/skill-memory.test.ts +23 -3
  206. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  207. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  208. package/src/__tests__/skills-install-extract.test.ts +49 -38
  209. package/src/__tests__/skills-install-staging.test.ts +159 -0
  210. package/src/__tests__/skills-uninstall.test.ts +9 -41
  211. package/src/__tests__/skills.test.ts +51 -58
  212. package/src/__tests__/slack-channel-config.test.ts +9 -0
  213. package/src/__tests__/subagent-call-site-routing.test.ts +78 -16
  214. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  215. package/src/__tests__/suggestion-routes.test.ts +3 -3
  216. package/src/__tests__/sync-message-contract.test.ts +63 -0
  217. package/src/__tests__/system-prompt.test.ts +737 -63
  218. package/src/__tests__/task-scheduler.test.ts +88 -23
  219. package/src/__tests__/terminal-tools.test.ts +28 -1
  220. package/src/__tests__/thread-backfill.test.ts +557 -27
  221. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  222. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  223. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  224. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  225. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  226. package/src/__tests__/tool-executor.test.ts +16 -4
  227. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  228. package/src/__tests__/turn-events-store.test.ts +256 -0
  229. package/src/__tests__/twilio-routes.test.ts +4 -0
  230. package/src/__tests__/update-bulletin-job.test.ts +96 -193
  231. package/src/__tests__/usage-cli.test.ts +11 -73
  232. package/src/__tests__/user-plugin-loader.test.ts +143 -5
  233. package/src/__tests__/vercel-config.test.ts +168 -0
  234. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  235. package/src/__tests__/web-search-catalog-parity.test.ts +108 -0
  236. package/src/__tests__/web-search.test.ts +303 -2
  237. package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +1 -21
  238. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +170 -0
  239. package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +53 -20
  240. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +241 -0
  241. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  242. package/src/__tests__/workspace-migration-076-drop-services-inference-mode.test.ts +211 -0
  243. package/src/__tests__/workspace-migration-077-seed-memory-router-callsite.test.ts +174 -0
  244. package/src/__tests__/workspace-migration-079-home-feed-notification-only.test.ts +323 -0
  245. package/src/__tests__/workspace-migration-080-restrict-vercel-api-token-metadata.test.ts +299 -0
  246. package/src/__tests__/workspace-migration-081-backfill-bash-allowed-tools.test.ts +410 -0
  247. package/src/__tests__/workspace-migration-082-backfill-managed-profile-labels.test.ts +268 -0
  248. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  249. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  250. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  251. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +3 -3
  252. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  253. package/src/__tests__/workspace-release-notes-feature-flag-guard.test.ts +115 -0
  254. package/src/acp/__tests__/helpers/which-stub.ts +4 -2
  255. package/src/acp/resolve-agent.test.ts +25 -0
  256. package/src/acp/resolve-agent.ts +13 -2
  257. package/src/acp/session-manager.ts +14 -0
  258. package/src/agent/image-optimize.ts +13 -5
  259. package/src/approvals/guardian-request-resolvers.ts +32 -87
  260. package/src/calls/relay-server.ts +35 -0
  261. package/src/calls/relay-setup-router.ts +36 -0
  262. package/src/calls/types.ts +1 -0
  263. package/src/calls/voice-session-bridge.ts +74 -36
  264. package/src/channels/config.ts +14 -1
  265. package/src/channels/types.ts +109 -0
  266. package/src/cli/AGENTS.md +164 -4
  267. package/src/cli/__tests__/notifications.test.ts +54 -0
  268. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  269. package/src/cli/commands/__tests__/avatar.test.ts +540 -0
  270. package/src/cli/commands/__tests__/backup.test.ts +236 -776
  271. package/src/cli/commands/__tests__/cache.test.ts +1 -1
  272. package/src/cli/commands/__tests__/changelog.test.ts +578 -0
  273. package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +503 -0
  274. package/src/cli/commands/__tests__/conversations-import.test.ts +515 -0
  275. package/src/cli/commands/__tests__/domain-register.test.ts +140 -167
  276. package/src/cli/commands/__tests__/domain-status.test.ts +137 -76
  277. package/src/cli/commands/__tests__/email-attachment.test.ts +314 -337
  278. package/src/cli/commands/__tests__/email-core.test.ts +579 -0
  279. package/src/cli/commands/__tests__/image-generation.test.ts +87 -824
  280. package/src/cli/commands/__tests__/inference-send.test.ts +30 -266
  281. package/src/cli/commands/__tests__/inference-session.test.ts +423 -0
  282. package/src/cli/commands/__tests__/memory-v2.test.ts +81 -110
  283. package/src/cli/commands/__tests__/schedules.test.ts +491 -0
  284. package/src/cli/commands/__tests__/skills.test.ts +563 -0
  285. package/src/cli/commands/__tests__/status.test.ts +249 -0
  286. package/src/cli/commands/__tests__/stt.test.ts +320 -0
  287. package/src/cli/commands/__tests__/tts-synthesize.test.ts +4 -603
  288. package/src/cli/commands/__tests__/tts.test.ts +321 -0
  289. package/src/cli/commands/__tests__/webhooks.test.ts +86 -511
  290. package/src/cli/commands/attachment.ts +8 -3
  291. package/src/cli/commands/audit.ts +95 -64
  292. package/src/cli/commands/auth.ts +61 -58
  293. package/src/cli/commands/avatar.ts +276 -390
  294. package/src/cli/commands/backup.ts +409 -505
  295. package/src/cli/commands/bash.ts +9 -5
  296. package/src/cli/commands/browser.ts +28 -9
  297. package/src/cli/commands/cache.ts +9 -4
  298. package/src/cli/commands/changelog.ts +478 -0
  299. package/src/cli/commands/channel-verification-sessions.ts +238 -317
  300. package/src/cli/commands/clients.ts +8 -3
  301. package/src/cli/commands/completions.ts +9 -9
  302. package/src/cli/commands/config.ts +102 -72
  303. package/src/cli/commands/contacts.ts +575 -696
  304. package/src/cli/commands/conversations-defer.ts +17 -69
  305. package/src/cli/commands/conversations-import.ts +90 -253
  306. package/src/cli/commands/conversations.ts +429 -434
  307. package/src/cli/commands/credential-execution.ts +9 -6
  308. package/src/cli/commands/credentials.ts +456 -736
  309. package/src/cli/commands/default-action.ts +10 -53
  310. package/src/cli/commands/domain.ts +128 -206
  311. package/src/cli/commands/email.ts +606 -794
  312. package/src/cli/commands/gateway.ts +8 -1
  313. package/src/cli/commands/image-generation.ts +157 -205
  314. package/src/cli/commands/inference-providers.ts +352 -0
  315. package/src/cli/commands/inference-session.ts +415 -0
  316. package/src/cli/commands/inference.ts +87 -65
  317. package/src/cli/commands/keys.ts +8 -3
  318. package/src/cli/commands/mcp.ts +103 -287
  319. package/src/cli/commands/memory-v2.ts +162 -516
  320. package/src/cli/commands/notifications.ts +342 -304
  321. package/src/cli/commands/oauth/apps.ts +292 -261
  322. package/src/cli/commands/oauth/connect.ts +176 -297
  323. package/src/cli/commands/oauth/disconnect.ts +16 -215
  324. package/src/cli/commands/oauth/index.ts +49 -45
  325. package/src/cli/commands/oauth/mode.ts +43 -199
  326. package/src/cli/commands/oauth/ping.ts +17 -125
  327. package/src/cli/commands/oauth/providers.ts +732 -921
  328. package/src/cli/commands/oauth/request.ts +60 -350
  329. package/src/cli/commands/oauth/shared.ts +11 -121
  330. package/src/cli/commands/oauth/status.ts +31 -121
  331. package/src/cli/commands/oauth/token.ts +13 -55
  332. package/src/cli/commands/pending.ts +19 -10
  333. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +133 -183
  334. package/src/cli/commands/platform/__tests__/connect.test.ts +66 -181
  335. package/src/cli/commands/platform/__tests__/disconnect.test.ts +71 -227
  336. package/src/cli/commands/platform/__tests__/status.test.ts +169 -287
  337. package/src/cli/commands/platform/connect.ts +16 -80
  338. package/src/cli/commands/platform/disconnect.ts +14 -112
  339. package/src/cli/commands/platform/index.ts +177 -246
  340. package/src/cli/commands/plugins.ts +185 -0
  341. package/src/cli/commands/routes.ts +153 -336
  342. package/src/cli/commands/schedules.ts +391 -0
  343. package/src/cli/commands/sequence.ts +316 -360
  344. package/src/cli/commands/skills.ts +449 -671
  345. package/src/cli/commands/status.ts +58 -37
  346. package/src/cli/commands/stt.ts +94 -262
  347. package/src/cli/commands/task.ts +14 -40
  348. package/src/cli/commands/telemetry.ts +40 -0
  349. package/src/cli/commands/trust.ts +8 -3
  350. package/src/cli/commands/tts.ts +162 -167
  351. package/src/cli/commands/ui.ts +35 -42
  352. package/src/cli/commands/usage.ts +188 -126
  353. package/src/cli/commands/watchers.ts +8 -3
  354. package/src/cli/commands/webhooks.ts +99 -193
  355. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  356. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  357. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  358. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  359. package/src/cli/lib/__tests__/register-command.test.ts +85 -0
  360. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  361. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  362. package/src/cli/lib/cli-colors.ts +12 -0
  363. package/src/cli/lib/confirm-prompt.ts +79 -0
  364. package/src/cli/lib/daemon-credential-client.ts +4 -5
  365. package/src/cli/lib/install-from-github.ts +304 -0
  366. package/src/cli/lib/list-installed-plugins.ts +137 -0
  367. package/src/cli/lib/nested-value.ts +44 -0
  368. package/src/cli/lib/open-browser.ts +36 -0
  369. package/src/cli/lib/register-command.ts +19 -0
  370. package/src/cli/lib/time-ago.ts +34 -0
  371. package/src/cli/lib/uninstall-plugin.ts +82 -0
  372. package/src/cli/lib/unknown-command.ts +111 -0
  373. package/src/cli/program.ts +40 -6
  374. package/src/cli/utils/__tests__/conversation-id.test.ts +66 -0
  375. package/src/cli/utils/__tests__/parse-duration.test.ts +49 -0
  376. package/src/cli/utils/conversation-id.ts +30 -0
  377. package/src/cli/utils/parse-duration.ts +41 -0
  378. package/src/config/acp-defaults.test.ts +5 -1
  379. package/src/config/acp-defaults.ts +11 -4
  380. package/src/config/bundled-skills/acp/TOOLS.json +2 -2
  381. package/src/config/bundled-skills/app-builder/SKILL.md +23 -21
  382. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  383. package/src/config/bundled-skills/app-control/TOOLS.json +32 -0
  384. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  385. package/src/config/bundled-skills/contacts/SKILL.md +12 -45
  386. package/src/config/bundled-skills/contacts/TOOLS.json +0 -57
  387. package/src/config/bundled-skills/document/SKILL.md +23 -3
  388. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  389. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  390. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  391. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  392. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +0 -12
  393. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +0 -58
  394. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  395. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  396. package/src/config/bundled-tool-registry.ts +6 -2
  397. package/src/config/feature-flag-registry.json +57 -1
  398. package/src/config/llm-resolver.ts +16 -1
  399. package/src/config/loader.ts +140 -52
  400. package/src/config/raw-config-utils.ts +2 -30
  401. package/src/config/schema.ts +8 -7
  402. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  403. package/src/config/schemas/__tests__/memory-v2.test.ts +49 -0
  404. package/src/config/schemas/call-site-catalog.ts +29 -7
  405. package/src/config/schemas/channels.ts +8 -0
  406. package/src/config/schemas/compaction.ts +28 -0
  407. package/src/config/schemas/heartbeat.ts +9 -0
  408. package/src/config/schemas/llm-request-logs.ts +81 -0
  409. package/src/config/schemas/llm.ts +55 -2
  410. package/src/config/schemas/memory-retrieval.ts +18 -0
  411. package/src/config/schemas/memory-retrospective.ts +48 -0
  412. package/src/config/schemas/memory-v2.ts +32 -1
  413. package/src/config/schemas/memory.ts +4 -0
  414. package/src/config/schemas/services.ts +15 -12
  415. package/src/config/schemas/tools.ts +14 -0
  416. package/src/config/seed-inference-profiles.ts +195 -134
  417. package/src/config/skills.ts +3 -96
  418. package/src/contacts/contact-store.ts +0 -61
  419. package/src/context/compactor.ts +1047 -0
  420. package/src/context/token-estimator.ts +2 -2
  421. package/src/context/window-manager.ts +197 -1334
  422. package/src/credential-execution/managed-catalog.ts +37 -0
  423. package/src/credential-health/credential-health-service.ts +280 -19
  424. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +113 -0
  425. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  426. package/src/daemon/__tests__/conversation-tool-setup.test.ts +183 -4
  427. package/src/daemon/__tests__/daemon-skill-host.test.ts +10 -4
  428. package/src/daemon/approval-generators.ts +26 -30
  429. package/src/daemon/config-watcher.ts +94 -29
  430. package/src/daemon/conversation-agent-loop-handlers.ts +24 -0
  431. package/src/daemon/conversation-agent-loop.ts +293 -103
  432. package/src/daemon/conversation-error.ts +188 -33
  433. package/src/daemon/conversation-lifecycle.ts +80 -26
  434. package/src/daemon/conversation-messaging.ts +25 -6
  435. package/src/daemon/conversation-process.ts +85 -31
  436. package/src/daemon/conversation-runtime-assembly.ts +30 -6
  437. package/src/daemon/conversation-slash.ts +184 -25
  438. package/src/daemon/conversation-store.ts +24 -10
  439. package/src/daemon/conversation-surfaces.ts +76 -12
  440. package/src/daemon/conversation-tool-setup.ts +63 -21
  441. package/src/daemon/conversation.ts +81 -10
  442. package/src/daemon/external-plugins-bootstrap.ts +231 -185
  443. package/src/daemon/first-greeting.ts +22 -2
  444. package/src/daemon/guardian-action-generators.ts +7 -22
  445. package/src/daemon/handlers/config-model.ts +13 -130
  446. package/src/daemon/handlers/config-slack-channel.ts +25 -10
  447. package/src/daemon/handlers/config-vercel.ts +3 -1
  448. package/src/daemon/handlers/shared.ts +14 -5
  449. package/src/daemon/handlers/skills.ts +166 -84
  450. package/src/daemon/history-repair.ts +61 -7
  451. package/src/daemon/host-app-control-proxy.ts +129 -29
  452. package/src/daemon/host-bash-proxy.ts +85 -158
  453. package/src/daemon/host-browser-proxy.ts +96 -35
  454. package/src/daemon/host-proxy-base.ts +13 -1
  455. package/src/daemon/host-proxy-preactivation.ts +25 -1
  456. package/src/daemon/identity-helpers.ts +19 -0
  457. package/src/daemon/lifecycle.ts +79 -70
  458. package/src/daemon/meet-host-supervisor.ts +20 -19
  459. package/src/daemon/memory-v2-startup.ts +58 -2
  460. package/src/daemon/message-protocol.ts +7 -0
  461. package/src/daemon/message-types/bookmarks.ts +18 -0
  462. package/src/daemon/message-types/conversations.ts +37 -9
  463. package/src/daemon/message-types/messages.ts +70 -1
  464. package/src/daemon/message-types/subagents.ts +1 -0
  465. package/src/daemon/message-types/sync.ts +61 -0
  466. package/src/daemon/pkb-reminder-builder.test.ts +54 -13
  467. package/src/daemon/pkb-reminder-builder.ts +21 -7
  468. package/src/daemon/plugin-source-watcher.ts +146 -0
  469. package/src/daemon/process-message.ts +77 -26
  470. package/src/daemon/server.ts +34 -20
  471. package/src/daemon/shutdown-handlers.ts +0 -2
  472. package/src/daemon/skill-memory-refresh.ts +29 -0
  473. package/src/daemon/tool-setup-types.ts +9 -0
  474. package/src/daemon/tool-side-effects.ts +6 -4
  475. package/src/daemon/wake-target-adapter.ts +11 -0
  476. package/src/documents/document-store.ts +221 -3
  477. package/src/embedded/plugin-api.ts +40 -0
  478. package/src/export/transcript-formatter.ts +61 -2
  479. package/src/filing/filing-service.ts +79 -53
  480. package/src/heartbeat/__tests__/heartbeat-service.test.ts +444 -0
  481. package/src/heartbeat/heartbeat-run-store.ts +3 -1
  482. package/src/heartbeat/heartbeat-service.ts +189 -127
  483. package/src/home/__tests__/feed-types.test.ts +99 -127
  484. package/src/home/__tests__/feed-writer.test.ts +77 -278
  485. package/src/home/__tests__/post-connect-feed.test.ts +9 -12
  486. package/src/home/feed-types.ts +41 -73
  487. package/src/home/feed-writer.ts +25 -156
  488. package/src/home/post-connect-feed.ts +2 -3
  489. package/src/index.ts +18 -1
  490. package/src/ipc/__tests__/cli-ipc.test.ts +2 -0
  491. package/src/ipc/__tests__/email-ipc.test.ts +506 -0
  492. package/src/ipc/__tests__/exit-helper.test.ts +104 -0
  493. package/src/ipc/__tests__/streaming-client.test.ts +237 -0
  494. package/src/ipc/__tests__/streaming-framing.test.ts +142 -0
  495. package/src/ipc/assistant-server.ts +55 -6
  496. package/src/ipc/cli-client.ts +370 -50
  497. package/src/ipc/routes/db-proxy-transaction.ts +151 -0
  498. package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +60 -0
  499. package/src/ipc/skill-routes/events.ts +30 -3
  500. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +46 -0
  501. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  502. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +1 -0
  503. package/src/live-voice/live-voice-session-manager.ts +11 -4
  504. package/src/live-voice/live-voice-session.ts +14 -6
  505. package/src/mcp/client.ts +20 -4
  506. package/src/media/image-credentials.ts +3 -3
  507. package/src/memory/__tests__/bookmark-crud.test.ts +264 -0
  508. package/src/memory/__tests__/bookmark-schema.test.ts +181 -0
  509. package/src/memory/__tests__/conversation-queries.test.ts +263 -0
  510. package/src/memory/__tests__/conversation-types.test.ts +36 -0
  511. package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +130 -0
  512. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  513. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +177 -0
  514. package/src/memory/__tests__/memory-retrospective-job.test.ts +328 -0
  515. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +318 -0
  516. package/src/memory/__tests__/memory-retrospective-trigger-check.test.ts +90 -0
  517. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +69 -0
  518. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +3 -0
  519. package/src/memory/__tests__/message-content.test.ts +35 -0
  520. package/src/memory/bookmark-crud.ts +211 -0
  521. package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +31 -9
  522. package/src/memory/context-search/agent-protocol.ts +5 -1
  523. package/src/memory/context-search/agent-runner.ts +60 -85
  524. package/src/memory/context-search/limits.ts +1 -4
  525. package/src/memory/context-search/search.ts +23 -113
  526. package/src/memory/context-search/sources/conversations.ts +80 -8
  527. package/src/memory/context-search/sources/memory-v2.ts +39 -14
  528. package/src/memory/context-search/sources/memory.ts +7 -0
  529. package/src/memory/context-search/sources/workspace.ts +17 -10
  530. package/src/memory/context-search/types.ts +1 -1
  531. package/src/memory/conversation-bootstrap.ts +11 -0
  532. package/src/memory/conversation-crud.ts +368 -22
  533. package/src/memory/conversation-queries.ts +116 -12
  534. package/src/memory/conversation-title-service.ts +1 -0
  535. package/src/memory/conversation-types.ts +16 -0
  536. package/src/memory/db-init.ts +20 -0
  537. package/src/memory/delivery-crud.ts +152 -5
  538. package/src/memory/embedding-backend.ts +6 -5
  539. package/src/memory/embedding-runtime-manager.ts +1 -2
  540. package/src/memory/external-conversation-store.ts +66 -5
  541. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +66 -9
  542. package/src/memory/graph/__tests__/remember-description.test.ts +55 -0
  543. package/src/memory/graph/conversation-graph-memory.ts +92 -5
  544. package/src/memory/graph/extraction.ts +4 -0
  545. package/src/memory/graph/graph-memory-state-store.ts +16 -3
  546. package/src/memory/graph/tool-handlers.ts +17 -7
  547. package/src/memory/graph/tools.ts +45 -6
  548. package/src/memory/indexer.ts +51 -29
  549. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +86 -15
  550. package/src/memory/jobs/embed-concept-page.ts +65 -20
  551. package/src/memory/jobs-store.ts +51 -1
  552. package/src/memory/jobs-worker.ts +57 -3
  553. package/src/memory/llm-request-log-source-clickhouse.ts +324 -0
  554. package/src/memory/llm-request-log-source-local.ts +26 -0
  555. package/src/memory/llm-request-log-source.ts +64 -0
  556. package/src/memory/llm-request-log-store.ts +1 -1
  557. package/src/memory/llm-usage-store.ts +125 -5
  558. package/src/memory/memory-retrospective-constants.ts +13 -0
  559. package/src/memory/memory-retrospective-enqueue.ts +114 -0
  560. package/src/memory/memory-retrospective-job.ts +351 -0
  561. package/src/memory/memory-retrospective-startup-cleanup.ts +175 -0
  562. package/src/memory/memory-retrospective-state.ts +162 -0
  563. package/src/memory/memory-retrospective-trigger-check.ts +91 -0
  564. package/src/memory/memory-v2-activation-log-store.ts +49 -5
  565. package/src/memory/memory-v2-concept-frequency.ts +4 -0
  566. package/src/memory/message-content.ts +38 -1
  567. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  568. package/src/memory/migrations/227-add-conversation-inference-profile.ts +6 -1
  569. package/src/memory/migrations/228-rename-inference-profile-snake-case.ts +20 -7
  570. package/src/memory/migrations/229-delete-private-conversations.test.ts +107 -1
  571. package/src/memory/migrations/229-delete-private-conversations.ts +19 -0
  572. package/src/memory/migrations/231-repair-memory-graph-event-dates.ts +16 -2
  573. package/src/memory/migrations/240-conversation-inference-profile-session.ts +25 -0
  574. package/src/memory/migrations/241-activation-state-fk-cascade.ts +50 -0
  575. package/src/memory/migrations/242-message-bookmarks.ts +38 -0
  576. package/src/memory/migrations/243-provider-connections.ts +68 -0
  577. package/src/memory/migrations/244-provider-connection-status-label.ts +23 -0
  578. package/src/memory/migrations/245-memory-retrospective-state.ts +36 -0
  579. package/src/memory/migrations/246-backfill-provider-connection-label.ts +81 -0
  580. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  581. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  582. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  583. package/src/memory/migrations/__tests__/244-provider-connection-status-label.test.ts +84 -0
  584. package/src/memory/migrations/__tests__/245-memory-retrospective-state.test.ts +125 -0
  585. package/src/memory/migrations/__tests__/246-backfill-provider-connection-label.test.ts +192 -0
  586. package/src/memory/migrations/index.ts +13 -0
  587. package/src/memory/migrations/registry.ts +8 -0
  588. package/src/memory/onboarding-events-store.ts +106 -0
  589. package/src/memory/published-pages-store.ts +16 -0
  590. package/src/memory/schema/bookmarks.ts +36 -0
  591. package/src/memory/schema/calls.ts +1 -0
  592. package/src/memory/schema/conversations.ts +2 -0
  593. package/src/memory/schema/index.ts +2 -0
  594. package/src/memory/schema/inference.ts +27 -0
  595. package/src/memory/schema/infrastructure.ts +12 -0
  596. package/src/memory/schema/memory-core.ts +9 -0
  597. package/src/memory/search/semantic.ts +1 -4
  598. package/src/memory/turn-events-store.ts +127 -2
  599. package/src/memory/v2/__tests__/__snapshots__/prompts-router.test.ts.snap +27 -0
  600. package/src/memory/v2/__tests__/activation-store.test.ts +5 -5
  601. package/src/memory/v2/__tests__/activation.test.ts +11 -12
  602. package/src/memory/v2/__tests__/backfill-jobs.test.ts +38 -21
  603. package/src/memory/v2/__tests__/consolidation-job.test.ts +123 -135
  604. package/src/memory/v2/__tests__/edge-index.test.ts +1 -1
  605. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +111 -0
  606. package/src/memory/v2/__tests__/injection.test.ts +726 -18
  607. package/src/memory/v2/__tests__/migration.test.ts +94 -3
  608. package/src/memory/v2/__tests__/page-index.test.ts +360 -0
  609. package/src/memory/v2/__tests__/page-store.test.ts +14 -1
  610. package/src/memory/v2/__tests__/prompts-router.test.ts +309 -0
  611. package/src/memory/v2/__tests__/qdrant.test.ts +138 -3
  612. package/src/memory/v2/__tests__/reranker.test.ts +4 -4
  613. package/src/memory/v2/__tests__/router.test.ts +531 -0
  614. package/src/memory/v2/__tests__/sim.test.ts +45 -1
  615. package/src/memory/v2/__tests__/skill-store.test.ts +445 -11
  616. package/src/memory/v2/__tests__/static-context.test.ts +7 -22
  617. package/src/memory/v2/__tests__/sweep-job.test.ts +95 -0
  618. package/src/memory/v2/activation-store.ts +34 -5
  619. package/src/memory/v2/activation.ts +40 -27
  620. package/src/memory/v2/backfill-jobs.ts +17 -84
  621. package/src/memory/v2/consolidation-job.ts +85 -78
  622. package/src/memory/v2/frontmatter-sweep.ts +91 -0
  623. package/src/memory/v2/injection.ts +466 -109
  624. package/src/memory/v2/migration.ts +147 -20
  625. package/src/memory/v2/page-index.ts +221 -0
  626. package/src/memory/v2/page-store.ts +3 -0
  627. package/src/memory/v2/prompts/consolidation.ts +9 -7
  628. package/src/memory/v2/prompts/router.ts +195 -0
  629. package/src/memory/v2/prompts/sweep.ts +2 -2
  630. package/src/memory/v2/qdrant.ts +234 -93
  631. package/src/memory/v2/reranker.ts +14 -7
  632. package/src/memory/v2/router.ts +323 -0
  633. package/src/memory/v2/sim.ts +25 -12
  634. package/src/memory/v2/skill-store.ts +204 -30
  635. package/src/memory/v2/static-context.ts +16 -9
  636. package/src/memory/v2/sweep-job.ts +122 -96
  637. package/src/memory/v2/types.ts +10 -6
  638. package/src/memory/validation.ts +13 -0
  639. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  640. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  641. package/src/messaging/providers/slack/adapter.ts +43 -5
  642. package/src/messaging/providers/slack/client.ts +27 -0
  643. package/src/messaging/providers/slack/deep-link.ts +65 -0
  644. package/src/messaging/providers/slack/download.ts +104 -0
  645. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  646. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  647. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  648. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  649. package/src/messaging/providers/slack/types.ts +20 -1
  650. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +182 -0
  651. package/src/notifications/__tests__/home-feed-side-effect.test.ts +199 -0
  652. package/src/notifications/__tests__/signal-registry.test.ts +17 -0
  653. package/src/notifications/adapters/platform.ts +171 -0
  654. package/src/notifications/conversation-pairing.ts +4 -3
  655. package/src/notifications/copy-composer.ts +15 -0
  656. package/src/notifications/decision-engine.ts +2 -1
  657. package/src/notifications/destination-resolver.ts +21 -0
  658. package/src/notifications/emit-signal.ts +48 -2
  659. package/src/notifications/home-feed-side-effect.ts +165 -0
  660. package/src/notifications/signal.ts +8 -1
  661. package/src/oauth/connection-resolver.ts +8 -4
  662. package/src/oauth/platform-connection.ts +6 -2
  663. package/src/oauth/seed-providers.ts +10 -1
  664. package/src/permissions/checker.ts +14 -0
  665. package/src/permissions/ipc-risk-types.ts +3 -0
  666. package/src/permissions/question-prompter.test.ts +416 -0
  667. package/src/permissions/question-prompter.ts +294 -0
  668. package/src/platform/client.test.ts +1 -1
  669. package/src/platform/client.ts +1 -1
  670. package/src/plugin-api/constants.ts +26 -0
  671. package/src/plugin-api/index.ts +46 -0
  672. package/src/plugin-api/package.json +12 -0
  673. package/src/plugin-api/types.ts +144 -0
  674. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  675. package/src/plugins/defaults/compaction.ts +0 -4
  676. package/src/plugins/defaults/empty-response.ts +0 -2
  677. package/src/plugins/defaults/history-repair.ts +0 -2
  678. package/src/plugins/defaults/injectors.ts +55 -6
  679. package/src/plugins/defaults/llm-call.ts +0 -2
  680. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  681. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  682. package/src/plugins/defaults/persistence.ts +0 -2
  683. package/src/plugins/defaults/title-generate.ts +0 -5
  684. package/src/plugins/defaults/token-estimate.ts +0 -2
  685. package/src/plugins/defaults/tool-error.ts +0 -7
  686. package/src/plugins/defaults/tool-execute.ts +0 -2
  687. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  688. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  689. package/src/plugins/external-api.ts +104 -0
  690. package/src/plugins/external-plugin-loader.ts +367 -0
  691. package/src/plugins/feature-gate.ts +22 -0
  692. package/src/plugins/pipeline.ts +37 -0
  693. package/src/plugins/registry.ts +48 -80
  694. package/src/plugins/types.ts +74 -53
  695. package/src/plugins/user-loader.ts +85 -43
  696. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  697. package/src/proactive-artifact/job.test.ts +49 -9
  698. package/src/proactive-artifact/job.ts +4 -0
  699. package/src/proactive-artifact/trigger-state.test.ts +9 -0
  700. package/src/proactive-artifact/trigger-state.ts +4 -0
  701. package/src/prompts/__tests__/system-prompt.test.ts +117 -0
  702. package/src/prompts/__tests__/task-progress-hint-section.test.ts +99 -0
  703. package/src/prompts/normalize-onboarding.ts +27 -0
  704. package/src/prompts/sections.ts +302 -0
  705. package/src/prompts/system-prompt.ts +72 -154
  706. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  707. package/src/prompts/templates/system-sections.ts +173 -0
  708. package/src/prompts/update-bulletin-job.ts +61 -73
  709. package/src/providers/__tests__/dispatch-connection-routing.test.ts +279 -0
  710. package/src/providers/__tests__/inference.test.ts +303 -0
  711. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  712. package/src/providers/__tests__/provider-secret-catalog.test.ts +6 -0
  713. package/src/providers/__tests__/retry-callsite.test.ts +14 -32
  714. package/src/providers/__tests__/satellite-connection-routing.test.ts +510 -0
  715. package/src/providers/__tests__/search-provider-catalog.test.ts +80 -0
  716. package/src/providers/anthropic/client.ts +123 -54
  717. package/src/providers/call-site-routing.ts +94 -16
  718. package/src/providers/connection-resolution.ts +170 -0
  719. package/src/providers/inference/__tests__/connections-status-label.test.ts +250 -0
  720. package/src/providers/inference/adapter-factory.ts +210 -0
  721. package/src/providers/inference/auth.ts +112 -0
  722. package/src/providers/inference/backfill.ts +196 -0
  723. package/src/providers/inference/connections.ts +401 -0
  724. package/src/providers/inference/resolve-auth.ts +73 -0
  725. package/src/providers/model-catalog.ts +386 -6
  726. package/src/providers/openai/chat-completions-provider.ts +10 -2
  727. package/src/providers/openai/responses-provider.ts +4 -2
  728. package/src/providers/openrouter/client.ts +7 -0
  729. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -1
  730. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  731. package/src/providers/provider-availability.ts +17 -2
  732. package/src/providers/provider-catalog-visibility.ts +36 -0
  733. package/src/providers/provider-env-vars.ts +17 -7
  734. package/src/providers/provider-secret-catalog.ts +49 -30
  735. package/src/providers/provider-send-message.ts +41 -20
  736. package/src/providers/registry.ts +151 -159
  737. package/src/providers/retry.ts +65 -11
  738. package/src/providers/search-provider-catalog.ts +121 -0
  739. package/src/runtime/AGENTS.md +18 -5
  740. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  741. package/src/runtime/__tests__/background-job-runner.test.ts +357 -0
  742. package/src/runtime/__tests__/pre-first-message-gate.test.ts +82 -0
  743. package/src/runtime/actor-trust-resolver.ts +32 -10
  744. package/src/runtime/agent-wake.ts +64 -7
  745. package/src/runtime/assistant-event-hub.ts +3 -85
  746. package/src/runtime/auth/route-policy.ts +311 -9
  747. package/src/runtime/auth/same-actor.ts +2 -0
  748. package/src/runtime/background-job-runner.ts +339 -0
  749. package/src/runtime/btw-sidechain.ts +3 -0
  750. package/src/runtime/http-router.ts +36 -1
  751. package/src/runtime/http-server.ts +31 -5
  752. package/src/runtime/http-types.ts +21 -0
  753. package/src/runtime/middleware/__tests__/request-logger.test.ts +162 -0
  754. package/src/runtime/middleware/request-logger.ts +62 -1
  755. package/src/runtime/migrations/origin-mode.ts +1 -1
  756. package/src/runtime/pending-interactions.ts +1 -0
  757. package/src/runtime/pre-first-message-gate.ts +83 -0
  758. package/src/runtime/routes/__tests__/backup-routes.test.ts +8 -1
  759. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +268 -0
  760. package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +142 -0
  761. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +319 -0
  762. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +280 -4
  763. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +15 -136
  764. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +736 -0
  765. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +4 -4
  766. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  767. package/src/runtime/routes/__tests__/stt-routes.test.ts +5 -1
  768. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +384 -0
  769. package/src/runtime/routes/__tests__/tts-routes.test.ts +70 -3
  770. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  771. package/src/runtime/routes/acp-routes.ts +12 -8
  772. package/src/runtime/routes/app-management-routes.ts +228 -3
  773. package/src/runtime/routes/approval-routes.ts +0 -18
  774. package/src/runtime/routes/audit-routes.ts +43 -0
  775. package/src/runtime/routes/auth-routes.ts +72 -0
  776. package/src/runtime/routes/avatar-routes.ts +273 -20
  777. package/src/runtime/routes/backup-routes.ts +406 -2
  778. package/src/runtime/routes/bookmark-routes.ts +156 -0
  779. package/src/runtime/routes/btw-routes.ts +5 -1
  780. package/src/runtime/routes/channel-availability-routes.ts +121 -0
  781. package/src/runtime/routes/channel-verification-routes.ts +2 -1
  782. package/src/runtime/routes/contact-routes.ts +0 -160
  783. package/src/runtime/routes/conversation-cli-routes.ts +233 -0
  784. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  785. package/src/runtime/routes/conversation-management-routes.ts +47 -85
  786. package/src/runtime/routes/conversation-query-routes.ts +350 -97
  787. package/src/runtime/routes/conversation-routes.ts +121 -21
  788. package/src/runtime/routes/conversations-import-routes.ts +229 -0
  789. package/src/runtime/routes/credential-routes.ts +540 -0
  790. package/src/runtime/routes/debug-routes.ts +2 -2
  791. package/src/runtime/routes/document-pdf-renderer.ts +5 -1
  792. package/src/runtime/routes/documents-routes.ts +25 -86
  793. package/src/runtime/routes/domain-routes.ts +167 -0
  794. package/src/runtime/routes/email-routes.ts +603 -0
  795. package/src/runtime/routes/errors.ts +2 -2
  796. package/src/runtime/routes/events-routes.ts +192 -0
  797. package/src/runtime/routes/group-routes.ts +5 -0
  798. package/src/runtime/routes/home-feed-routes.ts +6 -78
  799. package/src/runtime/routes/host-app-control-routes.ts +44 -2
  800. package/src/runtime/routes/host-browser-routes.ts +103 -22
  801. package/src/runtime/routes/http-adapter.ts +2 -0
  802. package/src/runtime/routes/identity-routes.ts +5 -0
  803. package/src/runtime/routes/image-generation-routes.ts +99 -0
  804. package/src/runtime/routes/inbound-conversation.ts +28 -8
  805. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  806. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +248 -1
  807. package/src/runtime/routes/inbound-stages/background-dispatch.ts +118 -7
  808. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  809. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +156 -0
  810. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +22 -4
  811. package/src/runtime/routes/index.ts +42 -0
  812. package/src/runtime/routes/inference-profile-session-handler.ts +285 -0
  813. package/src/runtime/routes/inference-profile-session-reaper.ts +84 -0
  814. package/src/runtime/routes/inference-profile-session-routes.ts +146 -0
  815. package/src/runtime/routes/inference-provider-connection-routes.ts +361 -0
  816. package/src/runtime/routes/inference-send-routes.ts +115 -0
  817. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  818. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  819. package/src/runtime/routes/integrations/twilio.ts +7 -13
  820. package/src/runtime/routes/mcp-auth-routes.ts +283 -9
  821. package/src/runtime/routes/memory-v2-routes.ts +13 -398
  822. package/src/runtime/routes/notification-routes.ts +3 -1
  823. package/src/runtime/routes/oauth-apps.ts +112 -7
  824. package/src/runtime/routes/oauth-commands-routes.ts +1097 -0
  825. package/src/runtime/routes/oauth-connect-routes.ts +67 -5
  826. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  827. package/src/runtime/routes/oauth-providers.ts +298 -8
  828. package/src/runtime/routes/platform-routes.ts +336 -0
  829. package/src/runtime/routes/playground/inject-failures.ts +2 -1
  830. package/src/runtime/routes/playground/reset-circuit.ts +2 -1
  831. package/src/runtime/routes/playground/state.ts +2 -1
  832. package/src/runtime/routes/publish-routes.ts +221 -0
  833. package/src/runtime/routes/question-routes.ts +259 -0
  834. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  835. package/src/runtime/routes/schedule-routes.ts +79 -0
  836. package/src/runtime/routes/sequence-routes.ts +291 -0
  837. package/src/runtime/routes/settings-routes.ts +2 -10
  838. package/src/runtime/routes/skills-routes.ts +31 -1
  839. package/src/runtime/routes/stt-routes.ts +240 -3
  840. package/src/runtime/routes/subagents-routes.ts +57 -18
  841. package/src/runtime/routes/surface-action-routes.ts +43 -7
  842. package/src/runtime/routes/telemetry-routes.ts +27 -0
  843. package/src/runtime/routes/tts-routes.ts +93 -1
  844. package/src/runtime/routes/types.ts +32 -0
  845. package/src/runtime/routes/user-routes-cli.ts +243 -0
  846. package/src/runtime/routes/webhook-routes.ts +165 -0
  847. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  848. package/src/runtime/routes/workspace-routes.ts +28 -0
  849. package/src/runtime/services/conversation-serializer.ts +39 -7
  850. package/src/runtime/sync/resource-sync-events.ts +117 -0
  851. package/src/runtime/sync/sync-publisher.test.ts +105 -0
  852. package/src/runtime/sync/sync-publisher.ts +21 -0
  853. package/src/schedule/schedule-store.ts +27 -2
  854. package/src/schedule/scheduler.ts +208 -123
  855. package/src/security/__tests__/provider-key-env-fallback.test.ts +12 -6
  856. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  857. package/src/security/secret-patterns.ts +3 -0
  858. package/src/security/untrusted-content.ts +93 -8
  859. package/src/sequence/engine.ts +38 -40
  860. package/src/skills/catalog-files.ts +1 -1
  861. package/src/skills/catalog-install.ts +233 -116
  862. package/src/skills/clawhub.ts +70 -13
  863. package/src/skills/managed-store.ts +4 -119
  864. package/src/skills/skillssh-registry.ts +27 -48
  865. package/src/subagent/manager.ts +28 -15
  866. package/src/telemetry/types.ts +113 -1
  867. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  868. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  869. package/src/tools/apps/executors.ts +58 -7
  870. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  871. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  872. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +206 -0
  873. package/src/tools/browser/browser-execution.ts +29 -14
  874. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +174 -0
  875. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +16 -13
  876. package/src/tools/browser/cdp-client/extension-cdp-client.ts +24 -1
  877. package/src/tools/browser/cdp-client/factory.ts +66 -5
  878. package/src/tools/browser/runtime-check.ts +77 -0
  879. package/src/tools/computer-use/definitions.ts +3 -3
  880. package/src/tools/credentials/vault.ts +1 -1
  881. package/src/tools/document/document-tool.ts +124 -1
  882. package/src/tools/filesystem/edit.ts +1 -1
  883. package/src/tools/filesystem/list.ts +1 -1
  884. package/src/tools/filesystem/read.ts +1 -1
  885. package/src/tools/filesystem/write.ts +5 -2
  886. package/src/tools/host-filesystem/transfer.ts +1 -1
  887. package/src/tools/host-terminal/host-shell.ts +1 -1
  888. package/src/tools/memory/register.test.ts +3 -3
  889. package/src/tools/memory/register.ts +9 -1
  890. package/src/tools/network/__tests__/web-search.test.ts +156 -0
  891. package/src/tools/network/web-search.ts +280 -37
  892. package/src/tools/permission-checker.ts +14 -6
  893. package/src/tools/registry.ts +17 -7
  894. package/src/tools/schedule/create.ts +2 -2
  895. package/src/tools/schema-transforms.ts +7 -2
  896. package/src/tools/side-effects.ts +1 -0
  897. package/src/tools/skills/delete-managed.ts +4 -4
  898. package/src/tools/skills/execute.ts +1 -1
  899. package/src/tools/skills/scaffold-managed.ts +3 -2
  900. package/src/tools/subagent/notify-parent.ts +1 -1
  901. package/src/tools/subagent/spawn.ts +3 -3
  902. package/src/tools/system/request-permission.ts +2 -2
  903. package/src/tools/terminal/safe-env.ts +60 -1
  904. package/src/tools/terminal/shell.ts +44 -0
  905. package/src/tools/tool-manifest.ts +2 -0
  906. package/src/tools/types.ts +72 -21
  907. package/src/tools/ui-surface/definitions.ts +6 -5
  908. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  909. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  910. package/src/types/onboarding-context.ts +2 -0
  911. package/src/usage/attribution.ts +3 -2
  912. package/src/util/errors.ts +17 -0
  913. package/src/util/platform.ts +10 -0
  914. package/src/util/pricing.ts +86 -160
  915. package/src/watcher/__tests__/engine.test.ts +323 -0
  916. package/src/watcher/constants.ts +7 -0
  917. package/src/watcher/engine.ts +94 -90
  918. package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +6 -9
  919. package/src/workspace/migrations/054-seed-recall-callsite.ts +10 -1
  920. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +94 -5
  921. package/src/workspace/migrations/069-seed-onboarding-threads.ts +8 -2
  922. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +117 -0
  923. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +95 -0
  924. package/src/workspace/migrations/074-drop-deprecated-secret-detection-keys.ts +117 -0
  925. package/src/workspace/migrations/075-memory-v2-bm25-b-default-reembed.ts +61 -0
  926. package/src/workspace/migrations/076-drop-services-inference-mode.ts +62 -0
  927. package/src/workspace/migrations/077-seed-memory-router-callsite.ts +89 -0
  928. package/src/workspace/migrations/078-release-notes-tavily-web-search.ts +66 -0
  929. package/src/workspace/migrations/079-home-feed-notification-only.ts +197 -0
  930. package/src/workspace/migrations/080-restrict-vercel-api-token-metadata.ts +182 -0
  931. package/src/workspace/migrations/081-backfill-bash-allowed-tools-for-injection-credentials.ts +160 -0
  932. package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +154 -0
  933. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  934. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  935. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  936. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  937. package/src/workspace/migrations/registry.ts +30 -0
  938. package/src/workspace/migrations/runner.ts +46 -5
  939. package/src/workspace/migrations/types.ts +17 -3
  940. package/src/workspace/provider-commit-message-generator.ts +3 -2
  941. package/examples/plugins/echo/bun.lock +0 -25
  942. package/src/__tests__/context-search-pkb-source.test.ts +0 -498
  943. package/src/__tests__/context-window-manager.test.ts +0 -2093
  944. package/src/__tests__/credentials-cli.test.ts +0 -1225
  945. package/src/__tests__/memory-admin-recall.test.ts +0 -213
  946. package/src/approvals/__tests__/guardian-feed-event.test.ts +0 -303
  947. package/src/cli/commands/__tests__/email-download.test.ts +0 -260
  948. package/src/cli/commands/__tests__/email-list.test.ts +0 -216
  949. package/src/cli/commands/__tests__/email-register.test.ts +0 -186
  950. package/src/cli/commands/__tests__/email-send.test.ts +0 -416
  951. package/src/cli/commands/__tests__/email-status.test.ts +0 -185
  952. package/src/cli/commands/__tests__/email-unregister.test.ts +0 -168
  953. package/src/cli/commands/__tests__/routes.test.ts +0 -562
  954. package/src/cli/commands/__tests__/stt-transcribe.test.ts +0 -454
  955. package/src/cli/commands/autonomy.ts +0 -365
  956. package/src/cli/commands/memory.ts +0 -424
  957. package/src/cli/commands/oauth/__tests__/connect.test.ts +0 -947
  958. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +0 -686
  959. package/src/cli/commands/oauth/__tests__/mode.test.ts +0 -632
  960. package/src/cli/commands/oauth/__tests__/ping.test.ts +0 -631
  961. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +0 -573
  962. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +0 -330
  963. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +0 -521
  964. package/src/cli/commands/oauth/__tests__/status.test.ts +0 -551
  965. package/src/cli/commands/oauth/__tests__/token.test.ts +0 -420
  966. package/src/cli/lib/daemon-avatar-client.ts +0 -37
  967. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -87
  968. package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +0 -207
  969. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  970. package/src/context/prompts/compact.md +0 -26
  971. package/src/daemon/__tests__/conversation-feed-event.test.ts +0 -304
  972. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +0 -233
  973. package/src/home/__tests__/assistant-feed-authoring.test.ts +0 -156
  974. package/src/home/__tests__/emit-feed-event.test.ts +0 -169
  975. package/src/home/__tests__/feed-population-integration.test.ts +0 -312
  976. package/src/home/__tests__/feed-scheduler.test.ts +0 -222
  977. package/src/home/__tests__/phase5-exit-criteria.test.ts +0 -229
  978. package/src/home/__tests__/platform-gmail-digest.test.ts +0 -222
  979. package/src/home/__tests__/rollup-producer.test.ts +0 -507
  980. package/src/home/assistant-feed-authoring.ts +0 -135
  981. package/src/home/emit-feed-event.ts +0 -169
  982. package/src/home/feed-scheduler.ts +0 -281
  983. package/src/home/platform-gmail-digest.ts +0 -163
  984. package/src/home/rewrite-command-preview.ts +0 -66
  985. package/src/home/rewrite-feed-title.ts +0 -58
  986. package/src/home/rollup-producer.ts +0 -426
  987. package/src/memory/admin.ts +0 -326
  988. package/src/memory/context-search/sources/pkb.ts +0 -476
  989. package/src/memory/graph/compaction.ts +0 -299
  990. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  991. /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,
@@ -777,17 +879,17 @@ describe("injectMemoryV2Block", () => {
777
879
  // Unified pool — skills as `skills/<id>` slugs
778
880
  // ---------------------------------------------------------------------------
779
881
 
780
- test("renders a skill-only block via the skills/ slug prefix", async () => {
882
+ test("renders a retrieved skills/<id> slug under Skills You Can Use", async () => {
781
883
  // No concept-page candidates this turn — the only ANN hit is a skill
782
884
  // slug. The render path branches on `skills/` prefix: it pulls the
783
885
  // entry from the skill-store cache (mocked) and emits the bullet under
784
886
  // the `### Skills You Can Use` subsection.
785
- stageTurn([{ slug: "skills/example-skill-a", denseScore: 0.9 }]);
887
+ stageTurn([{ slug: "skills/retrieved-skill", denseScore: 0.9 }]);
786
888
  stageSkills([
787
889
  {
788
- id: "example-skill-a",
890
+ id: "retrieved-skill",
789
891
  content:
790
- 'The "Example Skill A" skill (example-skill-a) is available. Helps with examples.',
892
+ 'The "Retrieved Skill" skill (retrieved-skill) is available. Helps with retrieved skills.',
791
893
  },
792
894
  ]);
793
895
 
@@ -802,16 +904,18 @@ describe("injectMemoryV2Block", () => {
802
904
  config: makeConfig(),
803
905
  });
804
906
 
805
- expect(result.toInject).toEqual(["skills/example-skill-a"]);
907
+ expect(result.toInject).toEqual(["skills/retrieved-skill"]);
806
908
  expect(result.block).not.toBeNull();
807
909
  expect(result.block).not.toContain("<memory>");
808
910
  expect(result.block).not.toContain("</memory>");
809
911
  expect(result.block).not.toContain("## What I Remember Right Now");
810
912
  expect(result.block).not.toContain("# memory/concepts/alice-vscode.md");
811
- expect(result.block).toContain("### Skills You Can Use");
812
- expect(result.block).toContain(
813
- '- The "Example Skill A" skill (example-skill-a) is available. Helps with examples. → use skill_load to activate',
913
+ const headerIdx = result.block!.indexOf("### Skills You Can Use");
914
+ const skillIdx = result.block!.indexOf(
915
+ '- The "Retrieved Skill" skill (retrieved-skill) is available. Helps with retrieved skills. → use skill_load to activate',
814
916
  );
917
+ expect(headerIdx).toBeGreaterThan(-1);
918
+ expect(skillIdx).toBeGreaterThan(headerIdx);
815
919
  });
816
920
 
817
921
  test("renders concept-page sections before the skills subsection in mixed blocks", async () => {
@@ -907,9 +1011,10 @@ describe("injectMemoryV2Block", () => {
907
1011
 
908
1012
  test("skill slugs whose entry is missing from the cache are dropped silently", async () => {
909
1013
  // 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
1014
+ // its content (skill uninstalled mid-run, or a startup race where the
1015
+ // Qdrant row landed before the skill cache was seeded). The render path
1016
+ // drops it without surfacing it as a `missingSlugs` page-missing event —
1017
+ // that status is reserved for on-disk concept pages, not catalog-derived
913
1018
  // skill entries.
914
1019
  stageTurn([{ slug: "skills/missing-skill", denseScore: 0.9 }]);
915
1020
  // No `stageSkills` call — cache stays empty.
@@ -925,10 +1030,16 @@ describe("injectMemoryV2Block", () => {
925
1030
  config: makeConfig(),
926
1031
  });
927
1032
 
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"]);
1033
+ // The skill is excluded from `toInject` (and `everInjected`) so future
1034
+ // per-turn runs re-attempt the attach once the cache is populated.
1035
+ // `block` collapses to null because the only candidate was a cache miss.
1036
+ expect(result.toInject).toEqual([]);
931
1037
  expect(result.block).toBeNull();
1038
+
1039
+ // Persisted `everInjected` must not record the missing skill — that
1040
+ // would block retry on a later turn until compaction-driven eviction.
1041
+ const persisted = await hydrate(db, "conv-1");
1042
+ expect(persisted!.everInjected).toEqual([]);
932
1043
  });
933
1044
 
934
1045
  test("returns null when both concept pages and skills are empty", async () => {
@@ -1287,4 +1398,601 @@ describe("injectMemoryV2Block", () => {
1287
1398
  { slug: "alice-vscode", turn: 1 },
1288
1399
  ]);
1289
1400
  });
1401
+
1402
+ // ---------------------------------------------------------------------------
1403
+ // Per-page error isolation + on-throw telemetry
1404
+ // ---------------------------------------------------------------------------
1405
+
1406
+ test("one slug's page-read failing isolates the error — other slugs still render and the corrupt slug records `status: corrupt`", async () => {
1407
+ // Two slugs rank into top-K together. Carol's page reads cleanly; alice's
1408
+ // `readPage` throws a ZodError mimicking the real "unrecognized
1409
+ // frontmatter key" failure that motivated this work. Before the fix, the
1410
+ // bare `Promise.all` rejected and the entire turn lost its block AND its
1411
+ // activation log row. With per-page isolation, carol still renders and
1412
+ // the activation log row marks alice as `corrupt` (telemetry remains
1413
+ // observable for triage).
1414
+ const zodErr = z.object({ x: z.string() }).safeParse({ x: 1 }).error!;
1415
+ pageStoreState.failingSlugs.set("alice-vscode", zodErr);
1416
+ stageTurn([
1417
+ { slug: "alice-vscode", denseScore: 0.95 },
1418
+ { slug: "carol-jazz", denseScore: 0.9 },
1419
+ ]);
1420
+
1421
+ const result = await injectMemoryV2Block({
1422
+ database: db,
1423
+ conversationId: "conv-1",
1424
+ currentTurn: 1,
1425
+ userMessage: "music and editors",
1426
+ assistantMessage: "",
1427
+ nowText: "Now",
1428
+ messageId: "msg-1",
1429
+ config: makeConfig(),
1430
+ });
1431
+
1432
+ // (a) Block is non-null and contains content from the OTHER slug; alice
1433
+ // is dropped from the rendered block but does not poison the batch.
1434
+ expect(result.block).not.toBeNull();
1435
+ expect(result.block).toContain("# memory/concepts/carol-jazz.md");
1436
+ expect(result.block).not.toContain("# memory/concepts/alice-vscode.md");
1437
+
1438
+ // (b) Activation log row exists with carol `injected` and alice
1439
+ // `corrupt`. Status `corrupt` is reserved for read-time throws and is
1440
+ // distinct from `page_missing` (which is null-return / file vanished).
1441
+ expect(telemetryState.recordCalls.length).toBe(1);
1442
+ const row = telemetryState.recordCalls[0] as {
1443
+ mode: string;
1444
+ concepts: Array<{ slug: string; status: string }>;
1445
+ };
1446
+ expect(row.mode).toBe("per-turn");
1447
+ const byslug = new Map(row.concepts.map((c) => [c.slug, c]));
1448
+ expect(byslug.get("alice-vscode")!.status).toBe("corrupt");
1449
+ expect(byslug.get("carol-jazz")!.status).toBe("injected");
1450
+
1451
+ // (c) Both slugs land in `toInject` and `everInjected` — same handling
1452
+ // as `page_missing` (see the phantom-slug test): the slug was attempted
1453
+ // this turn, telemetry records the outcome, and we don't keep re-trying
1454
+ // a stale Qdrant / edge-index entry on every subsequent turn.
1455
+ expect(new Set(result.toInject)).toEqual(
1456
+ new Set(["alice-vscode", "carol-jazz"]),
1457
+ );
1458
+ const persisted = await hydrate(db, "conv-1");
1459
+ const everInjectedSlugs = persisted!.everInjected.map((e) => e.slug);
1460
+ expect(new Set(everInjectedSlugs)).toEqual(
1461
+ new Set(["alice-vscode", "carol-jazz"]),
1462
+ );
1463
+ });
1464
+
1465
+ test("a throw before renderInjectionBlock still flushes telemetry as `mode: errored` and re-throws", async () => {
1466
+ // The activation-state save throws — the most realistic non-render
1467
+ // failure mode (transient SQLite write error mid-injection). The
1468
+ // `injectMemoryV2Block` outer try/finally must (a) flush an activation
1469
+ // log row tagged `mode: "errored"` so silent failures stay observable
1470
+ // in the database, and (b) re-throw so callers (e.g. `prepareMemory`'s
1471
+ // outer catch) see the original error and can degrade gracefully.
1472
+ activationStoreState.saveShouldThrow = true;
1473
+ stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
1474
+
1475
+ let threw: unknown = undefined;
1476
+ try {
1477
+ await injectMemoryV2Block({
1478
+ database: db,
1479
+ conversationId: "conv-1",
1480
+ currentTurn: 1,
1481
+ userMessage: "Alice's editor",
1482
+ assistantMessage: "",
1483
+ nowText: "Now",
1484
+ messageId: "msg-1",
1485
+ config: makeConfig(),
1486
+ });
1487
+ } catch (err) {
1488
+ threw = err;
1489
+ }
1490
+
1491
+ // The original error propagates to the caller.
1492
+ expect(threw).toBeInstanceOf(Error);
1493
+ expect((threw as Error).message).toContain(
1494
+ "simulated activation-store save failure",
1495
+ );
1496
+
1497
+ // A telemetry row was still written, tagged `errored`. `concepts` is
1498
+ // empty because the throw fired before the row-builder ran — that's
1499
+ // expected and documented as part of the contract.
1500
+ expect(telemetryState.recordCalls.length).toBe(1);
1501
+ const row = telemetryState.recordCalls[0] as {
1502
+ mode: string;
1503
+ conversationId: string;
1504
+ turn: number;
1505
+ concepts: unknown[];
1506
+ };
1507
+ expect(row.mode).toBe("errored");
1508
+ expect(row.conversationId).toBe("conv-1");
1509
+ expect(row.turn).toBe(1);
1510
+ expect(row.concepts).toEqual([]);
1511
+ });
1512
+
1513
+ test("activation pipeline routes through `finalizeInjection` — telemetry shape and config snapshot match the contract", async () => {
1514
+ // Pure-refactor regression check: `injectMemoryV2Block` now delegates the
1515
+ // tail (state save + render + telemetry finalization + log write) to a
1516
+ // private `finalizeInjection` helper. This test asserts the helper is
1517
+ // exercised by verifying `recordMemoryV2ActivationLog` is called with the
1518
+ // same arg shape as before — same conversationId/turn/mode, same config
1519
+ // snapshot, and a fully populated concept row whose status was finalized
1520
+ // to `"injected"` on the freshly-attached slug.
1521
+ stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
1522
+
1523
+ const result = await injectMemoryV2Block({
1524
+ database: db,
1525
+ conversationId: "conv-finalize",
1526
+ currentTurn: 7,
1527
+ userMessage: "Alice's editor",
1528
+ assistantMessage: "",
1529
+ nowText: "Now",
1530
+ messageId: "msg-finalize",
1531
+ config: makeConfig(),
1532
+ });
1533
+
1534
+ // The helper rendered + persisted just like the original tail did.
1535
+ expect(result.block).toContain("alice-vscode");
1536
+ expect(result.toInject).toEqual(["alice-vscode"]);
1537
+
1538
+ expect(telemetryState.recordCalls.length).toBe(1);
1539
+ const row = telemetryState.recordCalls[0] as {
1540
+ conversationId: string;
1541
+ turn: number;
1542
+ mode: string;
1543
+ concepts: Array<{
1544
+ slug: string;
1545
+ status: string;
1546
+ finalActivation: number;
1547
+ }>;
1548
+ config: {
1549
+ d: number;
1550
+ c_user: number;
1551
+ c_assistant: number;
1552
+ c_now: number;
1553
+ k: number;
1554
+ hops: number;
1555
+ top_k: number;
1556
+ epsilon: number;
1557
+ };
1558
+ };
1559
+ expect(row.conversationId).toBe("conv-finalize");
1560
+ expect(row.turn).toBe(7);
1561
+ expect(row.mode).toBe("per-turn");
1562
+ // Config snapshot must include all eight tunables — proves the helper is
1563
+ // pulling from `config.memory.v2` rather than synthesizing a partial.
1564
+ expect(Object.keys(row.config).sort()).toEqual(
1565
+ [
1566
+ "c_assistant",
1567
+ "c_now",
1568
+ "c_user",
1569
+ "d",
1570
+ "epsilon",
1571
+ "hops",
1572
+ "k",
1573
+ "top_k",
1574
+ ].sort(),
1575
+ );
1576
+ // Status finalization ran inside the helper — alice was selected and
1577
+ // rendered, so its row reads `injected`.
1578
+ const aliceRow = row.concepts.find((c) => c.slug === "alice-vscode");
1579
+ expect(aliceRow?.status).toBe("injected");
1580
+ });
1581
+
1582
+ // ---------------------------------------------------------------------------
1583
+ // Router mode (flag-gated)
1584
+ // ---------------------------------------------------------------------------
1585
+
1586
+ describe("router mode", () => {
1587
+ test("flag-on: router-selected slugs render and append to everInjected", async () => {
1588
+ // Router picks alice. The activation pipeline never runs — we don't
1589
+ // stage any qdrant responses here, and that's intentional. The
1590
+ // candidate set comes straight from the router's `selectedSlugs`.
1591
+ routerState.nextResult = {
1592
+ selectedSlugs: ["alice-vscode"],
1593
+ failureReason: null,
1594
+ };
1595
+
1596
+ const result = await injectMemoryV2Block({
1597
+ database: db,
1598
+ conversationId: "conv-router-1",
1599
+ currentTurn: 1,
1600
+ userMessage: "Tell me about Alice",
1601
+ assistantMessage: "",
1602
+ nowText: "Now",
1603
+ messageId: "msg-1",
1604
+ config: makeConfig({ router: { enabled: true } }),
1605
+ });
1606
+
1607
+ expect(routerState.callCount).toBe(1);
1608
+ expect(result.toInject).toEqual(["alice-vscode"]);
1609
+ expect(result.block).not.toBeNull();
1610
+ expect(result.block).toContain("# memory/concepts/alice-vscode.md");
1611
+
1612
+ const persisted = await hydrate(db, "conv-router-1");
1613
+ expect(persisted!.everInjected).toEqual([
1614
+ { slug: "alice-vscode", turn: 1 },
1615
+ ]);
1616
+ // Router mode persists an empty sparse activation map — the router
1617
+ // does not compute spreading-activation scores.
1618
+ expect(persisted!.state).toEqual({});
1619
+
1620
+ // Telemetry: success rows get `mode: "router"` and `source: "router"`,
1621
+ // with all activation fields zeroed.
1622
+ expect(telemetryState.recordCalls.length).toBe(1);
1623
+ const row = telemetryState.recordCalls[0] as {
1624
+ mode: string;
1625
+ concepts: Array<{
1626
+ slug: string;
1627
+ source: string;
1628
+ status: string;
1629
+ finalActivation: number;
1630
+ ownActivation: number;
1631
+ }>;
1632
+ };
1633
+ expect(row.mode).toBe("router");
1634
+ const aliceRow = row.concepts.find((c) => c.slug === "alice-vscode");
1635
+ expect(aliceRow).toBeDefined();
1636
+ expect(aliceRow!.source).toBe("router");
1637
+ expect(aliceRow!.status).toBe("injected");
1638
+ expect(aliceRow!.finalActivation).toBe(0);
1639
+ expect(aliceRow!.ownActivation).toBe(0);
1640
+ });
1641
+
1642
+ test("flag-on: router failure logs warn, writes mode:`errored` telemetry, returns null block", async () => {
1643
+ routerState.nextResult = {
1644
+ selectedSlugs: [],
1645
+ failureReason: "api_error",
1646
+ };
1647
+
1648
+ const result = await injectMemoryV2Block({
1649
+ database: db,
1650
+ conversationId: "conv-router-fail",
1651
+ currentTurn: 3,
1652
+ userMessage: "anything",
1653
+ assistantMessage: "ok",
1654
+ nowText: "Now",
1655
+ messageId: "msg-fail",
1656
+ config: makeConfig({ router: { enabled: true } }),
1657
+ });
1658
+
1659
+ expect(result.block).toBeNull();
1660
+ expect(result.toInject).toEqual([]);
1661
+
1662
+ // Stub state still advanced.
1663
+ const persisted = await hydrate(db, "conv-router-fail");
1664
+ expect(persisted).not.toBeNull();
1665
+ expect(persisted!.currentTurn).toBe(3);
1666
+ expect(persisted!.messageId).toBe("msg-fail");
1667
+ expect(persisted!.state).toEqual({});
1668
+ expect(persisted!.everInjected).toEqual([]);
1669
+
1670
+ // Single telemetry row with `mode: "errored"` (not `"router"`).
1671
+ expect(telemetryState.recordCalls.length).toBe(1);
1672
+ const row = telemetryState.recordCalls[0] as {
1673
+ mode: string;
1674
+ conversationId: string;
1675
+ turn: number;
1676
+ concepts: unknown[];
1677
+ };
1678
+ expect(row.mode).toBe("errored");
1679
+ expect(row.conversationId).toBe("conv-router-fail");
1680
+ expect(row.turn).toBe(3);
1681
+ expect(row.concepts).toEqual([]);
1682
+ });
1683
+
1684
+ test("flag-on: router-failure path swallows a save() error and returns block:null instead of throwing", async () => {
1685
+ // PR 30176 refactored router-failure handling to delegate to
1686
+ // `finalizeInjection`. That regressed the prior inline log-and-continue
1687
+ // semantics on the router-failure path: a transient SQLite write
1688
+ // throwing during the stub-state save now aborted the whole turn
1689
+ // because `finalizeInjection`'s try/catch re-threw caughtErr at the end.
1690
+ //
1691
+ // This test stages exactly that scenario — router returns
1692
+ // `failureReason: api_error` AND `save()` throws — and asserts the
1693
+ // turn completes with `{ block: null, toInject: [] }` rather than
1694
+ // propagating the SQLite error to `prepareMemory`.
1695
+ routerState.nextResult = {
1696
+ selectedSlugs: [],
1697
+ failureReason: "api_error",
1698
+ };
1699
+ activationStoreState.saveShouldThrow = true;
1700
+
1701
+ let threw: unknown = undefined;
1702
+ let result: Awaited<ReturnType<typeof injectMemoryV2Block>> | undefined;
1703
+ try {
1704
+ result = await injectMemoryV2Block({
1705
+ database: db,
1706
+ conversationId: "conv-router-fail-save-throws",
1707
+ currentTurn: 5,
1708
+ userMessage: "anything",
1709
+ assistantMessage: "ok",
1710
+ nowText: "Now",
1711
+ messageId: "msg-fail-save",
1712
+ config: makeConfig({ router: { enabled: true } }),
1713
+ });
1714
+ } catch (err) {
1715
+ threw = err;
1716
+ }
1717
+
1718
+ expect(threw).toBeUndefined();
1719
+ expect(result).toBeDefined();
1720
+ expect(result!.block).toBeNull();
1721
+ expect(result!.toInject).toEqual([]);
1722
+
1723
+ // Telemetry still flushes with `mode: "errored"` so the failure stays
1724
+ // observable — the same row the inline pre-refactor path emitted.
1725
+ expect(telemetryState.recordCalls.length).toBe(1);
1726
+ const row = telemetryState.recordCalls[0] as {
1727
+ mode: string;
1728
+ concepts: unknown[];
1729
+ };
1730
+ expect(row.mode).toBe("errored");
1731
+ expect(row.concepts).toEqual([]);
1732
+ });
1733
+
1734
+ test("flag-on: router abstention (empty selectedSlugs, no failure) writes mode:`router` row with no injected pages", async () => {
1735
+ routerState.nextResult = {
1736
+ selectedSlugs: [],
1737
+ failureReason: null,
1738
+ };
1739
+
1740
+ const result = await injectMemoryV2Block({
1741
+ database: db,
1742
+ conversationId: "conv-router-abstain",
1743
+ currentTurn: 1,
1744
+ userMessage: "small talk",
1745
+ assistantMessage: "",
1746
+ nowText: "Now",
1747
+ messageId: "msg-abstain",
1748
+ config: makeConfig({ router: { enabled: true } }),
1749
+ });
1750
+
1751
+ expect(result.block).toBeNull();
1752
+ expect(result.toInject).toEqual([]);
1753
+
1754
+ // No prior everInjected to dedup against, so toInject is empty and
1755
+ // nothing renders. State still advanced.
1756
+ const persisted = await hydrate(db, "conv-router-abstain");
1757
+ expect(persisted!.everInjected).toEqual([]);
1758
+ expect(persisted!.currentTurn).toBe(1);
1759
+
1760
+ // Telemetry: `mode: "router"` row with zero injected pages.
1761
+ expect(telemetryState.recordCalls.length).toBe(1);
1762
+ const row = telemetryState.recordCalls[0] as {
1763
+ mode: string;
1764
+ concepts: Array<{ slug: string; status: string }>;
1765
+ };
1766
+ expect(row.mode).toBe("router");
1767
+ const injectedCount = row.concepts.filter(
1768
+ (c) => c.status === "injected",
1769
+ ).length;
1770
+ expect(injectedCount).toBe(0);
1771
+ });
1772
+
1773
+ test("flag-on: router-selected slug whose page is missing on disk records `page_missing` and is NOT added to everInjected", async () => {
1774
+ routerState.nextResult = {
1775
+ selectedSlugs: ["phantom-router-slug"],
1776
+ failureReason: null,
1777
+ };
1778
+
1779
+ const result = await injectMemoryV2Block({
1780
+ database: db,
1781
+ conversationId: "conv-router-missing",
1782
+ currentTurn: 1,
1783
+ userMessage: "phantom",
1784
+ assistantMessage: "",
1785
+ nowText: "Now",
1786
+ messageId: "msg-missing",
1787
+ config: makeConfig({ router: { enabled: true } }),
1788
+ });
1789
+
1790
+ // No backing page → block collapses to null.
1791
+ expect(result.block).toBeNull();
1792
+ // toInject mirrors `newlyInjected` from `finalizeInjection` — the
1793
+ // missing slug still flowed through `slugsToRender` so it's recorded
1794
+ // here (matching the activation-mode phantom-slug contract).
1795
+ expect(result.toInject).toEqual(["phantom-router-slug"]);
1796
+
1797
+ // Activation-mode parity: the phantom slug DOES land in everInjected
1798
+ // so we don't infinite-retry it. (This matches the behavior the
1799
+ // existing `returns null block when toInject slugs all reference
1800
+ // missing pages` test asserts for activation mode.)
1801
+ const persisted = await hydrate(db, "conv-router-missing");
1802
+ expect(persisted!.everInjected).toEqual([
1803
+ { slug: "phantom-router-slug", turn: 1 },
1804
+ ]);
1805
+
1806
+ // Telemetry: `status: "page_missing"` for the phantom slug.
1807
+ expect(telemetryState.recordCalls.length).toBe(1);
1808
+ const row = telemetryState.recordCalls[0] as {
1809
+ mode: string;
1810
+ concepts: Array<{ slug: string; status: string; source: string }>;
1811
+ };
1812
+ expect(row.mode).toBe("router");
1813
+ const phantom = row.concepts.find(
1814
+ (c) => c.slug === "phantom-router-slug",
1815
+ );
1816
+ expect(phantom).toBeDefined();
1817
+ expect(phantom!.status).toBe("page_missing");
1818
+ expect(phantom!.source).toBe("router");
1819
+ });
1820
+
1821
+ test("flag-on: router re-picking a prior-everInjected slug does NOT re-render it; non-overlapping picks render and append to everInjected", async () => {
1822
+ // Turn 1: router picks alice. Standard append.
1823
+ routerState.nextResult = {
1824
+ selectedSlugs: ["alice-vscode"],
1825
+ failureReason: null,
1826
+ };
1827
+ const turn1 = await injectMemoryV2Block({
1828
+ database: db,
1829
+ conversationId: "conv-router-dedup",
1830
+ currentTurn: 1,
1831
+ userMessage: "Tell me about Alice",
1832
+ assistantMessage: "",
1833
+ nowText: "Now",
1834
+ messageId: "msg-1",
1835
+ config: makeConfig({ router: { enabled: true } }),
1836
+ });
1837
+ expect(turn1.toInject).toEqual(["alice-vscode"]);
1838
+
1839
+ // Turn 2: router re-picks alice (the "re-anchor" prompt branch) AND
1840
+ // adds bob. The block must NOT contain alice's body — her cached
1841
+ // attachment from turn 1 is still on the prior user message — but
1842
+ // must contain bob's.
1843
+ telemetryState.recordCalls.length = 0;
1844
+ routerState.nextResult = {
1845
+ selectedSlugs: ["alice-vscode", "bob-coffee"],
1846
+ failureReason: null,
1847
+ };
1848
+ const turn2 = await injectMemoryV2Block({
1849
+ database: db,
1850
+ conversationId: "conv-router-dedup",
1851
+ currentTurn: 2,
1852
+ userMessage: "And Bob?",
1853
+ assistantMessage: "Sure",
1854
+ nowText: "Now",
1855
+ messageId: "msg-2",
1856
+ config: makeConfig({ router: { enabled: true } }),
1857
+ });
1858
+
1859
+ // Re-picked alice was deduped; only bob is freshly injected.
1860
+ expect(turn2.toInject).toEqual(["bob-coffee"]);
1861
+ expect(turn2.block).not.toBeNull();
1862
+ expect(turn2.block).toContain("# memory/concepts/bob-coffee.md");
1863
+ expect(turn2.block).toContain("Bob takes his coffee");
1864
+ expect(turn2.block).not.toContain("VS Code");
1865
+ expect(turn2.block).not.toContain("# memory/concepts/alice-vscode.md");
1866
+
1867
+ // everInjected only gained bob — alice was already there.
1868
+ const persisted = await hydrate(db, "conv-router-dedup");
1869
+ expect(persisted!.everInjected).toEqual([
1870
+ { slug: "alice-vscode", turn: 1 },
1871
+ { slug: "bob-coffee", turn: 2 },
1872
+ ]);
1873
+ });
1874
+
1875
+ test("flag-on: telemetry distinguishes `source: router` (router picks) from `source: carry_over` (prior-everInjected slugs the router did not re-pick)", async () => {
1876
+ // Turn 1: seed everInjected with alice.
1877
+ routerState.nextResult = {
1878
+ selectedSlugs: ["alice-vscode"],
1879
+ failureReason: null,
1880
+ };
1881
+ await injectMemoryV2Block({
1882
+ database: db,
1883
+ conversationId: "conv-router-source",
1884
+ currentTurn: 1,
1885
+ userMessage: "Alice",
1886
+ assistantMessage: "",
1887
+ nowText: "Now",
1888
+ messageId: "msg-1",
1889
+ config: makeConfig({ router: { enabled: true } }),
1890
+ });
1891
+ telemetryState.recordCalls.length = 0;
1892
+
1893
+ // Turn 2: router picks bob only. alice is still in everInjected but
1894
+ // not re-picked — her telemetry row must read `source: carry_over`,
1895
+ // not `source: router`.
1896
+ routerState.nextResult = {
1897
+ selectedSlugs: ["bob-coffee"],
1898
+ failureReason: null,
1899
+ };
1900
+ await injectMemoryV2Block({
1901
+ database: db,
1902
+ conversationId: "conv-router-source",
1903
+ currentTurn: 2,
1904
+ userMessage: "Bob",
1905
+ assistantMessage: "",
1906
+ nowText: "Now",
1907
+ messageId: "msg-2",
1908
+ config: makeConfig({ router: { enabled: true } }),
1909
+ });
1910
+
1911
+ expect(telemetryState.recordCalls.length).toBe(1);
1912
+ const row = telemetryState.recordCalls[0] as {
1913
+ mode: string;
1914
+ concepts: Array<{ slug: string; source: string; status: string }>;
1915
+ };
1916
+ expect(row.mode).toBe("router");
1917
+ const aliceRow = row.concepts.find((c) => c.slug === "alice-vscode");
1918
+ const bobRow = row.concepts.find((c) => c.slug === "bob-coffee");
1919
+ expect(aliceRow).toBeDefined();
1920
+ expect(bobRow).toBeDefined();
1921
+ expect(aliceRow!.source).toBe("carry_over");
1922
+ expect(aliceRow!.status).toBe("in_context");
1923
+ expect(bobRow!.source).toBe("router");
1924
+ expect(bobRow!.status).toBe("injected");
1925
+ });
1926
+
1927
+ test("flag-off (default): activation pipeline still runs unchanged", async () => {
1928
+ // Regression check — with the router flag explicitly off (the
1929
+ // production default), `runRouter` must never be called and the
1930
+ // activation pipeline drives the selection just like before.
1931
+ stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
1932
+ routerState.nextResult = {
1933
+ selectedSlugs: ["should-not-be-used"],
1934
+ failureReason: null,
1935
+ };
1936
+
1937
+ const result = await injectMemoryV2Block({
1938
+ database: db,
1939
+ conversationId: "conv-flag-off",
1940
+ currentTurn: 1,
1941
+ userMessage: "Alice's editor",
1942
+ assistantMessage: "",
1943
+ nowText: "Now",
1944
+ messageId: "msg-1",
1945
+ config: makeConfig({ router: { enabled: false } }),
1946
+ });
1947
+
1948
+ // Router was not called.
1949
+ expect(routerState.callCount).toBe(0);
1950
+ // Activation pipeline produced its normal result.
1951
+ expect(result.toInject).toEqual(["alice-vscode"]);
1952
+ expect(result.block).toContain("# memory/concepts/alice-vscode.md");
1953
+
1954
+ // Telemetry row carries the activation mode, not router.
1955
+ expect(telemetryState.recordCalls.length).toBe(1);
1956
+ const row = telemetryState.recordCalls[0] as { mode: string };
1957
+ expect(row.mode).toBe("per-turn");
1958
+ });
1959
+
1960
+ test("flag-on + mode='context-load': router runs (everInjected was cleared by onCompacted so dedupe is a no-op; abstention is accepted as the trade-off)", async () => {
1961
+ // Context-load is the full top-K bootstrap fired after compaction or
1962
+ // a fresh conversation reload. The earlier worry about the router's
1963
+ // `everInjected` dedupe filtering out post-compaction restorations
1964
+ // doesn't apply: `ConversationGraphMemory.onCompacted` calls
1965
+ // `evictCompactedTurnsV2` which empties the list before this code
1966
+ // runs. Router abstention here means no v2 pages this turn — that's
1967
+ // preferable to letting the activation graph pick something arbitrary.
1968
+ routerState.nextResult = {
1969
+ selectedSlugs: ["alice-vscode"],
1970
+ failureReason: null,
1971
+ };
1972
+
1973
+ const result = await injectMemoryV2Block({
1974
+ database: db,
1975
+ conversationId: "conv-context-load-router-on",
1976
+ currentTurn: 1,
1977
+ userMessage: "Tell me about Alice",
1978
+ assistantMessage: "",
1979
+ nowText: "Now",
1980
+ messageId: "msg-1",
1981
+ mode: "context-load",
1982
+ config: makeConfig({ router: { enabled: true } }),
1983
+ });
1984
+
1985
+ // Router was called on context-load too.
1986
+ expect(routerState.callCount).toBe(1);
1987
+
1988
+ // Router's picks were rendered.
1989
+ expect(result.toInject).toEqual(["alice-vscode"]);
1990
+ expect(result.block).toContain("# memory/concepts/alice-vscode.md");
1991
+
1992
+ // Telemetry row reflects the router mode, not the activation mode.
1993
+ expect(telemetryState.recordCalls.length).toBe(1);
1994
+ const row = telemetryState.recordCalls[0] as { mode: string };
1995
+ expect(row.mode).toBe("router");
1996
+ });
1997
+ });
1290
1998
  });