@vellumai/assistant 0.7.2 → 0.7.3

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 (347) hide show
  1. package/ARCHITECTURE.md +16 -1
  2. package/docs/architecture/memory.md +5 -2
  3. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
  4. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -9
  5. package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
  6. package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
  7. package/openapi.yaml +449 -22
  8. package/package.json +1 -1
  9. package/src/__tests__/app-control-flow.test.ts +21 -11
  10. package/src/__tests__/assistant-event-hub.test.ts +48 -0
  11. package/src/__tests__/assistant-event.test.ts +0 -10
  12. package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
  13. package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
  14. package/src/__tests__/auto-analysis-end-to-end.test.ts +62 -1
  15. package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
  16. package/src/__tests__/call-conversation-messages.test.ts +8 -2
  17. package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
  18. package/src/__tests__/channel-readiness-service.test.ts +4 -2
  19. package/src/__tests__/config-loader-backfill.test.ts +379 -0
  20. package/src/__tests__/config-schema.test.ts +1 -0
  21. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
  22. package/src/__tests__/config-watcher.test.ts +140 -69
  23. package/src/__tests__/context-search-agent-runner.test.ts +61 -3
  24. package/src/__tests__/context-search-conversations-source.test.ts +0 -24
  25. package/src/__tests__/context-search-fanout.test.ts +0 -1
  26. package/src/__tests__/context-search-memory-source.test.ts +3 -7
  27. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
  28. package/src/__tests__/context-search-pkb-source.test.ts +0 -1
  29. package/src/__tests__/context-search-workspace-source.test.ts +0 -1
  30. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
  31. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
  32. package/src/__tests__/conversation-agent-loop.test.ts +454 -5
  33. package/src/__tests__/conversation-error.test.ts +150 -3
  34. package/src/__tests__/conversation-process-callsite.test.ts +43 -0
  35. package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
  36. package/src/__tests__/conversation-runtime-assembly.test.ts +65 -0
  37. package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
  38. package/src/__tests__/conversation-speed-override.test.ts +0 -3
  39. package/src/__tests__/conversation-store.test.ts +0 -18
  40. package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
  41. package/src/__tests__/conversation-surfaces-data-persist.test.ts +404 -0
  42. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +2 -5
  43. package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
  44. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
  45. package/src/__tests__/credentials-cli.test.ts +7 -0
  46. package/src/__tests__/cu-unified-flow.test.ts +176 -10
  47. package/src/__tests__/date-context.test.ts +164 -2
  48. package/src/__tests__/disk-pressure-guard.test.ts +262 -0
  49. package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
  50. package/src/__tests__/disk-pressure-policy.test.ts +241 -0
  51. package/src/__tests__/disk-pressure-routes.test.ts +379 -0
  52. package/src/__tests__/disk-pressure-tools.test.ts +277 -0
  53. package/src/__tests__/disk-usage.test.ts +150 -0
  54. package/src/__tests__/events-client-registration.test.ts +52 -0
  55. package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
  56. package/src/__tests__/file-write-tool.test.ts +4 -10
  57. package/src/__tests__/filing-service.test.ts +3 -4
  58. package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
  59. package/src/__tests__/heartbeat-service.test.ts +260 -11
  60. package/src/__tests__/host-app-control-proxy.test.ts +195 -25
  61. package/src/__tests__/host-bash-proxy.test.ts +227 -34
  62. package/src/__tests__/host-bash-routes.test.ts +178 -13
  63. package/src/__tests__/host-cu-proxy.test.ts +210 -3
  64. package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
  65. package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
  66. package/src/__tests__/host-file-proxy.test.ts +268 -6
  67. package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
  68. package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
  69. package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
  70. package/src/__tests__/http-user-message-parity.test.ts +107 -1
  71. package/src/__tests__/injector-chain.test.ts +18 -6
  72. package/src/__tests__/injector-disk-pressure.test.ts +224 -0
  73. package/src/__tests__/managed-profile-guard.test.ts +18 -0
  74. package/src/__tests__/mcp-abort-signal.test.ts +130 -0
  75. package/src/__tests__/memory-admin-recall.test.ts +3 -11
  76. package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
  77. package/src/__tests__/normalize-onboarding.test.ts +180 -0
  78. package/src/__tests__/oauth-connect-routes.test.ts +316 -0
  79. package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
  80. package/src/__tests__/onboarding-persona-write.test.ts +308 -0
  81. package/src/__tests__/openai-provider.test.ts +45 -8
  82. package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
  83. package/src/__tests__/platform-callback-registration.test.ts +21 -4
  84. package/src/__tests__/platform.test.ts +2 -1
  85. package/src/__tests__/playbook-execution.test.ts +0 -43
  86. package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
  87. package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
  88. package/src/__tests__/provider-tool-name.test.ts +23 -0
  89. package/src/__tests__/relay-server.test.ts +15 -4
  90. package/src/__tests__/runtime-events-sse.test.ts +4 -8
  91. package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
  92. package/src/__tests__/secret-ingress-http.test.ts +0 -1
  93. package/src/__tests__/suggestion-routes.test.ts +46 -0
  94. package/src/__tests__/twilio-validation.test.ts +2 -2
  95. package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
  96. package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
  97. package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
  98. package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +90 -0
  99. package/src/approvals/guardian-decision-primitive.ts +13 -0
  100. package/src/approvals/guardian-request-resolvers.ts +16 -17
  101. package/src/backup/snapshot-lock.ts +2 -27
  102. package/src/bundler/compiler-tools.ts +3 -2
  103. package/src/calls/call-conversation-messages.ts +46 -10
  104. package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
  105. package/src/cli/commands/bash.ts +35 -108
  106. package/src/cli/commands/contacts.ts +64 -25
  107. package/src/cli/commands/credentials.ts +56 -0
  108. package/src/cli/commands/memory-v2.ts +7 -6
  109. package/src/cli/commands/oauth/__tests__/connect.test.ts +437 -1
  110. package/src/cli/commands/oauth/connect.ts +127 -1
  111. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
  112. package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
  113. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  114. package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
  115. package/src/cli/commands/platform/index.ts +16 -7
  116. package/src/cli/commands/status.ts +57 -0
  117. package/src/cli/program.ts +4 -2
  118. package/src/config/assistant-feature-flags.ts +13 -3
  119. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
  120. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
  121. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
  122. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
  123. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
  124. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
  125. package/src/config/env.ts +0 -8
  126. package/src/config/feature-flag-registry.json +27 -3
  127. package/src/config/loader.ts +127 -8
  128. package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
  129. package/src/config/schemas/call-site-catalog.ts +14 -0
  130. package/src/config/schemas/channels.ts +0 -5
  131. package/src/config/schemas/heartbeat.ts +1 -1
  132. package/src/config/schemas/llm.ts +2 -0
  133. package/src/config/schemas/memory-lifecycle.ts +13 -0
  134. package/src/config/schemas/memory-v2.ts +75 -11
  135. package/src/config/schemas/platform.ts +43 -3
  136. package/src/config/schemas/services.ts +28 -0
  137. package/src/config/seed-inference-profiles.ts +230 -33
  138. package/src/contacts/contact-store.ts +0 -25
  139. package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
  140. package/src/daemon/assistant-attachments.ts +4 -4
  141. package/src/daemon/config-watcher.ts +85 -57
  142. package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
  143. package/src/daemon/conversation-agent-loop.ts +170 -33
  144. package/src/daemon/conversation-error.ts +87 -15
  145. package/src/daemon/conversation-lifecycle.ts +1 -3
  146. package/src/daemon/conversation-process.ts +8 -0
  147. package/src/daemon/conversation-runtime-assembly.ts +26 -0
  148. package/src/daemon/conversation-store.ts +2 -2
  149. package/src/daemon/conversation-surfaces.ts +195 -15
  150. package/src/daemon/conversation-tool-setup.ts +57 -14
  151. package/src/daemon/conversation.ts +17 -22
  152. package/src/daemon/date-context.ts +71 -22
  153. package/src/daemon/disk-pressure-background-gate.ts +73 -0
  154. package/src/daemon/disk-pressure-guard.ts +343 -0
  155. package/src/daemon/disk-pressure-policy.ts +163 -0
  156. package/src/daemon/handlers/shared.ts +0 -1
  157. package/src/daemon/handlers/skills.ts +3 -4
  158. package/src/daemon/host-app-control-proxy.ts +137 -41
  159. package/src/daemon/host-bash-proxy.ts +46 -21
  160. package/src/daemon/host-cu-proxy.ts +49 -3
  161. package/src/daemon/host-file-proxy.ts +43 -7
  162. package/src/daemon/host-transfer-proxy.ts +95 -4
  163. package/src/daemon/lifecycle.ts +79 -28
  164. package/src/daemon/meet-host-supervisor.ts +4 -4
  165. package/src/daemon/meet-manifest-loader.ts +0 -1
  166. package/src/daemon/memory-v2-startup.ts +14 -4
  167. package/src/daemon/message-protocol.ts +3 -0
  168. package/src/daemon/message-types/conversations.ts +4 -0
  169. package/src/daemon/message-types/disk-pressure.ts +9 -0
  170. package/src/daemon/message-types/messages.ts +3 -0
  171. package/src/daemon/profiler-run-store.ts +5 -5
  172. package/src/daemon/tool-setup-types.ts +2 -2
  173. package/src/documents/document-store.ts +85 -0
  174. package/src/filing/filing-service.ts +30 -5
  175. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
  176. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
  177. package/src/heartbeat/heartbeat-run-store.ts +13 -0
  178. package/src/heartbeat/heartbeat-service.ts +205 -31
  179. package/src/home/feed-scheduler.ts +18 -0
  180. package/src/inbound/platform-callback-registration.ts +8 -15
  181. package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
  182. package/src/ipc/assistant-server.ts +56 -2
  183. package/src/ipc/gateway-client.ts +37 -3
  184. package/src/live-voice/live-voice-archive.ts +4 -4
  185. package/src/live-voice/protocol.ts +5 -7
  186. package/src/media/image-service.ts +1 -7
  187. package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
  188. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +52 -22
  189. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
  190. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
  191. package/src/memory/admin.ts +5 -9
  192. package/src/memory/context-search/agent-runner.ts +19 -2
  193. package/src/memory/context-search/sources/conversations.ts +2 -11
  194. package/src/memory/context-search/sources/memory-v2.ts +5 -4
  195. package/src/memory/context-search/sources/memory.ts +0 -1
  196. package/src/memory/context-search/types.ts +0 -1
  197. package/src/memory/conversation-crud.ts +4 -12
  198. package/src/memory/db-init.ts +2 -0
  199. package/src/memory/embedding-runtime-manager.ts +119 -5
  200. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +32 -21
  201. package/src/memory/graph/conversation-graph-memory.ts +42 -54
  202. package/src/memory/graph/extraction.ts +1 -3
  203. package/src/memory/graph/graph-search.test.ts +10 -67
  204. package/src/memory/graph/graph-search.ts +1 -20
  205. package/src/memory/graph/retriever.test.ts +6 -0
  206. package/src/memory/graph/retriever.ts +6 -10
  207. package/src/memory/indexer.ts +54 -45
  208. package/src/memory/job-handlers/backfill.ts +2 -11
  209. package/src/memory/job-handlers/cleanup.ts +43 -0
  210. package/src/memory/job-handlers/embedding.ts +6 -8
  211. package/src/memory/job-handlers/summarization.ts +2 -7
  212. package/src/memory/jobs-store.ts +48 -0
  213. package/src/memory/jobs-worker.ts +81 -43
  214. package/src/memory/memory-v2-activation-log-store.ts +32 -14
  215. package/src/memory/memory-v2-concept-frequency.ts +169 -0
  216. package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
  217. package/src/memory/migrations/index.ts +1 -0
  218. package/src/memory/pkb/pkb-search.test.ts +6 -0
  219. package/src/memory/qdrant-client.ts +0 -13
  220. package/src/memory/rerank-local.ts +374 -0
  221. package/src/memory/search/semantic.ts +6 -67
  222. package/src/memory/trace-event-store.ts +1 -17
  223. package/src/memory/v2/__tests__/activation.test.ts +311 -250
  224. package/src/memory/v2/__tests__/consolidation-job.test.ts +40 -8
  225. package/src/memory/v2/__tests__/injection.test.ts +157 -167
  226. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
  227. package/src/memory/v2/__tests__/qdrant.test.ts +16 -0
  228. package/src/memory/v2/__tests__/reranker.test.ts +338 -0
  229. package/src/memory/v2/__tests__/sim.test.ts +5 -199
  230. package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
  231. package/src/memory/v2/__tests__/static-context.test.ts +76 -1
  232. package/src/memory/v2/activation.ts +149 -156
  233. package/src/memory/v2/consolidation-job.ts +62 -12
  234. package/src/memory/v2/injection.ts +47 -60
  235. package/src/memory/v2/prompts/consolidation.ts +36 -1
  236. package/src/memory/v2/qdrant.ts +99 -0
  237. package/src/memory/v2/reranker.ts +177 -0
  238. package/src/memory/v2/sim.ts +10 -84
  239. package/src/memory/v2/skill-content.ts +4 -3
  240. package/src/memory/v2/skill-store.ts +82 -59
  241. package/src/memory/v2/static-context.ts +22 -0
  242. package/src/memory/v2/types.ts +10 -10
  243. package/src/notifications/copy-composer.ts +13 -0
  244. package/src/notifications/signal.ts +4 -0
  245. package/src/oauth/AGENTS.md +3 -1
  246. package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
  247. package/src/oauth/connect-orchestrator.ts +2 -0
  248. package/src/oauth/connection-resolver.test.ts +66 -1
  249. package/src/oauth/connection-resolver.ts +55 -1
  250. package/src/oauth/oauth-connect-state.ts +77 -0
  251. package/src/oauth/seed-providers.ts +58 -1
  252. package/src/plugins/defaults/injectors.ts +35 -2
  253. package/src/plugins/defaults/memory-retrieval.ts +5 -6
  254. package/src/plugins/types.ts +7 -0
  255. package/src/proactive-artifact/aux-message-injector.ts +74 -0
  256. package/src/proactive-artifact/decision.test.ts +226 -0
  257. package/src/proactive-artifact/decision.ts +165 -0
  258. package/src/proactive-artifact/index.ts +7 -0
  259. package/src/proactive-artifact/job.test.ts +867 -0
  260. package/src/proactive-artifact/job.ts +352 -0
  261. package/src/proactive-artifact/message-copy.ts +41 -0
  262. package/src/proactive-artifact/trigger-state.test.ts +277 -0
  263. package/src/proactive-artifact/trigger-state.ts +119 -0
  264. package/src/prompts/normalize-onboarding.ts +80 -0
  265. package/src/prompts/persona-resolver.ts +101 -9
  266. package/src/prompts/system-prompt.ts +21 -7
  267. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  268. package/src/providers/__tests__/retry-callsite.test.ts +222 -1
  269. package/src/providers/model-intents.ts +7 -0
  270. package/src/providers/openrouter/client.ts +8 -0
  271. package/src/providers/retry.ts +50 -0
  272. package/src/providers/types.ts +1 -0
  273. package/src/runtime/__tests__/agent-wake.test.ts +456 -3
  274. package/src/runtime/agent-wake.ts +238 -100
  275. package/src/runtime/assistant-event-hub.ts +36 -6
  276. package/src/runtime/assistant-event.ts +0 -1
  277. package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
  278. package/src/runtime/auth/route-policy.ts +14 -1
  279. package/src/runtime/auth/same-actor.ts +216 -0
  280. package/src/runtime/channel-retry-sweep.ts +65 -1
  281. package/src/runtime/guardian-reply-router.ts +10 -0
  282. package/src/runtime/local-actor-identity.ts +52 -11
  283. package/src/runtime/pending-interactions.ts +8 -0
  284. package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
  285. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
  286. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  287. package/src/runtime/routes/client-routes.ts +20 -2
  288. package/src/runtime/routes/contact-routes.ts +0 -25
  289. package/src/runtime/routes/conversation-routes.ts +35 -26
  290. package/src/runtime/routes/debug-bash-routes.ts +163 -0
  291. package/src/runtime/routes/disk-pressure-routes.ts +121 -0
  292. package/src/runtime/routes/document-pdf-renderer.ts +6 -2
  293. package/src/runtime/routes/documents-routes.ts +2 -75
  294. package/src/runtime/routes/events-routes.ts +41 -9
  295. package/src/runtime/routes/host-bash-routes.ts +23 -3
  296. package/src/runtime/routes/host-cu-routes.ts +33 -6
  297. package/src/runtime/routes/host-file-routes.ts +32 -6
  298. package/src/runtime/routes/host-transfer-routes.ts +79 -16
  299. package/src/runtime/routes/identity-routes.ts +7 -138
  300. package/src/runtime/routes/inbound-message-handler.ts +77 -12
  301. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -0
  302. package/src/runtime/routes/index.ts +6 -0
  303. package/src/runtime/routes/memory-item-routes.test.ts +41 -15
  304. package/src/runtime/routes/memory-v2-routes.ts +33 -0
  305. package/src/runtime/routes/oauth-connect-routes.ts +153 -0
  306. package/src/runtime/verification-outbound-actions.ts +4 -4
  307. package/src/schedule/run-script.ts +37 -5
  308. package/src/schedule/scheduler.ts +20 -1
  309. package/src/security/encrypted-store.ts +2 -0
  310. package/src/security/secure-keys.ts +55 -0
  311. package/src/skills/remote-skill-policy.ts +4 -10
  312. package/src/subagent/index.ts +1 -7
  313. package/src/subagent/manager.ts +1 -15
  314. package/src/tasks/task-runner.ts +0 -1
  315. package/src/tasks/task-store.ts +0 -3
  316. package/src/tools/background-tool-registry.ts +17 -3
  317. package/src/tools/host-filesystem/edit.test.ts +151 -0
  318. package/src/tools/host-filesystem/edit.ts +43 -1
  319. package/src/tools/host-filesystem/read.test.ts +129 -0
  320. package/src/tools/host-filesystem/read.ts +43 -1
  321. package/src/tools/host-filesystem/transfer.test.ts +127 -2
  322. package/src/tools/host-filesystem/transfer.ts +56 -11
  323. package/src/tools/host-filesystem/write.test.ts +134 -0
  324. package/src/tools/host-filesystem/write.ts +43 -1
  325. package/src/tools/host-terminal/host-shell.ts +13 -6
  326. package/src/tools/mcp/mcp-tool-factory.ts +2 -1
  327. package/src/tools/memory/register.test.ts +12 -9
  328. package/src/tools/memory/register.ts +1 -2
  329. package/src/tools/provider-tool-name.ts +28 -0
  330. package/src/tools/registry.ts +30 -9
  331. package/src/tools/terminal/shell.ts +9 -1
  332. package/src/tools/tool-approval-handler.ts +31 -6
  333. package/src/tools/types.ts +24 -2
  334. package/src/tts/provider-catalog.ts +3 -5
  335. package/src/util/disk-usage.ts +138 -0
  336. package/src/util/platform.ts +21 -11
  337. package/src/util/process-liveness.ts +26 -0
  338. package/src/workspace/heartbeat-service.ts +19 -0
  339. package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
  340. package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
  341. package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +72 -0
  342. package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
  343. package/src/workspace/migrations/registry.ts +8 -0
  344. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
  345. package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
  346. package/src/memory/v2/skill-qdrant.ts +0 -404
  347. package/src/signals/bash.ts +0 -198
@@ -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
- }
@@ -59,6 +59,7 @@ import { INTERNAL_GUARDIAN_TRUST_CONTEXT } from "../../daemon/trust-context.js";
59
59
  import { wakeAgentForOpportunity } from "../../runtime/agent-wake.js";
60
60
  import { getLogger } from "../../util/logger.js";
61
61
  import { getWorkspaceDir } from "../../util/platform.js";
62
+ import { isProcessAlive } from "../../util/process-liveness.js";
62
63
  import { bootstrapConversation } from "../conversation-bootstrap.js";
63
64
  import { deleteConversation } from "../conversation-crud.js";
64
65
  import {
@@ -240,14 +241,20 @@ function readBufferContent(bufferPath: string): string {
240
241
  /**
241
242
  * Atomically create the lock file with `wx` (O_CREAT | O_EXCL) flags. Returns
242
243
  * `null` on success, or the current holder string (file contents, typically
243
- * `pid timestamp`) when the file already exists the holder is surfaced for
244
- * log diagnostics so operators can identify a stuck lock without re-reading.
244
+ * `pid timestamp`) when the file already exists and the holder is still alive.
245
245
  *
246
- * Crash recovery: if the prior daemon died with the lock held, the file will
247
- * still be on disk on the next start. PR 20 keeps the lock simple per the
248
- * plan instructions; a future iteration can probe liveness via `kill(pid, 0)`
249
- * the way `snapshot-lock.ts` does. Until then, an operator can clear a
250
- * stale lock by removing the file.
246
+ * Stale-lock takeover: if the file exists but its holder PID is not running,
247
+ * unlink the stale file and retry the create exactly once. This recovers
248
+ * automatically from a crashed daemon that died with the lock held —
249
+ * otherwise every subsequent scheduled consolidation would skip with `locked`
250
+ * indefinitely until an operator manually removed the file.
251
+ *
252
+ * The simple takeover-then-retry is safe here (unlike `snapshot-lock.ts`'s
253
+ * full rename-aside dance) because only the assistant's jobs worker calls
254
+ * this lock, and at most one assistant process runs per workspace at any
255
+ * time. A holder with an unparseable / empty payload is treated as stale —
256
+ * the only writers ever produce a `<pid> <timestamp>` line, so an
257
+ * unparseable file is corruption from a partial write that crashed.
251
258
  */
252
259
  function tryAcquireLock(lockPath: string): string | null {
253
260
  // The workspace migration seeds `memory/.v2-state/`, but tests and
@@ -255,9 +262,40 @@ function tryAcquireLock(lockPath: string): string | null {
255
262
  // is idempotent, so the call is cheap when the dir already exists.
256
263
  mkdirSync(dirname(lockPath), { recursive: true });
257
264
 
265
+ const firstHolder = tryCreate(lockPath);
266
+ if (firstHolder === null) return null;
267
+ if (!isHolderStale(firstHolder)) return firstHolder;
268
+
269
+ log.info(
270
+ { lockPath, holder: firstHolder },
271
+ "consolidation: taking over stale lock (holder not running)",
272
+ );
273
+ try {
274
+ unlinkSync(lockPath);
275
+ } catch (err) {
276
+ const code = (err as NodeJS.ErrnoException).code;
277
+ if (code !== "ENOENT") {
278
+ log.warn(
279
+ { err, lockPath },
280
+ "consolidation: failed to unlink stale lock; reporting as locked",
281
+ );
282
+ return firstHolder;
283
+ }
284
+ }
285
+ // After unlink, the next `wx` create should succeed. If a third party
286
+ // raced in and re-acquired (vanishingly unlikely with one writer per
287
+ // workspace), surface their holder string rather than overwriting.
288
+ return tryCreate(lockPath);
289
+ }
290
+
291
+ /**
292
+ * Atomically create the lock file. Returns `null` on success, or the holder
293
+ * string read from the file when it already exists (`"unknown"` if the read
294
+ * itself fails). Rethrows any non-EEXIST errno from `openSync`.
295
+ */
296
+ function tryCreate(lockPath: string): string | null {
258
297
  let fd: number;
259
298
  try {
260
- // `wx` = create-if-not-exists, fail with EEXIST if it does.
261
299
  fd = openSync(lockPath, "wx");
262
300
  } catch (err) {
263
301
  if ((err as NodeJS.ErrnoException).code !== "EEXIST") throw err;
@@ -267,13 +305,10 @@ function tryAcquireLock(lockPath: string): string | null {
267
305
  return "unknown";
268
306
  }
269
307
  }
270
-
271
- // Best-effort PID + timestamp payload so a stale lock can be diagnosed.
272
- // The worker only cares that the file exists; the contents are advisory.
273
308
  try {
274
309
  writeSync(fd, `${process.pid} ${Date.now()}\n`);
275
310
  } catch {
276
- // best-effort
311
+ // best-effort — payload is advisory, the file's existence is the lock
277
312
  } finally {
278
313
  try {
279
314
  closeSync(fd);
@@ -284,6 +319,21 @@ function tryAcquireLock(lockPath: string): string | null {
284
319
  return null;
285
320
  }
286
321
 
322
+ /**
323
+ * A holder string is stale when its PID parses to a non-running process.
324
+ * The payload format is `<pid> <timestamp>` (see `tryCreate`'s write), but
325
+ * an unparseable / empty / `"unknown"` payload is also treated as stale:
326
+ * the only writer is `tryCreate` itself, so corruption indicates a partial
327
+ * write from a crashed prior holder rather than a live writer mid-flush.
328
+ */
329
+ function isHolderStale(holder: string): boolean {
330
+ const match = /^\d+/.exec(holder);
331
+ if (!match) return true;
332
+ const pid = Number.parseInt(match[0], 10);
333
+ if (!Number.isFinite(pid) || pid <= 0) return true;
334
+ return !isProcessAlive(pid);
335
+ }
336
+
287
337
  /**
288
338
  * Idempotent unlink of the lock file. Called from the `finally` block so a
289
339
  * crash in the wake path doesn't leave the lock stranded. ENOENT is swallowed