@vellumai/assistant 0.5.10 → 0.5.12
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/AGENTS.md +8 -0
- package/ARCHITECTURE.md +43 -43
- package/Dockerfile +3 -0
- package/docs/architecture/integrations.md +37 -42
- package/docs/architecture/memory.md +7 -12
- package/docs/credential-execution-service.md +9 -9
- package/docs/skills.md +1 -1
- package/node_modules/@vellumai/ces-contracts/src/__tests__/grants.test.ts +7 -7
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +5 -4
- package/node_modules/@vellumai/credential-storage/src/index.ts +3 -3
- package/node_modules/@vellumai/credential-storage/src/static-credentials.ts +1 -1
- package/openapi.yaml +7208 -0
- package/package.json +2 -1
- package/scripts/generate-openapi.ts +562 -0
- package/src/__tests__/acp-session.test.ts +239 -44
- package/src/__tests__/assistant-feature-flag-guard.test.ts +8 -8
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +5 -86
- package/src/__tests__/assistant-feature-flags-integration.test.ts +7 -14
- package/src/__tests__/browser-skill-endstate.test.ts +1 -1
- package/src/__tests__/btw-routes.test.ts +8 -0
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +10 -10
- package/src/__tests__/catalog-cache.test.ts +164 -0
- package/src/__tests__/catalog-search.test.ts +61 -0
- package/src/__tests__/channel-approvals.test.ts +7 -7
- package/src/__tests__/channel-readiness-service.test.ts +41 -0
- package/src/__tests__/cli-command-risk-guard.test.ts +181 -6
- package/src/__tests__/config-schema.test.ts +10 -2
- package/src/__tests__/context-memory-e2e.test.ts +2 -6
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +396 -0
- package/src/__tests__/conversation-error.test.ts +3 -2
- package/src/__tests__/conversation-skill-tools.test.ts +1 -3
- package/src/__tests__/conversation-title-service.test.ts +2 -15
- package/src/__tests__/credential-execution-feature-gates.test.ts +4 -8
- package/src/__tests__/credential-execution-managed-contract.test.ts +8 -8
- package/src/__tests__/credential-security-e2e.test.ts +4 -4
- package/src/__tests__/credential-security-invariants.test.ts +12 -18
- package/src/__tests__/credential-vault-unit.test.ts +32 -34
- package/src/__tests__/credential-vault.test.ts +25 -33
- package/src/__tests__/credentials-cli.test.ts +3 -3
- package/src/__tests__/daemon-credential-client.test.ts +2 -2
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -1
- package/src/__tests__/gateway-only-guard.test.ts +3 -0
- package/src/__tests__/heartbeat-service.test.ts +35 -0
- package/src/__tests__/host-bash-proxy.test.ts +79 -0
- package/src/__tests__/host-cu-proxy.test.ts +90 -0
- package/src/__tests__/host-file-proxy.test.ts +89 -0
- package/src/__tests__/host-shell-tool.test.ts +1 -1
- package/src/__tests__/inline-skill-load-permissions.test.ts +3 -3
- package/src/__tests__/integration-status.test.ts +5 -5
- package/src/__tests__/list-messages-attachments.test.ts +171 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +64 -0
- package/src/__tests__/log-export-workspace.test.ts +1 -1
- package/src/__tests__/mcp-abort-signal.test.ts +205 -0
- package/src/__tests__/mcp-client-auth.test.ts +1 -1
- package/src/__tests__/memory-lifecycle-e2e.test.ts +2 -2
- package/src/__tests__/memory-recall-log-store.test.ts +182 -0
- package/src/__tests__/memory-recall-quality.test.ts +6 -8
- package/src/__tests__/memory-regressions.test.ts +53 -42
- package/src/__tests__/memory-retrieval.benchmark.test.ts +5 -9
- package/src/__tests__/messaging-send-tool.test.ts +5 -5
- package/src/__tests__/messaging-skill-split.test.ts +2 -17
- package/src/__tests__/notification-telegram-adapter.test.ts +125 -0
- package/src/__tests__/oauth-cli.test.ts +203 -649
- package/src/__tests__/oauth-provider-profiles.test.ts +55 -20
- package/src/__tests__/oauth-scope-policy.test.ts +4 -6
- package/src/__tests__/onboarding-template-contract.test.ts +2 -2
- package/src/__tests__/platform-callback-registration.test.ts +119 -0
- package/src/__tests__/secret-ingress-channel.test.ts +261 -0
- package/src/__tests__/secret-ingress-cli.test.ts +201 -0
- package/src/__tests__/secret-ingress-http.test.ts +312 -0
- package/src/__tests__/secret-ingress.test.ts +283 -0
- package/src/__tests__/secret-onetime-send.test.ts +4 -4
- package/src/__tests__/secret-routes-managed-proxy.test.ts +78 -0
- package/src/__tests__/secure-keys-managed-failover.test.ts +73 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +4 -4
- package/src/__tests__/skill-feature-flags.test.ts +11 -19
- package/src/__tests__/skill-load-feature-flag.test.ts +1 -1
- package/src/__tests__/skill-load-inline-command.test.ts +3 -3
- package/src/__tests__/skill-load-inline-includes.test.ts +2 -2
- package/src/__tests__/skill-memory.test.ts +2 -4
- package/src/__tests__/skill-projection-feature-flag.test.ts +2 -4
- package/src/__tests__/skill-projection.benchmark.test.ts +1 -3
- package/src/__tests__/skills-uninstall.test.ts +2 -2
- package/src/__tests__/skills.test.ts +16 -2
- package/src/__tests__/slack-channel-config.test.ts +1 -1
- package/src/__tests__/slack-messaging-token-resolution.test.ts +22 -24
- package/src/__tests__/slack-share-routes.test.ts +5 -5
- package/src/__tests__/slack-skill.test.ts +5 -69
- package/src/__tests__/system-prompt.test.ts +39 -0
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -1
- package/src/__tests__/workspace-migration-018-rekey-compound-credential-keys.test.ts +181 -0
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +5 -4
- package/src/acp/client-handler.ts +113 -31
- package/src/acp/session-manager.ts +29 -27
- package/src/approvals/guardian-request-resolvers.ts +1 -1
- package/src/cli/AGENTS.md +113 -0
- package/src/cli/commands/autonomy.ts +3 -5
- package/src/cli/commands/browser-relay.ts +2 -17
- package/src/cli/commands/contacts.ts +6 -4
- package/src/cli/commands/conversations.ts +13 -1
- package/src/cli/commands/credential-execution.ts +17 -3
- package/src/cli/commands/credentials.ts +2 -8
- package/src/cli/commands/memory.ts +2 -3
- package/src/cli/commands/oauth/__tests__/connect.test.ts +706 -0
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +686 -0
- package/src/cli/commands/oauth/__tests__/mode.test.ts +625 -0
- package/src/cli/commands/oauth/__tests__/ping.test.ts +631 -0
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +574 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +416 -0
- package/src/cli/commands/oauth/__tests__/status.test.ts +551 -0
- package/src/cli/commands/oauth/__tests__/token.test.ts +420 -0
- package/src/cli/commands/oauth/apps.ts +87 -50
- package/src/cli/commands/oauth/connect.ts +405 -0
- package/src/cli/commands/oauth/disconnect.ts +285 -0
- package/src/cli/commands/oauth/index.ts +62 -20
- package/src/cli/commands/oauth/mode.ts +251 -0
- package/src/cli/commands/oauth/ping.ts +196 -0
- package/src/cli/commands/oauth/providers.ts +589 -55
- package/src/cli/commands/oauth/request.ts +564 -0
- package/src/cli/commands/oauth/shared.ts +114 -0
- package/src/cli/commands/oauth/status.ts +191 -0
- package/src/cli/commands/oauth/token.ts +150 -0
- package/src/cli/commands/platform/connect.ts +104 -0
- package/src/cli/commands/platform/disconnect.ts +118 -0
- package/src/cli/commands/platform/index.ts +252 -0
- package/src/cli/commands/sequence.ts +5 -4
- package/src/cli/commands/shotgun.ts +16 -0
- package/src/cli/commands/skills.ts +173 -41
- package/src/cli/commands/usage.ts +5 -11
- package/src/cli/lib/daemon-credential-client.ts +22 -38
- package/src/cli/program.ts +1 -1
- package/src/cli.ts +82 -17
- package/src/config/assistant-feature-flags.ts +77 -18
- package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -1
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
- package/src/config/bundled-skills/conversations/SKILL.md +20 -0
- package/src/config/bundled-skills/conversations/TOOLS.json +23 -0
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +66 -0
- package/src/config/bundled-skills/gmail/SKILL.md +13 -13
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +3 -3
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +2 -2
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +2 -2
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +1 -1
- package/src/config/bundled-skills/google-calendar/SKILL.md +10 -4
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +19 -42
- package/src/config/bundled-skills/messaging/TOOLS.json +9 -9
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +1 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +5 -6
- package/src/config/bundled-skills/notifications/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/SKILL.md +2 -2
- package/src/config/bundled-skills/settings/SKILL.md +5 -3
- package/src/config/bundled-skills/settings/TOOLS.json +17 -0
- package/src/config/bundled-skills/settings/tools/avatar-get.ts +50 -0
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +7 -0
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +6 -1
- package/src/config/bundled-skills/settings/tools/identity-avatar.ts +55 -0
- package/src/config/bundled-skills/skills-catalog/SKILL.md +3 -3
- package/src/config/bundled-skills/slack/SKILL.md +58 -44
- package/src/config/bundled-tool-registry.ts +7 -19
- package/src/config/env.ts +5 -1
- package/src/config/feature-flag-registry.json +58 -42
- package/src/config/loader.ts +4 -0
- package/src/config/schemas/platform.ts +0 -8
- package/src/config/schemas/security.ts +9 -1
- package/src/config/schemas/services.ts +1 -1
- package/src/config/skill-state.ts +1 -3
- package/src/config/skills.ts +2 -4
- package/src/credential-execution/client.ts +1 -1
- package/src/credential-execution/feature-gates.ts +9 -16
- package/src/credential-execution/process-manager.ts +12 -0
- package/src/daemon/config-watcher.ts +4 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +10 -0
- package/src/daemon/conversation-agent-loop.ts +51 -2
- package/src/daemon/conversation-error.ts +36 -6
- package/src/daemon/conversation-memory.ts +0 -1
- package/src/daemon/conversation-messaging.ts +9 -0
- package/src/daemon/conversation-runtime-assembly.ts +33 -0
- package/src/daemon/conversation-surfaces.ts +120 -14
- package/src/daemon/conversation.ts +5 -0
- package/src/daemon/handlers/config-slack-channel.ts +43 -1
- package/src/daemon/handlers/conversations.ts +41 -33
- package/src/daemon/handlers/skills.ts +148 -3
- package/src/daemon/host-bash-proxy.ts +16 -0
- package/src/daemon/host-cu-proxy.ts +16 -0
- package/src/daemon/host-file-proxy.ts +16 -0
- package/src/daemon/lifecycle.ts +73 -3
- package/src/daemon/message-types/acp.ts +0 -15
- package/src/daemon/message-types/conversations.ts +1 -0
- package/src/daemon/message-types/guardian-actions.ts +2 -0
- package/src/daemon/message-types/host-bash.ts +6 -1
- package/src/daemon/message-types/host-cu.ts +6 -1
- package/src/daemon/message-types/host-file.ts +6 -1
- package/src/daemon/message-types/integrations.ts +0 -1
- package/src/daemon/message-types/memory.ts +0 -1
- package/src/daemon/message-types/messages.ts +9 -1
- package/src/daemon/message-types/schedules.ts +9 -0
- package/src/daemon/server.ts +48 -9
- package/src/email/feature-gate.ts +3 -3
- package/src/heartbeat/heartbeat-service.ts +48 -0
- package/src/hooks/cli.ts +74 -0
- package/src/inbound/platform-callback-registration.ts +68 -19
- package/src/mcp/client.ts +6 -1
- package/src/mcp/manager.ts +2 -1
- package/src/mcp/mcp-oauth-provider.ts +3 -3
- package/src/memory/app-store.ts +3 -3
- package/src/memory/conversation-crud.ts +213 -0
- package/src/memory/conversation-key-store.ts +26 -0
- package/src/memory/conversation-title-service.ts +7 -17
- package/src/memory/db-init.ts +24 -0
- package/src/memory/embedding-local.ts +47 -2
- package/src/memory/indexer.ts +13 -10
- package/src/memory/items-extractor.ts +12 -4
- package/src/memory/job-utils.ts +5 -0
- package/src/memory/jobs-store.ts +10 -2
- package/src/memory/journal-memory.ts +6 -2
- package/src/memory/llm-request-log-store.ts +88 -21
- package/src/memory/memory-recall-log-store.ts +128 -0
- package/src/memory/migrations/194-memory-recall-logs.ts +50 -0
- package/src/memory/migrations/195-oauth-providers-ping-config.ts +23 -0
- package/src/memory/migrations/196-messages-conversation-created-at-index.ts +9 -0
- package/src/memory/migrations/196-strip-integration-prefix-from-provider-keys.ts +186 -0
- package/src/memory/migrations/197-oauth-providers-behavior-columns.ts +29 -0
- package/src/memory/migrations/198-drop-setup-skill-id-column.ts +11 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/retriever.test.ts +4 -5
- package/src/memory/schema/infrastructure.ts +31 -0
- package/src/memory/schema/oauth.ts +14 -0
- package/src/messaging/provider.ts +13 -12
- package/src/messaging/providers/gmail/adapter.ts +44 -35
- package/src/messaging/providers/slack/adapter.ts +63 -33
- package/src/messaging/providers/telegram-bot/adapter.ts +7 -9
- package/src/messaging/providers/whatsapp/adapter.ts +6 -8
- package/src/notifications/adapters/telegram.ts +78 -2
- package/src/oauth/__tests__/identity-verifier.test.ts +464 -0
- package/src/oauth/byo-connection.test.ts +22 -24
- package/src/oauth/connect-orchestrator.ts +79 -64
- package/src/oauth/connect-types.ts +7 -65
- package/src/oauth/connection-resolver.test.ts +13 -13
- package/src/oauth/connection-resolver.ts +3 -4
- package/src/oauth/identity-verifier.ts +177 -0
- package/src/oauth/manual-token-connection.ts +5 -5
- package/src/oauth/oauth-store.ts +251 -5
- package/src/oauth/platform-connection.test.ts +56 -6
- package/src/oauth/platform-connection.ts +8 -1
- package/src/oauth/seed-providers.ts +256 -34
- package/src/permissions/checker.ts +129 -3
- package/src/permissions/trust-client.ts +2 -2
- package/src/platform/client.ts +2 -2
- package/src/prompts/journal-context.ts +6 -1
- package/src/prompts/system-prompt.ts +43 -9
- package/src/prompts/templates/BOOTSTRAP.md +16 -5
- package/src/providers/anthropic/client.ts +139 -28
- package/src/runtime/auth/__tests__/middleware.test.ts +19 -0
- package/src/runtime/auth/route-policy.ts +0 -1
- package/src/runtime/btw-sidechain.ts +7 -1
- package/src/runtime/channel-approvals.ts +2 -2
- package/src/runtime/channel-readiness-service.ts +30 -7
- package/src/runtime/guardian-action-service.ts +7 -2
- package/src/runtime/http-router.ts +31 -0
- package/src/runtime/http-server.ts +26 -7
- package/src/runtime/http-types.ts +9 -0
- package/src/runtime/pending-interactions.ts +21 -3
- package/src/runtime/routes/acp-routes.ts +46 -28
- package/src/runtime/routes/app-management-routes.ts +123 -0
- package/src/runtime/routes/app-routes.ts +31 -0
- package/src/runtime/routes/approval-routes.ts +108 -3
- package/src/runtime/routes/attachment-routes.ts +45 -0
- package/src/runtime/routes/avatar-routes.ts +16 -0
- package/src/runtime/routes/brain-graph-routes.ts +18 -0
- package/src/runtime/routes/btw-routes.ts +20 -0
- package/src/runtime/routes/call-routes.ts +81 -0
- package/src/runtime/routes/channel-readiness-routes.ts +48 -7
- package/src/runtime/routes/channel-routes.ts +18 -0
- package/src/runtime/routes/channel-verification-routes.ts +49 -1
- package/src/runtime/routes/contact-routes.ts +77 -0
- package/src/runtime/routes/conversation-attention-routes.ts +37 -0
- package/src/runtime/routes/conversation-management-routes.ts +125 -0
- package/src/runtime/routes/conversation-query-routes.ts +78 -0
- package/src/runtime/routes/conversation-routes.ts +191 -39
- package/src/runtime/routes/conversation-starter-routes.ts +29 -0
- package/src/runtime/routes/debug-routes.ts +23 -0
- package/src/runtime/routes/diagnostics-routes.ts +30 -0
- package/src/runtime/routes/documents-routes.ts +42 -0
- package/src/runtime/routes/events-routes.ts +10 -0
- package/src/runtime/routes/global-search-routes.ts +35 -0
- package/src/runtime/routes/guardian-action-routes.ts +61 -3
- package/src/runtime/routes/guardian-approval-prompt.ts +77 -2
- package/src/runtime/routes/heartbeat-routes.ts +278 -0
- package/src/runtime/routes/host-bash-routes.ts +16 -1
- package/src/runtime/routes/host-cu-routes.ts +23 -1
- package/src/runtime/routes/host-file-routes.ts +18 -1
- package/src/runtime/routes/identity-routes.ts +35 -0
- package/src/runtime/routes/inbound-message-handler.ts +46 -25
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -8
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +30 -2
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -2
- package/src/runtime/routes/integrations/slack/share.ts +1 -1
- package/src/runtime/routes/integrations/twilio.ts +32 -22
- package/src/runtime/routes/invite-routes.ts +83 -0
- package/src/runtime/routes/log-export-routes.ts +14 -0
- package/src/runtime/routes/memory-item-routes.ts +99 -1
- package/src/runtime/routes/migration-rollback-routes.ts +25 -0
- package/src/runtime/routes/migration-routes.ts +40 -0
- package/src/runtime/routes/notification-routes.ts +20 -0
- package/src/runtime/routes/oauth-apps.ts +13 -4
- package/src/runtime/routes/pairing-routes.ts +15 -0
- package/src/runtime/routes/recording-routes.ts +72 -0
- package/src/runtime/routes/schedule-routes.ts +77 -5
- package/src/runtime/routes/secret-routes.ts +99 -14
- package/src/runtime/routes/settings-routes.ts +102 -19
- package/src/runtime/routes/skills-routes.ts +141 -18
- package/src/runtime/routes/subagents-routes.ts +38 -3
- package/src/runtime/routes/surface-action-routes.ts +66 -24
- package/src/runtime/routes/surface-content-routes.ts +20 -0
- package/src/runtime/routes/telemetry-routes.ts +12 -0
- package/src/runtime/routes/trace-event-routes.ts +25 -0
- package/src/runtime/routes/trust-rules-routes.ts +46 -0
- package/src/runtime/routes/tts-routes.ts +15 -4
- package/src/runtime/routes/upgrade-broadcast-routes.ts +38 -0
- package/src/runtime/routes/usage-routes.ts +59 -0
- package/src/runtime/routes/watch-routes.ts +28 -0
- package/src/runtime/routes/work-items-routes.ts +59 -0
- package/src/runtime/routes/workspace-commit-routes.ts +12 -0
- package/src/runtime/routes/workspace-routes.ts +102 -0
- package/src/schedule/integration-status.ts +2 -2
- package/src/schedule/scheduler.ts +7 -1
- package/src/security/AGENTS.md +7 -0
- package/src/security/ces-rpc-credential-backend.ts +19 -16
- package/src/security/credential-backend.ts +1 -1
- package/src/security/encrypted-store.ts +3 -3
- package/src/security/oauth-completion-page.ts +153 -0
- package/src/security/oauth2.ts +58 -17
- package/src/security/secret-ingress.ts +174 -0
- package/src/security/secret-patterns.ts +133 -0
- package/src/security/secret-scanner.ts +28 -117
- package/src/security/secure-keys.ts +207 -7
- package/src/security/token-manager.ts +3 -6
- package/src/signals/bash.ts +6 -1
- package/src/signals/confirm.ts +12 -8
- package/src/signals/user-message.ts +18 -3
- package/src/skills/catalog-cache.ts +44 -0
- package/src/skills/catalog-search.ts +18 -0
- package/src/skills/skill-memory.ts +1 -2
- package/src/tasks/task-runner.ts +7 -1
- package/src/tools/credentials/broker.ts +1 -1
- package/src/tools/credentials/metadata-store.ts +1 -1
- package/src/tools/credentials/post-connect-hooks.ts +1 -1
- package/src/tools/credentials/vault.ts +36 -48
- package/src/tools/host-terminal/host-shell.ts +16 -3
- package/src/tools/mcp/mcp-tool-factory.ts +2 -1
- package/src/tools/memory/definitions.ts +1 -1
- package/src/tools/memory/handlers.test.ts +2 -4
- package/src/tools/skills/load.ts +1 -1
- package/src/tools/skills/sandbox-runner.ts +16 -3
- package/src/tools/terminal/safe-env.ts +7 -0
- package/src/tools/terminal/shell.ts +16 -3
- package/src/tools/tool-manifest.ts +1 -1
- package/src/util/log-redact.ts +9 -34
- package/src/util/logger.ts +11 -1
- package/src/util/sentry-log-stream.ts +51 -0
- package/src/watcher/providers/github.ts +2 -2
- package/src/watcher/providers/gmail.ts +1 -1
- package/src/watcher/providers/google-calendar.ts +1 -1
- package/src/watcher/providers/linear.ts +2 -2
- package/src/workspace/migrations/011-backfill-installation-id.ts +5 -3
- package/src/workspace/migrations/020-rename-oauth-skill-dirs.ts +119 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/docs/architecture/keychain-broker.md +0 -68
- package/src/cli/commands/oauth/connections.ts +0 -734
- package/src/cli/commands/oauth/platform.ts +0 -525
- package/src/cli/commands/platform.ts +0 -176
- package/src/config/bundled-skills/slack/TOOLS.json +0 -272
- package/src/config/bundled-skills/slack/tools/shared.ts +0 -34
- package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +0 -27
- package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +0 -38
- package/src/config/bundled-skills/slack/tools/slack-channel-permissions.ts +0 -146
- package/src/config/bundled-skills/slack/tools/slack-configure-channels.ts +0 -105
- package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +0 -26
- package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +0 -27
- package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +0 -25
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +0 -372
- package/src/oauth/provider-behaviors.ts +0 -634
|
@@ -112,11 +112,11 @@ const GATEWAY_RETRIEVAL_BANLIST: Array<{
|
|
|
112
112
|
},
|
|
113
113
|
];
|
|
114
114
|
|
|
115
|
-
const
|
|
116
|
-
// Keep empty unless a
|
|
115
|
+
const CREDENTIAL_LOOKUP_ALLOWLIST = new Set<string>([
|
|
116
|
+
// Keep empty unless a credential lookup instruction is intentionally required.
|
|
117
117
|
]);
|
|
118
118
|
|
|
119
|
-
const
|
|
119
|
+
const CREDENTIAL_LOOKUP_PATTERNS = [
|
|
120
120
|
"security find-generic-password",
|
|
121
121
|
"secret-tool lookup service vellum-assistant account credential:",
|
|
122
122
|
"secret-tool lookup service vellum-assistant account credential/",
|
|
@@ -134,7 +134,7 @@ const RETRIEVAL_MARKERS = [
|
|
|
134
134
|
];
|
|
135
135
|
|
|
136
136
|
describe("bundled skill retrieval guard", () => {
|
|
137
|
-
test("migrated skills do not reintroduce direct gateway/
|
|
137
|
+
test("migrated skills do not reintroduce direct gateway/credential lookup retrieval snippets", () => {
|
|
138
138
|
const violations: string[] = [];
|
|
139
139
|
|
|
140
140
|
for (const rule of GATEWAY_RETRIEVAL_BANLIST) {
|
|
@@ -152,7 +152,7 @@ describe("bundled skill retrieval guard", () => {
|
|
|
152
152
|
if (violations.length > 0) {
|
|
153
153
|
const message = [
|
|
154
154
|
"Skill retrieval contract regression detected.",
|
|
155
|
-
"Migrated skills must not reintroduce direct gateway/
|
|
155
|
+
"Migrated skills must not reintroduce direct gateway/credential lookup retrieval snippets.",
|
|
156
156
|
"",
|
|
157
157
|
"Violations:",
|
|
158
158
|
...violations.map((v) => ` - ${v}`),
|
|
@@ -162,14 +162,14 @@ describe("bundled skill retrieval guard", () => {
|
|
|
162
162
|
}
|
|
163
163
|
});
|
|
164
164
|
|
|
165
|
-
test("skills do not contain direct
|
|
165
|
+
test("skills do not contain direct credential lookup instructions", () => {
|
|
166
166
|
const violations: string[] = [];
|
|
167
167
|
|
|
168
168
|
for (const skillFile of ALL_SKILL_FILES) {
|
|
169
169
|
const rel = relative(REPO_ROOT, skillFile).replaceAll("\\", "/");
|
|
170
|
-
if (
|
|
170
|
+
if (CREDENTIAL_LOOKUP_ALLOWLIST.has(rel)) continue;
|
|
171
171
|
const content = readFileSync(skillFile, "utf-8");
|
|
172
|
-
for (const pattern of
|
|
172
|
+
for (const pattern of CREDENTIAL_LOOKUP_PATTERNS) {
|
|
173
173
|
if (content.includes(pattern)) {
|
|
174
174
|
violations.push(`${rel}: contains "${pattern}"`);
|
|
175
175
|
}
|
|
@@ -178,13 +178,13 @@ describe("bundled skill retrieval guard", () => {
|
|
|
178
178
|
|
|
179
179
|
if (violations.length > 0) {
|
|
180
180
|
const message = [
|
|
181
|
-
"Direct
|
|
181
|
+
"Direct credential lookup instructions were found in skills.",
|
|
182
182
|
"Use credential_store and CLI/proxied flows instead.",
|
|
183
183
|
"",
|
|
184
184
|
"Violations:",
|
|
185
185
|
...violations.map((v) => ` - ${v}`),
|
|
186
186
|
"",
|
|
187
|
-
"Add intentional exceptions to
|
|
187
|
+
"Add intentional exceptions to CREDENTIAL_LOOKUP_ALLOWLIST only when required.",
|
|
188
188
|
].join("\n");
|
|
189
189
|
|
|
190
190
|
expect(violations, message).toEqual([]);
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the catalog cache (catalog-cache.ts).
|
|
3
|
+
*
|
|
4
|
+
* Validates TTL-based caching, re-fetch after expiry, stale-cache fallback
|
|
5
|
+
* on fetch failure, and explicit cache invalidation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { afterEach, describe, expect, mock, test } from "bun:test";
|
|
9
|
+
|
|
10
|
+
import type { CatalogSkill } from "../skills/catalog-install.js";
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Mocks — must be defined before importing the module under test
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
// Suppress logger output
|
|
17
|
+
mock.module("../util/logger.js", () => ({
|
|
18
|
+
getLogger: () =>
|
|
19
|
+
new Proxy({} as Record<string, unknown>, {
|
|
20
|
+
get: () => () => {},
|
|
21
|
+
}),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
let mockRepoSkillsDir: string | undefined = undefined;
|
|
25
|
+
let mockFetchCatalogResult: CatalogSkill[] = [];
|
|
26
|
+
let mockFetchCatalogError: Error | null = null;
|
|
27
|
+
let fetchCatalogCallCount = 0;
|
|
28
|
+
let readLocalCatalogCallCount = 0;
|
|
29
|
+
|
|
30
|
+
mock.module("../skills/catalog-install.js", () => ({
|
|
31
|
+
getRepoSkillsDir: () => mockRepoSkillsDir,
|
|
32
|
+
readLocalCatalog: (_dir: string) => {
|
|
33
|
+
readLocalCatalogCallCount++;
|
|
34
|
+
return mockFetchCatalogResult;
|
|
35
|
+
},
|
|
36
|
+
fetchCatalog: async () => {
|
|
37
|
+
fetchCatalogCallCount++;
|
|
38
|
+
if (mockFetchCatalogError) {
|
|
39
|
+
throw mockFetchCatalogError;
|
|
40
|
+
}
|
|
41
|
+
return mockFetchCatalogResult;
|
|
42
|
+
},
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Imports (after mocks)
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
import { getCatalog, invalidateCatalogCache } from "../skills/catalog-cache.js";
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Helpers
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
const sampleCatalog: CatalogSkill[] = [
|
|
56
|
+
{ id: "web-search", name: "Web Search", description: "Search the web" },
|
|
57
|
+
{ id: "browser", name: "Browser", description: "Browse the web" },
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const updatedCatalog: CatalogSkill[] = [
|
|
61
|
+
{ id: "web-search", name: "Web Search v2", description: "Updated search" },
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
function resetState(): void {
|
|
65
|
+
invalidateCatalogCache();
|
|
66
|
+
mockRepoSkillsDir = undefined;
|
|
67
|
+
mockFetchCatalogResult = [];
|
|
68
|
+
mockFetchCatalogError = null;
|
|
69
|
+
fetchCatalogCallCount = 0;
|
|
70
|
+
readLocalCatalogCallCount = 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
afterEach(resetState);
|
|
74
|
+
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// Tests
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
describe("getCatalog", () => {
|
|
80
|
+
test("returns cached value within TTL without re-fetching", async () => {
|
|
81
|
+
mockFetchCatalogResult = sampleCatalog;
|
|
82
|
+
|
|
83
|
+
const first = await getCatalog();
|
|
84
|
+
expect(first).toEqual(sampleCatalog);
|
|
85
|
+
expect(fetchCatalogCallCount).toBe(1);
|
|
86
|
+
|
|
87
|
+
// Second call should use cache
|
|
88
|
+
const second = await getCatalog();
|
|
89
|
+
expect(second).toEqual(sampleCatalog);
|
|
90
|
+
expect(fetchCatalogCallCount).toBe(1); // no additional fetch
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("re-fetches after TTL expires", async () => {
|
|
94
|
+
mockFetchCatalogResult = sampleCatalog;
|
|
95
|
+
|
|
96
|
+
const first = await getCatalog();
|
|
97
|
+
expect(first).toEqual(sampleCatalog);
|
|
98
|
+
expect(fetchCatalogCallCount).toBe(1);
|
|
99
|
+
|
|
100
|
+
// Simulate TTL expiry by manipulating Date.now
|
|
101
|
+
const originalNow = Date.now;
|
|
102
|
+
Date.now = () => originalNow() + 5 * 60 * 1000 + 1;
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
mockFetchCatalogResult = updatedCatalog;
|
|
106
|
+
const second = await getCatalog();
|
|
107
|
+
expect(second).toEqual(updatedCatalog);
|
|
108
|
+
expect(fetchCatalogCallCount).toBe(2);
|
|
109
|
+
} finally {
|
|
110
|
+
Date.now = originalNow;
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("falls back to stale cache on fetch failure", async () => {
|
|
115
|
+
mockFetchCatalogResult = sampleCatalog;
|
|
116
|
+
|
|
117
|
+
// Populate cache
|
|
118
|
+
const first = await getCatalog();
|
|
119
|
+
expect(first).toEqual(sampleCatalog);
|
|
120
|
+
|
|
121
|
+
// Expire cache and make fetch fail
|
|
122
|
+
const originalNow = Date.now;
|
|
123
|
+
Date.now = () => originalNow() + 5 * 60 * 1000 + 1;
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
mockFetchCatalogError = new Error("Network timeout");
|
|
127
|
+
const fallback = await getCatalog();
|
|
128
|
+
expect(fallback).toEqual(sampleCatalog); // stale cache
|
|
129
|
+
} finally {
|
|
130
|
+
Date.now = originalNow;
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("returns empty array on fetch failure with no stale cache", async () => {
|
|
135
|
+
mockFetchCatalogError = new Error("Network timeout");
|
|
136
|
+
|
|
137
|
+
const result = await getCatalog();
|
|
138
|
+
expect(result).toEqual([]);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("invalidateCatalogCache forces re-fetch", async () => {
|
|
142
|
+
mockFetchCatalogResult = sampleCatalog;
|
|
143
|
+
|
|
144
|
+
await getCatalog();
|
|
145
|
+
expect(fetchCatalogCallCount).toBe(1);
|
|
146
|
+
|
|
147
|
+
invalidateCatalogCache();
|
|
148
|
+
|
|
149
|
+
mockFetchCatalogResult = updatedCatalog;
|
|
150
|
+
const refreshed = await getCatalog();
|
|
151
|
+
expect(refreshed).toEqual(updatedCatalog);
|
|
152
|
+
expect(fetchCatalogCallCount).toBe(2);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("uses local catalog when repoSkillsDir is set", async () => {
|
|
156
|
+
mockRepoSkillsDir = "/mock/repo/skills";
|
|
157
|
+
mockFetchCatalogResult = sampleCatalog;
|
|
158
|
+
|
|
159
|
+
const result = await getCatalog();
|
|
160
|
+
expect(result).toEqual(sampleCatalog);
|
|
161
|
+
expect(readLocalCatalogCallCount).toBe(1);
|
|
162
|
+
expect(fetchCatalogCallCount).toBe(0); // no remote fetch
|
|
163
|
+
});
|
|
164
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { filterByQuery } from "../skills/catalog-search.js";
|
|
4
|
+
|
|
5
|
+
interface FakeSkill {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const skills: FakeSkill[] = [
|
|
12
|
+
{
|
|
13
|
+
id: "weather",
|
|
14
|
+
name: "Weather Lookup",
|
|
15
|
+
description: "Get current weather for a city",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: "search",
|
|
19
|
+
name: "Web Search",
|
|
20
|
+
description: "Search the web for information",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: "deploy",
|
|
24
|
+
name: "Deploy Helper",
|
|
25
|
+
description: "Deploy apps to production",
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const fields: ((s: FakeSkill) => string)[] = [
|
|
30
|
+
(s) => s.id,
|
|
31
|
+
(s) => s.name,
|
|
32
|
+
(s) => s.description,
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
describe("filterByQuery", () => {
|
|
36
|
+
test("case-insensitive matching", () => {
|
|
37
|
+
const result = filterByQuery(skills, "WEATHER", fields);
|
|
38
|
+
expect(result).toEqual([skills[0]]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("matches on any supplied field accessor", () => {
|
|
42
|
+
// Match on id
|
|
43
|
+
expect(filterByQuery(skills, "deploy", fields)).toEqual([skills[2]]);
|
|
44
|
+
// Match on name
|
|
45
|
+
expect(filterByQuery(skills, "Web Search", fields)).toEqual([skills[1]]);
|
|
46
|
+
// Match on description
|
|
47
|
+
expect(filterByQuery(skills, "production", fields)).toEqual([skills[2]]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("returns empty array for no matches", () => {
|
|
51
|
+
const result = filterByQuery(skills, "nonexistent", fields);
|
|
52
|
+
expect(result).toEqual([]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("returns all items for broad query", () => {
|
|
56
|
+
// All skills have "e" somewhere in their fields
|
|
57
|
+
const result = filterByQuery(skills, "e", fields);
|
|
58
|
+
expect(result).toHaveLength(3);
|
|
59
|
+
expect(result).toEqual(skills);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -304,7 +304,7 @@ describe("handleChannelDecision", () => {
|
|
|
304
304
|
expect(result.applied).toBe(true);
|
|
305
305
|
expect(result.requestId).toBe("req-1");
|
|
306
306
|
expect(
|
|
307
|
-
interaction!.conversation
|
|
307
|
+
interaction!.conversation!.handleConfirmationResponse,
|
|
308
308
|
).toHaveBeenCalledWith("req-1", "allow");
|
|
309
309
|
});
|
|
310
310
|
|
|
@@ -320,7 +320,7 @@ describe("handleChannelDecision", () => {
|
|
|
320
320
|
expect(result.applied).toBe(true);
|
|
321
321
|
expect(result.requestId).toBe("req-1");
|
|
322
322
|
expect(
|
|
323
|
-
interaction!.conversation
|
|
323
|
+
interaction!.conversation!.handleConfirmationResponse,
|
|
324
324
|
).toHaveBeenCalledWith("req-1", "deny");
|
|
325
325
|
});
|
|
326
326
|
|
|
@@ -339,10 +339,10 @@ describe("handleChannelDecision", () => {
|
|
|
339
339
|
expect(result.applied).toBe(true);
|
|
340
340
|
expect(result.requestId).toBe("req-newer");
|
|
341
341
|
expect(
|
|
342
|
-
newerInteraction!.conversation
|
|
342
|
+
newerInteraction!.conversation!.handleConfirmationResponse,
|
|
343
343
|
).toHaveBeenCalledWith("req-newer", "allow");
|
|
344
344
|
expect(
|
|
345
|
-
olderInteraction!.conversation
|
|
345
|
+
olderInteraction!.conversation!.handleConfirmationResponse,
|
|
346
346
|
).not.toHaveBeenCalled();
|
|
347
347
|
});
|
|
348
348
|
|
|
@@ -393,7 +393,7 @@ describe("handleChannelDecision", () => {
|
|
|
393
393
|
|
|
394
394
|
// The session is approved with "allow"
|
|
395
395
|
expect(
|
|
396
|
-
interaction!.conversation
|
|
396
|
+
interaction!.conversation!.handleConfirmationResponse,
|
|
397
397
|
).toHaveBeenCalledWith("req-1", "allow");
|
|
398
398
|
|
|
399
399
|
addRuleSpy.mockRestore();
|
|
@@ -420,7 +420,7 @@ describe("handleChannelDecision", () => {
|
|
|
420
420
|
// The decision should still be applied as a one-time approval
|
|
421
421
|
expect(result.applied).toBe(true);
|
|
422
422
|
expect(
|
|
423
|
-
interaction!.conversation
|
|
423
|
+
interaction!.conversation!.handleConfirmationResponse,
|
|
424
424
|
).toHaveBeenCalledWith("req-1", "allow");
|
|
425
425
|
|
|
426
426
|
addRuleSpy.mockRestore();
|
|
@@ -446,7 +446,7 @@ describe("handleChannelDecision", () => {
|
|
|
446
446
|
// The current invocation should still be approved (one-time allow)
|
|
447
447
|
expect(result.applied).toBe(true);
|
|
448
448
|
expect(
|
|
449
|
-
interaction!.conversation
|
|
449
|
+
interaction!.conversation!.handleConfirmationResponse,
|
|
450
450
|
).toHaveBeenCalledWith("req-1", "allow");
|
|
451
451
|
|
|
452
452
|
addRuleSpy.mockRestore();
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
|
+
import { credentialKey } from "../security/credential-key.js";
|
|
4
|
+
|
|
3
5
|
let mockTwilioPhoneNumber: string | undefined;
|
|
4
6
|
let mockRawConfig: Record<string, unknown> | undefined;
|
|
5
7
|
let mockSecureKeys: Record<string, string>;
|
|
6
8
|
let mockHasTwilioCredentials: boolean;
|
|
9
|
+
let mockShouldUsePlatformCallbacks: boolean;
|
|
7
10
|
|
|
8
11
|
mock.module("../calls/twilio-rest.js", () => ({
|
|
9
12
|
getPhoneNumberSid: async () => null,
|
|
@@ -40,6 +43,10 @@ mock.module("../email/service.js", () => ({
|
|
|
40
43
|
}),
|
|
41
44
|
}));
|
|
42
45
|
|
|
46
|
+
mock.module("../inbound/platform-callback-registration.js", () => ({
|
|
47
|
+
shouldUsePlatformCallbacks: () => mockShouldUsePlatformCallbacks,
|
|
48
|
+
}));
|
|
49
|
+
|
|
43
50
|
mock.module("../security/secure-keys.js", () => ({
|
|
44
51
|
getSecureKeyAsync: async (key: string) => mockSecureKeys[key] ?? null,
|
|
45
52
|
}));
|
|
@@ -51,6 +58,7 @@ mock.module("../runtime/channel-invite-transports/whatsapp.js", () => ({
|
|
|
51
58
|
import type { ChannelId } from "../channels/types.js";
|
|
52
59
|
import {
|
|
53
60
|
ChannelReadinessService,
|
|
61
|
+
createReadinessService,
|
|
54
62
|
REMOTE_TTL_MS,
|
|
55
63
|
} from "../runtime/channel-readiness-service.js";
|
|
56
64
|
import type {
|
|
@@ -96,6 +104,7 @@ describe("ChannelReadinessService", () => {
|
|
|
96
104
|
mockRawConfig = undefined;
|
|
97
105
|
mockSecureKeys = {};
|
|
98
106
|
mockHasTwilioCredentials = false;
|
|
107
|
+
mockShouldUsePlatformCallbacks = false;
|
|
99
108
|
});
|
|
100
109
|
|
|
101
110
|
test("local checks run on every call (no caching of local results)", async () => {
|
|
@@ -257,6 +266,38 @@ describe("ChannelReadinessService", () => {
|
|
|
257
266
|
expect(snapshot.reasons).toHaveLength(0);
|
|
258
267
|
});
|
|
259
268
|
|
|
269
|
+
test("telegram readiness accepts managed callback routing when ingress is absent", async () => {
|
|
270
|
+
mockShouldUsePlatformCallbacks = true;
|
|
271
|
+
mockSecureKeys[credentialKey("telegram", "bot_token")] = "123:abc";
|
|
272
|
+
mockSecureKeys[credentialKey("telegram", "webhook_secret")] = "secret";
|
|
273
|
+
|
|
274
|
+
const readiness = createReadinessService();
|
|
275
|
+
const [snapshot] = await readiness.getReadiness("telegram");
|
|
276
|
+
|
|
277
|
+
expect(snapshot.ready).toBe(true);
|
|
278
|
+
expect(snapshot.localChecks).toContainEqual({
|
|
279
|
+
name: "ingress",
|
|
280
|
+
passed: true,
|
|
281
|
+
message: "Managed platform callback routing is configured",
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test("phone readiness accepts managed callback routing when ingress is absent", async () => {
|
|
286
|
+
mockShouldUsePlatformCallbacks = true;
|
|
287
|
+
mockHasTwilioCredentials = true;
|
|
288
|
+
mockTwilioPhoneNumber = "+15555550123";
|
|
289
|
+
|
|
290
|
+
const readiness = createReadinessService();
|
|
291
|
+
const [snapshot] = await readiness.getReadiness("phone");
|
|
292
|
+
|
|
293
|
+
expect(snapshot.ready).toBe(true);
|
|
294
|
+
expect(snapshot.localChecks).toContainEqual({
|
|
295
|
+
name: "ingress",
|
|
296
|
+
passed: true,
|
|
297
|
+
message: "Managed platform callback routing is configured",
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
260
301
|
test("getReadiness with no channel returns all registered channels", async () => {
|
|
261
302
|
service.registerProbe(
|
|
262
303
|
makeProbe("phone", [{ name: "a", passed: true, message: "ok" }]),
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
// Guard test: assistant CLI commands must
|
|
1
|
+
// Guard test: assistant CLI commands must classify at the expected risk level.
|
|
2
2
|
//
|
|
3
|
-
// The assistant uses its own CLI tools during normal operation.
|
|
4
|
-
//
|
|
3
|
+
// The assistant uses its own CLI tools during normal operation. Most commands
|
|
4
|
+
// should be Low risk so they don't block autonomous workflows. Certain
|
|
5
|
+
// sensitive subcommands are intentionally elevated to Medium or High.
|
|
5
6
|
// See #18982 / #18998 for the regression that motivated this guard.
|
|
6
7
|
|
|
7
8
|
import { mkdtempSync } from "node:fs";
|
|
@@ -60,10 +61,10 @@ function expectLowRisk(command: string, actual: RiskLevel): void {
|
|
|
60
61
|
if (actual !== RiskLevel.Low) {
|
|
61
62
|
throw new Error(
|
|
62
63
|
`"${command}" classified as ${actual} instead of Low. ` +
|
|
63
|
-
`assistant CLI commands must
|
|
64
|
+
`assistant CLI commands must be Low risk by default — the assistant ` +
|
|
64
65
|
`uses its own CLI during normal operation. If you need risk ` +
|
|
65
|
-
`escalation for specific subcommands, add them to
|
|
66
|
-
`in this guard test with justification.`,
|
|
66
|
+
`escalation for specific subcommands, add them to the elevated ` +
|
|
67
|
+
`risk tests in this guard test with justification.`,
|
|
67
68
|
);
|
|
68
69
|
}
|
|
69
70
|
expect(actual).toBe(RiskLevel.Low);
|
|
@@ -86,6 +87,9 @@ describe("CLI command risk guard: assistant commands", () => {
|
|
|
86
87
|
|
|
87
88
|
test("all assistant CLI subcommands classify as Low risk", async () => {
|
|
88
89
|
for (const subcommand of ASSISTANT_SUBCOMMANDS) {
|
|
90
|
+
// Subcommands with elevated children are tested separately below.
|
|
91
|
+
// The bare top-level subcommand (e.g. `assistant oauth`) is still
|
|
92
|
+
// expected to be Low.
|
|
89
93
|
const command = `assistant ${subcommand}`;
|
|
90
94
|
const risk = await classifyRisk("bash", { command });
|
|
91
95
|
expectLowRisk(command, risk);
|
|
@@ -110,3 +114,174 @@ describe("CLI command risk guard: assistant commands", () => {
|
|
|
110
114
|
}
|
|
111
115
|
});
|
|
112
116
|
});
|
|
117
|
+
|
|
118
|
+
// Sensitive subcommands that are intentionally elevated above Low risk.
|
|
119
|
+
// Each entry documents why the elevation is necessary.
|
|
120
|
+
|
|
121
|
+
describe("CLI command risk guard: elevated assistant subcommands", () => {
|
|
122
|
+
test("assistant oauth token is High risk (exposes raw tokens)", async () => {
|
|
123
|
+
const risk = await classifyRisk("bash", {
|
|
124
|
+
command: "assistant oauth token",
|
|
125
|
+
});
|
|
126
|
+
expect(risk).toBe(RiskLevel.High);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("assistant oauth mode --set is High risk (changes auth mode)", async () => {
|
|
130
|
+
const risk = await classifyRisk("bash", {
|
|
131
|
+
command: "assistant oauth mode --set managed",
|
|
132
|
+
});
|
|
133
|
+
expect(risk).toBe(RiskLevel.High);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("assistant oauth mode --set=value is High risk (equals syntax)", async () => {
|
|
137
|
+
const risk = await classifyRisk("bash", {
|
|
138
|
+
command: "assistant oauth mode google --set=managed",
|
|
139
|
+
});
|
|
140
|
+
expect(risk).toBe(RiskLevel.High);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("assistant oauth mode without --set is Low risk (read-only)", async () => {
|
|
144
|
+
const risk = await classifyRisk("bash", {
|
|
145
|
+
command: "assistant oauth mode",
|
|
146
|
+
});
|
|
147
|
+
expect(risk).toBe(RiskLevel.Low);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("assistant credentials reveal is High risk (exposes secrets)", async () => {
|
|
151
|
+
const risk = await classifyRisk("bash", {
|
|
152
|
+
command: "assistant credentials reveal",
|
|
153
|
+
});
|
|
154
|
+
expect(risk).toBe(RiskLevel.High);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("assistant oauth request is Medium risk (initiates OAuth flow)", async () => {
|
|
158
|
+
const risk = await classifyRisk("bash", {
|
|
159
|
+
command: "assistant oauth request",
|
|
160
|
+
});
|
|
161
|
+
expect(risk).toBe(RiskLevel.Medium);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("assistant oauth connect is Medium risk (modifies OAuth connections)", async () => {
|
|
165
|
+
const risk = await classifyRisk("bash", {
|
|
166
|
+
command: "assistant oauth connect",
|
|
167
|
+
});
|
|
168
|
+
expect(risk).toBe(RiskLevel.Medium);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("assistant oauth disconnect is Medium risk (removes OAuth connections)", async () => {
|
|
172
|
+
const risk = await classifyRisk("bash", {
|
|
173
|
+
command: "assistant oauth disconnect",
|
|
174
|
+
});
|
|
175
|
+
expect(risk).toBe(RiskLevel.Medium);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("--help on elevated subcommands is Low risk (read-only)", async () => {
|
|
179
|
+
const helpCommands = [
|
|
180
|
+
"assistant oauth token --help",
|
|
181
|
+
"assistant oauth mode --set --help",
|
|
182
|
+
"assistant credentials reveal --help",
|
|
183
|
+
"assistant oauth request --help",
|
|
184
|
+
"assistant oauth connect --help",
|
|
185
|
+
"assistant oauth disconnect -h",
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
for (const command of helpCommands) {
|
|
189
|
+
const risk = await classifyRisk("bash", { command });
|
|
190
|
+
expectLowRisk(command, risk);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("--help after -- option terminator does not downgrade risk", async () => {
|
|
195
|
+
const risk = await classifyRisk("bash", {
|
|
196
|
+
command: "assistant oauth token -- --help",
|
|
197
|
+
});
|
|
198
|
+
expect(risk).toBe(RiskLevel.High);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("non-sensitive oauth subcommands remain Low risk", async () => {
|
|
202
|
+
const lowRiskOauthCommands = [
|
|
203
|
+
"assistant oauth apps",
|
|
204
|
+
"assistant oauth apps list",
|
|
205
|
+
"assistant oauth providers",
|
|
206
|
+
"assistant oauth status",
|
|
207
|
+
];
|
|
208
|
+
|
|
209
|
+
for (const command of lowRiskOauthCommands) {
|
|
210
|
+
const risk = await classifyRisk("bash", { command });
|
|
211
|
+
expectLowRisk(command, risk);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("non-sensitive credentials subcommands remain Low risk", async () => {
|
|
216
|
+
const lowRiskCredCommands = [
|
|
217
|
+
"assistant credentials",
|
|
218
|
+
"assistant credentials list",
|
|
219
|
+
];
|
|
220
|
+
|
|
221
|
+
for (const command of lowRiskCredCommands) {
|
|
222
|
+
const risk = await classifyRisk("bash", { command });
|
|
223
|
+
expectLowRisk(command, risk);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
describe("CLI command risk guard: wrapper program propagation", () => {
|
|
229
|
+
test("env assistant oauth token is High risk", async () => {
|
|
230
|
+
const risk = await classifyRisk("bash", {
|
|
231
|
+
command: "env assistant oauth token",
|
|
232
|
+
});
|
|
233
|
+
expect(risk).toBe(RiskLevel.High);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("nice assistant credentials reveal is High risk", async () => {
|
|
237
|
+
const risk = await classifyRisk("bash", {
|
|
238
|
+
command: "nice assistant credentials reveal",
|
|
239
|
+
});
|
|
240
|
+
expect(risk).toBe(RiskLevel.High);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test("timeout 30 assistant oauth request is Medium risk", async () => {
|
|
244
|
+
const risk = await classifyRisk("bash", {
|
|
245
|
+
command: "timeout 30 assistant oauth request",
|
|
246
|
+
});
|
|
247
|
+
expect(risk).toBe(RiskLevel.Medium);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("timeout 30 assistant oauth token is High risk", async () => {
|
|
251
|
+
const risk = await classifyRisk("bash", {
|
|
252
|
+
command: "timeout 30 assistant oauth token",
|
|
253
|
+
});
|
|
254
|
+
expect(risk).toBe(RiskLevel.High);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("timeout 30 git push is Medium risk", async () => {
|
|
258
|
+
const risk = await classifyRisk("bash", {
|
|
259
|
+
command: "timeout 30 git push",
|
|
260
|
+
});
|
|
261
|
+
expect(risk).toBe(RiskLevel.Medium);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("timeout 30 git status is Low risk", async () => {
|
|
265
|
+
const risk = await classifyRisk("bash", {
|
|
266
|
+
command: "timeout 30 git status",
|
|
267
|
+
});
|
|
268
|
+
expectLowRisk("timeout 30 git status", risk);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("env assistant config is Low risk", async () => {
|
|
272
|
+
const risk = await classifyRisk("bash", {
|
|
273
|
+
command: "env assistant config",
|
|
274
|
+
});
|
|
275
|
+
expectLowRisk("env assistant config", risk);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test("env git push is Medium risk (not Low)", async () => {
|
|
279
|
+
const risk = await classifyRisk("bash", { command: "env git push" });
|
|
280
|
+
expect(risk).toBe(RiskLevel.Medium);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("env git status is Low risk", async () => {
|
|
284
|
+
const risk = await classifyRisk("bash", { command: "env git status" });
|
|
285
|
+
expectLowRisk("env git status", risk);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
@@ -135,6 +135,7 @@ describe("AssistantConfigSchema", () => {
|
|
|
135
135
|
expect(result.secretDetection).toEqual({
|
|
136
136
|
enabled: true,
|
|
137
137
|
action: "redact",
|
|
138
|
+
blockIngress: true,
|
|
138
139
|
entropyThreshold: 4.0,
|
|
139
140
|
allowOneTimeSend: false,
|
|
140
141
|
});
|
|
@@ -157,6 +158,7 @@ describe("AssistantConfigSchema", () => {
|
|
|
157
158
|
secretDetection: {
|
|
158
159
|
enabled: false,
|
|
159
160
|
action: "block" as const,
|
|
161
|
+
blockIngress: false,
|
|
160
162
|
entropyThreshold: 5.5,
|
|
161
163
|
},
|
|
162
164
|
auditLog: { retentionDays: 30 },
|
|
@@ -436,7 +438,10 @@ describe("AssistantConfigSchema", () => {
|
|
|
436
438
|
|
|
437
439
|
test("defaults permissions.mode to workspace", () => {
|
|
438
440
|
const result = AssistantConfigSchema.parse({});
|
|
439
|
-
expect(result.permissions).toEqual({
|
|
441
|
+
expect(result.permissions).toEqual({
|
|
442
|
+
mode: "workspace",
|
|
443
|
+
dangerouslySkipPermissions: false,
|
|
444
|
+
});
|
|
440
445
|
});
|
|
441
446
|
|
|
442
447
|
test("accepts explicit permissions.mode strict", () => {
|
|
@@ -1141,7 +1146,10 @@ describe("loadConfig with schema validation", () => {
|
|
|
1141
1146
|
test("defaults permissions.mode to workspace when not specified", () => {
|
|
1142
1147
|
writeConfig({});
|
|
1143
1148
|
const config = loadConfig();
|
|
1144
|
-
expect(config.permissions).toEqual({
|
|
1149
|
+
expect(config.permissions).toEqual({
|
|
1150
|
+
mode: "workspace",
|
|
1151
|
+
dangerouslySkipPermissions: false,
|
|
1152
|
+
});
|
|
1145
1153
|
});
|
|
1146
1154
|
|
|
1147
1155
|
test("loads explicit permissions.mode strict", () => {
|