@vellumai/assistant 0.10.9 → 0.10.10-dev.202607162206.d08e98e

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 (456) hide show
  1. package/ARCHITECTURE.md +1 -1
  2. package/Dockerfile +8 -0
  3. package/docs/activation-funnel-telemetry.md +13 -7
  4. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  5. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  7. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  8. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  9. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  10. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  11. package/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  12. package/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  13. package/openapi.yaml +530 -107
  14. package/package.json +1 -1
  15. package/scripts/generate-openapi.ts +8 -0
  16. package/src/__tests__/activation-early-marking.test.ts +6 -5
  17. package/src/__tests__/agent-loop-override-profile.test.ts +22 -25
  18. package/src/__tests__/agent-wake-override-profile.test.ts +21 -48
  19. package/src/__tests__/app-builder-tool-scripts.test.ts +0 -1
  20. package/src/__tests__/app-bundler.test.ts +4 -12
  21. package/src/__tests__/app-executors.test.ts +2 -49
  22. package/src/__tests__/app-routes-csp.test.ts +114 -146
  23. package/src/__tests__/auth-fallback-events-store.test.ts +8 -1
  24. package/src/__tests__/build-persisted-content.test.ts +96 -0
  25. package/src/__tests__/bundle-scanner.test.ts +27 -1
  26. package/src/__tests__/call-controller.test.ts +291 -0
  27. package/src/__tests__/call-site-routing-connection-auto-resolve.test.ts +165 -0
  28. package/src/__tests__/chat-credential-redaction.test.ts +1395 -0
  29. package/src/__tests__/chat-reveal-guard-priming.test.ts +791 -0
  30. package/src/__tests__/compaction.benchmark.test.ts +2 -1
  31. package/src/__tests__/compactor-image-manifest-trust.test.ts +50 -0
  32. package/src/__tests__/config-loader-backfill.test.ts +9 -4
  33. package/src/__tests__/config-schema-cmd.test.ts +10 -11
  34. package/src/__tests__/config-schema.test.ts +190 -257
  35. package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +208 -0
  36. package/src/__tests__/conversation-agent-loop-overflow.test.ts +5 -19
  37. package/src/__tests__/conversation-agent-loop.test.ts +22 -9
  38. package/src/__tests__/conversation-error.test.ts +31 -0
  39. package/src/__tests__/conversation-load-history-repair.test.ts +110 -0
  40. package/src/__tests__/conversation-process-callsite.test.ts +12 -19
  41. package/src/__tests__/conversation-routes-slash-commands.test.ts +7 -21
  42. package/src/__tests__/conversation-summarize-route.test.ts +36 -44
  43. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
  44. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +7 -7
  45. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +0 -1
  46. package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -1
  47. package/src/__tests__/conversation-usage.test.ts +4 -12
  48. package/src/__tests__/credential-routes.test.ts +250 -0
  49. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  50. package/src/__tests__/db-migration-rollback.test.ts +22 -0
  51. package/src/__tests__/empty-response-hook.test.ts +186 -1
  52. package/src/__tests__/external-plugin-loader.test.ts +11 -5
  53. package/src/__tests__/heartbeat-service.test.ts +0 -28
  54. package/src/__tests__/host-shell-tool.test.ts +2 -0
  55. package/src/__tests__/inactive-tool-error-messages.test.ts +2 -2
  56. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +52 -8
  57. package/src/__tests__/internal-telemetry-routes.test.ts +23 -4
  58. package/src/__tests__/invite-routes-http.test.ts +12 -16
  59. package/src/__tests__/list-all-apps.test.ts +0 -4
  60. package/src/__tests__/llm-context-resolution.test.ts +32 -56
  61. package/src/__tests__/llm-request-log-turn-query.test.ts +109 -0
  62. package/src/__tests__/llm-resolver-override-or-default.test.ts +3 -52
  63. package/src/__tests__/llm-resolver.test.ts +342 -602
  64. package/src/__tests__/llm-schema.test.ts +79 -37
  65. package/src/__tests__/max-tokens-continue-hook.test.ts +19 -0
  66. package/src/__tests__/media-stream-output.test.ts +259 -3
  67. package/src/__tests__/media-stream-server-integration.test.ts +22 -1
  68. package/src/__tests__/media-stream-stt-session.test.ts +47 -0
  69. package/src/__tests__/memory-jobs-worker-cleanup-cadence.test.ts +33 -0
  70. package/src/__tests__/memory-recall-log-store.test.ts +47 -13
  71. package/src/__tests__/mock-gateway-ipc.ts +46 -1
  72. package/src/__tests__/mtime-cache.test.ts +61 -0
  73. package/src/__tests__/navigate-settings-tab.test.ts +2 -0
  74. package/src/__tests__/normalize-onboarding.test.ts +33 -0
  75. package/src/__tests__/onboarding-persona-write.test.ts +26 -0
  76. package/src/__tests__/plugin-api-resolve-credential.test.ts +140 -0
  77. package/src/__tests__/plugin-app-serve-routes.test.ts +166 -14
  78. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +2 -0
  79. package/src/__tests__/post-turn-tool-result-truncation.test.ts +38 -0
  80. package/src/__tests__/provider-commit-message-generator.test.ts +27 -20
  81. package/src/__tests__/provider-connections-backfill.test.ts +138 -0
  82. package/src/__tests__/provider-platform-proxy-integration.test.ts +10 -31
  83. package/src/__tests__/provider-registry-ollama.test.ts +8 -18
  84. package/src/__tests__/provider-send-message-override-profile.test.ts +23 -23
  85. package/src/__tests__/provider-usage-tracking.test.ts +9 -19
  86. package/src/__tests__/prune-old-conversations-job.test.ts +12 -0
  87. package/src/__tests__/published-app-updater.test.ts +22 -16
  88. package/src/__tests__/registry.test.ts +5 -16
  89. package/src/__tests__/retry-openrouter-only-normalization.test.ts +22 -23
  90. package/src/__tests__/retry-thinking-adaptive-only.test.ts +43 -48
  91. package/src/__tests__/retry-thinking-tool-choice.test.ts +57 -66
  92. package/src/__tests__/retry-verbosity-normalization.test.ts +24 -23
  93. package/src/__tests__/reveal-success-registry.test.ts +123 -0
  94. package/src/__tests__/run-conversation-turn-persistence.test.ts +130 -0
  95. package/src/__tests__/secret-fixtures.ts +9 -0
  96. package/src/__tests__/server-history-render.test.ts +28 -0
  97. package/src/__tests__/skills.test.ts +9 -4
  98. package/src/__tests__/slack-share-routes.test.ts +0 -1
  99. package/src/__tests__/stt-stream-session.test.ts +6 -5
  100. package/src/__tests__/subagent-call-site-routing.test.ts +69 -95
  101. package/src/__tests__/subagent-disposal.test.ts +2 -0
  102. package/src/__tests__/subagent-fork-notifications.test.ts +2 -0
  103. package/src/__tests__/subagent-fork-spawn.test.ts +2 -0
  104. package/src/__tests__/subagent-manager-notify.test.ts +2 -0
  105. package/src/__tests__/subagent-role-registry.test.ts +37 -0
  106. package/src/__tests__/subagent-spawn-and-await.test.ts +1 -0
  107. package/src/__tests__/subagent-terminal-message.test.ts +50 -0
  108. package/src/__tests__/subagent-tool-gate-mode.test.ts +78 -5
  109. package/src/__tests__/surface-completion-nudge-hook.test.ts +19 -0
  110. package/src/__tests__/telemetry-routes.test.ts +99 -19
  111. package/src/__tests__/tool-audit.test.ts +34 -4
  112. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  113. package/src/__tests__/tool-profiler.test.ts +72 -1
  114. package/src/__tests__/tool-result-spool.test.ts +49 -4
  115. package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -1
  116. package/src/__tests__/ui-channel-variants.test.ts +108 -0
  117. package/src/__tests__/ui-shape-teaching.test.ts +255 -0
  118. package/src/__tests__/usage-attribution.test.ts +18 -41
  119. package/src/__tests__/user-plugin-loader.test.ts +4 -4
  120. package/src/__tests__/voice-config-update.test.ts +46 -0
  121. package/src/__tests__/voice-session-bridge.test.ts +216 -84
  122. package/src/__tests__/workspace-migration-131-drop-web-fetch-mode.test.ts +120 -0
  123. package/src/agent/loop.ts +4 -3
  124. package/src/api/events/open-conversation.test.ts +64 -0
  125. package/src/api/events/open-conversation.ts +33 -0
  126. package/src/api/index.ts +6 -0
  127. package/src/api/responses/conversation-message.ts +5 -0
  128. package/src/apps/app-store.ts +25 -35
  129. package/src/bundler/app-bundler.ts +34 -48
  130. package/src/bundler/app-compiler.ts +39 -4
  131. package/src/bundler/bundle-scanner.ts +13 -0
  132. package/src/bundler/manifest.ts +1 -1
  133. package/src/calls/__tests__/voice-session-bridge.test.ts +47 -0
  134. package/src/calls/call-constants.ts +5 -0
  135. package/src/calls/call-controller.ts +100 -32
  136. package/src/calls/call-transport.ts +9 -0
  137. package/src/calls/media-stream-output.ts +107 -4
  138. package/src/calls/media-stream-server.ts +29 -9
  139. package/src/calls/media-stream-stt-session.ts +7 -1
  140. package/src/calls/media-turn-detector.ts +11 -1
  141. package/src/calls/voice-session-bridge.ts +84 -63
  142. package/src/cli/commands/__tests__/inference-providers.test.ts +270 -33
  143. package/src/cli/commands/__tests__/notifications.test.ts +24 -3
  144. package/src/cli/commands/config.help.ts +6 -6
  145. package/src/cli/commands/credentials.help.ts +13 -0
  146. package/src/cli/commands/credentials.ts +6 -1
  147. package/src/cli/commands/email.help.ts +7 -0
  148. package/src/cli/commands/email.ts +35 -1
  149. package/src/cli/commands/inference-providers.ts +168 -107
  150. package/src/cli/commands/inference.help.ts +118 -46
  151. package/src/cli/commands/memory/index.help.ts +23 -0
  152. package/src/cli/commands/memory/nodes.ts +146 -0
  153. package/src/cli/commands/notifications.help.ts +10 -10
  154. package/src/cli/commands/oauth/connect-surface-guidance.test.ts +40 -0
  155. package/src/cli/commands/oauth/connect-surface-guidance.ts +54 -0
  156. package/src/cli/commands/oauth/connect.test.ts +126 -0
  157. package/src/cli/commands/oauth/connect.ts +29 -6
  158. package/src/cli/commands/oauth/index.help.ts +7 -1
  159. package/src/cli/commands/oauth/status.test.ts +69 -3
  160. package/src/cli/commands/oauth/status.ts +50 -12
  161. package/src/cli/commands/plugins.help.ts +13 -2
  162. package/src/cli/commands/plugins.ts +61 -7
  163. package/src/cli/commands/telemetry.help.ts +13 -0
  164. package/src/cli/commands/telemetry.ts +45 -5
  165. package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +8 -2
  166. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +213 -9
  167. package/src/cli/lib/bundled-marketplace.json +93 -36
  168. package/src/cli/lib/inspect-plugin.ts +5 -1
  169. package/src/cli/lib/install-from-github.ts +68 -4
  170. package/src/cli/lib/plugin-catalog-local.ts +6 -2
  171. package/src/cli/lib/plugin-fingerprint.ts +3 -3
  172. package/src/cli/lib/upgrade-plugin.ts +236 -35
  173. package/src/config/__tests__/default-profile-catalog.test.ts +8 -12
  174. package/src/config/__tests__/plugin-resident-skill-discovery.test.ts +137 -0
  175. package/src/config/__tests__/profile-materialization.test.ts +1 -88
  176. package/src/config/bundled-skills/AGENTS.md +3 -30
  177. package/src/config/bundled-skills/app-builder/SKILL.md +5 -3
  178. package/src/config/bundled-skills/app-builder/TOOLS.json +23 -0
  179. package/src/config/bundled-skills/app-builder/tools/app-open.ts +32 -0
  180. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +1 -1
  181. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +7 -7
  182. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  183. package/src/config/bundled-skills/schedule/TOOLS.json +1 -1
  184. package/src/config/bundled-skills/settings/TOOLS.json +3 -1
  185. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +2 -0
  186. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +18 -10
  187. package/src/config/bundled-skills/subagent/SKILL.md +2 -0
  188. package/src/config/bundled-skills/subagent/TOOLS.json +1 -1
  189. package/src/config/call-site-defaults.ts +3 -3
  190. package/src/config/feature-flag-registry.json +24 -39
  191. package/src/config/llm-resolver.ts +88 -475
  192. package/src/config/profile-materialization.ts +11 -11
  193. package/src/config/schema.ts +93 -127
  194. package/src/config/schemas/__tests__/live-voice.test.ts +24 -6
  195. package/src/config/schemas/__tests__/stt.test.ts +31 -3
  196. package/src/config/schemas/live-voice.ts +10 -4
  197. package/src/config/schemas/llm.ts +52 -15
  198. package/src/config/schemas/memory-lifecycle.ts +1 -1
  199. package/src/config/schemas/memory-retrospective.ts +8 -0
  200. package/src/config/schemas/memory-v2.ts +2 -2
  201. package/src/config/schemas/memory-v3.ts +1 -1
  202. package/src/config/schemas/services.ts +6 -3
  203. package/src/config/schemas/stt.ts +38 -21
  204. package/src/config/schemas/tts.ts +13 -18
  205. package/src/config/skills.ts +31 -21
  206. package/src/context/compactor.ts +44 -7
  207. package/src/context/post-turn-tool-result-truncation.ts +4 -2
  208. package/src/context/tool-result-spool.ts +26 -5
  209. package/src/conversations/__tests__/message-consolidation.test.ts +48 -0
  210. package/src/conversations/message-consolidation.ts +22 -2
  211. package/src/daemon/__tests__/conversation-tool-setup.test.ts +5 -12
  212. package/src/daemon/app-source-watcher.ts +17 -23
  213. package/src/daemon/chat-credential-redaction.ts +1365 -0
  214. package/src/daemon/conversation-agent-loop-handlers.ts +627 -34
  215. package/src/daemon/conversation-agent-loop.ts +71 -31
  216. package/src/daemon/conversation-error.ts +36 -14
  217. package/src/daemon/conversation-process.ts +22 -0
  218. package/src/daemon/conversation-store.ts +35 -0
  219. package/src/daemon/conversation-surfaces.ts +25 -25
  220. package/src/daemon/conversation-tool-setup.ts +33 -7
  221. package/src/daemon/conversation.ts +55 -5
  222. package/src/daemon/handlers/shared.ts +33 -2
  223. package/src/daemon/lifecycle.ts +4 -4
  224. package/src/daemon/message-types/conversations.ts +6 -17
  225. package/src/daemon/providers-setup.ts +8 -0
  226. package/src/daemon/tool-setup-types.ts +7 -1
  227. package/src/daemon/wake-conversation-ops.ts +10 -1
  228. package/src/hooks/types.ts +9 -0
  229. package/src/ipc/__tests__/email-ipc.test.ts +90 -0
  230. package/src/ipc/gateway-client.test.ts +59 -0
  231. package/src/ipc/gateway-client.ts +70 -27
  232. package/src/live-voice/__tests__/live-voice-events.test.ts +14 -2
  233. package/src/live-voice/__tests__/live-voice-integration.test.ts +116 -2
  234. package/src/live-voice/__tests__/live-voice-vad.test.ts +804 -13
  235. package/src/live-voice/__tests__/protocol.test.ts +122 -0
  236. package/src/live-voice/live-voice-session.ts +443 -40
  237. package/src/live-voice/protocol.ts +143 -1
  238. package/src/monitoring/__tests__/plugin-source-watch.test.ts +3 -1
  239. package/src/monitoring/plugin-source-watch.ts +3 -62
  240. package/src/notifications/README.md +1 -1
  241. package/src/permissions/checker.ts +8 -4
  242. package/src/persistence/__tests__/db-init-migrations-ok.test.ts +26 -0
  243. package/src/persistence/conversation-crud.ts +104 -2
  244. package/src/persistence/db-init.ts +14 -3
  245. package/src/persistence/job-handlers/cleanup.ts +15 -7
  246. package/src/persistence/llm-request-log-store.ts +88 -52
  247. package/src/persistence/migrations/298-move-memory-jobs-to-memory-db.ts +7 -31
  248. package/src/persistence/migrations/305-drop-contact-acl-columns.ts +3 -2
  249. package/src/persistence/migrations/326-move-injection-events-to-memory-db.ts +8 -34
  250. package/src/persistence/migrations/336-move-memory-v2-activation-logs-to-memory-db.ts +90 -0
  251. package/src/persistence/migrations/337-move-memory-recall-logs-to-memory-db.ts +114 -0
  252. package/src/persistence/migrations/338-move-memory-v3-selections-to-memory-db.ts +84 -0
  253. package/src/persistence/migrations/339-move-activation-sessions-to-memory-db.ts +52 -0
  254. package/src/persistence/migrations/__tests__/run-migrations.test.ts +155 -0
  255. package/src/persistence/migrations/helpers/relocation.ts +44 -1
  256. package/src/persistence/migrations/run-migrations.ts +25 -1
  257. package/src/persistence/schema/infrastructure.ts +9 -0
  258. package/src/persistence/schema/memory-core.ts +3 -0
  259. package/src/persistence/schema/memory-injection.ts +2 -0
  260. package/src/persistence/steps.ts +36 -0
  261. package/src/platform/client.test.ts +1 -44
  262. package/src/platform/client.ts +10 -20
  263. package/src/platform/consent-cache.test.ts +89 -23
  264. package/src/platform/consent-cache.ts +71 -33
  265. package/src/plugin-api/constants.ts +12 -0
  266. package/src/plugin-api/conversation-turn.ts +37 -14
  267. package/src/plugin-api/index.ts +11 -1
  268. package/src/plugin-api/resolve-credential.ts +75 -0
  269. package/src/plugin-api/vision-support.test.ts +8 -19
  270. package/src/plugin-api/vision-support.ts +26 -17
  271. package/src/plugins/collect-source-versions.ts +77 -0
  272. package/src/plugins/defaults/compaction/compact.ts +6 -0
  273. package/src/plugins/defaults/compaction/window-manager.ts +9 -0
  274. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +18 -24
  275. package/src/plugins/defaults/empty-response/hooks/user-prompt-submit.ts +38 -0
  276. package/src/plugins/defaults/empty-response/refusal-quarantine.ts +99 -0
  277. package/src/plugins/defaults/image-fallback/__tests__/caption-cache-persistence.test.ts +7 -3
  278. package/src/plugins/defaults/index.ts +8 -1
  279. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +4 -1
  280. package/src/plugins/defaults/memory/__tests__/activation-session-store.test.ts +48 -6
  281. package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +28 -22
  282. package/src/plugins/defaults/memory/__tests__/memory-log-stores-degraded.test.ts +148 -0
  283. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +80 -8
  284. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +202 -0
  285. package/src/plugins/defaults/memory/__tests__/memory-v2-activation-log-store.test.ts +64 -25
  286. package/src/plugins/defaults/memory/__tests__/memory-v2-concept-frequency.test.ts +24 -11
  287. package/src/plugins/defaults/memory/__tests__/prompt-override.test.ts +70 -6
  288. package/src/plugins/defaults/memory/__tests__/table-relocation.test.ts +278 -0
  289. package/src/plugins/defaults/memory/activation-session-store.ts +27 -20
  290. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +2 -9
  291. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +1 -8
  292. package/src/plugins/defaults/memory/graph/retriever.test.ts +6 -6
  293. package/src/plugins/defaults/memory/graph/store.ts +114 -0
  294. package/src/plugins/defaults/memory/graph/tool-handlers.ts +36 -1
  295. package/src/plugins/defaults/memory/graph/tools.ts +47 -13
  296. package/src/plugins/defaults/memory/graph-topology/build-memory-graph.ts +16 -35
  297. package/src/plugins/defaults/memory/jobs-worker.ts +10 -1
  298. package/src/plugins/defaults/memory/memory-db.ts +3 -2
  299. package/src/plugins/defaults/memory/memory-recall-log-store.ts +129 -66
  300. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +8 -0
  301. package/src/plugins/defaults/memory/memory-retrospective-job.ts +11 -122
  302. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +216 -0
  303. package/src/plugins/defaults/memory/memory-v2-activation-log-store.ts +84 -46
  304. package/src/plugins/defaults/memory/memory-v2-concept-frequency.ts +42 -32
  305. package/src/plugins/defaults/memory/path-containment.ts +21 -0
  306. package/src/plugins/defaults/memory/prompt-override.ts +51 -13
  307. package/src/plugins/defaults/memory/tools.test.ts +34 -0
  308. package/src/plugins/defaults/memory/tools.ts +12 -2
  309. package/src/plugins/defaults/memory/v2/__tests__/harness-compare.test.ts +19 -15
  310. package/src/plugins/defaults/memory/v2/__tests__/harness-oracle.test.ts +24 -19
  311. package/src/plugins/defaults/memory/v2/__tests__/harness-replay-input.test.ts +19 -15
  312. package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +22 -2
  313. package/src/plugins/defaults/memory/v2/__tests__/prompts-consolidation.test.ts +5 -3
  314. package/src/plugins/defaults/memory/v2/harness/oracle.ts +59 -41
  315. package/src/plugins/defaults/memory/v2/harness/replay-input.ts +29 -25
  316. package/src/plugins/defaults/memory/v2/migration.ts +46 -16
  317. package/src/plugins/defaults/memory/v2/prompts/consolidation.ts +4 -0
  318. package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +17 -15
  319. package/src/plugins/defaults/memory/v3/__tests__/gate.test.ts +24 -22
  320. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +9 -5
  321. package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +277 -10
  322. package/src/plugins/defaults/memory/v3/__tests__/selection-log-store.test.ts +57 -5
  323. package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +9 -7
  324. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +38 -7
  325. package/src/plugins/defaults/memory/v3/hot-set.test.ts +36 -18
  326. package/src/plugins/defaults/memory/v3/hot-set.ts +12 -14
  327. package/src/plugins/defaults/memory/v3/learned-edges.test.ts +47 -27
  328. package/src/plugins/defaults/memory/v3/learned-edges.ts +12 -14
  329. package/src/plugins/defaults/memory/v3/orchestrate.ts +139 -22
  330. package/src/plugins/defaults/memory/v3/prune.test.ts +10 -3
  331. package/src/plugins/defaults/memory/v3/prune.ts +17 -11
  332. package/src/plugins/defaults/memory/v3/selection-log-store.ts +23 -11
  333. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +70 -53
  334. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +9 -6
  335. package/src/plugins/external-plugin-loader.ts +15 -15
  336. package/src/plugins/mtime-cache.ts +47 -0
  337. package/src/plugins/pipeline.ts +9 -1
  338. package/src/plugins/plugin-execution-context.ts +44 -0
  339. package/src/plugins/plugin-tree-walk.ts +31 -24
  340. package/src/plugins/source-fingerprint.ts +3 -4
  341. package/src/prompts/normalize-onboarding.ts +12 -0
  342. package/src/prompts/persona-resolver.ts +8 -0
  343. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +1 -1
  344. package/src/providers/__tests__/dispatch-connection-routing.test.ts +6 -9
  345. package/src/providers/__tests__/registry-native-web-search.test.ts +4 -10
  346. package/src/providers/__tests__/retry-callsite.test.ts +235 -215
  347. package/src/providers/__tests__/satellite-connection-routing.test.ts +8 -16
  348. package/src/providers/atlascloud/client.ts +10 -49
  349. package/src/providers/baseten/client.ts +43 -0
  350. package/src/providers/call-site-routing.ts +26 -13
  351. package/src/providers/connection-resolution.ts +9 -11
  352. package/src/providers/fetch-provider-catalog.ts +4 -2
  353. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +29 -4
  354. package/src/providers/inference/__tests__/connection-availability-keyless.test.ts +78 -0
  355. package/src/providers/inference/adapter-factory.ts +27 -6
  356. package/src/providers/inference/auth.ts +29 -1
  357. package/src/providers/inference/backfill.ts +61 -10
  358. package/src/providers/inference/connection-availability.ts +3 -2
  359. package/src/providers/inference/resolve-auth.ts +9 -1
  360. package/src/providers/model-catalog.ts +37 -0
  361. package/src/providers/openai/__tests__/api-error-normalization.test.ts +24 -2
  362. package/src/providers/openai/api-key-validation.ts +70 -0
  363. package/src/providers/retry.ts +2 -0
  364. package/src/providers/types.ts +11 -4
  365. package/src/providers/vellum-model-routing.test.ts +26 -0
  366. package/src/providers/vellum-model-routing.ts +30 -0
  367. package/src/providers/voice-error-copy.ts +47 -0
  368. package/src/runtime/agent-wake.ts +40 -24
  369. package/src/runtime/for-chat-mint-registry.ts +118 -0
  370. package/src/runtime/reveal-nonce.ts +49 -0
  371. package/src/runtime/reveal-success-registry.ts +306 -0
  372. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +88 -3
  373. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +161 -30
  374. package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +7 -0
  375. package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +15 -0
  376. package/src/runtime/routes/app-management-routes.ts +152 -45
  377. package/src/runtime/routes/app-routes.ts +16 -77
  378. package/src/runtime/routes/canned-message-complete.ts +16 -16
  379. package/src/runtime/routes/conversation-management-routes.ts +17 -14
  380. package/src/runtime/routes/conversation-query-routes.ts +54 -6
  381. package/src/runtime/routes/conversation-routes.ts +48 -2
  382. package/src/runtime/routes/credential-routes.ts +116 -3
  383. package/src/runtime/routes/email-routes.ts +17 -1
  384. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +33 -5
  385. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +6 -5
  386. package/src/runtime/routes/inference-provider-connection-routes.ts +77 -28
  387. package/src/runtime/routes/inference-send-routes.ts +1 -1
  388. package/src/runtime/routes/internal-telemetry-routes.ts +7 -6
  389. package/src/runtime/routes/plugins-routes.ts +37 -6
  390. package/src/runtime/routes/publish-routes.ts +15 -18
  391. package/src/runtime/routes/schedule-worker-routes.ts +9 -0
  392. package/src/runtime/routes/secret-routes.ts +10 -0
  393. package/src/runtime/routes/telemetry-routes.ts +149 -45
  394. package/src/schedule/__tests__/schedule-timezone.test.ts +101 -0
  395. package/src/schedule/__tests__/worker-watchdog.test.ts +209 -0
  396. package/src/schedule/schedule-store.ts +12 -1
  397. package/src/schedule/schedule-timezone.ts +63 -0
  398. package/src/schedule/scheduler.ts +100 -6
  399. package/src/schedule/worker-control.ts +19 -0
  400. package/src/security/auth-fallback-events-store.ts +7 -6
  401. package/src/security/secret-scanner.ts +26 -1
  402. package/src/services/published-app-updater.ts +6 -11
  403. package/src/stt/stt-stream-session.ts +36 -16
  404. package/src/subagent/manager.ts +34 -8
  405. package/src/telemetry/AGENTS.md +30 -1
  406. package/src/telemetry/__tests__/config-setting-snapshot.test.ts +18 -0
  407. package/src/telemetry/__tests__/outbox-test-harness.ts +5 -3
  408. package/src/telemetry/config-setting-snapshot.ts +44 -10
  409. package/src/telemetry/telemetry-event-sources.test.ts +124 -30
  410. package/src/telemetry/telemetry-event-sources.ts +137 -83
  411. package/src/telemetry/telemetry-events-outbox.test.ts +46 -1
  412. package/src/telemetry/telemetry-events-outbox.ts +60 -9
  413. package/src/telemetry/telemetry-wire-source.json +1 -1
  414. package/src/telemetry/telemetry-wire-validation.ts +39 -2
  415. package/src/telemetry/telemetry-wire.generated.ts +8 -0
  416. package/src/telemetry/tool-audit.ts +15 -9
  417. package/src/telemetry/tool-executed-events-store.test.ts +1 -1
  418. package/src/telemetry/turn-events-store.ts +20 -0
  419. package/src/telemetry/types.ts +45 -12
  420. package/src/telemetry/usage-telemetry-reporter.test.ts +294 -25
  421. package/src/telemetry/usage-telemetry-reporter.ts +117 -18
  422. package/src/telemetry/watchdog-direct-emit.test.ts +11 -3
  423. package/src/telemetry/watchdog-direct-emit.ts +11 -6
  424. package/src/tools/apps/executors.ts +11 -36
  425. package/src/tools/executor.ts +14 -1
  426. package/src/tools/host-terminal/host-shell.ts +6 -2
  427. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +1 -1
  428. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +25 -0
  429. package/src/tools/network/web-fetch.ts +6 -2
  430. package/src/tools/skills/sandbox-runner.ts +5 -2
  431. package/src/tools/subagent/spawn.ts +7 -11
  432. package/src/tools/terminal/shell.ts +5 -2
  433. package/src/tools/tool-manifest.ts +0 -2
  434. package/src/tools/tool-profiler.ts +37 -6
  435. package/src/tools/ui-surface/channel-variants.ts +101 -0
  436. package/src/tools/ui-surface/definitions.ts +23 -67
  437. package/src/tools/ui-surface/surface-shape-docs.ts +229 -0
  438. package/src/tts/__tests__/provider-adapters.test.ts +18 -0
  439. package/src/tts/provider-catalog.ts +2 -4
  440. package/src/tts/providers/deepgram-provider.ts +10 -1
  441. package/src/types/onboarding-context.ts +8 -0
  442. package/src/usage/attribution.ts +18 -112
  443. package/src/{config/bundled-skills/messaging/tools/gmail-mime-helpers.ts → util/mime-type.ts} +4 -1
  444. package/src/util/provider-error-patterns.ts +7 -1
  445. package/src/util/worker-process.ts +105 -0
  446. package/src/watcher/__tests__/telemetry.test.ts +17 -4
  447. package/src/watcher/telemetry.ts +5 -5
  448. package/src/workspace/migrations/131-drop-web-fetch-mode.ts +61 -0
  449. package/src/workspace/migrations/registry.ts +2 -0
  450. package/src/workspace/provider-commit-message-generator.ts +6 -4
  451. package/src/__tests__/app-open-proxy.test.ts +0 -67
  452. package/src/onboarding/onboarding-research-events-store.test.ts +0 -230
  453. package/src/onboarding/onboarding-research-events-store.ts +0 -104
  454. package/src/runtime/routes/assets/vellum-design-system.css +0 -2236
  455. package/src/tools/apps/definitions.ts +0 -73
  456. package/src/tools/apps/open-proxy.ts +0 -43
@@ -60,6 +60,24 @@ describe("config-setting-snapshot", () => {
60
60
  expect(recordedPairs()).toEqual([["memory.v2.enabled", "false"]]);
61
61
  });
62
62
 
63
+ test("unknown consent skips entirely — no rows, no memo", () => {
64
+ setShareAnalytics("unknown");
65
+ recordConfigSettingSnapshot(makeConfig(true, false));
66
+
67
+ // A snapshot is re-derivable state (unlike outbox events), so nothing is
68
+ // recorded while consent is unresolved...
69
+ expect(recordedPairs()).toHaveLength(0);
70
+
71
+ // ...and nothing was memoized: once consent resolves, the next snapshot
72
+ // records the complete current set.
73
+ setShareAnalytics(true);
74
+ recordConfigSettingSnapshot(makeConfig(true, false));
75
+ expect(recordedPairs().sort()).toEqual([
76
+ ["memory.enabled", "true"],
77
+ ["memory.v2.enabled", "false"],
78
+ ]);
79
+ });
80
+
63
81
  test("consent off drops the snapshot without poisoning the memo", () => {
64
82
  setShareAnalytics(false);
65
83
  recordConfigSettingSnapshot(makeConfig(true, true));
@@ -1,6 +1,8 @@
1
1
  import { mock, spyOn } from "bun:test";
2
2
 
3
- let shareAnalytics = true;
3
+ import type { ConsentState } from "../../platform/consent-cache.js";
4
+
5
+ let shareAnalytics: ConsentState = true;
4
6
  // Owner's `share_diagnostics` consent + accepted version — a second,
5
7
  // independent gate some outbox event types layer on top of
6
8
  // `share_analytics` (their payload carries content richer than metadata).
@@ -17,7 +19,7 @@ let shareDiagnosticsVersion = "2999-01-01";
17
19
  // imports are as safe as in the test files themselves (AGENTS.md "Test
18
20
  // machinery isolation" scopes its no-src/ rule to preload-time machinery).
19
21
  mock.module("../../platform/consent-cache.js", () => ({
20
- getCachedShareAnalytics: () => shareAnalytics,
22
+ getRawShareAnalytics: () => shareAnalytics,
21
23
  getCachedShareDiagnostics: () => shareDiagnostics,
22
24
  getCachedShareDiagnosticsVersion: () => shareDiagnosticsVersion,
23
25
  }));
@@ -31,7 +33,7 @@ import { queryTelemetryOutboxBatch } from "../telemetry-events-outbox.js";
31
33
  await initializeDb();
32
34
 
33
35
  /** Flip the mocked share_analytics consent; call with true in beforeEach. */
34
- export function setShareAnalytics(value: boolean): void {
36
+ export function setShareAnalytics(value: ConsentState): void {
35
37
  shareAnalytics = value;
36
38
  }
37
39
 
@@ -1,6 +1,6 @@
1
1
  import { getConfigReadOnly } from "../config/loader.js";
2
2
  import type { AssistantConfig } from "../config/schema.js";
3
- import { getCachedShareAnalytics } from "../platform/consent-cache.js";
3
+ import { getRawShareAnalytics } from "../platform/consent-cache.js";
4
4
  import { getLogger } from "../util/logger.js";
5
5
  import { recordConfigSettingEvent } from "./config-setting-events-store.js";
6
6
 
@@ -51,13 +51,18 @@ const lastRecorded = new Map<string, string>();
51
51
  * Record a `config_setting` event for every tracked setting whose effective
52
52
  * value differs from what this process last recorded.
53
53
  *
54
- * Consent-gated with a memo reset on opt-out. While `share_analytics` is off,
55
- * nothing is recorded and the memo is cleared: the reporter's opt-out flush
56
- * discards any config_setting rows still pending upload, so a memo entry from
57
- * before the opt-out no longer corresponds to a delivered row. Clearing it
58
- * means a later re-opt-in (in the same process, unchanged config) re-records
59
- * the full current snapshot rather than skipping memoized keys — consumers
60
- * that expect a complete snapshot after re-opt-in stay correct.
54
+ * Consent-gated with a memo reset on opt-out. While `share_analytics` is a
55
+ * confirmed opt-out, nothing is recorded and the memo is cleared: the
56
+ * reporter's opt-out flush discards any config_setting rows still pending
57
+ * upload, so a memo entry from before the opt-out no longer corresponds to a
58
+ * delivered row. Clearing it means a later re-opt-in (in the same process,
59
+ * unchanged config) re-records the full current snapshot rather than skipping
60
+ * memoized keys — consumers that expect a complete snapshot after re-opt-in
61
+ * stay correct. An UNKNOWN consent state skips entirely (no record, no memo,
62
+ * no clear): unlike outbox events, a snapshot is re-derivable state, so
63
+ * deferring it loses nothing — while recording it would either memoize a row
64
+ * a later opt-out purge invalidates, or (without the memo) double-record on
65
+ * every boot.
61
66
  *
62
67
  * Retry-friendly when opted in: `recordConfigSettingEvent` returns false when
63
68
  * the telemetry DB is momentarily unavailable, and the memo is only advanced
@@ -67,7 +72,11 @@ const lastRecorded = new Map<string, string>();
67
72
  * retries on the next invocation.
68
73
  */
69
74
  export function recordConfigSettingSnapshot(config: AssistantConfig): void {
70
- if (!getCachedShareAnalytics()) {
75
+ const consent = getRawShareAnalytics();
76
+ if (consent === "unknown") {
77
+ return;
78
+ }
79
+ if (consent === false) {
71
80
  lastRecorded.clear();
72
81
  return;
73
82
  }
@@ -89,6 +98,14 @@ export function recordConfigSettingSnapshot(config: AssistantConfig): void {
89
98
  }
90
99
 
91
100
  let snapshotTimer: ReturnType<typeof setInterval> | null = null;
101
+ let bootRetryTimer: ReturnType<typeof setTimeout> | null = null;
102
+
103
+ // The boot-time snapshot races the first consent refresh (fire-and-forget in
104
+ // the monitor's startup), so consent is usually still "unknown" on the first
105
+ // call and the snapshot is skipped. Retry on a short timer until consent
106
+ // resolves rather than waiting a full hourly interval for the per-boot
107
+ // assertion.
108
+ const BOOT_RETRY_INTERVAL_MS = 60_000;
92
109
 
93
110
  function recordSnapshotFromConfig(): void {
94
111
  try {
@@ -100,6 +117,19 @@ function recordSnapshotFromConfig(): void {
100
117
  }
101
118
  }
102
119
 
120
+ function recordBootSnapshotOnceConsentKnown(): void {
121
+ if (getRawShareAnalytics() === "unknown") {
122
+ bootRetryTimer = setTimeout(
123
+ recordBootSnapshotOnceConsentKnown,
124
+ BOOT_RETRY_INTERVAL_MS,
125
+ );
126
+ bootRetryTimer.unref?.();
127
+ return;
128
+ }
129
+ bootRetryTimer = null;
130
+ recordSnapshotFromConfig();
131
+ }
132
+
103
133
  /**
104
134
  * Start the config-setting snapshot loop in the resource monitor process:
105
135
  * record the tracked settings once at boot, then hourly. No-op in dev mode
@@ -114,7 +144,7 @@ export function startConfigSnapshotReporter(): void {
114
144
  if (snapshotTimer) {
115
145
  return;
116
146
  }
117
- recordSnapshotFromConfig();
147
+ recordBootSnapshotOnceConsentKnown();
118
148
  snapshotTimer = setInterval(
119
149
  recordSnapshotFromConfig,
120
150
  CONFIG_SNAPSHOT_INTERVAL_MS,
@@ -127,6 +157,10 @@ export function stopConfigSnapshotReporter(): void {
127
157
  clearInterval(snapshotTimer);
128
158
  snapshotTimer = null;
129
159
  }
160
+ if (bootRetryTimer) {
161
+ clearTimeout(bootRetryTimer);
162
+ bootRetryTimer = null;
163
+ }
130
164
  }
131
165
 
132
166
  /** Test-only: clear the per-process last-recorded memo. */
@@ -6,8 +6,8 @@
6
6
  */
7
7
  import { beforeEach, describe, expect, test } from "bun:test";
8
8
 
9
- import { recordOnboardingResearchEvent } from "../onboarding/onboarding-research-events-store.js";
10
9
  import {
10
+ pendingOutboxRows,
11
11
  resetOutboxTable,
12
12
  setShareAnalytics,
13
13
  setShareDiagnostics,
@@ -16,25 +16,72 @@ import {
16
16
  ALL_TELEMETRY_EVENT_SOURCES,
17
17
  DAEMON_TELEMETRY_EVENT_SOURCES,
18
18
  MONITOR_TELEMETRY_EVENT_SOURCES,
19
+ ORPHAN_OUTBOX_DRAIN_SOURCE_ID,
20
+ orphanOutboxDrainSource,
19
21
  } from "./telemetry-event-sources.js";
20
- import { OUTBOX_TELEMETRY_EVENT_NAMES } from "./types.js";
22
+ import {
23
+ insertTelemetryOutboxEvents,
24
+ recordTelemetryEvent,
25
+ } from "./telemetry-events-outbox.js";
26
+ import { telemetryEventSchema } from "./telemetry-wire.generated.js";
27
+ import type { TelemetryEvent } from "./types.js";
28
+ import {
29
+ OUTBOX_TELEMETRY_EVENT_NAMES,
30
+ WATERMARK_TELEMETRY_EVENT_NAMES,
31
+ } from "./types.js";
21
32
 
22
33
  describe("telemetry event source partition", () => {
23
34
  test("the full source list carries every event type in payload order", () => {
35
+ // Watermark sources first (their ids are the table names, not the wire
36
+ // discriminants), then outbox events in wire-contract order, then the
37
+ // synthetic orphan-drain source last.
24
38
  expect(ALL_TELEMETRY_EVENT_SOURCES.map((s) => s.id)).toEqual([
25
39
  "usage",
26
40
  "turns",
41
+ "tool_executed",
27
42
  "lifecycle",
28
43
  "onboarding",
29
44
  "auth_fallback",
30
- "tool_executed",
31
45
  "skill_loaded",
32
46
  "watchdog",
33
47
  "config_setting",
34
48
  "onboarding_research",
49
+ ORPHAN_OUTBOX_DRAIN_SOURCE_ID,
35
50
  ]);
36
51
  });
37
52
 
53
+ test("every wire event type is partitioned to exactly one flush lane", () => {
54
+ // The derivation's core guarantee: a new wire event type can't silently go
55
+ // unflushed. Every discriminant in the generated contract is either
56
+ // watermark-flushed or (by default) outbox-backed, never both, never
57
+ // neither — and, ignoring the synthetic orphan-drain source, the counts
58
+ // line up with one source per type.
59
+ const wireTypes = new Set(
60
+ telemetryEventSchema.options.map((o) => o.shape.type.value),
61
+ );
62
+ const partitioned = [
63
+ ...WATERMARK_TELEMETRY_EVENT_NAMES,
64
+ ...OUTBOX_TELEMETRY_EVENT_NAMES,
65
+ ];
66
+ expect(new Set(partitioned)).toEqual(wireTypes);
67
+ expect(partitioned.length).toBe(wireTypes.size); // disjoint: no double-count
68
+ const perTypeSources = ALL_TELEMETRY_EVENT_SOURCES.filter(
69
+ (s) => s.id !== ORPHAN_OUTBOX_DRAIN_SOURCE_ID,
70
+ );
71
+ expect(perTypeSources.length).toBe(wireTypes.size);
72
+ });
73
+
74
+ test("every watermark name is a real wire discriminant", () => {
75
+ // Guards a stale/typo watermark name that would wrongly exclude a live type
76
+ // from the outbox (and thus from any flush source).
77
+ const wireTypes = new Set(
78
+ telemetryEventSchema.options.map((o) => o.shape.type.value),
79
+ );
80
+ for (const name of WATERMARK_TELEMETRY_EVENT_NAMES) {
81
+ expect(wireTypes.has(name)).toBe(true);
82
+ }
83
+ });
84
+
38
85
  test("the daemon flushes turns only", () => {
39
86
  expect(DAEMON_TELEMETRY_EVENT_SOURCES.map((s) => s.id)).toEqual(["turns"]);
40
87
  });
@@ -68,7 +115,7 @@ describe("telemetry event source partition", () => {
68
115
  });
69
116
  });
70
117
 
71
- describe("onboarding_research source: flush-time diagnostics gate", () => {
118
+ describe("onboarding_research source: flushes via the default outbox source", () => {
72
119
  beforeEach(() => {
73
120
  setShareAnalytics(true);
74
121
  setShareDiagnostics(true);
@@ -86,43 +133,90 @@ describe("onboarding_research source: flush-time diagnostics gate", () => {
86
133
  }
87
134
 
88
135
  function recordSample(): void {
89
- recordOnboardingResearchEvent({
90
- conversationId: "conv-x",
91
- status: "done",
92
- claims: [],
93
- suggestions: [],
94
- plugins: [],
95
- installedPlugins: [],
96
- });
136
+ recordTelemetryEvent(
137
+ "onboarding_research",
138
+ {
139
+ conversation_id: "conv-x",
140
+ status: "done",
141
+ claims: [],
142
+ claim_count: 0,
143
+ claims_confident: 0,
144
+ claims_maybe: 0,
145
+ claims_guessing: 0,
146
+ suggestions: [],
147
+ suggestion_count: 0,
148
+ plugins: [],
149
+ installed_plugins: [],
150
+ },
151
+ { conversationId: "conv-x" },
152
+ );
97
153
  }
98
154
 
99
- test("ships normally when diagnostics consent is eligible", () => {
155
+ test("ships pending rows regardless of diagnostics consent, like every other outbox event", () => {
156
+ // No flush-time diagnostics gate: the daemon rides `share_analytics`
157
+ // only and leaves the diagnostics decision to the platform's
158
+ // authoritative server-side ingest gate. Diagnostics off (and even a
159
+ // stale accepted version) must not hold the row back.
160
+ setShareDiagnostics(false, "2000-01-01");
100
161
  recordSample();
162
+
101
163
  const batch = onboardingResearchSource().collect(0, undefined, 100);
102
164
  expect(batch.events).toHaveLength(1);
103
165
  });
166
+ });
104
167
 
105
- test("purges pending rows outright once diagnostics consent is revoked before flush, rather than shipping them", () => {
106
- recordSample();
107
- // Consent was on at record time — the row is already pending — then
108
- // revoked before the reporter gets to flush it.
109
- setShareDiagnostics(false);
168
+ describe("orphan-drain source", () => {
169
+ beforeEach(() => {
170
+ resetOutboxTable();
171
+ setShareAnalytics(true);
172
+ });
110
173
 
111
- const batch = onboardingResearchSource().collect(0, undefined, 100);
112
- expect(batch.events).toHaveLength(0);
174
+ function insertOutboxRow(name: string, id: string): void {
175
+ insertTelemetryOutboxEvents([
176
+ {
177
+ id,
178
+ name,
179
+ createdAt: 1,
180
+ event: {
181
+ type: name,
182
+ daemon_event_id: id,
183
+ recorded_at: 1,
184
+ assistant_version: "1.2.3",
185
+ } as unknown as TelemetryEvent,
186
+ },
187
+ ]);
188
+ }
113
189
 
114
- // Purged, not merely skipped: a later collect (e.g. consent flips back
115
- // on) must never resurrect the stale pre-revocation row.
116
- setShareDiagnostics(true);
117
- const secondBatch = onboardingResearchSource().collect(0, undefined, 100);
118
- expect(secondBatch.events).toHaveLength(0);
119
- });
190
+ test("drains rows for a type no longer in the wire contract, then self-heals on ack", () => {
191
+ // A row whose name isn't a current outbox event type — e.g. recorded before
192
+ // the platform removed the type. No per-type source queries it.
193
+ const orphanName = "removed_legacy_event";
194
+ insertOutboxRow(orphanName, "orphan-row-1");
120
195
 
121
- test("purges pending rows when the accepted diagnostics-consent version becomes stale before flush", () => {
122
- recordSample();
123
- setShareDiagnostics(true, "2000-01-01");
196
+ const source = orphanOutboxDrainSource();
197
+ const batch = source.collect(0, undefined, 500);
198
+ expect(batch.events).toHaveLength(1);
199
+ expect((batch.events[0] as { type: string }).type).toBe(orphanName);
200
+ expect(batch.rowIds).toEqual(["orphan-row-1"]);
124
201
 
125
- const batch = onboardingResearchSource().collect(0, undefined, 100);
202
+ // Send-then-ack: the reporter deletes the row after a 2xx, so it self-heals.
203
+ source.ack?.acknowledge(batch.rowIds ?? []);
204
+ expect(pendingOutboxRows(orphanName)).toHaveLength(0);
205
+ });
206
+
207
+ test("ignores rows for a current outbox event type", () => {
208
+ // Known types drain through their own per-type source, not this one.
209
+ const knownName = OUTBOX_TELEMETRY_EVENT_NAMES[0];
210
+ insertOutboxRow(knownName, "known-row-1");
211
+ const batch = orphanOutboxDrainSource().collect(0, undefined, 500);
126
212
  expect(batch.events).toHaveLength(0);
127
213
  });
214
+
215
+ test("is registered in the monitor partition", () => {
216
+ expect(
217
+ MONITOR_TELEMETRY_EVENT_SOURCES.some(
218
+ (s) => s.id === ORPHAN_OUTBOX_DRAIN_SOURCE_ID,
219
+ ),
220
+ ).toBe(true);
221
+ });
128
222
  });
@@ -20,6 +20,7 @@ import { APP_VERSION } from "../version.js";
20
20
  import {
21
21
  deleteTelemetryOutboxEvents,
22
22
  discardPendingTelemetryOutboxEvents,
23
+ queryDistinctOutboxEventNames,
23
24
  queryTelemetryOutboxBatch,
24
25
  } from "./telemetry-events-outbox.js";
25
26
  import { queryUnreportedToolExecutedEvents } from "./tool-executed-events-store.js";
@@ -31,6 +32,7 @@ import type {
31
32
  TelemetryEvent,
32
33
  TurnTelemetryClientInfo,
33
34
  } from "./types.js";
35
+ import { OUTBOX_TELEMETRY_EVENT_NAMES } from "./types.js";
34
36
 
35
37
  const log = getLogger("usage-telemetry");
36
38
 
@@ -130,13 +132,48 @@ function simpleSource<Row extends { id: string; createdAt: number }>(
130
132
  };
131
133
  }
132
134
 
135
+ /**
136
+ * Query one outbox event name and parse each stored payload into its wire
137
+ * event, accumulating into `events`/`rowIds`. A row whose payload does not
138
+ * parse to an object is collected into `corruptIds` for immediate purge: an
139
+ * early empty-batch return in the reporter would otherwise strand it at the
140
+ * head of the queue forever. Shared by {@link outboxSource} and the
141
+ * orphan-drain source.
142
+ */
143
+ function collectOutboxName(
144
+ name: string,
145
+ limit: number,
146
+ into: { events: TelemetryEvent[]; rowIds: string[]; corruptIds: string[] },
147
+ ): { queried: number } {
148
+ const rows = queryTelemetryOutboxBatch(name, limit);
149
+ for (const row of rows) {
150
+ let event: TelemetryEvent | null = null;
151
+ try {
152
+ const parsed = JSON.parse(row.payload) as unknown;
153
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
154
+ event = parsed as TelemetryEvent;
155
+ }
156
+ } catch {
157
+ // Fall through to the purge below.
158
+ }
159
+ if (event) {
160
+ into.events.push(event);
161
+ into.rowIds.push(row.id);
162
+ } else {
163
+ into.corruptIds.push(row.id);
164
+ log.warn(
165
+ { name, rowId: row.id, payloadLength: row.payload.length },
166
+ "Telemetry outbox: unparseable payload — purging row",
167
+ );
168
+ }
169
+ }
170
+ return { queried: rows.length };
171
+ }
172
+
133
173
  /**
134
174
  * Build an ack-mode source over the generic `telemetry_events` outbox for one
135
175
  * event name. `collect` ignores the cursor args — acknowledged rows are
136
- * deleted, so the head of the queue is always the next batch — and parses
137
- * each stored payload into its wire event. A row whose payload does not parse
138
- * to an object is purged immediately with a warn: an early empty-batch return
139
- * in the reporter would otherwise strand it at the head of the queue forever.
176
+ * deleted, so the head of the queue is always the next batch.
140
177
  */
141
178
  export function outboxSource(
142
179
  name: OutboxTelemetryEventName,
@@ -144,39 +181,20 @@ export function outboxSource(
144
181
  return {
145
182
  id: name,
146
183
  collect(_afterCreatedAt, _afterId, limit) {
147
- const rows = queryTelemetryOutboxBatch(name, limit);
148
- const events: TelemetryEvent[] = [];
149
- const rowIds: string[] = [];
150
- const corruptIds: string[] = [];
151
- for (const row of rows) {
152
- let event: TelemetryEvent | null = null;
153
- try {
154
- const parsed = JSON.parse(row.payload) as unknown;
155
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
156
- event = parsed as TelemetryEvent;
157
- }
158
- } catch {
159
- // Fall through to the purge below.
160
- }
161
- if (event) {
162
- events.push(event);
163
- rowIds.push(row.id);
164
- } else {
165
- corruptIds.push(row.id);
166
- log.warn(
167
- { name, rowId: row.id, payloadLength: row.payload.length },
168
- "Telemetry outbox: unparseable payload — purging row",
169
- );
170
- }
171
- }
172
- if (corruptIds.length > 0) {
173
- deleteTelemetryOutboxEvents(corruptIds);
184
+ const acc = {
185
+ events: [] as TelemetryEvent[],
186
+ rowIds: [] as string[],
187
+ corruptIds: [] as string[],
188
+ };
189
+ const { queried } = collectOutboxName(name, limit, acc);
190
+ if (acc.corruptIds.length > 0) {
191
+ deleteTelemetryOutboxEvents(acc.corruptIds);
174
192
  }
175
193
  return {
176
- events,
177
- rowIds,
194
+ events: acc.events,
195
+ rowIds: acc.rowIds,
178
196
  lastCursor: null,
179
- fullBatch: rows.length === limit,
197
+ fullBatch: queried === limit,
180
198
  };
181
199
  },
182
200
  ack: {
@@ -186,44 +204,60 @@ export function outboxSource(
186
204
  };
187
205
  }
188
206
 
207
+ /** Source id of the synthetic orphan-drain source (not a wire event type). */
208
+ export const ORPHAN_OUTBOX_DRAIN_SOURCE_ID = "__orphan_outbox_drain";
209
+
189
210
  /**
190
- * Build an ack-mode source like {@link outboxSource}, but additionally
191
- * re-checks `share_diagnostics` eligibility (at an eligible accepted
192
- * version) on every collect, not just at record time. A writer's record-
193
- * time gate only covers the moment the row is recorded — if the owner
194
- * revokes diagnostics consent after that but before the next flush, a
195
- * still-pending row must not ship anyway (unlike `turnSource`'s trace,
196
- * which is assembled fresh at flush time and so re-evaluates consent for
197
- * free, a pre-built outbox payload has no such natural re-check point).
198
- * Ineligible pending rows are purged outright rather than held for a later
199
- * re-check, mirroring the corrupt-payload purge aboveconsent revocation
200
- * calls for dropping the backlog, not retrying it.
211
+ * Synthetic ack-mode source that drains outbox rows whose `name` is no longer a
212
+ * current outbox event type e.g. rows recorded before the platform removed or
213
+ * renamed a type, still pending on a client that has since upgraded. The
214
+ * derived per-type sources ({@link OUTBOX_TELEMETRY_EVENT_NAMES}) never query
215
+ * those names, so without this the rows would sit in the outbox forever.
216
+ *
217
+ * Draining is send-then-ack: the platform skips an unknown event type but still
218
+ * returns 2xx, after which the row is deleted. This is safe against a stale
219
+ * local wire copy a name merely missing from a lagging wire still ships and
220
+ * lands rather than being purged and lost and rides `share_analytics` like
221
+ * every other outbox event (no outbox type is diagnostics-gated at flush).
201
222
  */
202
- function diagnosticsGatedOutboxSource(
203
- name: OutboxTelemetryEventName,
204
- ): TelemetryEventSource {
205
- const base = outboxSource(name);
223
+ export function orphanOutboxDrainSource(): TelemetryEventSource {
224
+ const known = new Set<string>(OUTBOX_TELEMETRY_EVENT_NAMES);
225
+ const orphanNames = (): string[] =>
226
+ queryDistinctOutboxEventNames().filter((name) => !known.has(name));
206
227
  return {
207
- id: base.id,
208
- collect(afterCreatedAt, afterId, limit) {
209
- if (
210
- !getCachedShareDiagnostics() ||
211
- !isDiagnosticsConsentVersionEligible(getCachedShareDiagnosticsVersion())
212
- ) {
213
- const rows = queryTelemetryOutboxBatch(name, limit);
214
- if (rows.length > 0) {
215
- deleteTelemetryOutboxEvents(rows.map((row) => row.id));
228
+ id: ORPHAN_OUTBOX_DRAIN_SOURCE_ID,
229
+ collect(_afterCreatedAt, _afterId, limit) {
230
+ const acc = {
231
+ events: [] as TelemetryEvent[],
232
+ rowIds: [] as string[],
233
+ corruptIds: [] as string[],
234
+ };
235
+ for (const name of orphanNames()) {
236
+ if (acc.events.length >= limit) {
237
+ break;
216
238
  }
217
- return {
218
- events: [],
219
- rowIds: [],
220
- lastCursor: null,
221
- fullBatch: rows.length === limit,
222
- };
239
+ collectOutboxName(name, limit - acc.events.length, acc);
240
+ }
241
+ if (acc.corruptIds.length > 0) {
242
+ deleteTelemetryOutboxEvents(acc.corruptIds);
223
243
  }
224
- return base.collect(afterCreatedAt, afterId, limit);
244
+ return {
245
+ events: acc.events,
246
+ rowIds: acc.rowIds,
247
+ lastCursor: null,
248
+ // Conservative: never signals more-behind, so orphan draining spreads
249
+ // across flush cycles instead of monopolizing one. Rare by nature.
250
+ fullBatch: false,
251
+ };
252
+ },
253
+ ack: {
254
+ acknowledge: (rowIds) => deleteTelemetryOutboxEvents(rowIds),
255
+ discardPending: () => {
256
+ for (const name of orphanNames()) {
257
+ discardPendingTelemetryOutboxEvents(name);
258
+ }
259
+ },
225
260
  },
226
- ack: base.ack,
227
261
  };
228
262
  }
229
263
 
@@ -480,27 +514,47 @@ const toolExecutedSource = simpleSource(
480
514
  export const TOOL_EXECUTED_SOURCE_ID = toolExecutedSource.id;
481
515
 
482
516
  /**
483
- * Every telemetry event source, in payload order. The order is part of the
484
- * observable wire behavior (events of different types appear in this order
485
- * within one batch), so keep it stable. This is the test/reference list;
486
- * production reporters run the daemon/monitor partitions below.
517
+ * Watermark-flushed sources the high-volume events on their own SQLite
518
+ * tables (`WATERMARK_TELEMETRY_EVENT_NAMES`), read forward by a `(createdAt,
519
+ * id)` cursor rather than the generic outbox.
487
520
  */
488
- export const ALL_TELEMETRY_EVENT_SOURCES: readonly TelemetryEventSource[] = [
521
+ const WATERMARK_TELEMETRY_EVENT_SOURCES: readonly TelemetryEventSource[] = [
489
522
  usageSource,
490
523
  turnSource,
491
- outboxSource("lifecycle"),
492
- outboxSource("onboarding"),
493
- outboxSource("auth_fallback"),
494
524
  toolExecutedSource,
495
- outboxSource("skill_loaded"),
496
- outboxSource("watchdog"),
497
- outboxSource("config_setting"),
498
- // Onboarding research-turn results are client-orchestrated (the web
499
- // client reports the settled `{claims, suggestions, plugins}` payload
500
- // once via POST /v1/telemetry/onboarding-research) but land in the same
501
- // outbox as every other event type. Diagnostics-gated at flush time (not
502
- // just record time) since the payload carries raw inferred claims.
503
- diagnosticsGatedOutboxSource("onboarding_research"),
525
+ ];
526
+
527
+ /**
528
+ * Per-outbox-event flush-source override. The default for every outbox event is
529
+ * the plain {@link outboxSource}; list only the exceptions here. A new outbox
530
+ * event type therefore needs no edit it flushes through `outboxSource`
531
+ * automatically. There are currently no exceptions: every outbox event,
532
+ * including `onboarding_research`, flushes through the default source and is
533
+ * gated only by the `share_analytics` consent enforced at record time.
534
+ */
535
+ const OUTBOX_SOURCE_FACTORY: Partial<
536
+ Record<
537
+ OutboxTelemetryEventName,
538
+ (name: OutboxTelemetryEventName) => TelemetryEventSource
539
+ >
540
+ > = {};
541
+
542
+ /**
543
+ * Every telemetry event source, in payload order (watermark sources first, then
544
+ * outbox events in wire-contract order). Derived from the wire contract via
545
+ * {@link OUTBOX_TELEMETRY_EVENT_NAMES}, so a new event type gets a flush source
546
+ * with no edit here. Intra-batch ordering is not load-bearing downstream
547
+ * (events are typed and deduped on `daemon_event_id`, not positional). This is
548
+ * the test/reference list; production reporters run the daemon/monitor
549
+ * partitions below.
550
+ */
551
+ export const ALL_TELEMETRY_EVENT_SOURCES: readonly TelemetryEventSource[] = [
552
+ ...WATERMARK_TELEMETRY_EVENT_SOURCES,
553
+ ...OUTBOX_TELEMETRY_EVENT_NAMES.map((name) =>
554
+ (OUTBOX_SOURCE_FACTORY[name] ?? outboxSource)(name),
555
+ ),
556
+ // Not a wire event type — drains rows for types the wire no longer declares.
557
+ orphanOutboxDrainSource(),
504
558
  ];
505
559
 
506
560
  /**