@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,592 @@
|
|
|
1
|
+
import { EXTENSION_POINTS, ExtensionPermissionError, ExtensionPointUnavailableError, FILE_PREVIEW_EXTENSION_POINT, OBJECT_STORE_EXTENSION_POINT, parseExtensionPoint, validateFilePreviewProvider, validateToolSpec, } from "@capybara-hq/plugin-sdk";
|
|
2
|
+
import { Command, ConnectionBuilder, ControlledTransactionBuilder, Kysely, TransactionBuilder, } from "kysely";
|
|
3
|
+
import { conversationTopic } from "../conversations/event-log.js";
|
|
4
|
+
import { kernelDb } from "../db/kernel-schema.js";
|
|
5
|
+
import { enabledExtensionsQuery, isExtensionEnabled } from "../extensions/enabled.js";
|
|
6
|
+
import { checkHookRegistration } from "../extensions/hooks.js";
|
|
7
|
+
import { RegistrationLedger } from "../extensions/ledger.js";
|
|
8
|
+
import { audit } from "../identity/audit.js";
|
|
9
|
+
import { StorageService } from "../storage/storage.service.js";
|
|
10
|
+
import { EmbedTokens } from "../ui/embed-token.js";
|
|
11
|
+
import { canUsePlace, orgUiTopic, PluginUiRegistration, } from "../ui/registry.js";
|
|
12
|
+
import { createBus } from "./bus.js";
|
|
13
|
+
import { createKv } from "./kv.js";
|
|
14
|
+
import { createLock } from "./lock.js";
|
|
15
|
+
import { createQueue } from "./queue.js";
|
|
16
|
+
import { createSecrets } from "./secrets.js";
|
|
17
|
+
/**
|
|
18
|
+
* Queries the names of the extensions enabled in the organization (org config rows plus the
|
|
19
|
+
* required bootstrap plugins, see `extensions/enabled.ts`), not cached.
|
|
20
|
+
*/
|
|
21
|
+
// biome-ignore lint/suspicious/noExplicitAny: 内核不持有表结构类型
|
|
22
|
+
export async function queryEnabledPlugins(db, orgId) {
|
|
23
|
+
const rows = await enabledExtensionsQuery(kernelDb(db), orgId).execute();
|
|
24
|
+
return new Set(rows.map((r) => r.plugin));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 嵌入令牌校验时的一条查询:插件在该组织启用、组织未删除、账号 active、仍是成员;返回此刻的角色,不满足返回 undefined。
|
|
28
|
+
*/
|
|
29
|
+
async function currentEmbedRole(db, plugin, orgId, accountId) {
|
|
30
|
+
const row = await kernelDb(db)
|
|
31
|
+
.selectFrom("memberships")
|
|
32
|
+
.innerJoin("orgs", "orgs.id", "memberships.org_id")
|
|
33
|
+
.innerJoin("accounts", "accounts.id", "memberships.account_id")
|
|
34
|
+
.innerJoin(enabledExtensionsQuery(kernelDb(db), orgId).as("en"), (j) => j.on("en.plugin", "=", plugin))
|
|
35
|
+
.select("memberships.role")
|
|
36
|
+
.where("memberships.org_id", "=", orgId)
|
|
37
|
+
.where("memberships.account_id", "=", accountId)
|
|
38
|
+
.where("orgs.deleted_at", "is", null)
|
|
39
|
+
.where("accounts.status", "=", "active")
|
|
40
|
+
.executeTakeFirst();
|
|
41
|
+
return row?.role;
|
|
42
|
+
}
|
|
43
|
+
/** `ui.invalidate` 目标的一条查询:插件在该组织启用,且(带对话时)对话属于该组织。 */
|
|
44
|
+
async function invalidateTargetOk(db, plugin, orgId, conversationId) {
|
|
45
|
+
const k = kernelDb(db);
|
|
46
|
+
if (!(await isExtensionEnabled(k, orgId, plugin)))
|
|
47
|
+
return false;
|
|
48
|
+
if (conversationId === undefined)
|
|
49
|
+
return true;
|
|
50
|
+
const conv = await k
|
|
51
|
+
.selectFrom("conversations")
|
|
52
|
+
.select("conversations.id")
|
|
53
|
+
.where("conversations.id", "=", conversationId)
|
|
54
|
+
.where("conversations.org_id", "=", orgId)
|
|
55
|
+
.executeTakeFirst();
|
|
56
|
+
return conv !== undefined;
|
|
57
|
+
}
|
|
58
|
+
/** 越权审计去重键的 TTL:同一插件同一项在此期间只记一次。 */
|
|
59
|
+
const DENIAL_DEDUPE_TTL_SECONDS = 30 * 24 * 3600;
|
|
60
|
+
/**
|
|
61
|
+
* Records one `extension.permission_denied` audit, deduplicated per (extension, item) through Redis
|
|
62
|
+
* `SET NX EX` (once across replicas). When the audit write fails, the dedup key is removed (the next
|
|
63
|
+
* violation tries again) and a warning is logged; nothing is thrown to the extension (it already got
|
|
64
|
+
* an ExtensionPermissionError).
|
|
65
|
+
*/
|
|
66
|
+
async function recordDenial(deps, plugin, item) {
|
|
67
|
+
const key = `capk:plugin-permission-denied:${plugin}:${item}`;
|
|
68
|
+
try {
|
|
69
|
+
const first = await deps.redis.set(key, "1", "EX", DENIAL_DEDUPE_TTL_SECONDS, "NX");
|
|
70
|
+
if (first !== "OK")
|
|
71
|
+
return;
|
|
72
|
+
try {
|
|
73
|
+
await audit(kernelDb(deps.db), {
|
|
74
|
+
action: "extension.permission_denied",
|
|
75
|
+
target: plugin,
|
|
76
|
+
detail: { plugin, item },
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
await deps.redis.del(key).catch(() => { });
|
|
81
|
+
throw e;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
deps.log.warn("plugin permission denial audit failed", { plugin, item, err: e });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** 插件的 PG schema 名:`plugin_<name>`,name 中的 `-` 换成 `_`。 */
|
|
89
|
+
export function pluginSchemaName(name) {
|
|
90
|
+
return `plugin_${name.replaceAll("-", "_")}`;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* 包装插件拿到的 Kysely:`destroy`(及 `Symbol.asyncDispose`)换成抛错的函数(连接池归内核所有,插件销毁会拖垮整个进程的 DB 访问),
|
|
94
|
+
* 其余属性透传。方法绑定到原对象(Kysely 用私有字段,以 Proxy 作 this 调用会失败)。
|
|
95
|
+
* 递归包裹一切能再拿到 Kysely 的路径,使 `destroy` 在任何路径都被拦截:
|
|
96
|
+
* - 方法返回的新 Kysely(`withSchema` / `withPlugin` 等);
|
|
97
|
+
* - 方法返回的构建器(`transaction()` / `connection()` / `startTransaction()` 及其链式方法、
|
|
98
|
+
* 受控事务的 `Command`):其方法收到的回调拿到的 Kysely(`transaction().execute(cb)`、
|
|
99
|
+
* `connection().execute(cb)`)与返回的 Promise 结果(`startTransaction().execute()` 得到的受控事务)。
|
|
100
|
+
*/
|
|
101
|
+
// biome-ignore lint/suspicious/noExplicitAny: 内核不持有表结构类型
|
|
102
|
+
function guardDb(db, plugin) {
|
|
103
|
+
return new Proxy(db, {
|
|
104
|
+
get(target, prop) {
|
|
105
|
+
// `await using` 经 Symbol.asyncDispose 调用 destroy,同样拦截
|
|
106
|
+
if (prop === "destroy" || prop === Symbol.asyncDispose) {
|
|
107
|
+
return () => {
|
|
108
|
+
throw new Error(`plugin ${plugin}: ctx.db.destroy() is not allowed; the kernel owns the connection pool`);
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const value = Reflect.get(target, prop, target);
|
|
112
|
+
if (typeof value !== "function")
|
|
113
|
+
return value;
|
|
114
|
+
return (...args) => guardValue(value.apply(target, args), plugin);
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/** 能(经回调或返回值)再拿到 Kysely 的构建器类。 */
|
|
119
|
+
const BUILDERS = [ConnectionBuilder, TransactionBuilder, ControlledTransactionBuilder, Command];
|
|
120
|
+
const isBuilder = (v) => BUILDERS.some((B) => v instanceof B);
|
|
121
|
+
/** Kysely → guardDb;构建器 → guardBuilder;其余原样返回。 */
|
|
122
|
+
function guardValue(v, plugin) {
|
|
123
|
+
// biome-ignore lint/suspicious/noExplicitAny: 内核不持有表结构类型
|
|
124
|
+
if (v instanceof Kysely)
|
|
125
|
+
return guardDb(v, plugin);
|
|
126
|
+
if (isBuilder(v))
|
|
127
|
+
return guardBuilder(v, plugin);
|
|
128
|
+
return v;
|
|
129
|
+
}
|
|
130
|
+
/** 包装构建器:回调参数里的 Kysely、返回值与 Promise 结果都经 guardValue。 */
|
|
131
|
+
function guardBuilder(builder, plugin) {
|
|
132
|
+
return new Proxy(builder, {
|
|
133
|
+
get(target, prop) {
|
|
134
|
+
const value = Reflect.get(target, prop, target);
|
|
135
|
+
if (typeof value !== "function")
|
|
136
|
+
return value;
|
|
137
|
+
return (...args) => {
|
|
138
|
+
const wrapped = args.map((a) => typeof a === "function"
|
|
139
|
+
? (...cbArgs) => a(...cbArgs.map((c) => guardValue(c, plugin)))
|
|
140
|
+
: a);
|
|
141
|
+
const out = guardValue(value.apply(target, wrapped), plugin);
|
|
142
|
+
return out instanceof Promise ? out.then((r) => guardValue(r, plugin)) : out;
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/** 未注入能力访问时(单独构造 ctx 的测试等):没有任何提供方,组织启用照常查库。 */
|
|
148
|
+
const noProviders = (deps) => ({
|
|
149
|
+
providers: () => [],
|
|
150
|
+
enabledPlugins: (orgId) => queryEnabledPlugins(deps.db, orgId),
|
|
151
|
+
});
|
|
152
|
+
/**
|
|
153
|
+
* 插件实例的部署配置(已校验):`ctx.config` 是读它的 getter。hot 字段改动时宿主替换 `value`(C10),
|
|
154
|
+
* 插件下次读 `ctx.config` 就是新值;非 hot 改动重新加载实例(新 ctx)。
|
|
155
|
+
*/
|
|
156
|
+
export class LiveConfig {
|
|
157
|
+
value;
|
|
158
|
+
constructor(value) {
|
|
159
|
+
this.value = value;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Builds the namespaced ctx for one extension from its permission manifest, and returns its
|
|
164
|
+
* registered routes, provided extension points and a close function.
|
|
165
|
+
* `deploymentConfig` may be a {@link LiveConfig} (shared by the instance, replaceable in place);
|
|
166
|
+
* otherwise a fixed value is wrapped.
|
|
167
|
+
* - Granted automatically: `kv`, `lock`, `bus`, `queue`, `log`, `db`, `config`, `provide`;
|
|
168
|
+
* - `secrets` only with `permissions.secrets`, `storage` only with `permissions.storage`; `http.route`
|
|
169
|
+
* only the method + path declared in `permissions.http`; `tools.register` only names declared in
|
|
170
|
+
* `permissions.tools`; `ui.view` / `ui.action` only ids declared in `permissions.ui.views` (during
|
|
171
|
+
* setup only); `ui.invalidate` sends a refresh over the kernel bus (namespace `kernel`), across
|
|
172
|
+
* replicas; `ui.verifyEmbedToken` checks an embed token the kernel signed: `claims.plugin` must be
|
|
173
|
+
* this extension, the extension still enabled in that org and the caller still an active member of
|
|
174
|
+
* the org (org not deleted); returns the current role;
|
|
175
|
+
* - `extensionPoints` only gives extension points declared in `requires` / `optional` that are not
|
|
176
|
+
* brokered (through `extensionPointAccess`).
|
|
177
|
+
* - `hooks.on` only during setup, only mount points declared in `permissions.hooks` and available
|
|
178
|
+
* (`HOOKS_AVAILABLE`), `policy` only on `POLICY_HOOKS` declared in `permissions.policyHooks`; kept on the ctx (`registeredHooks`, read by
|
|
179
|
+
* the host's `HookRegistry` from active instances only) and recorded in the ledger.
|
|
180
|
+
* An undeclared access throws `ExtensionPermissionError` and audits `extension.permission_denied`
|
|
181
|
+
* (once per extension and item).
|
|
182
|
+
*/
|
|
183
|
+
export function createExtensionContext(deps, manifest, deploymentConfig, extensionPointAccess = noProviders(deps)) {
|
|
184
|
+
const name = manifest.name;
|
|
185
|
+
const live = deploymentConfig instanceof LiveConfig ? deploymentConfig : new LiveConfig(deploymentConfig);
|
|
186
|
+
const routes = [];
|
|
187
|
+
const provided = new Map();
|
|
188
|
+
const registeredTools = [];
|
|
189
|
+
let toolsSealed = false;
|
|
190
|
+
const queue = createQueue(deps.redisUrl, name, { runWorkers: deps.runWorkers, log: deps.log });
|
|
191
|
+
const bus = createBus(deps.redis, deps.busSubscriber, name, deps.log);
|
|
192
|
+
// 界面刷新发在内核命名空间(会话主题 conv.<id> / 组织主题 org-ui.<orgId>),不在插件自己的命名空间
|
|
193
|
+
const kernelBus = createBus(deps.redis, deps.busSubscriber, "kernel", deps.log);
|
|
194
|
+
const ledger = new RegistrationLedger(name);
|
|
195
|
+
const lifecycle = new AbortController();
|
|
196
|
+
/** 撤销时队列 worker 排空的截止时刻(epoch 毫秒);undefined = 一直等(停机路径)。 */
|
|
197
|
+
let drainDeadline;
|
|
198
|
+
const lock = createLock(deps.redis, name);
|
|
199
|
+
const tag = (fields) => ({ ...fields, plugin: name });
|
|
200
|
+
let closing;
|
|
201
|
+
const perms = manifest.permissions ?? {};
|
|
202
|
+
/** 进行中的越权审计写入(close 时等待,保证不比 ctx 活得久)。 */
|
|
203
|
+
const audits = new Set();
|
|
204
|
+
const deny = (item) => {
|
|
205
|
+
const p = recordDenial(deps, name, item).finally(() => audits.delete(p));
|
|
206
|
+
audits.add(p);
|
|
207
|
+
throw new ExtensionPermissionError(name, item);
|
|
208
|
+
};
|
|
209
|
+
// 只暴露插件契约中的 get / set(KernelSecrets 的 delete 仅供内核使用)
|
|
210
|
+
const secretsApi = perms.secrets
|
|
211
|
+
? (({ get, set }) => ({ get, set }))(createSecrets(deps.db, deps.keyring, name))
|
|
212
|
+
: undefined;
|
|
213
|
+
// ctx.storage:经已加载的 object-store@1 提供方(按调用时取;依赖排序保证提供方先于本插件 setup),
|
|
214
|
+
// 键加前缀 plugins/<name>/<orgId>/。没有提供方时各方法 reject storage.unavailable
|
|
215
|
+
// (宿主不会加载这样的插件,见 approvals.ts;单独构造 ctx 的测试会走到这里)
|
|
216
|
+
const storageApi = perms.storage
|
|
217
|
+
? new StorageService({
|
|
218
|
+
provider: () => extensionPointAccess.providers(OBJECT_STORE_EXTENSION_POINT)[0]?.impl,
|
|
219
|
+
plugins: async () => [name],
|
|
220
|
+
}).forPlugin(name)
|
|
221
|
+
: undefined;
|
|
222
|
+
const declaredRoutes = new Set((perms.http ?? []).map((r) => `${r.method} ${r.path}`));
|
|
223
|
+
const declaredTools = new Set(perms.tools ?? []);
|
|
224
|
+
const declaredHooks = new Set(perms.hooks ?? []);
|
|
225
|
+
const declaredPolicyHooks = new Set(perms.policyHooks ?? []);
|
|
226
|
+
const registeredHooks = [];
|
|
227
|
+
const declaredPoints = new Set([
|
|
228
|
+
...(manifest.requires ?? []),
|
|
229
|
+
...(manifest.optional ?? []),
|
|
230
|
+
]);
|
|
231
|
+
const uiReg = new PluginUiRegistration(manifest, deny);
|
|
232
|
+
const embedTokens = new EmbedTokens(deps.keyring);
|
|
233
|
+
const uiPlaces = new Map((manifest.ui ?? []).map((c) => [c.id, c.place]));
|
|
234
|
+
/** 校验能力可取用(非 brokered、已在清单声明),返回其登记信息。 */
|
|
235
|
+
const checkExtensionPoint = (ref) => {
|
|
236
|
+
const capName = parseExtensionPoint(ref).name;
|
|
237
|
+
const info = Object.hasOwn(EXTENSION_POINTS, capName) ? EXTENSION_POINTS[capName] : undefined;
|
|
238
|
+
if (info?.brokered) {
|
|
239
|
+
throw new Error(`plugin ${name}: extension point ${ref} is brokered and can only be used through the platform API`);
|
|
240
|
+
}
|
|
241
|
+
if (!info || !declaredPoints.has(ref))
|
|
242
|
+
return deny(`requires/optional ${ref}`);
|
|
243
|
+
return info;
|
|
244
|
+
};
|
|
245
|
+
/** 部署级视角选出提供方:指定 provider 时按名找;单选能力取唯一提供方;多选能力必须指定。 */
|
|
246
|
+
const pick = (ref, provider) => {
|
|
247
|
+
const info = checkExtensionPoint(ref);
|
|
248
|
+
const providers = extensionPointAccess.providers(ref);
|
|
249
|
+
if (provider !== undefined) {
|
|
250
|
+
const chosen = providers.find((p) => p.plugin === provider);
|
|
251
|
+
if (!chosen) {
|
|
252
|
+
throw new ExtensionPointUnavailableError(`extension point ${ref} is not provided by plugin ${provider}`);
|
|
253
|
+
}
|
|
254
|
+
return { info, chosen };
|
|
255
|
+
}
|
|
256
|
+
if (info.cardinality === "multi") {
|
|
257
|
+
// 多选能力即使只有一个提供者也要显式指定,避免新增提供者后行为悄悄变化
|
|
258
|
+
throw new Error(`plugin ${name}: extension point ${ref} is multi-provider; specify a provider name (available: ${providers.map((p) => p.plugin).join(", ") || "none"})`);
|
|
259
|
+
}
|
|
260
|
+
const chosen = providers[0];
|
|
261
|
+
if (!chosen)
|
|
262
|
+
throw new ExtensionPointUnavailableError(`extension point ${ref} has no provider`);
|
|
263
|
+
return { info, chosen };
|
|
264
|
+
};
|
|
265
|
+
/** 组织视角过滤提供方:orgScoped 能力只留该组织已启用的(每次查询,不缓存)。 */
|
|
266
|
+
const forOrg = async (orgScoped, orgId, entries) => {
|
|
267
|
+
if (!orgScoped || entries.length === 0)
|
|
268
|
+
return entries;
|
|
269
|
+
const enabled = await extensionPointAccess.enabledPlugins(orgId);
|
|
270
|
+
return entries.filter((e) => enabled.has(e.plugin));
|
|
271
|
+
};
|
|
272
|
+
const listAll = (ref) => {
|
|
273
|
+
const info = checkExtensionPoint(ref);
|
|
274
|
+
return { info, entries: extensionPointAccess.providers(ref).map((p) => ({ ...p })) };
|
|
275
|
+
};
|
|
276
|
+
// 不传 orgId:同步返回;传 orgId:返回 Promise,所有错误都以 reject 交出
|
|
277
|
+
const extensionPoints = {
|
|
278
|
+
get(ref, provider, opts) {
|
|
279
|
+
const orgId = opts?.orgId;
|
|
280
|
+
if (orgId === undefined)
|
|
281
|
+
return pick(ref, provider).chosen.impl;
|
|
282
|
+
return (async () => {
|
|
283
|
+
const { info, chosen } = pick(ref, provider);
|
|
284
|
+
if ((await forOrg(info.orgScoped, orgId, [chosen])).length === 0) {
|
|
285
|
+
throw new ExtensionPointUnavailableError(`extension point ${ref} provider ${chosen.plugin} is not enabled in organization ${orgId}`);
|
|
286
|
+
}
|
|
287
|
+
return chosen.impl;
|
|
288
|
+
})();
|
|
289
|
+
},
|
|
290
|
+
list(ref, opts) {
|
|
291
|
+
const orgId = opts?.orgId;
|
|
292
|
+
if (orgId === undefined)
|
|
293
|
+
return listAll(ref).entries;
|
|
294
|
+
return (async () => {
|
|
295
|
+
const { info, entries } = listAll(ref);
|
|
296
|
+
return forOrg(info.orgScoped, orgId, entries);
|
|
297
|
+
})();
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
const ctx = {
|
|
301
|
+
kv: createKv(deps.redis, name),
|
|
302
|
+
lock: {
|
|
303
|
+
// 取到的租约记账:实例撤销时释放仍持有的;插件自己 release 时销账
|
|
304
|
+
async acquire(key, opts) {
|
|
305
|
+
const lease = await lock.acquire(key, opts);
|
|
306
|
+
if (!lease)
|
|
307
|
+
return null;
|
|
308
|
+
const settle = ledger.add({ kind: "lease", key, dispose: () => lease.release() });
|
|
309
|
+
return {
|
|
310
|
+
renew: () => lease.renew(),
|
|
311
|
+
async release() {
|
|
312
|
+
settle();
|
|
313
|
+
await lease.release();
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
extensionPoints,
|
|
319
|
+
bus: {
|
|
320
|
+
publish: bus.publish,
|
|
321
|
+
async subscribe(topic, handler) {
|
|
322
|
+
const closedError = () => new Error(`plugin ${name}: ctx is closed; bus.subscribe is not allowed after close`);
|
|
323
|
+
if (closing || ledger.sealed)
|
|
324
|
+
throw closedError();
|
|
325
|
+
const un = await bus.subscribe(topic, handler);
|
|
326
|
+
// 订阅期间 ctx 被关闭 / 实例开始撤销:账已封,这里自己退订,不留下无人管的订阅
|
|
327
|
+
if (closing || ledger.sealed) {
|
|
328
|
+
await un();
|
|
329
|
+
throw closedError();
|
|
330
|
+
}
|
|
331
|
+
const settle = ledger.add({ kind: "bus", key: topic, dispose: un });
|
|
332
|
+
return async () => {
|
|
333
|
+
settle();
|
|
334
|
+
await un();
|
|
335
|
+
};
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
queue: {
|
|
339
|
+
define(qname, handler, o) {
|
|
340
|
+
queue.define(qname, handler, o);
|
|
341
|
+
ledger.add({
|
|
342
|
+
kind: "queue",
|
|
343
|
+
key: qname,
|
|
344
|
+
dispose: () => queue.stopWorker(qname, drainDeadline),
|
|
345
|
+
});
|
|
346
|
+
},
|
|
347
|
+
add: queue.add,
|
|
348
|
+
},
|
|
349
|
+
db: guardDb(deps.db.withSchema(pluginSchemaName(name)), name),
|
|
350
|
+
http: {
|
|
351
|
+
route(method, path, handler) {
|
|
352
|
+
if (typeof path !== "string" || !path.startsWith("/")) {
|
|
353
|
+
throw new Error(`plugin ${name}: route path "${path}" must start with "/"`);
|
|
354
|
+
}
|
|
355
|
+
if (!declaredRoutes.has(`${method} ${path}`))
|
|
356
|
+
deny(`permissions.http ${method} ${path}`);
|
|
357
|
+
const full = `/plugins/${name}${path}`;
|
|
358
|
+
if (routes.some((r) => r.method === method && r.path === full)) {
|
|
359
|
+
throw new Error(`plugin ${name}: duplicate route ${method} ${path}`);
|
|
360
|
+
}
|
|
361
|
+
const route = { method, path: full, handler };
|
|
362
|
+
routes.push(route);
|
|
363
|
+
ledger.add({
|
|
364
|
+
kind: "route",
|
|
365
|
+
key: `${method} ${path}`,
|
|
366
|
+
dispose: async () => {
|
|
367
|
+
const i = routes.indexOf(route);
|
|
368
|
+
if (i >= 0)
|
|
369
|
+
routes.splice(i, 1);
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
tools: {
|
|
375
|
+
register(spec, handler) {
|
|
376
|
+
if (toolsSealed) {
|
|
377
|
+
throw new Error(`plugin ${name}: tools.register is only allowed during setup; setup has completed`);
|
|
378
|
+
}
|
|
379
|
+
const problems = validateToolSpec(spec);
|
|
380
|
+
if (problems.length > 0) {
|
|
381
|
+
throw new Error(`plugin ${name}: invalid tool spec: ${problems.join("; ")}`);
|
|
382
|
+
}
|
|
383
|
+
if (!declaredTools.has(spec.name))
|
|
384
|
+
deny(`permissions.tools ${spec.name}`);
|
|
385
|
+
if (typeof handler !== "function") {
|
|
386
|
+
throw new Error(`plugin ${name}: tool "${spec.name}" handler must be a function`);
|
|
387
|
+
}
|
|
388
|
+
if (registeredTools.some((t) => t.spec.name === spec.name)) {
|
|
389
|
+
throw new Error(`plugin ${name}: duplicate tool "${spec.name}"`);
|
|
390
|
+
}
|
|
391
|
+
const tool = { spec, handler };
|
|
392
|
+
registeredTools.push(tool);
|
|
393
|
+
ledger.add({
|
|
394
|
+
kind: "tool",
|
|
395
|
+
key: spec.name,
|
|
396
|
+
dispose: async () => {
|
|
397
|
+
const i = registeredTools.indexOf(tool);
|
|
398
|
+
if (i >= 0)
|
|
399
|
+
registeredTools.splice(i, 1);
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
hooks: {
|
|
405
|
+
on(mount, handler, opts) {
|
|
406
|
+
if (toolsSealed) {
|
|
407
|
+
throw new Error(`plugin ${name}: hooks.on is only allowed during setup; setup has completed`);
|
|
408
|
+
}
|
|
409
|
+
// not-available / malformed registrations are programming errors, not permission denials
|
|
410
|
+
checkHookRegistration(name, mount, handler, opts);
|
|
411
|
+
if (!declaredHooks.has(mount))
|
|
412
|
+
deny(`permissions.hooks ${mount}`);
|
|
413
|
+
// M-1: deployment state learns policy mounts from the manifest, so they must be declared
|
|
414
|
+
if (opts.mode === "policy" && !declaredPolicyHooks.has(mount)) {
|
|
415
|
+
deny(`permissions.policyHooks ${mount}`);
|
|
416
|
+
}
|
|
417
|
+
const hook = {
|
|
418
|
+
mount: mount,
|
|
419
|
+
handler: handler,
|
|
420
|
+
mode: opts.mode,
|
|
421
|
+
};
|
|
422
|
+
registeredHooks.push(hook);
|
|
423
|
+
ledger.add({
|
|
424
|
+
kind: "hook",
|
|
425
|
+
key: mount,
|
|
426
|
+
dispose: async () => {
|
|
427
|
+
const i = registeredHooks.indexOf(hook);
|
|
428
|
+
if (i >= 0)
|
|
429
|
+
registeredHooks.splice(i, 1);
|
|
430
|
+
},
|
|
431
|
+
});
|
|
432
|
+
},
|
|
433
|
+
},
|
|
434
|
+
ui: {
|
|
435
|
+
view(id, render) {
|
|
436
|
+
uiReg.view(id, render);
|
|
437
|
+
ledger.add({
|
|
438
|
+
kind: "ui.view",
|
|
439
|
+
key: id,
|
|
440
|
+
dispose: async () => {
|
|
441
|
+
if (uiReg.views.get(id) === render)
|
|
442
|
+
uiReg.views.delete(id);
|
|
443
|
+
},
|
|
444
|
+
});
|
|
445
|
+
},
|
|
446
|
+
action(id, handler) {
|
|
447
|
+
uiReg.action(id, handler);
|
|
448
|
+
ledger.add({
|
|
449
|
+
kind: "ui.action",
|
|
450
|
+
key: id,
|
|
451
|
+
dispose: async () => {
|
|
452
|
+
if (uiReg.actions.get(id) === handler)
|
|
453
|
+
uiReg.actions.delete(id);
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
},
|
|
457
|
+
// 目标先核对(一条查询):插件在 orgId 启用;带对话时对话属于 orgId。不满足就只记 debug、不发
|
|
458
|
+
// (插件拿着过期或别的组织的 id 调用不是错误,但不能借它往别的组织的流里发刷新)
|
|
459
|
+
async invalidate(target) {
|
|
460
|
+
const t = uiReg.target(target);
|
|
461
|
+
if (!(await invalidateTargetOk(deps.db, name, t.orgId, t.conversationId))) {
|
|
462
|
+
deps.log.debug?.("plugin ui invalidate ignored: plugin not enabled or conversation not in org", {
|
|
463
|
+
plugin: name,
|
|
464
|
+
orgId: t.orgId,
|
|
465
|
+
...(t.conversationId !== undefined && { conversationId: t.conversationId }),
|
|
466
|
+
contribution: t.contribution,
|
|
467
|
+
});
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
const msg = { kind: "ui", plugin: name, contribution: t.contribution };
|
|
471
|
+
await kernelBus.publish(t.conversationId !== undefined
|
|
472
|
+
? conversationTopic(t.conversationId)
|
|
473
|
+
: orgUiTopic(t.orgId), msg);
|
|
474
|
+
},
|
|
475
|
+
// 只认签给本插件、未过期、未篡改的令牌(见 ui/embed-token.ts);再用一条查询核对此刻:本插件仍在该组织
|
|
476
|
+
// 启用、组织未删除、账号 active 且仍是成员(每次查 PG、不缓存:停用插件、离职、停用账号后已签出的令牌立即失效)。
|
|
477
|
+
// 返回的 role 是此刻的角色;组织页面的令牌在调用者已降为 guest 时失效(A38)。其余一律 null
|
|
478
|
+
async verifyEmbedToken(token) {
|
|
479
|
+
const claims = embedTokens.verify(token, name);
|
|
480
|
+
if (claims === null)
|
|
481
|
+
return null;
|
|
482
|
+
const role = await currentEmbedRole(deps.db, name, claims.orgId, claims.accountId);
|
|
483
|
+
if (role === undefined)
|
|
484
|
+
return null;
|
|
485
|
+
if (!canUsePlace(uiPlaces.get(claims.contribution) ?? "page", role))
|
|
486
|
+
return null;
|
|
487
|
+
return { ...claims, role };
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
provide(extensionPoint, impl) {
|
|
491
|
+
if (!manifest.provides.includes(extensionPoint)) {
|
|
492
|
+
throw new Error(`plugin ${name}: provided extension point ${extensionPoint} not declared in provides`);
|
|
493
|
+
}
|
|
494
|
+
if (provided.has(extensionPoint)) {
|
|
495
|
+
throw new Error(`plugin ${name}: extension point ${extensionPoint} provided more than once`);
|
|
496
|
+
}
|
|
497
|
+
if (extensionPoint === FILE_PREVIEW_EXTENSION_POINT)
|
|
498
|
+
checkFilePreviewProvider(manifest, impl);
|
|
499
|
+
provided.set(extensionPoint, impl);
|
|
500
|
+
ledger.add({
|
|
501
|
+
kind: "provide",
|
|
502
|
+
key: extensionPoint,
|
|
503
|
+
dispose: async () => {
|
|
504
|
+
if (provided.get(extensionPoint) === impl)
|
|
505
|
+
provided.delete(extensionPoint);
|
|
506
|
+
},
|
|
507
|
+
});
|
|
508
|
+
},
|
|
509
|
+
signal: lifecycle.signal,
|
|
510
|
+
// getter:hot 字段改动就地替换(C10),插件用时读到新值
|
|
511
|
+
get config() {
|
|
512
|
+
return live.value;
|
|
513
|
+
},
|
|
514
|
+
log: {
|
|
515
|
+
info: (msg, fields) => deps.log.info(msg, tag(fields)),
|
|
516
|
+
warn: (msg, fields) => deps.log.warn(msg, tag(fields)),
|
|
517
|
+
error: (msg, fields) => deps.log.error(msg, tag(fields)),
|
|
518
|
+
},
|
|
519
|
+
routes,
|
|
520
|
+
provided,
|
|
521
|
+
registeredTools,
|
|
522
|
+
registeredHooks,
|
|
523
|
+
uiViews: uiReg.views,
|
|
524
|
+
uiActions: uiReg.actions,
|
|
525
|
+
ledger,
|
|
526
|
+
async beginDispose(deadline) {
|
|
527
|
+
drainDeadline = deadline;
|
|
528
|
+
await queue.pauseWorkers();
|
|
529
|
+
},
|
|
530
|
+
drainQueues: (deadline) => queue.drainWorkers(deadline),
|
|
531
|
+
abort() {
|
|
532
|
+
if (!lifecycle.signal.aborted)
|
|
533
|
+
lifecycle.abort();
|
|
534
|
+
},
|
|
535
|
+
sealTools() {
|
|
536
|
+
toolsSealed = true;
|
|
537
|
+
uiReg.seal();
|
|
538
|
+
},
|
|
539
|
+
/** 幂等:重复 / 并发调用共享同一次关闭。 */
|
|
540
|
+
close() {
|
|
541
|
+
closing ??= (async () => {
|
|
542
|
+
if (!lifecycle.signal.aborted)
|
|
543
|
+
lifecycle.abort();
|
|
544
|
+
await ledger.disposeAll((r, err) => deps.log.warn("plugin registration dispose failed", {
|
|
545
|
+
plugin: name,
|
|
546
|
+
kind: r.kind,
|
|
547
|
+
key: r.key,
|
|
548
|
+
err,
|
|
549
|
+
}));
|
|
550
|
+
await queue.close();
|
|
551
|
+
await Promise.allSettled([...audits]);
|
|
552
|
+
})();
|
|
553
|
+
return closing;
|
|
554
|
+
},
|
|
555
|
+
};
|
|
556
|
+
// 声明了 secrets / storage 时是普通属性;未声明时是不可枚举的抛错 getter——展开 / 打印 ctx 不会触发越权与审计
|
|
557
|
+
const gated = (key, api) => {
|
|
558
|
+
if (api) {
|
|
559
|
+
Object.assign(ctx, { [key]: api });
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
Object.defineProperty(ctx, key, {
|
|
563
|
+
get: () => deny(`permissions.${key}`),
|
|
564
|
+
enumerable: false,
|
|
565
|
+
configurable: false,
|
|
566
|
+
});
|
|
567
|
+
};
|
|
568
|
+
gated("secrets", secretsApi);
|
|
569
|
+
gated("storage", storageApi);
|
|
570
|
+
return ctx;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* A `file-preview@1` provider must be valid when it is provided (setup fails otherwise): the shape
|
|
574
|
+
* ({@link validateFilePreviewProvider}), and every `embed` names a ui contribution of this extension
|
|
575
|
+
* with `kind: "embed"` and `place: "file-preview"`.
|
|
576
|
+
*/
|
|
577
|
+
function checkFilePreviewProvider(manifest, impl) {
|
|
578
|
+
const problems = validateFilePreviewProvider(impl);
|
|
579
|
+
if (problems.length === 0) {
|
|
580
|
+
const embeds = new Set((manifest.ui ?? [])
|
|
581
|
+
.filter((c) => c.kind === "embed" && c.place === "file-preview")
|
|
582
|
+
.map((c) => c.id));
|
|
583
|
+
impl.previewers.forEach((p, i) => {
|
|
584
|
+
if (p.embed !== undefined && !embeds.has(p.embed)) {
|
|
585
|
+
problems.push(`previewers[${i}].embed "${p.embed}" is not a ui contribution { kind: "embed", place: "file-preview" }`);
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
if (problems.length > 0) {
|
|
590
|
+
throw new Error(`plugin ${manifest.name}: invalid ${FILE_PREVIEW_EXTENSION_POINT} provider: ${problems.join("; ")}`);
|
|
591
|
+
}
|
|
592
|
+
}
|
package/dist/ctx/kv.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@capybara-hq/plugin-sdk";
|
|
2
|
+
import type { Redis } from "ioredis";
|
|
3
|
+
/** 创建带命名空间的 ctx.kv:实际键为 `cap:<namespace>:kv:<key>`(与锁键 `cap:<namespace>:lock:<key>` 不重叠),写入必须带 TTL。 */
|
|
4
|
+
export declare function createKv(redis: Redis, namespace: string): ExtensionContext["kv"];
|
package/dist/ctx/kv.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { assertNamespace } from "./namespace.js";
|
|
2
|
+
/** 首次创建时原子设置 TTL;已存在的键不重置 TTL。 */
|
|
3
|
+
const INCR_WITH_TTL = `
|
|
4
|
+
local v = redis.call('INCR', KEYS[1])
|
|
5
|
+
redis.call('EXPIRE', KEYS[1], ARGV[1], 'NX')
|
|
6
|
+
return v
|
|
7
|
+
`;
|
|
8
|
+
function assertTtl(name, value) {
|
|
9
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
10
|
+
throw new TypeError(`${name} must be a positive integer`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** 创建带命名空间的 ctx.kv:实际键为 `cap:<namespace>:kv:<key>`(与锁键 `cap:<namespace>:lock:<key>` 不重叠),写入必须带 TTL。 */
|
|
14
|
+
export function createKv(redis, namespace) {
|
|
15
|
+
assertNamespace(namespace);
|
|
16
|
+
const k = (key) => `cap:${namespace}:kv:${key}`;
|
|
17
|
+
return {
|
|
18
|
+
get: (key) => redis.get(k(key)),
|
|
19
|
+
async set(key, value, opts) {
|
|
20
|
+
assertTtl("ttlSeconds", opts?.ttlSeconds);
|
|
21
|
+
await redis.set(k(key), value, "EX", opts.ttlSeconds);
|
|
22
|
+
},
|
|
23
|
+
async del(key) {
|
|
24
|
+
await redis.del(k(key));
|
|
25
|
+
},
|
|
26
|
+
async incr(key, opts) {
|
|
27
|
+
assertTtl("ttlSeconds", opts?.ttlSeconds);
|
|
28
|
+
return Number(await redis.eval(INCR_WITH_TTL, 1, k(key), opts.ttlSeconds));
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@capybara-hq/plugin-sdk";
|
|
2
|
+
import type { Redis } from "ioredis";
|
|
3
|
+
/** 创建带命名空间的 ctx.lock:键为 `cap:<namespace>:lock:<key>`,token 比对保证只操作自己持有的锁。 */
|
|
4
|
+
export declare function createLock(redis: Redis, namespace: string): ExtensionContext["lock"];
|
package/dist/ctx/lock.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { assertNamespace } from "./namespace.js";
|
|
3
|
+
const RENEW = `
|
|
4
|
+
if redis.call('GET', KEYS[1]) == ARGV[1] then
|
|
5
|
+
return redis.call('PEXPIRE', KEYS[1], ARGV[2])
|
|
6
|
+
end
|
|
7
|
+
return 0
|
|
8
|
+
`;
|
|
9
|
+
const RELEASE = `
|
|
10
|
+
if redis.call('GET', KEYS[1]) == ARGV[1] then
|
|
11
|
+
return redis.call('DEL', KEYS[1])
|
|
12
|
+
end
|
|
13
|
+
return 0
|
|
14
|
+
`;
|
|
15
|
+
/** 创建带命名空间的 ctx.lock:键为 `cap:<namespace>:lock:<key>`,token 比对保证只操作自己持有的锁。 */
|
|
16
|
+
export function createLock(redis, namespace) {
|
|
17
|
+
assertNamespace(namespace);
|
|
18
|
+
return {
|
|
19
|
+
async acquire(key, opts) {
|
|
20
|
+
const ttlMs = opts?.ttlMs;
|
|
21
|
+
if (typeof ttlMs !== "number" || !Number.isInteger(ttlMs) || ttlMs <= 0) {
|
|
22
|
+
throw new TypeError("ttlMs must be a positive integer");
|
|
23
|
+
}
|
|
24
|
+
const redisKey = `cap:${namespace}:lock:${key}`;
|
|
25
|
+
const token = randomUUID();
|
|
26
|
+
const ok = await redis.set(redisKey, token, "PX", ttlMs, "NX");
|
|
27
|
+
if (ok !== "OK")
|
|
28
|
+
return null;
|
|
29
|
+
return {
|
|
30
|
+
async renew() {
|
|
31
|
+
return Number(await redis.eval(RENEW, 1, redisKey, token, ttlMs)) === 1;
|
|
32
|
+
},
|
|
33
|
+
async release() {
|
|
34
|
+
await redis.eval(RELEASE, 1, redisKey, token);
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|