@vellumai/assistant 0.4.46 → 0.4.49
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/ARCHITECTURE.md +7 -7
- package/README.md +2 -23
- package/docs/architecture/integrations.md +45 -41
- package/docs/architecture/keychain-broker.md +3 -3
- package/docs/architecture/security.md +5 -5
- package/docs/runbook-trusted-contacts.md +3 -8
- package/hook-templates/debug-prompt-logger/hook.json +1 -1
- package/hook-templates/debug-prompt-logger/run.sh +1 -3
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +0 -1
- package/src/__tests__/anthropic-provider.test.ts +156 -0
- package/src/__tests__/approval-cascade.test.ts +810 -0
- package/src/__tests__/approval-primitive.test.ts +0 -1
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-attachments.test.ts +12 -34
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
- package/src/__tests__/browser-fill-credential.test.ts +5 -2
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +2 -1
- package/src/__tests__/channel-guardian.test.ts +0 -2
- package/src/__tests__/channel-readiness-routes.test.ts +35 -25
- package/src/__tests__/channel-readiness-service.test.ts +10 -9
- package/src/__tests__/checker.test.ts +9 -29
- package/src/__tests__/cli.test.ts +23 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
- package/src/__tests__/computer-use-tools.test.ts +2 -19
- package/src/__tests__/config-watcher.test.ts +0 -1
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
- package/src/__tests__/context-image-dimensions.test.ts +332 -0
- package/src/__tests__/context-token-estimator.test.ts +196 -13
- package/src/__tests__/conversation-attention-store.test.ts +0 -1
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +144 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/credential-broker-browser-fill.test.ts +23 -22
- package/src/__tests__/credential-broker-server-use.test.ts +22 -21
- package/src/__tests__/credential-broker.test.ts +2 -1
- package/src/__tests__/credential-metadata-store.test.ts +239 -26
- package/src/__tests__/credential-resolve.test.ts +5 -4
- package/src/__tests__/credential-security-e2e.test.ts +8 -8
- package/src/__tests__/credential-security-invariants.test.ts +111 -7
- package/src/__tests__/credential-vault-unit.test.ts +287 -54
- package/src/__tests__/credential-vault.test.ts +406 -12
- package/src/__tests__/credentials-cli.test.ts +82 -6
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- package/src/__tests__/ephemeral-permissions.test.ts +3 -3
- package/src/__tests__/gateway-only-enforcement.test.ts +4 -2
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/gemini-image-service.test.ts +75 -45
- package/src/__tests__/gemini-provider.test.ts +9 -6
- package/src/__tests__/guardian-action-conversation-turn.test.ts +1 -33
- package/src/__tests__/guardian-action-copy-generator.test.ts +0 -20
- package/src/__tests__/guardian-action-followup-executor.test.ts +1 -28
- package/src/__tests__/guardian-action-followup-store.test.ts +1 -1
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
- package/src/__tests__/guardian-grant-minting.test.ts +35 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
- package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
- package/src/__tests__/heartbeat-service.test.ts +0 -1
- package/src/__tests__/host-cu-proxy.test.ts +629 -0
- package/src/__tests__/host-shell-tool.test.ts +27 -15
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/ingress-url-consistency.test.ts +14 -21
- package/src/__tests__/integration-status.test.ts +38 -25
- package/src/__tests__/intent-routing.test.ts +0 -1
- package/src/__tests__/invite-routes-http.test.ts +10 -9
- package/src/__tests__/keychain-broker-client.test.ts +11 -43
- package/src/__tests__/managed-proxy-context.test.ts +5 -3
- package/src/__tests__/media-generate-image.test.ts +63 -2
- package/src/__tests__/media-reuse-story.e2e.test.ts +7 -3
- package/src/__tests__/messaging-send-tool.test.ts +4 -6
- package/src/__tests__/notification-routing-intent.test.ts +0 -1
- package/src/__tests__/oauth-cli.test.ts +373 -14
- package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
- package/src/__tests__/oauth-scope-policy.test.ts +4 -6
- package/src/__tests__/oauth-store.test.ts +756 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
- package/src/__tests__/provider-error-scenarios.test.ts +0 -1
- package/src/__tests__/provider-fail-open-selection.test.ts +3 -1
- package/src/__tests__/provider-managed-proxy-integration.test.ts +70 -6
- package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
- package/src/__tests__/public-ingress-urls.test.ts +15 -21
- package/src/__tests__/recording-handler.test.ts +3 -4
- package/src/__tests__/registry.test.ts +2 -2
- package/src/__tests__/runtime-events-sse.test.ts +55 -7
- package/src/__tests__/schedule-store.test.ts +0 -1
- package/src/__tests__/scheduler-recurrence.test.ts +0 -1
- package/src/__tests__/schema-transforms.test.ts +226 -0
- package/src/__tests__/scoped-approval-grants.test.ts +0 -1
- package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
- package/src/__tests__/script-proxy-injection-runtime.test.ts +23 -13
- package/src/__tests__/script-proxy-policy-runtime.test.ts +1 -1
- package/src/__tests__/script-proxy-session-manager.test.ts +1 -1
- package/src/__tests__/secret-ingress-handler.test.ts +0 -1
- package/src/__tests__/secret-onetime-send.test.ts +5 -3
- package/src/__tests__/send-endpoint-busy.test.ts +21 -6
- package/src/__tests__/sequence-store.test.ts +0 -1
- package/src/__tests__/session-init.benchmark.test.ts +4 -5
- package/src/__tests__/session-messaging-secret-redirect.test.ts +5 -4
- package/src/__tests__/skill-include-graph.test.ts +66 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
- package/src/__tests__/skill-load-tool.test.ts +149 -1
- package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
- package/src/__tests__/skills-uninstall.test.ts +3 -3
- package/src/__tests__/skills.test.ts +3 -12
- package/src/__tests__/slack-channel-config.test.ts +76 -11
- package/src/__tests__/slack-share-routes.test.ts +17 -14
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/telegram-bot-username-resolution.test.ts +3 -0
- package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
- package/src/__tests__/terminal-tools.test.ts +4 -3
- package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
- package/src/__tests__/tool-approval-handler.test.ts +0 -1
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
- package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
- package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
- package/src/__tests__/trust-store.test.ts +1 -22
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
- package/src/__tests__/twilio-config.test.ts +2 -1
- package/src/__tests__/twilio-provider.test.ts +4 -2
- package/src/__tests__/twilio-routes.test.ts +5 -20
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
- package/src/agent/ax-tree-compaction.test.ts +235 -0
- package/src/agent/loop.ts +76 -130
- package/src/calls/call-domain.ts +8 -10
- package/src/calls/relay-server.ts +9 -13
- package/src/calls/twilio-config.ts +4 -8
- package/src/calls/twilio-provider.ts +2 -1
- package/src/calls/twilio-rest.ts +2 -1
- package/src/calls/twilio-routes.ts +1 -2
- package/src/calls/voice-ingress-preflight.ts +1 -1
- package/src/cli/commands/browser-relay.ts +46 -15
- package/src/cli/commands/completions.ts +0 -3
- package/src/cli/commands/credentials.ts +110 -23
- package/src/cli/commands/oauth/apps.ts +255 -0
- package/src/cli/commands/oauth/connections.ts +299 -0
- package/src/cli/commands/oauth/index.ts +52 -0
- package/src/cli/commands/oauth/providers.ts +242 -0
- package/src/cli/commands/skills.ts +4 -338
- package/src/cli/program.ts +1 -5
- package/src/cli/reference.ts +1 -3
- package/src/cli.ts +3 -2
- package/src/config/assistant-feature-flags.ts +0 -3
- package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
- package/src/config/bundled-skills/claude-code/TOOLS.json +0 -4
- package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
- package/src/config/bundled-skills/computer-use/TOOLS.json +22 -4
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +29 -32
- package/src/config/bundled-skills/gmail/SKILL.md +4 -4
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +54 -61
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +25 -28
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +14 -17
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +39 -44
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +61 -58
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +50 -49
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +11 -13
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +148 -146
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +4 -7
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +175 -173
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +4 -7
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +71 -76
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +32 -38
- package/src/config/bundled-skills/google-calendar/SKILL.md +2 -2
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +90 -44
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +9 -10
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +5 -6
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +4 -5
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +14 -15
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +37 -37
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +4 -9
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +24 -3
- package/src/config/bundled-skills/messaging/SKILL.md +6 -6
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +62 -63
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +15 -16
- package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +4 -5
- package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +6 -7
- package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +4 -5
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +14 -15
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +4 -5
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +128 -128
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +33 -34
- package/src/config/bundled-skills/messaging/tools/shared.ts +12 -15
- package/src/config/bundled-skills/settings/SKILL.md +1 -1
- package/src/config/bundled-skills/settings/TOOLS.json +2 -8
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
- package/src/config/bundled-skills/slack/tools/shared.ts +4 -10
- package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +4 -5
- package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +15 -16
- package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +4 -5
- package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +4 -5
- package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +4 -5
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +95 -92
- package/src/config/env-registry.ts +14 -83
- package/src/config/env.ts +11 -50
- package/src/config/feature-flag-registry.json +16 -16
- package/src/config/schema.ts +3 -1
- package/src/config/skills.ts +21 -2
- package/src/context/image-dimensions.ts +229 -0
- package/src/context/token-estimator.ts +75 -12
- package/src/context/window-manager.ts +49 -10
- package/src/daemon/assistant-attachments.ts +1 -13
- package/src/daemon/guardian-action-generators.ts +4 -5
- package/src/daemon/handlers/config-ingress.ts +8 -33
- package/src/daemon/handlers/config-slack-channel.ts +76 -56
- package/src/daemon/handlers/config-telegram.ts +53 -24
- package/src/daemon/handlers/sessions.ts +10 -24
- package/src/daemon/handlers/shared.ts +0 -130
- package/src/daemon/host-cu-proxy.ts +401 -0
- package/src/daemon/lifecycle.ts +39 -63
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/computer-use.ts +2 -119
- package/src/daemon/message-types/host-cu.ts +19 -0
- package/src/daemon/message-types/integrations.ts +1 -0
- package/src/daemon/message-types/messages.ts +3 -0
- package/src/daemon/server.ts +14 -21
- package/src/daemon/session-agent-loop-handlers.ts +2 -0
- package/src/daemon/session-attachments.ts +1 -2
- package/src/daemon/session-messaging.ts +3 -1
- package/src/daemon/session-slash.ts +1 -1
- package/src/daemon/session-surfaces.ts +40 -28
- package/src/daemon/session-tool-setup.ts +20 -11
- package/src/daemon/session.ts +139 -16
- package/src/daemon/tool-side-effects.ts +2 -8
- package/src/daemon/watch-handler.ts +2 -2
- package/src/email/providers/index.ts +2 -1
- package/src/events/tool-metrics-listener.ts +2 -2
- package/src/hooks/manager.ts +1 -4
- package/src/inbound/public-ingress-urls.ts +7 -7
- package/src/instrument.ts +15 -1
- package/src/logfire.ts +16 -5
- package/src/media/app-icon-generator.ts +30 -4
- package/src/media/avatar-router.ts +26 -3
- package/src/media/gemini-image-service.ts +28 -2
- package/src/memory/conversation-key-store.ts +21 -0
- package/src/memory/db-init.ts +4 -0
- package/src/memory/guardian-action-store.ts +1 -1
- package/src/memory/migrations/149-oauth-tables.ts +60 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/schema/guardian.ts +1 -1
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/oauth.ts +65 -0
- package/src/messaging/provider.ts +19 -13
- package/src/messaging/providers/gmail/adapter.ts +40 -23
- package/src/messaging/providers/gmail/client.ts +283 -122
- package/src/messaging/providers/gmail/people-client.ts +32 -24
- package/src/messaging/providers/slack/adapter.ts +29 -19
- package/src/messaging/providers/slack/client.ts +265 -78
- package/src/messaging/providers/telegram-bot/adapter.ts +19 -18
- package/src/messaging/providers/whatsapp/adapter.ts +17 -11
- package/src/messaging/registry.ts +2 -31
- package/src/notifications/copy-composer.ts +0 -5
- package/src/notifications/signal.ts +4 -5
- package/src/oauth/byo-connection.test.ts +537 -0
- package/src/oauth/byo-connection.ts +128 -0
- package/src/oauth/connect-orchestrator.ts +139 -56
- package/src/oauth/connect-types.ts +17 -23
- package/src/oauth/connection-resolver.ts +58 -0
- package/src/oauth/connection.ts +38 -0
- package/src/oauth/manual-token-connection.ts +104 -0
- package/src/oauth/oauth-store.ts +496 -0
- package/src/oauth/platform-connection.test.ts +192 -0
- package/src/oauth/platform-connection.ts +111 -0
- package/src/oauth/provider-behaviors.ts +124 -0
- package/src/oauth/scope-policy.ts +9 -2
- package/src/oauth/seed-providers.ts +161 -0
- package/src/oauth/token-persistence.ts +74 -78
- package/src/permissions/checker.ts +8 -4
- package/src/permissions/defaults.ts +0 -1
- package/src/permissions/prompter.ts +10 -1
- package/src/permissions/trust-store.ts +13 -0
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
- package/src/prompts/system-prompt.ts +70 -45
- package/src/providers/anthropic/client.ts +133 -24
- package/src/providers/gemini/client.ts +15 -6
- package/src/providers/managed-proxy/constants.ts +2 -2
- package/src/providers/managed-proxy/context.ts +5 -1
- package/src/providers/ratelimit.ts +17 -0
- package/src/providers/registry.ts +2 -2
- package/src/providers/retry.ts +1 -27
- package/src/runtime/AGENTS.md +17 -0
- package/src/runtime/auth/route-policy.ts +0 -3
- package/src/runtime/channel-invite-transports/telegram.ts +2 -1
- package/src/runtime/channel-readiness-service.ts +168 -195
- package/src/runtime/channel-readiness-types.ts +4 -0
- package/src/runtime/channel-reply-delivery.ts +0 -40
- package/src/runtime/gateway-client.ts +0 -7
- package/src/runtime/guardian-action-conversation-turn.ts +1 -3
- package/src/runtime/guardian-action-followup-executor.ts +1 -1
- package/src/runtime/guardian-action-message-composer.ts +3 -23
- package/src/runtime/http-server.ts +17 -10
- package/src/runtime/http-types.ts +2 -3
- package/src/runtime/middleware/rate-limiter.ts +74 -20
- package/src/runtime/middleware/twilio-validation.ts +1 -11
- package/src/runtime/pending-interactions.ts +14 -12
- package/src/runtime/routes/channel-delivery-routes.ts +0 -1
- package/src/runtime/routes/channel-readiness-routes.ts +2 -0
- package/src/runtime/routes/conversation-routes.ts +73 -19
- package/src/runtime/routes/diagnostics-routes.ts +11 -9
- package/src/runtime/routes/events-routes.ts +21 -11
- package/src/runtime/routes/guardian-approval-interception.ts +20 -5
- package/src/runtime/routes/host-cu-routes.ts +97 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
- package/src/runtime/routes/integrations/slack/share.ts +6 -6
- package/src/runtime/routes/integrations/twilio.ts +6 -5
- package/src/runtime/routes/log-export-routes.ts +126 -8
- package/src/runtime/routes/secret-routes.ts +3 -2
- package/src/runtime/routes/settings-routes.ts +113 -48
- package/src/runtime/routes/surface-action-routes.ts +1 -1
- package/src/runtime/routes/watch-routes.ts +128 -0
- package/src/schedule/integration-status.ts +10 -8
- package/src/security/credential-key.ts +14 -0
- package/src/security/keychain-broker-client.ts +5 -6
- package/src/security/oauth2.ts +1 -1
- package/src/security/token-manager.ts +145 -43
- package/src/skills/catalog-install.ts +358 -0
- package/src/skills/include-graph.ts +32 -0
- package/src/telegram/bot-username.ts +2 -3
- package/src/tools/apps/definitions.ts +0 -5
- package/src/tools/assets/materialize.ts +0 -5
- package/src/tools/assets/search.ts +0 -5
- package/src/tools/browser/headless-browser.ts +1 -67
- package/src/tools/browser/network-recorder.ts +1 -1
- package/src/tools/browser/network-recording-types.ts +1 -1
- package/src/tools/claude-code/claude-code.ts +0 -5
- package/src/tools/computer-use/definitions.ts +46 -11
- package/src/tools/computer-use/registry.ts +4 -5
- package/src/tools/credentials/broker.ts +5 -4
- package/src/tools/credentials/metadata-store.ts +22 -74
- package/src/tools/credentials/resolve.ts +2 -1
- package/src/tools/credentials/vault.ts +139 -151
- package/src/tools/filesystem/edit.ts +1 -6
- package/src/tools/filesystem/read.ts +0 -5
- package/src/tools/filesystem/write.ts +1 -6
- package/src/tools/host-filesystem/edit.ts +1 -6
- package/src/tools/host-filesystem/read.ts +1 -6
- package/src/tools/host-filesystem/write.ts +1 -6
- package/src/tools/mcp/mcp-tool-factory.ts +18 -1
- package/src/tools/memory/definitions.ts +0 -5
- package/src/tools/network/web-fetch.ts +0 -5
- package/src/tools/network/web-search.ts +0 -5
- package/src/tools/registry.ts +2 -7
- package/src/tools/schema-transforms.ts +99 -0
- package/src/tools/skills/load.ts +62 -8
- package/src/tools/swarm/delegate.ts +0 -5
- package/src/tools/system/avatar-generator.ts +0 -5
- package/src/tools/ui-surface/definitions.ts +0 -15
- package/src/tools/watch/screen-watch.ts +0 -5
- package/src/tools/watch/watch-state.ts +0 -12
- package/src/util/logger.ts +7 -41
- package/src/util/platform.ts +9 -28
- package/src/version.ts +10 -0
- package/src/watcher/providers/github.ts +51 -52
- package/src/watcher/providers/gmail.ts +88 -80
- package/src/watcher/providers/google-calendar.ts +94 -86
- package/src/watcher/providers/linear.ts +87 -93
- package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
- package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
- package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
- package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
- package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
- package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
- package/src/cli/commands/dev.ts +0 -129
- package/src/cli/commands/map.ts +0 -391
- package/src/cli/commands/oauth.ts +0 -77
- package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +0 -16
- package/src/daemon/computer-use-session.ts +0 -1020
- package/src/daemon/ride-shotgun-handler.ts +0 -567
- package/src/oauth/provider-profiles.ts +0 -192
- package/src/prompts/computer-use-prompt.ts +0 -98
- package/src/runtime/routes/computer-use-routes.ts +0 -641
- package/src/runtime/telegram-streaming-delivery.test.ts +0 -597
- package/src/runtime/telegram-streaming-delivery.ts +0 -383
- package/src/tools/computer-use/request-computer-control.ts +0 -61
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as http from "node:http";
|
|
2
2
|
import { afterEach, describe, expect, mock, test } from "bun:test";
|
|
3
3
|
|
|
4
|
+
import { credentialKey } from "../security/credential-key.js";
|
|
4
5
|
import type { CredentialMetadata } from "../tools/credentials/metadata-store.js";
|
|
5
6
|
import type { CredentialInjectionTemplate } from "../tools/credentials/policy-types.js";
|
|
6
7
|
import type { ResolvedCredential } from "../tools/credentials/resolve.js";
|
|
@@ -82,7 +83,7 @@ function makeResolved(
|
|
|
82
83
|
credentialId,
|
|
83
84
|
service,
|
|
84
85
|
field,
|
|
85
|
-
storageKey:
|
|
86
|
+
storageKey: credentialKey(service, field),
|
|
86
87
|
injectionTemplates: templates,
|
|
87
88
|
metadata: {
|
|
88
89
|
credentialId,
|
|
@@ -154,7 +155,7 @@ describe("policyCallback credential injection", () => {
|
|
|
154
155
|
resolveByIdResults.set("cred-local", resolved);
|
|
155
156
|
credentialMetadataList.push(resolved.metadata);
|
|
156
157
|
secureKeyValues.set(
|
|
157
|
-
"
|
|
158
|
+
credentialKey("test-service", "api-key"),
|
|
158
159
|
"fal_secretvalue123",
|
|
159
160
|
);
|
|
160
161
|
|
|
@@ -194,7 +195,10 @@ describe("policyCallback credential injection", () => {
|
|
|
194
195
|
const resolved = makeResolved("cred-bearer", [tpl]);
|
|
195
196
|
resolveByIdResults.set("cred-bearer", resolved);
|
|
196
197
|
credentialMetadataList.push(resolved.metadata);
|
|
197
|
-
secureKeyValues.set(
|
|
198
|
+
secureKeyValues.set(
|
|
199
|
+
credentialKey("test-service", "api-key"),
|
|
200
|
+
"tok_abc123",
|
|
201
|
+
);
|
|
198
202
|
|
|
199
203
|
const session = createSession(
|
|
200
204
|
CONV_ID,
|
|
@@ -311,7 +315,7 @@ describe("MITM rewriteCallback credential injection", () => {
|
|
|
311
315
|
const tpl = makeTemplate("*.fal.ai", "authorization", "Key ");
|
|
312
316
|
const resolved = makeResolved("cred-fal", [tpl]);
|
|
313
317
|
resolveByIdResults.set("cred-fal", resolved);
|
|
314
|
-
secureKeyValues.set("
|
|
318
|
+
secureKeyValues.set(credentialKey("test-service", "api-key"), "fal_secret");
|
|
315
319
|
|
|
316
320
|
const templates = new Map([["cred-fal", [tpl]]]);
|
|
317
321
|
const headers: Record<string, string> = {
|
|
@@ -350,7 +354,7 @@ describe("MITM rewriteCallback credential injection", () => {
|
|
|
350
354
|
|
|
351
355
|
const tpl = makeTemplate("*.fal.ai", "authorization", "Key ");
|
|
352
356
|
resolveByIdResults.set("cred-fal", makeResolved("cred-fal", [tpl]));
|
|
353
|
-
secureKeyValues.set("
|
|
357
|
+
secureKeyValues.set(credentialKey("test-service", "api-key"), "fal_secret");
|
|
354
358
|
|
|
355
359
|
const templates = new Map([["cred-fal", [tpl]]]);
|
|
356
360
|
const headers: Record<string, string> = {
|
|
@@ -479,8 +483,8 @@ describe("composeWith injection", () => {
|
|
|
479
483
|
);
|
|
480
484
|
resolveByServiceFieldResults.set("twilio:auth_token", composedResolved);
|
|
481
485
|
|
|
482
|
-
secureKeyValues.set("
|
|
483
|
-
secureKeyValues.set("
|
|
486
|
+
secureKeyValues.set(credentialKey("twilio", "account_sid"), "ACtest123");
|
|
487
|
+
secureKeyValues.set(credentialKey("twilio", "auth_token"), "secret456");
|
|
484
488
|
|
|
485
489
|
const session = createSession(
|
|
486
490
|
CONV_ID,
|
|
@@ -531,7 +535,7 @@ describe("composeWith injection", () => {
|
|
|
531
535
|
);
|
|
532
536
|
resolveByIdResults.set("cred-primary", primaryResolved);
|
|
533
537
|
credentialMetadataList.push(primaryResolved.metadata);
|
|
534
|
-
secureKeyValues.set("
|
|
538
|
+
secureKeyValues.set(credentialKey("twilio", "account_sid"), "ACtest123");
|
|
535
539
|
|
|
536
540
|
// Do NOT register the composed credential in resolveByServiceFieldResults
|
|
537
541
|
|
|
@@ -578,7 +582,10 @@ describe("composeWith injection", () => {
|
|
|
578
582
|
const resolved = makeResolved("cred-b64", [tpl]);
|
|
579
583
|
resolveByIdResults.set("cred-b64", resolved);
|
|
580
584
|
credentialMetadataList.push(resolved.metadata);
|
|
581
|
-
secureKeyValues.set(
|
|
585
|
+
secureKeyValues.set(
|
|
586
|
+
credentialKey("test-service", "api-key"),
|
|
587
|
+
"plaintext",
|
|
588
|
+
);
|
|
582
589
|
|
|
583
590
|
const session = createSession(CONV_ID, ["cred-b64"], undefined, DATA_DIR);
|
|
584
591
|
const started = await startSession(session.id);
|
|
@@ -624,7 +631,7 @@ describe("composeWith injection", () => {
|
|
|
624
631
|
);
|
|
625
632
|
resolveByIdResults.set("cred-primary", primaryResolved);
|
|
626
633
|
credentialMetadataList.push(primaryResolved.metadata);
|
|
627
|
-
secureKeyValues.set("
|
|
634
|
+
secureKeyValues.set(credentialKey("twilio", "account_sid"), "ACtest123");
|
|
628
635
|
|
|
629
636
|
// Composed credential metadata resolves, but no secret value stored
|
|
630
637
|
const composedResolved = makeResolved(
|
|
@@ -634,7 +641,7 @@ describe("composeWith injection", () => {
|
|
|
634
641
|
"auth_token",
|
|
635
642
|
);
|
|
636
643
|
resolveByServiceFieldResults.set("twilio:auth_token", composedResolved);
|
|
637
|
-
// Do NOT set secureKeyValues for "
|
|
644
|
+
// Do NOT set secureKeyValues for credentialKey("twilio", "auth_token")
|
|
638
645
|
|
|
639
646
|
const session = createSession(
|
|
640
647
|
CONV_ID,
|
|
@@ -700,8 +707,11 @@ describe("composeWith injection", () => {
|
|
|
700
707
|
composedResolved,
|
|
701
708
|
);
|
|
702
709
|
|
|
703
|
-
secureKeyValues.set("
|
|
704
|
-
secureKeyValues.set(
|
|
710
|
+
secureKeyValues.set(credentialKey("my-service", "primary-key"), "value1");
|
|
711
|
+
secureKeyValues.set(
|
|
712
|
+
credentialKey("my-service", "secondary-key"),
|
|
713
|
+
"value2",
|
|
714
|
+
);
|
|
705
715
|
|
|
706
716
|
const session = createSession(
|
|
707
717
|
CONV_ID,
|
|
@@ -69,6 +69,8 @@ mock.module("./policy-validate.js", () => ({
|
|
|
69
69
|
}),
|
|
70
70
|
}));
|
|
71
71
|
|
|
72
|
+
import { credentialKey } from "../security/credential-key.js";
|
|
73
|
+
|
|
72
74
|
const { credentialStoreTool } = await import("../tools/credentials/vault.js");
|
|
73
75
|
|
|
74
76
|
describe("one-time send override", () => {
|
|
@@ -96,7 +98,7 @@ describe("one-time send override", () => {
|
|
|
96
98
|
expect(result.isError).toBe(true);
|
|
97
99
|
expect(result.content).toContain("not enabled");
|
|
98
100
|
// Value must NOT be stored in keychain
|
|
99
|
-
expect(storedKeys.has("
|
|
101
|
+
expect(storedKeys.has(credentialKey("svc", "key"))).toBe(false);
|
|
100
102
|
});
|
|
101
103
|
|
|
102
104
|
test("transient_send succeeds when allowOneTimeSend is enabled", async () => {
|
|
@@ -119,7 +121,7 @@ describe("one-time send override", () => {
|
|
|
119
121
|
expect(result.isError).toBe(false);
|
|
120
122
|
expect(result.content).toContain("NOT saved");
|
|
121
123
|
// Value must NOT be stored in keychain
|
|
122
|
-
expect(storedKeys.has("
|
|
124
|
+
expect(storedKeys.has(credentialKey("svc", "key"))).toBe(false);
|
|
123
125
|
});
|
|
124
126
|
|
|
125
127
|
test("store delivery always persists to keychain regardless of allowOneTimeSend", async () => {
|
|
@@ -138,7 +140,7 @@ describe("one-time send override", () => {
|
|
|
138
140
|
);
|
|
139
141
|
expect(result.isError).toBe(false);
|
|
140
142
|
expect(result.content).toContain("stored");
|
|
141
|
-
expect(storedKeys.has("
|
|
143
|
+
expect(storedKeys.has(credentialKey("svc", "key"))).toBe(true);
|
|
142
144
|
});
|
|
143
145
|
|
|
144
146
|
test("transient_send response content never contains the secret value", async () => {
|
|
@@ -117,6 +117,7 @@ function makeCompletingSession(): Session {
|
|
|
117
117
|
updateClient: () => {},
|
|
118
118
|
setHostBashProxy: () => {},
|
|
119
119
|
setHostFileProxy: () => {},
|
|
120
|
+
setHostCuProxy: () => {},
|
|
120
121
|
hasAnyPendingConfirmation: () => false,
|
|
121
122
|
hasPendingConfirmation: () => false,
|
|
122
123
|
denyAllPendingConfirmations: () => {},
|
|
@@ -173,6 +174,7 @@ function makeHangingSession(): Session {
|
|
|
173
174
|
updateClient: () => {},
|
|
174
175
|
setHostBashProxy: () => {},
|
|
175
176
|
setHostFileProxy: () => {},
|
|
177
|
+
setHostCuProxy: () => {},
|
|
176
178
|
hasAnyPendingConfirmation: () => false,
|
|
177
179
|
hasPendingConfirmation: () => false,
|
|
178
180
|
denyAllPendingConfirmations: () => {},
|
|
@@ -257,6 +259,7 @@ function makePendingApprovalSession(
|
|
|
257
259
|
updateClient: () => {},
|
|
258
260
|
setHostBashProxy: () => {},
|
|
259
261
|
setHostFileProxy: () => {},
|
|
262
|
+
setHostCuProxy: () => {},
|
|
260
263
|
hasAnyPendingConfirmation: () => pending.size > 0,
|
|
261
264
|
hasPendingConfirmation: (candidateRequestId: string) =>
|
|
262
265
|
pending.has(candidateRequestId),
|
|
@@ -364,11 +367,17 @@ describe("POST /v1/messages — queue-if-busy and hub publishing", () => {
|
|
|
364
367
|
interface: "macos",
|
|
365
368
|
}),
|
|
366
369
|
});
|
|
367
|
-
const body = (await res.json()) as {
|
|
370
|
+
const body = (await res.json()) as {
|
|
371
|
+
accepted: boolean;
|
|
372
|
+
messageId: string;
|
|
373
|
+
conversationId: string;
|
|
374
|
+
};
|
|
368
375
|
|
|
369
376
|
expect(res.status).toBe(202);
|
|
370
377
|
expect(body.accepted).toBe(true);
|
|
371
378
|
expect(body.messageId).toBeDefined();
|
|
379
|
+
expect(typeof body.conversationId).toBe("string");
|
|
380
|
+
expect(body.conversationId.length).toBeGreaterThan(0);
|
|
372
381
|
|
|
373
382
|
await stopServer();
|
|
374
383
|
});
|
|
@@ -485,8 +494,8 @@ describe("POST /v1/messages — queue-if-busy and hub publishing", () => {
|
|
|
485
494
|
createCanonicalGuardianRequest({
|
|
486
495
|
id: requestId,
|
|
487
496
|
kind: "tool_approval",
|
|
488
|
-
sourceType: "
|
|
489
|
-
sourceChannel: "
|
|
497
|
+
sourceType: "voice",
|
|
498
|
+
sourceChannel: "slack",
|
|
490
499
|
conversationId,
|
|
491
500
|
toolName: "call_start",
|
|
492
501
|
status: "pending",
|
|
@@ -511,8 +520,8 @@ describe("POST /v1/messages — queue-if-busy and hub publishing", () => {
|
|
|
511
520
|
body: JSON.stringify({
|
|
512
521
|
conversationKey,
|
|
513
522
|
content: "sure let's do that",
|
|
514
|
-
sourceChannel: "
|
|
515
|
-
interface: "
|
|
523
|
+
sourceChannel: "slack",
|
|
524
|
+
interface: "slack",
|
|
516
525
|
}),
|
|
517
526
|
});
|
|
518
527
|
const body = (await res.json()) as {
|
|
@@ -808,11 +817,17 @@ describe("POST /v1/messages — queue-if-busy and hub publishing", () => {
|
|
|
808
817
|
interface: "macos",
|
|
809
818
|
}),
|
|
810
819
|
});
|
|
811
|
-
const body2 = (await res2.json()) as {
|
|
820
|
+
const body2 = (await res2.json()) as {
|
|
821
|
+
accepted: boolean;
|
|
822
|
+
queued: boolean;
|
|
823
|
+
conversationId: string;
|
|
824
|
+
};
|
|
812
825
|
|
|
813
826
|
expect(res2.status).toBe(202);
|
|
814
827
|
expect(body2.accepted).toBe(true);
|
|
815
828
|
expect(body2.queued).toBe(true);
|
|
829
|
+
expect(typeof body2.conversationId).toBe("string");
|
|
830
|
+
expect(body2.conversationId.length).toBeGreaterThan(0);
|
|
816
831
|
|
|
817
832
|
await stopServer();
|
|
818
833
|
});
|
|
@@ -127,7 +127,6 @@ mock.module("../util/logger.js", () => ({
|
|
|
127
127
|
new Proxy({} as Record<string, unknown>, {
|
|
128
128
|
get: () => () => {},
|
|
129
129
|
}),
|
|
130
|
-
isDebug: () => false,
|
|
131
130
|
truncateForLog: (value: string, maxLen = 500) =>
|
|
132
131
|
value.length > maxLen ? value.slice(0, maxLen) + "..." : value,
|
|
133
132
|
initLogger: () => {},
|
|
@@ -342,7 +341,7 @@ describe("Session initialization benchmark", () => {
|
|
|
342
341
|
}
|
|
343
342
|
|
|
344
343
|
timings.sort((a, b) => a - b);
|
|
345
|
-
expect(median(timings)).toBeLessThan(
|
|
344
|
+
expect(median(timings)).toBeLessThan(15);
|
|
346
345
|
});
|
|
347
346
|
|
|
348
347
|
test("buildSystemPrompt assembles prompt under 50ms (median of 5)", () => {
|
|
@@ -384,9 +383,9 @@ describe("Session initialization benchmark", () => {
|
|
|
384
383
|
await initializeTools();
|
|
385
384
|
const definitions = getAllToolDefinitions();
|
|
386
385
|
|
|
387
|
-
// Sanity: we expect a meaningful number of core tools (at least
|
|
386
|
+
// Sanity: we expect a meaningful number of core tools (at least 18)
|
|
388
387
|
// but not an unreasonable explosion (under 200)
|
|
389
|
-
expect(definitions.length).toBeGreaterThanOrEqual(
|
|
388
|
+
expect(definitions.length).toBeGreaterThanOrEqual(18);
|
|
390
389
|
expect(definitions.length).toBeLessThan(200);
|
|
391
390
|
});
|
|
392
391
|
});
|
|
@@ -548,6 +547,6 @@ describe("End-to-end session creation benchmark", () => {
|
|
|
548
547
|
}
|
|
549
548
|
|
|
550
549
|
timings.sort((a, b) => a - b);
|
|
551
|
-
expect(median(timings)).toBeLessThan(
|
|
550
|
+
expect(median(timings)).toBeLessThan(15);
|
|
552
551
|
});
|
|
553
552
|
});
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
redirectToSecurePrompt,
|
|
6
6
|
} from "../daemon/session-messaging.js";
|
|
7
7
|
import type { SecretPrompter } from "../permissions/secret-prompter.js";
|
|
8
|
+
import { credentialKey } from "../security/credential-key.js";
|
|
8
9
|
|
|
9
10
|
const setSecureKeyMock = mock((_key?: string, _value?: string) => true);
|
|
10
11
|
const upsertCredentialMetadataMock = mock(
|
|
@@ -108,7 +109,7 @@ describe("session-messaging secret redirect", () => {
|
|
|
108
109
|
label: "Telegram Bot Token",
|
|
109
110
|
});
|
|
110
111
|
expect(setSecureKeyMock).toHaveBeenCalledWith(
|
|
111
|
-
"
|
|
112
|
+
credentialKey("telegram", "bot_token"),
|
|
112
113
|
"123456789:ABCDefGHIJklmnopQRSTuvwxyz012345678",
|
|
113
114
|
);
|
|
114
115
|
expect(upsertCredentialMetadataMock).toHaveBeenCalledWith(
|
|
@@ -158,7 +159,7 @@ describe("session-messaging secret redirect", () => {
|
|
|
158
159
|
label: "Telegram Bot Token",
|
|
159
160
|
});
|
|
160
161
|
expect(setSecureKeyMock).toHaveBeenCalledWith(
|
|
161
|
-
"
|
|
162
|
+
credentialKey("telegram", "bot_token"),
|
|
162
163
|
"123456789:ABCDefGHIJklmnopQRSTuvwxyz012345678",
|
|
163
164
|
);
|
|
164
165
|
});
|
|
@@ -197,7 +198,7 @@ describe("session-messaging secret redirect", () => {
|
|
|
197
198
|
label: "Telegram Bot Token",
|
|
198
199
|
});
|
|
199
200
|
expect(setSecureKeyMock).toHaveBeenCalledWith(
|
|
200
|
-
"
|
|
201
|
+
credentialKey("telegram", "bot_token"),
|
|
201
202
|
"123456789:ABCDefGHIJklmnopQRSTuvwxyz012345678",
|
|
202
203
|
);
|
|
203
204
|
});
|
|
@@ -231,7 +232,7 @@ describe("session-messaging secret redirect", () => {
|
|
|
231
232
|
label: "Secure Credential Entry",
|
|
232
233
|
});
|
|
233
234
|
expect(setSecureKeyMock).toHaveBeenCalledWith(
|
|
234
|
-
"
|
|
235
|
+
credentialKey("detected", "Some Unknown Secret"),
|
|
235
236
|
"opaque-secret",
|
|
236
237
|
);
|
|
237
238
|
expect(upsertCredentialMetadataMock).toHaveBeenCalledWith(
|
|
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
|
|
3
3
|
import type { SkillSummary } from "../config/skills.js";
|
|
4
4
|
import {
|
|
5
|
+
collectAllMissing,
|
|
5
6
|
getImmediateChildren,
|
|
6
7
|
indexCatalogById,
|
|
7
8
|
traverseIncludes,
|
|
@@ -298,3 +299,68 @@ describe("validateIncludes — cycle detection", () => {
|
|
|
298
299
|
}
|
|
299
300
|
});
|
|
300
301
|
});
|
|
302
|
+
|
|
303
|
+
describe("collectAllMissing", () => {
|
|
304
|
+
test("returns empty set when skill has no includes", () => {
|
|
305
|
+
const catalog = [makeSkill("root")];
|
|
306
|
+
const index = indexCatalogById(catalog);
|
|
307
|
+
expect(collectAllMissing("root", index)).toEqual(new Set([]));
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test("returns empty set when all includes are present", () => {
|
|
311
|
+
const catalog = [makeSkill("A", ["B"]), makeSkill("B")];
|
|
312
|
+
const index = indexCatalogById(catalog);
|
|
313
|
+
expect(collectAllMissing("A", index)).toEqual(new Set([]));
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test("returns immediate missing children", () => {
|
|
317
|
+
const catalog = [makeSkill("A", ["B", "C"]), makeSkill("C")];
|
|
318
|
+
const index = indexCatalogById(catalog);
|
|
319
|
+
expect(collectAllMissing("A", index)).toEqual(new Set(["B"]));
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test("returns transitive missing children", () => {
|
|
323
|
+
const catalog = [makeSkill("A", ["B"]), makeSkill("B", ["C"])];
|
|
324
|
+
const index = indexCatalogById(catalog);
|
|
325
|
+
expect(collectAllMissing("A", index)).toEqual(new Set(["C"]));
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("returns multiple missing at different levels", () => {
|
|
329
|
+
// A→B→C, B present but C missing
|
|
330
|
+
const catalog1 = [makeSkill("A", ["B"]), makeSkill("B", ["C"])];
|
|
331
|
+
const index1 = indexCatalogById(catalog1);
|
|
332
|
+
expect(collectAllMissing("A", index1)).toEqual(new Set(["C"]));
|
|
333
|
+
|
|
334
|
+
// A includes B and C, both missing
|
|
335
|
+
const catalog2 = [makeSkill("A", ["B", "C"])];
|
|
336
|
+
const index2 = indexCatalogById(catalog2);
|
|
337
|
+
expect(collectAllMissing("A", index2)).toEqual(new Set(["B", "C"]));
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test("handles diamond with missing leaf", () => {
|
|
341
|
+
const catalog = [
|
|
342
|
+
makeSkill("A", ["B", "C"]),
|
|
343
|
+
makeSkill("B", ["D"]),
|
|
344
|
+
makeSkill("C", ["D"]),
|
|
345
|
+
];
|
|
346
|
+
const index = indexCatalogById(catalog);
|
|
347
|
+
const result = collectAllMissing("A", index);
|
|
348
|
+
expect(result).toEqual(new Set(["D"]));
|
|
349
|
+
// Verify no duplicates (Set handles this, but confirm size)
|
|
350
|
+
expect(result.size).toBe(1);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("does not loop infinitely on cycles", () => {
|
|
354
|
+
const catalog = [makeSkill("A", ["B"]), makeSkill("B", ["A"])];
|
|
355
|
+
const index = indexCatalogById(catalog);
|
|
356
|
+
expect(collectAllMissing("A", index)).toEqual(new Set([]));
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
test("handles cycle with missing node", () => {
|
|
360
|
+
const catalog = [makeSkill("A", ["B"]), makeSkill("B", ["C"])];
|
|
361
|
+
// C is missing, and if C referenced B it would be a cycle — but C isn't in catalog
|
|
362
|
+
// So A→B→C, C missing
|
|
363
|
+
const index = indexCatalogById(catalog);
|
|
364
|
+
expect(collectAllMissing("A", index)).toEqual(new Set(["C"]));
|
|
365
|
+
});
|
|
366
|
+
});
|
|
@@ -60,10 +60,16 @@ mock.module("../util/logger.js", () => ({
|
|
|
60
60
|
new Proxy({} as Record<string, unknown>, {
|
|
61
61
|
get: () => () => {},
|
|
62
62
|
}),
|
|
63
|
-
isDebug: () => false,
|
|
64
63
|
truncateForLog: (s: unknown) => String(s),
|
|
65
64
|
}));
|
|
66
65
|
|
|
66
|
+
// Mock autoInstallFromCatalog — default returns false (not found in catalog).
|
|
67
|
+
// Tests can override via `mockAutoInstall.mockImplementation(...)`.
|
|
68
|
+
const mockAutoInstall = mock((_skillId: string) => Promise.resolve(false));
|
|
69
|
+
mock.module("../skills/catalog-install.js", () => ({
|
|
70
|
+
autoInstallFromCatalog: (skillId: string) => mockAutoInstall(skillId),
|
|
71
|
+
}));
|
|
72
|
+
|
|
67
73
|
await import("../tools/skills/load.js");
|
|
68
74
|
const { getTool } = await import("../tools/registry.js");
|
|
69
75
|
|
|
@@ -145,6 +151,10 @@ async function executeSkillLoad(
|
|
|
145
151
|
describe("skill_load tool", () => {
|
|
146
152
|
beforeEach(() => {
|
|
147
153
|
mkdirSync(join(TEST_DIR, "skills"), { recursive: true });
|
|
154
|
+
mockAutoInstall.mockReset();
|
|
155
|
+
mockAutoInstall.mockImplementation((_skillId: string) =>
|
|
156
|
+
Promise.resolve(false),
|
|
157
|
+
);
|
|
148
158
|
});
|
|
149
159
|
|
|
150
160
|
afterEach(() => {
|
|
@@ -850,4 +860,142 @@ describe("skill_load tool", () => {
|
|
|
850
860
|
"Use `skill_execute` to call these tools.",
|
|
851
861
|
);
|
|
852
862
|
});
|
|
863
|
+
|
|
864
|
+
test("auto-installs missing includes from catalog", async () => {
|
|
865
|
+
// Parent includes "dep-a" which is not initially in the catalog
|
|
866
|
+
writeSkillWithIncludes(
|
|
867
|
+
"auto-parent",
|
|
868
|
+
"Auto Parent",
|
|
869
|
+
"Has auto-installable dep",
|
|
870
|
+
"Parent body",
|
|
871
|
+
["dep-a"],
|
|
872
|
+
);
|
|
873
|
+
writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- auto-parent\n");
|
|
874
|
+
|
|
875
|
+
// Mock autoInstallFromCatalog to succeed and write the skill to disk
|
|
876
|
+
mockAutoInstall.mockImplementation((skillId: string) => {
|
|
877
|
+
if (skillId === "dep-a") {
|
|
878
|
+
writeSkill("dep-a", "Dep A", "A dependency", "Dep A body");
|
|
879
|
+
// Add to SKILLS.md so catalog reload finds it
|
|
880
|
+
writeFileSync(
|
|
881
|
+
join(TEST_DIR, "skills", "SKILLS.md"),
|
|
882
|
+
"- auto-parent\n- dep-a\n",
|
|
883
|
+
);
|
|
884
|
+
return Promise.resolve(true);
|
|
885
|
+
}
|
|
886
|
+
return Promise.resolve(false);
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
const result = await executeSkillLoad({ skill: "auto-parent" });
|
|
890
|
+
expect(result.isError).toBe(false);
|
|
891
|
+
expect(result.content).toContain("Skill: Auto Parent");
|
|
892
|
+
expect(result.content).toContain("<loaded_skill");
|
|
893
|
+
expect(mockAutoInstall).toHaveBeenCalledWith("dep-a");
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
test("auto-installs transitive missing includes across rounds", async () => {
|
|
897
|
+
// Skill A includes B, B includes C. Neither B nor C in initial catalog.
|
|
898
|
+
writeSkillWithIncludes("trans-a", "Trans A", "Top level", "Body A", [
|
|
899
|
+
"trans-b",
|
|
900
|
+
]);
|
|
901
|
+
writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- trans-a\n");
|
|
902
|
+
|
|
903
|
+
let round = 0;
|
|
904
|
+
mockAutoInstall.mockImplementation((skillId: string) => {
|
|
905
|
+
if (skillId === "trans-b" && round === 0) {
|
|
906
|
+
// First round: install B (which includes C)
|
|
907
|
+
writeSkillWithIncludes("trans-b", "Trans B", "Mid level", "Body B", [
|
|
908
|
+
"trans-c",
|
|
909
|
+
]);
|
|
910
|
+
writeFileSync(
|
|
911
|
+
join(TEST_DIR, "skills", "SKILLS.md"),
|
|
912
|
+
"- trans-a\n- trans-b\n",
|
|
913
|
+
);
|
|
914
|
+
round++;
|
|
915
|
+
return Promise.resolve(true);
|
|
916
|
+
}
|
|
917
|
+
if (skillId === "trans-c") {
|
|
918
|
+
// Second round: install C
|
|
919
|
+
writeSkill("trans-c", "Trans C", "Leaf", "Body C");
|
|
920
|
+
writeFileSync(
|
|
921
|
+
join(TEST_DIR, "skills", "SKILLS.md"),
|
|
922
|
+
"- trans-a\n- trans-b\n- trans-c\n",
|
|
923
|
+
);
|
|
924
|
+
return Promise.resolve(true);
|
|
925
|
+
}
|
|
926
|
+
return Promise.resolve(false);
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
const result = await executeSkillLoad({ skill: "trans-a" });
|
|
930
|
+
expect(result.isError).toBe(false);
|
|
931
|
+
expect(result.content).toContain("Skill: Trans A");
|
|
932
|
+
expect(result.content).toContain("<loaded_skill");
|
|
933
|
+
expect(mockAutoInstall).toHaveBeenCalledWith("trans-b");
|
|
934
|
+
expect(mockAutoInstall).toHaveBeenCalledWith("trans-c");
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
test("returns error when auto-install of missing include fails", async () => {
|
|
938
|
+
writeSkillWithIncludes(
|
|
939
|
+
"fail-parent",
|
|
940
|
+
"Fail Parent",
|
|
941
|
+
"Has failing dep",
|
|
942
|
+
"Body",
|
|
943
|
+
["dep-x"],
|
|
944
|
+
);
|
|
945
|
+
writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- fail-parent\n");
|
|
946
|
+
|
|
947
|
+
// autoInstallFromCatalog throws an error
|
|
948
|
+
mockAutoInstall.mockImplementation((skillId: string) => {
|
|
949
|
+
if (skillId === "dep-x") {
|
|
950
|
+
return Promise.reject(new Error("Network error"));
|
|
951
|
+
}
|
|
952
|
+
return Promise.resolve(false);
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
const result = await executeSkillLoad({ skill: "fail-parent" });
|
|
956
|
+
expect(result.isError).toBe(true);
|
|
957
|
+
expect(result.content).toContain("dep-x");
|
|
958
|
+
expect(result.content).toContain("not found");
|
|
959
|
+
expect(result.content).not.toContain("<loaded_skill");
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
test("stops after MAX_INSTALL_ROUNDS", async () => {
|
|
963
|
+
// Pathological case: each install round reveals a new missing dep
|
|
964
|
+
writeSkillWithIncludes("loop-root", "Loop Root", "Infinite deps", "Body", [
|
|
965
|
+
"loop-dep-0",
|
|
966
|
+
]);
|
|
967
|
+
writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- loop-root\n");
|
|
968
|
+
|
|
969
|
+
let installCount = 0;
|
|
970
|
+
mockAutoInstall.mockImplementation((skillId: string) => {
|
|
971
|
+
const id = skillId;
|
|
972
|
+
if (id.startsWith("loop-dep-")) {
|
|
973
|
+
installCount++;
|
|
974
|
+
const nextDepId = `loop-dep-${installCount}`;
|
|
975
|
+
// Install the requested dep, but it includes yet another missing dep
|
|
976
|
+
writeSkillWithIncludes(
|
|
977
|
+
id,
|
|
978
|
+
`Loop Dep ${installCount}`,
|
|
979
|
+
"Generated dep",
|
|
980
|
+
"Body",
|
|
981
|
+
[nextDepId],
|
|
982
|
+
);
|
|
983
|
+
// Update SKILLS.md to include all installed deps so far
|
|
984
|
+
const entries = ["- loop-root\n"];
|
|
985
|
+
for (let i = 0; i < installCount; i++) {
|
|
986
|
+
entries.push(`- loop-dep-${i}\n`);
|
|
987
|
+
}
|
|
988
|
+
writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), entries.join(""));
|
|
989
|
+
return Promise.resolve(true);
|
|
990
|
+
}
|
|
991
|
+
return Promise.resolve(false);
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
const result = await executeSkillLoad({ skill: "loop-root" });
|
|
995
|
+
// Should terminate with an error (the final dep is still missing)
|
|
996
|
+
expect(result.isError).toBe(true);
|
|
997
|
+
expect(result.content).toContain("not found");
|
|
998
|
+
// Should have terminated — installCount should be bounded by MAX_INSTALL_ROUNDS (5)
|
|
999
|
+
expect(installCount).toBeLessThanOrEqual(5);
|
|
1000
|
+
});
|
|
853
1001
|
});
|
|
@@ -9,7 +9,7 @@ import { tmpdir } from "node:os";
|
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
11
11
|
|
|
12
|
-
import { uninstallSkillLocally } from "../
|
|
12
|
+
import { uninstallSkillLocally } from "../skills/catalog-install.js";
|
|
13
13
|
|
|
14
14
|
let tempDir: string;
|
|
15
15
|
let originalBaseDataDir: string | undefined;
|
|
@@ -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-applescript\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-applescript");
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
test("errors when skill is not installed", () => {
|