stageflow 0.25.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 +10 -3
- package/dist/a2a/contracts.d.ts +1 -0
- package/dist/a2a/contracts.js +1 -0
- package/dist/a2a/registry.d.ts +2 -0
- package/dist/a2a/registry.js +20 -1
- package/dist/a2a/server.d.ts +7 -0
- package/dist/a2a/server.js +5 -2
- package/dist/a2a/service.d.ts +1 -1
- package/dist/a2a/service.js +57 -8
- package/dist/a2a/store.d.ts +8 -5
- package/dist/a2a/store.js +24 -67
- 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/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 +1 -0
- package/dist/cli/runStageCommand.js +44 -25
- package/dist/cli/runsCommand.d.ts +1 -1
- package/dist/cli/runsCommand.js +162 -8
- package/dist/cli.js +171 -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 +456 -80
- 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 +18 -1
- package/dist/runstore/createStore.js +64 -5
- 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 +111 -13
- 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 +28 -3
- package/dist/runstore/sqlite/SqliteRunStore.js +352 -219
- 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 +277 -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/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 +36 -1
- package/dist/runtime/pipelineRunner.js +178 -17
- package/dist/runtime/pipelineScheduler.d.ts +15 -0
- package/dist/runtime/pipelineScheduler.js +135 -27
- package/dist/runtime/reloadRunCatalog.js +6 -1
- package/dist/runtime/repositoryMaterialize.d.ts +47 -0
- package/dist/runtime/repositoryMaterialize.js +235 -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 +158 -4
- package/dist/runtime/runManager.js +1596 -79
- 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 +36 -2
- package/dist/server/bootstrap.js +209 -5
- 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 +103 -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 +5 -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
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type LoadOutcome } from "../config/loadOutcome.js";
|
|
2
|
+
export type WorkspaceBinding = {
|
|
3
|
+
kind: "repository";
|
|
4
|
+
repository: string;
|
|
5
|
+
ref: string;
|
|
6
|
+
} | {
|
|
7
|
+
kind: "checkout";
|
|
8
|
+
path: string;
|
|
9
|
+
} | {
|
|
10
|
+
kind: "unbound";
|
|
11
|
+
};
|
|
12
|
+
export type WorkspaceBindingInput = {
|
|
13
|
+
repository?: string;
|
|
14
|
+
ref?: string;
|
|
15
|
+
checkout?: string;
|
|
16
|
+
};
|
|
17
|
+
export type ResolveWorkspaceBindingOptions = {
|
|
18
|
+
checkoutOverride?: string;
|
|
19
|
+
};
|
|
20
|
+
export declare function resolveWorkspaceBinding(task: WorkspaceBindingInput, options?: ResolveWorkspaceBindingOptions): LoadOutcome<WorkspaceBinding>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { loadFailure, loadSuccess } from "../config/loadOutcome.js";
|
|
2
|
+
const OWNER_REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
3
|
+
function nonEmptyTrimmed(value) {
|
|
4
|
+
if (value === undefined)
|
|
5
|
+
return undefined;
|
|
6
|
+
const trimmed = value.trim();
|
|
7
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
8
|
+
}
|
|
9
|
+
function isGithubOwnerRepo(repository) {
|
|
10
|
+
return OWNER_REPO_RE.test(repository);
|
|
11
|
+
}
|
|
12
|
+
export function resolveWorkspaceBinding(task, options) {
|
|
13
|
+
const repository = nonEmptyTrimmed(task.repository);
|
|
14
|
+
const ref = nonEmptyTrimmed(task.ref);
|
|
15
|
+
const checkoutPath = nonEmptyTrimmed(options?.checkoutOverride ?? task.checkout);
|
|
16
|
+
const hasRepositoryField = repository !== undefined;
|
|
17
|
+
if (hasRepositoryField && checkoutPath !== undefined) {
|
|
18
|
+
return loadFailure([
|
|
19
|
+
{
|
|
20
|
+
code: "task.binding_conflict",
|
|
21
|
+
message: "Task cannot declare both repository and checkout (exactly one workspace binding)",
|
|
22
|
+
category: "task",
|
|
23
|
+
},
|
|
24
|
+
]);
|
|
25
|
+
}
|
|
26
|
+
if (hasRepositoryField && ref === undefined) {
|
|
27
|
+
return loadFailure([
|
|
28
|
+
{
|
|
29
|
+
code: "task.repository_ref_required",
|
|
30
|
+
message: "Task repository requires a non-empty ref",
|
|
31
|
+
category: "task",
|
|
32
|
+
},
|
|
33
|
+
]);
|
|
34
|
+
}
|
|
35
|
+
if (ref !== undefined && repository === undefined) {
|
|
36
|
+
return loadFailure([
|
|
37
|
+
{
|
|
38
|
+
code: "task.ref_without_repository",
|
|
39
|
+
message: "Task ref requires repository",
|
|
40
|
+
category: "task",
|
|
41
|
+
},
|
|
42
|
+
]);
|
|
43
|
+
}
|
|
44
|
+
if (repository !== undefined && ref !== undefined) {
|
|
45
|
+
if (!isGithubOwnerRepo(repository)) {
|
|
46
|
+
return loadFailure([
|
|
47
|
+
{
|
|
48
|
+
code: "task.repository_invalid",
|
|
49
|
+
message: "Task repository must be a GitHub owner/repo (e.g. acme/api); hosts and URLs are not accepted",
|
|
50
|
+
category: "task",
|
|
51
|
+
},
|
|
52
|
+
]);
|
|
53
|
+
}
|
|
54
|
+
return loadSuccess({ kind: "repository", repository, ref });
|
|
55
|
+
}
|
|
56
|
+
if (checkoutPath !== undefined) {
|
|
57
|
+
return loadSuccess({ kind: "checkout", path: checkoutPath });
|
|
58
|
+
}
|
|
59
|
+
return loadSuccess({ kind: "unbound" });
|
|
60
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
export type AllowedHostEntry = {
|
|
3
|
+
hostname: string;
|
|
4
|
+
port?: string;
|
|
5
|
+
};
|
|
6
|
+
export type AllowedHosts = {
|
|
7
|
+
entries: AllowedHostEntry[];
|
|
8
|
+
};
|
|
9
|
+
export declare function hostnameFromHostHeader(hostHeader: string): string;
|
|
10
|
+
export declare function portFromHostHeader(hostHeader: string): string | undefined;
|
|
11
|
+
export declare function isLoopbackHostname(hostname: string): boolean;
|
|
12
|
+
export declare function isLoopbackRemoteAddress(addr: string | undefined): boolean;
|
|
13
|
+
/** Loopback peer plus Host (and Origin, when present) — trusted local plane for ensure. */
|
|
14
|
+
export declare function isTrustedLocalHttpRequest(req: IncomingMessage): boolean;
|
|
15
|
+
export declare function resolveAllowedHosts(env?: NodeJS.ProcessEnv): AllowedHosts;
|
|
16
|
+
export declare function isHostAllowed(allowed: AllowedHosts, hostname: string, port?: string): boolean;
|
|
17
|
+
export declare function assertAllowedHttpAccess(allowed: AllowedHosts, req: IncomingMessage, res: ServerResponse, options?: {
|
|
18
|
+
requireOrigin?: boolean;
|
|
19
|
+
}): boolean;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { isIP } from "node:net";
|
|
2
|
+
function writeForbidden(res, body) {
|
|
3
|
+
const payload = JSON.stringify(body);
|
|
4
|
+
res.writeHead(403, {
|
|
5
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
6
|
+
"Content-Length": Buffer.byteLength(payload),
|
|
7
|
+
});
|
|
8
|
+
res.end(payload);
|
|
9
|
+
}
|
|
10
|
+
export function hostnameFromHostHeader(hostHeader) {
|
|
11
|
+
if (hostHeader.startsWith("[")) {
|
|
12
|
+
const end = hostHeader.indexOf("]");
|
|
13
|
+
return end === -1 ? hostHeader : hostHeader.slice(1, end);
|
|
14
|
+
}
|
|
15
|
+
const colon = hostHeader.lastIndexOf(":");
|
|
16
|
+
if (colon > 0 && /^\d+$/.test(hostHeader.slice(colon + 1))) {
|
|
17
|
+
return hostHeader.slice(0, colon);
|
|
18
|
+
}
|
|
19
|
+
return hostHeader;
|
|
20
|
+
}
|
|
21
|
+
export function portFromHostHeader(hostHeader) {
|
|
22
|
+
if (hostHeader.startsWith("[")) {
|
|
23
|
+
const end = hostHeader.indexOf("]");
|
|
24
|
+
if (end === -1)
|
|
25
|
+
return undefined;
|
|
26
|
+
const rest = hostHeader.slice(end + 1);
|
|
27
|
+
if (rest.startsWith(":") && /^\d+$/.test(rest.slice(1))) {
|
|
28
|
+
return rest.slice(1);
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
const colon = hostHeader.lastIndexOf(":");
|
|
33
|
+
if (colon > 0 && /^\d+$/.test(hostHeader.slice(colon + 1))) {
|
|
34
|
+
return hostHeader.slice(colon + 1);
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
export function isLoopbackHostname(hostname) {
|
|
39
|
+
const h = hostname.toLowerCase();
|
|
40
|
+
if (h === "localhost" || h === "::1")
|
|
41
|
+
return true;
|
|
42
|
+
if (isIP(h) === 4) {
|
|
43
|
+
const first = Number(h.split(".")[0]);
|
|
44
|
+
return first === 127;
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
export function isLoopbackRemoteAddress(addr) {
|
|
49
|
+
if (addr === undefined || addr.length === 0)
|
|
50
|
+
return false;
|
|
51
|
+
let normalized = addr;
|
|
52
|
+
if (normalized.toLowerCase().startsWith("::ffff:")) {
|
|
53
|
+
normalized = normalized.slice(7);
|
|
54
|
+
}
|
|
55
|
+
if (normalized === "::1")
|
|
56
|
+
return true;
|
|
57
|
+
if (isIP(normalized) === 4) {
|
|
58
|
+
const first = Number(normalized.split(".")[0]);
|
|
59
|
+
return first === 127;
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
/** Loopback peer plus Host (and Origin, when present) — trusted local plane for ensure. */
|
|
64
|
+
export function isTrustedLocalHttpRequest(req) {
|
|
65
|
+
const hostHeader = req.headers.host;
|
|
66
|
+
if (typeof hostHeader !== "string" || hostHeader.length === 0)
|
|
67
|
+
return false;
|
|
68
|
+
if (!isLoopbackHostname(hostnameFromHostHeader(hostHeader)))
|
|
69
|
+
return false;
|
|
70
|
+
const origin = req.headers.origin;
|
|
71
|
+
if (typeof origin === "string" && origin.length > 0) {
|
|
72
|
+
try {
|
|
73
|
+
const originUrl = new URL(origin);
|
|
74
|
+
if (!isLoopbackHostname(originUrl.hostname))
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return isLoopbackRemoteAddress(req.socket?.remoteAddress);
|
|
82
|
+
}
|
|
83
|
+
export function resolveAllowedHosts(env = process.env) {
|
|
84
|
+
const raw = env.STAGEFLOW_ALLOWED_HOSTS;
|
|
85
|
+
const entries = [
|
|
86
|
+
{ hostname: "localhost" },
|
|
87
|
+
{ hostname: "127.0.0.1" },
|
|
88
|
+
{ hostname: "::1" },
|
|
89
|
+
];
|
|
90
|
+
if (raw === undefined || raw.trim() === "") {
|
|
91
|
+
return { entries };
|
|
92
|
+
}
|
|
93
|
+
for (const part of raw.split(",")) {
|
|
94
|
+
const token = part.trim();
|
|
95
|
+
if (token.length === 0)
|
|
96
|
+
continue;
|
|
97
|
+
if (token === "*") {
|
|
98
|
+
throw new Error("STAGEFLOW_ALLOWED_HOSTS must not contain '*'; list explicit hostnames instead.");
|
|
99
|
+
}
|
|
100
|
+
const parsed = parseAllowedHostEntry(token);
|
|
101
|
+
if (!entries.some((e) => e.hostname === parsed.hostname &&
|
|
102
|
+
(e.port ?? "") === (parsed.port ?? ""))) {
|
|
103
|
+
entries.push(parsed);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return { entries };
|
|
107
|
+
}
|
|
108
|
+
function parseAllowedHostEntry(token) {
|
|
109
|
+
if (token.startsWith("[")) {
|
|
110
|
+
const end = token.indexOf("]");
|
|
111
|
+
if (end === -1) {
|
|
112
|
+
throw new Error(`Invalid STAGEFLOW_ALLOWED_HOSTS entry: ${token}`);
|
|
113
|
+
}
|
|
114
|
+
const hostname = token.slice(1, end).toLowerCase();
|
|
115
|
+
const rest = token.slice(end + 1);
|
|
116
|
+
if (rest.length === 0)
|
|
117
|
+
return { hostname };
|
|
118
|
+
if (rest.startsWith(":") && /^\d+$/.test(rest.slice(1))) {
|
|
119
|
+
return { hostname, port: rest.slice(1) };
|
|
120
|
+
}
|
|
121
|
+
throw new Error(`Invalid STAGEFLOW_ALLOWED_HOSTS entry: ${token}`);
|
|
122
|
+
}
|
|
123
|
+
const colon = token.lastIndexOf(":");
|
|
124
|
+
if (colon > 0 && /^\d+$/.test(token.slice(colon + 1))) {
|
|
125
|
+
return {
|
|
126
|
+
hostname: token.slice(0, colon).toLowerCase(),
|
|
127
|
+
port: token.slice(colon + 1),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
return { hostname: token.toLowerCase() };
|
|
131
|
+
}
|
|
132
|
+
export function isHostAllowed(allowed, hostname, port) {
|
|
133
|
+
const h = hostname.toLowerCase();
|
|
134
|
+
if (isLoopbackHostname(h))
|
|
135
|
+
return true;
|
|
136
|
+
for (const entry of allowed.entries) {
|
|
137
|
+
if (entry.hostname !== h)
|
|
138
|
+
continue;
|
|
139
|
+
if (entry.port === undefined)
|
|
140
|
+
return true;
|
|
141
|
+
if (port !== undefined && entry.port === port)
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
export function assertAllowedHttpAccess(allowed, req, res, options = {}) {
|
|
147
|
+
const hostHeader = req.headers.host;
|
|
148
|
+
if (typeof hostHeader !== "string" || hostHeader.length === 0) {
|
|
149
|
+
writeForbidden(res, { error: "Forbidden host" });
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
const hostname = hostnameFromHostHeader(hostHeader);
|
|
153
|
+
const port = portFromHostHeader(hostHeader);
|
|
154
|
+
if (!isHostAllowed(allowed, hostname, port)) {
|
|
155
|
+
writeForbidden(res, { error: "Forbidden host", host: hostname });
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
const origin = req.headers.origin;
|
|
159
|
+
if (typeof origin === "string" && origin.length > 0) {
|
|
160
|
+
try {
|
|
161
|
+
const originUrl = new URL(origin);
|
|
162
|
+
const originHost = originUrl.hostname;
|
|
163
|
+
const originPort = originUrl.port || undefined;
|
|
164
|
+
if (!isHostAllowed(allowed, originHost, originPort)) {
|
|
165
|
+
writeForbidden(res, { error: "Forbidden origin", host: originHost });
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
writeForbidden(res, { error: "Forbidden origin" });
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else if (options.requireOrigin) {
|
|
175
|
+
writeForbidden(res, { error: "Origin required" });
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
@@ -3,9 +3,13 @@ import type { AgentPort } from "../agent/port.js";
|
|
|
3
3
|
import type { ProviderAuthContext } from "../agent/providerAuth.js";
|
|
4
4
|
import { type RunStoreKind } from "../runstore/createStore.js";
|
|
5
5
|
import type { RunStore } from "../runstore/port.js";
|
|
6
|
+
import type { StoreFilesystemClassification } from "../runstore/storeFilesystem.js";
|
|
6
7
|
import { RunManager } from "../runtime/runManager.js";
|
|
7
8
|
import { type RunChangeBus } from "../runtime/runChangeBus.js";
|
|
8
9
|
import { type McpHttpHandler } from "../mcp/server.js";
|
|
10
|
+
import { type HostConfig } from "../config/hostConfig.js";
|
|
11
|
+
import { bootProviderConfig } from "../agent/bootProviderConfig.js";
|
|
12
|
+
export declare const DEFAULT_GC_INTERVAL_MS: number;
|
|
9
13
|
export type StageflowHostOptions = {
|
|
10
14
|
agent: AgentPort;
|
|
11
15
|
cwd?: string;
|
|
@@ -17,6 +21,10 @@ export type StageflowHostOptions = {
|
|
|
17
21
|
providerAuthContext?: ProviderAuthContext;
|
|
18
22
|
mcpStateless?: boolean;
|
|
19
23
|
runChangeBus?: RunChangeBus;
|
|
24
|
+
env?: NodeJS.ProcessEnv;
|
|
25
|
+
hostConfig?: HostConfig;
|
|
26
|
+
/** Skip HostConfig load (tests that inject store/manager pieces only). */
|
|
27
|
+
skipHostConfig?: boolean;
|
|
20
28
|
};
|
|
21
29
|
export type StageflowHostBootstrap = {
|
|
22
30
|
a2a?: A2aHost;
|
|
@@ -24,11 +32,37 @@ export type StageflowHostBootstrap = {
|
|
|
24
32
|
agentDir: string;
|
|
25
33
|
rootDir: string;
|
|
26
34
|
isGitProject: boolean;
|
|
27
|
-
store
|
|
28
|
-
|
|
35
|
+
/** Absent when `serveBlocked` — store must not be opened after a failed restore. */
|
|
36
|
+
store?: RunStore;
|
|
37
|
+
/** Absent when `serveBlocked` — no RunManager lifecycle/resume. */
|
|
38
|
+
manager?: RunManager;
|
|
29
39
|
runChangeBus: RunChangeBus;
|
|
30
40
|
mcpStateless: boolean;
|
|
31
41
|
providerAuthContext: ProviderAuthContext | undefined;
|
|
32
42
|
mcpHandler: McpHttpHandler;
|
|
43
|
+
hostConfig?: HostConfig;
|
|
44
|
+
providerBoot?: Awaited<ReturnType<typeof bootProviderConfig>>;
|
|
45
|
+
/** Periodic retention GC handle when enabled; already `.unref()`'d. */
|
|
46
|
+
gcInterval?: NodeJS.Timeout;
|
|
47
|
+
stopGcInterval: () => void;
|
|
48
|
+
/** Filesystem classification for `$STAGEFLOW_HOME` (Slot 8). */
|
|
49
|
+
storeFilesystem?: StoreFilesystemClassification;
|
|
50
|
+
/**
|
|
51
|
+
* When set, Host must not serve API/MCP (restore.failed or failed boot apply).
|
|
52
|
+
* /livez still answers; /readyz fails.
|
|
53
|
+
*/
|
|
54
|
+
serveBlocked?: {
|
|
55
|
+
code: string;
|
|
56
|
+
reason: string;
|
|
57
|
+
};
|
|
33
58
|
};
|
|
59
|
+
/** Parse `STAGEFLOW_GC_INTERVAL_MS`; default 1h; `0` disables. */
|
|
60
|
+
export declare function gcIntervalMsFromEnv(env?: NodeJS.ProcessEnv): number;
|
|
61
|
+
/**
|
|
62
|
+
* Start the unattended retention sweep. First fire is one interval after start
|
|
63
|
+
* (`setInterval` semantics). Returns undefined when intervalMs is 0.
|
|
64
|
+
*/
|
|
65
|
+
export declare function startPeriodicRunGc(manager: RunManager, intervalMs: number, options?: {
|
|
66
|
+
logError?: (message: string) => void;
|
|
67
|
+
}): NodeJS.Timeout | undefined;
|
|
34
68
|
export declare function bootstrapStageflowHost(options: StageflowHostOptions): Promise<StageflowHostBootstrap>;
|
package/dist/server/bootstrap.js
CHANGED
|
@@ -1,31 +1,205 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
2
3
|
import { createA2aHost } from "../a2a/server.js";
|
|
4
|
+
import { A2aStore } from "../a2a/store.js";
|
|
3
5
|
import { createRunStoreWithConnection } from "../runstore/createStore.js";
|
|
4
6
|
import { resolveA2aConfigPath } from "../a2a/configDiscovery.js";
|
|
7
|
+
import { ensureGlobalHome } from "../project/globalHome.js";
|
|
5
8
|
import { resolveStageflowContext } from "../project/resolveStageflowContext.js";
|
|
6
9
|
import { findProjectRoot } from "../project/findProjectRoot.js";
|
|
10
|
+
import { warnDurableRootDiskIfNeeded } from "../runstore/diskUsage.js";
|
|
11
|
+
import { assertStoreFilesystemSupported } from "../runstore/storeFilesystem.js";
|
|
12
|
+
import { assertStoreQuickCheck, isSqliteCorruptError, maybeCheckpointResidualWal, rethrowAsStoreIntegrityFailed, } from "../runstore/sqlite/applyStorePragmas.js";
|
|
7
13
|
import { RunManager } from "../runtime/runManager.js";
|
|
14
|
+
import { PI_CODING_AGENT_DIR_ENV } from "../runtime/stageRoots.js";
|
|
8
15
|
import { createRunChangeBus, getRunChangeBusFromWrappedStore, isRunStoreWrapped, wrapRunStoreWithChangeBus, } from "../runtime/runChangeBus.js";
|
|
9
16
|
import { createMcpHttpHandler, resolveMcpStateless, } from "../mcp/server.js";
|
|
17
|
+
import { loadHostConfig, } from "../config/hostConfig.js";
|
|
18
|
+
import { bootProviderConfig } from "../agent/bootProviderConfig.js";
|
|
19
|
+
import { applyPendingRestoreAtBoot, } from "../runstore/restore.js";
|
|
20
|
+
import { assertTmpdirUsable } from "../runstore/assertTmpdir.js";
|
|
21
|
+
import { logger as rootLogger } from "../logging/logger.js";
|
|
22
|
+
export const DEFAULT_GC_INTERVAL_MS = 60 * 60 * 1000;
|
|
23
|
+
function sqliteConnectionFromStore(store) {
|
|
24
|
+
const connection = store.connection;
|
|
25
|
+
if (connection !== undefined &&
|
|
26
|
+
connection !== null &&
|
|
27
|
+
typeof connection.prepare === "function") {
|
|
28
|
+
return connection;
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
/** Parse `STAGEFLOW_GC_INTERVAL_MS`; default 1h; `0` disables. */
|
|
33
|
+
export function gcIntervalMsFromEnv(env = process.env) {
|
|
34
|
+
const raw = env.STAGEFLOW_GC_INTERVAL_MS;
|
|
35
|
+
if (raw === undefined || raw.trim() === "")
|
|
36
|
+
return DEFAULT_GC_INTERVAL_MS;
|
|
37
|
+
const parsed = Number(raw);
|
|
38
|
+
if (!Number.isFinite(parsed) || parsed < 0)
|
|
39
|
+
return DEFAULT_GC_INTERVAL_MS;
|
|
40
|
+
return parsed;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Start the unattended retention sweep. First fire is one interval after start
|
|
44
|
+
* (`setInterval` semantics). Returns undefined when intervalMs is 0.
|
|
45
|
+
*/
|
|
46
|
+
export function startPeriodicRunGc(manager, intervalMs, options) {
|
|
47
|
+
if (intervalMs <= 0)
|
|
48
|
+
return undefined;
|
|
49
|
+
const logError = options?.logError ??
|
|
50
|
+
((message) => {
|
|
51
|
+
console.error(message);
|
|
52
|
+
});
|
|
53
|
+
let inFlight = false;
|
|
54
|
+
return setInterval(() => {
|
|
55
|
+
if (inFlight)
|
|
56
|
+
return;
|
|
57
|
+
inFlight = true;
|
|
58
|
+
void manager
|
|
59
|
+
.gcRuns({ execute: true, channel: "periodic" })
|
|
60
|
+
.then((result) => {
|
|
61
|
+
if (!result.ok) {
|
|
62
|
+
logError(`periodic run GC failed: ${result.reason}`);
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
.catch((err) => {
|
|
66
|
+
logError(`periodic run GC failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
67
|
+
})
|
|
68
|
+
.finally(() => {
|
|
69
|
+
inFlight = false;
|
|
70
|
+
});
|
|
71
|
+
}, intervalMs).unref();
|
|
72
|
+
}
|
|
10
73
|
export async function bootstrapStageflowHost(options) {
|
|
74
|
+
const env = options.env ?? process.env;
|
|
11
75
|
const invocationCwd = options.cwd ?? process.cwd();
|
|
12
76
|
const ctx = await resolveStageflowContext(invocationCwd);
|
|
13
77
|
const cwd = ctx.invocationCwd;
|
|
78
|
+
ensureGlobalHome();
|
|
79
|
+
assertTmpdirUsable(env);
|
|
80
|
+
const hostConfig = options.hostConfig ??
|
|
81
|
+
(options.skipHostConfig
|
|
82
|
+
? undefined
|
|
83
|
+
: loadHostConfig({
|
|
84
|
+
env,
|
|
85
|
+
homeDir: ctx.globalHome,
|
|
86
|
+
overrides: options.maxConcurrent !== undefined
|
|
87
|
+
? { maxConcurrentRuns: options.maxConcurrent }
|
|
88
|
+
: undefined,
|
|
89
|
+
}));
|
|
90
|
+
for (const warning of hostConfig?.warnings ?? []) {
|
|
91
|
+
console.warn(`stageflow: ${warning}`);
|
|
92
|
+
}
|
|
93
|
+
const providerBoot = options.skipHostConfig
|
|
94
|
+
? undefined
|
|
95
|
+
: await bootProviderConfig({
|
|
96
|
+
cwd: invocationCwd,
|
|
97
|
+
env,
|
|
98
|
+
requireProviders: hostConfig?.requireProviders,
|
|
99
|
+
authContext: options.providerAuthContext,
|
|
100
|
+
});
|
|
101
|
+
process.env[PI_CODING_AGENT_DIR_ENV] = path.join(ctx.globalHome, "agent");
|
|
14
102
|
const agentDir = options.agentDir ?? getAgentDir();
|
|
15
103
|
const rootDir = options.rootDir ?? ctx.projectRoot;
|
|
16
104
|
const isGitProject = options.rootDir !== undefined
|
|
17
105
|
? findProjectRoot(rootDir) !== null
|
|
18
106
|
: ctx.isGitProject;
|
|
107
|
+
const storeFilesystem = assertStoreFilesystemSupported(ctx.globalHome, {
|
|
108
|
+
env,
|
|
109
|
+
warn: (message) => console.warn(message),
|
|
110
|
+
});
|
|
111
|
+
const bootLog = rootLogger.child({ component: "bootstrap" });
|
|
112
|
+
let serveBlocked;
|
|
113
|
+
let restoreOutcome = { status: "none" };
|
|
114
|
+
if (!options.store) {
|
|
115
|
+
restoreOutcome = await applyPendingRestoreAtBoot(ctx.globalHome);
|
|
116
|
+
if (restoreOutcome.status === "blocked") {
|
|
117
|
+
serveBlocked = {
|
|
118
|
+
code: "restore_failed",
|
|
119
|
+
reason: restoreOutcome.reason,
|
|
120
|
+
};
|
|
121
|
+
bootLog.error("restore.blocked", `restore.failed: ${restoreOutcome.reason}`);
|
|
122
|
+
}
|
|
123
|
+
else if (restoreOutcome.status === "failed") {
|
|
124
|
+
serveBlocked = {
|
|
125
|
+
code: "restore_failed",
|
|
126
|
+
reason: restoreOutcome.reason,
|
|
127
|
+
};
|
|
128
|
+
bootLog.error("restore.apply_failed", `restore apply failed (attempts=${restoreOutcome.attempts}): ${restoreOutcome.reason}`);
|
|
129
|
+
}
|
|
130
|
+
else if (restoreOutcome.status === "applied") {
|
|
131
|
+
bootLog.info("restore.applied", `restore applied schema_version=${restoreOutcome.result.schema_version}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (serveBlocked !== undefined) {
|
|
135
|
+
const runChangeBus = options.runChangeBus ?? createRunChangeBus();
|
|
136
|
+
const mcpStateless = resolveMcpStateless({
|
|
137
|
+
mcpStateless: options.mcpStateless,
|
|
138
|
+
});
|
|
139
|
+
return {
|
|
140
|
+
cwd,
|
|
141
|
+
agentDir,
|
|
142
|
+
rootDir,
|
|
143
|
+
isGitProject,
|
|
144
|
+
runChangeBus,
|
|
145
|
+
mcpStateless,
|
|
146
|
+
providerAuthContext: options.providerAuthContext,
|
|
147
|
+
mcpHandler: {
|
|
148
|
+
handle: async () => { },
|
|
149
|
+
close: async () => { },
|
|
150
|
+
},
|
|
151
|
+
stopGcInterval: () => { },
|
|
152
|
+
storeFilesystem,
|
|
153
|
+
serveBlocked,
|
|
154
|
+
...(hostConfig !== undefined ? { hostConfig } : {}),
|
|
155
|
+
...(providerBoot !== undefined ? { providerBoot } : {}),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
19
158
|
let rawStore;
|
|
20
159
|
let sqliteConnection;
|
|
160
|
+
const storeRootDir = options.store
|
|
161
|
+
? (options.rootDir ?? ctx.globalHome)
|
|
162
|
+
: ctx.globalHome;
|
|
21
163
|
if (options.store) {
|
|
22
164
|
rawStore = options.store;
|
|
165
|
+
sqliteConnection = sqliteConnectionFromStore(rawStore);
|
|
23
166
|
}
|
|
24
167
|
else {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
168
|
+
try {
|
|
169
|
+
const created = createRunStoreWithConnection({
|
|
170
|
+
rootDir: ctx.globalHome,
|
|
171
|
+
kind: options.storeKind,
|
|
172
|
+
openerMode: "migrate",
|
|
173
|
+
});
|
|
174
|
+
rawStore = created.store;
|
|
175
|
+
sqliteConnection = created.connection;
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
if (isSqliteCorruptError(err)) {
|
|
179
|
+
rethrowAsStoreIntegrityFailed(err);
|
|
180
|
+
}
|
|
181
|
+
throw err;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (sqliteConnection !== undefined) {
|
|
185
|
+
try {
|
|
186
|
+
assertStoreQuickCheck(sqliteConnection);
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
if (isSqliteCorruptError(err)) {
|
|
190
|
+
rethrowAsStoreIntegrityFailed(err);
|
|
191
|
+
}
|
|
192
|
+
throw err;
|
|
193
|
+
}
|
|
194
|
+
maybeCheckpointResidualWal(storeRootDir, sqliteConnection, {
|
|
195
|
+
log: (event, fields) => {
|
|
196
|
+
bootLog.info(event, event, fields);
|
|
197
|
+
},
|
|
198
|
+
});
|
|
28
199
|
}
|
|
200
|
+
const a2aStore = sqliteConnection !== undefined
|
|
201
|
+
? new A2aStore(storeRootDir, sqliteConnection)
|
|
202
|
+
: undefined;
|
|
29
203
|
const boundBus = isRunStoreWrapped(rawStore)
|
|
30
204
|
? getRunChangeBusFromWrappedStore(rawStore)
|
|
31
205
|
: undefined;
|
|
@@ -42,12 +216,30 @@ export async function bootstrapStageflowHost(options) {
|
|
|
42
216
|
projectRoot: rootDir,
|
|
43
217
|
isGitProject,
|
|
44
218
|
store,
|
|
45
|
-
maxConcurrent: options.maxConcurrent
|
|
219
|
+
maxConcurrent: options.maxConcurrent ??
|
|
220
|
+
(hostConfig?.maxConcurrentRunsExplicit
|
|
221
|
+
? hostConfig.maxConcurrentRuns
|
|
222
|
+
: undefined),
|
|
223
|
+
maxQueued: hostConfig?.maxQueued,
|
|
224
|
+
maxConcurrentPerProject: hostConfig?.maxConcurrentRunsPerProject,
|
|
225
|
+
callerQuotas: Object.fromEntries(Object.entries(hostConfig?.callers ?? {}).map(([id, cfg]) => [
|
|
226
|
+
id,
|
|
227
|
+
cfg.maxConcurrent,
|
|
228
|
+
])),
|
|
46
229
|
operatorCatalog: { cwd, agentDir },
|
|
230
|
+
a2aStore,
|
|
47
231
|
});
|
|
48
232
|
await manager.attachWaitingStages();
|
|
49
233
|
await manager.reconcileOrphanedStages();
|
|
234
|
+
await manager.autoResumeInterruptedStages();
|
|
50
235
|
await manager.resumeStalledSchedules();
|
|
236
|
+
await manager.reenqueuePersistedQueuedRuns();
|
|
237
|
+
await warnDurableRootDiskIfNeeded(ctx.globalHome, { env });
|
|
238
|
+
const gcInterval = startPeriodicRunGc(manager, gcIntervalMsFromEnv(env));
|
|
239
|
+
const stopGcInterval = () => {
|
|
240
|
+
if (gcInterval !== undefined)
|
|
241
|
+
clearInterval(gcInterval);
|
|
242
|
+
};
|
|
51
243
|
const mcpStateless = resolveMcpStateless({
|
|
52
244
|
mcpStateless: options.mcpStateless,
|
|
53
245
|
});
|
|
@@ -55,12 +247,19 @@ export async function bootstrapStageflowHost(options) {
|
|
|
55
247
|
manager,
|
|
56
248
|
store,
|
|
57
249
|
cwd,
|
|
250
|
+
agentDir,
|
|
58
251
|
runChangeBus,
|
|
59
252
|
providerAuthContext: options.providerAuthContext,
|
|
60
253
|
projectRoot: rootDir,
|
|
61
254
|
}, { mcpStateless });
|
|
62
255
|
return {
|
|
63
|
-
a2a: await createA2aHost({
|
|
256
|
+
a2a: await createA2aHost({
|
|
257
|
+
manager,
|
|
258
|
+
runStore: store,
|
|
259
|
+
rootDir: storeRootDir,
|
|
260
|
+
connection: sqliteConnection,
|
|
261
|
+
a2aStore,
|
|
262
|
+
}, resolveA2aConfigPath(rootDir)),
|
|
64
263
|
cwd,
|
|
65
264
|
agentDir,
|
|
66
265
|
rootDir,
|
|
@@ -71,5 +270,10 @@ export async function bootstrapStageflowHost(options) {
|
|
|
71
270
|
mcpStateless,
|
|
72
271
|
providerAuthContext: options.providerAuthContext,
|
|
73
272
|
mcpHandler,
|
|
273
|
+
...(hostConfig !== undefined ? { hostConfig } : {}),
|
|
274
|
+
...(providerBoot !== undefined ? { providerBoot } : {}),
|
|
275
|
+
...(gcInterval !== undefined ? { gcInterval } : {}),
|
|
276
|
+
stopGcInterval,
|
|
277
|
+
storeFilesystem,
|
|
74
278
|
};
|
|
75
279
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
export type ControlScope = "read" | "drive";
|
|
3
|
+
export type BearerAuth = {
|
|
4
|
+
scope: ControlScope;
|
|
5
|
+
caller_id: string;
|
|
6
|
+
};
|
|
7
|
+
export type NamedDriveToken = {
|
|
8
|
+
caller_id: string;
|
|
9
|
+
digest: Buffer;
|
|
10
|
+
};
|
|
11
|
+
export type ControlTokens = {
|
|
12
|
+
driveDigest: Buffer | undefined;
|
|
13
|
+
readDigest: Buffer | undefined;
|
|
14
|
+
namedDrive: readonly NamedDriveToken[];
|
|
15
|
+
};
|
|
16
|
+
/** Parse named drive callers from STAGEFLOW_CONTROL_TOKEN_<NAME> / _FILE. */
|
|
17
|
+
export declare function listNamedControlTokenEnvKeys(env?: NodeJS.ProcessEnv): Array<{
|
|
18
|
+
envKey: string;
|
|
19
|
+
caller_id: string;
|
|
20
|
+
isFile: boolean;
|
|
21
|
+
}>;
|
|
22
|
+
export declare function loadControlTokens(env?: NodeJS.ProcessEnv): ControlTokens;
|
|
23
|
+
/** Plain bearer for CLI→host calls. Drive preferred; read only for GET/HEAD. */
|
|
24
|
+
export declare function resolveClientBearerToken(method: string, env?: NodeJS.ProcessEnv): string | undefined;
|
|
25
|
+
export declare function clientAuthorizationHeaders(method: string, env?: NodeJS.ProcessEnv): Record<string, string>;
|
|
26
|
+
export declare function hasDriveToken(tokens: ControlTokens): boolean;
|
|
27
|
+
export declare function hasAnyToken(tokens: ControlTokens): boolean;
|
|
28
|
+
export declare function isLoopbackBind(bind: string): boolean;
|
|
29
|
+
export declare function refuseBindMessage(bind: string): string;
|
|
30
|
+
export declare function assertBindAllowed(bind: string, tokens: ControlTokens): void;
|
|
31
|
+
export declare class BindRefusedError extends Error {
|
|
32
|
+
readonly exitCode = 1;
|
|
33
|
+
constructor(message: string);
|
|
34
|
+
}
|
|
35
|
+
export declare function requiredScopeFor(method: string, pathname: string): ControlScope | null;
|
|
36
|
+
/**
|
|
37
|
+
* Digest once; timingSafeEqual against every loaded digest (no early-return oracle).
|
|
38
|
+
* Named control tokens are drive-only; READ stays the singular global token.
|
|
39
|
+
*/
|
|
40
|
+
export declare function authenticateBearer(tokens: ControlTokens, authorization: string | undefined): BearerAuth | undefined;
|
|
41
|
+
export type EnforceBearerResult = {
|
|
42
|
+
ok: true;
|
|
43
|
+
auth: BearerAuth | undefined;
|
|
44
|
+
} | {
|
|
45
|
+
ok: false;
|
|
46
|
+
};
|
|
47
|
+
export declare function enforceBearerAuth(tokens: ControlTokens, req: IncomingMessage, res: ServerResponse, required: ControlScope): EnforceBearerResult;
|