@vellumai/assistant 0.7.1 → 0.7.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 (535) hide show
  1. package/ARCHITECTURE.md +32 -49
  2. package/Dockerfile +1 -0
  3. package/README.md +1 -2
  4. package/__tests__/permissions/gateway-threshold-reader.test.ts +9 -3
  5. package/bun.lock +26 -26
  6. package/docs/architecture/security.md +20 -0
  7. package/docs/plugins.md +7 -9
  8. package/knip.json +1 -0
  9. package/node_modules/@vellumai/gateway-client/src/index.ts +1 -0
  10. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +39 -1
  11. package/node_modules/@vellumai/gateway-client/src/types.ts +11 -0
  12. package/node_modules/@vellumai/service-contracts/package.json +2 -0
  13. package/node_modules/@vellumai/service-contracts/src/__tests__/contracts.test.ts +4 -0
  14. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +107 -0
  15. package/node_modules/@vellumai/service-contracts/src/index.ts +5 -1
  16. package/node_modules/@vellumai/service-contracts/src/ingress.ts +24 -0
  17. package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +84 -0
  18. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +9 -0
  19. package/node_modules/@vellumai/twilio-client/bun.lock +24 -0
  20. package/node_modules/@vellumai/twilio-client/package.json +18 -0
  21. package/node_modules/@vellumai/twilio-client/src/__tests__/twilio-client.test.ts +128 -0
  22. package/node_modules/@vellumai/twilio-client/src/index.ts +179 -0
  23. package/node_modules/@vellumai/twilio-client/tsconfig.json +20 -0
  24. package/openapi.yaml +565 -12
  25. package/package.json +6 -3
  26. package/src/__tests__/app-builder-tool-scripts.test.ts +3 -3
  27. package/src/__tests__/app-bundler.test.ts +170 -1
  28. package/src/__tests__/app-control-flow.test.ts +374 -0
  29. package/src/__tests__/app-control-no-global-cgevent.test.ts +98 -0
  30. package/src/__tests__/app-control-tool-schemas.test.ts +621 -0
  31. package/src/__tests__/app-executors.test.ts +30 -43
  32. package/src/__tests__/approval-routes-http.test.ts +23 -6
  33. package/src/__tests__/assistant-event-hub-machine-name.test.ts +146 -0
  34. package/src/__tests__/assistant-event-hub-targeted.test.ts +257 -0
  35. package/src/__tests__/assistant-event-hub.test.ts +109 -2
  36. package/src/__tests__/assistant-event.test.ts +10 -0
  37. package/src/__tests__/assistant-events-sse-hardening.test.ts +7 -2
  38. package/src/__tests__/assistant-feature-flags-integration.test.ts +11 -7
  39. package/src/__tests__/background-shell-host-bash.test.ts +14 -15
  40. package/src/__tests__/bootstrap-turn-cleanup.test.ts +44 -0
  41. package/src/__tests__/btw-routes.test.ts +13 -4
  42. package/src/__tests__/call-controller.test.ts +49 -1
  43. package/src/__tests__/call-domain.test.ts +0 -2
  44. package/src/__tests__/call-routes-http.test.ts +0 -2
  45. package/src/__tests__/channel-readiness-service.test.ts +59 -1
  46. package/src/__tests__/checker.test.ts +3 -4
  47. package/src/__tests__/config-loader-backfill.test.ts +90 -155
  48. package/src/__tests__/config-loader-platform-defaults.test.ts +196 -0
  49. package/src/__tests__/config-schema-cmd.test.ts +0 -1
  50. package/src/__tests__/config-set-platform-guard.test.ts +48 -4
  51. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +2 -2
  52. package/src/__tests__/config-watcher.test.ts +2 -2
  53. package/src/__tests__/conversation-app-control-instantiation.test.ts +392 -0
  54. package/src/__tests__/conversation-app-control-lifecycle.test.ts +237 -0
  55. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  56. package/src/__tests__/conversation-lifecycle.test.ts +36 -0
  57. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +283 -0
  58. package/src/__tests__/conversation-routes-disk-view.test.ts +6 -0
  59. package/src/__tests__/conversation-routes-guardian-reply.test.ts +120 -72
  60. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  61. package/src/__tests__/conversation-slash-commands.test.ts +0 -4
  62. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +202 -0
  63. package/src/__tests__/conversation-surfaces-app-control.test.ts +317 -0
  64. package/src/__tests__/credential-execution-feature-gates.test.ts +5 -12
  65. package/src/__tests__/credential-execution-managed-contract.test.ts +3 -131
  66. package/src/__tests__/credentials-cli.test.ts +5 -12
  67. package/src/__tests__/cu-unified-flow.test.ts +185 -23
  68. package/src/__tests__/daemon-credential-client.test.ts +101 -19
  69. package/src/__tests__/db-schedule-syntax-migration.test.ts +2 -0
  70. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  71. package/src/__tests__/gateway-only-enforcement.test.ts +0 -1
  72. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -2
  73. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -2
  74. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -1
  75. package/src/__tests__/heartbeat-service.test.ts +718 -1
  76. package/src/__tests__/helpers/call-route-handler.ts +7 -1
  77. package/src/__tests__/host-app-control-proxy.test.ts +602 -0
  78. package/src/__tests__/host-app-control-routes.test.ts +263 -0
  79. package/src/__tests__/host-bash-proxy.test.ts +246 -47
  80. package/src/__tests__/host-bash-routes.test.ts +294 -0
  81. package/src/__tests__/host-browser-proxy.test.ts +24 -22
  82. package/src/__tests__/host-browser-routes.test.ts +39 -13
  83. package/src/__tests__/host-cu-proxy.test.ts +41 -52
  84. package/src/__tests__/host-cu-routes-targeted.test.ts +300 -0
  85. package/src/__tests__/host-file-edit-tool.test.ts +47 -1
  86. package/src/__tests__/host-file-proxy-targeted.test.ts +339 -0
  87. package/src/__tests__/host-file-proxy.test.ts +37 -43
  88. package/src/__tests__/host-file-read-tool.test.ts +17 -0
  89. package/src/__tests__/host-file-routes-targeted.test.ts +262 -0
  90. package/src/__tests__/host-file-write-tool.test.ts +42 -1
  91. package/src/__tests__/host-proxy-base.test.ts +312 -0
  92. package/src/__tests__/host-shell-tool.test.ts +22 -4
  93. package/src/__tests__/host-transfer-proxy-targeted.test.ts +583 -0
  94. package/src/__tests__/host-transfer-proxy.test.ts +121 -22
  95. package/src/__tests__/host-transfer-routes-targeted.test.ts +447 -0
  96. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  97. package/src/__tests__/identity-intro-cache.test.ts +29 -0
  98. package/src/__tests__/identity-routes.test.ts +103 -1
  99. package/src/__tests__/init-feature-flag-overrides.test.ts +26 -3
  100. package/src/__tests__/inline-command-runner.test.ts +0 -1
  101. package/src/__tests__/inline-skill-load-permissions.test.ts +5 -11
  102. package/src/__tests__/integration-status.test.ts +85 -5
  103. package/src/__tests__/intent-routing.test.ts +0 -1
  104. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +95 -5
  105. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +17 -0
  106. package/src/__tests__/managed-skill-lifecycle.test.ts +0 -1
  107. package/src/__tests__/mcp-auth-routes.test.ts +197 -0
  108. package/src/__tests__/mcp-cli.test.ts +338 -2
  109. package/src/__tests__/memory-jobs-worker-lanes.test.ts +188 -0
  110. package/src/__tests__/migration-import-commit-http.test.ts +108 -2
  111. package/src/__tests__/mock-gateway-ipc.ts +1 -0
  112. package/src/__tests__/oauth-cli.test.ts +0 -2
  113. package/src/__tests__/oauth2-gateway-transport.test.ts +0 -1
  114. package/src/__tests__/persistence-secret-redaction.test.ts +299 -0
  115. package/src/__tests__/platform-bash-auto-approve.test.ts +5 -9
  116. package/src/__tests__/prechat-onboarding-contract.test.ts +3 -1
  117. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  118. package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
  119. package/src/__tests__/public-ingress-urls.test.ts +97 -0
  120. package/src/__tests__/require-fresh-approval.test.ts +0 -1
  121. package/src/__tests__/retry-backoff.test.ts +87 -0
  122. package/src/__tests__/runtime-events-sse.test.ts +10 -6
  123. package/src/__tests__/sanitize-config-for-transfer.test.ts +24 -2
  124. package/src/__tests__/schedule-retry.test.ts +715 -0
  125. package/src/__tests__/script-proxy-mitm-handler.test.ts +1 -1
  126. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  127. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  128. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -1
  129. package/src/__tests__/skill-feature-flags.test.ts +43 -41
  130. package/src/__tests__/skill-load-feature-flag.test.ts +13 -14
  131. package/src/__tests__/skill-load-inline-command.test.ts +0 -51
  132. package/src/__tests__/skill-load-inline-includes.test.ts +0 -43
  133. package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
  134. package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -1
  135. package/src/__tests__/slack-channel-config.test.ts +9 -14
  136. package/src/__tests__/system-prompt-ask-mode.test.ts +0 -1
  137. package/src/__tests__/system-prompt.test.ts +0 -1
  138. package/src/__tests__/telegram-config.test.ts +0 -1
  139. package/src/__tests__/test-preload.ts +8 -0
  140. package/src/__tests__/tool-approval-handler.test.ts +3 -4
  141. package/src/__tests__/tool-audit-listener.test.ts +48 -0
  142. package/src/__tests__/tool-execute-pipeline.test.ts +0 -1
  143. package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
  144. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  145. package/src/__tests__/tool-executor.test.ts +0 -1
  146. package/src/__tests__/twilio-config.test.ts +3 -16
  147. package/src/__tests__/twilio-routes.test.ts +3 -5
  148. package/src/__tests__/twilio-validation.test.ts +93 -0
  149. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -4
  150. package/src/__tests__/verification-control-plane-policy.test.ts +2 -4
  151. package/src/__tests__/voice-ingress-preflight.test.ts +19 -0
  152. package/src/__tests__/workspace-migration-006-services-config.test.ts +3 -2
  153. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +1 -5
  154. package/src/__tests__/workspace-migration-down-functions.test.ts +8 -8
  155. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +10 -6
  156. package/src/backup/__tests__/paths.test.ts +0 -22
  157. package/src/backup/__tests__/restore.test.ts +51 -151
  158. package/src/backup/paths.ts +2 -18
  159. package/src/backup/restore.ts +107 -231
  160. package/src/bundler/app-bundler.ts +51 -3
  161. package/src/calls/relay-server.ts +4 -44
  162. package/src/calls/twilio-config.ts +2 -17
  163. package/src/calls/twilio-rest.ts +33 -105
  164. package/src/calls/twilio-routes.ts +11 -12
  165. package/src/channels/types.ts +8 -7
  166. package/src/cli/commands/__tests__/backup.test.ts +6 -277
  167. package/src/cli/commands/__tests__/gateway.test.ts +288 -0
  168. package/src/cli/commands/__tests__/memory-v2.test.ts +4 -0
  169. package/src/cli/commands/__tests__/webhooks.test.ts +0 -1
  170. package/src/cli/commands/backup.ts +6 -331
  171. package/src/cli/commands/clients.ts +36 -37
  172. package/src/cli/commands/contacts.ts +73 -0
  173. package/src/cli/commands/conversations.ts +2 -5
  174. package/src/cli/commands/credentials.ts +15 -7
  175. package/src/cli/commands/domain.ts +66 -15
  176. package/src/cli/commands/gateway.ts +183 -0
  177. package/src/cli/commands/keys.ts +9 -6
  178. package/src/cli/commands/mcp.ts +116 -156
  179. package/src/cli/commands/memory-v2.ts +296 -1
  180. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
  181. package/src/cli/commands/platform/__tests__/connect.test.ts +0 -2
  182. package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -2
  183. package/src/cli/commands/platform/__tests__/status.test.ts +13 -15
  184. package/src/cli/commands/platform/disconnect.ts +5 -4
  185. package/src/cli/commands/platform/index.ts +0 -18
  186. package/src/cli/lib/daemon-credential-client.ts +110 -28
  187. package/src/cli/program.ts +2 -0
  188. package/src/config/assistant-feature-flags.ts +67 -10
  189. package/src/config/bundled-skills/acp/SKILL.md +6 -0
  190. package/src/config/bundled-skills/acp/TOOLS.json +1 -22
  191. package/src/config/bundled-skills/app-builder/SKILL.md +14 -109
  192. package/src/config/bundled-skills/app-builder/TOOLS.json +1 -28
  193. package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -10
  194. package/src/config/bundled-skills/app-control/SKILL.md +75 -0
  195. package/src/config/bundled-skills/app-control/TOOLS.json +299 -0
  196. package/src/config/bundled-skills/app-control/tools/app-control-click.ts +12 -0
  197. package/src/config/bundled-skills/app-control/tools/app-control-combo.ts +12 -0
  198. package/src/config/bundled-skills/app-control/tools/app-control-drag.ts +12 -0
  199. package/src/config/bundled-skills/app-control/tools/app-control-observe.ts +12 -0
  200. package/src/config/bundled-skills/app-control/tools/app-control-press.ts +12 -0
  201. package/src/config/bundled-skills/app-control/tools/app-control-sequence.ts +12 -0
  202. package/src/config/bundled-skills/app-control/tools/app-control-start.ts +12 -0
  203. package/src/config/bundled-skills/app-control/tools/app-control-stop.ts +12 -0
  204. package/src/config/bundled-skills/app-control/tools/app-control-type.ts +12 -0
  205. package/src/config/bundled-skills/computer-use/SKILL.md +6 -0
  206. package/src/config/bundled-skills/computer-use/TOOLS.json +67 -43
  207. package/src/config/bundled-skills/contacts/TOOLS.json +0 -16
  208. package/src/config/bundled-skills/document/TOOLS.json +0 -8
  209. package/src/config/bundled-skills/followups/TOOLS.json +0 -12
  210. package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
  211. package/src/config/bundled-skills/image-studio/TOOLS.json +0 -4
  212. package/src/config/bundled-skills/media-processing/TOOLS.json +0 -24
  213. package/src/config/bundled-skills/messaging/TOOLS.json +0 -40
  214. package/src/config/bundled-skills/phone-calls/TOOLS.json +0 -12
  215. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +19 -4
  216. package/src/config/bundled-skills/playbooks/TOOLS.json +0 -16
  217. package/src/config/bundled-skills/schedule/TOOLS.json +14 -14
  218. package/src/config/bundled-skills/sequences/TOOLS.json +0 -36
  219. package/src/config/bundled-skills/settings/SKILL.md +4 -0
  220. package/src/config/bundled-skills/settings/TOOLS.json +0 -12
  221. package/src/config/bundled-skills/skill-management/SKILL.md +6 -0
  222. package/src/config/bundled-skills/skill-management/TOOLS.json +0 -8
  223. package/src/config/bundled-skills/subagent/SKILL.md +6 -2
  224. package/src/config/bundled-skills/subagent/TOOLS.json +0 -20
  225. package/src/config/bundled-skills/transcribe/SKILL.md +4 -0
  226. package/src/config/bundled-skills/transcribe/TOOLS.json +0 -4
  227. package/src/config/bundled-tool-registry.ts +21 -0
  228. package/src/config/env-registry.ts +0 -2
  229. package/src/config/env.ts +19 -12
  230. package/src/config/feature-flag-registry.json +21 -133
  231. package/src/config/loader.ts +73 -99
  232. package/src/config/sanitize-for-transfer.ts +2 -0
  233. package/src/config/schemas/__tests__/memory-lifecycle.test.ts +80 -0
  234. package/src/config/schemas/__tests__/memory-v2.test.ts +7 -4
  235. package/src/config/schemas/calls.ts +0 -9
  236. package/src/config/schemas/heartbeat.ts +63 -0
  237. package/src/config/schemas/ingress.ts +10 -6
  238. package/src/config/schemas/llm.ts +5 -10
  239. package/src/config/schemas/memory-lifecycle.ts +77 -24
  240. package/src/config/schemas/memory-v2.ts +48 -4
  241. package/src/config/schemas/platform.ts +6 -0
  242. package/src/config/schemas/services.ts +1 -15
  243. package/src/config/schemas/skills.ts +0 -6
  244. package/src/config/seed-inference-profiles.ts +1 -1
  245. package/src/contacts/contact-store.ts +0 -30
  246. package/src/contacts/contacts-write.ts +0 -27
  247. package/src/context/window-manager.ts +1 -2
  248. package/src/credential-execution/feature-gates.ts +10 -10
  249. package/src/credential-execution/process-manager.ts +12 -41
  250. package/src/daemon/__tests__/conversation-tool-setup.test.ts +126 -5
  251. package/src/daemon/bootstrap-turn-cleanup.ts +45 -0
  252. package/src/daemon/config-watcher.ts +4 -3
  253. package/src/daemon/conversation-agent-loop-handlers.ts +21 -3
  254. package/src/daemon/conversation-agent-loop.ts +32 -28
  255. package/src/daemon/conversation-lifecycle.ts +8 -1
  256. package/src/daemon/conversation-process.ts +16 -11
  257. package/src/daemon/conversation-runtime-assembly.ts +2 -2
  258. package/src/daemon/conversation-surfaces.ts +125 -4
  259. package/src/daemon/conversation-tool-setup.ts +16 -55
  260. package/src/daemon/conversation.ts +21 -2
  261. package/src/daemon/doordash-steps.ts +1 -1
  262. package/src/daemon/handlers/shared.ts +4 -1
  263. package/src/daemon/host-app-control-proxy.ts +293 -0
  264. package/src/daemon/host-bash-proxy.ts +84 -74
  265. package/src/daemon/host-browser-proxy.ts +67 -82
  266. package/src/daemon/host-cu-proxy.ts +81 -86
  267. package/src/daemon/host-file-proxy.ts +93 -69
  268. package/src/daemon/host-proxy-base.ts +294 -0
  269. package/src/daemon/host-proxy-preactivation.ts +82 -0
  270. package/src/daemon/host-transfer-proxy.ts +247 -129
  271. package/src/daemon/lifecycle.ts +115 -117
  272. package/src/daemon/message-protocol.ts +3 -8
  273. package/src/daemon/message-types/contacts.ts +23 -1
  274. package/src/daemon/message-types/conversations.ts +11 -8
  275. package/src/daemon/message-types/host-app-control.ts +150 -0
  276. package/src/daemon/message-types/host-bash.ts +4 -0
  277. package/src/daemon/message-types/host-cu.ts +2 -0
  278. package/src/daemon/message-types/host-file.ts +4 -0
  279. package/src/daemon/message-types/host-transfer.ts +3 -0
  280. package/src/daemon/message-types/schedules.ts +8 -3
  281. package/src/daemon/message-types/skills.ts +2 -2
  282. package/src/daemon/process-message.ts +18 -1
  283. package/src/daemon/shutdown-handlers.ts +0 -3
  284. package/src/daemon/tool-setup-types.ts +51 -0
  285. package/src/daemon/tool-side-effects.ts +1 -1
  286. package/src/events/tool-audit-listener.ts +2 -1
  287. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +15 -7
  288. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +216 -0
  289. package/src/heartbeat/heartbeat-run-store.ts +236 -0
  290. package/src/heartbeat/heartbeat-service.ts +280 -49
  291. package/src/home/__tests__/post-connect-feed.test.ts +99 -0
  292. package/src/home/__tests__/relationship-state-writer.test.ts +11 -9
  293. package/src/home/__tests__/suggested-prompts.test.ts +89 -0
  294. package/src/home/post-connect-feed.ts +68 -0
  295. package/src/home/relationship-state-writer.ts +17 -92
  296. package/src/home/suggested-prompts.ts +46 -10
  297. package/src/inbound/public-ingress-urls.ts +32 -34
  298. package/src/ipc/__tests__/route-error-envelope.test.ts +80 -0
  299. package/src/ipc/assistant-server.ts +14 -1
  300. package/src/ipc/cli-client.ts +32 -1
  301. package/src/live-voice/live-voice-metrics.ts +10 -10
  302. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +304 -0
  303. package/src/mcp/mcp-auth-orchestrator.ts +213 -0
  304. package/src/mcp/mcp-auth-state.ts +133 -0
  305. package/src/mcp/mcp-oauth-provider.ts +19 -0
  306. package/src/memory/__tests__/jobs-store-job-classes.test.ts +24 -0
  307. package/src/memory/__tests__/qdrant-client-sentinel.test.ts +49 -0
  308. package/src/memory/__tests__/sparse-tokenize.test.ts +66 -0
  309. package/src/memory/anisotropy.test.ts +247 -0
  310. package/src/memory/anisotropy.ts +443 -0
  311. package/src/memory/auto-analysis-constants.ts +17 -0
  312. package/src/memory/auto-analysis-guard.ts +5 -15
  313. package/src/memory/canonical-guardian-store.ts +7 -7
  314. package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +122 -0
  315. package/src/memory/context-search/agent-protocol.ts +6 -6
  316. package/src/memory/context-search/agent-runner.ts +32 -7
  317. package/src/memory/context-search/sources/memory-v2.ts +17 -5
  318. package/src/memory/conversation-crud.ts +1 -1
  319. package/src/memory/conversation-key-store.ts +2 -15
  320. package/src/memory/db-init.ts +4 -0
  321. package/src/memory/embedding-backend.ts +9 -21
  322. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +49 -4
  323. package/src/memory/graph/conversation-graph-memory.ts +1 -24
  324. package/src/memory/graph/graph-search.ts +8 -0
  325. package/src/memory/graph/retriever.ts +28 -0
  326. package/src/memory/graph/tools.ts +1 -1
  327. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +8 -2
  328. package/src/memory/jobs/embed-concept-page.ts +28 -2
  329. package/src/memory/jobs/embed-pkb-file.test.ts +2 -2
  330. package/src/memory/jobs-store.ts +66 -22
  331. package/src/memory/jobs-worker.ts +112 -63
  332. package/src/memory/memory-v2-activation-log-store.ts +1 -1
  333. package/src/memory/migrations/237-heartbeat-runs.ts +45 -0
  334. package/src/memory/migrations/238-schedule-retry-policy.ts +20 -0
  335. package/src/memory/migrations/index.ts +5 -0
  336. package/src/memory/migrations/registry.ts +8 -0
  337. package/src/memory/pkb/pkb-search.ts +7 -0
  338. package/src/memory/qdrant-client.ts +50 -20
  339. package/src/memory/schema/infrastructure.ts +15 -0
  340. package/src/memory/search/semantic.ts +7 -0
  341. package/src/memory/sparse-tokenize.ts +49 -0
  342. package/src/memory/v2/__tests__/activation.test.ts +77 -95
  343. package/src/memory/v2/__tests__/injection.test.ts +43 -21
  344. package/src/memory/v2/__tests__/sim.test.ts +166 -6
  345. package/src/memory/v2/__tests__/sparse-bm25.test.ts +292 -0
  346. package/src/memory/v2/__tests__/static-context.test.ts +0 -1
  347. package/src/memory/v2/activation.ts +69 -88
  348. package/src/memory/v2/consolidation-job.ts +3 -5
  349. package/src/memory/v2/constants.ts +7 -0
  350. package/src/memory/v2/injection.ts +86 -53
  351. package/src/memory/v2/prompts/consolidation.ts +312 -91
  352. package/src/memory/v2/qdrant.ts +99 -1
  353. package/src/memory/v2/sim.ts +126 -16
  354. package/src/memory/v2/skill-qdrant.ts +12 -3
  355. package/src/memory/v2/skill-store.ts +16 -1
  356. package/src/memory/v2/sparse-bm25.ts +245 -0
  357. package/src/memory/v2/static-context.ts +6 -5
  358. package/src/messaging/providers/gmail/types.ts +0 -49
  359. package/src/messaging/providers/slack/adapter.ts +1 -31
  360. package/src/messaging/providers/slack/types.ts +0 -32
  361. package/src/notifications/README.md +10 -10
  362. package/src/notifications/broadcaster.ts +1 -1
  363. package/src/notifications/guardian-question-mode.ts +5 -5
  364. package/src/oauth/connect-orchestrator.ts +4 -0
  365. package/src/oauth/credential-token-resolver.ts +1 -3
  366. package/src/oauth/manual-token-connection.ts +0 -4
  367. package/src/outbound-proxy/index.ts +1 -37
  368. package/src/outbound-proxy/logging.ts +1 -1
  369. package/src/outbound-proxy/policy.ts +6 -5
  370. package/src/outbound-proxy/router.ts +2 -1
  371. package/src/permissions/approval-policy.test.ts +6 -275
  372. package/src/permissions/approval-policy.ts +0 -51
  373. package/src/permissions/checker.test.ts +0 -1
  374. package/src/permissions/checker.ts +3 -17
  375. package/src/permissions/gateway-threshold-reader.ts +2 -0
  376. package/src/permissions/prompter.ts +34 -1
  377. package/src/permissions/secret-prompter.ts +6 -2
  378. package/src/prompts/bootstrap-cleanup.ts +27 -0
  379. package/src/prompts/system-prompt.ts +3 -18
  380. package/src/prompts/templates/SOUL.md +13 -1
  381. package/src/providers/speech-to-text/provider-catalog.ts +7 -8
  382. package/src/runtime/assistant-event-hub.ts +118 -96
  383. package/src/runtime/assistant-event.ts +1 -0
  384. package/src/runtime/auth/__tests__/middleware.test.ts +11 -56
  385. package/src/runtime/auth/middleware.ts +0 -96
  386. package/src/runtime/auth/route-policy.ts +19 -0
  387. package/src/runtime/btw-sidechain.ts +2 -3
  388. package/src/runtime/channel-invite-transport.ts +2 -48
  389. package/src/runtime/channel-invite-transports/email.ts +1 -1
  390. package/src/runtime/channel-invite-transports/slack.ts +1 -1
  391. package/src/runtime/channel-invite-transports/telegram.ts +1 -1
  392. package/src/runtime/channel-invite-transports/voice.ts +1 -1
  393. package/src/runtime/channel-invite-transports/whatsapp.ts +1 -1
  394. package/src/runtime/channel-invite-types.ts +54 -0
  395. package/src/runtime/channel-readiness-service.ts +32 -13
  396. package/src/runtime/http-server.ts +3 -329
  397. package/src/runtime/http-types.ts +0 -5
  398. package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +413 -0
  399. package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +260 -0
  400. package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +189 -0
  401. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +153 -1
  402. package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +451 -0
  403. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
  404. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +515 -0
  405. package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +437 -0
  406. package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +319 -0
  407. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +51 -1
  408. package/src/runtime/migrations/migration-transport.ts +7 -7
  409. package/src/runtime/migrations/vbundle-builder.ts +327 -60
  410. package/src/runtime/migrations/vbundle-import-analyzer.ts +4 -4
  411. package/src/runtime/migrations/vbundle-import-policy.ts +172 -0
  412. package/src/runtime/migrations/vbundle-importer.ts +245 -68
  413. package/src/runtime/migrations/vbundle-streaming-importer.ts +326 -35
  414. package/src/runtime/migrations/vbundle-streaming-validator.ts +157 -4
  415. package/src/runtime/migrations/vbundle-tar-stream.ts +15 -6
  416. package/src/runtime/migrations/vbundle-validator.ts +114 -0
  417. package/src/runtime/pending-interactions.ts +35 -9
  418. package/src/runtime/routes/__tests__/backup-routes.test.ts +22 -150
  419. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
  420. package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +242 -0
  421. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +112 -0
  422. package/src/runtime/routes/approval-interception-types.ts +13 -0
  423. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +1 -1
  424. package/src/runtime/routes/backup-routes.ts +15 -38
  425. package/src/runtime/routes/btw-routes.ts +14 -37
  426. package/src/runtime/routes/client-routes.ts +1 -0
  427. package/src/runtime/routes/contact-prompt-routes.ts +183 -0
  428. package/src/runtime/routes/conversation-query-routes.ts +36 -1
  429. package/src/runtime/routes/conversation-routes.ts +30 -13
  430. package/src/runtime/routes/document-pdf-renderer.ts +165 -0
  431. package/src/runtime/routes/documents-routes.ts +30 -0
  432. package/src/runtime/routes/errors.ts +19 -4
  433. package/src/runtime/routes/events-routes.ts +12 -6
  434. package/src/runtime/routes/gateway-log-routes.ts +79 -0
  435. package/src/runtime/routes/guardian-approval-interception.ts +2 -8
  436. package/src/runtime/routes/heartbeat-routes.ts +103 -38
  437. package/src/runtime/routes/host-app-control-routes.ts +134 -0
  438. package/src/runtime/routes/host-bash-routes.ts +36 -6
  439. package/src/runtime/routes/host-browser-routes.ts +108 -13
  440. package/src/runtime/routes/host-cu-routes.ts +44 -14
  441. package/src/runtime/routes/host-file-routes.ts +33 -10
  442. package/src/runtime/routes/host-transfer-routes.ts +64 -24
  443. package/src/runtime/routes/http-adapter.ts +1 -0
  444. package/src/runtime/routes/identity-intro-cache.ts +30 -0
  445. package/src/runtime/routes/identity-routes.ts +15 -43
  446. package/src/runtime/routes/inbound-message-handler.ts +1 -9
  447. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -7
  448. package/src/runtime/routes/inbound-stages/edit-intercept.ts +0 -8
  449. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +0 -20
  450. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +5 -13
  451. package/src/runtime/routes/index.ts +8 -0
  452. package/src/runtime/routes/mcp-auth-routes.ts +132 -0
  453. package/src/runtime/routes/memory-item-routes.ts +10 -12
  454. package/src/runtime/routes/memory-v2-routes.ts +441 -1
  455. package/src/runtime/routes/migration-routes.ts +96 -0
  456. package/src/runtime/routes/schedule-routes.ts +7 -0
  457. package/src/runtime/verification-templates.ts +4 -7
  458. package/src/schedule/integration-status.ts +66 -2
  459. package/src/schedule/recurrence-engine.ts +4 -1
  460. package/src/schedule/retry-backoff.ts +18 -0
  461. package/src/schedule/retry-policy.ts +82 -0
  462. package/src/schedule/schedule-recovery.ts +64 -0
  463. package/src/schedule/schedule-store.ts +106 -2
  464. package/src/schedule/scheduler-types.ts +25 -0
  465. package/src/schedule/scheduler.ts +63 -38
  466. package/src/security/oauth-callback-registry.ts +8 -0
  467. package/src/sequence/analytics.ts +5 -5
  468. package/src/sequence/engine.ts +1 -1
  469. package/src/skills/catalog-files.ts +2 -8
  470. package/src/skills/include-graph.ts +5 -5
  471. package/src/skills/remote-skill-policy.ts +5 -5
  472. package/src/skills/skill-file-provider.ts +1 -1
  473. package/src/skills/skill-file-types.ts +13 -0
  474. package/src/skills/skillssh-audit-types.ts +28 -0
  475. package/src/skills/skillssh-registry.ts +8 -21
  476. package/src/telemetry/types.ts +2 -0
  477. package/src/telemetry/usage-telemetry-reporter.test.ts +21 -0
  478. package/src/telemetry/usage-telemetry-reporter.ts +1 -0
  479. package/src/tools/app-control/skill-proxy-bridge.ts +28 -0
  480. package/src/tools/apps/executors.ts +56 -69
  481. package/src/tools/browser/__tests__/browser-status.test.ts +21 -18
  482. package/src/tools/browser/browser-execution.ts +2 -2
  483. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +55 -4
  484. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +12 -6
  485. package/src/tools/browser/cdp-client/factory.ts +23 -24
  486. package/src/tools/browser/cdp-client/index.ts +1 -14
  487. package/src/tools/computer-use/definitions.ts +42 -20
  488. package/src/tools/executor.ts +2 -0
  489. package/src/tools/host-filesystem/edit.ts +26 -0
  490. package/src/tools/host-filesystem/read.ts +26 -0
  491. package/src/tools/host-filesystem/transfer.ts +31 -1
  492. package/src/tools/host-filesystem/write.ts +26 -0
  493. package/src/tools/host-terminal/host-shell.ts +58 -0
  494. package/src/tools/schedule/create.ts +6 -0
  495. package/src/tools/schedule/list.ts +2 -0
  496. package/src/tools/schedule/update.ts +10 -0
  497. package/src/tools/shared/filesystem/file-ops-service.ts +2 -0
  498. package/src/tools/shared/filesystem/path-policy.ts +25 -1
  499. package/src/tools/skills/load.ts +0 -32
  500. package/src/tools/tool-approval-handler.ts +1 -5
  501. package/src/tools/types.ts +4 -0
  502. package/src/usage/pricing.ts +1 -1
  503. package/src/workspace/hatched-date.ts +86 -0
  504. package/src/workspace/migrations/003-seed-device-id.ts +1 -1
  505. package/src/workspace/migrations/006-services-config.ts +8 -5
  506. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +3 -9
  507. package/src/workspace/migrations/021-move-signals-to-workspace.ts +4 -10
  508. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +4 -10
  509. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +4 -11
  510. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +3 -10
  511. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +3 -2
  512. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +2 -1
  513. package/src/workspace/migrations/059-move-pid-to-workspace.ts +3 -8
  514. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +3 -8
  515. package/src/workspace/migrations/AGENTS.md +1 -1
  516. package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -10
  517. package/src/workspace/migrations/utils.ts +21 -0
  518. package/src/__tests__/host-browser-e2e-cloud.test.ts +0 -443
  519. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +0 -226
  520. package/src/__tests__/host-browser-ws-events-e2e.test.ts +0 -427
  521. package/src/__tests__/twilio-rest.test.ts +0 -34
  522. package/src/backup/__tests__/backup-key.test.ts +0 -152
  523. package/src/backup/__tests__/backup-worker.test.ts +0 -782
  524. package/src/backup/__tests__/offsite-writer.test.ts +0 -641
  525. package/src/backup/__tests__/stream-crypt.test.ts +0 -228
  526. package/src/backup/backup-key.ts +0 -137
  527. package/src/backup/backup-worker.ts +0 -472
  528. package/src/backup/offsite-writer.ts +0 -222
  529. package/src/backup/stream-crypt.ts +0 -263
  530. package/src/daemon/message-types/pairing.ts +0 -58
  531. package/src/outbound-proxy/config.ts +0 -20
  532. package/src/outbound-proxy/health.ts +0 -18
  533. package/src/outbound-proxy/types.ts +0 -150
  534. package/src/runtime/capability-tokens.ts +0 -190
  535. package/src/signals/mcp-reload.ts +0 -18
@@ -8,6 +8,16 @@ import { z } from "zod";
8
8
 
9
9
  import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
10
10
  import { loadConfig } from "../../config/loader.js";
11
+ import {
12
+ applyCorrectionIfCalibrated,
13
+ explainedVarianceRatio,
14
+ fitAnisotropyCalibration,
15
+ saveCalibration,
16
+ } from "../../memory/anisotropy.js";
17
+ import {
18
+ embedWithBackend,
19
+ selectEmbeddingBackend,
20
+ } from "../../memory/embedding-backend.js";
11
21
  import {
12
22
  enqueueMemoryJob,
13
23
  type MemoryJobType,
@@ -17,8 +27,22 @@ import {
17
27
  totalEdgeCount,
18
28
  validateEdgeTargets,
19
29
  } from "../../memory/v2/edge-index.js";
20
- import { listPages, readPage } from "../../memory/v2/page-store.js";
30
+ import {
31
+ listPages,
32
+ readPage,
33
+ renderPageContent,
34
+ } from "../../memory/v2/page-store.js";
35
+ import {
36
+ hybridQueryConceptPages,
37
+ sampleConceptPageDenseVectors,
38
+ } from "../../memory/v2/qdrant.js";
39
+ import { effectiveWeights } from "../../memory/v2/sim.js";
21
40
  import { seedV2SkillEntries } from "../../memory/v2/skill-store.js";
41
+ import {
42
+ generateBm25QueryEmbedding,
43
+ getConceptPageCorpusStats,
44
+ rebuildConceptPageCorpusStats,
45
+ } from "../../memory/v2/sparse-bm25.js";
22
46
  import { getWorkspaceDir } from "../../util/platform.js";
23
47
  import { RouteError } from "./errors.js";
24
48
  import type { RouteDefinition } from "./types.js";
@@ -113,6 +137,79 @@ async function handleValidate({
113
137
  };
114
138
  }
115
139
 
140
+ // ── Get concept page ────────────────────────────────────────────────────
141
+
142
+ const MemoryV2GetConceptPageParams = z
143
+ .object({
144
+ slug: z.string().min(1),
145
+ })
146
+ .strict();
147
+
148
+ export type MemoryV2GetConceptPageResult = {
149
+ slug: string;
150
+ /** Frontmatter + body, as produced by `renderPageContent`. */
151
+ rendered: string;
152
+ };
153
+
154
+ async function handleGetConceptPage({
155
+ body = {},
156
+ }: RouteHandlerArgs): Promise<MemoryV2GetConceptPageResult> {
157
+ const { slug } = MemoryV2GetConceptPageParams.parse(body);
158
+ const workspaceDir = getWorkspaceDir();
159
+ let page;
160
+ try {
161
+ page = await readPage(workspaceDir, slug);
162
+ } catch (err) {
163
+ throw new RouteError(
164
+ `Failed to read concept page '${slug}': ${err instanceof Error ? err.message : String(err)}`,
165
+ "MEMORY_V2_PAGE_READ_FAILED",
166
+ 400,
167
+ );
168
+ }
169
+ if (!page) {
170
+ throw new RouteError(
171
+ `Concept page '${slug}' not found on disk`,
172
+ "MEMORY_V2_PAGE_NOT_FOUND",
173
+ 404,
174
+ );
175
+ }
176
+ return { slug, rendered: renderPageContent(page) };
177
+ }
178
+
179
+ // ── Rebuild BM25 corpus stats ───────────────────────────────────────────
180
+
181
+ const MemoryV2RebuildCorpusStatsParams = z.object({}).strict();
182
+
183
+ export interface MemoryV2RebuildCorpusStatsResult {
184
+ totalDocs: number;
185
+ avgDl: number;
186
+ /** Number of distinct hashed-token buckets that received any DF count. */
187
+ vocabularyBuckets: number;
188
+ }
189
+
190
+ async function handleRebuildCorpusStats({
191
+ body = {},
192
+ }: RouteHandlerArgs): Promise<MemoryV2RebuildCorpusStatsResult> {
193
+ MemoryV2RebuildCorpusStatsParams.parse(body);
194
+ const workspaceDir = getWorkspaceDir();
195
+ await rebuildConceptPageCorpusStats(workspaceDir);
196
+ const stats = getConceptPageCorpusStats();
197
+ if (!stats) {
198
+ // The rebuild always swaps in a non-null table on success, so a missing
199
+ // value here means an unexpected reset between rebuild and read.
200
+ throw new RouteError(
201
+ "Corpus stats rebuild completed but no table is loaded",
202
+ "MEMORY_V2_CORPUS_STATS_MISSING",
203
+ 500,
204
+ );
205
+ }
206
+ return {
207
+ totalDocs: stats.totalDocs,
208
+ avgDl: stats.avgDl,
209
+ vocabularyBuckets: stats.df.size,
210
+ };
211
+ }
212
+
116
213
  // ── Reembed skills ──────────────────────────────────────────────────────
117
214
 
118
215
  const MemoryV2ReembedSkillsParams = z.object({}).strict();
@@ -149,6 +246,305 @@ async function handleReembedSkills({
149
246
  return { success: true };
150
247
  }
151
248
 
249
+ // ── Explain similarity ──────────────────────────────────────────────────
250
+
251
+ const MemoryV2ExplainSimilarityParams = z
252
+ .object({
253
+ userText: z.string().min(1),
254
+ assistantText: z.string().optional(),
255
+ nowText: z.string().optional(),
256
+ top: z.number().int().min(1).default(25),
257
+ })
258
+ .strict();
259
+
260
+ export interface MemoryV2ExplainSimilarityRow {
261
+ slug: string;
262
+ /** Raw dense cosine score, or null when the slug missed the dense channel. */
263
+ denseScore: number | null;
264
+ /** Raw sparse score (Qdrant scale), or null when the slug missed sparse. */
265
+ sparseRaw: number | null;
266
+ /** Sparse score divided by the per-batch max, in [0, 1]. */
267
+ sparseNorm: number | null;
268
+ /** `clamp01(dense_weight·dense + sparse_weight·sparseNorm)` — the simBatch fused value. */
269
+ fused: number;
270
+ }
271
+
272
+ export interface MemoryV2ExplainSimilarityStats {
273
+ count: number;
274
+ min: number;
275
+ max: number;
276
+ mean: number;
277
+ stddev: number;
278
+ }
279
+
280
+ export interface MemoryV2ExplainSimilarityChannel {
281
+ channel: "user" | "assistant" | "now";
282
+ textPreview: string;
283
+ maxSparse: number;
284
+ /**
285
+ * Spread (max - min) of normalized sparse scores across this channel's
286
+ * hits. Drives adaptive sparse weighting — low spread means the sparse
287
+ * channel can't discriminate, so its weight collapses for this query.
288
+ */
289
+ sparseSpread: number;
290
+ /** Sparse weight after adaptive collapse (≤ the configured base). */
291
+ effectiveSparseWeight: number;
292
+ /** Dense weight after adaptive compensation (≥ the configured base). */
293
+ effectiveDenseWeight: number;
294
+ rows: MemoryV2ExplainSimilarityRow[];
295
+ stats: {
296
+ dense: MemoryV2ExplainSimilarityStats;
297
+ sparseRaw: MemoryV2ExplainSimilarityStats;
298
+ sparseNorm: MemoryV2ExplainSimilarityStats;
299
+ fused: MemoryV2ExplainSimilarityStats;
300
+ };
301
+ }
302
+
303
+ export interface MemoryV2ExplainSimilarityResult {
304
+ config: {
305
+ dense_weight: number;
306
+ sparse_weight: number;
307
+ };
308
+ channels: MemoryV2ExplainSimilarityChannel[];
309
+ }
310
+
311
+ function summarizeStats(values: number[]): MemoryV2ExplainSimilarityStats {
312
+ if (values.length === 0) {
313
+ return { count: 0, min: 0, max: 0, mean: 0, stddev: 0 };
314
+ }
315
+ let min = Infinity;
316
+ let max = -Infinity;
317
+ let sum = 0;
318
+ for (const v of values) {
319
+ if (v < min) min = v;
320
+ if (v > max) max = v;
321
+ sum += v;
322
+ }
323
+ const mean = sum / values.length;
324
+ let sqDiff = 0;
325
+ for (const v of values) sqDiff += (v - mean) * (v - mean);
326
+ const stddev = Math.sqrt(sqDiff / values.length);
327
+ return { count: values.length, min, max, mean, stddev };
328
+ }
329
+
330
+ async function scoreChannel(
331
+ channel: "user" | "assistant" | "now",
332
+ text: string,
333
+ top: number,
334
+ denseWeight: number,
335
+ sparseWeight: number,
336
+ config: ReturnType<typeof loadConfig>,
337
+ ): Promise<MemoryV2ExplainSimilarityChannel> {
338
+ const denseResult = await embedWithBackend(config, [text]);
339
+ const denseVec = await applyCorrectionIfCalibrated(
340
+ denseResult.vectors[0],
341
+ denseResult.provider,
342
+ denseResult.model,
343
+ );
344
+ const sparseVec = generateBm25QueryEmbedding(text);
345
+
346
+ const hits = await hybridQueryConceptPages(denseVec, sparseVec, top);
347
+
348
+ let maxSparse = 0;
349
+ for (const hit of hits) {
350
+ if (hit.sparseScore !== undefined && hit.sparseScore > maxSparse) {
351
+ maxSparse = hit.sparseScore;
352
+ }
353
+ }
354
+
355
+ // Mirror simBatch's adaptive weighting so the printed `fused` matches what
356
+ // production retrieval would actually score for this query — otherwise
357
+ // operators staring at the diagnostic would see different numbers than
358
+ // the activation pipeline saw.
359
+ const {
360
+ dense: effDense,
361
+ sparse: effSparse,
362
+ spread: sparseSpread,
363
+ } = effectiveWeights(hits, maxSparse, denseWeight, sparseWeight, config);
364
+
365
+ const rows: MemoryV2ExplainSimilarityRow[] = hits.map((hit) => {
366
+ const dense = hit.denseScore ?? 0;
367
+ const sparseNorm =
368
+ hit.sparseScore !== undefined && maxSparse > 0
369
+ ? hit.sparseScore / maxSparse
370
+ : 0;
371
+ const fusedRaw = effDense * dense + effSparse * sparseNorm;
372
+ const fused = Math.max(0, Math.min(1, fusedRaw));
373
+ return {
374
+ slug: hit.slug,
375
+ denseScore: hit.denseScore ?? null,
376
+ sparseRaw: hit.sparseScore ?? null,
377
+ sparseNorm: hit.sparseScore !== undefined ? sparseNorm : null,
378
+ fused,
379
+ };
380
+ });
381
+
382
+ rows.sort((a, b) => b.fused - a.fused);
383
+
384
+ const denseValues: number[] = [];
385
+ const sparseRawValues: number[] = [];
386
+ const sparseNormValues: number[] = [];
387
+ const fusedValues: number[] = [];
388
+ for (const row of rows) {
389
+ if (row.denseScore !== null) denseValues.push(row.denseScore);
390
+ if (row.sparseRaw !== null) sparseRawValues.push(row.sparseRaw);
391
+ if (row.sparseNorm !== null) sparseNormValues.push(row.sparseNorm);
392
+ fusedValues.push(row.fused);
393
+ }
394
+
395
+ return {
396
+ channel,
397
+ textPreview: text.length > 120 ? `${text.slice(0, 120)}…` : text,
398
+ maxSparse,
399
+ sparseSpread,
400
+ effectiveSparseWeight: effSparse,
401
+ effectiveDenseWeight: effDense,
402
+ rows,
403
+ stats: {
404
+ dense: summarizeStats(denseValues),
405
+ sparseRaw: summarizeStats(sparseRawValues),
406
+ sparseNorm: summarizeStats(sparseNormValues),
407
+ fused: summarizeStats(fusedValues),
408
+ },
409
+ };
410
+ }
411
+
412
+ async function handleExplainSimilarity({
413
+ body = {},
414
+ }: RouteHandlerArgs): Promise<MemoryV2ExplainSimilarityResult> {
415
+ const params = MemoryV2ExplainSimilarityParams.parse(body);
416
+ const config = loadConfig();
417
+ const { dense_weight: denseWeight, sparse_weight: sparseWeight } =
418
+ config.memory.v2;
419
+
420
+ const channels: MemoryV2ExplainSimilarityChannel[] = [];
421
+ channels.push(
422
+ await scoreChannel(
423
+ "user",
424
+ params.userText,
425
+ params.top,
426
+ denseWeight,
427
+ sparseWeight,
428
+ config,
429
+ ),
430
+ );
431
+ if (params.assistantText && params.assistantText.length > 0) {
432
+ channels.push(
433
+ await scoreChannel(
434
+ "assistant",
435
+ params.assistantText,
436
+ params.top,
437
+ denseWeight,
438
+ sparseWeight,
439
+ config,
440
+ ),
441
+ );
442
+ }
443
+ if (params.nowText && params.nowText.length > 0) {
444
+ channels.push(
445
+ await scoreChannel(
446
+ "now",
447
+ params.nowText,
448
+ params.top,
449
+ denseWeight,
450
+ sparseWeight,
451
+ config,
452
+ ),
453
+ );
454
+ }
455
+
456
+ return {
457
+ config: { dense_weight: denseWeight, sparse_weight: sparseWeight },
458
+ channels,
459
+ };
460
+ }
461
+
462
+ // ── Fit anisotropy calibration ──────────────────────────────────────────
463
+
464
+ const MemoryV2FitAnisotropyParams = z
465
+ .object({
466
+ /**
467
+ * Number of leading principal components to project out at apply time.
468
+ * `1` is the canonical default for transformer embeddings; raise to 2-3
469
+ * only when the variance spectrum shows multiple dominant directions.
470
+ */
471
+ k: z.number().int().min(1).max(16).default(1),
472
+ /**
473
+ * Maximum number of stored vectors to pull from Qdrant for the fit.
474
+ * 5_000 is plenty for 3072-dim Gemini — power iteration converges fast
475
+ * and pulling the full corpus would just cost wall-clock time.
476
+ */
477
+ sample: z.number().int().min(1).max(100_000).default(5_000),
478
+ })
479
+ .strict();
480
+
481
+ export interface MemoryV2FitAnisotropyResult {
482
+ provider: string;
483
+ model: string;
484
+ dim: number;
485
+ k: number;
486
+ sampleCount: number;
487
+ totalVariance: number;
488
+ componentVariance: number[];
489
+ /** `componentVariance[i] / totalVariance` for each component. */
490
+ explainedVarianceRatio: number[];
491
+ /** Absolute path the calibration was written to. */
492
+ path: string;
493
+ }
494
+
495
+ async function handleFitAnisotropy({
496
+ body = {},
497
+ }: RouteHandlerArgs): Promise<MemoryV2FitAnisotropyResult> {
498
+ const { k, sample } = MemoryV2FitAnisotropyParams.parse(body);
499
+ const config = loadConfig();
500
+
501
+ const selection = await selectEmbeddingBackend(config);
502
+ if (!selection.backend) {
503
+ throw new RouteError(
504
+ `Cannot fit anisotropy calibration: ${selection.reason ?? "no embedding backend configured"}`,
505
+ "MEMORY_V2_NO_EMBEDDING_BACKEND",
506
+ 409,
507
+ );
508
+ }
509
+
510
+ const vectors = await sampleConceptPageDenseVectors(sample);
511
+ if (vectors.length === 0) {
512
+ throw new RouteError(
513
+ "Cannot fit anisotropy calibration: the v2 concept-page collection is empty. " +
514
+ "Embed some concept pages first (run `assistant memory v2 reembed`), then retry.",
515
+ "MEMORY_V2_NO_VECTORS",
516
+ 409,
517
+ );
518
+ }
519
+ if (vectors.length < k * 4) {
520
+ // PCA on too-few samples is unstable — refuse rather than hand back
521
+ // overfit components. The 4× heuristic is conservative; in practice
522
+ // anisotropy fits stabilise at a few hundred samples per component.
523
+ throw new RouteError(
524
+ `Cannot fit k=${k} components from only ${vectors.length} vectors — need at least ${k * 4}. ` +
525
+ "Embed more concept pages or fit a smaller k.",
526
+ "MEMORY_V2_INSUFFICIENT_VECTORS",
527
+ 409,
528
+ );
529
+ }
530
+
531
+ const { provider, model } = selection.backend;
532
+ const calib = fitAnisotropyCalibration(vectors, k, { provider, model });
533
+ const path = await saveCalibration(calib);
534
+
535
+ return {
536
+ provider,
537
+ model,
538
+ dim: calib.dim,
539
+ k,
540
+ sampleCount: calib.sampleCount,
541
+ totalVariance: calib.totalVariance,
542
+ componentVariance: calib.componentVariance,
543
+ explainedVarianceRatio: explainedVarianceRatio(calib),
544
+ path,
545
+ };
546
+ }
547
+
152
548
  // ── Route definitions ───────────────────────────────────────────────────
153
549
 
154
550
  export const ROUTES: RouteDefinition[] = [
@@ -174,6 +570,17 @@ export const ROUTES: RouteDefinition[] = [
174
570
  tags: ["memory"],
175
571
  requestBody: MemoryV2ValidateParams,
176
572
  },
573
+ {
574
+ operationId: "memory_v2_get_concept_page",
575
+ method: "POST",
576
+ endpoint: "memory/v2/concept-page",
577
+ handler: handleGetConceptPage,
578
+ summary: "Read a single memory v2 concept page",
579
+ description:
580
+ "Returns the rendered (frontmatter + body) markdown for a slug. 404 when the slug has no on-disk page — the activation log inspector uses this to show what got injected.",
581
+ tags: ["memory"],
582
+ requestBody: MemoryV2GetConceptPageParams,
583
+ },
177
584
  {
178
585
  operationId: "memory_v2_reembed_skills",
179
586
  method: "POST",
@@ -185,4 +592,37 @@ export const ROUTES: RouteDefinition[] = [
185
592
  tags: ["memory"],
186
593
  requestBody: MemoryV2ReembedSkillsParams,
187
594
  },
595
+ {
596
+ operationId: "memory_v2_explain_similarity",
597
+ method: "POST",
598
+ endpoint: "memory/v2/explain-similarity",
599
+ handler: handleExplainSimilarity,
600
+ summary: "Diagnose dense vs sparse similarity score distributions",
601
+ description:
602
+ "Read-only diagnostic. Embeds the supplied text(s), runs hybrid dense + sparse queries against the concept-page collection, and returns per-slug raw dense, raw sparse, normalized sparse, and fused scores plus per-channel summary stats. Used to investigate score-compression at the head of the activation distribution.",
603
+ tags: ["memory"],
604
+ requestBody: MemoryV2ExplainSimilarityParams,
605
+ },
606
+ {
607
+ operationId: "memory_v2_rebuild_corpus_stats",
608
+ method: "POST",
609
+ endpoint: "memory/v2/rebuild-corpus-stats",
610
+ handler: handleRebuildCorpusStats,
611
+ summary: "Rebuild the BM25 corpus statistics for memory v2",
612
+ description:
613
+ "Walks every concept page on disk, recomputes the document-frequency table and average document length used by the BM25 sparse channel, and atomically swaps the in-memory stats. Run after bulk content imports or to recover from a rebuild that errored at startup. Does not reembed individual page sparse vectors — pair with `assistant memory v2 reembed` when document-side weights need refreshing.",
614
+ tags: ["memory"],
615
+ requestBody: MemoryV2RebuildCorpusStatsParams,
616
+ },
617
+ {
618
+ operationId: "memory_v2_fit_anisotropy",
619
+ method: "POST",
620
+ endpoint: "memory/v2/fit-anisotropy",
621
+ handler: handleFitAnisotropy,
622
+ summary: "Fit the embedding anisotropy correction for memory v2",
623
+ description:
624
+ "Samples stored dense vectors from the concept-page Qdrant collection, fits a corpus mean + top-k principal components (Mu & Viswanath 'all-but-the-top'), and persists the calibration so subsequent embeds and queries apply the correction. Run `assistant memory v2 reembed` after fitting so stored vectors are written under the new calibration — until then, queries (corrected) and stored vectors (uncorrected) live in different spaces.",
625
+ tags: ["memory"],
626
+ requestBody: MemoryV2FitAnisotropyParams,
627
+ },
188
628
  ];
@@ -65,6 +65,11 @@ import {
65
65
  analyzeImport,
66
66
  DefaultPathResolver,
67
67
  } from "../migrations/vbundle-import-analyzer.js";
68
+ import {
69
+ evaluateRuntimeCompatibility,
70
+ formatRuntimeCompatibilityMessage,
71
+ type RuntimeCompatibility,
72
+ } from "../migrations/vbundle-import-policy.js";
68
73
  import {
69
74
  commitImport,
70
75
  extractCredentialsFromBundle,
@@ -79,6 +84,7 @@ import {
79
84
  InternalError,
80
85
  NotFoundError,
81
86
  RouteError,
87
+ UnprocessableEntityError,
82
88
  } from "./errors.js";
83
89
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
84
90
  import { RouteResponse } from "./types.js";
@@ -866,6 +872,23 @@ export async function handleMigrationImport(
866
872
  };
867
873
  }
868
874
 
875
+ // Pre-check runtime-version compat before the DB close/reopen cycle.
876
+ // commitImport runs the same gate as defense-in-depth for callers that
877
+ // don't pre-check; we run it here too so an incompatible bundle short-
878
+ // circuits before resetDb().
879
+ const compatResult = evaluateRuntimeCompatibility(
880
+ validation.manifest!.compatibility,
881
+ APP_VERSION,
882
+ );
883
+ if (!compatResult.ok) {
884
+ throwImportCommitFailure({
885
+ ok: false,
886
+ reason: "version_incompatible",
887
+ bundle_compat: compatResult.bundle_compat,
888
+ runtime_version: compatResult.runtime_version,
889
+ });
890
+ }
891
+
869
892
  const pathResolver = new DefaultPathResolver(
870
893
  getWorkspaceDir(),
871
894
  getWorkspaceHooksDir(),
@@ -918,6 +941,12 @@ export async function handleMigrationImport(
918
941
 
919
942
  return importCommitSuccessResult(result.report, credentialsImported);
920
943
  } catch (err) {
944
+ // Preserve typed RouteError instances (e.g. UnprocessableEntityError for
945
+ // version_incompatible, BadRequestError for validation_failed) — only
946
+ // wrap genuinely unexpected errors as 500 InternalError.
947
+ if (err instanceof RouteError) {
948
+ throw err;
949
+ }
921
950
  log.error({ err }, "Unexpected error during import commit");
922
951
  throw new InternalError(
923
952
  err instanceof Error ? err.message : "Unexpected import error",
@@ -1014,12 +1043,18 @@ interface GcsImportErrorInit {
1014
1043
  | "fetch_failed"
1015
1044
  | "validation_failed"
1016
1045
  | "extraction_failed"
1046
+ | "version_incompatible"
1017
1047
  | "write_failed";
1018
1048
  message: string;
1019
1049
  upstreamStatus?: number;
1020
1050
  reason?: string;
1021
1051
  errors?: Array<{ code: string; message: string; path?: string }>;
1022
1052
  partial_report?: ImportCommitReport;
1053
+ /** Populated for `version_incompatible` — mirrors the platform's PR #5470
1054
+ * response shape so the URL-body endpoint can return the same body. */
1055
+ bundle_compat?: RuntimeCompatibility;
1056
+ /** Populated for `version_incompatible`. */
1057
+ runtime_version?: string;
1023
1058
  }
1024
1059
 
1025
1060
  class GcsImportError extends Error {
@@ -1028,6 +1063,8 @@ class GcsImportError extends Error {
1028
1063
  public readonly reason?: string;
1029
1064
  public readonly errors?: GcsImportErrorInit["errors"];
1030
1065
  public readonly partial_report?: ImportCommitReport;
1066
+ public readonly bundle_compat?: RuntimeCompatibility;
1067
+ public readonly runtime_version?: string;
1031
1068
 
1032
1069
  constructor(init: GcsImportErrorInit) {
1033
1070
  super(init.message);
@@ -1045,6 +1082,12 @@ class GcsImportError extends Error {
1045
1082
  if (init.partial_report !== undefined) {
1046
1083
  this.partial_report = init.partial_report;
1047
1084
  }
1085
+ if (init.bundle_compat !== undefined) {
1086
+ this.bundle_compat = init.bundle_compat;
1087
+ }
1088
+ if (init.runtime_version !== undefined) {
1089
+ this.runtime_version = init.runtime_version;
1090
+ }
1048
1091
  }
1049
1092
  }
1050
1093
 
@@ -1343,6 +1386,22 @@ async function runGcsImport(
1343
1386
  reason: result.reason,
1344
1387
  });
1345
1388
  }
1389
+ if (result.reason === "version_incompatible") {
1390
+ // Returned by commitImport / streamCommitImport when the runtime falls
1391
+ // outside the bundle's compat range. The platform-side gate is the
1392
+ // primary check; this catches legacy bundles whose ExportJob row
1393
+ // predates PR #5470 (compat columns NULL → platform gate skipped).
1394
+ throw new GcsImportError({
1395
+ code: "version_incompatible",
1396
+ message: formatRuntimeCompatibilityMessage(
1397
+ result.bundle_compat,
1398
+ result.runtime_version,
1399
+ ),
1400
+ reason: result.reason,
1401
+ bundle_compat: result.bundle_compat,
1402
+ runtime_version: result.runtime_version,
1403
+ });
1404
+ }
1346
1405
  // write_failed
1347
1406
  throw new GcsImportError({
1348
1407
  code: "write_failed",
@@ -1442,6 +1501,20 @@ function throwGcsImportError(err: unknown): never {
1442
1501
  }),
1443
1502
  );
1444
1503
  }
1504
+ if (err.code === "version_incompatible") {
1505
+ // 422 (not 500) — the bundle is structurally valid but cannot be
1506
+ // imported on this runtime. Body mirrors the platform's PR #5470
1507
+ // response shape.
1508
+ throw new UnprocessableEntityError(err.message, {
1509
+ reason: "version_incompatible" as const,
1510
+ ...(err.bundle_compat !== undefined && {
1511
+ bundle_compat: err.bundle_compat,
1512
+ }),
1513
+ ...(err.runtime_version !== undefined && {
1514
+ runtime_version: err.runtime_version,
1515
+ }),
1516
+ });
1517
+ }
1445
1518
  if (err.code === "extraction_failed") {
1446
1519
  throw new InternalError(err.message);
1447
1520
  }
@@ -1674,6 +1747,29 @@ function throwImportCommitFailure(
1674
1747
  throw new InternalError(result.message);
1675
1748
  }
1676
1749
 
1750
+ if (result.reason === "version_incompatible") {
1751
+ // Returned by commitImport / streamCommitImport when the runtime falls
1752
+ // outside the bundle's compat range. The platform-side gate is the
1753
+ // primary check; this catches legacy bundles whose ExportJob row
1754
+ // predates PR #5470 (compat columns NULL → platform gate skipped).
1755
+ //
1756
+ // 422 (not 500) — the bundle is structurally valid but cannot be
1757
+ // imported on this runtime; the caller can act on it (upgrade the
1758
+ // runtime, choose a different bundle). Body mirrors the platform's
1759
+ // PR #5470 response shape.
1760
+ throw new UnprocessableEntityError(
1761
+ formatRuntimeCompatibilityMessage(
1762
+ result.bundle_compat,
1763
+ result.runtime_version,
1764
+ ),
1765
+ {
1766
+ reason: "version_incompatible" as const,
1767
+ bundle_compat: result.bundle_compat,
1768
+ runtime_version: result.runtime_version,
1769
+ },
1770
+ );
1771
+ }
1772
+
1677
1773
  // write_failed
1678
1774
  throw new InternalError(result.message);
1679
1775
  }
@@ -54,6 +54,9 @@ function handleListSchedules(queryParams: Record<string, string>) {
54
54
  nextRunAt: j.nextRunAt,
55
55
  lastRunAt: j.lastRunAt,
56
56
  lastStatus: j.lastStatus,
57
+ retryCount: j.retryCount,
58
+ maxRetries: j.maxRetries,
59
+ retryBackoffMs: j.retryBackoffMs,
57
60
  description:
58
61
  j.syntax === "cron"
59
62
  ? describeCronExpression(j.cronExpression)
@@ -139,6 +142,8 @@ function handleUpdateSchedule(id: string, body: Record<string, unknown>) {
139
142
  "quiet",
140
143
  "reuseConversation",
141
144
  "wakeConversationId",
145
+ "maxRetries",
146
+ "retryBackoffMs",
142
147
  ] as const) {
143
148
  if (key in body) {
144
149
  updates[key] = body[key];
@@ -294,6 +299,8 @@ export const ROUTES: RouteDefinition[] = [
294
299
  .describe("single_channel, multi_channel, or all_channels"),
295
300
  quiet: z.boolean(),
296
301
  reuseConversation: z.boolean(),
302
+ maxRetries: z.number().describe("Maximum retry attempts"),
303
+ retryBackoffMs: z.number().describe("Retry backoff in milliseconds"),
297
304
  }),
298
305
  responseBody: z.object({
299
306
  schedules: z.array(z.unknown()).describe("Updated schedule list"),