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,242 @@
1
+ import { getLogger } from '../util/logger.js';
2
+ import { getConfig } from '../config/loader.js';
3
+ import type { CommitContext } from './commit-message-provider.js';
4
+ import { DefaultCommitMessageProvider } from './commit-message-provider.js';
5
+ import type { Message } from '../providers/types.js';
6
+
7
+ const log = getLogger('commit-message-llm');
8
+
9
+ export type CommitMessageSource = 'llm' | 'deterministic';
10
+ export type LLMFallbackReason =
11
+ | 'disabled'
12
+ | 'provider_not_initialized'
13
+ | 'breaker_open'
14
+ | 'insufficient_budget'
15
+ | 'timeout'
16
+ | 'provider_error'
17
+ | 'invalid_output';
18
+
19
+ export interface GenerateCommitMessageResult {
20
+ message: string;
21
+ source: CommitMessageSource;
22
+ reason?: LLMFallbackReason;
23
+ }
24
+
25
+ interface GenerateOptions {
26
+ deadlineMs?: number;
27
+ changedFiles: string[];
28
+ diffSummary?: string;
29
+ }
30
+
31
+ const SYSTEM_PROMPT = `You generate concise git commit messages for workspace file changes.
32
+ Rules:
33
+ - Write a single short subject line (max 72 chars), optionally followed by a blank line and 2-4 concise bullet points
34
+ - No markdown headings or formatting
35
+ - Only mention files and changes actually provided
36
+ - Total output must be under 300 characters
37
+ - If you cannot determine a meaningful message, respond with exactly: FALLBACK`;
38
+
39
+ const deterministicProvider = new DefaultCommitMessageProvider();
40
+
41
+ function buildDeterministicResult(
42
+ context: CommitContext,
43
+ reason: LLMFallbackReason,
44
+ ): GenerateCommitMessageResult {
45
+ return {
46
+ message: deterministicProvider.buildImmediateMessage(context).message,
47
+ source: 'deterministic',
48
+ reason,
49
+ };
50
+ }
51
+
52
+ export class ProviderCommitMessageGenerator {
53
+ private consecutiveFailures = 0;
54
+ private nextAllowedAttemptMs = 0;
55
+
56
+ private isBreakerOpen(): boolean {
57
+ const config = getConfig();
58
+ const { openAfterFailures } = config.workspaceGit.commitMessageLLM.breaker;
59
+ if (this.consecutiveFailures < openAfterFailures) return false;
60
+ return Date.now() < this.nextAllowedAttemptMs;
61
+ }
62
+
63
+ private recordSuccess(): void {
64
+ if (this.consecutiveFailures > 0) {
65
+ log.info(
66
+ { previousFailures: this.consecutiveFailures },
67
+ 'Commit message LLM breaker closed: succeeded after failures',
68
+ );
69
+ }
70
+ this.consecutiveFailures = 0;
71
+ this.nextAllowedAttemptMs = 0;
72
+ }
73
+
74
+ private recordFailure(): void {
75
+ const config = getConfig();
76
+ const { backoffBaseMs, backoffMaxMs } = config.workspaceGit.commitMessageLLM.breaker;
77
+ this.consecutiveFailures++;
78
+ const delay = Math.min(
79
+ backoffBaseMs * Math.pow(2, this.consecutiveFailures - 1),
80
+ backoffMaxMs,
81
+ );
82
+ this.nextAllowedAttemptMs = Date.now() + delay;
83
+ log.warn(
84
+ { consecutiveFailures: this.consecutiveFailures, backoffMs: delay },
85
+ 'Commit message LLM breaker opened: backing off',
86
+ );
87
+ }
88
+
89
+ async generateCommitMessage(
90
+ context: CommitContext,
91
+ options: GenerateOptions,
92
+ ): Promise<GenerateCommitMessageResult> {
93
+ const config = getConfig();
94
+ const llmConfig = config.workspaceGit.commitMessageLLM;
95
+
96
+ // Step 1: Feature gate
97
+ if (!llmConfig.enabled) {
98
+ return buildDeterministicResult(context, 'disabled');
99
+ }
100
+
101
+ // Step 2: Provider gate
102
+ if (!llmConfig.useConfiguredProvider) {
103
+ return buildDeterministicResult(context, 'disabled');
104
+ }
105
+
106
+ // Step 3: Circuit breaker
107
+ if (this.isBreakerOpen()) {
108
+ log.debug(
109
+ { consecutiveFailures: this.consecutiveFailures },
110
+ 'Commit message LLM breaker open; falling back to deterministic',
111
+ );
112
+ return buildDeterministicResult(context, 'breaker_open');
113
+ }
114
+
115
+ // Step 4: Budget check
116
+ if (options.deadlineMs !== undefined) {
117
+ const remaining = options.deadlineMs - Date.now();
118
+ if (remaining < llmConfig.minRemainingTurnBudgetMs) {
119
+ log.debug(
120
+ { remainingMs: remaining, minBudgetMs: llmConfig.minRemainingTurnBudgetMs },
121
+ 'Insufficient budget for LLM commit message',
122
+ );
123
+ return buildDeterministicResult(context, 'insufficient_budget');
124
+ }
125
+ }
126
+
127
+ // Step 5: Call the provider
128
+ try {
129
+ const { getProvider } = await import('../providers/registry.js');
130
+
131
+ let provider;
132
+ try {
133
+ provider = getProvider(config.provider);
134
+ } catch {
135
+ log.debug({ provider: config.provider }, 'Provider not initialized; falling back to deterministic');
136
+ return buildDeterministicResult(context, 'provider_not_initialized');
137
+ }
138
+
139
+ // Build prompt
140
+ const fileList = options.changedFiles
141
+ .slice(0, llmConfig.maxFilesInPrompt)
142
+ .join('\n');
143
+ const truncatedSuffix = options.changedFiles.length > llmConfig.maxFilesInPrompt
144
+ ? `\n... and ${options.changedFiles.length - llmConfig.maxFilesInPrompt} more files`
145
+ : '';
146
+
147
+ let userText = `Changed files:\n${fileList}${truncatedSuffix}`;
148
+ if (options.diffSummary) {
149
+ const diffBytes = new TextEncoder().encode(options.diffSummary).length;
150
+ const diff = diffBytes > llmConfig.maxDiffBytes
151
+ ? new TextDecoder().decode(new TextEncoder().encode(options.diffSummary).slice(0, llmConfig.maxDiffBytes)) + '\n... (truncated)'
152
+ : options.diffSummary;
153
+ userText += `\n\nDiff summary:\n${diff}`;
154
+ }
155
+
156
+ const messages: Message[] = [
157
+ {
158
+ role: 'user',
159
+ content: [{ type: 'text', text: userText }],
160
+ },
161
+ ];
162
+
163
+ // AbortController with timeout
164
+ const ac = new AbortController();
165
+ const timer = setTimeout(() => ac.abort(), llmConfig.timeoutMs);
166
+
167
+ let response;
168
+ try {
169
+ response = await provider.sendMessage(
170
+ messages,
171
+ undefined,
172
+ SYSTEM_PROMPT,
173
+ {
174
+ signal: ac.signal,
175
+ config: { max_tokens: llmConfig.maxTokens, temperature: llmConfig.temperature },
176
+ },
177
+ );
178
+ } catch (err: unknown) {
179
+ clearTimeout(timer);
180
+ if (ac.signal.aborted) {
181
+ log.warn('Commit message LLM timed out; falling back to deterministic');
182
+ this.recordFailure();
183
+ return buildDeterministicResult(context, 'timeout');
184
+ }
185
+ throw err;
186
+ }
187
+ clearTimeout(timer);
188
+
189
+ // Extract text from response
190
+ const textBlocks = response.content.filter((b) => b.type === 'text');
191
+ const text = textBlocks
192
+ .map((b) => (b as { type: 'text'; text: string }).text)
193
+ .join('')
194
+ .trim();
195
+
196
+ // Validate output
197
+ if (!text || text === 'FALLBACK' || text.length > 500) {
198
+ log.debug(
199
+ { outputLength: text?.length ?? 0, isFallback: text === 'FALLBACK' },
200
+ 'LLM output invalid; falling back to deterministic',
201
+ );
202
+ this.recordFailure();
203
+ return buildDeterministicResult(context, 'invalid_output');
204
+ }
205
+
206
+ // Validate single-line subject: first line must be <= 72 chars
207
+ const firstLine = text.split('\n')[0];
208
+ if (firstLine.length > 72) {
209
+ log.debug(
210
+ { subjectLength: firstLine.length },
211
+ 'LLM subject line too long; falling back to deterministic',
212
+ );
213
+ this.recordFailure();
214
+ return buildDeterministicResult(context, 'invalid_output');
215
+ }
216
+
217
+ this.recordSuccess();
218
+ return { message: text, source: 'llm' };
219
+ } catch (err: unknown) {
220
+ // Step 6: Any error -> deterministic fallback
221
+ log.warn(
222
+ { err: err instanceof Error ? err.message : String(err) },
223
+ 'Commit message LLM provider error; falling back to deterministic',
224
+ );
225
+ this.recordFailure();
226
+ return buildDeterministicResult(context, 'provider_error');
227
+ }
228
+ }
229
+ }
230
+
231
+ let instance: ProviderCommitMessageGenerator | null = null;
232
+
233
+ export function getCommitMessageGenerator(): ProviderCommitMessageGenerator {
234
+ if (!instance) {
235
+ instance = new ProviderCommitMessageGenerator();
236
+ }
237
+ return instance;
238
+ }
239
+
240
+ export function _resetCommitMessageGenerator(): void {
241
+ instance = null;
242
+ }
@@ -11,6 +11,14 @@
11
11
 
12
12
  import { getWorkspaceGitService } from './git-service.js';
13
13
  import { getLogger } from '../util/logger.js';
14
+ import {
15
+ DefaultCommitMessageProvider,
16
+ type CommitContext,
17
+ type CommitMessageProvider,
18
+ } from './commit-message-provider.js';
19
+ import { getEnrichmentService } from './commit-message-enrichment-service.js';
20
+ import { getCommitMessageGenerator } from './provider-commit-message-generator.js';
21
+ import type { CommitMessageSource, LLMFallbackReason } from './provider-commit-message-generator.js';
14
22
 
15
23
  const log = getLogger('turn-commit');
16
24
 
@@ -25,46 +33,6 @@ export interface TurnCommitMetadata {
25
33
  filesChanged: number;
26
34
  }
27
35
 
28
- /**
29
- * Build a commit message with structured metadata for a turn boundary commit.
30
- *
31
- * Format:
32
- * ```
33
- * Turn: <summary>
34
- *
35
- * Session: sess_xyz
36
- * Turn: 5
37
- * Timestamp: 2026-02-18T15:30:00Z
38
- * Files: 3 changed
39
- * ```
40
- */
41
- function buildCommitMessage(summary: string, metadata: TurnCommitMetadata): string {
42
- return [
43
- `Turn: ${summary}`,
44
- '',
45
- `Session: ${metadata.sessionId}`,
46
- `Turn: ${metadata.turnNumber}`,
47
- `Timestamp: ${metadata.timestamp}`,
48
- `Files: ${metadata.filesChanged} changed`,
49
- ].join('\n');
50
- }
51
-
52
- /**
53
- * Build a short summary of what changed from a list of file paths.
54
- */
55
- function buildChangeSummary(files: string[]): string {
56
- if (files.length === 0) {
57
- return 'workspace changes';
58
- }
59
- if (files.length === 1) {
60
- return files[0];
61
- }
62
- if (files.length <= 3) {
63
- return files.join(', ');
64
- }
65
- return `${files.slice(0, 2).join(', ')} and ${files.length - 2} more`;
66
- }
67
-
68
36
  /**
69
37
  * Attempt a turn-boundary commit for the workspace.
70
38
  *
@@ -77,40 +45,121 @@ function buildChangeSummary(files: string[]): string {
77
45
  * @param workspaceDir - Absolute path to the workspace directory
78
46
  * @param sessionId - Session/conversation identifier
79
47
  * @param turnNumber - 1-based turn number within the session
48
+ * @param provider - Optional commit message provider (defaults to deterministic)
49
+ * @param deadlineMs - Optional absolute deadline (Date.now()) after which the commit should be skipped
80
50
  */
81
51
  export async function commitTurnChanges(
82
52
  workspaceDir: string,
83
53
  sessionId: string,
84
54
  turnNumber: number,
55
+ provider?: CommitMessageProvider,
56
+ deadlineMs?: number,
85
57
  ): Promise<void> {
58
+ const messageProvider = provider ?? new DefaultCommitMessageProvider();
86
59
  try {
87
60
  const gitService = getWorkspaceGitService(workspaceDir);
61
+ const commitStartMs = Date.now();
62
+
63
+ // Attempt LLM message generation BEFORE entering commitIfDirty so
64
+ // the LLM call never runs while holding the git mutex.
65
+ // Only attempt LLM when:
66
+ // 1. No custom provider was injected (respect caller contract)
67
+ // 2. The workspace actually has pending changes (avoid wasting budget)
68
+ let llmMessage: string | undefined;
69
+ let commitMessageSource: CommitMessageSource = 'deterministic';
70
+ let llmFallbackReason: LLMFallbackReason | undefined;
71
+
72
+ if (!provider) {
73
+ // Guard: skip pre-check if deadline already elapsed to avoid unnecessary mutex contention
74
+ let preClean = false;
75
+ if (!deadlineMs || Date.now() < deadlineMs) {
76
+ try {
77
+ const preStatus = await gitService.getStatus();
78
+ preClean = preStatus.clean;
79
+ } catch {
80
+ // If we can't determine status, assume dirty so we don't skip the commit
81
+ }
82
+ }
83
+
84
+ if (!preClean) {
85
+ try {
86
+ const generator = getCommitMessageGenerator();
87
+ const result = await generator.generateCommitMessage(
88
+ {
89
+ workspaceDir,
90
+ trigger: 'turn',
91
+ sessionId,
92
+ turnNumber,
93
+ changedFiles: [], // File list unavailable outside the git mutex; generator handles empty arrays
94
+ timestampMs: Date.now(),
95
+ },
96
+ { deadlineMs, changedFiles: [] },
97
+ );
98
+ commitMessageSource = result.source;
99
+ llmFallbackReason = result.reason;
100
+ if (result.source === 'llm') {
101
+ llmMessage = result.message;
102
+ }
103
+ } catch (llmErr) {
104
+ // Never let LLM errors affect the commit path
105
+ log.debug({ err: llmErr }, 'LLM commit message generation failed (non-fatal)');
106
+ llmFallbackReason = 'provider_error';
107
+ }
108
+ }
109
+ }
88
110
 
89
- // Atomic status check + commit within a single mutex lock to prevent
90
- // TOCTOU races with heartbeat commits.
91
111
  const { committed, status } = await gitService.commitIfDirty((st) => {
92
112
  const uniqueFiles = [...new Set([...st.staged, ...st.modified, ...st.untracked])];
93
- const timestamp = new Date().toISOString();
94
- const summary = buildChangeSummary(uniqueFiles);
95
113
 
96
- const metadata: TurnCommitMetadata = {
114
+ const ctx: CommitContext = {
115
+ workspaceDir,
116
+ trigger: 'turn',
97
117
  sessionId,
98
118
  turnNumber,
99
- timestamp,
100
- filesChanged: uniqueFiles.length,
119
+ changedFiles: uniqueFiles,
120
+ timestampMs: Date.now(),
101
121
  };
102
122
 
103
- return { message: buildCommitMessage(summary, metadata) };
104
- });
123
+ // Use LLM message if available, otherwise deterministic
124
+ if (llmMessage) {
125
+ return { message: llmMessage };
126
+ }
127
+ return messageProvider.buildImmediateMessage(ctx);
128
+ }, deadlineMs !== undefined ? { deadlineMs } : undefined);
129
+
130
+ const commitDurationMs = Date.now() - commitStartMs;
105
131
 
106
132
  if (committed) {
107
133
  const uniqueFiles = [...new Set([...status.staged, ...status.modified, ...status.untracked])];
108
134
  log.info(
109
- { sessionId, turnNumber, filesChanged: uniqueFiles.length },
135
+ {
136
+ sessionId,
137
+ turnNumber,
138
+ filesChanged: uniqueFiles.length,
139
+ durationMs: commitDurationMs,
140
+ commitMessageSource,
141
+ ...(llmFallbackReason ? { llmFallbackReason } : {}),
142
+ },
110
143
  'Turn-boundary commit created',
111
144
  );
145
+
146
+ // Fire-and-forget enrichment — never blocks turn completion
147
+ try {
148
+ const commitHash = await gitService.getHeadHash();
149
+ const ctx: CommitContext = {
150
+ workspaceDir,
151
+ trigger: 'turn',
152
+ sessionId,
153
+ turnNumber,
154
+ changedFiles: uniqueFiles,
155
+ timestampMs: Date.now(),
156
+ };
157
+ getEnrichmentService().enqueue({ workspaceDir, commitHash, context: ctx, gitService });
158
+ } catch (enrichErr) {
159
+ log.debug({ enrichErr }, 'Failed to enqueue enrichment (non-fatal)');
160
+ }
112
161
  } else {
113
- log.debug({ sessionId, turnNumber }, 'No workspace changes to commit for turn');
162
+ log.debug({ sessionId, turnNumber, durationMs: commitDurationMs }, 'No workspace changes to commit for turn');
114
163
  }
115
164
  } catch (err) {
116
165
  // Never let commit failures propagate — they must not affect the turn
@@ -1,4 +0,0 @@
1
- // Side-effect imports — each module calls registerTool() at the top level.
2
- import './contact-upsert.js';
3
- import './contact-search.js';
4
- import './contact-merge.js';
@@ -1,5 +0,0 @@
1
- export * from './document-tool.js';
2
-
3
- import { documentCreateTool, documentUpdateTool } from './document-tool.js';
4
-
5
- export const allDocumentTools = [documentCreateTool, documentUpdateTool];
@@ -1,3 +0,0 @@
1
- export { followupCreateTool } from './followup_create.js';
2
- export { followupListTool } from './followup_list.js';
3
- export { followupResolveTool } from './followup_resolve.js';
@@ -1,5 +0,0 @@
1
- export { subagentSpawnTool } from './spawn.js';
2
- export { subagentStatusTool } from './status.js';
3
- export { subagentAbortTool } from './abort.js';
4
- export { subagentMessageTool } from './message.js';
5
- export { subagentReadTool } from './read.js';