@vellumai/assistant 0.8.5 → 0.8.6

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 (544) hide show
  1. package/AGENTS.md +33 -1
  2. package/ARCHITECTURE.md +1 -1
  3. package/bunfig.toml +6 -1
  4. package/docs/credential-execution-service.md +6 -6
  5. package/docs/plugins.md +4 -3
  6. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +12 -13
  7. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +4 -1
  8. package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +16 -14
  9. package/openapi.yaml +1900 -166
  10. package/package.json +1 -1
  11. package/src/__tests__/actor-token-service.test.ts +3 -2
  12. package/src/__tests__/agent-loop-exit-reason.test.ts +102 -9
  13. package/src/__tests__/agent-loop-override-profile.test.ts +2 -1
  14. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
  15. package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
  16. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  17. package/src/__tests__/annotate-risk-options.test.ts +1 -0
  18. package/src/__tests__/approval-cascade.test.ts +1 -0
  19. package/src/__tests__/approval-routes-http.test.ts +9 -13
  20. package/src/__tests__/assert-not-live-db.ts +79 -0
  21. package/src/__tests__/assistant-feature-flags-integration.test.ts +9 -25
  22. package/src/__tests__/audit-log-rotation.test.ts +2 -2
  23. package/src/__tests__/auto-analysis-end-to-end.test.ts +6 -6
  24. package/src/__tests__/background-workers-disk-pressure.test.ts +5 -8
  25. package/src/__tests__/browser-skill-endstate.test.ts +3 -3
  26. package/src/__tests__/btw-routes.test.ts +3 -2
  27. package/src/__tests__/call-controller.test.ts +3 -2
  28. package/src/__tests__/channel-approval-routes.test.ts +3 -2
  29. package/src/__tests__/channel-guardian.test.ts +3 -2
  30. package/src/__tests__/channel-readiness-slack-remote.test.ts +175 -0
  31. package/src/__tests__/channel-reply-delivery.test.ts +35 -0
  32. package/src/__tests__/channel-retry-sweep.test.ts +320 -3
  33. package/src/__tests__/checker.test.ts +12 -12
  34. package/src/__tests__/compaction-events.test.ts +1 -0
  35. package/src/__tests__/compaction-trail-store.test.ts +264 -0
  36. package/src/__tests__/compactor-call-site-logging.test.ts +1 -0
  37. package/src/__tests__/compactor-preserved-tail-count.test.ts +1 -0
  38. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +7 -5
  39. package/src/__tests__/computer-use-tools.test.ts +12 -14
  40. package/src/__tests__/config-loader-backfill.test.ts +13 -28
  41. package/src/__tests__/config-loader-corrupt.test.ts +5 -5
  42. package/src/__tests__/config-loader-platform-defaults.test.ts +93 -26
  43. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +3 -3
  44. package/src/__tests__/config-managed-gemini-defaults.test.ts +3 -4
  45. package/src/__tests__/config-schema.test.ts +10 -10
  46. package/src/__tests__/connection-model-compat.test.ts +83 -0
  47. package/src/__tests__/contacts-tools.test.ts +3 -2
  48. package/src/__tests__/context-token-estimator.test.ts +22 -0
  49. package/src/__tests__/conversation-abort-tool-results.test.ts +5 -0
  50. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -0
  51. package/src/__tests__/conversation-agent-loop-handlers-max-tokens.test.ts +55 -0
  52. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  53. package/src/__tests__/conversation-agent-loop-overflow.test.ts +34 -0
  54. package/src/__tests__/conversation-agent-loop.test.ts +488 -2
  55. package/src/__tests__/conversation-analysis-routes.test.ts +1 -0
  56. package/src/__tests__/conversation-app-control-instantiation.test.ts +29 -19
  57. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -0
  58. package/src/__tests__/conversation-attention-store.test.ts +101 -0
  59. package/src/__tests__/conversation-attention-telegram.test.ts +3 -2
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
  61. package/src/__tests__/conversation-error.test.ts +30 -0
  62. package/src/__tests__/conversation-fork-crud.test.ts +69 -8
  63. package/src/__tests__/conversation-fork-route.test.ts +3 -2
  64. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  65. package/src/__tests__/conversation-inference-profile-list.test.ts +3 -2
  66. package/src/__tests__/conversation-inference-profile-route.test.ts +3 -2
  67. package/src/__tests__/conversation-lifecycle.test.ts +1 -0
  68. package/src/__tests__/conversation-list-source.test.ts +3 -2
  69. package/src/__tests__/conversation-load-history-repair.test.ts +2 -1
  70. package/src/__tests__/conversation-load-history-stripped.test.ts +1 -0
  71. package/src/__tests__/conversation-pairing.test.ts +53 -0
  72. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +26 -7
  73. package/src/__tests__/conversation-process-callsite.test.ts +1 -0
  74. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -0
  75. package/src/__tests__/conversation-queue.test.ts +333 -291
  76. package/src/__tests__/conversation-routes-disk-view.test.ts +3 -18
  77. package/src/__tests__/conversation-routes-guardian-reply.test.ts +33 -8
  78. package/src/__tests__/conversation-routes-slash-commands.test.ts +33 -2
  79. package/src/__tests__/conversation-runtime-assembly.test.ts +78 -0
  80. package/src/__tests__/conversation-skill-tools.test.ts +38 -142
  81. package/src/__tests__/conversation-slash-queue.test.ts +84 -32
  82. package/src/__tests__/conversation-slash-unknown.test.ts +5 -0
  83. package/src/__tests__/conversation-speed-override.test.ts +1 -0
  84. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +46 -0
  85. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -0
  86. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +6 -3
  87. package/src/__tests__/conversation-surfaces-standalone.test.ts +6 -3
  88. package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -3
  89. package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -17
  90. package/src/__tests__/conversation-sync-tags.test.ts +128 -12
  91. package/src/__tests__/conversation-title-service.test.ts +1 -0
  92. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +30 -0
  93. package/src/__tests__/conversation-usage.test.ts +1 -0
  94. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
  95. package/src/__tests__/conversation-workspace-injection.test.ts +5 -0
  96. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -0
  97. package/src/__tests__/credential-broker-browser-fill.test.ts +3 -3
  98. package/src/__tests__/credential-broker-server-use.test.ts +5 -5
  99. package/src/__tests__/credential-execution-client.test.ts +72 -1
  100. package/src/__tests__/credential-execution-feature-gates.test.ts +10 -12
  101. package/src/__tests__/credential-health-service.test.ts +252 -3
  102. package/src/__tests__/credential-security-invariants.test.ts +5 -5
  103. package/src/__tests__/credential-vault-unit.test.ts +19 -19
  104. package/src/__tests__/credential-vault.test.ts +5 -5
  105. package/src/__tests__/cross-provider-web-search.test.ts +56 -2
  106. package/src/__tests__/db-connection-isolation.test.ts +7 -6
  107. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +8 -10
  108. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +7 -10
  109. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +9 -15
  110. package/src/__tests__/db-test-helpers.ts +58 -0
  111. package/src/__tests__/disk-pressure-guard.test.ts +58 -41
  112. package/src/__tests__/disk-pressure-lifecycle.test.ts +13 -10
  113. package/src/__tests__/disk-pressure-routes.test.ts +0 -33
  114. package/src/__tests__/disk-pressure-tools.test.ts +0 -4
  115. package/src/__tests__/dm-persistence.test.ts +26 -40
  116. package/src/__tests__/document-create-dedupe.test.ts +189 -0
  117. package/src/__tests__/document-find-replace.test.ts +3 -2
  118. package/src/__tests__/document-tool-security.test.ts +81 -2
  119. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +5 -4
  120. package/src/__tests__/encrypted-store-test-helpers.ts +56 -0
  121. package/src/__tests__/encrypted-store.test.ts +11 -9
  122. package/src/__tests__/feature-flag-test-helpers.ts +53 -0
  123. package/src/__tests__/filing-service.test.ts +1 -0
  124. package/src/__tests__/first-greeting.test.ts +62 -12
  125. package/src/__tests__/gateway-flag-listener.test.ts +0 -1
  126. package/src/__tests__/gemini-provider.test.ts +26 -0
  127. package/src/__tests__/guardian-action-sweep.test.ts +3 -2
  128. package/src/__tests__/guardian-outbound-http.test.ts +3 -2
  129. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +48 -3
  130. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -0
  131. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  132. package/src/__tests__/heartbeat-service.test.ts +1 -0
  133. package/src/__tests__/helpers/mock-logger.ts +26 -0
  134. package/src/__tests__/host-bash-routes.test.ts +1 -0
  135. package/src/__tests__/host-cu-routes-targeted.test.ts +1 -0
  136. package/src/__tests__/host-file-routes-targeted.test.ts +1 -0
  137. package/src/__tests__/host-shell-tool.test.ts +5 -4
  138. package/src/__tests__/host-transfer-routes-targeted.test.ts +1 -0
  139. package/src/__tests__/http-conversation-lineage.test.ts +3 -2
  140. package/src/__tests__/http-user-message-parity.test.ts +29 -7
  141. package/src/__tests__/identity-intro-cache.test.ts +133 -22
  142. package/src/__tests__/inbound-slack-persistence.test.ts +44 -72
  143. package/src/__tests__/inference-profile-reaper.test.ts +3 -2
  144. package/src/__tests__/inference-profile-session-ipc.test.ts +3 -2
  145. package/src/__tests__/injector-disk-pressure.test.ts +3 -17
  146. package/src/__tests__/inline-skill-load-permissions.test.ts +4 -4
  147. package/src/__tests__/list-messages-hidden-metadata.test.ts +80 -0
  148. package/src/__tests__/llm-context-normalization.test.ts +42 -0
  149. package/src/__tests__/llm-resolver.test.ts +331 -0
  150. package/src/__tests__/llm-schema.test.ts +1 -1
  151. package/src/__tests__/manual-token-reconciliation.test.ts +76 -1
  152. package/src/__tests__/mcp-abort-signal.test.ts +14 -0
  153. package/src/__tests__/mcp-client-auth.test.ts +14 -0
  154. package/src/__tests__/messaging-send-tool.test.ts +1 -0
  155. package/src/__tests__/migration-import-from-url.test.ts +3 -3
  156. package/src/__tests__/mock-gateway-ipc.ts +18 -2
  157. package/src/__tests__/model-intents.test.ts +3 -3
  158. package/src/__tests__/native-web-search.test.ts +30 -2
  159. package/src/__tests__/notification-deep-link.test.ts +62 -0
  160. package/src/__tests__/oauth-commands-routes.test.ts +37 -0
  161. package/src/__tests__/oauth-provider-visibility.test.ts +8 -8
  162. package/src/__tests__/oauth-store.test.ts +3 -2
  163. package/src/__tests__/onboarding-template-contract.test.ts +3 -2
  164. package/src/__tests__/openai-provider.test.ts +8 -9
  165. package/src/__tests__/openai-responses-provider.test.ts +70 -10
  166. package/src/__tests__/openrouter-provider-only.test.ts +27 -5
  167. package/src/__tests__/outbound-slack-persistence.test.ts +46 -1
  168. package/src/__tests__/persistence-pipeline.test.ts +139 -1
  169. package/src/__tests__/persistence-secret-redaction.test.ts +83 -12
  170. package/src/__tests__/plugin-bootstrap.test.ts +9 -11
  171. package/src/__tests__/plugin-tool-contribution.test.ts +41 -38
  172. package/src/__tests__/process-message-background-slack.test.ts +21 -16
  173. package/src/__tests__/process-message-display-content.test.ts +19 -22
  174. package/src/__tests__/provider-catalog-visibility.test.ts +9 -9
  175. package/src/__tests__/provider-platform-proxy-integration.test.ts +216 -4
  176. package/src/__tests__/provider-registry-ollama.test.ts +45 -22
  177. package/src/__tests__/recording-handler.test.ts +1 -0
  178. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
  179. package/src/__tests__/registry.test.ts +82 -76
  180. package/src/__tests__/relay-server.test.ts +10 -10
  181. package/src/__tests__/runtime-attachment-metadata.test.ts +3 -2
  182. package/src/__tests__/schedule-store.test.ts +16 -1
  183. package/src/__tests__/scheduler-reuse-conversation.test.ts +48 -3
  184. package/src/__tests__/secret-ingress-http.test.ts +5 -1
  185. package/src/__tests__/secure-keys.test.ts +3 -3
  186. package/src/__tests__/send-endpoint-busy.test.ts +81 -42
  187. package/src/__tests__/server-history-render.test.ts +4 -1
  188. package/src/__tests__/skill-feature-flags-integration.test.ts +8 -10
  189. package/src/__tests__/skill-feature-flags.test.ts +14 -16
  190. package/src/__tests__/skill-load-feature-flag.test.ts +5 -5
  191. package/src/__tests__/skill-projection-feature-flag.test.ts +44 -30
  192. package/src/__tests__/skill-projection.benchmark.test.ts +5 -7
  193. package/src/__tests__/skill-tool-factory.test.ts +96 -95
  194. package/src/__tests__/slack-channel-config.test.ts +3 -3
  195. package/src/__tests__/subagent-call-site-routing.test.ts +11 -3
  196. package/src/__tests__/subagent-disposal.test.ts +27 -8
  197. package/src/__tests__/subagent-fork-notifications.test.ts +24 -9
  198. package/src/__tests__/subagent-fork-spawn.test.ts +13 -4
  199. package/src/__tests__/subagent-manager-notify.test.ts +20 -8
  200. package/src/__tests__/subagent-notify-parent.test.ts +5 -4
  201. package/src/__tests__/subagent-spawn-tool-fork.test.ts +58 -0
  202. package/src/__tests__/subagent-tools.test.ts +2 -1
  203. package/src/__tests__/suggestion-routes.test.ts +1 -0
  204. package/src/__tests__/system-prompt.test.ts +38 -0
  205. package/src/__tests__/test-preload-verifier.ts +68 -0
  206. package/src/__tests__/test-preload.ts +32 -39
  207. package/src/__tests__/tool-executor-lifecycle-events.test.ts +20 -7
  208. package/src/__tests__/tool-executor.test.ts +55 -10
  209. package/src/__tests__/tool-preview-lifecycle.test.ts +1 -0
  210. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  211. package/src/__tests__/twilio-routes.test.ts +3 -2
  212. package/src/__tests__/validate-input.test.ts +381 -0
  213. package/src/__tests__/verification-control-plane-policy.test.ts +1 -0
  214. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -1
  215. package/src/__tests__/voice-session-bridge.test.ts +37 -28
  216. package/src/__tests__/workspace-migration-090-memory-router-cost-optimized-profile.test.ts +326 -0
  217. package/src/__tests__/workspace-migration-091-retighten-migration-onboarding-thread.test.ts +166 -0
  218. package/src/acp/session-manager.ts +5 -6
  219. package/src/agent/loop.ts +80 -0
  220. package/src/api/README.md +124 -2
  221. package/src/api/constants/call-sites.ts +27 -0
  222. package/src/api/events/assistant-outbound-attachment.ts +51 -0
  223. package/src/api/events/assistant-text-delta.ts +32 -0
  224. package/src/api/events/assistant-turn-start.ts +33 -0
  225. package/src/api/events/document-comment-created.ts +48 -0
  226. package/src/api/events/document-comment-deleted.ts +24 -0
  227. package/src/api/events/document-comment-reopened.ts +25 -0
  228. package/src/api/events/document-comment-resolved.ts +27 -0
  229. package/src/api/events/generation-cancelled.ts +24 -0
  230. package/src/api/events/generation-handoff.ts +41 -0
  231. package/src/api/events/message-complete.ts +42 -0
  232. package/src/api/events/open-url.ts +30 -0
  233. package/src/{events → api/events}/relationship-state-updated.ts +3 -3
  234. package/src/api/events/tool-use-start.ts +32 -0
  235. package/src/api/index.ts +128 -3
  236. package/src/api/responses/llm-context-response.ts +39 -0
  237. package/src/api/responses/llm-request-log-entry.ts +93 -0
  238. package/src/api/responses/memory-recall-log.ts +65 -0
  239. package/src/api/responses/memory-v2-activation-log.ts +78 -0
  240. package/src/background-wake/background-wake-routes.test.ts +687 -52
  241. package/src/background-wake/platform-client.test.ts +308 -0
  242. package/src/background-wake/platform-client.ts +167 -0
  243. package/src/background-wake/publisher.ts +91 -0
  244. package/src/background-wake/runtime-registry.ts +2 -2
  245. package/src/background-wake/wake-intent-hooks.test.ts +282 -0
  246. package/src/calls/guardian-dispatch.ts +1 -0
  247. package/src/calls/voice-session-bridge.ts +4 -4
  248. package/src/cli/commands/__tests__/conversations-slack.test.ts +16 -0
  249. package/src/cli/commands/__tests__/notifications.test.ts +184 -40
  250. package/src/cli/commands/channels/__tests__/channels.test.ts +143 -0
  251. package/src/cli/commands/channels/index.ts +229 -0
  252. package/src/cli/commands/memory-v3-render.ts +147 -0
  253. package/src/cli/commands/memory-v3.ts +255 -4
  254. package/src/cli/commands/notifications.ts +365 -55
  255. package/src/cli/lib/open-browser.ts +7 -2
  256. package/src/cli/program.ts +2 -0
  257. package/src/config/assistant-feature-flags.ts +23 -42
  258. package/src/config/bundled-skills/document-editor/SKILL.md +5 -1
  259. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  260. package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
  261. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -0
  262. package/src/config/call-site-defaults.ts +1 -1
  263. package/src/config/feature-flag-cache.ts +86 -0
  264. package/src/config/feature-flag-registry.json +17 -17
  265. package/src/config/llm-context-resolution.ts +10 -1
  266. package/src/config/llm-resolver.ts +121 -15
  267. package/src/config/loader.ts +4 -5
  268. package/src/config/schemas/__tests__/memory-v2.test.ts +15 -0
  269. package/src/config/schemas/heartbeat.ts +1 -1
  270. package/src/config/schemas/llm.ts +90 -1
  271. package/src/config/schemas/memory-v2.ts +26 -0
  272. package/src/config/schemas/services.ts +6 -2
  273. package/src/config/seed-inference-profiles.ts +36 -16
  274. package/src/context/token-estimator.ts +10 -5
  275. package/src/credential-execution/executable-discovery.ts +40 -0
  276. package/src/credential-execution/process-manager.ts +6 -2
  277. package/src/credential-health/credential-health-service.ts +125 -40
  278. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -6
  279. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +13 -15
  280. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -2
  281. package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -0
  282. package/src/daemon/__tests__/meet-manifest-loader.test.ts +25 -12
  283. package/src/daemon/__tests__/native-web-search-metadata.test.ts +1 -0
  284. package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +107 -0
  285. package/src/daemon/__tests__/web-search-status-text.test.ts +1 -0
  286. package/src/daemon/conversation-agent-loop-handlers.ts +389 -68
  287. package/src/daemon/conversation-agent-loop.ts +132 -28
  288. package/src/daemon/conversation-error.ts +33 -5
  289. package/src/daemon/conversation-messaging.ts +84 -43
  290. package/src/daemon/conversation-process.ts +74 -37
  291. package/src/daemon/conversation-runtime-assembly.ts +29 -9
  292. package/src/daemon/conversation-skill-tools.ts +14 -30
  293. package/src/daemon/conversation-surfaces.ts +69 -34
  294. package/src/daemon/conversation-tool-setup.ts +33 -48
  295. package/src/daemon/conversation.ts +26 -46
  296. package/src/daemon/daemon-control.ts +1 -1
  297. package/src/daemon/daemon-skill-host.ts +9 -2
  298. package/src/daemon/disk-pressure-guard.ts +27 -29
  299. package/src/daemon/first-greeting.ts +31 -13
  300. package/src/daemon/handlers/shared.ts +6 -1
  301. package/src/daemon/lifecycle.ts +12 -12
  302. package/src/daemon/mcp-reload-service.ts +1 -1
  303. package/src/daemon/meet-manifest-loader.ts +10 -17
  304. package/src/daemon/message-types/conversations.ts +20 -22
  305. package/src/daemon/message-types/document-comments.ts +8 -44
  306. package/src/daemon/message-types/home.ts +2 -2
  307. package/src/daemon/message-types/integrations.ts +2 -7
  308. package/src/daemon/message-types/messages.ts +23 -38
  309. package/src/daemon/message-types/subagents.ts +6 -0
  310. package/src/daemon/process-message.ts +9 -9
  311. package/src/daemon/providers-setup.ts +1 -1
  312. package/src/daemon/server.ts +16 -0
  313. package/src/daemon/switch-inference-profile-tool.ts +13 -3
  314. package/src/daemon/tool-setup-types.ts +0 -6
  315. package/src/daemon/wake-target-adapter.ts +10 -0
  316. package/src/documents/document-store.ts +38 -0
  317. package/src/export/__tests__/transcript-formatter.test.ts +1 -0
  318. package/src/heartbeat/__tests__/heartbeat-service.test.ts +29 -0
  319. package/src/heartbeat/heartbeat-service.ts +63 -0
  320. package/src/home/__tests__/feed-writer.test.ts +161 -0
  321. package/src/home/__tests__/post-connect-feed.test.ts +1 -0
  322. package/src/home/__tests__/suggested-prompts.test.ts +55 -59
  323. package/src/home/feed-writer.ts +146 -7
  324. package/src/home/suggested-prompts.ts +27 -145
  325. package/src/ipc/__tests__/cli-ipc.test.ts +1 -0
  326. package/src/ipc/gateway-client.test.ts +4 -1
  327. package/src/ipc/skill-routes/__tests__/memory.test.ts +1 -0
  328. package/src/ipc/skill-routes/__tests__/registries.test.ts +36 -7
  329. package/src/ipc/skill-routes/memory.ts +4 -3
  330. package/src/ipc/skill-routes/registries.ts +28 -29
  331. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +1 -0
  332. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +26 -5
  333. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +1 -0
  334. package/src/memory/__tests__/memory-retrospective-job.test.ts +1 -0
  335. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +1 -0
  336. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +31 -0
  337. package/src/memory/conversation-attention-store.ts +17 -3
  338. package/src/memory/conversation-crud.ts +352 -112
  339. package/src/memory/db-connection.ts +29 -19
  340. package/src/memory/db-init.ts +4 -0
  341. package/src/memory/db-singleton.ts +77 -0
  342. package/src/memory/delivery-channels.ts +82 -0
  343. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +2 -4
  344. package/src/memory/graph/retriever.test.ts +3 -3
  345. package/src/memory/job-handlers/embedding.test.ts +3 -2
  346. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -2
  347. package/src/memory/jobs-worker.ts +12 -1
  348. package/src/memory/llm-request-log-source-clickhouse.ts +80 -0
  349. package/src/memory/llm-request-log-source-local.ts +24 -0
  350. package/src/memory/llm-request-log-source.ts +31 -0
  351. package/src/memory/llm-request-log-store.ts +188 -3
  352. package/src/memory/memory-v2-activation-log-store.ts +95 -1
  353. package/src/memory/migrations/265-drop-provider-connection-status.ts +26 -0
  354. package/src/memory/migrations/266-messages-client-message-id.ts +43 -0
  355. package/src/memory/migrations/index.ts +2 -0
  356. package/src/memory/schema/conversations.ts +9 -1
  357. package/src/memory/schema/inference.ts +0 -1
  358. package/src/memory/v2/__tests__/backfill-jobs.test.ts +5 -2
  359. package/src/memory/v2/__tests__/harness-metrics.test.ts +9 -0
  360. package/src/memory/v2/__tests__/harness-replay-input.test.ts +9 -4
  361. package/src/memory/v2/__tests__/harness-runner.test.ts +26 -0
  362. package/src/memory/v2/__tests__/sweep-job.test.ts +6 -3
  363. package/src/memory/v2/harness/metrics.ts +5 -1
  364. package/src/memory/v2/harness/replay-input.ts +19 -3
  365. package/src/memory/v2/harness/runner.ts +6 -0
  366. package/src/memory/v2/harness/trace.ts +6 -0
  367. package/src/memory/v3/__tests__/consolidation-job.test.ts +2 -4
  368. package/src/memory/v3/__tests__/coretrieval-seed.test.ts +270 -0
  369. package/src/memory/v3/__tests__/edges.test.ts +144 -1
  370. package/src/memory/v3/__tests__/filter.test.ts +48 -0
  371. package/src/memory/v3/__tests__/gate.test.ts +96 -33
  372. package/src/memory/v3/__tests__/index-composition.test.ts +58 -0
  373. package/src/memory/v3/__tests__/loop.test.ts +250 -5
  374. package/src/memory/v3/__tests__/scouts.test.ts +49 -0
  375. package/src/memory/v3/__tests__/shadow-diff.test.ts +225 -0
  376. package/src/memory/v3/__tests__/shadow-middleware.test.ts +88 -2
  377. package/src/memory/v3/__tests__/traversal.test.ts +39 -0
  378. package/src/memory/v3/__tests__/tree-walk.test.ts +77 -0
  379. package/src/memory/v3/__tests__/validate.test.ts +32 -0
  380. package/src/memory/v3/coretrieval-seed.ts +240 -0
  381. package/src/memory/v3/edges.ts +58 -21
  382. package/src/memory/v3/filter.ts +27 -22
  383. package/src/memory/v3/gate.ts +51 -36
  384. package/src/memory/v3/index-composition.ts +18 -5
  385. package/src/memory/v3/loop.ts +65 -17
  386. package/src/memory/v3/scouts.ts +15 -4
  387. package/src/memory/v3/shadow-diff.ts +287 -0
  388. package/src/memory/v3/shadow-middleware.ts +44 -2
  389. package/src/memory/v3/traversal.ts +6 -1
  390. package/src/memory/v3/tree-walk.ts +6 -1
  391. package/src/memory/v3/validate.ts +56 -33
  392. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  393. package/src/notifications/__tests__/home-feed-side-effect.test.ts +1 -0
  394. package/src/notifications/adapters/slack.ts +45 -11
  395. package/src/notifications/broadcaster.ts +114 -63
  396. package/src/notifications/conversation-pairing.ts +23 -3
  397. package/src/notifications/decisions-store.ts +32 -1
  398. package/src/notifications/deliveries-store.ts +45 -0
  399. package/src/notifications/edit-notification.ts +201 -0
  400. package/src/notifications/emit-signal.ts +11 -1
  401. package/src/notifications/signal.ts +10 -0
  402. package/src/notifications/types.ts +37 -0
  403. package/src/oauth/byo-connection.test.ts +67 -3
  404. package/src/oauth/byo-connection.ts +32 -5
  405. package/src/oauth/connect-orchestrator.ts +9 -0
  406. package/src/oauth/connection-resolver.test.ts +76 -0
  407. package/src/oauth/connection-resolver.ts +49 -10
  408. package/src/oauth/manual-token-connection.ts +51 -3
  409. package/src/oauth/seed-providers.ts +3 -0
  410. package/src/permissions/approval-policy.test.ts +19 -5
  411. package/src/permissions/approval-policy.ts +14 -3
  412. package/src/permissions/checker.ts +21 -8
  413. package/src/platform/client.test.ts +24 -1
  414. package/src/platform/client.ts +8 -0
  415. package/src/platform/feature-gate.ts +15 -0
  416. package/src/plugins/defaults/injectors.ts +2 -8
  417. package/src/plugins/defaults/persistence.ts +25 -6
  418. package/src/plugins/types.ts +57 -13
  419. package/src/proactive-artifact/job.test.ts +1 -0
  420. package/src/prompts/__tests__/system-prompt.test.ts +4 -4
  421. package/src/prompts/system-prompt.ts +38 -40
  422. package/src/prompts/template-detection.ts +10 -4
  423. package/src/prompts/templates/BOOTSTRAP.md +7 -11
  424. package/src/prompts/templates/IDENTITY.md +0 -2
  425. package/src/providers/__tests__/connection-model-compat.test.ts +3 -4
  426. package/src/providers/__tests__/registry-native-web-search.test.ts +122 -0
  427. package/src/providers/call-site-routing.ts +33 -9
  428. package/src/providers/connection-model-compat.ts +23 -0
  429. package/src/providers/connection-resolution.ts +39 -20
  430. package/src/providers/fireworks/client.ts +1 -0
  431. package/src/providers/gemini/client.ts +24 -3
  432. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +0 -2
  433. package/src/providers/inference/__tests__/base-url-security.test.ts +2 -3
  434. package/src/providers/inference/__tests__/{connections-status-label.test.ts → connections-label.test.ts} +12 -111
  435. package/src/providers/inference/auth.ts +0 -8
  436. package/src/providers/inference/connections.ts +3 -66
  437. package/src/providers/inference/resolve-auth.ts +2 -3
  438. package/src/providers/model-catalog.ts +35 -1
  439. package/src/providers/model-intents.ts +3 -3
  440. package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
  441. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +157 -5
  442. package/src/providers/openai/chat-completions-provider.ts +110 -12
  443. package/src/providers/openai/codex-models.ts +2 -0
  444. package/src/providers/openai/responses-provider.ts +53 -53
  445. package/src/providers/openrouter/client.ts +13 -8
  446. package/src/providers/provider-send-message.ts +18 -9
  447. package/src/providers/registry.ts +48 -8
  448. package/src/providers/retry.ts +16 -4
  449. package/src/providers/search-provider-catalog.ts +17 -9
  450. package/src/providers/types.ts +9 -0
  451. package/src/runtime/__tests__/agent-wake.test.ts +1 -0
  452. package/src/runtime/__tests__/background-job-runner.test.ts +1 -0
  453. package/src/runtime/access-request-helper.ts +1 -0
  454. package/src/runtime/auth/route-policy.ts +10 -0
  455. package/src/runtime/channel-readiness-service.ts +68 -0
  456. package/src/runtime/channel-reply-delivery.ts +23 -0
  457. package/src/runtime/channel-retry-sweep.ts +47 -14
  458. package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
  459. package/src/runtime/migrations/vbundle-builder.ts +3 -2
  460. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -0
  461. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +406 -0
  462. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
  463. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  464. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +209 -1
  465. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +13 -50
  466. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +51 -3
  467. package/src/runtime/routes/__tests__/memory-v3-simulate-params.test.ts +35 -0
  468. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +3 -2
  469. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +294 -0
  470. package/src/runtime/routes/__tests__/task-routes.test.ts +48 -3
  471. package/src/runtime/routes/acp-routes-list.test.ts +3 -0
  472. package/src/runtime/routes/app-management-routes.ts +111 -4
  473. package/src/runtime/routes/background-wake-routes.ts +188 -20
  474. package/src/runtime/routes/btw-routes.ts +4 -4
  475. package/src/runtime/routes/conversation-analysis-routes.ts +6 -0
  476. package/src/runtime/routes/conversation-compaction-routes.ts +263 -0
  477. package/src/runtime/routes/conversation-list-routes.ts +147 -0
  478. package/src/runtime/routes/conversation-management-routes.ts +39 -14
  479. package/src/runtime/routes/conversation-query-routes.ts +60 -10
  480. package/src/runtime/routes/conversation-routes.ts +186 -140
  481. package/src/runtime/routes/conversations-import-routes.ts +19 -6
  482. package/src/runtime/routes/documents-routes.ts +10 -1
  483. package/src/runtime/routes/group-routes.ts +11 -0
  484. package/src/runtime/routes/home-feed-routes.ts +129 -0
  485. package/src/runtime/routes/identity-intro-cache.ts +61 -16
  486. package/src/runtime/routes/identity-routes.ts +30 -9
  487. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +530 -6
  488. package/src/runtime/routes/inbound-stages/background-dispatch.ts +57 -8
  489. package/src/runtime/routes/index.ts +2 -0
  490. package/src/runtime/routes/inference-provider-connection-routes.ts +5 -26
  491. package/src/runtime/routes/integrations/vercel.ts +15 -0
  492. package/src/runtime/routes/llm-context-normalization.ts +7 -2
  493. package/src/runtime/routes/memory-v3-routes.ts +160 -2
  494. package/src/runtime/routes/migration-routes.ts +20 -13
  495. package/src/runtime/routes/notification-routes.ts +63 -1
  496. package/src/runtime/routes/oauth-commands-routes.ts +6 -1
  497. package/src/runtime/routes/surface-action-routes.ts +1 -38
  498. package/src/runtime/routes/surface-content-routes.ts +12 -5
  499. package/src/runtime/routes/surface-conversation-resolver.ts +65 -0
  500. package/src/runtime/routes/wipe-conversation-routes.ts +3 -0
  501. package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -0
  502. package/src/runtime/slack-dm-text-delivery.ts +177 -0
  503. package/src/runtime/sync/resource-sync-events.ts +1 -1
  504. package/src/runtime/tool-grant-request-helper.ts +1 -0
  505. package/src/schedule/schedule-store.ts +8 -1
  506. package/src/schedule/scheduler.ts +111 -15
  507. package/src/security/__tests__/provider-key-env-fallback.test.ts +3 -3
  508. package/src/security/encrypted-store.ts +7 -16
  509. package/src/security/store-path-override.ts +61 -0
  510. package/src/signals/user-message.ts +5 -8
  511. package/src/skills/validate-input.ts +177 -0
  512. package/src/subagent/manager.ts +13 -13
  513. package/src/subagent/types.ts +6 -0
  514. package/src/tasks/tool-sanitizer.ts +2 -2
  515. package/src/tools/apps/definitions.ts +35 -21
  516. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +2 -8
  517. package/src/tools/computer-use/definitions.ts +268 -266
  518. package/src/tools/document/document-tool.ts +131 -8
  519. package/src/tools/execution-target.ts +2 -5
  520. package/src/tools/executor.ts +18 -55
  521. package/src/tools/host-filesystem/edit.test.ts +1 -0
  522. package/src/tools/host-filesystem/read.test.ts +1 -0
  523. package/src/tools/host-filesystem/transfer.test.ts +31 -6
  524. package/src/tools/host-filesystem/write.test.ts +1 -0
  525. package/src/tools/mcp/mcp-tool-factory.ts +0 -2
  526. package/src/tools/network/__tests__/managed-search-proxy.test.ts +282 -0
  527. package/src/tools/network/__tests__/web-search.test.ts +211 -3
  528. package/src/tools/network/managed-search-proxy.ts +183 -0
  529. package/src/tools/network/web-search.ts +199 -44
  530. package/src/tools/policy-context.ts +3 -1
  531. package/src/tools/registry.ts +146 -103
  532. package/src/tools/schedule/create.ts +1 -1
  533. package/src/tools/skills/skill-tool-factory.ts +17 -36
  534. package/src/tools/subagent/spawn.ts +3 -0
  535. package/src/tools/tool-approval-handler.ts +10 -4
  536. package/src/tools/tool-name-aliases.ts +72 -14
  537. package/src/tools/types.ts +17 -15
  538. package/src/tools/ui-surface/definitions.ts +98 -86
  539. package/src/types/onboarding-context.ts +6 -0
  540. package/src/usage/attribution.ts +32 -1
  541. package/src/util/browser.ts +7 -2
  542. package/src/workspace/migrations/090-memory-router-cost-optimized-profile.ts +109 -0
  543. package/src/workspace/migrations/091-retighten-migration-onboarding-thread.ts +41 -0
  544. package/src/workspace/migrations/registry.ts +4 -0
@@ -1,6 +1,21 @@
1
- import { and, asc, desc, eq, gte, inArray, isNull, lte, sql } from "drizzle-orm";
1
+ import {
2
+ and,
3
+ asc,
4
+ desc,
5
+ eq,
6
+ gt,
7
+ gte,
8
+ inArray,
9
+ isNull,
10
+ lt,
11
+ lte,
12
+ ne,
13
+ or,
14
+ sql,
15
+ } from "drizzle-orm";
2
16
  import { v4 as uuid } from "uuid";
3
17
 
18
+ import { CALL_SITE_SYNTHETIC_AGENT_ERROR_MESSAGE } from "../api/constants/call-sites.js";
4
19
  import type { LLMCallSite } from "../config/schemas/llm.js";
5
20
  import { AssistantError, ProviderError } from "../util/errors.js";
6
21
  import {
@@ -105,6 +120,80 @@ export function recordRequestLog(
105
120
  return id;
106
121
  }
107
122
 
123
+ /**
124
+ * Insert a synthetic `llm_request_logs` row for an agent-loop error
125
+ * message that has no LLM call backing it but should appear in the
126
+ * inspector rail. Today the only caller is the
127
+ * `budget_yield_unrecovered` persistence path
128
+ * (`conversation-agent-loop.ts`); the helper is named generically so
129
+ * the next out-of-funds / provider-error / etc. path can route through
130
+ * the same primitive.
131
+ *
132
+ * The caller persists the user-visible assistant message separately
133
+ * via the `persistence` pipeline; this helper only writes the synthetic
134
+ * call row. `messageId` should be the id of the just-persisted notice
135
+ * so `getRequestLogsByMessageId` surfaces both together.
136
+ *
137
+ * Payload semantics mirror real LLM-call rows:
138
+ * - `requestPayload`: the best-known LLM request body the loop was
139
+ * about to send when it yielded — typically the prepared messages
140
+ * snapshot and any input-token budget context. Stored as JSON so
141
+ * the Raw tab renders it consistently with real calls.
142
+ * - `responsePayload`: the synthetic notice text the user saw plus
143
+ * the exit reason. This is the "response" from the user's point of
144
+ * view — what came back from a call that never actually happened.
145
+ *
146
+ * Stamps `agent_loop_exit_reason` directly so the row already carries
147
+ * the reason at insert time — the post-loop
148
+ * `setAgentLoopExitReasonOnLatestLog` query then skips it (its IS NULL
149
+ * guard) and stamps the prior real LLM call instead, preserving the
150
+ * existing "latest LLM call carries the exit reason" invariant that
151
+ * other consumers depend on.
152
+ */
153
+ export function recordSyntheticAgentErrorMessageLog(args: {
154
+ conversationId: string;
155
+ messageId: string;
156
+ exitReason: string;
157
+ /** User-visible notice text — goes into `response_payload`. */
158
+ noticeText: string;
159
+ /**
160
+ * Best-known LLM request state at the moment the loop gave up.
161
+ * `null` when no prepared request was available (rare — generally
162
+ * we know at least the conversation history we were about to send).
163
+ */
164
+ preparedRequest: unknown | null;
165
+ createdAt: number;
166
+ }): string {
167
+ const db = getDb();
168
+ const id = uuid();
169
+ const requestPayload = JSON.stringify({
170
+ syntheticAgentErrorMessage: {
171
+ exitReason: args.exitReason,
172
+ preparedRequest: args.preparedRequest,
173
+ },
174
+ });
175
+ const responsePayload = JSON.stringify({
176
+ syntheticAgentErrorMessage: {
177
+ exitReason: args.exitReason,
178
+ noticeText: args.noticeText,
179
+ },
180
+ });
181
+ db.insert(llmRequestLogs)
182
+ .values({
183
+ id,
184
+ conversationId: args.conversationId,
185
+ messageId: args.messageId,
186
+ provider: null,
187
+ requestPayload,
188
+ responsePayload,
189
+ createdAt: args.createdAt,
190
+ agentLoopExitReason: args.exitReason,
191
+ callSite: CALL_SITE_SYNTHETIC_AGENT_ERROR_MESSAGE,
192
+ })
193
+ .run();
194
+ return id;
195
+ }
196
+
108
197
  /**
109
198
  * Stamp an `agent_loop_exit_reason` onto the most-recent unstamped
110
199
  * `llm_request_logs` row for the given conversation. Called by the
@@ -208,7 +297,9 @@ function selectLogsByMessageIds(messageIds: string[]): LogRow[] {
208
297
  * not apply turn recovery: the `conversation_id` column already includes
209
298
  * linked, unlinked, and orphaned rows for the full conversation.
210
299
  */
211
- export function getRequestLogsByConversationId(conversationId: string): LogRow[] {
300
+ export function getRequestLogsByConversationId(
301
+ conversationId: string,
302
+ ): LogRow[] {
212
303
  const db = getDb();
213
304
  return db
214
305
  .select({
@@ -310,6 +401,100 @@ function selectUnlinkedLogsInRange(
310
401
  .all();
311
402
  }
312
403
 
404
+ /**
405
+ * Fetch every `compactionAgent` log row in the conversation whose
406
+ * `createdAt` falls in the **open window** `(afterCreatedAt, beforeCreatedAt)`,
407
+ * ordered chronologically.
408
+ *
409
+ * Drives the Inspector's Compaction tab. The caller resolves both
410
+ * bounds:
411
+ * - `beforeCreatedAt` = the selected LLM call's `createdAt` (ceiling).
412
+ * - `afterCreatedAt` = the previous non-`compactionAgent` call's
413
+ * `createdAt` (floor), or `null` when the selected call is the first
414
+ * real call in the conversation.
415
+ *
416
+ * Both bounds are **strict**: the selected call itself never appears in
417
+ * its own trail (`<` ceiling), and compactions that fed an earlier real
418
+ * call's context don't bleed into this call's window (`>` floor). When
419
+ * `afterCreatedAt` is `null` the floor is dropped entirely — every
420
+ * preceding compaction is in scope, which is the right behavior for the
421
+ * very first real call in the conversation.
422
+ *
423
+ * NULL `callSite` rows (pre-migration-264) are excluded by the explicit
424
+ * `callSite = 'compactionAgent'` predicate without a separate IS NOT
425
+ * NULL clause.
426
+ */
427
+ export function getCompactionLogsBetween(
428
+ conversationId: string,
429
+ afterCreatedAt: number | null,
430
+ beforeCreatedAt: number,
431
+ ): LogRow[] {
432
+ const db = getDb();
433
+ const predicates = [
434
+ eq(llmRequestLogs.conversationId, conversationId),
435
+ eq(llmRequestLogs.callSite, "compactionAgent"),
436
+ lt(llmRequestLogs.createdAt, beforeCreatedAt),
437
+ ];
438
+ if (afterCreatedAt !== null) {
439
+ predicates.push(gt(llmRequestLogs.createdAt, afterCreatedAt));
440
+ }
441
+ return db
442
+ .select({
443
+ id: llmRequestLogs.id,
444
+ conversationId: llmRequestLogs.conversationId,
445
+ messageId: llmRequestLogs.messageId,
446
+ provider: llmRequestLogs.provider,
447
+ requestPayload: llmRequestLogs.requestPayload,
448
+ responsePayload: llmRequestLogs.responsePayload,
449
+ createdAt: llmRequestLogs.createdAt,
450
+ agentLoopExitReason: llmRequestLogs.agentLoopExitReason,
451
+ callSite: llmRequestLogs.callSite,
452
+ })
453
+ .from(llmRequestLogs)
454
+ .where(and(...predicates))
455
+ .orderBy(asc(llmRequestLogs.createdAt), asc(llmRequestLogs.id))
456
+ .all();
457
+ }
458
+
459
+ /**
460
+ * Find the `createdAt` of the most recent non-`compactionAgent` LLM
461
+ * call in the conversation strictly before `beforeCreatedAt`, or `null`
462
+ * when no such call exists (i.e. the cutoff is the first real call).
463
+ *
464
+ * Pairs with `getCompactionLogsBetween` to bound the compaction trail
465
+ * to the window between the prior real call and the selected call.
466
+ *
467
+ * "Non-compactionAgent" means `callSite IS NULL OR callSite !=
468
+ * 'compactionAgent'`. NULL rows are pre-migration-264 (no backfill) and
469
+ * are treated as real agent calls — they were `mainAgent` in practice.
470
+ * The OR-with-IS NULL is required because SQL's three-valued logic
471
+ * makes `callSite != 'compactionAgent'` return UNKNOWN (not TRUE) for
472
+ * NULL rows, which would otherwise filter them out.
473
+ */
474
+ export function getPreviousNonCompactionCallCreatedAt(
475
+ conversationId: string,
476
+ beforeCreatedAt: number,
477
+ ): number | null {
478
+ const db = getDb();
479
+ const row = db
480
+ .select({ createdAt: llmRequestLogs.createdAt })
481
+ .from(llmRequestLogs)
482
+ .where(
483
+ and(
484
+ eq(llmRequestLogs.conversationId, conversationId),
485
+ lt(llmRequestLogs.createdAt, beforeCreatedAt),
486
+ or(
487
+ isNull(llmRequestLogs.callSite),
488
+ ne(llmRequestLogs.callSite, "compactionAgent"),
489
+ ),
490
+ ),
491
+ )
492
+ .orderBy(desc(llmRequestLogs.createdAt), desc(llmRequestLogs.id))
493
+ .limit(1)
494
+ .get();
495
+ return row?.createdAt ?? null;
496
+ }
497
+
313
498
  export function getRequestLogById(logId: string): LogRow | null {
314
499
  const db = getDb();
315
500
  return (
@@ -323,7 +508,7 @@ export function getRequestLogById(logId: string): LogRow | null {
323
508
  responsePayload: llmRequestLogs.responsePayload,
324
509
  createdAt: llmRequestLogs.createdAt,
325
510
  agentLoopExitReason: llmRequestLogs.agentLoopExitReason,
326
- callSite: llmRequestLogs.callSite,
511
+ callSite: llmRequestLogs.callSite,
327
512
  })
328
513
  .from(llmRequestLogs)
329
514
  .where(eq(llmRequestLogs.id, logId))
@@ -1,8 +1,9 @@
1
- import { and, desc, eq, inArray, isNull } from "drizzle-orm";
1
+ import { and, desc, eq, gte, inArray, isNull, lte, ne } from "drizzle-orm";
2
2
  import { v4 as uuid } from "uuid";
3
3
 
4
4
  import { getDb } from "./db-connection.js";
5
5
  import { memoryV2ActivationLogs } from "./schema.js";
6
+ import type { ShadowDiffLogRow } from "./v3/shadow-diff.js";
6
7
 
7
8
  export interface MemoryV2ConceptRowRecord {
8
9
  slug: string;
@@ -94,6 +95,14 @@ export interface MemoryV2ConceptRowRecord {
94
95
  | "not_injected"
95
96
  | "page_missing"
96
97
  | "corrupt";
98
+ /**
99
+ * v3 shadow only: the retrieval lane that surfaced this slug
100
+ * (`hot` | `sparse` | `dense` | `tree` | `edge`). Lets a shadow run be
101
+ * analyzed by provenance — which lane each v3 pick came from. Undefined on
102
+ * `router`/`per-turn`/etc. v2 rows; stored in the JSON concept blob, so older
103
+ * rows decode with `undefined`.
104
+ */
105
+ lane?: string;
97
106
  }
98
107
 
99
108
  export interface MemoryV2ConfigSnapshot {
@@ -157,12 +166,16 @@ export function backfillMemoryV2ActivationMessageId(
157
166
  messageId: string,
158
167
  ): void {
159
168
  const db = getDb();
169
+ // `v3_shadow` rows are detached telemetry written outside the live turn with
170
+ // a null messageId; they are not tied to any specific message. Excluding them
171
+ // keeps their messageId null instead of stamping them with a later turn's id.
160
172
  db.update(memoryV2ActivationLogs)
161
173
  .set({ messageId })
162
174
  .where(
163
175
  and(
164
176
  eq(memoryV2ActivationLogs.conversationId, conversationId),
165
177
  isNull(memoryV2ActivationLogs.messageId),
178
+ ne(memoryV2ActivationLogs.mode, "v3_shadow"),
166
179
  ),
167
180
  )
168
181
  .run();
@@ -202,3 +215,84 @@ export function getMemoryV2ActivationLogByMessageIds(
202
215
  config: JSON.parse(row.configJson) as MemoryV2ConfigSnapshot,
203
216
  };
204
217
  }
218
+
219
+ function parseConcepts(conceptsJson: string): MemoryV2ConceptRowRecord[] {
220
+ try {
221
+ const parsed = JSON.parse(conceptsJson);
222
+ return Array.isArray(parsed) ? (parsed as MemoryV2ConceptRowRecord[]) : [];
223
+ } catch {
224
+ return [];
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Read the activation-log rows the v3 shadow-diff needs: every `v3_shadow` row
230
+ * (optionally newer than `sinceMs`), plus the `router` rows that could pair
231
+ * with them. The router read is bounded to the shadow rows' conversations and
232
+ * their time span (padded by `paddingMs`) so a shadow-diff over a few turns
233
+ * never scans the entire multi-GB log table. Returns `{ shadow, router }` for
234
+ * {@link computeShadowDiff} to pair and diff.
235
+ */
236
+ export function readActivationLogsForShadowDiff(opts: {
237
+ sinceMs: number | null;
238
+ paddingMs: number;
239
+ }): { shadow: ShadowDiffLogRow[]; router: ShadowDiffLogRow[] } {
240
+ const db = getDb();
241
+
242
+ const shadow = db
243
+ .select({
244
+ conversationId: memoryV2ActivationLogs.conversationId,
245
+ createdAt: memoryV2ActivationLogs.createdAt,
246
+ conceptsJson: memoryV2ActivationLogs.conceptsJson,
247
+ })
248
+ .from(memoryV2ActivationLogs)
249
+ .where(
250
+ opts.sinceMs !== null
251
+ ? and(
252
+ eq(memoryV2ActivationLogs.mode, "v3_shadow"),
253
+ gte(memoryV2ActivationLogs.createdAt, opts.sinceMs),
254
+ )
255
+ : eq(memoryV2ActivationLogs.mode, "v3_shadow"),
256
+ )
257
+ .orderBy(memoryV2ActivationLogs.createdAt)
258
+ .all()
259
+ .map((row) => ({
260
+ conversationId: row.conversationId,
261
+ createdAt: row.createdAt,
262
+ concepts: parseConcepts(row.conceptsJson),
263
+ }));
264
+
265
+ if (shadow.length === 0) return { shadow: [], router: [] };
266
+
267
+ const convIds = [...new Set(shadow.map((s) => s.conversationId))];
268
+ let tMin = Number.POSITIVE_INFINITY;
269
+ let tMax = Number.NEGATIVE_INFINITY;
270
+ for (const s of shadow) {
271
+ if (s.createdAt < tMin) tMin = s.createdAt;
272
+ if (s.createdAt > tMax) tMax = s.createdAt;
273
+ }
274
+
275
+ const router = db
276
+ .select({
277
+ conversationId: memoryV2ActivationLogs.conversationId,
278
+ createdAt: memoryV2ActivationLogs.createdAt,
279
+ conceptsJson: memoryV2ActivationLogs.conceptsJson,
280
+ })
281
+ .from(memoryV2ActivationLogs)
282
+ .where(
283
+ and(
284
+ eq(memoryV2ActivationLogs.mode, "router"),
285
+ inArray(memoryV2ActivationLogs.conversationId, convIds),
286
+ gte(memoryV2ActivationLogs.createdAt, tMin - opts.paddingMs),
287
+ lte(memoryV2ActivationLogs.createdAt, tMax + opts.paddingMs),
288
+ ),
289
+ )
290
+ .all()
291
+ .map((row) => ({
292
+ conversationId: row.conversationId,
293
+ createdAt: row.createdAt,
294
+ concepts: parseConcepts(row.conceptsJson),
295
+ }));
296
+
297
+ return { shadow, router };
298
+ }
@@ -0,0 +1,26 @@
1
+ import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
2
+
3
+ /**
4
+ * Drops the `status` column from the `provider_connections` table.
5
+ *
6
+ * Connection status ("active" | "disabled") has been removed — connections
7
+ * either exist or they don't. Profile status (enabled/disabled) is a separate
8
+ * concept and is untouched by this migration.
9
+ *
10
+ * Idempotent: checks PRAGMA table_info before issuing the DROP so re-running
11
+ * on a database that already lacks the column is a no-op.
12
+ *
13
+ * SQLite 3.35.0+ (Bun bundles a recent SQLite) supports ALTER TABLE DROP COLUMN.
14
+ */
15
+ export function migrateDropProviderConnectionStatus(database: DrizzleDb): void {
16
+ const raw = getSqliteFrom(database);
17
+
18
+ const columns = raw
19
+ .query(`PRAGMA table_info(provider_connections)`)
20
+ .all() as Array<{ name: string }>;
21
+ const columnNames = new Set(columns.map((c) => c.name));
22
+
23
+ if (columnNames.has("status")) {
24
+ raw.exec(`ALTER TABLE provider_connections DROP COLUMN status`);
25
+ }
26
+ }
@@ -0,0 +1,43 @@
1
+ import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
2
+
3
+ /**
4
+ * Adds `client_message_id` (nullable TEXT) to the `messages` table and
5
+ * creates a partial unique index on `(conversation_id, client_message_id)`
6
+ * for rows where `client_message_id IS NOT NULL`.
7
+ *
8
+ * The client-generated correlation nonce was previously wire-protocol-only
9
+ * (echoed on SSE `user_message_echo` events for optimistic-row dedup) but
10
+ * never persisted. Storing it enables server-side idempotency: a duplicate
11
+ * INSERT with the same `(conversation_id, client_message_id)` pair is
12
+ * silently skipped via `ON CONFLICT DO NOTHING`.
13
+ *
14
+ * Idempotent — re-running is a no-op once the column and index exist.
15
+ */
16
+ export function migrateMessagesClientMessageId(database: DrizzleDb): void {
17
+ const raw = getSqliteFrom(database);
18
+
19
+ const columns = raw.query(`PRAGMA table_info(messages)`).all() as Array<{
20
+ name: string;
21
+ }>;
22
+ const columnNames = new Set(columns.map((c) => c.name));
23
+
24
+ if (!columnNames.has("client_message_id")) {
25
+ raw.exec(`ALTER TABLE messages ADD COLUMN client_message_id TEXT`);
26
+ }
27
+
28
+ // Partial unique index: only enforced when client_message_id is non-NULL.
29
+ // Messages without a client_message_id (assistant messages, system-generated
30
+ // messages, legacy rows) are unconstrained.
31
+ const indexes = raw.query(`PRAGMA index_list(messages)`).all() as Array<{
32
+ name: string;
33
+ }>;
34
+ const indexNames = new Set(indexes.map((i) => i.name));
35
+
36
+ if (!indexNames.has("idx_messages_conv_client_msg_id")) {
37
+ raw.exec(
38
+ `CREATE UNIQUE INDEX idx_messages_conv_client_msg_id
39
+ ON messages (conversation_id, client_message_id)
40
+ WHERE client_message_id IS NOT NULL`,
41
+ );
42
+ }
43
+ }
@@ -251,6 +251,8 @@ export {
251
251
  migrateMemoryV3AutoEdges,
252
252
  } from "./263-memory-v3-auto-edges.js";
253
253
  export { migrateLlmRequestLogCallSite } from "./264-llm-request-log-call-site.js";
254
+ export { migrateDropProviderConnectionStatus } from "./265-drop-provider-connection-status.js";
255
+ export { migrateMessagesClientMessageId } from "./266-messages-client-message-id.js";
254
256
  export {
255
257
  MIGRATION_REGISTRY,
256
258
  type MigrationRegistryEntry,
@@ -1,9 +1,11 @@
1
+ import { sql } from "drizzle-orm";
1
2
  import {
2
3
  index,
3
4
  integer,
4
5
  real,
5
6
  sqliteTable,
6
7
  text,
8
+ uniqueIndex,
7
9
  } from "drizzle-orm/sqlite-core";
8
10
 
9
11
  export const conversations = sqliteTable(
@@ -66,8 +68,14 @@ export const messages = sqliteTable(
66
68
  content: text("content").notNull(),
67
69
  createdAt: integer("created_at").notNull(),
68
70
  metadata: text("metadata"),
71
+ clientMessageId: text("client_message_id"),
69
72
  },
70
- (table) => [index("idx_messages_conversation_id").on(table.conversationId)],
73
+ (table) => [
74
+ index("idx_messages_conversation_id").on(table.conversationId),
75
+ uniqueIndex("idx_messages_conv_client_msg_id")
76
+ .on(table.conversationId, table.clientMessageId)
77
+ .where(sql`client_message_id IS NOT NULL`),
78
+ ],
71
79
  );
72
80
 
73
81
  export const toolInvocations = sqliteTable(
@@ -15,7 +15,6 @@ export const providerConnections = sqliteTable(
15
15
  name: text("name").primaryKey(),
16
16
  provider: text("provider").notNull(),
17
17
  auth: text("auth").notNull(),
18
- status: text("status").notNull().default("active"),
19
18
  label: text("label"),
20
19
  baseUrl: text("base_url"),
21
20
  models: text("models"),
@@ -202,7 +202,10 @@ afterAll(() => {
202
202
  rmSync(tmpWorkspace, { recursive: true, force: true });
203
203
  });
204
204
 
205
- const { getDb, resetDb } = await import("../../db-connection.js");
205
+ const { getDb } = await import("../../db-connection.js");
206
+ const { resetDbForTesting } = await import(
207
+ "../../../__tests__/db-test-helpers.js"
208
+ );
206
209
  const { initializeDb } = await import("../../db-init.js");
207
210
  const { rawExec } = await import("../../raw-query.js");
208
211
  const { conversations, memoryJobs, messages } = await import("../../schema.js");
@@ -247,7 +250,7 @@ function makeJob(
247
250
  }
248
251
 
249
252
  beforeEach(() => {
250
- resetDb();
253
+ resetDbForTesting();
251
254
  initializeDb();
252
255
  // The shared template-DB caching does not clear WAL state between tests,
253
256
  // so explicitly truncate every table this suite writes to. Without this,
@@ -25,6 +25,15 @@ describe("harness/metrics recallAtK", () => {
25
25
  test("empty ground truth is vacuously complete (recall 1)", () => {
26
26
  expect(recallAtK([], new Set<string>(), 5)).toBe(1);
27
27
  });
28
+
29
+ test("duplicate selections cannot push recall above 1.0", () => {
30
+ // A retriever emitting the same slug twice must not double-count it.
31
+ expect(recallAtK(["a", "a"], new Set(["a"]), 10)).toBe(1);
32
+ // Duplicates inside the top-k window still count once.
33
+ const gt = new Set(["a", "b"]);
34
+ expect(recallAtK(["a", "a", "b"], gt, 10)).toBeCloseTo(1);
35
+ expect(recallAtK(["a", "a", "b"], gt, 2)).toBeCloseTo(0.5);
36
+ });
28
37
  });
29
38
 
30
39
  describe("harness/metrics evalTurn", () => {
@@ -195,7 +195,7 @@ describe("harness/replay-input reconstructInput", () => {
195
195
  expect(r).toBeNull();
196
196
  });
197
197
 
198
- test("priorEverInjected unions injected/in_context slugs from earlier router turns", async () => {
198
+ test("priorEverInjected mirrors production everInjected retention from earlier router turns", async () => {
199
199
  insertMessage("u1", "c1", "user", "u1", 10);
200
200
  insertMessage("a1", "c1", "assistant", "a1", 20);
201
201
  insertMessage("u2", "c1", "user", "u2", 30);
@@ -207,7 +207,12 @@ describe("harness/replay-input reconstructInput", () => {
207
207
  [
208
208
  makeConcept("p1", "injected"),
209
209
  makeConcept("p2", "in_context"),
210
- makeConcept("p3", "not_injected"),
210
+ // page_missing / corrupt concept pages are retained in production's
211
+ // everInjected (so they aren't re-attempted every turn), so the replay
212
+ // must include them too.
213
+ makeConcept("p3", "page_missing"),
214
+ makeConcept("p4", "corrupt"),
215
+ makeConcept("p5", "not_injected"),
211
216
  ],
212
217
  20,
213
218
  );
@@ -219,7 +224,7 @@ describe("harness/replay-input reconstructInput", () => {
219
224
  WORKSPACE,
220
225
  );
221
226
  const slugs = (r?.input.priorEverInjected ?? []).map((e) => e.slug).sort();
222
- expect(slugs).toEqual(["p1", "p2"]);
223
- expect(r?.meta.priorEverInjectedCount).toBe(2);
227
+ expect(slugs).toEqual(["p1", "p2", "p3", "p4"]);
228
+ expect(r?.meta.priorEverInjectedCount).toBe(4);
224
229
  });
225
230
  });
@@ -92,6 +92,32 @@ describe("harness/runner runComparison", () => {
92
92
  expect(loop?.aggregate.meanRecallAtK[5]).toBeCloseTo(0.75);
93
93
  });
94
94
 
95
+ test("threads the abort signal into each retriever's input", async () => {
96
+ const controller = new AbortController();
97
+ const seenSignals: (AbortSignal | undefined)[] = [];
98
+ const capturingRetriever: Retriever = {
99
+ name: "router",
100
+ retrieve: async (input): Promise<RetrievalOutput> => {
101
+ seenSignals.push(input.signal);
102
+ return { selectedSlugs: [], sourceBySlug: new Map() };
103
+ },
104
+ };
105
+
106
+ // Fresh reconstructed input per turn so we exercise the per-turn assignment.
107
+ await runComparison({
108
+ retrievers: [capturingRetriever],
109
+ oracleTurns: [oracleTurn("c1", 1, ["a"])],
110
+ reconstruct: async () => ({
111
+ ...STUB_RECONSTRUCTED,
112
+ input: { ...STUB_INPUT },
113
+ }),
114
+ ks: [5],
115
+ signal: controller.signal,
116
+ });
117
+
118
+ expect(seenSignals).toEqual([controller.signal]);
119
+ });
120
+
95
121
  test("skips turns whose reconstruction returns null", async () => {
96
122
  const report = await runComparison({
97
123
  retrievers: [fixedRetriever("router", ["a"])],
@@ -99,7 +99,10 @@ afterAll(() => {
99
99
  rmSync(tmpWorkspace, { recursive: true, force: true });
100
100
  });
101
101
 
102
- const { resetDb, getDb } = await import("../../db-connection.js");
102
+ const { getDb } = await import("../../db-connection.js");
103
+ const { resetDbForTesting } = await import(
104
+ "../../../__tests__/db-test-helpers.js"
105
+ );
103
106
  const { initializeDb } = await import("../../db-init.js");
104
107
  const { messages, conversations } = await import("../../schema.js");
105
108
  const { memoryV2SweepJob } = await import("../sweep-job.js");
@@ -212,7 +215,7 @@ function seedMessages(
212
215
  }
213
216
 
214
217
  beforeEach(() => {
215
- resetDb();
218
+ resetDbForTesting();
216
219
  initializeDb();
217
220
  // Fresh memory dir per test — keeps assertions on file contents independent.
218
221
  rmSync(join(tmpWorkspace, "memory"), { recursive: true, force: true });
@@ -275,7 +278,7 @@ describe("memoryV2SweepJob — no recent messages", () => {
275
278
  });
276
279
 
277
280
  // Per-test conversation id ensures each test seeds a row that doesn't
278
- // collide with the previous test's row in the (shared) test DB. `resetDb`
281
+ // collide with the previous test's row in the (shared) test DB. `resetDbForTesting`
279
282
  // is called in the outer beforeEach, but bun's mock module flow keeps the
280
283
  // DB intact long enough for the SQL inserts here to clash.
281
284
  let convCounter = 0;
@@ -36,6 +36,10 @@ export interface AggregateEval {
36
36
  /**
37
37
  * recall@k = |topK(selected) ∩ G| / |G|. An empty ground-truth set is defined
38
38
  * as recall 1 (nothing to recall — vacuously complete).
39
+ *
40
+ * The top-k window is deduped before intersecting with the ground-truth set so
41
+ * a retriever that emits the same slug twice (e.g. `['a','a']`) cannot count it
42
+ * twice and push recall above 1.0. Recall is therefore bounded in [0, 1].
39
43
  */
40
44
  export function recallAtK(
41
45
  selected: readonly string[],
@@ -44,7 +48,7 @@ export function recallAtK(
44
48
  ): number {
45
49
  if (groundTruth.size === 0) return 1;
46
50
  let hit = 0;
47
- for (const slug of selected.slice(0, k)) {
51
+ for (const slug of new Set(selected.slice(0, k))) {
48
52
  if (groundTruth.has(slug)) hit++;
49
53
  }
50
54
  return hit / groundTruth.size;
@@ -10,8 +10,10 @@
10
10
  * - `nowText`: read from current workspace files (`loadNowText`). NOT stored
11
11
  * in the log, so it may differ from what the live turn saw —
12
12
  * always-approximate; see `ReconstructionMeta.nowReconstructedFromCurrent`.
13
- * - `priorEverInjected`: the union of injected / in_context slugs from earlier
14
- * `mode='router'` logs in the same conversation (turn < target).
13
+ * - `priorEverInjected`: the union of retained slugs from earlier
14
+ * `mode='router'` logs in the same conversation (turn < target). Retained
15
+ * statuses mirror production's `everInjected` (injected / in_context, plus
16
+ * page_missing / corrupt — see `PRIOR_STATUSES`).
15
17
  *
16
18
  * The anchor is the turn's assistant reply; the messages the router saw are
17
19
  * those strictly before it, so we fetch a bounded recent window up to the
@@ -172,7 +174,21 @@ export async function reconstructInput(
172
174
  };
173
175
  }
174
176
 
175
- const PRIOR_STATUSES = new Set<string>(["injected", "in_context"]);
177
+ // Production's `everInjected` retains a slug once it is rendered, EXCEPT for
178
+ // missing synthetic slugs (skills/CLI commands whose capability cache is empty
179
+ // — see `missingSyntheticSlugs` in `injection.ts`). Concept pages that turn out
180
+ // `page_missing` or `corrupt` at render time are still retained so they aren't
181
+ // re-attempted every turn (see the `page_missing ... DOES land in everInjected`
182
+ // case in `injection.test.ts`). The replay must mirror that retention or it
183
+ // builds a narrower prior-state than live routing, skewing comparisons. Missing
184
+ // synthetic slugs never enter the missing/corrupt buckets — they log as
185
+ // `injected` — so widening here introduces no new synthetic-slug discrepancy.
186
+ const PRIOR_STATUSES = new Set<string>([
187
+ "injected",
188
+ "in_context",
189
+ "page_missing",
190
+ "corrupt",
191
+ ]);
176
192
 
177
193
  /**
178
194
  * Union of slugs injected on earlier `mode='router'` turns in this conversation
@@ -74,6 +74,12 @@ export async function runComparison(
74
74
  }
75
75
  turnsScored++;
76
76
 
77
+ // Thread the abort signal into the reconstructed input so retrievers that
78
+ // wrap LLM calls (e.g. the router retriever forwarding to `runRouter`) abort
79
+ // the in-flight per-turn call on caller disconnect — the loop gating below
80
+ // only stops scheduling new work, it can't cancel the current retrieval.
81
+ if (signal) reconstructed.input.signal = signal;
82
+
77
83
  const byRetriever: Record<string, TurnEval> = {};
78
84
  for (const retriever of retrievers) {
79
85
  if (signal?.aborted) break;