@vellumai/assistant 0.8.1 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (506) hide show
  1. package/ARCHITECTURE.md +2 -7
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +5 -0
  5. package/docker-init-apt-root.sh +94 -0
  6. package/docker-kata-apt-env.sh +39 -0
  7. package/docs/plugins.md +88 -47
  8. package/docs/skills.md +9 -7
  9. package/examples/plugins/echo/README.md +27 -27
  10. package/examples/plugins/echo/package.json +3 -0
  11. package/examples/plugins/echo/register.ts +31 -31
  12. package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
  13. package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
  14. package/openapi.yaml +325 -3
  15. package/package.json +3 -1
  16. package/scripts/generate-openapi.ts +83 -10
  17. package/scripts/sync-llm-catalog.ts +2 -2
  18. package/scripts/sync-web-search-catalog.ts +47 -25
  19. package/src/__tests__/agent-image-optimize.test.ts +11 -3
  20. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  21. package/src/__tests__/anthropic-provider.test.ts +45 -0
  22. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  23. package/src/__tests__/app-executors.test.ts +220 -4
  24. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  25. package/src/__tests__/bundled-asset.test.ts +6 -6
  26. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  27. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  28. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  29. package/src/__tests__/clawhub.test.ts +75 -16
  30. package/src/__tests__/compactor-tail-resolution.test.ts +41 -0
  31. package/src/__tests__/config-schema.test.ts +21 -0
  32. package/src/__tests__/config-set-route.test.ts +80 -0
  33. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  34. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  35. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  36. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  37. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  38. package/src/__tests__/context-token-estimator.test.ts +1 -0
  39. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  40. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  41. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  42. package/src/__tests__/conversation-agent-loop.test.ts +2 -0
  43. package/src/__tests__/conversation-error.test.ts +42 -3
  44. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  45. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  46. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  47. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  48. package/src/__tests__/conversation-pairing.test.ts +54 -0
  49. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  50. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  51. package/src/__tests__/conversation-queue.test.ts +4 -1
  52. package/src/__tests__/conversation-runtime-assembly.test.ts +76 -9
  53. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  54. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  55. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  56. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  57. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  59. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  60. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  61. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  62. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  63. package/src/__tests__/dm-backfill.test.ts +121 -10
  64. package/src/__tests__/document-tool-security.test.ts +258 -0
  65. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  66. package/src/__tests__/edit-propagation.test.ts +33 -0
  67. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  68. package/src/__tests__/external-plugin-loader.test.ts +60 -36
  69. package/src/__tests__/filing-service.test.ts +140 -0
  70. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  71. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  72. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  73. package/src/__tests__/helpers/wait-for.ts +21 -0
  74. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  75. package/src/__tests__/history-repair.test.ts +73 -0
  76. package/src/__tests__/host-app-control-proxy.test.ts +266 -10
  77. package/src/__tests__/image-credentials.test.ts +1 -1
  78. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  79. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  80. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  81. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  82. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  83. package/src/__tests__/injector-chain.test.ts +10 -8
  84. package/src/__tests__/install-skill-routing.test.ts +155 -37
  85. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +92 -3
  86. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  87. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  88. package/src/__tests__/llm-catalog-parity.test.ts +55 -13
  89. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +34 -0
  90. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  91. package/src/__tests__/llm-usage-store.test.ts +114 -0
  92. package/src/__tests__/managed-profile-guard.test.ts +31 -29
  93. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  94. package/src/__tests__/managed-store.test.ts +84 -192
  95. package/src/__tests__/media-generate-image.test.ts +1 -1
  96. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  97. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  98. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  99. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  100. package/src/__tests__/openai-provider.test.ts +24 -0
  101. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  102. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  103. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  104. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +1 -1
  105. package/src/__tests__/platform.test.ts +2 -0
  106. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  107. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  108. package/src/__tests__/plugin-external-api.test.ts +68 -0
  109. package/src/__tests__/plugin-registry.test.ts +0 -77
  110. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  111. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  112. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  113. package/src/__tests__/plugin-types.test.ts +3 -13
  114. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  115. package/src/__tests__/process-message-display-content.test.ts +421 -0
  116. package/src/__tests__/provider-catalog-visibility.test.ts +142 -0
  117. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  118. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +8 -8
  119. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  120. package/src/__tests__/schedule-routes.test.ts +50 -3
  121. package/src/__tests__/schedule-store.test.ts +94 -0
  122. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  123. package/src/__tests__/schema-transforms.test.ts +20 -0
  124. package/src/__tests__/search-skills-unified.test.ts +0 -5
  125. package/src/__tests__/server-history-render.test.ts +43 -0
  126. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  127. package/src/__tests__/skill-load-tool.test.ts +27 -89
  128. package/src/__tests__/skill-memory.test.ts +23 -3
  129. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  130. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  131. package/src/__tests__/skills-install-extract.test.ts +49 -38
  132. package/src/__tests__/skills-install-staging.test.ts +159 -0
  133. package/src/__tests__/skills-uninstall.test.ts +9 -41
  134. package/src/__tests__/skills.test.ts +51 -58
  135. package/src/__tests__/slack-channel-config.test.ts +9 -0
  136. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  137. package/src/__tests__/system-prompt.test.ts +737 -63
  138. package/src/__tests__/terminal-tools.test.ts +28 -1
  139. package/src/__tests__/thread-backfill.test.ts +557 -27
  140. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  141. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  142. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  143. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  144. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  145. package/src/__tests__/tool-executor.test.ts +16 -4
  146. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  147. package/src/__tests__/turn-events-store.test.ts +256 -0
  148. package/src/__tests__/twilio-routes.test.ts +4 -0
  149. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  150. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  151. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  152. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  153. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  154. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  155. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  156. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  157. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  158. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  159. package/src/acp/resolve-agent.ts +1 -1
  160. package/src/agent/image-optimize.ts +13 -5
  161. package/src/calls/voice-session-bridge.ts +61 -42
  162. package/src/channels/types.ts +108 -0
  163. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  164. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  165. package/src/cli/commands/__tests__/schedules.test.ts +491 -0
  166. package/src/cli/commands/changelog.ts +106 -42
  167. package/src/cli/commands/conversations.ts +102 -17
  168. package/src/cli/commands/default-action.ts +10 -53
  169. package/src/cli/commands/notifications.ts +329 -317
  170. package/src/cli/commands/plugins.ts +185 -0
  171. package/src/cli/commands/schedules.ts +391 -0
  172. package/src/cli/commands/telemetry.ts +40 -0
  173. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  174. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  175. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  176. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  177. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  178. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  179. package/src/cli/lib/cli-colors.ts +12 -0
  180. package/src/cli/lib/confirm-prompt.ts +79 -0
  181. package/src/cli/lib/install-from-github.ts +304 -0
  182. package/src/cli/lib/list-installed-plugins.ts +137 -0
  183. package/src/cli/lib/uninstall-plugin.ts +82 -0
  184. package/src/cli/lib/unknown-command.ts +111 -0
  185. package/src/cli/program.ts +38 -2
  186. package/src/config/bundled-skills/app-builder/SKILL.md +23 -21
  187. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  188. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  189. package/src/config/bundled-skills/document/SKILL.md +23 -3
  190. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  191. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  192. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  193. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  194. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  195. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  196. package/src/config/bundled-tool-registry.ts +6 -0
  197. package/src/config/feature-flag-registry.json +41 -1
  198. package/src/config/loader.ts +64 -38
  199. package/src/config/schema.ts +7 -10
  200. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  201. package/src/config/schemas/channels.ts +8 -0
  202. package/src/config/schemas/compaction.ts +28 -0
  203. package/src/config/schemas/heartbeat.ts +9 -0
  204. package/src/config/schemas/llm-request-logs.ts +31 -7
  205. package/src/config/schemas/llm.ts +3 -0
  206. package/src/config/schemas/memory-retrieval.ts +18 -0
  207. package/src/config/schemas/tools.ts +14 -0
  208. package/src/config/skills.ts +3 -96
  209. package/src/context/compactor.ts +1047 -0
  210. package/src/context/token-estimator.ts +2 -2
  211. package/src/context/window-manager.ts +197 -1520
  212. package/src/credential-execution/managed-catalog.ts +37 -0
  213. package/src/credential-health/credential-health-service.ts +280 -19
  214. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +34 -0
  215. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  216. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  217. package/src/daemon/approval-generators.ts +8 -6
  218. package/src/daemon/config-watcher.ts +94 -31
  219. package/src/daemon/conversation-agent-loop.ts +169 -9
  220. package/src/daemon/conversation-error.ts +171 -37
  221. package/src/daemon/conversation-lifecycle.ts +53 -40
  222. package/src/daemon/conversation-messaging.ts +25 -6
  223. package/src/daemon/conversation-process.ts +49 -12
  224. package/src/daemon/conversation-runtime-assembly.ts +16 -1
  225. package/src/daemon/conversation-slash.ts +12 -5
  226. package/src/daemon/conversation-store.ts +11 -4
  227. package/src/daemon/conversation-tool-setup.ts +39 -7
  228. package/src/daemon/conversation.ts +33 -1
  229. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  230. package/src/daemon/first-greeting.ts +22 -2
  231. package/src/daemon/handlers/config-model.ts +6 -5
  232. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  233. package/src/daemon/handlers/shared.ts +14 -5
  234. package/src/daemon/handlers/skills.ts +111 -108
  235. package/src/daemon/history-repair.ts +28 -1
  236. package/src/daemon/host-app-control-proxy.ts +98 -23
  237. package/src/daemon/lifecycle.ts +45 -35
  238. package/src/daemon/meet-host-supervisor.ts +5 -4
  239. package/src/daemon/memory-v2-startup.ts +49 -0
  240. package/src/daemon/message-protocol.ts +1 -0
  241. package/src/daemon/message-types/conversations.ts +25 -0
  242. package/src/daemon/message-types/messages.ts +61 -0
  243. package/src/daemon/message-types/subagents.ts +1 -0
  244. package/src/daemon/message-types/sync.ts +1 -0
  245. package/src/daemon/pkb-reminder-builder.test.ts +1 -1
  246. package/src/daemon/pkb-reminder-builder.ts +1 -1
  247. package/src/daemon/plugin-source-watcher.ts +146 -0
  248. package/src/daemon/process-message.ts +21 -3
  249. package/src/daemon/server.ts +11 -2
  250. package/src/daemon/skill-memory-refresh.ts +29 -0
  251. package/src/documents/document-store.ts +221 -3
  252. package/src/embedded/plugin-api.ts +40 -0
  253. package/src/filing/filing-service.ts +39 -0
  254. package/src/heartbeat/__tests__/heartbeat-service.test.ts +91 -6
  255. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  256. package/src/heartbeat/heartbeat-service.ts +41 -0
  257. package/src/home/__tests__/feed-types.test.ts +40 -0
  258. package/src/home/feed-types.ts +22 -0
  259. package/src/home/post-connect-feed.ts +1 -0
  260. package/src/index.ts +18 -1
  261. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  262. package/src/mcp/client.ts +20 -4
  263. package/src/media/image-credentials.ts +3 -3
  264. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  265. package/src/memory/__tests__/conversation-queries.test.ts +263 -0
  266. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  267. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  268. package/src/memory/__tests__/message-content.test.ts +35 -0
  269. package/src/memory/bookmark-crud.ts +42 -10
  270. package/src/memory/context-search/sources/conversations.ts +62 -2
  271. package/src/memory/context-search/sources/workspace.ts +4 -0
  272. package/src/memory/conversation-crud.ts +63 -19
  273. package/src/memory/conversation-queries.ts +110 -10
  274. package/src/memory/db-init.ts +6 -0
  275. package/src/memory/delivery-crud.ts +152 -5
  276. package/src/memory/embedding-backend.ts +4 -4
  277. package/src/memory/external-conversation-store.ts +66 -5
  278. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +66 -9
  279. package/src/memory/graph/conversation-graph-memory.ts +31 -15
  280. package/src/memory/graph/tools.ts +3 -3
  281. package/src/memory/indexer.ts +34 -29
  282. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  283. package/src/memory/jobs/embed-concept-page.ts +20 -11
  284. package/src/memory/jobs-worker.ts +6 -1
  285. package/src/memory/llm-request-log-source-clickhouse.ts +17 -10
  286. package/src/memory/llm-request-log-source.ts +19 -52
  287. package/src/memory/llm-usage-store.ts +125 -5
  288. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  289. package/src/memory/message-content.ts +1 -1
  290. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  291. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  292. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  293. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  294. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  295. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  296. package/src/memory/migrations/index.ts +6 -0
  297. package/src/memory/migrations/registry.ts +8 -0
  298. package/src/memory/onboarding-events-store.ts +106 -0
  299. package/src/memory/schema/bookmarks.ts +0 -2
  300. package/src/memory/schema/calls.ts +1 -0
  301. package/src/memory/schema/inference.ts +1 -3
  302. package/src/memory/schema/infrastructure.ts +12 -0
  303. package/src/memory/turn-events-store.ts +127 -2
  304. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  305. package/src/memory/v2/__tests__/injection.test.ts +98 -8
  306. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  307. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  308. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  309. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  310. package/src/memory/v2/__tests__/router.test.ts +15 -0
  311. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  312. package/src/memory/v2/injection.ts +32 -6
  313. package/src/memory/v2/migration.ts +49 -19
  314. package/src/memory/v2/page-index.ts +35 -5
  315. package/src/memory/v2/prompts/router.ts +11 -8
  316. package/src/memory/v2/prompts/sweep.ts +2 -2
  317. package/src/memory/v2/qdrant.ts +135 -7
  318. package/src/memory/v2/router.ts +9 -8
  319. package/src/memory/v2/skill-store.ts +120 -35
  320. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  321. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  322. package/src/messaging/providers/slack/adapter.ts +43 -5
  323. package/src/messaging/providers/slack/client.ts +27 -0
  324. package/src/messaging/providers/slack/deep-link.ts +65 -0
  325. package/src/messaging/providers/slack/download.ts +104 -0
  326. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  327. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  328. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  329. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  330. package/src/messaging/providers/slack/types.ts +20 -1
  331. package/src/notifications/conversation-pairing.ts +2 -1
  332. package/src/notifications/decision-engine.ts +2 -1
  333. package/src/notifications/emit-signal.ts +20 -1
  334. package/src/notifications/home-feed-side-effect.ts +54 -0
  335. package/src/notifications/signal.ts +3 -1
  336. package/src/oauth/connection-resolver.ts +8 -4
  337. package/src/oauth/platform-connection.ts +6 -2
  338. package/src/oauth/seed-providers.ts +10 -1
  339. package/src/permissions/checker.ts +2 -0
  340. package/src/permissions/ipc-risk-types.ts +1 -0
  341. package/src/permissions/question-prompter.test.ts +416 -0
  342. package/src/permissions/question-prompter.ts +294 -0
  343. package/src/platform/client.test.ts +1 -1
  344. package/src/platform/client.ts +1 -1
  345. package/src/plugin-api/constants.ts +26 -0
  346. package/src/plugin-api/index.ts +34 -1
  347. package/src/plugin-api/types.ts +104 -22
  348. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  349. package/src/plugins/defaults/compaction.ts +0 -4
  350. package/src/plugins/defaults/empty-response.ts +0 -2
  351. package/src/plugins/defaults/history-repair.ts +0 -2
  352. package/src/plugins/defaults/injectors.ts +36 -3
  353. package/src/plugins/defaults/llm-call.ts +0 -2
  354. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  355. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  356. package/src/plugins/defaults/persistence.ts +0 -2
  357. package/src/plugins/defaults/title-generate.ts +0 -5
  358. package/src/plugins/defaults/token-estimate.ts +0 -2
  359. package/src/plugins/defaults/tool-error.ts +0 -7
  360. package/src/plugins/defaults/tool-execute.ts +0 -2
  361. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  362. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  363. package/src/plugins/external-api.ts +104 -0
  364. package/src/plugins/external-plugin-loader.ts +105 -32
  365. package/src/plugins/feature-gate.ts +22 -0
  366. package/src/plugins/pipeline.ts +37 -0
  367. package/src/plugins/registry.ts +48 -80
  368. package/src/plugins/types.ts +31 -26
  369. package/src/plugins/user-loader.ts +21 -2
  370. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  371. package/src/proactive-artifact/job.test.ts +37 -5
  372. package/src/prompts/__tests__/system-prompt.test.ts +12 -0
  373. package/src/prompts/__tests__/task-progress-hint-section.test.ts +99 -0
  374. package/src/prompts/normalize-onboarding.ts +27 -0
  375. package/src/prompts/sections.ts +302 -0
  376. package/src/prompts/system-prompt.ts +63 -166
  377. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  378. package/src/prompts/templates/system-sections.ts +173 -0
  379. package/src/providers/__tests__/inference.test.ts +22 -7
  380. package/src/providers/anthropic/client.ts +28 -28
  381. package/src/providers/connection-resolution.ts +7 -0
  382. package/src/providers/inference/adapter-factory.ts +41 -4
  383. package/src/providers/inference/connections.ts +74 -29
  384. package/src/providers/inference/resolve-auth.ts +12 -4
  385. package/src/providers/model-catalog.ts +294 -12
  386. package/src/providers/openai/chat-completions-provider.ts +10 -2
  387. package/src/providers/openrouter/client.ts +7 -0
  388. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -1
  389. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  390. package/src/providers/provider-availability.ts +17 -2
  391. package/src/providers/provider-catalog-visibility.ts +36 -0
  392. package/src/providers/registry.ts +22 -14
  393. package/src/providers/retry.ts +47 -1
  394. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  395. package/src/runtime/agent-wake.ts +42 -14
  396. package/src/runtime/auth/route-policy.ts +8 -1
  397. package/src/runtime/btw-sidechain.ts +2 -0
  398. package/src/runtime/http-types.ts +19 -0
  399. package/src/runtime/migrations/origin-mode.ts +1 -1
  400. package/src/runtime/pending-interactions.ts +1 -0
  401. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  402. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  403. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +107 -20
  404. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  405. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  406. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  407. package/src/runtime/routes/acp-routes.ts +5 -3
  408. package/src/runtime/routes/auth-routes.ts +1 -1
  409. package/src/runtime/routes/bookmark-routes.ts +5 -3
  410. package/src/runtime/routes/btw-routes.ts +5 -1
  411. package/src/runtime/routes/channel-availability-routes.ts +121 -0
  412. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  413. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  414. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  415. package/src/runtime/routes/conversation-query-routes.ts +40 -35
  416. package/src/runtime/routes/conversation-routes.ts +90 -11
  417. package/src/runtime/routes/documents-routes.ts +25 -86
  418. package/src/runtime/routes/group-routes.ts +5 -0
  419. package/src/runtime/routes/inbound-conversation.ts +28 -8
  420. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  421. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  422. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  423. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  424. package/src/runtime/routes/index.ts +6 -0
  425. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  426. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  427. package/src/runtime/routes/inference-provider-connection-routes.ts +65 -21
  428. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  429. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  430. package/src/runtime/routes/integrations/twilio.ts +6 -13
  431. package/src/runtime/routes/notification-routes.ts +1 -1
  432. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  433. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  434. package/src/runtime/routes/question-routes.ts +259 -0
  435. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  436. package/src/runtime/routes/schedule-routes.ts +4 -7
  437. package/src/runtime/routes/subagents-routes.ts +57 -18
  438. package/src/runtime/routes/telemetry-routes.ts +27 -0
  439. package/src/runtime/routes/tts-routes.ts +27 -2
  440. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  441. package/src/runtime/routes/workspace-routes.ts +28 -0
  442. package/src/runtime/services/conversation-serializer.ts +39 -7
  443. package/src/runtime/sync/resource-sync-events.ts +93 -1
  444. package/src/schedule/schedule-store.ts +27 -2
  445. package/src/schedule/scheduler.ts +9 -1
  446. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  447. package/src/security/untrusted-content.ts +93 -8
  448. package/src/skills/catalog-files.ts +1 -1
  449. package/src/skills/catalog-install.ts +233 -116
  450. package/src/skills/clawhub.ts +70 -13
  451. package/src/skills/managed-store.ts +4 -119
  452. package/src/skills/skillssh-registry.ts +27 -48
  453. package/src/subagent/manager.ts +15 -7
  454. package/src/telemetry/types.ts +113 -1
  455. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  456. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  457. package/src/tools/apps/executors.ts +58 -7
  458. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  459. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  460. package/src/tools/browser/browser-execution.ts +15 -11
  461. package/src/tools/computer-use/definitions.ts +3 -3
  462. package/src/tools/credentials/vault.ts +1 -1
  463. package/src/tools/document/document-tool.ts +124 -1
  464. package/src/tools/filesystem/edit.ts +1 -1
  465. package/src/tools/filesystem/list.ts +1 -1
  466. package/src/tools/filesystem/read.ts +1 -1
  467. package/src/tools/filesystem/write.ts +5 -2
  468. package/src/tools/host-filesystem/transfer.ts +1 -1
  469. package/src/tools/host-terminal/host-shell.ts +1 -1
  470. package/src/tools/permission-checker.ts +1 -1
  471. package/src/tools/registry.ts +17 -7
  472. package/src/tools/schedule/create.ts +2 -2
  473. package/src/tools/schema-transforms.ts +7 -2
  474. package/src/tools/side-effects.ts +1 -0
  475. package/src/tools/skills/delete-managed.ts +4 -4
  476. package/src/tools/skills/execute.ts +1 -1
  477. package/src/tools/skills/scaffold-managed.ts +3 -2
  478. package/src/tools/subagent/notify-parent.ts +1 -1
  479. package/src/tools/system/request-permission.ts +2 -2
  480. package/src/tools/terminal/safe-env.ts +60 -1
  481. package/src/tools/tool-manifest.ts +2 -0
  482. package/src/tools/types.ts +72 -21
  483. package/src/tools/ui-surface/definitions.ts +6 -5
  484. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  485. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  486. package/src/types/onboarding-context.ts +2 -0
  487. package/src/util/errors.ts +17 -0
  488. package/src/util/platform.ts +10 -0
  489. package/src/watcher/__tests__/engine.test.ts +22 -0
  490. package/src/watcher/engine.ts +6 -2
  491. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  492. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  493. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  494. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  495. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  496. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  497. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  498. package/src/workspace/migrations/registry.ts +8 -0
  499. package/src/workspace/migrations/runner.ts +39 -9
  500. package/src/workspace/migrations/types.ts +4 -0
  501. package/examples/plugins/echo/bun.lock +0 -25
  502. package/src/__tests__/context-window-manager.test.ts +0 -2481
  503. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  504. package/src/context/prompts/compact.md +0 -26
  505. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  506. /package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +0 -0
@@ -0,0 +1,240 @@
1
+ import {
2
+ readSlackMetadata,
3
+ type SlackMessageMetadata,
4
+ } from "../../messaging/providers/slack/message-metadata.js";
5
+ import {
6
+ parseExternalContentEnvelope,
7
+ unwrapExternalContentForDisplay,
8
+ } from "../../security/untrusted-content.js";
9
+ import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
10
+ import { withCrashRecovery } from "./validate-migration-state.js";
11
+
12
+ const CHECKPOINT_KEY = "migration_normalize_slack_external_content_v1";
13
+ const BATCH_SIZE = 100;
14
+
15
+ interface CandidateMessageRow {
16
+ rowid: number;
17
+ id: string;
18
+ role: string;
19
+ content: string;
20
+ metadata: string;
21
+ }
22
+
23
+ interface NormalizedMessageRow {
24
+ content: string;
25
+ metadata: string;
26
+ }
27
+
28
+ export function migrateNormalizeSlackExternalContent(
29
+ database: DrizzleDb,
30
+ ): void {
31
+ withCrashRecovery(database, CHECKPOINT_KEY, () => {
32
+ const raw = getSqliteFrom(database);
33
+
34
+ const tableExists = raw
35
+ .query(
36
+ `SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'messages'`,
37
+ )
38
+ .get();
39
+ if (!tableExists) return;
40
+
41
+ let lastRowid = 0;
42
+
43
+ for (;;) {
44
+ const rows = raw
45
+ .query(
46
+ /*sql*/ `
47
+ SELECT rowid, id, role, content, metadata
48
+ FROM messages
49
+ WHERE rowid > ?
50
+ AND metadata LIKE '%"slackMeta"%'
51
+ AND (
52
+ content LIKE '%<external_content%'
53
+ OR metadata NOT LIKE '%"provenanceTrustClass"%'
54
+ )
55
+ ORDER BY rowid
56
+ LIMIT ?
57
+ `,
58
+ )
59
+ .all(lastRowid, BATCH_SIZE) as CandidateMessageRow[];
60
+
61
+ if (rows.length === 0) break;
62
+
63
+ for (const row of rows) {
64
+ lastRowid = row.rowid;
65
+ const normalized = normalizeSlackMessageRow(row);
66
+ if (!normalized) continue;
67
+
68
+ raw
69
+ .query(`UPDATE messages SET content = ?, metadata = ? WHERE id = ?`)
70
+ .run(normalized.content, normalized.metadata, row.id);
71
+ }
72
+ }
73
+ });
74
+ }
75
+
76
+ export function downNormalizeSlackExternalContent(_database: DrizzleDb): void {
77
+ // Irreversible by design: this migration discards redundant persisted
78
+ // wrappers and leaves runtime assembly responsible for model boundaries.
79
+ }
80
+
81
+ function normalizeSlackMessageRow(
82
+ row: CandidateMessageRow,
83
+ ): NormalizedMessageRow | null {
84
+ const parsed = parseSlackMetadataEnvelope(row.metadata);
85
+ if (!parsed) return null;
86
+
87
+ const normalizedContent = normalizeMessageContent(row.content);
88
+ if (normalizedContent !== null) {
89
+ const { metadata } = parsed;
90
+ if (
91
+ !Object.prototype.hasOwnProperty.call(metadata, "provenanceTrustClass")
92
+ ) {
93
+ metadata.provenanceTrustClass = "unknown";
94
+ }
95
+
96
+ return {
97
+ content: normalizedContent,
98
+ metadata: JSON.stringify(metadata),
99
+ };
100
+ }
101
+
102
+ if (isLegacyGuardianBackfillRow(row, parsed)) {
103
+ const { metadata } = parsed;
104
+ metadata.provenanceTrustClass = "guardian";
105
+ if (
106
+ !Object.prototype.hasOwnProperty.call(metadata, "provenanceSourceChannel")
107
+ ) {
108
+ metadata.provenanceSourceChannel = "slack";
109
+ }
110
+
111
+ return {
112
+ content: row.content,
113
+ metadata: JSON.stringify(metadata),
114
+ };
115
+ }
116
+
117
+ return null;
118
+ }
119
+
120
+ function parseSlackMetadataEnvelope(rawMetadata: string): {
121
+ metadata: Record<string, unknown>;
122
+ slackMeta: SlackMessageMetadata;
123
+ } | null {
124
+ let parsed: unknown;
125
+ try {
126
+ parsed = JSON.parse(rawMetadata);
127
+ } catch {
128
+ return null;
129
+ }
130
+
131
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
132
+ return null;
133
+ }
134
+
135
+ const metadata = parsed as Record<string, unknown>;
136
+ if (typeof metadata.slackMeta !== "string") return null;
137
+ const slackMeta = readSlackMetadata(metadata.slackMeta);
138
+ if (!slackMeta) return null;
139
+ return { metadata, slackMeta };
140
+ }
141
+
142
+ function normalizeMessageContent(content: string): string | null {
143
+ const wholeEnvelope = parseExternalContentEnvelope(content);
144
+ if (wholeEnvelope) {
145
+ return wholeEnvelope.content;
146
+ }
147
+
148
+ let parsed: unknown;
149
+ try {
150
+ parsed = JSON.parse(content);
151
+ } catch {
152
+ return null;
153
+ }
154
+
155
+ if (!Array.isArray(parsed)) {
156
+ return null;
157
+ }
158
+
159
+ let changed = false;
160
+ const normalizedBlocks = parsed.map((block) => {
161
+ if (block === null || typeof block !== "object" || Array.isArray(block)) {
162
+ return block;
163
+ }
164
+ const record = block as Record<string, unknown>;
165
+ if (record.type !== "text" || typeof record.text !== "string") {
166
+ return block;
167
+ }
168
+
169
+ const unwrapped = unwrapExternalContentForDisplay(record.text);
170
+ if (unwrapped === record.text) {
171
+ return block;
172
+ }
173
+
174
+ changed = true;
175
+ return { ...record, text: unwrapped };
176
+ });
177
+
178
+ return changed ? JSON.stringify(normalizedBlocks) : null;
179
+ }
180
+
181
+ function isLegacyGuardianBackfillRow(
182
+ row: CandidateMessageRow,
183
+ parsed: {
184
+ metadata: Record<string, unknown>;
185
+ slackMeta: SlackMessageMetadata;
186
+ },
187
+ ): boolean {
188
+ if (row.role !== "user") return false;
189
+ if (parsed.slackMeta.eventKind !== "message") return false;
190
+ if (
191
+ Object.prototype.hasOwnProperty.call(
192
+ parsed.metadata,
193
+ "provenanceTrustClass",
194
+ )
195
+ ) {
196
+ return false;
197
+ }
198
+
199
+ // Old live Slack turns were written with turn-channel metadata. Old
200
+ // backfill rows were written directly with only `slackMeta`, and the old
201
+ // backfill invariant was: non-guardian non-empty text was stored wrapped,
202
+ // while guardian-authored non-empty text was stored raw. Only stamp the
203
+ // non-empty raw-text case; attachment-only / empty rows stay conservative.
204
+ if (
205
+ Object.prototype.hasOwnProperty.call(parsed.metadata, "userMessageChannel")
206
+ ) {
207
+ return false;
208
+ }
209
+
210
+ return hasNonEmptyRawText(row.content);
211
+ }
212
+
213
+ function hasNonEmptyRawText(content: string): boolean {
214
+ if (parseExternalContentEnvelope(content)) return false;
215
+
216
+ let parsed: unknown;
217
+ try {
218
+ parsed = JSON.parse(content);
219
+ } catch {
220
+ return content.trim().length > 0;
221
+ }
222
+
223
+ if (typeof parsed === "string") {
224
+ return parsed.trim().length > 0 && !parseExternalContentEnvelope(parsed);
225
+ }
226
+
227
+ if (!Array.isArray(parsed)) return false;
228
+
229
+ return parsed.some((block) => {
230
+ if (block === null || typeof block !== "object" || Array.isArray(block)) {
231
+ return false;
232
+ }
233
+ const text = (block as Record<string, unknown>).text;
234
+ return (
235
+ typeof text === "string" &&
236
+ text.trim().length > 0 &&
237
+ !parseExternalContentEnvelope(text)
238
+ );
239
+ });
240
+ }
@@ -208,6 +208,12 @@ export { migrateCreateProviderConnections } from "./243-provider-connections.js"
208
208
  export { migrateProviderConnectionStatusLabel } from "./244-provider-connection-status-label.js";
209
209
  export { migrateMemoryRetrospectiveState } from "./245-memory-retrospective-state.js";
210
210
  export { migrateBackfillProviderConnectionLabel } from "./246-backfill-provider-connection-label.js";
211
+ export { migrateExternalConversationBindingThreadId } from "./247-external-conversation-binding-thread-id.js";
212
+ export { createOnboardingEventsTable } from "./248-create-onboarding-events.js";
213
+ export {
214
+ downNormalizeSlackExternalContent,
215
+ migrateNormalizeSlackExternalContent,
216
+ } from "./249-normalize-slack-external-content.js";
211
217
  export {
212
218
  MIGRATION_REGISTRY,
213
219
  type MigrationRegistryEntry,
@@ -48,6 +48,7 @@ import { downMemoryV2ActivationLogs } from "./234-memory-v2-activation-logs.js";
48
48
  import { downSlackCompactionWatermark } from "./235-slack-compaction-watermark.js";
49
49
  import { downToolInvocationsMatchedRuleId } from "./236-tool-invocations-matched-rule-id.js";
50
50
  import { downHeartbeatRuns } from "./237-heartbeat-runs.js";
51
+ import { downNormalizeSlackExternalContent } from "./249-normalize-slack-external-content.js";
51
52
 
52
53
  export interface MigrationRegistryEntry {
53
54
  /** The checkpoint key written to memory_checkpoints on completion. */
@@ -412,6 +413,13 @@ export const MIGRATION_REGISTRY: MigrationRegistryEntry[] = [
412
413
  "Create heartbeat_runs table for tracking heartbeat execution lifecycle with CAS state transitions",
413
414
  down: downHeartbeatRuns,
414
415
  },
416
+ {
417
+ key: "migration_normalize_slack_external_content_v1",
418
+ version: 48,
419
+ description:
420
+ "Normalize legacy persisted Slack external_content wrappers back to raw message content",
421
+ down: downNormalizeSlackExternalContent,
422
+ },
415
423
  ];
416
424
 
417
425
  export function getMaxMigrationVersion(): number {
@@ -0,0 +1,106 @@
1
+ import { and, asc, eq, gt, or } from "drizzle-orm";
2
+ import { v4 as uuid } from "uuid";
3
+
4
+ import { getConfig } from "../config/loader.js";
5
+ import { getDb } from "./db-connection.js";
6
+ import { onboardingEvents } from "./schema.js";
7
+
8
+ export interface OnboardingEvent {
9
+ id: string;
10
+ createdAt: number;
11
+ screen: string;
12
+ toolsJson: string | null;
13
+ tasksJson: string | null;
14
+ tone: string | null;
15
+ googleConnected: boolean | null;
16
+ googleScopesJson: string | null;
17
+ abVariant: string | null;
18
+ }
19
+
20
+ export interface RecordOnboardingEventParams {
21
+ screen: string;
22
+ tools?: string[];
23
+ tasks?: string[];
24
+ tone?: string;
25
+ googleConnected?: boolean;
26
+ googleScopes?: string[];
27
+ abVariant?: string;
28
+ }
29
+
30
+ /**
31
+ * Record an onboarding event (pre-chat selections and Google connect status).
32
+ * Returns null when usage data collection is disabled.
33
+ */
34
+ export function recordOnboardingEvent(
35
+ params: RecordOnboardingEventParams,
36
+ ): OnboardingEvent | null {
37
+ if (!getConfig().collectUsageData) return null;
38
+ const db = getDb();
39
+ const event: OnboardingEvent = {
40
+ id: uuid(),
41
+ createdAt: Date.now(),
42
+ screen: params.screen,
43
+ toolsJson: params.tools ? JSON.stringify(params.tools) : null,
44
+ tasksJson: params.tasks ? JSON.stringify(params.tasks) : null,
45
+ tone: params.tone ?? null,
46
+ googleConnected: params.googleConnected ?? null,
47
+ googleScopesJson: params.googleScopes
48
+ ? JSON.stringify(params.googleScopes)
49
+ : null,
50
+ abVariant: params.abVariant ?? null,
51
+ };
52
+ db.insert(onboardingEvents)
53
+ .values({
54
+ id: event.id,
55
+ createdAt: event.createdAt,
56
+ screen: event.screen,
57
+ toolsJson: event.toolsJson,
58
+ tasksJson: event.tasksJson,
59
+ tone: event.tone,
60
+ googleConnected: event.googleConnected,
61
+ googleScopesJson: event.googleScopesJson,
62
+ abVariant: event.abVariant,
63
+ })
64
+ .run();
65
+ return event;
66
+ }
67
+
68
+ /**
69
+ * Query onboarding events that haven't been reported to telemetry yet.
70
+ * Uses a compound cursor (createdAt + id) for reliable watermarking.
71
+ */
72
+ export function queryUnreportedOnboardingEvents(
73
+ afterCreatedAt: number,
74
+ afterId: string | undefined,
75
+ limit: number,
76
+ ): OnboardingEvent[] {
77
+ const db = getDb();
78
+ const rows = db
79
+ .select({
80
+ id: onboardingEvents.id,
81
+ createdAt: onboardingEvents.createdAt,
82
+ screen: onboardingEvents.screen,
83
+ toolsJson: onboardingEvents.toolsJson,
84
+ tasksJson: onboardingEvents.tasksJson,
85
+ tone: onboardingEvents.tone,
86
+ googleConnected: onboardingEvents.googleConnected,
87
+ googleScopesJson: onboardingEvents.googleScopesJson,
88
+ abVariant: onboardingEvents.abVariant,
89
+ })
90
+ .from(onboardingEvents)
91
+ .where(
92
+ afterId
93
+ ? or(
94
+ gt(onboardingEvents.createdAt, afterCreatedAt),
95
+ and(
96
+ eq(onboardingEvents.createdAt, afterCreatedAt),
97
+ gt(onboardingEvents.id, afterId),
98
+ ),
99
+ )
100
+ : gt(onboardingEvents.createdAt, afterCreatedAt),
101
+ )
102
+ .orderBy(asc(onboardingEvents.createdAt), asc(onboardingEvents.id))
103
+ .limit(limit)
104
+ .all();
105
+ return rows;
106
+ }
@@ -34,5 +34,3 @@ export const messageBookmarks = sqliteTable(
34
34
  index("message_bookmarks_created_at_idx").on(table.createdAt),
35
35
  ],
36
36
  );
37
-
38
- export type MessageBookmarkRow = typeof messageBookmarks.$inferSelect;
@@ -78,6 +78,7 @@ export const externalConversationBindings = sqliteTable(
78
78
  .references(() => conversations.id, { onDelete: "cascade" }),
79
79
  sourceChannel: text("source_channel").notNull(),
80
80
  externalChatId: text("external_chat_id").notNull(),
81
+ externalThreadId: text("external_thread_id"),
81
82
  externalUserId: text("external_user_id"),
82
83
  displayName: text("display_name"),
83
84
  username: text("username"),
@@ -20,9 +20,7 @@ export const providerConnections = sqliteTable(
20
20
  createdAt: integer("created_at").notNull(),
21
21
  updatedAt: integer("updated_at").notNull(),
22
22
  },
23
- (table) => [
24
- index("idx_provider_connections_provider").on(table.provider),
25
- ],
23
+ (table) => [index("idx_provider_connections_provider").on(table.provider)],
26
24
  );
27
25
 
28
26
  export type ProviderConnectionRow = typeof providerConnections.$inferSelect;
@@ -232,6 +232,18 @@ export const lifecycleEvents = sqliteTable("lifecycle_events", {
232
232
  createdAt: integer("created_at").notNull(),
233
233
  });
234
234
 
235
+ export const onboardingEvents = sqliteTable("onboarding_events", {
236
+ id: text("id").primaryKey(),
237
+ createdAt: integer("created_at").notNull(),
238
+ screen: text("screen").notNull(),
239
+ toolsJson: text("tools_json"),
240
+ tasksJson: text("tasks_json"),
241
+ tone: text("tone"),
242
+ googleConnected: integer("google_connected", { mode: "boolean" }),
243
+ googleScopesJson: text("google_scopes_json"),
244
+ abVariant: text("ab_variant"),
245
+ });
246
+
235
247
  export const traceEvents = sqliteTable(
236
248
  "trace_events",
237
249
  {
@@ -1,16 +1,102 @@
1
1
  import { and, asc, eq, gt, or, sql } from "drizzle-orm";
2
2
 
3
3
  import { getDb } from "./db-connection.js";
4
- import { messages } from "./schema.js";
4
+ import { conversations, messages } from "./schema.js";
5
5
 
6
6
  export interface TurnEvent {
7
7
  id: string;
8
8
  createdAt: number;
9
+ /**
10
+ * Parent conversation id. Lets downstream analytics group turns by
11
+ * conversation (e.g. avg turns per conversation).
12
+ */
13
+ conversationId: string;
14
+ /**
15
+ * Conversation type of the parent conversation. Used downstream to
16
+ * distinguish user-initiated turns (`"standard"`) from system-generated
17
+ * prompts in `"background"` / `"scheduled"` conversations so analytics
18
+ * (e.g. DAU) can exclude the latter.
19
+ */
20
+ conversationType: string;
21
+ /**
22
+ * 1-indexed position of this user turn within the parent conversation,
23
+ * counting only real user turns (tool-result rows persisted with
24
+ * role="user" are excluded — same filter as the eligibility predicate
25
+ * below). The first user turn in a conversation is `1`.
26
+ *
27
+ * Computed via correlated subquery on the same filtered set used for
28
+ * eligibility; this scales with batch size (≤ BATCH_SIZE turns per
29
+ * flush) and uses the `idx_messages_conversation_id` index for the
30
+ * partition lookup.
31
+ */
32
+ turnIndex: number;
33
+ /**
34
+ * Canonical `InterfaceId` enum value identifying the UI surface the user
35
+ * was interacting from at message-creation time (`"macos"`, `"ios"`,
36
+ * `"cli"`, `"web"`, `"chrome-extension"`, `"slack"`, `"telegram"`,
37
+ * `"whatsapp"`, `"email"`, `"phone"`). Sourced from
38
+ * `messages.metadata.userMessageInterface` (stamped on insert by every
39
+ * `persistUserMessage` path that flows through `TurnChannelContext`).
40
+ *
41
+ * Null when the metadata didn't carry the field — historical rows
42
+ * predating the threading, or system-initiated turns with no inbound
43
+ * client context. Downstream analytics should treat null as
44
+ * `"unknown"`.
45
+ */
46
+ interfaceId: string | null;
47
+ /**
48
+ * Canonical `ChannelId` enum value identifying the messaging fabric the
49
+ * user message arrived on (`"vellum"` for in-app messaging from
50
+ * macos/ios/web/cli; `"slack"`/`"telegram"`/`"whatsapp"`/`"email"`/
51
+ * `"phone"` for channel-based interfaces). Sourced from
52
+ * `messages.metadata.userMessageChannel`.
53
+ *
54
+ * The 7th `ChannelId` value (`"platform"`) is APNs-push outbound-only
55
+ * and should never appear on a user-message row.
56
+ */
57
+ channelId: string | null;
58
+ /**
59
+ * Flexible client metadata stashed under `messages.metadata.client` by
60
+ * the HTTP header middleware. Carries optional `browserFamily`,
61
+ * `browserVersion`, `os`, `interfaceVersion` (and is extensible without
62
+ * a migration since it lives inside the JSON column). Null when no
63
+ * client headers were attached.
64
+ *
65
+ * Returned as raw JSON text — the reporter parses + re-shapes for the
66
+ * wire format.
67
+ */
68
+ clientMetadata: string | null;
69
+ }
70
+
71
+ /**
72
+ * SQL fragment that excludes tool-result rows persisted with role="user".
73
+ * Kept as a single source of truth so the eligibility predicate and the
74
+ * correlated `turn_index` count stay in lockstep — otherwise the index
75
+ * can drift from the visible turn stream and break "first turn" /
76
+ * "turns per conversation" math.
77
+ *
78
+ * `<alias>` is interpolated as the SQL identifier for the table whose
79
+ * `content` column should be filtered (e.g. `messages` for the outer
80
+ * query, `m2` for the correlated subquery).
81
+ */
82
+ function realUserTurnContentFilter(alias: string): ReturnType<typeof sql> {
83
+ return sql.raw(
84
+ `${alias}.content NOT LIKE '%"type":"tool\\_result"%' ESCAPE '\\' ` +
85
+ `AND ${alias}.content NOT LIKE '%"type":"web\\_search\\_tool\\_result"%' ESCAPE '\\'`,
86
+ );
9
87
  }
10
88
 
11
89
  /**
12
90
  * Query user messages (turns) that haven't been reported to telemetry yet.
13
91
  * Uses a compound cursor (createdAt + id) for reliable watermarking.
92
+ *
93
+ * Joins to `conversations` so each turn carries its `conversationType`.
94
+ * The inner join is safe because `messages.conversationId` has a
95
+ * not-null FK to `conversations.id` (cascade on delete): every message
96
+ * row has a matching conversation row.
97
+ *
98
+ * `turnIndex` is computed via a correlated subquery counting the real
99
+ * user turns in the same conversation up to and including this row.
14
100
  */
15
101
  export function queryUnreportedTurnEvents(
16
102
  afterCreatedAt: number,
@@ -19,8 +105,47 @@ export function queryUnreportedTurnEvents(
19
105
  ): TurnEvent[] {
20
106
  const db = getDb();
21
107
  const rows = db
22
- .select({ id: messages.id, createdAt: messages.createdAt })
108
+ .select({
109
+ id: messages.id,
110
+ createdAt: messages.createdAt,
111
+ conversationId: messages.conversationId,
112
+ conversationType: conversations.conversationType,
113
+ // 1-indexed turn position within the parent conversation. Counts
114
+ // only real user turns (same filter applied to the outer query).
115
+ // `(created_at, id)` lex-comparison matches the watermark cursor
116
+ // ordering so ties on `created_at` are broken deterministically.
117
+ turnIndex: sql<number>`(
118
+ SELECT COUNT(*) FROM messages AS m2
119
+ WHERE m2.conversation_id = ${messages.conversationId}
120
+ AND m2.role = 'user'
121
+ AND ${realUserTurnContentFilter("m2")}
122
+ AND (m2.created_at < ${messages.createdAt}
123
+ OR (m2.created_at = ${messages.createdAt}
124
+ AND m2.id <= ${messages.id}))
125
+ )`.as("turn_index"),
126
+ // Client attribution: extract from `messages.metadata` JSON.
127
+ // `userMessageInterface` and `userMessageChannel` are stamped on
128
+ // insert by every `persistUserMessage` path that flows through
129
+ // `TurnChannelContext`. `client` is the flexible namespace for
130
+ // browser/os/version metadata attached by HTTP header middleware.
131
+ // `json_extract` returns SQL NULL when the JSON path is absent —
132
+ // exactly the null semantics we want on the wire.
133
+ interfaceId: sql<
134
+ string | null
135
+ >`json_extract(${messages.metadata}, '$.userMessageInterface')`.as(
136
+ "interface_id",
137
+ ),
138
+ channelId: sql<
139
+ string | null
140
+ >`json_extract(${messages.metadata}, '$.userMessageChannel')`.as(
141
+ "channel_id",
142
+ ),
143
+ clientMetadata: sql<
144
+ string | null
145
+ >`json_extract(${messages.metadata}, '$.client')`.as("client_metadata"),
146
+ })
23
147
  .from(messages)
148
+ .innerJoin(conversations, eq(messages.conversationId, conversations.id))
24
149
  .where(
25
150
  and(
26
151
  eq(messages.role, "user"),
@@ -52,9 +52,7 @@ mock.module("../../qdrant-client.js", () => ({
52
52
 
53
53
  const state = {
54
54
  embedCalls: [] as Array<{ inputs: unknown[] }>,
55
- sparseCalls: [] as string[],
56
55
  embedReturn: [[0.1, 0.2, 0.3]] as number[][],
57
- sparseReturn: { indices: [1, 2, 3], values: [0.5, 0.5, 0.5] },
58
56
  /**
59
57
  * Programmable Qdrant query response queues — one per channel. Each test
60
58
  * stages whatever ordered hits it needs and lets `simBatch` /
@@ -87,10 +85,6 @@ mock.module("../../embedding-backend.js", () => ({
87
85
  vectors: state.embedReturn,
88
86
  };
89
87
  },
90
- generateSparseEmbedding: (text: string) => {
91
- state.sparseCalls.push(text);
92
- return state.sparseReturn;
93
- },
94
88
  }));
95
89
 
96
90
  class MockQdrantClient {
@@ -176,9 +170,7 @@ const { _resetMemoryV2QdrantForTests } = await import("../qdrant.js");
176
170
 
177
171
  function resetState(): void {
178
172
  state.embedCalls.length = 0;
179
- state.sparseCalls.length = 0;
180
173
  state.embedReturn = [[0.1, 0.2, 0.3]];
181
- state.sparseReturn = { indices: [1, 2, 3], values: [0.5, 0.5, 0.5] };
182
174
  state.queryResponses.dense.length = 0;
183
175
  state.queryResponses.sparse.length = 0;
184
176
  state.queryCalls.length = 0;