@vellumai/assistant 0.8.12 → 0.9.0-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (442) hide show
  1. package/AGENTS.md +0 -14
  2. package/ARCHITECTURE.md +45 -45
  3. package/README.md +1 -1
  4. package/bun.lock +200 -154
  5. package/docs/architecture/integrations.md +3 -3
  6. package/docs/architecture/memory.md +2 -2
  7. package/docs/architecture/security.md +10 -10
  8. package/docs/runbook-trusted-contacts.md +12 -12
  9. package/docs/skills.md +6 -6
  10. package/docs/workflows-testing.md +221 -0
  11. package/docs/workflows.md +510 -0
  12. package/examples/plugins/echo/README.md +5 -5
  13. package/knip.json +2 -0
  14. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +105 -0
  15. package/node_modules/@vellumai/gateway-client/src/index.ts +12 -0
  16. package/openapi.yaml +7197 -5708
  17. package/package.json +8 -4
  18. package/scripts/generate-openapi.ts +66 -114
  19. package/src/__tests__/access-request-seed-content-blocks.test.ts +213 -0
  20. package/src/__tests__/adaptive-thinking-repair.test.ts +32 -3
  21. package/src/__tests__/agent-loop-output-hooks.test.ts +183 -0
  22. package/src/__tests__/agent-loop-regrowth-guard.test.ts +506 -0
  23. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  24. package/src/__tests__/agent-wake-override-profile.test.ts +77 -0
  25. package/src/__tests__/app-compiler.test.ts +7 -1
  26. package/src/__tests__/app-dir-path-guard.test.ts +27 -3
  27. package/src/__tests__/app-executors.test.ts +43 -0
  28. package/src/__tests__/approval-cascade.test.ts +0 -5
  29. package/src/__tests__/approval-routes-http.test.ts +91 -0
  30. package/src/__tests__/assistant-stream-state.test.ts +107 -0
  31. package/src/__tests__/browser-fill-credential.test.ts +3 -3
  32. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -1
  33. package/src/__tests__/compaction-events.test.ts +63 -7
  34. package/src/__tests__/compaction-trail-store.test.ts +74 -1
  35. package/src/__tests__/compaction.benchmark.test.ts +63 -41
  36. package/src/__tests__/compactor-low-watermark-cut.test.ts +349 -0
  37. package/src/__tests__/context-window-manager-compact-retry.test.ts +64 -0
  38. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -5
  39. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -5
  40. package/src/__tests__/conversation-history-web-search.test.ts +7 -0
  41. package/src/__tests__/conversation-process-callsite.test.ts +0 -5
  42. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -5
  43. package/src/__tests__/conversation-queue.test.ts +0 -5
  44. package/src/__tests__/conversation-slash-queue.test.ts +0 -5
  45. package/src/__tests__/conversation-slash-unknown.test.ts +0 -5
  46. package/src/__tests__/conversation-speed-override.test.ts +0 -5
  47. package/src/__tests__/conversation-surfaces-data-persist.test.ts +97 -0
  48. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  49. package/src/__tests__/conversation-usage.test.ts +2 -0
  50. package/src/__tests__/conversation-workspace-injection.test.ts +0 -5
  51. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -5
  52. package/src/__tests__/credential-broker-browser-fill.test.ts +2 -2
  53. package/src/__tests__/credential-broker-server-use.test.ts +2 -2
  54. package/src/__tests__/credential-broker.test.ts +1 -1
  55. package/src/__tests__/credential-prompt-route.test.ts +417 -0
  56. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  57. package/src/__tests__/credential-vault.test.ts +37 -0
  58. package/src/__tests__/db-schedule-syntax-migration.test.ts +24 -0
  59. package/src/__tests__/dynamic-page-surface.test.ts +219 -0
  60. package/src/__tests__/empty-state-greeting-cache.test.ts +94 -0
  61. package/src/__tests__/gateway-flag-listener.test.ts +24 -7
  62. package/src/__tests__/guardian-action-sweep.test.ts +56 -219
  63. package/src/__tests__/guardian-routing-invariants.test.ts +138 -0
  64. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  65. package/src/__tests__/list-messages-hidden-metadata.test.ts +99 -0
  66. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +87 -1
  67. package/src/__tests__/llm-resolver.test.ts +115 -0
  68. package/src/__tests__/managed-profile-guard.test.ts +6 -5
  69. package/src/__tests__/max-tokens-continue-hook.test.ts +184 -0
  70. package/src/__tests__/media-generate-image.test.ts +20 -9
  71. package/src/__tests__/mock-gateway-ipc.ts +23 -0
  72. package/src/__tests__/model-intents.test.ts +1 -1
  73. package/src/__tests__/normalize-onboarding.test.ts +26 -0
  74. package/src/__tests__/notification-decision-strategy.test.ts +4 -2
  75. package/src/__tests__/notification-telegram-adapter.test.ts +21 -3
  76. package/src/__tests__/pending-interactions-resolved-event.test.ts +62 -0
  77. package/src/__tests__/post-turn-tool-result-truncation.test.ts +72 -18
  78. package/src/__tests__/require-fresh-approval.test.ts +425 -1
  79. package/src/__tests__/resolve-app-id.test.ts +56 -0
  80. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -0
  81. package/src/__tests__/schedule-routes-workflow-validation.test.ts +408 -0
  82. package/src/__tests__/schedule-routes.test.ts +257 -4
  83. package/src/__tests__/schedule-store.test.ts +60 -0
  84. package/src/__tests__/schedule-tools.test.ts +247 -2
  85. package/src/__tests__/skill-execute-input.test.ts +85 -0
  86. package/src/__tests__/skill-secret-handling-guard.test.ts +21 -20
  87. package/src/__tests__/skills.test.ts +3 -3
  88. package/src/__tests__/slack-app-setup-skill-regression.test.ts +1 -1
  89. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  90. package/src/__tests__/subagent-tool-gate-mode.test.ts +547 -0
  91. package/src/__tests__/system-prompt.test.ts +1 -1
  92. package/src/__tests__/task-progress-nudge-hook.test.ts +372 -0
  93. package/src/__tests__/task-scheduler.test.ts +299 -0
  94. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +209 -0
  95. package/src/__tests__/tool-result-spool.test.ts +3 -1
  96. package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +181 -0
  97. package/src/__tests__/workspace-migration-103-upgrade-quality-profile-to-opus-4-8.test.ts +174 -0
  98. package/src/agent/compaction-circuit.ts +11 -0
  99. package/src/agent/loop.ts +181 -12
  100. package/src/api/constants/call-sites.ts +12 -0
  101. package/src/api/events/assistant-thinking-delta.ts +10 -0
  102. package/src/api/events/usage-update.ts +7 -0
  103. package/src/api/index.ts +4 -1
  104. package/src/api/responses/memory-v3-selection-log.ts +18 -11
  105. package/src/approvals/approval-primitive.ts +2 -2
  106. package/src/background-wake/background-wake-routes.test.ts +5 -2
  107. package/src/bundler/compiler-tools.ts +1 -1
  108. package/src/bundler/package-resolver.ts +0 -1
  109. package/src/calls/call-domain.ts +1 -1
  110. package/src/calls/guardian-action-sweep.ts +16 -93
  111. package/src/cli/AGENTS.md +4 -0
  112. package/src/cli/commands/__tests__/schedules.test.ts +430 -1
  113. package/src/cli/commands/credentials.ts +28 -24
  114. package/src/cli/commands/image-generation.ts +23 -9
  115. package/src/cli/commands/notifications.ts +1 -1
  116. package/src/cli/commands/plugins.ts +89 -46
  117. package/src/cli/commands/schedules.ts +384 -11
  118. package/src/cli/lib/__tests__/inspect-plugin.test.ts +69 -5
  119. package/src/cli/lib/__tests__/install-from-github.test.ts +15 -0
  120. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +81 -4
  121. package/src/cli/lib/inspect-plugin.ts +62 -1
  122. package/src/cli/lib/install-from-github.ts +52 -5
  123. package/src/cli/lib/upgrade-plugin.ts +18 -0
  124. package/src/config/__tests__/workflows-schema.test.ts +60 -0
  125. package/src/config/bundled-skills/acp/SKILL.md +2 -2
  126. package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
  127. package/src/config/bundled-skills/app-builder/tools/app-create.ts +6 -1
  128. package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +7 -1
  129. package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +7 -1
  130. package/src/config/bundled-skills/app-builder/tools/app-update.ts +10 -1
  131. package/src/config/bundled-skills/image-studio/SKILL.md +66 -19
  132. package/src/config/bundled-skills/image-studio/TOOLS.json +1 -6
  133. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +22 -3
  134. package/src/config/bundled-skills/personal-page/SKILL.md +57 -0
  135. package/src/config/bundled-skills/personal-page/TOOLS.json +27 -0
  136. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +17 -0
  137. package/src/config/bundled-skills/schedule/SKILL.md +7 -2
  138. package/src/config/bundled-skills/schedule/TOOLS.json +48 -4
  139. package/src/config/bundled-skills/workflows/SKILL.md +214 -0
  140. package/src/config/bundled-skills/workflows/TOOLS.json +84 -0
  141. package/src/config/bundled-skills/workflows/tools/manage-workflows.ts +12 -0
  142. package/src/config/bundled-skills/workflows/tools/run-workflow.ts +12 -0
  143. package/src/config/bundled-tool-registry.ts +14 -2
  144. package/src/config/call-site-defaults.ts +5 -0
  145. package/src/config/feature-flag-registry.json +12 -4
  146. package/src/config/llm-context-resolution.ts +8 -0
  147. package/src/config/llm-resolver.ts +30 -0
  148. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +22 -0
  149. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +16 -0
  150. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +77 -0
  151. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +57 -0
  152. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +28 -0
  153. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +234 -0
  154. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +48 -0
  155. package/src/config/preloaded-apps/personal-page/src/components/media.ts +16 -0
  156. package/src/config/preloaded-apps/personal-page/src/index.html +20 -0
  157. package/src/config/preloaded-apps/personal-page/src/main.tsx +7 -0
  158. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +82 -0
  159. package/src/config/preloaded-apps/personal-page/src/styles.css +759 -0
  160. package/src/config/schema.ts +2 -0
  161. package/src/config/schemas/call-site-catalog.ts +7 -0
  162. package/src/config/schemas/heartbeat.ts +4 -1
  163. package/src/config/schemas/llm.ts +33 -26
  164. package/src/config/schemas/memory-retrospective.ts +19 -0
  165. package/src/config/schemas/platform.ts +8 -0
  166. package/src/config/schemas/services.ts +5 -2
  167. package/src/config/schemas/workflows.ts +42 -0
  168. package/src/config/skills.ts +3 -3
  169. package/src/context/compactor.ts +273 -39
  170. package/src/context/post-turn-tool-result-truncation.ts +23 -6
  171. package/src/context/tool-result-spool.ts +12 -17
  172. package/src/credential-execution/executable-discovery.ts +1 -1
  173. package/src/credential-execution/process-manager.ts +37 -3
  174. package/src/credential-execution/prompted-credential.ts +205 -0
  175. package/src/daemon/conversation-agent-loop-handlers.ts +14 -0
  176. package/src/daemon/conversation-process.ts +11 -2
  177. package/src/daemon/conversation-surfaces.ts +167 -3
  178. package/src/daemon/conversation-tool-setup.ts +103 -26
  179. package/src/daemon/conversation-usage.ts +2 -0
  180. package/src/daemon/conversation.ts +115 -11
  181. package/src/daemon/handlers/shared.ts +26 -14
  182. package/src/daemon/host-cu-proxy.ts +15 -12
  183. package/src/daemon/host-file-proxy.ts +15 -12
  184. package/src/daemon/host-transfer-proxy.ts +30 -24
  185. package/src/daemon/lifecycle.ts +40 -3
  186. package/src/daemon/message-protocol.ts +3 -0
  187. package/src/daemon/message-types/messages.ts +2 -10
  188. package/src/daemon/message-types/workflows.ts +49 -0
  189. package/src/daemon/parse-actual-tokens-from-error.test.ts +62 -1
  190. package/src/daemon/parse-actual-tokens-from-error.ts +43 -4
  191. package/src/daemon/process-message.ts +6 -0
  192. package/src/daemon/tool-setup-types.ts +57 -0
  193. package/src/daemon/wake-conversation-ops.ts +18 -0
  194. package/src/heartbeat/heartbeat-run-store.ts +8 -2
  195. package/src/home/feed-types.ts +1 -1
  196. package/src/ipc/gateway-flag-listener.ts +28 -6
  197. package/src/mcp/mcp-auth-state.ts +8 -20
  198. package/src/media/__tests__/image-models.test.ts +57 -0
  199. package/src/media/image-models.ts +66 -0
  200. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +38 -0
  201. package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +12 -2
  202. package/src/memory/__tests__/memory-retrospective-job.test.ts +911 -34
  203. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +227 -5
  204. package/src/memory/__tests__/memory-retrospective-state.test.ts +195 -0
  205. package/src/memory/__tests__/preloaded-apps.test.ts +85 -0
  206. package/src/memory/auto-analysis-enqueue.ts +14 -1
  207. package/src/memory/compaction-log-store-clickhouse.ts +6 -4
  208. package/src/memory/conversation-crud.ts +9 -2
  209. package/src/memory/conversation-disk-view.ts +1 -1
  210. package/src/memory/conversation-queries.ts +22 -7
  211. package/src/memory/db-init.ts +20 -0
  212. package/src/memory/db-maintenance.ts +16 -0
  213. package/src/memory/embedding-runtime-manager.ts +1 -1
  214. package/src/memory/llm-request-log-source-clickhouse.ts +112 -14
  215. package/src/memory/llm-request-log-source-local.ts +19 -1
  216. package/src/memory/llm-request-log-source.ts +35 -6
  217. package/src/memory/llm-request-log-store.ts +90 -2
  218. package/src/memory/memory-retrospective-constants.ts +9 -0
  219. package/src/memory/memory-retrospective-enqueue.ts +3 -6
  220. package/src/memory/memory-retrospective-fork-boundary.ts +94 -0
  221. package/src/memory/memory-retrospective-job.ts +500 -208
  222. package/src/memory/memory-retrospective-startup-cleanup.ts +97 -19
  223. package/src/memory/memory-retrospective-state.ts +85 -2
  224. package/src/memory/migrations/281-memory-retrospective-remembered-log.ts +40 -0
  225. package/src/memory/migrations/282-schedule-inference-profile.test.ts +77 -0
  226. package/src/memory/migrations/282-schedule-inference-profile.ts +26 -0
  227. package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.test.ts +102 -0
  228. package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.ts +53 -0
  229. package/src/memory/migrations/284-workflow-runs.ts +51 -0
  230. package/src/memory/migrations/285-schedule-workflow-mode.ts +26 -0
  231. package/src/memory/migrations/286-workflow-run-trust.ts +27 -0
  232. package/src/memory/migrations/287-conversation-origin-channel-index.ts +15 -0
  233. package/src/memory/migrations/288-backfill-origin-channel-from-bindings.ts +43 -0
  234. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +115 -0
  235. package/src/memory/migrations/290-schedule-capabilities.test.ts +77 -0
  236. package/src/memory/migrations/290-schedule-capabilities.ts +25 -0
  237. package/src/memory/migrations/__tests__/281-memory-retrospective-remembered-log.test.ts +96 -0
  238. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +571 -0
  239. package/src/memory/migrations/index.ts +10 -0
  240. package/src/memory/preloaded-apps.ts +116 -0
  241. package/src/memory/schema/infrastructure.ts +4 -0
  242. package/src/memory/schema/memory-core.ts +4 -0
  243. package/src/memory/v2/__tests__/concept-page-frontmatter-schema.test.ts +45 -0
  244. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +11 -7
  245. package/src/memory/v2/__tests__/page-store.test.ts +13 -2
  246. package/src/memory/v2/__tests__/qdrant.test.ts +24 -0
  247. package/src/memory/v2/frontmatter-sweep.ts +7 -6
  248. package/src/memory/v2/page-store.ts +4 -3
  249. package/src/memory/v2/qdrant.ts +42 -3
  250. package/src/memory/v2/types.ts +16 -10
  251. package/src/messaging/draft-store.ts +1 -1
  252. package/src/notifications/access-request-copy.ts +200 -113
  253. package/src/notifications/adapters/slack.ts +250 -111
  254. package/src/notifications/adapters/telegram.ts +7 -44
  255. package/src/notifications/approval-card-builder.ts +93 -0
  256. package/src/notifications/broadcaster.ts +74 -0
  257. package/src/notifications/conversation-pairing.ts +8 -6
  258. package/src/notifications/copy-composer.ts +32 -26
  259. package/src/notifications/decision-engine.ts +59 -7
  260. package/src/notifications/guardian-question-mode.ts +145 -155
  261. package/src/notifications/home-feed-side-effect.ts +28 -11
  262. package/src/notifications/notification-utils.ts +66 -0
  263. package/src/notifications/signal.ts +6 -0
  264. package/src/notifications/tool-approval-copy.ts +142 -0
  265. package/src/notifications/types.ts +19 -0
  266. package/src/permissions/threshold.ts +11 -0
  267. package/src/plugin-api/types.ts +16 -4
  268. package/src/plugins/defaults/compaction/window-manager.ts +44 -0
  269. package/src/plugins/defaults/index.ts +46 -0
  270. package/src/plugins/defaults/max-tokens-continue/continue-state-store.ts +53 -0
  271. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +80 -0
  272. package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +20 -0
  273. package/src/plugins/defaults/max-tokens-continue/package.json +14 -0
  274. package/src/plugins/defaults/memory-retrieval/hooks/__tests__/user-prompt-submit.test.ts +37 -0
  275. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +29 -1
  276. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +8 -3
  277. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +4 -2
  278. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +28 -18
  279. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +67 -0
  280. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +122 -22
  281. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +2 -0
  282. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +63 -1
  283. package/src/plugins/defaults/memory-v3-shadow/injector.ts +61 -18
  284. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +1 -1
  285. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +39 -10
  286. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +34 -1
  287. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +112 -47
  288. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +78 -15
  289. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +206 -0
  290. package/src/plugins/defaults/task-progress-nudge/package.json +15 -0
  291. package/src/prompts/__tests__/system-prompt.test.ts +100 -1
  292. package/src/prompts/__tests__/task-progress-hint-section.test.ts +5 -7
  293. package/src/prompts/normalize-onboarding.ts +2 -0
  294. package/src/prompts/persona-resolver.ts +3 -0
  295. package/src/prompts/system-prompt.ts +51 -2
  296. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +3 -1
  297. package/src/prompts/templates/system-sections.ts +8 -3
  298. package/src/providers/call-site-routing.ts +6 -3
  299. package/src/providers/fireworks/client.ts +3 -0
  300. package/src/providers/inference/auth.ts +52 -46
  301. package/src/providers/model-intents.ts +2 -2
  302. package/src/providers/openai/__tests__/coerce-object-args.test.ts +105 -0
  303. package/src/providers/openai/chat-completions-provider.ts +47 -9
  304. package/src/providers/openai/coerce-object-args.ts +104 -0
  305. package/src/providers/retry.ts +8 -5
  306. package/src/providers/types.ts +10 -0
  307. package/src/runtime/__tests__/agent-wake.test.ts +629 -7
  308. package/src/runtime/access-request-helper.ts +16 -9
  309. package/src/runtime/agent-wake.ts +302 -51
  310. package/src/runtime/assistant-stream-state.ts +141 -8
  311. package/src/runtime/background-job-runner.ts +9 -0
  312. package/src/runtime/channel-approval-types.ts +1 -0
  313. package/src/runtime/channel-invite-transports/telegram.ts +6 -5
  314. package/src/runtime/channel-invite-transports/voice.ts +2 -2
  315. package/src/runtime/channel-invite-types.ts +4 -2
  316. package/src/runtime/channel-retry-sweep.ts +19 -41
  317. package/src/runtime/finalize-event-delivery.ts +72 -0
  318. package/src/runtime/guardian-action-message-composer.ts +0 -54
  319. package/src/runtime/http-server.ts +6 -14
  320. package/src/runtime/http-types.ts +0 -1
  321. package/src/runtime/message-composer-types.ts +0 -9
  322. package/src/runtime/middleware/__tests__/rate-limiter.test.ts +63 -0
  323. package/src/runtime/middleware/auth.ts +27 -3
  324. package/src/runtime/middleware/rate-limiter.ts +28 -1
  325. package/src/runtime/migrations/vbundle-builder.ts +6 -5
  326. package/src/runtime/pending-interactions.ts +20 -1
  327. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +232 -173
  328. package/src/runtime/routes/__tests__/plugins-routes.test.ts +18 -0
  329. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +436 -0
  330. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +11 -0
  331. package/src/runtime/routes/approval-routes.ts +35 -8
  332. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -1
  333. package/src/runtime/routes/btw-routes.ts +37 -1
  334. package/src/runtime/routes/channel-delivery-routes.ts +11 -7
  335. package/src/runtime/routes/channel-route-definitions.ts +3 -0
  336. package/src/runtime/routes/channel-route-shared.ts +3 -1
  337. package/src/runtime/routes/consolidation-routes.ts +17 -13
  338. package/src/runtime/routes/conversation-compaction-routes.ts +159 -119
  339. package/src/runtime/routes/conversation-list-routes.ts +41 -4
  340. package/src/runtime/routes/conversation-query-routes.ts +196 -11
  341. package/src/runtime/routes/conversation-routes.ts +15 -1
  342. package/src/runtime/routes/credential-prompt-routes.ts +39 -17
  343. package/src/runtime/routes/empty-state-greeting-cache.ts +65 -0
  344. package/src/runtime/routes/heartbeat-routes.ts +18 -13
  345. package/src/runtime/routes/image-generation-routes.ts +20 -2
  346. package/src/runtime/routes/inbound-message-handler.ts +13 -12
  347. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +32 -31
  348. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +7 -5
  349. package/src/runtime/routes/inbound-stages/background-dispatch.ts +7 -21
  350. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +5 -5
  351. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +6 -15
  352. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +1 -1
  353. package/src/runtime/routes/index.ts +6 -0
  354. package/src/runtime/routes/log-export/AGENTS.md +1 -1
  355. package/src/runtime/routes/log-export/workspace-allowlist.ts +1 -1
  356. package/src/runtime/routes/migration-routes.ts +5 -9
  357. package/src/runtime/routes/plugins-routes.ts +26 -0
  358. package/src/runtime/routes/ps-routes.ts +10 -8
  359. package/src/runtime/routes/retrospective-routes.ts +235 -0
  360. package/src/runtime/routes/runs-pagination.ts +75 -0
  361. package/src/runtime/routes/schedule-routes.ts +367 -59
  362. package/src/runtime/routes/sounds-config-routes.ts +239 -0
  363. package/src/runtime/routes/surface-action-routes.ts +84 -4
  364. package/src/runtime/routes/workflow-routes.test.ts +372 -0
  365. package/src/runtime/routes/workflow-routes.ts +363 -0
  366. package/src/runtime/routes/workspace-routes.test.ts +61 -1
  367. package/src/runtime/routes/workspace-routes.ts +26 -1
  368. package/src/runtime/services/__tests__/analyze-conversation.test.ts +38 -0
  369. package/src/runtime/services/analyze-conversation.ts +26 -13
  370. package/src/schedule/inference-profile.ts +28 -0
  371. package/src/schedule/schedule-store.ts +152 -4
  372. package/src/schedule/scheduler-types.ts +6 -0
  373. package/src/schedule/scheduler.ts +96 -0
  374. package/src/security/secret-allowlist.ts +1 -1
  375. package/src/skills/path-classifier.ts +1 -1
  376. package/src/tools/apps/executors.ts +23 -0
  377. package/src/tools/apps/resolve-app-id.ts +42 -0
  378. package/src/tools/browser/browser-execution.ts +9 -11
  379. package/src/tools/credentials/broker.ts +4 -4
  380. package/src/tools/credentials/vault.ts +26 -137
  381. package/src/tools/executor.ts +69 -0
  382. package/src/tools/flag-gated-tools.test.ts +76 -0
  383. package/src/tools/permission-checker.ts +8 -1
  384. package/src/tools/registry.ts +51 -0
  385. package/src/tools/schedule/create.ts +77 -1
  386. package/src/tools/schedule/list.ts +1 -0
  387. package/src/tools/schedule/update.ts +73 -1
  388. package/src/tools/skills/execute.ts +56 -0
  389. package/src/tools/terminal/shell.ts +1 -1
  390. package/src/tools/ui-surface/definitions.ts +91 -2
  391. package/src/tools/workflows/manage-workflows.ts +183 -0
  392. package/src/tools/workflows/run-workflow.test.ts +442 -0
  393. package/src/tools/workflows/run-workflow.ts +88 -0
  394. package/src/types/onboarding-context.ts +2 -0
  395. package/src/usage/attribution.ts +24 -0
  396. package/src/util/canonicalize-identity.ts +12 -3
  397. package/src/util/platform.ts +17 -17
  398. package/src/watcher/__tests__/engine.test.ts +24 -0
  399. package/src/watcher/__tests__/telemetry.test.ts +135 -0
  400. package/src/watcher/engine.ts +7 -0
  401. package/src/watcher/telemetry.ts +74 -0
  402. package/src/workflows/capabilities.test.ts +365 -0
  403. package/src/workflows/capabilities.ts +359 -0
  404. package/src/workflows/deterministic-stringify.ts +27 -0
  405. package/src/workflows/engine-integration.test.ts +656 -0
  406. package/src/workflows/engine.test.ts +1144 -0
  407. package/src/workflows/engine.ts +1078 -0
  408. package/src/workflows/fanout-load.test.ts +168 -0
  409. package/src/workflows/journal-store.test.ts +369 -0
  410. package/src/workflows/journal-store.ts +470 -0
  411. package/src/workflows/leaf-runner.test.ts +704 -0
  412. package/src/workflows/leaf-runner.ts +589 -0
  413. package/src/workflows/library.test.ts +134 -0
  414. package/src/workflows/library.ts +124 -0
  415. package/src/workflows/run-manager.test.ts +711 -0
  416. package/src/workflows/run-manager.ts +593 -0
  417. package/src/workflows/sandbox-escape.test.ts +339 -0
  418. package/src/workflows/sandbox.test.ts +251 -0
  419. package/src/workflows/sandbox.ts +447 -0
  420. package/src/workspace/adaptive-thinking-repair.ts +33 -11
  421. package/src/workspace/migrations/021-move-signals-to-workspace.ts +1 -1
  422. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +1 -1
  423. package/src/workspace/migrations/026-backfill-install-meta.ts +1 -1
  424. package/src/workspace/migrations/030-seed-pkb-autoinject.ts +2 -1
  425. package/src/workspace/migrations/031-drop-user-md.ts +1 -4
  426. package/src/workspace/migrations/048-remove-workspace-hooks.ts +1 -1
  427. package/src/workspace/migrations/056-release-notes-inference-profile-reordering.ts +5 -2
  428. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +1 -1
  429. package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +8 -2
  430. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +41 -14
  431. package/src/workspace/migrations/102-preserve-heartbeat-enabled-for-existing-workspaces.ts +69 -0
  432. package/src/workspace/migrations/103-upgrade-quality-profile-to-opus-4-8.ts +83 -0
  433. package/src/workspace/migrations/104-recheck-adaptive-thinking-model-implied-anthropic.ts +133 -0
  434. package/src/workspace/migrations/registry.ts +6 -0
  435. package/src/workspace/migrations/runner.ts +1 -1
  436. package/tsconfig.json +1 -1
  437. package/src/__tests__/guardian-action-copy-generator.test.ts +0 -200
  438. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -579
  439. package/src/__tests__/guardian-action-store.test.ts +0 -106
  440. package/src/daemon/guardian-action-generators.ts +0 -71
  441. package/src/memory/guardian-action-store.ts +0 -484
  442. package/src/runtime/guardian-action-grant-minter.ts +0 -150
@@ -72,7 +72,7 @@ export function normalizeAssistantId(assistantId: string): string {
72
72
  }
73
73
 
74
74
  /**
75
- * Returns the internal data directory (~/.vellum/workspace/data). Runtime
75
+ * Returns the internal data directory ($VELLUM_WORKSPACE_DIR/data). Runtime
76
76
  * databases, logs, memory indices, and other internal state live here.
77
77
  */
78
78
  export function getDataDir(): string {
@@ -96,7 +96,7 @@ export function getConfigQuarantineNoticePath(): string {
96
96
  }
97
97
 
98
98
  /**
99
- * Returns the embedding models directory (~/.vellum/workspace/embedding-models).
99
+ * Returns the embedding models directory ($VELLUM_WORKSPACE_DIR/embedding-models).
100
100
  * Downloaded embedding runtime (onnxruntime-node, transformers bundle, model weights)
101
101
  * is stored here, downloaded post-hatch rather than shipped with the app.
102
102
  */
@@ -113,7 +113,7 @@ export function getSandboxRootDir(): string {
113
113
  }
114
114
 
115
115
  /**
116
- * Returns the default sandbox working directory (~/.vellum/workspace).
116
+ * Returns the default sandbox working directory ($VELLUM_WORKSPACE_DIR).
117
117
  * This is the workspace root — tool working directories should use this
118
118
  * path unless explicitly overridden.
119
119
  */
@@ -122,7 +122,7 @@ export function getSandboxWorkingDir(): string {
122
122
  }
123
123
 
124
124
  /**
125
- * Returns the sounds directory (~/.vellum/workspace/data/sounds).
125
+ * Returns the sounds directory ($VELLUM_WORKSPACE_DIR/data/sounds).
126
126
  * Custom sound files and sound configuration live here.
127
127
  */
128
128
  export function getSoundsDir(): string {
@@ -137,7 +137,7 @@ export function getAvatarDir(): string {
137
137
  /** Canonical filename for the custom avatar PNG. */
138
138
  export const AVATAR_IMAGE_FILENAME = "avatar-image.png";
139
139
 
140
- /** Returns the canonical avatar image path (~/.vellum/workspace/data/avatar/avatar-image.png). */
140
+ /** Returns the canonical avatar image path ($VELLUM_WORKSPACE_DIR/data/avatar/avatar-image.png). */
141
141
  export function getAvatarImagePath(): string {
142
142
  return join(getAvatarDir(), AVATAR_IMAGE_FILENAME);
143
143
  }
@@ -145,7 +145,7 @@ export function getAvatarImagePath(): string {
145
145
  /** Canonical filename for the avatar state manifest. */
146
146
  export const AVATAR_MANIFEST_FILENAME = "avatar.json";
147
147
 
148
- /** Returns the canonical avatar manifest path (~/.vellum/workspace/data/avatar/avatar.json). */
148
+ /** Returns the canonical avatar manifest path ($VELLUM_WORKSPACE_DIR/data/avatar/avatar.json). */
149
149
  export function getAvatarManifestPath(): string {
150
150
  return join(getAvatarDir(), AVATAR_MANIFEST_FILENAME);
151
151
  }
@@ -203,7 +203,7 @@ export function getProtectedDir(): string {
203
203
  return join(vellumRoot(), "protected");
204
204
  }
205
205
 
206
- /** Returns ~/.vellum/workspace/signals — the directory for IPC signal files. */
206
+ /** Returns $VELLUM_WORKSPACE_DIR/signals — the directory for IPC signal files. */
207
207
  export function getSignalsDir(): string {
208
208
  return join(getWorkspaceDir(), "signals");
209
209
  }
@@ -212,22 +212,22 @@ export function getSignalsDir(): string {
212
212
  // These expose specific root-level file paths so callers don't need to
213
213
  // import getRootDir() directly. getRootDir() is intentionally unexported.
214
214
 
215
- /** Returns the path to the daemon stderr log (~/.vellum/workspace/logs/daemon-stderr.log). */
215
+ /** Returns the path to the daemon stderr log ($VELLUM_WORKSPACE_DIR/logs/daemon-stderr.log). */
216
216
  export function getDaemonStderrLogPath(): string {
217
217
  return join(getWorkspaceDir(), "logs", "daemon-stderr.log");
218
218
  }
219
219
 
220
- /** Returns the path to the daemon startup lock file (~/.vellum/workspace/daemon-startup.lock). */
220
+ /** Returns the path to the daemon startup lock file ($VELLUM_WORKSPACE_DIR/daemon-startup.lock). */
221
221
  export function getDaemonStartupLockPath(): string {
222
222
  return join(getWorkspaceDir(), "daemon-startup.lock");
223
223
  }
224
224
 
225
- /** Returns the directory for externally-installed packages (~/.vellum/workspace/external). */
225
+ /** Returns the directory for externally-installed packages ($VELLUM_WORKSPACE_DIR/external). */
226
226
  export function getExternalDir(): string {
227
227
  return join(getWorkspaceDir(), "external");
228
228
  }
229
229
 
230
- /** Returns the directory for installed binaries (~/.vellum/workspace/bin). */
230
+ /** Returns the directory for installed binaries ($VELLUM_WORKSPACE_DIR/bin). */
231
231
  export function getBinDir(): string {
232
232
  return join(getWorkspaceDir(), "bin");
233
233
  }
@@ -237,7 +237,7 @@ export function getDotEnvPath(): string {
237
237
  return join(vellumRoot(), ".env");
238
238
  }
239
239
 
240
- /** Returns the path to the embed-worker PID file (~/.vellum/workspace/embed-worker.pid). */
240
+ /** Returns the path to the embed-worker PID file ($VELLUM_WORKSPACE_DIR/embed-worker.pid). */
241
241
  export function getEmbedWorkerPidPath(): string {
242
242
  return join(getWorkspaceDir(), "embed-worker.pid");
243
243
  }
@@ -273,17 +273,17 @@ export function getWorkspaceDirDisplay(): string {
273
273
  return abs;
274
274
  }
275
275
 
276
- /** Returns ~/.vellum/workspace/config.json */
276
+ /** Returns $VELLUM_WORKSPACE_DIR/config.json */
277
277
  export function getWorkspaceConfigPath(): string {
278
278
  return join(getWorkspaceDir(), "config.json");
279
279
  }
280
280
 
281
- /** Returns ~/.vellum/workspace/skills */
281
+ /** Returns $VELLUM_WORKSPACE_DIR/skills */
282
282
  export function getWorkspaceSkillsDir(): string {
283
283
  return join(getWorkspaceDir(), "skills");
284
284
  }
285
285
 
286
- /** Returns ~/.vellum/workspace/hooks */
286
+ /** Returns $VELLUM_WORKSPACE_DIR/hooks */
287
287
  export function getWorkspaceHooksDir(): string {
288
288
  return join(getWorkspaceDir(), "hooks");
289
289
  }
@@ -319,12 +319,12 @@ export function getWorkspaceRoutesDir(): string {
319
319
  return join(getWorkspaceDir(), "routes");
320
320
  }
321
321
 
322
- /** Returns ~/.vellum/workspace/deprecated — transitional files slated for removal. */
322
+ /** Returns $VELLUM_WORKSPACE_DIR/deprecated — transitional files slated for removal. */
323
323
  export function getDeprecatedDir(): string {
324
324
  return join(getWorkspaceDir(), "deprecated");
325
325
  }
326
326
 
327
- /** Returns ~/.vellum/workspace/conversations */
327
+ /** Returns $VELLUM_WORKSPACE_DIR/conversations */
328
328
  export function getConversationsDir(): string {
329
329
  return join(getWorkspaceDir(), "conversations");
330
330
  }
@@ -105,6 +105,21 @@ mock.module("../../runtime/background-job-runner.js", () => ({
105
105
  },
106
106
  }));
107
107
 
108
+ const inventoryCalls: number[] = [];
109
+ const llmProcessedCalls: Array<{
110
+ providerId: string;
111
+ conversationId: string;
112
+ }> = [];
113
+
114
+ mock.module("../telemetry.js", () => ({
115
+ recordWatcherInventoryIfDue: (now: number) => {
116
+ inventoryCalls.push(now);
117
+ },
118
+ recordWatcherLlmProcessed: (providerId: string, conversationId: string) => {
119
+ llmProcessedCalls.push({ providerId, conversationId });
120
+ },
121
+ }));
122
+
108
123
  // Import after mocks are in place.
109
124
  const { runWatchersOnce } = await import("../engine.js");
110
125
 
@@ -156,6 +171,8 @@ beforeEach(() => {
156
171
  setConvCalls.length = 0;
157
172
  dispositionCalls.length = 0;
158
173
  runJobCalls.length = 0;
174
+ inventoryCalls.length = 0;
175
+ llmProcessedCalls.length = 0;
159
176
  runJobImpl = async () => ({ conversationId: "conv-stub", ok: true });
160
177
  });
161
178
 
@@ -223,6 +240,9 @@ describe("runWatchersOnce — Phase 2 runBackgroundJob integration", () => {
223
240
  expect(setConvCalls).toEqual([
224
241
  { watcherId: "watcher-1", conversationId: "conv-success" },
225
242
  ]);
243
+ expect(llmProcessedCalls).toEqual([
244
+ { providerId: "linear", conversationId: "conv-success" },
245
+ ]);
226
246
  expect(dispositionCalls).toHaveLength(2);
227
247
  for (const call of dispositionCalls) {
228
248
  expect(call.disposition).toBe("silent");
@@ -267,6 +287,8 @@ describe("runWatchersOnce — Phase 2 runBackgroundJob integration", () => {
267
287
  // Critical: we must NOT have called setWatcherConversationId with "",
268
288
  // which would clobber a valid prior conversation id in the DB.
269
289
  expect(setConvCalls).toEqual([]);
290
+ // No conversation was bootstrapped, so no usage breadcrumb either.
291
+ expect(llmProcessedCalls).toEqual([]);
270
292
  // Failure path still updates event dispositions.
271
293
  expect(dispositionCalls).toHaveLength(1);
272
294
  expect(dispositionCalls[0].disposition).toBe("error");
@@ -280,6 +302,8 @@ describe("runWatchersOnce — Phase 2 runBackgroundJob integration", () => {
280
302
 
281
303
  expect(runJobCalls).toHaveLength(0);
282
304
  expect(setConvCalls).toHaveLength(0);
305
+ // Inventory telemetry runs on every tick regardless of pending work.
306
+ expect(inventoryCalls).toHaveLength(1);
283
307
  });
284
308
 
285
309
  test("malicious payload reaches the runner only inside assistant-role sandwich.content", async () => {
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Tests for watcher usage telemetry.
3
+ *
4
+ * Strategy: stub the checkpoint store, lifecycle-event store, and
5
+ * watcher store via `mock.module()` so we can drive the inventory
6
+ * throttle and event naming without touching the DB.
7
+ */
8
+
9
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
10
+
11
+ const checkpoints = new Map<string, string>();
12
+ const recordedEvents: string[] = [];
13
+ let fakeEnabledWatchers: Array<{ providerId: string }> = [];
14
+ let recordImpl: (name: string) => void = (name) => {
15
+ recordedEvents.push(name);
16
+ };
17
+
18
+ mock.module("../../memory/checkpoints.js", () => ({
19
+ getMemoryCheckpoint: (key: string) => checkpoints.get(key) ?? null,
20
+ setMemoryCheckpoint: (key: string, value: string) => {
21
+ checkpoints.set(key, value);
22
+ },
23
+ }));
24
+
25
+ mock.module("../../memory/lifecycle-events-store.js", () => ({
26
+ recordLifecycleEvent: (name: string) => {
27
+ recordImpl(name);
28
+ return null;
29
+ },
30
+ }));
31
+
32
+ mock.module("../watcher-store.js", () => ({
33
+ listWatchers: () => fakeEnabledWatchers,
34
+ }));
35
+
36
+ const {
37
+ recordWatcherInventoryIfDue,
38
+ recordWatcherLlmProcessed,
39
+ WATCHER_INVENTORY_INTERVAL_MS,
40
+ } = await import("../telemetry.js");
41
+
42
+ const INVENTORY_KEY = "telemetry:watchers:inventory_last_recorded";
43
+ // Realistic epoch base: an absent checkpoint reads as 0, so `now` must be
44
+ // at least one interval past the epoch for the inventory to be due.
45
+ const BASE = 1_700_000_000_000;
46
+
47
+ beforeEach(() => {
48
+ checkpoints.clear();
49
+ recordedEvents.length = 0;
50
+ fakeEnabledWatchers = [];
51
+ recordImpl = (name) => {
52
+ recordedEvents.push(name);
53
+ };
54
+ });
55
+
56
+ describe("recordWatcherInventoryIfDue", () => {
57
+ test("records one event per enabled watcher when due", () => {
58
+ fakeEnabledWatchers = [
59
+ { providerId: "gmail" },
60
+ { providerId: "linear" },
61
+ { providerId: "gmail" },
62
+ ];
63
+
64
+ recordWatcherInventoryIfDue(BASE);
65
+
66
+ expect(recordedEvents).toEqual([
67
+ "watcher_enabled:gmail",
68
+ "watcher_enabled:linear",
69
+ "watcher_enabled:gmail",
70
+ ]);
71
+ expect(checkpoints.get(INVENTORY_KEY)).toBe(String(BASE));
72
+ });
73
+
74
+ test("skips when the last inventory is within the interval", () => {
75
+ fakeEnabledWatchers = [{ providerId: "gmail" }];
76
+ checkpoints.set(INVENTORY_KEY, String(BASE));
77
+
78
+ recordWatcherInventoryIfDue(BASE + WATCHER_INVENTORY_INTERVAL_MS - 1);
79
+
80
+ expect(recordedEvents).toEqual([]);
81
+ expect(checkpoints.get(INVENTORY_KEY)).toBe(String(BASE));
82
+ });
83
+
84
+ test("records again once the interval has elapsed", () => {
85
+ fakeEnabledWatchers = [{ providerId: "github" }];
86
+ checkpoints.set(INVENTORY_KEY, String(BASE));
87
+
88
+ recordWatcherInventoryIfDue(BASE + WATCHER_INVENTORY_INTERVAL_MS);
89
+
90
+ expect(recordedEvents).toEqual(["watcher_enabled:github"]);
91
+ });
92
+
93
+ test("advances the checkpoint even when no watchers are enabled", () => {
94
+ recordWatcherInventoryIfDue(BASE + 1);
95
+
96
+ expect(recordedEvents).toEqual([]);
97
+ expect(checkpoints.get(INVENTORY_KEY)).toBe(String(BASE + 1));
98
+ });
99
+
100
+ test("swallows storage errors and leaves the checkpoint for retry", () => {
101
+ fakeEnabledWatchers = [{ providerId: "gmail" }];
102
+ recordImpl = () => {
103
+ throw new Error("db unavailable");
104
+ };
105
+
106
+ expect(() => recordWatcherInventoryIfDue(BASE + 2)).not.toThrow();
107
+ // Checkpoint must not advance on failure: the next tick retries
108
+ // instead of skipping a day of inventory.
109
+ expect(checkpoints.get(INVENTORY_KEY)).toBeUndefined();
110
+
111
+ recordImpl = (name) => {
112
+ recordedEvents.push(name);
113
+ };
114
+ recordWatcherInventoryIfDue(BASE + 3);
115
+
116
+ expect(recordedEvents).toEqual(["watcher_enabled:gmail"]);
117
+ expect(checkpoints.get(INVENTORY_KEY)).toBe(String(BASE + 3));
118
+ });
119
+ });
120
+
121
+ describe("recordWatcherLlmProcessed", () => {
122
+ test("embeds provider and conversation id in the event name", () => {
123
+ recordWatcherLlmProcessed("linear", "conv-123");
124
+
125
+ expect(recordedEvents).toEqual(["watcher_llm_processed:linear:conv-123"]);
126
+ });
127
+
128
+ test("swallows storage errors", () => {
129
+ recordImpl = () => {
130
+ throw new Error("db unavailable");
131
+ };
132
+
133
+ expect(() => recordWatcherLlmProcessed("gmail", "conv-456")).not.toThrow();
134
+ });
135
+ });
@@ -12,6 +12,10 @@ import { checkForSequenceReplies } from "../sequence/reply-matcher.js";
12
12
  import { getLogger } from "../util/logger.js";
13
13
  import { MAX_CONSECUTIVE_ERRORS, WATCHER_JOB_TIMEOUT_MS } from "./constants.js";
14
14
  import { getWatcherProvider } from "./provider-registry.js";
15
+ import {
16
+ recordWatcherInventoryIfDue,
17
+ recordWatcherLlmProcessed,
18
+ } from "./telemetry.js";
15
19
  import {
16
20
  claimDueWatchers,
17
21
  completeWatcherPoll,
@@ -69,6 +73,8 @@ export async function runWatchersOnce(
69
73
  const now = Date.now();
70
74
  let processed = 0;
71
75
 
76
+ recordWatcherInventoryIfDue(now);
77
+
72
78
  // ── Phase 1: Poll providers for new events ──────────────────────
73
79
  const claimed = claimDueWatchers(now);
74
80
  for (const watcher of claimed) {
@@ -269,6 +275,7 @@ export async function runWatchersOnce(
269
275
  // is empty) — otherwise we'd overwrite a valid prior id with "".
270
276
  if (result.conversationId !== "") {
271
277
  setWatcherConversationId(watcher.id, result.conversationId);
278
+ recordWatcherLlmProcessed(watcher.providerId, result.conversationId);
272
279
  }
273
280
 
274
281
  if (result.ok) {
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Watcher usage telemetry — lifecycle-event breadcrumbs that let fleet
3
+ * analytics answer "is anyone using watchers, and what do they cost?"
4
+ * ahead of the planned watcher → skills-and-schedules migration.
5
+ *
6
+ * Both event shapes ship through the existing lifecycle-event telemetry
7
+ * pipeline, so they inherit its `collectUsageData` opt-out gate and need
8
+ * no wire-contract or platform-side changes:
9
+ *
10
+ * - `watcher_enabled:<providerId>` — one per enabled watcher, at most
11
+ * once per 24h per daemon. Counts devices that have watchers
12
+ * configured, including ones whose providers rarely produce events.
13
+ * - `watcher_llm_processed:<providerId>:<conversationId>` — one per
14
+ * watcher tick that ran an LLM background job. The conversation id is
15
+ * embedded so analytics can join against `llm_usage` events for exact
16
+ * cost attribution.
17
+ *
18
+ * Telemetry must never break the polling loop, so every entry point
19
+ * swallows storage errors.
20
+ */
21
+
22
+ import {
23
+ getMemoryCheckpoint,
24
+ setMemoryCheckpoint,
25
+ } from "../memory/checkpoints.js";
26
+ import { recordLifecycleEvent } from "../memory/lifecycle-events-store.js";
27
+ import { getLogger } from "../util/logger.js";
28
+ import { listWatchers } from "./watcher-store.js";
29
+
30
+ const log = getLogger("watcher-telemetry");
31
+
32
+ const INVENTORY_CHECKPOINT_KEY = "telemetry:watchers:inventory_last_recorded";
33
+ export const WATCHER_INVENTORY_INTERVAL_MS = 24 * 60 * 60 * 1000;
34
+
35
+ /**
36
+ * Record one `watcher_enabled:<providerId>` lifecycle event per enabled
37
+ * watcher, at most once per 24h. Called from every watcher tick; the
38
+ * checkpoint advances only after the events are recorded, so a transient
39
+ * storage failure retries on the next tick instead of skipping a day.
40
+ * Retries can duplicate events for watchers recorded before the failure
41
+ * point — an acceptable overcount for best-effort telemetry, where losing
42
+ * a full day's inventory would not be.
43
+ */
44
+ export function recordWatcherInventoryIfDue(now: number): void {
45
+ try {
46
+ const last = Number(getMemoryCheckpoint(INVENTORY_CHECKPOINT_KEY) ?? "0");
47
+ if (now - last < WATCHER_INVENTORY_INTERVAL_MS) return;
48
+ for (const watcher of listWatchers({ enabledOnly: true })) {
49
+ recordLifecycleEvent(`watcher_enabled:${watcher.providerId}`);
50
+ }
51
+ setMemoryCheckpoint(INVENTORY_CHECKPOINT_KEY, String(now));
52
+ } catch (err) {
53
+ log.warn({ err }, "Failed to record watcher inventory telemetry");
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Record a `watcher_llm_processed:<providerId>:<conversationId>`
59
+ * lifecycle event for a watcher tick that bootstrapped an LLM background
60
+ * job. Recorded whenever a conversation was created — even if the job
61
+ * later failed — because tokens may already have been spent.
62
+ */
63
+ export function recordWatcherLlmProcessed(
64
+ providerId: string,
65
+ conversationId: string,
66
+ ): void {
67
+ try {
68
+ recordLifecycleEvent(
69
+ `watcher_llm_processed:${providerId}:${conversationId}`,
70
+ );
71
+ } catch (err) {
72
+ log.warn({ err }, "Failed to record watcher LLM-processed telemetry");
73
+ }
74
+ }