@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
|
@@ -17,6 +17,10 @@ import type { WorkspaceMigration } from "./types.js";
|
|
|
17
17
|
const LEGACY_CONVERSATION_DIR_PATTERN =
|
|
18
18
|
/^(.*)_(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}\.\d{3}Z)$/;
|
|
19
19
|
|
|
20
|
+
/** Matches the new timestamp-first format: {timestamp}_{conversationId} */
|
|
21
|
+
const NEW_CONVERSATION_DIR_PATTERN =
|
|
22
|
+
/^(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}\.\d{3}Z)_(.+)$/;
|
|
23
|
+
|
|
20
24
|
function parseLegacyConversationDirName(
|
|
21
25
|
dirName: string,
|
|
22
26
|
): { conversationId: string; timestamp: string } | null {
|
|
@@ -29,11 +33,51 @@ function parseLegacyConversationDirName(
|
|
|
29
33
|
};
|
|
30
34
|
}
|
|
31
35
|
|
|
36
|
+
function parseNewConversationDirName(
|
|
37
|
+
dirName: string,
|
|
38
|
+
): { timestamp: string; conversationId: string } | null {
|
|
39
|
+
const match = dirName.match(NEW_CONVERSATION_DIR_PATTERN);
|
|
40
|
+
if (!match) return null;
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
timestamp: match[1],
|
|
44
|
+
conversationId: match[2],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
32
48
|
export const renameConversationDiskViewDirsMigration: WorkspaceMigration = {
|
|
33
49
|
id: "012-rename-conversation-disk-view-dirs",
|
|
34
50
|
description:
|
|
35
51
|
"Rename legacy conversation disk-view directories to timestamp-first names",
|
|
36
52
|
|
|
53
|
+
down(workspaceDir: string): void {
|
|
54
|
+
const conversationsDir = join(workspaceDir, "conversations");
|
|
55
|
+
if (!existsSync(conversationsDir)) return;
|
|
56
|
+
|
|
57
|
+
const entries = readdirSync(conversationsDir, { withFileTypes: true })
|
|
58
|
+
.filter((entry) => entry.isDirectory())
|
|
59
|
+
.map((entry) => entry.name)
|
|
60
|
+
.sort();
|
|
61
|
+
|
|
62
|
+
for (const dirName of entries) {
|
|
63
|
+
const parsed = parseNewConversationDirName(dirName);
|
|
64
|
+
if (!parsed) continue;
|
|
65
|
+
|
|
66
|
+
const sourcePath = join(conversationsDir, dirName);
|
|
67
|
+
const targetName = `${parsed.conversationId}_${parsed.timestamp}`;
|
|
68
|
+
const targetPath = join(conversationsDir, targetName);
|
|
69
|
+
|
|
70
|
+
if (sourcePath === targetPath) continue;
|
|
71
|
+
if (existsSync(targetPath)) continue;
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
renameSync(sourcePath, targetPath);
|
|
75
|
+
} catch {
|
|
76
|
+
// Best-effort: leave the directory in place if a single rename fails.
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
|
|
37
81
|
run(workspaceDir: string): void {
|
|
38
82
|
const conversationsDir = join(workspaceDir, "conversations");
|
|
39
83
|
if (!existsSync(conversationsDir)) return;
|
|
@@ -8,4 +8,9 @@ export const repairConversationDiskViewMigration: WorkspaceMigration = {
|
|
|
8
8
|
run(_workspaceDir: string): void {
|
|
9
9
|
rebuildConversationDiskViewFromDb();
|
|
10
10
|
},
|
|
11
|
+
// No-op: this is a repair migration that rebuilds derived disk-view data
|
|
12
|
+
// from the database. There is no meaningful reverse operation — the data
|
|
13
|
+
// is a cache that can be regenerated, and removing it would just cause
|
|
14
|
+
// unnecessary churn on the next forward run.
|
|
15
|
+
down(_workspaceDir: string): void {},
|
|
11
16
|
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { getLogger } from "../../util/logger.js";
|
|
2
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
3
|
+
|
|
4
|
+
const log = getLogger("workspace-migrations");
|
|
5
|
+
|
|
6
|
+
const BROKER_WAIT_INTERVAL_MS = 500;
|
|
7
|
+
const BROKER_WAIT_MAX_ATTEMPTS = 10; // 5 seconds total
|
|
8
|
+
|
|
9
|
+
export const migrateCredentialsToKeychainMigration: WorkspaceMigration = {
|
|
10
|
+
id: "015-migrate-credentials-to-keychain",
|
|
11
|
+
description:
|
|
12
|
+
"Copy encrypted store credentials to keychain for single-backend migration",
|
|
13
|
+
|
|
14
|
+
async down(_workspaceDir: string): Promise<void> {
|
|
15
|
+
// Reverse: copy credentials from keychain back to encrypted store.
|
|
16
|
+
// Mirrors the forward logic of 016-migrate-credentials-from-keychain.
|
|
17
|
+
if (
|
|
18
|
+
process.env.VELLUM_DESKTOP_APP !== "1" ||
|
|
19
|
+
process.env.VELLUM_DEV === "1"
|
|
20
|
+
) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { createBrokerClient } =
|
|
25
|
+
await import("../../security/keychain-broker-client.js");
|
|
26
|
+
const client = createBrokerClient();
|
|
27
|
+
|
|
28
|
+
let brokerAvailable = false;
|
|
29
|
+
for (let i = 0; i < BROKER_WAIT_MAX_ATTEMPTS; i++) {
|
|
30
|
+
if (client.isAvailable()) {
|
|
31
|
+
brokerAvailable = true;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
await new Promise((r) => setTimeout(r, BROKER_WAIT_INTERVAL_MS));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!brokerAvailable) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
"Keychain broker not available after waiting — credential rollback " +
|
|
40
|
+
"will be retried on next startup",
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { setKey } = await import("../../security/encrypted-store.js");
|
|
45
|
+
|
|
46
|
+
const accounts = await client.list();
|
|
47
|
+
if (accounts.length === 0) return;
|
|
48
|
+
|
|
49
|
+
let rolledBackCount = 0;
|
|
50
|
+
let failedCount = 0;
|
|
51
|
+
|
|
52
|
+
for (const account of accounts) {
|
|
53
|
+
const result = await client.get(account);
|
|
54
|
+
if (!result || !result.found || result.value === undefined) {
|
|
55
|
+
log.warn(
|
|
56
|
+
{ account },
|
|
57
|
+
"Failed to read key from keychain during rollback — skipping",
|
|
58
|
+
);
|
|
59
|
+
failedCount++;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const written = setKey(account, result.value);
|
|
64
|
+
if (written) {
|
|
65
|
+
await client.del(account);
|
|
66
|
+
rolledBackCount++;
|
|
67
|
+
} else {
|
|
68
|
+
log.warn(
|
|
69
|
+
{ account },
|
|
70
|
+
"Failed to write key to encrypted store during rollback — skipping",
|
|
71
|
+
);
|
|
72
|
+
failedCount++;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
log.info(
|
|
77
|
+
{ rolledBackCount, failedCount },
|
|
78
|
+
"Credential rollback from keychain to encrypted store complete",
|
|
79
|
+
);
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
async run(_workspaceDir: string): Promise<void> {
|
|
83
|
+
// Only run on mac production builds (desktop app, non-dev).
|
|
84
|
+
if (
|
|
85
|
+
process.env.VELLUM_DESKTOP_APP !== "1" ||
|
|
86
|
+
process.env.VELLUM_DEV === "1"
|
|
87
|
+
) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const { createBrokerClient } =
|
|
92
|
+
await import("../../security/keychain-broker-client.js");
|
|
93
|
+
const client = createBrokerClient();
|
|
94
|
+
|
|
95
|
+
// Wait for the broker to become available (up to 5 seconds), matching
|
|
96
|
+
// the retry strategy in secure-keys.ts waitForBrokerAvailability().
|
|
97
|
+
let brokerAvailable = false;
|
|
98
|
+
for (let i = 0; i < BROKER_WAIT_MAX_ATTEMPTS; i++) {
|
|
99
|
+
if (client.isAvailable()) {
|
|
100
|
+
brokerAvailable = true;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
await new Promise((r) => setTimeout(r, BROKER_WAIT_INTERVAL_MS));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!brokerAvailable) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
"Keychain broker not available after waiting — credential migration " +
|
|
109
|
+
"will be retried on next startup",
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const { listKeys, getKey, deleteKey } =
|
|
114
|
+
await import("../../security/encrypted-store.js");
|
|
115
|
+
|
|
116
|
+
const accounts = listKeys();
|
|
117
|
+
if (accounts.length === 0) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let migratedCount = 0;
|
|
122
|
+
let failedCount = 0;
|
|
123
|
+
|
|
124
|
+
for (const account of accounts) {
|
|
125
|
+
const value = getKey(account);
|
|
126
|
+
if (value === undefined) {
|
|
127
|
+
log.warn(
|
|
128
|
+
{ account },
|
|
129
|
+
"Failed to read key from encrypted store — skipping",
|
|
130
|
+
);
|
|
131
|
+
failedCount++;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const result = await client.set(account, value);
|
|
136
|
+
if (result.status === "ok") {
|
|
137
|
+
deleteKey(account);
|
|
138
|
+
migratedCount++;
|
|
139
|
+
} else {
|
|
140
|
+
log.warn(
|
|
141
|
+
{ account, status: result.status },
|
|
142
|
+
"Failed to write key to keychain — skipping",
|
|
143
|
+
);
|
|
144
|
+
failedCount++;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
log.info(
|
|
149
|
+
{ migratedCount, failedCount },
|
|
150
|
+
"Credential migration to keychain complete",
|
|
151
|
+
);
|
|
152
|
+
},
|
|
153
|
+
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import {
|
|
2
|
+
chmodSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
renameSync,
|
|
7
|
+
unlinkSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
|
|
12
|
+
import { getRootDir } from "../../util/platform.js";
|
|
13
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
14
|
+
|
|
15
|
+
export const extractFeatureFlagsToProtectedMigration: WorkspaceMigration = {
|
|
16
|
+
id: "016-extract-feature-flags-to-protected",
|
|
17
|
+
description:
|
|
18
|
+
"Move assistantFeatureFlagValues from config.json to ~/.vellum/protected/feature-flags.json",
|
|
19
|
+
|
|
20
|
+
down(workspaceDir: string): void {
|
|
21
|
+
// Reverse: read feature flags from protected directory and write them
|
|
22
|
+
// back to config.json as assistantFeatureFlagValues.
|
|
23
|
+
const protectedDir = join(getRootDir(), "protected");
|
|
24
|
+
const featureFlagsPath = join(protectedDir, "feature-flags.json");
|
|
25
|
+
|
|
26
|
+
if (!existsSync(featureFlagsPath)) return;
|
|
27
|
+
|
|
28
|
+
let flagValues: Record<string, boolean>;
|
|
29
|
+
try {
|
|
30
|
+
const raw = JSON.parse(readFileSync(featureFlagsPath, "utf-8"));
|
|
31
|
+
if (
|
|
32
|
+
!raw ||
|
|
33
|
+
raw.version !== 1 ||
|
|
34
|
+
!raw.values ||
|
|
35
|
+
typeof raw.values !== "object"
|
|
36
|
+
) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
flagValues = raw.values;
|
|
40
|
+
} catch {
|
|
41
|
+
return; // Malformed file — skip
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (Object.keys(flagValues).length === 0) return;
|
|
45
|
+
|
|
46
|
+
// Read config.json and restore assistantFeatureFlagValues
|
|
47
|
+
const configPath = join(workspaceDir, "config.json");
|
|
48
|
+
let config: Record<string, unknown> = {};
|
|
49
|
+
if (existsSync(configPath)) {
|
|
50
|
+
try {
|
|
51
|
+
const raw = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
52
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
53
|
+
config = raw as Record<string, unknown>;
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
// Malformed config — start with empty object
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Merge into existing assistantFeatureFlagValues if present
|
|
61
|
+
const existing = (config.assistantFeatureFlagValues ?? {}) as Record<
|
|
62
|
+
string,
|
|
63
|
+
boolean
|
|
64
|
+
>;
|
|
65
|
+
config.assistantFeatureFlagValues = { ...existing, ...flagValues };
|
|
66
|
+
|
|
67
|
+
const tmpConfigPath = configPath + ".tmp";
|
|
68
|
+
writeFileSync(
|
|
69
|
+
tmpConfigPath,
|
|
70
|
+
JSON.stringify(config, null, 2) + "\n",
|
|
71
|
+
"utf-8",
|
|
72
|
+
);
|
|
73
|
+
renameSync(tmpConfigPath, configPath);
|
|
74
|
+
|
|
75
|
+
// Remove the protected feature-flags file
|
|
76
|
+
try {
|
|
77
|
+
unlinkSync(featureFlagsPath);
|
|
78
|
+
} catch {
|
|
79
|
+
// Best-effort cleanup
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
run(workspaceDir: string): void {
|
|
84
|
+
const configPath = join(workspaceDir, "config.json");
|
|
85
|
+
if (!existsSync(configPath)) return;
|
|
86
|
+
|
|
87
|
+
let config: Record<string, unknown>;
|
|
88
|
+
try {
|
|
89
|
+
const raw = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
90
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
|
|
91
|
+
config = raw as Record<string, unknown>;
|
|
92
|
+
} catch {
|
|
93
|
+
return; // Malformed config — skip
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const flagValues = config.assistantFeatureFlagValues as
|
|
97
|
+
| Record<string, boolean>
|
|
98
|
+
| undefined;
|
|
99
|
+
if (
|
|
100
|
+
!flagValues ||
|
|
101
|
+
typeof flagValues !== "object" ||
|
|
102
|
+
Object.keys(flagValues).length === 0
|
|
103
|
+
) {
|
|
104
|
+
return; // Nothing to migrate
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Write feature flags to protected directory
|
|
108
|
+
const protectedDir = join(getRootDir(), "protected");
|
|
109
|
+
mkdirSync(protectedDir, { recursive: true });
|
|
110
|
+
|
|
111
|
+
const featureFlagsPath = join(protectedDir, "feature-flags.json");
|
|
112
|
+
|
|
113
|
+
// Read existing feature-flags.json if present (may have been written by
|
|
114
|
+
// the gateway in a rolling deployment) so we merge rather than overwrite.
|
|
115
|
+
let existingValues: Record<string, boolean> = {};
|
|
116
|
+
if (existsSync(featureFlagsPath)) {
|
|
117
|
+
try {
|
|
118
|
+
const existing = JSON.parse(readFileSync(featureFlagsPath, "utf-8"));
|
|
119
|
+
if (
|
|
120
|
+
existing.version === 1 &&
|
|
121
|
+
existing.values &&
|
|
122
|
+
typeof existing.values === "object"
|
|
123
|
+
) {
|
|
124
|
+
existingValues = existing.values;
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
// Malformed file — start fresh
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Merge: config values take precedence, existing keys preserved
|
|
132
|
+
const mergedValues = { ...existingValues, ...flagValues };
|
|
133
|
+
|
|
134
|
+
const featureFlagsContent = JSON.stringify(
|
|
135
|
+
{ version: 1, values: mergedValues },
|
|
136
|
+
null,
|
|
137
|
+
2,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const tmpFeatureFlagsPath = featureFlagsPath + ".tmp";
|
|
141
|
+
writeFileSync(tmpFeatureFlagsPath, featureFlagsContent + "\n", "utf-8");
|
|
142
|
+
chmodSync(tmpFeatureFlagsPath, 0o600);
|
|
143
|
+
renameSync(tmpFeatureFlagsPath, featureFlagsPath);
|
|
144
|
+
|
|
145
|
+
// Remove assistantFeatureFlagValues from config.json
|
|
146
|
+
delete config.assistantFeatureFlagValues;
|
|
147
|
+
|
|
148
|
+
const tmpConfigPath = configPath + ".tmp";
|
|
149
|
+
writeFileSync(
|
|
150
|
+
tmpConfigPath,
|
|
151
|
+
JSON.stringify(config, null, 2) + "\n",
|
|
152
|
+
"utf-8",
|
|
153
|
+
);
|
|
154
|
+
renameSync(tmpConfigPath, configPath);
|
|
155
|
+
},
|
|
156
|
+
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { getLogger } from "../../util/logger.js";
|
|
2
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
3
|
+
|
|
4
|
+
const log = getLogger("workspace-migrations");
|
|
5
|
+
|
|
6
|
+
const BROKER_WAIT_INTERVAL_MS = 500;
|
|
7
|
+
const BROKER_WAIT_MAX_ATTEMPTS = 10; // 5 seconds total
|
|
8
|
+
|
|
9
|
+
export const migrateCredentialsFromKeychainMigration: WorkspaceMigration = {
|
|
10
|
+
id: "016-migrate-credentials-from-keychain",
|
|
11
|
+
description:
|
|
12
|
+
"Copy keychain credentials back to encrypted store for CES unification",
|
|
13
|
+
|
|
14
|
+
async down(_workspaceDir: string): Promise<void> {
|
|
15
|
+
// Reverse: copy credentials from encrypted store back to keychain.
|
|
16
|
+
// Mirrors the forward logic of 015-migrate-credentials-to-keychain.
|
|
17
|
+
if (
|
|
18
|
+
process.env.VELLUM_DESKTOP_APP !== "1" ||
|
|
19
|
+
process.env.VELLUM_DEV === "1"
|
|
20
|
+
) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { createBrokerClient } =
|
|
25
|
+
await import("../../security/keychain-broker-client.js");
|
|
26
|
+
const client = createBrokerClient();
|
|
27
|
+
|
|
28
|
+
let brokerAvailable = false;
|
|
29
|
+
for (let i = 0; i < BROKER_WAIT_MAX_ATTEMPTS; i++) {
|
|
30
|
+
if (client.isAvailable()) {
|
|
31
|
+
brokerAvailable = true;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
await new Promise((r) => setTimeout(r, BROKER_WAIT_INTERVAL_MS));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!brokerAvailable) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
"Keychain broker not available after waiting — credential rollback " +
|
|
40
|
+
"will be retried on next startup",
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { listKeys, getKey, deleteKey } =
|
|
45
|
+
await import("../../security/encrypted-store.js");
|
|
46
|
+
|
|
47
|
+
const accounts = listKeys();
|
|
48
|
+
if (accounts.length === 0) return;
|
|
49
|
+
|
|
50
|
+
let rolledBackCount = 0;
|
|
51
|
+
let failedCount = 0;
|
|
52
|
+
|
|
53
|
+
for (const account of accounts) {
|
|
54
|
+
const value = getKey(account);
|
|
55
|
+
if (value === undefined) {
|
|
56
|
+
log.warn(
|
|
57
|
+
{ account },
|
|
58
|
+
"Failed to read key from encrypted store during rollback — skipping",
|
|
59
|
+
);
|
|
60
|
+
failedCount++;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const result = await client.set(account, value);
|
|
65
|
+
if (result.status === "ok") {
|
|
66
|
+
deleteKey(account);
|
|
67
|
+
rolledBackCount++;
|
|
68
|
+
} else {
|
|
69
|
+
log.warn(
|
|
70
|
+
{ account, status: result.status },
|
|
71
|
+
"Failed to write key to keychain during rollback — skipping",
|
|
72
|
+
);
|
|
73
|
+
failedCount++;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
log.info(
|
|
78
|
+
{ rolledBackCount, failedCount },
|
|
79
|
+
"Credential rollback from encrypted store to keychain complete",
|
|
80
|
+
);
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
async run(_workspaceDir: string): Promise<void> {
|
|
84
|
+
// Only run on mac production builds (desktop app, non-dev).
|
|
85
|
+
if (
|
|
86
|
+
process.env.VELLUM_DESKTOP_APP !== "1" ||
|
|
87
|
+
process.env.VELLUM_DEV === "1"
|
|
88
|
+
) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const { createBrokerClient } =
|
|
93
|
+
await import("../../security/keychain-broker-client.js");
|
|
94
|
+
const client = createBrokerClient();
|
|
95
|
+
|
|
96
|
+
// Wait for the broker to become available (up to 5 seconds), matching
|
|
97
|
+
// the retry strategy in secure-keys.ts waitForBrokerAvailability().
|
|
98
|
+
let brokerAvailable = false;
|
|
99
|
+
for (let i = 0; i < BROKER_WAIT_MAX_ATTEMPTS; i++) {
|
|
100
|
+
if (client.isAvailable()) {
|
|
101
|
+
brokerAvailable = true;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
await new Promise((r) => setTimeout(r, BROKER_WAIT_INTERVAL_MS));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!brokerAvailable) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
"Keychain broker not available after waiting — credential migration " +
|
|
110
|
+
"will be retried on next startup",
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const { setKey } = await import("../../security/encrypted-store.js");
|
|
115
|
+
|
|
116
|
+
const accounts = await client.list();
|
|
117
|
+
if (accounts.length === 0) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let migratedCount = 0;
|
|
122
|
+
let failedCount = 0;
|
|
123
|
+
|
|
124
|
+
for (const account of accounts) {
|
|
125
|
+
const result = await client.get(account);
|
|
126
|
+
if (!result || !result.found || result.value === undefined) {
|
|
127
|
+
log.warn({ account }, "Failed to read key from keychain — skipping");
|
|
128
|
+
failedCount++;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const written = setKey(account, result.value);
|
|
133
|
+
if (written) {
|
|
134
|
+
await client.del(account);
|
|
135
|
+
migratedCount++;
|
|
136
|
+
} else {
|
|
137
|
+
log.warn(
|
|
138
|
+
{ account },
|
|
139
|
+
"Failed to write key to encrypted store — skipping",
|
|
140
|
+
);
|
|
141
|
+
failedCount++;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
log.info(
|
|
146
|
+
{ migratedCount, failedCount },
|
|
147
|
+
"Credential migration from keychain complete",
|
|
148
|
+
);
|
|
149
|
+
},
|
|
150
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {
|
|
2
|
+
copyFileSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
rmdirSync,
|
|
8
|
+
} from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
|
|
11
|
+
import { desc, eq } from "drizzle-orm";
|
|
12
|
+
|
|
13
|
+
import { generateUserFileSlug } from "../../contacts/contact-store.js";
|
|
14
|
+
import { getDb } from "../../memory/db.js";
|
|
15
|
+
import { contacts } from "../../memory/schema/contacts.js";
|
|
16
|
+
import {
|
|
17
|
+
isTemplateContent,
|
|
18
|
+
stripCommentLines,
|
|
19
|
+
} from "../../prompts/system-prompt.js";
|
|
20
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
21
|
+
|
|
22
|
+
export const seedPersonaDirsMigration: WorkspaceMigration = {
|
|
23
|
+
id: "017-seed-persona-dirs",
|
|
24
|
+
description:
|
|
25
|
+
"Create users/ and channels/ persona directories and migrate customized USER.md",
|
|
26
|
+
|
|
27
|
+
down(workspaceDir: string): void {
|
|
28
|
+
// Remove the seeded persona directories only if they are empty.
|
|
29
|
+
// We don't delete user-created content — only clean up the empty
|
|
30
|
+
// directories that the forward migration created.
|
|
31
|
+
const usersDir = join(workspaceDir, "users");
|
|
32
|
+
const channelsDir = join(workspaceDir, "channels");
|
|
33
|
+
|
|
34
|
+
for (const dir of [usersDir, channelsDir]) {
|
|
35
|
+
if (!existsSync(dir)) continue;
|
|
36
|
+
try {
|
|
37
|
+
const entries = readdirSync(dir);
|
|
38
|
+
if (entries.length === 0) {
|
|
39
|
+
rmdirSync(dir);
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
// Best-effort: skip if we can't read or remove
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
run(workspaceDir: string): void {
|
|
48
|
+
// Create persona directories
|
|
49
|
+
mkdirSync(join(workspaceDir, "users"), { recursive: true });
|
|
50
|
+
mkdirSync(join(workspaceDir, "channels"), { recursive: true });
|
|
51
|
+
|
|
52
|
+
// Check if USER.md exists and has been customized
|
|
53
|
+
const userMdPath = join(workspaceDir, "USER.md");
|
|
54
|
+
if (!existsSync(userMdPath)) return;
|
|
55
|
+
|
|
56
|
+
const rawContent = readFileSync(userMdPath, "utf-8");
|
|
57
|
+
const content = stripCommentLines(rawContent);
|
|
58
|
+
if (!content) return;
|
|
59
|
+
|
|
60
|
+
// Skip if the content is the unmodified template
|
|
61
|
+
if (isTemplateContent(content, "USER.md")) return;
|
|
62
|
+
|
|
63
|
+
// Determine destination filename based on guardian contact
|
|
64
|
+
let destFilename = "guardian.md";
|
|
65
|
+
try {
|
|
66
|
+
const db = getDb();
|
|
67
|
+
const guardian = db
|
|
68
|
+
.select()
|
|
69
|
+
.from(contacts)
|
|
70
|
+
.where(eq(contacts.role, "guardian"))
|
|
71
|
+
.orderBy(desc(contacts.createdAt))
|
|
72
|
+
.limit(1)
|
|
73
|
+
.get();
|
|
74
|
+
|
|
75
|
+
if (guardian) {
|
|
76
|
+
if (guardian.userFile) {
|
|
77
|
+
destFilename = guardian.userFile;
|
|
78
|
+
} else {
|
|
79
|
+
const slug = generateUserFileSlug(guardian.displayName);
|
|
80
|
+
db.update(contacts)
|
|
81
|
+
.set({ userFile: slug })
|
|
82
|
+
.where(eq(contacts.id, guardian.id))
|
|
83
|
+
.run();
|
|
84
|
+
destFilename = slug;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
// DB might not be initialized yet — fall back to guardian.md
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const destPath = join(workspaceDir, "users", destFilename);
|
|
92
|
+
if (!existsSync(destPath)) {
|
|
93
|
+
copyFileSync(userMdPath, destPath);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
};
|