@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 @@ import {
26
26
  } from "../../conversations/message-consolidation.js";
27
27
  import { createApprovalConversationGenerator } from "../../daemon/approval-generators.js";
28
28
  import type { Conversation } from "../../daemon/conversation.js";
29
+ import { persistQueuedMessageBody } from "../../daemon/conversation-messaging.js";
29
30
  import {
30
31
  buildModelInfoEvent,
31
32
  formatCleanResult,
@@ -75,6 +76,7 @@ import {
75
76
  } from "../../memory/canonical-guardian-store.js";
76
77
  import {
77
78
  addMessage,
79
+ extractImageSourcePaths,
78
80
  getConversation,
79
81
  getMessages,
80
82
  getMessagesPaginated,
@@ -82,8 +84,6 @@ import {
82
84
  type MessageRow,
83
85
  provenanceFromTrustContext,
84
86
  setConversationInferenceProfile,
85
- setConversationOriginChannelIfUnset,
86
- setConversationOriginInterfaceIfUnset,
87
87
  } from "../../memory/conversation-crud.js";
88
88
  import {
89
89
  getConversationByKey,
@@ -395,23 +395,10 @@ async function tryConsumeCanonicalGuardianReply(params: {
395
395
  // is not re-processed as a new user turn.
396
396
  let messageId: string | undefined;
397
397
  try {
398
- const guardianImageSourcePaths: Record<string, string> = {};
399
- for (let i = 0; i < attachments.length; i++) {
400
- const a = attachments[i];
401
- if (a.filePath && a.mimeType.toLowerCase().startsWith("image/")) {
402
- guardianImageSourcePaths[`${i}:${a.filename}`] = a.filePath;
403
- }
404
- }
405
- const channelMeta = {
406
- userMessageChannel: sourceChannel,
407
- assistantMessageChannel: sourceChannel,
408
- userMessageInterface: sourceInterface,
409
- assistantMessageInterface: sourceInterface,
410
- provenanceTrustClass: "guardian" as const,
411
- ...(Object.keys(guardianImageSourcePaths).length > 0
412
- ? { imageSourcePaths: guardianImageSourcePaths }
413
- : {}),
414
- };
398
+ const channelMeta = buildChannelMetadata(sourceChannel, sourceInterface, {
399
+ provenanceOverride: { provenanceTrustClass: "guardian" },
400
+ attachments,
401
+ });
415
402
 
416
403
  const cleanUserMessage = createUserMessage(content, attachments);
417
404
  const llmUserMessage = enrichMessageWithSourcePaths(
@@ -538,6 +525,10 @@ export function handleListMessages({
538
525
 
539
526
  let rawMessages: MessageRow[];
540
527
  let hasMore = false;
528
+ // Resume cursor surfaced when the paginated scan stops on its row cap with a
529
+ // (possibly empty) page — lets us still emit an oldest cursor so the client
530
+ // can request the next window instead of stalling.
531
+ let scanResumeCursor: { createdAt: number; id: string } | undefined;
541
532
 
542
533
  // Drop messages flagged as hidden in metadata (e.g. internal scaffolding
543
534
  // like retrospective instructions). The LLM-side history loader
@@ -561,6 +552,7 @@ export function handleListMessages({
561
552
  );
562
553
  rawMessages = result.messages;
563
554
  hasMore = result.hasMore;
555
+ scanResumeCursor = result.nextCursor;
564
556
  } else {
565
557
  rawMessages = getMessages(resolvedConversationId).filter(visibleFilter);
566
558
  }
@@ -755,10 +747,13 @@ export function handleListMessages({
755
747
 
756
748
  // Align msgAttachments order with the file-block order captured by
757
749
  // renderHistoryContent. When a file block was persisted with
758
- // `_attachmentId`, we can join on that id to position the chip inline
759
- // (the `attachment:N` entries in contentOrder index into msgAttachments).
760
- // DB rows without a matching ref go to the tail as orphan chips;
761
- // unmatched refs drop their contentOrder entry and trigger a remap.
750
+ // `_attachmentId` (user-message uploads), we join on that id to position
751
+ // the chip inline (the `attachment:N` entries in contentOrder index into
752
+ // msgAttachments). DB rows without a matching ref go to the tail as orphan
753
+ // chips; unmatched refs drop their contentOrder entry and trigger a remap.
754
+ // Assistant-authored file blocks carry no `_attachmentId`, so when no ids
755
+ // match we fall back to positional alignment if the ref and row counts
756
+ // agree; otherwise we strip the markers and let chips fall to the tail.
762
757
  let alignedContentOrder = m.contentOrder;
763
758
  if (
764
759
  m.attachmentRefs.length > 0 &&
@@ -809,14 +804,18 @@ export function handleListMessages({
809
804
  : undefined;
810
805
  })
811
806
  .filter((e): e is string => e !== undefined);
812
- } else {
813
- // No refs carried an attachmentId we could match strip any
814
- // attachment:N entries so the client doesn't try to position
815
- // attachments inline against a misaligned array.
807
+ } else if (m.attachmentRefs.length !== msgAttachments.length) {
808
+ // No ref carried an attachmentId we could match and the counts
809
+ // disagree, so positional mapping can't be trusted — strip any
810
+ // attachment:N entries so the client doesn't position attachments
811
+ // inline against a misaligned array (they fall to the tail instead).
816
812
  alignedContentOrder = m.contentOrder.filter(
817
813
  (entry) => !ATTACHMENT_ENTRY_RE.test(entry),
818
814
  );
819
815
  }
816
+ // Otherwise no ref matched an id but the counts agree (the
817
+ // assistant-authored case): the Nth marker maps to the Nth row
818
+ // positionally, so the original contentOrder is left untouched.
820
819
  } else if (m.attachmentRefs.length > 0 && msgAttachments.length === 0) {
821
820
  // Refs were captured but no DB rows came back — drop the
822
821
  // contentOrder entries to avoid out-of-bounds renders.
@@ -855,10 +854,16 @@ export function handleListMessages({
855
854
  });
856
855
 
857
856
  if (isPaginated) {
857
+ // Prefer the page's oldest visible row (the documented cursor semantic).
858
+ // When a scan-cap-truncated page comes back empty there's no visible row
859
+ // to anchor on, so fall back to the resume cursor so the client still gets
860
+ // a `(timestamp, id)` to continue paginating from instead of stalling.
858
861
  const oldestTimestamp =
859
- rawMessages.length > 0 ? rawMessages[0].createdAt : undefined;
862
+ rawMessages.length > 0
863
+ ? rawMessages[0].createdAt
864
+ : scanResumeCursor?.createdAt;
860
865
  const oldestMessageId =
861
- rawMessages.length > 0 ? rawMessages[0].id : undefined;
866
+ rawMessages.length > 0 ? rawMessages[0].id : scanResumeCursor?.id;
862
867
  // `page=latest` always emits both metadata fields so the web client has
863
868
  // a stable contract; emit `null` when the conversation is empty.
864
869
  // The existing `beforeTimestamp` branch keeps its conditional shape to
@@ -1002,13 +1007,15 @@ export async function handleSendMessage(
1002
1007
  cohort?: string;
1003
1008
  websiteUrl?: string;
1004
1009
  contentSourceUrl?: string;
1010
+ bootstrapTemplate?: string;
1011
+ initialMessage?: string;
1012
+ skills?: string[];
1005
1013
  };
1006
1014
  };
1007
1015
 
1008
1016
  const actorPrincipalId = headers?.["x-vellum-actor-principal-id"];
1009
1017
  const principalType = headers?.["x-vellum-principal-type"];
1010
- const originClientId =
1011
- headers?.["x-vellum-client-id"]?.trim() || undefined;
1018
+ const originClientId = headers?.["x-vellum-client-id"]?.trim() || undefined;
1012
1019
 
1013
1020
  const { conversationKey, content, attachmentIds } = body;
1014
1021
  const inboundConversationId =
@@ -1149,10 +1156,13 @@ export async function handleSendMessage(
1149
1156
  // assistant-minted internal id) when the client supplies it — clients
1150
1157
  // must obtain this id from a prior daemon response, so a missing row
1151
1158
  // is a 404. Otherwise fall through to the external-key path: the
1152
- // client-supplied `conversationKey` (used by non-vellum channels and
1153
- // the web idempotency flow) or, when neither is provided, a stable
1154
- // default keyed on sourceChannel + sourceInterface so repeated calls
1155
- // from the same channel/interface share a single thread.
1159
+ // client-supplied `conversationKey` (external-key lookup; materializes
1160
+ // on first use) or, when neither is provided, a channel-dependent
1161
+ // default. The vellum channel mints a fresh conversation on every
1162
+ // empty-handed send so first-message-of-a-new-chat surfaces with a
1163
+ // server-minted id; other channels (phone, slack, …) share a stable
1164
+ // `default:<channel>:<interface>` thread so repeated calls from the
1165
+ // same channel/interface stay co-located.
1156
1166
  let mapping: {
1157
1167
  conversationId: string;
1158
1168
  conversationType: string;
@@ -1174,7 +1184,9 @@ export async function handleSendMessage(
1174
1184
  const resolvedConversationKey =
1175
1185
  conversationKey && conversationKey.length > 0
1176
1186
  ? conversationKey
1177
- : `default:${sourceChannel}:${sourceInterface}`;
1187
+ : sourceChannel === "vellum"
1188
+ ? crypto.randomUUID()
1189
+ : `default:${sourceChannel}:${sourceInterface}`;
1178
1190
  mapping = getOrCreateConversation(resolvedConversationKey, {
1179
1191
  conversationType: "standard",
1180
1192
  });
@@ -1388,48 +1400,35 @@ export async function handleSendMessage(
1388
1400
  : ("content-source" as const);
1389
1401
  effectiveContent = buildScanFirstMessage(scanUrl, scanVariant);
1390
1402
  // Fall through to normal inference path below
1391
- } else if (isWakeUp && body.onboarding?.cohort === "content-automation") {
1392
- effectiveContent = "I want to write articles that rank better in GEO";
1393
- // Fall through to normal inference path — the bootstrap template
1394
- // and geo-writing skill handle this message.
1403
+ } else if (isWakeUp && body.onboarding?.initialMessage) {
1404
+ effectiveContent = body.onboarding.initialMessage;
1395
1405
  } else if (isWakeUp) {
1396
1406
  const cannedGreeting = getCannedFirstGreeting(body.onboarding ?? undefined);
1397
1407
 
1398
1408
  conversation.processing = true;
1399
1409
  let cleanupDeferred = false;
1400
1410
  try {
1401
- const provenance = provenanceFromTrustContext(conversation.trustContext);
1402
- const channelMeta = {
1403
- ...provenance,
1411
+ const rawContent = content ?? "";
1412
+ const attachments = hasAttachments
1413
+ ? smDeps.resolveAttachments(attachmentIds)
1414
+ : [];
1415
+ const greetingMeta = {
1404
1416
  userMessageChannel: sourceChannel,
1405
1417
  assistantMessageChannel: sourceChannel,
1406
1418
  userMessageInterface: sourceInterface,
1407
1419
  assistantMessageInterface: sourceInterface,
1408
1420
  };
1409
-
1410
- const rawContent = content ?? "";
1411
- const attachments = hasAttachments
1412
- ? smDeps.resolveAttachments(attachmentIds)
1413
- : [];
1414
- const userMsg = createUserMessage(rawContent, attachments);
1415
- const persisted = await addMessage(
1416
- mapping.conversationId,
1417
- "user",
1418
- JSON.stringify(userMsg.content),
1419
- channelMeta,
1420
- );
1421
- conversation.getMessages().push(userMsg);
1422
-
1423
- setConversationOriginChannelIfUnset(
1424
- mapping.conversationId,
1425
- sourceChannel,
1426
- );
1427
- setConversationOriginInterfaceIfUnset(
1428
- mapping.conversationId,
1429
- sourceInterface,
1430
- );
1421
+ const persisted = await persistQueuedMessageBody(conversation, {
1422
+ content: rawContent,
1423
+ attachments,
1424
+ requestId: crypto.randomUUID(),
1425
+ metadata: greetingMeta,
1426
+ });
1431
1427
 
1432
1428
  const conversationId = mapping.conversationId;
1429
+ const channelMeta = buildChannelMetadata(sourceChannel, sourceInterface, {
1430
+ trustContext: conversation.trustContext,
1431
+ });
1433
1432
 
1434
1433
  const assistantMsg = createAssistantMessage(cannedGreeting);
1435
1434
  const persistedAssistant = await addMessage(
@@ -1580,25 +1579,22 @@ export async function handleSendMessage(
1580
1579
  if (conversation.isProcessing()) {
1581
1580
  // Queue the message so it's processed when the current turn completes
1582
1581
  const requestId = crypto.randomUUID();
1583
- const enqueueResult = conversation.enqueueMessage(
1584
- contentAfterScan,
1582
+ const enqueueResult = conversation.enqueueMessage({
1583
+ content: contentAfterScan,
1585
1584
  attachments,
1586
- broadcastMessage,
1585
+ onEvent: broadcastMessage,
1587
1586
  requestId,
1588
- undefined, // activeSurfaceId
1589
- undefined, // currentPage
1590
- {
1587
+ metadata: {
1591
1588
  userMessageChannel: sourceChannel,
1592
1589
  assistantMessageChannel: sourceChannel,
1593
1590
  userMessageInterface: sourceInterface,
1594
1591
  assistantMessageInterface: sourceInterface,
1595
1592
  ...(body.automated === true ? { automated: true } : {}),
1596
1593
  },
1597
- { isInteractive },
1598
- undefined, // displayContent
1594
+ isInteractive,
1599
1595
  transport,
1600
1596
  clientMessageId,
1601
- );
1597
+ });
1602
1598
  if (enqueueResult.rejected) {
1603
1599
  return new RouteResponse(
1604
1600
  JSON.stringify({ accepted: false, error: "queue_full" }),
@@ -1718,35 +1714,31 @@ export async function handleSendMessage(
1718
1714
  conversation.processing = true;
1719
1715
  let cleanupDeferred = false;
1720
1716
  try {
1721
- const provenance = provenanceFromTrustContext(conversation.trustContext);
1722
- const imageSourcePaths: Record<string, string> = {};
1723
- for (let i = 0; i < attachments.length; i++) {
1724
- const a = attachments[i];
1725
- if (a.filePath && a.mimeType.toLowerCase().startsWith("image/")) {
1726
- imageSourcePaths[`${i}:${a.filename}`] = a.filePath;
1727
- }
1728
- }
1729
- const channelMeta = {
1730
- ...provenance,
1717
+ const slashMeta = {
1731
1718
  userMessageChannel: sourceChannel,
1732
1719
  assistantMessageChannel: sourceChannel,
1733
1720
  userMessageInterface: sourceInterface,
1734
1721
  assistantMessageInterface: sourceInterface,
1735
1722
  ...(body.automated === true ? { automated: true } : {}),
1736
- ...(Object.keys(imageSourcePaths).length > 0
1737
- ? { imageSourcePaths }
1738
- : {}),
1739
1723
  };
1740
- const cleanMsg = createUserMessage(rawContent, attachments);
1741
- const llmMsg = enrichMessageWithSourcePaths(cleanMsg, attachments);
1742
- const persisted = await addMessage(
1743
- mapping.conversationId,
1744
- "user",
1745
- JSON.stringify(cleanMsg.content),
1746
- channelMeta,
1747
- );
1748
- conversation.getMessages().push(llmMsg);
1724
+ const persisted = await persistQueuedMessageBody(conversation, {
1725
+ content: rawContent,
1726
+ attachments,
1727
+ requestId: crypto.randomUUID(),
1728
+ metadata: slashMeta,
1729
+ clientMessageId,
1730
+ });
1731
+ if (persisted.deduplicated) {
1732
+ return {
1733
+ accepted: true,
1734
+ messageId: persisted.id,
1735
+ conversationId: mapping.conversationId,
1736
+ };
1737
+ }
1749
1738
 
1739
+ const channelMeta = buildChannelMetadata(sourceChannel, sourceInterface, {
1740
+ trustContext: conversation.trustContext,
1741
+ });
1750
1742
  const assistantMsg = createAssistantMessage(slashResult.message);
1751
1743
  const persistedAssistant = await addMessage(
1752
1744
  mapping.conversationId,
@@ -1756,15 +1748,6 @@ export async function handleSendMessage(
1756
1748
  );
1757
1749
  conversation.getMessages().push(assistantMsg);
1758
1750
 
1759
- setConversationOriginChannelIfUnset(
1760
- mapping.conversationId,
1761
- sourceChannel,
1762
- );
1763
- setConversationOriginInterfaceIfUnset(
1764
- mapping.conversationId,
1765
- sourceInterface,
1766
- );
1767
-
1768
1751
  // Snapshot model info now so the deferred callback cannot observe
1769
1752
  // a config change from a concurrent request.
1770
1753
  const modelInfoEvent = isModelSlashCommand(rawContent)
@@ -1827,23 +1810,21 @@ export async function handleSendMessage(
1827
1810
 
1828
1811
  if (slashResult.kind === "compact") {
1829
1812
  conversation.processing = true;
1830
- const provenance = provenanceFromTrustContext(conversation.trustContext);
1831
- const channelMeta = {
1832
- ...provenance,
1813
+ const slashMeta = {
1833
1814
  userMessageChannel: sourceChannel,
1834
1815
  assistantMessageChannel: sourceChannel,
1835
1816
  userMessageInterface: sourceInterface,
1836
1817
  assistantMessageInterface: sourceInterface,
1837
1818
  };
1838
- const cleanMsg = createUserMessage(rawContent, attachments);
1839
- let persisted: Awaited<ReturnType<typeof addMessage>>;
1819
+ let persisted: Awaited<ReturnType<typeof persistQueuedMessageBody>>;
1840
1820
  try {
1841
- persisted = await addMessage(
1842
- mapping.conversationId,
1843
- "user",
1844
- JSON.stringify(cleanMsg.content),
1845
- channelMeta,
1846
- );
1821
+ persisted = await persistQueuedMessageBody(conversation, {
1822
+ content: rawContent,
1823
+ attachments,
1824
+ requestId: crypto.randomUUID(),
1825
+ metadata: slashMeta,
1826
+ clientMessageId,
1827
+ });
1847
1828
  } catch (err) {
1848
1829
  // The fire-and-forget compaction below owns clearing `processing`, but a
1849
1830
  // throw from this initial persist never reaches it — reset here so the
@@ -1852,9 +1833,20 @@ export async function handleSendMessage(
1852
1833
  silentlyWithLog(conversation.drainQueue(), "compact-command queue drain");
1853
1834
  throw err;
1854
1835
  }
1855
- conversation.getMessages().push(cleanMsg);
1836
+ if (persisted.deduplicated) {
1837
+ conversation.processing = false;
1838
+ silentlyWithLog(conversation.drainQueue(), "compact-dedup queue drain");
1839
+ return {
1840
+ accepted: true,
1841
+ messageId: persisted.id,
1842
+ conversationId: mapping.conversationId,
1843
+ };
1844
+ }
1856
1845
 
1857
1846
  const conversationId = mapping.conversationId;
1847
+ const channelMeta = buildChannelMetadata(sourceChannel, sourceInterface, {
1848
+ trustContext: conversation.trustContext,
1849
+ });
1858
1850
 
1859
1851
  // Fire-and-forget: return 202 immediately, run compaction async.
1860
1852
  // forceCompact() makes an LLM call that can exceed the client's
@@ -1937,23 +1929,30 @@ export async function handleSendMessage(
1937
1929
  // initial user-message persist below, which would otherwise leave the
1938
1930
  // conversation stuck in queued mode indefinitely.
1939
1931
  try {
1940
- const provenance = provenanceFromTrustContext(conversation.trustContext);
1941
- const channelMeta = {
1942
- ...provenance,
1932
+ const slashMeta = {
1943
1933
  userMessageChannel: sourceChannel,
1944
1934
  assistantMessageChannel: sourceChannel,
1945
1935
  userMessageInterface: sourceInterface,
1946
1936
  assistantMessageInterface: sourceInterface,
1947
1937
  };
1948
- const cleanMsg = createUserMessage(rawContent, attachments);
1949
- const persisted = await addMessage(
1950
- mapping.conversationId,
1951
- "user",
1952
- JSON.stringify(cleanMsg.content),
1953
- channelMeta,
1954
- );
1955
- conversation.getMessages().push(cleanMsg);
1938
+ const persisted = await persistQueuedMessageBody(conversation, {
1939
+ content: rawContent,
1940
+ attachments,
1941
+ requestId: crypto.randomUUID(),
1942
+ metadata: slashMeta,
1943
+ clientMessageId,
1944
+ });
1945
+ if (persisted.deduplicated) {
1946
+ return {
1947
+ accepted: true,
1948
+ messageId: persisted.id,
1949
+ conversationId,
1950
+ };
1951
+ }
1956
1952
 
1953
+ const channelMeta = buildChannelMetadata(sourceChannel, sourceInterface, {
1954
+ trustContext: conversation.trustContext,
1955
+ });
1957
1956
  let assistantMessagePersisted = false;
1958
1957
  try {
1959
1958
  broadcastMessage({
@@ -2017,16 +2016,22 @@ export async function handleSendMessage(
2017
2016
  const resolvedContent = slashResult.content;
2018
2017
 
2019
2018
  const requestId = crypto.randomUUID();
2020
- let messageId: string;
2021
- try {
2022
- messageId = await conversation.persistUserMessage(
2023
- resolvedContent,
2024
- attachments,
2025
- requestId,
2026
- body.automated === true ? { automated: true } : undefined,
2027
- );
2028
- } catch (err) {
2029
- throw err;
2019
+ const persistResult = await conversation.persistUserMessage({
2020
+ content: resolvedContent,
2021
+ attachments,
2022
+ requestId,
2023
+ metadata: body.automated === true ? { automated: true } : undefined,
2024
+ clientMessageId,
2025
+ });
2026
+
2027
+ const messageId = persistResult.id;
2028
+
2029
+ if (persistResult.deduplicated) {
2030
+ return {
2031
+ accepted: true,
2032
+ messageId,
2033
+ conversationId: mapping.conversationId,
2034
+ };
2030
2035
  }
2031
2036
 
2032
2037
  broadcastMessage({
@@ -2088,8 +2093,8 @@ async function generateLlmSuggestion(
2088
2093
  "",
2089
2094
  "CRITICAL — write from the USER'S perspective only, NEVER from the assistant's:",
2090
2095
  "- The suggestion is what the USER will type into the chat input",
2091
- "- Use first-person \"I\" only if the user has used it in their prior messages",
2092
- "- NEVER start with phrases like \"I can help\", \"Here's what\", \"Let me\", \"I'd suggest\" — those are assistant-voice",
2096
+ '- Use first-person "I" only if the user has used it in their prior messages',
2097
+ '- NEVER start with phrases like "I can help", "Here\'s what", "Let me", "I\'d suggest" — those are assistant-voice',
2093
2098
  "- Think: if you were the user reading the assistant's reply, what question or follow-up would you ask next?",
2094
2099
  "",
2095
2100
  "Output only the reply text inside the requested tags — no preamble, no commentary.",
@@ -2342,6 +2347,47 @@ function handleSearchConversations({
2342
2347
  return { query, results };
2343
2348
  }
2344
2349
 
2350
+ // ---------------------------------------------------------------------------
2351
+ // Metadata helpers
2352
+ // ---------------------------------------------------------------------------
2353
+
2354
+ /**
2355
+ * Assemble the standard channel metadata object for message persistence.
2356
+ *
2357
+ * Combines provenance (trust context), channel/interface routing, and
2358
+ * optional per-message fields (automated flag, image source paths) into the
2359
+ * Record that `addMessage` stores in the `metadata` column.
2360
+ */
2361
+ function buildChannelMetadata(
2362
+ sourceChannel: string,
2363
+ sourceInterface: string,
2364
+ opts?: {
2365
+ trustContext?: Parameters<typeof provenanceFromTrustContext>[0];
2366
+ provenanceOverride?: Record<string, unknown>;
2367
+ automated?: boolean;
2368
+ attachments?: ReadonlyArray<{
2369
+ filename: string;
2370
+ mimeType: string;
2371
+ filePath?: string;
2372
+ }>;
2373
+ },
2374
+ ): Record<string, unknown> {
2375
+ const provenance =
2376
+ opts?.provenanceOverride ?? provenanceFromTrustContext(opts?.trustContext);
2377
+ const imageSourcePaths = opts?.attachments
2378
+ ? extractImageSourcePaths(opts.attachments)
2379
+ : undefined;
2380
+ return {
2381
+ ...provenance,
2382
+ userMessageChannel: sourceChannel,
2383
+ assistantMessageChannel: sourceChannel,
2384
+ userMessageInterface: sourceInterface,
2385
+ assistantMessageInterface: sourceInterface,
2386
+ ...(opts?.automated ? { automated: true } : {}),
2387
+ ...(imageSourcePaths ? { imageSourcePaths } : {}),
2388
+ };
2389
+ }
2390
+
2345
2391
  // ---------------------------------------------------------------------------
2346
2392
  // Module-level state
2347
2393
  // ---------------------------------------------------------------------------
@@ -2,7 +2,11 @@ import { eq } from "drizzle-orm";
2
2
  import { z } from "zod";
3
3
 
4
4
  import { getConfig } from "../../config/loader.js";
5
- import { addMessage, createConversation } from "../../memory/conversation-crud.js";
5
+ import {
6
+ addMessage,
7
+ createConversation,
8
+ type MessageRole,
9
+ } from "../../memory/conversation-crud.js";
6
10
  import {
7
11
  getConversationByKey,
8
12
  setConversationKey,
@@ -22,7 +26,7 @@ const log = getLogger("conversations-import-routes");
22
26
  // -- Types --
23
27
 
24
28
  interface ImportMessage {
25
- role: string;
29
+ role: MessageRole;
26
30
  content: string | Array<{ type: string; text: string }>;
27
31
  createdAt?: number;
28
32
  }
@@ -59,7 +63,10 @@ function resolveTimestamps(conv: ImportConversation): {
59
63
  // -- Handler --
60
64
 
61
65
  async function handleConversationsImport({ body }: RouteHandlerArgs) {
62
- if (!body || !Array.isArray((body as Record<string, unknown>).conversations)) {
66
+ if (
67
+ !body ||
68
+ !Array.isArray((body as Record<string, unknown>).conversations)
69
+ ) {
63
70
  throw new BadRequestError("conversations array required");
64
71
  }
65
72
 
@@ -70,7 +77,8 @@ async function handleConversationsImport({ body }: RouteHandlerArgs) {
70
77
  let imported = 0;
71
78
  let skipped = 0;
72
79
  let totalMessages = 0;
73
- const errors: Array<{ index: number; sourceKey?: string; error: string }> = [];
80
+ const errors: Array<{ index: number; sourceKey?: string; error: string }> =
81
+ [];
74
82
 
75
83
  for (let idx = 0; idx < payload.conversations.length; idx++) {
76
84
  const conv = payload.conversations[idx];
@@ -90,7 +98,8 @@ async function handleConversationsImport({ body }: RouteHandlerArgs) {
90
98
  }
91
99
  }
92
100
 
93
- const { convCreatedAt, convUpdatedAt, messageTimestamps } = resolveTimestamps(conv);
101
+ const { convCreatedAt, convUpdatedAt, messageTimestamps } =
102
+ resolveTimestamps(conv);
94
103
 
95
104
  const conversation = createConversation(conv.title);
96
105
 
@@ -122,7 +131,11 @@ async function handleConversationsImport({ body }: RouteHandlerArgs) {
122
131
  .orderBy(messagesTable.createdAt)
123
132
  .all();
124
133
 
125
- for (let i = 0; i < dbMessages.length && i < messageTimestamps.length; i++) {
134
+ for (
135
+ let i = 0;
136
+ i < dbMessages.length && i < messageTimestamps.length;
137
+ i++
138
+ ) {
126
139
  db.update(messagesTable)
127
140
  .set({ createdAt: messageTimestamps[i] })
128
141
  .where(eq(messagesTable.id, dbMessages[i].id))
@@ -82,7 +82,16 @@ export const ROUTES: RouteDefinition[] = [
82
82
  },
83
83
  ],
84
84
  responseBody: z.object({
85
- documents: z.array(z.unknown()).describe("Document summary objects"),
85
+ documents: z.array(
86
+ z.object({
87
+ surfaceId: z.string(),
88
+ conversationId: z.string(),
89
+ title: z.string(),
90
+ wordCount: z.number(),
91
+ createdAt: z.number(),
92
+ updatedAt: z.number(),
93
+ }),
94
+ ),
86
95
  }),
87
96
  handler: ({ queryParams }) => {
88
97
  const conversationId = queryParams?.conversationId ?? undefined;
@@ -22,6 +22,13 @@ import { publishConversationListChanged } from "../sync/resource-sync-events.js"
22
22
  import { BadRequestError, ForbiddenError, NotFoundError } from "./errors.js";
23
23
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
24
24
 
25
+ const groupSchema = z.object({
26
+ id: z.string(),
27
+ name: z.string(),
28
+ sortPosition: z.number(),
29
+ isSystemGroup: z.boolean(),
30
+ });
31
+
25
32
  function serializeGroup(group: ReturnType<typeof getGroup>) {
26
33
  if (!group) return null;
27
34
  return {
@@ -169,6 +176,7 @@ export const ROUTES: RouteDefinition[] = [
169
176
  summary: "List groups",
170
177
  description: "Return all conversation groups.",
171
178
  tags: ["groups"],
179
+ responseBody: z.object({ groups: z.array(groupSchema) }),
172
180
  },
173
181
  {
174
182
  operationId: "groups_create",
@@ -184,6 +192,7 @@ export const ROUTES: RouteDefinition[] = [
184
192
  requestBody: z.object({
185
193
  name: z.string().describe("Group name"),
186
194
  }),
195
+ responseBody: groupSchema,
187
196
  additionalResponses: {
188
197
  "400": {
189
198
  description:
@@ -204,6 +213,7 @@ export const ROUTES: RouteDefinition[] = [
204
213
  name: z.string().optional(),
205
214
  sortPosition: z.number().optional(),
206
215
  }),
216
+ responseBody: groupSchema,
207
217
  additionalResponses: {
208
218
  "403": {
209
219
  description: "System group sort position cannot be changed",
@@ -251,6 +261,7 @@ export const ROUTES: RouteDefinition[] = [
251
261
  )
252
262
  .describe("Array of { groupId, sortPosition } objects"),
253
263
  }),
264
+ responseBody: z.object({ ok: z.boolean() }),
254
265
  additionalResponses: {
255
266
  "403": {
256
267
  description: "Cannot reorder system groups",