@capybara-hq/kernel 0.0.0-stage → 0.1.0-rc.0
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 +22 -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 +1125 -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 +223 -0
- package/dist/config/schema.js +1151 -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 +422 -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 +150 -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 +210 -0
- package/dist/errors.js +212 -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 +789 -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 +149 -0
- package/dist/extensions/bootstrap.js +441 -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 +170 -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 +67 -0
- package/dist/index.js +65 -0
- package/dist/kernel.d.ts +256 -0
- package/dist/kernel.js +1326 -0
- package/dist/log.d.ts +48 -0
- package/dist/log.js +229 -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 +271 -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 +147 -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 +805 -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 +550 -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 +114 -0
- package/dist/tools/builtins/sandbox/common.js +304 -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 +67 -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,1219 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { sql } from "kysely";
|
|
3
|
+
import { putTemplateVersionIn } from "../agents/templates.js";
|
|
4
|
+
import { ApiError } from "../errors.js";
|
|
5
|
+
import { isExtensionEnabled } from "../extensions/enabled.js";
|
|
6
|
+
import { audit } from "../identity/audit.js";
|
|
7
|
+
import { lockOrg } from "../orgs/org-lock.js";
|
|
8
|
+
import { packagePath } from "../packages/tgz.js";
|
|
9
|
+
import { skillConflictError } from "../skills/skills.service.js";
|
|
10
|
+
import { canonicalJson } from "../tools/registry.js";
|
|
11
|
+
import { getPath, lockOrgForActions, orgTemplateView, readCatalogAgentTemplates, readCatalogFiles, readCatalogTemplates, requiredActions, setPath, skillOf, templatePluginProblem, } from "./org-catalog.service.js";
|
|
12
|
+
import { catalogSkillNames, InstallFailure, journalEntries, missingTemplateInput, planActions, planSkillConflicts, resolveDependencies, under, } from "./plugin-install.js";
|
|
13
|
+
import { assertItemInstallable } from "./requests.service.js";
|
|
14
|
+
import { deployedExtension, deploymentTarget } from "./upgrade-targets.js";
|
|
15
|
+
import { satisfiesRange } from "./versions.js";
|
|
16
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
17
|
+
const installNotFound = () => new ApiError("plugin.install_not_found", "Plugin install not found");
|
|
18
|
+
const inProgress = () => new ApiError("plugin.install_in_progress", "This plugin install is being installed, upgraded or uninstalled; try again when it finishes");
|
|
19
|
+
const stale = () => new ApiError("plugin.upgrade_stale", "The upgrade preview is out of date (the catalog version or this install's components changed); preview the upgrade again");
|
|
20
|
+
const targetInvalid = (message, details) => new ApiError("plugin.upgrade_target_invalid", message, details);
|
|
21
|
+
const fail = (path, message) => new ApiError("validation_failed", "Request validation failed", [{ path, message }]);
|
|
22
|
+
const sha256 = (s) => createHash("sha256").update(s).digest("hex");
|
|
23
|
+
/**
|
|
24
|
+
* What a credential of `auth` looks like: two auths with the same key take the same stored
|
|
25
|
+
* credential (static: the type family, ownership and header names; OAuth: any change re-enters).
|
|
26
|
+
*/
|
|
27
|
+
function credentialKey(auth) {
|
|
28
|
+
switch (auth.type) {
|
|
29
|
+
case "none":
|
|
30
|
+
return "none";
|
|
31
|
+
case "bearer":
|
|
32
|
+
case "api_key":
|
|
33
|
+
return `token:${auth.ownership}`;
|
|
34
|
+
case "basic":
|
|
35
|
+
return `basic:${auth.ownership}`;
|
|
36
|
+
case "headers":
|
|
37
|
+
return `headers:${auth.ownership}:${[...auth.names].sort().join(",")}`;
|
|
38
|
+
default:
|
|
39
|
+
return canonicalJson(auth);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** The digest of all files of a catalog version. */
|
|
43
|
+
const filesDigest = (row) => sha256(row.files
|
|
44
|
+
.map((f) => `${f.path}\0${f.sha256}\n`)
|
|
45
|
+
.sort()
|
|
46
|
+
.join(""));
|
|
47
|
+
/** The digest of the files under `dir/` of a catalog version (paths relative to it). */
|
|
48
|
+
function dirDigest(row, dir) {
|
|
49
|
+
const prefix = `${dir}/`;
|
|
50
|
+
return sha256(row.files
|
|
51
|
+
.filter((f) => f.path.startsWith(prefix))
|
|
52
|
+
.map((f) => `${f.path.slice(prefix.length)}\0${f.sha256}\n`)
|
|
53
|
+
.sort()
|
|
54
|
+
.join(""));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Org in-place upgrade (and, for declarative plugins, rollback) of an installed plugin (package
|
|
58
|
+
* ecosystem spec §10, Decisions 9–11):
|
|
59
|
+
* - **preview** diffs the installed and the target version per component (extension, dependency
|
|
60
|
+
* plugins, skills by name, connector templates by id) and returns the inputs needed and a
|
|
61
|
+
* `diffHash`; the same checks as an install run first (dependencies resolve, extension and
|
|
62
|
+
* template connectors deployed, skill conflicts -> `plugin.skill_conflict`), plus the target
|
|
63
|
+
* rules (`plugin.upgrade_target_invalid`): an extension-bearing plugin can move only to the
|
|
64
|
+
* catalog version carrying the deployment's extension, and installs depending on this plugin must
|
|
65
|
+
* still accept the target;
|
|
66
|
+
* - **upgrade** re-checks the `diffHash` (`plugin.upgrade_stale`), sets the install `upgrading`
|
|
67
|
+
* under the org lock (another upgrade or an uninstall -> `plugin.install_in_progress`), then:
|
|
68
|
+
* added dependencies (installed by the install flow), the added extension (enabled), added and
|
|
69
|
+
* changed skills (a new version of the same skill; agents keep selecting it by id), added
|
|
70
|
+
* connectors (created from the template), and in one final transaction the changed connectors
|
|
71
|
+
* (template-owned fields only, `applyTemplateIn`), the removals by the uninstall rules, the new
|
|
72
|
+
* version and the audit `plugin.org_upgraded`. Each step is journaled in
|
|
73
|
+
* `plugin_installs.upgrade`; on failure the journal is undone (created components removed, skill
|
|
74
|
+
* versions reverted; changed connectors were never committed), the install stays `installed` at
|
|
75
|
+
* the old version with `error = { step, code }`, audit `plugin.org_upgrade_failed`, 502
|
|
76
|
+
* `plugin.upgrade_failed`. An upgrade interrupted for longer than `catalog.staleInstallMinutes`
|
|
77
|
+
* is undone by the next preview, upgrade or uninstall.
|
|
78
|
+
* Approval follows the item's policy (the org catalog service creates the request). No in-process
|
|
79
|
+
* state.
|
|
80
|
+
*/
|
|
81
|
+
export class PluginUpgrader {
|
|
82
|
+
installer;
|
|
83
|
+
d;
|
|
84
|
+
constructor(installer) {
|
|
85
|
+
this.installer = installer;
|
|
86
|
+
this.d = installer.deps;
|
|
87
|
+
}
|
|
88
|
+
/** The upgrade preview (see the class comment). */
|
|
89
|
+
async preview(orgId, actor, installId, version) {
|
|
90
|
+
await this.recoverInterrupted(orgId, actor, installId);
|
|
91
|
+
const diff = await this.diff(orgId, actor, installId, version, null);
|
|
92
|
+
await this.d.db.transaction().execute((t) => lockOrgForActions(t, orgId, actor, diff.actions));
|
|
93
|
+
return {
|
|
94
|
+
from: diff.from.version,
|
|
95
|
+
to: diff.to.version,
|
|
96
|
+
diffHash: diff.diffHash,
|
|
97
|
+
components: diff.components,
|
|
98
|
+
inputs: diff.inputs,
|
|
99
|
+
templates: diff.templates.flatMap((c) => c.target !== undefined &&
|
|
100
|
+
(diff.inputs.connectors.includes(c.id) || diff.inputs.credentialsChanged.includes(c.id))
|
|
101
|
+
? [orgTemplateView(c.id, c.target)]
|
|
102
|
+
: []),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The checks of {@link upgrade} before an approval request (the org catalog service creates one
|
|
107
|
+
* for a needs_approval item without a grant): the diff with the input, the `diffHash`. Returns
|
|
108
|
+
* the item and the org actions the upgrade needs.
|
|
109
|
+
*/
|
|
110
|
+
async check(orgId, actor, installId, input) {
|
|
111
|
+
await this.recoverInterrupted(orgId, actor, installId);
|
|
112
|
+
const diff = await this.diff(orgId, actor, installId, input.version, input);
|
|
113
|
+
if (diff.diffHash !== input.diffHash)
|
|
114
|
+
throw stale();
|
|
115
|
+
return { itemId: diff.itemId, name: diff.name, actions: diff.actions, diff };
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Runs an upgrade (see the class comment); returns the install view. `checked` is the diff
|
|
119
|
+
* {@link check} computed for the same input (re-verified under the org lock: the install's
|
|
120
|
+
* version and ledger must be unchanged), otherwise it is computed here.
|
|
121
|
+
*/
|
|
122
|
+
async upgrade(orgId, actor, installId, input, opts = {}, checked) {
|
|
123
|
+
let diff;
|
|
124
|
+
if (checked !== undefined &&
|
|
125
|
+
checked.installId === installId &&
|
|
126
|
+
checked.to.version === input.version &&
|
|
127
|
+
checked.diffHash === input.diffHash) {
|
|
128
|
+
diff = checked;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
await this.recoverInterrupted(orgId, actor, installId);
|
|
132
|
+
diff = await this.diff(orgId, actor, installId, input.version, input);
|
|
133
|
+
if (diff.diffHash !== input.diffHash)
|
|
134
|
+
throw stale();
|
|
135
|
+
}
|
|
136
|
+
const { itemId } = diff;
|
|
137
|
+
const upgradeId = randomUUID();
|
|
138
|
+
// Start: the install `upgrading`, under the org lock (one upgrade or uninstall at a time)
|
|
139
|
+
await this.d.db.transaction().execute(async (t) => {
|
|
140
|
+
await lockOrgForActions(t, orgId, actor, diff.actions);
|
|
141
|
+
const row = await t
|
|
142
|
+
.selectFrom("plugin_installs")
|
|
143
|
+
.select(["status", "version"])
|
|
144
|
+
.where("id", "=", installId)
|
|
145
|
+
.where("org_id", "=", orgId)
|
|
146
|
+
.forUpdate()
|
|
147
|
+
.executeTakeFirst();
|
|
148
|
+
if (!row || row.status === "failed")
|
|
149
|
+
throw installNotFound();
|
|
150
|
+
if (row.status !== "installed")
|
|
151
|
+
throw inProgress();
|
|
152
|
+
if (row.version !== diff.from.version ||
|
|
153
|
+
ledgerHash(await ledgerOf(t, installId)) !== diff.ledgerHash) {
|
|
154
|
+
throw stale();
|
|
155
|
+
}
|
|
156
|
+
await assertItemInstallable(t, orgId, itemId);
|
|
157
|
+
await t
|
|
158
|
+
.updateTable("plugin_installs")
|
|
159
|
+
.set({
|
|
160
|
+
status: "upgrading",
|
|
161
|
+
upgrade: sql `jsonb_build_object('id', ${upgradeId}::text, 'from', ${diff.from.version}::text, 'to', ${diff.to.version}::text, 'startedAt', now(), 'done', '[]'::jsonb)`,
|
|
162
|
+
})
|
|
163
|
+
.where("id", "=", installId)
|
|
164
|
+
.execute();
|
|
165
|
+
});
|
|
166
|
+
const done = [];
|
|
167
|
+
/** The step whose journal write found the journal gone (cleaned up as interrupted meanwhile). */
|
|
168
|
+
let unjournaled;
|
|
169
|
+
// Appends a step to the journal (the journal gone: the upgrade was cleaned up as interrupted)
|
|
170
|
+
const journal = async (s, t) => {
|
|
171
|
+
done.push(s);
|
|
172
|
+
unjournaled = s;
|
|
173
|
+
const r = await t
|
|
174
|
+
.updateTable("plugin_installs")
|
|
175
|
+
.set({
|
|
176
|
+
upgrade: sql `jsonb_set(upgrade, '{done}', (upgrade->'done') || ${JSON.stringify([s])}::jsonb)`,
|
|
177
|
+
})
|
|
178
|
+
.where("id", "=", installId)
|
|
179
|
+
.where(sql `upgrade->>'id'`, "=", upgradeId)
|
|
180
|
+
.executeTakeFirst();
|
|
181
|
+
if (r.numUpdatedRows === 0n)
|
|
182
|
+
throw cleanedUp();
|
|
183
|
+
unjournaled = undefined;
|
|
184
|
+
};
|
|
185
|
+
// Records a component in the ledger and the journal, in the given transaction
|
|
186
|
+
const record = async (c, t) => {
|
|
187
|
+
const inserted = await t
|
|
188
|
+
.insertInto("plugin_install_components")
|
|
189
|
+
.values({
|
|
190
|
+
install_id: installId,
|
|
191
|
+
kind: c.kind,
|
|
192
|
+
ref: c.ref,
|
|
193
|
+
created: c.created,
|
|
194
|
+
skill_version: c.skillVersion,
|
|
195
|
+
template_id: c.templateId ?? null,
|
|
196
|
+
})
|
|
197
|
+
.onConflict((oc) => oc.columns(["install_id", "kind", "ref"]).doNothing())
|
|
198
|
+
.returning("ref")
|
|
199
|
+
.executeTakeFirst();
|
|
200
|
+
if (c.created)
|
|
201
|
+
await journal({ op: "created", kind: c.kind, ref: c.ref }, t);
|
|
202
|
+
else if (inserted)
|
|
203
|
+
await journal({ op: "recorded", kind: c.kind, ref: c.ref }, t);
|
|
204
|
+
};
|
|
205
|
+
let step = "dependencies";
|
|
206
|
+
const disabled = [];
|
|
207
|
+
try {
|
|
208
|
+
for (const dep of diff.dependencies.filter((x) => x.change === "added")) {
|
|
209
|
+
const resolved = dep.resolved;
|
|
210
|
+
const rec = (c, t) => record(c, t);
|
|
211
|
+
if ((await this.installer.recordDependency(orgId, resolved, rec)) !== undefined)
|
|
212
|
+
continue;
|
|
213
|
+
if (!dep.plan) {
|
|
214
|
+
throw new ApiError("catalog.dependency_unresolved", `Dependency ${dep.name}@${resolved.range} cannot be resolved: its install in this organization was removed meanwhile; try again`, [{ dependency: dep.name, range: resolved.range }]);
|
|
215
|
+
}
|
|
216
|
+
let child;
|
|
217
|
+
try {
|
|
218
|
+
child = await this.installer.execute(orgId, actor, dep.plan, opts, {
|
|
219
|
+
actions: requiredActions(dep.plan.manifest),
|
|
220
|
+
approvedBy: itemId,
|
|
221
|
+
parent: installId,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
catch (e) {
|
|
225
|
+
if (e instanceof InstallFailure) {
|
|
226
|
+
throw new InstallFailure(e.step, e.cause, [dep.name, ...e.dependency], e.rolledBack);
|
|
227
|
+
}
|
|
228
|
+
if (e instanceof ApiError &&
|
|
229
|
+
e.code === "plugin.already_installed" &&
|
|
230
|
+
(await this.installer.recordDependency(orgId, resolved, rec)) !== undefined) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
throw new InstallFailure("dependencies", e, [dep.name], true);
|
|
234
|
+
}
|
|
235
|
+
await this.installer.recordDependency(orgId, resolved, rec, child);
|
|
236
|
+
}
|
|
237
|
+
step = "extension";
|
|
238
|
+
if (diff.extension?.change === "added") {
|
|
239
|
+
const name = diff.extension.name;
|
|
240
|
+
await this.d.orgConfigs.put(orgId, name, {
|
|
241
|
+
enabled: true,
|
|
242
|
+
...(input.extensionConfig !== undefined && { config: input.extensionConfig }),
|
|
243
|
+
}, actor, opts.actorTokenId, {
|
|
244
|
+
viaItem: itemId,
|
|
245
|
+
ifDisabled: { skipped: false },
|
|
246
|
+
inTransaction: (t, skipped) => record({ kind: "extension", ref: name, created: !skipped, skillVersion: null }, t),
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
step = "skills";
|
|
250
|
+
for (const s of diff.skills) {
|
|
251
|
+
if (s.change !== "added" && s.change !== "changed")
|
|
252
|
+
continue;
|
|
253
|
+
const files = await readCatalogFiles(this.d.storage, diff.to.object_prefix, diff.to.files, s.dir);
|
|
254
|
+
const r = await this.d.skills.createVersionFromFiles(orgId, actor, { ...skillOf(files), files }, {
|
|
255
|
+
catalog: {
|
|
256
|
+
itemId,
|
|
257
|
+
version: diff.to.version,
|
|
258
|
+
format: diff.to.source_format,
|
|
259
|
+
suggestPinned: s.suggestPinned === true,
|
|
260
|
+
},
|
|
261
|
+
...opts,
|
|
262
|
+
});
|
|
263
|
+
await this.d.db.transaction().execute(async (t) => {
|
|
264
|
+
if (r.created) {
|
|
265
|
+
await record({ kind: "skill", ref: r.skill.id, created: true, skillVersion: null }, t);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
await journal({ op: "skill_version", ref: r.skill.id, version: r.version }, t);
|
|
269
|
+
await record({ kind: "skill", ref: r.skill.id, created: false, skillVersion: r.version }, t);
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
step = "agent_templates";
|
|
273
|
+
for (const a of diff.agentTemplates) {
|
|
274
|
+
if (a.change !== "added" && a.change !== "changed")
|
|
275
|
+
continue;
|
|
276
|
+
await this.d.db.transaction().execute(async (t) => {
|
|
277
|
+
await lockOrg(t, orgId);
|
|
278
|
+
const r = await putTemplateVersionIn(t, {
|
|
279
|
+
orgId,
|
|
280
|
+
plugin: diff.name,
|
|
281
|
+
catalogItemId: itemId,
|
|
282
|
+
catalogVersion: diff.to.version,
|
|
283
|
+
sourceFormat: diff.to.source_format,
|
|
284
|
+
}, a.template);
|
|
285
|
+
if (r.created) {
|
|
286
|
+
await record({ kind: "agent_template", ref: r.id, created: true, skillVersion: null }, t);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (r.changed)
|
|
290
|
+
await journal({ op: "template_version", ref: r.id, version: r.version }, t);
|
|
291
|
+
await record({
|
|
292
|
+
kind: "agent_template",
|
|
293
|
+
ref: r.id,
|
|
294
|
+
created: false,
|
|
295
|
+
skillVersion: r.changed ? r.version : null,
|
|
296
|
+
}, t);
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
step = "connectors";
|
|
300
|
+
for (const [templateId, ci] of diff.creates) {
|
|
301
|
+
const r = await this.d.connectors.create(orgId, actor, ci, opts.actorTokenId);
|
|
302
|
+
await this.d.db.transaction().execute((t) => record({
|
|
303
|
+
kind: "connector",
|
|
304
|
+
ref: r.connector.id,
|
|
305
|
+
created: true,
|
|
306
|
+
skillVersion: null,
|
|
307
|
+
templateId,
|
|
308
|
+
}, t));
|
|
309
|
+
}
|
|
310
|
+
const prepared = [];
|
|
311
|
+
for (const tpl of diff.templates) {
|
|
312
|
+
const update = diff.updates.get(tpl.id);
|
|
313
|
+
if (!update || !tpl.connector)
|
|
314
|
+
continue;
|
|
315
|
+
try {
|
|
316
|
+
prepared.push({
|
|
317
|
+
connectorId: tpl.connector.id,
|
|
318
|
+
p: await this.d.connectors.prepareTemplateUpdate(actor, tpl.connector.connector, update),
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
catch (e) {
|
|
322
|
+
throw under(e, `connectors.${tpl.id}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// Finish, in one transaction: changed connectors, removals, the version switch
|
|
326
|
+
await this.d.db.transaction().execute(async (t) => {
|
|
327
|
+
await lockOrgForActions(t, orgId, actor, diff.actions);
|
|
328
|
+
const row = await t
|
|
329
|
+
.selectFrom("plugin_installs")
|
|
330
|
+
.select(["status", sql `upgrade->>'id'`.as("upgradeId")])
|
|
331
|
+
.where("id", "=", installId)
|
|
332
|
+
.forUpdate()
|
|
333
|
+
.executeTakeFirst();
|
|
334
|
+
if (row?.status !== "upgrading" || row.upgradeId !== upgradeId)
|
|
335
|
+
throw cleanedUp();
|
|
336
|
+
await assertItemInstallable(t, orgId, itemId);
|
|
337
|
+
for (const c of prepared) {
|
|
338
|
+
await this.d.connectors.applyTemplateIn(t, orgId, c.connectorId, actor, c.p, opts.actorTokenId);
|
|
339
|
+
}
|
|
340
|
+
step = "finish";
|
|
341
|
+
for (const dep of diff.dependencies) {
|
|
342
|
+
if (dep.change !== "changed" || !dep.child)
|
|
343
|
+
continue;
|
|
344
|
+
const live = await t
|
|
345
|
+
.selectFrom("plugin_installs")
|
|
346
|
+
.select("version")
|
|
347
|
+
.where("id", "=", dep.child.installId)
|
|
348
|
+
.forShare()
|
|
349
|
+
.executeTakeFirst();
|
|
350
|
+
if (!live || !satisfiesRange(live.version, dep.range)) {
|
|
351
|
+
throw new ApiError("catalog.dependency_unresolved", `Dependency ${dep.name}@${dep.range} cannot be resolved: its install in this organization changed meanwhile; preview the upgrade again`, [{ dependency: dep.name, range: dep.range }]);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
const report = await this.removeIn(t, orgId, actor, diff, disabled);
|
|
355
|
+
// An unchanged skill gets no new version, so the target's pinned suggestion goes onto its
|
|
356
|
+
// current version (when that version is this plugin's) here, with the version switch.
|
|
357
|
+
for (const sk of diff.skills) {
|
|
358
|
+
if (sk.change !== "unchanged" || !sk.ledger)
|
|
359
|
+
continue;
|
|
360
|
+
await t
|
|
361
|
+
.updateTable("skill_versions as v")
|
|
362
|
+
.set({ suggest_pinned: sk.suggestPinned === true })
|
|
363
|
+
.from("skills as s")
|
|
364
|
+
.whereRef("s.id", "=", "v.skill_id")
|
|
365
|
+
.whereRef("s.current_version", "=", "v.version")
|
|
366
|
+
.where("s.id", "=", sk.ledger.ref)
|
|
367
|
+
.where("s.org_id", "=", orgId)
|
|
368
|
+
.where("v.catalog_item_id", "=", itemId)
|
|
369
|
+
.where("v.suggest_pinned", "<>", sk.suggestPinned === true)
|
|
370
|
+
.execute();
|
|
371
|
+
}
|
|
372
|
+
await t
|
|
373
|
+
.updateTable("plugin_installs")
|
|
374
|
+
.set({ version: diff.to.version, status: "installed", upgrade: null, error: null })
|
|
375
|
+
.where("id", "=", installId)
|
|
376
|
+
.execute();
|
|
377
|
+
await this.d.requests.resolveByInstall(t, orgId, itemId, actor, opts);
|
|
378
|
+
await audit(t, {
|
|
379
|
+
actorAccountId: actor,
|
|
380
|
+
...(opts.actorTokenId !== undefined && { actorTokenId: opts.actorTokenId }),
|
|
381
|
+
orgId,
|
|
382
|
+
action: "plugin.org_upgraded",
|
|
383
|
+
target: installId,
|
|
384
|
+
detail: {
|
|
385
|
+
itemId,
|
|
386
|
+
name: diff.name,
|
|
387
|
+
from: diff.from.version,
|
|
388
|
+
to: diff.to.version,
|
|
389
|
+
components: diff.components,
|
|
390
|
+
removed: report.removed,
|
|
391
|
+
kept: report.kept,
|
|
392
|
+
},
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
catch (e) {
|
|
397
|
+
throw await this.failed(orgId, actor, installId, upgradeId, diff, { done, unjournaled }, step, e, opts);
|
|
398
|
+
}
|
|
399
|
+
for (const p of disabled)
|
|
400
|
+
await this.d.orgConfigs.afterDisable(orgId, p);
|
|
401
|
+
const [view] = await this.installer.list(orgId, installId);
|
|
402
|
+
if (!view)
|
|
403
|
+
throw installNotFound();
|
|
404
|
+
return view;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Undoes a failed upgrade (its journal, in one transaction): the install back `installed` at the
|
|
408
|
+
* old version with `error = { step, code }`, audit `plugin.org_upgrade_failed`. When the journal
|
|
409
|
+
* is no longer this upgrade's (another request cleaned it up as interrupted and undid what it
|
|
410
|
+
* journaled), only the step whose journal write failed is undone and the install row is left as
|
|
411
|
+
* the cleanup set it. Returns the error to throw: 502 `plugin.upgrade_failed` (a dependency
|
|
412
|
+
* another install is still installing: its 409 as it is).
|
|
413
|
+
*/
|
|
414
|
+
async failed(orgId, actor, installId, upgradeId, diff, steps, step, e, opts) {
|
|
415
|
+
const failure = e instanceof InstallFailure ? e : undefined;
|
|
416
|
+
const cause = failure ? failure.cause : e;
|
|
417
|
+
const code = cause instanceof ApiError ? cause.code : "internal";
|
|
418
|
+
const at = failure ? failure.step : step;
|
|
419
|
+
let rolledBack = failure?.rolledBack ?? true;
|
|
420
|
+
try {
|
|
421
|
+
const removed = await this.d.db.transaction().execute(async (t) => {
|
|
422
|
+
await lockOrg(t, orgId);
|
|
423
|
+
const row = await t
|
|
424
|
+
.selectFrom("plugin_installs")
|
|
425
|
+
.select(sql `upgrade->>'id'`.as("upgradeId"))
|
|
426
|
+
.where("id", "=", installId)
|
|
427
|
+
.forUpdate()
|
|
428
|
+
.executeTakeFirst();
|
|
429
|
+
const ours = row?.upgradeId === upgradeId;
|
|
430
|
+
// Cleaned up meanwhile: only the step whose journal write found the journal gone is ours to
|
|
431
|
+
// undo. When that step ran in the same transaction as its journal write (the extension
|
|
432
|
+
// enable, the ledger rows), the transaction rolled back with the failed write, so undoing
|
|
433
|
+
// it is a no-op (a never-enabled extension, a never-inserted row); undoing is still safe.
|
|
434
|
+
const undo = ours ? steps.done : steps.unjournaled ? [steps.unjournaled] : [];
|
|
435
|
+
const out = await this.installer.undoUpgradeIn(t, orgId, actor, installId, undo, "plugin_upgrade_rollback");
|
|
436
|
+
await t
|
|
437
|
+
.updateTable("plugin_installs")
|
|
438
|
+
.set({ status: "installed", upgrade: null, error: JSON.stringify({ step: at, code }) })
|
|
439
|
+
.where("id", "=", installId)
|
|
440
|
+
.where(sql `upgrade->>'id'`, "=", upgradeId)
|
|
441
|
+
.execute();
|
|
442
|
+
await audit(t, {
|
|
443
|
+
actorAccountId: actor,
|
|
444
|
+
...(opts.actorTokenId !== undefined && { actorTokenId: opts.actorTokenId }),
|
|
445
|
+
orgId,
|
|
446
|
+
action: "plugin.org_upgrade_failed",
|
|
447
|
+
target: installId,
|
|
448
|
+
detail: {
|
|
449
|
+
itemId: diff.itemId,
|
|
450
|
+
name: diff.name,
|
|
451
|
+
from: diff.from.version,
|
|
452
|
+
to: diff.to.version,
|
|
453
|
+
step: at,
|
|
454
|
+
code,
|
|
455
|
+
rolledBack: undo,
|
|
456
|
+
...(!ours && { cleanedUpMeanwhile: true }),
|
|
457
|
+
},
|
|
458
|
+
});
|
|
459
|
+
return out;
|
|
460
|
+
});
|
|
461
|
+
for (const r of removed) {
|
|
462
|
+
if (r.kind === "extension")
|
|
463
|
+
await this.d.orgConfigs.afterDisable(orgId, r.ref);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
catch (u) {
|
|
467
|
+
// Left `upgrading` with its journal: undone once it is older than catalog.staleInstallMinutes
|
|
468
|
+
rolledBack = false;
|
|
469
|
+
this.d.log?.warn("plugin upgrade rollback failed", {
|
|
470
|
+
orgId,
|
|
471
|
+
installId,
|
|
472
|
+
error: u instanceof Error ? u.message : String(u),
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
if (cause instanceof ApiError && cause.code === "plugin.install_in_progress" && rolledBack) {
|
|
476
|
+
return cause;
|
|
477
|
+
}
|
|
478
|
+
const dependency = failure && failure.dependency.length > 0 ? failure.dependency : [];
|
|
479
|
+
const dep = dependency.length > 0 ? ` in dependency ${dependency.join(" > ")}` : "";
|
|
480
|
+
return new ApiError("plugin.upgrade_failed", rolledBack
|
|
481
|
+
? `Upgrading the plugin failed at step ${at}${dep} (${code}); this organization's components are back at version ${diff.from.version}`
|
|
482
|
+
: `Upgrading the plugin failed at step ${at}${dep} (${code}) and rolling back failed; it is undone after catalog.staleInstallMinutes`, [
|
|
483
|
+
{
|
|
484
|
+
step: at,
|
|
485
|
+
code,
|
|
486
|
+
...(dependency.length > 0 && { dependency: dependency.join(" > ") }),
|
|
487
|
+
...(cause instanceof ApiError && { message: cause.message }),
|
|
488
|
+
...(cause instanceof ApiError &&
|
|
489
|
+
cause.details !== undefined && { causeDetails: cause.details }),
|
|
490
|
+
rolledBack,
|
|
491
|
+
},
|
|
492
|
+
]);
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* An upgrade interrupted for longer than `catalog.staleInstallMinutes` (a replica crashed
|
|
496
|
+
* midway): its journal is undone, the install back `installed` at the old version (error step
|
|
497
|
+
* `interrupted`), audit `plugin.org_upgrade_failed`. Nothing to do otherwise.
|
|
498
|
+
*/
|
|
499
|
+
async recoverInterrupted(orgId, actor, installId) {
|
|
500
|
+
if (!UUID_RE.test(installId))
|
|
501
|
+
throw installNotFound();
|
|
502
|
+
const staleMinutes = this.d.staleInstallMinutes();
|
|
503
|
+
const removed = await this.d.db.transaction().execute(async (t) => {
|
|
504
|
+
const peek = await t
|
|
505
|
+
.selectFrom("plugin_installs")
|
|
506
|
+
.select("status")
|
|
507
|
+
.where("id", "=", installId)
|
|
508
|
+
.where("org_id", "=", orgId)
|
|
509
|
+
.executeTakeFirst();
|
|
510
|
+
if (peek?.status !== "upgrading")
|
|
511
|
+
return [];
|
|
512
|
+
await lockOrg(t, orgId);
|
|
513
|
+
const row = await t
|
|
514
|
+
.selectFrom("plugin_installs as a")
|
|
515
|
+
.innerJoin("catalog_items as i", "i.id", "a.item_id")
|
|
516
|
+
.select([
|
|
517
|
+
"a.status",
|
|
518
|
+
"a.upgrade",
|
|
519
|
+
"a.item_id",
|
|
520
|
+
"i.name",
|
|
521
|
+
sql `coalesce((a.upgrade->>'startedAt')::timestamptz < now() - make_interval(mins => ${staleMinutes}), false)`.as("stale"),
|
|
522
|
+
])
|
|
523
|
+
.where("a.id", "=", installId)
|
|
524
|
+
.where("a.org_id", "=", orgId)
|
|
525
|
+
.forUpdate("a")
|
|
526
|
+
.executeTakeFirst();
|
|
527
|
+
if (row?.status !== "upgrading" || !row.stale)
|
|
528
|
+
return [];
|
|
529
|
+
const steps = journalEntries(row.upgrade);
|
|
530
|
+
const out = await this.installer.undoUpgradeIn(t, orgId, actor, installId, steps, "plugin_upgrade_cleanup");
|
|
531
|
+
await t
|
|
532
|
+
.updateTable("plugin_installs")
|
|
533
|
+
.set({
|
|
534
|
+
status: "installed",
|
|
535
|
+
upgrade: null,
|
|
536
|
+
error: JSON.stringify({ step: "interrupted", code: "internal" }),
|
|
537
|
+
})
|
|
538
|
+
.where("id", "=", installId)
|
|
539
|
+
.execute();
|
|
540
|
+
const j = row.upgrade;
|
|
541
|
+
await audit(t, {
|
|
542
|
+
actorAccountId: actor,
|
|
543
|
+
orgId,
|
|
544
|
+
action: "plugin.org_upgrade_failed",
|
|
545
|
+
target: installId,
|
|
546
|
+
detail: {
|
|
547
|
+
itemId: row.item_id,
|
|
548
|
+
name: row.name,
|
|
549
|
+
from: j?.from ?? null,
|
|
550
|
+
to: j?.to ?? null,
|
|
551
|
+
step: "interrupted",
|
|
552
|
+
code: "internal",
|
|
553
|
+
rolledBack: steps,
|
|
554
|
+
},
|
|
555
|
+
});
|
|
556
|
+
return out;
|
|
557
|
+
});
|
|
558
|
+
for (const r of removed) {
|
|
559
|
+
if (r.kind === "extension")
|
|
560
|
+
await this.d.orgConfigs.afterDisable(orgId, r.ref);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* The removals of an upgrade, in the final transaction, by the uninstall rules
|
|
565
|
+
* (`decideRemovalIn`): only components this install created are removed, and only when nothing
|
|
566
|
+
* else uses them; one still used (by an agent, a connector or another extension) stays in the
|
|
567
|
+
* ledger marked `dropped` ("no longer in the plugin", still this install's), every other removed
|
|
568
|
+
* component leaves the ledger; a dropped component the target brings back is un-dropped. A dependency
|
|
569
|
+
* install is uninstalled by the same rules (another install using it takes it over). Skills ->
|
|
570
|
+
* connectors -> extension -> dependencies.
|
|
571
|
+
*/
|
|
572
|
+
async removeIn(t, orgId, actor, diff, disabled) {
|
|
573
|
+
const report = { removed: [], kept: [] };
|
|
574
|
+
const reason = "plugin_upgrade";
|
|
575
|
+
const keep = (kind, ref, why) => report.kept.push({ kind, ref, reason: why });
|
|
576
|
+
const row = (kind, ref) => t
|
|
577
|
+
.updateTable("plugin_install_components")
|
|
578
|
+
.where("install_id", "=", diff.installId)
|
|
579
|
+
.where("kind", "=", kind)
|
|
580
|
+
.where("ref", "=", ref);
|
|
581
|
+
const dropRow = (kind, ref) => t
|
|
582
|
+
.deleteFrom("plugin_install_components")
|
|
583
|
+
.where("install_id", "=", diff.installId)
|
|
584
|
+
.where("kind", "=", kind)
|
|
585
|
+
.where("ref", "=", ref)
|
|
586
|
+
.execute();
|
|
587
|
+
const removed = [
|
|
588
|
+
...diff.skills.flatMap((x) => (x.change === "removed" && x.ledger ? [x.ledger] : [])),
|
|
589
|
+
...diff.agentTemplates.flatMap((x) => (x.change === "removed" && x.ledger ? [x.ledger] : [])),
|
|
590
|
+
...diff.templates.flatMap((x) => (x.change === "removed" && x.ledger ? [x.ledger] : [])),
|
|
591
|
+
...(diff.extension?.change === "removed"
|
|
592
|
+
? diff.ledger.filter((l) => l.kind === "extension" && l.ref === diff.extension?.name)
|
|
593
|
+
: []),
|
|
594
|
+
];
|
|
595
|
+
const agentRefs = await this.installer.agentRefs(t, orgId);
|
|
596
|
+
const self = new Set([diff.installId]);
|
|
597
|
+
for (const l of removed) {
|
|
598
|
+
if (!l.created) {
|
|
599
|
+
await dropRow(l.kind, l.ref);
|
|
600
|
+
keep(l.kind, l.ref, "pre_existing");
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
const outcome = await this.installer.decideRemovalIn(t, orgId, actor, { kind: l.kind, ref: l.ref, created: true, skillVersion: null }, self, agentRefs, reason);
|
|
604
|
+
if (outcome === "used_by_agents" ||
|
|
605
|
+
outcome === "used_by_connectors" ||
|
|
606
|
+
outcome === "used_by_plugins") {
|
|
607
|
+
// Still used: stays this install's, marked dropped, so an uninstall (or a later upgrade
|
|
608
|
+
// bringing it back) still finds it
|
|
609
|
+
await row(l.kind, l.ref).set({ dropped: true }).execute();
|
|
610
|
+
keep(l.kind, l.ref, outcome);
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
await dropRow(l.kind, l.ref);
|
|
614
|
+
if (outcome === "removed") {
|
|
615
|
+
report.removed.push({ kind: l.kind, ref: l.ref });
|
|
616
|
+
if (l.kind === "extension")
|
|
617
|
+
disabled.push(l.ref);
|
|
618
|
+
}
|
|
619
|
+
else if (outcome !== "gone")
|
|
620
|
+
keep(l.kind, l.ref, outcome);
|
|
621
|
+
}
|
|
622
|
+
// Components back in the plugin (dropped by an earlier upgrade, brought again by the target)
|
|
623
|
+
const back = [
|
|
624
|
+
...diff.skills.flatMap((x) => x.change !== "removed" && x.ledger?.dropped
|
|
625
|
+
? [{ kind: "skill", ref: x.ledger.ref }]
|
|
626
|
+
: []),
|
|
627
|
+
...diff.agentTemplates.flatMap((x) => x.change !== "removed" && x.ledger?.dropped
|
|
628
|
+
? [{ kind: "agent_template", ref: x.ledger.ref }]
|
|
629
|
+
: []),
|
|
630
|
+
...diff.templates.flatMap((x) => x.change !== "removed" && x.ledger?.dropped
|
|
631
|
+
? [{ kind: "connector", ref: x.ledger.ref }]
|
|
632
|
+
: []),
|
|
633
|
+
...(diff.extension && diff.extension.change !== "removed"
|
|
634
|
+
? diff.ledger
|
|
635
|
+
.filter((l) => l.kind === "extension" && l.ref === diff.extension?.name && l.dropped)
|
|
636
|
+
.map((l) => ({ kind: "extension", ref: l.ref }))
|
|
637
|
+
: []),
|
|
638
|
+
];
|
|
639
|
+
for (const b of back)
|
|
640
|
+
await row(b.kind, b.ref).set({ dropped: false }).execute();
|
|
641
|
+
for (const dep of diff.dependencies) {
|
|
642
|
+
if (dep.change !== "removed" || !dep.child)
|
|
643
|
+
continue;
|
|
644
|
+
const ref = dep.child.installId;
|
|
645
|
+
await dropRow("plugin", ref);
|
|
646
|
+
if (!dep.child.ledger.created) {
|
|
647
|
+
keep("plugin", ref, "pre_existing");
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
const sub = { removed: [], kept: [] };
|
|
651
|
+
const outcome = await this.installer.uninstallIn(t, orgId, actor, ref, {
|
|
652
|
+
report: sub,
|
|
653
|
+
removing: new Set(),
|
|
654
|
+
from: diff.installId,
|
|
655
|
+
authorize: false,
|
|
656
|
+
reason,
|
|
657
|
+
opts: {},
|
|
658
|
+
});
|
|
659
|
+
for (const r of sub.removed)
|
|
660
|
+
if (r.kind === "extension")
|
|
661
|
+
disabled.push(r.ref);
|
|
662
|
+
if (outcome === "removed")
|
|
663
|
+
report.removed.push({ kind: "plugin", ref });
|
|
664
|
+
else if (outcome === "kept")
|
|
665
|
+
keep("plugin", ref, "used_by_other_install");
|
|
666
|
+
}
|
|
667
|
+
return report;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Computes an upgrade (see the class comment). `input` null: the preview (no input checks);
|
|
671
|
+
* otherwise the connector inputs are built and checked as at install.
|
|
672
|
+
*/
|
|
673
|
+
async diff(orgId, actor, installId, version, input) {
|
|
674
|
+
const db = this.d.db;
|
|
675
|
+
if (!UUID_RE.test(installId))
|
|
676
|
+
throw installNotFound();
|
|
677
|
+
const install = await db
|
|
678
|
+
.selectFrom("plugin_installs as a")
|
|
679
|
+
.innerJoin("catalog_items as i", "i.id", "a.item_id")
|
|
680
|
+
.select(["a.item_id", "a.version", "a.status", "i.name", "i.policy", "i.deleted_at"])
|
|
681
|
+
.where("a.id", "=", installId)
|
|
682
|
+
.where("a.org_id", "=", orgId)
|
|
683
|
+
.executeTakeFirst();
|
|
684
|
+
if (!install || install.status === "failed")
|
|
685
|
+
throw installNotFound();
|
|
686
|
+
if (install.status !== "installed")
|
|
687
|
+
throw inProgress();
|
|
688
|
+
const itemId = install.item_id;
|
|
689
|
+
const name = install.name;
|
|
690
|
+
if (install.deleted_at !== null) {
|
|
691
|
+
throw new ApiError("catalog.item_not_found", "This plugin is no longer in the enterprise catalog");
|
|
692
|
+
}
|
|
693
|
+
if (install.policy === "disabled") {
|
|
694
|
+
throw new ApiError("catalog.item_disabled", "This catalog item is disabled by the enterprise admin and cannot be upgraded");
|
|
695
|
+
}
|
|
696
|
+
const versions = await db
|
|
697
|
+
.selectFrom("catalog_item_versions")
|
|
698
|
+
.select([
|
|
699
|
+
"version",
|
|
700
|
+
"manifest",
|
|
701
|
+
"object_prefix",
|
|
702
|
+
"files",
|
|
703
|
+
"extension_sha256",
|
|
704
|
+
"imported_at",
|
|
705
|
+
"source_format",
|
|
706
|
+
])
|
|
707
|
+
.where("item_id", "=", itemId)
|
|
708
|
+
.execute();
|
|
709
|
+
const fromRow = versions.find((v) => v.version === install.version);
|
|
710
|
+
const toRow = versions.find((v) => v.version === version);
|
|
711
|
+
if (!fromRow) {
|
|
712
|
+
throw new ApiError("catalog.item_not_found", `The installed version ${install.version} is no longer in the enterprise catalog`);
|
|
713
|
+
}
|
|
714
|
+
if (!toRow)
|
|
715
|
+
throw new ApiError("catalog.item_not_found", `Version ${version} of this item not found`);
|
|
716
|
+
const from = fromRow;
|
|
717
|
+
const to = toRow;
|
|
718
|
+
if (to.version === from.version) {
|
|
719
|
+
// Confirming a preview whose upgrade already ran (a concurrent confirm won): stale
|
|
720
|
+
if (input !== null)
|
|
721
|
+
throw stale();
|
|
722
|
+
throw targetInvalid(`This organization already has version ${from.version} installed`, [
|
|
723
|
+
{ reason: "same_version", version: from.version },
|
|
724
|
+
]);
|
|
725
|
+
}
|
|
726
|
+
// Target rules (Decision 10)
|
|
727
|
+
if (to.manifest.extension) {
|
|
728
|
+
const deployed = await deployedExtension(db, this.d.host, name);
|
|
729
|
+
const allowed = deploymentTarget(versions, deployed);
|
|
730
|
+
if (allowed?.version !== to.version) {
|
|
731
|
+
throw targetInvalid(deployed === undefined
|
|
732
|
+
? `The deployment does not run the extension of plugin ${name}; ask the enterprise admin to install it first`
|
|
733
|
+
: `The deployment runs the extension of plugin ${name} at version ${deployed.version}; this organization can upgrade only to ${allowed ? `catalog version ${allowed.version}` : "a catalog version carrying that extension, and none is imported"}`, [
|
|
734
|
+
{
|
|
735
|
+
reason: "deployment_version",
|
|
736
|
+
deploymentVersion: deployed?.version ?? null,
|
|
737
|
+
allowedVersion: allowed?.version ?? null,
|
|
738
|
+
},
|
|
739
|
+
]);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
// Installs in this org depending on this one must accept the target
|
|
743
|
+
const parents = await db
|
|
744
|
+
.selectFrom("plugin_install_components as c")
|
|
745
|
+
.innerJoin("plugin_installs as a", "a.id", "c.install_id")
|
|
746
|
+
.innerJoin("catalog_item_versions as v", (j) => j.onRef("v.item_id", "=", "a.item_id").onRef("v.version", "=", "a.version"))
|
|
747
|
+
.innerJoin("catalog_items as i", "i.id", "a.item_id")
|
|
748
|
+
.select(["i.name", "v.manifest"])
|
|
749
|
+
.where("c.kind", "=", "plugin")
|
|
750
|
+
.where("c.ref", "=", installId)
|
|
751
|
+
.where("a.org_id", "=", orgId)
|
|
752
|
+
.where("a.status", "<>", "failed")
|
|
753
|
+
.execute();
|
|
754
|
+
for (const p of parents) {
|
|
755
|
+
const range = p.manifest.dependencies?.find((x) => x.name === name)?.version;
|
|
756
|
+
if (range !== undefined && !satisfiesRange(to.version, range)) {
|
|
757
|
+
throw targetInvalid(`Plugin ${p.name} installed in this organization depends on ${name}@${range}, which version ${to.version} does not satisfy`, [{ reason: "dependent_range", dependent: p.name, range, version: to.version }]);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
const ledger = await ledgerOf(db, installId);
|
|
761
|
+
const components = [];
|
|
762
|
+
// Extension
|
|
763
|
+
let extension = null;
|
|
764
|
+
let extensionConfig = false;
|
|
765
|
+
if (from.manifest.extension || to.manifest.extension) {
|
|
766
|
+
const change = !from.manifest.extension
|
|
767
|
+
? "added"
|
|
768
|
+
: !to.manifest.extension
|
|
769
|
+
? "removed"
|
|
770
|
+
: from.extension_sha256 !== to.extension_sha256 ||
|
|
771
|
+
from.manifest.extension.manifest.version !== to.manifest.extension.manifest.version
|
|
772
|
+
? "changed"
|
|
773
|
+
: "unchanged";
|
|
774
|
+
extension = { change, name };
|
|
775
|
+
if (change === "added") {
|
|
776
|
+
await this.installer.planExtension(orgId, to.manifest);
|
|
777
|
+
extensionConfig = !(await isExtensionEnabled(db, orgId, name));
|
|
778
|
+
}
|
|
779
|
+
components.push({
|
|
780
|
+
kind: "extension",
|
|
781
|
+
ref: name,
|
|
782
|
+
change,
|
|
783
|
+
...(change === "changed" && {
|
|
784
|
+
detail: `${from.manifest.extension?.manifest.version} -> ${to.manifest.extension?.manifest.version}`,
|
|
785
|
+
}),
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
// Dependencies (by plugin name)
|
|
789
|
+
const resolved = await resolveDependencies(db, to.manifest);
|
|
790
|
+
const childIds = ledger.filter((l) => l.kind === "plugin").map((l) => l.ref);
|
|
791
|
+
const children = childIds.length === 0
|
|
792
|
+
? []
|
|
793
|
+
: await db
|
|
794
|
+
.selectFrom("plugin_installs as a")
|
|
795
|
+
.innerJoin("catalog_items as i", "i.id", "a.item_id")
|
|
796
|
+
.select(["a.id", "a.version", "i.name"])
|
|
797
|
+
.where("a.id", "in", childIds)
|
|
798
|
+
.execute();
|
|
799
|
+
const childOf = (n) => {
|
|
800
|
+
const c = children.find((x) => x.name === n);
|
|
801
|
+
const l = c && ledger.find((x) => x.kind === "plugin" && x.ref === c.id);
|
|
802
|
+
return c && l ? { installId: c.id, version: c.version, ledger: l } : undefined;
|
|
803
|
+
};
|
|
804
|
+
const dependencies = [];
|
|
805
|
+
const fromDeps = from.manifest.dependencies ?? [];
|
|
806
|
+
for (const r of resolved) {
|
|
807
|
+
const before = fromDeps.find((x) => x.name === r.name);
|
|
808
|
+
const child = childOf(r.name);
|
|
809
|
+
if (before && child) {
|
|
810
|
+
const change = before.version === r.range ? "unchanged" : "changed";
|
|
811
|
+
if (change === "changed" && !satisfiesRange(child.version, r.range)) {
|
|
812
|
+
throw new ApiError("catalog.dependency_unresolved", `Dependency ${r.name}@${r.range} cannot be resolved: this organization has version ${child.version} installed; upgrade it first`, [{ dependency: r.name, range: r.range }]);
|
|
813
|
+
}
|
|
814
|
+
dependencies.push({
|
|
815
|
+
name: r.name,
|
|
816
|
+
change,
|
|
817
|
+
range: r.range,
|
|
818
|
+
fromRange: before.version,
|
|
819
|
+
resolved: r,
|
|
820
|
+
child,
|
|
821
|
+
});
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
// New (or its install was not recorded): installed by the install flow
|
|
825
|
+
const live = await this.installer.liveDependency(db, orgId, r);
|
|
826
|
+
dependencies.push({
|
|
827
|
+
name: r.name,
|
|
828
|
+
change: "added",
|
|
829
|
+
range: r.range,
|
|
830
|
+
resolved: r,
|
|
831
|
+
plan: live !== undefined
|
|
832
|
+
? null
|
|
833
|
+
: await this.installer.plan(orgId, actor, r.itemId, r.version, input === null ? null : {}, `dependencies.${r.name}.`, itemId),
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
for (const before of fromDeps) {
|
|
837
|
+
if (resolved.some((r) => r.name === before.name))
|
|
838
|
+
continue;
|
|
839
|
+
const child = childOf(before.name);
|
|
840
|
+
dependencies.push({
|
|
841
|
+
name: before.name,
|
|
842
|
+
change: "removed",
|
|
843
|
+
fromRange: before.version,
|
|
844
|
+
...(child && { child }),
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
for (const dep of dependencies) {
|
|
848
|
+
components.push({
|
|
849
|
+
kind: "plugin",
|
|
850
|
+
ref: dep.name,
|
|
851
|
+
change: dep.change,
|
|
852
|
+
...(dep.change === "changed" && { detail: `${dep.fromRange} -> ${dep.range}` }),
|
|
853
|
+
...(dep.change === "added" && {
|
|
854
|
+
detail: dep.plan === null ? "already installed" : `installs ${dep.resolved?.version}`,
|
|
855
|
+
}),
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
// Skills (by name)
|
|
859
|
+
const fromNames = await catalogSkillNames(this.d.storage, from, from.manifest);
|
|
860
|
+
const toNames = await catalogSkillNames(this.d.storage, to, to.manifest);
|
|
861
|
+
const dirOf = (m, i) => packagePath((m.skills ?? [])[i]?.path);
|
|
862
|
+
const fromSkills = new Map(fromNames.map((n, i) => [n, dirOf(from.manifest, i)]));
|
|
863
|
+
const toSkills = new Map(toNames.map((n, i) => [n, dirOf(to.manifest, i)]));
|
|
864
|
+
const toPinned = new Set(toNames.filter((_, i) => (to.manifest.skills ?? [])[i]?.pinned === true));
|
|
865
|
+
const skillRefs = ledger.filter((l) => l.kind === "skill").map((l) => l.ref);
|
|
866
|
+
const liveSkills = skillRefs.length === 0
|
|
867
|
+
? []
|
|
868
|
+
: await db
|
|
869
|
+
.selectFrom("skills")
|
|
870
|
+
.select(["id", "name"])
|
|
871
|
+
.where("id", "in", skillRefs)
|
|
872
|
+
.where("org_id", "=", orgId)
|
|
873
|
+
.where("deleted_at", "is", null)
|
|
874
|
+
.execute();
|
|
875
|
+
const ledgerOfSkill = (n) => {
|
|
876
|
+
const s = liveSkills.find((x) => x.name === n);
|
|
877
|
+
return s && ledger.find((l) => l.kind === "skill" && l.ref === s.id);
|
|
878
|
+
};
|
|
879
|
+
const skills = [];
|
|
880
|
+
for (const [n, dir] of toSkills) {
|
|
881
|
+
const before = fromSkills.get(n);
|
|
882
|
+
const l = ledgerOfSkill(n);
|
|
883
|
+
const change = before === undefined
|
|
884
|
+
? "added"
|
|
885
|
+
: dirDigest(from, before) === dirDigest(to, dir)
|
|
886
|
+
? "unchanged"
|
|
887
|
+
: "changed";
|
|
888
|
+
skills.push({
|
|
889
|
+
name: n,
|
|
890
|
+
change,
|
|
891
|
+
dir,
|
|
892
|
+
suggestPinned: toPinned.has(n),
|
|
893
|
+
...(l && { ledger: l }),
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
for (const n of fromSkills.keys()) {
|
|
897
|
+
if (toSkills.has(n))
|
|
898
|
+
continue;
|
|
899
|
+
const l = ledgerOfSkill(n);
|
|
900
|
+
skills.push({ name: n, change: "removed", ...(l && { ledger: l }) });
|
|
901
|
+
}
|
|
902
|
+
for (const s of skills)
|
|
903
|
+
components.push({ kind: "skill", ref: s.name, change: s.change });
|
|
904
|
+
// The same skill conflict check as an install (spec §9.2)
|
|
905
|
+
const conflicts = await planSkillConflicts(db, orgId, {
|
|
906
|
+
itemId,
|
|
907
|
+
name,
|
|
908
|
+
version: to.version,
|
|
909
|
+
skillNames: toNames,
|
|
910
|
+
dependencies: dependencies
|
|
911
|
+
.filter((x) => x.change === "added")
|
|
912
|
+
.map((x) => ({ plan: x.plan ?? null })),
|
|
913
|
+
});
|
|
914
|
+
if (conflicts.length > 0)
|
|
915
|
+
throw skillConflictError(conflicts);
|
|
916
|
+
// Agent templates (by name; phase C)
|
|
917
|
+
const fromAgents = await readCatalogAgentTemplates(this.d.storage, from);
|
|
918
|
+
const toAgents = await readCatalogAgentTemplates(this.d.storage, to);
|
|
919
|
+
const templateRefs = ledger.filter((l) => l.kind === "agent_template").map((l) => l.ref);
|
|
920
|
+
const liveTemplates = templateRefs.length === 0
|
|
921
|
+
? []
|
|
922
|
+
: await db
|
|
923
|
+
.selectFrom("agent_templates")
|
|
924
|
+
.select(["id", "name"])
|
|
925
|
+
.where("id", "in", templateRefs)
|
|
926
|
+
.where("org_id", "=", orgId)
|
|
927
|
+
.where("deleted_at", "is", null)
|
|
928
|
+
.execute();
|
|
929
|
+
const ledgerOfTemplate = (n) => {
|
|
930
|
+
const r = liveTemplates.find((x) => x.name === n);
|
|
931
|
+
return r && ledger.find((l) => l.kind === "agent_template" && l.ref === r.id);
|
|
932
|
+
};
|
|
933
|
+
const agentTemplates = [];
|
|
934
|
+
for (const a of toAgents) {
|
|
935
|
+
const n = a.template.name;
|
|
936
|
+
const before = fromAgents.find((x) => x.template.name === n);
|
|
937
|
+
const l = ledgerOfTemplate(n);
|
|
938
|
+
const change = before === undefined
|
|
939
|
+
? "added"
|
|
940
|
+
: canonicalJson(before.template) === canonicalJson(a.template)
|
|
941
|
+
? "unchanged"
|
|
942
|
+
: "changed";
|
|
943
|
+
agentTemplates.push({ name: n, change, template: a.template, ...(l && { ledger: l }) });
|
|
944
|
+
}
|
|
945
|
+
for (const a of fromAgents) {
|
|
946
|
+
const n = a.template.name;
|
|
947
|
+
if (toAgents.some((x) => x.template.name === n))
|
|
948
|
+
continue;
|
|
949
|
+
const l = ledgerOfTemplate(n);
|
|
950
|
+
agentTemplates.push({ name: n, change: "removed", ...(l && { ledger: l }) });
|
|
951
|
+
}
|
|
952
|
+
for (const a of agentTemplates) {
|
|
953
|
+
components.push({ kind: "agent_template", ref: a.name, change: a.change });
|
|
954
|
+
}
|
|
955
|
+
// Connector templates (by id)
|
|
956
|
+
const fromTemplates = await readCatalogTemplates(this.d.storage, from);
|
|
957
|
+
const toTemplates = await readCatalogTemplates(this.d.storage, to);
|
|
958
|
+
// The install's connectors by template; a dropped one (kept only for the agents using it) counts
|
|
959
|
+
// only while its connector is live, so a template the target brings back maps onto it
|
|
960
|
+
const connectorRows = ledger.filter((l) => l.kind === "connector" && l.template_id !== null);
|
|
961
|
+
const liveConnectors = connectorRows.length === 0
|
|
962
|
+
? []
|
|
963
|
+
: await db
|
|
964
|
+
.selectFrom("connectors")
|
|
965
|
+
.select(["id", "connector", "endpoint", "auth"])
|
|
966
|
+
.where("id", "in", connectorRows.map((l) => l.ref))
|
|
967
|
+
.where("org_id", "=", orgId)
|
|
968
|
+
.where("deleted_at", "is", null)
|
|
969
|
+
.execute();
|
|
970
|
+
const connectorOf = (templateId) => {
|
|
971
|
+
const rows = connectorRows.filter((x) => x.template_id === templateId);
|
|
972
|
+
const l = rows.find((x) => !x.dropped) ??
|
|
973
|
+
rows.find((x) => liveConnectors.some((c) => c.id === x.ref));
|
|
974
|
+
const c = l && liveConnectors.find((x) => x.id === l.ref);
|
|
975
|
+
return {
|
|
976
|
+
...(l && { ledger: l }),
|
|
977
|
+
...(c && {
|
|
978
|
+
connector: {
|
|
979
|
+
id: c.id,
|
|
980
|
+
connector: c.connector,
|
|
981
|
+
endpoint: c.endpoint,
|
|
982
|
+
auth: c.auth,
|
|
983
|
+
},
|
|
984
|
+
}),
|
|
985
|
+
};
|
|
986
|
+
};
|
|
987
|
+
const deps = this.installer.templateDeps();
|
|
988
|
+
const templates = [];
|
|
989
|
+
for (const ct of toTemplates) {
|
|
990
|
+
const problem = templatePluginProblem(deps, ct.file.template);
|
|
991
|
+
const before = fromTemplates.find((x) => x.id === ct.id);
|
|
992
|
+
const mapped = connectorOf(ct.id);
|
|
993
|
+
if (!before && mapped.ledger?.dropped && mapped.connector) {
|
|
994
|
+
// Back in the plugin (an earlier upgrade dropped it, its connector was kept): the kept
|
|
995
|
+
// connector is updated to the template, not created again (its slug would be taken)
|
|
996
|
+
if (problem !== undefined)
|
|
997
|
+
throw new ApiError("catalog.not_deployed", problem);
|
|
998
|
+
if (mapped.connector.connector !== ct.file.template.connector) {
|
|
999
|
+
throw targetInvalid(`Connector template ${ct.id} comes back for another connector (${ct.file.template.plugin} "${ct.file.template.connector}"); uninstall the plugin and install the new version instead`, [
|
|
1000
|
+
{
|
|
1001
|
+
reason: "template_connector",
|
|
1002
|
+
template: ct.id,
|
|
1003
|
+
plugin: ct.file.template.plugin,
|
|
1004
|
+
connector: ct.file.template.connector,
|
|
1005
|
+
},
|
|
1006
|
+
]);
|
|
1007
|
+
}
|
|
1008
|
+
const credentialsChanged = credentialKey(mapped.connector.auth) !==
|
|
1009
|
+
credentialKey(ct.file.template.auth);
|
|
1010
|
+
templates.push({
|
|
1011
|
+
id: ct.id,
|
|
1012
|
+
change: "changed",
|
|
1013
|
+
target: ct.file,
|
|
1014
|
+
...mapped,
|
|
1015
|
+
credentialsChanged,
|
|
1016
|
+
});
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
if (!before) {
|
|
1020
|
+
if (problem !== undefined)
|
|
1021
|
+
throw new ApiError("catalog.not_deployed", problem);
|
|
1022
|
+
templates.push({ id: ct.id, change: "added", target: ct.file, credentialsChanged: false });
|
|
1023
|
+
continue;
|
|
1024
|
+
}
|
|
1025
|
+
const change = canonicalJson(before.file) === canonicalJson(ct.file) ? "unchanged" : "changed";
|
|
1026
|
+
if (change === "changed") {
|
|
1027
|
+
if (before.file.template.plugin !== ct.file.template.plugin ||
|
|
1028
|
+
before.file.template.connector !== ct.file.template.connector) {
|
|
1029
|
+
throw targetInvalid(`Connector template ${ct.id} moves to another connector (${ct.file.template.plugin} "${ct.file.template.connector}"); uninstall the plugin and install the new version instead`, [
|
|
1030
|
+
{
|
|
1031
|
+
reason: "template_connector",
|
|
1032
|
+
template: ct.id,
|
|
1033
|
+
plugin: ct.file.template.plugin,
|
|
1034
|
+
connector: ct.file.template.connector,
|
|
1035
|
+
},
|
|
1036
|
+
]);
|
|
1037
|
+
}
|
|
1038
|
+
if (problem !== undefined && mapped.connector) {
|
|
1039
|
+
throw new ApiError("catalog.not_deployed", problem);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
const credentialsChanged = change === "changed" &&
|
|
1043
|
+
mapped.connector !== undefined &&
|
|
1044
|
+
credentialKey(mapped.connector.auth) !==
|
|
1045
|
+
credentialKey(ct.file.template.auth);
|
|
1046
|
+
templates.push({ id: ct.id, change, target: ct.file, ...mapped, credentialsChanged });
|
|
1047
|
+
}
|
|
1048
|
+
for (const ct of fromTemplates) {
|
|
1049
|
+
if (toTemplates.some((x) => x.id === ct.id))
|
|
1050
|
+
continue;
|
|
1051
|
+
templates.push({
|
|
1052
|
+
id: ct.id,
|
|
1053
|
+
change: "removed",
|
|
1054
|
+
...connectorOf(ct.id),
|
|
1055
|
+
credentialsChanged: false,
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
for (const c of templates) {
|
|
1059
|
+
components.push({
|
|
1060
|
+
kind: "connector",
|
|
1061
|
+
ref: c.id,
|
|
1062
|
+
change: c.change,
|
|
1063
|
+
...(c.credentialsChanged && { detail: "credentials change" }),
|
|
1064
|
+
...(c.change !== "added" &&
|
|
1065
|
+
c.change !== "removed" &&
|
|
1066
|
+
!c.connector && { detail: "no connector in this organization" }),
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
const inputs = {
|
|
1070
|
+
connectors: templates.filter((c) => c.change === "added").map((c) => c.id),
|
|
1071
|
+
credentialsChanged: templates.filter((c) => c.credentialsChanged).map((c) => c.id),
|
|
1072
|
+
extensionConfig,
|
|
1073
|
+
};
|
|
1074
|
+
// The inputs (as at install), when given
|
|
1075
|
+
const creates = new Map();
|
|
1076
|
+
const updates = new Map();
|
|
1077
|
+
if (input !== null) {
|
|
1078
|
+
for (const k of Object.keys(input.connectors ?? {})) {
|
|
1079
|
+
if (!inputs.connectors.includes(k) && !inputs.credentialsChanged.includes(k)) {
|
|
1080
|
+
throw fail(`connectors.${k}`, "This upgrade takes no input for this connector template (only added templates and changed credentials)");
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
if (input.extensionConfig !== undefined && !inputs.extensionConfig) {
|
|
1084
|
+
throw fail("extensionConfig", "This upgrade does not enable the plugin's extension");
|
|
1085
|
+
}
|
|
1086
|
+
for (const c of templates) {
|
|
1087
|
+
if (c.change === "added") {
|
|
1088
|
+
creates.set(c.id, this.installer.planConnector(actor, { itemId, version: to.version, name }, { id: c.id, file: c.target }, input, "", undefined));
|
|
1089
|
+
continue;
|
|
1090
|
+
}
|
|
1091
|
+
if (c.change !== "changed" || !c.connector || !c.target)
|
|
1092
|
+
continue;
|
|
1093
|
+
updates.set(c.id, this.templateUpdate(itemId, to.version, c, input));
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
const actions = new Set([
|
|
1097
|
+
...requiredActions(from.manifest),
|
|
1098
|
+
...requiredActions(to.manifest),
|
|
1099
|
+
]);
|
|
1100
|
+
for (const dep of dependencies)
|
|
1101
|
+
if (dep.plan)
|
|
1102
|
+
for (const a of planActions(dep.plan))
|
|
1103
|
+
actions.add(a);
|
|
1104
|
+
const removedDeps = dependencies.filter((x) => x.change === "removed" && x.child);
|
|
1105
|
+
if (removedDeps.length > 0) {
|
|
1106
|
+
const manifests = await db
|
|
1107
|
+
.selectFrom("plugin_installs as a")
|
|
1108
|
+
.innerJoin("catalog_item_versions as v", (j) => j.onRef("v.item_id", "=", "a.item_id").onRef("v.version", "=", "a.version"))
|
|
1109
|
+
.select("v.manifest")
|
|
1110
|
+
.where("a.id", "in", removedDeps.map((x) => x.child?.installId))
|
|
1111
|
+
.execute();
|
|
1112
|
+
for (const m of manifests)
|
|
1113
|
+
for (const a of requiredActions(m.manifest))
|
|
1114
|
+
actions.add(a);
|
|
1115
|
+
}
|
|
1116
|
+
const lh = ledgerHash(ledger);
|
|
1117
|
+
const diffHash = sha256(canonicalJson({
|
|
1118
|
+
installId,
|
|
1119
|
+
from: from.version,
|
|
1120
|
+
to: to.version,
|
|
1121
|
+
fromFiles: filesDigest(from),
|
|
1122
|
+
toFiles: filesDigest(to),
|
|
1123
|
+
ledger: lh,
|
|
1124
|
+
components,
|
|
1125
|
+
inputs,
|
|
1126
|
+
}));
|
|
1127
|
+
return {
|
|
1128
|
+
installId,
|
|
1129
|
+
itemId,
|
|
1130
|
+
name,
|
|
1131
|
+
from,
|
|
1132
|
+
to,
|
|
1133
|
+
ledger,
|
|
1134
|
+
extension,
|
|
1135
|
+
skills,
|
|
1136
|
+
agentTemplates,
|
|
1137
|
+
templates,
|
|
1138
|
+
dependencies,
|
|
1139
|
+
components,
|
|
1140
|
+
inputs,
|
|
1141
|
+
diffHash,
|
|
1142
|
+
ledgerHash: lh,
|
|
1143
|
+
actions: [...actions],
|
|
1144
|
+
creates,
|
|
1145
|
+
updates,
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* The update of a changed template's connector: the new endpoint preset with the org's values at
|
|
1150
|
+
* the editable paths, the new auth preset and policies; credentials only when the auth method or
|
|
1151
|
+
* the credentials it needs changed (then the input must give what the template needs).
|
|
1152
|
+
*/
|
|
1153
|
+
templateUpdate(itemId, version, c, input) {
|
|
1154
|
+
const template = c.target.template;
|
|
1155
|
+
const connector = c.connector;
|
|
1156
|
+
const path = `connectors.${c.id}`;
|
|
1157
|
+
const given = input.connectors?.[c.id];
|
|
1158
|
+
if (given !== undefined) {
|
|
1159
|
+
for (const k of ["name", "slug", "endpoint"]) {
|
|
1160
|
+
if (given[k] !== undefined) {
|
|
1161
|
+
throw fail(`${path}.${k}`, "An existing connector keeps its name, slug and endpoint fields");
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
const endpoint = structuredClone(template.endpoint);
|
|
1166
|
+
for (const e of template.editableEndpoint ?? []) {
|
|
1167
|
+
const v = getPath(connector.endpoint, e);
|
|
1168
|
+
if (v !== undefined)
|
|
1169
|
+
setPath(endpoint, e, v);
|
|
1170
|
+
}
|
|
1171
|
+
const auth = template.auth;
|
|
1172
|
+
const oauth = auth.type === "oauth_code" || auth.type === "oauth_client";
|
|
1173
|
+
if (c.credentialsChanged) {
|
|
1174
|
+
const missing = missingTemplateInput(template, given, { personalCredential: false });
|
|
1175
|
+
if (missing !== undefined) {
|
|
1176
|
+
throw fail(`${path}.${missing}`, `The auth method of this connector template changed; it needs ${missing === "client" ? "an OAuth client" : "an org credential"}`);
|
|
1177
|
+
}
|
|
1178
|
+
if (oauth && given?.credential !== undefined) {
|
|
1179
|
+
throw fail(`${path}.credential`, "This connector uses OAuth; provide client instead");
|
|
1180
|
+
}
|
|
1181
|
+
if (!oauth && given?.client !== undefined) {
|
|
1182
|
+
throw fail(`${path}.client`, "This connector does not use OAuth; provide credential instead");
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
const credential = oauth ? given?.client : given?.credential;
|
|
1186
|
+
return {
|
|
1187
|
+
endpoint,
|
|
1188
|
+
auth,
|
|
1189
|
+
template: {
|
|
1190
|
+
itemId,
|
|
1191
|
+
version,
|
|
1192
|
+
templateId: c.id,
|
|
1193
|
+
...(template.defaultToolPolicies !== undefined && {
|
|
1194
|
+
defaultToolPolicies: template.defaultToolPolicies,
|
|
1195
|
+
}),
|
|
1196
|
+
},
|
|
1197
|
+
resetCredentials: c.credentialsChanged,
|
|
1198
|
+
...(c.credentialsChanged && credential !== undefined && { credential }),
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
const cleanedUp = () => new ApiError("plugin.install_not_found", "The upgrade was cleaned up as interrupted while it was running");
|
|
1203
|
+
/** The install's ledger rows. */
|
|
1204
|
+
function ledgerOf(db, installId) {
|
|
1205
|
+
return db
|
|
1206
|
+
.selectFrom("plugin_install_components")
|
|
1207
|
+
.select(["kind", "ref", "created", "skill_version", "template_id", "dropped"])
|
|
1208
|
+
.where("install_id", "=", installId)
|
|
1209
|
+
.orderBy("kind")
|
|
1210
|
+
.orderBy("ref")
|
|
1211
|
+
.execute();
|
|
1212
|
+
}
|
|
1213
|
+
/** A fingerprint of the ledger (order independent). */
|
|
1214
|
+
function ledgerHash(rows) {
|
|
1215
|
+
return sha256(rows
|
|
1216
|
+
.map((r) => `${r.kind}\0${r.ref}\0${r.created}\0${r.skill_version}\0${r.template_id}\0${r.dropped}\n`)
|
|
1217
|
+
.sort()
|
|
1218
|
+
.join(""));
|
|
1219
|
+
}
|