vellum 0.2.0 → 0.2.2

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 (361) hide show
  1. package/README.md +15 -2
  2. package/bun.lock +5 -2
  3. package/package.json +4 -2
  4. package/scripts/capture-x-graphql.ts +562 -0
  5. package/scripts/ipc/check-swift-decoder-drift.ts +2 -1
  6. package/scripts/test.sh +5 -0
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +161 -34
  8. package/src/__tests__/account-registry.test.ts +2 -1
  9. package/src/__tests__/agent-heartbeat-service.test.ts +250 -0
  10. package/src/__tests__/app-bundler.test.ts +12 -33
  11. package/src/__tests__/asset-materialize-tool.test.ts +16 -15
  12. package/src/__tests__/asset-search-tool.test.ts +23 -22
  13. package/src/__tests__/attachments-store.test.ts +56 -127
  14. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +5 -4
  15. package/src/__tests__/browser-skill-endstate.test.ts +5 -8
  16. package/src/__tests__/call-bridge.test.ts +385 -0
  17. package/src/__tests__/call-constants.test.ts +40 -0
  18. package/src/__tests__/call-orchestrator.test.ts +454 -0
  19. package/src/__tests__/call-recovery.test.ts +518 -0
  20. package/src/__tests__/call-routes-http.test.ts +459 -0
  21. package/src/__tests__/call-state-machine.test.ts +143 -0
  22. package/src/__tests__/call-state.test.ts +133 -0
  23. package/src/__tests__/call-store.test.ts +691 -0
  24. package/src/__tests__/cli-discover.test.ts +1 -1
  25. package/src/__tests__/commit-message-enrichment-service.test.ts +550 -0
  26. package/src/__tests__/compaction.benchmark.test.ts +176 -0
  27. package/src/__tests__/computer-use-tools.test.ts +250 -0
  28. package/src/__tests__/config-schema.test.ts +348 -3
  29. package/src/__tests__/conflict-store.test.ts +2 -1
  30. package/src/__tests__/contacts-tools.test.ts +331 -0
  31. package/src/__tests__/conversation-store.test.ts +30 -32
  32. package/src/__tests__/credential-security-invariants.test.ts +4 -0
  33. package/src/__tests__/date-context.test.ts +373 -0
  34. package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
  35. package/src/__tests__/doordash-session.test.ts +9 -0
  36. package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -3
  37. package/src/__tests__/followup-tools.test.ts +303 -0
  38. package/src/__tests__/handlers-twitter-config.test.ts +718 -0
  39. package/src/__tests__/intent-routing.test.ts +64 -57
  40. package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
  41. package/src/__tests__/ipc-snapshot.test.ts +96 -28
  42. package/src/__tests__/llm-usage-store.test.ts +3 -8
  43. package/src/__tests__/media-generate-image.test.ts +1 -1
  44. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
  45. package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
  46. package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
  47. package/src/__tests__/playbook-tools.test.ts +342 -0
  48. package/src/__tests__/profile-compiler.test.ts +2 -1
  49. package/src/__tests__/provider-streaming.benchmark.test.ts +773 -0
  50. package/src/__tests__/recurrence-engine-rruleset.test.ts +78 -0
  51. package/src/__tests__/recurrence-engine.test.ts +69 -0
  52. package/src/__tests__/recurrence-types.test.ts +71 -0
  53. package/src/__tests__/registry.test.ts +17 -10
  54. package/src/__tests__/relay-server.test.ts +633 -0
  55. package/src/__tests__/reminder-store.test.ts +6 -3
  56. package/src/__tests__/reminder.test.ts +43 -77
  57. package/src/__tests__/run-orchestrator-assistant-events.test.ts +222 -0
  58. package/src/__tests__/run-orchestrator.test.ts +7 -7
  59. package/src/__tests__/runtime-attachment-metadata.test.ts +19 -20
  60. package/src/__tests__/runtime-runs-http.test.ts +5 -23
  61. package/src/__tests__/runtime-runs.test.ts +11 -11
  62. package/src/__tests__/schedule-store.test.ts +482 -0
  63. package/src/__tests__/schedule-tools.test.ts +700 -0
  64. package/src/__tests__/scheduler-recurrence.test.ts +329 -0
  65. package/src/__tests__/server-history-render.test.ts +14 -13
  66. package/src/__tests__/session-error.test.ts +28 -0
  67. package/src/__tests__/session-init.benchmark.test.ts +462 -0
  68. package/src/__tests__/session-queue.test.ts +89 -16
  69. package/src/__tests__/session-runtime-assembly.test.ts +161 -0
  70. package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
  71. package/src/__tests__/signup-e2e.test.ts +2 -1
  72. package/src/__tests__/skill-projection.benchmark.test.ts +328 -0
  73. package/src/__tests__/skill-script-runner.test.ts +159 -0
  74. package/src/__tests__/speaker-identification.test.ts +52 -0
  75. package/src/__tests__/subagent-manager-notify.test.ts +42 -10
  76. package/src/__tests__/subagent-tools.test.ts +141 -41
  77. package/src/__tests__/task-compiler.test.ts +2 -1
  78. package/src/__tests__/task-runner.test.ts +2 -1
  79. package/src/__tests__/task-scheduler.test.ts +2 -1
  80. package/src/__tests__/task-tools.test.ts +49 -56
  81. package/src/__tests__/tool-audit-listener.test.ts +1 -0
  82. package/src/__tests__/tool-domain-event-publisher.test.ts +2 -0
  83. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
  84. package/src/__tests__/tool-executor.test.ts +13 -17
  85. package/src/__tests__/turn-commit.test.ts +273 -2
  86. package/src/__tests__/twilio-provider.test.ts +143 -0
  87. package/src/__tests__/twilio-routes.test.ts +789 -0
  88. package/src/__tests__/twitter-auth-handler.test.ts +581 -0
  89. package/src/__tests__/view-image-tool.test.ts +217 -0
  90. package/src/__tests__/workspace-git-service.test.ts +403 -0
  91. package/src/__tests__/workspace-heartbeat-service.test.ts +141 -2
  92. package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
  93. package/src/bundler/app-bundler.ts +35 -14
  94. package/src/calls/call-bridge.ts +95 -0
  95. package/src/calls/call-constants.ts +48 -0
  96. package/src/calls/call-domain.ts +276 -0
  97. package/src/calls/call-orchestrator.ts +390 -0
  98. package/src/calls/call-recovery.ts +207 -0
  99. package/src/calls/call-state-machine.ts +68 -0
  100. package/src/calls/call-state.ts +64 -0
  101. package/src/calls/call-store.ts +416 -0
  102. package/src/calls/relay-server.ts +335 -0
  103. package/src/calls/speaker-identification.ts +213 -0
  104. package/src/calls/twilio-config.ts +34 -0
  105. package/src/calls/twilio-provider.ts +173 -0
  106. package/src/calls/twilio-routes.ts +250 -0
  107. package/src/calls/types.ts +37 -0
  108. package/src/calls/voice-provider.ts +14 -0
  109. package/src/cli/config-commands.ts +334 -0
  110. package/src/cli/core-commands.ts +776 -0
  111. package/src/cli/doordash.ts +256 -25
  112. package/src/cli/ipc-client.ts +82 -0
  113. package/src/cli/map.ts +246 -0
  114. package/src/cli/twitter.ts +575 -0
  115. package/src/cli.ts +7 -5
  116. package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
  117. package/src/commands/cc-command-registry.ts +209 -0
  118. package/src/config/bundled-skills/contacts/SKILL.md +39 -0
  119. package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
  120. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +9 -0
  121. package/src/config/bundled-skills/contacts/tools/contact-search.ts +9 -0
  122. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +9 -0
  123. package/src/config/bundled-skills/document/SKILL.md +18 -0
  124. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  125. package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
  126. package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
  127. package/src/config/bundled-skills/doordash/SKILL.md +163 -0
  128. package/src/config/bundled-skills/followups/SKILL.md +32 -0
  129. package/src/config/bundled-skills/followups/TOOLS.json +100 -0
  130. package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
  131. package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
  132. package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
  133. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
  134. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -24
  135. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -1
  136. package/src/config/bundled-skills/playbooks/SKILL.md +31 -0
  137. package/src/config/bundled-skills/playbooks/TOOLS.json +126 -0
  138. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +9 -0
  139. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +9 -0
  140. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +9 -0
  141. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +9 -0
  142. package/src/config/bundled-skills/reminder/SKILL.md +20 -0
  143. package/src/config/bundled-skills/reminder/TOOLS.json +67 -0
  144. package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +9 -0
  145. package/src/config/bundled-skills/reminder/tools/reminder-create.ts +9 -0
  146. package/src/config/bundled-skills/reminder/tools/reminder-list.ts +9 -0
  147. package/src/config/bundled-skills/schedule/SKILL.md +74 -0
  148. package/src/config/bundled-skills/schedule/TOOLS.json +135 -0
  149. package/src/config/bundled-skills/schedule/tools/schedule-create.ts +9 -0
  150. package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +9 -0
  151. package/src/config/bundled-skills/schedule/tools/schedule-list.ts +9 -0
  152. package/src/config/bundled-skills/schedule/tools/schedule-update.ts +9 -0
  153. package/src/config/bundled-skills/subagent/SKILL.md +25 -0
  154. package/src/config/bundled-skills/subagent/TOOLS.json +107 -0
  155. package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +9 -0
  156. package/src/config/bundled-skills/subagent/tools/subagent-message.ts +9 -0
  157. package/src/config/bundled-skills/subagent/tools/subagent-read.ts +9 -0
  158. package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +9 -0
  159. package/src/config/bundled-skills/subagent/tools/subagent-status.ts +9 -0
  160. package/src/config/bundled-skills/tasks/SKILL.md +28 -0
  161. package/src/config/bundled-skills/tasks/TOOLS.json +256 -0
  162. package/src/config/bundled-skills/tasks/tools/task-delete.ts +9 -0
  163. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +9 -0
  164. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +9 -0
  165. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +9 -0
  166. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +9 -0
  167. package/src/config/bundled-skills/tasks/tools/task-list.ts +9 -0
  168. package/src/config/bundled-skills/tasks/tools/task-run.ts +9 -0
  169. package/src/config/bundled-skills/tasks/tools/task-save.ts +9 -0
  170. package/src/config/bundled-skills/twitter/SKILL.md +134 -0
  171. package/src/config/bundled-skills/watcher/SKILL.md +27 -0
  172. package/src/config/bundled-skills/watcher/TOOLS.json +147 -0
  173. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +9 -0
  174. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +9 -0
  175. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +9 -0
  176. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +9 -0
  177. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +9 -0
  178. package/src/config/defaults.ts +44 -0
  179. package/src/config/loader.ts +4 -1
  180. package/src/config/schema.ts +218 -1
  181. package/src/config/system-prompt.ts +100 -6
  182. package/src/config/templates/IDENTITY.md +7 -0
  183. package/src/config/types.ts +5 -0
  184. package/src/contacts/contact-store.ts +4 -4
  185. package/src/daemon/assistant-attachments.ts +10 -0
  186. package/src/daemon/classifier.ts +3 -1
  187. package/src/daemon/computer-use-session.ts +3 -1
  188. package/src/daemon/date-context.ts +136 -0
  189. package/src/daemon/handlers/apps.ts +16 -1
  190. package/src/daemon/handlers/browser.ts +54 -0
  191. package/src/daemon/handlers/computer-use.ts +7 -1
  192. package/src/daemon/handlers/config.ts +192 -4
  193. package/src/daemon/handlers/diagnostics.ts +5 -1
  194. package/src/daemon/handlers/documents.ts +18 -29
  195. package/src/daemon/handlers/home-base.ts +5 -1
  196. package/src/daemon/handlers/index.ts +40 -271
  197. package/src/daemon/handlers/misc.ts +9 -1
  198. package/src/daemon/handlers/publish.ts +6 -1
  199. package/src/daemon/handlers/sessions.ts +65 -12
  200. package/src/daemon/handlers/shared.ts +36 -1
  201. package/src/daemon/handlers/signing.ts +37 -0
  202. package/src/daemon/handlers/skills.ts +20 -6
  203. package/src/daemon/handlers/subagents.ts +8 -3
  204. package/src/daemon/handlers/twitter-auth.ts +169 -0
  205. package/src/daemon/handlers/work-items.ts +495 -39
  206. package/src/daemon/ipc-contract-inventory.json +40 -4
  207. package/src/daemon/ipc-contract.ts +185 -37
  208. package/src/daemon/ipc-protocol.ts +7 -2
  209. package/src/daemon/lifecycle.ts +48 -5
  210. package/src/daemon/main.ts +10 -4
  211. package/src/daemon/ride-shotgun-handler.ts +74 -10
  212. package/src/daemon/server.ts +144 -29
  213. package/src/daemon/session-agent-loop.ts +887 -0
  214. package/src/daemon/session-attachments.ts +28 -5
  215. package/src/daemon/session-error.ts +24 -3
  216. package/src/daemon/session-lifecycle.ts +147 -0
  217. package/src/daemon/session-media-retry.ts +147 -0
  218. package/src/daemon/session-messaging.ts +145 -0
  219. package/src/daemon/session-notifiers.ts +164 -0
  220. package/src/daemon/session-process.ts +2 -2
  221. package/src/daemon/session-queue-manager.ts +1 -0
  222. package/src/daemon/session-runtime-assembly.ts +52 -0
  223. package/src/daemon/session-skill-tools.ts +124 -5
  224. package/src/daemon/session-slash.ts +3 -0
  225. package/src/daemon/session-surfaces.ts +77 -2
  226. package/src/daemon/session-tool-setup.ts +222 -2
  227. package/src/daemon/session-usage.ts +0 -2
  228. package/src/daemon/session.ts +114 -1365
  229. package/src/daemon/video-thumbnail.ts +60 -0
  230. package/src/doordash/client.ts +121 -27
  231. package/src/doordash/queries.ts +1 -2
  232. package/src/export/formatter.ts +3 -1
  233. package/src/followups/followup-store.ts +4 -2
  234. package/src/followups/types.ts +6 -0
  235. package/src/hooks/templates.ts +1 -1
  236. package/src/index.ts +32 -1151
  237. package/src/media/gemini-image-service.ts +1 -1
  238. package/src/memory/attachments-store.ts +28 -83
  239. package/src/memory/channel-delivery-store.ts +7 -21
  240. package/src/memory/clarification-resolver.ts +6 -5
  241. package/src/memory/contradiction-checker.ts +3 -2
  242. package/src/memory/conversation-key-store.ts +10 -29
  243. package/src/memory/conversation-store.ts +2 -1
  244. package/src/memory/db.ts +362 -2
  245. package/src/memory/entity-extractor.ts +6 -3
  246. package/src/memory/items-extractor.ts +5 -4
  247. package/src/memory/jobs-store.ts +3 -2
  248. package/src/memory/llm-usage-store.ts +1 -2
  249. package/src/memory/runs-store.ts +1 -2
  250. package/src/memory/schema.ts +65 -2
  251. package/src/messaging/style-analyzer.ts +3 -2
  252. package/src/messaging/thread-summarizer.ts +8 -12
  253. package/src/messaging/triage-engine.ts +4 -2
  254. package/src/providers/openrouter/client.ts +20 -0
  255. package/src/providers/registry.ts +8 -0
  256. package/src/runtime/http-server.ts +277 -25
  257. package/src/runtime/http-types.ts +0 -2
  258. package/src/runtime/routes/attachment-routes.ts +5 -6
  259. package/src/runtime/routes/call-routes.ts +140 -0
  260. package/src/runtime/routes/channel-routes.ts +12 -19
  261. package/src/runtime/routes/conversation-routes.ts +5 -9
  262. package/src/runtime/routes/run-routes.ts +4 -8
  263. package/src/runtime/run-orchestrator.ts +39 -6
  264. package/src/schedule/recurrence-engine.ts +138 -0
  265. package/src/schedule/recurrence-types.ts +67 -0
  266. package/src/schedule/schedule-store.ts +102 -57
  267. package/src/schedule/scheduler.ts +9 -6
  268. package/src/security/oauth2.ts +29 -4
  269. package/src/security/secret-allowlist.ts +46 -0
  270. package/src/skills/clawhub.ts +1 -1
  271. package/src/subagent/manager.ts +40 -8
  272. package/src/swarm/backend-claude-code.ts +64 -9
  273. package/src/swarm/worker-prompts.ts +2 -1
  274. package/src/tasks/SPEC.md +34 -28
  275. package/src/tasks/ephemeral-permissions.ts +16 -7
  276. package/src/tasks/task-compiler.ts +5 -4
  277. package/src/tasks/task-runner.ts +10 -5
  278. package/src/tasks/task-scheduler.ts +1 -1
  279. package/src/tasks/tool-sanitizer.ts +36 -0
  280. package/src/tools/assets/search.ts +4 -4
  281. package/src/tools/browser/api-map.ts +220 -0
  282. package/src/tools/browser/auto-navigate.ts +270 -0
  283. package/src/tools/browser/browser-execution.ts +2 -1
  284. package/src/tools/browser/browser-manager.ts +2 -2
  285. package/src/tools/browser/network-recorder.ts +5 -4
  286. package/src/tools/browser/x-auto-navigate.ts +207 -0
  287. package/src/tools/calls/call-end.ts +67 -0
  288. package/src/tools/calls/call-start.ts +73 -0
  289. package/src/tools/calls/call-status.ts +81 -0
  290. package/src/tools/claude-code/claude-code.ts +77 -11
  291. package/src/tools/contacts/contact-merge.ts +46 -78
  292. package/src/tools/contacts/contact-search.ts +35 -79
  293. package/src/tools/contacts/contact-upsert.ts +35 -108
  294. package/src/tools/credentials/vault.ts +21 -5
  295. package/src/tools/document/document-tool.ts +71 -144
  296. package/src/tools/executor.ts +129 -10
  297. package/src/tools/followups/followup_create.ts +46 -88
  298. package/src/tools/followups/followup_list.ts +34 -74
  299. package/src/tools/followups/followup_resolve.ts +31 -66
  300. package/src/tools/host-terminal/cli-discover.ts +2 -1
  301. package/src/tools/host-terminal/host-shell.ts +10 -0
  302. package/src/tools/memory/handlers.ts +5 -4
  303. package/src/tools/network/__tests__/web-search.test.ts +427 -0
  304. package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
  305. package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
  306. package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
  307. package/src/tools/network/web-fetch.ts +18 -6
  308. package/src/tools/playbooks/index.ts +4 -5
  309. package/src/tools/playbooks/playbook-create.ts +3 -47
  310. package/src/tools/playbooks/playbook-delete.ts +1 -25
  311. package/src/tools/playbooks/playbook-list.ts +1 -28
  312. package/src/tools/playbooks/playbook-update.ts +3 -51
  313. package/src/tools/registry.ts +2 -4
  314. package/src/tools/reminder/reminder.ts +5 -78
  315. package/src/tools/schedule/create.ts +69 -74
  316. package/src/tools/schedule/delete.ts +21 -47
  317. package/src/tools/schedule/list.ts +55 -74
  318. package/src/tools/schedule/update.ts +77 -84
  319. package/src/tools/subagent/abort.ts +29 -58
  320. package/src/tools/subagent/message.ts +30 -63
  321. package/src/tools/subagent/read.ts +53 -84
  322. package/src/tools/subagent/spawn.ts +43 -82
  323. package/src/tools/subagent/status.ts +42 -71
  324. package/src/tools/swarm/delegate.ts +2 -1
  325. package/src/tools/tasks/index.ts +8 -6
  326. package/src/tools/tasks/task-delete.ts +69 -56
  327. package/src/tools/tasks/task-list.ts +31 -52
  328. package/src/tools/tasks/task-run.ts +74 -102
  329. package/src/tools/tasks/task-save.ts +33 -65
  330. package/src/tools/tasks/work-item-enqueue.ts +192 -134
  331. package/src/tools/tasks/work-item-list.ts +33 -78
  332. package/src/tools/tasks/work-item-remove.ts +60 -0
  333. package/src/tools/tasks/work-item-update.ts +114 -0
  334. package/src/tools/terminal/backends/native.ts +3 -1
  335. package/src/tools/tool-manifest.ts +20 -74
  336. package/src/tools/types.ts +6 -0
  337. package/src/tools/ui-surface/definitions.ts +6 -1
  338. package/src/tools/watch/screen-watch.ts +3 -1
  339. package/src/tools/watcher/create.ts +52 -98
  340. package/src/tools/watcher/delete.ts +20 -46
  341. package/src/tools/watcher/digest.ts +36 -70
  342. package/src/tools/watcher/list.ts +49 -79
  343. package/src/tools/watcher/update.ts +45 -91
  344. package/src/twitter/client.ts +690 -0
  345. package/src/twitter/session.ts +91 -0
  346. package/src/usage/types.ts +0 -1
  347. package/src/util/truncate.ts +6 -0
  348. package/src/watcher/providers/slack.ts +2 -1
  349. package/src/watcher/watcher-store.ts +3 -2
  350. package/src/work-items/work-item-store.ts +236 -2
  351. package/src/workspace/commit-message-enrichment-service.ts +284 -0
  352. package/src/workspace/commit-message-provider.ts +95 -0
  353. package/src/workspace/git-service.ts +272 -52
  354. package/src/workspace/heartbeat-service.ts +70 -13
  355. package/src/workspace/provider-commit-message-generator.ts +242 -0
  356. package/src/workspace/turn-commit.ts +100 -51
  357. package/src/tools/contacts/index.ts +0 -4
  358. package/src/tools/document/index.ts +0 -5
  359. package/src/tools/followups/index.ts +0 -3
  360. package/src/tools/subagent/index.ts +0 -5
  361. /package/src/__tests__/{memory-context-benchmark.test.ts → memory-context-benchmark.benchmark.test.ts} +0 -0
package/scripts/test.sh CHANGED
@@ -36,6 +36,11 @@ while IFS= read -r test_file; do
36
36
  continue
37
37
  fi
38
38
  fi
39
+ # Always exclude benchmark files — run them with `bun run test:bench` instead
40
+ if [[ "$(basename "${test_file}")" == *.benchmark.test.ts ]]; then
41
+ continue
42
+ fi
43
+
39
44
  test_files+=("${test_file}")
40
45
  done < <(find src/__tests__ -maxdepth 1 -type f -name '*.test.ts' | sort)
41
46
 
@@ -87,6 +87,13 @@ exports[`IPC message snapshots ClientMessage types model_set serializes to expec
87
87
  }
88
88
  `;
89
89
 
90
+ exports[`IPC message snapshots ClientMessage types image_gen_model_set serializes to expected JSON 1`] = `
91
+ {
92
+ "model": "gemini-2.5-flash-image",
93
+ "type": "image_gen_model_set",
94
+ }
95
+ `;
96
+
90
97
  exports[`IPC message snapshots ClientMessage types history_request serializes to expected JSON 1`] = `
91
98
  {
92
99
  "sessionId": "sess-001",
@@ -530,6 +537,25 @@ exports[`IPC message snapshots ClientMessage types vercel_api_config serializes
530
537
  }
531
538
  `;
532
539
 
540
+ exports[`IPC message snapshots ClientMessage types twitter_integration_config serializes to expected JSON 1`] = `
541
+ {
542
+ "action": "get",
543
+ "type": "twitter_integration_config",
544
+ }
545
+ `;
546
+
547
+ exports[`IPC message snapshots ClientMessage types twitter_auth_start serializes to expected JSON 1`] = `
548
+ {
549
+ "type": "twitter_auth_start",
550
+ }
551
+ `;
552
+
553
+ exports[`IPC message snapshots ClientMessage types twitter_auth_status serializes to expected JSON 1`] = `
554
+ {
555
+ "type": "twitter_auth_status",
556
+ }
557
+ `;
558
+
533
559
  exports[`IPC message snapshots ClientMessage types link_open_request serializes to expected JSON 1`] = `
534
560
  {
535
561
  "type": "link_open_request",
@@ -661,17 +687,6 @@ exports[`IPC message snapshots ClientMessage types work_item_get serializes to e
661
687
  }
662
688
  `;
663
689
 
664
- exports[`IPC message snapshots ClientMessage types work_item_create serializes to expected JSON 1`] = `
665
- {
666
- "notes": "High priority",
667
- "priorityTier": 1,
668
- "sortIndex": 0,
669
- "taskId": "task-001",
670
- "title": "Process report",
671
- "type": "work_item_create",
672
- }
673
- `;
674
-
675
690
  exports[`IPC message snapshots ClientMessage types work_item_update serializes to expected JSON 1`] = `
676
691
  {
677
692
  "id": "wi-001",
@@ -688,6 +703,13 @@ exports[`IPC message snapshots ClientMessage types work_item_complete serializes
688
703
  }
689
704
  `;
690
705
 
706
+ exports[`IPC message snapshots ClientMessage types work_item_delete serializes to expected JSON 1`] = `
707
+ {
708
+ "id": "wi-001",
709
+ "type": "work_item_delete",
710
+ }
711
+ `;
712
+
691
713
  exports[`IPC message snapshots ClientMessage types work_item_run_task serializes to expected JSON 1`] = `
692
714
  {
693
715
  "id": "wi-001",
@@ -695,6 +717,38 @@ exports[`IPC message snapshots ClientMessage types work_item_run_task serializes
695
717
  }
696
718
  `;
697
719
 
720
+ exports[`IPC message snapshots ClientMessage types work_item_output serializes to expected JSON 1`] = `
721
+ {
722
+ "id": "wi-001",
723
+ "type": "work_item_output",
724
+ }
725
+ `;
726
+
727
+ exports[`IPC message snapshots ClientMessage types work_item_preflight serializes to expected JSON 1`] = `
728
+ {
729
+ "id": "wi-001",
730
+ "type": "work_item_preflight",
731
+ }
732
+ `;
733
+
734
+ exports[`IPC message snapshots ClientMessage types work_item_approve_permissions serializes to expected JSON 1`] = `
735
+ {
736
+ "approvedTools": [
737
+ "bash",
738
+ "file_write",
739
+ ],
740
+ "id": "wi-001",
741
+ "type": "work_item_approve_permissions",
742
+ }
743
+ `;
744
+
745
+ exports[`IPC message snapshots ClientMessage types work_item_cancel serializes to expected JSON 1`] = `
746
+ {
747
+ "id": "wi-001",
748
+ "type": "work_item_cancel",
749
+ }
750
+ `;
751
+
698
752
  exports[`IPC message snapshots ClientMessage types document_save serializes to expected JSON 1`] = `
699
753
  {
700
754
  "content": "# Hello",
@@ -729,7 +783,6 @@ exports[`IPC message snapshots ClientMessage types subagent_abort serializes to
729
783
 
730
784
  exports[`IPC message snapshots ClientMessage types subagent_status serializes to expected JSON 1`] = `
731
785
  {
732
- "sessionId": "sess-001",
733
786
  "subagentId": "sub-001",
734
787
  "type": "subagent_status",
735
788
  }
@@ -1437,12 +1490,14 @@ exports[`IPC message snapshots ServerMessage types schedules_list_response seria
1437
1490
  "cronExpression": "0 9 * * 1-5",
1438
1491
  "description": "Every weekday at 9:00 AM",
1439
1492
  "enabled": true,
1493
+ "expression": "0 9 * * 1-5",
1440
1494
  "id": "sched-001",
1441
1495
  "lastRunAt": 1700000000000,
1442
1496
  "lastStatus": "ok",
1443
1497
  "message": "Remind me about the standup",
1444
1498
  "name": "Daily standup reminder",
1445
1499
  "nextRunAt": 1700100000000,
1500
+ "syntax": "cron",
1446
1501
  "timezone": "America/Los_Angeles",
1447
1502
  },
1448
1503
  ],
@@ -1723,6 +1778,34 @@ exports[`IPC message snapshots ServerMessage types vercel_api_config_response se
1723
1778
  }
1724
1779
  `;
1725
1780
 
1781
+ exports[`IPC message snapshots ServerMessage types twitter_integration_config_response serializes to expected JSON 1`] = `
1782
+ {
1783
+ "connected": false,
1784
+ "localClientConfigured": true,
1785
+ "managedAvailable": false,
1786
+ "mode": "local_byo",
1787
+ "success": true,
1788
+ "type": "twitter_integration_config_response",
1789
+ }
1790
+ `;
1791
+
1792
+ exports[`IPC message snapshots ServerMessage types twitter_auth_result serializes to expected JSON 1`] = `
1793
+ {
1794
+ "accountInfo": "@vellum_test",
1795
+ "success": true,
1796
+ "type": "twitter_auth_result",
1797
+ }
1798
+ `;
1799
+
1800
+ exports[`IPC message snapshots ServerMessage types twitter_auth_status_response serializes to expected JSON 1`] = `
1801
+ {
1802
+ "accountInfo": "@vellum_test",
1803
+ "connected": true,
1804
+ "mode": "local_byo",
1805
+ "type": "twitter_auth_status_response",
1806
+ }
1807
+ `;
1808
+
1726
1809
  exports[`IPC message snapshots ServerMessage types open_url serializes to expected JSON 1`] = `
1727
1810
  {
1728
1811
  "title": "Example",
@@ -1973,28 +2056,6 @@ exports[`IPC message snapshots ServerMessage types work_item_get_response serial
1973
2056
  }
1974
2057
  `;
1975
2058
 
1976
- exports[`IPC message snapshots ServerMessage types work_item_create_response serializes to expected JSON 1`] = `
1977
- {
1978
- "item": {
1979
- "createdAt": 1700000000,
1980
- "id": "wi-001",
1981
- "lastRunConversationId": null,
1982
- "lastRunId": null,
1983
- "lastRunStatus": null,
1984
- "notes": null,
1985
- "priorityTier": 1,
1986
- "sortIndex": null,
1987
- "sourceId": null,
1988
- "sourceType": null,
1989
- "status": "queued",
1990
- "taskId": "task-001",
1991
- "title": "Process report",
1992
- "updatedAt": 1700000000,
1993
- },
1994
- "type": "work_item_create_response",
1995
- }
1996
- `;
1997
-
1998
2059
  exports[`IPC message snapshots ServerMessage types work_item_update_response serializes to expected JSON 1`] = `
1999
2060
  {
2000
2061
  "item": {
@@ -2017,6 +2078,14 @@ exports[`IPC message snapshots ServerMessage types work_item_update_response ser
2017
2078
  }
2018
2079
  `;
2019
2080
 
2081
+ exports[`IPC message snapshots ServerMessage types work_item_delete_response serializes to expected JSON 1`] = `
2082
+ {
2083
+ "id": "wi-001",
2084
+ "success": true,
2085
+ "type": "work_item_delete_response",
2086
+ }
2087
+ `;
2088
+
2020
2089
  exports[`IPC message snapshots ServerMessage types work_item_run_task_response serializes to expected JSON 1`] = `
2021
2090
  {
2022
2091
  "id": "wi-001",
@@ -2026,6 +2095,58 @@ exports[`IPC message snapshots ServerMessage types work_item_run_task_response s
2026
2095
  }
2027
2096
  `;
2028
2097
 
2098
+ exports[`IPC message snapshots ServerMessage types work_item_output_response serializes to expected JSON 1`] = `
2099
+ {
2100
+ "id": "wi-001",
2101
+ "output": {
2102
+ "completedAt": 1700002000,
2103
+ "conversationId": "conv-001",
2104
+ "highlights": [
2105
+ "- Key finding 1",
2106
+ "- Key finding 2",
2107
+ ],
2108
+ "runId": "run-001",
2109
+ "status": "completed",
2110
+ "summary": "Report processed successfully.",
2111
+ "title": "Process report",
2112
+ },
2113
+ "success": true,
2114
+ "type": "work_item_output_response",
2115
+ }
2116
+ `;
2117
+
2118
+ exports[`IPC message snapshots ServerMessage types work_item_preflight_response serializes to expected JSON 1`] = `
2119
+ {
2120
+ "id": "wi-001",
2121
+ "permissions": [
2122
+ {
2123
+ "currentDecision": "prompt",
2124
+ "description": "Run shell commands",
2125
+ "riskLevel": "medium",
2126
+ "tool": "bash",
2127
+ },
2128
+ ],
2129
+ "success": true,
2130
+ "type": "work_item_preflight_response",
2131
+ }
2132
+ `;
2133
+
2134
+ exports[`IPC message snapshots ServerMessage types work_item_approve_permissions_response serializes to expected JSON 1`] = `
2135
+ {
2136
+ "id": "wi-001",
2137
+ "success": true,
2138
+ "type": "work_item_approve_permissions_response",
2139
+ }
2140
+ `;
2141
+
2142
+ exports[`IPC message snapshots ServerMessage types work_item_cancel_response serializes to expected JSON 1`] = `
2143
+ {
2144
+ "id": "wi-001",
2145
+ "success": true,
2146
+ "type": "work_item_cancel_response",
2147
+ }
2148
+ `;
2149
+
2029
2150
  exports[`IPC message snapshots ServerMessage types work_item_status_changed serializes to expected JSON 1`] = `
2030
2151
  {
2031
2152
  "item": {
@@ -2042,6 +2163,12 @@ exports[`IPC message snapshots ServerMessage types work_item_status_changed seri
2042
2163
  }
2043
2164
  `;
2044
2165
 
2166
+ exports[`IPC message snapshots ServerMessage types tasks_changed serializes to expected JSON 1`] = `
2167
+ {
2168
+ "type": "tasks_changed",
2169
+ }
2170
+ `;
2171
+
2045
2172
  exports[`IPC message snapshots ServerMessage types open_tasks_window serializes to expected JSON 1`] = `
2046
2173
  {
2047
2174
  "type": "open_tasks_window",
@@ -28,7 +28,7 @@ mock.module('../tools/registry.js', () => ({
28
28
  registerTool: () => {},
29
29
  }));
30
30
 
31
- import { initializeDb, getDb } from '../memory/db.js';
31
+ import { initializeDb, getDb, resetDb } from '../memory/db.js';
32
32
  import {
33
33
  createAccount,
34
34
  listAccounts,
@@ -47,6 +47,7 @@ const _ctx: ToolContext = {
47
47
  };
48
48
 
49
49
  afterAll(() => {
50
+ resetDb();
50
51
  mock.restore();
51
52
  try { rmSync(testDir, { recursive: true }); } catch { /* best effort */ }
52
53
  });
@@ -0,0 +1,250 @@
1
+ import { describe, test, expect, beforeEach, mock } from 'bun:test';
2
+ import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+
6
+ // Mock platform to use a temp workspace dir
7
+ let testWorkspaceDir: string;
8
+
9
+ mock.module('../util/platform.js', () => ({
10
+ getWorkspacePromptPath: (file: string) => join(testWorkspaceDir, file),
11
+ }));
12
+
13
+ // Mock config loader
14
+ let mockConfig = {
15
+ agentHeartbeat: {
16
+ enabled: true,
17
+ intervalMs: 60_000,
18
+ activeHoursStart: undefined as number | undefined,
19
+ activeHoursEnd: undefined as number | undefined,
20
+ },
21
+ };
22
+
23
+ mock.module('../config/loader.js', () => ({
24
+ getConfig: () => mockConfig,
25
+ loadConfig: () => mockConfig,
26
+ }));
27
+
28
+ // Mock conversation store
29
+ const createdConversations: Array<{ title: string; threadType: string }> = [];
30
+ let conversationIdCounter = 0;
31
+
32
+ mock.module('../memory/conversation-store.js', () => ({
33
+ createConversation: (opts: { title: string; threadType: string }) => {
34
+ createdConversations.push(opts);
35
+ return { id: `conv-${++conversationIdCounter}`, ...opts };
36
+ },
37
+ }));
38
+
39
+ // Mock logger
40
+ mock.module('../util/logger.js', () => ({
41
+ getLogger: () => ({
42
+ info: () => {},
43
+ debug: () => {},
44
+ warn: () => {},
45
+ error: () => {},
46
+ }),
47
+ }));
48
+
49
+ // Import after mocks are set up
50
+ const { AgentHeartbeatService } = await import('../agent-heartbeat/agent-heartbeat-service.js');
51
+
52
+ describe('AgentHeartbeatService', () => {
53
+ let processMessageCalls: Array<{ conversationId: string; content: string }>;
54
+ let alerterCalls: Array<{ type: string; title: string; body: string }>;
55
+
56
+ beforeEach(() => {
57
+ testWorkspaceDir = join(tmpdir(), `vellum-agent-hb-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
58
+ mkdirSync(testWorkspaceDir, { recursive: true });
59
+
60
+ processMessageCalls = [];
61
+ alerterCalls = [];
62
+ createdConversations.length = 0;
63
+ conversationIdCounter = 0;
64
+
65
+ mockConfig = {
66
+ agentHeartbeat: {
67
+ enabled: true,
68
+ intervalMs: 60_000,
69
+ activeHoursStart: undefined,
70
+ activeHoursEnd: undefined,
71
+ },
72
+ };
73
+ });
74
+
75
+ function createService(overrides?: {
76
+ processMessage?: (id: string, content: string) => Promise<{ messageId: string }>;
77
+ getCurrentHour?: () => number;
78
+ }) {
79
+ return new AgentHeartbeatService({
80
+ processMessage: overrides?.processMessage ?? (async (conversationId: string, content: string) => {
81
+ processMessageCalls.push({ conversationId, content });
82
+ return { messageId: 'msg-1' };
83
+ }),
84
+ alerter: (alert: { type: string; title: string; body: string }) => {
85
+ alerterCalls.push(alert);
86
+ },
87
+ getCurrentHour: overrides?.getCurrentHour,
88
+ });
89
+ }
90
+
91
+ test('runOnce() calls processMessage with correct prompt', async () => {
92
+ const service = createService();
93
+ await service.runOnce();
94
+
95
+ expect(processMessageCalls).toHaveLength(1);
96
+ expect(processMessageCalls[0].conversationId).toBe('conv-1');
97
+ expect(processMessageCalls[0].content).toContain('<heartbeat-checklist>');
98
+ expect(processMessageCalls[0].content).toContain('<heartbeat-disposition>');
99
+ expect(processMessageCalls[0].content).toContain('HEARTBEAT_OK');
100
+ expect(processMessageCalls[0].content).toContain('HEARTBEAT_ALERT');
101
+ });
102
+
103
+ test('HEARTBEAT.md content is embedded in prompt when file exists', async () => {
104
+ const customChecklist = '- Check the weather\n- Water the plants';
105
+ writeFileSync(join(testWorkspaceDir, 'HEARTBEAT.md'), customChecklist);
106
+
107
+ const service = createService();
108
+ await service.runOnce();
109
+
110
+ expect(processMessageCalls).toHaveLength(1);
111
+ expect(processMessageCalls[0].content).toContain('Check the weather');
112
+ expect(processMessageCalls[0].content).toContain('Water the plants');
113
+ });
114
+
115
+ test('default checklist used when no HEARTBEAT.md', async () => {
116
+ const service = createService();
117
+ await service.runOnce();
118
+
119
+ expect(processMessageCalls).toHaveLength(1);
120
+ expect(processMessageCalls[0].content).toContain('Check the current weather');
121
+ });
122
+
123
+ test('creates background conversation titled "Agent Heartbeat"', async () => {
124
+ const service = createService();
125
+ await service.runOnce();
126
+
127
+ expect(createdConversations).toHaveLength(1);
128
+ expect(createdConversations[0].title).toBe('Agent Heartbeat');
129
+ expect(createdConversations[0].threadType).toBe('background');
130
+ });
131
+
132
+ test('active hours guard skips outside window', async () => {
133
+ mockConfig.agentHeartbeat.activeHoursStart = 9;
134
+ mockConfig.agentHeartbeat.activeHoursEnd = 17;
135
+
136
+ const service = createService({ getCurrentHour: () => 3 });
137
+ await service.runOnce();
138
+
139
+ expect(processMessageCalls).toHaveLength(0);
140
+ });
141
+
142
+ test('active hours guard allows within window', async () => {
143
+ mockConfig.agentHeartbeat.activeHoursStart = 9;
144
+ mockConfig.agentHeartbeat.activeHoursEnd = 17;
145
+
146
+ const service = createService({ getCurrentHour: () => 12 });
147
+ await service.runOnce();
148
+
149
+ expect(processMessageCalls).toHaveLength(1);
150
+ });
151
+
152
+ test('active hours handles overnight window', async () => {
153
+ mockConfig.agentHeartbeat.activeHoursStart = 22;
154
+ mockConfig.agentHeartbeat.activeHoursEnd = 6;
155
+
156
+ // 23:00 should be within the window
157
+ const service = createService({ getCurrentHour: () => 23 });
158
+ await service.runOnce();
159
+ expect(processMessageCalls).toHaveLength(1);
160
+
161
+ // 10:00 should be outside the window
162
+ processMessageCalls.length = 0;
163
+ createdConversations.length = 0;
164
+ const service2 = createService({ getCurrentHour: () => 10 });
165
+ await service2.runOnce();
166
+ expect(processMessageCalls).toHaveLength(0);
167
+ });
168
+
169
+ test('overlap prevention works', async () => {
170
+ let resolveFirst: () => void;
171
+ const firstPromise = new Promise<void>((r) => { resolveFirst = r; });
172
+
173
+ const service = createService({
174
+ processMessage: async () => {
175
+ await firstPromise;
176
+ processMessageCalls.push({ conversationId: 'slow', content: 'slow' });
177
+ return { messageId: 'msg-1' };
178
+ },
179
+ });
180
+
181
+ // Start first run (will block)
182
+ const run1 = service.runOnce();
183
+ // Give the first run a tick to set activeRun
184
+ await new Promise((r) => setTimeout(r, 10));
185
+
186
+ // Second run should be skipped due to overlap
187
+ await service.runOnce();
188
+
189
+ // Resolve the first run
190
+ resolveFirst!();
191
+ await run1;
192
+
193
+ // Only the first run should have called processMessage
194
+ expect(processMessageCalls).toHaveLength(1);
195
+ });
196
+
197
+ test('disabled config prevents start', () => {
198
+ mockConfig.agentHeartbeat.enabled = false;
199
+ const service = createService();
200
+ service.start();
201
+ // No error, just a no-op. We can verify by calling stop which should also be a no-op.
202
+ // The key assertion is that no timer is set (verified by stop not hanging).
203
+ service.stop();
204
+ });
205
+
206
+ test('disabled config prevents runOnce', async () => {
207
+ mockConfig.agentHeartbeat.enabled = false;
208
+ const service = createService();
209
+ await service.runOnce();
210
+
211
+ expect(processMessageCalls).toHaveLength(0);
212
+ });
213
+
214
+ test('alerts on processMessage failure', async () => {
215
+ const service = createService({
216
+ processMessage: async () => {
217
+ throw new Error('LLM timeout');
218
+ },
219
+ });
220
+
221
+ await service.runOnce();
222
+
223
+ expect(alerterCalls).toHaveLength(1);
224
+ expect(alerterCalls[0].type).toBe('agent_heartbeat_alert');
225
+ expect(alerterCalls[0].title).toBe('Agent Heartbeat Failed');
226
+ expect(alerterCalls[0].body).toBe('LLM timeout');
227
+ });
228
+
229
+ test('alerts on conversation creation failure', async () => {
230
+ // Override createConversation to throw via a fresh import trick:
231
+ // Since createConversation is mocked at module level, we simulate
232
+ // this by having processMessage throw before it's called — but the
233
+ // real fix is that executeRun wraps createConversation in the try/catch.
234
+ // We verify by checking that any error in executeRun triggers the alert.
235
+ const service = createService({
236
+ processMessage: async () => {
237
+ throw new Error('DB locked');
238
+ },
239
+ });
240
+
241
+ await service.runOnce();
242
+
243
+ expect(alerterCalls).toHaveLength(1);
244
+ expect(alerterCalls[0].body).toBe('DB locked');
245
+ });
246
+
247
+ test('cleanup', () => {
248
+ try { rmSync(testWorkspaceDir, { recursive: true, force: true }); } catch { /* ignore */ }
249
+ });
250
+ });
@@ -1,5 +1,4 @@
1
1
  import { describe, test, expect, mock, beforeEach, afterEach } from 'bun:test';
2
- import { createHash } from 'node:crypto';
3
2
 
4
3
  // Mock the logger before importing the module under test
5
4
  mock.module('../util/logger.js', () => ({
@@ -11,27 +10,6 @@ mock.module('../util/logger.js', () => ({
11
10
 
12
11
  import { extractRemoteUrls, materializeAssets } from '../bundler/app-bundler.js';
13
12
 
14
- // ---------------------------------------------------------------------------
15
- // Helpers
16
- // ---------------------------------------------------------------------------
17
-
18
- /** Compute expected asset filename for a URL (mirrors the production logic). */
19
- function expectedFilename(url: string): string {
20
- const hash = createHash('sha256').update(url).digest('hex').slice(0, 12);
21
- let ext = '';
22
- try {
23
- const parsed = new URL(url);
24
- const match = parsed.pathname.match(/\.\w+$/);
25
- ext = match ? match[0] : '';
26
- } catch {
27
- // no extension
28
- }
29
- if (!ext || ext.length > 10 || !/^\.\w+$/.test(ext)) {
30
- ext = '';
31
- }
32
- return `${hash}${ext}`;
33
- }
34
-
35
13
  // ---------------------------------------------------------------------------
36
14
  // extractRemoteUrls
37
15
  // ---------------------------------------------------------------------------
@@ -167,10 +145,9 @@ describe('materializeAssets', () => {
167
145
  const html = `<img src="${imageUrl}">`;
168
146
  const result = await materializeAssets(html);
169
147
 
170
- const filename = expectedFilename(imageUrl);
171
- expect(result.rewrittenHtml).toBe(`<img src="assets/${filename}">`);
148
+ expect(result.rewrittenHtml).toBe('<img src="assets/e724846245db.png">');
172
149
  expect(result.assets).toHaveLength(1);
173
- expect(result.assets[0].archivePath).toBe(`assets/${filename}`);
150
+ expect(result.assets[0].archivePath).toBe('assets/e724846245db.png');
174
151
  expect(result.assets[0].data).toEqual(imageData);
175
152
  });
176
153
 
@@ -193,9 +170,14 @@ describe('materializeAssets', () => {
193
170
  const result = await materializeAssets(html);
194
171
 
195
172
  expect(result.assets).toHaveLength(3);
173
+
174
+ const expectedFilenames: Record<string, string> = {
175
+ 'https://cdn.example.com/a.png': '6155f67efa62.png',
176
+ 'https://cdn.example.com/b.css': '5e6d8d571910.css',
177
+ 'https://cdn.example.com/c.js': '20fb1ea9b4c9.js',
178
+ };
196
179
  for (const url of urls) {
197
- const filename = expectedFilename(url);
198
- expect(result.rewrittenHtml).toContain(`assets/${filename}`);
180
+ expect(result.rewrittenHtml).toContain(`assets/${expectedFilenames[url]}`);
199
181
  expect(result.rewrittenHtml).not.toContain(url);
200
182
  }
201
183
  });
@@ -242,8 +224,7 @@ describe('materializeAssets', () => {
242
224
  const html = `<img src="${goodUrl}"><img src="${badUrl}">`;
243
225
  const result = await materializeAssets(html);
244
226
 
245
- const goodFilename = expectedFilename(goodUrl);
246
- expect(result.rewrittenHtml).toContain(`assets/${goodFilename}`);
227
+ expect(result.rewrittenHtml).toContain('assets/691e2a787421.png');
247
228
  expect(result.rewrittenHtml).toContain(badUrl);
248
229
  expect(result.assets).toHaveLength(1);
249
230
  });
@@ -265,9 +246,8 @@ describe('materializeAssets', () => {
265
246
  expect(result.assets).toHaveLength(1);
266
247
 
267
248
  // Both occurrences should be rewritten
268
- const filename = expectedFilename(imageUrl);
269
249
  expect(result.rewrittenHtml).not.toContain(imageUrl);
270
- const matches = result.rewrittenHtml.match(new RegExp(`assets/${filename}`, 'g'));
250
+ const matches = result.rewrittenHtml.match(/assets\/2f7fc0f99275\.png/g);
271
251
  expect(matches).toHaveLength(2);
272
252
  });
273
253
 
@@ -306,8 +286,7 @@ describe('materializeAssets', () => {
306
286
  const html = '<style>body { background: url("https://cdn.example.com/bg.jpg"); }</style>';
307
287
  const result = await materializeAssets(html);
308
288
 
309
- const filename = expectedFilename(cssUrl);
310
- expect(result.rewrittenHtml).toContain(`assets/${filename}`);
289
+ expect(result.rewrittenHtml).toContain('assets/8550eecd4975.jpg');
311
290
  expect(result.rewrittenHtml).not.toContain(cssUrl);
312
291
  });
313
292
  });