@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
|
@@ -23,58 +23,13 @@ mock.module("../util/logger.js", () => ({
|
|
|
23
23
|
}),
|
|
24
24
|
}));
|
|
25
25
|
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
// Broker client mock — set up before importing secure-keys so the
|
|
28
|
-
// module-level `createBrokerClient()` call picks up our mock.
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
|
|
31
|
-
let mockBrokerAvailable = false;
|
|
32
|
-
let mockBrokerStore: Map<string, string> = new Map();
|
|
33
|
-
let mockBrokerGetError = false;
|
|
34
|
-
let mockBrokerSetError = false;
|
|
35
|
-
let mockBrokerDelError = false;
|
|
36
|
-
let mockBrokerGetCalled = false;
|
|
37
|
-
let mockBrokerSetCalled = false;
|
|
38
|
-
|
|
39
|
-
mock.module("../security/keychain-broker-client.js", () => ({
|
|
40
|
-
createBrokerClient: () => ({
|
|
41
|
-
isAvailable: () => mockBrokerAvailable,
|
|
42
|
-
ping: async () => (mockBrokerAvailable ? { pong: true } : null),
|
|
43
|
-
get: async (account: string) => {
|
|
44
|
-
mockBrokerGetCalled = true;
|
|
45
|
-
// null = broker error (fall back to encrypted store)
|
|
46
|
-
if (mockBrokerGetError) return null;
|
|
47
|
-
const value = mockBrokerStore.get(account);
|
|
48
|
-
if (value !== undefined) return { found: true, value };
|
|
49
|
-
return { found: false };
|
|
50
|
-
},
|
|
51
|
-
set: async (account: string, value: string) => {
|
|
52
|
-
mockBrokerSetCalled = true;
|
|
53
|
-
if (mockBrokerSetError)
|
|
54
|
-
return {
|
|
55
|
-
status: "rejected" as const,
|
|
56
|
-
code: "KEYCHAIN_ERROR",
|
|
57
|
-
message: "mock error",
|
|
58
|
-
};
|
|
59
|
-
mockBrokerStore.set(account, value);
|
|
60
|
-
return { status: "ok" as const };
|
|
61
|
-
},
|
|
62
|
-
del: async (account: string) => {
|
|
63
|
-
if (mockBrokerDelError) return false;
|
|
64
|
-
const existed = mockBrokerStore.has(account);
|
|
65
|
-
mockBrokerStore.delete(account);
|
|
66
|
-
return existed;
|
|
67
|
-
},
|
|
68
|
-
list: async () => Array.from(mockBrokerStore.keys()),
|
|
69
|
-
}),
|
|
70
|
-
}));
|
|
71
|
-
|
|
72
26
|
import * as encryptedStore from "../security/encrypted-store.js";
|
|
73
27
|
import { _setStorePath } from "../security/encrypted-store.js";
|
|
74
28
|
import {
|
|
75
29
|
_resetBackend,
|
|
76
30
|
deleteSecureKeyAsync,
|
|
77
31
|
getSecureKeyAsync,
|
|
32
|
+
getSecureKeyResultAsync,
|
|
78
33
|
listSecureKeysAsync,
|
|
79
34
|
setSecureKeyAsync,
|
|
80
35
|
} from "../security/secure-keys.js";
|
|
@@ -93,17 +48,9 @@ describe("secure-keys", () => {
|
|
|
93
48
|
beforeEach(() => {
|
|
94
49
|
_resetBackend();
|
|
95
50
|
|
|
96
|
-
//
|
|
97
|
-
mockBrokerAvailable = false;
|
|
98
|
-
mockBrokerStore = new Map();
|
|
99
|
-
mockBrokerGetError = false;
|
|
100
|
-
mockBrokerSetError = false;
|
|
101
|
-
mockBrokerDelError = false;
|
|
102
|
-
mockBrokerGetCalled = false;
|
|
103
|
-
mockBrokerSetCalled = false;
|
|
104
|
-
|
|
105
|
-
// Ensure VELLUM_DEV is NOT set so broker tests work by default
|
|
51
|
+
// Ensure VELLUM_DEV and VELLUM_DESKTOP_APP are NOT set
|
|
106
52
|
delete process.env.VELLUM_DEV;
|
|
53
|
+
delete process.env.VELLUM_DESKTOP_APP;
|
|
107
54
|
|
|
108
55
|
if (existsSync(TEST_DIR)) {
|
|
109
56
|
rmSync(TEST_DIR, { recursive: true });
|
|
@@ -116,6 +63,7 @@ describe("secure-keys", () => {
|
|
|
116
63
|
_setStorePath(null);
|
|
117
64
|
_resetBackend();
|
|
118
65
|
delete process.env.VELLUM_DEV;
|
|
66
|
+
delete process.env.VELLUM_DESKTOP_APP;
|
|
119
67
|
});
|
|
120
68
|
|
|
121
69
|
afterAll(() => {
|
|
@@ -125,9 +73,9 @@ describe("secure-keys", () => {
|
|
|
125
73
|
});
|
|
126
74
|
|
|
127
75
|
// -----------------------------------------------------------------------
|
|
128
|
-
// CRUD operations (
|
|
76
|
+
// CRUD operations (encrypted store backend)
|
|
129
77
|
// -----------------------------------------------------------------------
|
|
130
|
-
describe("CRUD with encrypted backend
|
|
78
|
+
describe("CRUD with encrypted backend", () => {
|
|
131
79
|
test("set and get a key", async () => {
|
|
132
80
|
await setSecureKeyAsync("openai", "sk-openai-789");
|
|
133
81
|
expect(await getSecureKeyAsync("openai")).toBe("sk-openai-789");
|
|
@@ -149,143 +97,90 @@ describe("secure-keys", () => {
|
|
|
149
97
|
});
|
|
150
98
|
|
|
151
99
|
// -----------------------------------------------------------------------
|
|
152
|
-
//
|
|
100
|
+
// Desktop app uses encrypted store (same as dev/CLI)
|
|
153
101
|
// -----------------------------------------------------------------------
|
|
154
|
-
describe("
|
|
155
|
-
test("
|
|
156
|
-
|
|
102
|
+
describe("desktop app uses encrypted store", () => {
|
|
103
|
+
test("VELLUM_DESKTOP_APP=1 writes to encrypted store", async () => {
|
|
104
|
+
process.env.VELLUM_DESKTOP_APP = "1";
|
|
157
105
|
_resetBackend();
|
|
158
106
|
|
|
159
107
|
const result = await setSecureKeyAsync("api-key", "new-value");
|
|
160
108
|
expect(result).toBe(true);
|
|
161
|
-
|
|
162
|
-
expect(mockBrokerStore.get("api-key")).toBe("new-value");
|
|
163
|
-
// Value should NOT be in the encrypted store (single-writer)
|
|
164
|
-
expect(encryptedStore.getKey("api-key")).toBeUndefined();
|
|
109
|
+
expect(encryptedStore.getKey("api-key")).toBe("new-value");
|
|
165
110
|
});
|
|
166
111
|
|
|
167
|
-
test("
|
|
168
|
-
|
|
169
|
-
mockBrokerSetError = true;
|
|
112
|
+
test("VELLUM_DESKTOP_APP=1 reads from encrypted store", async () => {
|
|
113
|
+
process.env.VELLUM_DESKTOP_APP = "1";
|
|
170
114
|
_resetBackend();
|
|
171
115
|
|
|
172
|
-
|
|
173
|
-
expect(result).toBe(false);
|
|
174
|
-
expect(mockBrokerStore.has("api-key")).toBe(false);
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
// -----------------------------------------------------------------------
|
|
179
|
-
// Reads: primary backend first, legacy fallback to encrypted store
|
|
180
|
-
// -----------------------------------------------------------------------
|
|
181
|
-
describe("reads with broker available", () => {
|
|
182
|
-
test("getSecureKeyAsync reads from broker (primary backend)", async () => {
|
|
183
|
-
mockBrokerAvailable = true;
|
|
184
|
-
_resetBackend();
|
|
116
|
+
encryptedStore.setKey("api-key", "encrypted-value");
|
|
185
117
|
|
|
186
|
-
mockBrokerStore.set("api-key", "broker-value");
|
|
187
118
|
const result = await getSecureKeyAsync("api-key");
|
|
188
|
-
expect(result).toBe("
|
|
189
|
-
expect(mockBrokerGetCalled).toBe(true);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
test("getSecureKeyAsync falls back to encrypted store for legacy keys", async () => {
|
|
193
|
-
mockBrokerAvailable = true;
|
|
194
|
-
_resetBackend();
|
|
195
|
-
|
|
196
|
-
// Pre-populate encrypted store directly (legacy key not in broker)
|
|
197
|
-
encryptedStore.setKey("legacy-key", "legacy-value");
|
|
198
|
-
|
|
199
|
-
const result = await getSecureKeyAsync("legacy-key");
|
|
200
|
-
expect(result).toBe("legacy-value");
|
|
201
|
-
// Broker was checked first (returned nothing), then encrypted store
|
|
202
|
-
expect(mockBrokerGetCalled).toBe(true);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
test("getSecureKeyAsync returns undefined when neither store has the key", async () => {
|
|
206
|
-
mockBrokerAvailable = true;
|
|
207
|
-
_resetBackend();
|
|
208
|
-
|
|
209
|
-
expect(await getSecureKeyAsync("missing-key")).toBeUndefined();
|
|
119
|
+
expect(result).toBe("encrypted-value");
|
|
210
120
|
});
|
|
211
121
|
|
|
212
|
-
test("
|
|
213
|
-
|
|
122
|
+
test("VELLUM_DESKTOP_APP=1 deletes from encrypted store", async () => {
|
|
123
|
+
process.env.VELLUM_DESKTOP_APP = "1";
|
|
214
124
|
_resetBackend();
|
|
215
125
|
|
|
216
|
-
// Both stores have a value — broker (primary) should win
|
|
217
|
-
mockBrokerStore.set("api-key", "broker-value");
|
|
218
126
|
encryptedStore.setKey("api-key", "encrypted-value");
|
|
219
127
|
|
|
220
|
-
const result = await
|
|
221
|
-
expect(result).toBe("
|
|
128
|
+
const result = await deleteSecureKeyAsync("api-key");
|
|
129
|
+
expect(result).toBe("deleted");
|
|
130
|
+
expect(encryptedStore.getKey("api-key")).toBeUndefined();
|
|
222
131
|
});
|
|
223
132
|
});
|
|
224
133
|
|
|
225
134
|
// -----------------------------------------------------------------------
|
|
226
|
-
// Dev mode
|
|
135
|
+
// Dev mode — VELLUM_DEV=1 uses encrypted store
|
|
227
136
|
// -----------------------------------------------------------------------
|
|
228
|
-
describe("dev mode
|
|
229
|
-
test("setSecureKeyAsync writes to encrypted store
|
|
137
|
+
describe("dev mode (VELLUM_DEV=1)", () => {
|
|
138
|
+
test("setSecureKeyAsync writes to encrypted store", async () => {
|
|
230
139
|
process.env.VELLUM_DEV = "1";
|
|
231
|
-
mockBrokerAvailable = true;
|
|
232
140
|
_resetBackend();
|
|
233
141
|
|
|
234
142
|
const result = await setSecureKeyAsync("api-key", "dev-value");
|
|
235
143
|
expect(result).toBe(true);
|
|
236
|
-
// Written to encrypted store
|
|
237
144
|
expect(encryptedStore.getKey("api-key")).toBe("dev-value");
|
|
238
|
-
// NOT written to broker
|
|
239
|
-
expect(mockBrokerStore.has("api-key")).toBe(false);
|
|
240
|
-
expect(mockBrokerSetCalled).toBe(false);
|
|
241
145
|
});
|
|
242
146
|
|
|
243
|
-
test("getSecureKeyAsync reads from encrypted store
|
|
147
|
+
test("getSecureKeyAsync reads from encrypted store", async () => {
|
|
244
148
|
process.env.VELLUM_DEV = "1";
|
|
245
|
-
mockBrokerAvailable = true;
|
|
246
149
|
_resetBackend();
|
|
247
150
|
|
|
248
|
-
mockBrokerStore.set("api-key", "broker-value");
|
|
249
151
|
encryptedStore.setKey("api-key", "encrypted-value");
|
|
250
152
|
|
|
251
153
|
const result = await getSecureKeyAsync("api-key");
|
|
252
154
|
expect(result).toBe("encrypted-value");
|
|
253
|
-
// Broker should not have been contacted
|
|
254
|
-
expect(mockBrokerGetCalled).toBe(false);
|
|
255
155
|
});
|
|
256
156
|
|
|
257
|
-
test("getSecureKeyAsync returns undefined when encrypted store is empty
|
|
157
|
+
test("getSecureKeyAsync returns undefined when encrypted store is empty", async () => {
|
|
258
158
|
process.env.VELLUM_DEV = "1";
|
|
259
|
-
mockBrokerAvailable = true;
|
|
260
159
|
_resetBackend();
|
|
261
160
|
|
|
262
|
-
mockBrokerStore.set("api-key", "broker-value");
|
|
263
|
-
|
|
264
161
|
const result = await getSecureKeyAsync("api-key");
|
|
265
162
|
expect(result).toBeUndefined();
|
|
266
|
-
expect(mockBrokerGetCalled).toBe(false);
|
|
267
163
|
});
|
|
268
164
|
});
|
|
269
165
|
|
|
270
166
|
// -----------------------------------------------------------------------
|
|
271
|
-
//
|
|
167
|
+
// Non-desktop topology uses encrypted store
|
|
272
168
|
// -----------------------------------------------------------------------
|
|
273
|
-
describe("
|
|
274
|
-
test("
|
|
275
|
-
mockBrokerAvailable = true;
|
|
169
|
+
describe("non-desktop topology", () => {
|
|
170
|
+
test("uses encrypted store", async () => {
|
|
276
171
|
_resetBackend();
|
|
277
172
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const result = await deleteSecureKeyAsync("api-key");
|
|
282
|
-
expect(result).toBe("deleted");
|
|
283
|
-
expect(mockBrokerStore.has("api-key")).toBe(false);
|
|
284
|
-
expect(encryptedStore.getKey("api-key")).toBeUndefined();
|
|
173
|
+
const result = await setSecureKeyAsync("api-key", "new-value");
|
|
174
|
+
expect(result).toBe(true);
|
|
175
|
+
expect(encryptedStore.getKey("api-key")).toBe("new-value");
|
|
285
176
|
});
|
|
177
|
+
});
|
|
286
178
|
|
|
287
|
-
|
|
288
|
-
|
|
179
|
+
// -----------------------------------------------------------------------
|
|
180
|
+
// Delete — single backend
|
|
181
|
+
// -----------------------------------------------------------------------
|
|
182
|
+
describe("delete from encrypted store", () => {
|
|
183
|
+
test("deleteSecureKeyAsync removes key from encrypted store", async () => {
|
|
289
184
|
encryptedStore.setKey("api-key", "encrypted-value");
|
|
290
185
|
|
|
291
186
|
const result = await deleteSecureKeyAsync("api-key");
|
|
@@ -293,179 +188,107 @@ describe("secure-keys", () => {
|
|
|
293
188
|
expect(encryptedStore.getKey("api-key")).toBeUndefined();
|
|
294
189
|
});
|
|
295
190
|
|
|
296
|
-
test("deleteSecureKeyAsync
|
|
297
|
-
mockBrokerAvailable = true;
|
|
298
|
-
mockBrokerDelError = true;
|
|
299
|
-
_resetBackend();
|
|
300
|
-
|
|
301
|
-
mockBrokerStore.set("api-key", "broker-value");
|
|
302
|
-
encryptedStore.setKey("api-key", "encrypted-value");
|
|
303
|
-
|
|
304
|
-
const result = await deleteSecureKeyAsync("api-key");
|
|
305
|
-
expect(result).toBe("error");
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
test("deleteSecureKeyAsync in dev mode still attempts both stores", async () => {
|
|
191
|
+
test("deleteSecureKeyAsync in dev mode deletes from encrypted store", async () => {
|
|
309
192
|
process.env.VELLUM_DEV = "1";
|
|
310
|
-
|
|
193
|
+
process.env.VELLUM_DESKTOP_APP = "1";
|
|
311
194
|
_resetBackend();
|
|
312
195
|
|
|
313
|
-
mockBrokerStore.set("api-key", "broker-value");
|
|
314
196
|
encryptedStore.setKey("api-key", "encrypted-value");
|
|
315
197
|
|
|
316
198
|
const result = await deleteSecureKeyAsync("api-key");
|
|
317
199
|
expect(result).toBe("deleted");
|
|
318
|
-
expect(mockBrokerStore.has("api-key")).toBe(false);
|
|
319
200
|
expect(encryptedStore.getKey("api-key")).toBeUndefined();
|
|
320
201
|
});
|
|
321
202
|
|
|
322
|
-
test("deleteSecureKeyAsync returns not-found when key missing
|
|
323
|
-
// Broker unavailable, encrypted store empty
|
|
203
|
+
test("deleteSecureKeyAsync returns not-found when key missing", async () => {
|
|
324
204
|
const result = await deleteSecureKeyAsync("missing-key");
|
|
325
205
|
expect(result).toBe("not-found");
|
|
326
206
|
});
|
|
327
207
|
});
|
|
328
208
|
|
|
329
209
|
// -----------------------------------------------------------------------
|
|
330
|
-
//
|
|
210
|
+
// listSecureKeysAsync — single-backend key listing
|
|
331
211
|
// -----------------------------------------------------------------------
|
|
332
|
-
describe("
|
|
333
|
-
test("returns encrypted store
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
// Simulate a legacy key that was written to encrypted store before
|
|
338
|
-
// the single-writer migration — broker doesn't have it.
|
|
339
|
-
encryptedStore.setKey("legacy-account", "legacy-secret");
|
|
340
|
-
|
|
341
|
-
const result = await getSecureKeyAsync("legacy-account");
|
|
342
|
-
expect(result).toBe("legacy-secret");
|
|
343
|
-
});
|
|
212
|
+
describe("listSecureKeysAsync", () => {
|
|
213
|
+
test("returns encrypted store keys", async () => {
|
|
214
|
+
encryptedStore.setKey("enc-key-1", "val1");
|
|
215
|
+
encryptedStore.setKey("enc-key-2", "val2");
|
|
344
216
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
// Should read directly from encrypted store (primary)
|
|
352
|
-
const result = await getSecureKeyAsync("account");
|
|
353
|
-
expect(result).toBe("value");
|
|
354
|
-
// Broker should not have been contacted
|
|
355
|
-
expect(mockBrokerGetCalled).toBe(false);
|
|
217
|
+
const result = await listSecureKeysAsync();
|
|
218
|
+
expect(result.unreachable).toBe(false);
|
|
219
|
+
expect(result.accounts).toContain("enc-key-1");
|
|
220
|
+
expect(result.accounts).toContain("enc-key-2");
|
|
221
|
+
expect(result.accounts.length).toBe(2);
|
|
356
222
|
});
|
|
357
|
-
});
|
|
358
223
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
// -----------------------------------------------------------------------
|
|
362
|
-
describe("stale-value prevention", () => {
|
|
363
|
-
test("setSecureKeyAsync failure does not corrupt broker store", async () => {
|
|
364
|
-
mockBrokerAvailable = true;
|
|
224
|
+
test("returns encrypted store keys with VELLUM_DEV=1", async () => {
|
|
225
|
+
process.env.VELLUM_DEV = "1";
|
|
365
226
|
_resetBackend();
|
|
366
227
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
// Now fail the next set
|
|
371
|
-
mockBrokerSetError = true;
|
|
372
|
-
const ok = await setSecureKeyAsync("api-key", "new-value");
|
|
373
|
-
expect(ok).toBe(false);
|
|
228
|
+
encryptedStore.setKey("dev-key-1", "val2");
|
|
229
|
+
encryptedStore.setKey("dev-key-2", "val3");
|
|
374
230
|
|
|
375
|
-
|
|
376
|
-
expect(
|
|
231
|
+
const result = await listSecureKeysAsync();
|
|
232
|
+
expect(result.unreachable).toBe(false);
|
|
233
|
+
expect(result.accounts).toContain("dev-key-1");
|
|
234
|
+
expect(result.accounts).toContain("dev-key-2");
|
|
235
|
+
expect(result.accounts.length).toBe(2);
|
|
377
236
|
});
|
|
378
|
-
});
|
|
379
237
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
// -----------------------------------------------------------------------
|
|
383
|
-
describe("listSecureKeysAsync", () => {
|
|
384
|
-
test("returns merged, deduplicated keys when broker is primary and encrypted store has legacy keys", async () => {
|
|
385
|
-
mockBrokerAvailable = true;
|
|
238
|
+
test("returns encrypted store keys with VELLUM_DESKTOP_APP=1", async () => {
|
|
239
|
+
process.env.VELLUM_DESKTOP_APP = "1";
|
|
386
240
|
_resetBackend();
|
|
387
241
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
mockBrokerStore.set("shared-key", "broker-val");
|
|
391
|
-
|
|
392
|
-
// Encrypted store has legacy keys (some overlapping)
|
|
393
|
-
encryptedStore.setKey("legacy-key-1", "val2");
|
|
394
|
-
encryptedStore.setKey("shared-key", "enc-val");
|
|
242
|
+
encryptedStore.setKey("desktop-key-1", "val1");
|
|
243
|
+
encryptedStore.setKey("desktop-key-2", "val2");
|
|
395
244
|
|
|
396
|
-
const
|
|
397
|
-
expect(
|
|
398
|
-
expect(
|
|
399
|
-
expect(
|
|
400
|
-
|
|
401
|
-
expect(keys.length).toBe(3);
|
|
245
|
+
const result = await listSecureKeysAsync();
|
|
246
|
+
expect(result.unreachable).toBe(false);
|
|
247
|
+
expect(result.accounts).toContain("desktop-key-1");
|
|
248
|
+
expect(result.accounts).toContain("desktop-key-2");
|
|
249
|
+
expect(result.accounts.length).toBe(2);
|
|
402
250
|
});
|
|
403
251
|
|
|
404
|
-
test("returns
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
encryptedStore.setKey("enc-key-2", "val2");
|
|
408
|
-
|
|
409
|
-
const keys = await listSecureKeysAsync();
|
|
410
|
-
expect(keys).toContain("enc-key-1");
|
|
411
|
-
expect(keys).toContain("enc-key-2");
|
|
412
|
-
expect(keys.length).toBe(2);
|
|
252
|
+
test("returns empty accounts when store is empty", async () => {
|
|
253
|
+
const result = await listSecureKeysAsync();
|
|
254
|
+
expect(result).toEqual({ accounts: [], unreachable: false });
|
|
413
255
|
});
|
|
256
|
+
});
|
|
414
257
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
mockBrokerStore.set("broker-only", "val1");
|
|
422
|
-
|
|
423
|
-
// Encrypted store has keys
|
|
424
|
-
encryptedStore.setKey("dev-key-1", "val2");
|
|
425
|
-
encryptedStore.setKey("dev-key-2", "val3");
|
|
258
|
+
// -----------------------------------------------------------------------
|
|
259
|
+
// getSecureKeyResultAsync — richer result with unreachable flag
|
|
260
|
+
// -----------------------------------------------------------------------
|
|
261
|
+
describe("getSecureKeyResultAsync", () => {
|
|
262
|
+
test("returns value and unreachable false on success", async () => {
|
|
263
|
+
encryptedStore.setKey("api-key", "stored-value");
|
|
426
264
|
|
|
427
|
-
const
|
|
428
|
-
expect(
|
|
429
|
-
expect(
|
|
430
|
-
// broker-only key should NOT appear since primary backend is encrypted store
|
|
431
|
-
expect(keys).not.toContain("broker-only");
|
|
432
|
-
expect(keys.length).toBe(2);
|
|
265
|
+
const result = await getSecureKeyResultAsync("api-key");
|
|
266
|
+
expect(result.value).toBe("stored-value");
|
|
267
|
+
expect(result.unreachable).toBe(false);
|
|
433
268
|
});
|
|
434
269
|
|
|
435
|
-
test("returns
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
mockBrokerStore.set("broker-key-1", "val1");
|
|
440
|
-
mockBrokerStore.set("broker-key-2", "val2");
|
|
441
|
-
|
|
442
|
-
const keys = await listSecureKeysAsync();
|
|
443
|
-
expect(keys).toContain("broker-key-1");
|
|
444
|
-
expect(keys).toContain("broker-key-2");
|
|
445
|
-
expect(keys.length).toBe(2);
|
|
270
|
+
test("returns unreachable false when key missing (encrypted store always reachable)", async () => {
|
|
271
|
+
const result = await getSecureKeyResultAsync("missing-key");
|
|
272
|
+
expect(result.value).toBeUndefined();
|
|
273
|
+
expect(result.unreachable).toBe(false);
|
|
446
274
|
});
|
|
447
275
|
|
|
448
|
-
test("
|
|
449
|
-
|
|
276
|
+
test("returns unreachable false in dev mode", async () => {
|
|
277
|
+
process.env.VELLUM_DEV = "1";
|
|
450
278
|
_resetBackend();
|
|
451
279
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
const keys = await listSecureKeysAsync();
|
|
457
|
-
expect(keys).toContain("api-key");
|
|
458
|
-
// Only one copy, not two
|
|
459
|
-
expect(keys.length).toBe(1);
|
|
460
|
-
expect(keys.filter((k) => k === "api-key").length).toBe(1);
|
|
280
|
+
const result = await getSecureKeyResultAsync("missing-key");
|
|
281
|
+
expect(result.value).toBeUndefined();
|
|
282
|
+
expect(result.unreachable).toBe(false);
|
|
461
283
|
});
|
|
462
284
|
|
|
463
|
-
test("returns
|
|
464
|
-
|
|
285
|
+
test("returns unreachable false with VELLUM_DESKTOP_APP=1", async () => {
|
|
286
|
+
process.env.VELLUM_DESKTOP_APP = "1";
|
|
465
287
|
_resetBackend();
|
|
466
288
|
|
|
467
|
-
const
|
|
468
|
-
expect(
|
|
289
|
+
const result = await getSecureKeyResultAsync("missing-key");
|
|
290
|
+
expect(result.value).toBeUndefined();
|
|
291
|
+
expect(result.unreachable).toBe(false);
|
|
469
292
|
});
|
|
470
293
|
});
|
|
471
294
|
});
|
|
@@ -82,12 +82,76 @@ describe("deriveShellActionKeys", () => {
|
|
|
82
82
|
]);
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
test("pipelines are marked non-simple", async () => {
|
|
85
|
+
test("pipelines are marked non-simple but produce action keys", async () => {
|
|
86
86
|
const analysis = await analyzeShellCommand("git log | grep fix");
|
|
87
87
|
const result = deriveShellActionKeys(analysis);
|
|
88
88
|
|
|
89
89
|
expect(result.isSimpleAction).toBe(false);
|
|
90
|
-
expect(result.keys).
|
|
90
|
+
expect(result.keys).toEqual([
|
|
91
|
+
{ key: "action:git log", depth: 2 },
|
|
92
|
+
{ key: "action:git", depth: 1 },
|
|
93
|
+
]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("pipeline extracts action keys from first segment", async () => {
|
|
97
|
+
const analysis = await analyzeShellCommand(
|
|
98
|
+
"pdftotext file.pdf | head -100",
|
|
99
|
+
);
|
|
100
|
+
const result = deriveShellActionKeys(analysis);
|
|
101
|
+
|
|
102
|
+
expect(result.isSimpleAction).toBe(false);
|
|
103
|
+
// file.pdf is treated as a subcommand token (doesn't start with . or contain /)
|
|
104
|
+
expect(result.keys).toEqual([
|
|
105
|
+
{ key: "action:pdftotext file.pdf", depth: 2 },
|
|
106
|
+
{ key: "action:pdftotext", depth: 1 },
|
|
107
|
+
]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("setup-prefix + pipeline extracts action keys", async () => {
|
|
111
|
+
const analysis = await analyzeShellCommand(
|
|
112
|
+
"cd /tmp && pdftotext file.pdf | grep oil",
|
|
113
|
+
);
|
|
114
|
+
const result = deriveShellActionKeys(analysis);
|
|
115
|
+
|
|
116
|
+
expect(result.isSimpleAction).toBe(false);
|
|
117
|
+
expect(result.keys).toEqual([
|
|
118
|
+
{ key: "action:pdftotext file.pdf", depth: 2 },
|
|
119
|
+
{ key: "action:pdftotext", depth: 1 },
|
|
120
|
+
]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("pipeline with subcommand extracts deeper keys", async () => {
|
|
124
|
+
const analysis = await analyzeShellCommand("cd repo && gh pr list | head");
|
|
125
|
+
const result = deriveShellActionKeys(analysis);
|
|
126
|
+
|
|
127
|
+
expect(result.isSimpleAction).toBe(false);
|
|
128
|
+
expect(result.keys).toEqual([
|
|
129
|
+
{ key: "action:gh pr list", depth: 3 },
|
|
130
|
+
{ key: "action:gh pr", depth: 2 },
|
|
131
|
+
{ key: "action:gh", depth: 1 },
|
|
132
|
+
]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("multi-pipe pipeline extracts first segment only", async () => {
|
|
136
|
+
const analysis = await analyzeShellCommand("cat file | grep error | wc -l");
|
|
137
|
+
const result = deriveShellActionKeys(analysis);
|
|
138
|
+
|
|
139
|
+
expect(result.isSimpleAction).toBe(false);
|
|
140
|
+
expect(result.keys).toEqual([
|
|
141
|
+
{ key: "action:cat file", depth: 2 },
|
|
142
|
+
{ key: "action:cat", depth: 1 },
|
|
143
|
+
]);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("dangerous pipe_to_shell still extracts keys", async () => {
|
|
147
|
+
const analysis = await analyzeShellCommand("curl url | bash");
|
|
148
|
+
const result = deriveShellActionKeys(analysis);
|
|
149
|
+
|
|
150
|
+
expect(result.isSimpleAction).toBe(false);
|
|
151
|
+
expect(result.keys).toEqual([
|
|
152
|
+
{ key: "action:curl url", depth: 2 },
|
|
153
|
+
{ key: "action:curl", depth: 1 },
|
|
154
|
+
]);
|
|
91
155
|
});
|
|
92
156
|
|
|
93
157
|
test("complex chains with multiple actions are non-simple", async () => {
|
|
@@ -198,9 +262,19 @@ describe("buildShellCommandCandidates", () => {
|
|
|
198
262
|
expect(candidates).toEqual(['git add . && git commit -m "fix"']);
|
|
199
263
|
});
|
|
200
264
|
|
|
201
|
-
test("pipeline returns raw
|
|
265
|
+
test("pipeline returns raw and action key candidates", async () => {
|
|
202
266
|
const candidates = await buildShellCommandCandidates("git log | grep fix");
|
|
203
|
-
expect(candidates).
|
|
267
|
+
expect(candidates[0]).toBe("git log | grep fix");
|
|
268
|
+
expect(candidates).toContain("action:git log");
|
|
269
|
+
expect(candidates).toContain("action:git");
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("pipeline with setup prefix includes action candidates", async () => {
|
|
273
|
+
const candidates = await buildShellCommandCandidates(
|
|
274
|
+
"cd /tmp && pdftotext file.pdf | wc -c",
|
|
275
|
+
);
|
|
276
|
+
expect(candidates[0]).toBe("cd /tmp && pdftotext file.pdf | wc -c");
|
|
277
|
+
expect(candidates).toContain("action:pdftotext");
|
|
204
278
|
});
|
|
205
279
|
|
|
206
280
|
test("candidate order is stable", async () => {
|
|
@@ -241,13 +315,29 @@ describe("buildShellAllowlistOptions — complex command restrictions", () => {
|
|
|
241
315
|
expect(options[0].description).toContain("compound");
|
|
242
316
|
});
|
|
243
317
|
|
|
244
|
-
test("pipeline offers exact
|
|
318
|
+
test("pipeline offers exact and action-key options", async () => {
|
|
245
319
|
const options = await buildShellAllowlistOptions(
|
|
246
320
|
"cat file.txt | grep error | wc -l",
|
|
247
321
|
);
|
|
248
|
-
expect(options).
|
|
322
|
+
expect(options.length).toBeGreaterThanOrEqual(2);
|
|
249
323
|
expect(options[0].pattern).toBe("cat file.txt | grep error | wc -l");
|
|
250
324
|
expect(options[0].description).toContain("compound");
|
|
325
|
+
expect(options.some((o) => o.pattern.startsWith("action:"))).toBe(true);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("pipeline offers action-key options from first segment", async () => {
|
|
329
|
+
const options = await buildShellAllowlistOptions(
|
|
330
|
+
"pdftotext file.pdf | head -100",
|
|
331
|
+
);
|
|
332
|
+
expect(options.length).toBeGreaterThanOrEqual(2);
|
|
333
|
+
expect(options[0].pattern).toBe("pdftotext file.pdf | head -100");
|
|
334
|
+
expect(
|
|
335
|
+
options.some(
|
|
336
|
+
(o) =>
|
|
337
|
+
o.pattern === "action:pdftotext" &&
|
|
338
|
+
o.description.includes('Any "pdftotext" command'),
|
|
339
|
+
),
|
|
340
|
+
).toBe(true);
|
|
251
341
|
});
|
|
252
342
|
|
|
253
343
|
test("semicolon chain offers exact only", async () => {
|