@vellumai/assistant 0.10.4 → 0.10.5-dev.202607022329.d89bf2f
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 +94 -91
- package/Dockerfile +5 -16
- package/README.md +7 -10
- package/bun.lock +0 -4
- package/docs/architecture/security.md +20 -20
- package/docs/stt-provider-onboarding.md +4 -5
- package/docs/trusted-contact-access.md +7 -7
- package/eslint-rules/cli-no-daemon-internals.js +4 -0
- package/eslint.config.mjs +7 -1
- package/knip.json +0 -1
- package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +36 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/invite-contract.test.ts +233 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +40 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +20 -8
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +33 -0
- package/node_modules/@vellumai/gateway-client/src/invite-contract.ts +216 -0
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +91 -0
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +39 -3
- package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +0 -8
- package/node_modules/@vellumai/service-contracts/src/__tests__/package-boundary.test.ts +1 -5
- package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +3 -14
- package/openapi.yaml +1073 -225
- package/package.json +1 -4
- package/scripts/test.sh +35 -13
- package/src/__tests__/access-request-seed-content-blocks.test.ts +60 -4
- package/src/__tests__/acp-session.test.ts +4 -1
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +0 -6
- package/src/__tests__/add-message-preassigned-id.test.ts +56 -0
- package/src/__tests__/agent-loop-set-system-prompt.test.ts +139 -0
- package/src/__tests__/app-conversation-ids-backfill.test.ts +3 -0
- package/src/__tests__/app-source-watcher.test.ts +11 -0
- package/src/__tests__/approval-interception-trust-gates.test.ts +148 -3
- package/src/__tests__/assistant-attachments.test.ts +62 -4
- package/src/__tests__/assistant-id-boundary-guard.test.ts +0 -1
- package/src/__tests__/attachments-store-heic-normalize.test.ts +230 -0
- package/src/__tests__/attachments-store.test.ts +3 -0
- package/src/__tests__/background-shell-bash.test.ts +34 -0
- package/src/__tests__/background-shell-host-bash.test.ts +12 -0
- package/src/__tests__/background-tool-registry.test.ts +97 -0
- package/src/__tests__/background-tool-routes.test.ts +72 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
- package/src/__tests__/btw-routes.test.ts +57 -3
- package/src/__tests__/call-controller.test.ts +474 -84
- package/src/__tests__/call-domain.test.ts +48 -1
- package/src/__tests__/call-pointer-messages.test.ts +57 -19
- package/src/__tests__/call-pointer-no-hardcoded-copy.guard.test.ts +2 -6
- package/src/__tests__/call-routes-http.test.ts +15 -14
- package/src/__tests__/call-setup-flow-invite.test.ts +568 -0
- package/src/__tests__/call-setup-flow-midcall-trust.test.ts +231 -0
- package/src/__tests__/call-setup-flow-name-capture.test.ts +754 -0
- package/src/__tests__/call-setup-flow-verification.test.ts +925 -0
- package/src/__tests__/call-setup-flow.test.ts +311 -0
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +0 -1
- package/src/__tests__/channel-guardian.test.ts +4 -7
- package/src/__tests__/channel-readiness-service.test.ts +0 -6
- package/src/__tests__/channel-readiness-slack-remote.test.ts +0 -4
- package/src/__tests__/channel-retry-sweep.test.ts +76 -64
- package/src/__tests__/channel-setup-panel-ack.test.ts +184 -0
- package/src/__tests__/clear-all-lexical.test.ts +90 -0
- package/src/__tests__/compactor-media-strip.test.ts +177 -0
- package/src/__tests__/config-loader-backfill.test.ts +5 -5
- package/src/__tests__/config-schema.test.ts +17 -0
- package/src/__tests__/contact-store-interaction-info.test.ts +27 -70
- package/src/__tests__/contacts-relay-reads.test.ts +188 -23
- package/src/__tests__/contacts-tools.test.ts +42 -1
- package/src/__tests__/contacts-write.test.ts +181 -0
- package/src/__tests__/context-search-conversations-source.test.ts +220 -81
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop.test.ts +39 -0
- package/src/__tests__/conversation-analysis-routes.test.ts +1 -1
- package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -0
- package/src/__tests__/conversation-clear-safety.test.ts +0 -1
- package/src/__tests__/conversation-crud-enabled-plugins.test.ts +60 -0
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -1
- package/src/__tests__/conversation-disk-view.test.ts +1 -0
- package/src/__tests__/conversation-enabledplugins-route.test.ts +184 -0
- package/src/__tests__/conversation-error.test.ts +0 -18
- package/src/__tests__/conversation-fork-crud.test.ts +67 -0
- package/src/__tests__/conversation-history-web-search.test.ts +7 -137
- package/src/__tests__/conversation-initial-prompt.test.ts +110 -0
- package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +191 -0
- package/src/__tests__/conversation-process-callsite.test.ts +37 -0
- package/src/__tests__/conversation-queue.test.ts +149 -0
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +392 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +84 -0
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +407 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +36 -16
- package/src/__tests__/conversation-skill-tools.test.ts +73 -0
- package/src/__tests__/conversation-wipe.test.ts +20 -5
- package/src/__tests__/credential-prompt-route.test.ts +69 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -3
- package/src/__tests__/db-drop-messages-fts.test.ts +115 -0
- package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -62
- package/src/__tests__/edit-propagation.test.ts +56 -2
- package/src/__tests__/empty-state-greeting-cache.test.ts +9 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +15 -110
- package/src/__tests__/guardian-expiry-notifier.test.ts +1 -0
- package/src/__tests__/guardian-question-mode.test.ts +57 -0
- package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -1
- package/src/__tests__/guardian-wait-controller.test.ts +619 -0
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +33 -2
- package/src/__tests__/heic-fixture.ts +55 -0
- package/src/__tests__/helpers/seed-contact-channel.ts +0 -2
- package/src/__tests__/http-conversation-lineage.test.ts +6 -1
- package/src/__tests__/image-conversion.test.ts +170 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +72 -341
- package/src/__tests__/introduction-card-resolver.test.ts +354 -0
- package/src/__tests__/invite-redeemed-ipc.test.ts +97 -0
- package/src/__tests__/invite-routes-http.test.ts +246 -420
- package/src/__tests__/job-handler-registration-guard.test.ts +104 -0
- package/src/__tests__/lexical-index-dual-write.test.ts +426 -0
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +2 -2
- package/src/__tests__/list-messages-attachments.test.ts +159 -0
- package/src/__tests__/list-messages-background-tool-completion.test.ts +141 -0
- package/src/__tests__/list-messages-queued.test.ts +25 -0
- package/src/__tests__/managed-store.test.ts +129 -1
- package/src/__tests__/media-stream-audio-transcode.test.ts +113 -0
- package/src/__tests__/media-stream-output.test.ts +350 -19
- package/src/__tests__/media-stream-server-integration.test.ts +943 -169
- package/src/__tests__/media-stream-stt-session.test.ts +498 -1
- package/src/__tests__/memory-jobs-worker-lanes.test.ts +2 -2
- package/src/__tests__/model-intents.test.ts +1 -1
- package/src/__tests__/mtime-cache.test.ts +19 -0
- package/src/__tests__/non-member-access-request.test.ts +349 -3
- package/src/__tests__/notification-decision-fallback.test.ts +104 -8
- package/src/__tests__/notification-decision-strategy.test.ts +57 -8
- package/src/__tests__/onboarding-template-contract.test.ts +27 -43
- package/src/__tests__/openai-provider.test.ts +33 -0
- package/src/__tests__/openai-responses-provider.test.ts +40 -0
- package/src/__tests__/persistence-layering-guard.test.ts +29 -20
- package/src/__tests__/persona-resolver.test.ts +56 -2
- package/src/__tests__/personal-memory-auth-bypass.test.ts +62 -0
- package/src/__tests__/phone.test.ts +1 -15
- package/src/__tests__/plugin-bootstrap.test.ts +25 -196
- package/src/__tests__/plugin-disabled-state.test.ts +33 -0
- package/src/__tests__/plugin-effective-enabled-set.test.ts +227 -0
- package/src/__tests__/plugin-event-hub-facade.test.ts +323 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +156 -42
- package/src/__tests__/plugin-injector-contribution.test.ts +4 -20
- package/src/__tests__/plugin-pipeline.test.ts +63 -1
- package/src/__tests__/plugin-route-contribution.test.ts +62 -115
- package/src/__tests__/plugin-tool-contribution.test.ts +0 -22
- package/src/__tests__/plugin-types.test.ts +0 -1
- package/src/__tests__/pointer-conversation-trust.test.ts +38 -6
- package/src/__tests__/pre-model-call-sanitize.test.ts +1 -1
- package/src/__tests__/prune-old-tool-invocations-job.test.ts +97 -0
- package/src/__tests__/public-ingress-urls.test.ts +0 -48
- package/src/__tests__/qdrant-collection-migration.test.ts +145 -2
- package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +0 -1
- package/src/__tests__/resolve-trust-class.test.ts +28 -10
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +137 -0
- package/src/__tests__/schedule-retry.test.ts +38 -14
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +3 -3
- package/src/__tests__/schedule-routes.test.ts +14 -6
- package/src/__tests__/schedule-store.test.ts +3 -1
- package/src/__tests__/scheduler-disk-pressure.test.ts +17 -6
- package/src/__tests__/scheduler-recurrence.test.ts +57 -107
- package/src/__tests__/scheduler-reuse-conversation.test.ts +51 -46
- package/src/__tests__/scheduler-wake.test.ts +13 -23
- package/src/__tests__/scheduler-worker-standdown.test.ts +220 -0
- package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
- package/src/__tests__/shell-observability.test.ts +1 -0
- package/src/__tests__/skill-boundary-guard.test.ts +3 -4
- package/src/__tests__/skill-load-plugin-scope.test.ts +238 -0
- package/src/__tests__/skill-projection-feature-flag.test.ts +2 -0
- package/src/__tests__/skill-script-runner-host.test.ts +53 -0
- package/src/__tests__/skill-script-runner-sandbox.test.ts +5 -2
- package/src/__tests__/skill-tool-factory.test.ts +73 -1
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +37 -14
- package/src/__tests__/slack-inbound-verification.test.ts +36 -0
- package/src/__tests__/slack-notification-approval-card.test.ts +89 -2
- package/src/__tests__/slack-reaction-canonical-approval.test.ts +51 -1
- package/src/__tests__/stt-hints.test.ts +2 -256
- package/src/__tests__/subagent-allowlist-validation.test.ts +47 -0
- package/src/__tests__/subagent-call-site-routing.test.ts +72 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +14 -2
- package/src/__tests__/subagent-persistence.test.ts +122 -0
- package/src/__tests__/subagent-spawn-and-await.test.ts +1 -0
- package/src/__tests__/subagent-terminal-message.test.ts +99 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +78 -1
- package/src/__tests__/system-prompt.test.ts +17 -118
- package/src/__tests__/task-scheduler.test.ts +68 -89
- package/src/__tests__/telephony-credential-preflight.test.ts +312 -0
- package/src/__tests__/telephony-tts-guard.test.ts +538 -0
- package/src/__tests__/title-generate-hook.test.ts +16 -0
- package/src/__tests__/tool-approval-handler.test.ts +87 -1
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +45 -20
- package/src/__tests__/tool-grant-request-escalation.test.ts +39 -0
- package/src/__tests__/trust-context-guards.test.ts +21 -10
- package/src/__tests__/trusted-contact-multichannel.test.ts +4 -0
- package/src/__tests__/twilio-provider.test.ts +17 -17
- package/src/__tests__/twilio-routes-twiml.test.ts +6 -543
- package/src/__tests__/twilio-routes.test.ts +257 -387
- package/src/__tests__/twilio-validation.test.ts +1 -1
- package/src/__tests__/unified-turn-context-client-os.test.ts +45 -0
- package/src/__tests__/usage-cache-backfill-migration.test.ts +5 -0
- package/src/__tests__/voice-approval-resolver-relay.test.ts +150 -0
- package/src/__tests__/voice-ingress-preflight.test.ts +61 -0
- package/src/__tests__/voice-quality.test.ts +2 -2
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +1 -7
- package/src/__tests__/voice-session-bridge-processing-wait.test.ts +27 -0
- package/src/__tests__/voice-session-bridge.test.ts +1 -10
- package/src/__tests__/workspace-git-service.test.ts +1 -1
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -0
- package/src/__tests__/workspace-migration-118-seed-now-md.test.ts +120 -0
- package/src/__tests__/workspace-migration-120-revise-onboarding-threads.test.ts +175 -0
- package/src/__tests__/workspace-migration-121-seed-default-user-guardrails.test.ts +118 -0
- package/src/__tests__/workspace-migration-122-relocate-default-user-boundary.test.ts +119 -0
- package/src/__tests__/workspace-migration-123-swap-quality-profile-to-fable.test.ts +191 -0
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +235 -241
- package/src/acp/__tests__/prepare-agent-env.test.ts +0 -97
- package/src/acp/__tests__/session-manager-persistence.test.ts +2 -0
- package/src/acp/__tests__/session-manager-resume.test.ts +8 -0
- package/src/acp/__tests__/session-manager-usage.test.ts +6 -0
- package/src/acp/__tests__/session-manager.test.ts +6 -0
- package/src/acp/agent-process.test.ts +116 -0
- package/src/acp/agent-process.ts +71 -0
- package/src/acp/auto-install.ts +12 -8
- package/src/acp/failure-error.test.ts +109 -0
- package/src/acp/failure-error.ts +111 -0
- package/src/acp/prepare-agent-env.ts +0 -12
- package/src/acp/resolve-agent.test.ts +6 -45
- package/src/acp/resolve-agent.ts +2 -4
- package/src/acp/session-manager.test.ts +312 -0
- package/src/acp/session-manager.ts +102 -46
- package/src/agent/ax-tree-compaction.test.ts +4 -1
- package/src/agent/image-optimize.ts +17 -127
- package/src/agent/loop.ts +45 -192
- package/src/api/events/background-tool-completed.test.ts +51 -0
- package/src/api/events/background-tool-completed.ts +29 -0
- package/src/api/events/background-tool-started.test.ts +36 -0
- package/src/api/events/background-tool-started.ts +28 -0
- package/src/api/events/conversation-error.ts +0 -1
- package/src/api/events/open-panel.ts +33 -0
- package/src/api/events/subagent-status-changed.ts +4 -1
- package/src/api/index.ts +19 -0
- package/src/api/responses/conversation-message.ts +34 -0
- package/src/approvals/guardian-decision-primitive.ts +74 -24
- package/src/approvals/guardian-request-resolvers.ts +450 -122
- package/src/apps/app-store.ts +1 -0
- package/src/apps/shared-app-links-store.ts +1 -0
- package/src/avatar/identity-avatar.ts +3 -7
- package/src/background-wake/wake-intent-hooks.test.ts +1 -1
- package/src/calls/__tests__/fish-audio-client.test.ts +73 -0
- package/src/calls/__tests__/gateway-invite-reader.test.ts +170 -0
- package/src/calls/__tests__/relay-setup-router.test.ts +126 -102
- package/src/calls/call-controller.ts +134 -48
- package/src/calls/call-domain.ts +18 -42
- package/src/calls/call-pointer-messages.ts +51 -67
- package/src/calls/call-setup-flow-types.ts +76 -0
- package/src/calls/call-setup-flow.ts +1743 -0
- package/src/calls/call-speech-output.ts +87 -23
- package/src/calls/call-store.ts +4 -0
- package/src/calls/call-transport.ts +22 -36
- package/src/calls/fish-audio-client.ts +3 -0
- package/src/calls/gateway-invite-reader.ts +98 -0
- package/src/calls/guardian-wait-controller.ts +451 -0
- package/src/calls/media-stream-audio-transcode.ts +82 -2
- package/src/calls/media-stream-output.ts +142 -36
- package/src/calls/media-stream-server.ts +297 -178
- package/src/calls/media-stream-stt-session.ts +393 -50
- package/src/calls/media-turn-detector.ts +4 -6
- package/src/calls/relay-access-wait.ts +3 -4
- package/src/calls/relay-setup-router.ts +22 -43
- package/src/calls/relay-verification.ts +17 -24
- package/src/calls/resolve-call-tts-provider.ts +96 -29
- package/src/calls/stt-hints.ts +24 -87
- package/src/calls/telephony-credential-preflight.ts +159 -0
- package/src/calls/telephony-tts-capability.ts +183 -0
- package/src/calls/tts-call-strategy.ts +18 -20
- package/src/calls/twilio-provider.ts +3 -2
- package/src/calls/twilio-routes.ts +47 -351
- package/src/calls/types.ts +5 -3
- package/src/calls/voice-ingress-preflight.ts +9 -0
- package/src/calls/voice-quality.ts +13 -18
- package/src/calls/voice-session-bridge.ts +25 -35
- package/src/channels/__tests__/types.test.ts +28 -0
- package/src/channels/channel-verification-sessions.ts +7 -2
- package/src/channels/config.ts +0 -42
- package/src/channels/slack-thread-store.ts +12 -0
- package/src/channels/types.ts +23 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -1
- package/src/cli/commands/__tests__/schedules.test.ts +83 -34
- package/src/cli/commands/__tests__/status.test.ts +42 -2
- package/src/cli/commands/contacts.ts +2 -2
- package/src/cli/commands/credentials.ts +17 -0
- package/src/cli/commands/monitoring.ts +191 -0
- package/src/cli/commands/schedules-worker.ts +175 -0
- package/src/cli/commands/schedules.ts +169 -43
- package/src/cli/commands/status.ts +12 -1
- package/src/cli/commands/tools.ts +5 -1
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +1 -0
- package/src/cli/lib/__tests__/search-plugins.test.ts +29 -1
- package/src/cli/lib/__tests__/toggle-plugin.test.ts +7 -0
- package/src/cli/lib/search-plugins.ts +6 -0
- package/src/cli/lib/toggle-plugin.ts +21 -2
- package/src/cli/program.ts +3 -1
- package/src/config/__tests__/deployment-context-defaults.test.ts +23 -0
- package/src/config/acp-defaults.test.ts +0 -10
- package/src/config/acp-defaults.ts +0 -6
- package/src/config/bundled-skills/acp/SKILL.md +13 -45
- package/src/config/bundled-skills/acp/TOOLS.json +3 -3
- package/src/config/bundled-skills/app-builder/SKILL.md +3 -3
- package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +9 -9
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +1 -1
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +1 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -9
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +10 -7
- package/src/config/bundled-skills/skill-management/TOOLS.json +10 -0
- package/src/config/call-site-defaults.ts +0 -2
- package/src/config/feature-flag-registry.json +16 -8
- package/src/config/loader.ts +11 -0
- package/src/config/schema.ts +9 -1
- package/src/config/schemas/__tests__/memory-v2.test.ts +2 -2
- package/src/config/schemas/__tests__/memory-v3.test.ts +64 -0
- package/src/config/schemas/call-site-catalog.ts +0 -12
- package/src/config/schemas/calls.ts +8 -0
- package/src/config/schemas/elevenlabs.ts +1 -1
- package/src/config/schemas/llm.ts +0 -2
- package/src/config/schemas/memory-v2.ts +1 -1
- package/src/config/schemas/memory-v3.ts +82 -0
- package/src/config/schemas/monitoring.ts +61 -0
- package/src/config/schemas/schedules.ts +35 -0
- package/src/config/seed-inference-profiles.ts +2 -2
- package/src/config/skills-plugin-filter.test.ts +96 -0
- package/src/config/skills.ts +26 -0
- package/src/contacts/__tests__/member-write-relay.test.ts +156 -4
- package/src/contacts/canonical-guardian-store.ts +5 -0
- package/src/contacts/contact-store.ts +81 -35
- package/src/contacts/contacts-write.ts +50 -25
- package/src/contacts/member-write-relay.ts +115 -31
- package/src/contacts/types.ts +0 -8
- package/src/context/compactor.ts +29 -15
- package/src/context/outbound-sanitize.ts +202 -0
- package/src/daemon/__tests__/abort-null-controller.test.ts +167 -0
- package/src/daemon/__tests__/conversation-tool-setup-plugin-scope.test.ts +122 -0
- package/src/daemon/__tests__/embedding-reconcile.test.ts +99 -16
- package/src/daemon/__tests__/wake-conversation-ops-completion.test.ts +107 -0
- package/src/daemon/abort-watchdog.ts +7 -0
- package/src/daemon/app-source-watcher.ts +9 -0
- package/src/daemon/assistant-attachments.ts +42 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +256 -147
- package/src/daemon/conversation-agent-loop.ts +61 -68
- package/src/daemon/conversation-error.ts +1 -15
- package/src/daemon/conversation-history.ts +32 -236
- package/src/daemon/conversation-initial-prompt.ts +77 -0
- package/src/daemon/conversation-lifecycle.ts +25 -1
- package/src/daemon/conversation-messaging.ts +2 -2
- package/src/daemon/conversation-plugin-scope.ts +38 -0
- package/src/daemon/conversation-process.ts +50 -14
- package/src/daemon/conversation-runtime-assembly.ts +36 -6
- package/src/daemon/conversation-skill-tools.ts +19 -3
- package/src/daemon/conversation-store.ts +3 -3
- package/src/daemon/conversation-surfaces.ts +171 -0
- package/src/daemon/conversation-tool-setup.test.ts +95 -0
- package/src/daemon/conversation-tool-setup.ts +116 -1
- package/src/daemon/conversation-turn-finalize.ts +211 -0
- package/src/daemon/conversation.ts +92 -1
- package/src/daemon/disk-pressure-policy.ts +2 -1
- package/src/daemon/event-loop-watchdog.ts +22 -6
- package/src/daemon/external-plugins-bootstrap.ts +47 -134
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +1 -1
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +26 -14
- package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +27 -14
- package/src/daemon/handlers/__tests__/config-channels.test.ts +0 -5
- package/src/daemon/handlers/config-a2a.ts +7 -14
- package/src/daemon/handlers/config-channels.ts +2 -10
- package/src/daemon/handlers/conversation-history.ts +3 -1
- package/src/daemon/handlers/conversations.ts +0 -52
- package/src/daemon/handlers/shared.ts +4 -24
- package/src/daemon/handlers/skills.ts +4 -1
- package/src/daemon/host-cu-proxy.ts +1 -1
- package/src/daemon/lifecycle.ts +59 -472
- package/src/daemon/message-protocol.ts +3 -3
- package/src/daemon/message-provenance.ts +12 -14
- package/src/daemon/message-types/background-tools.ts +28 -0
- package/src/daemon/message-types/contacts.ts +2 -10
- package/src/daemon/message-types/conversations.ts +10 -0
- package/src/daemon/message-types/integrations.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +1 -0
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/pointer-conversation-trust.ts +23 -9
- package/src/daemon/pointer-turn-runner.ts +174 -0
- package/src/daemon/providers-setup.ts +16 -0
- package/src/daemon/shutdown-handlers.ts +139 -47
- package/src/daemon/skill-memory-refresh.ts +2 -2
- package/src/daemon/sqlite-corruption-watchdog.test.ts +217 -0
- package/src/daemon/sqlite-corruption-watchdog.ts +155 -0
- package/src/daemon/tool-setup-types.ts +8 -0
- package/src/daemon/trust-context.ts +21 -6
- package/src/daemon/turn-latency-tracker.test.ts +93 -0
- package/src/daemon/turn-latency-tracker.ts +38 -0
- package/src/daemon/wake-conversation-ops.ts +10 -8
- package/src/documents/document-comments-store.test.ts +7 -1
- package/src/documents/document-comments-store.ts +8 -0
- package/src/documents/document-store.ts +15 -0
- package/src/export/__tests__/transcript-formatter.test.ts +11 -0
- package/src/export/transcript-formatter.ts +49 -67
- package/src/home/__tests__/feed-writer.test.ts +247 -1
- package/src/home/conversation-starter-checkpoints.ts +1 -0
- package/src/home/feed-writer.ts +61 -0
- package/src/home/job-handlers/conversation-starters.ts +1 -0
- package/src/hooks/hook-loader.ts +12 -0
- package/src/hooks/registry.ts +32 -4
- package/src/inbound/public-ingress-urls.ts +0 -17
- package/src/ipc/__tests__/contacts-info-ipc.test.ts +285 -0
- package/src/ipc/assistant-server.ts +28 -7
- package/src/ipc/routes/__tests__/contacts-mirror-apply-atomicity.test.ts +62 -0
- package/src/ipc/routes/__tests__/contacts-mirror-apply.test.ts +272 -0
- package/src/ipc/routes/__tests__/contacts-mirror-ipc-routes.test.ts +322 -0
- package/src/ipc/routes/__tests__/invite-ipc-routes.test.ts +60 -4
- package/src/ipc/routes/contacts-info-ipc-routes.ts +239 -0
- package/src/ipc/routes/contacts-mirror-ipc-routes.ts +202 -0
- package/src/ipc/routes/db-proxy.ts +5 -2
- package/src/ipc/routes/invite-ipc-routes.ts +53 -39
- package/src/jobs/worker.ts +9 -2
- package/src/live-voice/__tests__/live-voice-archive.test.ts +10 -1
- package/src/live-voice/live-voice-archive.ts +5 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +22 -6
- package/src/messaging/providers/channel-transport.ts +6 -0
- package/src/messaging/providers/index.ts +3 -0
- package/src/messaging/providers/slack/api.test.ts +49 -1
- package/src/messaging/providers/slack/api.ts +110 -0
- package/src/messaging/providers/slack/send.test.ts +85 -0
- package/src/messaging/providers/slack/send.ts +100 -28
- package/src/messaging/providers/slack/transport.ts +7 -13
- package/src/messaging/providers/whatsapp/send.ts +29 -9
- package/src/monitoring/__tests__/control.test.ts +235 -0
- package/src/monitoring/__tests__/sample-ring-buffer.test.ts +96 -0
- package/src/monitoring/control.ts +105 -0
- package/src/monitoring/resource-sampler.ts +258 -0
- package/src/monitoring/sample-ring-buffer.ts +108 -0
- package/src/monitoring/worker.ts +94 -0
- package/src/notifications/README.md +1 -1
- package/src/notifications/access-request-copy.ts +94 -23
- package/src/notifications/adapters/slack.ts +89 -21
- package/src/notifications/approval-card-builder.ts +33 -18
- package/src/notifications/approval-card-data.ts +48 -15
- package/src/notifications/broadcaster.ts +25 -16
- package/src/notifications/decision-engine.ts +131 -32
- package/src/notifications/guardian-question-mode.ts +58 -10
- package/src/permissions/approval-policy.ts +8 -1
- package/src/permissions/checker.ts +2 -0
- package/src/permissions/gateway-threshold-reader.ts +7 -6
- package/src/permissions/ipc-risk-types.ts +1 -0
- package/src/permissions/secret-prompter.ts +11 -3
- package/src/permissions/types.ts +2 -2
- package/src/persistence/__tests__/conversation-queries-search.test.ts +397 -0
- package/src/persistence/__tests__/conversation-search-lexical.test.ts +103 -0
- package/src/persistence/__tests__/jobs-worker-memory-disabled.test.ts +95 -0
- package/src/persistence/__tests__/slow-query-log.test.ts +266 -0
- package/src/persistence/__tests__/slow-sync-log.test.ts +155 -0
- package/src/persistence/a2a-invite-store.ts +205 -0
- package/src/persistence/attachments-store.ts +94 -10
- package/src/persistence/checkpoints.ts +45 -0
- package/src/persistence/conversation-crud.ts +453 -260
- package/src/persistence/conversation-display-order-migration.ts +8 -2
- package/src/persistence/conversation-group-migration.test.ts +8 -0
- package/src/persistence/conversation-group-migration.ts +9 -0
- package/src/persistence/conversation-queries.ts +244 -125
- package/src/persistence/conversation-row-batch-delete.ts +9 -5
- package/src/persistence/conversation-search-lexical.ts +49 -0
- package/src/persistence/db-async-query.ts +42 -0
- package/src/persistence/db-connection.ts +13 -0
- package/src/persistence/delivery-channels.ts +0 -82
- package/src/persistence/delivery-crud.ts +27 -6
- package/src/persistence/embeddings/__tests__/embedding-identity.test.ts +18 -5
- package/src/persistence/embeddings/__tests__/messages-lexical-index.test.ts +334 -0
- package/src/persistence/embeddings/embedding-identity.ts +17 -5
- package/src/persistence/embeddings/embedding-runtime-manager.ts +31 -0
- package/src/persistence/embeddings/messages-lexical-index.ts +396 -0
- package/src/persistence/embeddings/qdrant-client.ts +81 -5
- package/src/persistence/group-crud.ts +13 -1
- package/src/persistence/job-handlers/__tests__/message-lexical-backfill.test.ts +506 -0
- package/src/persistence/job-handlers/__tests__/message-lexical-enqueue.test.ts +164 -0
- package/src/persistence/job-handlers/__tests__/message-lexical.test.ts +259 -0
- package/src/persistence/job-handlers/cleanup.ts +57 -9
- package/src/persistence/job-handlers/message-lexical-backfill.ts +187 -0
- package/src/persistence/job-handlers/message-lexical.ts +251 -0
- package/src/persistence/jobs-store.ts +102 -8
- package/src/persistence/jobs-worker.ts +68 -28
- package/src/persistence/llm-request-log-store.ts +35 -19
- package/src/persistence/llm-usage-store.ts +11 -0
- package/src/persistence/memory-lifecycle-hooks.test.ts +65 -0
- package/src/persistence/memory-lifecycle-hooks.ts +55 -1
- package/src/persistence/migrations/028-call-session-mode.ts +1 -1
- package/src/persistence/migrations/116-messages-fts.ts +8 -1
- package/src/persistence/migrations/311-create-subagents-table.ts +50 -0
- package/src/persistence/migrations/312-drop-inbox-conversation-state-table.ts +15 -0
- package/src/persistence/migrations/313-drop-messages-fts.ts +17 -0
- package/src/persistence/migrations/314-add-conversation-enabled-plugins.ts +25 -0
- package/src/persistence/migrations/315-create-a2a-invites.ts +58 -0
- package/src/persistence/migrations/316-drop-contact-channels-invite-id.ts +28 -0
- package/src/persistence/migrations/317-canonical-guardian-requester-signals.ts +24 -0
- package/src/persistence/migrations/318-drop-contact-channel-telemetry.ts +32 -0
- package/src/persistence/migrations/__tests__/315-create-a2a-invites.test.ts +161 -0
- package/src/persistence/migrations/__tests__/316-drop-contact-channels-invite-id.test.ts +156 -0
- package/src/persistence/migrations/__tests__/318-drop-contact-channel-telemetry.test.ts +134 -0
- package/src/persistence/migrations/__tests__/migration-prefix-guard.test.ts +99 -0
- package/src/persistence/raw-query.ts +50 -11
- package/src/persistence/schema/a2a.ts +14 -0
- package/src/persistence/schema/contacts.ts +0 -62
- package/src/persistence/schema/conversation-groups.ts +12 -0
- package/src/persistence/schema/conversations.ts +2 -0
- package/src/persistence/schema/documents.ts +65 -0
- package/src/persistence/schema/guardian.ts +4 -0
- package/src/persistence/schema/index.ts +4 -0
- package/src/persistence/schema/memory-injection.ts +62 -0
- package/src/persistence/schema/workflows.ts +55 -0
- package/src/persistence/slow-query-log.ts +437 -0
- package/src/persistence/slow-sync-log.ts +204 -0
- package/src/persistence/steps.ts +16 -0
- package/src/persistence/subagent-store.ts +125 -0
- package/src/persistence/worker-control.ts +45 -238
- package/src/plugin-api/event-hub-facade.ts +172 -0
- package/src/plugin-api/index.ts +9 -1
- package/src/plugin-api/types.ts +40 -9
- package/src/plugins/defaults/default-plugin-names.ts +60 -0
- package/src/plugins/defaults/index.ts +19 -29
- package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +46 -67
- package/src/plugins/defaults/memory/__tests__/db-async-query.test.ts +36 -1
- package/src/plugins/defaults/memory/__tests__/find-most-recent-retrospective-for.test.ts +2 -4
- package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +2 -2
- package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-schedule.test.ts +9 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +7 -4
- package/src/plugins/defaults/memory/config.ts +11 -0
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +1 -1
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +124 -113
- package/src/plugins/defaults/memory/embeddings.ts +77 -0
- package/src/plugins/defaults/memory/find-most-recent-retrospective-for.ts +66 -0
- package/src/plugins/defaults/memory/frontmatter.ts +50 -0
- package/src/plugins/defaults/memory/graph/bootstrap.test.ts +36 -10
- package/src/plugins/defaults/memory/graph/bootstrap.ts +6 -5
- package/src/plugins/defaults/memory/graph/consolidation.ts +3 -5
- package/src/plugins/defaults/memory/graph/extraction.ts +3 -6
- package/src/plugins/defaults/memory/graph/graph-search.ts +12 -23
- package/src/plugins/defaults/memory/graph/narrative.ts +3 -5
- package/src/plugins/defaults/memory/graph/pattern-scan.ts +3 -5
- package/src/plugins/defaults/memory/graph/retriever.test.ts +1 -6
- package/src/plugins/defaults/memory/graph/retriever.ts +5 -10
- package/src/plugins/defaults/memory/graph/tools.ts +1 -1
- package/src/plugins/defaults/memory/hooks/init.ts +31 -5
- package/src/plugins/defaults/memory/indexer.ts +2 -3
- package/src/plugins/defaults/memory/injectors.ts +2 -2
- package/src/plugins/defaults/memory/job-handler-registration.ts +96 -0
- package/src/plugins/defaults/memory/job-handlers/backfill.ts +4 -26
- package/src/plugins/defaults/memory/job-handlers/embedding.test.ts +6 -19
- package/src/plugins/defaults/memory/job-handlers/embedding.ts +9 -22
- package/src/plugins/defaults/memory/job-handlers/index-maintenance.ts +2 -3
- package/src/plugins/defaults/memory/job-handlers.ts +13 -12
- package/src/plugins/defaults/memory/jobs/embed-concept-page.ts +1 -1
- package/src/plugins/defaults/memory/llm-helpers.ts +76 -0
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +8 -5
- package/src/plugins/defaults/memory/memory-retrospective-startup-cleanup.ts +2 -2
- package/src/plugins/defaults/memory/memory-v2-concept-frequency.ts +2 -0
- package/src/plugins/defaults/memory/persistence-hooks.ts +46 -1
- package/src/plugins/defaults/memory/pkb/pkb-index.ts +1 -5
- package/src/plugins/defaults/memory/pkb/pkb-search.ts +2 -2
- package/src/plugins/defaults/memory/routes/__tests__/memory-v2-simulate-route.test.ts +4 -6
- package/src/plugins/defaults/memory/routes/memory-item-routes.ts +1 -1
- package/src/plugins/defaults/memory/search/semantic.ts +2 -2
- package/src/plugins/defaults/memory/startup.ts +244 -0
- package/src/plugins/defaults/memory/task-memory-cleanup.ts +8 -0
- package/src/{daemon → plugins/defaults/memory/v2}/__tests__/memory-v2-startup.test.ts +6 -6
- package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +2 -13
- package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +4 -5
- package/src/plugins/defaults/memory/v2/__tests__/sweep-job.test.ts +1 -8
- package/src/plugins/defaults/memory/v2/activation.ts +2 -4
- package/src/plugins/defaults/memory/v2/cli-command-store.ts +2 -4
- package/src/plugins/defaults/memory/v2/frontmatter-sweep.ts +1 -1
- package/src/{daemon → plugins/defaults/memory/v2}/memory-v2-startup.test.ts +8 -8
- package/src/{daemon → plugins/defaults/memory/v2}/memory-v2-startup.ts +20 -22
- package/src/plugins/defaults/memory/v2/migration.ts +2 -5
- package/src/plugins/defaults/memory/v2/page-store.ts +1 -1
- package/src/plugins/defaults/memory/v2/qdrant.ts +2 -3
- package/src/plugins/defaults/memory/v2/router.ts +5 -5
- package/src/plugins/defaults/memory/v2/sim.ts +2 -4
- package/src/plugins/defaults/memory/v2/skill-store.ts +2 -4
- package/src/plugins/defaults/memory/v2/sweep-job.ts +6 -6
- package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +11 -9
- package/src/plugins/defaults/memory/v3/__tests__/dense.test.ts +107 -2
- package/src/plugins/defaults/memory/v3/__tests__/gate-flag.test.ts +69 -0
- package/src/plugins/defaults/memory/v3/__tests__/gate.test.ts +181 -0
- package/src/plugins/defaults/memory/v3/__tests__/live-integration.test.ts +7 -3
- package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +345 -8
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +1 -4
- package/src/plugins/defaults/memory/v3/__tests__/section-needle.test.ts +64 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +18 -13
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +62 -1
- package/src/plugins/defaults/memory/v3/card.ts +1 -4
- package/src/plugins/defaults/memory/v3/dense.ts +60 -25
- package/src/plugins/defaults/memory/v3/edge.ts +1 -1
- package/src/plugins/defaults/memory/v3/gate-flag.ts +20 -0
- package/src/plugins/defaults/memory/v3/gate.ts +158 -0
- package/src/plugins/defaults/memory/v3/maintain-job.ts +2 -4
- package/src/plugins/defaults/memory/v3/orchestrate.ts +186 -25
- package/src/plugins/defaults/memory/v3/pool-select.test.ts +7 -9
- package/src/plugins/defaults/memory/v3/pool-select.ts +6 -6
- package/src/plugins/defaults/memory/v3/prune.ts +2 -2
- package/src/plugins/defaults/memory/v3/section-dense-store.ts +8 -11
- package/src/plugins/defaults/memory/v3/section-needle.ts +27 -7
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +10 -0
- package/src/plugins/defaults/memory/v3-eval/eval-packets.ts +1 -4
- package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +5 -0
- package/src/plugins/defaults/turn-context/injectors.ts +1 -0
- package/src/plugins/defaults/turn-context/unified-turn-context.ts +12 -0
- package/src/plugins/injector-registry.ts +13 -2
- package/src/plugins/mtime-cache.ts +36 -47
- package/src/plugins/pipeline.ts +15 -1
- package/src/plugins/types.ts +14 -23
- package/src/prompts/__tests__/system-prompt.test.ts +170 -32
- package/src/prompts/persona-resolver.ts +13 -9
- package/src/prompts/system-prompt.ts +30 -9
- package/src/prompts/templates/BOOTSTRAP.md +16 -119
- package/src/prompts/templates/SOUL.md +43 -13
- package/src/prompts/templates/system-sections.ts +32 -27
- package/src/prompts/templates/users/default.md +19 -0
- package/src/providers/__tests__/placeholder-sentinels.test.ts +56 -0
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +96 -1
- package/src/providers/model-intents.ts +2 -2
- package/src/providers/openai/chat-completions-provider.ts +14 -1
- package/src/providers/openai/responses-provider.ts +18 -0
- package/src/providers/placeholder-sentinels.ts +18 -8
- package/src/providers/retry.ts +1 -0
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +0 -64
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +92 -79
- package/src/providers/speech-to-text/deepgram-realtime.test.ts +98 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +62 -3
- package/src/providers/speech-to-text/provider-catalog.ts +0 -89
- package/src/providers/speech-to-text/resolve.ts +55 -0
- package/src/providers/tool-progress-events.test.ts +50 -0
- package/src/providers/tool-progress-events.ts +99 -0
- package/src/providers/types.ts +1 -1
- package/src/providers/vellum-model-routing.test.ts +51 -0
- package/src/providers/vellum-model-routing.ts +84 -0
- package/src/runtime/AGENTS.md +5 -4
- package/src/runtime/__tests__/agent-wake.test.ts +158 -1
- package/src/runtime/__tests__/channel-verification-service.test.ts +2 -2
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +37 -8
- package/src/runtime/access-request-helper.ts +154 -3
- package/src/runtime/actor-trust-resolver.ts +7 -21
- package/src/runtime/agent-wake.ts +41 -1
- package/src/runtime/assistant-event.ts +86 -12
- package/src/runtime/auth/__tests__/route-policy.test.ts +31 -0
- package/src/runtime/channel-approval-types.ts +52 -2
- package/src/runtime/channel-approvals.ts +23 -8
- package/src/runtime/channel-invite-transport.ts +2 -2
- package/src/runtime/channel-invite-transports/voice.ts +1 -1
- package/src/runtime/channel-readiness-service.ts +3 -5
- package/src/runtime/channel-retry-sweep.ts +18 -24
- package/src/runtime/channel-verification-service.ts +9 -11
- package/src/runtime/finalize-event-delivery.ts +30 -16
- package/src/runtime/guardian-action-service.ts +10 -11
- package/src/runtime/guardian-reply-router.ts +122 -21
- package/src/runtime/http-server.ts +44 -101
- package/src/runtime/introduction-policy.test.ts +214 -0
- package/src/runtime/introduction-policy.ts +284 -0
- package/src/runtime/invite-service.ts +104 -361
- package/src/runtime/local-actor-identity.test.ts +38 -0
- package/src/runtime/local-actor-identity.ts +14 -0
- package/src/runtime/middleware/__tests__/rate-limiter.test.ts +22 -0
- package/src/runtime/middleware/rate-limiter.ts +30 -0
- package/src/runtime/middleware/twilio-validation.ts +5 -7
- package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +28 -0
- package/src/runtime/migrations/origin-mode.ts +6 -6
- package/src/runtime/pre-first-message-gate.ts +1 -0
- package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +6 -35
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +24 -3
- package/src/runtime/routes/__tests__/contact-routes.test.ts +80 -15
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +7 -1
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +0 -3
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +142 -1
- package/src/runtime/routes/__tests__/invite-relay-routes.test.ts +119 -30
- package/src/runtime/routes/__tests__/migration-import-allowed-hosts-env.test.ts +70 -0
- package/src/runtime/routes/__tests__/monitoring-routes.test.ts +194 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +755 -29
- package/src/runtime/routes/__tests__/retrospective-routes.test.ts +4 -0
- package/src/runtime/routes/__tests__/schedule-routes-create.test.ts +70 -0
- package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +161 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/surface-content-routes.test.ts +1 -1
- package/src/runtime/routes/acp-routes.ts +15 -3
- package/src/runtime/routes/app-management-routes.ts +4 -7
- package/src/runtime/routes/approval-routes.ts +4 -0
- package/src/runtime/routes/attachment-routes.ts +54 -3
- package/src/runtime/routes/avatar-routes.ts +0 -4
- package/src/runtime/routes/background-tool-routes.ts +34 -5
- package/src/runtime/routes/btw-routes.ts +32 -4
- package/src/runtime/routes/channel-route-shared.ts +18 -14
- package/src/runtime/routes/channel-verification-routes.ts +0 -3
- package/src/runtime/routes/contact-routes.ts +298 -117
- package/src/runtime/routes/conversation-list-routes.ts +6 -0
- package/src/runtime/routes/conversation-management-routes.ts +74 -37
- package/src/runtime/routes/conversation-query-routes.ts +5 -2
- package/src/runtime/routes/conversation-routes.ts +222 -82
- package/src/runtime/routes/conversations-import-routes.ts +8 -1
- package/src/runtime/routes/credential-prompt-routes.ts +20 -5
- package/src/runtime/routes/debug-routes.ts +1 -0
- package/src/runtime/routes/documents-routes.ts +5 -2
- package/src/runtime/routes/empty-state-greeting-cache.ts +37 -10
- package/src/runtime/routes/global-search-routes.ts +2 -1
- package/src/runtime/routes/guardian-approval-interception.ts +51 -0
- package/src/runtime/routes/home-feed-routes.ts +60 -1
- package/src/runtime/routes/identity-routes.ts +4 -87
- package/src/runtime/routes/inbound-message-handler.ts +82 -41
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +430 -57
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +302 -572
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +76 -270
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +21 -116
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +9 -1
- package/src/runtime/routes/inbound-stages/escalation-intercept.test.ts +158 -0
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +22 -1
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +22 -16
- package/src/runtime/routes/internal-twilio-routes.ts +0 -15
- package/src/runtime/routes/messages-lexical-routes.ts +75 -0
- package/src/runtime/routes/migration-routes.ts +69 -6
- package/src/runtime/routes/monitoring-routes.ts +237 -0
- package/src/runtime/routes/plugins-routes.ts +329 -8
- package/src/runtime/routes/schedule-routes.ts +49 -8
- package/src/runtime/routes/schedule-worker-routes.ts +210 -0
- package/src/runtime/routes/secret-routes.ts +1 -1
- package/src/runtime/routes/surface-conversation-resolver.ts +1 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +54 -9
- package/src/runtime/services/__tests__/auto-analysis-enqueue.test.ts +30 -6
- package/src/runtime/services/__tests__/conversation-analyze-job.test.ts +43 -8
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +20 -0
- package/src/runtime/services/analyze-conversation.ts +14 -4
- package/src/runtime/services/conversation-serializer.ts +5 -0
- package/src/runtime/slack-no-response.ts +47 -0
- package/src/runtime/slack-reply-session.test.ts +633 -0
- package/src/runtime/slack-reply-session.ts +382 -0
- package/src/runtime/slack-task-progress.ts +95 -0
- package/src/runtime/sync/resource-sync-events.ts +25 -0
- package/src/runtime/tool-grant-request-helper.ts +17 -1
- package/src/runtime/trust-class.test.ts +141 -0
- package/src/runtime/trust-class.ts +121 -0
- package/src/runtime/trust-verdict-consumer.ts +7 -6
- package/src/schedule/__tests__/run-script.test.ts +21 -0
- package/src/schedule/__tests__/worker-control.test.ts +241 -0
- package/src/schedule/run-script.ts +13 -6
- package/src/schedule/schedule-usage-store.ts +1 -0
- package/src/schedule/scheduler.ts +178 -127
- package/src/schedule/worker-control.ts +118 -0
- package/src/schedule/worker.ts +131 -0
- package/src/skills/__tests__/categories-cache.test.ts +38 -0
- package/src/skills/categories-cache.ts +40 -5
- package/src/skills/managed-store.ts +41 -6
- package/src/skills/validate-input.ts +47 -0
- package/src/stt/types.ts +5 -9
- package/src/subagent/manager.ts +267 -39
- package/src/subagent/types.ts +6 -2
- package/src/subagent/validate-allowlists.ts +65 -0
- package/src/telemetry/watchdog-direct-emit.test.ts +109 -0
- package/src/telemetry/watchdog-direct-emit.ts +89 -0
- package/src/tools/acp/list-agents.test.ts +0 -6
- package/src/tools/background-tool-registry.ts +61 -0
- package/src/tools/document/document-comment-tool.test.ts +4 -0
- package/src/tools/host-terminal/host-shell.test.ts +397 -0
- package/src/tools/host-terminal/host-shell.ts +203 -14
- package/src/tools/skills/find-similar-skills.test.ts +152 -1
- package/src/tools/skills/find-similar-skills.ts +30 -4
- package/src/tools/skills/load.ts +36 -0
- package/src/tools/skills/scaffold-managed.ts +66 -34
- package/src/tools/skills/skill-script-runner.ts +17 -0
- package/src/tools/skills/skill-tool-factory.ts +5 -1
- package/src/tools/terminal/safe-env.ts +3 -0
- package/src/tools/terminal/shell.test.ts +177 -0
- package/src/tools/terminal/shell.ts +98 -4
- package/src/tools/tool-approval-handler.ts +106 -29
- package/{node_modules/@vellumai/skill-host-contracts/src → src/tools}/tool-types.ts +16 -17
- package/src/tools/types.ts +29 -17
- package/src/tools/ui-surface/definitions.ts +3 -1
- package/src/tts/__tests__/provider-adapters.test.ts +37 -0
- package/src/tts/__tests__/provider-catalog.test.ts +21 -0
- package/src/tts/provider-catalog.ts +40 -9
- package/src/tts/providers/fish-audio-provider.ts +19 -10
- package/src/tts/types.ts +3 -4
- package/src/util/__tests__/process-tree.test.ts +10 -2
- package/src/util/__tests__/text-spacing.test.ts +53 -0
- package/src/util/cgroup-memory.ts +142 -0
- package/src/util/image-conversion.ts +281 -0
- package/src/util/phone.ts +1 -8
- package/src/util/platform.ts +33 -0
- package/src/util/process-tree.ts +10 -2
- package/src/util/text-spacing.ts +53 -0
- package/src/util/worker-process.ts +271 -0
- package/src/workflows/journal-store.ts +16 -1
- package/src/workflows/library.test.ts +35 -20
- package/src/workflows/library.ts +105 -66
- package/src/workspace/git-service.ts +1 -0
- package/src/workspace/migrations/011-backfill-installation-id.ts +83 -31
- package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +41 -22
- package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +1 -1
- package/src/workspace/migrations/118-seed-now-md.ts +64 -0
- package/src/workspace/migrations/119-strip-persisted-memory-v3-tuning-defaults.ts +169 -0
- package/src/workspace/migrations/120-revise-onboarding-threads.ts +47 -0
- package/src/workspace/migrations/121-seed-default-user-guardrails.ts +82 -0
- package/src/workspace/migrations/122-relocate-default-user-boundary.ts +111 -0
- package/src/workspace/migrations/123-swap-quality-profile-to-fable.ts +85 -0
- package/src/workspace/migrations/__tests__/119-strip-persisted-memory-v3-tuning-defaults.test.ts +181 -0
- package/src/workspace/migrations/registry.ts +12 -0
- package/drizzle/0000_dizzy_maggott.sql +0 -301
- package/drizzle/meta/0000_snapshot.json +0 -1933
- package/drizzle/meta/_journal.json +0 -13
- package/node_modules/@vellumai/skill-host-contracts/__tests__/client.test.ts +0 -887
- package/node_modules/@vellumai/skill-host-contracts/bun.lock +0 -24
- package/node_modules/@vellumai/skill-host-contracts/package.json +0 -18
- package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -97
- package/node_modules/@vellumai/skill-host-contracts/src/client.ts +0 -1349
- package/node_modules/@vellumai/skill-host-contracts/src/index.ts +0 -6
- package/node_modules/@vellumai/skill-host-contracts/src/runtime-mode.ts +0 -11
- package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +0 -32
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +0 -333
- package/node_modules/@vellumai/skill-host-contracts/tsconfig.json +0 -20
- package/node_modules/@vellumai/skill-host-contracts/tsconfig.test.json +0 -12
- package/src/__tests__/invite-redemption-service.test.ts +0 -1180
- package/src/__tests__/invite-service-ipc.test.ts +0 -350
- package/src/__tests__/job-handler-registry-guard.test.ts +0 -125
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +0 -203
- package/src/__tests__/relay-server.test.ts +0 -6153
- package/src/__tests__/telephony-stt-routing.test.ts +0 -329
- package/src/__tests__/voice-invite-redemption.test.ts +0 -702
- package/src/calls/relay-server.ts +0 -2463
- package/src/calls/telephony-stt-routing.ts +0 -145
- package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +0 -128
- package/src/daemon/__tests__/daemon-skill-host.test.ts +0 -273
- package/src/daemon/daemon-skill-host.ts +0 -262
- package/src/daemon/message-types/meet.ts +0 -143
- package/src/daemon/shutdown-registry.ts +0 -40
- package/src/ipc/__tests__/skill-server-bidirectional.test.ts +0 -253
- package/src/ipc/__tests__/skill-server.test.ts +0 -182
- package/src/ipc/skill-ipc-types.ts +0 -54
- package/src/ipc/skill-routes/__tests__/config.test.ts +0 -146
- package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +0 -448
- package/src/ipc/skill-routes/__tests__/identity.test.ts +0 -62
- package/src/ipc/skill-routes/__tests__/log.test.ts +0 -133
- package/src/ipc/skill-routes/__tests__/memory.test.ts +0 -203
- package/src/ipc/skill-routes/__tests__/platform.test.ts +0 -111
- package/src/ipc/skill-routes/__tests__/providers.test.ts +0 -265
- package/src/ipc/skill-routes/__tests__/registries.test.ts +0 -400
- package/src/ipc/skill-routes/config.ts +0 -47
- package/src/ipc/skill-routes/events.ts +0 -147
- package/src/ipc/skill-routes/identity.ts +0 -21
- package/src/ipc/skill-routes/index.ts +0 -37
- package/src/ipc/skill-routes/log.ts +0 -40
- package/src/ipc/skill-routes/memory.ts +0 -92
- package/src/ipc/skill-routes/platform.ts +0 -39
- package/src/ipc/skill-routes/providers.ts +0 -162
- package/src/ipc/skill-routes/registries.ts +0 -338
- package/src/ipc/skill-server.ts +0 -831
- package/src/ipc/skill-socket-path.ts +0 -20
- package/src/jobs/register-job-handlers.ts +0 -74
- package/src/persistence/invite-store.ts +0 -481
- package/src/plugins/job-handler-registry.ts +0 -94
- package/src/providers/cache-control.ts +0 -26
- package/src/runtime/__tests__/runtime-mode.test.ts +0 -62
- package/src/runtime/invite-redemption-service.ts +0 -828
- package/src/runtime/invite-redemption-templates.ts +0 -46
- package/src/runtime/runtime-mode.ts +0 -40
- package/src/runtime/slack-dm-text-delivery.ts +0 -177
- package/src/util/voice-code.ts +0 -31
|
@@ -6,8 +6,12 @@
|
|
|
6
6
|
* description, version, path; issues omitted when empty)
|
|
7
7
|
* - `?q=` substring filter (case-insensitive across id/name/description)
|
|
8
8
|
* - Trimming + empty-string fallthrough on `?q=`
|
|
9
|
-
* - Empty install dir → `{ plugins: [] }`
|
|
9
|
+
* - Empty install dir → `{ plugins: [], categoryCounts: {}, totalCount: 0 }`
|
|
10
10
|
* - Issues array surfaced when present
|
|
11
|
+
* - `category` resolved from the marketplace catalog (null when unknown)
|
|
12
|
+
* - `categoryCounts` / `totalCount` computed before the `?category=` filter
|
|
13
|
+
* - `?category=` filters the list while counts stay unfiltered
|
|
14
|
+
* - A catalog fetch failure degrades `category` to null without erroring
|
|
11
15
|
*
|
|
12
16
|
* GET /v1/plugins/search (catalog search):
|
|
13
17
|
* - Forwards `?q=` and `?ref=` to the `searchPlugins` lib
|
|
@@ -22,10 +26,24 @@
|
|
|
22
26
|
* DELETE /v1/plugins/:name (uninstall):
|
|
23
27
|
* - Forwards `pathParams.name` to the `uninstallPlugin` lib
|
|
24
28
|
* - Returns `{ name, target }` mirroring the lib's `UninstallPluginResult`
|
|
29
|
+
* - Publishes `sync_changed(plugins:list)` on success (threading the
|
|
30
|
+
* `x-vellum-client-id` origin); no broadcast on an error path
|
|
25
31
|
* - Maps `InvalidPluginNameError` → BadRequestError (400)
|
|
26
32
|
* - Maps `PluginNotInstalledError` → NotFoundError (404)
|
|
27
33
|
* - Maps unknown errors → InternalError (500) with message preserved
|
|
28
34
|
*
|
|
35
|
+
* POST /v1/plugins/:name/enable | disable (toggle):
|
|
36
|
+
* - Forwards `pathParams.name` to the `enablePlugin` / `disablePlugin` lib
|
|
37
|
+
* - Returns `{ ok: true }` and publishes a `sync_changed` carrying the
|
|
38
|
+
* `plugins:list` tag via the canonical resource-sync publisher (enable and
|
|
39
|
+
* disable emit the SAME invalidation)
|
|
40
|
+
* - Threads `x-vellum-client-id` into the published event's `originClientId`
|
|
41
|
+
* - A broadcast failure does not fail a successful toggle (the publisher
|
|
42
|
+
* swallows hub errors)
|
|
43
|
+
* - Maps `InvalidPluginNameError` → BadRequestError (400)
|
|
44
|
+
* - Maps `PluginDirectoryNotFoundError` → NotFoundError (404)
|
|
45
|
+
* - Maps `PluginAlreadyInStateException` → ConflictError (409); no broadcast
|
|
46
|
+
*
|
|
29
47
|
* The library functions themselves are covered by
|
|
30
48
|
* `assistant/src/cli/lib/__tests__/list-installed-plugins.test.ts`,
|
|
31
49
|
* `.../search-plugins.test.ts`, and `.../uninstall-plugin.test.ts`;
|
|
@@ -71,6 +89,12 @@ import type {
|
|
|
71
89
|
SearchPluginsDeps,
|
|
72
90
|
} from "../../../cli/lib/search-plugins.js";
|
|
73
91
|
import { PluginCatalogUnavailableError } from "../../../cli/lib/search-plugins.js";
|
|
92
|
+
import {
|
|
93
|
+
InvalidPluginNameError as ToggleInvalidPluginNameError,
|
|
94
|
+
PluginAlreadyInStateException,
|
|
95
|
+
PluginDirectoryNotFoundError,
|
|
96
|
+
type TogglePluginResult,
|
|
97
|
+
} from "../../../cli/lib/toggle-plugin.js";
|
|
74
98
|
import {
|
|
75
99
|
PluginNotInstalledError,
|
|
76
100
|
type UninstallPluginOptions,
|
|
@@ -92,6 +116,16 @@ mock.module("../../../cli/lib/list-installed-plugins.js", () => ({
|
|
|
92
116
|
listInstalledPlugins: () => installedFixture,
|
|
93
117
|
}));
|
|
94
118
|
|
|
119
|
+
// Set of plugin dir names carrying a `.disabled` sentinel. Tests populate it to
|
|
120
|
+
// mark a plugin disabled; the real check reads the workspace filesystem, so we
|
|
121
|
+
// substitute an in-memory set to keep the route's `enabled` projection
|
|
122
|
+
// deterministic and decoupled from disk.
|
|
123
|
+
const disabledFixture = new Set<string>();
|
|
124
|
+
|
|
125
|
+
mock.module("../../../plugins/disabled-state.js", () => ({
|
|
126
|
+
isPluginDisabled: (name: string) => disabledFixture.has(name),
|
|
127
|
+
}));
|
|
128
|
+
|
|
95
129
|
// Mock the catalog cache: `getCatalogSpy` records every invocation and
|
|
96
130
|
// returns the (unfiltered) catalog the route then filters in memory via the
|
|
97
131
|
// real `filterPluginCatalog`. The real `search-plugins.js` module is left
|
|
@@ -228,6 +262,60 @@ mock.module("../../../cli/lib/plugin-pin-history.js", () => ({
|
|
|
228
262
|
resolvePinToMarketplaceCommit: resolvePinSpy,
|
|
229
263
|
}));
|
|
230
264
|
|
|
265
|
+
// Mock the toggle-plugin lib. `enablePlugin` / `disablePlugin` flip a
|
|
266
|
+
// `.disabled` sentinel on disk in production; here we spy on them so the
|
|
267
|
+
// route's broadcast + error mapping is the wiring under test. The error
|
|
268
|
+
// classes pass through real so the handler's `instanceof` checks resolve to
|
|
269
|
+
// the same classes the spies throw.
|
|
270
|
+
const enablePluginSpy = mock((_name: string): TogglePluginResult => {
|
|
271
|
+
throw new Error("enablePluginSpy default impl not configured");
|
|
272
|
+
});
|
|
273
|
+
const disablePluginSpy = mock((_name: string): TogglePluginResult => {
|
|
274
|
+
throw new Error("disablePluginSpy default impl not configured");
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
mock.module("../../../cli/lib/toggle-plugin.js", () => ({
|
|
278
|
+
InvalidPluginNameError: ToggleInvalidPluginNameError,
|
|
279
|
+
PluginAlreadyInStateException,
|
|
280
|
+
PluginDirectoryNotFoundError,
|
|
281
|
+
disablePlugin: disablePluginSpy,
|
|
282
|
+
enablePlugin: enablePluginSpy,
|
|
283
|
+
}));
|
|
284
|
+
|
|
285
|
+
// Spy on broadcastMessage so we can assert the sync_changed invalidation the
|
|
286
|
+
// enable/disable handlers emit. The handlers publish through the canonical
|
|
287
|
+
// `publishPluginsChanged` → `publishSyncInvalidation` path (both left real), so
|
|
288
|
+
// the spy receives the actual `{ type: "sync_changed", tags: [...],
|
|
289
|
+
// originClientId? }` payload the publisher builds.
|
|
290
|
+
const broadcastMessageSpy = mock((_msg: unknown): void => {});
|
|
291
|
+
|
|
292
|
+
mock.module("../../assistant-event-hub.js", () => ({
|
|
293
|
+
broadcastMessage: broadcastMessageSpy,
|
|
294
|
+
}));
|
|
295
|
+
|
|
296
|
+
// Make the valid-slug source deterministic: the real `getLocalCategorySlugs`
|
|
297
|
+
// reads the bundled YAML (absent in the test sandbox), so we pin it to the
|
|
298
|
+
// authoritative Skills taxonomy. This decouples the route's category
|
|
299
|
+
// normalization from the filesystem / network.
|
|
300
|
+
const SKILLS_CATEGORY_SLUGS = new Set([
|
|
301
|
+
"email",
|
|
302
|
+
"calendar",
|
|
303
|
+
"messaging",
|
|
304
|
+
"browsing",
|
|
305
|
+
"productivity",
|
|
306
|
+
"development",
|
|
307
|
+
"voice",
|
|
308
|
+
"commerce",
|
|
309
|
+
"content",
|
|
310
|
+
"health",
|
|
311
|
+
"system",
|
|
312
|
+
"integrations",
|
|
313
|
+
]);
|
|
314
|
+
|
|
315
|
+
mock.module("../../../skills/categories-cache.js", () => ({
|
|
316
|
+
getLocalCategorySlugs: () => SKILLS_CATEGORY_SLUGS,
|
|
317
|
+
}));
|
|
318
|
+
|
|
231
319
|
import {
|
|
232
320
|
BadRequestError,
|
|
233
321
|
ConflictError,
|
|
@@ -235,7 +323,11 @@ import {
|
|
|
235
323
|
NotFoundError,
|
|
236
324
|
ServiceUnavailableError,
|
|
237
325
|
} from "../errors.js";
|
|
238
|
-
import {
|
|
326
|
+
import {
|
|
327
|
+
loadCategoryMapBounded,
|
|
328
|
+
normalizeMarketplaceCategory,
|
|
329
|
+
ROUTES as PLUGINS_ROUTES,
|
|
330
|
+
} from "../plugins-routes.js";
|
|
239
331
|
import type { RouteDefinition, RouteHandlerArgs } from "../types.js";
|
|
240
332
|
|
|
241
333
|
function findHandler(operationId: string): RouteDefinition["handler"] {
|
|
@@ -253,13 +345,22 @@ const inspectHandler = findHandler("plugins_inspect");
|
|
|
253
345
|
const versionsHandler = findHandler("plugins_versions");
|
|
254
346
|
const upgradeHandler = findHandler("plugins_upgrade");
|
|
255
347
|
const diffHandler = findHandler("plugins_diff");
|
|
348
|
+
const enableHandler = findHandler("plugins_enable");
|
|
349
|
+
const disableHandler = findHandler("plugins_disable");
|
|
256
350
|
|
|
257
|
-
function invoke(args: RouteHandlerArgs = {}): {
|
|
351
|
+
async function invoke(args: RouteHandlerArgs = {}): Promise<{
|
|
258
352
|
plugins: Array<Record<string, unknown>>;
|
|
259
|
-
|
|
260
|
-
|
|
353
|
+
categoryCounts: Record<string, number>;
|
|
354
|
+
totalCount: number;
|
|
355
|
+
}> {
|
|
356
|
+
return (await listHandler(args)) as {
|
|
357
|
+
plugins: Array<Record<string, unknown>>;
|
|
358
|
+
categoryCounts: Record<string, number>;
|
|
359
|
+
totalCount: number;
|
|
360
|
+
};
|
|
261
361
|
}
|
|
262
362
|
|
|
363
|
+
// The route wire shape mirrors the lib match — including `category`.
|
|
263
364
|
async function invokeSearch(args: RouteHandlerArgs = {}): Promise<{
|
|
264
365
|
query: string;
|
|
265
366
|
ref: string;
|
|
@@ -285,14 +386,29 @@ function pluginEntry(
|
|
|
285
386
|
|
|
286
387
|
beforeEach(() => {
|
|
287
388
|
installedFixture = [];
|
|
389
|
+
disabledFixture.clear();
|
|
288
390
|
});
|
|
289
391
|
|
|
290
392
|
describe("GET /v1/plugins", () => {
|
|
291
|
-
|
|
292
|
-
|
|
393
|
+
beforeEach(() => {
|
|
394
|
+
getCatalogSpy.mockClear();
|
|
395
|
+
// Default to an empty catalog so every installed plugin's category resolves
|
|
396
|
+
// to `null` (bucketed under "system"). Tests that exercise real categories
|
|
397
|
+
// override this.
|
|
398
|
+
getCatalogSpy.mockImplementation(async (ref) => catalog(ref, []));
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test("returns an empty list (with empty counts) when nothing is installed", async () => {
|
|
402
|
+
expect(await invoke()).toEqual({
|
|
403
|
+
plugins: [],
|
|
404
|
+
categoryCounts: {},
|
|
405
|
+
totalCount: 0,
|
|
406
|
+
});
|
|
407
|
+
// Nothing to categorize → the network-bound catalog lookup is skipped.
|
|
408
|
+
expect(getCatalogSpy).not.toHaveBeenCalled();
|
|
293
409
|
});
|
|
294
410
|
|
|
295
|
-
test("projects InstalledPluginInfo → response shape with all fields populated", () => {
|
|
411
|
+
test("projects InstalledPluginInfo → response shape with all fields populated", async () => {
|
|
296
412
|
installedFixture = [
|
|
297
413
|
pluginEntry({
|
|
298
414
|
name: "alpha",
|
|
@@ -305,20 +421,46 @@ describe("GET /v1/plugins", () => {
|
|
|
305
421
|
}),
|
|
306
422
|
];
|
|
307
423
|
|
|
308
|
-
const result = invoke();
|
|
424
|
+
const result = await invoke();
|
|
309
425
|
expect(result.plugins).toHaveLength(1);
|
|
426
|
+
// No catalog entry for `alpha`, so its category is null.
|
|
310
427
|
expect(result.plugins[0]).toEqual({
|
|
311
428
|
id: "alpha",
|
|
312
429
|
name: "alpha",
|
|
430
|
+
// No `.disabled` sentinel → the plugin is enabled.
|
|
431
|
+
enabled: true,
|
|
313
432
|
description: "Alpha plugin",
|
|
314
433
|
version: "1.2.3",
|
|
315
434
|
path: "/workspace/plugins/alpha",
|
|
435
|
+
category: null,
|
|
316
436
|
});
|
|
317
437
|
// `issues` is omitted (not just undefined) when the entry is clean.
|
|
318
438
|
expect("issues" in result.plugins[0]!).toBe(false);
|
|
319
439
|
});
|
|
320
440
|
|
|
321
|
-
test("
|
|
441
|
+
test("surfaces a marketplace category when the catalog declares one", async () => {
|
|
442
|
+
getCatalogSpy.mockImplementation(async (ref) =>
|
|
443
|
+
catalog(ref, [
|
|
444
|
+
{
|
|
445
|
+
name: "alpha",
|
|
446
|
+
path: "github:acme/alpha@v1",
|
|
447
|
+
category: "productivity",
|
|
448
|
+
source: { kind: "github", repo: "acme/alpha", ref: "v1" },
|
|
449
|
+
},
|
|
450
|
+
]),
|
|
451
|
+
);
|
|
452
|
+
installedFixture = [
|
|
453
|
+
pluginEntry({
|
|
454
|
+
name: "alpha",
|
|
455
|
+
packageJson: { name: "alpha", version: "1.2.3" },
|
|
456
|
+
}),
|
|
457
|
+
];
|
|
458
|
+
|
|
459
|
+
const [entry] = (await invoke()).plugins;
|
|
460
|
+
expect(entry?.category).toBe("productivity");
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
test("uses directory name for `id` and `name` even when package.json#name is scoped", async () => {
|
|
322
464
|
installedFixture = [
|
|
323
465
|
pluginEntry({
|
|
324
466
|
name: "fancy-plugin",
|
|
@@ -330,12 +472,12 @@ describe("GET /v1/plugins", () => {
|
|
|
330
472
|
}),
|
|
331
473
|
];
|
|
332
474
|
|
|
333
|
-
const [entry] = invoke().plugins;
|
|
475
|
+
const [entry] = (await invoke()).plugins;
|
|
334
476
|
expect(entry?.id).toBe("fancy-plugin");
|
|
335
477
|
expect(entry?.name).toBe("fancy-plugin");
|
|
336
478
|
});
|
|
337
479
|
|
|
338
|
-
test("nulls description and version when package.json is missing or partial", () => {
|
|
480
|
+
test("nulls description and version when package.json is missing or partial", async () => {
|
|
339
481
|
installedFixture = [
|
|
340
482
|
pluginEntry({ name: "no-pkg-json", packageJson: null }),
|
|
341
483
|
pluginEntry({
|
|
@@ -344,7 +486,7 @@ describe("GET /v1/plugins", () => {
|
|
|
344
486
|
}),
|
|
345
487
|
];
|
|
346
488
|
|
|
347
|
-
const [missing, partial] = invoke().plugins;
|
|
489
|
+
const [missing, partial] = (await invoke()).plugins;
|
|
348
490
|
expect(missing).toMatchObject({
|
|
349
491
|
id: "no-pkg-json",
|
|
350
492
|
description: null,
|
|
@@ -357,7 +499,7 @@ describe("GET /v1/plugins", () => {
|
|
|
357
499
|
});
|
|
358
500
|
});
|
|
359
501
|
|
|
360
|
-
test("surfaces non-fatal issues array when present", () => {
|
|
502
|
+
test("surfaces non-fatal issues array when present", async () => {
|
|
361
503
|
installedFixture = [
|
|
362
504
|
pluginEntry({
|
|
363
505
|
name: "broken",
|
|
@@ -366,11 +508,26 @@ describe("GET /v1/plugins", () => {
|
|
|
366
508
|
}),
|
|
367
509
|
];
|
|
368
510
|
|
|
369
|
-
const [entry] = invoke().plugins;
|
|
511
|
+
const [entry] = (await invoke()).plugins;
|
|
370
512
|
expect(entry?.issues).toEqual(["missing package.json"]);
|
|
371
513
|
});
|
|
372
514
|
|
|
373
|
-
test("
|
|
515
|
+
test("reports enabled: false for a plugin with a `.disabled` sentinel, true otherwise", async () => {
|
|
516
|
+
installedFixture = [
|
|
517
|
+
pluginEntry({ name: "off" }),
|
|
518
|
+
pluginEntry({ name: "on" }),
|
|
519
|
+
];
|
|
520
|
+
// Only `off` carries the sentinel; `on` has none.
|
|
521
|
+
disabledFixture.add("off");
|
|
522
|
+
|
|
523
|
+
const byId = new Map(
|
|
524
|
+
(await invoke()).plugins.map((p) => [p.id, p.enabled]),
|
|
525
|
+
);
|
|
526
|
+
expect(byId.get("off")).toBe(false);
|
|
527
|
+
expect(byId.get("on")).toBe(true);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
test("?q= filters case-insensitively on id, name, and description", async () => {
|
|
374
531
|
installedFixture = [
|
|
375
532
|
pluginEntry({
|
|
376
533
|
name: "calendar-sync",
|
|
@@ -400,49 +557,307 @@ describe("GET /v1/plugins", () => {
|
|
|
400
557
|
|
|
401
558
|
// id match
|
|
402
559
|
expect(
|
|
403
|
-
invoke({ queryParams: { q: "calendar" } }).plugins.map(
|
|
560
|
+
(await invoke({ queryParams: { q: "calendar" } })).plugins.map(
|
|
561
|
+
(p) => p.id,
|
|
562
|
+
),
|
|
404
563
|
).toEqual(["calendar-sync"]);
|
|
405
564
|
|
|
406
565
|
// description match (case-insensitive)
|
|
407
566
|
expect(
|
|
408
|
-
invoke({ queryParams: { q: "GOOGLE" } }).plugins.map((p) => p.id),
|
|
567
|
+
(await invoke({ queryParams: { q: "GOOGLE" } })).plugins.map((p) => p.id),
|
|
409
568
|
).toEqual(["calendar-sync"]);
|
|
410
569
|
|
|
411
570
|
// matches multiple
|
|
412
571
|
expect(
|
|
413
|
-
invoke({ queryParams: { q: "o" } })
|
|
414
|
-
.
|
|
572
|
+
(await invoke({ queryParams: { q: "o" } })).plugins
|
|
573
|
+
.map((p) => p.id)
|
|
415
574
|
.sort(),
|
|
416
575
|
).toEqual(["calendar-sync", "todo", "weather"].sort());
|
|
417
576
|
|
|
418
577
|
// no match
|
|
419
|
-
expect(invoke({ queryParams: { q: "zzz" } }).plugins).toEqual([]);
|
|
578
|
+
expect((await invoke({ queryParams: { q: "zzz" } })).plugins).toEqual([]);
|
|
420
579
|
});
|
|
421
580
|
|
|
422
|
-
test("?q= is trimmed; whitespace-only treated as no filter", () => {
|
|
581
|
+
test("?q= is trimmed; whitespace-only treated as no filter", async () => {
|
|
423
582
|
installedFixture = [
|
|
424
583
|
pluginEntry({ name: "alpha" }),
|
|
425
584
|
pluginEntry({ name: "beta" }),
|
|
426
585
|
];
|
|
427
586
|
|
|
428
587
|
expect(
|
|
429
|
-
invoke({ queryParams: { q: " " } }).plugins.map((p) => p.id),
|
|
588
|
+
(await invoke({ queryParams: { q: " " } })).plugins.map((p) => p.id),
|
|
430
589
|
).toEqual(["alpha", "beta"]);
|
|
431
590
|
});
|
|
432
591
|
|
|
433
|
-
test("preserves the order returned by listInstalledPlugins", () => {
|
|
592
|
+
test("preserves the order returned by listInstalledPlugins", async () => {
|
|
434
593
|
installedFixture = [
|
|
435
594
|
pluginEntry({ name: "alpha" }),
|
|
436
595
|
pluginEntry({ name: "beta" }),
|
|
437
596
|
pluginEntry({ name: "zeta" }),
|
|
438
597
|
];
|
|
439
598
|
|
|
440
|
-
expect(invoke().plugins.map((p) => p.id)).toEqual([
|
|
599
|
+
expect((await invoke()).plugins.map((p) => p.id)).toEqual([
|
|
441
600
|
"alpha",
|
|
442
601
|
"beta",
|
|
443
602
|
"zeta",
|
|
444
603
|
]);
|
|
445
604
|
});
|
|
605
|
+
|
|
606
|
+
test('reports categoryCounts + totalCount, bucketing unknown plugins under "system"', async () => {
|
|
607
|
+
getCatalogSpy.mockImplementation(async (ref) =>
|
|
608
|
+
catalog(ref, [
|
|
609
|
+
{
|
|
610
|
+
name: "calendar-sync",
|
|
611
|
+
path: "github:acme/calendar-sync@v1",
|
|
612
|
+
category: "calendar",
|
|
613
|
+
source: { kind: "github", repo: "acme/calendar-sync", ref: "v1" },
|
|
614
|
+
},
|
|
615
|
+
]),
|
|
616
|
+
);
|
|
617
|
+
installedFixture = [
|
|
618
|
+
pluginEntry({ name: "calendar-sync" }),
|
|
619
|
+
// Not in the catalog → category null → counted under "system".
|
|
620
|
+
pluginEntry({ name: "mystery" }),
|
|
621
|
+
];
|
|
622
|
+
|
|
623
|
+
const result = await invoke();
|
|
624
|
+
expect(result.totalCount).toBe(2);
|
|
625
|
+
expect(result.categoryCounts).toEqual({ calendar: 1, system: 1 });
|
|
626
|
+
expect(result.plugins.find((p) => p.id === "calendar-sync")?.category).toBe(
|
|
627
|
+
"calendar",
|
|
628
|
+
);
|
|
629
|
+
expect(result.plugins.find((p) => p.id === "mystery")?.category).toBeNull();
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
test("?category= filters the list while categoryCounts stays unfiltered", async () => {
|
|
633
|
+
getCatalogSpy.mockImplementation(async (ref) =>
|
|
634
|
+
catalog(ref, [
|
|
635
|
+
{
|
|
636
|
+
name: "notes",
|
|
637
|
+
path: "github:acme/notes@v1",
|
|
638
|
+
category: "productivity",
|
|
639
|
+
source: { kind: "github", repo: "acme/notes", ref: "v1" },
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
name: "inbox",
|
|
643
|
+
path: "github:acme/inbox@v1",
|
|
644
|
+
category: "email",
|
|
645
|
+
source: { kind: "github", repo: "acme/inbox", ref: "v1" },
|
|
646
|
+
},
|
|
647
|
+
]),
|
|
648
|
+
);
|
|
649
|
+
installedFixture = [
|
|
650
|
+
pluginEntry({ name: "notes" }),
|
|
651
|
+
pluginEntry({ name: "inbox" }),
|
|
652
|
+
// No catalog entry → "system".
|
|
653
|
+
pluginEntry({ name: "loose" }),
|
|
654
|
+
];
|
|
655
|
+
|
|
656
|
+
const result = await invoke({ queryParams: { category: "productivity" } });
|
|
657
|
+
// The list is filtered to the productivity bucket...
|
|
658
|
+
expect(result.plugins.map((p) => p.id)).toEqual(["notes"]);
|
|
659
|
+
// ...but the badge counts reflect the unfiltered totals.
|
|
660
|
+
expect(result.categoryCounts).toEqual({
|
|
661
|
+
productivity: 1,
|
|
662
|
+
email: 1,
|
|
663
|
+
system: 1,
|
|
664
|
+
});
|
|
665
|
+
expect(result.totalCount).toBe(3);
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
test("degrades to category: null without failing when the catalog fetch throws", async () => {
|
|
669
|
+
getCatalogSpy.mockImplementation(async () => {
|
|
670
|
+
throw new PluginCatalogUnavailableError("HTTP 403", 403);
|
|
671
|
+
});
|
|
672
|
+
installedFixture = [
|
|
673
|
+
pluginEntry({ name: "alpha" }),
|
|
674
|
+
pluginEntry({ name: "beta" }),
|
|
675
|
+
];
|
|
676
|
+
|
|
677
|
+
const result = await invoke();
|
|
678
|
+
expect(result.plugins.map((p) => p.category)).toEqual([null, null]);
|
|
679
|
+
expect(result.categoryCounts).toEqual({ system: 2 });
|
|
680
|
+
expect(result.totalCount).toBe(2);
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
// The category lookup is bounded so a slow/hanging marketplace fetch (a cold
|
|
684
|
+
// cache stuck on GitHub) can't hold up the installed list — it degrades to an
|
|
685
|
+
// empty map exactly like the rejection path above. `loadCategoryMapBounded`
|
|
686
|
+
// takes an injectable timeout so we can prove the bound without waiting the
|
|
687
|
+
// full 1500ms production budget.
|
|
688
|
+
test("bounds the catalog lookup: a stall past the budget degrades to an empty map", async () => {
|
|
689
|
+
// GIVEN a catalog fetch that resolves only AFTER the (shortened) budget.
|
|
690
|
+
getCatalogSpy.mockImplementation(
|
|
691
|
+
(ref) =>
|
|
692
|
+
new Promise<PluginCatalog>((resolve) => {
|
|
693
|
+
setTimeout(
|
|
694
|
+
() =>
|
|
695
|
+
resolve(
|
|
696
|
+
catalog(ref, [
|
|
697
|
+
{
|
|
698
|
+
name: "alpha",
|
|
699
|
+
path: "github:acme/alpha@v1",
|
|
700
|
+
category: "productivity",
|
|
701
|
+
source: { kind: "github", repo: "acme/alpha", ref: "v1" },
|
|
702
|
+
},
|
|
703
|
+
]),
|
|
704
|
+
),
|
|
705
|
+
80,
|
|
706
|
+
);
|
|
707
|
+
}),
|
|
708
|
+
);
|
|
709
|
+
|
|
710
|
+
// WHEN the bound (10ms) elapses first, the timer wins the race.
|
|
711
|
+
const map = await loadCategoryMapBounded(10);
|
|
712
|
+
|
|
713
|
+
// THEN we fall back to an empty map, so every category resolves to null and
|
|
714
|
+
// the installed list returns immediately instead of blocking on GitHub.
|
|
715
|
+
expect(map.size).toBe(0);
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
test("returns the catalog category map when the lookup resolves within the budget", async () => {
|
|
719
|
+
getCatalogSpy.mockImplementation(async (ref) =>
|
|
720
|
+
catalog(ref, [
|
|
721
|
+
{
|
|
722
|
+
name: "alpha",
|
|
723
|
+
path: "github:acme/alpha@v1",
|
|
724
|
+
category: "productivity",
|
|
725
|
+
source: { kind: "github", repo: "acme/alpha", ref: "v1" },
|
|
726
|
+
},
|
|
727
|
+
]),
|
|
728
|
+
);
|
|
729
|
+
|
|
730
|
+
const map = await loadCategoryMapBounded();
|
|
731
|
+
expect(map.get("alpha")).toBe("productivity");
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
test("normalizes a `developer` marketplace category to `development`", async () => {
|
|
735
|
+
// The marketplace ships `developer`, which is NOT a Skills slug; without
|
|
736
|
+
// normalization it would be counted in "All" but have no rail row.
|
|
737
|
+
getCatalogSpy.mockImplementation(async (ref) =>
|
|
738
|
+
catalog(ref, [
|
|
739
|
+
{
|
|
740
|
+
name: "dev-tools",
|
|
741
|
+
path: "github:acme/dev-tools@v1",
|
|
742
|
+
category: "developer",
|
|
743
|
+
source: { kind: "github", repo: "acme/dev-tools", ref: "v1" },
|
|
744
|
+
},
|
|
745
|
+
]),
|
|
746
|
+
);
|
|
747
|
+
installedFixture = [pluginEntry({ name: "dev-tools" })];
|
|
748
|
+
|
|
749
|
+
const result = await invoke();
|
|
750
|
+
expect(result.plugins[0]?.category).toBe("development");
|
|
751
|
+
expect(result.categoryCounts).toEqual({ development: 1 });
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
test("folds an unknown marketplace category (`memory`) to null → system", async () => {
|
|
755
|
+
getCatalogSpy.mockImplementation(async (ref) =>
|
|
756
|
+
catalog(ref, [
|
|
757
|
+
{
|
|
758
|
+
name: "simple-memory",
|
|
759
|
+
path: "github:acme/simple-memory@v1",
|
|
760
|
+
category: "memory",
|
|
761
|
+
source: { kind: "github", repo: "acme/simple-memory", ref: "v1" },
|
|
762
|
+
},
|
|
763
|
+
]),
|
|
764
|
+
);
|
|
765
|
+
installedFixture = [pluginEntry({ name: "simple-memory" })];
|
|
766
|
+
|
|
767
|
+
const result = await invoke();
|
|
768
|
+
expect(result.plugins[0]?.category).toBeNull();
|
|
769
|
+
expect(result.categoryCounts).toEqual({ system: 1 });
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
test("?category=development selects a `developer`-origin plugin (post-normalization)", async () => {
|
|
773
|
+
getCatalogSpy.mockImplementation(async (ref) =>
|
|
774
|
+
catalog(ref, [
|
|
775
|
+
{
|
|
776
|
+
name: "dev-tools",
|
|
777
|
+
path: "github:acme/dev-tools@v1",
|
|
778
|
+
category: "developer",
|
|
779
|
+
source: { kind: "github", repo: "acme/dev-tools", ref: "v1" },
|
|
780
|
+
},
|
|
781
|
+
]),
|
|
782
|
+
);
|
|
783
|
+
installedFixture = [pluginEntry({ name: "dev-tools" })];
|
|
784
|
+
|
|
785
|
+
const result = await invoke({ queryParams: { category: "development" } });
|
|
786
|
+
expect(result.plugins.map((p) => p.id)).toEqual(["dev-tools"]);
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
test("?category=memory selects nothing — there is no such Skills slug", async () => {
|
|
790
|
+
getCatalogSpy.mockImplementation(async (ref) =>
|
|
791
|
+
catalog(ref, [
|
|
792
|
+
{
|
|
793
|
+
name: "simple-memory",
|
|
794
|
+
path: "github:acme/simple-memory@v1",
|
|
795
|
+
category: "memory",
|
|
796
|
+
source: { kind: "github", repo: "acme/simple-memory", ref: "v1" },
|
|
797
|
+
},
|
|
798
|
+
]),
|
|
799
|
+
);
|
|
800
|
+
installedFixture = [pluginEntry({ name: "simple-memory" })];
|
|
801
|
+
|
|
802
|
+
const result = await invoke({ queryParams: { category: "memory" } });
|
|
803
|
+
expect(result.plugins).toEqual([]);
|
|
804
|
+
// The plugin is still counted (under "system"), just not reachable as
|
|
805
|
+
// "memory" — counts match visible rows.
|
|
806
|
+
expect(result.categoryCounts).toEqual({ system: 1 });
|
|
807
|
+
});
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
// ---------------------------------------------------------------------------
|
|
811
|
+
// normalizeMarketplaceCategory (unit)
|
|
812
|
+
// ---------------------------------------------------------------------------
|
|
813
|
+
|
|
814
|
+
describe("normalizeMarketplaceCategory", () => {
|
|
815
|
+
test("passes a valid Skills slug through unchanged", () => {
|
|
816
|
+
expect(
|
|
817
|
+
normalizeMarketplaceCategory("productivity", SKILLS_CATEGORY_SLUGS),
|
|
818
|
+
).toBe("productivity");
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
test("aliases `developer` → `development`", () => {
|
|
822
|
+
expect(
|
|
823
|
+
normalizeMarketplaceCategory("developer", SKILLS_CATEGORY_SLUGS),
|
|
824
|
+
).toBe("development");
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
test("folds unknown marketplace slugs to null", () => {
|
|
828
|
+
for (const unknown of ["memory", "interface", "marketing", "hobby"]) {
|
|
829
|
+
expect(
|
|
830
|
+
normalizeMarketplaceCategory(unknown, SKILLS_CATEGORY_SLUGS),
|
|
831
|
+
).toBeNull();
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
test("treats null / empty / whitespace as null", () => {
|
|
836
|
+
expect(
|
|
837
|
+
normalizeMarketplaceCategory(null, SKILLS_CATEGORY_SLUGS),
|
|
838
|
+
).toBeNull();
|
|
839
|
+
expect(
|
|
840
|
+
normalizeMarketplaceCategory(undefined, SKILLS_CATEGORY_SLUGS),
|
|
841
|
+
).toBeNull();
|
|
842
|
+
expect(normalizeMarketplaceCategory("", SKILLS_CATEGORY_SLUGS)).toBeNull();
|
|
843
|
+
expect(
|
|
844
|
+
normalizeMarketplaceCategory(" ", SKILLS_CATEGORY_SLUGS),
|
|
845
|
+
).toBeNull();
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
test("normalizes case + surrounding whitespace before matching", () => {
|
|
849
|
+
expect(
|
|
850
|
+
normalizeMarketplaceCategory(" Developer ", SKILLS_CATEGORY_SLUGS),
|
|
851
|
+
).toBe("development");
|
|
852
|
+
expect(
|
|
853
|
+
normalizeMarketplaceCategory("PRODUCTIVITY", SKILLS_CATEGORY_SLUGS),
|
|
854
|
+
).toBe("productivity");
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
test("an empty valid-slug set folds every category to null", () => {
|
|
858
|
+
expect(normalizeMarketplaceCategory("productivity", new Set())).toBeNull();
|
|
859
|
+
expect(normalizeMarketplaceCategory("developer", new Set())).toBeNull();
|
|
860
|
+
});
|
|
446
861
|
});
|
|
447
862
|
|
|
448
863
|
// ---------------------------------------------------------------------------
|
|
@@ -469,6 +884,7 @@ describe("GET /v1/plugins/search", () => {
|
|
|
469
884
|
{
|
|
470
885
|
name: "simple-memory",
|
|
471
886
|
path: "github:vellum-ai/simple-memory@ed09a4c01bf18e4ac8859faee94cb65c7cbd1ca3",
|
|
887
|
+
category: "productivity",
|
|
472
888
|
source: {
|
|
473
889
|
kind: "github",
|
|
474
890
|
repo: "vellum-ai/simple-memory",
|
|
@@ -479,6 +895,7 @@ describe("GET /v1/plugins/search", () => {
|
|
|
479
895
|
name: "caveman",
|
|
480
896
|
path: "github:JuliusBrussee/caveman@v1.8.2",
|
|
481
897
|
description: "Ultra-compressed communication mode.",
|
|
898
|
+
category: null,
|
|
482
899
|
source: {
|
|
483
900
|
kind: "github",
|
|
484
901
|
repo: "JuliusBrussee/caveman",
|
|
@@ -498,7 +915,8 @@ describe("GET /v1/plugins/search", () => {
|
|
|
498
915
|
expect(ref).toBe("my-feature-branch");
|
|
499
916
|
|
|
500
917
|
// The query is applied in-memory by the real filter: `^simple` matches
|
|
501
|
-
// only `simple-memory
|
|
918
|
+
// only `simple-memory`. The source discriminator and the marketplace
|
|
919
|
+
// `category` both flow through to the wire.
|
|
502
920
|
expect(result).toEqual({
|
|
503
921
|
query: "^simple",
|
|
504
922
|
ref: "my-feature-branch",
|
|
@@ -506,6 +924,7 @@ describe("GET /v1/plugins/search", () => {
|
|
|
506
924
|
{
|
|
507
925
|
name: "simple-memory",
|
|
508
926
|
path: "github:vellum-ai/simple-memory@ed09a4c01bf18e4ac8859faee94cb65c7cbd1ca3",
|
|
927
|
+
category: "productivity",
|
|
509
928
|
source: {
|
|
510
929
|
kind: "github",
|
|
511
930
|
repo: "vellum-ai/simple-memory",
|
|
@@ -516,12 +935,39 @@ describe("GET /v1/plugins/search", () => {
|
|
|
516
935
|
});
|
|
517
936
|
});
|
|
518
937
|
|
|
938
|
+
test("normalizes match categories to the Skills taxonomy (`developer` → `development`, `hobby` → null)", async () => {
|
|
939
|
+
getCatalogSpy.mockImplementation(async (ref) =>
|
|
940
|
+
catalog(ref, [
|
|
941
|
+
{
|
|
942
|
+
name: "dev-tools",
|
|
943
|
+
path: "github:acme/dev-tools@v1",
|
|
944
|
+
category: "developer",
|
|
945
|
+
source: { kind: "github", repo: "acme/dev-tools", ref: "v1" },
|
|
946
|
+
},
|
|
947
|
+
{
|
|
948
|
+
name: "snake-game",
|
|
949
|
+
path: "github:acme/snake-game@v1",
|
|
950
|
+
category: "hobby",
|
|
951
|
+
source: { kind: "github", repo: "acme/snake-game", ref: "v1" },
|
|
952
|
+
},
|
|
953
|
+
]),
|
|
954
|
+
);
|
|
955
|
+
|
|
956
|
+
const result = await invokeSearch();
|
|
957
|
+
const byName = new Map(result.matches.map((m) => [m.name, m.category]));
|
|
958
|
+
// `developer` is aliased to the Skills slug; `hobby` has no equivalent and
|
|
959
|
+
// folds to null so "Available" filters against the same taxonomy.
|
|
960
|
+
expect(byName.get("dev-tools")).toBe("development");
|
|
961
|
+
expect(byName.get("snake-game")).toBeNull();
|
|
962
|
+
});
|
|
963
|
+
|
|
519
964
|
test("missing ?q= matches all (empty-string query) at the default ref", async () => {
|
|
520
965
|
getCatalogSpy.mockImplementation(async (ref) =>
|
|
521
966
|
catalog(ref, [
|
|
522
967
|
{
|
|
523
968
|
name: "caveman",
|
|
524
969
|
path: "github:JuliusBrussee/caveman@v1.8.2",
|
|
970
|
+
category: null,
|
|
525
971
|
source: {
|
|
526
972
|
kind: "github",
|
|
527
973
|
repo: "JuliusBrussee/caveman",
|
|
@@ -536,6 +982,8 @@ describe("GET /v1/plugins/search", () => {
|
|
|
536
982
|
expect(ref).toBe("main");
|
|
537
983
|
expect(result.query).toBe("");
|
|
538
984
|
expect(result.matches.map((m) => m.name)).toEqual(["caveman"]);
|
|
985
|
+
// An entry that declares no category surfaces as `category: null`.
|
|
986
|
+
expect(result.matches[0]?.category).toBeNull();
|
|
539
987
|
});
|
|
540
988
|
|
|
541
989
|
test("whitespace-only ?ref= falls back to the default ref", async () => {
|
|
@@ -597,6 +1045,7 @@ describe("GET /v1/plugins/search", () => {
|
|
|
597
1045
|
Object.freeze({
|
|
598
1046
|
name: "a",
|
|
599
1047
|
path: "github:acme/a@bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
|
1048
|
+
category: null,
|
|
600
1049
|
source: {
|
|
601
1050
|
kind: "github",
|
|
602
1051
|
repo: "acme/a",
|
|
@@ -617,6 +1066,7 @@ describe("GET /v1/plugins/search", () => {
|
|
|
617
1066
|
result.matches.push({
|
|
618
1067
|
name: "b",
|
|
619
1068
|
path: "x",
|
|
1069
|
+
category: null,
|
|
620
1070
|
source: {
|
|
621
1071
|
kind: "github",
|
|
622
1072
|
repo: "acme/b",
|
|
@@ -641,6 +1091,7 @@ function invokeUninstall(args: RouteHandlerArgs = {}): {
|
|
|
641
1091
|
describe("DELETE /v1/plugins/:name", () => {
|
|
642
1092
|
beforeEach(() => {
|
|
643
1093
|
uninstallSpy.mockReset();
|
|
1094
|
+
broadcastMessageSpy.mockReset();
|
|
644
1095
|
});
|
|
645
1096
|
|
|
646
1097
|
test("forwards pathParams.name to uninstallPlugin and returns its result", () => {
|
|
@@ -659,6 +1110,35 @@ describe("DELETE /v1/plugins/:name", () => {
|
|
|
659
1110
|
});
|
|
660
1111
|
});
|
|
661
1112
|
|
|
1113
|
+
test("publishes sync_changed(plugins:list) on a successful uninstall", () => {
|
|
1114
|
+
uninstallSpy.mockImplementation((opts) => ({
|
|
1115
|
+
name: opts.name,
|
|
1116
|
+
target: `/workspace/.vellum/plugins/${opts.name}`,
|
|
1117
|
+
}));
|
|
1118
|
+
|
|
1119
|
+
invokeUninstall({ pathParams: { name: "simple-memory" } });
|
|
1120
|
+
|
|
1121
|
+
expectPluginsListBroadcast();
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
test("threads x-vellum-client-id into the published event's originClientId", () => {
|
|
1125
|
+
uninstallSpy.mockImplementation((opts) => ({
|
|
1126
|
+
name: opts.name,
|
|
1127
|
+
target: `/workspace/.vellum/plugins/${opts.name}`,
|
|
1128
|
+
}));
|
|
1129
|
+
|
|
1130
|
+
invokeUninstall({
|
|
1131
|
+
pathParams: { name: "simple-memory" },
|
|
1132
|
+
headers: { "x-vellum-client-id": "client-abc" },
|
|
1133
|
+
});
|
|
1134
|
+
|
|
1135
|
+
const [msg] = broadcastMessageSpy.mock.calls[0]!;
|
|
1136
|
+
expect(msg).toMatchObject({
|
|
1137
|
+
type: "sync_changed",
|
|
1138
|
+
originClientId: "client-abc",
|
|
1139
|
+
});
|
|
1140
|
+
});
|
|
1141
|
+
|
|
662
1142
|
test("missing pathParams.name passes the empty string through to the lib", () => {
|
|
663
1143
|
// The lib's `sanitizePluginName` is the validator of last resort —
|
|
664
1144
|
// the route hands off the raw value without pre-trimming. The lib
|
|
@@ -683,7 +1163,7 @@ describe("DELETE /v1/plugins/:name", () => {
|
|
|
683
1163
|
).toThrow(BadRequestError);
|
|
684
1164
|
});
|
|
685
1165
|
|
|
686
|
-
test("PluginNotInstalledError → NotFoundError (404)", () => {
|
|
1166
|
+
test("PluginNotInstalledError → NotFoundError (404), no broadcast", () => {
|
|
687
1167
|
uninstallSpy.mockImplementation((opts) => {
|
|
688
1168
|
throw new PluginNotInstalledError(
|
|
689
1169
|
opts.name,
|
|
@@ -694,6 +1174,8 @@ describe("DELETE /v1/plugins/:name", () => {
|
|
|
694
1174
|
expect(() => invokeUninstall({ pathParams: { name: "ghost" } })).toThrow(
|
|
695
1175
|
NotFoundError,
|
|
696
1176
|
);
|
|
1177
|
+
// A failed uninstall must not fan out a spurious invalidation.
|
|
1178
|
+
expect(broadcastMessageSpy).not.toHaveBeenCalled();
|
|
697
1179
|
});
|
|
698
1180
|
|
|
699
1181
|
test("unknown errors → InternalError with original message preserved", () => {
|
|
@@ -856,6 +1338,7 @@ describe("POST /v1/plugins/install", () => {
|
|
|
856
1338
|
beforeEach(() => {
|
|
857
1339
|
installSpy.mockReset();
|
|
858
1340
|
resolvePinSpy.mockReset();
|
|
1341
|
+
broadcastMessageSpy.mockReset();
|
|
859
1342
|
});
|
|
860
1343
|
|
|
861
1344
|
test("forwards name/force and shapes the result, pinning ref to the default", async () => {
|
|
@@ -886,6 +1369,43 @@ describe("POST /v1/plugins/install", () => {
|
|
|
886
1369
|
});
|
|
887
1370
|
});
|
|
888
1371
|
|
|
1372
|
+
test("publishes sync_changed(plugins:list) on a successful install", async () => {
|
|
1373
|
+
installSpy.mockImplementation(async (opts) => ({
|
|
1374
|
+
name: opts.name,
|
|
1375
|
+
target: `/workspace/.vellum/plugins/${opts.name}`,
|
|
1376
|
+
fileCount: 7,
|
|
1377
|
+
ref: opts.ref ?? "main",
|
|
1378
|
+
commit: null,
|
|
1379
|
+
committedAt: null,
|
|
1380
|
+
}));
|
|
1381
|
+
|
|
1382
|
+
await invokeInstall({ body: { name: "caveman" } });
|
|
1383
|
+
|
|
1384
|
+
expectPluginsListBroadcast();
|
|
1385
|
+
});
|
|
1386
|
+
|
|
1387
|
+
test("threads x-vellum-client-id into the published event's originClientId", async () => {
|
|
1388
|
+
installSpy.mockImplementation(async (opts) => ({
|
|
1389
|
+
name: opts.name,
|
|
1390
|
+
target: `/workspace/.vellum/plugins/${opts.name}`,
|
|
1391
|
+
fileCount: 7,
|
|
1392
|
+
ref: opts.ref ?? "main",
|
|
1393
|
+
commit: null,
|
|
1394
|
+
committedAt: null,
|
|
1395
|
+
}));
|
|
1396
|
+
|
|
1397
|
+
await invokeInstall({
|
|
1398
|
+
body: { name: "caveman" },
|
|
1399
|
+
headers: { "x-vellum-client-id": "client-abc" },
|
|
1400
|
+
});
|
|
1401
|
+
|
|
1402
|
+
const [msg] = broadcastMessageSpy.mock.calls[0]!;
|
|
1403
|
+
expect(msg).toMatchObject({
|
|
1404
|
+
type: "sync_changed",
|
|
1405
|
+
originClientId: "client-abc",
|
|
1406
|
+
});
|
|
1407
|
+
});
|
|
1408
|
+
|
|
889
1409
|
test("ignores a caller-supplied ref and pins to the curated default", async () => {
|
|
890
1410
|
// Security boundary: installing from an unreviewed ref (a PR branch,
|
|
891
1411
|
// fork ref, ...) could load attacker-controlled marketplace code, so the
|
|
@@ -942,7 +1462,7 @@ describe("POST /v1/plugins/install", () => {
|
|
|
942
1462
|
).rejects.toBeInstanceOf(ConflictError);
|
|
943
1463
|
});
|
|
944
1464
|
|
|
945
|
-
test("PluginNotFoundError → NotFoundError (404)", async () => {
|
|
1465
|
+
test("PluginNotFoundError → NotFoundError (404), no broadcast", async () => {
|
|
946
1466
|
installSpy.mockImplementation(async (opts) => {
|
|
947
1467
|
throw new PluginNotFoundError(opts.name, "main", "example-org/ghost");
|
|
948
1468
|
});
|
|
@@ -950,6 +1470,8 @@ describe("POST /v1/plugins/install", () => {
|
|
|
950
1470
|
await expect(
|
|
951
1471
|
invokeInstall({ body: { name: "ghost" } }),
|
|
952
1472
|
).rejects.toBeInstanceOf(NotFoundError);
|
|
1473
|
+
// A failed install must not fan out a spurious invalidation.
|
|
1474
|
+
expect(broadcastMessageSpy).not.toHaveBeenCalled();
|
|
953
1475
|
});
|
|
954
1476
|
|
|
955
1477
|
test("PluginSourceUnavailableError → ServiceUnavailableError (503)", async () => {
|
|
@@ -1295,6 +1817,30 @@ async function invokeUpgrade(args: RouteHandlerArgs = {}): Promise<{
|
|
|
1295
1817
|
describe("POST /v1/plugins/:name/upgrade", () => {
|
|
1296
1818
|
beforeEach(() => {
|
|
1297
1819
|
upgradeSpy.mockReset();
|
|
1820
|
+
broadcastMessageSpy.mockReset();
|
|
1821
|
+
});
|
|
1822
|
+
|
|
1823
|
+
test("publishes sync_changed(plugins:list) on a successful upgrade", async () => {
|
|
1824
|
+
upgradeSpy.mockImplementation(async () => upgradeResult());
|
|
1825
|
+
|
|
1826
|
+
await invokeUpgrade({ pathParams: { name: "level-up" } });
|
|
1827
|
+
|
|
1828
|
+
expectPluginsListBroadcast();
|
|
1829
|
+
});
|
|
1830
|
+
|
|
1831
|
+
test("threads x-vellum-client-id into the published event's originClientId", async () => {
|
|
1832
|
+
upgradeSpy.mockImplementation(async () => upgradeResult());
|
|
1833
|
+
|
|
1834
|
+
await invokeUpgrade({
|
|
1835
|
+
pathParams: { name: "level-up" },
|
|
1836
|
+
headers: { "x-vellum-client-id": "client-abc" },
|
|
1837
|
+
});
|
|
1838
|
+
|
|
1839
|
+
const [msg] = broadcastMessageSpy.mock.calls[0]!;
|
|
1840
|
+
expect(msg).toMatchObject({
|
|
1841
|
+
type: "sync_changed",
|
|
1842
|
+
originClientId: "client-abc",
|
|
1843
|
+
});
|
|
1298
1844
|
});
|
|
1299
1845
|
|
|
1300
1846
|
test("forwards name + dryRun and projects the upgrade result", async () => {
|
|
@@ -1430,7 +1976,7 @@ describe("POST /v1/plugins/:name/upgrade", () => {
|
|
|
1430
1976
|
).rejects.toBeInstanceOf(BadRequestError);
|
|
1431
1977
|
});
|
|
1432
1978
|
|
|
1433
|
-
test("PluginNotInstalledError → NotFoundError (404)", async () => {
|
|
1979
|
+
test("PluginNotInstalledError → NotFoundError (404), no broadcast", async () => {
|
|
1434
1980
|
upgradeSpy.mockImplementation(async () => {
|
|
1435
1981
|
throw new PluginNotInstalledError(
|
|
1436
1982
|
"ghost",
|
|
@@ -1441,6 +1987,8 @@ describe("POST /v1/plugins/:name/upgrade", () => {
|
|
|
1441
1987
|
await expect(
|
|
1442
1988
|
invokeUpgrade({ pathParams: { name: "ghost" } }),
|
|
1443
1989
|
).rejects.toBeInstanceOf(NotFoundError);
|
|
1990
|
+
// A failed upgrade must not fan out a spurious invalidation.
|
|
1991
|
+
expect(broadcastMessageSpy).not.toHaveBeenCalled();
|
|
1444
1992
|
});
|
|
1445
1993
|
|
|
1446
1994
|
test("PluginNotUpgradableError → ConflictError (409)", async () => {
|
|
@@ -1628,3 +2176,181 @@ describe("POST /v1/plugins/:name/diff", () => {
|
|
|
1628
2176
|
expect((caught as Error).message).toContain("ECONNRESET");
|
|
1629
2177
|
});
|
|
1630
2178
|
});
|
|
2179
|
+
|
|
2180
|
+
// ---------------------------------------------------------------------------
|
|
2181
|
+
// POST /v1/plugins/:name/enable | disable (toggle)
|
|
2182
|
+
// ---------------------------------------------------------------------------
|
|
2183
|
+
|
|
2184
|
+
function toggleResult(
|
|
2185
|
+
name: string,
|
|
2186
|
+
action: "enable" | "disable",
|
|
2187
|
+
): TogglePluginResult {
|
|
2188
|
+
return {
|
|
2189
|
+
name,
|
|
2190
|
+
action,
|
|
2191
|
+
sentinelPath: `/workspace/.vellum/plugins/${name}/.disabled`,
|
|
2192
|
+
};
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
function invokeEnable(args: RouteHandlerArgs = {}): { ok: boolean } {
|
|
2196
|
+
return enableHandler(args) as { ok: boolean };
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
function invokeDisable(args: RouteHandlerArgs = {}): { ok: boolean } {
|
|
2200
|
+
return disableHandler(args) as { ok: boolean };
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
/** Assert the spy received exactly one sync_changed carrying `plugins:list`. */
|
|
2204
|
+
function expectPluginsListBroadcast(): void {
|
|
2205
|
+
expect(broadcastMessageSpy.mock.calls).toHaveLength(1);
|
|
2206
|
+
const [msg] = broadcastMessageSpy.mock.calls[0]!;
|
|
2207
|
+
expect(msg).toMatchObject({ type: "sync_changed" });
|
|
2208
|
+
expect((msg as { tags: string[] }).tags).toContain("plugins:list");
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
describe("POST /v1/plugins/:name/enable", () => {
|
|
2212
|
+
beforeEach(() => {
|
|
2213
|
+
enablePluginSpy.mockReset();
|
|
2214
|
+
broadcastMessageSpy.mockReset();
|
|
2215
|
+
});
|
|
2216
|
+
|
|
2217
|
+
test("enables the plugin and broadcasts sync_changed(plugins:list)", () => {
|
|
2218
|
+
enablePluginSpy.mockImplementation((name) => toggleResult(name, "enable"));
|
|
2219
|
+
|
|
2220
|
+
const result = invokeEnable({ pathParams: { name: "simple-memory" } });
|
|
2221
|
+
|
|
2222
|
+
expect(result).toEqual({ ok: true });
|
|
2223
|
+
expect(enablePluginSpy.mock.calls[0]?.[0]).toBe("simple-memory");
|
|
2224
|
+
expectPluginsListBroadcast();
|
|
2225
|
+
});
|
|
2226
|
+
|
|
2227
|
+
test("threads x-vellum-client-id into the published event's originClientId", () => {
|
|
2228
|
+
enablePluginSpy.mockImplementation((name) => toggleResult(name, "enable"));
|
|
2229
|
+
|
|
2230
|
+
invokeEnable({
|
|
2231
|
+
pathParams: { name: "simple-memory" },
|
|
2232
|
+
headers: { "x-vellum-client-id": "client-abc" },
|
|
2233
|
+
});
|
|
2234
|
+
|
|
2235
|
+
// The initiating client's id flows through the canonical publisher so it
|
|
2236
|
+
// can self-echo-suppress its own invalidation.
|
|
2237
|
+
const [msg] = broadcastMessageSpy.mock.calls[0]!;
|
|
2238
|
+
expect(msg).toMatchObject({
|
|
2239
|
+
type: "sync_changed",
|
|
2240
|
+
originClientId: "client-abc",
|
|
2241
|
+
});
|
|
2242
|
+
});
|
|
2243
|
+
|
|
2244
|
+
test("a broadcast failure does not fail a successful toggle", () => {
|
|
2245
|
+
enablePluginSpy.mockImplementation((name) => toggleResult(name, "enable"));
|
|
2246
|
+
// The sentinel was already flipped; a hub throw AFTER that must not surface
|
|
2247
|
+
// as a 500 — the canonical publisher swallows broadcast errors.
|
|
2248
|
+
broadcastMessageSpy.mockImplementation(() => {
|
|
2249
|
+
throw new Error("hub unavailable");
|
|
2250
|
+
});
|
|
2251
|
+
|
|
2252
|
+
const result = invokeEnable({ pathParams: { name: "simple-memory" } });
|
|
2253
|
+
expect(result).toEqual({ ok: true });
|
|
2254
|
+
});
|
|
2255
|
+
|
|
2256
|
+
test("PluginAlreadyInStateException → ConflictError (409), no broadcast", () => {
|
|
2257
|
+
enablePluginSpy.mockImplementation((name) => {
|
|
2258
|
+
throw new PluginAlreadyInStateException(name, "enable");
|
|
2259
|
+
});
|
|
2260
|
+
|
|
2261
|
+
expect(() =>
|
|
2262
|
+
invokeEnable({ pathParams: { name: "simple-memory" } }),
|
|
2263
|
+
).toThrow(ConflictError);
|
|
2264
|
+
// A no-op toggle must not fan out a spurious invalidation.
|
|
2265
|
+
expect(broadcastMessageSpy).not.toHaveBeenCalled();
|
|
2266
|
+
});
|
|
2267
|
+
|
|
2268
|
+
test("PluginDirectoryNotFoundError → NotFoundError (404)", () => {
|
|
2269
|
+
enablePluginSpy.mockImplementation((name) => {
|
|
2270
|
+
throw new PluginDirectoryNotFoundError(name);
|
|
2271
|
+
});
|
|
2272
|
+
|
|
2273
|
+
expect(() => invokeEnable({ pathParams: { name: "ghost" } })).toThrow(
|
|
2274
|
+
NotFoundError,
|
|
2275
|
+
);
|
|
2276
|
+
});
|
|
2277
|
+
|
|
2278
|
+
test("InvalidPluginNameError → BadRequestError (400)", () => {
|
|
2279
|
+
enablePluginSpy.mockImplementation(() => {
|
|
2280
|
+
throw new ToggleInvalidPluginNameError("../escape");
|
|
2281
|
+
});
|
|
2282
|
+
|
|
2283
|
+
expect(() => invokeEnable({ pathParams: { name: "../escape" } })).toThrow(
|
|
2284
|
+
BadRequestError,
|
|
2285
|
+
);
|
|
2286
|
+
});
|
|
2287
|
+
});
|
|
2288
|
+
|
|
2289
|
+
describe("POST /v1/plugins/:name/disable", () => {
|
|
2290
|
+
beforeEach(() => {
|
|
2291
|
+
disablePluginSpy.mockReset();
|
|
2292
|
+
broadcastMessageSpy.mockReset();
|
|
2293
|
+
});
|
|
2294
|
+
|
|
2295
|
+
test("disables the plugin and broadcasts sync_changed(plugins:list)", () => {
|
|
2296
|
+
disablePluginSpy.mockImplementation((name) =>
|
|
2297
|
+
toggleResult(name, "disable"),
|
|
2298
|
+
);
|
|
2299
|
+
|
|
2300
|
+
const result = invokeDisable({ pathParams: { name: "simple-memory" } });
|
|
2301
|
+
|
|
2302
|
+
expect(result).toEqual({ ok: true });
|
|
2303
|
+
expect(disablePluginSpy.mock.calls[0]?.[0]).toBe("simple-memory");
|
|
2304
|
+
// Enable and disable emit the SAME invalidation — the tag names the
|
|
2305
|
+
// resource, not the new value.
|
|
2306
|
+
expectPluginsListBroadcast();
|
|
2307
|
+
});
|
|
2308
|
+
|
|
2309
|
+
test("threads x-vellum-client-id into the published event's originClientId", () => {
|
|
2310
|
+
disablePluginSpy.mockImplementation((name) =>
|
|
2311
|
+
toggleResult(name, "disable"),
|
|
2312
|
+
);
|
|
2313
|
+
|
|
2314
|
+
invokeDisable({
|
|
2315
|
+
pathParams: { name: "simple-memory" },
|
|
2316
|
+
headers: { "x-vellum-client-id": "client-xyz" },
|
|
2317
|
+
});
|
|
2318
|
+
|
|
2319
|
+
const [msg] = broadcastMessageSpy.mock.calls[0]!;
|
|
2320
|
+
expect(msg).toMatchObject({
|
|
2321
|
+
type: "sync_changed",
|
|
2322
|
+
originClientId: "client-xyz",
|
|
2323
|
+
});
|
|
2324
|
+
});
|
|
2325
|
+
|
|
2326
|
+
test("PluginAlreadyInStateException → ConflictError (409), no broadcast", () => {
|
|
2327
|
+
disablePluginSpy.mockImplementation((name) => {
|
|
2328
|
+
throw new PluginAlreadyInStateException(name, "disable");
|
|
2329
|
+
});
|
|
2330
|
+
|
|
2331
|
+
expect(() =>
|
|
2332
|
+
invokeDisable({ pathParams: { name: "simple-memory" } }),
|
|
2333
|
+
).toThrow(ConflictError);
|
|
2334
|
+
expect(broadcastMessageSpy).not.toHaveBeenCalled();
|
|
2335
|
+
});
|
|
2336
|
+
|
|
2337
|
+
test("PluginDirectoryNotFoundError → NotFoundError (404)", () => {
|
|
2338
|
+
disablePluginSpy.mockImplementation((name) => {
|
|
2339
|
+
throw new PluginDirectoryNotFoundError(name);
|
|
2340
|
+
});
|
|
2341
|
+
|
|
2342
|
+
expect(() => invokeDisable({ pathParams: { name: "ghost" } })).toThrow(
|
|
2343
|
+
NotFoundError,
|
|
2344
|
+
);
|
|
2345
|
+
});
|
|
2346
|
+
|
|
2347
|
+
test("InvalidPluginNameError → BadRequestError (400)", () => {
|
|
2348
|
+
disablePluginSpy.mockImplementation(() => {
|
|
2349
|
+
throw new ToggleInvalidPluginNameError("../escape");
|
|
2350
|
+
});
|
|
2351
|
+
|
|
2352
|
+
expect(() => invokeDisable({ pathParams: { name: "../escape" } })).toThrow(
|
|
2353
|
+
BadRequestError,
|
|
2354
|
+
);
|
|
2355
|
+
});
|
|
2356
|
+
});
|