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
@@ -0,0 +1,284 @@
1
+ /**
2
+ * Non-blocking enrichment queue for post-commit message enhancement.
3
+ *
4
+ * After a synchronous commit succeeds, callers can enqueue enrichment jobs
5
+ * that run asynchronously without blocking the commit path. This is the
6
+ * scaffold for future LLM-powered commit message enrichment.
7
+ *
8
+ * Key properties:
9
+ * - Bounded queue with configurable max size (drops oldest on overflow)
10
+ * - Bounded concurrency (default 1 worker)
11
+ * - Per-job timeout with retry + exponential backoff
12
+ * - Graceful shutdown: drains in-flight jobs, discards pending jobs
13
+ * - Fire-and-forget: enqueue() never blocks or throws
14
+ */
15
+
16
+ import { getLogger } from '../util/logger.js';
17
+ import { getConfig } from '../config/loader.js';
18
+ import type { WorkspaceGitService } from './git-service.js';
19
+ import type { CommitContext } from './commit-message-provider.js';
20
+
21
+ const log = getLogger('enrichment-queue');
22
+
23
+ export interface EnrichmentJob {
24
+ workspaceDir: string;
25
+ commitHash: string;
26
+ context: CommitContext;
27
+ gitService: WorkspaceGitService;
28
+ }
29
+
30
+ interface InternalJob extends EnrichmentJob {
31
+ attempts: number;
32
+ }
33
+
34
+ export interface EnrichmentServiceOptions {
35
+ maxQueueSize?: number;
36
+ maxConcurrency?: number;
37
+ jobTimeoutMs?: number;
38
+ maxRetries?: number;
39
+ }
40
+
41
+ /**
42
+ * Non-blocking enrichment queue service.
43
+ *
44
+ * Enqueue jobs after successful commits. Each job runs the enrichment
45
+ * worker (currently a no-op placeholder) and writes the result as a
46
+ * git note on the commit.
47
+ */
48
+ export class CommitEnrichmentService {
49
+ private readonly maxQueueSize: number;
50
+ private readonly maxConcurrency: number;
51
+ private readonly jobTimeoutMs: number;
52
+ private readonly maxRetries: number;
53
+
54
+ private queue: InternalJob[] = [];
55
+ private activeWorkers = 0;
56
+ private droppedCount = 0;
57
+ private succeededCount = 0;
58
+ private failedCount = 0;
59
+ private shuttingDown = false;
60
+ private inFlightPromises: Set<Promise<void>> = new Set();
61
+
62
+ constructor(options?: EnrichmentServiceOptions) {
63
+ const config = getConfig();
64
+ const gitConfig = config.workspaceGit;
65
+ this.maxQueueSize = options?.maxQueueSize ?? gitConfig?.enrichmentQueueSize ?? 50;
66
+ this.maxConcurrency = options?.maxConcurrency ?? gitConfig?.enrichmentConcurrency ?? 1;
67
+ this.jobTimeoutMs = options?.jobTimeoutMs ?? gitConfig?.enrichmentJobTimeoutMs ?? 30000;
68
+ this.maxRetries = options?.maxRetries ?? gitConfig?.enrichmentMaxRetries ?? 2;
69
+ }
70
+
71
+ /**
72
+ * Enqueue an enrichment job. Fire-and-forget — never blocks or throws.
73
+ */
74
+ enqueue(job: EnrichmentJob): void {
75
+ if (this.shuttingDown) {
76
+ log.debug({ commitHash: job.commitHash }, 'Enrichment queue shutting down, discarding job');
77
+ return;
78
+ }
79
+
80
+ const internalJob: InternalJob = { ...job, attempts: 0 };
81
+
82
+ // Drop oldest if queue is full
83
+ if (this.queue.length >= this.maxQueueSize) {
84
+ const dropped = this.queue.shift()!;
85
+ this.droppedCount++;
86
+ log.warn(
87
+ { droppedHash: dropped.commitHash, queueSize: this.queue.length, droppedCount: this.droppedCount },
88
+ 'Enrichment queue full, dropping oldest job',
89
+ );
90
+ }
91
+
92
+ this.queue.push(internalJob);
93
+ log.debug(
94
+ { commitHash: job.commitHash, queueSize: this.queue.length },
95
+ 'Enrichment job enqueued',
96
+ );
97
+
98
+ this.processNext();
99
+ }
100
+
101
+ /**
102
+ * Graceful shutdown: discard pending queue and wait for in-flight jobs only.
103
+ *
104
+ * Bounded shutdown time is more important than processing all pending
105
+ * enrichments. Enrichment is best-effort metadata and must never delay
106
+ * daemon shutdown materially. Pending jobs are counted as dropped.
107
+ */
108
+ async shutdown(): Promise<void> {
109
+ this.shuttingDown = true;
110
+
111
+ // Discard pending jobs — enrichment is best-effort and must not delay shutdown
112
+ if (this.queue.length > 0) {
113
+ const pendingCount = this.queue.length;
114
+ this.droppedCount += pendingCount;
115
+ this.queue = [];
116
+ log.info({ discarded: pendingCount, droppedCount: this.droppedCount }, 'Enrichment queue shutting down, discarded pending jobs');
117
+ }
118
+
119
+ // Wait for any in-flight workers to finish
120
+ if (this.inFlightPromises.size > 0) {
121
+ log.debug({ inFlight: this.inFlightPromises.size }, 'Waiting for in-flight enrichment jobs');
122
+ await Promise.all(this.inFlightPromises);
123
+ }
124
+
125
+ log.info(
126
+ { succeeded: this.succeededCount, failed: this.failedCount, dropped: this.droppedCount },
127
+ 'Enrichment queue shut down',
128
+ );
129
+ }
130
+
131
+ /** @internal Test-only: get queue size */
132
+ _getQueueSize(): number {
133
+ return this.queue.length;
134
+ }
135
+
136
+ /** @internal Test-only: get dropped count */
137
+ _getDroppedCount(): number {
138
+ return this.droppedCount;
139
+ }
140
+
141
+ /** @internal Test-only: get succeeded count */
142
+ _getSucceededCount(): number {
143
+ return this.succeededCount;
144
+ }
145
+
146
+ /** @internal Test-only: get failed count */
147
+ _getFailedCount(): number {
148
+ return this.failedCount;
149
+ }
150
+
151
+ /** @internal Test-only: get active workers */
152
+ _getActiveWorkers(): number {
153
+ return this.activeWorkers;
154
+ }
155
+
156
+ private processNext(): void {
157
+ if (this.shuttingDown) return;
158
+ if (this.activeWorkers >= this.maxConcurrency) return;
159
+ if (this.queue.length === 0) return;
160
+
161
+ const job = this.queue.shift()!;
162
+ this.activeWorkers++;
163
+
164
+ const promise = this.executeJob(job).finally(() => {
165
+ this.activeWorkers--;
166
+ this.inFlightPromises.delete(promise);
167
+ // Try to process next job after this one completes
168
+ this.processNext();
169
+ });
170
+
171
+ this.inFlightPromises.add(promise);
172
+ }
173
+
174
+ private async executeJob(job: InternalJob): Promise<void> {
175
+ job.attempts++;
176
+
177
+ const controller = new AbortController();
178
+ let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
179
+ try {
180
+ // Race the enrichment work against a timeout.
181
+ // When the timeout wins, controller.abort() kills in-progress work,
182
+ // causing doEnrichment to reject with an AbortError. Since Promise.race
183
+ // has already settled with the timeout error, that rejection is orphaned.
184
+ // The .catch() swallows it to prevent an unhandled promise rejection.
185
+ const enrichmentPromise = this.doEnrichment(job, controller.signal);
186
+ await Promise.race([
187
+ enrichmentPromise,
188
+ new Promise<never>((_, reject) => {
189
+ timeoutHandle = setTimeout(() => {
190
+ controller.abort();
191
+ reject(new Error('Enrichment job timed out'));
192
+ }, this.jobTimeoutMs);
193
+ }),
194
+ ]);
195
+ enrichmentPromise.catch(() => {
196
+ // Intentionally swallowed — the timeout branch already handled the error
197
+ });
198
+ this.succeededCount++;
199
+ log.debug(
200
+ { commitHash: job.commitHash, attempts: job.attempts },
201
+ 'Enrichment job completed',
202
+ );
203
+ } catch (err) {
204
+ controller.abort();
205
+ const isTimeout = err instanceof Error && err.message === 'Enrichment job timed out';
206
+ if (job.attempts <= this.maxRetries) {
207
+ // Exponential backoff: 1s, 2s, 4s, ...
208
+ const backoffMs = 1000 * Math.pow(2, job.attempts - 1);
209
+ log.debug(
210
+ { commitHash: job.commitHash, attempts: job.attempts, backoffMs, timedOut: isTimeout, err },
211
+ isTimeout ? 'Enrichment job timed out, scheduling retry' : 'Enrichment job failed, scheduling retry',
212
+ );
213
+ await new Promise<void>((resolve) => setTimeout(resolve, backoffMs));
214
+
215
+ if (!this.shuttingDown) {
216
+ // Re-enqueue at front for retry (don't count against queue limit)
217
+ this.queue.unshift(job);
218
+ this.processNext();
219
+ } else {
220
+ // Can't retry during shutdown — count as failed
221
+ this.failedCount++;
222
+ }
223
+ return;
224
+ }
225
+
226
+ this.failedCount++;
227
+ log.warn(
228
+ { commitHash: job.commitHash, attempts: job.attempts, timedOut: isTimeout, err },
229
+ isTimeout ? 'Enrichment job timed out after max retries' : 'Enrichment job failed after max retries',
230
+ );
231
+ } finally {
232
+ if (timeoutHandle !== undefined) {
233
+ clearTimeout(timeoutHandle);
234
+ }
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Perform the actual enrichment work.
240
+ *
241
+ * Currently a no-op placeholder that writes a scaffold JSON note
242
+ * to prove the plumbing works. Future: call LLM to generate a
243
+ * rich commit description and write it as a git note.
244
+ *
245
+ * Accepts an AbortSignal so callers (e.g. timeout) can cancel
246
+ * in-progress work and prevent zombie enrichment jobs.
247
+ */
248
+ private async doEnrichment(job: InternalJob, signal?: AbortSignal): Promise<void> {
249
+ if (signal?.aborted) return;
250
+
251
+ const note = JSON.stringify({
252
+ enriched: true,
253
+ trigger: job.context.trigger,
254
+ filesChanged: job.context.changedFiles.length,
255
+ timestamp: job.context.timestampMs,
256
+ sessionId: job.context.sessionId,
257
+ turnNumber: job.context.turnNumber,
258
+ });
259
+
260
+ if (signal?.aborted) return;
261
+ await job.gitService.writeNote(job.commitHash, note, signal);
262
+ }
263
+ }
264
+
265
+ /** Singleton enrichment service instance. */
266
+ let enrichmentService: CommitEnrichmentService | null = null;
267
+
268
+ /**
269
+ * Get the global enrichment service singleton.
270
+ * Created lazily on first access.
271
+ */
272
+ export function getEnrichmentService(): CommitEnrichmentService {
273
+ if (!enrichmentService) {
274
+ enrichmentService = new CommitEnrichmentService();
275
+ }
276
+ return enrichmentService;
277
+ }
278
+
279
+ /**
280
+ * @internal Test-only: reset the singleton
281
+ */
282
+ export function _resetEnrichmentService(): void {
283
+ enrichmentService = null;
284
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Abstraction for generating commit messages across different triggers.
3
+ *
4
+ * Provides a seam for future LLM-powered enrichment without changing
5
+ * the synchronous commit path.
6
+ */
7
+
8
+ export interface CommitContext {
9
+ workspaceDir: string;
10
+ trigger: 'turn' | 'heartbeat' | 'shutdown';
11
+ sessionId?: string;
12
+ turnNumber?: number;
13
+ changedFiles: string[];
14
+ timestampMs: number;
15
+ /** Optional reason string (used by heartbeat to describe threshold exceeded). */
16
+ reason?: string;
17
+ }
18
+
19
+ export interface CommitMessageResult {
20
+ message: string;
21
+ metadata?: Record<string, unknown>;
22
+ }
23
+
24
+ export interface CommitMessageProvider {
25
+ /** Build a commit message synchronously for immediate use. */
26
+ buildImmediateMessage(ctx: CommitContext): CommitMessageResult;
27
+ /** Optional: enqueue async enrichment after commit succeeds. */
28
+ enqueueEnrichment?(ctx: CommitContext & { commitHash: string }): Promise<void>;
29
+ }
30
+
31
+ /**
32
+ * Build a short summary of what changed from a list of file paths.
33
+ */
34
+ export function buildChangeSummary(files: string[]): string {
35
+ if (files.length === 0) {
36
+ return 'workspace changes';
37
+ }
38
+ if (files.length === 1) {
39
+ return files[0];
40
+ }
41
+ if (files.length <= 3) {
42
+ return files.join(', ');
43
+ }
44
+ return `${files.slice(0, 2).join(', ')} and ${files.length - 2} more`;
45
+ }
46
+
47
+ /**
48
+ * Default deterministic commit message provider.
49
+ *
50
+ * Produces identical output to the pre-refactor inline logic in
51
+ * turn-commit.ts and heartbeat-service.ts.
52
+ */
53
+ export class DefaultCommitMessageProvider implements CommitMessageProvider {
54
+ buildImmediateMessage(ctx: CommitContext): CommitMessageResult {
55
+ switch (ctx.trigger) {
56
+ case 'turn':
57
+ return this.buildTurnMessage(ctx);
58
+ case 'heartbeat':
59
+ return this.buildHeartbeatMessage(ctx);
60
+ case 'shutdown':
61
+ return this.buildShutdownMessage(ctx);
62
+ }
63
+ }
64
+
65
+ private buildTurnMessage(ctx: CommitContext): CommitMessageResult {
66
+ const summary = buildChangeSummary(ctx.changedFiles);
67
+ const timestamp = new Date(ctx.timestampMs).toISOString();
68
+ const message = [
69
+ `Turn: ${summary}`,
70
+ '',
71
+ `Session: ${ctx.sessionId}`,
72
+ `Turn: ${ctx.turnNumber}`,
73
+ `Timestamp: ${timestamp}`,
74
+ `Files: ${ctx.changedFiles.length} changed`,
75
+ ].join('\n');
76
+ return { message };
77
+ }
78
+
79
+ private buildHeartbeatMessage(ctx: CommitContext): CommitMessageResult {
80
+ const totalChanges = ctx.changedFiles.length;
81
+ const reason = ctx.reason ?? `${totalChanges} files`;
82
+ return {
83
+ message: `auto-commit: heartbeat safety net (${totalChanges} files, ${reason})`,
84
+ metadata: { trigger: 'heartbeat', timestamp: ctx.timestampMs },
85
+ };
86
+ }
87
+
88
+ private buildShutdownMessage(ctx: CommitContext): CommitMessageResult {
89
+ const totalChanges = ctx.changedFiles.length;
90
+ return {
91
+ message: `auto-commit: shutdown safety net (${totalChanges} files)`,
92
+ metadata: { trigger: 'shutdown', timestamp: ctx.timestampMs },
93
+ };
94
+ }
95
+ }