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