@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
@@ -34,7 +34,17 @@ mock.module("../memory/llm-usage-store.js", () => ({
34
34
  }));
35
35
 
36
36
  const mockQueryUnreportedTurnEvents = mock(
37
- () => [] as { id: string; createdAt: number }[],
37
+ () =>
38
+ [] as {
39
+ id: string;
40
+ createdAt: number;
41
+ conversationId: string;
42
+ conversationType: string;
43
+ turnIndex: number;
44
+ interfaceId: string | null;
45
+ channelId: string | null;
46
+ clientMetadata: string | null;
47
+ }[],
38
48
  );
39
49
 
40
50
  mock.module("../memory/turn-events-store.js", () => ({
@@ -95,6 +105,25 @@ mock.module("../memory/lifecycle-events-store.js", () => ({
95
105
  queryUnreportedLifecycleEvents: mockQueryUnreportedLifecycleEvents,
96
106
  }));
97
107
 
108
+ const mockQueryUnreportedOnboardingEvents = mock(
109
+ () =>
110
+ [] as {
111
+ id: string;
112
+ createdAt: number;
113
+ screen: string;
114
+ toolsJson: string | null;
115
+ tasksJson: string | null;
116
+ tone: string | null;
117
+ googleConnected: boolean | null;
118
+ googleScopesJson: string | null;
119
+ abVariant: string | null;
120
+ }[],
121
+ );
122
+
123
+ mock.module("../memory/onboarding-events-store.js", () => ({
124
+ queryUnreportedOnboardingEvents: mockQueryUnreportedOnboardingEvents,
125
+ }));
126
+
98
127
  // ---------------------------------------------------------------------------
99
128
  // Production import (after mocks)
100
129
  // ---------------------------------------------------------------------------
@@ -108,7 +137,18 @@ import { UsageTelemetryReporter } from "./usage-telemetry-reporter.js";
108
137
 
109
138
  let eventIdCounter = 0;
110
139
 
111
- function makeUsageEvent(overrides: Partial<UsageEvent> = {}): UsageEvent {
140
+ // The reporter consumes `UnreportedUsageEvent` (UsageEvent + the two
141
+ // JOIN-computed fields `conversationType` and `turnIndex`). Build that
142
+ // shape directly so the mock matches `queryUnreportedUsageEvents`'
143
+ // return type exactly.
144
+ type UnreportedUsageEventFixture = UsageEvent & {
145
+ conversationType: string | null;
146
+ turnIndex: number | null;
147
+ };
148
+
149
+ function makeUsageEvent(
150
+ overrides: Partial<UnreportedUsageEventFixture> = {},
151
+ ): UnreportedUsageEventFixture {
112
152
  eventIdCounter += 1;
113
153
  return {
114
154
  id: `evt-${eventIdCounter}`,
@@ -128,6 +168,8 @@ function makeUsageEvent(overrides: Partial<UsageEvent> = {}): UsageEvent {
128
168
  requestId: null,
129
169
  estimatedCostUsd: 0.001,
130
170
  pricingStatus: "priced",
171
+ conversationType: "standard",
172
+ turnIndex: 1,
131
173
  ...overrides,
132
174
  };
133
175
  }
@@ -149,6 +191,8 @@ beforeEach(() => {
149
191
  mockQueryUnreportedTurnEvents.mockReturnValue([]);
150
192
  mockQueryUnreportedLifecycleEvents.mockReset();
151
193
  mockQueryUnreportedLifecycleEvents.mockReturnValue([]);
194
+ mockQueryUnreportedOnboardingEvents.mockReset();
195
+ mockQueryUnreportedOnboardingEvents.mockReturnValue([]);
152
196
  mockPlatformClient = null;
153
197
  mockGetPlatformBaseUrl.mockReset();
154
198
  mockGetDeviceId.mockReset();
@@ -501,7 +545,16 @@ describe("UsageTelemetryReporter", () => {
501
545
  const usageEvent = makeUsageEvent({ id: "evt-mixed-usage" });
502
546
  mockQueryUnreportedUsageEvents.mockReturnValue([usageEvent]);
503
547
  mockQueryUnreportedTurnEvents.mockReturnValue([
504
- { id: "evt-mixed-turn", createdAt: 1700000050000 },
548
+ {
549
+ id: "evt-mixed-turn",
550
+ createdAt: 1700000050000,
551
+ conversationId: "conv-mixed",
552
+ conversationType: "standard",
553
+ turnIndex: 1,
554
+ interfaceId: null,
555
+ channelId: null,
556
+ clientMetadata: null,
557
+ },
505
558
  ]);
506
559
  mockFetch.mockImplementation(() =>
507
560
  Promise.resolve(new Response('{"accepted":2}', { status: 200 })),
@@ -532,6 +585,259 @@ describe("UsageTelemetryReporter", () => {
532
585
  expect(turnEvent).toBeDefined();
533
586
  expect(turnEvent.daemon_event_id).toBe("evt-mixed-turn");
534
587
  expect(turnEvent.recorded_at).toBe(1700000050000);
588
+ expect(turnEvent.conversation_id).toBe("conv-mixed");
589
+ expect(turnEvent.conversation_type).toBe("standard");
590
+ expect(turnEvent.turn_index).toBe(1);
591
+ });
592
+
593
+ test("turn events carry conversation_type for background/scheduled conversations", async () => {
594
+ mockQueryUnreportedUsageEvents.mockReturnValue([]);
595
+ mockQueryUnreportedTurnEvents.mockReturnValue([
596
+ {
597
+ id: "evt-turn-standard",
598
+ createdAt: 1700000100000,
599
+ conversationId: "conv-std",
600
+ conversationType: "standard",
601
+ turnIndex: 1,
602
+ interfaceId: null,
603
+ channelId: null,
604
+ clientMetadata: null,
605
+ },
606
+ {
607
+ id: "evt-turn-background",
608
+ createdAt: 1700000200000,
609
+ conversationId: "conv-bg",
610
+ conversationType: "background",
611
+ turnIndex: 1,
612
+ interfaceId: null,
613
+ channelId: null,
614
+ clientMetadata: null,
615
+ },
616
+ {
617
+ id: "evt-turn-scheduled",
618
+ createdAt: 1700000300000,
619
+ conversationId: "conv-sched",
620
+ conversationType: "scheduled",
621
+ turnIndex: 1,
622
+ interfaceId: null,
623
+ channelId: null,
624
+ clientMetadata: null,
625
+ },
626
+ ]);
627
+ mockFetch.mockImplementation(() =>
628
+ Promise.resolve(new Response('{"accepted":3}', { status: 200 })),
629
+ );
630
+
631
+ const reporter = new UsageTelemetryReporter();
632
+ await reporter.flush();
633
+
634
+ expect(mockFetch).toHaveBeenCalledTimes(1);
635
+ const body = JSON.parse(
636
+ (mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
637
+ );
638
+
639
+ const byId: Record<string, { conversation_type: string }> = {};
640
+ for (const e of body.events as Array<{
641
+ daemon_event_id: string;
642
+ conversation_type: string;
643
+ }>) {
644
+ byId[e.daemon_event_id] = e;
645
+ }
646
+
647
+ expect(byId["evt-turn-standard"].conversation_type).toBe("standard");
648
+ expect(byId["evt-turn-background"].conversation_type).toBe("background");
649
+ expect(byId["evt-turn-scheduled"].conversation_type).toBe("scheduled");
650
+ });
651
+
652
+ test("turn events carry interface_id, channel_id, and parsed client metadata", async () => {
653
+ // Four turns spanning the relevant cases:
654
+ // - macOS in-app turn with full client block (typical interactive user)
655
+ // - slack inbound turn with no client block (channel-based, no headers)
656
+ // - web turn with malformed client JSON (parsing guard: emit null,
657
+ // do not break the batch)
658
+ // - historical turn with no metadata at all (pre-rollout row)
659
+ mockQueryUnreportedUsageEvents.mockReturnValue([]);
660
+ mockQueryUnreportedTurnEvents.mockReturnValue([
661
+ {
662
+ id: "evt-turn-macos",
663
+ createdAt: 1700000400000,
664
+ conversationId: "conv-mac",
665
+ conversationType: "standard",
666
+ turnIndex: 1,
667
+ interfaceId: "macos",
668
+ channelId: "vellum",
669
+ clientMetadata: JSON.stringify({
670
+ browser_family: null,
671
+ os: "darwin",
672
+ interface_version: "0.8.2",
673
+ }),
674
+ },
675
+ {
676
+ id: "evt-turn-slack",
677
+ createdAt: 1700000500000,
678
+ conversationId: "conv-slack",
679
+ conversationType: "standard",
680
+ turnIndex: 1,
681
+ interfaceId: "slack",
682
+ channelId: "slack",
683
+ clientMetadata: null,
684
+ },
685
+ {
686
+ id: "evt-turn-web-broken",
687
+ createdAt: 1700000600000,
688
+ conversationId: "conv-web",
689
+ conversationType: "standard",
690
+ turnIndex: 1,
691
+ interfaceId: "web",
692
+ channelId: "vellum",
693
+ clientMetadata: "{not valid json",
694
+ },
695
+ {
696
+ id: "evt-turn-legacy",
697
+ createdAt: 1700000700000,
698
+ conversationId: "conv-legacy",
699
+ conversationType: "standard",
700
+ turnIndex: 1,
701
+ interfaceId: null,
702
+ channelId: null,
703
+ clientMetadata: null,
704
+ },
705
+ ]);
706
+ mockFetch.mockImplementation(() =>
707
+ Promise.resolve(new Response('{"accepted":4}', { status: 200 })),
708
+ );
709
+
710
+ const reporter = new UsageTelemetryReporter();
711
+ await reporter.flush();
712
+
713
+ expect(mockFetch).toHaveBeenCalledTimes(1);
714
+ const body = JSON.parse(
715
+ (mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
716
+ );
717
+
718
+ const byId: Record<
719
+ string,
720
+ {
721
+ interface_id: string | null;
722
+ channel_id: string | null;
723
+ client: Record<string, unknown> | null;
724
+ }
725
+ > = {};
726
+ for (const e of body.events as Array<{
727
+ daemon_event_id: string;
728
+ interface_id: string | null;
729
+ channel_id: string | null;
730
+ client: Record<string, unknown> | null;
731
+ }>) {
732
+ byId[e.daemon_event_id] = e;
733
+ }
734
+
735
+ expect(byId["evt-turn-macos"]).toMatchObject({
736
+ interface_id: "macos",
737
+ channel_id: "vellum",
738
+ client: {
739
+ os: "darwin",
740
+ interface_version: "0.8.2",
741
+ },
742
+ });
743
+ expect(byId["evt-turn-slack"]).toMatchObject({
744
+ interface_id: "slack",
745
+ channel_id: "slack",
746
+ client: null,
747
+ });
748
+ // Malformed client JSON is downgraded to null without failing the
749
+ // batch — the interface_id/channel_id from the typed columns still
750
+ // ride through cleanly.
751
+ expect(byId["evt-turn-web-broken"]).toMatchObject({
752
+ interface_id: "web",
753
+ channel_id: "vellum",
754
+ client: null,
755
+ });
756
+ expect(byId["evt-turn-legacy"]).toMatchObject({
757
+ interface_id: null,
758
+ channel_id: null,
759
+ client: null,
760
+ });
761
+ });
762
+
763
+ test("llm_usage events carry conversation_id, conversation_type, and turn_index", async () => {
764
+ // Three LLM calls across the spectrum of the new fields:
765
+ // - tied to a conversation, mid-turn (typical foreground)
766
+ // - tied to a background conversation, first turn
767
+ // - untied (memory consolidation: no conversation, no turn)
768
+ mockQueryUnreportedUsageEvents.mockReturnValue([
769
+ makeUsageEvent({
770
+ id: "evt-fg-call",
771
+ conversationId: "conv-fg",
772
+ conversationType: "standard",
773
+ turnIndex: 4,
774
+ }),
775
+ makeUsageEvent({
776
+ id: "evt-bg-call",
777
+ conversationId: "conv-bg",
778
+ conversationType: "background",
779
+ turnIndex: 1,
780
+ }),
781
+ makeUsageEvent({
782
+ id: "evt-untied-call",
783
+ conversationId: null,
784
+ conversationType: null,
785
+ turnIndex: null,
786
+ }),
787
+ ]);
788
+ mockFetch.mockImplementation(() =>
789
+ Promise.resolve(new Response('{"accepted":3}', { status: 200 })),
790
+ );
791
+
792
+ const reporter = new UsageTelemetryReporter();
793
+ await reporter.flush();
794
+
795
+ expect(mockFetch).toHaveBeenCalledTimes(1);
796
+ const body = JSON.parse(
797
+ (mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
798
+ );
799
+
800
+ const byId: Record<
801
+ string,
802
+ {
803
+ type: string;
804
+ conversation_id: string | null;
805
+ conversation_type: string | null;
806
+ turn_index: number | null;
807
+ }
808
+ > = {};
809
+ for (const e of body.events as Array<{
810
+ type: string;
811
+ daemon_event_id: string;
812
+ conversation_id: string | null;
813
+ conversation_type: string | null;
814
+ turn_index: number | null;
815
+ }>) {
816
+ byId[e.daemon_event_id] = e;
817
+ }
818
+
819
+ expect(byId["evt-fg-call"]).toMatchObject({
820
+ type: "llm_usage",
821
+ conversation_id: "conv-fg",
822
+ conversation_type: "standard",
823
+ turn_index: 4,
824
+ });
825
+ expect(byId["evt-bg-call"]).toMatchObject({
826
+ type: "llm_usage",
827
+ conversation_id: "conv-bg",
828
+ conversation_type: "background",
829
+ turn_index: 1,
830
+ });
831
+ // LLM calls without a parent conversation flush through with all
832
+ // three conversation-level fields null — the serializer accepts
833
+ // allow_null and downstream SQL filters can `WHERE conversation_id
834
+ // IS NOT NULL` to scope to foreground analytics.
835
+ expect(byId["evt-untied-call"]).toMatchObject({
836
+ type: "llm_usage",
837
+ conversation_id: null,
838
+ conversation_type: null,
839
+ turn_index: null,
840
+ });
535
841
  });
536
842
 
537
843
  test("flush is skipped and watermarks advanced when collectUsageData is false", async () => {
@@ -548,15 +854,16 @@ describe("UsageTelemetryReporter", () => {
548
854
  // No HTTP call should have been made
549
855
  expect(mockFetch).not.toHaveBeenCalled();
550
856
 
551
- // All 3 timestamp watermarks should have been advanced (IDs left untouched
857
+ // All 4 timestamp watermarks should have been advanced (IDs left untouched
552
858
  // so the compound-cursor branch stays active)
553
- expect(mockSetMemoryCheckpoint).toHaveBeenCalledTimes(3);
859
+ expect(mockSetMemoryCheckpoint).toHaveBeenCalledTimes(4);
554
860
 
555
861
  const calls = mockSetMemoryCheckpoint.mock.calls;
556
862
  const keys = calls.map((c) => c[0]);
557
863
  expect(keys).toContain("telemetry:usage:last_reported_at");
558
864
  expect(keys).toContain("telemetry:turns:last_reported_at");
559
865
  expect(keys).toContain("telemetry:lifecycle:last_reported_at");
866
+ expect(keys).toContain("telemetry:onboarding:last_reported_at");
560
867
  });
561
868
 
562
869
  test("events sent normally after re-enabling collectUsageData", async () => {
@@ -21,12 +21,13 @@ import {
21
21
  } from "../memory/checkpoints.js";
22
22
  import { queryUnreportedLifecycleEvents } from "../memory/lifecycle-events-store.js";
23
23
  import { queryUnreportedUsageEvents } from "../memory/llm-usage-store.js";
24
+ import { queryUnreportedOnboardingEvents } from "../memory/onboarding-events-store.js";
24
25
  import { queryUnreportedTurnEvents } from "../memory/turn-events-store.js";
25
26
  import { VellumPlatformClient } from "../platform/client.js";
26
27
  import { getDeviceId } from "../util/device-id.js";
27
28
  import { getLogger } from "../util/logger.js";
28
29
  import { APP_VERSION } from "../version.js";
29
- import type { TelemetryEvent } from "./types.js";
30
+ import type { TelemetryEvent, TurnTelemetryClientInfo } from "./types.js";
30
31
 
31
32
  const log = getLogger("usage-telemetry");
32
33
 
@@ -42,12 +43,32 @@ const CHECKPOINT_KEY_LIFECYCLE_WATERMARK =
42
43
  "telemetry:lifecycle:last_reported_at";
43
44
  const CHECKPOINT_KEY_LIFECYCLE_WATERMARK_ID =
44
45
  "telemetry:lifecycle:last_reported_id";
46
+ const CHECKPOINT_KEY_ONBOARDING_WATERMARK =
47
+ "telemetry:onboarding:last_reported_at";
48
+ const CHECKPOINT_KEY_ONBOARDING_WATERMARK_ID =
49
+ "telemetry:onboarding:last_reported_id";
45
50
  const REPORT_INTERVAL_MS = 5 * 60 * 1000;
46
51
  const INITIAL_FLUSH_DELAY_MS = 30_000; // Delay first flush to let CES handshake complete
47
52
  const BATCH_SIZE = 500;
48
53
  const MAX_CONSECUTIVE_BATCHES = 10;
49
54
  const TELEMETRY_PATH = "/v1/telemetry/ingest/";
50
55
 
56
+ // ---------------------------------------------------------------------------
57
+ // Singleton access
58
+ // ---------------------------------------------------------------------------
59
+
60
+ let _instance: UsageTelemetryReporter | null = null;
61
+
62
+ export function getUsageTelemetryReporter(): UsageTelemetryReporter | null {
63
+ return _instance;
64
+ }
65
+
66
+ export function setUsageTelemetryReporter(
67
+ reporter: UsageTelemetryReporter | null,
68
+ ): void {
69
+ _instance = reporter;
70
+ }
71
+
51
72
  // ---------------------------------------------------------------------------
52
73
  // Reporter
53
74
  // ---------------------------------------------------------------------------
@@ -117,6 +138,7 @@ export class UsageTelemetryReporter {
117
138
  setMemoryCheckpoint(CHECKPOINT_KEY_WATERMARK, now);
118
139
  setMemoryCheckpoint(CHECKPOINT_KEY_TURN_WATERMARK, now);
119
140
  setMemoryCheckpoint(CHECKPOINT_KEY_LIFECYCLE_WATERMARK, now);
141
+ setMemoryCheckpoint(CHECKPOINT_KEY_ONBOARDING_WATERMARK, now);
120
142
  return;
121
143
  }
122
144
 
@@ -141,6 +163,14 @@ export class UsageTelemetryReporter {
141
163
  const lifecycleWatermarkId =
142
164
  getMemoryCheckpoint(CHECKPOINT_KEY_LIFECYCLE_WATERMARK_ID) ?? undefined;
143
165
 
166
+ // Read onboarding watermark (compound cursor: createdAt + id)
167
+ const onboardingWatermark = Number(
168
+ getMemoryCheckpoint(CHECKPOINT_KEY_ONBOARDING_WATERMARK) ?? "0",
169
+ );
170
+ const onboardingWatermarkId =
171
+ getMemoryCheckpoint(CHECKPOINT_KEY_ONBOARDING_WATERMARK_ID) ??
172
+ undefined;
173
+
144
174
  // Query unreported events
145
175
  const events = queryUnreportedUsageEvents(
146
176
  watermark,
@@ -157,11 +187,17 @@ export class UsageTelemetryReporter {
157
187
  lifecycleWatermarkId,
158
188
  BATCH_SIZE,
159
189
  );
190
+ const onboardingEvents = queryUnreportedOnboardingEvents(
191
+ onboardingWatermark,
192
+ onboardingWatermarkId,
193
+ BATCH_SIZE,
194
+ );
160
195
 
161
196
  if (
162
197
  events.length === 0 &&
163
198
  turnEvents.length === 0 &&
164
- lifecycleEvents.length === 0
199
+ lifecycleEvents.length === 0 &&
200
+ onboardingEvents.length === 0
165
201
  )
166
202
  return;
167
203
 
@@ -174,6 +210,7 @@ export class UsageTelemetryReporter {
174
210
  usageCount: events.length,
175
211
  turnCount: turnEvents.length,
176
212
  lifecycleCount: lifecycleEvents.length,
213
+ onboardingCount: onboardingEvents.length,
177
214
  },
178
215
  "Telemetry flush: resolved auth context",
179
216
  );
@@ -184,6 +221,13 @@ export class UsageTelemetryReporter {
184
221
  (e): TelemetryEvent => ({
185
222
  type: "llm_usage",
186
223
  daemon_event_id: e.id,
224
+ // Conversation-level metadata for analytics joins. All three
225
+ // are nullable on the wire: `conversation_id` is null for
226
+ // LLM calls not tied to a conversation (memory consolidation,
227
+ // background work), and the other two cascade from that.
228
+ conversation_id: e.conversationId,
229
+ conversation_type: e.conversationType,
230
+ turn_index: e.turnIndex,
187
231
  provider: e.provider,
188
232
  model: e.model,
189
233
  input_tokens: e.inputTokens,
@@ -198,13 +242,43 @@ export class UsageTelemetryReporter {
198
242
  recorded_at: e.createdAt,
199
243
  }),
200
244
  ),
201
- ...turnEvents.map(
202
- (e): TelemetryEvent => ({
245
+ ...turnEvents.map((e): TelemetryEvent => {
246
+ // `messages.metadata.client` is a nested JSON object extracted
247
+ // via `json_extract`; sqlite returns it as a text representation.
248
+ // Parse defensively — a corrupted blob in the JSON column should
249
+ // not block the whole batch flush.
250
+ let client: TurnTelemetryClientInfo | null = null;
251
+ if (e.clientMetadata) {
252
+ try {
253
+ const parsed = JSON.parse(e.clientMetadata) as unknown;
254
+ if (
255
+ parsed &&
256
+ typeof parsed === "object" &&
257
+ !Array.isArray(parsed)
258
+ ) {
259
+ client = parsed as TurnTelemetryClientInfo;
260
+ }
261
+ } catch {
262
+ // Malformed client JSON — emit null rather than fail the
263
+ // batch. Logged once below for visibility.
264
+ log.warn(
265
+ { turnId: e.id, conversationId: e.conversationId },
266
+ "Telemetry turn: failed to parse messages.metadata.client; emitting null",
267
+ );
268
+ }
269
+ }
270
+ return {
203
271
  type: "turn",
204
272
  daemon_event_id: e.id,
205
273
  recorded_at: e.createdAt,
206
- }),
207
- ),
274
+ conversation_id: e.conversationId,
275
+ conversation_type: e.conversationType,
276
+ turn_index: e.turnIndex,
277
+ interface_id: e.interfaceId,
278
+ channel_id: e.channelId,
279
+ client,
280
+ };
281
+ }),
208
282
  ...lifecycleEvents.map(
209
283
  (e): TelemetryEvent => ({
210
284
  type: "lifecycle",
@@ -213,6 +287,24 @@ export class UsageTelemetryReporter {
213
287
  recorded_at: e.createdAt,
214
288
  }),
215
289
  ),
290
+ ...onboardingEvents.map(
291
+ (e): TelemetryEvent => ({
292
+ type: "onboarding",
293
+ daemon_event_id: e.id,
294
+ recorded_at: e.createdAt,
295
+ screen: e.screen,
296
+ ...(e.toolsJson ? { tools: JSON.parse(e.toolsJson) } : {}),
297
+ ...(e.tasksJson ? { tasks: JSON.parse(e.tasksJson) } : {}),
298
+ ...(e.tone ? { tone: e.tone } : {}),
299
+ ...(e.googleConnected != null
300
+ ? { google_connected: e.googleConnected }
301
+ : {}),
302
+ ...(e.googleScopesJson
303
+ ? { google_scopes: JSON.parse(e.googleScopesJson) }
304
+ : {}),
305
+ ...(e.abVariant ? { ab_variant: e.abVariant } : {}),
306
+ }),
307
+ ),
216
308
  ];
217
309
 
218
310
  const organizationId = getPlatformOrganizationId() || undefined;
@@ -285,11 +377,25 @@ export class UsageTelemetryReporter {
285
377
  );
286
378
  }
287
379
 
380
+ // Advance onboarding watermark (compound cursor)
381
+ if (onboardingEvents.length > 0) {
382
+ const lastOnboarding = onboardingEvents[onboardingEvents.length - 1];
383
+ setMemoryCheckpoint(
384
+ CHECKPOINT_KEY_ONBOARDING_WATERMARK,
385
+ String(lastOnboarding.createdAt),
386
+ );
387
+ setMemoryCheckpoint(
388
+ CHECKPOINT_KEY_ONBOARDING_WATERMARK_ID,
389
+ lastOnboarding.id,
390
+ );
391
+ }
392
+
288
393
  // If we got a full batch of any type, there may be more — recurse
289
394
  if (
290
395
  events.length === BATCH_SIZE ||
291
396
  turnEvents.length === BATCH_SIZE ||
292
- lifecycleEvents.length === BATCH_SIZE
397
+ lifecycleEvents.length === BATCH_SIZE ||
398
+ onboardingEvents.length === BATCH_SIZE
293
399
  ) {
294
400
  await this._doFlush(batchCount + 1);
295
401
  }