@capybara-hq/kernel 0.0.0-stage → 0.1.0-rc.1
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/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +20 -2
- package/bootstrap.json +26 -0
- package/dist/admin/admin.controller.d.ts +139 -0
- package/dist/admin/admin.controller.js +238 -0
- package/dist/admin/extensions.controller.d.ts +100 -0
- package/dist/admin/extensions.controller.js +226 -0
- package/dist/admin/setup.controller.d.ts +36 -0
- package/dist/admin/setup.controller.js +106 -0
- package/dist/admin/setup.service.d.ts +47 -0
- package/dist/admin/setup.service.js +90 -0
- package/dist/agents/agents.controller.d.ts +205 -0
- package/dist/agents/agents.controller.js +144 -0
- package/dist/agents/agents.service.d.ts +177 -0
- package/dist/agents/agents.service.js +677 -0
- package/dist/agents/definition.d.ts +198 -0
- package/dist/agents/definition.js +152 -0
- package/dist/agents/models.controller.d.ts +13 -0
- package/dist/agents/models.controller.js +31 -0
- package/dist/agents/templates.controller.d.ts +15 -0
- package/dist/agents/templates.controller.js +37 -0
- package/dist/agents/templates.d.ts +83 -0
- package/dist/agents/templates.js +173 -0
- package/dist/catalog/admin-catalog.controller.d.ts +143 -0
- package/dist/catalog/admin-catalog.controller.js +245 -0
- package/dist/catalog/catalog.service.d.ts +430 -0
- package/dist/catalog/catalog.service.js +882 -0
- package/dist/catalog/claude/agents.d.ts +27 -0
- package/dist/catalog/claude/agents.js +270 -0
- package/dist/catalog/claude/commands.d.ts +50 -0
- package/dist/catalog/claude/commands.js +179 -0
- package/dist/catalog/claude/manifest.d.ts +69 -0
- package/dist/catalog/claude/manifest.js +134 -0
- package/dist/catalog/claude/marketplace.d.ts +35 -0
- package/dist/catalog/claude/marketplace.js +194 -0
- package/dist/catalog/claude/mcp.d.ts +33 -0
- package/dist/catalog/claude/mcp.js +226 -0
- package/dist/catalog/claude/report.d.ts +86 -0
- package/dist/catalog/claude/report.js +121 -0
- package/dist/catalog/claude/translate.d.ts +56 -0
- package/dist/catalog/claude/translate.js +857 -0
- package/dist/catalog/cli.d.ts +46 -0
- package/dist/catalog/cli.js +211 -0
- package/dist/catalog/git-source.d.ts +127 -0
- package/dist/catalog/git-source.js +520 -0
- package/dist/catalog/marketplace-fetch.d.ts +99 -0
- package/dist/catalog/marketplace-fetch.js +203 -0
- package/dist/catalog/marketplaces.controller.d.ts +97 -0
- package/dist/catalog/marketplaces.controller.js +197 -0
- package/dist/catalog/marketplaces.service.d.ts +340 -0
- package/dist/catalog/marketplaces.service.js +1217 -0
- package/dist/catalog/org-catalog.controller.d.ts +128 -0
- package/dist/catalog/org-catalog.controller.js +277 -0
- package/dist/catalog/org-catalog.service.d.ts +246 -0
- package/dist/catalog/org-catalog.service.js +625 -0
- package/dist/catalog/plugin-import.d.ts +436 -0
- package/dist/catalog/plugin-import.js +1069 -0
- package/dist/catalog/plugin-install.d.ts +447 -0
- package/dist/catalog/plugin-install.js +1333 -0
- package/dist/catalog/plugin-package.d.ts +143 -0
- package/dist/catalog/plugin-package.js +382 -0
- package/dist/catalog/plugin-upgrade.d.ts +222 -0
- package/dist/catalog/plugin-upgrade.js +1219 -0
- package/dist/catalog/requests.service.d.ts +162 -0
- package/dist/catalog/requests.service.js +504 -0
- package/dist/catalog/schema-path.d.ts +7 -0
- package/dist/catalog/schema-path.js +63 -0
- package/dist/catalog/upgrade-targets.d.ts +59 -0
- package/dist/catalog/upgrade-targets.js +90 -0
- package/dist/catalog/versions.d.ts +31 -0
- package/dist/catalog/versions.js +44 -0
- package/dist/channels/actions.d.ts +37 -0
- package/dist/channels/actions.js +121 -0
- package/dist/channels/cards.d.ts +44 -0
- package/dist/channels/cards.js +156 -0
- package/dist/channels/connections.controller.d.ts +59 -0
- package/dist/channels/connections.controller.js +115 -0
- package/dist/channels/connections.service.d.ts +187 -0
- package/dist/channels/connections.service.js +534 -0
- package/dist/channels/features.d.ts +12 -0
- package/dist/channels/features.js +22 -0
- package/dist/channels/inbound.d.ts +120 -0
- package/dist/channels/inbound.js +332 -0
- package/dist/channels/outbound.d.ts +140 -0
- package/dist/channels/outbound.js +1126 -0
- package/dist/channels/phrases.d.ts +100 -0
- package/dist/channels/phrases.js +81 -0
- package/dist/channels/supervisor.d.ts +182 -0
- package/dist/channels/supervisor.js +625 -0
- package/dist/channels/threads.d.ts +29 -0
- package/dist/channels/threads.js +104 -0
- package/dist/channels/webhook-url.d.ts +16 -0
- package/dist/channels/webhook-url.js +34 -0
- package/dist/channels/webhook.controller.d.ts +19 -0
- package/dist/channels/webhook.controller.js +72 -0
- package/dist/channels/webhook.d.ts +73 -0
- package/dist/channels/webhook.js +274 -0
- package/dist/config/docs.d.ts +9 -0
- package/dist/config/docs.js +85 -0
- package/dist/config/env.d.ts +6 -0
- package/dist/config/env.js +73 -0
- package/dist/config/fields.d.ts +42 -0
- package/dist/config/fields.js +214 -0
- package/dist/config/file-source.d.ts +3 -0
- package/dist/config/file-source.js +31 -0
- package/dist/config/load.d.ts +20 -0
- package/dist/config/load.js +98 -0
- package/dist/config/nacos-source.d.ts +31 -0
- package/dist/config/nacos-source.js +229 -0
- package/dist/config/schema.d.ts +237 -0
- package/dist/config/schema.js +1192 -0
- package/dist/config/sources.d.ts +18 -0
- package/dist/config/sources.js +59 -0
- package/dist/config/types.d.ts +9 -0
- package/dist/config/types.js +7 -0
- package/dist/conversations/artifacts.d.ts +52 -0
- package/dist/conversations/artifacts.js +137 -0
- package/dist/conversations/attachments.controller.d.ts +28 -0
- package/dist/conversations/attachments.controller.js +96 -0
- package/dist/conversations/attachments.service.d.ts +180 -0
- package/dist/conversations/attachments.service.js +578 -0
- package/dist/conversations/conversations.controller.d.ts +288 -0
- package/dist/conversations/conversations.controller.js +607 -0
- package/dist/conversations/conversations.service.d.ts +321 -0
- package/dist/conversations/conversations.service.js +754 -0
- package/dist/conversations/event-log.d.ts +122 -0
- package/dist/conversations/event-log.js +259 -0
- package/dist/conversations/events.d.ts +856 -0
- package/dist/conversations/events.js +639 -0
- package/dist/conversations/files.d.ts +92 -0
- package/dist/conversations/files.js +213 -0
- package/dist/conversations/outputs-read.d.ts +34 -0
- package/dist/conversations/outputs-read.js +37 -0
- package/dist/conversations/queue.d.ts +169 -0
- package/dist/conversations/queue.js +527 -0
- package/dist/conversations/sandbox-status.d.ts +21 -0
- package/dist/conversations/sandbox-status.js +24 -0
- package/dist/conversations/selection.d.ts +46 -0
- package/dist/conversations/selection.js +75 -0
- package/dist/conversations/titles.d.ts +93 -0
- package/dist/conversations/titles.js +423 -0
- package/dist/ctx/bus.d.ts +16 -0
- package/dist/ctx/bus.js +101 -0
- package/dist/ctx/create-ctx.d.ts +109 -0
- package/dist/ctx/create-ctx.js +592 -0
- package/dist/ctx/kv.d.ts +4 -0
- package/dist/ctx/kv.js +31 -0
- package/dist/ctx/lock.d.ts +4 -0
- package/dist/ctx/lock.js +39 -0
- package/dist/ctx/namespace.d.ts +6 -0
- package/dist/ctx/namespace.js +12 -0
- package/dist/ctx/queue.d.ts +25 -0
- package/dist/ctx/queue.js +151 -0
- package/dist/ctx/secrets.d.ts +45 -0
- package/dist/ctx/secrets.js +175 -0
- package/dist/db/kernel-schema.d.ts +1122 -0
- package/dist/db/kernel-schema.js +5 -0
- package/dist/db/migrator.d.ts +28 -0
- package/dist/db/migrator.js +140 -0
- package/dist/db/pool.d.ts +21 -0
- package/dist/db/pool.js +28 -0
- package/dist/db/sql-statements.d.ts +34 -0
- package/dist/db/sql-statements.js +168 -0
- package/dist/errors.d.ts +211 -0
- package/dist/errors.js +213 -0
- package/dist/evals/budget.d.ts +61 -0
- package/dist/evals/budget.js +181 -0
- package/dist/evals/diff.d.ts +55 -0
- package/dist/evals/diff.js +133 -0
- package/dist/evals/judge.d.ts +53 -0
- package/dist/evals/judge.js +149 -0
- package/dist/evals/plan.d.ts +47 -0
- package/dist/evals/plan.js +77 -0
- package/dist/evals/reconstruct.d.ts +137 -0
- package/dist/evals/reconstruct.js +322 -0
- package/dist/evals/recorded-tools.d.ts +54 -0
- package/dist/evals/recorded-tools.js +167 -0
- package/dist/evals/run-replay.d.ts +86 -0
- package/dist/evals/run-replay.js +502 -0
- package/dist/evals/runs.controller.d.ts +100 -0
- package/dist/evals/runs.controller.js +199 -0
- package/dist/evals/runs.service.d.ts +275 -0
- package/dist/evals/runs.service.js +836 -0
- package/dist/evals/sets.controller.d.ts +61 -0
- package/dist/evals/sets.controller.js +183 -0
- package/dist/evals/sets.service.d.ts +128 -0
- package/dist/evals/sets.service.js +558 -0
- package/dist/evals/snapshot.d.ts +76 -0
- package/dist/evals/snapshot.js +207 -0
- package/dist/evals/step-replay.d.ts +113 -0
- package/dist/evals/step-replay.js +181 -0
- package/dist/evals/trajectory.controller.d.ts +41 -0
- package/dist/evals/trajectory.controller.js +88 -0
- package/dist/evals/trajectory.d.ts +144 -0
- package/dist/evals/trajectory.js +338 -0
- package/dist/evals/trajectory.service.d.ts +108 -0
- package/dist/evals/trajectory.service.js +323 -0
- package/dist/evals/worker.d.ts +101 -0
- package/dist/evals/worker.js +790 -0
- package/dist/extensions/admin-ops.d.ts +105 -0
- package/dist/extensions/admin-ops.js +353 -0
- package/dist/extensions/admin-view.d.ts +79 -0
- package/dist/extensions/admin-view.js +327 -0
- package/dist/extensions/approvals.d.ts +119 -0
- package/dist/extensions/approvals.js +205 -0
- package/dist/extensions/blobs.d.ts +45 -0
- package/dist/extensions/blobs.js +160 -0
- package/dist/extensions/bootstrap.d.ts +176 -0
- package/dist/extensions/bootstrap.js +535 -0
- package/dist/extensions/cli.d.ts +61 -0
- package/dist/extensions/cli.js +165 -0
- package/dist/extensions/config-json-schema.d.ts +9 -0
- package/dist/extensions/config-json-schema.js +73 -0
- package/dist/extensions/deployment-config.d.ts +138 -0
- package/dist/extensions/deployment-config.js +394 -0
- package/dist/extensions/enabled.d.ts +31 -0
- package/dist/extensions/enabled.js +55 -0
- package/dist/extensions/hooks.d.ts +161 -0
- package/dist/extensions/hooks.js +425 -0
- package/dist/extensions/host.d.ts +577 -0
- package/dist/extensions/host.js +2071 -0
- package/dist/extensions/inflight.d.ts +69 -0
- package/dist/extensions/inflight.js +277 -0
- package/dist/extensions/installs.d.ts +166 -0
- package/dist/extensions/installs.js +253 -0
- package/dist/extensions/instance.d.ts +167 -0
- package/dist/extensions/instance.js +329 -0
- package/dist/extensions/ledger.d.ts +30 -0
- package/dist/extensions/ledger.js +50 -0
- package/dist/extensions/lifecycle.d.ts +80 -0
- package/dist/extensions/lifecycle.js +152 -0
- package/dist/extensions/migrate-lock.d.ts +51 -0
- package/dist/extensions/migrate-lock.js +100 -0
- package/dist/extensions/org-config.controller.d.ts +24 -0
- package/dist/extensions/org-config.controller.js +59 -0
- package/dist/extensions/org-config.d.ts +169 -0
- package/dist/extensions/org-config.js +520 -0
- package/dist/extensions/package.d.ts +115 -0
- package/dist/extensions/package.js +246 -0
- package/dist/extensions/permission-set.d.ts +117 -0
- package/dist/extensions/permission-set.js +184 -0
- package/dist/extensions/permission-view.d.ts +194 -0
- package/dist/extensions/permission-view.js +284 -0
- package/dist/extensions/registry.d.ts +24 -0
- package/dist/extensions/registry.js +56 -0
- package/dist/extensions/resolve.d.ts +12 -0
- package/dist/extensions/resolve.js +71 -0
- package/dist/extensions/sources.d.ts +69 -0
- package/dist/extensions/sources.js +120 -0
- package/dist/extensions/sync.d.ts +40 -0
- package/dist/extensions/sync.js +242 -0
- package/dist/files/previewers.controller.d.ts +46 -0
- package/dist/files/previewers.controller.js +105 -0
- package/dist/files/previewers.d.ts +100 -0
- package/dist/files/previewers.js +250 -0
- package/dist/http/api.module.d.ts +114 -0
- package/dist/http/api.module.js +448 -0
- package/dist/http/app.d.ts +77 -0
- package/dist/http/app.js +220 -0
- package/dist/http/error.filter.d.ts +21 -0
- package/dist/http/error.filter.js +85 -0
- package/dist/http/gateway.module.d.ts +15 -0
- package/dist/http/gateway.module.js +13 -0
- package/dist/http/health.controller.d.ts +26 -0
- package/dist/http/health.controller.js +79 -0
- package/dist/http/nest-logger.d.ts +20 -0
- package/dist/http/nest-logger.js +28 -0
- package/dist/http/plugin-dispatch.d.ts +68 -0
- package/dist/http/plugin-dispatch.js +200 -0
- package/dist/http/tokens.d.ts +131 -0
- package/dist/http/tokens.js +131 -0
- package/dist/http/zod.pipe.d.ts +8 -0
- package/dist/http/zod.pipe.js +14 -0
- package/dist/identity/accounts.d.ts +93 -0
- package/dist/identity/accounts.js +367 -0
- package/dist/identity/api-tokens.d.ts +73 -0
- package/dist/identity/api-tokens.js +197 -0
- package/dist/identity/audit.d.ts +23 -0
- package/dist/identity/audit.js +41 -0
- package/dist/identity/auth.controller.d.ts +40 -0
- package/dist/identity/auth.controller.js +130 -0
- package/dist/identity/auth.guard.d.ts +41 -0
- package/dist/identity/auth.guard.js +162 -0
- package/dist/identity/binding.d.ts +47 -0
- package/dist/identity/binding.js +84 -0
- package/dist/identity/cookies.d.ts +16 -0
- package/dist/identity/cookies.js +32 -0
- package/dist/identity/credential-check.d.ts +17 -0
- package/dist/identity/credential-check.js +24 -0
- package/dist/identity/enterprise-admin.d.ts +14 -0
- package/dist/identity/enterprise-admin.js +27 -0
- package/dist/identity/external.d.ts +106 -0
- package/dist/identity/external.js +337 -0
- package/dist/identity/hash.d.ts +2 -0
- package/dist/identity/hash.js +3 -0
- package/dist/identity/login.d.ts +6 -0
- package/dist/identity/login.js +20 -0
- package/dist/identity/me.controller.d.ts +61 -0
- package/dist/identity/me.controller.js +159 -0
- package/dist/identity/passwords.d.ts +6 -0
- package/dist/identity/passwords.js +37 -0
- package/dist/identity/principal.d.ts +56 -0
- package/dist/identity/principal.js +38 -0
- package/dist/identity/rate-limit.d.ts +55 -0
- package/dist/identity/rate-limit.js +121 -0
- package/dist/identity/sessions.d.ts +84 -0
- package/dist/identity/sessions.js +306 -0
- package/dist/identity/settings.d.ts +121 -0
- package/dist/identity/settings.js +248 -0
- package/dist/identity/sso.controller.d.ts +59 -0
- package/dist/identity/sso.controller.js +192 -0
- package/dist/identity/sso.d.ts +83 -0
- package/dist/identity/sso.js +202 -0
- package/dist/inbox/inbox.service.d.ts +43 -0
- package/dist/inbox/inbox.service.js +57 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.js +67 -0
- package/dist/kernel.d.ts +259 -0
- package/dist/kernel.js +1326 -0
- package/dist/log-config.d.ts +9 -0
- package/dist/log-config.js +27 -0
- package/dist/log-context.d.ts +32 -0
- package/dist/log-context.js +40 -0
- package/dist/log-loki.d.ts +34 -0
- package/dist/log-loki.js +145 -0
- package/dist/log.d.ts +79 -0
- package/dist/log.js +303 -0
- package/dist/models/prices.d.ts +53 -0
- package/dist/models/prices.js +121 -0
- package/dist/models/purposes.controller.d.ts +19 -0
- package/dist/models/purposes.controller.js +56 -0
- package/dist/models/purposes.d.ts +140 -0
- package/dist/models/purposes.js +267 -0
- package/dist/net/proxy.d.ts +44 -0
- package/dist/net/proxy.js +162 -0
- package/dist/net/public-address.d.ts +60 -0
- package/dist/net/public-address.js +272 -0
- package/dist/net/safe-fetch.d.ts +38 -0
- package/dist/net/safe-fetch.js +140 -0
- package/dist/orgs/directory-sync.controller.d.ts +14 -0
- package/dist/orgs/directory-sync.controller.js +40 -0
- package/dist/orgs/directory-sync.d.ts +166 -0
- package/dist/orgs/directory-sync.js +558 -0
- package/dist/orgs/invitations.controller.d.ts +58 -0
- package/dist/orgs/invitations.controller.js +137 -0
- package/dist/orgs/invitations.service.d.ts +80 -0
- package/dist/orgs/invitations.service.js +217 -0
- package/dist/orgs/members.service.d.ts +63 -0
- package/dist/orgs/members.service.js +234 -0
- package/dist/orgs/offboarding.d.ts +141 -0
- package/dist/orgs/offboarding.js +398 -0
- package/dist/orgs/org-lock.d.ts +32 -0
- package/dist/orgs/org-lock.js +46 -0
- package/dist/orgs/org.guard.d.ts +35 -0
- package/dist/orgs/org.guard.js +95 -0
- package/dist/orgs/orgs.controller.d.ts +78 -0
- package/dist/orgs/orgs.controller.js +192 -0
- package/dist/orgs/orgs.service.d.ts +61 -0
- package/dist/orgs/orgs.service.js +209 -0
- package/dist/orgs/roles.d.ts +13 -0
- package/dist/orgs/roles.js +58 -0
- package/dist/orgs/settings.controller.d.ts +20 -0
- package/dist/orgs/settings.controller.js +53 -0
- package/dist/orgs/settings.service.d.ts +120 -0
- package/dist/orgs/settings.service.js +178 -0
- package/dist/packages/npm.d.ts +50 -0
- package/dist/packages/npm.js +218 -0
- package/dist/packages/provenance.d.ts +47 -0
- package/dist/packages/provenance.js +147 -0
- package/dist/packages/tgz.d.ts +76 -0
- package/dist/packages/tgz.js +225 -0
- package/dist/runtime/abort.d.ts +32 -0
- package/dist/runtime/abort.js +71 -0
- package/dist/runtime/checkpointer.d.ts +32 -0
- package/dist/runtime/checkpointer.js +243 -0
- package/dist/runtime/context/build.d.ts +254 -0
- package/dist/runtime/context/build.js +693 -0
- package/dist/runtime/context/claude-tools.d.ts +11 -0
- package/dist/runtime/context/claude-tools.js +24 -0
- package/dist/runtime/context/compaction.d.ts +26 -0
- package/dist/runtime/context/compaction.js +195 -0
- package/dist/runtime/context/pairing.d.ts +11 -0
- package/dist/runtime/context/pairing.js +82 -0
- package/dist/runtime/context/reminders.d.ts +5 -0
- package/dist/runtime/context/reminders.js +11 -0
- package/dist/runtime/context/skills.d.ts +105 -0
- package/dist/runtime/context/skills.js +283 -0
- package/dist/runtime/context/tokens.d.ts +19 -0
- package/dist/runtime/context/tokens.js +104 -0
- package/dist/runtime/conversation-lock.d.ts +40 -0
- package/dist/runtime/conversation-lock.js +97 -0
- package/dist/runtime/hook-calls.d.ts +84 -0
- package/dist/runtime/hook-calls.js +136 -0
- package/dist/runtime/langgraph-loop.d.ts +52 -0
- package/dist/runtime/langgraph-loop.js +247 -0
- package/dist/runtime/loop.d.ts +93 -0
- package/dist/runtime/loop.js +7 -0
- package/dist/runtime/maintenance.d.ts +80 -0
- package/dist/runtime/maintenance.js +148 -0
- package/dist/runtime/media-cache.d.ts +21 -0
- package/dist/runtime/media-cache.js +51 -0
- package/dist/runtime/model-call.d.ts +176 -0
- package/dist/runtime/model-call.js +1049 -0
- package/dist/runtime/providers.d.ts +86 -0
- package/dist/runtime/providers.js +224 -0
- package/dist/runtime/quota.d.ts +29 -0
- package/dist/runtime/quota.js +70 -0
- package/dist/runtime/run-finish.d.ts +45 -0
- package/dist/runtime/run-finish.js +69 -0
- package/dist/runtime/run-queue.d.ts +121 -0
- package/dist/runtime/run-queue.js +493 -0
- package/dist/runtime/run-worker.d.ts +138 -0
- package/dist/runtime/run-worker.js +806 -0
- package/dist/runtime/stream.controller.d.ts +34 -0
- package/dist/runtime/stream.controller.js +122 -0
- package/dist/runtime/stream.d.ts +63 -0
- package/dist/runtime/stream.js +262 -0
- package/dist/runtime/subagents/child.d.ts +80 -0
- package/dist/runtime/subagents/child.js +225 -0
- package/dist/runtime/subagents/definition.d.ts +103 -0
- package/dist/runtime/subagents/definition.js +392 -0
- package/dist/runtime/subagents/dispatch.d.ts +69 -0
- package/dist/runtime/subagents/dispatch.js +502 -0
- package/dist/runtime/subagents/limits.d.ts +23 -0
- package/dist/runtime/subagents/limits.js +34 -0
- package/dist/runtime/subagents/org-agents.d.ts +50 -0
- package/dist/runtime/subagents/org-agents.js +70 -0
- package/dist/runtime/subagents/preamble.d.ts +7 -0
- package/dist/runtime/subagents/preamble.js +11 -0
- package/dist/runtime/subagents/progress.d.ts +26 -0
- package/dist/runtime/subagents/progress.js +36 -0
- package/dist/runtime/subagents/roster.d.ts +55 -0
- package/dist/runtime/subagents/roster.js +111 -0
- package/dist/runtime/subagents/skill-fork.d.ts +123 -0
- package/dist/runtime/subagents/skill-fork.js +259 -0
- package/dist/runtime/usage-report.d.ts +163 -0
- package/dist/runtime/usage-report.js +551 -0
- package/dist/runtime/waits.d.ts +233 -0
- package/dist/runtime/waits.js +883 -0
- package/dist/sandbox/leases.d.ts +169 -0
- package/dist/sandbox/leases.js +515 -0
- package/dist/sandbox/lifecycle.d.ts +21 -0
- package/dist/sandbox/lifecycle.js +68 -0
- package/dist/sandbox/sandbox.service.d.ts +249 -0
- package/dist/sandbox/sandbox.service.js +908 -0
- package/dist/skills/catalog.d.ts +149 -0
- package/dist/skills/catalog.js +405 -0
- package/dist/skills/changes.controller.d.ts +56 -0
- package/dist/skills/changes.controller.js +136 -0
- package/dist/skills/changes.service.d.ts +175 -0
- package/dist/skills/changes.service.js +805 -0
- package/dist/skills/invocable.controller.d.ts +23 -0
- package/dist/skills/invocable.controller.js +50 -0
- package/dist/skills/invocation-keys.d.ts +23 -0
- package/dist/skills/invocation-keys.js +44 -0
- package/dist/skills/invoke.d.ts +135 -0
- package/dist/skills/invoke.js +286 -0
- package/dist/skills/package.d.ts +87 -0
- package/dist/skills/package.js +227 -0
- package/dist/skills/skills.controller.d.ts +40 -0
- package/dist/skills/skills.controller.js +110 -0
- package/dist/skills/skills.service.d.ts +192 -0
- package/dist/skills/skills.service.js +567 -0
- package/dist/storage/prefix.d.ts +36 -0
- package/dist/storage/prefix.js +104 -0
- package/dist/storage/purge.d.ts +27 -0
- package/dist/storage/purge.js +75 -0
- package/dist/storage/storage.service.d.ts +44 -0
- package/dist/storage/storage.service.js +145 -0
- package/dist/text.d.ts +13 -0
- package/dist/text.js +13 -0
- package/dist/tools/approvals.controller.d.ts +53 -0
- package/dist/tools/approvals.controller.js +128 -0
- package/dist/tools/approvals.service.d.ts +172 -0
- package/dist/tools/approvals.service.js +568 -0
- package/dist/tools/breaker.d.ts +28 -0
- package/dist/tools/breaker.js +72 -0
- package/dist/tools/builtins/agent.d.ts +81 -0
- package/dist/tools/builtins/agent.js +223 -0
- package/dist/tools/builtins/ask-user.d.ts +28 -0
- package/dist/tools/builtins/ask-user.js +139 -0
- package/dist/tools/builtins/read-tool-output.d.ts +18 -0
- package/dist/tools/builtins/read-tool-output.js +108 -0
- package/dist/tools/builtins/sandbox/bash.d.ts +16 -0
- package/dist/tools/builtins/sandbox/bash.js +137 -0
- package/dist/tools/builtins/sandbox/common.d.ts +116 -0
- package/dist/tools/builtins/sandbox/common.js +306 -0
- package/dist/tools/builtins/sandbox/deliver.d.ts +5 -0
- package/dist/tools/builtins/sandbox/deliver.js +180 -0
- package/dist/tools/builtins/sandbox/edit.d.ts +4 -0
- package/dist/tools/builtins/sandbox/edit.js +98 -0
- package/dist/tools/builtins/sandbox/glob-match.d.ts +25 -0
- package/dist/tools/builtins/sandbox/glob-match.js +240 -0
- package/dist/tools/builtins/sandbox/glob.d.ts +4 -0
- package/dist/tools/builtins/sandbox/glob.js +104 -0
- package/dist/tools/builtins/sandbox/grep.d.ts +9 -0
- package/dist/tools/builtins/sandbox/grep.js +133 -0
- package/dist/tools/builtins/sandbox/index.d.ts +12 -0
- package/dist/tools/builtins/sandbox/index.js +27 -0
- package/dist/tools/builtins/sandbox/names.d.ts +7 -0
- package/dist/tools/builtins/sandbox/names.js +20 -0
- package/dist/tools/builtins/sandbox/read.d.ts +8 -0
- package/dist/tools/builtins/sandbox/read.js +121 -0
- package/dist/tools/builtins/sandbox/write.d.ts +4 -0
- package/dist/tools/builtins/sandbox/write.js +88 -0
- package/dist/tools/builtins/skill-load.d.ts +53 -0
- package/dist/tools/builtins/skill-load.js +446 -0
- package/dist/tools/builtins/skill-write.d.ts +59 -0
- package/dist/tools/builtins/skill-write.js +244 -0
- package/dist/tools/builtins/tool-search.d.ts +31 -0
- package/dist/tools/builtins/tool-search.js +171 -0
- package/dist/tools/connector-content.controller.d.ts +34 -0
- package/dist/tools/connector-content.controller.js +78 -0
- package/dist/tools/connector-content.d.ts +104 -0
- package/dist/tools/connector-content.js +267 -0
- package/dist/tools/connectors.controller.d.ts +210 -0
- package/dist/tools/connectors.controller.js +234 -0
- package/dist/tools/connectors.service.d.ts +303 -0
- package/dist/tools/connectors.service.js +1102 -0
- package/dist/tools/credentials.d.ts +139 -0
- package/dist/tools/credentials.js +351 -0
- package/dist/tools/drift.d.ts +141 -0
- package/dist/tools/drift.js +539 -0
- package/dist/tools/elicitation.d.ts +81 -0
- package/dist/tools/elicitation.js +266 -0
- package/dist/tools/endpoint.d.ts +18 -0
- package/dist/tools/endpoint.js +115 -0
- package/dist/tools/info.d.ts +67 -0
- package/dist/tools/info.js +113 -0
- package/dist/tools/naming.d.ts +27 -0
- package/dist/tools/naming.js +47 -0
- package/dist/tools/oauth/discovery.d.ts +65 -0
- package/dist/tools/oauth/discovery.js +307 -0
- package/dist/tools/oauth/flow.d.ts +63 -0
- package/dist/tools/oauth/flow.js +237 -0
- package/dist/tools/oauth/oauth.controller.d.ts +31 -0
- package/dist/tools/oauth/oauth.controller.js +95 -0
- package/dist/tools/oauth/tokens.d.ts +111 -0
- package/dist/tools/oauth/tokens.js +258 -0
- package/dist/tools/outputs.d.ts +61 -0
- package/dist/tools/outputs.js +156 -0
- package/dist/tools/pipeline.d.ts +322 -0
- package/dist/tools/pipeline.js +1236 -0
- package/dist/tools/policy.d.ts +60 -0
- package/dist/tools/policy.js +236 -0
- package/dist/tools/questions.controller.d.ts +24 -0
- package/dist/tools/questions.controller.js +58 -0
- package/dist/tools/questions.service.d.ts +103 -0
- package/dist/tools/questions.service.js +295 -0
- package/dist/tools/redact.d.ts +20 -0
- package/dist/tools/redact.js +60 -0
- package/dist/tools/registry.d.ts +246 -0
- package/dist/tools/registry.js +481 -0
- package/dist/tools/resources.d.ts +16 -0
- package/dist/tools/resources.js +76 -0
- package/dist/tools/specs.d.ts +14 -0
- package/dist/tools/specs.js +31 -0
- package/dist/tools/validate.d.ts +20 -0
- package/dist/tools/validate.js +183 -0
- package/dist/tools/view.d.ts +43 -0
- package/dist/tools/view.js +225 -0
- package/dist/ui/embed-token.d.ts +31 -0
- package/dist/ui/embed-token.js +132 -0
- package/dist/ui/registry.d.ts +58 -0
- package/dist/ui/registry.js +90 -0
- package/dist/ui/ui-stream.d.ts +37 -0
- package/dist/ui/ui-stream.js +162 -0
- package/dist/ui/views.controller.d.ts +81 -0
- package/dist/ui/views.controller.js +261 -0
- package/dist/ui/views.d.ts +155 -0
- package/dist/ui/views.js +306 -0
- package/dist/version.d.ts +5 -0
- package/dist/version.js +6 -0
- package/package.json +69 -3
- package/src/migrations/0001_init.sql +8 -0
- package/src/migrations/0002_secrets.sql +8 -0
- package/src/migrations/0003_identity.sql +117 -0
- package/src/migrations/0004_plugin_org_config_nullable.sql +3 -0
- package/src/migrations/0005_agents.sql +27 -0
- package/src/migrations/0006_conversations.sql +37 -0
- package/src/migrations/0007_checkpoints.sql +26 -0
- package/src/migrations/0008_runs_trigger_seq.sql +3 -0
- package/src/migrations/0009_runs_active_unique.sql +4 -0
- package/src/migrations/0010_tools.sql +125 -0
- package/src/migrations/0011_approvals_spec.sql +10 -0
- package/src/migrations/0012_run_waits.sql +24 -0
- package/src/migrations/0013_app_tools_source.sql +4 -0
- package/src/migrations/0014_tool_specs.sql +8 -0
- package/src/migrations/0015_tool_drift.sql +20 -0
- package/src/migrations/0016_plugin_permissions.sql +11 -0
- package/src/migrations/0017_identity_sources.sql +6 -0
- package/src/migrations/0018_channels.sql +74 -0
- package/src/migrations/0019_identity_trust.sql +17 -0
- package/src/migrations/0020_channel_card_lookup.sql +10 -0
- package/src/migrations/0021_org_storage_purge.sql +9 -0
- package/src/migrations/0022_attachments.sql +29 -0
- package/src/migrations/0023_sandboxes.sql +37 -0
- package/src/migrations/0024_skills.sql +46 -0
- package/src/migrations/0025_skills_catalog_idx.sql +6 -0
- package/src/migrations/0026_skill_changes.sql +34 -0
- package/src/migrations/0027_model_prices.sql +18 -0
- package/src/migrations/0028_evals.sql +105 -0
- package/src/migrations/0029_model_purposes.sql +18 -0
- package/src/migrations/0030_plugin_installs.sql +73 -0
- package/src/migrations/0031_plugin_snapshot_source.sql +6 -0
- package/src/migrations/0032_plugin_deployment_configs.sql +10 -0
- package/src/migrations/0033_rename_apps_to_connectors.sql +25 -0
- package/src/migrations/0034_directory.sql +49 -0
- package/src/migrations/0035_catalogs.sql +40 -0
- package/src/migrations/0036_app_import_staging.sql +17 -0
- package/src/migrations/0037_directory_installs.sql +92 -0
- package/src/migrations/0038_channel_inbound_mode.sql +6 -0
- package/src/migrations/0039_channel_card_pending.sql +8 -0
- package/src/migrations/0040_usage_report_sweep.sql +10 -0
- package/src/migrations/0041_rename_plugins_to_extensions.sql +38 -0
- package/src/migrations/0042_rename_directory_to_catalog.sql +80 -0
- package/src/migrations/0043_rename_app_installs_to_plugin_installs.sql +20 -0
- package/src/migrations/0044_catalog_one_kind.sql +90 -0
- package/src/migrations/0045_plugin_dependencies.sql +9 -0
- package/src/migrations/0046_git_sources.sql +25 -0
- package/src/migrations/0047_marketplace_entry_heads.sql +7 -0
- package/src/migrations/0048_marketplace_entry_trees.sql +8 -0
- package/src/migrations/0049_plugin_upgrades.sql +37 -0
- package/src/migrations/0050_bootstrap.sql +53 -0
- package/src/migrations/0051_claude_compat.sql +26 -0
- package/src/migrations/0052_conversations_account_agent_idx.sql +5 -0
- package/src/migrations/0053_subagents.sql +73 -0
- package/src/migrations/0054_subagent_wait_expiry.sql +7 -0
- package/src/migrations/0055_subagent_parent_indexes.sql +10 -0
- package/src/migrations/0056_approval_reason.sql +3 -0
- package/src/migrations/0057_conversation_titles.sql +11 -0
- package/src/migrations/0058_conversation_selection.sql +4 -0
- package/src/migrations/0059_conversation_queue.sql +22 -0
- package/src/migrations/0060_file_previewers.sql +10 -0
- package/src/migrations/0061_queue_selection.sql +5 -0
- package/src/migrations/0062_queued_skill.sql +8 -0
|
@@ -0,0 +1,882 @@
|
|
|
1
|
+
import { isSafePackagePath, marketplaceEntryVersion, } from "@capybara-hq/plugin-sdk";
|
|
2
|
+
import { sql } from "kysely";
|
|
3
|
+
import { ApiError } from "../errors.js";
|
|
4
|
+
import { enabledExtensionsByOrg } from "../extensions/enabled.js";
|
|
5
|
+
import { audit } from "../identity/audit.js";
|
|
6
|
+
import { lockEnterpriseAdminsAndAuthorize } from "../identity/enterprise-admin.js";
|
|
7
|
+
import { sameRepository } from "../packages/provenance.js";
|
|
8
|
+
import { endpointCredentialIssues } from "../tools/endpoint.js";
|
|
9
|
+
import { expandGitUrl, GitSourceError, gitErrorDetails, gitLocationIdentity, normalGitPath, readGitTreeAndOids, resolveGitCommit, } from "./git-source.js";
|
|
10
|
+
import { marketplaceNetPolicy, marketplaceUrlProblem } from "./marketplace-fetch.js";
|
|
11
|
+
import { entryCompatible, entryContentChanged, entrySourceHost, HOST_VERSIONS, hostAllowed, loadMarketplaceEntry, pluginNameFrom, sourceEffective, } from "./marketplaces.service.js";
|
|
12
|
+
import { cancelPendingRequests } from "./requests.service.js";
|
|
13
|
+
import { schemaHasPath } from "./schema-path.js";
|
|
14
|
+
import { compareVersions, newestFirst, sameRelease, withBuild } from "./versions.js";
|
|
15
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
16
|
+
const notFound = () => new ApiError("catalog.item_not_found", "Catalog item not found");
|
|
17
|
+
/** An archive URL as recorded (origin + path): a query string or fragment may carry a token. */
|
|
18
|
+
export const archiveRef = (url) => {
|
|
19
|
+
const u = new URL(url);
|
|
20
|
+
return `${u.origin}${u.pathname}`;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* A git read failure as an API error (the network rule's code when net/ refused), with details
|
|
24
|
+
* naming the host, repo, ref or commit and refused entry ({@link gitErrorDetails}).
|
|
25
|
+
*/
|
|
26
|
+
function gitApiError(e, loc) {
|
|
27
|
+
if (!(e instanceof GitSourceError))
|
|
28
|
+
return e;
|
|
29
|
+
return new ApiError(e.netCode ?? e.code, e.message, [gitErrorDetails(e, loc)]);
|
|
30
|
+
}
|
|
31
|
+
/** `<host>/<owner>` of a repo URL (the publisher of a skills-only plugin without a marketplace owner). */
|
|
32
|
+
export function repoOwner(url) {
|
|
33
|
+
const u = new URL(url);
|
|
34
|
+
const segs = u.pathname.split("/").filter((s) => s !== "");
|
|
35
|
+
return segs.length > 1 ? `${u.host}/${segs[0]}` : u.host;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The version of a git plugin that has none (spec §5.3): `0.0.0-git.g<sha12>`. The `g` keeps the
|
|
39
|
+
* prerelease identifier alphanumeric: twelve hex characters that happen to be all digits with a
|
|
40
|
+
* leading zero are not valid semver.
|
|
41
|
+
*/
|
|
42
|
+
export const gitFallbackVersion = (commit) => `0.0.0-git.g${commit.slice(0, 12)}`;
|
|
43
|
+
/** The recorded git location and the `source_ref` text of a git import. */
|
|
44
|
+
function gitSource(loc, read, pinned) {
|
|
45
|
+
const commit = read.commit;
|
|
46
|
+
const u = new URL(loc.url);
|
|
47
|
+
const path = normalGitPath(loc.path);
|
|
48
|
+
return {
|
|
49
|
+
detail: {
|
|
50
|
+
host: u.host,
|
|
51
|
+
repo: u.pathname.replace(/^\/+/, ""),
|
|
52
|
+
path,
|
|
53
|
+
...(loc.ref !== undefined && !pinned && { ref: loc.ref }),
|
|
54
|
+
commit,
|
|
55
|
+
followsBranch: !pinned,
|
|
56
|
+
...(read.tree !== undefined && { tree: read.tree }),
|
|
57
|
+
},
|
|
58
|
+
ref: `${u.origin}${u.pathname}@${commit}${path === "." ? "" : `:${path}`}`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The object storage prefix of one import (relative to `deployment/`):
|
|
63
|
+
* `directory/<itemId>/<version>/<importId>/`. Each import gets a new `importId`, so the losing one of
|
|
64
|
+
* two concurrent imports of the same version never overwrites the committed version's files; reads
|
|
65
|
+
* always go through the `object_prefix` stored on the version row. Build metadata's `+` is written
|
|
66
|
+
* as `_` (a `+` in a key can turn into a space through URL query encoding).
|
|
67
|
+
*/
|
|
68
|
+
export const catalogObjectPrefix = (itemId, version, importId) => `directory/${itemId}/${version.replace(/\+/g, "_")}/${importId}/`;
|
|
69
|
+
/** Default / largest page size of the list. */
|
|
70
|
+
export const CATALOG_LIST_DEFAULT_LIMIT = 200;
|
|
71
|
+
export const CATALOG_LIST_MAX_LIMIT = 1000;
|
|
72
|
+
const PUT_CONCURRENCY = 8;
|
|
73
|
+
/**
|
|
74
|
+
* Enterprise catalog items (D9 / C21 / C22, `/api/v1/admin/catalog/*`, enterprise admins only). One
|
|
75
|
+
* kind: every item is a plugin (spec §4), imported through {@link PluginImport} (prepare -> confirm,
|
|
76
|
+
* one review); this service lists and shows items, changes policy, deletes, installs an item's
|
|
77
|
+
* extension on the deployment, and prepares marketplace imports. Stateless, shared by every replica.
|
|
78
|
+
*/
|
|
79
|
+
export class CatalogService {
|
|
80
|
+
d;
|
|
81
|
+
constructor(d) {
|
|
82
|
+
this.d = d;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Prepares the import of one marketplace entry (C25 / C26, spec §5.2): `version` defaults to the
|
|
86
|
+
* newest compatible version the marketplace lists for `name`; the entry must be compatible with
|
|
87
|
+
* the host (`marketplace.incompatible`); an npm source is downloaded from the configured registry
|
|
88
|
+
* and an archive source from its URL, both always verified against the marketplace's integrity
|
|
89
|
+
* (`marketplace.integrity_mismatch`, nothing written). A `path` source is read from the git
|
|
90
|
+
* marketplace's repository at its last refreshed commit (an http marketplace has none:
|
|
91
|
+
* `marketplace.path_unsupported`); a `git` source at its pinned `commit`, else at its `ref`'s head
|
|
92
|
+
* now, recorded; both through {@link PluginImport.prepareGit} with source `git` and
|
|
93
|
+
* `source_detail` (host, repo, path, ref, commit, followsBranch). A git / archive host outside
|
|
94
|
+
* `marketplaces.allowedHosts` -> `marketplace.host_not_allowed`. The marketplace token goes only
|
|
95
|
+
* to the marketplace URL's origin. The package must match the entry (name, version, npm package:
|
|
96
|
+
* `marketplace.package_mismatch`), then the same review as any plugin import
|
|
97
|
+
* ({@link PluginImport.prepare}) with source `marketplace` (ref `<marketplaceId>:<npm>@<version>`
|
|
98
|
+
* or `<marketplaceId>:<archive URL>`; git: `<marketplaceId>:<repo>@<commit>[:<path>]`), confirmed with
|
|
99
|
+
* `POST /api/v1/admin/catalog/imports/:stagingId/confirm`. Marketplace missing ->
|
|
100
|
+
* `marketplace.not_found`; disabled, or the entry / version missing -> `marketplace.entry_not_found`.
|
|
101
|
+
*/
|
|
102
|
+
async importFromMarketplace(actor, input, opts = {}) {
|
|
103
|
+
const config = this.d.config();
|
|
104
|
+
const { entry, marketplace, claude } = await loadMarketplaceEntry(this.d.db, config, input.marketplaceId, input.name, input.version);
|
|
105
|
+
const version = marketplaceEntryVersion(entry);
|
|
106
|
+
if (!entryCompatible(entry)) {
|
|
107
|
+
throw new ApiError("marketplace.incompatible", `${entry.name}${version !== null ? `@${version}` : ""} needs kernel >= ${entry.minKernel}; this deployment runs kernel ${HOST_VERSIONS.kernel}`);
|
|
108
|
+
}
|
|
109
|
+
const src = entry.source;
|
|
110
|
+
const host = entrySourceHost(entry);
|
|
111
|
+
if (host !== undefined && !hostAllowed(host, config)) {
|
|
112
|
+
throw new ApiError("marketplace.host_not_allowed", `Marketplace entry ${entry.name} downloads from ${host}, which is not in marketplaces.allowedHosts`, [{ host, entry: entry.name }]);
|
|
113
|
+
}
|
|
114
|
+
const prepareOpts = {
|
|
115
|
+
...(opts.actorTokenId !== undefined && { actorTokenId: opts.actorTokenId }),
|
|
116
|
+
expect: {
|
|
117
|
+
marketplace: {
|
|
118
|
+
name: entry.name,
|
|
119
|
+
version,
|
|
120
|
+
...("npm" in src && { packageName: src.npm.package }),
|
|
121
|
+
...(entry.permissions !== undefined && { permissions: entry.permissions.keys }),
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
let staged;
|
|
126
|
+
if ("npm" in src || "archive" in src) {
|
|
127
|
+
let got;
|
|
128
|
+
let ref;
|
|
129
|
+
let detail;
|
|
130
|
+
let provenance;
|
|
131
|
+
if ("npm" in src) {
|
|
132
|
+
got = await this.d.installs.fetchMarketplacePackage({ package: src.npm.package, version: src.npm.version, integrity: src.npm.integrity }, opts.signal);
|
|
133
|
+
ref = `${input.marketplaceId}:${src.npm.package}@${src.npm.version}`;
|
|
134
|
+
detail = { registry: new URL(config.packages.npmRegistry).origin };
|
|
135
|
+
const p = await this.d.installs.fetchProvenance({ package: src.npm.package, version: src.npm.version, integrity: got.integrity }, opts.signal);
|
|
136
|
+
// a git marketplace declares its repository: an attestation from another one is a warning
|
|
137
|
+
const mismatch = p.checked && marketplace.form === "git" && !sameRepository(p.repository, marketplace.url);
|
|
138
|
+
provenance = { provenance: p, ...(mismatch && { warning: "repository_mismatch" }) };
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
got = await this.d.fetchArchive(src.archive, opts.signal);
|
|
142
|
+
ref = `${input.marketplaceId}:${archiveRef(src.archive.url)}`;
|
|
143
|
+
detail = { url: archiveRef(src.archive.url) };
|
|
144
|
+
}
|
|
145
|
+
staged = await this.d.pluginImport.prepare(actor, got.bytes, {
|
|
146
|
+
kind: "marketplace",
|
|
147
|
+
ref,
|
|
148
|
+
integrity: got.integrity,
|
|
149
|
+
marketplaceId: input.marketplaceId,
|
|
150
|
+
detail,
|
|
151
|
+
}, {
|
|
152
|
+
...prepareOpts,
|
|
153
|
+
...(provenance !== undefined && { provenance }),
|
|
154
|
+
// an archive without plugin.json may be skills only (named by the entry, spec §5.3)
|
|
155
|
+
...("archive" in src && {
|
|
156
|
+
fallback: {
|
|
157
|
+
name: entry.name,
|
|
158
|
+
...(entry.version !== undefined && { version: entry.version }),
|
|
159
|
+
publisher: marketplace.ownerName ?? new URL(src.archive.url).host,
|
|
160
|
+
},
|
|
161
|
+
}),
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
// path: the marketplace's own repo at its last refresh; git: the entry's repo
|
|
166
|
+
let loc;
|
|
167
|
+
if ("path" in src) {
|
|
168
|
+
if (marketplace.form !== "git") {
|
|
169
|
+
throw new ApiError("marketplace.path_unsupported", `Marketplace entry ${entry.name} is a path in the marketplace's repository, but this marketplace is fetched over http; add the repository as a git marketplace`, [{ entry: entry.name, path: src.path }]);
|
|
170
|
+
}
|
|
171
|
+
if (marketplace.lastCommit === null) {
|
|
172
|
+
// the URL or ref changed after the entries were read: their commit is unknown
|
|
173
|
+
throw new ApiError("conflict", "The marketplace's repository or ref changed since its last refresh; refresh it before importing");
|
|
174
|
+
}
|
|
175
|
+
loc = { url: marketplace.url, commit: marketplace.lastCommit, path: src.path };
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
loc = {
|
|
179
|
+
url: src.git.url,
|
|
180
|
+
...(src.git.ref !== undefined && { ref: src.git.ref }),
|
|
181
|
+
...(src.git.commit !== undefined && { commit: src.git.commit }),
|
|
182
|
+
...(src.git.path !== undefined && { path: src.git.path }),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
const token = marketplace.builtin
|
|
186
|
+
? undefined
|
|
187
|
+
: await this.d.marketplaceToken(input.marketplaceId);
|
|
188
|
+
const read = await this.readGit(loc, {
|
|
189
|
+
...(token !== undefined && { token, tokenOrigin: new URL(marketplace.url).origin }),
|
|
190
|
+
...(opts.signal !== undefined && { signal: opts.signal }),
|
|
191
|
+
});
|
|
192
|
+
// path entries follow the marketplace's ref; git entries follow theirs unless a commit is pinned
|
|
193
|
+
const pinned = "git" in src && src.git.commit !== undefined;
|
|
194
|
+
const where = gitSource("path" in src && marketplace.ref !== null ? { ...loc, ref: marketplace.ref } : loc, read, pinned);
|
|
195
|
+
staged = await this.d.pluginImport.prepareGit(actor, read.files, {
|
|
196
|
+
kind: "git",
|
|
197
|
+
ref: `${input.marketplaceId}:${where.ref}`,
|
|
198
|
+
marketplaceId: input.marketplaceId,
|
|
199
|
+
detail: where.detail,
|
|
200
|
+
}, {
|
|
201
|
+
...prepareOpts,
|
|
202
|
+
fallback: {
|
|
203
|
+
name: entry.name,
|
|
204
|
+
version: entry.version ?? gitFallbackVersion(read.commit),
|
|
205
|
+
publisher: marketplace.ownerName ?? repoOwner(loc.url),
|
|
206
|
+
...(entry.version !== undefined && { entryVersion: entry.version }),
|
|
207
|
+
},
|
|
208
|
+
...(claude !== undefined && { claude }),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
stagingId: staged.stagingId,
|
|
213
|
+
review: staged.review,
|
|
214
|
+
permissionsMismatch: staged.permissionsMismatch,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Prepares the import of one plugin straight from git (spec §5.1, a one-off like an upload: no
|
|
219
|
+
* marketplace is created and no updates are tracked): `url` is an https git URL or `owner/repo`
|
|
220
|
+
* (GitHub), `ref` a branch / tag / commit (default the remote HEAD), `path` the plugin's directory
|
|
221
|
+
* (default the root). The commit is resolved and recorded (`source_detail`), then the same review
|
|
222
|
+
* as any plugin import. A skills-only directory without plugin.json is named after the last
|
|
223
|
+
* segment of `path` (else of the repo URL) and published by the repo owner.
|
|
224
|
+
*/
|
|
225
|
+
async importFromGit(actor, input, opts = {}) {
|
|
226
|
+
const url = expandGitUrl(input.url);
|
|
227
|
+
const problem = marketplaceUrlProblem(url, this.d.config().marketplaces.allowInsecureLoopback);
|
|
228
|
+
if (problem !== null)
|
|
229
|
+
throw new ApiError("validation_failed", `url: ${problem}`);
|
|
230
|
+
if (input.path !== undefined) {
|
|
231
|
+
const p = normalGitPath(input.path);
|
|
232
|
+
if (input.path.startsWith("/") || (p !== "." && !isSafePackagePath(p))) {
|
|
233
|
+
throw new ApiError("validation_failed", `path: "${input.path}" must be . or a relative path inside the repository`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const loc = {
|
|
237
|
+
url,
|
|
238
|
+
...(input.ref !== undefined && { ref: input.ref }),
|
|
239
|
+
...(input.path !== undefined && { path: input.path }),
|
|
240
|
+
};
|
|
241
|
+
const read = await this.readGit(loc, opts.signal !== undefined ? { signal: opts.signal } : {});
|
|
242
|
+
const pinned = input.ref !== undefined && /^[0-9a-f]{40}$/i.test(input.ref);
|
|
243
|
+
const where = gitSource(loc, read, pinned);
|
|
244
|
+
const segment = where.detail.path === "."
|
|
245
|
+
? (new URL(url).pathname
|
|
246
|
+
.split("/")
|
|
247
|
+
.filter((s) => s !== "")
|
|
248
|
+
.pop() ?? "plugin")
|
|
249
|
+
: where.detail.path.split("/").pop();
|
|
250
|
+
const r = await this.d.pluginImport.prepareGit(actor, read.files, { kind: "git", ref: where.ref, detail: where.detail }, {
|
|
251
|
+
...(opts.actorTokenId !== undefined && { actorTokenId: opts.actorTokenId }),
|
|
252
|
+
fallback: {
|
|
253
|
+
name: pluginNameFrom(segment),
|
|
254
|
+
version: gitFallbackVersion(read.commit),
|
|
255
|
+
publisher: repoOwner(url),
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
return { stagingId: r.stagingId, review: r.review };
|
|
259
|
+
}
|
|
260
|
+
/** Resolves the commit and reads the plugin's files; git failures become API errors. */
|
|
261
|
+
async readGit(loc, o) {
|
|
262
|
+
const config = this.d.config();
|
|
263
|
+
const options = {
|
|
264
|
+
net: marketplaceNetPolicy(config),
|
|
265
|
+
maxBytes: config.packages.maxBytes,
|
|
266
|
+
signal: o.signal ?? new AbortController().signal,
|
|
267
|
+
...(o.token !== undefined && { token: o.token }),
|
|
268
|
+
...(o.tokenOrigin !== undefined && { tokenOrigin: o.tokenOrigin }),
|
|
269
|
+
};
|
|
270
|
+
try {
|
|
271
|
+
const commit = await resolveGitCommit(loc, options);
|
|
272
|
+
const dir = normalGitPath(loc.path);
|
|
273
|
+
const read = await readGitTreeAndOids({ ...loc, commit }, { ...options, trees: () => [dir] });
|
|
274
|
+
return { commit, files: read.files, tree: read.trees.get(dir) };
|
|
275
|
+
}
|
|
276
|
+
catch (e) {
|
|
277
|
+
throw gitApiError(e, loc);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Item list (deleted items excluded by default), ordered by name, id. Paging: `limit`
|
|
282
|
+
* defaults to {@link CATALOG_LIST_DEFAULT_LIMIT}, at most {@link CATALOG_LIST_MAX_LIMIT}, `offset`
|
|
283
|
+
* defaults to 0; use the returned array directly when only the first page is needed.
|
|
284
|
+
*/
|
|
285
|
+
async list(q = {}) {
|
|
286
|
+
return (await this.page(q)).items;
|
|
287
|
+
}
|
|
288
|
+
/** Same as {@link list}, plus the offset of the next page (null when there is none). */
|
|
289
|
+
async page(q = {}) {
|
|
290
|
+
const limit = Math.min(Math.max(1, q.limit ?? CATALOG_LIST_DEFAULT_LIMIT), CATALOG_LIST_MAX_LIMIT);
|
|
291
|
+
const offset = Math.max(0, q.offset ?? 0);
|
|
292
|
+
let query = this.d.db
|
|
293
|
+
.selectFrom("catalog_items")
|
|
294
|
+
.selectAll()
|
|
295
|
+
.orderBy("name")
|
|
296
|
+
.orderBy("id")
|
|
297
|
+
.limit(limit + 1)
|
|
298
|
+
.offset(offset);
|
|
299
|
+
if (!q.includeDeleted)
|
|
300
|
+
query = query.where("deleted_at", "is", null);
|
|
301
|
+
const rows = await query.execute();
|
|
302
|
+
const nextOffset = rows.length > limit ? offset + limit : null;
|
|
303
|
+
const items = rows.slice(0, limit);
|
|
304
|
+
if (items.length === 0)
|
|
305
|
+
return { items: [], nextOffset: null };
|
|
306
|
+
const versions = await this.d.db
|
|
307
|
+
.selectFrom("catalog_item_versions")
|
|
308
|
+
.select([
|
|
309
|
+
"item_id",
|
|
310
|
+
"version",
|
|
311
|
+
"source",
|
|
312
|
+
"source_ref",
|
|
313
|
+
"extension_sha256",
|
|
314
|
+
"integrity",
|
|
315
|
+
"source_detail",
|
|
316
|
+
"imported_at",
|
|
317
|
+
])
|
|
318
|
+
.where("item_id", "in", items.map((i) => i.id))
|
|
319
|
+
.execute();
|
|
320
|
+
const byItem = new Map();
|
|
321
|
+
for (const v of versions) {
|
|
322
|
+
const list = byItem.get(v.item_id) ?? [];
|
|
323
|
+
list.push(v);
|
|
324
|
+
byItem.set(v.item_id, list);
|
|
325
|
+
}
|
|
326
|
+
const installs = await countInstalls(this.d.db, items, byItem);
|
|
327
|
+
const latest = await marketplaceLatest(this.d.db, this.d.config(), items, byItem);
|
|
328
|
+
return {
|
|
329
|
+
items: items.map((i) => itemView(i, byItem.get(i.id) ?? [], installs.get(i.id) ?? 0, latest.get(i.id))),
|
|
330
|
+
nextOffset,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
/** Item detail (with every version; deleted items too). Missing -> `catalog.item_not_found`. */
|
|
334
|
+
async get(itemId) {
|
|
335
|
+
if (!UUID_RE.test(itemId))
|
|
336
|
+
throw notFound();
|
|
337
|
+
const item = await this.d.db
|
|
338
|
+
.selectFrom("catalog_items")
|
|
339
|
+
.selectAll()
|
|
340
|
+
.where("id", "=", itemId)
|
|
341
|
+
.executeTakeFirst();
|
|
342
|
+
if (!item)
|
|
343
|
+
throw notFound();
|
|
344
|
+
const rows = await this.d.db
|
|
345
|
+
.selectFrom("catalog_item_versions")
|
|
346
|
+
.selectAll()
|
|
347
|
+
.where("item_id", "=", itemId)
|
|
348
|
+
.execute();
|
|
349
|
+
rows.sort((a, b) => newestFirst({ version: a.version, importedAt: a.imported_at }, { version: b.version, importedAt: b.imported_at }));
|
|
350
|
+
const byItem = new Map([[item.id, rows]]);
|
|
351
|
+
const installs = await countInstalls(this.d.db, [item], byItem);
|
|
352
|
+
const latest = await marketplaceLatest(this.d.db, this.d.config(), [item], byItem);
|
|
353
|
+
return {
|
|
354
|
+
item: {
|
|
355
|
+
...itemView(item, rows, installs.get(item.id) ?? 0, latest.get(item.id)),
|
|
356
|
+
packageName: item.package_name,
|
|
357
|
+
deletedAt: item.deleted_at?.toISOString() ?? null,
|
|
358
|
+
},
|
|
359
|
+
versions: rows.map((v) => ({
|
|
360
|
+
version: v.version,
|
|
361
|
+
source: v.source,
|
|
362
|
+
sourceRef: v.source_ref,
|
|
363
|
+
integrity: v.integrity,
|
|
364
|
+
contentDigest: v.content_digest,
|
|
365
|
+
sourceDetail: v.source_detail ?? null,
|
|
366
|
+
manifest: v.manifest,
|
|
367
|
+
files: v.files,
|
|
368
|
+
size: Number(v.size),
|
|
369
|
+
extensionSha256: v.extension_sha256,
|
|
370
|
+
sourceFormat: v.source_format,
|
|
371
|
+
translation: v.translation ?? null,
|
|
372
|
+
licenseText: v.license_text,
|
|
373
|
+
licenseTruncated: v.license_truncated,
|
|
374
|
+
importedBy: v.imported_by,
|
|
375
|
+
importedAt: v.imported_at.toISOString(),
|
|
376
|
+
})),
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Changes the policy (audit `catalog.policy_changed`, not written when unchanged). Deleted /
|
|
381
|
+
* missing -> `catalog.item_not_found`.
|
|
382
|
+
*/
|
|
383
|
+
async setPolicy(actor, itemId, policy, opts = {}) {
|
|
384
|
+
if (!UUID_RE.test(itemId))
|
|
385
|
+
throw notFound();
|
|
386
|
+
await this.d.db.transaction().execute(async (trx) => {
|
|
387
|
+
await lockEnterpriseAdminsAndAuthorize(trx, actor);
|
|
388
|
+
const row = await trx
|
|
389
|
+
.selectFrom("catalog_items")
|
|
390
|
+
.select(["name", "policy"])
|
|
391
|
+
.where("id", "=", itemId)
|
|
392
|
+
.where("deleted_at", "is", null)
|
|
393
|
+
.forUpdate()
|
|
394
|
+
.executeTakeFirst();
|
|
395
|
+
if (!row)
|
|
396
|
+
throw notFound();
|
|
397
|
+
if (row.policy === policy)
|
|
398
|
+
return;
|
|
399
|
+
await trx
|
|
400
|
+
.updateTable("catalog_items")
|
|
401
|
+
.set({ policy, updated_at: sql `now()` })
|
|
402
|
+
.where("id", "=", itemId)
|
|
403
|
+
.execute();
|
|
404
|
+
await audit(trx, {
|
|
405
|
+
actorAccountId: actor,
|
|
406
|
+
...(opts.actorTokenId !== undefined && { actorTokenId: opts.actorTokenId }),
|
|
407
|
+
action: "catalog.policy_changed",
|
|
408
|
+
target: itemId,
|
|
409
|
+
detail: { name: row.name, from: row.policy, to: policy },
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
return this.view(itemId);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Deletes the item (soft delete, audit `catalog.removed`; requests still pending are cancelled in
|
|
416
|
+
* the same transaction): versions and stored files are kept (installed components still refer to
|
|
417
|
+
* them), organizations no longer see it; the same name can later be imported again as a new
|
|
418
|
+
* item. Deleted / missing -> `catalog.item_not_found`.
|
|
419
|
+
*/
|
|
420
|
+
async remove(actor, itemId, opts = {}) {
|
|
421
|
+
if (!UUID_RE.test(itemId))
|
|
422
|
+
throw notFound();
|
|
423
|
+
await this.d.db.transaction().execute(async (trx) => {
|
|
424
|
+
await lockEnterpriseAdminsAndAuthorize(trx, actor);
|
|
425
|
+
const row = await trx
|
|
426
|
+
.updateTable("catalog_items")
|
|
427
|
+
.set({
|
|
428
|
+
deleted_at: sql `now()`,
|
|
429
|
+
updated_at: sql `now()`,
|
|
430
|
+
})
|
|
431
|
+
.where("id", "=", itemId)
|
|
432
|
+
.where("deleted_at", "is", null)
|
|
433
|
+
.returning(["name"])
|
|
434
|
+
.executeTakeFirst();
|
|
435
|
+
if (!row)
|
|
436
|
+
throw notFound();
|
|
437
|
+
// Pending requests for a deleted item are pointless: cancel them in the same transaction
|
|
438
|
+
await cancelPendingRequests(trx, { itemId }, actor, "item_removed", opts);
|
|
439
|
+
await audit(trx, {
|
|
440
|
+
actorAccountId: actor,
|
|
441
|
+
...(opts.actorTokenId !== undefined && { actorTokenId: opts.actorTokenId }),
|
|
442
|
+
action: "catalog.removed",
|
|
443
|
+
target: itemId,
|
|
444
|
+
detail: { name: row.name },
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Installs the extension of one version of an item on the deployment
|
|
450
|
+
* (`ExtensionAdminOps.install`: a new install is disabled, a version change keeps the desired
|
|
451
|
+
* state; audit `extension.installed`), with the install record linked to the item (F-AA). The
|
|
452
|
+
* returned status may be `pending_approval` when the version's permissions are not approved yet.
|
|
453
|
+
* A version without an extension -> `catalog.no_extension`; item or version missing ->
|
|
454
|
+
* `catalog.item_not_found`.
|
|
455
|
+
*/
|
|
456
|
+
async installToDeployment(actor, itemId, version, opts = {}) {
|
|
457
|
+
if (!UUID_RE.test(itemId))
|
|
458
|
+
throw notFound();
|
|
459
|
+
const item = await this.d.db
|
|
460
|
+
.selectFrom("catalog_items")
|
|
461
|
+
.select(["id", "name"])
|
|
462
|
+
.where("id", "=", itemId)
|
|
463
|
+
.where("deleted_at", "is", null)
|
|
464
|
+
.executeTakeFirst();
|
|
465
|
+
if (!item)
|
|
466
|
+
throw notFound();
|
|
467
|
+
const v = await this.d.db
|
|
468
|
+
.selectFrom("catalog_item_versions")
|
|
469
|
+
.select("extension_sha256")
|
|
470
|
+
.where("item_id", "=", itemId)
|
|
471
|
+
.where("version", "=", version)
|
|
472
|
+
.executeTakeFirst();
|
|
473
|
+
if (!v) {
|
|
474
|
+
throw new ApiError("catalog.item_not_found", `Version ${version} of this item not found`);
|
|
475
|
+
}
|
|
476
|
+
if (v.extension_sha256 === null) {
|
|
477
|
+
throw new ApiError("catalog.no_extension", `Plugin ${item.name} ${version} has no extension; there is nothing to install on the deployment`);
|
|
478
|
+
}
|
|
479
|
+
return this.d.ops.install(item.name, v.extension_sha256, actor, opts.actorTokenId, {
|
|
480
|
+
catalogItemId: itemId,
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
async view(itemId) {
|
|
484
|
+
return (await this.get(itemId)).item;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Checks a connector template against the loaded connector `c` (problem paths start with `at`): the
|
|
489
|
+
* endpoint matches its `endpointSchema` and carries no credential-like headers / query / URL user
|
|
490
|
+
* info (the same rule as connector installs); every `editableEndpoint` path exists in the endpoint
|
|
491
|
+
* schema.
|
|
492
|
+
*/
|
|
493
|
+
export function templateProblems(c, t, at) {
|
|
494
|
+
const problems = [];
|
|
495
|
+
const r = c.endpointSchema.safeParse(t.endpoint);
|
|
496
|
+
if (!r.success) {
|
|
497
|
+
for (const i of r.error.issues) {
|
|
498
|
+
problems.push(`${[`${at}.endpoint`, ...i.path.map(String)].join(".")}: ${i.message}`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
for (const leak of endpointCredentialIssues(r.data)) {
|
|
503
|
+
problems.push(`${at}.${leak.path}: ${leak.message}`);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
for (const p of t.editableEndpoint ?? []) {
|
|
507
|
+
if (!schemaHasPath(c.endpointSchema, p.split("."))) {
|
|
508
|
+
problems.push(`${at}.editableEndpoint "${p}" is not a field of the ${t.connector} connector's endpoint`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return problems;
|
|
512
|
+
}
|
|
513
|
+
/** Writes the files to `deployment/<prefix><path>` ({@link PUT_CONCURRENCY} at a time). */
|
|
514
|
+
export async function putCatalogFiles(storage, prefix, files) {
|
|
515
|
+
const store = storage.deployment();
|
|
516
|
+
const queue = [...files];
|
|
517
|
+
await Promise.all(Array.from({ length: Math.min(PUT_CONCURRENCY, queue.length) }, async () => {
|
|
518
|
+
for (let f = queue.shift(); f; f = queue.shift()) {
|
|
519
|
+
await store.put(`${prefix}${f.path}`, f.data, {
|
|
520
|
+
contentType: "application/octet-stream",
|
|
521
|
+
size: f.size,
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}));
|
|
525
|
+
}
|
|
526
|
+
/** Compares the marketplace's permission summary with the computed keys ({@link PermissionsMismatch}). */
|
|
527
|
+
export function permissionsMismatch(declared, actual) {
|
|
528
|
+
if (declared === undefined || actual === undefined)
|
|
529
|
+
return null;
|
|
530
|
+
const want = new Set(declared);
|
|
531
|
+
const have = new Set(actual);
|
|
532
|
+
const missing = [...have].filter((k) => !want.has(k)).sort();
|
|
533
|
+
const extra = [...want].filter((k) => !have.has(k)).sort();
|
|
534
|
+
return missing.length > 0 || extra.length > 0 ? { missing, extra } : null;
|
|
535
|
+
}
|
|
536
|
+
/** Takes the transaction-level advisory lock of an item name (concurrent imports of it serialize). */
|
|
537
|
+
export async function lockCatalogName(trx, name) {
|
|
538
|
+
await sql `SELECT pg_advisory_xact_lock(hashtext(${`capybara.directory-item:${name}`}))`.execute(trx);
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* The live item of a name, with the source identity of its latest version
|
|
542
|
+
* ({@link sourceIdentity}; null when there is none).
|
|
543
|
+
*/
|
|
544
|
+
async function liveItem(db, name) {
|
|
545
|
+
const item = await db
|
|
546
|
+
.selectFrom("catalog_items")
|
|
547
|
+
.select(["id", "package_name", "latest_version", "marketplace_id"])
|
|
548
|
+
.where("name", "=", name)
|
|
549
|
+
.where("deleted_at", "is", null)
|
|
550
|
+
.executeTakeFirst();
|
|
551
|
+
if (!item)
|
|
552
|
+
return undefined;
|
|
553
|
+
const latest = await db
|
|
554
|
+
.selectFrom("catalog_item_versions")
|
|
555
|
+
.select("source_detail")
|
|
556
|
+
.where("item_id", "=", item.id)
|
|
557
|
+
.where("version", "=", item.latest_version)
|
|
558
|
+
.executeTakeFirst();
|
|
559
|
+
return {
|
|
560
|
+
...item,
|
|
561
|
+
source: sourceIdentity(latest?.source_detail ?? undefined, item.marketplace_id ?? undefined),
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* The identity of a git source, `<host>/<repo>:<path>` (host lower case, no `.git`), or null when
|
|
566
|
+
* the source is not git. An item without package.json is identified by it instead of a package name.
|
|
567
|
+
*/
|
|
568
|
+
export function gitIdentity(detail) {
|
|
569
|
+
if (detail === undefined || !("commit" in detail))
|
|
570
|
+
return null;
|
|
571
|
+
return gitLocationIdentity(detail);
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* What identifies the source of a plugin without package.json: `git <host>/<repo>:<path>`, or, for
|
|
575
|
+
* a marketplace archive (a skills-only tgz without plugin.json), `marketplace <id>` (the
|
|
576
|
+
* marketplace vouches for its entries; archive URLs change from version to version). null for
|
|
577
|
+
* anything else.
|
|
578
|
+
*/
|
|
579
|
+
function sourceIdentity(detail, marketplaceId) {
|
|
580
|
+
const git = gitIdentity(detail);
|
|
581
|
+
if (git !== null)
|
|
582
|
+
return `git ${git}`;
|
|
583
|
+
if (detail !== undefined && "url" in detail && marketplaceId !== undefined) {
|
|
584
|
+
return `marketplace ${marketplaceId}`;
|
|
585
|
+
}
|
|
586
|
+
return null;
|
|
587
|
+
}
|
|
588
|
+
/** The versions of an item, with what tells their content apart. */
|
|
589
|
+
async function itemVersions(db, itemId) {
|
|
590
|
+
return db
|
|
591
|
+
.selectFrom("catalog_item_versions")
|
|
592
|
+
.select(["version", "content_digest", "integrity"])
|
|
593
|
+
.where("item_id", "=", itemId)
|
|
594
|
+
.execute();
|
|
595
|
+
}
|
|
596
|
+
/** `atConfirm`: found under the lock at confirm (another import committed after the review). */
|
|
597
|
+
const versionExists = (version, sameContent, atConfirm = false) => new ApiError("catalog.version_exists", (sameContent
|
|
598
|
+
? `Version ${version} of this catalog item is already imported with the same content`
|
|
599
|
+
: `Version ${version} of this catalog item has already been imported`) +
|
|
600
|
+
(atConfirm
|
|
601
|
+
? " (by another import since this one was prepared); prepare the import again"
|
|
602
|
+
: ""));
|
|
603
|
+
/**
|
|
604
|
+
* No version of the release of `p.version` (equal but for build metadata) holds the same content
|
|
605
|
+
* (digest; for a version without one, the same integrity), else `catalog.version_exists`.
|
|
606
|
+
*/
|
|
607
|
+
function assertNewContent(existing, p, atConfirm = false) {
|
|
608
|
+
const same = existing.find((v) => sameRelease(v.version, p.version) &&
|
|
609
|
+
(v.content_digest !== null
|
|
610
|
+
? v.content_digest === p.contentDigest
|
|
611
|
+
: v.integrity !== null && v.integrity === p.integrity));
|
|
612
|
+
if (same)
|
|
613
|
+
throw versionExists(same.version, true, atConfirm);
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* The version a new import records (Decision 6, spec §5.3): the plugin's own version, unless that
|
|
617
|
+
* release is already imported. A version of the same release (equal but for build metadata) with
|
|
618
|
+
* the same content (digest; for versions without one, the same integrity) -> `catalog.version_exists`
|
|
619
|
+
* "already imported"; other content -> `<version>+git.<sha12>` (git) or `+sha256.<12>` (any other
|
|
620
|
+
* source, the digest's first 12 hex), never overwriting the imported version. A git commit already
|
|
621
|
+
* recorded with other content (a Claude Code plugin whose marketplace entry changed while its own
|
|
622
|
+
* commit did not, see `claudeContentDigest`) falls back to `+sha256.<12>`.
|
|
623
|
+
*/
|
|
624
|
+
export function recordedVersion(existing, p) {
|
|
625
|
+
assertNewContent(existing, p);
|
|
626
|
+
if (!existing.some((v) => v.version === p.version))
|
|
627
|
+
return p.version;
|
|
628
|
+
const d = p.detail;
|
|
629
|
+
const byDigest = withBuild(p.version, `sha256.${p.contentDigest.slice(0, 12)}`);
|
|
630
|
+
let next = d !== undefined && "commit" in d
|
|
631
|
+
? withBuild(p.version, `git.${d.commit.slice(0, 12)}`)
|
|
632
|
+
: byDigest;
|
|
633
|
+
if (existing.some((v) => v.version === next))
|
|
634
|
+
next = byDigest;
|
|
635
|
+
if (existing.some((v) => v.version === next))
|
|
636
|
+
throw versionExists(next, false);
|
|
637
|
+
return next;
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Conflict precheck before writing (no lock; {@link writeCatalogVersion}'s re-check inside the
|
|
641
|
+
* transaction is final): an item with the same name from another npm package (or, without one,
|
|
642
|
+
* another git source) -> `catalog.name_conflict`; the release already imported with the same
|
|
643
|
+
* content -> `catalog.version_exists`. Returns the existing item (undefined when there is none) and
|
|
644
|
+
* the version to record ({@link recordedVersion}).
|
|
645
|
+
*/
|
|
646
|
+
export async function precheckCatalogImport(db, name, p) {
|
|
647
|
+
const existing = await liveItem(db, name);
|
|
648
|
+
if (!existing)
|
|
649
|
+
return { existing, version: p.version };
|
|
650
|
+
assertSameSource(existing, p.packageName, sourceIdentity(p.detail, p.marketplaceId));
|
|
651
|
+
return { existing, version: recordedVersion(await itemVersions(db, existing.id), p) };
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Writes a catalog version in the caller's transaction (the caller has re-checked the enterprise
|
|
655
|
+
* admin): takes the name's advisory lock, checks conflicts again (the same source; no version of
|
|
656
|
+
* the release with the same content; `version` not taken meanwhile), creates the item when there is
|
|
657
|
+
* none or adds the version (latest and display fields change only for a newer version, a version
|
|
658
|
+
* equal but for build metadata being newer as the later import), and writes the version row (with
|
|
659
|
+
* its content digest) and the `catalog.imported` audit. Returns the item id.
|
|
660
|
+
*/
|
|
661
|
+
export async function writeCatalogVersion(trx, w) {
|
|
662
|
+
const { name, version, listing } = w;
|
|
663
|
+
await lockCatalogName(trx, name);
|
|
664
|
+
const live = await liveItem(trx, name);
|
|
665
|
+
let id;
|
|
666
|
+
if (live) {
|
|
667
|
+
id = live.id;
|
|
668
|
+
assertSameSource(live, w.packageName, sourceIdentity(w.source.detail, w.marketplaceId));
|
|
669
|
+
const existing = await itemVersions(trx, id);
|
|
670
|
+
// the same content under any version of the release, then the recorded version itself
|
|
671
|
+
assertNewContent(existing, {
|
|
672
|
+
version,
|
|
673
|
+
contentDigest: w.contentDigest,
|
|
674
|
+
...(w.source.integrity !== undefined && { integrity: w.source.integrity }),
|
|
675
|
+
}, true);
|
|
676
|
+
if (existing.some((v) => v.version === version))
|
|
677
|
+
throw versionExists(version, false, true);
|
|
678
|
+
const newer = compareVersions(version, live.latest_version) >= 0;
|
|
679
|
+
await trx
|
|
680
|
+
.updateTable("catalog_items")
|
|
681
|
+
.set({
|
|
682
|
+
updated_at: sql `now()`,
|
|
683
|
+
...(w.marketplaceId !== undefined && { marketplace_id: w.marketplaceId }),
|
|
684
|
+
...(newer && {
|
|
685
|
+
latest_version: version,
|
|
686
|
+
publisher: listing.publisher,
|
|
687
|
+
description: listing.description,
|
|
688
|
+
display_name: listing.displayName ? JSON.stringify(listing.displayName) : null,
|
|
689
|
+
}),
|
|
690
|
+
})
|
|
691
|
+
.where("id", "=", id)
|
|
692
|
+
.execute();
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
id = w.newItemId;
|
|
696
|
+
await trx
|
|
697
|
+
.insertInto("catalog_items")
|
|
698
|
+
.values({
|
|
699
|
+
id,
|
|
700
|
+
name,
|
|
701
|
+
package_name: w.packageName,
|
|
702
|
+
publisher: listing.publisher,
|
|
703
|
+
description: listing.description,
|
|
704
|
+
display_name: listing.displayName ? JSON.stringify(listing.displayName) : null,
|
|
705
|
+
latest_version: version,
|
|
706
|
+
created_by: w.actor,
|
|
707
|
+
marketplace_id: w.marketplaceId ?? null,
|
|
708
|
+
})
|
|
709
|
+
.execute();
|
|
710
|
+
}
|
|
711
|
+
await trx
|
|
712
|
+
.insertInto("catalog_item_versions")
|
|
713
|
+
.values({
|
|
714
|
+
item_id: id,
|
|
715
|
+
version,
|
|
716
|
+
manifest: JSON.stringify(w.manifest),
|
|
717
|
+
source: w.source.source,
|
|
718
|
+
source_ref: w.source.ref,
|
|
719
|
+
integrity: w.source.integrity ?? null,
|
|
720
|
+
source_detail: w.source.detail !== undefined ? JSON.stringify(w.source.detail) : null,
|
|
721
|
+
content_digest: w.contentDigest,
|
|
722
|
+
object_prefix: w.objectPrefix,
|
|
723
|
+
extension_sha256: w.extensionSha256,
|
|
724
|
+
files: JSON.stringify(w.files.map(({ path, size, sha256 }) => ({ path, size, sha256 }))),
|
|
725
|
+
size: w.size,
|
|
726
|
+
imported_by: w.actor,
|
|
727
|
+
source_format: w.sourceFormat ?? "capybara",
|
|
728
|
+
translation: w.translation != null ? JSON.stringify(w.translation) : null,
|
|
729
|
+
license_text: w.licenseText ?? null,
|
|
730
|
+
license_truncated: w.licenseTruncated === true,
|
|
731
|
+
})
|
|
732
|
+
.execute();
|
|
733
|
+
await audit(trx, {
|
|
734
|
+
...(w.actor !== null && { actorAccountId: w.actor }),
|
|
735
|
+
...(w.actorTokenId !== undefined && { actorTokenId: w.actorTokenId }),
|
|
736
|
+
action: "catalog.imported",
|
|
737
|
+
target: id,
|
|
738
|
+
detail: {
|
|
739
|
+
name,
|
|
740
|
+
version,
|
|
741
|
+
source: w.source.source,
|
|
742
|
+
sourceRef: w.source.ref,
|
|
743
|
+
contentDigest: w.contentDigest,
|
|
744
|
+
...(w.extensionSha256 !== null && { sha256: w.extensionSha256 }),
|
|
745
|
+
...(w.source.integrity !== undefined && { integrity: w.source.integrity }),
|
|
746
|
+
...(w.marketplaceId !== undefined && { marketplaceId: w.marketplaceId }),
|
|
747
|
+
...w.auditDetail,
|
|
748
|
+
},
|
|
749
|
+
});
|
|
750
|
+
return id;
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* An extension name belongs to the npm package that first installed it (`extension_name_owners`,
|
|
754
|
+
* Ruling P3): when another package owns it, the import is refused right away with
|
|
755
|
+
* `extension.name_conflict` (the same code as at install), instead of failing only at deploy time.
|
|
756
|
+
*/
|
|
757
|
+
export async function assertPluginNameOwner(db, name, packageName) {
|
|
758
|
+
const owner = await db
|
|
759
|
+
.selectFrom("extension_name_owners")
|
|
760
|
+
.select("package_name")
|
|
761
|
+
.where("name", "=", name)
|
|
762
|
+
.executeTakeFirst();
|
|
763
|
+
if (owner && owner.package_name !== packageName) {
|
|
764
|
+
throw new ApiError("extension.name_conflict", `Extension name ${name} belongs to package ${owner.package_name}; ${packageLabel(packageName)} cannot use it`);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
/** "package <name>", or what a plugin without package.json is called in messages. */
|
|
768
|
+
const packageLabel = (p) => p === null ? "a plugin without package.json" : `package ${p}`;
|
|
769
|
+
/**
|
|
770
|
+
* Whether a plugin may add a version to an existing item: the same npm package, or, for an item
|
|
771
|
+
* without package.json, the same source ({@link sourceIdentity}: git host, repo and path, so
|
|
772
|
+
* unrelated repos that resolve to the same name never merge; or the same marketplace for an
|
|
773
|
+
* archive); never a mix of the two.
|
|
774
|
+
*/
|
|
775
|
+
function assertSameSource(item, packageName, source) {
|
|
776
|
+
const from = (p, src) => p !== null ? `package ${p}` : (src ?? "a plugin without package.json");
|
|
777
|
+
const same = item.package_name !== null
|
|
778
|
+
? item.package_name === packageName
|
|
779
|
+
: packageName === null && item.source !== null && item.source === source;
|
|
780
|
+
if (!same) {
|
|
781
|
+
throw new ApiError("catalog.name_conflict", `A catalog item with this name comes from ${from(item.package_name, item.source)}; ${from(packageName, source)} cannot add versions to it`);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* For each marketplace-imported item, its marketplace (when enabled): the highest compatible
|
|
786
|
+
* version listed, and whether a compatible entry's content changed at an imported version.
|
|
787
|
+
*/
|
|
788
|
+
async function marketplaceLatest(db, config, items, versions) {
|
|
789
|
+
const out = new Map();
|
|
790
|
+
const sourced = items.filter((i) => typeof i.marketplace_id === "string");
|
|
791
|
+
if (sourced.length === 0)
|
|
792
|
+
return out;
|
|
793
|
+
const rows = await db
|
|
794
|
+
.selectFrom("marketplace_entries as e")
|
|
795
|
+
.innerJoin("marketplaces as s", "s.id", "e.source_id")
|
|
796
|
+
.select([
|
|
797
|
+
"e.source_id",
|
|
798
|
+
"e.name",
|
|
799
|
+
"e.version",
|
|
800
|
+
"e.entry",
|
|
801
|
+
"e.head_commit",
|
|
802
|
+
"e.tree_oid",
|
|
803
|
+
"s.builtin",
|
|
804
|
+
"s.enabled",
|
|
805
|
+
"s.url",
|
|
806
|
+
"s.last_commit",
|
|
807
|
+
])
|
|
808
|
+
.where("e.source_id", "in", [...new Set(sourced.map((i) => i.marketplace_id))])
|
|
809
|
+
.where("e.name", "in", [...new Set(sourced.map((i) => i.name))])
|
|
810
|
+
.execute();
|
|
811
|
+
for (const i of sourced) {
|
|
812
|
+
const listed = rows.filter((r) => r.source_id === i.marketplace_id &&
|
|
813
|
+
r.name === i.name &&
|
|
814
|
+
sourceEffective(r, config) &&
|
|
815
|
+
entryCompatible(r.entry));
|
|
816
|
+
const [best] = listed
|
|
817
|
+
.filter((r) => r.version !== "")
|
|
818
|
+
.map((r) => r.version)
|
|
819
|
+
.sort(newestFirst);
|
|
820
|
+
const recorded = versions.get(i.id) ?? [];
|
|
821
|
+
out.set(i.id, {
|
|
822
|
+
latest: best ?? null,
|
|
823
|
+
contentChanged: listed.some((r) => entryContentChanged(r.entry, {
|
|
824
|
+
marketplaceId: r.source_id,
|
|
825
|
+
headCommit: r.head_commit,
|
|
826
|
+
treeOid: r.tree_oid,
|
|
827
|
+
url: r.url,
|
|
828
|
+
lastCommit: r.last_commit,
|
|
829
|
+
}, recorded)),
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
return out;
|
|
833
|
+
}
|
|
834
|
+
/** Org install counts per item (see {@link CatalogItemView.installs}). */
|
|
835
|
+
async function countInstalls(db, items, versions) {
|
|
836
|
+
const orgs = new Map(items.map((i) => [i.id, new Set()]));
|
|
837
|
+
const installed = await db
|
|
838
|
+
.selectFrom("plugin_installs")
|
|
839
|
+
.select(["item_id", "org_id"])
|
|
840
|
+
.where("item_id", "in", items.map((i) => i.id))
|
|
841
|
+
.where("status", "in", ["installed", "upgrading"])
|
|
842
|
+
.execute();
|
|
843
|
+
for (const r of installed)
|
|
844
|
+
orgs.get(r.item_id)?.add(r.org_id);
|
|
845
|
+
const withExtension = items.filter((i) => (versions.get(i.id) ?? []).find((v) => v.version === i.latest_version)?.extension_sha256 !=
|
|
846
|
+
null);
|
|
847
|
+
if (withExtension.length > 0) {
|
|
848
|
+
const enabled = await db
|
|
849
|
+
.selectFrom(enabledExtensionsByOrg(db).as("en"))
|
|
850
|
+
.select(["en.plugin", "en.org_id"])
|
|
851
|
+
.where("en.plugin", "in", withExtension.map((p) => p.name))
|
|
852
|
+
.execute();
|
|
853
|
+
for (const i of withExtension) {
|
|
854
|
+
for (const r of enabled)
|
|
855
|
+
if (r.plugin === i.name)
|
|
856
|
+
orgs.get(i.id)?.add(r.org_id);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
return new Map([...orgs].map(([id, set]) => [id, set.size]));
|
|
860
|
+
}
|
|
861
|
+
export function itemView(i, versions, installs = 0, marketplace = { latest: null, contentChanged: false }) {
|
|
862
|
+
const sorted = [...versions].sort((a, b) => newestFirst({ version: a.version, importedAt: a.imported_at ?? null }, { version: b.version, importedAt: b.imported_at ?? null }));
|
|
863
|
+
const top = sorted[0]?.version ?? i.latest_version;
|
|
864
|
+
const latestMarketplaceVersion = marketplace.latest;
|
|
865
|
+
return {
|
|
866
|
+
id: i.id,
|
|
867
|
+
name: i.name,
|
|
868
|
+
publisher: i.publisher,
|
|
869
|
+
displayName: i.display_name,
|
|
870
|
+
description: i.description,
|
|
871
|
+
policy: i.policy,
|
|
872
|
+
latestVersion: i.latest_version,
|
|
873
|
+
versions: sorted.map((v) => v.version),
|
|
874
|
+
installs,
|
|
875
|
+
source: versions.find((v) => v.version === i.latest_version)?.source ?? "file",
|
|
876
|
+
marketplaceId: i.marketplace_id ?? null,
|
|
877
|
+
latestMarketplaceVersion,
|
|
878
|
+
newerInMarketplace: latestMarketplaceVersion !== null && compareVersions(latestMarketplaceVersion, top) > 0,
|
|
879
|
+
contentChanged: marketplace.contentChanged,
|
|
880
|
+
updatedAt: i.updated_at.toISOString(),
|
|
881
|
+
};
|
|
882
|
+
}
|