@vellumai/assistant 0.5.6 → 0.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +16 -2
- package/ARCHITECTURE.md +6 -75
- package/Dockerfile +3 -2
- package/README.md +0 -2
- package/bun.lock +0 -414
- package/docker-entrypoint.sh +9 -0
- package/docs/architecture/keychain-broker.md +45 -240
- package/docs/architecture/memory.md +13 -11
- package/docs/architecture/security.md +0 -17
- package/docs/credential-execution-service.md +2 -2
- package/node_modules/@vellumai/ces-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-contracts/src/error.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/grants.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +120 -1
- package/node_modules/@vellumai/credential-storage/package.json +1 -0
- package/node_modules/@vellumai/egress-proxy/package.json +1 -0
- package/package.json +2 -3
- package/src/__tests__/actor-token-service.test.ts +0 -114
- package/src/__tests__/approval-cascade.test.ts +0 -1
- package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/browser-skill-endstate.test.ts +6 -5
- package/src/__tests__/btw-routes.test.ts +0 -39
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/call-domain.test.ts +0 -128
- package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
- package/src/__tests__/ces-startup-timeout.test.ts +40 -0
- package/src/__tests__/channel-approval-routes.test.ts +0 -5
- package/src/__tests__/channel-readiness-service.test.ts +1 -60
- package/src/__tests__/checker.test.ts +4 -2
- package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
- package/src/__tests__/config-schema-cmd.test.ts +0 -2
- package/src/__tests__/config-schema.test.ts +3 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop.test.ts +2 -4
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -1
- package/src/__tests__/conversation-error.test.ts +15 -1
- package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
- package/src/__tests__/conversation-messaging-secret-redirect.test.ts +1 -1
- package/src/__tests__/conversation-pre-run-repair.test.ts +0 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -1
- package/src/__tests__/conversation-queue.test.ts +0 -1
- package/src/__tests__/conversation-skill-tools.test.ts +0 -54
- package/src/__tests__/conversation-slash-queue.test.ts +0 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -1
- package/src/__tests__/conversation-title-service.test.ts +87 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -1
- package/src/__tests__/credential-execution-client.test.ts +5 -2
- package/src/__tests__/credential-execution-feature-gates.test.ts +59 -30
- package/src/__tests__/credential-execution-managed-contract.test.ts +35 -20
- package/src/__tests__/credential-security-e2e.test.ts +1 -67
- package/src/__tests__/credential-security-invariants.test.ts +6 -50
- package/src/__tests__/credentials-cli.test.ts +82 -3
- package/src/__tests__/daemon-credential-client.test.ts +123 -0
- package/src/__tests__/db-migration-rollback.test.ts +2015 -1
- package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -0
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +34 -143
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
- package/src/__tests__/gateway-client-managed-outbound.test.ts +79 -1
- package/src/__tests__/guardian-routing-state.test.ts +0 -5
- package/src/__tests__/host-shell-tool.test.ts +6 -7
- package/src/__tests__/http-user-message-parity.test.ts +3 -103
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
- package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
- package/src/__tests__/intent-routing.test.ts +0 -13
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
- package/src/__tests__/journal-context.test.ts +335 -0
- package/src/__tests__/keychain-broker-client.test.ts +161 -22
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -3
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
- package/src/__tests__/memory-lifecycle-e2e.test.ts +70 -25
- package/src/__tests__/memory-recall-quality.test.ts +48 -17
- package/src/__tests__/memory-regressions.test.ts +408 -363
- package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -3
- package/src/__tests__/migration-export-http.test.ts +2 -2
- package/src/__tests__/migration-import-commit-http.test.ts +2 -2
- package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
- package/src/__tests__/migration-validate-http.test.ts +2 -2
- package/src/__tests__/non-member-access-request.test.ts +2 -7
- package/src/__tests__/notification-decision-fallback.test.ts +4 -0
- package/src/__tests__/notification-decision-identity.test.ts +4 -0
- package/src/__tests__/notification-decision-strategy.test.ts +71 -0
- package/src/__tests__/oauth-cli.test.ts +5 -1
- package/src/__tests__/permission-types.test.ts +1 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +0 -37
- package/src/__tests__/provider-error-scenarios.test.ts +0 -267
- package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
- package/src/__tests__/provider-streaming.benchmark.test.ts +2 -81
- package/src/__tests__/qdrant-manager.test.ts +28 -2
- package/src/__tests__/registry.test.ts +0 -6
- package/src/__tests__/relay-server.test.ts +1 -2
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
- package/src/__tests__/script-proxy-injection-runtime.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -1
- package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
- package/src/__tests__/secure-keys.test.ts +95 -272
- package/src/__tests__/shell-identity.test.ts +96 -6
- package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
- package/src/__tests__/skill-feature-flags.test.ts +46 -45
- package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
- package/src/__tests__/skill-load-inline-command.test.ts +8 -12
- package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
- package/src/__tests__/skill-load-tool.test.ts +0 -2
- package/src/__tests__/skill-memory.test.ts +17 -3
- package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
- package/src/__tests__/skills.test.ts +0 -2
- package/src/__tests__/slack-inbound-verification.test.ts +0 -4
- package/src/__tests__/stale-approval-dedup.test.ts +171 -0
- package/src/__tests__/stt-hints.test.ts +437 -0
- package/src/__tests__/suggestion-routes.test.ts +1 -32
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/task-memory-cleanup.test.ts +14 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
- package/src/__tests__/twilio-routes-twiml.test.ts +139 -1
- package/src/__tests__/update-bulletin.test.ts +0 -2
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
- package/src/__tests__/voice-quality.test.ts +58 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -7
- package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
- package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +220 -0
- package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
- package/src/acp/agent-process.ts +9 -1
- package/src/agent/loop.ts +1 -1
- package/src/approvals/guardian-request-resolvers.ts +164 -38
- package/src/calls/__tests__/tts-text-sanitizer.test.ts +254 -0
- package/src/calls/audio-store.test.ts +97 -0
- package/src/calls/audio-store.ts +205 -0
- package/src/calls/call-controller.ts +90 -8
- package/src/calls/call-domain.ts +3 -0
- package/src/calls/call-store.ts +10 -3
- package/src/calls/fish-audio-client.ts +129 -0
- package/src/calls/relay-server.ts +27 -0
- package/src/calls/stt-hints.ts +189 -0
- package/src/calls/tts-text-sanitizer.ts +61 -0
- package/src/calls/twilio-routes.ts +34 -5
- package/src/calls/types.ts +1 -0
- package/src/calls/voice-ingress-preflight.ts +0 -42
- package/src/calls/voice-quality.ts +38 -5
- package/src/calls/voice-session-bridge.ts +7 -12
- package/src/cli/commands/avatar.ts +2 -2
- package/src/cli/commands/config.ts +1 -4
- package/src/cli/commands/credentials.ts +128 -82
- package/src/cli/commands/doctor.ts +2 -2
- package/src/cli/commands/keys.ts +7 -7
- package/src/cli/commands/memory.ts +1 -1
- package/src/cli/commands/oauth/connections.ts +11 -29
- package/src/cli/commands/oauth/index.ts +7 -0
- package/src/cli/commands/oauth/platform.ts +525 -0
- package/src/cli/commands/platform.ts +3 -3
- package/src/cli/lib/daemon-credential-client.ts +284 -0
- package/src/cli.ts +1 -1
- package/src/config/assistant-feature-flags.ts +186 -5
- package/src/config/bundled-skills/AGENTS.md +34 -0
- package/src/config/bundled-skills/acp/SKILL.md +10 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +0 -4
- package/src/config/bundled-skills/messaging/SKILL.md +5 -5
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
- package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +1 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +1 -0
- package/src/config/bundled-skills/settings/SKILL.md +15 -2
- package/src/config/bundled-skills/settings/TOOLS.json +47 -2
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +59 -0
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +80 -0
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
- package/src/config/bundled-skills/slack/SKILL.md +1 -1
- package/src/config/bundled-tool-registry.ts +5 -11
- package/src/config/defaults.ts +0 -2
- package/src/config/env-registry.ts +5 -5
- package/src/config/env.ts +21 -14
- package/src/config/feature-flag-registry.json +49 -9
- package/src/config/loader.ts +106 -42
- package/src/config/schema.ts +9 -29
- package/src/config/schemas/calls.ts +30 -0
- package/src/config/schemas/fish-audio.ts +39 -0
- package/src/config/schemas/inference.ts +2 -2
- package/src/config/schemas/journal.ts +16 -0
- package/src/config/schemas/memory-processing.ts +2 -2
- package/src/config/schemas/security.ts +0 -4
- package/src/config/types.ts +1 -1
- package/src/contacts/contact-store.ts +39 -0
- package/src/contacts/types.ts +2 -0
- package/src/credential-execution/approval-bridge.ts +1 -0
- package/src/credential-execution/executable-discovery.ts +28 -4
- package/src/credential-execution/feature-gates.ts +16 -0
- package/src/credential-execution/process-manager.ts +38 -0
- package/src/credential-execution/startup-timeout.ts +36 -0
- package/src/daemon/approval-generators.ts +3 -9
- package/src/daemon/assistant-attachments.ts +9 -0
- package/src/daemon/config-watcher.ts +5 -0
- package/src/daemon/conversation-error.ts +13 -1
- package/src/daemon/conversation-memory.ts +1 -2
- package/src/daemon/conversation-process.ts +18 -1
- package/src/daemon/conversation-surfaces.ts +30 -1
- package/src/daemon/conversation-tool-setup.ts +0 -105
- package/src/daemon/conversation.ts +21 -1
- package/src/daemon/guardian-action-generators.ts +3 -9
- package/src/daemon/handlers/config-vercel.ts +92 -0
- package/src/daemon/handlers/skills.ts +2 -15
- package/src/daemon/install-symlink.ts +195 -0
- package/src/daemon/lifecycle.ts +234 -51
- package/src/daemon/message-types/conversations.ts +4 -4
- package/src/daemon/message-types/diagnostics.ts +3 -22
- package/src/daemon/message-types/messages.ts +0 -2
- package/src/daemon/message-types/upgrades.ts +8 -0
- package/src/daemon/server.ts +32 -95
- package/src/events/domain-events.ts +2 -1
- package/src/inbound/platform-callback-registration.ts +3 -3
- package/src/instrument.ts +8 -5
- package/src/memory/app-store.ts +31 -0
- package/src/memory/conversation-title-service.ts +50 -1
- package/src/memory/db-init.ts +16 -0
- package/src/memory/indexer.ts +19 -10
- package/src/memory/items-extractor.ts +328 -321
- package/src/memory/job-handlers/conversation-starters.ts +4 -1
- package/src/memory/job-handlers/summarization.ts +26 -16
- package/src/memory/jobs-store.ts +63 -6
- package/src/memory/jobs-worker.ts +31 -7
- package/src/memory/journal-memory.ts +214 -0
- package/src/memory/migrations/001-job-deferrals.ts +19 -0
- package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
- package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
- package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
- package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
- package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
- package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
- package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
- package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
- package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
- package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
- package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
- package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
- package/src/memory/migrations/116-messages-fts.ts +106 -1
- package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
- package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
- package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
- package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
- package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
- package/src/memory/migrations/141-rename-verification-table.ts +54 -0
- package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
- package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
- package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
- package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
- package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
- package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
- package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
- package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
- package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
- package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
- package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
- package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
- package/src/memory/migrations/193-add-source-type-columns.ts +81 -0
- package/src/memory/migrations/index.ts +5 -0
- package/src/memory/migrations/registry.ts +98 -0
- package/src/memory/migrations/validate-migration-state.ts +137 -11
- package/src/memory/qdrant-circuit-breaker.ts +9 -0
- package/src/memory/qdrant-manager.ts +64 -7
- package/src/memory/retriever.test.ts +37 -25
- package/src/memory/retriever.ts +24 -49
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/contacts.ts +1 -0
- package/src/memory/schema/memory-core.ts +2 -0
- package/src/memory/search/formatting.ts +7 -44
- package/src/memory/search/staleness.ts +4 -0
- package/src/memory/search/tier-classifier.ts +10 -2
- package/src/memory/search/types.ts +2 -5
- package/src/memory/task-memory-cleanup.ts +4 -3
- package/src/notifications/adapters/slack.ts +168 -6
- package/src/notifications/broadcaster.ts +1 -0
- package/src/notifications/copy-composer.ts +59 -2
- package/src/notifications/decision-engine.ts +4 -1
- package/src/notifications/signal.ts +2 -0
- package/src/notifications/types.ts +2 -0
- package/src/oauth/connection-resolver.ts +6 -4
- package/src/permissions/checker.ts +0 -38
- package/src/permissions/shell-identity.ts +76 -22
- package/src/permissions/types.ts +4 -2
- package/src/platform/client.ts +35 -7
- package/src/prompts/journal-context.ts +133 -0
- package/src/prompts/persona-resolver.ts +194 -0
- package/src/prompts/system-prompt.ts +44 -4
- package/src/prompts/templates/SOUL.md +10 -0
- package/src/prompts/templates/users/default.md +1 -0
- package/src/providers/provider-send-message.ts +3 -32
- package/src/providers/registry.ts +29 -179
- package/src/providers/types.ts +1 -1
- package/src/runtime/access-request-helper.ts +4 -0
- package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
- package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
- package/src/runtime/auth/__tests__/guard-tests.test.ts +9 -50
- package/src/runtime/auth/external-assistant-id.ts +13 -59
- package/src/runtime/auth/route-policy.ts +17 -1
- package/src/runtime/auth/token-service.ts +43 -138
- package/src/runtime/channel-readiness-service.ts +1 -16
- package/src/runtime/gateway-client.ts +47 -4
- package/src/runtime/guardian-decision-types.ts +45 -4
- package/src/runtime/http-server.ts +31 -3
- package/src/runtime/middleware/error-handler.ts +1 -9
- package/src/runtime/routes/access-request-decision.ts +2 -2
- package/src/runtime/routes/app-management-routes.ts +2 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +219 -30
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +37 -14
- package/src/runtime/routes/audio-routes.ts +40 -0
- package/src/runtime/routes/btw-routes.ts +0 -17
- package/src/runtime/routes/channel-readiness-routes.ts +9 -4
- package/src/runtime/routes/conversation-query-routes.ts +63 -1
- package/src/runtime/routes/conversation-routes.ts +4 -44
- package/src/runtime/routes/debug-routes.ts +12 -9
- package/src/runtime/routes/diagnostics-routes.ts +1 -477
- package/src/runtime/routes/guardian-approval-interception.ts +168 -11
- package/src/runtime/routes/guardian-approval-prompt.ts +6 -1
- package/src/runtime/routes/guardian-approval-reply-helpers.ts +103 -21
- package/src/runtime/routes/identity-routes.ts +19 -30
- package/src/runtime/routes/inbound-message-handler.ts +31 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +64 -5
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +52 -40
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
- package/src/runtime/routes/integrations/twilio.ts +52 -10
- package/src/runtime/routes/integrations/vercel.ts +89 -0
- package/src/runtime/routes/log-export-routes.ts +5 -0
- package/src/runtime/routes/memory-item-routes.test.ts +3 -3
- package/src/runtime/routes/memory-item-routes.ts +46 -14
- package/src/runtime/routes/migration-rollback-routes.ts +209 -0
- package/src/runtime/routes/migration-routes.ts +17 -1
- package/src/runtime/routes/notification-routes.ts +58 -0
- package/src/runtime/routes/schedule-routes.ts +65 -0
- package/src/runtime/routes/secret-routes.ts +141 -10
- package/src/runtime/routes/settings-routes.ts +41 -1
- package/src/runtime/routes/tts-routes.ts +96 -0
- package/src/runtime/routes/upgrade-broadcast-routes.ts +26 -2
- package/src/runtime/routes/workspace-commit-routes.ts +62 -0
- package/src/runtime/routes/workspace-routes.test.ts +22 -1
- package/src/runtime/routes/workspace-routes.ts +1 -1
- package/src/runtime/routes/workspace-utils.ts +86 -2
- package/src/security/ces-credential-client.ts +75 -29
- package/src/security/ces-rpc-credential-backend.ts +86 -0
- package/src/security/credential-backend.ts +22 -92
- package/src/security/keychain-broker-client.ts +10 -2
- package/src/security/secure-keys.ts +113 -115
- package/src/skills/catalog-install.ts +6 -32
- package/src/skills/skill-memory.ts +1 -0
- package/src/subagent/manager.ts +2 -5
- package/src/telemetry/usage-telemetry-reporter.ts +4 -2
- package/src/tools/acp/spawn.ts +78 -1
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/credentials/vault.ts +5 -3
- package/src/tools/executor.ts +0 -4
- package/src/tools/memory/definitions.ts +3 -2
- package/src/tools/memory/handlers.ts +10 -7
- package/src/tools/network/script-proxy/session-manager.ts +19 -4
- package/src/tools/network/web-fetch.ts +3 -1
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/tools/types.ts +0 -8
- package/src/util/browser.ts +15 -0
- package/src/util/errors.ts +0 -12
- package/src/util/platform.ts +4 -51
- package/src/workspace/git-service.ts +5 -2
- package/src/workspace/migrations/001-avatar-rename.ts +15 -0
- package/src/workspace/migrations/003-seed-device-id.ts +17 -1
- package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
- package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
- package/src/workspace/migrations/006-services-config.ts +49 -0
- package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
- package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
- package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
- package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
- package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
- package/src/workspace/migrations/017-seed-persona-dirs.ts +96 -0
- package/src/workspace/migrations/018-rekey-compound-credential-keys.ts +184 -0
- package/src/workspace/migrations/019-scope-journal-to-guardian.ts +103 -0
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +27 -5
- package/src/workspace/migrations/registry.ts +12 -0
- package/src/workspace/migrations/runner.ts +106 -2
- package/src/workspace/migrations/types.ts +4 -0
- package/src/workspace/provider-commit-message-generator.ts +12 -21
- package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
- package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
- package/src/__tests__/diagnostics-export.test.ts +0 -288
- package/src/__tests__/local-gateway-health.test.ts +0 -209
- package/src/__tests__/provider-fail-open-selection.test.ts +0 -271
- package/src/__tests__/provider-failover-actual-provider.test.ts +0 -66
- package/src/__tests__/secret-ingress-handler.test.ts +0 -120
- package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
- package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
- package/src/__tests__/swarm-orchestrator.test.ts +0 -463
- package/src/__tests__/swarm-plan-validator.test.ts +0 -384
- package/src/__tests__/swarm-recursion.test.ts +0 -197
- package/src/__tests__/swarm-router-planner.test.ts +0 -234
- package/src/__tests__/swarm-tool.test.ts +0 -185
- package/src/__tests__/swarm-worker-backend.test.ts +0 -144
- package/src/__tests__/swarm-worker-runner.test.ts +0 -288
- package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
- package/src/commands/cc-command-registry.ts +0 -248
- package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
- package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
- package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
- package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
- package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
- package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
- package/src/config/schemas/swarm.ts +0 -82
- package/src/logfire.ts +0 -135
- package/src/memory/search/lexical.ts +0 -48
- package/src/providers/failover.ts +0 -186
- package/src/runtime/local-gateway-health.ts +0 -275
- package/src/security/secret-ingress.ts +0 -68
- package/src/swarm/backend-claude-code.ts +0 -225
- package/src/swarm/checkpoint.ts +0 -137
- package/src/swarm/graph-utils.ts +0 -53
- package/src/swarm/index.ts +0 -55
- package/src/swarm/limits.ts +0 -66
- package/src/swarm/orchestrator.ts +0 -424
- package/src/swarm/plan-validator.ts +0 -117
- package/src/swarm/router-planner.ts +0 -162
- package/src/swarm/router-prompts.ts +0 -39
- package/src/swarm/synthesizer.ts +0 -81
- package/src/swarm/types.ts +0 -72
- package/src/swarm/worker-backend.ts +0 -131
- package/src/swarm/worker-prompts.ts +0 -80
- package/src/swarm/worker-runner.ts +0 -170
- package/src/tools/claude-code/claude-code.ts +0 -610
- package/src/tools/swarm/delegate.ts +0 -205
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI helper for credential operations that routes through the daemon HTTP API
|
|
3
|
+
* when the daemon is running, falling back to direct secure-keys.ts reads
|
|
4
|
+
* when it is not.
|
|
5
|
+
*
|
|
6
|
+
* Follows the daemon HTTP fetch pattern established in conversations.ts
|
|
7
|
+
* (health check, JWT minting, HTTP call).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import providerEnvVarsRegistry from "../../../../meta/provider-env-vars.json" with { type: "json" };
|
|
11
|
+
import { getRuntimeHttpHost, getRuntimeHttpPort } from "../../config/env.js";
|
|
12
|
+
import { API_KEY_PROVIDERS } from "../../config/loader.js";
|
|
13
|
+
import { healthCheckHost, isHttpHealthy } from "../../daemon/daemon-control.js";
|
|
14
|
+
import {
|
|
15
|
+
initAuthSigningKey,
|
|
16
|
+
loadOrCreateSigningKey,
|
|
17
|
+
mintDaemonDeliveryToken,
|
|
18
|
+
} from "../../runtime/auth/token-service.js";
|
|
19
|
+
import type { DeleteResult } from "../../security/credential-backend.js";
|
|
20
|
+
import { credentialKey } from "../../security/credential-key.js";
|
|
21
|
+
import type { SecureKeyResult } from "../../security/secure-keys.js";
|
|
22
|
+
import {
|
|
23
|
+
deleteSecureKeyAsync,
|
|
24
|
+
getSecureKeyAsync,
|
|
25
|
+
getSecureKeyResultAsync,
|
|
26
|
+
setSecureKeyAsync,
|
|
27
|
+
} from "../../security/secure-keys.js";
|
|
28
|
+
import { getLogger } from "../../util/logger.js";
|
|
29
|
+
|
|
30
|
+
const log = getLogger("daemon-credential-client");
|
|
31
|
+
const CREDENTIAL_KEY_PREFIX = "credential/";
|
|
32
|
+
|
|
33
|
+
const PROVIDER_ENV_VARS: Record<string, string> =
|
|
34
|
+
providerEnvVarsRegistry.providers;
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Private daemon fetch helper
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Attempt an authenticated HTTP request to the running daemon.
|
|
42
|
+
*
|
|
43
|
+
* Returns the Response for ANY HTTP response (including non-ok status codes)
|
|
44
|
+
* so callers can distinguish "daemon rejected the request" from "daemon
|
|
45
|
+
* unreachable". Returns `null` only when the daemon is genuinely unreachable
|
|
46
|
+
* (health check fails or network error). Callers fall back to direct
|
|
47
|
+
* secure-keys.ts only when this returns `null`.
|
|
48
|
+
*/
|
|
49
|
+
async function daemonFetch(
|
|
50
|
+
path: string,
|
|
51
|
+
init?: RequestInit,
|
|
52
|
+
): Promise<Response | null> {
|
|
53
|
+
try {
|
|
54
|
+
if (!(await isHttpHealthy())) return null;
|
|
55
|
+
|
|
56
|
+
const port = getRuntimeHttpPort();
|
|
57
|
+
const host = healthCheckHost(getRuntimeHttpHost());
|
|
58
|
+
initAuthSigningKey(loadOrCreateSigningKey());
|
|
59
|
+
const token = mintDaemonDeliveryToken();
|
|
60
|
+
|
|
61
|
+
const res = await fetch(`http://${host}:${port}${path}`, {
|
|
62
|
+
...init,
|
|
63
|
+
headers: {
|
|
64
|
+
...init?.headers,
|
|
65
|
+
Authorization: `Bearer ${token}`,
|
|
66
|
+
"Content-Type": "application/json",
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (!res.ok) {
|
|
71
|
+
log.warn(
|
|
72
|
+
{ path, status: res.status },
|
|
73
|
+
"Daemon credential request returned non-ok status",
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return res;
|
|
78
|
+
} catch (err) {
|
|
79
|
+
log.warn(
|
|
80
|
+
{ path, error: err instanceof Error ? err.message : String(err) },
|
|
81
|
+
"Daemon credential request error — falling back to direct access",
|
|
82
|
+
);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// Internal helpers
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Derive the canonical credential storage key from a "service:field" name.
|
|
93
|
+
* Mirrors the parsing in secret-routes.ts handleAddSecret / handleDeleteSecret.
|
|
94
|
+
*
|
|
95
|
+
* Uses lastIndexOf to split on the *last* colon so compound service names
|
|
96
|
+
* (e.g. "integration:google") are preserved intact while the single-segment
|
|
97
|
+
* field name is extracted correctly.
|
|
98
|
+
*/
|
|
99
|
+
function deriveCredentialStorageKey(name: string): string {
|
|
100
|
+
// Already a canonical storage key (credential/service/field) — return as-is
|
|
101
|
+
// to avoid double-encoding (e.g. "credential/integration:google/access_token"
|
|
102
|
+
// would otherwise become "credential/credential/integration/google/access_token").
|
|
103
|
+
if (name.startsWith("credential/")) {
|
|
104
|
+
return name;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const colonIdx = name.lastIndexOf(":");
|
|
108
|
+
if (colonIdx < 1 || colonIdx === name.length - 1) {
|
|
109
|
+
// Malformed — return raw name so the caller stores *something*.
|
|
110
|
+
// The daemon would reject this with a 400, so this only fires in
|
|
111
|
+
// the offline fallback path with bad input.
|
|
112
|
+
return name;
|
|
113
|
+
}
|
|
114
|
+
const service = name.slice(0, colonIdx);
|
|
115
|
+
const field = name.slice(colonIdx + 1);
|
|
116
|
+
return credentialKey(service, field);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function deriveReadSecretRequest(account: string): {
|
|
120
|
+
type: "api_key" | "credential";
|
|
121
|
+
name: string;
|
|
122
|
+
} {
|
|
123
|
+
if (account.startsWith(CREDENTIAL_KEY_PREFIX)) {
|
|
124
|
+
const rest = account.slice(CREDENTIAL_KEY_PREFIX.length);
|
|
125
|
+
const slashIdx = rest.indexOf("/");
|
|
126
|
+
if (slashIdx > 0 && slashIdx < rest.length - 1) {
|
|
127
|
+
return {
|
|
128
|
+
type: "credential",
|
|
129
|
+
name: `${rest.slice(0, slashIdx)}:${rest.slice(slashIdx + 1)}`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (
|
|
135
|
+
account.includes(":") &&
|
|
136
|
+
!API_KEY_PROVIDERS.includes(account as (typeof API_KEY_PROVIDERS)[number])
|
|
137
|
+
) {
|
|
138
|
+
return { type: "credential", name: account };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return { type: "api_key", name: account };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// Exported wrapper functions
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Retrieve a secret value from the daemon (via POST /v1/secrets/read with
|
|
150
|
+
* reveal: true). Falls back to direct `getSecureKeyAsync()` when the daemon
|
|
151
|
+
* is not running.
|
|
152
|
+
*/
|
|
153
|
+
export async function getSecureKeyViaDaemon(
|
|
154
|
+
account: string,
|
|
155
|
+
): Promise<string | undefined> {
|
|
156
|
+
const request = deriveReadSecretRequest(account);
|
|
157
|
+
const res = await daemonFetch("/v1/secrets/read", {
|
|
158
|
+
method: "POST",
|
|
159
|
+
body: JSON.stringify({ ...request, reveal: true }),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
if (res?.ok) {
|
|
163
|
+
const json = (await res.json()) as { found: boolean; value?: string };
|
|
164
|
+
return json.found ? json.value : undefined;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Fall back to direct read when daemon is unreachable (null) OR returned
|
|
168
|
+
// a non-ok response — reads are safe to retry via direct access.
|
|
169
|
+
return getSecureKeyAsync(account);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Retrieve a secret value with richer result metadata. Uses the daemon
|
|
174
|
+
* POST /v1/secrets/read endpoint (reveal: true), falling back to
|
|
175
|
+
* `getSecureKeyResultAsync()`.
|
|
176
|
+
*/
|
|
177
|
+
export async function getSecureKeyResultViaDaemon(
|
|
178
|
+
account: string,
|
|
179
|
+
): Promise<SecureKeyResult> {
|
|
180
|
+
const request = deriveReadSecretRequest(account);
|
|
181
|
+
const res = await daemonFetch("/v1/secrets/read", {
|
|
182
|
+
method: "POST",
|
|
183
|
+
body: JSON.stringify({ ...request, reveal: true }),
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
if (res?.ok) {
|
|
187
|
+
const json = (await res.json()) as {
|
|
188
|
+
found: boolean;
|
|
189
|
+
value?: string;
|
|
190
|
+
unreachable?: boolean;
|
|
191
|
+
};
|
|
192
|
+
if (json.found && json.value != null) {
|
|
193
|
+
return { value: json.value, unreachable: false };
|
|
194
|
+
}
|
|
195
|
+
return { value: undefined, unreachable: json.unreachable ?? false };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Fall back to direct read when daemon is unreachable (null) OR returned
|
|
199
|
+
// a non-ok response — reads are safe to retry via direct access.
|
|
200
|
+
return getSecureKeyResultAsync(account);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Store a secret via the daemon POST /v1/secrets endpoint. Falls back to
|
|
205
|
+
* direct `setSecureKeyAsync()` when the daemon is not running.
|
|
206
|
+
*/
|
|
207
|
+
export async function setSecureKeyViaDaemon(
|
|
208
|
+
type: string,
|
|
209
|
+
name: string,
|
|
210
|
+
value: string,
|
|
211
|
+
): Promise<boolean> {
|
|
212
|
+
const res = await daemonFetch("/v1/secrets", {
|
|
213
|
+
method: "POST",
|
|
214
|
+
body: JSON.stringify({ type, name, value }),
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
if (res?.ok) {
|
|
218
|
+
const json = (await res.json()) as { success: boolean };
|
|
219
|
+
return json.success;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (res) {
|
|
223
|
+
// Daemon is running but deliberately rejected the write (e.g. 422
|
|
224
|
+
// validation failure, 400 bad input). Do NOT fall back — the daemon's
|
|
225
|
+
// rejection is authoritative and bypassing it would skip validation.
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Daemon unreachable — fall back to direct write.
|
|
230
|
+
// For credentials, derive the canonical storage key (credential/service/field)
|
|
231
|
+
// to match the daemon path which uses credentialKey().
|
|
232
|
+
const storageKey =
|
|
233
|
+
type === "credential" ? deriveCredentialStorageKey(name) : name;
|
|
234
|
+
return setSecureKeyAsync(storageKey, value);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Delete a secret via the daemon DELETE /v1/secrets endpoint. Falls back to
|
|
239
|
+
* direct `deleteSecureKeyAsync()` when the daemon is not running.
|
|
240
|
+
*/
|
|
241
|
+
export async function deleteSecureKeyViaDaemon(
|
|
242
|
+
type: string,
|
|
243
|
+
name: string,
|
|
244
|
+
): Promise<DeleteResult> {
|
|
245
|
+
const res = await daemonFetch("/v1/secrets", {
|
|
246
|
+
method: "DELETE",
|
|
247
|
+
body: JSON.stringify({ type, name }),
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
if (res?.ok) {
|
|
251
|
+
const json = (await res.json()) as { success: boolean };
|
|
252
|
+
return json.success ? "deleted" : "error";
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (res) {
|
|
256
|
+
// Daemon is running but rejected the delete. Map common status codes
|
|
257
|
+
// to appropriate results without falling back to direct access.
|
|
258
|
+
if (res.status === 404) return "not-found";
|
|
259
|
+
return "error";
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Daemon unreachable — fall back to direct delete.
|
|
263
|
+
// For credentials, derive the canonical storage key (credential/service/field)
|
|
264
|
+
// to match the daemon path which uses credentialKey().
|
|
265
|
+
const storageKey =
|
|
266
|
+
type === "credential" ? deriveCredentialStorageKey(name) : name;
|
|
267
|
+
return deleteSecureKeyAsync(storageKey);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Retrieve a provider API key via the daemon, with env var fallback.
|
|
272
|
+
*
|
|
273
|
+
* Mirrors the behavior of `getProviderKeyAsync()` from secure-keys.ts:
|
|
274
|
+
* first checks the secure store (via daemon), then falls back to the
|
|
275
|
+
* corresponding `<PROVIDER>_API_KEY` environment variable.
|
|
276
|
+
*/
|
|
277
|
+
export async function getProviderKeyViaDaemon(
|
|
278
|
+
provider: string,
|
|
279
|
+
): Promise<string | undefined> {
|
|
280
|
+
const stored = await getSecureKeyViaDaemon(provider);
|
|
281
|
+
if (stored) return stored;
|
|
282
|
+
const envVar = PROVIDER_ENV_VARS[provider];
|
|
283
|
+
return envVar ? process.env[envVar] : undefined;
|
|
284
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -728,7 +728,7 @@ export async function startCli(): Promise<void> {
|
|
|
728
728
|
case "memory_recalled":
|
|
729
729
|
spinner.stop();
|
|
730
730
|
process.stdout.write(
|
|
731
|
-
`\n\x1B[2m[Memory recalled: ${msg.injectedTokens} tokens | t1 ${msg.tier1Count} t2 ${msg.tier2Count} | semantic ${msg.semanticHits} |
|
|
731
|
+
`\n\x1B[2m[Memory recalled: ${msg.injectedTokens} tokens | t1 ${msg.tier1Count} t2 ${msg.tier2Count} | semantic ${msg.semanticHits} | merged ${msg.mergedCount} → selected ${msg.selectedCount}${msg.sparseVectorUsed ? " (sparse)" : ""} | hybrid ${msg.hybridSearchLatencyMs}ms | ${msg.provider}/${msg.model} | ${msg.latencyMs}ms]\x1B[0m\n`,
|
|
732
732
|
);
|
|
733
733
|
spinner.start("Thinking...");
|
|
734
734
|
break;
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* `meta/feature-flags/feature-flag-registry.json` and resolves the effective
|
|
6
6
|
* enabled/disabled state for each declared assistant-scope flag by consulting
|
|
7
7
|
* (in priority order):
|
|
8
|
-
* 1.
|
|
8
|
+
* 1. Override values from `~/.vellum/protected/feature-flags.json` (local)
|
|
9
|
+
* or via the gateway HTTP API (Docker/containerized)
|
|
9
10
|
* 2. defaults registry `defaultEnabled` (for declared keys)
|
|
10
11
|
* 3. `true` (for undeclared keys)
|
|
11
12
|
*
|
|
@@ -14,8 +15,10 @@
|
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
import { existsSync, readFileSync } from "node:fs";
|
|
18
|
+
import { homedir } from "node:os";
|
|
17
19
|
import { dirname, join } from "node:path";
|
|
18
20
|
|
|
21
|
+
import { getBaseDataDir, getIsContainerized } from "./env-registry.js";
|
|
19
22
|
import type { AssistantConfig } from "./schema.js";
|
|
20
23
|
|
|
21
24
|
// ---------------------------------------------------------------------------
|
|
@@ -105,6 +108,182 @@ function parseRegistryToDefaults(parsed: unknown): FeatureFlagDefaultsRegistry {
|
|
|
105
108
|
return result;
|
|
106
109
|
}
|
|
107
110
|
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// Override loading — reads from protected directory or gateway HTTP
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Module-level cache of feature flag override values. Populated lazily on
|
|
117
|
+
* first access, invalidated by `clearFeatureFlagOverridesCache()`.
|
|
118
|
+
*/
|
|
119
|
+
let cachedOverrides: Record<string, boolean> | null = null;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* File format for `~/.vellum/protected/feature-flags.json`, matching the
|
|
123
|
+
* gateway's feature-flag-store.ts schema.
|
|
124
|
+
*/
|
|
125
|
+
interface FeatureFlagFileData {
|
|
126
|
+
version: 1;
|
|
127
|
+
values: Record<string, boolean>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Resolve the path to the feature flag overrides file.
|
|
132
|
+
*
|
|
133
|
+
* Docker: `GATEWAY_SECURITY_DIR/feature-flags.json`
|
|
134
|
+
* Local: `~/.vellum/protected/feature-flags.json`
|
|
135
|
+
*
|
|
136
|
+
* Uses `BASE_DATA_DIR` when set (multi-instance mode) so per-instance
|
|
137
|
+
* feature flag files are correctly scoped.
|
|
138
|
+
*/
|
|
139
|
+
function getFeatureFlagOverridesPath(): string {
|
|
140
|
+
const securityDir = process.env.GATEWAY_SECURITY_DIR;
|
|
141
|
+
if (securityDir) {
|
|
142
|
+
return join(securityDir, "feature-flags.json");
|
|
143
|
+
}
|
|
144
|
+
const root = join(getBaseDataDir() || homedir(), ".vellum");
|
|
145
|
+
return join(root, "protected", "feature-flags.json");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Load override values from the protected feature-flags.json file.
|
|
150
|
+
* Returns an empty record if the file doesn't exist or is malformed.
|
|
151
|
+
*/
|
|
152
|
+
function loadOverridesFromFile(): Record<string, boolean> {
|
|
153
|
+
const path = getFeatureFlagOverridesPath();
|
|
154
|
+
if (!existsSync(path)) return {};
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const raw = readFileSync(path, "utf-8");
|
|
158
|
+
const data = JSON.parse(raw) as FeatureFlagFileData;
|
|
159
|
+
if (data.version !== 1) return {};
|
|
160
|
+
if (
|
|
161
|
+
data.values &&
|
|
162
|
+
typeof data.values === "object" &&
|
|
163
|
+
!Array.isArray(data.values)
|
|
164
|
+
) {
|
|
165
|
+
const filtered: Record<string, boolean> = {};
|
|
166
|
+
for (const [k, v] of Object.entries(data.values)) {
|
|
167
|
+
if (typeof v === "boolean") filtered[k] = v;
|
|
168
|
+
}
|
|
169
|
+
return filtered;
|
|
170
|
+
}
|
|
171
|
+
return {};
|
|
172
|
+
} catch {
|
|
173
|
+
return {};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Load override values from the gateway via synchronous HTTP call.
|
|
179
|
+
*
|
|
180
|
+
* Follows the trust-client pattern: uses `Bun.spawnSync` + `curl` to make
|
|
181
|
+
* a blocking GET request to the gateway's feature-flags endpoint. The
|
|
182
|
+
* gateway returns `{ flags: Array<{ key, enabled, ... }> }` and we extract
|
|
183
|
+
* just the key → enabled map.
|
|
184
|
+
*/
|
|
185
|
+
function loadOverridesFromGateway(): Record<string, boolean> {
|
|
186
|
+
try {
|
|
187
|
+
// Lazy-import to avoid circular dependency and keep this module
|
|
188
|
+
// importable from bootstrap code when not in containerized mode.
|
|
189
|
+
const { getGatewayInternalBaseUrl } =
|
|
190
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
191
|
+
require("./env.js") as typeof import("./env.js");
|
|
192
|
+
const { mintEdgeRelayToken } =
|
|
193
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
194
|
+
require("../runtime/auth/token-service.js") as typeof import("../runtime/auth/token-service.js");
|
|
195
|
+
|
|
196
|
+
const url = `${getGatewayInternalBaseUrl()}/v1/feature-flags`;
|
|
197
|
+
const token = mintEdgeRelayToken();
|
|
198
|
+
|
|
199
|
+
const proc = Bun.spawnSync(
|
|
200
|
+
[
|
|
201
|
+
"curl",
|
|
202
|
+
"-s",
|
|
203
|
+
"-S",
|
|
204
|
+
"-X",
|
|
205
|
+
"GET",
|
|
206
|
+
"--max-time",
|
|
207
|
+
"10",
|
|
208
|
+
"-H",
|
|
209
|
+
`Authorization: Bearer ${token}`,
|
|
210
|
+
"-H",
|
|
211
|
+
"Accept: application/json",
|
|
212
|
+
"-w",
|
|
213
|
+
"\n%{http_code}",
|
|
214
|
+
url,
|
|
215
|
+
],
|
|
216
|
+
{ stdout: "pipe", stderr: "pipe" },
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
if (proc.exitCode !== 0) return {};
|
|
220
|
+
|
|
221
|
+
const output = proc.stdout.toString().trim();
|
|
222
|
+
const lastNewline = output.lastIndexOf("\n");
|
|
223
|
+
const responseBody = lastNewline >= 0 ? output.slice(0, lastNewline) : "";
|
|
224
|
+
const statusCode = parseInt(
|
|
225
|
+
lastNewline >= 0 ? output.slice(lastNewline + 1) : output,
|
|
226
|
+
10,
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
if (statusCode < 200 || statusCode >= 300) return {};
|
|
230
|
+
if (!responseBody) return {};
|
|
231
|
+
|
|
232
|
+
const parsed = JSON.parse(responseBody) as {
|
|
233
|
+
flags?: Array<{ key: string; enabled: boolean }>;
|
|
234
|
+
};
|
|
235
|
+
if (!Array.isArray(parsed.flags)) return {};
|
|
236
|
+
|
|
237
|
+
const result: Record<string, boolean> = {};
|
|
238
|
+
for (const entry of parsed.flags) {
|
|
239
|
+
if (typeof entry.key === "string" && typeof entry.enabled === "boolean") {
|
|
240
|
+
result[entry.key] = entry.enabled;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return result;
|
|
244
|
+
} catch {
|
|
245
|
+
return {};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Load overrides from the appropriate source based on runtime mode.
|
|
251
|
+
* Results are cached at module level.
|
|
252
|
+
*/
|
|
253
|
+
function loadOverrides(): Record<string, boolean> {
|
|
254
|
+
if (cachedOverrides != null) return cachedOverrides;
|
|
255
|
+
|
|
256
|
+
cachedOverrides = getIsContainerized()
|
|
257
|
+
? loadOverridesFromGateway()
|
|
258
|
+
: loadOverridesFromFile();
|
|
259
|
+
|
|
260
|
+
return cachedOverrides;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Invalidate the cached override values so the next call to
|
|
265
|
+
* `isAssistantFeatureFlagEnabled` re-reads from the source.
|
|
266
|
+
*
|
|
267
|
+
* Called by the config watcher when the feature-flags file changes.
|
|
268
|
+
*/
|
|
269
|
+
export function clearFeatureFlagOverridesCache(): void {
|
|
270
|
+
cachedOverrides = null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Directly inject override values into the module-level cache.
|
|
275
|
+
*
|
|
276
|
+
* **Test-only** — bypasses file/gateway loading so unit tests can control
|
|
277
|
+
* flag state without writing to disk. Production code should never call this;
|
|
278
|
+
* use `clearFeatureFlagOverridesCache()` instead and let the resolver
|
|
279
|
+
* re-read from the appropriate source.
|
|
280
|
+
*/
|
|
281
|
+
export function _setOverridesForTesting(
|
|
282
|
+
overrides: Record<string, boolean>,
|
|
283
|
+
): void {
|
|
284
|
+
cachedOverrides = { ...overrides };
|
|
285
|
+
}
|
|
286
|
+
|
|
108
287
|
// ---------------------------------------------------------------------------
|
|
109
288
|
// Public API
|
|
110
289
|
// ---------------------------------------------------------------------------
|
|
@@ -113,19 +292,21 @@ function parseRegistryToDefaults(parsed: unknown): FeatureFlagDefaultsRegistry {
|
|
|
113
292
|
* Resolve whether an assistant feature flag is enabled.
|
|
114
293
|
*
|
|
115
294
|
* Resolution order:
|
|
116
|
-
* 1.
|
|
295
|
+
* 1. Override from `~/.vellum/protected/feature-flags.json` (local) or
|
|
296
|
+
* gateway HTTP (Docker/containerized)
|
|
117
297
|
* 2. defaults registry `defaultEnabled` (for declared assistant-scope keys)
|
|
118
298
|
* 3. `true` (for undeclared keys with no override)
|
|
119
299
|
*/
|
|
120
300
|
export function isAssistantFeatureFlagEnabled(
|
|
121
301
|
key: string,
|
|
122
|
-
|
|
302
|
+
_config: AssistantConfig,
|
|
123
303
|
): boolean {
|
|
124
304
|
const defaults = loadDefaultsRegistry();
|
|
125
305
|
const declared = defaults[key];
|
|
306
|
+
const overrides = loadOverrides();
|
|
126
307
|
|
|
127
|
-
// 1. Check
|
|
128
|
-
const explicit =
|
|
308
|
+
// 1. Check overrides from protected feature-flags file / gateway
|
|
309
|
+
const explicit = overrides[key];
|
|
129
310
|
if (typeof explicit === "boolean") return explicit;
|
|
130
311
|
|
|
131
312
|
// 2. For declared keys, use the registry default
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Bundled Skills — Agent Instructions
|
|
2
|
+
|
|
3
|
+
## Registering Tool Executors
|
|
4
|
+
|
|
5
|
+
When you add a new tool executor to a bundled skill's `TOOLS.json`, you **must** also register it in `assistant/src/config/bundled-tool-registry.ts`. Each new executor needs two things:
|
|
6
|
+
|
|
7
|
+
1. **A static import** at the top of the file, grouped under the skill's section comment.
|
|
8
|
+
2. **A registry entry** in the `bundledToolRegistry` map.
|
|
9
|
+
|
|
10
|
+
### Example (settings skill)
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
// ── settings ───────────────────────────────────────────────────────────────────
|
|
14
|
+
import * as avatarUpdate from "./bundled-skills/settings/tools/avatar-update.js";
|
|
15
|
+
// ... other imports ...
|
|
16
|
+
|
|
17
|
+
export const bundledToolRegistry = new Map<string, SkillToolScript>([
|
|
18
|
+
// settings
|
|
19
|
+
["settings:tools/avatar-update.ts", avatarUpdate],
|
|
20
|
+
// ... other entries ...
|
|
21
|
+
]);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The map key format is `skillDirBasename:executorPath` (e.g. `settings:tools/avatar-update.ts`).
|
|
25
|
+
|
|
26
|
+
### Why this is required
|
|
27
|
+
|
|
28
|
+
`knip` (`lint:unused`) flags dynamically-loaded executor files as unused exports unless they have a static import somewhere in the dependency graph. The registry provides that static import while also enabling the compiled Bun binary to bundle the scripts (since dynamic imports from the filesystem don't work inside `/$bunfs/`).
|
|
29
|
+
|
|
30
|
+
You can regenerate the full registry with:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
bun run scripts/generate-bundled-tool-registry.ts
|
|
34
|
+
```
|
|
@@ -50,6 +50,16 @@ When the user first tries to use ACP and it's not configured, set it up automati
|
|
|
50
50
|
- NEVER use `claude`, `claude -p`, `claude --acp`, or any other command. Only `claude-agent-acp` speaks the ACP JSON-RPC protocol.
|
|
51
51
|
- NEVER change an existing ACP config to use a different command. If the config already has `claude-agent-acp`, leave it alone.
|
|
52
52
|
|
|
53
|
+
## Updating the adapter
|
|
54
|
+
|
|
55
|
+
If `acp_spawn` reports that `claude-agent-acp` is outdated, ask the user before updating. To update:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm install -g @zed-industries/claude-agent-acp@latest
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Then retry the `acp_spawn` call.
|
|
62
|
+
|
|
53
63
|
## Tips
|
|
54
64
|
|
|
55
65
|
- The spawned agent runs autonomously with its own tools, file editing, and terminal access.
|
|
@@ -632,7 +632,3 @@ When the user wants to triage or bulk-act on items, generate an interactive UI w
|
|
|
632
632
|
## External Links
|
|
633
633
|
|
|
634
634
|
Use `vellum.openLink(url, metadata)` to make items clickable. Construct deep-link URLs when possible. Include `metadata.provider` and `metadata.type` for context.
|
|
635
|
-
|
|
636
|
-
## Branding
|
|
637
|
-
|
|
638
|
-
A "Built on Vellum" badge is auto-injected into every page. Do NOT add your own.
|
|
@@ -70,7 +70,7 @@ Slack connects via Socket Mode using a bot token and app-level token (not OAuth)
|
|
|
70
70
|
- Call `skill_load` with `skill: "slack-app-setup"` to load the dependency skill.
|
|
71
71
|
- Tell the user Slack isn't connected yet and briefly explain what the setup involves, then follow the skill's guided flow.
|
|
72
72
|
|
|
73
|
-
The slack-app-setup skill handles: manifest-driven app creation, app token and bot token collection via secure prompt (never accept tokens pasted in plaintext chat), and Slack connection setup through the same settings handler used by the Settings UI. That handler validates the bot token, stores workspace metadata, and activates Socket Mode. The skill also covers optional
|
|
73
|
+
The slack-app-setup skill handles: manifest-driven app creation, app token and bot token collection via secure prompt (never accept tokens pasted in plaintext chat), and Slack connection setup through the same settings handler used by the Settings UI. That handler validates the bot token, stores workspace metadata, and activates Socket Mode. The skill also covers optional identity verification.
|
|
74
74
|
|
|
75
75
|
### Telegram
|
|
76
76
|
|
|
@@ -81,15 +81,15 @@ Telegram uses a bot token (not OAuth). Load the **telegram-setup** skill (which
|
|
|
81
81
|
|
|
82
82
|
The telegram-setup skill handles: verifying the bot token from @BotFather, generating a webhook secret, registering bot commands, and storing credentials securely via the secure credential prompt flow. **Never accept a Telegram bot token pasted in plaintext chat - always use the secure prompt.** Webhook registration with Telegram is handled automatically by the gateway on startup and whenever credentials change.
|
|
83
83
|
|
|
84
|
-
The telegram-setup skill also includes **
|
|
84
|
+
The telegram-setup skill also includes **channel verification**, which links your Telegram account for verified message delivery.
|
|
85
85
|
|
|
86
|
-
###
|
|
86
|
+
### Channel Verification (Voice or Telegram)
|
|
87
87
|
|
|
88
|
-
If the user asks to verify their
|
|
88
|
+
If the user asks to verify their identity for voice or Telegram, load the **guardian-verify-setup** skill:
|
|
89
89
|
|
|
90
90
|
- Call `skill_load` with `skill: "guardian-verify-setup"` to load the dependency skill.
|
|
91
91
|
|
|
92
|
-
The guardian-verify-setup skill handles the full outbound verification flow for voice and Telegram channels. It collects the user's destination (phone number or Telegram chat ID/handle), initiates an outbound verification session, and guides the user through entering or replying with the verification code. This is the single source of truth for
|
|
92
|
+
The guardian-verify-setup skill handles the full outbound verification flow for voice and Telegram channels. It collects the user's destination (phone number or Telegram chat ID/handle), initiates an outbound verification session, and guides the user through entering or replying with the verification code. This is the single source of truth for channel verification setup -- do not duplicate the verification flow inline.
|
|
93
93
|
|
|
94
94
|
## Error Recovery
|
|
95
95
|
|
|
@@ -50,7 +50,7 @@ function upsertMemoryItem(opts: {
|
|
|
50
50
|
Math.max(existing.importance ?? 0, opts.importance),
|
|
51
51
|
),
|
|
52
52
|
lastSeenAt: now,
|
|
53
|
-
|
|
53
|
+
sourceType: "extraction",
|
|
54
54
|
})
|
|
55
55
|
.where(eq(memoryItems.id, existing.id))
|
|
56
56
|
.run();
|
|
@@ -67,7 +67,7 @@ function upsertMemoryItem(opts: {
|
|
|
67
67
|
confidence: 0.8,
|
|
68
68
|
importance: clampUnitInterval(opts.importance),
|
|
69
69
|
fingerprint,
|
|
70
|
-
|
|
70
|
+
sourceType: "extraction",
|
|
71
71
|
scopeId: opts.scopeId,
|
|
72
72
|
firstSeenAt: now,
|
|
73
73
|
lastSeenAt: now,
|
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
"enum": ["assistant_number", "user_number"],
|
|
27
27
|
"description": "Which phone number to use as the caller ID. assistant_number uses the AI assistant's Twilio number; user_number uses the user's verified personal number."
|
|
28
28
|
},
|
|
29
|
+
"skip_disclosure": {
|
|
30
|
+
"type": "boolean",
|
|
31
|
+
"description": "Skip the disclosure announcement at the start of the call. Set to true when calling someone who already knows who you are (e.g. your guardian, a family member, or a close contact)."
|
|
32
|
+
},
|
|
29
33
|
"activity": {
|
|
30
34
|
"type": "string",
|
|
31
35
|
"description": "Brief non-technical explanation of why this tool is being called"
|