vellum 0.2.1 → 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 (349) 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 +133 -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__/asset-materialize-tool.test.ts +16 -15
  11. package/src/__tests__/asset-search-tool.test.ts +23 -22
  12. package/src/__tests__/attachments-store.test.ts +56 -127
  13. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +5 -4
  14. package/src/__tests__/browser-skill-endstate.test.ts +4 -3
  15. package/src/__tests__/call-bridge.test.ts +385 -0
  16. package/src/__tests__/call-constants.test.ts +40 -0
  17. package/src/__tests__/call-orchestrator.test.ts +130 -4
  18. package/src/__tests__/call-recovery.test.ts +518 -0
  19. package/src/__tests__/call-routes-http.test.ts +459 -0
  20. package/src/__tests__/call-state-machine.test.ts +143 -0
  21. package/src/__tests__/call-store.test.ts +216 -1
  22. package/src/__tests__/cli-discover.test.ts +1 -1
  23. package/src/__tests__/commit-message-enrichment-service.test.ts +148 -7
  24. package/src/__tests__/compaction.benchmark.test.ts +176 -0
  25. package/src/__tests__/computer-use-tools.test.ts +250 -0
  26. package/src/__tests__/config-schema.test.ts +299 -3
  27. package/src/__tests__/conflict-store.test.ts +2 -1
  28. package/src/__tests__/contacts-tools.test.ts +331 -0
  29. package/src/__tests__/conversation-store.test.ts +30 -32
  30. package/src/__tests__/credential-security-invariants.test.ts +4 -0
  31. package/src/__tests__/date-context.test.ts +373 -0
  32. package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
  33. package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -3
  34. package/src/__tests__/followup-tools.test.ts +303 -0
  35. package/src/__tests__/handlers-twitter-config.test.ts +718 -0
  36. package/src/__tests__/intent-routing.test.ts +64 -57
  37. package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
  38. package/src/__tests__/ipc-snapshot.test.ts +62 -28
  39. package/src/__tests__/llm-usage-store.test.ts +3 -8
  40. package/src/__tests__/media-generate-image.test.ts +1 -1
  41. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
  42. package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
  43. package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
  44. package/src/__tests__/playbook-tools.test.ts +342 -0
  45. package/src/__tests__/profile-compiler.test.ts +2 -1
  46. package/src/__tests__/provider-streaming.benchmark.test.ts +773 -0
  47. package/src/__tests__/recurrence-engine-rruleset.test.ts +78 -0
  48. package/src/__tests__/recurrence-engine.test.ts +69 -0
  49. package/src/__tests__/recurrence-types.test.ts +71 -0
  50. package/src/__tests__/registry.test.ts +5 -3
  51. package/src/__tests__/relay-server.test.ts +633 -0
  52. package/src/__tests__/reminder-store.test.ts +6 -3
  53. package/src/__tests__/reminder.test.ts +43 -77
  54. package/src/__tests__/run-orchestrator-assistant-events.test.ts +8 -4
  55. package/src/__tests__/run-orchestrator.test.ts +4 -4
  56. package/src/__tests__/runtime-attachment-metadata.test.ts +7 -6
  57. package/src/__tests__/runtime-runs-http.test.ts +4 -4
  58. package/src/__tests__/runtime-runs.test.ts +4 -4
  59. package/src/__tests__/schedule-store.test.ts +482 -0
  60. package/src/__tests__/schedule-tools.test.ts +700 -0
  61. package/src/__tests__/scheduler-recurrence.test.ts +329 -0
  62. package/src/__tests__/server-history-render.test.ts +14 -13
  63. package/src/__tests__/session-error.test.ts +28 -0
  64. package/src/__tests__/session-init.benchmark.test.ts +462 -0
  65. package/src/__tests__/session-queue.test.ts +71 -48
  66. package/src/__tests__/session-runtime-assembly.test.ts +161 -0
  67. package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
  68. package/src/__tests__/signup-e2e.test.ts +2 -1
  69. package/src/__tests__/skill-projection.benchmark.test.ts +328 -0
  70. package/src/__tests__/skill-script-runner.test.ts +159 -0
  71. package/src/__tests__/speaker-identification.test.ts +52 -0
  72. package/src/__tests__/subagent-manager-notify.test.ts +42 -10
  73. package/src/__tests__/subagent-tools.test.ts +141 -41
  74. package/src/__tests__/task-compiler.test.ts +2 -1
  75. package/src/__tests__/task-runner.test.ts +2 -1
  76. package/src/__tests__/task-scheduler.test.ts +2 -1
  77. package/src/__tests__/task-tools.test.ts +49 -56
  78. package/src/__tests__/tool-audit-listener.test.ts +1 -0
  79. package/src/__tests__/tool-domain-event-publisher.test.ts +2 -0
  80. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
  81. package/src/__tests__/tool-executor.test.ts +13 -17
  82. package/src/__tests__/turn-commit.test.ts +218 -3
  83. package/src/__tests__/twilio-provider.test.ts +143 -0
  84. package/src/__tests__/twilio-routes.test.ts +789 -0
  85. package/src/__tests__/twitter-auth-handler.test.ts +581 -0
  86. package/src/__tests__/view-image-tool.test.ts +217 -0
  87. package/src/__tests__/workspace-git-service.test.ts +186 -0
  88. package/src/__tests__/workspace-heartbeat-service.test.ts +13 -3
  89. package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
  90. package/src/bundler/app-bundler.ts +12 -8
  91. package/src/calls/call-bridge.ts +95 -0
  92. package/src/calls/call-constants.ts +43 -5
  93. package/src/calls/call-domain.ts +276 -0
  94. package/src/calls/call-orchestrator.ts +43 -17
  95. package/src/calls/call-recovery.ts +207 -0
  96. package/src/calls/call-state-machine.ts +68 -0
  97. package/src/calls/call-store.ts +192 -5
  98. package/src/calls/relay-server.ts +41 -4
  99. package/src/calls/speaker-identification.ts +213 -0
  100. package/src/calls/twilio-provider.ts +10 -6
  101. package/src/calls/twilio-routes.ts +90 -76
  102. package/src/calls/types.ts +1 -1
  103. package/src/cli/config-commands.ts +334 -0
  104. package/src/cli/core-commands.ts +776 -0
  105. package/src/cli/doordash.ts +251 -1
  106. package/src/cli/ipc-client.ts +82 -0
  107. package/src/cli/map.ts +246 -0
  108. package/src/cli/twitter.ts +575 -0
  109. package/src/cli.ts +7 -5
  110. package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
  111. package/src/commands/cc-command-registry.ts +209 -0
  112. package/src/config/bundled-skills/contacts/SKILL.md +39 -0
  113. package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
  114. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +9 -0
  115. package/src/config/bundled-skills/contacts/tools/contact-search.ts +9 -0
  116. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +9 -0
  117. package/src/config/bundled-skills/document/SKILL.md +18 -0
  118. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  119. package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
  120. package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
  121. package/src/config/bundled-skills/doordash/SKILL.md +82 -23
  122. package/src/config/bundled-skills/followups/SKILL.md +32 -0
  123. package/src/config/bundled-skills/followups/TOOLS.json +100 -0
  124. package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
  125. package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
  126. package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
  127. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +1 -23
  128. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -1
  129. package/src/config/bundled-skills/playbooks/SKILL.md +31 -0
  130. package/src/config/bundled-skills/playbooks/TOOLS.json +126 -0
  131. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +9 -0
  132. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +9 -0
  133. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +9 -0
  134. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +9 -0
  135. package/src/config/bundled-skills/reminder/SKILL.md +20 -0
  136. package/src/config/bundled-skills/reminder/TOOLS.json +67 -0
  137. package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +9 -0
  138. package/src/config/bundled-skills/reminder/tools/reminder-create.ts +9 -0
  139. package/src/config/bundled-skills/reminder/tools/reminder-list.ts +9 -0
  140. package/src/config/bundled-skills/schedule/SKILL.md +74 -0
  141. package/src/config/bundled-skills/schedule/TOOLS.json +135 -0
  142. package/src/config/bundled-skills/schedule/tools/schedule-create.ts +9 -0
  143. package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +9 -0
  144. package/src/config/bundled-skills/schedule/tools/schedule-list.ts +9 -0
  145. package/src/config/bundled-skills/schedule/tools/schedule-update.ts +9 -0
  146. package/src/config/bundled-skills/subagent/SKILL.md +25 -0
  147. package/src/config/bundled-skills/subagent/TOOLS.json +107 -0
  148. package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +9 -0
  149. package/src/config/bundled-skills/subagent/tools/subagent-message.ts +9 -0
  150. package/src/config/bundled-skills/subagent/tools/subagent-read.ts +9 -0
  151. package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +9 -0
  152. package/src/config/bundled-skills/subagent/tools/subagent-status.ts +9 -0
  153. package/src/config/bundled-skills/tasks/SKILL.md +28 -0
  154. package/src/config/bundled-skills/tasks/TOOLS.json +256 -0
  155. package/src/config/bundled-skills/tasks/tools/task-delete.ts +9 -0
  156. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +9 -0
  157. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +9 -0
  158. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +9 -0
  159. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +9 -0
  160. package/src/config/bundled-skills/tasks/tools/task-list.ts +9 -0
  161. package/src/config/bundled-skills/tasks/tools/task-run.ts +9 -0
  162. package/src/config/bundled-skills/tasks/tools/task-save.ts +9 -0
  163. package/src/config/bundled-skills/twitter/SKILL.md +134 -0
  164. package/src/config/bundled-skills/watcher/SKILL.md +27 -0
  165. package/src/config/bundled-skills/watcher/TOOLS.json +147 -0
  166. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +9 -0
  167. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +9 -0
  168. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +9 -0
  169. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +9 -0
  170. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +9 -0
  171. package/src/config/defaults.ts +33 -0
  172. package/src/config/loader.ts +4 -1
  173. package/src/config/schema.ts +161 -1
  174. package/src/config/system-prompt.ts +61 -16
  175. package/src/config/templates/IDENTITY.md +7 -0
  176. package/src/config/types.ts +4 -0
  177. package/src/contacts/contact-store.ts +4 -4
  178. package/src/daemon/assistant-attachments.ts +10 -0
  179. package/src/daemon/classifier.ts +3 -1
  180. package/src/daemon/computer-use-session.ts +3 -1
  181. package/src/daemon/date-context.ts +136 -0
  182. package/src/daemon/handlers/apps.ts +16 -1
  183. package/src/daemon/handlers/browser.ts +54 -0
  184. package/src/daemon/handlers/computer-use.ts +7 -1
  185. package/src/daemon/handlers/config.ts +163 -5
  186. package/src/daemon/handlers/diagnostics.ts +5 -1
  187. package/src/daemon/handlers/documents.ts +18 -29
  188. package/src/daemon/handlers/home-base.ts +5 -1
  189. package/src/daemon/handlers/index.ts +40 -277
  190. package/src/daemon/handlers/misc.ts +9 -1
  191. package/src/daemon/handlers/publish.ts +6 -1
  192. package/src/daemon/handlers/sessions.ts +65 -12
  193. package/src/daemon/handlers/shared.ts +36 -1
  194. package/src/daemon/handlers/signing.ts +37 -0
  195. package/src/daemon/handlers/skills.ts +20 -6
  196. package/src/daemon/handlers/subagents.ts +8 -3
  197. package/src/daemon/handlers/twitter-auth.ts +169 -0
  198. package/src/daemon/handlers/work-items.ts +384 -68
  199. package/src/daemon/ipc-contract-inventory.json +28 -4
  200. package/src/daemon/ipc-contract.ts +133 -37
  201. package/src/daemon/ipc-protocol.ts +7 -2
  202. package/src/daemon/lifecycle.ts +21 -0
  203. package/src/daemon/main.ts +10 -4
  204. package/src/daemon/ride-shotgun-handler.ts +74 -10
  205. package/src/daemon/server.ts +143 -26
  206. package/src/daemon/session-agent-loop.ts +887 -0
  207. package/src/daemon/session-attachments.ts +28 -5
  208. package/src/daemon/session-error.ts +24 -3
  209. package/src/daemon/session-lifecycle.ts +147 -0
  210. package/src/daemon/session-media-retry.ts +147 -0
  211. package/src/daemon/session-messaging.ts +145 -0
  212. package/src/daemon/session-notifiers.ts +164 -0
  213. package/src/daemon/session-process.ts +2 -2
  214. package/src/daemon/session-queue-manager.ts +1 -0
  215. package/src/daemon/session-runtime-assembly.ts +52 -0
  216. package/src/daemon/session-skill-tools.ts +124 -5
  217. package/src/daemon/session-slash.ts +3 -0
  218. package/src/daemon/session-surfaces.ts +77 -2
  219. package/src/daemon/session-tool-setup.ts +216 -2
  220. package/src/daemon/session-usage.ts +0 -2
  221. package/src/daemon/session.ts +114 -1404
  222. package/src/daemon/video-thumbnail.ts +60 -0
  223. package/src/doordash/client.ts +121 -27
  224. package/src/doordash/queries.ts +1 -2
  225. package/src/export/formatter.ts +3 -1
  226. package/src/followups/followup-store.ts +4 -2
  227. package/src/followups/types.ts +6 -0
  228. package/src/hooks/templates.ts +1 -1
  229. package/src/index.ts +32 -1153
  230. package/src/memory/attachments-store.ts +28 -83
  231. package/src/memory/channel-delivery-store.ts +7 -21
  232. package/src/memory/clarification-resolver.ts +6 -5
  233. package/src/memory/contradiction-checker.ts +3 -2
  234. package/src/memory/conversation-key-store.ts +10 -29
  235. package/src/memory/conversation-store.ts +2 -1
  236. package/src/memory/db.ts +96 -2
  237. package/src/memory/entity-extractor.ts +6 -3
  238. package/src/memory/items-extractor.ts +5 -4
  239. package/src/memory/jobs-store.ts +3 -2
  240. package/src/memory/llm-usage-store.ts +1 -2
  241. package/src/memory/runs-store.ts +1 -2
  242. package/src/memory/schema.ts +23 -2
  243. package/src/messaging/style-analyzer.ts +3 -2
  244. package/src/messaging/thread-summarizer.ts +8 -12
  245. package/src/messaging/triage-engine.ts +4 -2
  246. package/src/providers/openrouter/client.ts +20 -0
  247. package/src/providers/registry.ts +8 -0
  248. package/src/runtime/http-server.ts +108 -20
  249. package/src/runtime/routes/attachment-routes.ts +2 -3
  250. package/src/runtime/routes/call-routes.ts +140 -0
  251. package/src/runtime/routes/channel-routes.ts +5 -10
  252. package/src/runtime/routes/conversation-routes.ts +5 -5
  253. package/src/runtime/routes/run-routes.ts +2 -2
  254. package/src/runtime/run-orchestrator.ts +9 -3
  255. package/src/schedule/recurrence-engine.ts +138 -0
  256. package/src/schedule/recurrence-types.ts +67 -0
  257. package/src/schedule/schedule-store.ts +102 -57
  258. package/src/schedule/scheduler.ts +9 -6
  259. package/src/security/oauth2.ts +29 -4
  260. package/src/security/secret-allowlist.ts +46 -0
  261. package/src/skills/clawhub.ts +1 -1
  262. package/src/subagent/manager.ts +40 -8
  263. package/src/swarm/backend-claude-code.ts +64 -9
  264. package/src/swarm/worker-prompts.ts +2 -1
  265. package/src/tasks/SPEC.md +34 -28
  266. package/src/tasks/ephemeral-permissions.ts +16 -7
  267. package/src/tasks/task-compiler.ts +5 -4
  268. package/src/tasks/task-runner.ts +10 -5
  269. package/src/tasks/task-scheduler.ts +1 -1
  270. package/src/tasks/tool-sanitizer.ts +36 -0
  271. package/src/tools/assets/search.ts +4 -4
  272. package/src/tools/browser/api-map.ts +220 -0
  273. package/src/tools/browser/auto-navigate.ts +270 -0
  274. package/src/tools/browser/browser-execution.ts +2 -1
  275. package/src/tools/browser/browser-manager.ts +2 -2
  276. package/src/tools/browser/network-recorder.ts +5 -4
  277. package/src/tools/browser/x-auto-navigate.ts +207 -0
  278. package/src/tools/calls/call-end.ts +17 -67
  279. package/src/tools/calls/call-start.ts +24 -85
  280. package/src/tools/calls/call-status.ts +35 -51
  281. package/src/tools/claude-code/claude-code.ts +77 -11
  282. package/src/tools/contacts/contact-merge.ts +46 -78
  283. package/src/tools/contacts/contact-search.ts +35 -79
  284. package/src/tools/contacts/contact-upsert.ts +35 -108
  285. package/src/tools/credentials/vault.ts +20 -4
  286. package/src/tools/document/document-tool.ts +71 -144
  287. package/src/tools/executor.ts +129 -10
  288. package/src/tools/followups/followup_create.ts +46 -88
  289. package/src/tools/followups/followup_list.ts +34 -74
  290. package/src/tools/followups/followup_resolve.ts +31 -66
  291. package/src/tools/host-terminal/cli-discover.ts +2 -1
  292. package/src/tools/host-terminal/host-shell.ts +10 -0
  293. package/src/tools/memory/handlers.ts +5 -4
  294. package/src/tools/network/__tests__/web-search.test.ts +427 -0
  295. package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
  296. package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
  297. package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
  298. package/src/tools/network/web-fetch.ts +18 -6
  299. package/src/tools/playbooks/index.ts +4 -5
  300. package/src/tools/playbooks/playbook-create.ts +3 -47
  301. package/src/tools/playbooks/playbook-delete.ts +1 -25
  302. package/src/tools/playbooks/playbook-list.ts +1 -28
  303. package/src/tools/playbooks/playbook-update.ts +3 -51
  304. package/src/tools/reminder/reminder.ts +5 -78
  305. package/src/tools/schedule/create.ts +69 -74
  306. package/src/tools/schedule/delete.ts +21 -47
  307. package/src/tools/schedule/list.ts +55 -74
  308. package/src/tools/schedule/update.ts +77 -84
  309. package/src/tools/subagent/abort.ts +29 -58
  310. package/src/tools/subagent/message.ts +30 -63
  311. package/src/tools/subagent/read.ts +53 -84
  312. package/src/tools/subagent/spawn.ts +43 -82
  313. package/src/tools/subagent/status.ts +42 -71
  314. package/src/tools/swarm/delegate.ts +2 -1
  315. package/src/tools/tasks/index.ts +8 -8
  316. package/src/tools/tasks/task-delete.ts +60 -88
  317. package/src/tools/tasks/task-list.ts +31 -52
  318. package/src/tools/tasks/task-run.ts +72 -108
  319. package/src/tools/tasks/task-save.ts +33 -65
  320. package/src/tools/tasks/work-item-enqueue.ts +183 -215
  321. package/src/tools/tasks/work-item-list.ts +33 -63
  322. package/src/tools/tasks/work-item-remove.ts +45 -97
  323. package/src/tools/tasks/work-item-update.ts +91 -163
  324. package/src/tools/terminal/backends/native.ts +3 -1
  325. package/src/tools/tool-manifest.ts +0 -62
  326. package/src/tools/types.ts +6 -0
  327. package/src/tools/ui-surface/definitions.ts +3 -1
  328. package/src/tools/watch/screen-watch.ts +3 -1
  329. package/src/tools/watcher/create.ts +52 -98
  330. package/src/tools/watcher/delete.ts +20 -46
  331. package/src/tools/watcher/digest.ts +36 -70
  332. package/src/tools/watcher/list.ts +49 -79
  333. package/src/tools/watcher/update.ts +45 -91
  334. package/src/twitter/client.ts +690 -0
  335. package/src/twitter/session.ts +91 -0
  336. package/src/usage/types.ts +0 -1
  337. package/src/util/truncate.ts +6 -0
  338. package/src/watcher/providers/slack.ts +2 -1
  339. package/src/watcher/watcher-store.ts +3 -2
  340. package/src/work-items/work-item-store.ts +27 -2
  341. package/src/workspace/commit-message-enrichment-service.ts +31 -7
  342. package/src/workspace/git-service.ts +87 -22
  343. package/src/workspace/provider-commit-message-generator.ts +242 -0
  344. package/src/workspace/turn-commit.ts +62 -3
  345. package/src/tools/contacts/index.ts +0 -4
  346. package/src/tools/document/index.ts +0 -5
  347. package/src/tools/followups/index.ts +0 -3
  348. package/src/tools/subagent/index.ts +0 -5
  349. /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
+ }
@@ -17,6 +17,8 @@ import {
17
17
  type CommitMessageProvider,
18
18
  } from './commit-message-provider.js';
19
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';
20
22
 
21
23
  const log = getLogger('turn-commit');
22
24
 
@@ -58,8 +60,54 @@ export async function commitTurnChanges(
58
60
  const gitService = getWorkspaceGitService(workspaceDir);
59
61
  const commitStartMs = Date.now();
60
62
 
61
- // Atomic status check + commit within a single mutex lock to prevent
62
- // TOCTOU races with heartbeat commits.
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
+ }
110
+
63
111
  const { committed, status } = await gitService.commitIfDirty((st) => {
64
112
  const uniqueFiles = [...new Set([...st.staged, ...st.modified, ...st.untracked])];
65
113
 
@@ -72,6 +120,10 @@ export async function commitTurnChanges(
72
120
  timestampMs: Date.now(),
73
121
  };
74
122
 
123
+ // Use LLM message if available, otherwise deterministic
124
+ if (llmMessage) {
125
+ return { message: llmMessage };
126
+ }
75
127
  return messageProvider.buildImmediateMessage(ctx);
76
128
  }, deadlineMs !== undefined ? { deadlineMs } : undefined);
77
129
 
@@ -80,7 +132,14 @@ export async function commitTurnChanges(
80
132
  if (committed) {
81
133
  const uniqueFiles = [...new Set([...status.staged, ...status.modified, ...status.untracked])];
82
134
  log.info(
83
- { sessionId, turnNumber, filesChanged: uniqueFiles.length, durationMs: commitDurationMs },
135
+ {
136
+ sessionId,
137
+ turnNumber,
138
+ filesChanged: uniqueFiles.length,
139
+ durationMs: commitDurationMs,
140
+ commitMessageSource,
141
+ ...(llmFallbackReason ? { llmFallbackReason } : {}),
142
+ },
84
143
  'Turn-boundary commit created',
85
144
  );
86
145
 
@@ -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';