@vellumai/assistant 0.7.2 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (347) hide show
  1. package/ARCHITECTURE.md +16 -1
  2. package/docs/architecture/memory.md +5 -2
  3. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
  4. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -9
  5. package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
  6. package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
  7. package/openapi.yaml +449 -22
  8. package/package.json +1 -1
  9. package/src/__tests__/app-control-flow.test.ts +21 -11
  10. package/src/__tests__/assistant-event-hub.test.ts +48 -0
  11. package/src/__tests__/assistant-event.test.ts +0 -10
  12. package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
  13. package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
  14. package/src/__tests__/auto-analysis-end-to-end.test.ts +62 -1
  15. package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
  16. package/src/__tests__/call-conversation-messages.test.ts +8 -2
  17. package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
  18. package/src/__tests__/channel-readiness-service.test.ts +4 -2
  19. package/src/__tests__/config-loader-backfill.test.ts +379 -0
  20. package/src/__tests__/config-schema.test.ts +1 -0
  21. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
  22. package/src/__tests__/config-watcher.test.ts +140 -69
  23. package/src/__tests__/context-search-agent-runner.test.ts +61 -3
  24. package/src/__tests__/context-search-conversations-source.test.ts +0 -24
  25. package/src/__tests__/context-search-fanout.test.ts +0 -1
  26. package/src/__tests__/context-search-memory-source.test.ts +3 -7
  27. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
  28. package/src/__tests__/context-search-pkb-source.test.ts +0 -1
  29. package/src/__tests__/context-search-workspace-source.test.ts +0 -1
  30. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
  31. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
  32. package/src/__tests__/conversation-agent-loop.test.ts +454 -5
  33. package/src/__tests__/conversation-error.test.ts +150 -3
  34. package/src/__tests__/conversation-process-callsite.test.ts +43 -0
  35. package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
  36. package/src/__tests__/conversation-runtime-assembly.test.ts +65 -0
  37. package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
  38. package/src/__tests__/conversation-speed-override.test.ts +0 -3
  39. package/src/__tests__/conversation-store.test.ts +0 -18
  40. package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
  41. package/src/__tests__/conversation-surfaces-data-persist.test.ts +404 -0
  42. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +2 -5
  43. package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
  44. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
  45. package/src/__tests__/credentials-cli.test.ts +7 -0
  46. package/src/__tests__/cu-unified-flow.test.ts +176 -10
  47. package/src/__tests__/date-context.test.ts +164 -2
  48. package/src/__tests__/disk-pressure-guard.test.ts +262 -0
  49. package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
  50. package/src/__tests__/disk-pressure-policy.test.ts +241 -0
  51. package/src/__tests__/disk-pressure-routes.test.ts +379 -0
  52. package/src/__tests__/disk-pressure-tools.test.ts +277 -0
  53. package/src/__tests__/disk-usage.test.ts +150 -0
  54. package/src/__tests__/events-client-registration.test.ts +52 -0
  55. package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
  56. package/src/__tests__/file-write-tool.test.ts +4 -10
  57. package/src/__tests__/filing-service.test.ts +3 -4
  58. package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
  59. package/src/__tests__/heartbeat-service.test.ts +260 -11
  60. package/src/__tests__/host-app-control-proxy.test.ts +195 -25
  61. package/src/__tests__/host-bash-proxy.test.ts +227 -34
  62. package/src/__tests__/host-bash-routes.test.ts +178 -13
  63. package/src/__tests__/host-cu-proxy.test.ts +210 -3
  64. package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
  65. package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
  66. package/src/__tests__/host-file-proxy.test.ts +268 -6
  67. package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
  68. package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
  69. package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
  70. package/src/__tests__/http-user-message-parity.test.ts +107 -1
  71. package/src/__tests__/injector-chain.test.ts +18 -6
  72. package/src/__tests__/injector-disk-pressure.test.ts +224 -0
  73. package/src/__tests__/managed-profile-guard.test.ts +18 -0
  74. package/src/__tests__/mcp-abort-signal.test.ts +130 -0
  75. package/src/__tests__/memory-admin-recall.test.ts +3 -11
  76. package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
  77. package/src/__tests__/normalize-onboarding.test.ts +180 -0
  78. package/src/__tests__/oauth-connect-routes.test.ts +316 -0
  79. package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
  80. package/src/__tests__/onboarding-persona-write.test.ts +308 -0
  81. package/src/__tests__/openai-provider.test.ts +45 -8
  82. package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
  83. package/src/__tests__/platform-callback-registration.test.ts +21 -4
  84. package/src/__tests__/platform.test.ts +2 -1
  85. package/src/__tests__/playbook-execution.test.ts +0 -43
  86. package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
  87. package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
  88. package/src/__tests__/provider-tool-name.test.ts +23 -0
  89. package/src/__tests__/relay-server.test.ts +15 -4
  90. package/src/__tests__/runtime-events-sse.test.ts +4 -8
  91. package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
  92. package/src/__tests__/secret-ingress-http.test.ts +0 -1
  93. package/src/__tests__/suggestion-routes.test.ts +46 -0
  94. package/src/__tests__/twilio-validation.test.ts +2 -2
  95. package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
  96. package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
  97. package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
  98. package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +90 -0
  99. package/src/approvals/guardian-decision-primitive.ts +13 -0
  100. package/src/approvals/guardian-request-resolvers.ts +16 -17
  101. package/src/backup/snapshot-lock.ts +2 -27
  102. package/src/bundler/compiler-tools.ts +3 -2
  103. package/src/calls/call-conversation-messages.ts +46 -10
  104. package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
  105. package/src/cli/commands/bash.ts +35 -108
  106. package/src/cli/commands/contacts.ts +64 -25
  107. package/src/cli/commands/credentials.ts +56 -0
  108. package/src/cli/commands/memory-v2.ts +7 -6
  109. package/src/cli/commands/oauth/__tests__/connect.test.ts +437 -1
  110. package/src/cli/commands/oauth/connect.ts +127 -1
  111. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
  112. package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
  113. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  114. package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
  115. package/src/cli/commands/platform/index.ts +16 -7
  116. package/src/cli/commands/status.ts +57 -0
  117. package/src/cli/program.ts +4 -2
  118. package/src/config/assistant-feature-flags.ts +13 -3
  119. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
  120. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
  121. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
  122. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
  123. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
  124. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
  125. package/src/config/env.ts +0 -8
  126. package/src/config/feature-flag-registry.json +27 -3
  127. package/src/config/loader.ts +127 -8
  128. package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
  129. package/src/config/schemas/call-site-catalog.ts +14 -0
  130. package/src/config/schemas/channels.ts +0 -5
  131. package/src/config/schemas/heartbeat.ts +1 -1
  132. package/src/config/schemas/llm.ts +2 -0
  133. package/src/config/schemas/memory-lifecycle.ts +13 -0
  134. package/src/config/schemas/memory-v2.ts +75 -11
  135. package/src/config/schemas/platform.ts +43 -3
  136. package/src/config/schemas/services.ts +28 -0
  137. package/src/config/seed-inference-profiles.ts +230 -33
  138. package/src/contacts/contact-store.ts +0 -25
  139. package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
  140. package/src/daemon/assistant-attachments.ts +4 -4
  141. package/src/daemon/config-watcher.ts +85 -57
  142. package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
  143. package/src/daemon/conversation-agent-loop.ts +170 -33
  144. package/src/daemon/conversation-error.ts +87 -15
  145. package/src/daemon/conversation-lifecycle.ts +1 -3
  146. package/src/daemon/conversation-process.ts +8 -0
  147. package/src/daemon/conversation-runtime-assembly.ts +26 -0
  148. package/src/daemon/conversation-store.ts +2 -2
  149. package/src/daemon/conversation-surfaces.ts +195 -15
  150. package/src/daemon/conversation-tool-setup.ts +57 -14
  151. package/src/daemon/conversation.ts +17 -22
  152. package/src/daemon/date-context.ts +71 -22
  153. package/src/daemon/disk-pressure-background-gate.ts +73 -0
  154. package/src/daemon/disk-pressure-guard.ts +343 -0
  155. package/src/daemon/disk-pressure-policy.ts +163 -0
  156. package/src/daemon/handlers/shared.ts +0 -1
  157. package/src/daemon/handlers/skills.ts +3 -4
  158. package/src/daemon/host-app-control-proxy.ts +137 -41
  159. package/src/daemon/host-bash-proxy.ts +46 -21
  160. package/src/daemon/host-cu-proxy.ts +49 -3
  161. package/src/daemon/host-file-proxy.ts +43 -7
  162. package/src/daemon/host-transfer-proxy.ts +95 -4
  163. package/src/daemon/lifecycle.ts +79 -28
  164. package/src/daemon/meet-host-supervisor.ts +4 -4
  165. package/src/daemon/meet-manifest-loader.ts +0 -1
  166. package/src/daemon/memory-v2-startup.ts +14 -4
  167. package/src/daemon/message-protocol.ts +3 -0
  168. package/src/daemon/message-types/conversations.ts +4 -0
  169. package/src/daemon/message-types/disk-pressure.ts +9 -0
  170. package/src/daemon/message-types/messages.ts +3 -0
  171. package/src/daemon/profiler-run-store.ts +5 -5
  172. package/src/daemon/tool-setup-types.ts +2 -2
  173. package/src/documents/document-store.ts +85 -0
  174. package/src/filing/filing-service.ts +30 -5
  175. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
  176. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
  177. package/src/heartbeat/heartbeat-run-store.ts +13 -0
  178. package/src/heartbeat/heartbeat-service.ts +205 -31
  179. package/src/home/feed-scheduler.ts +18 -0
  180. package/src/inbound/platform-callback-registration.ts +8 -15
  181. package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
  182. package/src/ipc/assistant-server.ts +56 -2
  183. package/src/ipc/gateway-client.ts +37 -3
  184. package/src/live-voice/live-voice-archive.ts +4 -4
  185. package/src/live-voice/protocol.ts +5 -7
  186. package/src/media/image-service.ts +1 -7
  187. package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
  188. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +52 -22
  189. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
  190. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
  191. package/src/memory/admin.ts +5 -9
  192. package/src/memory/context-search/agent-runner.ts +19 -2
  193. package/src/memory/context-search/sources/conversations.ts +2 -11
  194. package/src/memory/context-search/sources/memory-v2.ts +5 -4
  195. package/src/memory/context-search/sources/memory.ts +0 -1
  196. package/src/memory/context-search/types.ts +0 -1
  197. package/src/memory/conversation-crud.ts +4 -12
  198. package/src/memory/db-init.ts +2 -0
  199. package/src/memory/embedding-runtime-manager.ts +119 -5
  200. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +32 -21
  201. package/src/memory/graph/conversation-graph-memory.ts +42 -54
  202. package/src/memory/graph/extraction.ts +1 -3
  203. package/src/memory/graph/graph-search.test.ts +10 -67
  204. package/src/memory/graph/graph-search.ts +1 -20
  205. package/src/memory/graph/retriever.test.ts +6 -0
  206. package/src/memory/graph/retriever.ts +6 -10
  207. package/src/memory/indexer.ts +54 -45
  208. package/src/memory/job-handlers/backfill.ts +2 -11
  209. package/src/memory/job-handlers/cleanup.ts +43 -0
  210. package/src/memory/job-handlers/embedding.ts +6 -8
  211. package/src/memory/job-handlers/summarization.ts +2 -7
  212. package/src/memory/jobs-store.ts +48 -0
  213. package/src/memory/jobs-worker.ts +81 -43
  214. package/src/memory/memory-v2-activation-log-store.ts +32 -14
  215. package/src/memory/memory-v2-concept-frequency.ts +169 -0
  216. package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
  217. package/src/memory/migrations/index.ts +1 -0
  218. package/src/memory/pkb/pkb-search.test.ts +6 -0
  219. package/src/memory/qdrant-client.ts +0 -13
  220. package/src/memory/rerank-local.ts +374 -0
  221. package/src/memory/search/semantic.ts +6 -67
  222. package/src/memory/trace-event-store.ts +1 -17
  223. package/src/memory/v2/__tests__/activation.test.ts +311 -250
  224. package/src/memory/v2/__tests__/consolidation-job.test.ts +40 -8
  225. package/src/memory/v2/__tests__/injection.test.ts +157 -167
  226. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
  227. package/src/memory/v2/__tests__/qdrant.test.ts +16 -0
  228. package/src/memory/v2/__tests__/reranker.test.ts +338 -0
  229. package/src/memory/v2/__tests__/sim.test.ts +5 -199
  230. package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
  231. package/src/memory/v2/__tests__/static-context.test.ts +76 -1
  232. package/src/memory/v2/activation.ts +149 -156
  233. package/src/memory/v2/consolidation-job.ts +62 -12
  234. package/src/memory/v2/injection.ts +47 -60
  235. package/src/memory/v2/prompts/consolidation.ts +36 -1
  236. package/src/memory/v2/qdrant.ts +99 -0
  237. package/src/memory/v2/reranker.ts +177 -0
  238. package/src/memory/v2/sim.ts +10 -84
  239. package/src/memory/v2/skill-content.ts +4 -3
  240. package/src/memory/v2/skill-store.ts +82 -59
  241. package/src/memory/v2/static-context.ts +22 -0
  242. package/src/memory/v2/types.ts +10 -10
  243. package/src/notifications/copy-composer.ts +13 -0
  244. package/src/notifications/signal.ts +4 -0
  245. package/src/oauth/AGENTS.md +3 -1
  246. package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
  247. package/src/oauth/connect-orchestrator.ts +2 -0
  248. package/src/oauth/connection-resolver.test.ts +66 -1
  249. package/src/oauth/connection-resolver.ts +55 -1
  250. package/src/oauth/oauth-connect-state.ts +77 -0
  251. package/src/oauth/seed-providers.ts +58 -1
  252. package/src/plugins/defaults/injectors.ts +35 -2
  253. package/src/plugins/defaults/memory-retrieval.ts +5 -6
  254. package/src/plugins/types.ts +7 -0
  255. package/src/proactive-artifact/aux-message-injector.ts +74 -0
  256. package/src/proactive-artifact/decision.test.ts +226 -0
  257. package/src/proactive-artifact/decision.ts +165 -0
  258. package/src/proactive-artifact/index.ts +7 -0
  259. package/src/proactive-artifact/job.test.ts +867 -0
  260. package/src/proactive-artifact/job.ts +352 -0
  261. package/src/proactive-artifact/message-copy.ts +41 -0
  262. package/src/proactive-artifact/trigger-state.test.ts +277 -0
  263. package/src/proactive-artifact/trigger-state.ts +119 -0
  264. package/src/prompts/normalize-onboarding.ts +80 -0
  265. package/src/prompts/persona-resolver.ts +101 -9
  266. package/src/prompts/system-prompt.ts +21 -7
  267. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  268. package/src/providers/__tests__/retry-callsite.test.ts +222 -1
  269. package/src/providers/model-intents.ts +7 -0
  270. package/src/providers/openrouter/client.ts +8 -0
  271. package/src/providers/retry.ts +50 -0
  272. package/src/providers/types.ts +1 -0
  273. package/src/runtime/__tests__/agent-wake.test.ts +456 -3
  274. package/src/runtime/agent-wake.ts +238 -100
  275. package/src/runtime/assistant-event-hub.ts +36 -6
  276. package/src/runtime/assistant-event.ts +0 -1
  277. package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
  278. package/src/runtime/auth/route-policy.ts +14 -1
  279. package/src/runtime/auth/same-actor.ts +216 -0
  280. package/src/runtime/channel-retry-sweep.ts +65 -1
  281. package/src/runtime/guardian-reply-router.ts +10 -0
  282. package/src/runtime/local-actor-identity.ts +52 -11
  283. package/src/runtime/pending-interactions.ts +8 -0
  284. package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
  285. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
  286. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  287. package/src/runtime/routes/client-routes.ts +20 -2
  288. package/src/runtime/routes/contact-routes.ts +0 -25
  289. package/src/runtime/routes/conversation-routes.ts +35 -26
  290. package/src/runtime/routes/debug-bash-routes.ts +163 -0
  291. package/src/runtime/routes/disk-pressure-routes.ts +121 -0
  292. package/src/runtime/routes/document-pdf-renderer.ts +6 -2
  293. package/src/runtime/routes/documents-routes.ts +2 -75
  294. package/src/runtime/routes/events-routes.ts +41 -9
  295. package/src/runtime/routes/host-bash-routes.ts +23 -3
  296. package/src/runtime/routes/host-cu-routes.ts +33 -6
  297. package/src/runtime/routes/host-file-routes.ts +32 -6
  298. package/src/runtime/routes/host-transfer-routes.ts +79 -16
  299. package/src/runtime/routes/identity-routes.ts +7 -138
  300. package/src/runtime/routes/inbound-message-handler.ts +77 -12
  301. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -0
  302. package/src/runtime/routes/index.ts +6 -0
  303. package/src/runtime/routes/memory-item-routes.test.ts +41 -15
  304. package/src/runtime/routes/memory-v2-routes.ts +33 -0
  305. package/src/runtime/routes/oauth-connect-routes.ts +153 -0
  306. package/src/runtime/verification-outbound-actions.ts +4 -4
  307. package/src/schedule/run-script.ts +37 -5
  308. package/src/schedule/scheduler.ts +20 -1
  309. package/src/security/encrypted-store.ts +2 -0
  310. package/src/security/secure-keys.ts +55 -0
  311. package/src/skills/remote-skill-policy.ts +4 -10
  312. package/src/subagent/index.ts +1 -7
  313. package/src/subagent/manager.ts +1 -15
  314. package/src/tasks/task-runner.ts +0 -1
  315. package/src/tasks/task-store.ts +0 -3
  316. package/src/tools/background-tool-registry.ts +17 -3
  317. package/src/tools/host-filesystem/edit.test.ts +151 -0
  318. package/src/tools/host-filesystem/edit.ts +43 -1
  319. package/src/tools/host-filesystem/read.test.ts +129 -0
  320. package/src/tools/host-filesystem/read.ts +43 -1
  321. package/src/tools/host-filesystem/transfer.test.ts +127 -2
  322. package/src/tools/host-filesystem/transfer.ts +56 -11
  323. package/src/tools/host-filesystem/write.test.ts +134 -0
  324. package/src/tools/host-filesystem/write.ts +43 -1
  325. package/src/tools/host-terminal/host-shell.ts +13 -6
  326. package/src/tools/mcp/mcp-tool-factory.ts +2 -1
  327. package/src/tools/memory/register.test.ts +12 -9
  328. package/src/tools/memory/register.ts +1 -2
  329. package/src/tools/provider-tool-name.ts +28 -0
  330. package/src/tools/registry.ts +30 -9
  331. package/src/tools/terminal/shell.ts +9 -1
  332. package/src/tools/tool-approval-handler.ts +31 -6
  333. package/src/tools/types.ts +24 -2
  334. package/src/tts/provider-catalog.ts +3 -5
  335. package/src/util/disk-usage.ts +138 -0
  336. package/src/util/platform.ts +21 -11
  337. package/src/util/process-liveness.ts +26 -0
  338. package/src/workspace/heartbeat-service.ts +19 -0
  339. package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
  340. package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
  341. package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +72 -0
  342. package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
  343. package/src/workspace/migrations/registry.ts +8 -0
  344. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
  345. package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
  346. package/src/memory/v2/skill-qdrant.ts +0 -404
  347. package/src/signals/bash.ts +0 -198
@@ -0,0 +1,352 @@
1
+ /**
2
+ * Proactive artifact background job orchestrator.
3
+ *
4
+ * Runs a multi-phase pipeline:
5
+ * 1. Collect bounded transcript from the conversation
6
+ * 2. Phase 1 — Decision: ask the LLM whether to build an artifact
7
+ * 3. Phase 2 — Build: create the artifact (app or document)
8
+ * 4. Post-build message copy: generate a user-facing message
9
+ * 5. Message injection: deliver the message to the conversation
10
+ * 6. Notification: emit a notification signal
11
+ *
12
+ * Build failure = total silence (no message, no notification).
13
+ * Provider unavailable = silent return.
14
+ */
15
+
16
+ import { v4 as uuid } from "uuid";
17
+
18
+ import { processMessage } from "../daemon/process-message.js";
19
+ import { INTERNAL_GUARDIAN_TRUST_CONTEXT } from "../daemon/trust-context.js";
20
+ import { saveDocument } from "../documents/document-store.js";
21
+ import {
22
+ addAppConversationId,
23
+ listApps,
24
+ listAppsByConversation,
25
+ } from "../memory/app-store.js";
26
+ import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
27
+ import { rawAll } from "../memory/raw-query.js";
28
+ import type { BroadcastFn } from "../notifications/adapters/macos.js";
29
+ import { emitNotificationSignal } from "../notifications/emit-signal.js";
30
+ import {
31
+ extractText,
32
+ getConfiguredProvider,
33
+ } from "../providers/provider-send-message.js";
34
+ import { getLogger } from "../util/logger.js";
35
+ import { injectAuxAssistantMessage } from "./aux-message-injector.js";
36
+ import {
37
+ buildDecisionPrompt,
38
+ formatTranscript,
39
+ parseDecisionOutput,
40
+ } from "./decision.js";
41
+ import { buildMessageCopyPrompt, parseMessageCopy } from "./message-copy.js";
42
+ import { releaseProactiveArtifactClaim } from "./trigger-state.js";
43
+
44
+ const log = getLogger("proactive-artifact-job");
45
+
46
+ export async function runProactiveArtifactJob(params: {
47
+ conversationId: string;
48
+ userMessageCutoff: number;
49
+ assistantMessageId: string | undefined;
50
+ suppressAppBuild?: boolean;
51
+ broadcastMessage: BroadcastFn;
52
+ }): Promise<void> {
53
+ let buildSucceeded = false;
54
+ try {
55
+ // ── Collect transcript (bounded) ────────────────────────────────
56
+ const rows = rawAll<{ role: string; content: string }>(
57
+ `SELECT m.role, m.content FROM messages m
58
+ JOIN conversations c ON m.conversation_id = c.id
59
+ WHERE c.conversation_type = 'standard'
60
+ AND (m.created_at <= ? OR m.id = ?)
61
+ ORDER BY m.created_at ASC`,
62
+ params.userMessageCutoff,
63
+ params.assistantMessageId ?? "",
64
+ );
65
+
66
+ if (rows.length === 0) {
67
+ log.info(
68
+ { conversationId: params.conversationId },
69
+ "No messages found for proactive artifact transcript",
70
+ );
71
+ releaseProactiveArtifactClaim();
72
+ return;
73
+ }
74
+
75
+ const transcript = formatTranscript(rows);
76
+
77
+ // ── Phase 1 — Decision ──────────────────────────────────────────
78
+ const decisionProvider = await getConfiguredProvider(
79
+ "proactiveArtifactDecision",
80
+ );
81
+ if (!decisionProvider) {
82
+ log.info("Decision provider unavailable; skipping proactive artifact");
83
+ releaseProactiveArtifactClaim();
84
+ return;
85
+ }
86
+
87
+ const decisionResponse = await decisionProvider.sendMessage([
88
+ {
89
+ role: "user",
90
+ content: [{ type: "text", text: buildDecisionPrompt(transcript) }],
91
+ },
92
+ ]);
93
+ const decisionText = extractText(decisionResponse);
94
+ const decision = parseDecisionOutput(decisionText);
95
+
96
+ if (!decision) {
97
+ log.warn(
98
+ { conversationId: params.conversationId },
99
+ "Malformed decision output from proactive artifact LLM",
100
+ );
101
+ releaseProactiveArtifactClaim();
102
+ return;
103
+ }
104
+
105
+ if (!decision.shouldBuild) {
106
+ log.info(
107
+ {
108
+ conversationId: params.conversationId,
109
+ skipReason: decision.skipReason,
110
+ },
111
+ "Proactive artifact decision: skip",
112
+ );
113
+ releaseProactiveArtifactClaim();
114
+ return;
115
+ }
116
+
117
+ // ── Phase 2 — Build ─────────────────────────────────────────────
118
+ const { artifactType, artifactTitle, artifactDescription } = decision;
119
+ let artifactId: string;
120
+
121
+ if (artifactType === "app") {
122
+ const suppressionReason = getAppBuildSuppressionReason({
123
+ conversationId: params.conversationId,
124
+ userMessageCutoff: params.userMessageCutoff,
125
+ suppressAppBuild: params.suppressAppBuild,
126
+ });
127
+ if (suppressionReason) {
128
+ log.info(
129
+ {
130
+ conversationId: params.conversationId,
131
+ artifactTitle,
132
+ suppressionReason,
133
+ },
134
+ "Skipping proactive app build because foreground app work already exists",
135
+ );
136
+ return;
137
+ }
138
+ artifactId = await buildApp({
139
+ artifactTitle,
140
+ artifactDescription,
141
+ conversationId: params.conversationId,
142
+ });
143
+ } else {
144
+ artifactId = await buildDocument({
145
+ artifactTitle,
146
+ artifactDescription,
147
+ conversationId: params.conversationId,
148
+ transcript,
149
+ });
150
+ }
151
+ buildSucceeded = true;
152
+
153
+ // ── Post-build message copy ─────────────────────────────────────
154
+ let messageCopy: string;
155
+ const fallbackMessage = `I made something for you — ${artifactTitle}. Take a look when you get a chance.`;
156
+
157
+ try {
158
+ const copyProvider = await getConfiguredProvider(
159
+ "proactiveArtifactBuild",
160
+ );
161
+ if (!copyProvider) {
162
+ messageCopy = fallbackMessage;
163
+ } else {
164
+ const copyResponse = await copyProvider.sendMessage([
165
+ {
166
+ role: "user",
167
+ content: [
168
+ {
169
+ type: "text",
170
+ text: buildMessageCopyPrompt({
171
+ artifactType,
172
+ artifactTitle,
173
+ artifactId,
174
+ transcript,
175
+ }),
176
+ },
177
+ ],
178
+ },
179
+ ]);
180
+ const copyText = extractText(copyResponse);
181
+ messageCopy = parseMessageCopy(copyText) ?? fallbackMessage;
182
+ }
183
+ } catch (err) {
184
+ log.warn({ err }, "Message copy generation failed; using fallback");
185
+ messageCopy = fallbackMessage;
186
+ }
187
+
188
+ // ── Message injection ───────────────────────────────────────────
189
+ await injectAuxAssistantMessage({
190
+ conversationId: params.conversationId,
191
+ text: messageCopy,
192
+ broadcastMessage: params.broadcastMessage,
193
+ });
194
+
195
+ // ── Notification ────────────────────────────────────────────────
196
+ await emitNotificationSignal({
197
+ sourceEventName: "activity.complete",
198
+ sourceChannel: "vellum",
199
+ sourceContextId: params.conversationId,
200
+ attentionHints: {
201
+ urgency: "medium",
202
+ requiresAction: false,
203
+ isAsyncBackground: true,
204
+ visibleInSourceNow: false,
205
+ },
206
+ contextPayload: {
207
+ summary: `${artifactTitle} is ready`,
208
+ },
209
+ dedupeKey: "proactive-artifact",
210
+ });
211
+ } catch (err) {
212
+ log.error(
213
+ { err, conversationId: params.conversationId },
214
+ "Proactive artifact job failed",
215
+ );
216
+ if (!buildSucceeded) {
217
+ releaseProactiveArtifactClaim();
218
+ }
219
+ }
220
+ }
221
+
222
+ // ── App build ─────────────────────────────────────────────────────────
223
+
224
+ function getAppBuildSuppressionReason(params: {
225
+ conversationId: string;
226
+ userMessageCutoff: number;
227
+ suppressAppBuild?: boolean;
228
+ }): string | null {
229
+ if (params.suppressAppBuild) {
230
+ return "foreground-turn-used-app-tool";
231
+ }
232
+
233
+ const recentApps = listAppsByConversation(params.conversationId).filter(
234
+ (app) =>
235
+ app.createdAt >= params.userMessageCutoff ||
236
+ app.updatedAt >= params.userMessageCutoff,
237
+ );
238
+ if (recentApps.length > 0) {
239
+ return "conversation-has-recent-app-activity";
240
+ }
241
+
242
+ return null;
243
+ }
244
+
245
+ async function buildApp(params: {
246
+ artifactTitle: string;
247
+ artifactDescription: string;
248
+ conversationId: string;
249
+ }): Promise<string> {
250
+ const conversation = bootstrapConversation({
251
+ conversationType: "background",
252
+ source: "proactive_artifact",
253
+ groupId: "system:background",
254
+ origin: "heartbeat",
255
+ systemHint: "Proactive artifact build",
256
+ });
257
+
258
+ const buildStartedAt = Date.now();
259
+
260
+ const prompt = `Load the app-builder skill, then create an app with the following details:
261
+ - Title: ${params.artifactTitle}
262
+ - Description: ${params.artifactDescription}
263
+ - auto_open: false
264
+
265
+ For apps, keep scope tight — single file or 2-3 files max, under ~300 lines. Simple and immediately useful beats impressive and slow. This runs as a background job with limited credits — scope accordingly.
266
+
267
+ Write the source code following the skill instructions, then compile via app_refresh.`;
268
+
269
+ await processMessage(conversation.id, prompt, undefined, {
270
+ callSite: "proactiveArtifactBuild",
271
+ trustContext: INTERNAL_GUARDIAN_TRUST_CONTEXT,
272
+ });
273
+
274
+ // Query app store for newly created app
275
+ const apps = listApps();
276
+ const match = apps.find(
277
+ (app) =>
278
+ app.createdAt >= buildStartedAt &&
279
+ app.name
280
+ .trim()
281
+ .toLowerCase()
282
+ .includes(params.artifactTitle.trim().toLowerCase()),
283
+ );
284
+
285
+ if (!match) {
286
+ throw new Error(
287
+ `App build completed but no matching app found (title: ${params.artifactTitle})`,
288
+ );
289
+ }
290
+
291
+ addAppConversationId(match.id, params.conversationId);
292
+
293
+ return match.id;
294
+ }
295
+
296
+ // ── Document build ────────────────────────────────────────────────────
297
+
298
+ async function buildDocument(params: {
299
+ artifactTitle: string;
300
+ artifactDescription: string;
301
+ conversationId: string;
302
+ transcript: string;
303
+ }): Promise<string> {
304
+ const buildProvider = await getConfiguredProvider("proactiveArtifactBuild");
305
+ if (!buildProvider) {
306
+ throw new Error("Build provider unavailable for document generation");
307
+ }
308
+
309
+ const buildResponse = await buildProvider.sendMessage([
310
+ {
311
+ role: "user",
312
+ content: [
313
+ {
314
+ type: "text",
315
+ text: `Generate a well-structured markdown document based on the following specification.
316
+
317
+ Title: ${params.artifactTitle}
318
+ Description: ${params.artifactDescription}
319
+
320
+ Original conversation for context:
321
+ ${params.transcript}
322
+
323
+ Write the complete markdown content. Make it specific, actionable, and tailored to the user's situation.`,
324
+ },
325
+ ],
326
+ },
327
+ ]);
328
+
329
+ const generatedMarkdown = extractText(buildResponse);
330
+ if (!generatedMarkdown) {
331
+ throw new Error("Build provider returned empty content for document");
332
+ }
333
+
334
+ const surfaceId = `doc-${uuid()}`;
335
+ const wordCount = generatedMarkdown
336
+ .split(/\s+/)
337
+ .filter((w) => w.length > 0).length;
338
+
339
+ const result = saveDocument({
340
+ surfaceId,
341
+ conversationId: params.conversationId,
342
+ title: params.artifactTitle,
343
+ content: generatedMarkdown,
344
+ wordCount,
345
+ });
346
+
347
+ if (!result.success) {
348
+ throw new Error(`Failed to save document: ${result.error}`);
349
+ }
350
+
351
+ return surfaceId;
352
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Post-build message copy generation for proactive artifacts.
3
+ *
4
+ * After building an artifact, we ask the LLM to write a short,
5
+ * conversational message explaining what was built and why.
6
+ */
7
+
8
+ export function buildMessageCopyPrompt(params: {
9
+ artifactType: "app" | "document";
10
+ artifactTitle: string;
11
+ artifactId: string;
12
+ transcript: string;
13
+ }): string {
14
+ return `You just built a personalized ${params.artifactType} for the user based on their conversation.
15
+
16
+ Artifact details:
17
+ - Type: ${params.artifactType}
18
+ - Title: ${params.artifactTitle}
19
+ - ID: ${params.artifactId}
20
+
21
+ Original conversation:
22
+ ${params.transcript}
23
+
24
+ Write a short message (2-3 sentences) to the user explaining:
25
+ 1. What you built
26
+ 2. Why you built it (reference something specific from the conversation)
27
+ 3. Where to find it
28
+
29
+ Keep it warm and natural — not robotic. This should feel like a thoughtful gift, not a system notification.
30
+
31
+ Respond in EXACTLY this format (no extra text before or after):
32
+
33
+ MESSAGE: <your message>`;
34
+ }
35
+
36
+ export function parseMessageCopy(text: string): string | null {
37
+ const match = text.match(/^MESSAGE:\s*(.+)/ms);
38
+ if (!match) return null;
39
+ const value = match[1].trim();
40
+ return value.length > 0 ? value : null;
41
+ }
@@ -0,0 +1,277 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ rmSync,
6
+ writeFileSync,
7
+ } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { beforeEach, describe, expect, test } from "bun:test";
10
+
11
+ import { initializeDb } from "../memory/db-init.js";
12
+ import { rawRun } from "../memory/raw-query.js";
13
+ import { getDataDir } from "../util/platform.js";
14
+
15
+ initializeDb();
16
+
17
+ import {
18
+ backfillGuardIfNeeded,
19
+ getUserMessageCountUpTo,
20
+ hasProactiveArtifactCompleted,
21
+ releaseProactiveArtifactClaim,
22
+ tryClaimProactiveArtifactTrigger,
23
+ } from "./trigger-state.js";
24
+
25
+ let seedId = 0;
26
+
27
+ function guardPath(): string {
28
+ return join(getDataDir(), ".proactive-artifact-completed");
29
+ }
30
+
31
+ function seedUserMessage(createdAt: number): void {
32
+ const id = ++seedId;
33
+ const convId = `test-conv-${id}`;
34
+ rawRun(
35
+ `INSERT INTO conversations (id, title, created_at, updated_at, conversation_type, source)
36
+ VALUES (?, ?, ?, ?, 'standard', 'user')`,
37
+ convId,
38
+ `Test ${id}`,
39
+ createdAt,
40
+ createdAt,
41
+ );
42
+ rawRun(
43
+ `INSERT INTO messages (id, conversation_id, role, content, created_at)
44
+ VALUES (?, ?, 'user', 'hello', ?)`,
45
+ `test-msg-${id}`,
46
+ convId,
47
+ createdAt,
48
+ );
49
+ }
50
+
51
+ function removeGuard(): void {
52
+ try {
53
+ rmSync(guardPath(), { force: true });
54
+ } catch {
55
+ /* ignore */
56
+ }
57
+ }
58
+
59
+ describe("trigger-state", () => {
60
+ beforeEach(() => {
61
+ rawRun("DELETE FROM messages");
62
+ rawRun("DELETE FROM conversations");
63
+ removeGuard();
64
+ seedId = 0;
65
+ });
66
+
67
+ describe("getUserMessageCountUpTo", () => {
68
+ test("returns 0 when no messages exist", () => {
69
+ expect(getUserMessageCountUpTo(Date.now())).toBe(0);
70
+ });
71
+
72
+ test("counts only user messages in standard conversations", () => {
73
+ const now = 1000;
74
+ seedUserMessage(now);
75
+ seedUserMessage(now + 1);
76
+
77
+ // Insert a non-user message
78
+ const convId = "non-user-conv";
79
+ rawRun(
80
+ `INSERT INTO conversations (id, title, created_at, updated_at, conversation_type, source)
81
+ VALUES (?, ?, ?, ?, 'standard', 'user')`,
82
+ convId,
83
+ "Non-user",
84
+ now + 2,
85
+ now + 2,
86
+ );
87
+ rawRun(
88
+ `INSERT INTO messages (id, conversation_id, role, content, created_at)
89
+ VALUES (?, ?, 'assistant', 'hi', ?)`,
90
+ "assistant-msg",
91
+ convId,
92
+ now + 2,
93
+ );
94
+
95
+ // Insert a user message in a non-standard conversation
96
+ const bgConvId = "bg-conv";
97
+ rawRun(
98
+ `INSERT INTO conversations (id, title, created_at, updated_at, conversation_type, source)
99
+ VALUES (?, ?, ?, ?, 'background', 'user')`,
100
+ bgConvId,
101
+ "Background",
102
+ now + 3,
103
+ now + 3,
104
+ );
105
+ rawRun(
106
+ `INSERT INTO messages (id, conversation_id, role, content, created_at)
107
+ VALUES (?, ?, 'user', 'hello', ?)`,
108
+ "bg-msg",
109
+ bgConvId,
110
+ now + 3,
111
+ );
112
+
113
+ expect(getUserMessageCountUpTo(now + 10)).toBe(2);
114
+ });
115
+
116
+ test("respects beforeOrAt filter", () => {
117
+ seedUserMessage(100);
118
+ seedUserMessage(200);
119
+ seedUserMessage(300);
120
+
121
+ expect(getUserMessageCountUpTo(150)).toBe(1);
122
+ expect(getUserMessageCountUpTo(250)).toBe(2);
123
+ expect(getUserMessageCountUpTo(350)).toBe(3);
124
+ });
125
+
126
+ test("caps at 11 due to LIMIT", () => {
127
+ for (let i = 1; i <= 15; i++) {
128
+ seedUserMessage(i * 100);
129
+ }
130
+ expect(getUserMessageCountUpTo(Date.now())).toBe(11);
131
+ });
132
+ });
133
+
134
+ describe("tryClaimProactiveArtifactTrigger", () => {
135
+ test("returns false at count 1, 2, 3 and does NOT write guard", () => {
136
+ seedUserMessage(100);
137
+ expect(tryClaimProactiveArtifactTrigger(100)).toBe(false);
138
+ expect(existsSync(guardPath())).toBe(false);
139
+
140
+ seedUserMessage(200);
141
+ expect(tryClaimProactiveArtifactTrigger(200)).toBe(false);
142
+ expect(existsSync(guardPath())).toBe(false);
143
+
144
+ seedUserMessage(300);
145
+ expect(tryClaimProactiveArtifactTrigger(300)).toBe(false);
146
+ expect(existsSync(guardPath())).toBe(false);
147
+ });
148
+
149
+ test("returns true at count 4 (start of window) and writes guard", () => {
150
+ for (let i = 1; i <= 4; i++) seedUserMessage(i * 100);
151
+
152
+ expect(tryClaimProactiveArtifactTrigger(400)).toBe(true);
153
+ expect(existsSync(guardPath())).toBe(true);
154
+ });
155
+
156
+ test("returns true at count 10 (end of window) and writes guard", () => {
157
+ for (let i = 1; i <= 10; i++) seedUserMessage(i * 100);
158
+
159
+ expect(tryClaimProactiveArtifactTrigger(1000)).toBe(true);
160
+ expect(existsSync(guardPath())).toBe(true);
161
+ });
162
+
163
+ test("returns true at count 7 (mid-window) and writes guard", () => {
164
+ for (let i = 1; i <= 7; i++) seedUserMessage(i * 100);
165
+
166
+ expect(tryClaimProactiveArtifactTrigger(700)).toBe(true);
167
+ expect(existsSync(guardPath())).toBe(true);
168
+ });
169
+
170
+ test("returns false on second call (EEXIST from wx)", () => {
171
+ for (let i = 1; i <= 4; i++) seedUserMessage(i * 100);
172
+
173
+ expect(tryClaimProactiveArtifactTrigger(400)).toBe(true);
174
+ expect(tryClaimProactiveArtifactTrigger(400)).toBe(false);
175
+ });
176
+
177
+ test("returns false at count > 10 and writes guard permanently", () => {
178
+ for (let i = 1; i <= 11; i++) seedUserMessage(i * 100);
179
+
180
+ expect(tryClaimProactiveArtifactTrigger(1100)).toBe(false);
181
+ expect(existsSync(guardPath())).toBe(true);
182
+ });
183
+
184
+ test("concurrent calls: only one returns true", () => {
185
+ for (let i = 1; i <= 4; i++) seedUserMessage(i * 100);
186
+
187
+ const results = [
188
+ tryClaimProactiveArtifactTrigger(400),
189
+ tryClaimProactiveArtifactTrigger(400),
190
+ tryClaimProactiveArtifactTrigger(400),
191
+ ];
192
+
193
+ expect(results.filter((r) => r === true)).toHaveLength(1);
194
+ expect(results.filter((r) => r === false)).toHaveLength(2);
195
+ });
196
+
197
+ test("rapid next-message race: in-window trigger still fires", () => {
198
+ for (let i = 1; i <= 5; i++) seedUserMessage(i * 100);
199
+
200
+ expect(tryClaimProactiveArtifactTrigger(400)).toBe(true);
201
+ });
202
+ });
203
+
204
+ describe("releaseProactiveArtifactClaim", () => {
205
+ test("removes guard file so next turn can retry", () => {
206
+ for (let i = 1; i <= 4; i++) seedUserMessage(i * 100);
207
+
208
+ expect(tryClaimProactiveArtifactTrigger(400)).toBe(true);
209
+ expect(existsSync(guardPath())).toBe(true);
210
+
211
+ releaseProactiveArtifactClaim();
212
+ expect(existsSync(guardPath())).toBe(false);
213
+
214
+ seedUserMessage(500);
215
+ expect(tryClaimProactiveArtifactTrigger(500)).toBe(true);
216
+ expect(existsSync(guardPath())).toBe(true);
217
+ });
218
+
219
+ test("is no-op when guard does not exist", () => {
220
+ expect(existsSync(guardPath())).toBe(false);
221
+ releaseProactiveArtifactClaim();
222
+ expect(existsSync(guardPath())).toBe(false);
223
+ });
224
+ });
225
+
226
+ describe("hasProactiveArtifactCompleted", () => {
227
+ test("returns false when guard does not exist", () => {
228
+ expect(hasProactiveArtifactCompleted()).toBe(false);
229
+ });
230
+
231
+ test("returns true when guard exists", () => {
232
+ mkdirSync(join(getDataDir()), { recursive: true });
233
+ writeFileSync(guardPath(), new Date().toISOString());
234
+ expect(hasProactiveArtifactCompleted()).toBe(true);
235
+ });
236
+ });
237
+
238
+ describe("backfillGuardIfNeeded", () => {
239
+ test("creates guard when count > 10", () => {
240
+ for (let i = 1; i <= 11; i++) {
241
+ seedUserMessage(i * 100);
242
+ }
243
+
244
+ backfillGuardIfNeeded();
245
+ expect(existsSync(guardPath())).toBe(true);
246
+ });
247
+
248
+ test("is no-op when count is within window (4-10)", () => {
249
+ for (let i = 1; i <= 7; i++) {
250
+ seedUserMessage(i * 100);
251
+ }
252
+
253
+ backfillGuardIfNeeded();
254
+ expect(existsSync(guardPath())).toBe(false);
255
+ });
256
+
257
+ test("is no-op when count < 4", () => {
258
+ seedUserMessage(100);
259
+ seedUserMessage(200);
260
+ seedUserMessage(300);
261
+
262
+ backfillGuardIfNeeded();
263
+ expect(existsSync(guardPath())).toBe(false);
264
+ });
265
+
266
+ test("is no-op when guard already exists", () => {
267
+ for (let i = 1; i <= 11; i++) {
268
+ seedUserMessage(i * 100);
269
+ }
270
+
271
+ mkdirSync(join(getDataDir()), { recursive: true });
272
+ writeFileSync(guardPath(), "already-exists");
273
+ backfillGuardIfNeeded();
274
+ expect(readFileSync(guardPath(), "utf-8")).toBe("already-exists");
275
+ });
276
+ });
277
+ });