@vellumai/assistant 0.10.8 → 0.10.9-dev.202607152225.ec6fdbf

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 (1453) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +52 -88
  3. package/README.md +14 -35
  4. package/docs/activation-funnel-telemetry.md +20 -18
  5. package/docs/architecture/scheduling.md +0 -86
  6. package/docs/runbook-trusted-contacts.md +19 -19
  7. package/docs/trusted-contact-access.md +70 -69
  8. package/docs/workspace-tools.md +4 -2
  9. package/eslint-rules/__tests__/cli-no-daemon-internals.test.ts +62 -360
  10. package/eslint-rules/cli-no-daemon-internals.js +91 -275
  11. package/eslint.config.mjs +4 -4
  12. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  13. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  14. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  15. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  16. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  17. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  18. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-request-contract.test.ts +575 -0
  19. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +20 -0
  20. package/node_modules/@vellumai/gateway-client/src/guardian-request-contract.ts +687 -0
  21. package/node_modules/@vellumai/gateway-client/src/index.ts +82 -0
  22. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  23. package/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  24. package/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  25. package/openapi.yaml +353 -742
  26. package/package.json +2 -2
  27. package/scripts/build-test-fixtures.ts +3 -2
  28. package/scripts/test.ts +2 -7
  29. package/src/__tests__/activation-early-marking.test.ts +1 -8
  30. package/src/__tests__/actor-token-service.test.ts +0 -7
  31. package/src/__tests__/add-message-preassigned-id.test.ts +1 -1
  32. package/src/__tests__/add-message-skip-indexing.test.ts +164 -0
  33. package/src/__tests__/agent-image-optimize.test.ts +104 -1
  34. package/src/__tests__/agent-loop-callsite-precedence.test.ts +5 -15
  35. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +0 -6
  36. package/src/__tests__/agent-loop-override-profile.test.ts +48 -50
  37. package/src/__tests__/agent-loop-pushes-post-hook-prompt.test.ts +4 -17
  38. package/src/__tests__/agent-loop-set-system-prompt.test.ts +3 -14
  39. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -4
  40. package/src/__tests__/agent-wake-override-profile.test.ts +40 -71
  41. package/src/__tests__/always-loaded-tools-guard.test.ts +4 -7
  42. package/src/__tests__/annotate-activity-metadata.test.ts +4 -31
  43. package/src/__tests__/annotate-risk-options.test.ts +4 -31
  44. package/src/__tests__/anthropic-provider.test.ts +11 -0
  45. package/src/__tests__/app-asset-bytes.test.ts +72 -0
  46. package/src/__tests__/app-builder-tool-scripts.test.ts +0 -1
  47. package/src/__tests__/app-bundler.test.ts +4 -20
  48. package/src/__tests__/app-conversation-ids-backfill.test.ts +1 -8
  49. package/src/__tests__/app-executors.test.ts +2 -49
  50. package/src/__tests__/app-routes-csp.test.ts +126 -157
  51. package/src/__tests__/approval-cascade.test.ts +0 -82
  52. package/src/__tests__/approval-interception-trust-gates.test.ts +0 -7
  53. package/src/__tests__/approval-primitive.test.ts +1 -9
  54. package/src/__tests__/approval-routes-http.test.ts +5 -63
  55. package/src/__tests__/assistant-event-hub-machine-name.test.ts +1 -19
  56. package/src/__tests__/assistant-events-sse-hardening.test.ts +4 -21
  57. package/src/__tests__/attachment-stored-path-annotation.test.ts +1 -19
  58. package/src/__tests__/attachment-upload-trusted-source.test.ts +0 -13
  59. package/src/__tests__/attachments-store-heic-normalize.test.ts +0 -13
  60. package/src/__tests__/attachments-store.test.ts +5 -19
  61. package/src/__tests__/audit-log-rotation.test.ts +1 -9
  62. package/src/__tests__/auth-fallback-events-store.test.ts +43 -59
  63. package/src/__tests__/available-skills.test.ts +7 -15
  64. package/src/__tests__/avatar-e2e.test.ts +0 -19
  65. package/src/__tests__/avatar-generator.test.ts +0 -9
  66. package/src/__tests__/background-shell-bash.test.ts +0 -20
  67. package/src/__tests__/background-shell-host-bash.test.ts +0 -34
  68. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -43
  69. package/src/__tests__/browser-fill-credential.test.ts +0 -7
  70. package/src/__tests__/browser-manager.test.ts +1 -8
  71. package/src/__tests__/browser-skill-endstate.test.ts +1 -5
  72. package/src/__tests__/btw-routes.test.ts +7 -30
  73. package/src/__tests__/build-persisted-content.test.ts +96 -29
  74. package/src/__tests__/bundle-scanner.test.ts +27 -1
  75. package/src/__tests__/call-controller.test.ts +96 -149
  76. package/src/__tests__/call-conversation-messages.test.ts +2 -9
  77. package/src/__tests__/call-domain.test.ts +21 -56
  78. package/src/__tests__/call-pointer-message-composer.test.ts +0 -8
  79. package/src/__tests__/call-pointer-messages.test.ts +1 -8
  80. package/src/__tests__/call-recovery.test.ts +4 -11
  81. package/src/__tests__/call-routes-http.test.ts +12 -47
  82. package/src/__tests__/call-site-routing-provider.test.ts +5 -16
  83. package/src/__tests__/call-start-guardian-guard.test.ts +0 -10
  84. package/src/__tests__/call-state.test.ts +1 -8
  85. package/src/__tests__/call-store.test.ts +4 -11
  86. package/src/__tests__/cancel-resolves-conversation-key.test.ts +0 -32
  87. package/src/__tests__/catalog-cache.test.ts +0 -8
  88. package/src/__tests__/catalog-files.test.ts +0 -8
  89. package/src/__tests__/catalog-install-normalize.test.ts +0 -7
  90. package/src/__tests__/ces-rpc-credential-backend.test.ts +0 -7
  91. package/src/__tests__/channel-approval-routes.test.ts +23 -34
  92. package/src/__tests__/channel-approvals.test.ts +0 -7
  93. package/src/__tests__/channel-delivery-store.test.ts +6 -17
  94. package/src/__tests__/channel-guardian.test.ts +0 -7
  95. package/src/__tests__/channel-inbound-disk-pressure.test.ts +15 -20
  96. package/src/__tests__/channel-readiness-routes.test.ts +41 -67
  97. package/src/__tests__/channel-readiness-service.test.ts +13 -45
  98. package/src/__tests__/channel-readiness-slack-remote.test.ts +5 -22
  99. package/src/__tests__/channel-reply-delivery.test.ts +18 -5
  100. package/src/__tests__/channel-retry-sweep.test.ts +0 -7
  101. package/src/__tests__/chat-credential-redaction.test.ts +1395 -0
  102. package/src/__tests__/chat-reveal-guard-priming.test.ts +791 -0
  103. package/src/__tests__/checker.test.ts +0 -24
  104. package/src/__tests__/clawhub-files.test.ts +0 -8
  105. package/src/__tests__/clawhub.test.ts +0 -7
  106. package/src/__tests__/clear-all-lexical.test.ts +0 -6
  107. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -0
  108. package/src/__tests__/client-os-metadata-persistence.test.ts +0 -5
  109. package/src/__tests__/compaction-events.test.ts +26 -99
  110. package/src/__tests__/compaction-ledger-store.test.ts +1 -7
  111. package/src/__tests__/compaction-summary-head-persisted-count.test.ts +0 -27
  112. package/src/__tests__/compaction-trail-store.test.ts +1 -19
  113. package/src/__tests__/compaction.benchmark.test.ts +2 -6
  114. package/src/__tests__/compactor-call-site-logging.test.ts +0 -21
  115. package/src/__tests__/compactor-fixed-boundary.test.ts +0 -21
  116. package/src/__tests__/compactor-image-manifest-trust.test.ts +1 -10
  117. package/src/__tests__/compactor-low-watermark-cut.test.ts +0 -21
  118. package/src/__tests__/compactor-media-strip.test.ts +0 -21
  119. package/src/__tests__/compactor-preserved-tail-count.test.ts +0 -21
  120. package/src/__tests__/compactor-summary-call-truncation.test.ts +0 -21
  121. package/src/__tests__/compactor-web-search-strip.test.ts +0 -21
  122. package/src/__tests__/config-get-vision-flag.test.ts +24 -53
  123. package/src/__tests__/config-loader-backfill.test.ts +9 -25
  124. package/src/__tests__/config-loader-platform-defaults.test.ts +0 -21
  125. package/src/__tests__/config-schema-cmd.test.ts +10 -11
  126. package/src/__tests__/config-schema.test.ts +221 -275
  127. package/src/__tests__/config-set-route.test.ts +32 -62
  128. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +44 -83
  129. package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -16
  130. package/src/__tests__/config-watcher.test.ts +2 -17
  131. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +57 -63
  132. package/src/__tests__/contact-store-interaction-info.test.ts +1 -8
  133. package/src/__tests__/contact-store-user-file.test.ts +1 -8
  134. package/src/__tests__/contacts-tools.test.ts +21 -26
  135. package/src/__tests__/contacts-write.test.ts +1 -8
  136. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -6
  137. package/src/__tests__/context-window-manager-compact-retry.test.ts +0 -27
  138. package/src/__tests__/context-window-manager-overflow-rung.test.ts +0 -21
  139. package/src/__tests__/conversation-abort-tool-results.test.ts +9 -90
  140. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +5 -41
  141. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +16 -54
  142. package/src/__tests__/conversation-agent-loop-overflow.test.ts +14 -79
  143. package/src/__tests__/conversation-agent-loop.test.ts +213 -136
  144. package/src/__tests__/conversation-app-control-instantiation.test.ts +10 -16
  145. package/src/__tests__/conversation-app-control-lifecycle.test.ts +0 -43
  146. package/src/__tests__/conversation-attachments.test.ts +0 -17
  147. package/src/__tests__/conversation-attention-store.test.ts +1 -9
  148. package/src/__tests__/conversation-attention-telegram.test.ts +0 -8
  149. package/src/__tests__/conversation-clear-safety.test.ts +20 -20
  150. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -82
  151. package/src/__tests__/conversation-crud-enabled-plugins.test.ts +1 -7
  152. package/src/__tests__/conversation-crud-inference-profile.test.ts +1 -7
  153. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -7
  154. package/src/__tests__/conversation-disk-view-integration.test.ts +1 -26
  155. package/src/__tests__/conversation-disk-view.test.ts +1 -26
  156. package/src/__tests__/conversation-enabledplugins-route.test.ts +3 -21
  157. package/src/__tests__/conversation-error.test.ts +21 -7
  158. package/src/__tests__/conversation-fork-crud.test.ts +35 -44
  159. package/src/__tests__/conversation-fork-retrospective.test.ts +9 -24
  160. package/src/__tests__/conversation-fork-route.test.ts +0 -32
  161. package/src/__tests__/conversation-history-web-search.test.ts +20 -17
  162. package/src/__tests__/conversation-inference-profile-list.test.ts +0 -17
  163. package/src/__tests__/conversation-inference-profile-route.test.ts +2 -24
  164. package/src/__tests__/conversation-init.benchmark.test.ts +3 -56
  165. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -33
  166. package/src/__tests__/conversation-lifecycle.test.ts +63 -103
  167. package/src/__tests__/conversation-list-source.test.ts +0 -18
  168. package/src/__tests__/conversation-load-history-repair.test.ts +39 -91
  169. package/src/__tests__/conversation-load-history-stripped.test.ts +12 -57
  170. package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +10 -20
  171. package/src/__tests__/conversation-message-id-uuidv7.test.ts +1 -18
  172. package/src/__tests__/conversation-pairing.test.ts +0 -7
  173. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +0 -9
  174. package/src/__tests__/conversation-process-callsite.test.ts +15 -97
  175. package/src/__tests__/conversation-provider-retry-repair.test.ts +3 -84
  176. package/src/__tests__/conversation-queue.test.ts +12 -108
  177. package/src/__tests__/conversation-routes-disk-view.test.ts +8 -60
  178. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +5 -63
  179. package/src/__tests__/conversation-routes-guardian-reply.test.ts +13 -22
  180. package/src/__tests__/conversation-routes-hidden-queue.test.ts +5 -64
  181. package/src/__tests__/conversation-routes-slash-commands.test.ts +19 -87
  182. package/src/__tests__/conversation-runtime-assembly.test.ts +34 -37
  183. package/src/__tests__/conversation-seed-composer.test.ts +3 -3
  184. package/src/__tests__/conversation-skill-tools.test.ts +19 -32
  185. package/src/__tests__/conversation-slash-queue.test.ts +3 -90
  186. package/src/__tests__/conversation-slash-unknown.test.ts +0 -87
  187. package/src/__tests__/conversation-speed-override.test.ts +19 -94
  188. package/src/__tests__/conversation-starter-routes.test.ts +0 -7
  189. package/src/__tests__/conversation-store.test.ts +72 -66
  190. package/src/__tests__/conversation-summarize-route.test.ts +0 -11
  191. package/src/__tests__/conversation-summarize-up-to.test.ts +4 -89
  192. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +131 -0
  193. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +36 -36
  194. package/src/__tests__/conversation-surfaces-data-persist.test.ts +4 -2
  195. package/src/__tests__/conversation-title-service.test.ts +0 -7
  196. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +0 -1
  197. package/src/__tests__/conversation-tool-setup-attribution.test.ts +37 -60
  198. package/src/__tests__/conversation-tool-setup-tools-disabled.test.ts +17 -17
  199. package/src/__tests__/conversation-unread-route.test.ts +0 -7
  200. package/src/__tests__/conversation-usage.test.ts +21 -71
  201. package/src/__tests__/conversation-wait-for-idle.test.ts +0 -82
  202. package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -45
  203. package/src/__tests__/conversation-workspace-injection.test.ts +0 -88
  204. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -88
  205. package/src/__tests__/conversations-defer-cli.test.ts +3 -0
  206. package/src/__tests__/conversations-import-system-filter.test.ts +0 -17
  207. package/src/__tests__/credential-broker-browser-fill.test.ts +0 -9
  208. package/src/__tests__/credential-broker-server-use.test.ts +0 -9
  209. package/src/__tests__/credential-health-service.test.ts +14 -31
  210. package/src/__tests__/credential-metadata-store.test.ts +0 -8
  211. package/src/__tests__/credential-prompt-route.test.ts +9 -15
  212. package/src/__tests__/credential-resolve.test.ts +0 -8
  213. package/src/__tests__/credential-routes.test.ts +250 -0
  214. package/src/__tests__/credential-security-invariants.test.ts +4 -8
  215. package/src/__tests__/credential-token-resolver.test.ts +0 -9
  216. package/src/__tests__/cross-provider-web-search.test.ts +12 -18
  217. package/src/__tests__/daemon-assistant-events.test.ts +1 -9
  218. package/src/__tests__/daemon-credential-client.test.ts +0 -7
  219. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +1 -8
  220. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +1 -7
  221. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +0 -8
  222. package/src/__tests__/db-readiness-http-gate.test.ts +0 -18
  223. package/src/__tests__/db-rename-inference-profile-snake-case-migration.test.ts +1 -7
  224. package/src/__tests__/db-test-helpers.ts +10 -3
  225. package/src/__tests__/delete-managed-skill-tool.test.ts +0 -7
  226. package/src/__tests__/delete-propagation.test.ts +0 -7
  227. package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -7
  228. package/src/__tests__/device-id.test.ts +1 -9
  229. package/src/__tests__/disk-pressure-guard.test.ts +0 -4
  230. package/src/__tests__/disk-pressure-routes.test.ts +0 -4
  231. package/src/__tests__/disk-pressure-tools.test.ts +4 -42
  232. package/src/__tests__/dm-backfill.test.ts +0 -7
  233. package/src/__tests__/dm-persistence.test.ts +0 -5
  234. package/src/__tests__/docker-signing-key-bootstrap.test.ts +1 -8
  235. package/src/__tests__/drain-requeue-on-contention.test.ts +1 -8
  236. package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -43
  237. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -27
  238. package/src/__tests__/edit-propagation.test.ts +1 -8
  239. package/src/__tests__/email-invite-adapter.test.ts +8 -33
  240. package/src/__tests__/embedding-managed-proxy-selection.test.ts +0 -8
  241. package/src/__tests__/emit-event-signal.test.ts +1 -1
  242. package/src/__tests__/emit-signal-routing-intent.test.ts +0 -7
  243. package/src/__tests__/empty-state-greeting-cache.test.ts +9 -9
  244. package/src/__tests__/encrypted-store.test.ts +0 -9
  245. package/src/__tests__/ensure-conversation-exists.test.ts +1 -18
  246. package/src/__tests__/events-client-registration.test.ts +1 -19
  247. package/src/__tests__/events-dev-bypass-actor.test.ts +0 -7
  248. package/src/__tests__/events-tail-route.test.ts +1 -8
  249. package/src/__tests__/file-list-tool.test.ts +22 -15
  250. package/src/__tests__/followup-tools.test.ts +1 -16
  251. package/src/__tests__/gateway-only-enforcement.test.ts +3 -44
  252. package/src/__tests__/get-skill-detail-audit.test.ts +0 -7
  253. package/src/__tests__/google-calendar-watcher.test.ts +175 -0
  254. package/src/__tests__/guardian-action-sweep.test.ts +0 -7
  255. package/src/__tests__/guardian-binding-drift-heal.test.ts +0 -7
  256. package/src/__tests__/guardian-card-withdrawal.test.ts +60 -64
  257. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +172 -135
  258. package/src/__tests__/guardian-dispatch.test.ts +58 -157
  259. package/src/__tests__/guardian-expiry-notifier.test.ts +56 -48
  260. package/src/__tests__/guardian-gateway-sim.ts +626 -0
  261. package/src/__tests__/guardian-outbound-http.test.ts +0 -7
  262. package/src/__tests__/guardian-routing-invariants.test.ts +229 -220
  263. package/src/__tests__/guardian-routing-state.test.ts +2 -9
  264. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -17
  265. package/src/__tests__/guardian-wait-controller.test.ts +44 -43
  266. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +26 -91
  267. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +18 -103
  268. package/src/__tests__/headless-browser-interactions.test.ts +0 -7
  269. package/src/__tests__/headless-browser-mode.test.ts +0 -9
  270. package/src/__tests__/headless-browser-navigate.test.ts +0 -7
  271. package/src/__tests__/headless-browser-read-tools.test.ts +0 -7
  272. package/src/__tests__/headless-browser-snapshot.test.ts +0 -7
  273. package/src/__tests__/heartbeat-disk-pressure.test.ts +9 -38
  274. package/src/__tests__/heartbeat-service.test.ts +83 -106
  275. package/src/__tests__/helpers/channel-test-adapter.ts +10 -0
  276. package/src/__tests__/helpers/gateway-guardian-requests-store-bridge.ts +155 -0
  277. package/src/__tests__/helpers/set-config.ts +40 -0
  278. package/src/__tests__/host-bash-proxy.test.ts +18 -34
  279. package/src/__tests__/host-browser-proxy.test.ts +15 -20
  280. package/src/__tests__/host-shell-tool.test.ts +2 -34
  281. package/src/__tests__/http-conversation-lineage.test.ts +3 -19
  282. package/src/__tests__/http-user-message-parity.test.ts +3 -95
  283. package/src/__tests__/identity-routes.test.ts +1 -9
  284. package/src/__tests__/image-recovery-hook.test.ts +36 -0
  285. package/src/__tests__/inbound-admitted-introduction-nudge.test.ts +6 -12
  286. package/src/__tests__/inbound-invite-redemption.test.ts +0 -7
  287. package/src/__tests__/inbound-slack-persistence.test.ts +0 -5
  288. package/src/__tests__/inbound-trust-verdict.test.ts +2 -8
  289. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +52 -8
  290. package/src/__tests__/inference-profile-reaper.test.ts +1 -7
  291. package/src/__tests__/inference-profile-session-handler.test.ts +4 -30
  292. package/src/__tests__/inference-profile-session-ipc.test.ts +3 -20
  293. package/src/__tests__/ingress-url-consistency.test.ts +1 -23
  294. package/src/__tests__/injector-background-turn.test.ts +10 -24
  295. package/src/__tests__/injector-chain.test.ts +4 -13
  296. package/src/__tests__/injector-pkb-v2-silenced.test.ts +19 -17
  297. package/src/__tests__/inline-command-runner.test.ts +0 -34
  298. package/src/__tests__/inline-skill-load-permissions.test.ts +1 -30
  299. package/src/__tests__/install-skill-routing.test.ts +7 -6
  300. package/src/__tests__/integration-status.test.ts +25 -17
  301. package/src/__tests__/intent-routing.test.ts +1 -50
  302. package/src/__tests__/internal-telemetry-routes.test.ts +41 -22
  303. package/src/__tests__/interrupted-turn-reconciler.test.ts +1 -8
  304. package/src/__tests__/introduction-card-resolver.test.ts +108 -117
  305. package/src/__tests__/invite-redeemed-ipc.test.ts +0 -7
  306. package/src/__tests__/invite-routes-http.test.ts +12 -23
  307. package/src/__tests__/jobs-store-has-pending-job.test.ts +59 -0
  308. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +1 -14
  309. package/src/__tests__/jobs-store-skill-card-upsert.test.ts +191 -0
  310. package/src/__tests__/jobs-store-upsert-debounced.test.ts +1 -14
  311. package/src/__tests__/lexical-index-dual-write.test.ts +0 -6
  312. package/src/__tests__/list-all-apps.test.ts +200 -0
  313. package/src/__tests__/list-messages-attachments.test.ts +11 -20
  314. package/src/__tests__/list-messages-background-tool-completion.test.ts +6 -18
  315. package/src/__tests__/list-messages-client-message-id.test.ts +4 -16
  316. package/src/__tests__/list-messages-hidden-metadata.test.ts +6 -18
  317. package/src/__tests__/list-messages-page-latest.test.ts +3 -19
  318. package/src/__tests__/list-messages-queued.test.ts +1 -18
  319. package/src/__tests__/list-messages-tool-merge.test.ts +7 -18
  320. package/src/__tests__/llm-context-resolution.test.ts +32 -56
  321. package/src/__tests__/llm-context-route-provider.test.ts +1 -8
  322. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +1 -19
  323. package/src/__tests__/llm-request-log-call-site.test.ts +1 -19
  324. package/src/__tests__/llm-request-log-clickhouse-routing.test.ts +114 -0
  325. package/src/__tests__/llm-request-log-clickhouse-writer-guards.test.ts +108 -0
  326. package/src/__tests__/llm-request-log-disabled-writes.test.ts +11 -30
  327. package/src/__tests__/llm-request-log-sink-clickhouse.test.ts +205 -0
  328. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +1 -22
  329. package/src/__tests__/llm-request-log-source-factory.test.ts +23 -69
  330. package/src/__tests__/llm-request-log-turn-query.test.ts +1 -19
  331. package/src/__tests__/llm-resolver-override-or-default.test.ts +3 -52
  332. package/src/__tests__/llm-resolver.test.ts +342 -602
  333. package/src/__tests__/llm-schema.test.ts +79 -37
  334. package/src/__tests__/llm-usage-store.test.ts +1 -7
  335. package/src/__tests__/log-export-routes.test.ts +0 -7
  336. package/src/__tests__/log-export-workspace.test.ts +0 -7
  337. package/src/__tests__/managed-profile-guard.test.ts +125 -137
  338. package/src/__tests__/managed-skill-lifecycle.test.ts +6 -50
  339. package/src/__tests__/managed-speech-defaults.test.ts +138 -0
  340. package/src/__tests__/managed-store.test.ts +1 -16
  341. package/src/__tests__/mcp-auth-routes.test.ts +15 -26
  342. package/src/__tests__/mcp-config-secret-boundary.test.ts +49 -91
  343. package/src/__tests__/mcp-health-check.test.ts +14 -20
  344. package/src/__tests__/media-generate-image.test.ts +25 -32
  345. package/src/__tests__/media-stream-output.test.ts +0 -9
  346. package/src/__tests__/media-stream-server-integration.test.ts +19 -27
  347. package/src/__tests__/media-stream-stt-session.test.ts +16 -29
  348. package/src/__tests__/memory-jobs-worker-backoff.test.ts +6 -19
  349. package/src/__tests__/memory-jobs-worker-cleanup-cadence.test.ts +33 -12
  350. package/src/__tests__/memory-jobs-worker-lanes.test.ts +10 -29
  351. package/src/__tests__/memory-recall-log-store.test.ts +1 -19
  352. package/src/__tests__/memory-retrieval-hook.test.ts +0 -3
  353. package/src/__tests__/memory-upsert-concurrency.test.ts +12 -35
  354. package/src/__tests__/memory-v2-static-injector.test.ts +5 -17
  355. package/src/__tests__/messages-after-tiebreaker.test.ts +1 -7
  356. package/src/__tests__/messaging-send-tool.test.ts +67 -7
  357. package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -18
  358. package/src/__tests__/migration-export-http.test.ts +10 -23
  359. package/src/__tests__/migration-export-streaming.test.ts +0 -18
  360. package/src/__tests__/migration-export-to-gcs.test.ts +0 -19
  361. package/src/__tests__/migration-import-commit-http.test.ts +11 -23
  362. package/src/__tests__/migration-import-from-gcs.test.ts +0 -19
  363. package/src/__tests__/migration-import-from-url.test.ts +0 -19
  364. package/src/__tests__/migration-import-preflight-http.test.ts +10 -22
  365. package/src/__tests__/migration-jobs-status.test.ts +8 -23
  366. package/src/__tests__/migration-validate-http.test.ts +2 -19
  367. package/src/__tests__/mtime-cache.test.ts +116 -2
  368. package/src/__tests__/native-web-search.test.ts +0 -21
  369. package/src/__tests__/non-member-access-request.test.ts +102 -93
  370. package/src/__tests__/notification-broadcaster.test.ts +32 -21
  371. package/src/__tests__/notification-candidate-guardian-context.test.ts +36 -34
  372. package/src/__tests__/notification-decision-fallback.test.ts +0 -7
  373. package/src/__tests__/notification-decision-identity.test.ts +0 -7
  374. package/src/__tests__/notification-decision-recipient-context.test.ts +0 -7
  375. package/src/__tests__/notification-deep-link.test.ts +0 -7
  376. package/src/__tests__/notification-guardian-path.test.ts +48 -93
  377. package/src/__tests__/notification-platform-adapter.test.ts +0 -7
  378. package/src/__tests__/notification-routing-intent.test.ts +1 -9
  379. package/src/__tests__/notification-schedule-notify-dedup.test.ts +1 -9
  380. package/src/__tests__/notification-telegram-adapter.test.ts +0 -7
  381. package/src/__tests__/oauth-cli.test.ts +0 -19
  382. package/src/__tests__/oauth-commands-routes.test.ts +28 -43
  383. package/src/__tests__/oauth-connect-orchestrator.test.ts +17 -26
  384. package/src/__tests__/oauth-connect-routes.test.ts +19 -18
  385. package/src/__tests__/oauth-provider-profiles.test.ts +3 -9
  386. package/src/__tests__/oauth-provider-visibility.test.ts +0 -7
  387. package/src/__tests__/oauth-store.test.ts +0 -7
  388. package/src/__tests__/oauth2-gateway-transport.test.ts +41 -43
  389. package/src/__tests__/openai-responses-prompt-cache.test.ts +355 -0
  390. package/src/__tests__/openai-responses-provider.test.ts +98 -9
  391. package/src/__tests__/openrouter-responses-transport.test.ts +170 -0
  392. package/src/__tests__/outbound-slack-persistence.test.ts +13 -46
  393. package/src/__tests__/outlook-email-watcher.test.ts +0 -9
  394. package/src/__tests__/outlook-messaging-provider.test.ts +71 -0
  395. package/src/__tests__/path-policy.test.ts +0 -7
  396. package/src/__tests__/persist-media-references.test.ts +1 -6
  397. package/src/__tests__/persist-unsendable-image-downscale.test.ts +6 -3
  398. package/src/__tests__/persist-unsendable-image.test.ts +39 -7
  399. package/src/__tests__/persistence-secret-redaction.test.ts +7 -28
  400. package/src/__tests__/platform-bash-auto-approve.test.ts +0 -46
  401. package/src/__tests__/platform-callback-registration.test.ts +10 -9
  402. package/src/__tests__/platform-proxy-context.test.ts +0 -8
  403. package/src/__tests__/playbook-execution.test.ts +0 -14
  404. package/src/__tests__/playbook-tools.test.ts +0 -14
  405. package/src/__tests__/plugin-api-model-profiles.test.ts +12 -35
  406. package/src/__tests__/plugin-app-serve-routes.test.ts +315 -0
  407. package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -3
  408. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +0 -2
  409. package/src/__tests__/plugin-types.test.ts +0 -7
  410. package/src/__tests__/prechat-onboarding-contract.test.ts +0 -27
  411. package/src/__tests__/pricing.test.ts +45 -0
  412. package/src/__tests__/process-message-background-slack.test.ts +7 -21
  413. package/src/__tests__/process-message-display-content.test.ts +37 -6
  414. package/src/__tests__/processing-flag-persist-failure.test.ts +0 -44
  415. package/src/__tests__/provider-commit-message-generator.test.ts +57 -63
  416. package/src/__tests__/provider-error-scenarios.test.ts +5 -6
  417. package/src/__tests__/provider-platform-proxy-integration.test.ts +21 -57
  418. package/src/__tests__/provider-registry-ollama.test.ts +8 -18
  419. package/src/__tests__/provider-send-message-override-profile.test.ts +28 -39
  420. package/src/__tests__/provider-streaming.benchmark.test.ts +1 -6
  421. package/src/__tests__/provider-usage-tracking.test.ts +13 -35
  422. package/src/__tests__/proxy-approval-callback.test.ts +0 -16
  423. package/src/__tests__/prune-old-conversations-job.test.ts +29 -21
  424. package/src/__tests__/prune-old-tool-invocations-job.test.ts +1 -9
  425. package/src/__tests__/public-ingress-urls.test.ts +1 -24
  426. package/src/__tests__/published-app-updater.test.ts +22 -16
  427. package/src/__tests__/qdrant-collection-migration.test.ts +0 -7
  428. package/src/__tests__/qdrant-manager.test.ts +0 -8
  429. package/src/__tests__/ratelimit.test.ts +1 -8
  430. package/src/__tests__/reaction-persistence.test.ts +18 -24
  431. package/src/__tests__/rebuild-index-graph-nodes.test.ts +0 -14
  432. package/src/__tests__/recording-handler.test.ts +0 -27
  433. package/src/__tests__/request-secret-standalone-channel-gate.test.ts +4 -16
  434. package/src/__tests__/require-fresh-approval.test.ts +0 -45
  435. package/src/__tests__/retry-openrouter-only-normalization.test.ts +24 -36
  436. package/src/__tests__/retry-prompt-cache-key.test.ts +123 -0
  437. package/src/__tests__/retry-thinking-adaptive-only.test.ts +45 -61
  438. package/src/__tests__/retry-thinking-tool-choice.test.ts +60 -80
  439. package/src/__tests__/retry-verbosity-normalization.test.ts +27 -37
  440. package/src/__tests__/reveal-success-registry.test.ts +123 -0
  441. package/src/__tests__/run-conversation-turn-persistence.test.ts +113 -0
  442. package/src/__tests__/{scheduler-worker-standdown.test.ts → run-due-schedules.test.ts} +5 -77
  443. package/src/__tests__/runtime-attachment-metadata.test.ts +5 -18
  444. package/src/__tests__/runtime-events-sse-bilingual.test.ts +1 -19
  445. package/src/__tests__/runtime-events-sse-parity.test.ts +1 -20
  446. package/src/__tests__/runtime-events-sse-reconnect.test.ts +1 -19
  447. package/src/__tests__/runtime-events-sse.test.ts +1 -20
  448. package/src/__tests__/scaffold-managed-skill-tool.test.ts +306 -5
  449. package/src/__tests__/schedule-retry.test.ts +22 -23
  450. package/src/__tests__/schedule-routes-workflow-validation.test.ts +18 -42
  451. package/src/__tests__/schedule-routes.test.ts +2 -150
  452. package/src/__tests__/schedule-store.test.ts +0 -8
  453. package/src/__tests__/schedule-tools.test.ts +1 -16
  454. package/src/__tests__/scheduler-disk-pressure.test.ts +0 -26
  455. package/src/__tests__/scheduler-recurrence.test.ts +43 -76
  456. package/src/__tests__/scheduler-reuse-conversation.test.ts +19 -35
  457. package/src/__tests__/scheduler-wake.test.ts +8 -24
  458. package/src/__tests__/scoped-approval-grants.test.ts +1 -9
  459. package/src/__tests__/scoped-grant-security-matrix.test.ts +1 -9
  460. package/src/__tests__/search-skills-unified.test.ts +0 -6
  461. package/src/__tests__/secret-allowlist.test.ts +1 -8
  462. package/src/__tests__/secret-fixtures.ts +9 -0
  463. package/src/__tests__/secret-ingress-channel.test.ts +4 -32
  464. package/src/__tests__/secret-ingress-cli.test.ts +3 -37
  465. package/src/__tests__/secret-ingress-http.test.ts +15 -78
  466. package/src/__tests__/secret-ingress.test.ts +14 -31
  467. package/src/__tests__/secret-prompt-log-hygiene.test.ts +4 -10
  468. package/src/__tests__/secret-prompter-channel-fallback.test.ts +3 -9
  469. package/src/__tests__/secret-routes-platform-proxy.test.ts +3 -40
  470. package/src/__tests__/secure-keys-managed-failover.test.ts +0 -7
  471. package/src/__tests__/secure-keys.test.ts +0 -9
  472. package/src/__tests__/send-endpoint-busy.test.ts +47 -91
  473. package/src/__tests__/sequence-store.test.ts +0 -9
  474. package/src/__tests__/server-history-render.test.ts +29 -8
  475. package/src/__tests__/set-permission-mode.test.ts +8 -16
  476. package/src/__tests__/settings-routes.test.ts +0 -7
  477. package/src/__tests__/shell-credential-ref.test.ts +0 -18
  478. package/src/__tests__/shell-observability.test.ts +0 -21
  479. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -34
  480. package/src/__tests__/skill-load-feature-flag.test.ts +0 -27
  481. package/src/__tests__/skill-load-inline-command.test.ts +0 -25
  482. package/src/__tests__/skill-load-inline-includes.test.ts +0 -25
  483. package/src/__tests__/skill-load-plugin-scope.test.ts +0 -24
  484. package/src/__tests__/skill-projection-feature-flag.test.ts +14 -33
  485. package/src/__tests__/skill-projection.benchmark.test.ts +14 -19
  486. package/src/__tests__/skill-script-runner-sandbox.test.ts +1 -39
  487. package/src/__tests__/skills-file-content-endpoint.test.ts +0 -7
  488. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -14
  489. package/src/__tests__/slack-channel-config.test.ts +24 -79
  490. package/src/__tests__/slack-inbound-verification.test.ts +5 -13
  491. package/src/__tests__/{slack-reaction-canonical-approval.test.ts → slack-reaction-guardian-approval.test.ts} +18 -74
  492. package/src/__tests__/slack-share-routes.test.ts +0 -1
  493. package/src/__tests__/slim-skill-category.test.ts +0 -14
  494. package/src/__tests__/stale-approval-dedup.test.ts +0 -7
  495. package/src/__tests__/subagent-call-site-routing.test.ts +72 -111
  496. package/src/__tests__/subagent-detail.test.ts +1 -1
  497. package/src/__tests__/subagent-fork-prompt-role.test.ts +0 -12
  498. package/src/__tests__/subagent-notify-parent.test.ts +4 -6
  499. package/src/__tests__/subagent-spawn-and-await.test.ts +0 -18
  500. package/src/__tests__/subagent-tool-filtering.test.ts +21 -31
  501. package/src/__tests__/subagent-tool-gate-mode.test.ts +6 -32
  502. package/src/__tests__/subagent-tools.test.ts +92 -67
  503. package/src/__tests__/suggestion-routes.test.ts +28 -42
  504. package/src/__tests__/summarize-boundary.test.ts +2 -1
  505. package/src/__tests__/system-prompt-ask-mode.test.ts +0 -25
  506. package/src/__tests__/system-prompt.test.ts +0 -33
  507. package/src/__tests__/task-memory-cleanup.test.ts +18 -192
  508. package/src/__tests__/telegram-bot-username-resolution.test.ts +24 -48
  509. package/src/__tests__/telegram-config.test.ts +0 -8
  510. package/src/__tests__/telemetry-routes.test.ts +100 -0
  511. package/src/__tests__/telephony-credential-preflight.test.ts +7 -11
  512. package/src/__tests__/telephony-tts-guard.test.ts +38 -21
  513. package/src/__tests__/terminal-tools.test.ts +0 -21
  514. package/src/__tests__/thread-backfill.test.ts +0 -7
  515. package/src/__tests__/title-generate-hook.test.ts +11 -10
  516. package/src/__tests__/token-manager.test.ts +0 -9
  517. package/src/__tests__/tool-approval-handler.test.ts +0 -8
  518. package/src/__tests__/tool-execution-abort-cleanup.test.ts +5 -33
  519. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +3 -23
  520. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -48
  521. package/src/__tests__/tool-executor.test.ts +3 -49
  522. package/src/__tests__/tool-grant-request-escalation.test.ts +51 -153
  523. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -29
  524. package/src/__tests__/tool-profiler.test.ts +72 -1
  525. package/src/__tests__/tool-result-metadata-plumbing.test.ts +0 -29
  526. package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -1
  527. package/src/__tests__/tool-start-timestamp.test.ts +4 -31
  528. package/src/__tests__/tools-get-route.test.ts +2 -1
  529. package/src/__tests__/trust-context-guards.test.ts +19 -7
  530. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +75 -79
  531. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +14 -18
  532. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -7
  533. package/src/__tests__/trusted-contact-verification.test.ts +1 -9
  534. package/src/__tests__/turn-boundary-resolution.test.ts +1 -19
  535. package/src/__tests__/turn-events-store.test.ts +1 -7
  536. package/src/__tests__/turn-outcome-read.test.ts +1 -7
  537. package/src/__tests__/twilio-config.test.ts +11 -28
  538. package/src/__tests__/twilio-provider.test.ts +2 -13
  539. package/src/__tests__/twilio-routes-twiml.test.ts +1 -8
  540. package/src/__tests__/twilio-routes.test.ts +52 -103
  541. package/src/__tests__/twilio-validation.test.ts +5 -22
  542. package/src/__tests__/ui-file-upload-surface.test.ts +6 -9
  543. package/src/__tests__/usage-attribution.test.ts +25 -56
  544. package/src/__tests__/usage-cache-backfill-migration.test.ts +8 -15
  545. package/src/__tests__/usage-routes.test.ts +1 -8
  546. package/src/__tests__/vbundle-pax-and-symlink.test.ts +0 -18
  547. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +0 -24
  548. package/src/__tests__/vercel-config.test.ts +0 -7
  549. package/src/__tests__/verification-control-plane-policy.test.ts +0 -45
  550. package/src/__tests__/voice-approval-resolver-relay.test.ts +106 -75
  551. package/src/__tests__/voice-config-update.test.ts +114 -21
  552. package/src/__tests__/voice-ingress-preflight.test.ts +16 -15
  553. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -30
  554. package/src/__tests__/voice-session-bridge-processing-wait.test.ts +1 -17
  555. package/src/__tests__/voice-session-bridge.test.ts +14 -46
  556. package/src/__tests__/web-search-backend-failure.test.ts +0 -21
  557. package/src/__tests__/whatsapp-invite-adapter.test.ts +11 -30
  558. package/src/__tests__/workspace-git-service.test.ts +801 -2
  559. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +1 -26
  560. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -26
  561. package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +1 -26
  562. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +1 -9
  563. package/src/a2a/__tests__/task-store.test.ts +0 -9
  564. package/src/acp/__tests__/session-manager-persistence.test.ts +1 -8
  565. package/src/acp/__tests__/session-manager-resume.test.ts +0 -7
  566. package/src/acp/__tests__/session-manager-startup.test.ts +1 -8
  567. package/src/agent/image-optimize.ts +80 -0
  568. package/src/agent/loop.ts +20 -5
  569. package/src/api/events/open-conversation.test.ts +64 -0
  570. package/src/api/events/open-conversation.ts +33 -0
  571. package/src/api/index.ts +6 -0
  572. package/src/approvals/AGENTS.md +1 -1
  573. package/src/approvals/guardian-card-withdrawal.ts +26 -19
  574. package/src/approvals/guardian-decision-primitive.ts +172 -102
  575. package/src/approvals/guardian-expiry-notifier.ts +20 -19
  576. package/src/approvals/guardian-request-resolvers.ts +305 -275
  577. package/src/apps/app-store.ts +320 -15
  578. package/src/background-wake/background-wake-routes.test.ts +2 -3
  579. package/src/background-wake/next-wake.test.ts +23 -20
  580. package/src/background-wake/wake-intent-hooks.test.ts +0 -10
  581. package/src/bundler/app-bundler.ts +34 -48
  582. package/src/bundler/app-compiler.ts +39 -4
  583. package/src/bundler/bundle-scanner.ts +13 -0
  584. package/src/bundler/manifest.ts +1 -1
  585. package/src/calls/__tests__/call-setup-router.test.ts +84 -88
  586. package/src/calls/__tests__/fish-audio-client.test.ts +6 -11
  587. package/src/calls/__tests__/voice-control-protocol.test.ts +47 -0
  588. package/src/calls/__tests__/voice-session-bridge.test.ts +151 -16
  589. package/src/calls/__tests__/voice-triage-escalate.test.ts +128 -0
  590. package/src/calls/access-request-wait.ts +35 -35
  591. package/src/calls/call-controller.ts +48 -45
  592. package/src/calls/call-setup-flow.ts +2 -2
  593. package/src/calls/call-setup-router.ts +1 -24
  594. package/src/calls/guardian-dispatch.ts +47 -30
  595. package/src/calls/guardian-wait-controller.ts +31 -18
  596. package/src/calls/media-turn-detector.ts +11 -1
  597. package/src/calls/telephony-credential-preflight.ts +19 -1
  598. package/src/calls/telephony-tts-capability.ts +18 -1
  599. package/src/calls/voice-control-protocol.ts +12 -0
  600. package/src/calls/voice-session-bridge.ts +110 -15
  601. package/src/calls/voice-triage-escalate.ts +136 -0
  602. package/src/channels/__tests__/gateway-guardian-requests.test.ts +669 -0
  603. package/src/channels/gateway-guardian-requests.ts +410 -0
  604. package/src/channels/types.ts +10 -5
  605. package/src/cli/AGENTS.md +77 -51
  606. package/src/cli/__tests__/cli-command-help-coverage.test.ts +29 -0
  607. package/src/cli/commands/__tests__/conversations-import.test.ts +23 -24
  608. package/src/cli/commands/__tests__/gateway.test.ts +55 -0
  609. package/src/cli/commands/__tests__/inference-models.test.ts +3 -0
  610. package/src/cli/commands/__tests__/inference-profiles.test.ts +3 -0
  611. package/src/cli/commands/__tests__/inference-providers.test.ts +3 -0
  612. package/src/cli/commands/__tests__/inference-send.test.ts +0 -10
  613. package/src/cli/commands/__tests__/inference-session.test.ts +4 -11
  614. package/src/cli/commands/__tests__/ui.test.ts +2 -2
  615. package/src/cli/commands/apps.help.ts +46 -0
  616. package/src/cli/commands/apps.ts +51 -0
  617. package/src/cli/commands/avatar.help.ts +229 -0
  618. package/src/cli/commands/avatar.ts +58 -250
  619. package/src/cli/commands/backup.help.ts +183 -0
  620. package/src/cli/commands/backup.ts +107 -276
  621. package/src/cli/commands/bash.help.ts +38 -0
  622. package/src/cli/commands/bash.ts +51 -75
  623. package/src/cli/commands/browser.help.ts +193 -0
  624. package/src/cli/commands/browser.ts +234 -347
  625. package/src/cli/commands/cache.help.ts +123 -0
  626. package/src/cli/commands/cache.ts +72 -166
  627. package/src/cli/commands/changelog.help.ts +59 -0
  628. package/src/cli/commands/changelog.ts +29 -61
  629. package/src/cli/commands/channel-verification-sessions.help.ts +178 -0
  630. package/src/cli/commands/channel-verification-sessions.ts +71 -226
  631. package/src/cli/commands/channels/index.help.ts +67 -0
  632. package/src/cli/commands/channels/index.ts +55 -105
  633. package/src/cli/commands/clients.help.ts +72 -0
  634. package/src/cli/commands/clients.ts +100 -153
  635. package/src/cli/commands/completions.help.ts +33 -0
  636. package/src/cli/commands/completions.ts +43 -66
  637. package/src/cli/commands/config.help.ts +126 -0
  638. package/src/cli/commands/config.ts +59 -174
  639. package/src/cli/commands/contacts.help.ts +327 -0
  640. package/src/cli/commands/contacts.ts +318 -603
  641. package/src/cli/commands/conversations-defer.ts +141 -202
  642. package/src/cli/commands/conversations-import.ts +80 -113
  643. package/src/cli/commands/conversations.help.ts +364 -0
  644. package/src/cli/commands/conversations.ts +143 -328
  645. package/src/cli/commands/credentials.help.ts +290 -0
  646. package/src/cli/commands/credentials.ts +271 -487
  647. package/src/cli/commands/db/index.help.ts +27 -0
  648. package/src/cli/commands/db/index.ts +7 -3
  649. package/src/cli/commands/db/refresh.ts +131 -0
  650. package/src/cli/commands/db/repair-step-conversation-backfill.ts +19 -9
  651. package/src/cli/commands/db/repair.ts +41 -43
  652. package/src/cli/commands/db/status.ts +79 -83
  653. package/src/cli/commands/domain.help.ts +34 -0
  654. package/src/cli/commands/domain.ts +10 -15
  655. package/src/cli/commands/email.help.ts +176 -0
  656. package/src/cli/commands/email.ts +323 -412
  657. package/src/cli/commands/gateway.help.ts +115 -0
  658. package/src/cli/commands/gateway.ts +105 -123
  659. package/src/cli/commands/image-generation.help.ts +72 -0
  660. package/src/cli/commands/image-generation.ts +13 -62
  661. package/src/cli/commands/inference-callsites.ts +34 -49
  662. package/src/cli/commands/inference-models.ts +6 -21
  663. package/src/cli/commands/inference-profiles.ts +95 -156
  664. package/src/cli/commands/inference-providers-default.ts +27 -42
  665. package/src/cli/commands/inference-providers.ts +25 -87
  666. package/src/cli/commands/inference-session.ts +189 -249
  667. package/src/cli/commands/inference.help.ts +485 -0
  668. package/src/cli/commands/inference.ts +104 -165
  669. package/src/cli/commands/keys.help.ts +46 -0
  670. package/src/cli/commands/keys.ts +37 -71
  671. package/src/cli/commands/mcp.help.ts +167 -0
  672. package/src/cli/commands/mcp.ts +123 -267
  673. package/src/cli/commands/memory/__tests__/memory-items.test.ts +4 -0
  674. package/src/cli/commands/memory/__tests__/memory-v2.test.ts +4 -0
  675. package/src/cli/commands/memory/__tests__/memory-v3.test.ts +4 -0
  676. package/src/cli/commands/memory/__tests__/worker.test.ts +12 -5
  677. package/src/cli/commands/memory/index.help.ts +814 -0
  678. package/src/cli/commands/memory/index.ts +5 -27
  679. package/src/cli/commands/memory/items.ts +257 -404
  680. package/src/cli/commands/memory/memory-retrospective.ts +31 -71
  681. package/src/cli/commands/memory/memory-v2-compare-render.ts +2 -2
  682. package/src/cli/commands/memory/memory-v2.ts +435 -646
  683. package/src/cli/commands/memory/memory-v3.ts +158 -283
  684. package/src/cli/commands/memory/nodes.ts +154 -249
  685. package/src/cli/commands/memory/worker.ts +83 -128
  686. package/src/cli/commands/monitoring.help.ts +57 -0
  687. package/src/cli/commands/monitoring.ts +17 -41
  688. package/src/cli/commands/notifications.help.ts +293 -0
  689. package/src/cli/commands/notifications.ts +420 -657
  690. package/src/cli/commands/oauth/apps.ts +182 -315
  691. package/src/cli/commands/oauth/connect.ts +4 -35
  692. package/src/cli/commands/oauth/disconnect.ts +44 -71
  693. package/src/cli/commands/oauth/index.help.ts +855 -0
  694. package/src/cli/commands/oauth/index.ts +5 -31
  695. package/src/cli/commands/oauth/mode.ts +5 -28
  696. package/src/cli/commands/oauth/ping.ts +44 -66
  697. package/src/cli/commands/oauth/providers.ts +413 -772
  698. package/src/cli/commands/oauth/request.ts +6 -35
  699. package/src/cli/commands/oauth/shared.ts +1 -1
  700. package/src/cli/commands/oauth/status.test.ts +6 -6
  701. package/src/cli/commands/oauth/status.ts +45 -62
  702. package/src/cli/commands/oauth/token.ts +32 -71
  703. package/src/cli/commands/pending.help.ts +26 -0
  704. package/src/cli/commands/pending.ts +77 -78
  705. package/src/cli/commands/platform/__tests__/connect.test.ts +3 -1
  706. package/src/cli/commands/platform/__tests__/disconnect.test.ts +4 -4
  707. package/src/cli/commands/platform/__tests__/status.test.ts +0 -90
  708. package/src/cli/commands/platform/connect.ts +10 -21
  709. package/src/cli/commands/platform/disconnect.ts +11 -26
  710. package/src/cli/commands/platform/index.help.ts +187 -0
  711. package/src/cli/commands/platform/index.ts +22 -169
  712. package/src/cli/commands/plugins.help.ts +247 -0
  713. package/src/cli/commands/plugins.ts +356 -437
  714. package/src/cli/commands/ps.help.ts +26 -0
  715. package/src/cli/commands/ps.ts +5 -17
  716. package/src/cli/commands/routes.help.ts +72 -0
  717. package/src/cli/commands/routes.ts +78 -125
  718. package/src/cli/commands/schedules-worker.ts +70 -138
  719. package/src/cli/commands/schedules.help.ts +514 -0
  720. package/src/cli/commands/schedules.ts +494 -871
  721. package/src/cli/commands/sequence.help.ts +183 -0
  722. package/src/cli/commands/sequence.ts +181 -343
  723. package/src/cli/commands/skills.help.ts +159 -0
  724. package/src/cli/commands/skills.ts +230 -368
  725. package/src/cli/commands/status.help.ts +32 -0
  726. package/src/cli/commands/status.ts +38 -3
  727. package/src/cli/commands/stt.help.ts +52 -0
  728. package/src/cli/commands/stt.ts +9 -47
  729. package/src/cli/commands/telemetry.help.ts +14 -0
  730. package/src/cli/commands/telemetry.ts +19 -17
  731. package/src/cli/commands/tools-run.ts +30 -45
  732. package/src/cli/commands/tools.help.ts +81 -0
  733. package/src/cli/commands/tools.ts +43 -83
  734. package/src/cli/commands/trust.help.ts +43 -0
  735. package/src/cli/commands/trust.ts +82 -109
  736. package/src/cli/commands/tts.help.ts +88 -0
  737. package/src/cli/commands/tts.ts +116 -188
  738. package/src/cli/commands/ui.help.ts +213 -0
  739. package/src/cli/commands/ui.ts +363 -380
  740. package/src/cli/commands/usage.help.ts +147 -0
  741. package/src/cli/commands/usage.ts +87 -206
  742. package/src/cli/commands/watchers.help.ts +211 -0
  743. package/src/cli/commands/watchers.ts +261 -417
  744. package/src/cli/commands/webhooks.help.ts +95 -0
  745. package/src/cli/commands/webhooks.ts +44 -115
  746. package/src/cli/index.help.ts +91 -0
  747. package/src/cli/lib/__tests__/global-json-option.test.ts +97 -0
  748. package/src/cli/lib/__tests__/install-from-github.test.ts +144 -4
  749. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +96 -132
  750. package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +65 -0
  751. package/src/cli/lib/__tests__/plugin-catalog-platform.test.ts +167 -0
  752. package/src/cli/lib/__tests__/plugin-catalog-resolve.test.ts +197 -0
  753. package/src/cli/lib/__tests__/plugin-details.test.ts +349 -240
  754. package/src/cli/lib/__tests__/plugin-fingerprint.test.ts +31 -0
  755. package/src/cli/lib/__tests__/plugin-icon-file.test.ts +50 -1
  756. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +94 -0
  757. package/src/cli/lib/__tests__/plugins-install-offline.test.ts +147 -0
  758. package/src/cli/lib/__tests__/search-plugins.test.ts +193 -309
  759. package/src/cli/lib/bundled-marketplace.json +439 -0
  760. package/src/cli/lib/cli-command-help.ts +83 -10
  761. package/src/cli/lib/global-json-option.ts +41 -0
  762. package/src/cli/lib/install-from-github.ts +69 -2
  763. package/src/cli/lib/plugin-catalog-cache.ts +22 -50
  764. package/src/cli/lib/plugin-catalog-local.ts +71 -0
  765. package/src/cli/lib/plugin-catalog-platform.ts +117 -0
  766. package/src/cli/lib/plugin-catalog-resolve.ts +72 -0
  767. package/src/cli/lib/plugin-details.ts +100 -34
  768. package/src/cli/lib/plugin-fingerprint.ts +18 -7
  769. package/src/cli/lib/plugin-icon-file.ts +38 -26
  770. package/src/cli/lib/plugin-marketplace.ts +15 -2
  771. package/src/cli/lib/publish-plugin.ts +3 -3
  772. package/src/cli/lib/search-plugins.ts +48 -111
  773. package/src/cli/program.ts +7 -0
  774. package/src/cli.ts +22 -13
  775. package/src/config/__tests__/default-profile-catalog.test.ts +8 -12
  776. package/src/config/__tests__/default-provider.test.ts +0 -21
  777. package/src/config/__tests__/deployment-context-defaults.test.ts +0 -21
  778. package/src/config/__tests__/feature-flag-registry-bundled.test.ts +2 -2
  779. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +0 -21
  780. package/src/config/__tests__/loader-last-known-good.test.ts +0 -21
  781. package/src/config/__tests__/loader-sparse-array-cleanup.test.ts +0 -21
  782. package/src/config/__tests__/platform-rehydration.test.ts +125 -0
  783. package/src/config/__tests__/profile-materialization.test.ts +1 -88
  784. package/src/config/__tests__/seed-default-provider.test.ts +0 -21
  785. package/src/config/__tests__/sync-gated-profiles.test.ts +0 -21
  786. package/src/config/bundled-skills/app-builder/SKILL.md +24 -1
  787. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +30 -0
  788. package/src/config/bundled-skills/contacts/SKILL.md +1 -1
  789. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +4 -2
  790. package/src/config/bundled-skills/messaging/TOOLS.json +2 -2
  791. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +28 -20
  792. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +7 -7
  793. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
  794. package/src/config/bundled-skills/schedule/SKILL.md +0 -1
  795. package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
  796. package/src/config/bundled-skills/settings/TOOLS.json +5 -3
  797. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +84 -10
  798. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +0 -7
  799. package/src/config/call-site-defaults.ts +3 -3
  800. package/src/config/feature-flag-registry.json +15 -63
  801. package/src/config/llm-resolver.ts +88 -475
  802. package/src/config/managed-speech-defaults.ts +115 -0
  803. package/src/config/platform-rehydration.ts +76 -0
  804. package/src/config/profile-materialization.ts +11 -11
  805. package/src/config/schema.ts +93 -129
  806. package/src/config/schemas/__tests__/live-voice.test.ts +24 -6
  807. package/src/config/schemas/__tests__/stt.test.ts +37 -0
  808. package/src/config/schemas/live-voice.ts +10 -4
  809. package/src/config/schemas/llm.ts +51 -15
  810. package/src/config/schemas/memory-lifecycle.ts +1 -13
  811. package/src/config/schemas/memory-retrospective.ts +8 -0
  812. package/src/config/schemas/memory.ts +0 -4
  813. package/src/config/schemas/stt.ts +33 -6
  814. package/src/config/schemas/tts.ts +53 -6
  815. package/src/config/schemas/workspace-git.ts +8 -0
  816. package/src/contacts/contact-store.ts +3 -58
  817. package/src/contacts/types.ts +1 -1
  818. package/src/conversations/__tests__/message-consolidation.test.ts +6 -5
  819. package/src/conversations/message-consolidation.ts +34 -112
  820. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +64 -7
  821. package/src/daemon/__tests__/conversation-tool-setup-plugin-scope.test.ts +3 -7
  822. package/src/daemon/__tests__/conversation-tool-setup.test.ts +8 -6
  823. package/src/daemon/__tests__/embedding-reconcile.test.ts +0 -11
  824. package/src/daemon/__tests__/inference-profile-notification.test.ts +5 -11
  825. package/src/daemon/__tests__/inflight-message-content.test.ts +219 -0
  826. package/src/daemon/__tests__/native-web-search-metadata.test.ts +5 -27
  827. package/src/daemon/__tests__/route-conversation-post.test.ts +158 -0
  828. package/src/daemon/__tests__/wake-conversation-ops-completion.test.ts +0 -6
  829. package/src/daemon/__tests__/web-search-status-text.test.ts +0 -29
  830. package/src/daemon/app-source-watcher.ts +17 -23
  831. package/src/daemon/chat-credential-redaction.ts +1365 -0
  832. package/src/daemon/conversation-agent-loop-handlers.ts +742 -77
  833. package/src/daemon/conversation-agent-loop.ts +10 -17
  834. package/src/daemon/conversation-error.ts +53 -16
  835. package/src/daemon/conversation-history.ts +6 -12
  836. package/src/daemon/conversation-messaging.ts +13 -1
  837. package/src/daemon/conversation-process.ts +16 -11
  838. package/src/daemon/conversation-runtime-assembly.ts +148 -52
  839. package/src/daemon/conversation-surfaces.ts +217 -100
  840. package/src/daemon/conversation-tool-setup.ts +59 -112
  841. package/src/daemon/conversation-turn-finalize.ts +18 -0
  842. package/src/daemon/conversation.ts +35 -66
  843. package/src/daemon/event-loop-watchdog.test.ts +1 -6
  844. package/src/daemon/external-plugins-bootstrap.ts +20 -74
  845. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -7
  846. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +1 -8
  847. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +1 -8
  848. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +1 -8
  849. package/src/daemon/handlers/__tests__/config-a2a.test.ts +1 -8
  850. package/src/daemon/handlers/conversation-history.ts +6 -4
  851. package/src/daemon/handlers/conversations.ts +19 -9
  852. package/src/daemon/handlers/shared.ts +24 -1
  853. package/src/daemon/inflight-message-content.ts +228 -0
  854. package/src/daemon/lifecycle.ts +21 -31
  855. package/src/daemon/message-protocol.ts +2 -7
  856. package/src/daemon/message-provenance.ts +17 -8
  857. package/src/daemon/message-types/conversations.ts +6 -17
  858. package/src/daemon/message-types/host-ui-snapshot.ts +44 -0
  859. package/src/daemon/message-types/inbox.ts +3 -46
  860. package/src/daemon/orphan-reaper.test.ts +1 -6
  861. package/src/daemon/process-message.ts +9 -0
  862. package/src/daemon/providers-setup.ts +5 -75
  863. package/src/daemon/route-conversation-post.ts +158 -0
  864. package/src/daemon/shutdown-handlers.ts +11 -17
  865. package/src/daemon/sqlite-corruption-watchdog.test.ts +0 -6
  866. package/src/documents/document-comments-store.test.ts +1 -7
  867. package/src/export/__tests__/transcript-formatter.test.ts +8 -8
  868. package/src/export/formatter.ts +8 -17
  869. package/src/export/transcript-formatter.ts +4 -1
  870. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +0 -9
  871. package/src/heartbeat/__tests__/heartbeat-service.test.ts +29 -57
  872. package/src/home/__tests__/home-content-refresh.test.ts +0 -7
  873. package/src/home/__tests__/suggested-prompts.test.ts +0 -11
  874. package/src/ipc/__tests__/contacts-info-ipc.test.ts +12 -10
  875. package/src/ipc/__tests__/email-ipc.test.ts +90 -0
  876. package/src/ipc/assistant-server.ts +10 -22
  877. package/src/ipc/routes/__tests__/guardian-label-ipc-routes.test.ts +74 -0
  878. package/src/ipc/routes/__tests__/invite-ipc-routes.test.ts +0 -7
  879. package/src/ipc/routes/guardian-label-ipc-routes.ts +42 -0
  880. package/src/live-voice/__tests__/live-voice-archive.test.ts +1 -8
  881. package/src/live-voice/__tests__/live-voice-credential-preflight.test.ts +89 -49
  882. package/src/live-voice/__tests__/live-voice-integration.test.ts +116 -2
  883. package/src/live-voice/__tests__/live-voice-resume-registry.test.ts +58 -0
  884. package/src/live-voice/__tests__/live-voice-resume-turn.test.ts +280 -0
  885. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +337 -0
  886. package/src/live-voice/__tests__/live-voice-tts.test.ts +0 -4
  887. package/src/live-voice/__tests__/live-voice-vad.test.ts +432 -12
  888. package/src/live-voice/__tests__/protocol.test.ts +122 -0
  889. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +0 -41
  890. package/src/live-voice/live-voice-credential-preflight.ts +27 -3
  891. package/src/live-voice/live-voice-resume-registry.ts +54 -0
  892. package/src/live-voice/live-voice-session.ts +602 -61
  893. package/src/live-voice/protocol.ts +143 -1
  894. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +3 -7
  895. package/src/messaging/provider-types.ts +9 -0
  896. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +0 -9
  897. package/src/messaging/providers/outlook/adapter.ts +17 -2
  898. package/src/messaging/providers/outlook/client.ts +11 -2
  899. package/src/messaging/providers/outlook/types.ts +9 -0
  900. package/src/messaging/providers/slack/__tests__/download.test.ts +1 -8
  901. package/src/messaging/providers/slack/binding-metadata.test.ts +23 -21
  902. package/src/messaging/providers/slack/send.test.ts +0 -7
  903. package/src/messaging/providers/slack/withdraw.test.ts +0 -6
  904. package/src/messaging/providers/telegram-bot/send.test.ts +0 -7
  905. package/src/monitoring/__tests__/plugin-source-watch.test.ts +40 -1
  906. package/src/monitoring/plugin-source-watch.ts +56 -62
  907. package/src/monitoring/recovery/__tests__/inflight-content.test.ts +166 -0
  908. package/src/monitoring/recovery/inflight-content.ts +260 -0
  909. package/src/monitoring/recovery/run-recovery.ts +72 -0
  910. package/src/monitoring/worker.ts +68 -7
  911. package/src/notifications/AGENTS.md +1 -1
  912. package/src/notifications/README.md +4 -4
  913. package/src/notifications/__tests__/broadcaster.test.ts +4 -17
  914. package/src/notifications/__tests__/connected-channels.test.ts +11 -8
  915. package/src/notifications/__tests__/decision-engine.test.ts +0 -7
  916. package/src/notifications/__tests__/destination-resolver.test.ts +18 -12
  917. package/src/notifications/__tests__/deterministic-checks.test.ts +1 -9
  918. package/src/notifications/adapters/macos.ts +3 -5
  919. package/src/notifications/adapters/platform.ts +1 -1
  920. package/src/notifications/broadcaster.ts +13 -4
  921. package/src/notifications/conversation-candidates.ts +21 -28
  922. package/src/notifications/copy-composer.ts +0 -6
  923. package/src/notifications/decision-engine.ts +1 -1
  924. package/src/notifications/{canonical-delivery-recorder.ts → guardian-delivery-recorder.ts} +47 -33
  925. package/src/notifications/home-feed-side-effect.ts +0 -2
  926. package/src/notifications/signal.ts +0 -4
  927. package/src/oauth/byo-connection.test.ts +0 -9
  928. package/src/oauth/connection-resolver.test.ts +18 -46
  929. package/src/oauth/seed-providers.ts +6 -0
  930. package/src/onboarding/__tests__/onboarding-events-store.test.ts +122 -44
  931. package/src/onboarding/onboarding-events-store.ts +67 -115
  932. package/src/onboarding/onboarding-research-events-store.test.ts +222 -0
  933. package/src/onboarding/onboarding-research-events-store.ts +92 -0
  934. package/src/permissions/checker.test.ts +10 -24
  935. package/src/permissions/confirmation-guardian-request.test.ts +94 -0
  936. package/src/permissions/{confirmation-canonical-request.ts → confirmation-guardian-request.ts} +44 -29
  937. package/src/permissions/gateway-threshold-reader.test.ts +0 -8
  938. package/src/permissions/prompter.ts +6 -4
  939. package/src/permissions/question-prompter.test.ts +7 -14
  940. package/src/persistence/__tests__/bulk-write-gate.test.ts +1 -7
  941. package/src/persistence/__tests__/conversation-queries-search.test.ts +0 -7
  942. package/src/persistence/__tests__/main-db-readonly-probe.ts +80 -0
  943. package/src/persistence/__tests__/main-db-readonly.test.ts +35 -0
  944. package/src/persistence/__tests__/message-content-file.test.ts +161 -52
  945. package/src/persistence/__tests__/raw-query-telemetry.test.ts +74 -0
  946. package/src/persistence/conversation-crud.ts +175 -45
  947. package/src/persistence/conversation-disk-view.ts +32 -15
  948. package/src/persistence/conversation-group-migration.test.ts +1 -8
  949. package/src/persistence/conversation-title-service.ts +59 -22
  950. package/src/persistence/db-connection.ts +46 -1
  951. package/src/persistence/db-singleton.ts +7 -1
  952. package/src/persistence/embeddings/__tests__/messages-lexical-index.test.ts +3 -7
  953. package/src/persistence/embeddings/messages-lexical-index.ts +11 -0
  954. package/src/persistence/job-handlers/__tests__/message-lexical-backfill.test.ts +0 -7
  955. package/src/persistence/job-handlers/__tests__/message-lexical-enqueue.test.ts +0 -6
  956. package/src/persistence/job-handlers/__tests__/message-lexical.test.ts +0 -7
  957. package/src/persistence/job-handlers/cleanup.ts +30 -16
  958. package/src/persistence/job-handlers/message-lexical-backfill.ts +8 -5
  959. package/src/persistence/job-handlers/message-lexical.ts +2 -2
  960. package/src/persistence/jobs-store.ts +123 -17
  961. package/src/persistence/lifecycle-events-store.test.ts +84 -0
  962. package/src/persistence/lifecycle-events-store.ts +27 -52
  963. package/src/persistence/llm-request-log-sink-clickhouse.ts +325 -0
  964. package/src/persistence/llm-request-log-store.ts +185 -106
  965. package/src/persistence/llm-request-log-writer-types.ts +80 -0
  966. package/src/persistence/llm-usage-store.ts +5 -2
  967. package/src/persistence/message-content-file.ts +182 -71
  968. package/src/persistence/message-content.ts +18 -10
  969. package/src/persistence/migrations/305-drop-contact-acl-columns.ts +3 -2
  970. package/src/persistence/migrations/325-create-config-setting-events.ts +40 -0
  971. package/src/persistence/migrations/326-move-injection-events-to-memory-db.ts +104 -0
  972. package/src/persistence/migrations/327-create-flush-checkpoints.ts +86 -0
  973. package/src/persistence/migrations/328-drop-memory-v3-learned-edge-tables.ts +16 -0
  974. package/src/persistence/migrations/329-move-onboarding-events-to-telemetry-db.test.ts +179 -0
  975. package/src/persistence/migrations/329-move-onboarding-events-to-telemetry-db.ts +119 -0
  976. package/src/persistence/migrations/330-move-auth-fallback-events-to-telemetry-db.test.ts +197 -0
  977. package/src/persistence/migrations/330-move-auth-fallback-events-to-telemetry-db.ts +108 -0
  978. package/src/persistence/migrations/331-move-lifecycle-events-to-telemetry-db.test.ts +159 -0
  979. package/src/persistence/migrations/331-move-lifecycle-events-to-telemetry-db.ts +97 -0
  980. package/src/persistence/migrations/332-move-skill-loaded-events-to-telemetry-db.test.ts +219 -0
  981. package/src/persistence/migrations/332-move-skill-loaded-events-to-telemetry-db.ts +128 -0
  982. package/src/persistence/migrations/333-create-telemetry-events-table.test.ts +66 -0
  983. package/src/persistence/migrations/333-create-telemetry-events-table.ts +43 -0
  984. package/src/persistence/migrations/334-backfill-telemetry-events-outbox.test.ts +584 -0
  985. package/src/persistence/migrations/334-backfill-telemetry-events-outbox.ts +386 -0
  986. package/src/persistence/migrations/335-collapse-memory-embed-backlog.test.ts +181 -0
  987. package/src/persistence/migrations/335-collapse-memory-embed-backlog.ts +177 -0
  988. package/src/persistence/migrations/__tests__/327-create-flush-checkpoints.test.ts +124 -0
  989. package/src/persistence/raw-query.ts +27 -1
  990. package/src/persistence/schema/calls.ts +0 -23
  991. package/src/persistence/schema/conversation-starters.ts +31 -0
  992. package/src/persistence/schema/conversations.ts +3 -3
  993. package/src/persistence/schema/guardian.ts +0 -150
  994. package/src/persistence/schema/index.ts +1 -0
  995. package/src/persistence/schema/infrastructure.ts +25 -72
  996. package/src/persistence/schema/memory-core.ts +0 -25
  997. package/src/persistence/schema/memory-injection.ts +3 -1
  998. package/src/persistence/schema/tasks.ts +1 -60
  999. package/src/persistence/steps.ts +22 -0
  1000. package/src/platform/client.test.ts +110 -0
  1001. package/src/platform/client.ts +24 -4
  1002. package/src/platform/consent-cache.test.ts +5 -18
  1003. package/src/platform/consent-cache.ts +8 -6
  1004. package/src/platform/managed-speech.test.ts +218 -0
  1005. package/src/platform/managed-speech.ts +204 -0
  1006. package/src/plugin-api/conversation-turn.ts +250 -0
  1007. package/src/plugin-api/index.ts +21 -0
  1008. package/src/plugin-api/model-profiles.test.ts +29 -28
  1009. package/src/plugin-api/vision-support.test.ts +25 -38
  1010. package/src/plugin-api/vision-support.ts +26 -17
  1011. package/src/plugins/AGENTS.md +8 -0
  1012. package/src/plugins/__tests__/source-fingerprint.test.ts +41 -0
  1013. package/src/plugins/collect-source-versions.ts +77 -0
  1014. package/src/plugins/defaults/image-recovery/detect.ts +32 -10
  1015. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +20 -18
  1016. package/src/plugins/defaults/image-recovery/recover.ts +86 -59
  1017. package/src/plugins/defaults/memory/__tests__/activation-session-store.test.ts +1 -8
  1018. package/src/plugins/defaults/memory/__tests__/clear-stale-processing-flags.test.ts +1 -8
  1019. package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +1 -8
  1020. package/src/plugins/defaults/memory/__tests__/conversation-row-batch-delete.test.ts +7 -18
  1021. package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +22 -18
  1022. package/src/plugins/defaults/memory/__tests__/embedding-cache.test.ts +0 -6
  1023. package/src/plugins/defaults/memory/__tests__/find-most-recent-retrospective-for.test.ts +1 -8
  1024. package/src/plugins/defaults/memory/__tests__/fork-message-copy.test.ts +7 -18
  1025. package/src/plugins/defaults/memory/__tests__/jobs-store-enqueue-gate.test.ts +29 -39
  1026. package/src/plugins/defaults/memory/__tests__/jobs-worker-memory-disabled.test.ts +11 -26
  1027. package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +3 -22
  1028. package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-schedule.test.ts +215 -1
  1029. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +0 -7
  1030. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +114 -16
  1031. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +180 -0
  1032. package/src/plugins/defaults/memory/__tests__/memory-retrospective-skill-card.test.ts +153 -519
  1033. package/src/plugins/defaults/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +0 -7
  1034. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +6 -17
  1035. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +0 -7
  1036. package/src/plugins/defaults/memory/__tests__/memory-v2-activation-log-store.test.ts +1 -19
  1037. package/src/plugins/defaults/memory/__tests__/memory-v2-concept-frequency.test.ts +0 -18
  1038. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +8 -7
  1039. package/src/plugins/defaults/memory/__tests__/table-relocation.test.ts +49 -0
  1040. package/src/{persistence → plugins/defaults/memory}/__tests__/worker-control.test.ts +8 -10
  1041. package/src/plugins/defaults/memory/graph/capability-seed.ts +4 -6
  1042. package/src/plugins/defaults/memory/graph/extraction.ts +5 -1
  1043. package/src/plugins/defaults/memory/graph/graph-search.test.ts +2 -8
  1044. package/src/plugins/defaults/memory/graph/tool-handlers.test.ts +0 -6
  1045. package/src/plugins/defaults/memory/graph/tool-handlers.ts +2 -2
  1046. package/src/plugins/defaults/memory/graph/tools.ts +47 -13
  1047. package/src/plugins/defaults/memory/graph-topology/__tests__/build-memory-graph.test.ts +81 -2
  1048. package/src/plugins/defaults/memory/graph-topology/build-memory-graph.ts +67 -32
  1049. package/src/plugins/defaults/memory/hooks/shutdown.ts +8 -11
  1050. package/src/plugins/defaults/memory/indexer.ts +2 -1
  1051. package/src/plugins/defaults/memory/job-handlers/backfill.ts +8 -5
  1052. package/src/plugins/defaults/memory/job-handlers/embedding.test.ts +0 -7
  1053. package/src/plugins/defaults/memory/job-handlers/embedding.ts +2 -2
  1054. package/src/plugins/defaults/memory/job-handlers/index-maintenance.ts +7 -2
  1055. package/src/plugins/defaults/memory/jobs/__tests__/embed-concept-page.test.ts +73 -8
  1056. package/src/plugins/defaults/memory/jobs/embed-concept-page.ts +5 -3
  1057. package/src/plugins/defaults/memory/jobs/embed-pkb-file.test.ts +0 -13
  1058. package/src/plugins/defaults/memory/jobs-worker.ts +139 -107
  1059. package/src/plugins/defaults/memory/memory-db.ts +27 -0
  1060. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +4 -4
  1061. package/src/plugins/defaults/memory/memory-retrospective-job.ts +56 -180
  1062. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +216 -0
  1063. package/src/plugins/defaults/memory/memory-retrospective-skill-card.ts +122 -202
  1064. package/src/plugins/defaults/memory/message-media.ts +2 -2
  1065. package/src/plugins/defaults/memory/paths.ts +3 -2
  1066. package/src/plugins/defaults/memory/pkb/pkb-index.test.ts +0 -12
  1067. package/src/plugins/defaults/memory/pkb/pkb-reconcile.test.ts +0 -9
  1068. package/src/plugins/defaults/memory/pkb/pkb-search.test.ts +0 -6
  1069. package/src/plugins/defaults/memory/routes/__tests__/memory-v2-simulate-route.test.ts +15 -92
  1070. package/src/plugins/defaults/memory/routes/__tests__/memory-worker-routes.test.ts +201 -0
  1071. package/src/plugins/defaults/memory/routes/memory-item-routes.test.ts +7 -21
  1072. package/src/plugins/defaults/memory/routes/memory-v2-routes.ts +3 -8
  1073. package/src/plugins/defaults/memory/routes/memory-worker-routes.ts +162 -0
  1074. package/src/plugins/defaults/memory/startup.ts +6 -7
  1075. package/src/plugins/defaults/memory/task-memory-cleanup.ts +11 -22
  1076. package/src/plugins/defaults/memory/tools.test.ts +40 -8
  1077. package/src/plugins/defaults/memory/tools.ts +12 -2
  1078. package/src/plugins/defaults/memory/v2/__tests__/activation.test.ts +0 -19
  1079. package/src/plugins/defaults/memory/v2/__tests__/backfill-jobs.test.ts +63 -40
  1080. package/src/plugins/defaults/memory/v2/__tests__/cli-command-store.test.ts +61 -130
  1081. package/src/plugins/defaults/memory/v2/__tests__/consolidation-job.test.ts +406 -10
  1082. package/src/plugins/defaults/memory/v2/__tests__/harness-compare.test.ts +1 -10
  1083. package/src/plugins/defaults/memory/v2/__tests__/harness-oracle.test.ts +1 -10
  1084. package/src/plugins/defaults/memory/v2/__tests__/harness-replay-input.test.ts +1 -10
  1085. package/src/plugins/defaults/memory/v2/__tests__/injection-events.test.ts +61 -69
  1086. package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +0 -19
  1087. package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +11 -10
  1088. package/src/plugins/defaults/memory/v2/__tests__/page-index.test.ts +0 -5
  1089. package/src/plugins/defaults/memory/v2/__tests__/qdrant.test.ts +6 -22
  1090. package/src/plugins/defaults/memory/v2/__tests__/reranker.test.ts +3 -5
  1091. package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +7 -20
  1092. package/src/plugins/defaults/memory/v2/__tests__/sim.test.ts +0 -24
  1093. package/src/plugins/defaults/memory/v2/__tests__/skill-content.test.ts +17 -11
  1094. package/src/plugins/defaults/memory/v2/__tests__/skill-store.test.ts +0 -16
  1095. package/src/plugins/defaults/memory/v2/__tests__/static-context.test.ts +0 -10
  1096. package/src/plugins/defaults/memory/v2/__tests__/sweep-job.test.ts +0 -6
  1097. package/src/plugins/defaults/memory/v2/backfill-jobs.ts +5 -2
  1098. package/src/plugins/defaults/memory/v2/cli-command-content.ts +43 -12
  1099. package/src/plugins/defaults/memory/v2/cli-command-store.ts +34 -50
  1100. package/src/plugins/defaults/memory/v2/consolidation-job.ts +216 -10
  1101. package/src/plugins/defaults/memory/v2/harness/router-retriever.ts +1 -8
  1102. package/src/plugins/defaults/memory/v2/injection-events.ts +16 -14
  1103. package/src/plugins/defaults/memory/v2/injection.ts +1 -2
  1104. package/src/plugins/defaults/memory/v2/memory-v2-startup.test.ts +0 -5
  1105. package/src/plugins/defaults/memory/v2/migration.ts +55 -24
  1106. package/src/plugins/defaults/memory/v2/prompts/consolidation.ts +4 -0
  1107. package/src/plugins/defaults/memory/v2/router.ts +4 -14
  1108. package/src/plugins/defaults/memory/v2/sweep-job.ts +1 -0
  1109. package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +10 -22
  1110. package/src/plugins/defaults/memory/v3/__tests__/dense.test.ts +0 -5
  1111. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +18 -20
  1112. package/src/plugins/defaults/memory/v3/__tests__/section-dense-store.test.ts +0 -5
  1113. package/src/plugins/defaults/memory/v3/__tests__/selection-log-store.test.ts +5 -9
  1114. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +51 -31
  1115. package/src/plugins/defaults/memory/v3/prune.test.ts +3 -11
  1116. package/src/{persistence → plugins/defaults/memory}/worker-control.ts +12 -21
  1117. package/src/plugins/defaults/memory/worker.ts +19 -2
  1118. package/src/plugins/mtime-cache.ts +166 -70
  1119. package/src/plugins/plugin-tree-walk.ts +41 -4
  1120. package/src/plugins/source-fingerprint.ts +9 -3
  1121. package/src/plugins/surface-import.ts +28 -0
  1122. package/src/plugins/types.ts +9 -23
  1123. package/src/prompts/__tests__/system-prompt.test.ts +0 -28
  1124. package/src/prompts/__tests__/task-progress-hint-section.test.ts +0 -28
  1125. package/src/providers/__tests__/connection-model-compat.test.ts +6 -19
  1126. package/src/providers/__tests__/count-tokens-forwarding.test.ts +1 -21
  1127. package/src/providers/__tests__/dispatch-connection-routing.test.ts +9 -26
  1128. package/src/providers/__tests__/registry-native-web-search.test.ts +4 -10
  1129. package/src/providers/__tests__/retry-callsite.test.ts +240 -235
  1130. package/src/providers/__tests__/satellite-connection-routing.test.ts +11 -36
  1131. package/src/providers/__tests__/vellum-mismatch-routing.test.ts +0 -5
  1132. package/src/providers/anthropic/client.ts +9 -3
  1133. package/src/providers/call-site-routing.ts +7 -3
  1134. package/src/providers/connection-resolution.ts +9 -11
  1135. package/src/providers/content-block-schema.ts +149 -0
  1136. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +7 -11
  1137. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +1 -15
  1138. package/src/providers/inference/auth.ts +23 -0
  1139. package/src/providers/inference/backfill.ts +25 -9
  1140. package/src/providers/model-catalog.ts +213 -5
  1141. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +54 -0
  1142. package/src/providers/openai/chat-completions-provider.ts +13 -2
  1143. package/src/providers/openai/responses-provider.ts +232 -5
  1144. package/src/providers/openrouter/client.ts +53 -0
  1145. package/src/providers/retry.ts +25 -2
  1146. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +13 -0
  1147. package/src/providers/speech-to-text/__tests__/resolve.test.ts +211 -74
  1148. package/src/providers/speech-to-text/__tests__/vellum-managed-realtime.test.ts +408 -0
  1149. package/src/providers/speech-to-text/__tests__/vellum-managed.test.ts +121 -0
  1150. package/src/providers/speech-to-text/__tests__/vellum-speech-relay-connection.test.ts +126 -0
  1151. package/src/providers/speech-to-text/deepgram-realtime.ts +167 -37
  1152. package/src/providers/speech-to-text/incremental-batch-stream.ts +413 -0
  1153. package/src/providers/speech-to-text/openai-whisper-stream.ts +16 -363
  1154. package/src/providers/speech-to-text/provider-catalog.ts +26 -1
  1155. package/src/providers/speech-to-text/resolve.ts +74 -13
  1156. package/src/providers/speech-to-text/vellum-managed-realtime.ts +309 -0
  1157. package/src/providers/speech-to-text/vellum-managed.ts +71 -0
  1158. package/src/providers/speech-to-text/vellum-speech-relay-connection.ts +174 -0
  1159. package/src/providers/types.ts +23 -4
  1160. package/src/providers/vellum-model-routing.test.ts +26 -0
  1161. package/src/providers/vellum-model-routing.ts +30 -0
  1162. package/src/runtime/AGENTS.md +1 -1
  1163. package/src/runtime/__tests__/actor-trust-resolver.test.ts +2 -8
  1164. package/src/runtime/__tests__/agent-wake.test.ts +100 -27
  1165. package/src/runtime/__tests__/background-job-runner.test.ts +22 -0
  1166. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +0 -5
  1167. package/src/runtime/__tests__/member-verdict-cache.test.ts +2 -9
  1168. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +22 -5
  1169. package/src/runtime/access-request-helper.ts +84 -60
  1170. package/src/runtime/actor-trust-resolver.ts +2 -2
  1171. package/src/runtime/agent-wake.ts +75 -26
  1172. package/src/runtime/assistant-event-hub.ts +1 -0
  1173. package/src/runtime/auth/__tests__/middleware.test.ts +1 -10
  1174. package/src/runtime/auth/__tests__/route-policy.test.ts +0 -7
  1175. package/src/runtime/auth/__tests__/token-service.test.ts +1 -8
  1176. package/src/runtime/auth/same-actor.ts +2 -1
  1177. package/src/runtime/background-job-runner.ts +22 -0
  1178. package/src/runtime/channel-approval-types.ts +1 -1
  1179. package/src/runtime/channel-reply-delivery.ts +38 -19
  1180. package/src/runtime/confirmation-request-guardian-bridge.ts +45 -39
  1181. package/src/runtime/for-chat-mint-registry.ts +118 -0
  1182. package/src/runtime/guardian-action-service.ts +32 -27
  1183. package/src/runtime/guardian-decision-types.ts +2 -2
  1184. package/src/runtime/guardian-reply-router.ts +80 -98
  1185. package/src/runtime/http-server.ts +61 -41
  1186. package/src/runtime/introduction-policy.ts +2 -2
  1187. package/src/runtime/local-actor-identity.ts +19 -7
  1188. package/src/runtime/pending-interactions.ts +1 -0
  1189. package/src/runtime/reveal-nonce.ts +49 -0
  1190. package/src/runtime/reveal-success-registry.ts +306 -0
  1191. package/src/runtime/routes/__tests__/acp-routes.test.ts +0 -7
  1192. package/src/runtime/routes/__tests__/backup-routes.test.ts +195 -125
  1193. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +4 -1
  1194. package/src/runtime/routes/__tests__/client-routes.test.ts +0 -7
  1195. package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +1 -19
  1196. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +1 -8
  1197. package/src/runtime/routes/__tests__/contact-routes-merge-relay.test.ts +186 -0
  1198. package/src/runtime/routes/__tests__/contact-routes-update-channel-relay.test.ts +2 -3
  1199. package/src/runtime/routes/__tests__/content-source-routes.test.ts +0 -7
  1200. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +5 -27
  1201. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +0 -7
  1202. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +12 -30
  1203. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +59 -56
  1204. package/src/runtime/routes/__tests__/conversation-surface-routes.test.ts +0 -7
  1205. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +53 -73
  1206. package/src/runtime/routes/__tests__/gateway-status-routes.test.ts +84 -0
  1207. package/src/runtime/routes/__tests__/inference-callsites-routes.test.ts +2 -10
  1208. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +58 -90
  1209. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +161 -93
  1210. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +6 -1
  1211. package/src/runtime/routes/__tests__/monitoring-routes.test.ts +0 -8
  1212. package/src/runtime/routes/__tests__/plugins-routes.test.ts +218 -64
  1213. package/src/runtime/routes/__tests__/question-routes.test.ts +2 -4
  1214. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +1 -8
  1215. package/src/runtime/routes/__tests__/sanity-routes.test.ts +0 -7
  1216. package/src/runtime/routes/__tests__/schedule-routes-create.test.ts +2 -1
  1217. package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +16 -56
  1218. package/src/runtime/routes/__tests__/stt-routes.test.ts +0 -9
  1219. package/src/runtime/routes/__tests__/suggest-trust-rule-routes.test.ts +0 -7
  1220. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -0
  1221. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +354 -1
  1222. package/src/runtime/routes/__tests__/tts-routes.test.ts +0 -11
  1223. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +163 -10
  1224. package/src/runtime/routes/__tests__/user-routes-cli.test.ts +154 -0
  1225. package/src/runtime/routes/__tests__/wake-conversation-routes.test.ts +0 -7
  1226. package/src/runtime/routes/acp-routes.ts +21 -9
  1227. package/src/runtime/routes/app-management-routes.ts +231 -60
  1228. package/src/runtime/routes/app-routes.ts +130 -83
  1229. package/src/runtime/routes/auth-routes.ts +3 -1
  1230. package/src/runtime/routes/background-wake-routes.ts +0 -1
  1231. package/src/runtime/routes/cache-routes.ts +3 -3
  1232. package/src/runtime/routes/call-routes.ts +5 -1
  1233. package/src/runtime/routes/channel-verification-routes.ts +2 -1
  1234. package/src/runtime/routes/contact-prompt-routes.ts +11 -5
  1235. package/src/runtime/routes/contact-routes.ts +34 -42
  1236. package/src/runtime/routes/conversation-attention-routes.ts +1 -1
  1237. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  1238. package/src/runtime/routes/conversation-query-routes.ts +4 -4
  1239. package/src/runtime/routes/conversation-routes.ts +111 -72
  1240. package/src/runtime/routes/credential-routes.ts +116 -3
  1241. package/src/runtime/routes/email-routes.ts +17 -1
  1242. package/src/runtime/routes/events-routes.ts +1 -0
  1243. package/src/runtime/routes/gateway-status-routes.ts +69 -0
  1244. package/src/runtime/routes/guardian-action-routes.ts +18 -16
  1245. package/src/runtime/routes/guardian-approval-interception.ts +6 -4
  1246. package/src/runtime/routes/guardian-approval-prompt.ts +7 -5
  1247. package/src/runtime/routes/guardian-expiry-sweep.ts +130 -0
  1248. package/src/runtime/routes/inbound-message-handler.ts +9 -33
  1249. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -5
  1250. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +9 -9
  1251. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +24 -6
  1252. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +0 -7
  1253. package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -5
  1254. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +6 -3
  1255. package/src/runtime/routes/inbound-stages/edit-intercept.ts +5 -1
  1256. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +0 -9
  1257. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +4 -1
  1258. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +3 -12
  1259. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +16 -14
  1260. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +8 -2
  1261. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +15 -7
  1262. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +0 -9
  1263. package/src/runtime/routes/index.ts +5 -5
  1264. package/src/runtime/routes/inference-provider-connection-routes.ts +73 -24
  1265. package/src/runtime/routes/inference-send-routes.ts +1 -1
  1266. package/src/runtime/routes/internal-telemetry-routes.ts +11 -2
  1267. package/src/runtime/routes/oauth-connect-routes.ts +27 -7
  1268. package/src/runtime/routes/platform-routes.ts +4 -14
  1269. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +0 -4
  1270. package/src/runtime/routes/playground/__tests__/guard.test.ts +0 -4
  1271. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +0 -14
  1272. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +0 -21
  1273. package/src/runtime/routes/playground/__tests__/seed-conversation.test.ts +0 -4
  1274. package/src/runtime/routes/playground/__tests__/seeded-conversations.test.ts +16 -18
  1275. package/src/runtime/routes/playground/__tests__/state.test.ts +0 -14
  1276. package/src/runtime/routes/playground/seed-conversation.ts +5 -4
  1277. package/src/runtime/routes/playground/seeded-conversations.ts +3 -2
  1278. package/src/runtime/routes/plugins-routes.ts +163 -25
  1279. package/src/runtime/routes/profiler-routes.ts +2 -5
  1280. package/src/runtime/routes/publish-routes.ts +15 -18
  1281. package/src/runtime/routes/question-routes.ts +8 -4
  1282. package/src/runtime/routes/schedule-routes.ts +20 -78
  1283. package/src/runtime/routes/schedule-worker-routes.ts +16 -84
  1284. package/src/runtime/routes/secret-routes.ts +14 -0
  1285. package/src/runtime/routes/settings-routes.ts +21 -25
  1286. package/src/runtime/routes/subagents-routes.ts +17 -18
  1287. package/src/runtime/routes/surface-action-routes.ts +18 -39
  1288. package/src/runtime/routes/surface-content-routes.ts +59 -1
  1289. package/src/runtime/routes/surface-conversation-resolver.ts +148 -4
  1290. package/src/runtime/routes/telemetry-routes.ts +74 -1
  1291. package/src/runtime/routes/tts-routes.ts +9 -5
  1292. package/src/runtime/routes/ui-snapshot-routes.test.ts +219 -0
  1293. package/src/runtime/routes/ui-snapshot-routes.ts +313 -0
  1294. package/src/runtime/routes/user-route-dispatcher.ts +52 -53
  1295. package/src/runtime/routes/user-route-resolution.ts +141 -0
  1296. package/src/runtime/routes/user-routes-cli.ts +56 -35
  1297. package/src/runtime/routes/user-routes.ts +9 -2
  1298. package/src/runtime/routes/vellum-actor-trust.ts +67 -0
  1299. package/src/runtime/services/__tests__/auto-analysis-guard.test.ts +1 -8
  1300. package/src/runtime/tool-grant-request-helper.ts +34 -30
  1301. package/src/runtime/trust-verdict-consumer.ts +4 -6
  1302. package/src/schedule/__tests__/worker-control.test.ts +3 -3
  1303. package/src/schedule/schedule-store.ts +0 -41
  1304. package/src/schedule/scheduler.ts +38 -257
  1305. package/src/schedule/worker-control.ts +11 -21
  1306. package/src/schedule/worker.ts +18 -5
  1307. package/src/security/__tests__/oauth2-device-code.test.ts +12 -60
  1308. package/src/security/__tests__/provider-key-env-fallback.test.ts +0 -10
  1309. package/src/security/auth-fallback-events-store.ts +34 -72
  1310. package/src/security/secret-scanner.ts +26 -1
  1311. package/src/sequence/engine.ts +15 -6
  1312. package/src/services/published-app-updater.ts +6 -11
  1313. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +24 -4
  1314. package/src/stt/daemon-batch-transcriber.ts +33 -2
  1315. package/src/stt/stt-stream-session.ts +17 -11
  1316. package/src/stt/types.ts +2 -1
  1317. package/src/subagent/manager.ts +4 -3
  1318. package/src/telemetry/AGENTS.md +44 -0
  1319. package/src/telemetry/__tests__/config-setting-events-store.test.ts +63 -0
  1320. package/src/telemetry/__tests__/config-setting-snapshot.test.ts +107 -0
  1321. package/src/telemetry/__tests__/flush-checkpoints.test.ts +56 -0
  1322. package/src/telemetry/__tests__/outbox-test-harness.ts +83 -0
  1323. package/src/telemetry/__tests__/telemetry-event-fixtures.ts +258 -0
  1324. package/src/telemetry/__tests__/watchdog-events-store.test.ts +32 -122
  1325. package/src/telemetry/config-setting-events-store.ts +40 -0
  1326. package/src/telemetry/config-setting-snapshot.ts +135 -0
  1327. package/src/telemetry/flush-checkpoints.ts +70 -0
  1328. package/src/telemetry/skill-loaded-events-store.test.ts +54 -113
  1329. package/src/telemetry/skill-loaded-events-store.ts +21 -77
  1330. package/src/telemetry/telemetry-event-sources.test.ts +211 -0
  1331. package/src/telemetry/telemetry-event-sources.ts +582 -0
  1332. package/src/telemetry/telemetry-events-outbox.test.ts +330 -0
  1333. package/src/telemetry/telemetry-events-outbox.ts +204 -0
  1334. package/src/telemetry/telemetry-main-db.ts +48 -0
  1335. package/src/telemetry/telemetry-wire-source.json +4 -0
  1336. package/src/telemetry/telemetry-wire-validation.test.ts +157 -0
  1337. package/src/telemetry/telemetry-wire-validation.ts +125 -0
  1338. package/src/telemetry/telemetry-wire.generated.ts +433 -0
  1339. package/src/telemetry/tool-executed-events-store.test.ts +0 -8
  1340. package/src/telemetry/tool-executed-events-store.ts +2 -2
  1341. package/src/telemetry/turn-trace-store.test.ts +86 -7
  1342. package/src/telemetry/turn-trace-store.ts +29 -1
  1343. package/src/telemetry/types.test.ts +38 -0
  1344. package/src/telemetry/types.ts +302 -87
  1345. package/src/telemetry/usage-telemetry-reporter.test.ts +921 -364
  1346. package/src/telemetry/usage-telemetry-reporter.ts +235 -722
  1347. package/src/telemetry/watchdog-direct-emit.test.ts +0 -5
  1348. package/src/telemetry/watchdog-direct-emit.ts +7 -1
  1349. package/src/telemetry/watchdog-events-store.ts +11 -70
  1350. package/src/tools/acp/spawn.test.ts +0 -7
  1351. package/src/tools/apps/definitions.ts +1 -7
  1352. package/src/tools/apps/executors.ts +11 -36
  1353. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +11 -24
  1354. package/src/tools/browser/__tests__/browser-status.test.ts +47 -33
  1355. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +1 -11
  1356. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +75 -58
  1357. package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +0 -10
  1358. package/src/tools/browser/network-recorder.test.ts +0 -11
  1359. package/src/tools/document/document-comment-tool.test.ts +1 -7
  1360. package/src/tools/host-terminal/host-shell.test.ts +0 -32
  1361. package/src/tools/host-terminal/host-shell.ts +6 -2
  1362. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +13 -15
  1363. package/src/tools/network/__tests__/web-search-metadata.test.ts +15 -18
  1364. package/src/tools/network/__tests__/web-search.test.ts +39 -50
  1365. package/src/tools/registry.ts +148 -196
  1366. package/src/tools/skills/find-similar-skills.test.ts +0 -7
  1367. package/src/tools/skills/sandbox-runner.ts +5 -2
  1368. package/src/tools/skills/scaffold-managed.ts +105 -12
  1369. package/src/tools/subagent/read.ts +6 -4
  1370. package/src/tools/subagent/spawn.ts +20 -29
  1371. package/src/tools/terminal/safe-env.ts +7 -2
  1372. package/src/tools/terminal/shell.test.ts +4 -8
  1373. package/src/tools/terminal/shell.ts +5 -2
  1374. package/src/tools/tool-approval-handler.ts +39 -23
  1375. package/src/tools/tool-input-summary.ts +2 -1
  1376. package/src/tools/tool-manifest.ts +20 -6
  1377. package/src/tools/tool-profiler.ts +37 -6
  1378. package/src/tools/ui-surface/definitions.ts +1 -7
  1379. package/src/tools/workflows/run-workflow.test.ts +8 -22
  1380. package/src/tts/__tests__/provider-adapters.test.ts +28 -22
  1381. package/src/tts/__tests__/provider-resolution.test.ts +26 -0
  1382. package/src/tts/__tests__/synthesis-stream.test.ts +6 -6
  1383. package/src/tts/__tests__/vellum-provider.test.ts +251 -0
  1384. package/src/tts/provider-catalog.ts +6 -0
  1385. package/src/tts/providers/__tests__/vellum-tts-socket.test.ts +266 -0
  1386. package/src/tts/providers/vellum-provider.ts +220 -0
  1387. package/src/tts/providers/vellum-tts-socket.ts +377 -0
  1388. package/src/tts/synthesize-text.ts +15 -5
  1389. package/src/tts/tts-config-resolver.ts +2 -7
  1390. package/src/tts/types.ts +1 -0
  1391. package/src/usage/attribution.ts +20 -114
  1392. package/src/util/abort-reasons.ts +1 -3
  1393. package/src/util/image-conversion.ts +23 -5
  1394. package/src/util/logger.ts +8 -3
  1395. package/src/{config/bundled-skills/messaging/tools/gmail-mime-helpers.ts → util/mime-type.ts} +4 -1
  1396. package/src/util/platform.ts +0 -60
  1397. package/src/util/pricing.ts +10 -1
  1398. package/src/util/telemetry-db-path.ts +9 -7
  1399. package/src/watcher/__tests__/telemetry.test.ts +41 -11
  1400. package/src/watcher/providers/google-calendar.ts +81 -49
  1401. package/src/watcher/telemetry.ts +22 -5
  1402. package/src/workflows/engine-integration.test.ts +0 -6
  1403. package/src/workflows/engine.test.ts +1 -9
  1404. package/src/workflows/journal-store.test.ts +1 -9
  1405. package/src/workflows/leaf-runner.test.ts +17 -50
  1406. package/src/workflows/library.test.ts +1 -9
  1407. package/src/workflows/run-manager.test.ts +1 -9
  1408. package/src/workspace/git-service.ts +932 -37
  1409. package/src/workspace/provider-commit-message-generator.ts +6 -4
  1410. package/__tests__/permissions/gateway-threshold-reader.test.ts +0 -450
  1411. package/src/__tests__/canonical-guardian-store.test.ts +0 -928
  1412. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +0 -140
  1413. package/src/__tests__/plugin-route-contribution.test.ts +0 -245
  1414. package/src/__tests__/skill-runtime-path.test.ts +0 -63
  1415. package/src/__tests__/task-compiler.test.ts +0 -317
  1416. package/src/__tests__/task-management-tools.test.ts +0 -1128
  1417. package/src/__tests__/task-scheduler.test.ts +0 -786
  1418. package/src/config/schemas/schedules.ts +0 -35
  1419. package/src/contacts/canonical-guardian-store.ts +0 -902
  1420. package/src/daemon/message-types/work-items.ts +0 -243
  1421. package/src/runtime/routes/__tests__/memory-worker-routes.test.ts +0 -306
  1422. package/src/runtime/routes/__tests__/task-routes.test.ts +0 -644
  1423. package/src/runtime/routes/assets/vellum-design-system.css +0 -1959
  1424. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +0 -129
  1425. package/src/runtime/routes/inbound-stages/escalation-intercept.test.ts +0 -158
  1426. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +0 -187
  1427. package/src/runtime/routes/memory-worker-routes.ts +0 -227
  1428. package/src/runtime/routes/task-routes.ts +0 -380
  1429. package/src/runtime/routes/work-items-routes.test.ts +0 -75
  1430. package/src/runtime/routes/work-items-routes.ts +0 -881
  1431. package/src/runtime/skill-route-registry.ts +0 -131
  1432. package/src/tasks/SPEC.md +0 -132
  1433. package/src/tasks/task-compiler.ts +0 -194
  1434. package/src/tasks/task-runner.ts +0 -119
  1435. package/src/tasks/task-scheduler.ts +0 -22
  1436. package/src/tasks/task-store.ts +0 -153
  1437. package/src/tasks/tool-sanitizer.ts +0 -38
  1438. package/src/tools/apps/registry.ts +0 -16
  1439. package/src/tools/system/register.ts +0 -9
  1440. package/src/tools/tasks/task-delete.ts +0 -121
  1441. package/src/tools/tasks/task-list.ts +0 -56
  1442. package/src/tools/tasks/task-run.ts +0 -122
  1443. package/src/tools/tasks/task-save.ts +0 -64
  1444. package/src/tools/tasks/work-item-enqueue.ts +0 -300
  1445. package/src/tools/tasks/work-item-list.ts +0 -65
  1446. package/src/tools/tasks/work-item-remove.ts +0 -94
  1447. package/src/tools/tasks/work-item-run.ts +0 -99
  1448. package/src/tools/tasks/work-item-update.ts +0 -166
  1449. package/src/tools/ui-surface/registry.ts +0 -14
  1450. package/src/work-items/resolve-required-tools.test.ts +0 -37
  1451. package/src/work-items/resolve-required-tools.ts +0 -26
  1452. package/src/work-items/work-item-runner.ts +0 -187
  1453. package/src/work-items/work-item-store.ts +0 -416
@@ -11,17 +11,20 @@ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
11
11
  // Module mocks (must precede production imports)
12
12
  // ---------------------------------------------------------------------------
13
13
 
14
- const mockGetMemoryCheckpoint = mock<(key: string) => string | null>(
15
- () => null,
16
- );
17
- const mockSetMemoryCheckpoint = mock<(key: string, value: string) => void>(
14
+ // Watermark storage fake, injected via the reporter's constructor (see
15
+ // `makeReporter` below) — the real store lives on the telemetry DB and has
16
+ // its own DB-backed test, so it is deliberately NOT module-mocked here.
17
+ const mockGetFlushCheckpoint = mock<(key: string) => string | null>(() => null);
18
+ const mockSetFlushCheckpoint = mock<(key: string, value: string) => void>(
18
19
  () => {},
19
20
  );
21
+ const mockIsFlushCheckpointStoreAvailable = mock<() => boolean>(() => true);
20
22
 
21
- mock.module("../persistence/checkpoints.js", () => ({
22
- getMemoryCheckpoint: mockGetMemoryCheckpoint,
23
- setMemoryCheckpoint: mockSetMemoryCheckpoint,
24
- }));
23
+ const fakeFlushCheckpointStore = {
24
+ isAvailable: () => mockIsFlushCheckpointStoreAvailable(),
25
+ get: (key: string) => mockGetFlushCheckpoint(key),
26
+ set: (key: string, value: string) => mockSetFlushCheckpoint(key, value),
27
+ };
25
28
 
26
29
  const mockQueryUnreportedUsageEvents = mock(
27
30
  () =>
@@ -84,13 +87,6 @@ mock.module("../util/device-id.js", () => ({
84
87
  getDeviceId: mockGetDeviceId,
85
88
  }));
86
89
 
87
- mock.module("../util/logger.js", () => ({
88
- getLogger: () =>
89
- new Proxy({} as Record<string, unknown>, {
90
- get: () => () => {},
91
- }),
92
- }));
93
-
94
90
  mock.module("../version.js", () => ({
95
91
  APP_VERSION: "1.2.3-test",
96
92
  }));
@@ -100,7 +96,7 @@ const mockGetCachedShareAnalytics = mock(() => true);
100
96
  const mockGetCachedShareDiagnostics = mock(() => false);
101
97
  // Owner's accepted diagnostics-consent version — the disclosing-version part of
102
98
  // the trace gate. Default to a far-future (unconditionally eligible) version so
103
- // the consent/flag cases drive eligibility on those axes; the version-specific
99
+ // the consent cases drive eligibility on that axis; the version-specific
104
100
  // cases override it with old/empty values.
105
101
  const mockGetCachedShareDiagnosticsVersion = mock(() => "2999-01-01");
106
102
 
@@ -110,28 +106,14 @@ mock.module("../platform/consent-cache.js", () => ({
110
106
  getCachedShareDiagnosticsVersion: mockGetCachedShareDiagnosticsVersion,
111
107
  }));
112
108
 
113
- // The `trace-collection` feature flag — the other half of the trace gate.
114
- const mockIsAssistantFeatureFlagEnabled = mock(
115
- (_key: string, _config: unknown): boolean => true,
116
- );
117
-
118
- mock.module("../config/assistant-feature-flags.js", () => ({
119
- isAssistantFeatureFlagEnabled: mockIsAssistantFeatureFlagEnabled,
120
- }));
121
-
122
- // Stub config — the flag checker is mocked, so the contents don't matter; this
123
- // just keeps `getConfig()` from touching the real loader / filesystem.
124
- mock.module("../config/loader.js", () => ({
125
- getConfig: () => ({}),
126
- }));
127
-
128
109
  interface MockTurnTrace {
129
- schema_version: 2;
110
+ schema_version: 3;
130
111
  messages: {
131
112
  id: string;
132
113
  role: string;
133
114
  created_at: number;
134
115
  content: unknown;
116
+ model: string | null;
135
117
  }[];
136
118
  tool_calls: unknown[];
137
119
  system_prompt: string | null;
@@ -150,13 +132,14 @@ function defaultBoundedTurnTrace(boundary: {
150
132
  userMessageCreatedAt: number;
151
133
  }): MockTurnTrace | null {
152
134
  return {
153
- schema_version: 2,
135
+ schema_version: 3,
154
136
  messages: [
155
137
  {
156
138
  id: boundary.userMessageId,
157
139
  role: "user",
158
140
  created_at: boundary.userMessageCreatedAt,
159
141
  content: [{ type: "text", text: "hello" }],
142
+ model: null,
160
143
  },
161
144
  ],
162
145
  tool_calls: [],
@@ -188,49 +171,11 @@ mock.module("./turn-trace-store.js", () => ({
188
171
  isTurnSettled: mockIsTurnSettled,
189
172
  }));
190
173
 
191
- const mockQueryUnreportedLifecycleEvents = mock(
192
- () =>
193
- [] as {
194
- id: string;
195
- eventName: string;
196
- createdAt: number;
197
- }[],
198
- );
199
-
200
- mock.module("../persistence/lifecycle-events-store.js", () => ({
201
- queryUnreportedLifecycleEvents: mockQueryUnreportedLifecycleEvents,
202
- }));
203
-
204
- const mockQueryUnreportedOnboardingEvents = mock(
205
- () =>
206
- [] as {
207
- id: string;
208
- createdAt: number;
209
- screen: string;
210
- toolsJson: string | null;
211
- tasksJson: string | null;
212
- tone: string | null;
213
- googleConnected: boolean | null;
214
- googleScopesJson: string | null;
215
- priorAssistantsJson: string | null;
216
- abVariant: string | null;
217
- sessionId: string | null;
218
- stepName: string | null;
219
- stepIndex: number | null;
220
- completedAt: string | null;
221
- funnelVersion: string | null;
222
- }[],
223
- );
224
-
225
- mock.module("../onboarding/onboarding-events-store.js", () => ({
226
- queryUnreportedOnboardingEvents: mockQueryUnreportedOnboardingEvents,
227
- }));
228
-
229
- // The auth-fallback, tool-executed, and skill-loaded stores are intentionally
230
- // NOT mocked — they have their own DB-backed tests, and Bun's `mock.module`
231
- // is process-global, so mocking them here would leak into those tests when
232
- // files share an invocation. We seed the real DB instead so every test stays
233
- // order-independent.
174
+ // The auth-fallback, tool-executed, and skill-loaded stores are
175
+ // intentionally NOT mocked — they have their own DB-backed tests, and Bun's
176
+ // `mock.module` is process-global, so mocking them here would leak into those
177
+ // tests when files share an invocation. We seed the real DB instead so every
178
+ // test stays order-independent.
234
179
 
235
180
  // ---------------------------------------------------------------------------
236
181
  // Production import (after mocks)
@@ -241,12 +186,16 @@ import {
241
186
  TOOL_INVOCATION_PII_SENTINEL as TOOL_PII_SENTINEL,
242
187
  type ToolInvocationSeedSpec,
243
188
  } from "../__tests__/test-support/tool-invocation-seed.js";
244
- import { getDb } from "../persistence/db-connection.js";
189
+ import {
190
+ recordActivationEvent,
191
+ recordOnboardingEvent,
192
+ } from "../onboarding/onboarding-events-store.js";
193
+ import { getDb, getTelemetryDb } from "../persistence/db-connection.js";
245
194
  import { initializeDb } from "../persistence/db-init.js";
195
+ import { recordLifecycleEvent } from "../persistence/lifecycle-events-store.js";
246
196
  import {
247
- authFallbackEvents,
248
197
  conversations,
249
- skillLoadedEvents,
198
+ telemetryEvents,
250
199
  toolInvocations,
251
200
  } from "../persistence/schema/index.js";
252
201
  import { recordAuthFallbackCounts } from "../security/auth-fallback-events-store.js";
@@ -255,8 +204,37 @@ import {
255
204
  ACTIVATION_FUNNEL_VERSION,
256
205
  buildActivationDaemonEventId,
257
206
  } from "./activation-funnel.js";
207
+ import { recordConfigSettingEvent } from "./config-setting-events-store.js";
258
208
  import { recordSkillLoadedEvent } from "./skill-loaded-events-store.js";
259
- import { UsageTelemetryReporter } from "./usage-telemetry-reporter.js";
209
+ import {
210
+ ALL_TELEMETRY_EVENT_SOURCES,
211
+ DAEMON_TELEMETRY_EVENT_SOURCES,
212
+ MONITOR_TELEMETRY_EVENT_SOURCES,
213
+ outboxSource,
214
+ type TelemetryEventSource,
215
+ } from "./telemetry-event-sources.js";
216
+ import {
217
+ insertTelemetryOutboxEvent,
218
+ queryTelemetryOutboxBatch,
219
+ } from "./telemetry-events-outbox.js";
220
+ import type { OutboxTelemetryEventName, TelemetryEvent } from "./types.js";
221
+ import {
222
+ initToolExecutedWatermarkIfAbsent,
223
+ UsageTelemetryReporter,
224
+ } from "./usage-telemetry-reporter.js";
225
+ import { recordWatchdogEvent } from "./watchdog-events-store.js";
226
+
227
+ /**
228
+ * Construct a reporter wired to the in-memory checkpoint fake. All tests go
229
+ * through this so watermark reads/writes stay assertable via the mocks
230
+ * without process-global module mocking of the real telemetry-DB store.
231
+ */
232
+ function makeReporter(): UsageTelemetryReporter {
233
+ return new UsageTelemetryReporter(
234
+ ALL_TELEMETRY_EVENT_SOURCES,
235
+ fakeFlushCheckpointStore,
236
+ );
237
+ }
260
238
 
261
239
  await initializeDb();
262
240
 
@@ -306,34 +284,6 @@ function makeUsageEvent(
306
284
  };
307
285
  }
308
286
 
309
- type OnboardingEventFixture = ReturnType<
310
- typeof mockQueryUnreportedOnboardingEvents
311
- >[number];
312
-
313
- function makeOnboardingEvent(
314
- overrides: Partial<OnboardingEventFixture> = {},
315
- ): OnboardingEventFixture {
316
- eventIdCounter += 1;
317
- return {
318
- id: `onb-${eventIdCounter}`,
319
- createdAt: 1700000000000 + eventIdCounter * 1000,
320
- screen: "tools",
321
- toolsJson: null,
322
- tasksJson: null,
323
- tone: null,
324
- googleConnected: null,
325
- googleScopesJson: null,
326
- priorAssistantsJson: null,
327
- abVariant: null,
328
- sessionId: null,
329
- stepName: null,
330
- stepIndex: null,
331
- completedAt: null,
332
- funnelVersion: null,
333
- ...overrides,
334
- };
335
- }
336
-
337
287
  const TOOL_CONVERSATION_ID = "conv-reporter-tool-executed";
338
288
 
339
289
  function seedToolInvocation(
@@ -355,10 +305,10 @@ function useStatefulCheckpoints(
355
305
  seed: Record<string, string> = {},
356
306
  ): Map<string, string> {
357
307
  const checkpoints = new Map(Object.entries(seed));
358
- mockGetMemoryCheckpoint.mockImplementation(
308
+ mockGetFlushCheckpoint.mockImplementation(
359
309
  (key) => checkpoints.get(key) ?? null,
360
310
  );
361
- mockSetMemoryCheckpoint.mockImplementation((key, value) => {
311
+ mockSetFlushCheckpoint.mockImplementation((key, value) => {
362
312
  checkpoints.set(key, value);
363
313
  });
364
314
  return checkpoints;
@@ -381,29 +331,22 @@ beforeEach(() => {
381
331
  mockGetCachedShareDiagnostics.mockReset();
382
332
  mockGetCachedShareDiagnostics.mockReturnValue(false);
383
333
  // Default the accepted consent version eligible so trace tests drive the gate
384
- // via the flag + share_diagnostics knobs; version cases override it.
334
+ // via the share_diagnostics knob; version cases override it.
385
335
  mockGetCachedShareDiagnosticsVersion.mockReset();
386
336
  mockGetCachedShareDiagnosticsVersion.mockReturnValue("2999-01-01");
387
- // Default the `trace-collection` flag ON so trace tests can drive eligibility
388
- // via the consent knob alone; the flag-gating test flips it explicitly.
389
- mockIsAssistantFeatureFlagEnabled.mockReset();
390
- mockIsAssistantFeatureFlagEnabled.mockReturnValue(true);
391
337
  mockAssembleBoundedTurnTrace.mockReset();
392
338
  mockAssembleBoundedTurnTrace.mockImplementation(defaultBoundedTurnTrace);
393
339
  mockIsTurnSettled.mockReset();
394
340
  mockIsTurnSettled.mockReturnValue(true);
395
- mockGetMemoryCheckpoint.mockReset();
396
- mockSetMemoryCheckpoint.mockReset();
341
+ mockGetFlushCheckpoint.mockReset();
342
+ mockSetFlushCheckpoint.mockReset();
343
+ mockIsFlushCheckpointStoreAvailable.mockReset();
344
+ mockIsFlushCheckpointStoreAvailable.mockReturnValue(true);
397
345
  mockQueryUnreportedUsageEvents.mockReset();
398
346
  mockQueryUnreportedTurnEvents.mockReset();
399
347
  mockQueryUnreportedTurnEvents.mockReturnValue([]);
400
- mockQueryUnreportedLifecycleEvents.mockReset();
401
- mockQueryUnreportedLifecycleEvents.mockReturnValue([]);
402
- mockQueryUnreportedOnboardingEvents.mockReset();
403
- mockQueryUnreportedOnboardingEvents.mockReturnValue([]);
404
- getDb().delete(authFallbackEvents).run();
405
348
  getDb().delete(toolInvocations).run();
406
- getDb().delete(skillLoadedEvents).run();
349
+ getTelemetryDb()!.delete(telemetryEvents).run();
407
350
  delete process.env.VELLUM_DISABLE_PLATFORM;
408
351
  delete process.env.IS_PLATFORM;
409
352
  mockGetPlatformBaseUrl.mockReset();
@@ -415,7 +358,7 @@ beforeEach(() => {
415
358
  mockGetPlatformUserId.mockReturnValue("");
416
359
 
417
360
  // Defaults
418
- mockGetMemoryCheckpoint.mockReturnValue(null);
361
+ mockGetFlushCheckpoint.mockReturnValue(null);
419
362
  mockGetPlatformBaseUrl.mockReturnValue("https://platform.vellum.ai");
420
363
 
421
364
  mockFetch = mock(() =>
@@ -459,7 +402,7 @@ describe("UsageTelemetryReporter", () => {
459
402
  const events = [makeUsageEvent(), makeUsageEvent()];
460
403
  mockQueryUnreportedUsageEvents.mockReturnValue(events);
461
404
 
462
- const reporter = new UsageTelemetryReporter();
405
+ const reporter = makeReporter();
463
406
  await reporter.flush();
464
407
 
465
408
  expect(clientFetchMock).toHaveBeenCalledTimes(1);
@@ -477,11 +420,11 @@ describe("UsageTelemetryReporter", () => {
477
420
  const events = [makeUsageEvent()];
478
421
  mockQueryUnreportedUsageEvents.mockReturnValue(events);
479
422
 
480
- const reporter = new UsageTelemetryReporter();
423
+ const reporter = makeReporter();
481
424
  await reporter.flush();
482
425
 
483
426
  expect(mockFetch).not.toHaveBeenCalled();
484
- const watermarkCalls = mockSetMemoryCheckpoint.mock.calls.filter(
427
+ const watermarkCalls = mockSetFlushCheckpoint.mock.calls.filter(
485
428
  (c) => c[0] === "telemetry:usage:last_reported_at",
486
429
  );
487
430
  expect(watermarkCalls.length).toBe(0);
@@ -496,14 +439,14 @@ describe("UsageTelemetryReporter", () => {
496
439
  const events = [makeUsageEvent()];
497
440
  mockQueryUnreportedUsageEvents.mockReturnValue(events);
498
441
 
499
- const reporter = new UsageTelemetryReporter();
442
+ const reporter = makeReporter();
500
443
  // Construction initializes the absent tool_executed watermark; clear that
501
444
  // call so the assertion below covers only the flush.
502
- mockSetMemoryCheckpoint.mockClear();
445
+ mockSetFlushCheckpoint.mockClear();
503
446
  await reporter.flush();
504
447
 
505
448
  expect(mockFetch).not.toHaveBeenCalled();
506
- expect(mockSetMemoryCheckpoint).not.toHaveBeenCalled();
449
+ expect(mockSetFlushCheckpoint).not.toHaveBeenCalled();
507
450
  });
508
451
 
509
452
  test("VELLUM_DISABLE_PLATFORM is ignored when IS_PLATFORM is set (managed mode)", async () => {
@@ -516,7 +459,7 @@ describe("UsageTelemetryReporter", () => {
516
459
  const events = [makeUsageEvent()];
517
460
  mockQueryUnreportedUsageEvents.mockReturnValue(events);
518
461
 
519
- const reporter = new UsageTelemetryReporter();
462
+ const reporter = makeReporter();
520
463
  await reporter.flush();
521
464
 
522
465
  expect(mockFetch).toHaveBeenCalledTimes(1);
@@ -532,10 +475,10 @@ describe("UsageTelemetryReporter", () => {
532
475
  Promise.resolve(new Response('{"accepted":2}', { status: 200 })),
533
476
  );
534
477
 
535
- const reporter = new UsageTelemetryReporter();
478
+ const reporter = makeReporter();
536
479
  await reporter.flush();
537
480
 
538
- const watermarkCalls = mockSetMemoryCheckpoint.mock.calls.filter(
481
+ const watermarkCalls = mockSetFlushCheckpoint.mock.calls.filter(
539
482
  (c) => c[0] === "telemetry:usage:last_reported_at",
540
483
  );
541
484
  expect(watermarkCalls.length).toBeGreaterThanOrEqual(1);
@@ -545,7 +488,7 @@ describe("UsageTelemetryReporter", () => {
545
488
  );
546
489
 
547
490
  // The compound cursor ID should also be set to the last event's id
548
- const idCalls = mockSetMemoryCheckpoint.mock.calls.filter(
491
+ const idCalls = mockSetFlushCheckpoint.mock.calls.filter(
549
492
  (c) => c[0] === "telemetry:usage:last_reported_id",
550
493
  );
551
494
  expect(idCalls.length).toBeGreaterThanOrEqual(1);
@@ -559,10 +502,10 @@ describe("UsageTelemetryReporter", () => {
559
502
  Promise.resolve(new Response("error", { status: 500 })),
560
503
  );
561
504
 
562
- const reporter = new UsageTelemetryReporter();
505
+ const reporter = makeReporter();
563
506
  await reporter.flush();
564
507
 
565
- const watermarkCalls = mockSetMemoryCheckpoint.mock.calls.filter(
508
+ const watermarkCalls = mockSetFlushCheckpoint.mock.calls.filter(
566
509
  (c) => c[0] === "telemetry:usage:last_reported_at",
567
510
  );
568
511
  expect(watermarkCalls.length).toBe(0);
@@ -575,7 +518,7 @@ describe("UsageTelemetryReporter", () => {
575
518
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
576
519
  );
577
520
 
578
- const reporter = new UsageTelemetryReporter();
521
+ const reporter = makeReporter();
579
522
 
580
523
  // First flush — should use the per-device ID
581
524
  await reporter.flush();
@@ -598,7 +541,7 @@ describe("UsageTelemetryReporter", () => {
598
541
  test("empty batch makes no HTTP call", async () => {
599
542
  mockQueryUnreportedUsageEvents.mockReturnValue([]);
600
543
 
601
- const reporter = new UsageTelemetryReporter();
544
+ const reporter = makeReporter();
602
545
  await reporter.flush();
603
546
 
604
547
  expect(mockFetch).not.toHaveBeenCalled();
@@ -614,7 +557,7 @@ describe("UsageTelemetryReporter", () => {
614
557
  Promise.resolve(new Response('{"accepted":500}', { status: 200 })),
615
558
  );
616
559
 
617
- const reporter = new UsageTelemetryReporter();
560
+ const reporter = makeReporter();
618
561
  await reporter.flush();
619
562
 
620
563
  // MAX_CONSECUTIVE_BATCHES = 10
@@ -628,7 +571,7 @@ describe("UsageTelemetryReporter", () => {
628
571
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
629
572
  );
630
573
 
631
- const reporter = new UsageTelemetryReporter();
574
+ const reporter = makeReporter();
632
575
  reporter.start();
633
576
 
634
577
  // Wait a tick so start()'s immediate flush settles.
@@ -662,7 +605,7 @@ describe("UsageTelemetryReporter", () => {
662
605
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
663
606
  );
664
607
 
665
- const reporter = new UsageTelemetryReporter();
608
+ const reporter = makeReporter();
666
609
  await reporter.flush();
667
610
 
668
611
  expect(mockFetch).toHaveBeenCalledTimes(1);
@@ -731,7 +674,7 @@ describe("UsageTelemetryReporter", () => {
731
674
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
732
675
  );
733
676
 
734
- const reporter = new UsageTelemetryReporter();
677
+ const reporter = makeReporter();
735
678
  await reporter.flush();
736
679
 
737
680
  const body = JSON.parse(
@@ -755,7 +698,7 @@ describe("UsageTelemetryReporter", () => {
755
698
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
756
699
  );
757
700
 
758
- const reporter = new UsageTelemetryReporter();
701
+ const reporter = makeReporter();
759
702
  await reporter.flush();
760
703
 
761
704
  const body = JSON.parse(
@@ -782,7 +725,7 @@ describe("UsageTelemetryReporter", () => {
782
725
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
783
726
  );
784
727
 
785
- const reporter = new UsageTelemetryReporter();
728
+ const reporter = makeReporter();
786
729
  await reporter.flush();
787
730
 
788
731
  const body = JSON.parse(
@@ -800,7 +743,7 @@ describe("UsageTelemetryReporter", () => {
800
743
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
801
744
  );
802
745
 
803
- const reporter = new UsageTelemetryReporter();
746
+ const reporter = makeReporter();
804
747
  await reporter.flush();
805
748
 
806
749
  expect(mockFetch).toHaveBeenCalledTimes(1);
@@ -820,7 +763,7 @@ describe("UsageTelemetryReporter", () => {
820
763
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
821
764
  );
822
765
 
823
- const reporter = new UsageTelemetryReporter();
766
+ const reporter = makeReporter();
824
767
  await reporter.flush();
825
768
 
826
769
  expect(mockFetch).toHaveBeenCalledTimes(1);
@@ -838,7 +781,7 @@ describe("UsageTelemetryReporter", () => {
838
781
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
839
782
  );
840
783
 
841
- const reporter = new UsageTelemetryReporter();
784
+ const reporter = makeReporter();
842
785
  await reporter.flush();
843
786
 
844
787
  expect(mockFetch).toHaveBeenCalledTimes(1);
@@ -867,7 +810,7 @@ describe("UsageTelemetryReporter", () => {
867
810
  Promise.resolve(new Response('{"accepted":2}', { status: 200 })),
868
811
  );
869
812
 
870
- const reporter = new UsageTelemetryReporter();
813
+ const reporter = makeReporter();
871
814
  await reporter.flush();
872
815
 
873
816
  expect(mockFetch).toHaveBeenCalledTimes(1);
@@ -935,7 +878,7 @@ describe("UsageTelemetryReporter", () => {
935
878
  Promise.resolve(new Response('{"accepted":3}', { status: 200 })),
936
879
  );
937
880
 
938
- const reporter = new UsageTelemetryReporter();
881
+ const reporter = makeReporter();
939
882
  await reporter.flush();
940
883
 
941
884
  expect(mockFetch).toHaveBeenCalledTimes(1);
@@ -1014,7 +957,7 @@ describe("UsageTelemetryReporter", () => {
1014
957
  Promise.resolve(new Response('{"accepted":4}', { status: 200 })),
1015
958
  );
1016
959
 
1017
- const reporter = new UsageTelemetryReporter();
960
+ const reporter = makeReporter();
1018
961
  await reporter.flush();
1019
962
 
1020
963
  expect(mockFetch).toHaveBeenCalledTimes(1);
@@ -1068,8 +1011,8 @@ describe("UsageTelemetryReporter", () => {
1068
1011
  });
1069
1012
 
1070
1013
  // -------------------------------------------------------------------------
1071
- // Per-turn trace collection (gated on the trace-collection flag AND
1072
- // share_diagnostics consent)
1014
+ // Per-turn trace collection (gated on share_diagnostics consent at an
1015
+ // eligible accepted version)
1073
1016
  // -------------------------------------------------------------------------
1074
1017
 
1075
1018
  function singleTurnEvent() {
@@ -1087,8 +1030,7 @@ describe("UsageTelemetryReporter", () => {
1087
1030
  ];
1088
1031
  }
1089
1032
 
1090
- test("attaches the assembled trace when the trace-collection flag, share_diagnostics, and an eligible consent version are all true", async () => {
1091
- mockIsAssistantFeatureFlagEnabled.mockReturnValue(true);
1033
+ test("attaches the assembled trace when share_diagnostics and an eligible consent version are both true", async () => {
1092
1034
  mockGetCachedShareDiagnostics.mockReturnValue(true);
1093
1035
  mockQueryUnreportedUsageEvents.mockReturnValue([]);
1094
1036
  mockQueryUnreportedTurnEvents.mockReturnValue(singleTurnEvent());
@@ -1096,15 +1038,9 @@ describe("UsageTelemetryReporter", () => {
1096
1038
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
1097
1039
  );
1098
1040
 
1099
- const reporter = new UsageTelemetryReporter();
1041
+ const reporter = makeReporter();
1100
1042
  await reporter.flush();
1101
1043
 
1102
- // The gate consults the `trace-collection` flag.
1103
- expect(mockIsAssistantFeatureFlagEnabled).toHaveBeenCalledWith(
1104
- "trace-collection",
1105
- expect.anything(),
1106
- );
1107
-
1108
1044
  // The assembler is called with the turn's (conversationId, id, createdAt)
1109
1045
  // boundary so the window lines up with the turn event.
1110
1046
  expect(mockAssembleBoundedTurnTrace).toHaveBeenCalledTimes(1);
@@ -1124,13 +1060,13 @@ describe("UsageTelemetryReporter", () => {
1124
1060
  expect(turn.type).toBe("turn");
1125
1061
  expect(turn.daemon_event_id).toBe("evt-turn-trace");
1126
1062
  expect(turn.trace).toBeDefined();
1127
- expect(turn.trace.schema_version).toBe(2);
1063
+ expect(turn.trace.schema_version).toBe(3);
1128
1064
  expect(turn.trace.messages[0].id).toBe("evt-turn-trace");
1065
+ expect(turn.trace.messages[0].model).toBeNull();
1129
1066
  expect(Array.isArray(turn.trace.tool_calls)).toBe(true);
1130
1067
  });
1131
1068
 
1132
- test("omits the trace when share_diagnostics is false even though the flag is on (and still emits the turn event)", async () => {
1133
- mockIsAssistantFeatureFlagEnabled.mockReturnValue(true);
1069
+ test("omits the trace when share_diagnostics is false (and still emits the turn event)", async () => {
1134
1070
  mockGetCachedShareDiagnostics.mockReturnValue(false);
1135
1071
  mockQueryUnreportedUsageEvents.mockReturnValue([]);
1136
1072
  mockQueryUnreportedTurnEvents.mockReturnValue(singleTurnEvent());
@@ -1138,7 +1074,7 @@ describe("UsageTelemetryReporter", () => {
1138
1074
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
1139
1075
  );
1140
1076
 
1141
- const reporter = new UsageTelemetryReporter();
1077
+ const reporter = makeReporter();
1142
1078
  await reporter.flush();
1143
1079
 
1144
1080
  // Gate off → no assembly at all (no PII touched).
@@ -1156,8 +1092,7 @@ describe("UsageTelemetryReporter", () => {
1156
1092
  expect("trace" in turn).toBe(false);
1157
1093
  });
1158
1094
 
1159
- test("omits the trace when the accepted consent version predates the disclosure threshold (flag + share_diagnostics on)", async () => {
1160
- mockIsAssistantFeatureFlagEnabled.mockReturnValue(true);
1095
+ test("omits the trace when the accepted consent version predates the disclosure threshold (share_diagnostics on)", async () => {
1161
1096
  mockGetCachedShareDiagnostics.mockReturnValue(true);
1162
1097
  // Consent recorded under an older version that never disclosed trace
1163
1098
  // collection → gate closed, mirroring the platform ingest gate.
@@ -1168,7 +1103,7 @@ describe("UsageTelemetryReporter", () => {
1168
1103
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
1169
1104
  );
1170
1105
 
1171
- const reporter = new UsageTelemetryReporter();
1106
+ const reporter = makeReporter();
1172
1107
  await reporter.flush();
1173
1108
 
1174
1109
  // Version below threshold → no assembly at all (no PII touched).
@@ -1183,7 +1118,6 @@ describe("UsageTelemetryReporter", () => {
1183
1118
  });
1184
1119
 
1185
1120
  test("omits the trace when the owner never accepted a versioned consent (empty version)", async () => {
1186
- mockIsAssistantFeatureFlagEnabled.mockReturnValue(true);
1187
1121
  mockGetCachedShareDiagnostics.mockReturnValue(true);
1188
1122
  // Empty version (never accepted / no-row default where share_diagnostics is
1189
1123
  // true but unversioned) fails closed.
@@ -1194,7 +1128,7 @@ describe("UsageTelemetryReporter", () => {
1194
1128
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
1195
1129
  );
1196
1130
 
1197
- const reporter = new UsageTelemetryReporter();
1131
+ const reporter = makeReporter();
1198
1132
  await reporter.flush();
1199
1133
 
1200
1134
  expect(mockAssembleBoundedTurnTrace).not.toHaveBeenCalled();
@@ -1205,30 +1139,6 @@ describe("UsageTelemetryReporter", () => {
1205
1139
  expect("trace" in body.events[0]).toBe(false);
1206
1140
  });
1207
1141
 
1208
- test("omits the trace when the trace-collection flag is off even though share_diagnostics is true", async () => {
1209
- mockIsAssistantFeatureFlagEnabled.mockReturnValue(false);
1210
- mockGetCachedShareDiagnostics.mockReturnValue(true);
1211
- mockQueryUnreportedUsageEvents.mockReturnValue([]);
1212
- mockQueryUnreportedTurnEvents.mockReturnValue(singleTurnEvent());
1213
- mockFetch.mockImplementation(() =>
1214
- Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
1215
- );
1216
-
1217
- const reporter = new UsageTelemetryReporter();
1218
- await reporter.flush();
1219
-
1220
- // Flag off → gate closed → no assembly at all (no PII touched).
1221
- expect(mockAssembleBoundedTurnTrace).not.toHaveBeenCalled();
1222
-
1223
- expect(mockFetch).toHaveBeenCalledTimes(1);
1224
- const body = JSON.parse(
1225
- (mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
1226
- );
1227
- // The single turn event still flushes — just without a trace.
1228
- expect(body.events.length).toBe(1);
1229
- expect("trace" in body.events[0]).toBe(false);
1230
- });
1231
-
1232
1142
  test("omits the trace (key absent) when the assembler returns null (over-cap / failure) but still emits the turn event", async () => {
1233
1143
  mockGetCachedShareDiagnostics.mockReturnValue(true);
1234
1144
  // Over-cap / assembly-failure path: the bounded assembler returns null.
@@ -1239,7 +1149,7 @@ describe("UsageTelemetryReporter", () => {
1239
1149
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
1240
1150
  );
1241
1151
 
1242
- const reporter = new UsageTelemetryReporter();
1152
+ const reporter = makeReporter();
1243
1153
  await reporter.flush();
1244
1154
 
1245
1155
  expect(mockAssembleBoundedTurnTrace).toHaveBeenCalledTimes(1);
@@ -1253,13 +1163,12 @@ describe("UsageTelemetryReporter", () => {
1253
1163
  test("no trace is assembled or attached when the whole flush is gated off by share_analytics", async () => {
1254
1164
  // The analytics gate short-circuits the entire flush; trace assembly must
1255
1165
  // never run (and nothing is sent) even when the trace gate is fully on
1256
- // (flag + share_diagnostics both true).
1166
+ // (share_diagnostics true at an eligible version).
1257
1167
  mockGetCachedShareAnalytics.mockReturnValue(false);
1258
- mockIsAssistantFeatureFlagEnabled.mockReturnValue(true);
1259
1168
  mockGetCachedShareDiagnostics.mockReturnValue(true);
1260
1169
  mockQueryUnreportedTurnEvents.mockReturnValue(singleTurnEvent());
1261
1170
 
1262
- const reporter = new UsageTelemetryReporter();
1171
+ const reporter = makeReporter();
1263
1172
  await reporter.flush();
1264
1173
 
1265
1174
  expect(mockAssembleBoundedTurnTrace).not.toHaveBeenCalled();
@@ -1303,7 +1212,7 @@ describe("UsageTelemetryReporter", () => {
1303
1212
  // The turn's response is still streaming — not settled.
1304
1213
  mockIsTurnSettled.mockReturnValue(false);
1305
1214
 
1306
- const reporter = new UsageTelemetryReporter();
1215
+ const reporter = makeReporter();
1307
1216
  await reporter.flush();
1308
1217
 
1309
1218
  // No trace assembled, and the turn is NOT sent (the only event was deferred).
@@ -1324,7 +1233,7 @@ describe("UsageTelemetryReporter", () => {
1324
1233
 
1325
1234
  // Flush 1: turn in progress -> deferred, nothing sent.
1326
1235
  mockIsTurnSettled.mockReturnValue(false);
1327
- const reporter = new UsageTelemetryReporter();
1236
+ const reporter = makeReporter();
1328
1237
  await reporter.flush();
1329
1238
  expect(mockFetch).not.toHaveBeenCalled();
1330
1239
 
@@ -1332,19 +1241,21 @@ describe("UsageTelemetryReporter", () => {
1332
1241
  // the full trace assembles. The same (still-unreported) turn now ships.
1333
1242
  mockIsTurnSettled.mockReturnValue(true);
1334
1243
  mockAssembleBoundedTurnTrace.mockReturnValue({
1335
- schema_version: 2,
1244
+ schema_version: 3,
1336
1245
  messages: [
1337
1246
  {
1338
1247
  id: "evt-inflight",
1339
1248
  role: "user",
1340
1249
  created_at: 1700000050000,
1341
1250
  content: [{ type: "text", text: "do a thing" }],
1251
+ model: null,
1342
1252
  },
1343
1253
  {
1344
1254
  id: "asst-1",
1345
1255
  role: "assistant",
1346
1256
  created_at: 1700000051000,
1347
1257
  content: [{ type: "text", text: "done" }],
1258
+ model: "claude-fable-5",
1348
1259
  },
1349
1260
  ],
1350
1261
  tool_calls: [{ id: "ti-1" }],
@@ -1389,7 +1300,7 @@ describe("UsageTelemetryReporter", () => {
1389
1300
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
1390
1301
  );
1391
1302
 
1392
- const reporter = new UsageTelemetryReporter();
1303
+ const reporter = makeReporter();
1393
1304
  await reporter.flush();
1394
1305
 
1395
1306
  expect(mockAssembleBoundedTurnTrace).toHaveBeenCalledTimes(1);
@@ -1423,7 +1334,7 @@ describe("UsageTelemetryReporter", () => {
1423
1334
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
1424
1335
  );
1425
1336
 
1426
- const reporter = new UsageTelemetryReporter();
1337
+ const reporter = makeReporter();
1427
1338
  await reporter.flush();
1428
1339
 
1429
1340
  expect(mockFetch).toHaveBeenCalledTimes(1);
@@ -1460,7 +1371,7 @@ describe("UsageTelemetryReporter", () => {
1460
1371
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
1461
1372
  );
1462
1373
 
1463
- const reporter = new UsageTelemetryReporter();
1374
+ const reporter = makeReporter();
1464
1375
  await reporter.flush();
1465
1376
 
1466
1377
  expect(mockIsTurnSettled).toHaveBeenCalled();
@@ -1481,7 +1392,7 @@ describe("UsageTelemetryReporter", () => {
1481
1392
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
1482
1393
  );
1483
1394
 
1484
- const reporter = new UsageTelemetryReporter();
1395
+ const reporter = makeReporter();
1485
1396
  await reporter.flush();
1486
1397
 
1487
1398
  expect(mockFetch).toHaveBeenCalledTimes(1);
@@ -1520,7 +1431,7 @@ describe("UsageTelemetryReporter", () => {
1520
1431
  Promise.resolve(new Response('{"accepted":4}', { status: 200 })),
1521
1432
  );
1522
1433
 
1523
- const reporter = new UsageTelemetryReporter();
1434
+ const reporter = makeReporter();
1524
1435
  await reporter.flush();
1525
1436
 
1526
1437
  expect(mockFetch).toHaveBeenCalledTimes(1);
@@ -1562,7 +1473,7 @@ describe("UsageTelemetryReporter", () => {
1562
1473
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
1563
1474
  );
1564
1475
 
1565
- const reporter = new UsageTelemetryReporter();
1476
+ const reporter = makeReporter();
1566
1477
  await reporter.flush();
1567
1478
 
1568
1479
  const body = JSON.parse(
@@ -1602,7 +1513,7 @@ describe("UsageTelemetryReporter", () => {
1602
1513
  Promise.resolve(new Response('{"accepted":3}', { status: 200 })),
1603
1514
  );
1604
1515
 
1605
- const reporter = new UsageTelemetryReporter();
1516
+ const reporter = makeReporter();
1606
1517
  await reporter.flush();
1607
1518
 
1608
1519
  expect(mockFetch).toHaveBeenCalledTimes(1);
@@ -1661,33 +1572,28 @@ describe("UsageTelemetryReporter", () => {
1661
1572
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
1662
1573
  );
1663
1574
 
1664
- const reporter = new UsageTelemetryReporter();
1575
+ const reporter = makeReporter();
1665
1576
  // Construction initializes the absent tool_executed watermark; clear that
1666
1577
  // call so the count below covers only the flush's advancement.
1667
- mockSetMemoryCheckpoint.mockClear();
1578
+ mockSetFlushCheckpoint.mockClear();
1668
1579
  await reporter.flush();
1669
1580
 
1670
1581
  // No HTTP call should have been made
1671
1582
  expect(mockFetch).not.toHaveBeenCalled();
1672
1583
 
1673
- // All 8 timestamp watermarks should have been advanced, and all 8 ID
1674
- // watermarks pinned to the high-sorting sentinel (a truthy value keeps
1675
- // the compound-cursor branch active while closing its same-millisecond
1676
- // arm against opt-out rows).
1677
- expect(mockSetMemoryCheckpoint).toHaveBeenCalledTimes(16);
1584
+ // All 3 watermark-mode timestamp watermarks should have been advanced,
1585
+ // and their 3 ID watermarks pinned to the high-sorting sentinel (a truthy
1586
+ // value keeps the compound-cursor branch active while closing its
1587
+ // same-millisecond arm against opt-out rows). Ack-mode sources
1588
+ // (lifecycle, onboarding, auth_fallback, skill_loaded, watchdog,
1589
+ // config_setting) discard their pending outbox rows instead and never
1590
+ // touch `flush_checkpoints`.
1591
+ expect(mockSetFlushCheckpoint).toHaveBeenCalledTimes(6);
1678
1592
 
1679
- const calls = mockSetMemoryCheckpoint.mock.calls;
1593
+ const calls = mockSetFlushCheckpoint.mock.calls;
1680
1594
  const keys = calls.map((c) => c[0]);
1681
- const eventTypes = [
1682
- "usage",
1683
- "turns",
1684
- "lifecycle",
1685
- "onboarding",
1686
- "auth_fallback",
1687
- "tool_executed",
1688
- "skill_loaded",
1689
- "watchdog",
1690
- ];
1595
+ expect(keys.some((k) => k.includes("skill_loaded"))).toBe(false);
1596
+ const eventTypes = ["usage", "turns", "tool_executed"];
1691
1597
  for (const eventType of eventTypes) {
1692
1598
  expect(keys).toContain(`telemetry:${eventType}:last_reported_at`);
1693
1599
  const idCall = calls.find(
@@ -1695,6 +1601,11 @@ describe("UsageTelemetryReporter", () => {
1695
1601
  );
1696
1602
  expect(idCall?.[1]).toBe("ffffffff-ffff-ffff-ffff-ffffffffffff");
1697
1603
  }
1604
+ expect(keys).not.toContain("telemetry:auth_fallback:last_reported_at");
1605
+ expect(keys).not.toContain("telemetry:auth_fallback:last_reported_id");
1606
+ expect(keys.some((k) => k.includes(":lifecycle:"))).toBe(false);
1607
+ expect(keys).not.toContain("telemetry:onboarding:last_reported_at");
1608
+ expect(keys).not.toContain("telemetry:onboarding:last_reported_id");
1698
1609
  });
1699
1610
 
1700
1611
  test("platform disabled takes precedence over consent off — watermarks NOT advanced", async () => {
@@ -1708,23 +1619,44 @@ describe("UsageTelemetryReporter", () => {
1708
1619
  const events = [makeUsageEvent()];
1709
1620
  mockQueryUnreportedUsageEvents.mockReturnValue(events);
1710
1621
 
1711
- const reporter = new UsageTelemetryReporter();
1622
+ const reporter = makeReporter();
1712
1623
  // Construction initializes the absent tool_executed watermark; clear that
1713
1624
  // call so the assertion below covers only the flush.
1714
- mockSetMemoryCheckpoint.mockClear();
1625
+ mockSetFlushCheckpoint.mockClear();
1715
1626
  await reporter.flush();
1716
1627
 
1717
1628
  expect(mockFetch).not.toHaveBeenCalled();
1718
- expect(mockSetMemoryCheckpoint).not.toHaveBeenCalled();
1629
+ expect(mockSetFlushCheckpoint).not.toHaveBeenCalled();
1630
+ });
1631
+
1632
+ test("flush skipped entirely when the flush-checkpoint store is unavailable", async () => {
1633
+ // An unreadable checkpoint store must not be mistaken for cursor 0 —
1634
+ // that would requery and re-ship history from the beginning. Nothing is
1635
+ // sent and no watermark is touched (not even the opt-out sentinel);
1636
+ // the cycle retries once the store is back.
1637
+ mockIsFlushCheckpointStoreAvailable.mockReturnValue(false);
1638
+ const events = [makeUsageEvent()];
1639
+ mockQueryUnreportedUsageEvents.mockReturnValue(events);
1640
+
1641
+ const reporter = makeReporter();
1642
+ // Construction initializes the absent tool_executed watermark; clear
1643
+ // those calls so the assertions below cover only the flush.
1644
+ mockGetFlushCheckpoint.mockClear();
1645
+ mockSetFlushCheckpoint.mockClear();
1646
+ await reporter.flush();
1647
+
1648
+ expect(mockFetch).not.toHaveBeenCalled();
1649
+ expect(mockGetFlushCheckpoint).not.toHaveBeenCalled();
1650
+ expect(mockSetFlushCheckpoint).not.toHaveBeenCalled();
1719
1651
  });
1720
1652
 
1721
1653
  test("events sent normally after re-granting share_analytics consent", async () => {
1722
1654
  // First flush with opt-out — watermarks advance, nothing sent
1723
1655
  mockGetCachedShareAnalytics.mockReturnValue(false);
1724
- const reporter = new UsageTelemetryReporter();
1656
+ const reporter = makeReporter();
1725
1657
  await reporter.flush();
1726
1658
  expect(mockFetch).not.toHaveBeenCalled();
1727
- mockSetMemoryCheckpoint.mockReset();
1659
+ mockSetFlushCheckpoint.mockReset();
1728
1660
 
1729
1661
  // Re-grant consent and flush with new events
1730
1662
  mockGetCachedShareAnalytics.mockReturnValue(true);
@@ -1748,9 +1680,9 @@ describe("UsageTelemetryReporter", () => {
1748
1680
  //
1749
1681
  // The envelope's `assistant_version` is upload-time (always the current
1750
1682
  // binary). The per-event field is record-time (the binary that was running
1751
- // when the event was persisted to SQLite). In this PR only `llm_usage`
1752
- // events carry a true record-time value; turn events (and lifecycle /
1753
- // onboarding events, not asserted here) stamp the running binary's
1683
+ // when the event was persisted to SQLite). `llm_usage` events and the
1684
+ // outbox-backed types carry a true record-time value; turn events
1685
+ // (not asserted here) stamp the running binary's
1754
1686
  // `APP_VERSION` directly until their respective follow-ups land.
1755
1687
  // Nullable llm_usage cases (legacy rows from before migration 267 ran)
1756
1688
  // fall back to the running binary's `APP_VERSION` rather than emitting
@@ -1769,7 +1701,7 @@ describe("UsageTelemetryReporter", () => {
1769
1701
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
1770
1702
  );
1771
1703
 
1772
- const reporter = new UsageTelemetryReporter();
1704
+ const reporter = makeReporter();
1773
1705
  await reporter.flush();
1774
1706
 
1775
1707
  const body = JSON.parse(
@@ -1793,7 +1725,7 @@ describe("UsageTelemetryReporter", () => {
1793
1725
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
1794
1726
  );
1795
1727
 
1796
- const reporter = new UsageTelemetryReporter();
1728
+ const reporter = makeReporter();
1797
1729
  await reporter.flush();
1798
1730
 
1799
1731
  const body = JSON.parse(
@@ -1830,7 +1762,7 @@ describe("UsageTelemetryReporter", () => {
1830
1762
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
1831
1763
  );
1832
1764
 
1833
- const reporter = new UsageTelemetryReporter();
1765
+ const reporter = makeReporter();
1834
1766
  await reporter.flush();
1835
1767
 
1836
1768
  const body = JSON.parse(
@@ -1859,7 +1791,7 @@ describe("UsageTelemetryReporter", () => {
1859
1791
  Promise.resolve(new Response('{"accepted":2}', { status: 200 })),
1860
1792
  );
1861
1793
 
1862
- const reporter = new UsageTelemetryReporter();
1794
+ const reporter = makeReporter();
1863
1795
  await reporter.flush();
1864
1796
 
1865
1797
  const body = JSON.parse(
@@ -1891,7 +1823,7 @@ describe("UsageTelemetryReporter", () => {
1891
1823
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
1892
1824
  );
1893
1825
 
1894
- const reporter = new UsageTelemetryReporter();
1826
+ const reporter = makeReporter();
1895
1827
  await reporter.flush();
1896
1828
 
1897
1829
  expect(mockFetch).toHaveBeenCalledTimes(1);
@@ -1914,7 +1846,7 @@ describe("UsageTelemetryReporter", () => {
1914
1846
  expect(typeof body.events[0].daemon_event_id).toBe("string");
1915
1847
  });
1916
1848
 
1917
- test("auth_fallback watermark advances to the last reported row on success", async () => {
1849
+ test("auth_fallback rows are deleted on success and never watermarked (ack mode)", async () => {
1918
1850
  mockQueryUnreportedUsageEvents.mockReturnValue([]);
1919
1851
  recordAuthFallbackCounts(1700000000000, 1700000001000, [
1920
1852
  {
@@ -1933,59 +1865,66 @@ describe("UsageTelemetryReporter", () => {
1933
1865
  mockFetch.mockImplementation(() =>
1934
1866
  Promise.resolve(new Response('{"accepted":2}', { status: 200 })),
1935
1867
  );
1868
+ expect(queryTelemetryOutboxBatch("auth_fallback", 10)).toHaveLength(2);
1936
1869
 
1937
- // The last row by the reporter's (createdAt, id) cursor order is the one
1938
- // whose watermark should be persisted after a successful upload.
1939
- const rows = getDb()
1940
- .select()
1941
- .from(authFallbackEvents)
1942
- .orderBy(authFallbackEvents.createdAt, authFallbackEvents.id)
1943
- .all();
1944
- const lastRow = rows[rows.length - 1];
1945
-
1946
- const reporter = new UsageTelemetryReporter();
1870
+ const reporter = makeReporter();
1947
1871
  await reporter.flush();
1948
1872
 
1949
- const watermarkCalls = mockSetMemoryCheckpoint.mock.calls.filter(
1950
- (c) => c[0] === "telemetry:auth_fallback:last_reported_at",
1873
+ // Both rows shipped, were acknowledged (deleted), and no auth_fallback
1874
+ // watermark was ever written — ack-mode sources bypass flush_checkpoints.
1875
+ const body = JSON.parse(
1876
+ (mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
1951
1877
  );
1952
- expect(watermarkCalls.length).toBeGreaterThanOrEqual(1);
1953
- expect(watermarkCalls[watermarkCalls.length - 1][1]).toBe(
1954
- String(lastRow.createdAt),
1878
+ expect(
1879
+ body.events.filter((e: { type: string }) => e.type === "auth_fallback"),
1880
+ ).toHaveLength(2);
1881
+ expect(queryTelemetryOutboxBatch("auth_fallback", 10)).toHaveLength(0);
1882
+ const authFallbackKeys = mockSetFlushCheckpoint.mock.calls.filter((c) =>
1883
+ String(c[0]).startsWith("telemetry:auth_fallback:"),
1884
+ );
1885
+ expect(authFallbackKeys).toHaveLength(0);
1886
+ });
1887
+
1888
+ test("auth_fallback rows survive a failed POST and re-ship on the next flush", async () => {
1889
+ mockQueryUnreportedUsageEvents.mockReturnValue([]);
1890
+ recordAuthFallbackCounts(1700000000000, 1700000001000, [
1891
+ {
1892
+ guard: "edge",
1893
+ path: "/v1/messages",
1894
+ failureKind: "missing_authorization",
1895
+ count: 9,
1896
+ },
1897
+ ]);
1898
+ mockFetch.mockImplementation(() =>
1899
+ Promise.resolve(new Response("server error", { status: 500 })),
1955
1900
  );
1956
1901
 
1957
- const idCalls = mockSetMemoryCheckpoint.mock.calls.filter(
1958
- (c) => c[0] === "telemetry:auth_fallback:last_reported_id",
1902
+ const reporter = makeReporter();
1903
+ await reporter.flush();
1904
+ expect(queryTelemetryOutboxBatch("auth_fallback", 10)).toHaveLength(1);
1905
+
1906
+ mockFetch.mockImplementation(() =>
1907
+ Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
1959
1908
  );
1960
- expect(idCalls.length).toBeGreaterThanOrEqual(1);
1961
- expect(idCalls[idCalls.length - 1][1]).toBe(lastRow.id);
1909
+ await reporter.flush();
1910
+ expect(queryTelemetryOutboxBatch("auth_fallback", 10)).toHaveLength(0);
1962
1911
  });
1963
1912
 
1964
1913
  // -------------------------------------------------------------------------
1965
1914
  // Onboarding / activation funnel events
1966
1915
  // -------------------------------------------------------------------------
1967
1916
 
1968
- test("activation onboarding row serializes funnel fields + deterministic daemon_event_id", async () => {
1917
+ test("activation onboarding row flushes with funnel fields + deterministic daemon_event_id, then acks", async () => {
1969
1918
  mockQueryUnreportedUsageEvents.mockReturnValue([]);
1970
1919
  const sessionId = "sess-activation-1";
1971
1920
  const stepName = "activation_moment_1_complete";
1972
- mockQueryUnreportedOnboardingEvents.mockReturnValue([
1973
- makeOnboardingEvent({
1974
- id: "onb-activation-1",
1975
- screen: stepName,
1976
- abVariant: "variant-a",
1977
- sessionId,
1978
- stepName,
1979
- stepIndex: 1,
1980
- completedAt: "2026-06-06T00:00:00.000Z",
1981
- funnelVersion: ACTIVATION_FUNNEL_VERSION,
1982
- }),
1983
- ]);
1921
+ const recorded = recordActivationEvent({ stepName, sessionId });
1922
+ expect(recorded).not.toBeNull();
1984
1923
  mockFetch.mockImplementation(() =>
1985
1924
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
1986
1925
  );
1987
1926
 
1988
- const reporter = new UsageTelemetryReporter();
1927
+ const reporter = makeReporter();
1989
1928
  await reporter.flush();
1990
1929
 
1991
1930
  expect(mockFetch).toHaveBeenCalledTimes(1);
@@ -1998,69 +1937,66 @@ describe("UsageTelemetryReporter", () => {
1998
1937
  session_id: sessionId,
1999
1938
  step_name: stepName,
2000
1939
  step_index: 1,
2001
- completed_at: "2026-06-06T00:00:00.000Z",
2002
- funnel_version: "activation_v1_2026_06",
1940
+ funnel_version: ACTIVATION_FUNNEL_VERSION,
2003
1941
  daemon_event_id: buildActivationDaemonEventId(sessionId, stepName),
2004
1942
  });
1943
+ // The wire id is the deterministic activation id, distinct from the
1944
+ // outbox row id (a UUID) the ack deletes by.
1945
+ expect(body.events[0].daemon_event_id).not.toBe(recorded!.id);
1946
+ expect(queryTelemetryOutboxBatch("onboarding", 10)).toHaveLength(0);
2005
1947
  });
2006
1948
 
2007
- test("activation daemon_event_id is keyed on the row's stored funnel_version, not the binary constant", async () => {
1949
+ test("queued activation row recorded under an older funnel version ships its frozen payload verbatim", async () => {
2008
1950
  mockQueryUnreportedUsageEvents.mockReturnValue([]);
2009
- const sessionId = "sess-activation-old";
2010
- const stepName = "activation_moment_1_complete";
2011
- // A row recorded under an OLDER funnel version, queued across an upgrade.
1951
+ // The payload (including the deterministic daemon_event_id keyed on the
1952
+ // OLD funnel version) was frozen at record time; a flush after a version
1953
+ // bump must not restamp it with the new binary's constant.
2012
1954
  const oldVersion = "activation_v0_2026_05";
2013
1955
  expect(oldVersion).not.toBe(ACTIVATION_FUNNEL_VERSION);
2014
- mockQueryUnreportedOnboardingEvents.mockReturnValue([
2015
- makeOnboardingEvent({
2016
- id: "onb-activation-old",
2017
- screen: stepName,
2018
- abVariant: "variant-a",
2019
- sessionId,
2020
- stepName,
2021
- stepIndex: 1,
2022
- completedAt: "2026-05-01T00:00:00.000Z",
2023
- funnelVersion: oldVersion,
2024
- }),
2025
- ]);
1956
+ const frozen: TelemetryEvent = {
1957
+ type: "onboarding",
1958
+ daemon_event_id: `${oldVersion}:sess-activation-old:activation_moment_1_complete`,
1959
+ recorded_at: 1700000100000,
1960
+ screen: "activation_moment_1_complete",
1961
+ ab_variant: "variant-a",
1962
+ session_id: "sess-activation-old",
1963
+ step_name: "activation_moment_1_complete",
1964
+ step_index: 1,
1965
+ completed_at: "2026-05-01T00:00:00.000Z",
1966
+ funnel_version: oldVersion,
1967
+ assistant_version: "0.9.0-old",
1968
+ };
1969
+ insertTelemetryOutboxEvent({
1970
+ id: "row-activation-old",
1971
+ name: "onboarding",
1972
+ createdAt: 1700000100000,
1973
+ event: frozen,
1974
+ });
2026
1975
  mockFetch.mockImplementation(() =>
2027
1976
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
2028
1977
  );
2029
1978
 
2030
- const reporter = new UsageTelemetryReporter();
1979
+ const reporter = makeReporter();
2031
1980
  await reporter.flush();
2032
1981
 
2033
1982
  const body = JSON.parse(
2034
1983
  (mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
2035
1984
  );
2036
- expect(body.events[0]).toMatchObject({
2037
- funnel_version: oldVersion,
2038
- daemon_event_id: buildActivationDaemonEventId(
2039
- sessionId,
2040
- stepName,
2041
- oldVersion,
2042
- ),
2043
- });
2044
- // Guard: the id must carry the row's version, not the binary constant.
2045
- expect(body.events[0].daemon_event_id).toBe(
2046
- `${oldVersion}:${sessionId}:${stepName}`,
2047
- );
1985
+ expect(body.events).toEqual([frozen]);
2048
1986
  });
2049
1987
 
2050
- test("legacy onboarding row keeps daemon_event_id: e.id and omits funnel fields", async () => {
1988
+ test("legacy onboarding row keeps daemon_event_id = row id and omits funnel fields", async () => {
2051
1989
  mockQueryUnreportedUsageEvents.mockReturnValue([]);
2052
- mockQueryUnreportedOnboardingEvents.mockReturnValue([
2053
- makeOnboardingEvent({
2054
- id: "onb-legacy-1",
2055
- screen: "tools",
2056
- toolsJson: JSON.stringify(["calendar"]),
2057
- }),
2058
- ]);
1990
+ const recorded = recordOnboardingEvent({
1991
+ screen: "tools",
1992
+ tools: ["calendar"],
1993
+ });
1994
+ expect(recorded).not.toBeNull();
2059
1995
  mockFetch.mockImplementation(() =>
2060
1996
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
2061
1997
  );
2062
1998
 
2063
- const reporter = new UsageTelemetryReporter();
1999
+ const reporter = makeReporter();
2064
2000
  await reporter.flush();
2065
2001
 
2066
2002
  expect(mockFetch).toHaveBeenCalledTimes(1);
@@ -2070,7 +2006,8 @@ describe("UsageTelemetryReporter", () => {
2070
2006
  expect(body.events.length).toBe(1);
2071
2007
  const e = body.events[0];
2072
2008
  expect(e.type).toBe("onboarding");
2073
- expect(e.daemon_event_id).toBe("onb-legacy-1");
2009
+ expect(e.daemon_event_id).toBe(recorded!.id);
2010
+ expect(e.tools).toEqual(["calendar"]);
2074
2011
  expect(e.session_id).toBeUndefined();
2075
2012
  expect(e.step_name).toBeUndefined();
2076
2013
  expect(e.step_index).toBeUndefined();
@@ -2118,7 +2055,7 @@ describe("UsageTelemetryReporter", () => {
2118
2055
  Promise.resolve(new Response('{"accepted":2}', { status: 200 })),
2119
2056
  );
2120
2057
 
2121
- const reporter = new UsageTelemetryReporter();
2058
+ const reporter = makeReporter();
2122
2059
  await reporter.flush();
2123
2060
 
2124
2061
  expect(mockFetch).toHaveBeenCalledTimes(1);
@@ -2174,10 +2111,10 @@ describe("UsageTelemetryReporter", () => {
2174
2111
  Promise.resolve(new Response('{"accepted":2}', { status: 200 })),
2175
2112
  );
2176
2113
 
2177
- const reporter = new UsageTelemetryReporter();
2114
+ const reporter = makeReporter();
2178
2115
  await reporter.flush();
2179
2116
 
2180
- const watermarkCalls = mockSetMemoryCheckpoint.mock.calls.filter(
2117
+ const watermarkCalls = mockSetFlushCheckpoint.mock.calls.filter(
2181
2118
  (c) => c[0] === "telemetry:tool_executed:last_reported_at",
2182
2119
  );
2183
2120
  expect(watermarkCalls.length).toBeGreaterThanOrEqual(1);
@@ -2185,7 +2122,7 @@ describe("UsageTelemetryReporter", () => {
2185
2122
  String(1700000002000),
2186
2123
  );
2187
2124
 
2188
- const idCalls = mockSetMemoryCheckpoint.mock.calls.filter(
2125
+ const idCalls = mockSetFlushCheckpoint.mock.calls.filter(
2189
2126
  (c) => c[0] === "telemetry:tool_executed:last_reported_id",
2190
2127
  );
2191
2128
  expect(idCalls.length).toBeGreaterThanOrEqual(1);
@@ -2197,18 +2134,20 @@ describe("UsageTelemetryReporter", () => {
2197
2134
  seedToolInvocation({ id: "ti-r1", createdAt: 1700000001000 });
2198
2135
  seedToolInvocation({ id: "ti-r2", createdAt: 1700000002000 });
2199
2136
  // A previous flush already reported ti-r1.
2200
- mockGetMemoryCheckpoint.mockImplementation((key) => {
2137
+ mockGetFlushCheckpoint.mockImplementation((key) => {
2201
2138
  if (key === "telemetry:tool_executed:last_reported_at") {
2202
2139
  return String(1700000001000);
2203
2140
  }
2204
- if (key === "telemetry:tool_executed:last_reported_id") return "ti-r1";
2141
+ if (key === "telemetry:tool_executed:last_reported_id") {
2142
+ return "ti-r1";
2143
+ }
2205
2144
  return null;
2206
2145
  });
2207
2146
  mockFetch.mockImplementation(() =>
2208
2147
  Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
2209
2148
  );
2210
2149
 
2211
- const reporter = new UsageTelemetryReporter();
2150
+ const reporter = makeReporter();
2212
2151
  await reporter.flush();
2213
2152
 
2214
2153
  expect(mockFetch).toHaveBeenCalledTimes(1);
@@ -2229,7 +2168,7 @@ describe("UsageTelemetryReporter", () => {
2229
2168
  // predating the reporter) stay behind the watermark.
2230
2169
  const checkpoints = useStatefulCheckpoints();
2231
2170
 
2232
- const reporter = new UsageTelemetryReporter();
2171
+ const reporter = makeReporter();
2233
2172
  const rowCreatedAt =
2234
2173
  Number(checkpoints.get("telemetry:tool_executed:last_reported_at")) +
2235
2174
  1000;
@@ -2268,7 +2207,7 @@ describe("UsageTelemetryReporter", () => {
2268
2207
  createdAt: Date.now() - 60_000,
2269
2208
  });
2270
2209
 
2271
- const reporter = new UsageTelemetryReporter();
2210
+ const reporter = makeReporter();
2272
2211
 
2273
2212
  // The checkpoint is persisted immediately at construction so a crash
2274
2213
  // before the first flush can't re-initialize later.
@@ -2313,7 +2252,7 @@ describe("UsageTelemetryReporter", () => {
2313
2252
  // opted-in rows.
2314
2253
  seedToolInvocation({ id: "ti-backlog", createdAt: 1700000002000 });
2315
2254
 
2316
- const reporter = new UsageTelemetryReporter();
2255
+ const reporter = makeReporter();
2317
2256
  expect(checkpoints.get("telemetry:tool_executed:last_reported_at")).toBe(
2318
2257
  String(1700000001000),
2319
2258
  );
@@ -2347,7 +2286,7 @@ describe("UsageTelemetryReporter", () => {
2347
2286
  id: "ti-opt-out-window",
2348
2287
  createdAt: optOutRowCreatedAt,
2349
2288
  });
2350
- const optedOutReporter = new UsageTelemetryReporter();
2289
+ const optedOutReporter = makeReporter();
2351
2290
  await optedOutReporter.stop(); // shutdown path: runs the final flush
2352
2291
  expect(mockFetch).not.toHaveBeenCalled();
2353
2292
  const advanced = Number(
@@ -2359,7 +2298,7 @@ describe("UsageTelemetryReporter", () => {
2359
2298
  // after the opt-out epoch ship — the opt-out-window row never does.
2360
2299
  mockGetCachedShareAnalytics.mockReturnValue(true);
2361
2300
  seedToolInvocation({ id: "ti-after-opt-in", createdAt: advanced + 1000 });
2362
- const reporter = new UsageTelemetryReporter();
2301
+ const reporter = makeReporter();
2363
2302
  await reporter.flush();
2364
2303
 
2365
2304
  expect(mockFetch).toHaveBeenCalledTimes(1);
@@ -2383,7 +2322,7 @@ describe("UsageTelemetryReporter", () => {
2383
2322
  // Opted-out flush: advances the timestamp watermark to Date.now() and
2384
2323
  // must also pin the ID watermark to the high-sorting sentinel.
2385
2324
  mockGetCachedShareAnalytics.mockReturnValue(false);
2386
- const optedOutReporter = new UsageTelemetryReporter();
2325
+ const optedOutReporter = makeReporter();
2387
2326
  await optedOutReporter.flush();
2388
2327
  expect(mockFetch).not.toHaveBeenCalled();
2389
2328
  const watermark = Number(
@@ -2402,7 +2341,7 @@ describe("UsageTelemetryReporter", () => {
2402
2341
  // Re-opt-in: only rows strictly after the opt-out epoch ship.
2403
2342
  mockGetCachedShareAnalytics.mockReturnValue(true);
2404
2343
  seedToolInvocation({ id: "ti-after-opt-in", createdAt: watermark + 1000 });
2405
- const reporter = new UsageTelemetryReporter();
2344
+ const reporter = makeReporter();
2406
2345
  await reporter.flush();
2407
2346
 
2408
2347
  expect(mockFetch).toHaveBeenCalledTimes(1);
@@ -2414,21 +2353,61 @@ describe("UsageTelemetryReporter", () => {
2414
2353
  ).toEqual(["ti-after-opt-in"]);
2415
2354
  });
2416
2355
 
2356
+ test("a turn-only (daemon) reporter never touches the tool_executed watermark", () => {
2357
+ new UsageTelemetryReporter(
2358
+ DAEMON_TELEMETRY_EVENT_SOURCES,
2359
+ fakeFlushCheckpointStore,
2360
+ );
2361
+ expect(mockGetFlushCheckpoint).not.toHaveBeenCalled();
2362
+ expect(mockSetFlushCheckpoint).not.toHaveBeenCalled();
2363
+ });
2364
+
2365
+ test("a monitor reporter re-runs the guarded tool_executed watermark init as a backstop", () => {
2366
+ new UsageTelemetryReporter(
2367
+ MONITOR_TELEMETRY_EVENT_SOURCES,
2368
+ fakeFlushCheckpointStore,
2369
+ );
2370
+ expect(mockGetFlushCheckpoint).toHaveBeenCalledWith(
2371
+ "telemetry:tool_executed:last_reported_at",
2372
+ );
2373
+ expect(mockSetFlushCheckpoint).toHaveBeenCalledTimes(1);
2374
+ });
2375
+
2376
+ test("initToolExecutedWatermarkIfAbsent sets the epoch once and never overwrites", () => {
2377
+ const checkpoints = useStatefulCheckpoints();
2378
+ initToolExecutedWatermarkIfAbsent(fakeFlushCheckpointStore);
2379
+ const epoch = checkpoints.get("telemetry:tool_executed:last_reported_at");
2380
+ expect(epoch).toBeString();
2381
+
2382
+ initToolExecutedWatermarkIfAbsent(fakeFlushCheckpointStore);
2383
+ expect(checkpoints.get("telemetry:tool_executed:last_reported_at")).toBe(
2384
+ epoch!,
2385
+ );
2386
+
2387
+ // A store failure is non-fatal (degraded-DB daemons still start).
2388
+ mockGetFlushCheckpoint.mockImplementation(() => {
2389
+ throw new Error("database disk image is malformed");
2390
+ });
2391
+ expect(() =>
2392
+ initToolExecutedWatermarkIfAbsent(fakeFlushCheckpointStore),
2393
+ ).not.toThrow();
2394
+ });
2395
+
2417
2396
  test("checkpoint store failure during construction is non-fatal — degraded-DB daemons still start", () => {
2418
2397
  // initializeDb() failures are tolerated at daemon startup (degraded
2419
2398
  // mode), so the constructor's checkpoint init must never throw out of
2420
2399
  // the constructor and abort the daemon.
2421
- mockGetMemoryCheckpoint.mockImplementation(() => {
2400
+ mockGetFlushCheckpoint.mockImplementation(() => {
2422
2401
  throw new Error("database disk image is malformed");
2423
2402
  });
2424
- expect(() => new UsageTelemetryReporter()).not.toThrow();
2403
+ expect(() => makeReporter()).not.toThrow();
2425
2404
 
2426
2405
  // The write path failing is equally non-fatal.
2427
- mockGetMemoryCheckpoint.mockImplementation(() => null);
2428
- mockSetMemoryCheckpoint.mockImplementation(() => {
2406
+ mockGetFlushCheckpoint.mockImplementation(() => null);
2407
+ mockSetFlushCheckpoint.mockImplementation(() => {
2429
2408
  throw new Error("database disk image is malformed");
2430
2409
  });
2431
- expect(() => new UsageTelemetryReporter()).not.toThrow();
2410
+ expect(() => makeReporter()).not.toThrow();
2432
2411
  });
2433
2412
 
2434
2413
  // -------------------------------------------------------------------------
@@ -2452,7 +2431,7 @@ describe("UsageTelemetryReporter", () => {
2452
2431
  Promise.resolve(new Response('{"accepted":2}', { status: 200 })),
2453
2432
  );
2454
2433
 
2455
- const reporter = new UsageTelemetryReporter();
2434
+ const reporter = makeReporter();
2456
2435
  await reporter.flush();
2457
2436
 
2458
2437
  expect(mockFetch).toHaveBeenCalledTimes(1);
@@ -2491,59 +2470,637 @@ describe("UsageTelemetryReporter", () => {
2491
2470
  });
2492
2471
  });
2493
2472
 
2494
- test("skill_loaded watermark advances to the last reported row on success", async () => {
2473
+ test("skill_loaded rows are deleted from the outbox after a 2xx, never via watermarks", async () => {
2495
2474
  mockQueryUnreportedUsageEvents.mockReturnValue([]);
2496
2475
  recordSkillLoadedEvent({ skillName: "web-research" });
2497
2476
  recordSkillLoadedEvent({ skillName: "tasks" });
2477
+ expect(queryTelemetryOutboxBatch("skill_loaded", 10)).toHaveLength(2);
2498
2478
  mockFetch.mockImplementation(() =>
2499
2479
  Promise.resolve(new Response('{"accepted":2}', { status: 200 })),
2500
2480
  );
2501
2481
 
2502
- // The last row by the reporter's (createdAt, id) cursor order is the one
2503
- // whose watermark should be persisted after a successful upload.
2504
- const rows = getDb()
2505
- .select()
2506
- .from(skillLoadedEvents)
2507
- .orderBy(skillLoadedEvents.createdAt, skillLoadedEvents.id)
2508
- .all();
2509
- const lastRow = rows[rows.length - 1];
2482
+ const reporter = makeReporter();
2483
+ await reporter.flush();
2484
+
2485
+ expect(mockFetch).toHaveBeenCalledTimes(1);
2486
+ expect(queryTelemetryOutboxBatch("skill_loaded", 10)).toHaveLength(0);
2487
+ // Ack-mode: the skill_loaded source never touches flush_checkpoints.
2488
+ expect(
2489
+ mockSetFlushCheckpoint.mock.calls.some((c) =>
2490
+ c[0].includes("skill_loaded"),
2491
+ ),
2492
+ ).toBe(false);
2493
+ });
2494
+
2495
+ test("a full skill_loaded batch drives recursion until the outbox drains", async () => {
2496
+ mockQueryUnreportedUsageEvents.mockReturnValue([]);
2497
+ // BATCH_SIZE (500) + 1 rows: the first flush ships and deletes a full
2498
+ // batch, and the fullBatch recursion ships the remaining row.
2499
+ for (let i = 0; i < 501; i++) {
2500
+ recordSkillLoadedEvent({ skillName: `skill-${i}` });
2501
+ }
2502
+ mockFetch.mockImplementation(() =>
2503
+ Promise.resolve(new Response('{"accepted":500}', { status: 200 })),
2504
+ );
2510
2505
 
2511
- const reporter = new UsageTelemetryReporter();
2506
+ const reporter = makeReporter();
2512
2507
  await reporter.flush();
2513
2508
 
2514
- const watermarkCalls = mockSetMemoryCheckpoint.mock.calls.filter(
2515
- (c) => c[0] === "telemetry:skill_loaded:last_reported_at",
2509
+ expect(mockFetch).toHaveBeenCalledTimes(2);
2510
+ expect(queryTelemetryOutboxBatch("skill_loaded", 1000)).toHaveLength(0);
2511
+ });
2512
+
2513
+ // -------------------------------------------------------------------------
2514
+ // Config-setting events
2515
+ // -------------------------------------------------------------------------
2516
+
2517
+ test("config_setting events ship the key/value pair on the standard envelope", async () => {
2518
+ mockQueryUnreportedUsageEvents.mockReturnValue([]);
2519
+ recordConfigSettingEvent({
2520
+ configKey: "memory.enabled",
2521
+ configValue: "true",
2522
+ });
2523
+ recordConfigSettingEvent({
2524
+ configKey: "memory.v2.enabled",
2525
+ configValue: "false",
2526
+ });
2527
+ mockFetch.mockImplementation(() =>
2528
+ Promise.resolve(new Response('{"accepted":2}', { status: 200 })),
2516
2529
  );
2517
- expect(watermarkCalls.length).toBeGreaterThanOrEqual(1);
2518
- expect(watermarkCalls[watermarkCalls.length - 1][1]).toBe(
2519
- String(lastRow.createdAt),
2530
+
2531
+ const reporter = makeReporter();
2532
+ await reporter.flush();
2533
+
2534
+ expect(mockFetch).toHaveBeenCalledTimes(1);
2535
+ const body = JSON.parse(
2536
+ (mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
2520
2537
  );
2538
+ expect(body.events.length).toBe(2);
2539
+
2540
+ const byKey: Record<string, Record<string, unknown>> = {};
2541
+ for (const e of body.events as Array<{ config_key: string }>) {
2542
+ byKey[e.config_key] = e;
2543
+ }
2521
2544
 
2522
- const idCalls = mockSetMemoryCheckpoint.mock.calls.filter(
2523
- (c) => c[0] === "telemetry:skill_loaded:last_reported_id",
2545
+ expect(byKey["memory.enabled"]).toMatchObject({
2546
+ type: "config_setting",
2547
+ config_key: "memory.enabled",
2548
+ config_value: "true",
2549
+ assistant_version: "1.2.3-test",
2550
+ });
2551
+ expect(typeof byKey["memory.enabled"].daemon_event_id).toBe("string");
2552
+ expect(typeof byKey["memory.enabled"].recorded_at).toBe("number");
2553
+ expect(byKey["memory.v2.enabled"]).toMatchObject({
2554
+ type: "config_setting",
2555
+ config_key: "memory.v2.enabled",
2556
+ config_value: "false",
2557
+ });
2558
+ });
2559
+
2560
+ test("config_setting rows are deleted on success and no watermark is written", async () => {
2561
+ mockQueryUnreportedUsageEvents.mockReturnValue([]);
2562
+ recordConfigSettingEvent({
2563
+ configKey: "memory.enabled",
2564
+ configValue: "true",
2565
+ });
2566
+ recordConfigSettingEvent({
2567
+ configKey: "memory.v2.enabled",
2568
+ configValue: "true",
2569
+ });
2570
+ expect(queryTelemetryOutboxBatch("config_setting", 10)).toHaveLength(2);
2571
+ mockFetch.mockImplementation(() =>
2572
+ Promise.resolve(new Response('{"accepted":2}', { status: 200 })),
2524
2573
  );
2525
- expect(idCalls.length).toBeGreaterThanOrEqual(1);
2526
- expect(idCalls[idCalls.length - 1][1]).toBe(lastRow.id);
2574
+
2575
+ const reporter = makeReporter();
2576
+ await reporter.flush();
2577
+
2578
+ // Ack-mode: acknowledged rows are deleted from the outbox instead of
2579
+ // advancing a `flush_checkpoints` watermark.
2580
+ expect(queryTelemetryOutboxBatch("config_setting", 10)).toHaveLength(0);
2581
+ const watermarkKeys = mockSetFlushCheckpoint.mock.calls
2582
+ .map((c) => c[0] as string)
2583
+ .filter((key) => key.startsWith("telemetry:config_setting:"));
2584
+ expect(watermarkKeys).toHaveLength(0);
2527
2585
  });
2528
2586
 
2529
- test("batch recursion when skill_loaded returns a full batch, capped at 10", async () => {
2587
+ test("config_setting rows survive a failed POST for the next flush", async () => {
2588
+ mockQueryUnreportedUsageEvents.mockReturnValue([]);
2589
+ recordConfigSettingEvent({
2590
+ configKey: "memory.enabled",
2591
+ configValue: "true",
2592
+ });
2593
+ mockFetch.mockImplementation(() =>
2594
+ Promise.resolve(new Response("error", { status: 500 })),
2595
+ );
2596
+
2597
+ const reporter = makeReporter();
2598
+ await reporter.flush();
2599
+
2600
+ expect(queryTelemetryOutboxBatch("config_setting", 10)).toHaveLength(1);
2601
+ });
2602
+
2603
+ // -------------------------------------------------------------------------
2604
+ // Watchdog events
2605
+ // -------------------------------------------------------------------------
2606
+
2607
+ test("watchdog events ship the record-time payload with nested detail, then delete", async () => {
2530
2608
  mockQueryUnreportedUsageEvents.mockReturnValue([]);
2531
- // Exactly BATCH_SIZE rows; the mocked checkpoints never persist, so each
2532
- // recursion re-reads the same full batch until the cap.
2533
- const fullBatch = Array.from({ length: 500 }, (_, i) => ({
2534
- id: `sle-batch-${String(i).padStart(3, "0")}`,
2609
+ recordWatchdogEvent({
2610
+ checkName: "event_loop_blocked",
2611
+ value: 60000,
2612
+ detail: { reason: "no_bytes_60s", threshold_ms: 5000 },
2613
+ });
2614
+ mockFetch.mockImplementation(() =>
2615
+ Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
2616
+ );
2617
+
2618
+ const reporter = makeReporter();
2619
+ await reporter.flush();
2620
+
2621
+ expect(mockFetch).toHaveBeenCalledTimes(1);
2622
+ const body = JSON.parse(
2623
+ (mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
2624
+ );
2625
+ expect(body.events).toHaveLength(1);
2626
+ expect(body.events[0]).toMatchObject({
2627
+ type: "watchdog",
2628
+ check_name: "event_loop_blocked",
2629
+ value: 60000,
2630
+ detail: { reason: "no_bytes_60s", threshold_ms: 5000 },
2631
+ assistant_version: "1.2.3-test",
2632
+ });
2633
+ expect(typeof body.events[0].daemon_event_id).toBe("string");
2634
+ expect(typeof body.events[0].recorded_at).toBe("number");
2635
+ expect(queryTelemetryOutboxBatch("watchdog", 10)).toHaveLength(0);
2636
+ });
2637
+
2638
+ // -------------------------------------------------------------------------
2639
+ // Ack-mode sources (grouped `ack` field: acknowledge + discardPending)
2640
+ // -------------------------------------------------------------------------
2641
+
2642
+ // Wire id deliberately differs from the row id: acknowledge must receive
2643
+ // ROW ids, never wire daemon_event_ids.
2644
+ function fakeWireEvent(rowId: string, createdAt: number): TelemetryEvent {
2645
+ return {
2646
+ type: "lifecycle",
2647
+ daemon_event_id: `wire-${rowId}`,
2648
+ event_name: "fake_event",
2649
+ recorded_at: createdAt,
2650
+ assistant_version: "1.2.3-test",
2651
+ };
2652
+ }
2653
+
2654
+ /**
2655
+ * Ack-mode fake backed by a mutable in-memory row array: `collect` ignores
2656
+ * the cursor args, `acknowledge` deletes the acknowledged rows, and
2657
+ * `discardPending` drops everything — mirroring the outbox-store contract.
2658
+ */
2659
+ function makeAckSource(id: string, rowIds: string[] = []) {
2660
+ let rows = rowIds.map((rowId, i) => ({
2661
+ rowId,
2535
2662
  createdAt: 1700000000000 + i,
2536
- skillName: "web-research",
2537
2663
  }));
2538
- getDb().insert(skillLoadedEvents).values(fullBatch).run();
2664
+ const acknowledge = mock((acked: string[]) => {
2665
+ rows = rows.filter((r) => !acked.includes(r.rowId));
2666
+ });
2667
+ const discardPending = mock(() => {
2668
+ rows = [];
2669
+ });
2670
+ const source: TelemetryEventSource = {
2671
+ id,
2672
+ collect(_afterCreatedAt, _afterId, limit) {
2673
+ const batch = rows.slice(0, limit);
2674
+ return {
2675
+ events: batch.map((r) => fakeWireEvent(r.rowId, r.createdAt)),
2676
+ rowIds: batch.map((r) => r.rowId),
2677
+ lastCursor: null,
2678
+ fullBatch: batch.length === limit,
2679
+ };
2680
+ },
2681
+ ack: { acknowledge, discardPending },
2682
+ };
2683
+ return { source, acknowledge, discardPending, pending: () => rows.length };
2684
+ }
2685
+
2686
+ function makeWatermarkSource(
2687
+ id: string,
2688
+ rowIds: string[] = [],
2689
+ ): TelemetryEventSource {
2690
+ const rows = rowIds.map((rowId, i) => ({
2691
+ rowId,
2692
+ createdAt: 1700000000000 + i,
2693
+ }));
2694
+ return {
2695
+ id,
2696
+ collect(_afterCreatedAt, _afterId, limit) {
2697
+ const batch = rows.slice(0, limit);
2698
+ const last = batch.length > 0 ? batch[batch.length - 1] : null;
2699
+ return {
2700
+ events: batch.map((r) => fakeWireEvent(r.rowId, r.createdAt)),
2701
+ lastCursor: last
2702
+ ? { createdAt: last.createdAt, id: last.rowId }
2703
+ : null,
2704
+ fullBatch: batch.length === limit,
2705
+ };
2706
+ },
2707
+ };
2708
+ }
2709
+
2710
+ test("ack-mode source: acknowledge receives ROW ids after a 2xx and no watermark keys are written", async () => {
2711
+ const { source, acknowledge } = makeAckSource("fake_ack", [
2712
+ "row-1",
2713
+ "row-2",
2714
+ ]);
2715
+ const reporter = new UsageTelemetryReporter(
2716
+ [source],
2717
+ fakeFlushCheckpointStore,
2718
+ );
2719
+ await reporter.flush();
2720
+
2721
+ expect(mockFetch).toHaveBeenCalledTimes(1);
2722
+ const body = JSON.parse(
2723
+ (mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
2724
+ );
2725
+ expect(
2726
+ body.events.map((e: { daemon_event_id: string }) => e.daemon_event_id),
2727
+ ).toEqual(["wire-row-1", "wire-row-2"]);
2728
+ expect(acknowledge).toHaveBeenCalledTimes(1);
2729
+ expect(acknowledge.mock.calls[0][0]).toEqual(["row-1", "row-2"]);
2730
+ // Ack-mode sources never touch flush_checkpoints.
2731
+ expect(mockSetFlushCheckpoint).not.toHaveBeenCalled();
2732
+ });
2733
+
2734
+ test("acknowledge is NOT called when the POST fails", async () => {
2539
2735
  mockFetch.mockImplementation(() =>
2540
- Promise.resolve(new Response('{"accepted":500}', { status: 200 })),
2736
+ Promise.resolve(new Response("error", { status: 500 })),
2737
+ );
2738
+ const { source, acknowledge, pending } = makeAckSource("fake_ack", [
2739
+ "row-1",
2740
+ ]);
2741
+ const reporter = new UsageTelemetryReporter(
2742
+ [source],
2743
+ fakeFlushCheckpointStore,
2541
2744
  );
2745
+ await reporter.flush();
2746
+
2747
+ expect(acknowledge).not.toHaveBeenCalled();
2748
+ expect(pending()).toBe(1);
2749
+ expect(mockSetFlushCheckpoint).not.toHaveBeenCalled();
2750
+ });
2542
2751
 
2543
- const reporter = new UsageTelemetryReporter();
2752
+ test("acknowledge is NOT called when platform credentials are missing", async () => {
2753
+ mockPlatformClient = null;
2754
+ const { source, acknowledge, pending } = makeAckSource("fake_ack", [
2755
+ "row-1",
2756
+ ]);
2757
+ const reporter = new UsageTelemetryReporter(
2758
+ [source],
2759
+ fakeFlushCheckpointStore,
2760
+ );
2544
2761
  await reporter.flush();
2545
2762
 
2546
- // MAX_CONSECUTIVE_BATCHES = 10
2547
- expect(mockFetch).toHaveBeenCalledTimes(10);
2763
+ expect(mockFetch).not.toHaveBeenCalled();
2764
+ expect(acknowledge).not.toHaveBeenCalled();
2765
+ expect(pending()).toBe(1);
2766
+ });
2767
+
2768
+ test("acknowledge is skipped for an empty ack batch shipped alongside a watermark source", async () => {
2769
+ const { source: ackSource, acknowledge } = makeAckSource("fake_ack");
2770
+ const wmSource = makeWatermarkSource("fake_wm", ["wm-1"]);
2771
+ const reporter = new UsageTelemetryReporter(
2772
+ [wmSource, ackSource],
2773
+ fakeFlushCheckpointStore,
2774
+ );
2775
+ await reporter.flush();
2776
+
2777
+ expect(mockFetch).toHaveBeenCalledTimes(1);
2778
+ expect(acknowledge).not.toHaveBeenCalled();
2779
+ });
2780
+
2781
+ test("opt-out: discardPending drops ack-mode rows while watermark sources get the sentinel pin", async () => {
2782
+ mockGetCachedShareAnalytics.mockReturnValue(false);
2783
+ const {
2784
+ source: ackSource,
2785
+ discardPending,
2786
+ pending,
2787
+ } = makeAckSource("fake_ack", ["row-1"]);
2788
+ const wmSource = makeWatermarkSource("fake_wm", ["wm-1"]);
2789
+ const reporter = new UsageTelemetryReporter(
2790
+ [wmSource, ackSource],
2791
+ fakeFlushCheckpointStore,
2792
+ );
2793
+ await reporter.flush();
2794
+
2795
+ expect(mockFetch).not.toHaveBeenCalled();
2796
+ expect(discardPending).toHaveBeenCalledTimes(1);
2797
+ expect(pending()).toBe(0);
2798
+
2799
+ const keys = mockSetFlushCheckpoint.mock.calls.map((c) => c[0]);
2800
+ expect(keys).toContain("telemetry:fake_wm:last_reported_at");
2801
+ const wmIdCall = mockSetFlushCheckpoint.mock.calls.find(
2802
+ (c) => c[0] === "telemetry:fake_wm:last_reported_id",
2803
+ );
2804
+ expect(wmIdCall?.[1]).toBe("ffffffff-ffff-ffff-ffff-ffffffffffff");
2805
+ expect(keys.some((k) => k.includes("fake_ack"))).toBe(false);
2806
+ });
2807
+
2808
+ test("mixed watermark+ack sources flush in construction order, each acknowledged in its own mode", async () => {
2809
+ const wmSource = makeWatermarkSource("fake_wm", ["wm-1", "wm-2"]);
2810
+ const { source: ackSource, acknowledge } = makeAckSource("fake_ack", [
2811
+ "row-1",
2812
+ ]);
2813
+ const reporter = new UsageTelemetryReporter(
2814
+ [wmSource, ackSource],
2815
+ fakeFlushCheckpointStore,
2816
+ );
2817
+ await reporter.flush();
2818
+
2819
+ expect(mockFetch).toHaveBeenCalledTimes(1);
2820
+ const body = JSON.parse(
2821
+ (mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
2822
+ );
2823
+ expect(
2824
+ body.events.map((e: { daemon_event_id: string }) => e.daemon_event_id),
2825
+ ).toEqual(["wire-wm-1", "wire-wm-2", "wire-row-1"]);
2826
+
2827
+ expect(acknowledge).toHaveBeenCalledTimes(1);
2828
+ expect(acknowledge.mock.calls[0][0]).toEqual(["row-1"]);
2829
+
2830
+ const wmAtCalls = mockSetFlushCheckpoint.mock.calls.filter(
2831
+ (c) => c[0] === "telemetry:fake_wm:last_reported_at",
2832
+ );
2833
+ expect(wmAtCalls[wmAtCalls.length - 1][1]).toBe(String(1700000000001));
2834
+ const wmIdCalls = mockSetFlushCheckpoint.mock.calls.filter(
2835
+ (c) => c[0] === "telemetry:fake_wm:last_reported_id",
2836
+ );
2837
+ expect(wmIdCalls[wmIdCalls.length - 1][1]).toBe("wm-2");
2838
+ expect(
2839
+ mockSetFlushCheckpoint.mock.calls.some((c) => c[0].includes("fake_ack")),
2840
+ ).toBe(false);
2841
+ });
2842
+
2843
+ test("an ack-mode full batch drives recursion until the backlog drains", async () => {
2844
+ // 501 rows: the first collect fills BATCH_SIZE (500), acknowledge drains
2845
+ // them, and the fullBatch recursion ships the remaining row.
2846
+ const rowIds = Array.from({ length: 501 }, (_, i) => `row-${i}`);
2847
+ const { source, acknowledge, pending } = makeAckSource("fake_ack", rowIds);
2848
+ const reporter = new UsageTelemetryReporter(
2849
+ [source],
2850
+ fakeFlushCheckpointStore,
2851
+ );
2852
+ await reporter.flush();
2853
+
2854
+ expect(mockFetch).toHaveBeenCalledTimes(2);
2855
+ expect(acknowledge).toHaveBeenCalledTimes(2);
2856
+ expect(acknowledge.mock.calls[0][0]).toHaveLength(500);
2857
+ expect(acknowledge.mock.calls[1][0]).toEqual(["row-500"]);
2858
+ expect(pending()).toBe(0);
2859
+ });
2860
+
2861
+ /** Ack source whose collect returns events with broken rowIds. */
2862
+ function makeBrokenAckSource(id: string, rowIds: string[] | undefined) {
2863
+ const acknowledge = mock((_acked: string[]) => {});
2864
+ const discardPending = mock(() => {});
2865
+ const source: TelemetryEventSource = {
2866
+ id,
2867
+ collect() {
2868
+ return {
2869
+ events: [
2870
+ fakeWireEvent("row-1", 1700000000000),
2871
+ fakeWireEvent("row-2", 1700000000001),
2872
+ ],
2873
+ ...(rowIds ? { rowIds } : {}),
2874
+ lastCursor: null,
2875
+ fullBatch: false,
2876
+ };
2877
+ },
2878
+ ack: { acknowledge, discardPending },
2879
+ };
2880
+ return { source, acknowledge };
2881
+ }
2882
+
2883
+ test("an ack batch omitting rowIds ships nothing while other sources still ship", async () => {
2884
+ const { source: brokenSource, acknowledge } = makeBrokenAckSource(
2885
+ "fake_broken_ack",
2886
+ undefined,
2887
+ );
2888
+ const wmSource = makeWatermarkSource("fake_wm", ["wm-1"]);
2889
+ const reporter = new UsageTelemetryReporter(
2890
+ [brokenSource, wmSource],
2891
+ fakeFlushCheckpointStore,
2892
+ );
2893
+ await reporter.flush();
2894
+
2895
+ expect(mockFetch).toHaveBeenCalledTimes(1);
2896
+ const body = JSON.parse(
2897
+ (mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
2898
+ );
2899
+ expect(
2900
+ body.events.map((e: { daemon_event_id: string }) => e.daemon_event_id),
2901
+ ).toEqual(["wire-wm-1"]);
2902
+ expect(acknowledge).not.toHaveBeenCalled();
2903
+ expect(
2904
+ mockSetFlushCheckpoint.mock.calls.some((c) =>
2905
+ c[0].includes("fake_broken_ack"),
2906
+ ),
2907
+ ).toBe(false);
2908
+ });
2909
+
2910
+ test("an ack batch with mismatched rowIds ships nothing while other sources still ship", async () => {
2911
+ const { source: brokenSource, acknowledge } = makeBrokenAckSource(
2912
+ "fake_broken_ack",
2913
+ ["row-1"],
2914
+ );
2915
+ const wmSource = makeWatermarkSource("fake_wm", ["wm-1"]);
2916
+ const reporter = new UsageTelemetryReporter(
2917
+ [brokenSource, wmSource],
2918
+ fakeFlushCheckpointStore,
2919
+ );
2920
+ await reporter.flush();
2921
+
2922
+ expect(mockFetch).toHaveBeenCalledTimes(1);
2923
+ const body = JSON.parse(
2924
+ (mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
2925
+ );
2926
+ expect(
2927
+ body.events.map((e: { daemon_event_id: string }) => e.daemon_event_id),
2928
+ ).toEqual(["wire-wm-1"]);
2929
+ expect(acknowledge).not.toHaveBeenCalled();
2930
+ });
2931
+
2932
+ // -------------------------------------------------------------------------
2933
+ // outboxSource (real telemetry_events outbox behind an ack-mode source)
2934
+ // -------------------------------------------------------------------------
2935
+
2936
+ // Synthetic name: these tests exercise the generic outbox machinery, not a
2937
+ // shipped event type.
2938
+ const OUTBOX_NAME = "test_outbox" as OutboxTelemetryEventName;
2939
+
2940
+ function seedOutboxRow(id: string, createdAt: number): void {
2941
+ insertTelemetryOutboxEvent({
2942
+ id,
2943
+ name: OUTBOX_NAME,
2944
+ createdAt,
2945
+ event: fakeWireEvent(id, createdAt),
2946
+ });
2947
+ }
2948
+
2949
+ function pendingOutboxIds(): string[] {
2950
+ return queryTelemetryOutboxBatch(OUTBOX_NAME, 1000).map((r) => r.id);
2951
+ }
2952
+
2953
+ test("outboxSource ships pending rows and deletes them after a 2xx", async () => {
2954
+ seedOutboxRow("ob-1", 1700000001000);
2955
+ seedOutboxRow("ob-2", 1700000002000);
2956
+ const reporter = new UsageTelemetryReporter(
2957
+ [outboxSource(OUTBOX_NAME)],
2958
+ fakeFlushCheckpointStore,
2959
+ );
2960
+ await reporter.flush();
2961
+
2962
+ expect(mockFetch).toHaveBeenCalledTimes(1);
2963
+ const body = JSON.parse(
2964
+ (mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
2965
+ );
2966
+ expect(
2967
+ body.events.map((e: { daemon_event_id: string }) => e.daemon_event_id),
2968
+ ).toEqual(["wire-ob-1", "wire-ob-2"]);
2969
+ expect(pendingOutboxIds()).toEqual([]);
2970
+ // Ack-mode: flush_checkpoints untouched.
2971
+ expect(mockSetFlushCheckpoint).not.toHaveBeenCalled();
2972
+ });
2973
+
2974
+ test("outboxSource leaves rows intact when the POST fails", async () => {
2975
+ mockFetch.mockImplementation(() =>
2976
+ Promise.resolve(new Response("error", { status: 500 })),
2977
+ );
2978
+ seedOutboxRow("ob-1", 1700000001000);
2979
+ const reporter = new UsageTelemetryReporter(
2980
+ [outboxSource(OUTBOX_NAME)],
2981
+ fakeFlushCheckpointStore,
2982
+ );
2983
+ await reporter.flush();
2984
+
2985
+ expect(pendingOutboxIds()).toEqual(["ob-1"]);
2986
+ expect(mockSetFlushCheckpoint).not.toHaveBeenCalled();
2987
+ });
2988
+
2989
+ test("outboxSource opt-out discards all pending rows for the name", async () => {
2990
+ mockGetCachedShareAnalytics.mockReturnValue(false);
2991
+ seedOutboxRow("ob-1", 1700000001000);
2992
+ seedOutboxRow("ob-2", 1700000002000);
2993
+ const reporter = new UsageTelemetryReporter(
2994
+ [outboxSource(OUTBOX_NAME)],
2995
+ fakeFlushCheckpointStore,
2996
+ );
2997
+ await reporter.flush();
2998
+
2999
+ expect(mockFetch).not.toHaveBeenCalled();
3000
+ expect(pendingOutboxIds()).toEqual([]);
3001
+ expect(mockSetFlushCheckpoint).not.toHaveBeenCalled();
3002
+ });
3003
+
3004
+ test("outboxSource purges corrupt-payload rows at collect while valid siblings ship", async () => {
3005
+ // Raw inserts: the store API only writes JSON.stringify-ed events, so
3006
+ // corrupt payloads (invalid JSON, non-object JSON) go in directly.
3007
+ getTelemetryDb()!
3008
+ .insert(telemetryEvents)
3009
+ .values([
3010
+ {
3011
+ id: "ob-corrupt-json",
3012
+ name: OUTBOX_NAME,
3013
+ createdAt: 1700000001000,
3014
+ conversationId: null,
3015
+ payload: "{not json",
3016
+ },
3017
+ {
3018
+ id: "ob-non-object",
3019
+ name: OUTBOX_NAME,
3020
+ createdAt: 1700000002000,
3021
+ conversationId: null,
3022
+ payload: "42",
3023
+ },
3024
+ ])
3025
+ .run();
3026
+ seedOutboxRow("ob-valid", 1700000003000);
3027
+ const reporter = new UsageTelemetryReporter(
3028
+ [outboxSource(OUTBOX_NAME)],
3029
+ fakeFlushCheckpointStore,
3030
+ );
3031
+ await reporter.flush();
3032
+
3033
+ expect(mockFetch).toHaveBeenCalledTimes(1);
3034
+ const body = JSON.parse(
3035
+ (mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
3036
+ );
3037
+ expect(
3038
+ body.events.map((e: { daemon_event_id: string }) => e.daemon_event_id),
3039
+ ).toEqual(["wire-ob-valid"]);
3040
+ // Corrupt rows are gone (purged at collect), the valid row via ack.
3041
+ expect(pendingOutboxIds()).toEqual([]);
3042
+ });
3043
+
3044
+ test("a full outbox batch drives recursion until the backlog drains", async () => {
3045
+ // 501 rows: the first collect fills BATCH_SIZE (500) and the fullBatch
3046
+ // recursion ships the remaining row.
3047
+ for (let i = 0; i < 501; i++) {
3048
+ seedOutboxRow(`ob-${String(i).padStart(3, "0")}`, 1700000000000 + i);
3049
+ }
3050
+ const reporter = new UsageTelemetryReporter(
3051
+ [outboxSource(OUTBOX_NAME)],
3052
+ fakeFlushCheckpointStore,
3053
+ );
3054
+ await reporter.flush();
3055
+
3056
+ expect(mockFetch).toHaveBeenCalledTimes(2);
3057
+ const secondBody = JSON.parse(
3058
+ (mockFetch.mock.calls[1] as [string, RequestInit])[1].body as string,
3059
+ );
3060
+ expect(secondBody.events).toHaveLength(1);
3061
+ expect(pendingOutboxIds()).toEqual([]);
3062
+ });
3063
+
3064
+ // -------------------------------------------------------------------------
3065
+ // Lifecycle events (outbox-backed)
3066
+ // -------------------------------------------------------------------------
3067
+
3068
+ test("a recorded lifecycle event flushes once and its outbox row is deleted", async () => {
3069
+ mockQueryUnreportedUsageEvents.mockReturnValue([]);
3070
+ const recorded = recordLifecycleEvent("app_open");
3071
+ expect(recorded).not.toBeNull();
3072
+ mockFetch.mockImplementation(() =>
3073
+ Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
3074
+ );
3075
+
3076
+ const reporter = makeReporter();
3077
+ await reporter.flush();
3078
+
3079
+ expect(mockFetch).toHaveBeenCalledTimes(1);
3080
+ const body = JSON.parse(
3081
+ (mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
3082
+ );
3083
+ expect(body.events).toHaveLength(1);
3084
+ // The stored payload IS the wire event — record-time version included.
3085
+ expect(body.events[0]).toEqual({
3086
+ type: "lifecycle",
3087
+ daemon_event_id: recorded!.id,
3088
+ event_name: "app_open",
3089
+ recorded_at: recorded!.createdAt,
3090
+ assistant_version: "1.2.3-test",
3091
+ });
3092
+ // Delete-on-flush: the outbox row is gone, and lifecycle (ack-mode)
3093
+ // never touches flush_checkpoints.
3094
+ expect(queryTelemetryOutboxBatch("lifecycle", 10)).toEqual([]);
3095
+ expect(
3096
+ mockSetFlushCheckpoint.mock.calls.some((c) =>
3097
+ c[0].includes(":lifecycle:"),
3098
+ ),
3099
+ ).toBe(false);
3100
+
3101
+ // A second flush finds nothing pending — the event never re-ships.
3102
+ mockFetch.mockClear();
3103
+ await reporter.flush();
3104
+ expect(mockFetch).not.toHaveBeenCalled();
2548
3105
  });
2549
3106
  });