stageflow 0.33.0 → 0.36.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 +3 -3
- package/dist/agent/adapterSpill.d.ts +14 -0
- package/dist/agent/adapterSpill.js +83 -0
- package/dist/agent/agentBackend.d.ts +2 -2
- package/dist/agent/agentBackend.js +2 -2
- package/dist/agent/claudeAdapter.d.ts +13 -2
- package/dist/agent/claudeAdapter.js +56 -6
- package/dist/agent/cursorProvider.d.ts +3 -0
- package/dist/agent/cursorProvider.js +23 -6
- package/dist/agent/fakeAgent.d.ts +3 -3
- package/dist/agent/piAdapter.d.ts +2 -2
- package/dist/agent/piAdapter.js +125 -15
- package/dist/agent/piIsolatedMcp.d.ts +1 -0
- package/dist/agent/piIsolatedMcp.js +19 -1
- package/dist/agent/piIsolatedMcpProbe.d.ts +11 -0
- package/dist/agent/piIsolatedMcpProbe.js +20 -0
- package/dist/agent/port.d.ts +4 -2
- package/dist/agent/providerAuth.js +36 -3
- package/dist/agent/{resolveAgentPort.d.ts → resolveStagePort.d.ts} +8 -8
- package/dist/agent/{resolveAgentPort.js → resolveStagePort.js} +11 -11
- package/dist/agent/stageTurnDriver.d.ts +33 -0
- package/dist/agent/stageTurnDriver.js +64 -0
- package/dist/agent/toolOutputBudget.d.ts +46 -0
- package/dist/agent/toolOutputBudget.js +97 -0
- package/dist/agent/toolOutputBudgetExtension.d.ts +22 -0
- package/dist/agent/toolOutputBudgetExtension.js +141 -0
- package/dist/agent/toolOutputSpill.d.ts +34 -0
- package/dist/agent/toolOutputSpill.js +85 -0
- package/dist/browser/agentBrowserLiveViewRelay.d.ts +41 -0
- package/dist/browser/agentBrowserLiveViewRelay.js +801 -0
- package/dist/browser/anchor.d.ts +2 -0
- package/dist/browser/anchor.js +2 -1
- package/dist/browser/auditSink.d.ts +8 -0
- package/dist/browser/browserHost.d.ts +29 -0
- package/dist/browser/browserSweep.d.ts +4 -1
- package/dist/browser/browserSweep.js +7 -0
- package/dist/browser/browserTeardown.d.ts +1 -0
- package/dist/browser/browserTeardown.js +32 -2
- package/dist/browser/containerBrowserEndpoint.d.ts +11 -0
- package/dist/browser/containerBrowserEndpoint.js +57 -0
- package/dist/browser/containerBrowserHost.d.ts +24 -0
- package/dist/browser/containerBrowserHost.js +147 -0
- package/dist/browser/dockerSandboxOrchestrator.d.ts +26 -0
- package/dist/browser/dockerSandboxOrchestrator.js +142 -0
- package/dist/browser/fakeLiveViewRelay.d.ts +15 -0
- package/dist/browser/fakeLiveViewRelay.js +94 -0
- package/dist/browser/fakeLiveViewSource.d.ts +6 -0
- package/dist/browser/fakeLiveViewSource.js +16 -0
- package/dist/browser/fakeRemoteBrowserHost.d.ts +2 -1
- package/dist/browser/fakeRemoteBrowserHost.js +2 -1
- package/dist/browser/fakeSandboxOrchestrator.d.ts +9 -0
- package/dist/browser/fakeSandboxOrchestrator.js +44 -0
- package/dist/browser/gateHandoff.d.ts +0 -2
- package/dist/browser/gateHandoff.js +5 -11
- package/dist/browser/hostCapabilities.d.ts +23 -0
- package/dist/browser/hostCapabilities.js +23 -0
- package/dist/browser/humanLogin.d.ts +2 -2
- package/dist/browser/humanLogin.js +11 -2
- package/dist/browser/liveViewPersisted.d.ts +11 -0
- package/dist/browser/liveViewPersisted.js +22 -0
- package/dist/browser/liveViewRelay.d.ts +76 -0
- package/dist/browser/liveViewRelay.js +1 -0
- package/dist/browser/liveViewSessions.d.ts +25 -0
- package/dist/browser/liveViewSessions.js +139 -0
- package/dist/browser/liveViewSource.d.ts +25 -0
- package/dist/browser/liveViewSource.js +7 -0
- package/dist/browser/liveViewTickets.d.ts +46 -0
- package/dist/browser/liveViewTickets.js +122 -0
- package/dist/browser/localBrowserHost.d.ts +10 -0
- package/dist/browser/localBrowserHost.js +53 -7
- package/dist/browser/ownerScope.d.ts +10 -0
- package/dist/browser/ownerScope.js +6 -0
- package/dist/browser/profileStore.d.ts +5 -2
- package/dist/browser/profileStore.js +3 -5
- package/dist/browser/sandboxOrchestrator.d.ts +60 -0
- package/dist/browser/sandboxOrchestrator.js +8 -0
- package/dist/browser/sessionApiSandboxOrchestrator.d.ts +34 -0
- package/dist/browser/sessionApiSandboxOrchestrator.js +108 -0
- package/dist/browser/stageBrowserEnv.d.ts +7 -0
- package/dist/browser/stageBrowserEnv.js +53 -15
- package/dist/browser/stageHandoff.d.ts +47 -0
- package/dist/browser/stageHandoff.js +101 -0
- package/dist/browser/stageProfileLock.d.ts +1 -0
- package/dist/browser/stageProfileLock.js +1 -3
- package/dist/browser/volumeProfileStore.d.ts +7 -0
- package/dist/browser/volumeProfileStore.js +35 -0
- package/dist/channels/config.d.ts +11 -0
- package/dist/channels/config.js +95 -0
- package/dist/channels/configWriter.d.ts +40 -0
- package/dist/channels/configWriter.js +148 -0
- package/dist/channels/connectionEdits.d.ts +34 -0
- package/dist/channels/connectionEdits.js +53 -0
- package/dist/channels/errorInfo.d.ts +18 -0
- package/dist/channels/errorInfo.js +71 -0
- package/dist/channels/errors.d.ts +12 -0
- package/dist/channels/errors.js +13 -0
- package/dist/channels/health.d.ts +41 -0
- package/dist/channels/health.js +99 -0
- package/dist/channels/host.d.ts +55 -0
- package/dist/channels/host.js +185 -0
- package/dist/channels/http.d.ts +31 -0
- package/dist/channels/http.js +79 -0
- package/dist/channels/index.d.ts +10 -0
- package/dist/channels/index.js +9 -0
- package/dist/channels/ledger.d.ts +37 -0
- package/dist/channels/ledger.js +95 -0
- package/dist/channels/providers/agentmail.d.ts +26 -0
- package/dist/channels/providers/agentmail.js +81 -0
- package/dist/channels/providers/kit.d.ts +40 -0
- package/dist/channels/providers/kit.js +78 -0
- package/dist/channels/providers/photonImessage.d.ts +45 -0
- package/dist/channels/providers/photonImessage.js +152 -0
- package/dist/channels/providers/photonWhatsapp.d.ts +49 -0
- package/dist/channels/providers/photonWhatsapp.js +146 -0
- package/dist/channels/providers/slack.d.ts +25 -0
- package/dist/channels/providers/slack.js +101 -0
- package/dist/channels/providers/telegram.d.ts +21 -0
- package/dist/channels/providers/telegram.js +100 -0
- package/dist/channels/providers.d.ts +5 -0
- package/dist/channels/providers.js +22 -0
- package/dist/channels/registry.d.ts +54 -0
- package/dist/channels/registry.js +186 -0
- package/dist/channels/secretStore.d.ts +21 -0
- package/dist/channels/secretStore.js +75 -0
- package/dist/channels/secrets.d.ts +15 -0
- package/dist/channels/secrets.js +50 -0
- package/dist/channels/service.d.ts +54 -0
- package/dist/channels/service.js +307 -0
- package/dist/channels/stageChannels.d.ts +45 -0
- package/dist/channels/stageChannels.js +110 -0
- package/dist/channels/types.d.ts +142 -0
- package/dist/channels/types.js +1 -0
- package/dist/cli/browserCommand.d.ts +3 -1
- package/dist/cli/browserCommand.js +25 -20
- package/dist/cli/doctorCommand.js +3 -1
- package/dist/cli/resolveCliProjectRoot.d.ts +7 -0
- package/dist/cli/resolveCliProjectRoot.js +27 -0
- package/dist/cli/runCommand.d.ts +1 -1
- package/dist/cli/runCommand.js +59 -17
- package/dist/cli/skillsCommand.d.ts +20 -0
- package/dist/cli/skillsCommand.js +74 -31
- package/dist/cli/validateCommand.d.ts +1 -1
- package/dist/cli/validateCommand.js +22 -4
- package/dist/cli.js +4 -4
- package/dist/config/browseCatalog.d.ts +2 -0
- package/dist/config/browseCatalog.js +6 -0
- package/dist/config/catalogRelativePath.d.ts +4 -0
- package/dist/config/catalogRelativePath.js +37 -9
- package/dist/config/createStage.d.ts +1 -0
- package/dist/config/createStage.js +11 -2
- package/dist/config/createTask.d.ts +30 -0
- package/dist/config/createTask.js +161 -0
- package/dist/config/createTrigger.d.ts +15 -0
- package/dist/config/createTrigger.js +32 -25
- package/dist/config/draftPackage.d.ts +42 -0
- package/dist/config/draftPackage.js +220 -23
- package/dist/config/ensureCatalogScanRoot.d.ts +2 -1
- package/dist/config/ensureCatalogScanRoot.js +6 -2
- package/dist/config/findTaskInCatalog.d.ts +33 -0
- package/dist/config/findTaskInCatalog.js +69 -0
- package/dist/config/hostConfig.d.ts +21 -0
- package/dist/config/hostConfig.js +114 -1
- package/dist/config/importTask.d.ts +19 -0
- package/dist/config/importTask.js +133 -0
- package/dist/config/installSkillRequest.d.ts +40 -0
- package/dist/config/installSkillRequest.js +286 -0
- package/dist/config/loadOutcome.d.ts +11 -6
- package/dist/config/loadPipeline.js +27 -4
- package/dist/config/loadStage.js +17 -0
- package/dist/config/loadStageflowManifest.js +4 -1
- package/dist/config/loadTask.js +2 -0
- package/dist/config/loadTrigger.js +2 -0
- package/dist/config/mergePipelineIncludes.d.ts +3 -0
- package/dist/config/mergePipelineIncludes.js +30 -0
- package/dist/config/normalizePipelineStageEntry.js +10 -1
- package/dist/config/pipelineStageKeys.js +1 -0
- package/dist/config/printTargetYaml.js +0 -1
- package/dist/config/publishDraftPackage.d.ts +5 -1
- package/dist/config/publishDraftPackage.js +4 -1
- package/dist/config/resolveCatalogRoots.d.ts +4 -1
- package/dist/config/resolveCatalogRoots.js +18 -1
- package/dist/config/skillUsageIndex.d.ts +6 -0
- package/dist/config/skillUsageIndex.js +39 -0
- package/dist/config/stageBrowser.js +13 -0
- package/dist/config/triggerAdapterStatus.d.ts +11 -0
- package/dist/config/triggerAdapterStatus.js +90 -0
- package/dist/config/triggerCatalog.d.ts +41 -2
- package/dist/config/triggerCatalog.js +107 -15
- package/dist/config/updateTask.d.ts +30 -0
- package/dist/config/updateTask.js +119 -0
- package/dist/config/updateTrigger.d.ts +38 -0
- package/dist/config/updateTrigger.js +220 -0
- package/dist/config/validateCatalog.d.ts +9 -2
- package/dist/config/validateCatalog.js +71 -7
- package/dist/config/workshopCatalogFilenames.d.ts +37 -0
- package/dist/config/workshopCatalogFilenames.js +125 -0
- package/dist/config/workshopCatalogRepair.d.ts +7 -0
- package/dist/config/workshopCatalogRepair.js +144 -0
- package/dist/config/yamlParsePosition.d.ts +4 -0
- package/dist/config/yamlParsePosition.js +9 -0
- package/dist/errors/codes.d.ts +1 -1
- package/dist/errors/codes.js +1 -0
- package/dist/hosted/runtime.d.ts +2 -2
- package/dist/hosted/types.d.ts +1 -1
- package/dist/hosted/types.js +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/mcp/catalogTools.js +1 -0
- package/dist/mcp/getStartedTool.js +15 -6
- package/dist/mcp/triggerTools.js +2 -8
- package/dist/operatorAgent/agentPort.d.ts +11 -0
- package/dist/operatorAgent/agentPort.js +12 -0
- package/dist/operatorAgent/index.d.ts +1 -0
- package/dist/operatorAgent/index.js +1 -0
- package/dist/operatorAgent/piModel.js +26 -6
- package/dist/operatorAgent/profiles/workshopAuthorPlaybook.d.ts +1 -1
- package/dist/operatorAgent/profiles/workshopAuthorPlaybook.js +4 -2
- package/dist/operatorAgent/profiles/workshopAuthorTools.js +103 -16
- package/dist/package-meta.d.ts +1 -1
- package/dist/package-meta.js +1 -1
- package/dist/preflight/channelChecks.d.ts +17 -0
- package/dist/preflight/channelChecks.js +106 -0
- package/dist/preflight/pipelinePreflight.d.ts +9 -2
- package/dist/preflight/pipelinePreflight.js +9 -1
- package/dist/project/ensureProjectRootsFromEnv.d.ts +3 -0
- package/dist/project/ensureProjectRootsFromEnv.js +22 -0
- package/dist/project/globalHome.d.ts +1 -0
- package/dist/project/globalHome.js +18 -0
- package/dist/project/workspaceCatalog.d.ts +10 -0
- package/dist/project/workspaceCatalog.js +91 -0
- package/dist/runstore/backup.js +14 -4
- package/dist/runstore/port.d.ts +11 -1
- package/dist/runstore/restore.js +5 -2
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +8 -0
- package/dist/runstore/sqlite/SqliteRunStore.js +29 -9
- package/dist/runstore/sqlite/migrations/012-trigger-fires.d.ts +7 -0
- package/dist/runstore/sqlite/migrations/012-trigger-fires.js +18 -0
- package/dist/runstore/sqlite/migrations/index.js +2 -0
- package/dist/runstore/stageSnapshot.d.ts +3 -1
- package/dist/runstore/stageSnapshot.js +19 -0
- package/dist/runstore/workspaceLayout.js +27 -0
- package/dist/runstore/writeContainedArtifact.d.ts +1 -0
- package/dist/runstore/writeContainedArtifact.js +52 -0
- package/dist/runtime/credentialBinding.js +14 -6
- package/dist/runtime/emailSource.js +20 -28
- package/dist/runtime/githubPollSource.js +20 -28
- package/dist/runtime/pipelineRunner.d.ts +4 -4
- package/dist/runtime/pipelineRunner.js +3 -1
- package/dist/runtime/pipelineScheduler.d.ts +2 -2
- package/dist/runtime/pipelineScheduler.js +3 -0
- package/dist/runtime/repositoryMaterialize.d.ts +1 -1
- package/dist/runtime/resumeReconstruct.d.ts +2 -2
- package/dist/runtime/resumeTimedOut.d.ts +2 -2
- package/dist/runtime/runManager.d.ts +18 -2
- package/dist/runtime/runManager.js +99 -31
- package/dist/runtime/runRetryCoordinator.d.ts +2 -2
- package/dist/runtime/scheduleSource.js +15 -24
- package/dist/runtime/stageAttemptBootstrap.d.ts +2 -2
- package/dist/runtime/stageAttemptBootstrap.js +26 -1
- package/dist/runtime/stageProcessLauncher.js +33 -3
- package/dist/runtime/stageRoots.js +8 -1
- package/dist/runtime/stageRunner.d.ts +2 -2
- package/dist/runtime/stageRunner.js +8 -2
- package/dist/runtime/stageWorker.js +2 -2
- package/dist/runtime/triggerPort.d.ts +5 -3
- package/dist/runtime/triggerRunner.d.ts +1 -0
- package/dist/runtime/triggerRunner.js +27 -20
- package/dist/runtime/webhookAccept.d.ts +25 -0
- package/dist/runtime/webhookAccept.js +56 -0
- package/dist/server/bootstrap.d.ts +10 -2
- package/dist/server/bootstrap.js +19 -1
- package/dist/server/channelRoutes.d.ts +9 -0
- package/dist/server/channelRoutes.js +88 -0
- package/dist/server/controlToken.d.ts +1 -1
- package/dist/server/controlToken.js +18 -2
- package/dist/server/healthSurfaces.d.ts +12 -0
- package/dist/server/healthSurfaces.js +27 -6
- package/dist/server/http.d.ts +19 -10
- package/dist/server/http.js +616 -85
- package/dist/server/liveViewPath.d.ts +9 -0
- package/dist/server/liveViewPath.js +8 -0
- package/dist/server/liveViewRoutes.d.ts +58 -0
- package/dist/server/liveViewRoutes.js +565 -0
- package/dist/server/mcpHost.d.ts +4 -2
- package/dist/server/mcpHost.js +2 -0
- package/dist/server/readCatalogFile.d.ts +29 -0
- package/dist/server/readCatalogFile.js +215 -0
- package/dist/server/triggerRoutes.d.ts +1 -0
- package/dist/server/triggerRoutes.js +200 -73
- package/dist/tools/sendMessage.d.ts +21 -0
- package/dist/tools/sendMessage.js +25 -0
- package/dist/tools/writeStageArtifact.d.ts +9 -0
- package/dist/tools/writeStageArtifact.js +2 -47
- package/dist/types/pipeline.d.ts +5 -2
- package/dist/types/stage.d.ts +14 -1
- package/dist/types/stageflowManifest.d.ts +1 -1
- package/dist/ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/dist/ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/dist/ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/dist/ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/dist/ui/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
- package/dist/ui/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
- package/dist/ui/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
- package/dist/ui/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
- package/dist/ui/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
- package/dist/ui/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
- package/dist/ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/dist/ui/assets/index-BOhqnPNg.js +260 -0
- package/dist/ui/assets/{index-Bv4p1aPK.css → index-Clv4sJpU.css} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/workshop/autosave.d.ts +2 -2
- package/dist/workshop/autosave.js +4 -6
- package/dist/workshop/buildStore.d.ts +2 -0
- package/dist/workshop/chatAttachments.d.ts +27 -0
- package/dist/workshop/chatAttachments.js +98 -0
- package/dist/workshop/chatTurn.d.ts +5 -2
- package/dist/workshop/chatTurn.js +22 -18
- package/dist/workshop/sessionMutations.d.ts +17 -0
- package/dist/workshop/sessionMutations.js +107 -0
- package/dist/workshop/sessionStore.d.ts +10 -0
- package/dist/workshop/sessionStore.js +52 -0
- package/dist/workshop/workshopPin.d.ts +4 -3
- package/dist/workshop/workshopPin.js +26 -6
- package/dist/workshop/workshopTranscriptTurn.d.ts +8 -0
- package/dist/workshop/workshopTranscriptTurn.js +46 -0
- package/examples/README.md +1 -1
- package/examples/archify-on-pr/README.md +1 -1
- package/examples/archify-on-pr/archify-on-pr.pipeline.yaml +0 -1
- package/examples/archify-on-pr/author-diagrams.yaml +1 -0
- package/examples/channels-smoke/README.md +12 -0
- package/examples/channels-smoke/channels-smoke.pipeline.yaml +11 -0
- package/examples/channels-smoke/channels-smoke.task.yaml +3 -0
- package/examples/channels-smoke/send-email.yaml +32 -0
- package/examples/channels-smoke/send-text.yaml +31 -0
- package/examples/conditional-fork/branch-a.yaml +1 -1
- package/examples/conditional-fork/decide.yaml +2 -1
- package/examples/feature-loop/{implement.yaml → feature-implement.yaml} +1 -1
- package/examples/feature-loop/feature-loop.pipeline.yaml +10 -10
- package/examples/feature-loop/{plan.yaml → feature-plan.yaml} +1 -1
- package/examples/feature-loop/{review.yaml → feature-review.yaml} +1 -1
- package/examples/feedback-loop/{implement.yaml → feedback-implement.yaml} +1 -1
- package/examples/feedback-loop/feedback-loop-wait-human.pipeline.yaml +9 -9
- package/examples/feedback-loop/feedback-loop.pipeline.yaml +9 -9
- package/examples/feedback-loop/{plan.yaml → feedback-plan.yaml} +1 -1
- package/examples/feedback-loop/{review.yaml → feedback-review.yaml} +1 -1
- package/examples/generic-fan-in/{research.yaml → fan-in-research.yaml} +1 -1
- package/examples/generic-fan-in/generic-fan-in-accepted.pipeline.yaml +3 -3
- package/examples/generic-fan-in/generic-fan-in.pipeline.yaml +3 -3
- package/examples/hello-world/{research.yaml → hello-research.yaml} +1 -1
- package/examples/hello-world/hello.pipeline.yaml +2 -2
- package/examples/mcp-hitl-tour/{review.yaml → mcp-hitl-review.yaml} +1 -1
- package/examples/mcp-hitl-tour/mcp-hitl-tour.pipeline.yaml +3 -3
- package/examples/oss-issue-contribution/oss-explain-fix.yaml +1 -0
- package/examples/oss-issue-contribution/oss-explain-issue.yaml +1 -0
- package/examples/oss-issue-contribution/oss-issue-contribution.pipeline.yaml +0 -2
- package/examples/stageflow.yaml +6 -0
- package/examples/triggers/sample-github-pr.trigger.yaml +12 -0
- package/examples/triggers/sample-inbox-email.trigger.yaml +14 -0
- package/examples/triggers/sample-manual-hello.trigger.yaml +5 -0
- package/examples/triggers/sample-nightly-hello.trigger.yaml +8 -0
- package/examples/triggers/sample-signed-webhook.trigger.yaml +11 -0
- package/examples/triggers-readiness/README.md +48 -0
- package/examples/triggers-readiness/readiness-emit.yaml +18 -0
- package/examples/triggers-readiness/readiness.pipeline.yaml +4 -0
- package/examples/triggers-readiness/readiness.task.yaml +6 -0
- package/examples/triggers-readiness/stageflow.yaml +13 -0
- package/examples/triggers-readiness/triggers/readiness-dynamic-manual.trigger.yaml +4 -0
- package/examples/triggers-readiness/triggers/readiness-email.trigger.yaml +12 -0
- package/examples/triggers-readiness/triggers/readiness-github-pr.trigger.yaml +12 -0
- package/examples/triggers-readiness/triggers/readiness-manual.trigger.yaml +5 -0
- package/examples/triggers-readiness/triggers/readiness-schedule.trigger.yaml +8 -0
- package/examples/triggers-readiness/triggers/readiness-webhook.trigger.yaml +11 -0
- package/package.json +8 -3
- package/skills/stageflow/references/control-surface.md +1 -1
- package/skills/stageflow-author/references/catalog-write-conventions.md +2 -2
- package/skills/stageflow-run/SKILL.md +1 -1
- package/skills/stageflow-run/references/mcp-call.md +1 -1
- package/skills/stageflow-setup/SKILL.md +1 -1
- package/dist/ui/assets/index-59cjF0OA.js +0 -133
package/dist/mcp/triggerTools.js
CHANGED
|
@@ -63,10 +63,7 @@ export function registerTriggerTools(server, deps) {
|
|
|
63
63
|
description: "List catalog-declared triggers (same source as GET /api/triggers): manual/schedule/event kind, enabled, and store-recorded last_fired_at/last_run_id/next_run_at when present.",
|
|
64
64
|
inputSchema: z.object({}),
|
|
65
65
|
}, async () => {
|
|
66
|
-
const items = await loadCatalogTriggers(
|
|
67
|
-
if (items === undefined) {
|
|
68
|
-
return textResult({ error: "No Stageflow catalog found", status: 404 }, true);
|
|
69
|
-
}
|
|
66
|
+
const items = await loadCatalogTriggers({ store, bootCwd: cwd });
|
|
70
67
|
return textResult({ triggers: items });
|
|
71
68
|
});
|
|
72
69
|
server.registerTool("get_trigger", {
|
|
@@ -75,10 +72,7 @@ export function registerTriggerTools(server, deps) {
|
|
|
75
72
|
id: z.string(),
|
|
76
73
|
}),
|
|
77
74
|
}, async ({ id }) => {
|
|
78
|
-
const items = await loadCatalogTriggers(
|
|
79
|
-
if (items === undefined) {
|
|
80
|
-
return textResult({ error: "No Stageflow catalog found", status: 404 }, true);
|
|
81
|
-
}
|
|
75
|
+
const items = await loadCatalogTriggers({ store, bootCwd: cwd });
|
|
82
76
|
const found = items.find((item) => item.id === id);
|
|
83
77
|
if (!found) {
|
|
84
78
|
return textResult({ error: `Trigger not found: ${id}`, status: 404 }, true);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type LiveWorkshopOperatorHostOptions } from "./piModel.js";
|
|
2
|
+
import type { OperatorAgentHost } from "./types.js";
|
|
3
|
+
export declare const AGENT_PORT_BACKENDS: readonly ["pi"];
|
|
4
|
+
export type AgentPortBackendId = (typeof AGENT_PORT_BACKENDS)[number];
|
|
5
|
+
/** Stays up, takes a message, and answers. Stage runs stay on StagePort. */
|
|
6
|
+
export type AgentPort = OperatorAgentHost;
|
|
7
|
+
export type AgentPortOptions = LiveWorkshopOperatorHostOptions & {
|
|
8
|
+
backend?: AgentPortBackendId;
|
|
9
|
+
};
|
|
10
|
+
export declare function createPiAgentPort(options: LiveWorkshopOperatorHostOptions): AgentPort;
|
|
11
|
+
export declare function resolveAgentPort(options: AgentPortOptions): AgentPort;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createLiveWorkshopOperatorHost, } from "./piModel.js";
|
|
2
|
+
export const AGENT_PORT_BACKENDS = ["pi"];
|
|
3
|
+
export function createPiAgentPort(options) {
|
|
4
|
+
return createLiveWorkshopOperatorHost(options);
|
|
5
|
+
}
|
|
6
|
+
export function resolveAgentPort(options) {
|
|
7
|
+
const backend = options.backend ?? "pi";
|
|
8
|
+
switch (backend) {
|
|
9
|
+
case "pi":
|
|
10
|
+
return createPiAgentPort(options);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export type { AcceptProposalResult, OperatorAgentContextAdapter, OperatorAgentHost, OperatorAgentProfile, OperatorAgentProposal, OperatorAgentSession, OperatorAgentSessionEvent, OperatorAgentTool, OperatorAgentToolContext, OperatorAgentToolResult, ProposalArtifactDiff, UndoMutationResult, WorkshopDraftContext, WorkshopToolActivityUpdate, } from "./types.js";
|
|
2
2
|
export { createOperatorAgentHost, invokeProfileTool, type OperatorAgentModel, type OperatorAgentModelTurn, } from "./host.js";
|
|
3
3
|
export { createFakeOperatorAgentModel, createWorkshopOperatorHost, type FakeOperatorTurn, type WorkshopOperatorHostOptions, } from "./fakeHost.js";
|
|
4
|
+
export { AGENT_PORT_BACKENDS, createPiAgentPort, resolveAgentPort, type AgentPortBackendId, type AgentPort, type AgentPortOptions, } from "./agentPort.js";
|
|
4
5
|
export { createLiveWorkshopOperatorHost, createPiOperatorAgentModel, type LiveWorkshopOperatorHostOptions, type PiOperatorAgentModel, type PiOperatorAgentModelOptions, type PiOperatorOpenSessionInput, type PiOperatorSessionHandle, type WorkshopTranscriptSeedMessage, } from "./piModel.js";
|
|
5
6
|
export { createFailingDocsRetriever, createFilesystemDocsRetriever, createStubDocsRetriever, resolveWorkshopCatalogRoot, type DocsRetrievalHit, type DocsRetrievalResult, type DocsRetriever, } from "./docsRetrieval.js";
|
|
6
7
|
export { createWorkshopDraftContext, draftTrackStages, emptyDraftPackage, isWorkshopDraftContext, readDraftFromContext, withDestination, withDraft, withProjectRoot, workshopDraftContextAdapter, WORKSHOP_AUTHOR_GREETING, } from "./draftContext.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { createOperatorAgentHost, invokeProfileTool, } from "./host.js";
|
|
2
2
|
export { createFakeOperatorAgentModel, createWorkshopOperatorHost, } from "./fakeHost.js";
|
|
3
|
+
export { AGENT_PORT_BACKENDS, createPiAgentPort, resolveAgentPort, } from "./agentPort.js";
|
|
3
4
|
export { createLiveWorkshopOperatorHost, createPiOperatorAgentModel, } from "./piModel.js";
|
|
4
5
|
export { createFailingDocsRetriever, createFilesystemDocsRetriever, createStubDocsRetriever, resolveWorkshopCatalogRoot, } from "./docsRetrieval.js";
|
|
5
6
|
export { createWorkshopDraftContext, draftTrackStages, emptyDraftPackage, isWorkshopDraftContext, readDraftFromContext, withDestination, withDraft, withProjectRoot, workshopDraftContextAdapter, WORKSHOP_AUTHOR_GREETING, } from "./draftContext.js";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Live Pi-backed OperatorAgentModel for Workshop Author.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Pi backend behind AgentPort, using the shared session factory. Not a StagePort.
|
|
5
|
+
* Workshop tools only —
|
|
5
6
|
* resolveWorkshopToolNames / assertWorkshopToolsExcludeDiskShell. Durable Pi
|
|
6
7
|
* session per host tool-context (R7). Restart: new Pi + transcript replay (KTD7).
|
|
7
8
|
*/
|
|
@@ -10,7 +11,7 @@ import path from "node:path";
|
|
|
10
11
|
import { defineTool, ModelRuntime, resolveCliModel, SessionManager, SettingsManager, } from "@earendil-works/pi-coding-agent";
|
|
11
12
|
import { Type } from "typebox";
|
|
12
13
|
import { createPiAgentSession, createSealedResourceLoader, resolveWorkshopToolNames, } from "../agent/piSessionFactory.js";
|
|
13
|
-
import { cursorBridgePrompt } from "../agent/cursorProvider.js";
|
|
14
|
+
import { bindCursorApiKey, cursorBridgePrompt } from "../agent/cursorProvider.js";
|
|
14
15
|
import { findProviderSupport } from "../agent/providerSupport.js";
|
|
15
16
|
import "../agent/cursorProvider.js";
|
|
16
17
|
import { logger as rootLogger } from "../logging/logger.js";
|
|
@@ -158,8 +159,16 @@ function formatTranscriptReplay(transcript) {
|
|
|
158
159
|
...lines,
|
|
159
160
|
].join("\n");
|
|
160
161
|
}
|
|
162
|
+
function processDataAuthPath() {
|
|
163
|
+
return path.join(globalStageflowHome(), "agent", "auth.json");
|
|
164
|
+
}
|
|
161
165
|
function authNotConfiguredError(authPath) {
|
|
162
|
-
|
|
166
|
+
const dataAuth = processDataAuthPath();
|
|
167
|
+
const unusedDataFile = path.resolve(dataAuth) !== path.resolve(authPath) &&
|
|
168
|
+
isUsableAuthFile(dataAuth)
|
|
169
|
+
? ` A usable auth file at ${dataAuth} in the process data directory is not used.`
|
|
170
|
+
: "";
|
|
171
|
+
return new Error(`Workshop Author provider auth is not configured (auth file missing or empty: ${authPath}).${unusedDataFile} Configure credentials via \`sf providers\` / the operator console Providers page, then retry.`);
|
|
163
172
|
}
|
|
164
173
|
function syncAgentMessages(session, sessionManager) {
|
|
165
174
|
if (session.agent?.state) {
|
|
@@ -185,15 +194,23 @@ function extractAssistantText(message) {
|
|
|
185
194
|
return parts.length > 0 ? parts.join("") : undefined;
|
|
186
195
|
}
|
|
187
196
|
async function openDefaultPiSession(input) {
|
|
188
|
-
|
|
197
|
+
const provider = findProviderSupport(input.modelId);
|
|
198
|
+
if (!provider && !isUsableAuthFile(input.authPath)) {
|
|
189
199
|
throw authNotConfiguredError(input.authPath);
|
|
190
200
|
}
|
|
191
|
-
const provider = findProviderSupport(input.modelId);
|
|
192
201
|
const additionalExtensionPaths = [];
|
|
193
202
|
let restoreProvider;
|
|
203
|
+
let restoreCursorKey;
|
|
204
|
+
const bindCursor = (modelId) => {
|
|
205
|
+
const restore = bindCursorApiKey(modelId);
|
|
206
|
+
if (restore)
|
|
207
|
+
restoreCursorKey = restore;
|
|
208
|
+
};
|
|
209
|
+
bindCursor(input.modelId);
|
|
194
210
|
if (provider) {
|
|
195
211
|
const prepared = provider.prepare(input.modelId);
|
|
196
212
|
if (prepared.error) {
|
|
213
|
+
restoreCursorKey?.();
|
|
197
214
|
throw new Error(prepared.error);
|
|
198
215
|
}
|
|
199
216
|
additionalExtensionPaths.push(...prepared.extensionPaths);
|
|
@@ -260,6 +277,7 @@ async function openDefaultPiSession(input) {
|
|
|
260
277
|
const applyModel = async (modelId) => {
|
|
261
278
|
if (modelId === currentModelId)
|
|
262
279
|
return;
|
|
280
|
+
bindCursor(modelId);
|
|
263
281
|
const provider = findProviderSupport(modelId);
|
|
264
282
|
if (provider) {
|
|
265
283
|
const prepared = provider.prepare(modelId);
|
|
@@ -294,6 +312,7 @@ async function openDefaultPiSession(input) {
|
|
|
294
312
|
catch {
|
|
295
313
|
// best-effort
|
|
296
314
|
}
|
|
315
|
+
restoreCursorKey?.();
|
|
297
316
|
restoreProvider?.();
|
|
298
317
|
},
|
|
299
318
|
};
|
|
@@ -305,6 +324,7 @@ async function openDefaultPiSession(input) {
|
|
|
305
324
|
catch {
|
|
306
325
|
// best-effort
|
|
307
326
|
}
|
|
327
|
+
restoreCursorKey?.();
|
|
308
328
|
restoreProvider?.();
|
|
309
329
|
throw err;
|
|
310
330
|
}
|
|
@@ -522,7 +542,7 @@ export function createLiveWorkshopOperatorHost(options) {
|
|
|
522
542
|
}), [
|
|
523
543
|
createWorkshopAuthorProfile({
|
|
524
544
|
retriever,
|
|
525
|
-
projectRoot
|
|
545
|
+
...(projectRoot !== undefined ? { projectRoot } : {}),
|
|
526
546
|
}),
|
|
527
547
|
]);
|
|
528
548
|
}
|
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
* guidance and public YAML catalog concepts. First-class Workshop editors
|
|
4
4
|
* cover the core path; advanced topics are explain-only.
|
|
5
5
|
*/
|
|
6
|
-
export declare const WORKSHOP_AUTHOR_PLAYBOOK = "You are the Stageflow Workshop Author: a colleague designing a workflow with the operator. You are not a task runner and not a documentation bot.\n\n## How you talk\n- Sound like a working conversation. Short turns, plain words, present tense.\n- Default length is one to four sentences. No headings, no outlines, no catalog recitation, no multi-step plans unless they ask for a plan or a deeper explanation.\n- One move per turn: reflect what you heard, ask the one question that changes the draft, or make a small edit and say what you assumed.\n- Mirror their words. Refer back to decisions already made. If they correct you, drop the old assumption and continue.\n- Do not narrate tools (\"I'll call create_stage\"). The studio shows the draft. Say what changed in plain language.\n- The reference below is private. Use it when you edit. Do not recite it.\n\n## When you ask, when you edit\n- Clarify before you create when a missing detail would change the stages, the wiring, the goal, or a gate. Do not invent that detail.\n- Ask the single most useful question. Add a second only when both answers block the next edit and they don't depend on each other.\n- Make the question specific (\"Should review block shipping until someone accepts?\") rather than open (\"Tell me more\").\n- Skip questions you can default: names, ordinary linear order, a pipeline model when one is already set. Say the assumption in the same breath as the edit.\n- When the request is already specific, edit. Do not ask permission to do what they just asked.\n- Ask before a second pipeline when one is already open. If the answer is still ambiguous, ask once more.\n- When nothing is selected, create an untitled build and focus it.\n- When the operator names an existing pipeline, focus it rather than create a new one.\n- After an edit, one sentence on what landed, then the next open question if one remains. Do not paste YAML unless they ask to see it.\n\nGood: \"A release that waits for review, or one that publishes as soon as checks pass?\"\nBad: a paragraph of pipeline theory, a numbered plan, and three questions at once.\n\n## Tools\n- Never invent a disk path; never use bash/write/edit.\n- Tools (prefer these): list_builds, focus_build, create_build, create_pipeline / edit_pipeline, create_stage / edit_stage, create_task / edit_task, read_draft, validate_draft, save. propose_draft is legacy bulk replace \u2014 avoid unless the operator asks for a full replace. Do not wait for Accept before mutating; propose\u2192Accept gating is superseded by immediate mutate + soft undo.\n- list_builds returns the same rows as the studio and does not create a build. focus_build moves this chat onto a build id, or onto a disk pipeline by project root and path. create_build persists a new untitled build and focuses it, including when another build is already open. Do not refuse create_build.\n- Mutations apply to the in-memory draft immediately (studio updates before Accept). Accept confirms; Reject soft-undos that mutation when the draft fingerprint is unchanged. Soft undo does not reverse a successful save to disk.\n- Chat History/New threads persist under workshop/sessions (transcript + session id + activeBuildId). The draft persists as a build under workshop/builds and is not embedded in the session blob. The studio picker lists open builds and on-disk pipelines.\n- Always read_draft before mutating. Prefer uses: ./id.yaml file-backed stages and Author dialect (io / verify / on_verify_fail, route / entry).\n- Validate with validate_draft; fix hard errors. Warnings like pipeline.model_applies or pipeline.route_all_gated may be intentional.\n- save writes catalog YAML only when the operator asks to persist. If they do not name a folder, omit directory (the host writes workshop/<pipeline-id> and registers that folder so Run lists the pipeline and task). If they name a folder, pass that directory. Do not ask for a path before saving. validate-then-write; allowInvalid only if they explicitly request saving invalid YAML.\n- Stages are domain-agnostic configurable workflows (releases, research, ops, SDLC, \u2026) \u2014 not product stage types.\n- Use retrieve_docs when the operator asks for current docs/examples or when refining against public guidance. If retrieval fails, continue with this playbook alone.\n- Core path = first-class Workshop editors. Advanced topics below are explain-only \u2014 do not invent Workshop editors for them.\n\n## Core: DAG / linear stages\n- Pipeline needs id + non-empty stages. Set model on pipeline (or stage / stageflow.yaml); empty at all tiers fails load.\n- Wire on the source only: entry: true on \u22651 root; forward route: [{ to }]. Never author needs, fork, top-level feedback_loop, route_select, or allow_none.\n- Linear A\u2192B; fan-out = multiple to: (all run); Join = several parents to: the same child; exclusive branch = mutually exclusive ifs on required payload fields (agent emits a discriminator, not successor ids).\n- if: leaf {field,op,value} or all/any/not; ops eq|ne|gt|gte|lt|lte|in|not_in. Path segments must be required in the producer io.output.schema.\n- on: is skip-cascade policy only (default succeeded); it does not launch from failed/skipped.\n- Consumer io.input \u2286 each parent's io.output. Prefer external stages when shared or larger than a tiny draft.\n\n## Core: prompts\n- Required system_prompt. Last tool call of an attempt is emit_stage_envelope (or ask_operator while waiting on HITL).\n- Name required io.output.schema fields in the prompt. summary/payload carry outcomes and artifact pointers only.\n- Land required files with write_stage_artifact (attempt dir). Checkout edits are not after-phase artifact proof.\n- Gated sequence: write artifact \u2192 ask_operator \u2192 emit only after accept; revise on reject in the same attempt.\n- Prefer a configured provider model; siblings after fan-out share a family unless the operator asks otherwise.\n\n## Core: io / verify / on_verify_fail\n- Always author both io.input.schema and io.output.schema (object roots). Success payload is validated on emit.\n- Write Author keys only \u2014 not legacy payload_schema / pre_emit_checks / completion / recovery (those are IR after load).\n- verify lives on the stage body; when: [emit] | [after] | both. Defaults: gate\u2192emit; command/checkout_changes/checklist/payload_schema\u2192after; artifact must set when.\n- Types: gate (+ kind), artifact, command, checklist, payload_schema, checkout_changes (+ path_fields).\n- Emit-phase fail = soft reject (retry same turn). After-phase = verified stage execution hard proof.\n- on_verify_fail on the pipeline entry (beside uses:); needs \u22651 after-phase verify. Prefer repair (idempotent, max_attempts) vs manual (side-effecting publish).\n\n## Core: HITL (ask_operator)\n- gate_kinds omit/[] \u2192 tool off; non-empty allowlist \u2192 tool on (confirm, free_text, artifact_backed, multi_question).\n- Load-bearing gate: body verify type: gate (default emit) \u2014 success emit needs a matching accept / answered exchange.\n- ask_operator does not complete the stage \u2014 only emit_stage_envelope does.\n- artifact_backed paths come from write_stage_artifact (run-relative), not checkout paths.\n\n## Core: artifacts / envelopes\n- Envelope: status, non-empty summary, artifacts (may be []), success payload vs io.output.schema.\n- One advancing emit per attempt. Downstream reads envelopes, not chat. Fan-in Join gets priorEnvelopesByStage.\n- Catalog routing \u2260 agent fork_choice. Rejected: envelope clone_forks.\n\n## Optional task\n- A task (id + goal) may be attached or created for the package; pipeline-only drafts remain valid without one.\n\n## Explain-only (no first-class Workshop editors)\n\n### Clone Chain\nSealed emitter \u2192 clone child \u2192 Join. One named $ref array on the emitter; the child's whole input is that $ref. clone_cap + clone_mode on the emitter pipeline entry only. Empty/over-cap fails emitter emit. Never author clonable, clone_forks, or emit-time skip|once|fanout. Point operators at docs/yaml-catalog Clone Chain and examples/feature-loop.\n\n### Feedback loops\n{ type: loop, to, max_replays, on_max_replays, replay_session } inside source route (forward DAG stays acyclic). Success emit must include envelope feedback_loop: continue | send_back. replay_safe: false blocks replay. Prefer a forward review \u2192 address-feedback \u2192 approve when a separate fix stage is clearer. See examples/feedback-loop.\n\n### Stage MCP\nmcp: [names] on the pipeline entry; project-root .mcp.json; curated stage env interpolation; reserved name stageflow; builtins always available. Validate checks shape/names/PATH; connect/env fail at run. Explain and retrieve docs/examples (e.g. examples/stage-mcp); do not offer a Workshop MCP editor.\n\n### Complex recovery\nBeyond basic repair/manual: operator sf runs recover / MCP recover_manual_stage; after-phase gates/artifacts are history- and disk-aware. Point at verified-stage-execution docs; do not offer Workshop recovery editors.\n\n## Anti-patterns\n- Never needs / fork / top-level feedback_loop \u2192 use route / { type: loop }.\n- Never legacy contract keys \u2192 use io / verify / on_verify_fail.\n- Never omit io or leave model empty at all tiers \u2192 both schemas; pipeline model minimum.\n- Never emit before HITL accept \u2192 verify gate + prompt sequence.\n- Never ambient Host secrets \u2192 stage secrets:.\n- Never clonable / clone_forks \u2192 sealed Clone Chain.\n\nReview-with-blockers: wire review \u2192 address-feedback \u2192 approve/ship; put verdict / blocking_findings in review io.output.schema.\n";
|
|
6
|
+
export declare const WORKSHOP_AUTHOR_PLAYBOOK = "You are the Stageflow Workshop Author: a colleague designing a workflow with the operator. You are not a task runner and not a documentation bot.\n\n## How you talk\n- Sound like a working conversation. Short turns, plain words, present tense.\n- Default length is one to four sentences. No headings, no outlines, no catalog recitation, no multi-step plans unless they ask for a plan or a deeper explanation.\n- One move per turn: reflect what you heard, ask the one question that changes the draft, or make a small edit and say what you assumed.\n- Mirror their words. Refer back to decisions already made. If they correct you, drop the old assumption and continue.\n- Do not narrate tools (\"I'll call create_stage\"). The studio shows the draft. Say what changed in plain language.\n- The reference below is private. Use it when you edit. Do not recite it.\n\n## When you ask, when you edit\n- Clarify before you create when a missing detail would change the stages, the wiring, the goal, or a gate. Do not invent that detail.\n- Ask the single most useful question. Add a second only when both answers block the next edit and they don't depend on each other.\n- Make the question specific (\"Should review block shipping until someone accepts?\") rather than open (\"Tell me more\").\n- Skip questions you can default: names, ordinary linear order, a pipeline model when one is already set. Say the assumption in the same breath as the edit.\n- When the request is already specific, edit. Do not ask permission to do what they just asked.\n- Ask before a second pipeline when one is already open. If the answer is still ambiguous, ask once more.\n- When nothing is selected, create an untitled build and focus it.\n- When the operator names an existing pipeline, focus it rather than create a new one.\n- After an edit, one sentence on what landed, then the next open question if one remains. Do not paste YAML unless they ask to see it.\n\nGood: \"A release that waits for review, or one that publishes as soon as checks pass?\"\nBad: a paragraph of pipeline theory, a numbered plan, and three questions at once.\n\n## Tools\n- Never invent a disk path; never use bash/write/edit.\n- Tools (prefer these): list_builds, focus_build, create_build, create_pipeline / edit_pipeline, create_stage / edit_stage, create_task / edit_task, read_draft, validate_draft, save. propose_draft is legacy bulk replace \u2014 avoid unless the operator asks for a full replace. Do not wait for Accept before mutating; propose\u2192Accept gating is superseded by immediate mutate + soft undo.\n- list_builds returns the same rows as the studio and does not create a build; use catalogListed before telling the operator a pipeline or task appears on Tasks or Pipelines. focus_build moves this chat onto a build id, or onto a disk pipeline by project root and path. create_build persists a new untitled build and focuses it, including when another build is already open. Do not refuse create_build.\n- Build tie (projectRoot + relativePath on a row) is the last saved catalog anchor for that build id \u2014 not every path on disk counts for Tasks (e.g. image package roots or paths outside stageflow.yaml scan). When the operator asks what is on disk for Run/Tasks, cite the last successful save tool result (published.project_root_path, published.pipeline_path, published.task_path) or a catalogListed row. Draft-only task changes are not on disk until save returns task_path.\n- Mutations apply to the in-memory draft immediately (studio updates before Accept). Accept confirms; Reject soft-undos that mutation when the draft fingerprint is unchanged. Soft undo does not reverse a successful save to disk.\n- Chat History/New threads persist under workshop/sessions (transcript + session id + activeBuildId). The draft persists as a build under workshop/builds and is not embedded in the session blob. The studio picker lists open builds and on-disk pipelines.\n- Always read_draft before mutating. Prefer uses: ./id.yaml file-backed stages and Author dialect (io / verify / on_verify_fail, route / entry).\n- Validate with validate_draft; fix hard errors. Warnings like pipeline.model_applies or pipeline.route_all_gated may be intentional.\n- save writes catalog YAML only when the operator asks to persist. If they do not name a folder, omit directory (the host writes workshop/<pipeline-id> and registers that folder so Run lists the pipeline and task). If they name a folder, pass that directory. Do not ask for a path before saving. validate-then-write; allowInvalid only if they explicitly request saving invalid YAML. After save, use published.* paths in the tool result \u2014 not list_builds tie alone \u2014 to answer where files landed and whether a task file was written.\n- Stages are domain-agnostic configurable workflows (releases, research, ops, SDLC, \u2026) \u2014 not product stage types.\n- Use retrieve_docs when the operator asks for current docs/examples or when refining against public guidance. If retrieval fails, continue with this playbook alone.\n- Core path = first-class Workshop editors. Advanced topics below are explain-only \u2014 do not invent Workshop editors for them.\n\n## Core: DAG / linear stages\n- Pipeline needs id + non-empty stages. Set model on pipeline (or stage / stageflow.yaml); empty at all tiers fails load.\n- Wire on the source only: entry: true on \u22651 root; forward route: [{ to }]. Never author needs, fork, top-level feedback_loop, route_select, or allow_none.\n- Linear A\u2192B; fan-out = multiple to: (all run); Join = several parents to: the same child; exclusive branch = mutually exclusive ifs on required payload fields (agent emits a discriminator, not successor ids).\n- if: leaf {field,op,value} or all/any/not; ops eq|ne|gt|gte|lt|lte|in|not_in. Path segments must be required in the producer io.output.schema.\n- on: is skip-cascade policy only (default succeeded); it does not launch from failed/skipped.\n- Consumer io.input \u2286 each parent's io.output. Prefer external stages when shared or larger than a tiny draft.\n\n## Core: prompts\n- Required system_prompt. Last tool call of an attempt is emit_stage_envelope (or ask_operator while waiting on HITL).\n- Optional skill: belongs on the stage body (create_stage / edit_stage body or stage file) \u2014 never on a uses: wrapper.\n- Name required io.output.schema fields in the prompt. summary/payload carry outcomes and artifact pointers only.\n- Land required files with write_stage_artifact (attempt dir). Checkout edits are not after-phase artifact proof.\n- Gated sequence: write artifact \u2192 ask_operator \u2192 emit only after accept; revise on reject in the same attempt.\n- Prefer a configured provider model; siblings after fan-out share a family unless the operator asks otherwise.\n\n## Core: io / verify / on_verify_fail\n- Always author both io.input.schema and io.output.schema (object roots). Success payload is validated on emit.\n- Write Author keys only \u2014 not legacy payload_schema / pre_emit_checks / completion / recovery (those are IR after load).\n- verify lives on the stage body; when: [emit] | [after] | both. Defaults: gate\u2192emit; command/checkout_changes/checklist/payload_schema\u2192after; artifact must set when.\n- Types: gate (+ kind), artifact, command, checklist, payload_schema, checkout_changes (+ path_fields).\n- Emit-phase fail = soft reject (retry same turn). After-phase = verified stage execution hard proof.\n- on_verify_fail on the pipeline entry (beside uses:); needs \u22651 after-phase verify. Prefer repair (idempotent, max_attempts) vs manual (side-effecting publish).\n\n## Core: HITL (ask_operator)\n- gate_kinds omit/[] \u2192 tool off; non-empty allowlist \u2192 tool on (confirm, free_text, artifact_backed, multi_question).\n- Load-bearing gate: body verify type: gate (default emit) \u2014 success emit needs a matching accept / answered exchange.\n- ask_operator does not complete the stage \u2014 only emit_stage_envelope does.\n- artifact_backed paths come from write_stage_artifact (run-relative), not checkout paths.\n\n## Core: artifacts / envelopes\n- Envelope: status, non-empty summary, artifacts (may be []), success payload vs io.output.schema.\n- One advancing emit per attempt. Downstream reads envelopes, not chat. Fan-in Join gets priorEnvelopesByStage.\n- Catalog routing \u2260 agent fork_choice. Rejected: envelope clone_forks.\n\n## Optional task\n- A task (id + goal) may be attached or created for the package; pipeline-only drafts remain valid without one.\n\n## Explain-only (no first-class Workshop editors)\n\n### Clone Chain\nSealed emitter \u2192 clone child \u2192 Join. One named $ref array on the emitter; the child's whole input is that $ref. clone_cap + clone_mode on the emitter pipeline entry only. Empty/over-cap fails emitter emit. Never author clonable, clone_forks, or emit-time skip|once|fanout. Point operators at docs/yaml-catalog Clone Chain and examples/feature-loop.\n\n### Feedback loops\n{ type: loop, to, max_replays, on_max_replays, replay_session } inside source route (forward DAG stays acyclic). Success emit must include envelope feedback_loop: continue | send_back. replay_safe: false blocks replay. Prefer a forward review \u2192 address-feedback \u2192 approve when a separate fix stage is clearer. See examples/feedback-loop.\n\n### Stage MCP\nmcp: [names] on the pipeline entry; project-root .mcp.json; curated stage env interpolation; reserved name stageflow; builtins always available. Validate checks shape/names/PATH; connect/env fail at run. Explain and retrieve docs/examples (e.g. examples/stage-mcp); do not offer a Workshop MCP editor.\n\n### Complex recovery\nBeyond basic repair/manual: operator sf runs recover / MCP recover_manual_stage; after-phase gates/artifacts are history- and disk-aware. Point at verified-stage-execution docs; do not offer Workshop recovery editors.\n\n## Anti-patterns\n- Never needs / fork / top-level feedback_loop \u2192 use route / { type: loop }.\n- Never legacy contract keys \u2192 use io / verify / on_verify_fail.\n- Never omit io or leave model empty at all tiers \u2192 both schemas; pipeline model minimum.\n- Never emit before HITL accept \u2192 verify gate + prompt sequence.\n- Never ambient Host secrets \u2192 stage secrets:.\n- Never clonable / clone_forks \u2192 sealed Clone Chain.\n\nReview-with-blockers: wire review \u2192 address-feedback \u2192 approve/ship; put verdict / blocking_findings in review io.output.schema.\n";
|
|
@@ -30,12 +30,13 @@ Bad: a paragraph of pipeline theory, a numbered plan, and three questions at onc
|
|
|
30
30
|
## Tools
|
|
31
31
|
- Never invent a disk path; never use bash/write/edit.
|
|
32
32
|
- Tools (prefer these): list_builds, focus_build, create_build, create_pipeline / edit_pipeline, create_stage / edit_stage, create_task / edit_task, read_draft, validate_draft, save. propose_draft is legacy bulk replace — avoid unless the operator asks for a full replace. Do not wait for Accept before mutating; propose→Accept gating is superseded by immediate mutate + soft undo.
|
|
33
|
-
- list_builds returns the same rows as the studio and does not create a build. focus_build moves this chat onto a build id, or onto a disk pipeline by project root and path. create_build persists a new untitled build and focuses it, including when another build is already open. Do not refuse create_build.
|
|
33
|
+
- list_builds returns the same rows as the studio and does not create a build; use catalogListed before telling the operator a pipeline or task appears on Tasks or Pipelines. focus_build moves this chat onto a build id, or onto a disk pipeline by project root and path. create_build persists a new untitled build and focuses it, including when another build is already open. Do not refuse create_build.
|
|
34
|
+
- Build tie (projectRoot + relativePath on a row) is the last saved catalog anchor for that build id — not every path on disk counts for Tasks (e.g. image package roots or paths outside stageflow.yaml scan). When the operator asks what is on disk for Run/Tasks, cite the last successful save tool result (published.project_root_path, published.pipeline_path, published.task_path) or a catalogListed row. Draft-only task changes are not on disk until save returns task_path.
|
|
34
35
|
- Mutations apply to the in-memory draft immediately (studio updates before Accept). Accept confirms; Reject soft-undos that mutation when the draft fingerprint is unchanged. Soft undo does not reverse a successful save to disk.
|
|
35
36
|
- Chat History/New threads persist under workshop/sessions (transcript + session id + activeBuildId). The draft persists as a build under workshop/builds and is not embedded in the session blob. The studio picker lists open builds and on-disk pipelines.
|
|
36
37
|
- Always read_draft before mutating. Prefer uses: ./id.yaml file-backed stages and Author dialect (io / verify / on_verify_fail, route / entry).
|
|
37
38
|
- Validate with validate_draft; fix hard errors. Warnings like pipeline.model_applies or pipeline.route_all_gated may be intentional.
|
|
38
|
-
- save writes catalog YAML only when the operator asks to persist. If they do not name a folder, omit directory (the host writes workshop/<pipeline-id> and registers that folder so Run lists the pipeline and task). If they name a folder, pass that directory. Do not ask for a path before saving. validate-then-write; allowInvalid only if they explicitly request saving invalid YAML.
|
|
39
|
+
- save writes catalog YAML only when the operator asks to persist. If they do not name a folder, omit directory (the host writes workshop/<pipeline-id> and registers that folder so Run lists the pipeline and task). If they name a folder, pass that directory. Do not ask for a path before saving. validate-then-write; allowInvalid only if they explicitly request saving invalid YAML. After save, use published.* paths in the tool result — not list_builds tie alone — to answer where files landed and whether a task file was written.
|
|
39
40
|
- Stages are domain-agnostic configurable workflows (releases, research, ops, SDLC, …) — not product stage types.
|
|
40
41
|
- Use retrieve_docs when the operator asks for current docs/examples or when refining against public guidance. If retrieval fails, continue with this playbook alone.
|
|
41
42
|
- Core path = first-class Workshop editors. Advanced topics below are explain-only — do not invent Workshop editors for them.
|
|
@@ -50,6 +51,7 @@ Bad: a paragraph of pipeline theory, a numbered plan, and three questions at onc
|
|
|
50
51
|
|
|
51
52
|
## Core: prompts
|
|
52
53
|
- Required system_prompt. Last tool call of an attempt is emit_stage_envelope (or ask_operator while waiting on HITL).
|
|
54
|
+
- Optional skill: belongs on the stage body (create_stage / edit_stage body or stage file) — never on a uses: wrapper.
|
|
53
55
|
- Name required io.output.schema fields in the prompt. summary/payload carry outcomes and artifact pointers only.
|
|
54
56
|
- Land required files with write_stage_artifact (attempt dir). Checkout edits are not after-phase artifact proof.
|
|
55
57
|
- Gated sequence: write artifact → ask_operator → emit only after accept; revise on reject in the same attempt.
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
import { parse as parseYaml } from "yaml";
|
|
3
|
+
import { CatalogPathError, resolveDefaultWriteRoot, } from "../../config/catalogRelativePath.js";
|
|
2
4
|
import { defaultWorkshopPackageDirectory } from "../../config/ensureCatalogScanRoot.js";
|
|
3
5
|
import { publishDraftPackage } from "../../config/publishDraftPackage.js";
|
|
6
|
+
import { globalStageflowHome } from "../../project/globalHome.js";
|
|
4
7
|
import { emptyDraftPackage, isWorkshopDraftContext, readDraftFromContext, withDraft, } from "../draftContext.js";
|
|
5
8
|
import { focusUnboundWorkshopBuild, getWorkshopBuild, listWorkshopPickerRows, resolvePickerCatalogPipelines, } from "../../workshop/buildStore.js";
|
|
6
9
|
import { focusWorkshopBuildPointer, pinWorkshopBuildOnCreate, recordPinnedWorkshopSave, resolveLiveWorkshopBinding, resolvePinnedSaveDestination, } from "../../workshop/workshopPin.js";
|
|
@@ -99,10 +102,10 @@ function readWorkshopContext(ctx) {
|
|
|
99
102
|
return raw;
|
|
100
103
|
return { draft: readDraftFromContext(raw) };
|
|
101
104
|
}
|
|
102
|
-
function resolveDestination(ctx, args) {
|
|
105
|
+
function resolveDestination(ctx, args, catalogRootKind) {
|
|
103
106
|
const directory = stringArg(args, "directory") ??
|
|
104
107
|
readWorkshopContext(ctx).destination?.directory ??
|
|
105
|
-
defaultWorkshopPackageDirectory(readDraftFromContext(ctx.getContext()).pipeline.id);
|
|
108
|
+
defaultWorkshopPackageDirectory(readDraftFromContext(ctx.getContext()).pipeline.id, catalogRootKind);
|
|
106
109
|
const pipelineFilename = stringArg(args, "pipelineFilename") ??
|
|
107
110
|
stringArg(args, "pipeline_filename") ??
|
|
108
111
|
readWorkshopContext(ctx).destination?.pipelineFilename;
|
|
@@ -113,6 +116,64 @@ function resolveProjectRoot(ctx, defaultProjectRoot) {
|
|
|
113
116
|
// (or the profile/host default passed into createSaveTool).
|
|
114
117
|
return readWorkshopContext(ctx).projectRoot ?? defaultProjectRoot ?? null;
|
|
115
118
|
}
|
|
119
|
+
async function resolveSaveWriteTarget(ctx, options) {
|
|
120
|
+
const bound = resolveProjectRoot(ctx, options.projectRoot);
|
|
121
|
+
const live = liveWorkshopBinding(ctx);
|
|
122
|
+
const storeRoot = live?.storeRoot ?? globalStageflowHome();
|
|
123
|
+
const { createRunStore } = await import("../../runstore/createStore.js");
|
|
124
|
+
const store = createRunStore({ rootDir: storeRoot });
|
|
125
|
+
try {
|
|
126
|
+
if (bound) {
|
|
127
|
+
try {
|
|
128
|
+
const { wireRoot } = await resolveDefaultWriteRoot({ store }, bound);
|
|
129
|
+
return {
|
|
130
|
+
ok: true,
|
|
131
|
+
target: {
|
|
132
|
+
fsRoot: wireRoot.path,
|
|
133
|
+
wireProjectRoot: wireRoot.project_root,
|
|
134
|
+
catalogRootKind: wireRoot.kind,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
if (err instanceof CatalogPathError &&
|
|
140
|
+
err.code === "unknown_project_root") {
|
|
141
|
+
const abs = path.resolve(bound);
|
|
142
|
+
return {
|
|
143
|
+
ok: true,
|
|
144
|
+
target: {
|
|
145
|
+
fsRoot: abs,
|
|
146
|
+
wireProjectRoot: abs,
|
|
147
|
+
catalogRootKind: "registered",
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
if (err instanceof CatalogPathError) {
|
|
152
|
+
return { ok: false, error: err.message };
|
|
153
|
+
}
|
|
154
|
+
throw err;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const { wireRoot } = await resolveDefaultWriteRoot({ store }, undefined);
|
|
158
|
+
return {
|
|
159
|
+
ok: true,
|
|
160
|
+
target: {
|
|
161
|
+
fsRoot: wireRoot.path,
|
|
162
|
+
wireProjectRoot: wireRoot.project_root,
|
|
163
|
+
catalogRootKind: wireRoot.kind,
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
if (err instanceof CatalogPathError) {
|
|
169
|
+
return { ok: false, error: err.message };
|
|
170
|
+
}
|
|
171
|
+
throw err;
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
await store.close();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
116
177
|
export function buildCreateStageDraft(draft, args) {
|
|
117
178
|
const existingIds = new Set(draft.pipeline.stages.map((s) => (typeof s.id === "string" ? s.id : "")));
|
|
118
179
|
let stageId = stringArg(args, "id") ??
|
|
@@ -366,7 +427,7 @@ async function listPickerCatalog(storeRoot) {
|
|
|
366
427
|
export function createWorkshopBuildTools() {
|
|
367
428
|
const listBuildsTool = {
|
|
368
429
|
name: "list_builds",
|
|
369
|
-
description: "List the same workshop rows as the studio picker (open builds and unbound disk pipelines). Does not create a build. Works when no build is focused.",
|
|
430
|
+
description: "List the same workshop rows as the studio picker (open builds and unbound disk pipelines). Each row includes catalogListed (whether Tasks/Pipelines would list that pipeline path). Does not create a build. Works when no build is focused.",
|
|
370
431
|
async handler(_args, ctx) {
|
|
371
432
|
const live = liveWorkshopBinding(ctx);
|
|
372
433
|
if (!live) {
|
|
@@ -374,7 +435,16 @@ export function createWorkshopBuildTools() {
|
|
|
374
435
|
}
|
|
375
436
|
try {
|
|
376
437
|
const catalog = await listPickerCatalog(live.storeRoot);
|
|
377
|
-
const rows = listWorkshopPickerRows(live.storeRoot, catalog.pipelines, catalog.roots)
|
|
438
|
+
const rows = listWorkshopPickerRows(live.storeRoot, catalog.pipelines, catalog.roots).map((row) => {
|
|
439
|
+
if (!row.projectRoot || !row.relativePath) {
|
|
440
|
+
return { ...row, catalogListed: false };
|
|
441
|
+
}
|
|
442
|
+
const relPath = row.relativePath;
|
|
443
|
+
const normalize = (value) => value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
444
|
+
const listed = catalog.pipelines.some((pipeline) => pipeline.project_root === row.projectRoot &&
|
|
445
|
+
normalize(pipeline.path) === normalize(relPath));
|
|
446
|
+
return { ...row, catalogListed: listed };
|
|
447
|
+
});
|
|
378
448
|
return { ok: true, content: { rows } };
|
|
379
449
|
}
|
|
380
450
|
catch (err) {
|
|
@@ -556,23 +626,24 @@ export function createWorkshopAuthorMutatingTools() {
|
|
|
556
626
|
export function createSaveTool(options = {}) {
|
|
557
627
|
return {
|
|
558
628
|
name: "save",
|
|
559
|
-
description: "Validate-then-write the draft via catalog facades (createDraftPackage / overwriteDraftPackage). When directory is omitted, writes under workshop/<pipeline-id
|
|
629
|
+
description: "Validate-then-write the draft via catalog facades (createDraftPackage / overwriteDraftPackage). When directory is omitted, writes under workshop/<pipeline-id> on registered catalogs (or <pipeline-id> on the workspace catalog). The host adds registered workshop paths to stageflow.yaml so Run can see them. Pass directory only when the operator names a folder. Soft undo does not reverse disk. Prefer mode auto; set allowInvalid only when the operator explicitly requests saving invalid YAML. Do not pass projectRoot — the host binds it.",
|
|
560
630
|
async handler(args, ctx) {
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
if (!destination) {
|
|
631
|
+
const writeTarget = await resolveSaveWriteTarget(ctx, options);
|
|
632
|
+
if (!writeTarget.ok) {
|
|
564
633
|
return {
|
|
565
634
|
ok: false,
|
|
566
635
|
content: null,
|
|
567
|
-
error:
|
|
636
|
+
error: writeTarget.error,
|
|
568
637
|
};
|
|
569
638
|
}
|
|
570
|
-
const
|
|
571
|
-
|
|
639
|
+
const { fsRoot, catalogRootKind, wireProjectRoot } = writeTarget.target;
|
|
640
|
+
const pinnedDestination = resolvePinnedSaveDestination(ctx.getContext(), fsRoot);
|
|
641
|
+
const destination = pinnedDestination ?? resolveDestination(ctx, args, catalogRootKind);
|
|
642
|
+
if (!destination) {
|
|
572
643
|
return {
|
|
573
644
|
ok: false,
|
|
574
645
|
content: null,
|
|
575
|
-
error: "
|
|
646
|
+
error: "destination is required — set context.destination or pass directory (do not invent a disk path)",
|
|
576
647
|
};
|
|
577
648
|
}
|
|
578
649
|
const draft = readDraftFromContext(ctx.getContext());
|
|
@@ -589,7 +660,9 @@ export function createSaveTool(options = {}) {
|
|
|
589
660
|
: {}),
|
|
590
661
|
...(allowInvalid ? { allowInvalid: true } : {}),
|
|
591
662
|
};
|
|
592
|
-
const published = await publishDraftPackage(
|
|
663
|
+
const published = await publishDraftPackage(fsRoot, input, mode, {
|
|
664
|
+
catalogRootKind,
|
|
665
|
+
});
|
|
593
666
|
const result = published.write;
|
|
594
667
|
if (result.ok) {
|
|
595
668
|
const current = readWorkshopContext(ctx);
|
|
@@ -602,20 +675,34 @@ export function createSaveTool(options = {}) {
|
|
|
602
675
|
? { pipelineFilename: destination.pipelineFilename }
|
|
603
676
|
: {}),
|
|
604
677
|
},
|
|
605
|
-
projectRoot,
|
|
678
|
+
projectRoot: fsRoot,
|
|
606
679
|
});
|
|
607
680
|
recordPinnedWorkshopSave(ctx.getContext(), {
|
|
608
681
|
directory: destination.directory,
|
|
609
682
|
...(destination.pipelineFilename
|
|
610
683
|
? { pipelineFilename: destination.pipelineFilename }
|
|
611
684
|
: {}),
|
|
612
|
-
projectRoot,
|
|
685
|
+
projectRoot: fsRoot,
|
|
613
686
|
draft,
|
|
687
|
+
relativePath: result.pipelinePath,
|
|
614
688
|
});
|
|
615
689
|
}
|
|
616
690
|
return {
|
|
617
691
|
ok: result.ok,
|
|
618
|
-
content: result
|
|
692
|
+
content: result.ok
|
|
693
|
+
? {
|
|
694
|
+
...result,
|
|
695
|
+
published: {
|
|
696
|
+
project_root: wireProjectRoot,
|
|
697
|
+
project_root_path: fsRoot,
|
|
698
|
+
pipeline_path: result.pipelinePath,
|
|
699
|
+
stage_paths: result.stagePaths,
|
|
700
|
+
...(result.taskPath !== undefined
|
|
701
|
+
? { task_path: result.taskPath }
|
|
702
|
+
: { task_path: null }),
|
|
703
|
+
},
|
|
704
|
+
}
|
|
705
|
+
: result,
|
|
619
706
|
...(result.ok ? {} : { error: result.error }),
|
|
620
707
|
};
|
|
621
708
|
},
|
package/dist/package-meta.d.ts
CHANGED
package/dist/package-meta.js
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AnyChannelProvider } from "../channels/types.js";
|
|
2
|
+
import type { LoadedPipeline } from "../types/pipeline.js";
|
|
3
|
+
import type { PreflightItem } from "./pipelinePreflight.js";
|
|
4
|
+
export type ChannelCheckOptions = {
|
|
5
|
+
projectRoot: string;
|
|
6
|
+
hostEnv?: NodeJS.ProcessEnv;
|
|
7
|
+
providers?: readonly AnyChannelProvider[];
|
|
8
|
+
/** Waits between attempts. Each entry allows one more retry. */
|
|
9
|
+
retryDelaysMs?: readonly number[];
|
|
10
|
+
attemptTimeoutMs?: number;
|
|
11
|
+
};
|
|
12
|
+
export declare const DEFAULT_CHANNEL_RETRY_DELAYS_MS: readonly [500, 1500];
|
|
13
|
+
export declare const DEFAULT_CHANNEL_ATTEMPT_TIMEOUT_MS = 8000;
|
|
14
|
+
/** Live check of every connection a pipeline's stages declare. Never sends a message. */
|
|
15
|
+
export declare function checkStageChannels(loaded: LoadedPipeline, options: ChannelCheckOptions): Promise<PreflightItem[]>;
|
|
16
|
+
/** One line naming each failed connection and why. Codes and plain reasons only, never a secret. */
|
|
17
|
+
export declare function channelFailureSummary(checks: readonly PreflightItem[]): string | undefined;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { ChannelError } from "../channels/errors.js";
|
|
2
|
+
import { ChannelRegistry } from "../channels/registry.js";
|
|
3
|
+
import { channelErrorReason } from "../channels/errorInfo.js";
|
|
4
|
+
import { channelTarget } from "../channels/stageChannels.js";
|
|
5
|
+
export const DEFAULT_CHANNEL_RETRY_DELAYS_MS = [500, 1500];
|
|
6
|
+
export const DEFAULT_CHANNEL_ATTEMPT_TIMEOUT_MS = 8000;
|
|
7
|
+
function failed(code, retryable) {
|
|
8
|
+
return { state: "failed", checkedAt: new Date().toISOString(), error: { code, retryable } };
|
|
9
|
+
}
|
|
10
|
+
async function attempt(run, timeoutMs) {
|
|
11
|
+
const controller = new AbortController();
|
|
12
|
+
let timer;
|
|
13
|
+
const timeout = new Promise(resolve => {
|
|
14
|
+
timer = setTimeout(() => { controller.abort(); resolve(failed("CHANNEL_TIMEOUT", true)); }, timeoutMs);
|
|
15
|
+
});
|
|
16
|
+
try {
|
|
17
|
+
return await Promise.race([run(controller.signal), timeout]);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
return error instanceof ChannelError
|
|
21
|
+
? failed(error.code, error.retryable)
|
|
22
|
+
: failed("CHANNEL_CONNECTION_FAILED", false);
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Retries only failures the provider marked transient. Authentication, configuration,
|
|
30
|
+
* permission and target problems return at once so a bad setup is never slowed or hidden.
|
|
31
|
+
*/
|
|
32
|
+
async function withRetry(run, options) {
|
|
33
|
+
let result = await attempt(run, options.timeoutMs);
|
|
34
|
+
for (const delay of options.delays) {
|
|
35
|
+
if (result.state === "ok" || result.error?.retryable !== true)
|
|
36
|
+
break;
|
|
37
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
38
|
+
result = await attempt(run, options.timeoutMs);
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
function item(connection, health) {
|
|
43
|
+
if (health.state === "ok")
|
|
44
|
+
return { kind: "channel", status: "ok", connection };
|
|
45
|
+
const code = health.error?.code ?? "CHANNEL_CONNECTION_FAILED";
|
|
46
|
+
return {
|
|
47
|
+
kind: "channel", status: "channel_check_failed", connection, channelCode: code,
|
|
48
|
+
retryable: health.error?.retryable ?? false, message: `connection "${connection}": ${code}`,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** Live check of every connection a pipeline's stages declare. Never sends a message. */
|
|
52
|
+
export async function checkStageChannels(loaded, options) {
|
|
53
|
+
const targetsByConnection = new Map();
|
|
54
|
+
for (const stage of loaded.stages) {
|
|
55
|
+
for (const channel of stage.channels ?? []) {
|
|
56
|
+
const targets = targetsByConnection.get(channel.connection) ?? new Map();
|
|
57
|
+
targets.set(JSON.stringify(channelTarget(channel)), channelTarget(channel));
|
|
58
|
+
targetsByConnection.set(channel.connection, targets);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (targetsByConnection.size === 0)
|
|
62
|
+
return [];
|
|
63
|
+
let registry;
|
|
64
|
+
try {
|
|
65
|
+
registry = new ChannelRegistry({ cwd: options.projectRoot, env: options.hostEnv, providers: options.providers });
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
const code = error instanceof ChannelError ? error.code : "CHANNEL_INVALID_INPUT";
|
|
69
|
+
return [...targetsByConnection.keys()].map(connection => item(connection, failed(code, false)));
|
|
70
|
+
}
|
|
71
|
+
const retry = {
|
|
72
|
+
delays: options.retryDelaysMs ?? DEFAULT_CHANNEL_RETRY_DELAYS_MS,
|
|
73
|
+
timeoutMs: options.attemptTimeoutMs ?? DEFAULT_CHANNEL_ATTEMPT_TIMEOUT_MS,
|
|
74
|
+
};
|
|
75
|
+
return Promise.all([...targetsByConnection].map(async ([id, targets]) => {
|
|
76
|
+
let resolved;
|
|
77
|
+
let provider;
|
|
78
|
+
try {
|
|
79
|
+
provider = registry.providerFor(id);
|
|
80
|
+
resolved = registry.resolve(id);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
return item(id, error instanceof ChannelError ? failed(error.code, false) : failed("CHANNEL_CONNECTION_FAILED", false));
|
|
84
|
+
}
|
|
85
|
+
const health = await withRetry(signal => provider.check(resolved, signal), retry);
|
|
86
|
+
if (health.state !== "ok" || !provider.checkTargets)
|
|
87
|
+
return item(id, health);
|
|
88
|
+
const validated = [...targets.values()].map(target => provider.validateTarget(target));
|
|
89
|
+
if (validated.some(result => !result.ok))
|
|
90
|
+
return item(id, failed("CHANNEL_TARGET_INVALID", false));
|
|
91
|
+
const values = validated.flatMap(result => (result.ok ? [result.value] : []));
|
|
92
|
+
return item(id, await withRetry(signal => provider.checkTargets(resolved, values, signal), retry));
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
/** One line naming each failed connection and why. Codes and plain reasons only, never a secret. */
|
|
96
|
+
export function channelFailureSummary(checks) {
|
|
97
|
+
const failed = checks.filter(check => check.kind === "channel" && check.status === "channel_check_failed");
|
|
98
|
+
if (failed.length === 0)
|
|
99
|
+
return undefined;
|
|
100
|
+
return failed
|
|
101
|
+
.map(check => {
|
|
102
|
+
const code = check.channelCode ?? "CHANNEL_CONNECTION_FAILED";
|
|
103
|
+
return `${check.connection}: ${channelErrorReason(code)} (${code})`;
|
|
104
|
+
})
|
|
105
|
+
.join("; ");
|
|
106
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { LoadedPipeline } from "../types/pipeline.js";
|
|
2
2
|
import { type ToolRequirement } from "../config/toolRequires.js";
|
|
3
|
+
import { type ChannelCheckOptions } from "./channelChecks.js";
|
|
3
4
|
import { type CheckToolchainOptions, type ToolchainCheckResult } from "./toolchain.js";
|
|
4
|
-
export type PreflightCheckKind = "toolchain" | "secret" | "mcp";
|
|
5
|
+
export type PreflightCheckKind = "toolchain" | "secret" | "mcp" | "channel";
|
|
5
6
|
export type PreflightItem = {
|
|
6
7
|
kind: PreflightCheckKind;
|
|
7
|
-
status: "ok" | "missing_tool" | "tool_version_mismatch" | "unknown_version" | "secret_unavailable" | "stage.unknown_secret" | "stage.denied_secret" | "unresolved_var" | "missing_catalog" | "unknown_server" | "invalid_config" | "untrusted_config_origin";
|
|
8
|
+
status: "ok" | "missing_tool" | "tool_version_mismatch" | "unknown_version" | "secret_unavailable" | "stage.unknown_secret" | "stage.denied_secret" | "unresolved_var" | "missing_catalog" | "unknown_server" | "invalid_config" | "untrusted_config_origin" | "channel_check_failed";
|
|
8
9
|
tool?: string;
|
|
9
10
|
required?: string;
|
|
10
11
|
found?: string;
|
|
@@ -12,6 +13,11 @@ export type PreflightItem = {
|
|
|
12
13
|
secret?: string;
|
|
13
14
|
stageId?: string;
|
|
14
15
|
server?: string;
|
|
16
|
+
/** Connection id for `kind: "channel"`. */
|
|
17
|
+
connection?: string;
|
|
18
|
+
/** Provider-neutral channel error code, such as CHANNEL_AUTH_FAILED. */
|
|
19
|
+
channelCode?: string;
|
|
20
|
+
retryable?: boolean;
|
|
15
21
|
message?: string;
|
|
16
22
|
};
|
|
17
23
|
export type PipelinePreflightResult = {
|
|
@@ -33,6 +39,7 @@ export declare function runPipelinePreflight(loaded: LoadedPipeline, options: {
|
|
|
33
39
|
strict?: boolean;
|
|
34
40
|
/** start_run: unknown_version passes; missing/mismatch fail. */
|
|
35
41
|
forStart?: boolean;
|
|
42
|
+
channels?: Pick<ChannelCheckOptions, "providers" | "retryDelaysMs" | "attemptTimeoutMs">;
|
|
36
43
|
}): Promise<PipelinePreflightResult>;
|
|
37
44
|
export declare function preflightFailureCode(result: PipelinePreflightResult, options?: {
|
|
38
45
|
strict?: boolean;
|