stageflow 0.23.0 → 0.27.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/README.md +24 -5
- package/dist/a2a/configDiscovery.d.ts +9 -0
- package/dist/a2a/configDiscovery.js +18 -0
- package/dist/a2a/contracts.d.ts +94 -0
- package/dist/a2a/contracts.js +164 -0
- package/dist/a2a/limits.d.ts +14 -0
- package/dist/a2a/limits.js +29 -0
- package/dist/a2a/registry.d.ts +46 -0
- package/dist/a2a/registry.js +178 -0
- package/dist/a2a/server.d.ts +36 -0
- package/dist/a2a/server.js +344 -0
- package/dist/a2a/service.d.ts +97 -0
- package/dist/a2a/service.js +545 -0
- package/dist/a2a/store.d.ts +109 -0
- package/dist/a2a/store.js +202 -0
- package/dist/agent/activity.d.ts +3 -0
- package/dist/agent/bootProviderConfig.d.ts +21 -0
- package/dist/agent/bootProviderConfig.js +74 -0
- package/dist/agent/piAdapter.d.ts +9 -1
- package/dist/agent/piAdapter.js +58 -3
- package/dist/agent/piIsolatedMcp.d.ts +2 -0
- package/dist/agent/piIsolatedMcp.js +10 -2
- package/dist/agent/port.d.ts +6 -0
- package/dist/agent/streamLogRedact.d.ts +1 -1
- package/dist/agent/streamLogRedact.js +4 -11
- package/dist/cli/a2aCommand.d.ts +8 -0
- package/dist/cli/a2aCommand.js +131 -0
- package/dist/cli/artifactCommand.js +9 -16
- package/dist/cli/backupCommand.d.ts +9 -0
- package/dist/cli/backupCommand.js +111 -0
- package/dist/cli/debugRunCommand.d.ts +10 -0
- package/dist/cli/debugRunCommand.js +109 -0
- package/dist/cli/doctorCommand.d.ts +32 -0
- package/dist/cli/doctorCommand.js +453 -0
- package/dist/cli/envelopeCommand.js +9 -2
- package/dist/cli/exportAllCommand.d.ts +55 -0
- package/dist/cli/exportAllCommand.js +218 -0
- package/dist/cli/exportRunCommand.js +23 -21
- package/dist/cli/hostClient.d.ts +21 -3
- package/dist/cli/hostClient.js +93 -12
- package/dist/cli/initTemplates.d.ts +1 -1
- package/dist/cli/initTemplates.js +8 -1
- package/dist/cli/resolveSafeOutPath.d.ts +2 -0
- package/dist/cli/resolveSafeOutPath.js +40 -0
- package/dist/cli/restoreCommand.d.ts +10 -0
- package/dist/cli/restoreCommand.js +90 -0
- package/dist/cli/runCommand.d.ts +3 -2
- package/dist/cli/runCommand.js +93 -11
- package/dist/cli/runOutput.d.ts +4 -0
- package/dist/cli/runOutput.js +33 -7
- package/dist/cli/runStageCommand.d.ts +34 -0
- package/dist/cli/runStageCommand.js +426 -0
- package/dist/cli/runsCommand.d.ts +1 -1
- package/dist/cli/runsCommand.js +162 -8
- package/dist/cli.js +190 -30
- package/dist/config/catalogRelativePath.d.ts +62 -0
- package/dist/config/catalogRelativePath.js +118 -0
- package/dist/config/configOrigin.d.ts +27 -0
- package/dist/config/configOrigin.js +59 -0
- package/dist/config/hostConfig.d.ts +55 -0
- package/dist/config/hostConfig.js +333 -0
- package/dist/config/listSkills.d.ts +26 -0
- package/dist/config/listSkills.js +190 -3
- package/dist/config/loadOutcome.d.ts +1 -1
- package/dist/config/loadPipeline.js +23 -2
- package/dist/config/loadStage.js +18 -0
- package/dist/config/loadTask.d.ts +2 -0
- package/dist/config/loadTask.js +57 -18
- package/dist/config/materializeStageModels.js +5 -1
- package/dist/config/mergePipelineIncludes.d.ts +3 -0
- package/dist/config/mergePipelineIncludes.js +19 -0
- package/dist/config/migrateYaml.js +6 -11
- package/dist/config/multiProjectCatalog.d.ts +30 -0
- package/dist/config/multiProjectCatalog.js +142 -0
- package/dist/config/normalizePipelineStageEntry.js +20 -1
- package/dist/config/pipelineStageKeys.js +2 -0
- package/dist/config/resolveCatalogRoots.d.ts +27 -0
- package/dist/config/resolveCatalogRoots.js +76 -0
- package/dist/config/resolveModel.d.ts +7 -1
- package/dist/config/resolveModel.js +11 -1
- package/dist/config/resolveStageMcpServers.d.ts +9 -1
- package/dist/config/resolveStageMcpServers.js +36 -9
- package/dist/config/secretFromEnvOrFile.d.ts +9 -0
- package/dist/config/secretFromEnvOrFile.js +34 -0
- package/dist/config/seededCatalog.d.ts +11 -0
- package/dist/config/seededCatalog.js +29 -0
- package/dist/config/toolRequires.d.ts +24 -0
- package/dist/config/toolRequires.js +115 -0
- package/dist/config/validateCatalog.d.ts +5 -1
- package/dist/config/validateCatalog.js +140 -1
- package/dist/diagnostics/checks.d.ts +39 -0
- package/dist/diagnostics/checks.js +135 -0
- package/dist/errors/codes.d.ts +14 -0
- package/dist/errors/codes.js +59 -0
- package/dist/git/cache.d.ts +14 -0
- package/dist/git/cache.js +150 -0
- package/dist/git/credentials.d.ts +9 -0
- package/dist/git/credentials.js +88 -0
- package/dist/git/errors.d.ts +20 -0
- package/dist/git/errors.js +94 -0
- package/dist/git/exec.d.ts +16 -0
- package/dist/git/exec.js +135 -0
- package/dist/git/operations.d.ts +29 -0
- package/dist/git/operations.js +153 -0
- package/dist/git/version.d.ts +1 -0
- package/dist/git/version.js +10 -0
- package/dist/logging/audit.d.ts +16 -0
- package/dist/logging/audit.js +19 -0
- package/dist/logging/commonWords.d.ts +2 -0
- package/dist/logging/commonWords.js +13 -0
- package/dist/logging/logger.d.ts +49 -0
- package/dist/logging/logger.js +193 -0
- package/dist/logging/namedSecrets.d.ts +6 -0
- package/dist/logging/namedSecrets.js +58 -0
- package/dist/logging/redact.d.ts +13 -0
- package/dist/logging/redact.js +88 -0
- package/dist/mcp/catalogTools.js +591 -76
- package/dist/mcp/checkoutTools.d.ts +52 -0
- package/dist/mcp/checkoutTools.js +332 -0
- package/dist/mcp/controlTools.js +179 -5
- package/dist/mcp/deps.d.ts +1 -0
- package/dist/mcp/getStartedTool.d.ts +3 -0
- package/dist/mcp/getStartedTool.js +62 -0
- package/dist/mcp/readArtifact.d.ts +11 -0
- package/dist/mcp/readArtifact.js +46 -30
- package/dist/mcp/server.js +1 -0
- package/dist/mcp/tools.js +4 -0
- package/dist/net/proxy.d.ts +14 -0
- package/dist/net/proxy.js +78 -0
- package/dist/package-meta.d.ts +2 -1
- package/dist/package-meta.js +2 -1
- package/dist/preflight/bash.d.ts +8 -0
- package/dist/preflight/bash.js +34 -0
- package/dist/preflight/claudeRoot.d.ts +11 -0
- package/dist/preflight/claudeRoot.js +23 -0
- package/dist/preflight/mcpCommands.d.ts +8 -0
- package/dist/preflight/mcpCommands.js +36 -0
- package/dist/preflight/pipelinePreflight.d.ts +40 -0
- package/dist/preflight/pipelinePreflight.js +192 -0
- package/dist/preflight/tls.d.ts +6 -0
- package/dist/preflight/tls.js +18 -0
- package/dist/preflight/toolchain.d.ts +45 -0
- package/dist/preflight/toolchain.js +219 -0
- package/dist/project/findProjectRoot.js +3 -4
- package/dist/project/globalHome.d.ts +7 -0
- package/dist/project/globalHome.js +63 -2
- package/dist/project/resolveProjectContext.js +2 -2
- package/dist/project/resolveStageflowContext.js +2 -2
- package/dist/projection/projectRun.d.ts +2 -1
- package/dist/projection/projectRun.js +1 -0
- package/dist/runstore/assertTmpdir.d.ts +1 -0
- package/dist/runstore/assertTmpdir.js +21 -0
- package/dist/runstore/backup.d.ts +52 -0
- package/dist/runstore/backup.js +292 -0
- package/dist/runstore/createStore.d.ts +27 -0
- package/dist/runstore/createStore.js +75 -7
- package/dist/runstore/debugBundle.d.ts +60 -0
- package/dist/runstore/debugBundle.js +143 -0
- package/dist/runstore/diskUsage.d.ts +50 -0
- package/dist/runstore/diskUsage.js +214 -0
- package/dist/runstore/exportRunPayload.d.ts +8 -0
- package/dist/runstore/exportRunPayload.js +23 -0
- package/dist/runstore/normalizeCatalogPath.d.ts +1 -0
- package/dist/runstore/normalizeCatalogPath.js +10 -0
- package/dist/runstore/paths.d.ts +5 -0
- package/dist/runstore/paths.js +145 -1
- package/dist/runstore/port.d.ts +119 -8
- package/dist/runstore/port.js +8 -1
- package/dist/runstore/restore.d.ts +81 -0
- package/dist/runstore/restore.js +355 -0
- package/dist/runstore/retention.d.ts +27 -0
- package/dist/runstore/retention.js +74 -0
- package/dist/runstore/runManifest.d.ts +146 -0
- package/dist/runstore/runManifest.js +288 -0
- package/dist/runstore/runProjection.d.ts +3 -1
- package/dist/runstore/runProjection.js +63 -1
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +37 -3
- package/dist/runstore/sqlite/SqliteRunStore.js +401 -227
- package/dist/runstore/sqlite/applyStorePragmas.d.ts +39 -0
- package/dist/runstore/sqlite/applyStorePragmas.js +145 -0
- package/dist/runstore/sqlite/migrations/001-baseline.d.ts +7 -0
- package/dist/runstore/sqlite/migrations/001-baseline.js +10 -0
- package/dist/runstore/sqlite/migrations/002-repository-binding.d.ts +7 -0
- package/dist/runstore/sqlite/migrations/002-repository-binding.js +29 -0
- package/dist/runstore/sqlite/migrations/003-run-lifecycle.d.ts +7 -0
- package/dist/runstore/sqlite/migrations/003-run-lifecycle.js +28 -0
- package/dist/runstore/sqlite/migrations/004-auto-resume-count.d.ts +7 -0
- package/dist/runstore/sqlite/migrations/004-auto-resume-count.js +19 -0
- package/dist/runstore/sqlite/migrations/005-config-origins.d.ts +7 -0
- package/dist/runstore/sqlite/migrations/005-config-origins.js +19 -0
- package/dist/runstore/sqlite/migrations/006-pipeline-body-and-caller.d.ts +7 -0
- package/dist/runstore/sqlite/migrations/006-pipeline-body-and-caller.js +28 -0
- package/dist/runstore/sqlite/migrations/007-projects-registry.d.ts +7 -0
- package/dist/runstore/sqlite/migrations/007-projects-registry.js +31 -0
- package/dist/runstore/sqlite/migrations/index.d.ts +21 -0
- package/dist/runstore/sqlite/migrations/index.js +72 -0
- package/dist/runstore/sqlite/schema.d.ts +20 -1
- package/dist/runstore/sqlite/schema.js +283 -1
- package/dist/runstore/sqlite/storeOpenError.d.ts +6 -0
- package/dist/runstore/sqlite/storeOpenError.js +11 -0
- package/dist/runstore/sqlite/storeSchemaError.d.ts +5 -0
- package/dist/runstore/sqlite/storeSchemaError.js +8 -0
- package/dist/runstore/stageExecution.js +3 -0
- package/dist/runstore/storeFilesystem.d.ts +32 -0
- package/dist/runstore/storeFilesystem.js +148 -0
- package/dist/runstore/submission.d.ts +11 -0
- package/dist/runstore/submission.js +7 -0
- package/dist/runstore/trackProjection.js +2 -0
- package/dist/runstore/workspaceLayout.d.ts +13 -0
- package/dist/runstore/workspaceLayout.js +76 -0
- package/dist/runtime/completionCheckRunner.d.ts +3 -0
- package/dist/runtime/completionCheckRunner.js +45 -7
- package/dist/runtime/containerLimits.d.ts +16 -0
- package/dist/runtime/containerLimits.js +84 -0
- package/dist/runtime/credentialMaterialisation.d.ts +28 -0
- package/dist/runtime/credentialMaterialisation.js +128 -0
- package/dist/runtime/gitCheckoutCapability.js +7 -13
- package/dist/runtime/pipelineRunner.d.ts +38 -1
- package/dist/runtime/pipelineRunner.js +179 -15
- package/dist/runtime/pipelineScheduler.d.ts +15 -0
- package/dist/runtime/pipelineScheduler.js +167 -42
- package/dist/runtime/reloadRunCatalog.js +12 -1
- package/dist/runtime/repositoryMaterialize.d.ts +47 -0
- package/dist/runtime/repositoryMaterialize.js +235 -0
- package/dist/runtime/resolveEnvelopeRef.d.ts +30 -0
- package/dist/runtime/resolveEnvelopeRef.js +79 -0
- package/dist/runtime/resumeTimedOut.d.ts +1 -1
- package/dist/runtime/resumeTimedOut.js +5 -2
- package/dist/runtime/runChangeBus.js +9 -0
- package/dist/runtime/runDeletion.d.ts +7 -0
- package/dist/runtime/runDeletion.js +37 -0
- package/dist/runtime/runManager.d.ts +179 -4
- package/dist/runtime/runManager.js +1644 -83
- package/dist/runtime/runRetentionSweep.d.ts +31 -0
- package/dist/runtime/runRetentionSweep.js +328 -0
- package/dist/runtime/runRetryCoordinator.d.ts +2 -0
- package/dist/runtime/runRetryCoordinator.js +19 -2
- package/dist/runtime/runSkills.d.ts +40 -0
- package/dist/runtime/runSkills.js +173 -0
- package/dist/runtime/stageAttemptBootstrap.d.ts +5 -1
- package/dist/runtime/stageAttemptBootstrap.js +195 -24
- package/dist/runtime/stageCacheEnv.d.ts +6 -0
- package/dist/runtime/stageCacheEnv.js +34 -0
- package/dist/runtime/stageConcurrency.d.ts +2 -1
- package/dist/runtime/stageConcurrency.js +4 -2
- package/dist/runtime/stageEnvironment.d.ts +38 -0
- package/dist/runtime/stageEnvironment.js +193 -0
- package/dist/runtime/stageProcessLauncher.d.ts +18 -0
- package/dist/runtime/stageProcessLauncher.js +194 -38
- package/dist/runtime/stageRecovery.d.ts +3 -1
- package/dist/runtime/stageRecovery.js +19 -4
- package/dist/runtime/stageRoots.d.ts +38 -1
- package/dist/runtime/stageRoots.js +118 -0
- package/dist/runtime/stageRunner.d.ts +1 -0
- package/dist/runtime/stageRunner.js +3 -1
- package/dist/runtime/stageSecretDecl.d.ts +9 -0
- package/dist/runtime/stageSecretDecl.js +102 -0
- package/dist/runtime/stageSecrets.d.ts +40 -0
- package/dist/runtime/stageSecrets.js +181 -0
- package/dist/runtime/stageWorker.d.ts +3 -2
- package/dist/runtime/stageWorker.js +58 -10
- package/dist/runtime/stageWorkerProtocol.d.ts +5 -0
- package/dist/runtime/stageWorkerProtocol.js +8 -0
- package/dist/runtime/startPayload.d.ts +32 -0
- package/dist/runtime/startPayload.js +59 -0
- package/dist/runtime/taskInput.js +14 -5
- package/dist/runtime/verifiedStageExecution.d.ts +1 -0
- package/dist/runtime/verifiedStageExecution.js +2 -1
- package/dist/runtime/workspaceBinding.d.ts +20 -0
- package/dist/runtime/workspaceBinding.js +60 -0
- package/dist/server/allowedHosts.d.ts +19 -0
- package/dist/server/allowedHosts.js +179 -0
- package/dist/server/bootstrap.d.ts +38 -2
- package/dist/server/bootstrap.js +219 -4
- package/dist/server/controlToken.d.ts +47 -0
- package/dist/server/controlToken.js +241 -0
- package/dist/server/createHttpHost.d.ts +11 -2
- package/dist/server/createHttpHost.js +109 -10
- package/dist/server/ensureGlobalService.d.ts +5 -1
- package/dist/server/ensureGlobalService.js +35 -1
- package/dist/server/healthSurfaces.d.ts +6 -0
- package/dist/server/healthSurfaces.js +98 -0
- package/dist/server/http.d.ts +12 -1
- package/dist/server/http.js +1085 -485
- package/dist/server/listenHost.d.ts +5 -0
- package/dist/server/listenHost.js +56 -0
- package/dist/server/mcpHost.d.ts +10 -1
- package/dist/server/mcpHost.js +36 -6
- package/dist/server/operatorResults.d.ts +2 -2
- package/dist/server/operatorResults.js +40 -29
- package/dist/server/requestAuthContext.d.ts +12 -0
- package/dist/server/requestAuthContext.js +21 -0
- package/dist/server/shutdown.d.ts +69 -0
- package/dist/server/shutdown.js +219 -0
- package/dist/types/pipeline.d.ts +7 -0
- package/dist/types/stage.d.ts +8 -0
- package/dist/types/task.d.ts +8 -0
- package/dist/ui/assets/index-B6hlSXXn.js +120 -0
- package/dist/ui/index.html +1 -1
- package/examples/README.md +98 -0
- package/examples/archify-on-pr/README.md +213 -0
- package/examples/archify-on-pr/archify-on-pr.pipeline.yaml +19 -0
- package/examples/archify-on-pr/archify-on-pr.task.yaml +19 -0
- package/examples/archify-on-pr/author-diagrams.yaml +106 -0
- package/examples/archify-on-pr/detect-changes.yaml +89 -0
- package/examples/ci-validate/README.md +35 -0
- package/examples/ci-validate/check.yaml +13 -0
- package/examples/ci-validate/ci-demo.pipeline.yaml +4 -0
- package/examples/ci-validate/validate.sh +5 -0
- package/examples/compliance-review-demo/README.md +125 -0
- package/examples/compliance-review-demo/compliance-review-demo.pipeline.yaml +29 -0
- package/examples/compliance-review-demo/connect-account.yaml +47 -0
- package/examples/compliance-review-demo/map-to-control.yaml +123 -0
- package/examples/compliance-review-demo/poll-bucket-config.yaml +103 -0
- package/examples/compliance-review-demo/publish-report.yaml +79 -0
- package/examples/compliance-review-demo/review-verdict.yaml +122 -0
- package/examples/compliance-review-demo/run-compliance-check.yaml +96 -0
- package/examples/compliance-review-demo/store-evidence.yaml +143 -0
- package/examples/conditional-fork/README.md +89 -0
- package/examples/conditional-fork/branch-a.yaml +13 -0
- package/examples/conditional-fork/branch-b.yaml +13 -0
- package/examples/conditional-fork/decide.yaml +35 -0
- package/examples/conditional-fork/fork-demo.pipeline.yaml +20 -0
- package/examples/conditional-fork/fork-demo.task.yaml +8 -0
- package/examples/context7-mcp/README.md +42 -0
- package/examples/context7-mcp/context7-mcp.pipeline.yaml +15 -0
- package/examples/context7-mcp/context7-mcp.task.yaml +4 -0
- package/examples/context7-mcp/fetch-docs.yaml +52 -0
- package/examples/context7-mcp/resolve-library.yaml +31 -0
- package/examples/context7-mcp/write-brief.yaml +52 -0
- package/examples/feature-loop/README.md +104 -0
- package/examples/feature-loop/address-feedback.yaml +148 -0
- package/examples/feature-loop/align.yaml +116 -0
- package/examples/feature-loop/decompose.yaml +86 -0
- package/examples/feature-loop/default-model-hierarchy.task.yaml +58 -0
- package/examples/feature-loop/feature-loop.pipeline.yaml +140 -0
- package/examples/feature-loop/feature-loop.task.yaml +25 -0
- package/examples/feature-loop/implement.yaml +128 -0
- package/examples/feature-loop/plan.yaml +111 -0
- package/examples/feature-loop/publish.yaml +167 -0
- package/examples/feature-loop/review.yaml +146 -0
- package/examples/feature-loop/verify.yaml +106 -0
- package/examples/feedback-loop/README.md +72 -0
- package/examples/feedback-loop/VERIFY.md +208 -0
- package/examples/feedback-loop/continue-only.task.yaml +4 -0
- package/examples/feedback-loop/exhaust-require-continue.task.yaml +6 -0
- package/examples/feedback-loop/exhaust-wait-human.task.yaml +11 -0
- package/examples/feedback-loop/feedback-loop-wait-human.pipeline.yaml +23 -0
- package/examples/feedback-loop/feedback-loop.pipeline.yaml +23 -0
- package/examples/feedback-loop/feedback-loop.task.yaml +4 -0
- package/examples/feedback-loop/implement.yaml +14 -0
- package/examples/feedback-loop/plan.yaml +14 -0
- package/examples/feedback-loop/review.yaml +15 -0
- package/examples/feedback-loop/send-back-once.task.yaml +6 -0
- package/examples/feedback-loop/submit.yaml +12 -0
- package/examples/generic-fan-in/README.md +151 -0
- package/examples/generic-fan-in/clarify.yaml +18 -0
- package/examples/generic-fan-in/generic-fan-in-accepted.pipeline.yaml +19 -0
- package/examples/generic-fan-in/generic-fan-in-accepted.task.yaml +8 -0
- package/examples/generic-fan-in/generic-fan-in.pipeline.yaml +18 -0
- package/examples/generic-fan-in/generic-fan-in.task.yaml +9 -0
- package/examples/generic-fan-in/research.yaml +45 -0
- package/examples/generic-fan-in/synthesize.yaml +66 -0
- package/examples/generic-fan-in/validation.yaml +45 -0
- package/examples/github-release/README.md +78 -0
- package/examples/github-release/draft-release-notes.yaml +64 -0
- package/examples/github-release/github-release.pipeline.yaml +11 -0
- package/examples/github-release/github-release.task.yaml +13 -0
- package/examples/github-release/publish-github-release.yaml +64 -0
- package/examples/hello-world/README.md +28 -0
- package/examples/hello-world/hello.pipeline.yaml +4 -0
- package/examples/hello-world/my-task.task.yaml +6 -0
- package/examples/hello-world/research.yaml +18 -0
- package/examples/mcp-hitl-tour/README.md +113 -0
- package/examples/mcp-hitl-tour/draft-angle.yaml +33 -0
- package/examples/mcp-hitl-tour/draft-short.yaml +32 -0
- package/examples/mcp-hitl-tour/emit-angles.yaml +45 -0
- package/examples/mcp-hitl-tour/gather.yaml +31 -0
- package/examples/mcp-hitl-tour/intake.yaml +62 -0
- package/examples/mcp-hitl-tour/mcp-hitl-tour-exhaust.task.yaml +13 -0
- package/examples/mcp-hitl-tour/mcp-hitl-tour.pipeline.yaml +54 -0
- package/examples/mcp-hitl-tour/mcp-hitl-tour.task.yaml +16 -0
- package/examples/mcp-hitl-tour/polish.yaml +33 -0
- package/examples/mcp-hitl-tour/review.yaml +17 -0
- package/examples/mcp-hitl-tour/ship.yaml +25 -0
- package/examples/oss-issue-contribution/README.md +218 -0
- package/examples/oss-issue-contribution/oss-address-review-feedback.yaml +117 -0
- package/examples/oss-issue-contribution/oss-approve-contribution.yaml +181 -0
- package/examples/oss-issue-contribution/oss-approve-plan.yaml +111 -0
- package/examples/oss-issue-contribution/oss-explain-fix.yaml +108 -0
- package/examples/oss-issue-contribution/oss-explain-issue.yaml +114 -0
- package/examples/oss-issue-contribution/oss-implement-source-fix.yaml +171 -0
- package/examples/oss-issue-contribution/oss-investigate-area.yaml +116 -0
- package/examples/oss-issue-contribution/oss-issue-contribution.pipeline.yaml +125 -0
- package/examples/oss-issue-contribution/oss-issue-intake.yaml +106 -0
- package/examples/oss-issue-contribution/oss-plan-investigation.yaml +112 -0
- package/examples/oss-issue-contribution/oss-plan-review.yaml +109 -0
- package/examples/oss-issue-contribution/oss-reproduce-issue.yaml +140 -0
- package/examples/oss-issue-contribution/oss-review-change.yaml +107 -0
- package/examples/oss-issue-contribution/oss-verify-fix.yaml +130 -0
- package/examples/oss-issue-contribution/oss-write-regression-test.yaml +135 -0
- package/examples/oss-issue-contribution/pi-7629.task.yaml +79 -0
- package/examples/oss-issue-contribution/pi-8919.task.yaml +75 -0
- package/examples/oss-issue-contribution/pydantic-ai-7971.task.yaml +53 -0
- package/examples/oss-quick-fix/README.md +65 -0
- package/examples/oss-quick-fix/implement-fix.yaml +84 -0
- package/examples/oss-quick-fix/oss-quick-fix.pipeline.yaml +35 -0
- package/examples/oss-quick-fix/oss-quick-fix.task.yaml +19 -0
- package/examples/oss-quick-fix/ship-fix.yaml +81 -0
- package/examples/oss-quick-fix/understand-issue.yaml +67 -0
- package/examples/oss-quick-fix/verify-fix.yaml +73 -0
- package/examples/plan-review/README.md +37 -0
- package/examples/plan-review/plan-review-followup.yaml +13 -0
- package/examples/plan-review/plan-review.pipeline.yaml +9 -0
- package/examples/plan-review/plan-review.task.yaml +9 -0
- package/examples/plan-review/plan-review.yaml +25 -0
- package/examples/playwright-mcp/README.md +39 -0
- package/examples/playwright-mcp/playwright-mcp.pipeline.yaml +5 -0
- package/examples/playwright-mcp/playwright-mcp.task.yaml +4 -0
- package/examples/playwright-mcp/screenshot.yaml +40 -0
- package/examples/retry-tour/README.md +75 -0
- package/examples/retry-tour/retry-tour.pipeline.yaml +122 -0
- package/examples/retry-tour/retry-tour.task.yaml +9 -0
- package/examples/route-if-tour/README.md +202 -0
- package/examples/route-if-tour/route-if-tour.pipeline.yaml +950 -0
- package/examples/route-if-tour/route-if-tour.task.yaml +4 -0
- package/examples/route-wiring-smoke-test/01-core-routing.pipeline.yaml +168 -0
- package/examples/route-wiring-smoke-test/02-on-gating.pipeline.yaml +44 -0
- package/examples/route-wiring-smoke-test/03-two-entry-points.pipeline.yaml +28 -0
- package/examples/route-wiring-smoke-test/04-entry-false-equivalent-to-omitted.pipeline.yaml +27 -0
- package/examples/route-wiring-smoke-test/05-fork-choice.pipeline.yaml +110 -0
- package/examples/route-wiring-smoke-test/06-fan-out-fan-in.pipeline.yaml +82 -0
- package/examples/route-wiring-smoke-test/07-loop-human-decision.pipeline.yaml +82 -0
- package/examples/route-wiring-smoke-test/08-skip-fallback-and-multi-on-gate.pipeline.yaml +77 -0
- package/examples/route-wiring-smoke-test/09-multi-loop-targets.pipeline.yaml +102 -0
- package/examples/route-wiring-smoke-test/10-uses-dialect-fork.pipeline.yaml +23 -0
- package/examples/route-wiring-smoke-test/11-sequential-io-handoff.pipeline.yaml +37 -0
- package/examples/route-wiring-smoke-test/12-complex-io-schemas.pipeline.yaml +138 -0
- package/examples/route-wiring-smoke-test/13-ref-io-handoff.pipeline.yaml +84 -0
- package/examples/route-wiring-smoke-test/14-if-eq-gating.pipeline.yaml +50 -0
- package/examples/route-wiring-smoke-test/15-if-all-gated.pipeline.yaml +37 -0
- package/examples/route-wiring-smoke-test/16-if-composition.pipeline.yaml +65 -0
- package/examples/route-wiring-smoke-test/17-if-join.pipeline.yaml +80 -0
- package/examples/route-wiring-smoke-test/README.md +377 -0
- package/examples/route-wiring-smoke-test/rejected/01-reject-legacy-needs.pipeline.yaml +26 -0
- package/examples/route-wiring-smoke-test/rejected/02-reject-legacy-fork.pipeline.yaml +25 -0
- package/examples/route-wiring-smoke-test/rejected/03-reject-legacy-feedback-loop.pipeline.yaml +30 -0
- package/examples/route-wiring-smoke-test/rejected/04-reject-missing-entry.pipeline.yaml +24 -0
- package/examples/route-wiring-smoke-test/rejected/05-reject-unreachable.pipeline.yaml +35 -0
- package/examples/route-wiring-smoke-test/rejected/06-reject-allow-none-without-route-select.pipeline.yaml +37 -0
- package/examples/route-wiring-smoke-test/rejected/07-reject-cycle.pipeline.yaml +27 -0
- package/examples/route-wiring-smoke-test/rejected/08-reject-loop-non-ancestor.pipeline.yaml +42 -0
- package/examples/route-wiring-smoke-test/rejected/09-reject-route-select-leaf.pipeline.yaml +26 -0
- package/examples/route-wiring-smoke-test/rejected/12-reject-loop-replay-unsafe.pipeline.yaml +55 -0
- package/examples/route-wiring-smoke-test/rejected/13-reject-multiple-loop-entries.pipeline.yaml +51 -0
- package/examples/route-wiring-smoke-test/rejected/14-reject-duplicate-stage-id.pipeline.yaml +35 -0
- package/examples/route-wiring-smoke-test/rejected/15-reject-unknown-route-target.pipeline.yaml +15 -0
- package/examples/route-wiring-smoke-test/rejected/16-reject-malformed-route-item.pipeline.yaml +15 -0
- package/examples/route-wiring-smoke-test/rejected/17-reject-io-incompatible.pipeline.yaml +33 -0
- package/examples/route-wiring-smoke-test/rejected/18-reject-nested-io.pipeline.yaml +51 -0
- package/examples/route-wiring-smoke-test/rejected/19-reject-array-item-io.pipeline.yaml +55 -0
- package/examples/route-wiring-smoke-test/rejected/20-reject-closed-io.pipeline.yaml +36 -0
- package/examples/route-wiring-smoke-test/rejected/21-reject-ref-io.pipeline.yaml +42 -0
- package/examples/route-wiring-smoke-test/rejected/22-reject-if-unknown-field.pipeline.yaml +33 -0
- package/examples/route-wiring-smoke-test/rejected/23-reject-if-optional-field.pipeline.yaml +32 -0
- package/examples/route-wiring-smoke-test/rejected/24-reject-if-empty-all.pipeline.yaml +31 -0
- package/examples/route-wiring-smoke-test/rejected/25-reject-if-gt-on-string.pipeline.yaml +33 -0
- package/examples/route-wiring-smoke-test/rejected/26-reject-if-optional-nested.pipeline.yaml +36 -0
- package/examples/route-wiring-smoke-test/rejected/27-reject-if-on-loop.pipeline.yaml +63 -0
- package/examples/route-wiring-smoke-test/rejected/29-reject-if-on-failed.pipeline.yaml +46 -0
- package/examples/route-wiring-smoke-test/smoke-test.task.yaml +4 -0
- package/examples/route-wiring-smoke-test/stages/branch-a.yaml +12 -0
- package/examples/route-wiring-smoke-test/stages/branch-b.yaml +12 -0
- package/examples/route-wiring-smoke-test/stages/decide.yaml +12 -0
- package/examples/route-wiring-smoke-test/stages/finish.yaml +12 -0
- package/examples/ship-feature/README.md +190 -0
- package/examples/ship-feature/feat-address-feedback.yaml +119 -0
- package/examples/ship-feature/feat-implement.yaml +180 -0
- package/examples/ship-feature/feat-plan.yaml +120 -0
- package/examples/ship-feature/feat-review.yaml +109 -0
- package/examples/ship-feature/feat-ship.yaml +203 -0
- package/examples/ship-feature/ship-feature.pipeline.yaml +44 -0
- package/examples/ship-feature/ship-feature.task.yaml +78 -0
- package/examples/stage-mcp/README.md +144 -0
- package/examples/stage-mcp/echo-mcp.mjs +20 -0
- package/examples/stage-mcp/stage-mcp-dead.pipeline.yaml +5 -0
- package/examples/stage-mcp/stage-mcp-dead.task.yaml +3 -0
- package/examples/stage-mcp/stage-mcp-missing-var.pipeline.yaml +5 -0
- package/examples/stage-mcp/stage-mcp-missing-var.task.yaml +3 -0
- package/examples/stage-mcp/stage-mcp.pipeline.yaml +5 -0
- package/examples/stage-mcp/stage-mcp.task.yaml +4 -0
- package/examples/stage-mcp/use-echo.yaml +20 -0
- package/examples/verify-tour/README.md +80 -0
- package/examples/verify-tour/verify-tour.pipeline.yaml +162 -0
- package/examples/verify-tour/verify-tour.task.yaml +11 -0
- package/package.json +6 -1
- package/skills/stageflow/SKILL.md +6 -6
- package/skills/stageflow/references/control-surface.md +11 -5
- package/skills/stageflow/scripts/detect-host.mjs +14 -1
- package/skills/stageflow/scripts/detect-host.test.mjs +29 -1
- package/skills/stageflow-author/SKILL.md +4 -3
- package/skills/stageflow-author/references/catalog-mapping.md +16 -0
- package/skills/stageflow-author/references/catalog-write-conventions.md +1 -1
- package/skills/stageflow-run/SKILL.md +20 -12
- package/skills/stageflow-run/references/mcp-call.md +9 -1
- package/skills/stageflow-run/references/task-and-pipeline-selection.md +16 -5
- package/skills/stageflow-run/scripts/mcp-call.mjs +46 -6
- package/skills/stageflow-run/scripts/mcp-call.test.mjs +64 -0
- package/skills/stageflow-session-capture/references/catalog-authoring.md +2 -0
- package/skills/stageflow-setup/SKILL.md +40 -8
- package/dist/ui/assets/index-Da5EHU6M.js +0 -119
package/README.md
CHANGED
|
@@ -35,9 +35,11 @@ The same pipeline runs three ways without rewriting anything:
|
|
|
35
35
|
- **HITL gates** — operator questions in the console; CI exits `2` when a run is waiting
|
|
36
36
|
- **Operator console** — triage runs, connect providers, answer gates, inspect transcripts at `http://127.0.0.1:3847`
|
|
37
37
|
- **MCP endpoint** — Streamable HTTP at `/mcp` when `sf ui` or `sf mcp` is running
|
|
38
|
+
- **Standalone stage execution** — run a single stage directly with `run_stage` (MCP tool, `sf run-stage` CLI, or the A2A `run_stage` operation), no pipeline file required; chain calls with `envelope_ref` (one or many prior results) instead of a pipeline DAG
|
|
38
39
|
- **CI / headless** — `sf validate --strict --json`, `sf run --json` with exit codes `0` / `1` / `2`
|
|
39
40
|
- **Parallel stages** — pipeline DAG with fan-out, join, and Clone Chains (one Clone Instance per Clone Array element; see [YAML catalog](docs/yaml-catalog.md#clone-chain))
|
|
40
|
-
- **
|
|
41
|
+
- **Definition-time graph view** — `sf graph --pipeline <path>` prints how a pipeline is wired before any run (terminal diagram or `--json`)
|
|
42
|
+
- **SQLite run store** — global `~/.stageflow/.stageflow/` state plus per-run workspaces, shared across every project since Stageflow became a single auto-starting service
|
|
41
43
|
|
|
42
44
|
## Architecture at a glance
|
|
43
45
|
|
|
@@ -96,6 +98,8 @@ sf run --pipeline pipelines/hello.pipeline.yaml --task tasks/hello.task.yaml
|
|
|
96
98
|
|
|
97
99
|
Expanded walkthrough: [docs/quickstart.md](docs/quickstart.md)
|
|
98
100
|
|
|
101
|
+
**Docker Compose** (UI + MCP in one Host): copy [`.env.example`](.env.example) → `.env`, set `STAGEFLOW_CONTROL_TOKEN`, then `docker compose up --build -d`. Checklist: [docs/docker.md](docs/docker.md#local-try-compose).
|
|
102
|
+
|
|
99
103
|
## Connect a model provider
|
|
100
104
|
|
|
101
105
|
Each stage sets a **`model`** id in YAML (e.g. `anthropic/claude-sonnet-4-5`). The matching provider must be authenticated before runs succeed — `sf validate` does not check auth.
|
|
@@ -184,16 +188,28 @@ See [docs/ci.md](docs/ci.md) for the full CI recipe and [`.github/actions/sf-run
|
|
|
184
188
|
|
|
185
189
|
## State
|
|
186
190
|
|
|
187
|
-
Runtime state
|
|
191
|
+
Runtime state (SQLite run store + per-run workspaces) is **global** — `~/.stageflow/.stageflow/` — shared across every project on the machine, since Stageflow became a single auto-starting service. Global config and `sf_owned` auth live directly under **`~/.stageflow/`**; per-project settings (`maxConcurrent`, `credentialSource`) still live at `<git-root>/.stageflow/settings.json`. If `.stageflow` is missing and `.software-factory` exists from an older install, the next store open renames it to `.stageflow` once.
|
|
188
192
|
|
|
189
193
|
## MCP
|
|
190
194
|
|
|
191
195
|
Host MCP via `sf ui` or `sf mcp` at `http://127.0.0.1:3847/mcp` (URL printed on boot). Sessions are the default. Point a Cursor (or other) MCP client at that URL. Do not run both hosts against the same store.
|
|
192
196
|
|
|
193
|
-
|
|
197
|
+
Stageflow runs as **one global auto-starting service per machine** (not one process per project): `sf run`, `sf run-stage`, and `sf runs *` health-probe the well-known port and spawn the service headlessly if nothing answers, so you do not have to start `sf ui`/`sf mcp` by hand before using the CLI.
|
|
198
|
+
|
|
199
|
+
HITL-aware tools include `wait_run`, `answer_gate`, `list_waiting`, and `decide_feedback_loop`. Inspect tools (`list_providers`, `list_models`, `describe_pipeline`, …) are read-only. `run_stage` runs a single stage directly, no pipeline file required — see [Standalone stages](#standalone-stages) below. Full tool list: [docs/mcp.md](docs/mcp.md).
|
|
194
200
|
|
|
195
201
|
Stages use project `.mcp.json` plus stage `mcp` names. See [YAML catalog — Stage MCP](docs/yaml-catalog.md#stage-mcp).
|
|
196
202
|
|
|
203
|
+
## Standalone stages
|
|
204
|
+
|
|
205
|
+
Run a single stage directly, without authoring a pipeline: the MCP `run_stage` tool, the `sf run-stage` CLI command, and (over A2A, wildcard-access — see below) the `run_stage` operation all give a calling agent (Claude Code, Cursor, Codex, Pi, ...) the same capability, so it can decide what to run next itself rather than following a pre-authored DAG. Chain calls with `envelope_ref` — a reference to a previous call's stored envelope, one or many at once — instead of copying results around by hand. Full reference: [docs/mcp.md#run_stage](docs/mcp.md#run_stage), [docs/cli-reference.md#sf-run-stage](docs/cli-reference.md#sf-run-stage).
|
|
206
|
+
|
|
207
|
+
## A2A
|
|
208
|
+
|
|
209
|
+
MCP is for your own trusted, local tools. A2A's `invoke` operation is a second, gated door for other agents on the network: publish specific pipelines in a separate `a2a.yaml`, and outside callers invoke them over JSON-RPC with a bearer token, poll for results, and download frozen artifacts -- never touching your internal run IDs or unpublished pipelines. A2A also exposes `run_stage` (above) with deliberately no gate: any caller with a valid bearer token can run any catalog or inline stage/pipeline, the same wildcard-access default MCP and the CLI give a local harness -- a temporary trade-off, not a hardened access boundary; keep that in mind before exposing a host with real callers.
|
|
210
|
+
|
|
211
|
+
Drop `a2a.yaml` next to `stageflow.yaml` and it's auto-discovered when `sf ui` starts -- no extra flag needed. `sf a2a add-caller <id>` registers a caller and generates its token. Full reference: [docs/a2a.md](docs/a2a.md).
|
|
212
|
+
|
|
197
213
|
## Stageflow vs Conductor
|
|
198
214
|
|
|
199
215
|
Both projects address multi-step agent workflows. They differ in orchestration model and runtime.
|
|
@@ -237,12 +253,15 @@ Full docs: **[tejasghutukade.github.io/stageflow](https://tejasghutukade.github.
|
|
|
237
253
|
| [docs/architecture.md](docs/architecture.md) | Runtime components, execution flow, persistence, and design decisions |
|
|
238
254
|
| [docs/quickstart.md](docs/quickstart.md) | Expanded quick start |
|
|
239
255
|
| [docs/yaml-catalog.md](docs/yaml-catalog.md) | Pipelines, stages, tasks schema |
|
|
240
|
-
| [docs/cli-reference.md](docs/cli-reference.md) | `sf init`, `sf run`, `sf validate`, `sf ui`, `sf mcp`, `sf envelope`, `sf export-run`, `sf artifact`, `sf skills`, `sf providers` |
|
|
256
|
+
| [docs/cli-reference.md](docs/cli-reference.md) | `sf init`, `sf run`, `sf run-stage`, `sf graph`, `sf validate`, `sf ui`, `sf mcp`, `sf envelope`, `sf export-run`, `sf artifact`, `sf skills`, `sf providers` |
|
|
241
257
|
| [docs/envelopes.md](docs/envelopes.md) | Handoff envelope contract |
|
|
258
|
+
| [docs/verified-stage-execution.md](docs/verified-stage-execution.md) | After-phase `verify`, evidence, and `on_verify_fail` repair / manual recovery |
|
|
242
259
|
| [docs/hitl.md](docs/hitl.md) | Gate kinds, `--skip-gates`, exit code `2` |
|
|
243
260
|
| [docs/ci.md](docs/ci.md) | `--json`, env vars, GitHub Actions |
|
|
244
|
-
| [docs/mcp.md](docs/mcp.md) | MCP tool reference |
|
|
261
|
+
| [docs/mcp.md](docs/mcp.md) | MCP tool reference, including standalone `run_stage` |
|
|
262
|
+
| [docs/a2a.md](docs/a2a.md) | Publish pipelines for other agents to call over JSON-RPC (A2A), plus the wildcard-access `run_stage` operation |
|
|
245
263
|
| [docs/providers.md](docs/providers.md) | Pi providers, `sf providers` |
|
|
264
|
+
| [docs/docker.md](docs/docker.md) | Docker Compose local try, image pull/run, backup, self-hosting |
|
|
246
265
|
| [docs/operator-console.md](docs/operator-console.md) | Console IA and settings |
|
|
247
266
|
| [docs/skills-suite.md](docs/skills-suite.md) | Harness skills — router + jobs for Cursor, Claude Code, Codex, Pi, OpenCode |
|
|
248
267
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Where `a2a.yaml` lives when nothing else says otherwise: next to `stageflow.yaml`, at the project root. */
|
|
2
|
+
export declare function a2aConfigPathFor(projectRoot: string): string;
|
|
3
|
+
/**
|
|
4
|
+
* `STAGEFLOW_A2A_CONFIG` always wins when set (an explicit override, e.g. a config file living
|
|
5
|
+
* outside the project). Otherwise, `<projectRoot>/a2a.yaml` is used if it exists -- the same
|
|
6
|
+
* auto-discovery convention `stageflow.yaml` already follows for the pipeline catalog. Returns
|
|
7
|
+
* undefined (A2A disabled) when neither is present.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveA2aConfigPath(projectRoot: string, env?: NodeJS.ProcessEnv): string | undefined;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/** Where `a2a.yaml` lives when nothing else says otherwise: next to `stageflow.yaml`, at the project root. */
|
|
4
|
+
export function a2aConfigPathFor(projectRoot) {
|
|
5
|
+
return path.join(projectRoot, "a2a.yaml");
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* `STAGEFLOW_A2A_CONFIG` always wins when set (an explicit override, e.g. a config file living
|
|
9
|
+
* outside the project). Otherwise, `<projectRoot>/a2a.yaml` is used if it exists -- the same
|
|
10
|
+
* auto-discovery convention `stageflow.yaml` already follows for the pipeline catalog. Returns
|
|
11
|
+
* undefined (A2A disabled) when neither is present.
|
|
12
|
+
*/
|
|
13
|
+
export function resolveA2aConfigPath(projectRoot, env = process.env) {
|
|
14
|
+
if (env.STAGEFLOW_A2A_CONFIG)
|
|
15
|
+
return env.STAGEFLOW_A2A_CONFIG;
|
|
16
|
+
const candidate = a2aConfigPathFor(projectRoot);
|
|
17
|
+
return existsSync(candidate) ? candidate : undefined;
|
|
18
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export type ApplicationErrorCategory = "invalid-input" | "unknown-capability" | "busy" | "message-conflict" | "stale-question" | "export-failed" | "not-found";
|
|
3
|
+
export declare class A2aApplicationError extends Error {
|
|
4
|
+
readonly category: ApplicationErrorCategory;
|
|
5
|
+
readonly details?: Record<string, string> | undefined;
|
|
6
|
+
constructor(category: ApplicationErrorCategory, message: string, details?: Record<string, string> | undefined);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Same shape as TaskFile (src/types/task.ts), duplicated locally the same way
|
|
10
|
+
* src/mcp/catalogTools.ts's own taskFileSchema is: deliberately not shared
|
|
11
|
+
* across module boundaries for a schema this small.
|
|
12
|
+
*/
|
|
13
|
+
declare const taskFileSchema: z.ZodObject<{
|
|
14
|
+
id: z.ZodString;
|
|
15
|
+
goal: z.ZodString;
|
|
16
|
+
context: z.ZodOptional<z.ZodString>;
|
|
17
|
+
constraints: z.ZodOptional<z.ZodString>;
|
|
18
|
+
checkout: z.ZodOptional<z.ZodString>;
|
|
19
|
+
input: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
20
|
+
}, z.core.$strip>;
|
|
21
|
+
declare const envelopeRefSchema: z.ZodObject<{
|
|
22
|
+
runId: z.ZodString;
|
|
23
|
+
stageId: z.ZodString;
|
|
24
|
+
attempt: z.ZodOptional<z.ZodNumber>;
|
|
25
|
+
}, z.core.$strip>;
|
|
26
|
+
export type InvokeCommand = {
|
|
27
|
+
kind: "invoke";
|
|
28
|
+
messageId: string;
|
|
29
|
+
contextId?: string;
|
|
30
|
+
capability: string;
|
|
31
|
+
input: unknown;
|
|
32
|
+
};
|
|
33
|
+
export type AnswerCommand = {
|
|
34
|
+
kind: "answer";
|
|
35
|
+
messageId: string;
|
|
36
|
+
contextId?: string;
|
|
37
|
+
taskId: string;
|
|
38
|
+
handle: string;
|
|
39
|
+
answer: {
|
|
40
|
+
kind: "free_text";
|
|
41
|
+
text: string;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
export type TaskFileInput = z.infer<typeof taskFileSchema>;
|
|
45
|
+
export type EnvelopeRefInput = z.infer<typeof envelopeRefSchema>;
|
|
46
|
+
/**
|
|
47
|
+
* ADR-0001's wildcard-access operation. Unlike InvokeCommand, `stage`/`pipeline`
|
|
48
|
+
* name the target directly (a catalog path or an inline body) instead of a
|
|
49
|
+
* published capability id, so no publication/allowlist lookup is possible or
|
|
50
|
+
* required to route it.
|
|
51
|
+
*/
|
|
52
|
+
export type RunStageCommand = {
|
|
53
|
+
kind: "run_stage";
|
|
54
|
+
messageId: string;
|
|
55
|
+
contextId?: string;
|
|
56
|
+
stage?: string | Record<string, unknown>;
|
|
57
|
+
pipeline?: string | Record<string, unknown>;
|
|
58
|
+
project_root?: string;
|
|
59
|
+
task_path?: string;
|
|
60
|
+
task?: TaskFileInput;
|
|
61
|
+
envelope_ref?: EnvelopeRefInput | EnvelopeRefInput[];
|
|
62
|
+
checkout?: string;
|
|
63
|
+
model?: string;
|
|
64
|
+
blocking?: boolean;
|
|
65
|
+
timeout_ms?: number;
|
|
66
|
+
};
|
|
67
|
+
export type ValidatedMessage = InvokeCommand | AnswerCommand | RunStageCommand;
|
|
68
|
+
export type IncomingMessage = {
|
|
69
|
+
messageId: string;
|
|
70
|
+
contextId?: string;
|
|
71
|
+
taskId?: string;
|
|
72
|
+
parts: Array<{
|
|
73
|
+
content?: {
|
|
74
|
+
$case: "data";
|
|
75
|
+
value: unknown;
|
|
76
|
+
} | {
|
|
77
|
+
$case: string;
|
|
78
|
+
value: unknown;
|
|
79
|
+
};
|
|
80
|
+
}>;
|
|
81
|
+
};
|
|
82
|
+
export declare function parseIncomingMessage(message: IncomingMessage): ValidatedMessage;
|
|
83
|
+
export declare function contentHash(value: unknown): string;
|
|
84
|
+
export declare function submissionKeyFor(callerId: string, capability: string, hash: string): string;
|
|
85
|
+
export declare function encodePromptHandle(taskId: string, stageId: string, promptId: string): string;
|
|
86
|
+
export declare function decodePromptHandle(handle: string): {
|
|
87
|
+
taskId: string;
|
|
88
|
+
stageId: string;
|
|
89
|
+
promptId: string;
|
|
90
|
+
} | undefined;
|
|
91
|
+
export declare function newTaskId(): string;
|
|
92
|
+
export declare function newContextId(): string;
|
|
93
|
+
export declare function newArtifactId(): string;
|
|
94
|
+
export {};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export class A2aApplicationError extends Error {
|
|
4
|
+
category;
|
|
5
|
+
details;
|
|
6
|
+
constructor(category, message, details) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.category = category;
|
|
9
|
+
this.details = details;
|
|
10
|
+
this.name = "A2aApplicationError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const invokeDataPart = z.object({
|
|
14
|
+
contractVersion: z.literal(1),
|
|
15
|
+
operation: z.literal("invoke"),
|
|
16
|
+
capability: z.string().min(1),
|
|
17
|
+
input: z.unknown(),
|
|
18
|
+
});
|
|
19
|
+
const freeTextAnswerPayload = z.object({
|
|
20
|
+
kind: z.literal("free_text"),
|
|
21
|
+
text: z.string(),
|
|
22
|
+
});
|
|
23
|
+
const answerDataPart = z.object({
|
|
24
|
+
contractVersion: z.literal(1),
|
|
25
|
+
operation: z.literal("answer"),
|
|
26
|
+
prompt: z.string().min(1),
|
|
27
|
+
answer: freeTextAnswerPayload,
|
|
28
|
+
});
|
|
29
|
+
/**
|
|
30
|
+
* Same shape as TaskFile (src/types/task.ts), duplicated locally the same way
|
|
31
|
+
* src/mcp/catalogTools.ts's own taskFileSchema is: deliberately not shared
|
|
32
|
+
* across module boundaries for a schema this small.
|
|
33
|
+
*/
|
|
34
|
+
const taskFileSchema = z.object({
|
|
35
|
+
id: z.string(),
|
|
36
|
+
goal: z.string(),
|
|
37
|
+
context: z.string().optional(),
|
|
38
|
+
constraints: z.string().optional(),
|
|
39
|
+
checkout: z.string().optional(),
|
|
40
|
+
input: z.record(z.string(), z.unknown()).optional(),
|
|
41
|
+
});
|
|
42
|
+
const envelopeRefSchema = z.object({
|
|
43
|
+
runId: z.string().min(1),
|
|
44
|
+
stageId: z.string().min(1),
|
|
45
|
+
attempt: z.number().int().positive().optional(),
|
|
46
|
+
});
|
|
47
|
+
/**
|
|
48
|
+
* ADR-0001's wildcard-access operation: invokes any catalog or inline
|
|
49
|
+
* stage/pipeline directly, bypassing the `publications`/`allowed_callers`
|
|
50
|
+
* allowlist that `invoke` enforces. Mirrors run_stage's MCP call shape
|
|
51
|
+
* (src/mcp/catalogTools.ts) closely on purpose so a harness gets the same
|
|
52
|
+
* capability over A2A that it gets over MCP/CLI.
|
|
53
|
+
*/
|
|
54
|
+
const runStageDataPart = z
|
|
55
|
+
.object({
|
|
56
|
+
contractVersion: z.literal(1),
|
|
57
|
+
operation: z.literal("run_stage"),
|
|
58
|
+
stage: z.union([z.string().min(1), z.record(z.string(), z.unknown())]).optional(),
|
|
59
|
+
pipeline: z.union([z.string().min(1), z.record(z.string(), z.unknown())]).optional(),
|
|
60
|
+
project_root: z.string().optional(),
|
|
61
|
+
task_path: z.string().optional(),
|
|
62
|
+
task: taskFileSchema.optional(),
|
|
63
|
+
envelope_ref: z.union([envelopeRefSchema, z.array(envelopeRefSchema).min(1)]).optional(),
|
|
64
|
+
checkout: z.string().optional(),
|
|
65
|
+
model: z.string().optional(),
|
|
66
|
+
blocking: z.boolean().optional(),
|
|
67
|
+
timeout_ms: z.number().optional(),
|
|
68
|
+
})
|
|
69
|
+
.refine((data) => [data.stage, data.pipeline].filter((v) => v !== undefined).length === 1, {
|
|
70
|
+
message: "Exactly one of stage or pipeline is required",
|
|
71
|
+
})
|
|
72
|
+
.refine((data) => [data.task_path, data.task, data.envelope_ref].filter((v) => v !== undefined).length === 1, { message: "Exactly one of task_path, task, or envelope_ref is required" });
|
|
73
|
+
const dataPart = z.union([invokeDataPart, answerDataPart, runStageDataPart]);
|
|
74
|
+
export function parseIncomingMessage(message) {
|
|
75
|
+
const dataParts = message.parts.filter((part) => part.content?.$case === "data");
|
|
76
|
+
if (dataParts.length !== 1) {
|
|
77
|
+
throw new A2aApplicationError("invalid-input", "Message must carry exactly one structured data part with the invoke/answer contract");
|
|
78
|
+
}
|
|
79
|
+
const parsed = dataPart.safeParse(dataParts[0].content.value);
|
|
80
|
+
if (!parsed.success) {
|
|
81
|
+
throw new A2aApplicationError("invalid-input", "Message data part does not match the invoke/answer contract: " + parsed.error.message);
|
|
82
|
+
}
|
|
83
|
+
if (parsed.data.operation === "invoke") {
|
|
84
|
+
return {
|
|
85
|
+
kind: "invoke",
|
|
86
|
+
messageId: message.messageId,
|
|
87
|
+
contextId: message.contextId || undefined,
|
|
88
|
+
capability: parsed.data.capability,
|
|
89
|
+
input: parsed.data.input,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (parsed.data.operation === "run_stage") {
|
|
93
|
+
return {
|
|
94
|
+
kind: "run_stage",
|
|
95
|
+
messageId: message.messageId,
|
|
96
|
+
contextId: message.contextId || undefined,
|
|
97
|
+
stage: parsed.data.stage,
|
|
98
|
+
pipeline: parsed.data.pipeline,
|
|
99
|
+
task_path: parsed.data.task_path,
|
|
100
|
+
task: parsed.data.task,
|
|
101
|
+
envelope_ref: parsed.data.envelope_ref,
|
|
102
|
+
checkout: parsed.data.checkout,
|
|
103
|
+
model: parsed.data.model,
|
|
104
|
+
blocking: parsed.data.blocking,
|
|
105
|
+
timeout_ms: parsed.data.timeout_ms,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (!message.taskId) {
|
|
109
|
+
throw new A2aApplicationError("invalid-input", "An answer message must reference its task");
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
kind: "answer",
|
|
113
|
+
messageId: message.messageId,
|
|
114
|
+
contextId: message.contextId || undefined,
|
|
115
|
+
taskId: message.taskId,
|
|
116
|
+
handle: parsed.data.prompt,
|
|
117
|
+
answer: parsed.data.answer,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function canonicalize(value) {
|
|
121
|
+
if (Array.isArray(value))
|
|
122
|
+
return value.map(canonicalize);
|
|
123
|
+
if (value !== null && typeof value === "object") {
|
|
124
|
+
return Object.fromEntries(Object.keys(value)
|
|
125
|
+
.sort()
|
|
126
|
+
.map((key) => [key, canonicalize(value[key])]));
|
|
127
|
+
}
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
130
|
+
export function contentHash(value) {
|
|
131
|
+
return createHash("sha256").update(JSON.stringify(canonicalize(value))).digest("hex");
|
|
132
|
+
}
|
|
133
|
+
export function submissionKeyFor(callerId, capability, hash) {
|
|
134
|
+
return "a2a:" + callerId + ":" + capability + ":" + hash;
|
|
135
|
+
}
|
|
136
|
+
const HANDLE_SEPARATOR = ":";
|
|
137
|
+
export function encodePromptHandle(taskId, stageId, promptId) {
|
|
138
|
+
return Buffer.from([taskId, stageId, promptId].join(HANDLE_SEPARATOR), "utf8").toString("base64url");
|
|
139
|
+
}
|
|
140
|
+
export function decodePromptHandle(handle) {
|
|
141
|
+
let decoded;
|
|
142
|
+
try {
|
|
143
|
+
decoded = Buffer.from(handle, "base64url").toString("utf8");
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
const parts = decoded.split(HANDLE_SEPARATOR);
|
|
149
|
+
if (parts.length !== 3 || parts.some((part) => part.length === 0))
|
|
150
|
+
return undefined;
|
|
151
|
+
const taskId = parts[0];
|
|
152
|
+
const stageId = parts[1];
|
|
153
|
+
const promptId = parts[2];
|
|
154
|
+
return { taskId, stageId, promptId };
|
|
155
|
+
}
|
|
156
|
+
export function newTaskId() {
|
|
157
|
+
return randomUUID();
|
|
158
|
+
}
|
|
159
|
+
export function newContextId() {
|
|
160
|
+
return randomUUID();
|
|
161
|
+
}
|
|
162
|
+
export function newArtifactId() {
|
|
163
|
+
return randomUUID();
|
|
164
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const RATE_LIMIT_PER_MINUTE = 60;
|
|
2
|
+
export declare const RATE_LIMIT_BURST = 20;
|
|
3
|
+
export declare const MAX_NONTERMINAL_TASKS_PER_CALLER = 2;
|
|
4
|
+
export declare const TERMINAL_RETENTION_MS: number;
|
|
5
|
+
export declare const MESSAGE_TOMBSTONE_RETENTION_MS: number;
|
|
6
|
+
/** Token-bucket per caller: `capacity` burst, refilling at `perMinute` tokens/minute. Clock is injectable for deterministic tests. */
|
|
7
|
+
export declare class RateLimiter {
|
|
8
|
+
private readonly capacity;
|
|
9
|
+
private readonly now;
|
|
10
|
+
private readonly buckets;
|
|
11
|
+
private readonly refillPerMs;
|
|
12
|
+
constructor(capacity?: number, perMinute?: number, now?: () => number);
|
|
13
|
+
tryConsume(key: string): boolean;
|
|
14
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export const RATE_LIMIT_PER_MINUTE = 60;
|
|
2
|
+
export const RATE_LIMIT_BURST = 20;
|
|
3
|
+
export const MAX_NONTERMINAL_TASKS_PER_CALLER = 2;
|
|
4
|
+
export const TERMINAL_RETENTION_MS = 30 * 24 * 60 * 60 * 1000;
|
|
5
|
+
export const MESSAGE_TOMBSTONE_RETENTION_MS = 90 * 24 * 60 * 60 * 1000;
|
|
6
|
+
/** Token-bucket per caller: `capacity` burst, refilling at `perMinute` tokens/minute. Clock is injectable for deterministic tests. */
|
|
7
|
+
export class RateLimiter {
|
|
8
|
+
capacity;
|
|
9
|
+
now;
|
|
10
|
+
buckets = new Map();
|
|
11
|
+
refillPerMs;
|
|
12
|
+
constructor(capacity = RATE_LIMIT_BURST, perMinute = RATE_LIMIT_PER_MINUTE, now = Date.now) {
|
|
13
|
+
this.capacity = capacity;
|
|
14
|
+
this.now = now;
|
|
15
|
+
this.refillPerMs = perMinute / 60000;
|
|
16
|
+
}
|
|
17
|
+
tryConsume(key) {
|
|
18
|
+
const at = this.now();
|
|
19
|
+
const existing = this.buckets.get(key);
|
|
20
|
+
const elapsed = existing ? Math.max(0, at - existing.updatedAt) : 0;
|
|
21
|
+
const tokens = existing ? Math.min(this.capacity, existing.tokens + elapsed * this.refillPerMs) : this.capacity;
|
|
22
|
+
if (tokens < 1) {
|
|
23
|
+
this.buckets.set(key, { tokens, updatedAt: at });
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
this.buckets.set(key, { tokens: tokens - 1, updatedAt: at });
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
declare const publicationSchema: z.ZodObject<{
|
|
3
|
+
id: z.ZodString;
|
|
4
|
+
name: z.ZodString;
|
|
5
|
+
description: z.ZodString;
|
|
6
|
+
project_root: z.ZodString;
|
|
7
|
+
pipeline: z.ZodString;
|
|
8
|
+
goal: z.ZodString;
|
|
9
|
+
repository: z.ZodOptional<z.ZodString>;
|
|
10
|
+
ref: z.ZodOptional<z.ZodString>;
|
|
11
|
+
allowed_callers: z.ZodArray<z.ZodString>;
|
|
12
|
+
input_schema: z.ZodString;
|
|
13
|
+
results: z.ZodObject<{
|
|
14
|
+
stage: z.ZodString;
|
|
15
|
+
include_payload: z.ZodDefault<z.ZodBoolean>;
|
|
16
|
+
artifacts: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
17
|
+
}, z.core.$strict>;
|
|
18
|
+
caller_answerable_stages: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
19
|
+
}, z.core.$strict>;
|
|
20
|
+
export type PublicationConfig = z.infer<typeof publicationSchema>;
|
|
21
|
+
export type Publication = PublicationConfig & {
|
|
22
|
+
revision: string;
|
|
23
|
+
inputSchema: Record<string, unknown>;
|
|
24
|
+
outputSchema: unknown;
|
|
25
|
+
};
|
|
26
|
+
export declare class PublicationRegistry {
|
|
27
|
+
readonly publicUrl: string;
|
|
28
|
+
readonly configPath: string;
|
|
29
|
+
private readonly publications;
|
|
30
|
+
private readonly credentials;
|
|
31
|
+
constructor(publicUrl: string, configPath: string, publications: readonly Publication[], credentials: readonly {
|
|
32
|
+
id: string;
|
|
33
|
+
hash: Buffer;
|
|
34
|
+
}[]);
|
|
35
|
+
authenticate(authorization: string | undefined): string | undefined;
|
|
36
|
+
list(caller: string): Publication[];
|
|
37
|
+
get(caller: string, id: string): Publication | undefined;
|
|
38
|
+
summaries(): Array<{
|
|
39
|
+
id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
revision: string;
|
|
42
|
+
}>;
|
|
43
|
+
assertUnchanged(caller: string, id: string): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
export declare function loadPublicationRegistry(configPath: string, env?: NodeJS.ProcessEnv): Promise<PublicationRegistry>;
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { createHash, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { readFile, realpath } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { parse } from "yaml";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { loadPipeline } from "../config/loadPipeline.js";
|
|
7
|
+
import { compilePayloadSchema, isPayloadSchemaSubset } from "../envelope/payloadSchema.js";
|
|
8
|
+
const identifier = z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,79}$/);
|
|
9
|
+
const publicationSchema = z.object({
|
|
10
|
+
id: identifier,
|
|
11
|
+
name: z.string().trim().min(1),
|
|
12
|
+
description: z.string().trim().min(1),
|
|
13
|
+
project_root: z.string().min(1),
|
|
14
|
+
pipeline: z.string().min(1),
|
|
15
|
+
goal: z.string().trim().min(1),
|
|
16
|
+
repository: z.string().trim().min(1).optional(),
|
|
17
|
+
ref: z.string().trim().min(1).optional(),
|
|
18
|
+
allowed_callers: z.array(identifier).min(1),
|
|
19
|
+
input_schema: z.string().min(1),
|
|
20
|
+
results: z.object({
|
|
21
|
+
stage: identifier,
|
|
22
|
+
include_payload: z.boolean().default(true),
|
|
23
|
+
artifacts: z.array(z.string().regex(/^[^/\\.][^/\\]*$/)).default([]),
|
|
24
|
+
}).strict(),
|
|
25
|
+
caller_answerable_stages: z.array(identifier).default([]),
|
|
26
|
+
}).strict().superRefine((data, ctx) => {
|
|
27
|
+
const hasRepo = data.repository !== undefined;
|
|
28
|
+
const hasRef = data.ref !== undefined;
|
|
29
|
+
if (hasRepo && !hasRef) {
|
|
30
|
+
ctx.addIssue({
|
|
31
|
+
code: z.ZodIssueCode.custom,
|
|
32
|
+
message: "repository requires ref",
|
|
33
|
+
path: ["ref"],
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
if (hasRef && !hasRepo) {
|
|
37
|
+
ctx.addIssue({
|
|
38
|
+
code: z.ZodIssueCode.custom,
|
|
39
|
+
message: "ref requires repository",
|
|
40
|
+
path: ["repository"],
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
const configSchema = z.object({
|
|
45
|
+
version: z.literal(1),
|
|
46
|
+
public_url: z.string().url(),
|
|
47
|
+
callers: z.array(z.object({
|
|
48
|
+
id: identifier,
|
|
49
|
+
token_env: z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/),
|
|
50
|
+
}).strict()).min(1),
|
|
51
|
+
publications: z.array(publicationSchema).min(1),
|
|
52
|
+
}).strict();
|
|
53
|
+
function digest(value) {
|
|
54
|
+
return createHash("sha256").update(value).digest();
|
|
55
|
+
}
|
|
56
|
+
function requireUnique(values, label) {
|
|
57
|
+
if (new Set(values).size !== values.length)
|
|
58
|
+
throw new Error(`Duplicate ${label}`);
|
|
59
|
+
}
|
|
60
|
+
function publicUrl(value) {
|
|
61
|
+
const url = new URL(value);
|
|
62
|
+
const local = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
63
|
+
if ((url.protocol !== "https:" && !(local && url.protocol === "http:")) ||
|
|
64
|
+
url.username || url.password || url.search || url.hash || url.pathname !== "/") {
|
|
65
|
+
throw new Error("public_url must be an HTTPS origin (HTTP allowed on loopback)");
|
|
66
|
+
}
|
|
67
|
+
return url.origin;
|
|
68
|
+
}
|
|
69
|
+
function validatePublication(config, loaded, input) {
|
|
70
|
+
const result = loaded.stages.find((stage) => stage.id === config.results.stage);
|
|
71
|
+
if (!result || loaded.dag.childrenOf[result.id]?.length ||
|
|
72
|
+
loaded.dag.nodes.some((node) => node.clone_cap !== undefined)) {
|
|
73
|
+
throw new Error(`Publication ${config.id} requires a fixed terminal result stage without cloning`);
|
|
74
|
+
}
|
|
75
|
+
for (const root of loaded.dag.roots) {
|
|
76
|
+
const stage = loaded.stages.find((candidate) => candidate.id === root);
|
|
77
|
+
if (!isPayloadSchemaSubset(stage.clone_input_schema, input)) {
|
|
78
|
+
throw new Error(`Publication ${config.id} input is incompatible with entry stage ${root}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
for (const id of config.caller_answerable_stages) {
|
|
82
|
+
const stage = loaded.stages.find((candidate) => candidate.id === id);
|
|
83
|
+
if (!stage || stage.gate_kinds?.length !== 1 || stage.gate_kinds[0] !== "free_text") {
|
|
84
|
+
throw new Error(`Publication ${config.id} caller-answerable stage ${id} must allow only free_text`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const declared = new Set(result.pre_emit_checks?.flatMap((check) => check.type === "artifact_declared" ? check.basename : []) ?? []);
|
|
88
|
+
for (const name of config.results.artifacts) {
|
|
89
|
+
if (!declared.has(name))
|
|
90
|
+
throw new Error(`Publication ${config.id} artifact ${name} needs an emit artifact check`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function readPublication(config) {
|
|
94
|
+
const loaded = await loadPipeline(config.pipeline, { cwd: config.project_root, projectRoot: config.project_root });
|
|
95
|
+
const input = JSON.parse(await readFile(config.input_schema, "utf8"));
|
|
96
|
+
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
97
|
+
throw new Error(`Publication ${config.id} needs an object schema`);
|
|
98
|
+
compilePayloadSchema(input);
|
|
99
|
+
validatePublication(config, loaded, input);
|
|
100
|
+
const files = [...new Set([loaded.pipelinePath, ...Object.values(loaded.stageSources ?? {})
|
|
101
|
+
.flatMap((source) => source.kind === "file" ? [source.path] : [])])].sort();
|
|
102
|
+
const bodies = await Promise.all(files.map(async (file) => [file, await readFile(file, "utf8")]));
|
|
103
|
+
const revision = digest(JSON.stringify({ config, loaded, input, bodies })).toString("hex");
|
|
104
|
+
return {
|
|
105
|
+
...config,
|
|
106
|
+
revision,
|
|
107
|
+
inputSchema: input,
|
|
108
|
+
outputSchema: loaded.stages.find((stage) => stage.id === config.results.stage).payload_schema,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
export class PublicationRegistry {
|
|
112
|
+
publicUrl;
|
|
113
|
+
configPath;
|
|
114
|
+
publications;
|
|
115
|
+
credentials;
|
|
116
|
+
constructor(publicUrl, configPath, publications, credentials) {
|
|
117
|
+
this.publicUrl = publicUrl;
|
|
118
|
+
this.configPath = configPath;
|
|
119
|
+
this.publications = publications;
|
|
120
|
+
this.credentials = credentials;
|
|
121
|
+
}
|
|
122
|
+
authenticate(authorization) {
|
|
123
|
+
const match = authorization?.match(/^Bearer ([^\s]+)$/i);
|
|
124
|
+
if (!match)
|
|
125
|
+
return undefined;
|
|
126
|
+
const hash = digest(match[1]);
|
|
127
|
+
return this.credentials.find((credential) => timingSafeEqual(credential.hash, hash))?.id;
|
|
128
|
+
}
|
|
129
|
+
list(caller) {
|
|
130
|
+
return this.publications.filter((publication) => publication.allowed_callers.includes(caller))
|
|
131
|
+
.map((publication) => structuredClone(publication));
|
|
132
|
+
}
|
|
133
|
+
get(caller, id) {
|
|
134
|
+
return this.list(caller).find((publication) => publication.id === id);
|
|
135
|
+
}
|
|
136
|
+
summaries() {
|
|
137
|
+
return this.publications.map(({ id, name, revision }) => ({ id, name, revision }));
|
|
138
|
+
}
|
|
139
|
+
async assertUnchanged(caller, id) {
|
|
140
|
+
const publication = this.get(caller, id);
|
|
141
|
+
if (!publication)
|
|
142
|
+
throw new Error("Publication not found");
|
|
143
|
+
const { revision: _revision, inputSchema: _input, outputSchema: _output, ...config } = publication;
|
|
144
|
+
const current = await readPublication(config);
|
|
145
|
+
if (current.revision !== publication.revision)
|
|
146
|
+
throw new Error("Publication changed; reload the deployed configuration");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
export async function loadPublicationRegistry(configPath, env = process.env) {
|
|
150
|
+
const absolute = await realpath(configPath);
|
|
151
|
+
const config = configSchema.parse(parse(await readFile(absolute, "utf8")));
|
|
152
|
+
const origin = publicUrl(config.public_url);
|
|
153
|
+
requireUnique(config.callers.map((caller) => caller.id), "caller ID");
|
|
154
|
+
requireUnique(config.publications.map((publication) => publication.id), "publication ID");
|
|
155
|
+
const credentials = config.callers.map((caller) => {
|
|
156
|
+
const token = env[caller.token_env];
|
|
157
|
+
if (!token || /\s/.test(token) || token.length < 32)
|
|
158
|
+
throw new Error(`Caller ${caller.id} requires a token of at least 32 characters in ${caller.token_env}`);
|
|
159
|
+
return { id: caller.id, hash: digest(token) };
|
|
160
|
+
});
|
|
161
|
+
requireUnique(credentials.map((credential) => credential.hash.toString("hex")), "caller credential");
|
|
162
|
+
const publications = [];
|
|
163
|
+
for (const entry of config.publications) {
|
|
164
|
+
requireUnique(entry.allowed_callers, `caller in publication ${entry.id}`);
|
|
165
|
+
requireUnique(entry.results.artifacts, `artifact in publication ${entry.id}`);
|
|
166
|
+
requireUnique(entry.caller_answerable_stages, `answerable stage in publication ${entry.id}`);
|
|
167
|
+
if (entry.allowed_callers.some((id) => !credentials.some((caller) => caller.id === id))) {
|
|
168
|
+
throw new Error(`Publication ${entry.id} refers to an unknown caller`);
|
|
169
|
+
}
|
|
170
|
+
const root = await realpath(path.resolve(path.dirname(absolute), entry.project_root));
|
|
171
|
+
publications.push(await readPublication({
|
|
172
|
+
...entry, project_root: root,
|
|
173
|
+
pipeline: await realpath(path.resolve(root, entry.pipeline)),
|
|
174
|
+
input_schema: await realpath(path.resolve(root, entry.input_schema)),
|
|
175
|
+
}));
|
|
176
|
+
}
|
|
177
|
+
return new PublicationRegistry(origin, absolute, publications, credentials);
|
|
178
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { IncomingMessage as HttpIncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import type Database from "better-sqlite3";
|
|
3
|
+
import { AgentCard } from "@a2a-js/sdk";
|
|
4
|
+
import type { RunStore } from "../runstore/port.js";
|
|
5
|
+
import type { RunManager } from "../runtime/runManager.js";
|
|
6
|
+
import { type PublicationRegistry } from "./registry.js";
|
|
7
|
+
import type { A2aStore } from "./store.js";
|
|
8
|
+
export declare const A2A_BODY_LIMIT: number;
|
|
9
|
+
export declare function publicationCard(registry: PublicationRegistry, caller: string): AgentCard;
|
|
10
|
+
export type A2aRuntime = {
|
|
11
|
+
manager: RunManager;
|
|
12
|
+
runStore: RunStore;
|
|
13
|
+
rootDir: string;
|
|
14
|
+
/** The SqliteRunStore's own connection to `state.db`, when the caller constructed one via `createRunStoreWithConnection`. */
|
|
15
|
+
connection?: Database.Database;
|
|
16
|
+
/** Shared A2A store when the Host composition root already opened one for delete_run. */
|
|
17
|
+
a2aStore?: A2aStore;
|
|
18
|
+
};
|
|
19
|
+
export type A2aHost = {
|
|
20
|
+
status: {
|
|
21
|
+
state: "disabled" | "enabled" | "configuration_error";
|
|
22
|
+
configPath?: string;
|
|
23
|
+
error?: {
|
|
24
|
+
code: string;
|
|
25
|
+
message: string;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
handle(req: HttpIncomingMessage, res: ServerResponse, pathname: string): Promise<boolean>;
|
|
29
|
+
/** Deletes expired terminal tasks/artifacts and message tombstones. A no-op when A2A is not enabled. */
|
|
30
|
+
pruneExpired(now?: Date): Promise<{
|
|
31
|
+
removedTasks: number;
|
|
32
|
+
removedMessages: number;
|
|
33
|
+
}>;
|
|
34
|
+
close(): void;
|
|
35
|
+
};
|
|
36
|
+
export declare function createA2aHost(runtime: A2aRuntime, configPath?: string, env?: NodeJS.ProcessEnv): Promise<A2aHost>;
|