@vellumai/assistant 0.7.2 → 0.8.0

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 (424) hide show
  1. package/ARCHITECTURE.md +45 -29
  2. package/Dockerfile +1 -0
  3. package/__tests__/permissions/gateway-threshold-reader.test.ts +236 -9
  4. package/bun.lock +3 -0
  5. package/docs/architecture/memory.md +5 -2
  6. package/knip.json +1 -0
  7. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
  8. package/node_modules/@vellumai/ipc-server-utils/bun.lock +24 -0
  9. package/node_modules/@vellumai/ipc-server-utils/package.json +18 -0
  10. package/node_modules/@vellumai/ipc-server-utils/src/index.ts +6 -0
  11. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +430 -0
  12. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +221 -0
  13. package/node_modules/@vellumai/ipc-server-utils/tsconfig.json +20 -0
  14. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -9
  15. package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
  16. package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
  17. package/openapi.yaml +470 -25
  18. package/package.json +3 -1
  19. package/src/__tests__/annotate-risk-options.test.ts +291 -0
  20. package/src/__tests__/app-control-flow.test.ts +21 -11
  21. package/src/__tests__/approval-cascade.test.ts +8 -16
  22. package/src/__tests__/approval-routes-http.test.ts +6 -0
  23. package/src/__tests__/assistant-event-hub.test.ts +48 -0
  24. package/src/__tests__/assistant-event.test.ts +0 -10
  25. package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
  26. package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
  27. package/src/__tests__/auto-analysis-end-to-end.test.ts +48 -0
  28. package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
  29. package/src/__tests__/call-constants.test.ts +10 -1
  30. package/src/__tests__/call-controller.test.ts +127 -0
  31. package/src/__tests__/call-conversation-messages.test.ts +8 -2
  32. package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
  33. package/src/__tests__/channel-readiness-service.test.ts +4 -2
  34. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +58 -28
  35. package/src/__tests__/config-loader-backfill.test.ts +379 -0
  36. package/src/__tests__/config-loader-platform-defaults.test.ts +284 -1
  37. package/src/__tests__/config-schema.test.ts +1 -0
  38. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
  39. package/src/__tests__/config-watcher.test.ts +140 -69
  40. package/src/__tests__/context-search-agent-runner.test.ts +61 -3
  41. package/src/__tests__/context-search-conversations-source.test.ts +0 -24
  42. package/src/__tests__/context-search-fanout.test.ts +0 -1
  43. package/src/__tests__/context-search-memory-source.test.ts +6 -33
  44. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
  45. package/src/__tests__/context-search-pkb-source.test.ts +12 -7
  46. package/src/__tests__/context-search-workspace-source.test.ts +0 -1
  47. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -0
  48. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
  49. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
  50. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
  51. package/src/__tests__/conversation-agent-loop.test.ts +457 -8
  52. package/src/__tests__/conversation-confirmation-signals.test.ts +5 -13
  53. package/src/__tests__/conversation-error.test.ts +150 -3
  54. package/src/__tests__/conversation-init.benchmark.test.ts +1 -1
  55. package/src/__tests__/conversation-process-callsite.test.ts +38 -0
  56. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
  57. package/src/__tests__/conversation-runtime-assembly.test.ts +74 -0
  58. package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
  59. package/src/__tests__/conversation-speed-override.test.ts +0 -3
  60. package/src/__tests__/conversation-store.test.ts +0 -18
  61. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +170 -9
  62. package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
  63. package/src/__tests__/conversation-surfaces-data-persist.test.ts +476 -0
  64. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +61 -5
  65. package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
  66. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  67. package/src/__tests__/credentials-cli.test.ts +7 -0
  68. package/src/__tests__/cu-unified-flow.test.ts +176 -10
  69. package/src/__tests__/date-context.test.ts +164 -2
  70. package/src/__tests__/disk-pressure-guard.test.ts +262 -0
  71. package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
  72. package/src/__tests__/disk-pressure-policy.test.ts +241 -0
  73. package/src/__tests__/disk-pressure-routes.test.ts +379 -0
  74. package/src/__tests__/disk-pressure-tools.test.ts +277 -0
  75. package/src/__tests__/disk-usage.test.ts +150 -0
  76. package/src/__tests__/events-client-registration.test.ts +52 -0
  77. package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
  78. package/src/__tests__/file-write-tool.test.ts +4 -10
  79. package/src/__tests__/filing-service.test.ts +2 -20
  80. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +10 -26
  81. package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
  82. package/src/__tests__/heartbeat-service.test.ts +260 -11
  83. package/src/__tests__/host-app-control-proxy.test.ts +195 -25
  84. package/src/__tests__/host-bash-proxy.test.ts +227 -34
  85. package/src/__tests__/host-bash-routes.test.ts +178 -13
  86. package/src/__tests__/host-cu-proxy.test.ts +210 -3
  87. package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
  88. package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
  89. package/src/__tests__/host-file-proxy.test.ts +268 -6
  90. package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
  91. package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
  92. package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
  93. package/src/__tests__/http-user-message-parity.test.ts +107 -1
  94. package/src/__tests__/injector-chain.test.ts +36 -16
  95. package/src/__tests__/injector-disk-pressure.test.ts +224 -0
  96. package/src/__tests__/injector-pkb-v2-silenced.test.ts +10 -7
  97. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +154 -67
  98. package/src/__tests__/managed-profile-guard.test.ts +18 -0
  99. package/src/__tests__/mcp-abort-signal.test.ts +130 -0
  100. package/src/__tests__/memory-admin-recall.test.ts +3 -11
  101. package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
  102. package/src/__tests__/normalize-onboarding.test.ts +180 -0
  103. package/src/__tests__/notification-decision-fallback.test.ts +91 -0
  104. package/src/__tests__/notification-decision-strategy.test.ts +22 -0
  105. package/src/__tests__/oauth-cli.test.ts +121 -0
  106. package/src/__tests__/oauth-connect-routes.test.ts +316 -0
  107. package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
  108. package/src/__tests__/onboarding-persona-write.test.ts +308 -0
  109. package/src/__tests__/openai-provider.test.ts +45 -8
  110. package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
  111. package/src/__tests__/platform-callback-registration.test.ts +21 -4
  112. package/src/__tests__/platform.test.ts +2 -1
  113. package/src/__tests__/playbook-execution.test.ts +0 -43
  114. package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
  115. package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
  116. package/src/__tests__/provider-tool-name.test.ts +23 -0
  117. package/src/__tests__/relay-server.test.ts +60 -5
  118. package/src/__tests__/runtime-events-sse.test.ts +4 -8
  119. package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
  120. package/src/__tests__/secret-ingress-http.test.ts +0 -1
  121. package/src/__tests__/secret-prompt-log-hygiene.test.ts +7 -5
  122. package/src/__tests__/secret-prompter-channel-fallback.test.ts +7 -5
  123. package/src/__tests__/secret-response-routing.test.ts +7 -5
  124. package/src/__tests__/server-history-render.test.ts +82 -0
  125. package/src/__tests__/skill-include-graph.test.ts +31 -0
  126. package/src/__tests__/skill-load-tool.test.ts +44 -16
  127. package/src/__tests__/skills.test.ts +39 -0
  128. package/src/__tests__/suggestion-routes.test.ts +46 -0
  129. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -42
  130. package/src/__tests__/tool-executor.test.ts +155 -0
  131. package/src/__tests__/twilio-validation.test.ts +2 -2
  132. package/src/__tests__/voice-session-bridge.test.ts +3 -0
  133. package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
  134. package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
  135. package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
  136. package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +120 -0
  137. package/src/__tests__/workspace-migration-071-remove-safe-storage-release-note.test.ts +206 -0
  138. package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +78 -0
  139. package/src/agent/loop.ts +11 -0
  140. package/src/approvals/guardian-request-resolvers.ts +3 -32
  141. package/src/backup/snapshot-lock.ts +2 -27
  142. package/src/bundler/compiler-tools.ts +3 -2
  143. package/src/calls/call-constants.ts +5 -8
  144. package/src/calls/call-controller.ts +130 -67
  145. package/src/calls/call-conversation-messages.ts +46 -10
  146. package/src/calls/relay-server.ts +7 -1
  147. package/src/calls/voice-session-bridge.ts +1 -1
  148. package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
  149. package/src/cli/commands/bash.ts +35 -108
  150. package/src/cli/commands/contacts.ts +64 -25
  151. package/src/cli/commands/credentials.ts +56 -0
  152. package/src/cli/commands/memory-v2.ts +11 -10
  153. package/src/cli/commands/oauth/__tests__/connect.test.ts +401 -219
  154. package/src/cli/commands/oauth/connect.ts +124 -40
  155. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
  156. package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
  157. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  158. package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
  159. package/src/cli/commands/platform/index.ts +16 -7
  160. package/src/cli/commands/status.ts +57 -0
  161. package/src/cli/program.ts +4 -2
  162. package/src/config/assistant-feature-flags.ts +13 -3
  163. package/src/config/bundled-skills/app-builder/SKILL.md +1 -3
  164. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
  165. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
  166. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
  167. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
  168. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
  169. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
  170. package/src/config/env.ts +0 -8
  171. package/src/config/feature-flag-registry.json +13 -5
  172. package/src/config/loader.ts +199 -27
  173. package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
  174. package/src/config/schemas/call-site-catalog.ts +14 -0
  175. package/src/config/schemas/channels.ts +0 -5
  176. package/src/config/schemas/heartbeat.ts +1 -1
  177. package/src/config/schemas/llm.ts +2 -0
  178. package/src/config/schemas/memory-lifecycle.ts +13 -0
  179. package/src/config/schemas/memory-v2.ts +76 -12
  180. package/src/config/schemas/platform.ts +43 -3
  181. package/src/config/schemas/services.ts +28 -0
  182. package/src/config/seed-inference-profiles.ts +230 -33
  183. package/src/contacts/contact-store.ts +0 -25
  184. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +32 -0
  185. package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
  186. package/src/daemon/assistant-attachments.ts +4 -4
  187. package/src/daemon/config-watcher.ts +85 -57
  188. package/src/daemon/conversation-agent-loop-handlers.ts +38 -0
  189. package/src/daemon/conversation-agent-loop.ts +183 -43
  190. package/src/daemon/conversation-error.ts +87 -15
  191. package/src/daemon/conversation-lifecycle.ts +22 -10
  192. package/src/daemon/conversation-process.ts +8 -0
  193. package/src/daemon/conversation-runtime-assembly.ts +26 -0
  194. package/src/daemon/conversation-store.ts +2 -2
  195. package/src/daemon/conversation-surfaces.ts +211 -29
  196. package/src/daemon/conversation-tool-setup.ts +66 -19
  197. package/src/daemon/conversation.ts +18 -23
  198. package/src/daemon/date-context.ts +71 -22
  199. package/src/daemon/disk-pressure-background-gate.ts +73 -0
  200. package/src/daemon/disk-pressure-guard.ts +343 -0
  201. package/src/daemon/disk-pressure-policy.ts +163 -0
  202. package/src/daemon/handlers/shared.ts +26 -1
  203. package/src/daemon/handlers/skills.ts +3 -4
  204. package/src/daemon/host-app-control-proxy.ts +137 -41
  205. package/src/daemon/host-bash-proxy.ts +47 -22
  206. package/src/daemon/host-browser-proxy.ts +1 -1
  207. package/src/daemon/host-cu-proxy.ts +50 -4
  208. package/src/daemon/host-file-proxy.ts +44 -8
  209. package/src/daemon/host-transfer-proxy.ts +97 -6
  210. package/src/daemon/lifecycle.ts +167 -101
  211. package/src/daemon/meet-host-supervisor.ts +4 -4
  212. package/src/daemon/meet-manifest-loader.ts +0 -1
  213. package/src/daemon/memory-v2-startup.ts +66 -15
  214. package/src/daemon/message-protocol.ts +3 -0
  215. package/src/daemon/message-types/conversations.ts +4 -0
  216. package/src/daemon/message-types/disk-pressure.ts +9 -0
  217. package/src/daemon/message-types/messages.ts +22 -1
  218. package/src/daemon/profiler-run-store.ts +5 -5
  219. package/src/daemon/tool-setup-types.ts +2 -2
  220. package/src/documents/document-store.ts +119 -0
  221. package/src/filing/filing-service.ts +29 -5
  222. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
  223. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
  224. package/src/heartbeat/heartbeat-run-store.ts +13 -0
  225. package/src/heartbeat/heartbeat-service.ts +205 -31
  226. package/src/home/feed-scheduler.ts +18 -0
  227. package/src/inbound/platform-callback-registration.ts +8 -15
  228. package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
  229. package/src/ipc/assistant-server.ts +149 -38
  230. package/src/ipc/gateway-client.ts +37 -3
  231. package/src/ipc/skill-server.ts +99 -42
  232. package/src/live-voice/live-voice-archive.ts +4 -4
  233. package/src/live-voice/protocol.ts +5 -7
  234. package/src/media/image-service.ts +1 -7
  235. package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
  236. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +34 -51
  237. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
  238. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
  239. package/src/memory/admin.ts +5 -9
  240. package/src/memory/context-search/agent-runner.ts +19 -2
  241. package/src/memory/context-search/sources/conversations.ts +2 -11
  242. package/src/memory/context-search/sources/memory-v2.ts +1 -16
  243. package/src/memory/context-search/sources/memory.ts +2 -3
  244. package/src/memory/context-search/sources/pkb.ts +2 -3
  245. package/src/memory/context-search/types.ts +0 -1
  246. package/src/memory/conversation-crud.ts +4 -12
  247. package/src/memory/db-init.ts +2 -0
  248. package/src/memory/embedding-runtime-manager.ts +119 -5
  249. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +136 -82
  250. package/src/memory/graph/__tests__/handle-remember-v2.test.ts +11 -26
  251. package/src/memory/graph/conversation-graph-memory.ts +72 -61
  252. package/src/memory/graph/extraction.ts +1 -3
  253. package/src/memory/graph/graph-search.test.ts +11 -67
  254. package/src/memory/graph/graph-search.ts +4 -24
  255. package/src/memory/graph/retriever.test.ts +12 -1
  256. package/src/memory/graph/retriever.ts +10 -15
  257. package/src/memory/graph/tool-handlers.ts +3 -4
  258. package/src/memory/graph/tools.ts +4 -4
  259. package/src/memory/indexer.ts +53 -45
  260. package/src/memory/job-handlers/backfill.ts +2 -11
  261. package/src/memory/job-handlers/cleanup.ts +43 -0
  262. package/src/memory/job-handlers/embedding.ts +6 -8
  263. package/src/memory/job-handlers/summarization.ts +2 -7
  264. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +116 -0
  265. package/src/memory/jobs/embed-concept-page.ts +223 -87
  266. package/src/memory/jobs-store.ts +48 -0
  267. package/src/memory/jobs-worker.ts +85 -43
  268. package/src/memory/memory-v2-activation-log-store.ts +32 -14
  269. package/src/memory/memory-v2-concept-frequency.ts +169 -0
  270. package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
  271. package/src/memory/migrations/index.ts +1 -0
  272. package/src/memory/pkb/pkb-search.test.ts +7 -0
  273. package/src/memory/pkb/pkb-search.ts +4 -5
  274. package/src/memory/qdrant-client.ts +3 -13
  275. package/src/memory/rerank-local.ts +374 -0
  276. package/src/memory/search/semantic.ts +10 -72
  277. package/src/memory/trace-event-store.ts +1 -17
  278. package/src/memory/v2/__tests__/activation.test.ts +346 -255
  279. package/src/memory/v2/__tests__/consolidation-job.test.ts +61 -40
  280. package/src/memory/v2/__tests__/injection.test.ts +297 -190
  281. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
  282. package/src/memory/v2/__tests__/qdrant.test.ts +326 -9
  283. package/src/memory/v2/__tests__/reranker.test.ts +338 -0
  284. package/src/memory/v2/__tests__/sim.test.ts +113 -196
  285. package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
  286. package/src/memory/v2/__tests__/static-context.test.ts +77 -14
  287. package/src/memory/v2/__tests__/sweep-job.test.ts +19 -33
  288. package/src/memory/v2/activation.ts +149 -156
  289. package/src/memory/v2/consolidation-job.ts +69 -20
  290. package/src/memory/v2/injection.ts +75 -68
  291. package/src/memory/v2/page-store.ts +39 -0
  292. package/src/memory/v2/prompts/consolidation.ts +41 -1
  293. package/src/memory/v2/qdrant.ts +306 -46
  294. package/src/memory/v2/reranker.ts +177 -0
  295. package/src/memory/v2/sim.ts +77 -110
  296. package/src/memory/v2/skill-content.ts +4 -3
  297. package/src/memory/v2/skill-store.ts +82 -59
  298. package/src/memory/v2/static-context.ts +26 -8
  299. package/src/memory/v2/sweep-job.ts +5 -6
  300. package/src/memory/v2/types.ts +17 -10
  301. package/src/notifications/copy-composer.ts +47 -0
  302. package/src/notifications/decision-engine.ts +46 -0
  303. package/src/notifications/signal.ts +4 -0
  304. package/src/oauth/AGENTS.md +3 -1
  305. package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
  306. package/src/oauth/connect-orchestrator.ts +2 -0
  307. package/src/oauth/connection-resolver.test.ts +66 -1
  308. package/src/oauth/connection-resolver.ts +55 -1
  309. package/src/oauth/oauth-connect-state.ts +77 -0
  310. package/src/oauth/seed-providers.ts +58 -1
  311. package/src/permissions/gateway-threshold-reader.ts +116 -8
  312. package/src/permissions/prompter.ts +86 -96
  313. package/src/permissions/secret-prompter.ts +31 -31
  314. package/src/plugins/defaults/injectors.ts +36 -4
  315. package/src/plugins/defaults/memory-retrieval.ts +5 -6
  316. package/src/plugins/types.ts +7 -0
  317. package/src/proactive-artifact/aux-message-injector.ts +74 -0
  318. package/src/proactive-artifact/decision.test.ts +226 -0
  319. package/src/proactive-artifact/decision.ts +165 -0
  320. package/src/proactive-artifact/index.ts +7 -0
  321. package/src/proactive-artifact/job.test.ts +914 -0
  322. package/src/proactive-artifact/job.ts +366 -0
  323. package/src/proactive-artifact/message-copy.ts +58 -0
  324. package/src/proactive-artifact/trigger-state.test.ts +277 -0
  325. package/src/proactive-artifact/trigger-state.ts +119 -0
  326. package/src/prompts/normalize-onboarding.ts +80 -0
  327. package/src/prompts/persona-resolver.ts +101 -9
  328. package/src/prompts/system-prompt.ts +21 -7
  329. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  330. package/src/prompts/templates/SOUL.md +13 -28
  331. package/src/providers/__tests__/retry-callsite.test.ts +222 -1
  332. package/src/providers/model-intents.ts +7 -0
  333. package/src/providers/openrouter/client.ts +8 -0
  334. package/src/providers/retry.ts +50 -0
  335. package/src/providers/types.ts +1 -0
  336. package/src/runtime/__tests__/agent-wake.test.ts +456 -3
  337. package/src/runtime/agent-wake.ts +238 -100
  338. package/src/runtime/assistant-event-hub.ts +36 -6
  339. package/src/runtime/assistant-event.ts +0 -1
  340. package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
  341. package/src/runtime/auth/route-policy.ts +15 -1
  342. package/src/runtime/auth/same-actor.ts +216 -0
  343. package/src/runtime/channel-approvals.ts +3 -2
  344. package/src/runtime/channel-retry-sweep.ts +65 -1
  345. package/src/runtime/local-actor-identity.ts +52 -11
  346. package/src/runtime/pending-interactions.ts +27 -15
  347. package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
  348. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
  349. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  350. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +147 -0
  351. package/src/runtime/routes/approval-routes.ts +7 -3
  352. package/src/runtime/routes/client-routes.ts +20 -2
  353. package/src/runtime/routes/consolidation-routes.ts +8 -9
  354. package/src/runtime/routes/contact-routes.ts +0 -25
  355. package/src/runtime/routes/conversation-query-routes.ts +44 -1
  356. package/src/runtime/routes/conversation-routes.ts +35 -26
  357. package/src/runtime/routes/debug-bash-routes.ts +165 -0
  358. package/src/runtime/routes/disk-pressure-routes.ts +121 -0
  359. package/src/runtime/routes/document-pdf-renderer.ts +6 -2
  360. package/src/runtime/routes/documents-routes.ts +2 -75
  361. package/src/runtime/routes/events-routes.ts +41 -9
  362. package/src/runtime/routes/filing-routes.ts +2 -3
  363. package/src/runtime/routes/host-bash-routes.ts +23 -3
  364. package/src/runtime/routes/host-cu-routes.ts +33 -6
  365. package/src/runtime/routes/host-file-routes.ts +32 -6
  366. package/src/runtime/routes/host-transfer-routes.ts +79 -16
  367. package/src/runtime/routes/identity-routes.ts +7 -138
  368. package/src/runtime/routes/inbound-message-handler.ts +77 -12
  369. package/src/runtime/routes/index.ts +6 -0
  370. package/src/runtime/routes/memory-item-routes.test.ts +37 -17
  371. package/src/runtime/routes/memory-item-routes.ts +5 -6
  372. package/src/runtime/routes/memory-v2-routes.ts +136 -17
  373. package/src/runtime/routes/oauth-connect-routes.ts +153 -0
  374. package/src/runtime/verification-outbound-actions.ts +4 -4
  375. package/src/schedule/run-script.ts +37 -5
  376. package/src/schedule/scheduler.ts +20 -1
  377. package/src/security/encrypted-store.ts +2 -0
  378. package/src/security/secure-keys.ts +55 -0
  379. package/src/skills/include-graph.ts +35 -13
  380. package/src/skills/remote-skill-policy.ts +4 -10
  381. package/src/subagent/index.ts +1 -7
  382. package/src/subagent/manager.ts +1 -15
  383. package/src/tasks/task-runner.ts +0 -1
  384. package/src/tasks/task-store.ts +0 -3
  385. package/src/tools/background-tool-registry.ts +17 -3
  386. package/src/tools/document/document-tool.ts +20 -0
  387. package/src/tools/executor.ts +18 -2
  388. package/src/tools/host-filesystem/edit.test.ts +151 -0
  389. package/src/tools/host-filesystem/edit.ts +43 -1
  390. package/src/tools/host-filesystem/read.test.ts +129 -0
  391. package/src/tools/host-filesystem/read.ts +43 -1
  392. package/src/tools/host-filesystem/transfer.test.ts +127 -2
  393. package/src/tools/host-filesystem/transfer.ts +56 -11
  394. package/src/tools/host-filesystem/write.test.ts +134 -0
  395. package/src/tools/host-filesystem/write.ts +43 -1
  396. package/src/tools/host-terminal/host-shell.ts +13 -6
  397. package/src/tools/mcp/mcp-tool-factory.ts +2 -1
  398. package/src/tools/memory/register.test.ts +14 -9
  399. package/src/tools/memory/register.ts +1 -2
  400. package/src/tools/permission-checker.ts +15 -0
  401. package/src/tools/provider-tool-name.ts +28 -0
  402. package/src/tools/registry.ts +30 -9
  403. package/src/tools/skills/load.ts +24 -20
  404. package/src/tools/terminal/shell.ts +9 -1
  405. package/src/tools/tool-approval-handler.ts +31 -6
  406. package/src/tools/tool-name-aliases.ts +19 -0
  407. package/src/tools/types.ts +43 -3
  408. package/src/tts/provider-catalog.ts +3 -5
  409. package/src/util/disk-usage.ts +138 -0
  410. package/src/util/platform.ts +21 -11
  411. package/src/util/process-liveness.ts +26 -0
  412. package/src/workspace/heartbeat-service.ts +19 -0
  413. package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
  414. package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
  415. package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +14 -0
  416. package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
  417. package/src/workspace/migrations/069-seed-onboarding-threads.ts +28 -0
  418. package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +31 -0
  419. package/src/workspace/migrations/071-remove-safe-storage-release-note.ts +111 -0
  420. package/src/workspace/migrations/registry.ts +14 -0
  421. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
  422. package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
  423. package/src/memory/v2/skill-qdrant.ts +0 -404
  424. package/src/signals/bash.ts +0 -198
@@ -1,8 +1,6 @@
1
1
  /**
2
2
  * Tests for `readMemoryV2StaticContent` — the loader that powers the
3
- * `memory-v2-static` user-message auto-injection. Mirrors the coverage that
4
- * lived in the deprecated `system-prompt-memory-v2.test.ts`:
5
- * - Returns null when the v2 flag is off.
3
+ * `memory-v2-static` user-message auto-injection.
6
4
  * - Returns null when `config.memory.v2.enabled` is off.
7
5
  * - Reads the four files in canonical order and joins them under headings.
8
6
  * - Skips empty / missing files.
@@ -47,9 +45,8 @@ mock.module("../../../config/loader.js", () => ({
47
45
  setNestedValue: () => {},
48
46
  }));
49
47
 
50
- const { _setOverridesForTesting } =
51
- await import("../../../config/assistant-feature-flags.js");
52
- const { readMemoryV2StaticContent } = await import("../static-context.js");
48
+ const { readMemoryV2StaticContent, shouldLoadMemoryV2Static } =
49
+ await import("../static-context.js");
53
50
 
54
51
  const MEMORY_FILES = [
55
52
  "essentials.md",
@@ -74,18 +71,10 @@ describe("readMemoryV2StaticContent", () => {
74
71
  beforeEach(() => {
75
72
  mkdirSync(TEST_DIR, { recursive: true });
76
73
  configMemoryV2Enabled = true;
77
- _setOverridesForTesting({ "memory-v2-enabled": true });
78
74
  });
79
75
 
80
76
  afterEach(() => {
81
77
  cleanupMemoryDir();
82
- _setOverridesForTesting({});
83
- });
84
-
85
- test("returns null when the feature flag is off", () => {
86
- _setOverridesForTesting({ "memory-v2-enabled": false });
87
- for (const file of MEMORY_FILES) writeMemoryFile(file, `Content ${file}`);
88
- expect(readMemoryV2StaticContent()).toBeNull();
89
78
  });
90
79
 
91
80
  test("returns null when config.memory.v2.enabled is off", () => {
@@ -150,3 +139,77 @@ describe("readMemoryV2StaticContent", () => {
150
139
  expect(readMemoryV2StaticContent()).toBeNull();
151
140
  });
152
141
  });
142
+
143
+ describe("shouldLoadMemoryV2Static", () => {
144
+ test("blocks all turns until the cadence gate fires", () => {
145
+ expect(
146
+ shouldLoadMemoryV2Static({
147
+ shouldInjectNowAndPkb: false,
148
+ sourceChannel: "vellum",
149
+ isTrustedActor: true,
150
+ }),
151
+ ).toBe(false);
152
+ });
153
+
154
+ test("allows guardian-trusted local conversations", () => {
155
+ expect(
156
+ shouldLoadMemoryV2Static({
157
+ shouldInjectNowAndPkb: true,
158
+ sourceChannel: "vellum",
159
+ isTrustedActor: true,
160
+ }),
161
+ ).toBe(true);
162
+ });
163
+
164
+ test("allows local-channel conversations even when trust class is unknown (analyze runs, dev)", () => {
165
+ expect(
166
+ shouldLoadMemoryV2Static({
167
+ shouldInjectNowAndPkb: true,
168
+ sourceChannel: "vellum",
169
+ isTrustedActor: false,
170
+ }),
171
+ ).toBe(true);
172
+ });
173
+
174
+ test("allows turns with no trust context (work-item task runs, internal background)", () => {
175
+ expect(
176
+ shouldLoadMemoryV2Static({
177
+ shouldInjectNowAndPkb: true,
178
+ sourceChannel: undefined,
179
+ isTrustedActor: false,
180
+ }),
181
+ ).toBe(true);
182
+ });
183
+
184
+ const REMOTE_CHANNELS = [
185
+ "phone",
186
+ "slack",
187
+ "telegram",
188
+ "whatsapp",
189
+ "email",
190
+ ] as const;
191
+
192
+ test("allows guardian-trusted remote channels (user's own phone/Slack)", () => {
193
+ for (const channel of REMOTE_CHANNELS) {
194
+ expect(
195
+ shouldLoadMemoryV2Static({
196
+ shouldInjectNowAndPkb: true,
197
+ sourceChannel: channel,
198
+ isTrustedActor: true,
199
+ }),
200
+ ).toBe(true);
201
+ }
202
+ });
203
+
204
+ test("blocks non-guardian remote-channel actors (the leak this gate exists to prevent)", () => {
205
+ for (const channel of REMOTE_CHANNELS) {
206
+ expect(
207
+ shouldLoadMemoryV2Static({
208
+ shouldInjectNowAndPkb: true,
209
+ sourceChannel: channel,
210
+ isTrustedActor: false,
211
+ }),
212
+ ).toBe(false);
213
+ }
214
+ });
215
+ });
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * Tests for `assistant/src/memory/v2/sweep-job.ts`.
3
3
  *
4
- * Coverage matrix (from PR 18 acceptance criteria):
5
- * - Flag off → no provider/DB calls, returns 0 (early bail).
6
- * - Flag on + no recent messages → no provider call, returns 0.
7
- * - Flag on + recent messages → provider invoked with rendered prompt;
4
+ * Coverage matrix:
5
+ * - v2 disabled in config → no provider/DB calls, returns 0 (early bail).
6
+ * - sweep_enabled off → no provider call, returns 0.
7
+ * - v2 on + no recent messages → no provider call, returns 0.
8
+ * - v2 on + recent messages → provider invoked with rendered prompt;
8
9
  * each entry is appended to `memory/buffer.md` AND today's archive.
9
10
  * - Tool-call response shape mismatch → returns 0 without writes.
10
11
  * - Empty entries are skipped (the model can't pad the buffer).
@@ -24,7 +25,6 @@ import { tmpdir } from "node:os";
24
25
  import { join } from "node:path";
25
26
  import {
26
27
  afterAll,
27
- afterEach,
28
28
  beforeAll,
29
29
  beforeEach,
30
30
  describe,
@@ -85,23 +85,20 @@ afterAll(() => {
85
85
  const { resetDb, getDb } = await import("../../db-connection.js");
86
86
  const { initializeDb } = await import("../../db-init.js");
87
87
  const { messages, conversations } = await import("../../schema.js");
88
- const { _setOverridesForTesting } =
89
- await import("../../../config/assistant-feature-flags.js");
90
88
  const { memoryV2SweepJob } = await import("../sweep-job.js");
91
89
 
92
- // `isAssistantFeatureFlagEnabled` ignores the `config` argument it receives
93
- // (resolution is purely from the overrides + registry caches), so we hand
94
- // the handler a minimal stand-in instead of materializing the full default
90
+ // The handler reads `config.memory.v2.enabled` and `sweep_enabled`, so we
91
+ // hand it a minimal stand-in instead of materializing the full default
95
92
  // config — which would otherwise pull in heavy schemas this test doesn't
96
- // exercise. The handler reads `config.memory.v2.sweep_enabled`, so the
97
- // `flag on` cases need the field set; the `flag off` case bails before
98
- // the check and uses the bare empty stand-in.
93
+ // exercise.
99
94
  const CONFIG = {
100
- memory: { v2: { sweep_enabled: true } },
95
+ memory: { v2: { enabled: true, sweep_enabled: true } },
96
+ } as Parameters<typeof memoryV2SweepJob>[1];
97
+ const CONFIG_V2_OFF = {
98
+ memory: { v2: { enabled: false, sweep_enabled: true } },
101
99
  } as Parameters<typeof memoryV2SweepJob>[1];
102
- const CONFIG_FLAG_OFF = {} as Parameters<typeof memoryV2SweepJob>[1];
103
100
  const CONFIG_SWEEP_OFF = {
104
- memory: { v2: { sweep_enabled: false } },
101
+ memory: { v2: { enabled: true, sweep_enabled: false } },
105
102
  } as Parameters<typeof memoryV2SweepJob>[1];
106
103
 
107
104
  function makeJob(): Parameters<typeof memoryV2SweepJob>[0] {
@@ -205,18 +202,13 @@ beforeEach(() => {
205
202
  providerStub = null;
206
203
  });
207
204
 
208
- afterEach(() => {
209
- _setOverridesForTesting({});
210
- });
211
-
212
205
  // ---------------------------------------------------------------------------
213
206
 
214
- describe("memoryV2SweepJob — flag off", () => {
215
- test("returns 0 without invoking the provider when flag is off", async () => {
216
- _setOverridesForTesting({ "memory-v2-enabled": false });
207
+ describe("memoryV2SweepJob — v2 disabled", () => {
208
+ test("returns 0 without invoking the provider when memory.v2.enabled is false", async () => {
217
209
  providerStub = makeEntriesProvider(["should-not-be-written"]);
218
210
 
219
- const written = await memoryV2SweepJob(makeJob(), CONFIG_FLAG_OFF);
211
+ const written = await memoryV2SweepJob(makeJob(), CONFIG_V2_OFF);
220
212
 
221
213
  expect(written).toBe(0);
222
214
  expect(providerCalls).toHaveLength(0);
@@ -224,9 +216,8 @@ describe("memoryV2SweepJob — flag off", () => {
224
216
  });
225
217
  });
226
218
 
227
- describe("memoryV2SweepJob — flag on, sweep_enabled off", () => {
219
+ describe("memoryV2SweepJob — sweep_enabled off", () => {
228
220
  test("returns 0 without invoking the provider when sweep_enabled is false", async () => {
229
- _setOverridesForTesting({ "memory-v2-enabled": true });
230
221
  // No message seeding required — the sweep_enabled bail short-circuits
231
222
  // before any DB or workspace reads.
232
223
  providerStub = makeEntriesProvider(["should-not-be-written"]);
@@ -239,11 +230,7 @@ describe("memoryV2SweepJob — flag on, sweep_enabled off", () => {
239
230
  });
240
231
  });
241
232
 
242
- describe("memoryV2SweepJob — flag on, no recent messages", () => {
243
- beforeEach(() => {
244
- _setOverridesForTesting({ "memory-v2-enabled": true });
245
- });
246
-
233
+ describe("memoryV2SweepJob — no recent messages", () => {
247
234
  test("returns 0 when no messages exist in the recent window", async () => {
248
235
  providerStub = makeEntriesProvider(["should-not-be-written"]);
249
236
 
@@ -273,9 +260,8 @@ describe("memoryV2SweepJob — flag on, no recent messages", () => {
273
260
  // DB intact long enough for the SQL inserts here to clash.
274
261
  let convCounter = 0;
275
262
 
276
- describe("memoryV2SweepJob — flag on, recent messages", () => {
263
+ describe("memoryV2SweepJob — recent messages", () => {
277
264
  beforeEach(() => {
278
- _setOverridesForTesting({ "memory-v2-enabled": true });
279
265
  seedMessages(`conv-${++convCounter}`, [
280
266
  {
281
267
  role: "user",
@@ -2,12 +2,15 @@
2
2
  // Memory v2 — Per-turn activation update
3
3
  // ---------------------------------------------------------------------------
4
4
  //
5
- // Implements the activation formula from §4 of the design doc:
5
+ // Implements the activation formula from §4 of the design doc plus an
6
+ // additive cross-encoder rerank boost on the unified top-K-by-A_o pool:
6
7
  //
7
8
  // A_o(n, t+1) = d · A(n, t)
8
9
  // + c_user · sim(User_{t+1}, n)
9
10
  // + c_assistant · sim(Assistant_t, n)
10
11
  // + c_now · sim(NOW.md, n)
12
+ // + c_user · α · r_norm(User_{t+1}, n) [n ∈ topK]
13
+ // + c_assistant · α · r_norm(Assistant_t, n) [n ∈ topK]
11
14
  //
12
15
  // A(n, t+1) = [ A_o(n)
13
16
  // + k · Σ_{m∈in1(n)} A_o(m)
@@ -40,7 +43,8 @@ import {
40
43
  import { clampUnitInterval } from "../validation.js";
41
44
  import type { EdgeIndex } from "./edge-index.js";
42
45
  import { hybridQueryConceptPages } from "./qdrant.js";
43
- import { simBatch, simSkillBatch } from "./sim.js";
46
+ import { rerankCandidates } from "./reranker.js";
47
+ import { simBatch } from "./sim.js";
44
48
  import type { ActivationState, EverInjectedEntry } from "./types.js";
45
49
 
46
50
  /**
@@ -77,6 +81,7 @@ interface SelectCandidatesParams {
77
81
  /** NOW context string (essentials/threads/recent or NOW.md). */
78
82
  nowText: string;
79
83
  config: AssistantConfig;
84
+ signal?: AbortSignal;
80
85
  }
81
86
 
82
87
  interface SelectCandidatesResult {
@@ -104,7 +109,8 @@ interface SelectCandidatesResult {
104
109
  export async function selectCandidates(
105
110
  params: SelectCandidatesParams,
106
111
  ): Promise<SelectCandidatesResult> {
107
- const { priorState, userText, assistantText, nowText, config } = params;
112
+ const { priorState, userText, assistantText, nowText, config, signal } =
113
+ params;
108
114
 
109
115
  const fromPrior = new Set<string>();
110
116
  const fromAnn = new Set<string>();
@@ -125,12 +131,16 @@ export async function selectCandidates(
125
131
  .join("\n");
126
132
 
127
133
  if (annQueryText.length > 0) {
128
- const denseResult = await embedWithBackend(config, [annQueryText]);
134
+ throwIfAborted(signal);
135
+ const denseResult = await embedWithBackend(config, [annQueryText], {
136
+ signal,
137
+ });
129
138
  const dense = await applyCorrectionIfCalibrated(
130
139
  denseResult.vectors[0],
131
140
  denseResult.provider,
132
141
  denseResult.model,
133
142
  );
143
+ throwIfAborted(signal);
134
144
  const sparse = generateSparseEmbedding(annQueryText);
135
145
  const limit =
136
146
  config.memory.v2.ann_candidate_limit ?? UNLIMITED_ANN_CANDIDATE_LIMIT;
@@ -154,6 +164,7 @@ interface ComputeOwnActivationParams {
154
164
  assistantText: string;
155
165
  nowText: string;
156
166
  config: AssistantConfig;
167
+ signal?: AbortSignal;
157
168
  }
158
169
 
159
170
  /**
@@ -164,12 +175,18 @@ interface ComputeOwnActivationParams {
164
175
  interface OwnActivationBreakdown {
165
176
  /** `d * prev(slug)` — the decayed prior-turn activation contribution. */
166
177
  priorContribution: number;
167
- /** Raw `sim(user, slug)` similarity, before `c_user` weighting. */
178
+ /** Raw fused `sim(user, slug)`, before `c_user` weighting. */
168
179
  simUser: number;
169
- /** Raw `sim(assistant, slug)` similarity, before `c_assistant` weighting. */
180
+ /** Raw fused `sim(assistant, slug)`, before `c_assistant` weighting. */
170
181
  simAssistant: number;
171
- /** Raw `sim(now, slug)` similarity, before `c_now` weighting. */
182
+ /** Raw fused `sim(now, slug)`, before `c_now` weighting. */
172
183
  simNow: number;
184
+ /** Rerank delta `α · r_norm_u`; 0 outside the top-K pool. Applied to `A_o` weighted by `c_user`. */
185
+ simUserRerankBoost: number;
186
+ /** Rerank delta `α · r_norm_a`; 0 outside the top-K pool. Applied to `A_o` weighted by `c_assistant`. NOW skips rerank. */
187
+ simAssistantRerankBoost: number;
188
+ /** True when this slug was in the unified top-K rerank pool. Lets the inspector distinguish "cross-encoder normalised to 0" from "rerank skipped this slug." */
189
+ inRerankPool: boolean;
173
190
  }
174
191
 
175
192
  interface ComputeOwnActivationResult {
@@ -181,21 +198,34 @@ interface ComputeOwnActivationResult {
181
198
 
182
199
  /**
183
200
  * Apply the own-activation formula
184
- * A_o(n) = d · prev(n) + c_user · sim_u + c_assistant · sim_a + c_now · sim_n
185
- * over the candidate set. Returns a sparse map keyed by slug; slugs whose
186
- * computed value rounds to 0 are still included so callers can see the
187
- * candidate set explicitly. Also returns a per-slug breakdown of the raw
188
- * inputs (decayed prior + raw sims) so callers can render contribution
189
- * diagnostics without re-running the math.
201
+ * A_o(n) = d · prev(n)
202
+ * + c_user · sim_u + c_assistant · sim_a + c_now · sim_n
203
+ * + c_user · α · r_norm_u + c_assistant · α · r_norm_a
204
+ * over the candidate set, where the rerank terms only fire for slugs that
205
+ * land in the unified top-K-by-pre-rerank-A_o window. Returns a sparse map
206
+ * keyed by slug; slugs whose computed value rounds to 0 are still included
207
+ * so callers can see the candidate set explicitly. Also returns a per-slug
208
+ * breakdown of the raw inputs (decayed prior + raw sims + rerank deltas) so
209
+ * callers can render contribution diagnostics without re-running the math.
190
210
  *
191
211
  * The three `simBatch` calls run concurrently — they hit independent named
192
- * vectors and embed independent query texts.
212
+ * vectors and embed independent query texts. Cross-encoder rerank then runs
213
+ * once on the unified top-K (selected by pre-rerank A_o, not per-channel
214
+ * fused sim) so an entry strong in both channels can't double-boost itself
215
+ * past entries that only land in one channel.
193
216
  */
194
217
  export async function computeOwnActivation(
195
218
  params: ComputeOwnActivationParams,
196
219
  ): Promise<ComputeOwnActivationResult> {
197
- const { candidates, priorState, userText, assistantText, nowText, config } =
198
- params;
220
+ const {
221
+ candidates,
222
+ priorState,
223
+ userText,
224
+ assistantText,
225
+ nowText,
226
+ config,
227
+ signal,
228
+ } = params;
199
229
 
200
230
  const activation = new Map<string, number>();
201
231
  const breakdown = new Map<string, OwnActivationBreakdown>();
@@ -204,30 +234,122 @@ export async function computeOwnActivation(
204
234
  const { d, c_user, c_assistant, c_now } = config.memory.v2;
205
235
  const slugList = [...candidates];
206
236
 
237
+ // NOW context is structured (timestamps, current focus) — outside the
238
+ // cross-encoder's training distribution, so it never participates in rerank.
207
239
  const [simUser, simAssistant, simNow] = await Promise.all([
208
- simBatch(userText, slugList, config),
209
- simBatch(assistantText, slugList, config),
210
- simBatch(nowText, slugList, config),
240
+ simBatch(userText, slugList, config, { signal }),
241
+ simBatch(assistantText, slugList, config, { signal }),
242
+ simBatch(nowText, slugList, config, { signal }),
211
243
  ]);
212
244
 
213
- for (const slug of slugList) {
245
+ interface SlugInputs {
246
+ slug: string;
247
+ priorContribution: number;
248
+ simU: number;
249
+ simA: number;
250
+ simN: number;
251
+ /** Pre-rerank A_o; ranking signal for the unified rerank pool. */
252
+ preRerank: number;
253
+ }
254
+ const inputs: SlugInputs[] = slugList.map((slug) => {
214
255
  const prev = priorState?.state[slug] ?? 0;
215
256
  const simU = simUser.get(slug) ?? 0;
216
257
  const simA = simAssistant.get(slug) ?? 0;
217
258
  const simN = simNow.get(slug) ?? 0;
218
- const value = d * prev + c_user * simU + c_assistant * simA + c_now * simN;
219
- activation.set(slug, clampUnitInterval(value));
220
- breakdown.set(slug, {
221
- priorContribution: d * prev,
222
- simUser: simU,
223
- simAssistant: simA,
224
- simNow: simN,
259
+ const priorContribution = d * prev;
260
+ return {
261
+ slug,
262
+ priorContribution,
263
+ simU,
264
+ simA,
265
+ simN,
266
+ preRerank:
267
+ priorContribution + c_user * simU + c_assistant * simA + c_now * simN,
268
+ };
269
+ });
270
+
271
+ // Unified top-K by pre-rerank A_o. Both channels rerank against the **same**
272
+ // slug set, so a slug strong on user can't crowd out one strong on assistant
273
+ // by virtue of appearing in both per-channel top-Ks. Both channel queries
274
+ // ride in a single `rerankCandidates` call so the worker tokenizes and
275
+ // forward-passes them together — half the per-call overhead of two
276
+ // serialised round-trips.
277
+ let userRerankBoost: ReadonlyMap<string, number> = new Map();
278
+ let assistantRerankBoost: ReadonlyMap<string, number> = new Map();
279
+ let inPoolSet: ReadonlySet<string> = new Set();
280
+ const rerankCfg = config.memory.v2.rerank;
281
+ if (rerankCfg?.enabled) {
282
+ throwIfAborted(signal);
283
+ const topSlugs = inputs
284
+ .slice()
285
+ .sort((a, b) => b.preRerank - a.preRerank)
286
+ .slice(0, rerankCfg.top_k)
287
+ .map((e) => e.slug);
288
+ if (topSlugs.length > 0) {
289
+ inPoolSet = new Set(topSlugs);
290
+ const [userScores, assistantScores] = await rerankCandidates(
291
+ [userText, assistantText],
292
+ topSlugs,
293
+ config,
294
+ );
295
+ throwIfAborted(signal);
296
+ userRerankBoost = normalizeRerankScores(userScores, rerankCfg.alpha);
297
+ assistantRerankBoost = normalizeRerankScores(
298
+ assistantScores,
299
+ rerankCfg.alpha,
300
+ );
301
+ }
302
+ }
303
+
304
+ for (const e of inputs) {
305
+ const boostU = userRerankBoost.get(e.slug) ?? 0;
306
+ const boostA = assistantRerankBoost.get(e.slug) ?? 0;
307
+ activation.set(
308
+ e.slug,
309
+ clampUnitInterval(e.preRerank + c_user * boostU + c_assistant * boostA),
310
+ );
311
+ breakdown.set(e.slug, {
312
+ priorContribution: e.priorContribution,
313
+ simUser: e.simU,
314
+ simAssistant: e.simA,
315
+ simNow: e.simN,
316
+ simUserRerankBoost: boostU,
317
+ simAssistantRerankBoost: boostA,
318
+ inRerankPool: inPoolSet.has(e.slug),
225
319
  });
226
320
  }
227
321
 
228
322
  return { activation, breakdown };
229
323
  }
230
324
 
325
+ /**
326
+ * Per-batch normalisation: divide raw cross-encoder scores by the channel's
327
+ * own max and return `alpha · r_norm` per slug. Empty input or all-zero
328
+ * scores yield an empty Map so the channel contributes 0 boost.
329
+ */
330
+ function normalizeRerankScores(
331
+ rawScores: ReadonlyMap<string, number>,
332
+ alpha: number,
333
+ ): Map<string, number> {
334
+ const out = new Map<string, number>();
335
+ if (rawScores.size === 0) return out;
336
+ let maxScore = 0;
337
+ for (const v of rawScores.values()) {
338
+ if (v > maxScore) maxScore = v;
339
+ }
340
+ if (maxScore === 0) return out;
341
+ for (const [slug, raw] of rawScores) {
342
+ out.set(slug, alpha * (raw / maxScore));
343
+ }
344
+ return out;
345
+ }
346
+
347
+ function throwIfAborted(signal: AbortSignal | undefined): void {
348
+ if (signal?.aborted) {
349
+ throw new DOMException("Aborted", "AbortError");
350
+ }
351
+ }
352
+
231
353
  // ---------------------------------------------------------------------------
232
354
  // Spreading activation
233
355
  // ---------------------------------------------------------------------------
@@ -411,132 +533,3 @@ export function selectInjections(
411
533
 
412
534
  return { topNow, toInject };
413
535
  }
414
-
415
- // ---------------------------------------------------------------------------
416
- // Skill autoinjection — candidate / activation / injection selection
417
- // ---------------------------------------------------------------------------
418
- //
419
- // Skills are stateless: there is no decay carry-over (`d · prev`), no
420
- // spreading activation, and no `everInjected` dedup. The agent re-presents
421
- // the top-K active skills every turn so it can drop or pick them up freely.
422
- // The pipeline therefore reduces to:
423
- // 1. ANN candidate selection against the dedicated skills collection.
424
- // 2. Pure similarity-only activation: A_skill = c_user·sim_u +
425
- // c_assistant·sim_a + c_now·sim_n, clamped to [0, 1].
426
- // 3. Top-K by activation, lexicographic tie-break, no injection delta.
427
- //
428
- // The activation coefficients are reused from `config.memory.v2.{c_user,
429
- // c_assistant, c_now}` — the design doc (§9) deliberately shares them with
430
- // concept-page activation rather than introducing parallel knobs.
431
-
432
- interface ComputeSkillActivationParams {
433
- candidates: ReadonlySet<string>;
434
- userText: string;
435
- assistantText: string;
436
- nowText: string;
437
- config: AssistantConfig;
438
- }
439
-
440
- /**
441
- * Per-skill breakdown of the raw similarity inputs, captured before any
442
- * coefficient weighting. Skills have no decay term, so the breakdown is just
443
- * the three raw sims. Surfaced for telemetry / inspector views.
444
- */
445
- interface SkillActivationBreakdown {
446
- /** Raw `sim(user, skill)` similarity, before `c_user` weighting. */
447
- simUser: number;
448
- /** Raw `sim(assistant, skill)` similarity, before `c_assistant` weighting. */
449
- simAssistant: number;
450
- /** Raw `sim(now, skill)` similarity, before `c_now` weighting. */
451
- simNow: number;
452
- }
453
-
454
- interface ComputeSkillActivationResult {
455
- /** Final clamped skill-activation value per id. */
456
- activation: Map<string, number>;
457
- /** Per-skill breakdown of the raw sim inputs that fed into `activation`. */
458
- breakdown: Map<string, SkillActivationBreakdown>;
459
- }
460
-
461
- /**
462
- * Apply the skill-side activation formula (no decay carry-over, no spread):
463
- * A_skill(s) = clamp01(c_user · sim_u + c_assistant · sim_a + c_now · sim_n)
464
- *
465
- * Reuses the activation coefficients from `config.memory.v2`. The three
466
- * `simSkillBatch` calls run concurrently — they hit independent named
467
- * vectors and embed independent query texts. Returns a per-skill breakdown
468
- * of the raw sims alongside the activation map so callers can render
469
- * contribution diagnostics without re-running the math.
470
- *
471
- * Empty candidates short-circuits to an empty map without touching the
472
- * embedding backend or Qdrant.
473
- */
474
- export async function computeSkillActivation(
475
- params: ComputeSkillActivationParams,
476
- ): Promise<ComputeSkillActivationResult> {
477
- const { candidates, userText, assistantText, nowText, config } = params;
478
-
479
- const activation = new Map<string, number>();
480
- const breakdown = new Map<string, SkillActivationBreakdown>();
481
- if (candidates.size === 0) return { activation, breakdown };
482
-
483
- const { c_user, c_assistant, c_now } = config.memory.v2;
484
- const idList = [...candidates];
485
-
486
- const [simUser, simAssistant, simNow] = await Promise.all([
487
- simSkillBatch(userText, idList, config),
488
- simSkillBatch(assistantText, idList, config),
489
- simSkillBatch(nowText, idList, config),
490
- ]);
491
-
492
- for (const id of idList) {
493
- const simU = simUser.get(id) ?? 0;
494
- const simA = simAssistant.get(id) ?? 0;
495
- const simN = simNow.get(id) ?? 0;
496
- const value = c_user * simU + c_assistant * simA + c_now * simN;
497
- activation.set(id, clampUnitInterval(value));
498
- breakdown.set(id, { simUser: simU, simAssistant: simA, simNow: simN });
499
- }
500
-
501
- return { activation, breakdown };
502
- }
503
-
504
- interface SelectSkillInjectionsParams {
505
- /** Final skill activation map. */
506
- A: ReadonlyMap<string, number>;
507
- /** Cap on the per-turn skill slate, e.g. `config.memory.v2.skills_top_k`. */
508
- topK: number;
509
- }
510
-
511
- interface SelectSkillInjectionsResult {
512
- /**
513
- * Top-K skill ids by activation (descending), tie-broken lexicographically.
514
- * Skills are re-presented every turn — no `toInject` delta — so the caller
515
- * uses this list verbatim to render the skill slate.
516
- */
517
- topNow: string[];
518
- }
519
-
520
- /**
521
- * Pick the top-K skill ids by activation (descending; stable on ties via id
522
- * lexicographic order). Skills are stateless — there is no `everInjected`
523
- * dedup, so the same id can appear on consecutive turns.
524
- *
525
- * Returns `{ topNow: [] }` for an empty activation map or `topK <= 0`.
526
- */
527
- export function selectSkillInjections(
528
- params: SelectSkillInjectionsParams,
529
- ): SelectSkillInjectionsResult {
530
- const { A, topK } = params;
531
- if (A.size === 0 || topK <= 0) {
532
- return { topNow: [] };
533
- }
534
-
535
- const ranked = [...A.entries()].sort(([idA, valA], [idB, valB]) => {
536
- if (valB !== valA) return valB - valA; // higher activation first
537
- return idA < idB ? -1 : idA > idB ? 1 : 0; // stable tie-break
538
- });
539
-
540
- const topNow = ranked.slice(0, topK).map(([id]) => id);
541
- return { topNow };
542
- }