@vellumai/assistant 0.7.1 → 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 (739) hide show
  1. package/ARCHITECTURE.md +48 -50
  2. package/Dockerfile +1 -0
  3. package/README.md +1 -2
  4. package/__tests__/permissions/gateway-threshold-reader.test.ts +9 -3
  5. package/bun.lock +26 -26
  6. package/docs/architecture/memory.md +5 -2
  7. package/docs/architecture/security.md +20 -0
  8. package/docs/plugins.md +7 -9
  9. package/knip.json +1 -0
  10. package/node_modules/@vellumai/gateway-client/src/index.ts +1 -0
  11. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +52 -5
  12. package/node_modules/@vellumai/gateway-client/src/types.ts +11 -0
  13. package/node_modules/@vellumai/service-contracts/package.json +2 -0
  14. package/node_modules/@vellumai/service-contracts/src/__tests__/contracts.test.ts +4 -0
  15. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +107 -0
  16. package/node_modules/@vellumai/service-contracts/src/index.ts +5 -1
  17. package/node_modules/@vellumai/service-contracts/src/ingress.ts +24 -0
  18. package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +84 -0
  19. package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
  20. package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
  21. package/node_modules/@vellumai/twilio-client/bun.lock +24 -0
  22. package/node_modules/@vellumai/twilio-client/package.json +18 -0
  23. package/node_modules/@vellumai/twilio-client/src/__tests__/twilio-client.test.ts +128 -0
  24. package/node_modules/@vellumai/twilio-client/src/index.ts +179 -0
  25. package/node_modules/@vellumai/twilio-client/tsconfig.json +20 -0
  26. package/openapi.yaml +1020 -40
  27. package/package.json +6 -3
  28. package/src/__tests__/app-builder-tool-scripts.test.ts +3 -3
  29. package/src/__tests__/app-bundler.test.ts +170 -1
  30. package/src/__tests__/app-control-flow.test.ts +384 -0
  31. package/src/__tests__/app-control-no-global-cgevent.test.ts +98 -0
  32. package/src/__tests__/app-control-tool-schemas.test.ts +621 -0
  33. package/src/__tests__/app-executors.test.ts +30 -43
  34. package/src/__tests__/approval-routes-http.test.ts +23 -6
  35. package/src/__tests__/assistant-event-hub-machine-name.test.ts +146 -0
  36. package/src/__tests__/assistant-event-hub-targeted.test.ts +257 -0
  37. package/src/__tests__/assistant-event-hub.test.ts +157 -2
  38. package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -7
  39. package/src/__tests__/auto-analysis-end-to-end.test.ts +62 -1
  40. package/src/__tests__/background-shell-host-bash.test.ts +14 -15
  41. package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
  42. package/src/__tests__/bootstrap-turn-cleanup.test.ts +44 -0
  43. package/src/__tests__/btw-routes.test.ts +13 -4
  44. package/src/__tests__/call-controller.test.ts +49 -1
  45. package/src/__tests__/call-conversation-messages.test.ts +8 -2
  46. package/src/__tests__/call-domain.test.ts +0 -2
  47. package/src/__tests__/call-routes-http.test.ts +0 -2
  48. package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
  49. package/src/__tests__/channel-readiness-service.test.ts +62 -2
  50. package/src/__tests__/checker.test.ts +3 -4
  51. package/src/__tests__/config-loader-backfill.test.ts +461 -147
  52. package/src/__tests__/config-loader-platform-defaults.test.ts +196 -0
  53. package/src/__tests__/config-schema-cmd.test.ts +0 -1
  54. package/src/__tests__/config-schema.test.ts +1 -0
  55. package/src/__tests__/config-set-platform-guard.test.ts +48 -4
  56. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +20 -11
  57. package/src/__tests__/config-watcher.test.ts +142 -71
  58. package/src/__tests__/context-search-agent-runner.test.ts +61 -3
  59. package/src/__tests__/context-search-conversations-source.test.ts +0 -24
  60. package/src/__tests__/context-search-fanout.test.ts +0 -1
  61. package/src/__tests__/context-search-memory-source.test.ts +3 -7
  62. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
  63. package/src/__tests__/context-search-pkb-source.test.ts +0 -1
  64. package/src/__tests__/context-search-workspace-source.test.ts +0 -1
  65. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
  66. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
  67. package/src/__tests__/conversation-agent-loop.test.ts +454 -5
  68. package/src/__tests__/conversation-app-control-instantiation.test.ts +392 -0
  69. package/src/__tests__/conversation-app-control-lifecycle.test.ts +237 -0
  70. package/src/__tests__/conversation-error.test.ts +150 -3
  71. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  72. package/src/__tests__/conversation-lifecycle.test.ts +36 -0
  73. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +283 -0
  74. package/src/__tests__/conversation-process-callsite.test.ts +43 -0
  75. package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
  76. package/src/__tests__/conversation-routes-disk-view.test.ts +6 -0
  77. package/src/__tests__/conversation-routes-guardian-reply.test.ts +120 -72
  78. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  79. package/src/__tests__/conversation-runtime-assembly.test.ts +65 -0
  80. package/src/__tests__/conversation-slash-commands.test.ts +0 -4
  81. package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
  82. package/src/__tests__/conversation-speed-override.test.ts +0 -3
  83. package/src/__tests__/conversation-store.test.ts +0 -18
  84. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +202 -0
  85. package/src/__tests__/conversation-surfaces-app-control.test.ts +328 -0
  86. package/src/__tests__/conversation-surfaces-data-persist.test.ts +404 -0
  87. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +2 -5
  88. package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
  89. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
  90. package/src/__tests__/credential-execution-feature-gates.test.ts +5 -12
  91. package/src/__tests__/credential-execution-managed-contract.test.ts +3 -131
  92. package/src/__tests__/credentials-cli.test.ts +12 -12
  93. package/src/__tests__/cu-unified-flow.test.ts +351 -23
  94. package/src/__tests__/daemon-credential-client.test.ts +101 -19
  95. package/src/__tests__/date-context.test.ts +164 -2
  96. package/src/__tests__/db-schedule-syntax-migration.test.ts +2 -0
  97. package/src/__tests__/disk-pressure-guard.test.ts +262 -0
  98. package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
  99. package/src/__tests__/disk-pressure-policy.test.ts +241 -0
  100. package/src/__tests__/disk-pressure-routes.test.ts +379 -0
  101. package/src/__tests__/disk-pressure-tools.test.ts +277 -0
  102. package/src/__tests__/disk-usage.test.ts +150 -0
  103. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  104. package/src/__tests__/events-client-registration.test.ts +52 -0
  105. package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
  106. package/src/__tests__/file-write-tool.test.ts +4 -10
  107. package/src/__tests__/filing-service.test.ts +3 -4
  108. package/src/__tests__/gateway-only-enforcement.test.ts +0 -1
  109. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -2
  110. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -2
  111. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -1
  112. package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
  113. package/src/__tests__/heartbeat-service.test.ts +968 -2
  114. package/src/__tests__/helpers/call-route-handler.ts +7 -1
  115. package/src/__tests__/host-app-control-proxy.test.ts +772 -0
  116. package/src/__tests__/host-app-control-routes.test.ts +263 -0
  117. package/src/__tests__/host-bash-proxy.test.ts +439 -47
  118. package/src/__tests__/host-bash-routes.test.ts +459 -0
  119. package/src/__tests__/host-browser-proxy.test.ts +24 -22
  120. package/src/__tests__/host-browser-routes.test.ts +39 -13
  121. package/src/__tests__/host-cu-proxy.test.ts +248 -52
  122. package/src/__tests__/host-cu-routes-targeted.test.ts +429 -0
  123. package/src/__tests__/host-file-edit-tool.test.ts +47 -1
  124. package/src/__tests__/host-file-proxy-targeted.test.ts +378 -0
  125. package/src/__tests__/host-file-proxy.test.ts +301 -45
  126. package/src/__tests__/host-file-read-tool.test.ts +17 -0
  127. package/src/__tests__/host-file-routes-targeted.test.ts +420 -0
  128. package/src/__tests__/host-file-write-tool.test.ts +42 -1
  129. package/src/__tests__/host-proxy-base.test.ts +312 -0
  130. package/src/__tests__/host-shell-tool.test.ts +22 -4
  131. package/src/__tests__/host-transfer-proxy-targeted.test.ts +932 -0
  132. package/src/__tests__/host-transfer-proxy.test.ts +121 -22
  133. package/src/__tests__/host-transfer-routes-targeted.test.ts +662 -0
  134. package/src/__tests__/http-user-message-parity.test.ts +108 -1
  135. package/src/__tests__/identity-intro-cache.test.ts +29 -0
  136. package/src/__tests__/identity-routes.test.ts +103 -1
  137. package/src/__tests__/init-feature-flag-overrides.test.ts +26 -3
  138. package/src/__tests__/injector-chain.test.ts +18 -6
  139. package/src/__tests__/injector-disk-pressure.test.ts +224 -0
  140. package/src/__tests__/inline-command-runner.test.ts +0 -1
  141. package/src/__tests__/inline-skill-load-permissions.test.ts +5 -11
  142. package/src/__tests__/integration-status.test.ts +85 -5
  143. package/src/__tests__/intent-routing.test.ts +0 -1
  144. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +95 -5
  145. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +17 -0
  146. package/src/__tests__/managed-profile-guard.test.ts +18 -0
  147. package/src/__tests__/managed-skill-lifecycle.test.ts +0 -1
  148. package/src/__tests__/mcp-abort-signal.test.ts +130 -0
  149. package/src/__tests__/mcp-auth-routes.test.ts +197 -0
  150. package/src/__tests__/mcp-cli.test.ts +338 -2
  151. package/src/__tests__/memory-admin-recall.test.ts +3 -11
  152. package/src/__tests__/memory-jobs-worker-lanes.test.ts +188 -0
  153. package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
  154. package/src/__tests__/migration-import-commit-http.test.ts +108 -2
  155. package/src/__tests__/mock-gateway-ipc.ts +1 -0
  156. package/src/__tests__/normalize-onboarding.test.ts +180 -0
  157. package/src/__tests__/oauth-cli.test.ts +0 -2
  158. package/src/__tests__/oauth-connect-routes.test.ts +316 -0
  159. package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
  160. package/src/__tests__/oauth2-gateway-transport.test.ts +0 -1
  161. package/src/__tests__/onboarding-persona-write.test.ts +308 -0
  162. package/src/__tests__/openai-provider.test.ts +45 -8
  163. package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
  164. package/src/__tests__/persistence-secret-redaction.test.ts +299 -0
  165. package/src/__tests__/platform-bash-auto-approve.test.ts +5 -9
  166. package/src/__tests__/platform-callback-registration.test.ts +21 -4
  167. package/src/__tests__/platform.test.ts +2 -1
  168. package/src/__tests__/playbook-execution.test.ts +0 -43
  169. package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
  170. package/src/__tests__/prechat-onboarding-contract.test.ts +214 -25
  171. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  172. package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
  173. package/src/__tests__/provider-tool-name.test.ts +23 -0
  174. package/src/__tests__/public-ingress-urls.test.ts +97 -0
  175. package/src/__tests__/relay-server.test.ts +15 -4
  176. package/src/__tests__/require-fresh-approval.test.ts +0 -1
  177. package/src/__tests__/retry-backoff.test.ts +87 -0
  178. package/src/__tests__/runtime-events-sse.test.ts +2 -2
  179. package/src/__tests__/sanitize-config-for-transfer.test.ts +24 -2
  180. package/src/__tests__/schedule-retry.test.ts +715 -0
  181. package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
  182. package/src/__tests__/script-proxy-mitm-handler.test.ts +1 -1
  183. package/src/__tests__/secret-ingress-http.test.ts +1 -1
  184. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  185. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -1
  186. package/src/__tests__/skill-feature-flags.test.ts +43 -41
  187. package/src/__tests__/skill-load-feature-flag.test.ts +13 -14
  188. package/src/__tests__/skill-load-inline-command.test.ts +0 -51
  189. package/src/__tests__/skill-load-inline-includes.test.ts +0 -43
  190. package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
  191. package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -1
  192. package/src/__tests__/slack-channel-config.test.ts +9 -14
  193. package/src/__tests__/suggestion-routes.test.ts +46 -0
  194. package/src/__tests__/system-prompt-ask-mode.test.ts +0 -1
  195. package/src/__tests__/system-prompt.test.ts +0 -1
  196. package/src/__tests__/telegram-config.test.ts +0 -1
  197. package/src/__tests__/test-preload.ts +8 -0
  198. package/src/__tests__/tool-approval-handler.test.ts +3 -4
  199. package/src/__tests__/tool-audit-listener.test.ts +48 -0
  200. package/src/__tests__/tool-execute-pipeline.test.ts +0 -1
  201. package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
  202. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  203. package/src/__tests__/tool-executor.test.ts +0 -1
  204. package/src/__tests__/twilio-config.test.ts +3 -16
  205. package/src/__tests__/twilio-routes.test.ts +3 -5
  206. package/src/__tests__/twilio-validation.test.ts +93 -0
  207. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -4
  208. package/src/__tests__/verification-control-plane-policy.test.ts +2 -4
  209. package/src/__tests__/voice-ingress-preflight.test.ts +19 -0
  210. package/src/__tests__/workspace-migration-006-services-config.test.ts +3 -2
  211. package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
  212. package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
  213. package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
  214. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +1 -5
  215. package/src/__tests__/workspace-migration-down-functions.test.ts +8 -8
  216. package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +90 -0
  217. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +10 -6
  218. package/src/approvals/guardian-decision-primitive.ts +13 -0
  219. package/src/approvals/guardian-request-resolvers.ts +16 -17
  220. package/src/backup/__tests__/paths.test.ts +0 -22
  221. package/src/backup/__tests__/restore.test.ts +51 -151
  222. package/src/backup/paths.ts +2 -18
  223. package/src/backup/restore.ts +107 -231
  224. package/src/backup/snapshot-lock.ts +2 -27
  225. package/src/bundler/app-bundler.ts +51 -3
  226. package/src/bundler/compiler-tools.ts +3 -2
  227. package/src/calls/call-conversation-messages.ts +46 -10
  228. package/src/calls/relay-server.ts +4 -44
  229. package/src/calls/twilio-config.ts +2 -17
  230. package/src/calls/twilio-rest.ts +33 -105
  231. package/src/calls/twilio-routes.ts +11 -12
  232. package/src/channels/types.ts +8 -7
  233. package/src/cli/commands/__tests__/backup.test.ts +6 -277
  234. package/src/cli/commands/__tests__/gateway.test.ts +288 -0
  235. package/src/cli/commands/__tests__/memory-v2.test.ts +4 -0
  236. package/src/cli/commands/__tests__/webhooks.test.ts +0 -5
  237. package/src/cli/commands/backup.ts +6 -331
  238. package/src/cli/commands/bash.ts +35 -108
  239. package/src/cli/commands/clients.ts +36 -37
  240. package/src/cli/commands/contacts.ts +137 -25
  241. package/src/cli/commands/conversations.ts +2 -5
  242. package/src/cli/commands/credentials.ts +71 -7
  243. package/src/cli/commands/domain.ts +66 -15
  244. package/src/cli/commands/gateway.ts +183 -0
  245. package/src/cli/commands/keys.ts +9 -6
  246. package/src/cli/commands/mcp.ts +116 -156
  247. package/src/cli/commands/memory-v2.ts +303 -7
  248. package/src/cli/commands/oauth/__tests__/connect.test.ts +437 -1
  249. package/src/cli/commands/oauth/connect.ts +127 -1
  250. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -4
  251. package/src/cli/commands/platform/__tests__/connect.test.ts +7 -3
  252. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -3
  253. package/src/cli/commands/platform/__tests__/status.test.ts +116 -21
  254. package/src/cli/commands/platform/disconnect.ts +5 -4
  255. package/src/cli/commands/platform/index.ts +16 -25
  256. package/src/cli/commands/status.ts +57 -0
  257. package/src/cli/lib/daemon-credential-client.ts +110 -28
  258. package/src/cli/program.ts +6 -2
  259. package/src/config/assistant-feature-flags.ts +79 -12
  260. package/src/config/bundled-skills/acp/SKILL.md +6 -0
  261. package/src/config/bundled-skills/acp/TOOLS.json +1 -22
  262. package/src/config/bundled-skills/app-builder/SKILL.md +14 -109
  263. package/src/config/bundled-skills/app-builder/TOOLS.json +1 -28
  264. package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -10
  265. package/src/config/bundled-skills/app-control/SKILL.md +75 -0
  266. package/src/config/bundled-skills/app-control/TOOLS.json +299 -0
  267. package/src/config/bundled-skills/app-control/tools/app-control-click.ts +12 -0
  268. package/src/config/bundled-skills/app-control/tools/app-control-combo.ts +12 -0
  269. package/src/config/bundled-skills/app-control/tools/app-control-drag.ts +12 -0
  270. package/src/config/bundled-skills/app-control/tools/app-control-observe.ts +12 -0
  271. package/src/config/bundled-skills/app-control/tools/app-control-press.ts +12 -0
  272. package/src/config/bundled-skills/app-control/tools/app-control-sequence.ts +12 -0
  273. package/src/config/bundled-skills/app-control/tools/app-control-start.ts +12 -0
  274. package/src/config/bundled-skills/app-control/tools/app-control-stop.ts +12 -0
  275. package/src/config/bundled-skills/app-control/tools/app-control-type.ts +12 -0
  276. package/src/config/bundled-skills/computer-use/SKILL.md +6 -0
  277. package/src/config/bundled-skills/computer-use/TOOLS.json +67 -43
  278. package/src/config/bundled-skills/contacts/TOOLS.json +0 -16
  279. package/src/config/bundled-skills/document/TOOLS.json +0 -8
  280. package/src/config/bundled-skills/followups/TOOLS.json +0 -12
  281. package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
  282. package/src/config/bundled-skills/image-studio/TOOLS.json +0 -4
  283. package/src/config/bundled-skills/media-processing/TOOLS.json +0 -24
  284. package/src/config/bundled-skills/messaging/TOOLS.json +0 -40
  285. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
  286. package/src/config/bundled-skills/phone-calls/TOOLS.json +0 -12
  287. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +25 -4
  288. package/src/config/bundled-skills/playbooks/TOOLS.json +0 -16
  289. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
  290. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
  291. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
  292. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
  293. package/src/config/bundled-skills/schedule/TOOLS.json +14 -14
  294. package/src/config/bundled-skills/sequences/TOOLS.json +0 -36
  295. package/src/config/bundled-skills/settings/SKILL.md +4 -0
  296. package/src/config/bundled-skills/settings/TOOLS.json +0 -12
  297. package/src/config/bundled-skills/skill-management/SKILL.md +6 -0
  298. package/src/config/bundled-skills/skill-management/TOOLS.json +0 -8
  299. package/src/config/bundled-skills/subagent/SKILL.md +6 -2
  300. package/src/config/bundled-skills/subagent/TOOLS.json +0 -20
  301. package/src/config/bundled-skills/transcribe/SKILL.md +4 -0
  302. package/src/config/bundled-skills/transcribe/TOOLS.json +0 -4
  303. package/src/config/bundled-tool-registry.ts +21 -0
  304. package/src/config/env-registry.ts +0 -2
  305. package/src/config/env.ts +19 -20
  306. package/src/config/feature-flag-registry.json +47 -135
  307. package/src/config/loader.ts +197 -104
  308. package/src/config/sanitize-for-transfer.ts +2 -0
  309. package/src/config/schemas/__tests__/memory-lifecycle.test.ts +80 -0
  310. package/src/config/schemas/__tests__/memory-v2.test.ts +17 -9
  311. package/src/config/schemas/call-site-catalog.ts +14 -0
  312. package/src/config/schemas/calls.ts +0 -9
  313. package/src/config/schemas/channels.ts +0 -5
  314. package/src/config/schemas/heartbeat.ts +64 -1
  315. package/src/config/schemas/ingress.ts +10 -6
  316. package/src/config/schemas/llm.ts +7 -10
  317. package/src/config/schemas/memory-lifecycle.ts +90 -24
  318. package/src/config/schemas/memory-v2.ts +121 -13
  319. package/src/config/schemas/platform.ts +49 -3
  320. package/src/config/schemas/services.ts +29 -15
  321. package/src/config/schemas/skills.ts +0 -6
  322. package/src/config/seed-inference-profiles.ts +230 -33
  323. package/src/contacts/contact-store.ts +0 -55
  324. package/src/contacts/contacts-write.ts +0 -27
  325. package/src/context/window-manager.ts +1 -2
  326. package/src/credential-execution/feature-gates.ts +10 -10
  327. package/src/credential-execution/process-manager.ts +12 -41
  328. package/src/daemon/__tests__/conversation-tool-setup.test.ts +187 -5
  329. package/src/daemon/assistant-attachments.ts +4 -4
  330. package/src/daemon/bootstrap-turn-cleanup.ts +45 -0
  331. package/src/daemon/config-watcher.ts +89 -60
  332. package/src/daemon/conversation-agent-loop-handlers.ts +27 -3
  333. package/src/daemon/conversation-agent-loop.ts +202 -61
  334. package/src/daemon/conversation-error.ts +87 -15
  335. package/src/daemon/conversation-lifecycle.ts +9 -4
  336. package/src/daemon/conversation-process.ts +24 -11
  337. package/src/daemon/conversation-runtime-assembly.ts +28 -2
  338. package/src/daemon/conversation-store.ts +2 -2
  339. package/src/daemon/conversation-surfaces.ts +305 -4
  340. package/src/daemon/conversation-tool-setup.ts +66 -62
  341. package/src/daemon/conversation.ts +38 -24
  342. package/src/daemon/date-context.ts +71 -22
  343. package/src/daemon/disk-pressure-background-gate.ts +73 -0
  344. package/src/daemon/disk-pressure-guard.ts +343 -0
  345. package/src/daemon/disk-pressure-policy.ts +163 -0
  346. package/src/daemon/doordash-steps.ts +1 -1
  347. package/src/daemon/handlers/shared.ts +4 -2
  348. package/src/daemon/handlers/skills.ts +3 -4
  349. package/src/daemon/host-app-control-proxy.ts +389 -0
  350. package/src/daemon/host-bash-proxy.ts +117 -82
  351. package/src/daemon/host-browser-proxy.ts +67 -82
  352. package/src/daemon/host-cu-proxy.ts +127 -86
  353. package/src/daemon/host-file-proxy.ts +129 -69
  354. package/src/daemon/host-proxy-base.ts +294 -0
  355. package/src/daemon/host-proxy-preactivation.ts +82 -0
  356. package/src/daemon/host-transfer-proxy.ts +338 -129
  357. package/src/daemon/lifecycle.ts +194 -145
  358. package/src/daemon/meet-host-supervisor.ts +4 -4
  359. package/src/daemon/meet-manifest-loader.ts +0 -1
  360. package/src/daemon/memory-v2-startup.ts +14 -4
  361. package/src/daemon/message-protocol.ts +6 -8
  362. package/src/daemon/message-types/contacts.ts +23 -1
  363. package/src/daemon/message-types/conversations.ts +15 -8
  364. package/src/daemon/message-types/disk-pressure.ts +9 -0
  365. package/src/daemon/message-types/host-app-control.ts +150 -0
  366. package/src/daemon/message-types/host-bash.ts +4 -0
  367. package/src/daemon/message-types/host-cu.ts +2 -0
  368. package/src/daemon/message-types/host-file.ts +4 -0
  369. package/src/daemon/message-types/host-transfer.ts +3 -0
  370. package/src/daemon/message-types/messages.ts +3 -0
  371. package/src/daemon/message-types/schedules.ts +8 -3
  372. package/src/daemon/message-types/skills.ts +2 -2
  373. package/src/daemon/process-message.ts +18 -1
  374. package/src/daemon/profiler-run-store.ts +5 -5
  375. package/src/daemon/shutdown-handlers.ts +0 -3
  376. package/src/daemon/tool-setup-types.ts +51 -0
  377. package/src/daemon/tool-side-effects.ts +1 -1
  378. package/src/documents/document-store.ts +85 -0
  379. package/src/events/tool-audit-listener.ts +2 -1
  380. package/src/filing/filing-service.ts +30 -5
  381. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +24 -23
  382. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +252 -0
  383. package/src/heartbeat/heartbeat-run-store.ts +249 -0
  384. package/src/heartbeat/heartbeat-service.ts +459 -54
  385. package/src/home/__tests__/post-connect-feed.test.ts +99 -0
  386. package/src/home/__tests__/relationship-state-writer.test.ts +11 -9
  387. package/src/home/__tests__/suggested-prompts.test.ts +89 -0
  388. package/src/home/feed-scheduler.ts +18 -0
  389. package/src/home/post-connect-feed.ts +68 -0
  390. package/src/home/relationship-state-writer.ts +17 -92
  391. package/src/home/suggested-prompts.ts +46 -10
  392. package/src/inbound/platform-callback-registration.ts +8 -15
  393. package/src/inbound/public-ingress-urls.ts +32 -34
  394. package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
  395. package/src/ipc/__tests__/route-error-envelope.test.ts +80 -0
  396. package/src/ipc/assistant-server.ts +70 -3
  397. package/src/ipc/cli-client.ts +32 -1
  398. package/src/ipc/gateway-client.ts +37 -3
  399. package/src/live-voice/live-voice-archive.ts +4 -4
  400. package/src/live-voice/live-voice-metrics.ts +10 -10
  401. package/src/live-voice/protocol.ts +5 -7
  402. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +304 -0
  403. package/src/mcp/mcp-auth-orchestrator.ts +213 -0
  404. package/src/mcp/mcp-auth-state.ts +133 -0
  405. package/src/mcp/mcp-oauth-provider.ts +19 -0
  406. package/src/media/image-service.ts +1 -7
  407. package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
  408. package/src/memory/__tests__/jobs-store-job-classes.test.ts +24 -0
  409. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +52 -22
  410. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
  411. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
  412. package/src/memory/__tests__/qdrant-client-sentinel.test.ts +49 -0
  413. package/src/memory/__tests__/sparse-tokenize.test.ts +66 -0
  414. package/src/memory/admin.ts +5 -9
  415. package/src/memory/anisotropy.test.ts +247 -0
  416. package/src/memory/anisotropy.ts +443 -0
  417. package/src/memory/auto-analysis-constants.ts +17 -0
  418. package/src/memory/auto-analysis-guard.ts +5 -15
  419. package/src/memory/canonical-guardian-store.ts +7 -7
  420. package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +122 -0
  421. package/src/memory/context-search/agent-protocol.ts +6 -6
  422. package/src/memory/context-search/agent-runner.ts +51 -9
  423. package/src/memory/context-search/sources/conversations.ts +2 -11
  424. package/src/memory/context-search/sources/memory-v2.ts +22 -9
  425. package/src/memory/context-search/sources/memory.ts +0 -1
  426. package/src/memory/context-search/types.ts +0 -1
  427. package/src/memory/conversation-crud.ts +5 -13
  428. package/src/memory/conversation-key-store.ts +2 -15
  429. package/src/memory/db-init.ts +6 -0
  430. package/src/memory/embedding-backend.ts +9 -21
  431. package/src/memory/embedding-runtime-manager.ts +119 -5
  432. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +81 -25
  433. package/src/memory/graph/conversation-graph-memory.ts +43 -78
  434. package/src/memory/graph/extraction.ts +1 -3
  435. package/src/memory/graph/graph-search.test.ts +10 -67
  436. package/src/memory/graph/graph-search.ts +9 -20
  437. package/src/memory/graph/retriever.test.ts +6 -0
  438. package/src/memory/graph/retriever.ts +34 -10
  439. package/src/memory/graph/tools.ts +1 -1
  440. package/src/memory/indexer.ts +54 -45
  441. package/src/memory/job-handlers/backfill.ts +2 -11
  442. package/src/memory/job-handlers/cleanup.ts +43 -0
  443. package/src/memory/job-handlers/embedding.ts +6 -8
  444. package/src/memory/job-handlers/summarization.ts +2 -7
  445. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +8 -2
  446. package/src/memory/jobs/embed-concept-page.ts +28 -2
  447. package/src/memory/jobs/embed-pkb-file.test.ts +2 -2
  448. package/src/memory/jobs-store.ts +114 -22
  449. package/src/memory/jobs-worker.ts +193 -106
  450. package/src/memory/memory-v2-activation-log-store.ts +33 -15
  451. package/src/memory/memory-v2-concept-frequency.ts +169 -0
  452. package/src/memory/migrations/237-heartbeat-runs.ts +45 -0
  453. package/src/memory/migrations/238-schedule-retry-policy.ts +20 -0
  454. package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
  455. package/src/memory/migrations/index.ts +6 -0
  456. package/src/memory/migrations/registry.ts +8 -0
  457. package/src/memory/pkb/pkb-search.test.ts +6 -0
  458. package/src/memory/pkb/pkb-search.ts +7 -0
  459. package/src/memory/qdrant-client.ts +49 -32
  460. package/src/memory/rerank-local.ts +374 -0
  461. package/src/memory/schema/infrastructure.ts +15 -0
  462. package/src/memory/search/semantic.ts +13 -67
  463. package/src/memory/sparse-tokenize.ts +49 -0
  464. package/src/memory/trace-event-store.ts +1 -17
  465. package/src/memory/v2/__tests__/activation.test.ts +387 -344
  466. package/src/memory/v2/__tests__/consolidation-job.test.ts +40 -8
  467. package/src/memory/v2/__tests__/injection.test.ts +181 -169
  468. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
  469. package/src/memory/v2/__tests__/qdrant.test.ts +16 -0
  470. package/src/memory/v2/__tests__/reranker.test.ts +338 -0
  471. package/src/memory/v2/__tests__/sim.test.ts +154 -188
  472. package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
  473. package/src/memory/v2/__tests__/sparse-bm25.test.ts +292 -0
  474. package/src/memory/v2/__tests__/static-context.test.ts +76 -2
  475. package/src/memory/v2/activation.ts +213 -239
  476. package/src/memory/v2/consolidation-job.ts +65 -17
  477. package/src/memory/v2/constants.ts +7 -0
  478. package/src/memory/v2/injection.ts +123 -103
  479. package/src/memory/v2/prompts/consolidation.ts +348 -92
  480. package/src/memory/v2/qdrant.ts +198 -1
  481. package/src/memory/v2/reranker.ts +177 -0
  482. package/src/memory/v2/sim.ts +113 -77
  483. package/src/memory/v2/skill-content.ts +4 -3
  484. package/src/memory/v2/skill-store.ts +91 -53
  485. package/src/memory/v2/sparse-bm25.ts +245 -0
  486. package/src/memory/v2/static-context.ts +28 -5
  487. package/src/memory/v2/types.ts +10 -10
  488. package/src/messaging/providers/gmail/types.ts +0 -49
  489. package/src/messaging/providers/slack/adapter.ts +1 -31
  490. package/src/messaging/providers/slack/types.ts +0 -32
  491. package/src/notifications/README.md +10 -10
  492. package/src/notifications/broadcaster.ts +1 -1
  493. package/src/notifications/copy-composer.ts +13 -0
  494. package/src/notifications/guardian-question-mode.ts +5 -5
  495. package/src/notifications/signal.ts +4 -0
  496. package/src/oauth/AGENTS.md +3 -1
  497. package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
  498. package/src/oauth/connect-orchestrator.ts +6 -0
  499. package/src/oauth/connection-resolver.test.ts +66 -1
  500. package/src/oauth/connection-resolver.ts +55 -1
  501. package/src/oauth/credential-token-resolver.ts +1 -3
  502. package/src/oauth/manual-token-connection.ts +0 -4
  503. package/src/oauth/oauth-connect-state.ts +77 -0
  504. package/src/oauth/seed-providers.ts +58 -1
  505. package/src/outbound-proxy/index.ts +1 -37
  506. package/src/outbound-proxy/logging.ts +1 -1
  507. package/src/outbound-proxy/policy.ts +6 -5
  508. package/src/outbound-proxy/router.ts +2 -1
  509. package/src/permissions/approval-policy.test.ts +6 -275
  510. package/src/permissions/approval-policy.ts +0 -51
  511. package/src/permissions/checker.test.ts +0 -1
  512. package/src/permissions/checker.ts +3 -17
  513. package/src/permissions/gateway-threshold-reader.ts +2 -0
  514. package/src/permissions/prompter.ts +34 -1
  515. package/src/permissions/secret-prompter.ts +6 -2
  516. package/src/plugins/defaults/injectors.ts +35 -2
  517. package/src/plugins/defaults/memory-retrieval.ts +5 -6
  518. package/src/plugins/types.ts +7 -0
  519. package/src/proactive-artifact/aux-message-injector.ts +74 -0
  520. package/src/proactive-artifact/decision.test.ts +226 -0
  521. package/src/proactive-artifact/decision.ts +165 -0
  522. package/src/proactive-artifact/index.ts +7 -0
  523. package/src/proactive-artifact/job.test.ts +867 -0
  524. package/src/proactive-artifact/job.ts +352 -0
  525. package/src/proactive-artifact/message-copy.ts +41 -0
  526. package/src/proactive-artifact/trigger-state.test.ts +277 -0
  527. package/src/proactive-artifact/trigger-state.ts +119 -0
  528. package/src/prompts/bootstrap-cleanup.ts +27 -0
  529. package/src/prompts/normalize-onboarding.ts +80 -0
  530. package/src/prompts/persona-resolver.ts +101 -9
  531. package/src/prompts/system-prompt.ts +23 -24
  532. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  533. package/src/prompts/templates/SOUL.md +13 -1
  534. package/src/providers/__tests__/retry-callsite.test.ts +222 -1
  535. package/src/providers/model-intents.ts +7 -0
  536. package/src/providers/openrouter/client.ts +8 -0
  537. package/src/providers/retry.ts +50 -0
  538. package/src/providers/speech-to-text/provider-catalog.ts +7 -8
  539. package/src/providers/types.ts +1 -0
  540. package/src/runtime/__tests__/agent-wake.test.ts +456 -3
  541. package/src/runtime/agent-wake.ts +238 -100
  542. package/src/runtime/assistant-event-hub.ts +151 -99
  543. package/src/runtime/auth/__tests__/middleware.test.ts +11 -56
  544. package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
  545. package/src/runtime/auth/middleware.ts +0 -96
  546. package/src/runtime/auth/route-policy.ts +32 -0
  547. package/src/runtime/auth/same-actor.ts +216 -0
  548. package/src/runtime/btw-sidechain.ts +2 -3
  549. package/src/runtime/channel-invite-transport.ts +2 -48
  550. package/src/runtime/channel-invite-transports/email.ts +1 -1
  551. package/src/runtime/channel-invite-transports/slack.ts +1 -1
  552. package/src/runtime/channel-invite-transports/telegram.ts +1 -1
  553. package/src/runtime/channel-invite-transports/voice.ts +1 -1
  554. package/src/runtime/channel-invite-transports/whatsapp.ts +1 -1
  555. package/src/runtime/channel-invite-types.ts +54 -0
  556. package/src/runtime/channel-readiness-service.ts +32 -13
  557. package/src/runtime/channel-retry-sweep.ts +65 -1
  558. package/src/runtime/guardian-reply-router.ts +10 -0
  559. package/src/runtime/http-server.ts +3 -329
  560. package/src/runtime/http-types.ts +0 -5
  561. package/src/runtime/local-actor-identity.ts +52 -11
  562. package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +413 -0
  563. package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +260 -0
  564. package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +189 -0
  565. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +153 -1
  566. package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +451 -0
  567. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
  568. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +515 -0
  569. package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +437 -0
  570. package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +319 -0
  571. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +51 -1
  572. package/src/runtime/migrations/migration-transport.ts +7 -7
  573. package/src/runtime/migrations/vbundle-builder.ts +327 -60
  574. package/src/runtime/migrations/vbundle-import-analyzer.ts +4 -4
  575. package/src/runtime/migrations/vbundle-import-policy.ts +172 -0
  576. package/src/runtime/migrations/vbundle-importer.ts +245 -68
  577. package/src/runtime/migrations/vbundle-streaming-importer.ts +326 -35
  578. package/src/runtime/migrations/vbundle-streaming-validator.ts +157 -4
  579. package/src/runtime/migrations/vbundle-tar-stream.ts +15 -6
  580. package/src/runtime/migrations/vbundle-validator.ts +114 -0
  581. package/src/runtime/pending-interactions.ts +43 -9
  582. package/src/runtime/routes/__tests__/backup-routes.test.ts +22 -150
  583. package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
  584. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -5
  585. package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +242 -0
  586. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +112 -0
  587. package/src/runtime/routes/approval-interception-types.ts +13 -0
  588. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +1 -1
  589. package/src/runtime/routes/backup-routes.ts +15 -38
  590. package/src/runtime/routes/btw-routes.ts +14 -37
  591. package/src/runtime/routes/client-routes.ts +21 -2
  592. package/src/runtime/routes/contact-prompt-routes.ts +183 -0
  593. package/src/runtime/routes/contact-routes.ts +0 -25
  594. package/src/runtime/routes/conversation-query-routes.ts +36 -1
  595. package/src/runtime/routes/conversation-routes.ts +65 -39
  596. package/src/runtime/routes/debug-bash-routes.ts +163 -0
  597. package/src/runtime/routes/disk-pressure-routes.ts +121 -0
  598. package/src/runtime/routes/document-pdf-renderer.ts +169 -0
  599. package/src/runtime/routes/documents-routes.ts +32 -75
  600. package/src/runtime/routes/errors.ts +19 -4
  601. package/src/runtime/routes/events-routes.ts +38 -0
  602. package/src/runtime/routes/gateway-log-routes.ts +79 -0
  603. package/src/runtime/routes/guardian-approval-interception.ts +2 -8
  604. package/src/runtime/routes/heartbeat-routes.ts +103 -38
  605. package/src/runtime/routes/host-app-control-routes.ts +134 -0
  606. package/src/runtime/routes/host-bash-routes.ts +56 -6
  607. package/src/runtime/routes/host-browser-routes.ts +108 -13
  608. package/src/runtime/routes/host-cu-routes.ts +66 -9
  609. package/src/runtime/routes/host-file-routes.ts +54 -5
  610. package/src/runtime/routes/host-transfer-routes.ts +122 -19
  611. package/src/runtime/routes/http-adapter.ts +1 -0
  612. package/src/runtime/routes/identity-intro-cache.ts +30 -0
  613. package/src/runtime/routes/identity-routes.ts +21 -180
  614. package/src/runtime/routes/inbound-message-handler.ts +78 -21
  615. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -7
  616. package/src/runtime/routes/inbound-stages/edit-intercept.ts +0 -8
  617. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -0
  618. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +0 -20
  619. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +5 -13
  620. package/src/runtime/routes/index.ts +14 -0
  621. package/src/runtime/routes/mcp-auth-routes.ts +132 -0
  622. package/src/runtime/routes/memory-item-routes.test.ts +41 -15
  623. package/src/runtime/routes/memory-item-routes.ts +10 -12
  624. package/src/runtime/routes/memory-v2-routes.ts +474 -1
  625. package/src/runtime/routes/migration-routes.ts +96 -0
  626. package/src/runtime/routes/oauth-connect-routes.ts +153 -0
  627. package/src/runtime/routes/schedule-routes.ts +7 -0
  628. package/src/runtime/verification-outbound-actions.ts +4 -4
  629. package/src/runtime/verification-templates.ts +4 -7
  630. package/src/schedule/integration-status.ts +66 -2
  631. package/src/schedule/recurrence-engine.ts +4 -1
  632. package/src/schedule/retry-backoff.ts +18 -0
  633. package/src/schedule/retry-policy.ts +82 -0
  634. package/src/schedule/run-script.ts +37 -5
  635. package/src/schedule/schedule-recovery.ts +64 -0
  636. package/src/schedule/schedule-store.ts +106 -2
  637. package/src/schedule/scheduler-types.ts +25 -0
  638. package/src/schedule/scheduler.ts +83 -39
  639. package/src/security/encrypted-store.ts +2 -0
  640. package/src/security/oauth-callback-registry.ts +8 -0
  641. package/src/security/secure-keys.ts +55 -0
  642. package/src/sequence/analytics.ts +5 -5
  643. package/src/sequence/engine.ts +1 -1
  644. package/src/skills/catalog-files.ts +2 -8
  645. package/src/skills/include-graph.ts +5 -5
  646. package/src/skills/remote-skill-policy.ts +10 -16
  647. package/src/skills/skill-file-provider.ts +1 -1
  648. package/src/skills/skill-file-types.ts +13 -0
  649. package/src/skills/skillssh-audit-types.ts +28 -0
  650. package/src/skills/skillssh-registry.ts +8 -21
  651. package/src/subagent/index.ts +1 -7
  652. package/src/subagent/manager.ts +1 -15
  653. package/src/tasks/task-runner.ts +0 -1
  654. package/src/tasks/task-store.ts +0 -3
  655. package/src/telemetry/types.ts +2 -0
  656. package/src/telemetry/usage-telemetry-reporter.test.ts +21 -0
  657. package/src/telemetry/usage-telemetry-reporter.ts +1 -0
  658. package/src/tools/app-control/skill-proxy-bridge.ts +28 -0
  659. package/src/tools/apps/executors.ts +56 -69
  660. package/src/tools/background-tool-registry.ts +17 -3
  661. package/src/tools/browser/__tests__/browser-status.test.ts +21 -18
  662. package/src/tools/browser/browser-execution.ts +2 -2
  663. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +55 -4
  664. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +12 -6
  665. package/src/tools/browser/cdp-client/factory.ts +23 -24
  666. package/src/tools/browser/cdp-client/index.ts +1 -14
  667. package/src/tools/computer-use/definitions.ts +42 -20
  668. package/src/tools/executor.ts +2 -0
  669. package/src/tools/host-filesystem/edit.test.ts +151 -0
  670. package/src/tools/host-filesystem/edit.ts +68 -0
  671. package/src/tools/host-filesystem/read.test.ts +129 -0
  672. package/src/tools/host-filesystem/read.ts +68 -0
  673. package/src/tools/host-filesystem/transfer.test.ts +127 -2
  674. package/src/tools/host-filesystem/transfer.ts +78 -3
  675. package/src/tools/host-filesystem/write.test.ts +134 -0
  676. package/src/tools/host-filesystem/write.ts +68 -0
  677. package/src/tools/host-terminal/host-shell.ts +66 -1
  678. package/src/tools/mcp/mcp-tool-factory.ts +2 -1
  679. package/src/tools/memory/register.test.ts +12 -9
  680. package/src/tools/memory/register.ts +1 -2
  681. package/src/tools/provider-tool-name.ts +28 -0
  682. package/src/tools/registry.ts +30 -9
  683. package/src/tools/schedule/create.ts +6 -0
  684. package/src/tools/schedule/list.ts +2 -0
  685. package/src/tools/schedule/update.ts +10 -0
  686. package/src/tools/shared/filesystem/file-ops-service.ts +2 -0
  687. package/src/tools/shared/filesystem/path-policy.ts +25 -1
  688. package/src/tools/skills/load.ts +0 -32
  689. package/src/tools/terminal/shell.ts +9 -1
  690. package/src/tools/tool-approval-handler.ts +32 -11
  691. package/src/tools/types.ts +28 -2
  692. package/src/tts/provider-catalog.ts +3 -5
  693. package/src/usage/pricing.ts +1 -1
  694. package/src/util/disk-usage.ts +138 -0
  695. package/src/util/platform.ts +21 -11
  696. package/src/util/process-liveness.ts +26 -0
  697. package/src/workspace/hatched-date.ts +86 -0
  698. package/src/workspace/heartbeat-service.ts +19 -0
  699. package/src/workspace/migrations/003-seed-device-id.ts +1 -1
  700. package/src/workspace/migrations/006-services-config.ts +8 -5
  701. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +3 -9
  702. package/src/workspace/migrations/021-move-signals-to-workspace.ts +4 -10
  703. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +4 -10
  704. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +4 -11
  705. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +3 -10
  706. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +3 -2
  707. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +2 -1
  708. package/src/workspace/migrations/059-move-pid-to-workspace.ts +3 -8
  709. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +3 -8
  710. package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
  711. package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
  712. package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +72 -0
  713. package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
  714. package/src/workspace/migrations/AGENTS.md +1 -1
  715. package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -10
  716. package/src/workspace/migrations/registry.ts +8 -0
  717. package/src/workspace/migrations/utils.ts +21 -0
  718. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
  719. package/src/__tests__/host-browser-e2e-cloud.test.ts +0 -443
  720. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +0 -226
  721. package/src/__tests__/host-browser-ws-events-e2e.test.ts +0 -427
  722. package/src/__tests__/twilio-rest.test.ts +0 -34
  723. package/src/backup/__tests__/backup-key.test.ts +0 -152
  724. package/src/backup/__tests__/backup-worker.test.ts +0 -782
  725. package/src/backup/__tests__/offsite-writer.test.ts +0 -641
  726. package/src/backup/__tests__/stream-crypt.test.ts +0 -228
  727. package/src/backup/backup-key.ts +0 -137
  728. package/src/backup/backup-worker.ts +0 -472
  729. package/src/backup/offsite-writer.ts +0 -222
  730. package/src/backup/stream-crypt.ts +0 -263
  731. package/src/daemon/message-types/pairing.ts +0 -58
  732. package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
  733. package/src/memory/v2/skill-qdrant.ts +0 -395
  734. package/src/outbound-proxy/config.ts +0 -20
  735. package/src/outbound-proxy/health.ts +0 -18
  736. package/src/outbound-proxy/types.ts +0 -150
  737. package/src/runtime/capability-tokens.ts +0 -190
  738. package/src/signals/bash.ts +0 -198
  739. package/src/signals/mcp-reload.ts +0 -18
@@ -2,21 +2,38 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
 
4
4
  import { getConfig } from "../config/loader.js";
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";
5
11
  import type { HeartbeatAlert } from "../daemon/message-protocol.js";
6
12
  import { processMessage } from "../daemon/process-message.js";
7
13
  import { emitFeedEvent } from "../home/emit-feed-event.js";
8
14
  import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
9
- import { getConversation } from "../memory/conversation-crud.js";
15
+ import { getConversation, getMessages } from "../memory/conversation-crud.js";
10
16
  import { GENERATING_TITLE } from "../memory/conversation-title-service.js";
11
17
  import {
12
18
  GUARDIAN_PERSONA_TEMPLATE,
13
19
  resolveGuardianPersona,
14
20
  } from "../prompts/persona-resolver.js";
15
21
  import { isTemplateContent } from "../prompts/system-prompt.js";
22
+ import { computeNextRunAt } from "../schedule/recurrence-engine.js";
16
23
  import { readTextFileSync } from "../util/fs.js";
17
24
  import { getLogger } from "../util/logger.js";
18
25
  import { getWorkspaceDir, getWorkspacePromptPath } from "../util/platform.js";
19
26
  import { stripCommentLines } from "../util/strip-comment-lines.js";
27
+ import {
28
+ completeHeartbeatRun,
29
+ countCompletedHeartbeatRuns,
30
+ insertPendingHeartbeatRun,
31
+ markStaleRunningAsError,
32
+ markStaleRunsAsMissed,
33
+ skipHeartbeatRun,
34
+ startHeartbeatRun,
35
+ supersedePendingRun,
36
+ } from "./heartbeat-run-store.js";
20
37
 
21
38
  const log = getLogger("heartbeat-check");
22
39
 
@@ -27,8 +44,12 @@ const DEFAULT_CHECKLIST = `- Check in with yourself. Read NOW.md. Is it still ac
27
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.
28
45
  - If something has happened since your last journal entry, write one. Even a few sentences. The journal is how future-you stays connected.`;
29
46
 
47
+ const EARLY_HEARTBEAT_THRESHOLD = 3;
30
48
  const REENGAGEMENT_COOLDOWN_MS = 18 * 60 * 60 * 1000; // 18 hours
31
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;
32
53
 
33
54
  // Stripped-comment form of the guardian persona scaffold. Computed
34
55
  // once at module load because stripping comment lines is deterministic
@@ -81,6 +102,69 @@ function recordReengagementTimestamp(): void {
81
102
  }
82
103
  }
83
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
+
84
168
  export interface HeartbeatDeps {
85
169
  alerter: (alert: HeartbeatAlert) => void;
86
170
  onConversationCreated?: (info: {
@@ -100,10 +184,20 @@ export class HeartbeatService {
100
184
  }
101
185
 
102
186
  private readonly deps: HeartbeatDeps;
103
- private timer: ReturnType<typeof setInterval> | null = null;
187
+ private timer:
188
+ | ReturnType<typeof setInterval>
189
+ | ReturnType<typeof setTimeout>
190
+ | null = null;
104
191
  private activeRun: Promise<void> | null = null;
105
192
  private _lastRunAt: number | null = null;
106
193
  private _nextRunAt: number | null = null;
194
+ private cronMode = false;
195
+ private stopped = false;
196
+ private configEpoch = 0;
197
+ private _pendingRunId: string | null = null;
198
+ private _startupMissedCount = 0;
199
+ private _startupCrashedCount = 0;
200
+ private _hasRunStartupRecovery = false;
107
201
 
108
202
  constructor(deps: HeartbeatDeps) {
109
203
  this.deps = deps;
@@ -121,6 +215,7 @@ export class HeartbeatService {
121
215
  }
122
216
 
123
217
  start(): void {
218
+ this.stopped = false;
124
219
  const config = getConfig().heartbeat;
125
220
  if (!config.enabled) {
126
221
  log.info("Heartbeat disabled by config");
@@ -129,7 +224,61 @@ export class HeartbeatService {
129
224
  }
130
225
  if (this.timer) return;
131
226
 
132
- log.info({ intervalMs: config.intervalMs }, "Heartbeat service started");
227
+ if (!this._hasRunStartupRecovery) {
228
+ this._hasRunStartupRecovery = true;
229
+ try {
230
+ this._startupMissedCount = markStaleRunsAsMissed();
231
+ this._startupCrashedCount = markStaleRunningAsError();
232
+ } catch (err) {
233
+ log.error({ err }, "Failed to recover stale heartbeat runs on startup");
234
+ }
235
+ if (this._startupMissedCount > 0 || this._startupCrashedCount > 0) {
236
+ log.info(
237
+ {
238
+ missedCount: this._startupMissedCount,
239
+ crashedCount: this._startupCrashedCount,
240
+ },
241
+ "Recovered stale heartbeat runs on startup",
242
+ );
243
+
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
+ }
260
+ }
261
+ }
262
+
263
+ if (config.cronExpression != null) {
264
+ this.cronMode = true;
265
+ this.scheduleNextCronRun(config);
266
+ } else {
267
+ this.startIntervalMode(config);
268
+ }
269
+ }
270
+
271
+ private startIntervalMode(config: HeartbeatConfig): void {
272
+ this.cronMode = false;
273
+ if (this.timer) {
274
+ clearTimeout(this.timer as ReturnType<typeof setTimeout>);
275
+ clearInterval(this.timer as ReturnType<typeof setInterval>);
276
+ this.timer = null;
277
+ }
278
+ log.info(
279
+ { intervalMs: config.intervalMs },
280
+ "Heartbeat service started (interval mode)",
281
+ );
133
282
  this.scheduleNextRun(config.intervalMs);
134
283
  this.timer = setInterval(() => {
135
284
  this.runOnce().catch((err) => {
@@ -138,13 +287,69 @@ export class HeartbeatService {
138
287
  }, config.intervalMs);
139
288
  }
140
289
 
290
+ private scheduleNextCronRun(config: HeartbeatConfig): void {
291
+ if (this.stopped) return;
292
+ try {
293
+ const nextRunAt = computeNextRunAt({
294
+ syntax: "cron",
295
+ expression: config.cronExpression!,
296
+ timezone: config.timezone,
297
+ });
298
+ this._nextRunAt = nextRunAt;
299
+ if (this.timer) {
300
+ clearTimeout(this.timer as ReturnType<typeof setTimeout>);
301
+ clearInterval(this.timer as ReturnType<typeof setInterval>);
302
+ this.timer = null;
303
+ }
304
+ const MAX_TIMEOUT_MS = 24 * 60 * 60 * 1000; // 24 hours
305
+ const delayMs = Math.max(0, nextRunAt - Date.now());
306
+ const epoch = this.configEpoch;
307
+ if (delayMs > MAX_TIMEOUT_MS) {
308
+ // Re-evaluate after 24h — the actual cron time is still far away
309
+ this.timer = setTimeout(() => {
310
+ if (this.configEpoch === epoch) {
311
+ this.scheduleNextCronRun(getConfig().heartbeat);
312
+ }
313
+ }, MAX_TIMEOUT_MS);
314
+ } else {
315
+ this.timer = setTimeout(() => {
316
+ this.runOnce()
317
+ .catch((err) => log.error({ err }, "Cron heartbeat failed"))
318
+ .finally(() => {
319
+ if (this.configEpoch === epoch) {
320
+ this.scheduleNextCronRun(getConfig().heartbeat);
321
+ }
322
+ });
323
+ }, delayMs);
324
+ }
325
+ (this.timer as ReturnType<typeof setTimeout>).unref();
326
+ log.info(
327
+ { nextRunAt: new Date(nextRunAt).toISOString(), delayMs },
328
+ "Heartbeat cron run scheduled",
329
+ );
330
+ } catch (err) {
331
+ log.warn(
332
+ { err },
333
+ "Failed to compute next cron run, falling back to interval mode",
334
+ );
335
+ this.startIntervalMode(config);
336
+ }
337
+ }
338
+
141
339
  /** Restart the timer with the latest config (e.g. after settings change). */
142
340
  reconfigure(): void {
341
+ this.configEpoch++;
342
+ if (this._pendingRunId) {
343
+ supersedePendingRun(this._pendingRunId);
344
+ this._pendingRunId = null;
345
+ }
143
346
  if (this.timer) {
144
- clearInterval(this.timer);
347
+ clearTimeout(this.timer as ReturnType<typeof setTimeout>);
348
+ clearInterval(this.timer as ReturnType<typeof setInterval>);
145
349
  this.timer = null;
146
350
  }
147
351
  this._nextRunAt = null;
352
+ this.cronMode = false;
148
353
  this.start();
149
354
  }
150
355
 
@@ -155,8 +360,15 @@ export class HeartbeatService {
155
360
  */
156
361
  resetTimer(): void {
157
362
  if (!this.timer) return;
363
+ if (this.cronMode) {
364
+ clearTimeout(this.timer as ReturnType<typeof setTimeout>);
365
+ clearInterval(this.timer as ReturnType<typeof setInterval>);
366
+ this.timer = null;
367
+ this.scheduleNextCronRun(getConfig().heartbeat);
368
+ return;
369
+ }
158
370
  const config = getConfig().heartbeat;
159
- clearInterval(this.timer);
371
+ clearInterval(this.timer as ReturnType<typeof setInterval>);
160
372
  this.scheduleNextRun(config.intervalMs);
161
373
  this.timer = setInterval(() => {
162
374
  this.runOnce().catch((err) => {
@@ -166,10 +378,16 @@ export class HeartbeatService {
166
378
  }
167
379
 
168
380
  async stop(): Promise<void> {
381
+ this.stopped = true;
169
382
  if (this.timer) {
170
- clearInterval(this.timer);
383
+ clearTimeout(this.timer as ReturnType<typeof setTimeout>);
384
+ clearInterval(this.timer as ReturnType<typeof setInterval>);
171
385
  this.timer = null;
172
386
  }
387
+ if (this._pendingRunId) {
388
+ supersedePendingRun(this._pendingRunId);
389
+ this._pendingRunId = null;
390
+ }
173
391
  this._nextRunAt = null;
174
392
  if (this.activeRun) {
175
393
  let timerId: ReturnType<typeof setTimeout>;
@@ -186,7 +404,26 @@ export class HeartbeatService {
186
404
  * When `force` is true (e.g. manual "Run Now"), skip enabled & active-hours guards. */
187
405
  async runOnce({ force = false }: { force?: boolean } = {}): Promise<boolean> {
188
406
  const config = getConfig().heartbeat;
189
- if (!force && !config.enabled) return false;
407
+
408
+ if (!force && isDiskPressureBackgroundLocked("heartbeat")) {
409
+ return false;
410
+ }
411
+
412
+ let runId: string | null;
413
+ let scheduledFor: number;
414
+ if (force) {
415
+ scheduledFor = Date.now();
416
+ runId = insertPendingHeartbeatRun(scheduledFor);
417
+ } else {
418
+ runId = this._pendingRunId;
419
+ scheduledFor = this._nextRunAt ?? Date.now();
420
+ this._pendingRunId = null;
421
+ }
422
+
423
+ if (!force && !config.enabled) {
424
+ if (runId) skipHeartbeatRun(runId, "disabled");
425
+ return false;
426
+ }
190
427
 
191
428
  // Active hours guard — only applied when both bounds are set.
192
429
  // The schema rejects configs where only one bound is provided.
@@ -195,7 +432,17 @@ export class HeartbeatService {
195
432
  config.activeHoursStart != null &&
196
433
  config.activeHoursEnd != null
197
434
  ) {
198
- const hour = this.deps.getCurrentHour?.() ?? new Date().getHours();
435
+ let hour: number;
436
+ if (this.cronMode && config.timezone) {
437
+ const parts = new Intl.DateTimeFormat("en-US", {
438
+ timeZone: config.timezone,
439
+ hourCycle: "h23",
440
+ hour: "numeric",
441
+ }).formatToParts(new Date());
442
+ hour = Number(parts.find((p) => p.type === "hour")!.value);
443
+ } else {
444
+ hour = this.deps.getCurrentHour?.() ?? new Date().getHours();
445
+ }
199
446
  if (
200
447
  !isWithinActiveHours(
201
448
  hour,
@@ -211,7 +458,10 @@ export class HeartbeatService {
211
458
  },
212
459
  "Outside active hours, skipping",
213
460
  );
214
- this.scheduleNextRun(config.intervalMs);
461
+ if (runId) skipHeartbeatRun(runId, "outside_active_hours");
462
+ if (!this.cronMode) {
463
+ this.scheduleNextRun(config.intervalMs);
464
+ }
215
465
  return false;
216
466
  }
217
467
  }
@@ -219,10 +469,14 @@ export class HeartbeatService {
219
469
  // Overlap prevention
220
470
  if (this.activeRun) {
221
471
  log.debug("Previous heartbeat run still active, skipping");
472
+ if (runId) skipHeartbeatRun(runId, "overlap");
222
473
  return false;
223
474
  }
224
475
 
225
- const run = this.executeRun();
476
+ if (!runId) {
477
+ runId = insertPendingHeartbeatRun(scheduledFor);
478
+ }
479
+ const run = this.executeRun(runId, scheduledFor);
226
480
  this.activeRun = run;
227
481
  // Clear activeRun once executeRun finishes. On timeout, runOnce releases
228
482
  // activeRun separately (see catch block below) so future runs aren't
@@ -252,16 +506,39 @@ export class HeartbeatService {
252
506
  // Release activeRun so the overlap guard doesn't permanently block
253
507
  // future heartbeat runs when executeRun hangs past the timeout.
254
508
  this.activeRun = null;
509
+ const transitioned = runId
510
+ ? completeHeartbeatRun(runId, {
511
+ status: "timeout",
512
+ error: "Heartbeat execution exceeded the 30-minute timeout",
513
+ })
514
+ : false;
515
+ if (transitioned) {
516
+ const today = new Date().toISOString().split("T")[0];
517
+ void emitFeedEvent({
518
+ source: "assistant",
519
+ title: "Heartbeat Timed Out",
520
+ summary: "Heartbeat execution exceeded the 30-minute timeout.",
521
+ dedupKey: `heartbeat:timeout:${today}`,
522
+ priority: 55,
523
+ urgency: "high",
524
+ }).catch(() => {});
525
+ }
255
526
  } finally {
256
527
  clearTimeout(timerId);
257
528
  this._lastRunAt = Date.now();
258
- this.scheduleNextRun(getConfig().heartbeat.intervalMs);
529
+ if (!this.cronMode) {
530
+ this.scheduleNextRun(getConfig().heartbeat.intervalMs);
531
+ }
259
532
  }
260
533
  return true;
261
534
  }
262
535
 
263
536
  private scheduleNextRun(intervalMs: number): void {
537
+ if (this._pendingRunId) {
538
+ supersedePendingRun(this._pendingRunId);
539
+ }
264
540
  this._nextRunAt = Date.now() + intervalMs;
541
+ this._pendingRunId = insertPendingHeartbeatRun(this._nextRunAt);
265
542
  }
266
543
 
267
544
  /**
@@ -383,19 +660,86 @@ export class HeartbeatService {
383
660
  }
384
661
  }
385
662
 
386
- private async executeRun(): Promise<void> {
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
+
722
+ private async executeRun(runId: string, scheduledFor: number): Promise<void> {
387
723
  log.info("Running heartbeat");
388
724
 
725
+ startHeartbeatRun(runId);
726
+
727
+ const latenessMs = Date.now() - scheduledFor;
728
+ const LATE_THRESHOLD_MS = 5 * 60 * 1000;
729
+
389
730
  // Credential health check — surface broken credentials proactively
390
731
  // before the LLM heartbeat prompt runs. Returns unhealthy provider
391
732
  // names so the prompt can instruct the LLM to skip those providers.
392
733
  const unhealthyProviders = await this.runCredentialHealthCheck();
393
734
 
735
+ let conversationId: string | undefined;
394
736
  try {
395
737
  const checklist = this.readChecklist();
738
+ const completedRunCount = countCompletedHeartbeatRuns();
396
739
  const { prompt, includedReengagement } = this.buildPrompt(
397
740
  checklist,
398
741
  unhealthyProviders,
742
+ completedRunCount,
399
743
  );
400
744
 
401
745
  const conversation = bootstrapConversation({
@@ -405,11 +749,7 @@ export class HeartbeatService {
405
749
  origin: "heartbeat",
406
750
  systemHint: "Heartbeat",
407
751
  });
408
-
409
- this.deps.onConversationCreated?.({
410
- conversationId: conversation.id,
411
- title: "Heartbeat",
412
- });
752
+ conversationId = conversation.id;
413
753
 
414
754
  await processMessage(conversation.id, prompt, undefined, {
415
755
  trustContext: {
@@ -425,50 +765,90 @@ export class HeartbeatService {
425
765
 
426
766
  log.info({ conversationId: conversation.id }, "Heartbeat completed");
427
767
 
428
- let title = "Heartbeat";
429
- try {
430
- const row = getConversation(conversation.id);
431
- if (row?.title && row.title !== GENERATING_TITLE) {
432
- title = row.title;
768
+ const transitioned = completeHeartbeatRun(runId, {
769
+ status: "ok",
770
+ conversationId,
771
+ });
772
+
773
+ if (transitioned) {
774
+ let title = "Heartbeat";
775
+ try {
776
+ const row = getConversation(conversation.id);
777
+ if (row?.title && row.title !== GENERATING_TITLE) {
778
+ title = row.title;
779
+ }
780
+ } catch {
781
+ // Best-effort; fall back to generic title.
433
782
  }
434
- } catch {
435
- // Best-effort; fall back to generic title.
436
- }
437
783
 
438
- const today = new Date().toISOString().split("T")[0];
439
- void emitFeedEvent({
440
- source: "assistant",
441
- title,
442
- summary: "Periodic check completed. Tap to see details.",
443
- dedupKey: `heartbeat:ok:${today}`,
444
- priority: 30,
445
- }).catch((err) => {
446
- log.warn(
447
- { err, conversationId: conversation.id },
448
- "Failed to emit heartbeat feed event",
784
+ const assistantMessage = this.getLatestAssistantMessage(
785
+ conversation.id,
449
786
  );
450
- });
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
+ }
808
+
809
+ const today = new Date().toISOString().split("T")[0];
810
+ if (latenessMs > LATE_THRESHOLD_MS) {
811
+ const lateMinutes = Math.round(latenessMs / 60_000);
812
+ void emitFeedEvent({
813
+ source: "assistant",
814
+ title: "Heartbeat Ran Late",
815
+ summary: `Heartbeat ran ${lateMinutes} minutes late (scheduled for ${new Date(scheduledFor).toLocaleTimeString()}).`,
816
+ dedupKey: `heartbeat:late:${today}`,
817
+ priority: 45,
818
+ urgency: "medium",
819
+ }).catch(() => {});
820
+ }
821
+ }
451
822
  } catch (err) {
452
823
  log.error({ err }, "Heartbeat failed");
453
- try {
454
- this.deps.alerter({
455
- type: "heartbeat_alert",
824
+
825
+ const transitioned = completeHeartbeatRun(runId, {
826
+ status: "error",
827
+ conversationId,
828
+ error: err instanceof Error ? err.message : String(err),
829
+ });
830
+
831
+ if (transitioned) {
832
+ try {
833
+ this.deps.alerter({
834
+ type: "heartbeat_alert",
835
+ title: "Heartbeat Failed",
836
+ body: err instanceof Error ? err.message : String(err),
837
+ });
838
+ } catch (alertErr) {
839
+ log.error({ alertErr }, "Failed to broadcast heartbeat alert");
840
+ }
841
+
842
+ const today = new Date().toISOString().split("T")[0];
843
+ void emitFeedEvent({
844
+ source: "assistant",
456
845
  title: "Heartbeat Failed",
457
- body: err instanceof Error ? err.message : String(err),
458
- });
459
- } catch (alertErr) {
460
- log.error({ alertErr }, "Failed to broadcast heartbeat alert");
846
+ summary: `Heartbeat check failed: ${(err instanceof Error ? err.message : String(err)).slice(0, 200)}`,
847
+ dedupKey: `heartbeat:fail:${today}`,
848
+ priority: 55,
849
+ urgency: "high",
850
+ }).catch(() => {});
461
851
  }
462
-
463
- const today = new Date().toISOString().split("T")[0];
464
- void emitFeedEvent({
465
- source: "assistant",
466
- title: "Heartbeat",
467
- summary: "Heartbeat check failed. Check logs for details.",
468
- dedupKey: `heartbeat:fail:${today}`,
469
- priority: 55,
470
- urgency: "medium",
471
- }).catch(() => {});
472
852
  }
473
853
  }
474
854
 
@@ -483,6 +863,7 @@ export class HeartbeatService {
483
863
  buildPrompt(
484
864
  checklist: string,
485
865
  unhealthyProviders: string[] = [],
866
+ completedRunCount: number = Infinity,
486
867
  ): { prompt: string; includedReengagement: boolean } {
487
868
  let prompt = `You are running a periodic heartbeat check. Review the following checklist and take any necessary actions.
488
869
 
@@ -499,11 +880,20 @@ Do NOT attempt to use tools for these providers — they will fail. Skip any che
499
880
  }
500
881
 
501
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.
502
886
  After completing your review, end your response with one of:
503
887
  - HEARTBEAT_OK — if everything looks good, no action needed
504
888
  - HEARTBEAT_ALERT — if you found issues that need attention (describe them before this marker)
505
889
  </heartbeat-disposition>`;
506
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
+
507
897
  let includedReengagement = false;
508
898
  if (isShallowProfile() && isReengagementCooldownElapsed()) {
509
899
  includedReengagement = true;
@@ -514,6 +904,21 @@ After completing your review, end your response with one of:
514
904
  }
515
905
  }
516
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
+
517
922
  /**
518
923
  * Check if the given hour falls within the active window.
519
924
  * Handles overnight windows (e.g. start=22, end=6).