stageflow 0.32.0 → 0.35.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 +4 -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 +50 -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/fakeAgent.js +0 -6
- package/dist/agent/piAdapter.d.ts +2 -2
- package/dist/agent/piAdapter.js +116 -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 +2 -2
- package/dist/agent/providerAuth.d.ts +6 -0
- package/dist/agent/providerAuth.js +86 -17
- package/dist/agent/{resolveAgentPort.d.ts → resolveStagePort.d.ts} +8 -6
- package/dist/agent/{resolveAgentPort.js → resolveStagePort.js} +13 -9
- 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/cli/browserCommand.d.ts +3 -1
- package/dist/cli/browserCommand.js +25 -20
- 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 +41 -0
- package/dist/config/draftPackage.js +216 -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 +113 -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/listConfig.d.ts +0 -16
- package/dist/config/listConfig.js +0 -3
- package/dist/config/loadOutcome.d.ts +11 -6
- package/dist/config/loadPipeline.js +0 -1
- package/dist/config/loadStage.js +2 -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.js +3 -0
- package/dist/config/normalizePipelineStageEntry.js +10 -1
- 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 +6 -2
- package/dist/config/validateCatalog.js +48 -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/hosted/containment.d.ts +3 -0
- package/dist/hosted/containment.js +36 -0
- package/dist/hosted/errors.d.ts +21 -0
- package/dist/hosted/errors.js +30 -0
- package/dist/hosted/packageSkills.d.ts +3 -0
- package/dist/hosted/packageSkills.js +80 -0
- package/dist/hosted/runtime.d.ts +12 -0
- package/dist/hosted/runtime.js +596 -0
- package/dist/hosted/sequencer.d.ts +28 -0
- package/dist/hosted/sequencer.js +129 -0
- package/dist/hosted/translate.d.ts +3 -0
- package/dist/hosted/translate.js +43 -0
- package/dist/hosted/types.d.ts +108 -0
- package/dist/hosted/types.js +10 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.js +6 -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/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/projection/projectRun.d.ts +0 -1
- package/dist/projection/projectRun.js +0 -1
- 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/containerLimits.d.ts +0 -3
- package/dist/runtime/containerLimits.js +0 -5
- package/dist/runtime/credentialBinding.d.ts +3 -0
- package/dist/runtime/credentialBinding.js +23 -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/pipelineScheduler.d.ts +2 -2
- package/dist/runtime/pipelineScheduler.js +3 -0
- 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 +95 -30
- package/dist/runtime/runRetentionSweep.d.ts +0 -2
- package/dist/runtime/runRetentionSweep.js +0 -8
- 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 +10 -1
- package/dist/runtime/stageProcessLauncher.js +2 -2
- package/dist/runtime/stageRoots.js +12 -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 +7 -2
- package/dist/server/bootstrap.js +15 -1
- 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 +605 -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/readEmail.d.ts +3 -3
- package/dist/tools/writeStageArtifact.d.ts +9 -0
- package/dist/tools/writeStageArtifact.js +2 -47
- package/dist/types/pipeline.d.ts +1 -1
- package/dist/types/stage.d.ts +1 -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-BoFPkKFg.js +256 -0
- package/dist/ui/assets/{index-Bv4p1aPK.css → index-CIPbbBUi.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/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 +9 -1
- 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
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { access, mkdir, realpath, unlink, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { stringify as stringifyYaml } from "yaml";
|
|
4
|
+
import { getCatalogScanPaths } from "./browseCatalog.js";
|
|
5
|
+
import { STAGE_ID_PATTERN } from "./createStage.js";
|
|
6
|
+
import { loadTaskOutcome } from "./loadTask.js";
|
|
7
|
+
import { catalogContextFromStageflow } from "./resolveCatalogContext.js";
|
|
8
|
+
import { resolveStageflowContext } from "../project/resolveStageflowContext.js";
|
|
9
|
+
const OPTIONAL_KEYS = ["context", "constraints", "checkout", "repository", "ref"];
|
|
10
|
+
function isPlainObject(value) {
|
|
11
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
12
|
+
}
|
|
13
|
+
export function validateTaskId(id) {
|
|
14
|
+
if (id.length === 0 || id.length > 64) {
|
|
15
|
+
return "id must be 1-64 characters";
|
|
16
|
+
}
|
|
17
|
+
if (!STAGE_ID_PATTERN.test(id)) {
|
|
18
|
+
return "id must be lowercase kebab-case";
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
function optionalCreateString(body, key) {
|
|
23
|
+
if (!Object.hasOwn(body, key) || body[key] === undefined || body[key] === null) {
|
|
24
|
+
return { ok: true };
|
|
25
|
+
}
|
|
26
|
+
if (typeof body[key] !== "string") {
|
|
27
|
+
return { ok: false, error: `${key} must be a string` };
|
|
28
|
+
}
|
|
29
|
+
if (body[key].length === 0)
|
|
30
|
+
return { ok: true };
|
|
31
|
+
return { ok: true, value: body[key] };
|
|
32
|
+
}
|
|
33
|
+
export function parseCreateTaskBody(body) {
|
|
34
|
+
if (!isPlainObject(body)) {
|
|
35
|
+
return { ok: false, status: 400, error: "Request body must be an object" };
|
|
36
|
+
}
|
|
37
|
+
if (typeof body.directory !== "string" || body.directory.trim().length === 0) {
|
|
38
|
+
return { ok: false, status: 400, error: "directory is required" };
|
|
39
|
+
}
|
|
40
|
+
const directory = body.directory.trim().replace(/\\/g, "/").replace(/\/+$/, "");
|
|
41
|
+
if (directory.length === 0) {
|
|
42
|
+
return { ok: false, status: 400, error: "directory is required" };
|
|
43
|
+
}
|
|
44
|
+
if (typeof body.id !== "string") {
|
|
45
|
+
return { ok: false, status: 400, error: "id is required" };
|
|
46
|
+
}
|
|
47
|
+
const idError = validateTaskId(body.id);
|
|
48
|
+
if (idError) {
|
|
49
|
+
return { ok: false, status: 400, error: idError };
|
|
50
|
+
}
|
|
51
|
+
if (typeof body.goal !== "string" || body.goal.length === 0) {
|
|
52
|
+
return { ok: false, status: 400, error: "goal is required" };
|
|
53
|
+
}
|
|
54
|
+
const input = {
|
|
55
|
+
directory,
|
|
56
|
+
id: body.id,
|
|
57
|
+
goal: body.goal,
|
|
58
|
+
};
|
|
59
|
+
for (const key of OPTIONAL_KEYS) {
|
|
60
|
+
const parsed = optionalCreateString(body, key);
|
|
61
|
+
if (!parsed.ok) {
|
|
62
|
+
return { ok: false, status: 400, error: parsed.error };
|
|
63
|
+
}
|
|
64
|
+
if (parsed.value !== undefined)
|
|
65
|
+
input[key] = parsed.value;
|
|
66
|
+
}
|
|
67
|
+
return input;
|
|
68
|
+
}
|
|
69
|
+
export function resolveTaskDirectory(projectRoot, directory) {
|
|
70
|
+
const absDirectory = path.resolve(projectRoot, directory);
|
|
71
|
+
const rel = path.relative(projectRoot, absDirectory);
|
|
72
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
return absDirectory;
|
|
76
|
+
}
|
|
77
|
+
async function fileExists(filePath) {
|
|
78
|
+
try {
|
|
79
|
+
await access(filePath);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export function taskFieldsToYaml(fields) {
|
|
87
|
+
const doc = {
|
|
88
|
+
id: fields.id,
|
|
89
|
+
goal: fields.goal,
|
|
90
|
+
};
|
|
91
|
+
for (const key of OPTIONAL_KEYS) {
|
|
92
|
+
const value = fields[key];
|
|
93
|
+
if (typeof value === "string" && value.length > 0)
|
|
94
|
+
doc[key] = value;
|
|
95
|
+
}
|
|
96
|
+
const text = stringifyYaml(doc, { indent: 2, lineWidth: 0 });
|
|
97
|
+
return text.endsWith("\n") ? text : `${text}\n`;
|
|
98
|
+
}
|
|
99
|
+
export async function findTaskIdCollision(projectRoot, id, targetPath) {
|
|
100
|
+
if (await fileExists(targetPath)) {
|
|
101
|
+
return path.relative(projectRoot, targetPath).replace(/\\/g, "/");
|
|
102
|
+
}
|
|
103
|
+
const ctx = catalogContextFromStageflow(await resolveStageflowContext(projectRoot));
|
|
104
|
+
const scanPaths = await getCatalogScanPaths(ctx);
|
|
105
|
+
if (!scanPaths)
|
|
106
|
+
return null;
|
|
107
|
+
const resolvedTarget = path.resolve(targetPath);
|
|
108
|
+
for (const filePath of scanPaths.taskPaths) {
|
|
109
|
+
if (path.resolve(filePath) === resolvedTarget)
|
|
110
|
+
continue;
|
|
111
|
+
const outcome = await loadTaskOutcome(filePath);
|
|
112
|
+
if (outcome.ok && outcome.value.id === id) {
|
|
113
|
+
return path.relative(projectRoot, filePath).replace(/\\/g, "/");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
function taskDetail(task, projectRoot, filePath) {
|
|
119
|
+
return {
|
|
120
|
+
...task,
|
|
121
|
+
path: path.relative(projectRoot, filePath).replace(/\\/g, "/"),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
export async function createTask(rawProjectRoot, input) {
|
|
125
|
+
const idError = validateTaskId(input.id);
|
|
126
|
+
if (idError) {
|
|
127
|
+
return { ok: false, status: 400, error: idError };
|
|
128
|
+
}
|
|
129
|
+
const projectRoot = await realpath(rawProjectRoot).catch(() => rawProjectRoot);
|
|
130
|
+
const directory = resolveTaskDirectory(projectRoot, input.directory);
|
|
131
|
+
if (!directory) {
|
|
132
|
+
return { ok: false, status: 400, error: "directory must be inside the project root" };
|
|
133
|
+
}
|
|
134
|
+
const filePath = path.join(directory, `${input.id}.task.yaml`);
|
|
135
|
+
const collision = await findTaskIdCollision(projectRoot, input.id, filePath);
|
|
136
|
+
if (collision) {
|
|
137
|
+
const targetRel = path.relative(projectRoot, filePath).replace(/\\/g, "/");
|
|
138
|
+
const error = collision === targetRel
|
|
139
|
+
? `Task file already exists (${collision})`
|
|
140
|
+
: `Task id already exists (${collision})`;
|
|
141
|
+
return { ok: false, status: 409, error };
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
await mkdir(directory, { recursive: true });
|
|
145
|
+
await writeFile(filePath, taskFieldsToYaml(input), "utf8");
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
149
|
+
return { ok: false, status: 500, error: message };
|
|
150
|
+
}
|
|
151
|
+
const outcome = await loadTaskOutcome(filePath);
|
|
152
|
+
if (!outcome.ok) {
|
|
153
|
+
await unlink(filePath).catch(() => { });
|
|
154
|
+
return {
|
|
155
|
+
ok: false,
|
|
156
|
+
status: 400,
|
|
157
|
+
error: outcome.issues[0]?.message ?? "Invalid task",
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return { ok: true, task: taskDetail(outcome.value, projectRoot, filePath) };
|
|
161
|
+
}
|
|
@@ -23,5 +23,20 @@ export type CreateTriggerResult = {
|
|
|
23
23
|
status: 400 | 409 | 422 | 500;
|
|
24
24
|
error: string;
|
|
25
25
|
};
|
|
26
|
+
type ParsedField<T> = {
|
|
27
|
+
ok: true;
|
|
28
|
+
value: T | undefined;
|
|
29
|
+
} | {
|
|
30
|
+
ok: false;
|
|
31
|
+
message: string;
|
|
32
|
+
};
|
|
33
|
+
export declare function parseScheduleField(raw: unknown): ParsedField<TriggerSchedule>;
|
|
34
|
+
export declare function parseEventField(raw: unknown): ParsedField<TriggerEvent>;
|
|
26
35
|
export declare function parseCreateTriggerBody(body: unknown): CreateTriggerInput | CreateTriggerParseError;
|
|
36
|
+
export declare function scheduleCronError(schedule: TriggerSchedule): string | null;
|
|
37
|
+
export declare function triggerReferenceError(scanPaths: {
|
|
38
|
+
pipelinePaths: string[];
|
|
39
|
+
taskPaths: string[];
|
|
40
|
+
}, pipeline: string, task: string | undefined): Promise<string | null>;
|
|
27
41
|
export declare function createTrigger(rawProjectRoot: string, input: CreateTriggerInput): Promise<CreateTriggerResult>;
|
|
42
|
+
export {};
|
|
@@ -21,7 +21,7 @@ function validateTriggerId(id) {
|
|
|
21
21
|
}
|
|
22
22
|
return null;
|
|
23
23
|
}
|
|
24
|
-
function parseScheduleField(raw) {
|
|
24
|
+
export function parseScheduleField(raw) {
|
|
25
25
|
if (raw === undefined)
|
|
26
26
|
return { ok: true, value: undefined };
|
|
27
27
|
if (!isPlainObject(raw)) {
|
|
@@ -41,7 +41,7 @@ function parseScheduleField(raw) {
|
|
|
41
41
|
},
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
-
function parseEventField(raw) {
|
|
44
|
+
export function parseEventField(raw) {
|
|
45
45
|
if (raw === undefined)
|
|
46
46
|
return { ok: true, value: undefined };
|
|
47
47
|
if (!isPlainObject(raw)) {
|
|
@@ -129,6 +129,29 @@ function resolveDirectory(projectRoot, directory) {
|
|
|
129
129
|
}
|
|
130
130
|
return absDirectory;
|
|
131
131
|
}
|
|
132
|
+
export function scheduleCronError(schedule) {
|
|
133
|
+
try {
|
|
134
|
+
new Cron(schedule.cron, schedule.timezone !== undefined ? { timezone: schedule.timezone } : {});
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
139
|
+
return `Invalid schedule.cron: ${message}`;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
export async function triggerReferenceError(scanPaths, pipeline, task) {
|
|
143
|
+
const pipelineIds = await collectPipelineIdsFromPaths(scanPaths.pipelinePaths);
|
|
144
|
+
if (!pipelineIds.has(pipeline)) {
|
|
145
|
+
return `Trigger references unknown pipeline "${pipeline}"`;
|
|
146
|
+
}
|
|
147
|
+
if (task !== undefined) {
|
|
148
|
+
const taskIds = await collectTaskIdsFromPaths(scanPaths.taskPaths);
|
|
149
|
+
if (!taskIds.has(task)) {
|
|
150
|
+
return `Trigger references unknown task "${task}"`;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
132
155
|
function triggerInputToYaml(input, enabled) {
|
|
133
156
|
const doc = {
|
|
134
157
|
id: input.id,
|
|
@@ -172,34 +195,17 @@ export async function createTrigger(rawProjectRoot, input) {
|
|
|
172
195
|
error: `Trigger id already exists (${path.relative(projectRoot, collisionPath).replace(/\\/g, "/")})`,
|
|
173
196
|
};
|
|
174
197
|
}
|
|
175
|
-
const
|
|
176
|
-
if (
|
|
177
|
-
return {
|
|
178
|
-
ok: false,
|
|
179
|
-
status: 422,
|
|
180
|
-
error: `Trigger references unknown pipeline "${input.pipeline}"`,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
if (input.task !== undefined) {
|
|
184
|
-
const taskIds = await collectTaskIdsFromPaths(scanPaths.taskPaths);
|
|
185
|
-
if (!taskIds.has(input.task)) {
|
|
186
|
-
return {
|
|
187
|
-
ok: false,
|
|
188
|
-
status: 422,
|
|
189
|
-
error: `Trigger references unknown task "${input.task}"`,
|
|
190
|
-
};
|
|
191
|
-
}
|
|
198
|
+
const refError = await triggerReferenceError(scanPaths, input.pipeline, input.task);
|
|
199
|
+
if (refError) {
|
|
200
|
+
return { ok: false, status: 422, error: refError };
|
|
192
201
|
}
|
|
193
202
|
if (input.kind === "schedule") {
|
|
194
203
|
if (!input.schedule) {
|
|
195
204
|
return { ok: false, status: 422, error: "schedule.cron is required for kind=schedule" };
|
|
196
205
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
catch (err) {
|
|
201
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
202
|
-
return { ok: false, status: 422, error: `Invalid schedule.cron: ${message}` };
|
|
206
|
+
const cronError = scheduleCronError(input.schedule);
|
|
207
|
+
if (cronError) {
|
|
208
|
+
return { ok: false, status: 422, error: cronError };
|
|
203
209
|
}
|
|
204
210
|
}
|
|
205
211
|
const filePath = path.join(directory, `${input.id}.trigger.yaml`);
|
|
@@ -227,6 +233,7 @@ export async function createTrigger(rawProjectRoot, input) {
|
|
|
227
233
|
trigger: {
|
|
228
234
|
...loadOutcome.value,
|
|
229
235
|
definition_ref: relPath,
|
|
236
|
+
project_root: projectRoot,
|
|
230
237
|
},
|
|
231
238
|
};
|
|
232
239
|
}
|
|
@@ -81,9 +81,50 @@ export type LoadTaskArtifactResult = {
|
|
|
81
81
|
status: 400 | 404;
|
|
82
82
|
error: string;
|
|
83
83
|
};
|
|
84
|
+
/** Move uses-wrapper skill: onto the matching stage artifact body, then clear the wrapper. */
|
|
85
|
+
export declare function hoistUsesWrapperSkills(draft: DraftPackage): DraftPackage;
|
|
84
86
|
export declare function validateDraftPackage(draft: DraftPackage, options?: ValidateDraftPackageOptions): Promise<ValidationResult>;
|
|
85
87
|
export declare function overwriteDraftPackage(projectRoot: string, input: OverwriteDraftPackageInput): Promise<OverwriteDraftPackageResult>;
|
|
86
88
|
export declare function createDraftPackage(projectRoot: string, input: CreateDraftPackageInput): Promise<CreateDraftPackageResult>;
|
|
89
|
+
export type DraftPlanMode = "create" | "overwrite";
|
|
90
|
+
export type DraftPlanFile = {
|
|
91
|
+
path: string;
|
|
92
|
+
kind: "pipeline" | "stage" | "task";
|
|
93
|
+
action: "new" | "overwrite" | "unchanged";
|
|
94
|
+
added: number;
|
|
95
|
+
removed: number;
|
|
96
|
+
};
|
|
97
|
+
export type DraftPlanResult = {
|
|
98
|
+
pipelinePath: string;
|
|
99
|
+
directory: string;
|
|
100
|
+
files: DraftPlanFile[];
|
|
101
|
+
pipelineIdTaken: boolean;
|
|
102
|
+
};
|
|
103
|
+
export type PlanDraftPackageOptions = {
|
|
104
|
+
mode?: DraftPlanMode;
|
|
105
|
+
catalogPipelines?: ReadonlyArray<{
|
|
106
|
+
id: string;
|
|
107
|
+
path: string;
|
|
108
|
+
}>;
|
|
109
|
+
};
|
|
110
|
+
export declare function countLineChanges(before: string, after: string): {
|
|
111
|
+
added: number;
|
|
112
|
+
removed: number;
|
|
113
|
+
};
|
|
114
|
+
export declare function planDraftPackage(projectRoot: string, input: CreateDraftPackageInput, options?: PlanDraftPackageOptions): Promise<({
|
|
115
|
+
ok: true;
|
|
116
|
+
} & DraftPlanResult) | {
|
|
117
|
+
ok: false;
|
|
118
|
+
status: 400;
|
|
119
|
+
error: string;
|
|
120
|
+
}>;
|
|
121
|
+
export declare function parsePlanDraftPackageBody(body: unknown): (CreateDraftPackageInput & {
|
|
122
|
+
mode: DraftPlanMode;
|
|
123
|
+
}) | {
|
|
124
|
+
ok: false;
|
|
125
|
+
status: 400;
|
|
126
|
+
error: string;
|
|
127
|
+
};
|
|
87
128
|
export declare function parseDraftPackageBody(body: unknown): DraftPackage | {
|
|
88
129
|
ok: false;
|
|
89
130
|
status: 400;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { access, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
1
|
+
import { access, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { loadPipelineOutcome } from "./loadPipeline.js";
|
|
@@ -6,6 +6,8 @@ import { parseTaskFile } from "./loadTask.js";
|
|
|
6
6
|
import { stringifyTargetYaml } from "./printTargetYaml.js";
|
|
7
7
|
import { readYamlObject } from "./readYamlObject.js";
|
|
8
8
|
import { buildValidationResult, findingsFromLoadIssues, loadPipelineValidated, validatePipeline, } from "./validateCatalog.js";
|
|
9
|
+
import { normalizeDraftCatalogFilenames, taskIdFromDraftTask, } from "./workshopCatalogFilenames.js";
|
|
10
|
+
import { reconcileWorkshopCatalogPackageDir } from "./workshopCatalogRepair.js";
|
|
9
11
|
function isPlainObject(value) {
|
|
10
12
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
11
13
|
}
|
|
@@ -15,10 +17,57 @@ function normalizeStageRelPath(stagePath) {
|
|
|
15
17
|
function pipelineHasExternalUses(pipeline) {
|
|
16
18
|
return pipeline.stages.some((stage) => typeof stage.uses === "string" && stage.uses.trim().length > 0);
|
|
17
19
|
}
|
|
20
|
+
function stripUsesWrapperSkill(stages) {
|
|
21
|
+
return stages.map((stage) => {
|
|
22
|
+
if (typeof stage.uses !== "string" || stage.skill === undefined)
|
|
23
|
+
return stage;
|
|
24
|
+
const { skill: _skill, ...rest } = stage;
|
|
25
|
+
return rest;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/** Move uses-wrapper skill: onto the matching stage artifact body, then clear the wrapper. */
|
|
29
|
+
export function hoistUsesWrapperSkills(draft) {
|
|
30
|
+
const artifacts = draft.stages ? [...draft.stages] : [];
|
|
31
|
+
const byPath = new Map(artifacts.map((artifact, index) => [
|
|
32
|
+
normalizeStageRelPath(artifact.path),
|
|
33
|
+
index,
|
|
34
|
+
]));
|
|
35
|
+
const stages = draft.pipeline.stages.map((stage) => {
|
|
36
|
+
if (typeof stage.uses !== "string" || stage.skill === undefined)
|
|
37
|
+
return stage;
|
|
38
|
+
const skill = typeof stage.skill === "string" && stage.skill.trim()
|
|
39
|
+
? stage.skill.trim()
|
|
40
|
+
: undefined;
|
|
41
|
+
if (skill === undefined) {
|
|
42
|
+
const { skill: _skill, ...rest } = stage;
|
|
43
|
+
return rest;
|
|
44
|
+
}
|
|
45
|
+
const key = normalizeStageRelPath(stage.uses);
|
|
46
|
+
const index = byPath.get(key);
|
|
47
|
+
if (index !== undefined) {
|
|
48
|
+
const current = artifacts[index];
|
|
49
|
+
const bodySkill = current.body.skill;
|
|
50
|
+
const bodyHasSkill = typeof bodySkill === "string" && bodySkill.trim().length > 0;
|
|
51
|
+
if (!bodyHasSkill) {
|
|
52
|
+
artifacts[index] = {
|
|
53
|
+
...current,
|
|
54
|
+
body: { ...current.body, skill },
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const { skill: _skill, ...rest } = stage;
|
|
59
|
+
return rest;
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
...draft,
|
|
63
|
+
pipeline: { ...draft.pipeline, stages },
|
|
64
|
+
...(artifacts.length > 0 ? { stages: artifacts } : {}),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
18
67
|
function pipelineDocument(pipeline) {
|
|
19
68
|
const doc = {
|
|
20
69
|
id: pipeline.id,
|
|
21
|
-
stages: pipeline.stages,
|
|
70
|
+
stages: stripUsesWrapperSkill(pipeline.stages),
|
|
22
71
|
};
|
|
23
72
|
if (pipeline.agent !== undefined)
|
|
24
73
|
doc.agent = pipeline.agent;
|
|
@@ -48,9 +97,10 @@ async function fileExists(filePath) {
|
|
|
48
97
|
}
|
|
49
98
|
}
|
|
50
99
|
async function materializeDraft(draft, dir, pipelineFilename) {
|
|
100
|
+
const migrated = hoistUsesWrapperSkills(draft);
|
|
51
101
|
await mkdir(dir, { recursive: true });
|
|
52
|
-
await writeFile(path.join(dir, pipelineFilename), stringifyTargetYaml(pipelineDocument(
|
|
53
|
-
for (const stage of
|
|
102
|
+
await writeFile(path.join(dir, pipelineFilename), stringifyTargetYaml(pipelineDocument(migrated.pipeline)), "utf8");
|
|
103
|
+
for (const stage of migrated.stages ?? []) {
|
|
54
104
|
const rel = normalizeStageRelPath(stage.path);
|
|
55
105
|
const stagePath = path.join(dir, rel);
|
|
56
106
|
await mkdir(path.dirname(stagePath), { recursive: true });
|
|
@@ -185,11 +235,13 @@ function resolveDraftWriteTarget(projectRoot, input) {
|
|
|
185
235
|
error: "directory must be inside the project root",
|
|
186
236
|
};
|
|
187
237
|
}
|
|
188
|
-
const
|
|
189
|
-
if (!isPlainObject(
|
|
238
|
+
const rawDraft = input.draft;
|
|
239
|
+
if (!isPlainObject(rawDraft?.pipeline) || typeof rawDraft.pipeline.id !== "string") {
|
|
190
240
|
return { ok: false, status: 400, error: "draft.pipeline.id is required" };
|
|
191
241
|
}
|
|
192
|
-
const
|
|
242
|
+
const normalized = normalizeDraftCatalogFilenames(rawDraft, input.pipelineFilename?.trim());
|
|
243
|
+
const draft = hoistUsesWrapperSkills(normalized.draft);
|
|
244
|
+
const pipelineFilename = normalized.pipelineFilename;
|
|
193
245
|
const pipelineAbsPath = path.join(packageDirectory, path.basename(pipelineFilename));
|
|
194
246
|
const pipelineRelPath = path
|
|
195
247
|
.relative(projectRoot, pipelineAbsPath)
|
|
@@ -223,9 +275,17 @@ async function gateDraftWrite(projectRoot, draft, allowInvalid) {
|
|
|
223
275
|
findings: validation.findings,
|
|
224
276
|
};
|
|
225
277
|
}
|
|
226
|
-
|
|
278
|
+
function resolveDraftWriteFiles(projectRoot, target) {
|
|
227
279
|
const { packageDirectory, draft, pipelineAbsPath, pipelineRelPath } = target;
|
|
228
|
-
const
|
|
280
|
+
const toRel = (abs) => path.relative(projectRoot, abs).replace(/\\/g, "/");
|
|
281
|
+
const files = [
|
|
282
|
+
{
|
|
283
|
+
kind: "pipeline",
|
|
284
|
+
absPath: pipelineAbsPath,
|
|
285
|
+
relPath: pipelineRelPath,
|
|
286
|
+
content: stringifyTargetYaml(pipelineDocument(draft.pipeline)),
|
|
287
|
+
},
|
|
288
|
+
];
|
|
229
289
|
for (const stage of draft.stages ?? []) {
|
|
230
290
|
const rel = normalizeStageRelPath(stage.path);
|
|
231
291
|
if (!rel || path.isAbsolute(rel) || rel.split(/[/\\]/).includes("..")) {
|
|
@@ -244,23 +304,39 @@ async function writeDraftPackageFiles(projectRoot, target, allowInvalid) {
|
|
|
244
304
|
error: `Stage path escapes package directory (${stage.path})`,
|
|
245
305
|
};
|
|
246
306
|
}
|
|
307
|
+
files.push({
|
|
308
|
+
kind: "stage",
|
|
309
|
+
absPath: stageAbs,
|
|
310
|
+
relPath: toRel(stageAbs),
|
|
311
|
+
content: stringifyTargetYaml(stage.body),
|
|
312
|
+
});
|
|
247
313
|
}
|
|
314
|
+
if (draft.task) {
|
|
315
|
+
const taskAbs = path.join(packageDirectory, path.basename(draft.task.filename));
|
|
316
|
+
files.push({
|
|
317
|
+
kind: "task",
|
|
318
|
+
absPath: taskAbs,
|
|
319
|
+
relPath: toRel(taskAbs),
|
|
320
|
+
content: stringifyTargetYaml(draft.task.body),
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
return { ok: true, files };
|
|
324
|
+
}
|
|
325
|
+
async function writeDraftPackageFiles(projectRoot, target, allowInvalid) {
|
|
326
|
+
const { packageDirectory, draft, pipelineRelPath } = target;
|
|
327
|
+
const resolved = resolveDraftWriteFiles(projectRoot, target);
|
|
328
|
+
if (!resolved.ok)
|
|
329
|
+
return resolved;
|
|
248
330
|
try {
|
|
249
331
|
await mkdir(packageDirectory, { recursive: true });
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const stageAbs = path.resolve(packageDirectory, rel);
|
|
254
|
-
await mkdir(path.dirname(stageAbs), { recursive: true });
|
|
255
|
-
await writeFile(stageAbs, stringifyTargetYaml(stage.body), "utf8");
|
|
256
|
-
stagePaths.push(path.relative(projectRoot, stageAbs).replace(/\\/g, "/"));
|
|
257
|
-
}
|
|
258
|
-
let taskPath;
|
|
259
|
-
if (draft.task) {
|
|
260
|
-
const taskAbs = path.join(packageDirectory, path.basename(draft.task.filename));
|
|
261
|
-
await writeFile(taskAbs, stringifyTargetYaml(draft.task.body), "utf8");
|
|
262
|
-
taskPath = path.relative(projectRoot, taskAbs).replace(/\\/g, "/");
|
|
332
|
+
for (const file of resolved.files) {
|
|
333
|
+
await mkdir(path.dirname(file.absPath), { recursive: true });
|
|
334
|
+
await writeFile(file.absPath, file.content, "utf8");
|
|
263
335
|
}
|
|
336
|
+
const stagePaths = resolved.files
|
|
337
|
+
.filter((file) => file.kind === "stage")
|
|
338
|
+
.map((file) => file.relPath);
|
|
339
|
+
const taskPath = resolved.files.find((file) => file.kind === "task")?.relPath;
|
|
264
340
|
const loadOutcome = await loadPipelineOutcome(pipelineRelPath, {
|
|
265
341
|
cwd: projectRoot,
|
|
266
342
|
});
|
|
@@ -274,6 +350,11 @@ async function writeDraftPackageFiles(projectRoot, target, allowInvalid) {
|
|
|
274
350
|
error: loadOutcome.issues[0]?.message ?? "Failed to load written pipeline",
|
|
275
351
|
};
|
|
276
352
|
}
|
|
353
|
+
const taskId = draft.task ? taskIdFromDraftTask(draft.task) : null;
|
|
354
|
+
await reconcileWorkshopCatalogPackageDir(packageDirectory, draft.pipeline.id, taskId, {
|
|
355
|
+
pipelineBasename: path.basename(target.pipelineAbsPath),
|
|
356
|
+
taskBasename: draft.task ? path.basename(draft.task.filename) : null,
|
|
357
|
+
});
|
|
277
358
|
return {
|
|
278
359
|
ok: true,
|
|
279
360
|
pipeline,
|
|
@@ -287,10 +368,22 @@ async function writeDraftPackageFiles(projectRoot, target, allowInvalid) {
|
|
|
287
368
|
return { ok: false, status: 500, error: message };
|
|
288
369
|
}
|
|
289
370
|
}
|
|
371
|
+
async function prepareDraftPackageDirectory(target) {
|
|
372
|
+
const taskId = target.draft.task
|
|
373
|
+
? taskIdFromDraftTask(target.draft.task)
|
|
374
|
+
: null;
|
|
375
|
+
await reconcileWorkshopCatalogPackageDir(target.packageDirectory, target.draft.pipeline.id, taskId, {
|
|
376
|
+
pipelineBasename: path.basename(target.pipelineAbsPath),
|
|
377
|
+
taskBasename: target.draft.task
|
|
378
|
+
? path.basename(path.join(target.packageDirectory, path.basename(target.draft.task.filename)))
|
|
379
|
+
: null,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
290
382
|
export async function overwriteDraftPackage(projectRoot, input) {
|
|
291
383
|
const resolved = resolveDraftWriteTarget(projectRoot, input);
|
|
292
384
|
if (!resolved.ok)
|
|
293
385
|
return resolved;
|
|
386
|
+
await prepareDraftPackageDirectory(resolved.value);
|
|
294
387
|
if (!(await fileExists(resolved.value.pipelineAbsPath))) {
|
|
295
388
|
return {
|
|
296
389
|
ok: false,
|
|
@@ -307,6 +400,7 @@ export async function createDraftPackage(projectRoot, input) {
|
|
|
307
400
|
const resolved = resolveDraftWriteTarget(projectRoot, input);
|
|
308
401
|
if (!resolved.ok)
|
|
309
402
|
return resolved;
|
|
403
|
+
await prepareDraftPackageDirectory(resolved.value);
|
|
310
404
|
if (await fileExists(resolved.value.pipelineAbsPath)) {
|
|
311
405
|
return {
|
|
312
406
|
ok: false,
|
|
@@ -319,6 +413,104 @@ export async function createDraftPackage(projectRoot, input) {
|
|
|
319
413
|
return blocked;
|
|
320
414
|
return writeDraftPackageFiles(projectRoot, resolved.value, input.allowInvalid);
|
|
321
415
|
}
|
|
416
|
+
function splitLines(text) {
|
|
417
|
+
if (!text)
|
|
418
|
+
return [];
|
|
419
|
+
return (text.endsWith("\n") ? text.slice(0, -1) : text).split("\n");
|
|
420
|
+
}
|
|
421
|
+
export function countLineChanges(before, after) {
|
|
422
|
+
const a = splitLines(before);
|
|
423
|
+
const b = splitLines(after);
|
|
424
|
+
let start = 0;
|
|
425
|
+
while (start < a.length && start < b.length && a[start] === b[start])
|
|
426
|
+
start += 1;
|
|
427
|
+
let endA = a.length;
|
|
428
|
+
let endB = b.length;
|
|
429
|
+
while (endA > start && endB > start && a[endA - 1] === b[endB - 1]) {
|
|
430
|
+
endA -= 1;
|
|
431
|
+
endB -= 1;
|
|
432
|
+
}
|
|
433
|
+
const midA = a.slice(start, endA);
|
|
434
|
+
const midB = b.slice(start, endB);
|
|
435
|
+
let prev = new Uint32Array(midB.length + 1);
|
|
436
|
+
let curr = new Uint32Array(midB.length + 1);
|
|
437
|
+
for (const lineA of midA) {
|
|
438
|
+
for (let j = 1; j <= midB.length; j += 1) {
|
|
439
|
+
curr[j] =
|
|
440
|
+
lineA === midB[j - 1] ? prev[j - 1] + 1 : Math.max(prev[j], curr[j - 1]);
|
|
441
|
+
}
|
|
442
|
+
[prev, curr] = [curr, prev];
|
|
443
|
+
}
|
|
444
|
+
const common = prev[midB.length];
|
|
445
|
+
return { added: midB.length - common, removed: midA.length - common };
|
|
446
|
+
}
|
|
447
|
+
async function readExisting(filePath) {
|
|
448
|
+
try {
|
|
449
|
+
return await readFile(filePath, "utf8");
|
|
450
|
+
}
|
|
451
|
+
catch {
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
export async function planDraftPackage(projectRoot, input, options = {}) {
|
|
456
|
+
const resolved = resolveDraftWriteTarget(projectRoot, input);
|
|
457
|
+
if (!resolved.ok)
|
|
458
|
+
return resolved;
|
|
459
|
+
const writeFiles = resolveDraftWriteFiles(projectRoot, resolved.value);
|
|
460
|
+
if (!writeFiles.ok)
|
|
461
|
+
return writeFiles;
|
|
462
|
+
const files = [];
|
|
463
|
+
for (const file of writeFiles.files) {
|
|
464
|
+
const existing = await readExisting(file.absPath);
|
|
465
|
+
if (existing === null) {
|
|
466
|
+
files.push({
|
|
467
|
+
path: file.relPath,
|
|
468
|
+
kind: file.kind,
|
|
469
|
+
action: "new",
|
|
470
|
+
added: splitLines(file.content).length,
|
|
471
|
+
removed: 0,
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
else if (existing === file.content) {
|
|
475
|
+
files.push({
|
|
476
|
+
path: file.relPath,
|
|
477
|
+
kind: file.kind,
|
|
478
|
+
action: "unchanged",
|
|
479
|
+
added: 0,
|
|
480
|
+
removed: 0,
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
files.push({
|
|
485
|
+
path: file.relPath,
|
|
486
|
+
kind: file.kind,
|
|
487
|
+
action: "overwrite",
|
|
488
|
+
...countLineChanges(existing, file.content),
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
const { pipelineRelPath, packageDirectory, draft } = resolved.value;
|
|
493
|
+
const pipelineIdTaken = (options.mode ?? "create") === "create" &&
|
|
494
|
+
(options.catalogPipelines ?? []).some((listing) => listing.id === draft.pipeline.id &&
|
|
495
|
+
listing.path.replace(/\\/g, "/") !== pipelineRelPath);
|
|
496
|
+
return {
|
|
497
|
+
ok: true,
|
|
498
|
+
pipelinePath: pipelineRelPath,
|
|
499
|
+
directory: path.relative(projectRoot, packageDirectory).replace(/\\/g, "/") || ".",
|
|
500
|
+
files,
|
|
501
|
+
pipelineIdTaken,
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
export function parsePlanDraftPackageBody(body) {
|
|
505
|
+
const parsed = parseCreateDraftPackageBody(body);
|
|
506
|
+
if ("ok" in parsed)
|
|
507
|
+
return parsed;
|
|
508
|
+
const mode = isPlainObject(body) ? body.mode : undefined;
|
|
509
|
+
if (mode !== undefined && mode !== "create" && mode !== "overwrite") {
|
|
510
|
+
return { ok: false, status: 400, error: 'mode must be "create" or "overwrite"' };
|
|
511
|
+
}
|
|
512
|
+
return { ...parsed, mode: mode ?? "create" };
|
|
513
|
+
}
|
|
322
514
|
export function parseDraftPackageBody(body) {
|
|
323
515
|
if (!isPlainObject(body)) {
|
|
324
516
|
return { ok: false, status: 400, error: "JSON object body required" };
|
|
@@ -540,9 +732,10 @@ export async function loadDraftPackage(projectRoot, pipelineRelPath, options = {
|
|
|
540
732
|
draft.task = loaded.task;
|
|
541
733
|
attachedTaskPath = loaded.taskPath;
|
|
542
734
|
}
|
|
735
|
+
const migrated = hoistUsesWrapperSkills(draft);
|
|
543
736
|
return {
|
|
544
737
|
ok: true,
|
|
545
|
-
draft,
|
|
738
|
+
draft: migrated,
|
|
546
739
|
destination: { directory, pipelineFilename },
|
|
547
740
|
pipelinePath,
|
|
548
741
|
...(attachedTaskPath !== undefined ? { taskPath: attachedTaskPath } : {}),
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import type { CatalogRootKind } from "./resolveCatalogRoots.js";
|
|
1
2
|
/** Directory scanned for workshop-authored pipelines and tasks. */
|
|
2
3
|
export declare const WORKSHOP_PACKAGE_ROOT = "workshop";
|
|
3
4
|
export declare function normalizeCatalogDir(directory: string): string;
|
|
4
5
|
/** Untitled workshop saves land here so Run can see them without a new catalog edit. */
|
|
5
|
-
export declare function defaultWorkshopPackageDirectory(pipelineId: string | undefined): string;
|
|
6
|
+
export declare function defaultWorkshopPackageDirectory(pipelineId: string | undefined, catalogRootKind?: CatalogRootKind): string;
|
|
6
7
|
/**
|
|
7
8
|
* Catalog root that covers this package directory.
|
|
8
9
|
* Packages under `workshop/` share one root. Any other folder is listed itself
|