@vellumai/assistant 0.6.3 → 0.6.4

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 (667) hide show
  1. package/ARCHITECTURE.md +273 -10
  2. package/Dockerfile +2 -3
  3. package/bun.lock +5 -13
  4. package/docs/backup-troubleshooting.md +52 -0
  5. package/docs/browser-use-architecture-phase2.md +174 -0
  6. package/docs/stt-provider-onboarding.md +120 -0
  7. package/knip.json +12 -2
  8. package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
  9. package/node_modules/@vellumai/ces-contracts/package.json +3 -3
  10. package/openapi.yaml +982 -72
  11. package/package.json +4 -6
  12. package/scripts/generate-openapi.ts +0 -1
  13. package/scripts/test.sh +73 -18
  14. package/src/__tests__/agent-image-optimize.test.ts +28 -0
  15. package/src/__tests__/agent-loop.test.ts +123 -0
  16. package/src/__tests__/anthropic-provider.test.ts +263 -10
  17. package/src/__tests__/auto-analysis-end-to-end.test.ts +550 -0
  18. package/src/__tests__/auto-analysis-prompt.test.ts +50 -0
  19. package/src/__tests__/browser-fill-credential.test.ts +11 -0
  20. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  21. package/src/__tests__/browser-skill-endstate.test.ts +31 -7
  22. package/src/__tests__/btw-routes.test.ts +7 -0
  23. package/src/__tests__/call-controller.test.ts +581 -20
  24. package/src/__tests__/catalog-files.test.ts +138 -0
  25. package/src/__tests__/channel-invite-transport.test.ts +2 -2
  26. package/src/__tests__/channel-readiness-routes.test.ts +16 -20
  27. package/src/__tests__/channel-readiness-service.test.ts +12 -7
  28. package/src/__tests__/checker.test.ts +157 -10
  29. package/src/__tests__/clawhub-files.test.ts +347 -0
  30. package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
  31. package/src/__tests__/config-analysis.test.ts +100 -0
  32. package/src/__tests__/config-schema.test.ts +1013 -66
  33. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
  34. package/src/__tests__/config-watcher.test.ts +43 -8
  35. package/src/__tests__/contact-store-user-file.test.ts +512 -0
  36. package/src/__tests__/contacts-write.test.ts +197 -0
  37. package/src/__tests__/context-window-manager.test.ts +88 -0
  38. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  39. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  40. package/src/__tests__/conversation-agent-loop.test.ts +98 -2
  41. package/src/__tests__/conversation-confirmation-signals.test.ts +135 -0
  42. package/src/__tests__/conversation-error.test.ts +70 -0
  43. package/src/__tests__/conversation-history-web-search.test.ts +11 -4
  44. package/src/__tests__/conversation-init.benchmark.test.ts +6 -1
  45. package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
  46. package/src/__tests__/conversation-list-source.test.ts +145 -0
  47. package/src/__tests__/conversation-pre-run-repair.test.ts +2 -0
  48. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  49. package/src/__tests__/conversation-queue.test.ts +901 -60
  50. package/src/__tests__/conversation-routes-disk-view.test.ts +270 -0
  51. package/src/__tests__/conversation-runtime-assembly.test.ts +55 -0
  52. package/src/__tests__/conversation-skill-tools.test.ts +7 -4
  53. package/src/__tests__/conversation-slash-commands.test.ts +33 -0
  54. package/src/__tests__/conversation-slash-queue.test.ts +89 -18
  55. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  56. package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
  57. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  59. package/src/__tests__/credential-health-service.test.ts +352 -0
  60. package/src/__tests__/credential-security-invariants.test.ts +5 -3
  61. package/src/__tests__/credential-vault-unit.test.ts +379 -3
  62. package/src/__tests__/credentials-cli.test.ts +40 -16
  63. package/src/__tests__/cross-provider-web-search.test.ts +146 -35
  64. package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
  65. package/src/__tests__/device-id.test.ts +112 -0
  66. package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
  67. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
  68. package/src/__tests__/email-html-renderer.test.ts +71 -0
  69. package/src/__tests__/email-invite-adapter.test.ts +36 -32
  70. package/src/__tests__/emit-event-signal.test.ts +71 -0
  71. package/src/__tests__/extension-id-sync-guard.test.ts +75 -8
  72. package/src/__tests__/fixtures/mock-chrome-extension.ts +11 -0
  73. package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
  74. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  75. package/src/__tests__/gemini-provider.test.ts +64 -0
  76. package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
  77. package/src/__tests__/gmail-archive-fallback.test.ts +193 -0
  78. package/src/__tests__/gmail-archive-gate.test.ts +246 -0
  79. package/src/__tests__/gmail-preferences.test.ts +117 -0
  80. package/src/__tests__/headless-browser-interactions.test.ts +43 -0
  81. package/src/__tests__/headless-browser-mode.test.ts +614 -0
  82. package/src/__tests__/headless-browser-navigate.test.ts +142 -5
  83. package/src/__tests__/headless-browser-read-tools.test.ts +11 -0
  84. package/src/__tests__/headless-browser-snapshot.test.ts +10 -0
  85. package/src/__tests__/heartbeat-service.test.ts +70 -17
  86. package/src/__tests__/home-state-routes.test.ts +162 -0
  87. package/src/__tests__/host-bash-proxy.test.ts +0 -5
  88. package/src/__tests__/host-browser-e2e-cloud.test.ts +138 -4
  89. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +4 -4
  90. package/src/__tests__/host-browser-ws-events-e2e.test.ts +103 -0
  91. package/src/__tests__/host-cu-proxy.test.ts +0 -5
  92. package/src/__tests__/identity-intro-cache.test.ts +40 -10
  93. package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
  94. package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
  95. package/src/__tests__/llm-context-normalization.test.ts +488 -0
  96. package/src/__tests__/llm-context-route-provider.test.ts +86 -5
  97. package/src/__tests__/llm-usage-store.test.ts +363 -0
  98. package/src/__tests__/media-stream-output.test.ts +555 -0
  99. package/src/__tests__/media-stream-parser.test.ts +374 -0
  100. package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
  101. package/src/__tests__/media-stream-stt-session.test.ts +588 -0
  102. package/src/__tests__/media-turn-detector.test.ts +440 -0
  103. package/src/__tests__/message-queue.test.ts +125 -0
  104. package/src/__tests__/migration-export-http.test.ts +6 -6
  105. package/src/__tests__/migration-import-commit-http.test.ts +8 -6
  106. package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
  107. package/src/__tests__/migration-validate-http.test.ts +3 -3
  108. package/src/__tests__/mock-gateway-ipc.ts +151 -0
  109. package/src/__tests__/model-intents.test.ts +2 -2
  110. package/src/__tests__/oauth-apps-routes.test.ts +1 -0
  111. package/src/__tests__/oauth-cli.test.ts +2 -0
  112. package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
  113. package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
  114. package/src/__tests__/oauth-providers-routes.test.ts +2 -0
  115. package/src/__tests__/oauth-store.test.ts +85 -0
  116. package/src/__tests__/oauth2-gateway-transport.test.ts +249 -6
  117. package/src/__tests__/onboarding-template-contract.test.ts +6 -13
  118. package/src/__tests__/openai-provider.test.ts +176 -0
  119. package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
  120. package/src/__tests__/openai-responses-provider.test.ts +1105 -0
  121. package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
  122. package/src/__tests__/outlook-unsubscribe.test.ts +31 -2
  123. package/src/__tests__/persona-resolver.test.ts +251 -0
  124. package/src/__tests__/platform-bash-auto-approve.test.ts +4 -0
  125. package/src/__tests__/platform.test.ts +92 -1
  126. package/src/__tests__/post-turn-tool-result-truncation.test.ts +47 -0
  127. package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
  128. package/src/__tests__/pricing.test.ts +174 -0
  129. package/src/__tests__/qdrant-manager.test.ts +29 -8
  130. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
  131. package/src/__tests__/relationship-state-contract.test.ts +175 -0
  132. package/src/__tests__/relay-server.test.ts +423 -5
  133. package/src/__tests__/search-skills-unified.test.ts +118 -0
  134. package/src/__tests__/secret-scanner-executor.test.ts +4 -0
  135. package/src/__tests__/secure-keys.test.ts +107 -0
  136. package/src/__tests__/send-endpoint-busy.test.ts +5 -1
  137. package/src/__tests__/sequence-store.test.ts +1 -1
  138. package/src/__tests__/server-history-render.test.ts +49 -0
  139. package/src/__tests__/settings-routes.test.ts +201 -0
  140. package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
  141. package/src/__tests__/skills-file-content-endpoint.test.ts +276 -145
  142. package/src/__tests__/skills-files-catalog-fallback.test.ts +381 -93
  143. package/src/__tests__/skills.test.ts +5 -2
  144. package/src/__tests__/skillssh-files.test.ts +446 -0
  145. package/src/__tests__/slack-block-formatting.test.ts +110 -0
  146. package/src/__tests__/slack-channel-config.test.ts +564 -1
  147. package/src/__tests__/stt-catalog-parity.test.ts +282 -0
  148. package/src/__tests__/stt-stream-session.test.ts +535 -0
  149. package/src/__tests__/system-prompt.test.ts +112 -26
  150. package/src/__tests__/telephony-stt-routing.test.ts +329 -0
  151. package/src/__tests__/terminal-tools.test.ts +18 -7
  152. package/src/__tests__/test-preload.ts +18 -0
  153. package/src/__tests__/test-support/browser-skill-harness.ts +4 -1
  154. package/src/__tests__/tool-executor-lifecycle-events.test.ts +9 -5
  155. package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
  156. package/src/__tests__/tool-executor.test.ts +33 -24
  157. package/src/__tests__/tool-result-truncation.test.ts +36 -0
  158. package/src/__tests__/trust-store.test.ts +7 -1
  159. package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
  160. package/src/__tests__/tts-catalog-parity.test.ts +345 -0
  161. package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
  162. package/src/__tests__/twilio-routes.test.ts +376 -0
  163. package/src/__tests__/unicode.test.ts +293 -0
  164. package/src/__tests__/update-bulletin-format.test.ts +59 -0
  165. package/src/__tests__/update-bulletin.test.ts +206 -5
  166. package/src/__tests__/usage-routes.test.ts +25 -4
  167. package/src/__tests__/user-reference.test.ts +46 -61
  168. package/src/__tests__/verification-control-plane-policy.test.ts +4 -0
  169. package/src/__tests__/voice-config-update.test.ts +403 -0
  170. package/src/__tests__/voice-quality.test.ts +434 -19
  171. package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
  172. package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
  173. package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
  174. package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
  175. package/src/__tests__/workspace-migration-meets.test.ts +244 -0
  176. package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
  177. package/src/__tests__/workspace-policy.test.ts +2 -0
  178. package/src/agent/image-optimize.ts +24 -12
  179. package/src/agent/loop.ts +43 -3
  180. package/src/backup/__tests__/backup-key.test.ts +152 -0
  181. package/src/backup/__tests__/backup-worker.test.ts +767 -0
  182. package/src/backup/__tests__/list-snapshots.test.ts +87 -0
  183. package/src/backup/__tests__/local-writer.test.ts +218 -0
  184. package/src/backup/__tests__/offsite-writer.test.ts +641 -0
  185. package/src/backup/__tests__/paths.test.ts +300 -0
  186. package/src/backup/__tests__/restore.test.ts +498 -0
  187. package/src/backup/__tests__/snapshot-lock.test.ts +352 -0
  188. package/src/backup/__tests__/stream-crypt.test.ts +228 -0
  189. package/src/backup/backup-key.ts +137 -0
  190. package/src/backup/backup-worker.ts +459 -0
  191. package/src/backup/list-snapshots.ts +147 -0
  192. package/src/backup/local-writer.ts +133 -0
  193. package/src/backup/offsite-writer.ts +222 -0
  194. package/src/backup/paths.ts +226 -0
  195. package/src/backup/restore.ts +322 -0
  196. package/src/backup/snapshot-lock.ts +431 -0
  197. package/src/backup/stream-crypt.ts +263 -0
  198. package/src/bundler/package-resolver.ts +4 -0
  199. package/src/calls/audio-store.ts +11 -5
  200. package/src/calls/call-controller.ts +226 -71
  201. package/src/calls/call-domain.ts +9 -0
  202. package/src/calls/call-speech-output.ts +190 -0
  203. package/src/calls/call-transport.ts +77 -0
  204. package/src/calls/media-stream-audio-transcode.ts +173 -0
  205. package/src/calls/media-stream-output.ts +660 -0
  206. package/src/calls/media-stream-parser.ts +300 -0
  207. package/src/calls/media-stream-protocol.ts +166 -0
  208. package/src/calls/media-stream-server.ts +592 -0
  209. package/src/calls/media-stream-stt-session.ts +460 -0
  210. package/src/calls/media-turn-detector.ts +230 -0
  211. package/src/calls/relay-server.ts +90 -75
  212. package/src/calls/resolve-call-tts-provider.ts +136 -0
  213. package/src/calls/telephony-stt-routing.ts +145 -0
  214. package/src/calls/tts-call-strategy.ts +161 -0
  215. package/src/calls/tts-text-sanitizer.ts +32 -16
  216. package/src/calls/twilio-routes.ts +281 -17
  217. package/src/calls/voice-quality.ts +78 -35
  218. package/src/calls/voice-session-bridge.ts +8 -1
  219. package/src/channels/types.ts +16 -0
  220. package/src/cli/__tests__/run-assistant-command.ts +11 -1
  221. package/src/cli/commands/__tests__/backup.test.ts +1165 -0
  222. package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
  223. package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
  224. package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
  225. package/src/cli/commands/__tests__/email-download.test.ts +16 -1
  226. package/src/cli/commands/__tests__/email-list.test.ts +22 -4
  227. package/src/cli/commands/__tests__/email-register.test.ts +4 -4
  228. package/src/cli/commands/__tests__/email-send.test.ts +37 -4
  229. package/src/cli/commands/__tests__/email-status.test.ts +5 -1
  230. package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
  231. package/src/cli/commands/backup.ts +993 -0
  232. package/src/cli/commands/conversations.ts +77 -0
  233. package/src/cli/commands/credentials.ts +0 -1
  234. package/src/cli/commands/domain.ts +210 -0
  235. package/src/cli/commands/email.ts +255 -3
  236. package/src/cli/commands/oauth/__tests__/connect.test.ts +12 -0
  237. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +1 -0
  238. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -0
  239. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -0
  240. package/src/cli/commands/oauth/mode.ts +12 -3
  241. package/src/cli/commands/oauth/providers.ts +15 -0
  242. package/src/cli/commands/oauth/shared.ts +2 -1
  243. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +4 -9
  244. package/src/cli/commands/platform/__tests__/connect.test.ts +6 -0
  245. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  246. package/src/cli/commands/platform/__tests__/status.test.ts +6 -0
  247. package/src/cli/program.ts +30 -4
  248. package/src/config/__tests__/backup-schema.test.ts +134 -0
  249. package/src/config/assistant-feature-flags.ts +61 -62
  250. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +37 -1
  251. package/src/config/bundled-skills/browser/SKILL.md +30 -5
  252. package/src/config/bundled-skills/browser/TOOLS.json +123 -0
  253. package/src/config/bundled-skills/browser/tools/browser-attach.ts +12 -0
  254. package/src/config/bundled-skills/browser/tools/browser-detach.ts +12 -0
  255. package/src/config/bundled-skills/browser/tools/browser-status.ts +12 -0
  256. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +17 -0
  257. package/src/config/bundled-skills/contacts/SKILL.md +2 -2
  258. package/src/config/bundled-skills/gmail/SKILL.md +53 -7
  259. package/src/config/bundled-skills/gmail/TOOLS.json +33 -3
  260. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +116 -9
  261. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +138 -11
  262. package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +59 -0
  263. package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +82 -0
  264. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +113 -17
  265. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -2
  266. package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
  267. package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
  268. package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
  269. package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
  270. package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
  271. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
  272. package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
  273. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
  274. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  275. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
  276. package/src/config/bundled-skills/outlook/SKILL.md +2 -2
  277. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +2 -2
  278. package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
  279. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +27 -18
  280. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
  281. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  282. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
  283. package/src/config/bundled-skills/slack/SKILL.md +1 -0
  284. package/src/config/bundled-skills/transcribe/SKILL.md +9 -14
  285. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
  286. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
  287. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
  288. package/src/config/bundled-tool-registry.ts +8 -0
  289. package/src/config/env-registry.ts +24 -0
  290. package/src/config/env.ts +34 -10
  291. package/src/config/feature-flag-registry.json +46 -14
  292. package/src/config/loader.ts +26 -12
  293. package/src/config/schema.ts +35 -10
  294. package/src/config/schemas/__tests__/stt.test.ts +43 -0
  295. package/src/config/schemas/analysis.ts +51 -0
  296. package/src/config/schemas/backup.ts +72 -0
  297. package/src/config/schemas/calls.ts +1 -26
  298. package/src/config/schemas/elevenlabs.ts +0 -59
  299. package/src/config/schemas/filing.ts +47 -7
  300. package/src/config/schemas/heartbeat.ts +27 -5
  301. package/src/config/schemas/host-browser.ts +47 -1
  302. package/src/config/schemas/inference.ts +1 -1
  303. package/src/config/schemas/memory-lifecycle.ts +14 -2
  304. package/src/config/schemas/services.ts +44 -0
  305. package/src/config/schemas/stt.ts +59 -0
  306. package/src/config/schemas/tts.ts +230 -0
  307. package/src/config/schemas/updates.ts +14 -0
  308. package/src/config/skills.ts +4 -0
  309. package/src/config/types.ts +4 -0
  310. package/src/contacts/contact-store.ts +56 -11
  311. package/src/contacts/contacts-write.ts +38 -1
  312. package/src/context/post-turn-tool-result-truncation.ts +3 -2
  313. package/src/context/tool-result-truncation.ts +2 -1
  314. package/src/context/window-manager.ts +45 -12
  315. package/src/credential-execution/executable-discovery.ts +12 -2
  316. package/src/credential-execution/process-manager.ts +33 -2
  317. package/src/credential-health/credential-health-service.ts +366 -0
  318. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
  319. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
  320. package/src/daemon/__tests__/conversation-tool-setup.test.ts +17 -8
  321. package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
  322. package/src/daemon/config-watcher.ts +99 -5
  323. package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
  324. package/src/daemon/conversation-agent-loop.ts +101 -24
  325. package/src/daemon/conversation-error.ts +11 -0
  326. package/src/daemon/conversation-history.ts +40 -6
  327. package/src/daemon/conversation-launch.ts +220 -0
  328. package/src/daemon/conversation-lifecycle.ts +59 -9
  329. package/src/daemon/conversation-messaging.ts +37 -3
  330. package/src/daemon/conversation-notifiers.ts +5 -0
  331. package/src/daemon/conversation-process.ts +581 -19
  332. package/src/daemon/conversation-queue-manager.ts +24 -0
  333. package/src/daemon/conversation-runtime-assembly.ts +11 -1
  334. package/src/daemon/conversation-slash.ts +36 -0
  335. package/src/daemon/conversation-surfaces.ts +94 -4
  336. package/src/daemon/conversation-tool-setup.ts +25 -0
  337. package/src/daemon/conversation-usage.ts +7 -4
  338. package/src/daemon/conversation.ts +86 -28
  339. package/src/daemon/handlers/config-slack-channel.ts +269 -94
  340. package/src/daemon/handlers/conversations.ts +4 -1
  341. package/src/daemon/handlers/shared.ts +22 -0
  342. package/src/daemon/handlers/skills.ts +321 -77
  343. package/src/daemon/host-browser-proxy.ts +2 -1
  344. package/src/daemon/lifecycle.ts +122 -25
  345. package/src/daemon/message-protocol.ts +6 -0
  346. package/src/daemon/message-types/conversations.ts +34 -1
  347. package/src/daemon/message-types/home.ts +40 -0
  348. package/src/daemon/message-types/meet.ts +143 -0
  349. package/src/daemon/message-types/messages.ts +14 -0
  350. package/src/daemon/message-types/schedules.ts +34 -2
  351. package/src/daemon/message-types/skills.ts +16 -0
  352. package/src/daemon/message-types/surfaces.ts +2 -0
  353. package/src/daemon/server.ts +347 -2
  354. package/src/daemon/shutdown-handlers.ts +32 -4
  355. package/src/daemon/shutdown-registry.ts +40 -0
  356. package/src/daemon/tool-side-effects.ts +9 -0
  357. package/src/email/html-renderer.ts +76 -0
  358. package/src/heartbeat/heartbeat-service.ts +93 -7
  359. package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
  360. package/src/home/__tests__/emit-feed-event.test.ts +169 -0
  361. package/src/home/__tests__/feed-scheduler.test.ts +194 -0
  362. package/src/home/__tests__/feed-types.test.ts +275 -0
  363. package/src/home/__tests__/feed-writer.test.ts +688 -0
  364. package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
  365. package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
  366. package/src/home/__tests__/progress-formula.test.ts +213 -0
  367. package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
  368. package/src/home/__tests__/rollup-producer.test.ts +398 -0
  369. package/src/home/assistant-feed-authoring.ts +124 -0
  370. package/src/home/emit-feed-event.ts +158 -0
  371. package/src/home/feed-scheduler.ts +247 -0
  372. package/src/home/feed-types.ts +181 -0
  373. package/src/home/feed-writer.ts +469 -0
  374. package/src/home/platform-gmail-digest.ts +163 -0
  375. package/src/home/progress-formula.ts +86 -0
  376. package/src/home/relationship-state-writer.ts +824 -0
  377. package/src/home/relationship-state.ts +143 -0
  378. package/src/home/rollup-producer.ts +384 -0
  379. package/src/hooks/runner.ts +7 -0
  380. package/src/inbound/platform-callback-registration.ts +12 -3
  381. package/src/inbound/public-ingress-urls.ts +12 -0
  382. package/src/instrument.ts +1 -1
  383. package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
  384. package/src/ipc/cli-client.ts +151 -0
  385. package/src/ipc/cli-server.ts +234 -0
  386. package/src/ipc/gateway-client.ts +180 -0
  387. package/src/ipc/routes/index.ts +5 -0
  388. package/src/ipc/routes/wake-conversation.ts +19 -0
  389. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
  390. package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
  391. package/src/memory/__tests__/conversation-analyze-job.test.ts +232 -0
  392. package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
  393. package/src/memory/app-store.ts +1 -1
  394. package/src/memory/attachments-store.ts +70 -0
  395. package/src/memory/auto-analysis-enqueue.ts +127 -0
  396. package/src/memory/auto-analysis-guard.ts +27 -0
  397. package/src/memory/cleanup-schedule-state.ts +37 -0
  398. package/src/memory/conversation-analyze-job.ts +73 -0
  399. package/src/memory/conversation-crud.ts +99 -0
  400. package/src/memory/conversation-disk-view.ts +7 -0
  401. package/src/memory/conversation-group-migration.ts +34 -2
  402. package/src/memory/conversation-queries.ts +6 -5
  403. package/src/memory/db-init.ts +6 -0
  404. package/src/memory/db-maintenance.ts +108 -0
  405. package/src/memory/db.ts +1 -0
  406. package/src/memory/graph/conversation-graph-memory.ts +15 -0
  407. package/src/memory/graph/extraction.test.ts +23 -0
  408. package/src/memory/graph/extraction.ts +8 -0
  409. package/src/memory/graph/retriever.ts +27 -18
  410. package/src/memory/graph/scoring.test.ts +186 -0
  411. package/src/memory/graph/scoring.ts +31 -1
  412. package/src/memory/graph/tools.ts +1 -1
  413. package/src/memory/group-crud.ts +6 -1
  414. package/src/memory/indexer.ts +95 -16
  415. package/src/memory/job-handlers/cleanup.ts +11 -8
  416. package/src/memory/job-handlers/conversation-starters.ts +16 -10
  417. package/src/memory/jobs-store.ts +64 -4
  418. package/src/memory/jobs-worker.ts +22 -9
  419. package/src/memory/llm-usage-store.ts +92 -56
  420. package/src/memory/migrations/219-oauth-providers-token-exchange-body-format.ts +15 -0
  421. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
  422. package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
  423. package/src/memory/migrations/index.ts +6 -0
  424. package/src/memory/migrations/registry.ts +8 -0
  425. package/src/memory/qdrant-manager.ts +43 -16
  426. package/src/memory/schema/conversations.ts +2 -0
  427. package/src/memory/schema/oauth.ts +3 -0
  428. package/src/memory/usage-buckets.ts +396 -0
  429. package/src/messaging/providers/gmail/client.ts +57 -6
  430. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
  431. package/src/messaging/providers/slack/adapter.ts +143 -38
  432. package/src/messaging/providers/slack/client.ts +16 -0
  433. package/src/messaging/providers/slack/types.ts +4 -0
  434. package/src/notifications/decision-engine.ts +3 -3
  435. package/src/notifications/signal.ts +5 -0
  436. package/src/oauth/__tests__/identity-verifier.test.ts +1 -0
  437. package/src/oauth/byo-connection.test.ts +18 -1
  438. package/src/oauth/byo-connection.ts +3 -1
  439. package/src/oauth/connect-orchestrator.ts +2 -0
  440. package/src/oauth/connection-resolver.ts +6 -2
  441. package/src/oauth/connection.ts +2 -0
  442. package/src/oauth/oauth-store.ts +9 -0
  443. package/src/oauth/platform-connection.test.ts +98 -0
  444. package/src/oauth/platform-connection.ts +52 -31
  445. package/src/oauth/seed-providers.ts +7 -0
  446. package/src/permissions/checker.ts +16 -6
  447. package/src/permissions/defaults.ts +49 -1
  448. package/src/permissions/trust-store.ts +3 -3
  449. package/src/permissions/workspace-policy.ts +3 -0
  450. package/src/platform/client.test.ts +10 -0
  451. package/src/platform/sync-identity.ts +129 -0
  452. package/src/prompts/persona-resolver.ts +126 -2
  453. package/src/prompts/system-prompt.ts +59 -18
  454. package/src/prompts/templates/BOOTSTRAP.md +5 -5
  455. package/src/prompts/templates/SOUL.md +3 -1
  456. package/src/prompts/templates/UPDATES.md +12 -0
  457. package/src/prompts/templates/channels/slack.md +20 -0
  458. package/src/prompts/update-bulletin-format.ts +26 -9
  459. package/src/prompts/update-bulletin.ts +34 -23
  460. package/src/prompts/user-reference.ts +20 -17
  461. package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
  462. package/src/providers/anthropic/client.ts +157 -61
  463. package/src/providers/fireworks/client.ts +2 -2
  464. package/src/providers/gemini/client.ts +9 -1
  465. package/src/providers/model-catalog.ts +6 -0
  466. package/src/providers/model-intents.ts +4 -4
  467. package/src/providers/ollama/client.ts +2 -2
  468. package/src/providers/openai/chat-completions-provider.ts +474 -0
  469. package/src/providers/openai/client.ts +25 -440
  470. package/src/providers/openai/responses-provider.ts +502 -0
  471. package/src/providers/openrouter/client.ts +101 -4
  472. package/src/providers/provider-secret-catalog.ts +139 -0
  473. package/src/providers/registry.ts +2 -2
  474. package/src/providers/retry.ts +14 -3
  475. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
  476. package/src/providers/speech-to-text/__tests__/resolve.test.ts +828 -0
  477. package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
  478. package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
  479. package/src/providers/speech-to-text/deepgram.test.ts +332 -0
  480. package/src/providers/speech-to-text/deepgram.ts +115 -0
  481. package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
  482. package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
  483. package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
  484. package/src/providers/speech-to-text/google-gemini.ts +101 -0
  485. package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
  486. package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
  487. package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
  488. package/src/providers/speech-to-text/openai-whisper.ts +63 -33
  489. package/src/providers/speech-to-text/provider-catalog.ts +306 -0
  490. package/src/providers/speech-to-text/resolve.ts +386 -6
  491. package/src/providers/types.ts +9 -0
  492. package/src/runtime/AGENTS.md +43 -1
  493. package/src/runtime/__tests__/agent-wake.test.ts +831 -0
  494. package/src/runtime/__tests__/runtime-mode.test.ts +62 -0
  495. package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
  496. package/src/runtime/agent-wake.ts +512 -0
  497. package/src/runtime/auth/__tests__/route-policy.test.ts +40 -0
  498. package/src/runtime/auth/route-policy.ts +30 -5
  499. package/src/runtime/auth/token-service.ts +56 -1
  500. package/src/runtime/btw-sidechain.ts +2 -0
  501. package/src/runtime/capability-tokens.ts +10 -10
  502. package/src/runtime/channel-invite-transport.ts +1 -1
  503. package/src/runtime/channel-invite-transports/email.ts +14 -6
  504. package/src/runtime/channel-readiness-service.ts +12 -22
  505. package/src/runtime/chrome-extension-registry.ts +38 -2
  506. package/src/runtime/http-server.ts +395 -10
  507. package/src/runtime/http-types.ts +6 -2
  508. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +36 -0
  509. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
  510. package/src/runtime/migrations/migration-transport.ts +1 -0
  511. package/src/runtime/migrations/migration-wizard.ts +1 -0
  512. package/src/runtime/migrations/vbundle-import-analyzer.ts +77 -1
  513. package/src/runtime/migrations/vbundle-importer.ts +34 -0
  514. package/src/runtime/pending-interactions.ts +0 -11
  515. package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
  516. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +507 -0
  517. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +208 -0
  518. package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
  519. package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
  520. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
  521. package/src/runtime/routes/app-management-routes.ts +12 -18
  522. package/src/runtime/routes/attachment-routes.test.ts +9 -3
  523. package/src/runtime/routes/attachment-routes.ts +216 -17
  524. package/src/runtime/routes/backup-routes.ts +519 -0
  525. package/src/runtime/routes/browser-extension-pair-routes.ts +82 -23
  526. package/src/runtime/routes/btw-routes.ts +8 -6
  527. package/src/runtime/routes/contact-routes.test.ts +298 -0
  528. package/src/runtime/routes/contact-routes.ts +132 -5
  529. package/src/runtime/routes/conversation-analysis-routes.ts +22 -142
  530. package/src/runtime/routes/conversation-management-routes.ts +115 -0
  531. package/src/runtime/routes/conversation-routes.ts +367 -146
  532. package/src/runtime/routes/filing-routes.ts +93 -0
  533. package/src/runtime/routes/home-feed-routes.ts +334 -0
  534. package/src/runtime/routes/home-state-routes.ts +138 -0
  535. package/src/runtime/routes/host-browser-routes.ts +3 -14
  536. package/src/runtime/routes/identity-intro-cache.ts +7 -3
  537. package/src/runtime/routes/identity-routes.ts +3 -17
  538. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
  539. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
  540. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
  541. package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
  542. package/src/runtime/routes/integrations/slack/channel.ts +11 -3
  543. package/src/runtime/routes/integrations/slack/share.ts +45 -7
  544. package/src/runtime/routes/llm-context-normalization.ts +303 -0
  545. package/src/runtime/routes/memory-item-routes.test.ts +3 -2
  546. package/src/runtime/routes/migration-routes.ts +40 -5
  547. package/src/runtime/routes/settings-routes.ts +22 -5
  548. package/src/runtime/routes/skills-routes.ts +76 -7
  549. package/src/runtime/routes/stt-routes.ts +233 -0
  550. package/src/runtime/routes/surface-action-routes.ts +41 -2
  551. package/src/runtime/routes/tts-routes.ts +108 -24
  552. package/src/runtime/routes/usage-routes.ts +30 -2
  553. package/src/runtime/routes/user-route-dispatcher.ts +50 -5
  554. package/src/runtime/routes/user-routes.ts +13 -1
  555. package/src/runtime/routes/work-items-routes.ts +8 -1
  556. package/src/runtime/runtime-mode.ts +33 -0
  557. package/src/runtime/services/__tests__/analyze-conversation.test.ts +444 -0
  558. package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
  559. package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
  560. package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
  561. package/src/runtime/services/analyze-conversation.ts +344 -0
  562. package/src/runtime/services/analyze-deps-singleton.ts +32 -0
  563. package/src/runtime/services/auto-analysis-prompt.ts +55 -0
  564. package/src/runtime/skill-route-registry.ts +49 -0
  565. package/src/runtime/slack-block-formatting.ts +437 -10
  566. package/src/schedule/scheduler.ts +50 -0
  567. package/src/security/oauth2.ts +26 -4
  568. package/src/security/secure-keys.ts +25 -2
  569. package/src/security/token-manager.ts +8 -0
  570. package/src/sequence/engine.ts +23 -0
  571. package/src/sequence/types.ts +1 -1
  572. package/src/skills/catalog-files.ts +64 -2
  573. package/src/skills/category-inference.ts +122 -0
  574. package/src/skills/clawhub-files.ts +213 -0
  575. package/src/skills/clawhub.ts +84 -23
  576. package/src/skills/skill-file-provider.ts +40 -0
  577. package/src/skills/skillssh-files.ts +395 -0
  578. package/src/skills/skillssh-registry.ts +4 -4
  579. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +392 -0
  580. package/src/stt/__tests__/types.test.ts +89 -0
  581. package/src/stt/daemon-batch-transcriber.ts +195 -0
  582. package/src/stt/stt-stream-session.ts +499 -0
  583. package/src/stt/types.ts +330 -0
  584. package/src/stt/wav-encoder.test.ts +373 -0
  585. package/src/stt/wav-encoder.ts +175 -0
  586. package/src/subagent/manager.ts +38 -14
  587. package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
  588. package/src/tools/browser/__tests__/browser-status.test.ts +123 -0
  589. package/src/tools/browser/browser-execution.ts +1163 -23
  590. package/src/tools/browser/browser-manager.ts +45 -0
  591. package/src/tools/browser/browser-mode-constants.ts +12 -0
  592. package/src/tools/browser/browser-mode.ts +92 -0
  593. package/src/tools/browser/browser-status-constants.ts +33 -0
  594. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +393 -0
  595. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +29 -0
  596. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1648 -32
  597. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +264 -0
  598. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +183 -17
  599. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +254 -21
  600. package/src/tools/browser/cdp-client/errors.ts +15 -0
  601. package/src/tools/browser/cdp-client/extension-cdp-client.ts +39 -16
  602. package/src/tools/browser/cdp-client/factory.ts +797 -87
  603. package/src/tools/browser/cdp-client/index.ts +16 -2
  604. package/src/tools/browser/cdp-client/types.ts +68 -0
  605. package/src/tools/credentials/vault.ts +35 -6
  606. package/src/tools/network/web-fetch.ts +5 -2
  607. package/src/tools/network/web-search.ts +5 -2
  608. package/src/tools/shared/shell-output.ts +3 -1
  609. package/src/tools/side-effects.ts +2 -0
  610. package/src/tools/skills/sandbox-runner.ts +3 -2
  611. package/src/tools/terminal/safe-env.ts +10 -2
  612. package/src/tools/terminal/shell.ts +15 -4
  613. package/src/tools/tool-manifest.ts +21 -0
  614. package/src/tools/types.ts +17 -0
  615. package/src/tools/ui-surface/definitions.ts +6 -1
  616. package/src/tts/__tests__/provider-adapters.test.ts +834 -0
  617. package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
  618. package/src/tts/__tests__/provider-catalog.test.ts +183 -0
  619. package/src/tts/__tests__/provider-registry.test.ts +90 -0
  620. package/src/tts/provider-catalog.ts +201 -0
  621. package/src/tts/provider-registry.ts +73 -0
  622. package/src/tts/providers/deepgram-provider.ts +219 -0
  623. package/src/tts/providers/elevenlabs-provider.ts +211 -0
  624. package/src/tts/providers/fish-audio-provider.ts +183 -0
  625. package/src/tts/providers/index.ts +42 -0
  626. package/src/tts/providers/register-builtins.ts +130 -0
  627. package/src/tts/synthesize-text.ts +110 -0
  628. package/src/tts/tts-config-resolver.ts +78 -0
  629. package/src/tts/types.ts +153 -0
  630. package/src/types/onboarding-context.ts +7 -0
  631. package/src/util/abort-reasons.ts +58 -0
  632. package/src/util/device-id.ts +32 -16
  633. package/src/util/errors.ts +9 -1
  634. package/src/util/platform.ts +54 -10
  635. package/src/util/pricing.ts +66 -3
  636. package/src/util/spawn.ts +1 -1
  637. package/src/util/truncate.ts +4 -2
  638. package/src/util/unicode.ts +201 -0
  639. package/src/version.ts +19 -24
  640. package/src/watcher/engine.ts +23 -0
  641. package/src/watcher/watcher-store.ts +31 -0
  642. package/src/workspace/migrations/003-seed-device-id.ts +9 -3
  643. package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
  644. package/src/workspace/migrations/029-seed-pkb.ts +1 -1
  645. package/src/workspace/migrations/031-drop-user-md.ts +317 -0
  646. package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
  647. package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
  648. package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
  649. package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
  650. package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
  651. package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
  652. package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
  653. package/src/workspace/migrations/registry.ts +16 -0
  654. package/src/workspace/top-level-renderer.ts +13 -1
  655. package/src/workspace/turn-commit.ts +31 -0
  656. package/src/__tests__/email-cli.test.ts +0 -297
  657. package/src/__tests__/email-service-config-fallback.test.ts +0 -102
  658. package/src/cli/commands/browser-relay.ts +0 -466
  659. package/src/email/guardrails.ts +0 -221
  660. package/src/email/provider.ts +0 -117
  661. package/src/email/providers/agentmail.ts +0 -361
  662. package/src/email/providers/index.ts +0 -65
  663. package/src/email/service.ts +0 -384
  664. package/src/email/types.ts +0 -126
  665. package/src/prompts/templates/USER.md +0 -13
  666. package/src/providers/speech-to-text/types.ts +0 -17
  667. package/src/runtime/routes/browser-cdp-routes.ts +0 -229
@@ -0,0 +1,398 @@
1
+ /**
2
+ * Unit tests for the home-feed rollup producer.
3
+ *
4
+ * All dependencies are injected via `RollupProducerDeps` spies so the
5
+ * tests never touch `mock.module`, which leaks across files in Bun's
6
+ * test runner. The production caller passes `undefined` and the
7
+ * producer falls through to the real config loader, feed reader,
8
+ * relationship-state reader, and provider registry.
9
+ */
10
+
11
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
12
+
13
+ import type {
14
+ ContentBlock,
15
+ Provider,
16
+ ProviderResponse,
17
+ } from "../../providers/types.js";
18
+ import type { WriteAssistantFeedItemParams } from "../assistant-feed-authoring.js";
19
+ import type { FeedItem } from "../feed-types.js";
20
+ import { runRollupProducer } from "../rollup-producer.js";
21
+
22
+ const writeItem = mock<
23
+ (params: WriteAssistantFeedItemParams) => Promise<unknown>
24
+ >(async () => ({}));
25
+
26
+ const stubRelationshipState = async () =>
27
+ ({
28
+ version: 1,
29
+ assistantId: "self",
30
+ tier: 2,
31
+ progressPercent: 42,
32
+ facts: [
33
+ { category: "voice", text: "Ships fast, explains the why." },
34
+ { category: "priorities", text: "JARVIS is the current focus." },
35
+ ],
36
+ capabilities: [],
37
+ conversationCount: 17,
38
+ hatchedDate: "2026-03-01T00:00:00.000Z",
39
+ assistantName: "Vellum",
40
+ userName: "Alex",
41
+ updatedAt: "2026-04-14T12:00:00.000Z",
42
+ // biome-ignore lint/suspicious/noExplicitAny: the real shape is
43
+ // internal-only and we only need the subset the producer reads.
44
+ }) as any;
45
+
46
+ function makeAction(overrides: Partial<FeedItem> & { id: string }): FeedItem {
47
+ return {
48
+ id: overrides.id,
49
+ type: "action",
50
+ priority: 50,
51
+ title: overrides.title ?? "Action title",
52
+ summary: overrides.summary ?? "Action summary.",
53
+ timestamp: overrides.timestamp ?? "2026-04-14T12:00:00.000Z",
54
+ status: overrides.status ?? "new",
55
+ author: overrides.author ?? "assistant",
56
+ createdAt: overrides.createdAt ?? "2026-04-14T12:00:00.000Z",
57
+ source: overrides.source,
58
+ expiresAt: overrides.expiresAt,
59
+ minTimeAway: overrides.minTimeAway,
60
+ actions: overrides.actions,
61
+ };
62
+ }
63
+
64
+ const stubLoadRecentActions = (items: FeedItem[]) => () => items;
65
+
66
+ function makeProvider(
67
+ handler: (
68
+ messages: Parameters<Provider["sendMessage"]>[0],
69
+ tools: Parameters<Provider["sendMessage"]>[1],
70
+ systemPrompt: Parameters<Provider["sendMessage"]>[2],
71
+ options: Parameters<Provider["sendMessage"]>[3],
72
+ ) => Promise<ProviderResponse>,
73
+ ): Provider {
74
+ return {
75
+ name: "mock",
76
+ sendMessage: handler,
77
+ };
78
+ }
79
+
80
+ function scriptedProvider(content: ContentBlock[]): Provider {
81
+ return makeProvider(async () => ({
82
+ content,
83
+ model: "mock-model",
84
+ usage: { inputTokens: 0, outputTokens: 0 },
85
+ stopReason: "tool_use",
86
+ }));
87
+ }
88
+
89
+ function throwingProvider(error: Error): Provider {
90
+ return makeProvider(async () => {
91
+ throw error;
92
+ });
93
+ }
94
+
95
+ function toolUseContent(input: unknown): ContentBlock {
96
+ return {
97
+ type: "tool_use",
98
+ id: "tu_1",
99
+ name: "write_feed_items",
100
+ input: input as Record<string, unknown>,
101
+ };
102
+ }
103
+
104
+ const oneAction: FeedItem[] = [
105
+ makeAction({
106
+ id: "a1",
107
+ source: "gmail",
108
+ title: "Replied to Alice",
109
+ summary: "Sent a reply to alice@example.com.",
110
+ createdAt: "2026-04-14T11:30:00.000Z",
111
+ }),
112
+ ];
113
+
114
+ beforeEach(() => {
115
+ writeItem.mockClear();
116
+ });
117
+
118
+ describe("runRollupProducer", () => {
119
+ test("writes each digest/thread returned in the tool call", async () => {
120
+ const provider = scriptedProvider([
121
+ toolUseContent({
122
+ items: [
123
+ {
124
+ type: "digest",
125
+ source: "gmail",
126
+ title: "3 replies sent this morning",
127
+ summary: "Replied to Alice, Bob, and Carol over the past hour.",
128
+ priority: 70,
129
+ },
130
+ {
131
+ type: "thread",
132
+ source: "assistant",
133
+ title: "Outreach sequence 'Q2 renewals'",
134
+ summary: "Step 1 sent to 2 of 5 contacts; awaiting replies.",
135
+ priority: 55,
136
+ },
137
+ ],
138
+ }),
139
+ ]);
140
+
141
+ const result = await runRollupProducer(new Date(), {
142
+ writeItem,
143
+ loadRelationshipState: stubRelationshipState,
144
+ loadRecentActions: stubLoadRecentActions(oneAction),
145
+ resolveProvider: () => provider,
146
+ });
147
+
148
+ expect(result.skippedReason).toBeNull();
149
+ expect(result.wroteCount).toBe(2);
150
+ expect(writeItem).toHaveBeenCalledTimes(2);
151
+ const firstCall = writeItem.mock.calls[0]![0];
152
+ expect(firstCall.type).toBe("digest");
153
+ expect(firstCall.title).toBe("3 replies sent this morning");
154
+ expect(firstCall.priority).toBe(70);
155
+ });
156
+
157
+ test("returns no_actions when the activity log is empty", async () => {
158
+ // When there's nothing to roll up, we don't even hit the provider.
159
+ // The scheduler uses this to avoid advancing the cooldown gate.
160
+ const provider = scriptedProvider([toolUseContent({ items: [] })]);
161
+ const providerSpy = mock(provider.sendMessage);
162
+ provider.sendMessage = providerSpy;
163
+
164
+ const result = await runRollupProducer(new Date(), {
165
+ writeItem,
166
+ loadRelationshipState: stubRelationshipState,
167
+ loadRecentActions: stubLoadRecentActions([]),
168
+ resolveProvider: () => provider,
169
+ });
170
+
171
+ expect(result.skippedReason).toBe("no_actions");
172
+ expect(result.wroteCount).toBe(0);
173
+ expect(providerSpy).not.toHaveBeenCalled();
174
+ expect(writeItem).not.toHaveBeenCalled();
175
+ });
176
+
177
+ test("serializes recent actions into the user prompt", async () => {
178
+ let capturedPrompt = "";
179
+ const provider = makeProvider(async (messages) => {
180
+ capturedPrompt = messages
181
+ .flatMap((m) => m.content)
182
+ .filter((b): b is { type: "text"; text: string } => b.type === "text")
183
+ .map((b) => b.text)
184
+ .join("\n");
185
+ return {
186
+ content: [toolUseContent({ items: [] })],
187
+ model: "mock-model",
188
+ usage: { inputTokens: 0, outputTokens: 0 },
189
+ stopReason: "tool_use",
190
+ };
191
+ });
192
+
193
+ const actions: FeedItem[] = [
194
+ makeAction({
195
+ id: "a1",
196
+ source: "gmail",
197
+ title: "Replied to Alice",
198
+ summary: "Sent a reply to alice@example.com.",
199
+ createdAt: "2026-04-14T11:30:00.000Z",
200
+ }),
201
+ makeAction({
202
+ id: "a2",
203
+ source: "slack",
204
+ title: "Posted in #general",
205
+ summary: "Answered a question about the deploy.",
206
+ createdAt: "2026-04-14T11:45:00.000Z",
207
+ }),
208
+ ];
209
+
210
+ await runRollupProducer(new Date(), {
211
+ writeItem,
212
+ loadRelationshipState: stubRelationshipState,
213
+ loadRecentActions: stubLoadRecentActions(actions),
214
+ resolveProvider: () => provider,
215
+ });
216
+
217
+ expect(capturedPrompt).toContain("Replied to Alice");
218
+ expect(capturedPrompt).toContain("alice@example.com");
219
+ expect(capturedPrompt).toContain("Posted in #general");
220
+ expect(capturedPrompt).toContain("[gmail]");
221
+ expect(capturedPrompt).toContain("[slack]");
222
+ });
223
+
224
+ test("returns empty_items when the model emits an empty items array", async () => {
225
+ const provider = scriptedProvider([toolUseContent({ items: [] })]);
226
+
227
+ const result = await runRollupProducer(new Date(), {
228
+ writeItem,
229
+ loadRelationshipState: stubRelationshipState,
230
+ loadRecentActions: stubLoadRecentActions(oneAction),
231
+ resolveProvider: () => provider,
232
+ });
233
+
234
+ expect(result.skippedReason).toBe("empty_items");
235
+ expect(result.wroteCount).toBe(0);
236
+ expect(writeItem).not.toHaveBeenCalled();
237
+ });
238
+
239
+ test("caps the batch at MAX_ITEMS_PER_ROLLUP (3)", async () => {
240
+ const provider = scriptedProvider([
241
+ toolUseContent({
242
+ items: [
243
+ { type: "digest", title: "One", summary: "One summary" },
244
+ { type: "digest", title: "Two", summary: "Two summary" },
245
+ { type: "thread", title: "Three", summary: "Three summary" },
246
+ {
247
+ type: "digest",
248
+ title: "Four",
249
+ summary: "Four summary — should be dropped.",
250
+ },
251
+ ],
252
+ }),
253
+ ]);
254
+
255
+ const result = await runRollupProducer(new Date(), {
256
+ writeItem,
257
+ loadRelationshipState: stubRelationshipState,
258
+ loadRecentActions: stubLoadRecentActions(oneAction),
259
+ resolveProvider: () => provider,
260
+ });
261
+
262
+ expect(result.wroteCount).toBe(3);
263
+ expect(writeItem).toHaveBeenCalledTimes(3);
264
+ });
265
+
266
+ test("rejects nudge and action types at coercion time", async () => {
267
+ // The tool schema narrows `type` to digest/thread, but the runtime
268
+ // coercion enforces it too so a drifted model can't sneak through.
269
+ const provider = scriptedProvider([
270
+ toolUseContent({
271
+ items: [
272
+ {
273
+ type: "nudge",
274
+ title: "Should be rejected",
275
+ summary: "Rollup must never emit nudges.",
276
+ },
277
+ {
278
+ type: "action",
279
+ title: "Also rejected",
280
+ summary: "Rollup must never emit actions.",
281
+ },
282
+ {
283
+ type: "digest",
284
+ title: "Valid digest",
285
+ summary: "This one should land.",
286
+ },
287
+ ],
288
+ }),
289
+ ]);
290
+
291
+ const result = await runRollupProducer(new Date(), {
292
+ writeItem,
293
+ loadRelationshipState: stubRelationshipState,
294
+ loadRecentActions: stubLoadRecentActions(oneAction),
295
+ resolveProvider: () => provider,
296
+ });
297
+
298
+ expect(result.wroteCount).toBe(1);
299
+ expect(writeItem).toHaveBeenCalledTimes(1);
300
+ expect(writeItem.mock.calls[0]![0].title).toBe("Valid digest");
301
+ });
302
+
303
+ test("reports malformed_output when every item in a non-empty batch fails coercion", async () => {
304
+ const provider = scriptedProvider([
305
+ toolUseContent({
306
+ items: [
307
+ { type: "digest", title: "", summary: "empty title, rejected" },
308
+ { type: "bogus", title: "bad type", summary: "also rejected" },
309
+ { type: "digest", title: "valid title" }, // missing summary
310
+ ],
311
+ }),
312
+ ]);
313
+
314
+ const result = await runRollupProducer(new Date(), {
315
+ writeItem,
316
+ loadRelationshipState: stubRelationshipState,
317
+ loadRecentActions: stubLoadRecentActions(oneAction),
318
+ resolveProvider: () => provider,
319
+ });
320
+
321
+ expect(result.skippedReason).toBe("malformed_output");
322
+ expect(result.wroteCount).toBe(0);
323
+ expect(writeItem).not.toHaveBeenCalled();
324
+ });
325
+
326
+ test("returns no_provider when the resolver returns null", async () => {
327
+ const result = await runRollupProducer(new Date(), {
328
+ writeItem,
329
+ loadRelationshipState: stubRelationshipState,
330
+ loadRecentActions: stubLoadRecentActions(oneAction),
331
+ resolveProvider: () => null,
332
+ });
333
+
334
+ expect(result.skippedReason).toBe("no_provider");
335
+ expect(result.wroteCount).toBe(0);
336
+ expect(writeItem).not.toHaveBeenCalled();
337
+ });
338
+
339
+ test("returns provider_error when sendMessage throws", async () => {
340
+ const provider = throwingProvider(new Error("network down"));
341
+
342
+ const result = await runRollupProducer(new Date(), {
343
+ writeItem,
344
+ loadRelationshipState: stubRelationshipState,
345
+ loadRecentActions: stubLoadRecentActions(oneAction),
346
+ resolveProvider: () => provider,
347
+ });
348
+
349
+ expect(result.skippedReason).toBe("provider_error");
350
+ expect(result.wroteCount).toBe(0);
351
+ });
352
+
353
+ test("returns malformed_output when the response has no matching tool_use block", async () => {
354
+ const provider = scriptedProvider([{ type: "text", text: "just prose" }]);
355
+
356
+ const result = await runRollupProducer(new Date(), {
357
+ writeItem,
358
+ loadRelationshipState: stubRelationshipState,
359
+ loadRecentActions: stubLoadRecentActions(oneAction),
360
+ resolveProvider: () => provider,
361
+ });
362
+
363
+ expect(result.skippedReason).toBe("malformed_output");
364
+ expect(result.wroteCount).toBe(0);
365
+ });
366
+
367
+ test("clamps priority to the valid [0, 100] window", async () => {
368
+ const provider = scriptedProvider([
369
+ toolUseContent({
370
+ items: [
371
+ {
372
+ type: "digest",
373
+ title: "High priority",
374
+ summary: "Model returned 150",
375
+ priority: 150,
376
+ },
377
+ {
378
+ type: "thread",
379
+ title: "Low priority",
380
+ summary: "Model returned -5",
381
+ priority: -5,
382
+ },
383
+ ],
384
+ }),
385
+ ]);
386
+
387
+ await runRollupProducer(new Date(), {
388
+ writeItem,
389
+ loadRelationshipState: stubRelationshipState,
390
+ loadRecentActions: stubLoadRecentActions(oneAction),
391
+ resolveProvider: () => provider,
392
+ });
393
+
394
+ expect(writeItem).toHaveBeenCalledTimes(2);
395
+ expect(writeItem.mock.calls[0]![0].priority).toBe(100);
396
+ expect(writeItem.mock.calls[1]![0].priority).toBe(0);
397
+ });
398
+ });
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Assistant-authoring helper for the home activity feed.
3
+ *
4
+ * This is the in-process API the assistant daemon (or a skill / tool
5
+ * running inside it) calls to append a nudge, digest, action, or
6
+ * thread to the macOS Home page feed under `author: "assistant"`.
7
+ *
8
+ * The helper exists so callers don't have to hand-roll the ceremony
9
+ * around a `FeedItem`:
10
+ *
11
+ * - generate a stable `id`
12
+ * - set the `author` discriminator to `"assistant"` (so the hybrid
13
+ * authoring resolver in `feed-writer.ts` lets it override
14
+ * platform baseline items for the same `(type, source)` pair)
15
+ * - seed `status` / `timestamp` / `createdAt`
16
+ * - pick a sensible default `priority` that outranks the platform
17
+ * baseline (40) so assistant-authored items surface above
18
+ * background generators unless an explicit priority is passed
19
+ * - validate the constructed item against `feedItemSchema` so a
20
+ * malformed call throws loudly at the source instead of corrupting
21
+ * the on-disk snapshot via `appendFeedItem`
22
+ *
23
+ * Persistence is delegated to `appendFeedItem` — all of the merge
24
+ * semantics (digest replacement, thread in-place update, nudge author
25
+ * precedence, action append-without-replace, per-source action cap)
26
+ * continue to live in the writer and are not re-implemented here.
27
+ *
28
+ * NOTE: This helper is intentionally in-process only. There is no
29
+ * HTTP route wrapping it. Callers (skills, tools, daemon code) import
30
+ * and call `writeAssistantFeedItem` directly. Wiring the trigger side
31
+ * — prompt-driven calls, skill entry points, etc. — is deliberately
32
+ * out of scope for Phase 5 of the home-activity-feed plan and lives
33
+ * in follow-up work.
34
+ */
35
+
36
+ import { randomUUID } from "node:crypto";
37
+
38
+ import {
39
+ type FeedAction,
40
+ type FeedItem,
41
+ feedItemSchema,
42
+ type FeedItemSource,
43
+ type FeedItemType,
44
+ } from "./feed-types.js";
45
+ import { appendFeedItem } from "./feed-writer.js";
46
+
47
+ /**
48
+ * Default priority for assistant-authored feed items. Sits above the
49
+ * platform baseline (40) so an assistant-authored digest / nudge
50
+ * surfaces above a same-source platform default unless the caller
51
+ * passes an explicit value.
52
+ */
53
+ const DEFAULT_ASSISTANT_PRIORITY = 60;
54
+
55
+ /**
56
+ * Parameters accepted by {@link writeAssistantFeedItem}.
57
+ *
58
+ * The helper hard-codes `author: "assistant"`, `status: "new"`, and
59
+ * the id / timestamps, so callers only have to supply the semantic
60
+ * fields that describe *what* the item is about.
61
+ */
62
+ export interface WriteAssistantFeedItemParams {
63
+ /** Kind of feed item — drives the Swift view used to render it. */
64
+ type: FeedItemType;
65
+ /** Origin of the underlying event (gmail, slack, calendar, assistant). */
66
+ source?: FeedItemSource;
67
+ /** Short headline rendered in the feed row. */
68
+ title: string;
69
+ /** Body copy rendered below the title. */
70
+ summary: string;
71
+ /**
72
+ * Priority in [0, 100]. Defaults to {@link DEFAULT_ASSISTANT_PRIORITY}
73
+ * (60) — higher than the platform baseline of 40 so assistant items
74
+ * beat same-source platform defaults by default.
75
+ */
76
+ priority?: number;
77
+ /** Action buttons surfaced on the feed row. */
78
+ actions?: FeedAction[];
79
+ /** Minimum seconds the user must be away before the item is shown. */
80
+ minTimeAway?: number;
81
+ /** Absolute ISO-8601 expiry timestamp. */
82
+ expiresAt?: string;
83
+ }
84
+
85
+ /**
86
+ * Build a fully-formed assistant-authored {@link FeedItem}, validate
87
+ * it against the canonical schema, persist it through
88
+ * {@link appendFeedItem}, and return the constructed item so the
89
+ * caller can log / reference it downstream.
90
+ *
91
+ * Throws a `ZodError` if the constructed item fails validation —
92
+ * that is a programming error in the caller (e.g. empty `title`) and
93
+ * must not be silently swallowed. Persistence-layer failures are
94
+ * absorbed by `appendFeedItem` itself per its warn-log contract.
95
+ */
96
+ export async function writeAssistantFeedItem(
97
+ params: WriteAssistantFeedItemParams,
98
+ ): Promise<FeedItem> {
99
+ const now = new Date().toISOString();
100
+
101
+ const item: FeedItem = {
102
+ id: randomUUID(),
103
+ type: params.type,
104
+ source: params.source,
105
+ title: params.title,
106
+ summary: params.summary,
107
+ priority: params.priority ?? DEFAULT_ASSISTANT_PRIORITY,
108
+ status: "new",
109
+ author: "assistant",
110
+ timestamp: now,
111
+ createdAt: now,
112
+ actions: params.actions,
113
+ minTimeAway: params.minTimeAway,
114
+ expiresAt: params.expiresAt,
115
+ };
116
+
117
+ // Programming-error guardrail: invalid input throws at the source
118
+ // instead of corrupting the on-disk snapshot via the writer.
119
+ feedItemSchema.parse(item);
120
+
121
+ await appendFeedItem(item);
122
+
123
+ return item;
124
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Background-job → home feed event helper.
3
+ *
4
+ * The "force-write, not taught-write" entry point for the activity
5
+ * log. Every background job that wants to surface something on the
6
+ * Home page calls `emitFeedEvent({ source, title, summary, ... })`
7
+ * at the end of its completion path — no LLM involved, no prompt
8
+ * instruction, just a deterministic side effect. This keeps the
9
+ * "what got surfaced" question grep-able to a single symbol.
10
+ *
11
+ * Opinionated defaults for action items:
12
+ *
13
+ * - `type` is hard-coded to `"action"` — this helper is specifically
14
+ * for the activity log. Nudges / digests / threads continue to
15
+ * go through `writeAssistantFeedItem` or platform baseline
16
+ * generators.
17
+ * - `author` is hard-coded to `"assistant"` so hybrid authoring
18
+ * resolution treats these as assistant-produced (platform
19
+ * defaults can never overwrite them).
20
+ * - `source` is REQUIRED — actions always have an origin (gmail,
21
+ * slack, calendar, assistant). The per-source volume cap in
22
+ * `feed-writer.ts` depends on this.
23
+ * - No default `expiresAt`. Action items persist until the user
24
+ * dismisses them. Callers that want auto-expiry pass `expiresAt`
25
+ * explicitly.
26
+ * - Optional `dedupKey` — when set, the helper derives a
27
+ * deterministic id so a second emit for the same logical event
28
+ * (e.g. the same background job running twice on the same
29
+ * signal) updates the existing entry in place instead of
30
+ * appending a duplicate. When absent, a fresh `randomUUID` is
31
+ * used and every call produces a new entry.
32
+ *
33
+ * Persistence goes through `appendFeedItem`, inheriting its
34
+ * warn-log-on-failure contract — callers never need a try/catch.
35
+ * Schema validation runs at build time so a malformed call throws
36
+ * loudly at the source rather than silently corrupting the file.
37
+ */
38
+
39
+ import { randomUUID } from "node:crypto";
40
+
41
+ import {
42
+ type FeedAction,
43
+ type FeedItem,
44
+ feedItemSchema,
45
+ type FeedItemSource,
46
+ } from "./feed-types.js";
47
+ import { appendFeedItem } from "./feed-writer.js";
48
+
49
+ /**
50
+ * Default priority for background-job action items. Sits below the
51
+ * assistant nudge default (60) so an explicit nudge from
52
+ * `writeAssistantFeedItem` surfaces above routine activity log
53
+ * entries, but above the platform baseline (40) so background job
54
+ * traces outrank same-source platform defaults.
55
+ */
56
+ const DEFAULT_EMIT_PRIORITY = 50;
57
+
58
+ /**
59
+ * Parameters accepted by {@link emitFeedEvent}.
60
+ *
61
+ * All action items emitted by background jobs have an origin, so
62
+ * `source` is required. Everything else is optional — callers supply
63
+ * only the fields that describe the specific event.
64
+ */
65
+ export interface EmitFeedEventParams {
66
+ /** Origin of the underlying event (gmail, slack, calendar, assistant). */
67
+ source: FeedItemSource;
68
+ /** Short headline rendered in the feed row. */
69
+ title: string;
70
+ /** Body copy rendered below the title. */
71
+ summary: string;
72
+ /**
73
+ * Stable key used to derive a deterministic id so a second emit
74
+ * for the same logical event updates the existing feed entry in
75
+ * place. Should include enough structure to identify the event
76
+ * uniquely (e.g. `"gmail-unread:msg-<messageId>"`,
77
+ * `"task-runner:<taskId>"`). When omitted, every call produces a
78
+ * fresh id and appends a new entry.
79
+ */
80
+ dedupKey?: string;
81
+ /**
82
+ * Priority in [0, 100]. Defaults to {@link DEFAULT_EMIT_PRIORITY}
83
+ * (50) — above the platform baseline of 40, below the assistant
84
+ * nudge default of 60.
85
+ */
86
+ priority?: number;
87
+ /** Action buttons surfaced on the feed row. */
88
+ actions?: FeedAction[];
89
+ /** Minimum seconds the user must be away before the item is shown. */
90
+ minTimeAway?: number;
91
+ /**
92
+ * Absolute ISO-8601 expiry timestamp. Omit to let the item persist
93
+ * until the user dismisses it (default for activity-log actions).
94
+ */
95
+ expiresAt?: string;
96
+ }
97
+
98
+ /**
99
+ * Build a deterministic feed item id from a source + dedup key.
100
+ *
101
+ * The id is intentionally human-readable: `emit:<source>:<dedupKey>`.
102
+ * This makes debugging easier than a hash (you can eyeball the file
103
+ * and immediately see which background job produced which entry)
104
+ * and `FeedItem.id` is a free-form string so there is no length or
105
+ * charset constraint to worry about.
106
+ */
107
+ function deterministicId(source: FeedItemSource, dedupKey: string): string {
108
+ return `emit:${source}:${dedupKey}`;
109
+ }
110
+
111
+ /**
112
+ * Emit a background-job activity-log entry onto the home feed.
113
+ *
114
+ * Builds a fully-formed assistant-authored `action` {@link FeedItem},
115
+ * validates it against the canonical schema, and persists it via
116
+ * {@link appendFeedItem}. Returns the constructed item so the caller
117
+ * can log / reference it downstream.
118
+ *
119
+ * Throws a `ZodError` if the constructed item fails validation
120
+ * (e.g. a `priority` outside `[0, 100]`) — a programming error in
121
+ * the caller that must not be silently swallowed. Persistence-layer
122
+ * failures are absorbed by `appendFeedItem` per its warn-log
123
+ * contract.
124
+ */
125
+ export async function emitFeedEvent(
126
+ params: EmitFeedEventParams,
127
+ ): Promise<FeedItem> {
128
+ const now = new Date().toISOString();
129
+
130
+ const id =
131
+ params.dedupKey !== undefined
132
+ ? deterministicId(params.source, params.dedupKey)
133
+ : randomUUID();
134
+
135
+ const item: FeedItem = {
136
+ id,
137
+ type: "action",
138
+ source: params.source,
139
+ title: params.title,
140
+ summary: params.summary,
141
+ priority: params.priority ?? DEFAULT_EMIT_PRIORITY,
142
+ status: "new",
143
+ author: "assistant",
144
+ timestamp: now,
145
+ createdAt: now,
146
+ actions: params.actions,
147
+ minTimeAway: params.minTimeAway,
148
+ expiresAt: params.expiresAt,
149
+ };
150
+
151
+ // Programming-error guardrail: invalid input throws at the source
152
+ // instead of corrupting the on-disk snapshot via the writer.
153
+ feedItemSchema.parse(item);
154
+
155
+ await appendFeedItem(item);
156
+
157
+ return item;
158
+ }