@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
@@ -26,6 +26,7 @@ mock.module("../memory/conversation-crud.js", () => ({
26
26
  getMessages: mockGetMessages,
27
27
  createConversation: mockCreateConversation,
28
28
  addMessage: mockAddMessage,
29
+ reserveMessage: mock(async () => ({ id: "msg-reserve" })),
29
30
  }));
30
31
 
31
32
  mock.module("../export/transcript-formatter.js", () => ({
@@ -98,12 +98,13 @@ mock.module("../skills/tool-manifest.js", () => ({
98
98
  }));
99
99
 
100
100
  mock.module("../tools/skills/skill-tool-factory.js", () => ({
101
+ // Mirrors the real factory: no skillId in/out — ownership is recorded by
102
+ // the registry at `registerSkillTools(skillId, tools)` time.
101
103
  createSkillToolsFromManifest: (
102
104
  entries: SkillToolManifest["tools"],
103
- skillId: string,
104
105
  _skillDir: string,
105
- versionHash: string,
106
- bundled?: boolean,
106
+ _versionHash: string,
107
+ _bundled?: boolean,
107
108
  ): Tool[] =>
108
109
  entries.map((entry) => ({
109
110
  name: entry.name,
@@ -111,28 +112,20 @@ mock.module("../tools/skills/skill-tool-factory.js", () => ({
111
112
  category: entry.category,
112
113
  defaultRiskLevel: RiskLevel.Medium,
113
114
  executionTarget: "sandbox" as const,
114
- origin: "skill" as const,
115
- ownerSkillId: skillId,
116
- ownerSkillVersionHash: versionHash,
117
- ownerSkillBundled: bundled ?? undefined,
118
115
  input_schema: entry.input_schema as object,
119
116
  execute: async () => ({ content: "", isError: false }),
120
117
  })),
121
118
  }));
122
119
 
123
120
  mock.module("../tools/registry.js", () => ({
124
- registerSkillTools: (tools: Tool[]) => {
125
- const skillIds = new Set<string>();
126
- for (const tool of tools) {
127
- const skillId = tool.ownerSkillId!;
128
- skillIds.add(skillId);
129
- const existing = mockRegisteredTools.get(skillId) ?? [];
130
- existing.push(tool);
131
- mockRegisteredTools.set(skillId, existing);
132
- }
133
- for (const id of skillIds) {
134
- mockSkillRefCount.set(id, (mockSkillRefCount.get(id) ?? 0) + 1);
135
- }
121
+ // Matches the new signature: `registerSkillTools(skillId, tools)`. The
122
+ // skillId comes from the caller (conversation-skill-tools) and is the
123
+ // sole source of truth for ownership.
124
+ registerSkillTools: (skillId: string, tools: Tool[]) => {
125
+ const existing = mockRegisteredTools.get(skillId) ?? [];
126
+ existing.push(...tools);
127
+ mockRegisteredTools.set(skillId, existing);
128
+ mockSkillRefCount.set(skillId, (mockSkillRefCount.get(skillId) ?? 0) + 1);
136
129
  return tools;
137
130
  },
138
131
  unregisterSkillTools: (skillId: string) => {
@@ -153,6 +146,23 @@ mock.module("../tools/registry.js", () => ({
153
146
  }
154
147
  return found;
155
148
  },
149
+ // Mirrors the registry's `ownersByName` accessor: derives the owning
150
+ // skillId from `mockRegisteredTools` keying so the production
151
+ // `getToolOwner(name)` call in `conversation-skill-tools.ts` resolves to
152
+ // the same shape the real registry would return.
153
+ getToolOwner: (
154
+ name: string,
155
+ ): { kind: "skill" | "plugin" | "mcp"; id: string } | undefined => {
156
+ let ownerSkillId: string | undefined;
157
+ for (const [skillId, tools] of mockRegisteredTools.entries()) {
158
+ for (const tool of tools) {
159
+ if (tool.name === name) ownerSkillId = skillId;
160
+ }
161
+ }
162
+ return ownerSkillId === undefined
163
+ ? undefined
164
+ : { kind: "skill", id: ownerSkillId };
165
+ },
156
166
  getSkillToolNames: () => {
157
167
  const names: string[] = [];
158
168
  for (const tools of mockRegisteredTools.values()) {
@@ -92,6 +92,7 @@ mock.module("../memory/conversation-crud.js", () => ({
92
92
  addMessage: async () => ({ id: "persisted-1" }),
93
93
  setConversationOriginChannelIfUnset: () => {},
94
94
  setConversationOriginInterfaceIfUnset: () => {},
95
+ reserveMessage: mock(async () => ({ id: "msg-reserve" })),
95
96
  }));
96
97
 
97
98
  mock.module("../memory/conversation-queries.js", () => ({
@@ -147,6 +147,107 @@ describe("conversation-attention-store", () => {
147
147
  const state = states.get("conv-1")!;
148
148
  expect(state.latestAssistantMessageId).toBe("msg-1");
149
149
  });
150
+
151
+ test("returns true when creating new attention state (no prior state)", () => {
152
+ ensureConversation("conv-1");
153
+ const result = projectAssistantMessage({
154
+ conversationId: "conv-1",
155
+ messageId: "msg-1",
156
+ messageAt: 1000,
157
+ });
158
+ expect(result).toBe(true);
159
+ });
160
+
161
+ test("returns true when advancing cursor past seen position (seen→unseen)", () => {
162
+ ensureConversation("conv-1");
163
+ projectAssistantMessage({
164
+ conversationId: "conv-1",
165
+ messageId: "msg-1",
166
+ messageAt: 1000,
167
+ });
168
+ recordConversationSeenSignal({
169
+ conversationId: "conv-1",
170
+ sourceChannel: "vellum",
171
+ signalType: "macos_conversation_opened",
172
+ confidence: "explicit",
173
+ source: "test",
174
+ });
175
+ const result = projectAssistantMessage({
176
+ conversationId: "conv-1",
177
+ messageId: "msg-2",
178
+ messageAt: 2000,
179
+ });
180
+ expect(result).toBe(true);
181
+ });
182
+
183
+ test("returns false when conversation is already unseen", () => {
184
+ ensureConversation("conv-1");
185
+ projectAssistantMessage({
186
+ conversationId: "conv-1",
187
+ messageId: "msg-1",
188
+ messageAt: 1000,
189
+ });
190
+ const result = projectAssistantMessage({
191
+ conversationId: "conv-1",
192
+ messageId: "msg-2",
193
+ messageAt: 2000,
194
+ });
195
+ expect(result).toBe(false);
196
+ });
197
+
198
+ test("returns false when cursor does not advance (older message)", () => {
199
+ ensureConversation("conv-1");
200
+ projectAssistantMessage({
201
+ conversationId: "conv-1",
202
+ messageId: "msg-2",
203
+ messageAt: 2000,
204
+ });
205
+ const result = projectAssistantMessage({
206
+ conversationId: "conv-1",
207
+ messageId: "msg-1",
208
+ messageAt: 1000,
209
+ });
210
+ expect(result).toBe(false);
211
+ });
212
+
213
+ test("returns false when cursor does not advance (equal timestamp)", () => {
214
+ ensureConversation("conv-1");
215
+ projectAssistantMessage({
216
+ conversationId: "conv-1",
217
+ messageId: "msg-1",
218
+ messageAt: 1000,
219
+ });
220
+ const result = projectAssistantMessage({
221
+ conversationId: "conv-1",
222
+ messageId: "msg-1-dup",
223
+ messageAt: 1000,
224
+ });
225
+ expect(result).toBe(false);
226
+ });
227
+
228
+ test("returns true when row exists with null latestAssistantMessageAt (seen signal before first assistant msg)", () => {
229
+ ensureConversation("conv-1");
230
+ // Simulate: user opens conversation before any assistant message,
231
+ // which calls recordConversationSeenSignal and creates a state row
232
+ // with latestAssistantMessageAt = null.
233
+ recordConversationSeenSignal({
234
+ conversationId: "conv-1",
235
+ sourceChannel: "vellum",
236
+ signalType: "macos_conversation_opened",
237
+ confidence: "explicit",
238
+ source: "test",
239
+ });
240
+ const states = getAttentionStateByConversationIds(["conv-1"]);
241
+ expect(states.get("conv-1")!.latestAssistantMessageAt).toBeNull();
242
+
243
+ // First assistant message should transition to unseen
244
+ const result = projectAssistantMessage({
245
+ conversationId: "conv-1",
246
+ messageId: "msg-1",
247
+ messageAt: 1000,
248
+ });
249
+ expect(result).toBe(true);
250
+ });
150
251
  });
151
252
 
152
253
  // ── recordConversationSeenSignal ────────────────────────────────
@@ -32,18 +32,19 @@ mock.module("../daemon/handlers/shared.js", () => ({
32
32
  import { eq } from "drizzle-orm";
33
33
 
34
34
  import { upsertContact } from "../contacts/contact-store.js";
35
- import { getDb, resetDb } from "../memory/db-connection.js";
35
+ import { getDb } from "../memory/db-connection.js";
36
36
  import { initializeDb } from "../memory/db-init.js";
37
37
  import * as deliveryChannels from "../memory/delivery-channels.js";
38
38
  import { resetTestTables } from "../memory/raw-query.js";
39
39
  import { attachments, conversationAttentionEvents } from "../memory/schema.js";
40
40
  import * as pendingInteractions from "../runtime/pending-interactions.js";
41
+ import { resetDbForTesting } from "./db-test-helpers.js";
41
42
  import { handleChannelInbound } from "./helpers/channel-test-adapter.js";
42
43
 
43
44
  initializeDb();
44
45
 
45
46
  afterAll(() => {
46
- resetDb();
47
+ resetDbForTesting();
47
48
  });
48
49
 
49
50
  // ---------------------------------------------------------------------------
@@ -142,6 +142,7 @@ mock.module("../memory/conversation-crud.js", () => ({
142
142
  addMessage: () => ({ id: `msg-${Date.now()}` }),
143
143
  updateConversationUsage: () => {},
144
144
  updateConversationTitle: () => {},
145
+ reserveMessage: mock(async () => ({ id: "msg-reserve" })),
145
146
  }));
146
147
 
147
148
  mock.module("../memory/conversation-queries.js", () => ({
@@ -14,6 +14,7 @@ import {
14
14
  classifyConversationError,
15
15
  isUserCancellation,
16
16
  } from "../daemon/conversation-error.js";
17
+ import { ConnectionResolutionError } from "../providers/connection-resolution.js";
17
18
  import {
18
19
  type AbortReasonKind,
19
20
  createAbortReason,
@@ -978,3 +979,32 @@ describe("budgetYieldUnrecoveredClassification", () => {
978
979
  expect(envelope.userMessage).toBe(classified.userMessage);
979
980
  });
980
981
  });
982
+
983
+ describe("ConnectionResolutionError classification", () => {
984
+ const errCtx: ErrorContext = { phase: "agent_loop" };
985
+
986
+ it("classifies provider_mismatch as PROVIDER_NOT_CONFIGURED with user-friendly message", () => {
987
+ const err = new ConnectionResolutionError(
988
+ "anthropic-managed",
989
+ "provider_mismatch",
990
+ 'provider_connection "anthropic-managed" has provider="anthropic" but resolving profile declared provider="openai"',
991
+ );
992
+ const result = classifyConversationError(err, errCtx);
993
+ expect(result.code).toBe("PROVIDER_NOT_CONFIGURED");
994
+ expect(result.userMessage).toContain("No compatible provider connection");
995
+ expect(result.userMessage).toContain("Settings");
996
+ expect(result.userMessage).not.toContain("provider_connection");
997
+ expect(result.connectionName).toBe("anthropic-managed");
998
+ });
999
+
1000
+ it("classifies not_found as PROVIDER_NOT_CONFIGURED", () => {
1001
+ const err = new ConnectionResolutionError(
1002
+ "deleted-connection",
1003
+ "not_found",
1004
+ 'provider_connection "deleted-connection" not found in DB',
1005
+ );
1006
+ const result = classifyConversationError(err, errCtx);
1007
+ expect(result.code).toBe("PROVIDER_NOT_CONFIGURED");
1008
+ expect(result.userMessage).not.toContain("not found in DB");
1009
+ });
1010
+ });
@@ -214,6 +214,40 @@ describe("forkConversation", () => {
214
214
  ]);
215
215
  });
216
216
 
217
+ test("pinned fork through a (createdAt, id) cutoff matches the cursor slice for same-timestamp rows", () => {
218
+ // Regression for the memory-retrospective cutoff/fork divergence: the job
219
+ // picks its cutoff from `getMessagesAfter`, which orders by `(createdAt,
220
+ // id)`. `forkConversation` must slice on the same order so same-millisecond
221
+ // siblings aren't skipped forever or reprocessed. Insert rows whose
222
+ // insertion order is the reverse of their `(createdAt, id)` order to expose
223
+ // the divergence: a `createdAt`-only slice would pick the wrong prefix.
224
+ const source = createConversation("Same-timestamp cutoff thread");
225
+ const db = getDb();
226
+ const createdAt = Date.now();
227
+ // Insert d, c, b, a so SQLite's createdAt-only tie order (≈ rowid /
228
+ // insertion order) is the opposite of the (createdAt, id) cursor order
229
+ // (a, b, c, d). All are plain user rows so no display-turn extension fires.
230
+ for (const id of ["msg-d", "msg-c", "msg-b", "msg-a"]) {
231
+ db.run(
232
+ `INSERT INTO messages (id, conversation_id, role, content, created_at) VALUES ('${id}', '${source.id}', 'user', '${id}', ${createdAt})`,
233
+ );
234
+ }
235
+
236
+ // Cutoff = "msg-c": the cursor treats {msg-a, msg-b, msg-c} as processed
237
+ // and {msg-d} as still-after-the-cutoff. The fork must contain exactly the
238
+ // first three in `(createdAt, id)` order.
239
+ const fork = forkConversation({
240
+ conversationId: source.id,
241
+ throughMessageId: "msg-c",
242
+ });
243
+
244
+ expect(getMessages(fork.id).map((message) => message.content)).toEqual([
245
+ "msg-a",
246
+ "msg-b",
247
+ "msg-c",
248
+ ]);
249
+ });
250
+
217
251
  test("advances fork boundary through consecutive assistant rows after the requested message", async () => {
218
252
  // When the read-path merges consecutive assistant DB rows into a single
219
253
  // display row, the client only addresses the anchor id. Forking through
@@ -273,9 +307,15 @@ describe("forkConversation", () => {
273
307
  // between — otherwise the fork loses tool_use ↔ tool_result pairing
274
308
  // and produces an invalid LLM history.
275
309
  const source = createConversation("Tool-result gap thread");
276
- await addMessage(source.id, "user", "Find the latest sales numbers", undefined, {
277
- skipIndexing: true,
278
- });
310
+ await addMessage(
311
+ source.id,
312
+ "user",
313
+ "Find the latest sales numbers",
314
+ undefined,
315
+ {
316
+ skipIndexing: true,
317
+ },
318
+ );
279
319
  const anchor = await addMessage(
280
320
  source.id,
281
321
  "assistant",
@@ -527,13 +567,34 @@ describe("forkConversation", () => {
527
567
  },
528
568
  { skipIndexing: true },
529
569
  );
530
- await addMessage(source.id, "assistant", "Reply 1", undefined, {
531
- skipIndexing: true,
532
- });
533
- await addMessage(source.id, "user", "Tail turn", undefined, {
570
+ const reply1 = await addMessage(
571
+ source.id,
572
+ "assistant",
573
+ "Reply 1",
574
+ undefined,
575
+ {
576
+ skipIndexing: true,
577
+ },
578
+ );
579
+ const tail = await addMessage(source.id, "user", "Tail turn", undefined, {
534
580
  skipIndexing: true,
535
581
  });
536
- const compactedAt = Date.now();
582
+ // Pin strictly-increasing timestamps so the pinned fork boundary is
583
+ // unambiguous. `addMessage` stamps `Date.now()`, and these three rows can
584
+ // land in the same millisecond; under the `(createdAt, id)` tie-break the
585
+ // pinned fork uses, that would let `m1`'s slice reorder relative to its
586
+ // siblings. Distinct timestamps keep this test focused on its intent —
587
+ // pre-compaction metadata + compaction-state inheritance.
588
+ const db = getDb();
589
+ const base = Date.now();
590
+ db.run(`UPDATE messages SET created_at = ${base} WHERE id = '${m1.id}'`);
591
+ db.run(
592
+ `UPDATE messages SET created_at = ${base + 1} WHERE id = '${reply1.id}'`,
593
+ );
594
+ db.run(
595
+ `UPDATE messages SET created_at = ${base + 2} WHERE id = '${tail.id}'`,
596
+ );
597
+ const compactedAt = base + 3;
537
598
  getDb()
538
599
  .update(conversations)
539
600
  .set({
@@ -45,11 +45,12 @@ mock.module("../config/loader.js", () => ({
45
45
  }));
46
46
 
47
47
  import { addMessage, createConversation } from "../memory/conversation-crud.js";
48
- import { getDb, resetDb } from "../memory/db-connection.js";
48
+ import { getDb } from "../memory/db-connection.js";
49
49
  import { initializeDb } from "../memory/db-init.js";
50
50
  import { getPolicy } from "../runtime/auth/route-policy.js";
51
51
  import { mintToken } from "../runtime/auth/token-service.js";
52
52
  import { RuntimeHttpServer } from "../runtime/http-server.js";
53
+ import { resetDbForTesting } from "./db-test-helpers.js";
53
54
 
54
55
 
55
56
  initializeDb();
@@ -98,7 +99,7 @@ describe("POST /v1/conversations/fork", () => {
98
99
 
99
100
  afterAll(async () => {
100
101
  await server?.stop();
101
- resetDb();
102
+ resetDbForTesting();
102
103
  });
103
104
 
104
105
  test("returns the same conversation summary shape as GET /v1/conversations/:id", async () => {
@@ -50,6 +50,7 @@ mock.module("../memory/conversation-crud.js", () => ({
50
50
  },
51
51
  relinkAttachments: () => 0,
52
52
  deleteLastExchange: () => 0,
53
+ reserveMessage: mock(async () => ({ id: "msg-reserve" })),
53
54
  }));
54
55
 
55
56
  mock.module("../memory/conversation-queries.js", () => ({
@@ -42,9 +42,10 @@ import {
42
42
  createConversation,
43
43
  setConversationInferenceProfile,
44
44
  } from "../memory/conversation-crud.js";
45
- import { getDb, resetDb } from "../memory/db-connection.js";
45
+ import { getDb } from "../memory/db-connection.js";
46
46
  import { initializeDb } from "../memory/db-init.js";
47
47
  import { RuntimeHttpServer } from "../runtime/http-server.js";
48
+ import { resetDbForTesting } from "./db-test-helpers.js";
48
49
 
49
50
  initializeDb();
50
51
 
@@ -65,7 +66,7 @@ describe("conversation HTTP responses include inferenceProfile", () => {
65
66
 
66
67
  afterAll(async () => {
67
68
  await server?.stop();
68
- resetDb();
69
+ resetDbForTesting();
69
70
  });
70
71
 
71
72
  test("GET /v1/conversations includes inferenceProfile for pinned conversations and omits it when unset", async () => {
@@ -30,11 +30,12 @@ import {
30
30
  createConversation,
31
31
  getConversation,
32
32
  } from "../memory/conversation-crud.js";
33
- import { getDb, resetDb } from "../memory/db-connection.js";
33
+ import { getDb } from "../memory/db-connection.js";
34
34
  import { initializeDb } from "../memory/db-init.js";
35
35
  import { assistantEventHub } from "../runtime/assistant-event-hub.js";
36
36
  import { ROUTES } from "../runtime/routes/conversation-management-routes.js";
37
37
  import { BadRequestError, NotFoundError } from "../runtime/routes/errors.js";
38
+ import { resetDbForTesting } from "./db-test-helpers.js";
38
39
 
39
40
  initializeDb();
40
41
 
@@ -57,7 +58,7 @@ describe("PUT /v1/conversations/:id/inference-profile", () => {
57
58
  });
58
59
 
59
60
  afterAll(() => {
60
- resetDb();
61
+ resetDbForTesting();
61
62
  mock.restore();
62
63
  });
63
64
 
@@ -102,6 +102,7 @@ mock.module("../memory/conversation-crud.js", () => ({
102
102
  },
103
103
  setConversationOriginChannelIfUnset: () => {},
104
104
  setConversationOriginInterfaceIfUnset: () => {},
105
+ reserveMessage: mock(async () => ({ id: "msg-reserve" })),
105
106
  }));
106
107
 
107
108
  mock.module("../memory/conversation-queries.js", () => ({
@@ -44,9 +44,10 @@ mock.module("../config/loader.js", () => ({
44
44
  }));
45
45
 
46
46
  import { createConversation } from "../memory/conversation-crud.js";
47
- import { getDb, resetDb } from "../memory/db-connection.js";
47
+ import { getDb } from "../memory/db-connection.js";
48
48
  import { initializeDb } from "../memory/db-init.js";
49
49
  import { RuntimeHttpServer } from "../runtime/http-server.js";
50
+ import { resetDbForTesting } from "./db-test-helpers.js";
50
51
 
51
52
  initializeDb();
52
53
 
@@ -68,7 +69,7 @@ describe("GET /v1/conversations includes source discriminator", () => {
68
69
 
69
70
  afterAll(async () => {
70
71
  await server?.stop();
71
- resetDb();
72
+ resetDbForTesting();
72
73
  });
73
74
 
74
75
  test("returns source for every listed conversation", async () => {
@@ -104,6 +104,7 @@ mock.module("../memory/conversation-crud.js", () => ({
104
104
  },
105
105
  setConversationOriginChannelIfUnset: () => {},
106
106
  setConversationOriginInterfaceIfUnset: () => {},
107
+ reserveMessage: mock(async () => ({ id: "msg-reserve" })),
107
108
  }));
108
109
 
109
110
  mock.module("../memory/conversation-queries.js", () => ({
@@ -535,7 +536,7 @@ describe("loadFromDb history repair", () => {
535
536
  trustClass: "guardian",
536
537
  sourceChannel: "telegram",
537
538
  });
538
- await conversation.persistUserMessage("Guardian follow-up", []);
539
+ await conversation.persistUserMessage({ content: "Guardian follow-up" });
539
540
  const messagesAfterPersist = conversation.getMessages();
540
541
 
541
542
  expect(messagesAfterPersist).toHaveLength(5);
@@ -87,6 +87,7 @@ mock.module("../memory/conversation-crud.js", () => ({
87
87
  setConversationHistoryStrippedAt: () => {},
88
88
  setConversationOriginChannelIfUnset: () => {},
89
89
  setConversationOriginInterfaceIfUnset: () => {},
90
+ reserveMessage: mock(async () => ({ id: "msg-reserve" })),
90
91
  }));
91
92
 
92
93
  mock.module("../memory/conversation-queries.js", () => ({
@@ -67,6 +67,7 @@ mock.module("../memory/conversation-crud.js", () => ({
67
67
  createConversation: createConversationMock,
68
68
  addMessage: addMessageMock,
69
69
  getConversation: getConversationMock,
70
+ reserveMessage: mock(async () => ({ id: "msg-reserve" })),
70
71
  }));
71
72
 
72
73
  /** Simulated bindings for external-conversation-store mock. */
@@ -122,6 +123,11 @@ function makeSignal(
122
123
  isAsyncBackground: true,
123
124
  visibleInSourceNow: false,
124
125
  },
126
+ // Most existing pairing tests pre-date the passive-by-default gate and
127
+ // exercise the conversation-creation path. Default to the opt-in flag so
128
+ // they continue to assert creation semantics; the dedicated passive-gate
129
+ // test below explicitly omits it to cover the new default.
130
+ requiresConversation: true,
125
131
  ...overrides,
126
132
  };
127
133
  }
@@ -815,6 +821,53 @@ describe("pairDeliveryWithConversation", () => {
815
821
  expect(getBindingByChannelChatMock).not.toHaveBeenCalled();
816
822
  });
817
823
 
824
+ // ── Passive default: vellum no-creates without opt-in ─────────────
825
+
826
+ test("passive vellum signal does not create a conversation or message", async () => {
827
+ const signal = makeSignal({ requiresConversation: undefined });
828
+ const copy = makeCopy();
829
+
830
+ const result = await pairDeliveryWithConversation(
831
+ signal,
832
+ "vellum" as NotificationChannel,
833
+ copy,
834
+ );
835
+
836
+ expect(result.conversationId).toBeNull();
837
+ expect(result.messageId).toBeNull();
838
+ expect(result.strategy).toBe("start_new_conversation");
839
+ expect(result.createdNewConversation).toBe(false);
840
+ expect(result.conversationFallbackUsed).toBe(false);
841
+ expect(createConversationMock).not.toHaveBeenCalled();
842
+ expect(addMessageMock).not.toHaveBeenCalled();
843
+ });
844
+
845
+ test("passive vellum signal still honors explicit reuse_existing action", async () => {
846
+ mockExistingConversations["conv-explicit-passive"] = {
847
+ id: "conv-explicit-passive",
848
+ source: "notification",
849
+ title: "Existing",
850
+ };
851
+ const signal = makeSignal({ requiresConversation: undefined });
852
+ const copy = makeCopy();
853
+ const conversationAction: ConversationAction = {
854
+ action: "reuse_existing",
855
+ conversationId: "conv-explicit-passive",
856
+ };
857
+
858
+ const result = await pairDeliveryWithConversation(
859
+ signal,
860
+ "vellum" as NotificationChannel,
861
+ copy,
862
+ { conversationAction },
863
+ );
864
+
865
+ expect(result.conversationId).toBe("conv-explicit-passive");
866
+ expect(result.createdNewConversation).toBe(false);
867
+ expect(createConversationMock).not.toHaveBeenCalled();
868
+ expect(addMessageMock).toHaveBeenCalledTimes(1);
869
+ });
870
+
818
871
  // ── conversationMetadata.conversationType override ─────────────────
819
872
 
820
873
  test("uses conversationMetadata.conversationType when set, overriding channel strategy", async () => {