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.
- package/README.md +15 -2
- package/bun.lock +5 -2
- package/package.json +4 -2
- package/scripts/capture-x-graphql.ts +562 -0
- package/scripts/ipc/check-swift-decoder-drift.ts +2 -1
- package/scripts/test.sh +5 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +161 -34
- package/src/__tests__/account-registry.test.ts +2 -1
- package/src/__tests__/agent-heartbeat-service.test.ts +250 -0
- package/src/__tests__/app-bundler.test.ts +12 -33
- package/src/__tests__/asset-materialize-tool.test.ts +16 -15
- package/src/__tests__/asset-search-tool.test.ts +23 -22
- package/src/__tests__/attachments-store.test.ts +56 -127
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +5 -4
- package/src/__tests__/browser-skill-endstate.test.ts +5 -8
- package/src/__tests__/call-bridge.test.ts +385 -0
- package/src/__tests__/call-constants.test.ts +40 -0
- package/src/__tests__/call-orchestrator.test.ts +454 -0
- package/src/__tests__/call-recovery.test.ts +518 -0
- package/src/__tests__/call-routes-http.test.ts +459 -0
- package/src/__tests__/call-state-machine.test.ts +143 -0
- package/src/__tests__/call-state.test.ts +133 -0
- package/src/__tests__/call-store.test.ts +691 -0
- package/src/__tests__/cli-discover.test.ts +1 -1
- package/src/__tests__/commit-message-enrichment-service.test.ts +550 -0
- package/src/__tests__/compaction.benchmark.test.ts +176 -0
- package/src/__tests__/computer-use-tools.test.ts +250 -0
- package/src/__tests__/config-schema.test.ts +348 -3
- package/src/__tests__/conflict-store.test.ts +2 -1
- package/src/__tests__/contacts-tools.test.ts +331 -0
- package/src/__tests__/conversation-store.test.ts +30 -32
- package/src/__tests__/credential-security-invariants.test.ts +4 -0
- package/src/__tests__/date-context.test.ts +373 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
- package/src/__tests__/doordash-session.test.ts +9 -0
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -3
- package/src/__tests__/followup-tools.test.ts +303 -0
- package/src/__tests__/handlers-twitter-config.test.ts +718 -0
- package/src/__tests__/intent-routing.test.ts +64 -57
- package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
- package/src/__tests__/ipc-snapshot.test.ts +96 -28
- package/src/__tests__/llm-usage-store.test.ts +3 -8
- package/src/__tests__/media-generate-image.test.ts +1 -1
- package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
- package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
- package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
- package/src/__tests__/playbook-tools.test.ts +342 -0
- package/src/__tests__/profile-compiler.test.ts +2 -1
- package/src/__tests__/provider-streaming.benchmark.test.ts +773 -0
- package/src/__tests__/recurrence-engine-rruleset.test.ts +78 -0
- package/src/__tests__/recurrence-engine.test.ts +69 -0
- package/src/__tests__/recurrence-types.test.ts +71 -0
- package/src/__tests__/registry.test.ts +17 -10
- package/src/__tests__/relay-server.test.ts +633 -0
- package/src/__tests__/reminder-store.test.ts +6 -3
- package/src/__tests__/reminder.test.ts +43 -77
- package/src/__tests__/run-orchestrator-assistant-events.test.ts +222 -0
- package/src/__tests__/run-orchestrator.test.ts +7 -7
- package/src/__tests__/runtime-attachment-metadata.test.ts +19 -20
- package/src/__tests__/runtime-runs-http.test.ts +5 -23
- package/src/__tests__/runtime-runs.test.ts +11 -11
- package/src/__tests__/schedule-store.test.ts +482 -0
- package/src/__tests__/schedule-tools.test.ts +700 -0
- package/src/__tests__/scheduler-recurrence.test.ts +329 -0
- package/src/__tests__/server-history-render.test.ts +14 -13
- package/src/__tests__/session-error.test.ts +28 -0
- package/src/__tests__/session-init.benchmark.test.ts +462 -0
- package/src/__tests__/session-queue.test.ts +89 -16
- package/src/__tests__/session-runtime-assembly.test.ts +161 -0
- package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
- package/src/__tests__/signup-e2e.test.ts +2 -1
- package/src/__tests__/skill-projection.benchmark.test.ts +328 -0
- package/src/__tests__/skill-script-runner.test.ts +159 -0
- package/src/__tests__/speaker-identification.test.ts +52 -0
- package/src/__tests__/subagent-manager-notify.test.ts +42 -10
- package/src/__tests__/subagent-tools.test.ts +141 -41
- package/src/__tests__/task-compiler.test.ts +2 -1
- package/src/__tests__/task-runner.test.ts +2 -1
- package/src/__tests__/task-scheduler.test.ts +2 -1
- package/src/__tests__/task-tools.test.ts +49 -56
- package/src/__tests__/tool-audit-listener.test.ts +1 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +2 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
- package/src/__tests__/tool-executor.test.ts +13 -17
- package/src/__tests__/turn-commit.test.ts +273 -2
- package/src/__tests__/twilio-provider.test.ts +143 -0
- package/src/__tests__/twilio-routes.test.ts +789 -0
- package/src/__tests__/twitter-auth-handler.test.ts +581 -0
- package/src/__tests__/view-image-tool.test.ts +217 -0
- package/src/__tests__/workspace-git-service.test.ts +403 -0
- package/src/__tests__/workspace-heartbeat-service.test.ts +141 -2
- package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
- package/src/bundler/app-bundler.ts +35 -14
- package/src/calls/call-bridge.ts +95 -0
- package/src/calls/call-constants.ts +48 -0
- package/src/calls/call-domain.ts +276 -0
- package/src/calls/call-orchestrator.ts +390 -0
- package/src/calls/call-recovery.ts +207 -0
- package/src/calls/call-state-machine.ts +68 -0
- package/src/calls/call-state.ts +64 -0
- package/src/calls/call-store.ts +416 -0
- package/src/calls/relay-server.ts +335 -0
- package/src/calls/speaker-identification.ts +213 -0
- package/src/calls/twilio-config.ts +34 -0
- package/src/calls/twilio-provider.ts +173 -0
- package/src/calls/twilio-routes.ts +250 -0
- package/src/calls/types.ts +37 -0
- package/src/calls/voice-provider.ts +14 -0
- package/src/cli/config-commands.ts +334 -0
- package/src/cli/core-commands.ts +776 -0
- package/src/cli/doordash.ts +256 -25
- package/src/cli/ipc-client.ts +82 -0
- package/src/cli/map.ts +246 -0
- package/src/cli/twitter.ts +575 -0
- package/src/cli.ts +7 -5
- package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
- package/src/commands/cc-command-registry.ts +209 -0
- package/src/config/bundled-skills/contacts/SKILL.md +39 -0
- package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +9 -0
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +9 -0
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +9 -0
- package/src/config/bundled-skills/document/SKILL.md +18 -0
- package/src/config/bundled-skills/document/TOOLS.json +53 -0
- package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
- package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
- package/src/config/bundled-skills/doordash/SKILL.md +163 -0
- package/src/config/bundled-skills/followups/SKILL.md +32 -0
- package/src/config/bundled-skills/followups/TOOLS.json +100 -0
- package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -24
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -1
- package/src/config/bundled-skills/playbooks/SKILL.md +31 -0
- package/src/config/bundled-skills/playbooks/TOOLS.json +126 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +9 -0
- package/src/config/bundled-skills/reminder/SKILL.md +20 -0
- package/src/config/bundled-skills/reminder/TOOLS.json +67 -0
- package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-create.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-list.ts +9 -0
- package/src/config/bundled-skills/schedule/SKILL.md +74 -0
- package/src/config/bundled-skills/schedule/TOOLS.json +135 -0
- package/src/config/bundled-skills/schedule/tools/schedule-create.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-list.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-update.ts +9 -0
- package/src/config/bundled-skills/subagent/SKILL.md +25 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +107 -0
- package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-message.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-read.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-status.ts +9 -0
- package/src/config/bundled-skills/tasks/SKILL.md +28 -0
- package/src/config/bundled-skills/tasks/TOOLS.json +256 -0
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-run.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-save.ts +9 -0
- package/src/config/bundled-skills/twitter/SKILL.md +134 -0
- package/src/config/bundled-skills/watcher/SKILL.md +27 -0
- package/src/config/bundled-skills/watcher/TOOLS.json +147 -0
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +9 -0
- package/src/config/defaults.ts +44 -0
- package/src/config/loader.ts +4 -1
- package/src/config/schema.ts +218 -1
- package/src/config/system-prompt.ts +100 -6
- package/src/config/templates/IDENTITY.md +7 -0
- package/src/config/types.ts +5 -0
- package/src/contacts/contact-store.ts +4 -4
- package/src/daemon/assistant-attachments.ts +10 -0
- package/src/daemon/classifier.ts +3 -1
- package/src/daemon/computer-use-session.ts +3 -1
- package/src/daemon/date-context.ts +136 -0
- package/src/daemon/handlers/apps.ts +16 -1
- package/src/daemon/handlers/browser.ts +54 -0
- package/src/daemon/handlers/computer-use.ts +7 -1
- package/src/daemon/handlers/config.ts +192 -4
- package/src/daemon/handlers/diagnostics.ts +5 -1
- package/src/daemon/handlers/documents.ts +18 -29
- package/src/daemon/handlers/home-base.ts +5 -1
- package/src/daemon/handlers/index.ts +40 -271
- package/src/daemon/handlers/misc.ts +9 -1
- package/src/daemon/handlers/publish.ts +6 -1
- package/src/daemon/handlers/sessions.ts +65 -12
- package/src/daemon/handlers/shared.ts +36 -1
- package/src/daemon/handlers/signing.ts +37 -0
- package/src/daemon/handlers/skills.ts +20 -6
- package/src/daemon/handlers/subagents.ts +8 -3
- package/src/daemon/handlers/twitter-auth.ts +169 -0
- package/src/daemon/handlers/work-items.ts +495 -39
- package/src/daemon/ipc-contract-inventory.json +40 -4
- package/src/daemon/ipc-contract.ts +185 -37
- package/src/daemon/ipc-protocol.ts +7 -2
- package/src/daemon/lifecycle.ts +48 -5
- package/src/daemon/main.ts +10 -4
- package/src/daemon/ride-shotgun-handler.ts +74 -10
- package/src/daemon/server.ts +144 -29
- package/src/daemon/session-agent-loop.ts +887 -0
- package/src/daemon/session-attachments.ts +28 -5
- package/src/daemon/session-error.ts +24 -3
- package/src/daemon/session-lifecycle.ts +147 -0
- package/src/daemon/session-media-retry.ts +147 -0
- package/src/daemon/session-messaging.ts +145 -0
- package/src/daemon/session-notifiers.ts +164 -0
- package/src/daemon/session-process.ts +2 -2
- package/src/daemon/session-queue-manager.ts +1 -0
- package/src/daemon/session-runtime-assembly.ts +52 -0
- package/src/daemon/session-skill-tools.ts +124 -5
- package/src/daemon/session-slash.ts +3 -0
- package/src/daemon/session-surfaces.ts +77 -2
- package/src/daemon/session-tool-setup.ts +222 -2
- package/src/daemon/session-usage.ts +0 -2
- package/src/daemon/session.ts +114 -1365
- package/src/daemon/video-thumbnail.ts +60 -0
- package/src/doordash/client.ts +121 -27
- package/src/doordash/queries.ts +1 -2
- package/src/export/formatter.ts +3 -1
- package/src/followups/followup-store.ts +4 -2
- package/src/followups/types.ts +6 -0
- package/src/hooks/templates.ts +1 -1
- package/src/index.ts +32 -1151
- package/src/media/gemini-image-service.ts +1 -1
- package/src/memory/attachments-store.ts +28 -83
- package/src/memory/channel-delivery-store.ts +7 -21
- package/src/memory/clarification-resolver.ts +6 -5
- package/src/memory/contradiction-checker.ts +3 -2
- package/src/memory/conversation-key-store.ts +10 -29
- package/src/memory/conversation-store.ts +2 -1
- package/src/memory/db.ts +362 -2
- package/src/memory/entity-extractor.ts +6 -3
- package/src/memory/items-extractor.ts +5 -4
- package/src/memory/jobs-store.ts +3 -2
- package/src/memory/llm-usage-store.ts +1 -2
- package/src/memory/runs-store.ts +1 -2
- package/src/memory/schema.ts +65 -2
- package/src/messaging/style-analyzer.ts +3 -2
- package/src/messaging/thread-summarizer.ts +8 -12
- package/src/messaging/triage-engine.ts +4 -2
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/registry.ts +8 -0
- package/src/runtime/http-server.ts +277 -25
- package/src/runtime/http-types.ts +0 -2
- package/src/runtime/routes/attachment-routes.ts +5 -6
- package/src/runtime/routes/call-routes.ts +140 -0
- package/src/runtime/routes/channel-routes.ts +12 -19
- package/src/runtime/routes/conversation-routes.ts +5 -9
- package/src/runtime/routes/run-routes.ts +4 -8
- package/src/runtime/run-orchestrator.ts +39 -6
- package/src/schedule/recurrence-engine.ts +138 -0
- package/src/schedule/recurrence-types.ts +67 -0
- package/src/schedule/schedule-store.ts +102 -57
- package/src/schedule/scheduler.ts +9 -6
- package/src/security/oauth2.ts +29 -4
- package/src/security/secret-allowlist.ts +46 -0
- package/src/skills/clawhub.ts +1 -1
- package/src/subagent/manager.ts +40 -8
- package/src/swarm/backend-claude-code.ts +64 -9
- package/src/swarm/worker-prompts.ts +2 -1
- package/src/tasks/SPEC.md +34 -28
- package/src/tasks/ephemeral-permissions.ts +16 -7
- package/src/tasks/task-compiler.ts +5 -4
- package/src/tasks/task-runner.ts +10 -5
- package/src/tasks/task-scheduler.ts +1 -1
- package/src/tasks/tool-sanitizer.ts +36 -0
- package/src/tools/assets/search.ts +4 -4
- package/src/tools/browser/api-map.ts +220 -0
- package/src/tools/browser/auto-navigate.ts +270 -0
- package/src/tools/browser/browser-execution.ts +2 -1
- package/src/tools/browser/browser-manager.ts +2 -2
- package/src/tools/browser/network-recorder.ts +5 -4
- package/src/tools/browser/x-auto-navigate.ts +207 -0
- package/src/tools/calls/call-end.ts +67 -0
- package/src/tools/calls/call-start.ts +73 -0
- package/src/tools/calls/call-status.ts +81 -0
- package/src/tools/claude-code/claude-code.ts +77 -11
- package/src/tools/contacts/contact-merge.ts +46 -78
- package/src/tools/contacts/contact-search.ts +35 -79
- package/src/tools/contacts/contact-upsert.ts +35 -108
- package/src/tools/credentials/vault.ts +21 -5
- package/src/tools/document/document-tool.ts +71 -144
- package/src/tools/executor.ts +129 -10
- package/src/tools/followups/followup_create.ts +46 -88
- package/src/tools/followups/followup_list.ts +34 -74
- package/src/tools/followups/followup_resolve.ts +31 -66
- package/src/tools/host-terminal/cli-discover.ts +2 -1
- package/src/tools/host-terminal/host-shell.ts +10 -0
- package/src/tools/memory/handlers.ts +5 -4
- package/src/tools/network/__tests__/web-search.test.ts +427 -0
- package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
- package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
- package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
- package/src/tools/network/web-fetch.ts +18 -6
- package/src/tools/playbooks/index.ts +4 -5
- package/src/tools/playbooks/playbook-create.ts +3 -47
- package/src/tools/playbooks/playbook-delete.ts +1 -25
- package/src/tools/playbooks/playbook-list.ts +1 -28
- package/src/tools/playbooks/playbook-update.ts +3 -51
- package/src/tools/registry.ts +2 -4
- package/src/tools/reminder/reminder.ts +5 -78
- package/src/tools/schedule/create.ts +69 -74
- package/src/tools/schedule/delete.ts +21 -47
- package/src/tools/schedule/list.ts +55 -74
- package/src/tools/schedule/update.ts +77 -84
- package/src/tools/subagent/abort.ts +29 -58
- package/src/tools/subagent/message.ts +30 -63
- package/src/tools/subagent/read.ts +53 -84
- package/src/tools/subagent/spawn.ts +43 -82
- package/src/tools/subagent/status.ts +42 -71
- package/src/tools/swarm/delegate.ts +2 -1
- package/src/tools/tasks/index.ts +8 -6
- package/src/tools/tasks/task-delete.ts +69 -56
- package/src/tools/tasks/task-list.ts +31 -52
- package/src/tools/tasks/task-run.ts +74 -102
- package/src/tools/tasks/task-save.ts +33 -65
- package/src/tools/tasks/work-item-enqueue.ts +192 -134
- package/src/tools/tasks/work-item-list.ts +33 -78
- package/src/tools/tasks/work-item-remove.ts +60 -0
- package/src/tools/tasks/work-item-update.ts +114 -0
- package/src/tools/terminal/backends/native.ts +3 -1
- package/src/tools/tool-manifest.ts +20 -74
- package/src/tools/types.ts +6 -0
- package/src/tools/ui-surface/definitions.ts +6 -1
- package/src/tools/watch/screen-watch.ts +3 -1
- package/src/tools/watcher/create.ts +52 -98
- package/src/tools/watcher/delete.ts +20 -46
- package/src/tools/watcher/digest.ts +36 -70
- package/src/tools/watcher/list.ts +49 -79
- package/src/tools/watcher/update.ts +45 -91
- package/src/twitter/client.ts +690 -0
- package/src/twitter/session.ts +91 -0
- package/src/usage/types.ts +0 -1
- package/src/util/truncate.ts +6 -0
- package/src/watcher/providers/slack.ts +2 -1
- package/src/watcher/watcher-store.ts +3 -2
- package/src/work-items/work-item-store.ts +236 -2
- package/src/workspace/commit-message-enrichment-service.ts +284 -0
- package/src/workspace/commit-message-provider.ts +95 -0
- package/src/workspace/git-service.ts +272 -52
- package/src/workspace/heartbeat-service.ts +70 -13
- package/src/workspace/provider-commit-message-generator.ts +242 -0
- package/src/workspace/turn-commit.ts +100 -51
- package/src/tools/contacts/index.ts +0 -4
- package/src/tools/document/index.ts +0 -5
- package/src/tools/followups/index.ts +0 -3
- package/src/tools/subagent/index.ts +0 -5
- /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
|
|
114
|
+
const ctx: CommitContext = {
|
|
115
|
+
workspaceDir,
|
|
116
|
+
trigger: 'turn',
|
|
97
117
|
sessionId,
|
|
98
118
|
turnNumber,
|
|
99
|
-
|
|
100
|
-
|
|
119
|
+
changedFiles: uniqueFiles,
|
|
120
|
+
timestampMs: Date.now(),
|
|
101
121
|
};
|
|
102
122
|
|
|
103
|
-
|
|
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
|
-
{
|
|
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
|
|
File without changes
|