@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,1236 @@
|
|
|
1
|
+
import { ToolError, validateInputForm, validateToolDisplay, } from "@capybara-hq/plugin-sdk";
|
|
2
|
+
import { normalizeStoredDefinition } from "../agents/definition.js";
|
|
3
|
+
import { ToolViewSchema } from "../conversations/events.js";
|
|
4
|
+
import { clipText } from "../extensions/hooks.js";
|
|
5
|
+
import { ExtensionUnloadedError, PLUGIN_UNLOADED_TOOL_ERROR } from "../extensions/inflight.js";
|
|
6
|
+
import { audit } from "../identity/audit.js";
|
|
7
|
+
import { UNKNOWN_RESULT_TEXT } from "../runtime/context/pairing.js";
|
|
8
|
+
import { fireObserve, hookDeniedAudit, hookDeniedEvent, hookScope, } from "../runtime/hook-calls.js";
|
|
9
|
+
import { subagentsWaitId, subagentWaitingIdemKey, } from "../runtime/loop.js";
|
|
10
|
+
import { assistantIdemKey, MAX_MODEL_ATTEMPTS, modelCallIdemKey } from "../runtime/model-call.js";
|
|
11
|
+
import { TERMINAL_STATUSES } from "../runtime/run-finish.js";
|
|
12
|
+
import { childResult, dispatchOfCall, finishedEvent, } from "../runtime/subagents/child.js";
|
|
13
|
+
import { forkSeqOfCall, pairStoppedForks } from "../runtime/subagents/skill-fork.js";
|
|
14
|
+
import { skillLoadOfferedIn } from "../skills/catalog.js";
|
|
15
|
+
import { breakerOpenMessage } from "./breaker.js";
|
|
16
|
+
import { AGENT_STOPPED_WITH_RUN_TEXT, AGENT_TOOL, AgentToolInput, } from "./builtins/agent.js";
|
|
17
|
+
import { isAlwaysLoaded, notLoadedText } from "./builtins/tool-search.js";
|
|
18
|
+
import { CredentialMissing } from "./credentials.js";
|
|
19
|
+
import { ElicitationSession } from "./elicitation.js";
|
|
20
|
+
import { renderResult, resolveOutputRefs, spillIfLarge } from "./outputs.js";
|
|
21
|
+
import { createDecidePolicy } from "./policy.js";
|
|
22
|
+
import { redactDeep, redactorFor } from "./redact.js";
|
|
23
|
+
import { runResourceTool } from "./resources.js";
|
|
24
|
+
import { validateInput } from "./validate.js";
|
|
25
|
+
// ---- 模型可见文案(英文) ----
|
|
26
|
+
/** 审批被拒绝。 */
|
|
27
|
+
export const APPROVAL_DENIED_TEXT = "The user denied this tool call.";
|
|
28
|
+
/**
|
|
29
|
+
* The denied call's result when the approver gave a reason (model-visible, in the log). "An
|
|
30
|
+
* approver": under role escalation an admin, not the requester, decides. Older logs keep the text
|
|
31
|
+
* they recorded ("The user denied this tool call. Reason: …"); only new denials use this one.
|
|
32
|
+
*/
|
|
33
|
+
export const approvalDeniedText = (reason) => reason === null ? APPROVAL_DENIED_TEXT : `An approver denied this tool call. Reason: ${reason}`;
|
|
34
|
+
/** 审批超时(按拒绝)。 */
|
|
35
|
+
export const approvalExpiredText = (hours) => `Approval timed out after ${hours}h; treated as denied.`;
|
|
36
|
+
/** 等待审批期间 run 被中止。 */
|
|
37
|
+
export const APPROVAL_CANCELLED_TEXT = "The run was stopped while waiting for approval.";
|
|
38
|
+
/** 等待用户输入期间 run 被中止。 */
|
|
39
|
+
export const INPUT_CANCELLED_TEXT = "The run was stopped while waiting for user input.";
|
|
40
|
+
/** 同一调用连续请求输入超过上限。 */
|
|
41
|
+
export const TOO_MANY_INPUT_REQUESTS_TEXT = "Tool requested user input too many times.";
|
|
42
|
+
/** run 已被中止 / 到达截止时间,调用尚未开始执行。 */
|
|
43
|
+
export const RUN_STOPPED_TEXT = "The run was stopped before this tool call was executed.";
|
|
44
|
+
/** 调用执行中 run 被中止 / 到达截止时间。 */
|
|
45
|
+
export const RUN_STOPPED_DURING_TEXT = "The run was stopped while this tool call was running; its outcome is unknown. Verify before retrying.";
|
|
46
|
+
const timeoutText = (key, ms) => `Tool ${key} timed out after ${ms / 1000}s. The operation may or may not have completed; verify before retrying.`;
|
|
47
|
+
const failedText = (key, errName) => `Tool ${key} failed (${errName}). The operation may not have completed; check before retrying or try another approach.`;
|
|
48
|
+
const unknownToolText = (name, keys, toolSearch) => `Unknown tool "${name}". Available tools: ${keys.length === 0 ? "(none)" : keys.join(", ")}.` +
|
|
49
|
+
(toolSearch ? " Use tool_search to load other tools." : "");
|
|
50
|
+
const unavailableText = (name, reason) => `Tool "${name}" is unavailable: ${reason}.`;
|
|
51
|
+
const deniedText = (key) => `Tool ${key} is denied by policy.`;
|
|
52
|
+
/** Longest hook reason quoted in {@link hookDeniedText}. */
|
|
53
|
+
const HOOK_REASON_MAX = 500;
|
|
54
|
+
/**
|
|
55
|
+
* A plugin policy hook denied the call (model-visible). `reason` is the sanitized deny reason, or the
|
|
56
|
+
* generic sentence for a hook error / timeout; truncated to 500 characters.
|
|
57
|
+
*/
|
|
58
|
+
export const hookDeniedText = (plugin, reason) => `Tool call blocked by the "${plugin}" policy hook: ${clipText(reason, HOOK_REASON_MAX)}`;
|
|
59
|
+
const invalidFormText = (key, problems) => `Tool ${key} returned an invalid input form: ${problems.join("; ")}.`;
|
|
60
|
+
const stateTooLargeText = (key) => `Tool ${key} returned input state larger than ${MAX_INPUT_STATE_BYTES / 1024} KB.`;
|
|
61
|
+
const personalMissingText = (connectorName) => `The user has not connected ${connectorName}. A connect card was shown; ask the user to connect, then retry.`;
|
|
62
|
+
const orgMissingText = (connectorName) => `${connectorName} has no organization credential configured; ask an organization admin to set it up.`;
|
|
63
|
+
/** `runCall` dispatched a sub-agent: the call stays open until the child run finishes. */
|
|
64
|
+
const OPEN = "open";
|
|
65
|
+
/**
|
|
66
|
+
* The wait of a step whose sub-agents still run (spec §7.2): id `sa-<runId>-<step>`, expiring after
|
|
67
|
+
* the approval timeout plus one run's duration plus 10 minutes (a child may itself wait for an
|
|
68
|
+
* approval); the timeout is a safety net.
|
|
69
|
+
*/
|
|
70
|
+
export function subagentsWait(runId, step, config, now = Date.now()) {
|
|
71
|
+
const ms = config.tools.approvalTimeoutHours * 3_600_000 + config.runtime.maxRunSeconds * 1000 + 600_000;
|
|
72
|
+
return {
|
|
73
|
+
kind: "subagents",
|
|
74
|
+
id: subagentsWaitId(runId, step),
|
|
75
|
+
expiresAt: new Date(now + ms).toISOString(),
|
|
76
|
+
step,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const approvalAuthorization = (a) => ({
|
|
80
|
+
approvalId: a.id,
|
|
81
|
+
approvedByAccountId: a.decidedBy,
|
|
82
|
+
scope: a.scope,
|
|
83
|
+
});
|
|
84
|
+
/**
|
|
85
|
+
* 幂等键(brief 约定)。同一调用因请求用户输入而被再次调用时,第 n(≥ 2)次调用的开始事件带轮次后缀。
|
|
86
|
+
*/
|
|
87
|
+
const idem = {
|
|
88
|
+
started: (runId, callId, attempt = 1) => attempt === 1
|
|
89
|
+
? `tool_started:${runId}:${callId}`
|
|
90
|
+
: `tool_started:${runId}:${callId}:${attempt}`,
|
|
91
|
+
result: (runId, callId) => `tool_result:${runId}:${callId}`,
|
|
92
|
+
connection: (runId, callId) => `connection_required:${runId}:${callId}`,
|
|
93
|
+
hookDenied: (runId, callId) => `hook_denied:${runId}:${callId}`,
|
|
94
|
+
};
|
|
95
|
+
/** 同一调用最多连续请求用户输入的次数。 */
|
|
96
|
+
const MAX_INPUT_ROUNDS = 3;
|
|
97
|
+
/** 输入请求 state 的上限(JSON 序列化后的字节数)。 */
|
|
98
|
+
const MAX_INPUT_STATE_BYTES = 16 * 1024;
|
|
99
|
+
/** 关闭会话时最多等待的时间(连接器 close 卡住不拖住运行)。 */
|
|
100
|
+
const CLOSE_WAIT_MS = 5_000;
|
|
101
|
+
const errName = (e) => e instanceof Error && typeof e.name === "string" && e.name !== "" ? e.name : "Error";
|
|
102
|
+
const isInputRequired = (v) => typeof v === "object" && v !== null && v.kind === "input_required";
|
|
103
|
+
/**
|
|
104
|
+
* 内置工具结果里的 `loadedTools`(见 `BuiltinResult`):只保留本步可用、非内置的工具 key,去重、保持顺序。
|
|
105
|
+
*/
|
|
106
|
+
function loadedFrom(result, tools) {
|
|
107
|
+
const raw = result?.loadedTools;
|
|
108
|
+
if (!Array.isArray(raw))
|
|
109
|
+
return [];
|
|
110
|
+
const known = new Set(tools.filter((t) => !isAlwaysLoaded(t.key)).map((t) => t.key));
|
|
111
|
+
return [...new Set(raw.filter((k) => typeof k === "string" && known.has(k)))];
|
|
112
|
+
}
|
|
113
|
+
class Stopped extends Error {
|
|
114
|
+
}
|
|
115
|
+
/** 在工具结果末尾附上说明(如被拒绝的 elicitation);结果不是 `{ content: [] }` 形状时原样返回。 */
|
|
116
|
+
function withNotes(result, notes) {
|
|
117
|
+
if (notes.length === 0)
|
|
118
|
+
return result;
|
|
119
|
+
const content = result?.content;
|
|
120
|
+
if (!Array.isArray(content))
|
|
121
|
+
return result;
|
|
122
|
+
return {
|
|
123
|
+
...result,
|
|
124
|
+
content: [...content, ...notes.map((text) => ({ type: "text", text }))],
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/** 审批的等待时长(小时,四舍五入,至少 1)。 */
|
|
128
|
+
const approvalHours = (a) => Math.max(1, Math.round((new Date(a.expiresAt).getTime() - new Date(a.createdAt).getTime()) / 3_600_000));
|
|
129
|
+
const approvalWait = (a) => ({
|
|
130
|
+
kind: "approval",
|
|
131
|
+
id: a.id,
|
|
132
|
+
expiresAt: a.expiresAt,
|
|
133
|
+
});
|
|
134
|
+
const inputWait = (q) => ({
|
|
135
|
+
kind: "input",
|
|
136
|
+
id: q.id,
|
|
137
|
+
expiresAt: q.expiresAt,
|
|
138
|
+
});
|
|
139
|
+
/**
|
|
140
|
+
* 工具执行管线(spec 5.4 / 6.2):每个阶段是独立函数——
|
|
141
|
+
* `resolveOutputRefs` → `validateInput` → `decidePolicy` → `execute`(超时、熔断)→ `renderResult` / `spillIfLarge`
|
|
142
|
+
* → 写 `tool_result` 与审计。
|
|
143
|
+
*
|
|
144
|
+
* 有副作用不盲目重跑(spec 4.3):执行前写 `tool_call_started`;续跑时「有开始无结果」的调用记为结果未知,不再执行。
|
|
145
|
+
*/
|
|
146
|
+
export class ToolPipeline {
|
|
147
|
+
deps;
|
|
148
|
+
decide;
|
|
149
|
+
constructor(deps) {
|
|
150
|
+
this.deps = deps;
|
|
151
|
+
this.decide = deps.decidePolicy ?? createDecidePolicy(deps.db);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* tools 节点:按顺序处理本步 assistant 消息里的全部 `tool_call`,每个调用都以一条 `tool_result` 结束
|
|
155
|
+
* (之后才回到 prepare 构建下一次请求,保持「已记录调用、未记录结果之间不构建请求」的不变式)。
|
|
156
|
+
* 已有结果 → 跳过;有开始无结果 → 结果未知;run 已中止 → 不执行。
|
|
157
|
+
* 某个调用需要等人处理(审批 / 用户输入)→ 返回 `suspended`,之后的调用留到续跑时处理;
|
|
158
|
+
* 续跑时重新进入本函数,按 PG 里的审批 / 输入请求结果继续。
|
|
159
|
+
*/
|
|
160
|
+
async runStep(ctx) {
|
|
161
|
+
const { eventLog } = this.deps;
|
|
162
|
+
const msg = await eventLog.findByIdem(ctx.conversationId, assistantIdemKey(ctx.runId, ctx.step));
|
|
163
|
+
if (msg?.type !== "assistant_message")
|
|
164
|
+
return { kind: "done" };
|
|
165
|
+
const calls = msg.payload.content.flatMap((b) => b.type === "tool_call" ? [{ id: b.id, name: b.name, input: b.input }] : []);
|
|
166
|
+
let resolved;
|
|
167
|
+
const resolve = async () => {
|
|
168
|
+
// 每步实时解析(连接器 / 插件状态可能在模型调用之后变化)
|
|
169
|
+
if (!resolved) {
|
|
170
|
+
// the request builder's rule: a skills catalog snapshot (even an empty one) or a person's
|
|
171
|
+
// skill invocation in the log → skill_load is offered (see skillLoadOffered)
|
|
172
|
+
const skillLoad = await skillLoadOfferedIn(this.deps.db, this.deps.eventLog, ctx.conversationId);
|
|
173
|
+
resolved = await this.deps.registry.forAgent(ctx.orgId, {
|
|
174
|
+
tools: ctx.agent.tools ?? [],
|
|
175
|
+
skillLoad,
|
|
176
|
+
subagents: ctx.agent.subagents,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return resolved;
|
|
180
|
+
};
|
|
181
|
+
// `agent` calls whose sub-agent still runs (spec §7.1): a dispatch never blocks later calls;
|
|
182
|
+
// once every call was handled, the step waits for them
|
|
183
|
+
let open = 0;
|
|
184
|
+
for (const call of calls) {
|
|
185
|
+
if (await eventLog.findByIdem(ctx.conversationId, idem.result(ctx.runId, call.id)))
|
|
186
|
+
continue;
|
|
187
|
+
if (call.name === AGENT_TOOL) {
|
|
188
|
+
const row = await dispatchOfCall(this.deps.db, ctx.runId, call.id);
|
|
189
|
+
if (row) {
|
|
190
|
+
if (!(await this.finishAgentCall(ctx, row, call, resolve)))
|
|
191
|
+
open++;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const waitingFor = await this.processCall(ctx, call, resolve);
|
|
196
|
+
if (waitingFor === OPEN)
|
|
197
|
+
open++;
|
|
198
|
+
else if (waitingFor)
|
|
199
|
+
return { kind: "suspended", waitingFor };
|
|
200
|
+
}
|
|
201
|
+
if (open > 0) {
|
|
202
|
+
// the run is being stopped (a person, its deadline): it does not wait for its sub-agents;
|
|
203
|
+
// their calls get their results now, and the children are stopped after the run's terminal
|
|
204
|
+
// commit (spec §12)
|
|
205
|
+
if (ctx.signal.aborted) {
|
|
206
|
+
const stopped = await this.deps.db
|
|
207
|
+
.transaction()
|
|
208
|
+
.execute((t) => this.stopOpenAgentCalls(t, ctx.runId, ctx.agent));
|
|
209
|
+
if (stopped.toSeq !== null) {
|
|
210
|
+
await eventLog.notify(ctx.conversationId, stopped.toSeq);
|
|
211
|
+
}
|
|
212
|
+
stopped.fire();
|
|
213
|
+
return { kind: "done" };
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
kind: "suspended",
|
|
217
|
+
waitingFor: subagentsWait(ctx.runId, ctx.step, this.deps.config()),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
return { kind: "done" };
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* An `agent` call that was dispatched (spec §7.1 "already dispatched"): when its child run is
|
|
224
|
+
* terminal, writes the parent's `subagent_finished` (if the child's finisher did not) and the
|
|
225
|
+
* call's `tool_result` from the child's log, and returns true; else false (still open).
|
|
226
|
+
*
|
|
227
|
+
* The call ran like any tool, so the result is audited `tool.called` (duration = the child's;
|
|
228
|
+
* the authorization is known only when an approval let it run: a policy or remembered grant at
|
|
229
|
+
* dispatch time is not recorded, so nothing is guessed) and fires `tool.postExecute` with the
|
|
230
|
+
* result, from the replica that writes it (spec §11).
|
|
231
|
+
*/
|
|
232
|
+
async finishAgentCall(ctx, row, call, resolve) {
|
|
233
|
+
const modelGiven = typeof call.input === "object" &&
|
|
234
|
+
call.input !== null &&
|
|
235
|
+
call.input.model !== undefined;
|
|
236
|
+
const r = await childResult(this.deps.eventLog, row, { modelGiven });
|
|
237
|
+
if (!r)
|
|
238
|
+
return false;
|
|
239
|
+
// No spec-hash check as in processCall: an `agent` call writes no tool_call_started holding
|
|
240
|
+
// the executed definition's hash, and its description changes with the roster and limits
|
|
241
|
+
// while the child runs. The call's approval (forCall: its latest) is by construction the one
|
|
242
|
+
// that let this dispatch start, since a dispatch happens only after an approved decision.
|
|
243
|
+
const approval = await this.deps.approvals.forCall(ctx.runId, call.id);
|
|
244
|
+
await this.writeResult(ctx, row.parentCallId, AGENT_TOOL, { content: r.content, isError: r.isError }, [finishedEvent(row, r.finished)], {
|
|
245
|
+
durationMs: r.finished.durationMs,
|
|
246
|
+
connectorId: null,
|
|
247
|
+
...(approval?.status === "approved" && {
|
|
248
|
+
authorization: approvalAuthorization(approval),
|
|
249
|
+
}),
|
|
250
|
+
});
|
|
251
|
+
const hooks = this.deps.pluginHooks;
|
|
252
|
+
const scope = hookScope(hooks, ctx);
|
|
253
|
+
if (hooks && scope && hooks.has("tool.postExecute")) {
|
|
254
|
+
const tool = (await resolve()).tools.find((t) => t.key === AGENT_TOOL);
|
|
255
|
+
if (tool) {
|
|
256
|
+
fireObserve(hooks, "tool.postExecute", {
|
|
257
|
+
...scope,
|
|
258
|
+
step: ctx.step,
|
|
259
|
+
callId: call.id,
|
|
260
|
+
toolKey: AGENT_TOOL,
|
|
261
|
+
specHash: tool.specHash,
|
|
262
|
+
input: call.input,
|
|
263
|
+
isError: r.isError,
|
|
264
|
+
content: r.content,
|
|
265
|
+
durationMs: r.finished.durationMs,
|
|
266
|
+
}, ctx.signal, this.deps.log);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Writes, in `t`, the `tool_result` of every open `agent` call of a run that is being stopped
|
|
273
|
+
* (sub-agents spec §12: a person stopped it while it waited on sub-agents, or its deadline or a
|
|
274
|
+
* stop hit its tools node after a dispatch). A call whose child already ended gets the child's
|
|
275
|
+
* result (and the missing `subagent_finished`); one whose child is still active gets
|
|
276
|
+
* {@link AGENT_STOPPED_WITH_RUN_TEXT}. The caller stops those children after its commit.
|
|
277
|
+
*
|
|
278
|
+
* Like every `agent` result (see {@link finishAgentCall}), each is audited `tool.called` in `t`
|
|
279
|
+
* (duration: the child's, or the time since the dispatch) and fires `tool.postExecute`, through
|
|
280
|
+
* `fire()`, which the caller runs after its commit. Returns the steps and calls written (the
|
|
281
|
+
* caller must not write a "not executed" result for them) and the highest seq written.
|
|
282
|
+
*/
|
|
283
|
+
async stopOpenAgentCalls(t, runId, agent) {
|
|
284
|
+
// phase D: the run's fork invocation without a report gets one (a fork has no tool call)
|
|
285
|
+
const forks = await pairStoppedForks(t, this.deps.eventLog, runId);
|
|
286
|
+
const none = {
|
|
287
|
+
steps: [],
|
|
288
|
+
callIds: new Set(forks.callIds),
|
|
289
|
+
toSeq: forks.toSeq,
|
|
290
|
+
fire: () => { },
|
|
291
|
+
};
|
|
292
|
+
const all = await t
|
|
293
|
+
.selectFrom("subagent_dispatches")
|
|
294
|
+
.select([
|
|
295
|
+
"parent_run_id",
|
|
296
|
+
"parent_call_id",
|
|
297
|
+
"parent_conversation_id",
|
|
298
|
+
"child_conversation_id",
|
|
299
|
+
"child_run_id",
|
|
300
|
+
"step",
|
|
301
|
+
"created_at",
|
|
302
|
+
])
|
|
303
|
+
.where("parent_run_id", "=", runId)
|
|
304
|
+
.orderBy("step")
|
|
305
|
+
.orderBy("created_at")
|
|
306
|
+
.execute();
|
|
307
|
+
const dispatches = all.filter((d) => forkSeqOfCall(d.parent_call_id, d.step) === null);
|
|
308
|
+
if (dispatches.length === 0)
|
|
309
|
+
return none;
|
|
310
|
+
const run = await t
|
|
311
|
+
.selectFrom("runs as r")
|
|
312
|
+
.innerJoin("conversations as c", "c.id", "r.conversation_id")
|
|
313
|
+
.select([
|
|
314
|
+
"r.conversation_id",
|
|
315
|
+
"r.trigger_account_id",
|
|
316
|
+
"r.agent_version",
|
|
317
|
+
"c.org_id",
|
|
318
|
+
"c.agent_id",
|
|
319
|
+
"c.kind",
|
|
320
|
+
])
|
|
321
|
+
.where("r.id", "=", runId)
|
|
322
|
+
.executeTakeFirst();
|
|
323
|
+
if (!run)
|
|
324
|
+
return none;
|
|
325
|
+
const conversationId = run.conversation_id;
|
|
326
|
+
const inputs = new Map();
|
|
327
|
+
const events = [];
|
|
328
|
+
const calls = [];
|
|
329
|
+
for (const d of dispatches) {
|
|
330
|
+
const written = await t
|
|
331
|
+
.selectFrom("conversation_events")
|
|
332
|
+
.select("seq")
|
|
333
|
+
.where("conversation_id", "=", conversationId)
|
|
334
|
+
.where("idem_key", "=", idem.result(runId, d.parent_call_id))
|
|
335
|
+
.executeTakeFirst();
|
|
336
|
+
if (written)
|
|
337
|
+
continue;
|
|
338
|
+
if (!inputs.has(`step:${d.step}`)) {
|
|
339
|
+
inputs.set(`step:${d.step}`, true);
|
|
340
|
+
const msg = await t
|
|
341
|
+
.selectFrom("conversation_events")
|
|
342
|
+
.select("payload")
|
|
343
|
+
.where("conversation_id", "=", conversationId)
|
|
344
|
+
.where("idem_key", "=", assistantIdemKey(runId, d.step))
|
|
345
|
+
.executeTakeFirst();
|
|
346
|
+
const content = msg?.payload?.content ?? [];
|
|
347
|
+
for (const b of content) {
|
|
348
|
+
if (b.type === "tool_call" && b.id !== undefined)
|
|
349
|
+
inputs.set(b.id, b.input);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
const input = inputs.get(d.parent_call_id);
|
|
353
|
+
const row = {
|
|
354
|
+
parentRunId: d.parent_run_id,
|
|
355
|
+
parentCallId: d.parent_call_id,
|
|
356
|
+
parentConversationId: d.parent_conversation_id,
|
|
357
|
+
childConversationId: d.child_conversation_id,
|
|
358
|
+
childRunId: d.child_run_id,
|
|
359
|
+
};
|
|
360
|
+
const child = await t
|
|
361
|
+
.selectFrom("runs")
|
|
362
|
+
.select("status")
|
|
363
|
+
.where("id", "=", d.child_run_id)
|
|
364
|
+
.executeTakeFirst();
|
|
365
|
+
// a child that already ended keeps its own result (its log is committed)
|
|
366
|
+
const ended = child !== undefined && TERMINAL_STATUSES.has(child.status)
|
|
367
|
+
? await childResult(this.deps.eventLog, row, {
|
|
368
|
+
modelGiven: typeof input === "object" &&
|
|
369
|
+
input !== null &&
|
|
370
|
+
input.model !== undefined,
|
|
371
|
+
})
|
|
372
|
+
: null;
|
|
373
|
+
if (ended)
|
|
374
|
+
events.push(finishedEvent(row, ended.finished));
|
|
375
|
+
const r = ended
|
|
376
|
+
? { content: ended.content, isError: ended.isError, durationMs: ended.finished.durationMs }
|
|
377
|
+
: {
|
|
378
|
+
content: AGENT_STOPPED_WITH_RUN_TEXT,
|
|
379
|
+
isError: true,
|
|
380
|
+
durationMs: Math.max(0, Date.now() - new Date(d.created_at).getTime()),
|
|
381
|
+
};
|
|
382
|
+
events.push({
|
|
383
|
+
type: "tool_result",
|
|
384
|
+
runId,
|
|
385
|
+
idemKey: idem.result(runId, d.parent_call_id),
|
|
386
|
+
payload: {
|
|
387
|
+
step: d.step,
|
|
388
|
+
callId: d.parent_call_id,
|
|
389
|
+
toolKey: AGENT_TOOL,
|
|
390
|
+
content: r.content,
|
|
391
|
+
isError: r.isError,
|
|
392
|
+
},
|
|
393
|
+
});
|
|
394
|
+
calls.push({ step: d.step, callId: d.parent_call_id, input, r });
|
|
395
|
+
}
|
|
396
|
+
if (calls.length === 0)
|
|
397
|
+
return none;
|
|
398
|
+
const written = await this.deps.eventLog.append(conversationId, events, t);
|
|
399
|
+
for (const c of calls) {
|
|
400
|
+
const approval = await t
|
|
401
|
+
.selectFrom("approvals")
|
|
402
|
+
.select(["id", "status", "decided_by", "scope"])
|
|
403
|
+
.where("run_id", "=", runId)
|
|
404
|
+
.where("call_id", "=", c.callId)
|
|
405
|
+
.orderBy("attempt", "desc")
|
|
406
|
+
.limit(1)
|
|
407
|
+
.executeTakeFirst();
|
|
408
|
+
await audit(t, {
|
|
409
|
+
actorAccountId: run.trigger_account_id,
|
|
410
|
+
orgId: run.org_id,
|
|
411
|
+
action: "tool.called",
|
|
412
|
+
target: conversationId,
|
|
413
|
+
detail: {
|
|
414
|
+
toolKey: AGENT_TOOL,
|
|
415
|
+
isError: c.r.isError,
|
|
416
|
+
durationMs: c.r.durationMs,
|
|
417
|
+
agentId: run.agent_id,
|
|
418
|
+
agentVersion: run.agent_version,
|
|
419
|
+
conversationId,
|
|
420
|
+
runId,
|
|
421
|
+
callId: c.callId,
|
|
422
|
+
...(approval?.status === "approved" && {
|
|
423
|
+
approvalId: approval.id,
|
|
424
|
+
approvedByAccountId: approval.decided_by,
|
|
425
|
+
scope: approval.scope,
|
|
426
|
+
}),
|
|
427
|
+
},
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
const fire = () => {
|
|
431
|
+
const hooks = this.deps.pluginHooks;
|
|
432
|
+
const scope = hookScope(hooks, {
|
|
433
|
+
conversationKind: run.kind,
|
|
434
|
+
orgId: run.org_id,
|
|
435
|
+
accountId: run.trigger_account_id,
|
|
436
|
+
agentId: run.agent_id,
|
|
437
|
+
agentVersion: run.agent_version,
|
|
438
|
+
conversationId,
|
|
439
|
+
runId,
|
|
440
|
+
});
|
|
441
|
+
if (!hooks || !scope || !hooks.has("tool.postExecute"))
|
|
442
|
+
return;
|
|
443
|
+
void (async () => {
|
|
444
|
+
const def = agent ??
|
|
445
|
+
normalizeStoredDefinition((await this.deps.db
|
|
446
|
+
.selectFrom("agent_versions")
|
|
447
|
+
.select("definition")
|
|
448
|
+
.where("agent_id", "=", run.agent_id)
|
|
449
|
+
.where("version", "=", run.agent_version)
|
|
450
|
+
.executeTakeFirstOrThrow()).definition);
|
|
451
|
+
const skillLoad = await skillLoadOfferedIn(this.deps.db, this.deps.eventLog, conversationId);
|
|
452
|
+
const resolved = await this.deps.registry.forAgent(run.org_id, {
|
|
453
|
+
tools: def.tools ?? [],
|
|
454
|
+
skillLoad,
|
|
455
|
+
subagents: def.subagents,
|
|
456
|
+
});
|
|
457
|
+
const tool = resolved.tools.find((x) => x.key === AGENT_TOOL);
|
|
458
|
+
if (!tool)
|
|
459
|
+
return;
|
|
460
|
+
// the run is over: nothing aborts these observe hooks but their own timeouts
|
|
461
|
+
const signal = new AbortController().signal;
|
|
462
|
+
for (const c of calls) {
|
|
463
|
+
fireObserve(hooks, "tool.postExecute", {
|
|
464
|
+
...scope,
|
|
465
|
+
step: c.step,
|
|
466
|
+
callId: c.callId,
|
|
467
|
+
toolKey: AGENT_TOOL,
|
|
468
|
+
specHash: tool.specHash,
|
|
469
|
+
input: c.input,
|
|
470
|
+
isError: c.r.isError,
|
|
471
|
+
content: c.r.content,
|
|
472
|
+
durationMs: c.r.durationMs,
|
|
473
|
+
}, signal, this.deps.log);
|
|
474
|
+
}
|
|
475
|
+
})().catch((err) => this.deps.log.warn("cannot fire tool.postExecute for stopped agent calls", { runId, err }));
|
|
476
|
+
};
|
|
477
|
+
return {
|
|
478
|
+
steps: [...new Set(calls.map((c) => c.step))],
|
|
479
|
+
callIds: new Set([...forks.callIds, ...calls.map((c) => c.callId)]),
|
|
480
|
+
toSeq: Math.max(forks.toSeq ?? 0, ...written.map((e) => e.seq)),
|
|
481
|
+
fire,
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* 处理一个尚无结果的调用。先看输入请求的轮次:最近一轮仍 pending → 继续等;否则本次是第
|
|
486
|
+
* `已有轮数 + 1` 次调用,已有轮次时带 `ctx.resume`(answered → 答案,expired / cancelled → null)。
|
|
487
|
+
* 本次调用「有开始无结果」→ 结果未知(spec 4.3)。返回需要等待的对象,或 undefined(已写结果)。
|
|
488
|
+
*/
|
|
489
|
+
async processCall(ctx, call, resolve) {
|
|
490
|
+
const rounds = await this.deps.questions.forCall(ctx.runId, call.id);
|
|
491
|
+
const last = rounds.at(-1);
|
|
492
|
+
if (last?.status === "pending")
|
|
493
|
+
return inputWait(last);
|
|
494
|
+
const attempt = rounds.length + 1;
|
|
495
|
+
const started = await this.deps.eventLog.findByIdem(ctx.conversationId, idem.started(ctx.runId, call.id, attempt));
|
|
496
|
+
if (started?.type === "tool_call_started") {
|
|
497
|
+
// 确实开始执行过:照样审计 tool.called(耗时未知);放行依据只认批准了所执行定义的审批记录
|
|
498
|
+
const key = started.payload.toolKey;
|
|
499
|
+
const approval = await this.deps.approvals.forCall(ctx.runId, call.id);
|
|
500
|
+
const approved = approval?.status === "approved" && approval.specHash === started.payload.specHash
|
|
501
|
+
? approval
|
|
502
|
+
: null;
|
|
503
|
+
const source = approved ? null : (await resolve()).tools.find((t) => t.key === key)?.source;
|
|
504
|
+
await this.writeResult(ctx, call.id, key, { content: UNKNOWN_RESULT_TEXT, isError: true }, [], {
|
|
505
|
+
durationMs: null,
|
|
506
|
+
connectorId: approved
|
|
507
|
+
? approved.connectorId
|
|
508
|
+
: source?.kind === "connector"
|
|
509
|
+
? source.connectorId
|
|
510
|
+
: null,
|
|
511
|
+
...(approved && { authorization: approvalAuthorization(approved) }),
|
|
512
|
+
});
|
|
513
|
+
return undefined;
|
|
514
|
+
}
|
|
515
|
+
if (ctx.signal.aborted) {
|
|
516
|
+
await this.writeResult(ctx, call.id, call.name, {
|
|
517
|
+
content: RUN_STOPPED_TEXT,
|
|
518
|
+
isError: true,
|
|
519
|
+
});
|
|
520
|
+
return undefined;
|
|
521
|
+
}
|
|
522
|
+
const resume = last
|
|
523
|
+
? { state: last.state, answer: last.status === "answered" ? last.answer : null }
|
|
524
|
+
: undefined;
|
|
525
|
+
return this.runCall(ctx, call, await resolve(), attempt, resume);
|
|
526
|
+
}
|
|
527
|
+
async runCall(ctx, call, resolved, attempt, resume) {
|
|
528
|
+
const fail = async (content, key = call.name) => {
|
|
529
|
+
await this.writeResult(ctx, call.id, key, { content, isError: true });
|
|
530
|
+
return undefined;
|
|
531
|
+
};
|
|
532
|
+
const tool = resolved.tools.find((t) => t.key === call.name);
|
|
533
|
+
if (!tool) {
|
|
534
|
+
const why = resolved.unavailable.find((u) => u.ref === call.name) ??
|
|
535
|
+
resolved.hidden?.find((u) => u.ref === call.name);
|
|
536
|
+
if (why)
|
|
537
|
+
return fail(unavailableText(call.name, why.reason));
|
|
538
|
+
let keys = resolved.tools.map((t) => t.key);
|
|
539
|
+
// tool_search 模式:目录可能很大,只列内置工具与发出本次调用的请求里带了定义的工具
|
|
540
|
+
if (resolved.toolSearch === true) {
|
|
541
|
+
const sent = await this.sentTools(ctx);
|
|
542
|
+
if (sent !== null)
|
|
543
|
+
keys = keys.filter((k) => isAlwaysLoaded(k) || sent.has(k));
|
|
544
|
+
}
|
|
545
|
+
keys.sort();
|
|
546
|
+
return fail(unknownToolText(call.name, keys, keys.includes("tool_search")));
|
|
547
|
+
}
|
|
548
|
+
// tool_search 模式下,模型只能调用请求里带了完整定义的工具:目录里有、但发出本次调用的请求没加载 →
|
|
549
|
+
// 明确错误,不自动加载(spec 5.5 / 6.3)。同一步里先 tool_search 再调用刚加载的工具同样报错。
|
|
550
|
+
if (resolved.toolSearch === true && !isAlwaysLoaded(tool.key)) {
|
|
551
|
+
const sent = await this.sentTools(ctx);
|
|
552
|
+
if (sent !== null && !sent.has(tool.key))
|
|
553
|
+
return fail(notLoadedText(tool.key), tool.key);
|
|
554
|
+
}
|
|
555
|
+
let input;
|
|
556
|
+
try {
|
|
557
|
+
input = await resolveOutputRefs(call.input, (id) => this.deps.outputs.load({ orgId: ctx.orgId, conversationId: ctx.conversationId }, id));
|
|
558
|
+
}
|
|
559
|
+
catch (e) {
|
|
560
|
+
if (e instanceof ToolError)
|
|
561
|
+
return fail(e.message, tool.key);
|
|
562
|
+
throw e;
|
|
563
|
+
}
|
|
564
|
+
// 连接器工具的 schema 来自连接器(不受信任):去掉有风险的关键字并限制大小 / 层数
|
|
565
|
+
const invalid = validateInput(tool.key, tool.spec, input, {
|
|
566
|
+
untrusted: tool.source.kind === "connector",
|
|
567
|
+
log: this.deps.log,
|
|
568
|
+
});
|
|
569
|
+
if (invalid !== null)
|
|
570
|
+
return fail(invalid, tool.key);
|
|
571
|
+
const decision = await this.decide(tool, ctx.agent, {
|
|
572
|
+
orgId: ctx.orgId,
|
|
573
|
+
accountId: ctx.accountId,
|
|
574
|
+
agentId: ctx.agentId,
|
|
575
|
+
conversationId: ctx.conversationId,
|
|
576
|
+
runId: ctx.runId,
|
|
577
|
+
...(ctx.parent !== undefined && {
|
|
578
|
+
parentConversationId: ctx.parent.conversationId,
|
|
579
|
+
...(ctx.parent.otherAgent !== undefined && { parentAgentDiffers: ctx.parent.otherAgent }),
|
|
580
|
+
}),
|
|
581
|
+
});
|
|
582
|
+
if (decision === "deny")
|
|
583
|
+
return fail(deniedText(tool.key), tool.key);
|
|
584
|
+
if (typeof decision === "object" && "unavailable" in decision) {
|
|
585
|
+
return fail(unavailableText(tool.key, decision.unavailable), tool.key);
|
|
586
|
+
}
|
|
587
|
+
// 该调用已有审批(续跑)→ 按审批结果;没有且策略为 ask → 建审批并等待
|
|
588
|
+
// (不写 tool_call_started:此时尚未开始执行)。
|
|
589
|
+
let approval = await this.deps.approvals.forCall(ctx.runId, call.id);
|
|
590
|
+
// 批准的是旧定义(等待期间工具定义变了):原批准不适用于当前定义,策略仍需审批时按新定义重新审批。
|
|
591
|
+
const stale = approval?.status === "approved" && approval.specHash !== tool.specHash;
|
|
592
|
+
if ((!approval || stale) && typeof decision === "object" && "ask" in decision) {
|
|
593
|
+
approval = await this.deps.approvals.request({
|
|
594
|
+
attempt: (approval?.attempt ?? 0) + 1,
|
|
595
|
+
orgId: ctx.orgId,
|
|
596
|
+
conversationId: ctx.conversationId,
|
|
597
|
+
runId: ctx.runId,
|
|
598
|
+
step: ctx.step,
|
|
599
|
+
callId: call.id,
|
|
600
|
+
toolKey: tool.key,
|
|
601
|
+
specHash: decision.ask.specHash,
|
|
602
|
+
connectorId: decision.ask.connectorId,
|
|
603
|
+
input: call.input,
|
|
604
|
+
requestedFor: decision.ask.requestedFor,
|
|
605
|
+
approverRole: decision.ask.approverRole,
|
|
606
|
+
policy: decision.ask.policy,
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
if (approval && approval.status !== "approved") {
|
|
610
|
+
const refused = this.refusalText(approval);
|
|
611
|
+
return refused === null ? approvalWait(approval) : fail(refused, tool.key);
|
|
612
|
+
}
|
|
613
|
+
// 走到这里即放行:在(与当前定义一致的)审批下执行,否则是授权或策略放行
|
|
614
|
+
const authorization = approval?.status === "approved" && approval.specHash === tool.specHash
|
|
615
|
+
? approvalAuthorization(approval)
|
|
616
|
+
: typeof decision === "object" && "grant" in decision
|
|
617
|
+
? { grant: decision.grant }
|
|
618
|
+
: { policy: "allow" };
|
|
619
|
+
// tool.preExecute after policy and approval, before tool_call_started (F10)
|
|
620
|
+
const hooks = this.deps.pluginHooks;
|
|
621
|
+
const scope = hookScope(hooks, ctx);
|
|
622
|
+
if (scope && hooks?.has("tool.preExecute")) {
|
|
623
|
+
// sticky: a denial already logged for this call (e.g. an older log, or a crash right after it)
|
|
624
|
+
// is final: fail with the recorded reason, never re-run the hooks or execute the tool. Only
|
|
625
|
+
// looked up when a tool.preExecute hook could run (M-2: no query without hooks); a denial is
|
|
626
|
+
// written with the call's tool_result, so a call with a logged denial never runs again anyway.
|
|
627
|
+
const prior = await this.deps.eventLog.findByIdem(ctx.conversationId, idem.hookDenied(ctx.runId, call.id));
|
|
628
|
+
if (prior?.type === "hook_denied") {
|
|
629
|
+
return fail(hookDeniedText(prior.payload.plugin, prior.payload.reason), tool.key);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
// payload only built when a tool hook is registered (zero overhead otherwise)
|
|
633
|
+
const hookBase = scope && hooks && (hooks.has("tool.preExecute") || hooks.has("tool.postExecute"))
|
|
634
|
+
? {
|
|
635
|
+
...scope,
|
|
636
|
+
step: ctx.step,
|
|
637
|
+
callId: call.id,
|
|
638
|
+
toolKey: tool.key,
|
|
639
|
+
specHash: tool.specHash,
|
|
640
|
+
input,
|
|
641
|
+
}
|
|
642
|
+
: null;
|
|
643
|
+
if (hooks && hookBase && hooks.has("tool.preExecute")) {
|
|
644
|
+
let verdict;
|
|
645
|
+
try {
|
|
646
|
+
verdict = await hooks.run("tool.preExecute", hookBase, ctx.signal);
|
|
647
|
+
}
|
|
648
|
+
catch (e) {
|
|
649
|
+
// run() rejects only when the run's signal aborted: an abort, never a deny
|
|
650
|
+
if (ctx.signal.aborted)
|
|
651
|
+
return fail(RUN_STOPPED_TEXT, tool.key);
|
|
652
|
+
throw e;
|
|
653
|
+
}
|
|
654
|
+
if (!verdict.allowed) {
|
|
655
|
+
// hook_denied, its audit entry and the tool_result in one transaction (no window where the
|
|
656
|
+
// denial is logged but the call has no result)
|
|
657
|
+
const site = {
|
|
658
|
+
orgId: ctx.orgId,
|
|
659
|
+
conversationId: ctx.conversationId,
|
|
660
|
+
runId: ctx.runId,
|
|
661
|
+
accountId: ctx.accountId,
|
|
662
|
+
agentId: ctx.agentId,
|
|
663
|
+
idemKey: idem.hookDenied(ctx.runId, call.id),
|
|
664
|
+
};
|
|
665
|
+
const event = hookDeniedEvent(site, "tool.preExecute", verdict, {
|
|
666
|
+
step: ctx.step,
|
|
667
|
+
callId: call.id,
|
|
668
|
+
});
|
|
669
|
+
await this.writeResult(ctx, call.id, tool.key, { content: hookDeniedText(verdict.plugin, verdict.reason), isError: true }, [event], undefined, [hookDeniedAudit(site, event.payload, verdict)]);
|
|
670
|
+
return undefined;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
// `agent` (sub-agents spec §7.1): no tool_call_started; the dispatch is made durable in one
|
|
674
|
+
// transaction (its marker is the subagent_dispatches row) and the call stays open
|
|
675
|
+
const dispatcher = this.deps.subagents;
|
|
676
|
+
if (tool.source.kind === "builtin" && tool.key === AGENT_TOOL && dispatcher) {
|
|
677
|
+
const parsed = AgentToolInput.safeParse(input);
|
|
678
|
+
if (!parsed.success) {
|
|
679
|
+
return fail("Invalid agent call: pass description and prompt, and optionally subagent_type, model or resume.", tool.key);
|
|
680
|
+
}
|
|
681
|
+
const r = await dispatcher.dispatch({ ctx, callId: call.id, input: parsed.data });
|
|
682
|
+
return r.kind === "refused" ? fail(r.content, tool.key) : OPEN;
|
|
683
|
+
}
|
|
684
|
+
await this.deps.eventLog.append(ctx.conversationId, [
|
|
685
|
+
{
|
|
686
|
+
type: "tool_call_started",
|
|
687
|
+
runId: ctx.runId,
|
|
688
|
+
idemKey: idem.started(ctx.runId, call.id, attempt),
|
|
689
|
+
payload: { step: ctx.step, callId: call.id, toolKey: tool.key, specHash: tool.specHash },
|
|
690
|
+
},
|
|
691
|
+
]);
|
|
692
|
+
const t0 = Date.now();
|
|
693
|
+
let outcome = await this.execute(tool, input, ctx, call.id, resume, resolved.tools);
|
|
694
|
+
const durationMs = Date.now() - t0;
|
|
695
|
+
await this.deps.hooks?.afterExecute?.(ctx.runId, call.id);
|
|
696
|
+
if (outcome.kind === "input_required") {
|
|
697
|
+
const asked = await this.askInput(ctx, call.id, tool.key, attempt, outcome);
|
|
698
|
+
if (typeof asked !== "string")
|
|
699
|
+
return asked;
|
|
700
|
+
outcome = { kind: "error", message: asked };
|
|
701
|
+
}
|
|
702
|
+
const rendered = outcome.kind === "result"
|
|
703
|
+
? renderResult(outcome.result)
|
|
704
|
+
: { text: outcome.message, contentTypes: [], isError: true };
|
|
705
|
+
// 内置工具(tool_search)报告的已加载工具:只认本步可用、非内置的 key,写进 tool_result(日志即事实源)
|
|
706
|
+
const loadedTools = outcome.kind === "result" && tool.source.kind === "builtin"
|
|
707
|
+
? loadedFrom(outcome.result, resolved.tools)
|
|
708
|
+
: [];
|
|
709
|
+
// 显示摘要:只认插件 / 内置工具与内核的 resources 工具给的(连接器结果来自外部服务,不采信),
|
|
710
|
+
// 校验不过就丢弃并告警
|
|
711
|
+
const display = outcome.kind === "result" && (tool.source.kind !== "connector" || tool.source.resources)
|
|
712
|
+
? this.displayOf(outcome.result, tool.key)
|
|
713
|
+
: undefined;
|
|
714
|
+
// structured view: builtins only (extension and connector results never carry one)
|
|
715
|
+
const view = outcome.kind === "result" && tool.source.kind === "builtin"
|
|
716
|
+
? this.viewOf(outcome.result, tool.key)
|
|
717
|
+
: undefined;
|
|
718
|
+
// inline card: plugin tools only, naming an inline view of the same extension
|
|
719
|
+
const card = outcome.kind === "result" && tool.source.kind === "plugin"
|
|
720
|
+
? this.cardOf(outcome.result, tool.source.plugin, tool.key)
|
|
721
|
+
: undefined;
|
|
722
|
+
const cfg = this.deps.config().tools;
|
|
723
|
+
const spilled = await spillIfLarge(rendered.text, {
|
|
724
|
+
// 内置工具可给更低的转存阈值(沙箱 bash 30000 字符)
|
|
725
|
+
spillChars: Math.min(cfg.outputSpillChars, tool.spillChars ?? Number.POSITIVE_INFINITY),
|
|
726
|
+
previewChars: cfg.outputPreviewChars,
|
|
727
|
+
}, (content) => this.deps.outputs.save({
|
|
728
|
+
orgId: ctx.orgId,
|
|
729
|
+
conversationId: ctx.conversationId,
|
|
730
|
+
callId: call.id,
|
|
731
|
+
content,
|
|
732
|
+
}));
|
|
733
|
+
const pre = outcome.kind === "connection_required"
|
|
734
|
+
? [
|
|
735
|
+
{
|
|
736
|
+
type: "connection_required",
|
|
737
|
+
runId: ctx.runId,
|
|
738
|
+
idemKey: idem.connection(ctx.runId, call.id),
|
|
739
|
+
payload: {
|
|
740
|
+
step: ctx.step,
|
|
741
|
+
callId: call.id,
|
|
742
|
+
// 事件 payload 字段名改名后不变(C27:日志确定性)
|
|
743
|
+
appId: outcome.connectorId,
|
|
744
|
+
appName: outcome.connectorName,
|
|
745
|
+
},
|
|
746
|
+
},
|
|
747
|
+
]
|
|
748
|
+
: [];
|
|
749
|
+
await this.writeResult(ctx, call.id, tool.key, {
|
|
750
|
+
content: spilled.content,
|
|
751
|
+
isError: rendered.isError,
|
|
752
|
+
...(spilled.outputId !== undefined && { outputId: spilled.outputId }),
|
|
753
|
+
...(rendered.contentTypes.length > 0 && { contentTypes: rendered.contentTypes }),
|
|
754
|
+
...(loadedTools.length > 0 && { loadedTools }),
|
|
755
|
+
...(display !== undefined && { display }),
|
|
756
|
+
...(view !== undefined && { view }),
|
|
757
|
+
...(card !== undefined && { card }),
|
|
758
|
+
}, pre, {
|
|
759
|
+
durationMs,
|
|
760
|
+
connectorId: tool.source.kind === "connector" ? tool.source.connectorId : null,
|
|
761
|
+
authorization,
|
|
762
|
+
});
|
|
763
|
+
// a sub-agent that needs a personal connection: mirrored in the parent for display (spec §9;
|
|
764
|
+
// not a wait, the child got the connect text as its result)
|
|
765
|
+
if (outcome.kind === "connection_required" && ctx.parent !== undefined) {
|
|
766
|
+
await this.deps.eventLog
|
|
767
|
+
.append(ctx.parent.conversationId, [
|
|
768
|
+
{
|
|
769
|
+
type: "subagent_waiting",
|
|
770
|
+
runId: ctx.parent.runId,
|
|
771
|
+
idemKey: subagentWaitingIdemKey(ctx.runId, `connect-${call.id}`),
|
|
772
|
+
payload: {
|
|
773
|
+
subagentId: ctx.conversationId,
|
|
774
|
+
childRunId: ctx.runId,
|
|
775
|
+
kind: "connect",
|
|
776
|
+
toolKey: tool.key,
|
|
777
|
+
},
|
|
778
|
+
},
|
|
779
|
+
])
|
|
780
|
+
.catch((err) => this.deps.log.warn("cannot surface a sub-agent's connect card in its parent", {
|
|
781
|
+
runId: ctx.runId,
|
|
782
|
+
err,
|
|
783
|
+
}));
|
|
784
|
+
}
|
|
785
|
+
if (hooks && hookBase) {
|
|
786
|
+
fireObserve(hooks, "tool.postExecute", { ...hookBase, isError: rendered.isError, content: spilled.content, durationMs }, ctx.signal, this.deps.log);
|
|
787
|
+
}
|
|
788
|
+
return undefined;
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* 发出本步调用的请求里带了完整定义的工具 key:本步最后一次尝试的 `model_call.request.tools`
|
|
792
|
+
* (`assistant_message` 来自最后一次尝试)。该请求不在 tool_search 模式(没有 `catalog`,工具全部发送)
|
|
793
|
+
* 或找不到 model_call 时返回 null(不做加载检查)。已加载集合由此从日志得出,不放进程内存。
|
|
794
|
+
*/
|
|
795
|
+
async sentTools(ctx) {
|
|
796
|
+
for (let attempt = MAX_MODEL_ATTEMPTS; attempt >= 1; attempt--) {
|
|
797
|
+
const e = await this.deps.eventLog.findByIdem(ctx.conversationId, modelCallIdemKey(ctx.runId, ctx.step, attempt));
|
|
798
|
+
if (e?.type !== "model_call")
|
|
799
|
+
continue;
|
|
800
|
+
const { tools, catalog } = e.payload.request;
|
|
801
|
+
return catalog === undefined ? null : new Set((tools ?? []).map((t) => t.key));
|
|
802
|
+
}
|
|
803
|
+
return null;
|
|
804
|
+
}
|
|
805
|
+
/** 审批的非批准结果 → 给模型的说明;仍 pending → null(继续等待)。 */
|
|
806
|
+
refusalText(a) {
|
|
807
|
+
switch (a.status) {
|
|
808
|
+
case "denied":
|
|
809
|
+
return approvalDeniedText(a.denyReason);
|
|
810
|
+
case "expired":
|
|
811
|
+
// 按该审批实际的等待时长(创建到到期),不读当前配置(配置可热更新)
|
|
812
|
+
return approvalExpiredText(approvalHours(a));
|
|
813
|
+
case "cancelled":
|
|
814
|
+
return APPROVAL_CANCELLED_TEXT;
|
|
815
|
+
default:
|
|
816
|
+
return null;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* 处理函数返回 `input_required`:超过轮次上限、表单不合规(`validateInputForm`)或 state 过大 → 返回给模型的错误说明;
|
|
821
|
+
* 否则建输入请求(写 `question_asked`)并返回等待对象。
|
|
822
|
+
*/
|
|
823
|
+
async askInput(ctx, callId, toolKey, attempt, req) {
|
|
824
|
+
if (attempt > MAX_INPUT_ROUNDS)
|
|
825
|
+
return TOO_MANY_INPUT_REQUESTS_TEXT;
|
|
826
|
+
const problems = validateInputForm(req.form);
|
|
827
|
+
if (problems.length > 0)
|
|
828
|
+
return invalidFormText(toolKey, problems);
|
|
829
|
+
let stateJson;
|
|
830
|
+
try {
|
|
831
|
+
stateJson = JSON.stringify(req.state ?? null);
|
|
832
|
+
}
|
|
833
|
+
catch {
|
|
834
|
+
stateJson = undefined;
|
|
835
|
+
}
|
|
836
|
+
if (stateJson === undefined)
|
|
837
|
+
return `Tool ${toolKey} returned input state that is not JSON.`;
|
|
838
|
+
if (Buffer.byteLength(stateJson, "utf8") > MAX_INPUT_STATE_BYTES) {
|
|
839
|
+
return stateTooLargeText(toolKey);
|
|
840
|
+
}
|
|
841
|
+
const q = await this.deps.questions.ask({
|
|
842
|
+
orgId: ctx.orgId,
|
|
843
|
+
conversationId: ctx.conversationId,
|
|
844
|
+
runId: ctx.runId,
|
|
845
|
+
step: ctx.step,
|
|
846
|
+
callId,
|
|
847
|
+
attempt,
|
|
848
|
+
toolKey,
|
|
849
|
+
form: req.form,
|
|
850
|
+
state: JSON.parse(stateJson),
|
|
851
|
+
});
|
|
852
|
+
return inputWait(q);
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* 管线第 4 阶段:执行。内置 / 插件工具直接调用处理函数,连接器工具经连接器(每次调用打开并关闭会话)。
|
|
856
|
+
* 带 `tools.timeoutMs` 超时与 run 的中止信号(合并后传给处理函数 / 连接器,不响应信号的也按时结束)。
|
|
857
|
+
* 熔断只作用于连接器工具(键为组织 × 连接器)。
|
|
858
|
+
*/
|
|
859
|
+
async execute(tool, input, ctx, callId, resume, stepTools) {
|
|
860
|
+
if (tool.source.kind === "connector") {
|
|
861
|
+
return this.executeConnector(tool, tool.source, input, ctx, resume);
|
|
862
|
+
}
|
|
863
|
+
const handler = this.deps.registry.handler(tool.source);
|
|
864
|
+
if (!handler)
|
|
865
|
+
return { kind: "error", message: `Tool ${tool.key} is no longer available.` };
|
|
866
|
+
// 插件处理函数只拿到 SDK 的 ToolCallContext;内置工具另外拿到本步解析出的工具(tool_search 用)
|
|
867
|
+
const base = {
|
|
868
|
+
orgId: ctx.orgId,
|
|
869
|
+
accountId: ctx.accountId,
|
|
870
|
+
agentId: ctx.agentId,
|
|
871
|
+
conversationId: ctx.conversationId,
|
|
872
|
+
callId,
|
|
873
|
+
...(resume !== undefined && { resume }),
|
|
874
|
+
...(tool.source.kind === "builtin" && stepTools !== undefined && { tools: stepTools }),
|
|
875
|
+
...(tool.source.kind === "builtin" && { runId: ctx.runId }),
|
|
876
|
+
...(tool.source.kind === "builtin" &&
|
|
877
|
+
ctx.deadline !== undefined && { runDeadline: ctx.deadline }),
|
|
878
|
+
// a sub-agent works in its parent conversation's sandbox (spec §10.1)
|
|
879
|
+
...(tool.source.kind === "builtin" &&
|
|
880
|
+
ctx.parent !== undefined && { sandboxConversationId: ctx.parent.conversationId }),
|
|
881
|
+
};
|
|
882
|
+
// 内置工具可按输入给自己的时限(沙箱 bash 按命令超时加余量);缺省 tools.timeoutMs
|
|
883
|
+
const timeoutMs = tool.timeoutMs?.(input) ?? this.deps.config().tools.timeoutMs;
|
|
884
|
+
try {
|
|
885
|
+
const r = await this.bounded(ctx.signal, (signal) => handler(input, { ...base, signal }), undefined, timeoutMs);
|
|
886
|
+
if (r === "timeout") {
|
|
887
|
+
return { kind: "error", message: timeoutText(tool.key, timeoutMs) };
|
|
888
|
+
}
|
|
889
|
+
if (r === "aborted")
|
|
890
|
+
return { kind: "error", message: RUN_STOPPED_DURING_TEXT };
|
|
891
|
+
if (isInputRequired(r.value)) {
|
|
892
|
+
return { kind: "input_required", form: r.value.form, state: r.value.state ?? null };
|
|
893
|
+
}
|
|
894
|
+
return { kind: "result", result: r.value };
|
|
895
|
+
}
|
|
896
|
+
catch (e) {
|
|
897
|
+
return this.thrown(tool.key, e);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
async executeConnector(tool, source, input, ctx, resume) {
|
|
901
|
+
const { db, registry, breaker, credentials, config } = this.deps;
|
|
902
|
+
const { orgId } = ctx;
|
|
903
|
+
const connectorId = source.connectorId;
|
|
904
|
+
const app = await db
|
|
905
|
+
.selectFrom("connectors")
|
|
906
|
+
.select(["name", "connector", "endpoint", "auth", "status"])
|
|
907
|
+
.where("id", "=", connectorId)
|
|
908
|
+
.where("org_id", "=", orgId)
|
|
909
|
+
.where("deleted_at", "is", null)
|
|
910
|
+
.executeTakeFirst();
|
|
911
|
+
if (app?.status !== "active") {
|
|
912
|
+
return { kind: "error", message: `The app providing ${tool.key} is no longer available.` };
|
|
913
|
+
}
|
|
914
|
+
const connector = registry.findConnector(app.connector);
|
|
915
|
+
if (!connector) {
|
|
916
|
+
return {
|
|
917
|
+
kind: "error",
|
|
918
|
+
message: `The connector for ${tool.key} is not installed; ask an administrator.`,
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
const state = await breaker.check(orgId, connectorId);
|
|
922
|
+
if (state.open) {
|
|
923
|
+
return {
|
|
924
|
+
kind: "error",
|
|
925
|
+
message: breakerOpenMessage(app.name, state.failures, state.retryAfterSeconds),
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
const auth = app.auth;
|
|
929
|
+
let injection;
|
|
930
|
+
/** 本次调用实际注入过的凭据(含 401 后重新读取的),结果打码用。 */
|
|
931
|
+
const injected = [];
|
|
932
|
+
const getAuth = () => {
|
|
933
|
+
injection ??= credentials.injection(orgId, connectorId, auth, ctx.accountId).then((inj) => {
|
|
934
|
+
injected.push(inj);
|
|
935
|
+
return inj;
|
|
936
|
+
});
|
|
937
|
+
return injection;
|
|
938
|
+
};
|
|
939
|
+
// 远端在调用途中向用户要输入(MCP elicitation,见 elicitation.ts):需要问用户时中止本次调用、按 input_required
|
|
940
|
+
// 处理;续跑时用 ctx.resume 的答案答复远端再次发起的同一请求。resources 工具不续跑,一律拒绝。
|
|
941
|
+
const suspend = new AbortController();
|
|
942
|
+
const elicit = new ElicitationSession({
|
|
943
|
+
resume,
|
|
944
|
+
onSuspend: () => suspend.abort(),
|
|
945
|
+
declineAll: source.resources === true,
|
|
946
|
+
});
|
|
947
|
+
// 远端可能把收到的凭据回显在结果 / 错误 / elicitation 表单里:写入事件之前按本次注入的值打码(见 redact.ts)
|
|
948
|
+
const clean = (o) => {
|
|
949
|
+
const redact = redactorFor(injected);
|
|
950
|
+
if (o.kind === "result") {
|
|
951
|
+
return { kind: "result", result: redactDeep(withNotes(o.result, elicit.notes), redact) };
|
|
952
|
+
}
|
|
953
|
+
if (o.kind === "error") {
|
|
954
|
+
return { kind: "error", message: redact([o.message, ...elicit.notes].join(" ")) };
|
|
955
|
+
}
|
|
956
|
+
if (o.kind === "input_required") {
|
|
957
|
+
return {
|
|
958
|
+
kind: "input_required",
|
|
959
|
+
form: redactDeep(o.form, redact),
|
|
960
|
+
state: redactDeep(o.state, redact),
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
return o;
|
|
964
|
+
};
|
|
965
|
+
/** 本次执行要问用户(elicitation):服务可达,算成功;返回打码后的输入请求。 */
|
|
966
|
+
const asked = async () => {
|
|
967
|
+
if (!elicit.pending)
|
|
968
|
+
return undefined;
|
|
969
|
+
await breaker.success(orgId, connectorId);
|
|
970
|
+
return clean({
|
|
971
|
+
kind: "input_required",
|
|
972
|
+
form: elicit.pending.form,
|
|
973
|
+
state: elicit.pending.state,
|
|
974
|
+
});
|
|
975
|
+
};
|
|
976
|
+
const missing = (e) => e.ownership === "personal"
|
|
977
|
+
? {
|
|
978
|
+
kind: "connection_required",
|
|
979
|
+
connectorId,
|
|
980
|
+
connectorName: app.name,
|
|
981
|
+
message: personalMissingText(app.name),
|
|
982
|
+
}
|
|
983
|
+
: { kind: "error", message: orgMissingText(app.name) };
|
|
984
|
+
// 先取一次凭据:缺凭据在打开会话之前就确定,不依赖连接器把 CredentialMissing 原样抛出。
|
|
985
|
+
try {
|
|
986
|
+
await getAuth();
|
|
987
|
+
}
|
|
988
|
+
catch (e) {
|
|
989
|
+
if (e instanceof CredentialMissing)
|
|
990
|
+
return missing(e);
|
|
991
|
+
// 取 token 时授权服务器暂时不可用(OAuth):给模型可执行的说明,不让整轮失败
|
|
992
|
+
if (e instanceof ToolError)
|
|
993
|
+
return { kind: "error", message: e.message };
|
|
994
|
+
throw e;
|
|
995
|
+
}
|
|
996
|
+
let opened;
|
|
997
|
+
try {
|
|
998
|
+
// suspend 也参与竞速:为问用户中止时立即结束,不等连接器自己收尾
|
|
999
|
+
const r = await this.bounded(ctx.signal, async (signal) => {
|
|
1000
|
+
opened = connector.open({
|
|
1001
|
+
endpoint: app.endpoint,
|
|
1002
|
+
auth: getAuth,
|
|
1003
|
+
// OAuth:在刷新锁内强制刷新(别的副本已刷新则直接用新 token);之后丢弃本次调用缓存的注入,
|
|
1004
|
+
// 连接器重试时重新读取。refresh 被拒 → CredentialMissing,经连接器原样抛出,下面转成「需要连接」。
|
|
1005
|
+
onUnauthorized: async () => {
|
|
1006
|
+
const stale = injected.at(-1);
|
|
1007
|
+
injection = undefined;
|
|
1008
|
+
await credentials.onUnauthorized(orgId, connectorId, auth, ctx.accountId, stale);
|
|
1009
|
+
},
|
|
1010
|
+
signal,
|
|
1011
|
+
onElicit: elicit.handle,
|
|
1012
|
+
onToolsChanged: () => {
|
|
1013
|
+
try {
|
|
1014
|
+
this.deps.onToolsChanged?.(orgId, connectorId);
|
|
1015
|
+
}
|
|
1016
|
+
catch (err) {
|
|
1017
|
+
this.deps.log.warn("tools changed notification failed", {
|
|
1018
|
+
orgId,
|
|
1019
|
+
connectorId,
|
|
1020
|
+
err,
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
},
|
|
1024
|
+
});
|
|
1025
|
+
const session = await opened;
|
|
1026
|
+
return source.resources
|
|
1027
|
+
? runResourceTool(session, source.name, input)
|
|
1028
|
+
: // 交回已复核的定义(connector_tools.spec,含 connectorData):连接器按它路由,远端变化须先经漂移复核
|
|
1029
|
+
session.callTool(source.name, input, AbortSignal.any([signal, suspend.signal]), {
|
|
1030
|
+
spec: tool.spec,
|
|
1031
|
+
});
|
|
1032
|
+
}, suspend.signal);
|
|
1033
|
+
if (r === "aborted")
|
|
1034
|
+
return { kind: "error", message: RUN_STOPPED_DURING_TEXT };
|
|
1035
|
+
const ask = await asked();
|
|
1036
|
+
if (ask)
|
|
1037
|
+
return ask;
|
|
1038
|
+
if (r === "timeout") {
|
|
1039
|
+
await breaker.failure(orgId, connectorId);
|
|
1040
|
+
return { kind: "error", message: timeoutText(tool.key, config().tools.timeoutMs) };
|
|
1041
|
+
}
|
|
1042
|
+
// 工具自己报告的错误(isError)说明服务可用,也算成功。
|
|
1043
|
+
await breaker.success(orgId, connectorId);
|
|
1044
|
+
return clean({ kind: "result", result: r.value });
|
|
1045
|
+
}
|
|
1046
|
+
catch (e) {
|
|
1047
|
+
// 为问用户而中止了调用:中止引起的错误不是失败
|
|
1048
|
+
const ask = await asked();
|
|
1049
|
+
if (ask)
|
|
1050
|
+
return ask;
|
|
1051
|
+
if (e instanceof CredentialMissing)
|
|
1052
|
+
return missing(e);
|
|
1053
|
+
// 熔断只计远端不可用(连接 / 传输错误、HTTP 5xx:连接器抛 kind = unavailable 的 ToolError 或原始错误)。
|
|
1054
|
+
// 其余 ToolError(JSON-RPC 业务错误、鉴权失败、重定向等)说明服务可达,与 isError 结果一样算成功。
|
|
1055
|
+
if (e instanceof ToolError && e.kind !== "unavailable")
|
|
1056
|
+
await breaker.success(orgId, connectorId);
|
|
1057
|
+
else
|
|
1058
|
+
await breaker.failure(orgId, connectorId);
|
|
1059
|
+
return clean(this.thrown(tool.key, e));
|
|
1060
|
+
}
|
|
1061
|
+
finally {
|
|
1062
|
+
if (opened)
|
|
1063
|
+
await this.closeSession(opened, tool.key);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
/** 抛出的错误 → 给模型的结果:ToolError 的消息原样(插件约定为英文、可操作),其他只给错误类名。 */
|
|
1067
|
+
thrown(key, e) {
|
|
1068
|
+
if (e instanceof ToolError)
|
|
1069
|
+
return { kind: "error", message: e.message };
|
|
1070
|
+
// 提供工具的插件实例在调用进行中被撤销(停用 / 升级),排空时限到后中止(C17)
|
|
1071
|
+
if (e instanceof ExtensionUnloadedError) {
|
|
1072
|
+
this.deps.log.warn("tool call aborted: plugin unloaded", { toolKey: key, plugin: e.plugin });
|
|
1073
|
+
return { kind: "error", message: PLUGIN_UNLOADED_TOOL_ERROR };
|
|
1074
|
+
}
|
|
1075
|
+
// 不记参数与结果;错误经日志打码
|
|
1076
|
+
this.deps.log.warn("tool call failed", { toolKey: key, err: e });
|
|
1077
|
+
return { kind: "error", message: failedText(key, errName(e)) };
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* 带超时与中止执行 work:合并 run 信号与超时信号传给 work,并与之竞速(work 不响应信号也按时返回)。
|
|
1081
|
+
* 返回 `{ value }`,或 `"timeout"` / `"aborted"`;work 自己的错误照常抛出。
|
|
1082
|
+
*/
|
|
1083
|
+
async bounded(runSignal, work,
|
|
1084
|
+
/** 额外参与竞速的信号(不传给 work):中止时以 `Stopped` 抛出,由调用方判断原因。 */
|
|
1085
|
+
interrupt,
|
|
1086
|
+
/** 时限(毫秒),缺省 `tools.timeoutMs`。 */
|
|
1087
|
+
timeoutMs = this.deps.config().tools.timeoutMs) {
|
|
1088
|
+
if (runSignal.aborted)
|
|
1089
|
+
return "aborted";
|
|
1090
|
+
const timeout = new AbortController();
|
|
1091
|
+
// 本进程发起的调用自己的计时器,结束即清除
|
|
1092
|
+
const timer = setTimeout(() => timeout.abort(), timeoutMs);
|
|
1093
|
+
const signal = AbortSignal.any([runSignal, timeout.signal]);
|
|
1094
|
+
let onAbort = () => { };
|
|
1095
|
+
const race = interrupt ? AbortSignal.any([signal, interrupt]) : signal;
|
|
1096
|
+
const stopped = new Promise((_, reject) => {
|
|
1097
|
+
onAbort = () => reject(new Stopped());
|
|
1098
|
+
race.addEventListener("abort", onAbort, { once: true });
|
|
1099
|
+
});
|
|
1100
|
+
stopped.catch(() => { });
|
|
1101
|
+
try {
|
|
1102
|
+
const p = (async () => work(signal))();
|
|
1103
|
+
p.catch(() => { });
|
|
1104
|
+
return { value: await Promise.race([p, stopped]) };
|
|
1105
|
+
}
|
|
1106
|
+
catch (e) {
|
|
1107
|
+
if (signal.aborted)
|
|
1108
|
+
return runSignal.aborted ? "aborted" : "timeout";
|
|
1109
|
+
throw e;
|
|
1110
|
+
}
|
|
1111
|
+
finally {
|
|
1112
|
+
clearTimeout(timer);
|
|
1113
|
+
race.removeEventListener("abort", onAbort);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
/** 关闭会话(打开仍在进行时等它完成后关闭),最多等 {@link CLOSE_WAIT_MS};失败只记日志。 */
|
|
1117
|
+
async closeSession(opened, key) {
|
|
1118
|
+
const closing = opened
|
|
1119
|
+
.then((s) => s.close())
|
|
1120
|
+
.catch((err) => this.deps.log.warn("connector session close failed", { toolKey: key, err }));
|
|
1121
|
+
let timer;
|
|
1122
|
+
await Promise.race([
|
|
1123
|
+
closing,
|
|
1124
|
+
new Promise((resolve) => {
|
|
1125
|
+
timer = setTimeout(resolve, CLOSE_WAIT_MS);
|
|
1126
|
+
timer.unref();
|
|
1127
|
+
}),
|
|
1128
|
+
]);
|
|
1129
|
+
clearTimeout(timer);
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* 工具结果里的显示摘要(`ToolResult.display`):合法时只取 `{ summary: { zh, en } }`,没有给返回 undefined,
|
|
1133
|
+
* 不合法时记告警并丢弃(结果照常写入)。摘要只进事件日志给界面看,不进模型请求。
|
|
1134
|
+
*/
|
|
1135
|
+
displayOf(result, toolKey) {
|
|
1136
|
+
const d = result?.display;
|
|
1137
|
+
if (d === undefined)
|
|
1138
|
+
return undefined;
|
|
1139
|
+
const problems = validateToolDisplay(d);
|
|
1140
|
+
if (problems.length > 0) {
|
|
1141
|
+
this.deps.log.warn("tool returned an invalid display summary; dropped", {
|
|
1142
|
+
toolKey,
|
|
1143
|
+
problems,
|
|
1144
|
+
});
|
|
1145
|
+
return undefined;
|
|
1146
|
+
}
|
|
1147
|
+
const { zh, en } = d.summary;
|
|
1148
|
+
return { summary: { zh, en } };
|
|
1149
|
+
}
|
|
1150
|
+
/**
|
|
1151
|
+
* A plugin tool's inline card (`ToolResult.card` → `tool_result.card`, display only): `view` must
|
|
1152
|
+
* be an `inline` ui contribution of the same extension and `key` at most 200 characters; anything
|
|
1153
|
+
* else is dropped with a warning and the result is written as usual.
|
|
1154
|
+
*/
|
|
1155
|
+
cardOf(result, plugin, toolKey) {
|
|
1156
|
+
const c = result?.card;
|
|
1157
|
+
if (c === undefined)
|
|
1158
|
+
return undefined;
|
|
1159
|
+
const x = (typeof c === "object" && c !== null ? c : {});
|
|
1160
|
+
const views = this.deps.inlineViews?.(plugin);
|
|
1161
|
+
const keyOk = x.key === undefined || (typeof x.key === "string" && x.key !== "" && x.key.length <= 200);
|
|
1162
|
+
if (typeof x.view !== "string" || !views?.has(x.view) || !keyOk) {
|
|
1163
|
+
this.deps.log.warn("tool returned an inline card that is not an inline view of its extension; dropped", {
|
|
1164
|
+
toolKey,
|
|
1165
|
+
view: typeof x.view === "string" ? x.view.slice(0, 100) : undefined,
|
|
1166
|
+
});
|
|
1167
|
+
return undefined;
|
|
1168
|
+
}
|
|
1169
|
+
return { plugin, view: x.view, ...(x.key !== undefined && { key: x.key }) };
|
|
1170
|
+
}
|
|
1171
|
+
/**
|
|
1172
|
+
* A builtin's structured view (`tool_result.view`, display only): checked against
|
|
1173
|
+
* {@link ToolViewSchema}; an invalid one is dropped with a warning and the result is written as usual.
|
|
1174
|
+
*/
|
|
1175
|
+
viewOf(result, toolKey) {
|
|
1176
|
+
const v = result?.view;
|
|
1177
|
+
if (v === undefined)
|
|
1178
|
+
return undefined;
|
|
1179
|
+
const parsed = ToolViewSchema.safeParse(v);
|
|
1180
|
+
if (!parsed.success) {
|
|
1181
|
+
this.deps.log.warn("builtin tool returned an invalid view; dropped", { toolKey });
|
|
1182
|
+
return undefined;
|
|
1183
|
+
}
|
|
1184
|
+
return parsed.data;
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* 管线第 6 阶段:写 `tool_result`(与 `connection_required` 同批)。传入 `callAudit`(即工具实际执行过;
|
|
1188
|
+
* 耗时 null = 执行过但耗时未知,如续跑时「有开始无结果」的调用)时在同一事务里写审计 `tool.called`。
|
|
1189
|
+
* 审计链(R6):actor = 发起人 → agent(id + 版本)→ 会话 / 运行 / 调用 / 工具(连接器工具带 connectorId)→
|
|
1190
|
+
* 放行依据(审批人 / 授权 / 策略)。不含参数、结果与凭据。
|
|
1191
|
+
*/
|
|
1192
|
+
async writeResult(ctx, callId, toolKey, r, pre = [], callAudit,
|
|
1193
|
+
/** Extra audit entries written in the same transaction (e.g. `hook.denied`). */
|
|
1194
|
+
audits = []) {
|
|
1195
|
+
const events = [
|
|
1196
|
+
...pre,
|
|
1197
|
+
{
|
|
1198
|
+
type: "tool_result",
|
|
1199
|
+
runId: ctx.runId,
|
|
1200
|
+
idemKey: idem.result(ctx.runId, callId),
|
|
1201
|
+
payload: { step: ctx.step, callId, toolKey, ...r },
|
|
1202
|
+
},
|
|
1203
|
+
];
|
|
1204
|
+
if (callAudit === undefined && audits.length === 0) {
|
|
1205
|
+
await this.deps.eventLog.append(ctx.conversationId, events);
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
const written = await this.deps.db.transaction().execute(async (t) => {
|
|
1209
|
+
const out = await this.deps.eventLog.append(ctx.conversationId, events, t);
|
|
1210
|
+
for (const a of audits)
|
|
1211
|
+
await audit(t, a);
|
|
1212
|
+
if (callAudit === undefined)
|
|
1213
|
+
return out;
|
|
1214
|
+
await audit(t, {
|
|
1215
|
+
actorAccountId: ctx.accountId,
|
|
1216
|
+
orgId: ctx.orgId,
|
|
1217
|
+
action: "tool.called",
|
|
1218
|
+
target: ctx.conversationId,
|
|
1219
|
+
detail: {
|
|
1220
|
+
toolKey,
|
|
1221
|
+
isError: r.isError,
|
|
1222
|
+
durationMs: callAudit.durationMs,
|
|
1223
|
+
agentId: ctx.agentId,
|
|
1224
|
+
agentVersion: ctx.agentVersion,
|
|
1225
|
+
conversationId: ctx.conversationId,
|
|
1226
|
+
runId: ctx.runId,
|
|
1227
|
+
callId,
|
|
1228
|
+
...(callAudit.connectorId !== null && { connectorId: callAudit.connectorId }),
|
|
1229
|
+
...callAudit.authorization,
|
|
1230
|
+
},
|
|
1231
|
+
});
|
|
1232
|
+
return out;
|
|
1233
|
+
});
|
|
1234
|
+
await this.deps.eventLog.notify(ctx.conversationId, Math.max(...written.map((e) => e.seq)));
|
|
1235
|
+
}
|
|
1236
|
+
}
|