@vellumai/assistant 0.5.13 → 0.5.15

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 (425) hide show
  1. package/.env.example +1 -6
  2. package/AGENTS.md +4 -0
  3. package/ARCHITECTURE.md +0 -1
  4. package/bunfig.toml +1 -0
  5. package/docs/architecture/memory.md +3 -3
  6. package/openapi.yaml +127 -22
  7. package/package.json +1 -1
  8. package/src/__tests__/access-request-decision.test.ts +2 -32
  9. package/src/__tests__/actor-token-service.test.ts +1 -31
  10. package/src/__tests__/anthropic-provider.test.ts +53 -40
  11. package/src/__tests__/app-git-history.test.ts +9 -17
  12. package/src/__tests__/app-git-service.test.ts +14 -20
  13. package/src/__tests__/app-store-dir-names.test.ts +10 -20
  14. package/src/__tests__/approval-cascade.test.ts +2 -19
  15. package/src/__tests__/approval-primitive.test.ts +2 -27
  16. package/src/__tests__/approval-routes-http.test.ts +2 -30
  17. package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -28
  18. package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -45
  19. package/src/__tests__/attachments-store.test.ts +5 -32
  20. package/src/__tests__/audit-log-rotation.test.ts +5 -36
  21. package/src/__tests__/avatar-e2e.test.ts +1 -9
  22. package/src/__tests__/avatar-generator.test.ts +1 -7
  23. package/src/__tests__/browser-fill-credential.test.ts +0 -4
  24. package/src/__tests__/browser-manager.test.ts +0 -6
  25. package/src/__tests__/call-controller.test.ts +1 -22
  26. package/src/__tests__/call-conversation-messages.test.ts +0 -21
  27. package/src/__tests__/call-domain.test.ts +0 -25
  28. package/src/__tests__/call-pointer-messages.test.ts +0 -21
  29. package/src/__tests__/call-recovery.test.ts +0 -22
  30. package/src/__tests__/call-routes-http.test.ts +0 -24
  31. package/src/__tests__/call-store.test.ts +0 -21
  32. package/src/__tests__/cancel-resolves-conversation-key.test.ts +0 -24
  33. package/src/__tests__/canonical-guardian-store.test.ts +48 -21
  34. package/src/__tests__/channel-approval-routes.test.ts +6 -26
  35. package/src/__tests__/channel-approvals.test.ts +1 -38
  36. package/src/__tests__/channel-delivery-store.test.ts +0 -21
  37. package/src/__tests__/channel-guardian.test.ts +0 -26
  38. package/src/__tests__/channel-reply-delivery.test.ts +5 -0
  39. package/src/__tests__/channel-retry-sweep.test.ts +0 -21
  40. package/src/__tests__/checker.test.ts +26 -61
  41. package/src/__tests__/clawhub.test.ts +9 -25
  42. package/src/__tests__/cli-command-risk-guard.test.ts +0 -18
  43. package/src/__tests__/config-loader-backfill.test.ts +9 -28
  44. package/src/__tests__/config-schema-cmd.test.ts +5 -25
  45. package/src/__tests__/config-schema.test.ts +21 -40
  46. package/src/__tests__/config-watcher.test.ts +4 -91
  47. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -21
  48. package/src/__tests__/contacts-tools.test.ts +0 -21
  49. package/src/__tests__/context-memory-e2e.test.ts +0 -21
  50. package/src/__tests__/context-window-manager.test.ts +130 -3
  51. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -4
  52. package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -4
  53. package/src/__tests__/conversation-agent-loop.test.ts +0 -4
  54. package/src/__tests__/conversation-attachments.test.ts +1 -24
  55. package/src/__tests__/conversation-attention-store.test.ts +0 -21
  56. package/src/__tests__/conversation-attention-telegram.test.ts +0 -22
  57. package/src/__tests__/conversation-clear-safety.test.ts +0 -22
  58. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -21
  59. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -24
  60. package/src/__tests__/conversation-disk-view-integration.test.ts +1 -23
  61. package/src/__tests__/conversation-disk-view.test.ts +5 -27
  62. package/src/__tests__/conversation-error.test.ts +1 -1
  63. package/src/__tests__/conversation-fork-crud.test.ts +1 -33
  64. package/src/__tests__/conversation-fork-route.test.ts +0 -27
  65. package/src/__tests__/conversation-history-web-search.test.ts +23 -16
  66. package/src/__tests__/conversation-init.benchmark.test.ts +22 -43
  67. package/src/__tests__/conversation-key-store-disk-view.test.ts +8 -34
  68. package/src/__tests__/conversation-load-history-repair.test.ts +0 -4
  69. package/src/__tests__/conversation-pre-run-repair.test.ts +0 -4
  70. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -4
  71. package/src/__tests__/conversation-queue.test.ts +8 -8
  72. package/src/__tests__/conversation-routes-disk-view.test.ts +13 -51
  73. package/src/__tests__/conversation-runtime-assembly.test.ts +64 -38
  74. package/src/__tests__/conversation-slash-commands.test.ts +5 -0
  75. package/src/__tests__/conversation-slash-queue.test.ts +0 -4
  76. package/src/__tests__/conversation-slash-unknown.test.ts +0 -4
  77. package/src/__tests__/conversation-speed-override.test.ts +326 -0
  78. package/src/__tests__/conversation-starter-routes.test.ts +0 -23
  79. package/src/__tests__/conversation-store.test.ts +0 -21
  80. package/src/__tests__/conversation-unread-route.test.ts +0 -24
  81. package/src/__tests__/conversation-usage.test.ts +56 -21
  82. package/src/__tests__/conversation-wipe.test.ts +0 -21
  83. package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -4
  84. package/src/__tests__/conversation-workspace-injection.test.ts +0 -4
  85. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -4
  86. package/src/__tests__/credential-execution-shell-lockdown.test.ts +8 -5
  87. package/src/__tests__/credential-vault-unit.test.ts +9 -428
  88. package/src/__tests__/credentials-cli.test.ts +10 -10
  89. package/src/__tests__/daemon-assistant-events.test.ts +0 -19
  90. package/src/__tests__/date-context.test.ts +77 -97
  91. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +7 -24
  92. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +29 -42
  93. package/src/__tests__/delete-managed-skill-tool.test.ts +2 -10
  94. package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -26
  95. package/src/__tests__/docker-signing-key-bootstrap.test.ts +61 -15
  96. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -36
  97. package/src/__tests__/email-cli.test.ts +6 -6
  98. package/src/__tests__/ephemeral-permissions.test.ts +5 -17
  99. package/src/__tests__/first-greeting.test.ts +4 -32
  100. package/src/__tests__/followup-tools.test.ts +0 -21
  101. package/src/__tests__/gateway-only-enforcement.test.ts +0 -20
  102. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -23
  103. package/src/__tests__/guardian-action-followup-executor.test.ts +0 -23
  104. package/src/__tests__/guardian-action-followup-store.test.ts +0 -21
  105. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -21
  106. package/src/__tests__/guardian-action-late-reply.test.ts +0 -21
  107. package/src/__tests__/guardian-action-store.test.ts +0 -21
  108. package/src/__tests__/guardian-action-sweep.test.ts +0 -21
  109. package/src/__tests__/guardian-binding-drift-heal.test.ts +0 -23
  110. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +172 -22
  111. package/src/__tests__/guardian-dispatch.test.ts +0 -21
  112. package/src/__tests__/guardian-grant-minting.test.ts +0 -22
  113. package/src/__tests__/guardian-outbound-http.test.ts +0 -22
  114. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +0 -23
  115. package/src/__tests__/guardian-routing-invariants.test.ts +0 -22
  116. package/src/__tests__/guardian-routing-state.test.ts +0 -22
  117. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -24
  118. package/src/__tests__/headless-browser-interactions.test.ts +0 -4
  119. package/src/__tests__/headless-browser-navigate.test.ts +0 -4
  120. package/src/__tests__/headless-browser-read-tools.test.ts +0 -4
  121. package/src/__tests__/headless-browser-snapshot.test.ts +0 -4
  122. package/src/__tests__/heartbeat-service.test.ts +99 -26
  123. package/src/__tests__/hooks-blocking.test.ts +3 -3
  124. package/src/__tests__/hooks-config.test.ts +7 -7
  125. package/src/__tests__/hooks-discovery.test.ts +3 -3
  126. package/src/__tests__/hooks-integration.test.ts +5 -5
  127. package/src/__tests__/hooks-manager.test.ts +3 -3
  128. package/src/__tests__/hooks-runner.test.ts +5 -23
  129. package/src/__tests__/hooks-settings.test.ts +3 -3
  130. package/src/__tests__/hooks-templates.test.ts +3 -3
  131. package/src/__tests__/http-conversation-lineage.test.ts +0 -27
  132. package/src/__tests__/identity-intro-cache.test.ts +0 -4
  133. package/src/__tests__/inbound-invite-redemption.test.ts +0 -22
  134. package/src/__tests__/inline-skill-load-permissions.test.ts +5 -16
  135. package/src/__tests__/intent-routing.test.ts +2 -55
  136. package/src/__tests__/invite-redemption-service.test.ts +0 -21
  137. package/src/__tests__/invite-routes-http.test.ts +0 -21
  138. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +0 -17
  139. package/src/__tests__/journal-context.test.ts +8 -75
  140. package/src/__tests__/list-messages-attachments.test.ts +0 -22
  141. package/src/__tests__/llm-context-route-provider.test.ts +0 -21
  142. package/src/__tests__/llm-request-log-turn-query.test.ts +46 -28
  143. package/src/__tests__/llm-usage-store.test.ts +0 -21
  144. package/src/__tests__/log-export-workspace.test.ts +1 -1
  145. package/src/__tests__/managed-skill-lifecycle.test.ts +1 -1
  146. package/src/__tests__/managed-store.test.ts +1 -1
  147. package/src/__tests__/mcp-cli.test.ts +7 -10
  148. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -21
  149. package/src/__tests__/memory-jobs-worker-backoff.test.ts +0 -11
  150. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -21
  151. package/src/__tests__/memory-recall-log-store.test.ts +0 -27
  152. package/src/__tests__/memory-recall-quality.test.ts +0 -21
  153. package/src/__tests__/memory-regressions.experimental.test.ts +31 -30
  154. package/src/__tests__/memory-regressions.test.ts +282 -70
  155. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -21
  156. package/src/__tests__/memory-upsert-concurrency.test.ts +0 -21
  157. package/src/__tests__/messaging-send-tool.test.ts +201 -0
  158. package/src/__tests__/migration-cross-version-compatibility.test.ts +18 -13
  159. package/src/__tests__/migration-export-http.test.ts +7 -1
  160. package/src/__tests__/migration-import-commit-http.test.ts +16 -14
  161. package/src/__tests__/migration-import-preflight-http.test.ts +27 -44
  162. package/src/__tests__/migration-validate-http.test.ts +1 -28
  163. package/src/__tests__/native-web-search.test.ts +25 -22
  164. package/src/__tests__/non-member-access-request.test.ts +0 -22
  165. package/src/__tests__/notification-guardian-path.test.ts +0 -21
  166. package/src/__tests__/notification-schedule-dedup.test.ts +1 -25
  167. package/src/__tests__/oauth-apps-routes.test.ts +103 -2
  168. package/src/__tests__/oauth-cli.test.ts +52 -0
  169. package/src/__tests__/oauth-provider-profiles.test.ts +0 -16
  170. package/src/__tests__/oauth-provider-serializer.test.ts +232 -0
  171. package/src/__tests__/oauth-providers-routes.test.ts +257 -0
  172. package/src/__tests__/oauth-store.test.ts +0 -21
  173. package/src/__tests__/onboarding-template-contract.test.ts +2 -2
  174. package/src/__tests__/openai-provider.test.ts +261 -0
  175. package/src/__tests__/pairing-concurrent.test.ts +6 -6
  176. package/src/__tests__/pairing-routes.test.ts +7 -1
  177. package/src/__tests__/path-policy.test.ts +1 -1
  178. package/src/__tests__/platform.test.ts +64 -88
  179. package/src/__tests__/playbook-execution.test.ts +0 -21
  180. package/src/__tests__/playbook-tools.test.ts +0 -21
  181. package/src/__tests__/pricing.test.ts +100 -0
  182. package/src/__tests__/relay-server.test.ts +1 -25
  183. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -24
  184. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -24
  185. package/src/__tests__/runtime-events-sse.test.ts +0 -24
  186. package/src/__tests__/sandbox-diagnostics.test.ts +2 -1
  187. package/src/__tests__/scaffold-managed-skill-tool.test.ts +1 -1
  188. package/src/__tests__/schedule-store.test.ts +0 -21
  189. package/src/__tests__/schedule-tools.test.ts +0 -21
  190. package/src/__tests__/scheduler-recurrence.test.ts +0 -21
  191. package/src/__tests__/scoped-approval-grants.test.ts +0 -21
  192. package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -21
  193. package/src/__tests__/secret-allowlist.test.ts +1 -1
  194. package/src/__tests__/secret-ingress-channel.test.ts +0 -5
  195. package/src/__tests__/secret-ingress-cli.test.ts +0 -6
  196. package/src/__tests__/secret-ingress-http.test.ts +0 -5
  197. package/src/__tests__/secret-ingress.test.ts +0 -5
  198. package/src/__tests__/send-endpoint-busy.test.ts +0 -24
  199. package/src/__tests__/sequence-store.test.ts +0 -21
  200. package/src/__tests__/server-history-render.test.ts +0 -24
  201. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -4
  202. package/src/__tests__/skill-load-inline-command.test.ts +9 -0
  203. package/src/__tests__/skill-load-inline-includes.test.ts +9 -0
  204. package/src/__tests__/skill-load-tool.test.ts +11 -0
  205. package/src/__tests__/skills-uninstall.test.ts +10 -8
  206. package/src/__tests__/skills.test.ts +1 -1
  207. package/src/__tests__/slack-channel-config.test.ts +1 -1
  208. package/src/__tests__/slack-inbound-verification.test.ts +0 -22
  209. package/src/__tests__/starter-bundle.test.ts +4 -1
  210. package/src/__tests__/suggestion-routes.test.ts +2 -0
  211. package/src/__tests__/system-prompt.test.ts +1 -1
  212. package/src/__tests__/terminal-tools.test.ts +1 -1
  213. package/src/__tests__/test-preload.ts +31 -0
  214. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -1
  215. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  216. package/src/__tests__/tool-executor.test.ts +0 -20
  217. package/src/__tests__/tool-input-summary.test.ts +124 -0
  218. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -1
  219. package/src/__tests__/trust-store.test.ts +7 -1
  220. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -1
  221. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +1 -1
  222. package/src/__tests__/trusted-contact-multichannel.test.ts +1 -1
  223. package/src/__tests__/trusted-contact-verification.test.ts +1 -1
  224. package/src/__tests__/turn-boundary-resolution.test.ts +1 -1
  225. package/src/__tests__/twilio-routes.test.ts +1 -1
  226. package/src/__tests__/update-bulletin.test.ts +1 -1
  227. package/src/__tests__/vbundle-pax-and-symlink.test.ts +1 -1
  228. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -0
  229. package/src/__tests__/voice-scoped-grant-consumer.test.ts +1 -1
  230. package/src/__tests__/voice-session-bridge.test.ts +1 -1
  231. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +4 -4
  232. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -1
  233. package/src/__tests__/workspace-migration-down-functions.test.ts +15 -3
  234. package/src/__tests__/workspace-migration-seed-device-id.test.ts +40 -4
  235. package/src/agent/loop.ts +6 -9
  236. package/src/approvals/guardian-decision-primitive.ts +46 -18
  237. package/src/approvals/guardian-request-resolvers.ts +19 -2
  238. package/src/calls/active-call-lease.ts +2 -2
  239. package/src/cli/AGENTS.md +1 -1
  240. package/src/cli/commands/doctor.ts +9 -9
  241. package/src/cli/commands/memory.ts +142 -0
  242. package/src/cli/commands/oauth/__tests__/connect.test.ts +13 -11
  243. package/src/cli/commands/oauth/__tests__/ping.test.ts +1 -1
  244. package/src/cli/commands/oauth/connect.ts +13 -12
  245. package/src/cli/commands/oauth/index.ts +1 -1
  246. package/src/cli/commands/oauth/providers.ts +47 -62
  247. package/src/cli/commands/platform/__tests__/connect.test.ts +72 -46
  248. package/src/cli/commands/platform/__tests__/disconnect.test.ts +54 -1
  249. package/src/cli/commands/platform/__tests__/status.test.ts +36 -0
  250. package/src/cli/commands/platform/connect.ts +17 -7
  251. package/src/cli/commands/platform/disconnect.ts +28 -3
  252. package/src/cli/commands/platform/index.ts +3 -3
  253. package/src/cli.ts +1 -299
  254. package/src/config/assistant-feature-flags.ts +23 -15
  255. package/src/config/bundled-skills/app-builder/TOOLS.json +16 -0
  256. package/src/config/bundled-skills/app-builder/tools/app-create.ts +4 -0
  257. package/src/config/bundled-skills/app-builder/tools/app-delete.ts +5 -1
  258. package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +9 -1
  259. package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +5 -1
  260. package/src/config/bundled-skills/contacts/TOOLS.json +8 -0
  261. package/src/config/bundled-skills/contacts/tools/contact-search.ts +10 -1
  262. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +16 -2
  263. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +1 -0
  264. package/src/config/bundled-skills/messaging/SKILL.md +7 -7
  265. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +37 -0
  266. package/src/config/bundled-skills/slack/SKILL.md +18 -0
  267. package/src/config/env-registry.ts +15 -11
  268. package/src/config/env.ts +1 -11
  269. package/src/config/feature-flag-registry.json +16 -0
  270. package/src/config/schema.ts +4 -0
  271. package/src/config/schemas/heartbeat.ts +6 -1
  272. package/src/config/schemas/inference.ts +14 -3
  273. package/src/config/schemas/memory-processing.ts +16 -8
  274. package/src/config/schemas/memory-retrieval.ts +3 -3
  275. package/src/config/skills.ts +1 -1
  276. package/src/context/window-manager.ts +174 -51
  277. package/src/credential-execution/executable-discovery.ts +2 -2
  278. package/src/daemon/approved-devices-store.ts +2 -2
  279. package/src/daemon/assistant-attachments.ts +2 -0
  280. package/src/daemon/config-watcher.ts +4 -50
  281. package/src/daemon/conversation-agent-loop-handlers.ts +9 -1
  282. package/src/daemon/conversation-agent-loop.ts +12 -0
  283. package/src/daemon/conversation-error.ts +3 -5
  284. package/src/daemon/conversation-history.ts +7 -3
  285. package/src/daemon/conversation-lifecycle.ts +16 -0
  286. package/src/daemon/conversation-messaging.ts +1 -0
  287. package/src/daemon/conversation-notifiers.ts +67 -30
  288. package/src/daemon/conversation-process.ts +161 -2
  289. package/src/daemon/conversation-queue-manager.ts +2 -0
  290. package/src/daemon/conversation-runtime-assembly.ts +33 -11
  291. package/src/daemon/conversation-slash.ts +14 -3
  292. package/src/daemon/conversation-tool-setup.ts +2 -0
  293. package/src/daemon/conversation-usage.ts +32 -4
  294. package/src/daemon/conversation.ts +33 -1
  295. package/src/daemon/daemon-control.ts +32 -16
  296. package/src/daemon/date-context.ts +47 -45
  297. package/src/daemon/dictation-profile-store.ts +2 -2
  298. package/src/daemon/handlers/conversations.ts +19 -0
  299. package/src/daemon/handlers/shared.ts +14 -21
  300. package/src/daemon/lifecycle.ts +5 -7
  301. package/src/daemon/message-types/conversations.ts +2 -0
  302. package/src/daemon/message-types/guardian-actions.ts +3 -17
  303. package/src/daemon/message-types/integrations.ts +11 -1
  304. package/src/daemon/message-types/messages.ts +1 -0
  305. package/src/daemon/pairing-store.ts +2 -79
  306. package/src/daemon/server.ts +154 -8
  307. package/src/daemon/watch-handler.ts +65 -21
  308. package/src/email/guardrails.ts +3 -3
  309. package/src/heartbeat/heartbeat-service.ts +14 -7
  310. package/src/hooks/cli.ts +2 -2
  311. package/src/hooks/config.ts +2 -2
  312. package/src/hooks/discovery.ts +2 -2
  313. package/src/hooks/manager.ts +2 -2
  314. package/src/hooks/runner.ts +5 -2
  315. package/src/hooks/templates.ts +2 -2
  316. package/src/memory/admin.ts +181 -2
  317. package/src/memory/app-git-service.ts +61 -4
  318. package/src/memory/attachments-store.ts +2 -0
  319. package/src/memory/canonical-guardian-store.ts +16 -0
  320. package/src/memory/db-init.ts +8 -0
  321. package/src/memory/embedding-local.ts +5 -2
  322. package/src/memory/indexer.ts +44 -26
  323. package/src/memory/items-extractor.ts +34 -82
  324. package/src/memory/job-handlers/batch-extraction.ts +741 -0
  325. package/src/memory/job-handlers/journal-carry-forward.test.ts +383 -0
  326. package/src/memory/job-handlers/journal-carry-forward.ts +255 -0
  327. package/src/memory/jobs-store.ts +28 -0
  328. package/src/memory/jobs-worker.ts +56 -9
  329. package/src/memory/lifecycle-events-store.ts +4 -2
  330. package/src/memory/llm-request-log-store.ts +40 -2
  331. package/src/memory/llm-usage-store.ts +4 -3
  332. package/src/memory/migrations/199-guardian-request-enrichment-columns.ts +71 -0
  333. package/src/memory/migrations/200-usage-llm-call-count.ts +20 -0
  334. package/src/memory/migrations/index.ts +2 -0
  335. package/src/memory/query-expansion.ts +83 -0
  336. package/src/memory/retriever.test.ts +119 -0
  337. package/src/memory/retriever.ts +513 -105
  338. package/src/memory/schema/guardian.ts +4 -0
  339. package/src/memory/schema/infrastructure.ts +1 -0
  340. package/src/memory/search/formatting.test.ts +140 -0
  341. package/src/memory/search/formatting.ts +143 -198
  342. package/src/memory/search/mmr.ts +136 -0
  343. package/src/memory/search/staleness.ts +0 -15
  344. package/src/memory/search/tier-classifier.ts +10 -21
  345. package/src/memory/search/types.ts +17 -0
  346. package/src/messaging/providers/slack/adapter.ts +51 -5
  347. package/src/notifications/broadcaster.ts +13 -0
  348. package/src/notifications/copy-composer.ts +8 -0
  349. package/src/oauth/connect-orchestrator.ts +1 -1
  350. package/src/oauth/connection-resolver.ts +2 -2
  351. package/src/oauth/provider-serializer.ts +116 -0
  352. package/src/permissions/trust-store.ts +24 -7
  353. package/src/prompts/__tests__/build-cli-reference-section.test.ts +5 -0
  354. package/src/prompts/journal-context.ts +50 -35
  355. package/src/prompts/persona-resolver.ts +1 -1
  356. package/src/prompts/system-prompt.ts +27 -28
  357. package/src/prompts/templates/BOOTSTRAP.md +14 -1
  358. package/src/prompts/templates/HEARTBEAT.md +10 -0
  359. package/src/prompts/templates/NOW.md +19 -25
  360. package/src/prompts/templates/SOUL.md +13 -1
  361. package/src/prompts/templates/UPDATES.md +12 -0
  362. package/src/prompts/update-bulletin.ts +1 -1
  363. package/src/providers/anthropic/client.ts +89 -18
  364. package/src/providers/model-catalog.ts +22 -2
  365. package/src/providers/model-intents.ts +2 -2
  366. package/src/providers/openai/client.ts +40 -1
  367. package/src/providers/retry.ts +23 -4
  368. package/src/providers/types.ts +2 -0
  369. package/src/runtime/assistant-scope.ts +1 -1
  370. package/src/runtime/auth/__tests__/credential-service.test.ts +1 -0
  371. package/src/runtime/auth/route-policy.ts +1 -0
  372. package/src/runtime/auth/token-service.ts +51 -29
  373. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -1
  374. package/src/runtime/guardian-decision-types.ts +16 -10
  375. package/src/runtime/http-server.ts +3 -14
  376. package/src/runtime/http-types.ts +1 -0
  377. package/src/runtime/migrations/vbundle-builder.ts +7 -4
  378. package/src/runtime/migrations/vbundle-import-analyzer.ts +0 -4
  379. package/src/runtime/migrations/vbundle-importer.ts +1 -1
  380. package/src/runtime/routes/conversation-query-routes.ts +40 -8
  381. package/src/runtime/routes/conversation-routes.ts +125 -3
  382. package/src/runtime/routes/guardian-action-routes.ts +9 -3
  383. package/src/runtime/routes/identity-routes.ts +25 -4
  384. package/src/runtime/routes/llm-context-normalization.ts +1 -0
  385. package/src/runtime/routes/log-export-routes.ts +34 -12
  386. package/src/runtime/routes/migration-routes.ts +6 -10
  387. package/src/runtime/routes/oauth-apps.ts +2 -9
  388. package/src/runtime/routes/oauth-providers.ts +60 -0
  389. package/src/runtime/routes/pairing-routes.ts +0 -8
  390. package/src/runtime/routes/settings-routes.ts +0 -1
  391. package/src/runtime/routes/telemetry-routes.ts +16 -4
  392. package/src/security/encrypted-store.ts +2 -2
  393. package/src/security/secret-allowlist.ts +3 -3
  394. package/src/signals/emit-event.ts +42 -0
  395. package/src/signals/user-message.ts +37 -0
  396. package/src/telemetry/usage-telemetry-reporter.test.ts +83 -19
  397. package/src/telemetry/usage-telemetry-reporter.ts +23 -17
  398. package/src/tools/browser/runtime-check.ts +2 -2
  399. package/src/tools/credentials/vault.ts +2 -249
  400. package/src/tools/memory/definitions.ts +1 -1
  401. package/src/tools/memory/handlers.test.ts +50 -8
  402. package/src/tools/memory/handlers.ts +3 -1
  403. package/src/tools/side-effects.ts +1 -6
  404. package/src/tools/terminal/safe-env.ts +3 -2
  405. package/src/tools/terminal/shell.ts +11 -14
  406. package/src/tools/tool-approval-handler.ts +20 -1
  407. package/src/tools/tool-input-summary.ts +66 -0
  408. package/src/tools/types.ts +4 -0
  409. package/src/usage/types.ts +4 -0
  410. package/src/util/device-id.ts +10 -10
  411. package/src/util/platform.ts +71 -33
  412. package/src/util/pricing.ts +19 -6
  413. package/src/util/strip-comment-lines.ts +28 -0
  414. package/src/workspace/git-service.ts +8 -18
  415. package/src/workspace/migrations/003-seed-device-id.ts +6 -4
  416. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +7 -1
  417. package/src/workspace/migrations/017-seed-persona-dirs.ts +2 -4
  418. package/src/workspace/migrations/021-move-signals-to-workspace.ts +84 -0
  419. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +94 -0
  420. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +86 -0
  421. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +126 -0
  422. package/src/workspace/migrations/migrate-to-workspace-volume.ts +3 -6
  423. package/src/workspace/migrations/registry.ts +8 -0
  424. package/src/signals/confirm.ts +0 -82
  425. package/src/signals/trust-rule.ts +0 -174
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync } from "node:fs";
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
 
4
4
  import {
@@ -48,11 +48,13 @@ import { getSigningKeyFingerprint } from "../runtime/auth/token-service.js";
48
48
  import { bridgeConfirmationRequestToGuardian } from "../runtime/confirmation-request-guardian-bridge.js";
49
49
  import * as pendingInteractions from "../runtime/pending-interactions.js";
50
50
  import { checkIngressForSecrets } from "../security/secret-ingress.js";
51
+ import { redactSecrets } from "../security/secret-scanner.js";
51
52
  import { registerCancelCallback } from "../signals/cancel.js";
52
53
  import { registerConversationUndoCallback } from "../signals/conversation-undo.js";
53
54
  import { appendEventToStream } from "../signals/event-stream.js";
54
55
  import { registerUserMessageCallback } from "../signals/user-message.js";
55
56
  import { getSubagentManager } from "../subagent/index.js";
57
+ import { summarizeToolInput } from "../tools/tool-input-summary.js";
56
58
  import { getLogger } from "../util/logger.js";
57
59
  import {
58
60
  getSandboxWorkingDir,
@@ -66,6 +68,7 @@ import {
66
68
  DEFAULT_MEMORY_POLICY,
67
69
  } from "./conversation.js";
68
70
  import { ConversationEvictor } from "./conversation-evictor.js";
71
+ import { formatCompactResult } from "./conversation-process.js";
69
72
  import { resolveChannelCapabilities } from "./conversation-runtime-assembly.js";
70
73
  import { resolveSlash, type SlashContext } from "./conversation-slash.js";
71
74
  import { undoLastMessage } from "./handlers/conversations.js";
@@ -78,7 +81,10 @@ import type { SkillOperationContext } from "./handlers/skills.js";
78
81
  import { HostBashProxy } from "./host-bash-proxy.js";
79
82
  import { HostCuProxy } from "./host-cu-proxy.js";
80
83
  import { HostFileProxy } from "./host-file-proxy.js";
81
- import type { ServerMessage } from "./message-protocol.js";
84
+ import type {
85
+ ServerMessage,
86
+ UserMessageAttachment,
87
+ } from "./message-protocol.js";
82
88
 
83
89
  const log = getLogger("server");
84
90
 
@@ -175,6 +181,14 @@ function makePendingInteractionRegistrar(
175
181
  try {
176
182
  const trustContext = conversation.trustContext;
177
183
  const sourceChannel = trustContext?.sourceChannel ?? "vellum";
184
+ const inputRecord = msg.input as Record<string, unknown>;
185
+ const activityRaw =
186
+ (typeof inputRecord.activity === "string"
187
+ ? inputRecord.activity
188
+ : undefined) ??
189
+ (typeof inputRecord.reason === "string"
190
+ ? inputRecord.reason
191
+ : undefined);
178
192
  const canonicalRequest = createCanonicalGuardianRequest({
179
193
  id: msg.requestId,
180
194
  kind: "tool_approval",
@@ -186,6 +200,15 @@ function makePendingInteractionRegistrar(
186
200
  guardianExternalUserId: trustContext?.guardianExternalUserId,
187
201
  guardianPrincipalId: trustContext?.guardianPrincipalId ?? undefined,
188
202
  toolName: msg.toolName,
203
+ commandPreview:
204
+ redactSecrets(
205
+ summarizeToolInput(msg.toolName, inputRecord),
206
+ ) || undefined,
207
+ riskLevel: msg.riskLevel,
208
+ activityText: activityRaw
209
+ ? redactSecrets(activityRaw)
210
+ : undefined,
211
+ executionTarget: msg.executionTarget,
189
212
  status: "pending",
190
213
  requestCode: generateCanonicalRequestCode(),
191
214
  expiresAt: Date.now() + 5 * 60 * 1000,
@@ -545,14 +568,68 @@ export class DaemonServer {
545
568
  params.conversationKey,
546
569
  );
547
570
  const conversation = await this.getOrCreateConversation(conversationId);
571
+
572
+ // Register file-backed attachments so they flow through the send
573
+ // pipeline as images the LLM can see directly.
574
+ const attachmentIds: string[] = [];
575
+ const resolvedAttachments: UserMessageAttachment[] = [];
576
+ if (params.attachments && params.attachments.length > 0) {
577
+ for (const a of params.attachments) {
578
+ try {
579
+ const size = statSync(a.path).size;
580
+ const stored = attachmentsStore.uploadFileBackedAttachment(
581
+ a.filename,
582
+ a.mimeType,
583
+ a.path,
584
+ size,
585
+ );
586
+ attachmentIds.push(stored.id);
587
+ const fileData = readFileSync(a.path).toString("base64");
588
+ resolvedAttachments.push({
589
+ id: stored.id,
590
+ filename: a.filename,
591
+ mimeType: a.mimeType,
592
+ data: fileData,
593
+ filePath: a.path,
594
+ });
595
+ } catch (err) {
596
+ log.warn(
597
+ { err, path: a.path },
598
+ "Failed to register signal attachment",
599
+ );
600
+ }
601
+ }
602
+ }
603
+
604
+ // Build a hub-publishing sender so events reach SSE clients.
605
+ const hubSender = (msg: ServerMessage) => {
606
+ const msgConversationId =
607
+ "conversationId" in msg &&
608
+ typeof (msg as { conversationId?: unknown }).conversationId ===
609
+ "string"
610
+ ? (msg as { conversationId: string }).conversationId
611
+ : undefined;
612
+ const event = buildAssistantEvent(
613
+ DAEMON_INTERNAL_ASSISTANT_ID,
614
+ msg,
615
+ msgConversationId ?? conversationId,
616
+ );
617
+ assistantEventHub.publish(event).catch((err) => {
618
+ log.warn(
619
+ { err },
620
+ "assistant-events hub subscriber threw during signal user-message",
621
+ );
622
+ });
623
+ };
624
+
548
625
  if (conversation.isProcessing()) {
549
626
  const requestId = crypto.randomUUID();
550
627
  const resolvedChannel = resolveTurnChannel(params.sourceChannel);
551
628
  const resolvedInterface = resolveTurnInterface(params.sourceInterface);
552
629
  const result = conversation.enqueueMessage(
553
630
  params.content,
554
- [],
555
- () => {},
631
+ resolvedAttachments,
632
+ hubSender,
556
633
  requestId,
557
634
  undefined,
558
635
  undefined,
@@ -568,8 +645,8 @@ export class DaemonServer {
568
645
  await this.persistAndProcessMessage(
569
646
  conversationId,
570
647
  params.content,
571
- undefined,
572
- undefined,
648
+ attachmentIds.length > 0 ? attachmentIds : undefined,
649
+ { onEvent: hubSender },
573
650
  params.sourceChannel,
574
651
  params.sourceInterface,
575
652
  );
@@ -773,6 +850,7 @@ export class DaemonServer {
773
850
  (msg) => this.broadcast(msg),
774
851
  memoryPolicy,
775
852
  sharedCesClient,
853
+ storedOptions?.speed,
776
854
  );
777
855
  newConversation.updateClient(sendToClient, true);
778
856
  await newConversation.loadFromDb();
@@ -885,9 +963,29 @@ export class DaemonServer {
885
963
  conversation.setAssistantId(
886
964
  options?.assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID,
887
965
  );
888
- conversation.setTrustContext(options?.trustContext ?? null);
889
- conversation.setAuthContext(options?.authContext ?? null);
966
+ // Only overwrite trust/auth context when explicitly provided. Callers that
967
+ // don't supply a trust context (e.g. signal-injected messages) should
968
+ // inherit whatever the conversation already has from a prior session.
969
+ if (options?.trustContext !== undefined) {
970
+ conversation.setTrustContext(options.trustContext);
971
+ }
972
+ if (options?.authContext !== undefined) {
973
+ conversation.setAuthContext(options.authContext);
974
+ }
890
975
  await conversation.ensureActorScopedHistory();
976
+
977
+ // Persist the conversation's current trust/auth context so it survives
978
+ // eviction and recreation. The restore path in getOrCreateConversation
979
+ // reads from storedOptions.trustContext / storedOptions.authContext.
980
+ const currentTrust = conversation.trustContext;
981
+ const currentAuth = conversation.authContext;
982
+ if (currentTrust || currentAuth) {
983
+ this.conversationOptions.set(conversationId, {
984
+ ...this.conversationOptions.get(conversationId),
985
+ ...(currentTrust ? { trustContext: currentTrust } : {}),
986
+ ...(currentAuth ? { authContext: currentAuth } : {}),
987
+ });
988
+ }
891
989
  conversation.setChannelCapabilities(
892
990
  resolveChannelCapabilities(
893
991
  sourceChannel,
@@ -1155,6 +1253,54 @@ export class DaemonServer {
1155
1253
  return { messageId: persisted.id };
1156
1254
  }
1157
1255
 
1256
+ if (slashResult.kind === "compact") {
1257
+ const serverTurnCtx = conversation.getTurnChannelContext();
1258
+ const serverProvenance = provenanceFromTrustContext(
1259
+ conversation.trustContext,
1260
+ );
1261
+ const compactChannelMeta = {
1262
+ ...serverProvenance,
1263
+ ...(serverTurnCtx
1264
+ ? {
1265
+ userMessageChannel: serverTurnCtx.userMessageChannel,
1266
+ assistantMessageChannel: serverTurnCtx.assistantMessageChannel,
1267
+ }
1268
+ : {}),
1269
+ ...(serverInterfaceCtx
1270
+ ? {
1271
+ userMessageInterface: serverInterfaceCtx.userMessageInterface,
1272
+ assistantMessageInterface:
1273
+ serverInterfaceCtx.assistantMessageInterface,
1274
+ }
1275
+ : {}),
1276
+ };
1277
+ const cleanMsg = createUserMessage(content, attachments);
1278
+ const persisted = await addMessage(
1279
+ conversationId,
1280
+ "user",
1281
+ JSON.stringify(cleanMsg.content),
1282
+ compactChannelMeta,
1283
+ );
1284
+ conversation.getMessages().push(cleanMsg);
1285
+
1286
+ conversation.emitActivityState(
1287
+ "thinking",
1288
+ "context_compacting",
1289
+ "assistant_turn",
1290
+ );
1291
+ const result = await conversation.forceCompact();
1292
+ const responseText = formatCompactResult(result);
1293
+ const assistantMsg = createAssistantMessage(responseText);
1294
+ await addMessage(
1295
+ conversationId,
1296
+ "assistant",
1297
+ JSON.stringify(assistantMsg.content),
1298
+ compactChannelMeta,
1299
+ );
1300
+ conversation.getMessages().push(assistantMsg);
1301
+ return { messageId: persisted.id };
1302
+ }
1303
+
1158
1304
  const resolvedContent = slashResult.content;
1159
1305
 
1160
1306
  const requestId = crypto.randomUUID();
@@ -1,3 +1,4 @@
1
+ import { recordRequestLog } from "../memory/llm-request-log-store.js";
1
2
  import {
2
3
  extractText,
3
4
  getConfiguredProvider,
@@ -20,11 +21,16 @@ import type { WatchObservation } from "./message-protocol.js";
20
21
  const log = getLogger("watch-handler");
21
22
 
22
23
  /**
23
- * Module-level maps to store commentary/summary text so that session
24
- * notifier callbacks can retrieve it from the WatchSession's conversationId.
24
+ * Module-level maps to store commentary/summary text (and associated LLM log
25
+ * IDs) so that session notifier callbacks can retrieve them from the
26
+ * WatchSession's conversationId and link logs to the persisted message.
25
27
  */
26
- export const lastCommentaryByConversation = new Map<string, string>();
27
- export const lastSummaryByConversation = new Map<string, string>();
28
+ export interface WatchResult {
29
+ text: string;
30
+ logIds: string[];
31
+ }
32
+ export const lastCommentaryByConversation = new Map<string, WatchResult>();
33
+ export const lastSummaryByConversation = new Map<string, WatchResult>();
28
34
 
29
35
  export async function handleWatchObservation(
30
36
  msg: WatchObservation,
@@ -116,15 +122,15 @@ async function generateCommentary(session: WatchSession): Promise<void> {
116
122
  return;
117
123
  }
118
124
  const lastThree = session.observations.slice(-3);
119
- const previousCommentary = lastCommentaryByConversation.get(
125
+ const previousResult = lastCommentaryByConversation.get(
120
126
  session.conversationId,
121
127
  );
122
128
 
123
129
  const userContent = [
124
130
  `Focus area: ${session.focusArea}`,
125
131
  "",
126
- previousCommentary
127
- ? `Previous commentary: "${previousCommentary}"`
132
+ previousResult
133
+ ? `Previous commentary: "${previousResult.text}"`
128
134
  : "No previous commentary yet.",
129
135
  "",
130
136
  ...lastThree.map(
@@ -164,10 +170,29 @@ async function generateCommentary(session: WatchSession): Promise<void> {
164
170
  },
165
171
  );
166
172
 
173
+ const logIds: string[] = [];
174
+ if (response.rawRequest && response.rawResponse) {
175
+ try {
176
+ const logId = recordRequestLog(
177
+ session.conversationId,
178
+ JSON.stringify(response.rawRequest),
179
+ JSON.stringify(response.rawResponse),
180
+ undefined,
181
+ response.actualProvider ?? provider.name,
182
+ );
183
+ logIds.push(logId);
184
+ } catch (err) {
185
+ log.warn({ err }, "Failed to persist watch commentary LLM log");
186
+ }
187
+ }
188
+
167
189
  const commentaryText = extractText(response);
168
190
 
169
191
  if (commentaryText && commentaryText !== "SKIP") {
170
- lastCommentaryByConversation.set(session.conversationId, commentaryText);
192
+ lastCommentaryByConversation.set(session.conversationId, {
193
+ text: commentaryText,
194
+ logIds,
195
+ });
171
196
  fireWatchCommentaryNotifier(session.conversationId, session);
172
197
  session.commentaryCount++;
173
198
  }
@@ -206,10 +231,10 @@ export async function generateSummary(session: WatchSession): Promise<void> {
206
231
  { watchId: session.watchId },
207
232
  "Configured provider unavailable for summary generation",
208
233
  );
209
- lastSummaryByConversation.set(
210
- session.conversationId,
211
- "[error] Configured provider unavailable. Check your settings.",
212
- );
234
+ lastSummaryByConversation.set(session.conversationId, {
235
+ text: "[error] Configured provider unavailable. Check your settings.",
236
+ logIds: [],
237
+ });
213
238
  fireWatchCompletionNotifier(session.conversationId, session);
214
239
  return;
215
240
  }
@@ -310,6 +335,22 @@ export async function generateSummary(session: WatchSession): Promise<void> {
310
335
  },
311
336
  );
312
337
 
338
+ const logIds: string[] = [];
339
+ if (response.rawRequest && response.rawResponse) {
340
+ try {
341
+ const logId = recordRequestLog(
342
+ session.conversationId,
343
+ JSON.stringify(response.rawRequest),
344
+ JSON.stringify(response.rawResponse),
345
+ undefined,
346
+ response.actualProvider ?? provider.name,
347
+ );
348
+ logIds.push(logId);
349
+ } catch (err) {
350
+ log.warn({ err }, "Failed to persist watch summary LLM log");
351
+ }
352
+ }
353
+
313
354
  log.debug(
314
355
  { watchId: session.watchId },
315
356
  "LLM API call completed successfully",
@@ -323,7 +364,10 @@ export async function generateSummary(session: WatchSession): Promise<void> {
323
364
  );
324
365
 
325
366
  if (summaryText) {
326
- lastSummaryByConversation.set(session.conversationId, summaryText);
367
+ lastSummaryByConversation.set(session.conversationId, {
368
+ text: summaryText,
369
+ logIds,
370
+ });
327
371
  log.debug(
328
372
  { watchId: session.watchId, conversationId: session.conversationId },
329
373
  "Firing completion notifier with summary",
@@ -334,10 +378,10 @@ export async function generateSummary(session: WatchSession): Promise<void> {
334
378
  { watchId: session.watchId },
335
379
  "Summary was empty from API response",
336
380
  );
337
- lastSummaryByConversation.set(
338
- session.conversationId,
339
- "[error] The API returned an empty summary. This may indicate a service issue.",
340
- );
381
+ lastSummaryByConversation.set(session.conversationId, {
382
+ text: "[error] The API returned an empty summary. This may indicate a service issue.",
383
+ logIds,
384
+ });
341
385
  fireWatchCompletionNotifier(session.conversationId, session);
342
386
  }
343
387
  } catch (err) {
@@ -346,10 +390,10 @@ export async function generateSummary(session: WatchSession): Promise<void> {
346
390
  "Error generating watch summary — LLM API call failed",
347
391
  );
348
392
  const message = err instanceof Error ? err.message : String(err);
349
- lastSummaryByConversation.set(
350
- session.conversationId,
351
- `[error] Summary generation failed: ${message}`,
352
- );
393
+ lastSummaryByConversation.set(session.conversationId, {
394
+ text: `[error] Summary generation failed: ${message}`,
395
+ logIds: [],
396
+ });
353
397
  fireWatchCompletionNotifier(session.conversationId, session);
354
398
  }
355
399
  }
@@ -8,7 +8,7 @@ import { join } from "node:path";
8
8
 
9
9
  import { minimatch } from "minimatch";
10
10
 
11
- import { getRootDir } from "../util/platform.js";
11
+ import { getWorkspaceDir } from "../util/platform.js";
12
12
 
13
13
  export interface AddressRule {
14
14
  id: string;
@@ -36,7 +36,7 @@ const DEFAULT_STATE: GuardrailsState = {
36
36
  };
37
37
 
38
38
  function getGuardrailsPath(): string {
39
- return join(getRootDir(), "email-guardrails.json");
39
+ return join(getWorkspaceDir(), "email-guardrails.json");
40
40
  }
41
41
 
42
42
  function loadState(): GuardrailsState {
@@ -59,7 +59,7 @@ function loadState(): GuardrailsState {
59
59
 
60
60
  function saveState(state: GuardrailsState): void {
61
61
  const path = getGuardrailsPath();
62
- const dir = join(getRootDir());
62
+ const dir = getWorkspaceDir();
63
63
  if (!existsSync(dir)) {
64
64
  mkdirSync(dir, { recursive: true });
65
65
  }
@@ -1,20 +1,24 @@
1
1
  import { getConfig } from "../config/loader.js";
2
+ import type { Speed } from "../config/schemas/inference.js";
2
3
  import type { HeartbeatAlert } from "../daemon/message-protocol.js";
3
4
  import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
4
5
  import { readTextFileSync } from "../util/fs.js";
5
6
  import { getLogger } from "../util/logger.js";
6
7
  import { getWorkspacePromptPath } from "../util/platform.js";
8
+ import { stripCommentLines } from "../util/strip-comment-lines.js";
7
9
 
8
10
  const log = getLogger("heartbeat-check");
9
11
 
10
- const DEFAULT_CHECKLIST = `- Check the current weather and note anything notable
11
- - Review any recent news headlines worth flagging
12
- - Look for calendar events or reminders coming up soon`;
12
+ const DEFAULT_CHECKLIST = `- Check in with yourself. Read NOW.md. Is it still accurate? Update it if anything has changed.
13
+ - Think about your user. Is there anything from recent conversations you should follow up on? Anything you noticed that you should bring up?
14
+ - Check if there's anything on the horizon — events, deadlines, things they mentioned wanting to do.
15
+ - 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.`;
13
16
 
14
17
  export interface HeartbeatDeps {
15
18
  processMessage: (
16
19
  conversationId: string,
17
20
  content: string,
21
+ options?: { speed?: Speed },
18
22
  ) => Promise<{ messageId: string }>;
19
23
  alerter: (alert: HeartbeatAlert) => void;
20
24
  onConversationCreated?: (info: {
@@ -167,6 +171,7 @@ export class HeartbeatService {
167
171
  log.info("Running heartbeat");
168
172
 
169
173
  try {
174
+ const config = getConfig().heartbeat;
170
175
  const checklist = this.readChecklist();
171
176
  const prompt = this.buildPrompt(checklist);
172
177
 
@@ -182,7 +187,9 @@ export class HeartbeatService {
182
187
  title: "Heartbeat",
183
188
  });
184
189
 
185
- await this.deps.processMessage(conversation.id, prompt);
190
+ await this.deps.processMessage(conversation.id, prompt, {
191
+ speed: config.speed,
192
+ });
186
193
  log.info({ conversationId: conversation.id }, "Heartbeat completed");
187
194
  } catch (err) {
188
195
  log.error({ err }, "Heartbeat failed");
@@ -199,10 +206,10 @@ export class HeartbeatService {
199
206
  }
200
207
 
201
208
  private readChecklist(): string {
202
- return (
209
+ const raw =
203
210
  readTextFileSync(getWorkspacePromptPath("HEARTBEAT.md")) ??
204
- DEFAULT_CHECKLIST
205
- );
211
+ DEFAULT_CHECKLIST;
212
+ return stripCommentLines(raw);
206
213
  }
207
214
 
208
215
  /** @internal Exposed for testing. */
package/src/hooks/cli.ts CHANGED
@@ -5,7 +5,7 @@ import { Command } from "commander";
5
5
 
6
6
  import { pathExists } from "../util/fs.js";
7
7
  import { getCliLogger } from "../util/logger.js";
8
- import { getHooksDir } from "../util/platform.js";
8
+ import { getWorkspaceHooksDir } from "../util/platform.js";
9
9
  import { ensureHookInConfig, removeHook, setHookEnabled } from "./config.js";
10
10
  import { discoverHooks, isValidInstallManifest } from "./discovery.js";
11
11
 
@@ -171,7 +171,7 @@ Examples:
171
171
  process.exit(1);
172
172
  }
173
173
 
174
- const hooksDir = getHooksDir();
174
+ const hooksDir = getWorkspaceHooksDir();
175
175
  const resolvedHooksDir = resolve(hooksDir);
176
176
  const targetDir = resolve(join(hooksDir, manifest.name));
177
177
  if (!targetDir.startsWith(resolvedHooksDir + sep)) {
@@ -3,7 +3,7 @@ import { dirname, join } from "node:path";
3
3
 
4
4
  import { ensureDir, readTextFileSync } from "../util/fs.js";
5
5
  import { getLogger } from "../util/logger.js";
6
- import { getHooksDir } from "../util/platform.js";
6
+ import { getWorkspaceHooksDir } from "../util/platform.js";
7
7
  import type { HookConfig, HookConfigEntry, HookManifest } from "./types.js";
8
8
 
9
9
  const log = getLogger("hooks-config");
@@ -11,7 +11,7 @@ const log = getLogger("hooks-config");
11
11
  const HOOKS_CONFIG_VERSION = 1;
12
12
 
13
13
  function getConfigPath(): string {
14
- return join(getHooksDir(), "config.json");
14
+ return join(getWorkspaceHooksDir(), "config.json");
15
15
  }
16
16
 
17
17
  export function loadHooksConfig(): HookConfig {
@@ -3,7 +3,7 @@ import { join, relative, resolve } from "node:path";
3
3
 
4
4
  import { pathExists } from "../util/fs.js";
5
5
  import { getLogger } from "../util/logger.js";
6
- import { getHooksDir } from "../util/platform.js";
6
+ import { getWorkspaceHooksDir } from "../util/platform.js";
7
7
  import { loadHooksConfig } from "./config.js";
8
8
  import type { DiscoveredHook, HookManifest } from "./types.js";
9
9
 
@@ -64,7 +64,7 @@ export function isValidInstallManifest(
64
64
  }
65
65
 
66
66
  export function discoverHooks(hooksDir?: string): DiscoveredHook[] {
67
- const dir = hooksDir ?? getHooksDir();
67
+ const dir = hooksDir ?? getWorkspaceHooksDir();
68
68
  if (!pathExists(dir)) return [];
69
69
 
70
70
  const config = loadHooksConfig();
@@ -4,7 +4,7 @@ import { getIsContainerized } from "../config/env-registry.js";
4
4
  import { Debouncer } from "../util/debounce.js";
5
5
  import { pathExists } from "../util/fs.js";
6
6
  import { getLogger } from "../util/logger.js";
7
- import { getHooksDir } from "../util/platform.js";
7
+ import { getWorkspaceHooksDir } from "../util/platform.js";
8
8
  import { discoverHooks } from "./discovery.js";
9
9
  import { runHookScript } from "./runner.js";
10
10
  import type {
@@ -121,7 +121,7 @@ export class HookManager {
121
121
 
122
122
  watch(): void {
123
123
  if (getIsContainerized()) return;
124
- const hooksDir = getHooksDir();
124
+ const hooksDir = getWorkspaceHooksDir();
125
125
  if (!pathExists(hooksDir)) return;
126
126
 
127
127
  this.stopWatching();
@@ -3,7 +3,7 @@ import { homedir } from "node:os";
3
3
  import { basename, extname, join } from "node:path";
4
4
 
5
5
  import { pathExists } from "../util/fs.js";
6
- import { getRootDir, getWorkspaceDir } from "../util/platform.js";
6
+ import { getWorkspaceDir } from "../util/platform.js";
7
7
  import { getHookSettings } from "./config.js";
8
8
  import type { DiscoveredHook, HookEventData } from "./types.js";
9
9
 
@@ -69,7 +69,10 @@ export async function runHookScript(
69
69
  ...process.env,
70
70
  VELLUM_HOOK_EVENT: eventData.event,
71
71
  VELLUM_HOOK_NAME: hook.name,
72
- VELLUM_ROOT_DIR: getRootDir(),
72
+ // @deprecated — usage of VELLUM_ROOT_DIR by hook scripts is deprecated.
73
+ // Removing this requires an LLM-based migration or declarative migration
74
+ // file to update existing user-authored hooks to use VELLUM_WORKSPACE_DIR.
75
+ VELLUM_ROOT_DIR: join(homedir(), ".vellum"),
73
76
  VELLUM_WORKSPACE_DIR: getWorkspaceDir(),
74
77
  VELLUM_HOOK_SETTINGS: JSON.stringify(
75
78
  getHookSettings(hook.name, hook.manifest),
@@ -11,7 +11,7 @@ import { join } from "node:path";
11
11
  import { resolveBundledDir } from "../util/bundled-asset.js";
12
12
  import { pathExists } from "../util/fs.js";
13
13
  import { getLogger } from "../util/logger.js";
14
- import { getHooksDir } from "../util/platform.js";
14
+ import { getWorkspaceHooksDir } from "../util/platform.js";
15
15
  import { ensureHookInConfig } from "./config.js";
16
16
 
17
17
  const log = getLogger("hooks-templates");
@@ -30,7 +30,7 @@ export function installTemplates(): void {
30
30
  );
31
31
  if (!pathExists(templatesDir)) return;
32
32
 
33
- const hooksDir = getHooksDir();
33
+ const hooksDir = getWorkspaceHooksDir();
34
34
  const entries = readdirSync(templatesDir, {
35
35
  withFileTypes: true,
36
36
  }) as Dirent[];