@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
|
@@ -13,7 +13,12 @@ import { mintDaemonDeliveryToken } from "../../runtime/auth/token-service.js";
|
|
|
13
13
|
import { deliverChannelReply } from "../../runtime/gateway-client.js";
|
|
14
14
|
import { getLogger } from "../../util/logger.js";
|
|
15
15
|
import { isConversationSeedSane } from "../conversation-seed-composer.js";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
buildAccessRequestIdentityLine,
|
|
18
|
+
buildAccessRequestInviteDirective,
|
|
19
|
+
nonEmpty,
|
|
20
|
+
sanitizeIdentityField,
|
|
21
|
+
} from "../copy-composer.js";
|
|
17
22
|
import type {
|
|
18
23
|
ChannelAdapter,
|
|
19
24
|
ChannelDeliveryPayload,
|
|
@@ -41,6 +46,149 @@ function resolveSlackMessageText(payload: ChannelDeliveryPayload): string {
|
|
|
41
46
|
return payload.sourceEventName.replace(/[._]/g, " ");
|
|
42
47
|
}
|
|
43
48
|
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Block Kit helpers for access request notifications
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Build Block Kit blocks for an access request notification.
|
|
55
|
+
*
|
|
56
|
+
* Returns an array of Slack Block Kit block objects with structured layout:
|
|
57
|
+
* - Header: "New access request"
|
|
58
|
+
* - Section: requester identity details
|
|
59
|
+
* - Optional context: message preview
|
|
60
|
+
* - Context: approval code instructions + invite directive
|
|
61
|
+
*/
|
|
62
|
+
export function buildAccessRequestBlocks(
|
|
63
|
+
payload: Record<string, unknown>,
|
|
64
|
+
): unknown[] {
|
|
65
|
+
const blocks: unknown[] = [];
|
|
66
|
+
|
|
67
|
+
// Header
|
|
68
|
+
blocks.push({
|
|
69
|
+
type: "header",
|
|
70
|
+
text: { type: "plain_text", text: "New access request", emoji: true },
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Requester identity section
|
|
74
|
+
const identityLine = buildAccessRequestIdentityLine(payload);
|
|
75
|
+
blocks.push({
|
|
76
|
+
type: "section",
|
|
77
|
+
text: { type: "mrkdwn", text: identityLine },
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Build fields for structured requester details
|
|
81
|
+
const fields: Array<{ type: "mrkdwn"; text: string }> = [];
|
|
82
|
+
|
|
83
|
+
const senderIdentifier = nonEmpty(
|
|
84
|
+
typeof payload.senderIdentifier === "string"
|
|
85
|
+
? sanitizeIdentityField(payload.senderIdentifier)
|
|
86
|
+
: undefined,
|
|
87
|
+
);
|
|
88
|
+
if (senderIdentifier) {
|
|
89
|
+
fields.push({ type: "mrkdwn", text: `*Name:*\n${senderIdentifier}` });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const actorUsername = nonEmpty(
|
|
93
|
+
typeof payload.actorUsername === "string"
|
|
94
|
+
? sanitizeIdentityField(payload.actorUsername)
|
|
95
|
+
: undefined,
|
|
96
|
+
);
|
|
97
|
+
if (actorUsername) {
|
|
98
|
+
fields.push({ type: "mrkdwn", text: `*Username:*\n@${actorUsername}` });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const sourceChannel = nonEmpty(
|
|
102
|
+
typeof payload.sourceChannel === "string"
|
|
103
|
+
? payload.sourceChannel
|
|
104
|
+
: undefined,
|
|
105
|
+
);
|
|
106
|
+
if (sourceChannel) {
|
|
107
|
+
fields.push({ type: "mrkdwn", text: `*Channel:*\n${sourceChannel}` });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const actorExternalId = nonEmpty(
|
|
111
|
+
typeof payload.actorExternalId === "string"
|
|
112
|
+
? sanitizeIdentityField(payload.actorExternalId)
|
|
113
|
+
: undefined,
|
|
114
|
+
);
|
|
115
|
+
if (actorExternalId && actorExternalId !== senderIdentifier) {
|
|
116
|
+
fields.push({ type: "mrkdwn", text: `*ID:*\n${actorExternalId}` });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (fields.length > 0) {
|
|
120
|
+
blocks.push({
|
|
121
|
+
type: "section",
|
|
122
|
+
fields,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Previously revoked warning
|
|
127
|
+
const previousMemberStatus =
|
|
128
|
+
typeof payload.previousMemberStatus === "string"
|
|
129
|
+
? payload.previousMemberStatus
|
|
130
|
+
: undefined;
|
|
131
|
+
if (previousMemberStatus === "revoked") {
|
|
132
|
+
blocks.push({
|
|
133
|
+
type: "context",
|
|
134
|
+
elements: [
|
|
135
|
+
{
|
|
136
|
+
type: "mrkdwn",
|
|
137
|
+
text: ":warning: This user was previously revoked.",
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Divider before instructions
|
|
144
|
+
blocks.push({ type: "divider" });
|
|
145
|
+
|
|
146
|
+
// Approval code instructions
|
|
147
|
+
const requestCode = nonEmpty(
|
|
148
|
+
typeof payload.requestCode === "string" ? payload.requestCode : undefined,
|
|
149
|
+
);
|
|
150
|
+
if (requestCode) {
|
|
151
|
+
const code = requestCode.toUpperCase();
|
|
152
|
+
blocks.push({
|
|
153
|
+
type: "section",
|
|
154
|
+
text: {
|
|
155
|
+
type: "mrkdwn",
|
|
156
|
+
text: `Reply *\`${code} approve\`* to grant access or *\`${code} reject\`* to deny.`,
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Invite directive
|
|
162
|
+
const inviteDirective = buildAccessRequestInviteDirective();
|
|
163
|
+
blocks.push({
|
|
164
|
+
type: "context",
|
|
165
|
+
elements: [{ type: "mrkdwn", text: inviteDirective }],
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Guardian verification note
|
|
169
|
+
const guardianResolutionSource =
|
|
170
|
+
typeof payload.guardianResolutionSource === "string"
|
|
171
|
+
? payload.guardianResolutionSource
|
|
172
|
+
: undefined;
|
|
173
|
+
if (
|
|
174
|
+
(guardianResolutionSource === "vellum-anchor" ||
|
|
175
|
+
guardianResolutionSource === "none") &&
|
|
176
|
+
sourceChannel
|
|
177
|
+
) {
|
|
178
|
+
blocks.push({
|
|
179
|
+
type: "context",
|
|
180
|
+
elements: [
|
|
181
|
+
{
|
|
182
|
+
type: "mrkdwn",
|
|
183
|
+
text: `_You haven't verified your identity on ${sourceChannel} yet. If this was you trying to message your assistant, say "help me verify as guardian on ${sourceChannel}" to set up direct access._`,
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return blocks;
|
|
190
|
+
}
|
|
191
|
+
|
|
44
192
|
export class SlackAdapter implements ChannelAdapter {
|
|
45
193
|
readonly channel: NotificationChannel = "slack";
|
|
46
194
|
|
|
@@ -65,12 +213,26 @@ export class SlackAdapter implements ChannelAdapter {
|
|
|
65
213
|
|
|
66
214
|
const messageText = resolveSlackMessageText(payload);
|
|
67
215
|
|
|
216
|
+
// Build Block Kit blocks for access request notifications
|
|
217
|
+
const isAccessRequest =
|
|
218
|
+
payload.sourceEventName === "ingress.access_request" &&
|
|
219
|
+
payload.contextPayload != null;
|
|
220
|
+
|
|
68
221
|
try {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
222
|
+
if (isAccessRequest) {
|
|
223
|
+
const blocks = buildAccessRequestBlocks(payload.contextPayload!);
|
|
224
|
+
await deliverChannelReply(
|
|
225
|
+
deliverUrl,
|
|
226
|
+
{ chatId, text: messageText, blocks },
|
|
227
|
+
mintDaemonDeliveryToken(),
|
|
228
|
+
);
|
|
229
|
+
} else {
|
|
230
|
+
await deliverChannelReply(
|
|
231
|
+
deliverUrl,
|
|
232
|
+
{ chatId, text: messageText, useBlocks: true },
|
|
233
|
+
mintDaemonDeliveryToken(),
|
|
234
|
+
);
|
|
235
|
+
}
|
|
74
236
|
|
|
75
237
|
log.info(
|
|
76
238
|
{ sourceEventName: payload.sourceEventName, chatId },
|
|
@@ -99,7 +99,14 @@ export function buildAccessRequestIdentityLine(
|
|
|
99
99
|
const sanitizedExternalId = actorExternalId
|
|
100
100
|
? sanitizeIdentityField(actorExternalId)
|
|
101
101
|
: undefined;
|
|
102
|
-
|
|
102
|
+
// When the requester is a raw Slack user ID (e.g. the fallback path in
|
|
103
|
+
// access-request-helper sets senderIdentifier to the raw actorExternalId),
|
|
104
|
+
// format it as a Slack mention so it renders as a clickable display name.
|
|
105
|
+
const formattedRequester =
|
|
106
|
+
sourceChannel === "slack" && /^U[A-Z0-9]+$/i.test(requester)
|
|
107
|
+
? `<@${requester}>`
|
|
108
|
+
: requester;
|
|
109
|
+
const parts = [formattedRequester];
|
|
103
110
|
if (sanitizedUsername && sanitizedUsername !== requester) {
|
|
104
111
|
parts.push(`@${sanitizedUsername}`);
|
|
105
112
|
}
|
|
@@ -108,7 +115,13 @@ export function buildAccessRequestIdentityLine(
|
|
|
108
115
|
sanitizedExternalId !== requester &&
|
|
109
116
|
sanitizedExternalId !== sanitizedUsername
|
|
110
117
|
) {
|
|
111
|
-
|
|
118
|
+
// For Slack, use the <@U...> mention format so Slack auto-renders
|
|
119
|
+
// the user ID as a clickable display name.
|
|
120
|
+
const formattedId =
|
|
121
|
+
sourceChannel === "slack" && /^U[A-Z0-9]+$/i.test(sanitizedExternalId)
|
|
122
|
+
? `<@${sanitizedExternalId}>`
|
|
123
|
+
: `[${sanitizedExternalId}]`;
|
|
124
|
+
parts.push(formattedId);
|
|
112
125
|
}
|
|
113
126
|
if (sourceChannel) {
|
|
114
127
|
parts.push(`via ${sourceChannel}`);
|
|
@@ -117,6 +130,46 @@ export function buildAccessRequestIdentityLine(
|
|
|
117
130
|
return `${parts.join(" ")} is requesting access to the assistant.`;
|
|
118
131
|
}
|
|
119
132
|
|
|
133
|
+
export const MESSAGE_PREVIEW_MAX_LENGTH = 200;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Sanitize an untrusted message preview for inclusion in notification copy.
|
|
137
|
+
*
|
|
138
|
+
* Like {@link sanitizeIdentityField} but uses the higher
|
|
139
|
+
* MESSAGE_PREVIEW_MAX_LENGTH limit (200 chars) instead of the identity
|
|
140
|
+
* field limit (120 chars).
|
|
141
|
+
*/
|
|
142
|
+
export function sanitizeMessagePreview(value: string): string {
|
|
143
|
+
const stripped = value.replace(/[\x00-\x1f\x7f-\x9f\r\n]+/g, " ").trim();
|
|
144
|
+
const clamped =
|
|
145
|
+
stripped.length > MESSAGE_PREVIEW_MAX_LENGTH
|
|
146
|
+
? stripped.slice(0, MESSAGE_PREVIEW_MAX_LENGTH) + "…"
|
|
147
|
+
: stripped;
|
|
148
|
+
return clamped;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Build a quoted preview of the requester's original message for inclusion
|
|
153
|
+
* in guardian-facing access-request copy. Sanitizes and truncates to keep
|
|
154
|
+
* the notification concise.
|
|
155
|
+
*
|
|
156
|
+
* Returns `undefined` when no usable preview is available.
|
|
157
|
+
*/
|
|
158
|
+
export function buildAccessRequestMessagePreview(
|
|
159
|
+
payload: Record<string, unknown>,
|
|
160
|
+
): string | undefined {
|
|
161
|
+
const raw =
|
|
162
|
+
typeof payload.messagePreview === "string"
|
|
163
|
+
? payload.messagePreview
|
|
164
|
+
: undefined;
|
|
165
|
+
if (!raw) return undefined;
|
|
166
|
+
|
|
167
|
+
const sanitized = sanitizeMessagePreview(raw);
|
|
168
|
+
if (sanitized.length === 0) return undefined;
|
|
169
|
+
|
|
170
|
+
return `> Their message: "${sanitized}"`;
|
|
171
|
+
}
|
|
172
|
+
|
|
120
173
|
export function buildAccessRequestInviteDirective(): string {
|
|
121
174
|
return 'Reply "open invite flow" to start Trusted Contacts invite flow.';
|
|
122
175
|
}
|
|
@@ -227,6 +280,10 @@ export function buildAccessRequestContractText(
|
|
|
227
280
|
|
|
228
281
|
const lines: string[] = [];
|
|
229
282
|
lines.push(buildAccessRequestIdentityLine(payload));
|
|
283
|
+
const preview = buildAccessRequestMessagePreview(payload);
|
|
284
|
+
if (preview) {
|
|
285
|
+
lines.push(preview);
|
|
286
|
+
}
|
|
230
287
|
if (previousMemberStatus === "revoked") {
|
|
231
288
|
lines.push("Note: this user was previously revoked.");
|
|
232
289
|
}
|
|
@@ -13,6 +13,7 @@ import { v4 as uuid } from "uuid";
|
|
|
13
13
|
|
|
14
14
|
import { getDeliverableChannels } from "../channels/config.js";
|
|
15
15
|
import { getConfig } from "../config/loader.js";
|
|
16
|
+
import { resolveGuardianPersona } from "../prompts/persona-resolver.js";
|
|
16
17
|
import { buildCoreIdentityContext } from "../prompts/system-prompt.js";
|
|
17
18
|
import {
|
|
18
19
|
createTimeout,
|
|
@@ -800,7 +801,9 @@ async function classifyWithLLM(
|
|
|
800
801
|
const candidateContext = candidateSet
|
|
801
802
|
? (serializeCandidatesForPrompt(candidateSet) ?? undefined)
|
|
802
803
|
: undefined;
|
|
803
|
-
const rawIdentityContext = buildCoreIdentityContext(
|
|
804
|
+
const rawIdentityContext = buildCoreIdentityContext({
|
|
805
|
+
userPersona: resolveGuardianPersona(),
|
|
806
|
+
});
|
|
804
807
|
const identityContext = rawIdentityContext
|
|
805
808
|
? truncate(rawIdentityContext, MAX_IDENTITY_CONTEXT_CHARS, "\n…[truncated]")
|
|
806
809
|
: undefined;
|
|
@@ -151,6 +151,8 @@ export interface AccessRequestContextPayload {
|
|
|
151
151
|
guardianBindingChannel: string | null;
|
|
152
152
|
guardianResolutionSource: GuardianResolutionSource;
|
|
153
153
|
previousMemberStatus: string | null;
|
|
154
|
+
/** Preview of the requester's original message (first ~200 chars). */
|
|
155
|
+
messagePreview: string | null;
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
export interface NotificationEventContextPayloadMap {
|
|
@@ -79,6 +79,8 @@ export interface ChannelDeliveryPayload {
|
|
|
79
79
|
sourceEventName: string;
|
|
80
80
|
copy: RenderedChannelCopy;
|
|
81
81
|
deepLinkTarget?: Record<string, unknown>;
|
|
82
|
+
/** Original signal context payload — available for channel-specific structured rendering. */
|
|
83
|
+
contextPayload?: Record<string, unknown>;
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
/** Interface that each channel adapter must implement. */
|
|
@@ -156,10 +156,12 @@ async function resolvePlatformConnectionId(
|
|
|
156
156
|
);
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
const body = (await response.json()) as
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
159
|
+
const body = (await response.json()) as unknown;
|
|
160
|
+
const connections = (
|
|
161
|
+
Array.isArray(body)
|
|
162
|
+
? body
|
|
163
|
+
: ((body as Record<string, unknown>).results ?? [])
|
|
164
|
+
) as Array<{ id: string; account_label?: string }>;
|
|
163
165
|
|
|
164
166
|
if (connections.length === 0) {
|
|
165
167
|
throw new Error(
|
|
@@ -147,7 +147,6 @@ const LOW_RISK_PROGRAMS = new Set([
|
|
|
147
147
|
"du",
|
|
148
148
|
"df",
|
|
149
149
|
"assistant",
|
|
150
|
-
"vellum",
|
|
151
150
|
]);
|
|
152
151
|
|
|
153
152
|
// High-risk shell programs / patterns
|
|
@@ -201,32 +200,6 @@ const LOW_RISK_GIT_SUBCOMMANDS = new Set([
|
|
|
201
200
|
"reflog",
|
|
202
201
|
]);
|
|
203
202
|
|
|
204
|
-
// Mutating assistant/vellum CLI subcommands that should be escalated to Medium
|
|
205
|
-
// risk. Most assistant/vellum subcommands are read-only and stay Low risk.
|
|
206
|
-
// This mirrors the git subcommand pattern — only known mutating operations
|
|
207
|
-
// get escalated.
|
|
208
|
-
const MEDIUM_RISK_CLI_SUBCOMMANDS = new Set([
|
|
209
|
-
"credentials",
|
|
210
|
-
"config",
|
|
211
|
-
"bash",
|
|
212
|
-
"trust",
|
|
213
|
-
"autonomy",
|
|
214
|
-
"contacts",
|
|
215
|
-
"mcp",
|
|
216
|
-
"keys",
|
|
217
|
-
"wake",
|
|
218
|
-
"sleep",
|
|
219
|
-
"hatch",
|
|
220
|
-
"retire",
|
|
221
|
-
"clean",
|
|
222
|
-
"setup",
|
|
223
|
-
"upgrade",
|
|
224
|
-
"recover",
|
|
225
|
-
"login",
|
|
226
|
-
"use",
|
|
227
|
-
"pair",
|
|
228
|
-
]);
|
|
229
|
-
|
|
230
203
|
// Commands that wrap another program — the real program appears as the first
|
|
231
204
|
// non-flag argument. When one of these is the segment program we look through
|
|
232
205
|
// its args to find the effective program (e.g. `env curl …` → curl).
|
|
@@ -771,17 +744,6 @@ async function classifyRiskUncached(
|
|
|
771
744
|
continue;
|
|
772
745
|
}
|
|
773
746
|
|
|
774
|
-
if (prog === "vellum" || prog === "assistant") {
|
|
775
|
-
const subcommand = firstPositionalArg(seg.args);
|
|
776
|
-
if (subcommand && MEDIUM_RISK_CLI_SUBCOMMANDS.has(subcommand)) {
|
|
777
|
-
// Known mutating subcommands are medium
|
|
778
|
-
maxRisk = RiskLevel.Medium;
|
|
779
|
-
continue;
|
|
780
|
-
}
|
|
781
|
-
// Read-only / unknown subcommands stay at current risk
|
|
782
|
-
continue;
|
|
783
|
-
}
|
|
784
|
-
|
|
785
747
|
if (!LOW_RISK_PROGRAMS.has(prog)) {
|
|
786
748
|
// Unknown program → medium
|
|
787
749
|
if (maxRisk === RiskLevel.Low) {
|
|
@@ -105,8 +105,10 @@ export async function analyzeShellCommand(
|
|
|
105
105
|
* - action:gh pr
|
|
106
106
|
* - action:gh
|
|
107
107
|
*
|
|
108
|
-
*
|
|
109
|
-
* action keys.
|
|
108
|
+
* Simple actions (optional setup prefix + one action) and pipelines get
|
|
109
|
+
* action keys. Both are marked non-simple when they involve pipes, but
|
|
110
|
+
* pipelines extract keys from the first segment before the first pipe.
|
|
111
|
+
* Complex chains (semicolons, ||, &, newlines) get no action keys.
|
|
110
112
|
*/
|
|
111
113
|
export function deriveShellActionKeys(
|
|
112
114
|
analysis: ShellIdentityAnalysis,
|
|
@@ -117,18 +119,22 @@ export function deriveShellActionKeys(
|
|
|
117
119
|
return { keys: [], isSimpleAction: false };
|
|
118
120
|
}
|
|
119
121
|
|
|
120
|
-
// For multi-segment commands,
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
// separators (;, newline, &) and leaves them as empty operators.
|
|
122
|
+
// For multi-segment commands, check operators to determine the command shape.
|
|
123
|
+
// Pipes (|) get special handling — we can extract action keys from the first
|
|
124
|
+
// segment before the pipe. Other complex operators (||, ;, &, empty/missing)
|
|
125
|
+
// are truly opaque and get no action keys.
|
|
125
126
|
if (segments.length > 1) {
|
|
127
|
+
let hasPipe = false;
|
|
128
|
+
|
|
126
129
|
for (const seg of segments) {
|
|
127
130
|
const op = seg.operator;
|
|
128
|
-
// Non-empty operator that isn't && → definitely complex
|
|
129
|
-
if (op && op !== "&&") {
|
|
131
|
+
// Non-empty operator that isn't && or | → definitely complex, no keys
|
|
132
|
+
if (op && op !== "&&" && op !== "|") {
|
|
130
133
|
return { keys: [], isSimpleAction: false };
|
|
131
134
|
}
|
|
135
|
+
if (op === "|") {
|
|
136
|
+
hasPipe = true;
|
|
137
|
+
}
|
|
132
138
|
}
|
|
133
139
|
// Also check: if there are multiple segments but no operators at all
|
|
134
140
|
// between them (e.g. newline-separated), that's suspicious.
|
|
@@ -140,6 +146,42 @@ export function deriveShellActionKeys(
|
|
|
140
146
|
return { keys: [], isSimpleAction: false };
|
|
141
147
|
}
|
|
142
148
|
}
|
|
149
|
+
|
|
150
|
+
// For pipelines, extract action keys from the first non-setup-prefix segment
|
|
151
|
+
// before the first pipe. This enables broader "Any pdftotext command" rules
|
|
152
|
+
// that match pipelines like "pdftotext file | head -100".
|
|
153
|
+
if (hasPipe) {
|
|
154
|
+
const firstPipeIndex = segments.findIndex((s) => s.operator === "|");
|
|
155
|
+
if (firstPipeIndex > 0) {
|
|
156
|
+
const preSegments = segments.slice(0, firstPipeIndex);
|
|
157
|
+
const actionSegs = preSegments.filter(
|
|
158
|
+
(s) => !SETUP_PREFIX_PROGRAMS.has(s.program),
|
|
159
|
+
);
|
|
160
|
+
if (actionSegs.length === 1) {
|
|
161
|
+
const seg = actionSegs[0];
|
|
162
|
+
const tokens: string[] = [seg.program];
|
|
163
|
+
for (const arg of seg.args) {
|
|
164
|
+
if (tokens.length >= MAX_ACTION_KEY_DEPTH) break;
|
|
165
|
+
if (arg.startsWith("-")) continue;
|
|
166
|
+
if (arg.includes("/") || arg.startsWith(".")) continue;
|
|
167
|
+
if (/^\d+$/.test(arg)) continue;
|
|
168
|
+
if (arg.includes("$") || arg.includes('"') || arg.includes("'"))
|
|
169
|
+
continue;
|
|
170
|
+
tokens.push(arg);
|
|
171
|
+
}
|
|
172
|
+
const keys: ShellActionKey[] = [];
|
|
173
|
+
for (let depth = tokens.length; depth >= 1; depth--) {
|
|
174
|
+
keys.push({
|
|
175
|
+
key: `action:${tokens.slice(0, depth).join(" ")}`,
|
|
176
|
+
depth,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return { keys, isSimpleAction: false, primarySegment: seg };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// Pipeline but couldn't extract a single primary action — no keys
|
|
183
|
+
return { keys: [], isSimpleAction: false };
|
|
184
|
+
}
|
|
143
185
|
}
|
|
144
186
|
|
|
145
187
|
// Separate setup-prefix segments from action segments
|
|
@@ -190,9 +232,10 @@ export function deriveShellActionKeys(
|
|
|
190
232
|
* Candidate ordering:
|
|
191
233
|
* 1. Raw command (most specific match — the full command as written)
|
|
192
234
|
* 2. Canonical primary command (if simple action) — the full primary segment text
|
|
193
|
-
* 3. Action keys from narrowest to broadest (if simple action)
|
|
235
|
+
* 3. Action keys from narrowest to broadest (if simple action or pipeline)
|
|
194
236
|
*
|
|
195
|
-
* Complex commands (
|
|
237
|
+
* Complex non-pipeline commands (multi-action chains, semicolons, etc.) only
|
|
238
|
+
* return the raw candidate.
|
|
196
239
|
*/
|
|
197
240
|
export async function buildShellCommandCandidates(
|
|
198
241
|
command: string,
|
|
@@ -206,14 +249,15 @@ export async function buildShellCommandCandidates(
|
|
|
206
249
|
|
|
207
250
|
const candidates: string[] = [trimmed];
|
|
208
251
|
|
|
209
|
-
if (
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (
|
|
213
|
-
|
|
252
|
+
// Add action keys as candidates if available (simple actions AND pipelines)
|
|
253
|
+
if (actionResult.keys.length > 0) {
|
|
254
|
+
// For simple actions, also add the canonical primary command text
|
|
255
|
+
if (actionResult.isSimpleAction && actionResult.primarySegment) {
|
|
256
|
+
const canonical = actionResult.primarySegment.command;
|
|
257
|
+
if (canonical !== trimmed) {
|
|
258
|
+
candidates.push(canonical);
|
|
259
|
+
}
|
|
214
260
|
}
|
|
215
|
-
|
|
216
|
-
// Add action keys
|
|
217
261
|
for (const actionKey of actionResult.keys) {
|
|
218
262
|
candidates.push(actionKey.key);
|
|
219
263
|
}
|
|
@@ -231,8 +275,9 @@ export async function buildShellCommandCandidates(
|
|
|
231
275
|
* 2. Deepest action key (e.g. "action:gh pr view")
|
|
232
276
|
* 3. Broader action keys (e.g. "action:gh pr", "action:gh")
|
|
233
277
|
*
|
|
234
|
-
* For
|
|
235
|
-
*
|
|
278
|
+
* For pipelines, the exact command plus action-key-based broader options
|
|
279
|
+
* are offered. For other complex commands (multi-action chains, semicolons,
|
|
280
|
+
* etc.), only the exact command is offered.
|
|
236
281
|
*/
|
|
237
282
|
export async function buildShellAllowlistOptions(
|
|
238
283
|
command: string,
|
|
@@ -244,14 +289,23 @@ export async function buildShellAllowlistOptions(
|
|
|
244
289
|
const actionResult = deriveShellActionKeys(analysis);
|
|
245
290
|
|
|
246
291
|
if (!actionResult.isSimpleAction || !actionResult.primarySegment) {
|
|
247
|
-
|
|
248
|
-
return [
|
|
292
|
+
const options: AllowlistOption[] = [
|
|
249
293
|
{
|
|
250
294
|
label: trimmed,
|
|
251
295
|
description: "This exact compound command",
|
|
252
296
|
pattern: trimmed,
|
|
253
297
|
},
|
|
254
298
|
];
|
|
299
|
+
// If pipeline action keys were extracted, offer them as broader options
|
|
300
|
+
for (const actionKey of actionResult.keys) {
|
|
301
|
+
const keyTokens = actionKey.key.replace(/^action:/, "");
|
|
302
|
+
options.push({
|
|
303
|
+
label: `${keyTokens} *`,
|
|
304
|
+
description: `Any "${keyTokens}" command`,
|
|
305
|
+
pattern: actionKey.key,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
return options;
|
|
255
309
|
}
|
|
256
310
|
|
|
257
311
|
const options: AllowlistOption[] = [];
|
package/src/permissions/types.ts
CHANGED
|
@@ -24,7 +24,8 @@ export type UserDecision =
|
|
|
24
24
|
| "always_allow_high_risk"
|
|
25
25
|
| "deny"
|
|
26
26
|
| "always_deny"
|
|
27
|
-
| "temporary_override"
|
|
27
|
+
| "temporary_override"
|
|
28
|
+
| "dangerously_skip_permissions";
|
|
28
29
|
|
|
29
30
|
/** Returns true for any allow-variant decision. Centralizes the check to prevent omissions when new allow variants are added. */
|
|
30
31
|
export function isAllowDecision(decision: UserDecision): boolean {
|
|
@@ -34,7 +35,8 @@ export function isAllowDecision(decision: UserDecision): boolean {
|
|
|
34
35
|
decision === "allow_conversation" ||
|
|
35
36
|
decision === "always_allow" ||
|
|
36
37
|
decision === "always_allow_high_risk" ||
|
|
37
|
-
decision === "temporary_override"
|
|
38
|
+
decision === "temporary_override" ||
|
|
39
|
+
decision === "dangerously_skip_permissions"
|
|
38
40
|
);
|
|
39
41
|
}
|
|
40
42
|
|
package/src/platform/client.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
import { getPlatformAssistantId } from "../config/env.js";
|
|
9
9
|
import { resolveManagedProxyContext } from "../providers/managed-proxy/context.js";
|
|
10
|
+
import { credentialKey } from "../security/credential-key.js";
|
|
11
|
+
import { getSecureKeyAsync } from "../security/secure-keys.js";
|
|
10
12
|
|
|
11
13
|
export class VellumPlatformClient {
|
|
12
14
|
private readonly platformBaseUrl: string;
|
|
@@ -26,21 +28,47 @@ export class VellumPlatformClient {
|
|
|
26
28
|
/**
|
|
27
29
|
* Create a platform client by resolving managed proxy context.
|
|
28
30
|
*
|
|
31
|
+
* First tries the in-memory managed proxy context (available when the daemon
|
|
32
|
+
* has rehydrated env overrides). Falls back to reading platform credentials
|
|
33
|
+
* directly from the secure keychain so that standalone CLI invocations work
|
|
34
|
+
* without the daemon having run its rehydration step.
|
|
35
|
+
*
|
|
29
36
|
* Returns `null` when auth prerequisites are missing (not logged in, no API
|
|
30
37
|
* key). The assistant ID is resolved but not required — callers that need it
|
|
31
38
|
* should check `platformAssistantId` themselves.
|
|
32
39
|
*/
|
|
33
40
|
static async create(): Promise<VellumPlatformClient | null> {
|
|
34
41
|
const ctx = await resolveManagedProxyContext();
|
|
35
|
-
if (!ctx.enabled) return null;
|
|
36
42
|
|
|
37
|
-
|
|
43
|
+
let baseUrl = ctx.enabled ? ctx.platformBaseUrl : "";
|
|
44
|
+
let apiKey = ctx.enabled ? ctx.assistantApiKey : "";
|
|
45
|
+
let assistantId = getPlatformAssistantId();
|
|
46
|
+
|
|
47
|
+
// Fall back to keychain for values not yet rehydrated (standalone CLI).
|
|
48
|
+
if (!baseUrl) {
|
|
49
|
+
baseUrl =
|
|
50
|
+
(await getSecureKeyAsync(
|
|
51
|
+
credentialKey("vellum", "platform_base_url"),
|
|
52
|
+
)) ?? "";
|
|
53
|
+
}
|
|
54
|
+
if (!apiKey) {
|
|
55
|
+
apiKey =
|
|
56
|
+
(await getSecureKeyAsync(
|
|
57
|
+
credentialKey("vellum", "assistant_api_key"),
|
|
58
|
+
)) ?? "";
|
|
59
|
+
}
|
|
60
|
+
if (!assistantId) {
|
|
61
|
+
assistantId =
|
|
62
|
+
(
|
|
63
|
+
await getSecureKeyAsync(
|
|
64
|
+
credentialKey("vellum", "platform_assistant_id"),
|
|
65
|
+
)
|
|
66
|
+
)?.trim() ?? "";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!baseUrl || !apiKey) return null;
|
|
38
70
|
|
|
39
|
-
return new VellumPlatformClient(
|
|
40
|
-
ctx.platformBaseUrl,
|
|
41
|
-
ctx.assistantApiKey,
|
|
42
|
-
assistantId,
|
|
43
|
-
);
|
|
71
|
+
return new VellumPlatformClient(baseUrl, apiKey, assistantId);
|
|
44
72
|
}
|
|
45
73
|
|
|
46
74
|
/**
|