@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,271 @@
|
|
|
1
|
+
import { lookup as dnsLookup } from "node:dns/promises";
|
|
2
|
+
import { BlockList, isIP } from "node:net";
|
|
3
|
+
/**
|
|
4
|
+
* Public-address checks for downloads whose URL comes from a marketplace or an admin (spec §5.1).
|
|
5
|
+
* A host is resolved once, every resolved address must be public, and the caller connects to the
|
|
6
|
+
* checked addresses only (see `safe-fetch.ts`), so a DNS answer that changes between check and
|
|
7
|
+
* connect (DNS rebinding, e.g. `10.0.0.1.nip.io`) cannot reach a private address.
|
|
8
|
+
*
|
|
9
|
+
* The blocked ranges are those of `plugins/sandbox-docker/egress-proxy/proxy.mjs`, ported here
|
|
10
|
+
* because the kernel must not import from a plugin. IPv6 addresses that embed an IPv4 address
|
|
11
|
+
* (v4-mapped `::ffff:0:0/96`, v4-compatible `::/96`, NAT64 `64:ff9b::/96`, 6to4 `2002::/16`) are
|
|
12
|
+
* judged by the embedded IPv4 address; anything unparseable is refused. One addition over proxy.mjs:
|
|
13
|
+
* SIIT IPv4-translated addresses (`::ffff:0:a.b.c.d`, `::ffff:0:0:0/96`, RFC 2765) are also judged by
|
|
14
|
+
* the embedded IPv4 address (proxy.mjs falls through to the IPv6 table, where they are not blocked);
|
|
15
|
+
* a host behind a SIIT translator could otherwise reach a private IPv4 address.
|
|
16
|
+
*
|
|
17
|
+
* Link-local addresses (`169.254.0.0/16`, including cloud metadata, and `fe80::/10`) stay forbidden
|
|
18
|
+
* whatever the exemptions: no internal mirror lives there.
|
|
19
|
+
*/
|
|
20
|
+
const BLOCKED_V4 = [
|
|
21
|
+
["0.0.0.0", 8], // unspecified / this network
|
|
22
|
+
["10.0.0.0", 8],
|
|
23
|
+
["100.64.0.0", 10], // CGNAT
|
|
24
|
+
["127.0.0.0", 8],
|
|
25
|
+
["169.254.0.0", 16], // link-local, including cloud metadata 169.254.169.254
|
|
26
|
+
["172.16.0.0", 12],
|
|
27
|
+
["192.0.0.0", 24],
|
|
28
|
+
["192.0.2.0", 24],
|
|
29
|
+
["192.88.99.0", 24],
|
|
30
|
+
["192.168.0.0", 16],
|
|
31
|
+
["198.18.0.0", 15],
|
|
32
|
+
["198.51.100.0", 24],
|
|
33
|
+
["203.0.113.0", 24],
|
|
34
|
+
["224.0.0.0", 4], // multicast
|
|
35
|
+
["240.0.0.0", 4], // reserved and broadcast
|
|
36
|
+
];
|
|
37
|
+
const BLOCKED_V6 = [
|
|
38
|
+
["::", 128],
|
|
39
|
+
["::1", 128],
|
|
40
|
+
["100::", 64],
|
|
41
|
+
["2001:db8::", 32],
|
|
42
|
+
["fc00::", 7], // ULA
|
|
43
|
+
["fe80::", 10], // link-local
|
|
44
|
+
["fec0::", 10],
|
|
45
|
+
["ff00::", 8], // multicast
|
|
46
|
+
];
|
|
47
|
+
// Read-only after construction: address tables, not shared mutable state.
|
|
48
|
+
const blocked = new BlockList();
|
|
49
|
+
for (const [a, p] of BLOCKED_V4)
|
|
50
|
+
blocked.addSubnet(a, p, "ipv4");
|
|
51
|
+
for (const [a, p] of BLOCKED_V6)
|
|
52
|
+
blocked.addSubnet(a, p, "ipv6");
|
|
53
|
+
const linkLocal = new BlockList();
|
|
54
|
+
linkLocal.addSubnet("169.254.0.0", 16, "ipv4");
|
|
55
|
+
linkLocal.addSubnet("fe80::", 10, "ipv6");
|
|
56
|
+
/** An IPv6 address as its 8 16-bit groups; null when it cannot be parsed. */
|
|
57
|
+
function v6Groups(ip) {
|
|
58
|
+
let s = ip.toLowerCase();
|
|
59
|
+
const zone = s.indexOf("%");
|
|
60
|
+
if (zone >= 0)
|
|
61
|
+
s = s.slice(0, zone);
|
|
62
|
+
// a trailing dotted IPv4 (::ffff:1.2.3.4)
|
|
63
|
+
const m = /^(.*:)(\d+)\.(\d+)\.(\d+)\.(\d+)$/.exec(s);
|
|
64
|
+
if (m) {
|
|
65
|
+
const [a = 0, b = 0, c = 0, d = 0] = m.slice(2).map(Number);
|
|
66
|
+
if ([a, b, c, d].some((x) => x > 255))
|
|
67
|
+
return null;
|
|
68
|
+
s = `${m[1]}${((a << 8) | b).toString(16)}:${((c << 8) | d).toString(16)}`;
|
|
69
|
+
}
|
|
70
|
+
const halves = s.split("::");
|
|
71
|
+
if (halves.length > 2)
|
|
72
|
+
return null;
|
|
73
|
+
const head = halves[0] ? halves[0].split(":") : [];
|
|
74
|
+
const tail = halves.length === 2 && halves[1] ? halves[1].split(":") : [];
|
|
75
|
+
const fill = halves.length === 2 ? 8 - head.length - tail.length : 0;
|
|
76
|
+
if (fill < 0)
|
|
77
|
+
return null;
|
|
78
|
+
const parts = [...head, ...Array(fill).fill("0"), ...tail];
|
|
79
|
+
if (!parts.every((g) => /^[0-9a-f]{1,4}$/.test(g)))
|
|
80
|
+
return null;
|
|
81
|
+
const groups = parts.map((g) => Number.parseInt(g, 16));
|
|
82
|
+
return groups.length === 8 ? groups : null;
|
|
83
|
+
}
|
|
84
|
+
const v4Of = (hi, lo) => `${hi >> 8}.${hi & 0xff}.${lo >> 8}.${lo & 0xff}`;
|
|
85
|
+
/**
|
|
86
|
+
* Whether `ip` falls in `table`, judging an IPv6 address that embeds an IPv4 address by the
|
|
87
|
+
* embedded one. Anything unparseable (including an IPv4 address with a `%` zone) counts as inside.
|
|
88
|
+
*/
|
|
89
|
+
function inTable(ip, table) {
|
|
90
|
+
if (isIP(ip) === 4)
|
|
91
|
+
return table.check(ip, "ipv4");
|
|
92
|
+
if (isIP(ip.split("%")[0] ?? "") !== 6)
|
|
93
|
+
return true;
|
|
94
|
+
const g = v6Groups(ip);
|
|
95
|
+
if (g === null)
|
|
96
|
+
return true;
|
|
97
|
+
const zeros = (from, to) => g.slice(from, to).every((x) => x === 0);
|
|
98
|
+
const [g0, g1, g2, , g4, g5, g6 = 0, g7 = 0] = g;
|
|
99
|
+
// v4-mapped ::ffff:a.b.c.d
|
|
100
|
+
if (zeros(0, 5) && g5 === 0xffff)
|
|
101
|
+
return inTable(v4Of(g6, g7), table);
|
|
102
|
+
// SIIT ::ffff:0:a.b.c.d (not unwrapped by proxy.mjs; see the file header)
|
|
103
|
+
if (zeros(0, 4) && g4 === 0xffff && g5 === 0)
|
|
104
|
+
return inTable(v4Of(g6, g7), table);
|
|
105
|
+
// v4-compatible ::a.b.c.d (:: and ::1 are left to the IPv6 table)
|
|
106
|
+
if (zeros(0, 6) && (g6 !== 0 || g7 > 1))
|
|
107
|
+
return inTable(v4Of(g6, g7), table);
|
|
108
|
+
// NAT64 64:ff9b::a.b.c.d
|
|
109
|
+
if (g0 === 0x64 && g1 === 0xff9b)
|
|
110
|
+
return inTable(v4Of(g6, g7), table);
|
|
111
|
+
// 6to4 2002:AABB:CCDD::
|
|
112
|
+
if (g0 === 0x2002)
|
|
113
|
+
return inTable(v4Of(g1 ?? 0, g2 ?? 0), table);
|
|
114
|
+
return table.check(g.map((x) => x.toString(16)).join(":"), "ipv6");
|
|
115
|
+
}
|
|
116
|
+
/** Whether connecting to this (resolved) IP is forbidden: private, loopback, reserved, or unparseable. */
|
|
117
|
+
export function isForbiddenAddress(ip) {
|
|
118
|
+
return inTable(ip, blocked);
|
|
119
|
+
}
|
|
120
|
+
/** Link-local (incl. cloud metadata 169.254.169.254) or unparseable: forbidden whatever the exemptions. */
|
|
121
|
+
export function isLinkLocalAddress(ip) {
|
|
122
|
+
return inTable(ip, linkLocal);
|
|
123
|
+
}
|
|
124
|
+
/** A hostname normalized for comparison: lower case, no IPv6 brackets, no trailing dots. */
|
|
125
|
+
export function normalizeHost(host) {
|
|
126
|
+
let h = host.trim().toLowerCase();
|
|
127
|
+
if (h.startsWith("[") && h.endsWith("]"))
|
|
128
|
+
h = h.slice(1, -1);
|
|
129
|
+
while (h.endsWith("."))
|
|
130
|
+
h = h.slice(0, -1);
|
|
131
|
+
return h;
|
|
132
|
+
}
|
|
133
|
+
/** `localhost`, `127.0.0.0/8` or `::1` (as written in a URL). */
|
|
134
|
+
export function isLoopbackHost(host) {
|
|
135
|
+
const h = normalizeHost(host);
|
|
136
|
+
return h === "localhost" || h === "::1" || (isIP(h) === 4 && h.startsWith("127."));
|
|
137
|
+
}
|
|
138
|
+
/** A download refused by the network rules. English message, safe to show. */
|
|
139
|
+
export class NetDeniedError extends Error {
|
|
140
|
+
code;
|
|
141
|
+
host;
|
|
142
|
+
constructor(code, host, message) {
|
|
143
|
+
super(message);
|
|
144
|
+
this.code = code;
|
|
145
|
+
this.host = host;
|
|
146
|
+
this.name = "NetDeniedError";
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const defaultLookup = (host) => dnsLookup(host, { all: true, verbatim: true });
|
|
150
|
+
/** The shortest prefix an `allowPrivateHosts` CIDR may have, per family. */
|
|
151
|
+
export const MIN_CIDR_PREFIX = { ipv4: 8, ipv6: 16 };
|
|
152
|
+
/** A syntactically valid `addr/prefix` CIDR as [address, prefix, family], else undefined. */
|
|
153
|
+
export function parseCidr(s) {
|
|
154
|
+
const m = /^([^/]+)\/(\d{1,3})$/.exec(s.trim());
|
|
155
|
+
if (!m)
|
|
156
|
+
return undefined;
|
|
157
|
+
const addr = m[1].toLowerCase();
|
|
158
|
+
const prefix = Number(m[2]);
|
|
159
|
+
const family = isIP(addr);
|
|
160
|
+
if (family === 4 && prefix <= 32)
|
|
161
|
+
return [addr, prefix, "ipv4"];
|
|
162
|
+
if (family === 6 && prefix <= 128)
|
|
163
|
+
return [addr, prefix, "ipv6"];
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
const HOSTNAME = /^(?=.{1,253}$)([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/;
|
|
167
|
+
/**
|
|
168
|
+
* Why a `marketplaces.allowPrivateHosts` entry is invalid (English), or null when it is a hostname,
|
|
169
|
+
* an IP address or a CIDR no broader than /8 (IPv4) or /16 (IPv6).
|
|
170
|
+
*/
|
|
171
|
+
export function privateHostEntryProblem(entry) {
|
|
172
|
+
const cidr = parseCidr(entry);
|
|
173
|
+
if (cidr) {
|
|
174
|
+
const [, prefix, family] = cidr;
|
|
175
|
+
const min = MIN_CIDR_PREFIX[family];
|
|
176
|
+
return prefix < min
|
|
177
|
+
? `the CIDR ${entry.trim()} is too broad: use a prefix of at least /${min} for ${family === "ipv4" ? "IPv4" : "IPv6"}`
|
|
178
|
+
: null;
|
|
179
|
+
}
|
|
180
|
+
const h = normalizeHost(entry);
|
|
181
|
+
if (isIP(h) !== 0 || HOSTNAME.test(h))
|
|
182
|
+
return null;
|
|
183
|
+
return "must be a hostname, an IP address or a CIDR such as 10.1.0.0/16";
|
|
184
|
+
}
|
|
185
|
+
/** A valid `marketplaces.allowPrivateHosts` entry (see {@link privateHostEntryProblem}). */
|
|
186
|
+
export function isPrivateHostEntry(entry) {
|
|
187
|
+
return privateHostEntryProblem(entry) === null;
|
|
188
|
+
}
|
|
189
|
+
function exemptions(host, policy) {
|
|
190
|
+
const cidrs = new BlockList();
|
|
191
|
+
let all = false;
|
|
192
|
+
const literal = isIP(host) !== 0;
|
|
193
|
+
// a loopback host under allowInsecureLoopback may resolve to loopback addresses only
|
|
194
|
+
if (policy.allowInsecureLoopback && isLoopbackHost(host)) {
|
|
195
|
+
cidrs.addSubnet("127.0.0.0", 8, "ipv4");
|
|
196
|
+
cidrs.addAddress("::1", "ipv6");
|
|
197
|
+
}
|
|
198
|
+
for (const entry of policy.allowPrivateHosts) {
|
|
199
|
+
if (!isPrivateHostEntry(entry))
|
|
200
|
+
continue;
|
|
201
|
+
const cidr = parseCidr(entry);
|
|
202
|
+
const name = normalizeHost(entry);
|
|
203
|
+
if (cidr) {
|
|
204
|
+
// IP and CIDR entries match IP-literal hosts only
|
|
205
|
+
if (literal)
|
|
206
|
+
cidrs.addSubnet(...cidr);
|
|
207
|
+
}
|
|
208
|
+
else if (isIP(name) !== 0) {
|
|
209
|
+
if (literal)
|
|
210
|
+
cidrs.addAddress(name, isIP(name) === 4 ? "ipv4" : "ipv6");
|
|
211
|
+
}
|
|
212
|
+
else if (name === host) {
|
|
213
|
+
all = true;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
for (const origin of policy.trustedOrigins) {
|
|
217
|
+
try {
|
|
218
|
+
if (normalizeHost(new URL(origin).hostname) === host)
|
|
219
|
+
all = true;
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// an unparseable origin trusts nothing
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return { all, cidrs };
|
|
226
|
+
}
|
|
227
|
+
function allowedBy(cidrs, ip) {
|
|
228
|
+
const family = isIP(ip);
|
|
229
|
+
if (family === 4)
|
|
230
|
+
return cidrs.check(ip, "ipv4");
|
|
231
|
+
if (family === 6) {
|
|
232
|
+
if (cidrs.check(ip, "ipv6"))
|
|
233
|
+
return true;
|
|
234
|
+
// a v4-mapped answer also matches an IPv4 CIDR
|
|
235
|
+
const g = v6Groups(ip);
|
|
236
|
+
if (g?.slice(0, 5).every((x) => x === 0) && g[5] === 0xffff) {
|
|
237
|
+
return cidrs.check(v4Of(g[6] ?? 0, g[7] ?? 0), "ipv4");
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Resolves `host` (an IP literal is taken as is) and returns every address, in resolution order,
|
|
244
|
+
* after checking that each is public or exempt by `policy`. Throws {@link NetDeniedError}
|
|
245
|
+
* `net.dns_failed` (no answer) or `net.private_address` (some address is private and not exempt).
|
|
246
|
+
*/
|
|
247
|
+
export async function resolvePublic(host, policy, lookup = defaultLookup) {
|
|
248
|
+
const h = normalizeHost(host);
|
|
249
|
+
let addrs;
|
|
250
|
+
if (isIP(h) !== 0) {
|
|
251
|
+
addrs = [h];
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
try {
|
|
255
|
+
addrs = (await lookup(h)).map((a) => a.address);
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
throw new NetDeniedError("net.dns_failed", h, `Cannot resolve ${h}.`);
|
|
259
|
+
}
|
|
260
|
+
if (addrs.length === 0)
|
|
261
|
+
throw new NetDeniedError("net.dns_failed", h, `Cannot resolve ${h}.`);
|
|
262
|
+
}
|
|
263
|
+
const ex = exemptions(h, policy);
|
|
264
|
+
const refused = (a) => isForbiddenAddress(a) && (isLinkLocalAddress(a) || (!ex.all && !allowedBy(ex.cidrs, a)));
|
|
265
|
+
if (addrs.some(refused)) {
|
|
266
|
+
throw new NetDeniedError("net.private_address", h, addrs.some(isLinkLocalAddress)
|
|
267
|
+
? `Access to ${h} is refused: it resolves to a link-local address, which is never allowed.`
|
|
268
|
+
: `Access to ${h} is refused: it resolves to a private or reserved address (add the hostname to marketplaces.allowPrivateHosts to allow an internal host).`);
|
|
269
|
+
}
|
|
270
|
+
return addrs;
|
|
271
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Agent } from "undici";
|
|
2
|
+
import { type LookupFn, type NetPolicy } from "./public-address.js";
|
|
3
|
+
/**
|
|
4
|
+
* `fetch` for URLs that come from a marketplace or an admin (spec §5.1):
|
|
5
|
+
* - https only; plain http only for a loopback host under `allowInsecureLoopback` (testing);
|
|
6
|
+
* - the host is resolved once by {@link resolvePublic} and the connection goes to the checked
|
|
7
|
+
* addresses: a per-request undici `Agent` whose `connect.lookup` answers only those addresses,
|
|
8
|
+
* so there is no second DNS lookup (no rebinding), while the Host header and TLS SNI keep the
|
|
9
|
+
* original hostname;
|
|
10
|
+
* - with `policy.proxy` (`net.httpsProxy`), an https URL whose host is not loopback and not in
|
|
11
|
+
* `noProxy` tunnels through the proxy (`proxy.ts`) instead: the host is not resolved locally
|
|
12
|
+
* (the proxy resolves it and owns SSRF protection for the resolved address), but an IP-literal
|
|
13
|
+
* host is still checked against the private ranges and `allowPrivateHosts`;
|
|
14
|
+
* - redirects are never followed: a 3xx (other than 304) is {@link NetDeniedError} `net.redirect`;
|
|
15
|
+
* - with `maxBytes`, a declared or streamed body over the limit is {@link NetBodyTooLargeError}.
|
|
16
|
+
*/
|
|
17
|
+
/** The response body is larger than `maxBytes`. */
|
|
18
|
+
export declare class NetBodyTooLargeError extends Error {
|
|
19
|
+
readonly maxBytes: number;
|
|
20
|
+
constructor(maxBytes: number);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* An undici dispatcher whose connections go only to `ips` (already checked), whatever the URL's
|
|
24
|
+
* hostname; the hostname still names the Host header and TLS SNI. One agent per download.
|
|
25
|
+
*/
|
|
26
|
+
export declare function pinnedAgent(ips: string[], tls?: {
|
|
27
|
+
ca?: string | string[];
|
|
28
|
+
}): Agent;
|
|
29
|
+
export declare function safeFetch(url: string, init: RequestInit & {
|
|
30
|
+
signal: AbortSignal;
|
|
31
|
+
}, policy: NetPolicy, opts?: {
|
|
32
|
+
lookup?: LookupFn;
|
|
33
|
+
maxBytes?: number;
|
|
34
|
+
/** Tests only: wraps the pinned dispatcher (defaults to {@link pinnedAgent}). */
|
|
35
|
+
dispatcherFor?: (ips: string[]) => Agent;
|
|
36
|
+
/** Tests only: builds the proxy dispatcher (defaults to {@link proxyAgent}). */
|
|
37
|
+
proxyDispatcherFor?: (proxyUrl: string) => Agent;
|
|
38
|
+
}): Promise<Response>;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { isIP } from "node:net";
|
|
2
|
+
import { Agent, fetch as undiciFetch } from "undici";
|
|
3
|
+
import { bypassesProxy, proxyAgent } from "./proxy.js";
|
|
4
|
+
import { isLoopbackHost, NetDeniedError, normalizeHost, resolvePublic, } from "./public-address.js";
|
|
5
|
+
/**
|
|
6
|
+
* `fetch` for URLs that come from a marketplace or an admin (spec §5.1):
|
|
7
|
+
* - https only; plain http only for a loopback host under `allowInsecureLoopback` (testing);
|
|
8
|
+
* - the host is resolved once by {@link resolvePublic} and the connection goes to the checked
|
|
9
|
+
* addresses: a per-request undici `Agent` whose `connect.lookup` answers only those addresses,
|
|
10
|
+
* so there is no second DNS lookup (no rebinding), while the Host header and TLS SNI keep the
|
|
11
|
+
* original hostname;
|
|
12
|
+
* - with `policy.proxy` (`net.httpsProxy`), an https URL whose host is not loopback and not in
|
|
13
|
+
* `noProxy` tunnels through the proxy (`proxy.ts`) instead: the host is not resolved locally
|
|
14
|
+
* (the proxy resolves it and owns SSRF protection for the resolved address), but an IP-literal
|
|
15
|
+
* host is still checked against the private ranges and `allowPrivateHosts`;
|
|
16
|
+
* - redirects are never followed: a 3xx (other than 304) is {@link NetDeniedError} `net.redirect`;
|
|
17
|
+
* - with `maxBytes`, a declared or streamed body over the limit is {@link NetBodyTooLargeError}.
|
|
18
|
+
*/
|
|
19
|
+
/** The response body is larger than `maxBytes`. */
|
|
20
|
+
export class NetBodyTooLargeError extends Error {
|
|
21
|
+
maxBytes;
|
|
22
|
+
constructor(maxBytes) {
|
|
23
|
+
super(`the response is larger than ${maxBytes} bytes`);
|
|
24
|
+
this.maxBytes = maxBytes;
|
|
25
|
+
this.name = "NetBodyTooLargeError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** A `net.connect` lookup that answers only the checked addresses, whatever the hostname. */
|
|
29
|
+
function pinnedLookup(ips) {
|
|
30
|
+
const all = ips.map((address) => ({
|
|
31
|
+
address,
|
|
32
|
+
family: isIP(address) === 6 ? 6 : 4,
|
|
33
|
+
}));
|
|
34
|
+
return (_hostname, options, callback) => {
|
|
35
|
+
if (options.all)
|
|
36
|
+
callback(null, all);
|
|
37
|
+
else
|
|
38
|
+
callback(null, all[0]?.address ?? "", all[0]?.family ?? 4);
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* An undici dispatcher whose connections go only to `ips` (already checked), whatever the URL's
|
|
43
|
+
* hostname; the hostname still names the Host header and TLS SNI. One agent per download.
|
|
44
|
+
*/
|
|
45
|
+
export function pinnedAgent(ips, tls = {}) {
|
|
46
|
+
return new Agent({
|
|
47
|
+
connect: { lookup: pinnedLookup(ips), ...(tls.ca !== undefined && { ca: tls.ca }) },
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
const NULL_BODY_STATUS = new Set([101, 103, 204, 205, 304]);
|
|
51
|
+
function capBody(body, maxBytes) {
|
|
52
|
+
let seen = 0;
|
|
53
|
+
return body.pipeThrough(new TransformStream({
|
|
54
|
+
transform(chunk, controller) {
|
|
55
|
+
seen += chunk.byteLength;
|
|
56
|
+
if (seen > maxBytes)
|
|
57
|
+
controller.error(new NetBodyTooLargeError(maxBytes));
|
|
58
|
+
else
|
|
59
|
+
controller.enqueue(chunk);
|
|
60
|
+
},
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
export async function safeFetch(url, init, policy, opts = {}) {
|
|
64
|
+
let target;
|
|
65
|
+
try {
|
|
66
|
+
target = new URL(url);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
throw new NetDeniedError("net.insecure_url", "", "The URL is not valid.");
|
|
70
|
+
}
|
|
71
|
+
const host = normalizeHost(target.hostname);
|
|
72
|
+
const insecureOk = policy.allowInsecureLoopback && target.protocol === "http:" && isLoopbackHost(host);
|
|
73
|
+
if (target.protocol !== "https:" && !insecureOk) {
|
|
74
|
+
throw new NetDeniedError("net.insecure_url", host, `The URL must use https (${host}).`);
|
|
75
|
+
}
|
|
76
|
+
const proxy = policy.proxy;
|
|
77
|
+
const proxied = proxy !== undefined &&
|
|
78
|
+
target.protocol === "https:" &&
|
|
79
|
+
!isLoopbackHost(host) &&
|
|
80
|
+
!bypassesProxy(host, proxy.noProxy);
|
|
81
|
+
// getaddrinfo cannot be cancelled: stop waiting for it when the caller gives up
|
|
82
|
+
init.signal.throwIfAborted();
|
|
83
|
+
let onAbort = () => { };
|
|
84
|
+
const aborted = new Promise((_, reject) => {
|
|
85
|
+
onAbort = () => reject(init.signal.reason);
|
|
86
|
+
init.signal.addEventListener("abort", onAbort, { once: true });
|
|
87
|
+
});
|
|
88
|
+
let ips = [];
|
|
89
|
+
try {
|
|
90
|
+
// behind the proxy only an IP literal is checked here (no lookup); the proxy resolves names
|
|
91
|
+
if (!proxied || isIP(host) !== 0) {
|
|
92
|
+
ips = await Promise.race([resolvePublic(host, policy, opts.lookup), aborted]);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
init.signal.removeEventListener("abort", onAbort);
|
|
97
|
+
}
|
|
98
|
+
const agent = proxied && proxy !== undefined
|
|
99
|
+
? (opts.proxyDispatcherFor ?? proxyAgent)(proxy.url)
|
|
100
|
+
: (opts.dispatcherFor ?? pinnedAgent)(ips);
|
|
101
|
+
let res;
|
|
102
|
+
try {
|
|
103
|
+
res = await undiciFetch(target, {
|
|
104
|
+
...init,
|
|
105
|
+
redirect: "manual",
|
|
106
|
+
dispatcher: agent,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
await agent.destroy().catch(() => { });
|
|
111
|
+
// a refusal raised while connecting (the proxy's answer to CONNECT) surfaces as itself
|
|
112
|
+
const cause = e?.cause;
|
|
113
|
+
throw cause instanceof NetDeniedError ? cause : e;
|
|
114
|
+
}
|
|
115
|
+
// close() waits for the in-flight request (the body still being read) before it closes
|
|
116
|
+
void agent.close().catch(() => { });
|
|
117
|
+
if (res.status >= 300 && res.status < 400 && res.status !== 304) {
|
|
118
|
+
await res.body?.cancel().catch(() => { });
|
|
119
|
+
throw new NetDeniedError("net.redirect", host, `${host} responded with a redirect (${res.status}); redirects are not followed.`);
|
|
120
|
+
}
|
|
121
|
+
const max = opts.maxBytes;
|
|
122
|
+
if (max !== undefined) {
|
|
123
|
+
const declared = Number(res.headers.get("content-length"));
|
|
124
|
+
if (Number.isFinite(declared) && declared > max) {
|
|
125
|
+
await res.body?.cancel().catch(() => { });
|
|
126
|
+
throw new NetBodyTooLargeError(max);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const raw = res.body;
|
|
130
|
+
const body = raw === null || NULL_BODY_STATUS.has(res.status)
|
|
131
|
+
? null
|
|
132
|
+
: max === undefined
|
|
133
|
+
? raw
|
|
134
|
+
: capBody(raw, max);
|
|
135
|
+
return new Response(body, {
|
|
136
|
+
status: res.status,
|
|
137
|
+
statusText: res.statusText,
|
|
138
|
+
headers: new Headers([...res.headers]),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Principal } from "../identity/principal.js";
|
|
2
|
+
import type { DirectorySync } from "./directory-sync.js";
|
|
3
|
+
import { type OrgContext } from "./org.guard.js";
|
|
4
|
+
/**
|
|
5
|
+
* Manual directory sync (`/api/v1/orgs/:orgId/extensions/:extension/directory/sync`): it adds and
|
|
6
|
+
* removes members, so only an admin and only a human may start it.
|
|
7
|
+
*/
|
|
8
|
+
export declare class DirectorySyncController {
|
|
9
|
+
private readonly directory;
|
|
10
|
+
constructor(directory: DirectorySync);
|
|
11
|
+
sync(principal: Principal, org: OrgContext, extension: string): Promise<{
|
|
12
|
+
jobId: string;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
8
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
9
|
+
};
|
|
10
|
+
import { Controller, HttpCode, Inject, Param, Post } from "@nestjs/common";
|
|
11
|
+
import { DIRECTORY_SYNC } from "../http/tokens.js";
|
|
12
|
+
import { CurrentPrincipal, patTokenId, requireUser, } from "../identity/principal.js";
|
|
13
|
+
import { CurrentOrg, OrgAction } from "./org.guard.js";
|
|
14
|
+
/**
|
|
15
|
+
* Manual directory sync (`/api/v1/orgs/:orgId/extensions/:extension/directory/sync`): it adds and
|
|
16
|
+
* removes members, so only an admin and only a human may start it.
|
|
17
|
+
*/
|
|
18
|
+
let DirectorySyncController = class DirectorySyncController {
|
|
19
|
+
directory;
|
|
20
|
+
constructor(directory) {
|
|
21
|
+
this.directory = directory;
|
|
22
|
+
}
|
|
23
|
+
async sync(principal, org, extension) {
|
|
24
|
+
const p = requireUser(principal);
|
|
25
|
+
return this.directory.request(org.orgId, extension, p.account.id, patTokenId(p));
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
__decorate([
|
|
29
|
+
Post("sync"),
|
|
30
|
+
HttpCode(202),
|
|
31
|
+
OrgAction("org.members.manage"),
|
|
32
|
+
__param(0, CurrentPrincipal()),
|
|
33
|
+
__param(1, CurrentOrg()),
|
|
34
|
+
__param(2, Param("extension"))
|
|
35
|
+
], DirectorySyncController.prototype, "sync", null);
|
|
36
|
+
DirectorySyncController = __decorate([
|
|
37
|
+
Controller("api/v1/orgs/:orgId/extensions/:extension/directory"),
|
|
38
|
+
__param(0, Inject(DIRECTORY_SYNC))
|
|
39
|
+
], DirectorySyncController);
|
|
40
|
+
export { DirectorySyncController };
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { type Logger } from "@capybara-hq/plugin-sdk";
|
|
2
|
+
import type { Redis } from "ioredis";
|
|
3
|
+
import type { KernelDb } from "../db/kernel-schema.js";
|
|
4
|
+
import type { ExtensionHost } from "../extensions/host.js";
|
|
5
|
+
import type { OrgExtensionConfigs } from "../extensions/org-config.js";
|
|
6
|
+
import type { ExternalIdentities } from "../identity/external.js";
|
|
7
|
+
import type { Maintenance } from "../runtime/maintenance.js";
|
|
8
|
+
import type { Offboarding } from "./offboarding.js";
|
|
9
|
+
/** 每小时扫描:维护队列任务名、job scheduler id(不含 `:`)与周期。 */
|
|
10
|
+
export declare const DIRECTORY_SCAN_JOB = "directory-scan";
|
|
11
|
+
export declare const DIRECTORY_SCAN_SCHEDULER_ID = "directory-scan-hourly";
|
|
12
|
+
export declare const DIRECTORY_SCAN_EVERY_MS = 3600000;
|
|
13
|
+
/** 一次 (组织, 插件) 同步的维护任务名。 */
|
|
14
|
+
export declare const DIRECTORY_SYNC_JOB = "directory-sync";
|
|
15
|
+
/** 同步锁的 Redis 键前缀(键为 `<前缀><orgId>:<plugin>`,带 TTL、持有期间续租)。 */
|
|
16
|
+
export declare const DIRECTORY_SYNC_LOCK_PREFIX = "capk:directory-sync:";
|
|
17
|
+
/** 脏标记的保留时长(秒):锁被占时记下,持有者跑完后取走并补一轮。 */
|
|
18
|
+
export declare const DIRECTORY_SYNC_DIRTY_TTL_SECONDS = 3600;
|
|
19
|
+
/** 脏标记(Redis 键):同步进行中又有同步请求被锁挡掉。 */
|
|
20
|
+
export declare const directorySyncDirtyKey: (orgId: string, plugin: string) => string;
|
|
21
|
+
/**
|
|
22
|
+
* Ruling A17 大批移除保护(非手动触发):计划移除数超过 `max(下限, 比例 × 现有通讯录成员数)`,
|
|
23
|
+
* 或列表里一个在职成员都没有而已有通讯录成员时,本轮当作不完整、不移除任何人。
|
|
24
|
+
*/
|
|
25
|
+
export declare const DIRECTORY_REMOVAL_GUARD_MIN = 5;
|
|
26
|
+
export declare const DIRECTORY_REMOVAL_GUARD_RATIO = 0.2;
|
|
27
|
+
/** 单页 `listMembers` 的时限。 */
|
|
28
|
+
export declare const DIRECTORY_PAGE_TIMEOUT_MS = 60000;
|
|
29
|
+
/** 一轮同步最多翻的页数:防止插件反复返回游标让任务无限长(超出按中途失败处理)。 */
|
|
30
|
+
export declare const DIRECTORY_MAX_PAGES = 10000;
|
|
31
|
+
/** 触发来源:每小时扫描、入站 `directory_changed` 提示、管理员手动。 */
|
|
32
|
+
export type DirectorySyncTrigger = "schedule" | "changed" | "manual";
|
|
33
|
+
/**
|
|
34
|
+
* 不同步的原因(陈旧任务、插件已停用 / 配置不再有效、组织已删除、另一同步在跑;`not_configured`:插件的
|
|
35
|
+
* `Directory.configured` 说这份组织配置没配好通讯录,Ruling A28,静默跳过)。
|
|
36
|
+
*/
|
|
37
|
+
export type DirectorySkipReason = "plugin_unavailable" | "org_not_found" | "not_enabled" | "config_invalid" | "not_configured" | "locked";
|
|
38
|
+
export type DirectorySyncResult = {
|
|
39
|
+
status: "skipped";
|
|
40
|
+
reason: DirectorySkipReason;
|
|
41
|
+
} | {
|
|
42
|
+
status: "synced";
|
|
43
|
+
/** 新加入的成员关系数。 */
|
|
44
|
+
added: number;
|
|
45
|
+
/** 离职处理(撤销成员关系)的人数。 */
|
|
46
|
+
removed: number;
|
|
47
|
+
/** 是该组织最后一个 owner、因而跳过的人数。 */
|
|
48
|
+
skippedLastOwner: number;
|
|
49
|
+
/** 处理失败的成员数(资料不合规、加入或离职事务失败)。 */
|
|
50
|
+
failed: number;
|
|
51
|
+
/** 通讯录返回的成员总数(含离职的)。 */
|
|
52
|
+
total: number;
|
|
53
|
+
/** 分页没有完整走完、有成员处理失败或被大批移除保护拦下:只加人,不移除任何人。 */
|
|
54
|
+
partial: boolean;
|
|
55
|
+
/** 被大批移除保护(Ruling A17)拦下。 */
|
|
56
|
+
removalBlocked: boolean;
|
|
57
|
+
/** 本轮持锁期间来过通讯录变更(Ruling A32):不移除,由随后的补跑移除。 */
|
|
58
|
+
partialReason?: "changed_during_sync";
|
|
59
|
+
};
|
|
60
|
+
/** 一个 (组织, 插件) 投递目标。 */
|
|
61
|
+
export interface DirectorySyncTarget {
|
|
62
|
+
orgId: string;
|
|
63
|
+
plugin: string;
|
|
64
|
+
jobId: string;
|
|
65
|
+
}
|
|
66
|
+
/** UTC 小时桶,如 `2026102014`(不含 `:`)。 */
|
|
67
|
+
export declare function hourBucket(d?: Date): string;
|
|
68
|
+
/** UTC 分钟桶,如 `202610201405`。 */
|
|
69
|
+
export declare function minuteBucket(d?: Date): string;
|
|
70
|
+
/**
|
|
71
|
+
* 同步任务的 job id(不含 `:`):扫描按小时桶(`directory-sync-<org>-<plugin>-<小时桶>`)、
|
|
72
|
+
* `directory_changed` 按分钟桶去重;手动触发每次唯一,不会被同一小时的扫描任务吞掉。
|
|
73
|
+
*/
|
|
74
|
+
export declare function directorySyncJobId(orgId: string, plugin: string, trigger: DirectorySyncTrigger, now?: Date): string;
|
|
75
|
+
export interface DirectorySyncDeps {
|
|
76
|
+
db: KernelDb;
|
|
77
|
+
redis: Redis;
|
|
78
|
+
host: Pick<ExtensionHost, "list">;
|
|
79
|
+
orgExtensionConfigs: Pick<OrgExtensionConfigs, "get">;
|
|
80
|
+
external: Pick<ExternalIdentities, "resolve">;
|
|
81
|
+
offboarding: Pick<Offboarding, "offboard" | "afterCommit">;
|
|
82
|
+
/** 给了就登记每小时扫描与同步任务(worker 角色执行);不给时只能直接调用 `sync`。 */
|
|
83
|
+
maintenance?: Pick<Maintenance, "add" | "handle" | "schedule">;
|
|
84
|
+
log: Pick<Logger, "warn" | "info">;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 通讯录同步(`directory@1`):把企业通讯录同步为组织成员。框架无关。
|
|
88
|
+
*
|
|
89
|
+
* - 维护队列每小时 `directory-scan`:给每个「插件提供 `directory@1`、在该组织启用、组织级配置满足 schema」的
|
|
90
|
+
* (组织, 插件) 投递一次同步(job id 按小时桶去重,多副本重复扫描只跑一次);手动与 `directory_changed` 也投递同步。
|
|
91
|
+
* - 同一 (组织, 插件) 同时只跑一个同步(Redis 锁 `capk:directory-sync:<org>:<plugin>`,持有期间续租);
|
|
92
|
+
* 锁被占时本任务记下脏标记 `capk:directory-sync-dirty:<org>:<plugin>`(1 小时)后结束;持有者释放锁后 GETDEL
|
|
93
|
+
* 取走标记,有就投递一次 changed 同步(在跑的那一轮可能已经读过变化之前的页)。Ruling A32:移除阶段前 GET
|
|
94
|
+
* (不取走)脏标记,有就本轮按不完整处理、不移除任何人(审计 `partialReason = changed_during_sync`),移除留给补跑。
|
|
95
|
+
* After writing the flag, a blocked sync tries the lock once more: when the holder finished in
|
|
96
|
+
* between (and its GETDEL found no flag), this sync clears the flag and runs itself.
|
|
97
|
+
* - Ruling A17:非手动触发的一轮,计划移除数超过 `max(5, 20% × 现有通讯录成员数)`,或列表里没有在职成员而已有
|
|
98
|
+
* 通讯录成员时,当作不完整、不移除任何人,审计 `org.directory.removal_blocked`;手动触发不受此限。
|
|
99
|
+
* - 分页 `listMembers`:在职成员按 `<插件>:<租户>` + externalId 解析外部身份(不合并既有账号、不登录、不建会话),
|
|
100
|
+
* 不是成员就加入(角色 member,`memberships.source = "directory:<插件>"`),每人一个短事务;新身份行记
|
|
101
|
+
* `linked_via = directory`。被管理员手动移出(或本人离开)而记下通讯录排除的人不加回(Ruling A20),
|
|
102
|
+
* 直到有人手动重新加入。
|
|
103
|
+
* - **整轮分页成功走完、且没有成员处理失败**、并且移除前再次复核插件仍可同步(仍启用、配置仍有效;中途被停用或
|
|
104
|
+
* 配置变得无效则本轮按不完整处理)之后,才对「离职(active = false)或不再出现、且 source 为该通讯录」的
|
|
105
|
+
* 成员逐个走离职处理(每人一个事务:组织行锁 → 成员关系行锁 → `Offboarding.offboard`);中途失败只加人不删人。
|
|
106
|
+
* 手动 / 邀请加入的成员(source 为空)永远不被同步移除;账号本身不停用。最后一个 owner 跳过并计数。
|
|
107
|
+
* - Ruling A28:插件实现了 `Directory.configured` 且对该组织配置返回 false(通讯录未配置,组织只用插件的别的
|
|
108
|
+
* 能力)时,扫描、`directory_changed` 与执行时的复核都静默跳过(不投递、不审计、不记警告),手动触发 409
|
|
109
|
+
* `directory.not_configured`。
|
|
110
|
+
* - 审计 `org.directory.synced`(计数、是否不完整)。任务执行时再核对插件与配置:已停用的插件的陈旧任务什么都不做。
|
|
111
|
+
*/
|
|
112
|
+
export declare class DirectorySync {
|
|
113
|
+
private readonly d;
|
|
114
|
+
private readonly lock;
|
|
115
|
+
constructor(d: DirectorySyncDeps);
|
|
116
|
+
/** 插件提供的 `directory@1`;未加载或不提供时 undefined。 */
|
|
117
|
+
private provider;
|
|
118
|
+
/** The org exists, the extension provides a directory, is enabled in the org and its org config satisfies the schema (reuses the `OrgExtensionConfigs.get` validation). */
|
|
119
|
+
private eligible;
|
|
120
|
+
/**
|
|
121
|
+
* 投递一次 `directory_changed` 同步(按分钟桶去重);返回 job id。入站 `directory_changed` 经
|
|
122
|
+
* {@link DirectorySyncScheduler} 调用这里。通讯录未配置(`Directory.configured` 返回 false,Ruling A28)时
|
|
123
|
+
* 静默不投递,返回 undefined;其他不可同步的情况照常投递,由任务执行时跳过。
|
|
124
|
+
*/
|
|
125
|
+
enqueue(orgId: string, plugin: string, now?: Date): Promise<string | undefined>;
|
|
126
|
+
private addJob;
|
|
127
|
+
/**
|
|
128
|
+
* Manual trigger by an admin: re-checks `org.members.manage` live under the org row lock, then that
|
|
129
|
+
* the extension can sync (does not provide a directory -> `extension.not_found`, not enabled ->
|
|
130
|
+
* `extension.not_enabled`, config no longer valid -> `extension.config_invalid`, the extension says
|
|
131
|
+
* the directory is not configured -> `directory.not_configured`, Ruling A28), and enqueues a sync
|
|
132
|
+
* job with a unique id.
|
|
133
|
+
*/
|
|
134
|
+
request(orgId: string, plugin: string, actorAccountId: string, actorTokenId?: string): Promise<{
|
|
135
|
+
jobId: string;
|
|
136
|
+
}>;
|
|
137
|
+
/**
|
|
138
|
+
* 每小时扫描:给每个可同步的 (组织, 插件) 投递一次同步(job id 按小时桶),返回投递的目标。
|
|
139
|
+
* 已存配置不再满足 schema 的跳过(记 warn);单个投递失败记 warn、继续其他目标。
|
|
140
|
+
*/
|
|
141
|
+
scan(now?: Date): Promise<DirectorySyncTarget[]>;
|
|
142
|
+
/** 执行一轮同步(维护任务的处理函数;测试可直接调用)。 */
|
|
143
|
+
sync(orgId: string, plugin: string, opts?: {
|
|
144
|
+
trigger?: DirectorySyncTrigger;
|
|
145
|
+
requestedBy?: string | undefined;
|
|
146
|
+
requestedByToken?: string | undefined;
|
|
147
|
+
}): Promise<DirectorySyncResult>;
|
|
148
|
+
/**
|
|
149
|
+
* 释放锁之后取走脏标记(GETDEL,只有一个副本取得到);有标记就投递一次 changed 同步。
|
|
150
|
+
* job id 每次唯一(`…-rerun-<uuid>`):不能被刚刚被挡掉的同一分钟的 changed 任务 id 吞掉。失败只记 warn。
|
|
151
|
+
*/
|
|
152
|
+
private rerunIfDirty;
|
|
153
|
+
private run;
|
|
154
|
+
/**
|
|
155
|
+
* 一个在职成员:解析外部身份(不合并、不登录,新身份行 `linked_via = directory`),不是成员就以 member 加入
|
|
156
|
+
* (source = 本通讯录);此人在该组织有本通讯录的排除(被手动移出 / 本人离开过)时不加入。
|
|
157
|
+
* 组织行锁内完成(与移出成员同一把锁、与移出时写排除串行),组织已删除 → {@link OrgGone}。
|
|
158
|
+
*/
|
|
159
|
+
private addMember;
|
|
160
|
+
/**
|
|
161
|
+
* 一个离职者:独立事务内先锁组织行、再锁其成员关系行(与 `switchCurrentOrg` / 移出成员同一顺序),
|
|
162
|
+
* 确认仍是本通讯录加入的,再走 `Offboarding.offboard`(吊销会话与令牌、删个人凭据、转交 agent 都在这个事务里)。
|
|
163
|
+
* 最后一个 owner → 回滚并返回 `last_owner`;期间已被移除或不再是本通讯录加入的 → `gone`。
|
|
164
|
+
*/
|
|
165
|
+
private offboardOne;
|
|
166
|
+
}
|