@vellumai/assistant 0.7.2 → 0.7.3

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 (347) hide show
  1. package/ARCHITECTURE.md +16 -1
  2. package/docs/architecture/memory.md +5 -2
  3. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
  4. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -9
  5. package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
  6. package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
  7. package/openapi.yaml +449 -22
  8. package/package.json +1 -1
  9. package/src/__tests__/app-control-flow.test.ts +21 -11
  10. package/src/__tests__/assistant-event-hub.test.ts +48 -0
  11. package/src/__tests__/assistant-event.test.ts +0 -10
  12. package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
  13. package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
  14. package/src/__tests__/auto-analysis-end-to-end.test.ts +62 -1
  15. package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
  16. package/src/__tests__/call-conversation-messages.test.ts +8 -2
  17. package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
  18. package/src/__tests__/channel-readiness-service.test.ts +4 -2
  19. package/src/__tests__/config-loader-backfill.test.ts +379 -0
  20. package/src/__tests__/config-schema.test.ts +1 -0
  21. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
  22. package/src/__tests__/config-watcher.test.ts +140 -69
  23. package/src/__tests__/context-search-agent-runner.test.ts +61 -3
  24. package/src/__tests__/context-search-conversations-source.test.ts +0 -24
  25. package/src/__tests__/context-search-fanout.test.ts +0 -1
  26. package/src/__tests__/context-search-memory-source.test.ts +3 -7
  27. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
  28. package/src/__tests__/context-search-pkb-source.test.ts +0 -1
  29. package/src/__tests__/context-search-workspace-source.test.ts +0 -1
  30. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
  31. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
  32. package/src/__tests__/conversation-agent-loop.test.ts +454 -5
  33. package/src/__tests__/conversation-error.test.ts +150 -3
  34. package/src/__tests__/conversation-process-callsite.test.ts +43 -0
  35. package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
  36. package/src/__tests__/conversation-runtime-assembly.test.ts +65 -0
  37. package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
  38. package/src/__tests__/conversation-speed-override.test.ts +0 -3
  39. package/src/__tests__/conversation-store.test.ts +0 -18
  40. package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
  41. package/src/__tests__/conversation-surfaces-data-persist.test.ts +404 -0
  42. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +2 -5
  43. package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
  44. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
  45. package/src/__tests__/credentials-cli.test.ts +7 -0
  46. package/src/__tests__/cu-unified-flow.test.ts +176 -10
  47. package/src/__tests__/date-context.test.ts +164 -2
  48. package/src/__tests__/disk-pressure-guard.test.ts +262 -0
  49. package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
  50. package/src/__tests__/disk-pressure-policy.test.ts +241 -0
  51. package/src/__tests__/disk-pressure-routes.test.ts +379 -0
  52. package/src/__tests__/disk-pressure-tools.test.ts +277 -0
  53. package/src/__tests__/disk-usage.test.ts +150 -0
  54. package/src/__tests__/events-client-registration.test.ts +52 -0
  55. package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
  56. package/src/__tests__/file-write-tool.test.ts +4 -10
  57. package/src/__tests__/filing-service.test.ts +3 -4
  58. package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
  59. package/src/__tests__/heartbeat-service.test.ts +260 -11
  60. package/src/__tests__/host-app-control-proxy.test.ts +195 -25
  61. package/src/__tests__/host-bash-proxy.test.ts +227 -34
  62. package/src/__tests__/host-bash-routes.test.ts +178 -13
  63. package/src/__tests__/host-cu-proxy.test.ts +210 -3
  64. package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
  65. package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
  66. package/src/__tests__/host-file-proxy.test.ts +268 -6
  67. package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
  68. package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
  69. package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
  70. package/src/__tests__/http-user-message-parity.test.ts +107 -1
  71. package/src/__tests__/injector-chain.test.ts +18 -6
  72. package/src/__tests__/injector-disk-pressure.test.ts +224 -0
  73. package/src/__tests__/managed-profile-guard.test.ts +18 -0
  74. package/src/__tests__/mcp-abort-signal.test.ts +130 -0
  75. package/src/__tests__/memory-admin-recall.test.ts +3 -11
  76. package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
  77. package/src/__tests__/normalize-onboarding.test.ts +180 -0
  78. package/src/__tests__/oauth-connect-routes.test.ts +316 -0
  79. package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
  80. package/src/__tests__/onboarding-persona-write.test.ts +308 -0
  81. package/src/__tests__/openai-provider.test.ts +45 -8
  82. package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
  83. package/src/__tests__/platform-callback-registration.test.ts +21 -4
  84. package/src/__tests__/platform.test.ts +2 -1
  85. package/src/__tests__/playbook-execution.test.ts +0 -43
  86. package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
  87. package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
  88. package/src/__tests__/provider-tool-name.test.ts +23 -0
  89. package/src/__tests__/relay-server.test.ts +15 -4
  90. package/src/__tests__/runtime-events-sse.test.ts +4 -8
  91. package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
  92. package/src/__tests__/secret-ingress-http.test.ts +0 -1
  93. package/src/__tests__/suggestion-routes.test.ts +46 -0
  94. package/src/__tests__/twilio-validation.test.ts +2 -2
  95. package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
  96. package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
  97. package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
  98. package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +90 -0
  99. package/src/approvals/guardian-decision-primitive.ts +13 -0
  100. package/src/approvals/guardian-request-resolvers.ts +16 -17
  101. package/src/backup/snapshot-lock.ts +2 -27
  102. package/src/bundler/compiler-tools.ts +3 -2
  103. package/src/calls/call-conversation-messages.ts +46 -10
  104. package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
  105. package/src/cli/commands/bash.ts +35 -108
  106. package/src/cli/commands/contacts.ts +64 -25
  107. package/src/cli/commands/credentials.ts +56 -0
  108. package/src/cli/commands/memory-v2.ts +7 -6
  109. package/src/cli/commands/oauth/__tests__/connect.test.ts +437 -1
  110. package/src/cli/commands/oauth/connect.ts +127 -1
  111. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
  112. package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
  113. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  114. package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
  115. package/src/cli/commands/platform/index.ts +16 -7
  116. package/src/cli/commands/status.ts +57 -0
  117. package/src/cli/program.ts +4 -2
  118. package/src/config/assistant-feature-flags.ts +13 -3
  119. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
  120. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
  121. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
  122. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
  123. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
  124. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
  125. package/src/config/env.ts +0 -8
  126. package/src/config/feature-flag-registry.json +27 -3
  127. package/src/config/loader.ts +127 -8
  128. package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
  129. package/src/config/schemas/call-site-catalog.ts +14 -0
  130. package/src/config/schemas/channels.ts +0 -5
  131. package/src/config/schemas/heartbeat.ts +1 -1
  132. package/src/config/schemas/llm.ts +2 -0
  133. package/src/config/schemas/memory-lifecycle.ts +13 -0
  134. package/src/config/schemas/memory-v2.ts +75 -11
  135. package/src/config/schemas/platform.ts +43 -3
  136. package/src/config/schemas/services.ts +28 -0
  137. package/src/config/seed-inference-profiles.ts +230 -33
  138. package/src/contacts/contact-store.ts +0 -25
  139. package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
  140. package/src/daemon/assistant-attachments.ts +4 -4
  141. package/src/daemon/config-watcher.ts +85 -57
  142. package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
  143. package/src/daemon/conversation-agent-loop.ts +170 -33
  144. package/src/daemon/conversation-error.ts +87 -15
  145. package/src/daemon/conversation-lifecycle.ts +1 -3
  146. package/src/daemon/conversation-process.ts +8 -0
  147. package/src/daemon/conversation-runtime-assembly.ts +26 -0
  148. package/src/daemon/conversation-store.ts +2 -2
  149. package/src/daemon/conversation-surfaces.ts +195 -15
  150. package/src/daemon/conversation-tool-setup.ts +57 -14
  151. package/src/daemon/conversation.ts +17 -22
  152. package/src/daemon/date-context.ts +71 -22
  153. package/src/daemon/disk-pressure-background-gate.ts +73 -0
  154. package/src/daemon/disk-pressure-guard.ts +343 -0
  155. package/src/daemon/disk-pressure-policy.ts +163 -0
  156. package/src/daemon/handlers/shared.ts +0 -1
  157. package/src/daemon/handlers/skills.ts +3 -4
  158. package/src/daemon/host-app-control-proxy.ts +137 -41
  159. package/src/daemon/host-bash-proxy.ts +46 -21
  160. package/src/daemon/host-cu-proxy.ts +49 -3
  161. package/src/daemon/host-file-proxy.ts +43 -7
  162. package/src/daemon/host-transfer-proxy.ts +95 -4
  163. package/src/daemon/lifecycle.ts +79 -28
  164. package/src/daemon/meet-host-supervisor.ts +4 -4
  165. package/src/daemon/meet-manifest-loader.ts +0 -1
  166. package/src/daemon/memory-v2-startup.ts +14 -4
  167. package/src/daemon/message-protocol.ts +3 -0
  168. package/src/daemon/message-types/conversations.ts +4 -0
  169. package/src/daemon/message-types/disk-pressure.ts +9 -0
  170. package/src/daemon/message-types/messages.ts +3 -0
  171. package/src/daemon/profiler-run-store.ts +5 -5
  172. package/src/daemon/tool-setup-types.ts +2 -2
  173. package/src/documents/document-store.ts +85 -0
  174. package/src/filing/filing-service.ts +30 -5
  175. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
  176. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
  177. package/src/heartbeat/heartbeat-run-store.ts +13 -0
  178. package/src/heartbeat/heartbeat-service.ts +205 -31
  179. package/src/home/feed-scheduler.ts +18 -0
  180. package/src/inbound/platform-callback-registration.ts +8 -15
  181. package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
  182. package/src/ipc/assistant-server.ts +56 -2
  183. package/src/ipc/gateway-client.ts +37 -3
  184. package/src/live-voice/live-voice-archive.ts +4 -4
  185. package/src/live-voice/protocol.ts +5 -7
  186. package/src/media/image-service.ts +1 -7
  187. package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
  188. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +52 -22
  189. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
  190. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
  191. package/src/memory/admin.ts +5 -9
  192. package/src/memory/context-search/agent-runner.ts +19 -2
  193. package/src/memory/context-search/sources/conversations.ts +2 -11
  194. package/src/memory/context-search/sources/memory-v2.ts +5 -4
  195. package/src/memory/context-search/sources/memory.ts +0 -1
  196. package/src/memory/context-search/types.ts +0 -1
  197. package/src/memory/conversation-crud.ts +4 -12
  198. package/src/memory/db-init.ts +2 -0
  199. package/src/memory/embedding-runtime-manager.ts +119 -5
  200. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +32 -21
  201. package/src/memory/graph/conversation-graph-memory.ts +42 -54
  202. package/src/memory/graph/extraction.ts +1 -3
  203. package/src/memory/graph/graph-search.test.ts +10 -67
  204. package/src/memory/graph/graph-search.ts +1 -20
  205. package/src/memory/graph/retriever.test.ts +6 -0
  206. package/src/memory/graph/retriever.ts +6 -10
  207. package/src/memory/indexer.ts +54 -45
  208. package/src/memory/job-handlers/backfill.ts +2 -11
  209. package/src/memory/job-handlers/cleanup.ts +43 -0
  210. package/src/memory/job-handlers/embedding.ts +6 -8
  211. package/src/memory/job-handlers/summarization.ts +2 -7
  212. package/src/memory/jobs-store.ts +48 -0
  213. package/src/memory/jobs-worker.ts +81 -43
  214. package/src/memory/memory-v2-activation-log-store.ts +32 -14
  215. package/src/memory/memory-v2-concept-frequency.ts +169 -0
  216. package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
  217. package/src/memory/migrations/index.ts +1 -0
  218. package/src/memory/pkb/pkb-search.test.ts +6 -0
  219. package/src/memory/qdrant-client.ts +0 -13
  220. package/src/memory/rerank-local.ts +374 -0
  221. package/src/memory/search/semantic.ts +6 -67
  222. package/src/memory/trace-event-store.ts +1 -17
  223. package/src/memory/v2/__tests__/activation.test.ts +311 -250
  224. package/src/memory/v2/__tests__/consolidation-job.test.ts +40 -8
  225. package/src/memory/v2/__tests__/injection.test.ts +157 -167
  226. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
  227. package/src/memory/v2/__tests__/qdrant.test.ts +16 -0
  228. package/src/memory/v2/__tests__/reranker.test.ts +338 -0
  229. package/src/memory/v2/__tests__/sim.test.ts +5 -199
  230. package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
  231. package/src/memory/v2/__tests__/static-context.test.ts +76 -1
  232. package/src/memory/v2/activation.ts +149 -156
  233. package/src/memory/v2/consolidation-job.ts +62 -12
  234. package/src/memory/v2/injection.ts +47 -60
  235. package/src/memory/v2/prompts/consolidation.ts +36 -1
  236. package/src/memory/v2/qdrant.ts +99 -0
  237. package/src/memory/v2/reranker.ts +177 -0
  238. package/src/memory/v2/sim.ts +10 -84
  239. package/src/memory/v2/skill-content.ts +4 -3
  240. package/src/memory/v2/skill-store.ts +82 -59
  241. package/src/memory/v2/static-context.ts +22 -0
  242. package/src/memory/v2/types.ts +10 -10
  243. package/src/notifications/copy-composer.ts +13 -0
  244. package/src/notifications/signal.ts +4 -0
  245. package/src/oauth/AGENTS.md +3 -1
  246. package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
  247. package/src/oauth/connect-orchestrator.ts +2 -0
  248. package/src/oauth/connection-resolver.test.ts +66 -1
  249. package/src/oauth/connection-resolver.ts +55 -1
  250. package/src/oauth/oauth-connect-state.ts +77 -0
  251. package/src/oauth/seed-providers.ts +58 -1
  252. package/src/plugins/defaults/injectors.ts +35 -2
  253. package/src/plugins/defaults/memory-retrieval.ts +5 -6
  254. package/src/plugins/types.ts +7 -0
  255. package/src/proactive-artifact/aux-message-injector.ts +74 -0
  256. package/src/proactive-artifact/decision.test.ts +226 -0
  257. package/src/proactive-artifact/decision.ts +165 -0
  258. package/src/proactive-artifact/index.ts +7 -0
  259. package/src/proactive-artifact/job.test.ts +867 -0
  260. package/src/proactive-artifact/job.ts +352 -0
  261. package/src/proactive-artifact/message-copy.ts +41 -0
  262. package/src/proactive-artifact/trigger-state.test.ts +277 -0
  263. package/src/proactive-artifact/trigger-state.ts +119 -0
  264. package/src/prompts/normalize-onboarding.ts +80 -0
  265. package/src/prompts/persona-resolver.ts +101 -9
  266. package/src/prompts/system-prompt.ts +21 -7
  267. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  268. package/src/providers/__tests__/retry-callsite.test.ts +222 -1
  269. package/src/providers/model-intents.ts +7 -0
  270. package/src/providers/openrouter/client.ts +8 -0
  271. package/src/providers/retry.ts +50 -0
  272. package/src/providers/types.ts +1 -0
  273. package/src/runtime/__tests__/agent-wake.test.ts +456 -3
  274. package/src/runtime/agent-wake.ts +238 -100
  275. package/src/runtime/assistant-event-hub.ts +36 -6
  276. package/src/runtime/assistant-event.ts +0 -1
  277. package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
  278. package/src/runtime/auth/route-policy.ts +14 -1
  279. package/src/runtime/auth/same-actor.ts +216 -0
  280. package/src/runtime/channel-retry-sweep.ts +65 -1
  281. package/src/runtime/guardian-reply-router.ts +10 -0
  282. package/src/runtime/local-actor-identity.ts +52 -11
  283. package/src/runtime/pending-interactions.ts +8 -0
  284. package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
  285. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
  286. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  287. package/src/runtime/routes/client-routes.ts +20 -2
  288. package/src/runtime/routes/contact-routes.ts +0 -25
  289. package/src/runtime/routes/conversation-routes.ts +35 -26
  290. package/src/runtime/routes/debug-bash-routes.ts +163 -0
  291. package/src/runtime/routes/disk-pressure-routes.ts +121 -0
  292. package/src/runtime/routes/document-pdf-renderer.ts +6 -2
  293. package/src/runtime/routes/documents-routes.ts +2 -75
  294. package/src/runtime/routes/events-routes.ts +41 -9
  295. package/src/runtime/routes/host-bash-routes.ts +23 -3
  296. package/src/runtime/routes/host-cu-routes.ts +33 -6
  297. package/src/runtime/routes/host-file-routes.ts +32 -6
  298. package/src/runtime/routes/host-transfer-routes.ts +79 -16
  299. package/src/runtime/routes/identity-routes.ts +7 -138
  300. package/src/runtime/routes/inbound-message-handler.ts +77 -12
  301. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -0
  302. package/src/runtime/routes/index.ts +6 -0
  303. package/src/runtime/routes/memory-item-routes.test.ts +41 -15
  304. package/src/runtime/routes/memory-v2-routes.ts +33 -0
  305. package/src/runtime/routes/oauth-connect-routes.ts +153 -0
  306. package/src/runtime/verification-outbound-actions.ts +4 -4
  307. package/src/schedule/run-script.ts +37 -5
  308. package/src/schedule/scheduler.ts +20 -1
  309. package/src/security/encrypted-store.ts +2 -0
  310. package/src/security/secure-keys.ts +55 -0
  311. package/src/skills/remote-skill-policy.ts +4 -10
  312. package/src/subagent/index.ts +1 -7
  313. package/src/subagent/manager.ts +1 -15
  314. package/src/tasks/task-runner.ts +0 -1
  315. package/src/tasks/task-store.ts +0 -3
  316. package/src/tools/background-tool-registry.ts +17 -3
  317. package/src/tools/host-filesystem/edit.test.ts +151 -0
  318. package/src/tools/host-filesystem/edit.ts +43 -1
  319. package/src/tools/host-filesystem/read.test.ts +129 -0
  320. package/src/tools/host-filesystem/read.ts +43 -1
  321. package/src/tools/host-filesystem/transfer.test.ts +127 -2
  322. package/src/tools/host-filesystem/transfer.ts +56 -11
  323. package/src/tools/host-filesystem/write.test.ts +134 -0
  324. package/src/tools/host-filesystem/write.ts +43 -1
  325. package/src/tools/host-terminal/host-shell.ts +13 -6
  326. package/src/tools/mcp/mcp-tool-factory.ts +2 -1
  327. package/src/tools/memory/register.test.ts +12 -9
  328. package/src/tools/memory/register.ts +1 -2
  329. package/src/tools/provider-tool-name.ts +28 -0
  330. package/src/tools/registry.ts +30 -9
  331. package/src/tools/terminal/shell.ts +9 -1
  332. package/src/tools/tool-approval-handler.ts +31 -6
  333. package/src/tools/types.ts +24 -2
  334. package/src/tts/provider-catalog.ts +3 -5
  335. package/src/util/disk-usage.ts +138 -0
  336. package/src/util/platform.ts +21 -11
  337. package/src/util/process-liveness.ts +26 -0
  338. package/src/workspace/heartbeat-service.ts +19 -0
  339. package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
  340. package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
  341. package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +72 -0
  342. package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
  343. package/src/workspace/migrations/registry.ts +8 -0
  344. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
  345. package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
  346. package/src/memory/v2/skill-qdrant.ts +0 -404
  347. package/src/signals/bash.ts +0 -198
@@ -4,6 +4,11 @@ import { join } from "node:path";
4
4
  import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
5
5
  import { getConfig } from "../config/loader.js";
6
6
  import type { LLMCallSite } from "../config/schemas/llm.js";
7
+ import {
8
+ checkDiskPressureBackgroundGate,
9
+ diskPressureBackgroundSkipLogFields,
10
+ shouldLogDiskPressureBackgroundSkip,
11
+ } from "../daemon/disk-pressure-background-gate.js";
7
12
  import { processMessage } from "../daemon/process-message.js";
8
13
  import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
9
14
  import { getLogger } from "../util/logger.js";
@@ -110,11 +115,8 @@ export class FilingService {
110
115
 
111
116
  start(): void {
112
117
  const fullConfig = getConfig();
113
- if (
114
- isAssistantFeatureFlagEnabled("memory-v2-enabled", fullConfig) &&
115
- fullConfig.memory.v2.enabled
116
- ) {
117
- log.info("Filing service disabled — memory v2 is active");
118
+ if (isAssistantFeatureFlagEnabled("memory-v2-enabled", fullConfig)) {
119
+ log.info("Filing service disabled — memory v2 flag is set");
118
120
  this._nextRunAt = null;
119
121
  this._nextCompactionAt = null;
120
122
  return;
@@ -195,6 +197,10 @@ export class FilingService {
195
197
  const config = getConfig().filing;
196
198
  if (!force && !config.enabled) return false;
197
199
 
200
+ if (!force && this.shouldSkipForDiskPressure("filing")) {
201
+ return false;
202
+ }
203
+
198
204
  if (
199
205
  !force &&
200
206
  !this.isWithinActiveHoursNow(
@@ -245,6 +251,10 @@ export class FilingService {
245
251
  const config = getConfig().filing;
246
252
  if (!force && !config.compactionEnabled) return false;
247
253
 
254
+ if (!force && this.shouldSkipForDiskPressure("compaction")) {
255
+ return false;
256
+ }
257
+
248
258
  if (
249
259
  !force &&
250
260
  !this.isWithinActiveHoursNow(
@@ -299,6 +309,21 @@ export class FilingService {
299
309
  this._nextCompactionAt = Date.now() + intervalMs;
300
310
  }
301
311
 
312
+ private shouldSkipForDiskPressure(source: "filing" | "compaction"): boolean {
313
+ const diskPressureGate = checkDiskPressureBackgroundGate("background-work");
314
+ if (diskPressureGate.action === "allow") return false;
315
+ if (shouldLogDiskPressureBackgroundSkip(`filing-service:${source}`)) {
316
+ log.warn(
317
+ {
318
+ source,
319
+ ...diskPressureBackgroundSkipLogFields(diskPressureGate),
320
+ },
321
+ "Filing service skipped during disk pressure cleanup mode",
322
+ );
323
+ }
324
+ return true;
325
+ }
326
+
302
327
  private hasBufferContent(): boolean {
303
328
  const bufferPath = join(getWorkspaceDir(), "pkb", "buffer.md");
304
329
  if (!existsSync(bufferPath)) return false;
@@ -1,4 +1,4 @@
1
- import { mkdtempSync, readFileSync, rmSync } from "node:fs";
1
+ import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
@@ -15,6 +15,7 @@ mock.module("../heartbeat-run-store.js", () => ({
15
15
  markStaleRunsAsMissed: () => 0,
16
16
  markStaleRunningAsError: () => 0,
17
17
  listHeartbeatRuns: () => [],
18
+ countCompletedHeartbeatRuns: () => 10,
18
19
  }));
19
20
 
20
21
  // Stub the in-process SSE hub so the writer's publish path is a
@@ -146,6 +147,7 @@ interface OnDiskItem {
146
147
  }
147
148
 
148
149
  function readFeedItems(): OnDiskItem[] {
150
+ if (!existsSync(getHomeFeedPath())) return [];
149
151
  const raw = JSON.parse(readFileSync(getHomeFeedPath(), "utf-8"));
150
152
  return raw.items as OnDiskItem[];
151
153
  }
@@ -174,7 +176,7 @@ afterEach(() => {
174
176
  });
175
177
 
176
178
  describe("heartbeat feed events", () => {
177
- test("successful heartbeat emits feed event with priority 30 and no urgency", async () => {
179
+ test("successful heartbeat without an alert does not emit a feed event", async () => {
178
180
  _testProcessMessage = async () => ({ messageId: "msg-1" });
179
181
  const service = new HeartbeatService({
180
182
  alerter: () => {},
@@ -182,18 +184,12 @@ describe("heartbeat feed events", () => {
182
184
 
183
185
  await service.runOnce({ force: true });
184
186
 
185
- // Give the fire-and-forget emitFeedEvent time to flush.
187
+ // Give any fire-and-forget surfacing work time to flush.
186
188
  await new Promise((r) => setTimeout(r, 100));
187
189
 
188
190
  const items = readFeedItems();
189
191
  const heartbeatItem = items.find((i) => i.title === "Heartbeat");
190
- expect(heartbeatItem).toBeDefined();
191
- expect(heartbeatItem!.summary).toBe(
192
- "Periodic check completed. Tap to see details.",
193
- );
194
- expect(heartbeatItem!.priority).toBe(30);
195
- expect(heartbeatItem!.urgency).toBeUndefined();
196
- expect(heartbeatItem!.source).toBe("assistant");
192
+ expect(heartbeatItem).toBeUndefined();
197
193
  });
198
194
 
199
195
  test("failed heartbeat emits feed event with priority 55 and urgency medium", async () => {
@@ -218,13 +214,13 @@ describe("heartbeat feed events", () => {
218
214
  expect(heartbeatItem!.source).toBe("assistant");
219
215
  });
220
216
 
221
- test("dedupKey uses date for daily dedup", async () => {
217
+ test("repeated successful heartbeats without alerts stay silent", async () => {
222
218
  _testProcessMessage = async () => ({ messageId: "msg-1" });
223
219
  const service = new HeartbeatService({
224
220
  alerter: () => {},
225
221
  });
226
222
 
227
- // Run twice — same day should dedup to one item.
223
+ // Run twice — neither should create a generic success item.
228
224
  await service.runOnce({ force: true });
229
225
  await new Promise((r) => setTimeout(r, 100));
230
226
  await service.runOnce({ force: true });
@@ -232,9 +228,6 @@ describe("heartbeat feed events", () => {
232
228
 
233
229
  const items = readFeedItems();
234
230
  const heartbeatItems = items.filter((i) => i.title === "Heartbeat");
235
- expect(heartbeatItems).toHaveLength(1);
236
-
237
- const today = new Date().toISOString().split("T")[0];
238
- expect(heartbeatItems[0]!.id).toBe(`emit:assistant:heartbeat:ok:${today}`);
231
+ expect(heartbeatItems).toHaveLength(0);
239
232
  });
240
233
  });
@@ -15,6 +15,7 @@ import { getDb } from "../../memory/db-connection.js";
15
15
  import { initializeDb } from "../../memory/db-init.js";
16
16
  import {
17
17
  completeHeartbeatRun,
18
+ countCompletedHeartbeatRuns,
18
19
  insertPendingHeartbeatRun,
19
20
  listHeartbeatRuns,
20
21
  markStaleRunningAsError,
@@ -213,4 +214,39 @@ describe("heartbeat-run-store", () => {
213
214
  const rows = listHeartbeatRuns(3);
214
215
  expect(rows).toHaveLength(3);
215
216
  });
217
+
218
+ test("countCompletedHeartbeatRuns counts only ok rows", () => {
219
+ const now = Date.now();
220
+
221
+ // Insert runs with various statuses
222
+ const id1 = insertPendingHeartbeatRun(now);
223
+ startHeartbeatRun(id1);
224
+ completeHeartbeatRun(id1, { status: "ok", conversationId: "conv-1" });
225
+
226
+ const id2 = insertPendingHeartbeatRun(now + 1);
227
+ startHeartbeatRun(id2);
228
+ completeHeartbeatRun(id2, { status: "error", error: "something broke" });
229
+
230
+ const id3 = insertPendingHeartbeatRun(now + 2);
231
+ skipHeartbeatRun(id3, "disabled");
232
+
233
+ const id4 = insertPendingHeartbeatRun(now + 3);
234
+ startHeartbeatRun(id4);
235
+ completeHeartbeatRun(id4, { status: "ok", conversationId: "conv-2" });
236
+
237
+ expect(countCompletedHeartbeatRuns()).toBe(2);
238
+ });
239
+
240
+ test("countCompletedHeartbeatRuns returns 0 when no ok rows exist", () => {
241
+ const now = Date.now();
242
+
243
+ const id1 = insertPendingHeartbeatRun(now);
244
+ startHeartbeatRun(id1);
245
+ completeHeartbeatRun(id1, { status: "error", error: "fail" });
246
+
247
+ const id2 = insertPendingHeartbeatRun(now + 1);
248
+ skipHeartbeatRun(id2, "outside_active_hours");
249
+
250
+ expect(countCompletedHeartbeatRuns()).toBe(0);
251
+ });
216
252
  });
@@ -202,6 +202,19 @@ export function markStaleRunningAsError(
202
202
  return rawChanges();
203
203
  }
204
204
 
205
+ /**
206
+ * Count the number of heartbeat runs that completed with status `ok`.
207
+ */
208
+ export function countCompletedHeartbeatRuns(): number {
209
+ const db = getDb();
210
+ const row = db
211
+ .select({ count: sql<number>`count(*)` })
212
+ .from(heartbeatRuns)
213
+ .where(eq(heartbeatRuns.status, "ok"))
214
+ .get();
215
+ return row?.count ?? 0;
216
+ }
217
+
205
218
  /**
206
219
  * List heartbeat runs ordered by `scheduledFor` descending.
207
220
  */
@@ -3,11 +3,16 @@ import { join } from "node:path";
3
3
 
4
4
  import { getConfig } from "../config/loader.js";
5
5
  import type { HeartbeatConfig } from "../config/schemas/heartbeat.js";
6
+ import {
7
+ checkDiskPressureBackgroundGate,
8
+ diskPressureBackgroundSkipLogFields,
9
+ shouldLogDiskPressureBackgroundSkip,
10
+ } from "../daemon/disk-pressure-background-gate.js";
6
11
  import type { HeartbeatAlert } from "../daemon/message-protocol.js";
7
12
  import { processMessage } from "../daemon/process-message.js";
8
13
  import { emitFeedEvent } from "../home/emit-feed-event.js";
9
14
  import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
10
- import { getConversation } from "../memory/conversation-crud.js";
15
+ import { getConversation, getMessages } from "../memory/conversation-crud.js";
11
16
  import { GENERATING_TITLE } from "../memory/conversation-title-service.js";
12
17
  import {
13
18
  GUARDIAN_PERSONA_TEMPLATE,
@@ -21,6 +26,7 @@ import { getWorkspaceDir, getWorkspacePromptPath } from "../util/platform.js";
21
26
  import { stripCommentLines } from "../util/strip-comment-lines.js";
22
27
  import {
23
28
  completeHeartbeatRun,
29
+ countCompletedHeartbeatRuns,
24
30
  insertPendingHeartbeatRun,
25
31
  markStaleRunningAsError,
26
32
  markStaleRunsAsMissed,
@@ -38,8 +44,12 @@ const DEFAULT_CHECKLIST = `- Check in with yourself. Read NOW.md. Is it still ac
38
44
  - If you have a thought worth sharing, send it. A follow-up, a useful find, a check-in. Not every beat, but when it feels right.
39
45
  - If something has happened since your last journal entry, write one. Even a few sentences. The journal is how future-you stays connected.`;
40
46
 
47
+ const EARLY_HEARTBEAT_THRESHOLD = 3;
41
48
  const REENGAGEMENT_COOLDOWN_MS = 18 * 60 * 60 * 1000; // 18 hours
42
49
  const HEARTBEAT_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
50
+ const HEARTBEAT_ALERT_MARKER = "HEARTBEAT_ALERT";
51
+ const HEARTBEAT_OK_MARKER = "HEARTBEAT_OK";
52
+ const HEARTBEAT_ALERT_SUMMARY_MAX_CHARS = 700;
43
53
 
44
54
  // Stripped-comment form of the guardian persona scaffold. Computed
45
55
  // once at module load because stripping comment lines is deterministic
@@ -92,6 +102,69 @@ function recordReengagementTimestamp(): void {
92
102
  }
93
103
  }
94
104
 
105
+ type HeartbeatDisposition = "alert" | "ok" | "unknown";
106
+
107
+ function parseHeartbeatDisposition(text: string | null): HeartbeatDisposition {
108
+ if (!text) return "unknown";
109
+ const lines = text
110
+ .trim()
111
+ .split(/\r?\n/)
112
+ .map((line) => line.trim())
113
+ .filter((line) => line.length > 0);
114
+ const lastLine = lines.at(-1);
115
+ if (lastLine === HEARTBEAT_ALERT_MARKER) return "alert";
116
+ if (lastLine === HEARTBEAT_OK_MARKER) return "ok";
117
+ return "unknown";
118
+ }
119
+
120
+ function stripHeartbeatDispositionMarkers(text: string): string {
121
+ return text
122
+ .replace(
123
+ new RegExp(
124
+ `(?:\\r?\\n)?\\s*(?:${HEARTBEAT_ALERT_MARKER}|${HEARTBEAT_OK_MARKER})\\s*$`,
125
+ ),
126
+ "",
127
+ )
128
+ .trim();
129
+ }
130
+
131
+ function truncateSummary(text: string, maxChars: number): string {
132
+ if (text.length <= maxChars) return text;
133
+ return `${text.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
134
+ }
135
+
136
+ function buildHeartbeatAlertSummary(text: string | null): string {
137
+ const summary = text ? stripHeartbeatDispositionMarkers(text) : "";
138
+ return truncateSummary(
139
+ summary || "Your assistant found something worth your attention.",
140
+ HEARTBEAT_ALERT_SUMMARY_MAX_CHARS,
141
+ );
142
+ }
143
+
144
+ function extractVisibleTextFromStoredMessageContent(raw: string): string {
145
+ try {
146
+ const parsed = JSON.parse(raw) as unknown;
147
+ if (typeof parsed === "string") return parsed;
148
+ if (!Array.isArray(parsed)) return "";
149
+ const texts: string[] = [];
150
+ for (const block of parsed) {
151
+ if (
152
+ block != null &&
153
+ typeof block === "object" &&
154
+ "type" in block &&
155
+ block.type === "text" &&
156
+ "text" in block &&
157
+ typeof block.text === "string"
158
+ ) {
159
+ texts.push(block.text);
160
+ }
161
+ }
162
+ return texts.join("\n").trim();
163
+ } catch {
164
+ return raw;
165
+ }
166
+ }
167
+
95
168
  export interface HeartbeatDeps {
96
169
  alerter: (alert: HeartbeatAlert) => void;
97
170
  onConversationCreated?: (info: {
@@ -168,18 +241,22 @@ export class HeartbeatService {
168
241
  "Recovered stale heartbeat runs on startup",
169
242
  );
170
243
 
171
- const total = this._startupMissedCount + this._startupCrashedCount;
172
- const today = new Date().toISOString().split("T")[0];
173
- void emitFeedEvent({
174
- source: "assistant",
175
- title: "Heartbeat Runs Missed",
176
- summary: `${total} heartbeat run${total > 1 ? "s were" : " was"} missed while the assistant was offline.`,
177
- dedupKey: `heartbeat:missed:${today}`,
178
- priority: 55,
179
- urgency: "high",
180
- }).catch((err) => {
181
- log.warn({ err }, "Failed to emit missed heartbeat feed event");
182
- });
244
+ if (!isDiskPressureBackgroundLocked("heartbeat-startup")) {
245
+ const total = this._startupMissedCount + this._startupCrashedCount;
246
+ const today = new Date().toISOString().split("T")[0];
247
+ void emitFeedEvent({
248
+ source: "assistant",
249
+ title: "Heartbeat Runs Missed",
250
+ summary: `${total} heartbeat run${
251
+ total > 1 ? "s were" : " was"
252
+ } missed while the assistant was offline.`,
253
+ dedupKey: `heartbeat:missed:${today}`,
254
+ priority: 55,
255
+ urgency: "high",
256
+ }).catch((err) => {
257
+ log.warn({ err }, "Failed to emit missed heartbeat feed event");
258
+ });
259
+ }
183
260
  }
184
261
  }
185
262
 
@@ -328,6 +405,10 @@ export class HeartbeatService {
328
405
  async runOnce({ force = false }: { force?: boolean } = {}): Promise<boolean> {
329
406
  const config = getConfig().heartbeat;
330
407
 
408
+ if (!force && isDiskPressureBackgroundLocked("heartbeat")) {
409
+ return false;
410
+ }
411
+
331
412
  let runId: string | null;
332
413
  let scheduledFor: number;
333
414
  if (force) {
@@ -579,6 +660,65 @@ export class HeartbeatService {
579
660
  }
580
661
  }
581
662
 
663
+ private getLatestAssistantMessage(
664
+ conversationId: string,
665
+ ): { id: string; text: string } | null {
666
+ try {
667
+ const messages = getMessages(conversationId);
668
+ for (let i = messages.length - 1; i >= 0; i--) {
669
+ const message = messages[i]!;
670
+ if (message.role !== "assistant") continue;
671
+ return {
672
+ id: message.id,
673
+ text: extractVisibleTextFromStoredMessageContent(message.content),
674
+ };
675
+ }
676
+ } catch (err) {
677
+ log.warn(
678
+ { err, conversationId },
679
+ "Failed to read heartbeat assistant message",
680
+ );
681
+ }
682
+ return null;
683
+ }
684
+
685
+ private async emitHeartbeatAlertNotification(params: {
686
+ runId: string;
687
+ conversationId: string;
688
+ messageId?: string;
689
+ conversationTitle: string;
690
+ summary: string;
691
+ }): Promise<void> {
692
+ const { emitNotificationSignal } =
693
+ await import("../notifications/emit-signal.js");
694
+
695
+ await emitNotificationSignal({
696
+ sourceEventName: "heartbeat.alert",
697
+ sourceChannel: "watcher",
698
+ sourceContextId: params.runId,
699
+ dedupeKey: `heartbeat:alert:${params.runId}`,
700
+ attentionHints: {
701
+ requiresAction: true,
702
+ urgency: "medium",
703
+ isAsyncBackground: true,
704
+ visibleInSourceNow: false,
705
+ },
706
+ contextPayload: {
707
+ title: "Heartbeat Alert",
708
+ summary: params.summary,
709
+ conversationTitle: params.conversationTitle,
710
+ conversationId: params.conversationId,
711
+ messageId: params.messageId,
712
+ },
713
+ routingIntent: "single_channel",
714
+ conversationAffinityHint: { vellum: params.conversationId },
715
+ conversationMetadata: {
716
+ source: "heartbeat",
717
+ groupId: "system:background",
718
+ },
719
+ });
720
+ }
721
+
582
722
  private async executeRun(runId: string, scheduledFor: number): Promise<void> {
583
723
  log.info("Running heartbeat");
584
724
 
@@ -595,9 +735,11 @@ export class HeartbeatService {
595
735
  let conversationId: string | undefined;
596
736
  try {
597
737
  const checklist = this.readChecklist();
738
+ const completedRunCount = countCompletedHeartbeatRuns();
598
739
  const { prompt, includedReengagement } = this.buildPrompt(
599
740
  checklist,
600
741
  unhealthyProviders,
742
+ completedRunCount,
601
743
  );
602
744
 
603
745
  const conversation = bootstrapConversation({
@@ -609,11 +751,6 @@ export class HeartbeatService {
609
751
  });
610
752
  conversationId = conversation.id;
611
753
 
612
- this.deps.onConversationCreated?.({
613
- conversationId: conversation.id,
614
- title: "Heartbeat",
615
- });
616
-
617
754
  await processMessage(conversation.id, prompt, undefined, {
618
755
  trustContext: {
619
756
  sourceChannel: "vellum",
@@ -644,20 +781,32 @@ export class HeartbeatService {
644
781
  // Best-effort; fall back to generic title.
645
782
  }
646
783
 
647
- const today = new Date().toISOString().split("T")[0];
648
- void emitFeedEvent({
649
- source: "assistant",
650
- title,
651
- summary: "Periodic check completed. Tap to see details.",
652
- dedupKey: `heartbeat:ok:${today}`,
653
- priority: 30,
654
- }).catch((err) => {
655
- log.warn(
656
- { err, conversationId: conversation.id },
657
- "Failed to emit heartbeat feed event",
658
- );
659
- });
784
+ const assistantMessage = this.getLatestAssistantMessage(
785
+ conversation.id,
786
+ );
787
+ const disposition = parseHeartbeatDisposition(
788
+ assistantMessage?.text ?? null,
789
+ );
790
+ if (disposition === "alert") {
791
+ this.deps.onConversationCreated?.({
792
+ conversationId: conversation.id,
793
+ title,
794
+ });
795
+ void this.emitHeartbeatAlertNotification({
796
+ runId,
797
+ conversationId: conversation.id,
798
+ messageId: assistantMessage?.id,
799
+ conversationTitle: title,
800
+ summary: buildHeartbeatAlertSummary(assistantMessage?.text ?? null),
801
+ }).catch((err) => {
802
+ log.warn(
803
+ { err, conversationId: conversation.id },
804
+ "Failed to emit heartbeat alert notification",
805
+ );
806
+ });
807
+ }
660
808
 
809
+ const today = new Date().toISOString().split("T")[0];
661
810
  if (latenessMs > LATE_THRESHOLD_MS) {
662
811
  const lateMinutes = Math.round(latenessMs / 60_000);
663
812
  void emitFeedEvent({
@@ -714,6 +863,7 @@ export class HeartbeatService {
714
863
  buildPrompt(
715
864
  checklist: string,
716
865
  unhealthyProviders: string[] = [],
866
+ completedRunCount: number = Infinity,
717
867
  ): { prompt: string; includedReengagement: boolean } {
718
868
  let prompt = `You are running a periodic heartbeat check. Review the following checklist and take any necessary actions.
719
869
 
@@ -730,11 +880,20 @@ Do NOT attempt to use tools for these providers — they will fail. Skip any che
730
880
  }
731
881
 
732
882
  prompt += `\n\n<heartbeat-disposition>
883
+ This heartbeat runs frequently. Do not manufacture a report just because it ran.
884
+ If there is nothing genuinely useful, actionable, or interesting to surface, keep the response brief and end with HEARTBEAT_OK.
885
+ If there is something worth interrupting the guardian for, write a concise guardian-facing note first: what happened, why it matters, and the recommended next step. Then end with HEARTBEAT_ALERT. That note may be used as notification copy.
733
886
  After completing your review, end your response with one of:
734
887
  - HEARTBEAT_OK — if everything looks good, no action needed
735
888
  - HEARTBEAT_ALERT — if you found issues that need attention (describe them before this marker)
736
889
  </heartbeat-disposition>`;
737
890
 
891
+ if (completedRunCount < EARLY_HEARTBEAT_THRESHOLD) {
892
+ prompt += `\n\n<early-heartbeat>
893
+ This is one of your first heartbeats. Your user hasn't heard from you yet and may not know you're here. Find something genuinely useful to share — a follow-up from a recent conversation, something you noticed, or a quick check-in. Lean toward HEARTBEAT_ALERT this time. First impressions matter.
894
+ </early-heartbeat>`;
895
+ }
896
+
738
897
  let includedReengagement = false;
739
898
  if (isShallowProfile() && isReengagementCooldownElapsed()) {
740
899
  includedReengagement = true;
@@ -745,6 +904,21 @@ After completing your review, end your response with one of:
745
904
  }
746
905
  }
747
906
 
907
+ function isDiskPressureBackgroundLocked(logKey: string): boolean {
908
+ const diskPressureGate = checkDiskPressureBackgroundGate("background-work");
909
+ if (diskPressureGate.action === "allow") return false;
910
+ if (shouldLogDiskPressureBackgroundSkip(logKey)) {
911
+ log.warn(
912
+ {
913
+ source: "heartbeat",
914
+ ...diskPressureBackgroundSkipLogFields(diskPressureGate),
915
+ },
916
+ "Heartbeat skipped during disk pressure cleanup mode",
917
+ );
918
+ }
919
+ return true;
920
+ }
921
+
748
922
  /**
749
923
  * Check if the given hour falls within the active window.
750
924
  * Handles overnight windows (e.g. start=22, end=6).
@@ -28,6 +28,11 @@
28
28
  * scheduler needing to touch the event hub.
29
29
  */
30
30
 
31
+ import {
32
+ checkDiskPressureBackgroundGate,
33
+ diskPressureBackgroundSkipLogFields,
34
+ shouldLogDiskPressureBackgroundSkip,
35
+ } from "../daemon/disk-pressure-background-gate.js";
31
36
  import { getLogger } from "../util/logger.js";
32
37
  import type { FeedItem } from "./feed-types.js";
33
38
  import {
@@ -119,6 +124,19 @@ export function startFeedScheduler(
119
124
  rollupRan: false,
120
125
  };
121
126
  if (stopped || tickRunning) return summary;
127
+ const diskPressureGate = checkDiskPressureBackgroundGate("background-work");
128
+ if (diskPressureGate.action === "skip") {
129
+ if (shouldLogDiskPressureBackgroundSkip("home-feed-scheduler")) {
130
+ log.warn(
131
+ {
132
+ source: "feed-scheduler",
133
+ ...diskPressureBackgroundSkipLogFields(diskPressureGate),
134
+ },
135
+ "Home feed scheduler skipped during disk pressure cleanup mode",
136
+ );
137
+ }
138
+ return summary;
139
+ }
122
140
  tickRunning = true;
123
141
  const nowMs = now.getTime();
124
142
  try {
@@ -16,11 +16,7 @@
16
16
  * callback_url that external services should use.
17
17
  */
18
18
 
19
- import {
20
- getPlatformAssistantId,
21
- getPlatformBaseUrl,
22
- getPlatformInternalApiKey,
23
- } from "../config/env.js";
19
+ import { getPlatformAssistantId, getPlatformBaseUrl } from "../config/env.js";
24
20
  import { getIsPlatform } from "../config/env-registry.js";
25
21
  import { credentialKey } from "../security/credential-key.js";
26
22
  import { getSecureKeyAsync } from "../security/secure-keys.js";
@@ -32,7 +28,6 @@ export interface PlatformCallbackRegistrationContext {
32
28
  isPlatform: boolean;
33
29
  platformBaseUrl: string;
34
30
  assistantId: string;
35
- hasInternalApiKey: boolean;
36
31
  hasAssistantApiKey: boolean;
37
32
  authHeader: string | null;
38
33
  enabled: boolean;
@@ -54,20 +49,18 @@ export async function resolvePlatformCallbackRegistrationContext(): Promise<Plat
54
49
  );
55
50
  const assistantId =
56
51
  getPlatformAssistantId().trim() || storedAssistantIdRaw?.trim() || "";
57
- const internalApiKey = getPlatformInternalApiKey().trim();
58
- const assistantApiKey = storedAssistantApiKeyRaw?.trim() || "";
59
- const authHeader = internalApiKey
60
- ? `Bearer ${internalApiKey}`
61
- : assistantApiKey
62
- ? `Api-Key ${assistantApiKey}`
63
- : null;
52
+ const envAssistantCredential = process.env.ASSISTANT_API_KEY?.trim();
53
+ const assistantCredential =
54
+ storedAssistantApiKeyRaw?.trim() || envAssistantCredential || undefined;
55
+ const authHeader = assistantCredential
56
+ ? `Api-Key ${assistantCredential}`
57
+ : null;
64
58
 
65
59
  return {
66
60
  isPlatform: platform,
67
61
  platformBaseUrl,
68
62
  assistantId,
69
- hasInternalApiKey: internalApiKey.length > 0,
70
- hasAssistantApiKey: assistantApiKey.length > 0,
63
+ hasAssistantApiKey: !!assistantCredential,
71
64
  authHeader,
72
65
  // Enabled when we have enough context to register callback routes.
73
66
  // Does NOT require IS_PLATFORM — self-hosted assistants with stored