@vellumai/assistant 0.8.0 → 0.8.1

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 (692) hide show
  1. package/AGENTS.md +11 -0
  2. package/Dockerfile +5 -4
  3. package/README.md +2 -2
  4. package/docker-entrypoint.sh +16 -0
  5. package/eslint-rules/__tests__/cli-no-daemon-internals.test.ts +420 -0
  6. package/eslint-rules/cli-no-daemon-internals.js +283 -0
  7. package/eslint.config.mjs +12 -0
  8. package/knip.json +2 -1
  9. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +10 -1
  10. package/openapi.yaml +4847 -1698
  11. package/package.json +3 -1
  12. package/scripts/generate-openapi.ts +52 -4
  13. package/scripts/sync-llm-catalog.ts +165 -0
  14. package/scripts/sync-web-search-catalog.ts +107 -0
  15. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +169 -0
  16. package/src/__tests__/agent-loop-override-profile.test.ts +26 -1
  17. package/src/__tests__/anthropic-provider.test.ts +92 -2
  18. package/src/__tests__/app-control-flow.test.ts +7 -0
  19. package/src/__tests__/assistant-events-sse-shed.test.ts +232 -0
  20. package/src/__tests__/avatar-identity-sync.test.ts +87 -0
  21. package/src/__tests__/background-workers-disk-pressure.test.ts +11 -22
  22. package/src/__tests__/btw-routes.test.ts +1 -0
  23. package/src/__tests__/call-site-routing-provider.test.ts +172 -45
  24. package/src/__tests__/cancel-resolves-conversation-key.test.ts +44 -3
  25. package/src/__tests__/channel-policy.test.ts +12 -0
  26. package/src/__tests__/checker.test.ts +89 -0
  27. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +35 -7
  28. package/src/__tests__/compact-event-conversation-id-guard.test.ts +33 -5
  29. package/src/__tests__/compaction-strip-metadata-clear.test.ts +26 -1
  30. package/src/__tests__/config-loader-backfill.test.ts +526 -102
  31. package/src/__tests__/config-loader-corrupt.test.ts +68 -0
  32. package/src/__tests__/config-loader-platform-defaults.test.ts +77 -23
  33. package/src/__tests__/config-schema-cmd.test.ts +63 -29
  34. package/src/__tests__/config-schema.test.ts +14 -3
  35. package/src/__tests__/config-set-platform-guard.test.ts +75 -152
  36. package/src/__tests__/config-set-route.test.ts +198 -0
  37. package/src/__tests__/config-watcher.test.ts +6 -0
  38. package/src/__tests__/contacts-tools.test.ts +51 -199
  39. package/src/__tests__/context-search-agent-protocol.test.ts +21 -2
  40. package/src/__tests__/context-search-agent-runner.test.ts +22 -138
  41. package/src/__tests__/context-search-conversations-source.test.ts +42 -16
  42. package/src/__tests__/context-search-fanout.test.ts +20 -157
  43. package/src/__tests__/context-search-memory-v2-source.test.ts +3 -3
  44. package/src/__tests__/context-search-types.test.ts +7 -2
  45. package/src/__tests__/context-window-manager.test.ts +389 -1
  46. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  47. package/src/__tests__/conversation-crud-inference-profile.test.ts +100 -0
  48. package/src/__tests__/conversation-error.test.ts +38 -0
  49. package/src/__tests__/conversation-fork-crud.test.ts +241 -1
  50. package/src/__tests__/conversation-inference-profile-route.test.ts +14 -14
  51. package/src/__tests__/conversation-init.benchmark.test.ts +1 -0
  52. package/src/__tests__/conversation-lifecycle.test.ts +124 -0
  53. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +100 -1
  54. package/src/__tests__/conversation-process-callsite.test.ts +21 -1
  55. package/src/__tests__/conversation-runtime-assembly.test.ts +4 -4
  56. package/src/__tests__/conversation-slash-commands.test.ts +194 -2
  57. package/src/__tests__/conversation-surfaces-app-control.test.ts +323 -3
  58. package/src/__tests__/credential-security-invariants.test.ts +5 -6
  59. package/src/__tests__/daemon-credential-client.test.ts +56 -1
  60. package/src/__tests__/db-activation-state-fk-cascade.test.ts +132 -0
  61. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +37 -0
  62. package/src/__tests__/db-memory-graph-event-date-repair.test.ts +43 -20
  63. package/src/__tests__/db-proxy-transaction.test.ts +206 -0
  64. package/src/__tests__/external-plugin-loader.test.ts +458 -0
  65. package/src/__tests__/filing-service.test.ts +23 -3
  66. package/src/__tests__/fixtures/mock-chrome-extension.ts +5 -0
  67. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  68. package/src/__tests__/graph-extraction-event-date.test.ts +34 -0
  69. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -8
  70. package/src/__tests__/heartbeat-disk-pressure.test.ts +21 -8
  71. package/src/__tests__/heartbeat-service.test.ts +50 -233
  72. package/src/__tests__/history-repair.test.ts +89 -0
  73. package/src/__tests__/host-app-control-proxy.test.ts +109 -1
  74. package/src/__tests__/host-app-control-routes.test.ts +247 -1
  75. package/src/__tests__/host-browser-proxy.test.ts +416 -20
  76. package/src/__tests__/host-browser-routes.test.ts +325 -33
  77. package/src/__tests__/host-proxy-preactivation.test.ts +211 -0
  78. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +246 -0
  79. package/src/__tests__/inference-profile-reaper.test.ts +154 -0
  80. package/src/__tests__/inference-profile-session-handler.test.ts +398 -0
  81. package/src/__tests__/inference-profile-session-ipc.test.ts +236 -0
  82. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -1
  83. package/src/__tests__/install-skill-routing.test.ts +2 -2
  84. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +15 -0
  85. package/src/__tests__/llm-callsite-catalog.test.ts +20 -1
  86. package/src/__tests__/llm-catalog-parity.test.ts +146 -0
  87. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +188 -0
  88. package/src/__tests__/llm-request-log-source-factory.test.ts +124 -0
  89. package/src/__tests__/llm-resolver.test.ts +46 -0
  90. package/src/__tests__/managed-profile-guard.test.ts +131 -2
  91. package/src/__tests__/mcp-auth-routes.test.ts +1 -0
  92. package/src/__tests__/mcp-cli.test.ts +182 -220
  93. package/src/__tests__/mcp-health-check.test.ts +56 -27
  94. package/src/__tests__/memory-jobs-worker-lanes.test.ts +18 -11
  95. package/src/__tests__/message-complete-display-id.test.ts +175 -0
  96. package/src/__tests__/notification-platform-adapter.test.ts +229 -0
  97. package/src/__tests__/oauth-cli.test.ts +38 -2009
  98. package/src/__tests__/oauth-commands-routes.test.ts +711 -0
  99. package/src/__tests__/oauth-connect-routes.test.ts +174 -11
  100. package/src/__tests__/oauth-providers-routes.test.ts +14 -10
  101. package/src/__tests__/openai-responses-cutover-guard.test.ts +33 -12
  102. package/src/__tests__/openai-responses-provider.test.ts +17 -0
  103. package/src/__tests__/plugin-bootstrap.test.ts +31 -2
  104. package/src/__tests__/plugin-route-contribution.test.ts +31 -3
  105. package/src/__tests__/plugin-tool-contribution.test.ts +31 -3
  106. package/src/__tests__/plugin-types.test.ts +13 -11
  107. package/src/__tests__/process-message-background-slack.test.ts +46 -0
  108. package/src/__tests__/profile-entry-status.test.ts +43 -0
  109. package/src/__tests__/provider-managed-proxy-integration.test.ts +12 -4
  110. package/src/__tests__/provider-registry-ollama.test.ts +12 -4
  111. package/src/__tests__/provider-send-message-override-profile.test.ts +10 -4
  112. package/src/__tests__/relay-server.test.ts +118 -0
  113. package/src/__tests__/retry-thinking-tool-choice.test.ts +15 -0
  114. package/src/__tests__/schedule-retry.test.ts +56 -4
  115. package/src/__tests__/schedule-routes.test.ts +104 -0
  116. package/src/__tests__/scheduler-disk-pressure.test.ts +0 -4
  117. package/src/__tests__/scheduler-recurrence.test.ts +87 -34
  118. package/src/__tests__/scheduler-reuse-conversation.test.ts +161 -5
  119. package/src/__tests__/scheduler-wake.test.ts +0 -63
  120. package/src/__tests__/secret-allowlist.test.ts +1 -0
  121. package/src/__tests__/secret-routes-managed-proxy.test.ts +12 -4
  122. package/src/__tests__/shell-credential-ref.test.ts +95 -3
  123. package/src/__tests__/shell-tool-proxy-mode.test.ts +14 -0
  124. package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
  125. package/src/__tests__/skill-load-tool.test.ts +2 -4
  126. package/src/__tests__/subagent-call-site-routing.test.ts +78 -16
  127. package/src/__tests__/suggestion-routes.test.ts +3 -3
  128. package/src/__tests__/sync-message-contract.test.ts +63 -0
  129. package/src/__tests__/task-scheduler.test.ts +88 -23
  130. package/src/__tests__/update-bulletin-job.test.ts +96 -193
  131. package/src/__tests__/usage-cli.test.ts +11 -73
  132. package/src/__tests__/user-plugin-loader.test.ts +145 -0
  133. package/src/__tests__/vercel-config.test.ts +168 -0
  134. package/src/__tests__/web-search-catalog-parity.test.ts +86 -0
  135. package/src/__tests__/web-search.test.ts +303 -2
  136. package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +1 -21
  137. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +58 -0
  138. package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +53 -20
  139. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +191 -0
  140. package/src/__tests__/workspace-migration-076-drop-services-inference-mode.test.ts +211 -0
  141. package/src/__tests__/workspace-migration-077-seed-memory-router-callsite.test.ts +174 -0
  142. package/src/__tests__/workspace-migration-079-home-feed-notification-only.test.ts +323 -0
  143. package/src/__tests__/workspace-migration-080-restrict-vercel-api-token-metadata.test.ts +299 -0
  144. package/src/__tests__/workspace-migration-081-backfill-bash-allowed-tools.test.ts +410 -0
  145. package/src/__tests__/workspace-migration-082-backfill-managed-profile-labels.test.ts +268 -0
  146. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +3 -3
  147. package/src/__tests__/workspace-release-notes-feature-flag-guard.test.ts +115 -0
  148. package/src/acp/__tests__/helpers/which-stub.ts +4 -2
  149. package/src/acp/resolve-agent.test.ts +25 -0
  150. package/src/acp/resolve-agent.ts +13 -2
  151. package/src/acp/session-manager.ts +14 -0
  152. package/src/approvals/guardian-request-resolvers.ts +32 -87
  153. package/src/calls/relay-server.ts +35 -0
  154. package/src/calls/relay-setup-router.ts +36 -0
  155. package/src/calls/types.ts +1 -0
  156. package/src/calls/voice-session-bridge.ts +23 -4
  157. package/src/channels/config.ts +14 -1
  158. package/src/channels/types.ts +1 -0
  159. package/src/cli/AGENTS.md +164 -4
  160. package/src/cli/__tests__/notifications.test.ts +54 -0
  161. package/src/cli/commands/__tests__/avatar.test.ts +540 -0
  162. package/src/cli/commands/__tests__/backup.test.ts +236 -776
  163. package/src/cli/commands/__tests__/cache.test.ts +1 -1
  164. package/src/cli/commands/__tests__/changelog.test.ts +593 -0
  165. package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +503 -0
  166. package/src/cli/commands/__tests__/conversations-import.test.ts +515 -0
  167. package/src/cli/commands/__tests__/domain-register.test.ts +140 -167
  168. package/src/cli/commands/__tests__/domain-status.test.ts +137 -76
  169. package/src/cli/commands/__tests__/email-attachment.test.ts +314 -337
  170. package/src/cli/commands/__tests__/email-core.test.ts +579 -0
  171. package/src/cli/commands/__tests__/image-generation.test.ts +87 -824
  172. package/src/cli/commands/__tests__/inference-send.test.ts +30 -266
  173. package/src/cli/commands/__tests__/inference-session.test.ts +423 -0
  174. package/src/cli/commands/__tests__/memory-v2.test.ts +81 -110
  175. package/src/cli/commands/__tests__/skills.test.ts +563 -0
  176. package/src/cli/commands/__tests__/status.test.ts +249 -0
  177. package/src/cli/commands/__tests__/stt.test.ts +320 -0
  178. package/src/cli/commands/__tests__/tts-synthesize.test.ts +4 -603
  179. package/src/cli/commands/__tests__/tts.test.ts +321 -0
  180. package/src/cli/commands/__tests__/webhooks.test.ts +86 -511
  181. package/src/cli/commands/attachment.ts +8 -3
  182. package/src/cli/commands/audit.ts +95 -64
  183. package/src/cli/commands/auth.ts +61 -58
  184. package/src/cli/commands/avatar.ts +276 -390
  185. package/src/cli/commands/backup.ts +409 -505
  186. package/src/cli/commands/bash.ts +9 -5
  187. package/src/cli/commands/browser.ts +28 -9
  188. package/src/cli/commands/cache.ts +9 -4
  189. package/src/cli/commands/changelog.ts +414 -0
  190. package/src/cli/commands/channel-verification-sessions.ts +238 -317
  191. package/src/cli/commands/clients.ts +8 -3
  192. package/src/cli/commands/completions.ts +9 -9
  193. package/src/cli/commands/config.ts +102 -72
  194. package/src/cli/commands/contacts.ts +575 -696
  195. package/src/cli/commands/conversations-defer.ts +17 -69
  196. package/src/cli/commands/conversations-import.ts +90 -253
  197. package/src/cli/commands/conversations.ts +346 -436
  198. package/src/cli/commands/credential-execution.ts +9 -6
  199. package/src/cli/commands/credentials.ts +456 -736
  200. package/src/cli/commands/domain.ts +128 -206
  201. package/src/cli/commands/email.ts +606 -794
  202. package/src/cli/commands/gateway.ts +8 -1
  203. package/src/cli/commands/image-generation.ts +157 -205
  204. package/src/cli/commands/inference-providers.ts +352 -0
  205. package/src/cli/commands/inference-session.ts +415 -0
  206. package/src/cli/commands/inference.ts +87 -65
  207. package/src/cli/commands/keys.ts +8 -3
  208. package/src/cli/commands/mcp.ts +103 -287
  209. package/src/cli/commands/memory-v2.ts +162 -516
  210. package/src/cli/commands/notifications.ts +33 -7
  211. package/src/cli/commands/oauth/apps.ts +292 -261
  212. package/src/cli/commands/oauth/connect.ts +176 -297
  213. package/src/cli/commands/oauth/disconnect.ts +16 -215
  214. package/src/cli/commands/oauth/index.ts +49 -45
  215. package/src/cli/commands/oauth/mode.ts +43 -199
  216. package/src/cli/commands/oauth/ping.ts +17 -125
  217. package/src/cli/commands/oauth/providers.ts +732 -921
  218. package/src/cli/commands/oauth/request.ts +60 -350
  219. package/src/cli/commands/oauth/shared.ts +11 -121
  220. package/src/cli/commands/oauth/status.ts +31 -121
  221. package/src/cli/commands/oauth/token.ts +13 -55
  222. package/src/cli/commands/pending.ts +19 -10
  223. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +133 -183
  224. package/src/cli/commands/platform/__tests__/connect.test.ts +66 -181
  225. package/src/cli/commands/platform/__tests__/disconnect.test.ts +71 -227
  226. package/src/cli/commands/platform/__tests__/status.test.ts +169 -287
  227. package/src/cli/commands/platform/connect.ts +16 -80
  228. package/src/cli/commands/platform/disconnect.ts +14 -112
  229. package/src/cli/commands/platform/index.ts +177 -246
  230. package/src/cli/commands/routes.ts +153 -336
  231. package/src/cli/commands/sequence.ts +316 -360
  232. package/src/cli/commands/skills.ts +449 -671
  233. package/src/cli/commands/status.ts +58 -37
  234. package/src/cli/commands/stt.ts +94 -262
  235. package/src/cli/commands/task.ts +14 -40
  236. package/src/cli/commands/trust.ts +8 -3
  237. package/src/cli/commands/tts.ts +162 -167
  238. package/src/cli/commands/ui.ts +35 -42
  239. package/src/cli/commands/usage.ts +188 -126
  240. package/src/cli/commands/watchers.ts +8 -3
  241. package/src/cli/commands/webhooks.ts +99 -193
  242. package/src/cli/lib/__tests__/register-command.test.ts +85 -0
  243. package/src/cli/lib/daemon-credential-client.ts +4 -5
  244. package/src/cli/lib/nested-value.ts +44 -0
  245. package/src/cli/lib/open-browser.ts +36 -0
  246. package/src/cli/lib/register-command.ts +19 -0
  247. package/src/cli/lib/time-ago.ts +34 -0
  248. package/src/cli/program.ts +2 -4
  249. package/src/cli/utils/__tests__/conversation-id.test.ts +66 -0
  250. package/src/cli/utils/__tests__/parse-duration.test.ts +49 -0
  251. package/src/cli/utils/conversation-id.ts +30 -0
  252. package/src/cli/utils/parse-duration.ts +41 -0
  253. package/src/config/acp-defaults.test.ts +5 -1
  254. package/src/config/acp-defaults.ts +11 -4
  255. package/src/config/bundled-skills/acp/TOOLS.json +2 -2
  256. package/src/config/bundled-skills/app-control/TOOLS.json +32 -0
  257. package/src/config/bundled-skills/contacts/SKILL.md +12 -45
  258. package/src/config/bundled-skills/contacts/TOOLS.json +0 -57
  259. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +0 -12
  260. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +0 -58
  261. package/src/config/bundled-tool-registry.ts +0 -2
  262. package/src/config/feature-flag-registry.json +16 -0
  263. package/src/config/llm-resolver.ts +16 -1
  264. package/src/config/loader.ts +76 -14
  265. package/src/config/raw-config-utils.ts +2 -30
  266. package/src/config/schema.ts +4 -0
  267. package/src/config/schemas/__tests__/memory-v2.test.ts +49 -0
  268. package/src/config/schemas/call-site-catalog.ts +29 -7
  269. package/src/config/schemas/llm-request-logs.ts +57 -0
  270. package/src/config/schemas/llm.ts +52 -2
  271. package/src/config/schemas/memory-retrospective.ts +48 -0
  272. package/src/config/schemas/memory-v2.ts +32 -1
  273. package/src/config/schemas/memory.ts +4 -0
  274. package/src/config/schemas/services.ts +15 -12
  275. package/src/config/seed-inference-profiles.ts +195 -134
  276. package/src/contacts/contact-store.ts +0 -61
  277. package/src/context/window-manager.ts +191 -5
  278. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +79 -0
  279. package/src/daemon/__tests__/conversation-tool-setup.test.ts +109 -4
  280. package/src/daemon/__tests__/daemon-skill-host.test.ts +10 -4
  281. package/src/daemon/approval-generators.ts +23 -29
  282. package/src/daemon/config-watcher.ts +2 -0
  283. package/src/daemon/conversation-agent-loop-handlers.ts +24 -0
  284. package/src/daemon/conversation-agent-loop.ts +127 -97
  285. package/src/daemon/conversation-error.ts +21 -0
  286. package/src/daemon/conversation-lifecycle.ts +46 -5
  287. package/src/daemon/conversation-process.ts +36 -19
  288. package/src/daemon/conversation-runtime-assembly.ts +14 -5
  289. package/src/daemon/conversation-slash.ts +175 -23
  290. package/src/daemon/conversation-store.ts +17 -10
  291. package/src/daemon/conversation-surfaces.ts +76 -12
  292. package/src/daemon/conversation-tool-setup.ts +24 -14
  293. package/src/daemon/conversation.ts +48 -9
  294. package/src/daemon/external-plugins-bootstrap.ts +18 -8
  295. package/src/daemon/guardian-action-generators.ts +7 -22
  296. package/src/daemon/handlers/config-model.ts +8 -126
  297. package/src/daemon/handlers/config-slack-channel.ts +10 -7
  298. package/src/daemon/handlers/config-vercel.ts +3 -1
  299. package/src/daemon/handlers/skills.ts +84 -5
  300. package/src/daemon/history-repair.ts +33 -6
  301. package/src/daemon/host-app-control-proxy.ts +44 -19
  302. package/src/daemon/host-bash-proxy.ts +85 -158
  303. package/src/daemon/host-browser-proxy.ts +96 -35
  304. package/src/daemon/host-proxy-base.ts +13 -1
  305. package/src/daemon/host-proxy-preactivation.ts +25 -1
  306. package/src/daemon/identity-helpers.ts +19 -0
  307. package/src/daemon/lifecycle.ts +42 -43
  308. package/src/daemon/meet-host-supervisor.ts +15 -15
  309. package/src/daemon/memory-v2-startup.ts +9 -2
  310. package/src/daemon/message-protocol.ts +6 -0
  311. package/src/daemon/message-types/bookmarks.ts +18 -0
  312. package/src/daemon/message-types/conversations.ts +12 -9
  313. package/src/daemon/message-types/messages.ts +9 -1
  314. package/src/daemon/message-types/sync.ts +60 -0
  315. package/src/daemon/pkb-reminder-builder.test.ts +54 -13
  316. package/src/daemon/pkb-reminder-builder.ts +21 -7
  317. package/src/daemon/process-message.ts +56 -23
  318. package/src/daemon/server.ts +23 -18
  319. package/src/daemon/shutdown-handlers.ts +0 -2
  320. package/src/daemon/tool-setup-types.ts +9 -0
  321. package/src/daemon/tool-side-effects.ts +6 -4
  322. package/src/daemon/wake-target-adapter.ts +11 -0
  323. package/src/export/transcript-formatter.ts +61 -2
  324. package/src/filing/filing-service.ts +40 -53
  325. package/src/heartbeat/__tests__/heartbeat-service.test.ts +359 -0
  326. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  327. package/src/heartbeat/heartbeat-service.ts +148 -127
  328. package/src/home/__tests__/feed-types.test.ts +63 -131
  329. package/src/home/__tests__/feed-writer.test.ts +77 -278
  330. package/src/home/__tests__/post-connect-feed.test.ts +9 -12
  331. package/src/home/feed-types.ts +19 -73
  332. package/src/home/feed-writer.ts +25 -156
  333. package/src/home/post-connect-feed.ts +1 -3
  334. package/src/ipc/__tests__/cli-ipc.test.ts +2 -0
  335. package/src/ipc/__tests__/email-ipc.test.ts +506 -0
  336. package/src/ipc/__tests__/exit-helper.test.ts +104 -0
  337. package/src/ipc/__tests__/streaming-client.test.ts +237 -0
  338. package/src/ipc/__tests__/streaming-framing.test.ts +142 -0
  339. package/src/ipc/assistant-server.ts +55 -6
  340. package/src/ipc/cli-client.ts +370 -50
  341. package/src/ipc/routes/db-proxy-transaction.ts +151 -0
  342. package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +60 -0
  343. package/src/ipc/skill-routes/events.ts +30 -3
  344. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +46 -0
  345. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +1 -0
  346. package/src/live-voice/live-voice-session-manager.ts +11 -4
  347. package/src/live-voice/live-voice-session.ts +14 -6
  348. package/src/memory/__tests__/bookmark-crud.test.ts +258 -0
  349. package/src/memory/__tests__/bookmark-schema.test.ts +181 -0
  350. package/src/memory/__tests__/conversation-types.test.ts +36 -0
  351. package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +130 -0
  352. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +177 -0
  353. package/src/memory/__tests__/memory-retrospective-job.test.ts +328 -0
  354. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +213 -0
  355. package/src/memory/__tests__/memory-retrospective-trigger-check.test.ts +90 -0
  356. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +69 -0
  357. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +3 -0
  358. package/src/memory/bookmark-crud.ts +179 -0
  359. package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +31 -9
  360. package/src/memory/context-search/agent-protocol.ts +5 -1
  361. package/src/memory/context-search/agent-runner.ts +60 -85
  362. package/src/memory/context-search/limits.ts +1 -4
  363. package/src/memory/context-search/search.ts +23 -113
  364. package/src/memory/context-search/sources/conversations.ts +18 -6
  365. package/src/memory/context-search/sources/memory-v2.ts +39 -14
  366. package/src/memory/context-search/sources/memory.ts +7 -0
  367. package/src/memory/context-search/sources/workspace.ts +13 -10
  368. package/src/memory/context-search/types.ts +1 -1
  369. package/src/memory/conversation-bootstrap.ts +11 -0
  370. package/src/memory/conversation-crud.ts +312 -10
  371. package/src/memory/conversation-queries.ts +9 -5
  372. package/src/memory/conversation-title-service.ts +1 -0
  373. package/src/memory/conversation-types.ts +16 -0
  374. package/src/memory/db-init.ts +14 -0
  375. package/src/memory/embedding-backend.ts +2 -1
  376. package/src/memory/embedding-runtime-manager.ts +1 -2
  377. package/src/memory/graph/__tests__/remember-description.test.ts +55 -0
  378. package/src/memory/graph/conversation-graph-memory.ts +76 -5
  379. package/src/memory/graph/extraction.ts +4 -0
  380. package/src/memory/graph/graph-memory-state-store.ts +16 -3
  381. package/src/memory/graph/tool-handlers.ts +17 -7
  382. package/src/memory/graph/tools.ts +44 -5
  383. package/src/memory/indexer.ts +17 -0
  384. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +13 -15
  385. package/src/memory/jobs/embed-concept-page.ts +45 -9
  386. package/src/memory/jobs-store.ts +51 -1
  387. package/src/memory/jobs-worker.ts +52 -3
  388. package/src/memory/llm-request-log-source-clickhouse.ts +317 -0
  389. package/src/memory/llm-request-log-source-local.ts +26 -0
  390. package/src/memory/llm-request-log-source.ts +97 -0
  391. package/src/memory/llm-request-log-store.ts +1 -1
  392. package/src/memory/memory-retrospective-constants.ts +13 -0
  393. package/src/memory/memory-retrospective-enqueue.ts +114 -0
  394. package/src/memory/memory-retrospective-job.ts +351 -0
  395. package/src/memory/memory-retrospective-startup-cleanup.ts +108 -0
  396. package/src/memory/memory-retrospective-state.ts +162 -0
  397. package/src/memory/memory-retrospective-trigger-check.ts +91 -0
  398. package/src/memory/memory-v2-activation-log-store.ts +49 -5
  399. package/src/memory/memory-v2-concept-frequency.ts +4 -0
  400. package/src/memory/message-content.ts +38 -1
  401. package/src/memory/migrations/227-add-conversation-inference-profile.ts +6 -1
  402. package/src/memory/migrations/228-rename-inference-profile-snake-case.ts +20 -7
  403. package/src/memory/migrations/229-delete-private-conversations.test.ts +70 -1
  404. package/src/memory/migrations/229-delete-private-conversations.ts +12 -0
  405. package/src/memory/migrations/231-repair-memory-graph-event-dates.ts +16 -2
  406. package/src/memory/migrations/240-conversation-inference-profile-session.ts +25 -0
  407. package/src/memory/migrations/241-activation-state-fk-cascade.ts +50 -0
  408. package/src/memory/migrations/242-message-bookmarks.ts +38 -0
  409. package/src/memory/migrations/243-provider-connections.ts +68 -0
  410. package/src/memory/migrations/244-provider-connection-status-label.ts +23 -0
  411. package/src/memory/migrations/245-memory-retrospective-state.ts +36 -0
  412. package/src/memory/migrations/246-backfill-provider-connection-label.ts +81 -0
  413. package/src/memory/migrations/__tests__/244-provider-connection-status-label.test.ts +84 -0
  414. package/src/memory/migrations/__tests__/245-memory-retrospective-state.test.ts +125 -0
  415. package/src/memory/migrations/__tests__/246-backfill-provider-connection-label.test.ts +192 -0
  416. package/src/memory/migrations/index.ts +7 -0
  417. package/src/memory/published-pages-store.ts +16 -0
  418. package/src/memory/schema/bookmarks.ts +38 -0
  419. package/src/memory/schema/conversations.ts +2 -0
  420. package/src/memory/schema/index.ts +2 -0
  421. package/src/memory/schema/inference.ts +29 -0
  422. package/src/memory/schema/memory-core.ts +9 -0
  423. package/src/memory/search/semantic.ts +1 -4
  424. package/src/memory/v2/__tests__/__snapshots__/prompts-router.test.ts.snap +27 -0
  425. package/src/memory/v2/__tests__/activation-store.test.ts +5 -5
  426. package/src/memory/v2/__tests__/activation.test.ts +11 -4
  427. package/src/memory/v2/__tests__/backfill-jobs.test.ts +38 -21
  428. package/src/memory/v2/__tests__/consolidation-job.test.ts +123 -135
  429. package/src/memory/v2/__tests__/edge-index.test.ts +1 -1
  430. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +111 -0
  431. package/src/memory/v2/__tests__/injection.test.ts +628 -10
  432. package/src/memory/v2/__tests__/migration.test.ts +7 -3
  433. package/src/memory/v2/__tests__/page-index.test.ts +277 -0
  434. package/src/memory/v2/__tests__/page-store.test.ts +14 -1
  435. package/src/memory/v2/__tests__/prompts-router.test.ts +257 -0
  436. package/src/memory/v2/__tests__/qdrant.test.ts +72 -0
  437. package/src/memory/v2/__tests__/reranker.test.ts +4 -4
  438. package/src/memory/v2/__tests__/router.test.ts +516 -0
  439. package/src/memory/v2/__tests__/sim.test.ts +45 -1
  440. package/src/memory/v2/__tests__/skill-store.test.ts +58 -3
  441. package/src/memory/v2/__tests__/static-context.test.ts +7 -22
  442. package/src/memory/v2/__tests__/sweep-job.test.ts +95 -0
  443. package/src/memory/v2/activation-store.ts +34 -5
  444. package/src/memory/v2/activation.ts +40 -27
  445. package/src/memory/v2/backfill-jobs.ts +17 -84
  446. package/src/memory/v2/consolidation-job.ts +85 -78
  447. package/src/memory/v2/frontmatter-sweep.ts +91 -0
  448. package/src/memory/v2/injection.ts +440 -109
  449. package/src/memory/v2/migration.ts +117 -20
  450. package/src/memory/v2/page-index.ts +191 -0
  451. package/src/memory/v2/page-store.ts +3 -0
  452. package/src/memory/v2/prompts/consolidation.ts +9 -7
  453. package/src/memory/v2/prompts/router.ts +192 -0
  454. package/src/memory/v2/qdrant.ts +100 -87
  455. package/src/memory/v2/reranker.ts +14 -7
  456. package/src/memory/v2/router.ts +322 -0
  457. package/src/memory/v2/sim.ts +25 -12
  458. package/src/memory/v2/skill-store.ts +118 -29
  459. package/src/memory/v2/static-context.ts +16 -9
  460. package/src/memory/v2/sweep-job.ts +122 -96
  461. package/src/memory/v2/types.ts +10 -6
  462. package/src/memory/validation.ts +13 -0
  463. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +182 -0
  464. package/src/notifications/__tests__/home-feed-side-effect.test.ts +199 -0
  465. package/src/notifications/__tests__/signal-registry.test.ts +17 -0
  466. package/src/notifications/adapters/platform.ts +171 -0
  467. package/src/notifications/conversation-pairing.ts +2 -2
  468. package/src/notifications/copy-composer.ts +15 -0
  469. package/src/notifications/destination-resolver.ts +21 -0
  470. package/src/notifications/emit-signal.ts +28 -1
  471. package/src/notifications/home-feed-side-effect.ts +111 -0
  472. package/src/notifications/signal.ts +5 -0
  473. package/src/permissions/checker.ts +12 -0
  474. package/src/permissions/ipc-risk-types.ts +2 -0
  475. package/src/plugin-api/index.ts +13 -0
  476. package/src/plugin-api/package.json +12 -0
  477. package/src/plugin-api/types.ts +62 -0
  478. package/src/plugins/defaults/injectors.ts +19 -3
  479. package/src/plugins/external-plugin-loader.ts +294 -0
  480. package/src/plugins/types.ts +46 -30
  481. package/src/plugins/user-loader.ts +64 -41
  482. package/src/proactive-artifact/job.test.ts +12 -4
  483. package/src/proactive-artifact/job.ts +4 -0
  484. package/src/proactive-artifact/trigger-state.test.ts +9 -0
  485. package/src/proactive-artifact/trigger-state.ts +4 -0
  486. package/src/prompts/__tests__/system-prompt.test.ts +105 -0
  487. package/src/prompts/system-prompt.ts +22 -1
  488. package/src/prompts/update-bulletin-job.ts +61 -73
  489. package/src/providers/__tests__/dispatch-connection-routing.test.ts +279 -0
  490. package/src/providers/__tests__/inference.test.ts +288 -0
  491. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  492. package/src/providers/__tests__/provider-secret-catalog.test.ts +6 -0
  493. package/src/providers/__tests__/retry-callsite.test.ts +14 -32
  494. package/src/providers/__tests__/satellite-connection-routing.test.ts +510 -0
  495. package/src/providers/__tests__/search-provider-catalog.test.ts +80 -0
  496. package/src/providers/anthropic/client.ts +95 -26
  497. package/src/providers/call-site-routing.ts +94 -16
  498. package/src/providers/connection-resolution.ts +163 -0
  499. package/src/providers/inference/__tests__/connections-status-label.test.ts +250 -0
  500. package/src/providers/inference/adapter-factory.ts +173 -0
  501. package/src/providers/inference/auth.ts +112 -0
  502. package/src/providers/inference/backfill.ts +196 -0
  503. package/src/providers/inference/connections.ts +356 -0
  504. package/src/providers/inference/resolve-auth.ts +65 -0
  505. package/src/providers/model-catalog.ts +104 -6
  506. package/src/providers/openai/responses-provider.ts +4 -2
  507. package/src/providers/provider-env-vars.ts +17 -7
  508. package/src/providers/provider-secret-catalog.ts +49 -30
  509. package/src/providers/provider-send-message.ts +41 -20
  510. package/src/providers/registry.ts +143 -159
  511. package/src/providers/retry.ts +18 -10
  512. package/src/providers/search-provider-catalog.ts +121 -0
  513. package/src/runtime/AGENTS.md +18 -5
  514. package/src/runtime/__tests__/background-job-runner.test.ts +357 -0
  515. package/src/runtime/__tests__/pre-first-message-gate.test.ts +82 -0
  516. package/src/runtime/actor-trust-resolver.ts +32 -10
  517. package/src/runtime/agent-wake.ts +35 -6
  518. package/src/runtime/assistant-event-hub.ts +3 -85
  519. package/src/runtime/auth/route-policy.ts +303 -8
  520. package/src/runtime/auth/same-actor.ts +2 -0
  521. package/src/runtime/background-job-runner.ts +339 -0
  522. package/src/runtime/btw-sidechain.ts +1 -0
  523. package/src/runtime/http-router.ts +36 -1
  524. package/src/runtime/http-server.ts +31 -5
  525. package/src/runtime/http-types.ts +2 -0
  526. package/src/runtime/middleware/__tests__/request-logger.test.ts +162 -0
  527. package/src/runtime/middleware/request-logger.ts +62 -1
  528. package/src/runtime/pre-first-message-gate.ts +83 -0
  529. package/src/runtime/routes/__tests__/backup-routes.test.ts +8 -1
  530. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +251 -0
  531. package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +142 -0
  532. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +315 -0
  533. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +189 -0
  534. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +15 -136
  535. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +736 -0
  536. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +4 -4
  537. package/src/runtime/routes/__tests__/stt-routes.test.ts +5 -1
  538. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +384 -0
  539. package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
  540. package/src/runtime/routes/acp-routes.ts +10 -8
  541. package/src/runtime/routes/app-management-routes.ts +228 -3
  542. package/src/runtime/routes/approval-routes.ts +0 -18
  543. package/src/runtime/routes/audit-routes.ts +43 -0
  544. package/src/runtime/routes/auth-routes.ts +72 -0
  545. package/src/runtime/routes/avatar-routes.ts +273 -20
  546. package/src/runtime/routes/backup-routes.ts +406 -2
  547. package/src/runtime/routes/bookmark-routes.ts +154 -0
  548. package/src/runtime/routes/channel-verification-routes.ts +2 -1
  549. package/src/runtime/routes/contact-routes.ts +0 -160
  550. package/src/runtime/routes/conversation-cli-routes.ts +192 -0
  551. package/src/runtime/routes/conversation-management-routes.ts +30 -43
  552. package/src/runtime/routes/conversation-query-routes.ts +334 -86
  553. package/src/runtime/routes/conversation-routes.ts +31 -10
  554. package/src/runtime/routes/conversations-import-routes.ts +229 -0
  555. package/src/runtime/routes/credential-routes.ts +540 -0
  556. package/src/runtime/routes/debug-routes.ts +2 -2
  557. package/src/runtime/routes/document-pdf-renderer.ts +5 -1
  558. package/src/runtime/routes/domain-routes.ts +167 -0
  559. package/src/runtime/routes/email-routes.ts +603 -0
  560. package/src/runtime/routes/errors.ts +2 -2
  561. package/src/runtime/routes/events-routes.ts +192 -0
  562. package/src/runtime/routes/home-feed-routes.ts +6 -78
  563. package/src/runtime/routes/host-app-control-routes.ts +44 -2
  564. package/src/runtime/routes/host-browser-routes.ts +103 -22
  565. package/src/runtime/routes/http-adapter.ts +2 -0
  566. package/src/runtime/routes/identity-routes.ts +5 -0
  567. package/src/runtime/routes/image-generation-routes.ts +99 -0
  568. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +137 -1
  569. package/src/runtime/routes/inbound-stages/background-dispatch.ts +87 -7
  570. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +156 -0
  571. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +22 -4
  572. package/src/runtime/routes/index.ts +36 -0
  573. package/src/runtime/routes/inference-profile-session-handler.ts +312 -0
  574. package/src/runtime/routes/inference-profile-session-reaper.ts +98 -0
  575. package/src/runtime/routes/inference-profile-session-routes.ts +146 -0
  576. package/src/runtime/routes/inference-provider-connection-routes.ts +317 -0
  577. package/src/runtime/routes/inference-send-routes.ts +115 -0
  578. package/src/runtime/routes/integrations/twilio.ts +1 -0
  579. package/src/runtime/routes/mcp-auth-routes.ts +283 -9
  580. package/src/runtime/routes/memory-v2-routes.ts +13 -398
  581. package/src/runtime/routes/notification-routes.ts +2 -0
  582. package/src/runtime/routes/oauth-apps.ts +112 -7
  583. package/src/runtime/routes/oauth-commands-routes.ts +1007 -0
  584. package/src/runtime/routes/oauth-connect-routes.ts +67 -5
  585. package/src/runtime/routes/oauth-providers.ts +298 -8
  586. package/src/runtime/routes/platform-routes.ts +336 -0
  587. package/src/runtime/routes/playground/inject-failures.ts +2 -1
  588. package/src/runtime/routes/playground/reset-circuit.ts +2 -1
  589. package/src/runtime/routes/playground/state.ts +2 -1
  590. package/src/runtime/routes/publish-routes.ts +221 -0
  591. package/src/runtime/routes/schedule-routes.ts +82 -0
  592. package/src/runtime/routes/sequence-routes.ts +291 -0
  593. package/src/runtime/routes/settings-routes.ts +2 -10
  594. package/src/runtime/routes/skills-routes.ts +31 -1
  595. package/src/runtime/routes/stt-routes.ts +240 -3
  596. package/src/runtime/routes/surface-action-routes.ts +43 -7
  597. package/src/runtime/routes/tts-routes.ts +67 -0
  598. package/src/runtime/routes/types.ts +32 -0
  599. package/src/runtime/routes/user-routes-cli.ts +243 -0
  600. package/src/runtime/routes/webhook-routes.ts +165 -0
  601. package/src/runtime/sync/resource-sync-events.ts +25 -0
  602. package/src/runtime/sync/sync-publisher.test.ts +105 -0
  603. package/src/runtime/sync/sync-publisher.ts +21 -0
  604. package/src/schedule/scheduler.ts +200 -123
  605. package/src/security/__tests__/provider-key-env-fallback.test.ts +12 -6
  606. package/src/security/secret-patterns.ts +3 -0
  607. package/src/sequence/engine.ts +38 -40
  608. package/src/subagent/manager.ts +20 -15
  609. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +206 -0
  610. package/src/tools/browser/browser-execution.ts +15 -4
  611. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +174 -0
  612. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +16 -13
  613. package/src/tools/browser/cdp-client/extension-cdp-client.ts +24 -1
  614. package/src/tools/browser/cdp-client/factory.ts +66 -5
  615. package/src/tools/browser/runtime-check.ts +77 -0
  616. package/src/tools/memory/register.test.ts +3 -3
  617. package/src/tools/memory/register.ts +9 -1
  618. package/src/tools/network/__tests__/web-search.test.ts +156 -0
  619. package/src/tools/network/web-search.ts +280 -37
  620. package/src/tools/permission-checker.ts +13 -5
  621. package/src/tools/subagent/spawn.ts +3 -3
  622. package/src/tools/terminal/shell.ts +44 -0
  623. package/src/usage/attribution.ts +3 -2
  624. package/src/util/pricing.ts +86 -160
  625. package/src/watcher/__tests__/engine.test.ts +301 -0
  626. package/src/watcher/constants.ts +7 -0
  627. package/src/watcher/engine.ts +90 -90
  628. package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +6 -9
  629. package/src/workspace/migrations/054-seed-recall-callsite.ts +10 -1
  630. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +28 -4
  631. package/src/workspace/migrations/069-seed-onboarding-threads.ts +8 -2
  632. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +104 -0
  633. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +93 -0
  634. package/src/workspace/migrations/074-drop-deprecated-secret-detection-keys.ts +117 -0
  635. package/src/workspace/migrations/075-memory-v2-bm25-b-default-reembed.ts +61 -0
  636. package/src/workspace/migrations/076-drop-services-inference-mode.ts +62 -0
  637. package/src/workspace/migrations/077-seed-memory-router-callsite.ts +89 -0
  638. package/src/workspace/migrations/078-release-notes-tavily-web-search.ts +66 -0
  639. package/src/workspace/migrations/079-home-feed-notification-only.ts +197 -0
  640. package/src/workspace/migrations/080-restrict-vercel-api-token-metadata.ts +182 -0
  641. package/src/workspace/migrations/081-backfill-bash-allowed-tools-for-injection-credentials.ts +160 -0
  642. package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +154 -0
  643. package/src/workspace/migrations/registry.ts +22 -0
  644. package/src/workspace/migrations/runner.ts +13 -2
  645. package/src/workspace/migrations/types.ts +13 -3
  646. package/src/workspace/provider-commit-message-generator.ts +3 -2
  647. package/src/__tests__/context-search-pkb-source.test.ts +0 -498
  648. package/src/__tests__/credentials-cli.test.ts +0 -1225
  649. package/src/__tests__/memory-admin-recall.test.ts +0 -213
  650. package/src/approvals/__tests__/guardian-feed-event.test.ts +0 -303
  651. package/src/cli/commands/__tests__/email-download.test.ts +0 -260
  652. package/src/cli/commands/__tests__/email-list.test.ts +0 -216
  653. package/src/cli/commands/__tests__/email-register.test.ts +0 -186
  654. package/src/cli/commands/__tests__/email-send.test.ts +0 -416
  655. package/src/cli/commands/__tests__/email-status.test.ts +0 -185
  656. package/src/cli/commands/__tests__/email-unregister.test.ts +0 -168
  657. package/src/cli/commands/__tests__/routes.test.ts +0 -562
  658. package/src/cli/commands/__tests__/stt-transcribe.test.ts +0 -454
  659. package/src/cli/commands/autonomy.ts +0 -365
  660. package/src/cli/commands/memory.ts +0 -424
  661. package/src/cli/commands/oauth/__tests__/connect.test.ts +0 -947
  662. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +0 -686
  663. package/src/cli/commands/oauth/__tests__/mode.test.ts +0 -632
  664. package/src/cli/commands/oauth/__tests__/ping.test.ts +0 -631
  665. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +0 -573
  666. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +0 -330
  667. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +0 -521
  668. package/src/cli/commands/oauth/__tests__/status.test.ts +0 -551
  669. package/src/cli/commands/oauth/__tests__/token.test.ts +0 -420
  670. package/src/cli/lib/daemon-avatar-client.ts +0 -37
  671. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -87
  672. package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +0 -207
  673. package/src/daemon/__tests__/conversation-feed-event.test.ts +0 -304
  674. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +0 -233
  675. package/src/home/__tests__/assistant-feed-authoring.test.ts +0 -156
  676. package/src/home/__tests__/emit-feed-event.test.ts +0 -169
  677. package/src/home/__tests__/feed-population-integration.test.ts +0 -312
  678. package/src/home/__tests__/feed-scheduler.test.ts +0 -222
  679. package/src/home/__tests__/phase5-exit-criteria.test.ts +0 -229
  680. package/src/home/__tests__/platform-gmail-digest.test.ts +0 -222
  681. package/src/home/__tests__/rollup-producer.test.ts +0 -507
  682. package/src/home/assistant-feed-authoring.ts +0 -135
  683. package/src/home/emit-feed-event.ts +0 -169
  684. package/src/home/feed-scheduler.ts +0 -281
  685. package/src/home/platform-gmail-digest.ts +0 -163
  686. package/src/home/rewrite-command-preview.ts +0 -66
  687. package/src/home/rewrite-feed-title.ts +0 -58
  688. package/src/home/rollup-producer.ts +0 -426
  689. package/src/memory/admin.ts +0 -326
  690. package/src/memory/context-search/sources/pkb.ts +0 -476
  691. package/src/memory/graph/compaction.ts +0 -299
  692. /package/src/cli/{commands → lib}/cache-fs.ts +0 -0
@@ -203,7 +203,7 @@ describe("embedConceptPageJob — happy path", () => {
203
203
  test("reads the page, embeds it, and upserts to the v2 collection", async () => {
204
204
  await writePage(tmpWorkspace, {
205
205
  slug: "alice-prefers-vs-code",
206
- frontmatter: { edges: [], ref_files: [] },
206
+ frontmatter: { edges: [], ref_files: [], ref_urls: [] },
207
207
  body: "Alice prefers VS Code over Vim.\nShe ships at end of day.\n",
208
208
  });
209
209
 
@@ -231,7 +231,7 @@ describe("embedConceptPageJob — happy path", () => {
231
231
  test("populates the SQLite embedding cache row keyed on (concept_page, slug)", async () => {
232
232
  await writePage(tmpWorkspace, {
233
233
  slug: "bob-uses-zsh",
234
- frontmatter: { edges: [], ref_files: [] },
234
+ frontmatter: { edges: [], ref_files: [], ref_urls: [] },
235
235
  body: "Bob uses zsh.\n",
236
236
  });
237
237
 
@@ -257,6 +257,7 @@ describe("embedConceptPageJob — summary embedding", () => {
257
257
  frontmatter: {
258
258
  edges: [],
259
259
  ref_files: [],
260
+ ref_urls: [],
260
261
  summary: "A short prose summary that retrieval indexes separately.",
261
262
  },
262
263
  body: "Long-form body content.\n",
@@ -282,7 +283,7 @@ describe("embedConceptPageJob — summary embedding", () => {
282
283
  test("skips summary embedding when the page has no summary in frontmatter", async () => {
283
284
  await writePage(tmpWorkspace, {
284
285
  slug: "legacy-page",
285
- frontmatter: { edges: [], ref_files: [] },
286
+ frontmatter: { edges: [], ref_files: [], ref_urls: [] },
286
287
  body: "Body only — no summary in frontmatter.\n",
287
288
  });
288
289
 
@@ -303,6 +304,7 @@ describe("embedConceptPageJob — summary embedding", () => {
303
304
  frontmatter: {
304
305
  edges: [],
305
306
  ref_files: [],
307
+ ref_urls: [],
306
308
  summary: " ",
307
309
  },
308
310
  body: "Body content.\n",
@@ -324,14 +326,12 @@ describe("embedConceptPageJob — summary embedding", () => {
324
326
  frontmatter: {
325
327
  edges: [],
326
328
  ref_files: [],
329
+ ref_urls: [],
327
330
  summary: "First version of the summary.",
328
331
  },
329
332
  body: "Stable body that never changes.\n",
330
333
  });
331
- await embedConceptPageJob(
332
- makeJob({ slug: "cached-summary" }),
333
- TEST_CONFIG,
334
- );
334
+ await embedConceptPageJob(makeJob({ slug: "cached-summary" }), TEST_CONFIG);
335
335
  // Body + summary batched into a single backend call on first run.
336
336
  expect(embedWithBackendCalls).toHaveLength(1);
337
337
  expect(embedWithBackendCalls[0].inputs).toHaveLength(2);
@@ -344,14 +344,12 @@ describe("embedConceptPageJob — summary embedding", () => {
344
344
  frontmatter: {
345
345
  edges: [],
346
346
  ref_files: [],
347
+ ref_urls: [],
347
348
  summary: "Second version of the summary, different wording.",
348
349
  },
349
350
  body: "Stable body that never changes.\n",
350
351
  });
351
- await embedConceptPageJob(
352
- makeJob({ slug: "cached-summary" }),
353
- TEST_CONFIG,
354
- );
352
+ await embedConceptPageJob(makeJob({ slug: "cached-summary" }), TEST_CONFIG);
355
353
  // One additional backend call with only the summary text — body hit the cache.
356
354
  expect(embedWithBackendCalls).toHaveLength(2);
357
355
  expect(embedWithBackendCalls[1].inputs).toHaveLength(1);
@@ -362,7 +360,7 @@ describe("embedConceptPageJob — cache hit", () => {
362
360
  test("reuses the cached dense vector when content hash matches", async () => {
363
361
  await writePage(tmpWorkspace, {
364
362
  slug: "alice-prefers-vs-code",
365
- frontmatter: { edges: [], ref_files: [] },
363
+ frontmatter: { edges: [], ref_files: [], ref_urls: [] },
366
364
  body: "Stable content.\n",
367
365
  });
368
366
 
@@ -387,7 +385,7 @@ describe("embedConceptPageJob — cache hit", () => {
387
385
  test("re-embeds when the body changes (content hash mismatch)", async () => {
388
386
  await writePage(tmpWorkspace, {
389
387
  slug: "alice-prefers-vs-code",
390
- frontmatter: { edges: [], ref_files: [] },
388
+ frontmatter: { edges: [], ref_files: [], ref_urls: [] },
391
389
  body: "First content.\n",
392
390
  });
393
391
  await embedConceptPageJob(
@@ -398,7 +396,7 @@ describe("embedConceptPageJob — cache hit", () => {
398
396
  // Rewrite with different body.
399
397
  await writePage(tmpWorkspace, {
400
398
  slug: "alice-prefers-vs-code",
401
- frontmatter: { edges: [], ref_files: [] },
399
+ frontmatter: { edges: [], ref_files: [], ref_urls: [] },
402
400
  body: "Second content (different).\n",
403
401
  });
404
402
  await embedConceptPageJob(
@@ -453,7 +451,7 @@ describe("enqueueEmbedConceptPageJob", () => {
453
451
  test("round-trip: enqueued job dispatches through embedConceptPageJob", async () => {
454
452
  await writePage(tmpWorkspace, {
455
453
  slug: "round-trip-slug",
456
- frontmatter: { edges: [], ref_files: [] },
454
+ frontmatter: { edges: [], ref_files: [], ref_urls: [] },
457
455
  body: "Round-trip body.\n",
458
456
  });
459
457
 
@@ -163,21 +163,54 @@ export async function embedConceptPageJob(
163
163
  slots.push("summary");
164
164
  }
165
165
 
166
- let bodyDense: number[] | undefined = bodyCacheHit ? bodyCache!.dense : undefined;
166
+ let bodyDense: number[] | undefined = bodyCacheHit
167
+ ? bodyCache!.dense
168
+ : undefined;
167
169
  let summaryDense: number[] | undefined = summaryCacheHit
168
170
  ? summaryCache!.dense
169
171
  : undefined;
170
172
  let writeProvider = cacheProvider;
171
173
  let writeModel = cacheModel;
174
+ let bodyFresh = false;
175
+ let summaryFresh = false;
172
176
  if (toEmbed.length > 0) {
173
- const embedded = await embedWithBackend(config, toEmbed);
177
+ let embedded = await embedWithBackend(config, toEmbed);
178
+ let appliedSlots = slots;
179
+ // Backend rotation between `getMemoryBackendStatus()` and
180
+ // `embedWithBackend()` would tag the cached half with the old
181
+ // provider/model and the fresh half with the new — writing both into
182
+ // one Qdrant point mixes embedding spaces. Re-embed every slot fresh
183
+ // when we detect the rotation so the point's named vectors share one
184
+ // identity.
185
+ const rotated =
186
+ (bodyCacheHit || summaryCacheHit) &&
187
+ (embedded.provider !== cacheProvider || embedded.model !== cacheModel);
188
+ if (rotated) {
189
+ const allTexts: Array<{ type: "text"; text: string }> = [
190
+ { type: "text", text },
191
+ ];
192
+ const allSlots: Slot[] = ["body"];
193
+ if (hasSummary) {
194
+ allTexts.push({ type: "text", text: summaryText });
195
+ allSlots.push("summary");
196
+ }
197
+ embedded = await embedWithBackend(config, allTexts);
198
+ appliedSlots = allSlots;
199
+ bodyDense = undefined;
200
+ summaryDense = undefined;
201
+ }
174
202
  writeProvider = embedded.provider;
175
203
  writeModel = embedded.model;
176
- for (let i = 0; i < slots.length; i++) {
204
+ for (let i = 0; i < appliedSlots.length; i++) {
177
205
  const vector = embedded.vectors[i];
178
206
  if (!vector) continue;
179
- if (slots[i] === "body") bodyDense = vector;
180
- else summaryDense = vector;
207
+ if (appliedSlots[i] === "body") {
208
+ bodyDense = vector;
209
+ bodyFresh = true;
210
+ } else {
211
+ summaryDense = vector;
212
+ summaryFresh = true;
213
+ }
181
214
  }
182
215
  }
183
216
  // Body embedding is the ground truth — without it the page can't surface.
@@ -205,9 +238,12 @@ export async function embedConceptPageJob(
205
238
  const now = Date.now();
206
239
  // Persist freshly embedded vectors for cross-restart reuse. On cache hit
207
240
  // the existing row already has identical content + hash, so the write
208
- // would be a no-op — skip it. Best-effort: write failure is not fatal,
209
- // we still want the Qdrant upsert below to fire.
210
- if (!bodyCacheHit) {
241
+ // would be a no-op — skip it. Backend rotation flips a cache hit into a
242
+ // fresh embed (see `rotated` above); the `*Fresh` flags capture that so
243
+ // the new vector overwrites the now-stale cache row under the new
244
+ // provider/model identity. Best-effort: write failure is not fatal, we
245
+ // still want the Qdrant upsert below to fire.
246
+ if (bodyFresh) {
211
247
  writeEmbeddingCache(db, {
212
248
  slug,
213
249
  cacheId: slug,
@@ -218,7 +254,7 @@ export async function embedConceptPageJob(
218
254
  now,
219
255
  });
220
256
  }
221
- if (hasSummary && !summaryCacheHit && summaryDense && summaryContentHash) {
257
+ if (hasSummary && summaryFresh && summaryDense && summaryContentHash) {
222
258
  writeEmbeddingCache(db, {
223
259
  slug,
224
260
  cacheId: summaryCacheId,
@@ -42,7 +42,8 @@ export type MemoryJobType =
42
42
  | "memory_v2_consolidate"
43
43
  | "memory_v2_migrate"
44
44
  | "memory_v2_reembed"
45
- | "memory_v2_activation_recompute";
45
+ | "memory_v2_activation_recompute"
46
+ | "memory_retrospective";
46
47
 
47
48
  export const EMBED_JOB_TYPES: MemoryJobType[] = [
48
49
  "embed_segment",
@@ -52,6 +53,7 @@ export const EMBED_JOB_TYPES: MemoryJobType[] = [
52
53
  "embed_graph_node",
53
54
  "embed_pkb_file",
54
55
  "graph_trigger_embed",
56
+ "embed_concept_page",
55
57
  ];
56
58
 
57
59
  export const SLOW_LLM_JOB_TYPES: MemoryJobType[] = [
@@ -65,6 +67,7 @@ export const SLOW_LLM_JOB_TYPES: MemoryJobType[] = [
65
67
  "memory_v2_sweep",
66
68
  "memory_v2_consolidate",
67
69
  "memory_v2_migrate",
70
+ "memory_retrospective",
68
71
  "backfill",
69
72
  "graph_bootstrap",
70
73
  ];
@@ -251,6 +254,53 @@ export function upsertAutoAnalysisJob(
251
254
  }
252
255
  }
253
256
 
257
+ /**
258
+ * Upsert a pending `memory_retrospective` job keyed by `conversationId`. All
259
+ * four retrospective triggers (interval, message_count, compaction,
260
+ * lifecycle) collapse into a single pending row per conversation — rapid
261
+ * triggers coalesce instead of double-firing. The `runAfter` parameter on a
262
+ * follow-up enqueue overwrites the existing row's `runAfter` so a sooner
263
+ * trigger can pull a later-scheduled job earlier; a later-scheduled trigger
264
+ * does NOT push a sooner-scheduled row further out (consumer takes the
265
+ * minimum). The trigger metadata is intentionally not retained — it is only
266
+ * useful for observability at enqueue time.
267
+ */
268
+ export function upsertMemoryRetrospectiveJob(
269
+ payload: { conversationId: string },
270
+ runAfter: number = Date.now(),
271
+ dbOverride?: Parameters<ReturnType<typeof getDb>["transaction"]>[0] extends (
272
+ tx: infer T,
273
+ ) => unknown
274
+ ? T
275
+ : never,
276
+ ): void {
277
+ const db = dbOverride ?? getDb();
278
+ const existing = db
279
+ .select()
280
+ .from(memoryJobs)
281
+ .where(
282
+ and(
283
+ eq(memoryJobs.type, "memory_retrospective"),
284
+ eq(memoryJobs.status, "pending"),
285
+ sql`json_extract(${memoryJobs.payload}, '$.conversationId') = ${payload.conversationId}`,
286
+ ),
287
+ )
288
+ .get();
289
+ if (existing) {
290
+ // Take the minimum of the existing and incoming runAfter so the earliest
291
+ // trigger always wins. A later trigger never pushes work further out.
292
+ const nextRunAfter = Math.min(existing.runAfter, runAfter);
293
+ if (nextRunAfter !== existing.runAfter) {
294
+ db.update(memoryJobs)
295
+ .set({ runAfter: nextRunAfter, updatedAt: Date.now() })
296
+ .where(eq(memoryJobs.id, existing.id))
297
+ .run();
298
+ }
299
+ return;
300
+ }
301
+ enqueueMemoryJob("memory_retrospective", payload, runAfter, dbOverride);
302
+ }
303
+
254
304
  /**
255
305
  * Check whether a pending or running job of the given type already exists.
256
306
  * Used to prevent duplicate enqueues for long-running maintenance jobs.
@@ -67,6 +67,8 @@ import {
67
67
  resetRunningJobsToPending,
68
68
  SLOW_LLM_JOB_TYPES,
69
69
  } from "./jobs-store.js";
70
+ import { memoryRetrospectiveJob } from "./memory-retrospective-job.js";
71
+ import { sweepOrphanMemoryRetrospectiveConversations } from "./memory-retrospective-startup-cleanup.js";
70
72
  import { QdrantCircuitOpenError } from "./qdrant-circuit-breaker.js";
71
73
  import {
72
74
  memoryV2ActivationRecomputeJob,
@@ -78,6 +80,26 @@ import { memoryV2SweepJob } from "./v2/sweep-job.js";
78
80
 
79
81
  const log = getLogger("memory-jobs-worker");
80
82
 
83
+ /**
84
+ * V1 job types that read or write the v1 Qdrant collection via
85
+ * `getQdrantClient()`. When `memory.v2.enabled` is true, the v1 client is
86
+ * intentionally left uninitialized in `lifecycle.ts`, so these handlers would
87
+ * throw `BackendUnavailableError` and accumulate as a deferred backlog. Stale
88
+ * rows from indexer.ts and other unguarded enqueue sites must short-circuit
89
+ * here for the same reason `graph_extract` does below.
90
+ */
91
+ const V1_QDRANT_JOB_TYPES = new Set<MemoryJobType>([
92
+ "embed_segment",
93
+ "embed_summary",
94
+ "embed_media",
95
+ "embed_attachment",
96
+ "embed_graph_node",
97
+ "embed_pkb_file",
98
+ "graph_trigger_embed",
99
+ "rebuild_index",
100
+ "delete_qdrant_vectors",
101
+ ]);
102
+
81
103
  /**
82
104
  * Job types whose handlers have been removed. Existing rows may still sit in
83
105
  * the database — the worker completes them silently instead of throwing.
@@ -111,6 +133,19 @@ export function startMemoryJobsWorker(): MemoryJobsWorker {
111
133
  log.info({ recovered }, "Recovered stale running memory jobs");
112
134
  }
113
135
 
136
+ // After running-job recovery (so legitimate in-flight retries aren't
137
+ // swept), clean up orphan memory-retrospective background conversations
138
+ // left behind by daemon crashes mid-job. Best-effort — never block worker
139
+ // startup on cleanup failures.
140
+ try {
141
+ sweepOrphanMemoryRetrospectiveConversations();
142
+ } catch (err) {
143
+ log.warn(
144
+ { err },
145
+ "Memory-retrospective startup cleanup failed; continuing worker startup",
146
+ );
147
+ }
148
+
114
149
  let stopped = false;
115
150
  let tickRunning = false;
116
151
  let timer: ReturnType<typeof setTimeout>;
@@ -124,16 +159,24 @@ export function startMemoryJobsWorker(): MemoryJobsWorker {
124
159
  enableScheduledCleanup: true,
125
160
  });
126
161
  if (processed > 0) {
127
- currentIntervalMs = POLL_INTERVAL_MIN_MS;
162
+ // Per-tick claim budget equals the lane caps, so when a tick
163
+ // processed work the next tick must run immediately to drain any
164
+ // remaining backlog. Holding the 1.5s floor between ticks would cap
165
+ // sustained throughput at lane-cap jobs per 1.5s and starve large
166
+ // backlogs of short jobs.
167
+ currentIntervalMs = 0;
128
168
  } else {
129
169
  currentIntervalMs = Math.min(
130
- currentIntervalMs * 2,
170
+ Math.max(currentIntervalMs * 2, POLL_INTERVAL_MIN_MS),
131
171
  POLL_INTERVAL_MAX_MS,
132
172
  );
133
173
  }
134
174
  } catch (err) {
135
175
  log.error({ err }, "Memory worker tick failed");
136
- currentIntervalMs = Math.min(currentIntervalMs * 2, POLL_INTERVAL_MAX_MS);
176
+ currentIntervalMs = Math.min(
177
+ Math.max(currentIntervalMs * 2, POLL_INTERVAL_MIN_MS),
178
+ POLL_INTERVAL_MAX_MS,
179
+ );
137
180
  } finally {
138
181
  tickRunning = false;
139
182
  }
@@ -462,6 +505,9 @@ async function processJob(
462
505
  job: MemoryJob,
463
506
  config: AssistantConfig,
464
507
  ): Promise<void> {
508
+ if (config.memory.v2.enabled && V1_QDRANT_JOB_TYPES.has(job.type)) {
509
+ return;
510
+ }
465
511
  switch (job.type) {
466
512
  case "embed_segment":
467
513
  await embedSegmentJob(job, config);
@@ -555,6 +601,9 @@ async function processJob(
555
601
  case "memory_v2_activation_recompute":
556
602
  await memoryV2ActivationRecomputeJob(job, config);
557
603
  return;
604
+ case "memory_retrospective":
605
+ await memoryRetrospectiveJob(job, config);
606
+ return;
558
607
 
559
608
  default: {
560
609
  const rawType = (job as { type: string }).type;
@@ -0,0 +1,317 @@
1
+ /**
2
+ * ClickHouse-backed LLM request log read source.
3
+ *
4
+ * Reads from the ClickHouse mirror (populated out-of-band by the
5
+ * `mirror-llm-logs-to-clickhouse` cron). Scoped to the running
6
+ * assistant's own `assistant_id` — never cross-assistant. URL and
7
+ * password are resolved lazily from the credential store
8
+ * (`clickhouse:url`, `clickhouse:password`); database/table/user come
9
+ * from workspace config.
10
+ *
11
+ * Known limitation: the mirror is INSERT-only. A row inserted locally
12
+ * with `message_id = NULL` and backfilled later will appear in
13
+ * ClickHouse with `message_id = ''` forever. Reads via this source for
14
+ * the most-recent ~minute of activity therefore have lower fidelity
15
+ * than the local source. Acceptable for the "internal use while we
16
+ * finetune prompts" use case; revisit when mirror updates are added.
17
+ */
18
+ import type { LlmRequestLogsClickHouseConfig } from "../config/schemas/llm-request-logs.js";
19
+ import { credentialKey } from "../security/credential-key.js";
20
+ import { getSecureKeyAsync } from "../security/secure-keys.js";
21
+ import { getLogger } from "../util/logger.js";
22
+ import {
23
+ getAssistantMessageIdsInTurn,
24
+ getMessageById,
25
+ messageMetadataSchema,
26
+ } from "./conversation-crud.js";
27
+ import type { LlmRequestLogSource } from "./llm-request-log-source.js";
28
+ import type { LogRow } from "./llm-request-log-store.js";
29
+
30
+ const log = getLogger("clickhouse-llm-request-log-source");
31
+
32
+ /**
33
+ * Read a credential and normalize `undefined` → `null`. The credential
34
+ * resolver factories on this class are typed `() => Promise<string | null>`;
35
+ * `getSecureKeyAsync` returns `Promise<string | undefined>`. Keep the
36
+ * coercion in one place so TypeScript stays happy without per-call casts.
37
+ */
38
+ async function readCredentialOrNull(
39
+ service: string,
40
+ field: string,
41
+ ): Promise<string | null> {
42
+ const value = await getSecureKeyAsync(credentialKey(service, field));
43
+ return value ?? null;
44
+ }
45
+
46
+ /**
47
+ * Wire-format row returned by ClickHouse for our query columns. Note
48
+ * that `created_at` arrives as a string because Int64 is emitted as a
49
+ * quoted string under the default `output_format_json_quote_64bit_integers=1`
50
+ * setting; we coerce to `number` in `toLogRow`.
51
+ */
52
+ interface ClickHouseRow {
53
+ id: string;
54
+ conversation_id: string;
55
+ message_id: string;
56
+ provider: string;
57
+ request_payload: string;
58
+ response_payload: string;
59
+ created_at: string;
60
+ }
61
+
62
+ /** Injectable fetch override for tests. Defaults to globalThis.fetch. */
63
+ export type ClickHouseFetch = typeof fetch;
64
+
65
+ /** Minimal subset of the SQLite message row the fork-source fallback needs. */
66
+ export interface ClickHouseMessageRow {
67
+ metadata: string | null;
68
+ }
69
+
70
+ export interface ClickHouseLlmRequestLogSourceDeps {
71
+ /** Override the credential read for `clickhouse:url`. */
72
+ resolveUrl?: () => Promise<string | null>;
73
+ /** Override the credential read for `clickhouse:password`. */
74
+ resolvePassword?: () => Promise<string | null>;
75
+ /** Override the credential read for `vellum:platform_assistant_id`. */
76
+ resolveAssistantId?: () => Promise<string | null>;
77
+ /** Override the turn-id resolver (default: `getAssistantMessageIdsInTurn`). */
78
+ resolveTurnMessageIds?: (messageId: string) => string[];
79
+ /** Override the message lookup (default: `getMessageById`). */
80
+ resolveMessage?: (messageId: string) => ClickHouseMessageRow | null;
81
+ /** Override fetch for testing. */
82
+ fetchImpl?: ClickHouseFetch;
83
+ }
84
+
85
+ export class ClickHouseLlmRequestLogSource implements LlmRequestLogSource {
86
+ private cachedUrl: string | null = null;
87
+ private cachedPassword: string | null = null;
88
+ private cachedAssistantId: string | null = null;
89
+
90
+ private readonly resolveUrl: () => Promise<string | null>;
91
+ private readonly resolvePassword: () => Promise<string | null>;
92
+ private readonly resolveAssistantId: () => Promise<string | null>;
93
+ private readonly resolveTurnMessageIds: (messageId: string) => string[];
94
+ private readonly resolveMessage: (
95
+ messageId: string,
96
+ ) => ClickHouseMessageRow | null;
97
+ private readonly fetchImpl: ClickHouseFetch;
98
+
99
+ constructor(
100
+ private readonly config: LlmRequestLogsClickHouseConfig,
101
+ deps: ClickHouseLlmRequestLogSourceDeps = {},
102
+ ) {
103
+ this.resolveUrl =
104
+ deps.resolveUrl ?? (() => readCredentialOrNull("clickhouse", "url"));
105
+ this.resolvePassword =
106
+ deps.resolvePassword ??
107
+ (() => readCredentialOrNull("clickhouse", "password"));
108
+ this.resolveAssistantId =
109
+ deps.resolveAssistantId ??
110
+ (() => readCredentialOrNull("vellum", "platform_assistant_id"));
111
+ this.resolveTurnMessageIds =
112
+ deps.resolveTurnMessageIds ?? getAssistantMessageIdsInTurn;
113
+ this.resolveMessage = deps.resolveMessage ?? getMessageById;
114
+ this.fetchImpl = deps.fetchImpl ?? globalThis.fetch.bind(globalThis);
115
+ }
116
+
117
+ async getRequestLogById(logId: string): Promise<LogRow | null> {
118
+ const aid = await this.assistantId();
119
+ const sql = `SELECT
120
+ id,
121
+ conversation_id,
122
+ message_id,
123
+ provider,
124
+ request_payload,
125
+ response_payload,
126
+ toUnixTimestamp64Milli(created_at) AS created_at
127
+ FROM ${this.tableRef()}
128
+ WHERE assistant_id = {assistant_id:String}
129
+ AND id = {log_id:String}
130
+ ORDER BY created_at DESC
131
+ LIMIT 1
132
+ FORMAT JSONEachRow`;
133
+ const rows = await this.exec(sql, { assistant_id: aid, log_id: logId });
134
+ return rows[0] ? this.toLogRow(rows[0]) : null;
135
+ }
136
+
137
+ async getRequestLogsByMessageId(messageId: string): Promise<LogRow[]> {
138
+ const turnIds = this.resolveTurnMessageIds(messageId);
139
+ let rows = await this.selectByMessageIds(turnIds);
140
+
141
+ if (rows.length === 0) {
142
+ // Fork-source fallback. Mirror behavior of the local source: when no
143
+ // logs match the queried message's turn, see if it was forked from
144
+ // another and resolve that source's turn. The fork relationship lives
145
+ // in local SQLite (message.metadata.forkSourceMessageId), not CH.
146
+ const message = this.resolveMessage(messageId);
147
+ if (message?.metadata) {
148
+ try {
149
+ const parsed = messageMetadataSchema.safeParse(
150
+ JSON.parse(message.metadata),
151
+ );
152
+ const sourceMessageId =
153
+ parsed.success &&
154
+ typeof parsed.data.forkSourceMessageId === "string"
155
+ ? parsed.data.forkSourceMessageId
156
+ : null;
157
+ if (sourceMessageId && sourceMessageId !== messageId) {
158
+ const sourceTurnIds = this.resolveTurnMessageIds(sourceMessageId);
159
+ rows = await this.selectByMessageIds(sourceTurnIds);
160
+ }
161
+ } catch {
162
+ // metadata not JSON / schema mismatch — no fork fallback, return []
163
+ }
164
+ }
165
+ }
166
+
167
+ return rows.sort(
168
+ (a, b) => a.createdAt - b.createdAt || a.id.localeCompare(b.id),
169
+ );
170
+ }
171
+
172
+ private async selectByMessageIds(ids: string[]): Promise<LogRow[]> {
173
+ if (ids.length === 0) return [];
174
+ const aid = await this.assistantId();
175
+ // ClickHouse Array(String) URL encoding is fiddly; the message IDs are
176
+ // server-generated UUIDs (or safe internal strings), so inline the
177
+ // literal after escaping single quotes. No SQL-injection surface — the
178
+ // values originate from our own SQLite messages table.
179
+ const idLiteral =
180
+ "[" +
181
+ ids.map((id) => `'${id.replace(/'/g, "''")}'`).join(",") +
182
+ "]";
183
+ const sql = `SELECT
184
+ id,
185
+ conversation_id,
186
+ message_id,
187
+ provider,
188
+ request_payload,
189
+ response_payload,
190
+ toUnixTimestamp64Milli(created_at) AS created_at
191
+ FROM ${this.tableRef()}
192
+ WHERE assistant_id = {assistant_id:String}
193
+ AND message_id IN ${idLiteral}
194
+ ORDER BY created_at ASC, id ASC
195
+ LIMIT 1 BY id
196
+ FORMAT JSONEachRow`;
197
+ const rows = await this.exec(sql, { assistant_id: aid });
198
+ return rows.map((r) => this.toLogRow(r));
199
+ }
200
+
201
+ private tableRef(): string {
202
+ // Database is set via the `database=` URL param in `exec`, so we only
203
+ // need to quote the table identifier here. Backtick-quote both to
204
+ // tolerate non-default names with special characters.
205
+ return `\`${this.config.table.replace(/`/g, "``")}\``;
206
+ }
207
+
208
+ private async exec(
209
+ sql: string,
210
+ params: Record<string, string>,
211
+ ): Promise<ClickHouseRow[]> {
212
+ const baseUrl = await this.url();
213
+ const password = await this.password();
214
+
215
+ let target: URL;
216
+ try {
217
+ target = new URL(baseUrl);
218
+ } catch (err) {
219
+ throw new Error(
220
+ `clickhouse:url is not a valid URL: ${err instanceof Error ? err.message : String(err)}`,
221
+ );
222
+ }
223
+ target.searchParams.set("database", this.config.database);
224
+ for (const [k, v] of Object.entries(params)) {
225
+ target.searchParams.set(`param_${k}`, v);
226
+ }
227
+
228
+ const auth =
229
+ "Basic " +
230
+ Buffer.from(`${this.config.user}:${password}`, "utf8").toString("base64");
231
+
232
+ const res = await this.fetchImpl(target.toString(), {
233
+ method: "POST",
234
+ headers: { Authorization: auth, "Content-Type": "text/plain" },
235
+ body: sql,
236
+ });
237
+
238
+ if (!res.ok) {
239
+ const body = await res.text().catch(() => "");
240
+ log.error(
241
+ { status: res.status, table: this.config.table, bodySnippet: body.slice(0, 200) },
242
+ "ClickHouse query failed",
243
+ );
244
+ throw new Error(
245
+ `ClickHouse query failed (HTTP ${res.status}): ${body.slice(0, 500)}`,
246
+ );
247
+ }
248
+
249
+ const text = await res.text();
250
+ if (text.trim().length === 0) return [];
251
+
252
+ const rows: ClickHouseRow[] = [];
253
+ for (const line of text.split("\n")) {
254
+ const trimmed = line.trim();
255
+ if (trimmed.length === 0) continue;
256
+ try {
257
+ rows.push(JSON.parse(trimmed) as ClickHouseRow);
258
+ } catch (err) {
259
+ throw new Error(
260
+ `Failed to parse ClickHouse JSONEachRow line: ${err instanceof Error ? err.message : String(err)}`,
261
+ );
262
+ }
263
+ }
264
+ return rows;
265
+ }
266
+
267
+ private toLogRow(row: ClickHouseRow): LogRow {
268
+ return {
269
+ id: row.id,
270
+ conversationId: row.conversation_id,
271
+ // The mirror writes empty-string for missing message_id/provider
272
+ // because the CH table columns have `DEFAULT ''` (Nullable adds
273
+ // overhead). Map empty back to null to match the local LogRow shape.
274
+ messageId: row.message_id === "" ? null : row.message_id,
275
+ provider: row.provider === "" ? null : row.provider,
276
+ requestPayload: row.request_payload,
277
+ responsePayload: row.response_payload,
278
+ createdAt: Number(row.created_at),
279
+ };
280
+ }
281
+
282
+ private async assistantId(): Promise<string> {
283
+ if (this.cachedAssistantId) return this.cachedAssistantId;
284
+ const val = await this.resolveAssistantId();
285
+ if (!val) {
286
+ throw new Error(
287
+ "vellum:platform_assistant_id credential is required when readSource=clickhouse",
288
+ );
289
+ }
290
+ this.cachedAssistantId = val;
291
+ return val;
292
+ }
293
+
294
+ private async url(): Promise<string> {
295
+ if (this.cachedUrl) return this.cachedUrl;
296
+ const val = await this.resolveUrl();
297
+ if (!val) {
298
+ throw new Error(
299
+ "clickhouse:url credential is required when readSource=clickhouse",
300
+ );
301
+ }
302
+ this.cachedUrl = val;
303
+ return val;
304
+ }
305
+
306
+ private async password(): Promise<string> {
307
+ if (this.cachedPassword) return this.cachedPassword;
308
+ const val = await this.resolvePassword();
309
+ if (!val) {
310
+ throw new Error(
311
+ "clickhouse:password credential is required when readSource=clickhouse",
312
+ );
313
+ }
314
+ this.cachedPassword = val;
315
+ return val;
316
+ }
317
+ }