@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
|
@@ -472,8 +472,7 @@ describe("seedCatalogSkillMemories", () => {
|
|
|
472
472
|
mockResolveCatalog = async () => skills;
|
|
473
473
|
|
|
474
474
|
// Disable the feature flag for the flagged skill
|
|
475
|
-
mockIsFeatureFlagEnabled = (key: string) =>
|
|
476
|
-
key !== "feature_flags.my_gated_feature.enabled";
|
|
475
|
+
mockIsFeatureFlagEnabled = (key: string) => key !== "my_gated_feature";
|
|
477
476
|
|
|
478
477
|
await seedCatalogSkillMemories();
|
|
479
478
|
|
|
@@ -519,8 +518,7 @@ describe("seedCatalogSkillMemories", () => {
|
|
|
519
518
|
expect(beforeItems.every((i) => i.status === "active")).toBe(true);
|
|
520
519
|
|
|
521
520
|
// Now disable the flag — the flagged skill should be pruned
|
|
522
|
-
mockIsFeatureFlagEnabled = (key: string) =>
|
|
523
|
-
key !== "feature_flags.my_gated_feature.enabled";
|
|
521
|
+
mockIsFeatureFlagEnabled = (key: string) => key !== "my_gated_feature";
|
|
524
522
|
await seedCatalogSkillMemories();
|
|
525
523
|
|
|
526
524
|
const afterItems = db
|
|
@@ -22,7 +22,7 @@ let mockSkillRefCount: Map<string, number> = new Map();
|
|
|
22
22
|
|
|
23
23
|
let currentConfig: Record<string, unknown> = {};
|
|
24
24
|
const DECLARED_SKILL_ID = "contacts";
|
|
25
|
-
const DECLARED_FLAG_KEY = "
|
|
25
|
+
const DECLARED_FLAG_KEY = "contacts";
|
|
26
26
|
|
|
27
27
|
// ---------------------------------------------------------------------------
|
|
28
28
|
// Mocks
|
|
@@ -40,9 +40,7 @@ mock.module("../config/loader.js", () => ({
|
|
|
40
40
|
|
|
41
41
|
mock.module("../config/skill-state.js", () => ({
|
|
42
42
|
skillFlagKey: (skill: { featureFlag?: string }) =>
|
|
43
|
-
skill.featureFlag
|
|
44
|
-
? `feature_flags.${skill.featureFlag}.enabled`
|
|
45
|
-
: undefined,
|
|
43
|
+
skill.featureFlag || undefined,
|
|
46
44
|
}));
|
|
47
45
|
|
|
48
46
|
// Mock assistant-feature-flags to avoid loading the real module (which
|
|
@@ -66,9 +66,7 @@ mock.module("../config/skills.js", () => ({
|
|
|
66
66
|
// only needs skillFlagKey and doesn't exercise resolveSkillStates.
|
|
67
67
|
mock.module("../config/skill-state.js", () => ({
|
|
68
68
|
skillFlagKey: (skill: { featureFlag?: string }) =>
|
|
69
|
-
skill.featureFlag
|
|
70
|
-
? `feature_flags.${skill.featureFlag}.enabled`
|
|
71
|
-
: undefined,
|
|
69
|
+
skill.featureFlag || undefined,
|
|
72
70
|
resolveSkillStates: () => [],
|
|
73
71
|
}));
|
|
74
72
|
|
|
@@ -58,7 +58,7 @@ describe("assistant skills uninstall", () => {
|
|
|
58
58
|
|
|
59
59
|
// GIVEN a skill is installed locally
|
|
60
60
|
installFakeSkill("weather");
|
|
61
|
-
writeSkillsIndex("- weather\n- google-oauth-
|
|
61
|
+
writeSkillsIndex("- weather\n- google-oauth-app-setup\n");
|
|
62
62
|
|
|
63
63
|
// WHEN we uninstall the skill
|
|
64
64
|
uninstallSkillLocally("weather");
|
|
@@ -71,7 +71,7 @@ describe("assistant skills uninstall", () => {
|
|
|
71
71
|
expect(index).not.toContain("weather");
|
|
72
72
|
|
|
73
73
|
// AND other skills should remain in the index
|
|
74
|
-
expect(index).toContain("google-oauth-
|
|
74
|
+
expect(index).toContain("google-oauth-app-setup");
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
test("errors when skill is not installed", () => {
|
|
@@ -603,7 +603,7 @@ describe("bundled browser skill", () => {
|
|
|
603
603
|
});
|
|
604
604
|
});
|
|
605
605
|
|
|
606
|
-
describe("ingress-dependent setup skills declare public-ingress", () => {
|
|
606
|
+
describe("ingress-dependent setup skills declare public-ingress intentionally", () => {
|
|
607
607
|
const FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
|
|
608
608
|
const FIRST_PARTY_SKILLS_DIR = join(
|
|
609
609
|
import.meta.dir,
|
|
@@ -649,14 +649,28 @@ describe("ingress-dependent setup skills declare public-ingress", () => {
|
|
|
649
649
|
return undefined;
|
|
650
650
|
}
|
|
651
651
|
|
|
652
|
-
test("telegram-setup
|
|
652
|
+
test("telegram-setup does not hard-depend on public-ingress", () => {
|
|
653
653
|
const includes = readSkillIncludes(
|
|
654
654
|
FIRST_PARTY_SKILLS_DIR,
|
|
655
655
|
"telegram-setup",
|
|
656
656
|
);
|
|
657
|
+
expect(includes ?? []).not.toContain("public-ingress");
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
test("twilio-setup includes public-ingress", () => {
|
|
661
|
+
const includes = readSkillIncludes(FIRST_PARTY_SKILLS_DIR, "twilio-setup");
|
|
657
662
|
expect(includes).toBeDefined();
|
|
658
663
|
expect(includes).toContain("public-ingress");
|
|
659
664
|
});
|
|
665
|
+
|
|
666
|
+
test("public-ingress frontmatter advertises managed-mode avoidance", () => {
|
|
667
|
+
const content = readFileSync(
|
|
668
|
+
join(FIRST_PARTY_SKILLS_DIR, "public-ingress", "SKILL.md"),
|
|
669
|
+
"utf-8",
|
|
670
|
+
);
|
|
671
|
+
expect(content).toContain("avoid-when:");
|
|
672
|
+
expect(content.toLowerCase()).toContain("managed/containerized");
|
|
673
|
+
});
|
|
660
674
|
});
|
|
661
675
|
|
|
662
676
|
describe("bundled computer-use skill", () => {
|
|
@@ -252,7 +252,7 @@ describe("Slack channel config handler", () => {
|
|
|
252
252
|
});
|
|
253
253
|
|
|
254
254
|
test("GET reports per-field token presence independently of connection row", async () => {
|
|
255
|
-
// Only bot_token in
|
|
255
|
+
// Only bot_token in credential store, no app_token, but connection row exists
|
|
256
256
|
oauthConnectionStore["slack_channel"] = {
|
|
257
257
|
id: "conn-slack",
|
|
258
258
|
status: "active",
|
|
@@ -117,12 +117,12 @@ describe("Slack messaging token resolution", () => {
|
|
|
117
117
|
expect(await slackProvider.isConnected!()).toBe(true);
|
|
118
118
|
});
|
|
119
119
|
|
|
120
|
-
test("returns true when only
|
|
120
|
+
test("returns true when only slack has active OAuth connection (backwards compat)", async () => {
|
|
121
121
|
// No bot token
|
|
122
122
|
getSecureKeyAsyncMock.mockImplementation(async () => null);
|
|
123
123
|
// But OAuth provider is connected
|
|
124
124
|
isProviderConnectedMock.mockImplementation(async (service: string) =>
|
|
125
|
-
service === "
|
|
125
|
+
service === "slack" ? true : false,
|
|
126
126
|
);
|
|
127
127
|
|
|
128
128
|
expect(await slackProvider.isConnected!()).toBe(true);
|
|
@@ -139,7 +139,7 @@ describe("Slack messaging token resolution", () => {
|
|
|
139
139
|
// ── slackProvider.resolveConnection() ───────────────────────────────────
|
|
140
140
|
|
|
141
141
|
describe("slackProvider.resolveConnection()", () => {
|
|
142
|
-
test("returns
|
|
142
|
+
test("returns undefined when Socket Mode credentials exist (token cached internally)", async () => {
|
|
143
143
|
getSecureKeyAsyncMock.mockImplementation(async (key: string) =>
|
|
144
144
|
key === "credential/slack_channel/bot_token"
|
|
145
145
|
? "xoxb-socket-token"
|
|
@@ -147,20 +147,20 @@ describe("Slack messaging token resolution", () => {
|
|
|
147
147
|
);
|
|
148
148
|
|
|
149
149
|
const result = await slackProvider.resolveConnection!();
|
|
150
|
-
expect(result).
|
|
150
|
+
expect(result).toBeUndefined();
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
-
test("returns
|
|
153
|
+
test("returns undefined even without a slack_channel connection row (token-only resilience)", async () => {
|
|
154
154
|
getSecureKeyAsyncMock.mockImplementation(async (key: string) =>
|
|
155
155
|
key === "credential/slack_channel/bot_token" ? "xoxb-token-only" : null,
|
|
156
156
|
);
|
|
157
|
-
// No connection row — resolveConnection should still return
|
|
157
|
+
// No connection row — resolveConnection should still return undefined (token cached internally)
|
|
158
158
|
|
|
159
159
|
const result = await slackProvider.resolveConnection!();
|
|
160
|
-
expect(result).
|
|
160
|
+
expect(result).toBeUndefined();
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
-
test("returns OAuthConnection when only OAuth
|
|
163
|
+
test("returns OAuthConnection when only OAuth slack credentials exist (backwards compat)", async () => {
|
|
164
164
|
getSecureKeyAsyncMock.mockImplementation(async () => null);
|
|
165
165
|
const oauthConn = {
|
|
166
166
|
accessToken: "xoxp-oauth-token",
|
|
@@ -169,16 +169,15 @@ describe("Slack messaging token resolution", () => {
|
|
|
169
169
|
|
|
170
170
|
const result = await slackProvider.resolveConnection!();
|
|
171
171
|
expect(result).toBe(oauthConn);
|
|
172
|
-
expect(resolveOAuthConnectionMock).toHaveBeenCalledWith(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
);
|
|
172
|
+
expect(resolveOAuthConnectionMock).toHaveBeenCalledWith("slack", {
|
|
173
|
+
account: undefined,
|
|
174
|
+
});
|
|
176
175
|
});
|
|
177
176
|
|
|
178
177
|
test("throws when no credentials exist at all (no Socket Mode, no OAuth)", async () => {
|
|
179
178
|
getSecureKeyAsyncMock.mockImplementation(async () => null);
|
|
180
179
|
resolveOAuthConnectionMock.mockImplementation(async () => {
|
|
181
|
-
throw new Error("No OAuth connection found for
|
|
180
|
+
throw new Error("No OAuth connection found for slack");
|
|
182
181
|
});
|
|
183
182
|
|
|
184
183
|
await expect(slackProvider.resolveConnection!()).rejects.toThrow(
|
|
@@ -190,13 +189,13 @@ describe("Slack messaging token resolution", () => {
|
|
|
190
189
|
// ── getProviderConnection() integration ─────────────────────────────────
|
|
191
190
|
|
|
192
191
|
describe("getProviderConnection()", () => {
|
|
193
|
-
test("returns
|
|
192
|
+
test("returns undefined for Slack when Socket Mode credentials exist (token cached internally)", async () => {
|
|
194
193
|
getSecureKeyAsyncMock.mockImplementation(async (key: string) =>
|
|
195
194
|
key === "credential/slack_channel/bot_token" ? "xoxb-conn-token" : null,
|
|
196
195
|
);
|
|
197
196
|
|
|
198
197
|
const result = await getProviderConnection(slackProvider);
|
|
199
|
-
expect(result).
|
|
198
|
+
expect(result).toBeUndefined();
|
|
200
199
|
});
|
|
201
200
|
|
|
202
201
|
test("returns OAuthConnection for Slack when only OAuth credentials exist (backwards compat)", async () => {
|
|
@@ -221,9 +220,9 @@ describe("Slack messaging token resolution", () => {
|
|
|
221
220
|
);
|
|
222
221
|
});
|
|
223
222
|
|
|
224
|
-
test(
|
|
223
|
+
test("Telegram returns undefined (no resolveConnection, uses isConnected path — regression check)", async () => {
|
|
225
224
|
// Telegram has isConnected but no resolveConnection.
|
|
226
|
-
// When isConnected returns true, getProviderConnection returns
|
|
225
|
+
// When isConnected returns true, getProviderConnection returns undefined
|
|
227
226
|
getSecureKeyAsyncMock.mockImplementation(async (key: string) => {
|
|
228
227
|
if (key === "credential/telegram/bot_token") return "bot-token";
|
|
229
228
|
if (key === "credential/telegram/webhook_secret") return "secret";
|
|
@@ -234,7 +233,7 @@ describe("Slack messaging token resolution", () => {
|
|
|
234
233
|
);
|
|
235
234
|
|
|
236
235
|
const result = await getProviderConnection(telegramBotMessagingProvider);
|
|
237
|
-
expect(result).
|
|
236
|
+
expect(result).toBeUndefined();
|
|
238
237
|
});
|
|
239
238
|
|
|
240
239
|
test("Gmail still calls resolveOAuthConnection (no resolveConnection, no isConnected — regression check)", async () => {
|
|
@@ -247,10 +246,9 @@ describe("Slack messaging token resolution", () => {
|
|
|
247
246
|
|
|
248
247
|
const result = await getProviderConnection(gmailMessagingProvider);
|
|
249
248
|
expect(result).toBe(oauthConn);
|
|
250
|
-
expect(resolveOAuthConnectionMock).toHaveBeenCalledWith(
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
);
|
|
249
|
+
expect(resolveOAuthConnectionMock).toHaveBeenCalledWith("google", {
|
|
250
|
+
account: undefined,
|
|
251
|
+
});
|
|
254
252
|
});
|
|
255
253
|
});
|
|
256
254
|
|
|
@@ -264,7 +262,7 @@ describe("Slack messaging token resolution", () => {
|
|
|
264
262
|
);
|
|
265
263
|
// Gmail connected via OAuth
|
|
266
264
|
isProviderConnectedMock.mockImplementation(async (service: string) =>
|
|
267
|
-
service === "
|
|
265
|
+
service === "google" ? true : false,
|
|
268
266
|
);
|
|
269
267
|
|
|
270
268
|
await expect(resolveProvider()).rejects.toThrow(
|
|
@@ -285,7 +283,7 @@ describe("Slack messaging token resolution", () => {
|
|
|
285
283
|
test("auto-selects Gmail when it is the only connected provider (no Slack credentials)", async () => {
|
|
286
284
|
getSecureKeyAsyncMock.mockImplementation(async () => null);
|
|
287
285
|
isProviderConnectedMock.mockImplementation(async (service: string) =>
|
|
288
|
-
service === "
|
|
286
|
+
service === "google" ? true : false,
|
|
289
287
|
);
|
|
290
288
|
|
|
291
289
|
const provider = await resolveProvider();
|
|
@@ -111,7 +111,7 @@ describe("handleListSlackChannels", () => {
|
|
|
111
111
|
});
|
|
112
112
|
|
|
113
113
|
test("returns channels sorted by type then name", async () => {
|
|
114
|
-
connectionByProvider["
|
|
114
|
+
connectionByProvider["slack"] = { id: "conn-slack-1" };
|
|
115
115
|
secureKeyValues.set(
|
|
116
116
|
"oauth_connection/conn-slack-1/access_token",
|
|
117
117
|
"xoxb-test",
|
|
@@ -192,7 +192,7 @@ describe("handleShareToSlackChannel", () => {
|
|
|
192
192
|
});
|
|
193
193
|
|
|
194
194
|
test("returns 400 for malformed JSON", async () => {
|
|
195
|
-
connectionByProvider["
|
|
195
|
+
connectionByProvider["slack"] = { id: "conn-slack-1" };
|
|
196
196
|
secureKeyValues.set(
|
|
197
197
|
"oauth_connection/conn-slack-1/access_token",
|
|
198
198
|
"xoxb-test",
|
|
@@ -207,7 +207,7 @@ describe("handleShareToSlackChannel", () => {
|
|
|
207
207
|
});
|
|
208
208
|
|
|
209
209
|
test("returns 400 when missing required fields", async () => {
|
|
210
|
-
connectionByProvider["
|
|
210
|
+
connectionByProvider["slack"] = { id: "conn-slack-1" };
|
|
211
211
|
secureKeyValues.set(
|
|
212
212
|
"oauth_connection/conn-slack-1/access_token",
|
|
213
213
|
"xoxb-test",
|
|
@@ -220,7 +220,7 @@ describe("handleShareToSlackChannel", () => {
|
|
|
220
220
|
});
|
|
221
221
|
|
|
222
222
|
test("returns 404 when app not found", async () => {
|
|
223
|
-
connectionByProvider["
|
|
223
|
+
connectionByProvider["slack"] = { id: "conn-slack-1" };
|
|
224
224
|
secureKeyValues.set(
|
|
225
225
|
"oauth_connection/conn-slack-1/access_token",
|
|
226
226
|
"xoxb-test",
|
|
@@ -232,7 +232,7 @@ describe("handleShareToSlackChannel", () => {
|
|
|
232
232
|
});
|
|
233
233
|
|
|
234
234
|
test("posts message and returns success", async () => {
|
|
235
|
-
connectionByProvider["
|
|
235
|
+
connectionByProvider["slack"] = { id: "conn-slack-1" };
|
|
236
236
|
secureKeyValues.set(
|
|
237
237
|
"oauth_connection/conn-slack-1/access_token",
|
|
238
238
|
"xoxb-test",
|
|
@@ -46,75 +46,11 @@ describe("slack adapter isPrivate mapping", () => {
|
|
|
46
46
|
});
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
-
describe("slack skill TOOLS.json", () => {
|
|
49
|
+
describe("slack skill has no TOOLS.json (uses Web API via CLI)", () => {
|
|
50
50
|
const toolsPath = join(BUNDLED_SKILLS_DIR, "slack", "TOOLS.json");
|
|
51
|
-
const toolsJson = JSON.parse(readFileSync(toolsPath, "utf-8"));
|
|
52
51
|
|
|
53
|
-
test("
|
|
54
|
-
expect(
|
|
55
|
-
expect(Array.isArray(toolsJson.tools)).toBe(true);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test("has expected tools", () => {
|
|
59
|
-
const names = toolsJson.tools.map((t: { name: string }) => t.name);
|
|
60
|
-
expect(names).toContain("slack_scan_digest");
|
|
61
|
-
expect(names).toContain("slack_channel_details");
|
|
62
|
-
expect(names).toContain("slack_configure_channels");
|
|
63
|
-
expect(names).toContain("slack_add_reaction");
|
|
64
|
-
expect(names).toContain("slack_edit_message");
|
|
65
|
-
expect(names).toContain("slack_delete_message");
|
|
66
|
-
expect(names).toContain("slack_leave_channel");
|
|
67
|
-
expect(names).toContain("slack_channel_permissions");
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test("has 8 tools total", () => {
|
|
71
|
-
expect(toolsJson.tools.length).toBe(8);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
test("all tools have required fields", () => {
|
|
75
|
-
for (const tool of toolsJson.tools) {
|
|
76
|
-
expect(tool.name).toBeDefined();
|
|
77
|
-
expect(tool.description).toBeDefined();
|
|
78
|
-
expect(tool.category).toBeDefined();
|
|
79
|
-
expect(tool.risk).toBeDefined();
|
|
80
|
-
expect(tool.input_schema).toBeDefined();
|
|
81
|
-
expect(tool.executor).toBeDefined();
|
|
82
|
-
expect(tool.execution_target).toBeDefined();
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
test("all executor files exist", () => {
|
|
87
|
-
const slackSkillDir = join(BUNDLED_SKILLS_DIR, "slack");
|
|
88
|
-
for (const tool of toolsJson.tools) {
|
|
89
|
-
const executorPath = join(slackSkillDir, tool.executor);
|
|
90
|
-
expect(() => readFileSync(executorPath)).not.toThrow();
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
describe("messaging skill no longer has Slack tools", () => {
|
|
96
|
-
const messagingToolsPath = join(
|
|
97
|
-
BUNDLED_SKILLS_DIR,
|
|
98
|
-
"messaging",
|
|
99
|
-
"TOOLS.json",
|
|
100
|
-
);
|
|
101
|
-
const messagingToolsJson = JSON.parse(
|
|
102
|
-
readFileSync(messagingToolsPath, "utf-8"),
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
test("slack_add_reaction not in messaging TOOLS.json", () => {
|
|
106
|
-
const names = messagingToolsJson.tools.map((t: { name: string }) => t.name);
|
|
107
|
-
expect(names).not.toContain("slack_add_reaction");
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
test("slack_delete_message not in messaging TOOLS.json", () => {
|
|
111
|
-
const names = messagingToolsJson.tools.map((t: { name: string }) => t.name);
|
|
112
|
-
expect(names).not.toContain("slack_delete_message");
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test("slack_leave_channel not in messaging TOOLS.json", () => {
|
|
116
|
-
const names = messagingToolsJson.tools.map((t: { name: string }) => t.name);
|
|
117
|
-
expect(names).not.toContain("slack_leave_channel");
|
|
52
|
+
test("TOOLS.json does not exist", () => {
|
|
53
|
+
expect(() => readFileSync(toolsPath)).toThrow();
|
|
118
54
|
});
|
|
119
55
|
});
|
|
120
56
|
|
|
@@ -129,7 +65,7 @@ describe("slack skill SKILL.md", () => {
|
|
|
129
65
|
});
|
|
130
66
|
|
|
131
67
|
test("mentions privacy rules", () => {
|
|
132
|
-
expect(skillMd).toContain("
|
|
133
|
-
expect(skillMd).toContain("
|
|
68
|
+
expect(skillMd).toContain("is_private");
|
|
69
|
+
expect(skillMd).toContain("must NEVER be shared");
|
|
134
70
|
});
|
|
135
71
|
});
|
|
@@ -24,6 +24,7 @@ mock.module("../util/platform.js", () => ({
|
|
|
24
24
|
getWorkspaceConfigPath: () => join(TEST_DIR, "config.json"),
|
|
25
25
|
getWorkspaceSkillsDir: () => join(TEST_DIR, "skills"),
|
|
26
26
|
getWorkspaceHooksDir: () => join(TEST_DIR, "hooks"),
|
|
27
|
+
getConversationsDir: () => join(TEST_DIR, "conversations"),
|
|
27
28
|
getWorkspacePromptPath: (file: string) => join(TEST_DIR, file),
|
|
28
29
|
ensureDataDir: () => {},
|
|
29
30
|
getPidPath: () => join(TEST_DIR, "vellum.pid"),
|
|
@@ -560,4 +561,42 @@ describe("ensurePromptFiles", () => {
|
|
|
560
561
|
const bootstrapPath = join(TEST_DIR, "BOOTSTRAP.md");
|
|
561
562
|
expect(existsSync(bootstrapPath)).toBe(false);
|
|
562
563
|
});
|
|
564
|
+
|
|
565
|
+
test("auto-deletes stale BOOTSTRAP.md when prior conversations exist", () => {
|
|
566
|
+
// Simulate a non-first-run workspace: core files + BOOTSTRAP.md still present
|
|
567
|
+
writeFileSync(join(TEST_DIR, "IDENTITY.md"), "My identity");
|
|
568
|
+
writeFileSync(join(TEST_DIR, "SOUL.md"), "My soul");
|
|
569
|
+
writeFileSync(join(TEST_DIR, "USER.md"), "My user");
|
|
570
|
+
writeFileSync(join(TEST_DIR, "BOOTSTRAP.md"), "# Stale bootstrap");
|
|
571
|
+
|
|
572
|
+
// Create a conversations directory with at least one entry
|
|
573
|
+
const convDir = join(TEST_DIR, "conversations");
|
|
574
|
+
mkdirSync(convDir, { recursive: true });
|
|
575
|
+
writeFileSync(join(convDir, "conv-001.json"), "{}");
|
|
576
|
+
|
|
577
|
+
ensurePromptFiles();
|
|
578
|
+
|
|
579
|
+
expect(existsSync(join(TEST_DIR, "BOOTSTRAP.md"))).toBe(false);
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
test("keeps BOOTSTRAP.md on first run even if conversations dir exists", () => {
|
|
583
|
+
// First run: no core files exist, BOOTSTRAP.md should be created and kept
|
|
584
|
+
const convDir = join(TEST_DIR, "conversations");
|
|
585
|
+
mkdirSync(convDir, { recursive: true });
|
|
586
|
+
writeFileSync(join(convDir, "conv-001.json"), "{}");
|
|
587
|
+
|
|
588
|
+
ensurePromptFiles();
|
|
589
|
+
|
|
590
|
+
expect(existsSync(join(TEST_DIR, "BOOTSTRAP.md"))).toBe(true);
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
test("keeps BOOTSTRAP.md when no conversations exist yet", () => {
|
|
594
|
+
// Non-first-run but no conversations — user hasn't chatted yet
|
|
595
|
+
writeFileSync(join(TEST_DIR, "IDENTITY.md"), "My identity");
|
|
596
|
+
writeFileSync(join(TEST_DIR, "BOOTSTRAP.md"), "# Bootstrap");
|
|
597
|
+
|
|
598
|
+
ensurePromptFiles();
|
|
599
|
+
|
|
600
|
+
expect(existsSync(join(TEST_DIR, "BOOTSTRAP.md"))).toBe(true);
|
|
601
|
+
});
|
|
563
602
|
});
|
|
@@ -226,7 +226,7 @@ describe("vellum-self-knowledge inline command expansion", () => {
|
|
|
226
226
|
|
|
227
227
|
// Enable the feature flag via protected directory override
|
|
228
228
|
_setOverridesForTesting({
|
|
229
|
-
"
|
|
229
|
+
"inline-skill-commands": true,
|
|
230
230
|
});
|
|
231
231
|
testConfig.skills = { load: { extraDirs: [] } };
|
|
232
232
|
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Mocks — must precede migration import
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
// In-memory credential store. Using `let` so tests can reset between runs.
|
|
8
|
+
let store = new Map<string, string>();
|
|
9
|
+
let storeUnreachable = false;
|
|
10
|
+
|
|
11
|
+
mock.module("../security/secure-keys.js", () => ({
|
|
12
|
+
listSecureKeysAsync: async () => ({
|
|
13
|
+
accounts: [...store.keys()],
|
|
14
|
+
unreachable: storeUnreachable,
|
|
15
|
+
}),
|
|
16
|
+
getSecureKeyAsync: async (key: string) => store.get(key),
|
|
17
|
+
setSecureKeyAsync: async (key: string, value: string) => {
|
|
18
|
+
store.set(key, value);
|
|
19
|
+
return true;
|
|
20
|
+
},
|
|
21
|
+
deleteSecureKeyAsync: async (key: string) => {
|
|
22
|
+
store.delete(key);
|
|
23
|
+
},
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
import { rekeyCompoundCredentialKeysMigration } from "../workspace/migrations/018-rekey-compound-credential-keys.js";
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Helpers
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
function resetStore(entries: Record<string, string> = {}): void {
|
|
33
|
+
store = new Map(Object.entries(entries));
|
|
34
|
+
storeUnreachable = false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function storeEntries(): Record<string, string> {
|
|
38
|
+
return Object.fromEntries(store);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Tests
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
describe("018-rekey-compound-credential-keys migration", () => {
|
|
46
|
+
test("has correct migration id", () => {
|
|
47
|
+
expect(rekeyCompoundCredentialKeysMigration.id).toBe(
|
|
48
|
+
"018-rekey-compound-credential-keys",
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("run() re-keys compound credential from indexOf to lastIndexOf format", async () => {
|
|
53
|
+
resetStore({
|
|
54
|
+
"credential/integration/google:access_token": "my-token",
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await rekeyCompoundCredentialKeysMigration.run("/fake");
|
|
58
|
+
|
|
59
|
+
expect(storeEntries()).toEqual({
|
|
60
|
+
"credential/integration:google/access_token": "my-token",
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("run() leaves simple single-colon keys unchanged", async () => {
|
|
65
|
+
resetStore({
|
|
66
|
+
"credential/github/token": "gh-token",
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
await rekeyCompoundCredentialKeysMigration.run("/fake");
|
|
70
|
+
|
|
71
|
+
expect(storeEntries()).toEqual({
|
|
72
|
+
"credential/github/token": "gh-token",
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("run() ignores non-credential keys", async () => {
|
|
77
|
+
resetStore({
|
|
78
|
+
"other/integration/google:access_token": "my-token",
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
await rekeyCompoundCredentialKeysMigration.run("/fake");
|
|
82
|
+
|
|
83
|
+
expect(storeEntries()).toEqual({
|
|
84
|
+
"other/integration/google:access_token": "my-token",
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("run() is idempotent — second run is a no-op", async () => {
|
|
89
|
+
resetStore({
|
|
90
|
+
"credential/integration/google:access_token": "my-token",
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
await rekeyCompoundCredentialKeysMigration.run("/fake");
|
|
94
|
+
const afterFirst = storeEntries();
|
|
95
|
+
|
|
96
|
+
await rekeyCompoundCredentialKeysMigration.run("/fake");
|
|
97
|
+
|
|
98
|
+
expect(storeEntries()).toEqual(afterFirst);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("run() deletes orphaned old key when new key already exists", async () => {
|
|
102
|
+
resetStore({
|
|
103
|
+
"credential/integration/google:access_token": "old-token",
|
|
104
|
+
"credential/integration:google/access_token": "new-token",
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
await rekeyCompoundCredentialKeysMigration.run("/fake");
|
|
108
|
+
|
|
109
|
+
// Old key removed; new key (already present) wins
|
|
110
|
+
expect(storeEntries()).toEqual({
|
|
111
|
+
"credential/integration:google/access_token": "new-token",
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("run() throws when credential store is unreachable", async () => {
|
|
116
|
+
resetStore();
|
|
117
|
+
storeUnreachable = true;
|
|
118
|
+
|
|
119
|
+
await expect(
|
|
120
|
+
rekeyCompoundCredentialKeysMigration.run("/fake"),
|
|
121
|
+
).rejects.toThrow("Credential store unreachable");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("down() reverses run() — re-keys from lastIndexOf back to indexOf format", async () => {
|
|
125
|
+
resetStore({
|
|
126
|
+
"credential/integration:google/access_token": "my-token",
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
await rekeyCompoundCredentialKeysMigration.down("/fake");
|
|
130
|
+
|
|
131
|
+
expect(storeEntries()).toEqual({
|
|
132
|
+
"credential/integration/google:access_token": "my-token",
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("down() leaves simple keys unchanged", async () => {
|
|
137
|
+
resetStore({
|
|
138
|
+
"credential/github/token": "gh-token",
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
await rekeyCompoundCredentialKeysMigration.down("/fake");
|
|
142
|
+
|
|
143
|
+
expect(storeEntries()).toEqual({
|
|
144
|
+
"credential/github/token": "gh-token",
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("down() is idempotent — second down() is a no-op", async () => {
|
|
149
|
+
resetStore({
|
|
150
|
+
"credential/integration:google/access_token": "my-token",
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
await rekeyCompoundCredentialKeysMigration.down("/fake");
|
|
154
|
+
const afterFirst = storeEntries();
|
|
155
|
+
|
|
156
|
+
await rekeyCompoundCredentialKeysMigration.down("/fake");
|
|
157
|
+
|
|
158
|
+
expect(storeEntries()).toEqual(afterFirst);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("run() then down() restores original state", async () => {
|
|
162
|
+
const original = {
|
|
163
|
+
"credential/integration/google:access_token": "my-token",
|
|
164
|
+
};
|
|
165
|
+
resetStore(original);
|
|
166
|
+
|
|
167
|
+
await rekeyCompoundCredentialKeysMigration.run("/fake");
|
|
168
|
+
await rekeyCompoundCredentialKeysMigration.down("/fake");
|
|
169
|
+
|
|
170
|
+
expect(storeEntries()).toEqual(original);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("down() throws when credential store is unreachable", async () => {
|
|
174
|
+
resetStore();
|
|
175
|
+
storeUnreachable = true;
|
|
176
|
+
|
|
177
|
+
await expect(
|
|
178
|
+
rekeyCompoundCredentialKeysMigration.down("/fake"),
|
|
179
|
+
).rejects.toThrow("Credential store unreachable");
|
|
180
|
+
});
|
|
181
|
+
});
|
|
@@ -284,20 +284,21 @@ describe("011-backfill-installation-id migration", () => {
|
|
|
284
284
|
expect(parsed.assistants[0].installationId).toBe("sqlite-id");
|
|
285
285
|
});
|
|
286
286
|
|
|
287
|
-
test("
|
|
287
|
+
test("ignores BASE_DATA_DIR and always reads lockfile from homedir", () => {
|
|
288
288
|
process.env.BASE_DATA_DIR = "/custom-base";
|
|
289
289
|
getMemoryCheckpointFn.mockReturnValue("sqlite-id");
|
|
290
290
|
|
|
291
|
-
|
|
291
|
+
// Lockfile under BASE_DATA_DIR should be ignored — the migration
|
|
292
|
+
// always reads from homedir() (per-user, not per-instance).
|
|
292
293
|
setupFs({
|
|
293
|
-
[
|
|
294
|
+
[LOCK_PATH]: makeLockfile([{ assistantId: "my-assistant" }]),
|
|
294
295
|
});
|
|
295
296
|
|
|
296
297
|
backfillInstallationIdMigration.run(WORKSPACE_DIR);
|
|
297
298
|
|
|
298
299
|
expect(writeFileSyncFn).toHaveBeenCalledTimes(1);
|
|
299
300
|
const [path] = writeFileSyncFn.mock.calls[0] as [string, string];
|
|
300
|
-
expect(path).toBe(
|
|
301
|
+
expect(path).toBe(LOCK_PATH);
|
|
301
302
|
});
|
|
302
303
|
|
|
303
304
|
test("preserves other assistants in lockfile when writing", () => {
|