@vellumai/assistant 0.9.0 → 0.10.0-staging.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 (572) hide show
  1. package/ARCHITECTURE.md +18 -34
  2. package/bun.lock +7 -8
  3. package/docs/activation-funnel-telemetry.md +28 -22
  4. package/docs/architecture/security.md +29 -28
  5. package/docs/stt-provider-onboarding.md +3 -5
  6. package/docs/workflows-testing.md +13 -44
  7. package/docs/workflows.md +3 -5
  8. package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
  9. package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
  10. package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
  11. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
  12. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  13. package/node_modules/@vellumai/gateway-client/src/index.ts +32 -6
  14. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
  15. package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
  16. package/openapi.yaml +976 -63
  17. package/package.json +2 -1
  18. package/scripts/sync-llm-catalog.ts +6 -15
  19. package/scripts/sync-web-search-catalog.ts +3 -11
  20. package/src/__tests__/access-request-card-view.test.ts +98 -0
  21. package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
  22. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +72 -32
  23. package/src/__tests__/agent-loop-compaction-strip.test.ts +241 -0
  24. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
  25. package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
  26. package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
  27. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
  28. package/src/__tests__/app-compiler.test.ts +15 -1
  29. package/src/__tests__/app-dir-path-guard.test.ts +0 -1
  30. package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
  31. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
  32. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  33. package/src/__tests__/avatar-identity-sync.test.ts +2 -27
  34. package/src/__tests__/btw-routes.test.ts +6 -8
  35. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  36. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  37. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  38. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  39. package/src/__tests__/checker.test.ts +0 -3
  40. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  41. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  42. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  43. package/src/__tests__/config-loader-backfill.test.ts +268 -27
  44. package/src/__tests__/config-schema.test.ts +35 -0
  45. package/src/__tests__/config-watcher.test.ts +0 -18
  46. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  47. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  48. package/src/__tests__/contacts-tools.test.ts +29 -0
  49. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
  50. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  51. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  52. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  53. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  54. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  55. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  56. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  57. package/src/__tests__/conversation-title-service.test.ts +62 -0
  58. package/src/__tests__/credential-broker.test.ts +449 -1
  59. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  60. package/src/__tests__/credential-execution-tools.test.ts +0 -1
  61. package/src/__tests__/credential-prompt-route.test.ts +4 -4
  62. package/src/__tests__/credential-routes.test.ts +360 -0
  63. package/src/__tests__/credential-security-invariants.test.ts +4 -13
  64. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  65. package/src/__tests__/disk-usage.test.ts +65 -0
  66. package/src/__tests__/dynamic-page-surface.test.ts +152 -1
  67. package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
  68. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  69. package/src/__tests__/gateway-only-guard.test.ts +3 -7
  70. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  71. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  72. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  73. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  74. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  75. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  76. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  77. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  78. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  79. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  80. package/src/__tests__/identity-routes.test.ts +0 -189
  81. package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
  82. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  83. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  84. package/src/__tests__/invite-redemption-service.test.ts +4 -7
  85. package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
  86. package/src/__tests__/llm-catalog-parity.test.ts +30 -23
  87. package/src/__tests__/llm-resolver.test.ts +70 -24
  88. package/src/__tests__/llm-schema.test.ts +1 -0
  89. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  90. package/src/__tests__/mcp-health-check.test.ts +6 -7
  91. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  92. package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
  93. package/src/__tests__/onboarding-persona-write.test.ts +1 -1
  94. package/src/__tests__/path-policy.test.ts +34 -0
  95. package/src/__tests__/persona-resolver.test.ts +49 -14
  96. package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
  97. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  98. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  99. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  100. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  101. package/src/__tests__/reaction-persistence.test.ts +150 -29
  102. package/src/__tests__/registry.test.ts +2 -7
  103. package/src/__tests__/relay-server.test.ts +285 -0
  104. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  105. package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
  106. package/src/__tests__/schedule-routes.test.ts +0 -30
  107. package/src/__tests__/schedule-tools.test.ts +2 -18
  108. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  109. package/src/__tests__/skill-execute-input.test.ts +51 -1
  110. package/src/__tests__/skill-runtime-path.test.ts +2 -3
  111. package/src/__tests__/skills.test.ts +51 -0
  112. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  113. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  114. package/src/__tests__/subagent-tools.test.ts +266 -0
  115. package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
  116. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  117. package/src/__tests__/title-generate-hook.test.ts +100 -3
  118. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
  119. package/src/__tests__/token-manager.test.ts +519 -0
  120. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  121. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  122. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  123. package/src/__tests__/tool-executor.test.ts +0 -79
  124. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  125. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  126. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
  127. package/src/__tests__/trusted-contact-verification.test.ts +8 -10
  128. package/src/__tests__/twilio-routes.test.ts +81 -1
  129. package/src/__tests__/voice-invite-redemption.test.ts +2 -3
  130. package/src/__tests__/weak-open-model.test.ts +30 -0
  131. package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
  132. package/src/__tests__/workspace-greetings.test.ts +152 -0
  133. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  134. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  135. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  136. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  137. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  138. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  139. package/src/agent/loop.ts +49 -29
  140. package/src/api/README.md +6 -6
  141. package/src/api/events/tool-result.ts +6 -0
  142. package/src/api/events/workflow-completed.ts +53 -0
  143. package/src/api/events/workflow-leaf-finished.ts +38 -0
  144. package/src/api/events/workflow-leaf-started.ts +35 -0
  145. package/src/api/events/workflow-progress.ts +32 -0
  146. package/src/api/events/workflow-started.ts +31 -0
  147. package/src/api/index.ts +40 -0
  148. package/src/api/responses/conversation-message.ts +28 -4
  149. package/src/api/responses/home.ts +26 -4
  150. package/src/api/responses/workflow-journal.ts +53 -0
  151. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  152. package/src/approvals/guardian-decision-primitive.ts +26 -3
  153. package/src/approvals/guardian-request-resolvers.ts +183 -80
  154. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  155. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  156. package/src/calls/call-pointer-messages.ts +10 -4
  157. package/src/calls/channel-admission-reader.ts +104 -0
  158. package/src/calls/guardian-dispatch.ts +17 -45
  159. package/src/calls/media-stream-server.ts +84 -2
  160. package/src/calls/relay-access-wait.ts +1 -1
  161. package/src/calls/relay-server.ts +66 -0
  162. package/src/calls/relay-setup-router.ts +82 -1
  163. package/src/calls/twilio-routes.ts +17 -8
  164. package/src/calls/voice-session-bridge.ts +2 -2
  165. package/src/cli/commands/clients.ts +3 -0
  166. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  167. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  168. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  169. package/src/cli/commands/memory/index.ts +30 -0
  170. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  171. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  172. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  173. package/src/cli/commands/oauth/status.test.ts +36 -0
  174. package/src/cli/commands/oauth/status.ts +23 -3
  175. package/src/cli/commands/plugins.ts +197 -4
  176. package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
  177. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  178. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
  179. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  180. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
  181. package/src/cli/lib/diff-plugin.ts +346 -0
  182. package/src/cli/lib/inspect-plugin.ts +12 -1
  183. package/src/cli/lib/install-from-github.ts +105 -17
  184. package/src/cli/lib/merge-plugin-tree.ts +328 -0
  185. package/src/cli/lib/plugin-fingerprint.ts +14 -0
  186. package/src/cli/lib/plugin-surfaces.ts +104 -0
  187. package/src/cli/lib/upgrade-plugin.ts +298 -10
  188. package/src/cli/program.ts +2 -6
  189. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  190. package/src/config/assistant-feature-flags.ts +22 -7
  191. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  192. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  193. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  194. package/src/config/bundled-skills/subagent/SKILL.md +4 -0
  195. package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
  196. package/src/config/bundled-skills/workflows/SKILL.md +14 -8
  197. package/src/config/bundled-tool-registry.ts +2 -7
  198. package/src/config/call-site-defaults.ts +15 -2
  199. package/src/config/feature-flag-registry.json +46 -31
  200. package/src/config/inference-profile-validation.ts +26 -0
  201. package/src/config/llm-resolver.ts +3 -0
  202. package/src/config/loader.ts +4 -0
  203. package/src/config/memory-v3-gate.ts +11 -0
  204. package/src/config/profile-order.ts +28 -0
  205. package/src/config/schema.ts +8 -6
  206. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  207. package/src/config/schemas/call-site-catalog.ts +7 -0
  208. package/src/config/schemas/channels.ts +11 -0
  209. package/src/config/schemas/elevenlabs.ts +0 -1
  210. package/src/config/schemas/llm.ts +31 -0
  211. package/src/config/schemas/memory-lifecycle.ts +3 -7
  212. package/src/config/schemas/memory-v3.ts +6 -0
  213. package/src/config/schemas/platform.ts +0 -8
  214. package/src/config/schemas/services.ts +18 -0
  215. package/src/config/seed-inference-profiles.ts +109 -44
  216. package/src/config/skills.ts +21 -0
  217. package/src/config/sync-gated-profiles.ts +220 -0
  218. package/src/contacts/contact-store.ts +89 -106
  219. package/src/contacts/contacts-write.ts +5 -22
  220. package/src/contacts/types.ts +0 -1
  221. package/src/context/compactor.ts +88 -54
  222. package/src/context/strip-injections.ts +58 -10
  223. package/src/context/token-estimator.ts +1 -1
  224. package/src/credential-execution/process-manager.ts +55 -14
  225. package/src/credential-execution/prompted-credential.ts +2 -3
  226. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  227. package/src/daemon/config-watcher.ts +0 -4
  228. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  229. package/src/daemon/conversation-agent-loop.ts +114 -22
  230. package/src/daemon/conversation-history.ts +1 -1
  231. package/src/daemon/conversation-lifecycle.ts +3 -5
  232. package/src/daemon/conversation-process.ts +13 -5
  233. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  234. package/src/daemon/conversation-slash.ts +2 -23
  235. package/src/daemon/conversation-surfaces.ts +26 -0
  236. package/src/daemon/conversation-tool-setup.ts +27 -14
  237. package/src/daemon/conversation.ts +66 -14
  238. package/src/daemon/disk-pressure-policy.ts +5 -3
  239. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  240. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  241. package/src/daemon/handlers/config-a2a.ts +0 -2
  242. package/src/daemon/handlers/config-channels.ts +15 -16
  243. package/src/daemon/handlers/config-slack-channel.ts +22 -3
  244. package/src/daemon/handlers/conversations.ts +107 -0
  245. package/src/daemon/host-browser-proxy.ts +41 -0
  246. package/src/daemon/lifecycle.ts +55 -27
  247. package/src/daemon/message-provenance.ts +2 -0
  248. package/src/daemon/message-types/contacts.ts +0 -1
  249. package/src/daemon/message-types/conversations.ts +3 -3
  250. package/src/daemon/message-types/sync.ts +0 -1
  251. package/src/daemon/message-types/web-activity.ts +7 -1
  252. package/src/daemon/message-types/workflows.ts +83 -1
  253. package/src/daemon/orphan-reaper.test.ts +0 -19
  254. package/src/daemon/orphan-reaper.ts +2 -24
  255. package/src/daemon/server.ts +0 -10
  256. package/src/daemon/tool-setup-types.ts +4 -0
  257. package/src/daemon/trust-context.ts +1 -1
  258. package/src/events/tool-audit-listener.ts +2 -2
  259. package/src/home/feed-source-enrichment.test.ts +151 -0
  260. package/src/home/feed-source-enrichment.ts +176 -0
  261. package/src/home/relationship-state.ts +2 -4
  262. package/src/instrument.ts +18 -6
  263. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  264. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  265. package/src/ipc/assistant-server.ts +37 -4
  266. package/src/ipc/gateway-flag-listener.ts +18 -2
  267. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  268. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  269. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  270. package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
  271. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  272. package/src/memory/auth-fallback-events-store.ts +2 -2
  273. package/src/memory/auto-analysis-enqueue.ts +3 -5
  274. package/src/memory/bookmark-crud.ts +1 -2
  275. package/src/memory/canonical-guardian-store.ts +39 -1
  276. package/src/memory/conversation-crud.ts +9 -4
  277. package/src/memory/conversation-key-store.ts +17 -2
  278. package/src/memory/conversation-title-service.ts +64 -7
  279. package/src/memory/db-init.ts +17 -17
  280. package/src/memory/embedding-backend.ts +38 -1
  281. package/src/memory/embedding-billing-breaker.ts +96 -0
  282. package/src/memory/jobs-store.ts +25 -13
  283. package/src/memory/jobs-worker.ts +54 -1
  284. package/src/memory/lifecycle-events-store.ts +2 -2
  285. package/src/memory/memory-retrospective-constants.ts +4 -4
  286. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  287. package/src/memory/memory-retrospective-job.ts +28 -227
  288. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  289. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  290. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  291. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
  292. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  293. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  294. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  295. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  296. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  297. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  298. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  299. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  300. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  301. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
  302. package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
  303. package/src/memory/migrations/index.ts +6 -0
  304. package/src/memory/migrations/run-migrations.ts +41 -0
  305. package/src/memory/migrations/validate-migration-state.ts +1 -1
  306. package/src/memory/onboarding-events-store.ts +3 -3
  307. package/src/memory/schema/contacts.ts +0 -5
  308. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  309. package/src/memory/skill-loaded-events-store.ts +2 -2
  310. package/src/memory/tool-executed-events-store.test.ts +7 -7
  311. package/src/memory/turn-trace-store.test.ts +736 -0
  312. package/src/memory/turn-trace-store.ts +364 -0
  313. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  314. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  315. package/src/memory/v2/consolidation-job.ts +2 -2
  316. package/src/memory/v2/skill-content.ts +25 -7
  317. package/src/memory/v2/skill-store.ts +7 -1
  318. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  319. package/src/memory/v3-eval/eval-packets.ts +546 -0
  320. package/src/messaging/providers/slack/adapter.ts +1 -1
  321. package/src/messaging/providers/slack/api.ts +31 -0
  322. package/src/messaging/providers/slack/send.test.ts +114 -2
  323. package/src/messaging/providers/slack/send.ts +30 -7
  324. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  325. package/src/messaging/providers/slack/withdraw.ts +161 -0
  326. package/src/notifications/AGENTS.md +2 -0
  327. package/src/notifications/access-request-copy.ts +72 -59
  328. package/src/notifications/adapters/shared.ts +29 -0
  329. package/src/notifications/adapters/slack.ts +58 -103
  330. package/src/notifications/adapters/telegram.ts +2 -20
  331. package/src/notifications/approval-card-data.ts +333 -0
  332. package/src/notifications/broadcaster.ts +16 -3
  333. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  334. package/src/notifications/copy-composer.ts +3 -3
  335. package/src/notifications/decision-engine.ts +4 -2
  336. package/src/notifications/destination-resolver.ts +4 -6
  337. package/src/notifications/guardian-question-mode.ts +10 -0
  338. package/src/notifications/home-feed-side-effect.ts +7 -16
  339. package/src/notifications/notification-utils.ts +19 -20
  340. package/src/notifications/signal.ts +79 -43
  341. package/src/notifications/types.ts +98 -121
  342. package/src/oauth/AGENTS.md +5 -24
  343. package/src/permissions/checker.test.ts +51 -0
  344. package/src/permissions/checker.ts +185 -26
  345. package/src/permissions/ipc-risk-types.ts +24 -0
  346. package/src/permissions/question-prompter.test.ts +27 -0
  347. package/src/permissions/question-prompter.ts +4 -0
  348. package/src/platform/client.test.ts +119 -0
  349. package/src/platform/client.ts +66 -0
  350. package/src/platform/consent-cache.test.ts +267 -0
  351. package/src/platform/consent-cache.ts +174 -0
  352. package/src/plugin-api/constants.ts +1 -1
  353. package/src/plugin-api/index.ts +33 -1
  354. package/src/plugin-api/model-profiles.ts +33 -0
  355. package/src/plugin-api/types.ts +50 -2
  356. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  357. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  358. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  359. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  360. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  361. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  362. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  363. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  364. package/src/plugins/defaults/advisor/config.ts +21 -0
  365. package/src/plugins/defaults/advisor/consult.ts +93 -0
  366. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  367. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  368. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  369. package/src/plugins/defaults/advisor/package.json +14 -0
  370. package/src/plugins/defaults/advisor/steering.ts +67 -0
  371. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  372. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  373. package/src/plugins/defaults/index.ts +60 -0
  374. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  375. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  376. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  377. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  378. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  379. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  380. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
  381. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  382. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  383. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  384. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  385. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
  386. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  387. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  388. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  389. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
  390. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
  391. package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
  392. package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
  393. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
  394. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  395. package/src/prompts/persona-resolver.ts +14 -4
  396. package/src/prompts/templates/system-sections.ts +7 -2
  397. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  398. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  399. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  400. package/src/providers/atlascloud/client.ts +85 -0
  401. package/src/providers/fetch-provider-catalog.ts +85 -0
  402. package/src/providers/inference/adapter-factory.ts +3 -0
  403. package/src/providers/model-catalog.ts +58 -0
  404. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  405. package/src/providers/openai/chat-completions-provider.ts +7 -0
  406. package/src/providers/openai/responses-provider.ts +10 -0
  407. package/src/providers/provider-send-message.ts +11 -3
  408. package/src/providers/retry.ts +53 -12
  409. package/src/providers/search-provider-catalog.ts +10 -0
  410. package/src/providers/weak-open-model.ts +22 -0
  411. package/src/runtime/AGENTS.md +0 -1
  412. package/src/runtime/__tests__/agent-wake.test.ts +181 -0
  413. package/src/runtime/__tests__/client-health.test.ts +44 -0
  414. package/src/runtime/access-request-helper.ts +21 -53
  415. package/src/runtime/actor-trust-resolver.ts +59 -63
  416. package/src/runtime/agent-wake.ts +52 -0
  417. package/src/runtime/assistant-event-hub.ts +18 -4
  418. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  419. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  420. package/src/runtime/btw-sidechain.ts +3 -6
  421. package/src/runtime/capabilities.test.ts +120 -0
  422. package/src/runtime/capabilities.ts +197 -0
  423. package/src/runtime/channel-approval-types.ts +22 -45
  424. package/src/runtime/channel-invite-transports/telegram.ts +4 -4
  425. package/src/runtime/channel-retry-sweep.ts +1 -0
  426. package/src/runtime/channel-verification-service.ts +3 -3
  427. package/src/runtime/client-health.ts +26 -0
  428. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  429. package/src/runtime/effective-capabilities.test.ts +128 -0
  430. package/src/runtime/effective-capabilities.ts +84 -0
  431. package/src/runtime/guardian-reply-router.ts +106 -21
  432. package/src/runtime/invite-redemption-service.ts +9 -25
  433. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
  434. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  435. package/src/runtime/pending-interactions.ts +15 -0
  436. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  437. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  438. package/src/runtime/routes/__tests__/plugins-routes.test.ts +240 -1
  439. package/src/runtime/routes/app-routes.ts +1 -1
  440. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
  441. package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
  442. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  443. package/src/runtime/routes/btw-routes.ts +1 -27
  444. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  445. package/src/runtime/routes/client-routes.ts +10 -0
  446. package/src/runtime/routes/contact-routes.ts +31 -8
  447. package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
  448. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  449. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  450. package/src/runtime/routes/conversation-routes.ts +39 -14
  451. package/src/runtime/routes/credential-routes.ts +40 -16
  452. package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
  453. package/src/runtime/routes/events-routes.ts +1 -3
  454. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  455. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  456. package/src/runtime/routes/home-feed-routes.ts +8 -3
  457. package/src/runtime/routes/identity-routes.ts +1 -296
  458. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  459. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
  460. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  461. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  462. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  463. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  464. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  465. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  466. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  467. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  468. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  469. package/src/runtime/routes/index.ts +2 -0
  470. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  471. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  472. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  473. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  474. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  475. package/src/runtime/routes/notification-routes.ts +122 -133
  476. package/src/runtime/routes/platform-routes.ts +2 -2
  477. package/src/runtime/routes/plugins-routes.ts +202 -3
  478. package/src/runtime/routes/schedule-routes.ts +0 -22
  479. package/src/runtime/routes/secret-routes.ts +10 -0
  480. package/src/runtime/routes/surface-action-routes.ts +2 -1
  481. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  482. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  483. package/src/runtime/routes/workflow-routes.test.ts +229 -44
  484. package/src/runtime/routes/workflow-routes.ts +131 -29
  485. package/src/runtime/routes/workspace-greetings.ts +55 -0
  486. package/src/runtime/sync/resource-sync-events.ts +1 -11
  487. package/src/runtime/tool-grant-request-helper.ts +18 -16
  488. package/src/runtime/trust-context-resolver.ts +8 -5
  489. package/src/schedule/inference-profile.ts +2 -14
  490. package/src/schedule/schedule-store.ts +1 -1
  491. package/src/schedule/scheduler-types.ts +5 -1
  492. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  493. package/src/security/secret-patterns.ts +3 -0
  494. package/src/subagent/manager.ts +17 -4
  495. package/src/subagent/types.ts +6 -0
  496. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  497. package/src/telemetry/trace-collection-policy.ts +30 -0
  498. package/src/telemetry/types.ts +89 -0
  499. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  500. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  501. package/src/tools/AGENTS.md +3 -3
  502. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  503. package/src/tools/browser/browser-execution.ts +30 -19
  504. package/src/tools/document/document-tool.ts +2 -3
  505. package/src/tools/executor.ts +5 -3
  506. package/src/tools/host-terminal/host-shell.ts +5 -4
  507. package/src/tools/memory/register.ts +2 -2
  508. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  509. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  510. package/src/tools/network/web-fetch.ts +372 -1
  511. package/src/tools/network/web-search-error.ts +1 -1
  512. package/src/tools/network/web-search.ts +213 -10
  513. package/src/tools/permission-checker.ts +4 -3
  514. package/src/tools/registry.ts +20 -0
  515. package/src/tools/schedule/create.ts +7 -12
  516. package/src/tools/schedule/update.ts +4 -11
  517. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  518. package/src/tools/side-effects.ts +2 -17
  519. package/src/tools/skills/execute.ts +33 -0
  520. package/src/tools/subagent/spawn.ts +61 -12
  521. package/src/tools/terminal/shell.ts +10 -4
  522. package/src/tools/tool-approval-handler.ts +18 -13
  523. package/src/tools/tool-manifest.ts +0 -2
  524. package/src/tools/types.ts +9 -0
  525. package/src/tools/ui-surface/definitions.ts +64 -3
  526. package/src/tools/verification-control-plane-policy.ts +3 -1
  527. package/src/tools/workflows/run-workflow.test.ts +8 -18
  528. package/src/tools/workflows/run-workflow.ts +1 -0
  529. package/src/util/disk-usage.ts +78 -23
  530. package/src/util/platform.ts +10 -3
  531. package/src/watcher/telemetry.ts +2 -2
  532. package/src/workflows/capabilities.ts +2 -3
  533. package/src/workflows/engine.test.ts +175 -1
  534. package/src/workflows/engine.ts +82 -0
  535. package/src/workflows/journal-store.test.ts +70 -0
  536. package/src/workflows/journal-store.ts +18 -3
  537. package/src/workflows/run-manager.test.ts +171 -28
  538. package/src/workflows/run-manager.ts +66 -24
  539. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  540. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  541. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  542. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  543. package/src/workspace/migrations/registry.ts +8 -0
  544. package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
  545. package/src/__tests__/credential-security-e2e.test.ts +0 -362
  546. package/src/__tests__/credential-vault-unit.test.ts +0 -1528
  547. package/src/__tests__/credential-vault.test.ts +0 -1706
  548. package/src/__tests__/identity-intro-cache.test.ts +0 -315
  549. package/src/__tests__/secret-onetime-send.test.ts +0 -182
  550. package/src/cli/commands/__tests__/task.test.ts +0 -914
  551. package/src/cli/commands/task.ts +0 -771
  552. package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
  553. package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
  554. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
  555. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
  556. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
  557. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
  558. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
  559. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
  560. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
  561. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
  562. package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
  563. package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
  564. package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
  565. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
  566. package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
  567. package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
  568. package/src/memory/preloaded-apps.ts +0 -116
  569. package/src/notifications/tool-approval-copy.ts +0 -142
  570. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
  571. package/src/runtime/routes/identity-intro-cache.ts +0 -172
  572. package/src/tools/credentials/vault.ts +0 -712
@@ -0,0 +1,546 @@
1
+ /**
2
+ * Memory-v3 corpus eval: build blinded A/B judge packets comparing retrieval
3
+ * over two on-disk concept corpora (e.g. a pre-migration v2 snapshot vs a staged
4
+ * v3 wiki) on the same mined historical turns.
5
+ *
6
+ * The two corpora are a pure reorganization of the same knowledge (the staged
7
+ * wiki is authored FROM the snapshot), so the comparison measures retrieval
8
+ * *shape*, not knowledge — no per-turn time-gating is needed: both sides hold
9
+ * the same information, so a page encoding post-turn knowledge is reachable in
10
+ * both and cannot bias one side. The blind judge (a separate workflow) scores
11
+ * each packet on coverage; this module only produces the packets.
12
+ *
13
+ * Retrieval mirrors the live section-lane engine's cheap lanes: a BM25F needle
14
+ * over sections, optionally unioned with dense cosine over section embeddings.
15
+ * Everything runs in memory — no Qdrant writes, no live-lane mutation — which is
16
+ * why it is safe to run against arbitrary staging/snapshot directories.
17
+ */
18
+
19
+ import {
20
+ existsSync,
21
+ mkdirSync,
22
+ readdirSync,
23
+ readFileSync,
24
+ writeFileSync,
25
+ } from "node:fs";
26
+ import { join, resolve, sep } from "node:path";
27
+
28
+ import { and, desc, eq, sql } from "drizzle-orm";
29
+
30
+ import type { AssistantConfig } from "../../config/types.js";
31
+ import { renderCard } from "../../plugins/defaults/memory-v3-shadow/card.js";
32
+ import { buildSectionNeedle } from "../../plugins/defaults/memory-v3-shadow/section-needle.js";
33
+ import { buildSectionIndex } from "../../plugins/defaults/memory-v3-shadow/sections.js";
34
+ import type {
35
+ SectionIndex,
36
+ Slug,
37
+ } from "../../plugins/defaults/memory-v3-shadow/types.js";
38
+ import {
39
+ FRONTMATTER_REGEX,
40
+ parseFrontmatterFields,
41
+ } from "../../skills/frontmatter.js";
42
+ import type { getDb } from "../db-connection.js";
43
+ import { embedWithRetry } from "../embed.js";
44
+ import { stringifyMessageContent } from "../message-content.js";
45
+ import { conversations, messages } from "../schema.js";
46
+ import { injectedConceptHeader } from "../v2/injected-block-slugs.js";
47
+ import { slugFromConceptPath } from "../v2/page-store.js";
48
+
49
+ type DrizzleDb = ReturnType<typeof getDb>;
50
+
51
+ /** A text-embedding function returning one vector per input, in order. */
52
+ export type EmbedAll = (texts: string[]) => Promise<number[][]>;
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // Corpus loading (arbitrary directory of `.md` concept pages)
56
+ // ---------------------------------------------------------------------------
57
+
58
+ export interface Corpus {
59
+ /** Flat slugs (path under `dir`, minus `.md`, forward-slashed). */
60
+ slugs: Slug[];
61
+ /** slug -> full file text (frontmatter included; the card renderer strips it). */
62
+ rawBySlug: Map<Slug, string>;
63
+ /** slug -> frontmatter-stripped body (what the section index chunks). */
64
+ bodyBySlug: Map<Slug, string>;
65
+ }
66
+
67
+ /** Read every `.md` page under `dir` into a {@link Corpus}. */
68
+ export function loadCorpus(dir: string): Corpus {
69
+ if (!existsSync(dir)) {
70
+ throw new Error(`corpus directory does not exist: ${dir}`);
71
+ }
72
+ const slugs: Slug[] = [];
73
+ const rawBySlug = new Map<Slug, string>();
74
+ const bodyBySlug = new Map<Slug, string>();
75
+
76
+ for (const rel of readdirSync(dir, { recursive: true })) {
77
+ const relPath = typeof rel === "string" ? rel : String(rel);
78
+ if (!relPath.endsWith(".md")) continue;
79
+ const full = join(dir, relPath);
80
+ const raw = readFileSync(full, "utf8");
81
+ const slug = slugFromConceptPath(dir, full);
82
+ const parsed = parseFrontmatterFields(raw);
83
+ const body = parsed ? parsed.body : raw.replace(FRONTMATTER_REGEX, "");
84
+ slugs.push(slug);
85
+ rawBySlug.set(slug, raw);
86
+ bodyBySlug.set(slug, body);
87
+ }
88
+ return { slugs, rawBySlug, bodyBySlug };
89
+ }
90
+
91
+ // ---------------------------------------------------------------------------
92
+ // Vector helpers
93
+ // ---------------------------------------------------------------------------
94
+
95
+ function normalize(v: number[]): number[] {
96
+ let sumSq = 0;
97
+ for (const x of v) sumSq += x * x;
98
+ const norm = Math.sqrt(sumSq) || 1;
99
+ return v.map((x) => x / norm);
100
+ }
101
+
102
+ /** Dot product; on unit-normalized vectors this is cosine similarity. */
103
+ export function dot(a: number[], b: number[]): number {
104
+ let sum = 0;
105
+ const n = Math.min(a.length, b.length);
106
+ for (let i = 0; i < n; i++) sum += a[i]! * b[i]!;
107
+ return sum;
108
+ }
109
+
110
+ // ---------------------------------------------------------------------------
111
+ // Retriever (needle ∪ dense, in memory)
112
+ // ---------------------------------------------------------------------------
113
+
114
+ export interface RetrievalHit {
115
+ slug: Slug;
116
+ /** Index into `SectionIndex.sections` of the matched section. */
117
+ sectionIdx: number;
118
+ }
119
+
120
+ export interface Retriever {
121
+ index: SectionIndex;
122
+ rawBySlug: Map<Slug, string>;
123
+ /** Top-`k` distinct articles (needle ∪ dense), each with a matched section. */
124
+ retrieve(
125
+ queryText: string,
126
+ queryVec: number[] | null,
127
+ k: number,
128
+ ): RetrievalHit[];
129
+ }
130
+
131
+ /**
132
+ * Build a retriever over a corpus. When `dense` is true, every section is
133
+ * embedded (via `embedAll`) and stored unit-normalized for cosine search.
134
+ */
135
+ export async function buildRetriever(
136
+ corpus: Corpus,
137
+ embedAll: EmbedAll,
138
+ dense: boolean,
139
+ ): Promise<Retriever> {
140
+ const index = await buildSectionIndex(
141
+ corpus.slugs,
142
+ async (slug) => corpus.bodyBySlug.get(slug) ?? "",
143
+ );
144
+ const needle = buildSectionNeedle(index);
145
+
146
+ let sectionVecs: number[][] | null = null;
147
+ if (dense && index.sections.length > 0) {
148
+ const raw = await embedAll(index.sections.map((s) => s.text));
149
+ sectionVecs = raw.map(normalize);
150
+ }
151
+
152
+ function denseHits(queryVec: number[], k: number): RetrievalHit[] {
153
+ if (!sectionVecs) return [];
154
+ const scored = sectionVecs.map((sv, i) => ({
155
+ i,
156
+ score: dot(queryVec, sv),
157
+ }));
158
+ scored.sort((a, b) => b.score - a.score || a.i - b.i);
159
+ const seen = new Set<Slug>();
160
+ const hits: RetrievalHit[] = [];
161
+ for (const { i } of scored) {
162
+ const slug = index.sections[i]!.article;
163
+ if (seen.has(slug)) continue;
164
+ seen.add(slug);
165
+ hits.push({ slug, sectionIdx: i });
166
+ if (hits.length >= k) break;
167
+ }
168
+ return hits;
169
+ }
170
+
171
+ function retrieve(
172
+ queryText: string,
173
+ queryVec: number[] | null,
174
+ k: number,
175
+ ): RetrievalHit[] {
176
+ const lexical: RetrievalHit[] = needle
177
+ .query(queryText, k)
178
+ .map((h) => ({ slug: h.article, sectionIdx: h.section }));
179
+ const semantic = queryVec ? denseHits(queryVec, k) : [];
180
+
181
+ // Round-robin interleave the two lanes so neither dominates, dedupe by
182
+ // article, keep the section from whichever lane surfaced it first.
183
+ const merged: RetrievalHit[] = [];
184
+ const seen = new Set<Slug>();
185
+ for (let i = 0; i < Math.max(lexical.length, semantic.length); i++) {
186
+ for (const hit of [lexical[i], semantic[i]]) {
187
+ if (!hit || seen.has(hit.slug)) continue;
188
+ seen.add(hit.slug);
189
+ merged.push(hit);
190
+ if (merged.length >= k) return merged;
191
+ }
192
+ }
193
+ return merged;
194
+ }
195
+
196
+ return { index, rawBySlug: corpus.rawBySlug, retrieve };
197
+ }
198
+
199
+ /**
200
+ * Render the retrieved pages as one "memory set" string: each page's live-style
201
+ * card (lead + section TOC) followed by its matched section in full, mirroring
202
+ * what the model sees (accumulated cards + spotlighted section).
203
+ */
204
+ export function renderMemorySet(
205
+ retriever: Retriever,
206
+ hits: RetrievalHit[],
207
+ sectionCharCap: number,
208
+ ): string {
209
+ if (hits.length === 0) return "(no pages retrieved)";
210
+ const parts: string[] = [];
211
+ for (const hit of hits) {
212
+ const raw = retriever.rawBySlug.get(hit.slug) ?? "";
213
+ const card = renderCard(hit.slug, raw);
214
+ const section = retriever.index.sections[hit.sectionIdx];
215
+ if (section) {
216
+ const body = section.text.trim().slice(0, sectionCharCap);
217
+ parts.push(`${card}\n\n${injectedConceptHeader(hit.slug)}\n${body}`);
218
+ } else {
219
+ parts.push(card);
220
+ }
221
+ }
222
+ return parts.join("\n\n---\n\n");
223
+ }
224
+
225
+ // ---------------------------------------------------------------------------
226
+ // Turn mining
227
+ // ---------------------------------------------------------------------------
228
+
229
+ export interface RawMsgRow {
230
+ conversationId: string;
231
+ id: string;
232
+ role: string;
233
+ content: string;
234
+ createdAt: number;
235
+ }
236
+
237
+ export interface MinedTurn {
238
+ /** Stable id: `${conversationId}:${createdAt}`. */
239
+ turn: string;
240
+ conversationId: string;
241
+ userText: string;
242
+ replyText: string;
243
+ /** The preceding reply in the same conversation (truncated), or "". */
244
+ context: string;
245
+ createdAt: number;
246
+ }
247
+
248
+ /**
249
+ * Pair each user message with the next assistant reply in the same conversation,
250
+ * cap per conversation, and keep the most recent `limit` turns. Pure over `rows`
251
+ * (which must be chronological per conversation) so it is unit-testable.
252
+ */
253
+ export function pairTurns(
254
+ rows: RawMsgRow[],
255
+ opts: { limit: number; perConversationCap: number; contextCharCap?: number },
256
+ ): MinedTurn[] {
257
+ const contextCap = opts.contextCharCap ?? 600;
258
+ const byConversation: MinedTurn[][] = [];
259
+ const indexOfConversation = new Map<string, number>();
260
+
261
+ let pendingUser: RawMsgRow | null = null;
262
+ let lastReply = "";
263
+ let currentConversation = "";
264
+
265
+ for (const m of rows) {
266
+ if (m.conversationId !== currentConversation) {
267
+ currentConversation = m.conversationId;
268
+ pendingUser = null;
269
+ lastReply = "";
270
+ }
271
+ if (m.role === "user") {
272
+ pendingUser = m;
273
+ } else if (m.role === "assistant" && pendingUser) {
274
+ const userText = stringifyMessageContent(pendingUser.content);
275
+ const replyText = stringifyMessageContent(m.content);
276
+ if (userText.length > 0 && replyText.length >= 40) {
277
+ let bucket = indexOfConversation.get(m.conversationId);
278
+ if (bucket === undefined) {
279
+ bucket = byConversation.length;
280
+ indexOfConversation.set(m.conversationId, bucket);
281
+ byConversation.push([]);
282
+ }
283
+ byConversation[bucket]!.push({
284
+ turn: `${m.conversationId}:${pendingUser.createdAt}`,
285
+ conversationId: m.conversationId,
286
+ userText,
287
+ replyText,
288
+ context: lastReply.slice(0, contextCap),
289
+ createdAt: pendingUser.createdAt,
290
+ });
291
+ }
292
+ lastReply = replyText;
293
+ pendingUser = null;
294
+ }
295
+ }
296
+
297
+ // Cap per conversation (keep the most recent within each), then globally rank
298
+ // by recency and take `limit`.
299
+ const capped: MinedTurn[] = [];
300
+ for (const turns of byConversation) {
301
+ capped.push(...turns.slice(-opts.perConversationCap));
302
+ }
303
+ capped.sort((a, b) => b.createdAt - a.createdAt);
304
+ return capped.slice(0, opts.limit);
305
+ }
306
+
307
+ /** Read recent user→assistant turns from the live DB (read-only). */
308
+ export function mineTurns(
309
+ db: DrizzleDb,
310
+ opts: { limit: number; perConversationCap: number; maxScan?: number },
311
+ ): MinedTurn[] {
312
+ const maxScan = opts.maxScan ?? 6000;
313
+ const recent = db
314
+ .select({
315
+ conversationId: messages.conversationId,
316
+ id: messages.id,
317
+ role: messages.role,
318
+ content: messages.content,
319
+ createdAt: messages.createdAt,
320
+ })
321
+ .from(messages)
322
+ .innerJoin(conversations, eq(messages.conversationId, conversations.id))
323
+ .where(
324
+ and(
325
+ sql`COALESCE(${conversations.source}, 'user') = 'user'`,
326
+ sql`${conversations.scheduleJobId} IS NULL`,
327
+ ),
328
+ )
329
+ .orderBy(desc(messages.createdAt), desc(messages.id))
330
+ .limit(maxScan)
331
+ .all() as RawMsgRow[];
332
+
333
+ // Re-sort ascending and group so the pairing walks each conversation in order.
334
+ recent.sort(
335
+ (a, b) =>
336
+ a.conversationId.localeCompare(b.conversationId) ||
337
+ a.createdAt - b.createdAt ||
338
+ a.id.localeCompare(b.id),
339
+ );
340
+ return pairTurns(recent, opts);
341
+ }
342
+
343
+ // ---------------------------------------------------------------------------
344
+ // Packet assembly (with seeded blinding)
345
+ // ---------------------------------------------------------------------------
346
+
347
+ export interface EvalPacket {
348
+ turn: string;
349
+ context: string;
350
+ userMessage: string;
351
+ reply: string;
352
+ setA: string;
353
+ setB: string;
354
+ }
355
+
356
+ export interface EvalKeyEntry {
357
+ turn: string;
358
+ a: "snapshot" | "staging";
359
+ b: "snapshot" | "staging";
360
+ }
361
+
362
+ /** Deterministic PRNG so the A/B assignment is reproducible from a seed. */
363
+ export function mulberry32(seed: number): () => number {
364
+ let a = seed >>> 0;
365
+ return () => {
366
+ a |= 0;
367
+ a = (a + 0x6d2b79f5) | 0;
368
+ let t = Math.imul(a ^ (a >>> 15), 1 | a);
369
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
370
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
371
+ };
372
+ }
373
+
374
+ export async function buildPackets(
375
+ turns: MinedTurn[],
376
+ snapshot: Retriever,
377
+ staging: Retriever,
378
+ embedAll: EmbedAll,
379
+ opts: { dense: boolean; seed: number; k: number; sectionCharCap: number },
380
+ ): Promise<{ packets: EvalPacket[]; key: EvalKeyEntry[] }> {
381
+ const queryVecs: (number[] | null)[] = opts.dense
382
+ ? (await embedAll(turns.map((t) => t.userText))).map(normalize)
383
+ : turns.map(() => null);
384
+
385
+ const rng = mulberry32(opts.seed);
386
+ const packets: EvalPacket[] = [];
387
+ const key: EvalKeyEntry[] = [];
388
+
389
+ for (let i = 0; i < turns.length; i++) {
390
+ const t = turns[i]!;
391
+ const qVec = queryVecs[i] ?? null;
392
+ const snapSet = renderMemorySet(
393
+ snapshot,
394
+ snapshot.retrieve(t.userText, qVec, opts.k),
395
+ opts.sectionCharCap,
396
+ );
397
+ const stageSet = renderMemorySet(
398
+ staging,
399
+ staging.retrieve(t.userText, qVec, opts.k),
400
+ opts.sectionCharCap,
401
+ );
402
+ const stagingIsA = rng() < 0.5;
403
+ packets.push({
404
+ turn: t.turn,
405
+ context: t.context,
406
+ userMessage: t.userText,
407
+ reply: t.replyText,
408
+ setA: stagingIsA ? stageSet : snapSet,
409
+ setB: stagingIsA ? snapSet : stageSet,
410
+ });
411
+ key.push({
412
+ turn: t.turn,
413
+ a: stagingIsA ? "staging" : "snapshot",
414
+ b: stagingIsA ? "snapshot" : "staging",
415
+ });
416
+ }
417
+ return { packets, key };
418
+ }
419
+
420
+ // ---------------------------------------------------------------------------
421
+ // Top-level run
422
+ // ---------------------------------------------------------------------------
423
+
424
+ export interface EvalParams {
425
+ /** Staged v3 wiki dir (relative to workspace, or absolute). */
426
+ stagingDir: string;
427
+ /** Read-only v2 snapshot dir (relative to workspace, or absolute). */
428
+ snapshotDir: string;
429
+ /** Output dir for packets.json + key.json (relative to workspace, or absolute). */
430
+ outDir: string;
431
+ turns?: number;
432
+ perConversationCap?: number;
433
+ /** Pages per memory set. */
434
+ k?: number;
435
+ /** Include the dense lane (embeds both corpora). Off = needle-only, no embeds. */
436
+ dense?: boolean;
437
+ seed?: number;
438
+ sectionCharCap?: number;
439
+ }
440
+
441
+ export interface EvalResult {
442
+ turnsMined: number;
443
+ packetsWritten: number;
444
+ packetsPath: string;
445
+ keyPath: string;
446
+ snapshotPages: number;
447
+ stagingPages: number;
448
+ dense: boolean;
449
+ }
450
+
451
+ export interface EvalDeps {
452
+ config: AssistantConfig;
453
+ workspaceDir: string;
454
+ db: DrizzleDb;
455
+ /** Embed one batch of texts. Defaults to the live embedding backend. */
456
+ embed?: EmbedAll;
457
+ }
458
+
459
+ /** Chunk an embed function so a large corpus never overruns provider batch limits. */
460
+ function chunkedEmbed(embed: EmbedAll, batchSize = 96): EmbedAll {
461
+ return async (texts: string[]) => {
462
+ const out: number[][] = [];
463
+ for (let i = 0; i < texts.length; i += batchSize) {
464
+ out.push(...(await embed(texts.slice(i, i + batchSize))));
465
+ }
466
+ return out;
467
+ };
468
+ }
469
+
470
+ /**
471
+ * Resolve a corpus/output path against the workspace, rejecting anything that
472
+ * escapes it. The route is in the shared table (HTTP + IPC), so an actor caller
473
+ * could otherwise pass an absolute path to read/write arbitrary trees — these
474
+ * dirs must stay under the workspace.
475
+ */
476
+ export function resolveDir(workspaceDir: string, p: string): string {
477
+ const root = resolve(workspaceDir);
478
+ const resolved = resolve(root, p);
479
+ if (resolved !== root && !resolved.startsWith(root + sep)) {
480
+ throw new Error(`eval path must stay within the workspace: ${p}`);
481
+ }
482
+ return resolved;
483
+ }
484
+
485
+ /**
486
+ * Run the full eval: load both corpora, mine turns, retrieve both sides, and
487
+ * write blinded packets + the unblinding key. Returns counts.
488
+ */
489
+ export async function runMemoryEval(
490
+ params: EvalParams,
491
+ deps: EvalDeps,
492
+ ): Promise<EvalResult> {
493
+ const dense = params.dense ?? true;
494
+ const k = params.k ?? 8;
495
+ const seed = params.seed ?? 1;
496
+ const sectionCharCap = params.sectionCharCap ?? 1200;
497
+
498
+ const snapshotDir = resolveDir(deps.workspaceDir, params.snapshotDir);
499
+ const stagingDir = resolveDir(deps.workspaceDir, params.stagingDir);
500
+ const outDir = resolveDir(deps.workspaceDir, params.outDir);
501
+
502
+ const baseEmbed: EmbedAll =
503
+ deps.embed ??
504
+ (async (texts) => (await embedWithRetry(deps.config, texts)).vectors);
505
+ const embedAll = chunkedEmbed(baseEmbed);
506
+
507
+ const snapshotCorpus = loadCorpus(snapshotDir);
508
+ const stagingCorpus = loadCorpus(stagingDir);
509
+
510
+ const snapshot = await buildRetriever(snapshotCorpus, embedAll, dense);
511
+ const staging = await buildRetriever(stagingCorpus, embedAll, dense);
512
+
513
+ const turns = mineTurns(deps.db, {
514
+ limit: params.turns ?? 30,
515
+ perConversationCap: params.perConversationCap ?? 4,
516
+ });
517
+
518
+ const { packets, key } = await buildPackets(
519
+ turns,
520
+ snapshot,
521
+ staging,
522
+ embedAll,
523
+ {
524
+ dense,
525
+ seed,
526
+ k,
527
+ sectionCharCap,
528
+ },
529
+ );
530
+
531
+ mkdirSync(outDir, { recursive: true });
532
+ const packetsPath = join(outDir, "packets.json");
533
+ const keyPath = join(outDir, "key.json");
534
+ writeFileSync(packetsPath, JSON.stringify(packets, null, 2));
535
+ writeFileSync(keyPath, JSON.stringify(key, null, 2));
536
+
537
+ return {
538
+ turnsMined: turns.length,
539
+ packetsWritten: packets.length,
540
+ packetsPath,
541
+ keyPath,
542
+ snapshotPages: snapshotCorpus.slugs.length,
543
+ stagingPages: stagingCorpus.slugs.length,
544
+ dense,
545
+ };
546
+ }
@@ -246,7 +246,7 @@ async function resolveUserInfoUncached(
246
246
  try {
247
247
  const result = findContactChannel({
248
248
  channelType: "slack",
249
- externalUserId: userId,
249
+ address: userId,
250
250
  });
251
251
  if (result) {
252
252
  contactDisplayName = result.contact.displayName;
@@ -294,6 +294,37 @@ export async function getSlackConversationInfo(
294
294
  };
295
295
  }
296
296
 
297
+ interface SlackHistoryResponse extends SlackApiResponse {
298
+ messages?: Array<{ ts?: string; blocks?: unknown[] }>;
299
+ }
300
+
301
+ /**
302
+ * Fetch the Block Kit blocks of a single channel message by timestamp.
303
+ *
304
+ * Used to edit a message in place while preserving its existing content — e.g.
305
+ * withdrawing an approval card's buttons without discarding the card body.
306
+ * Returns null when the message can't be read (missing `*:history` scope, a
307
+ * threaded reply not present in channel history, or a deleted message) so
308
+ * callers can degrade gracefully instead of failing the edit.
309
+ */
310
+ export async function getSlackMessageBlocks(
311
+ channelId: string,
312
+ ts: string,
313
+ ): Promise<unknown[] | null> {
314
+ const data = (await callSlackApiGet(
315
+ "conversations.history",
316
+ new URLSearchParams({
317
+ channel: channelId,
318
+ latest: ts,
319
+ oldest: ts,
320
+ inclusive: "true",
321
+ limit: "1",
322
+ }),
323
+ )) as SlackHistoryResponse;
324
+ const message = data.messages?.find((m) => m.ts === ts) ?? data.messages?.[0];
325
+ return Array.isArray(message?.blocks) ? message.blocks : null;
326
+ }
327
+
297
328
  /**
298
329
  * Call a Slack Web API method with form-urlencoded body.
299
330
  */