@vellumai/assistant 0.5.5 → 0.5.7
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 +4 -5
- package/README.md +0 -2
- package/bun.lock +0 -414
- package/docs/architecture/keychain-broker.md +45 -240
- 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/rpc.ts +119 -0
- 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 +1 -2
- package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
- package/src/__tests__/browser-skill-endstate.test.ts +6 -5
- package/src/__tests__/btw-routes.test.ts +0 -39
- package/src/__tests__/call-domain.test.ts +0 -128
- package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -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 -1
- package/src/__tests__/config-schema.test.ts +3 -3
- package/src/__tests__/context-window-manager.test.ts +78 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
- package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
- package/src/__tests__/conversation-skill-tools.test.ts +0 -54
- package/src/__tests__/conversation-title-service.test.ts +117 -1
- package/src/__tests__/credential-execution-feature-gates.test.ts +28 -14
- package/src/__tests__/credential-execution-managed-contract.test.ts +33 -18
- package/src/__tests__/credential-security-e2e.test.ts +0 -66
- package/src/__tests__/credential-security-invariants.test.ts +4 -45
- package/src/__tests__/credentials-cli.test.ts +78 -0
- package/src/__tests__/db-migration-rollback.test.ts +2015 -1
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +98 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
- 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__/keychain-broker-client.test.ts +161 -22
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
- package/src/__tests__/memory-regressions.test.ts +8 -30
- 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 +0 -5
- package/src/__tests__/notification-decision-fallback.test.ts +4 -0
- package/src/__tests__/notification-decision-identity.test.ts +4 -0
- package/src/__tests__/permission-types.test.ts +1 -0
- package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
- package/src/__tests__/qdrant-manager.test.ts +28 -2
- package/src/__tests__/registry.test.ts +0 -6
- package/src/__tests__/require-fresh-approval.test.ts +4 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
- package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
- package/src/__tests__/secure-keys.test.ts +83 -263
- 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-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__/suggestion-routes.test.ts +1 -32
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +4 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
- package/src/__tests__/tool-executor.test.ts +4 -0
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
- 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-scoped-grant-consumer.test.ts +0 -6
- 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 +218 -0
- package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
- package/src/calls/audio-store.test.ts +97 -0
- package/src/calls/audio-store.ts +205 -0
- package/src/calls/call-controller.ts +85 -7
- package/src/calls/call-domain.ts +3 -0
- package/src/calls/call-store.ts +10 -3
- package/src/calls/fish-audio-client.ts +117 -0
- package/src/calls/relay-server.ts +27 -0
- package/src/calls/twilio-routes.ts +2 -1
- package/src/calls/types.ts +1 -0
- package/src/calls/voice-ingress-preflight.ts +0 -42
- package/src/calls/voice-quality.ts +26 -5
- package/src/calls/voice-session-bridge.ts +6 -12
- package/src/cli/commands/config.ts +1 -4
- package/src/cli/commands/conversations.ts +0 -18
- package/src/cli/commands/credentials.ts +34 -4
- package/src/cli/commands/oauth/index.ts +7 -0
- package/src/cli/commands/oauth/platform.ts +179 -0
- package/src/cli/commands/platform.ts +3 -3
- package/src/config/assistant-feature-flags.ts +186 -5
- package/src/config/bundled-skills/messaging/SKILL.md +5 -5
- package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
- package/src/config/bundled-skills/settings/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
- package/src/config/bundled-tool-registry.ts +1 -11
- package/src/config/env-registry.ts +1 -1
- package/src/config/env.ts +16 -16
- package/src/config/feature-flag-registry.json +48 -16
- package/src/config/loader.ts +98 -31
- package/src/config/schema.ts +4 -25
- package/src/config/schemas/calls.ts +13 -0
- package/src/config/schemas/fish-audio.ts +39 -0
- package/src/config/schemas/memory.ts +0 -4
- package/src/config/schemas/platform.ts +1 -1
- package/src/config/schemas/security.ts +4 -4
- package/src/config/types.ts +0 -1
- package/src/contacts/contact-store.ts +39 -0
- package/src/contacts/types.ts +2 -0
- package/src/context/window-manager.ts +53 -2
- 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/daemon/assistant-attachments.ts +9 -0
- package/src/daemon/config-watcher.ts +6 -4
- package/src/daemon/conversation-agent-loop.ts +0 -60
- package/src/daemon/conversation-memory.ts +0 -117
- package/src/daemon/conversation-runtime-assembly.ts +0 -2
- package/src/daemon/conversation-tool-setup.ts +0 -105
- package/src/daemon/conversation.ts +10 -1
- package/src/daemon/handlers/config-vercel.ts +92 -0
- package/src/daemon/handlers/conversations.ts +0 -11
- package/src/daemon/handlers/skills.ts +2 -15
- package/src/daemon/install-symlink.ts +195 -0
- package/src/daemon/lifecycle.ts +229 -96
- package/src/daemon/message-types/conversations.ts +3 -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 +30 -92
- package/src/events/domain-events.ts +2 -1
- package/src/followups/followup-store.ts +5 -2
- package/src/inbound/platform-callback-registration.ts +3 -3
- package/src/instrument.ts +8 -5
- package/src/memory/conversation-crud.ts +0 -236
- package/src/memory/conversation-title-service.ts +76 -11
- package/src/memory/db-init.ts +15 -11
- package/src/memory/indexer.ts +15 -106
- package/src/memory/items-extractor.ts +15 -1
- package/src/memory/job-handlers/conversation-starters.ts +4 -1
- package/src/memory/job-handlers/embedding.ts +0 -79
- package/src/memory/job-utils.ts +1 -1
- package/src/memory/jobs-store.ts +30 -13
- package/src/memory/jobs-worker.ts +31 -27
- 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/189-drop-simplified-memory.ts +42 -0
- 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/index.ts +5 -3
- package/src/memory/migrations/registry.ts +90 -0
- package/src/memory/migrations/validate-migration-state.ts +137 -11
- package/src/memory/qdrant-circuit-breaker.ts +9 -0
- package/src/memory/qdrant-client.ts +4 -6
- package/src/memory/qdrant-manager.ts +64 -7
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/contacts.ts +1 -0
- package/src/memory/schema/conversations.ts +0 -3
- package/src/memory/schema/index.ts +0 -2
- package/src/messaging/draft-store.ts +2 -2
- package/src/notifications/decision-engine.ts +4 -1
- package/src/oauth/connection-resolver.ts +6 -4
- package/src/permissions/checker.ts +0 -38
- package/src/permissions/defaults.ts +3 -3
- package/src/permissions/shell-identity.ts +76 -22
- package/src/permissions/trust-client.ts +2 -13
- package/src/permissions/trust-store.ts +8 -3
- package/src/permissions/types.ts +4 -2
- package/src/platform/client.ts +35 -7
- package/src/prompts/persona-resolver.ts +138 -0
- package/src/prompts/system-prompt.ts +36 -4
- package/src/prompts/templates/users/default.md +1 -0
- package/src/providers/registry.ts +27 -40
- 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/external-assistant-id.ts +13 -59
- package/src/runtime/auth/route-policy.ts +29 -1
- package/src/runtime/auth/token-service.ts +53 -15
- package/src/runtime/channel-readiness-service.ts +1 -16
- package/src/runtime/http-server.ts +29 -2
- package/src/runtime/middleware/error-handler.ts +1 -9
- package/src/runtime/routes/audio-routes.ts +40 -0
- package/src/runtime/routes/btw-routes.ts +0 -17
- package/src/runtime/routes/conversation-management-routes.ts +0 -36
- package/src/runtime/routes/conversation-query-routes.ts +106 -2
- package/src/runtime/routes/conversation-routes.ts +4 -43
- package/src/runtime/routes/diagnostics-routes.ts +1 -477
- package/src/runtime/routes/identity-routes.ts +18 -29
- 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/vercel.ts +89 -0
- package/src/runtime/routes/log-export-routes.ts +5 -0
- package/src/runtime/routes/memory-item-routes.test.ts +221 -3
- package/src/runtime/routes/memory-item-routes.ts +144 -4
- 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/settings-routes.ts +41 -1
- package/src/runtime/routes/tts-routes.ts +86 -0
- package/src/runtime/routes/upgrade-broadcast-routes.ts +175 -0
- 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/schedule/schedule-store.ts +0 -21
- package/src/security/ces-credential-client.ts +59 -22
- package/src/security/ces-rpc-credential-backend.ts +85 -0
- package/src/security/credential-backend.ts +12 -88
- package/src/security/keychain-broker-client.ts +10 -2
- package/src/security/secure-keys.ts +94 -113
- package/src/skills/catalog-install.ts +13 -7
- package/src/skills/inline-command-render.ts +5 -1
- package/src/skills/inline-command-runner.ts +30 -2
- package/src/telemetry/usage-telemetry-reporter.ts +4 -2
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/executor.ts +0 -4
- package/src/tools/memory/handlers.ts +1 -129
- package/src/tools/network/script-proxy/session-manager.ts +19 -4
- package/src/tools/network/web-fetch.ts +3 -1
- package/src/tools/permission-checker.ts +18 -0
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/skills/load.ts +9 -2
- package/src/tools/types.ts +0 -8
- package/src/util/errors.ts +0 -12
- package/src/util/platform.ts +8 -55
- package/src/util/xml.ts +8 -0
- package/src/workspace/git-service.ts +5 -2
- package/src/workspace/heartbeat-service.ts +5 -24
- 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 +95 -0
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +23 -1
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/workspace/migrations/runner.ts +106 -2
- package/src/workspace/migrations/types.ts +4 -0
- package/src/__tests__/archive-recall.test.ts +0 -560
- 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__/conversation-memory-dirty-tail.test.ts +0 -150
- package/src/__tests__/conversation-switch-memory-reduction.test.ts +0 -474
- package/src/__tests__/db-memory-archive-migration.test.ts +0 -372
- package/src/__tests__/db-memory-brief-state-migration.test.ts +0 -213
- package/src/__tests__/db-memory-reducer-checkpoints.test.ts +0 -273
- package/src/__tests__/diagnostics-export.test.ts +0 -288
- package/src/__tests__/local-gateway-health.test.ts +0 -209
- package/src/__tests__/memory-brief-open-loops.test.ts +0 -530
- package/src/__tests__/memory-brief-time.test.ts +0 -285
- package/src/__tests__/memory-brief-wrapper.test.ts +0 -311
- package/src/__tests__/memory-chunk-archive.test.ts +0 -400
- package/src/__tests__/memory-chunk-dual-write.test.ts +0 -453
- package/src/__tests__/memory-episode-archive.test.ts +0 -370
- package/src/__tests__/memory-episode-dual-write.test.ts +0 -626
- package/src/__tests__/memory-observation-archive.test.ts +0 -375
- package/src/__tests__/memory-observation-dual-write.test.ts +0 -318
- package/src/__tests__/memory-reducer-job.test.ts +0 -538
- package/src/__tests__/memory-reducer-scheduling.test.ts +0 -473
- package/src/__tests__/memory-reducer-store.test.ts +0 -728
- package/src/__tests__/memory-reducer-types.test.ts +0 -707
- package/src/__tests__/memory-reducer.test.ts +0 -704
- package/src/__tests__/memory-simplified-config.test.ts +0 -281
- package/src/__tests__/secret-ingress-handler.test.ts +0 -120
- package/src/__tests__/simplified-memory-e2e.test.ts +0 -666
- package/src/__tests__/simplified-memory-runtime.test.ts +0 -616
- 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/memory-simplified.ts +0 -101
- package/src/config/schemas/swarm.ts +0 -82
- package/src/logfire.ts +0 -135
- package/src/memory/archive-recall.ts +0 -516
- package/src/memory/archive-store.ts +0 -400
- package/src/memory/brief-formatting.ts +0 -33
- package/src/memory/brief-open-loops.ts +0 -266
- package/src/memory/brief-time.ts +0 -162
- package/src/memory/brief.ts +0 -75
- package/src/memory/job-handlers/backfill-simplified-memory.ts +0 -462
- package/src/memory/job-handlers/reduce-conversation-memory.ts +0 -229
- package/src/memory/migrations/185-memory-brief-state.ts +0 -52
- package/src/memory/migrations/186-memory-archive.ts +0 -109
- package/src/memory/migrations/187-memory-reducer-checkpoints.ts +0 -19
- package/src/memory/reducer-scheduler.ts +0 -242
- package/src/memory/reducer-store.ts +0 -271
- package/src/memory/reducer-types.ts +0 -106
- package/src/memory/reducer.ts +0 -467
- package/src/memory/schema/memory-archive.ts +0 -121
- package/src/memory/schema/memory-brief.ts +0 -55
- 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
|
@@ -137,6 +137,24 @@ export class PermissionChecker {
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
if (result.decision === "prompt") {
|
|
140
|
+
// dangerouslySkipPermissions: when enabled, auto-approve all prompts
|
|
141
|
+
// without user interaction. Deny rules are still respected (they
|
|
142
|
+
// return before reaching this block).
|
|
143
|
+
//
|
|
144
|
+
// Note: unlike guardian auto-approve and temporary overrides below,
|
|
145
|
+
// this intentionally does NOT check `context.requireFreshApproval`.
|
|
146
|
+
// The setting is designed to skip ALL interactive prompts
|
|
147
|
+
// unconditionally — it is an explicit operator opt-out from the
|
|
148
|
+
// permission system, so requireFreshApproval does not apply.
|
|
149
|
+
const cfg = getConfig();
|
|
150
|
+
if (cfg.permissions.dangerouslySkipPermissions) {
|
|
151
|
+
log.info(
|
|
152
|
+
{ toolName: name, riskLevel },
|
|
153
|
+
"dangerouslySkipPermissions active — auto-approving without prompt",
|
|
154
|
+
);
|
|
155
|
+
return { allowed: true, decision: "dangerously_skip_permissions", riskLevel };
|
|
156
|
+
}
|
|
157
|
+
|
|
140
158
|
// Guardian-trust sessions (e.g. scheduled jobs, reminders) should be
|
|
141
159
|
// able to use bundled tools without interactive approval. The guardian
|
|
142
160
|
// is the owner - prompting makes no sense when there is no client.
|
|
@@ -30,7 +30,7 @@ export class SkillExecuteTool implements Tool {
|
|
|
30
30
|
activity: {
|
|
31
31
|
type: "string",
|
|
32
32
|
description:
|
|
33
|
-
"Brief non-technical explanation of what you are doing and why, shown to the user as a
|
|
33
|
+
"Brief non-technical explanation of what you are doing and why, shown to the user as a progress update.",
|
|
34
34
|
},
|
|
35
35
|
},
|
|
36
36
|
required: ["tool", "input", "activity"],
|
package/src/tools/skills/load.ts
CHANGED
|
@@ -440,9 +440,16 @@ export class SkillLoadTool implements Tool {
|
|
|
440
440
|
"Rendered inline command expansions for included skill",
|
|
441
441
|
);
|
|
442
442
|
} catch (err) {
|
|
443
|
-
log.
|
|
443
|
+
log.error(
|
|
444
444
|
{ err, skillId: childId, parentSkillId: skill.id },
|
|
445
|
-
"Failed to render inline commands for included skill
|
|
445
|
+
"Failed to render inline commands for included skill; falling back to sanitized body",
|
|
446
|
+
);
|
|
447
|
+
// Strip raw !`...` inline command tokens so they don't leak into
|
|
448
|
+
// the prompt. Replace with a safe stub to maintain fail-closed
|
|
449
|
+
// contract for raw tokens while still isolating child failures.
|
|
450
|
+
childBody = childBody.replace(
|
|
451
|
+
/!`[^`]*`/g,
|
|
452
|
+
"[inline command unavailable]",
|
|
446
453
|
);
|
|
447
454
|
}
|
|
448
455
|
}
|
package/src/tools/types.ts
CHANGED
|
@@ -118,14 +118,6 @@ export interface ToolContext {
|
|
|
118
118
|
proxyToolResolver?: ProxyToolResolver;
|
|
119
119
|
/** When set, only tools in this set may execute. Tools outside the set are blocked with an error. */
|
|
120
120
|
allowedToolNames?: Set<string>;
|
|
121
|
-
/** Request user confirmation for a sub-tool operation (used by claude_code tool). */
|
|
122
|
-
requestConfirmation?: (req: {
|
|
123
|
-
toolName: string;
|
|
124
|
-
input: Record<string, unknown>;
|
|
125
|
-
riskLevel: string;
|
|
126
|
-
executionTarget?: ExecutionTarget;
|
|
127
|
-
principal?: string;
|
|
128
|
-
}) => Promise<{ decision: "allow" | "deny" }>;
|
|
129
121
|
/** Prompt the user for a secret value via native SecureField UI. */
|
|
130
122
|
requestSecret?: (params: {
|
|
131
123
|
service: string;
|
package/src/util/errors.ts
CHANGED
|
@@ -20,9 +20,6 @@ export enum ErrorCode {
|
|
|
20
20
|
// WASM integrity check failures
|
|
21
21
|
INTEGRITY_ERROR = "INTEGRITY_ERROR",
|
|
22
22
|
|
|
23
|
-
// Secret detected in inbound content
|
|
24
|
-
INGRESS_BLOCKED = "INGRESS_BLOCKED",
|
|
25
|
-
|
|
26
23
|
// Internal/unexpected errors
|
|
27
24
|
INTERNAL_ERROR = "INTERNAL_ERROR",
|
|
28
25
|
}
|
|
@@ -178,12 +175,3 @@ export class IntegrityError extends AssistantError {
|
|
|
178
175
|
}
|
|
179
176
|
}
|
|
180
177
|
|
|
181
|
-
export class IngressBlockedError extends AssistantError {
|
|
182
|
-
constructor(
|
|
183
|
-
message: string,
|
|
184
|
-
public readonly detectedTypes: string[],
|
|
185
|
-
) {
|
|
186
|
-
super(message, ErrorCode.INGRESS_BLOCKED);
|
|
187
|
-
this.name = "IngressBlockedError";
|
|
188
|
-
}
|
|
189
|
-
}
|
package/src/util/platform.ts
CHANGED
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
existsSync,
|
|
4
4
|
mkdirSync,
|
|
5
5
|
readFileSync,
|
|
6
|
-
writeFileSync,
|
|
7
6
|
} from "node:fs";
|
|
8
7
|
import { homedir } from "node:os";
|
|
9
8
|
import { join } from "node:path";
|
|
@@ -45,25 +44,6 @@ export function getClipboardCommand(): string | null {
|
|
|
45
44
|
return null;
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
/**
|
|
49
|
-
* Read and parse the lockfile (~/.vellum.lock.json).
|
|
50
|
-
* Respects BASE_DATA_DIR for non-standard home directories.
|
|
51
|
-
* Returns null if the file doesn't exist or is malformed.
|
|
52
|
-
*/
|
|
53
|
-
export function readLockfile(): Record<string, unknown> | null {
|
|
54
|
-
const base = getBaseDataDir() || homedir();
|
|
55
|
-
const lockPath = join(base, ".vellum.lock.json");
|
|
56
|
-
if (!existsSync(lockPath)) return null;
|
|
57
|
-
try {
|
|
58
|
-
const raw = JSON.parse(readFileSync(lockPath, "utf-8"));
|
|
59
|
-
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
60
|
-
return raw as Record<string, unknown>;
|
|
61
|
-
}
|
|
62
|
-
} catch {
|
|
63
|
-
// malformed JSON
|
|
64
|
-
}
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
47
|
|
|
68
48
|
/**
|
|
69
49
|
* Resolve the instance data directory from the lockfile.
|
|
@@ -155,45 +135,18 @@ export function resolveInstanceDataDir(): string | undefined {
|
|
|
155
135
|
* (see migration 007-assistant-id-to-self). However, the desktop UI
|
|
156
136
|
* sends the real assistant ID (e.g., "vellum-true-eel") while the
|
|
157
137
|
* inbound call path resolves phone numbers to config keys (typically
|
|
158
|
-
* "self"). This function maps
|
|
138
|
+
* "self"). This function maps the current assistant's ID to "self"
|
|
159
139
|
* so both sides use a consistent DB key.
|
|
160
|
-
*
|
|
161
|
-
* Multi-instance safety: each daemon process runs with a scoped
|
|
162
|
-
* BASE_DATA_DIR, so readLockfile() only sees the lockfile for this
|
|
163
|
-
* instance. The mapping to "self" is correct because each daemon is
|
|
164
|
-
* single-tenant — it only manages its own instance's data.
|
|
165
140
|
*/
|
|
166
141
|
export function normalizeAssistantId(assistantId: string): string {
|
|
167
142
|
if (assistantId === "self") return "self";
|
|
168
143
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const assistants = lockData?.assistants as
|
|
172
|
-
| Array<Record<string, unknown>>
|
|
173
|
-
| undefined;
|
|
174
|
-
if (assistants) {
|
|
175
|
-
for (const entry of assistants) {
|
|
176
|
-
if (entry.assistantId === assistantId) return "self";
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
} catch {
|
|
180
|
-
// lockfile unreadable — return as-is
|
|
181
|
-
}
|
|
144
|
+
const ownName = process.env.VELLUM_ASSISTANT_NAME;
|
|
145
|
+
if (ownName && assistantId === ownName) return "self";
|
|
182
146
|
|
|
183
147
|
return assistantId;
|
|
184
148
|
}
|
|
185
149
|
|
|
186
|
-
/**
|
|
187
|
-
* Write data to the primary lockfile (~/.vellum.lock.json).
|
|
188
|
-
* Respects BASE_DATA_DIR for non-standard home directories.
|
|
189
|
-
*/
|
|
190
|
-
export function writeLockfile(data: Record<string, unknown>): void {
|
|
191
|
-
const base = getBaseDataDir() || homedir();
|
|
192
|
-
writeFileSync(
|
|
193
|
-
join(base, ".vellum.lock.json"),
|
|
194
|
-
JSON.stringify(data, null, 2) + "\n",
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
150
|
|
|
198
151
|
/**
|
|
199
152
|
* Returns the root ~/.vellum directory. User-facing files (config, prompt
|
|
@@ -436,11 +389,11 @@ export function ensureDataDir(): void {
|
|
|
436
389
|
const dirs = [
|
|
437
390
|
// Root-level dirs (runtime)
|
|
438
391
|
root,
|
|
439
|
-
//
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
392
|
+
// signals dir is needed everywhere (MCP reload, user-message signals)
|
|
393
|
+
join(root, "signals"),
|
|
394
|
+
// protected, hooks are local-only — skip in containerized mode
|
|
395
|
+
// (credentials via CES HTTP API, trust via gateway API)
|
|
396
|
+
...(containerized ? [] : [join(root, "protected"), join(root, "hooks")]),
|
|
444
397
|
// Workspace dirs
|
|
445
398
|
workspace,
|
|
446
399
|
join(workspace, "skills"),
|
package/src/util/xml.ts
CHANGED
|
@@ -6,3 +6,11 @@ export function escapeXmlAttr(s: string): string {
|
|
|
6
6
|
.replace(/</g, "<")
|
|
7
7
|
.replace(/>/g, ">");
|
|
8
8
|
}
|
|
9
|
+
|
|
10
|
+
/** Escape a string for safe inclusion as XML/HTML text content. */
|
|
11
|
+
export function escapeXmlContent(s: string): string {
|
|
12
|
+
return s
|
|
13
|
+
.replace(/&/g, "&")
|
|
14
|
+
.replace(/</g, "<")
|
|
15
|
+
.replace(/>/g, ">");
|
|
16
|
+
}
|
|
@@ -753,8 +753,11 @@ export class WorkspaceGitService {
|
|
|
753
753
|
* Must be called with the mutex lock held.
|
|
754
754
|
*/
|
|
755
755
|
private async ensureCommitIdentityLocked(): Promise<void> {
|
|
756
|
-
|
|
757
|
-
|
|
756
|
+
const gitName = process.env.ASSISTANT_GIT_USER_NAME || "Vellum Assistant";
|
|
757
|
+
const gitEmail =
|
|
758
|
+
process.env.ASSISTANT_GIT_USER_EMAIL || "assistant@vellum.ai";
|
|
759
|
+
await this.execGit(["config", "user.name", gitName]);
|
|
760
|
+
await this.execGit(["config", "user.email", gitEmail]);
|
|
758
761
|
}
|
|
759
762
|
|
|
760
763
|
/**
|
|
@@ -210,13 +210,11 @@ export class WorkspaceHeartbeatService {
|
|
|
210
210
|
|
|
211
211
|
try {
|
|
212
212
|
const now = this.now();
|
|
213
|
-
let shutdownFiles: string[] = [];
|
|
214
213
|
const { committed } = await service.commitIfDirty(
|
|
215
214
|
(st) => {
|
|
216
215
|
const uniqueFiles = [
|
|
217
216
|
...new Set([...st.staged, ...st.modified, ...st.untracked]),
|
|
218
217
|
];
|
|
219
|
-
shutdownFiles = uniqueFiles;
|
|
220
218
|
log.info(
|
|
221
219
|
{ workspaceDir, totalChanges: uniqueFiles.length },
|
|
222
220
|
"Committing pending changes on shutdown",
|
|
@@ -237,28 +235,11 @@ export class WorkspaceHeartbeatService {
|
|
|
237
235
|
if (committed) {
|
|
238
236
|
firstSeenDirty.delete(workspaceDir);
|
|
239
237
|
result.committed++;
|
|
240
|
-
|
|
241
|
-
//
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
workspaceDir,
|
|
246
|
-
trigger: "shutdown",
|
|
247
|
-
changedFiles: shutdownFiles,
|
|
248
|
-
timestampMs: this.now(),
|
|
249
|
-
};
|
|
250
|
-
getEnrichmentService().enqueue({
|
|
251
|
-
workspaceDir,
|
|
252
|
-
commitHash,
|
|
253
|
-
context: shutdownCtx,
|
|
254
|
-
gitService: service,
|
|
255
|
-
});
|
|
256
|
-
} catch (enrichErr) {
|
|
257
|
-
log.debug(
|
|
258
|
-
{ enrichErr },
|
|
259
|
-
"Failed to enqueue shutdown enrichment (non-fatal)",
|
|
260
|
-
);
|
|
261
|
-
}
|
|
238
|
+
// Skip enrichment for shutdown commits — the enrichment queue is
|
|
239
|
+
// about to be shut down anyway, and the fire-and-forget writeNote()
|
|
240
|
+
// can race with subsequent commitAllPending() calls (the async
|
|
241
|
+
// git-notes operation acquires the mutex and may leave behind an
|
|
242
|
+
// index.lock on some git versions, causing the next commit to fail).
|
|
262
243
|
} else {
|
|
263
244
|
result.skipped++;
|
|
264
245
|
}
|
|
@@ -22,4 +22,19 @@ export const avatarRenameMigration: WorkspaceMigration = {
|
|
|
22
22
|
renameSync(oldTraits, newTraits);
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
|
+
down(workspaceDir: string): void {
|
|
26
|
+
const avatarDir = join(workspaceDir, "data", "avatar");
|
|
27
|
+
|
|
28
|
+
const newImage = join(avatarDir, "avatar-image.png");
|
|
29
|
+
const oldImage = join(avatarDir, "custom-avatar.png");
|
|
30
|
+
if (existsSync(newImage) && !existsSync(oldImage)) {
|
|
31
|
+
renameSync(newImage, oldImage);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const newTraits = join(avatarDir, "character-traits.json");
|
|
35
|
+
const oldTraits = join(avatarDir, "avatar-components.json");
|
|
36
|
+
if (existsSync(newTraits) && !existsSync(oldTraits)) {
|
|
37
|
+
renameSync(newTraits, oldTraits);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
25
40
|
};
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
unlinkSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} from "node:fs";
|
|
2
8
|
import { join } from "node:path";
|
|
3
9
|
|
|
4
10
|
import { getDeviceIdBaseDir } from "../../util/device-id.js";
|
|
@@ -97,4 +103,14 @@ export const seedDeviceIdMigration: WorkspaceMigration = {
|
|
|
97
103
|
// Best-effort — getDeviceId() will generate a new one if this fails.
|
|
98
104
|
}
|
|
99
105
|
},
|
|
106
|
+
down(_workspaceDir: string): void {
|
|
107
|
+
// The forward migration seeds deviceId in ~/.vellum/device.json from the
|
|
108
|
+
// lockfile. Reverse by removing device.json entirely — getDeviceId() will
|
|
109
|
+
// generate a fresh one on next startup if needed.
|
|
110
|
+
const base = getDeviceIdBaseDir();
|
|
111
|
+
const devicePath = join(base, ".vellum", "device.json");
|
|
112
|
+
if (existsSync(devicePath)) {
|
|
113
|
+
unlinkSync(devicePath);
|
|
114
|
+
}
|
|
115
|
+
},
|
|
100
116
|
};
|
|
@@ -45,6 +45,39 @@ export const extractCollectUsageDataMigration: WorkspaceMigration = {
|
|
|
45
45
|
delete config.assistantFeatureFlagValues;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
49
|
+
},
|
|
50
|
+
down(workspaceDir: string): void {
|
|
51
|
+
const configPath = join(workspaceDir, "config.json");
|
|
52
|
+
if (!existsSync(configPath)) return;
|
|
53
|
+
|
|
54
|
+
let config: Record<string, unknown>;
|
|
55
|
+
try {
|
|
56
|
+
const raw = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
57
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
|
|
58
|
+
config = raw as Record<string, unknown>;
|
|
59
|
+
} catch {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Only reverse if collectUsageData was explicitly set to false
|
|
64
|
+
// (the forward migration only persisted false).
|
|
65
|
+
if (!("collectUsageData" in config)) return;
|
|
66
|
+
const value = config.collectUsageData;
|
|
67
|
+
if (typeof value !== "boolean") return;
|
|
68
|
+
|
|
69
|
+
// Restore the feature flag value
|
|
70
|
+
const FLAG_KEY = "feature_flags.collect-usage-data.enabled";
|
|
71
|
+
const flagValues = (config.assistantFeatureFlagValues ?? {}) as Record<
|
|
72
|
+
string,
|
|
73
|
+
unknown
|
|
74
|
+
>;
|
|
75
|
+
flagValues[FLAG_KEY] = value;
|
|
76
|
+
config.assistantFeatureFlagValues = flagValues;
|
|
77
|
+
|
|
78
|
+
// Remove the extracted top-level key
|
|
79
|
+
delete config.collectUsageData;
|
|
80
|
+
|
|
48
81
|
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
49
82
|
},
|
|
50
83
|
};
|
|
@@ -9,4 +9,7 @@ export const addSendDiagnosticsMigration: WorkspaceMigration = {
|
|
|
9
9
|
// will sync the UserDefaults value on first startup. This migration exists
|
|
10
10
|
// as a checkpoint marker for future reference.
|
|
11
11
|
},
|
|
12
|
+
down(_workspaceDir: string): void {
|
|
13
|
+
// No-op — the forward migration is a checkpoint marker with no data changes.
|
|
14
|
+
},
|
|
12
15
|
};
|
|
@@ -132,6 +132,55 @@ export const servicesConfigMigration: WorkspaceMigration = {
|
|
|
132
132
|
delete config.imageGenModel;
|
|
133
133
|
delete config.webSearchProvider;
|
|
134
134
|
|
|
135
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
136
|
+
},
|
|
137
|
+
down(workspaceDir: string): void {
|
|
138
|
+
const configPath = join(workspaceDir, "config.json");
|
|
139
|
+
if (!existsSync(configPath)) return;
|
|
140
|
+
|
|
141
|
+
let config: Record<string, unknown>;
|
|
142
|
+
try {
|
|
143
|
+
const raw = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
144
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
|
|
145
|
+
config = raw as Record<string, unknown>;
|
|
146
|
+
} catch {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const services = config.services;
|
|
151
|
+
if (!services || typeof services !== "object" || Array.isArray(services))
|
|
152
|
+
return;
|
|
153
|
+
|
|
154
|
+
const svc = services as Record<string, Record<string, unknown>>;
|
|
155
|
+
|
|
156
|
+
// Extract inference provider and model back to top-level fields.
|
|
157
|
+
// Note: inferenceMode is lost in this rollback — the original config did
|
|
158
|
+
// not store a mode field. This is an accepted lossy reversal.
|
|
159
|
+
if (svc.inference) {
|
|
160
|
+
if (typeof svc.inference.provider === "string") {
|
|
161
|
+
config.provider = svc.inference.provider;
|
|
162
|
+
}
|
|
163
|
+
if (typeof svc.inference.model === "string") {
|
|
164
|
+
config.model = svc.inference.model;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Extract image generation model back to top-level
|
|
169
|
+
if (svc["image-generation"]) {
|
|
170
|
+
if (typeof svc["image-generation"].model === "string") {
|
|
171
|
+
config.imageGenModel = svc["image-generation"].model;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Extract web search provider back to top-level
|
|
176
|
+
if (svc["web-search"]) {
|
|
177
|
+
if (typeof svc["web-search"].provider === "string") {
|
|
178
|
+
config.webSearchProvider = svc["web-search"].provider;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
delete config.services;
|
|
183
|
+
|
|
135
184
|
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
136
185
|
},
|
|
137
186
|
};
|
|
@@ -34,4 +34,31 @@ export const webSearchProviderRenameMigration: WorkspaceMigration = {
|
|
|
34
34
|
ws.provider = "inference-provider-native";
|
|
35
35
|
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
36
36
|
},
|
|
37
|
+
down(workspaceDir: string): void {
|
|
38
|
+
const configPath = join(workspaceDir, "config.json");
|
|
39
|
+
if (!existsSync(configPath)) return;
|
|
40
|
+
|
|
41
|
+
let config: Record<string, unknown>;
|
|
42
|
+
try {
|
|
43
|
+
const raw = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
44
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
|
|
45
|
+
config = raw as Record<string, unknown>;
|
|
46
|
+
} catch {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const services = config.services;
|
|
51
|
+
if (!services || typeof services !== "object" || Array.isArray(services))
|
|
52
|
+
return;
|
|
53
|
+
|
|
54
|
+
const webSearch = (services as Record<string, unknown>)["web-search"];
|
|
55
|
+
if (!webSearch || typeof webSearch !== "object" || Array.isArray(webSearch))
|
|
56
|
+
return;
|
|
57
|
+
|
|
58
|
+
const ws = webSearch as Record<string, unknown>;
|
|
59
|
+
if (ws.provider !== "inference-provider-native") return;
|
|
60
|
+
|
|
61
|
+
ws.provider = "anthropic-native";
|
|
62
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
63
|
+
},
|
|
37
64
|
};
|
|
@@ -9,4 +9,7 @@ export const voiceTimeoutAndMaxStepsMigration: WorkspaceMigration = {
|
|
|
9
9
|
// Existing users: macOS client will sync UserDefaults values
|
|
10
10
|
// to config on next startup via settings sync endpoints.
|
|
11
11
|
},
|
|
12
|
+
down(_workspaceDir: string): void {
|
|
13
|
+
// No-op — the forward migration is a checkpoint marker with no data changes.
|
|
14
|
+
},
|
|
12
15
|
};
|
|
@@ -7,4 +7,8 @@ export const backfillConversationDiskViewMigration: WorkspaceMigration = {
|
|
|
7
7
|
run(_workspaceDir: string): void {
|
|
8
8
|
rebuildConversationDiskViewFromDb();
|
|
9
9
|
},
|
|
10
|
+
// No-op: the disk view is a derived cache that can be regenerated from the
|
|
11
|
+
// database at any time. Removing it would only cause unnecessary I/O churn
|
|
12
|
+
// since the next forward migration (or startup rebuild) will recreate it.
|
|
13
|
+
down(_workspaceDir: string): void {},
|
|
10
14
|
};
|
|
@@ -76,6 +76,84 @@ export const appDirRenameMigration: WorkspaceMigration = {
|
|
|
76
76
|
description:
|
|
77
77
|
"Rename UUID-based app directories and files to human-readable slugified names",
|
|
78
78
|
|
|
79
|
+
down(workspaceDir: string): void {
|
|
80
|
+
const appsDir = join(workspaceDir, "data", "apps");
|
|
81
|
+
if (!existsSync(appsDir)) return;
|
|
82
|
+
|
|
83
|
+
const jsonFiles = readdirSync(appsDir)
|
|
84
|
+
.filter((f) => f.endsWith(".json"))
|
|
85
|
+
.sort();
|
|
86
|
+
|
|
87
|
+
if (jsonFiles.length === 0) return;
|
|
88
|
+
|
|
89
|
+
for (const jsonFile of jsonFiles) {
|
|
90
|
+
const jsonPath = join(appsDir, jsonFile);
|
|
91
|
+
let raw: string;
|
|
92
|
+
try {
|
|
93
|
+
raw = readFileSync(jsonPath, "utf-8");
|
|
94
|
+
} catch {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let parsed: {
|
|
99
|
+
id?: string;
|
|
100
|
+
name?: string;
|
|
101
|
+
dirName?: string;
|
|
102
|
+
};
|
|
103
|
+
try {
|
|
104
|
+
parsed = JSON.parse(raw);
|
|
105
|
+
} catch {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const appId = parsed.id;
|
|
110
|
+
if (!appId || !parsed.dirName || !isValidDirName(parsed.dirName)) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const dirName = parsed.dirName;
|
|
115
|
+
|
|
116
|
+
// 1. Rename the app directory: {dirName}/ -> {appId}/
|
|
117
|
+
const slugDir = join(appsDir, dirName);
|
|
118
|
+
const uuidDir = join(appsDir, appId);
|
|
119
|
+
if (existsSync(slugDir) && !existsSync(uuidDir) && slugDir !== uuidDir) {
|
|
120
|
+
renameSync(slugDir, uuidDir);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 2. Rename the preview file: {dirName}.preview -> {appId}.preview
|
|
124
|
+
const slugPreview = join(appsDir, `${dirName}.preview`);
|
|
125
|
+
const uuidPreview = join(appsDir, `${appId}.preview`);
|
|
126
|
+
if (
|
|
127
|
+
existsSync(slugPreview) &&
|
|
128
|
+
!existsSync(uuidPreview) &&
|
|
129
|
+
slugPreview !== uuidPreview
|
|
130
|
+
) {
|
|
131
|
+
renameSync(slugPreview, uuidPreview);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 3. Remove dirName from JSON and rename file: {dirName}.json -> {appId}.json
|
|
135
|
+
const updatedParsed = { ...parsed };
|
|
136
|
+
delete updatedParsed.dirName;
|
|
137
|
+
const updatedJson = JSON.stringify(updatedParsed, null, 2);
|
|
138
|
+
|
|
139
|
+
const uuidJsonFile = `${appId}.json`;
|
|
140
|
+
const uuidJsonPath = join(appsDir, uuidJsonFile);
|
|
141
|
+
|
|
142
|
+
if (jsonFile !== uuidJsonFile) {
|
|
143
|
+
writeFileSync(uuidJsonPath, updatedJson, "utf-8");
|
|
144
|
+
if (existsSync(jsonPath) && jsonPath !== uuidJsonPath) {
|
|
145
|
+
try {
|
|
146
|
+
unlinkSync(jsonPath);
|
|
147
|
+
} catch {
|
|
148
|
+
// Old file cleanup is best-effort
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
writeFileSync(uuidJsonPath, updatedJson, "utf-8");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
|
|
79
157
|
run(workspaceDir: string): void {
|
|
80
158
|
const appsDir = join(workspaceDir, "data", "apps");
|
|
81
159
|
if (!existsSync(appsDir)) return;
|
|
@@ -14,6 +14,17 @@ export const backfillInstallationIdMigration: WorkspaceMigration = {
|
|
|
14
14
|
id: "011-backfill-installation-id",
|
|
15
15
|
description:
|
|
16
16
|
"Backfill installationId into lockfile from SQLite checkpoint and clean up stale row",
|
|
17
|
+
|
|
18
|
+
down(_workspaceDir: string): void {
|
|
19
|
+
// The forward migration moved an installationId from a SQLite checkpoint
|
|
20
|
+
// into the lockfile entry. Rolling back by removing installationId from
|
|
21
|
+
// the lockfile would break telemetry continuity and the field is harmless
|
|
22
|
+
// to leave in place. The SQLite checkpoint was already deleted and
|
|
23
|
+
// cannot be restored.
|
|
24
|
+
//
|
|
25
|
+
// No-op: leaving installationId in the lockfile is safe and non-disruptive.
|
|
26
|
+
},
|
|
27
|
+
|
|
17
28
|
run(_workspaceDir: string): void {
|
|
18
29
|
// a. Read existing installation ID from SQLite, or generate a new one.
|
|
19
30
|
// On fresh installs the memory_checkpoints table may not exist yet,
|
|
@@ -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
|
};
|