@vellumai/assistant 0.5.6 → 0.5.8
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/.env.example +16 -2
- package/ARCHITECTURE.md +6 -75
- package/Dockerfile +3 -2
- package/README.md +0 -2
- package/bun.lock +0 -414
- package/docker-entrypoint.sh +9 -0
- package/docs/architecture/keychain-broker.md +45 -240
- package/docs/architecture/memory.md +13 -11
- package/docs/architecture/security.md +0 -17
- package/docs/credential-execution-service.md +2 -2
- package/node_modules/@vellumai/ces-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-contracts/src/error.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/grants.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +120 -1
- package/node_modules/@vellumai/credential-storage/package.json +1 -0
- package/node_modules/@vellumai/egress-proxy/package.json +1 -0
- package/package.json +2 -3
- package/src/__tests__/actor-token-service.test.ts +0 -114
- package/src/__tests__/approval-cascade.test.ts +0 -1
- package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/browser-skill-endstate.test.ts +6 -5
- package/src/__tests__/btw-routes.test.ts +0 -39
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/call-domain.test.ts +0 -128
- package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
- package/src/__tests__/ces-startup-timeout.test.ts +40 -0
- package/src/__tests__/channel-approval-routes.test.ts +0 -5
- package/src/__tests__/channel-readiness-service.test.ts +1 -60
- package/src/__tests__/checker.test.ts +4 -2
- package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
- package/src/__tests__/config-schema-cmd.test.ts +0 -2
- package/src/__tests__/config-schema.test.ts +3 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop.test.ts +2 -4
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -1
- package/src/__tests__/conversation-error.test.ts +15 -1
- package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
- package/src/__tests__/conversation-messaging-secret-redirect.test.ts +1 -1
- package/src/__tests__/conversation-pre-run-repair.test.ts +0 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -1
- package/src/__tests__/conversation-queue.test.ts +0 -1
- package/src/__tests__/conversation-skill-tools.test.ts +0 -54
- package/src/__tests__/conversation-slash-queue.test.ts +0 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -1
- package/src/__tests__/conversation-title-service.test.ts +87 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -1
- package/src/__tests__/credential-execution-client.test.ts +5 -2
- package/src/__tests__/credential-execution-feature-gates.test.ts +59 -30
- package/src/__tests__/credential-execution-managed-contract.test.ts +35 -20
- package/src/__tests__/credential-security-e2e.test.ts +1 -67
- package/src/__tests__/credential-security-invariants.test.ts +6 -50
- package/src/__tests__/credentials-cli.test.ts +82 -3
- package/src/__tests__/daemon-credential-client.test.ts +123 -0
- package/src/__tests__/db-migration-rollback.test.ts +2015 -1
- package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -0
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +34 -143
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
- package/src/__tests__/gateway-client-managed-outbound.test.ts +79 -1
- package/src/__tests__/guardian-routing-state.test.ts +0 -5
- package/src/__tests__/host-shell-tool.test.ts +6 -7
- package/src/__tests__/http-user-message-parity.test.ts +3 -103
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
- package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
- package/src/__tests__/intent-routing.test.ts +0 -13
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
- package/src/__tests__/journal-context.test.ts +335 -0
- package/src/__tests__/keychain-broker-client.test.ts +161 -22
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -3
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
- package/src/__tests__/memory-lifecycle-e2e.test.ts +70 -25
- package/src/__tests__/memory-recall-quality.test.ts +48 -17
- package/src/__tests__/memory-regressions.test.ts +408 -363
- package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -3
- package/src/__tests__/migration-export-http.test.ts +2 -2
- package/src/__tests__/migration-import-commit-http.test.ts +2 -2
- package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
- package/src/__tests__/migration-validate-http.test.ts +2 -2
- package/src/__tests__/non-member-access-request.test.ts +2 -7
- package/src/__tests__/notification-decision-fallback.test.ts +4 -0
- package/src/__tests__/notification-decision-identity.test.ts +4 -0
- package/src/__tests__/notification-decision-strategy.test.ts +71 -0
- package/src/__tests__/oauth-cli.test.ts +5 -1
- package/src/__tests__/permission-types.test.ts +1 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +0 -37
- package/src/__tests__/provider-error-scenarios.test.ts +0 -267
- package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
- package/src/__tests__/provider-streaming.benchmark.test.ts +2 -81
- package/src/__tests__/qdrant-manager.test.ts +28 -2
- package/src/__tests__/registry.test.ts +0 -6
- package/src/__tests__/relay-server.test.ts +1 -2
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
- package/src/__tests__/script-proxy-injection-runtime.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -1
- package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
- package/src/__tests__/secure-keys.test.ts +95 -272
- package/src/__tests__/shell-identity.test.ts +96 -6
- package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
- package/src/__tests__/skill-feature-flags.test.ts +46 -45
- package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
- package/src/__tests__/skill-load-inline-command.test.ts +8 -12
- package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
- package/src/__tests__/skill-load-tool.test.ts +0 -2
- package/src/__tests__/skill-memory.test.ts +17 -3
- package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
- package/src/__tests__/skills.test.ts +0 -2
- package/src/__tests__/slack-inbound-verification.test.ts +0 -4
- package/src/__tests__/stale-approval-dedup.test.ts +171 -0
- package/src/__tests__/stt-hints.test.ts +437 -0
- package/src/__tests__/suggestion-routes.test.ts +1 -32
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/task-memory-cleanup.test.ts +14 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
- package/src/__tests__/twilio-routes-twiml.test.ts +139 -1
- package/src/__tests__/update-bulletin.test.ts +0 -2
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
- package/src/__tests__/voice-quality.test.ts +58 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -7
- package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
- package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +220 -0
- package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
- package/src/acp/agent-process.ts +9 -1
- package/src/agent/loop.ts +1 -1
- package/src/approvals/guardian-request-resolvers.ts +164 -38
- package/src/calls/__tests__/tts-text-sanitizer.test.ts +254 -0
- package/src/calls/audio-store.test.ts +97 -0
- package/src/calls/audio-store.ts +205 -0
- package/src/calls/call-controller.ts +90 -8
- package/src/calls/call-domain.ts +3 -0
- package/src/calls/call-store.ts +10 -3
- package/src/calls/fish-audio-client.ts +129 -0
- package/src/calls/relay-server.ts +27 -0
- package/src/calls/stt-hints.ts +189 -0
- package/src/calls/tts-text-sanitizer.ts +61 -0
- package/src/calls/twilio-routes.ts +34 -5
- package/src/calls/types.ts +1 -0
- package/src/calls/voice-ingress-preflight.ts +0 -42
- package/src/calls/voice-quality.ts +38 -5
- package/src/calls/voice-session-bridge.ts +7 -12
- package/src/cli/commands/avatar.ts +2 -2
- package/src/cli/commands/config.ts +1 -4
- package/src/cli/commands/credentials.ts +128 -82
- package/src/cli/commands/doctor.ts +2 -2
- package/src/cli/commands/keys.ts +7 -7
- package/src/cli/commands/memory.ts +1 -1
- package/src/cli/commands/oauth/connections.ts +11 -29
- package/src/cli/commands/oauth/index.ts +7 -0
- package/src/cli/commands/oauth/platform.ts +525 -0
- package/src/cli/commands/platform.ts +3 -3
- package/src/cli/lib/daemon-credential-client.ts +284 -0
- package/src/cli.ts +1 -1
- package/src/config/assistant-feature-flags.ts +186 -5
- package/src/config/bundled-skills/AGENTS.md +34 -0
- package/src/config/bundled-skills/acp/SKILL.md +10 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +0 -4
- package/src/config/bundled-skills/messaging/SKILL.md +5 -5
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
- package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +1 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +1 -0
- package/src/config/bundled-skills/settings/SKILL.md +15 -2
- package/src/config/bundled-skills/settings/TOOLS.json +47 -2
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +59 -0
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +80 -0
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
- package/src/config/bundled-skills/slack/SKILL.md +1 -1
- package/src/config/bundled-tool-registry.ts +5 -11
- package/src/config/defaults.ts +0 -2
- package/src/config/env-registry.ts +5 -5
- package/src/config/env.ts +21 -14
- package/src/config/feature-flag-registry.json +49 -9
- package/src/config/loader.ts +106 -42
- package/src/config/schema.ts +9 -29
- package/src/config/schemas/calls.ts +30 -0
- package/src/config/schemas/fish-audio.ts +39 -0
- package/src/config/schemas/inference.ts +2 -2
- package/src/config/schemas/journal.ts +16 -0
- package/src/config/schemas/memory-processing.ts +2 -2
- package/src/config/schemas/security.ts +0 -4
- package/src/config/types.ts +1 -1
- package/src/contacts/contact-store.ts +39 -0
- package/src/contacts/types.ts +2 -0
- package/src/credential-execution/approval-bridge.ts +1 -0
- package/src/credential-execution/executable-discovery.ts +28 -4
- package/src/credential-execution/feature-gates.ts +16 -0
- package/src/credential-execution/process-manager.ts +38 -0
- package/src/credential-execution/startup-timeout.ts +36 -0
- package/src/daemon/approval-generators.ts +3 -9
- package/src/daemon/assistant-attachments.ts +9 -0
- package/src/daemon/config-watcher.ts +5 -0
- package/src/daemon/conversation-error.ts +13 -1
- package/src/daemon/conversation-memory.ts +1 -2
- package/src/daemon/conversation-process.ts +18 -1
- package/src/daemon/conversation-surfaces.ts +30 -1
- package/src/daemon/conversation-tool-setup.ts +0 -105
- package/src/daemon/conversation.ts +21 -1
- package/src/daemon/guardian-action-generators.ts +3 -9
- package/src/daemon/handlers/config-vercel.ts +92 -0
- package/src/daemon/handlers/skills.ts +2 -15
- package/src/daemon/install-symlink.ts +195 -0
- package/src/daemon/lifecycle.ts +234 -51
- package/src/daemon/message-types/conversations.ts +4 -4
- package/src/daemon/message-types/diagnostics.ts +3 -22
- package/src/daemon/message-types/messages.ts +0 -2
- package/src/daemon/message-types/upgrades.ts +8 -0
- package/src/daemon/server.ts +32 -95
- package/src/events/domain-events.ts +2 -1
- package/src/inbound/platform-callback-registration.ts +3 -3
- package/src/instrument.ts +8 -5
- package/src/memory/app-store.ts +31 -0
- package/src/memory/conversation-title-service.ts +50 -1
- package/src/memory/db-init.ts +16 -0
- package/src/memory/indexer.ts +19 -10
- package/src/memory/items-extractor.ts +328 -321
- package/src/memory/job-handlers/conversation-starters.ts +4 -1
- package/src/memory/job-handlers/summarization.ts +26 -16
- package/src/memory/jobs-store.ts +63 -6
- package/src/memory/jobs-worker.ts +31 -7
- package/src/memory/journal-memory.ts +214 -0
- package/src/memory/migrations/001-job-deferrals.ts +19 -0
- package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
- package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
- package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
- package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
- package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
- package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
- package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
- package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
- package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
- package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
- package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
- package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
- package/src/memory/migrations/116-messages-fts.ts +106 -1
- package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
- package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
- package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
- package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
- package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
- package/src/memory/migrations/141-rename-verification-table.ts +54 -0
- package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
- package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
- package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
- package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
- package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
- package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
- package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
- package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
- package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
- package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
- package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
- package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
- package/src/memory/migrations/193-add-source-type-columns.ts +81 -0
- package/src/memory/migrations/index.ts +5 -0
- package/src/memory/migrations/registry.ts +98 -0
- package/src/memory/migrations/validate-migration-state.ts +137 -11
- package/src/memory/qdrant-circuit-breaker.ts +9 -0
- package/src/memory/qdrant-manager.ts +64 -7
- package/src/memory/retriever.test.ts +37 -25
- package/src/memory/retriever.ts +24 -49
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/contacts.ts +1 -0
- package/src/memory/schema/memory-core.ts +2 -0
- package/src/memory/search/formatting.ts +7 -44
- package/src/memory/search/staleness.ts +4 -0
- package/src/memory/search/tier-classifier.ts +10 -2
- package/src/memory/search/types.ts +2 -5
- package/src/memory/task-memory-cleanup.ts +4 -3
- package/src/notifications/adapters/slack.ts +168 -6
- package/src/notifications/broadcaster.ts +1 -0
- package/src/notifications/copy-composer.ts +59 -2
- package/src/notifications/decision-engine.ts +4 -1
- package/src/notifications/signal.ts +2 -0
- package/src/notifications/types.ts +2 -0
- package/src/oauth/connection-resolver.ts +6 -4
- package/src/permissions/checker.ts +0 -38
- package/src/permissions/shell-identity.ts +76 -22
- package/src/permissions/types.ts +4 -2
- package/src/platform/client.ts +35 -7
- package/src/prompts/journal-context.ts +133 -0
- package/src/prompts/persona-resolver.ts +194 -0
- package/src/prompts/system-prompt.ts +44 -4
- package/src/prompts/templates/SOUL.md +10 -0
- package/src/prompts/templates/users/default.md +1 -0
- package/src/providers/provider-send-message.ts +3 -32
- package/src/providers/registry.ts +29 -179
- package/src/providers/types.ts +1 -1
- package/src/runtime/access-request-helper.ts +4 -0
- package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
- package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
- package/src/runtime/auth/__tests__/guard-tests.test.ts +9 -50
- package/src/runtime/auth/external-assistant-id.ts +13 -59
- package/src/runtime/auth/route-policy.ts +17 -1
- package/src/runtime/auth/token-service.ts +43 -138
- package/src/runtime/channel-readiness-service.ts +1 -16
- package/src/runtime/gateway-client.ts +47 -4
- package/src/runtime/guardian-decision-types.ts +45 -4
- package/src/runtime/http-server.ts +31 -3
- package/src/runtime/middleware/error-handler.ts +1 -9
- package/src/runtime/routes/access-request-decision.ts +2 -2
- package/src/runtime/routes/app-management-routes.ts +2 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +219 -30
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +37 -14
- package/src/runtime/routes/audio-routes.ts +40 -0
- package/src/runtime/routes/btw-routes.ts +0 -17
- package/src/runtime/routes/channel-readiness-routes.ts +9 -4
- package/src/runtime/routes/conversation-query-routes.ts +63 -1
- package/src/runtime/routes/conversation-routes.ts +4 -44
- package/src/runtime/routes/debug-routes.ts +12 -9
- package/src/runtime/routes/diagnostics-routes.ts +1 -477
- package/src/runtime/routes/guardian-approval-interception.ts +168 -11
- package/src/runtime/routes/guardian-approval-prompt.ts +6 -1
- package/src/runtime/routes/guardian-approval-reply-helpers.ts +103 -21
- package/src/runtime/routes/identity-routes.ts +19 -30
- package/src/runtime/routes/inbound-message-handler.ts +31 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +64 -5
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +52 -40
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
- package/src/runtime/routes/integrations/twilio.ts +52 -10
- package/src/runtime/routes/integrations/vercel.ts +89 -0
- package/src/runtime/routes/log-export-routes.ts +5 -0
- package/src/runtime/routes/memory-item-routes.test.ts +3 -3
- package/src/runtime/routes/memory-item-routes.ts +46 -14
- package/src/runtime/routes/migration-rollback-routes.ts +209 -0
- package/src/runtime/routes/migration-routes.ts +17 -1
- package/src/runtime/routes/notification-routes.ts +58 -0
- package/src/runtime/routes/schedule-routes.ts +65 -0
- package/src/runtime/routes/secret-routes.ts +141 -10
- package/src/runtime/routes/settings-routes.ts +41 -1
- package/src/runtime/routes/tts-routes.ts +96 -0
- package/src/runtime/routes/upgrade-broadcast-routes.ts +26 -2
- package/src/runtime/routes/workspace-commit-routes.ts +62 -0
- package/src/runtime/routes/workspace-routes.test.ts +22 -1
- package/src/runtime/routes/workspace-routes.ts +1 -1
- package/src/runtime/routes/workspace-utils.ts +86 -2
- package/src/security/ces-credential-client.ts +75 -29
- package/src/security/ces-rpc-credential-backend.ts +86 -0
- package/src/security/credential-backend.ts +22 -92
- package/src/security/keychain-broker-client.ts +10 -2
- package/src/security/secure-keys.ts +113 -115
- package/src/skills/catalog-install.ts +6 -32
- package/src/skills/skill-memory.ts +1 -0
- package/src/subagent/manager.ts +2 -5
- package/src/telemetry/usage-telemetry-reporter.ts +4 -2
- package/src/tools/acp/spawn.ts +78 -1
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/credentials/vault.ts +5 -3
- package/src/tools/executor.ts +0 -4
- package/src/tools/memory/definitions.ts +3 -2
- package/src/tools/memory/handlers.ts +10 -7
- package/src/tools/network/script-proxy/session-manager.ts +19 -4
- package/src/tools/network/web-fetch.ts +3 -1
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/tools/types.ts +0 -8
- package/src/util/browser.ts +15 -0
- package/src/util/errors.ts +0 -12
- package/src/util/platform.ts +4 -51
- package/src/workspace/git-service.ts +5 -2
- package/src/workspace/migrations/001-avatar-rename.ts +15 -0
- package/src/workspace/migrations/003-seed-device-id.ts +17 -1
- package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
- package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
- package/src/workspace/migrations/006-services-config.ts +49 -0
- package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
- package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
- package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
- package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
- package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
- package/src/workspace/migrations/017-seed-persona-dirs.ts +96 -0
- package/src/workspace/migrations/018-rekey-compound-credential-keys.ts +184 -0
- package/src/workspace/migrations/019-scope-journal-to-guardian.ts +103 -0
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +27 -5
- package/src/workspace/migrations/registry.ts +12 -0
- package/src/workspace/migrations/runner.ts +106 -2
- package/src/workspace/migrations/types.ts +4 -0
- package/src/workspace/provider-commit-message-generator.ts +12 -21
- package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
- package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
- package/src/__tests__/diagnostics-export.test.ts +0 -288
- package/src/__tests__/local-gateway-health.test.ts +0 -209
- package/src/__tests__/provider-fail-open-selection.test.ts +0 -271
- package/src/__tests__/provider-failover-actual-provider.test.ts +0 -66
- package/src/__tests__/secret-ingress-handler.test.ts +0 -120
- package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
- package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
- package/src/__tests__/swarm-orchestrator.test.ts +0 -463
- package/src/__tests__/swarm-plan-validator.test.ts +0 -384
- package/src/__tests__/swarm-recursion.test.ts +0 -197
- package/src/__tests__/swarm-router-planner.test.ts +0 -234
- package/src/__tests__/swarm-tool.test.ts +0 -185
- package/src/__tests__/swarm-worker-backend.test.ts +0 -144
- package/src/__tests__/swarm-worker-runner.test.ts +0 -288
- package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
- package/src/commands/cc-command-registry.ts +0 -248
- package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
- package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
- package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
- package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
- package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
- package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
- package/src/config/schemas/swarm.ts +0 -82
- package/src/logfire.ts +0 -135
- package/src/memory/search/lexical.ts +0 -48
- package/src/providers/failover.ts +0 -186
- package/src/runtime/local-gateway-health.ts +0 -275
- package/src/security/secret-ingress.ts +0 -68
- package/src/swarm/backend-claude-code.ts +0 -225
- package/src/swarm/checkpoint.ts +0 -137
- package/src/swarm/graph-utils.ts +0 -53
- package/src/swarm/index.ts +0 -55
- package/src/swarm/limits.ts +0 -66
- package/src/swarm/orchestrator.ts +0 -424
- package/src/swarm/plan-validator.ts +0 -117
- package/src/swarm/router-planner.ts +0 -162
- package/src/swarm/router-prompts.ts +0 -39
- package/src/swarm/synthesizer.ts +0 -81
- package/src/swarm/types.ts +0 -72
- package/src/swarm/worker-backend.ts +0 -131
- package/src/swarm/worker-prompts.ts +0 -80
- package/src/swarm/worker-runner.ts +0 -170
- package/src/tools/claude-code/claude-code.ts +0 -610
- package/src/tools/swarm/delegate.ts +0 -205
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { credentialKey } from "../../security/credential-key.js";
|
|
2
|
+
import { getLogger } from "../../util/logger.js";
|
|
3
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
4
|
+
|
|
5
|
+
const log = getLogger("workspace-migrations");
|
|
6
|
+
const CREDENTIAL_PREFIX = "credential/";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Re-key compound credential storage keys from the old indexOf-based split
|
|
10
|
+
* to the new lastIndexOf-based split.
|
|
11
|
+
*
|
|
12
|
+
* The old code split "integration:google:access_token" at the first colon:
|
|
13
|
+
* service = "integration", field = "google:access_token"
|
|
14
|
+
* → key = "credential/integration/google:access_token"
|
|
15
|
+
*
|
|
16
|
+
* The new code splits at the last colon:
|
|
17
|
+
* service = "integration:google", field = "access_token"
|
|
18
|
+
* → key = "credential/integration:google/access_token"
|
|
19
|
+
*
|
|
20
|
+
* Detection heuristic: if the field portion of a stored key contains a colon,
|
|
21
|
+
* it was stored with the old indexOf logic and needs re-keying. Simple
|
|
22
|
+
* service:field names (single colon) produce the same key with both methods
|
|
23
|
+
* and don't need migration.
|
|
24
|
+
*/
|
|
25
|
+
export const rekeyCompoundCredentialKeysMigration: WorkspaceMigration = {
|
|
26
|
+
id: "018-rekey-compound-credential-keys",
|
|
27
|
+
description:
|
|
28
|
+
"Re-key compound credential keys from indexOf to lastIndexOf split format",
|
|
29
|
+
|
|
30
|
+
async run(_workspaceDir: string): Promise<void> {
|
|
31
|
+
const {
|
|
32
|
+
listSecureKeysAsync,
|
|
33
|
+
getSecureKeyAsync,
|
|
34
|
+
setSecureKeyAsync,
|
|
35
|
+
deleteSecureKeyAsync,
|
|
36
|
+
} = await import("../../security/secure-keys.js");
|
|
37
|
+
|
|
38
|
+
const { accounts, unreachable } = await listSecureKeysAsync();
|
|
39
|
+
if (unreachable) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
"Credential store unreachable — migration will be retried on next startup",
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let migratedCount = 0;
|
|
46
|
+
let failedCount = 0;
|
|
47
|
+
|
|
48
|
+
for (const account of accounts) {
|
|
49
|
+
if (!account.startsWith(CREDENTIAL_PREFIX)) continue;
|
|
50
|
+
|
|
51
|
+
const rest = account.slice(CREDENTIAL_PREFIX.length);
|
|
52
|
+
const slashIdx = rest.indexOf("/");
|
|
53
|
+
if (slashIdx < 1 || slashIdx >= rest.length - 1) continue;
|
|
54
|
+
|
|
55
|
+
const oldService = rest.slice(0, slashIdx);
|
|
56
|
+
const oldField = rest.slice(slashIdx + 1);
|
|
57
|
+
|
|
58
|
+
// Only migrate keys where the field contains a colon — these were
|
|
59
|
+
// stored using the old indexOf(":") split and need re-keying.
|
|
60
|
+
if (!oldField.includes(":")) continue;
|
|
61
|
+
|
|
62
|
+
// Reconstruct the original "service:field" name and re-split with lastIndexOf
|
|
63
|
+
const originalName = `${oldService}:${oldField}`;
|
|
64
|
+
const lastColonIdx = originalName.lastIndexOf(":");
|
|
65
|
+
const newService = originalName.slice(0, lastColonIdx);
|
|
66
|
+
const newField = originalName.slice(lastColonIdx + 1);
|
|
67
|
+
const newKey = credentialKey(newService, newField);
|
|
68
|
+
|
|
69
|
+
// Skip if the key format didn't actually change
|
|
70
|
+
if (account === newKey) continue;
|
|
71
|
+
|
|
72
|
+
// Skip if the new key already exists (idempotent — may have been
|
|
73
|
+
// partially migrated or the user already stored under the new format)
|
|
74
|
+
const existingNewValue = await getSecureKeyAsync(newKey);
|
|
75
|
+
if (existingNewValue !== undefined) {
|
|
76
|
+
// New key exists — just clean up the old orphaned key
|
|
77
|
+
await deleteSecureKeyAsync(account);
|
|
78
|
+
log.info(
|
|
79
|
+
{ oldKey: account, newKey },
|
|
80
|
+
"Deleted orphaned old-format credential key (new key already exists)",
|
|
81
|
+
);
|
|
82
|
+
migratedCount++;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const value = await getSecureKeyAsync(account);
|
|
87
|
+
if (value === undefined) continue;
|
|
88
|
+
|
|
89
|
+
// Write new key first, then delete old key (crash-safe order)
|
|
90
|
+
const stored = await setSecureKeyAsync(newKey, value);
|
|
91
|
+
if (!stored) {
|
|
92
|
+
log.warn(
|
|
93
|
+
{ oldKey: account, newKey },
|
|
94
|
+
"Failed to write re-keyed credential — skipping",
|
|
95
|
+
);
|
|
96
|
+
failedCount++;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await deleteSecureKeyAsync(account);
|
|
101
|
+
migratedCount++;
|
|
102
|
+
log.info({ oldKey: account, newKey }, "Re-keyed compound credential");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (migratedCount > 0 || failedCount > 0) {
|
|
106
|
+
log.info(
|
|
107
|
+
{ migratedCount, failedCount },
|
|
108
|
+
"Compound credential key migration complete",
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
async down(_workspaceDir: string): Promise<void> {
|
|
114
|
+
// Reverse: re-key from lastIndexOf format back to indexOf format.
|
|
115
|
+
// Keys where the service contains ":" were migrated from old format.
|
|
116
|
+
const {
|
|
117
|
+
listSecureKeysAsync,
|
|
118
|
+
getSecureKeyAsync,
|
|
119
|
+
setSecureKeyAsync,
|
|
120
|
+
deleteSecureKeyAsync,
|
|
121
|
+
} = await import("../../security/secure-keys.js");
|
|
122
|
+
|
|
123
|
+
const { accounts, unreachable } = await listSecureKeysAsync();
|
|
124
|
+
if (unreachable) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
"Credential store unreachable — rollback will be retried on next startup",
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let rolledBackCount = 0;
|
|
131
|
+
let failedCount = 0;
|
|
132
|
+
|
|
133
|
+
for (const account of accounts) {
|
|
134
|
+
if (!account.startsWith(CREDENTIAL_PREFIX)) continue;
|
|
135
|
+
|
|
136
|
+
const rest = account.slice(CREDENTIAL_PREFIX.length);
|
|
137
|
+
const slashIdx = rest.indexOf("/");
|
|
138
|
+
if (slashIdx < 1 || slashIdx >= rest.length - 1) continue;
|
|
139
|
+
|
|
140
|
+
const service = rest.slice(0, slashIdx);
|
|
141
|
+
const field = rest.slice(slashIdx + 1);
|
|
142
|
+
|
|
143
|
+
// Only rollback keys where the service contains ":" — these are in
|
|
144
|
+
// the new lastIndexOf format and need reverting to indexOf format.
|
|
145
|
+
if (!service.includes(":")) continue;
|
|
146
|
+
|
|
147
|
+
// Reconstruct the original name and re-split with indexOf (old format)
|
|
148
|
+
const originalName = `${service}:${field}`;
|
|
149
|
+
const firstColonIdx = originalName.indexOf(":");
|
|
150
|
+
const oldService = originalName.slice(0, firstColonIdx);
|
|
151
|
+
const oldField = originalName.slice(firstColonIdx + 1);
|
|
152
|
+
const oldKey = credentialKey(oldService, oldField);
|
|
153
|
+
|
|
154
|
+
if (account === oldKey) continue;
|
|
155
|
+
|
|
156
|
+
const value = await getSecureKeyAsync(account);
|
|
157
|
+
if (value === undefined) continue;
|
|
158
|
+
|
|
159
|
+
const stored = await setSecureKeyAsync(oldKey, value);
|
|
160
|
+
if (!stored) {
|
|
161
|
+
log.warn(
|
|
162
|
+
{ newKey: account, oldKey },
|
|
163
|
+
"Failed to rollback re-keyed credential — skipping",
|
|
164
|
+
);
|
|
165
|
+
failedCount++;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
await deleteSecureKeyAsync(account);
|
|
170
|
+
rolledBackCount++;
|
|
171
|
+
log.info(
|
|
172
|
+
{ newKey: account, oldKey },
|
|
173
|
+
"Rolled back compound credential key",
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (rolledBackCount > 0 || failedCount > 0) {
|
|
178
|
+
log.info(
|
|
179
|
+
{ rolledBackCount, failedCount },
|
|
180
|
+
"Compound credential key rollback complete",
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readdirSync,
|
|
5
|
+
renameSync,
|
|
6
|
+
rmdirSync,
|
|
7
|
+
statSync,
|
|
8
|
+
} from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
|
|
11
|
+
import { desc, eq } from "drizzle-orm";
|
|
12
|
+
|
|
13
|
+
import { getDb } from "../../memory/db.js";
|
|
14
|
+
import { contacts } from "../../memory/schema/contacts.js";
|
|
15
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
16
|
+
|
|
17
|
+
export const scopeJournalToGuardianMigration: WorkspaceMigration = {
|
|
18
|
+
id: "019-scope-journal-to-guardian",
|
|
19
|
+
description:
|
|
20
|
+
"Move root journal entries into per-user subdirectory for guardian",
|
|
21
|
+
|
|
22
|
+
run(workspaceDir: string): void {
|
|
23
|
+
const journalDir = join(workspaceDir, "journal");
|
|
24
|
+
if (!existsSync(journalDir)) return;
|
|
25
|
+
|
|
26
|
+
// Find .md files in the root journal directory (not in subdirs)
|
|
27
|
+
let entries: string[];
|
|
28
|
+
try {
|
|
29
|
+
entries = readdirSync(journalDir);
|
|
30
|
+
} catch {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const mdFiles = entries.filter((f) => {
|
|
34
|
+
if (!f.endsWith(".md") || f.toLowerCase() === "readme.md") return false;
|
|
35
|
+
try {
|
|
36
|
+
return statSync(join(journalDir, f)).isFile();
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
if (mdFiles.length === 0) return;
|
|
42
|
+
|
|
43
|
+
// Resolve guardian user slug (same pattern as 017-seed-persona-dirs)
|
|
44
|
+
let slug = "guardian";
|
|
45
|
+
try {
|
|
46
|
+
const db = getDb();
|
|
47
|
+
const guardian = db
|
|
48
|
+
.select()
|
|
49
|
+
.from(contacts)
|
|
50
|
+
.where(eq(contacts.role, "guardian"))
|
|
51
|
+
.orderBy(desc(contacts.createdAt))
|
|
52
|
+
.limit(1)
|
|
53
|
+
.get();
|
|
54
|
+
if (guardian?.userFile) {
|
|
55
|
+
slug = guardian.userFile.replace(/\.md$/, "");
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
// DB not ready — use fallback "guardian"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Create per-user directory and move files (renameSync preserves birthtimes)
|
|
62
|
+
const destDir = join(journalDir, slug);
|
|
63
|
+
mkdirSync(destDir, { recursive: true });
|
|
64
|
+
for (const f of mdFiles) {
|
|
65
|
+
const src = join(journalDir, f);
|
|
66
|
+
const dest = join(destDir, f);
|
|
67
|
+
if (!existsSync(dest)) {
|
|
68
|
+
renameSync(src, dest);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
down(workspaceDir: string): void {
|
|
74
|
+
const journalDir = join(workspaceDir, "journal");
|
|
75
|
+
if (!existsSync(journalDir)) return;
|
|
76
|
+
let entries: string[];
|
|
77
|
+
try {
|
|
78
|
+
entries = readdirSync(journalDir);
|
|
79
|
+
} catch {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
for (const entry of entries) {
|
|
83
|
+
const subdir = join(journalDir, entry);
|
|
84
|
+
try {
|
|
85
|
+
if (!statSync(subdir).isDirectory()) continue;
|
|
86
|
+
} catch {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
for (const f of readdirSync(subdir)) {
|
|
90
|
+
if (!f.endsWith(".md")) continue;
|
|
91
|
+
const dest = join(journalDir, f);
|
|
92
|
+
if (!existsSync(dest)) {
|
|
93
|
+
renameSync(join(subdir, f), dest);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
rmdirSync(subdir);
|
|
98
|
+
} catch {
|
|
99
|
+
// not empty — leave it
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
};
|
|
@@ -2,19 +2,25 @@
|
|
|
2
2
|
* Workspace migration: Migrate workspace data from /data to /workspace volume.
|
|
3
3
|
*
|
|
4
4
|
* In the old Docker volume layout, workspace data lived at
|
|
5
|
-
* `$BASE_DATA_DIR/.vellum/workspace`. In the new layout,
|
|
5
|
+
* `$BASE_DATA_DIR/.vellum/workspace`. In the new layout, VELLUM_WORKSPACE_DIR points
|
|
6
6
|
* to a dedicated volume (e.g. `/workspace`). On first boot with the new layout,
|
|
7
7
|
* this migration copies existing workspace data from the old location to the
|
|
8
8
|
* new volume so nothing is lost.
|
|
9
9
|
*
|
|
10
10
|
* Idempotent:
|
|
11
|
-
* - Skips if
|
|
11
|
+
* - Skips if VELLUM_WORKSPACE_DIR is not set (non-Docker or old layout).
|
|
12
12
|
* - Skips if the workspace volume already has data (config.json exists).
|
|
13
13
|
* - Skips if the sentinel file exists (already migrated).
|
|
14
14
|
* - Skips if the old workspace directory doesn't exist or is empty.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
cpSync,
|
|
19
|
+
existsSync,
|
|
20
|
+
readdirSync,
|
|
21
|
+
unlinkSync,
|
|
22
|
+
writeFileSync,
|
|
23
|
+
} from "node:fs";
|
|
18
24
|
import { join } from "node:path";
|
|
19
25
|
|
|
20
26
|
import {
|
|
@@ -28,12 +34,28 @@ const SENTINEL_FILENAME = ".workspace-volume-migrated";
|
|
|
28
34
|
export const migrateToWorkspaceVolumeMigration: WorkspaceMigration = {
|
|
29
35
|
id: "014-migrate-to-workspace-volume",
|
|
30
36
|
description:
|
|
31
|
-
"Copy workspace data from old /data/.vellum/workspace to new
|
|
37
|
+
"Copy workspace data from old /data/.vellum/workspace to new VELLUM_WORKSPACE_DIR volume on first boot",
|
|
38
|
+
|
|
39
|
+
down(workspaceDir: string): void {
|
|
40
|
+
// This migration copies data between volumes. Actually reversing the copy
|
|
41
|
+
// (deleting data from the workspace volume) is dangerous and could cause
|
|
42
|
+
// data loss. Instead, we just remove the sentinel file so the migration
|
|
43
|
+
// will re-run and re-evaluate on next startup.
|
|
44
|
+
const sentinelPath = join(workspaceDir, SENTINEL_FILENAME);
|
|
45
|
+
if (existsSync(sentinelPath)) {
|
|
46
|
+
try {
|
|
47
|
+
unlinkSync(sentinelPath);
|
|
48
|
+
} catch {
|
|
49
|
+
// Best-effort — the migration runner's checkpoint removal will
|
|
50
|
+
// also ensure the migration re-runs.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
32
54
|
|
|
33
55
|
run(workspaceDir: string): void {
|
|
34
56
|
const workspaceDirOverride = getWorkspaceDirOverride();
|
|
35
57
|
|
|
36
|
-
// Only relevant when
|
|
58
|
+
// Only relevant when VELLUM_WORKSPACE_DIR is explicitly set (Docker with separate volume)
|
|
37
59
|
if (!workspaceDirOverride) return;
|
|
38
60
|
|
|
39
61
|
const sentinelPath = join(workspaceDir, SENTINEL_FILENAME);
|
|
@@ -10,6 +10,12 @@ import { appDirRenameMigration } from "./010-app-dir-rename.js";
|
|
|
10
10
|
import { backfillInstallationIdMigration } from "./011-backfill-installation-id.js";
|
|
11
11
|
import { renameConversationDiskViewDirsMigration } from "./012-rename-conversation-disk-view-dirs.js";
|
|
12
12
|
import { repairConversationDiskViewMigration } from "./013-repair-conversation-disk-view.js";
|
|
13
|
+
import { migrateCredentialsToKeychainMigration } from "./015-migrate-credentials-to-keychain.js";
|
|
14
|
+
import { extractFeatureFlagsToProtectedMigration } from "./016-extract-feature-flags-to-protected.js";
|
|
15
|
+
import { migrateCredentialsFromKeychainMigration } from "./016-migrate-credentials-from-keychain.js";
|
|
16
|
+
import { seedPersonaDirsMigration } from "./017-seed-persona-dirs.js";
|
|
17
|
+
import { rekeyCompoundCredentialKeysMigration } from "./018-rekey-compound-credential-keys.js";
|
|
18
|
+
import { scopeJournalToGuardianMigration } from "./019-scope-journal-to-guardian.js";
|
|
13
19
|
import { migrateToWorkspaceVolumeMigration } from "./migrate-to-workspace-volume.js";
|
|
14
20
|
import type { WorkspaceMigration } from "./types.js";
|
|
15
21
|
|
|
@@ -31,4 +37,10 @@ export const WORKSPACE_MIGRATIONS: WorkspaceMigration[] = [
|
|
|
31
37
|
renameConversationDiskViewDirsMigration,
|
|
32
38
|
repairConversationDiskViewMigration,
|
|
33
39
|
migrateToWorkspaceVolumeMigration,
|
|
40
|
+
migrateCredentialsToKeychainMigration,
|
|
41
|
+
migrateCredentialsFromKeychainMigration,
|
|
42
|
+
seedPersonaDirsMigration,
|
|
43
|
+
extractFeatureFlagsToProtectedMigration,
|
|
44
|
+
rekeyCompoundCredentialKeysMigration,
|
|
45
|
+
scopeJournalToGuardianMigration,
|
|
34
46
|
];
|
|
@@ -7,10 +7,16 @@ import type { WorkspaceMigration } from "./types.js";
|
|
|
7
7
|
|
|
8
8
|
const log = getLogger("workspace-migrations");
|
|
9
9
|
|
|
10
|
+
export function getLastWorkspaceMigrationId(
|
|
11
|
+
migrations: WorkspaceMigration[],
|
|
12
|
+
): string | null {
|
|
13
|
+
return migrations.length > 0 ? migrations[migrations.length - 1].id : null;
|
|
14
|
+
}
|
|
15
|
+
|
|
10
16
|
export type CheckpointFile = {
|
|
11
17
|
applied: Record<
|
|
12
18
|
string,
|
|
13
|
-
{ appliedAt: string; status?: "started" | "completed" }
|
|
19
|
+
{ appliedAt: string; status?: "started" | "completed" | "rolling_back" }
|
|
14
20
|
>;
|
|
15
21
|
};
|
|
16
22
|
|
|
@@ -73,7 +79,7 @@ export async function runWorkspaceMigrations(
|
|
|
73
79
|
const checkpoints = loadCheckpoints(workspaceDir);
|
|
74
80
|
|
|
75
81
|
for (const [id, entry] of Object.entries(checkpoints.applied)) {
|
|
76
|
-
if (entry.status === "started") {
|
|
82
|
+
if (entry.status === "started" || entry.status === "rolling_back") {
|
|
77
83
|
log.warn(
|
|
78
84
|
`Workspace migration "${id}" was interrupted during a previous run; will re-run`,
|
|
79
85
|
);
|
|
@@ -115,3 +121,101 @@ export async function runWorkspaceMigrations(
|
|
|
115
121
|
saveCheckpoints(workspaceDir, checkpoints);
|
|
116
122
|
}
|
|
117
123
|
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Roll back workspace (filesystem) migrations in reverse order, stopping before
|
|
127
|
+
* the target migration.
|
|
128
|
+
*
|
|
129
|
+
* Migrations after `targetMigrationId` in the registry array are reversed in
|
|
130
|
+
* reverse order; the target migration itself is kept applied.
|
|
131
|
+
*
|
|
132
|
+
* **Usage**: Pass the full migrations array (typically `WORKSPACE_MIGRATIONS`
|
|
133
|
+
* from `registry.ts`) and the ID of the migration you want to roll back *to*.
|
|
134
|
+
* For example, `rollbackWorkspaceMigrations(dir, migrations, "010-app-dir-rename")`
|
|
135
|
+
* rolls back all applied migrations that appear after `010-app-dir-rename` in
|
|
136
|
+
* the registry.
|
|
137
|
+
*
|
|
138
|
+
* **Checkpoint state**: Each rolled-back migration's entry is deleted from the
|
|
139
|
+
* `.workspace-migrations.json` checkpoint file. If the process crashes
|
|
140
|
+
* mid-rollback, the `"rolling_back"` marker is detected and cleared by
|
|
141
|
+
* `runWorkspaceMigrations` on the next startup (it re-runs interrupted
|
|
142
|
+
* migrations).
|
|
143
|
+
*
|
|
144
|
+
* **Warning — data loss**: Every workspace migration must define a `down()`
|
|
145
|
+
* method (enforced at the type level), but some rollbacks are lossy (e.g.,
|
|
146
|
+
* file deletions or format conversions that discard the original cannot fully
|
|
147
|
+
* restore prior state). Review each migration's `down()` implementation
|
|
148
|
+
* before calling this function.
|
|
149
|
+
*
|
|
150
|
+
* **Important**: Stop the assistant before running rollbacks. Rolling back
|
|
151
|
+
* workspace migrations while the assistant is running may cause file conflicts,
|
|
152
|
+
* stale caches, or data corruption.
|
|
153
|
+
*
|
|
154
|
+
* @param workspaceDir The workspace directory path (e.g., `~/.vellum/workspace`).
|
|
155
|
+
* @param migrations The full ordered array of workspace migrations (from `WORKSPACE_MIGRATIONS`).
|
|
156
|
+
* @param targetMigrationId The migration ID to roll back to (exclusive — all
|
|
157
|
+
* migrations after this one are reversed).
|
|
158
|
+
*/
|
|
159
|
+
export async function rollbackWorkspaceMigrations(
|
|
160
|
+
workspaceDir: string,
|
|
161
|
+
migrations: WorkspaceMigration[],
|
|
162
|
+
targetMigrationId: string,
|
|
163
|
+
): Promise<void> {
|
|
164
|
+
// Find the index of the target migration
|
|
165
|
+
const targetIndex = migrations.findIndex((m) => m.id === targetMigrationId);
|
|
166
|
+
if (targetIndex === -1) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`Target migration "${targetMigrationId}" not found in the migrations array`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Collect migrations that come after the target, in reverse order
|
|
173
|
+
const migrationsToRollback = migrations.slice(targetIndex + 1).reverse();
|
|
174
|
+
if (migrationsToRollback.length === 0) {
|
|
175
|
+
log.info("No migrations to roll back");
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const checkpoints = loadCheckpoints(workspaceDir);
|
|
180
|
+
|
|
181
|
+
for (const migration of migrationsToRollback) {
|
|
182
|
+
// Only roll back migrations that have been fully applied.
|
|
183
|
+
// Legacy checkpoints may not have a status field (just appliedAt) — treat
|
|
184
|
+
// missing/undefined status as completed, matching runWorkspaceMigrations behavior.
|
|
185
|
+
const entry = checkpoints.applied[migration.id];
|
|
186
|
+
if (
|
|
187
|
+
!entry ||
|
|
188
|
+
entry.status === "started" ||
|
|
189
|
+
entry.status === "rolling_back"
|
|
190
|
+
) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
log.info(
|
|
195
|
+
`Rolling back workspace migration: ${migration.id} — ${migration.description}`,
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
// Mark as rolling_back before execution (for crash recovery)
|
|
199
|
+
checkpoints.applied[migration.id] = {
|
|
200
|
+
appliedAt: checkpoints.applied[migration.id]!.appliedAt,
|
|
201
|
+
status: "rolling_back",
|
|
202
|
+
};
|
|
203
|
+
saveCheckpoints(workspaceDir, checkpoints);
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
await migration.down(workspaceDir);
|
|
207
|
+
} catch (error) {
|
|
208
|
+
log.error(
|
|
209
|
+
{ migrationId: migration.id, error },
|
|
210
|
+
`Workspace migration rollback failed: ${migration.id}`,
|
|
211
|
+
);
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Remove the migration entry from checkpoints
|
|
216
|
+
delete checkpoints.applied[migration.id];
|
|
217
|
+
saveCheckpoints(workspaceDir, checkpoints);
|
|
218
|
+
|
|
219
|
+
log.info(`Rolled back workspace migration: ${migration.id}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -8,4 +8,8 @@ export interface WorkspaceMigration {
|
|
|
8
8
|
* Must be idempotent — safe to re-run if it was interrupted.
|
|
9
9
|
* Both synchronous and asynchronous migrations are supported. */
|
|
10
10
|
run(workspaceDir: string): void | Promise<void>;
|
|
11
|
+
/** Reverse the migration. Receives the workspace directory path.
|
|
12
|
+
* Must be idempotent — safe to re-run if it was interrupted.
|
|
13
|
+
* Both synchronous and asynchronous rollbacks are supported. */
|
|
14
|
+
down(workspaceDir: string): void | Promise<void>;
|
|
11
15
|
}
|
|
@@ -52,15 +52,7 @@ const KEYLESS_PROVIDERS = new Set(["ollama"]);
|
|
|
52
52
|
const deterministicProvider = new DefaultCommitMessageProvider();
|
|
53
53
|
|
|
54
54
|
function getProviderCandidates(config: ReturnType<typeof getConfig>): string[] {
|
|
55
|
-
|
|
56
|
-
const seen = new Set<string>();
|
|
57
|
-
const out: string[] = [];
|
|
58
|
-
for (const name of [config.services.inference.provider, ...order]) {
|
|
59
|
-
if (seen.has(name)) continue;
|
|
60
|
-
seen.add(name);
|
|
61
|
-
out.push(name);
|
|
62
|
-
}
|
|
63
|
-
return out;
|
|
55
|
+
return [config.services.inference.provider];
|
|
64
56
|
}
|
|
65
57
|
|
|
66
58
|
function buildDeterministicResult(
|
|
@@ -121,7 +113,7 @@ export class ProviderCommitMessageGenerator {
|
|
|
121
113
|
|
|
122
114
|
// ── Fallback check order (canonical) ──────────────────────────────
|
|
123
115
|
// 1. disabled
|
|
124
|
-
// 2. resolve configured provider
|
|
116
|
+
// 2. resolve configured provider:
|
|
125
117
|
// - missing_provider_api_key OR provider_not_initialized
|
|
126
118
|
// 3. selected-provider API key preflight (except keyless providers)
|
|
127
119
|
// 4. breaker_open
|
|
@@ -138,7 +130,7 @@ export class ProviderCommitMessageGenerator {
|
|
|
138
130
|
return buildDeterministicResult(context, "disabled");
|
|
139
131
|
}
|
|
140
132
|
|
|
141
|
-
// Step 2: Resolve configured provider
|
|
133
|
+
// Step 2: Resolve configured provider.
|
|
142
134
|
// If nothing is resolvable, differentiate likely missing-key cases from
|
|
143
135
|
// true registry/init failures.
|
|
144
136
|
const resolved = await resolveConfiguredProvider();
|
|
@@ -168,18 +160,17 @@ export class ProviderCommitMessageGenerator {
|
|
|
168
160
|
}
|
|
169
161
|
|
|
170
162
|
const provider = resolved.provider;
|
|
171
|
-
const
|
|
163
|
+
const providerName = resolved.configuredProviderName;
|
|
172
164
|
|
|
173
|
-
// Step 2b: API key preflight for the
|
|
174
|
-
if (!KEYLESS_PROVIDERS.has(
|
|
175
|
-
const providerApiKey = await getSecureKeyAsync(
|
|
165
|
+
// Step 2b: API key preflight for the configured provider (skip keyless).
|
|
166
|
+
if (!KEYLESS_PROVIDERS.has(providerName)) {
|
|
167
|
+
const providerApiKey = await getSecureKeyAsync(providerName);
|
|
176
168
|
if (!providerApiKey) {
|
|
177
169
|
log.debug(
|
|
178
170
|
{
|
|
179
|
-
|
|
180
|
-
configuredProvider: config.services.inference.provider,
|
|
171
|
+
provider: providerName,
|
|
181
172
|
},
|
|
182
|
-
"
|
|
173
|
+
"Provider API key missing; falling back to deterministic",
|
|
183
174
|
);
|
|
184
175
|
return buildDeterministicResult(context, "missing_provider_api_key");
|
|
185
176
|
}
|
|
@@ -211,13 +202,13 @@ export class ProviderCommitMessageGenerator {
|
|
|
211
202
|
|
|
212
203
|
// Step 5: Fast model preflight — resolve before any provider call
|
|
213
204
|
const fastModel =
|
|
214
|
-
llmConfig.providerFastModelOverrides[
|
|
215
|
-
PROVIDER_DEFAULT_FAST_MODELS[
|
|
205
|
+
llmConfig.providerFastModelOverrides[providerName] ??
|
|
206
|
+
PROVIDER_DEFAULT_FAST_MODELS[providerName];
|
|
216
207
|
|
|
217
208
|
if (!fastModel) {
|
|
218
209
|
log.debug(
|
|
219
210
|
{
|
|
220
|
-
provider:
|
|
211
|
+
provider: providerName,
|
|
221
212
|
configuredProvider: config.services.inference.provider,
|
|
222
213
|
},
|
|
223
214
|
"No fast model resolvable for provider; falling back to deterministic",
|